/*
* This file is part of gwap, an open platform for games with a purpose
*
* Copyright (C) 2013
* Project play4science
* Lehr- und Forschungseinheit für Programmier- und Modellierungssprachen
* Ludwig-Maximilians-Universität München
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package gwap.game;
import gwap.model.GameRound;
import gwap.model.Person;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.EventListener;
import java.util.List;
import org.ajax4jsf.event.PushEventListener;
import org.jboss.seam.annotations.End;
import org.jboss.seam.annotations.Logger;
import org.jboss.seam.log.Log;
public abstract class Player<P extends Player<P>> implements Serializable {
@Logger private Log log;
//Time to wait before user is considered disconnected
final long TIMEOUT=10000;
//Time to wait for a second player before adding an Ai
final long WAIT_TIMEOUT=6000;
//Set to true to allow player to be matched with an Ai
private boolean allowAi=false;
/* This method should be implemented and called from the view using
* a PUSH object
* The returned value is used to redirect the user
* See testGame for further examples
*/
public abstract String notified();
private SharedGame sharedGame;
private PlayerMatcher playerMatcher;
private Person person=null;
private P partner=null;
private Date lastAccess=new Date();
private Date created=new Date();
private List<String> notifiers=new ArrayList<String>();
private int id;
private String conversation;
private List<GameRound> gameRounds=new ArrayList<GameRound>();
private GameRound gameRound;
private PushEventListener listener;
public String getConversation() {
return conversation;
}
public void setConversation(String conversation) {
this.conversation = conversation;
}
public void addNotifierAll(String notifier)
{
addNotifier(notifier);
if (partner!=null)
partner.addNotifier(notifier);
}
/* Strange effect, that I don't really understand:
* If this method is synchronized, deadlocks ensue (how? this method does nothing complicated)
* If there's an inner synchronized block, everything appears to be fine
*
* http://www.javamex.com/tutorials/synchronization_synchronized_method.shtml states:
* "When synchronized is used in a method declaration, it is marked as a flag on the method.
* The compiler does not insert instructions to acquire and release the lock; instead, the
* VM sees the flag on the method declaration and knows to "automatically" acquire and
* release the lock on method entry and exit"
* This might be a reason
*
* In any case, EJB specification explicitly forbids using java synchronization
*
* It appears that the PlayerMatcher and the associated classes will likely fail in a
* clustered environment.
*/
public void addNotifier(String notifier)
{
//log.info("Notified: "+notifier);
synchronized(notifiers)
{
if (!notifiers.contains(notifier))
notifiers.add(notifier);
}
}
public boolean isNotified(String notifier)
{
synchronized(notifiers)
{
return notifiers.remove(notifier);
}
}
public void setAllowAi(boolean allowAi) {
this.allowAi = allowAi;
}
public void setSharedGame(SharedGame sharedGame) {
this.sharedGame = sharedGame;
}
public void setPlayerMatcher(PlayerMatcher playerMatcher) {
this.playerMatcher = playerMatcher;
}
public P getPartner() {
return partner;
}
public void setPartner(P partner) {
this.partner = partner;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public PushEventListener getListener() {
return listener;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
public boolean isMatched()
{
return partner!=null;
}
public boolean isTimedOut()
{
return (new Date().getTime()-lastAccess.getTime())>TIMEOUT;
}
public void addListener(EventListener listener) {
synchronized (listener) {
this.listener=(PushEventListener)listener;
//Make sure notifier action fires at least once
this.listener.onEvent(null);
}
}
public void signalPartner()
{
signalPartner(null);
}
public void signalPartner(String updated)
{
if (partner!=null)
partner.signal(updated);
}
public void signalAll(String updated)
{
signalPartner(updated);
signal(updated);
}
public void signal(String updated)
{
//log.info("signal #0", updated);
if (updated!=null && updated!="")
{
//log.info("adding notifier");
addNotifier(updated);
}
if (listener!=null)
listener.onEvent(null);
}
public boolean isAi()
{
return false;
}
public void signal()
{
signal("");
}
public void forceTimeout()
{
lastAccess=new Date(0);
}
public GameRound getGameRound() {
return gameRound;
}
public void startRound()
{
//sharedGame.getGameSessionBeanNew().getGameSession();
if (!sharedGame.isFirstRound())
endRound();
gameRound=new GameRound();
log.info("Starting game round #"+sharedGame.getCurrentRound()+" "+gameRound.hashCode());
log.info("gameSession id=#0, code=#1", sharedGame.getGameSessionBeanNew().getGameSession().getId(), sharedGame.getGameSessionBeanNew().getGameSession().hashCode());
gameRound.setNumber(sharedGame.getCurrentRound());
sharedGame.getGameSessionBeanNew().addGameRound(gameRound, isAi()?null:person);
}
public void endRound()
{
gameRound.setScore(sharedGame.getLastScore().intValue());
gameRounds.add(gameRound);
log.info("Ending game round #0", gameRound.getId());
sharedGame.getGameSessionBeanNew().endGameRound(gameRound);
}
public List<GameRound> getGameRounds()
{
return gameRounds;
}
@End
public synchronized String poll(int round)
{
lastAccess=new Date();
if (partner!=null)
{
if (partner.isAi())
{
partner.poll(round);
}
else if (partner.isTimedOut())
{
partner=null;
forceTimeout();
return "abandoned";
}
}
if (sharedGame!=null && sharedGame.isRunning())
{
sharedGame.checkRound(0);
}
else
{
if (allowAi && (new Date().getTime()-created.getTime()>WAIT_TIMEOUT))
{
if (playerMatcher!=null)
playerMatcher.MatchTimeOut(person);
}
}
return null;
}
}