From 31f5d010923ab04f72df949db3a763db6cf9728c Mon Sep 17 00:00:00 2001 From: Alec Date: Wed, 22 Dec 2021 15:49:12 -0500 Subject: [PATCH] ending games, style low timer --- client/config/defaultCards.js | 5 + client/config/globals.js | 9 +- client/images/GitHub-Mark-32px.png | Bin 0 -> 1714 bytes client/images/GitHub-Mark-Light-32px.png | Bin 0 -> 1571 bytes client/images/clock.svg | 2 +- client/images/email.svg | 1 + client/images/info.svg | 14 ++ client/images/person.svg | 107 ++++++++++ client/images/roles/Villager-2.png | Bin 0 -> 3412 bytes client/images/vanilla_js.png | Bin 0 -> 218 bytes client/images/x.svg | 14 ++ client/modules/GameStateRenderer.js | 103 +++++++--- client/modules/GameTimerManager.js | 32 ++- client/modules/ModalManager.js | 2 + client/modules/Templates.js | 65 +++++- client/modules/Toast.js | 2 +- client/scripts/game.js | 83 ++++++-- client/scripts/home.js | 6 +- client/styles/GLOBAL.css | 99 ++++++++- client/styles/create.css | 6 +- client/styles/game.css | 243 +++++++++++++++++++++-- client/styles/home.css | 18 +- client/styles/modal.css | 4 +- client/views/game.html | 27 ++- client/views/home.html | 13 ++ server/config/globals.js | 3 +- server/modules/ActiveGameRunner.js | 2 +- server/modules/GameManager.js | 13 +- server/modules/GameStateCurator.js | 46 +++-- 29 files changed, 794 insertions(+), 125 deletions(-) create mode 100644 client/images/GitHub-Mark-32px.png create mode 100644 client/images/GitHub-Mark-Light-32px.png create mode 100644 client/images/email.svg create mode 100644 client/images/info.svg create mode 100644 client/images/person.svg create mode 100644 client/images/roles/Villager-2.png create mode 100644 client/images/vanilla_js.png create mode 100644 client/images/x.svg diff --git a/client/config/defaultCards.js b/client/config/defaultCards.js index b24fd81..9e25dce 100644 --- a/client/config/defaultCards.js +++ b/client/config/defaultCards.js @@ -33,5 +33,10 @@ export const defaultCards = [ role: "Hunter", team: "good", description: "If you are alive with a wolf at the end of the game, you best the wolf, and the village wins.", + }, + { + role: "Mason", + team: "good", + description: "Masons know who the other masons are.", } ]; diff --git a/client/config/globals.js b/client/config/globals.js index 23feb1e..f4293f5 100644 --- a/client/config/globals.js +++ b/client/config/globals.js @@ -15,11 +15,13 @@ export const globals = { KILL_PLAYER: 'killPlayer', REVEAL_PLAYER: 'revealPlayer', TRANSFER_MODERATOR: 'transferModerator', - CHANGE_NAME: 'changeName' + CHANGE_NAME: 'changeName', + END_GAME: 'endGame' }, STATUS: { LOBBY: "lobby", - IN_PROGRESS: "in progress" + IN_PROGRESS: "in progress", + ENDED: "ended" }, ALIGNMENT: { GOOD: "good", @@ -51,6 +53,7 @@ export const globals = { player: ' \uD83C\uDFAE', moderator: ' \uD83D\uDC51', 'player / temp mod': ' \uD83C\uDFAE\uD83D\uDC51', - spectator: ' \uD83D\uDC7B' + spectator: ' \uD83D\uDC7B', + killed: '\uD83D\uDC80' } }; diff --git a/client/images/GitHub-Mark-32px.png b/client/images/GitHub-Mark-32px.png new file mode 100644 index 0000000000000000000000000000000000000000..8b25551a97921681334176ee143b41510a117d86 GIT binary patch literal 1714 zcmaJ?X;2eq7*4oFu!ne{XxAht2qc?8LXr|_LPCfTpaBK7K$c{I0Ld=NLIOeuC;@2) zZ$K%a)k+m-s0>xHmKxL%0V&0TRzzznhgyqrIC$F)0{WwLXLrBvd*^wc_uSc%h%m9E z{W5z3f#4_!7RvAyFh6!S_*<8qJ%KOIm?#E|L=rJQq=gB5C6WLG5;c?r%V0>EmEH#X z5eSwPRa6WXBMs#$5H%GtW2go-in9p>zW@UYDNNWc^XOXZQ? z1QjEV00I#$3^1wQUJ8&-2UsjB-G|9y(LDhMNN3PM{APL4eYi{(m*ERcUnJa{R+-3^ z34^A6;U^v`8N*O6ji%S@sd{fJqD`XFIUJ5zgTe5^5nj414F(y!G&=H(f)Lgzv?>%+ zAsWD}2qhpH7>|TU`X&W6IxDNuO_vET7|j5oG&&VDr!)hUO8+0KR?nh!m<)a!?|%yG zqOwq!CWCcIhE{<$E|F|@g>nP6FoYr6C<8>D?ID9%&5J(4oSbR1I^byW*g@__U z4QsF&uJSEcFeleM3~ChjEQGbHOjsGDMbyAl(p=Ttv9RaVo8~I#js@@Y9C^_2U})yn zzSHU%6FxuY?d;&65MyR({^lU*3$z$ZllDb(o&<7d;A_`h2U+3~BJ2Hv`{W}KEU801#cv_B|9Cm!ynR{S`AMsSn z;7E=B;mb!wx$L;S>yGXG^6=&WlQn9$s?&L%Y1D8TI^MlKB1DqsEng$>f4=xYWBoPI z_S1p!sJ#d2?YI4kPA{k}Eby?F=f-J9zIc`YDl^pzjVm~9ebE?Hn?t0Nx+la|D0MB; z9)2xv1G>a1|A9kQ>~DV<=X3-4yC&n!m8-3K#P z{X@0zRuQsy$+N ziSCoLJU{Z$nQy4A4Y5UJ07$5FA~qL2%Q+cLaqDU?Lz3?=BC5;Nk6BbTmmceEaM>-Z zi>O&-dSE=%ex;vcvCOk{*JQ5^_4M z4lW7%l9IqY(z7pV(?I@@8=KPFO82)O{VDI18-*d-k$YmI^XiuPs_LuFw<^ZcD}yP5 c*NrbeloN*74g`U%%F6r~k%+>C^#XapzmV0H-2eap literal 0 HcmV?d00001 diff --git a/client/images/GitHub-Mark-Light-32px.png b/client/images/GitHub-Mark-Light-32px.png new file mode 100644 index 0000000000000000000000000000000000000000..628da97c70890c73e59204f5b140c4e67671e92d GIT binary patch literal 1571 zcmaJ>c~BE~6izDPQq)#Nu*KOf(n^(VHY9;fiINM65``pc+9*v(mL$bwfCjbc%v9V{8r9iX|O%>Nr%pLD2qT{mty}c=LVleeamv znz3SOSm@kP8jThvOOq(56Yzh*fz(booe!uZij=BJC6+_lbvQ~B8nA2>kXdv_RDtRY z`5QXWWEySCe6vbTs^#f?J!WC*{1~RgVx!nJTJjQyO{dRANgx|FnymtGbD9%JmCh9^y)##j7{Dcqfn*1ta$rG89pJF6w-S7Z037$rr|y0;1Onp_ zGFJdT6Q!1C0AdVB0WOmpuV=AgAQ550Tn+-mivTtYPJmz*#75#_n9oV%!#rSOfmAfy zki%C~=fTp1{O#BLpJ|0jj#m6#|LRWit-vq3PE1z9ZqyvET4sX$-Icqy7t z<=aq5ff86AuBZBu6EjJsYWM0uejufWFTwPA7Su}0Bm$7KFb!q{Um_8~A{LUG#1l(l zSehUda@kU8LIRg9fkk2tZ;~ss5~R+mM<==F7hLHpxqLB>>PQS%Vc7b~?q!%T5+h8Q z4G=4Nzyi5WZ?^gkasJ{?Xhm`JC#WG6$1K2jb@=9&D3EgD#3UhGh#*21rJjulVXjCF zvp76q62jt0zzMG5C7DlfMgPl%C^3+~wf|}Lq=}jz|MmIcQjh1Ok6NjD$Em^Iv26D> z8tt_TnM9~^Tt8mflRGPOrrX|HtT3gG4LEuuk{g2Rn}QgJIa?gZo))!!=o_l9bvD%A zZ`aHajl8#~u?!4f7F#*b*->A=R2L)6!>saz?h>#wTXT-I(XmQ zx{84skS>k=i~i`(6k4C7;Zpfx%dCPVjPayMf8pugtGM=~s=Id1l#8MZJ1-73wV#Q3 zR3>v3%}jbQs1f_Z0xo;%=LILlA+nTpKI4ha%xWW}uqHrNao~&T4AY6m`P$_n-6h*g zhoX+e4n%~gl_lhe#s+AMb7d{5WzvYTa%6Q~si@@4{;s(0zU|H&P3fE+t{7X`S#Cj@ zC#vd}^4pcBD*77Ny5=j$h8EL2_t$O38$SQiJ6fPjJMimypr~MB2(&P0aI|h}$64<0 z>_~duqNjaT=DM^6+N{&B_lED;F2wrl?!4Lk*2((x!fmrcsw+=cI^qttuZ9C}-m~5E z-ryYVpL%^xR#&(0YI5hz<(}F7-p)?FPcyJO-zVO>%9ZDXJH8pnY;GJYFDQ>vd#j_* zRrd}L(r=!g+1#nQwsO?kpS`Qq8`NxE+Zy{gf7*_7J*U2V_|NpLo{iasj7VCg_V9&| ShohtYzipXxh2)4xTk Layer 1 - + diff --git a/client/images/email.svg b/client/images/email.svg new file mode 100644 index 0000000..b510f10 --- /dev/null +++ b/client/images/email.svg @@ -0,0 +1 @@ + diff --git a/client/images/info.svg b/client/images/info.svg new file mode 100644 index 0000000..c997041 --- /dev/null +++ b/client/images/info.svg @@ -0,0 +1,14 @@ + + + + background + + + + + + + Layer 1 + + + diff --git a/client/images/person.svg b/client/images/person.svg new file mode 100644 index 0000000..17e7fea --- /dev/null +++ b/client/images/person.svg @@ -0,0 +1,107 @@ + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/client/images/roles/Villager-2.png b/client/images/roles/Villager-2.png new file mode 100644 index 0000000000000000000000000000000000000000..c2283a644b5241d994b1d0e44322a139b662bf4c GIT binary patch literal 3412 zcmcIneOQv`8YjyrD=j0x7U{}Ok7+8NWkLBZ*0xNtEYXRXsEy1@P!V5TxoN4>c9xZv z`N5_ZB9W4*2{l)?$`3@v!SL-=exbrPR1_5Vx;pE1=kIY{@5S{#&-HNsp5OhufA`IW zpum7NMpi}$1Y*tJJ$?reh$Yzz&x+;HiYasXTIg#@;(-7kL{0m~5$I)EtoJ@|1cH~p zYWAoh^uF@cp3p=D!nkGOS<;$N6%8%EPxU`S^*uvANtZE%{u`0GX6Wuxoz@p#mCOWSPX zP-O9nmB^Ko7mXaWzQ(zm9nPR zDp%x-JHL|qQ>LPsv@c&v#d1^V22!+zUj~ z&Oq;2-ls7rgx@)XFZMu@LruQ4w3HHZ<*vE%e|;)H>$1iT!s5Ne%NB}Jjr<|^IcNQC z?zs){)%l0H&zsj<=w`>*N0$F>V@>l^aGStrTP7TcF7k0jM6I>-Y%aj$7=Kq$+qWo+ z>S1mh0RGTD;NUXxpK zTC&vqX_S<=GxV;0Kzp!EDwpzUb+aLR}5LH`%qgX zKdkU5UK|B_+~~8& zi}rI8+)!Z?4?$W;(G`%X7|m|2k0n~Y$=v`B40QTb9(rHBKEa;F7tRio$aNvfsL5BY zN*AKefr<)cv#Oi`&`-X%yI)tK932pM>>}Z1Z{#&KUF&>V2apNP5Z{d7t>r3JnYlg` zFB`G1I0`F?(yI-J?0ED`>3U&^&S87LK+9~F-b(D-Wg#&r0edKMY75z|#2p?X#Xk=6 z6pq{Tw*;4Tc)zW`1qYhB_Og)8IF~bm*2aCd_11Q(wLeGIsO!=Yx^*9Zj=Y^;uGUs* z_I2xQKj#4J0~D%y_2d}aVv*Ag#nki=!smEfpAJVy=zkJEi)#{~{W&zxY-yi_Nh}g* zN~)aZq=Dul!o(btX12xUod@g*a=F!0M8`hOI_)F7(bC3B>$_f!J=?}Q*0cA-CN?9g zGK0PrW|(d_8U&gz?+sj$ToMsMU_D=;vnLWInb-*c1#rjzE#9cV&aNOkCy7>CF1qe&nd(S9UOdpS9-QgoP7;un7|FRD|` z9|cVLPQ`xl5)}cP?F6ei?1oVa@+24iXyt06Z$d*_%sgAIppb9d(S}@Tt(~c}E0xZK zYcj@v+FzcBwIgiyzx0|6C!4*rd#?CD$d5I(r<(%X%m;t%c-1{tf1= zPRTDO4$c3VC4kJT!&67KkA%k3ZcT~;cZ%Q}9oi{@pBD((?47{w_A8Du?8{0Ka3ft}Zhhlm3jiEHFYpt6X z8sXa}9@tX!vTU@PF(nV+sThv+H3bttgK7NS-od-77o9bebp!f!{afy|tH3fP8}RV03bCz?DdTg^*s|JK=6p!Ea0wLv;*A+tSKd_SCzVE>Hj)vV7hSF$)cE%^7-> zv#Ul&a0EbaK+o^h1ha)gX{UPk!#Ugg$j)e(drbZ`GfFk3RR)o+#g65b^B)6pq~IXl zPAuIjB8tJuh@6`pm6J1J3N8semiDZpL$#|JCZ6c#Gt8GB7kIdAEfZyTM6A3{k`u-n zeM{NOT{qIl!k}*ae+TBF14K%RFPImV(@XIQa6N`Ks(_b5X(T_UnU0{Yc*kM&UKJ^p zb>O+2(OMX2 z=Q8Dyot3V6T8%Zz6cJnu;jVIc8`OGMe^d{kR3|s(D2V}GWk)$**qklFK z`X}&?^ks&@VVpu-Qu2f0zZC!1-1=7FuT?cy5{CJg?l1ph(M%)Gs_Y4!E?nCDZW8-v hCw#>1M;~07HRlmGG`#<e*loHw(S4_ literal 0 HcmV?d00001 diff --git a/client/images/vanilla_js.png b/client/images/vanilla_js.png new file mode 100644 index 0000000000000000000000000000000000000000..0b030a326fc4c4979caa8eb1c021ed3671345050 GIT binary patch literal 218 zcmeAS@N?(olHy`uVBq!ia0vp^0YJ>p!VDxEpNH-NQYryHA+G<^7&`kZ)6&xZ!@#Ol ztANbUHzoiXKvh8YCeda0fUH7K7sn8diCZTfGPzkcujXNGkgX8B&sC*A`sXYh3Ob6Mw< G&;$U5nO>y; literal 0 HcmV?d00001 diff --git a/client/images/x.svg b/client/images/x.svg new file mode 100644 index 0000000..5f6dd02 --- /dev/null +++ b/client/images/x.svg @@ -0,0 +1,14 @@ + + + + background + + + + + + + Layer 1 + + + diff --git a/client/modules/GameStateRenderer.js b/client/modules/GameStateRenderer.js index ed237ff..5b59379 100644 --- a/client/modules/GameStateRenderer.js +++ b/client/modules/GameStateRenderer.js @@ -48,6 +48,29 @@ export class GameStateRenderer { let copyImg = document.createElement("img"); copyImg.setAttribute("src", "../images/copy.svg"); gameLinkContainer.appendChild(copyImg); + + let time = document.getElementById("game-time"); + let playerCount = document.getElementById("game-player-count"); + playerCount.innerText = getGameSize(this.stateBucket.currentGameState.deck) + ' Players' + + if (this.stateBucket.currentGameState.timerParams) { + let timeString = ""; + let hours = this.stateBucket.currentGameState.timerParams.hours; + let minutes = this.stateBucket.currentGameState.timerParams.minutes + if (hours) { + timeString += hours > 1 + ? hours + ' hours ' + : hours + ' hour ' + } + if (minutes) { + timeString += minutes > 1 + ? minutes + ' minutes ' + : minutes + ' minute ' + } + time.innerText = timeString; + } else { + time.innerText = 'untimed'; + } } renderLobbyFooter() { @@ -69,7 +92,16 @@ export class GameStateRenderer { renderModeratorView() { let div = document.createElement("div"); div.innerHTML = templates.END_GAME_PROMPT; - document.body.appendChild(div); + document.getElementById("game-content").appendChild(div); + document.getElementById("end-game-button").addEventListener('click', (e) => { + e.preventDefault(); + if (confirm("End the game?")) { + this.socket.emit( + globals.COMMANDS.END_GAME, + this.stateBucket.currentGameState.accessCode + ); + } + }); let modTransferButton = document.getElementById("mod-transfer-button"); modTransferButton.addEventListener( @@ -78,7 +110,7 @@ export class GameStateRenderer { ModalManager.displayModal( "transfer-mod-modal", "transfer-mod-modal-background", - "close-modal-button" + "close-mod-transfer-modal-button" ) } ) @@ -118,18 +150,7 @@ export class GameStateRenderer { } renderPlayersWithRoleAndAlignmentInfo() { - document.querySelectorAll('.game-player').forEach((el) => { - let pointer = el.dataset.pointer; - if (pointer && this.killPlayerHandlers[pointer]) { - el.removeEventListener('click', this.killPlayerHandlers[pointer]); - delete this.killPlayerHandlers[pointer]; - } - if (pointer && this.revealRoleHandlers[pointer]) { - el.removeEventListener('click', this.revealRoleHandlers[pointer]); - delete this.revealRoleHandlers[pointer]; - } - el.remove(); - }); + removeExistingPlayerElements(this.killPlayerHandlers, this.revealRoleHandlers); this.stateBucket.currentGameState.people.sort((a, b) => { return a.name >= b.name ? 1 : -1; }); @@ -151,7 +172,8 @@ export class GameStateRenderer { this.stateBucket.currentGameState.accessCode, globals.ALIGNMENT.GOOD, this.stateBucket.currentGameState.moderator.userType, - this.socket); + this.socket + ); document.getElementById("players-alive-label").innerText = 'Players: ' + this.stateBucket.currentGameState.people.filter((person) => !person.out).length + ' / ' + this.stateBucket.currentGameState.people.length + ' Alive'; @@ -208,28 +230,27 @@ export class GameStateRenderer { } el.remove(); }); - let modalContent = document.getElementById("transfer-mod-form-content"); - if (modalContent) { - renderPotentialMods( - this.stateBucket.currentGameState, - this.stateBucket.currentGameState.people, - this.transferModHandlers, - modalContent, - this.socket - ); - renderPotentialMods( // spectators can also be made mods. - this.stateBucket.currentGameState, - this.stateBucket.currentGameState.spectators, - this.transferModHandlers, - modalContent, - this.socket - ); - } + renderPotentialMods( + this.stateBucket.currentGameState, + this.stateBucket.currentGameState.people, + this.transferModHandlers, + this.socket + ); + renderPotentialMods( // spectators can also be made mods. + this.stateBucket.currentGameState, + this.stateBucket.currentGameState.spectators, + this.transferModHandlers, + this.socket + ); } + renderEndOfGame() { + this.renderPlayersWithNoRoleInformationUnlessRevealed(); + } } -function renderPotentialMods(gameState, group, transferModHandlers, modalContent, socket) { +function renderPotentialMods(gameState, group, transferModHandlers, socket) { + let modalContent = document.getElementById("transfer-mod-modal-content"); for (let member of group) { if ((member.out || member.userType === globals.USER_TYPES.SPECTATOR) && !(member.id === gameState.client.id)) { let container = document.createElement("div"); @@ -244,6 +265,7 @@ function renderPotentialMods(gameState, group, transferModHandlers, modalContent container.addEventListener('click', transferModHandlers[member.id]); modalContent.appendChild(container); + console.log('test'); } } } @@ -420,3 +442,18 @@ function insertPlaceholderButton(container, append, type) { container.querySelector('.player-action-buttons').prepend(button); } } + +function removeExistingPlayerElements(killPlayerHandlers, revealRoleHandlers) { + document.querySelectorAll('.game-player').forEach((el) => { + let pointer = el.dataset.pointer; + if (pointer && killPlayerHandlers[pointer]) { + el.removeEventListener('click', killPlayerHandlers[pointer]); + delete killPlayerHandlers[pointer]; + } + if (pointer && revealRoleHandlers[pointer]) { + el.removeEventListener('click', revealRoleHandlers[pointer]); + delete revealRoleHandlers[pointer]; + } + el.remove(); + }); +} diff --git a/client/modules/GameTimerManager.js b/client/modules/GameTimerManager.js index cbd73e0..ad34eba 100644 --- a/client/modules/GameTimerManager.js +++ b/client/modules/GameTimerManager.js @@ -22,6 +22,11 @@ export class GameTimerManager { let instance = this; let timer = document.getElementById('game-timer'); timer.classList.remove('paused'); + timer.classList.remove('paused-low'); + timer.classList.remove('low'); + if (totalTime < 60000) { + timer.classList.add('low'); + } timer.innerText = totalTime < 60000 ? returnHumanReadableTime(totalTime, true) : returnHumanReadableTime(totalTime); @@ -29,6 +34,9 @@ export class GameTimerManager { if (e.data.hasOwnProperty('timeRemainingInMilliseconds') && e.data.timeRemainingInMilliseconds >= 0) { if (e.data.timeRemainingInMilliseconds === 0) { instance.displayExpiredTime(); + } else if (e.data.timeRemainingInMilliseconds < 60000) { + timer.classList.add('low'); + timer.innerText = e.data.displayTime; } else { timer.innerText = e.data.displayTime; } @@ -49,10 +57,14 @@ export class GameTimerManager { timerWorker.postMessage('stop'); let timer = document.getElementById('game-timer'); - timer.innerText = timeRemaining < 60000 - ? returnHumanReadableTime(timeRemaining, true) - : returnHumanReadableTime(timeRemaining); - timer.classList.add('paused'); + if (timeRemaining < 60000) { + timer.innerText = returnHumanReadableTime(timeRemaining, true); + timer.classList.add('paused-low'); + timer.classList.add('low'); + } else { + timer.innerText = returnHumanReadableTime(timeRemaining); + timer.classList.add('paused'); + } } } @@ -65,10 +77,14 @@ export class GameTimerManager { } let timer = document.getElementById('game-timer'); - timer.innerText = time < 60000 - ? returnHumanReadableTime(time, true) - : returnHumanReadableTime(time); - timer.classList.add('paused'); + if (time < 60000) { + timer.innerText = returnHumanReadableTime(time, true); + timer.classList.add('paused-low'); + timer.classList.add('low'); + } else { + timer.innerText = returnHumanReadableTime(time); + timer.classList.add('paused'); + } } displayExpiredTime() { diff --git a/client/modules/ModalManager.js b/client/modules/ModalManager.js index ba95163..e5e4d19 100644 --- a/client/modules/ModalManager.js +++ b/client/modules/ModalManager.js @@ -18,6 +18,8 @@ function displayModal(modalId, backgroundId, closeButtonId) { }); closeBtn.removeEventListener("click", closeModalHandler); closeBtn.addEventListener("click", closeModalHandler); + } else { + throw new Error("One or more of the ids supplied to ModalManager.displayModal is invalid."); } } diff --git a/client/modules/Templates.js b/client/modules/Templates.js index 3699a98..ae6a5b3 100644 --- a/client/modules/Templates.js +++ b/client/modules/Templates.js @@ -5,8 +5,19 @@ export const templates = { "" + "" + "" + - "
" + - "
" + + "
" + + "
" + + "clock" + + "
" + + "
" + + "
" + + "person" + + "
" + + "
" + + "
" + + "
" + + "
" + "" + "
" + "
" + @@ -31,6 +42,9 @@ export const templates = { "" + "
" + "
" + + "
" + + "
" + "
" + "" + + "
" + + "
" + "" + - "
" + + "
" + "" + "
" + "
", MODERATOR_GAME_VIEW: "" + "" + "
" + "
" + @@ -77,6 +91,9 @@ export const templates = { "
" + "
" + "
" + "" + + "
" + + "
" + "
" + "
" + "" + @@ -178,5 +195,31 @@ export const templates = { "" + "
" + "" + + "
", + ROLE_INFO_MODAL: + "" + + "", + END_OF_GAME_VIEW: + "

The moderator has ended the game. Roles are revealed.

" + + "
" + + "
" + + "
" + + "" + + "
" + + "
" + + "" + + "
" + "
" + } diff --git a/client/modules/Toast.js b/client/modules/Toast.js index f6366dd..8c83b17 100644 --- a/client/modules/Toast.js +++ b/client/modules/Toast.js @@ -9,7 +9,7 @@ export const toast = (message, type, positionAtTop = true, dispelAutomatically=t function buildAndInsertMessageElement (message, type, positionAtTop, dispelAutomatically, duration) { cancelCurrentToast(); let backgroundColor, border; - const position = positionAtTop ? 'top:2rem;' : 'bottom: 35px;'; + const position = positionAtTop ? 'top:2rem;' : 'bottom: 90px;'; switch (type) { case 'warning': backgroundColor = '#fff5b1'; diff --git a/client/scripts/game.js b/client/scripts/game.js index ddc253a..832598d 100644 --- a/client/scripts/game.js +++ b/client/scripts/game.js @@ -69,7 +69,7 @@ function prepareGamePage(environment, socket, timerWorker) { } }) } else { - toast("Name must be fewer than 30 characters.", 'error', true, true, 8); + toast("Name must be between 1 and 30 characters.", 'error', true, true, 8); } } } @@ -131,12 +131,19 @@ function processGameState (currentGameState, userId, socket, gameStateRenderer) default: break; } - socket.emit(globals.COMMANDS.GET_TIME_REMAINING, currentGameState.accessCode); break; + case globals.STATUS.ENDED: + let container = document.getElementById("game-state-container") + container.innerHTML = templates.END_OF_GAME_VIEW; + container.classList.add('vertical-flex'); + gameStateRenderer.renderEndOfGame(); + break; default: break; } + + activateRoleInfoButton(stateBucket.currentGameState.deck); } function displayClientInfo(name, userType) { @@ -148,7 +155,7 @@ function displayClientInfo(name, userType) { function setClientSocketHandlers(stateBucket, gameStateRenderer, socket, timerWorker, gameTimerManager) { if (!socket.hasListeners(globals.EVENTS.PLAYER_JOINED)) { socket.on(globals.EVENTS.PLAYER_JOINED, (player, gameIsFull) => { - toast(player.name + " joined!", "success", false); + toast(player.name + " joined!", "success", false, true, 3); stateBucket.currentGameState.people.push(player); gameStateRenderer.renderLobbyPlayers(); if ( @@ -187,7 +194,7 @@ function setClientSocketHandlers(stateBucket, gameStateRenderer, socket, timerWo killedPerson.out = true; if (stateBucket.currentGameState.client.userType === globals.USER_TYPES.MODERATOR) { toast(killedPerson.name + ' killed.', 'success', true, true, 6); - gameStateRenderer.renderPlayersWithRoleAndAlignmentInfo() + gameStateRenderer.renderPlayersWithRoleAndAlignmentInfo(stateBucket.currentGameState.status === globals.STATUS.ENDED) } else { if (killedPerson.id === stateBucket.currentGameState.client.id) { let clientUserType = document.getElementById("client-user-type"); @@ -195,9 +202,9 @@ function setClientSocketHandlers(stateBucket, gameStateRenderer, socket, timerWo clientUserType.innerText = globals.USER_TYPES.KILLED_PLAYER + ' \uD83D\uDC80' } gameStateRenderer.updatePlayerCardToKilledState(); - toast('You have been killed!', 'warning', false, true, 6); + toast('You have been killed!', 'warning', true, true, 6); } else { - toast(killedPerson.name + ' was killed!', 'warning', false, true, 6); + toast(killedPerson.name + ' was killed!', 'warning', true, true, 6); } if (stateBucket.currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR) { gameStateRenderer.renderPlayersWithNoRoleInformationUnlessRevealed(true); @@ -218,12 +225,12 @@ function setClientSocketHandlers(stateBucket, gameStateRenderer, socket, timerWo revealedPerson.alignment = revealData.alignment; if (stateBucket.currentGameState.client.userType === globals.USER_TYPES.MODERATOR) { toast(revealedPerson.name + ' revealed.', 'success', true, true, 6); - gameStateRenderer.renderPlayersWithRoleAndAlignmentInfo() + gameStateRenderer.renderPlayersWithRoleAndAlignmentInfo(stateBucket.currentGameState.status === globals.STATUS.ENDED) } else { if (revealedPerson.id === stateBucket.currentGameState.client.id) { - toast('Your role has been revealed!', 'warning', false, true, 6); + toast('Your role has been revealed!', 'warning', true, true, 6); } else { - toast(revealedPerson.name + ' was revealed as a ' + revealedPerson.gameRole + '!', 'warning', false, true, 6); + toast(revealedPerson.name + ' was revealed as a ' + revealedPerson.gameRole + '!', 'warning', true, true, 6); } if (stateBucket.currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR) { gameStateRenderer.renderPlayersWithNoRoleInformationUnlessRevealed(true); @@ -242,19 +249,27 @@ function setClientSocketHandlers(stateBucket, gameStateRenderer, socket, timerWo processGameState(stateBucket.currentGameState, stateBucket.currentGameState.client.cookie, socket, gameStateRenderer); }); } + + if (!socket.hasListeners(globals.COMMANDS.END_GAME)) { + socket.on(globals.COMMANDS.END_GAME, (people) => { + stateBucket.currentGameState.people = people; + stateBucket.currentGameState.status = globals.STATUS.ENDED; + processGameState(stateBucket.currentGameState, stateBucket.currentGameState.client.cookie, socket, gameStateRenderer); + }); + } } function displayStartGamePromptForModerators(gameState, socket) { let div = document.createElement("div"); div.innerHTML = templates.START_GAME_PROMPT; - document.body.appendChild(div); - document.getElementById("start-game-button").addEventListener('click', (e) => { + div.querySelector('#start-game-button').addEventListener('click', (e) => { e.preventDefault(); if (confirm("Start the game and deal roles?")) { - socket.emit(globals.COMMANDS.START_GAME, gameState.accessCode, gameState.client.cookie); + socket.emit(globals.COMMANDS.START_GAME, gameState.accessCode); } }); + document.body.appendChild(div); } function runGameTimer (hours, minutes, tickRate, soundManager, timerWorker) { @@ -269,7 +284,7 @@ function runGameTimer (hours, minutes, tickRate, soundManager, timerWorker) { } function validateName(name) { - return typeof name === 'string' && name.length <= 30; + return typeof name === 'string' && name.length > 0 && name.length <= 30; } function propagateNameChange(gameState, name, personId) { @@ -297,7 +312,7 @@ function updateDOMWithNameChange(gameState, gameStateRenderer) { gameStateRenderer.renderPlayersWithNoRoleInformationUnlessRevealed(false); break; case globals.USER_TYPES.MODERATOR: - gameStateRenderer.renderPlayersWithRoleAndAlignmentInfo(); + gameStateRenderer.renderPlayersWithRoleAndAlignmentInfo(gameState.status === globals.STATUS.ENDED); break; case globals.USER_TYPES.TEMPORARY_MODERATOR: gameStateRenderer.renderPlayersWithNoRoleInformationUnlessRevealed(true); @@ -306,3 +321,43 @@ function updateDOMWithNameChange(gameState, gameStateRenderer) { break; } } + +function activateRoleInfoButton(deck) { + deck.sort((a, b) => { + return a.team === globals.ALIGNMENT.GOOD ? 1 : -1; + }) + document.getElementById("role-info-button").addEventListener("click", (e) => { + e.preventDefault(); + document.getElementById("prompt").innerHTML = templates.ROLE_INFO_MODAL; + let modalContent = document.getElementById('game-role-info-container'); + for (let card of deck) { + let roleDiv = document.createElement("div"); + let roleNameDiv = document.createElement("div"); + + roleNameDiv.classList.add('role-info-name'); + + let roleName = document.createElement("h5"); + let roleQuantity = document.createElement("h5"); + let roleDescription = document.createElement("p"); + + roleDescription.innerText = card.description; + roleName.innerText = card.role; + roleQuantity.innerText = card.quantity + 'x'; + + if (card.team === globals.ALIGNMENT.GOOD) { + roleName.classList.add(globals.ALIGNMENT.GOOD); + } else { + roleName.classList.add(globals.ALIGNMENT.EVIL); + } + + roleNameDiv .appendChild(roleQuantity); + roleNameDiv .appendChild(roleName); + + roleDiv.appendChild(roleNameDiv); + roleDiv.appendChild(roleDescription); + + modalContent.appendChild(roleDiv); + } + ModalManager.displayModal('role-info-modal', 'role-info-modal-background', 'close-role-info-modal-button'); + }); +} diff --git a/client/scripts/home.js b/client/scripts/home.js index 87e4872..f30de66 100644 --- a/client/scripts/home.js +++ b/client/scripts/home.js @@ -7,17 +7,19 @@ export const home = () => { let userCode = document.getElementById("room-code").value; if (roomCodeIsValid(userCode)) { attemptToJoinGame(userCode); + } else { + toast('Invalid code. Codes are 6 numbers or letters.', 'error', true, true); } } }; function roomCodeIsValid(code) { - return typeof code === "string" && /^[a-z0-9]{6}$/.test(code); + return typeof code === "string" && /^[a-z0-9]{6}$/.test(code.toLowerCase()); } function attemptToJoinGame(code) { XHRUtility.xhr( - '/api/games/availability/' + code, + '/api/games/availability/' + code.toLowerCase().trim(), 'GET', null, null diff --git a/client/styles/GLOBAL.css b/client/styles/GLOBAL.css index b782f18..ebecbfc 100644 --- a/client/styles/GLOBAL.css +++ b/client/styles/GLOBAL.css @@ -54,6 +54,45 @@ h3 { margin: 0.5em 0; } +#footer { + bottom: 0; + width: 100%; + text-align: center; + align-items: center; + display: flex; + justify-content: center; + flex-wrap: wrap; + color: #d7d7d7; + font-size: 14px; + margin-top: 3em; +} +#footer a img { + width: 32px; +} + +#footer a { + color: #f7f7f7; + text-decoration: none; + cursor: pointer; + font-family: 'diavlo', sans-serif; +} + +#footer a:hover { + color: gray; +} + +#footer div { + display: flex; +} + +#footer > div, #footer > a { + margin: 0.5em; +} + +#footer div:nth-child(2) > a, #footer div:nth-child(2) > p { + margin: 0 5px; +} + label { color: #d7d7d7; font-family: 'signika-negative', sans-serif; @@ -68,11 +107,21 @@ input, textarea { color: #f7f7f7; } +a { + text-decoration: none; +} + textarea, input { font-family: 'signika-negative', sans-serif; font-size: 16px; } +button { + display: flex; + align-items: center; + justify-content: center; +} + button, input[type="submit"] { font-family: 'signika-negative', sans-serif !important; padding: 10px; @@ -119,7 +168,12 @@ input { border-radius: 3px; font-family: 'signika-negative', sans-serif; font-weight: 100; - box-shadow: 0 2px 4px 0 rgb(0 0 0 / 25%); + box-shadow: 0 1px 1px rgba(0,0,0,0.11), + 0 2px 2px rgba(0,0,0,0.11), + 0 4px 4px rgba(0,0,0,0.11), + 0 8px 8px rgba(0,0,0,0.11), + 0 16px 16px rgba(0,0,0,0.11), + 0 32px 32px rgba(0,0,0,0.11); left: 0; right: 0; width: fit-content; @@ -196,7 +250,7 @@ input { width: 100%; max-width: 30em; height: 8em; - margin: 0 auto 0.5em auto; + margin: 0 auto 1em auto; } .animated-placeholder-invisible { @@ -208,10 +262,11 @@ input { display: flex; margin: 0; justify-content: center; + width: 100%; } .placeholder-row .animated-placeholder-short { - margin: 0 0 0.5em 0; + margin: 0 0 1em 0; } .good, .compact-card.good .card-role { @@ -280,12 +335,17 @@ input { @media(max-width: 550px) { h1 { - font-size: 35px; + font-size: 30px; } #step-1 div { font-size: 20px; } + + .info-message { + padding: 5px; + font-size: 16px; + } } @media(min-width: 551px) { @@ -404,6 +464,37 @@ input { transform: rotate(330deg); animation-delay: 0s; } + +.bmc-button span:nth-child(1) { + font-size: 20px; +} + +.bmc-button { + line-height: 35px !important; + height:40px !important; + text-decoration: none !important; + display:inline-flex !important; + align-items: center !important; + color:#ffffff !important; + background-color:#333243 !important; + border-radius: 5px !important; + border: 1px solid transparent !important; + padding: 7px 15px 7px 10px !important; + font-size: 15px !important; + box-shadow: 0px 1px 1px rgba(190, 190, 190, 0.5) !important; + -webkit-box-shadow: 0px 1px 2px 1px rgba(190, 190, 190, 0.5) !important; + font-family: sitewide-sans-serif, sans-serif !important; + -webkit-box-sizing: border-box !important; + box-sizing: border-box !important; +} + +.bmc-button:hover, .bmc-button:active, .bmc-button:focus { + -webkit-box-shadow: 0px 1px 2px 2px rgba(190, 190, 190, 0.5) !important; + text-decoration: none !important;box-shadow: 0px 1px 2px 2px rgba(190, 190, 190, 0.5) !important; + opacity: 0.85 !important;color:#ffffff !important; +} + + @keyframes lds-spinner { 0% { opacity: 1; diff --git a/client/styles/create.css b/client/styles/create.css index 5224e5e..74b7fd5 100644 --- a/client/styles/create.css +++ b/client/styles/create.css @@ -10,7 +10,7 @@ border-radius: 3px; user-select: none; max-width: 15em; - min-width: 12em; + min-width: 9em; display: flex; height: max-content; } @@ -42,6 +42,7 @@ .card-role { font-weight: bold; + pointer-events: none; } .compact-card-right p { @@ -106,7 +107,7 @@ #step-4 > div { display: flex; flex-direction: column; - align-items: center; + align-items: flex-start; text-align: left; justify-content: center; margin: 0 auto; @@ -244,6 +245,7 @@ input[type="number"] { #creation-step-tracker { display: flex; justify-content: center; + margin: 0 20px; } #step-forward-button, #step-back-button, #create-game { diff --git a/client/styles/game.css b/client/styles/game.css index 5ac025a..3a0e601 100644 --- a/client/styles/game.css +++ b/client/styles/game.css @@ -14,6 +14,10 @@ margin: 0.5em 0; } +body { + margin-bottom: 100px; +} + #lobby-players { overflow-y: auto; max-height: 30em; @@ -45,7 +49,11 @@ flex-wrap: wrap; display: flex; width: 95%; - margin: 0 auto 115px auto; + margin: 1em auto 0 auto; +} + +#game-state-container h2 { + margin: 0.5em 0; } #lobby-header { @@ -61,6 +69,28 @@ h1 { margin: 1em; } +#game-content .placeholder-row:nth-child(1) { + margin-top: 2em; +} + +#footer.game-page-footer { + margin-top: 1em; + margin-bottom: 1em; +} + +#footer.game-page-footer a { + margin: 0 5px; +} + +#end-of-game-header { + display: flex; + flex-wrap: wrap; + align-items: center; +} + +#end-of-game-header button { + margin: 0.5em; +} .potential-moderator { display: flex; color: #d7d7d7; @@ -96,17 +126,83 @@ h1 { margin-top: 10px; padding: 7px; border-radius: 3px; - background-color: #722c2c; + background-color: #121314; + border: 2px solid #333243; color: whitesmoke; align-items: center; display: flex; transition: background-color 0.2s; } -#game-player-count { - color: whitesmoke; +.role-info-name { + display: flex; +} + +.role-info-name h5:nth-child(1) { + margin-right: 10px; + color: #21ba45; +} + +#role-info-button { + margin-top: 1em; +} + +#role-info-button img { + height: 25px; + margin-left: 10px; +} + +#game-role-info-container { + display: flex; + flex-direction: column; + align-items: flex-start; + margin: 1em 0; + overflow-y: auto; + max-height: 35em; +} + +#game-role-info-container > div { + width: 95%; +} + +#transfer-mod-modal-content { + overflow-y: auto; + max-height: 35em; + width: 100%; +} + +.potential-moderator { + width: 90%; +} + +#role-info-modal #modal-button-container { + margin-top: 1em; +} + +#game-role-info-container .role-info-name { + padding: 5px; + border-radius: 3px; + font-size: 20px; + font-family: signika-negative, sans-serif; margin: 0.5em 0; - font-size: 25px; + background-color: #15191c; +} + +#role-info-modal h2 { + color: #d7d7d7; + font-family: diavlo, sans-serif; + font-weight: normal; +} + +#game-role-info-container p, #game-role-info-container h5 { + text-align: left; + font-weight: normal; +} + +#game-role-info-container p { + color: #d7d7d7; + font-size: 14px; + font-family: signika-negative, sans-serif; } #game-role { @@ -183,6 +279,12 @@ h1 { border: 1px solid #747474; } +#game-timer.low { + color: #e71c0d; + border: 1px solid #ca1b17; + background-color: #361a1a; +} + #role-name { position: absolute; top: 6%; @@ -215,7 +317,7 @@ h1 { bottom: 8%; left: 50%; transform: translate(-50%, 0); - font-size: 16px; + font-size: 15px; width: 78%; max-height: 6em; } @@ -279,7 +381,7 @@ label[for='moderator'] { border-radius: 3px; font-family: 'signika-negative', sans-serif; font-weight: 100; - box-shadow: 0 2px 4px 0 rgb(0 0 0 / 25%); + box-shadow: 0 -2px 6px 0 rgb(0 0 0 / 45%); left: 0; right: 0; bottom: 0; @@ -355,11 +457,15 @@ label[for='moderator'] { animation: pulse 0.75s linear infinite alternate; } +.paused-low { + animation: pulse-low 0.75s linear infinite alternate; +} + #game-header { display: flex; flex-wrap: wrap; - align-items: center; flex-direction: column; + align-items: flex-start; } .timer-container-moderator { @@ -380,12 +486,7 @@ label[for='moderator'] { justify-content: space-between; margin: 0.5em 0; position: relative; - box-shadow: 0 1px 1px rgba(0,0,0,0.11), - 0 2px 2px rgba(0,0,0,0.11), - 0 4px 4px rgba(0,0,0,0.11), - 0 8px 8px rgba(0,0,0,0.11), - 0 16px 16px rgba(0,0,0,0.11), - 0 32px 32px rgba(0,0,0,0.11); + box-shadow: 2px 3px 6px rgb(0 0 0 / 50%); } .game-player-name { @@ -488,14 +589,30 @@ label[for='moderator'] { #game-player-list { overflow-y: auto; overflow-x: hidden; - padding: 0 10px; + padding: 0; max-height: 37em; } #game-player-list > div { padding: 2px 10px; border-radius: 3px; - margin-bottom: 1em; + margin-bottom: 0.5em; +} + +#game-parameters { + color: #d7d7d7; + font-size: 25px; + margin: 0.5em; +} + +#game-parameters > div { + display: flex; + align-items: center; +} + +#game-parameters img { + height: 20px; + margin-right: 10px; } #players-alive-label { @@ -512,6 +629,10 @@ label[for='moderator'] { justify-content: center; } +#transfer-mod-modal #modal-button-container { + justify-content: center; +} + #change-name-modal-background { cursor: default; } @@ -520,6 +641,88 @@ label[for='moderator'] { background-color: #333243; padding: 10px 10px 0 10px; border-radius: 3px; + min-height: 25em; + min-width: 15em; + max-width: 30em; +} + +#transfer-mod-modal-content { + margin-bottom: 2em; +} + +#game-state-container.vertical-flex { + flex-direction: column; + align-items: center; +} + +@media(max-width: 500px) { + #client-name { + font-size: 25px; + } + + #client-user-type, #game-parameters { + font-size: 20px; + } + + #game-state-container { + margin: 0 auto 0 auto; + } + + button { + font-size: 16px; + padding: 5px; + } + + #play-pause img { + width: 45px; + } + + .make-mod-button { + font-size: 16px; + padding: 5px; + } + + .game-player-name { + font-size: 16px; + } + + #game-timer { + font-size: 30px; + } + + #players-alive-label { + font-size: 20px; + } + + #start-game-prompt, #end-game-prompt { + height: 65px; + } + + #start-game-button, #end-game-button { + font-size: 20px; + padding: 5px; + } + + #game-role, #game-role-back { + height: 20em; + max-width: 15em; + } + + #client-container { + margin: 0; + } + + #game-role-back p { + font-size: 16px; + } + + #game-role-back h4 { + font-size: 20px; + } + + h2 { + font-size: 18px; + } } @keyframes pulse { @@ -530,6 +733,14 @@ label[for='moderator'] { } } +@keyframes pulse-low { + from { + color: rgba(231, 28, 13 , 0.1); + } to { + color: rgba(231, 28, 13, 1); + } +} + @keyframes fade-in-slide-up { 0% { opacity: 0; diff --git a/client/styles/home.css b/client/styles/home.css index f84aee8..e8092c0 100644 --- a/client/styles/home.css +++ b/client/styles/home.css @@ -5,7 +5,6 @@ html { body { align-items: center; justify-content: center; - height: 100%; } button { @@ -31,7 +30,18 @@ a button { #join-button { min-width: 6em; max-height: 3em; - color: #21ba45; + background-color: #1c8a36; + color: whitesmoke; + font-size: 16px; +} + +#join-button:hover { + background-color: #326243; + border: 2px solid #1c8a36; +} + +#join-form div:nth-child(1) { + margin-right: 1em; } h3 { @@ -42,11 +52,11 @@ h3 { font-family: 'diavlo', sans-serif; } -img { +img[src='../images/logo_cropped.gif'] { max-width: 400px; width: 63vw; min-width: 250px; - margin: 1em 0; + margin: 3em 0 1em 0; } form > div { diff --git a/client/styles/modal.css b/client/styles/modal.css index aa600dd..a2bfa24 100644 --- a/client/styles/modal.css +++ b/client/styles/modal.css @@ -2,7 +2,7 @@ border-radius: 2px; text-align: center; position: fixed; - width: 100%; + width: 85%; z-index: 100; top: 50%; left: 50%; @@ -10,7 +10,7 @@ background-color: #23282b; align-items: center; justify-content: center; - max-width: 19em; + max-width: 25em; max-height: 80%; height: fit-content; font-family: sans-serif; diff --git a/client/views/game.html b/client/views/game.html index f4cea66..150cee5 100644 --- a/client/views/game.html +++ b/client/views/game.html @@ -43,7 +43,32 @@ Home -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + +
+
+

Werewolf created by Andrew Plotkin

+
+