Module:Arimaa/board

From Wikibooks, open books for an open world
Jump to navigation Jump to search
Documentation[create]
p = {}

function p.from_fen(fen)
    local ret = {}
    for i = 1, 8 do
        ret[i] = {}
    end

    local x = 1
    local y = 1
    local i = 1
    while i <= #fen do
        local separator = false
        local a = string.match(fen, '^[%d]+', i)
        if a then
            x = x + a
            i = i + #a
        else
            local a = string.match(fen, '^/?\n?', i)
            if a ~= '' then
                separator = true
                x = 1
                y = y + 1
                i = i + #a
            else
                local c = string.sub(fen, i, i)
                if(c == ' ') then
                    x = x + 1
                elseif(string.match(c, '[rcdhmeRCDHME]')) then
                    ret[x][y] = c
                    x = x + 1
                else
                    break
                end
                i = i + 1
            end
        end

        local z = math.floor((x - 1) / 8)
        x = x - z * 8
        y = y + z

        if x == 1 and not separator then
            local a = string.match(fen, '^/?\n?', i)
            i = i + #a
        end
    end

    return ret
end

function p.from_long(s)
    local ret = {}
    for i = 1, 8 do
        ret[i] = {}
    end

    local a = string.gmatch(s, '[rcdhmeRCDHME][a-h][1-8]')
    while true do
        local b = a()
        if not b then break end
        local x = string.byte(string.sub(b,2,2)) - string.byte('a') + 1
        local y = tonumber(string.sub(b,3,3))
        ret[x][9-y] = string.sub(b,1,1)
    end

    return ret
end

function p.expand_char(c)
    if c == " " then
        return "  "
    elseif string.match(c, "%u") then
        return string.lower(c) .. "g"
    else
        return c .. "s"
    end
end

function p.step_dest(sq, dir)
    if dir == "n" then
        return {sq[1], sq[2] - 1}
    elseif dir == "e" then
        return {sq[1] + 1, sq[2]}
    elseif dir == "s" then
        return {sq[1], sq[2] + 1}
    elseif dir == "w" then
        return {sq[1] - 1, sq[2]}
    else
        return sq
    end
end

function p.expand(s)
    local ret = {}
    local k = 1
    local g = string.gmatch(s, "[rcdhmeRCDHME][a-h][1-8][neswx]+")
    while true do
        local expr = g()
        if not expr then break end
        local x = string.byte(string.sub(expr, 2, 2)) - string.byte("a") + 1
        local y = 9 - string.sub(expr, 3, 3)
        local sq = {x, y}
        for i = 4, #expr do
            local dir = string.sub(expr, i, i)
            ret[k] = {piece = string.sub(expr, 1, 1),
                      sq = sq,
                      dir = dir
                     }
            sq = p.step_dest(sq, dir)
            k = k + 1
        end
    end

    return ret
end

function p.play_move(board, move)
    for i = 1, #move do
        board[move[i].sq[1]][move[i].sq[2]] = nil
        if move[i].dir ~= 'x' then
            dest = p.step_dest(move[i].sq, move[i].dir)
            board[dest[1]][dest[2]] = move[i].piece
        end
    end
end

function p.diagram1(region, x, y, board, class, caption)
    local width, height, min_x, max_x, min_y, max_y
    if string.match(region, "w") then
        width = 33 + 37 * x
        min_x = 1
        max_x = x
    elseif string.match(region, "e") then
        width = 32 + 37 * x
        max_x = 8
        min_x = 9 - x
    else
        width = 344
        min_x = 1
        max_x = 8
    end
    if string.match(region, "n") then
        height = 16 + 37 * y
        marginBottom = 1 + 37 * y
        min_y = 1
        max_y = y
    elseif string.match(region, "s") then
        height = 17 + 37 * y
        marginBottom = 17 + 37 * y
        max_y = 8
        min_y = 9 - y
    else
        height = 328
        marginBottom = 313
        min_y = 1
        max_y = 8
    end

    local padding
    if caption then
        padding = "3px 3px 0"
    else
        padding = "3px"
    end

    local margin
    if string.match(region, "e") then
	margin = "0 0 0 0"
    else
	margin = "0 0 0 16px"
    end
    table = '{|cellpadding="0" style="border-collapse: collapse; margin: ' .. margin .. ' !important;"\n'

    for j = min_y, max_y do
	table = table .. '|- style="height: 37px"\n'
	for i = min_x, max_x do
            if j == min_y then
                table = table .. '| style="width: 37px; padding: 0" '
            end
            if not board[i][j] then
                table = table .. "| \n"
            else
                table = table .. "| [[Image:Arimaa_" .. p.expand_char(board[i][j]) .. "b74.gif|37px]]\n"
            end
	end
    end
    table = table .. "|}"

    local div = mw.html.create("div")
    if class then
        div:attr("class", class)
    end
    div:attr("style", string.format("width: %dpx; padding: %s; border: 1px solid #b0b0b0; background-color: #f9f9f9;",
                                     width, padding))
    div:tag("div")
	:attr("style", string.format("height: %dpx; border: 1px solid #b0b0b0; padding: 3px; background-color: #f9f9f9;", height))
	:tag("div")
	    :attr("style", string.format("margin-bottom: %dpx", -marginBottom))
	    :wikitext("[[Image:Arimaa board " .. region .. x .. y .. ".jpg]]")
	    :done()
	:wikitext("\n" .. table .. "\n")
	:done()

    if caption then
        div:tag('p')
            :attr("style", "line-height: 1.4; text-align: left; font-size: 90%; margin: 0.3em")
            :wikitext(caption)
    end

    return tostring(div)
