モジュールの解説[作成]
require('strict')

-- these are the column coordinate labels 9-1
local colLabels = {'9', '8', '7', '6', '5', '4', '3', '2', '1', ' '}

-- these are the row coordinate labels 1-9 (Japanese notation)
local rowLabels = {'一', '二', '三', '四', '五', '六', '七', '八', '九'}

-- letter to character mapping (hash table)
local pieces = {
	p = {'歩'},
	t = {'と', true},
	l = {'香'},
	pl = {'杏', true},
	n = {'桂'},
	pn = {'圭', true},
	s = {'銀'},
	ps = {'全', true},
	g = {'金'},
	b = {'角'},
	h = {'馬', true},
	r = {'飛'},
	d = {'龍', true},
	gyoku = {'玉'},
	ou = {'王'},
	tx = {'个', true},
	plx = {'仝', true},
	pnx = {'今', true},
	dx = {'竜', true},
	e = {'象'},
	a = {'太', true}
}

local arrows = {'yy', 'gr', 'rat', 'lat', 'uat', 'dat', 
           'lra', 'las', 'ras', 'uda', 'das', 'uas', 
           'da', 'dau', 'dad', 'daus', 'dads', 'daa', 
           'daad', 'daau', 'daaus', 'daads', 'kar', 'kal', 
           'kadr', 'kadl', 'rah', 'lah', 'dah', 'uah', 
           'durh', 'dulh', 'ddrh', 'ddlh', 'ddl', 'ddr', 
           'dul', 'dur'}

-- function separates out the piece, the side, and the boldness info present in the string argument
-- it returns an array with these three values plus the color (for promoted pieces)
-- this info is passed to the makeTD() function
local function processString(ss)
	-- 先頭・末尾の空白文字類(全角空白なども含む)を除去する
	ss = mw.text.trim(ss or '', '%s')

	if ss == '' then
		return ' '
	end
	-- 'da', 'ddl', 'dul', 'gr', 'kal' に対しては69行以下の判定が正しく動作しないため、ここで個別に判定
	if ss == 'da' then
		return '[[File:shogi_da22.svg|22px]]'
	end
	if ss == 'ddl' then
		return '[[File:shogi_ddl22.svg|22px]]'
	end
	if ss == 'dul' then
		return '[[File:shogi_dul22.svg|22px]]'
	end
	if ss == 'gr' then
		return '[[File:shogi_gr22.svg|22px]]'
	end
	if ss == 'kal' then
		return '[[File:shogi_kal22.svg|22px]]'
	end

	-- get the last character of the string
	local lastchar = mw.ustring.sub(ss, -1)
	
	-- default is normal font
	-- but if the string ends with 'l' for 'last move', 
	-- then the font should be bold and we need to get a new string with the this 'l' chopped off (with a new last character)
	local bold = false
	local side = lastchar
	local pieceabbr = mw.ustring.sub(ss, 1, -2)
	if lastchar == 'l' then
		bold = true
		side = mw.ustring.sub(ss, -2, -2)
		pieceabbr = mw.ustring.sub(ss, 1, -3)
	end
	local gote = (side == 'g')
	
	-- this is an exceptional bit:
	-- gote's king is usually 王 instead of 玉 by convention, 
	-- but it's convenient to use the 'k' code for both sente and gote and let the default character be side-dependent
	if pieceabbr == 'k' then
		if gote then
			pieceabbr = 'ou'
		else
			pieceabbr = 'gyoku'
		end
	elseif pieceabbr == 'ak' then
		if gote then
			pieceabbr = 'gyoku'
		else
			pieceabbr = 'ou'
		end
	end
	-- convert abbreviation to Japanese character
	local piece = pieces[pieceabbr]
	if not piece then
		for _, v in ipairs(arrows) do
			if ss == v then
				return '[[File:shogi_' .. ss .. '22.svg|22px]]'
			end
		end
		error(ss, 0)
	end
	
	return piece[1], piece[2], gote, bold
end


-- function makes a <td> containing the piece with CSS stuff
-- uses the info from processString() to customize the CSS based on which side, color, and boldness
local function makeTD(stringarg)
	-- got to process the string argument into its informational bits
	local piecechar, promoted, gote, bold = processString(stringarg)

	-- the default <td>
	local td = mw.html.create('td')
		td:addClass('shogi-piece')
			:wikitext( piecechar )
	
	-- g = gote
	-- gote should be upside down text
	if gote then
		td:addClass('piece-gote')
	end
	
	-- for promoted pieces
	if promoted then
		td:addClass('piece-promoted')
	end

	-- for bold pieces
	if bold then
		td:addClass('piece-bold')
	end
	
	return td
end


-- function makes the shogi diagram
-- this is basically a <div> enclosing a .css <div> wrapper with a <table> inside
local function shogiboard(args)
	
	-- <div> wrapper
	local shogiboardwrapper = mw.html.create('div')
		:addClass('shogiboard-wrapper')
	
	-- the diagram header/caption
	shogiboardwrapper:tag('div')
        :addClass('shogiboard-header')
		:wikitext(mw.text.trim(args[2] or ''))

	-- the shogi table
	local shogitable = shogiboardwrapper:tag('table')
		:addClass('shogiboard-table')
		:attr('border', '1')
		
	-- the row for the column coordinate labels
	local columnlabelrow = shogitable:tag('tr')
		:addClass('shogiboard-collabel')
	-- iterating over the column label to put each label in a <td>
	for _, v in ipairs(colLabels) do 
		columnlabelrow:tag('td')
			:wikitext( v )
	end
	
	-- iterate over the 81 shogi piece arguments (left to right, top to bottom)
	-- i couldn't figure out how to do this is in a clever loopy way as i couldn't figure out how to close the html tags
	-- whatever, it's repetitive, but it works
	
	-- index number displacement/offset
	-- this is just the number of arguments that precede the 81 shogi piece arguments that are in the html <table>
	-- i just keep the piece arguments as indexes 1-81, then add nx to the index value
	local nx = 2
	
	-- the row for the shogi pieces
	for irow = 1,9 do
		local trow = shogitable:tag('tr')
		-- put a single piece into a <td>
		for icol = 1, 9 do
			local result, td = pcall(makeTD, args[(irow - 1) * 9 + icol + nx])
			if not result then
				return string.format(
					'<strong class="error">%s%sの位置で不明な値"%s"が指定されました。</strong>',
					colLabels[icol],
					rowLabels[irow],
					td
					)
			end
			trow:node(td)
		end
		-- add row coordinate label <td>
		trow:tag('td')
			:addClass('shogiboard-rowlabel')
			:wikitext( rowLabels[irow] )
	end
	

    -- the diagram footer
	shogiboardwrapper:tag('div')
        :addClass('shogiboard-footer', 'bold')
		:wikitext(mw.text.trim(args[84] or ''))

	return tostring(shogiboardwrapper)
end

local valueFunc = function(_, value)
	if value then
		return mw.text.trim(value)
	else
		return false
	end
end

-----------------------
-- output
-----------------------
return {
	board = function(frame)
		return shogiboard(frame:getParent().args)
	end
}