游戏开发|五子棋极简AI算法

分享一个五子棋AI算法,完整代码, 核心代码不到300行
主要思路:落子时, 遍历棋盘上所有空白位置,算出该点落子的得分,并保存下来, 最后算出最大得分的落子位置,出现多个相同得分时, 随机选择一个。
评分主要类型见代码中 socre_type枚举定义
AI强度大约为中等,和普通人对弈, 大约80%胜率。
【游戏开发|五子棋极简AI算法】带AI对弈的完整lua代码:

local sid = 0 local function id(initial) sid = initial and initial or sid + 1 return sid endscore_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) thenelse return score_type.zudang3 end end end endfor 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 endif not (lx and rx) then if (not lx) and (not rx) then return score_type.fazhan3 else return score_type.fazhan3x end end end ends=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) thenelse 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) thenelse return score_type.zudang2 end end end end-- 发展2return 0 end -- 左右各取最多四个坐标的值 local function get_line(ldx,ldy,rdx,rdy) local left = {} local right = {}local c = 4for 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 endfor k=1, c do local v = board[i+rdx*k] andboard[i+rdx*k][j+rdy*k] if v then table.insert(right, v) end endreturn 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 endfunction 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 endfunction 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 endfunction 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 endinit_board() play()

    推荐阅读