[common] add GameDispatcher

This commit is contained in:
2019-03-26 11:23:34 +03:00
parent a948dfbaad
commit e609dafedc
16 changed files with 259 additions and 89 deletions

View File

@@ -31,7 +31,7 @@ import ru.m.tankz.preset.DotaGame;
var tankConfig = resultState.config.getTank(tankType); var tankConfig = resultState.config.getTank(tankType);
view.tank = tankConfig == null ? 'ba' : tankConfig.skin; view.tank = tankConfig == null ? 'ba' : tankConfig.skin;
view.color = resultState.config.getColor(player.id); view.color = resultState.config.getColor(player.id);
view.live = player.frags; view.life = player.frags;
view.score = player.score; view.score = player.score;
return view; return view;
} }

View File

@@ -2,10 +2,11 @@ package ru.m.tankz.view.classic;
import haxework.view.LabelView; import haxework.view.LabelView;
import haxework.view.VGroupView; 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.IGamePanel;
import ru.m.tankz.view.common.LifeView; 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 { @:template class ClassicGamePanel extends VGroupView implements IGamePanel {
@@ -14,26 +15,27 @@ import ru.m.tankz.preset.ClassicGame;
@:view var player2:LifeView; @:view var player2:LifeView;
@:view var level:LabelView; @: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 { public function onGameStart(state:GameState):Void {
level.text = 'Level ${game.state.level}'; level.text = 'Level ${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; public function onGameChange(state:GameState):Void {
if (game.teams[ClassicGame.HUMAN].players[1] != null) { bot.life = state.getTeamLife(ClassicGame.BOT);
player1.life = state.getPlayerLife(player1Id);
player1.score = state.getPlayerScore(player1Id);
if (true) {
player2.visible = true; player2.visible = true;
player2.live = game.teams[ClassicGame.HUMAN].players[1].state.life; player2.life = state.getPlayerLife(player2Id);
player2.score = game.teams[ClassicGame.HUMAN].players[1].state.score; player2.score = state.getPlayerScore(player2Id);
} else { } else {
player2.visible = false; player2.visible = false;
} }
} }
override public function update():Void { public function onGameComplete(state:GameState):Void {
if (game != null) {
updateViews();
}
super.update();
} }
} }

View File

@@ -44,12 +44,13 @@ class GameFrame extends GroupView {
game = new Game(state.type); game = new Game(state.type);
game.engine.connect(render); game.engine.connect(render);
game.engine.connect(sound); 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 = new Timer(10);
timer.run = updateEngine; timer.run = updateEngine;
if (panel != null) {
panel.game = game;
}
content.addEventListener(Event.ENTER_FRAME, _redraw); content.addEventListener(Event.ENTER_FRAME, _redraw);
render.draw(game.engine); render.draw(game.engine);
sound.play('start'); sound.play('start');
@@ -68,17 +69,15 @@ class GameFrame extends GroupView {
render.reset(); render.reset();
} }
private function onGameStateChange(_):Void { public function onGameStart(state:GameState):Void {}
if (panel != null) {
panel.toUpdate();
}
}
private function onGameComplete(_):Void { public function onGameChange(state:GameState):Void {}
public function onGameComplete(state:GameState):Void {
result = state; result = state;
state = switch game.next() { state = switch game.next() {
case Option.Some(s): s; case Some(s): s;
case Option.None: null; case None: null;
} }
stop(); stop();
switcher.change(ResultFrame.ID); switcher.change(ResultFrame.ID);

View File

@@ -1,8 +1,10 @@
package ru.m.tankz.view.common; package ru.m.tankz.view.common;
import ru.m.tankz.game.Game;
import haxework.view.IView; import haxework.view.IView;
import ru.m.tankz.game.GameState;
interface IGamePanel extends IView<Dynamic> { interface IGamePanel extends IView<Dynamic> {
public var game:Game; public function onGameStart(state:GameState):Void;
public function onGameChange(state:GameState):Void;
public function onGameComplete(state:GameState):Void;
} }

View File

@@ -1,25 +1,37 @@
package ru.m.tankz.view.common; package ru.m.tankz.view.common;
import openfl.Assets;
import haxework.view.LabelView;
import haxework.view.ImageView;
import haxework.view.HGroupView; 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 { @:template class LifeView extends HGroupView {
@:provide static var currentState:GameState;
@:view("tank") public var tankImage:ImageView; @:view("tank") public var tankImage:ImageView;
@:view("live") public var liveLabel:LabelView; @:view("life") public var lifeLabel:LabelView;
@:view("score") public var scoreLabel:LabelView; @:view("score") public var scoreLabel:LabelView;
public var state(null, set):PlayerState;
public var tank(null, set):String; public var tank(null, set):String;
public var color(null, set):Int; public var color(null, set):Int;
public var live(null, set):Int; public var life(null, set):Int;
public var score(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 { private inline function set_tank(value:String):String {
if (value != null) { if (value != null && value != tank) {
tankImage.image = Assets.getBitmapData('resources/image/tank/${value}-0.png'); tank = value;
tankImage.image = Assets.getBitmapData('resources/image/tank/${tank}-0.png');
} }
return value; return tank;
} }
private inline function set_color(value:Int):Int { private inline function set_color(value:Int):Int {
@@ -27,8 +39,8 @@ import haxework.view.HGroupView;
return value; return value;
} }
private inline function set_live(value:Int):Int { private inline function set_life(value:Int):Int {
liveLabel.text = '${value}'; lifeLabel.text = '${value}';
return value; return value;
} }
@@ -36,4 +48,21 @@ import haxework.view.HGroupView;
scoreLabel.text = '${value}$'; scoreLabel.text = '${value}$';
return 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;
}
} }

View File

@@ -3,7 +3,7 @@ layout.margin: 5
views: views:
- id: tank - id: tank
$type: haxework.view.ImageView $type: haxework.view.ImageView
- id: live - id: life
$type: haxework.view.LabelView $type: haxework.view.LabelView
skinId: text.box skinId: text.box
geometry.size.fixed: [50, 38] geometry.size.fixed: [50, 38]

View File

@@ -9,13 +9,13 @@ import ru.m.tankz.view.common.IGamePanel;
public static inline var ID = "death.game"; public static inline var ID = "death.game";
@:view("render") private var renderView(default, null):Render; @: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 { override private function get_render():Render {
return renderView; return renderView;
} }
/*override private function get_panel():IGamePanel { override private function get_panel():IGamePanel {
return panelView; return panelView;
}*/ }
} }

