之前用HTML5,css3,JavaScript写了一个网页版的五子棋。这几天接触了Node.js将之前的单机五子棋改成能够联机对战的五子棋。
github地址
进入主页首先需要选择房间,快速开始可以加入别人创建好的房间,组队对战可以创建房间邀请别人加入自己的房间。
整个项目由两个HTML页面构成:
index.html 项目首页提供可选房间
chat.html 游戏界面
index.html
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>title>
<link href="css/bootstrap.min.css" rel="stylesheet">
<style type="text/css">
[v-cloak]{
display: none;
}
style>
head>
<body>
<div id="app">
<form class="form-horizontal" method="get" action="add" name="dem" id="form">
<div class="form-group">
<label for="text" class="col-sm-2 control-label">选择房间:label>
<div class="col-sm-2">
<select class="form-control" id="room" name="room">
<option v-for="(u,i) in wait" v-cloak>{{u}}option>
select>
div>
<button type="submit" class="btn btn-primary">快速开始button>
div>
form>
<form class="form-horizontal" method="get" action="add2" name="demo" id="for">
<div class="form-group">
<label for="text" class="col-sm-2 control-label">组队对战:label>
<div class="col-sm-2">
<select class="form-control" id="ro" name="ro">
<option v-for="(u,i) in Null" v-cloak>{{u}}option>
select>
div>
<button type="submit" class="btn btn-primary">组队对战button>
div>
form>
div>
<script src="js/jquery-1.11.3.min.js">script>
<script src="js/bootstrap.min.js">script>
<script type="text/javascript" src="/vue.js">script>
<script type="text/javascript">
var wait;
var Null;
var full;
$.get("/check", function (result) {
wait = result.wait;
Null = result.Null;
full = result.full;
var app = new Vue({
el: '#app',
data: {
"wait": wait,
"Null": Null
}
});
});
script>
body>
html>
这里采用了BootStrap模板,采用ajax方法访问/check网址,得到房间数据,通过Vue.js实现数据绑定
chat.html
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>五子棋title>
<style type="text/css">
[v-cloak]{
display: none;
}
#guize {
display: block;
margin-top: -450px;
margin-right: 50px;
margin-bottom: 50px;
margin-left: 480px;
}
#myCanvas {
border-radius: 10px;
display: block;
border: 1px solid #c3c3c3;
margin: 100px auto 0;
padding: 0px;
background-color:rgba(153, 167, 255, 0.49);
box-shadow: 10px 10px 10px #000;
cursor: pointer;
}
#app {animation:change 15s linear 0s infinite;}
@keyframes change{0%{color:#333;}10%{color:#ff00e9;}20%{color:#ff00de;}30%{color:#00ff80;}40%{color:#7000ff;}50%{color:#f60;}60%{color:#1d00ff;}70%{color:#02ff00;}80%{color:#cf0;}90%{color:#ffbe00;}100% {color:#333;}
}
style>
<link href="css/bootstrap.min.css" rel="stylesheet">
head>
<body style="background-color:#fcf8e3">
<div id="app" v-cloak>{{name}}
<h2> {{msg}}h2>
div>
<canvas id="myCanvas" width="500" height="500">
canvas>
<button type="button" class="btn btn-success" name="bt3" id="bt3">离开房间button>
body>
<script src="js/jquery-1.11.3.min.js">script>
<script src="js/bootstrap.min.js">script>
<script type="text/javascript" src="/socket.io/socket.io.js">script>
<script type="text/javascript" src="js/jquery-1.11.3.min.js">script>
<script type="text/javascript" src="/vue.js">script>
<script type="text/javascript">
var app = new Vue({
el: '#app',
data: {
name: "",
msg: '等待对方进入房间'
}
})
var ren, room, usr, roomnum;
var bt3 = document.getElementById("bt3");
bt3.onclick = function () {
$.get("/logout", function (result) {
})
window.location.href = "/index.html";
}
$.get("/usr", function (result) {
room = result.room;
usr = result.usrname;
// alert(usr);
roomnum = parseInt(result.roomnum);
socket.emit("num", {
"roomnum": roomnum
});
socket.on("num", function (msg) {
if (msg.roomnum == 2) {
app.msg = "游戏开始";
}
});
});
var c = document.getElementById("myCanvas");
var b = document.getElementById("AI");
var cxt = c.getContext("2d");
var e = e || window.event;
var socket = io();
var X;
var Y;
for (var i = 25; i <= 500; i += 25) {
cxt.moveTo(i, 0);
cxt.lineTo(i, 500);
cxt.moveTo(0, i);
cxt.lineTo(500, i);
cxt.stroke();
}
var a = new Array(); //先声明一维
for (var p = 0; p < 20; p++) { //一维长度为2
a[p] = new Array(); //再声明二维
for (var q = 0; q < 20; q++) { //二维长度为3
a[p][q] = 0;
}
}
var temp = 1;
var self = true;
c.onmousedown = function (e) {
X = e.offsetX;
Y = e.offsetY;
X = Math.round(X / 25);//坐标取整
Y = Math.round(Y / 25);
if (X == 0 || Y == 0 || X == 20 || Y == 20) {
alert("请在棋盘范围内落子");
}else if(self){
socket.emit("hua", {
"x": X,
"y": Y,
"room": room,
"usr": usr,
});
}
}
socket.on("huida", function (msg) {
X = msg.x;
Y = msg.y;
rn = msg.roomnum;
//alert(msg.usr)
if (rn == 2) {
if (msg.usr == usr ) {
self = false;
} if(msg.usr != usr ) {
self = true;
}
}
if (a[X][Y] == 0 && rn == 2 && msg.room == room) {
//alert(usr);
cxt.beginPath();
cxt.arc(X * 25, Y * 25, 11, 0, Math.PI * 2, true);
cxt.shadowOffsetX = 2; // 阴影Y轴偏移
cxt.shadowOffsetY = 2; // 阴影X轴偏移
cxt.shadowBlur = 3; // 模糊尺寸
cxt.shadowColor = 'rgba(0, 0, 0, 0.7)'; // 颜色
cxt.closePath();
if (temp % 2 == 0) {
cxt.fillStyle = "#FFFFFF";
a[X][Y] = -1;
app.msg = "白棋落在" + X + " " + Y;
}
if (temp % 2 == 1) {
cxt.fillStyle = "#000000";
a[X][Y] = 1;
app.msg = "黑棋落在" + X + " " + Y;
}
temp++;
cxt.fill();
}
for (var m = 0; m < 20; m++)
for (var n = 0; n < 16; n++) {
if (a[m][n] == 1 && a[m][n + 1] == 1 && a[m][n + 2] == 1 && a[m][n + 3] == 1 && a[m][n + 4] == 1) {
con = confirm("黒棋获胜,是否重新开始");
if (con)
window.location.href = "/chat";
temp = 1;
} else if (a[m][n] == -1 && a[m][n + 1] == -1 && a[m][n + 2] == -1 && a[m][n + 3] == -1 && a[m][n + 4] == -1) {
con = confirm("白棋获胜,是否重新开始");
if (con)
window.location.href = "/chat";
temp = 1;
}
}
for (var m = 0; m < 16; m++)
for (var n = 0; n < 20; n++) {
if (a[m][n] == 1 && a[m + 1][n] == 1 && a[m + 2][n] == 1 && a[m + 3][n] == 1 && a[m + 4][n] == 1) {
con = confirm("黒棋获胜,是否重新开始");
if (con)
window.location.href = "/chat";
temp = 1;
} else if (a[m][n] == -1 && a[m + 1][n] == -1 && a[m + 2][n] == -1 && a[m + 3][n] == -1 && a[m + 4][n] == -1) {
con = confirm("白棋获胜,是否重新开始");
if (con)
window.location.href = "/chat";
temp = 1;
}
}
for (var m = 0; m < 16; m++)
for (var n = 0; n < 16; n++) {
if (a[m][n] == 1 && a[m + 1][n + 1] == 1 && a[m + 2][n + 2] == 1 && a[m + 3][n + 3] == 1 && a[m + 4][n + 4] == 1) {
con = confirm("黒棋获胜,是否重新开始");
if (con)
window.location.href = "/chat";
temp = 1;
} else if (a[m][n] == -1 && a[m + 1][n + 1] == -1 && a[m + 2][n + 2] == -1 && a[m + 3][n + 3] == -1 && a[m + 4][n + 4] == -1) {
con = confirm("白棋获胜,是否重新开始");
if (con)
window.location.href = "/chat";
temp = 1;
}
}
for (var m = 0; m < 16; m++)
for (var n = 20; n > 4; n--) {
if (a[m][n] == 1 && a[m + 1][n - 1] == 1 && a[m + 2][n - 2] == 1 && a[m + 3][n - 3] == 1 && a[m + 4][n - 4] == 1) {
con = confirm("黒棋获胜,是否重新开始");
if (con)
window.location.href = "chat";
temp = 1;
}
if (a[m][n] == -1 && a[m + 1][n - 1] == -1 && a[m + 2][n - 2] == -1 && a[m + 3][n - 3] == -1 && a[m + 4][n - 4] == -1) {
con = confirm("白棋获胜,是否重新开始");
if (con)
window.location.href = "chat";
temp = 1;
}
}
});
script>
html>
五子棋具体的实现原理我就不介绍了,整个项目的核心在于
通过emit()方法将用户鼠标点击的坐标房间号以及服务器给每个用户分配的用户名发送给服务器
socket.emit("hua", {
"x": X,
"y": Y,
"room": room,
"usr": usr,
});
然后浏览器通过on()方法接受服务器返回的坐标,房间号,用户名
socket.on("huida", function (msg) {
X = msg.x;
Y = msg.y;
rn = msg.roomnum;
将五子棋的绘制棋子操作放在on()方法中,只有当房间人数为两人,且返回的房间号与浏览器的房间号一致是才绘制棋子,并在页面左上角提醒落子位置
if (a[X][Y] == 0 && rn == 2 && msg.room == room) {
//alert(usr);
cxt.beginPath();
cxt.arc(X * 25, Y * 25, 11, 0, Math.PI * 2, true);
cxt.shadowOffsetX = 2; // 阴影Y轴偏移
cxt.shadowOffsetY = 2; // 阴影X轴偏移
cxt.shadowBlur = 3; // 模糊尺寸
cxt.shadowColor = 'rgba(0, 0, 0, 0.7)'; // 颜色
cxt.closePath();
if (temp % 2 == 0) {
cxt.fillStyle = "#FFFFFF";
a[X][Y] = -1;
app.msg = "白棋落在" + X + " " + Y;
}
if (temp % 2 == 1) {
cxt.fillStyle = "#000000";
a[X][Y] = 1;
app.msg = "黑棋落在" + X + " " + Y;
}
temp++;
cxt.fill();
}
另外加入self变量判断是否轮到自己落子,当服务器返回的usr与浏览器的ust相同时说明自己已经落子self变成false反之变为true,当self为true是才能向服务器发送数据,这样就解决了交替落子的问题
if (rn == 2) {
if (msg.usr == usr ) {
self = false;
} if(msg.usr != usr ) {
self = true;
}
}
服务器主要代码就是app.js负责整个项目的数据处理与页面呈递
app.js
var express = require("express"); //引用express模块
var app = express();
var server = require("http").Server(app);
var io = require("socket.io")(server);
var path = require("path");
var session = require('express-session');
app.use(session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: true
}));
//模板引擎
app.set("view engine", "ejs");
//静态服务
app.use(express.static("./public"));
var alluser = [];
var Room = [];
var usr = 0;
var socketid = new Array(1000);
var roomnum = new Array(1000);
for (var i = 0; i < 1000; i++) {
roomnum[i] = 0;
Room[i] = i;
}
//主界面
app.get("/", function (req, res, next) {
res.redirect("/index");
});
//确认登陆,检查此人是否有用户名,并且昵称不能重复
app.get("/add", function (req, res, next) {
var room = parseInt(req.query.room);
//var usrname = req.query.usr;
if (!room) {
res.send("必须填写房间");
return;
}
if (room < 0 || room > 10000) {
res.send("房间号应在0~1000之间");
return;
}
if (Room.indexOf(room) == -1) {
res.send("房间不存在");
return;
}
if (roomnum[room] > 1) {
res.send("房间已满");
return;
}
// if(alluser.indexOf(usrname) !=-1 ){
// res.send("用户名重复");
// return;
// }
Room.push(room);
//alluser.push(usrname);
roomnum[room]++;
//付给session
req.session.room = room;
req.session.usrname = usr;
usr = usr + 1;
res.redirect("/chat");
});
app.get("/add2", function (req, res, next) {
var room = parseInt(req.query.ro);
if (!room) {
res.send("必须填写房间");
return;
}
if (room < 0 || room > 10000) {
res.send("房间号应在0~1000之间");
return;
}
if (Room.indexOf(room) == -1) {
res.send("房间不存在");
return;
}
if (roomnum[room] > 1) {
res.send("房间已满");
return;
}
Room.push(room);
//alluser.push(usrname);
roomnum[room]++;
//付给session
req.session.room = room;
req.session.usrname = usr;
usr = usr + 1;
res.redirect("/chat");
});
app.get("/chat", function (req, res, next) {
//这个页面必须保证有用户名了,
if (!req.session.room) {
res.redirect("/");
return;
}
res.redirect("/chat.html");
});
app.get("/usr", function (req, res, next) {
res.json({
"room": req.session.room,
"usrname": req.session.usrname,
"roomnum": roomnum[req.session.room]
});
});
app.get("/logout", function (req, res, next) {
var Ro = parseInt(req.session.room);
roomnum[Ro]--;
// var num = alluser.indexOf(req.session.usrname);
// alluser[num] = "";
});
app.get("/check", function (req, res, next) {
var wait = [];
var Null = [];
for (var m = 0; m < roomnum.length; m++) {
if (roomnum[m] == 0) {
Null.push(m);
} else if (roomnum[m] == 1) {
wait.push(m);
}
}
res.json({
"wait": wait,
"Null": Null
});
});
server.listen("3000", function () {
console.log("正在监听端口 3000");
});
var io = require('socket.io')(server);
//监听连接事件
io.on("connection", function (socket) {
socket.on("hua", function (msg) {
msg.roomnum = roomnum[msg.room];
io.emit("huida", msg);
console.log(msg);
});
socket.on("num", function (msg) {
io.emit("num", msg);
});
});
用到的第三方模块有express,socket.io,express-session,当用户进去房间是获取到room的值以及服务器生成的usr保存到session中,再将对应的值以json的格式返回到浏览器,roomnum记录所有房间的人数
if (!room) {
res.send("必须填写房间");
return;
}
if (room < 0 || room > 10000) {
res.send("房间号应在0~1000之间");
return;
}
if (Room.indexOf(room) == -1) {
res.send("房间不存在");
return;
}
if (roomnum[room] > 1) {
res.send("房间已满");
return;
}
每个页面都需要检查是否选择了房间,以及房间范围,避免直接输入网址进入游戏界面。