局域网双人五子棋在线对战
前端代码:
<div id="bigBackground">
<!-- 棋盘水平线背景板 -->
<div id="background1"></div>
</div>
<div id="bigBackground">
<!-- 棋盘竖直线背景板 -->
<div id="background2"></div>
</div>
<div id="bigBackground">
<!-- 棋子背景板 -->
<div id="black">
<div id="blackContent">
<div id="blackContentLog"></div>黑方
</div>
<div id="blackMessage"></div>
</div>
<div id="background3"></div>
<div id="blue">
<div id="blueContent">
<div id="blueContentLog"></div>白方
</div>
<div id="blueMessage"></div>
</div>
</div>
<script>
var connectId = '39bd662b7942418595c21a1ef0af7fad';
getSseConnetcion(connectId);
const httpRequest = new XMLHttpRequest();
//游戏是否开始
var started = false;
var user = 'user1';
//游戏是否结束
var isOver = false;
const message = '请落子<div>!</div>';
const messageWin = '恭喜<div>,</div>你赢了<div>!</div>';
document.getElementById('blackMessage').innerHTML = message;
//上次出棋方
var lastMover = null; // 0代表黑方,1代表蓝方
var background1 = document.getElementById('background1');
var content = '';
//绘制棋盘竖直线
for (i = 0; i < 32; i++) {
content += '<div class="horizon"></div>';
}
background1.innerHTML = content;
var background2 = document.getElementById('background2');
content = '';
//绘制棋盘竖直线
for (i = 0; i < 32; i++) {
content += '<div class="vertical"></div>';
}
background2.innerHTML = content;
var background3 = document.getElementById('background3');
content = '';
//绘制棋盘竖直线
for (i = 0; i < 1024; i++) {
content += '<div id="' + Math.ceil((i+1)/32) + ',' + ((i+1)%32==0?32:(i+1)%32) + '" class="cell" onClick="moves()"></div>';
}
background3.innerHTML = content;
//绑定落子事件
for (i = 0; i < 1024; i++) {
var x = Math.ceil((i+1)/32);
var y = ((i+1)%32==0?32:(i+1)%32);
let id = x + ',' + y;
let cell = document.getElementById(id);
cell.onclick = function(){
moves(id);
nextStep(user, id, connectId);
}
}
//落子事件
function moves (id) {
if (isOver) {
return;
}
let cell = document.getElementById(id);
if (!started) {
cell.style.cssText='background-color: black;'
started = true;
lastMover = 0;
document.getElementById('blackMessage').innerHTML = '';
document.getElementById('blueMessage').innerHTML = message;
return;
}
if (lastMover == 0) {
document.getElementById('blackMessage').innerHTML = message;
document.getElementById('blueMessage').innerHTML = '';
} else if (lastMover == 1) {
document.getElementById('blackMessage').innerHTML = '';
document.getElementById('blueMessage').innerHTML = message;
}
if (cell.style.cssText != "") {
return;
}
if (lastMover == 0) {
cell.style.cssText='background-color: white;'
lastMover = 1;
} else {
cell.style.cssText='background-color: black;'
lastMover = 0;
}
var coordinate = id.split(',');
checkWin(coordinate[0], coordinate[1], cell.style.cssText);
}
//判定输赢
function checkWin (x, y, color) {
x = parseInt(x);
y = parseInt(y);
//判断水平线(-)上是否形成五子
let nearCellIds = [];
let leftNear = y-4;
if (leftNear <= 0) {
leftNear = 1;
}
let rightNear = y+5;
if (rightNear > 33) {
rightNear = 33;
}
for (i = leftNear; i < rightNear; i++) {
nearCellIds.push(x + ',' + i);
}
if (checkLineFiveCell(nearCellIds, color)) {
return;
};
//判断竖直线(|)上是否形成五子
nearCellIds = [];
let topNear = x-4;
if (topNear <= 0) {
topNear = 1;
}
let bottomNear= x+5
if (bottomNear > 33) {
bottomNear = 33
}
for (i = topNear; i < bottomNear; i++) {
nearCellIds.push(i + ',' + y);
}
if (checkLineFiveCell(nearCellIds, color)) {
return;
};
//判断右斜线(\)上是否形成五子
nearCellIds = [];
for (i = topNear; i < bottomNear; i++) {
if (y-(x-i) <= 0) {
continue;
}
if (y-(x-i) > 32) {
break;
}
nearCellIds.push(i + ',' + (y-(x-i)));
}
if (checkLineFiveCell(nearCellIds, color)) {
return;
};
//判断左斜线(/)上是否形成五子
nearCellIds = [];
for (i = topNear; i < bottomNear; i++) {
if (y+(x-i) <= 0) {
break;
}
if (y+(x-i) > 32) {
continue;
}
nearCellIds.push(i + ',' + (y+(x-i)));
}
checkLineFiveCell(nearCellIds, color);
}
//判断直线上是否形成连续同色五子
function checkLineFiveCell (lineCellIds, color) {
let seriesColorIds = [];
for (let id of lineCellIds) {
let nearColor = document.getElementById(id).style.cssText;
if (nearColor == color) {
seriesColorIds.push(id);
if (seriesColorIds.length == 5) {
changLineFiveCellToWinColor(seriesColorIds);
let cell = null;
if ('background-color: black;' == color) {
cell = document.getElementById('blackMessage');
document.getElementById('blackContent').style.cssText='color: gold;';
document.getElementById('blackContentLog').style.cssText='background-color: gold;';
document.getElementById('blueMessage').innerHTML = '';
} else {
cell = document.getElementById('blueMessage');
document.getElementById('blueContent').style.cssText='color: gold;';
document.getElementById('blueContentLog').style.cssText='background-color: gold;';
document.getElementById('blackMessage').innerHTML = '';
}
cell.innerHTML = messageWin;
cell.style.cssText='color: gold;';
isOver = true;
return true;
}
} else {
seriesColorIds = [];
}
}
return false;
}
//改变连续同色五子为胜利色
function changLineFiveCellToWinColor (lineCellIds) {
for (let id of lineCellIds) {
let cell = document.getElementById(id);
cell.style.cssText='background-color: gold;'
}
}
//console.log(cell.getAttribute('id'));
function getSseConnetcion(connectId) {
if (!!window.EventSource) {
// 建立连接
source = new EventSource('http://localhost:8081/xxl-job-executor/sse/createSseConnect?connectId=' + connectId);
/**
* 连接一旦建立,就会触发open事件
* 另一种写法:source.onopen = function (event) {}
*/
source.addEventListener('open', function (e) {
setMessageInnerHTML("建立连接...");
}, false);
/**
* 客户端收到服务器发来的数据
* 另一种写法:source.onmessage = function (event) {}
*/
source.addEventListener('message', function (e) {
setMessageInnerHTML(e.data);
moves(e.data);
});
/**
* 如果发生通信错误(比如连接中断),就会触发error事件
* 或者:
* 另一种写法:source.onerror = function (event) {}
*/
source.addEventListener('error', function (e) {
if (e.readyState === EventSource.CLOSED) {
setMessageInnerHTML("连接关闭");
} else {
console.log(e);
}
}, false);
} else {
setMessageInnerHTML("你的浏览器不支持SSE");
}
}
// 将消息显示在网页上
function setMessageInnerHTML(innerHTML) {
console.log(innerHTML);
}
function nextStep(user, xy, connectId) {
//source.close();
httpRequest.open('POST', 'http://localhost:8081/xxl-job-executor/sse/nextStep', true);
httpRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
httpRequest.send('user=' + user + '&xyCoordinate=' + xy + '&connectId=' + connectId);
httpRequest.onreadystatechange = function () {
if (httpRequest.readyState === 4 && httpRequest.status === 200) { //固定写法
//数据获取成功,获取服务器响应的数据
console.log(httpRequest.responseText)
}
}
console.log("close");
}
</script>
<style>
body{
background-color: grey;
}
#bigBackground{
position: absolute;
width: 99%;
height: 694px;
}
#background1{
width: 692px;
height: 692px;
background-color: grey; //#f9f9fa;
margin: auto;
border: solid 3px;
}
.horizon{
width: 693px;
height: 20px;
border-bottom: solid 1px;
margin-left: -1px;
}
#background2{
width: 692px;
height: 692px;
margin: auto;
}
.vertical{
width: 20px;
height: 694px;
border-right: solid 1px;
display: inline-block;
margin-top: 1px;
}
#background3{
width: 672px;
height: 672px;
margin: auto;
margin-top: 10px;
}
.cell{
width: 20px;
height: 20px;
display: inline-block;
margin-left: 1px;
margin-top: 1px;
border-radius: 10px;
}
#black{
float: left;
height: 694px;
width: 400px;
background-color: #FFFFFF;
}
#blue{
float: right;
height: 694px;
width: 400px;
position: relative;
top: -681px;
background-color: black;
}
#blackContent{
margin-top: 10px;
width: 135px;
margin: auto;
height: 50px;
font-size: 40px;
}
#blackContentLog{
width: 40px;
height: 40px;
display: inline-block;
background-color: black;
padding-top: -8px;
position: relative;
top: 6px;
border-radius: 20px;
margin-right: 10px;
}
#blueContent{
margin-top: 10px;
width: 135px;
margin: auto;
height: 50px;
font-size: 40px;
color: #FFFFFF;
}
#blueContentLog{
width: 40px;
height: 40px;
display: inline-block;
background-color: #FFFFFF;
padding-top: -8px;
position: relative;
top: 6px;
border-radius: 20px;
margin-right: 10px;
}
#blackMessage, #blueMessage{
margin: auto;
display: block;
margin-top: 50px;
width: 25px;
font-size: 20px;
}
#blueMessage{
color: #FFFFFF;
}
</style>
后端代码:
package com.xxl.job.executor.mvc.service.impl;
import com.xxl.job.executor.mvc.dao.SseDao;
import com.xxl.job.executor.mvc.po.SseGomoku;
import com.xxl.job.executor.mvc.service.SseService;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author xuxueli 2019-05-04 22:13:264
*/
@Service
public class SseServiceImpl implements SseService {
private static Map<String, SseEmitter> sseCache = new ConcurrentHashMap<>();
@Resource
private SseDao sseDao;
@Override
public SseEmitter createSseConnect(String connectId) {
// 设置超时时间,0表示不过期。默认30秒,超过时间未完成会抛出异常:AsyncRequestTimeoutException
SseEmitter sseEmitter = new SseEmitter(0L);
sseEmitter.onCompletion(() -> {
sseCache.remove(connectId);
});
sseEmitter.onError(e -> {
sseCache.remove(connectId);
e.printStackTrace();
});
sseEmitter.onTimeout(() -> {
sseCache.remove(connectId);
});
sseCache.put(connectId, sseEmitter);
try {
sseEmitter.send(SseEmitter.event().id(connectId).data("test"));
} catch (Exception e) {
e.printStackTrace();
}
return sseEmitter;
}
@Override
public Map<String, Object> nextStep(Map<String, String> param) {
Map<String,Object> res = new HashMap<>();
res.put("data", "success");
String connectId = param.get("connectId");
synchronized (SseServiceImpl.class) {
String user = param.get("user");
SseGomoku sseGomoku = new SseGomoku();
sseGomoku.setAccount(user);
sseGomoku.setXyCoordinate(param.get("xyCoordinate"));
Integer steps = sseDao.getAllSteps();
int allSteps = Objects.isNull(steps) ? 0 : steps;
sseGomoku.setSteps(++allSteps);
sseDao.save(sseGomoku);
for (String key : sseCache.keySet()) {
if (!key.equals(connectId)) {
SseEmitter sseEmitter = sseCache.get(key);
try {
sseEmitter.send(param.get("xyCoordinate"));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
return res;
}
}
表结构:
CREATE TABLE `sse_gomoku` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`account` varchar(50) NULL COMMENT '账号',
`xy_coordinate` varchar(10) NULL COMMENT '落子xy坐标',
`steps` int(10) NULL COMMENT '总步数',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;