tomcat websock html5,websocket实战(4) websocket版贪食蛇游戏(tomcat官方自带)
通过前面3篇的阐述,相信可以构建一个简单的socket应用了。当然,也会遗漏了许多知识点,相信会在以后分享的实例中捎带说明下。
本文的主要是分析下tomcat官方自带的贪食蛇游戏。为什么选择分析这个项目呢。贪食蛇游戏规则,人人明白,业务方面不需要过多解释(当然这款websocket版的游戏规则也有一定特色)。
游戏设计简单,一个对象足以完成游戏,但不涉及到一些复杂的逻辑算法。
通过游戏,有很好的代入感
1.游戏规则介绍
1.能够实现贪吃蛇自动向前移动,一旦贪食蛇选择了方向,贪食蛇就按所选方向开始运动,可以任意。移动方向为贪吃蛇当前行走方向。
2.游戏通过键盘的上下左右四个方向控制贪吃蛇当前行走方向。(没有可以吃的食物)。
3.支持对战功能,如果发生碰撞情况,后蛇会自杀,重置信息,重新来玩。
4.如果移动出画布外,从对立方向进入,移动方向不变。
界面是"群蛇乱舞”界面。
2.贪食蛇设计
贪食蛇状态快照
贪食蛇类图
贪食蛇:有几个重要属性。颜色,头(head),身体(tail),行动方向。
颜色:随机生成。
头&身体:决定蛇的长度,在画布中的位置。还有决定是否发生碰撞。有(x,y)坐标说明。
行动方向:东西南北四个方向。
重点说一下和websocket相关的信息。贪食蛇的session属性。
session主要负责贪食蛇状态信息的传播,将自己的颜色和位置信息传递到前端。
传播时机状态变化要传播(kill,join,..)
位置变化要传播(包括方向,其实也是状态变化)
重置要传播(也是状态变化)
分析序列图得知,其实作为游戏的websocket的EndPoint,做的事情很简单。两件事有新需求:创建贪食蛇,发送渲染命令(join)
响应客户端的命令(方向命令)
不难分析,游戏贪食蛇的移动,是应该有定时器驱动的,所有贪食蛇位置的变化,都是通过SnakeTimer驱动的。然后更新位置信息,最后调用贪食蛇,将自己信息传递到前端。所以定时器,需要维护贪食蛇的聚合信息。
1.贪食蛇聚合信息维护(CRD,没有更新,贪食蛇信息的更新不属于聚合信息范畴)protected static synchronized void addSnake(Snake snake) {
if (snakes.size() == 0) {
startTimer();
}
snakes.put(Integer.valueOf(snake.getId()), snake);
}
protected static Collection getSnakes() {
return Collections.unmodifiableCollection(snakes.values());
}
protected static synchronized void removeSnake(Snake snake) {
snakes.remove(Integer.valueOf(snake.getId()));
if (snakes.size() == 0) {
stopTimer();
}
}
2. 消息广播(将贪食蛇最新状态信息,实时广播到前端)
就是调用snake自动发送,不难猜,调用session相关的方法。//SnakeTimer.java
protected static void broadcast(String message) {
for (Snake snake : SnakeTimer.getSnakes()) {
try {
snake.sendMessage(message);
} catch (IllegalStateException ise) {
// An ISE can occur if an attempt is made to write to a
// WebSocket connection after it has been closed. The
// alternative to catching this exception is to synchronise
// the writes to the clients along with the addSnake() and
// removeSnake() methods that are already synchronised.
}
}
}
//Snake.java
protected void sendMessage(String msg) {
try {
session.getBasicRemote().sendText(msg);
} catch (IOException ioe) {
CloseReason cr =
new CloseReason(CloseCodes.CLOSED_ABNORMALLY, ioe.getMessage());
try {
session.close(cr);
} catch (IOException ioe2) {
// Ignore
}
}
}
实时更新位置信息
websocket.snake.SnakeTimer.tick()protected static void tick() {
StringBuilder sb = new StringBuilder();
for (Iterator iterator = SnakeTimer.getSnakes().iterator();
iterator.hasNext();) {
Snake snake = iterator.next();
snake.update(SnakeTimer.getSnakes());
sb.append(snake.getLocationsJson());
if (iterator.hasNext()) {
sb.append(',');
}
}
broadcast(String.format("{'type': 'update', 'data' : [%s]}",
sb.toString()));
}
按方向计算贪食蛇头下一个的位置
websocket.snake.Location. getAdjacentLocation(Direction direction)
没有方向,不变化位置。public Location getAdjacentLocation(Direction direction) {
switch (direction) {
case NORTH:
return new Location(x, y - SnakeAnnotation.GRID_SIZE);
case SOUTH:
return new Location(x, y + SnakeAnnotation.GRID_SIZE);
case EAST:
return new Location(x + SnakeAnnotation.GRID_SIZE, y);
case WEST:
return new Location(x - SnakeAnnotation.GRID_SIZE, y);
case NONE:
// fall through
default:
return this;
}
}
websocket.snake.Snake. update(Collection snakes)public synchronized void update(Collection snakes) {
Location nextLocation = head.getAdjacentLocation(direction);
if (nextLocation.x >= SnakeAnnotation.PLAYFIELD_WIDTH) {
nextLocation.x = 0;
}
if (nextLocation.y >= SnakeAnnotation.PLAYFIELD_HEIGHT) {
nextLocation.y = 0;
}
if (nextLocation.x
nextLocation.x = SnakeAnnotation.PLAYFIELD_WIDTH;
}
if (nextLocation.y
nextLocation.y = SnakeAnnotation.PLAYFIELD_HEIGHT;
}
if (direction != Direction.NONE) {
tail.addFirst(head);
if (tail.size() > length) {
tail.removeLast();//这一步很关键,实现动态位置变化,否则蛇就无限增长了
}
head = nextLocation;
}
//处理蛇是否发生碰撞
handleCollisions(snakes);
}
判断是否发生碰撞
判断和其他,是否发生重叠。是否迎头碰撞,还是头尾碰撞。private void handleCollisions(Collection snakes) {
for (Snake snake : snakes) {
boolean headCollision = id != snake.id && snake.getHead().equals(head);
boolean tailCollision = snake.getTail().contains(head);
if (headCollision || tailCollision) {
kill();//牺牲自己,触发dead类型信息
if (id != snake.id) {
snake.reward();//成全别人,让别人长度增加1.触发kill类型信息
}
}
}
}
主要业务逻辑就分析完毕了。有对canvas感兴趣的,可以关注前端js.var Game = {};
Game.fps = 30;
Game.socket = null;
Game.nextFrame = null;
Game.interval = null;
Game.direction = 'none';
Game.gridSize = 10;
function Snake() {
this.snakeBody = [];
this.color = null;
}
Snake.prototype.draw = function(context) {
for (var id in this.snakeBody) {
context.fillStyle = this.color;
context.fillRect(this.snakeBody[id].x, this.snakeBody[id].y, Game.gridSize, Game.gridSize);
}
};
Game.initialize = function() {
this.entities = [];
canvas = document.getElementById('playground');
if (!canvas.getContext) {
Console.log('Error: 2d canvas not supported by this browser.');
return;
}
this.context = canvas.getContext('2d');
window.addEventListener('keydown', function (e) {
var code = e.keyCode;
if (code > 36 && code
switch (code) {
case 37:
if (Game.direction != 'east') Game.setDirection('west');
break;
case 38:
if (Game.direction != 'south') Game.setDirection('north');
break;
case 39:
if (Game.direction != 'west') Game.setDirection('east');
break;
case 40:
if (Game.direction != 'north') Game.setDirection('south');
break;
}
}
}, false);
if (window.location.protocol == 'http:') {
Game.connect('ws://' + window.location.host + '/wsexample/websocket/snake');
} else {
Game.connect('wss://' + window.location.host + '/wsexample/websocket/snake');
}
};
Game.setDirection = function(direction) {
Game.direction = direction;
Game.socket.send(direction);
Console.log('Sent: Direction ' + direction);
};
Game.startGameLoop = function() {
if (window.webkitRequestAnimationFrame) {
Game.nextFrame = function () {
webkitRequestAnimationFrame(Game.run);
};
} else if (window.mozRequestAnimationFrame) {
Game.nextFrame = function () {
mozRequestAnimationFrame(Game.run);
};
} else {
Game.interval = setInterval(Game.run, 1000 / Game.fps);
}
if (Game.nextFrame != null) {
Game.nextFrame();
}
};
Game.stopGameLoop = function () {
Game.nextFrame = null;
if (Game.interval != null) {
clearInterval(Game.interval);
}
};
Game.draw = function() {
this.context.clearRect(0, 0, 640, 480);
for (var id in this.entities) {
this.entities[id].draw(this.context);
}
};
Game.addSnake = function(id, color) {
Game.entities[id] = new Snake();
Game.entities[id].color = color;
};
Game.updateSnake = function(id, snakeBody) {
if (typeof Game.entities[id] != "undefined") {
Game.entities[id].snakeBody = snakeBody;
}
};
Game.removeSnake = function(id) {
Game.entities[id] = null;
// Force GC.
delete Game.entities[id];
};
Game.run = (function() {
var skipTicks = 1000 / Game.fps, nextGameTick = (new Date).getTime();
return function() {
while ((new Date).getTime() > nextGameTick) {
nextGameTick += skipTicks;
}
Game.draw();
if (Game.nextFrame != null) {
Game.nextFrame();
}
};
})();
Game.connect = (function(host) {
if ('WebSocket' in window) {
Game.socket = new WebSocket(host);
} else if ('MozWebSocket' in window) {
Game.socket = new MozWebSocket(host);
} else {
Console.log('Error: WebSocket is not supported by this browser.');
return;
}
Game.socket.onopen = function () {
// Socket open.. start the game loop.
Console.log('Info: WebSocket connection opened.');
Console.log('Info: Press an arrow key to begin.');
Game.startGameLoop();
setInterval(function() {
// Prevent server read timeout.
Game.socket.send('ping');
}, 5000);
};
Game.socket.onclose = function () {
Console.log('Info: WebSocket closed.');
Game.stopGameLoop();
};
Game.socket.onmessage = function (message) {
// _Potential_ security hole, consider using json lib to parse data in production.
var packet = eval('(' + message.data + ')');
switch (packet.type) {
case 'update':
for (var i = 0; i
Game.updateSnake(packet.data[i].id, packet.data[i].body);
}
break;
case 'join':
for (var j = 0; j
Game.addSnake(packet.data[j].id, packet.data[j].color);
}
break;
case 'leave':
Game.removeSnake(packet.id);
break;
case 'dead':
Console.log('Info: Your snake is dead, bad luck!');
Game.direction = 'none';
break;
case 'kill':
Console.log('Info: Head shot!');
break;
}
};
});
var Console = {};
Console.log = (function(message) {
var console = document.getElementById('console');
var p = document.createElement('p');
p.style.wordWrap = 'break-word';
p.innerHTML = message;
console.appendChild(p);
while (console.childNodes.length > 25) {
console.removeChild(console.firstChild);
}
console.scrollTop = console.scrollHeight;
});
Game.initialize();
document.addEventListener("DOMContentLoaded", function() {
// Remove elements with "noscript" class -
is not allowed in XHTML
var noscripts = document.getElementsByClassName("noscript");
for (var i = 0; i
noscripts[i].parentNode.removeChild(noscripts[i]);
}
}, false);
结论
通过阅读一些官方文档的代码,学习人家的编码风格,细节。比如线程安全方面。js的面向对象编写,很优雅。不像笔者遇到的经常看到的一个方法,一个方法式的嵌套调用,不考虑性能,就阅读起来就特别费劲。
tomcat websock html5,websocket实战(4) websocket版贪食蛇游戏(tomcat官方自带)相关推荐
- Java版贪食蛇游戏
技术:Java等 摘要: 近年来Java作为一种新的编程语言,以其简单性.可移植性和平台无关性等优点,得到了广泛地应用,特别是Java与万维网的完美结合,使其成为网络编程和嵌入式编程领域的首选编程语言 ...
- Java版贪食蛇游戏的设计与实现毕业设计
技术:Java.JSP等 摘要: 使用java语言,贪吃蛇游戏,其界面简单易于操作,受到了世界各地玩家的喜爱.近年来,作为一种的高级语言java收到了大家的欢迎欢迎,具有简单.易于理解的特性,被广泛应 ...
- [原]Console小技巧——Console版贪食蛇
这一篇是我的Console小技巧的最后一篇文章,以下是索引: 1.[原]Console小技巧--七彩输出 2.[原]Console小技巧--字符涂鸦 3.[原]Console小技巧--Console版 ...
- Python 简易版贪食蛇(源代码)
Python 简易版贪食蛇 简易版贪食蛇代码如下,直接运行即可. 1. 效果图 2.源代码 源代码如下: #!/usr/bin/env python # -*- coding: utf-8 -*-im ...
- java贪吃蛇设计流程_JAVA版贪食蛇(贪吃蛇)游戏的设计与实现(含录像)
JAVA版贪食蛇(贪吃蛇)游戏的设计与实现(含录像)(任务书,开题报告,外文翻译,毕业论文12000字,程序代码,MySQL数据库,答辩PPT,答辩视频录像) 摘要 "贪食蛇"游戏 ...
- 网页版贪食蛇【基于HTML+CSS+JavaScript】实现
最终效果 HTML代码 <!DOCTYPE html> <html lang="en"><head><meta charset=" ...
- 视频教程| 3D版切水果游戏开发实战:认识水果
本周,很多Egret 老铁看到了我们的3D实战内容并积极给予了回应:要与我们一起实战开发3D版切水果游戏!看完后很是感动,在此谢谢老铁们对我们的支持,你们的认可与鼓励是我们不断前行的动力! Egret ...
- tomcat处理html流程,基于Tomcat运行HTML5 WebSocket echo实例详解
一.概述 作为HTML5新特性之一的WebSocket组件,在实时性有一定要求的WEB应用开 发中还是有一定用武之地,高版本的IE.Chrome.FF浏览器都支持Websocket,标准的Websoc ...
- HTML5 Canvas游戏开发实战 PDF扫描版
HTML5 Canvas游戏开发实战主要讲解使用HTML5 Canvas来开发和设计各类常见游戏的思路和技巧,在介绍HTML5 Canvas相关特性的同时,还通过游戏开发实例深入剖析了其内在原理,让读 ...
最新文章
- BZOJ1965 [Ahoi2005]SHUFFLE 洗牌 快速幂
- 请解释为什么集合类没有实现Cloneable和Serializable接口?
- 计算机论文指导书,计算机毕业论文指导书.doc
- servlet文件上传下载_Servlet上传文件和下载文件示例
- 米筐量化不支持c语言_量化 | 从零开始学量化(三):数据获取途径
- strace调试(Linux Device Driver)
- python 判断数字连续_关于python:检测列表中的连续整数
- 这篇带你熟悉 SpringBoot+RabbitMQ 方式收发消息
- 敏捷开发“松结对编程”实践之四:日常工作篇(大型研发团队,学习型团队,139团队,师徒制度,检查点,代码审查,每日立会)...
- linux系统设置服务开机启动3种方法,Linux开机启动程序详解
- 语言用符号打印出落叶的图案_普通语言学概要(第一章第二节,语言是符号系统)...
- QQ音乐系统API接口文档
- matlab除与左除,Matlab左除和右除
- scrapy持久化存储
- linux互信文件,linux SSH互信
- 闽南歌歌词有一句电子计算机,一首闽南歌,有一句歌词是(提起男儿的志气)歌名是什么?...
- win10浏览器闪退_win10系统ie打不开闪退怎么办
- 川土微电子 | 如何隔离 RS-485 系统
- pink老师 js p85思考题
- Jest测试语法系列之Expect
热门文章
- Android系统信息获取 之八:WIFI设备和WIFI信号信息获取
- Abseil之string_view
- 系统定时任务linux,Linux系统管理之定时任务
- python torchvision_pip install torchvision error:安装版本为0.4.1的torch后继续安装torchvision报错...
- navision系统和sap区别_上海生产管理EPR系统和SAP系统有什么不同,我们应该选择哪个?...
- 压力测试工具之DDos-Attack
- windows 系统配置多网关win添加静态路由
- 《机器学习》二刷超详细笔记| 第一章 绪论
- Error while waiting for device: The emulator process for AVD Pixel_2_XL_API_28 was killed.
- php-fpm linux_Linux下PHP-fpm配置