RosettaCodeData/Task/Generate-random-chess-position/Lua/generate-random-chess-position.lua
2026-04-30 12:34:36 -04:00

191 lines
5.1 KiB
Lua

-- Seed the random number generator with the current system time
math.randomseed(os.time())
-- Define a `Helper` table to store utility functions
local Helper = {}
-- Create a 1D table (array) filled with the same value
--- @param len number number of elements
--- @param val any value to fill each element with
function Helper.newFilledTable(len, val)
local t = {}
for i = 1, len do
t[i] = val
end
return t
end
-- Create a 2D table (rows x cols) initialized with a given value
---@param rows number number of rows
---@param cols number number of columns
---@param init any initial value for each cell
function Helper.new2D(rows, cols, init)
local b = {}
for r = 1, rows do
b[r] = {}
for c = 1, cols do
b[r][c] = init
end
end
return b
end
-- Compare two tables element by element for equality
-- Works only for flat 1D arrays
function Helper.tablesEqual(tbl1, tbl2)
if #tbl1 ~= #tbl2 then
error("Tables must have equal length")
end
if #tbl1 == 0 and #tbl2 == 0 then
return true
end
for i = 1, #tbl1 do
if tbl1[i] ~= tbl2[i] then
return false
end
end
return true
end
-- Initialise an 8x8 chess board (empty, filled with nils)
local board = Helper.new2D(8, 8, nil)
-- Place white and black kings randomly on the board
-- Ensures:
-- 1. They are not on the same square
-- 2. They are not adjacent (Chebyshev distance > 1)
local function placeKings(board)
local attempts = 0
while true do
attempts = attempts + 1
if attempts > 10000 then
error("Failed to place kings after many attempts")
end
-- Random row and column for each king
local wr = math.random(1, 8)
local wc = math.random(1, 8)
local br = math.random(1, 8)
local bc = math.random(1, 8)
local skip1 = false
local skip2 = false
-- Ensure kings are not on the same square
if wr == br and wc == bc then
skip1 = true
end
if not skip1 then
-- Check Chebyshev distance (max of row/col difference)
local dr = math.abs(wr-br)
local dc = math.abs(wc-bc)
local chebyshev = (dr > dc) and dr or dc
-- Skip if kings are adjacent
if chebyshev <= 1 then
skip2 = true
end
-- Place kings if squares are empty and valid
if not skip2 then
if not board[wr][wc] and not board[br][bc] then
board[wr][wc] = "K" -- White king
board[br][bc] = "k" -- Black king
return
end
end
end
end
end
-- Populate the board with a given set of pieces
---@param board table board
---@param pieces string string of piece characters (e.g. "PPPPPPPP")
---@param isPawn boolean boolean, true if placing pawns (to avoid ranks 1 and 8)
local function populate(board, pieces, isPawn)
local piecesAmount = #pieces
for i = 1, piecesAmount do
local pieceChar = pieces:sub(i, i)
local attempts = 0
while true do
attempts = attempts + 1
if attempts > 5000 then
error("Failed to place piece "..pieceChar)
end
-- Choose a random square
local r = math.random(1, 8)
local c = math.random(1, 8)
if board[r][c] then
-- Skip if already occupied
else
-- Prevent pawns on promotion ranks (1 or 8)
if isPawn and (r == 1 or r == 8) then
else
board[r][c] = pieceChar
break
end
end
end
end
end
-- Convert the board into Forsyth-Edwards Notation (FEN)
-- Only encodes piece placement and default values for side-to-move, castling, etc.
local function toFENFormat(board)
local result = ""
-- Loop through ranks 8 to 1 (FEN goes top-down)
for r = 8, 1, -1 do
local emptyCount = 0
for c = 1, 8 do
local row = board[r]
local ch = row and row[c]
if not ch then
-- Count consecutive empty squares
emptyCount = emptyCount + 1
else
-- Insert number for empty squares, then piece
if emptyCount > 0 then
result = result .. tostring(emptyCount)
emptyCount = 0
end
result = result .. tostring(ch)
end
end
-- Handle trailing empty squares in row
if emptyCount > 0 then
result = result .. tostring(emptyCount)
end
-- Add rank separator except for last row
if r > 1 then
result = result .. "/"
end
end
-- Append default FEN suffix ("w - - 0 1")
return result .. " w - - 0 1"
end
-- Main function --
placeKings(board)
populate(board, "PPPPPPPP", true)
populate(board, "pppppppp", true)
populate(board, "RNBQBNR", false)
populate(board, "rnbqbnr", false)
print(toFENFormat(board))