package gameserver;
import common.PhysicsWorld;
import common.State;
import common.packet.BodyState;
import common.packet.ClientIn;
import common.packet.ClientInfo;
import common.packet.ClientOut;
import common.packet.GameState;
import common.packet.WorldState;
import common.packet.Packet;
import common.packet.PacketWithCode;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.jbox2d.common.Vec2;
import org.jbox2d.dynamics.Body;
/**
* Contains everything related to the game itself, specially for the match. It
* owns the physics world, the client list, game state and configuration, etc.
* Its like a singleton wannabe.
*
* @author xissburg
*/
public class Game
{
private final Map<String, ClientThread> clients;
private State state;
private int lTeamPoints, rTeamPoints;
private int time;//match time in milliseconds
private final PhysicsThreadS physicsThread;
private final PingRunnable pingRunnable;
private Thread updateThread;
public Game()
{
clients = new ConcurrentHashMap<String, ClientThread>();
state = State.WAITING;
lTeamPoints = rTeamPoints = 0;
time = 0;
physicsThread = new PhysicsThreadS(this);
pingRunnable = new PingRunnable();
new Thread(pingRunnable).start();//Bad practice: start thread in constructor
}
/**
* Returns a GameState packet containing data about the current state of the game
*/
public GameState getGameState()
{
Collection<ClientThread> clientsCopy = getClients();
Collection<ClientInfo> cInfo = new ArrayList<ClientInfo>(clientsCopy.size());
for(ClientThread ct: clientsCopy)
cInfo.add(ct.getClient().getClientInfo());
GameState gs = new GameState();
gs.setClients(cInfo);
gs.setState(state);
gs.setTime(time);
gs.setlTeamPoints(lTeamPoints);
gs.setrTeamPoints(rTeamPoints);
return gs;
}
public State getState() {
return state;
}
/**
* Effectively adds a new client to the game. It also notifies all other clients
* in the clients map about the newcomer.
*
* @param ct The client to be added.
*/
public void addClientThread(ClientThread ct)
{
synchronized(clients)
{
if (clients.isEmpty()) {
//There aren't any others, then this one is the owner
ct.getClient().getClientInfo().setOwner(true);
} else {
//Notify all other clients
ClientIn cIn = new ClientIn(ct.getClient().getClientInfo());
Collection<ClientThread> clientCol = getClients();
for (ClientThread c : clientCol) {
try {
c.getClient().write(cIn);
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
//make sure the new client is a spectator
ct.getClient().getClientInfo().setType(ClientInfo.Type.SPECTATOR);
clients.put(ct.getClient().getClientInfo().getName(), ct);
}
}
public void removeClientThread(String clientName)
{
physicsThread.getPhysicsWorld().removeBody(clientName);
ClientThread client = clients.get(clientName);
clients.remove(clientName);
//If there aren't anymore players stop the game if running.
if(clients.isEmpty())
stop();
else
{
//Notify all clients
ClientOut cOut = new ClientOut(client.getClient().getClientInfo().getName());
Collection<ClientThread> clients_ = getClients();
for(ClientThread ct: clients_)
try {
ct.getClient().write(cOut);
} catch (IOException ex) {}
}
}
public void setClientInfo(ClientInfo ci)
{
String name = ci.getName();
ClientInfo clientInfo = clients.get(ci.getName()).getClient().getClientInfo();
boolean changed = false;
//if it was a player and continues to be a player, just update its position
if(clientInfo.getType() == ClientInfo.Type.PLAYER &&
ci.getType() == ClientInfo.Type.PLAYER)
{
changed = true;
float x = ci.getStartX();
float y = ci.getStartY();
clientInfo.setStartXY(x, y);
clientInfo.setTeam(clientInfo.getStartX() < 0f?
ClientInfo.Team.LEFT: ClientInfo.Team.RIGHT);
physicsThread.getPhysicsWorld().setButtonState(name, x, y, ci.getTeam());
}
//if it was a player and now it is a spectator, remove from physicsWorld
//and graphicsWorld
else if(clientInfo.getType() == ClientInfo.Type.PLAYER &&
ci.getType() == ClientInfo.Type.SPECTATOR)
{
changed = true;
clientInfo.setType(ClientInfo.Type.SPECTATOR);
physicsThread.getPhysicsWorld().removeBody(name);
}
//if it was a spectator and now it is a player, add it to the physicsWorld
//and change its state in the physicsWorld and update its starting position
else if(clientInfo.getType() == ClientInfo.Type.SPECTATOR &&
ci.getType() == ClientInfo.Type.PLAYER)
{
changed = true;
clientInfo.setType(ClientInfo.Type.PLAYER);
float x = ci.getStartX(), y = ci.getStartY();
clientInfo.setStartXY(x, y);
clientInfo.setTeam(clientInfo.getStartX() < 0f?
ClientInfo.Team.LEFT: ClientInfo.Team.RIGHT);
physicsThread.getPhysicsWorld().addButton(name, x, y, clientInfo.getTeam());
}
//else if it was a spectator and wants to be a spectator there's nothing
//to do.
//Notify all other clients about any changes
if(changed)
{
Collection<ClientThread> clients_ = getClients();
ci.set(clientInfo);
for(ClientThread ct: clients_)
{
try {
ct.getClient().write(ci);
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
}
/**
* Returns an array (independent of the client map for thread safety) containing
* the ClientThread's instances of all clients currently connected. ClientThread
* is thread safe.
*
* @return An array of ClientThreads.
*/
public Collection<ClientThread> getClients() {
return new java.util.ArrayList<ClientThread>(clients.values());
}
/**
* Starts physics and update thread. Asynchronous call, returns immediately.
*/
public void start()
{
Collection<ClientThread> clientsCopy = getClients();
state = State.RUNNING;
new Thread(physicsThread, "Physics Thread").start();
updateThread = new Thread(new UpdateRunnable(), "Update Thread");
updateThread.start();
//Notify all clients about the game start
Packet p = new PacketWithCode(Packet.Code.START_GAME);
for (ClientThread ct : clientsCopy) {
try {
ct.getClient().write(p);
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
public void reset()
{
PhysicsWorld world = physicsThread.getPhysicsWorld();
world.resetBall();
Collection<ClientThread> clients_ = getClients();
for(ClientThread ct: clients_)
{
ClientInfo ci = ct.getClient().getClientInfo();
world.setButtonState(ci.getName(), ci.getStartX(), ci.getStartY(), ci.getTeam());
Body b = world.getBody(ci.getName());
b.setLinearVelocity(new Vec2(0,0));
b.setAngularVelocity(0);
}
}
public void stop()
{
state = State.WAITING;
physicsThread.stop();
reset();
if(updateThread != null)
updateThread.interrupt();
}
/**
* Update thread code. It is responsible for sending game updates during the
* match to all clients.
* Note: to stop a thread running this runnable, call interrupt() in the
* thread object.
*/
private class UpdateRunnable implements Runnable
{
private volatile boolean running;
private int stepsPerSecond;
public UpdateRunnable() {
running = true;
stepsPerSecond = 20;
}
public void setStepsPerSecond(int stepsPerSecond) {
this.stepsPerSecond = stepsPerSecond;
}
public void run()
{
running = true;
while(running)
{
Collection<BodyState> out = null;
try {
out = physicsThread.getPhysicsOutput(true);
} catch (InterruptedException ex) {
//If interrupted, stop this thread
running = false;
break;
}
WorldState worldState = new WorldState(out);
Collection<ClientThread> clients = getClients();
for(ClientThread ct: clients)
{
try {
ct.getClient().write(worldState);
} catch (IOException ex) {
ex.printStackTrace();
}
}
try {
Thread.sleep(1000/stepsPerSecond);
} catch (InterruptedException ex) {}
}
}
}
private class PingRunnable implements Runnable
{
private volatile boolean running;
private long delay;
public PingRunnable() {
this(3000);
}
public PingRunnable(long delay) {
running = true;
this.delay = delay;
}
public void setDelay(long delay) {
this.delay = delay;
}
public void stop()
{
running = false;
}
public void run()
{
running = true;
while(running)
{
Collection<ClientThread> clients = getClients();
for(ClientThread ct: clients)
ct.refreshLatency();
try {
Thread.sleep(delay);
} catch (InterruptedException ex) {}
}
}
}
}
|