yasuhito/text2048

View on GitHub
lib/text2048/board.rb

Summary

Maintainability
A
2 hrs
Test Coverage
# encoding: utf-8

require 'text2048/monkey_patch/array'
require 'text2048/monkey_patch/hash'
require 'text2048/tile'

# This module smells of :reek:UncommunicativeModuleName
module Text2048
  # 2048 game board
  class Board
    # @return [Number] returns the current score
    attr_reader :score

    def initialize(tiles = Array.new(4, Array.new(4)), score = 0)
      @all_tiles = tiles.hashinize
      @score = score
    end

    # @!group Move

    # @!macro [new] move
    #   Move the tiles to the $0.
    #   @return [Board] returns a new board

    # @macro move
    def right
      board, score = to_a.reduce([[], @score]) do |(rows, sc), each|
        row, row_sc = each.right
        [rows << row, sc + row_sc]
      end
      new_board(board, score)
    end

    # @macro move
    def left
      flip_horizontal { right }
    end

    # @macro move
    def up
      transpose { left }
    end

    # @macro move
    def down
      transpose { right }
    end

    # @!endgroup

    # @!group Tiles

    # @return [Array<Tile>] the list of tiles
    def tiles
      @all_tiles.select { |_key, each| each.to_i > 0 }
    end

    # @return [Array] the list of +[row, col]+ of the merged tiles
    def merged_tiles
      find_tiles :merged
    end

    # @return [Array] the list of +[row, col]+ of the newly generated tiles
    def generated_tiles
      find_tiles :generated
    end

    # Need to generate a new tile?
    # @param other [Board] the previous {Board} object
    # @return [Boolean] generate a new tile?
    def generate?(other)
      to_a != other.to_a
    end

    # Generates a new tile
    # @return [Board] a new board
    def generate
      tiles = @all_tiles.dup
      tiles[sample_empty_tile] = Tile.new(rand < 0.9 ? 2 : 4, :generated)
      new_board(tiles, @score)
    end

    # @!endgroup

    # @!group Win/Lose

    def win?
      @all_tiles.any? { |_key, each| each.to_i >= 2048 }
    end

    def lose?
      right.left.up.down.tiles.size == 4 * 4
    end

    # @!endgroup

    # @!group Conversion

    # @return [Array] a 2D array of tiles.
    def to_a
      [0, 1, 2, 3].map { |each| row(each) }
    end

    # @!endgroup

    private

    def flip_horizontal(&block)
      board = flipped_board.instance_eval(&block)
      new_board(board.to_a.map(&:reverse), board.score)
    end

    def flipped_board
      new_board(to_a.map(&:reverse), @score)
    end

    def transpose(&block)
      board = transposed_board.instance_eval(&block)
      new_board(board.to_a.transpose, board.score)
    end

    def transposed_board
      new_board(to_a.transpose, @score)
    end

    def empty_tiles
      @all_tiles.select { |_key, each| each.to_i == 0 }
    end

    def sample_empty_tile
      fail if empty_tiles.empty?
      empty_tiles.keys.shuffle.first
    end

    def new_board(tiles, score)
      self.class.new(tiles, score)
    end

    def find_tiles(status)
      @all_tiles.select { |_key, each| each.status == status }.keys
    end

    def row(index)
      [index].product([0, 1, 2, 3]).map { |each| @all_tiles[each] }
    end
  end
end