diff --git a/spec/lx_chess/game_spec.cr b/spec/lx_chess/game_spec.cr index 72b9844..c3b073b 100644 --- a/spec/lx_chess/game_spec.cr +++ b/spec/lx_chess/game_spec.cr @@ -3,66 +3,204 @@ require "../../src/lx_chess/board" require "../../src/lx_chess/piece" require "../../src/lx_chess/move_set" require "../../src/lx_chess/game" +require "../../src/lx_chess/term_board" + +def debug_board(game : LxChess::Game, moves : Array(Int16)) + puts + gb = LxChess::TermBoard.new(game.board) + gb.highlight(moves) + gb.draw + puts +end describe LxChess::Game do describe "#moves" do it "correctly generates white pawn moves from the initial rank" do game = LxChess::Game.new game.board["e2"] = LxChess::Piece.from_fen('P') - moves = game.moves("e2") - moves.map { |m| game.board.cord(m) }.should eq(["e3", "e4"]) + if move_set = game.moves("e2") + debug_board(game, move_set.moves) + move_set.moves.map { |m| game.board.cord(m) }.should eq(%w[e3 e4]) + else + raise "no moves" + end end it "correctly generates black pawn moves from the initial rank" do game = LxChess::Game.new game.board["e7"] = LxChess::Piece.from_fen('p') - moves = game.moves("e7") - moves.map { |m| game.board.cord(m) }.should eq(["e6", "e5"]) + if move_set = game.moves("e7") + debug_board(game, move_set.moves) + move_set.moves.map { |m| game.board.cord(m) }.should eq(%w[e6 e5]) + else + raise "no moves" + end end it "correctly generates single white pawn moves" do game = LxChess::Game.new game.board["e3"] = LxChess::Piece.from_fen('P') - moves = game.moves("e3") - moves.map { |m| game.board.cord(m) }.should eq(["e4"]) + if move_set = game.moves("e3") + debug_board(game, move_set.moves) + move_set.moves.map { |m| game.board.cord(m) }.should eq(%w[e4]) + else + raise "no moves" + end end it "correctly generates black white pawn moves" do game = LxChess::Game.new game.board["e6"] = LxChess::Piece.from_fen('p') - moves = game.moves("e6") - moves.map { |m| game.board.cord(m) }.should eq(["e5"]) + if move_set = game.moves("e6") + debug_board(game, move_set.moves) + move_set.moves.map { |m| game.board.cord(m) }.should eq(%w[e5]) + else + raise "no moves" + end end it "correctly generates knight moves" do game = LxChess::Game.new game.board["c3"] = LxChess::Piece.from_fen('N') - moves = game.moves("c3") - moves.map { |m| game.board.cord(m) }.should eq(["a4", "b5", "d5", "e4", "e2", "d1", "b1", "a2"]) + if move_set = game.moves("c3") + debug_board(game, move_set.moves) + move_set.moves.map { |m| game.board.cord(m) }.should eq(%w[a4 b5 d5 e4 e2 d1 b1 a2]) + else + raise "no moves" + end + end + + it "does not generate knight moves that cross the left border edge" do + game = LxChess::Game.new + game.board["a1"] = LxChess::Piece.from_fen('N') + if move_set = game.moves("a1") + debug_board(game, move_set.moves) + move_set.moves.map { |m| game.board.cord(m) }.should eq(%w[b3 c2]) + else + raise "no moves" + end + end + + it "does not generate knight moves that cross the right border edge" do + game = LxChess::Game.new + game.board["h1"] = LxChess::Piece.from_fen('N') + if move_set = game.moves("h1") + debug_board(game, move_set.moves) + move_set.moves.map { |m| game.board.cord(m) }.should eq(%w[f2 g3]) + else + raise "no moves" + end end it "correctly generates rook moves" do game = LxChess::Game.new game.board["e4"] = LxChess::Piece.from_fen('R') - moves = game.moves("e4") - moves.map { |m| game.board.cord(m) }.should eq([ - "d4", "c4", "b4", "a4", - "e5", "e6", "e7", "e8", - "f4", "g4", "h4", - "e3", "e2", "e1", - ]) + if move_set = game.moves("e4") + debug_board(game, move_set.moves) + move_set.moves.map { |m| game.board.cord(m) }.should eq(%w[ + d4 c4 b4 a4 + e5 e6 e7 e8 + f4 g4 h4 + e3 e2 e1 + ]) + else + raise "no moves" + end end it "correctly generates bishop moves" do game = LxChess::Game.new game.board["e4"] = LxChess::Piece.from_fen('B') - moves = game.moves("e4") - moves.map { |m| game.board.cord(m) }.should eq([ - "d5", "c6", "b7", "a8", - "f5", "g6", "h7", - "f3", "g2", "h1", - "d3", "c2", "b1", - ]) + if move_set = game.moves("e4") + debug_board(game, move_set.moves) + move_set.moves.map { |m| game.board.cord(m) }.should eq(%w[ + d5 c6 b7 a8 + f5 g6 h7 + f3 g2 h1 + d3 c2 b1 + ]) + else + raise "no moves" + end + end + + it "correctly generates king moves" do + game = LxChess::Game.new + game.board["e4"] = LxChess::Piece.from_fen('K') + if move_set = game.moves("e4") + debug_board(game, move_set.moves) + move_set.moves.map { |m| game.board.cord(m) }.should eq(%w[ + d4 d5 e5 f5 f4 f3 e3 d3 + ]) + else + raise "no moves" + end + end + + it "does not generate king moves crossing the right border" do + game = LxChess::Game.new + game.board["h4"] = LxChess::Piece.from_fen('K') + if move_set = game.moves("h4") + debug_board(game, move_set.moves) + move_set.moves.map { |m| game.board.cord(m) }.should eq(%w[ + g4 g5 h5 h3 g3 + ]) + else + raise "no moves" + end + end + + it "correctly generates queen moves" do + game = LxChess::Game.new + game.board["e4"] = LxChess::Piece.from_fen('Q') + if move_set = game.moves("e4") + debug_board(game, move_set.moves) + move_set.moves.map { |m| game.board.cord(m) }.should eq(%w[ + d5 c6 b7 a8 + f5 g6 h7 + f3 g2 h1 + d3 c2 b1 + d4 c4 b4 a4 + e5 e6 e7 e8 + f4 g4 h4 + e3 e2 e1 + ]) + else + raise "no moves" + end + end + + it "blocks moves when a piece is in the way" do + game = LxChess::Game.new + game.board["e4"] = LxChess::Piece.from_fen('B') + game.board["f5"] = LxChess::Piece.from_fen('P') + if move_set = game.moves("e4") + debug_board(game, move_set.moves) + move_set.moves.map { |m| game.board.cord(m) }.should eq(%w[ + d5 c6 b7 a8 + f3 g2 h1 + d3 c2 b1 + ]) + else + raise "no moves" + end + end + + it "adds captures before blocking further moves" do + game = LxChess::Game.new + game.board["e4"] = LxChess::Piece.from_fen('B') + game.board["f5"] = LxChess::Piece.from_fen('p') + if move_set = game.moves("e4") + debug_board(game, move_set.moves) + move_set.moves.map { |m| game.board.cord(m) }.should eq(%w[ + d5 c6 b7 a8 + f5 + f3 g2 h1 + d3 c2 b1 + ]) + else + raise "no moves" + end end end end diff --git a/src/lx_chess.cr b/src/lx_chess.cr index d6028e0..3c858f9 100644 --- a/src/lx_chess.cr +++ b/src/lx_chess.cr @@ -44,7 +44,9 @@ loop do if input notation = LxChess::Notation.new(input) input = input.to_i16 if input =~ /^\d+$/ - puts game.moves(input).map { |m| game.board.cord(m) } + if move_set = game.moves(input) + puts move_set.moves.map { |m| game.board.cord(m) } + end # from, to = game.parse_san(notation) # if from && to # puts "#{notation.to_s}: #{game.board.cord(from)} => #{game.board.cord(to)}" diff --git a/src/lx_chess/game.cr b/src/lx_chess/game.cr index b78b8be..f0809b8 100644 --- a/src/lx_chess/game.cr +++ b/src/lx_chess/game.cr @@ -76,9 +76,7 @@ module LxChess {x: -1, y: -1}, # down left ]) end - set.moves - else - [] of Int16 + set end end end diff --git a/src/lx_chess/move_set.cr b/src/lx_chess/move_set.cr index 1fd2908..c6a61b8 100644 --- a/src/lx_chess/move_set.cr +++ b/src/lx_chess/move_set.cr @@ -11,13 +11,17 @@ module LxChess @moves = [] of Int16 end - def add_vector(x : Int16, y : Int16, limit : Int16) - add_vector(y * @board.width + x, limit) + def origin + @piece.index.as(Int16) end - def add_vector(offset : Int16, limit : Int16) + def add_vector(x : Int16, y : Int16, limit : Int16) + dist_edge = @board.dist_left(origin) if x.negative? + dist_edge = @board.dist_right(origin) if x.positive? + limit = dist_edge if dist_edge && dist_edge < limit + + offset = y * @board.width + x step = offset - location = @piece.index.as(Int16) limit.times do info = add_offset(offset) offset += step @@ -27,25 +31,22 @@ module LxChess def add_offsets(offsets : Array(NamedTuple(x: Int32, y: Int32))) offsets.each do |cord| + # Check if the offset crosses the border + next if cord[:x].negative? && origin + cord[:x] < @board.border_left(origin) + next if cord[:x].positive? && origin + cord[:x] > @board.border_right(origin) offset = cord[:y] * @board.width + cord[:x] add_offset(offset.to_i16) end end - def add_offsets(offsets : Array(Int16)) - offsets.each do |offset| - add_offset(offset) - end - end - def add_offset(x : Int16, y : Int16) add_offset(y * @board.width + x) end - # TODO: Stop at the board edges + # Does not check for crossing border edges def add_offset(offset : Int16) added = false; stop = false - location = @piece.index.as(Int16) + offset + location = origin + offset if location < 0 || location >= @board.squares.size # Beyond top or bottom stop = true diff --git a/src/lx_chess/term_board.cr b/src/lx_chess/term_board.cr index 950ff61..4e2b1e7 100644 --- a/src/lx_chess/term_board.cr +++ b/src/lx_chess/term_board.cr @@ -8,6 +8,7 @@ module LxChess def initialize(@board : Board) @flipped = false + @highlights = {} of Int16 => Tuple(Symbol, Symbol) @bg_dark = :green @bg_light = :light_green @@ -42,6 +43,16 @@ module LxChess @flipped ? (width - 1).downto(0) : 0.upto(width - 1) end + def highlight(indicies : Array(Int16), color = {:light_yellow, :yellow}) + indicies.each do |index| + @highlights[index] = color + end + end + + def clear + @highlights = {} of Int16 => Tuple(Symbol, Symbol) + end + def draw(io = STDOUT) ranks.each do |y| io << (y + 1) << ": " @@ -50,7 +61,14 @@ module LxChess index = @board.index(x, y) piece = @board[index] - background = (index + y) % 2 == 0 ? @bg_dark : @bg_light + tint = (index + y) % 2 == 0 ? :light : :dark + + background = + if @highlights[index]? + @highlights[index][tint == :light ? 0 : 1] + else + tint == :light ? @bg_light : @bg_dark + end if piece foreground = piece.white? ? @fg_light : @fg_dark