View File

@@ -1,8 +1,10 @@
--- ---
skinId: container skinId: container
views: views:
- $type: haxework.view.VGroupView - $type: haxework.view.HGroupView
layout.margin: 5 layout.margin: 5
views: views:
- id: render - id: render
$type: ru.m.tankz.render.Render $type: ru.m.tankz.render.Render
- id: panel
$type: ru.m.tankz.view.death.DeathGamePanel

View File

@@ -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<PlayerState, LifeView>;
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 {
}
}

View File

@@ -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

View File

@@ -1,11 +1,11 @@
package ru.m.tankz.view.dota; package ru.m.tankz.view.dota;
import ru.m.tankz.preset.DotaGame;
import haxework.view.HGroupView; import haxework.view.HGroupView;
import haxework.view.LabelView; import haxework.view.LabelView;
import ru.m.tankz.game.GameState;
import ru.m.tankz.view.common.IGamePanel; import ru.m.tankz.view.common.IGamePanel;
import ru.m.tankz.view.common.LifeView; 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 { @:template class DotaGamePanel extends HGroupView implements IGamePanel {
@@ -13,20 +13,18 @@ import ru.m.tankz.preset.DotaGame;
@:view var dire:LifeView; @:view var dire:LifeView;
@:view var level:LabelView; @:view var level:LabelView;
public var game:Game; public function onGameStart(state:GameState):Void {
level.text = 'Level ${state.level}';
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;
} }
override public function update():Void { public function onGameChange(state:GameState):Void {
if (game != null) { radiant.life = state.getTeamLife(DotaGame.RADIANT);
updateViews(); radiant.score = state.getTeamScore(DotaGame.RADIANT);
} dire.life = state.getTeamLife(DotaGame.DIRE);
super.update(); dire.score = state.getTeamScore(DotaGame.DIRE);
}
public function onGameComplete(state:GameState):Void {
} }
} }

View File

