Created for Wiktionary:Christmas Competition 2017. One function powers {{game entry}}
, and other functions use data generated by Module:games/data to count points and generate other statistics about the game (see Wiktionary talk:Christmas Competition 2017/game data).
local m_fun = require("Module:fun")
local m_str_utils = require("Module:string utilities")
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 "]"
else
return "]"
end
end
end
local function italicize(word)
if word:find("''") then
return word
else
return "''" .. word .. "''"
end
end
local function tag(word, script)
return '<span class="' .. script .. '">' .. word .. '</span>'
end
local function add_color(color, text, attribute)
return '<span style="color: ' .. color .. ';" ' .. (attribute or '') .. '>' .. text .. '</span>'
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(
"+",
function (non_ASCII_character)
if is_combining(non_ASCII_character) then
return ""
end
return non_diacritic_equivalencies
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 = 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 = langs 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 = translits 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 then
error("You used the word " .. quote(word) .. " twice, in parameters " .. i .. " and " .. j .. "!")
end
end
end
local tr
if word:find("") then -- non-ASCII
local script = find_best_script(word):getCode()
if script and not script:find "Lat" then -- non-Latin
-- ] requires word to be in NFC.
nonLatin_i = nonLatin_i + 1
local lang = langs and langs
if translits and translits then
tr = translits
elseif lang then
tr = lang:transliterate(toNFC(word), require("Module:scripts").getByCode(script))
or lang:transliterate(toNFC(word))
or translit
and require("Module:" .. translit)](word, unpack(translit, 3))
or error("The " .. lang:getCode()
.. " transliteration module was unable to generate a transliteration for "
.. quote(word) .. ".")
elseif translit then
local success, result = pcall(require("Module:" .. translit).tr, toNFC(word), script)
tr = success and result or error("[[Module:"
.. translit
.. "]] 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 = word
regularized_words = 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
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) ..
' <span style="color: red;">Rules broken ' .. messages ..
' time' .. (plural and "s" or "") .. '!</span>'
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:pages").get_section(
mw.title.new(Wonderfool_page):getContent(),
"List"
)
local names = {}
for name in list:gmatch("%f|(.-)|") do
names = 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 and "Wonderfool" or user
end
local userdata = users
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.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 "]"
else
return "]"
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
local interposing_count = entry.interposing.count
userdata = (userdata or 0) + 1
all = (all 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 = 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 .. " interposing word" .. (averages * 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) * 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 or ""
for i = 2, 15 do
if array then
str = str .. sep .. array
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,<br>entry !! username !! date !! day<br>difference !! preceding<br>word !! interposing words !! count !! following<br>word !! points'
}
local function detect_and_tag(text)
if not text:find("") 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(".+", '<span style="white-space: nowrap;">%1</span>'),
entry.day_difference and ("%.4f"):format(entry.day_difference) or "—",
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