[lobby-svn] SF.net SVN: lobby:[1053] trunk/src_server/net/yura/lobby/server
Brought to you by:
yuranet
From: <yu...@us...> - 2025-05-04 22:57:38
|
Revision: 1053 http://sourceforge.net/p/lobby/code/1053 Author: yuranet Date: 2025-05-04 22:57:21 +0000 (Sun, 04 May 2025) Log Message: ----------- AbstractTurnBasedServerGame Modified Paths: -------------- trunk/src_server/net/yura/lobby/server/AbstractServerGame.java trunk/src_server/net/yura/lobby/server/GameLobby.java trunk/src_server/net/yura/lobby/server/TurnBasedGame.java Added Paths: ----------- trunk/src_server/net/yura/lobby/server/AbstractTurnBasedServerGame.java Modified: trunk/src_server/net/yura/lobby/server/AbstractServerGame.java =================================================================== --- trunk/src_server/net/yura/lobby/server/AbstractServerGame.java 2025-04-26 16:20:37 UTC (rev 1052) +++ trunk/src_server/net/yura/lobby/server/AbstractServerGame.java 2025-05-04 22:57:21 UTC (rev 1053) @@ -7,8 +7,9 @@ * @author yura */ public abstract class AbstractServerGame implements ServerGame { - + protected int id; + protected String startGameOptions; protected ServerGameListener listoner; protected Collection<LobbySession> spectators = new ConcurrentSkipListSet(); @@ -21,8 +22,13 @@ public int getId() { return id; } - + @Override + public void setOptions(String startGameOptions) { + this.startGameOptions = startGameOptions; + } + + @Override public final void addServerGameListener(ServerGameListener l) { listoner = l; } Copied: trunk/src_server/net/yura/lobby/server/AbstractTurnBasedServerGame.java (from rev 1052, trunk/src_server/net/yura/lobby/server/TurnBasedGame.java) =================================================================== --- trunk/src_server/net/yura/lobby/server/AbstractTurnBasedServerGame.java (rev 0) +++ trunk/src_server/net/yura/lobby/server/AbstractTurnBasedServerGame.java 2025-05-04 22:57:21 UTC (rev 1053) @@ -0,0 +1,266 @@ +/* + (C) 2007-2025 yura.net + This file is part of Lobby. + + Lobby is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License + + Lobby 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ +package net.yura.lobby.server; + +import java.io.ByteArrayOutputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import net.yura.util.PausableScheduledThreadPoolExecutor; + +/** + * @author Yura + */ +public abstract class AbstractTurnBasedServerGame extends AbstractServerGame { + + public static final int MAX_SKIPS = 5; + + public static final PausableScheduledThreadPoolExecutor scheduler = new PausableScheduledThreadPoolExecutor(1); + static { + // JAVA 7 ONLY + scheduler.setRemoveOnCancelPolicy(true); + } + + private Future future; + + protected boolean finished,startGameCalled; + String whoiwantinputfrom; + + /** + * in seconds + */ + protected int timeout; + + Map<String,Integer> skips = new HashMap(); + + // ################################### abstract methods #################################### + + // life cycle of a game + @Override public abstract void startGame(String[] players); + @Override public abstract void loadGame(byte[] gameData); + public abstract void destroyGame(); + + public abstract void playerJoins(String username); + public abstract boolean playerResigns(String username); + public abstract void renamePlayer(String oldser,String newuser); + + //comunication + public abstract void clientHasJoined(String username); + public abstract void stringFromPlayer(String username, String message); + + // called by this if the player has left or they have timmed out + public abstract void doBasicGo(String username); + + // ################################### methods callable by the game #################################### + + // called by the game when it comes to an end + // set things up for another game + public final boolean gameFinished(String winner) { + killFuture(); + whoiwantinputfrom = null; + finished=true; + sendStringToAllClient("LOBBY_GAMEOVER"); // stops the timeout clock on the client, IF it has been implemented + + listoner.sendChatroomMessage("Game over! "+winner+" has won!"); + return listoner.gameFinished(winner); + } + + public final void getInputFromClient(String username) { + killFuture(); + + String oldPlayer = whoiwantinputfrom; + whoiwantinputfrom = username; + + if (whoiwantinputfrom != null) { + future = scheduler.schedule(new Runnable() { + @Override + public void run() { + final String username = whoiwantinputfrom; + try { + if (username==null) { + throw new IllegalStateException("username is null"); + } + + // when MAX_SKIPS turns have been skipped, the player is resigned + int skip = skips.get(username)==null?1:skips.get(username)+1; + skips.put(username, skip); + + if (skip >= MAX_SKIPS) { + listoner.sendChatroomMessage(username+" has timed out "+MAX_SKIPS+" times and has been resigned from the game."); + listoner.resignPlayer(username); + } + else { + listoner.sendChatroomMessage(username+" has timed out on their turn"); + doBasicGo(username); + } + } + catch(Throwable th) { + LobbyLogger.log(id, Level.WARNING, "error in timeout for game: "+id+" for user "+username, th); + } + } + }, timeout + 10, TimeUnit.SECONDS); // add 10 seconds for bad ping + } + + if (!Objects.equals(oldPlayer, whoiwantinputfrom)) { + + // the first time we need input from a user we know the game has finished setup + // is now started/ready to be opened + if (whoiwantinputfrom != null && !startGameCalled) { + listoner.gameStarted(); + startGameCalled = true; + } + + // if whoiwantinputfrom is null we send it to tell the client + // that we do not need input from any human player + listoner.needInputFrom(whoiwantinputfrom); + } + } + + private Collection<LobbySession> usernameToLobbySessionArray(String username) { + List<LobbySession> sessions = new ArrayList(); + for (LobbySession session:spectators) { + if (username.equals(session.getUsername())) { + sessions.add(session); + } + } + return sessions; + } + + public final void sendStringToClient(String a, String username) { + listoner.messageFromGame(a, usernameToLobbySessionArray(username) ); + } + + public final void sendStringToAllClient(String a) { + listoner.messageFromGame(a, spectators ); + } + + public final void sendObjectToClient(Serializable a,String username) { + listoner.messageFromGame( serializableToByteArray(a) , usernameToLobbySessionArray(username) ); + } + + public final void sendObjectToAllClient(Serializable a) { + listoner.messageFromGame( serializableToByteArray(a), spectators ); + } + + private static byte[] serializableToByteArray(Serializable a) { + try { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ObjectOutputStream oout = new ObjectOutputStream(out); + oout.writeObject(a); + oout.flush(); + return out.toByteArray(); + } + catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + // ################################### implementation of ServerGame #################################### + + @Override + public void setTimeout(int seconds) { + this.timeout = seconds; + + if (this.timeout <= 0) { + this.timeout = 60; // a turn based game MUST have a timeout to give players a time to take there turn + } + } + + @Override + public final void clientHasJoined(LobbySession session) { + // TODO we send game object to every session with this username + // would be better if just sent it to the session we needed to + clientHasJoined(session.getUsername()); + } + + @Override + public boolean playerResigned(String player) { + // TODO what if player clicks resign twice, there is not check if we are already resigned here + if (player.equals(whoiwantinputfrom)) { + killFuture(); + whoiwantinputfrom = null; + } + listoner.sendChatroomMessage( player+" has resigned from the game"); + // even though a error may happen in the resign, that would lead to us not being resigned + // we still have to call the sendChatroomMessage method first, as it will make more sense + // in the log (player X resigned, game over) and the game may get deleted by the resign + // command, and calling sendChatroomMessage on a deleted game will throw a nullpointer + return playerResigns( player ); // this may lead to the game being deleted (gameFinished called) + } + + @Override + public void playerJoined(String player) { + playerJoins( player ); + listoner.sendChatroomMessage( player+" has joined the game"); + } + + @Override + public final void messageFromUser(String username, Object object) { + if (username.equals(whoiwantinputfrom)) { + killFuture(); + } + skips.put(username, 0); // if someone does something reset skips counter + stringFromPlayer(username, (String)object ); + } + + // TODO should be synchronized with something? + @Override + public final void midgameLogin(String oldser,String newuser) { + renamePlayer( oldser , newuser ); + + Integer skip = skips.remove(oldser); + if (skip!=null) { + skips.put(newuser, skip); + } + if (oldser.equals(whoiwantinputfrom)) { + whoiwantinputfrom = newuser; + } + + listoner.sendChatroomMessage(oldser+" has logged in as "+newuser); + } + + @Override + public boolean isFinished() { + return finished; + } + + @Override + public String getWhosTurn() { + return whoiwantinputfrom; + } + + @Override + public void destroyServerGame() { + killFuture(); + destroyGame(); + } + + void killFuture() { + Future f = future; + if (f!=null && !f.isDone() && !f.isCancelled()) { + f.cancel(true); + } + } +} Modified: trunk/src_server/net/yura/lobby/server/GameLobby.java =================================================================== --- trunk/src_server/net/yura/lobby/server/GameLobby.java 2025-04-26 16:20:37 UTC (rev 1052) +++ trunk/src_server/net/yura/lobby/server/GameLobby.java 2025-05-04 22:57:21 UTC (rev 1053) @@ -93,9 +93,9 @@ this.database = database; - TurnBasedGame.scheduler.pause(); + AbstractTurnBasedServerGame.scheduler.pause(); loadFromDB(); - TurnBasedGame.scheduler.resume(); + AbstractTurnBasedServerGame.scheduler.resume(); // add shutdown hook only if we manage to create a DB Runtime.getRuntime().addShutdownHook(new Thread() { @@ -1205,7 +1205,7 @@ catch (RuntimeException ex) { StringWriter sw = new StringWriter(); ex.printStackTrace(new PrintWriter(sw)); - Alert.sendAlert("StartGameError", sw.toString(), database); + Alert.sendAlert("StartGameError", sw.toString() + "\nsession: " + session, database); //leaveGame(gameId, username); // there may be other players, we dont want to leave them there removeGame(gameId); Modified: trunk/src_server/net/yura/lobby/server/TurnBasedGame.java =================================================================== --- trunk/src_server/net/yura/lobby/server/TurnBasedGame.java 2025-04-26 16:20:37 UTC (rev 1052) +++ trunk/src_server/net/yura/lobby/server/TurnBasedGame.java 2025-05-04 22:57:21 UTC (rev 1053) @@ -1,273 +1,14 @@ -/* - (C) 2007-2013 yura.net - This file is part of Lobby. - - Lobby is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License - - Lobby 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 General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ -package net.yura.lobby.server; - -import java.io.ByteArrayOutputStream; -import java.io.ObjectOutputStream; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.logging.Level; -import net.yura.util.PausableScheduledThreadPoolExecutor; - -/** - * @author Yura - */ -public abstract class TurnBasedGame extends AbstractServerGame { - - public static final int MAX_SKIPS = 5; - - public static final PausableScheduledThreadPoolExecutor scheduler = new PausableScheduledThreadPoolExecutor(1); - static { - // JAVA 7 ONLY - scheduler.setRemoveOnCancelPolicy(true); - } - - private Future future; - - protected boolean finished,startGameCalled; - String whoiwantinputfrom; - - /** - * in seconds - */ - protected int timeout; - - protected String startGameOptions; - - Map<String,Integer> skips = new HashMap(); - - // ################################### abstract methods #################################### - - // life cycle of a game - @Override public abstract void startGame(String[] players); - @Override public abstract void loadGame(byte[] gameData); - public abstract void destroyGame(); - - public abstract void playerJoins(String username); - public abstract boolean playerResigns(String username); - public abstract void renamePlayer(String oldser,String newuser); - - //comunication - public abstract void clientHasJoined(String username); - public abstract void stringFromPlayer(String username, String message); - - // called by this if the player has left or they have timmed out - public abstract void doBasicGo(String username); - - // ################################### methods callable by the game #################################### - - // called by the game when it comes to an end - // set things up for another game - public final boolean gameFinished(String winner) { - killFuture(); - whoiwantinputfrom = null; - finished=true; - sendStringToAllClient("LOBBY_GAMEOVER"); // stops the timeout clock on the client, IF it has been implemented - - listoner.sendChatroomMessage("Game over! "+winner+" has won!"); - return listoner.gameFinished(winner); - } - - public final void getInputFromClient(String username) { - killFuture(); - - String oldPlayer = whoiwantinputfrom; - whoiwantinputfrom = username; - - if (whoiwantinputfrom != null) { - future = scheduler.schedule(new Runnable() { - @Override - public void run() { - final String username = whoiwantinputfrom; - try { - if (username==null) { - throw new IllegalStateException("username is null"); - } - - // when MAX_SKIPS turns have been skipped, the player is resigned - int skip = skips.get(username)==null?1:skips.get(username)+1; - skips.put(username, skip); - - if (skip >= MAX_SKIPS) { - listoner.sendChatroomMessage(username+" has timed out "+MAX_SKIPS+" times and has been resigned from the game."); - listoner.resignPlayer(username); - } - else { - listoner.sendChatroomMessage(username+" has timed out on their turn"); - doBasicGo(username); - } - } - catch(Throwable th) { - LobbyLogger.log(id, Level.WARNING, "error in timeout for game: "+id+" for user "+username, th); - } - } - }, timeout + 10, TimeUnit.SECONDS); // add 10 seconds for bad ping - } - - if (!Objects.equals(oldPlayer, whoiwantinputfrom)) { - - // the first time we need input from a user we know the game has finished setup - // is now started/ready to be opened - if (whoiwantinputfrom != null && !startGameCalled) { - listoner.gameStarted(); - startGameCalled = true; - } - - // if whoiwantinputfrom is null we send it to tell the client - // that we do not need input from any human player - listoner.needInputFrom(whoiwantinputfrom); - } - } - - private Collection<LobbySession> usernameToLobbySessionArray(String username) { - List<LobbySession> sessions = new ArrayList(); - for (LobbySession session:spectators) { - if (username.equals(session.getUsername())) { - sessions.add(session); - } - } - return sessions; - } - - public final void sendStringToClient(String a, String username) { - listoner.messageFromGame(a, usernameToLobbySessionArray(username) ); - } - - public final void sendStringToAllClient(String a) { - listoner.messageFromGame(a, spectators ); - } - - public final void sendObjectToClient(Serializable a,String username) { - listoner.messageFromGame( serializableToByteArray(a) , usernameToLobbySessionArray(username) ); - } - - public final void sendObjectToAllClient(Serializable a) { - listoner.messageFromGame( serializableToByteArray(a), spectators ); - } - - private static byte[] serializableToByteArray(Serializable a) { - try { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - ObjectOutputStream oout = new ObjectOutputStream(out); - oout.writeObject(a); - oout.flush(); - return out.toByteArray(); - } - catch (Exception ex) { - throw new RuntimeException(ex); - } - } - - // ################################### implementation of ServerGame #################################### - - @Override - public void setTimeout(int seconds) { - this.timeout = seconds; - - if (this.timeout <= 0) { - this.timeout = 60; // a turn based game MUST have a timeout to give players a time to take there turn - } - } - - @Override - public void setOptions(String startGameOptions) { - this.startGameOptions = startGameOptions; - } - - @Override - public final void clientHasJoined(LobbySession session) { - // TODO we send game object to every session with this username - // would be better if just sent it to the session we needed to - clientHasJoined(session.getUsername()); - } - - @Override - public boolean playerResigned(String player) { - // TODO what if player clicks resign twice, there is not check if we are already resigned here - if (player.equals(whoiwantinputfrom)) { - killFuture(); - whoiwantinputfrom = null; - } - listoner.sendChatroomMessage( player+" has resigned from the game"); - // even though a error may happen in the resign, that would lead to us not being resigned - // we still have to call the sendChatroomMessage method first, as it will make more sense - // in the log (player X resigned, game over) and the game may get deleted by the resign - // command, and calling sendChatroomMessage on a deleted game will throw a nullpointer - return playerResigns( player ); // this may lead to the game being deleted (gameFinished called) - } - - @Override - public void playerJoined(String player) { - playerJoins( player ); - listoner.sendChatroomMessage( player+" has joined the game"); - } - - @Override - public final void messageFromUser(String username, Object object) { - if (username.equals(whoiwantinputfrom)) { - killFuture(); - } - skips.put(username, 0); // if someone does something reset skips counter - stringFromPlayer(username, (String)object ); - } - - // TODO should be synchronized with something? - @Override - public final void midgameLogin(String oldser,String newuser) { - renamePlayer( oldser , newuser ); - - Integer skip = skips.remove(oldser); - if (skip!=null) { - skips.put(newuser, skip); - } - if (oldser.equals(whoiwantinputfrom)) { - whoiwantinputfrom = newuser; - } - - listoner.sendChatroomMessage(oldser+" has logged in as "+newuser); - } - - @Override - public boolean isFinished() { - return finished; - } - - @Override - public String getWhosTurn() { - return whoiwantinputfrom; - } - - @Override - public void destroyServerGame() { - killFuture(); - destroyGame(); - } - - void killFuture() { - Future f = future; - if (f!=null && !f.isDone() && !f.isCancelled()) { - f.cancel(true); - } - } -} +package net.yura.lobby.server; + +/** + * This class extends AbstractTurnBasedServerGame to add extra functionality + * - send string and java serialised object + * - treat incoming objects as string + * - keep track of missed turns and handle doing a basic move when turns are missed + * - resign player when player misses 5 ({@link #MAX_SKIPS}) turns + * + * @author Yura + */ +public abstract class TurnBasedGame extends AbstractTurnBasedServerGame { + +} This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |