본문으로 이동

모듈:체스/이론표

위키책, 위키책

이 모듈에 대한 설명문서는 모듈:체스/이론표/설명문서에서 만들 수 있습니다

local p = {}

--re-written for this purpose, but originally from [[Module:Chess]]
local function pagename2pgn(pagename)

	local path = pagename:match("^Chess Opening Theory/(.+)")
	if not path then
		return ""
	end

	--clean-up so that parseMoves() can interpet
	local line = path:gsub("/", " ") --convert slashes to spaces
	line = line:gsub("%d+%.%.%.", "") -- removes 1... etc
	line = line:gsub("%d+%."      , "") -- removes 1. etc.
	line = mw.text.trim(line)

	return line
end

local function parseMoves(line)
	local moves = {}
	line = line or '' --prevents failure if line is nil

	--black to move: N... -> ...
	line = line:gsub("(%d+)%.%.%.", "... ")

	for token in line:gmatch("%S+") do
		--removes move number
		if not token:match("^%d+%.%.?") then
			table.insert(moves, token)
		end
	end

	return moves
end

local function deparseMoves(moves)
	local output = {}
	local move_number = 1

	for i = 1, #moves, 2 do
		local white = moves[i]
		local black = moves[i + 1]

		if black then
			table.insert(output, string.format("%d. %s %s", move_number, white, black))
		else
			table.insert(output, string.format("%d. %s", move_number, white))
		end

		move_number = move_number + 1
	end

	return table.concat(output, " ")
end

-- Reconstruct full move path leading up to the divergent move
local function collectParentMoves(lineIndex, lines)
    local currentLine = lines[lineIndex]
    local parentMoves = {}

    -- Number of moves in this line
    local numMoves = #currentLine.moves

    -- Walk through each move position in currentLine
    for pos = 1, numMoves do
        local move = currentLine.moves[pos]

        if move ~= "..." then
            -- We found the divergent move at this position; include it and stop here
            table.insert(parentMoves, move)
            break
        else
            -- move == "...", so look backwards to find first explicit move at this position
            local found = false
            for prevIndex = lineIndex - 1, 1, -1 do
                local prevLine = lines[prevIndex]
                if prevLine and prevLine.moves[pos] and prevLine.moves[pos] ~= "..." then
                    table.insert(parentMoves, prevLine.moves[pos])
                    found = true
                    break
                end
            end
        end
    end

    return parentMoves
end

