@@ -79,7 +79,7 @@ public class SecretHitlerServer {
7979 private static final String CODE_CHARACTERS = "ABCDEFGHIJKLMNOPQRSTWXYZ" ; // u,v characters can look ambiguous
8080 private static final int CODE_LENGTH = 4 ;
8181
82- private static final float UPDATE_FREQUENCY_MIN = 1 ;
82+ private static final float UPDATE_FREQUENCY_SECONDS = 60 ;
8383 // </editor-fold>
8484
8585 ///// Private Fields
@@ -94,6 +94,32 @@ public class SecretHitlerServer {
9494
9595 ////// Private Methods
9696
97+ // TODO: Replace this with actual log levels, or a logging library.
98+ /**
99+ * Optionally prints an input string if the debug flag is enabled.
100+ */
101+ private static void debugPrint (String input ) {
102+ if (DEBUG ) {
103+ System .out .print (input );
104+ }
105+ }
106+
107+ private static void debugPrintLn (String input ) {
108+ debugPrint (input + "\n " );
109+ }
110+
111+ /**
112+ * Prints the current list of lobbies and their players.
113+ */
114+ private static void printLobbyStatus () {
115+ synchronized (codeToLobby ) {
116+ System .out .println ("Lobbies (" + codeToLobby .mappingCount () + ") : " + codeToLobby .keySet ().toString ());
117+ for (Map .Entry <String , Lobby > entry : codeToLobby .entrySet ()) {
118+ System .out .println (" " + entry .getKey () + ": " + entry .getValue ().getUserNames ());
119+ }
120+ }
121+ }
122+
97123 private static int getHerokuAssignedPort () {
98124 String herokuPort = System .getenv ("PORT" );
99125 if (herokuPort != null ) {
@@ -108,9 +134,7 @@ public static void main(String[] args) {
108134 loadDatabaseBackup ();
109135 removeInactiveLobbies (); // immediately clean in case of redundant lobbies.
110136
111- if (DEBUG ) {
112- System .out .println ("Running in DEBUG mode." );
113- }
137+ debugPrintLn ("Running in DEBUG mode." );
114138
115139 // Only initialize Javalin communication after the database has been queried.
116140 Javalin serverApp = Javalin .create (config -> {
@@ -140,26 +164,30 @@ public static void main(String[] args) {
140164 // Add hook for termination that backs up the lobbies to the database.
141165 Runtime .getRuntime ().addShutdownHook (new Thread () {
142166 public void run () {
143- System . out . println ("Attempting to back up lobby data." );
167+ debugPrintLn ("Attempting to back up lobby data." );
144168 storeDatabaseBackup ();
169+ printLobbyStatus ();
145170 }
146171 });
147172
148173 // Add timer for periodic updates.
149- int delay = 0 ;
150- int period = (int ) (UPDATE_FREQUENCY_MIN * 60.0f * 1000.0f );
174+ int delayMs = 0 ;
175+ int periodMs = (int ) (UPDATE_FREQUENCY_SECONDS * 1000.0f );
151176 Timer timer = new Timer ();
152177 timer .schedule (new TimerTask () {
153178 @ Override
154179 public void run () {
155180 removeInactiveLobbies ();
181+ if (!codeToLobby .isEmpty ()) {
182+ printLobbyStatus ();
183+ }
156184 // If there are active lobbies, store a backup of the game.
157185 if (!codeToLobby .isEmpty () && hasLobbyChanged ) {
158186 storeDatabaseBackup ();
159187 hasLobbyChanged = false ;
160188 }
161189 }
162- }, delay , period );
190+ }, delayMs , periodMs );
163191 }
164192
165193 /**
@@ -191,8 +219,8 @@ private static void removeInactiveLobbies() {
191219 }
192220 }
193221 if (removedCount > 0 ) {
194- System . out . println (String .format ("Removed %d inactive lobbies: %s" , removedCount , removedLobbyCodes ));
195- System . out . println ( "Available lobbies: " + codeToLobby . keySet () );
222+ debugPrintLn (String .format ("Removed %d inactive lobbies: %s" , removedCount , removedLobbyCodes ));
223+ printLobbyStatus ( );
196224 hasLobbyChanged = true ;
197225 }
198226 }
@@ -226,9 +254,10 @@ private static Connection getDatabaseConnection() {
226254
227255 Class .forName ("org.postgresql.Driver" );
228256 c = DriverManager .getConnection (dbUrl , username , password );
229- System . out . println ("Successfully connected to database." );
257+ debugPrintLn ("Successfully connected to database." );
230258 return c ;
231259 } catch (Exception e ) {
260+ // Print failures no matter what
232261 System .out .println ("Failed to connect to database." );
233262 System .err .println (e );
234263 return null ;
@@ -278,7 +307,7 @@ private static void loadDatabaseBackup() {
278307 ObjectInputStream objectStream = new ObjectInputStream (lobbyByteStream )) {
279308 codeToLobby = (ConcurrentHashMap <String , Lobby >) objectStream .readObject ();
280309 objectStream .close ();
281- System . out . println ("Successfully parsed lobby data from the database." );
310+ debugPrintLn ("Successfully parsed lobby data from the database." );
282311 } catch (Exception e ) {
283312 System .out .println ("Failed to parse lobby data from stored backup. " );
284313 System .err .println (e .getClass ().getName () + ": " + e .getMessage ());
@@ -288,6 +317,7 @@ private static void loadDatabaseBackup() {
288317 System .out .println ("Failed to retrieve lobby backups from the database." );
289318 System .err .println (e .getClass ().getName () + ": " + e .getMessage ());
290319 }
320+ printLobbyStatus ();
291321 }
292322
293323 private static void storeDatabaseBackup () {
@@ -333,7 +363,7 @@ private static void storeDatabaseBackup() {
333363 System .err .println (e );
334364 return ;
335365 }
336- System . out . println ("Successfully saved Lobby state to the database." );
366+ debugPrintLn ("Successfully saved Lobby state to the database." );
337367 }
338368
339369 /**
@@ -497,7 +527,7 @@ private static String generateCode() {
497527 */
498528 private static void onWebsocketConnect (WsConnectContext ctx ) {
499529 if (ctx .queryParam (PARAM_LOBBY ) == null || ctx .queryParam (PARAM_NAME ) == null ) {
500- System . out . println ("A websocket request was missing a parameter and was disconnected." );
530+ debugPrintLn ("A websocket request was missing a parameter and was disconnected." );
501531 ctx .session .close (StatusCode .PROTOCOL ,
502532 "Must have the '" + PARAM_LOBBY + "' and '" + PARAM_NAME + "' parameters." );
503533 return ;
@@ -508,32 +538,32 @@ private static void onWebsocketConnect(WsConnectContext ctx) {
508538 String name = ctx .queryParam (PARAM_NAME );
509539
510540 if (code == null || name == null || name .isEmpty () || name .isBlank ()) {
511- System . out . println ("FAILED (Lobby or name is empty/null)" );
541+ debugPrintLn ("FAILED (Lobby or name is empty/null)" );
512542 ctx .session .close (StatusCode .PROTOCOL , "Lobby and name must be specified." );
513543 }
514544
515- System . out . print ("Attempting to connect user '" + name + "' to lobby '" + code + "': " );
545+ debugPrint ("Attempting to connect user '" + name + "' to lobby '" + code + "': " );
516546 if (!codeToLobby .containsKey (code )) { // the lobby does not exist.
517- System . out . println ("FAILED (The lobby does not exist)" );
547+ debugPrintLn ("FAILED (The lobby does not exist)" );
518548 ctx .session .close (StatusCode .PROTOCOL , "The lobby '" + code + "' does not exist." );
519549 return ;
520550 }
521551
522552 Lobby lobby = codeToLobby .get (code );
523553 if (lobby .hasUserWithName (name )) { // duplicate names not allowed
524- System . out . println ("FAILED (Repeat username)" );
554+ debugPrintLn ("FAILED (Repeat username)" );
525555 ctx .session .close (StatusCode .PROTOCOL , "A user with the name " + name + " is already in the lobby." );
526556 return ;
527557 } else if (lobby .isFull ()) {
528- System . out . println ("FAILED (Lobby is full)" );
558+ debugPrintLn ("FAILED (Lobby is full)" );
529559 ctx .session .close (StatusCode .PROTOCOL , "The lobby " + code + " is currently full." );
530560 return ;
531561 } else if (lobby .isInGame () && !lobby .canAddUserDuringGame (name )) {
532- System . out . println ("FAILED (Lobby in game)" );
562+ debugPrintLn ("FAILED (Lobby in game)" );
533563 ctx .session .close (StatusCode .PROTOCOL , "The lobby " + code + " is currently in a game.." );
534564 return ;
535565 }
536- System . out . println ("SUCCESS" );
566+ debugPrintLn ("SUCCESS" );
537567 lobby .addUser (ctx , name );
538568 userToLobby .put (ctx , lobby ); // keep track of which lobby this connection is in.
539569 lobby .updateAllUsers ();
@@ -575,23 +605,21 @@ private static void onWebSocketMessage(WsMessageContext ctx) {
575605 String name = message .getString (PARAM_NAME );
576606 String lobbyCode = message .getString (PARAM_LOBBY );
577607
578- String log_message = "Received a message from user '" + name + "' in lobby '" + lobbyCode + "' ("
608+ String logMessage = "Received a message from user '" + name + "' in lobby '" + lobbyCode + "' ("
579609 + ctx .message () + "): " ;
580- int log_length = log_message .length ();
581- System .out .print (log_message );
610+ int log_length = logMessage .length ();
582611
583612 if (!codeToLobby .containsKey (lobbyCode )) {
584- System . out . println ( " FAILED (Lobby requested does not exist)" );
613+ debugPrintLn ( logMessage + " FAILED (Lobby requested does not exist)" );
585614 ctx .session .close (StatusCode .PROTOCOL , "The lobby does not exist." );
586615 return ;
587616 }
588617
589618 Lobby lobby = codeToLobby .get (lobbyCode );
590619
591620 synchronized (lobby ) {
592-
593621 if (!lobby .hasUser (ctx , name )) {
594- System . out . println ( " FAILED (Lobby does not have the user)" );
622+ debugPrintLn ( logMessage + " FAILED (Lobby does not have the user)" );
595623 ctx .session .close (StatusCode .PROTOCOL , "The user is not in the lobby " + lobbyCode + "." );
596624 return ;
597625 }
@@ -606,8 +634,8 @@ private static void onWebSocketMessage(WsMessageContext ctx) {
606634 sendOKMessage = false ;
607635 updateUsers = false ;
608636 // Erase the previous line with spaces and \r
609- System . out . print ("\r " + (' ' * log_length ));
610- System . out . print ("\r " );
637+ debugPrint ("\r " + (' ' * log_length ));
638+ debugPrint ("\r " );
611639 JSONObject msg = new JSONObject ();
612640 msg .put (PARAM_PACKET_TYPE , PACKET_PONG );
613641 ctx .send (msg .toString ());
@@ -695,21 +723,23 @@ private static void onWebSocketMessage(WsMessageContext ctx) {
695723 break ;
696724
697725 default : // This is an invalid command.
698- throw new RuntimeException ("FAILED ( unrecognized command " + message .get (PARAM_COMMAND ) + ")" );
726+ throw new RuntimeException ("unrecognized command " + message .get (PARAM_COMMAND ));
699727 } // End switch
700728
701729 if (sendOKMessage ) {
702- System . out . println ( " SUCCESS" );
730+ debugPrintLn ( logMessage + " SUCCESS" );
703731 JSONObject msg = new JSONObject ();
704732 msg .put (PARAM_PACKET_TYPE , PACKET_OK );
705733 ctx .send (msg .toString ());
706734 }
707735
708736 } catch (NullPointerException e ) {
709- System .out .println ("FAILED (" + e .toString () + ")" );
737+ // Show error messages by default, since they indicate API access
738+ // issues.
739+ System .out .println (logMessage + " FAILED (" + e .toString () + ")" );
710740 ctx .session .close (StatusCode .PROTOCOL , "NullPointerException:" + e .toString ());
711741 } catch (RuntimeException e ) {
712- System .out .println (" FAILED (" + e .toString () + ")" );
742+ System .out .println (logMessage + " FAILED (" + e .toString () + ")" );
713743 ctx .session .close (StatusCode .PROTOCOL , "RuntimeException:" + e .toString ());
714744 }
715745 if (updateUsers ) {
@@ -719,6 +749,9 @@ private static void onWebSocketMessage(WsMessageContext ctx) {
719749 hasLobbyChanged = true ;
720750 }
721751
752+ // TODO: This is bad. This is bad code practice. Exceptions should not be
753+ // used for control flow.
754+
722755 /**
723756 * Verifies that the user is the president.
724757 *
0 commit comments