@@ -24,6 +24,10 @@ abstract PlayerId(Array<Dynamic>) {
private inline function get_index():Int return this[1]; private inline function get_index():Int return this[1];
@:from static public inline function fromArray(value:Array<Dynamic>):PlayerId {
return new PlayerId(value[0], value[1]);
}
@:to public inline function toString():String { @:to public inline function toString():String {
return '${team}:${index}'; return '${team}:${index}';
} }

View File

@@ -1,11 +1,10 @@
package ru.m.tankz.game; package ru.m.tankz.game;
import haxework.color.Color;
import haxe.ds.Option; import haxe.ds.Option;
import haxe.Timer; import haxe.Timer;
import haxework.color.Color;
import haxework.provider.Provider; import haxework.provider.Provider;
import promhx.Deferred; import haxework.signal.Signal;
import promhx.Stream;
import ru.m.geom.Point; import ru.m.geom.Point;
import ru.m.tankz.bundle.IConfigBundle; import ru.m.tankz.bundle.IConfigBundle;
import ru.m.tankz.bundle.ILevelBundle; 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.game.Spawner;
import ru.m.tankz.Type; 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<GameState> = new Signal();
public var onGameChange(default, null):Signal<GameState> = new Signal();
public var onGameComplete(default, null):Signal<GameState> = 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'; private static var TAG(default, never):String = 'Game';
@@ -35,13 +62,12 @@ class Game {
public var state(default, null):GameState; public var state(default, null):GameState;
private var points:Array<SpawnPoint>; private var points:Array<SpawnPoint>;
private var deferred:Deferred<GameState>;
private var stream:Stream<GameState>;
@:provide var configBundle:IConfigBundle; @:provide var configBundle:IConfigBundle;
@:provide var levelBundle:ILevelBundle; @:provide var levelBundle:ILevelBundle;
public function new(type:GameType) { public function new(type:GameType) {
super();
this.type = type; this.type = type;
this.config = configBundle.get(type); this.config = configBundle.get(type);
this.engine = new Engine(config); this.engine = new Engine(config);
@@ -94,10 +120,9 @@ class Game {
entity.rect.direction = point.direction; entity.rect.direction = point.direction;
} }
public function start(state:GameState):Stream<GameState> { public function start(state:GameState):Void {
this.state = state; this.state = state;
this.loser = null; this.loser = null;
this.deferred = new Deferred();
var level:LevelConfig = levelBundle.get(type, config, state.level); var level:LevelConfig = levelBundle.get(type, config, state.level);
points = level.points != null ? level.points : config.points; points = level.points != null ? level.points : config.points;
engine.map.setData(level.data); engine.map.setData(level.data);
@@ -129,6 +154,8 @@ class Game {
} }
team.spawner.runner = spawn; team.spawner.runner = spawn;
} }
// ToDo:
state.teams = [for (team in teams.iterator()) team.state];
for (team in teams.iterator()) { for (team in teams.iterator()) {
for (player in team.players.iterator()) { for (player in team.players.iterator()) {
@@ -145,8 +172,8 @@ class Game {
engine.spawn(eagle); engine.spawn(eagle);
} }
} }
onGameStart.emit(state);
return stream = deferred.stream(); onGameChange.emit(state);
} }
private function spawn(task:SpawnTask):Void { private function spawn(task:SpawnTask):Void {
@@ -158,7 +185,7 @@ class Game {
player.tankId = tank.id; player.tankId = tank.id;
player.state.tank = tank.config.type; player.state.tank = tank.config.type;
engine.spawn(tank); engine.spawn(tank);
deferred.resolve(state); onGameChange.emit(state);
} }
private function complete():Void { private function complete():Void {
@@ -169,8 +196,8 @@ class Game {
} }
} }
Timer.delay(function() { Timer.delay(function() {
deferred.resolve(state); //onGameChange.emit(state);
stream.end(); onGameComplete.emit(state);
}, 5000); }, 5000);
} }
@@ -190,7 +217,7 @@ class Game {
if (eagle.death) { if (eagle.death) {
getPlayer(playerId).state.score += eagle.score * (eagle.team == playerId.team ? 0 : 1); getPlayer(playerId).state.score += eagle.score * (eagle.team == playerId.team ? 0 : 1);
lose(eagle.team); lose(eagle.team);
deferred.resolve(state); onGameChange.emit(state);
} }
case [EntityType.TANK(tank), EntityChange.HIT]: case [EntityType.TANK(tank), EntityChange.HIT]:
if (tank.bonus) { if (tank.bonus) {
@@ -241,12 +268,12 @@ class Game {
getPlayer(playerId).state.frags++; getPlayer(playerId).state.frags++;
getPlayer(playerId).state.score += tank.config.score * (tank.playerId.team == playerId.team ? 0 : 1); getPlayer(playerId).state.score += tank.config.score * (tank.playerId.team == playerId.team ? 0 : 1);
} }
deferred.resolve(state); onGameChange.emit(state);
case EntityType.BONUS(bonus): case EntityType.BONUS(bonus):
if (bonus.config.score > 0 && playerId != null) { if (bonus.config.score > 0 && playerId != null) {
getPlayer(playerId).state.score += bonus.config.score; getPlayer(playerId).state.score += bonus.config.score;
} }
deferred.resolve(state); onGameChange.emit(state);
case _: case _:
} }
} }
@@ -268,11 +295,14 @@ class Game {
} }
var level = this.state.level + 1; var level = this.state.level + 1;
if (level >= config.game.levels) level = 0; 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 { public function dispose():Void {
engine.dispose(); engine.dispose();
onGameStart.dispose();
onGameChange.dispose();
onGameComplete.dispose();
} }
private function spawnBonus(?type:BonusType):Void { private function spawnBonus(?type:BonusType):Void {

View File

@@ -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 { class GameState {
public var type:GameType; public var type:GameType;
public var presetId:PresetId; public var presetId:PresetId;
public var level:Int; public var level:Int;
public var players:Array<PlayerState>; public var players:Array<PlayerState>;
public var teams:Array<TeamState>;
public var preset(get, null):GamePreset; public var preset(get, null):GamePreset;
public var config(get, null):Config; public var config(get, null):Config;
@:provide private var configBundle:IConfigBundle; @:provide private var configBundle:IConfigBundle;
public function new(type:GameType, presetId:PresetId, level:Int = 1, players:Array<PlayerState> = null) { public function new(type:GameType, presetId:PresetId, level:Int = 1, state:GameState = null) {
this.type = type; this.type = type;
this.presetId = presetId; this.presetId = presetId;
this.level = level; 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 { private function get_preset():GamePreset {
@@ -56,4 +68,47 @@ class GameState {
private function get_config():Config { private function get_config():Config {
return configBundle.get(type); 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;
}
} }

View File

@@ -1,5 +1,6 @@
package ru.m.tankz.game; package ru.m.tankz.game;
import ru.m.tankz.game.GameState.TeamState;
import ru.m.tankz.Type; import ru.m.tankz.Type;
import ru.m.tankz.config.Config; import ru.m.tankz.config.Config;
@@ -9,14 +10,13 @@ class Team {
public var config(default, null):TeamConfig; public var config(default, null):TeamConfig;
public var spawner(default, null):Spawner; public var spawner(default, null):Spawner;
public var players(default, null):Map<Int, Player>; public var players(default, null):Map<Int, Player>;
public var life(default, default):Int;
public var isAlive(get, null):Bool; public var isAlive(get, null):Bool;
public var score(get, null):Int; public var score(get, null):Int;
public var eagleId(default, default):Int; public var eagleId(default, default):Int;
private var active(default, default):Int; private var active(default, default):Int;
public var state(default, default):TeamState;
public function new(config:TeamConfig, points:Array<SpawnPoint>) { public function new(config:TeamConfig, points:Array<SpawnPoint>, state:TeamState = null) {
this.id = config.id; this.id = config.id;
this.config = config; this.config = config;
this.players = new Map(); this.players = new Map();
@@ -24,13 +24,14 @@ class Team {
var player:Player = new Player(id, playerConfig); var player:Player = new Player(id, playerConfig);
this.players[playerConfig.index] = player; this.players[playerConfig.index] = player;
} }
this.life = config.life;
this.spawner = new Spawner(config, points); 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 { public function tryRespawn(playerId:PlayerId):Bool {
var player:Player = players[playerId.index]; var player:Player = players[playerId.index];
var result = player.state.life > 0 || life > active; var result = player.state.life > 0 || state.life > active;
active++; active++;
return result; return result;
} }
@@ -41,13 +42,13 @@ class Team {
if (player.state.life > 0) { if (player.state.life > 0) {
player.state.life--; player.state.life--;
} else { } else {
life--; state.life--;
} }
} }
// ToDo: eagle state? // ToDo: eagle state?
private function get_isAlive():Bool { private function get_isAlive():Bool {
if (life > 0) { if (state.life > 0) {
return true; return true;
} }
if (spawner.active) { if (spawner.active) {

View File

@@ -20,13 +20,13 @@ bricks:
player: player:
default: &player default: &player
protect: 3 life: 10
protect: 2
tanks: tanks:
- {type: default, rate: 1} - {type: default, rate: 1}
team: team:
base: &team base: &team
life: 10
players: players:
- {<<: *player, index: 0} - {<<: *player, index: 0}