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