利用HTML5+CSS3+JavaScript实现五子棋人机对弈
HTML5代码
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>五子棋</title>
<link rel="stylesheet" href="./css/chess.css" />
</head>
<body>
<p id="result_wrap">**********人机大战五子棋**********</p>
<canvas id="chess" width="450px" height="450px"></canvas>
<div id="demo">坐标:</div>
<div class="btn_wrap">
<div id="restart" class="restart">
<span>重新开始</span>
</div>
<div id="goback" class="goback unable">
<span>悔棋一次</span>
</div>
<div id="return" class="return unable">
<span>撤销悔棋</span>
</div>
</div>
</body>
<script src="./js/chess.js"></script>
</html>
CSS3代码
body {
background: url('./images/背景图.jpg');
background-size: cover;
background-repeat: no-repeat;
width: 100%;
height: 100%;
}
canvas {
display: block; /*让棋盘块状显示*/
margin: 50px auto; /*让棋盘左右居中*/
box-shadow: -2px -2px 2px #efefef, 3px 3px 3px #ffdaaf; /*添加棋盘阴影,使棋盘看起来更有立体感*/
cursor: pointer; /*改变鼠标光标,增强用户的下棋落点准确性 */
background: url('./images/棋盘.jpg');
background-size: contain;
}
#demo {
text-align: center;
margin-top: -30px;
margin-bottom: 10px;
color: #ffffff;
}
.btn_wrap {
display: flex;
justify-content: center; /*水平居中*/
}
.btn_wrap div {
margin: 0 10px;
}
div > span {
display: inline-block;
padding: 10px 20px;
color: #fff;
background-color: #eeb741;
border-radius: 5px;
cursor: pointer;
}
div.unable span {
background: #d6d6d4;
color: #adacaa;
}
#result_wrap {
text-align: center;
font-weight: bold;
font-size: 28px;
line-height: 28px;
color: #000000;
}
JavaScript代码
// 获取DOM对象
var chess = document.getElementById('chess')
var context = chess.getContext('2d')
var demo = document.getElementById('demo')
var result_wrap = document.getElementById('result_wrap')
var restart = document.getElementById('restart')
var goback = document.getElementById('goback')
var returnbtn = document.getElementById('return')
// 定义全局变量
var over = false // 对局结束的变量
var me = true // 我下棋回合的变量
var _nowi = 0, // 记录自己下棋的x坐标
_nowj = 0 // 记录自己下棋的y坐标
var _compi = 0, // 记录计算机当前下棋的x坐标
_compj = 0 // 记录计算机当前下棋的y坐标
var backAble = false, // 悔棋情况
returnAble = false // 撤销悔棋情况
var chressBord = [] //棋盘数组落子记录
var _myWin = [], // 记录我赢的情况
_compWin = [] // 记录计算机赢的情况
var myWin = [] // 自己赢法的统计数组
var computerWin = [] // 计算机赢法的统计数组
var count = 0 // 赢法总数
var wins = [] // 赢法数组
// 赢法数组初始化
for (var i = 0; i < 15; i++) {
wins[i] = []
for (var j = 0; j < 15; j++) {
wins[i][j] = []
}
}
// 边框颜色
context.strokeStyle = '#bfbfbf'
// 绘制棋盘
var drawChessBoard = function () {
for (var i = 0; i < 15; i++) {
context.moveTo(15 + i * 30, 15)
context.lineTo(15 + i * 30, 435)
context.stroke()
context.moveTo(15, 15 + i * 30)
context.lineTo(435, 15 + i * 30)
context.stroke()
}
}
// 调用绘制棋盘函数
window.onload = function () {
drawChessBoard()
}
// 初始化棋盘
for (var i = 0; i < 15; i++) {
chressBord[i] = []
for (var j = 0; j < 15; j++) {
chressBord[i][j] = 0
}
}
// 显示14*14的棋盘的坐标,从0,0开始
chess.onmousemove = function (e) {
var x = Math.floor((e.offsetX - 24) / 32 + 0.5),
y = Math.floor((e.offsetY - 24) / 32 + 0.5)
if (x >= 0 && y >= 0) {
demo.innerHTML = '坐标:' + x + ',' + y
}
}
chess.onmouseleave = function () {
demo.innerHTML = '坐标:'
}
// 绘制棋子
var oneStep = function (i, j, me) {
context.beginPath()
context.arc(15 + i * 30, 15 + j * 30, 13, 0, 2 * Math.PI) // 画圆
context.closePath()
// 棋子颜色渐变
var gradient = context.createRadialGradient(
// createRadialGradient()方法创建放射状/圆形渐变对象
15 + i * 30 + 2, // 渐变的开始圆的 x 坐标
15 + j * 30 - 2, // 渐变的开始圆的 y 坐标
13, // 开始圆的半径
15 + i * 30 + 2, // 渐变的结束圆的 x 坐标
15 + j * 30 - 2, // 渐变的结束圆的 y 坐标
0, // 结束圆的半径
)
if (me) {
gradient.addColorStop(0, '#0a0a0a') // 我下的黑棋渐变开始的颜色
gradient.addColorStop(1, '#636766') // 我下的黑棋渐变结束的颜色
} else {
gradient.addColorStop(0, '#6495ED') // 计算机下的白棋渐变开始的颜色
gradient.addColorStop(1, '#f9f9f9') // 计算机下的白棋渐变结束的颜色
}
context.fillStyle = gradient // 选取渐变颜色变量
context.fill() // 填充棋子
}
// 重新开始
restart.onclick = function () {
window.location.reload()
}
// 悔棋-销毁棋子
var minusStep = function (i, j) {
context.clearRect(i * 30, j * 30, 30, 30) // 擦除棋子的圆形
// 重画该棋子的圆行周围的十字格子边线
context.beginPath()
context.moveTo(15 + i * 30, j * 30)
context.lineTo(15 + i * 30, j * 30 + 30)
context.moveTo(i * 30, j * 30 + 15)
context.lineTo((i + 1) * 30, j * 30 + 15)
context.strokeStyle = '#bfbfbf'
context.stroke()
context.stroke()
context.stroke() // 重新绘制十字格子边线
}
// 横线赢法,同一x轴,不同y轴有5子,第二维数组++
for (var i = 0; i < 15; i++) {
for (var j = 0; j < 11; j++) {
for (var k = 0; k < 5; k++) {
wins[i][j + k][count] = true
}
count++
}
}
// 竖线赢法,同一y轴,不同x轴有5子,第一维数组++
for (var i = 0; i < 15; i++) {
for (var j = 0; j < 11; j++) {
for (var k = 0; k < 5; k++) {
wins[j + k][i][count] = true
}
count++
}
}
// 正斜线"\"赢法,第一维数组++,第二维数组++
for (var i = 0; i < 11; i++) {
for (var j = 0; j < 11; j++) {
for (var k = 0; k < 5; k++) {
wins[i + k][j + k][count] = true
}
count++
}
}
// 反斜线"/"赢法,第一维数组++,第二维数组--
for (var i = 0; i < 11; i++) {
for (var j = 14; j > 3; j--) {
for (var k = 0; k < 5; k++) {
wins[i + k][j - k][count] = true
}
count++
}
}
// 赢的情况和赢法统计数组初始化,实现回合制
for (var i = 0; i < count; i++) {
myWin[i] = 0
_myWin[i] = 0
computerWin[i] = 0
_compWin[i] = 0
}
// 我,下棋
chess.onclick = function (e) {
// 对局结束我禁止下棋
if (over) {
return
}
// 计算机下棋回合我禁止下棋
if (!me) {
return
}
var x = e.offsetX // 鼠标坐标到元素的左侧的距离,作为x轴
var y = e.offsetY // 鼠标坐标到元素的顶部的距离,作为y轴
// 记录自己下棋的坐标
var i = Math.floor(x / 30)
var j = Math.floor(y / 30)
_nowi = i // 记录自己下棋的x坐标
_nowj = j // 记录自己下棋的y坐标
// 判断棋盘是否可下棋,可下棋且我已下棋则赋值为1
if (chressBord[i][j] == 0) {
oneStep(i, j, me) // 调用绘制棋子函数
chressBord[i][j] = 1 // 我,已占位置
for (var k = 0; k < count; k++) {
// 将可能赢的情况都加1
if (wins[i][j][k]) {
myWin[k]++
_compWin[k] = computerWin[k]
computerWin[k] = 6 // 这个位置计算机不可能赢了
if (myWin[k] == 5) {
result_wrap.innerHTML = '********** 你赢了 **********'
over = true // 对局结束
}
}
}
// 计算机下棋回合
if (!over) {
me = !me
computerAI() // 调用计算机下棋函数
}
}
// 悔棋功能可用
goback.className = goback.className.replace(
new RegExp('(\\s|^)unable(\\s|$)'),
' ',
) // 选取含有unable首尾有空白符或者什么都没有的的字符串,^是取非,\\s匹配空白字符,$界定结束符,()子表达式被视为一个独立元素,|表示或
}
// 悔棋
goback.onclick = function (e) {
// 悔棋功能为假时不能悔棋
if (!backAble) {
return
}
over = false
me = true
// 我,悔棋
chressBord[_nowi][_nowj] = 0 // 我,已占位置 还原
minusStep(_nowi, _nowj) // 调用悔棋-销毁棋子函数
for (var k = 0; k < count; k++) {
// 将可能赢的情况都减1
if (wins[_nowi][_nowj][k]) {
myWin[k]--
computerWin[k] = _compWin[k] // 这个位置计算机可能赢
}
}
// 计算机相应被动悔棋
chressBord[_compi][_compj] = 0 // 计算机,已占位置 还原
minusStep(_compi, _compj) // 调用悔棋-销毁棋子函数
for (var k = 0; k < count; k++) {
// 将可能赢的情况都减1
if (wins[_compi][_compj][k]) {
computerWin[k]--
myWin[k] = _myWin[i] // 这个位置我可能赢
}
}
result_wrap.innerHTML = '**********人机大战五子棋**********'
returnAble = true
backAble = false
// 撤销悔棋功能可用
returnbtn.className = returnbtn.className.replace(
new RegExp('(\\s|^)unable(\\s|$)'),
' ',
) // 选取含有unable首尾有空白符或者什么都没有的的字符串,^是取非,\\s匹配空白字符,$界定结束符,()子表达式被视为一个独立元素,|表示或
}
// 撤销悔棋
returnbtn.onclick = function (e) {
// 撤销悔棋功能为假时不能撤销悔棋
if (!returnAble) {
return
}
// 我,撤销悔棋
chressBord[_nowi][_nowj] = 1 // 我,已占位置
oneStep(_nowi, _nowj, me)
for (var k = 0; k < count; k++) {
if (wins[_nowi][_nowj][k]) {
myWin[k]++
_compWin[k] = computerWin[k]
computerWin[k] = 6 // 这个位置计算机不可能赢了
}
if (myWin[k] == 5) {
result_wrap.innerHTML = '********** 你赢了 **********'
over = true // 对局结束
}
}
// 计算机相应被动撤销悔棋
chressBord[_compi][_compj] = 2 // 计算机,已占位置
oneStep(_compi, _compj, false)
for (var k = 0; k < count; k++) {
// 将可能赢的情况都减1
if (wins[_compi][_compj][k]) {
computerWin[k]++
_myWin[k] = myWin[k]
myWin[k] = 6 // 这个位置我不可能赢了
}
if (computerWin[k] == 5) {
result_wrap.innerHTML = '********** 电脑获胜 **********'
over = true // 对局结束
}
}
returnbtn.className += ' ' + 'unable'
returnAble = false
backAble = true
}
// 计算机下棋
var computerAI = function () {
var myScore = [] // 我的分数权值
var computerScore = [] // 计算机分数权值
var max = 0 // 分数权值比较的中间量
var u = 0, // 计算机下棋的x坐标记录
v = 0 // 计算机下棋的x坐标记录
// 初始化数组
for (var i = 0; i < 15; i++) {
myScore[i] = []
computerScore[i] = []
for (var j = 0; j < 15; j++) {
myScore[i][j] = 0
computerScore[i][j] = 0
}
}
// 计算机下棋坐标判断算法,通过分数权值的比较得出计算机的下棋落点坐标
for (var i = 0; i < 15; i++) {
for (var j = 0; j < 15; j++) {
// 判断棋盘是否可下棋
if (chressBord[i][j] == 0) {
// 在实现四种赢法的过程中,实现循环
for (var k = 0; k < count; k++) {
// 判断我的赢法是否为四种赢法中的一种
if (wins[i][j][k]) {
// 判断我的赢法为四种赢法其中一种的第几步,则加上相对应的分数权值
if (myWin[k] == 1) {
myScore[i][j] += 200
} else if (myWin[k] == 2) {
myScore[i][j] += 400
} else if (myWin[k] == 3) {
myScore[i][j] += 2000
} else if (myWin[k] == 4) {
myScore[i][j] += 10000
}
// 判断计算机的赢法为四种赢法其中一种的第几步,则加上相对应的分数权值
if (computerWin[k] == 1) {
computerScore[i][j] += 220
} else if (computerWin[k] == 2) {
computerScore[i][j] += 420
} else if (computerWin[k] == 3) {
computerScore[i][j] += 2100
} else if (computerWin[k] == 4) {
computerScore[i][j] += 20000
}
}
}
// 判断我的分数权值最大的下棋落点,若大于max则赋值给max
if (myScore[i][j] > max) {
max = myScore[i][j]
u = i
v = j
}
// 判断我的分数权值最大的下棋落点,是否等于max
else if (myScore[i][j] == max) {
// 判断计算机的分数权值最大的下棋落点是否比我的分数权值最大的下棋落点要大
if (computerScore[i][j] > computerScore[u][v]) {
u = i
v = j
}
}
// 判断计算机的分数权值最大的下棋落点,若大于max则赋值给max
if (computerScore[i][j] > max) {
max = computerScore[i][j]
u = i
v = j
}
// 判断计算机的分数权值最大的下棋落点,是否等于max
else if (computerScore[i][j] == max) {
// 判断我的分数权值最大的下棋落点是否比计算机的分数权值最大的下棋落点要大
if (myScore[i][j] > myScore[u][v]) {
u = i
v = j
}
}
}
}
}
_compi = u // 计算机下棋的x坐标
_compj = v // 计算机下棋的y坐标
oneStep(u, v, false) // // 调用绘制棋子函数,计算机下棋
chressBord[u][v] = 2 // 计算机占据位置,赋值为2
// 在实现四种赢法的过程中,实现循环
for (var k = 0; k < count; k++) {
if (wins[u][v][k]) {
computerWin[k]++
_myWin[k] = myWin[k]
myWin[k] = 6 // 这个位置我不可能赢了
if (computerWin[k] == 5) {
result_wrap.innerHTML = '********** 电脑获胜 **********'
over = true // 对局结束
}
}
}
// 若对局还没结束,将轮到我的回合
if (!over) {
me = !me
}
backAble = true
returnAble = false
// 定义局部变量hasClass匹配unable类名
var hasClass = new RegExp('unable').test(' ' + returnbtn.className + ' ') // test()方法用于检索字符串
// 若没有检索到unable,则撤销悔棋变为不可用
if (!hasClass) {
returnbtn.className += ' ' + 'unable'
}
}