<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://mywikibiz.com/index.php?action=history&amp;feed=atom&amp;title=Module%3APgn</id>
	<title>Module:Pgn - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://mywikibiz.com/index.php?action=history&amp;feed=atom&amp;title=Module%3APgn"/>
	<link rel="alternate" type="text/html" href="https://mywikibiz.com/index.php?title=Module:Pgn&amp;action=history"/>
	<updated>2026-06-13T22:12:42Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.35.3</generator>
	<entry>
		<id>https://mywikibiz.com/index.php?title=Module:Pgn&amp;diff=478930&amp;oldid=prev</id>
		<title>Zoran: Pywikibot 6.4.0</title>
		<link rel="alternate" type="text/html" href="https://mywikibiz.com/index.php?title=Module:Pgn&amp;diff=478930&amp;oldid=prev"/>
		<updated>2021-07-16T05:17:54Z</updated>

		<summary type="html">&lt;p&gt;Pywikibot 6.4.0&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;--[[&lt;br /&gt;
the purpose of this module is to provide pgn analysis local functionality&lt;br /&gt;
main local function, called pgn2fen:&lt;br /&gt;
input: either algebraic notation or full pgn of a single game&lt;br /&gt;
output: &lt;br /&gt;
* 1 table of positions (using FEN notation), one per each move of the game&lt;br /&gt;
* 1 lua table with pgn metadata (if present)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
purpose:&lt;br /&gt;
	using this local , we can create utility local functions to be used by templates.&lt;br /&gt;
	the utility local function will work something like so:&lt;br /&gt;
	it receives (in addition to the pgn, of course) list of moves and captions, and some wikicode in &amp;quot;nowiki&amp;quot; tag.&lt;br /&gt;
	per each move, it will replace the token FEN with the fen of the move, and the token COMMENT with the comment (if any) of the move.&lt;br /&gt;
	it will then parse the wikicode, return all the parser results concataneted.&lt;br /&gt;
	others may fund other ways to use it.&lt;br /&gt;
&lt;br /&gt;
the logic:&lt;br /&gt;
the analysis part copies freely from the javascipt &amp;quot;pgn&amp;quot; program.&lt;br /&gt;
&lt;br /&gt;
main object: &amp;quot;board&amp;quot;: 0-based table(one dimensional array) of 64 squares (0-63), &lt;br /&gt;
	each square is either empty or contains the letter of the charToFile, e.g., &amp;quot;pP&amp;quot; is pawn.&lt;br /&gt;
&lt;br /&gt;
utility local functions&lt;br /&gt;
index to row/col&lt;br /&gt;
row/col to index&lt;br /&gt;
disambig(file, row): if file is number, return it, otherwise return rowtoindex().&lt;br /&gt;
create(fen): returns ready board&lt;br /&gt;
generateFen(board) - selbverständlich&lt;br /&gt;
&lt;br /&gt;
pieceAt(coords): returns the piece at row/col&lt;br /&gt;
findPieces(piece): returns list of all squares containing specific piece (&amp;quot;black king&amp;quot;, &amp;quot;white rook&amp;quot; etc).&lt;br /&gt;
roadIsClear(start/end row/column): start and end _must_ be on the same row, same column, or a diagonal. will error if not.&lt;br /&gt;
	returns true if all the squares between start and end are clear.&lt;br /&gt;
canMove(source, dest, capture): boolean (capture is usually reduntant, except for en passant)&lt;br /&gt;
promote(coordinate, designation, color)&lt;br /&gt;
move(color, algebraic notation): finds out which piece should move, error if no piece or more than one piece found,&lt;br /&gt;
	and execute the move.&lt;br /&gt;
	&lt;br /&gt;
rawPgnAnalysis(input)&lt;br /&gt;
gets a pgn or algebraic notation, returns a table withthe metadata, and a second table with the algebraic notation individual moves&lt;br /&gt;
&lt;br /&gt;
main:&lt;br /&gt;
-- metadata, notations := rawPgnAnalysis(input)&lt;br /&gt;
-- result := empty table&lt;br /&gt;
-- startFen := metadata.fen || default; results += startFen&lt;br /&gt;
-- board := create(startFen)&lt;br /&gt;
-- loop through notations &lt;br /&gt;
----- pass board, color and notation, get modified board&lt;br /&gt;
----- results += generateFen()&lt;br /&gt;
-- return result&lt;br /&gt;
&lt;br /&gt;
the &amp;quot;meat&amp;quot; is the &amp;quot;canMove. however, as it turns out, it is not that difficult.&lt;br /&gt;
the only complexity is with pawns, both because they are asymmetrical, and irregular. brute force (as elegantly as possible)&lt;br /&gt;
&lt;br /&gt;
other pieces are a breeze. color does not matter. calc da := abs(delta raw), db := abs(delta column)&lt;br /&gt;
piece  | rule&lt;br /&gt;
Knight: 	da * db - 2 = 0&lt;br /&gt;
Rook:		da * db = 0&lt;br /&gt;
Bishop: 	da - db = 0&lt;br /&gt;
King		db | db = 1 (bitwise or)&lt;br /&gt;
Queen		da * db * (da - db) = 0&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
move:&lt;br /&gt;
find out which piece. find all of them on the board. ask each if it can execute the move, and count &amp;quot;yes&amp;quot;. &lt;br /&gt;
	there should be only one yes (some execptions to be handled). execute the move. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local BLACK = &amp;quot;black&amp;quot;&lt;br /&gt;
local WHITE = &amp;quot;white&amp;quot;&lt;br /&gt;
&lt;br /&gt;
local PAWN  = &amp;quot;P&amp;quot;&lt;br /&gt;
local ROOK  = &amp;quot;R&amp;quot;&lt;br /&gt;
local KNIGHT = &amp;quot;N&amp;quot;&lt;br /&gt;
local BISHOP = &amp;quot;B&amp;quot;&lt;br /&gt;
local QUEEN = &amp;quot;Q&amp;quot;&lt;br /&gt;
local KING = &amp;quot;K&amp;quot;&lt;br /&gt;
&lt;br /&gt;
local KINGSIDE = 7&lt;br /&gt;
local QUEENSIDE = 12&lt;br /&gt;
&lt;br /&gt;
local DEFAULT_BOARD = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR'&lt;br /&gt;
&lt;br /&gt;
local bit32 = bit32 or require('bit32')&lt;br /&gt;
&lt;br /&gt;
--[[ following lines require when running locally - uncomment.&lt;br /&gt;
mw = mw or {&lt;br /&gt;
	ustring = string,&lt;br /&gt;
	text = {&lt;br /&gt;
		['split'] = local function(s, pattern)&lt;br /&gt;
			local res = {}&lt;br /&gt;
			while true do&lt;br /&gt;
				local start, finish = s:find(pattern)&lt;br /&gt;
				if finish and finish &amp;gt; 1 then&lt;br /&gt;
					local frag = s:sub(1, start - 1)&lt;br /&gt;
					table.insert(res, frag)&lt;br /&gt;
					s = s:sub(finish + 1) &lt;br /&gt;
				else&lt;br /&gt;
					break&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
			if #s then table.insert(res, s) end&lt;br /&gt;
			return res&lt;br /&gt;
		end,&lt;br /&gt;
		['trim'] = local function(t)&lt;br /&gt;
			t = type(t) == 'string' and t:gsub('^%s+', '')&lt;br /&gt;
			t = t:gsub('%s+$', '')&lt;br /&gt;
			return t&lt;br /&gt;
		end&lt;br /&gt;
	}&lt;br /&gt;
}&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- in lua 5.3, unpack is not a first class citizen anymore, but - assign table.unpack&lt;br /&gt;
local unpack = unpack or table.unpack&lt;br /&gt;
		&lt;br /&gt;
local function apply(f, ...)&lt;br /&gt;
	res = {}&lt;br /&gt;
	targ = {...}&lt;br /&gt;
	for ind = 1, #targ do&lt;br /&gt;
		res[ind] = f(targ[ind])&lt;br /&gt;
	end&lt;br /&gt;
	return unpack(res)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function empty(s)&lt;br /&gt;
	return not s or mw.text.trim(s) == ''&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function falseIfEmpty(s)&lt;br /&gt;
	return not empty(s) and s&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function charToFile(ch)&lt;br /&gt;
	return falseIfEmpty(ch) and string.byte(ch) - string.byte('a')&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function charToRow(ch)&lt;br /&gt;
	return falseIfEmpty(ch) and tonumber(ch) - 1&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function indexToCoords(index)&lt;br /&gt;
	return index % 8, math.floor(index / 8)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function coordsToIndex(file, row) &lt;br /&gt;
	return row * 8 + file&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function charToPiece(letter)&lt;br /&gt;
	local piece = mw.ustring.upper(letter)&lt;br /&gt;
	return piece, piece == letter and WHITE or BLACK&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function pieceToChar(piece, color)&lt;br /&gt;
	return color == WHITE and piece or mw.ustring.lower(piece)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function ambigToIndex(file, row)&lt;br /&gt;
	if row == nil then return file end&lt;br /&gt;
	return coordsToIndex(file, row)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function enPasantRow(color)&lt;br /&gt;
	return color == WHITE and 5 or 2&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function sign(a)&lt;br /&gt;
	return a &amp;lt; 0 and -1 &lt;br /&gt;
		or a &amp;gt; 0 and 1 &lt;br /&gt;
		or 0&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function pieceAt(board, fileOrInd, row) -- called with 2 params, fileOrInd is the index, otherwise it's the file.&lt;br /&gt;
	local letter = board[ambigToIndex(fileOrInd, row)] &lt;br /&gt;
	if not letter then return end&lt;br /&gt;
	return charToPiece(letter)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function findPieces(board, piece, color)&lt;br /&gt;
	local result = {}&lt;br /&gt;
	local lookFor = pieceToChar(piece, color)&lt;br /&gt;
	for index = 0, 63 do&lt;br /&gt;
		local letter = board[index]&lt;br /&gt;
		if letter == lookFor then table.insert(result, index) end&lt;br /&gt;
	end&lt;br /&gt;
	return result&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function roadIsClear(board, ind1, ind2)&lt;br /&gt;
	if ind1 == ind2 then error('call to roadIsClear with identical indices', ind1) end&lt;br /&gt;
	local file1, row1 = indexToCoords(ind1)&lt;br /&gt;
	local  file2, row2 = indexToCoords(ind2) &lt;br /&gt;
	if (file1 - file2) * (row1 - row2) * (math.abs(row1 - row2) - math.abs(file1 - file2)) ~= 0 then&lt;br /&gt;
		error('sent two indices to roadIsClear which are not same row, col, or diagonal: ', ind1, ind2)&lt;br /&gt;
	end&lt;br /&gt;
	local hdelta = sign(file2 - file1)&lt;br /&gt;
	local vdelta = sign(row2 - row1)&lt;br /&gt;
	local row, file = row1 + vdelta, file1 + hdelta&lt;br /&gt;
	while row ~= row2 or file ~= file2 do&lt;br /&gt;
		if pieceAt(board, file, row) then return false end&lt;br /&gt;
		row = row + vdelta&lt;br /&gt;
		file = file + hdelta&lt;br /&gt;
	end&lt;br /&gt;
	return true&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function pawnCanMove(board, color, startFile, startRow, file, row, capture)&lt;br /&gt;
	local hor, ver = file - startFile, row - startRow&lt;br /&gt;
	local absVer = math.abs(ver)&lt;br /&gt;
	if capture then&lt;br /&gt;
		local ok = hor * hor == 1 and (&lt;br /&gt;
				color == WHITE and ver == 1 or&lt;br /&gt;
				color == BLACK and ver == - 1&lt;br /&gt;
			)&lt;br /&gt;
			&lt;br /&gt;
		local enpassant = ok and&lt;br /&gt;
			row == enPasantRow(color) and &lt;br /&gt;
			pieceAt(board, file, row) == nil&lt;br /&gt;
		return ok, enpassant&lt;br /&gt;
	else &lt;br /&gt;
		if hor ~= 0 then return false end&lt;br /&gt;
	end&lt;br /&gt;
	if absVer == 2 then&lt;br /&gt;
		if not roadIsClear(board, coordsToIndex(startFile, startRow), coordsToIndex(file, row)) then return false end&lt;br /&gt;
		return color == WHITE and startRow == 1 and ver == 2 or&lt;br /&gt;
			color == BLACK and startRow == 6 and ver == -2&lt;br /&gt;
	end&lt;br /&gt;
	return color == WHITE and ver == 1 or color == BLACK and ver == -1&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function canMove(board, start, dest, capture, verbose)&lt;br /&gt;
	local startFile, startRow = indexToCoords(start) &lt;br /&gt;
	local file, row = indexToCoords(dest)&lt;br /&gt;
	local piece, color = pieceAt(board, startFile, startRow)&lt;br /&gt;
	if piece == PAWN then return pawnCanMove(board, color, startFile, startRow, file, row, capture) end&lt;br /&gt;
	local dx, dy = math.abs(startFile - file), math.abs(startRow - row)&lt;br /&gt;
	return 	piece == KNIGHT and dx * dy == 2&lt;br /&gt;
			or piece == KING and bit32.bor(dx, dy) == 1 &lt;br /&gt;
			or (&lt;br /&gt;
				piece == ROOK and dx * dy == 0 &lt;br /&gt;
				or piece == BISHOP and dx == dy &lt;br /&gt;
				or piece == QUEEN and dx * dy * (dx - dy) == 0&lt;br /&gt;
			) and roadIsClear(board, start, dest, verbose)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function exposed(board, color) -- only test for queen, rook, bishop.&lt;br /&gt;
	local king = findPieces(board, KING, color)[1]&lt;br /&gt;
	for ind = 1, 63 do&lt;br /&gt;
		local letter = board[ind]&lt;br /&gt;
		if letter then&lt;br /&gt;
			local _, pcolor = charToPiece(letter)&lt;br /&gt;
			if pcolor ~= color and canMove(board, ind, king, true) then&lt;br /&gt;
				return true &lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function clone(orig)&lt;br /&gt;
	local res = {}&lt;br /&gt;
	for k, v in pairs(orig) do res[k] = v end&lt;br /&gt;
	return res&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function place(board, piece, color, file, row) -- in case of chess960, we have to search&lt;br /&gt;
	board[ambigToIndex(file, row)] = pieceToChar(piece, color)&lt;br /&gt;
	return board&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function clear(board, file, row)&lt;br /&gt;
	board[ambigToIndex(file, row)] = nil&lt;br /&gt;
	return board&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function doCastle(board, color, side)&lt;br /&gt;
	local row = color == WHITE and 0 or 7&lt;br /&gt;
	local startFile, step = 0, 1&lt;br /&gt;
	local kingDestFile, rookDestFile = 2, 3&lt;br /&gt;
	local king = findPieces(board, KING, color)[1]&lt;br /&gt;
	local rook&lt;br /&gt;
	if side == KINGSIDE then&lt;br /&gt;
		startFile, step = 7, -1&lt;br /&gt;
		kingDestFile, rookDestFile = 6, 5&lt;br /&gt;
	end&lt;br /&gt;
	for file = startFile, 7 - startFile, step do&lt;br /&gt;
		local piece = pieceAt(board, file, row)&lt;br /&gt;
		if piece == ROOK then&lt;br /&gt;
			rook = coordsToIndex(file, row)&lt;br /&gt;
			break&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	board = clear(board, king)&lt;br /&gt;
	board = clear(board, rook)&lt;br /&gt;
	board = place(board, KING, color, kingDestFile, row) &lt;br /&gt;
	board = place(board, ROOK, color, rookDestFile, row)&lt;br /&gt;
	return board&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function doEnPassant(board, pawn, file, row)&lt;br /&gt;
	local _, color = pieceAt(board, pawn)&lt;br /&gt;
	board = clear(board, pawn)&lt;br /&gt;
	board = place(board, PAWN, color, file, row)&lt;br /&gt;
	if row == 5 then board = clear(board, file, 4) end&lt;br /&gt;
	if row == 2 then board = clear(board, file, 3) end&lt;br /&gt;
	return board&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function generateFen(board)&lt;br /&gt;
	local res = ''&lt;br /&gt;
	local offset = 0&lt;br /&gt;
	for row = 7, 0, -1 do&lt;br /&gt;
		for file = 0, 7 do&lt;br /&gt;
			piece = board[coordsToIndex(file, row)]&lt;br /&gt;
			res = res .. (piece or '1')&lt;br /&gt;
		end&lt;br /&gt;
		if row &amp;gt; 0 then res = res .. '/' end&lt;br /&gt;
	end&lt;br /&gt;
	return mw.ustring.gsub(res, '1+', function( s ) return #s end )&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function findCandidate(board, piece, color, oldFile, oldRow, file, row, capture, notation)&lt;br /&gt;
	local enpassant = {}&lt;br /&gt;
	local candidates, newCands = findPieces(board, piece, color), {} -- all black pawns or white kings etc.&lt;br /&gt;
	if oldFile or oldRow then &lt;br /&gt;
		local newCands = {}&lt;br /&gt;
		for _, cand in ipairs(candidates) do&lt;br /&gt;
			local file, row = indexToCoords(cand)&lt;br /&gt;
			if file == oldFile then table.insert(newCands, cand) end&lt;br /&gt;
			if row == oldRow then table.insert(newCands, cand) end&lt;br /&gt;
		end&lt;br /&gt;
		candidates, newCands = newCands, {}&lt;br /&gt;
	end&lt;br /&gt;
	local dest = coordsToIndex(file, row)&lt;br /&gt;
	for _, candidate in ipairs(candidates) do&lt;br /&gt;
		local can&lt;br /&gt;
		can, enpassant[candidate] = canMove(board, candidate, dest, capture)&lt;br /&gt;
		if can then table.insert(newCands, candidate) end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	candidates, newCands = newCands, {}&lt;br /&gt;
	if #candidates == 1 then return candidates[1], enpassant[candidates[1]] end&lt;br /&gt;
	if #candidates == 0 then &lt;br /&gt;
		error('could not find a piece that can execute ' .. notation) &lt;br /&gt;
	end&lt;br /&gt;
	-- we have more than one candidate. this means that all but one of them can't really move, b/c it will expose the king &lt;br /&gt;
	-- test for it by creating a new board with this candidate removed, and see if the king became exposed&lt;br /&gt;
	for _, candidate in ipairs(candidates) do &lt;br /&gt;
		local cloneBoard = clone(board) -- first, clone the board&lt;br /&gt;
		cloneBoard = clear(cloneBoard, candidate) -- now, remove the piece&lt;br /&gt;
		if not exposed(cloneBoard, color) then table.insert(newCands, candidate) end&lt;br /&gt;
	end&lt;br /&gt;
	candidates, newCands = newCands, {}&lt;br /&gt;
	if #candidates == 1 then return candidates[1] end&lt;br /&gt;
	error(mw.ustring.format('too many (%d, expected 1) pieces can execute %s at board %s', #candidates, notation, generateFen(board)))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function move(board, notation, color)&lt;br /&gt;
	local endGame = {['1-0']=true, ['0-1']=true, ['1/2-1/2']=true, ['*']=true}&lt;br /&gt;
&lt;br /&gt;
	local cleanNotation = mw.ustring.gsub(notation, '[!?+# ]', '')&lt;br /&gt;
&lt;br /&gt;
	if cleanNotation == 'O-O' then&lt;br /&gt;
		return doCastle(board, color, KINGSIDE)&lt;br /&gt;
	end&lt;br /&gt;
	if cleanNotation == 'O-O-O' then&lt;br /&gt;
		return doCastle(board, color, QUEENSIDE)&lt;br /&gt;
	end&lt;br /&gt;
	if endGame[cleanNotation] then&lt;br /&gt;
		return board, true&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local pattern = '([RNBKQ]?)([a-h]?)([1-8]?)(x?)([a-h])([1-8])(=?[RNBKQ]?)'&lt;br /&gt;
	local _, _, piece, oldFile, oldRow, isCapture, file, row, promotion = mw.ustring.find(cleanNotation, pattern)&lt;br /&gt;
	oldFile, file = apply(charToFile, oldFile, file) &lt;br /&gt;
	oldRow, row = apply(charToRow, oldRow, row)&lt;br /&gt;
	piece = falseIfEmpty(piece) or PAWN&lt;br /&gt;
	promotion = falseIfEmpty(promotion)&lt;br /&gt;
	isCapture = falseIfEmpty(isCapture)&lt;br /&gt;
	local candidate, enpassant = findCandidate(board, piece, color, oldFile, oldRow, file, row, isCapture, notation) -- findCandidates should panic if # != 1&lt;br /&gt;
	if enpassant then&lt;br /&gt;
		return doEnPassant(board, candidate, file, row)&lt;br /&gt;
	end&lt;br /&gt;
	board[coordsToIndex(file, row)] = promotion and pieceToChar(promotion:sub(-1), color) or board[candidate]&lt;br /&gt;
	board = clear(board, candidate)&lt;br /&gt;
	return board&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function create( fen )&lt;br /&gt;
	-- converts FEN notation to 64 entry array of positions. copied from enwiki Module:Chessboard (in some distant past i prolly wrote it)&lt;br /&gt;
	local res = {}&lt;br /&gt;
	local row = 8&lt;br /&gt;
	-- Loop over rows, which are delimited by /&lt;br /&gt;
	for srow in string.gmatch( &amp;quot;/&amp;quot; .. fen, &amp;quot;/%w+&amp;quot; ) do&lt;br /&gt;
	srow = srow:sub(2)&lt;br /&gt;
		row = row - 1&lt;br /&gt;
		local ind = row * 8&lt;br /&gt;
		-- Loop over all letters and numbers in the row&lt;br /&gt;
		for piece in srow:gmatch( &amp;quot;%w&amp;quot; ) do&lt;br /&gt;
			if piece:match( &amp;quot;%d&amp;quot; ) then -- if a digit&lt;br /&gt;
				ind = ind + piece&lt;br /&gt;
			else -- not a digit&lt;br /&gt;
				res[ind] = piece&lt;br /&gt;
				ind = ind + 1&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return res&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function processMeta(grossMeta) &lt;br /&gt;
	res = {}&lt;br /&gt;
	-- process grossMEta here&lt;br /&gt;
	for item in mw.ustring.gmatch(grossMeta or '', '%[([^%]]*)%]') do&lt;br /&gt;
		key, val = item:match('([^&amp;quot;]+)&amp;quot;([^&amp;quot;]*)&amp;quot;')&lt;br /&gt;
		if key and val then&lt;br /&gt;
			res[mw.text.trim(key)] = mw.text.trim(val) -- add mw.text.trim()&lt;br /&gt;
		else&lt;br /&gt;
			error('strange item detected: ' .. item .. #items) -- error later&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return res&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function analyzePgn(pgn)&lt;br /&gt;
	local grossMeta = pgn:match('%[(.*)%]') -- first open to to last bracket &lt;br /&gt;
	pgn = string.gsub(pgn, '%[(.*)%]', '')&lt;br /&gt;
	local steps = mw.text.split(pgn, '%s*%d+%.%s*')&lt;br /&gt;
	local moves = {}&lt;br /&gt;
	for _, step in ipairs(steps) do&lt;br /&gt;
		if mw.ustring.len(mw.text.trim(step)) then&lt;br /&gt;
			ssteps = mw.text.split(step, '%s+')&lt;br /&gt;
			for _, sstep in ipairs(ssteps) do &lt;br /&gt;
				if sstep and not mw.ustring.match(sstep, '^%s*$') then table.insert(moves, sstep) end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return processMeta(grossMeta), moves&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function pgn2fen(pgn)&lt;br /&gt;
	local metadata, notationList = analyzePgn(pgn)&lt;br /&gt;
	local fen = metadata.fen or DEFAULT_BOARD&lt;br /&gt;
	local board = create(fen)&lt;br /&gt;
	local res = {fen}&lt;br /&gt;
	local colors = {BLACK, WHITE} &lt;br /&gt;
	for step, notation in ipairs(notationList) do&lt;br /&gt;
		local color = colors[step % 2 + 1]&lt;br /&gt;
		board = move(board, notation, color)&lt;br /&gt;
		local fen = generateFen(board)&lt;br /&gt;
		table.insert(res, fen)&lt;br /&gt;
	end&lt;br /&gt;
	return res, metadata&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return {&lt;br /&gt;
	pgn2fen = pgn2fen,&lt;br /&gt;
	main = function(pgn) &lt;br /&gt;
		local res, metadata = pgn2fen(pgn) &lt;br /&gt;
		return metadata, res &lt;br /&gt;
	end,&lt;br /&gt;
}&lt;/div&gt;</summary>
		<author><name>Zoran</name></author>
	</entry>
</feed>