Determining the legality of a move in the game of checkers

I would like to know how to determine if a move in the game of checkers, also known as draughts, is legal or not.

Whenever I have tried to create this function, only one aspect has worked properly while many others have not.

Below is half of the script, which includes everything that is necessary.

``````function createCheckerboard(boardSize, squareSize, whiteColor, blackColor)
-- Set default values for the function parameters
boardSize = boardSize or 8
squareSize = squareSize or 1
whiteColor = whiteColor or BrickColor.new("White")
blackColor = blackColor or BrickColor.new("Black")

local squares = {}

local folder = Instance.new("Folder", workspace)
folder.Name = "CheckerBoard"

-- Loop through the rows and columns of the checkerboard
for row = 1, boardSize do
for col = 1, boardSize do
local square = Instance.new("Part", folder)

square.Anchored = true
square.Position = Vector3.new((row - 1) * squareSize - (boardSize / 2) * squareSize, 0.5, (col - 1) * squareSize - (boardSize / 2) * squareSize)
square.Size = Vector3.new(squareSize, 0, squareSize)
square.Material = Enum.Material.Wood
square.Name = row..col

if (row + col) % 2 == 0 then
square.BrickColor = whiteColor
else
square.BrickColor = blackColor
Instance.new("ClickDetector", square)
end
table.insert(squares, square)
end
end

return squares;
end
function placeCheckersPiece(board, row, col, pieceModel)
-- Find the square at the specified row and column
local square = board[(row - 1) * 8 + col]

-- Place the checkers piece on the square
local piece = pieceModel:Clone()
piece.Parent = square
piece:MoveTo(square.Position + Vector3.new(0, 1, 0))
end

function isLegalMove(board, fromRow, fromCol, toRow, toCol, plr)
-- Check if the move is within the bounds of the board
if fromRow < 1 or fromRow > 8 or fromCol < 1 or fromCol > 8 or toRow < 1 or toRow > 8 or toCol < 1 or toCol > 8 then
warn("Over the bounds")
return false
end

return true
end

function moveCheckersPiece(board, fromRow, fromCol, toRow, toCol, plr)
-- Find the square that the piece is currently on
local fromSquare = board[(fromRow - 1) * 8 + fromCol]

-- Find the square that the piece will be moved to
local toSquare = board[(toRow - 1) * 8 + toCol]

-- Get the checkers piece that is on the from square
local piece = fromSquare:FindFirstChildWhichIsA("Model")
-- Make sure there is a piece on the from square
if piece then
-- Check if the move is legal
if isLegalMove(board, fromRow, fromCol, toRow, toCol, plr) then
-- Move the piece to the to square
if piece then
piece:MoveTo(toSquare.Position + Vector3.new(0, 1, 0))
end
else
-- Print an error message
print("Illegal move!")
end
else
-- Print an error message
print("There is no piece on the from square!")
end
end
``````

Since the availability of moves depends on which pieces are in what places, it would be handy to be able to determine which pieces are in any spot given by itâ€™s coordinates:

``````function getPieceAt(board, row, col)
--return the piece that occupies the given spot
end
``````

Then we can do arithmetic on a pieceâ€™s coordinates to get the diagonally adjacent spots one and two steps away:

``````function coordCandidateMoveSpots(coord, plr)
return {
--1 left 1 forward
--2 left 2 forward
--1 right 1 forward
--2 right 2 forward
}
end

function coord(row, col)
return {row=row, col=col}
end

return coord(coordA.row + coordB.row, coordA.col + coordB.col)
end
``````

We can check if the given â€śtoâ€ť coordinate is one of the candidates:

``````function eqCoords(coordA, coordB)
return coordA.row == coordB.row and coordA.col == coordB.col
end

--Returns a function with it's first parameter fixed to a given value
function curry(f, arg1)
return function(...)
return f(arg1, ...)
end
end

--Returns true if f(v) is truthey for any v in t, otherwise returns false.
--I.e. checks if some condition holds for *any* value in a table
function any(t, f)
for _, v in pairs(t) do
if f(v) then
return true
end
end
return false
end

--In isLegalMove
local toCoordIsACandidate = any( --Does any...
candidateSpots, -- ... of the candidate spots ...
curry(eqCoords, toCoord) -- ... equal toCoord?d
)
if not toCoordIsACandidate then
return false
end
``````

Some conditions might make a candidate position invalid, e.g. being outside the board, but also if it already has a piece in it:

``````    --In isLegalMove
--The move cannot place a piece on any other piece
if nil ~= getPieceAt(board, toRow, toCol) then
return false
end
``````

and finally, if the to position is two diagonal moves away from the from position, then thatâ€™s invalid unless thereâ€™s an enemy piece in the position between those two:

``````    --In isLegalMove
--If the move jumps over a spot, an opponent piece must be in that spot
local dist = manhattanDistCoords(toCoord, fromCoord)
if dist == 4 then --4 orthogonal moves = 2 diagonal
local dRow, dCol = toRow - fromRow, toCol - toCol
local jumpedPiece = getPieceAt(board, fromRow + dRow/2, fromCol + dCol/2)
if jumpedPiece and jumpedPiece.Owner ~= plr then
return true
else
return false
end
elseif dist ~= 2 then  --2 orthogonal moves = 1 diagonal
error() --In case I'm thinking wrong or something :)
end
``````

For most of this code I used some helper functions that let me treat a pair of numbers as a single coordinate object, because thatâ€™s more convenient. Of course the built in Vector2 class already has most of this functionality, in my own code Iâ€™d definitely just use that.

Hope this helps, ask away if you have any questions.

1 Like