Class: Tak::Board

Inherits:
Object
  • Object
show all
Defined in:
lib/tak/board.rb

Constant Summary collapse

ROAD =

What constitutes a road-forming piece

{
  white: %w(w Cw),
  black: %w(b Cb)
}

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(size, ptn = nil) ⇒ Board

Creates a new Tak Board

Parameters:

  • size (Integer)

    Size of the board

  • ptn (Array[String]) (defaults to: nil)

    Previous game moveset to reconstruct from



19
20
21
22
23
24
25
26
27
28
29
30
# 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

Instance Attribute Details

#black_piece_setObject (readonly)

Returns the value of attribute black_piece_set.



7
8
9
# File 'lib/tak/board.rb', line 7

def black_piece_set
  @black_piece_set
end

#boardObject

Returns the value of attribute board.



4
5
6
# File 'lib/tak/board.rb', line 4

def board
  @board
end

#sizeObject

Returns the value of attribute size.



3
4
5
# File 'lib/tak/board.rb', line 3

def size
  @size
end

#white_piece_setObject (readonly)

Returns the value of attribute white_piece_set.



6
7
8
# File 'lib/tak/board.rb', line 6

def white_piece_set
  @white_piece_set
end

Instance Method Details

#bit_board(color) ⇒ Array[Array[Integer]]

Converts the current board into a bit variant in which viable road pieces for ‘color` are represented as 1s

Parameters:

  • color (Symbol)

Returns:

  • (Array[Array[Integer]])


75
76
77
78
79
# 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

#distribute_pieces(move) ⇒ Boolean

Distributes pieces from a stack move across the board

Parameters:

Returns:

  • (Boolean)


146
147
148
149
150
151
152
153
154
# 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

#empty_piece_set?Boolean

Checks to see if any piece sets are empty

Returns:

  • (Boolean)


49
50
51
# File 'lib/tak/board.rb', line 49

def empty_piece_set?
  @piece_sets.any? { |c, piece_set| piece_set.empty? }
end

#flat_countsHash[Symbol, Integer]

Gets the current visible flat counts on the board

Returns:

  • (Hash[Symbol, Integer])


35
36
37
38
39
40
41
42
43
44
# 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

#flat_winnerBoolean || Symbol

Returns who the flat winner is, or false if there is none yet

Not a fan of the Bool/Sym response, consider refactor later.

Returns:

  • (Boolean || Symbol)


58
59
60
61
62
63
64
65
66
67
# 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

#move!(ptn, color) ⇒ Boolean

Moves pieces according to PTN notation

Parameters:

  • ptn (String)

    String representation of a Tak move

  • color (Symbol)

    Which player is making the move

Returns:

  • (Boolean)

    Success status



128
129
130
131
132
133
134
135
136
137
138
139
# 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

#pieces_at(x, y) ⇒ Array[String]

Retrieves the pieces present at a coordinate

Parameters:

  • x (Integer)
  • y (Integer)

Returns:

  • (Array[String])


114
115
116
# File 'lib/tak/board.rb', line 114

def pieces_at(x, y)
  @board[x][y]
end

#place_piece(move, color) ⇒ Boolean

Places a piece at a given move coordinate

Parameters:

Returns:

  • (Boolean)

    Success of placement



163
164
165
166
167
168
169
170
171
# 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

#road_win?(color) ⇒ Boolean

Checks if a road win for ‘color` is present

Parameters:

  • color (Symbol)

    Color to check for a win

Returns:

  • (Boolean)


86
87
88
89
90
91
92
93
94
# 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

#square_owner(x, y) ⇒ Symbol

Checks which color player currently owns a square

Parameters:

  • x (Integer)
  • y (Integer)

Returns:

  • (Symbol)


102
103
104
105
106
# 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

#to_sObject

Consider moving this all out into a board formatter later. It’d be cleaner.

May Matz forgive me for my debugging code here.



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# 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