From 7a47b613d3a84b23c0986e296e8e9edbb3f34f3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sander=20=C3=98strem=20Fagernes?= <sanderof@stud.ntnu.no> Date: Mon, 10 Apr 2023 16:47:23 +0200 Subject: [PATCH] Resolve "Connect FindGameController to backend" --- backend/src/index.ts | 49 ++- backend/src/interfaces/ILobby.ts | 1 + backend/src/lobby/Lobby.ts | 4 + frontend/assets/menu-textures.atlas | 9 +- frontend/assets/menu-textures.png | Bin 52278 -> 52306 bytes .../com/game/tankwars/HTTPRequestHandler.java | 74 +++++ .../com/game/tankwars/ReceiverHandler.java | 4 + .../src/com/game/tankwars/TankWarsGame.java | 1 + .../controller/FindGameController.java | 295 +++++++++++++++--- .../src/com/game/tankwars/model/LobbyId.java | 14 + .../com/game/tankwars/model/LobbyStatus.java | 13 + .../game/tankwars/view/FindGameScreen.java | 41 ++- 12 files changed, 448 insertions(+), 57 deletions(-) create mode 100644 frontend/core/src/com/game/tankwars/HTTPRequestHandler.java create mode 100644 frontend/core/src/com/game/tankwars/model/LobbyId.java create mode 100644 frontend/core/src/com/game/tankwars/model/LobbyStatus.java diff --git a/backend/src/index.ts b/backend/src/index.ts index fc2b951..c73a1f1 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -18,6 +18,8 @@ app.use((req, res, next) => { next(); }); +app.use(express.json()); + app.get('/', (req, res) => { res.send('Hello, World!'); }); @@ -123,26 +125,43 @@ app.get('/highscores', async (req, res) => { }); // join a lobby with specific id -app.post('/lobby/:id', async (req, res) => { - if (gameHandler.getLobbyById(req.params.id)) { - const lobby = gameHandler.getLobbyById(req.params.id); +app.post('/lobby/create', async (req, res) => { + // if lobby doesn't exist, create a new lobby + const lobby = gameHandler.createLobby(); + lobby.addUser(req.body.userId); + res.status(201).send({ lobbyId: lobby.getId() }); +}); - if (!lobby) { - res.status(404).send('Lobby not found'); - return; - } +// Inform client if requested lobby is full or not +app.get('/lobby/:id/status', async (req, res) => { + const lobby = gameHandler.getLobbyById(req.params.id); - lobby.addUser(req.body.userId); - gameHandler.createGame(lobby); // start game since 2 players are in lobby - res.status(200).send(lobby.getId()); + if (!lobby) { + res.status(404).send('Lobby not found'); + return; + } + + if (lobby.isFull()) { + res.status(200).send({ isFull: true }); } else { - // if lobby doesn't exist, create a new lobby - const lobby = gameHandler.createLobby(); - lobby.addUser(req.body.userId); - res.status(201).send(lobby.getId()); + res.status(200).send({ isFull: false }); } }); +// join a lobby with specific id +app.post('/lobby/:id/join', async (req, res) => { + const lobby = gameHandler.getLobbyById(req.params.id); + + if (!lobby) { + res.status(404).send('Lobby not found'); + return; + } + + lobby.addUser(req.body.userId); + gameHandler.createGame(lobby); // start game since 2 players are in lobby + res.status(200).send({ lobbyId: lobby.getId() }); +}); + // leave a lobby with specific id app.post('/lobby/:id/leave', async (req, res) => { const lobby = gameHandler.getLobbyById(req.params.id); @@ -151,7 +170,7 @@ app.post('/lobby/:id/leave', async (req, res) => { if (lobby?.getUsers().length === 0) { gameHandler.removeLobby(lobby); } - res.status(200).send(lobby?.getId()); + res.status(200).send({ lobbyId: lobby?.getId() }); }); // Polling endpoint to check if its your turn (sends username) diff --git a/backend/src/interfaces/ILobby.ts b/backend/src/interfaces/ILobby.ts index 46edf40..072b103 100644 --- a/backend/src/interfaces/ILobby.ts +++ b/backend/src/interfaces/ILobby.ts @@ -6,5 +6,6 @@ export interface ILobby { getUsers(): User[]; id: string; getId(): string; + isFull(): boolean; // maybe add a relation to a game? } diff --git a/backend/src/lobby/Lobby.ts b/backend/src/lobby/Lobby.ts index 2c3acec..34e0f12 100644 --- a/backend/src/lobby/Lobby.ts +++ b/backend/src/lobby/Lobby.ts @@ -44,4 +44,8 @@ export class Lobby implements ILobby { getUsers() { return this.users; } + + isFull() { + return this.game != undefined; + } } diff --git a/frontend/assets/menu-textures.atlas b/frontend/assets/menu-textures.atlas index 046381c..a9e0a1c 100644 --- a/frontend/assets/menu-textures.atlas +++ b/frontend/assets/menu-textures.atlas @@ -67,13 +67,20 @@ logo orig: 134, 40 offset: 0, 0 index: -1 -transparent-white-box +semi-transparent-white-box rotate: false xy: 921, 1009 size: 1, 1 orig: 1, 1 offset: 0, 0 index: -1 +transparent-white-box + rotate: false + xy: 892, 973 + size: 1, 1 + orig: 1, 1 + offset: 0, 0 + index: -1 typing-cursor rotate: false xy: 2, 2 diff --git a/frontend/assets/menu-textures.png b/frontend/assets/menu-textures.png index 6b223eced211d505301197211b096bcdbc94a6c7..bd0676b21eceb450b41ae64eee8bc8f276ba16f6 100644 GIT binary patch delta 7017 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 delta 6988 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 diff --git a/frontend/core/src/com/game/tankwars/HTTPRequestHandler.java b/frontend/core/src/com/game/tankwars/HTTPRequestHandler.java new file mode 100644 index 0000000..dace91d --- /dev/null +++ b/frontend/core/src/com/game/tankwars/HTTPRequestHandler.java @@ -0,0 +1,74 @@ +package com.game.tankwars; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.Net; +import com.badlogic.gdx.Net.HttpResponse; + +/** + * Handles request and response from the HTTP request using + * a passed callback function that treats request success, failure and cancellation. + * Implements the retry tactic by resending the request on failure + * a few times with increasing backoff time between resends. + */ +public class HTTPRequestHandler implements Net.HttpResponseListener { + + public static final String PROTOCOL = "http"; + public static final String HOST = "localhost"; + public static final int PORT = 3000; + + private final Callback callback; + private final Net.HttpRequest request; + private int attempts = 0; + private final int MAX_ATTEMPTS = 3; + private final int BACKOFF_TIME = 300; + + public HTTPRequestHandler(Callback callback, Net.HttpRequest request) { + this.callback = callback; + this.request = request; + } + + /** + * Send the HTTP request + */ + public void sendRequest() { + Gdx.net.sendHttpRequest(request, this); + } + + /** + * Request was successful and response received. Passes response body + * to callback. + * + * @param httpResponse The {@link HttpResponse} with the HTTP response values. + */ + public void handleHttpResponse(HttpResponse httpResponse) { + callback.onResult(httpResponse.getResultAsString()); + } + + /** + * Request failed. Request will be retried until the attempts + * have been exhausted with an increasing backoff time between each retry. + * + * @param t If the HTTP request failed because an Exception, t encapsulates it to give more information. + */ + public void failed(Throwable t) { + if (attempts < MAX_ATTEMPTS) { + attempts++; + + try { + Thread.sleep((long) attempts * BACKOFF_TIME); + sendRequest(); + } catch(InterruptedException e) { + System.err.println(e.getMessage()); + } + } else { + callback.onFailed(t); + } + } + + /** + * Request was cancelled + */ + public void cancelled() { + System.out.println("Request cancelled"); + } +} \ No newline at end of file diff --git a/frontend/core/src/com/game/tankwars/ReceiverHandler.java b/frontend/core/src/com/game/tankwars/ReceiverHandler.java index 4f16b98..a0adc82 100644 --- a/frontend/core/src/com/game/tankwars/ReceiverHandler.java +++ b/frontend/core/src/com/game/tankwars/ReceiverHandler.java @@ -19,6 +19,10 @@ public class ReceiverHandler implements Net.HttpResponseListener { private final Callback callback; public static final int MAX_RETRIES = 5; + public static final String PROTOCOL = "http"; + public static final String HOST = "localhost"; + public static final int PORT = 3000; + // Constructor to initialize the Callback instance public ReceiverHandler(Callback callback) { this.callback = callback; diff --git a/frontend/core/src/com/game/tankwars/TankWarsGame.java b/frontend/core/src/com/game/tankwars/TankWarsGame.java index 2e86cff..3c93ec8 100644 --- a/frontend/core/src/com/game/tankwars/TankWarsGame.java +++ b/frontend/core/src/com/game/tankwars/TankWarsGame.java @@ -4,6 +4,7 @@ package com.game.tankwars; import com.badlogic.gdx.Game; +import com.game.tankwars.view.FindGameScreen; import com.game.tankwars.view.LoginScreen; public class TankWarsGame extends Game { diff --git a/frontend/core/src/com/game/tankwars/controller/FindGameController.java b/frontend/core/src/com/game/tankwars/controller/FindGameController.java index 4730e36..f98492c 100644 --- a/frontend/core/src/com/game/tankwars/controller/FindGameController.java +++ b/frontend/core/src/com/game/tankwars/controller/FindGameController.java @@ -1,71 +1,104 @@ package com.game.tankwars.controller; +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.Net; +import com.badlogic.gdx.net.HttpRequestBuilder; +import com.badlogic.gdx.scenes.scene2d.EventListener; import com.badlogic.gdx.scenes.scene2d.InputEvent; import com.badlogic.gdx.scenes.scene2d.InputListener; import com.badlogic.gdx.scenes.scene2d.Stage; import com.badlogic.gdx.scenes.scene2d.ui.Button; +import com.badlogic.gdx.scenes.scene2d.ui.Label; import com.badlogic.gdx.scenes.scene2d.ui.TextButton; import com.badlogic.gdx.scenes.scene2d.ui.TextField; import com.badlogic.gdx.scenes.scene2d.utils.ClickListener; +import com.badlogic.gdx.utils.Json; +import com.badlogic.gdx.utils.SerializationException; +import com.game.tankwars.Callback; +import com.game.tankwars.ConfigReader; +import com.game.tankwars.HTTPRequestHandler; import com.game.tankwars.ResourceManager; import com.game.tankwars.TankWarsGame; +import com.game.tankwars.model.CurrentUser; +import com.game.tankwars.model.LobbyId; +import com.game.tankwars.model.LobbyStatus; +import com.game.tankwars.view.FindGameScreen; import com.game.tankwars.view.GameScreen; import com.game.tankwars.view.MainMenuScreen; + public class FindGameController { private final TankWarsGame tankWarsGame; + private final FindGameScreen screen; private final Stage stage; private final TextField gamePinField; - private final TextButton joinLobbyButton, createLobbyButton; + private final TextButton joinLobbyButton, createLobbyButton, cancelButton; private final Button backButton; + private final Label gamePinWaitingLabel; + + private EventListener backButtonInputListener, gamePinFieldInputListener, + gamePinFieldClickListener, joinLobbyButtonInputListener, + createLobbyButtonInputListener, cancelButtonInputListener; + + private String lobbyId = null; + private final Runnable gameScreenTransition; + /** + * TODO: Ensure that user is logged in -> e.g. auth method in Auth/Utils class * Sets the event listeners of the buttons and the text field of the FindGameScreen, - * and allows for transitioning to MainMenuScreen and to GameScreen. + * and allows for transitioning to MainMenuScreen and to GameScreen. Handles communication + * with server to create, join and leave a lobbies. */ - public FindGameController(final TankWarsGame tankWarsGame, + public FindGameController(final TankWarsGame tankWarsGame, FindGameScreen screen, TextField gamePinField, TextButton joinLobbyButton, - TextButton createLobbyButton, Button backButton, final Stage stage) { + TextButton createLobbyButton, Button backButton, + TextButton cancelButton, Label gamePinWaitingLabel, + final Stage stage) { this.tankWarsGame = tankWarsGame; + this.screen = screen; + this.gamePinField = gamePinField; this.joinLobbyButton = joinLobbyButton; this.createLobbyButton = createLobbyButton; this.backButton = backButton; + this.cancelButton = cancelButton; + this.gamePinWaitingLabel = gamePinWaitingLabel; this.stage = stage; - setEventListeners(); + // This Runnable will be passed to the "render" thread + // using Gdx.app.postRunnable() from the threads of HTTP requests + gameScreenTransition = new Runnable() { + @Override + public void run() { + ResourceManager.getInstance().clear(); + tankWarsGame.setScreen(new GameScreen(tankWarsGame)); + } + }; + + defineEventListeners(); + setMainListeners(); } - public void setEventListeners() { + private void defineEventListeners() { /* - * Transitions back to MainMenuScreen + * Transition back to MainMenuScreen */ - backButton.addListener(new InputListener() { + backButtonInputListener = new InputListener() { @Override public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) { tankWarsGame.setScreen(new MainMenuScreen(tankWarsGame)); return true; } - }); + }; /* - * Filters text field input: - * Max 4 characters long and only digits + * Remove keyboard and reset camera position when "Enter" button is pressed. + * Enable the joinLobbyButton when gamePinField contains at least one character. */ - gamePinField.setTextFieldFilter(new TextField.TextFieldFilter() { - @Override - public boolean acceptChar(TextField textField, char c) { - return textField.getText().length() < 4 && Character.isDigit(c); - } - }); - - /* - * Enables the joinLobbyButton when the gamePinField contains 4 digits, - * and disables it otherwise - */ - gamePinField.addListener(new InputListener() { + gamePinFieldInputListener = new InputListener() { @Override public boolean keyTyped(InputEvent event, char character) { super.keyTyped(event, character); @@ -77,50 +110,236 @@ public class FindGameController { stage.getViewport().apply(); } - joinLobbyButton.setDisabled(gamePinField.getText().length() != 4); - + joinLobbyButton.setDisabled(gamePinField.getText().length() == 0); return true; } - }); + }; /* * Move camera down when text field is clicked * to make the field appear above the keyboard. */ - gamePinField.addListener(new ClickListener() { + gamePinFieldClickListener = new ClickListener() { @Override public void clicked(InputEvent event, float x, float y) { super.clicked(event, x, y); stage.getViewport().setScreenY((int) (2 * stage.getHeight() / 3)); stage.getViewport().apply(); } - }); + }; /* - * Disables input listener when the button is disabled. - * TODO: Join a lobby by sending a request to the backend + * Send HTTP request to join lobby with game pin specified in text field. + * Avoid sending request if joinLobbyButton is disabled. */ - joinLobbyButton.addListener(new InputListener() { + joinLobbyButtonInputListener = new InputListener() { @Override public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) { if (joinLobbyButton.isDisabled()) return true; - System.out.println("Game pin: " + gamePinField.getText() + " - yet to be implemented"); + joinLobby(); return true; } - }); + }; /* - * TODO: Create a lobby by sending request to backend - Transition to waiting screen? + * Create new lobby and display waiting window. + * Poll server for lobby status to know when to transition to game screen. */ - createLobbyButton.addListener(new InputListener() { + createLobbyButtonInputListener = new InputListener() { @Override public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) { - ResourceManager.getInstance().clear(); - tankWarsGame.setScreen(new GameScreen(tankWarsGame)); + createLobby(); return true; } - }); + }; + + /* + * Exit from waiting window and render normal FindGameScreen + */ + cancelButtonInputListener = new InputListener() { + @Override + public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) { + exitLobby(); + return true; + } + }; + } + + + public void setMainListeners() { + backButton.addListener(backButtonInputListener); + gamePinField.addListener(gamePinFieldInputListener); + gamePinField.addListener(gamePinFieldClickListener); + joinLobbyButton.addListener(joinLobbyButtonInputListener); + createLobbyButton.addListener(createLobbyButtonInputListener); + } + + public void removeMainListeners() { + backButton.removeListener(backButtonInputListener); + gamePinField.removeListener(gamePinFieldInputListener); + gamePinField.removeListener(gamePinFieldClickListener); + joinLobbyButton.removeListener(joinLobbyButtonInputListener); + createLobbyButton.removeListener(createLobbyButtonInputListener); + } + + public void setWaitingWindowListeners() { + cancelButton.addListener(cancelButtonInputListener); + } + + public void removeWaitingWindowListeners() { + cancelButton.removeListener(cancelButtonInputListener); } + + /** + * Send HTTP request to create a new lobby. + * On success, a waiting window is shown, and polling for + * the lobby status is initiated. + */ + private void createLobby() { + new HTTPRequestHandler(new Callback() { + @Override + public void onResult(String result) { + try { + LobbyId lobbyIdClass = new Json().fromJson(LobbyId.class, result); + lobbyId = lobbyIdClass.getLobbyId(); + + removeMainListeners(); + setWaitingWindowListeners(); + + gamePinWaitingLabel.setText("Game pin: " + lobbyId); + screen.showWaitingWindow(); + + checkLobbyStatus(); + } catch (SerializationException e) { + System.err.println("Invalid HTTP response on create lobby"); + } + } + + @Override + public void onFailed(Throwable t) { + System.err.println("Create lobby request failed:\n" + t); + } + }, new HttpRequestBuilder() + .newRequest() + .url(String.format("%s/lobby/create", ConfigReader.getProperty("backend.url"))) + .method(Net.HttpMethods.POST) + .header("Content-Type", "application/json") + .content(String.format("{\"userId\": \"%s\"}", CurrentUser.getCurrentUser().getUser().id)) + .build()) + .sendRequest(); + } + + /** + * Send HTTP request while waiting for lobby to fill, + * polling for the lobby's fill status with a set backoff period between requests. + * Transition to game screen when lobby is full. + */ + private void checkLobbyStatus() { + new HTTPRequestHandler(new Callback() { + @Override + public void onResult(String result) { + try { + LobbyStatus lobbyStatus = new Json().fromJson(LobbyStatus.class, result); + + if (lobbyStatus.isFull()) { + Gdx.app.postRunnable(gameScreenTransition); + } else if (lobbyId != null) { + System.out.println("Awaiting opponent..."); + + try { + Thread.sleep(1500); + if (lobbyId != null) checkLobbyStatus(); + } catch (InterruptedException e) { + System.out.println(e.getMessage()); + exitLobby(); + } + } + } catch (SerializationException e) { + System.err.println("Invalid HTTP response on check lobby status"); + exitLobby(); + } + } + + @Override + public void onFailed(Throwable t) { + System.err.println("Check lobby status request failed:\n" + t); + exitLobby(); + } + }, new HttpRequestBuilder() + .newRequest() + .url(String.format("%s/lobby/%s/status", + ConfigReader.getProperty("backend.url"), lobbyId)) + .method(Net.HttpMethods.GET) + .build()) + .sendRequest(); + } + + /** + * Send HTTP request to exit the joined lobby while the lobby + * is not yet full. Hide the waiting window. + */ + private void exitLobby() { + new HTTPRequestHandler(new Callback() { + @Override + public void onResult(String result) { + removeWaitingWindowListeners(); + setMainListeners(); + + lobbyId = null; + screen.hideWaitingWindow(); + } + + @Override + public void onFailed(Throwable t) { + System.err.println("Exit lobby request failed:\n" + t); + removeWaitingWindowListeners(); + setMainListeners(); + + lobbyId = null; + screen.hideWaitingWindow(); + } + }, new HttpRequestBuilder() + .newRequest() + .url(String.format("%s/lobby/%s/leave", + ConfigReader.getProperty("backend.url"), lobbyId)) + .method(Net.HttpMethods.POST) + .header("Content-Type", "application/json") + .content(String.format("{\"userId\": \"%s\"}", CurrentUser.getCurrentUser().getUser().id)) + .build()) + .sendRequest(); + } + + /** + * Send HTTP request to join a lobby using the game pin in the text field. + * On success, transition to game screen. + */ + private void joinLobby() { + new HTTPRequestHandler(new Callback() { + @Override + public void onResult(String result) { + try { + new Json().fromJson(LobbyId.class, result); + + Gdx.app.postRunnable(gameScreenTransition); + } catch (SerializationException e) { + System.out.println(result); + } + } + + @Override + public void onFailed(Throwable t) { + System.err.println("Join lobby request failed:\n" + t); + } + }, new HttpRequestBuilder() + .newRequest() + .url(String.format("%s/lobby/%s/join", + ConfigReader.getProperty("backend.url"), gamePinField.getText())) + .method(Net.HttpMethods.POST) + .header("Content-Type", "application/json") + .content(String.format("{\"userId\": \"%s\"}", CurrentUser.getCurrentUser().getUser().id)) + .build()) + .sendRequest(); + } } diff --git a/frontend/core/src/com/game/tankwars/model/LobbyId.java b/frontend/core/src/com/game/tankwars/model/LobbyId.java new file mode 100644 index 0000000..a2cb375 --- /dev/null +++ b/frontend/core/src/com/game/tankwars/model/LobbyId.java @@ -0,0 +1,14 @@ +package com.game.tankwars.model; + +public class LobbyId { + + private String lobbyId; + + public String getLobbyId() { + return lobbyId; + } + + public void setLobbyId(String lobbyId) { + this.lobbyId = lobbyId; + } +} diff --git a/frontend/core/src/com/game/tankwars/model/LobbyStatus.java b/frontend/core/src/com/game/tankwars/model/LobbyStatus.java new file mode 100644 index 0000000..2c0bc32 --- /dev/null +++ b/frontend/core/src/com/game/tankwars/model/LobbyStatus.java @@ -0,0 +1,13 @@ +package com.game.tankwars.model; + +public class LobbyStatus { + private boolean isFull; + + public boolean isFull() { + return isFull; + } + + public void setFull(boolean full) { + isFull = full; + } +} diff --git a/frontend/core/src/com/game/tankwars/view/FindGameScreen.java b/frontend/core/src/com/game/tankwars/view/FindGameScreen.java index 3c4f488..bf5f2c5 100644 --- a/frontend/core/src/com/game/tankwars/view/FindGameScreen.java +++ b/frontend/core/src/com/game/tankwars/view/FindGameScreen.java @@ -28,6 +28,8 @@ public class FindGameScreen implements Screen { private final TankWarsGame tankWarsGame; private Stage stage; + private Group layoutGroup; + private Table windowTable; public FindGameScreen(final TankWarsGame tankWarsGame) { this.tankWarsGame = tankWarsGame; @@ -65,7 +67,7 @@ public class FindGameScreen implements Screen { float rw = stage.getWidth() - lw; Table rootTable = new Table(); - rootTable.setFillParent(true); + rootTable.setBounds(0, 0, stage.getWidth(), stage.getHeight()); Group leftGroup = new Group(); leftGroup.setSize(lw, stage.getHeight()); @@ -96,9 +98,42 @@ public class FindGameScreen implements Screen { rootTable.add(leftGroup).width(lw).height(stage.getHeight()); rootTable.add(rightTable).expandX().height(stage.getHeight()); - stage.addActor(rootTable); - new FindGameController(tankWarsGame, gamePinField, joinLobbyButton, createLobbyButton, backButton, stage); + //--- Awaiting opponent window + windowTable = new Table(); + float ww = 3 * stage.getWidth() / 5f; + float wh = 3 * stage.getHeight() / 5f; + + windowTable.setBounds(stage.getWidth() / 2f - ww / 2f, stage.getHeight() / 2f - wh / 2f, ww, wh); + + Drawable windowBackground = skin.getDrawable("semi-transparent-white-box"); + Label waitingLabel = new Label("Awaiting opponent...", skin.get("default", Label.LabelStyle.class)); + Label gamePinWaitingLabel = new Label("Game pin: ---", skin.get("default", Label.LabelStyle.class)); + TextButton cancelButton = new TextButton("Cancel", skin.get("default", TextButton.TextButtonStyle.class)); + + windowTable.background(windowBackground); + windowTable.row().expand(); + windowTable.add(waitingLabel); + windowTable.row().expand(); + windowTable.add(gamePinWaitingLabel); + windowTable.row().expand(); + windowTable.add(cancelButton).width(2 * ww / 3f).height(30); + + + layoutGroup = new Group(); + layoutGroup.addActor(rootTable); + + stage.addActor(layoutGroup); + + new FindGameController(tankWarsGame, this, gamePinField, + joinLobbyButton, createLobbyButton, + backButton, cancelButton, gamePinWaitingLabel, stage); + } + + public void showWaitingWindow() { layoutGroup.addActor(windowTable); } + + public void hideWaitingWindow() { + layoutGroup.removeActor(windowTable); } @Override -- GitLab