[common] add hard bot
This commit is contained in:
@@ -6,8 +6,6 @@ typedef GameType = String;
|
||||
|
||||
typedef TeamId = String;
|
||||
|
||||
typedef ControlType = String;
|
||||
|
||||
typedef BrickType = String;
|
||||
|
||||
typedef TankType = String;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
47
src/common/haxe/ru/m/tankz/bot/BotHelper.hx
Normal file
47
src/common/haxe/ru/m/tankz/bot/BotHelper.hx
Normal 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);
|
||||
}
|
||||
}
|
||||
78
src/common/haxe/ru/m/tankz/bot/HardBotControl.hx
Normal file
78
src/common/haxe/ru/m/tankz/bot/HardBotControl.hx
Normal 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());
|
||||
}
|
||||
}
|
||||
54
src/common/haxe/ru/m/tankz/bot/StupidBotControl.hx
Normal file
54
src/common/haxe/ru/m/tankz/bot/StupidBotControl.hx
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user