package pong.server.model;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.LinkedList;
import java.util.Queue;
import pong.common.GlobalSettings;
import pong.common.Position;
import pong.server.ServerSettings;
/**
* The server model class. It creates the server socket, wait for clients to connect and manage
* rounds.
*
* @author Lorenzo Gatto
*
*/
public class ServerModel extends Thread {
private static final double NANOSECONDS_PER_SECOND = 1000000000;
private final CollisionDetectorInterface collisionDetector;
private final Player[] player;
private final Ball ball;
private final ServerModelListener listener;
private final int portNumber;
private final int roundNumber;
private final Queue<ServerProtocolThread> disconnected;
private ServerProtocolThread[] connectionThread;
private int roundPlayed;
private int[] roundWon;
private long lastFrameTime;
private Boolean waitingPlayer;
private Double currentBallSpeed;
/**
* Initializer.
*
* @param portNumber
* server's port number
* @param roundNumber
* total number of rounds for a match
* @param listener
* the server model listener
*/
public ServerModel(final int portNumber, final int roundNumber,
final ServerModelListener listener) {
super();
this.collisionDetector = new CollisionDetector();
this.connectionThread = new ServerProtocolThread[2];
this.portNumber = portNumber;
this.roundNumber = roundNumber;
this.roundWon = new int[2];
this.player = new Player[2];
this.ball = new Ball();
for (int i = 0; i < 2; i++) {
this.player[i] = new Player();
}
this.listener = listener;
this.listener.drawLogPanel();
this.disconnected = new LinkedList<>();
this.waitingPlayer = false;
}
/**
* Creates the socket and starts listening to it. When both players are connected the match
* starts.
*/
@Override
public synchronized void run() {
this.listener.printMessage("Server is creating socket on port " + portNumber);
// Create the socket
ServerSocket serverSocket;
try {
serverSocket = new ServerSocket(portNumber);
} catch (IllegalArgumentException | IOException e) {
listener.showErrorDialog("Error creating socket!", "Error!");
this.listener.drawOptionsPanel();
return;
}
this.listener.printMessage("Socket created successfully...");
try {
while (true) {
while (checkDisconnections()) {
if (!waitingPlayer) {
new SocketWaiter(serverSocket, this).start();
waitingPlayer = true;
}
wait(ServerSettings.SERVER_SLEEP_TIME);
}
initializeForMatchPlaying();
// PLAYING: here the match is played
// avviso che inizia il gioco
for (int i = 0; i < 2; i++) {
this.connectionThread[i].matchBeginsEvent(roundNumber);
}
while (true) {
wait(ServerSettings.ROUND_BEGIN_PAUSE);
if (checkDisconnections()) {
break;
}
if (roundPlayed == roundNumber) { // end of the match
connectionThread[0].matchEndsEvent();
connectionThread[1].matchEndsEvent();
checkMatchWinner();
break;
} else {
playRound();
if (checkDisconnections()) {
break;
}
}
}
wait(GlobalSettings.MATCH_END_PAUSE);
}
} catch (InterruptedException e) {
for (int i = 0; i < 2; i++) {
if (connectionThread[i] != null) {
connectionThread[i].interrupt();
}
}
listener.drawOptionsPanel();
try {
serverSocket.close();
} catch (IOException e1) {
return;
}
return;
}
}
/**
* A player connected to the server.
*
* @param socket
* socket
*/
public synchronized void playerConnectedEvent(final Socket socket) {
waitingPlayer = false;
for (int i = 0; i < 2; i++) {
if (connectionThread[i] == null) {
connectionThread[i] = new ServerProtocolThread(socket, player[i], this, i);
connectionThread[i].start();
listener.printMessage("Player " + (i + 1) + " connected");
break;
}
}
}
/**
* Return objects positions.
*
* @return an array with 3 Position objects: player 1, player 2 and ball positions
*/
public Position[] getObjectsPositions() {
Position[] ret = new Position[3];
ret[0] = player[0].getPosition();
ret[1] = player[1].getPosition();
ret[2] = ball.getPosition();
return ret;
}
/**
* A player has disconnected.
*
* @param disconnectedThread
* the thread that managed the connection with the client.
*/
public void playerDisconnectedEvent(final ServerProtocolThread disconnectedThread) {
synchronized (disconnected) {
disconnected.add(disconnectedThread);
}
}
/**
* Check for disconnections and, if any, prints a disconnection message and warn the other
* player.
*
* @return false if two players are connected, true otherwise
*/
/*
* this method takes into consideration the fact that the lock on disconnected should be
* acquired after the lock on ServerProtocolThread objects.
*/
private Boolean checkDisconnections() {
while (true) {
ServerProtocolThread disc = null;
synchronized (disconnected) {
if (disconnected.isEmpty()) {
break;
} else {
disc = disconnected.remove();
}
}
for (int i = 0; i < 2; i++) {
if (disc == connectionThread[i]) {
listener.printMessage("Player " + (i + 1) + " disconnected");
connectionThread[i] = null;
if (connectionThread[1 - i] != null) {
connectionThread[1 - i].otherDisconnectedWhilePlayingEvent();
}
}
}
}
for (int i = 0; i < 2; i++) {
if (connectionThread[i] == null) {
return true;
}
}
return false;
}
/**
* Plays one round of the match. Call this function only if both players are connected.
*
* @throws InterruptedException
* in case of thread interruption
*/
private void playRound() throws InterruptedException {
listener.printMessage("Starting round...");
initializeForRoundPlaying();
for (int i = 0; i < 2; i++) {
connectionThread[i].roundBeginsEvent();
}
while (true) {
if (checkDisconnections()) {
break;
}
int roundWinner = updatePositions();
if (roundWinner != 0) {
roundWinner--;
connectionThread[roundWinner].roundEndEvent(true);
connectionThread[1 - roundWinner].roundEndEvent(false);
roundWon[roundWinner]++;
listener.printMessage("Player " + (roundWinner + 1) + " won the round");
roundWinner++;
break;
}
wait(ServerSettings.SERVER_SLEEP_TIME);
}
roundPlayed++;
}
private void initializeForMatchPlaying() {
roundPlayed = 0;
roundWon[0] = 0;
roundWon[1] = 0;
}
private void initializeForRoundPlaying() {
player[0].initializeForRoundPlaying(0);
player[1].initializeForRoundPlaying(1);
ball.initializeForRoundPlaying();
currentBallSpeed = (double) GlobalSettings.INITIAL_BALL_SPEED;
lastFrameTime = System.nanoTime();
}
/**
* Updates positions of objects and check for round end
*
* @return the round winner index (1 or 2). 0 if none still won
*/
private int updatePositions() {
final long newFrameTime = System.nanoTime();
final long elapsedTimeNanoSeconds = newFrameTime - lastFrameTime;
currentBallSpeed += (double) GlobalSettings.BALL_ACCELERATION * elapsedTimeNanoSeconds
/ NANOSECONDS_PER_SECOND;
currentBallSpeed = Math.min(currentBallSpeed, GlobalSettings.MAX_BALL_SPEED);
lastFrameTime = newFrameTime;
final Position[] positions = getObjectsPositions();
// move rackets
for (int i = 0; i < 2; i++) {
float y = positions[i].getY();
if (player[i].isGoingUp()) {
y -= GlobalSettings.RACKETS_SPEED * elapsedTimeNanoSeconds / NANOSECONDS_PER_SECOND;
}
if (player[i].isGoingDown()) {
y += GlobalSettings.RACKETS_SPEED * elapsedTimeNanoSeconds / NANOSECONDS_PER_SECOND;
}
if (y < 0) {
y = 0;
} else if (y > GlobalSettings.FIELD_HEIGHT - GlobalSettings.RACKET_HEIGHT) {
y = GlobalSettings.FIELD_HEIGHT - GlobalSettings.RACKET_HEIGHT;
}
player[i].getPosition().setY(y);
}
// move ball
float x = positions[2].getX();
final double deltaX = Math.cos(Math.atan(ball.getAngularCoefficient())) * currentBallSpeed
* elapsedTimeNanoSeconds / NANOSECONDS_PER_SECOND;
if (ball.getDirection() == Ball.Direction.LEFT_TO_RIGHT) {
x += deltaX;
} else {
x -= deltaX;
}
float y = positions[2].getY();
final double deltaY = deltaX * ball.getAngularCoefficient();
y += deltaY;
ball.setPosition(new Position(x, y));
if (ball.getPosition().getX() < 0 - GlobalSettings.BALL_DIAMETER) {
return 2;
}
if (ball.getPosition().getX() > GlobalSettings.FIELD_WIDTH) {
return 1;
}
this.collisionDetector.handleCollisions(player, ball);
return 0;
}
/**
* Checks who won the match and prints it to screen
*/
private void checkMatchWinner() {
if (roundWon[0] > roundWon[1]) {
this.listener.printMessage("Player 1 won the match");
} else if (roundWon[0] == roundWon[1]) {
this.listener.printMessage("Match drawn");
} else {
this.listener.printMessage("Player 2 won the match");
}
}
}