Class: Sashite::Pcn::Game
- Inherits:
-
Object
- Object
- Sashite::Pcn::Game
- Defined in:
- lib/sashite/pcn/game.rb,
lib/sashite/pcn/game/meta.rb,
lib/sashite/pcn/game/sides.rb,
lib/sashite/pcn/game/sides/player.rb
Overview
Represents a complete game record in PCN (Portable Chess Notation) format.
A game consists of an initial position (setup), optional move sequence with time tracking, optional game status, optional draw offer tracking, optional winner declaration, optional metadata, and optional player information with time control. All instances are immutable - transformations return new instances.
All parameters are validated at initialization time. An instance of Game cannot be created with invalid data.
Defined Under Namespace
Constant Summary collapse
- ERROR_MISSING_SETUP =
Error messages
"setup is required"- ERROR_INVALID_MOVES =
"moves must be an array"- ERROR_INVALID_MOVE_FORMAT =
"each move must be [PAN string, seconds float] tuple"- ERROR_INVALID_PAN =
"invalid PAN notation in move"- ERROR_INVALID_SECONDS =
"seconds must be a non-negative number"- ERROR_INVALID_META =
"meta must be a hash"- ERROR_INVALID_SIDES =
"sides must be a hash"- ERROR_INVALID_DRAW_OFFERED_BY =
"draw_offered_by must be nil, 'first', or 'second'"- ERROR_INVALID_WINNER =
"winner must be nil, 'first', 'second', or 'none'"- STATUS_IN_PROGRESS =
Status constants
"in_progress"- VALID_DRAW_OFFERED_BY =
Valid draw_offered_by values
[nil, "first", "second"].freeze
- VALID_WINNER =
Valid winner values
[nil, "first", "second", "none"].freeze
Instance Method Summary collapse
-
#==(other) ⇒ Boolean
Compare with another game.
-
#add_move(move) ⇒ Game
Add a move to the game.
-
#decisive? ⇒ Boolean?
Check if the game had a decisive outcome (not a draw).
-
#draw_offered? ⇒ Boolean
Check if a draw offer is pending.
-
#draw_offered_by ⇒ String?
Get draw offer indicator.
-
#drawn? ⇒ Boolean
Check if the game ended in a draw.
-
#event ⇒ String?
Get event from metadata.
-
#finished? ⇒ Boolean?
Check if the game is finished.
-
#first_player ⇒ Hash?
Get first player information.
-
#first_player_time ⇒ Float
Calculate total time spent by first player.
-
#has_winner? ⇒ Boolean
Check if a winner has been determined.
-
#hash ⇒ Integer
Generate hash code.
-
#href ⇒ String?
Get href from metadata.
-
#in_progress? ⇒ Boolean?
Check if the game is in progress.
-
#initialize(setup:, moves: [], status: nil, draw_offered_by: nil, winner: nil, meta: {}, sides: {}) ⇒ Game
constructor
Create a new game instance.
-
#inspect ⇒ String
Generate debug representation.
-
#location ⇒ String?
Get location from metadata.
-
#meta ⇒ Meta
Get game metadata.
-
#move_at(index) ⇒ Array?
Get move at specified index.
-
#move_count ⇒ Integer
Get total number of moves.
-
#moves ⇒ Array<Array>
Get move sequence with time tracking.
-
#pan_at(index) ⇒ String?
Get PAN notation at specified index.
-
#round ⇒ Integer?
Get round from metadata.
-
#second_player ⇒ Hash?
Get second player information.
-
#second_player_time ⇒ Float
Calculate total time spent by second player.
-
#seconds_at(index) ⇒ Float?
Get seconds at specified index.
-
#setup ⇒ Sashite::Feen::Position
Get initial position.
-
#sides ⇒ Sides
Get player information.
-
#started_at ⇒ String?
Get started_at from metadata.
-
#status ⇒ Sashite::Cgsn::Status?
Get game status.
-
#to_h ⇒ Hash
Convert to hash representation.
-
#winner ⇒ String?
Get competitive outcome.
-
#with_draw_offered_by(player) ⇒ Game
Create new game with updated draw offer.
-
#with_meta(**new_meta) ⇒ Game
Create new game with updated metadata.
-
#with_moves(new_moves) ⇒ Game
Create new game with specified move sequence.
-
#with_status(new_status) ⇒ Game
Create new game with updated status.
-
#with_winner(new_winner) ⇒ Game
Create new game with updated winner.
Constructor Details
#initialize(setup:, moves: [], status: nil, draw_offered_by: nil, winner: nil, meta: {}, sides: {}) ⇒ Game
Create a new game instance
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
# File 'lib/sashite/pcn/game.rb', line 106 def initialize(setup:, moves: [], status: nil, draw_offered_by: nil, winner: nil, meta: {}, sides: {}) # Validate and parse setup (required) raise ::ArgumentError, ERROR_MISSING_SETUP if setup.nil? @setup = ::Sashite::Feen.parse(setup) # Validate and parse moves (optional, defaults to []) raise ::ArgumentError, ERROR_INVALID_MOVES unless moves.is_a?(::Array) @moves = validate_and_parse_moves(moves).freeze # Validate and parse status (optional) @status = status.nil? ? nil : ::Sashite::Cgsn.parse(status) # Validate draw_offered_by (optional) validate_draw_offered_by(draw_offered_by) @draw_offered_by = draw_offered_by # Validate winner (optional) validate_winner(winner) @winner = winner # Validate meta (optional) raise ::ArgumentError, ERROR_INVALID_META unless .is_a?(::Hash) = Meta.new(**.transform_keys(&:to_sym)) # Validate sides (optional) raise ::ArgumentError, ERROR_INVALID_SIDES unless sides.is_a?(::Hash) @sides = Sides.new(**sides.transform_keys(&:to_sym)) freeze end |
Instance Method Details
#==(other) ⇒ Boolean
Compare with another game
627 628 629 630 631 632 633 634 635 636 637 |
# File 'lib/sashite/pcn/game.rb', line 627 def ==(other) return false unless other.is_a?(Game) @setup.to_s == other.setup.to_s && @moves == other.moves && @status&.to_s == other.status&.to_s && @draw_offered_by == other.draw_offered_by && @winner == other.winner && == other. && @sides == other.sides end |
#add_move(move) ⇒ Game
Add a move to the game
274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 |
# File 'lib/sashite/pcn/game.rb', line 274 def add_move(move) # Validate the new move validate_move_tuple(move) new_moves = @moves + [move] self.class.new( setup: @setup.to_s, moves: new_moves, status: @status&.to_s, draw_offered_by: @draw_offered_by, winner: @winner, meta: .to_h, sides: @sides.to_h ) end |
#decisive? ⇒ Boolean?
Check if the game had a decisive outcome (not a draw)
568 569 570 571 572 |
# File 'lib/sashite/pcn/game.rb', line 568 def decisive? return nil if @winner.nil? @winner != "none" end |
#draw_offered? ⇒ Boolean
Check if a draw offer is pending
545 546 547 |
# File 'lib/sashite/pcn/game.rb', line 545 def draw_offered? !@draw_offered_by.nil? end |
#draw_offered_by ⇒ String?
Get draw offer indicator
198 199 200 |
# File 'lib/sashite/pcn/game.rb', line 198 def draw_offered_by @draw_offered_by end |
#drawn? ⇒ Boolean
Check if the game ended in a draw
581 582 583 |
# File 'lib/sashite/pcn/game.rb', line 581 def drawn? @winner == "none" end |
#event ⇒ String?
Get event from metadata
348 349 350 |
# File 'lib/sashite/pcn/game.rb', line 348 def event [:event] end |
#finished? ⇒ Boolean?
Check if the game is finished
532 533 534 535 536 |
# File 'lib/sashite/pcn/game.rb', line 532 def finished? return if @status.nil? !in_progress? end |
#first_player ⇒ Hash?
Get first player information
226 227 228 |
# File 'lib/sashite/pcn/game.rb', line 226 def first_player @sides.first end |
#first_player_time ⇒ Float
Calculate total time spent by first player
320 321 322 323 324 |
# File 'lib/sashite/pcn/game.rb', line 320 def first_player_time @moves.each_with_index.sum do |move, index| index.even? ? move.last : 0.0 end end |
#has_winner? ⇒ Boolean
Check if a winner has been determined
556 557 558 |
# File 'lib/sashite/pcn/game.rb', line 556 def has_winner? !@winner.nil? end |
#hash ⇒ Integer
Generate hash code
645 646 647 |
# File 'lib/sashite/pcn/game.rb', line 645 def hash [@setup.to_s, @moves, @status&.to_s, @draw_offered_by, @winner, , @sides].hash end |
#href ⇒ String?
Get href from metadata
388 389 390 |
# File 'lib/sashite/pcn/game.rb', line 388 def href [:href] end |
#in_progress? ⇒ Boolean?
Check if the game is in progress
520 521 522 523 524 |
# File 'lib/sashite/pcn/game.rb', line 520 def in_progress? return if @status.nil? @status.to_s == STATUS_IN_PROGRESS end |
#inspect ⇒ String
Generate debug representation
656 657 658 659 660 661 662 663 664 665 666 |
# File 'lib/sashite/pcn/game.rb', line 656 def inspect parts = ["setup=#{@setup.to_s.inspect}"] parts << "moves=#{@moves.inspect}" parts << "status=#{@status&.to_s.inspect}" if @status parts << "draw_offered_by=#{@draw_offered_by.inspect}" if @draw_offered_by parts << "winner=#{@winner.inspect}" if @winner parts << "meta=#{@meta.inspect}" unless .empty? parts << "sides=#{@sides.inspect}" unless @sides.empty? "#<#{self.class.name} #{parts.join(' ')}>" end |
#location ⇒ String?
Get location from metadata
368 369 370 |
# File 'lib/sashite/pcn/game.rb', line 368 def location [:location] end |
#move_at(index) ⇒ Array?
Get move at specified index
252 253 254 |
# File 'lib/sashite/pcn/game.rb', line 252 def move_at(index) @moves[index] end |
#move_count ⇒ Integer
Get total number of moves
262 263 264 |
# File 'lib/sashite/pcn/game.rb', line 262 def move_count @moves.length end |
#moves ⇒ Array<Array>
Get move sequence with time tracking
177 178 179 |
# File 'lib/sashite/pcn/game.rb', line 177 def moves @moves end |
#pan_at(index) ⇒ String?
Get PAN notation at specified index
297 298 299 300 |
# File 'lib/sashite/pcn/game.rb', line 297 def pan_at(index) move = @moves[index] move&.first end |
#round ⇒ Integer?
Get round from metadata
358 359 360 |
# File 'lib/sashite/pcn/game.rb', line 358 def round [:round] end |
#second_player ⇒ Hash?
Get second player information
237 238 239 |
# File 'lib/sashite/pcn/game.rb', line 237 def second_player @sides.second end |
#second_player_time ⇒ Float
Calculate total time spent by second player
332 333 334 335 336 |
# File 'lib/sashite/pcn/game.rb', line 332 def second_player_time @moves.each_with_index.sum do |move, index| index.odd? ? move.last : 0.0 end end |
#seconds_at(index) ⇒ Float?
Get seconds at specified index
309 310 311 312 |
# File 'lib/sashite/pcn/game.rb', line 309 def seconds_at(index) move = @moves[index] move&.last end |
#setup ⇒ Sashite::Feen::Position
Get initial position
147 148 149 |
# File 'lib/sashite/pcn/game.rb', line 147 def setup @setup end |
#sides ⇒ Sides
Get player information
167 168 169 |
# File 'lib/sashite/pcn/game.rb', line 167 def sides @sides end |
#started_at ⇒ String?
Get started_at from metadata
378 379 380 |
# File 'lib/sashite/pcn/game.rb', line 378 def started_at [:started_at] end |
#status ⇒ Sashite::Cgsn::Status?
Get game status
187 188 189 |
# File 'lib/sashite/pcn/game.rb', line 187 def status @status end |
#to_h ⇒ Hash
Convert to hash representation
604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 |
# File 'lib/sashite/pcn/game.rb', line 604 def to_h result = { "setup" => @setup.to_s } # Always include moves array (even if empty) result["moves"] = @moves # Include optional fields if present result["status"] = @status.to_s if @status result["draw_offered_by"] = @draw_offered_by if @draw_offered_by result["winner"] = @winner if @winner result["meta"] = .to_h unless .empty? result["sides"] = @sides.to_h unless @sides.empty? result end |
#winner ⇒ String?
Get competitive outcome
211 212 213 |
# File 'lib/sashite/pcn/game.rb', line 211 def winner @winner end |
#with_draw_offered_by(player) ⇒ Game
Create new game with updated draw offer
428 429 430 431 432 433 434 435 436 437 438 |
# File 'lib/sashite/pcn/game.rb', line 428 def with_draw_offered_by(player) self.class.new( setup: @setup.to_s, moves: @moves, status: @status&.to_s, draw_offered_by: player, winner: @winner, meta: .to_h, sides: @sides.to_h ) end |
#with_meta(**new_meta) ⇒ Game
Create new game with updated metadata
477 478 479 480 481 482 483 484 485 486 487 488 |
# File 'lib/sashite/pcn/game.rb', line 477 def (**) = .to_h.merge() self.class.new( setup: @setup.to_s, moves: @moves, status: @status&.to_s, draw_offered_by: @draw_offered_by, winner: @winner, meta: , sides: @sides.to_h ) end |
#with_moves(new_moves) ⇒ Game
Create new game with specified move sequence
498 499 500 501 502 503 504 505 506 507 508 |
# File 'lib/sashite/pcn/game.rb', line 498 def with_moves(new_moves) self.class.new( setup: @setup.to_s, moves: new_moves, status: @status&.to_s, draw_offered_by: @draw_offered_by, winner: @winner, meta: .to_h, sides: @sides.to_h ) end |
#with_status(new_status) ⇒ Game
Create new game with updated status
404 405 406 407 408 409 410 411 412 413 414 |
# File 'lib/sashite/pcn/game.rb', line 404 def with_status(new_status) self.class.new( setup: @setup.to_s, moves: @moves, status: new_status, draw_offered_by: @draw_offered_by, winner: @winner, meta: .to_h, sides: @sides.to_h ) end |
#with_winner(new_winner) ⇒ Game
Create new game with updated winner
458 459 460 461 462 463 464 465 466 467 468 |
# File 'lib/sashite/pcn/game.rb', line 458 def with_winner(new_winner) self.class.new( setup: @setup.to_s, moves: @moves, status: @status&.to_s, draw_offered_by: @draw_offered_by, winner: new_winner, meta: .to_h, sides: @sides.to_h ) end |