end

function p.puzzle(frame)
    local board

    local fen = frame.args.fen
    if fen then
        if string.sub(fen, 1, 2) == "\\\n" then
            fen = string.sub(fen, 3)
        end
        board = p.from_fen(fen)
    elseif frame.args.long then
        board = p.from_long(frame.args.long)
    end

    local ret = p.diagram1(frame.args[1], frame.args[2], frame.args[3], board, frame.args.class1, frame.args.caption1)

    p.play_move(board, p.expand(frame.args.move))

    local div = mw.html.create("div")
    div:attr("class", "collapsible")
    div:tag("div")
        :attr("class", "title")
        :wikitext(frame.args.q)
    div:tag("div")
        :attr("class", "body")
        :wikitext(p.diagram1(frame.args[1], frame.args[2], frame.args[3], board, frame.args.class2, frame.args.caption2))
        :wikitext("<p>'''Solution: " .. frame.args.move .. "'''</p><p>" .. frame.args.desc .. "</p>")

    return ret .. tostring(div) .. '<div style="clear: both"></div>'
end

function p.pieceImage(pieceset, piece)
    local gold = string.match(piece, '%u')
    local t = string.lower(piece)
    if pieceset == 'stone' or pieceset == 'flat' then
        local s = 'Arimaa_' .. t
        if gold then
            s = s .. 'g'
        else
            s = s .. 's'
        end
        if pieceset == 'stone' then
            return s .. 'b74.gif'
        else
            return s .. '.svg'
        end
    else
        local A = {e = '1-elephant', m = '2-camel', h = '3-horse', d = '4-dog', c = '5-cat', r = '6-rabbit'}
        if gold then
            return string.format('G-%s.svg', A[t])
        else
            return string.format('S-%s.svg', A[t])
        end
    end
end

