"모듈:마작패"의 두 판 사이의 차이

(마작패 모듈 테스트(리브레에서 퍼옴))
 
(펑 -> 퐁으로 용어 변경)
 
22번째 줄: 22번째 줄:
  
 
-- 펑, 치, 깡 --
 
-- 펑, 치, 깡 --
pon = '', chi = '치', kan = '깡',
+
pon = '', chi = '치', kan = '깡',
 
left = '상가', face = '대가', right = '하가',
 
left = '상가', face = '대가', right = '하가',
  

2022년 9월 19일 (월) 23:55 기준 최신판

이 모듈에 대한 설명문서는 모듈:마작패/설명문서에서 만들 수 있습니다

--[[
--   token 변수는 실제 사용자로부터 입력받는 글자, 또는 화면에 출력되는 글자를
--   정의합니다. 만약 이 모듈을 포크해서 다른 위키로 가져가는 경우 이 부분을 필
--   요에 따라 다르게 수정하면 됩니다.
--]]
local token = {
-- mw 파일 표기 prefix--
	file_header = '파일:마작-',
	img_side = '-누움',
	ext = '.png',

-- 수패 --
	man = '만', pin = '통', sou = '삭',

-- 자패 --
	E = '동', S = '남', W = '서', N = '북',
	white = '백', green = '발', red = '중',

-- 꽃패 --
	f1 = '춘', f2 = '하', f3 = '추', f4 = '동',
	f5 = '매', f6 = '난', f7 = '국', f8 = '죽',

-- 펑, 치, 깡 --
	pon = '퐁', chi = '치', kan = '깡',
	left = '상가', face = '대가', right = '하가',

-- 뒷면 --
	back = '뒤',

-- 특수표기 --
	dora = '적',
	side = '-',
	double = '/',

-- 쯔모, 론, 도라, 게임 등의 옵션들 --
	tsumo_ = '쯔모',
	ron_ = '론',
	dora_ = '도라',
	wind_ = '자리',
	side_ = '가',
	game_ = '게임',

-- 에러메시지 --
	error = {
		default = "에러가 발생했습니다. 구문을 확인해주세요",
		nofunct = "알 수 없는 함수가 호출되었습니다",
		body = "패가 잘못되었습니다. 구문을 확인해주세요",
		dir = "알 수 없는 방향입니다. 구문을 확인해주세요",
		braket = "괄호가 없거나 잘못되었습니다. 구문을 확인해주세요"
	},

-- 공백 --
	space = ' '
}

-- 이미지 기본값 --
local image_default_size = 33
local image_real_width = 66
local image_real_height = 90

--[[ 헬퍼 함수들 ]]--
local ustring = mw.ustring
local image_ratio = function (x) return math.floor(x * image_real_height / image_real_width) end

-- switch-case 문을 위한 함수 --
-- from http://lua-users.org/wiki/SwitchStatement --
local function switch (t)
	t.case = function (self, x)
		local f = self[x] or self.default
		if f then
			if type(f) == "function" then
				f(x, self)
			else
				error(tostring(x) .. ":" .. token.error.nofunct)
			end
		end
	end
	return t
end

-- mw용 이미지 문법을 생성해주는 함수 --
local function toImg ( data, width )
	width = '|' .. width .. 'px'
	local result = '[[' .. token.file_header .. data .. token.ext .. width ..'|link=|bottom]]'
	return result
end

-- 에러 출력용 함수 --
local function err ( message, pos, func )
	func = (func ~= nil) and '['..func..'] ' or ''
	pos = (pos ~= nil) and 'p'..pos..': ' or ''
	error(func..pos..message)
end

-- 펑/깡을 위한 방향 체크 --
local pon_dir = {
	[token.left] = 1,
	[token.face] = 2,
	[token.right] = 3
}
local kan_dir = {
	[token.left] = 1,
	[token.face] = 2,
	[token.right] = 4
}

-- forward declaration --
local pre_option
local post_option

