Module:games

local m_fun = require("Module:fun") local m_str_utils = require("Module:string utilities")

local codepoint = m_str_utils.codepoint local concat = table.concat local insert = table.insert local invert = require("Module:table").invert local lower = m_str_utils.lower local map = m_fun.map local mapIter = m_fun.mapIter local remove = table.remove local sub = m_str_utils.sub local toNFC = mw.ustring.toNFC local toNFD = mw.ustring.toNFD

local export = {}

local title = mw.title.getCurrentTitle local namespace = title.nsText local fullpagename = title.fullText

-- local langbr = "⟨" local rangbr = "⟩" --

local function quote(word) return "“" .. word .. "”" end

local function link(word, lang) if word:find("%[%[") then return word else if lang then return "" .. word .. "" else return "" .. word .. "" end end end

local function italicize(word) if word:find("''") then return word else return "''" .. word .. "''"	end end

local function tag(word, script) return ' ' .. word .. ' ' end

local function add_color(color, text, attribute) return '' .. text .. ' ' end

local is_combining = require "Module:Unicode data".is_combining

local find_best_script = require "Module:scripts".findBestScriptWithoutLang

local translit = { Hang = "ko-translit", ja = { "Hrkt-translit", "tr", "ja" }, }

local non_diacritic_equivalencies = { ["ð"] = "d", ["ø"] = "o", ["þ"] = "???", ["đ"] = "d", ["ħ"] = "h", ["ı"] = "i", ["ŋ"] = "n", ["œ"] = "???", ["ŧ"] = "t", ["ſ"] = "s", ["ß"] = "???", ["ł"] = "l", ["ᵃ"] = "a", ["ᵇ"] = "b", ["ᶜ"] = "c", ["ᵈ"] = "d", ["ᵉ"] = "e", ["ᶠ"] = "f", ["ᵍ"] = "g", ["ʰ"] = "h", ["ⁱ"] = "i", ["ʲ"] = "j", ["ᵏ"] = "k", ["ˡ"] = "l", ["ᵐ"] = "m", ["ⁿ"] = "n", ["ᵒ"] = "o", ["ᵖ"] = "p", ["ʳ"] = "r", ["ˢ"] = "s", ["ᵗ"] = "t", ["ᵘ"] = "u", ["ᵛ"] = "v", ["ʷ"] = "w", ["ˣ"] = "x", ["ʸ"] = "y", ["ᶻ"] = "z", ["ᴬ"] = "a", ["ᴮ"] = "b", ["ᴰ"] = "d", ["ᴱ"] = "e", ["ᴳ"] = "g", ["ᴴ"] = "h", ["ᴵ"] = "i", ["ᴶ"] = "j", ["ᴷ"] = "k", ["ᴸ"] = "l", ["ᴹ"] = "m", ["ᴺ"] = "n", ["ᴼ"] = "o", ["ᴾ"] = "p", ["ᴿ"] = "r", ["ᵀ"] = "t", ["ᵁ"] = "u", ["ⱽ"] = "v", ["ᵂ"] = "w", ["ₐ"] = "a", ["ₑ"] = "e", ["ₕ"] = "h", ["ᵢ"] = "i", ["ⱼ"] = "j", ["ₖ"] = "k", ["ₗ"] = "l", ["ₘ"] = "m", ["ₙ"] = "n", ["ₒ"] = "o", ["ₚ"] = "o", ["ᵣ"] = "r", ["ₛ"] = "s", ["ₜ"] = "t", ["ᵤ"] = "u", ["ᵥ"] = "v", ["ₓ"] = "x", -- ["ŋ"] = "n", ["ɔ"] = "o", ["ʿ"] = "", ["ʾ"] = "", ["ʻ"] = "", ["ʼ"] = "", ["‘"] = "", ["’"] = "", -- ... }

-- Remove all diacritics. Make use of the replacements shown above. -- Remove spaces and hyphens. local function regularize(word) return (lower(toNFD(word))		:gsub( "[\194-\244][\128-\191]+",			function (non_ASCII_character) if is_combining(codepoint(non_ASCII_character)) then return "" end return non_diacritic_equivalencies[non_ASCII_character] end)) :gsub(			"[ %-%']",			"") end

-- Allowed parameters: -- -- numbers -- -- lang, lang1, lang2, ... -- -- tr, tr1, tr2, ... -- -- ignore rules (boolean) local function get_args(args) local words = {} local langs, translits, ignore_rules for arg, value in pairs(args) do		if type(arg) == "number" then words[arg] = value elseif type(arg) == "string" then if arg == "ignore rules" then ignore_rules = require("Module:yesno")(value) else local name, number = arg:match("^(%l+)(%d*)$") if not (name == "lang" or name == "tr") then error("The parameter |" .. arg .. "= is not used by this template.") end if number == "" then number = 1 else number = tonumber(number) end if name == "lang" then langs = langs or {} langs[number] = langs[number] and error("Two langs with index " .. number .. ".") or value and (require("Module:languages").getByCode(value)						or error("Invalid language code " .. quote(value) .. "."))				else translits = translits or {} translits[number] = translits[number] and error("Two translits with index " .. number .. ".") or value end end end end return words, langs, translits, ignore_rules end