-- TODO: accumulation in ret is inefficient
function p.diagram2(board, min_x, max_x, min_y, max_y, caption, side, size, pieceset, border, backgroundColour, boardColour, trapColour, gridColour, captionColour)
    if not size or size == '' then size = '28' end
    size = tonumber(size)
    if not pieceset or pieceset == '' then pieceset = 'flat' end
    if not backgroundColour or backgroundColour == '' then backgroundColour = '#cae2ed' end
    if not boardColour or boardColour == '' then boardColour = '#fff' end
    if not trapColour or trapColour == '' then trapColour = '#9fbbc6' end
    if not gridColour or gridColour == '' then gridColour = '#a2adb1' end
    if not captionColour or captionColour == '' then captionColour = '#9fbbc6' end
    
    local boardBorder = '2px solid #888'

    local labelWidth = 18

    local topMargin, rightMargin, bottomMargin, leftMargin
    if max_y == 8 then topMargin = 3 else topMargin = 10 end
    if max_x == 8 then rightMargin = 3 else rightMargin = 10 end
    if min_x == 1 then leftMargin = 3 else leftMargin = 10 end
    if min_y == 1 then bottomMargin = 3 else bottomMargin = 10 end

	local width = (max_x - min_x + 1) * size + (max_x - min_x + 2) + leftMargin + rightMargin
	if min_x == 1 then width = width + labelWidth + 1 end
	if max_x == 8 then width = width + labelWidth + 1 end

	local outerStyle = string.format('background: %s;', backgroundColour)
	if side == 'right' then
		outerStyle = outerStyle .. ' float: right; clear: right; margin: 0.75em 0 0.75em 1em;'
	elseif side == 'left' then
		outerStyle = outerStyle .. ' float: left; clear: left; margin: 0.75em 1.5em 0.75em 0;'
	end
	if border and border ~= '' then
		outerStyle = outerStyle .. ' border: 1px solid #aaa;'
	end
	local ret = string.format('{| cellpadding="0" cellspacing="0" style="%s"\n', outerStyle)
	local boardStyle = string.format('font-weight: bold; font-size: 80%%; text-align: center; border-collapse: collapse; margin: %dpx %dpx %dpx %dpx;',
		topMargin, rightMargin, bottomMargin, leftMargin)
	ret = ret .. string.format('|\n{| cellpadding="0" style="%s"\n', boardStyle)

    local files = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}
    local labelRow = '|-\n|'
    if min_x == 1 then
        labelRow = labelRow .. ' ||'
    end
    for x = min_x, max_x - 1 do
        labelRow = labelRow .. ' ' .. files[x] .. ' ||'
    end
    labelRow = labelRow .. ' ' .. files[max_x] .. '\n'

    if max_y == 8 then
        ret = ret .. labelRow
    end

    for y = max_y, min_y, -1 do
        ret = ret .. string.format('|- style="height: %dpx"\n', size)
        if min_x == 1 then
            ret = ret .. '|'
            if y == max_y then
                ret = ret .. string.format(' style="width: %dpx" |', labelWidth)
            end
            ret = ret .. string.format(' %d\n', y)
        end
        local s = string.format('border: 1px solid %s;', gridColour)
        if y == max_y then
            s = s .. string.format(' width: %dpx;', size)
        end
        if y == 8 then
        	s = s .. string.format(' border-top: %s;', boardBorder)
        elseif y == 1 then
        	s = s .. string.format(' border-bottom: %s;', boardBorder)
        end
        for x = min_x, max_x do
        	local s2 = s
        	if x == 1 then
        		s2 = s2 .. string.format(' border-left: %s;', boardBorder)
        	elseif x == 8 then
        		s2 = s2 .. string.format(' border-right: %s;', boardBorder)
        	end
            if (x == 3 or x == 6) and (y == 3 or y == 6) then
            	s2 = s2 .. string.format(' background: %s;', trapColour)
            else
            	s2 = s2 .. string.format(' background: %s;', boardColour)
            end
            ret = ret .. string.format('| style="%s" |', s2)
            if board[x][9-y] then
                ret = ret .. string.format(' [[Image:%s|%dpx]]', p.pieceImage(pieceset, board[x][9-y]), size)
            end
            ret = ret .. '\n'
        end
        if max_x == 8 then
            ret = ret .. '|'
            if y == max_y then
                ret = ret .. string.format(' style="width: %dpx" |', labelWidth)
            end
            ret = ret .. string.format(' %d\n', y)
        end
    end

    if min_y == 1 then
        ret = ret .. labelRow
    end

    ret = ret .. '|}\n'

    if caption and caption ~= '' then
    	ret = ret .. '|-\n'
    	local padding = 6
    	captionStyle = string.format('font-size: 90%%; background: %s; padding: 3px %dpx; margin: 0; width: %dpx;', captionColour, padding, width - 2*padding)
    	ret = ret .. string.format('| <p style="%s">%s</p>\n', captionStyle, caption)
    end

    ret = ret .. '|}\n'

    return ret
end

function p.board(frame)
    local board

    local fen = frame.args.fen
    if fen ~= '' then
        if string.sub(fen, 1, 2) == "\\\n" then
            fen = string.sub(fen, 3)
        end
        board = p.from_fen(fen)
    elseif frame.args.long ~= '' then
        board = p.from_long(frame.args.long)
    end

    local min_x = 1
    local max_x = 8
    local min_y = 1
    local max_y = 8

    if frame.args.region ~= '' then
        local _, _, a, b = string.find(frame.args.region, '([a-h])-?([a-h])')
        if a then
            min_x = string.byte(a) - string.byte('a') + 1
            max_x = string.byte(b) - string.byte('a') + 1
        end
        local _, _, c, d = string.find(frame.args.region, '([1-8])-?([1-8])')
        if c then
            min_y = tonumber(c)
            max_y = tonumber(d)
        end
    end

    return p.diagram2(board, min_x, max_x, min_y, max_y, frame.args.caption, frame.args.side, frame.args.size, frame.args.pieceset, frame.args.border, frame.args["background-colour"], frame.args["board-colour"], frame.args["trap-colour"], frame.args["grid-colour"], frame.args["caption-colour"])
end

function p.wrapper(frame)
	local board = {}
    for x = 1, 8 do
        board[x] = {}
    end
    
    for y = 1, 8 do
    	for x = 1, 8 do
    		if(frame.args[(y-1)*8+x]) then
	    		local p, c = string.match(frame.args[(y-1)*8+x], '([rcdhme])([gs])')
    			if c == 'g' then
    				board[x][y] = string.upper(p)
    			elseif c == 's' then
    				board[x][y] = p
    			end
    		end
    	end
    end
    
    local side
    if frame.args.class == 'tright' then
    	side = 'right'
    elseif frame.args.class == 'tleft' then
    	side = 'left'
    end
    
    return p.diagram2(board, 1, 8, 1, 8, frame.args.caption, side, frame.args.size, frame.args.pieceset, frame.args.border, frame.args["background-colour"], frame.args["board-colour"], frame.args["trap-colour"], frame.args["grid-colour"], frame.args["caption-colour"])
end

return p