模块:PFTest

来自俄罗斯方块中文维基
{{pf2|lcase=mini|w=1|
[ ijlostzg ]
[ IJLOSTZG ]
}}
{{pf2|sys=arika|w=1|
[ IJLOSTZG ]
}}
{{pf2|small=1|w=1|
[ IJLOSTZG ]
[ ijlostzg ]
[ IJLOSTZG ]
[ ijlostzg ]
}}
{{pf2|.0=xd9a9a9|.1=xba71ba|
[      - X  0 1 2    ]
[  z L0o0SmIlj3T4g   ]
[          0 1       ]
}}
{{pf2|sys=arikaw|
[  J L     ]
[  I S Z   ]
[  G T O   ]
}}
{{pf2|
[a0a1a2a3a4a5a6a7a8a9Z1Z2z1z2]
[Z L O S I J T G   - Z3Z4z3z4]
[B0B X ALARAlArADAHAUL1L2l1l2]
[ZmLmOmSmImJmTmBlg GlL3L4l3l4]
[                    O1O2o1o2]
[z l o s i j t       O3O4o3o4]
[ZlLlOlSlIlJlTl      S1S2s1s2]
[zmlmomsmimjmtmGm    S3S4s3s4]
[Z0L0O0S0I0J0T0G0 0  I1I2i1i2]
[z0l0o0s0i0j0t0      I3I4i3i4]
[                    J1J2j1j2]
[                    J3J4j3j4]
[                    T1T2t1t2]
[                    T3T4t3t4]
}}

施工中,格式尚未定型。

文件名

图片存在 $wgServer/tet/ 下,文件命名方式(省略“.png”),暂定全用小写字母数字(在windows上操作不撞车不爆炸):

  • SKIN-COLOR(-LUCASETAG)(-MODIFIERs)
  • empty、cross、dotted(皮肤无关类特殊单块)
  • arrow-NAME
  • ascii-NAME

SKIN指皮肤名,默认且最常用为plain,另有arika arikaw gb thenew等。可用参数“skin=SKIN”指定。

COLOR指每个方块对应什么颜色(广义),可能是单词(大致颜色类,区分了purple和magenta?),或者是(以后扩展)xRRGGBB(一些具体的旋转系统,基本只用于plain)。除非是类似gb,无彩色且用花纹区分各种块,则此时COLOR也会是 i j l o s t z 等方块名。可用参数“color=COLORSCHEME”指定,现有super sega thenew direct。

可用参数“sys=ROTSYS”一次指定以上两项一套,现有super sega arika arikaw gb thenew。

皮肤混用用法待思考【

LUCASETAG部分,取决于大小写的解释方式,也就是可指定添加到文件名后的后缀,用参数“ucase=TAG” “lcase=TAG”指定,会自动加“-”分隔,空则不加。默认为lcase无,lcase=dark。

如默认情况下“t”就会生成文件名“plain-purple-dark.png”。

文件完全由服务器管理员单独维护,不在维基的“文件”系统内(正是因为每渲染一个块都要用这个系统缩略图,所以很慢)。目前尚在手工添加。之后的添加需求联系User:Farter

本体语法

参数“w”,默认w=2,每2个字符代表一个 mino。第一个字符为块名(ijlostz g b)或A、a、-、X(可能继续添加)或空格。

无名参数即为场地数据,每行用[]包裹,每行内容字符数必须为w的倍数。每W字符中,第一个字符为:

  • 对于块、X、-,后续字符每个为一个修改符。可用参数“M.X=MODIFIER”新增定义,X为字符,modifier为文件名上新的一截。现有字符dlm01234对应dark light mini 五种旋转中心。空格也可以接修改符01234。
  • 对于A(对应箭头、占格标记)宽度内的后续字符视作整体“箭头名”。现有字符L R U D l r对应left right up down ccw cw。可用“A.XX=ARROWNAME”新增定义。
  • 对于a(对应ascii字符)宽度内的后续字符视作整体“字符名”,普通字符已定义为直接单字对应。{、|、}、[、]、<、>这些特殊字符,可用参数“a.XX=HH”新增定义。XX为字符名,HH为字符的十六进制码。可能参考添加预制。
  • 可用参数“.X=COLOR”新增定义一种用字符X表示的mino对应的颜色(广义)。