local function show_words(previous_word, interposing_words, following_word) return link(previous_word) .. " ("		.. concat( map(				function(word)					return italicize(link(word))				end,				interposing_words), ", ")		.. ") "		.. link(following_word) end

function export.Christmas_Competition_entry(frame) local words, langs, translits, ignore_rules = get_args(frame:getParent.args) if namespace == "" or namespace == "Reconstruction" or namespace == "Appendix" then error("This template is only supposed to be used in the Christmas Competition!") end words.length = #words if words.length < 3 then if namespace == "Template" then words = { "Shèngdànjié", "jiellat", "llatino" } else error("This template wants at least three words.") end end local regularized_words = {} local nonLatin_i = 0 local last_interposing_word = (words.length or #words) - 1 for i, word in ipairs(words) do -- Check for duplicate interposing words. if 1 < i and i < last_interposing_word then for j = i + 1, last_interposing_word do				if word == words[j] then error("You used the word " .. quote(word) .. " twice, in parameters " .. i .. " and " .. j .. "!") end end end local tr		if word:find("[\128-\255]") then -- non-ASCII local script = find_best_script(word):getCode if script and not script:find "Lat" then -- non-Latin -- Module:ko-translit requires word to be in NFC. nonLatin_i = nonLatin_i + 1 local lang = langs and langs[nonLatin_i] if translits and translits[nonLatin_i] then tr = translits[nonLatin_i] elseif lang then tr = lang:transliterate(toNFC(word), require("Module:scripts").getByCode(script)) or lang:transliterate(toNFC(word)) or translit[lang:getCode] and require("Module:" .. translit[lang:getCode][1])[translit[lang:getCode][2]](word, unpack(translit[lang:getCode], 3)) or error("The " .. lang:getCode							.. " transliteration module was unable to generate a transliteration for "							.. quote(word) .. ".") elseif translit[script] then local success, result = pcall(require("Module:" .. translit[script]).tr, toNFC(word), script) tr = success and result or error("Module:"						.. translit[script]						.. " wasn't able to generate a transliteration for "						.. quote(word) .. ".") else error("You need to give the language code for the word "						.. quote(word)						.. " in the |lang" .. nonLatin_i .. "= parameter so that the word can be transliterated.") end if tr then if i == 1 or i == words.length then word = tag(link(word, lang), script) .. " (" .. tr .. ")" else word = tag(link(word, lang), script) .. " (" .. italicize(tr) .. ")" end -- word = italicize(tr) .. " " .. langbr .. tag(link(word), script) .. rangbr end end end words[i] = word regularized_words[i] = regularize(tr or word) end -- Complain if there aren't as many non-Latin-script words as lang parameters. if langs then if nonLatin_i < #langs then error(#langs .. " |lang= parameter"				.. (#langs == 1 and "" or "s") .. ", but only "				.. nonLatin_i .. " non-Latin-script word"				.. (nonLatin_i == 1 and "" or "s") .. "!") end end local previous_word = remove(words, 1) local last_three = sub(previous_word, -3) local following_word = remove(words, #words) local regularized_previous_word = remove(regularized_words, 1) local regularized_last_three = regularized_previous_word:sub(-3) local regularized_following_word = remove(regularized_words, #regularized_words) -- Check interposing words. local messages, message_number, message for i, word in ipairs(regularized_words) do local ending = word:match("^" .. regularized_last_three .. "(.+)$") -- Rules: --	-- Interposing words must start with last three letters of previous word. --	-- After these three letters must be three or more additional letters. --	-- These additional letters must begin the following word. -- WARNING! The color-adding will probably not work with transliteration. if not ending then if ignore_rules then message = true else message = "The interposing word " .. quote(word) .. " must start with the last three letters of " .. quote(previous_word) .. ", " .. quote(regularized_last_three) .. "."			end elseif #ending < 3 then if ignore_rules then message = true else local difference = 3 - #ending local agreement = difference == 1 and "" or "s" message = "The interposing word " .. quote(word) .. " needs " .. difference .. " more letter" .. agreement .. "."			end elseif not regularized_following_word:find("^" .. ending) then if ignore_rules then message = true else message = "The last word " .. quote(following_word) .. " must start with " .. quote(ending) .. ", the final letters of " .. quote(word) .. " after the last three letters of " .. quote(previous_word) .. ", " .. quote(last_three) .. ", are removed." end end if message then if ignore_rules then messages = (messages or 0) + 1 else messages = messages or {} message_number = message_number or 1 messages[message_number] = message message_number = message_number + 1 end end message = nil end if messages then if ignore_rules then local plural = messages > 1 and true or false return show_words(previous_word, words, following_word) .. ' Rules broken ' .. messages .. ' time' .. (plural and "s" or "") .. '! '		else error(concat(messages, " ")) end else return show_words(previous_word, words, following_word) end end

