Class: Qi
- Inherits:
-
Object
- Object
- Qi
- Defined in:
- lib/qi.rb,
lib/qi/board.rb,
lib/qi/hands.rb,
lib/qi/styles.rb
Overview
A minimal, format-agnostic library for representing positions in two-player, turn-based board games.
Qi models the components of a position as defined by the Sashité Game Protocol:
- Board — a flat array in row-major order (1D, 2D, or 3D) where each element is either empty (+nil+) or occupied by a piece (+String+).
- Hands — piece-to-count hashes (String keys, Integer values) for each player.
- Styles — one style
Stringper player side. - Turn — which player is active (+:first+ or
:second).
Pieces and styles must be String values. Non-string inputs are
rejected at the boundary to avoid per-operation coercion overhead.
All returned internal state is frozen. Callers cannot mutate positions through accessors.
Construction
A position is constructed from the board shape and player styles.
The board starts empty (all squares nil), both hands start empty,
and the turn starts as :first.
pos = Qi.new([8, 8], first_player_style: "C", second_player_style: "c")
Accessors
Use board, first_player_hand, second_player_hand, turn,
first_player_style, second_player_style, and shape to read
field values. Accessors return frozen internal state.
Transformations
Use board_diff, first_player_hand_diff, second_player_hand_diff,
and toggle to derive new positions. Transformation methods return
a new Qi instance and can be chained:
pos2 = pos.board_diff(12 => nil, 28 => "C:P").first_player_hand_diff("c:p": 1).toggle
Defined Under Namespace
Constant Summary collapse
- MAX_DIMENSIONS =
Board::MAX_DIMENSIONS
- MAX_DIMENSION_SIZE =
Board::MAX_DIMENSION_SIZE
- MAX_SQUARE_COUNT =
Board::MAX_SQUARE_COUNT
- MAX_PIECE_BYTESIZE =
Board::MAX_PIECE_BYTESIZE
- MAX_STYLE_BYTESIZE =
Styles::MAX_STYLE_BYTESIZE
Instance Method Summary collapse
-
#board ⇒ Array<String, nil>
Returns the board as a flat array in row-major order.
-
#board_diff(**squares) ⇒ Qi
Returns a new position with modified squares on the board.
-
#first_player_hand ⇒ Hash{String => Integer}
Returns the pieces held by the first player.
-
#first_player_hand_diff(**pieces) ⇒ Qi
Returns a new position with the first player's hand modified.
-
#first_player_style ⇒ String
Returns the first player's style.
-
#initialize(shape, first_player_style:, second_player_style:) ⇒ Qi
constructor
Creates a validated position with an empty board.
-
#inspect ⇒ String
Returns a developer-friendly string representation.
-
#second_player_hand ⇒ Hash{String => Integer}
Returns the pieces held by the second player.
-
#second_player_hand_diff(**pieces) ⇒ Qi
Returns a new position with the second player's hand modified.
-
#second_player_style ⇒ String
Returns the second player's style.
-
#shape ⇒ Array<Integer>
Returns the board dimensions.
-
#to_nested ⇒ Array
Returns the board as a nested array matching the shape.
-
#toggle ⇒ Qi
Returns a new position with the active player swapped.
-
#turn ⇒ Symbol
Returns the active player's side.
Constructor Details
#initialize(shape, first_player_style:, second_player_style:) ⇒ Qi
Creates a validated position with an empty board.
The board starts with all squares empty (+nil+), both hands empty,
and the turn set to :first. Styles must be String values.
81 82 83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/qi.rb', line 81 def initialize(shape, first_player_style:, second_player_style:) @square_count = Board.validate_shape(shape) @first_player_style = Styles.validate(:first, first_player_style) @second_player_style = Styles.validate(:second, second_player_style) @shape = shape.dup.freeze @board = ::Array.new(@square_count).freeze @first_hand = {}.freeze @second_hand = {}.freeze @turn = :first @board_piece_count = 0 @first_hand_count = 0 @second_hand_count = 0 end |
Instance Method Details
#board ⇒ Array<String, nil>
Returns the board as a flat array in row-major order.
Each element is nil (empty square) or a String (a piece).
The returned array is frozen. Use to_nested when a nested
structure is needed.
109 110 111 |
# File 'lib/qi.rb', line 109 def board @board end |
#board_diff(**squares) ⇒ Qi
Returns a new position with modified squares on the board.
Accepts keyword arguments where each key is a flat index (Integer,
0-based, row-major order) and each value is a piece (+String+) or
nil (empty square).
188 189 190 191 192 193 194 195 196 197 |
# File 'lib/qi.rb', line 188 def board_diff(**squares) new_board, new_board_piece_count = Board.apply_diff( @board, @square_count, @board_piece_count, squares ) validate_cardinality(new_board_piece_count + @first_hand_count + @second_hand_count) derive(new_board, @first_hand, @second_hand, @turn, new_board_piece_count, @first_hand_count, @second_hand_count) end |
#first_player_hand ⇒ Hash{String => Integer}
Returns the pieces held by the first player.
116 117 118 |
# File 'lib/qi.rb', line 116 def first_player_hand @first_hand end |
#first_player_hand_diff(**pieces) ⇒ Qi
Returns a new position with the first player's hand modified.
Accepts keyword arguments where each key is a piece identifier and each value is an integer delta: positive adds copies, negative removes. A delta of zero is a no-op.
213 214 215 216 217 218 219 220 |
# File 'lib/qi.rb', line 213 def first_player_hand_diff(**pieces) new_hand, new_count = Hands.apply_diff(@first_hand, @first_hand_count, pieces) validate_cardinality(@board_piece_count + new_count + @second_hand_count) derive(@board, new_hand, @second_hand, @turn, @board_piece_count, new_count, @second_hand_count) end |
#first_player_style ⇒ String
Returns the first player's style.
137 138 139 |
# File 'lib/qi.rb', line 137 def first_player_style @first_player_style end |
#inspect ⇒ String
Returns a developer-friendly string representation.
The format is not stable and should not be parsed.
265 266 267 |
# File 'lib/qi.rb', line 265 def inspect "#<#{self.class} shape=#{@shape.inspect} turn=#{@turn.inspect}>" end |
#second_player_hand ⇒ Hash{String => Integer}
Returns the pieces held by the second player.
123 124 125 |
# File 'lib/qi.rb', line 123 def second_player_hand @second_hand end |
#second_player_hand_diff(**pieces) ⇒ Qi
Returns a new position with the second player's hand modified.
Accepts keyword arguments where each key is a piece identifier and each value is an integer delta: positive adds copies, negative removes. A delta of zero is a no-op.
236 237 238 239 240 241 242 243 |
# File 'lib/qi.rb', line 236 def second_player_hand_diff(**pieces) new_hand, new_count = Hands.apply_diff(@second_hand, @second_hand_count, pieces) validate_cardinality(@board_piece_count + @first_hand_count + new_count) derive(@board, @first_hand, new_hand, @turn, @board_piece_count, @first_hand_count, new_count) end |
#second_player_style ⇒ String
Returns the second player's style.
144 145 146 |
# File 'lib/qi.rb', line 144 def second_player_style @second_player_style end |
#shape ⇒ Array<Integer>
Returns the board dimensions.
151 152 153 |
# File 'lib/qi.rb', line 151 def shape @shape end |
#to_nested ⇒ Array
Returns the board as a nested array matching the shape.
This is an O(n) operation intended for display or serialization, not for the hot path.
168 169 170 |
# File 'lib/qi.rb', line 168 def to_nested Board.to_nested(@board, @shape) end |
#toggle ⇒ Qi
Returns a new position with the active player swapped.
All other fields (board, hands, styles) are preserved unchanged.
255 256 257 258 |
# File 'lib/qi.rb', line 255 def toggle derive(@board, @first_hand, @second_hand, other_turn, @board_piece_count, @first_hand_count, @second_hand_count) end |
#turn ⇒ Symbol
Returns the active player's side.
130 131 132 |
# File 'lib/qi.rb', line 130 def turn @turn end |