参数总结

w=CELLWIDTH  sys=ROTSYS  color=COLORSCHEME  skin=SKIN  lcase=TAG  ucase=TAG
.X=COLOR  M.X=MODIFIER  A.XX=ARROWN  a.XX=ASCIIHEX

local mwServer=mw.site.server
local function split(inputstr, sep)
	if sep == nil then
		sep = "%s"
	end
	local t = {}
	for part in string.gmatch(inputstr, "([^" .. sep .. "]+)") do
		table.insert(t, part)
	end
	return t
end
local function tableassign(dst,src)
	for k,v in pairs(src) do -- merge/override
		dst[k]=v
	end
	return dst
end
local function message(msg, id)
	-- Return formatted message text for an error or warning.
	local categories = {
		error = '[[Category:PF模块错误]]',
		warning = '[[Category:PF模块错误]]',  -- same as error until determine whether 'Age warning' would be worthwhile
	}
	local a, b, category
	if id == 'warning' then
		a = '<sup>[<i>'
		b = '</i>]</sup>'
	else
		a = '<strong class="error">错误:'
		b = '</strong>'
	end
	if mw.title.getCurrentTitle():inNamespaces(0) then
		-- Category only in namespaces: 0=article.
		category = categories[id or 'error']
	end
	return
		a ..
		mw.text.nowiki(msg) ..
		b ..
		(category or '')
