diff --git a/src/client/haxe/ru/m/tankz/view/ResultFrame.hx b/src/client/haxe/ru/m/tankz/view/ResultFrame.hx index f1e019d..461d0ea 100644 --- a/src/client/haxe/ru/m/tankz/view/ResultFrame.hx +++ b/src/client/haxe/ru/m/tankz/view/ResultFrame.hx @@ -31,7 +31,7 @@ import ru.m.tankz.preset.DotaGame; var tankConfig = resultState.config.getTank(tankType); view.tank = tankConfig == null ? 'ba' : tankConfig.skin; view.color = resultState.config.getColor(player.id); - view.live = player.frags; + view.life = player.frags; view.score = player.score; return view; } diff --git a/src/client/haxe/ru/m/tankz/view/classic/ClassicGamePanel.hx b/src/client/haxe/ru/m/tankz/view/classic/ClassicGamePanel.hx index 6533145..dfff1fa 100644 --- a/src/client/haxe/ru/m/tankz/view/classic/ClassicGamePanel.hx +++ b/src/client/haxe/ru/m/tankz/view/classic/ClassicGamePanel.hx @@ -2,10 +2,11 @@ package ru.m.tankz.view.classic; import haxework.view.LabelView; import haxework.view.VGroupView; +import ru.m.tankz.game.GameState; +import ru.m.tankz.preset.ClassicGame; +import ru.m.tankz.Type.PlayerId; import ru.m.tankz.view.common.IGamePanel; import ru.m.tankz.view.common.LifeView; -import ru.m.tankz.game.Game; -import ru.m.tankz.preset.ClassicGame; @:template class ClassicGamePanel extends VGroupView implements IGamePanel { @@ -14,26 +15,27 @@ import ru.m.tankz.preset.ClassicGame; @:view var player2:LifeView; @:view var level:LabelView; - public var game:Game; + private var player1Id:PlayerId = new PlayerId(ClassicGame.HUMAN, 0); + private var player2Id:PlayerId = new PlayerId(ClassicGame.HUMAN, 1); - private function updateViews():Void { - level.text = 'Level ${game.state.level}'; - bot.live = game.teams[ClassicGame.BOT].life; - player1.live = game.teams[ClassicGame.HUMAN].players[0].state.life; - player1.score = game.teams[ClassicGame.HUMAN].players[0].state.score; - if (game.teams[ClassicGame.HUMAN].players[1] != null) { + public function onGameStart(state:GameState):Void { + level.text = 'Level ${state.level}'; + } + + public function onGameChange(state:GameState):Void { + bot.life = state.getTeamLife(ClassicGame.BOT); + player1.life = state.getPlayerLife(player1Id); + player1.score = state.getPlayerScore(player1Id); + if (true) { player2.visible = true; - player2.live = game.teams[ClassicGame.HUMAN].players[1].state.life; - player2.score = game.teams[ClassicGame.HUMAN].players[1].state.score; + player2.life = state.getPlayerLife(player2Id); + player2.score = state.getPlayerScore(player2Id); } else { player2.visible = false; } } - override public function update():Void { - if (game != null) { - updateViews(); - } - super.update(); + public function onGameComplete(state:GameState):Void { + } } diff --git a/src/client/haxe/ru/m/tankz/view/common/GameFrame.hx b/src/client/haxe/ru/m/tankz/view/common/GameFrame.hx index 31faa17..59f409d 100644 --- a/src/client/haxe/ru/m/tankz/view/common/GameFrame.hx +++ b/src/client/haxe/ru/m/tankz/view/common/GameFrame.hx @@ -44,12 +44,13 @@ class GameFrame extends GroupView { game = new Game(state.type); game.engine.connect(render); game.engine.connect(sound); - game.start(state).then(onGameStateChange).endThen(onGameComplete); + game.connect(this); + if (panel != null) { + game.connect(panel); + } + game.start(state); timer = new Timer(10); timer.run = updateEngine; - if (panel != null) { - panel.game = game; - } content.addEventListener(Event.ENTER_FRAME, _redraw); render.draw(game.engine); sound.play('start'); @@ -68,17 +69,15 @@ class GameFrame extends GroupView { render.reset(); } - private function onGameStateChange(_):Void { - if (panel != null) { - panel.toUpdate(); - } - } + public function onGameStart(state:GameState):Void {} - private function onGameComplete(_):Void { + public function onGameChange(state:GameState):Void {} + + public function onGameComplete(state:GameState):Void { result = state; state = switch game.next() { - case Option.Some(s): s; - case Option.None: null; + case Some(s): s; + case None: null; } stop(); switcher.change(ResultFrame.ID); diff --git a/src/client/haxe/ru/m/tankz/view/common/IGamePanel.hx b/src/client/haxe/ru/m/tankz/view/common/IGamePanel.hx index f29c95d..72d9dfd 100644 --- a/src/client/haxe/ru/m/tankz/view/common/IGamePanel.hx +++ b/src/client/haxe/ru/m/tankz/view/common/IGamePanel.hx @@ -1,8 +1,10 @@ package ru.m.tankz.view.common; -import ru.m.tankz.game.Game; import haxework.view.IView; +import ru.m.tankz.game.GameState; interface IGamePanel extends IView { - public var game:Game; + public function onGameStart(state:GameState):Void; + public function onGameChange(state:GameState):Void; + public function onGameComplete(state:GameState):Void; } diff --git a/src/client/haxe/ru/m/tankz/view/common/LifeView.hx b/src/client/haxe/ru/m/tankz/view/common/LifeView.hx index 0939c63..f1a3b17 100644 --- a/src/client/haxe/ru/m/tankz/view/common/LifeView.hx +++ b/src/client/haxe/ru/m/tankz/view/common/LifeView.hx @@ -1,25 +1,37 @@ package ru.m.tankz.view.common; -import openfl.Assets; -import haxework.view.LabelView; -import haxework.view.ImageView; import haxework.view.HGroupView; +import haxework.view.ImageView; +import haxework.view.LabelView; +import openfl.Assets; +import ru.m.tankz.game.GameState.PlayerState; +import ru.m.tankz.game.GameState; @:template class LifeView extends HGroupView { + @:provide static var currentState:GameState; + @:view("tank") public var tankImage:ImageView; - @:view("live") public var liveLabel:LabelView; + @:view("life") public var lifeLabel:LabelView; @:view("score") public var scoreLabel:LabelView; + public var state(null, set):PlayerState; public var tank(null, set):String; public var color(null, set):Int; - public var live(null, set):Int; + public var life(null, set):Int; public var score(null, set):Int; + private inline function set_state(value:PlayerState):PlayerState { + state = value; + toUpdate(); + return state; + } + private inline function set_tank(value:String):String { - if (value != null) { - tankImage.image = Assets.getBitmapData('resources/image/tank/${value}-0.png'); + if (value != null && value != tank) { + tank = value; + tankImage.image = Assets.getBitmapData('resources/image/tank/${tank}-0.png'); } - return value; + return tank; } private inline function set_color(value:Int):Int { @@ -27,8 +39,8 @@ import haxework.view.HGroupView; return value; } - private inline function set_live(value:Int):Int { - liveLabel.text = '${value}'; + private inline function set_life(value:Int):Int { + lifeLabel.text = '${value}'; return value; } @@ -36,4 +48,21 @@ import haxework.view.HGroupView; scoreLabel.text = '${value}$'; return value; } + + override public function update():Void { + super.update(); + if (state != null) { + var tankConfig = currentState.config.getTank(state.tank); + tank = tankConfig == null ? 'ba' : tankConfig.skin; + color = currentState.config.getColor(state.id); + life = state.life; + score = state.score; + } + } + + public static inline function factory(index:Int, data:PlayerState):LifeView { + var result = new LifeView(); + result.state = data; + return result; + } } diff --git a/src/client/haxe/ru/m/tankz/view/common/LifeView.yaml b/src/client/haxe/ru/m/tankz/view/common/LifeView.yaml index 5a84299..a6499b3 100644 --- a/src/client/haxe/ru/m/tankz/view/common/LifeView.yaml +++ b/src/client/haxe/ru/m/tankz/view/common/LifeView.yaml @@ -3,7 +3,7 @@ layout.margin: 5 views: - id: tank $type: haxework.view.ImageView - - id: live + - id: life $type: haxework.view.LabelView skinId: text.box geometry.size.fixed: [50, 38] diff --git a/src/client/haxe/ru/m/tankz/view/death/DeathGameFrame.hx b/src/client/haxe/ru/m/tankz/view/death/DeathGameFrame.hx index 290341e..2a31e60 100755 --- a/src/client/haxe/ru/m/tankz/view/death/DeathGameFrame.hx +++ b/src/client/haxe/ru/m/tankz/view/death/DeathGameFrame.hx @@ -9,13 +9,13 @@ import ru.m.tankz.view.common.IGamePanel; public static inline var ID = "death.game"; @:view("render") private var renderView(default, null):Render; - //@:view("panel") private var panelView(default, null):IGamePanel; + @:view("panel") private var panelView(default, null):IGamePanel; override private function get_render():Render { return renderView; } - /*override private function get_panel():IGamePanel { + override private function get_panel():IGamePanel { return panelView; - }*/ + } } diff --git a/src/client/haxe/ru/m/tankz/view/death/DeathGameFrame.yaml b/src/client/haxe/ru/m/tankz/view/death/DeathGameFrame.yaml index 1e4149b..4f861b2 100644 --- a/src/client/haxe/ru/m/tankz/view/death/DeathGameFrame.yaml +++ b/src/client/haxe/ru/m/tankz/view/death/DeathGameFrame.yaml @@ -1,8 +1,10 @@ --- skinId: container views: - - $type: haxework.view.VGroupView + - $type: haxework.view.HGroupView layout.margin: 5 views: - id: render $type: ru.m.tankz.render.Render + - id: panel + $type: ru.m.tankz.view.death.DeathGamePanel diff --git a/src/client/haxe/ru/m/tankz/view/death/DeathGamePanel.hx b/src/client/haxe/ru/m/tankz/view/death/DeathGamePanel.hx new file mode 100644 index 0000000..6b983e0 --- /dev/null +++ b/src/client/haxe/ru/m/tankz/view/death/DeathGamePanel.hx @@ -0,0 +1,30 @@ +package ru.m.tankz.view.death; + +import ru.m.tankz.game.GameState; +import haxework.view.DataView; +import haxework.view.LabelView; +import haxework.view.VGroupView; +import ru.m.tankz.game.GameState.PlayerState; +import ru.m.tankz.view.common.IGamePanel; +import ru.m.tankz.view.common.LifeView; + +@:template class DeathGamePanel extends VGroupView implements IGamePanel { + + @:view var level:LabelView; + @:view var players:DataView; + + public function onGameStart(state:GameState):Void { + level.text = 'Level ${state.level}'; + players.data = state.players; + } + + public function onGameChange(state:GameState):Void { + for (view in players.views) { + view.toUpdate(); + } + } + + public function onGameComplete(state:GameState):Void { + + } +} diff --git a/src/client/haxe/ru/m/tankz/view/death/DeathGamePanel.yaml b/src/client/haxe/ru/m/tankz/view/death/DeathGamePanel.yaml new file mode 100644 index 0000000..98b3ca1 --- /dev/null +++ b/src/client/haxe/ru/m/tankz/view/death/DeathGamePanel.yaml @@ -0,0 +1,18 @@ +--- +geometry.padding: 5 +geometry.size.height: 100% +layout.margin: 5 +layout.hAlign: right +views: + - id: level + $type: haxework.view.LabelView + skinId: text.box + geometry.size.height: 38 + geometry.size.width: 100% + - $type: haxework.view.SpriteView + geometry.size.height: 50% + - id: players + $type: haxework.view.DataView + layout: + $type: haxework.view.layout.VerticalLayout + factory: $code:ru.m.tankz.view.common.LifeView.factory diff --git a/src/client/haxe/ru/m/tankz/view/dota/DotaGamePanel.hx b/src/client/haxe/ru/m/tankz/view/dota/DotaGamePanel.hx index bdbec83..5c7ef4f 100644 --- a/src/client/haxe/ru/m/tankz/view/dota/DotaGamePanel.hx +++ b/src/client/haxe/ru/m/tankz/view/dota/DotaGamePanel.hx @@ -1,11 +1,11 @@ package ru.m.tankz.view.dota; +import ru.m.tankz.preset.DotaGame; import haxework.view.HGroupView; import haxework.view.LabelView; +import ru.m.tankz.game.GameState; import ru.m.tankz.view.common.IGamePanel; import ru.m.tankz.view.common.LifeView; -import ru.m.tankz.game.Game; -import ru.m.tankz.preset.DotaGame; @:template class DotaGamePanel extends HGroupView implements IGamePanel { @@ -13,20 +13,18 @@ import ru.m.tankz.preset.DotaGame; @:view var dire:LifeView; @:view var level:LabelView; - public var game:Game; - - private function updateViews():Void { - level.text = 'Level ${game.state.level}'; - radiant.live = game.teams[DotaGame.RADIANT].life; - radiant.score = game.teams[DotaGame.RADIANT].score; - dire.live = game.teams[DotaGame.DIRE].life; - dire.score = game.teams[DotaGame.DIRE].score; + public function onGameStart(state:GameState):Void { + level.text = 'Level ${state.level}'; } - override public function update():Void { - if (game != null) { - updateViews(); - } - super.update(); + public function onGameChange(state:GameState):Void { + radiant.life = state.getTeamLife(DotaGame.RADIANT); + radiant.score = state.getTeamScore(DotaGame.RADIANT); + dire.life = state.getTeamLife(DotaGame.DIRE); + dire.score = state.getTeamScore(DotaGame.DIRE); + } + + public function onGameComplete(state:GameState):Void { + } } diff --git a/src/common/haxe/ru/m/tankz/Type.hx b/src/common/haxe/ru/m/tankz/Type.hx index eba4b60..decbcd9 100644 --- a/src/common/haxe/ru/m/tankz/Type.hx +++ b/src/common/haxe/ru/m/tankz/Type.hx @@ -24,6 +24,10 @@ abstract PlayerId(Array) { private inline function get_index():Int return this[1]; + @:from static public inline function fromArray(value:Array):PlayerId { + return new PlayerId(value[0], value[1]); + } + @:to public inline function toString():String { return '${team}:${index}'; } diff --git a/src/common/haxe/ru/m/tankz/game/Game.hx b/src/common/haxe/ru/m/tankz/game/Game.hx index e95332f..09da1c8 100644 --- a/src/common/haxe/ru/m/tankz/game/Game.hx +++ b/src/common/haxe/ru/m/tankz/game/Game.hx @@ -1,11 +1,10 @@ package ru.m.tankz.game; -import haxework.color.Color; import haxe.ds.Option; import haxe.Timer; +import haxework.color.Color; import haxework.provider.Provider; -import promhx.Deferred; -import promhx.Stream; +import haxework.signal.Signal; import ru.m.geom.Point; import ru.m.tankz.bundle.IConfigBundle; import ru.m.tankz.bundle.ILevelBundle; @@ -22,7 +21,35 @@ import ru.m.tankz.game.GameState; import ru.m.tankz.game.Spawner; import ru.m.tankz.Type; -class Game { +typedef GameListener = { + public function onGameStart(state:GameState):Void; + public function onGameChange(state:GameState):Void; + public function onGameComplete(state:GameState):Void; +} + +class GameDispatcher { + + public var onGameStart(default, null):Signal = new Signal(); + public var onGameChange(default, null):Signal = new Signal(); + public var onGameComplete(default, null):Signal = new Signal(); + + public function new() { + } + + public function connect(listener:GameListener) { + onGameStart.connect(listener.onGameStart); + onGameChange.connect(listener.onGameChange); + onGameComplete.connect(listener.onGameComplete); + } + + public function disconnect(listener:GameListener) { + onGameStart.disconnect(listener.onGameStart); + onGameChange.disconnect(listener.onGameChange); + onGameComplete.disconnect(listener.onGameComplete); + } +} + +class Game extends GameDispatcher { private static var TAG(default, never):String = 'Game'; @@ -35,13 +62,12 @@ class Game { public var state(default, null):GameState; private var points:Array; - private var deferred:Deferred; - private var stream:Stream; @:provide var configBundle:IConfigBundle; @:provide var levelBundle:ILevelBundle; public function new(type:GameType) { + super(); this.type = type; this.config = configBundle.get(type); this.engine = new Engine(config); @@ -94,10 +120,9 @@ class Game { entity.rect.direction = point.direction; } - public function start(state:GameState):Stream { + public function start(state:GameState):Void { this.state = state; this.loser = null; - this.deferred = new Deferred(); var level:LevelConfig = levelBundle.get(type, config, state.level); points = level.points != null ? level.points : config.points; engine.map.setData(level.data); @@ -129,6 +154,8 @@ class Game { } team.spawner.runner = spawn; } + // ToDo: + state.teams = [for (team in teams.iterator()) team.state]; for (team in teams.iterator()) { for (player in team.players.iterator()) { @@ -145,8 +172,8 @@ class Game { engine.spawn(eagle); } } - - return stream = deferred.stream(); + onGameStart.emit(state); + onGameChange.emit(state); } private function spawn(task:SpawnTask):Void { @@ -158,7 +185,7 @@ class Game { player.tankId = tank.id; player.state.tank = tank.config.type; engine.spawn(tank); - deferred.resolve(state); + onGameChange.emit(state); } private function complete():Void { @@ -169,8 +196,8 @@ class Game { } } Timer.delay(function() { - deferred.resolve(state); - stream.end(); + //onGameChange.emit(state); + onGameComplete.emit(state); }, 5000); } @@ -190,7 +217,7 @@ class Game { if (eagle.death) { getPlayer(playerId).state.score += eagle.score * (eagle.team == playerId.team ? 0 : 1); lose(eagle.team); - deferred.resolve(state); + onGameChange.emit(state); } case [EntityType.TANK(tank), EntityChange.HIT]: if (tank.bonus) { @@ -241,12 +268,12 @@ class Game { getPlayer(playerId).state.frags++; getPlayer(playerId).state.score += tank.config.score * (tank.playerId.team == playerId.team ? 0 : 1); } - deferred.resolve(state); + onGameChange.emit(state); case EntityType.BONUS(bonus): if (bonus.config.score > 0 && playerId != null) { getPlayer(playerId).state.score += bonus.config.score; } - deferred.resolve(state); + onGameChange.emit(state); case _: } } @@ -268,11 +295,14 @@ class Game { } var level = this.state.level + 1; if (level >= config.game.levels) level = 0; - return Option.Some(new GameState(type, state.presetId, level, state.players)); + return Option.Some(new GameState(type, state.presetId, level, state)); } public function dispose():Void { engine.dispose(); + onGameStart.dispose(); + onGameChange.dispose(); + onGameComplete.dispose(); } private function spawnBonus(?type:BonusType):Void { diff --git a/src/common/haxe/ru/m/tankz/game/GameState.hx b/src/common/haxe/ru/m/tankz/game/GameState.hx index addd8d2..b1ba00c 100644 --- a/src/common/haxe/ru/m/tankz/game/GameState.hx +++ b/src/common/haxe/ru/m/tankz/game/GameState.hx @@ -29,22 +29,34 @@ class PlayerState { } } +class TeamState { + public var id:TeamId; + public var life:Int; + + public function new(id:TeamId, life:Int = 0) { + this.id = id; + this.life = life; + } +} + class GameState { public var type:GameType; public var presetId:PresetId; public var level:Int; public var players:Array; + public var teams:Array; public var preset(get, null):GamePreset; public var config(get, null):Config; @:provide private var configBundle:IConfigBundle; - public function new(type:GameType, presetId:PresetId, level:Int = 1, players:Array = null) { + public function new(type:GameType, presetId:PresetId, level:Int = 1, state:GameState = null) { this.type = type; this.presetId = presetId; this.level = level; - this.players = players == null ? [] : players; + this.players = state == null ? [] : state.players; + this.teams = state == null ? [] : state.teams; } private function get_preset():GamePreset { @@ -56,4 +68,47 @@ class GameState { private function get_config():Config { return configBundle.get(type); } + + public function getTeamLife(id:TeamId):Int { + var result = 0; + for (team in teams) { + if (team.id == id) { + result += team.life; + } + } + for (player in players) { + if (player.id.team == id) { + result += player.life; + } + } + return result; + } + + public function getTeamScore(id:TeamId):Int { + var result = 0; + for (player in players) { + if (player.id.team == id) { + result += player.score; + } + } + return result; + } + + public function getPlayerLife(id:PlayerId):Int { + for (player in players) { + if (player.id == id) { + return player.life; + } + } + return 0; + } + + public function getPlayerScore(id:PlayerId):Int { + for (player in players) { + if (player.id == id) { + return player.score; + } + } + return 0; + } } diff --git a/src/common/haxe/ru/m/tankz/game/Team.hx b/src/common/haxe/ru/m/tankz/game/Team.hx index 0ef9213..965ebd2 100644 --- a/src/common/haxe/ru/m/tankz/game/Team.hx +++ b/src/common/haxe/ru/m/tankz/game/Team.hx @@ -1,5 +1,6 @@ package ru.m.tankz.game; +import ru.m.tankz.game.GameState.TeamState; import ru.m.tankz.Type; import ru.m.tankz.config.Config; @@ -9,14 +10,13 @@ class Team { public var config(default, null):TeamConfig; public var spawner(default, null):Spawner; public var players(default, null):Map; - public var life(default, default):Int; public var isAlive(get, null):Bool; public var score(get, null):Int; public var eagleId(default, default):Int; - private var active(default, default):Int; + public var state(default, default):TeamState; - public function new(config:TeamConfig, points:Array) { + public function new(config:TeamConfig, points:Array, state:TeamState = null) { this.id = config.id; this.config = config; this.players = new Map(); @@ -24,13 +24,14 @@ class Team { var player:Player = new Player(id, playerConfig); this.players[playerConfig.index] = player; } - this.life = config.life; this.spawner = new Spawner(config, points); + this.state = state == null ? new TeamState(id) : state; + this.state.life = config.life; } public function tryRespawn(playerId:PlayerId):Bool { var player:Player = players[playerId.index]; - var result = player.state.life > 0 || life > active; + var result = player.state.life > 0 || state.life > active; active++; return result; } @@ -41,13 +42,13 @@ class Team { if (player.state.life > 0) { player.state.life--; } else { - life--; + state.life--; } } // ToDo: eagle state? private function get_isAlive():Bool { - if (life > 0) { + if (state.life > 0) { return true; } if (spawner.active) { diff --git a/src/common/resources/death/config.yaml b/src/common/resources/death/config.yaml index 930ce92..42323c7 100644 --- a/src/common/resources/death/config.yaml +++ b/src/common/resources/death/config.yaml @@ -20,13 +20,13 @@ bricks: player: default: &player - protect: 3 + life: 10 + protect: 2 tanks: - {type: default, rate: 1} team: base: &team - life: 10 players: - {<<: *player, index: 0}