Merge branch 'develop'

This commit is contained in:
2018-01-31 23:04:38 +03:00
89 changed files with 1830 additions and 826 deletions

13
WORK.md
View File

@@ -20,6 +20,12 @@
* bonuses 0% * bonuses 0%
* eagle 0% * eagle 0%
* game
* classic
* state 0%
* bot 5%
* player: 50%
* render * render
* map 100% * map 100%
* tanks 100% * tanks 100%
@@ -34,6 +40,7 @@
* proto * proto
* common
* bot 0% * webapp
* single game 20% * angular app 0%
* google analytics 0%

View File

@@ -10,6 +10,9 @@ const version = require('./version');
const prepare = require('./prepare'); const prepare = require('./prepare');
const debug = require('../tasks/debug'); const debug = require('../tasks/debug');
const webserver = require('gulp-webserver'); const webserver = require('gulp-webserver');
const run = require('gulp-run');
const tail = require('../tasks/tail');
const deb = require('gulp-debian');
const generate = () => function generate() { const generate = () => function generate() {
@@ -54,11 +57,47 @@ const webapp = function () {
return gulp.src('src/webapp/*').pipe(gulp.dest('target')); return gulp.src('src/webapp/*').pipe(gulp.dest('target'));
}; };
const buildDeb = gulp.series(
() => gulp.src('src/client/debian/**/*').pipe(gulp.dest('target/debian')),
() => gulp.src('target/linux/**/*').pipe(gulp.dest('target/debian/usr/share/tankz')),
() => gulp.src('target/debian/*')
.pipe(deb({
package: 'tankz',
version: version,
section: 'base',
priority: 'optional',
architecture: 'all',
maintainer: 'shmyga <shmyga.z@gmail.com>',
description: 'Tank\'z',
changelog: [],
postinst: [
'if [ "$1" = "configure" ] && [ -x "`which update-menus 2>/dev/null`" ] ; then\n' +
' update-menus\n' +
'fi'
],
_target: '/',
_out: 'target',
_clean: false,
_verbose: true
}))
);
exports['client:flash:html'] = gulp.parallel(flashIndex, flashJs); exports['client:flash:html'] = gulp.parallel(flashIndex, flashJs);
exports['client:flash'] = gulp.series(prepare(Haxe.ID), generate(), build('flash'), exports['client:flash:html']); exports['client:flash'] = gulp.series(prepare(Haxe.ID), generate(), build('flash'), exports['client:flash:html']);
exports['client:html5'] = gulp.series(prepare(Haxe.ID), generate(), build('html5')); exports['client:html5'] = gulp.series(prepare(Haxe.ID), generate(), build('html5'));
exports['client:linux'] = gulp.series(prepare(Haxe.ID), generate(), build('linux'));
exports['client:webapp'] = webapp; exports['client:webapp'] = webapp;
exports['client'] = gulp.series(prepare(Haxe.ID), generate(), gulp.parallel(build('flash'), build('html5')), exports['client:flash:html'], webapp); exports['client:deb'] = buildDeb;
exports['client'] = gulp.series(
prepare(Haxe.ID),
generate(),
gulp.parallel(build('flash'), build('html5'), build('linux')),
exports['client:flash:html'],
webapp,
buildDeb,
);
@@ -70,15 +109,29 @@ const testFlash = function() {
}; };
const testHtml5 = function() { const testHtml5 = function() {
return gulp.series(build('html5'), () => gulp.src('target/html5').pipe(webserver({ return gulp.series(
build('html5'),
() => gulp.src('target/html5').pipe(webserver({
host: 'localhost', port: 3000, host: 'localhost', port: 3000,
open: true, open: true,
fallback: 'index.html' fallback: 'index.html'
})))(); }))
)();
}; };
const testLinux = gulp.series(
build('linux'),
() => gulp.src('target/linux/tankz')
.pipe(run('./tankz', {cwd: 'target/linux', verbosity: 1}))
.pipe(tail(debug.log))
.pipe(gulp.dest('target/log'))
);
exports['client:flash:test'] = gulp.series(prepare(Haxe.ID, FlashPlayer.ID), testFlash); exports['client:flash:test'] = gulp.series(prepare(Haxe.ID, FlashPlayer.ID), testFlash);
exports['client:html5:test'] = gulp.series(prepare(Haxe.ID), testHtml5); exports['client:html5:test'] = gulp.series(prepare(Haxe.ID), testHtml5);
exports['client:linux:test'] = gulp.series(prepare(Haxe.ID), testLinux);
exports['client:test'] = exports['client:flash:test']; exports['client:test'] = exports['client:flash:test'];

View File

@@ -1,23 +1,14 @@
const AdobeAir = require('../tasks/adobeAir'); const AdobeAir = require('../tasks/adobeAir');
const Haxe = require('../tasks/haxe'); const Haxe = require('../tasks/haxe');
const FlashPlayer = require('../tasks/flashplayer'); const FlashPlayer = require('../tasks/flashplayer');
const packageInfo = require('../package.json');
const packages = [
{name:'openfl', version:'6.5.3'},
{name:'lime', version:'5.9.1'},
{name:'promhx', version:'1.1.0'},
{name:'protohx', version:'0.4.6'},
{name:'yaml', version:'1.3.0'},
{name:'haxework', git:'git@bitbucket.org:shmyga/haxework.git'},
{name:'orm', version:'2.1.0'},
];
const prepareOne = (value) => { const prepareOne = (value) => {
switch (value) { switch (value) {
case Haxe.ID: case Haxe.ID:
const haxe = new Haxe(); const haxe = new Haxe();
return haxe.prepare().then(() => haxe.install(packages)); return haxe.prepare().then(() => haxe.install(packageInfo.haxeDependencies));
case AdobeAir.ID: case AdobeAir.ID:
return new AdobeAir().prepare(); return new AdobeAir().prepare();
case FlashPlayer.ID: case FlashPlayer.ID:

View File

@@ -6,7 +6,7 @@ user = 'holop'
# git # git
set :application, app set :application, app
set :repo_url, "git@bitbucket.org:shmyga/#{app}.git" set :repo_url, "git@bitbucket.org:infernalgames/#{app}.git"
set :user, user set :user, user
set :deploy_to, "/home/#{user}/repo/#{app}" set :deploy_to, "/home/#{user}/repo/#{app}"

View File

@@ -1,6 +1,6 @@
{ {
"name": "tankz", "name": "tankz",
"version": "0.0.6", "version": "0.2.1",
"private": true, "private": true,
"devDependencies": { "devDependencies": {
"ansi-colors": "^1.0.1", "ansi-colors": "^1.0.1",
@@ -15,6 +15,8 @@
"gulp-babel": "^7.0.0", "gulp-babel": "^7.0.0",
"gulp-clean": "^0.3.2", "gulp-clean": "^0.3.2",
"gulp-concat": "^2.6.1", "gulp-concat": "^2.6.1",
"gulp-debian": "^0.1.9",
"gulp-run": "^1.7.1",
"gulp-template": "^5.0.0", "gulp-template": "^5.0.0",
"gulp-uglify": "^3.0.0", "gulp-uglify": "^3.0.0",
"gulp-webserver": "^0.9.1", "gulp-webserver": "^0.9.1",
@@ -26,5 +28,14 @@
"unzip-stream": "^0.2.1", "unzip-stream": "^0.2.1",
"vinyl-fs": "^3.0.1", "vinyl-fs": "^3.0.1",
"yargs": "^10.0.3" "yargs": "^10.0.3"
},
"haxeDependencies": {
"lime": "6.0.1",
"openfl": "7.0.0",
"promhx": "1.1.0",
"protohx": "0.4.6",
"yaml": "1.3.0",
"haxework": "git@bitbucket.org:shmyga/haxework.git",
"orm": "2.1.0"
} }
} }

View File

@@ -6,15 +6,17 @@
<source path="src/client/haxe"/> <source path="src/client/haxe"/>
<source path="src-gen/haxe"/> <source path="src-gen/haxe"/>
<assets path="src/client/resources" rename="resources" include="*"/> <assets path="src/client/resources" rename="resources" include="*"/>
<haxelib name="openfl"/> <haxelib name="lime" version="6.0.1"/>
<haxelib name="protohx"/> <haxelib name="openfl" version="7.0.0"/>
<haxelib name="promhx" version="1.1.0"/>
<haxelib name="protohx" version="0.4.6"/>
<haxelib name="haxework" version="git"/> <haxelib name="haxework" version="git"/>
<haxelib name="yaml"/> <haxelib name="yaml" version="1.3.0"/>
<!--<window width="760" height="600"/>--> <window width="1024" height="768" unless="html5"/>
<haxeflag name="-D" value="swf-gpu"/> <haxeflag name="-D" value="swf-gpu"/>
<haxeflag name="-D" value="native-trace"/> <haxeflag name="-D" value="native-trace"/>
<!--<haxeflag name="-D" value="proto_debug"/>--> <!--<haxeflag name="-D" value="proto_debug"/>-->
<haxeflag name="-dce" value="no"/> <haxeflag name="-dce" value="no"/>
<haxeflag name="-D" value="dom"/>
<!--<template path="src/client/webapp/index_template.html" rename="index.html"/>--> <!--<template path="src/client/webapp/index_template.html" rename="index.html"/>-->
</project> </project>

View File

@@ -0,0 +1,9 @@
[Desktop Entry]
Encoding=UTF-8
Type=Application
Comment=Tank'z game
Exec=bash -c 'cd "/usr/share/tankz" && ./tankz'
Icon=/usr/share/tankz/resources/images/tank/player/tank_p3_0-0.png
Name=Tank'z
Categories=Game
Terminal=false

View File

@@ -1,6 +1,10 @@
{ {
"pWidth": 100, "pHeight": 100, "pWidth": 100, "pHeight": 100,
"views": [ "views": [
{
"id": "state", "@type": "haxework.gui.LabelView", "@style": "label",
"pWidth": 100, "height": 20
},
{ {
"id": "render", "@type": "ru.m.tankz.render.Render", "id": "render", "@type": "ru.m.tankz.render.Render",
"contentSize": true "contentSize": true

View File

@@ -0,0 +1,21 @@
{
"pWidth": 100, "pHeight": 100,
"views": [
{
"id":"levels", "@type":"haxework.gui.list.VListView<Int>",
"factory": "@class:ru.m.tankz.view.frames.list.LevelView",
"pWidth":100,
"pHeight":100,
"paddings":10,
"scroll":{
"@type":"haxework.gui.list.VScrollView",
"width":10, "pHeight":100,
"skin":{"@type":"haxework.gui.list.VScrollSkin"}
},
"skin": {
"@type": "haxework.gui.skin.ColorSkin",
"color": "0x000000", "alpha": 0.0
}
}
]
}

View File

@@ -19,9 +19,9 @@
"@style": "button" "@style": "button"
}, },
{ {
"id": "start_lan", "id": "dota",
"@type": "haxework.gui.ButtonView", "@type": "haxework.gui.ButtonView",
"text": "Multiplayer", "text": "DotA",
"@style": "button" "@style": "button"
} }
] ]

View File

@@ -15,6 +15,10 @@
"id": "start", "id": "start",
"@type": "ru.m.tankz.view.frames.StartFrame" "@type": "ru.m.tankz.view.frames.StartFrame"
}, },
{
"id": "level",
"@type": "ru.m.tankz.view.frames.LevelFrame"
},
{ {
"id": "game", "id": "game",
"@type": "ru.m.tankz.view.frames.GameFrame" "@type": "ru.m.tankz.view.frames.GameFrame"

View File

@@ -1,13 +1,18 @@
{ {
"person": { "level": {
"width": 440, "height": 44, "width": 440, "height": 44,
"margins": 5,
"views": [ "views": [
{ {
"id": "nameLabel", "id": "label",
"@type": "haxework.gui.LabelView", "@type": "haxework.gui.LabelView",
"pWidth": 100, "height": 44, "text": "Выбор персонажа", "pWidth": 100, "pHeight": 100, "text": "",
"@style": "label" "@style": "label"
} }
] ],
"skin": {
"@type": "haxework.gui.skin.ColorSkin",
"color": "0x000000", "alpha": 0.2
}
} }
} }

View File

@@ -1,5 +1,8 @@
package ru.m.tankz; package ru.m.tankz;
import ru.m.tankz.game.ClassicGame;
import ru.m.tankz.game.DotaGame;
import ru.m.tankz.game.Game;
import flash.ui.Keyboard; import flash.ui.Keyboard;
import flash.events.KeyboardEvent; import flash.events.KeyboardEvent;
import flash.text.Font; import flash.text.Font;
@@ -10,8 +13,6 @@ import haxework.resources.IResources;
import haxework.gui.VGroupView; import haxework.gui.VGroupView;
import haxework.gui.ViewBuilder; import haxework.gui.ViewBuilder;
import haxework.gui.ButtonView; import haxework.gui.ButtonView;
import flash.display.Sprite;
import haxework.gui.IGroupView;
import ru.m.tankz.PacketBuilder; import ru.m.tankz.PacketBuilder;
import haxework.log.JSLogger; import haxework.log.JSLogger;
import haxework.gui.frame.IFrameSwitcher; import haxework.gui.frame.IFrameSwitcher;
@@ -61,11 +62,11 @@ class Client implements IConnectionHandler {
Provider.set(IConnection, new ru.m.connect.js.JsConnection("localhost", 5001)); Provider.set(IConnection, new ru.m.connect.js.JsConnection("localhost", 5001));
#end #end
Provider.get(IConnection).handler.addListener(this); //Provider.get(IConnection).handler.addListener(this);
view = new MainView(); view = new MainView();
Provider.set(IFrameSwitcher, view.switcher); Provider.set(IFrameSwitcher, view.switcher);
Root.bind(view); Root.bind(view);
view.content.stage.stageFocusRect = false;
//view.logout.onPress = this; //view.logout.onPress = this;
view.switcher.change(StartFrame.ID); view.switcher.change(StartFrame.ID);
@@ -74,6 +75,9 @@ class Client implements IConnectionHandler {
view.switcher.change(StartFrame.ID); view.switcher.change(StartFrame.ID);
} }
}); });
Provider.setFactory(Game, ClassicGame, ClassicGame.TYPE);
Provider.setFactory(Game, DotaGame, DotaGame.TYPE);
} }
public function onPress(view:ButtonView):Void { public function onPress(view:ButtonView):Void {

View File

@@ -1,27 +1,27 @@
package ru.m.tankz.core; package ru.m.tankz.control;
import ru.m.tankz.control.Control;
import haxe.Timer; import haxe.Timer;
import ru.m.geom.Direction; import ru.m.geom.Direction;
import flash.events.FocusEvent; import flash.events.FocusEvent;
import flash.ui.Keyboard; import flash.ui.Keyboard;
import ru.m.tankz.engine.Engine;
import ru.m.tankz.core.Tank.TankAction;
import flash.events.KeyboardEvent; import flash.events.KeyboardEvent;
import flash.Lib; import flash.Lib;
class PlayerControl {
private var engine:Engine; typedef KeyBinding = Map<Int, TankAction>;
private var id:Int;
private var keyBinding:Map<Int, TankAction>;
class HumanControl extends Control {
public static var TYPE(default, never):ControlType = 'human';
private var keyBinding:KeyBinding;
private var moveQueue:Array<Int>; private var moveQueue:Array<Int>;
private var shotTimer:Timer; private var shotTimer:Timer;
public function new(id:Int, engine:Engine, keyBinding:Map<Int, TankAction>) { public function new(index:Int) {
this.id = id; super({type:TYPE, index:index});
this.engine = engine; this.keyBinding = resolve(index);
this.keyBinding = keyBinding;
moveQueue = new Array<Int>(); moveQueue = new Array<Int>();
Lib.current.stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown); Lib.current.stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
Lib.current.stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp); Lib.current.stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp);
@@ -44,7 +44,7 @@ class PlayerControl {
case _: case _:
} }
if (event.keyCode == Keyboard.U) { if (event.keyCode == Keyboard.U) {
engine.action(id, TankAction.LEVEL_UP(1)); action(TankAction.LEVEL_UP(1));
} }
} }
@@ -69,40 +69,40 @@ class PlayerControl {
private function updateMove():Void { private function updateMove():Void {
if (moveQueue.length == 0) { if (moveQueue.length == 0) {
engine.action(id, TankAction.STOP); action(TankAction.STOP);
} else { } else {
switch (keyBinding.get(moveQueue[0])) { switch (keyBinding.get(moveQueue[0])) {
case TankAction.MOVE(direction): case TankAction.MOVE(direction):
engine.action(id, TankAction.MOVE(direction)); action(TankAction.MOVE(direction));
case _: case _:
} }
} }
} }
private function shot():Void { private function shot():Void {
engine.action(id, TankAction.SHOT); action(TankAction.SHOT);
} }
public static function forPlayer(index:Int, tankId:Int, engine:Engine):PlayerControl { private static function resolve(index:Int):KeyBinding {
switch (index) { switch (index) {
case 0: case 0:
return new PlayerControl(tankId, engine, [ return [
Keyboard.A => TankAction.MOVE(Direction.LEFT), Keyboard.A => TankAction.MOVE(Direction.LEFT),
Keyboard.S => TankAction.MOVE(Direction.BOTTOM), Keyboard.S => TankAction.MOVE(Direction.BOTTOM),
Keyboard.W => TankAction.MOVE(Direction.TOP), Keyboard.W => TankAction.MOVE(Direction.TOP),
Keyboard.D => TankAction.MOVE(Direction.RIGHT), Keyboard.D => TankAction.MOVE(Direction.RIGHT),
Keyboard.SPACE => TankAction.SHOT Keyboard.SPACE => TankAction.SHOT
]); ];
case 1: case 1:
return new PlayerControl(tankId, engine, [ return [
Keyboard.LEFT => TankAction.MOVE(Direction.LEFT), Keyboard.LEFT => TankAction.MOVE(Direction.LEFT),
Keyboard.DOWN => TankAction.MOVE(Direction.BOTTOM), Keyboard.DOWN => TankAction.MOVE(Direction.BOTTOM),
Keyboard.UP => TankAction.MOVE(Direction.TOP), Keyboard.UP => TankAction.MOVE(Direction.TOP),
Keyboard.RIGHT => TankAction.MOVE(Direction.RIGHT), Keyboard.RIGHT => TankAction.MOVE(Direction.RIGHT),
Keyboard.NUMPAD_0 => TankAction.SHOT Keyboard.NUMPAD_0 => TankAction.SHOT
]); ];
case _: case _:
throw 'Invalid player index ${index}'; throw 'Invalid control index ${index}';
} }
} }
} }

