From 26ad5f47cd902efbcfbd91dd27f05c1c34d48061 Mon Sep 17 00:00:00 2001 From: shmyga Date: Thu, 23 May 2019 17:57:54 +0300 Subject: [PATCH] [client] add GameRoomFrame --- gulpfile.js | 4 +- .../haxe/ru/m/tankz/network/NetworkManager.hx | 22 +- .../haxe/ru/m/tankz/view/ClientView.yaml | 2 + .../ru/m/tankz/view/network/GameListFrame.hx | 29 ++- .../m/tankz/view/network/GameListFrame.yaml | 7 +- .../ru/m/tankz/view/network/GameRoomFrame.hx | 45 ++++ .../m/tankz/view/network/GameRoomFrame.yaml | 20 ++ src/common/haxe/ru/m/Timer.hx | 60 ++++++ .../haxe/ru/m/tankz/core/Modificator.hx | 2 +- src/common/haxe/ru/m/tankz/game/GameRunner.hx | 2 +- src/common/haxe/ru/m/tankz/game/GameState.hx | 5 +- src/common/haxe/ru/m/tankz/game/Spawner.hx | 3 +- src/common/proto/pack.proto | 4 + src/server/haxe/ru/m/tankz/server/Server.hx | 9 +- .../haxe/ru/m/tankz/server/db/DbProvider.hx | 37 ---- .../ru/m/tankz/server/game/GameManager.hx | 204 ++++++------------ .../ru/m/tankz/server/game/IGameManager.hx | 35 +++ .../haxe/ru/m/tankz/server/game/ServerGame.hx | 18 ++ .../ru/m/tankz/server/session/GameSession.hx | 137 ++++++++---- .../haxe/ru/m/tankz/server/session/Thread.hx | 112 ---------- .../ru/m/tankz/server/session/_Session.hx | 156 -------------- 21 files changed, 415 insertions(+), 498 deletions(-) create mode 100644 src/client/haxe/ru/m/tankz/view/network/GameRoomFrame.hx create mode 100644 src/client/haxe/ru/m/tankz/view/network/GameRoomFrame.yaml create mode 100644 src/common/haxe/ru/m/Timer.hx delete mode 100755 src/server/haxe/ru/m/tankz/server/db/DbProvider.hx create mode 100644 src/server/haxe/ru/m/tankz/server/game/IGameManager.hx create mode 100644 src/server/haxe/ru/m/tankz/server/game/ServerGame.hx delete mode 100644 src/server/haxe/ru/m/tankz/server/session/Thread.hx delete mode 100755 src/server/haxe/ru/m/tankz/server/session/_Session.hx diff --git a/gulpfile.js b/gulpfile.js index c0a0f4e..7ffac29 100755 --- a/gulpfile.js +++ b/gulpfile.js @@ -78,6 +78,7 @@ const client = new Project( height: 768, }, flags: [ + //'proto_debug', //'dom', //'dev_layout', //'bitmap_text', @@ -125,7 +126,8 @@ const server = new Project( name: 'server', sources: ['src/server/haxe'], main: 'ru.m.tankz.server.Server', - }) + }), + module.exports.generate ).bind(module, gulp); /** diff --git a/src/client/haxe/ru/m/tankz/network/NetworkManager.hx b/src/client/haxe/ru/m/tankz/network/NetworkManager.hx index ddd0e6f..5135741 100644 --- a/src/client/haxe/ru/m/tankz/network/NetworkManager.hx +++ b/src/client/haxe/ru/m/tankz/network/NetworkManager.hx @@ -1,10 +1,10 @@ package ru.m.tankz.network; +import ru.m.tankz.proto.core.UserProto; import haxework.signal.Signal; import ru.m.connect.IConnection; import ru.m.tankz.control.Control; import ru.m.tankz.proto.core.GameProto; -import ru.m.tankz.proto.game.GameActionTypeProto; import ru.m.tankz.proto.game.GameChangeProto; import ru.m.tankz.proto.pack.CreateGameRequest; import ru.m.tankz.proto.pack.JoinGameRequest; @@ -31,6 +31,9 @@ enum ConnectionState { class NetworkManager { public var state(default, null):ConnectionState; + public var game(default, null):GameProto; + public var user(default, null):UserProto; + public var stateSignal:Signal; public var listGameSignal:Signal>; public var gameSignal:Signal; @@ -80,8 +83,8 @@ class NetworkManager { connection.send(new Request().setListGame(new ListGameRequest())); } - public function createGame(type:String):Void { - connection.send(new Request().setCreateGame(new CreateGameRequest().setType(type))); + public function createGame(type:String, level:Int):Void { + connection.send(new Request().setCreateGame(new CreateGameRequest().setType(type).setLevel(level))); } public function joinGame(gameId:Int):Void { @@ -116,9 +119,10 @@ class NetworkManager { private function onResponse(packet:Response):Void { if (packet.hasLogin()) { + this.user = packet.login.user; var user = { - uuid: packet.login.user.uuid, - name: packet.login.user.name, + uuid: this.user.uuid, + name: this.user.name, }; storage.user = user; updateState(ONLINE(user)); @@ -128,11 +132,17 @@ class NetworkManager { } else if (packet.hasListGame()) { listGameSignal.emit(packet.listGame.games); } else if (packet.hasCreateGame()) { + game = packet.createGame.game; gameSignal.emit(packet.createGame.game); } else if (packet.hasJoinGame()) { + game = packet.createGame.game; gameSignal.emit(packet.joinGame.game); } else if (packet.hasLeaveGame()) { - gameSignal.emit(null); + if (packet.leaveGame.user.uuid == user.uuid) { + gameSignal.emit(null); + } else { + gameSignal.emit(packet.leaveGame.game); + } } else if (packet.hasStartGame()) { gameSignal.emit(packet.startGame.game); } diff --git a/src/client/haxe/ru/m/tankz/view/ClientView.yaml b/src/client/haxe/ru/m/tankz/view/ClientView.yaml index fc8edc7..b648fdf 100755 --- a/src/client/haxe/ru/m/tankz/view/ClientView.yaml +++ b/src/client/haxe/ru/m/tankz/view/ClientView.yaml @@ -17,3 +17,5 @@ views: $type: ru.m.tankz.view.RecordFrame - id: game_list $type: ru.m.tankz.view.network.GameListFrame + - id: game_room + $type: ru.m.tankz.view.network.GameRoomFrame diff --git a/src/client/haxe/ru/m/tankz/view/network/GameListFrame.hx b/src/client/haxe/ru/m/tankz/view/network/GameListFrame.hx index 17c8a74..c1b0a4f 100644 --- a/src/client/haxe/ru/m/tankz/view/network/GameListFrame.hx +++ b/src/client/haxe/ru/m/tankz/view/network/GameListFrame.hx @@ -1,10 +1,10 @@ package ru.m.tankz.view.network; -import haxework.view.list.VListView; -import ru.m.tankz.proto.core.GameProto; -import ru.m.tankz.network.NetworkManager; import haxework.view.frame.FrameSwitcher; +import haxework.view.list.VListView; import haxework.view.VGroupView; +import ru.m.tankz.network.NetworkManager; +import ru.m.tankz.proto.core.GameProto; @:template class GameListFrame extends VGroupView { public static inline var ID = "game_list"; @@ -16,14 +16,37 @@ import haxework.view.VGroupView; public function onShow():Void { network.listGameSignal.connect(onGameList); + network.gameSignal.connect(onGame); + network.stateSignal.connect(onState); network.listGame(); } public function onHide():Void { network.listGameSignal.disconnect(onGameList); + network.gameSignal.disconnect(onGame); + network.stateSignal.disconnect(onState); + } + + private function create():Void { + network.createGame("classic", 0); + } + + private function onState(state:ConnectionState):Void { + switch state { + case ONLINE(_): + case _: switcher.change(StartFrame.ID); + } } private function onGameList(data:Array):Void { games.data = data; } + + private function onGame(game:GameProto):Void { + switcher.change(GameRoomFrame.ID); + } + + private function selectGame(game:GameProto):Void { + network.joinGame(game.id); + } } diff --git a/src/client/haxe/ru/m/tankz/view/network/GameListFrame.yaml b/src/client/haxe/ru/m/tankz/view/network/GameListFrame.yaml index b8be10a..9f75d9c 100644 --- a/src/client/haxe/ru/m/tankz/view/network/GameListFrame.yaml +++ b/src/client/haxe/ru/m/tankz/view/network/GameListFrame.yaml @@ -6,14 +6,19 @@ views: views: - id: header $type: haxework.view.LabelView + geometry.margin.bottom: 20 skinId: text.header text: Games List + - $type: haxework.view.ButtonView + skinId: button.simple + text: Create + +onPress: $code:create() - id: games $type: haxework.view.list.VListView geometry.size.stretch: true - geometry.margin.top: 20 factory: $code:ru.m.tankz.view.network.GameItemView.factory geometry.margin: 10 + +onItemSelect: $code:function(item) selectGame(item.data) - $type: haxework.view.HGroupView skinId: panel views: diff --git a/src/client/haxe/ru/m/tankz/view/network/GameRoomFrame.hx b/src/client/haxe/ru/m/tankz/view/network/GameRoomFrame.hx new file mode 100644 index 0000000..4637e68 --- /dev/null +++ b/src/client/haxe/ru/m/tankz/view/network/GameRoomFrame.hx @@ -0,0 +1,45 @@ +package ru.m.tankz.view.network; + +import haxework.view.frame.FrameSwitcher; +import haxework.view.TextView; +import haxework.view.VGroupView; +import ru.m.tankz.network.NetworkManager; +import ru.m.tankz.proto.core.GameProto; + +@:template class GameRoomFrame extends VGroupView { + + public static inline var ID = "game_room"; + + @:view var info:TextView; + + @:provide var switcher:FrameSwitcher; + @:provide var network:NetworkManager; + + private function refresh(game:GameProto):Void { + info.text = '${game.id}. ${game.type}'; + } + + public function onShow():Void { + refresh(network.game); + network.gameSignal.connect(onGame); + network.stateSignal.connect(onState); + } + + public function onHide():Void { + network.gameSignal.disconnect(onGame); + network.stateSignal.disconnect(onState); + network.leaveGame(); + } + + private function onGame(game:GameProto):Void { + refresh(game); + } + + private function onState(state:ConnectionState):Void { + switch state { + case ONLINE(_): + case _: switcher.change(StartFrame.ID); + } + } + +} diff --git a/src/client/haxe/ru/m/tankz/view/network/GameRoomFrame.yaml b/src/client/haxe/ru/m/tankz/view/network/GameRoomFrame.yaml new file mode 100644 index 0000000..741d658 --- /dev/null +++ b/src/client/haxe/ru/m/tankz/view/network/GameRoomFrame.yaml @@ -0,0 +1,20 @@ +--- +views: + - $type: haxework.view.VGroupView + skinId: container + geometry.padding: 20 + views: + - id: header + $type: haxework.view.LabelView + geometry.margin.bottom: 20 + skinId: text.header + text: Game Room + - id: info + $type: haxework.view.TextView + geometry.size.stretch: true + - $type: haxework.view.HGroupView + skinId: panel + views: + - $type: haxework.view.ButtonView + skinId: button.close + +onPress: $code:switcher.change('start') diff --git a/src/common/haxe/ru/m/Timer.hx b/src/common/haxe/ru/m/Timer.hx new file mode 100644 index 0000000..c1732ac --- /dev/null +++ b/src/common/haxe/ru/m/Timer.hx @@ -0,0 +1,60 @@ +package ru.m; + +#if neko + +import haxe.Log; +import haxe.PosInfos; +import neko.vm.Thread; + +class Timer { + + 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 function stop() { + stopped = true; + } + + public dynamic function run() {} + + public static function delay(f:Void -> Void, time_ms:Int) { + var t = new Timer(time_ms); + t.run = function() { + t.stop(); + f(); + }; + return t; + } + + public static function measure(f:Void -> T, ?pos:PosInfos):T { + var t0 = stamp(); + var r = f(); + Log.trace((stamp() - t0) + "s", pos); + return r; + } + + public static inline function stamp():Float { + return Sys.time(); + } +} + +#else + +typedef Timer = haxe.Timer; + +#end diff --git a/src/common/haxe/ru/m/tankz/core/Modificator.hx b/src/common/haxe/ru/m/tankz/core/Modificator.hx index d53b14c..bf9a871 100644 --- a/src/common/haxe/ru/m/tankz/core/Modificator.hx +++ b/src/common/haxe/ru/m/tankz/core/Modificator.hx @@ -1,7 +1,7 @@ package ru.m.tankz.core; -import haxe.Timer; import haxework.signal.Signal; +import ru.m.Timer; class Modificator extends Signal2 { diff --git a/src/common/haxe/ru/m/tankz/game/GameRunner.hx b/src/common/haxe/ru/m/tankz/game/GameRunner.hx index 1462523..e2f9672 100644 --- a/src/common/haxe/ru/m/tankz/game/GameRunner.hx +++ b/src/common/haxe/ru/m/tankz/game/GameRunner.hx @@ -1,7 +1,6 @@ package ru.m.tankz.game; import haxe.ds.Option; -import haxe.Timer; import haxework.signal.Signal; import ru.m.geom.Line; import ru.m.geom.Point; @@ -18,6 +17,7 @@ import ru.m.tankz.game.GameEvent; import ru.m.tankz.game.IGame; import ru.m.tankz.game.Spawner; import ru.m.tankz.Type; +import ru.m.Timer; class GameRunner implements EngineListener implements GameListener { @:provide var controlFactory:IControlFactory; diff --git a/src/common/haxe/ru/m/tankz/game/GameState.hx b/src/common/haxe/ru/m/tankz/game/GameState.hx index 69e6fdb..5b8ffba 100644 --- a/src/common/haxe/ru/m/tankz/game/GameState.hx +++ b/src/common/haxe/ru/m/tankz/game/GameState.hx @@ -13,7 +13,10 @@ class State { public var hits:Int; public function new() { - + score = 0; + frags = 0; + shots = 0; + hits = 0; } public function add(state:State) { diff --git a/src/common/haxe/ru/m/tankz/game/Spawner.hx b/src/common/haxe/ru/m/tankz/game/Spawner.hx index 7523033..5f6b5a8 100644 --- a/src/common/haxe/ru/m/tankz/game/Spawner.hx +++ b/src/common/haxe/ru/m/tankz/game/Spawner.hx @@ -1,9 +1,8 @@ package ru.m.tankz.game; -import haxe.Timer; import ru.m.tankz.config.Config; import ru.m.tankz.Type; - +import ru.m.Timer; typedef SpawnTask = { var point:SpawnPoint; diff --git a/src/common/proto/pack.proto b/src/common/proto/pack.proto index 807d06e..66ecb25 100644 --- a/src/common/proto/pack.proto +++ b/src/common/proto/pack.proto @@ -50,6 +50,7 @@ message JoinGameRequest { message JoinGameResponse { ru.m.tankz.proto.core.GameProto game = 1; + ru.m.tankz.proto.core.UserProto user = 2; } // Leave Game @@ -57,6 +58,7 @@ message LeaveGameRequest {} message LeaveGameResponse { ru.m.tankz.proto.core.GameProto game = 1; + ru.m.tankz.proto.core.UserProto user = 2; } // Start Game @@ -89,5 +91,7 @@ message Response { JoinGameResponse joinGame = 5; LeaveGameResponse leaveGame = 6; StartGameResponse startGame = 7; + + ErrorResponse error = 999; } } diff --git a/src/server/haxe/ru/m/tankz/server/Server.hx b/src/server/haxe/ru/m/tankz/server/Server.hx index e6da764..0af0ae2 100755 --- a/src/server/haxe/ru/m/tankz/server/Server.hx +++ b/src/server/haxe/ru/m/tankz/server/Server.hx @@ -6,8 +6,12 @@ import haxework.provider.Provider; import neko.net.ThreadServer; import ru.m.tankz.bundle.IConfigBundle; import ru.m.tankz.bundle.ILevelBundle; +import ru.m.tankz.control.IControlFactory; +import ru.m.tankz.control.NoneControlFactory; import ru.m.tankz.server.bundle.ServerConfigBundle; import ru.m.tankz.server.bundle.ServerLevelBundle; +import ru.m.tankz.server.game.GameManager; +import ru.m.tankz.server.game.IGameManager; import ru.m.tankz.server.session.GameSession; import sys.net.Socket; @@ -35,6 +39,8 @@ class Server extends ThreadServer { session.pushData(bytes); } + @:provide static var gameManager:IGameManager; + public static function main() { L.push(new TraceLogger()); #if debug @@ -44,7 +50,8 @@ class Server extends ThreadServer { L.i(TAG, 'Build: ${CompilationOption.get("build")}'); Provider.setFactory(IConfigBundle, ServerConfigBundle); Provider.setFactory(ILevelBundle, ServerLevelBundle); - //Provider.setFactory(IControlFactory, NoneControlFactory); + Provider.setFactory(IControlFactory, NoneControlFactory); + gameManager = new GameManager(); var host:String = Sys.args().length > 0 ? Sys.args()[0] : "localhost"; var port:Int = Sys.args().length > 1 ? Std.parseInt(Sys.args()[1]) : 5000; var wserver = new Server(); diff --git a/src/server/haxe/ru/m/tankz/server/db/DbProvider.hx b/src/server/haxe/ru/m/tankz/server/db/DbProvider.hx deleted file mode 100755 index 0c31186..0000000 --- a/src/server/haxe/ru/m/tankz/server/db/DbProvider.hx +++ /dev/null @@ -1,37 +0,0 @@ -package ru.m.tankz.server.db; - -import ru.m.tankz.db.Orm; -import ru.m.tankz.proto.Person; -import ru.m.tankz.proto.Account; - -class DbProvider { - - private var orm:Orm; - - public function new() { - var db = new orm.Db("mysql://shmyga:password@localhost:3306/armageddon"); - orm = new Orm(db); - } - - public function getAccount(login:String, password:String):Null { - var account = null; - var accountModel = orm.account.getByLogin(login); - if (accountModel != null && accountModel.password == password) { - account = new Account().setLogin(accountModel.login); - var personsModels = orm.person.getByAccount_id(accountModel.id); - for (personModel in personsModels) { - account.addPersons(new Person().setId(personModel.id).setName(personModel.name)); - } - } - return account; - } - - public function getPerson(person_id:Int):Null { - var person = null; - var personModel = orm.person.get(person_id); - if (personModel != null) { - person = new Person().setId(personModel.id).setName(personModel.name); - } - return person; - } -} diff --git a/src/server/haxe/ru/m/tankz/server/game/GameManager.hx b/src/server/haxe/ru/m/tankz/server/game/GameManager.hx index e9f0057..b10dd8a 100644 --- a/src/server/haxe/ru/m/tankz/server/game/GameManager.hx +++ b/src/server/haxe/ru/m/tankz/server/game/GameManager.hx @@ -1,149 +1,79 @@ package ru.m.tankz.server.game; -import ru.m.tankz.game.Game; -import ru.m.tankz.game.IGame; -import ru.m.tankz.game.GameState; -import ru.m.tankz.preset.ClassicGame; -import ru.m.tankz.proto.pack.StartGameResponse; -import ru.m.tankz.proto.pack.LeaveGameResponse; -import ru.m.tankz.proto.pack.JoinGameResponse; -import ru.m.tankz.proto.pack.ListGameResponse; -import ru.m.tankz.proto.pack.CreateGameResponse; -import ru.m.tankz.proto.pack.Response; import ru.m.tankz.proto.core.GameStateProto; -import ru.m.tankz.proto.core.GameInfoProto; +import ru.m.tankz.game.GameState; +import ru.m.tankz.proto.core.GameProto; import ru.m.tankz.proto.core.UserProto; -import ru.m.tankz.server.session.Thread; -import ru.m.tankz.server.session._Session; +import ru.m.tankz.server.game.IGameManager; +@:dispatcher(GameManagerListener) class GameManager implements IGameManager { + public var games(default, null):Array; + public var gamesById(default, null):Map; + public var gamesByCreator(default, null):Map; + public var gamesByUser(default, null):Map; -/** - * - **/ -class NekoTimer { + private var counter:Int; - 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 function new() { + counter = 0; + games = []; + gamesById = new Map(); + gamesByCreator = new Map(); + gamesByUser = new Map(); } - public dynamic function run() {} + public function create(user:UserProto, type:String, level:Int):ServerGame { + if (gamesByCreator.exists(user.uuid)) { + delete(gamesByCreator[user.uuid].proto.id); + } + var proto = new GameProto() + .setId(++counter) + .setCreator(user) + .setType(type) + .setLevel(level) + .setPlayers([user]); + var game = new ServerGame(proto); + games.push(game); + gamesById[game.proto.id] = game; + gamesByCreator[game.proto.creator.uuid] = game; + createSignal.emit(game); + return game; + } - public function stop() { - stopped = true; - } -} - -/** - * - **/ -class GameManager { - - public static var byGameId:Map = new Map(); - public static var byPersonId:Map = new Map(); - public static var subscribers:Map = new Map(); - - private static var idCounter:Int = 0; - - public var gameInfo(default, null):GameInfoProto; - public var game(default, null):IGame; - - private var timer:NekoTimer; - - //private var changes:Array = new Array(); - - public function new(creator:UserProto) { - gameInfo = new GameInfoProto() - .setId(idCounter++) - .setState(GameStateProto.READY) - .setCreator(creator); - gameInfo.addPlayers(creator); - byGameId.set(gameInfo.id, this); - byPersonId.set(creator.uuid, this); - broadcast(new Response().setCreateGame(new CreateGameResponse().setGame(gameInfo))); - broadcastGames(); - } - - public static function getReadyGames():Array { - return Lambda.array(Lambda.filter(Lambda.map(GameManager.byGameId, function(gm) return gm.gameInfo), function(game) return game.state == GameStateProto.READY)); - } - - public function broadcastGames() { - var packet = new Response().setListGame(new ListGameResponse().setGames(getReadyGames())); - for (personId in subscribers.keys()) { - var session:_Session = _Session.sessions.get(personId); - session.send(packet); - } - } - - public function broadcast(packet:Response) { - for (player in gameInfo.players) { - var session:_Session = _Session.sessions.get(player.uuid); - session.send(packet); - } - } - - public function join(user:UserProto) { - gameInfo.addPlayers(user); - byPersonId.set(user.uuid, this); - broadcast(new Response().setJoinGame(new JoinGameResponse().setGame(gameInfo))); - } - - public function leave(user:UserProto) { - gameInfo.setPlayers(gameInfo.players.filter(function(p:UserProto) return p.uuid != user.uuid)); - byPersonId.remove(user.uuid); - var packet = new Response().setLeaveGame(new LeaveGameResponse().setGame(gameInfo)); - _Session.sessions.get(user.uuid).send(packet); - if (gameInfo.players.length == 0/* || person.id == game.creator.id*/) { - stop(); - } else { - broadcast(packet); - } - } - - public function start() { - gameInfo.setState(GameStateProto.STARTED); - var state = new GameState(ClassicGame.TYPE); - game = new Game(state); - //game.start(new GameState(ClassicGame.TYPE,ClassicGame.PLAYER1)); - timer = new NekoTimer(30); - timer.run = update; - broadcast(new Response().setStartGame(new StartGameResponse().setGame(gameInfo))); - } - - public function stop() { - gameInfo.setState(GameStateProto.ENDED); - gameInfo.setPlayers([]); - byGameId.remove(gameInfo.id); - for (p in gameInfo.players) byPersonId.remove(p.uuid); - if (timer != null) { - timer.stop(); - timer = null; - } - broadcast(new Response().setLeaveGame(new LeaveGameResponse().setGame(gameInfo))); - broadcastGames(); - } - - private function update() { - //game.engine.update(); - /*var changes = engine.update(); - changes = this.changes.concat(changes); - this.changes = []; - if (changes.length > 0) { - broadcast(new GameUpdateResponse().setChanges(changes)); - }*/ + public function join(gameId:Int, user:UserProto):Void { + if (gamesById.exists(gameId)) { + var game = gamesById[gameId]; + game.proto.players.push(user); + gamesByUser[user.uuid] = game; + changeSignal.emit(game, JOIN(user)); + } + } + + public function delete(gameId:Int):Void { + if (gamesById.exists(gameId)) { + var game = gamesById[gameId]; + games.remove(game); + gamesById.remove(game.proto.id); + gamesByCreator.remove(game.proto.creator.uuid); + deleteSignal.emit(game); + } + } + + public function leave(user:UserProto):Void { + if (gamesByCreator.exists(user.uuid)) { + delete(gamesByCreator[user.uuid].proto.id); + } else if (gamesByUser.exists(user.uuid)) { + var game = gamesByUser[user.uuid]; + game.proto.setPlayers(game.proto.players.filter(function(player) return player.uuid != user.uuid)); + changeSignal.emit(game, LEAVE(user)); + } + } + + public function start(gameId:Int):Void { + if (gamesById.exists(gameId)) { + var game = gamesById[gameId]; + game.proto.setState(GameStateProto.STARTED); + changeSignal.emit(game, START); + } } } diff --git a/src/server/haxe/ru/m/tankz/server/game/IGameManager.hx b/src/server/haxe/ru/m/tankz/server/game/IGameManager.hx new file mode 100644 index 0000000..cf18c92 --- /dev/null +++ b/src/server/haxe/ru/m/tankz/server/game/IGameManager.hx @@ -0,0 +1,35 @@ +package ru.m.tankz.server.game; + +import haxework.signal.Signal; +import ru.m.tankz.proto.core.UserProto; + +enum GameChange { + JOIN(user:UserProto); + LEAVE(user:UserProto); + START(); +} + +interface GameManagerListener { + public function onCreate(game:ServerGame):Void; + public function onChange(game:ServerGame, change:GameChange):Void; + public function onDelete(game:ServerGame):Void; +} + +interface IGameManager { + public var games(default, null):Array; + public var gamesById(default, null):Map; + public var gamesByCreator(default, null):Map; + + private var createSignal(default, null):Signal; + private var changeSignal(default, null):Signal2; + private var deleteSignal(default, null):Signal; + + public function connect(listener:GameManagerListener):Void; + public function disconnect(listener:GameManagerListener):Void; + + public function create(user:UserProto, type:String, level:Int):ServerGame; + public function delete(gameId:Int):Void; + public function join(gameId:Int, user:UserProto):Void; + public function leave(user:UserProto):Void; + public function start(gameId:Int):Void; +} diff --git a/src/server/haxe/ru/m/tankz/server/game/ServerGame.hx b/src/server/haxe/ru/m/tankz/server/game/ServerGame.hx new file mode 100644 index 0000000..2f5ea5a --- /dev/null +++ b/src/server/haxe/ru/m/tankz/server/game/ServerGame.hx @@ -0,0 +1,18 @@ +package ru.m.tankz.server.game; + +import ru.m.tankz.game.Game; +//import ru.m.tankz.game.GameRunner; +import ru.m.tankz.game.GameState; +import ru.m.tankz.proto.core.GameProto; + +class ServerGame extends Game { + + //public var runner(default, null):GameRunner; + public var proto(default, null):GameProto; + + public function new(proto:GameProto) { + super(new GameState(proto.type, 0, proto.level)); + this.proto = proto; + //runner = new GameRunner(this); + } +} diff --git a/src/server/haxe/ru/m/tankz/server/session/GameSession.hx b/src/server/haxe/ru/m/tankz/server/session/GameSession.hx index f952ecc..264aeaa 100644 --- a/src/server/haxe/ru/m/tankz/server/session/GameSession.hx +++ b/src/server/haxe/ru/m/tankz/server/session/GameSession.hx @@ -2,69 +2,128 @@ package ru.m.tankz.server.session; import com.hurlant.crypto.extra.UUID; import com.hurlant.crypto.prng.Random; -import ru.m.tankz.proto.core.GameProto; +import haxework.log.BaseLogger.LoggerUtil; import ru.m.tankz.proto.core.UserProto; -import ru.m.tankz.proto.pack.CreateGameRequest; import ru.m.tankz.proto.pack.CreateGameResponse; -import ru.m.tankz.proto.pack.ListGameRequest; +import ru.m.tankz.proto.pack.ErrorResponse; +import ru.m.tankz.proto.pack.JoinGameResponse; +import ru.m.tankz.proto.pack.LeaveGameResponse; import ru.m.tankz.proto.pack.ListGameResponse; -import ru.m.tankz.proto.pack.LoginRequest; import ru.m.tankz.proto.pack.LoginResponse; -import ru.m.tankz.proto.pack.LogoutRequest; import ru.m.tankz.proto.pack.LogoutResponse; import ru.m.tankz.proto.pack.Request; import ru.m.tankz.proto.pack.Response; +import ru.m.tankz.proto.pack.StartGameResponse; +import ru.m.tankz.server.game.IGameManager; +import ru.m.tankz.server.game.ServerGame; import sys.net.Socket; -class GameSession extends ProtoSession { +class GameSession extends ProtoSession implements GameManagerListener { private static inline var TAG = "Session"; + @:provide static var gameManager:IGameManager; + public var user(default, null):UserProto; + public var gameId(default, null):Int; public function new(socket:Socket) { super(socket, Request); } - private function onLogin(request:LoginRequest):LoginResponse { - user = new UserProto() - .setUuid(request.uuid != null ? request.uuid : UUID.generateRandom(new Random()).toString()) - .setName(request.name); - return new LoginResponse().setUser(user); + private function sendError(code:Int, message:String):Void { + send(new Response().setError(new ErrorResponse().setCode(code).setMessage(message))); } - private function onLogout(request:LogoutRequest):LogoutResponse { - return new LogoutResponse(); + private function listGame():ListGameResponse { + var games = gameManager.games; + return new ListGameResponse().setGames([for (game in games) game.proto]); } - private function onCreateGame(request:CreateGameRequest):CreateGameResponse { - var game = new GameProto() - .setId(1) - .setCreator(user) - .setType(request.type) - .setLevel(request.level); - return new CreateGameResponse().setGame(game); - } - - private function onListGame(request:ListGameRequest):ListGameResponse { - // ToDo: test games - var games = [for (i in 0...5) new GameProto() - .setId(i) - .setCreator(user) - .setType("classic") - .setLevel(i)]; - return new ListGameResponse().setGames(games); + override public function send(packet:Response):Void { + L.d(TAG, 'send: ${user == null ? '' : user.name} - ${packet}'); + super.send(packet); } override private function onRequest(request:Request):Void { - L.d(TAG, 'onRequest: ${request}'); - if (request.hasLogin()) { - send(new Response().setLogin(onLogin(request.login))); - } else if (request.hasLogout()) { - send(new Response().setLogout(onLogout(request.logout))); - } else if (request.hasCreateGame()) { - send(new Response().setCreateGame(onCreateGame(request.createGame))); - } else if (request.hasListGame()) { - send(new Response().setListGame(onListGame(request.listGame))); + L.d(TAG, 'onRequest: ${user == null ? '' : user.name} - ${request}'); + try { + if (!request.hasLogin() && user == null) { + throw "Not Authorized"; + } + // login + if (request.hasLogin()) { + user = new UserProto() + .setUuid(request.login.uuid != null ? request.login.uuid : UUID.generateRandom(new Random()).toString()) + .setName(request.login.name); + gameManager.connect(this); + send(new Response().setLogin(new LoginResponse().setUser(user))); + // logout + } else if (request.hasLogout()) { + gameManager.disconnect(this); + if (user != null) { + gameManager.leave(user); + user = null; + } + send(new Response().setLogout(new LogoutResponse())); + // create + } else if (request.hasCreateGame()) { + var game = gameManager.create(user, request.createGame.type, request.createGame.level); + gameId = game.proto.id; + send(new Response().setCreateGame(new CreateGameResponse().setGame(game.proto))); + // list + } else if (request.hasListGame()) { + send(new Response().setListGame(listGame())); + // join + } else if (request.hasJoinGame()) { + gameId = request.joinGame.gameId; + gameManager.join(request.joinGame.gameId, user); + // leave + } else if (request.hasLeaveGame()) { + gameManager.leave(user); + // start + } else if (request.hasStartGame()) { + gameManager.start(gameId); + } + } catch (error:Dynamic) { + L.e(TAG, "onRequest ", error); + sendError(500, LoggerUtil.printError(error)); } } + + override public function disconnect():Void { + gameId = null; + gameManager.disconnect(this); + if (user != null) { + gameManager.leave(user); + user = null; + } + super.disconnect(); + } + + public function onCreate(game:ServerGame):Void { + send(new Response().setListGame(listGame())); + } + + public function onChange(game:ServerGame, change:GameChange):Void { + if (gameId == game.proto.id) { + switch change { + case JOIN(user): + if (user.uuid == this.user.uuid) { + gameId = gameId = game.proto.id; + } + send(new Response().setJoinGame(new JoinGameResponse().setGame(game.proto).setUser(user))); + case LEAVE(user): + if (user.uuid == this.user.uuid) { + gameId = null; + } + send(new Response().setLeaveGame(new LeaveGameResponse().setGame(game.proto).setUser(user))); + case START: + send(new Response().setStartGame(new StartGameResponse().setGame(game.proto))); + } + } + } + + public function onDelete(game:ServerGame):Void { + send(new Response().setListGame(listGame())); + } } diff --git a/src/server/haxe/ru/m/tankz/server/session/Thread.hx b/src/server/haxe/ru/m/tankz/server/session/Thread.hx deleted file mode 100644 index 518ff4c..0000000 --- a/src/server/haxe/ru/m/tankz/server/session/Thread.hx +++ /dev/null @@ -1,112 +0,0 @@ -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); -} diff --git a/src/server/haxe/ru/m/tankz/server/session/_Session.hx b/src/server/haxe/ru/m/tankz/server/session/_Session.hx deleted file mode 100755 index 9727a66..0000000 --- a/src/server/haxe/ru/m/tankz/server/session/_Session.hx +++ /dev/null @@ -1,156 +0,0 @@ -package ru.m.tankz.server.session; - -import ru.m.tankz.proto.pack.GameResponse; -import ru.m.tankz.proto.pack.GameRequest; -import com.hurlant.crypto.extra.UUID; -import com.hurlant.crypto.prng.Random; -import haxe.io.Bytes; -import ru.m.connect.IConnection; -import ru.m.connect.neko.NekoConnection; -import ru.m.connect.neko.NekoWSConnection; -import ru.m.tankz.proto.core.UserProto; -import ru.m.tankz.proto.pack.CreateGameRequest; -import ru.m.tankz.proto.pack.CreateGameResponse; -import ru.m.tankz.proto.pack.JoinGameRequest; -import ru.m.tankz.proto.pack.JoinGameResponse; -import ru.m.tankz.proto.pack.LeaveGameRequest; -import ru.m.tankz.proto.pack.LeaveGameResponse; -import ru.m.tankz.proto.pack.ListGameRequest; -import ru.m.tankz.proto.pack.ListGameResponse; -import ru.m.tankz.proto.pack.LoginRequest; -import ru.m.tankz.proto.pack.LoginResponse; -import ru.m.tankz.proto.pack.LogoutRequest; -import ru.m.tankz.proto.pack.LogoutResponse; -import ru.m.tankz.proto.pack.Request; -import ru.m.tankz.proto.pack.Response; -import ru.m.tankz.proto.pack.StartGameRequest; -import ru.m.tankz.proto.pack.StartGameResponse; -import ru.m.tankz.server.game.GameManager; -import sys.net.Socket; - - -typedef ServerConnection = IConnection; - -class _Session { - private static inline var TAG = 'Session'; - - private static var POLICY_FILE:String = [ - '', - '', - '', - '', - '', - '' - ].join('\n'); - - public static var sessions:Map = new Map(); - - public var user(default, null):UserProto; - public var gameId(default, null):Int = -1; - - public var connection(default, null):ServerConnection; - - private var socket:Socket; - - public function new(socket:Socket) { - this.socket = socket; - } - - public function send(packet:Response):Void { - connection.send(packet); - } - - public function pushData(bytes:Bytes):Void { - if (connection != null) { - connection.pushData(bytes); - } else { - var str:String = bytes.getString(0, bytes.length); - if (str == '' + String.fromCharCode(0)) { - L.d(TAG, 'policy-file-request'); - socket.output.writeString(POLICY_FILE + String.fromCharCode(0)); - socket.output.flush(); - return; - } - if (StringTools.startsWith(str, "GET")) { - connection = new NekoWSConnection(socket, Request); - } else { - connection = new NekoConnection(socket, Request); - } - connection.handler.connect(onConnectionEvent); - connection.receiveHandler.connect(onRequest); - connection.pushData(bytes); - } - } - - private function onConnectionEvent(event:ConnectionEvent):Void { - L.d(TAG, '${event}'); - } - - public function onRequest(request:Request):Void { - if (request.hasLogin()) { - send(new Response().setLogin(login(request.login))); - } else if (request.hasLogout()) { - send(new Response().setLogout(logout(request.logout))); - } else if (request.hasListGame()) { - send(new Response().setListGame(listGame(request.listGame))); - } else if (request.hasCreateGame()) { - send(new Response().setCreateGame(createGame(request.createGame))); - } else if (request.hasJoinGame()) { - send(new Response().setJoinGame(joinGame(request.joinGame))); - } else if (request.hasLeaveGame()) { - send(new Response().setLeaveGame(leaveGame(request.leaveGame))); - } else if (request.hasStartGame()) { - send(new Response().setStartGame(startGame(request.startGame))); - }else if (request.hasGame()) { - send(new Response().setGame(game(request.game))); - } - } - - private function login(request:LoginRequest):LoginResponse { - user = new UserProto() - .setUuid(request.uuid != null ? request.uuid : UUID.generateRandom(new Random()).toString()) - .setName(request.name); - sessions.set(user.uuid, this); - GameManager.subscribers.set(user.uuid, true); - return new LoginResponse().setUser(user); - } - - private function logout(request:LogoutRequest):LogoutResponse { - GameManager.subscribers.remove(user.uuid); - user = null; - return new LogoutResponse(); - } - - private function listGame(request:ListGameRequest):ListGameResponse { - return new ListGameResponse().setGames(GameManager.getReadyGames()); - } - - private function createGame(request:CreateGameRequest):CreateGameResponse { - var gameManager:GameManager = new GameManager(user); - return new CreateGameResponse().setGame(gameManager.gameInfo); - } - - private function joinGame(request:JoinGameRequest):JoinGameResponse { - var gameManager:GameManager = GameManager.byGameId.get(request.gameId); - gameManager.join(user); - return new JoinGameResponse().setGame(gameManager.gameInfo); - } - - private function leaveGame(request:LeaveGameRequest):LeaveGameResponse { - var gameManager:GameManager = GameManager.byPersonId.get(user.uuid); - gameManager.leave(user); - return new LeaveGameResponse().setGame(gameManager.gameInfo); - } - - private function startGame(request:StartGameRequest):StartGameResponse { - var gameManager:GameManager = GameManager.byPersonId.get(user.uuid); - gameManager.start(); - return new StartGameResponse().setGame(gameManager.gameInfo); - } - - private function game(request:GameRequest):GameResponse { - var gameManager:GameManager = GameManager.byPersonId.get(user.uuid); - return new GameResponse(); - //return new GameResponse().setGame(gameManager.game.export()); - } -}