//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()
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 < 0) {
nextLocation.x = SnakeAnnotation.PLAYFIELD_WIDTH;
}
if (nextLocation.y < 0) {
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 < 41) {
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 < packet.data.length; i++) {
Game.updateSnake(packet.data.id, packet.data.body);
}
break;
case 'join':
for (var j = 0; j < packet.data.length; 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.length; i++) {
noscripts.parentNode.removeChild(noscripts);
}
}, false);