View File

@@ -1,10 +0,0 @@
package ru.m.tankz.render;
import ru.m.tankz.engine.Engine;
import haxework.gui.IView;
interface IRender extends IView {
public function draw(game:Engine):Void;
public function reset():Void;
}

View File

@@ -1,12 +1,11 @@
package ru.m.tankz.render; package ru.m.tankz.render;
import ru.m.tankz.core.MobileEntity; import flash.display.DisplayObjectContainer;
import ru.m.tankz.core.EntityType;
import flash.display.DisplayObject;
import Type.ValueType;
import ru.m.tankz.render.RenderItem;
import ru.m.tankz.engine.Engine; import ru.m.tankz.engine.Engine;
import ru.m.tankz.core.Entity;
import ru.m.tankz.map.Brick;
import ru.m.geom.Direction;
import flash.geom.Matrix;
import openfl.Assets;
import ru.m.tankz.core.Bullet; import ru.m.tankz.core.Bullet;
import ru.m.tankz.core.Tank; import ru.m.tankz.core.Tank;
import flash.display.Sprite; import flash.display.Sprite;
@@ -14,64 +13,18 @@ import flash.display.Graphics;
import haxework.gui.SpriteView; import haxework.gui.SpriteView;
interface IState<T> { class Render extends SpriteView implements EngineListener {
public function update(object:T):Bool;
}
class BrickState implements IState<Brick> {
private var type:Int;
public function new() {}
public function update(object:Brick):Bool {
if (type != object.config.type) {
type = object.config.type;
return true;
}
return false;
}
}
class EntityState implements IState<Entity> {
private var x:Float;
private var y:Float;
private var d:Direction;
private var type:Int;
public function new() {}
public function update(object:Entity):Bool {
if (x != object.rect.x || y != object.rect.y || !d.equals(object.rect.direction)) {
x = object.rect.x;
y = object.rect.y;
d = object.rect.direction;
return true;
}
if (Std.is(object, Tank)) {
var tank:Tank = cast object;
if (tank.config.type != type) {
type = tank.config.type;
return true;
}
}
return false;
}
}
class Render extends SpriteView implements IRender {
private var backgroundLayer:Sprite; private var backgroundLayer:Sprite;
private var groundLayer:Sprite; private var groundLayer:Sprite;
private var entryLayer:Sprite; private var entryLayer:Sprite;
private var upLayer:Sprite; private var upLayer:Sprite;
private var layersForUpdate:Map<Sprite, Bool>; private var background:Sprite;
private var states:Map<String, IState<Dynamic>>; private var items:Map<String, RenderItem<Dynamic>>;
public function new() { public function new() {
super(); super();
layersForUpdate = new Map();
backgroundLayer = new Sprite(); backgroundLayer = new Sprite();
groundLayer = new Sprite(); groundLayer = new Sprite();
entryLayer = new Sprite(); entryLayer = new Sprite();
@@ -83,38 +36,9 @@ class Render extends SpriteView implements IRender {
reset(); reset();
} }
private function invalidateLayers(game:Engine):Void {
for (brick in game.map.bricks) {
if (!states.exists(brick.key)) {
states[brick.key] = new BrickState();
}
if (states.get(brick.key).update(brick)) {
layersForUpdate[groundLayer] = true;
layersForUpdate[upLayer] = true;
}
}
layersForUpdate[groundLayer] = true; //ToDo:
for (entry in game.entities) {
if (!states.exists(entry.key)) {
states[entry.key] = new EntityState();
}
if (states.get(entry.key).update(entry)) {
layersForUpdate[entryLayer] = true;
}
}
for (key in game.removedEntities) {
if (states.exists(key)) {
states.remove(key);
layersForUpdate[entryLayer] = true;
}
}
}
private function drawBackground(game:Engine):Void { private function drawBackground(game:Engine):Void {
var mapWidth = game.map.gridWidth * game.map.cellWidth; var mapWidth = game.map.gridWidth * game.map.cellWidth;
var mapHeight = game.map.gridHeight * game.map.cellHeight; var mapHeight = game.map.gridHeight * game.map.cellHeight;
if (layersForUpdate[backgroundLayer]) {
var g:Graphics = backgroundLayer.graphics; var g:Graphics = backgroundLayer.graphics;
g.clear(); g.clear();
g.beginFill(0x000000); g.beginFill(0x000000);
@@ -124,119 +48,85 @@ class Render extends SpriteView implements IRender {
width = mapWidth; width = mapWidth;
height = mapHeight; height = mapHeight;
} }
layersForUpdate[backgroundLayer] = false;
}
}
private function drawMap(game:Engine):Void {
if (layersForUpdate[groundLayer] || layersForUpdate[upLayer]) {
groundLayer.graphics.clear();
upLayer.graphics.clear();
for (brick in game.map.bricks) if (!brick.destroyed) {
var g:Graphics = null;
if (brick.config.layer < 3) {
g = groundLayer.graphics;
} else if (brick.config.layer >= 3) {
g = upLayer.graphics;
}
if (g != null) {
g.beginBitmapFill(Assets.getBitmapData('resources/images/map/map_${brick.config.type}.png'));
g.drawRect(
brick.rect.x,
brick.rect.y,
brick.rect.width,
brick.rect.height
);
for (cell in brick.cells.iterator()) {
if (cell.destroyed) {
g.beginFill(0x000000);
g.drawRect(
cell.rect.x,
cell.rect.y,
cell.rect.width,
cell.rect.height
);
}
}
g.endFill();
}
}
// Debug cells
/*var g = groundLayer.graphics;
for (c in game.map.grid.cells.iterator()) {
var color:Int = 0x000000;
if (c.armor == 1) {
color = 0xffcc00;
} else if (c.armor > 1) {
color = 0x606060;
} else if (c.layer == 0) {
color = 0xffff90;
} else if (c.layer == 1) {
color = 0x0000ff;
}else if (c.layer > 1) {
color = 0x00ff00;
}
g.beginFill(color);
g.drawRect(c.rect.x, c.rect.y, c.rect.width, c.rect.height);
g.endFill();
}
layersForUpdate[groundLayer] = false;
layersForUpdate[upLayer] = false;*/
}
}
public function drawEntities(game:Engine):Void {
if (layersForUpdate[entryLayer]) {
var g:Graphics = entryLayer.graphics;
g.clear();
for (ent in game.entities) {
var image:String = null;
if (Std.is(ent, Tank)) {
var tank:Tank = cast ent;
image = 'resources/images/tank/${tank.config.group}/tank_${tank.config.group.charAt(0)}${tank.config.type}_${tank.index}-0.png';
} else if (Std.is(ent, Bullet)) {
var bullet:Bullet = cast ent;
image = 'resources/images/bullet/bullet_${bullet.config.piercing > 1 ? 1 : 0}.png';
} else {
image = 'ERROR'; // ToDo:
}
var m = new Matrix();
if (Std.is(ent, MobileEntity)) {
m.rotate(calcRotate(cast(ent, MobileEntity).direction));
}
m.translate(ent.rect.x, ent.rect.y);
g.beginBitmapFill(Assets.getBitmapData(image), m, true, true);
g.drawRect(ent.rect.x, ent.rect.y, ent.rect.width, ent.rect.height);
g.endFill();
}
layersForUpdate[entryLayer] = false;
}
} }
public function draw(game:Engine):Void { public function draw(game:Engine):Void {
invalidateLayers(game); for (brick in game.map.bricks) if (brick.config.type > 0) {
if (!items.exists(brick.key)) {
items[brick.key] = new BrickItem(brick);
if (brick.config.layer > 2) {
upLayer.addChild(items[brick.key].view);
} else {
groundLayer.addChild(items[brick.key].view);
}
}
}
for (item in items) {
item.update();
}
if (background == null) {
drawBackground(game); drawBackground(game);
drawMap(game); }
drawEntities(game); }
private function clearLayer(layer:DisplayObjectContainer) {
while (layer.numChildren > 0) layer.removeChildAt(0);
} }
public function reset():Void { public function reset():Void {
states = new Map<String, IState<Dynamic>>(); items = new Map<String, RenderItem<Dynamic>>();
layersForUpdate[backgroundLayer] = true; if (background != null) {
backgroundLayer.removeChild(background);
background = null;
}
clearLayer(entryLayer);
clearLayer(groundLayer);
clearLayer(upLayer);
} }
private function calcRotate(direction:Direction):Float { public function onSpawn(entity:EntityType):Void {
return (if (direction == Direction.RIGHT) { switch(entity) {
0; case EntityType.TANK(tank):
} else if (direction == Direction.LEFT) { var item = new TankItem(tank);
180; items.set(tank.key, item);
} else if (direction == Direction.TOP) { entryLayer.addChild(item.view);
270; item.update();
} else if (direction == Direction.BOTTOM) { case EntityType.BULLET(bullet):
90; var item = new BulletItem(bullet);
} else { items.set(bullet.key, item);
0; entryLayer.addChild(item.view);
}) * (Math.PI / 180); item.update();
case EntityType.EAGLE(eagle):
var item = new EagleItem(eagle);
items.set(eagle.key, item);
entryLayer.addChild(item.view);
item.update();
case _:
}
}
public function onCollision(entity:EntityType, with:EntityType):Void {
}
public function onDestroy(entity:EntityType):Void {
switch(entity) {
case EntityType.TANK(tank):
if (items.exists(tank.key)) {
entryLayer.removeChild(items.get(tank.key).view);
items.remove(tank.key);
}
case EntityType.BULLET(bullet):
if (items.exists(bullet.key)) {
entryLayer.removeChild(items.get(bullet.key).view);
items.remove(bullet.key);
}
case EntityType.EAGLE(eagle):
if (items.exists(eagle.key)) {
cast(items.get(eagle.key), EagleItem).destoyed = true;
items.get(eagle.key).redraw();
}
case _:
}
} }
} }

