Skip to content

Latest commit

 

History

History
636 lines (527 loc) · 14.9 KB

text2048.org

File metadata and controls

636 lines (527 loc) · 14.9 KB

Text2048

初期化

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

タイルの移動

# @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
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

# @macro move
def down
  transpose { right }
end

勝ち負けの判定

すべてのタイルの中で値が 2048 以上のものがあれば勝ち.

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

上下左右に動かしてみて, タイルが一つも消えずに 16 個残っていたら手詰まり.

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

盤の状態

すべてのタイル

# @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

ソースコード

# 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

    <<board_initialize>>

    # @!group Move

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

    <<board_right>>

    <<board_left>>

    <<board_up>>

    <<board_down>>

    # @!endgroup

    # @!group Tiles

    <<board_tiles>>

    <<board_merged_tiles>>

    <<board_generated_tiles>>

    <<board_generate_q>>

    <<board_generate>>

    # @!endgroup

    # @!group Win/Lose

    <<board_win_q>>

    <<board_lose_q>>

    # @!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

    <<board_transpose>>

    <<board_transposed_board>>

    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

テストコード

# encoding: utf-8

require 'text2048'

describe Text2048::Board do
  Given(:board) do
    initial_tiles ? Text2048::Board.new(initial_tiles) : Text2048::Board.new
  end
  Invariant { board.score == 0 }
  Invariant { board.generated_tiles.empty? }
  Invariant { board.merged_tiles.empty? }

  context 'with no arguments' do
    Given(:initial_tiles) {}

    describe '#tiles' do
      When(:tiles) { board.tiles }
      Then { tiles.empty? }
    end

    describe '#generate' do
      When(:new_board) { board.generate }
      Then { new_board.generated_tiles.size == 1 }
    end

    describe '#generate?' do
      context 'with empty board' do
        Given(:other) { Text2048::Board.new }
        When(:result) { board.generate?(other) }
        Then { result == false }
      end
    end

    describe '#win?' do
      When(:result) { board.win? }
      Then { result == false }
    end

    describe '#lose?' do
      When(:result) { board.lose? }
      Then { result == false }
    end

    describe '#right' do
      When(:result) { board.right }

      Then do
        result.to_a == [[nil, nil, nil, nil],
                        [nil, nil, nil, nil],
                        [nil, nil, nil, nil],
                        [nil, nil, nil, nil]]
      end
      And { result.score == 0 }
    end

    describe '#left' do
      When(:result) { board.left }

      Then do
        result.to_a == [[nil, nil, nil, nil],
                        [nil, nil, nil, nil],
                        [nil, nil, nil, nil],
                        [nil, nil, nil, nil]]
      end
      And { result.score == 0 }
    end

    describe '#up' do
      When(:result) { board.up }

      Then do
        result.to_a == [[nil, nil, nil, nil],
                        [nil, nil, nil, nil],
                        [nil, nil, nil, nil],
                        [nil, nil, nil, nil]]
      end
      And { result.score == 0 }
    end

    describe '#down' do
      When(:result) { board.down }

      Then do
        result.to_a == [[nil, nil, nil, nil],
                        [nil, nil, nil, nil],
                        [nil, nil, nil, nil],
                        [nil, nil, nil, nil]]
      end
      And { result.score == 0 }
    end

    describe '#to_a' do
      When(:result) { board.to_a }
      Then do
        result == [[nil, nil, nil, nil],
                   [nil, nil, nil, nil],
                   [nil, nil, nil, nil],
                   [nil, nil, nil, nil]]
      end
    end
  end

  context 'with four 2s in diagonal' do
    Given(:initial_tiles) do
      [[2, nil, nil, nil],
       [nil, 2, nil, nil],
       [nil, nil, 2, nil],
       [nil, nil, nil, 2]]
    end

    describe '#tiles' do
      When(:tiles) { board.tiles }
      Then { board.tiles.size == 4 }
      And { board.tiles[[0, 0]] == 2 }
      And { board.tiles[[1, 1]] == 2 }
      And { board.tiles[[2, 2]] == 2 }
      And { board.tiles[[3, 3]] == 2 }
    end

    describe '#generate' do
      When(:new_board) { board.generate }
      Then { new_board.generated_tiles.size == 1 }
    end

    describe '#generate?' do
      context 'with empty board' do
        Given(:other) { Text2048::Board.new }
        When(:result) { board.generate?(other) }
        Then { result == true }
      end
    end

    describe '#win?' do
      When(:result) { board.win? }
      Then { result == false }
    end

    describe '#lose?' do
      When(:result) { board.lose? }
      Then { result == false }
    end

    describe '#right' do
      When(:result) { board.right }

      Then do
        result.to_a == [[nil, nil, nil, 2],
                        [nil, nil, nil, 2],
                        [nil, nil, nil, 2],
                        [nil, nil, nil, 2]]
      end
      And { result.merged_tiles.empty? }
      And { result.score == 0 }
    end

    describe '#left' do
      When(:result) { board.left }

      Then do
        result.to_a == [[2, nil, nil, nil],
                        [2, nil, nil, nil],
                        [2, nil, nil, nil],
                        [2, nil, nil, nil]]
      end
      And { result.merged_tiles.empty? }
      And { result.score == 0 }
    end

    describe '#up' do
      When(:result) { board.up }

      Then do
        result.to_a == [[2, 2, 2, 2],
                        [nil, nil, nil, nil],
                        [nil, nil, nil, nil],
                        [nil, nil, nil, nil]]
      end
      And { result.merged_tiles.empty? }
      And { result.score == 0 }
    end

    describe '#down' do
      When(:result) { board.down }

      Then do
        result.to_a == [[nil, nil, nil, nil],
                        [nil, nil, nil, nil],
                        [nil, nil, nil, nil],
                        [2, 2, 2, 2]]
      end
      And { result.merged_tiles.empty? }
      And { result.score == 0 }
    end

    describe '#to_a' do
      When(:result) { board.to_a }
      Then do
        result == [[2, nil, nil, nil],
                   [nil, 2, nil, nil],
                   [nil, nil, 2, nil],
                   [nil, nil, nil, 2]]
      end
    end
  end

  context 'with six 2s that can be merged' do
    Given(:initial_tiles) do
      [[2, nil, 2, nil],
       [nil, 2, nil, nil],
       [nil, 2, nil, 2],
       [nil, nil, nil, 2]]
    end

    describe '#tiles' do
      When(:tiles) { board.tiles }
      Then { board.tiles.size == 6 }
      And { board.tiles[[0, 0]] == 2 }
      And { board.tiles[[0, 2]] == 2 }
      And { board.tiles[[1, 1]] == 2 }
      And { board.tiles[[2, 1]] == 2 }
      And { board.tiles[[2, 3]] == 2 }
      And { board.tiles[[3, 3]] == 2 }
    end

    describe '#generate' do
      When(:new_board) { board.generate }
      Then { new_board.generated_tiles.size == 1 }
    end

    describe '#generate?' do
      context 'with empty board' do
        Given(:other) { Text2048::Board.new }
        When(:result) { board.generate?(other) }
        Then { result == true }
      end
    end

    describe '#win?' do
      When(:result) { board.win? }
      Then { result == false }
    end

    describe '#lose?' do
      When(:result) { board.lose? }
      Then { result == false }
    end

    describe '#right' do
      When(:result) { board.right }

      Then do
        result.to_a == [[nil, nil, nil, 4],
                        [nil, nil, nil, 2],
                        [nil, nil, nil, 4],
                        [nil, nil, nil, 2]]
      end
      And { result.to_a[0][3].merged? }
      And { result.to_a[2][3].merged? }
      And { result.merged_tiles == [[0, 3], [2, 3]] }
      And { result.score == 8 }
    end

    describe '#left' do
      When(:result) { board.left }

      Then do
        result.to_a == [[4, nil, nil, nil],
                        [2, nil, nil, nil],
                        [4, nil, nil, nil],
                        [2, nil, nil, nil]]
      end
      And { result.to_a[0][0].merged? }
      And { result.to_a[2][0].merged? }
      And { result.merged_tiles == [[0, 0], [2, 0]] }
      And { result.score == 8 }
    end

    describe '#up' do
      When(:result) { board.up }

      Then do
        result.to_a == [[2, 4, 2, 4],
                        [nil, nil, nil, nil],
                        [nil, nil, nil, nil],
                        [nil, nil, nil, nil]]
      end
      And { result.to_a[0][1].merged? }
      And { result.to_a[0][3].merged? }
      And { result.merged_tiles == [[0, 1], [0, 3]] }
      And { result.score == 8 }
    end

    describe '#down' do
      When(:result) { board.down }

      Then do
        result.to_a == [[nil, nil, nil, nil],
                        [nil, nil, nil, nil],
                        [nil, nil, nil, nil],
                        [2, 4, 2, 4]]
      end
      And { result.to_a[3][1].merged? }
      And { result.to_a[3][3].merged? }
      And { result.merged_tiles == [[3, 1], [3, 3]] }
      And { result.score == 8 }
    end

    describe '#to_a' do
      When(:result) { board.to_a }
      Then do
        result == [[2, nil, 2, nil],
                   [nil, 2, nil, nil],
                   [nil, 2, nil, 2],
                   [nil, nil, nil, 2]]
      end
    end
  end

  context 'with one 2048 tile' do
    Given(:initial_tiles) do
      [[nil, nil, nil, nil],
       [nil, nil, nil, nil],
       [nil, 2048, nil, nil],
       [nil, nil, nil, nil]]
    end

    describe '#win?' do
      When(:result) { board.win? }
      Then { result == true }
    end

    describe '#lose?' do
      When(:result) { board.lose? }
      Then { result == false }
    end
  end

  context 'with 16 tiles which cannot be merged' do
    Given(:initial_tiles) do
      [[2, 4, 8, 16],
       [4, 8, 16, 32],
       [8, 16, 32, 64],
       [16, 32, 64, 128]]
    end

    describe '#tiles' do
      When(:result) { board.tiles }
      Then { result.size == 16 }
    end

    describe '#generate' do
      When(:result) { board.generate }
      Then { result == Failure(RuntimeError) }
    end

    describe '#win?' do
      When(:result) { board.win? }
      Then { result == false }
    end

    describe '#lose?' do
      When(:result) { board.lose? }
      Then { result == true }
    end

    describe '#right' do
      When(:result) { board.right }
      Then { result.to_a == board.to_a }
    end

    describe '#left' do
      When(:result) { board.left }
      Then { result.to_a == board.to_a }
    end

    describe '#up' do
      When(:result) { board.up }
      Then { result.to_a == board.to_a }
    end

    describe '#down' do
      When(:result) { board.down }
      Then { result.to_a == board.to_a }
    end

    describe '#to_a' do
      When(:result) { board.to_a }
      Then do
        result == [[2, 4, 8, 16],
                   [4, 8, 16, 32],
                   [8, 16, 32, 64],
                   [16, 32, 64, 128]]
      end
    end
  end
end