local MAHJONG_TOTAL_TILES = 108 -- 总牌数(万条筒各1-9×4)
local MAHJONG_TILES_PER_WALL = 27 -- 每方墙 108/4 = 27 张
--- 根据骰子点数和剩余张数计算4方墙各显示多少张
--- 牌墙是连续一整条,从切牌位顺时针顺序消耗,剩余显示在对应墙
--- @param nRemainingCount number 剩余总张数
--- @param tDice table|nil 骰子数组 {d1, d2},nil时均分
--- @return table tWallTiles[1..4] 每方墙张数
function MahjongUI_CalcWallTiles(nRemainingCount, tDice)
local tWall = { 0, 0, 0, 0 }
local R = nRemainingCount or 0
if R <= 0 then return tWall end
local nCutWall = 1
local nDeadCards = 0
if tDice and tDice[1] and tDice[2] and not (tDice[1] == 0 and tDice[2] == 0) then
local nSum = tDice[1] + tDice[2]
nCutWall = ((nSum - 1) % 4) + 1
nDeadCards = (nSum - 1) * 2
end
-- 1. 切牌墙先放死牌
local nDead = math.min(nDeadCards, R)
tWall[nCutWall] = nDead
R = R - nDead
if R <= 0 then return tWall end
-- 2. 顺时针分配(下家->对家->上家)
local MAX_PER_WALL = 26
local ORDER = { [1]=2, [2]=3, [3]=4, [4]=1 }
local nIdx = nCutWall
while R > 0 do
nIdx = ORDER[nIdx]
local nCanAdd = MAX_PER_WALL - tWall[nIdx]
local nAdd = math.min(nCanAdd, R)
tWall[nIdx] = tWall[nIdx] + nAdd
R = R - nAdd
if nCanAdd <= 0 then break end
end
if R > 0 then
tWall[nCutWall] = tWall[nCutWall] + R
end
if MAHJONG_DEBUG then
module_mahjong.MJLOGT("CalcWallTiles: result=%s", toString(tWall))
end
return tWall
end
--- 根据骰子计算每方墙初始牌数(发牌后剩余56张)
--- @param tDice table 骰子点数
--- @return table[4] 每方墙初始牌数
function MahjongUI_GetInitWallTiles(tDice)
return MahjongUI_CalcWallTiles(56, tDice)
end
-- === _UpdateMaPai_Unified ===
-- 统一牌墙渲染(带详细日志)
local function _UpdateMaPai_Unified(nDir, wndMaPai, nTiles, nCutSeat, nDiceSum)
local szTemplate = MAPAI_TEMPLATES[nDir]
local MAX_PILES = 13
local bIsCutWall = (nDir == nCutSeat)
local nDeadPiles = bIsCutWall and (nDiceSum - 1) or 0
local nDeadCards = nDeadPiles * 2
local nEffMaxPiles = MAX_PILES - nDeadPiles
local nEffectiveTiles = math.max(0, nTiles - nDeadCards)
local nRemainingPiles = math.ceil(nEffectiveTiles / 2)
local bPartial = (nEffectiveTiles > 0) and (nEffectiveTiles % 2 == 1)
local nConsumed = nEffMaxPiles - nRemainingPiles
-- 入口方向:仅本家入口在索引13,其他三家入口在索引1
local bEntryAtHigh = (nDir == 1 or nDir == 4)
if MAHJONG_DEBUG then
module_mahjong.MJLOGT("_UpdateMaPai_Unified: dir=%d, tiles=%d, isCut=%s, deadPiles=%d, effTiles=%d, remainPiles=%d, consumed=%d, partial=%s",
nDir, nTiles, tostring(bIsCutWall), nDeadPiles, nEffectiveTiles, nRemainingPiles, nConsumed, tostring(bPartial))
end
local tCutWalls = {}
for i = 1, MAX_PILES do
local nIdx = i -- 控件从左到右创建
-- 计算该位置在消耗方向上的逻辑序号(1 = 入口端)
local nPos
if bEntryAtHigh then
nPos = MAX_PILES - i + 1 -- 本家:索引13对应位置1(入口)
else
nPos = i -- 其他:索引1对应位置1(入口)
end
local bPH, bIsPartial
if nPos <= nDeadPiles then
bPH, bIsPartial = false, false
elseif nPos <= nDeadPiles + nConsumed then
bPH, bIsPartial = true, false
elseif nPos == nDeadPiles + nConsumed + 1 and bPartial then
bPH, bIsPartial = false, true
else
bPH, bIsPartial = false, false
end
local wndCard = wndMaPai:CreateChildWindowFromTemplate(szTemplate)
if not wndCard then break end
wndCard:SetName("mapai_" .. tostring(nDir) .. "_" .. tostring(nIdx))
wndCard:Show(true)
local card1 = wndCard:FindEx("card1")
local card2 = wndCard:FindEx("card2")
card1:Show(true)
card2:Show(true)
if bPH then
card1:SetAlpha(0.0)
card2:SetAlpha(0.0)
elseif bIsPartial then
card1:SetAlpha(1.0)
card2:SetAlpha(0.0)
elseif bIsCutWall then
table_insert(tCutWalls, wndCard)
else
card1:SetAlpha(1.0)
card2:SetAlpha(1.0)
end
end
-- 切牌墙专用二次修正
if bIsCutWall and #tCutWalls > 0 then
local totalCutPiles = nDiceSum - 1
local totalCutCards = totalCutPiles * 2
local nDrawn = math.max(0, totalCutCards - nTiles)
local nHideFull = math.floor(nDrawn / 2)
local bCutPartial = (nDrawn % 2 == 1)
-- 根据方向决定遍历顺序:本家/左家倒序(从右到左),右家/对家正序(从左到右)
local bReverse = bEntryAtHigh -- 高位入口的方向采用倒序
local nCount = #tCutWalls
for seq = 1, nCount do
local wndCard
if bReverse then
wndCard = tCutWalls[nCount - seq + 1] -- 倒序
else
wndCard = tCutWalls[seq] -- 正序
end
local card1 = wndCard:FindEx("card1")
local card2 = wndCard:FindEx("card2")
if seq <= nHideFull then
card1:SetAlpha(0.0)
card2:SetAlpha(0.0)
elseif seq == nHideFull + 1 and bCutPartial then
card1:SetAlpha(1.0)
card2:SetAlpha(0.0)
else
card1:SetAlpha(1.0)
card2:SetAlpha(1.0)
end
end
end
end
-- === MahjongUI_UpdateMaPai ===
-- 牌墙渲染入口:根据切牌位置分派到固定13墩(Fixed13)或动态 SC 模板。
-- 销毁并重新创建所有牌墙实例。
-- 阶段:所有阶段(初始发牌和游戏中)。
-- @param nDir number 方向 (1-4)
-- @param nRemainingTiles number 该墙当前剩余牌数
-- @param nInitTiles number 该墙初始牌数
-- @return nil
function MahjongUI_UpdateMaPai(nDir, nRemainingTiles, nInitTiles)
local frame = FindWindow("mahjong_main_panel")
if not frame then return end
local wndMaPai = MahjongUI_FindWindow(frame, "wnd_table", "list_mapai" .. tostring(nDir))
if not wndMaPai then return end
local nTiles = nRemainingTiles or 0
local nInit = nInitTiles or 0
if nInit <= 0 then
if nDir ~= 1 and nDir ~= 2 and nDir ~= 3 and nDir ~= 4 then return end
end
wndMaPai:DestroyAllChildWindow()
local tDice = module_mahjong.mahjong_client_core.Client_GetData().tDice
local nDiceSum = (tDice and tDice[1] and tDice[2]) and (tDice[1] + tDice[2]) or 0
local nCutSeatAbs = (nDiceSum > 0) and ((nDiceSum - 1) % 4 + 1) or 0
local nMySeat = MahjongUI_GetMySeat()
local nCutSeat = (nCutSeatAbs > 0 and nMySeat > 0) and MahjongUI_GetRelativeSeat(nMySeat, nCutSeatAbs) or 0
if MAHJONG_DEBUG then
module_mahjong.MJLOGT("UpdateMaPai: nDir=%d nCutSeat(abs=%d→rel=%d) nMySeat=%d nTiles=%d",
nDir, nCutSeatAbs, nCutSeat, nMySeat, nTiles)
end
-- 统一渲染(正式使用)
_UpdateMaPai_Unified(nDir, wndMaPai, nTiles, nCutSeat, nDiceSum)
end