mirror of
https://github.com/OpenLiberty/liberty-bikes.git
synced 2025-02-23 11:19:12 +08:00
Ai framework and basic bot AI
This commit is contained in:
parent
8e75b2a025
commit
c173b5facd
@ -42,7 +42,7 @@ export class GameComponent implements OnInit {
|
||||
}
|
||||
if (json.players) {
|
||||
for (let player of json.players) {
|
||||
if (player.isAlive) {
|
||||
if (player.alive) {
|
||||
this.drawPlayer(player);
|
||||
}
|
||||
}
|
||||
|
149
game-service/src/main/java/org/libertybikes/game/bot/Hal.java
Normal file
149
game-service/src/main/java/org/libertybikes/game/bot/Hal.java
Normal file
@ -0,0 +1,149 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package org.libertybikes.game.bot;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import org.libertybikes.game.core.AIPlayer;
|
||||
import org.libertybikes.game.core.DIRECTION;
|
||||
import org.libertybikes.game.core.GameBoard;
|
||||
|
||||
public class Hal extends AIPlayer {
|
||||
|
||||
private int ticksTillRandomMove, ticksTillMove, numOfRandomMoves;
|
||||
private final static int BOARD_SIZE = 121;
|
||||
static Random ran = new Random();
|
||||
private final static int CD = 2;
|
||||
private final static int BD = 6;
|
||||
|
||||
public Hal(String id, String name, short playerNum) {
|
||||
super(id, name, playerNum);
|
||||
ticksTillRandomMove = 20;
|
||||
ticksTillMove = 4;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void broadCastBoard(short[][] board) {
|
||||
if (--ticksTillRandomMove < 1 && numOfRandomMoves < 10) {
|
||||
setDirection(DIRECTION.values()[ran.nextInt(4)]);
|
||||
ticksTillRandomMove = 35;
|
||||
numOfRandomMoves++;
|
||||
|
||||
} else {
|
||||
|
||||
if (--ticksTillMove > 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
ticksTillMove = 2;
|
||||
|
||||
switch (direction) {
|
||||
case DOWN:
|
||||
if (ran.nextBoolean()) {
|
||||
if (y + BD > BOARD_SIZE || checkCollision(board, x, y + CD)) {
|
||||
if (checkCollision(board, x + CD, y + CD)) {
|
||||
direction = DIRECTION.LEFT;
|
||||
}
|
||||
direction = DIRECTION.RIGHT;
|
||||
}
|
||||
} else {
|
||||
if (y + BD > BOARD_SIZE || checkCollision(board, x, y + CD)) {
|
||||
if (checkCollision(board, x - CD, y + CD)) {
|
||||
direction = DIRECTION.RIGHT;
|
||||
}
|
||||
direction = DIRECTION.LEFT;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case LEFT:
|
||||
if (ran.nextBoolean()) {
|
||||
if (x - BD < 0 || checkCollision(board, x - CD, y)) {
|
||||
if (checkCollision(board, x - CD, y - CD)) {
|
||||
direction = DIRECTION.DOWN;
|
||||
}
|
||||
direction = DIRECTION.UP;
|
||||
}
|
||||
} else {
|
||||
if (x - BD < 0 || checkCollision(board, x - CD, y)) {
|
||||
if (checkCollision(board, x - CD, y + CD)) {
|
||||
direction = DIRECTION.UP;
|
||||
}
|
||||
direction = DIRECTION.DOWN;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case RIGHT:
|
||||
if (ran.nextBoolean()) {
|
||||
if (x + BD > BOARD_SIZE || checkCollision(board, x + CD, y)) {
|
||||
if (checkCollision(board, x + CD, y - CD)) {
|
||||
direction = DIRECTION.DOWN;
|
||||
}
|
||||
direction = DIRECTION.UP;
|
||||
}
|
||||
} else {
|
||||
if (x + BD > BOARD_SIZE || checkCollision(board, x + CD, y)) {
|
||||
if (checkCollision(board, x + CD, y + CD)) {
|
||||
direction = DIRECTION.UP;
|
||||
}
|
||||
direction = DIRECTION.DOWN;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case UP:
|
||||
if (ran.nextBoolean()) {
|
||||
if (y - BD < 0 || checkCollision(board, x, y - CD)) {
|
||||
if (checkCollision(board, x + CD, y - CD)) {
|
||||
direction = DIRECTION.LEFT;
|
||||
}
|
||||
direction = DIRECTION.RIGHT;
|
||||
}
|
||||
} else {
|
||||
if (y - BD < 0 || checkCollision(board, x, y - CD)) {
|
||||
if (checkCollision(board, x - CD, y - CD)) {
|
||||
direction = DIRECTION.RIGHT;
|
||||
}
|
||||
direction = DIRECTION.LEFT;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (direction) {
|
||||
case DOWN:
|
||||
if (y + BD > BOARD_SIZE || checkCollision(board, x, y + CD)) {
|
||||
setDirection(DIRECTION.UP);
|
||||
}
|
||||
break;
|
||||
case LEFT:
|
||||
if (x - BD < 0 || checkCollision(board, x - CD, y)) {
|
||||
setDirection(DIRECTION.RIGHT);
|
||||
}
|
||||
break;
|
||||
case RIGHT:
|
||||
if (x + BD > BOARD_SIZE || checkCollision(board, x + CD, y)) {
|
||||
setDirection(DIRECTION.LEFT);
|
||||
}
|
||||
break;
|
||||
case UP:
|
||||
if (y - BD < 0 || checkCollision(board, x, y - CD)) {
|
||||
setDirection(DIRECTION.DOWN);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkCollision(short[][] board, int x, int y) {
|
||||
for (int i = 0; i < width; i++) {
|
||||
for (int j = 0; j < height; j++) {
|
||||
if (board[x + i][y + j] == GameBoard.PLAYER_SPOT_TAKEN + playerNum || board[x + i][y + j] == GameBoard.SPOT_AVAILABLE) {
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package org.libertybikes.game.core;
|
||||
|
||||
public abstract class AIPlayer extends Player {
|
||||
|
||||
public AIPlayer(String id, String name, short playerNum) {
|
||||
super(id, name, playerNum);
|
||||
}
|
||||
|
||||
public void broadCastBoard(short[][] board) {}
|
||||
|
||||
}
|
@ -9,6 +9,8 @@ import java.util.Set;
|
||||
|
||||
import javax.json.bind.annotation.JsonbTransient;
|
||||
|
||||
import org.libertybikes.game.bot.Hal;
|
||||
|
||||
public class GameBoard {
|
||||
|
||||
public static final int BOARD_SIZE = 121;
|
||||
@ -20,6 +22,7 @@ public class GameBoard {
|
||||
public final Set<Obstacle> obstacles = new HashSet<>();
|
||||
public final Set<MovingObstacle> movingObstacles = new HashSet<>();
|
||||
public final Set<Player> players = new HashSet<>();
|
||||
private final Set<AIPlayer> ai = new HashSet<>();
|
||||
private final boolean[] takenPlayerSlots = new boolean[Player.MAX_PLAYERS];
|
||||
|
||||
public GameBoard() {
|
||||
@ -139,4 +142,58 @@ public class GameBoard {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean broadcastToAI() {
|
||||
if (ai.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (AIPlayer p : ai) {
|
||||
try {
|
||||
p.broadCastBoard(board);
|
||||
} catch (Exception e) {
|
||||
// bad ai
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public void addAI() {
|
||||
// Find first open player slot to fill, which determines position
|
||||
short playerNum = -1;
|
||||
for (short i = 0; i < takenPlayerSlots.length; i++) {
|
||||
if (!takenPlayerSlots[i]) {
|
||||
playerNum = i;
|
||||
takenPlayerSlots[i] = true;
|
||||
System.out.println("Player slot " + i + " taken");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize Player
|
||||
AIPlayer p = new Hal("Hal", "Hal", playerNum);
|
||||
|
||||
if (p.x > BOARD_SIZE || p.y > BOARD_SIZE)
|
||||
throw new IllegalArgumentException("Player does not fit on board: " + p);
|
||||
|
||||
for (int i = 0; i < p.width; i++) {
|
||||
for (int j = 0; j < p.height; j++) {
|
||||
board[p.x + i][p.y + j] = (short) (PLAYER_SPOT_TAKEN + playerNum);
|
||||
}
|
||||
}
|
||||
|
||||
players.add(p);
|
||||
ai.add(p);
|
||||
}
|
||||
|
||||
public boolean removeAI(Player p) {
|
||||
takenPlayerSlots[p.getPlayerNum()] = false;
|
||||
|
||||
players.remove(p);
|
||||
return ai.remove(p);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -69,10 +69,23 @@ public class GameRound implements Runnable {
|
||||
public GameRound(String id) {
|
||||
this.id = id;
|
||||
nextRoundId = getRandomId();
|
||||
board.addObstacle(new MovingObstacle(10, 5, 60, 60, 0, -1, 5));
|
||||
board.addObstacle(new MovingObstacle(10, 5, 60, 65, 0, 1));
|
||||
board.addObstacle(new MovingObstacle(5, 5, GameBoard.BOARD_SIZE / 2, GameBoard.BOARD_SIZE / 3, -1, -1, 1));
|
||||
board.addObstacle(new MovingObstacle(5, 5, GameBoard.BOARD_SIZE / 2, GameBoard.BOARD_SIZE / 3 * 2, 1, 1));
|
||||
// board.addObstacle(new MovingObstacle(10, 5, 60, 60, 0, -1, 5));
|
||||
// board.addObstacle(new MovingObstacle(10, 5, 60, 65, 0, 1));
|
||||
board.addObstacle(new MovingObstacle(5, 5, GameBoard.BOARD_SIZE / 2 - 10, GameBoard.BOARD_SIZE / 3, -1, -1));
|
||||
board.addObstacle(new MovingObstacle(5, 5, GameBoard.BOARD_SIZE / 2 + 10, (GameBoard.BOARD_SIZE / 3 * 2) - 5, 1, 1));
|
||||
|
||||
// TL
|
||||
board.addObstacle(new Obstacle(15, 1, GameBoard.BOARD_SIZE / 8, GameBoard.BOARD_SIZE / 8));
|
||||
board.addObstacle(new Obstacle(1, 14, GameBoard.BOARD_SIZE / 8, GameBoard.BOARD_SIZE / 8 + 1));
|
||||
// TR
|
||||
board.addObstacle(new Obstacle(15, 1, ((GameBoard.BOARD_SIZE / 8) * 7) - 14, GameBoard.BOARD_SIZE / 8));
|
||||
board.addObstacle(new Obstacle(1, 14, (GameBoard.BOARD_SIZE / 8) * 7, GameBoard.BOARD_SIZE / 8 + 1));
|
||||
// BL
|
||||
board.addObstacle(new Obstacle(15, 1, GameBoard.BOARD_SIZE / 8, (GameBoard.BOARD_SIZE / 8) * 7));
|
||||
board.addObstacle(new Obstacle(1, 14, GameBoard.BOARD_SIZE / 8, ((GameBoard.BOARD_SIZE / 8) * 7) - 14));
|
||||
// BR
|
||||
board.addObstacle(new Obstacle(15, 1, ((GameBoard.BOARD_SIZE / 8) * 7) - 14, (GameBoard.BOARD_SIZE / 8) * 7));
|
||||
board.addObstacle(new Obstacle(1, 14, (GameBoard.BOARD_SIZE / 8) * 7, ((GameBoard.BOARD_SIZE / 8) * 7) - 14));
|
||||
}
|
||||
|
||||
public GameBoard getBoard() {
|
||||
@ -110,6 +123,20 @@ public class GameRound implements Runnable {
|
||||
broadcastGameBoard();
|
||||
}
|
||||
|
||||
public void addAI() {
|
||||
if (gameState != State.OPEN) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (getPlayers().size() + 1 >= Player.MAX_PLAYERS) {
|
||||
gameState = State.FULL;
|
||||
}
|
||||
|
||||
board.addAI();
|
||||
broadcastPlayerList();
|
||||
broadcastGameBoard();
|
||||
}
|
||||
|
||||
public void addSpectator(Session s) {
|
||||
System.out.println("A spectator has joined.");
|
||||
clients.put(s, new Client(s));
|
||||
@ -205,6 +232,8 @@ public class GameRound implements Runnable {
|
||||
return;
|
||||
}
|
||||
|
||||
board.broadcastToAI();
|
||||
|
||||
boolean boardUpdated = board.moveObjects();
|
||||
|
||||
boolean death = false;
|
||||
@ -212,7 +241,7 @@ public class GameRound implements Runnable {
|
||||
boolean playerStatusChange = false;
|
||||
boolean playersMoved = false;
|
||||
for (Player p : getPlayers()) {
|
||||
if (p.isAlive) {
|
||||
if (p.isAlive()) {
|
||||
if (p.movePlayer(board.board)) {
|
||||
playersMoved = true;
|
||||
} else {
|
||||
@ -266,7 +295,7 @@ public class GameRound implements Runnable {
|
||||
int alivePlayers = 0;
|
||||
Player alive = null;
|
||||
for (Player cur : getPlayers()) {
|
||||
if (cur.isAlive) {
|
||||
if (cur.isAlive()) {
|
||||
alivePlayers++;
|
||||
alive = cur;
|
||||
}
|
||||
@ -285,6 +314,10 @@ public class GameRound implements Runnable {
|
||||
if (gameState != State.OPEN && gameState != State.FULL)
|
||||
return;
|
||||
|
||||
while (gameState == State.OPEN) {
|
||||
addAI();
|
||||
}
|
||||
|
||||
// Issue a countdown to all of the clients
|
||||
gameState = State.STARTING;
|
||||
|
||||
|
@ -9,7 +9,7 @@ import java.util.Queue;
|
||||
import javax.json.bind.annotation.JsonbPropertyOrder;
|
||||
import javax.json.bind.annotation.JsonbTransient;
|
||||
|
||||
@JsonbPropertyOrder({ "id", "name", "color", "status", "isAlive", "x", "y", "width", "height", "oldX", "oldY", "trailPosX", "trailPosY", "trailPosX2", "trailPosY2" })
|
||||
@JsonbPropertyOrder({ "id", "name", "color", "status", "alive", "x", "y", "width", "height", "oldX", "oldY", "trailPosX", "trailPosY", "trailPosX2", "trailPosY2" })
|
||||
public class Player {
|
||||
|
||||
public static enum STATUS {
|
||||
@ -51,12 +51,12 @@ public class Player {
|
||||
public int oldY, y;
|
||||
public final int width = 3;
|
||||
public final int height = 3;
|
||||
public boolean isAlive = true;
|
||||
private boolean isAlive = true;
|
||||
public int trailPosX, trailPosY, trailPosX2, trailPosY2;
|
||||
private STATUS playerStatus = STATUS.Connected;
|
||||
|
||||
private final short playerNum;
|
||||
private DIRECTION direction;
|
||||
protected final short playerNum;
|
||||
protected DIRECTION direction;
|
||||
private DIRECTION lastDirection = null;
|
||||
private DIRECTION desiredNextDirection = null;
|
||||
|
||||
@ -110,7 +110,7 @@ public class Player {
|
||||
*
|
||||
* @return True if the player is still alive after moving forward one space. False otherwise.
|
||||
*/
|
||||
public boolean movePlayer(short[][] s) {
|
||||
public final boolean movePlayer(short[][] s) {
|
||||
|
||||
oldX = x;
|
||||
oldY = y;
|
||||
@ -126,28 +126,28 @@ public class Player {
|
||||
case UP:
|
||||
if (y - 1 < 0 || checkCollision(s, x, y - 1)) {
|
||||
setStatus(STATUS.Dead);
|
||||
return isAlive;
|
||||
return isAlive();
|
||||
}
|
||||
moveUp(s);
|
||||
break;
|
||||
case DOWN:
|
||||
if (y + height + 1 >= GameBoard.BOARD_SIZE || checkCollision(s, x, y + 1)) {
|
||||
setStatus(STATUS.Dead);
|
||||
return isAlive;
|
||||
return isAlive();
|
||||
}
|
||||
moveDown(s);
|
||||
break;
|
||||
case RIGHT:
|
||||
if (x + width + 1 >= GameBoard.BOARD_SIZE || checkCollision(s, x + 1, y)) {
|
||||
setStatus(STATUS.Dead);
|
||||
return isAlive;
|
||||
return isAlive();
|
||||
}
|
||||
moveRight(s);
|
||||
break;
|
||||
case LEFT:
|
||||
if (x - 1 < 0 || checkCollision(s, x - 1, y)) {
|
||||
setStatus(STATUS.Dead);
|
||||
return isAlive;
|
||||
return isAlive();
|
||||
}
|
||||
moveLeft(s);
|
||||
break;
|
||||
@ -174,7 +174,7 @@ public class Player {
|
||||
|
||||
lastDirection = direction;
|
||||
|
||||
return isAlive;
|
||||
return isAlive();
|
||||
}
|
||||
|
||||
private boolean checkCollision(short[][] board, int x, int y) {
|
||||
@ -226,7 +226,7 @@ public class Player {
|
||||
setStatus(STATUS.Disconnected);
|
||||
}
|
||||
|
||||
public void setStatus(STATUS newState) {
|
||||
public final void setStatus(STATUS newState) {
|
||||
if (newState == STATUS.Dead || newState == STATUS.Disconnected)
|
||||
this.isAlive = false;
|
||||
if (newState == STATUS.Dead && this.playerStatus == STATUS.Winner)
|
||||
@ -242,4 +242,8 @@ public class Player {
|
||||
public int getPlayerNum() {
|
||||
return this.playerNum;
|
||||
}
|
||||
|
||||
public boolean isAlive() {
|
||||
return isAlive;
|
||||
}
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ public class JsonDataTest {
|
||||
assertEquals("{\"movingObstacles\":[],\"obstacles\":[" + obstacleJson + "],\"players\":[]}", jsonb.toJson(board));
|
||||
|
||||
// @JsonbPropertyOrder({ "id", "name", "color", "status", "isAlive", "x", "y", "width", "height", "oldX", "oldY", "trailPosX", "trailPosY", "trailPosX2", "trailPosY2" })
|
||||
String bobJson = "{\"id\":\"1234\",\"name\":\"Bob\",\"color\":\"#DF740C\",\"status\":\"Connected\",\"isAlive\":true,\"x\":9,\"y\":9,\"width\":3,\"height\":3,\"oldX\":9,\"oldY\":9,\"trailPosX\":10,\"trailPosY\":10,\"trailPosX2\":10,\"trailPosY2\":10}";
|
||||
String bobJson = "{\"id\":\"1234\",\"name\":\"Bob\",\"color\":\"#DF740C\",\"status\":\"Connected\",\"alive\":true,\"x\":9,\"y\":9,\"width\":3,\"height\":3,\"oldX\":9,\"oldY\":9,\"trailPosX\":10,\"trailPosY\":10,\"trailPosX2\":10,\"trailPosY2\":10}";
|
||||
board.addPlayer("1234", "Bob");
|
||||
assertEquals("{\"movingObstacles\":[],\"obstacles\":[" + obstacleJson + "],\"players\":[" + bobJson + "]}",
|
||||
jsonb.toJson(board));
|
||||
@ -98,7 +98,7 @@ public class JsonDataTest {
|
||||
@Test
|
||||
public void testPlayerList() {
|
||||
PlayerList list = new OutboundMessage.PlayerList(Collections.singleton(new Player("123", "Bob", (short) 1)));
|
||||
assertEquals("{\"playerlist\":[{\"id\":\"123\",\"name\":\"Bob\",\"color\":\"#FF0000\",\"status\":\"Connected\",\"isAlive\":true,\"x\":9,\"y\":110,\"width\":3,\"height\":3,\"oldX\":9,\"oldY\":110,\"trailPosX\":10,\"trailPosY\":111,\"trailPosX2\":10,\"trailPosY2\":111}]}",
|
||||
assertEquals("{\"playerlist\":[{\"id\":\"123\",\"name\":\"Bob\",\"color\":\"#FF0000\",\"status\":\"Connected\",\"alive\":true,\"x\":9,\"y\":110,\"width\":3,\"height\":3,\"oldX\":9,\"oldY\":110,\"trailPosX\":10,\"trailPosY\":111,\"trailPosX2\":10,\"trailPosY2\":111}]}",
|
||||
jsonb.toJson(list));
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user