class Tak::Board
Constants
- ROAD
What constitutes a road-forming piece
Attributes
Public Class Methods
@param size [Integer] Size of the board @param ptn [Array] Previous game moveset to reconstruct from
# File lib/tak/board.rb, line 19 def initialize(size, ptn = nil) @size = size @board = generate_board(ptn) @white_piece_set = Tak::PieceSet.new(board_size: size) @black_piece_set = Tak::PieceSet.new(board_size: size) @piece_sets = { white: white_piece_set, black: black_piece_set } end
Public Instance Methods
Converts the current board into a bit variant in which viable road pieces for `color` are represented as 1s
@param color [Symbol]
@return [Array[Array]]
# File lib/tak/board.rb, line 75 def bit_board(color) @board.map { |row| row.map { |cell| ROAD[color].include?(cell.last) ? 1 : 0 } } end
Distributes pieces from a stack move across the board
@param move [Tak::Move]
@return [Boolean]
# File lib/tak/board.rb, line 146 def distribute_pieces(move) stack = pieces_at(*move.origin).pop(move.size) move.coordinates.each do |(x, y)| @board[x][y].push(stack.pop) end true end
Checks to see if any piece sets are empty
@return [Boolean]
# File lib/tak/board.rb, line 49 def empty_piece_set? @piece_sets.any? { |c, piece_set| piece_set.empty? } end
Gets the current visible flat counts on the board
@return [Hash[Symbol, Integer]]
# File lib/tak/board.rb, line 35 def flat_counts count_hash = Hash.new { |h,k| h[k] = 0 } @board.each_with_object(count_hash) do |row, counts| row.each do |cell| counts[:white] += 1 if cell.last == 'w' counts[:black] += 1 if cell.last == 'b' end end end
Returns who the flat winner is, or false if there is none yet
Not a fan of the Bool/Sym response, consider refactor later.
@return [Boolean || Symbol]
# File lib/tak/board.rb, line 58 def flat_winner return false unless empty_piece_set? white_flats, black_flats = flat_counts.values case white_flats <=> black_flats when 0 then :tie when 1 then :white when -1 then :black end end
Moves pieces according to PTN
notation
@param ptn [String]
String representation of a Tak move
@param color [Symbol]
Which player is making the move
@return [Boolean]
Success status
# File lib/tak/board.rb, line 128 def move!(ptn, color) move = Tak::Move.new(ptn, self, color) return false unless move.valid? && square_owner(*move.origin) case move.type when 'movement' distribute_pieces(move) when 'placement' place_piece(move, color) end end
Retrieves the pieces present at a coordinate
@param x [Integer] @param y [Integer]
@return [Array]
# File lib/tak/board.rb, line 114 def pieces_at(x, y) @board[x][y] end
Places a piece at a given move coordinate
@param move [Tak::Move] @param color [Symbol]
@return [Boolean]
Success of placement
# File lib/tak/board.rb, line 163 def place_piece(move, color) square = pieces_at(*move.origin) return false unless square.empty? && @piece_sets[color].remove(move.piece_type) square.push(move.piece) true end
Checks if a road win for `color` is present
@param color [Symbol] Color to check for a win
@return [Boolean]
# File lib/tak/board.rb, line 86 def road_win?(color) current_bit_board = bit_board(color) # Begin traversing through the board for potential roads. path_search(0, 0, nil, current_bit_board) || !!@size.times.find { |n| path_search(1, n, :horizontal, current_bit_board) || path_search(n, 1, :vertical, current_bit_board) } end
Checks which color player currently owns a square
@param x [Integer] @param y [Integer]
@return [Symbol]
# File lib/tak/board.rb, line 102 def square_owner(x, y) return true if @board[x][y].empty? @board[x][y].last == 'b' ? :black : :white end
Consider moving this all out into a board formatter later. It'd be cleaner.
May Matz forgive me for my debugging code here.
# File lib/tak/board.rb, line 176 def to_s max_size = board.flatten(1).max.join(' ').size + 1 counts = flat_counts.map { |c, i| set = @piece_sets[c] "#{c}: #{i} flats, #{set.flats} remaining pieces, #{set.capstones} remaining capstones" }.join("\n") board_state = board.reverse.map.with_index { |row, i| row_head = " #{size - i} " row_head + row.map { |cell| "[%#{max_size}s]" % cell.join(' ') }.join(' ') }.join("\n") footer = ('a'..'h').take(size).map { |c| "%#{max_size + 2}s" % c }.join(' ') "#{counts}\n\n#{board_state}\n #{footer}" end
Private Instance Methods
# File lib/tak/board.rb, line 263 def generate_board(ptn) return Array.new(size) { Array.new(size) { [] } } unless ptn end
Checks whether a given coordinate pair is out of bounds
@param x [Integer] @param y [Integer]
@return [Boolean]
# File lib/tak/board.rb, line 205 def out_of_bounds?(x,y) x < 0 || y < 0 || x > @size - 1 || y > @size - 1 end
Does a DFS variant to locate a potential road.
@param x [Integer] Start X coordinate @param y [Integer] Start Y coordinate @param direction [Symbol] Direction the search completes on
@param bit_board
[Array[Array]] Board
to parse against @param traversed [Array[Array]] Traversal tracker
@return [Boolean] Whether or not a road was round
# File lib/tak/board.rb, line 219 def path_search(x, y, direction, bit_board, traversed = visited_board) return false if out_of_bounds?(x,y) || traversed[x][y] piece_value = bit_board[x][y] return false if piece_value == 0 # Non-road piece return true if road_end?(direction, x, y) traversed[x][y] = true # Recurse in all four directions. While this may retrace steps it is # necessary as roads in Tak can curve wildly. path_search(x + 1, y, direction, bit_board, traversed) || path_search(x - 1, y, direction, bit_board, traversed) || path_search(x, y + 1, direction, bit_board, traversed) || path_search(x, y - 1, direction, bit_board, traversed) end
Checks to see if a given x y coordinate pair is counted as a road win by being on the appropriate opposite end from the starting direction of the road.
@param direction [Symbol] Starting direction of the road @param x [Integer] @param y [Integer]
@return [Boolean]
# File lib/tak/board.rb, line 245 def road_end?(direction, x, y) case direction when :horizontal x == @size - 1 when :vertical y == @size - 1 else x == @size - 1 || y == @size - 1 end end
Creates a board to mark previous traversals of the path search
@return [Array[Array]]
# File lib/tak/board.rb, line 259 def visited_board Array.new(@size) { Array.new(@size, false) } end