View File

@@ -0,0 +1,154 @@
package ru.m.tankz.render;
import ru.m.tankz.core.Eagle;
import flash.display.DisplayObject;
import flash.display.Shape;
import ru.m.geom.Direction;
import ru.m.geom.Rectangle;
import ru.m.tankz.core.Bullet;
import ru.m.tankz.map.Brick;
import openfl.Assets;
import ru.m.tankz.core.Tank;
import flash.display.Bitmap;
typedef TRectangle = {
var rect(default, null):Rectangle;
}
class RenderItem<T:TRectangle> {
public var value(default, null):T;
public var view(default, null):DisplayObject;
private var bitmap:Bitmap;
public function new(value:T) {
this.value = value;
this.bitmap = new Bitmap();
this.view = bitmap;
redraw();
}
public function redraw():Void {
bitmap.bitmapData = Assets.getBitmapData(getImage());
}
public function update():Void {
var rect = value.rect;
view.rotation = calcRotate(rect.direction);
view.x = rect.x - rect.width * (rect.direction.x + 1) / 2 + rect.width * (rect.direction.y + 1) / 2 + 0.5 * rect.width;
view.y = rect.y - rect.height * (rect.direction.x + 1) / 2 - rect.height * (rect.direction.y + 1) / 2 + 1.5 * rect.height;
}
private function getImage():String {
return 'ERROR';
}
private static function calcRotate(direction:Direction):Float {
return (if (direction == Direction.RIGHT) {
0;
} else if (direction == Direction.LEFT) {
180;
} else if (direction == Direction.TOP) {
270;
} else if (direction == Direction.BOTTOM) {
90;
} else {
0;
});
}
}
class BrickItem extends RenderItem<Brick> {
private var shape:Shape;
private var broken:Int;
public function new(value:Brick) {
this.shape = new Shape();
super(value);
this.view = shape;
}
override public function redraw():Void {
var image = Assets.getBitmapData(getImage());
var g = shape.graphics;
g.clear();
if (value.destroyed) return;
g.beginBitmapFill(image);
g.drawRect(0, 0, value.rect.width, value.rect.height);
for (c in value.cells) {
if (c.destroyed) {
g.beginFill(0x000000);
g.drawRect(c.rect.x - value.rect.x, c.rect.y - value.rect.y, c.rect.width, c.rect.height);
}
}
g.endFill();
}
override public function update():Void {
super.update();
var b = value.broken;
if (b != this.broken) {
this.broken = b;
redraw();
}
}
override private function getImage():String {
return 'resources/images/map/map_${value.config.type}.png';
}
}
class TankItem extends RenderItem<Tank> {
private var type:String;
override private function getImage():String {
var group = value.config.group;
var index = value.playerId.index;
if (group == 'human') group = 'player';
if (group == 'radiant') {
group = 'player';
index = 0;
}
if (group == 'dire') {
group = 'player';
index = 1;
}
if (group == 'bot') index = 0;
return 'resources/images/tank/${group}/tank_${group.charAt(0)}${value.config.type}_${index}-0.png';
}
override public function update():Void {
super.update();
var t = value.config.type;
if (t != this.type) {
this.type = t;
redraw();
}
}
}
class BulletItem extends RenderItem<Bullet> {
override private function getImage():String {
return 'resources/images/bullet/bullet_${value.config.piercing > 1 ? 1 : 0}.png';
}
}
class EagleItem extends RenderItem<Eagle> {
public var destoyed(default, default):Bool;
override private function getImage():String {
return 'resources/images/eagle/eagle-${destoyed ? 1 : 0}.png';
}
}

View File

