分享一个五子棋AI算法,完整代码, 核心代码不到300行
主要思路:落子时, 遍历棋盘上所有空白位置,算出该点落子的得分,并保存下来, 最后算出最大得分的落子位置,出现多个相同得分时, 随机选择一个。
评分主要类型见代码中 socre_type枚举定义
AI强度大约为中等,和普通人对弈, 大约80%胜率。
带AI对弈的完整lua代码:
local sid = 0
local function id(initial)
sid = initial and initial or sid + 1
return sid
end
score_type = {
fazhan2 = id(), -- 发展2
fazhan3x = id(), -- 一端被阻挡的3
zudang2 = id(), -- 阻挡2
fazhan3 = id(), -- 两端无阻挡的3
zudang3 = id(), -- 阻挡对方形成的双向无阻挡的3
fazhan4x = id(), -- 发展一端阻挡的4
fazhan4 = id(), -- 发展无阻挡的4
zudang4 = id(), -- 阻挡对方单封堵的4
five = id(), -- 五子连珠
}
size = 15 -- 棋盘大小
board = {}
-- 评分相等时,计算坐标离中心的距离
-- TODO, 优化为同时考虑坐标周围无其他方向的对手棋子
function center(i,j)
return math.abs(size/2-i) + math.abs(size/2-j)
end
-- 计算指定坐标下t类型的棋子的得分
function score(board, t, i, j)
local function get_score(left, right, t)
local line = {}
for _, v in pairs(left) do
table.insert(line, v)
end
table.insert(line, t)
for _, v in pairs(right) do
table.insert(line, v)
end
-- local print = function() end
-- 五个的情况
for k=1, #line-4 do
if line[k]==t and line[k+1]==t and line[k+2]==t and line[k+3]==t and line[k+4]==t then
print(i,j, "five")
return score_type.five
end
end
-- 阻挡四
for k=1, #line-4 do
local diff_count = 0
local same_count = 0
for i=k, k+4 do
same_count = same_count + (line[i] == t and 1 or 0)
diff_count = diff_count + (line[i] == -t and 1 or 0)
end
if diff_count == 4 and same_count == 1 then
return score_type.zudang4
end
end
-- 四个的情况
local s = 4==(#left) and 2 or 1
for k=s, #left+1 do
if line[k]==t and line[k+1]==t and line[k+2]==t and line[k+3]==t then -- 能形成连续的四个
-- 左端有无阻挡
local lx,rx = false, false
if not line[k-1] or line[k-1] ~= 0 then
lx = true
end
-- 右端有无阻挡
if not line[k+4] or line[k+4] ~= 0 then
rx = true
end
if not (lx and rx) then
if (not lx) and (not rx) then
return score_type.fazhan4
else
return score_type.fazhan4x
end
end
end
end
--中间有一个空白的四个 和 一端被阻挡的四个 同分
for k=1, #left+1 do
local same_count = 0
local zero_count = 0
for i=k, k+4 do
same_count = same_count + (line[i] == t and 1 or 0)
zero_count = zero_count + (line[i] == 0 and 1 or 0)
end
if same_count == 4 and zero_count == 1 then
return score_type.fazhan4x
end
end
-- 三个的情况
s = #left+1-2
if s<0 then
s=1
end
-- 阻挡3
local t_idx = #left+1
for k=1, #left+1 do
if k<=t_idx and k+3>=t_idx then
local diff_count = 0
local same_count = 0
for i=k, k+3 do
same_count = same_count + (line[i] == t and 1 or 0)
diff_count = diff_count + (line[i] == -t and 1 or 0)
end
if diff_count == 3 and same_count == 1 then
-- 有一端已经挡住了的都不算
if (line[k-1] and line[k-1] == t ) or
(line[k+4] and line[k+4] == t) then
else
return score_type.zudang3
end
end
end
end
for k=s, #left+1 do
-- 连续的三个
if line[k] == t and line[k+1]==t and line[k+2]==t then
-- 阻挡
local lx,rx = false, false
if line[k-1] and line[k-1] ~= 0 then
lx = true
end
-- 右端有无阻挡
if line[k+3] and line[k+3] ~= 0 then
rx = true
end
if not (lx and rx) then
if (not lx) and (not rx) then
return score_type.fazhan3
else
return score_type.fazhan3x
end
end
end
end
s=s-1
if s<=0 then s = 1 end
for k=s, #left+1 do
local same_count = 0
local zero_count = 0
for i=k, k+3 do
same_count = same_count + (line[i] == t and 1 or 0)
zero_count = zero_count + (line[i] == 0 and 1 or 0)
end
if same_count == 3 and zero_count == 1 then
-- 有一段已经挡住了的都不算
if (not line[k-1] or line[k-1] == -t ) or
(not line[k+4] or line[k+4] == -t) then
else
return score_type.fazhan3
end
end
end
-- 两个的情况
-- 阻挡2
local t_idx = #left+1
for k=1, #left+1 do
if k<=t_idx and k+3>=t_idx then
local diff_count = 0
local same_count = 0
for i=k, k+3 do
same_count = same_count + (line[i] == t and 1 or 0)
diff_count = diff_count + (line[i] == -t and 1 or 0)
end
if diff_count == 2 and same_count == 1 then
-- 有一端已经挡住了的都不算
if (line[k-1] and line[k-1] == t ) or
(line[k+4] and line[k+4] == t) then
else
return score_type.zudang2
end
end
end
end
-- 发展2
return 0
end
-- 左右各取最多四个坐标的值
local function get_line(ldx,ldy,rdx,rdy)
local left = {}
local right = {}
local c = 4
for k=c, 1, -1 do
local v = board[i+ldx*k] and board[i+ldx*k][j+ldy*k]
if v then
table.insert(left, v)
end
end
for k=1, c do
local v = board[i+rdx*k] and board[i+rdx*k][j+rdy*k]
if v then
table.insert(right, v)
end
end
return left, right
end
local scores = {}
-- 横向
local l, r = get_line(-1,0,1,0)
scores[1] = get_score(l,r,t)
-- 纵向
l,r = get_line(0,-1,0,1)
scores[2] = get_score(l,r,t)
-- 斜线
l,r = get_line(-1,1,1,-1)
scores[3] = get_score(l,r,t)
-- 反斜线
l,r = get_line(-1,-1,1,1)
scores[4] = get_score(l,r,t)
table.sort(scores, function(a,b) return a > b end )
return scores
end
function drop(board, t)
local ss = {} -- 所有可下点的评分和下注点
for i=1, size do
for j=1, size do
if board[i][j] == 0 then
s = score(board, t,i,j)
table.insert(ss, {s=s,i=i,j=j})
end
end
end
-- 棋局已经满了
if #ss == 0 then return 0 end
-- 按照评分排序
table.sort(ss, function(a,b)
for k=1, 4 do
if a.s[k] == b.s[k] then
if a.s[k] == 0 then
return center(a.i, a.j) < center(b.i, b.j)
end
elseif a.s[k] > b.s[k] then
return true
else
return false
end
end
end)
local chooses = {ss[1]}
-- 增加AI随机性
local function is_same_score(s1, s2)
if s1 and s2 then
for k=1, 4 do
if s1.s[k] ~= s2.s[k] then
return false
end
end
return true
end
end
for k=1, #ss-1 do
if ss[k].s[1] > 0 and is_same_score(ss[k], ss[k+1]) then
table.insert(chooses, ss[k+1])
else
break
end
end
local choose = chooses[math.random(#chooses)]
local i,j = choose.i, choose.j
board[i][j] = t
if choose.s[1] == score_type.five then
return i,j,true
end
return i,j
end
-- 初始化棋盘
function init_board()
for i=1, size do
board[i] = {}
for j=1, size do
board[i][j] = 0
end
end
end
function print_board()
for i=1, size do
str = ""
for j=1, size do
local t = board[i][j]
if t == 0 then
str =str.." . "
elseif t == 1 then
str = str.. " □ "
elseif t == -1 then
str = str.." ○ "
end
end
print(str.."\n")
end
end
function play()
t = 1
step = 1
while true do
print(step.."-----------------------------------")
-- 类型为t的 下棋
i,j, over = drop(board, t)
if i== 0 then
print("棋盘已满")
break
end
-- 打印棋牌状况
print_board()
if over then
break
end
-- 换人
t = -t
-- 步数+1
step = step+1
end
end
init_board()
play()