From 4dd8b8d68016ef464d11382624f3ea17ef2e1acf Mon Sep 17 00:00:00 2001 From: Ancient Nimbus <99920424+AncientNimbus@users.noreply.github.com> Date: Sun, 3 Aug 2025 23:30:41 +0100 Subject: [PATCH 01/13] feat: Improve PGN export messaging refactor: Optimise ChessInput towards O-78 --- .config/locale/en.yml | 7 +++++ lib/console_game/chess/game.rb | 1 + lib/console_game/chess/input/chess_input.rb | 35 +++++++++++---------- 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/.config/locale/en.yml b/.config/locale/en.yml index 59b2046..027fbde 100644 --- a/.config/locale/en.yml +++ b/.config/locale/en.yml @@ -302,9 +302,16 @@ en: * Black: %{b_player} * ♞ Ruby Chess by Ancient Nimbus ver: %{ver} export: | + %{sep} + * Your Chess session has been exported as: '%{filename}' * It can be found at the following directory: * ==> '%{dir}' + * You can also select and copy the text below: ↴ + + %{pgn_out} + + %{sep} input: alg: "Press 'enter' to switch input detection to Algebraic notation" smith: "Press 'enter' to switch input detection to Smith notation" diff --git a/lib/console_game/chess/game.rb b/lib/console_game/chess/game.rb index 2ff77a1..0f063b9 100644 --- a/lib/console_game/chess/game.rb +++ b/lib/console_game/chess/game.rb @@ -111,6 +111,7 @@ def reset_state @sides = {} setup_p1 @p2 = nil + @fen = nil end # Create new session data diff --git a/lib/console_game/chess/input/chess_input.rb b/lib/console_game/chess/input/chess_input.rb index 2a1f763..c8c8d28 100644 --- a/lib/console_game/chess/input/chess_input.rb +++ b/lib/console_game/chess/input/chess_input.rb @@ -120,28 +120,17 @@ def export(_args = []) return cmd_disabled if level.nil? save_moves - - dir, filename, output = PgnExport.export_session(level.session).values_at(:path, :filename, :export_data) - print_msg(s("cmd.export", { filename: [filename, "gold"], dir: [dir, "gold"] })) - puts output + dir, filename, pgn_out = PgnExport.export_session(level.session).values_at(:path, :filename, :export_data) + print_msg(s("cmd.export", { + filename: [filename, "gold"], dir: [dir, "gold"], sep: ["PGN".center(80, "=")], pgn_out: + })) end # Change input mode to detect Smith Notation | command pattern: `smith` - def smith(_args = []) - return cmd_disabled if level.nil? - - print_msg(s("cmd.input.smith"), pre: "* ") - self.input_scheme = smith_reg - self.input_parser = SMITH_PARSER - end + def smith(_args = []) = switch_notation(:smith) # Change input mode to detect Algebraic Notation | command pattern: `alg` - def alg(_args = []) - return cmd_disabled if level.nil? - - print_msg(s("cmd.input.alg"), pre: "* ") - self.input_scheme = alg_reg - end + def alg(_args = []) = switch_notation(:alg) # Update board settings | command pattern: `board` # @example usage example @@ -161,6 +150,18 @@ def board(args = []) # == Utilities == + # Switch notation depending on user input + # @param mode [Symbol] expects :smith or :alg + def switch_notation(mode) + return cmd_disabled if level.nil? + + self.input_scheme = case mode + when :smith then smith_reg + when :alg then alg_reg + end + print_msg(s("cmd.input.#{mode}"), pre: "* ") + end + # Create regexp patterns for various input modes def notation_patterns_builder @alg_reg = regexp_algebraic From c4e8c1d3a2e81477c9b3c87a044a7cfb0a57af61 Mon Sep 17 00:00:00 2001 From: Ancient Nimbus <99920424+AncientNimbus@users.noreply.github.com> Date: Sun, 3 Aug 2025 23:44:37 +0100 Subject: [PATCH 02/13] perf: Remove instance variable: input_parser --- lib/console_game/chess/input/chess_input.rb | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/lib/console_game/chess/input/chess_input.rb b/lib/console_game/chess/input/chess_input.rb index c8c8d28..7ed36c6 100644 --- a/lib/console_game/chess/input/chess_input.rb +++ b/lib/console_game/chess/input/chess_input.rb @@ -16,7 +16,7 @@ class ChessInput < Input include SmithNotation include AlgebraicNotation - attr_accessor :input_scheme, :input_parser + attr_accessor :input_scheme attr_reader :alg_reg, :smith_reg, :level, :active_side, :chess_manager # @param game_manager [GameManager] @@ -26,7 +26,6 @@ def initialize(game_manager = nil, chess_manager = nil) @chess_manager = chess_manager notation_patterns_builder @input_scheme = smith_reg - @input_parser = SMITH_PARSER end # Store active level object @@ -50,7 +49,7 @@ def turn_action(player) # @param player [ChessPlayer] def make_a_move(player) input = ask(s("level.action2"), reg: SMITH_PATTERN[:gp1], input_type: :custom, empty: true) - ops = case input.scan(input_parser) + ops = case input.scan(SMITH_PARSER) in [new_pos] then { type: :move_piece, args: [new_pos] } else { type: :invalid_input, args: [input] } end @@ -169,10 +168,8 @@ def notation_patterns_builder end # Setup input commands - def setup_commands - super.merge({ "save" => method(:save), "load" => method(:load), "export" => method(:export), - "smith" => method(:smith), "alg" => method(:alg), "board" => method(:board) }) - end + def setup_commands = super.merge({ "save" => method(:save), "load" => method(:load), "export" => method(:export), + "smith" => method(:smith), "alg" => method(:alg), "board" => method(:board) }) # Print command is disabled at this stage def cmd_disabled = print_msg(s("cmd.disabled"), pre: D_MSG[:warn_prefix]) From 8194ecc2af9790071fa45b5508b946d7aece936c Mon Sep 17 00:00:00 2001 From: Ancient Nimbus <99920424+AncientNimbus@users.noreply.github.com> Date: Mon, 4 Aug 2025 11:25:14 +0100 Subject: [PATCH 03/13] refactor: Optimise Game class --- lib/console_game/chess/game.rb | 9 +++------ lib/console_game/chess/input/chess_input.rb | 2 +- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/lib/console_game/chess/game.rb b/lib/console_game/chess/game.rb index 0f063b9..70ecf94 100644 --- a/lib/console_game/chess/game.rb +++ b/lib/console_game/chess/game.rb @@ -107,11 +107,8 @@ def end_game # Reset state def reset_state Player.player_count(0) - @player_builder = nil - @sides = {} - setup_p1 - @p2 = nil - @fen = nil + original_state = { player_builder: nil, sides: {}, p1: setup_p1, p2: nil, fen: nil } + @player_builder, @sides, @p1, @p2, @fen = original_state.values_at(:player_builder, :sides, :p1, :p2, :fen) end # Create new session data @@ -168,7 +165,7 @@ def determine_opt = p1.side == w_sym ? 1 : 2 # == Player object creation == # Setup player 1 - def setup_p1 = @p1 = create_player(user.profile[:username]) + def setup_p1 = create_player(user.profile[:username]) # Create new player builder service # @return [PlayerBuilder] diff --git a/lib/console_game/chess/input/chess_input.rb b/lib/console_game/chess/input/chess_input.rb index 7ed36c6..528b338 100644 --- a/lib/console_game/chess/input/chess_input.rb +++ b/lib/console_game/chess/input/chess_input.rb @@ -109,7 +109,7 @@ def save(_args = [], mute: false) def load(_args = []) return cmd_disabled if level.nil? - # save(mute: true) + @level = nil print_msg(s("cmd.load"), pre: "* ") chess_manager.setup_game end From 4b8e9544818aad94da569d60192af817b5f7c447 Mon Sep 17 00:00:00 2001 From: Ancient Nimbus <99920424+AncientNimbus@users.noreply.github.com> Date: Mon, 4 Aug 2025 11:30:04 +0100 Subject: [PATCH 04/13] style: Update textfile --- .config/locale/en.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.config/locale/en.yml b/.config/locale/en.yml index 027fbde..ddc3e92 100644 --- a/.config/locale/en.yml +++ b/.config/locale/en.yml @@ -272,7 +272,7 @@ en: * • [2] Player vs Computer (Level: Easy) f1a: "Please select a game mode: " f1a_err: "Invalid input, please choose between 1 or 2" - f2: "Please name Player %{count} (Default: %{name})" + f2: "Please name Player %{count} (Default: %{name}) " f2a: "Name has been updated to '%{name}'." f3: "Provide a valid FEN below, invalid FEN will cancel the operation and a new game will be setup instead." err: "Sessions not found, entering new game creation mode..." @@ -308,6 +308,7 @@ en: * It can be found at the following directory: * ==> '%{dir}' * You can also select and copy the text below: ↴ + * To import a game, visit chess site such as: https://lichess.org/paste %{pgn_out} From aec3817f4b752732cb5dac98e1ec3b32f08c50f2 Mon Sep 17 00:00:00 2001 From: Ancient Nimbus <99920424+AncientNimbus@users.noreply.github.com> Date: Mon, 4 Aug 2025 13:09:31 +0100 Subject: [PATCH 05/13] style: Unify variable names in FenImport refactor: Optimise instance variable reset flow in Game class --- lib/console_game/chess/game.rb | 4 ++-- lib/console_game/chess/level.rb | 2 +- .../chess/utilities/fen_import.rb | 8 ++++--- spec/console_game/chess/fen_export_spec.rb | 4 ++-- spec/console_game/chess/fen_import_spec.rb | 24 +++++++++---------- 5 files changed, 22 insertions(+), 20 deletions(-) diff --git a/lib/console_game/chess/game.rb b/lib/console_game/chess/game.rb index 70ecf94..6084b67 100644 --- a/lib/console_game/chess/game.rb +++ b/lib/console_game/chess/game.rb @@ -107,8 +107,8 @@ def end_game # Reset state def reset_state Player.player_count(0) - original_state = { player_builder: nil, sides: {}, p1: setup_p1, p2: nil, fen: nil } - @player_builder, @sides, @p1, @p2, @fen = original_state.values_at(:player_builder, :sides, :p1, :p2, :fen) + reset_config = { player_builder: nil, sides: {}, p1: setup_p1, p2: nil, fen: nil } + reset_config.each { |var, v| instance_variable_set("@#{var}", v) } end # Create new session data diff --git a/lib/console_game/chess/level.rb b/lib/console_game/chess/level.rb index 3bf2097..33aa979 100644 --- a/lib/console_game/chess/level.rb +++ b/lib/console_game/chess/level.rb @@ -144,7 +144,7 @@ def init_level @threats_map, @usable_pieces = Array.new(2) { PieceAnalysis.bw_arr_hash } @event_msgs = [] @turn_data, @white_turn, @castling_states, @en_passant, @half_move, @full_move = - fen_data.values_at(:turn_data, :white_turn, :castling_states, :en_passant, :half, :full) + fen_data.values_at(:turn_data, :white_turn, :castling_states, :en_passant, :half_move, :full_move) set_current_player refresh(print_turn: false) greet_player diff --git a/lib/console_game/chess/utilities/fen_import.rb b/lib/console_game/chess/utilities/fen_import.rb index cd693b6..31f9ab5 100644 --- a/lib/console_game/chess/utilities/fen_import.rb +++ b/lib/console_game/chess/utilities/fen_import.rb @@ -59,10 +59,10 @@ def parse_fen # @param fen_data [Array] expects splitted FEN as an array # @return [Array] def process_fen_data(fen_data) - fen_board, active_color, c_state, ep_state, halfmove, fullmove = fen_data + fen_board, active_color, c_state, ep_state, half_move, full_move = fen_data [ parse_piece_placement(fen_board), parse_active_color(active_color), parse_castling_str(c_state), - parse_en_passant(ep_state), parse_move_num(halfmove, :half), parse_move_num(fullmove, :full) + parse_en_passant(ep_state), parse_move_num(half_move, :half_move), parse_move_num(full_move, :full_move) ] end @@ -173,7 +173,9 @@ def ep_ghost_pos(alg_pos) = to_1d_pos(alg_pos) # @param num [String] expects a string with either half-move or full-move data # @param type [Symbol] specify the key type for the hash # @return [Hash, nil] a hash of either half-move or full-move data - def parse_move_num(num, type) = num.match?(/\A\d+\z/) && %i[half full].include?(type) ? { type => num.to_i } : nil + def parse_move_num(num, type) + num.match?(/\A\d+\z/) && %i[half_move full_move].include?(type) ? { type => num.to_i } : nil + end # Initialize chess piece via string value # @param pos [Integer] positional value diff --git a/spec/console_game/chess/fen_export_spec.rb b/spec/console_game/chess/fen_export_spec.rb index bf5cda7..92c7e31 100644 --- a/spec/console_game/chess/fen_export_spec.rb +++ b/spec/console_game/chess/fen_export_spec.rb @@ -20,7 +20,7 @@ before do turn_data, white_turn, castling_states, en_passant, half_move, full_move = - session_data.values_at(:turn_data, :white_turn, :castling_states, :en_passant, :half, :full) + session_data.values_at(:turn_data, :white_turn, :castling_states, :en_passant, :half_move, :full_move) allow(level_double).to receive_messages( fen_data: session_data, turn_data:, white_turn:, castling_states:, en_passant:, half_move:, full_move: ) @@ -41,7 +41,7 @@ mock_pawn = ConsoleGame::Chess::Pawn.new(:e5, level: level_double) allow(level_double).to receive(:fetch_piece).and_return(mock_pawn) turn_data, white_turn, castling_states, en_passant, half_move, full_move = - session_data.values_at(:turn_data, :white_turn, :castling_states, :en_passant, :half, :full) + session_data.values_at(:turn_data, :white_turn, :castling_states, :en_passant, :half_move, :full_move) allow(level_double).to receive_messages( fen_data: session_data, turn_data:, white_turn:, castling_states:, en_passant:, half_move:, full_move: ) diff --git a/spec/console_game/chess/fen_import_spec.rb b/spec/console_game/chess/fen_import_spec.rb index d75582a..379c24b 100644 --- a/spec/console_game/chess/fen_import_spec.rb +++ b/spec/console_game/chess/fen_import_spec.rb @@ -18,7 +18,7 @@ it "returns a hash with 6 internal hash data fields: turn_data, white_turn, castling_states, en_passant, half, and full" do result = fen_import_test.parse_fen(level_double, new_game_placement_w) - expect(result.keys).to eq(%i[turn_data white_turn castling_states en_passant half full]) + expect(result.keys).to eq(%i[turn_data white_turn castling_states en_passant half_move full_move]) end it "returns a hash where turn_data contains an array with 64 elements" do @@ -43,19 +43,19 @@ it "returns a hash where half-move is 0" do result = fen_import_test.parse_fen(level_double, new_game_placement_w) - expect(result[:half]).to eq(0) + expect(result[:half_move]).to eq(0) end it "returns a hash where full-move is 1" do result = fen_import_test.parse_fen(level_double, new_game_placement_w) - expect(result[:full]).to eq(1) + expect(result[:full_move]).to eq(1) end end context "when default FEN start string is used" do it "returns a hash with 6 internal hash data fields: turn_data, white_turn, castling_states, en_passant, half, and full" do result = fen_import_test.parse_fen(level_double) - expect(result.keys).to eq(%i[turn_data white_turn castling_states en_passant half full]) + expect(result.keys).to eq(%i[turn_data white_turn castling_states en_passant half_move full_move]) end it "returns a hash where turn_data contains an array with 64 elements" do @@ -80,12 +80,12 @@ it "returns a hash where half-move is 0" do result = fen_import_test.parse_fen(level_double) - expect(result[:half]).to eq(0) + expect(result[:half_move]).to eq(0) end it "returns a hash where full-move is 1" do result = fen_import_test.parse_fen(level_double) - expect(result[:full]).to eq(1) + expect(result[:full_move]).to eq(1) end end @@ -94,7 +94,7 @@ it "returns a hash of a standard new game as fallback" do result = fen_import_test.parse_fen(level_double, invalid_fen_string) - expect(result.keys).to eq(%i[turn_data white_turn castling_states en_passant half full]) + expect(result.keys).to eq(%i[turn_data white_turn castling_states en_passant half_move full_move]) end end end @@ -327,27 +327,27 @@ describe "#parse_move_num" do context "when value is a number and type is half-move" do let(:move_num) { "2" } - let(:type_is_half_move) { :half } + let(:type_is_half_move) { :half_move } it "returns a hash where key is set to half and value is converted to an integer" do result = fen_import_test.new(level_double).send(:parse_move_num, move_num, type_is_half_move) - expect(result).to eq({ half: 2 }) + expect(result).to eq({ half_move: 2 }) end end context "when value is a number and type is full-move" do let(:move_num) { "20" } - let(:type_is_full_move) { :full } + let(:type_is_full_move) { :full_move } it "returns a hash where key is set to full and value is converted to an integer" do result = fen_import_test.new(level_double).send(:parse_move_num, move_num, type_is_full_move) - expect(result).to eq({ full: 20 }) + expect(result).to eq({ full_move: 20 }) end end context "when value is not a number and type is full-move" do let(:move_num) { "ABC" } - let(:type_is_full_move) { :full } + let(:type_is_full_move) { :full_move } it "returns nil" do result = fen_import_test.new(level_double).send(:parse_move_num, move_num, type_is_full_move) From 4f09419869234d9f8c97a74eebf48e8e421d8504 Mon Sep 17 00:00:00 2001 From: Ancient Nimbus <99920424+AncientNimbus@users.noreply.github.com> Date: Mon, 4 Aug 2025 13:48:39 +0100 Subject: [PATCH 06/13] refactor: Simplify board state updates refactor: Simplify fen_data variables assignment structure refactor: enhance board analysis return structure --- lib/console_game/chess/level.rb | 14 +++++++------- lib/console_game/chess/logics/piece_analysis.rb | 10 +++++----- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/console_game/chess/level.rb b/lib/console_game/chess/level.rb index 33aa979..dbf3514 100644 --- a/lib/console_game/chess/level.rb +++ b/lib/console_game/chess/level.rb @@ -71,11 +71,7 @@ def refresh(print_turn: true) end # Board state refresher - # Generate all possible move and send it to board analysis - # @see PieceAnalysis #board_analysis - def update_board_state - @threats_map, @usable_pieces = PieceAnalysis.board_analysis(fetch_all.each(&:query_moves)) - end + def update_board_state = board_analysis.each { |var, v| instance_variable_set("@#{var}", v) } # Simulate next move - Find good moves # @param piece [ChessPiece] expects a ChessPiece object @@ -143,8 +139,7 @@ def init_level @kings = PieceAnalysis.bw_nil_hash @threats_map, @usable_pieces = Array.new(2) { PieceAnalysis.bw_arr_hash } @event_msgs = [] - @turn_data, @white_turn, @castling_states, @en_passant, @half_move, @full_move = - fen_data.values_at(:turn_data, :white_turn, :castling_states, :en_passant, :half_move, :full_move) + fen_data.each { |field, v| instance_variable_set("@#{field}", v) } set_current_player refresh(print_turn: false) greet_player @@ -187,6 +182,11 @@ def set_current_player @player, @opponent = white_turn ? [w_player, b_player] : [b_player, w_player] end + # Generate all possible move and send it to board analysis + # @see PieceAnalysis #board_analysis + # @return [Hash] + def board_analysis = PieceAnalysis.board_analysis(fetch_all.each(&:query_moves)) + # == Endgame Logics == # Handle checkmate and draw event diff --git a/lib/console_game/chess/logics/piece_analysis.rb b/lib/console_game/chess/logics/piece_analysis.rb index cb8aaa3..bee6dcb 100644 --- a/lib/console_game/chess/logics/piece_analysis.rb +++ b/lib/console_game/chess/logics/piece_analysis.rb @@ -7,9 +7,9 @@ module Chess class PieceAnalysis class << self # Analyse the board - # usable_pieces: usable pieces of the given turn - # threats_map: all blunder tile for each side - # @return [Array>] usable_pieces and threats_map + # @return [Hash] Board analysis data + # @option return [Hash>] :threats_map Threatened squares by side + # @option return [Hash>] :usable_pieces Movable piece positions by side def board_analysis(...) = new(...).board_analysis # Returns a hash with :white and :black keys to nil. @@ -33,14 +33,14 @@ def initialize(all_pieces) # Analyse the board # usable_pieces: usable pieces of the given turn # threats_map: all blunder tile for each side - # @return [Array>] usable_pieces and threats_map + # @return [Hash] Board analysis data def board_analysis threats_map, usable_pieces = Array.new(2) { PieceAnalysis.bw_arr_hash } pieces_group.each do |side, pieces| threats_map[side] = add_pos_to_blunder_tracker(pieces) usable_pieces[side] = pieces.map { |piece| piece.info unless piece.possible_moves.empty? }.compact end - [threats_map, usable_pieces] + { threats_map:, usable_pieces: } end private From f2d997b82eaf96d184e7e9c4f2d333dd7e729791 Mon Sep 17 00:00:00 2001 From: Ancient Nimbus <99920424+AncientNimbus@users.noreply.github.com> Date: Mon, 4 Aug 2025 14:20:15 +0100 Subject: [PATCH 07/13] fix: Resolve an issue related to game saving This iteration fixes an issue where fen string won't be saved if the imported session is at its endgame position (draw or checkmate) --- lib/console_game/chess/level.rb | 3 ++- spec/console_game/chess/endgame_logic_spec.rb | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/console_game/chess/level.rb b/lib/console_game/chess/level.rb index dbf3514..ca833ca 100644 --- a/lib/console_game/chess/level.rb +++ b/lib/console_game/chess/level.rb @@ -194,10 +194,11 @@ def board_analysis = PieceAnalysis.board_analysis(fetch_all.each(&:query_moves)) # @param side [Symbol, nil] player side # @return [Boolean] def handle_result(type:, side: nil) + save_turn winner = session[opposite_of(side)] kings[side].color = "#CC0000" if type == "checkmate" event_msgs << board.s("level.endgame.#{type}", { win_player: winner }) - board.print_turn(event_msgs) + board.print_turn(event_msgs[-1]) @game_ended = true end diff --git a/spec/console_game/chess/endgame_logic_spec.rb b/spec/console_game/chess/endgame_logic_spec.rb index b196d7b..b75915c 100644 --- a/spec/console_game/chess/endgame_logic_spec.rb +++ b/spec/console_game/chess/endgame_logic_spec.rb @@ -6,9 +6,13 @@ describe ConsoleGame::Chess::EndgameLogic do NimbusFileUtils.set_locale("en") let(:controller) { ConsoleGame::Chess::ChessInput.new } - let(:session) { { event: "Integration test", site: "Level and Endgame logic", date: nil, round: nil, white: "Ancient", black: "Nimbus", result: nil, mode: 1, moves: {}, fens: [] } } + let(:session) { { event: "Integration test", site: "Level and Endgame logic", date: nil, round: nil, white: "Ancient", black: "Nimbus", result: nil, mode: 1, moves: {}, fens: [], white_moves: [], black_moves: [] } } let(:sides) { { white: ConsoleGame::Chess::ChessPlayer.new("Ancient", controller, :white), black: ConsoleGame::Chess::ChessPlayer.new("Nimbus", controller, :black) } } + before do + allow(controller).to receive(:save) + end + describe "#any_checkmate?" do context "when imported FEN session will result in black being checkmate" do subject(:fen_str) { "rnbqkbnr/ppppp2p/5p2/6pQ/8/4P3/PPPP1PPP/RNB1KBNR b KQkq - 1 1" } From f65055eecd28d7d69bc5d13a307a3bac12671c87 Mon Sep 17 00:00:00 2001 From: Ancient Nimbus <99920424+AncientNimbus@users.noreply.github.com> Date: Mon, 4 Aug 2025 15:58:04 +0100 Subject: [PATCH 08/13] feat: Add error handling to PGN export (ref O-76) --- .../chess/utilities/pgn_export.rb | 39 +++++++++++++++---- spec/console_game/chess/endgame_logic_spec.rb | 1 + spec/console_game/chess/game_spec.rb | 4 +- 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/lib/console_game/chess/utilities/pgn_export.rb b/lib/console_game/chess/utilities/pgn_export.rb index f1357f1..f20f17f 100644 --- a/lib/console_game/chess/utilities/pgn_export.rb +++ b/lib/console_game/chess/utilities/pgn_export.rb @@ -36,26 +36,51 @@ def initialize(session) # @return [Hash] def export_session process_time_field + pgn_filepath export_data = to_pgn - F.write_to_disk(pgn_filepath, export_data) + F.write_to_disk(path, export_data) { path:, filename:, export_data: } end private # Create file name and return full file path - # @return [String] valid filepath def pgn_filepath name = build_name + begin + make_filename(name) + make_filepath + append_file_num(name) + rescue ArgumentError + name = handle_filename_err + retry + end + end + + # Format filename + # @param name [String] + def make_filename(name) = @filename = F.formatted_filename(name, DOT_PGN) + + # Format filepath + def make_filepath = @path = F.filepath(filename, *sub_path) + + # Append number suffix if file exist + # @param name [String] + def append_file_num(name) count = 0 - @filename = F.formatted_filename(name, DOT_PGN) - @path = F.filepath(filename, *sub_path) while F.file_exist?(path, use_filetype: false) count += 1 - @filename = F.formatted_filename("#{name}_#{count}", DOT_PGN) - @path = F.filepath(filename, *sub_path) + make_filename("#{name}_#{count}") + make_filepath end - path + end + + # Error handling when forbidden character is found in filename + # Likely due to my #formatted_filename method is a little too strict + # @return [String] default name + def handle_filename_err + puts "\n! Forbidden characters found in event name, a default filename will be used." + "chess session" end # Returns PGN ready string diff --git a/spec/console_game/chess/endgame_logic_spec.rb b/spec/console_game/chess/endgame_logic_spec.rb index b75915c..6c94fde 100644 --- a/spec/console_game/chess/endgame_logic_spec.rb +++ b/spec/console_game/chess/endgame_logic_spec.rb @@ -11,6 +11,7 @@ before do allow(controller).to receive(:save) + allow(level).to receive(:update_event_status) end describe "#any_checkmate?" do diff --git a/spec/console_game/chess/game_spec.rb b/spec/console_game/chess/game_spec.rb index bdc3c8e..a52ef2d 100644 --- a/spec/console_game/chess/game_spec.rb +++ b/spec/console_game/chess/game_spec.rb @@ -208,7 +208,7 @@ end context "when user loads a valid ended game session" do - let(:test_sessions) { { "1" => { event: "Chess Sessions Integration test", site: "Chess game menu", date: Time.new(2025, 7, 26), round: nil, white: "Ancient", black: "Nimbus", result: nil, mode: 1, moves: {}, fens: ["rnbqkbnr/ppppp2p/5p2/6pQ/8/4P3/PPPP1PPP/RNB1KBNR b KQkq - 1 1"], white_moves: [], black_moves: [] } } } + let(:test_sessions) { { "1" => { event: +"Chess Sessions Integration test", site: "Chess game menu", date: Time.new(2025, 7, 26), round: nil, white: "Ancient", black: "Nimbus", result: nil, mode: 1, moves: {}, fens: ["rnbqkbnr/ppppp2p/5p2/6pQ/8/4P3/PPPP1PPP/RNB1KBNR b KQkq - 1 1"], white_moves: [], black_moves: [] } } } before do allow($stdout).to receive(:puts) @@ -224,7 +224,7 @@ end context "when user loads a valid stalemate game session" do - let(:test_sessions) { { "1" => { event: "Chess Sessions Integration test", site: "Chess game menu", date: Time.new(2025, 7, 26), round: nil, white: "Ancient", black: "Nimbus", result: nil, mode: 1, moves: {}, fens: ["7k/5K2/6Q1/8/8/8/8/8 b - - 1 1"], white_moves: [], black_moves: [] } } } + let(:test_sessions) { { "1" => { event: +"Chess Sessions Integration test", site: "Chess game menu", date: Time.new(2025, 7, 26), round: nil, white: "Ancient", black: "Nimbus", result: nil, mode: 1, moves: {}, fens: ["7k/5K2/6Q1/8/8/8/8/8 b - - 1 1"], white_moves: [], black_moves: [] } } } before do allow($stdout).to receive(:puts) From 4917d14151df96f599314499d880fb0c64141e78 Mon Sep 17 00:00:00 2001 From: Ancient Nimbus <99920424+AncientNimbus@users.noreply.github.com> Date: Mon, 4 Aug 2025 16:02:37 +0100 Subject: [PATCH 09/13] feat(UX): Add support to show game status on load towards O-76 --- .config/locale/en.yml | 4 ++++ lib/console_game/chess/level.rb | 4 ++++ lib/console_game/chess/utilities/session_builder.rb | 9 +++++---- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/.config/locale/en.yml b/.config/locale/en.yml index ddc3e92..9aa3f9e 100644 --- a/.config/locale/en.yml +++ b/.config/locale/en.yml @@ -369,6 +369,10 @@ en: err: "FEN error, '%{fen_str}' is not a valid sequence. Starting a new game..." misc: site: "Ruby Arcade Terminal Chess by Ancient Nimbus" + status: + ongoing: "Ongoing" + draw: "Draw" + checkmate: "Checkmate" pieces: k: name: "King" diff --git a/lib/console_game/chess/level.rb b/lib/console_game/chess/level.rb index ca833ca..71e87a5 100644 --- a/lib/console_game/chess/level.rb +++ b/lib/console_game/chess/level.rb @@ -194,6 +194,7 @@ def board_analysis = PieceAnalysis.board_analysis(fetch_all.each(&:query_moves)) # @param side [Symbol, nil] player side # @return [Boolean] def handle_result(type:, side: nil) + update_event_status(type:) save_turn winner = session[opposite_of(side)] kings[side].color = "#CC0000" if type == "checkmate" @@ -202,6 +203,9 @@ def handle_result(type:, side: nil) @game_ended = true end + # Update event state + def update_event_status(type:) = session[:event].sub!(board.s("status.ongoing"), board.s("status.#{type}")) + # Add checked or checkmate marker to opponent's last move def add_check_marker side = player.side diff --git a/lib/console_game/chess/utilities/session_builder.rb b/lib/console_game/chess/utilities/session_builder.rb index 680ab3c..48bdda6 100644 --- a/lib/console_game/chess/utilities/session_builder.rb +++ b/lib/console_game/chess/utilities/session_builder.rb @@ -13,14 +13,15 @@ class SessionBuilder # @return [Array] session data def self.build_session(...) = new(...).build_session - attr_reader :sessions, :mode, :sides, :site_text + attr_reader :sessions, :mode, :sides, :site_txt, :ongoing_txt # @param game [Game] expects chess game manager object def initialize(game) @sessions = game.sessions @mode = game.mode @sides = game.sides - @site_text = game.s("misc.site") + @site_txt = game.s("misc.site") + @ongoing_txt = game.s("status.ongoing") end # Build session data @@ -32,8 +33,8 @@ def build_session session = { mode: mode, white: wp_name, black: bp_name, - event: "#{wp_name} vs #{bp_name}", - site: site_text, + event: "#{wp_name} vs #{bp_name} status-#{ongoing_txt}", + site: site_txt, date: Time.new.ceil.strftime(STR_TIME) } [id, session] end From 68937d7685ecd3ae4e5214af25078147ea087794 Mon Sep 17 00:00:00 2001 From: Ancient Nimbus <99920424+AncientNimbus@users.noreply.github.com> Date: Mon, 4 Aug 2025 16:28:32 +0100 Subject: [PATCH 10/13] test: Add integration test to test error handling --- spec/console_game/chess/game_spec.rb | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/spec/console_game/chess/game_spec.rb b/spec/console_game/chess/game_spec.rb index a52ef2d..6bf4783 100644 --- a/spec/console_game/chess/game_spec.rb +++ b/spec/console_game/chess/game_spec.rb @@ -105,12 +105,32 @@ Dir.glob("user_data/pgn_export/*.pgn").each { |f| File.delete(f) } end - it "opens level, switch input scheme to smith notation, play all the moves, export file twice and exit successfully" do + it "opens level, switch input scheme to smith notation, play all the moves, export file and exit successfully" do allow(Readline).to receive(:readline).and_return("", "", "", "1", "1", "", "", "1", "f2f4", "e7e5", "g2g4", "d8", "h4", "--info", "--export", "--exit") expect { chess_manager.start }.to raise_error(SystemExit) end end + context "when the event name contains forbidden character" do + let(:test_sessions) { { "1" => { event: +"Chess Sessions (Integration test)", site: "Chess game menu", date: Time.new(2025, 7, 26), round: nil, white: "Ancient", black: "Nimbus", result: nil, mode: 1, moves: {}, fens: ["rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"], white_moves: [], black_moves: [] } } } + + before do + allow($stdout).to receive(:puts) + chess_manager.instance_variable_set(:@sessions, test_sessions) + allow(game_manager).to receive(:save_user_profile) + allow(game_manager).to receive(:exit_arcade).and_raise(SystemExit) + end + + after do + Dir.glob("user_data/pgn_export/*.pgn").each { |f| File.delete(f) } + end + + it "opens level, switch input scheme to smith notation, play all the moves, export file where file is renamed to default, and exit successfully" do + allow(Readline).to receive(:readline).and_return("", "", "", "2", "1", "", "", "1", "f2f4", "e7e5", "g2g4", "d8", "h4", "--info", "--export", "--exit") + expect { chess_manager.start }.to raise_error(SystemExit) + end + end + context "when user wishes to create a new game with computer player" do before do allow($stdout).to receive(:puts) From 2e175d15b58d6797156ee32e1906ede324c2d2e5 Mon Sep 17 00:00:00 2001 From: Ancient Nimbus <99920424+AncientNimbus@users.noreply.github.com> Date: Mon, 4 Aug 2025 18:30:07 +0100 Subject: [PATCH 11/13] feat(UX): Improve FEN import error notification feat(UX): Replace prefix with unique icons feat(UX): Add WaitUtils class to handle loading messages test: Update test cases to support wait events --- .config/locale/en.yml | 7 ++++-- lib/console/wait_utils.rb | 25 +++++++++++++++++++ lib/console_game/chess/board.rb | 12 ++++++--- lib/console_game/chess/game.rb | 2 +- lib/console_game/chess/input/chess_input.rb | 4 +-- lib/console_game/chess/level.rb | 7 +++++- .../chess/utilities/fen_import.rb | 2 +- lib/console_game/console_menu.rb | 2 +- spec/console_game/chess/fen_import_spec.rb | 4 +++ .../dummy_user_vs_player_2_2025_08_01.pgn | 10 -------- 10 files changed, 54 insertions(+), 21 deletions(-) create mode 100644 lib/console/wait_utils.rb delete mode 100644 user_data/pgn_export/dummy_user_vs_player_2_2025_08_01.pgn diff --git a/.config/locale/en.yml b/.config/locale/en.yml index 9aa3f9e..1b48716 100644 --- a/.config/locale/en.yml +++ b/.config/locale/en.yml @@ -370,8 +370,11 @@ en: misc: site: "Ruby Arcade Terminal Chess by Ancient Nimbus" status: - ongoing: "Ongoing" - draw: "Draw" + ongoing: "In Progress" + stalemate: "Draw" + insufficient: "Draw" + fifty_move: "Draw" + threefold: "Draw" checkmate: "Checkmate" pieces: k: diff --git a/lib/console/wait_utils.rb b/lib/console/wait_utils.rb new file mode 100644 index 0000000..c85bb1e --- /dev/null +++ b/lib/console/wait_utils.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require "whirly" + +module ConsoleGame + # Wait Utils class uses the whirly gem to enhance user experience when interacting with the terminal. + # @author Ancient Nimbus + class WaitUtils + def self.wait_msg(...) = new(...).wait_msg + + attr_reader :msg, :time + + def initialize(msg, time: 0) + @msg = msg + @time = time + end + + # Wait event via whirly + def wait_msg + Whirly.start spinner: "random_dots", status: msg, color: false, stop: "⣿" do + sleep time + end + end + end +end diff --git a/lib/console_game/chess/board.rb b/lib/console_game/chess/board.rb index bd08362..a1212c5 100644 --- a/lib/console_game/chess/board.rb +++ b/lib/console_game/chess/board.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +require_relative "../../console/wait_utils" require_relative "logics/display" module ConsoleGame @@ -26,17 +27,22 @@ def initialize(level) # board.print_msg(board.s(keypath, sub)) # end + # Loading message + # @see WaitUtils #wait_msg + def loading_msg(...) = WaitUtils.wait_msg(...) + # Print after the chessboard # @param keypath [String] TF keypath # @param sub [Hash] `{ demo: ["some text", :red] }` - def print_after_cb(keypath, sub = {}) = print_msg(s(keypath, sub), pre: "* ") + def print_after_cb(keypath, sub = {}) = print_msg(s(keypath, sub), pre: "⠗ ") # Print turn # @param event_msgs [Array] def print_turn(event_msgs = [""]) - print "\e[2J\e[H" + system("clear") + # print "\e[2J\e[H" - print_msg(*event_msgs, pre: "* ") unless event_msgs.empty? + print_msg(*event_msgs, pre: "⠗ ") unless event_msgs.empty? print_chessboard level.event_msgs.clear # self.live_board = 0 diff --git a/lib/console_game/chess/game.rb b/lib/console_game/chess/game.rb index 6084b67..731fe18 100644 --- a/lib/console_game/chess/game.rb +++ b/lib/console_game/chess/game.rb @@ -61,7 +61,7 @@ def new_game(err: false, import: false) # Import game mode def import_game - print_msg(s("new.f3"), pre: "* ") + print_msg(s("new.f3"), pre: "⠗ ") @fen = controller.ask("FEN: ", input_type: :any) end diff --git a/lib/console_game/chess/input/chess_input.rb b/lib/console_game/chess/input/chess_input.rb index 528b338..b89876c 100644 --- a/lib/console_game/chess/input/chess_input.rb +++ b/lib/console_game/chess/input/chess_input.rb @@ -110,7 +110,7 @@ def load(_args = []) return cmd_disabled if level.nil? @level = nil - print_msg(s("cmd.load"), pre: "* ") + print_msg(s("cmd.load"), pre: "⠗ ") chess_manager.setup_game end @@ -158,7 +158,7 @@ def switch_notation(mode) when :smith then smith_reg when :alg then alg_reg end - print_msg(s("cmd.input.#{mode}"), pre: "* ") + print_msg(s("cmd.input.#{mode}"), pre: "⠗ ") end # Create regexp patterns for various input modes diff --git a/lib/console_game/chess/level.rb b/lib/console_game/chess/level.rb index 71e87a5..ff1c74a 100644 --- a/lib/console_game/chess/level.rb +++ b/lib/console_game/chess/level.rb @@ -113,6 +113,11 @@ def fetch_all(...) = piece_lookup.fetch_all(...) # @see PieceLookup #reverse_lookup def reverse_lookup(...) = piece_lookup.reverse_lookup(...) + # Loading message + # @param msg [String] message + # @param time [Integer] wait time + def loading_msg(msg, time: 2) = board.loading_msg(msg, time:) + # == Export == # Update session moves record @@ -172,7 +177,7 @@ def play_chess # Player action flow def player_action - board.print_msg(board.s("level.turn", { player: player.name }), pre: "* ") + board.print_msg(board.s("level.turn", { player: player.name }), pre: "⠗ ") player.is_a?(ChessComputer) ? player.play_turn : controller.turn_action(player) end diff --git a/lib/console_game/chess/utilities/fen_import.rb b/lib/console_game/chess/utilities/fen_import.rb index 31f9ab5..99634e2 100644 --- a/lib/console_game/chess/utilities/fen_import.rb +++ b/lib/console_game/chess/utilities/fen_import.rb @@ -69,7 +69,7 @@ def process_fen_data(fen_data) # Process flow when there is an issue during FEN parsing # @param err_msg [String] error message during FEN error def fen_error(err_msg: "FEN error, '#{fen_str}' is not a valid sequence. Starting a new game...") - puts err_msg + level.loading_msg(err_msg, time: 3) self.class.parse_fen(level) end diff --git a/lib/console_game/console_menu.rb b/lib/console_game/console_menu.rb index 1551cb5..43f1c1e 100644 --- a/lib/console_game/console_menu.rb +++ b/lib/console_game/console_menu.rb @@ -55,7 +55,7 @@ def lol(_args = []) Whirly.status = s("cli.lol.sub").sample sleep 1.5 msgs = s("cli.lol.msgs") - print_msg(msgs[command_usage[:lol] % msgs.size], pre: "* ") + print_msg(msgs[command_usage[:lol] % msgs.size], pre: "⠗ ") end end diff --git a/spec/console_game/chess/fen_import_spec.rb b/spec/console_game/chess/fen_import_spec.rb index 379c24b..d2dea4b 100644 --- a/spec/console_game/chess/fen_import_spec.rb +++ b/spec/console_game/chess/fen_import_spec.rb @@ -92,6 +92,10 @@ context "when value is an invalid FEN string" do let(:invalid_fen_string) { "Invalid fen string" } + before do + allow(level_double).to receive(:loading_msg) + end + it "returns a hash of a standard new game as fallback" do result = fen_import_test.parse_fen(level_double, invalid_fen_string) expect(result.keys).to eq(%i[turn_data white_turn castling_states en_passant half_move full_move]) diff --git a/user_data/pgn_export/dummy_user_vs_player_2_2025_08_01.pgn b/user_data/pgn_export/dummy_user_vs_player_2_2025_08_01.pgn deleted file mode 100644 index f72b1e5..0000000 --- a/user_data/pgn_export/dummy_user_vs_player_2_2025_08_01.pgn +++ /dev/null @@ -1,10 +0,0 @@ -[Event "Dummy User vs Player 2"] -[Site "Ruby Arcade Terminal Chess by Ancient Nimbus"] -[Date "2025.08.01"] -[Round ""] -[White "Dummy User"] -[Black "Player 2"] -[Result ""] - -1. e4 e5 2. d3 d6 3. f3 b5 4. g3 a6 5. h3 Ra7 6. h4 a5 7. Nh3 Ra6 8. g4 c5 -9. Be2 Be7 10. d4 cxd4 11. h5 a4 12. b4 d3 13. O-O Kf8 14. Kh1 d2 15. Kg1 dxc1=Q 16. Rf2 Qcxd1+ From d9f73ab0bf983b9253a176c6f60fe22425b15a8d Mon Sep 17 00:00:00 2001 From: Ancient Nimbus <99920424+AncientNimbus@users.noreply.github.com> Date: Mon, 4 Aug 2025 18:38:42 +0100 Subject: [PATCH 12/13] chore: Update dummy user data sample --- user_data/dummy_user.json | 33 ++++++++++++++++--- ..._master_yo_status-checkmate_2025_08_04.pgn | 9 +++++ 2 files changed, 37 insertions(+), 5 deletions(-) create mode 100644 user_data/pgn_export/dummy_user_vs_master_yo_status-checkmate_2025_08_04.pgn diff --git a/user_data/dummy_user.json b/user_data/dummy_user.json index 7746e7e..f4c216d 100644 --- a/user_data/dummy_user.json +++ b/user_data/dummy_user.json @@ -1,13 +1,13 @@ { "uuid": "9e39f092-534e-4dbb-ac3c-64846b78d727", "username": "Dummy User", - "saved_date": "2025-08-03 10:04:44 +0100", + "saved_date": "2025-08-04 18:37:57 +0100", "appdata": { "chess": { "1": { "event": "Dummy User vs Legal Mate", "site": "Ruby Arcade Terminal Chess by Ancient Nimbus", - "date": "08/03/2025 09:56 AM", + "date": "08/04/2025 06:37 PM", "round": null, "white": "Dummy User", "black": "Legal Mate", @@ -20,7 +20,7 @@ "4": ["Nc3", "Bg4"], "5": ["h3", "Bh5"], "6": ["Nfxe5", "Bhxd1"], - "7": ["Bcxf7+", "Ke7"] + "7": ["Bcxf7", "Ke7"] }, "white_moves": [ "e4", @@ -30,7 +30,7 @@ "h3", "Nfxe5", "Bcxf7", - "Nd5" + "Nd5+" ], "black_moves": ["e5", "Nc6", "d6", "Bg4", "Bh5", "Bhxd1", "Ke7"], "fens": [ @@ -51,10 +51,33 @@ "r2q1bnr/ppp1kBpp/2np4/4N3/4P3/2N4P/PPPP1PP1/R1BbK2R w KQ - 5 8", "r2q1bnr/ppp1kBpp/2np4/3NN3/4P3/7P/PPPP1PP1/R1BbK2R b KQ - 6 8" ] + }, + "2": { + "event": "Dummy User vs Master Yo status-Checkmate", + "site": "Ruby Arcade Terminal Chess by Ancient Nimbus", + "date": "08/04/2025 06:37 PM", + "round": null, + "white": "Dummy User", + "black": "Master Yo", + "result": null, + "mode": 1, + "moves": { + "1": ["f4", "e5"], + "2": ["g4", "Qh4#"] + }, + "white_moves": ["f4", "g4"], + "black_moves": ["e5", "Qh4"], + "fens": [ + "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", + "rnbqkbnr/pppppppp/8/8/5P2/8/PPPPP1PP/RNBQKBNR b KQkq - 0 1", + "rnbqkbnr/pppp1ppp/8/4p3/5P2/8/PPPPP1PP/RNBQKBNR w KQkq - 0 2", + "rnbqkbnr/pppp1ppp/8/4p3/5PP1/8/PPPPP2P/RNBQKBNR b KQkq - 0 2", + "rnb1kbnr/pppp1ppp/8/4p3/5PPq/8/PPPPP2P/RNBQKBNR w KQkq - 1 3" + ] } } }, "stats": { - "launch_count": 218 + "launch_count": 370 } } diff --git a/user_data/pgn_export/dummy_user_vs_master_yo_status-checkmate_2025_08_04.pgn b/user_data/pgn_export/dummy_user_vs_master_yo_status-checkmate_2025_08_04.pgn new file mode 100644 index 0000000..1114bdc --- /dev/null +++ b/user_data/pgn_export/dummy_user_vs_master_yo_status-checkmate_2025_08_04.pgn @@ -0,0 +1,9 @@ +[Event "Dummy User vs Master Yo status-Checkmate"] +[Site "Ruby Arcade Terminal Chess by Ancient Nimbus"] +[Date "2025.08.04"] +[Round ""] +[White "Dummy User"] +[Black "Master Yo"] +[Result ""] + +1. f4 e5 2. g4 Qh4# From 926d33214103dc73a4dfeaae101afec0ae31f70b Mon Sep 17 00:00:00 2001 From: Ancient Nimbus <99920424+AncientNimbus@users.noreply.github.com> Date: Mon, 4 Aug 2025 19:21:56 +0100 Subject: [PATCH 13/13] fix: Address a delayed resolution for Stalemate This iteration fix the Stalemate delayed print issue, further testing may be required, closing O-78 for now. --- lib/console_game/chess/level.rb | 1 + spec/console_game/chess/level_spec.rb | 34 +++++++++++++-------------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/lib/console_game/chess/level.rb b/lib/console_game/chess/level.rb index ff1c74a..d13d797 100644 --- a/lib/console_game/chess/level.rb +++ b/lib/console_game/chess/level.rb @@ -146,6 +146,7 @@ def init_level @event_msgs = [] fen_data.each { |field, v| instance_variable_set("@#{field}", v) } set_current_player + update_board_state refresh(print_turn: false) greet_player end diff --git a/spec/console_game/chess/level_spec.rb b/spec/console_game/chess/level_spec.rb index 35fe80a..af5920c 100644 --- a/spec/console_game/chess/level_spec.rb +++ b/spec/console_game/chess/level_spec.rb @@ -22,7 +22,7 @@ it "enters the game loop and white pawn moves from a2 to a3" do allow(Readline).to receive(:readline).and_return("a2a3") - allow(level_test).to receive(:game_ended).and_return(false, false, true) + allow(level_test).to receive(:game_ended).and_return(*Array.new(3, false), true) level_test.open_level result = level_test.turn_data[16].is_a?(ConsoleGame::Chess::Pawn) expect(result).to be true @@ -41,7 +41,7 @@ it "enters the game loop and black pawn moves from h7 to h5" do allow(Readline).to receive(:readline).and_return("h7h5") - allow(level_test).to receive(:game_ended).and_return(false, false, true) + allow(level_test).to receive(:game_ended).and_return(*Array.new(3, false), true) level_test.open_level result = level_test.turn_data[39].is_a?(ConsoleGame::Chess::Pawn) expect(result).to be true @@ -63,7 +63,7 @@ it "enters the game loop and black knight captures pawn from c6" do allow(Readline).to receive(:readline).and_return("Nxd4") - allow(level_test).to receive(:game_ended).and_return(false, false, true) + allow(level_test).to receive(:game_ended).and_return(*Array.new(3, false), true) level_test.open_level result = level_test.turn_data[27].is_a?(ConsoleGame::Chess::Knight) expect(result).to be true @@ -85,7 +85,7 @@ it "enters the game loop and white pawn to move from e2 to e4, trigger en passant" do allow(Readline).to receive(:readline).and_return("e4") - allow(level_test).to receive(:game_ended).and_return(false, false, true) + allow(level_test).to receive(:game_ended).and_return(*Array.new(3, false), true) level_test.open_level result = level_test.en_passant[1] expect(result).to eq(20) @@ -105,7 +105,7 @@ it "enters the game loop and moves to e3, white pawn at e4 is removed and en_passant is reset" do allow(Readline).to receive(:readline).and_return("dxe3") - allow(level_test).to receive(:game_ended).and_return(false, false, true) + allow(level_test).to receive(:game_ended).and_return(*Array.new(3, false), true) level_test.open_level result = level_test.en_passant.nil? && level_test.turn_data[28] == "" expect(result).to be true @@ -125,7 +125,7 @@ it "enters the game loop and moves to e3, white pawn at e4 is removed and en_passant is reset" do allow(Readline).to receive(:readline).and_return("d3") - allow(level_test).to receive(:game_ended).and_return(false, false, true) + allow(level_test).to receive(:game_ended).and_return(*Array.new(3, false), true) level_test.open_level result = level_test.en_passant.nil? && level_test.turn_data[28].is_a?(ConsoleGame::Chess::Pawn) expect(result).to be true @@ -147,7 +147,7 @@ it "enters the game loop, white pawn moves from a7 to a8 and promotes to a Queen" do allow(Readline).to receive(:readline).and_return("a8=Q") - allow(level_test).to receive(:game_ended).and_return(false, false, true) + allow(level_test).to receive(:game_ended).and_return(*Array.new(3, false), true) level_test.open_level result = level_test.turn_data[56].is_a?(ConsoleGame::Chess::Queen) expect(result).to be true @@ -167,7 +167,7 @@ it "enters the game loop, white pawn captures black knight in b8 and promotes to a Rook" do allow(Readline).to receive(:readline).and_return("axb8=R") - allow(level_test).to receive(:game_ended).and_return(false, false, true) + allow(level_test).to receive(:game_ended).and_return(*Array.new(3, false), true) level_test.open_level result = level_test.turn_data[57].is_a?(ConsoleGame::Chess::Rook) expect(result).to be true @@ -186,7 +186,7 @@ it "enters the game loop, black pawn moves from a2 to a1 and promotes to a Bishop" do allow(Readline).to receive(:readline).and_return("a2a1b") - allow(level_test).to receive(:game_ended).and_return(false, false, true) + allow(level_test).to receive(:game_ended).and_return(*Array.new(3, false), true) level_test.open_level result = level_test.turn_data[0].is_a?(ConsoleGame::Chess::Bishop) expect(result).to be true @@ -205,7 +205,7 @@ it "enters the game loop, black pawn moves from a2 to a1 and promotes to a Knight" do allow(Readline).to receive(:readline).and_return("a2a1", "n") - allow(level_test).to receive(:game_ended).and_return(false, false, false, true) + allow(level_test).to receive(:game_ended).and_return(*Array.new(3, false), true) level_test.open_level result = level_test.turn_data[0].is_a?(ConsoleGame::Chess::Knight) expect(result).to be true @@ -227,7 +227,7 @@ it "enters the game loop, if input is O-O, Kingside castling is triggered, white king's new position is g1 and rook's position is f1" do allow(Readline).to receive(:readline).and_return("O-O") - allow(level_test).to receive(:game_ended).and_return(false, false, true) + allow(level_test).to receive(:game_ended).and_return(*Array.new(3, false), true) level_test.open_level result = level_test.turn_data[5].is_a?(ConsoleGame::Chess::Rook) && level_test.turn_data[6].is_a?(ConsoleGame::Chess::King) expect(result).to be true @@ -235,7 +235,7 @@ it "enters the game loop, if input is O-O-O, Queenside castling is triggered, white king's new position is c1 and rook's position is d1" do allow(Readline).to receive(:readline).and_return("O-O-O") - allow(level_test).to receive(:game_ended).and_return(false, false, true) + allow(level_test).to receive(:game_ended).and_return(*Array.new(3, false), true) level_test.open_level result = level_test.turn_data[3].is_a?(ConsoleGame::Chess::Rook) && level_test.turn_data[2].is_a?(ConsoleGame::Chess::King) expect(result).to be true @@ -254,7 +254,7 @@ it "enters the game loop, if input is e8g8, Kingside castling is triggered, black king's new position is g8 and rook's position is f8" do allow(Readline).to receive(:readline).and_return("e8", "g8") - allow(level_test).to receive(:game_ended).and_return(false, false, false, true) + allow(level_test).to receive(:game_ended).and_return(*Array.new(3, false), true) level_test.open_level result = level_test.turn_data[61].is_a?(ConsoleGame::Chess::Rook) && level_test.turn_data[62].is_a?(ConsoleGame::Chess::King) expect(result).to be true @@ -262,7 +262,7 @@ it "enters the game loop, if input is e8c8, Queenside castling is triggered, black king's new position is c8 and rook's position is d8" do allow(Readline).to receive(:readline).and_return("e8c8") - allow(level_test).to receive(:game_ended).and_return(false, false, false, true) + allow(level_test).to receive(:game_ended).and_return(*Array.new(3, false), true) level_test.open_level result = level_test.turn_data[59].is_a?(ConsoleGame::Chess::Rook) && level_test.turn_data[58].is_a?(ConsoleGame::Chess::King) expect(result).to be true @@ -283,7 +283,7 @@ end it "enters the game loop and each take turn to make a move" do - allow(level_test).to receive(:game_ended).and_return(false, false, false, false, true) + allow(level_test).to receive(:game_ended).and_return(*Array.new(5, false), true) level_test.open_level result = level_test.send(:all_moves).flatten.compact.size expect(result).to eq(2) @@ -305,7 +305,7 @@ it "adjusts the chessboard size by making it bigger" do allow(Readline).to receive(:readline).and_return("--board size", "e4") - allow(level_test).to receive(:game_ended).and_return(false, false, true) + allow(level_test).to receive(:game_ended).and_return(*Array.new(3, false), true) level_test.open_level result = level_test.board.board_size == 2 expect(result).to be true @@ -325,7 +325,7 @@ it "disable the chessboard flip feature" do allow(Readline).to receive(:readline).and_return("--board flip", "e4") - allow(level_test).to receive(:game_ended).and_return(false, false, true) + allow(level_test).to receive(:game_ended).and_return(*Array.new(3, false), true) level_test.open_level result = level_test.board.flip_board == false expect(result).to be true