diff --git a/spec/lx_chess/game/make_move_spec.cr b/spec/lx_chess/game/make_move_spec.cr new file mode 100644 index 0000000..43816ad --- /dev/null +++ b/spec/lx_chess/game/make_move_spec.cr @@ -0,0 +1,32 @@ +require "../../spec_helper" +require "../../../src/lx_chess/board" +require "../../../src/lx_chess/piece" +require "../../../src/lx_chess/move_set" +require "../../../src/lx_chess/game" + +include LxChess + +describe Game do + describe "#make_move" do + context "when a move will promote" do + it "raises an exception when promotion is not specified" do + game = Game.new + game.board["e7"] = Piece.from_fen('P') + expect_raises(Game::IllegalMove) do + game.make_move("e7", "e8") + end + end + + it "raises an exception when promotion is not specified" do + game = Game.new + game.board["e7"] = Piece.from_fen('P') + game.make_move("e7", "e8", 'Q') + piece = game.board["e8"] + from = game.board["e7"] + from.should be_nil + raise "e8 is empty" unless piece + piece.fen_symbol.should eq('Q') + end + end + end +end diff --git a/src/lx_chess/game.cr b/src/lx_chess/game.cr index 964bcde..e0f3116 100644 --- a/src/lx_chess/game.cr +++ b/src/lx_chess/game.cr @@ -126,7 +126,7 @@ module LxChess end # TODO: checkmate - def move_to_san(from : Int, to : Int, promotion : String? = nil, turn = @turn) + def move_to_san(from : Int, to : Int, promotion : Char? = nil, turn = @turn) raise "No piece at #{@board.cord(from)}" unless piece = @board[from] en_passant = piece.pawn? && to == @en_passant_target @@ -355,6 +355,7 @@ module LxChess if piece.pawn? distance = from - to if distance.abs == @board.width * 2 + # Double pawn push, set en passant target @en_passant_target = distance > 0 ? to + @board.width : to - @board.width else if to == @en_passant_target @@ -368,6 +369,22 @@ module LxChess @en_passant_target = nil end + # Promotion + if piece.pawn? + rank = @board.rank(to) + if rank == 0 || rank == @board.height - 1 + if promotion + raise IllegalMove.new("Cannot promote to #{promotion}") unless "RNBQ".chars.includes?(promotion.upcase) + promotion = piece.white? ? promotion.upcase : promotion.downcase + @board[from] = Piece.from_fen(promotion) + else + raise IllegalMove.new("Pawns must promote on the last rank") + end + elsif promotion + raise IllegalMove.new("Cannot promote on #{@board.cord(to)}") + end + end + @board.move(from, to) next_turn! @pgn.history << san @@ -408,6 +425,7 @@ module LxChess # Get the next turn index def next_turn + return 0.to_i8 if @players.size == 0 (@turn + 1) % @players.size end end diff --git a/src/lx_chess/piece.cr b/src/lx_chess/piece.cr index 5234d18..c9967dd 100644 --- a/src/lx_chess/piece.cr +++ b/src/lx_chess/piece.cr @@ -12,6 +12,7 @@ module LxChess KNIGHT = 5 def self.from_fen(fen : Char) + raise "#{fen} is not a valid FEN symbol" unless FEN_SYMBOLS.chars.includes?(fen) id = FEN_SYMBOLS.index(fen).as(Int32).to_i8 Piece.new(id) end diff --git a/src/lx_chess/term_game.cr b/src/lx_chess/term_game.cr index 056d7f7..03bded7 100644 --- a/src/lx_chess/term_game.cr +++ b/src/lx_chess/term_game.cr @@ -71,7 +71,7 @@ module LxChess end if from && to @gb.clear - san = @game.make_move(from, to) + san = @game.make_move(from, to, promo) @gb.highlight([@game.board.index(from), @game.board.index(to)]) @log.unshift "#{san.to_s}: #{from} => #{to}" end @@ -84,7 +84,7 @@ module LxChess from, to = @game.parse_san(notation) if from && to @gb.clear - san = @game.make_move(from, to) + san = @game.make_move(from, to, notation.promotion) @gb.highlight([from.to_i16, to.to_i16]) @log.unshift "#{san.to_s}: #{@game.board.cord(from)} => #{@game.board.cord(to)}" end