--[[ 입력받은 값을 분석하는 메인 함수 ]]--
local function single_parse ( data, options )
	if (data == nil) then err(token.error.default) end

	local result = ""

	-- parser variable --
	local index = 0
	local stack = {}
	local pre = ""
	local side = ""
	local wrapper = false

	clean_stack = function () index = 0 ; stack = {} end
	clean_state = function () pre = "" ; side = "" ; wrapper = false end

	local size = options.size or image_default_size

	local token_parse = {
		default = function (x) return true end
	}

	-- 특수표기 --
	token_parse[token.dora] = function (x) pre = x end
	token_parse[token.side] = function (x) side = token.img_side end
	token_parse[token.double] = function (x) wrapper = true ; side = token.img_side end

	-- 숫자 --
	for i = 1, 9 do
		token_parse[tostring(i)] = function (x)
			index = index + 1
			stack[index] = {}

			-- 필요한 값들을 스택에 넣는다
			stack[index].main = x
			stack[index].pre = pre
			stack[index].side = side
			stack[index].wrapper = wrapper
			clean_state()
		end
	end

	-- 수패 --
	local function numbers (x)
		for i = 1, index do
			local width =  (stack[i].side ~= '') and image_ratio(size) or size
			local caption = (stack[i].pre or '') .. stack[i].main .. x .. (stack[i].side or '')

			if (stack[i].wrapper) then -- 이중 처리
				result = result .. '<div style="display: inline-block;">'
								.. '<div>' .. toImg( caption, width ) .. '</div>'
								.. toImg( caption, width )
								.. '</div>'
			else
				result = result .. toImg( caption, width )
			end
		end
		clean_stack() ; clean_state()
	end
	token_parse[token.man], token_parse[token.pin], token_parse[token.sou] = numbers, numbers, numbers

	-- 자패, 뒷면 --
	local function strings (x)
		local width = (side ~= '') and image_ratio(size) or size

		if (wrapper) then -- 이중 처리
			result = result .. '<div style="display: inline-block;">'
							.. '<div>' .. toImg( x..side, width ) .. '</div>'
							.. toImg( x..side, width )
							.. '</div>'
		else
			result = result .. toImg( x..side, width )
		end
		clean_stack() ; clean_state()
	end
	token_parse[token.E] = strings
	token_parse[token.S] = strings
	token_parse[token.W] = strings
	token_parse[token.N] = strings
	token_parse[token.white] = strings
	token_parse[token.green] = strings
	token_parse[token.red] = strings
	token_parse[token.back] = strings

	-- 꽃패 --
	token_parse[token.f1] = strings
	token_parse[token.f2] = strings
	token_parse[token.f3] = strings
	token_parse[token.f4] = strings
	token_parse[token.f5] = strings
	token_parse[token.f6] = strings
	token_parse[token.f7] = strings
	token_parse[token.f8] = strings
	
	-- switch 문 구성 --
	token_parse = switch(token_parse)

	-- utf8 문자별로 루프 진행 --
	local pos = 0
	while #data > 0 do
		local c = ustring.match(data, ".")
		data = data:sub(#c+1) ; pos = pos + 1

		token_parse:case(c) -- 위 select-case 문에 따라 처리
	end

	return '<div class="majong" style="display:inline-block;">'..result..'</div>'
end

local function parse ( frame ) -- '파싱' 함수 entry point
	return pre_option(frame.args) .. single_parse( frame.args[1]:match("^%s*(.-)%s*$"), frame.args ) .. post_option(frame.args) -- trim
end

local function single_print ( data, options )
	if (data == nil) then err(token.error.default) end
	local pos = 1

	local op = ustring.match(data, ".") or ''
	pos = pos + #op
	local caption = ''

	-- 치 --
	if (op == token.chi) then
		data = data:sub(#op+1)
		local body = ustring.match(data, "%s*(%d?.)") or ''
		if (#body == 0) then err(token.error.body, pos, token.chi) end
		pos = pos + #body

		local tail = ustring.match(data, ".*%((.-)%)") or '' -- 괄호 부분 체크
		if (#tail == 0) then err(token.error.braket, pos, token.chi) end
		pos = pos + #tail

		caption = token.side .. body .. tail -- 치는 반드시 상가에게만 받게됨

	-- 펑 --
	elseif (op == token.pon) then
		data = data:sub(#op+1)
		local body = ustring.match(data, "%s*(/?%d?.)") or ''
		if (#body == 0) then err(token.error.body, pos, token.pon) end
		pos = pos + #body

		local tail = ustring.match(data, ".*%((.-)%)") or '' -- 괄호 부분 체크
		if (#tail == 0) then err(token.error.braket, pos, token.pon) end
		pos = pos + #tail

		-- 가깡 처리 --
		local selector = token.side
		if (body:sub(1, 1) == '/') then
			body = body:sub(2)
			selector = token.double
		end

		-- 방향에 맞게 캡션 처리 --
		local dir = pon_dir[tail] or -1
		if (dir == -1) then err(token.error.dir, pos, token.pon) end
		for i = 1, 3 do
			if (i == dir) then caption = caption .. selector end
			caption = caption .. body
		end

	-- 깡 --
	elseif (op == token.kan) then
		data = data:sub(#op+1)
		local body = ustring.match(data, "%s*(/?%d?.)") or ''
		if (#body == 0) then err(token.error.body, pos, token.kan) end
		pos = pos + #body

		local tail = ustring.match(data, "%s*%((.-)%)") or '' -- 괄호 부분 체크
		pos = pos + #tail

		-- 안깡 --
		if (#tail == 0) then
			caption = token.back .. body .. body .. token.back

		-- 가깡 --
		elseif (body:sub(1, 1) == '/') then
			local dir = pon_dir[tail] or -1
			if (dir == -1) then err(token.error.dir, pos, token.kan) end
			body = body:sub(2)

			for i = 1, 3 do
				if (i == dir) then caption = caption .. token.double end
				caption = caption .. body
			end

		-- 대명깡 --
		else
			local dir = kan_dir[tail] or -1
			if (dir == -1) then err(token.error.dir, pos, token.kan) end

			for i = 1, 4 do
				if (i == dir) then caption = caption .. token.side end
				caption = caption .. body
			end
		end

	-- 기타 --
	else
		caption = data
	end

	return single_parse(caption, options)
end

local function print ( frame ) -- '출력' 함수 entry point
	local result = ''
	local data = {}

	for k, line in pairs(frame.args) do -- 옵션값을 제외한 나머지 입력값들을 data에 모음
		if (type(k) == "number") then data[k] = line end
	end

	for i = 1, #data do
		result = result .. single_print( data[i]:match("^%s*(.-)%s*$"), frame.args ) -- trim
		if (i ~= #data) then result = result .. token.space end
	end

	return pre_option(frame.args) .. result .. post_option(frame.args)
end

--[[ 옵션 출력을 위한 보조 함수 ]]--
pre_option = function (options)
	local result = ''

	-- 게임 옵션 출력 --
	if  (options[token.game_] ~= nil) then
		result = options[token.game_]
	end

	-- 자리 출력 --
	if (options[token.wind_] ~= nil) then
		if (result ~= '') then result = result .. token.space end
		result = result .. options[token.wind_] .. token.side_
	end

	if (result ~= '') then result = '(' .. result .. ')' .. token.space end

	return '<div class="majong-group" style="white-space:nowrap;overflow-x:auto;">' .. result
end

post_option = function (options)
	local result = ''

	-- 화료패 출력 --
	if (options[token.tsumo_] ~= nil) then
		result = token.space .. token.tsumo_ .. single_parse(options[token.tsumo_], options)
	elseif (options[token.ron_] ~= nil) then
		result = token.space .. token.ron_ .. single_parse(options[token.ron_], options)
	end

	-- 도라패 출력 --
	if (options[token.dora_] ~= nil) then
		if (result ~= '') then result = result .. token.space end
		result = result .. token.dora_ .. single_parse(options[token.dora_], options)
	end

	if (result ~= '') then result = token.space .. result end

	return result .. '</div>'
end

return {
	['파싱'] = parse,
	['출력'] = print,

-- 자주 쓰이는 함수들 --
	['만수패'] = function (f) return single_parse( "123456789만", f.args ) end,
	['통수패'] = function (f) return single_parse( "123456789통", f.args ) end,
	['삭수패'] = function (f) return single_parse( "123456789삭", f.args ) end,
	['자패'] = function (f) return single_parse( "동남서북백발중", f.args ) end,
	['바람패'] = function (f) return single_parse( "동남서북", f.args ) end,
--  ['꽃패'] = function (f) return single_parse( "춘하추동매난국죽", f.args ) end,
	['삼원패'] = function (f) return single_parse( "백발중", f.args ) end
}