dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'io.netty:netty-all:4.1.36.Final'
implementation 'org.projectlombok:lombok:1.18.16'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
socket 服务
@Slf4j
@Component
public class NettyWebSocketApplicationRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) {
// 创建主线程池组,处理客户端的连接(多反应器)
NioEventLoopGroup mainGroup = new NioEventLoopGroup();
// 创建从线程池组,处理客户端的读写(多反应器)
NioEventLoopGroup subGroup = new NioEventLoopGroup();
try {
// 创建netty引导类,配置和串联系列组件(设置线程模型,设置通道类型,设置客户端处理器handler,设置绑定端口号)
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(mainGroup, subGroup);
bootstrap.channel(NioServerSocketChannel.class);
bootstrap.childHandler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel channel) throws Exception {
// 配置链式解码器
ChannelPipeline pipeline = channel.pipeline();
// 解码成HttpRequest
pipeline.addLast(new HttpServerCodec());
// 解码成FullHttpRequest POST
pipeline.addLast(new HttpObjectAggregator(1024 * 10));
// 添加WebSocket解编码
pipeline.addLast(new WebSocketServerProtocolHandler("/"));
// 添加处自定义的处理器
pipeline.addLast(new TextWebSocketFrameHandler());
}
});
// 异步绑定端口号,需要阻塞住直到端口号绑定成功
ChannelFuture channelFuture = bootstrap.bind(7397);
channelFuture.sync();
log.info("websocket服务端启动成功啦!");
} catch (InterruptedException e) {
log.info("{} websocket服务器启动异常", e.getMessage());
} finally {
// mainGroup.shutdownGracefully();
// subGroup.shutdownGracefully();
}
}
}
消息
/**
* 1 连接
* 0 连接失败
* 2 开始游戏 2表示白,21表示黑
* 3 推送游戏信息
* 4 游戏结束
*/
@Slf4j
public class TextWebSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
public static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
boolean start = false;
@Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) {
log.info("消息 {}", msg.text());
if (!start && "2".equals(msg.text())) {
for (Channel c : channels) {
if (ctx.channel() != c) {
c.writeAndFlush(new TextWebSocketFrame("21:游戏开始"));
}
}
start = true;
} else {
for (Channel c : channels) {
if (ctx.channel() != c) {
c.writeAndFlush(new TextWebSocketFrame(msg.text()));
}
}
if (msg.text().startsWith("4:")) {
start = false;
for (Channel c : channels) {
channels.remove(c);
}
}
}
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) {
if (channels.size() == 2) {
ctx.channel().writeAndFlush(new TextWebSocketFrame("0:已有人在对弈中。。"));
} else if (channels.size() == 1) {
channels.add(ctx.channel());
channels.writeAndFlush(new TextWebSocketFrame("2:游戏开始"));
} else if (channels.size() == 0) {
ctx.channel().writeAndFlush(new TextWebSocketFrame("1:等待人加入可以开始博弈了。。"));
channels.add(ctx.channel());
} else {
ctx.channel().writeAndFlush(new TextWebSocketFrame("0:连接失败。。"));
}
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) {
channels.remove(ctx.channel());
ctx.channel().writeAndFlush(new TextWebSocketFrame("1:等待人加入可以开始博弈了。。"));
start = false;
}
}
页面
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>js画布--五指棋</title>
</head>
<style>
#now {
position:absolute;
width: 30px;
height: 30px;
border: 2px solid #e4007e;
text-align: center;
line-height: 200px;
font-weight: bold;
font-size: 24px;
background: burlywood;
border-radius: 50%;
z-index:100;
}
</style>
<body>
<div id="content">五指棋</div>
<canvas id="myCanvas" width="800" height="800" style="border:1px solid #c3c3c3;">
您的浏览器不支持 HTML5 canvas 标签。
</canvas>
<div id="now"></div>
<script>
const set = new Set();
// 黑白色
let bool = true;
// 当前是否是 本人走棋
let go = true;
// 本人棋子
let b = true;
const now = document.getElementById("now");
const t = document.getElementById("content");
const c = document.getElementById("myCanvas");
const ctx = c.getContext("2d");
let websocket = new WebSocket("ws://127.0.0.1:7397");
websocket.onopen = (evt) => {
t.innerText = "等待人加入可以开始博弈了";
};
websocket.onmessage = (evt) => {
let split = evt.data.split(":");
if (split[0] == 2) {
init();
websocket.send("2");
t.innerText = split[1] + ",执白棋 先走";
} else if (split[0] == 21) {
init();
bool = false;
go = false;
b = false;
t.innerText = split[1] + ",执黑棋 后走";
} else if (split[0] == 3) {
let s = split[1].split("_");
ctx.beginPath();
ctx.arc(s[1] * 40, s[2] * 40, 15, 0, 2 * Math.PI);
if (s[0] == 1) {
ctx.fillStyle = "#ffffff";
now.style.background = "#ffffff";
} else {
ctx.fillStyle = "#000000";
now.style.background = "#000000";
}
ctx.fill();
set.add(split[1]);
bool = b;
go = true;
now.style.left = (c.getBoundingClientRect().left + s[1] * 40 - 15) + "px";
now.style.top = (c.getBoundingClientRect().top + s[2] * 40 - 15) + "px";
} else if (split[0] == 4) {
ctx.font = "60px Georgia";
ctx.fillStyle = "red";
ctx.fillText('您输了!!!', 240, 340);
t.innerText = "等待人加入可以开始博弈了";
}
};
function init() {
ctx.fillStyle = "#ccc";
ctx.fillRect(0, 0, 800, 800);
let i;
for (i = 40; i < 800; i = i + 40) {
ctx.beginPath();
ctx.moveTo(i, 40);
ctx.lineTo(i, 760);
ctx.stroke();
}
for (i = 40; i < 800; i = i + 40) {
ctx.beginPath();
ctx.moveTo(40, i);
ctx.lineTo(760, i);
ctx.stroke();
}
}
c.addEventListener('click', (function () {
//标准的获取鼠标点击相对于canvas画布的坐标公式
let x = event.pageX - c.getBoundingClientRect().left;
let y = event.pageY - c.getBoundingClientRect().top;
if (x < 40 || x > 780 || y < 40 || y > 780 || !go) {
return;
}
go = false;
x = getLocation(x);
y = getLocation(y);
var key = getKey(bool, x, y);
if (set.has(key) || set.has(getKey(!bool, x, y))) {
return;
}
ctx.beginPath();
ctx.arc(x, y, 15, 0, 2 * Math.PI);
if (bool) {
ctx.fillStyle = "#ffffff";
} else {
ctx.fillStyle = "#000000";
}
ctx.fill();
set.add(key);
websocket.send('3:' + key);
if (valid(b, x, y)) {
websocket.send("4:游戏结束");
ctx.font = "60px Georgia";
ctx.fillStyle = "red";
ctx.fillText('您赢了!!!' , 240, 340);
t.innerText = "等待人加入可以开始博弈了";
}
bool = !b;
}));
function getLocation(l) {
const v = l % 40;
const v1 = Math.floor(l / 40);
return v > 20.0 ? v1 * 40 + 40 : v1 * 40;
}
function getKey(bool, x, y) {
const i = bool ? 1 : 0;
x = Math.floor(x / 40);
y = Math.floor(y / 40);
return i + "_" + x + "_" + y;
}
function getKeyStr(i, x, y) {
return i + "_" + x + "_" + y;
}
function valid(bool, x, y) {
const r = bool ? 1 : 0;
x = Math.floor(x / 40);
y = Math.floor(y / 40);
const heng = hengValid(r, x, y);
if (heng) return true;
const su = suValid(r, x, y);
if (su) return true;
const x1 = xieZheng(r, x, y);
if (x1) return true;
return xieSu(r, x, y);
}
function xieZheng(r, x, y) {
let str = '';
for (let i = (x > 4 ? x - 4 : 1); i <= (x > 15 ? 19 : x + 4); i++) {
str = str + (set.has(getKeyStr(r, i, x + y - i)) ? '1' : '0');
}
return str.indexOf("11111") !== -1;
}
function xieSu(r, x, y) {
let str = '';
for (let i = (y > 4 ? y - 4 : 1); i <= (y > 15 ? 19 : y + 4); i++) {
str = str + (set.has(getKeyStr(r, x - (y - i), i)) ? '1' : '0');
}
return str.indexOf("11111") !== -1;
}
function suValid(r, x, y) {
let str = '';
for (let i = (y > 4 ? y - 4 : 1); i <= (y > 15 ? 19 : y + 4); i++) {
str = str + (set.has(getKeyStr(r, x, i)) ? '1' : '0');
}
return str.indexOf("11111") !== -1;
}
function hengValid(r, x, y) {
let str = '';
for (let i = (x > 4 ? x - 4 : 1); i <= (x > 15 ? 19 : x + 4); i++) {
str = str + (set.has(getKeyStr(r, i, y)) ? '1' : '0');
}
return str.indexOf("11111") !== -1;
}
</script>
</body>
</html>
五子棋 js canvas 校验逻辑