From af020fb3c61d1e3d2a20190988fcf3a4d6005770 Mon Sep 17 00:00:00 2001 From: Alex Clink Date: Thu, 9 Sep 2021 23:59:31 -0400 Subject: [PATCH] Add more moves to generation --- spec/lx_chess/game_spec.cr | 68 ++++++++++++++++++++++++++++++++++ spec/lx_chess/move_set_spec.cr | 18 +++++++++ src/lx_chess.cr | 12 +++--- src/lx_chess/fen.cr | 4 ++ src/lx_chess/game.cr | 39 ++++++++++++++++++- src/lx_chess/move_set.cr | 41 ++++++++++++++++++-- 6 files changed, 172 insertions(+), 10 deletions(-) create mode 100644 spec/lx_chess/game_spec.cr create mode 100644 spec/lx_chess/move_set_spec.cr diff --git a/spec/lx_chess/game_spec.cr b/spec/lx_chess/game_spec.cr new file mode 100644 index 0000000..72b9844 --- /dev/null +++ b/spec/lx_chess/game_spec.cr @@ -0,0 +1,68 @@ +require "../spec_helper" +require "../../src/lx_chess/board" +require "../../src/lx_chess/piece" +require "../../src/lx_chess/move_set" +require "../../src/lx_chess/game" + +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"]) + 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"]) + 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"]) + 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"]) + 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"]) + 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", + ]) + 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", + ]) + end + end +end diff --git a/spec/lx_chess/move_set_spec.cr b/spec/lx_chess/move_set_spec.cr new file mode 100644 index 0000000..cb0fc5c --- /dev/null +++ b/spec/lx_chess/move_set_spec.cr @@ -0,0 +1,18 @@ +require "../spec_helper" +require "../../src/lx_chess/board" +require "../../src/lx_chess/piece" +require "../../src/lx_chess/move_set" + +describe LxChess::MoveSet do + describe "#add_vector" do + it "generates moves to the right" do + game = LxChess::Game.new + piece = LxChess::Piece.from_fen('R') + game.board["a1"] = piece + move_set = LxChess::MoveSet.new(piece, game.board) + move_set.add_vector(x: 1, y: 0, limit: 3) + move_set.moves.size.should eq(3) + move_set.moves.should eq([1, 2, 3]) + end + end +end diff --git a/src/lx_chess.cr b/src/lx_chess.cr index 1eb7ed0..d6028e0 100644 --- a/src/lx_chess.cr +++ b/src/lx_chess.cr @@ -43,12 +43,12 @@ loop do input = gets if input notation = LxChess::Notation.new(input) - # input = input.to_i16 if input =~ /^\d+$/ - # puts game.moves(input) - from, to = game.parse_san(notation) - if from && to - puts "#{notation.to_s}: #{game.board.cord(from)} => #{game.board.cord(to)}" - end + input = input.to_i16 if input =~ /^\d+$/ + puts game.moves(input).map { |m| game.board.cord(m) } + # from, to = game.parse_san(notation) + # if from && to + # puts "#{notation.to_s}: #{game.board.cord(from)} => #{game.board.cord(to)}" + # end end rescue e : LxChess::Notation::InvalidNotation puts e.message diff --git a/src/lx_chess/fen.cr b/src/lx_chess/fen.cr index 00dbfc2..023e788 100644 --- a/src/lx_chess/fen.cr +++ b/src/lx_chess/fen.cr @@ -51,6 +51,10 @@ module LxChess board end + def self.standard + self.parse "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" + end + property board : Board property turn : String property castling : String diff --git a/src/lx_chess/game.cr b/src/lx_chess/game.cr index 859af34..b78b8be 100644 --- a/src/lx_chess/game.cr +++ b/src/lx_chess/game.cr @@ -7,7 +7,7 @@ module LxChess class Game property turn : Int8, board : Board - def initialize(@board : Board, @players = [] of Player) + def initialize(@board : Board = Board.new, @players = [] of Player) @turn = 0 end @@ -38,6 +38,43 @@ module LxChess set.add_vector(x: 0, y: 1, limit: (@board.rank(index) == 1 ? 2 : 1).to_i16) when 'p' # Black pawn set.add_vector(x: 0, y: -1, limit: (@board.rank(index) == @board.height - 2 ? 2 : 1).to_i16) + when 'B', 'b' # Bishop + set.add_vector(x: -1, y: 1, limit: 8) + set.add_vector(x: 1, y: 1, limit: 8) + set.add_vector(x: 1, y: -1, limit: 8) + set.add_vector(x: -1, y: -1, limit: 8) + when 'R', 'r' # Rook + set.add_vector(x: -1, y: 0, limit: 8) + set.add_vector(x: 0, y: 1, limit: 8) + set.add_vector(x: 1, y: 0, limit: 8) + set.add_vector(x: 0, y: -1, limit: 8) + when 'Q', 'q' # Queen + set.add_vector(x: -1, y: 1, limit: 8) + set.add_vector(x: 1, y: 1, limit: 8) + set.add_vector(x: 1, y: -1, limit: 8) + set.add_vector(x: -1, y: -1, limit: 8) + set.add_vector(x: -1, y: 0, limit: 8) + set.add_vector(x: 0, y: 1, limit: 8) + set.add_vector(x: 1, y: 0, limit: 8) + set.add_vector(x: 0, y: -1, limit: 8) + when 'N', 'n' # Knight + set.add_offsets([ + {x: -2, y: 1}, {x: -1, y: 2}, # up left + {x: 1, y: 2}, {x: 2, y: 1}, # up right + {x: 2, y: -1}, {x: 1, y: -2}, # down right + {x: -1, y: -2}, {x: -2, y: -1}, # down left + ]) + when 'K', 'k' # King + set.add_offsets([ + {x: -1, y: 0}, # left + {x: -1, y: 1}, # left up + {x: 0, y: 1}, # up + {x: 1, y: 1}, # up right + {x: 1, y: 0}, # right + {x: 1, y: -1}, # down right + {x: 0, y: -1}, # down + {x: -1, y: -1}, # down left + ]) end set.moves else diff --git a/src/lx_chess/move_set.cr b/src/lx_chess/move_set.cr index b7a008a..1fd2908 100644 --- a/src/lx_chess/move_set.cr +++ b/src/lx_chess/move_set.cr @@ -16,17 +16,52 @@ module LxChess end def add_vector(offset : Int16, limit : Int16) + step = offset location = @piece.index.as(Int16) limit.times do - location = location + offset + info = add_offset(offset) + offset += step + break if info[:stop] + end + end + + def add_offsets(offsets : Array(NamedTuple(x: Int32, y: Int32))) + offsets.each do |cord| + 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 + def add_offset(offset : Int16) + added = false; stop = false + location = @piece.index.as(Int16) + offset + if location < 0 || location >= @board.squares.size + # Beyond top or bottom + stop = true + else if capture = @board[location] unless capture.color == @piece.color @moves.push(location) + added = true end - break + stop = true + else + @moves.push(location) + added = true end - @moves.push(location) end + {added: added, stop: stop, location: location} end end end