package model.Snake;
import main.Main;
import utils.MathUtils;
import utils.RandomUtils;
import websocket.SnakeUpdatesManager;
import java.util.ArrayList;
import java.util.List;
/**
* Created by egor on 12.04.15.
*/
public class Snake {
public static final int defaultSpeed = Main.mechanicsConfig.getInt("snake.defaultSpeed");
public static final int defaultTurnRadius = Main.mechanicsConfig.getInt("snake.defaultTurnRadius");
public static final int FPS = Main.mechanicsConfig.getInt("FPS");
public static final int holeLength = Main.mechanicsConfig.getInt("snake.holeLength");
public static final int minPartLength = Main.mechanicsConfig.getInt("snake.minPartLength");
public static final int maxPartLength = Main.mechanicsConfig.getInt("snake.maxPartLength");
private double angle;
private int id;
private double angleV;
private double v, vx, vy;
private double cosV, sinV;
private int partStopper, holeStopper;
private double x, y;
private double turnRadius, arcCenterX, arcCenterY;
private List<SnakePartLine> snakeLines;
private List<SnakePartArc> snakeArcs;
private double travSinceLastHole = 0;
private boolean drawing = true, alive = true;
private turningState turning = turningState.NOT_TURNING;
private int radius = Main.mechanicsConfig.getInt("snake.defaultWidth") / 2;
private int linesSent = 0, arcsSent = 0;
private SnakeUpdatesManager updatesManager;
private boolean bigHole = false;
private boolean travThroughWalls = false;
public void setArcsSent(int arcsSent) {
this.arcsSent = arcsSent;
}
public void setLinesSent(int linesSent) {
this.linesSent = linesSent;
}
public enum turningState {
TURNING_LEFT,
TURNING_RIGHT,
NOT_TURNING
}
public Snake(double x, double y, double angle, SnakeUpdatesManager updatesManager, int id) {
snakeArcs = new ArrayList<>();
snakeLines = new ArrayList<>();
this.id = id;
this.updatesManager = updatesManager;
v = (double) defaultSpeed / FPS;
angleV = v / defaultTurnRadius;
this.angle = angle;
vx = v * Math.cos(angle);
vy = v * Math.sin(angle);
turnRadius = defaultTurnRadius;
partStopper = RandomUtils.randInt(minPartLength, maxPartLength);
holeStopper = partStopper + holeLength;
this.x = x;
this.y = y;
cosV = Math.cos(angleV);
sinV = Math.sin(angleV);
doLine();
}
public Snake(double x, double y, double angle, SnakeUpdatesManager updatesManager, int id, int partStopper) {
this(x, y, angle, updatesManager, id);
this.partStopper = partStopper;
}
public void kill() {
alive = false;
sendUpdates();
}
private boolean reversed = false;
private boolean sharp = false;
public synchronized void startTurning(turningState where) {
if (reversed && where != turningState.NOT_TURNING)
where = (where == turningState.TURNING_LEFT) ? turningState.TURNING_RIGHT : turningState.TURNING_LEFT;
if(sharp) {
double oldVx = vx;
if((where == turningState.TURNING_LEFT)) {
vx = vy;
vy = -oldVx;
angle = MathUtils.normAngle(angle - Math.PI/2);
} else {
vx = -vy;
vy = oldVx;
angle = MathUtils.normAngle(angle + Math.PI/2);
}
doLine();
} else {
if ((angleV > 0) != (where == turningState.TURNING_RIGHT)) {
angleV = -angleV;
sinV = -sinV;
}
turning = where;
doArc();
}
}
public synchronized void stopTurning(turningState where) {
if (reversed && where != turningState.NOT_TURNING)
where = (where == turningState.TURNING_LEFT) ? turningState.TURNING_RIGHT : turningState.TURNING_LEFT;
if (turning == where) {
turning = turningState.NOT_TURNING;
angle = MathUtils.normAngle(angle);
vx = v * Math.cos(angle);
vy = v * Math.sin(angle);
doLine();
}
}
private void doArc() {
arcsSent = Math.min(arcsSent, snakeArcs.size() - 1);
linesSent = Math.min(linesSent, snakeLines.size() - 1);
sendUpdates();
boolean clockwise;
double arcStartAngle;
if (turning == turningState.TURNING_LEFT) {
arcStartAngle = MathUtils.normAngle(angle + Math.PI / 2);
arcCenterX = x + turnRadius * Math.sin(angle);
arcCenterY = y - turnRadius * Math.cos(angle);
clockwise = true;
} else {
arcStartAngle = MathUtils.normAngle(angle - Math.PI / 2);
arcCenterX = x - turnRadius * Math.sin(angle);
arcCenterY = y + turnRadius * Math.cos(angle);
clockwise = false;
}
if (!drawing) return;
SnakePartArc newArc = new SnakePartArc(arcCenterX, arcCenterY
, turnRadius, arcStartAngle, radius, snakeArcs.size(), clockwise, angleV);
snakeArcs.add(newArc);
}
public void sendUpdates() {
updatesManager.broadcast(this);
}
private void doLine() {
linesSent = Math.min(linesSent, snakeLines.size() - 1);
arcsSent = Math.min(arcsSent, snakeArcs.size() - 1);
sendUpdates();
if (!drawing) return;
SnakePartLine newLine = new SnakePartLine(x, y, vx, vy, radius, snakeLines.size());
snakeLines.add(newLine);
}
public void multiplyRadiusBy(double koef) {
radius *= koef;
doNewPart();
}
public synchronized void step(double frames) {
makeHoles();
if (turning == turningState.NOT_TURNING) {
x += vx * frames;
y += vy * frames;
if (drawing) lastLine().updateHead(x, y, v * frames);
} else {
angle += angleV * frames;
if (turning == turningState.TURNING_LEFT) {
y = arcCenterY + turnRadius * Math.cos(angle);
x = arcCenterX - turnRadius * Math.sin(angle);
} else {
y = arcCenterY - turnRadius * Math.cos(angle);
x = arcCenterX + turnRadius * Math.sin(angle);
}
if (drawing) lastArc().updateHead(angleV * frames);
}
}
private void makeHoles() {
travSinceLastHole += v;
if (travSinceLastHole > partStopper) {
if (travSinceLastHole >= holeStopper) {
travSinceLastHole = 0;
partStopper = RandomUtils.randInt(minPartLength, maxPartLength);
holeStopper = partStopper + holeLength;
drawing = true;
doNewPart();
} else drawing = false;
}
}
private SnakePartLine lastLine() {
return snakeLines.get(snakeLines.size() - 1);
}
private SnakePartArc lastArc() {
return snakeArcs.get(snakeArcs.size() - 1);
}
public void multiplyTurnRadiusBy(double koef) {
turnRadius *= koef;
angleV /= koef;
cosV = Math.cos(angleV);
sinV = Math.sin(angleV);
if (turning != turningState.NOT_TURNING) {
doArc();
} else
sendUpdates();
}
public void reverse() {
if (reversed) {
startTurning(turning);
reversed = !reversed;
} else {
reversed = !reversed;
startTurning(turning);
}
}
public void multiplySpeedBy(double koef) {
v *= koef;
vx *= koef;
vy *= koef;
angleV *= koef;
cosV = Math.cos(angleV);
sinV = Math.sin(angleV);
//turnRadius *= koef;
if (turning != turningState.NOT_TURNING) {
doArc();
} else sendUpdates();
}
private void doNewPart() {
if (turning == turningState.NOT_TURNING) {
doLine();
} else {
doArc();
}
}
public void teleport(double newX, double newY) {
x = newX;
y = newY;
doNewPart();
}
public boolean isAlive() {
return alive;
}
public double getX() {
return x;
}
public double getY() {
return y;
}
public int getRadius() {
return radius;
}
public void eraseSelf() {
snakeArcs.clear();
snakeLines.clear();
doNewPart();
// sendUpdates();
}
public void setBigHole(boolean bigHole) {
this.bigHole = bigHole;
}
public boolean isBigHole() {
return bigHole;
}
public void setTravThroughWalls(boolean travThroughWalls) {
this.travThroughWalls = travThroughWalls;
}
public boolean canTravThroughWalls() {
return travThroughWalls;
}
public List<SnakePartLine> getSnakeLines() {
return snakeLines;
}
public List<SnakePartArc> getSnakeArcs() {
return snakeArcs;
}
public boolean isTurning() {
return turning != turningState.NOT_TURNING;
}
public int getArcsSent() {
return arcsSent;
}
public int getLinesSent() {
return linesSent;
}
public int getId() {
return id;
}
public double getAngle() {
return angle;
}
public double getAngleSpeed() {
return angleV;
}
public double getSpeed() {
return v;
}
public double getTravSinceLastHole() {
return travSinceLastHole;
}
public double getTurnRadius() {
return turnRadius;
}
public int getPartStopper() {
return partStopper;
}
public boolean isReversed() {
return reversed;
}
public void setReversed(boolean reversed) {
this.reversed = reversed;
}
}