tomcat websock html5,websocket实战(4) websocket版贪食蛇游戏(tomcat官方自带)

通过前面3篇的阐述,相信可以构建一个简单的socket应用了。当然,也会遗漏了许多知识点,相信会在以后分享的实例中捎带说明下。

本文的主要是分析下tomcat官方自带的贪食蛇游戏。为什么选择分析这个项目呢。贪食蛇游戏规则,人人明白,业务方面不需要过多解释(当然这款websocket版的游戏规则也有一定特色)。

游戏设计简单,一个对象足以完成游戏,但不涉及到一些复杂的逻辑算法。

通过游戏,有很好的代入感

1.游戏规则介绍

1.能够实现贪吃蛇自动向前移动,一旦贪食蛇选择了方向,贪食蛇就按所选方向开始运动,可以任意。移动方向为贪吃蛇当前行走方向。

2.游戏通过键盘的上下左右四个方向控制贪吃蛇当前行走方向。(没有可以吃的食物)。

3.支持对战功能,如果发生碰撞情况,后蛇会自杀,重置信息,重新来玩。

4.如果移动出画布外,从对立方向进入,移动方向不变。

界面是"群蛇乱舞”界面。

d8c77cd28c130bf75c4549acfaa239a1.png

2.贪食蛇设计

贪食蛇状态快照

86f47cf6852abe699d4a8c059dcfae9f.png

贪食蛇类图

820a27fa7e6b57c6db0c26211187ccf0.png

贪食蛇:有几个重要属性。颜色,头(head),身体(tail),行动方向。

颜色:随机生成。

头&身体:决定蛇的长度,在画布中的位置。还有决定是否发生碰撞。有(x,y)坐标说明。

行动方向:东西南北四个方向。

重点说一下和websocket相关的信息。贪食蛇的session属性。

session主要负责贪食蛇状态信息的传播,将自己的颜色和位置信息传递到前端。

传播时机状态变化要传播(kill,join,..)

位置变化要传播(包括方向,其实也是状态变化)

重置要传播(也是状态变化)

85524f339b2f0a90271530199fda369e.png

分析序列图得知,其实作为游戏的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的面向对象编写,很优雅。不像笔者遇到的经常看到的一个方法,一个方法式的嵌套调用,不考虑性能,就阅读起来就特别费劲。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值