local function renderTheoryTable(lines, startingMove, precedingMoves, links)
	local output = {}
	startingMove = tonumber(startingMove) - 1

	-- Get max number of half moves across all lines
	local max_moves = 0
	for _, line in ipairs(lines) do
		if #line.moves > max_moves then
			max_moves = #line.moves
		end
	end

	local full_move_columns = math.ceil(max_moves / 2)

	table.insert(output,'<strong>' .. deparseMoves(precedingMoves) .. '</strong>')
	table.insert(output, '{| cellspacing="0" cellpadding="4"')
	table.insert(output, '|- align="left"')

	local header = { "! " }

	local isWhiteToMove = (#precedingMoves % 2 == 0)
	local columnStart = startingMove + (isWhiteToMove and 1 or 0)

	for i = 1, full_move_columns do
		table.insert(header, string.format("%d", i + columnStart))
	end
	table.insert(header, " ")
	table.insert(output, table.concat(header, " !! "))
	table.insert(output, "|-")

	for rowIndex, line in ipairs(lines) do
		local row = {}
		table.insert(row, string.format('| align="right"| <strong>%s</strong>', line.name or ""))

		--the first original move in each variation should be linked.
		--firstlink keeps track of whether the 1st original move has been found
		--yet for each line
		local firstLinked = false
		local j = 0 --halfmove counter

		for i = 1, max_moves, 2 do --advances in two half moves, i.e. one full turn

			local white = line.moves[i] or ""
			local black = line.moves[i + 1] or ""
			local cell = ""

			--half=0=white's half move, half=1=black's half move
			for half = 0, 1 do
				local move = (half == 0) and white or black --white move if half=0, otherwise black
				if move ~= "" then --if line contains a halfmove here (some variations may end earlier than others)
					j = j + 1 --counts halfmoves for this line

					--if we are at the 1st original move
					if not firstLinked and move ~= "..." then
						firstLinked = true

						--build pgn up to this move. This is to generate the
						--article name for the link
						local pgn_path = {}
						local parent_line = lines[line.parent]

						local j2 = j --used for creating the link url. adjusted
						--if it's black to move.

						local all_moves = collectParentMoves(rowIndex, lines)

						--if we had to skip the first move
						if parent_line and parent_line.moves[1] == "..." then
							j2 = j2 - 1
						end

						--format link path
						for m = 1, #all_moves do
							local turn = math.ceil((m + #precedingMoves) / 2)
							local suffix = (m + #precedingMoves) % 2 == 1 and (turn .. ". ") or (turn .. "...")
							--remove annotations from past moves for the new article name
							local clean_move = mw.ustring.gsub(all_moves[m], "[!?+#]+", "")
							table.insert(pgn_path, suffix .. clean_move)
						end

						--add divergent move to link path
						local this_turn = math.ceil((j2 + #precedingMoves) / 2)
						local this_suffix = (j2 + #precedingMoves) % 2 == 1 and (this_turn .. ". ") or (this_turn .. "...")
						--remove annotations from the divergent move for the new article name only
						local clean_move = mw.ustring.gsub(move, "[!?+#]+", "")

						-- Use clean_move for the path, but keep move (with annotations) for display
						--table.insert(pgn_path, this_suffix .. clean_move)

						local link = "[[/" .. table.concat(pgn_path, "/") .. "|" .. move .. "]]"

						if links then move = link end

					end
					cell = cell .. (cell ~= "" and "<br>" or "") .. move
				end
			end

			table.insert(row, "|| " .. cell)
		end

		table.insert(row, "|| " .. (line.eval or ""))
		table.insert(output, table.concat(row, " "))
		table.insert(output, "|-")
	end

	table.insert(output, "|}")
	return table.concat(output, "\n")
end

function p.parse(frame)
	local args = frame:getParent().args
	local lines = {}
	local links = args.links or true

	if links == 'true' or links == '1' then
    	links = true
	elseif links == 'false' or links == '0' then
    	links = false
	end

	local startingMove = tonumber(args.beginning) or 0
	local precedingMoves
	if args.preceding and args.preceding ~= "" then
		precedingMoves = parseMoves(args.preceding)
	else
		precedingMoves = parseMoves(pagename2pgn(mw.title.getCurrentTitle().text))
	end

	if startingMove == 0 then
		startingMove =  math.ceil(#precedingMoves / 2)
	end

	for key, val in pairs(args) do
		--filters just for supplied parameters with names like lineN
		local index = key:match("^line(%d+)$")
		if index and val and val ~= "" then
			local name = args["name" .. index] or ""
			local eval = args["eval" .. index] or ""
			local moves = parseMoves(val)

			--determine how deep a variation is by counting
			--leading "..." as dotcount
			local dotcount = 0
			for _, move in ipairs(moves) do
				if move == "..." then
					dotcount = dotcount + 1
				else
					break
				end
			end

			--adds each line as a new row in the lines datafame.
			table.insert(lines, {
				index = tonumber(index),
				name = name,
				moves = moves,
				eval = eval,
				dotcount = dotcount,
				parent = nil -- to be set later
			})
		end
	end

	-- Sort by line number
	table.sort(lines, function(a, b)
		return a.index < b.index
	end)

	--determine variation parenthood, based on most recent line with fewer dots
	--Set parent line for each line based on dotcount
	for i, line in ipairs(lines) do
		for j = i - 1, 1, -1 do
			if lines[j].dotcount < line.dotcount then
				line.parent = j
				break
			end
		end

		-- fallback: assume top-level if no parent found
		if not line.parent then
			line.parent = i
		end
	end

	-- Debug output
	--local debug = {}
	--for _, row in ipairs(lines) do
	--	local summary = string.format("[%s] %s | %s | %s", row.index, row.name or "(no name)", table.concat(row.moves, " "), row.eval)
	--	table.insert(debug, summary)
	--end

	return renderTheoryTable(lines,startingMove,precedingMoves,links)
end

return p