@@ -1,62 +1,129 @@
package ru.m.tankz.view.frames; package ru.m.tankz.view.frames;
import ru.m.tankz.config.ConfigBundle; import haxe.ds.Option;
import ru.m.tankz.proto.core.GameType; import haxe.ds.Option;
import ru.m.tankz.proto.core.Game;
import ru.m.tankz.core.PlayerControl;
import ru.m.tankz.engine.Engine;
import protohx.Message;
import ru.m.tankz.proto.pack.GameUpdateResponse;
import ru.m.connect.IConnection;
import haxework.gui.ViewBuilder;
import flash.events.Event; import flash.events.Event;
import haxework.provider.Provider; import haxe.Timer;
import haxework.gui.frame.IFrameSwitcher;
import haxework.gui.LabelView;
import haxework.gui.VGroupView; import haxework.gui.VGroupView;
import haxework.gui.ViewBuilder;
import haxework.provider.Provider;
import protohx.Message;
import ru.m.connect.IConnection;
import ru.m.tankz.game.Game;
import ru.m.tankz.game.GameState;
import ru.m.tankz.proto.pack.GameUpdateResponse;
import ru.m.tankz.render.Render;
interface GameFrameLayout {
var state(default, null):LabelView;
var render(default, null):Render;
}
@:template("layout/frames/game.json", "layout/styles.json") @:template("layout/frames/game.json", "layout/styles.json")
class GameFrame extends VGroupView implements ViewBuilder implements IPacketHandler { class GameFrame extends VGroupView implements ViewBuilder implements IPacketHandler implements GameFrameLayout {
private static inline var TAG = "GameFrame"; private static inline var TAG = "GameFrame";
public static inline var ID = "game"; public static inline var ID = "game";
private var engine:Engine; private var game:Game;
private var controls:Map<Int, PlayerControl>; private var timer:Timer;
public function init():Void { public function init():Void {}
engine = new Engine();
controls = new Map<Int, PlayerControl>();
}
public function onShow():Void { public function onShow():Void {
var game:Game = Provider.get(Game); start(Provider.get(GameState));
engine.init(ConfigBundle.get(GameType.CLASSIC, 0));
engine.initTanks(game.players);
content.addEventListener(Event.ENTER_FRAME, updateGame);
for (index in 0...game.players.length) {
var playerId:Int = game.players[index].id;
controls.set(playerId, PlayerControl.forPlayer(index, playerId, engine));
}
Provider.get(IConnection).packetHandler.addListener(this);
render.draw(engine);
} }
public function onHide():Void { private function start(s:GameState):Void {
Provider.get(IConnection).packetHandler.removeListener(this); game = Provider.build(Game, s.type);
content.removeEventListener(Event.ENTER_FRAME, updateGame); if (game == null) {
engine.clear(); throw 'Unsupported game type "${s.type}"';
}
game.engine.listeners.push(render);
game.start(s).then(onGameStateChange).endThen(onGameComplete);
content.addEventListener(Event.ENTER_FRAME, redraw);
//Provider.get(IConnection).packetHandler.addListener(this);
render.draw(game.engine);
timer = new Timer(10);
timer.run = updateEngine;
state.text = stateString(s);
}
private function stop():Void {
//Provider.get(IConnection).packetHandler.removeListener(this);
if (timer != null) {
timer.stop();
timer = null;
}
content.removeEventListener(Event.ENTER_FRAME, redraw);
if (game != null) {
game.dispose();
game = null;
}
render.reset(); render.reset();
} }
private function updateGame(_):Void { private function stateString(state:GameState):String {
engine.update(); var result:Array<String> = [];
render.draw(engine); result.push('Level: ${state.level}');
for (teamId in state.teams.keys()) {
var ts:TeamState = state.teams[teamId];
if (ts.lose) {
result.push('${teamId}: LOSE');
} else if (ts.life > -1) {
result.push('${teamId}: ${ts.life}');
} else {
for (index in ts.players.keys()) {
var ps:PlayerState = ts.players[index];
if (ps.life > -1) {
result.push('${teamId}${index}: ${ps.life}');
}
}
}
}
return '[ ${result.join(' | ')} ]';
}
private function onGameStateChange(s:GameState):GameState {
state.text = stateString(s);
return s;
}
private function onGameComplete(result:Option<GameState>):Void {
switch (result) {
case Option.Some(s):
state.text = stateString(s);
case Option.None:
}
switch (game.next()) {
case Option.Some(s):
stop();
start(s);
case Option.None:
Provider.get(IFrameSwitcher).change(StartFrame.ID);
}
}
public function onHide():Void {
stop();
}
private function updateEngine():Void {
game.engine.update();
}
private function redraw(_):Void {
render.draw(game.engine);
} }
public function onGameUpdateResponse(packet:GameUpdateResponse):Void { public function onGameUpdateResponse(packet:GameUpdateResponse):Void {
//engine.updateFromChanges(packet.changes); //engine.updateFromChanges(packet.changes);
render.draw(engine); render.draw(game.engine);
} }
public function onPacket(packet:Message):Void {} public function onPacket(packet:Message):Void {}

View File

@@ -0,0 +1,35 @@
package ru.m.tankz.view.frames;
import ru.m.tankz.config.ConfigBundle;
import haxework.gui.frame.IFrameSwitcher;
import ru.m.tankz.game.GameState;
import haxework.provider.Provider;
import haxework.gui.list.ListView;
import haxework.gui.ViewBuilder;
import haxework.gui.VGroupView;
interface LevelFrameLayout {
var levels(default, null):ListView<Int>;
}
@:template("layout/frames/level.json", "layout/styles.json")
class LevelFrame extends VGroupView implements ViewBuilder implements LevelFrameLayout implements ListViewListener<Int> {
public static inline var ID = "level";
public function init():Void {
levels.dispatcher.addListener(this);
}
public function onShow():Void {
var state = Provider.get(GameState);
var c = ConfigBundle.get(state.type).levels;
levels.data = [for (i in 0...c) i];
}
public function onListItemClick(item:IListItemView<Int>):Void {
Provider.get(GameState).level = item.data;
Provider.get(IFrameSwitcher).change(GameFrame.ID);
}
}

View File

@@ -1,43 +1,53 @@
package ru.m.tankz.view.frames; package ru.m.tankz.view.frames;
import ru.m.tankz.proto.core.GameType; import ru.m.tankz.game.Game;
import ru.m.tankz.proto.core.Player; import ru.m.tankz.game.GameState;
import ru.m.tankz.proto.core.Game; import ru.m.tankz.game.ClassicGame;
import ru.m.tankz.game.DotaGame;
import haxework.gui.frame.IFrameSwitcher; import haxework.gui.frame.IFrameSwitcher;
import haxework.provider.Provider; import haxework.provider.Provider;
import haxework.gui.ButtonView; import haxework.gui.ButtonView;
import haxework.gui.ViewBuilder; import haxework.gui.ViewBuilder;
import haxework.gui.VGroupView; import haxework.gui.VGroupView;
interface StartFrameLayout {
var start_1p(default, null):ButtonView;
var start_2p(default, null):ButtonView;
var dota(default, null):ButtonView;
}
@:template("layout/frames/start.json", "layout/styles.json") @:template("layout/frames/start.json", "layout/styles.json")
class StartFrame extends VGroupView implements ViewBuilder { class StartFrame extends VGroupView implements ViewBuilder implements StartFrameLayout {
public static inline var ID = "start"; public static inline var ID = "start";
public function init() { public function init() {
start_1p.onPress = this; start_1p.onPress = this;
start_2p.onPress = this; start_2p.onPress = this;
dota.onPress = this;
} }
public function onPress(view:ButtonView):Void { public function onPress(view:ButtonView):Void {
switch (view.id) { switch (view.id) {
case "start_1p": case 'start_1p':
startGame(1); startGame(ClassicGame.TYPE, 1);
case "start_2p": case 'start_2p':
startGame(2); startGame(ClassicGame.TYPE, 2);
case 'dota':
startGame(DotaGame.TYPE, 2);
} }
} }
private function startGame(playersCount:Int):Void { private function startGame(type:GameType, humans:Int):Void {
var game = new Game(); switch (type) {
game.type = GameType.CLASSIC; case ClassicGame.TYPE:
for (i in 0...playersCount) { Provider.set(GameState, ClassicGame.buildState(0, humans));
var player = new Player(); Provider.get(IFrameSwitcher).change(LevelFrame.ID);
player.id = i; case DotaGame.TYPE:
game.players.push(player); Provider.set(GameState, DotaGame.buildState(0, humans));
}
game.id = 1;
Provider.set(Game, game);
Provider.get(IFrameSwitcher).change(GameFrame.ID); Provider.get(IFrameSwitcher).change(GameFrame.ID);
} }
} }
}

View File

@@ -1,33 +0,0 @@
package ru.m.tankz.view.frames.list;
import ru.m.tankz.proto.Person;
import ru.m.tankz.proto.Game;
import haxework.gui.list.ListView.IListItemView;
import haxework.gui.LabelView;
import haxework.gui.skin.ColorSkin;
import haxework.gui.HGroupView;
class GameView extends HGroupView implements IListItemView<Game> {
public var item_index(default, default):Int;
public var data(default, set):Game;
private var nameLabel:LabelView;
public function new() {
super();
pWidth = 100;
height = 50;
skin = new ColorSkin(0xffffff);
nameLabel = new LabelView();
addView(nameLabel);
}
private function set_data(value:Game):Game {
this.data = value;
var persons:String = Lambda.map(this.data.persons, function(p:Person) return p.name).join(",");
nameLabel.text = "Game_" + this.data.id + "(" + persons + ")";
return this.data;
}
}

View File

@@ -0,0 +1,24 @@
package ru.m.tankz.view.frames.list;
import haxework.gui.ViewBuilder;
import haxework.gui.HGroupView;
import haxework.gui.LabelView;
import haxework.gui.list.ListView.IListItemView;
interface LevelViewLayout {
var label(default, null):LabelView;
}
@:template("layout/other.json@level", "layout/styles.json")
class LevelView extends HGroupView implements ViewBuilder implements IListItemView<Int> implements LevelViewLayout {
public var item_index(default, default):Int;
public var data(default, set):Int;
private function set_data(value:Int):Int {
data = value;
label.text = 'Level ${data}';
return data;
}
}

View File

@@ -1,19 +0,0 @@
package ru.m.tankz.view.frames.list;
import haxework.gui.ViewBuilder;
import haxework.gui.list.ListView.IListItemView;
import ru.m.tankz.proto.Person;
import haxework.gui.HGroupView;
@:template("layout/other.json@person", "layout/styles.json")
class PersonView extends HGroupView implements ViewBuilder implements IListItemView<Person> {
public var item_index(default, default):Int;
public var data(default, set):Person;
private function set_data(value:Person):Person {
this.data = value;
nameLabel.text = this.data.name;
return this.data;
}
}

View File

@@ -1,35 +1,10 @@
levels: 36
map: map:
cellWidth: 22 cellWidth: 22
cellHeight: 22 cellHeight: 22
gridWidth: 26 gridWidth: 26
gridHeight: 26 gridHeight: 26
bricks: null
points:
- type: player
index: 0
x: 8
y: 24
direction: top
- type: player
index: 1
x: 16
y: 24
direction: top
- type: bot
index: 1
x: 0
y: 0
direction: bottom
- type: bot
index: 2
x: 12
y: 0
direction: bottom
- type: bot
index: 3
x: 24
y: 0
direction: bottom
bricks: bricks:
# border # border
@@ -61,6 +36,44 @@ bricks:
layer: 2 layer: 2
armor: 1 armor: 1
teams:
- id: human
spawnInterval: 0
points:
- type: eagle
index: -1
x: 12
y: 24
direction: right
- type: tank
index: 0
x: 8
y: 24
direction: top
- type: tank
index: 1
x: 16
y: 24
direction: top
- id: bot
spawnInterval: 3000
points:
- type: tank
index: -1
x: 0
y: 0
direction: bottom
- type: tank
index: -1
x: 12
y: 0
direction: bottom
- type: tank
index: -1
x: 24
y: 0
direction: bottom
bullet: &bullet bullet: &bullet
width: 12 width: 12
height: 12 height: 12
@@ -68,7 +81,7 @@ bullet: &bullet
piercing: 1 piercing: 1
tanks: tanks:
player: human:
- type: 0 - type: 0
width: 36 width: 36
height: 36 height: 36

View File

@@ -0,0 +1,134 @@
levels: 36
map:
cellWidth: 22
cellHeight: 22
gridWidth: 40
gridHeight: 30
bricks:
# border
- type: -1
layer: 2
armor: -1
# none
- type: 0
layer: 0
armor: 0
# ace
- type: 1
layer: 0
armor: 0
# bush
- type: 2
layer: 3
armor: 0
# water
- type: 3
layer: 1
armor: 0
# armor
- type: 4
layer: 2
armor: 2
# brick
- type: 5
layer: 2
armor: 1
teams:
- id: radiant
spawnInterval: 0
points:
- type: eagle
index: -1
x: 0
y: 28
direction: right
- type: tank
index: 0
x: 0
y: 0
direction: top
- type: tank
index: 1
x: 6
y: 10
direction: top
- type: tank
index: 2
x: 6
y: 16
direction: top
- type: tank
index: 3
x: 6
y: 22
direction: top
- type: tank
index: 4
x: 10
y: 28
direction: top
- id: dire
spawnInterval: 0
points:
- type: eagle
index: -1
x: 38
y: 0
direction: right
- type: tank
index: 0
x: 38
y: 28
direction: bottom
- type: tank
index: 1
x: 32
y: 18
direction: bottom
- type: tank
index: 2
x: 32
y: 12
direction: bottom
- type: tank
index: 3
x: 32
y: 6
direction: bottom
- type: tank
index: 4
x: 28
y: 0
direction: bottom
bullet: &bullet
width: 12
height: 12
speed: 0
piercing: 1
tanks:
radiant: &tanks
- type: 0
width: 36
height: 36
speed: 2.5
bullet:
<<: *bullet
speed: 8.0
bullets: 1
- type: 1
width: 40
height: 36
speed: 3.0
bullet:
<<: *bullet
speed: 8.5
bullets: 1
dire:
- <<: *tanks

View File

@@ -0,0 +1,30 @@
0000000000000000000000000000000000005500
0000000000000000000000000000000000005500
0000000000000000000000000000000000005555
0000000000000000000000000000000000005555
0000000000000000000000000000000000000000
0000000000000000000000000000000000000000
0000000000000000000000000000000000000000
0000000000000000000000000000000000000000
0000000000000000000000000000000000000000
0000000000000000000000000000000000000000
0000000000000000000000000000000000000000
0000000000000000000000000000000000000000
0000000000000000000000000000000000000000
0000000000000000000000000000000000000000
0000000000000000000000000000000000000000
0000000000000000000000000000000000000000
0000000000000000000000000000000000000000
0000000000000000000000000000000000000000
0000000000000000000000000000000000000000
0000000000000000000000000000000000000000
0000000000000000000000000000000000000000
0000000000000000000000000000000000000000
0000000000000000000000000000000000000000
0000000000000000000000000000000000000000
0000000000000000000000000000000000000000
0000000000000000000000000000000000000000
5555000000000000000000000000000000000000
5555000000000000000000000000000000000000
0055000000000000000000000000000000000000
0055000000000000000000000000000000000000

View File

@@ -1,161 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg"
><metadata
></metadata
><defs
><font horiz-adv-x="300"
><font-face font-family="8BIT WONDER" units-per-em="300" ascent="300" descent="0"
></font-face
><missing-glyph horiz-adv-x="150" d="M19 0V300H131V0H19ZM38 19h75V281H38V19Z"
></missing-glyph
><glyph unicode=" " horiz-adv-x="150" d=""
></glyph
><glyph unicode="#" d="M300 0H0V300H300V0ZM250 50V250H50V50H250Z"
></glyph
><glyph unicode="(" horiz-adv-x="150" d="M150 0H25V25H0V225H25v25H150V200H100V50h50V0Z"
></glyph
><glyph unicode=")" horiz-adv-x="150" d="M150 25H125V0H0V50H50V200H0v50H125V225h25V25Z"
></glyph
><glyph unicode="*" d="M250 220L230 200H220l-20 20v10l20 20h10l20 -20V220ZM200 165L185 150H165l-15 15v20l15 15h20l15 -15V165ZM150 110L140 100H110l-10 10v30l10 10h30l10 -10V110ZM50 220L30 200H20L0 220v10l20 20H30L50 230V220ZM100 165L85 150H65L50 165v20l15 15H85l15 -15V165ZM200 65L185 50H165L150 65V85l15 15h20L200 85V65ZM250 20L230 0H220L200 20V30l20 20h10L250 30V20ZM100 65L85 50H65L50 65V85l15 15H85L100 85V65ZM50 20L30 0H20L0 20V30L20 50H30L50 30V20Z"
></glyph
><glyph unicode="0" d="M250 25H225V0H25V25H0V225H25v25H225V225h25V25ZM150 150v50H100V150h50ZM150 50v50H100V50h50Z"
></glyph
><glyph unicode="1" horiz-adv-x="150" d="M100 0H0V200H-25v50H75V225h25V0Z"
></glyph
><glyph unicode="2" d="M250 0H0V125H25v25H150v50H0v50H225V225h25V125H225V100H100V50H250V0Z"
></glyph
><glyph unicode="3" d="M250 25H225V0H0V50H150v50H75v50h75v50H0v50H225V225h25V150H225V100h25V25Z"
></glyph
><glyph unicode="4" d="M250 0H150V100H25v25H0V250H100V150h50V250H250V0Z"
></glyph
><glyph unicode="5" d="M250 25H225V0H0V50H150v50H25v25H0V250H250V200H100V150H225V125h25V25Z"
></glyph
><glyph unicode="6" d="M250 25H225V0H25V25H0V225H25v25H250V200H100V150H225V125h25V25ZM150 50v50H100V50h50Z"
></glyph
><glyph unicode="7" d="M250 0H150V200H0v50H225V225h25V0Z"
></glyph
><glyph unicode="8" d="M250 25H225V0H25V25H0v75H25v50H0v75H25v25H225V225h25V150H225V100h25V25ZM150 150v50H100V150h50ZM150 50v50H100V50h50Z"
></glyph
><glyph unicode="9" d="M250 25H225V0H0V50H150v50H25v25H0V225H25v25H225V225h25V25ZM150 150v50H100V150h50Z"
></glyph
><glyph unicode="A" d="M250 0H150V100H100V0H0V225H25v25H225V225h25V0ZM150 150v50H100V150h50Z"
></glyph
><glyph unicode="B" d="M250 25H225V0H0V250H225V225h25V150H225V100h25V25ZM150 150v50H100V150h50ZM150 50v50H100V50h50Z"
></glyph
><glyph unicode="C" d="M250 0H25V25H0V225H25v25H250V200H100V50H250V0Z"
></glyph
><glyph unicode="D" d="M250 25H225V0H0V250H225V225h25V25ZM150 50V200H100V50h50Z"
></glyph
><glyph unicode="E" d="M250 0H25V25H0V225H25v25H250V200H100V150h75V100H100V50H250V0Z"
></glyph
><glyph unicode="F" d="M250 200H100V150h75V100H100V0H0V225H25v25H250V200Z"
></glyph
><glyph unicode="G" d="M250 0H25V25H0V225H25v25H250V200H100V50h50v50H125v50H250V0Z"
></glyph
><glyph unicode="H" d="M250 0H150V100H100V0H0V250H100V150h50V250H250V0Z"
></glyph
><glyph unicode="I" horiz-adv-x="150" d="M100 0H0V250H100V0Z"
></glyph
><glyph unicode="J" d="M250 25H225V0H0V50H150V250H250V25Z"
></glyph
><glyph unicode="K" d="M250 0H150V100H100V0H0V250H100V150h50V250H250V150H225V100h25V0Z"
></glyph
><glyph unicode="L" d="M250 0H0V250H100V50H250V0Z"
></glyph
><glyph unicode="M" horiz-adv-x="450" d="M400 0H300V200H250V0H150V200H100V0H0V250H375V225h25V0Z"
></glyph
><glyph unicode="N" d="M250 0H150V200H100V0H0V250H225V225h25V0Z"
></glyph
><glyph unicode="O" d="M250 25H225V0H25V25H0V225H25v25H225V225h25V25ZM150 50V200H100V50h50Z"
></glyph
><glyph unicode="P" d="M250 125H225V100H100V0H0V250H225V225h25V125ZM150 150v50H100V150h50Z"
></glyph
><glyph unicode="Q" d="M250 25H225V0H25V25H0V225H25v25H225V225h25V25ZM150 50V200H100V50h50Z"
></glyph
><glyph unicode="R" d="M250 0H150V100H100V0H0V250H225V225h25V150H225V100h25V0ZM150 150v50H100V150h50Z"
></glyph
><glyph unicode="S" d="M250 25H225V0H0V50H150v50H25v25H0V225H25v25H250V200H100V150H225V125h25V25Z"
></glyph
><glyph unicode="T" d="M250 200H175V0H75V200H0v50H250V200Z"
></glyph
><glyph unicode="U" d="M250 25H225V0H25V25H0V250H100V50h50V250H250V25Z"
></glyph
><glyph unicode="V" d="M250 25H225V0H0V250H100V50h50V250H250V25Z"
></glyph
><glyph unicode="W" horiz-adv-x="450" d="M400 25H375V0H0V250H100V50h50V250H250V50h50V250H400V25Z"
></glyph
><glyph unicode="X" d="M250 0H150V100H100V0H0V100H25v50H0V250H100V150h50V250H250V150H225V100h25V0Z"
></glyph
><glyph unicode="Y" d="M250 125H225V100H175V0H75V100H25v25H0V250H100V150h50V250H250V125Z"
></glyph
><glyph unicode="Z" d="M250 0H0V125H25v25H150v50H0v50H250V125H225V100H100V50H250V0Z"
></glyph
><glyph unicode="[" horiz-adv-x="150" d="M150 0H0V250H150V200H100V50h50V0Z"
></glyph
><glyph unicode="]" horiz-adv-x="150" d="M150 0H0V50H50V200H0v50H150V0Z"
></glyph
><glyph unicode="a" d="M250 0H150V100H100V0H0V225H25v25H225V225h25V0ZM150 150v50H100V150h50Z"
></glyph
><glyph unicode="b" d="M250 25H225V0H0V250H225V225h25V150H225V100h25V25ZM150 150v50H100V150h50ZM150 50v50H100V50h50Z"
></glyph
><glyph unicode="c" d="M250 0H25V25H0V225H25v25H250V200H100V50H250V0Z"
></glyph
><glyph unicode="d" d="M250 25H225V0H0V250H225V225h25V25ZM150 50V200H100V50h50Z"
></glyph
><glyph unicode="e" d="M250 0H25V25H0V225H25v25H250V200H100V150h75V100H100V50H250V0Z"
></glyph
><glyph unicode="f" d="M250 200H100V150h75V100H100V0H0V225H25v25H250V200Z"
></glyph
><glyph unicode="g" d="M250 0H25V25H0V225H25v25H250V200H100V50h50v50H125v50H250V0Z"
></glyph
><glyph unicode="h" d="M250 0H150V100H100V0H0V250H100V150h50V250H250V0Z"
></glyph
><glyph unicode="i" horiz-adv-x="150" d="M100 0H0V250H100V0Z"
></glyph
><glyph unicode="j" d="M250 25H225V0H0V50H150V250H250V25Z"
></glyph
><glyph unicode="k" d="M250 0H150V100H100V0H0V250H100V150h50V250H250V150H225V100h25V0Z"
></glyph
><glyph unicode="l" d="M250 0H0V250H100V50H250V0Z"
></glyph
><glyph unicode="m" horiz-adv-x="450" d="M400 0H300V200H250V0H150V200H100V0H0V250H375V225h25V0Z"
></glyph
><glyph unicode="n" d="M250 0H150V200H100V0H0V250H225V225h25V0Z"
></glyph
><glyph unicode="o" d="M250 25H225V0H25V25H0V225H25v25H225V225h25V25ZM150 50V200H100V50h50Z"
></glyph
><glyph unicode="p" d="M250 125H225V100H100V0H0V250H225V225h25V125ZM150 150v50H100V150h50Z"
></glyph
><glyph unicode="q" d="M250 25H225V0H25V25H0V225H25v25H225V225h25V25ZM150 50V200H100V50h50Z"
></glyph
><glyph unicode="r" d="M250 0H150V100H100V0H0V250H225V225h25V150H225V100h25V0ZM150 150v50H100V150h50Z"
></glyph
><glyph unicode="s" d="M250 25H225V0H0V50H150v50H25v25H0V225H25v25H250V200H100V150H225V125h25V25Z"
></glyph
><glyph unicode="t" d="M250 200H175V0H75V200H0v50H250V200Z"
></glyph
><glyph unicode="u" d="M250 25H225V0H25V25H0V250H100V50h50V250H250V25Z"
></glyph
><glyph unicode="v" d="M250 25H225V0H0V250H100V50h50V250H250V25Z"
></glyph
><glyph unicode="w" horiz-adv-x="450" d="M400 25H375V0H0V250H100V50h50V250H250V50h50V250H400V25Z"
></glyph
><glyph unicode="x" d="M250 0H150V100H100V0H0V100H25v50H0V250H100V150h50V250H250V150H225V100h25V0Z"
></glyph
><glyph unicode="y" d="M250 125H225V100H175V0H75V100H25v25H0V250H100V150h50V250H250V125Z"
></glyph
><glyph unicode="z" d="M250 0H0V125H25v25H150v50H0v50H250V125H225V100H100V50H250V0Z"
></glyph
></font
></defs
><g style="font-family: &quot;8BIT WONDER&quot;; font-size:50;fill:black"
><text x="20" y="50"
>!&quot;#$%&amp;&#39;()*+,-./0123456789:;&#229;&lt;&gt;?</text
><text x="20" y="100"
>@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_</text
><text x="20" y="150"
>` abcdefghijklmnopqrstuvwxyz|{}~</text
></g
></svg
>

Before

Width:  |  Height:  |  Size: 7.4 KiB

View File

@@ -0,0 +1,68 @@
package ru.m.tankz.bot;
import ru.m.tankz.core.EntityType;
import ru.m.tankz.control.Control;
import ru.m.geom.Direction;
import haxe.Timer;
class BotControl extends Control {
public static var TYPE(default, never):ControlType = 'bot';
private var shotTimer:Timer;
private var turnTimer:Timer;
public function new(index:Int) {
super({type:TYPE, index:index});
}
override public function onCollision(with:EntityType):Void {
switch (with) {
case EntityType.TANK(_): turn();
case EntityType.CELL(_): turn();
case _:
}
}
override public function start():Void {
//var tank = handler.entities.get(tankId);
//action(TankAction.MOVE(tank.rect.direction));
action(TankAction.MOVE(Direction.BOTTOM)); // ToDo: hardcode bot start direction
if (shotTimer == null) {
shotTimer = new Timer(1000);
shotTimer.run = shot;
}
if (turnTimer == null) {
turnTimer = new Timer(3000);
turnTimer.run = turn;
}
}
override public function stop():Void {
if (shotTimer != null) {
shotTimer.stop();
shotTimer = null;
}
if (turnTimer != null) {
turnTimer.stop();
turnTimer = null;
}
}
public function shot():Void {
action(TankAction.SHOT);
}
public function turn():Void {
action(TankAction.MOVE(randomDirection()));
}
private function randomDirection():Direction {
return [
Direction.TOP,
Direction.BOTTOM,
Direction.LEFT,
Direction.RIGHT,
][Math.floor(Math.random() * 4)];
}
}

View File

@@ -1,14 +1,8 @@
package ru.m.tankz.config; package ru.m.tankz.config;
@:enum abstract EntityType(String) from String to String {
var PLAYER = 'player';
var BOT = 'bot';
var EAGLE = 'eagle';
}
typedef SpawnPoint = { typedef SpawnPoint = {
var type:EntityType; var type:String;
var index:Int; var index:Int;
var x:Int; var x:Int;
var y:Int; var y:Int;
@@ -20,9 +14,6 @@ typedef MapConfig = {
var cellHeight:Float; var cellHeight:Float;
var gridWidth:Int; var gridWidth:Int;
var gridHeight:Int; var gridHeight:Int;
var bricks:Array<BrickConfig>;
var points:Array<SpawnPoint>;
} }
typedef BrickConfig = { typedef BrickConfig = {
@@ -39,9 +30,12 @@ typedef BulletConfig = {
} }
typedef TankConfig = { typedef TankType = {
var group:String; var group:String;
var type:Int; var type:String;
}
typedef TankConfig = { > TankType,
var width:Float; var width:Float;
var height:Float; var height:Float;
var speed:Float; var speed:Float;
@@ -50,48 +44,61 @@ typedef TankConfig = {
} }
typedef TeamConfig = {
var id:String;
var size:Int;
var spawnInterval:Int;
var points:Array<SpawnPoint>;
}
class Config { class Config {
public var type(default, null):String;
public var levels(default, null):Int;
public var map(default, null):MapConfig; public var map(default, null):MapConfig;
public var bricks(default, null):Array<BrickConfig>; public var bricks(default, null):Array<BrickConfig>;
public var tanks(default, null):Array<TankConfig>; public var tanks(default, null):Array<TankConfig>;
public var teams(default, null):Array<TeamConfig>;
private var pointMap:Map<EntityType, Map<Int, SpawnPoint>>;
private var brickMap:Map<Int, BrickConfig>; private var brickMap:Map<Int, BrickConfig>;
private var tankMap:Map<String, Map<Int, TankConfig>>; private var tankMap:Map<String, Map<String, TankConfig>>;
private var teamMap:Map<String, TeamConfig>;
public function new(map:MapConfig, bricks:Array<BrickConfig>, tanks:Array<TankConfig>) { public function new(type:String, levels:Int, map:MapConfig, bricks:Array<BrickConfig>, teams:Array<TeamConfig>, tanks:Array<TankConfig>) {
this.type = type;
this.levels = levels;
this.map = map; this.map = map;
this.bricks = bricks; this.bricks = bricks;
this.teams = teams;
this.tanks = tanks; this.tanks = tanks;
init(); init();
} }
private function init() { private function init() {
pointMap = new Map();
for (item in map.points) {
if (!pointMap.exists(item.type)) pointMap.set(item.type, new Map<Int, SpawnPoint>());
pointMap.get(item.type).set(item.index, item);
}
brickMap = new Map(); brickMap = new Map();
for (item in bricks) { for (item in bricks) {
brickMap.set(item.type, item); brickMap.set(item.type, item);
} }
teamMap = new Map();
for (team in teams) {
teamMap.set(team.id, team);
}
tankMap = new Map(); tankMap = new Map();
for (item in tanks) { for (item in tanks) {
if (!tankMap.exists(item.group)) tankMap.set(item.group, new Map<Int, TankConfig>()); if (!tankMap.exists(item.group)) tankMap.set(item.group, new Map<String, TankConfig>());
tankMap.get(item.group).set(item.type, item); tankMap.get(item.group).set(item.type, item);
} }
} }
public function getSpawnPoint(type:EntityType, index:Int):SpawnPoint {
return pointMap.get(type).get(index);
}
public function getBrick(type:Int):BrickConfig { public function getBrick(type:Int):BrickConfig {
return brickMap.get(type); return brickMap.get(type);
} }
public function getTank(group:String, type:Int):TankConfig { public function getTeam(id:String):TeamConfig {
return teamMap.get(id);
}
public function getTank(group:String, type:String):TankConfig {
return tankMap.get(group).get(type); return tankMap.get(group).get(type);
} }
} }

View File

@@ -1,15 +1,17 @@
package ru.m.tankz.config; package ru.m.tankz.config;
import ru.m.tankz.game.ClassicGame;
import yaml.Parser; import yaml.Parser;
import openfl.Assets; import openfl.Assets;
import yaml.Yaml; import yaml.Yaml;
import ru.m.tankz.proto.core.GameType;
import ru.m.tankz.config.Config; import ru.m.tankz.config.Config;
typedef ConfigSource = { typedef ConfigSource = {
var levels:Int;
var map: MapConfig; var map: MapConfig;
var bricks: Array<BrickConfig>; var bricks: Array<BrickConfig>;
var teams: Array<TeamConfig>;
var tanks: Dynamic<Array<TankConfig>>; var tanks: Dynamic<Array<TankConfig>>;
} }
@@ -19,20 +21,8 @@ class ConfigBundle {
return raw; return raw;
} }
public static function get(type:Int, level:Int):Config { public static function get(type:String):Config {
switch (type) { var source:ConfigSource = convert(Yaml.parse(Assets.getText('resources/${type}/config.yaml'), Parser.options().useObjects()));
case GameType.CLASSIC:
var source:ConfigSource = convert(Yaml.parse(Assets.getText('resources/config/config.yaml'), Parser.options().useObjects()));
var bricksData:String = Assets.getText('resources/levels/level00${level}.txt');
var bricks:Array<BrickConfig> = [];
for (line in ~/\s+/g.split(bricksData)) {
for (c in line.split('')) {
if (c.length > 0) {
bricks.push(source.bricks[Std.parseInt(c) + 1]);
}
}
}
source.map.bricks = bricks;
var tanks:Array<TankConfig> = []; var tanks:Array<TankConfig> = [];
for (group in Reflect.fields(source.tanks)) { for (group in Reflect.fields(source.tanks)) {
var data:Array<TankConfig> = Reflect.field(source.tanks, group); var data:Array<TankConfig> = Reflect.field(source.tanks, group);
@@ -41,9 +31,6 @@ class ConfigBundle {
tanks.push(item); tanks.push(item);
} }
} }
return new Config(source.map, source.bricks, tanks); return new Config(type, source.levels, source.map, source.bricks, source.teams, tanks);
case _:
return null;
}
} }
} }

View File

@@ -0,0 +1,28 @@
package ru.m.tankz.config;
import openfl.Assets;
import ru.m.tankz.game.Game;
import ru.m.tankz.config.Config;
class LevelBundle {
private static function formatLevel(level:Int):String {
var result = Std.string(level);
while (result.length < 3) result = '0${result}';
return result;
}
public static function get(type:GameType, config:Config, level:Int):Array<BrickConfig> {
var bricksData:String = Assets.getText('resources/${type}/levels/level${formatLevel(level)}.txt');
var bricks:Array<BrickConfig> = [];
for (line in ~/\s+/g.split(bricksData)) {
for (c in line.split('')) {
if (c.length > 0) {
bricks.push(config.bricks[Std.parseInt(c) + 1]);
}
}
}
return bricks;
}
}

View File

@@ -0,0 +1,61 @@
package ru.m.tankz.control;
import ru.m.tankz.core.Entity;
import ru.m.tankz.core.EntityType;
import ru.m.geom.Direction;
enum TankAction {
MOVE(direction:Direction);
LEVEL_UP(level:Int);
STOP;
SHOT;
}
typedef ControlType = String;
typedef ControlId = {
var type:ControlType;
var index:Int;
}
class Control {
public var id:ControlId;
public var tankId(default, default):Int;
private var handler:ControlHandler;
public function new(id:ControlId) {
this.id = id;
}
public function bind(handler:ControlHandler):Void {
this.handler = handler;
}
public function action(action:TankAction):Void {
if (tankId != 0 && handler != null) {
handler.action(tankId, action);
}
}
public function onCollision(with:EntityType):Void {
}
public function start():Void {}
public function stop():Void {}
public function dispose():Void {
stop();
handler = null;
}
}
interface ControlHandler {
public var entities(default, null):Map<Int, Entity>;
public function action(tankId:Int, action:TankAction):Void;
}

View File

@@ -1,20 +1,21 @@
package ru.m.tankz.core; package ru.m.tankz.core;
import ru.m.tankz.game.Game;
import ru.m.tankz.config.Config; import ru.m.tankz.config.Config;
import ru.m.geom.Rectangle; import ru.m.geom.Rectangle;
import ru.m.geom.Direction; import ru.m.geom.Direction;
class Bullet extends MobileEntity { class Bullet extends MobileEntity {
public var playerId(default, null):PlayerId;
public var tankId(default, null):Int; public var tankId(default, null):Int;
public var tankConfig(default, null):TankConfig;
public var config(default, null):BulletConfig; public var config(default, null):BulletConfig;
public function new(tankId:Int, tankConfig:TankConfig, config:BulletConfig) { public function new(tank:Tank) {
this.playerId = tank.playerId;
this.config = tank.config.bullet;
super(new Rectangle(0, 0, config.width, config.height), config.speed, Direction.RIGHT); super(new Rectangle(0, 0, config.width, config.height), config.speed, Direction.RIGHT);
this.tankId = tankId; this.tankId = tank.id;
this.tankConfig = tankConfig;
this.config = config;
this.layer = 2; this.layer = 2;
} }
} }

View File

@@ -0,0 +1,15 @@
package ru.m.tankz.core;
import ru.m.tankz.game.Game;
import ru.m.geom.Rectangle;
class Eagle extends Entity {
public var team(default, null):TeamId;
public function new(team:TeamId) {
super(new Rectangle(0, 0, 44, 44));
this.team = team;
}
}

View File

@@ -0,0 +1,26 @@
package ru.m.tankz.core;
import Type.ValueType;
import ru.m.tankz.map.Grid.GridCell;
enum EntityType {
EAGLE(eagle:Eagle);
TANK(tank:Tank);
BULLET(bullet:Bullet);
CELL(cell:GridCell);
}
class EntityTypeResolver {
public static function of(entity:Dynamic):EntityType {
return switch (Type.typeof(entity)) {
case ValueType.TClass(Eagle): EntityType.EAGLE(cast entity);
case ValueType.TClass(Tank): EntityType.TANK(cast entity);
case ValueType.TClass(Bullet): EntityType.BULLET(cast entity);
case ValueType.TClass(GridCell): EntityType.CELL(cast entity);
case x: null;
}
}
}

View File

@@ -10,24 +10,21 @@ class MobileEntity extends Entity {
public var layer(default, null):Int; public var layer(default, null):Int;
public var speed(default, null):Float; public var speed(default, null):Float;
public var direction(default, default):Direction;
public function new(rect:Rectangle, speed:Float, direction:Direction) { public function new(rect:Rectangle, speed:Float, direction:Direction) {
super(rect); super(rect);
this.speed = speed; this.speed = speed;
this.direction = direction;
this.layer = 0; this.layer = 0;
this.mx = 0; this.mx = 0;
this.my = 0; this.my = 0;
} }
public function move(direction:Direction):Void { public function move(direction:Direction):Void {
if (this.direction != direction) { if (this.rect.direction != direction) {
this.rect.direction = direction; this.rect.direction = direction;
this.direction = direction;
} }
mx = direction.x * speed; mx = rect.direction.x * speed;
my = direction.y * speed; my = rect.direction.y * speed;
} }
public function stop():Void { public function stop():Void {

View File

@@ -1,35 +1,30 @@
package ru.m.tankz.core; package ru.m.tankz.core;
import ru.m.tankz.game.Game;
import ru.m.geom.Point; import ru.m.geom.Point;
import ru.m.tankz.config.Config.TankConfig; import ru.m.tankz.config.Config;
import ru.m.tankz.core.Bullet; import ru.m.tankz.core.Bullet;
import ru.m.geom.Rectangle; import ru.m.geom.Rectangle;
import ru.m.geom.Direction; import ru.m.geom.Direction;
enum TankAction {
MOVE(direction:Direction);
LEVEL_UP(level:Int);
STOP;
SHOT;
}
class Tank extends MobileEntity { class Tank extends MobileEntity {
public var index(default, null):Int; public var playerId(default, null):PlayerId;
public var config(default, set):TankConfig; public var config(default, set):TankConfig;
private var bulletsCounter:Int = 0; private var bulletsCounter:Int = 0;
public function new(index:Int, config: TankConfig) { public function new(playerId:PlayerId, config:TankConfig) {
super(new Rectangle(0, 0, config.width, config.height), config.speed, Direction.RIGHT); super(new Rectangle(0, 0, config.width, config.height), config.speed, Direction.RIGHT);
this.index = index; this.playerId = playerId;
this.config = config; this.config = config;
this.layer = 1; this.layer = 1;
} }
private function set_config(value:TankConfig):TankConfig { private function set_config(value:TankConfig):TankConfig {
var d = rect.direction;
rect = new Rectangle(rect.x, rect.y, value.width, value.height); rect = new Rectangle(rect.x, rect.y, value.width, value.height);
rect.direction = direction; rect.direction = d;
speed = value.speed; speed = value.speed;
config = value; config = value;
return value; return value;
@@ -37,22 +32,13 @@ class Tank extends MobileEntity {
public function shot():Null<Bullet> { public function shot():Null<Bullet> {
if (bulletsCounter >= config.bullets) return null; if (bulletsCounter >= config.bullets) return null;
var bullet = new Bullet(id, config, config.bullet); var bullet = new Bullet(this);
bullet.rect.center = rect.center.add(new Point(rect.width / 4 * direction.x, rect.height / 4 * direction.y)); bullet.rect.center = rect.center.add(new Point(rect.width / 4 * rect.direction.x, rect.height / 4 * rect.direction.y));
bullet.move(direction); bullet.move(rect.direction);
bulletsCounter++; bulletsCounter++;
return bullet; return bullet;
} }
override public function move(direction:Direction):Void {
// ToDo: spike
/*if (direction != this.direction) {
rect.x -= this.direction.x * 4;
rect.y -= this.direction.y * 4;
}*/
super.move(direction);
}
public function onDestroyBullet():Void { public function onDestroyBullet():Void {
bulletsCounter--; bulletsCounter--;
} }

View File

@@ -1,191 +1,146 @@
package ru.m.tankz.engine; package ru.m.tankz.engine;
import haxe.macro.Type.Ref;
import haxe.Constraints.Function;
import ru.m.geom.Rectangle;
import ru.m.geom.Line; import ru.m.geom.Line;
import ru.m.tankz.core.MobileEntity;
import ru.m.tankz.core.Entity;
import ru.m.geom.Direction;
import ru.m.tankz.config.Config.TankConfig;
import ru.m.geom.Point;
import ru.m.tankz.core.Bullet;
import ru.m.tankz.proto.game.GameObjectType;
import ru.m.tankz.proto.game.GameChangeType;
import ru.m.tankz.proto.game.GameChange;
import ru.m.tankz.proto.core.Player;
import ru.m.tankz.config.Config; import ru.m.tankz.config.Config;
import ru.m.tankz.control.Control;
import ru.m.tankz.core.Bullet;
import ru.m.tankz.core.Entity;
import ru.m.tankz.core.EntityType;
import ru.m.tankz.core.MobileEntity;
import ru.m.tankz.core.Tank; import ru.m.tankz.core.Tank;
import ru.m.tankz.map.LevelMap; import ru.m.tankz.map.LevelMap;
class Engine {
interface EngineListener {
public function onSpawn(entity:EntityType):Void;
public function onCollision(entity:EntityType, with:EntityType):Void;
public function onDestroy(entity:EntityType):Void;
}
class CollisionProcessor implements EngineListener {
private var engine:Engine;
public function new(engine:Engine) {
this.engine = engine;
}
public function onSpawn(entity:EntityType):Void {}
private function checkTankBullet(tank:Tank, bullet:Bullet):Bool {
return tank.playerId.team != bullet.playerId.team;
}
public function onCollision(entity:EntityType, with:EntityType):Void {
switch (entity) {
case EntityType.TANK(tank1):
switch (with) {
case EntityType.TANK(tank2):
tank1.rect.lean(tank2.rect);
case EntityType.BULLET(bullet2):
if (checkTankBullet(tank1, bullet2)) {
engine.destroy(tank1);
engine.destroy(bullet2);
}
case EntityType.EAGLE(eagle):
tank1.rect.lean(eagle.rect);
case EntityType.CELL(cell):
tank1.rect.lean(cell.rect);
}
case EntityType.BULLET(bullet1):
switch (with) {
case EntityType.TANK(tank2):
if (checkTankBullet(tank2, bullet1)) {
engine.destroy(bullet1);
engine.destroy(tank2);
}
case EntityType.BULLET(bullet2):
engine.destroy(bullet1);
engine.destroy(bullet2);
case EntityType.EAGLE(eagle):
engine.destroy(bullet1);
engine.destroy(eagle);
case EntityType.CELL(cell):
engine.destroy(bullet1);
}
case _:
}
}
public function onDestroy(entity:EntityType):Void { }
}
class Engine implements ControlHandler {
public var config(default, default):Config; public var config(default, default):Config;
public var map(default, null):LevelMap; public var map(default, null):LevelMap;
public var entities(default, null):Map<Int, Entity>; public var entities(default, null):Map<Int, Entity>;
public var removedEntities(default, null):Array<String>; public var listeners(default, null):Array<EngineListener>;
private var collision:CollisionProcessor;
private var playerTanks(default, null):Map<Int, Tank>;
private var time:Float; private var time:Float;
public function new() {} public function new(config:Config) {
public function clear():Void {
playerTanks = new Map<Int, Tank>();
}
private function buildTank(index:Int, config:TankConfig, point:SpawnPoint):Tank {
var tank = new Tank(index, config);
tank.rect.center = new Point((point.x + 1) * map.cellWidth, (point.y + 1) * map.cellHeight);
tank.direction = Direction.fromString(point.direction);
return tank;
}
public function init(config:Config):Void {
this.config = config; this.config = config;
listeners = [];
map = new LevelMap(config.map); map = new LevelMap(config.map);
playerTanks = new Map<Int, Tank>();
entities = new Map<Int, Entity>(); entities = new Map<Int, Entity>();
removedEntities = new Array<String>();
time = Date.now().getTime(); time = Date.now().getTime();
collision = new CollisionProcessor(this);
listeners.push(collision);
} }
public function initTanks(players:Array<Player>):Array<GameChange> { public function spawn(entity:Entity):Void {
var changes = new Array<GameChange>(); entities.set(entity.id, entity);
for (index in 0...players.length) { var type = EntityTypeResolver.of(entity);
var player:Player = players[index]; for (l in listeners) l.onSpawn(type);
var point:SpawnPoint = config.getSpawnPoint(EntityType.PLAYER, index);
var tank = buildTank(index, config.getTank('player', 0), point);
playerTanks.set(player.id, tank);
entities.set(tank.id, tank);
changes.push(new GameChange()
.setType(GameChangeType.APPEND)
.setObjectType(GameObjectType.TANK)
.setObjectId(tank.id)
.setX(tank.rect.x)
.setY(tank.rect.y)
.setDirectionX(tank.direction.x)
.setDirectionY(tank.direction.y)
);
} }
for (index in 1...4) { public function destroy(entity:Entity):Void {
var point:SpawnPoint = config.getSpawnPoint(EntityType.BOT, index); if (entities.exists(entity.id)) {
var tank = buildTank(0, config.getTank('bot', 0), point); var type = EntityTypeResolver.of(entity);
entities.set(tank.id, tank); switch (type) {
case EntityType.BULLET(bullet):
var tank:Tank = cast entities.get(bullet.tankId);
if (tank != null) tank.onDestroyBullet();
case _:
}
for (l in listeners) l.onDestroy(type);
entities.remove(entity.id);
} }
return changes;
} }
public function action(playerId:Int, action:TankAction):Void { public function action(tankId:Int, action:TankAction):Void {
var tank:Tank = playerTanks.get(playerId); if (!entities.exists(tankId)) return;
var tank:Tank = cast entities.get(tankId);
switch (action) { switch (action) {
case TankAction.MOVE(direction): case TankAction.MOVE(direction):
tank.move(direction); tank.move(direction);
/*Provider.get(IConnection).send(
Map GameActionRequest()
.setType(GameActionType.MOVE)
.setDirectionX(direction.x)
.setDirectionY(direction.y)
);*/
case TankAction.LEVEL_UP(level): case TankAction.LEVEL_UP(level):
tank.config = config.getTank('player', Std.int(Math.min(tank.config.type + level, 3))); // ToDo:
tank.config = config.getTank('human', Std.string(Std.int(Math.min(Std.parseInt(tank.config.type) + level, 3))));
case TankAction.STOP: case TankAction.STOP:
tank.stop(); tank.stop();
/*Provider.get(IConnection).send(
Map GameActionRequest()
.setType(GameActionType.STOP)
);*/
case TankAction.SHOT: case TankAction.SHOT:
var bullet = tank.shot(); var bullet = tank.shot();
if (bullet != null) { if (bullet != null) {
entities.set(bullet.id, bullet); spawn(bullet);
}
/*Provider.get(IConnection).send(
Map GameActionRequest()
.setType(GameActionType.SHOT)
);*/
}
}
/*public function updateFromChanges(changes:Array<GameChange>):Void {
for (change in changes) {
switch (change.type) {
case GameChangeType.APPEND:
switch (change.objectType) {
case GameObjectType.TANK:
var tank:Tank = buildTank(change.personId, change.objectId, change.x, change.y, Direction.from(change.directionX, change.directionY));
mobileEntities.set(tank.id, tank);
tanks.set(tank.personId, tank);
case GameObjectType.BULLET:
var bullet:Bullet = Map Bullet(change.personId, change.objectId, change.x, change.y, 0, Direction.from(change.directionX, change.directionY));
mobileEntities.set(bullet.id, bullet);
}
case GameChangeType.DESTROED:
mobileEntities.remove(change.objectId);
case GameChangeType.DIRECTION:
var target = mobileEntities.get(change.objectId);
target.direction = Direction.from(change.directionX, change.directionY);
case GameChangeType.MOVED:
var target = mobileEntities.get(change.objectId);
target.x = change.x;
target.y = change.y;
case GameChangeType.MODIFIED:
}
}
}*/
private function collisionTankTank(a:Tank, b:Tank):Bool {
a.rect.lean(b.rect);
return true;
}
private function collisionBulletTank(a:Bullet, b:Tank):Bool {
if (a.tankConfig.group != b.config.group) {
removeEntity(a);
removeEntity(b);
return true;
}
return false;
}
private function collisionTankBullet(a:Tank, b:Bullet):Bool {
if (a.config.group != b.tankConfig.group) {
removeEntity(a);
removeEntity(b);
return true;
}
return false;
}
private function collisionBulletBullet(a:Bullet, b:Bullet):Bool {
removeEntity(a);
removeEntity(b);
return true;
}
private function removeEntity(entity:Entity):Void {
if (entities.exists(entity.id)) {
entities.remove(entity.id);
removedEntities.push(entity.key);
if (Std.is(entity, Bullet)) {
var tank:Tank = cast entities.get(cast(entity, Bullet).tankId);
tank.onDestroyBullet();
} }
} }
} }
public function update():Array<GameChange> { public function update():Void {
var newTime:Float = Date.now().getTime(); var newTime:Float = Date.now().getTime();
var d:Float = newTime - time; var d:Float = newTime - time;
time = newTime; time = newTime;
var changes = new Array<GameChange>();
for (ent in entities) if (Std.is(ent, MobileEntity)) { for (ent in entities) if (Std.is(ent, MobileEntity)) {
var entityType:EntityType = EntityTypeResolver.of(ent);
var entity:MobileEntity = cast ent; var entity:MobileEntity = cast ent;
/*if (Std.is(entity, Tank)) { /*if (Std.is(entity, Tank)) {
@@ -209,19 +164,17 @@ class Engine {
if (cell.layer >= entity.layer && cell.layer < 3) { if (cell.layer >= entity.layer && cell.layer < 3) {
entity.rect.lean(cell.rect); entity.rect.lean(cell.rect);
collision = true; collision = true;
var with = EntityTypeResolver.of(cell);
for (l in listeners) l.onCollision(entityType, with);
break; break;
} }
} }
for (other in entities.iterator()) { for (other in entities.iterator()) {
if (other != ent) { if (other != ent && other != null) {
if (other.rect.intersection2(side)) { if (other.rect.intersection2(side)) {
//if (ent.rect.intersection(other.rect)) { var with = EntityTypeResolver.of(other);
var funName = 'collision${ent.type}${other.type}'; for (l in listeners) l.onCollision(entityType, with);
var fun:Function = Reflect.field(this, funName);
if (fun != null) {
collision = Reflect.callMethod(this, fun, [ent, other]) || collision;
}
} }
} }
} }
@@ -240,19 +193,14 @@ class Engine {
} }
} }
} }
removeEntity(entity); }
}
}
} }
} }
/*changes.push(new GameChange() public function dispose():Void {
.setType(GameChangeType.MOVED) listeners = [];
.setObjectType(objectType) entities = new Map();
.setObjectId(entity.id)
.setX(entity.rect.x)
.setY(entity.rect.y)
);*/
}
}
return changes;
} }
} }

View File

@@ -0,0 +1,72 @@
package ru.m.tankz.game;
import haxe.ds.Option;
import ru.m.tankz.game.GameState.PlayerState;
import ru.m.tankz.game.Game;
class ClassicGame extends Game {
public static var TYPE(default, never):GameType = 'classic';
public static var HUMAN(default, never):TeamId = 'human';
public static var BOT(default, never):TeamId = 'bot';
private static var HUMAN_LIFE(default, never):Int = 3;
private static var BOT_LIFE(default, never):Int = 20;
public function new() {
super(TYPE);
}
public static function buildState(level:Int, humans:Int):GameState {
var state = new GameState();
state.type = TYPE;
state.level = level;
state.teams[HUMAN] = {life: -1, players: new Map(), lose: false};
state.teams[BOT] = {life: BOT_LIFE, players: new Map(), lose: false};
for (i in 0...humans) {
state.teams[HUMAN].players[i] = {
index:i,
tank:{
group: HUMAN,
type: '1'
},
control:{
type: 'human',
index: i
},
life:HUMAN_LIFE,
};
}
for (i in 0...humans*2+2) {
state.teams[BOT].players[i] = {
index:i,
tank:{
group: BOT,
type: '1'
},
control:{
type: 'bot',
index: i
},
life:-1,
};
}
return state;
}
override public function next():Option<GameState> {
if (!state.teams[HUMAN].lose) {
state.level++;
if (state.level >= config.levels) state.level = 0;
state.teams[BOT].lose = false;
state.teams[BOT].life = BOT_LIFE;
for (ps in state.teams[HUMAN].players) {
if (ps.life > 0) ps.life++;
}
return Option.Some(state);
}
return Option.None;
}
}

View File

@@ -0,0 +1,56 @@
package ru.m.tankz.game;
import ru.m.tankz.game.Game;
import ru.m.tankz.game.GameState;
class DotaGame extends Game {
public static var TYPE(default, never):GameType = 'dota';
public static var RADIANT(default, never):TeamId = 'radiant';
public static var DIRE(default, never):TeamId = 'dire';
private static var TEAM_SIZE(default, never):Int = 5;
public function new() {
super(TYPE);
}
public static function buildState(level:Int, humans:Int):GameState {
var state = new GameState();
state.type = TYPE;
state.level = level;
state.teams[RADIANT] = {life: 20, players: new Map(), lose: false};
state.teams[DIRE] = {life: 20, players: new Map(), lose: false};
for (i in 0...TEAM_SIZE) {
state.teams[RADIANT].players[i] = {
index:i,
tank:{
group: RADIANT,
type: '1'
},
control:{
type: 'bot',
index: i
},
life:-1,
};
}
for (i in 0...TEAM_SIZE) {
state.teams[DIRE].players[i] = {
index:i,
tank:{
group: DIRE,
type: '1'
},
control:{
type: 'bot',
index: i
},
life:-1,
};
}
return state;
}
}

View File

@@ -0,0 +1,242 @@
package ru.m.tankz.game;
import haxe.ds.Option;
import haxe.Timer;
import promhx.Deferred;
import promhx.Stream;
import ru.m.geom.Direction;
import ru.m.geom.Point;
import ru.m.tankz.bot.BotControl;
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.HumanControl;
import ru.m.tankz.core.Eagle;
import ru.m.tankz.core.Entity;
import ru.m.tankz.core.EntityType;
import ru.m.tankz.core.Tank;
import ru.m.tankz.engine.Engine;
import ru.m.tankz.game.GameState;
import ru.m.tankz.game.Spawner;
typedef GameType = String;
typedef TeamId = String;
typedef PlayerId = {
var team:TeamId;
var index:Int;
}
class Game implements EngineListener {
public var type(default, null):GameType;
public var state(default, null):GameState;
public var teams(default, null):Map<TeamId, Team>;
public var config(default, null):Config;
public var engine(default, null):Engine;
private var spawners:Map<TeamId, Spawner>;
private var deferred:Deferred<GameState>;
private var stream:Stream<GameState>;
public function new(type:GameType) {
this.type = type;
this.config = ConfigBundle.get(type);
this.engine = new Engine(config);
engine.listeners.push(this);
}
public function getPlayer(playerId:PlayerId):Player {
return teams.get(playerId.team).players[playerId.index];
}
private function buildTank(playerId:PlayerId, config:TankConfig, point:SpawnPoint):Tank {
var tank = new Tank(playerId, config);
applyPoint(tank, point);
return tank;
}
private function applyPoint(entity:Entity, point:SpawnPoint):Void {
entity.rect.center = new Point((point.x + 1) * engine.map.cellWidth, (point.y + 1) * engine.map.cellHeight);
entity.rect.direction = Direction.fromString(point.direction);
}
public function start(state:GameState):Stream<GameState> {
this.deferred = new Deferred();
this.state = state;
var bricks = LevelBundle.get(type, config, state.level);
engine.map.setData(bricks);
teams = new Map<TeamId, Team>();
spawners = new Map<TeamId, Spawner>();
for (teamConfig in config.teams) {
var team = new Team(teamConfig);
for (playerState in state.teams.get(team.id).players) {
var player = new Player({team:team.id, index:playerState.index});
team.players.push(player);
teams.set(team.id, team);
if (playerState.control != null) {
var control = switch (playerState.control.type) {
case HumanControl.TYPE: new HumanControl(playerState.control.index);
case BotControl.TYPE: new BotControl(playerState.control.index);
case 'none': null;
case _: throw 'Unsupported control type: "${playerState.control.type}"';
}
if (control != null) {
player.control = control;
player.control.bind(engine);
}
}
}
spawners.set(team.id, new Spawner(team.config, spawn));
}
for (team in teams) {
for (player in team.players) {
if (trySpawn(player.id)) {
spawners.get(team.id).push(player.id);
}
}
var eaglePoint = spawners.get(team.id).getPoint('eagle');
if (eaglePoint != null) {
var eagle = new Eagle(team.id);
applyPoint(eagle, eaglePoint);
engine.spawn(eagle);
}
}
return stream = deferred.stream();
}
private function spawn(task:SpawnTask):Void {
getPlayer(task.playerId).tankId = 0;
if (trySpawn(task.playerId, true)) {
var tank = buildTank(task.playerId, config.getTank(task.playerId.team, '0'), task.point);
var player:Player = getPlayer(task.playerId);
engine.spawn(tank);
player.tankId = tank.id;
} else if (!isTeamAlive(task.playerId.team)) {
state.teams[task.playerId.team].lose = true;
complete();
}
deferred.resolve(state);
}
private function complete():Void {
for (team in teams.iterator()) {
for (player in team.players) {
player.control.action(TankAction.STOP);
player.control.dispose();
}
}
var timer = new Timer(5000);
timer.run = function() {
timer.stop();
deferred.resolve(state);
stream.end();
}
}
public function setControl(playerId:PlayerId, control:Control):Void {
for (team in teams.iterator()) {
if (team.id == playerId.team) {
var player = team.players[playerId.index];
if (player.control != null) {
player.control.dispose();
}
player.control = control;
player.control.bind(engine);
break;
}
}
}
public function onSpawn(entity:EntityType):Void {
}
public function onCollision(entity:EntityType, with:EntityType):Void {
switch (entity) {
case EntityType.TANK(tank):
var control = getPlayer(tank.playerId).control;
if (control != null) control.onCollision(with);
case x:
}
}
private function isTeamAlive(team:TeamId):Bool {
var ts:TeamState = state.teams[team];
var life:Int = Lambda.fold(ts.players, function(ps, t) return t + ps.life, ts.life);
if (life > 0) {
return true;
} else {
for (player in teams[team].players) {
if (player.tankId > 0) {
return true;
}
}
if (spawners[team].active) {
return true;
}
}
return false;
}
private function trySpawn(player:PlayerId, spawn:Bool = false):Bool {
var ts:TeamState = state.teams[player.team];
var ps:PlayerState = ts.players[player.index];
var result = false;
if (ps.life > -1) {
if (ps.life > 0) {
if (spawn) ps.life--;
result = true;
}
} else if (ts.life > -1) {
if (ts.life > 0) {
if (spawn) ts.life--;
result = true;
}
}
return result;
}
public function onDestroy(entity:EntityType):Void {
switch (entity) {
case EntityType.TANK(tank):
getPlayer(tank.playerId).tankId = 0;
var respawn:Bool = trySpawn(tank.playerId);
if (respawn) {
spawners.get(tank.playerId.team).push(tank.playerId);
}
if (!isTeamAlive(tank.playerId.team)) {
state.teams[tank.playerId.team].lose = true;
complete();
}
deferred.resolve(state);
case EntityType.EAGLE(eagle):
state.teams[eagle.team].lose = true;
complete();
deferred.resolve(state);
case x:
}
}
public function onAction(tankId:Int, action:TankAction):Void {
engine.action(tankId, action);
}
public function next():Option<GameState> {
return Option.None;
}
public function dispose():Void {
engine.dispose();
}
}

View File

@@ -0,0 +1,41 @@
package ru.m.tankz.game;
import ru.m.tankz.game.Game;
import ru.m.tankz.config.Config;
typedef ControlType = String;
typedef ControId = {
var type:ControlType;
var index:Int;
}
typedef PlayerState = {
var index:Int;
var tank:TankType;
var life:Int;
var control:ControId;
}
typedef TeamState = {
var players:Map<Int, PlayerState>;
var life:Int;
var lose:Bool;
}
class GameState {
public var type:GameType;
public var level:Int;
public var teams:Map<TeamId, TeamState>;
public function new() {
type = null;
level = -1;
teams = new Map();
}
}

View File

@@ -0,0 +1,42 @@
package ru.m.tankz.game;
import ru.m.tankz.control.Control;
import ru.m.tankz.game.Game;
class Player {
public var id(default, null):PlayerId;
public var tankId(default, set):Int;
public var control(default, set):Control;
public function new(id:PlayerId, control:Control=null) {
this.id = id;
this.control = control;
}
public function set_tankId(value:Int):Int {
tankId = value;
if (control != null) {
control.tankId = tankId;
if (tankId != 0) {
control.start();
} else {
control.stop();
}
}
return tankId;
}
public function set_control(value:Control):Control {
if (control != null) control.dispose();
control = value;
if (control != null) {
control.tankId = tankId;
if (tankId != 0) {
control.start();
}
}
return control;
}
}

View File

@@ -0,0 +1,89 @@
package ru.m.tankz.game;
import haxe.Timer;
import ru.m.tankz.game.Game;
import ru.m.tankz.config.Config;
typedef SpawnTask = {
var point:SpawnPoint;
var playerId:PlayerId;
}
class Spawner {
public var active(get, never):Bool;
private var config:TeamConfig;
private var runner:SpawnTask -> Void;
private var queue:Array<SpawnTask>;
private var timer:Timer;
private var indexedPoints:Map<Int, SpawnPoint>;
private var anyPoints:Array<SpawnPoint>;
private var index:Int;
public function new(config:TeamConfig, runner:SpawnTask -> Void) {
this.config = config;
this.runner = runner;
queue = [];
indexedPoints = new Map();
anyPoints = [];
for (point in config.points) {
if (point.type == 'tank') {
if (point.index > -1) {
indexedPoints.set(point.index, point);
} else {
anyPoints.push(point);
}
}
}
}
public function getPoint(type:String, index:Int=-1):Null<SpawnPoint> {
for (point in config.points) {
if (point.type == type && point.index == index) {
return point;
}
}
return null;
}
public function push(playerId:PlayerId):Void {
var point:SpawnPoint = null;
if (indexedPoints.exists(playerId.index)) {
point = indexedPoints.get(playerId.index);
} else {
point = anyPoints[index++];
if (index >= anyPoints.length) index = 0;
}
if (point != null) {
queue.push({playerId:playerId, point:point});
run();
}
}
private function run():Void {
if (timer == null) {
timer = new Timer(config.spawnInterval);
timer.run = spawn;
}
}
private function spawn():Void {
if (queue.length == 0) {
if (timer != null) {
timer.stop();
timer = null;
}
} else {
runner(queue.shift());
}
}
private function get_active():Bool {
return queue.length > 0;
}
}

View File

@@ -0,0 +1,21 @@
package ru.m.tankz.game;
import ru.m.tankz.config.Config.TeamConfig;
import ru.m.tankz.game.Player;
import ru.m.tankz.game.Game;
class Team {
public var id(default, null):TeamId;
public var config(default, null):TeamConfig;
public var players(default, null):Array<Player>;
private static var i:Int = 0;
public function new(config:TeamConfig) {
this.id = config.id;
this.config = config;
this.players = [];
}
}

View File

@@ -24,6 +24,7 @@ class Brick implements IKey {
public var rect(default, null):Rectangle; public var rect(default, null):Rectangle;
public var cells(default, null):HashMap<Point, GridCell>; public var cells(default, null):HashMap<Point, GridCell>;
public var broken(get, null):Int;
public var destroyed(get, set):Bool; public var destroyed(get, set):Bool;
public function new(mapConfig:MapConfig, config:BrickConfig, cellX:Int, cellY:Int, cells:HashMap<Point, GridCell>) { public function new(mapConfig:MapConfig, config:BrickConfig, cellX:Int, cellY:Int, cells:HashMap<Point, GridCell>) {
@@ -40,6 +41,16 @@ class Brick implements IKey {
); );
} }
public function get_broken():Int {
var i:Int = 0;
for (c in cells.iterator()) {
if (c.destroyed) {
i++;
}
}
return i;
}
public function get_destroyed():Bool { public function get_destroyed():Bool {
var i = 0; var i = 0;
var result:Bool = false; var result:Bool = false;

View File

@@ -28,15 +28,20 @@ class LevelMap {
gridWidth = config.gridWidth; gridWidth = config.gridWidth;
gridHeight = config.gridHeight; gridHeight = config.gridHeight;
bricksMap = new HashMap(); bricksMap = new HashMap();
bricks = [];
grid = new Grid( grid = new Grid(
Std.int(cellWidth / 2), Std.int(cellWidth / 2),
Std.int(cellHeight / 2), Std.int(cellHeight / 2),
Std.int(cellWidth * gridWidth), Std.int(cellWidth * gridWidth),
Std.int(cellHeight * gridHeight) Std.int(cellHeight * gridHeight)
); );
bricks = Lambda.array(Lambda.mapi(config.bricks, function(i:Int, brickConfig:BrickConfig):Brick { }
public function setData(data:Array<BrickConfig>):Void {
bricksMap = new HashMap();
bricks = Lambda.array(Lambda.mapi(data, function(i:Int, brickConfig:BrickConfig):Brick {
var cellX = Std.int(i % gridWidth); var cellX = Std.int(i % gridWidth);
var cellY = Std.int(Math.floor(i / gridHeight)); var cellY = Std.int(Math.floor(i / gridWidth));
var cells:HashMap<Point, GridCell> = new HashMap(); var cells:HashMap<Point, GridCell> = new HashMap();
var point:Point = new Point(cellX * 2, cellY * 2); var point:Point = new Point(cellX * 2, cellY * 2);
if (brickConfig.layer > 0 || brickConfig.armor > 0) { if (brickConfig.layer > 0 || brickConfig.armor > 0) {

View File

@@ -44,4 +44,4 @@ module.exports = () => {
}) })
}; };
module.exports.log = log; module.exports.log = _log;

View File

@@ -76,6 +76,10 @@ class Haxe extends Sdk {
return this.haxelib(args); return this.haxelib(args);
}; };
if (!Array.isArray(packages)) {
packages = Object.entries(packages).map(([k, v]) => ({name: k, version: v}));
}
for (let pack of packages) { for (let pack of packages) {
const args = []; const args = [];
let version = null; let version = null;
@@ -83,6 +87,10 @@ class Haxe extends Sdk {
args.push('install', pack); args.push('install', pack);
} else if (typeof pack === 'object') { } else if (typeof pack === 'object') {
version = pack.version; version = pack.version;
if (version.substr(0, 3) === 'git') {
pack.git = version;
version = null;
}
if (pack.git) { if (pack.git) {
args.push('git', pack.name, pack.git); args.push('git', pack.name, pack.git);
if (pack.branch) args.push(pack.branch); if (pack.branch) args.push(pack.branch);
@@ -93,7 +101,11 @@ class Haxe extends Sdk {
args.push('--always'); args.push('--always');
} }
let path = `${this.path}/lib/${args[1]}`; let path = `${this.path}/lib/${args[1]}`;
if (version) path += `/${version.replace(/\./g, ',')}`; if (version) {
path += `/${version.replace(/\./g, ',')}`;
} else if (pack.git) {
path += '/git';
}
if (!fs.existsSync(path)) { if (!fs.existsSync(path)) {
promise = promise.then(next(args)); promise = promise.then(next(args));
} }
@@ -171,6 +183,7 @@ class Haxe extends Sdk {
const result = { const result = {
'flash': `${buildDir}/flash/bin/*.swf`, 'flash': `${buildDir}/flash/bin/*.swf`,
'html5': `${buildDir}/html5/bin/**/*`, 'html5': `${buildDir}/html5/bin/**/*`,
'linux': `${buildDir}/linux/bin/**/*`,
}[params.platform]; }[params.platform];
vfs.src(result).pipe(through.obj((file, enc, cb) => { vfs.src(result).pipe(through.obj((file, enc, cb) => {
file.debug = debug; file.debug = debug;

43
tasks/tail.js Normal file
View File

@@ -0,0 +1,43 @@
const through = require('through2');
const colors = require('ansi-colors');
const log = require('fancy-log');
const TAG = colors.green('[tail]');
const { Writable } = require('stream');
const { StringDecoder } = require('string_decoder');
class StringWritable extends Writable {
constructor(handler, options) {
super(options);
this.handler = handler;
const state = this._writableState;
this._decoder = new StringDecoder(state.defaultEncoding);
this.data = '';
}
_write(chunk, encoding, callback) {
if (encoding === 'buffer') {
chunk = this._decoder.write(chunk);
for (const line of chunk.split('\n')) if (line.length) {
this.handler(line);
}
}
this.data += chunk;
callback();
}
_final(callback) {
this.data += this._decoder.end();
callback();
}
}
module.exports = (handler) => {
return through.obj(function (file, enc, callback) {
file.contents.pipe(new StringWritable(handler));
this.push(file);
callback();
});
};