local Wonderfool_page = "User:Wonderfool/alternative accounts" local function get_Wonderfool_names local list = require("Module:utilities").get_section(		mw.title.new(Wonderfool_page):getContent,		"List"	) local names = {} for name in list:gmatch("%f[^\n]|(.-)|") do		names[name] = true end return names end

local Autotable = require("Module:auto-subtable")

local function compile_points_per_user(games, dont_merge_Wonderfool) local users = Autotable local merge_Wonderfool = not dont_merge_Wonderfool local is_Wonderfool if merge_Wonderfool then is_Wonderfool = get_Wonderfool_names end for _, game in ipairs(games) do		for i, entry in ipairs(game) do			local user = entry.username if merge_Wonderfool then user = is_Wonderfool[user] and "Wonderfool" or user end local userdata = users[user] userdata.points = (userdata.points or 0) + entry.points end end return users end

local function compare(user_table) local function get_compare_value(user) return user_table[user].points end return function (user1, user2) return get_compare_value(user1) > get_compare_value(user2) end end

local function link_username(username) if username == "Wonderfool" then return "Wonderfool" else return "" .. username .. "" end end

local function print_points(users) -- Have to remove auto-subtabling; copy in case this causes errors in the -- calling function. users = require("Module:table").deepcopy(users):un_auto_subtable return concat(		mapIter( function(data, user) return "* " .. link_username(user) .. ": " .. data.points end, require("Module:table").sortedPairs(users, compare(users))),		"\n") end

local function generate_statistics(games) local statistics = Autotable local all = {} statistics.all = all for _, game in ipairs(games) do		for i, entry in ipairs(game) do			local user = entry.username local userdata = statistics[user] local interposing_count = entry.interposing.count userdata[interposing_count] = (userdata[interposing_count] or 0) + 1 all[interposing_count] = (all[interposing_count] or 0) + 1 end end local averages = {} for user, interposing_data in pairs(statistics) do		local total_interposing_words = 0 local total_entries = 0 for interposing_count, number_of_times in pairs(interposing_data) do			total_interposing_words = total_interposing_words + interposing_count * number_of_times total_entries = total_entries + number_of_times end local average = total_interposing_words / total_entries averages[user] = math.floor(average * 10 + 0.5 ) / 10 end return statistics, averages end

function print_statistics(statistics, averages) local output = {} for user, data in pairs(statistics) do insert(output, "* " .. (user ~= "all" and link_username(user) or "all players")) insert(output, "** " .. averages[user] .. " interposing word" .. (averages[user] * 10 ~= 10 and "s" or "") .. " per entry") for interposing_count, number_of_times in pairs(data) do insert(output, "** " .. interposing_count				.. " interposing word" .. (interposing_count ~= 1 and "s " or " ")				.. number_of_times .. " time"				.. (number_of_times ~= 1 and "s" or "")) end end return concat(output, "\n") end

local month_numbers = invert { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }

-- Converts a date into a number that will allow the date column to be sorted reliably. local function make_date_number(date) local hour, minute, day_of_month, month, last_two_of_year = string.match(date, "^(%d%d):(%d%d), (%d%d?) (%a+) %d%d(%d%d)") -- Each part of the date is multiplied by the least power of two greater than -- the maximum possible value that that part can have. return ((((last_two_of_year - 17) * 2 + month_numbers[month]) * 16 + day_of_month) * 32 + hour) * 64 + minute end

-- For concatenating virtual interposing word tables produced by mw.loadData. local function concat_array(array, sep) local str = array[1] or "" for i = 2, 15 do		if array[i] then str = str .. sep .. array[i] else break end end return str end

function export.show_parsed_games(frame) local games = mw.loadData("Module:games/data") local t = { '{| class="wikitable sortable"', '! game, entry !! username !! date !! day difference !! preceding word !! interposing words !! count !! following word !! points' }	local function detect_and_tag(text) if not text:find("[\128-\255]") then return tag(link(text), "Latn") end return tag(link(text), find_best_script(text):getCode) end for game_number, game in ipairs(games) do		for entry_number, entry in ipairs(game) do			insert(				t,				('|-\n| data-sort-value="%s" | %d–%d || %s || data-sort-value="%d" | %s || %s || %s || %s || %d || %s || %d')					:format(string.format("%02d%02d", game_number, entry_number), game_number, entry_number, entry.username, make_date_number(entry.date), entry.date:gsub("December", "Dec"):gsub(".+", ' %1 '), entry.day_difference and ("%.4f"):format(entry.day_difference) or "&mdash;", detect_and_tag(entry.preceding), concat_array(map(detect_and_tag, entry.interposing), ", "), entry.interposing.count, detect_and_tag(entry.following), entry.points)) end end insert(t, "|}") return concat(t, "\n") -- return require("Module:debug").highlight_dump end

function export.show_statistics(frame) return print_statistics(generate_statistics(mw.loadData("Module:games/data"))) end

function export.print_points(frame) return print_points(compile_points_per_user(mw.loadData("Module:games/data"))) end

function export.print_game_data(frame) return require("Module:debug").highlight_dump(mw.loadData("Module:games/data")) end

-- m_fun.logAll(export)

return export