【游戏功能】
棋牌室里流传一句老话:新手看自己的棋,高手看对手的棋。五子棋 AI 的写法恰好是这句话的注脚——只会给自己找连点的 AI 像新手,会算对手连点的 AI 才像高手。今天拆五子连珠的对局表现,重点讲"攻防打分"怎么让两边都下得像人。
一、黑白轮流与活三冲四
对局的表演节奏由落子间隔撑起:每手之间留出半秒上下的"思考空档",棋子从半空落下带一段小弹跳,落定在交叉点上。打分的逻辑外化成一句解说级的规则:轮到谁,先看自己有没有"下这一手就五连"的点(万分开销必拿),再看有没有"四连带两头空"的活四点(八千分紧随其后),然后是活三(九百分)、眠三(九十分)——分数的量级差就是棋手的语气差:必杀点毫不犹豫,活三点果断,小点犹豫着下。防守分乘个系数加进总分,于是会出现"明明自己能成三,却先堵了对面的冲四"这种像人下的棋。
完整演示在这,默认自动对局,可调落子间隔:
fx-gomoku
二、幂尔框架实现:四方向双向计分
每个空点的分数由四个方向的两端延伸计数得出:
local Gomoku = {}
local KEY = { N = 11 }
local DIRS = { { 1, 0 }, { 0, 1 }, { 1, 1 }, { 1, -1 } }
local function LineScore(board, c, r, me)
local total = 0
for _, d in ipairs(DIRS) do
local cnt, block = 1, 0
for s = 1, 4 do
local cc, rr = c + d[1] * s, r + d[2] * s
if cc < 1 or rr < 1 or cc > KEY.N or rr > KEY.N then
block = block + 1; break
end
local v = board[rr][cc]
if v == me then cnt = cnt + 1
elseif v > 0 then block = block + 1; break end
end
for s = 1, 4 do
local cc, rr = c - d[1] * s, r - d[2] * s
if cc < 1 or rr < 1 or cc > KEY.N or rr > KEY.N then
block = block + 1; break
end
local v = board[rr][cc]
if v == me then cnt = cnt + 1
elseif v > 0 then block = block + 1; break end
end
if cnt >= 5 then total = total + 100000
elseif cnt == 4 then total = total + (block >= 2 and 800 or 8000)
elseif cnt == 3 then total = total + (block >= 2 and 90 or 900)
else total = total + cnt * 10 - block * 5 end
end
return total
end
function Gomoku.Pick(board, me, foe)
local best, bestScore = nil, -1
for r = 1, KEY.N do
for c = 1, KEY.N do
if board[r][c] == 0 then
-- 进攻分放大一点, 先手优势
local s = LineScore(board, c, r, me) * 1.05
+ LineScore(board, c, r, foe)
if s > bestScore then bestScore = s; best = { c = c, r = r } end
end
end
end
return best
end
-- 落子后由引擎回调判胜: 任一方向连满五子即胜
function Gomoku.OnPlace(c, r, me)
for _, d in ipairs(DIRS) do
local cnt = 1
for s = 1, 4 do
local cc, rr = c + d[1] * s, r + d[2] * s
if SL:GetValue("cell", cc, rr) == me then cnt = cnt + 1 else break end
end
for s = 1, 4 do
local cc, rr = c - d[1] * s, r - d[2] * s
if SL:GetValue("cell", cc, rr) == me then cnt = cnt + 1 else break end
end
if cnt >= 5 then
SL:PlayFxAt("win_line", c, r, d[1], d[2])
SL:ShowSystemTips((me == 1 and "黑" or "白") .. "五连!")
end
end
end
return Gomoku
进攻分乘 1.05 是先手微调:同分时倾向自己进攻,棋风立刻积极起来。
三、终局的仪式感
五连成就的瞬间是全场唯一的重音:五颗子连成一条金线,线端各亮一颗光点,输家一侧的棋子整体降低透明度——金线在暗子上,胜负一目了然。停两秒半再重开,这两秒半里观众会自己复盘"刚才哪手是败招",这是棋类演示特有的回味时间,别省。落子的弹跳动画要短促:半空落下、触底压缩、回弹一次定格,超过 0.3 秒的弹跳会拖累整个对局的节奏感。棋盘的线画成松绿色而不是灰白,和传统棋盘的视觉记忆对上,深色主题里这一抹绿就是棋类的身份牌。
全站技术干货持续更新:996 引擎 / Lua 实战帖,语法、参数与示例一篇讲透。进入文章地图 · 查看全部 →
【游戏功能】 法师的困扰千篇一律:蓝条见底符纸哑火,站在怪堆里干瞪眼——直到有人给他看了一行账:符落三十五蓝,杖挥一刀回十二…
【游戏功能】 地刺从脚底下炸出来的那半秒,大多数人的选择是躲——只有极少数人按下了偷师。偷师的机制是一场限时的一秒课:它抬手…
【游戏功能】 两只狼配合得像一个人:一只咬前蹄、一只扑后背,多少人栽在这对搭档手里——直到有人往白狼背上钉了一张嫁祸符。离间…
【游戏功能】 弓狼在远处放冷箭,刀狼在近处追着你——两头受气的死局,怎么破?有老手给出的答案匪夷所思:往刀狼身后一站,让它替…
【游戏功能】 buffs[name] = nil ——一行删表项的代码,写尽了驱散全部的狠辣:狼怪顶着三层好处——狂暴加攻、…
【游戏功能】 弓箭手的伤害明明不低,为什么同样的怪别人十秒放倒、他要磨半分钟?看完他的操作就明白了:箭是乱射的,囊空了站在原…