[common] add hard bot

This commit is contained in:
2019-03-21 17:45:03 +03:00
parent c6639e11b1
commit 0b17fbf804
23 changed files with 288 additions and 149 deletions

View File

@@ -30,7 +30,8 @@ class Style {
Skin.text(textColor, 16, fontFamily),
]);
resources.skin.put("text.header", [
Skin.color(lightColor),
Skin.color(0x000000, 0.1),
Skin.border(lightColor, 1, 2),
Skin.text(textColor, 22, fontFamily),
Skin.size(200, 38),
]);
@@ -40,11 +41,13 @@ class Style {
Skin.size(250, 50)
]);
resources.skin.put("text.box", [
Skin.color(lightColor),
Skin.color(0x000000, 0.1),
Skin.border(lightColor, 1, 2),
Skin.text(textColor, 16, fontFamily),
]);
resources.skin.put("text.box.active", [
Skin.color(0x55aa55),
Skin.border(0x88dd88, 1, 2),
Skin.text(textColor, 16, fontFamily),
]);
resources.skin.put("button.simple", [

View File

@@ -1,23 +1,21 @@
package ru.m.tankz.control;
import ru.m.tankz.bot.StupidBotControl;
import ru.m.tankz.bot.HardBotControl;
import ru.m.tankz.control.Control.Controller;
import ru.m.tankz.Type;
import ru.m.tankz.bot.BotControl;
class ClientControlFactory implements IControlFactory {
private var humanControlIndex:Int;
public function new() {
humanControlIndex = 0;
}
public function build(id:PlayerId, type:ControlType):Control {
return switch (type) {
case Control.HUMAN: new HumanControl(id, humanControlIndex++);
case Control.BOT: new BotControl(id);
case Control.NONE: null;
case _: throw 'Unsupported control type: "${type}"';
public function build(id:PlayerId, controller:Controller):Control {
return switch controller {
case HUMAN(index): new HumanControl(id, index);
case BOT(type): switch type {
case StupidBotControl.BOT_TYPE: new StupidBotControl(id);
case HardBotControl.BOT_TYPE: new HardBotControl(id);
case _: null;
}
case NONE: null;
}
}
}

View File

@@ -37,7 +37,10 @@ import ru.m.tankz.preset.DotaGame;
}
public function onShow() {
resultView.data = resultState.players.filter(function(player) return player.control == Control.HUMAN);
resultView.data = resultState.players.filter(function(player) return switch player.controller {
case HUMAN(_): true;
case _: false;
});
levelLabel.text = 'Level: ${resultState.level}';
nextButton.visible = state != null;
}

View File

@@ -2,7 +2,7 @@
views:
- $type: haxework.gui.VGroupView
skinId: container
layout.margin: 3
layout.margin: 5
views:
- $type: haxework.gui.ImageView
image: $asset:image:resources/image/ui/logo.png

View File

@@ -8,8 +8,7 @@ views:
$type: haxework.gui.LabelView
skinId: text.box
geometry.size.height: 38
geometry.padding: [20, 0]
geometry.hAlign: center
geometry.size.width: 100%
- $type: haxework.gui.SpriteView
geometry.size.height: 50%
- id: bot

View File

@@ -5,6 +5,7 @@ import haxework.gui.GroupView;
import haxework.resources.IResources;
import ru.m.tankz.bundle.IConfigBundle;
import ru.m.tankz.config.Config;
import ru.m.tankz.control.Control.Controller;
import ru.m.tankz.game.GameState;
import ru.m.tankz.Type;
@@ -35,7 +36,8 @@ class LevelFrame extends GroupView {
for (team in value.teams) {
for (player in team.players) {
var playerId = new PlayerId(team.id, player.index);
state.players.push(new PlayerState(playerId, player.control, player.life));
var controller = player.human ? HUMAN(player.index) : NONE;
state.players.push(new PlayerState(playerId, controller, player.life));
}
}
}

View File

@@ -1,12 +1,12 @@
package ru.m.tankz.frame.common;
import haxework.color.ColorUtil;
import ru.m.tankz.control.Control;
import haxework.gui.DataView;
import haxework.gui.HGroupView;
import haxework.gui.LabelView;
import haxework.gui.skin.ISkin;
import haxework.gui.ToggleButtonView;
import ru.m.tankz.control.Control;
import ru.m.tankz.game.GameState;
import ru.m.tankz.Type.TeamId;
@@ -42,7 +42,7 @@ class TeamSkin implements ISkin<TeamButton> {
}
@:template class PlayerView extends HGroupView {
private static inline var NONE:TeamId = "none";
private static inline var TEAM_NONE:TeamId = "none";
public var item_index(default, set):Int;
public var data(default, set):Array<PlayerState>;
@@ -59,13 +59,13 @@ class TeamSkin implements ISkin<TeamButton> {
view.skin = [new TeamSkin(getTeamColor(team))];
view.geometry.padding = [10, 5];
view.team = team;
view.on = team == NONE;
view.on = team == TEAM_NONE;
return view;
}
private function set_data(value:Array<PlayerState>):Array<PlayerState> {
data = value;
teams.data = [NONE].concat([for (team in state.preset.teams) team.id]);
teams.data = [TEAM_NONE].concat([for (team in state.preset.teams) team.id]);
return data;
}
@@ -88,16 +88,20 @@ class TeamSkin implements ISkin<TeamButton> {
private function onTeamSelect(team:TeamId) {
if (player != null) {
player.control = Control.BOT;
player.controller = NONE;
player.color = 0;
player = null;
}
for (p in data) {
if (p.id.team == team && p.control != Control.HUMAN) {
player = p;
player.control = Control.HUMAN;
player.color = ColorUtil.multiply(state.config.getTeam(team).color, 1.7);
break;
if (p.id.team == team) {
switch (p.controller) {
case NONE:
player = p;
player.controller = HUMAN(item_index);
player.color = ColorUtil.multiply(state.config.getTeam(team).color, 1.7);
break;
case _:
}
}
}
for (view in teams.views) {

View File

@@ -7,14 +7,14 @@ views:
tank: bc
color: 0xff4422
- $type: haxework.gui.SpriteView
geometry.size.width: 50%
geometry.size.width: 25%
- id: level
$type: haxework.gui.LabelView
skinId: text.box
geometry.size.height: 38
geometry.padding: [20, 0]
- $type: haxework.gui.SpriteView
geometry.size.width: 50%
- $type: haxework.gui.SpriteView
geometry.size.width: 25%
- id: dire
$type: ru.m.tankz.frame.common.LifeView
tank: bc

View File

@@ -44,11 +44,14 @@ import ru.m.tankz.storage.SettingsStorage;
}
}
private function onItemSelect(index:Int, value:ActionItem, view:ActionView):Void {
view.edit();
}
private function _change():Void {
var p: Promise<Int> = Promise.promise(0);
for (view in list.views) {
var v: ActionView = cast view;
if (v.data == null) break;
p = p.pipe(function(_):Promise<Int> return v.edit());
}
p.then(function(_) _save());

View File

@@ -27,3 +27,4 @@ views:
layout:
$type: haxework.gui.layout.VerticalLayout
factory: $this:viewFactory
+onItemSelect: $this:onItemSelect

View File

@@ -6,8 +6,6 @@ typedef GameType = String;
typedef TeamId = String;
typedef ControlType = String;
typedef BrickType = String;
typedef TankType = String;

View File

@@ -3,117 +3,55 @@ package ru.m.tankz.bot;
import haxe.Timer;
import ru.m.geom.Direction;
import ru.m.tankz.control.Control;
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.Type;
class BotHelper {
public static function findEagle(team:TeamId, handler:ControlHandler):Null<Eagle> {
for (entity in handler.entities) {
switch (EntityTypeResolver.of(entity)) {
case EntityType.EAGLE(eagle):
if (eagle.team != team) {
return eagle;
}
case x:
}
}
return null;
}
public static function getDirectionTo(entity:Entity, target:Entity):Direction {
var x:Float = target.rect.x - entity.rect.x;
var y:Float = target.rect.y - entity.rect.y;
var xD:Direction = Direction.from(Std.int(x / Math.abs(x)), 0);
var yD:Direction = Direction.from(0, Std.int(y / Math.abs(y)));
if (entity.rect.direction == xD) return yD;
if (entity.rect.direction == yD) return xD;
return Math.abs(x) > Math.abs(y) ? xD : yD;
}
public static function randomDirection():Direction {
return [
Direction.TOP,
Direction.BOTTOM,
Direction.LEFT,
Direction.RIGHT,
][Math.floor(Math.random() * 4)];
}
}
class BotControl extends Control {
private var shotTimer:Timer;
private var turnRandomTimer:Timer;
private var turnTimer:Timer;
private var tank(get, null):Tank;
public function new(playerId:PlayerId) {
super(playerId);
}
override public function onCollision(with:EntityType):Void {
switch (with) {
case EntityType.TANK(_): turnAfter(300);
case EntityType.CELL(_): turnAfter(300);
case _:
}
}
override public function start():Void {
if (handler == null) return;
action(TankAction.MOVE(tank.rect.direction));
if (shotTimer == null) {
shotTimer = new Timer(1000);
shotTimer.run = shot;
}
if (turnRandomTimer == null) {
turnRandomTimer = new Timer(2000);
turnRandomTimer.run = turn;
}
private inline function get_tank():Tank {
return cast handler.entities[tankId];
}
override public function stop():Void {
super.stop();
if (shotTimer != null) {
shotTimer.stop();
shotTimer = null;
}
if (turnRandomTimer != null) {
turnRandomTimer.stop();
turnRandomTimer = null;
}
}
public function shot():Void {
action(TankAction.SHOT);
}
public function turnAfter(delay:Int):Void {
if (turnTimer == null) {
turnTimer = Timer.delay(turn, delay);
}
}
public function turn():Void {
if (turnTimer != null) {
turnTimer.stop();
turnTimer = null;
}
// ToDo:
if (handler == null || tank == null) return;
var eagle:Eagle = BotHelper.findEagle(playerId.team, handler);
if (eagle != null && Math.random() > 0.5) {
action(TankAction.MOVE(BotHelper.getDirectionTo(tank, eagle)));
} else {
action(TankAction.MOVE(BotHelper.randomDirection()));
}
private function _shot():Void {
if (shotTimer != null) {
shotTimer.stop();
shotTimer = null;
}
action(SHOT);
}
public function shot(delay:Int = 100):Void {
if (shotTimer == null) {
shotTimer = Timer.delay(function() _shot(), delay);
}
}
private function get_tank():Tank {
return cast handler.entities[tankId];
private function _turn(direction:Direction):Void {
if (turnTimer != null) {
turnTimer.stop();
turnTimer = null;
}
action(MOVE(direction));
}
public function turn(direction:Direction, delay:Int = 300):Void {
if (turnTimer == null) {
turnTimer = Timer.delay(function() _turn(direction), delay);
}
}
}

View File

@@ -0,0 +1,47 @@
package ru.m.tankz.bot;
import ru.m.geom.Direction;
import ru.m.tankz.core.Entity;
import ru.m.tankz.core.EntityType;
import ru.m.tankz.core.Eagle;
import ru.m.tankz.control.Control.ControlHandler;
import ru.m.tankz.Type;
class BotHelper {
public static function findEagle(team:TeamId, handler:ControlHandler):Null<Eagle> {
for (entity in handler.entities) {
switch (EntityTypeResolver.of(entity)) {
case EntityType.EAGLE(eagle):
if (eagle.team != team) {
return eagle;
}
case _:
}
}
return null;
}
public static function getDirectionTo(entity:Entity, target:Entity):Direction {
var x:Float = target.rect.center.x - entity.rect.center.x;
var y:Float = target.rect.center.y - entity.rect.center.y;
var xD:Direction = Direction.from(Std.int(x / Math.abs(x)), 0);
var yD:Direction = Direction.from(0, Std.int(y / Math.abs(y)));
return Math.abs(x) > Math.abs(y) ? xD : yD;
}
public static function randomDirection():Direction {
return [
Direction.TOP,
Direction.BOTTOM,
Direction.LEFT,
Direction.RIGHT,
][Math.floor(Math.random() * 4)];
}
public static function getDistance(entity:Entity, target:Entity):Float {
var x:Float = target.rect.center.x - entity.rect.center.x;
var y:Float = target.rect.center.y - entity.rect.center.y;
return Math.abs(x) + Math.abs(y);
}
}

View File

@@ -0,0 +1,78 @@
package ru.m.tankz.bot;
import haxe.Timer;
import ru.m.tankz.core.EntityType;
import ru.m.tankz.core.Tank;
class HardBotControl extends BotControl {
public static inline var BOT_TYPE = "hard";
private var actionTimer:Timer;
override public function onCollision(with:EntityType):Void {
switch (with) {
case TANK(t):
if (t.playerId.team != tank.playerId.team) {
shot();
}
case CELL(cell):
if (cell.layer == 2 && cell.armor <= tank.config.bullet.piercing) {
shot();
}
case EAGLE(eagle):
if (eagle.team != tank.playerId.team) {
shot();
}
case _:
}
calcTurn();
}
override public function start():Void {
if (handler == null) return;
turn(tank.rect.direction);
if (actionTimer == null) {
actionTimer = new Timer(500);
actionTimer.run = nextAction;
}
}
override public function stop():Void {
super.stop();
if (actionTimer != null) {
actionTimer.stop();
actionTimer = null;
}
}
private function nextAction():Void {
var enemy:Tank = null;
var distance:Float = Math.POSITIVE_INFINITY;
for (entity in handler.entities.iterator()) {
switch EntityTypeResolver.of(entity) {
case TANK(t):
if (t.playerId.team != tank.playerId.team) {
var d = BotHelper.getDistance(tank, t);
if (d < distance && (Math.abs(t.rect.x - tank.rect.x) < tank.rect.width / 2 || Math.abs(t.rect.y - tank.rect.y) < tank.rect.height / 2)) {
enemy = t;
distance = d;
}
}
case _:
}
}
if (enemy != null) {
var d = BotHelper.getDirectionTo(tank, enemy);
if (d != tank.rect.direction) {
turn(BotHelper.getDirectionTo(tank, enemy), 1);
}
shot();
} else if (Math.random() > 0.8) {
calcTurn();
}
}
private function calcTurn():Void {
turn(BotHelper.randomDirection());
}
}

View File

@@ -0,0 +1,54 @@
package ru.m.tankz.bot;
import haxe.Timer;
import ru.m.tankz.control.Control;
import ru.m.tankz.core.Eagle;
import ru.m.tankz.core.EntityType;
class StupidBotControl extends BotControl {
public static inline var BOT_TYPE = "stupid";
private var turnRandomTimer:Timer;
override public function onCollision(with:EntityType):Void {
switch (with) {
case TANK(_): calcTurn();
case CELL(_): calcTurn();
case EAGLE(_): calcTurn();
case _:
}
}
override public function start():Void {
super.start();
if (handler == null) return;
action(MOVE(tank.rect.direction));
if (turnRandomTimer == null) {
turnRandomTimer = new Timer(2000);
turnRandomTimer.run = calcTurn;
}
}
override public function stop():Void {
super.stop();
if (turnRandomTimer != null) {
turnRandomTimer.stop();
turnRandomTimer = null;
}
}
private function calcTurn():Void {
if (turnTimer != null) {
turnTimer.stop();
turnTimer = null;
}
// ToDo:
if (handler == null || tank == null) return;
var eagle:Eagle = BotHelper.findEagle(playerId.team, handler);
if (eagle != null && Math.random() > 0.5) {
turn(BotHelper.getDirectionTo(tank, eagle));
} else {
turn(BotHelper.randomDirection());
}
}
}

View File

@@ -75,7 +75,7 @@ typedef PlayerConfig = {
@:optional var protect:Float;
@:optional var life:Int;
@:optional var color:Color;
@:optional var control:ControlType;
@:optional var human:Bool;
}
typedef EagleConfig = {

View File

@@ -3,6 +3,7 @@ package ru.m.tankz.control;
import ru.m.geom.Direction;
import ru.m.tankz.core.Entity;
import ru.m.tankz.core.EntityType;
import ru.m.tankz.map.LevelMap;
import ru.m.tankz.Type;
enum TankAction {
@@ -12,11 +13,13 @@ enum TankAction {
SHOT;
}
class Control {
public static var NONE(default, never):ControlType = 'none';
public static var HUMAN(default, never):ControlType = 'human';
public static var BOT(default, never):ControlType = 'bot';
enum Controller {
NONE;
HUMAN(index:Int);
BOT(type:String);
}
class Control {
public var playerId(default, null):PlayerId;
public var tankId(default, default):Int;
private var handler:ControlHandler;
@@ -35,9 +38,7 @@ class Control {
}
}
public function onCollision(with:EntityType):Void {
}
public function onCollision(with:EntityType):Void {}
public function start():Void {}
@@ -50,6 +51,7 @@ class Control {
}
interface ControlHandler {
public var map(default, null):LevelMap;
public var entities(default, null):Map<Int, Entity>;
public function action(tankId:Int, action:TankAction):Void;
}

View File

@@ -1,8 +1,9 @@
package ru.m.tankz.control;
import ru.m.tankz.control.Control;
import ru.m.tankz.Type;
interface IControlFactory {
public function build(id:PlayerId, type:ControlType):Control;
public function build(id:PlayerId, controller:Controller):Control;
}

View File

@@ -1,5 +1,6 @@
package ru.m.tankz.control;
import ru.m.tankz.control.Control;
import ru.m.tankz.Type;
@@ -7,7 +8,7 @@ class NoneControlFactory implements IControlFactory {
public function new() {}
public function build(id:PlayerId, type:ControlType):Control {
public function build(id:PlayerId, controller:Controller):Control {
return null;
}
}

View File

@@ -109,14 +109,18 @@ class Game {
var team:Team = new Team(teamConfig, teamPoints);
teams[team.id] = team;
for (player in team.players.iterator()) {
var controlType:ControlType = Control.BOT;
var controller:Controller = Controller.BOT("hard");
var nextPlayer:PlayerState = Lambda.find(players, function(p) return p.id == player.id);
if (nextPlayer != null) {
player.state = nextPlayer;
players.remove(nextPlayer);
controlType = nextPlayer.control;
switch (nextPlayer.controller) {
case HUMAN(_):
controller = nextPlayer.controller;
case _:
}
}
var control = controlFactory.build(player.id, controlType);
var control = controlFactory.build(player.id, controller);
L.d(TAG, 'control(${player.id} - ${control})');
if (control != null) {
player.control = control;

View File

@@ -9,7 +9,7 @@ import ru.m.tankz.Type;
class PlayerState {
public var id:PlayerId;
public var tank:TankType;
public var control:ControlType;
public var controller:Controller;
public var color:Color;
public var life:Int;
public var score:Int;
@@ -17,10 +17,10 @@ class PlayerState {
public var shots:Int;
public var hits:Int;
public function new(id:PlayerId, control:ControlType = null, life:Int = 0) {
public function new(id:PlayerId, controller:Controller = null, life:Int = 0) {
this.id = id;
this.tank = null;
this.control = control == null ? Control.BOT : control;
this.controller = controller == null ? Controller.NONE : controller;
this.life = life;
this.score = 0;
this.frags = 0;

View File

@@ -23,7 +23,7 @@ player:
human: &human
life: 3
protect: 5
control: human
human: true
tanks:
- {type: human0, rate: 1}
bot: &bot

View File

@@ -1,5 +1,6 @@
package ru.m.tankz.editor.level;
import openfl.Assets;
import flash.display.DisplayObjectContainer;
import flash.display.Graphics;
import flash.display.Sprite;
@@ -25,8 +26,8 @@ class SpawnPointEntity extends Entity {
class SpawnPointItem extends BitmapItem<SpawnPointEntity> {
private var cellX:Int;
private var cellY:Int;
private var cellX:Int = -1;
private var cellY:Int = -1;
private var src:String;
public function new(value:SpawnPoint, config:Config) {
@@ -62,11 +63,12 @@ class SpawnPointItem extends BitmapItem<SpawnPointEntity> {
override public function update():Void {
super.update();
var image = Assets.getBitmapData(getImage());
if (cellX != value.point.x || cellY != value.point.y) {
cellX = value.point.x;
cellY = value.point.y;
value.rect.x = cellX * (value.rect.width / 2);
value.rect.y = cellY * (value.rect.height / 2);
value.rect.x = cellX * (value.rect.width / 2) + (value.rect.width - image.width) / 2;
value.rect.y = cellY * (value.rect.height / 2) + (value.rect.height - image.height) / 2;
redraw();
}
}
@@ -133,6 +135,7 @@ class MapEditView extends SpriteView {
p.x = b.cellX;
p.y = b.cellY;
drawMap();
drawMap();
break;
}
}