五子棋极简AI算法

分享一个五子棋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()

你可能感兴趣的:(游戏开发,算法,lua,游戏开发)