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
zcmY*ddpy(s_n%v%k&ux)Eu@*0iY)8GCo2>wi!ql^7vwURP<XE-xzvh^A}W_sxh2dc
za~pl|u_ER^wofUS&62xqzc>1PzrXMP+T*dk&g-1#dA`nho>QJC)|n<&Y(x^ZktsDM
z;dTKJPVKNU+kjWJCYc6pf8?q|obr?^NP2Pm_|J_Sz4T*BN_^j3rk0})r#?IJ=}k?~
zc76V@xpNn&U8UxGqORsuk1fAhdH~OvqIQjCjM<zd8|P)`+@d}|ircwKx>_wm7f;X(
zRCS6UNS?-HTjLl5$Qrlhn*#@6^RAuFTZBPuAnPuSBa2zFT3OlEa#4WcPcq2cfCY9|
zo}dtmR?yPexA2!<mg9hYn24H&M$mVkB;!2W7o$Zbt2b;ht80&+P}#^imP+x@G{O#z
zl<kKV^_>~L(%!&N{-R1N8*n0Hgar6-7~5BX%P=xf6z*7AKF1lXR7in@u~8&_KmZ7r
znZf^SSHtZ4|Jr4TQY78FW>`TT4StWlT%!$e`R@$`>>gqGJawu-hy?xq#W*dz@F-m4
zP^CJ#l_kWZVRdmLDqc3<EE@@4am8o-HoUz!N(xP)(9MXh=&*64%<2fDnKNLNnH9>O
z!>T-BeG}qOas>qMWe$+e!t^njm|B>Y#m3EmidWKK4=yeEPHyh;3_A5!!yf1+d`rUe
zQw0Vs_*FM+5eM~8=}8=WX^0|pzH*eff`;rG<_F9?1k4hI(+w~qi@SdDe#cWF&B|W8
zx2p(L%x{*ZsfE0oZoe)z0F<zz$C^TUYOl@FbGG5NRn4=@RrhiZW4Z4L2}OJ_V~Zj@
z4Ui|maQ<DvC4P1gWiOlN^^oAMio20^`y|LmQFvK;_RrB0H`=0K;=qpy%jBzX42m(r
zm6;E|jNvB+O=I_~7i3EeOKf?cZ@MQL>z-cEU}9kcpbFPXztN(tR2VmAA2Dqj7GYOi
z=QlsAgZ+?VAq2R&scxADP4rGHo~B#WY*`wF;)Kb#*XZPxWZb5^9?SRFpFBj~?Fd&3
z8oe5>(XBx?rq|yKBt-sg?GTV7MS><q3#e~cn5GJmreMg6g)q@N5QpDq-GTe}vKL0*
ztwv#ocWRi)r}5H$_R4OZzq5?OI#>QuB#~?9W&>;#S*0Q8R^oae_3G+DzzBlZBY?pr
z%{FSIE-Fkb|I<9X9i8QDb2s>7a59Y1)%;ITgfs%X^NeC|79d!3l(T57uHc3^ujrN7
zuEDs*3?Qx;jyw>wMGN1dJ7WCt&cumui?X!xG4fdU3H<yE-BcP|3+wziRuAGYe5nAY
za8K`VLLv9{!`1LTy+mO!t4frf^{8~RlD*1>u9{Bn49+_F><fcpz9h-G`Lgxz2Lo4X
z((#nT_k1D>tC~Xf(17zG@NO(+%+r<PI&+*#XKQ1f<Hxr`cy%ABSHBIMj$QDs$`Gfl
z?5DS=Lu4}`yPDJ5<8M)$H+dA`@S78FTLqpw+QOfZXFQ!=@g}xqoXm`~7}?jcjgKP7
zBl3e=2x1GeMYa3R+|J5<gC(W9U8NO?m^7!M<Yj<6>PL!-KWf2&Er}!VZrxl?R$%Of
z(H}l4Z8!T``CZJPjqc?BOtVftphN>~L?>OV=XxBqEyyTc5l<J-^cu0%9O|v*-_7Ix
zVWd91{=AIT{C2%UUwGzD!;7D1T4z2%2b&{oQ86v&yZE%WlNV`UPxOa(i`NAJ%L8M>
zfG{TiNTeH3sV8EwFcK5AHOp;jvXq|iUwVF5hpXD+aL9zV30409J3Ir4HfoJ&c{~=k
zz-8PSsJQ*t*J(<UR3guUU4BBPKwpsfuD38#Z1u(2DY9o;_ez}t1j5mx=-D?u)i;cr
z(`?qTdme@hY;Rz_kU?Ejr`Q6O#Hfp-5%v~|nPHjfp{(-1eS@~5HN5H?uHL4cO#UO+
zbb+bbji?KVo=LC1c5V5z#G+0R2nd&pK~;P`KKOWZh0t6Cbb7188mQdjk+fKeKUDE^
z2LjWB*h?J^GcO{;--^+l6a*axL>CYzUbwU(?7K0`;)Kdu7O(_xm8i=@+=Rx)#$aCe
zwZv#uNZDiY@H~82%`(BCu3)@FF3Gt0PaBh!y;meZG&gv-ArFBst)qcSQ1ox9318_p
z)DYGM_uZs+?JWe{@OC4{${XR$=?N`Xo$gzNJOU=3C0sy}%)>Y~ir*4h2#7HH(bABx
zn#TcbjsuZAnvz--SmL}>)=J!lcq<wjmyskfAMA$CbC%6atqVrO03$H7pzQ`eXAk_t
zlV&g7{P_gU;mB-*_u1bC3v4o*4h^VXq@SiOzUV~MX$lrz=2Ly;Cz8U1n5FO8zuX&q
z<Uz{(E?v8<k*II=N6sTDS-=d-xclmK4FXMnIm#`keWfS1J#ON2RN<5-vgGWX;n&UA
z?E_o7=GsKj7GZ!OmAdjZyDe3C<+U~U_~4R|*P@9zULk0?DD+BSg~`rmLG{4R&+hfc
zMsVY(CcjK2^8Ka6vAYXT(x9oIuSD2V@T}-L8JKffBYS85{?`D}9g)EK_31;nBy*Wd
z6;h^VAm8L@K;#rti4e&PvN;q!K{aj;N&7OrKSVN{b_4;1WBda9+D0sV)xtC5i*nDf
z=Qhv3B;zOsni{^|_Lolwv8wic{33p+W=4ZhUy*<%QQEW;gg7S~wB;zbK-jF@<>AoC
z{G^=(H|MJXILWg5^F}etZ+W9;dbNdBb)oVVm1JDbYZtUY1yc_5YUHawV@r{&m&QMs
zNNwh}wzP-@p%qA;kBI}o7UAs8qqB12O#c4nv`r~0f?JzBwiR#F)ZwJ;lO-rz^w0)W
z1RUgGo`skF>u$E$=fDy}ocKWc^O5L`TP<(m>}&vtr7nyYjykm3>pLncDzd)%lPF&?
zwccRsSI$~Isjw-U!&LS<dv6T@(JSMwtg777>Amj_2Jc#U$-SOsE!tCg{TNu{H`^w!
z-7xZOf~_sY^2I`trX+50x{=^WkW14QeH3wM1ZGr&i~5^TSrg6>a`dtygCeT|V2c0_
zLQ;BTO|@(;Zsf=_49n^nj2Dy|ZI93S0#@Z|YO$*+EOGQ-^Ly<emeZ%|*a4EaUFN)W
zv@&G_r}|yUgsVRuwis(qmlXkQN-sH!?SI{g5LK|WpJygqc&fi5D=SO48pTAsyq>iP
zqNTWf?~0824FDk{4?7_PqrdDSOYAI}?l#dV)kSl5?hY2!Iu8aRkmfz+p9JNF!{UtF
z54XV%P3~kW=Gb$>hc-<KV8wMM{U_C^g_{$$YX40GGmL-7_yP?p100Rh!p1ZlacHCk
zznOKv?(;bdN<OcUnYihxnaJjN77!3nM7c8M3=n6>yT=PjON)?}6jASFcX=VRtU~Z%
z;WN1qUPR?x;5}8Mx?J|-9`|_{`VWitwDSt@%V`%{%1Mhe*OF6T8PDjg#etaBfE~Ld
zORubI)cJA)*c<-V2`v}+O}uVy?#j8_mz9)I=yMqupmw3vsn$7i_RUERMcvT*yUzeM
z);<BA@>;r(%&X&`XI2O`lryd=L_d|%z%`OuJ8+9lzkD0@JrYqojko$tpPdz{AU>oR
zO3}|;*rNwQ;VGvSRqaF6Y(*y>xfYoiK!9JI8Kxyx$DJqse1>$?@Y{?2b<)9?{Mz@j
z?>zW}xl`)|FP@^RRY;@gk^x=unr(H+_2-rR1%`y=-YilyBwOhvc4bfsL86AlPkdW!
zeMJ=2+L)w|0$mj(g{igOU{}7|T**9@W9|H~VCWjrFnY0sl#Q*TT)6XiXZ~ow_st0J
z0^B6Fbh<2?Rx<ME;U{2&+cHQ-5r@!(t*Z>xzK=cw7XV5qylKFw(QJDU=R>x+D2ojt
zxRo|%JYJ)F@_RUvlVdeKo3j{lZsc8>`Zi9m9bSp<dO}YZq6>@fAAVG^a3X5<Rn;HF
zA(ddCb;vqfOVU#MM<z~mRiQl3)!bE_H`gmn94pRcOF`grxz#cfaXGc|zTH^Jo&ezA
zBgKI!L2O^UV^s#8=id8o)QH1VG%{P@uZiwUZ>cn@$th4V{}>QW^0ry6aN<~JDY%aV
zV?kx`PglhvByRt28R!M;Hr<sF>mxt%eWr;xAPl*#ozHw7FlKsnmwTi+$@tE1YCiSy
z&@B)nqyp(VZ&(i{Ch|szA7dv*BQ8G=7u>n{CQN$br67y>bC0m|)WHO(SaXF~P7&AS
zg2Cr$Mz6ND!$lkk8`Z#=NGmCBxBnP)ZdF3+7495e#2?{J?qt5+7@Pevo;&QTZ-9`Y
z^>FwL33ZgbigHlG$gDNt!^sEgJx`3A*8;a%Zc3cLd*e}CpE!C0*o>v<WAX{qmFfV;
zEY-VQc$R@KLdGuC(24QOj`*W<`y=2&*IK8&fjCclaa={Gwy@<!?D>HgkJ?nsIRL03
zM;3jbFHKUpf)m@`m0iOq3ahKDk;3-8HMJs+ZM`A!zyuyyAiEW2<7r<7NTPhdQvk52
zDP&UN33^#*!K5%mTig2{I7AF~bok39ncZJ&jEMZbaGOqLUMR*Axhp>QhYscKbG9Hf
ze!?zX;H=NO?xAGF!Fz~kd)Jj;nJcddODvdD|9{>Z-jPUtFBPsXy%lEX`Me6e)|z5u
zMg&|-r}1T5Xw(YfiEY8eYxC2r9=reE>AeG)kYn5E>-h>7zRyFhS|3#uK=}YpFs0Kq
zf5K@%c%A5xPSQihFWS%kE24EaYvTn-4rQ3O_i0|gvJU)3X>qY%Oq(0j|Fl1zc`n>-
zxA_xD?{JjKnM2kp?~VaPzZL|^SZ_@d-ar`Ml(?5ev=JAF=gxTY(qW8XxIpka(EJ;X
z&3vve5KDw&8=Y*Yw+8F16QOeu_sjY~Jfj^~9QYKP_)p15bVp)K2rUD0uenVMvgheK
zbU$p6L5Qb!8J|9tq&<bg#{T`=iX=cXvFknt0f}+?H!&`F+HdnVuE}e@DZ#11otl@c
zh;T#yagcQsl53pcTUAvaCHd^|nxMXyvjge*UJVYNXrAgCpH@-bRy!Qz?lHU}^@B8L
zhU6&GFgM2;bp%uLk>lsb-BO>X@cWLtC6_)&;t<jj^T5#|_TK$xBMW5Y=DkN=EReCJ
z54S)}4^2%@;`{pg49mkn->lcfSF)qW^A|=XyKr#keS}~+lx2L%twykKE8!YQpZ(Ty
zRt>o)QaegW_e}7s9YX;mp4F~zmcM<?XIUP@p^-OeQ6NcE!)+cY713;)00usl!jX}u
z0VV1c8v~|>FUo=3<ItWS1Wkm5MydXbbblbydY?M%Rz!)l=5CHEFD?I|TdKna+8(3G
z(&$ZfbHOsPH*=o%RfU*oK+<$Y{=-_oU2yI6Br6UYXG)ftuaF{V_mA#1IPZX7ZmoE+
zFb|+0-fFMIgcaqeMMYAc*?L<04<Qfi+Y0N~MJDZ6B*S>UJ`pt+avvp(F~iGy`}(kS
zQB$fSQ`5rL(R#e{-88Tzi_h%);OIs-78#im=4r26$*vdqg{CBxumu6T;YGGM$iyU;
zROeOjm8bIPk>edP_F)emaBehBytj|1Dw1=!zrQch*VKQRIa?~caCW^g#x&Olx!?u3
zK+OKFiDx|P-}Hq~-fnSgWA8OIe5C@l+am0qzm*!>G?dRe;sDV0R-bXL?k=0WDEzy9
zO;Oz25F^GaB=V0oMfHauPZ_QZu!$j}&&+FTb0r+vbsqssUe@4KgWZT(I`~a_&z`m3
zku%KJ3I~U>sAfN>U>@D`?9}wafvulp+_;@;cl97TdI`6u!u3j)Oi6U{dsp=>W;84K
zE-UG=$*T(N@c8yk;mfxM2B3p~M8(s-A4mWSg(fRQ+M~4yw4P5KgBo&6K>>1qRhHKS
zr1Q^-WLjKKSry^nbbwyw0u#T2iCzg$Ft&qS+H6{<F3M>HM~M)5m_7V0SRW^v{9HiY
zkI2+4Yn(gu#KT8aYq$N16VOB0c_pGP9^yU3X(>}?{E42zSWIN;iYUW{c91lK^{~p=
zq$`&wm^TempG@Fcu1Yk#i&IvhOLg<C9{QLM*)QhHGqBHsWQ;Vg`zI`+g-$$H*U{1O
zupmvkw=e3{&@HrUj<rvIRe7uLBx{c25vlgFX0Aism3)lxXJj`4Jq7fL33WFLso;>W
zrk7&eXe2V1;?W2P=4<C2cuNxjaHk>xg%Uc^V*?0gvQE}4YYP7cf}dmwSH@`7+*+5)
zsO5d>8}@uz&)wO(F+Jd%WR%d>6?E4A`@3fv1{^1Uye9qOPj?rc?v4z|Z5^$I>sc%R
z=+Oj%XkLdocgy~O;S2*f;?9XDklQR~JV|a<=K<<B-aDwhy{kSCq>yQ^SM@+YJQbrm
z$XM;Nd%o6}|FOi`p#VXOv*`LO^Yiochp}~Ti_ADE6e`<Fz2+!b!F5h|GiLam9*Z*%
z-;Dq|g~AcbI_vd>J4<akm0MWS`=@dLhSY=m2ALVUjK<)+n%+&m*`Qi&f&dsrK6a5w
z?~bi^{H*2uWs{lQDFKx=GD5PA+X`w#a2l|FI_mD|oLiXSj0$^8VO9nnL<h8z{Yv+B
zf}@1=WMdPeZqWrUNFI+TxrFjGSr_*duY>fCJ#yL^3V(vMOr?FjyLJ9x>R+MLHTmY3
z-;~+g*+81yqa6l;aju~tSr?6j2~$t=vUEk;N$Z}s-iopHep;n}p|Tx(renJ+H)ZY*
zCHqzEYv6$c3|mudNh(ZrgD`mu$8K0+a~SOLb;O$7d>{c$>#X{Ux=>kTTjzBT28R2x
z+ugj-{!R0BZwSU%DzL(%&IsGm%ED-Da4rMX_xlgNs@>ist+h_X9@AG3mPl5*iJz>9
z5ne5Pdi(^dvdgE$mZ7hH5|VR0l7CZ}Utf!`N2{6Cs+c4QE11#gxp_al*0DM<3UL@$
zg+KO7bVm8MQ@G%}K!-WYmd~g>WR+z_13$}!Gtu7&ouV4ePBCuOXyC)`cnDP^5VBtX
zoh6}34uq1Amt6*f%)FYT=yxXAm0ed$h8X%=Fum9K3p<6a817!_>FwTmF)=Y@|KXZ*
zib9^TPS?!Z@<;3MSTRd)N!+D!L;uRRAVbHRXL%X<n!V6V(+O_^guZ)4Hdp)%ayP=x
z+%AH?9gqVarRN2#-+e#<daLtPaqnf9f55vN`G4P;fT1sr%g#xty#I?XT;@=7a+_Qs
z6}p+*J+Ef0wa=W#rpga)-Ilmmcq6q<<p77^k0&(D<O)2P&ki3DOk$wFl1++0KacSq
zl76r)^IFz#deTszWfFos5Uf}W;CoIHso>V&_868=i#`&QGF(#KAo2lW_(Gt3UjY(z
za;?ER#Ks)-PQsDoVZE!XrMf6k5_&fTXM4CapVwPw3FqektlyBKB2ka@gSg(-@-~Eb
zrj=~o4lf1wNJ~irnc(K_=9pz;gN0?xJza=~iS)Pa-u}S(QNSnx(1TpfT#M*^z3Hoz
zeIkjU&*6GeH`>O<{JqxpJ4!03Ilb`@zvM{A)Y?yU>a;cpuPZ>r*4uCYDYbTN`pwuM
zNMuNA{rN(b3cdY>Tobgcip8Aac)_pDb@}x*2*dBs|9fJWS`9WmNn#v0hg(6#w6(`h
z{E%lvsb~b>kzL+8o2&py4vGkGy9oJ(S?gGrjt180{*Z+AL=<av#7>yW0AY$0nTw+(
zJqsmV`+x;G2)1%?eiKOqvAX#ZzoQJjkZji?dPM`7+;VIAnu^9rKh)eY=V}v?k)x39
zWuRn%Ca=W{m>f~NjdhlW4MY?NY8>EzI!rpZPJs129R)jN7LNJH-7Og&!jSrpWl5-8
za=+K}YjAky(RzLwl>E>Hw!E_dPh6gxVhx2chkTV+@j)?vZiVpIlcyrJsT}c@#+pYW
zi<c7yqTm`z-g1-G?|0Llngd}8^TXG;_(t%aZ?n@qpT(?p{!0_FmimV|-R>S}BlrlY
zV^Zt+kAmC@c^%-!Hw@DBj33?j3_L`{0T~~qNVV%KT4poUA-N}p7UY5)0aJlWSk@1X
z=cgYyy|S=G&4r3sFm}C4&Dk?l8?_vt29eAI#?~n*)^I%~C)(3~$Z62u+qIXuqnZW1
zR`e1oa#e|xbKa3FV9*sjD!rE;t)XVJSq+ID$G`_ceV4LyD=O>V4!7kW^W=a5!>2~j
zpJbH{3&~&p@whsVNf4%q?>e}S!?@X|y9lg``CP0|feLi^wH{>rFi^vk)bd7!aR&Qr
zW5Llt7O_@_zMNK*#wNe_?%nU>-J$^bp<c648Imd%g?$nd7+9<{^+#liwGCu}BRsTT
zEe|%<lM&eTyHUCDvv<U{DbT1XkYqbW8jvsPEng@7+o*!tq;+ymN^h8tKWOy6i=G|?
zcuf{tuz_H)LmGHH8sNF%!P<D~yl0a?0^9kcH+9?w94K`vYLSAj3bCdb5pK!md+&*x
zRy)y$nGW+as-GR#Rl$#QYZdU4t!dhQ1UAuw`TB7lcv68T%xSX|w@%+a%ri<{p>T|t
zH4&v0BKwaLDwV(o&IvI4)KAl>3Xp^A^J9TB)F>H|uRd4yq|+2!NYwMC!CwC`O<Mop
z^fuAX@TOTcM(E;<$B#JPU2sRkvjY~HXGVpkS^T$8#4oj4jtT)g$S;%pQO4DS!bU2p
zmQQ9PTk8`L*jBBj?(c-YF`s_#_`^U7<><3fl${86n_R`x%di#pn2A)N^8#np6KQiu
z!Ejjg%6<?g=8Axkw_#T@!rS<BR-P2;`^CQb@tlYT)}hXDs}fBs;p_zCWK>I*2ahXG
nO8MMoo7}FE+%B!$(JB;6F`Ttk)9=)RfFG=ton_$>_Z$BQmbvoa

delta 6988
zcmY*ec_5T+_nxt9LRp3=B%vCl2$_s5F|w4%l3mLyF(eG(nJg(|i;BupUM*CXkU@-X
z%v+Q~$To{4O_m86X8S$p{l4!v|IXa^ea>}X=Umr)rZ-RUb)I0QE>7S$vsw@5wjcO<
zVvn7%h^L$l&gk;)yL)aCBCC>4A3JHeJ6bqd?{@3ZP_4^r3Pbvec!Q8vg3i(Dr*%CC
z*ad}S_on*}jvb-+e12M^8q6;+okCuSiAmGjvD<(nv7^l+%y(##s!v;8#f=bbNAD(0
z**clN89z^38rB>ga3{=V4-~TomH4rMXDG1vse(S0Vr;s=|1w34+rH9Q#6PeUWlz5c
zVOF*}qPR7~cL^}+{Ct*$Ye1S_u(Sk_zIMXHf1u|bJO@vNq8d6P7GlvqL@JuxvG2<_
z)dj$3b4$jx%Ey{xI4k^D^UX>=3uJoi$x@ahjnZ>@>O(=E5J4#lYRTjSWEAh#|KE51
zpQ9K4-@eKf?N;;m3*~de;UU!}<NL;=`BN@Tl{y}|ksplVUSRUSTp$*#8di5KK;F$@
zo#Ff`x;w;l^GAh%1Dc9FMU$l3iwAv=z(ijJbi{qQ$)MMt0+^%k@m-%{m;mR6Qk`S(
zLs>)qa!kxJ-*zO5KO)P2%j5BwUJ&L<9IH<u!0w4<yH7w$B|In57G7u^N<DmfbS|Hu
zQ)uxL-a|ed`k=-ei3$!|$v*aYZ{-9O$D9QoRB*aZa#G~IvCObb$1k0_FSNf?A!pC0
z`N5%`FGyb><|JQ`+&+{c2P~UtmgA>RjM1tvZ3~|b$|-}%>Jqjwxm>5%85KM;dU7u>
zhvm_7`9UE*I9#9wOi5!SrSD}~eqpcnV7FhU#bA~1NM$bV{eU?$>foc9-G9dA8kZ#R
z^V6&G_iyK&rkSH8;70Jx;M7bFA@Bz8{kg#_E~AyemM^kM$@<VE$jo<%<od4gVd0R*
z3VHO8g2fg7Ro$k=U(P$@@9L!T=YKq}APKcJt<sXVH!B9DxgXld#mY5V5|(A(I*|n%
zY+rcx8=Dp_c|D0u4?30|v&$ZWbX#2Dd&%*4HcO}$3T`10Wgs99dK+tn@kzjTl2mT(
zS{C#M=|V^C4DxMq{67TDXcW5l-Qx;K>|#15KB#O<(c>;Us^fLXA!Dd7n1Rd&2P@6~
zIVl!q`~XK*H7wVqt@Qg}&bKJc@gx);(#Y)!P(^!`mbLKcc-<$d^X}t~aZmf`sGGr;
z*J0dX22kt!>DJ#6+T}yg%%dTmK5Yx7q?#iDQpJnjAa78QP!<K_Kp>%^Y+^oSe`j9n
zAN2jJKE0+=Uv*}%i)04nu)Lc5b3HrOx$x~yd=c3^yGGjO+Ke?MP4B$LAGoUss_?CN
z>Z|J+?7`jTCbX44`rRWy5mKJfHgn%~HBMOy-D~UI1GynO%YA>4Qn0tfv2RknWpeI_
zN)!Yc`aeO}`iC7wsKHy$N$gmzIPMwMw(yjs9c~(}3|6~Q=3*mKBfI3o4nTs6YF-^)
zyT5V`sCUe?98m-;i3S^ejzOeYQ=3|%+X)1a(4{CndmOAc1k%;x!-1*WQ^it*^aUGY
zSAZJ&ho>xGPrNc}W_aXOG7H&|C$vVRUy_i3w72<xv4VbXiAlB+se0%plxv_qnww83
zcgL33<XiRZ>#<M?RhlxW->jT#zwD}uDv-<74V-Y1fe`uJxgs_-QR?X?klclU5Np0&
zs_R^A<n%%hN;g5TGtchkbz-OFqbRpa`hI>@S~?XNLX&ZrSM~fGEh*&-g(`HSk6{Q!
zO(VB`;(!LClbtrA6IfHs(tYRO7$eyN(|u#)lGd|tP~D8X!cQTtyvLNyZ0ZE-X7_sC
zjQh3f!SMJ)gr5=ALzA7rQbF>R)e0hJJUV3H=E$Lnx98@uX;2trC*2gSc!y5qhdAdz
zi0rYs+1Z&HN4c-~e?UNpL_T5k?!K@K=A*nhA)H?4S-TBT+Y<udaE0K|FN~Jpcj*ih
zSP%Xetx6K<W6v^q-Vy9BluMe>b4ENQ-(VvGD^nSKpxTE+lLE9PrKEaDu5;R}!I1v*
z($ssG$G|31f5fq)#B!~1Y!u9*A}l4k3i39YVGIKR7s8GOI@7fd%Mt83C&P0XB=Dlc
zjIWV>wcHdidzIzUi6=Qz08x&_2Rm8R?C8C{-SQ+fgvUiJHS(h-#`t-+S-*fTUE?bT
zoeF&#>m|8a8lM_nX|xSCJM{)@SF4QFy*7n~^SnWRydmT#vBxeTVhqyBc20A0sQQ&-
zE`M2n;=a`^>L`*Lk6rM2lDc5^7>z@5S9DJSgX#m@=6C>yNuF*^`m7Ful$W-Cvwm4_
zp52Mb@7zLBQzRguY>66;#uhrZQ-bkbMe?}^Z?eY>5upYu%GT&6mvq6usTogaa)q(6
zvph}(z-3tH>hi~&Z;js}Eb((3O`~sza5H;&<U`xmIN3jMctz~*)`S3Wg#YS|5u~p2
zt|j@N9ZUl~DV@J(e%%kP@8JGag4bM3A-g(ZoF`fU8`-hxM^~9!nx#xx)Uc5$X!ovE
zK5Lk$OXU2T%)`wO00C*!`%7t<?^}-HlWims@x#<txvg;nM)OD+pX1LvXLimys#Jx#
zm?8&yi-(?k?T84@Diz0Jr;aoyVB~TjVYx?T`Bmk4*c)w>GyHVDC+#KIiH|H4v?EuI
zE4Kg_6T7@cmfa`X!yRJ1BCaviL&8<6Y+S%wHHg0?-+7&FVn&vM+*<s6HBef0{7z=3
z;8&~$gmhryHD8Oz5KsBBORSlV0fk><UHFgrTANyqY6dGeYkX)k=jn6C+zIw^oyE@2
z$>GH`|1*_huMI6)xkeJ1u2KbgkQ;y<oX+&bVKvN<`4|ia)inkQ>jGl?%#fG%n=TY-
zZ0C#;^*Udut^*+Sv%j$;;`jwx&|e_=;yTrw8Ki~3<0onXIQ;#C8|1TaWro~?`;Z%!
zOdr&i%3q(XiDk`d`|X7g3%C|g3aeJ{6E8<I%lTuVt5{@3^0hdCHZNW8yf9h<ut-t$
zO&Qx=e&%9gMTJ#Zv_)I-DK07eX}n)!C>-AY@Ag6YkQ>xTI5K6+mb#y0OEaVjb-1=p
zg}B(eFEhZLszl)ogjK1a$YTN(&8r?u7<xF5X>yXKqO4qqZv%cg2%;81wD#FvWGNjM
zLMsC5@VkoeLdTaXu+#pu=Yh!c_SjL^v)_cw-hoMQx6u!>i{gHNdHidtskezxfExdz
z3uKrRV{%cOc&tX2;UwUbK|#;0?SDF%IJc@4(4Cs2$h$6iG+QJ&x#T*2>NKNO;Bzrb
zO1Vn|DZk@zxPYoOi9bH>9X2yuOaB3Ue*W1Dy0iez&=l@fZ&l|JgD`w+W?zxFwl^^G
zp3c_gJscVyztew~@ec`a@_2?``t)_GW>IX}Z*|?10pA=q^7wHCo}s8x7#@*g^-P$5
zao(*0t48e>$IZ;lyxQ_bN5>QU$_U`aGHWa1npmrYMaaE&Kh(~ZVQ&Hb;~nZ|b`a!J
zKdFpG#D+a@(3lw3KTlgHX;#U&U)Zzv?So0bd};Nm#YGH;V93PYurtzy&%4h%Ks*Vd
zkM<ra{%&+%XsC2N4hir$^&nDs`6#I_`q!iG&yd3xvEPeI29#X}hX!WXz9_#vI3W{e
zXy%Czvsbnk1F+UO_l@#WFO_$L*oyn?^H9b+z@LIGA0+B`>B094cTP?SnaL`g^aMlY
zY@0K>D@+bQxAQXbk<G1|y=NMi=^mUP0Z{9lD`D>y54t+FQ$Fsr>Gh}@$~jL9vc?Cr
zzD>~AEDzEmtciMb^MU&yL(SEq%Mcu0Pk<0Q-sbb;-pjG8)=@$oWN354`E=uh>vUHS
z)OH(1<Eaev!t|?_b9yGnMyJ+XU*lZ|JBS@zy0%Fh!^~a%aWW#E-eB;p{|{t$0`xR%
zTL*KWM0j<)!tU5N8|C0B-{W^2EMtV4ZIsd`NpzgdU|A02{CLTA0Bi+WC3pO28k3!a
z`59MSI$r%X_OBz;1{>6(JAelQA8Keod+UT1`tsI09Oy0({Uzm01H#*ez25GXA)Ay_
zJPFE$WO!MauPp4dddsJ&n_!2S7O-$4x==oZE&8^wA|U~af2etw+-Bz?+N@)U23$$|
zU<3c!w;^*TN2%xZoWg)37e$=Bz<c~CMpjBB4I^sfLkaA5E~Zsff#H6rgygIyRSfC{
z_}%+EYx310;Y<q$PuI4E`Xu@n*XC1lo2(sWC{2SWoNW|SUyiYva@WG_F~Oj9Q(X5#
z3F;9Eq`Uo5u@aepIPBeaVeQ;%aqQ;H6RXmNJ-cNA?lPs!)NngR+tkI*Mlt_~M|t^l
z0loL}aRk5n$+~Y7LcdG3x}$Fy>y;@C>w0aaoof&|$_M>N%fg`f(iecj%{_KDXgPi_
zFDfd^op;aPab2IYZwte;0ud|^l>_j+j^Zm@J%NFbVSMoYqKO2wWkEIJeBn=<oA;{T
zA@S=A3dYnF!qwJ=(Z(qVkyoNeXT;xd^Oc76S%tb=khu)$!Gd{C?THS-b{+50`ZfXc
zUR?0Z&tS|P{LF~S|3vGd2vZM~nLn&>0G{7r9<kMPAS=`dw7lm5TK&a6sQ`wbd+Zzi
z_biR{vf#gG_By%Aq90rz>^M<1f2mC^@}#FXCG=`IxQrJ476E#4oN!VJbYI0ZezJB(
z2zSqRBgvZmaP!_L^jbYjgyjW4zY&q#Cdtt!ql9MALY<A&hgLSR|0=^}>7UJ{4+9x!
zNPvU-nl%zadx*hQ(Gz)MVhGzQ65UX~d<wzgy3iuK@noXKNscI7D*JlHed@t)mVby)
zU-kXg73qq5Rlc;tQt-C{t=xS1%;{8PsA_ty4CMUb4d{CBxqY=cyrbgLqX0b<Dfowf
zf3~s}5(wm%WkQUgzEA!T<9$c*v0i_yeY!YoG`i~H?5>c7H`uh8jueP(fb8Ivh+y^>
zv&8k+m@4{|TgP9<2+4u;k*A+&28WKtHchs)y^+wlx@~j{TDmD|W@fCt6ugqGv612N
zJqdc6f6mwu@TqvE(nev^Y)S(_4hng}@=#Kn?Q5}Os^mbfN`RPN8~*a8{#AALnM-qE
zY+370SG}~&+{(w#uM*L`3(Z+(bObzngXIPvX#Ne-2a~0inshu@ryu~jGmuaizdEfM
z85M>1?Y{YMtfZS6l~^|+JR2mb4l_?n*Bk%fMnyfK6E>!#p33((36UEgZt773xi^M4
zxw^K+kJUr}B3(ms%er(JzInm_mg(71L%QCnt8a83cc)(PW<_$h<7TE5H;JPysw1W^
zBO&|k1^&a@VPAy#p>6ol=*m!KrCE%OvUP2Hkn_6|?C(2smdrUX2+(WfJjcTXd;Qu6
zu?HQ(T7B1CbGvfs0KCS2!$;DqKL5hLx7C*GAu=yjRafT}3Wvt;YevO3Jj?12KS$$h
znX-x1S4-a%1_-o_@a-)&b~%<P_&qvwTL76$Ig9XqPzf3Mk^{L$_UM{0KxmgLigqTp
z(W~14&`noQ7z)@V*3?$Z4~CBZCNeJY-@n-Cr6TExldlP!Dr$Vl0r>Nd1lMZj(ALbR
zyW}7bj}r!x`C7c^a}v6+n~kSP$tAqhpvdP9k<Wv^^za_s+i(;TBBC8$6RRmV*frB`
z4?S3Q<caS>W>MI<<FIF)*KXaU1E_ivm$NoMif+<Tdgsq?Xh#$I=zK$H<7(PS`8RqI
z>BTUO8FApD(wp(e#@ihsmJYI8N0<)&OJR^Aan+?0vENW3>^hbqY<={S(0_dU{BMc6
zI`9>>x(WX&LYD8z4`el3w8f#Z4*il9!1QKK4TzcyXv-6!U4Z$7GF}QD6hVxkQXG&>
z3Y~(;TJP~rm{6RF&}a(yOqo`P7bD!lCsx51%Im$O8E7QxV#>@l{q}w#v$Msl3f>Fi
z>7T#u%0PP?#xjHS%HLv#f!F~Q$b)~?Fo+DD`4|eEP9Av_h%jx`Nv$t8rM-?!XV^Pp
zGy0WBL`si>5)1N>6^VO0C!1L`k%22HDA;#KZ(C(G`(cAbbc4;Uey51wJA*?cu&h`{
z4$(6OCJoA&<!@PKMC>q8igOUQI|tRS3!Tluw8H@@faSzoWnkd0V0eTG#_0?3D_uVD
zxu#|B$=@?0mlV5CY>1JEv7`t19D1Xr<JjeH^{6W$<{81KJ}eID`Yti}rn3oXm{M_C
zBI7ZPm;L}i#4e9T;U5@c9OPixoxANJAd4+qZe*`pE{{Eur^y*?;#;ym@YyZ&l`8n6
zP?4Dp((#n)x%bQW%q}kVwm1YXz`P*KW3$;)@63HGICJfZiHV|<lp}kActjxU2V*6E
zx{|YE>pqX#ItYZS`KoVF>7|h9Gp9kfORp4y6T8-m$svb9Gd|^gDW&$4q9(*2SCq;t
z;ml3_X!9-mD+|!Sn36B1#rFvd4Q+YSzIo@mqbT_hx_|FWCs$CfOu!M+nq(VJl3%+k
z1zr?T75VMwFHPlW!vOS>Wy{M3mK3vko7)B>zl6>2c_|39hZlFzFO$>Qg<W3{W5@e<
zy#73_1+vM1KWb0N(lQ)C%P8MX=698{07n_<d9#hk&$YKcsd4a<t@ivj6vhv~=PBCr
zpn?4Qj1mnipgPXXgmRM_B@zMjrYOxhfg6p<-DVqbzDp3a3mm&FU+p=rLDl;wnL(@X
zyI8qn@R`d*i$1OhoZ*e^r{<OfoMWm;3BDZ8P%;d0rbzpXtnnAPBIXpd-Pty-MJ>nb
zdokfn)dX*<BZ4^^SUdD2=yrxCq}QxDzK!4S>yxdeEUD-tp-AGJ%)M!M>RZ&?!0K@*
zWY3ZWzI$EW&yOyT5=;wCUW$%Hr`s}zdjS3D5q>mkK{pU8HSzptg%8oY!xHBsWrSPr
zxPYp5lHjq@3b(VbwL|Lj^(LC9I+wuHn}%kCWK&h<n&LTkfXSj|MG;oEeqU>AE7=e;
z{sQE`!ma7(jRy9u4O*-(gwP*>*Le;ogcX7HhyBnm^;1wEOZptedkMP8GttJCxh2sZ
zgMtGyVqNxqbsOEj;HDs85vb*M>9zLd`ur*y?_1}RaN)rvQMQ1@@YgAR+_F!v6I};)
zMy5wv?})>?YWLc0-)w8qk#NwpwQFszqq{!Pa^hEFKC12jeisSGpzjZYasJJ+ZCMOc
z>zmj(%AQ7bR6GnXsNdwH6SP)1p+`CvN_hSN>QO6tZpe#<^Uj8!1k(Ny$2D&_r_#38
zLj7Y1vzx*{)d(+kQcb_ME$P$hKkcua`{-NJ!@tTVmb{+5n@W&rSv1-Z@Kb`wz;3r2
z()cf+?TPCQ?n!0@wuKk_`Kc5iwUL2HDT*v)>({-SC(5tQ_l~T2Eg({C*mrwB`T>sg
z9f7zc-+FEdTBirPaCjk1LY^(N<h?4^vP<&Y?!c{}g0zU^pc@_}SWEI-iG5q!6*#=1
zi+aE{eI!R|FiNpdR6>OfUpgHlM(y_nGg|!k-xUgy<0bZ;0!s))d~WNxZ*5CgZZb>(
ziOl-W6mD9~TX6`w9{FbSAY`>@UB_#78u{j2NfKrt-L_1%E#+S)Hf|cdUOn#?#2H$C
zX2=o4m3Yox6>WiRxP@bBf(Ox01E8RBc^lBqJtC3$^pHGz*GsSakoldsY!Bv6oFIDr
z1{HTocBpG#Po6=#8A$;<mgf-2>rTG>lG@T{;fLGbs9fH!i-t8Yg#NR0lAtbnrt0Gn
z7;O3xHNHEMY<oYMd*^gHA&wo%9KSj@W3YJ*pIpq!W*l$h^3sJw&t(7dXI_QFhvG;x
zrm#$Wjo5E5k9+hF1tui+91$q@1CLpV0kDkk*NWx-(B$E@394=H#mfDK^00Z(adc7R
zj~iF&o|-CVWtu?zKgJXNPJpY7UZ5Pl3Yk&Xr>hAfHw5>f8Lj^H+VFYsD&$FVw;MWm
z_62+5Be|5mL`YGPLjUpJdm)r&uB9DBsBdANkq@D?7PJ!zsCqWLFQl$U=jYsQFXlwk
zAh@6jX4ZS4EQ-&Vx>-4@A9s8MH8IJ%>PsrH*@%hX=(zS~$1k!KT#hxlZszWXT)`D<
z3Bg$O#b5iI*eWEzP9U#v1Ba#UqSo2!={wlwwWresF`s21ORD1-p)CDelc?FBPueIz
z6kjxZT}OZHk-V;s9@hV<R9iD4j$4%U=6E)AU29giKWt<)gpb_Sj8@lx#4ZD>>)rBU
zA&qLCU1Cpec7i=$w+Iw81pPFXPIOMv7xxwUv#Gdbq&m76{nNyt4&3ygmpL+!&tzz<
z2Ck$eL`vr==qvODH)aekw}~2Nt509~c|78Y3^>U-<0HqP9)Z$Zam#LEHp50KYf3a8
zw>(ys{?}ff3Mw8+T12dOz#5J1-8R`Jq)N<ZyHnsw(5>{I(-bIqFFnV~wTky#&c%$s
zaUUgw{XImXRPYddl?NH6bc#4mS;GJT{{>1oot7w%&hD3Fd)NnpW0MYQ{B^=Es;!;K
zZDrP0Xdsdvlfh`<tW+-MV9BUWeC$&qXRd6XPtuJ)X)mCEu6Uj=stJj?-#;JJRLFlz
zfm66oG4hkXR@v(3ws0>lR{RV%n5pS_NJNkB{1}>xE4g*!47bmO#|*zObks;Yc6lw7
z>pO6#=h!IrI8&&l4p>Ytg{G;aJ?s{r1`4RIy8muE99URsl%?-|@~8DDfV;YMio(3$
o#xa5V=6vc3%%OHkDCAZD;^U-Rv$MJ@84&P;wsNp6H}_5WKl7RQVE_OC

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