diff --git a/sql/init.sql b/sql/init.sql index 71a6872..75b7676 100755 --- a/sql/init.sql +++ b/sql/init.sql @@ -26,3 +26,6 @@ CREATE TABLE IF NOT EXISTS armageddon.person ( INSERT INTO armageddon.account (id,login,password) VALUES(1,'shmyga', 'd48cc4eb42c058869ae90daef9606e43'); INSERT INTO armageddon.person (id,account_id,name) VALUES(1,1,'-=Shmyga=-'); +INSERT INTO armageddon.account (id,login,password) VALUES(2,'a', md5('a')); +INSERT INTO armageddon.person (id,account_id,name) VALUES(2,2,'a'); + diff --git a/src/client/haxe/ru/m/tankz/core/PlayerTank.hx b/src/client/haxe/ru/m/tankz/core/PlayerTank.hx index c4d15b5..ea8ca4d 100755 --- a/src/client/haxe/ru/m/tankz/core/PlayerTank.hx +++ b/src/client/haxe/ru/m/tankz/core/PlayerTank.hx @@ -49,7 +49,11 @@ class PlayerTank extends Tank { private function updateMove():Void { if (moveQueue.length == 0) { - stop(); + //stop(); + Provider.get(IConnection).send( + new GameActionRequest() + .setType(GameActionType.STOP) + ); } else { switch (keyBinding.get(moveQueue[0])) { case TankAction.MOVE(direction): diff --git a/src/client/haxe/ru/m/tankz/view/frames/GameFrame.hx b/src/client/haxe/ru/m/tankz/view/frames/GameFrame.hx index a8ef639..01ed32e 100755 --- a/src/client/haxe/ru/m/tankz/view/frames/GameFrame.hx +++ b/src/client/haxe/ru/m/tankz/view/frames/GameFrame.hx @@ -1,5 +1,9 @@ package ru.m.tankz.view.frames; +import ru.m.tankz.core.MobileEntity; +import ru.m.tankz.core.Direction; +import ru.m.tankz.proto.GameObjectType; +import ru.m.tankz.proto.GameChangeType; import ru.m.tankz.game.ClientTankz; import protohx.Message; import ru.m.tankz.proto.GameUpdateResponse; @@ -32,6 +36,7 @@ class GameFrame extends VGroupView implements ViewBuilder implements IPacketHand game.init(persons, DEFAULT.CONFIG); content.addEventListener(Event.ENTER_FRAME, updateGame); Provider.get(IConnection).packetHandler.addListener(this); + render.draw(game); } public function onHide():Void { @@ -41,12 +46,47 @@ class GameFrame extends VGroupView implements ViewBuilder implements IPacketHand } private function updateGame(_):Void { - game.update(); - render.draw(game); + //game.update(); + //render.draw(game); } public function onGameUpdateResponse(packet:GameUpdateResponse):Void { - + for (change in packet.changes) { + switch (change.type) { + case GameChangeType.DIRECTION: + switch (change.objectType) { + case GameObjectType.TANK: + for (tank in game.tanks) { + if (tank.id == change.objectId) { + tank.direction = new Direction(change.directionX, change.directionY); + break; + } + } + } + case GameChangeType.MOVED: + switch (change.objectType) { + case GameObjectType.TANK: + for (tank in game.tanks) { + if (tank.id == change.objectId) { + tank.x = change.x; + tank.y = change.y; + break; + } + } + } + case GameChangeType.APPEND: + switch (change.objectType) { + case GameObjectType.BULLET: + for (tank in game.tanks) { + if (tank.id == change.parentObjectId) { + tank.bullets.push(new MobileEntity(0, change.x, change.y, 0, new Direction(change.directionX, change.directionY))); + break; + } + } + } + } + } + render.draw(game); } public function onPacket(packet:Message):Void {} diff --git a/src/common/haxe/ru/m/core/connect/BaseConnection.hx b/src/common/haxe/ru/m/core/connect/BaseConnection.hx index d72e9cb..ca8802a 100755 --- a/src/common/haxe/ru/m/core/connect/BaseConnection.hx +++ b/src/common/haxe/ru/m/core/connect/BaseConnection.hx @@ -42,11 +42,11 @@ class BaseConnection implements IConnection { } } public function send(packet:Message):Void { - L.d("Send", Type.getClassName(Type.getClass(packet)).split(".").pop()); + //L.d("Send", Type.getClassName(Type.getClass(packet)).split(".").pop()); } public function receive(packet:Message):Void { - L.d("Receive", Type.getClassName(Type.getClass(packet)).split(".").pop()); + //L.d("Receive", Type.getClassName(Type.getClass(packet)).split(".").pop()); var name = "on" + Type.getClassName(Type.getClass(packet)).split(".").pop(); packetHandler.dispatch(function(h) { var method = Reflect.field(h, name); diff --git a/src/common/haxe/ru/m/core/connect/neko/NekoWebConnection.hx b/src/common/haxe/ru/m/core/connect/neko/NekoWebConnection.hx index b751baa..d771399 100644 --- a/src/common/haxe/ru/m/core/connect/neko/NekoWebConnection.hx +++ b/src/common/haxe/ru/m/core/connect/neko/NekoWebConnection.hx @@ -17,7 +17,7 @@ class NekoWebConnection extends NekoConnection { } override public function send(packet:Message):Void { - L.d("Send", Type.getClassName(Type.getClass(packet)).split(".").pop()); + //L.d("Send", Type.getClassName(Type.getClass(packet)).split(".").pop()); try { var data = WebSocketTools.packet2string(packet, builder); writeData(data, socket); diff --git a/src/common/haxe/ru/m/tankz/core/Entity.hx b/src/common/haxe/ru/m/tankz/core/Entity.hx index 410bbe0..60939de 100755 --- a/src/common/haxe/ru/m/tankz/core/Entity.hx +++ b/src/common/haxe/ru/m/tankz/core/Entity.hx @@ -2,8 +2,8 @@ package ru.m.tankz.core; class Entity implements IEntity { - public var x(default, default):Float; - public var y(default, default):Float; + public var x(default, default):Float = 0; + public var y(default, default):Float = 0; public var width(default, default):Float; public var height(default, default):Float; diff --git a/src/common/haxe/ru/m/tankz/core/IMobileEntity.hx b/src/common/haxe/ru/m/tankz/core/IMobileEntity.hx index cff6155..2467e96 100755 --- a/src/common/haxe/ru/m/tankz/core/IMobileEntity.hx +++ b/src/common/haxe/ru/m/tankz/core/IMobileEntity.hx @@ -1,6 +1,8 @@ package ru.m.tankz.core; interface IMobileEntity extends IEntity { + public var id(default, null):Int; + public var mx(default, default):Float; public var my(default, default):Float; diff --git a/src/common/haxe/ru/m/tankz/core/ITank.hx b/src/common/haxe/ru/m/tankz/core/ITank.hx index cc75a8c..c316fae 100755 --- a/src/common/haxe/ru/m/tankz/core/ITank.hx +++ b/src/common/haxe/ru/m/tankz/core/ITank.hx @@ -1,7 +1,6 @@ package ru.m.tankz.core; interface ITank extends IMobileEntity { - public var id(default, null):Int; public var bullets:Array; public function shot():Void; diff --git a/src/common/haxe/ru/m/tankz/core/MobileEntity.hx b/src/common/haxe/ru/m/tankz/core/MobileEntity.hx index 32cb079..09fe936 100755 --- a/src/common/haxe/ru/m/tankz/core/MobileEntity.hx +++ b/src/common/haxe/ru/m/tankz/core/MobileEntity.hx @@ -1,15 +1,17 @@ package ru.m.tankz.core; class MobileEntity extends Entity implements IMobileEntity { + public var id(default, null):Int; - public var mx(default, default):Float; - public var my(default, default):Float; + public var mx(default, default):Float = 0; + public var my(default, default):Float = 0; - public var speed(default, null):Float; + public var speed(default, null):Float = 0; public var direction(default, default):Direction; - public function new(x:Float, y:Float, speed:Float, direction:Direction = null) { + public function new(id:Int, x:Float, y:Float, speed:Float, direction:Direction = null) { super(x, y); + this.id = id; this.speed = speed; this.direction = direction == null ? Direction.BOTTOM : direction; } diff --git a/src/common/haxe/ru/m/tankz/core/Tank.hx b/src/common/haxe/ru/m/tankz/core/Tank.hx index 351f717..14d8e9b 100755 --- a/src/common/haxe/ru/m/tankz/core/Tank.hx +++ b/src/common/haxe/ru/m/tankz/core/Tank.hx @@ -7,11 +7,10 @@ enum TankAction { class Tank extends MobileEntity implements ITank { - public var id(default, null):Int; public var bullets:Array; public function new(id:Int, x:Float, y:Float) { - super(x, y, 4); + super(id, x, y, 4); this.id = id; bullets = new Array(); width = 34; @@ -20,7 +19,7 @@ class Tank extends MobileEntity implements ITank { public function shot():Void { if (bullets.length >= 5) return; - var bullet = new MobileEntity(x + width / 2 - 5, y + height / 2 - 5, 6, direction); + var bullet = new MobileEntity(0, x + width / 2 - 5, y + height / 2 - 5, 6, direction); bullet.width = 10; bullet.height = 10; bullet.move(direction); diff --git a/src/common/proto/base.proto b/src/common/proto/base.proto index f7e4fec..2819634 100755 --- a/src/common/proto/base.proto +++ b/src/common/proto/base.proto @@ -93,6 +93,7 @@ message ExitGameResponse { enum GameActionType { MOVE = 1; SHOT = 2; + STOP = 3; } message GameActionRequest { @@ -103,6 +104,7 @@ message GameActionRequest { enum GameObjectType { TANK = 1; + BULLET = 2; } enum GameChangeType { @@ -110,14 +112,18 @@ enum GameChangeType { DESTROED = 2; MODIFIED = 3; APPEND = 4; + DIRECTION = 5; } message GameChange { required GameChangeType type = 1; required GameObjectType objectType = 2; required int32 objectId = 3; - optional int32 newX = 4; - optional int32 newY = 5; + optional int32 parentObjectId = 4; + optional float x = 5; + optional float y = 6; + optional int32 directionX = 7; + optional int32 directionY = 8; } message GameUpdateResponse { diff --git a/src/server/haxe/ru/m/tankz/server/game/Game.hx b/src/server/haxe/ru/m/tankz/server/game/Game.hx new file mode 100644 index 0000000..c014624 --- /dev/null +++ b/src/server/haxe/ru/m/tankz/server/game/Game.hx @@ -0,0 +1,12 @@ +package ru.m.tankz.server.game; + +import ru.m.tankz.game.ITankz; + +class Game { + + private var tankz:ITankz; + + public function new() { + + } +} diff --git a/src/server/haxe/ru/m/tankz/server/session/Session.hx b/src/server/haxe/ru/m/tankz/server/session/Session.hx index 848f6a3..0d169f5 100755 --- a/src/server/haxe/ru/m/tankz/server/session/Session.hx +++ b/src/server/haxe/ru/m/tankz/server/session/Session.hx @@ -1,5 +1,12 @@ package ru.m.tankz.server.session; +import ru.m.tankz.proto.GameObjectType; +import ru.m.tankz.proto.GameChangeType; +import ru.m.tankz.proto.GameChange; +import ru.m.tankz.config.TankzConfig.DEFAULT; +import ru.m.tankz.proto.GameUpdateResponse; +import haxe.Timer; +import ru.m.tankz.core.Direction; import ru.m.tankz.game.Tankz; import ru.m.tankz.game.ITankz; import ru.m.tankz.proto.GameActionType; @@ -31,6 +38,39 @@ import protohx.Message; import ru.m.core.connect.IConnection; import sys.net.Socket; +class NekoTimer { + + private var sleep:Float; + private var stopped:Bool; + + public function new(time_ms:Int) { + this.sleep = time_ms / 1000.0; + this.stopped = false; + Thread.create(function() { + while (!stopped) { + Sys.sleep(sleep); + try { + run(); + } catch (error:Dynamic) { + trace(error); + } + } + }); + } + + public dynamic function run() {} + + public function stop() { + stopped = true; + } +} + + +typedef ObjectState = { + var x:Float; + var y:Float; + var d:Direction; +} class GameCenter { @@ -39,21 +79,27 @@ class GameCenter { private var created:Map; private var persons:Map; - private var running:Map; + public var running:Map; + private var timers:Map; public function new() { games = new Map(); created = new Map(); persons = new Map(); running = new Map(); + timers = new Map(); } public function getReadyGames():Array { return Lambda.array(games).filter(function(g) return g.state == GameState.READY); } - public function getCreatedGame(person_id:Int):Game { - return games.get(created.get(person_id)); + public function getCreatedGame(peronsId:Int):Game { + return games.get(created.get(peronsId)); + } + + public function getPersonGameId(personId:Int):Int { + return persons.get(personId); } public function createGame(person:Person):Game { @@ -82,6 +128,10 @@ class GameCenter { game.persons.remove(person); if (game.persons.length == 0) { games.remove(game.id); + if (timers.exists(game.id)) { + timers.get(game.id).stop(); + timers.remove(game.id); + } } break; } @@ -91,9 +141,57 @@ class GameCenter { public function start(gameId:Int):Void { if (games.exists(gameId)) { - games.get(gameId).setState(GameState.STARTED); + var game:Game = games.get(gameId); + game.setState(GameState.STARTED); var tankz = new Tankz(); + tankz.init(game.persons, DEFAULT.CONFIG); running.set(gameId, tankz); + var timer = new NekoTimer(30); + timer.run = buildUpdater(gameId, tankz); + timers.set(gameId, timer); + broadcast(gameId, new StartGameResponse().setGame(game)); + } + } + + private function buildUpdater(gameId:Int, tankz:ITankz):Void->Void { + return function() { + var states = new Map(); + for (tank in tankz.tanks) { + states.set(tank.id, { + x: tank.x, + y: tank.y, + d: tank.direction + }); + } + tankz.update(); + var changes = new Array(); + for (tank in tankz.tanks) { + if (states.exists(tank.id)) { + var state = states.get(tank.id); + if (state.d != tank.direction) { + trace("DDD"); + changes.push(new GameChange() + .setType(GameChangeType.DIRECTION) + .setObjectType(GameObjectType.TANK) + .setObjectId(tank.id) + .setDirectionX(tank.direction.x) + .setDirectionY(tank.direction.y) + ); + } + if (state.x != tank.x || state.y != tank.y) { + changes.push(new GameChange() + .setType(GameChangeType.MOVED) + .setObjectType(GameObjectType.TANK) + .setObjectId(tank.id) + .setX(tank.x) + .setY(tank.y) + ); + } + } + } + if (changes.length > 0) { + broadcast(gameId, new GameUpdateResponse().setChanges(changes)); + } } } @@ -209,8 +307,7 @@ class Session implements IConnectionHandler implements IPacketHandler { public function onStartGameRequest(packet:StartGameRequest):Void { var game:Game = games.getCreatedGame(person.id); - game.setState(GameState.STARTED); - games.broadcast(game.id, new StartGameResponse().setGame(game)); + games.start(game.id); } public function onExitGameRequest(packet:ExitGameRequest):Void { @@ -219,11 +316,18 @@ class Session implements IConnectionHandler implements IPacketHandler { } public function onGameActionRequest(packet:GameActionRequest):Void { - switch (packet.type) { - case GameActionType.SHOT: - - case GameActionType.MOVE: - + var game:ITankz = games.running.get(games.getPersonGameId(person.id)); + for (tank in game.tanks) { + if (tank.id == person.id) { + switch (packet.type) { + case GameActionType.SHOT: + tank.shot(); + case GameActionType.MOVE: + tank.move(new Direction(packet.directionX, packet.directionY)); + case GameActionType.STOP: + tank.stop(); + } + } } } diff --git a/src/server/haxe/ru/m/tankz/server/session/Thread.hx b/src/server/haxe/ru/m/tankz/server/session/Thread.hx new file mode 100644 index 0000000..c7442f3 --- /dev/null +++ b/src/server/haxe/ru/m/tankz/server/session/Thread.hx @@ -0,0 +1,113 @@ +package ru.m.tankz.server.session; + +enum ThreadHandle { +} + +class Thread { + +var handle : ThreadHandle; + +function new(h) { +handle = h; +} + +/** + Send a message to the thread queue. This message can be readed by using [readMessage]. + **/ +public function sendMessage( msg : Dynamic ) { +thread_send(handle,msg); +} + + +/** + Returns the current thread. + **/ +public static function current() { +return new Thread(thread_current()); +} + +/** + Creates a new thread that will execute the [callb] function, then exit. + **/ +public static function create( callb : Void -> Void ) { +return new Thread(thread_create(function(_) { return callb(); },null)); +} + +/** + Reads a message from the thread queue. If [block] is true, the function + blocks until a message is available. If [block] is false, the function + returns [null] if no message is available. + **/ +public static function readMessage( block : Bool ) : Dynamic { +return thread_read_message(block); +} + +@:keep function __compare(t) { +return untyped __dollar__compare(handle,t.handle); +} + +/** + Starts an OS message loop after [osInitialize] has been done. + In that state, the UI handled by this thread will be updated and + [sync] calls can be performed. The loop returns when [exitLoop] is + called for this thread. + ** + public static function osLoop() { + if( os_loop == null ) throw "Please call osInitialize() first"; + os_loop(); + } + + /** + The function [f] will be called by this thread if it's in [osLoop]. + [sync] returns immediatly. See [osInitialize] remarks. + ** + public function sync( f : Void -> Void ) { + os_sync(handle,f); + } + + /** + The function [f] will be called by this thread and the calling thread + will wait until the result is available then return its value. + ** + public function syncResult( f : Void -> T ) : T { + if( this == current() ) + return f(); + var v = new neko.vm.Lock(); + var r = null; + sync(function() { + r = f(); + v.release(); + }); + v.wait(); + return r; + } + + /** + Exit from [osLoop]. + ** + public function exitLoop() { + os_loop_stop(handle); + } + + /** + If you want to use the [osLoop], [sync] and [syncResult] methods, you + need to call [osInitialize] before creating any thread or calling [current]. + This will load [os.ndll] library and initialize UI methods for each thread. + ** + public static function osInitialize() { + os_loop = neko.Lib.load("os","os_loop",0); + os_loop_stop = neko.Lib.load("os","os_loop_stop",1); + os_sync = neko.Lib.load("os","os_sync",2); + } + + static var os_loop = null; + static var os_loop_stop = null; + static var os_sync = null; + */ + +static var thread_create = neko.Lib.load("std","thread_create",2); +static var thread_current = neko.Lib.load("std","thread_current",0); +static var thread_send = neko.Lib.load("std","thread_send",2); +static var thread_read_message = neko.Lib.load("std","thread_read_message",1); + +}