From 5f576272259e587be86f79959e69ba8c1251c3a0 Mon Sep 17 00:00:00 2001 From: Camden Dixie O'Brien Date: Wed, 13 Dec 2023 16:36:46 +0000 Subject: [PATCH] Add legal chess captures exercise --- 20_legal_captures.lua | 340 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 340 insertions(+) create mode 100644 20_legal_captures.lua diff --git a/20_legal_captures.lua b/20_legal_captures.lua new file mode 100644 index 0000000..cc1a71d --- /dev/null +++ b/20_legal_captures.lua @@ -0,0 +1,340 @@ +-- Write a function which takes a description of a chess position and +-- returns a list of all legal captures from that position. +-- +-- The position will be given as a table, containing the keys "turn", +-- indicating which player it is to move, "board", which will contain +-- a rank-major grid of pieces (that is, a list of ranks/rows). Each +-- piece will be indicated by its unicode symbol. The lack of a piece +-- will be indicated by nil. If the previous move was a pawn moving +-- two places, the key "en_passant_target" will be set to a table with +-- "rank" and "file" keys set to the (1-based) rank and file indices +-- of the square passed over by the pawn. +-- +-- For example, the starting position would be given as: +-- +-- { +-- turn = "white", +-- board = { +-- {"♖", "♘", "♗", "♕", "♔", "♗", "♘", "♖"}, +-- {"♙", "♙", "♙", "♙", "♙", "♙", "♙", "♙"}, +-- {nil, nil, nil, nil, nil, nil, nil, nil}, +-- {nil, nil, nil, nil, nil, nil, nil, nil}, +-- {nil, nil, nil, nil, nil, nil, nil, nil}, +-- {nil, nil, nil, nil, nil, nil, nil, nil}, +-- {"♟︎", "♟︎", "♟︎", "♟︎", "♟︎", "♟︎", "♟︎", "♟︎"}, +-- {"♜", "♞", "♝", "♛", "♚", "♝", "♞", "♜"}, +-- }, +-- } +-- +-- Moves should be returned in figurine algebraic notation, without +-- indicators for en passant, check or checkmate (i.e. no "e.p.", "+" +-- or "#"). For example, "♞xc6" for a black night capturing on c6 and +-- "exd7" for an e-file pawn capturing on d7. For the sake of +-- simplicity, there's no requirement to disambiguate between two +-- pieces of the same type when the notation for the capture would +-- otherwise be the same (e.g. if two rooks can capture on the same +-- square). However, there should be duplicate entries in the results +-- list for each. +-- +-- Solution -------------------------------------------------------------------- +function legal_captures(position) + -- Your implementation here +end + +-- Tests ----------------------------------------------------------------------- + +local luaunit = require("luaunit.luaunit") + +function test_starting_position() + local position = { + turn = "white", + board = { + {"♖", "♘", "♗", "♕", "♔", "♗", "♘", "♖"}, + {"♙", "♙", "♙", "♙", "♙", "♙", "♙", "♙"}, + {nil, nil, nil, nil, nil, nil, nil, nil}, + {nil, nil, nil, nil, nil, nil, nil, nil}, + {nil, nil, nil, nil, nil, nil, nil, nil}, + {nil, nil, nil, nil, nil, nil, nil, nil}, + {"♟︎", "♟︎", "♟︎", "♟︎", "♟︎", "♟︎", "♟︎", "♟︎"}, + {"♜", "♞", "♝", "♛", "♚", "♝", "♞", "♜"}, + }, + } + local expected = {} + luaunit.assertItemsEquals(legal_captures(position), expected) +end + +function test_queens_gambit() + local position = { + turn = "black", + board = { + {"♖", "♘", "♗", "♕", "♔", "♗", "♘", "♖"}, + {"♙", "♙", nil, nil, "♙", "♙", "♙", "♙"}, + {nil, nil, nil, nil, nil, nil, nil, nil}, + {nil, nil, "♙", "♙", nil, nil, nil, nil}, + {nil, nil, nil, "♟︎", nil, nil, nil, nil}, + {nil, nil, nil, nil, nil, nil, nil, nil}, + {"♟︎", "♟︎", "♟︎", nil, "♟︎", "♟︎", "♟︎", "♟︎"}, + {"♜", "♞", "♝", "♛", "♚", "♝", "♞", "♜"}, + }, + } + local expected = {"dxc4"} + luaunit.assertItemsEquals(legal_captures(position), expected) +end + +function test_scandinavian_defense() + local position = { + turn = "white", + board = { + {"♖", "♘", "♗", "♕", "♔", "♗", "♘", "♖"}, + {"♙", "♙", "♙", "♙", nil, "♙", "♙", "♙"}, + {nil, nil, nil, nil, nil, nil, nil, nil}, + {nil, nil, nil, nil, "♙", nil, nil, nil}, + {nil, nil, nil, "♟︎", nil, nil, nil, nil}, + {nil, nil, nil, nil, nil, nil, nil, nil}, + {"♟︎", "♟︎", "♟︎", nil, "♟︎", "♟︎", "♟︎", "♟︎"}, + {"♜", "♞", "♝", "♛", "♚", "♝", "♞", "♜"}, + }, + } + local expected = {"exd5"} + luaunit.assertItemsEquals(legal_captures(position), expected) +end + +function test_knight_captures() + local position = { + turn = "black", + board = { + {"♙", "♙", "♙", "♙", "♙", "♙", "♙", "♙"}, + {"♙", "♙", "♙", nil, "♙", "♙", "♙", "♙"}, + {"♙", "♙", nil, nil, nil, "♙", "♙", "♙"}, + {"♙", nil, nil, "♞", nil, nil, "♙", "♙"}, + {"♙", "♙", nil, nil, nil, "♙", "♙", "♙"}, + {"♙", "♙", "♙", nil, "♙", "♙", "♙", "♙"}, + {"♙", "♙", "♙", "♙", "♙", "♙", "♙", "♙"}, + {"♙", "♙", "♙", "♙", "♙", "♙", "♙", "♙"}, + }, + } + local expected = { + "♞xb3", "♞xb5", "♞xc2", "♞xc6", "♞xe2", "♞xe6", "♞xf3", "♞xf5", + } + luaunit.assertItemsEquals(legal_captures(position), expected) +end + +function test_knight_not_blocked() + local position = { + turn = "white", + board = { + {nil, nil, nil, nil, nil, nil, nil, nil}, + {nil, nil, nil, nil, nil, nil, nil, nil}, + {nil, nil, nil, "♟︎", "♟︎", "♟︎", nil, nil}, + {nil, nil, nil, nil, "♟︎", nil, nil, nil}, + {nil, nil, "♟︎", "♟︎", "♘", nil, nil, nil}, + {nil, nil, nil, nil, nil, nil, nil, nil}, + {nil, nil, nil, nil, nil, nil, nil, nil}, + {nil, nil, nil, nil, nil, nil, nil, nil}, + }, + } + local expected = {"♘xd3", "♘xf3"} + luaunit.assertItemsEquals(legal_captures(position), expected) +end + +function test_bishop_captures() + local position = { + turn = "black", + board = { + {nil, nil, nil, nil, nil, nil, nil, nil}, + {"♙", nil, nil, nil, nil, nil, "♙", nil}, + {nil, nil, nil, "♙", nil, nil, "♙", nil}, + {nil, nil, nil, nil, nil, nil, nil, nil}, + {nil, nil, "♙", "♝", nil, nil, nil, "♙"}, + {nil, nil, nil, nil, "♙", nil, nil, nil}, + {nil, "♙", nil, nil, nil, nil, nil, nil}, + {nil, nil, nil, "♙", nil, nil, nil, nil}, + }, + } + local expected = {"♝xa2", "♝xb7", "♝xe6", "♝xg2"} + luaunit.assertItemsEquals(legal_captures(position), expected) +end + +function test_bishop_blocked() + local position = { + turn = "black", + board = { + {nil, nil, nil, nil, nil, nil, nil, nil}, + {nil, nil, nil, nil, nil, nil, nil, nil}, + {nil, nil, nil, nil, nil, "♙", nil, nil}, + {nil, nil, nil, nil, nil, nil, nil, nil}, + {nil, nil, nil, "♞", nil, nil, nil, nil}, + {nil, nil, nil, nil, nil, nil, nil, nil}, + {nil, "♝", nil, nil, nil, nil, nil, nil}, + {nil, nil, nil, nil, nil, nil, nil, nil}, + }, + } + local expected = {} + luaunit.assertItemsEquals(legal_captures(position), expected) +end + +function test_rook_captures() + local position = { + turn = "white", + board = { + {nil, nil, nil, nil, nil, nil, nil, nil}, + {"♟︎", nil, nil, nil, nil, nil, "♟︎", nil}, + {nil, nil, nil, "♟︎", nil, nil, "♟︎", nil}, + {nil, nil, nil, nil, nil, nil, nil, nil}, + {nil, nil, "♟︎", "♖", nil, nil, nil, "♟︎"}, + {nil, nil, nil, nil, "♟︎", nil, nil, nil}, + {nil, "♟︎", nil, nil, nil, nil, nil, nil}, + {nil, nil, nil, "♟︎", nil, nil, nil, nil}, + }, + } + local expected = {"♖xc5", "♖xd3", "♖xd8", "♖xh5"} + luaunit.assertItemsEquals(legal_captures(position), expected) +end + +function test_rook_blocked() + local position = { + turn = "white", + board = { + {nil, nil, nil, nil, nil, nil, nil, nil}, + {nil, nil, nil, nil, nil, nil, nil, nil}, + {nil, nil, nil, nil, nil, nil, nil, nil}, + {nil, nil, nil, nil, nil, nil, nil, nil}, + {nil, "♖", "♙", nil, nil, nil, "♟︎", nil}, + {nil, nil, nil, nil, nil, nil, nil, nil}, + {nil, nil, nil, nil, nil, nil, nil, nil}, + {nil, nil, nil, nil, nil, nil, nil, nil}, + }, + } + local expected = {} + luaunit.assertItemsEquals(legal_captures(position), expected) +end + +function test_queen_captures() + local position = { + turn = "black", + board = { + {nil, "♙", "♙", "♙", "♙", "♙", nil, "♙"}, + {"♙", "♙", "♙", nil, "♙", "♙", "♙", "♙"}, + {"♙", "♙", nil, nil, nil, "♙", "♙", "♙"}, + {"♙", nil, nil, "♛", nil, nil, "♙", nil}, + {"♙", "♙", nil, nil, nil, "♙", "♙", "♙"}, + {"♙", "♙", "♙", nil, "♙", "♙", "♙", "♙"}, + {nil, "♙", "♙", "♙", "♙", "♙", nil, "♙"}, + {"♙", "♙", "♙", nil, "♙", "♙", "♙", nil}, + }, + } + local expected = { + "♛xa4", "♛xb2", "♛xb6", "♛xd1", "♛xd7", "♛xf2", "♛xf6", "♛xg4", + } + luaunit.assertItemsEquals(legal_captures(position), expected) +end + +function test_queen_blocked() + local position = { + turn = "white", + board = { + {nil, nil, nil, nil, nil, nil, nil, nil}, + {nil, nil, nil, nil, "♟︎", nil, nil, nil}, + {nil, nil, nil, nil, nil, nil, nil, nil}, + {nil, nil, "♙", nil, nil, nil, nil, nil}, + {nil, "♕", "♙", nil, nil, nil, "♟︎", nil}, + {nil, nil, nil, nil, nil, nil, nil, nil}, + {nil, nil, nil, nil, nil, nil, nil, nil}, + {nil, nil, nil, nil, nil, nil, nil, nil}, + }, + } + local expected = {} + luaunit.assertItemsEquals(legal_captures(position), expected) +end + +function test_king_captures() + local position = { + turn = "black", + board = { + {nil, nil, nil, nil, nil, nil, nil, nil}, + {nil, nil, nil, nil, nil, nil, nil, nil}, + {nil, nil, "♙", "♙", "♙", nil, nil, nil}, + {nil, nil, "♙", "♚", "♙", nil, nil, nil}, + {nil, nil, "♙", "♙", "♙", nil, nil, nil}, + {nil, nil, nil, nil, nil, nil, nil, nil}, + {nil, nil, nil, nil, nil, nil, nil, nil}, + {nil, nil, nil, nil, nil, nil, nil, nil}, + }, + } + local expected = {"♚xc3","♚xc4","♚xc5","♚xd3","♚xd5","♚xe3","♚xe4","♚xe5",} + luaunit.assertItemsEquals(legal_captures(position), expected) +end + +function test_en_passant() + local position = { + turn = "white", + board = { + {nil, nil, nil, nil, nil, nil, nil, nil}, + {nil, nil, nil, nil, nil, nil, nil, nil}, + {nil, nil, nil, nil, nil, nil, nil, nil}, + {nil, nil, nil, nil, nil, nil, nil, nil}, + {nil, nil, nil, "♟︎", "♙", nil, nil, nil}, + {nil, nil, nil, nil, nil, nil, nil, nil}, + {nil, nil, nil, nil, nil, nil, nil, nil}, + {nil, nil, nil, nil, nil, nil, nil, nil}, + }, + en_passant_target = {rank = 6, file = 4}, + } + local expected = {"exd6"} + luaunit.assertItemsEquals(legal_captures(position), expected) +end + +function test_cannot_capture_if_leads_to_check() + local position = { + turn = "white", + board = { + {nil, nil, nil, nil, "♔", nil, nil, nil}, + {nil, nil, nil, nil, "♗", nil, nil, nil}, + {nil, nil, nil, nil, nil, nil, nil, nil}, + {nil, nil, "♟︎", nil, nil, nil, nil, nil}, + {nil, nil, nil, nil, nil, nil, nil, nil}, + {nil, nil, nil, nil, "♜", nil, nil, nil}, + {nil, nil, nil, nil, nil, nil, nil, nil}, + {nil, nil, nil, nil, nil, nil, nil, nil}, + }, + en_passant_target = {rank = 6, file = 4}, + } + local expected = {} + luaunit.assertItemsEquals(legal_captures(position), expected) +end + +function test_position_with_lots_of_captures() + local position = { + turn = "white", + board = { + {"♚", nil, nil, "♔", "♝", nil, nil, "♖"}, + {nil, "♖", "♜", "♕", "♞", "♕", "♘", nil}, + {nil, nil, nil, nil, nil, nil, nil, nil}, + {"♗", "♛", "♕", "♝", "♕", "♛", "♕", "♞"}, + {nil, nil, nil, nil, nil, nil, nil, nil}, + {nil, "♕", "♝", "♕", "♛", "♕", "♘", "♗"}, + {nil, nil, nil, nil, nil, nil, nil, nil}, + {nil, nil, nil, "♜", "♕", "♛", nil, nil}, + }, + en_passant_target = {rank = 6, file = 4}, + } + local expected = { + "♔xe1", "♔xe2", "♔xc2", "♖xh4", "♖xe1", + "♖xb4", "♖xc2", "♕xf4", "♕xb4", "♕xe1", + "♕xd4", "♕xe2", "♕xc2", "♕xh4", "♕xd4", + "♕xe1", "♕xf4", "♕xe2", "♘xh4", "♘xf4", + "♘xe1", "♗xc6", "♗xc2", "♕xe6", "♕xe2", + "♕xc6", "♕xc2", "♕xd4", "♕xb4", "♕xc6", + "♕xc2", "♕xe6", "♕xe2", "♕xf4", "♕xd4", + "♕xe6", "♕xe2", "♕xh4", "♕xf4", "♕xd8", + "♕xd4", "♕xb4", "♕xc6", "♕xf8", "♕xf4", + "♕xb4", "♕xd8", "♕xd4", "♕xe6", "♕xc6", + "♕xd8", "♕xh4", "♕xd4", "♕xf8", "♕xf4", + "♕xe6", "♘xf8", "♘xh4", "♘xf4", "♗xf8", + "♗xf4", "♕xc6", "♕xe6", "♕xf8", "♕xd8", + } + local captures = legal_captures(position) + luaunit.assertItemsEquals(captures, expected) +end + +os.exit(luaunit.LuaUnit.run())