wip: implement action tree and minimax for ai
This commit is contained in:
parent
81cbec5348
commit
7a5d9dca76
@ -48,11 +48,13 @@ class AI {
|
||||
Character character = (Character)state.getEntities().findEntity(turn);
|
||||
|
||||
BoardAnalyzer analyzer = new BoardAnalyzer(state, turn);
|
||||
Action action = analyzer.analyze(state, character.getPosition(), turn);
|
||||
Action action = analyzer.analyze(state);
|
||||
|
||||
switch(action.type) {
|
||||
case None -> {
|
||||
System.out.println(" Result: doing nothing");
|
||||
result.add(new RequestBuilder(RequestType.EndRoundRequest)
|
||||
.buildGameRequest());
|
||||
}
|
||||
case Move -> {
|
||||
System.out.println(" Result: moving to " + action.target);
|
||||
@ -163,11 +165,13 @@ class AI {
|
||||
.buildCharacterRequest());
|
||||
}
|
||||
}
|
||||
case EndTurn -> {
|
||||
System.out.println(" Result: ending turn");
|
||||
result.add(new RequestBuilder(RequestType.EndRoundRequest)
|
||||
.buildGameRequest());
|
||||
}
|
||||
}
|
||||
|
||||
result.add(new RequestBuilder(RequestType.EndRoundRequest)
|
||||
.buildGameRequest());
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@ -51,4 +51,8 @@ class Action {
|
||||
this.targetEntity = targetEntity;
|
||||
this.stone = stone;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return this.type.toString();
|
||||
}
|
||||
}
|
||||
|
@ -6,5 +6,6 @@ enum ActionType {
|
||||
MeleeAttack,
|
||||
RangedAttack,
|
||||
UseStone,
|
||||
GiveStone
|
||||
GiveStone,
|
||||
EndTurn
|
||||
}
|
||||
|
@ -11,13 +11,22 @@ import java.util.Objects;
|
||||
|
||||
class Board {
|
||||
private final Piece[][] data;
|
||||
private final EntityType origin;
|
||||
private final EntityID turn;
|
||||
public final EntityType origin;
|
||||
public final EntityID turn;
|
||||
public final boolean ended;
|
||||
|
||||
private Board(Piece[][] data, EntityType origin, EntityID turn) {
|
||||
this.data = data;
|
||||
this.origin = origin;
|
||||
this.turn = turn;
|
||||
this.ended = false;
|
||||
}
|
||||
|
||||
private Board(Piece[][] data, EntityType origin, EntityID turn, boolean ended) {
|
||||
this.data = data;
|
||||
this.origin = origin;
|
||||
this.turn = turn;
|
||||
this.ended = ended;
|
||||
}
|
||||
|
||||
public static Board generate(GameStateView state, EntityID turn) {
|
||||
@ -100,10 +109,16 @@ class Board {
|
||||
}
|
||||
for(IntVector2 dir: IntVector2.CardinalDirections) {
|
||||
IntVector2 target = position.add(dir);
|
||||
if(target.getX() < 0 || target.getX() >= state.getMapSize().getX() || target.getY() < 0 || target.getY() >= state.getMapSize().getY()) {
|
||||
if(
|
||||
target.getX() < 0 || target.getX() >= state.getMapSize().getX() ||
|
||||
target.getY() < 0 || target.getY() >= state.getMapSize().getY()
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
if(this.data[target.getY()][target.getX()].type == PieceType.Empty) {
|
||||
if(
|
||||
this.data[target.getY()][target.getX()].type == PieceType.Empty ||
|
||||
this.data[target.getY()][target.getX()].type == PieceType.Character
|
||||
) {
|
||||
result.add(new Action(ActionType.Move, target));
|
||||
}
|
||||
}
|
||||
@ -142,6 +157,9 @@ class Board {
|
||||
continue;
|
||||
}
|
||||
for(StoneType stone: StoneType.values()) {
|
||||
if(state.getStoneCooldown(stone) > 0) {
|
||||
continue;
|
||||
}
|
||||
if(!character.inventory.hasStone(stone)) {
|
||||
continue;
|
||||
}
|
||||
@ -223,6 +241,9 @@ class Board {
|
||||
}
|
||||
}
|
||||
}
|
||||
case EndTurn -> {
|
||||
result.add(new Action(ActionType.EndTurn));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -251,6 +272,8 @@ class Board {
|
||||
}
|
||||
}
|
||||
|
||||
boolean ended = false;
|
||||
|
||||
//TODO: finalize action applying
|
||||
|
||||
switch(action.type) {
|
||||
@ -262,9 +285,10 @@ class Board {
|
||||
case MeleeAttack -> {
|
||||
clone[action.target.getY()][action.target.getX()].hp.decreaseValue(character.meleeDamage);
|
||||
current.ap.decreaseValue(1);
|
||||
//todo: drop stones........
|
||||
}
|
||||
case RangedAttack -> {
|
||||
clone[action.target.getY()][action.target.getX()].hp.decreaseValue(character.meleeDamage);
|
||||
clone[action.target.getY()][action.target.getX()].hp.decreaseValue(character.rangedDamage);
|
||||
current.ap.decreaseValue(1);
|
||||
}
|
||||
case UseStone -> {
|
||||
@ -292,11 +316,31 @@ class Board {
|
||||
case GiveStone -> {
|
||||
|
||||
}
|
||||
case EndTurn -> {
|
||||
ended = true;
|
||||
}
|
||||
}
|
||||
|
||||
//...get next turn in turn order...
|
||||
//TODO: figure out next turn properly
|
||||
return new Board(clone, origin, turn);
|
||||
if(ended || (current.mp.getValue() == 0 && current.ap.getValue() == 0)) {
|
||||
ArrayList<EntityID> alive = new ArrayList<>();
|
||||
for(EntityID next: state.getTurnOrder()) {
|
||||
if(next.type == EntityType.NPC && next.id == NPCType.Thanos.getID()) {
|
||||
alive.add(next);
|
||||
}else if(next.type == EntityType.P1 || next.type == EntityType.P2) {
|
||||
if(((Character)state.getEntities().findEntity(next)).isAlive()) {
|
||||
alive.add(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
int index = alive.indexOf(turn);
|
||||
if(index == alive.size() - 1) {
|
||||
return new Board(clone, origin, turn, true);
|
||||
}else {
|
||||
return new Board(clone, origin, alive.get(index + 1));
|
||||
}
|
||||
}else {
|
||||
return new Board(clone, origin, turn);
|
||||
}
|
||||
}
|
||||
|
||||
protected float calculateScore() {
|
||||
|
@ -1,6 +1,5 @@
|
||||
package uulm.teamname.marvelous.gamelibrary.ai;
|
||||
|
||||
import uulm.teamname.marvelous.gamelibrary.IntVector2;
|
||||
import uulm.teamname.marvelous.gamelibrary.entities.EntityID;
|
||||
import uulm.teamname.marvelous.gamelibrary.entities.EntityType;
|
||||
import uulm.teamname.marvelous.gamelibrary.gamelogic.GameStateView;
|
||||
@ -11,18 +10,148 @@ import java.util.HashMap;
|
||||
class BoardAnalyzer {
|
||||
private final Board origin;
|
||||
private final EntityType player;
|
||||
private final HashMap<Integer, Float> cache = new HashMap<>();
|
||||
private final HashMap<Integer, Float> scoreCache = new HashMap<>();
|
||||
private final HashMap<Integer, ArrayList<Action>> actionCache = new HashMap<>();
|
||||
private Action bestAction = null;
|
||||
|
||||
public BoardAnalyzer(GameStateView state, EntityID turn) {
|
||||
this.origin = Board.generate(state, turn);
|
||||
this.player = turn.type;
|
||||
}
|
||||
|
||||
public Action analyze(GameStateView state, IntVector2 position, EntityID turn) {
|
||||
ArrayList<Action> actions = origin.generateActions(state);
|
||||
public Action analyze(GameStateView state) {
|
||||
Node tree = new Node(origin, new Action(ActionType.None));
|
||||
|
||||
//TODO: create minimax tree
|
||||
int maxDepth = 2;
|
||||
int depth = 0;
|
||||
long startTime = System.nanoTime();
|
||||
while(System.nanoTime() - startTime <= 1000 * 1000 * 1000) {
|
||||
expandTree(tree, state);
|
||||
depth++;
|
||||
if(depth > maxDepth) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
alphaBetaMax(tree, Float.MIN_VALUE, Float.MAX_VALUE, true);
|
||||
|
||||
if(bestAction != null) {
|
||||
return bestAction;
|
||||
}
|
||||
|
||||
return new Action(ActionType.None);
|
||||
}
|
||||
|
||||
private Float alphaBetaMax(Node root, Float a, Float b, boolean main) {
|
||||
if(!root.hasChildren()) {
|
||||
return calculateScore(root.board);
|
||||
}
|
||||
Float w = a;
|
||||
for(Node child: root.getChildren()) {
|
||||
Float v;
|
||||
if(child.board.turn.type == child.board.origin) {
|
||||
v = alphaBetaMax(child, a, w, false);
|
||||
}else {
|
||||
v = alphaBetaMin(child, w, b);
|
||||
}
|
||||
if(v > w) {
|
||||
w = v;
|
||||
}
|
||||
if(w >= b) {
|
||||
if(main) {
|
||||
bestAction = child.action;
|
||||
}
|
||||
return w;
|
||||
}
|
||||
if(main) {
|
||||
bestAction = child.action;
|
||||
}
|
||||
}
|
||||
return w;
|
||||
}
|
||||
|
||||
private Float alphaBetaMin(Node root, Float a, Float b) {
|
||||
if(!root.hasChildren()) {
|
||||
return calculateScore(root.board);
|
||||
}
|
||||
Float w = b;
|
||||
for(Node child: root.getChildren()) {
|
||||
Float v;
|
||||
if(child.board.turn.type == child.board.origin) {
|
||||
v = alphaBetaMax(child, a, w, false);
|
||||
}else {
|
||||
v = alphaBetaMin(child, w, b);
|
||||
}
|
||||
if(v < w) {
|
||||
w = v;
|
||||
}
|
||||
if(w <= a) {
|
||||
return w;
|
||||
}
|
||||
}
|
||||
return w;
|
||||
}
|
||||
|
||||
private ArrayList<Node> getLeaves(Node root) {
|
||||
return getLeaves(root, new ArrayList<>());
|
||||
}
|
||||
|
||||
private ArrayList<Node> getLeaves(Node root, ArrayList<Node> accumulator) {
|
||||
for(Node child: root.getChildren()) {
|
||||
if(child.hasChildren()) {
|
||||
getLeaves(child, accumulator);
|
||||
}else {
|
||||
accumulator.add(child);
|
||||
}
|
||||
}
|
||||
return accumulator;
|
||||
}
|
||||
|
||||
private void expandTree(Node root, GameStateView state) {
|
||||
if(!root.hasChildren()) {
|
||||
expandNode(root, state);
|
||||
return;
|
||||
}
|
||||
for(Node child: root.getChildren()) {
|
||||
if(child.board.ended) {
|
||||
continue;
|
||||
}
|
||||
if(child.hasChildren()) {
|
||||
expandTree(child, state);
|
||||
}else {
|
||||
expandNode(child, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void expandNode(Node origin, GameStateView state) {
|
||||
ArrayList<Action> actions = generateActions(origin.board, state);
|
||||
for(Action action: actions) {
|
||||
Board result = origin.board.applyAction(state, action);
|
||||
scoreCache.put(result.hashCode(), result.calculateScore());
|
||||
origin.addChild(result, action, result.calculateScore());
|
||||
}
|
||||
}
|
||||
|
||||
private ArrayList<Action> generateActions(Board board, GameStateView state) {
|
||||
int hash = board.hashCode();
|
||||
if(actionCache.containsKey(hash)) {
|
||||
return actionCache.get(hash);
|
||||
}else {
|
||||
ArrayList<Action> result = board.generateActions(state);
|
||||
actionCache.put(hash, result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private Float calculateScore(Board board) {
|
||||
int hash = board.hashCode();
|
||||
if(scoreCache.containsKey(hash)) {
|
||||
return scoreCache.get(hash);
|
||||
}else {
|
||||
Float result = board.calculateScore();
|
||||
scoreCache.put(hash, result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,60 @@
|
||||
package uulm.teamname.marvelous.gamelibrary.ai;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Node {
|
||||
public final Board board;
|
||||
public final Action action;
|
||||
public final Float score;
|
||||
private final List<Node> children = new ArrayList<>();
|
||||
private Node parent = null;
|
||||
|
||||
public Node(Board board, Action action) {
|
||||
this.board = board;
|
||||
this.action = action;
|
||||
this.score = 0f;
|
||||
}
|
||||
|
||||
public Node(Board board, Action action, Float score) {
|
||||
this.board = board;
|
||||
this.action = action;
|
||||
this.score = score;
|
||||
}
|
||||
|
||||
public void addChild(Node child) {
|
||||
child.setParent(this);
|
||||
this.children.add(child);
|
||||
}
|
||||
|
||||
public void addChild(Board board, Action action) {
|
||||
this.addChild(new Node(board, action));
|
||||
}
|
||||
|
||||
public void addChild(Board board, Action action, Float score) {
|
||||
this.addChild(new Node(board, action, score));
|
||||
}
|
||||
|
||||
public void addChildren(List<Node> children) {
|
||||
for(Node t : children) {
|
||||
t.setParent(this);
|
||||
}
|
||||
this.children.addAll(children);
|
||||
}
|
||||
|
||||
public List<Node> getChildren() {
|
||||
return children;
|
||||
}
|
||||
|
||||
public boolean hasChildren() {
|
||||
return !children.isEmpty();
|
||||
}
|
||||
|
||||
private void setParent(Node parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public Node getParent() {
|
||||
return parent;
|
||||
}
|
||||
}
|
@ -8,15 +8,15 @@ import java.util.HashSet;
|
||||
class Piece {
|
||||
public final PieceType type;
|
||||
|
||||
public final EntityID id;
|
||||
public EntityID id;
|
||||
|
||||
public final Stat hp;
|
||||
public final Stat mp;
|
||||
public final Stat ap;
|
||||
public Stat hp;
|
||||
public Stat mp;
|
||||
public Stat ap;
|
||||
|
||||
public final HashSet<StoneType> inventory;
|
||||
public HashSet<StoneType> inventory;
|
||||
|
||||
public final StoneType stone;
|
||||
public StoneType stone;
|
||||
|
||||
public Piece(PieceType type) {
|
||||
this.type = type;
|
||||
@ -69,17 +69,13 @@ class Piece {
|
||||
}
|
||||
|
||||
public Piece clone() {
|
||||
return new Piece(
|
||||
this.type,
|
||||
this.id,
|
||||
this.hp.getValue(),
|
||||
this.hp.getMax(),
|
||||
this.mp.getValue(),
|
||||
this.mp.getMax(),
|
||||
this.ap.getValue(),
|
||||
this.ap.getMax(),
|
||||
this.inventory.toArray(new StoneType[0]),
|
||||
this.stone
|
||||
);
|
||||
Piece clone = new Piece(type);
|
||||
clone.id = this.id != null ? this.id.clone() : null;
|
||||
clone.hp = this.hp != null ? new Stat(StatType.HP, this.hp.getValue(), this.hp.getMax()) : null;
|
||||
clone.mp = this.mp != null ? new Stat(StatType.MP, this.mp.getValue(), this.mp.getMax()) : null;
|
||||
clone.ap = this.ap != null ? new Stat(StatType.AP, this.ap.getValue(), this.ap.getMax()) : null;
|
||||
clone.inventory = this.inventory != null ? (HashSet<StoneType>)this.inventory.clone() : null;
|
||||
clone.stone = this.stone;
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user