孤陋寡闻如我,也只是去年才知道有“连环夺宝”这么个游戏,而且似乎各福彩大厅都有数十台游戏机供人“娱乐”。去年和几个朋友路过福彩看了下,大厅里在玩《连环夺宝》的人似乎挺多的,几十台的游戏机都基本不够用。玩众也百色,无论是西装革履的中年同志,抑或打扮普通的普通民众,都玩得不亦乐乎...老实说,我是无法理解为何他们对此沉迷如斯的,当然我对此类游戏确实也不感冒。
可能,通过游戏赚(大)钱、打发时间是比较合理的解释吧。譬如每台游戏机都会不定时的滚屏播报某地谁又中了多少奖励之类信息,对不少玩众还是有些或较有诱惑力的。
当然,《连环夺宝》由福彩运营(?),在某些方面,可能玩众也会放心一些吧。
作为技术人员,我其实对《连环夺宝》里的宝石连线算法以及宝石矩阵填充处理等技术点比较感兴趣。较之游戏开发里的常规业务逻辑编写,可能它们在实现上更有“技术含量”吧。
言归正传。第一个问题,宝石连线算法,一开始我没看清连线规则,以为是“一笔画”之类,后发现不是,只要相邻的宝石同类即可连线。细想了会算法实现,很明显,广度优先搜索即非常适合这种应用场景(连连看之类游戏差不多应也是类似算法处理)。
于是来到第二个问题,当每次符合连线规则的宝石消除后,应如何填充这个宝石矩阵呢?毕竟,这是个联网游戏,假定关键性的游戏逻辑都在服务器端计算,那么服务器应如何将宝石矩阵(可能较大)以及消除信息(可能较多)等告知客户端呢?我想出的做法粗略如下:
0) 宝石矩阵(GemMatrix)生成这个没啥说的,一般都是通过各类宝石概率配置来生成每一个元素
1) 消除经 BFS 标记出的宝石(可能有多组)
2) 对 GemMatrix 按列逐行由低往高,移动宝石至低行空位,最后计算出需填补的行数(NeedRows)
3) 对额外的宝石填充矩阵(GemMatrixEx)作类似上一步的处理
4) 若 GemMatrixEx 的完整行数(AvailRows)少于 NeedRows,则将 GemMatrixEx 扩充 NeedRows - AvailRows 行并按步骤 0 的规则填充,再以步骤 2 的规则作宝石移动处理
5) 按列逐行由低往高将 GemMatrixEx 中的非空元素置入 GemMatrix 中的空位
6) 对填充后的 GemMatrix 作 BFS 处理,再转到步骤 1,循环往复直至 GemMatrix 消无可消
而综合以上思路,由服务器告知客户端的最终的宝石大矩阵及消除历史等信息已不难生成。
简化的处理代码如下。
-- 每关中宝石矩阵尺寸 local MatrixSizeArr = {4, 5, 6} -- 宝石矩阵 local Gems = {} -- 宝石填补矩阵 local GemsStandby = {} local function GetValidStandbyRowNum(lv) local ret = 0 local sz = MatrixSizeArr[lv] local r = #GemsStandby for row = 1, r do for col = 1, sz do if GemsStandby[row][col] == 0 then return ret end end ret = ret + 1 end return ret end local function ExtendStandbyGems(row, lv) local sz = MatrixSizeArr[lv] for i = 1, row do table.insert(GemsStandby, {}) for j = 1, sz do table.insert(GemsStandby[#GemsStandby], GenGemIndex(j)) end end end local function ShiftStandbyGems(lv) local r = #GemsStandby if r < 2 then return true end local sz = MatrixSizeArr[lv] for col = 1, sz do for row = 1, r do if GemsStandby[row][col] == 0 then for k = row + 1, r do if GemsStandby[k][col] >= 1 and GemsStandby[k][col] <= 5 then GemsStandby[row][col] = GemsStandby[k][col] GemsStandby[k][col] = 0 break end end end end end return true end local function ShiftGems(lv) local sz = MatrixSizeArr[lv] for col = 1, sz do for row = 1, sz do if Gems[row][col].Selected or Gems[row][col].GemIndex == 0 then local bFlag = false for k = row + 1, sz do if (not Gems[k][col].Selected) and (Gems[k][col].GemIndex >= 1) and (Gems[k][col].GemIndex <= 5) then Gems[row][col].GemIndex = Gems[k][col].GemIndex Gems[row][col].Selected = false Gems[k][col].GemIndex = 0 Gems[k][col].Selected = false bFlag = true break else Gems[k][col].Selected = false Gems[k][col].GemIndex = 0 end end if not bFlag then Gems[row][col].GemIndex = 0 Gems[row][col].Selected = false end end end end return true end local function FillGems(lv) local needRows = 0 local sz = MatrixSizeArr[lv] for col = 1, sz do local tmp = 0 for row = sz, 1, -1 do if Gems[row][col].GemIndex == 0 then tmp = tmp + 1 else break end end if tmp > needRows then needRows = tmp end end ShiftStandbyGems(lv) local num = GetValidStandbyRowNum(lv) if num < needRows then ExtendStandbyGems(needRows - num, lv) ShiftStandbyGems(lv) end for col = 1, sz do for row = 1, sz do if Gems[row][col].GemIndex == 0 then for row2 = 1, needRows do if GemsStandby[row2][col] ~= 0 then Gems[row][col].GemIndex = GemsStandby[row2][col] GemsStandby[row2][col] = 0 break end end end end end return true end