package game;
import interfaces.GameField;
import main.Main;
import model.Snake.Snake;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import websocket.SnakeUpdatesManager;
import java.util.ArrayList;
import java.util.List;
/**
* Created by egor on 12.04.15.
*/
public class GameFieldImpl implements GameField {
public static final Logger LOG = LogManager.getLogger(GameService.class);
public static final int FPS = Main.mechanicsConfig.getInt("FPS");
public static final int SECOND = 1_000_000_000;
public static final int STEP_TIME = SECOND / FPS;
public static final int width = Main.mechanicsConfig.getInt("gameField.width");
public static final int height = Main.mechanicsConfig.getInt("gameField.height");
public static final int LAST_DEATH_DELAY = 1700;
private final GameService gameService;
private final SnakeUpdatesManager updatesManager;
private boolean playing;
private int numPlayers, dead;
private List<Snake> snakes;
private BonusManager bonusManager;
private SnakeCollisionChecker snakeCollisionChecker;
private Room room;
private boolean lastDeath = false;
private long lastDeathTimeMillis;
public GameFieldImpl(Room room, GameService gameService) {
updatesManager = new SnakeUpdatesManager(room);
this.room = room;
this.gameService = gameService;
playing = false;
this.numPlayers = room.getPlayerCount();
snakes = new ArrayList<>();
int mindim = Math.min(width, height);
for (int i = 0; i < numPlayers; i++) {
double angle = i * 2 * Math.PI / numPlayers;
double x = width / 2 + mindim * 0.25 * Math.cos(angle);
double y = height / 2 + mindim * 0.25 * Math.sin(angle);
Snake snake = new Snake(x, y, angle + Math.PI / 2, updatesManager, i);
snakes.add(snake);
}
dead = 0;
bonusManager = new BonusManager(snakes, room);
snakeCollisionChecker = new SnakeCollisionChecker(snakes, this);
}
@Override
public void doLeftDown(int sender) {
snakes.get(sender).startTurning(Snake.turningState.TURNING_LEFT);
}
@Override
public void doLeftUp(int sender) {
snakes.get(sender).stopTurning(Snake.turningState.TURNING_LEFT);
}
@Override
public void doRightDown(int sender) {
snakes.get(sender).startTurning(Snake.turningState.TURNING_RIGHT);
}
@Override
public void doRightUp(int sender) {
snakes.get(sender).stopTurning(Snake.turningState.TURNING_RIGHT);
}
@Override
public void play() {
playing = true;
new Thread(() -> {
try {
Thread.sleep(Main.mechanicsConfig.getInt("gameStartCountdown") * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
run();
}).start();
}
@Override
public void pause() {
playing = false;
}
private void teleportOrKill(Snake snake, double x, double y) {
if (snake.canTravThroughWalls()) {
snake.teleport(x, y);
} else {
killSnake(snake);
}
}
public void killSnake(Snake snake) {
snake.kill();
int lastKilled = snakes.indexOf(snake);
room.onPlayerDeath(lastKilled, dead);
dead++;
}
public void killAll() {
snakes.stream().filter(Snake::isAlive).forEach(this::killSnake);
}
@Override
public SnakeUpdatesManager getUpdatesManager() {
return updatesManager;
}
private void step(double frames) {
snakes.stream().filter(Snake::isAlive).forEach(snake -> {
snake.step(frames);
double x = snake.getX();
double y = snake.getY();
if (x > width) {
teleportOrKill(snake, 0, snake.getY());
} else if (x < 0) {
teleportOrKill(snake, width, snake.getY());
}
if (y > height) {
teleportOrKill(snake, snake.getX(), 0);
} else if (y < 0) {
teleportOrKill(snake, snake.getX(), height);
}
});
snakeCollisionChecker.timeStep();
bonusManager.timeStep();
if (dead >= numPlayers - 1 && !lastDeath && numPlayers != 1 || numPlayers == 1 && dead == 1) {
LOG.debug("Round over");
lastDeath = true;
lastDeathTimeMillis = System.currentTimeMillis();
}
if (lastDeath) {
if ((System.currentTimeMillis() - lastDeathTimeMillis) >= LAST_DEATH_DELAY) {
killAll();
playing = false;
room.startRound();
}
}
}
public void run() {
Runnable gameLoop = () -> {
long now, dt = 0;
long last = System.nanoTime();
long stepTime = STEP_TIME;
while (playing) {
now = System.nanoTime();
dt = Math.min(SECOND, (now - last));
step((double)dt / stepTime);
long sleepNano = (stepTime - (System.nanoTime() - now) );
if (sleepNano > 0) {
try {
Thread.sleep(sleepNano / 1000_000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
last = now;
}
};
Thread loopThread = new Thread(gameLoop);
loopThread.start();
}
}