end
local p={}
function p.genempty()
	local sImgL,sImgR=[[<img src="]]..mwServer..[[/tet/]],[[empty.png">]]
	local sRow="<div>"..string.rep(sImgL..sImgR,10).."</div>"
	local sField=string.rep(sRow,20)
	local sAll= [=[{| class="pfield" style="line-height: 10px; font-size: 7px; border: 1px solid #999"
	|]=]..sField.."\n|}"
	return sAll
end
function p.test(frame)
	local argfield=frame.args[1]
	local args=tableassign({},frame.args)
	local pargs=frame:getParent().args -- must be called in template
	for k,v in pairs(pargs) do -- merge/override
		args[k]=v
	end
	local t={} -- join
	for k,v in pairs(args) do
		table.insert(t,"["..k.."]="..v)
	end
	
	return table.concat(t,"\n\n").."\n\n"..table.concat(split(argfield,"\n"),",")
end

-- Sanitizer.php validateAttributes hrefExp should be changed to allow relative path
local sImgL=[[<img src="]]..mwServer..[[/tet/]]
sImgL=[[<img src="/tet/]]
local sImgRb=[[.png" width="12" height="12">]]
local sImgRs=[[.png" width="8" height="8">]]
local function genRow(row,small)
	local sb={}
	local sImgR = small and sImgRs or sImgRb
	for i=1,#row do
		sb[i]=sImgL..row[i]..sImgR
	end
	local sRow="<div>"..table.concat(sb,"").."</div>"
	return sRow
end

local function genRows(rows,small)
	local sb={}
	for i=1,#rows do
		sb[i]=genRow(rows[i],small)
	end
	local sField=table.concat(sb)
	local sAll
	if small then sAll=
[=[{| class="pfield" style="line-height: 5px; font-size: 5px; border: 1px solid #999"
|]=]..sField.."\n|}"
	else sAll=
[=[{| class="pfield" style="line-height: 7px; font-size: 7px; border: 1px solid #999"
|]=]..sField.."\n|}"
	end
	return sAll
end

-- normalize to lower case
local colorSuper={
	i="cyan",t="purple",o="yellow",s="green",z="red",j="blue",l="orange",g="gray",b="black"
}
local colorSega={
	i="red",t="cyan",o="yellow",s="magenta",z="green",j="blue",l="orange",g="gray"
}
local colorTheNew={
	i="cyan",t="yellow",o="silver",s="green",z="red",j="purple",l="magenta",g="gray"
}
local colorDirect={
	i="i",t="t",o="o",s="s",z="z",j="j",l="l",g="g"
}
local colorDict={
	sega=colorSega,super=colorSuper,thenew=colorTheNew,direct=colorDirect,
	""
}
local sysDict={
	sega={color=colorSega,skin="sega"},
	arika={color=colorSega,skin="arika"},
	arikaw={color=colorSuper,skin="arikaw"},
	super={color=colorSuper,skin="plain"},
	gb={color=colorDirect,skin="gb"},
	thenew={color=colorTheNewTetris,skin="thenew"},
	""
}
local skinDict={
	sega=1,arika=1,arikaw=1,plain=1,gb=1,thenew=1,
	""
}
-- SKIN-COLOR-LUCASE-MODIFIERs
-- arrow-SECOND
-- ascii-ASCIIHEX

local function isBadComponent(s)
	return s:match("^[a-z0-9_-]*$")==nil
end
local function checkShortDef(s)
	s=mw.text.trim(s)
	if #s==0 then return false end
	for i=1,#s do
		local b=s:byte(i)
		if b<=32 or b>=127 then return false end
	end
	return s
end
function p._field(args)
	local argfield=args[1]
	if not argfield then return message("no arg for field (unnamed first argument, without '=')") end
	local rows=split(argfield,"\n")
	if #rows==0 then return message("arg for field looks like empty") end
	
	local w=2 -- per mino
	local skin='plain'
	local minoDict=tableassign({},colorSuper)
	local primDict={[' ']='empty',['-']='dotted',X='cross'}
	local modiDict={
		['0']='rot0',['1']='rot1',['2']='rot2',['3']='rot3',['4']='rot4', -- overlay
		m='mini',d='dark',l='light', -- modifier
		[' ']=' ',
		""
	}
	local arrowDict={
		['L']='left',['R']='right',['U']='up',['D']='down',['l']='ccw',['r']='cw',["H"]="uptack"
	}
	local asciiDict={['']='20'}
	for i=33,127 do
		local sAscii=string.char(i)
		local sHex=string.format("%x",i)
		asciiDict[sAscii]=sHex
	end
	local argSys=args.sys
	if argSys~=nil then
		if sysDict[argSys]==nil then return message("argument sys: rotation system not found") end
		minoDict=tableassign({},sysDict[argSys].color)
		skin=sysDict[argSys].skin
	end
	local argSkin=args.skin
	if argSkin~=nil then
		if skinDict[argSkin]==nil then return message("argument skin: skin not found") end
		skin=argSkin
	end
	local argColor=args.color
	if argColor~=nil then
		if colorDict[argColor]==nil then return message("argument color: color scheme not found") end
		minoDict=tableassign({},colorDict[argColor])
	end
	local argW=tonumber(args.w)
	if argW~=nil then
		argw=math.floor(argW)
		if argW<1 and argW>4 then return message("argument w: should be 1 to 4") end
		w=argW
	end
	local argSmall=not not args.small
	local ucase,lcase="","dark" -- default ucase lcase semantics
	
	for k,v in pairs(args) do
		if type(k)=="number" then
		
		elseif k:sub(1,1)=="." then -- mino
			if #k~=2 then return message("argument .X specify mino color: should only add 1 char") end
			local byte=k:byte(2)
			if byte<32 or byte>127 then return message("argument .X: invalid char to define") end
			if isBadComponent(v) then return message("argument .X: invalid component") end
			minoDict[k:sub(2,2)]=v
		elseif k:sub(1,2)=="M." then -- modifier or overlay
			if #k<3 then return message("argument M.X: should follow 1-3 chars to define as modifier") end
			k=checkShortDef(k)
			if k==false then return message("argument M.X: invalid name to define") end
			if isBadComponent(v) then return message("argument M.X: invalid component") end
			modiDict[k:sub(3)]=v
		elseif k:sub(1,2)=="A." then -- arrow or other markers
			if #k<3 then return message("argument A.X: should follow 1-3 chars to define as arrow/marker") end
			k=checkShortDef(k)
			if k==false then return message("argument A.X: invalid name to define") end
			if isBadComponent(v) then return message("argument A.X: invalid component") end
			arrowDict[k:sub(3)]=v
		elseif k:sub(1,2)=="a." then -- ascii char
			if #k<3 then return message("argument a.X: should follow 1-3 chars to define as ascii char") end
			k=checkShortDef(k)
			if k==false then return message("argument a.X: invalid name to define") end
			if v.match("^%x%x$")==nil then return message("argument a.X: value should be exactly 2 hexadecimal digits") end
			asciiDict[k:sub(3)]=v
		elseif k=="lcase" or k=="ucase" then
			if isBadComponent(v) then return message("argument ucase/lcase: invalid component") end
			if k=="ucase" then ucase=v end
			if k=="lcase" then lcase=v end
		end -- ignore others
	end
	
	local tfield={}
	for y,srow in ipairs(rows) do
		local dbgy="line "..y.." "
		if #srow<4 then return message(dbgy.."too short") end
		if srow:sub(1,1)~="[" or srow:sub(-1,-1)~="]" then return message(dbgy.."should be enclosed by []") end
		srow=srow:sub(2,-2)
		if #srow%w~=0 then return message(dbgy.."number of chars within [] should be multiple of "..w) end
		trow={}
		local n=#srow/w
		for x=1,n do
			local dbgx=dbgy.."mino "..x.." "
			local sslice=srow:sub((x-1)*w+1,(x-1)*w+w)
			local smino=sslice:sub(1,1)
			local sminoLow=smino:lower()
			local tmino
			if minoDict[sminoLow]~=nil or primDict[smino]~=nil then
				tmino=minoDict[sminoLow] or primDict[smino]
				if primDict[smino]==nil then -- is mino
					tmino=skin..'-'..tmino
					local isUCase=smino:match("^%u$")~=nil
					local isLCase=smino:match("^%l$")~=nil
					if isUCase and ucase~="" then
						tmino=tmino..'-'..ucase
					elseif isLCase and lcase~="" then
						tmino=tmino..'-'..lcase
					end
				end
				for i=2,w do
					local smodi=sslice:sub(i,i)
					if smodi==' 'then -- nothing
					elseif modiDict[smodi]~=nil then
						tmino=tmino..'-'..modiDict[smodi]
					else
						return message(dbgx.."modifier invalid, mdl01234")
					end
				end
			elseif smino=='A' then
				tmino='arrow'
				local sarrow=arrowDict[mw.text.trim(sslice:sub(2))]
				if sarrow==nil then return message(dbgx.."arrow/marker invalid, LRUDlr") end
				tmino=tmino..'-'..sarrow
			elseif smino=='a' then
				tmino='ascii'
				local sascii=asciiDict[mw.text.trim(sslice:sub(2))]
				if sascii==nil then return message(dbgx.."ascii invalid") end
				tmino=tmino..'-'..sascii
			else
				return message(dbgx.."unrecognized first char, mino or X-Aa but got "..smino)
			end
			trow[x]=tmino
		end
		tfield[y]=trow
	end
	return genRows(tfield,argSmall)
end
function p.field(frame)
	local args=tableassign({},frame.args)
	local pargs=frame:getParent().args -- must be called in template
	tableassign(args,pargs)
	return p._field(args)
end
return p