[common] added GameSave

This commit is contained in:
2018-02-20 00:01:35 +03:00
parent 11aea59cf8
commit a127777e49
15 changed files with 220 additions and 51 deletions

View File

@@ -13,11 +13,16 @@ import haxework.provider.Provider;
import haxework.resources.IResources; import haxework.resources.IResources;
import haxework.resources.Resources; import haxework.resources.Resources;
import ru.m.connect.IConnection; import ru.m.connect.IConnection;
import ru.m.tankz.bundle.ConfigBundle;
import ru.m.tankz.bundle.IConfigBundle;
import ru.m.tankz.bundle.ILevelBundle;
import ru.m.tankz.bundle.LevelBundle;
import ru.m.tankz.frame.StartFrame; import ru.m.tankz.frame.StartFrame;
import ru.m.tankz.game.ClassicGame; import ru.m.tankz.game.ClassicGame;
import ru.m.tankz.game.DotaGame; import ru.m.tankz.game.DotaGame;
import ru.m.tankz.game.Game; import ru.m.tankz.game.Game;
import ru.m.tankz.PacketBuilder; import ru.m.tankz.PacketBuilder;
import ru.m.tankz.storage.SaveStorage;
#if flash import haxework.log.JSLogger; #end #if flash import haxework.log.JSLogger; #end
#if debug import haxework.log.SocketLogger; #end #if debug import haxework.log.SocketLogger; #end
@@ -71,7 +76,6 @@ class Client implements IConnectionHandler {
Root.bind(view); Root.bind(view);
view.content.stage.stageFocusRect = false; view.content.stage.stageFocusRect = false;
//view.logout.onPress = this; //view.logout.onPress = this;
view.switcher.change(StartFrame.ID);
view.content.stage.addEventListener(KeyboardEvent.KEY_UP, function(event:KeyboardEvent):Void { view.content.stage.addEventListener(KeyboardEvent.KEY_UP, function(event:KeyboardEvent):Void {
if (event.keyCode == Keyboard.ESCAPE) { if (event.keyCode == Keyboard.ESCAPE) {
@@ -79,8 +83,13 @@ class Client implements IConnectionHandler {
} }
}); });
Provider.setFactory(IConfigBundle, ConfigBundle);
Provider.setFactory(ILevelBundle, LevelBundle);
Provider.setFactory(SaveStorage, SaveStorage);
Provider.setFactory(Game, ClassicGame, ClassicGame.TYPE); Provider.setFactory(Game, ClassicGame, ClassicGame.TYPE);
Provider.setFactory(Game, DotaGame, DotaGame.TYPE); Provider.setFactory(Game, DotaGame, DotaGame.TYPE);
view.switcher.change(StartFrame.ID);
} }
public function onPress(view:ButtonView):Void { public function onPress(view:ButtonView):Void {

View File

@@ -1,9 +1,10 @@
package ru.m.tankz.config; package ru.m.tankz.bundle;
import yaml.Parser;
import openfl.Assets; import openfl.Assets;
import yaml.Yaml;
import ru.m.tankz.config.Config; import ru.m.tankz.config.Config;
import ru.m.tankz.Type;
import yaml.Parser;
import yaml.Yaml;
typedef ConfigSource = { typedef ConfigSource = {
@@ -16,13 +17,13 @@ typedef ConfigSource = {
var bonuses: Array<BonusConfig>; var bonuses: Array<BonusConfig>;
} }
class ConfigBundle { class ConfigBundle implements IConfigBundle {
private static function convert(raw:Dynamic):ConfigSource { private static function convert(raw:Dynamic):ConfigSource {
return raw; return raw;
} }
public static function get(type:String):Config { public function get(type:GameType):Config {
var source = convert(Yaml.parse(Assets.getText('resources/${type}/config.yaml'), Parser.options().useObjects())); var source = convert(Yaml.parse(Assets.getText('resources/${type}/config.yaml'), Parser.options().useObjects()));
return new Config(type, source.game, source.map, source.bricks, source.presets, source.points, source.tanks, source.bonuses); return new Config(type, source.game, source.map, source.bricks, source.presets, source.points, source.tanks, source.bonuses);
} }

View File

@@ -0,0 +1,15 @@
package ru.m.tankz.bundle;
import openfl.Assets;
import ru.m.tankz.config.Config;
import ru.m.tankz.Type;
import ru.m.tankz.util.LevelUtil;
class LevelBundle implements ILevelBundle {
public function get(type:GameType, config:Config, level:Int):LevelConfig {
var data:String = Assets.getText('resources/${type}/levels/level${LevelUtil.formatLevel(level)}.txt');
return LevelUtil.loads(config, data);
}
}

View File

@@ -11,9 +11,11 @@ import haxework.provider.Provider;
import protohx.Message; import protohx.Message;
import ru.m.connect.IConnection; import ru.m.connect.IConnection;
import ru.m.tankz.game.Game; import ru.m.tankz.game.Game;
import ru.m.tankz.game.GameSave;
import ru.m.tankz.game.GameState; import ru.m.tankz.game.GameState;
import ru.m.tankz.proto.pack.GameUpdateResponse; import ru.m.tankz.proto.pack.GameUpdateResponse;
import ru.m.tankz.render.Render; import ru.m.tankz.render.Render;
import ru.m.tankz.storage.SaveStorage;
interface GameFrameLayout { interface GameFrameLayout {
@@ -34,16 +36,16 @@ class GameFrame extends VGroupView implements ViewBuilder implements IPacketHand
public function init():Void {} public function init():Void {}
public function onShow():Void { public function onShow():Void {
start(Provider.get(GameState)); start(Provider.get(GameSave));
} }
private function start(s:GameState):Void { private function start(save:GameSave):Void {
game = Provider.build(Game, s.type); game = Provider.build(Game, save.state.type);
if (game == null) { if (game == null) {
throw 'Unsupported game type "${s.type}"'; throw 'Unsupported game type "${save.state.type}"';
} }
game.engine.listeners.push(render); game.engine.listeners.push(render);
game.start(s).then(onGameStateChange).endThen(onGameComplete); game.start(save).then(onGameStateChange).endThen(onGameComplete);
content.addEventListener(Event.ENTER_FRAME, redraw); content.addEventListener(Event.ENTER_FRAME, redraw);
//Provider.get(IConnection).packetHandler.addListener(this); //Provider.get(IConnection).packetHandler.addListener(this);
render.draw(game.engine); render.draw(game.engine);
@@ -67,6 +69,9 @@ class GameFrame extends VGroupView implements ViewBuilder implements IPacketHand
} }
private function stateString(game:Game):String { private function stateString(game:Game):String {
if (game.state == null) {
return '';
}
var result:Array<String> = []; var result:Array<String> = [];
result.push('Level: ${game.state.level}'); result.push('Level: ${game.state.level}');
for (team in game.teams) { for (team in game.teams) {
@@ -98,8 +103,10 @@ class GameFrame extends VGroupView implements ViewBuilder implements IPacketHand
} }
switch (game.next()) { switch (game.next()) {
case Option.Some(s): case Option.Some(s):
var save = game.save();
Provider.get(SaveStorage).write(save);
stop(); stop();
start(s); start(save);
case Option.None: case Option.None:
Provider.get(IFrameSwitcher).change(StartFrame.ID); Provider.get(IFrameSwitcher).change(StartFrame.ID);
} }

View File

@@ -1,12 +1,13 @@
package ru.m.tankz.frame; package ru.m.tankz.frame;
import ru.m.tankz.config.ConfigBundle; import ru.m.tankz.game.GameSave;
import haxework.gui.frame.IFrameSwitcher; import haxework.gui.frame.IFrameSwitcher;
import ru.m.tankz.game.GameState;
import haxework.provider.Provider;
import haxework.gui.list.ListView; import haxework.gui.list.ListView;
import haxework.gui.ViewBuilder;
import haxework.gui.VGroupView; import haxework.gui.VGroupView;
import haxework.gui.ViewBuilder;
import haxework.provider.Provider;
import ru.m.tankz.bundle.IConfigBundle;
import ru.m.tankz.game.GameState;
interface LevelFrameLayout { interface LevelFrameLayout {
@@ -23,13 +24,13 @@ class LevelFrame extends VGroupView implements ViewBuilder implements LevelFrame
} }
public function onShow():Void { public function onShow():Void {
var state = Provider.get(GameState); var state:GameState = Provider.get(GameSave).state;
var c = ConfigBundle.get(state.type).game.levels; var c = Provider.get(IConfigBundle).get(state.type).game.levels;
levels.data = [for (i in 0...c) i]; levels.data = [for (i in 0...c) i];
} }
public function onListItemClick(item:IListItemView<Int>):Void { public function onListItemClick(item:IListItemView<Int>):Void {
Provider.get(GameState).level = item.data; Provider.get(GameSave).state.level = item.data;
Provider.get(IFrameSwitcher).change(GameFrame.ID); Provider.get(IFrameSwitcher).change(GameFrame.ID);
} }
} }

View File

@@ -1,5 +1,7 @@
package ru.m.tankz.frame; package ru.m.tankz.frame;
import ru.m.tankz.storage.SaveStorage;
import ru.m.tankz.game.GameSave;
import haxework.gui.ButtonView; import haxework.gui.ButtonView;
import haxework.gui.frame.IFrameSwitcher; import haxework.gui.frame.IFrameSwitcher;
import haxework.gui.VGroupView; import haxework.gui.VGroupView;
@@ -14,6 +16,7 @@ import ru.m.tankz.Type;
interface StartFrameLayout { interface StartFrameLayout {
var classic_1p(default, null):ButtonView; var classic_1p(default, null):ButtonView;
var classic_2p(default, null):ButtonView; var classic_2p(default, null):ButtonView;
var classic_load(default, null):ButtonView;
var dota_1p(default, null):ButtonView; var dota_1p(default, null):ButtonView;
var dota_2p_coop(default, null):ButtonView; var dota_2p_coop(default, null):ButtonView;
var dota_2p_vs(default, null):ButtonView; var dota_2p_vs(default, null):ButtonView;
@@ -24,20 +27,27 @@ class StartFrame extends VGroupView implements ViewBuilder implements StartFrame
public static inline var ID = "start"; public static inline var ID = "start";
public function init() { public function init():Void {
classic_1p.onPress = this; classic_1p.onPress = this;
classic_2p.onPress = this; classic_2p.onPress = this;
classic_load.onPress = this;
dota_1p.onPress = this; dota_1p.onPress = this;
dota_2p_coop.onPress = this; dota_2p_coop.onPress = this;
dota_2p_vs.onPress = this; dota_2p_vs.onPress = this;
} }
public function onShow():Void {
classic_load.visible = Provider.get(SaveStorage).read(ClassicGame.TYPE) != null;
}
public function onPress(view:ButtonView):Void { public function onPress(view:ButtonView):Void {
switch (view.id) { switch (view.id) {
case 'classic_1p': case 'classic_1p':
startGame(ClassicGame.TYPE, ClassicGame.PLAYER1); startGame(ClassicGame.TYPE, ClassicGame.PLAYER1);
case 'classic_2p': case 'classic_2p':
startGame(ClassicGame.TYPE, ClassicGame.PLAYER2); startGame(ClassicGame.TYPE, ClassicGame.PLAYER2);
case 'classic_load':
loadGame(ClassicGame.TYPE);
case 'dota_1p': case 'dota_1p':
startGame(DotaGame.TYPE, DotaGame.PLAYER1); startGame(DotaGame.TYPE, DotaGame.PLAYER1);
case 'dota_2p_coop': case 'dota_2p_coop':
@@ -48,7 +58,13 @@ class StartFrame extends VGroupView implements ViewBuilder implements StartFrame
} }
private function startGame(type:GameType, presetId:PresetId):Void { private function startGame(type:GameType, presetId:PresetId):Void {
Provider.set(GameState, new GameState(type, presetId)); Provider.set(GameSave, new GameSave({type: type, presetId: presetId}));
Provider.get(IFrameSwitcher).change(LevelFrame.ID); Provider.get(IFrameSwitcher).change(LevelFrame.ID);
} }
private function loadGame(type:GameType):Void {
var save:GameSave = Provider.get(SaveStorage).read(type);
Provider.set(GameSave, save);
Provider.get(IFrameSwitcher).change(GameFrame.ID);
}
} }

View File

@@ -20,6 +20,10 @@ views:
$type: haxework.gui.ButtonView $type: haxework.gui.ButtonView
text: 2 Player text: 2 Player
$style: button $style: button
- id: classic_load
$type: haxework.gui.ButtonView
text: Load
$style: button
- $type: haxework.gui.LabelView - $type: haxework.gui.LabelView
$style: label $style: label
fontSize: 20 fontSize: 20

View File

@@ -0,0 +1,33 @@
package ru.m.tankz.storage;
import flash.net.SharedObject;
import ru.m.tankz.Type;
import ru.m.tankz.game.GameSave;
class SaveStorage {
private static var TAG(default, never):String = 'SaveStorage';
private var so:SharedObject;
public function new() {
so = SharedObject.getLocal('tankz');
}
public function read(type:GameType):Null<GameSave> {
var data:String = Reflect.getProperty(so.data, type);
L.d(TAG, 'read: ${data}');
if (data != null) {
return GameSave.fromYaml(data);
}
return null;
}
public function write(save:GameSave):Void {
var data:String = save.toYaml();
L.d(TAG, 'write: ${data}');
so.setProperty(save.state.type, data);
so.flush();
}
}

View File

@@ -42,7 +42,7 @@ presets:
- {<<: *human, index: 0, color: 0xF5C040, life: 3} - {<<: *human, index: 0, color: 0xF5C040, life: 3}
- id: bot - id: bot
spawnInterval: 3000 spawnInterval: 3000
life: 20 life: 1
players: players:
- {<<: *bot, index: 0} - {<<: *bot, index: 0}
- {<<: *bot, index: 1} - {<<: *bot, index: 1}

View File

@@ -0,0 +1,9 @@
package ru.m.tankz.bundle;
import ru.m.tankz.config.Config;
import ru.m.tankz.Type;
interface IConfigBundle {
public function get(type:GameType):Config;
}

View File

@@ -0,0 +1,9 @@
package ru.m.tankz.bundle;
import ru.m.tankz.config.Config;
import ru.m.tankz.Type;
interface ILevelBundle {
public function get(type:GameType, config:Config, level:Int):LevelConfig;
}

View File

@@ -1,15 +1,17 @@
package ru.m.tankz.game; package ru.m.tankz.game;
import ru.m.tankz.game.GameSave.PlayerSave;
import haxe.ds.Option; import haxe.ds.Option;
import haxe.Timer; import haxe.Timer;
import haxework.provider.Provider;
import promhx.Deferred; import promhx.Deferred;
import promhx.Stream; import promhx.Stream;
import ru.m.geom.Direction; import ru.m.geom.Direction;
import ru.m.geom.Point; import ru.m.geom.Point;
import ru.m.tankz.bot.BotControl; import ru.m.tankz.bot.BotControl;
import ru.m.tankz.bundle.IConfigBundle;
import ru.m.tankz.bundle.ILevelBundle;
import ru.m.tankz.config.Config; import ru.m.tankz.config.Config;
import ru.m.tankz.config.ConfigBundle;
import ru.m.tankz.config.LevelBundle;
import ru.m.tankz.control.Control; import ru.m.tankz.control.Control;
import ru.m.tankz.control.HumanControl; import ru.m.tankz.control.HumanControl;
import ru.m.tankz.core.Bonus; import ru.m.tankz.core.Bonus;
@@ -40,7 +42,7 @@ class Game implements EngineListener {
public function new(type:GameType) { public function new(type:GameType) {
this.type = type; this.type = type;
this.config = ConfigBundle.get(type); this.config = Provider.get(IConfigBundle).get(type);
this.engine = new Engine(config); this.engine = new Engine(config);
engine.listeners.push(this); engine.listeners.push(this);
} }
@@ -73,11 +75,11 @@ class Game implements EngineListener {
entity.rect.direction = Direction.fromString(point.direction); entity.rect.direction = Direction.fromString(point.direction);
} }
public function start(state:GameState):Stream<GameState> { public function start(save:GameSave):Stream<GameState> {
this.state = state; this.state = save.state;
this.preset = config.getPreset(state.presetId); this.preset = config.getPreset(state.presetId);
this.deferred = new Deferred(); this.deferred = new Deferred();
var level = LevelBundle.get(type, config, state.level); var level:LevelConfig = Provider.get(ILevelBundle).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);
teams = new Map<TeamId, Team>(); teams = new Map<TeamId, Team>();
@@ -87,6 +89,16 @@ class Game implements EngineListener {
var team:Team = new Team(teamConfig, teamPoints); var team:Team = new Team(teamConfig, teamPoints);
teams[team.id] = team; teams[team.id] = team;
for (player in team.players.iterator()) { for (player in team.players.iterator()) {
var playerSave:PlayerSave = save.getPlayer(player.id);
if (playerSave != null) {
player.life = playerSave.life;
if (playerSave.tank != null) {
player.config.tanks = [{
type: playerSave.tank,
rate: 1,
}];
}
}
if (player.config.control != null) { if (player.config.control != null) {
var control = switch (player.config.control) { var control = switch (player.config.control) {
case Control.HUMAN: new HumanControl(player.id, humanControlIndex++); case Control.HUMAN: new HumanControl(player.id, humanControlIndex++);
@@ -223,7 +235,7 @@ class Game implements EngineListener {
var level = state.level + 1; var level = state.level + 1;
state.level++; state.level++;
if (level >= config.game.levels) level = 0; if (level >= config.game.levels) level = 0;
return Option.Some(new GameState(state.type, preset.id, level)); return Option.Some({type: state.type, presetId: preset.id, level: level});
} }
public function dispose():Void { public function dispose():Void {
@@ -277,4 +289,25 @@ class Game implements EngineListener {
engine.destroy(tank); // :-D engine.destroy(tank); // :-D
} }
} }
public function save():GameSave {
var players:Array<PlayerSave> = [];
for (team in teams) {
for (player in team.players) {
if (player.config.control == Control.HUMAN) {
var tank:Tank = EntityTypeResolver.as(engine.entities[player.tankId], Tank);
players.push({
id: player.id,
life: player.life + (player.tankId > 0 ? 1 : 0),
tank: tank != null ? tank.config.type : null
});
}
}
}
return new GameSave({
type: state.type,
presetId: state.presetId,
level: state.level
}, players);
}
} }

View File

@@ -0,0 +1,46 @@
package ru.m.tankz.game;
import yaml.Parser;
import yaml.Renderer;
import yaml.Yaml;
import ru.m.tankz.Type;
typedef PlayerSave = {
var id:PlayerId;
var tank:TankType;
var life:Int;
}
class GameSave {
public var state:GameState;
public var players:Array<PlayerSave>;
public function new(state:GameState, ?players:Array<PlayerSave>) {
this.state = state;
this.players = players != null ? players : [];
}
public function getPlayer(id:PlayerId):PlayerSave {
for (player in players) {
if (player.id.team == id.team && player.id.index == id.index) {
return player;
}
}
return null;
}
public function toYaml():String {
return Yaml.render({
state: state,
players: players,
}, Renderer.options().setFlowLevel(0));
}
public static function fromYaml(value:String):GameSave {
var data:Dynamic = Yaml.parse(value, Parser.options().useObjects());
return new GameSave(data.state, data.players);
}
}

View File

@@ -3,16 +3,9 @@ package ru.m.tankz.game;
import ru.m.tankz.Type; import ru.m.tankz.Type;
class GameState { typedef GameState = {
public var type:GameType; var type:GameType;
public var level:Int; var presetId:PresetId;
public var presetId:PresetId; @:optional var level:Int;
public var loser:TeamId; @:optional var loser:TeamId;
public function new(type:GameType, presetId:PresetId, level:Int = 0) {
this.type = type;
this.presetId = presetId;
this.level = level;
this.loser = null;
}
} }

View File

@@ -1,8 +1,6 @@
package ru.m.tankz.config; package ru.m.tankz.util;
import openfl.Assets;
import ru.m.tankz.config.Config; import ru.m.tankz.config.Config;
import ru.m.tankz.Type;
import yaml.Parser; import yaml.Parser;
import yaml.Renderer; import yaml.Renderer;
import yaml.Yaml; import yaml.Yaml;
@@ -13,9 +11,9 @@ typedef LevelSource = {
@:optional var points:Array<SpawnPoint>; @:optional var points:Array<SpawnPoint>;
} }
class LevelBundle { class LevelUtil {
private static function formatLevel(level:Int):String { public static function formatLevel(level:Int):String {
var result = Std.string(level); var result = Std.string(level);
while (result.length < 3) result = '0${result}'; while (result.length < 3) result = '0${result}';
return result; return result;
@@ -60,9 +58,4 @@ class LevelBundle {
data: [for (i in 0...config.map.gridWidth * config.map.gridHeight) config.bricks[1]] data: [for (i in 0...config.map.gridWidth * config.map.gridHeight) config.bricks[1]]
} }
} }
public static function get(type:GameType, config:Config, level:Int):LevelConfig {
var data:String = Assets.getText('resources/${type}/levels/level${formatLevel(level)}.txt');
return loads(config, data);
}
} }