mirror of
https://github.com/AlecM33/Werewolf.git
synced 2025-12-26 15:57:50 +01:00
progress on new lobby
This commit is contained in:
@@ -4,6 +4,26 @@ export const globals = {
|
||||
PLAYER_ID_COOKIE_KEY: 'play-werewolf-anon-id',
|
||||
ACCESS_CODE_CHAR_POOL: 'abcdefghijklmnopqrstuvwxyz0123456789',
|
||||
COMMANDS: {
|
||||
FETCH_GAME_STATE: 'fetchGameState'
|
||||
FETCH_GAME_STATE: 'fetchGameState',
|
||||
GET_ENVIRONMENT: 'getEnvironment'
|
||||
},
|
||||
GAME_STATE: {
|
||||
LOBBY: 'lobby'
|
||||
},
|
||||
ALIGNMENT: {
|
||||
GOOD: "good",
|
||||
EVIL: "evil"
|
||||
},
|
||||
EVENTS: {
|
||||
PLAYER_JOINED: "playerJoined"
|
||||
},
|
||||
USER_TYPES: {
|
||||
MODERATOR: "moderator",
|
||||
PLAYER: "player",
|
||||
TEMPORARY_MODERATOR: "temporary moderator"
|
||||
},
|
||||
ENVIRONMENT: {
|
||||
LOCAL: "local",
|
||||
PRODUCTION: "production"
|
||||
}
|
||||
};
|
||||
|
||||
14
client/images/clock.svg
Normal file
14
client/images/clock.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<svg width="158" height="155" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- Created with Method Draw - http://github.com/duopixel/Method-Draw/ -->
|
||||
<g>
|
||||
<title>background</title>
|
||||
<rect fill="none" id="canvas_background" height="157" width="160" y="-1" x="-1"/>
|
||||
<g display="none" id="canvasGrid">
|
||||
<rect fill="none" stroke-width="0" y="0" x="0" height="100%" width="100%"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<title>Layer 1</title>
|
||||
<path stroke="none" id="svg_1" d="m78.750021,0.749998c-43.078721,0 -78.000016,34.473574 -78.000016,76.999988c0,42.526414 34.921295,76.999988 78.000016,76.999988c43.078721,0 78.000016,-34.473574 78.000016,-76.999988c0,-42.526414 -34.921295,-76.999988 -78.000016,-76.999988zm0,138.100913c-34.137753,0 -61.899834,-27.411445 -61.899834,-61.100926c0,-33.694779 27.762081,-61.106224 61.899834,-61.106224c34.137753,0 61.894467,27.406147 61.894467,61.106224c0,33.700077 -27.767448,61.100926 -61.894467,61.100926zm-0.005367,-118.71582c-2.9678,0 -5.361361,2.373469 -5.361361,5.297921l0,48.852132l-32.447234,14.007704c-2.715564,1.176139 -3.949911,4.301912 -2.758498,6.98266c0.880143,1.98672 2.844366,3.173455 4.910556,3.173455c0.719141,0 1.454383,-0.143044 2.152058,-0.450323l35.559936,-15.353376c0.026834,-0.010596 0.048301,-0.021192 0.069767,-0.031788l0.021467,-0.010596c0.080501,-0.031788 0.123435,-0.105958 0.198569,-0.132448c0.55814,-0.275492 1.078712,-0.598665 1.497317,-1.033095c0.182469,-0.180129 0.284437,-0.413238 0.423971,-0.619857c0.257603,-0.339067 0.542039,-0.672836 0.697675,-1.080776c0.128801,-0.317875 0.139535,-0.66224 0.203936,-1.001307c0.069767,-0.344365 0.203936,-0.646346 0.203936,-0.990711l0,-52.316972c0,-2.919155 -2.404294,-5.292623 -5.372094,-5.292623z" stroke-width="1.5" fill="#86cb92"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
210
client/images/copy.svg
Normal file
210
client/images/copy.svg
Normal file
@@ -0,0 +1,210 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="121.00714mm"
|
||||
height="144.81964mm"
|
||||
viewBox="0 0 121.00714 144.81964"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="1.0.2-2 (e86c870879, 2021-01-15)"
|
||||
sodipodi:docname="copy.svg">
|
||||
<defs
|
||||
id="defs2">
|
||||
<inkscape:path-effect
|
||||
effect="powerclip"
|
||||
id="path-effect951"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
inverse="true"
|
||||
flatten="false"
|
||||
hide_clip="false"
|
||||
message="Use fill-rule evenodd on <b>fill and stroke</b> dialog if no flatten result after convert clip to paths." />
|
||||
<inkscape:path-effect
|
||||
effect="powerclip"
|
||||
id="path-effect937"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
inverse="true"
|
||||
flatten="false"
|
||||
hide_clip="false"
|
||||
message="Use fill-rule evenodd on <b>fill and stroke</b> dialog if no flatten result after convert clip to paths." />
|
||||
<inkscape:path-effect
|
||||
effect="powerclip"
|
||||
id="path-effect923"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
inverse="true"
|
||||
flatten="false"
|
||||
hide_clip="false"
|
||||
message="Use fill-rule evenodd on <b>fill and stroke</b> dialog if no flatten result after convert clip to paths." />
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath913">
|
||||
<g
|
||||
id="g921"
|
||||
style="display:none">
|
||||
<path
|
||||
id="path915"
|
||||
style="opacity:0.996;fill:#f9f9f9;fill-opacity:1;stroke:whitesmoke;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 109.61309,67.279762 h 52.91667 c 2.51278,0 4.53571,2.022929 4.53571,4.535714 0,2.512786 -2.02293,4.535715 -4.53571,4.535715 h -52.91667 c -2.51278,0 -4.53571,-2.022929 -4.53571,-4.535715 0,-2.512785 2.02293,-4.535714 4.53571,-4.535714 z" />
|
||||
<path
|
||||
id="path917"
|
||||
style="opacity:0.996;fill:#f9f9f9;fill-opacity:1;stroke:whitesmoke;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 109.12738,83.424988 h 52.91667 c 2.51278,0 4.53571,2.022928 4.53571,4.535714 0,2.512786 -2.02293,4.535714 -4.53571,4.535714 h -52.91667 c -2.51278,0 -4.53571,-2.022928 -4.53571,-4.535714 0,-2.512786 2.02293,-4.535714 4.53571,-4.535714 z" />
|
||||
<path
|
||||
id="path919"
|
||||
style="opacity:0.996;fill:#f9f9f9;fill-opacity:1;stroke:whitesmoke;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 109.12738,99.375595 h 52.91667 c 2.51278,0 4.53571,2.022925 4.53571,4.535715 0,2.51278 -2.02293,4.53571 -4.53571,4.53571 h -52.91667 c -2.51278,0 -4.53571,-2.02293 -4.53571,-4.53571 0,-2.51279 2.02293,-4.535715 4.53571,-4.535715 z" />
|
||||
</g>
|
||||
<path
|
||||
id="lpe_path-effect923"
|
||||
class="powerclip"
|
||||
d="M 55.205951,21.944047 H 156.46071 V 151.16905 H 55.205951 Z m 54.407139,45.335715 c -2.51278,0 -4.53571,2.022929 -4.53571,4.535714 0,2.512786 2.02293,4.535715 4.53571,4.535715 h 52.91667 c 2.51278,0 4.53571,-2.022929 4.53571,-4.535715 0,-2.512785 -2.02293,-4.535714 -4.53571,-4.535714 z m -0.48571,16.145226 c -2.51278,0 -4.53571,2.022928 -4.53571,4.535714 0,2.512786 2.02293,4.535714 4.53571,4.535714 h 52.91667 c 2.51278,0 4.53571,-2.022928 4.53571,-4.535714 0,-2.512786 -2.02293,-4.535714 -4.53571,-4.535714 z m 0,15.950607 c -2.51278,0 -4.53571,2.022925 -4.53571,4.535715 0,2.51278 2.02293,4.53571 4.53571,4.53571 h 52.91667 c 2.51278,0 4.53571,-2.02293 4.53571,-4.53571 0,-2.51279 -2.02293,-4.535715 -4.53571,-4.535715 z" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath927">
|
||||
<g
|
||||
id="g935"
|
||||
transform="translate(0.24285867,0.02228802)"
|
||||
style="display:none">
|
||||
<path
|
||||
id="path929"
|
||||
style="opacity:0.996;fill:#f9f9f9;fill-opacity:1;stroke:whitesmoke;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 109.61309,67.279762 h 52.91667 c 2.51278,0 4.53571,2.022929 4.53571,4.535714 0,2.512786 -2.02293,4.535715 -4.53571,4.535715 h -52.91667 c -2.51278,0 -4.53571,-2.022929 -4.53571,-4.535715 0,-2.512785 2.02293,-4.535714 4.53571,-4.535714 z" />
|
||||
<path
|
||||
id="path931"
|
||||
style="opacity:0.996;fill:#f9f9f9;fill-opacity:1;stroke:whitesmoke;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 109.12738,83.424988 h 52.91667 c 2.51278,0 4.53571,2.022928 4.53571,4.535714 0,2.512786 -2.02293,4.535714 -4.53571,4.535714 h -52.91667 c -2.51278,0 -4.53571,-2.022928 -4.53571,-4.535714 0,-2.512786 2.02293,-4.535714 4.53571,-4.535714 z" />
|
||||
<path
|
||||
id="path933"
|
||||
style="opacity:0.996;fill:#f9f9f9;fill-opacity:1;stroke:whitesmoke;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 109.12738,99.375595 h 52.91667 c 2.51278,0 4.53571,2.022925 4.53571,4.535715 0,2.51278 -2.02293,4.53571 -4.53571,4.53571 h -52.91667 c -2.51278,0 -4.53571,-2.02293 -4.53571,-4.53571 0,-2.51279 2.02293,-4.535715 4.53571,-4.535715 z" />
|
||||
</g>
|
||||
<path
|
||||
id="lpe_path-effect937"
|
||||
class="powerclip"
|
||||
d="M 84.958331,47.538688 H 186.21309 V 176.76369 H 84.958331 Z m 24.654759,19.741074 c -2.51278,0 -4.53571,2.022929 -4.53571,4.535714 0,2.512786 2.02293,4.535715 4.53571,4.535715 h 52.91667 c 2.51278,0 4.53571,-2.022929 4.53571,-4.535715 0,-2.512785 -2.02293,-4.535714 -4.53571,-4.535714 z m -0.48571,16.145226 c -2.51278,0 -4.53571,2.022928 -4.53571,4.535714 0,2.512786 2.02293,4.535714 4.53571,4.535714 h 52.91667 c 2.51278,0 4.53571,-2.022928 4.53571,-4.535714 0,-2.512786 -2.02293,-4.535714 -4.53571,-4.535714 z m 0,15.950607 c -2.51278,0 -4.53571,2.022925 -4.53571,4.535715 0,2.51278 2.02293,4.53571 4.53571,4.53571 h 52.91667 c 2.51278,0 4.53571,-2.02293 4.53571,-4.53571 0,-2.51279 -2.02293,-4.535715 -4.53571,-4.535715 z" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath941">
|
||||
<g
|
||||
id="g949"
|
||||
style="display:none">
|
||||
<rect
|
||||
style="opacity:0.996;fill:whitesmoke;stroke:#a5ccd1;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
id="rect943"
|
||||
width="39.498516"
|
||||
height="11.483695"
|
||||
x="116.32217"
|
||||
y="66.357109"
|
||||
d="m 116.32217,66.357109 h 39.49851 v 11.483695 h -39.49851 z" />
|
||||
<rect
|
||||
style="opacity:0.996;fill:whitesmoke;stroke:#a5ccd1;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
id="rect945"
|
||||
width="39.498516"
|
||||
height="11.483695"
|
||||
x="115.93681"
|
||||
y="82.326607"
|
||||
d="m 115.93681,82.326607 h 39.49852 v 11.483695 h -39.49852 z" />
|
||||
<rect
|
||||
style="opacity:0.996;fill:whitesmoke;stroke:#a5ccd1;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
id="rect947"
|
||||
width="39.498516"
|
||||
height="11.483695"
|
||||
x="115.91958"
|
||||
y="98.25666"
|
||||
d="m 115.91958,98.25666 h 39.49851 v 11.4837 h -39.49851 z" />
|
||||
</g>
|
||||
<path
|
||||
id="lpe_path-effect951"
|
||||
class="powerclip"
|
||||
d="M 55.205951,21.944047 H 156.46071 V 151.16905 H 55.205951 Z m 61.116219,44.413062 v 11.483695 h 39.49851 V 66.357109 Z m -0.38536,15.969498 v 11.483695 h 39.49852 V 82.326607 Z m -0.0172,15.930053 v 11.4837 h 39.49851 v -11.4837 z" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1.4"
|
||||
inkscape:cx="229.70018"
|
||||
inkscape:cy="291.6465"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="g862"
|
||||
inkscape:document-rotation="0"
|
||||
showgrid="false"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:snap-bbox-edge-midpoints="true"
|
||||
inkscape:snap-bbox-midpoints="true"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:snap-global="true" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-60.205951,-26.944047)">
|
||||
<g
|
||||
id="g862">
|
||||
<g
|
||||
id="g870">
|
||||
<path
|
||||
id="rect833"
|
||||
style="opacity:0.996;fill:whitesmoke;fill-opacity:0;stroke:whitesmoke;stroke-width:8.1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 77.863093,30.994047 h 55.940477 c 7.53836,0 13.60714,6.068786 13.60714,13.607143 v 83.91071 c 0,7.53836 -6.06878,13.60715 -13.60714,13.60715 H 77.863093 c -7.538357,0 -13.607142,-6.06879 -13.607142,-13.60715 V 44.60119 c 0,-7.538357 6.068785,-13.607143 13.607142,-13.607143 z"
|
||||
clip-path="url(#clipPath941)"
|
||||
inkscape:original-d="m 77.863093,30.994047 h 55.940477 c 7.53836,0 13.60714,6.068786 13.60714,13.607143 v 83.91071 c 0,7.53836 -6.06878,13.60715 -13.60714,13.60715 H 77.863093 c -7.538357,0 -13.607142,-6.06879 -13.607142,-13.60715 V 44.60119 c 0,-7.538357 6.068785,-13.607143 13.607142,-13.607143 z"
|
||||
inkscape:path-effect="#path-effect923;#path-effect951" />
|
||||
<path
|
||||
id="rect833-1"
|
||||
style="opacity:0.996;fill:whitesmoke;fill-opacity:1;stroke:whitesmoke;stroke-width:8.1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 107.61547,56.588688 h 55.94048 c 7.53836,0 13.60714,6.068785 13.60714,13.607142 v 83.91072 c 0,7.53835 -6.06878,13.60714 -13.60714,13.60714 h -55.94048 c -7.53835,0 -13.607139,-6.06879 -13.607139,-13.60714 V 70.19583 c 0,-7.538357 6.068789,-13.607142 13.607139,-13.607142 z"
|
||||
clip-path="url(#clipPath927)"
|
||||
inkscape:original-d="m 107.61547,56.588688 h 55.94048 c 7.53836,0 13.60714,6.068785 13.60714,13.607142 v 83.91072 c 0,7.53835 -6.06878,13.60714 -13.60714,13.60714 h -55.94048 c -7.53835,0 -13.607139,-6.06879 -13.607139,-13.60714 V 70.19583 c 0,-7.538357 6.068789,-13.607142 13.607139,-13.607142 z"
|
||||
inkscape:path-effect="#path-effect937" />
|
||||
<path
|
||||
style="opacity:0.996;fill:#cfced2;fill-opacity:0;stroke:whitesmoke;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 181.02138,183.03773 c -5.53501,-2.80025 -7.14286,-5.82668 -7.14286,-13.44493 0,-16.70234 2.35705,-17.05861 112.85714,-17.05861 110.5001,0 112.85715,0.35627 112.85715,17.05861 0,16.70235 -2.35705,17.05862 -112.85715,17.05862 -73.8022,0 -100.3663,-0.90805 -105.71428,-3.61369 z"
|
||||
id="path953"
|
||||
transform="matrix(0.26458333,0,0,0.26458333,60.205951,26.944047)" />
|
||||
<path
|
||||
style="opacity:0.996;fill:#cfced2;fill-opacity:0;stroke:whitesmoke;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 174.32134,241.51069 c -5.9592,-6.58484 -6.06152,-15.89102 -0.23874,-21.7138 3.92103,-3.92103 17.98077,-4.4898 110.98575,-4.4898 99.543,0 106.80467,0.34113 111.22449,5.22497 6.28388,6.94361 5.96667,13.53689 -0.98575,20.48931 -5.5075,5.5075 -9.52381,5.71429 -110.98575,5.71429 -98.35673,0 -105.58205,-0.3432 -110,-5.22497 z"
|
||||
id="path955"
|
||||
transform="matrix(0.26458333,0,0,0.26458333,60.205951,26.944047)" />
|
||||
<path
|
||||
style="opacity:0.996;fill:#cfced2;fill-opacity:0;stroke:whitesmoke;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 174.32134,301.51069 c -6.21551,-6.86807 -5.99825,-15.59745 0.53536,-21.51029 4.74851,-4.29734 15.71793,-4.69038 112.03415,-4.01425 l 106.77025,0.74951 4.22446,7.52791 c 3.7896,6.753 3.72048,8.29708 -0.67144,15 l -4.8959,7.47209 H 285.68405 c -99.6769,0 -106.94268,-0.3409 -111.36271,-5.22497 z"
|
||||
id="path957"
|
||||
transform="matrix(0.26458333,0,0,0.26458333,60.205951,26.944047)" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 12 KiB |
@@ -3,6 +3,7 @@ import { cancelCurrentToast, toast } from "./Toast.js";
|
||||
import { customCards } from "../config/customCards.js";
|
||||
import { ModalManager } from "./ModalManager.js";
|
||||
import {XHRUtility} from "./XHRUtility.js";
|
||||
import {globals} from "../config/globals.js";
|
||||
|
||||
export class GameCreationStepManager {
|
||||
constructor(deckManager) {
|
||||
@@ -401,8 +402,9 @@ function loadCustomRoles(deckManager) {
|
||||
|
||||
function constructCompactDeckBuilderElement(card, deckManager) {
|
||||
const cardContainer = document.createElement("div");
|
||||
let alignmentClass = card.team === globals.ALIGNMENT.GOOD ? globals.ALIGNMENT.GOOD : globals.ALIGNMENT.EVIL
|
||||
|
||||
cardContainer.setAttribute("class", "compact-card");
|
||||
cardContainer.setAttribute("class", "compact-card " + alignmentClass);
|
||||
|
||||
cardContainer.setAttribute("id", "card-" + card.role.replaceAll(' ', '-'));
|
||||
|
||||
@@ -419,6 +421,7 @@ function constructCompactDeckBuilderElement(card, deckManager) {
|
||||
"</div>";
|
||||
|
||||
cardContainer.querySelector('.card-role').innerText = card.role;
|
||||
cardContainer.title = card.role;
|
||||
cardContainer.querySelector('.card-quantity').innerText = card.quantity;
|
||||
|
||||
if (card.quantity > 0) {
|
||||
@@ -477,6 +480,8 @@ function updateCustomRoleOptionsList(deckManager, selectEl) {
|
||||
function addOptionsToList(options, selectEl) {
|
||||
for (let i = 0; i < options.length; i ++) {
|
||||
let optionEl = document.createElement("option");
|
||||
let alignmentClass = customCards[i].team === globals.ALIGNMENT.GOOD ? globals.ALIGNMENT.GOOD : globals.ALIGNMENT.EVIL
|
||||
optionEl.classList.add(alignmentClass);
|
||||
optionEl.setAttribute("value", customCards[i].role);
|
||||
optionEl.innerText = customCards[i].role;
|
||||
selectEl.appendChild(optionEl);
|
||||
|
||||
75
client/modules/GameStateRenderer.js
Normal file
75
client/modules/GameStateRenderer.js
Normal file
@@ -0,0 +1,75 @@
|
||||
import {globals} from "../config/globals.js";
|
||||
|
||||
export class GameStateRenderer {
|
||||
constructor(gameState) {
|
||||
this.gameState = gameState;
|
||||
}
|
||||
|
||||
renderLobbyPlayers() {
|
||||
document.querySelectorAll('.lobby-player').forEach((el) => el.remove())
|
||||
let lobbyPlayersContainer = document.getElementById("lobby-players");
|
||||
if (this.gameState.userType !== globals.USER_TYPES.MODERATOR) {
|
||||
renderClient(this.gameState.client, lobbyPlayersContainer);
|
||||
}
|
||||
for (let person of this.gameState.people) {
|
||||
let personEl = document.createElement("div");
|
||||
personEl.innerText = person.name;
|
||||
personEl.classList.add('lobby-player');
|
||||
lobbyPlayersContainer.appendChild(personEl);
|
||||
}
|
||||
let playerCount;
|
||||
if (this.gameState.userType === globals.USER_TYPES.MODERATOR) {
|
||||
playerCount = this.gameState.people.length;
|
||||
} else {
|
||||
playerCount = 1 + this.gameState.people.length;
|
||||
}
|
||||
document.querySelector("label[for='lobby-players']").innerText =
|
||||
"Players ( " + playerCount + " / " + getGameSize(this.gameState.deck) + " )";
|
||||
}
|
||||
|
||||
renderLobbyHeader() {
|
||||
let title = document.createElement("h1");
|
||||
title.innerText = "Lobby";
|
||||
document.body.prepend(title);
|
||||
let gameLinkContainer = document.getElementById("game-link");
|
||||
gameLinkContainer.innerText = window.location;
|
||||
let copyImg = document.createElement("img");
|
||||
copyImg.setAttribute("src", "../images/copy.svg");
|
||||
gameLinkContainer.appendChild(copyImg);
|
||||
|
||||
let moderatorContainer = document.getElementById("moderator");
|
||||
let text, modClass;
|
||||
if (this.gameState.userType === globals.USER_TYPES.MODERATOR || this.gameState.userType === globals.USER_TYPES.TEMPORARY_MODERATOR) {
|
||||
moderatorContainer.innerText = this.gameState.moderator.name + " (you)";
|
||||
moderatorContainer.classList.add('moderator-client');
|
||||
} else {
|
||||
moderatorContainer.innerText = this.gameState.moderator.name;
|
||||
}
|
||||
}
|
||||
|
||||
renderLobbyFooter() {
|
||||
let gameDeckContainer = document.getElementById("game-deck");
|
||||
for (let card of this.gameState.deck) {
|
||||
let cardEl = document.createElement("div");
|
||||
cardEl.innerText = card.quantity + 'x ' + card.role;
|
||||
cardEl.classList.add('lobby-card')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function renderClient(client, container) {
|
||||
let clientEl = document.createElement("div");
|
||||
clientEl.innerText = client.name + ' (you)';
|
||||
clientEl.classList.add('lobby-player');
|
||||
clientEl.classList.add('lobby-player-client');
|
||||
container.prepend(clientEl);
|
||||
}
|
||||
|
||||
function getGameSize(cards) {
|
||||
let quantity = 0;
|
||||
for (let card of cards) {
|
||||
quantity += card.quantity;
|
||||
}
|
||||
|
||||
return quantity;
|
||||
}
|
||||
24
client/modules/Templates.js
Normal file
24
client/modules/Templates.js
Normal file
@@ -0,0 +1,24 @@
|
||||
export const templates = {
|
||||
LOBBY:
|
||||
"<div id='lobby-header'>" +
|
||||
"<div>" +
|
||||
"<label for='game-link'>Share Link</label>" +
|
||||
"<div id='game-link'></div>" +
|
||||
"</div>" +
|
||||
"<div id='game-time'></div>" +
|
||||
"<div id='game-player-count'></div>" +
|
||||
"<div>" +
|
||||
"<label for='moderator'>Moderator</label>" +
|
||||
"<div id='moderator'></div>" +
|
||||
"</div>" +
|
||||
"</div>" +
|
||||
"<div>" +
|
||||
"<div>" +
|
||||
"<label for='lobby-players'>Players</label>" +
|
||||
"<div id='lobby-players'></div>" +
|
||||
"</div>" +
|
||||
"<div id='lobby-footer'>" +
|
||||
"<div id='game-deck'></div>" +
|
||||
"</div>" +
|
||||
"</div>"
|
||||
}
|
||||
86
client/modules/Timer.js
Normal file
86
client/modules/Timer.js
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
A timer using setTimeout that compensates for drift. Drift can happen for several reasons:
|
||||
https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout#reasons_for_delays
|
||||
|
||||
This means the timer may very well be late in executing the next call (but never early).
|
||||
This timer is accurate to within a few ms for any amount of time provided. It's meant to be utilized as a Web Worker.
|
||||
See: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API
|
||||
*/
|
||||
|
||||
const messageParameters = {
|
||||
STOP: 'stop',
|
||||
TOTAL_TIME: 'totalTime',
|
||||
TICK_INTERVAL: 'tickInterval'
|
||||
};
|
||||
|
||||
onmessage = function (e) {
|
||||
if (typeof e.data === 'object'
|
||||
&& e.data.hasOwnProperty(messageParameters.TOTAL_TIME)
|
||||
&& e.data.hasOwnProperty(messageParameters.TICK_INTERVAL)
|
||||
) {
|
||||
const timer = new Singleton(e.data.totalTime, e.data.tickInterval);
|
||||
timer.startTimer();
|
||||
}
|
||||
};
|
||||
|
||||
function stepFn (expected, interval, start, totalTime) {
|
||||
const now = Date.now();
|
||||
if (now - start >= totalTime) {
|
||||
return;
|
||||
}
|
||||
const delta = now - expected;
|
||||
expected += interval;
|
||||
postMessage({ timeRemaining: (totalTime - (expected - start)) / 1000 });
|
||||
Singleton.setNewTimeoutReference(setTimeout(() => {
|
||||
stepFn(expected, interval, start, totalTime);
|
||||
}, Math.max(0, interval - delta)
|
||||
)); // take into account drift - also retain a reference to this clock tick so it can be cleared later
|
||||
}
|
||||
|
||||
class Timer {
|
||||
constructor (totalTime, tickInterval) {
|
||||
this.timeoutId = undefined;
|
||||
this.totalTime = totalTime;
|
||||
this.tickInterval = tickInterval;
|
||||
}
|
||||
|
||||
startTimer () {
|
||||
if (!isNaN(this.totalTime) && !isNaN(this.tickInterval)) {
|
||||
const interval = this.tickInterval;
|
||||
const totalTime = this.totalTime;
|
||||
const start = Date.now();
|
||||
const expected = Date.now() + this.tickInterval;
|
||||
if (this.timeoutId) {
|
||||
clearTimeout(this.timeoutId);
|
||||
}
|
||||
this.timeoutId = setTimeout(() => {
|
||||
stepFn(expected, interval, start, totalTime);
|
||||
}, this.tickInterval);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Singleton {
|
||||
constructor (totalTime, tickInterval) {
|
||||
if (!Singleton.instance) {
|
||||
Singleton.instance = new Timer(totalTime, tickInterval);
|
||||
} else {
|
||||
// This allows the same timer to be configured to run for different intervals / at a different granularity.
|
||||
Singleton.setNewTimerParameters(totalTime, tickInterval);
|
||||
}
|
||||
return Singleton.instance;
|
||||
}
|
||||
|
||||
static setNewTimerParameters (totalTime, tickInterval) {
|
||||
if (Singleton.instance) {
|
||||
Singleton.instance.totalTime = totalTime;
|
||||
Singleton.instance.tickInterval = tickInterval;
|
||||
}
|
||||
}
|
||||
|
||||
static setNewTimeoutReference (timeoutId) {
|
||||
if (Singleton.instance) {
|
||||
Singleton.instance.timeoutId = timeoutId;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ export const toast = (message, type, positionAtTop = true) => {
|
||||
function buildAndInsertMessageElement (message, type, positionAtTop) {
|
||||
cancelCurrentToast();
|
||||
let backgroundColor;
|
||||
const position = positionAtTop ? 'top:4rem;' : 'bottom: 15px;';
|
||||
const position = positionAtTop ? 'top:4rem;' : 'bottom: 35px;';
|
||||
switch (type) {
|
||||
case 'warning':
|
||||
backgroundColor = '#fff5b1';
|
||||
|
||||
@@ -2,24 +2,41 @@ import { globals } from '../config/globals.js';
|
||||
|
||||
export const UserUtility = {
|
||||
|
||||
createNewAnonymousUserId (force = true) {
|
||||
let newId;
|
||||
const currentId = sessionStorage.getItem(globals.PLAYER_ID_COOKIE_KEY);
|
||||
createNewAnonymousUserId (force = true, environment) {
|
||||
let newId, currentId;
|
||||
if (environment === globals.ENVIRONMENT.LOCAL) {
|
||||
currentId = sessionStorage.getItem(globals.PLAYER_ID_COOKIE_KEY);
|
||||
} else {
|
||||
currentId = localStorage.getItem(globals.PLAYER_ID_COOKIE_KEY);
|
||||
}
|
||||
if (currentId !== null && !force) {
|
||||
newId = currentId;
|
||||
} else {
|
||||
newId = createRandomUserId();
|
||||
sessionStorage.setItem(globals.PLAYER_ID_COOKIE_KEY, newId);
|
||||
if (environment === globals.ENVIRONMENT.LOCAL) {
|
||||
sessionStorage.setItem(globals.PLAYER_ID_COOKIE_KEY, newId);
|
||||
} else {
|
||||
localStorage.setItem(globals.PLAYER_ID_COOKIE_KEY, newId);
|
||||
}
|
||||
}
|
||||
return newId;
|
||||
},
|
||||
|
||||
setAnonymousUserId (id) {
|
||||
sessionStorage.setItem(globals.PLAYER_ID_COOKIE_KEY, id);
|
||||
setAnonymousUserId (id, environment) {
|
||||
if (environment === globals.ENVIRONMENT.LOCAL) { // use sessionStorage for cookie during local development to aid in testing.
|
||||
sessionStorage.setItem(globals.PLAYER_ID_COOKIE_KEY, id);
|
||||
} else {
|
||||
localStorage.setItem(globals.PLAYER_ID_COOKIE_KEY, id);
|
||||
}
|
||||
},
|
||||
|
||||
validateAnonUserSignature () {
|
||||
const userSig = sessionStorage.getItem(globals.PLAYER_ID_COOKIE_KEY);
|
||||
validateAnonUserSignature (environment) {
|
||||
let userSig;
|
||||
if (environment === globals.ENVIRONMENT.LOCAL) { // use sessionStorage for cookie during local development to aid in testing.
|
||||
userSig = sessionStorage.getItem(globals.PLAYER_ID_COOKIE_KEY);
|
||||
} else {
|
||||
userSig = localStorage.getItem(globals.PLAYER_ID_COOKIE_KEY);
|
||||
}
|
||||
return (
|
||||
userSig
|
||||
&& typeof userSig === 'string'
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -2109,7 +2109,7 @@
|
||||
module.error(error.noStorage);
|
||||
return;
|
||||
}
|
||||
name = sessionStorage.getItem(value);
|
||||
name = localStorage.getItem(value);
|
||||
return (name !== undefined)
|
||||
? name
|
||||
: false
|
||||
@@ -2153,7 +2153,7 @@
|
||||
return;
|
||||
}
|
||||
module.verbose('Saving remote data to session storage', value, name);
|
||||
sessionStorage.setItem(value, name);
|
||||
localStorage.setItem(value, name);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -3714,7 +3714,7 @@
|
||||
minCharacters : 0, // Minimum characters required to trigger API call
|
||||
|
||||
filterRemoteData : false, // Whether API results should be filtered after being returned for query term
|
||||
saveRemoteData : true, // Whether remote name/value pairs should be stored in sessionStorage to allow remote data to be restored on page refresh
|
||||
saveRemoteData : true, // Whether remote name/value pairs should be stored in localStorage to allow remote data to be restored on page refresh
|
||||
|
||||
throttle : 200, // How long to wait after last user input to search remotely
|
||||
|
||||
|
||||
@@ -35,23 +35,3 @@ function loadCustomRoles(deckManager) {
|
||||
});
|
||||
deckManager.customRoleOptions = customCards;
|
||||
}
|
||||
|
||||
function createGameForHosting(deck, hasTimer, modName, timerParams) {
|
||||
XHRUtility.xhr(
|
||||
'/api/games/create',
|
||||
'POST',
|
||||
null,
|
||||
JSON.stringify(
|
||||
new Game(deck, hasTimer, modName, timerParams)
|
||||
)
|
||||
)
|
||||
.then((res) => {
|
||||
if (res
|
||||
&& typeof res === 'object'
|
||||
&& Object.prototype.hasOwnProperty.call(res, 'content')
|
||||
&& typeof res.content === 'string'
|
||||
) {
|
||||
window.location = ('/game/' + res.content);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,22 +1,49 @@
|
||||
import { UserUtility } from "../modules/UserUtility.js";
|
||||
import { globals } from "../config/globals.js";
|
||||
import {templates} from "../modules/Templates.js";
|
||||
import {GameStateRenderer} from "../modules/GameStateRenderer.js";
|
||||
import {toast} from "../modules/Toast.js";
|
||||
|
||||
export const game = () => {
|
||||
let userId = UserUtility.validateAnonUserSignature();
|
||||
const splitUrl = window.location.href.split('/game/');
|
||||
const accessCode = splitUrl[1];
|
||||
if (/^[a-zA-Z0-9]+$/.test(accessCode) && accessCode.length === globals.ACCESS_CODE_LENGTH) {
|
||||
socket.emit(globals.COMMANDS.FETCH_GAME_STATE, accessCode, userId, function (gameState) {
|
||||
if (gameState === null) {
|
||||
window.location.replace('/not-found');
|
||||
} else {
|
||||
console.log(gameState);
|
||||
userId = gameState.id;
|
||||
UserUtility.setAnonymousUserId(userId);
|
||||
// processGameState(gameState, userId, socket);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
window.location.replace('/not-found');
|
||||
}
|
||||
socket.emit(globals.COMMANDS.GET_ENVIRONMENT, function(environment) {
|
||||
let userId = UserUtility.validateAnonUserSignature(environment);
|
||||
const splitUrl = window.location.href.split('/game/');
|
||||
const accessCode = splitUrl[1];
|
||||
if (/^[a-zA-Z0-9]+$/.test(accessCode) && accessCode.length === globals.ACCESS_CODE_LENGTH) {
|
||||
socket.emit(globals.COMMANDS.FETCH_GAME_STATE, accessCode, userId, function (gameState) {
|
||||
if (gameState === null) {
|
||||
window.location.replace('/not-found');
|
||||
} else {
|
||||
console.log(gameState);
|
||||
userId = gameState.client.id;
|
||||
UserUtility.setAnonymousUserId(userId, environment);
|
||||
let gameStateRenderer = new GameStateRenderer(gameState);
|
||||
processGameState(gameState, userId, socket, gameStateRenderer); // this socket is initialized via a script tag in the game page HTML.
|
||||
setClientSocketHandlers(gameStateRenderer, socket);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
window.location.replace('/not-found');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function processGameState (gameState, userId, socket, gameStateRenderer) {
|
||||
switch (gameState.status) {
|
||||
case globals.GAME_STATE.LOBBY:
|
||||
document.getElementById("game-state-container").innerHTML = templates.LOBBY;
|
||||
gameStateRenderer.renderLobbyPlayers();
|
||||
gameStateRenderer.renderLobbyHeader();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function setClientSocketHandlers(gameStateRenderer, socket) {
|
||||
socket.on(globals.EVENTS.PLAYER_JOINED, (player) => {
|
||||
toast(player.name + " joined!", "success", false);
|
||||
gameStateRenderer.gameState.people.push(player);
|
||||
gameStateRenderer.renderLobbyPlayers();
|
||||
})
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ h1 {
|
||||
font-family: 'diavlo', sans-serif;
|
||||
color: #ab2626;
|
||||
filter: drop-shadow(2px 2px 4px black);
|
||||
font-size: 40px;
|
||||
font-size: 50px;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
|
||||
@@ -209,6 +209,14 @@ input {
|
||||
margin: 0 0 0.5em 0;
|
||||
}
|
||||
|
||||
.good, .compact-card.good .card-role {
|
||||
color: #4b6bfa !important;
|
||||
}
|
||||
|
||||
.evil, .compact-card.evil .card-role {
|
||||
color: #e73333 !important
|
||||
}
|
||||
|
||||
@keyframes placeholder {
|
||||
0%{
|
||||
background-position: 50% 0
|
||||
|
||||
@@ -33,13 +33,17 @@
|
||||
}
|
||||
|
||||
.selected-card {
|
||||
border: 2px solid #0075F2;
|
||||
border: 2px solid #c5c5c5;
|
||||
}
|
||||
|
||||
.search {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.card-role {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.compact-card-right p {
|
||||
font-size: 40px;
|
||||
margin: 0 10px 0 0;
|
||||
@@ -93,6 +97,8 @@
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: fit-content;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
#deck {
|
||||
@@ -133,10 +139,11 @@ select {
|
||||
|
||||
#game-time label, #game-time input {
|
||||
margin-right: 10px;
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
label[for="game-time"], label[for="add-card-to-deck-form"], label[for="deck"] {
|
||||
color: #0075F2;
|
||||
color: whitesmoke;
|
||||
font-size: 20px;
|
||||
border-radius: 3px;
|
||||
margin-bottom: 10px;
|
||||
@@ -145,6 +152,7 @@ label[for="game-time"], label[for="add-card-to-deck-form"], label[for="deck"] {
|
||||
|
||||
input[type="number"] {
|
||||
min-width: 3em;
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
#add-card-to-deck-form {
|
||||
@@ -232,16 +240,16 @@ input[type="number"] {
|
||||
}
|
||||
|
||||
#step-1 div.option-selected {
|
||||
border: 2px solid #0075F2;
|
||||
background-color: #252730;
|
||||
border: 2px solid whitesmoke;
|
||||
background-color: #3a3c46;
|
||||
}
|
||||
|
||||
#step-1 div > strong {
|
||||
color: #0075F2;
|
||||
color: #00a718;
|
||||
}
|
||||
|
||||
#step-1 div:hover {
|
||||
border: 2px solid #0075F2;
|
||||
border: 2px solid whitesmoke;
|
||||
}
|
||||
|
||||
.review-option {
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
.lobby-player, #moderator {
|
||||
background-color: black;
|
||||
color: whitesmoke;
|
||||
padding: 10px;
|
||||
border-radius: 3px;
|
||||
font-size: 17px;
|
||||
width: fit-content;
|
||||
min-width: 15em;
|
||||
border: 2px solid transparent;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
|
||||
.lobby-player-client {
|
||||
border: 2px solid #21ba45;
|
||||
}
|
||||
|
||||
#moderator.moderator-client {
|
||||
border: 2px solid lightgray;
|
||||
}
|
||||
|
||||
#game-state-container {
|
||||
justify-content: center;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
#lobby-header {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
margin: 0.5em auto;
|
||||
}
|
||||
|
||||
#game-state-container > div {
|
||||
margin: 1em;
|
||||
}
|
||||
|
||||
#game-link {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
border: 2px solid transparent;
|
||||
cursor: pointer;
|
||||
margin-top: 10px;
|
||||
padding: 5px;
|
||||
border-radius: 3px;
|
||||
background-color: #1f1f1f;
|
||||
color: whitesmoke;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#game-player-count {
|
||||
color: whitesmoke;
|
||||
margin: 0.5em 0;
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
#game-link img {
|
||||
width: 20px;
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
#game-link:hover {
|
||||
border: 2px solid #0075F2;
|
||||
}
|
||||
|
||||
label[for='moderator'] {
|
||||
font-family: 'diavlo', sans-serif;
|
||||
color: lightgray;
|
||||
filter: drop-shadow(2px 2px 4px black);
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
label[for='lobby-players'] {
|
||||
font-family: 'diavlo', sans-serif;
|
||||
color: #21ba45;
|
||||
filter: drop-shadow(2px 2px 4px black);
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
max-width: 17em;
|
||||
max-height: 20em;
|
||||
max-height: 24em;
|
||||
font-family: sans-serif;
|
||||
font-size: 22px;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -36,6 +36,13 @@
|
||||
<label for="role-name">Role Name</label>
|
||||
<input id="role-name" type="text" placeholder="Name your role..." required/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="role-alignment">Role Alignment</label>
|
||||
<select id="role-alignment" required>
|
||||
<option value="good">Good</option>
|
||||
<option value="evil">Evil</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="role-description">Description</label>
|
||||
<textarea style="resize:none" id="role-description" rows="10" cols="30" placeholder="Describe your role..." required></textarea>
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
<link rel="stylesheet" href="../styles/modal.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container" id="game-state-container"></div>
|
||||
<script type="module">
|
||||
import { game } from "../scripts/game.js";
|
||||
game();
|
||||
|
||||
@@ -3,19 +3,27 @@ const globals = {
|
||||
ACCESS_CODE_LENGTH: 6,
|
||||
CLIENT_COMMANDS: {
|
||||
FETCH_GAME_STATE: 'fetchGameState',
|
||||
TOGGLE_READY: 'toggleReady',
|
||||
PROCESS_GUESS: 'processGuess'
|
||||
GET_ENVIRONMENT: 'getEnvironment'
|
||||
},
|
||||
STATUS: {
|
||||
LOBBY: "lobby"
|
||||
LOBBY: "lobby",
|
||||
IN_PROGRESS: "in progress"
|
||||
},
|
||||
USER_SIGNATURE_LENGTH: 25,
|
||||
USER_TYPES: {
|
||||
MODERATOR: "moderator",
|
||||
PLAYER: "player"
|
||||
PLAYER: "player",
|
||||
TEMPORARY_MODERATOR: "temporary moderator"
|
||||
},
|
||||
ERROR_MESSAGE: {
|
||||
GAME_IS_FULL: "This game is full"
|
||||
},
|
||||
EVENTS: {
|
||||
PLAYER_JOINED: "playerJoined"
|
||||
},
|
||||
ENVIRONMENT: {
|
||||
LOCAL: "local",
|
||||
PRODUCTION: "production"
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@ const path = require('path');
|
||||
const fs = require('fs');
|
||||
const socketIO = require('socket.io');
|
||||
const app = express();
|
||||
let main;
|
||||
const bodyParser = require('body-parser');
|
||||
const GameManager = require('./modules/GameManager.js');
|
||||
const globals = require('./config/globals');
|
||||
// const QueueManager = require('./modules/managers/QueueManager');
|
||||
|
||||
app.use(bodyParser.json()); // to support JSON-encoded bodies
|
||||
@@ -15,6 +15,9 @@ app.use(bodyParser.urlencoded({ // to support URL-encoded bodies
|
||||
extended: true
|
||||
}));
|
||||
|
||||
|
||||
let main, environment;
|
||||
|
||||
const debugMode = Array.from(process.argv.map((arg) => arg.trim().toLowerCase())).includes('debug');
|
||||
const localServer = Array.from(process.argv.map((arg) => arg.trim().toLowerCase())).includes('local');
|
||||
const useHttps = Array.from(process.argv.map((arg) => arg.trim().toLowerCase())).includes('https');
|
||||
@@ -31,6 +34,7 @@ const port = process.env.PORT || Array
|
||||
const logger = require('./modules/Logger')(debugMode);
|
||||
|
||||
if (localServer) {
|
||||
environment = globals.ENVIRONMENT.LOCAL;
|
||||
logger.log('starting main in LOCAL mode.');
|
||||
if (useHttps && fs.existsSync(path.join(__dirname, './certs/localhost-key.pem')) && fs.existsSync(path.join(__dirname, './certs/localhost.pem'))) {
|
||||
const key = fs.readFileSync(path.join(__dirname, './certs/localhost-key.pem'), 'utf-8');
|
||||
@@ -44,7 +48,8 @@ if (localServer) {
|
||||
logger.log(`navigate to http://localhost:${port}`);
|
||||
}
|
||||
} else {
|
||||
logger.log('starting main in PRODUCTION mode. WARNING: This should not be used for local development.');
|
||||
environment = globals.ENVIRONMENT.PRODUCTION;
|
||||
logger.warn('starting main in PRODUCTION mode. This should not be used for local development.');
|
||||
main = http.createServer(app);
|
||||
const secure = require('express-force-https');
|
||||
app.use(secure);
|
||||
@@ -58,7 +63,7 @@ const inGame = io.of('/in-game');
|
||||
|
||||
|
||||
/* Instantiate the singleton game manager */
|
||||
const gameManager = new GameManager(logger).getInstance();
|
||||
const gameManager = new GameManager(logger, environment).getInstance();
|
||||
|
||||
/* Instantiate the singleton queue manager */
|
||||
//const queueManager = new QueueManager(matchmaking, logger).getInstance();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
class Game {
|
||||
constructor(status, people, deck, hasTimer, timerParams=null) {
|
||||
constructor(status, people, deck, hasTimer, moderator, timerParams=null) {
|
||||
this.status = status;
|
||||
this.moderator = moderator;
|
||||
this.people = people;
|
||||
this.deck = deck;
|
||||
this.hasTimer = hasTimer;
|
||||
|
||||
@@ -2,10 +2,13 @@ const globals = require('../config/globals');
|
||||
const ActiveGameRunner = require('./ActiveGameRunner');
|
||||
const Game = require('../model/Game');
|
||||
const Person = require('../model/Person');
|
||||
const GameStateCurator = require('./GameStateCurator');
|
||||
const UsernameGenerator = require('./UsernameGenerator');
|
||||
|
||||
class GameManager {
|
||||
constructor (logger) {
|
||||
constructor (logger, environment) {
|
||||
this.logger = logger;
|
||||
this.environment = environment;
|
||||
this.activeGameRunner = new ActiveGameRunner().getInstance();
|
||||
this.namespace = null;
|
||||
//this.gameSocketUtility = GameSocketUtility;
|
||||
@@ -14,8 +17,20 @@ class GameManager {
|
||||
addGameSocketHandlers = (namespace, socket) => {
|
||||
this.namespace = namespace;
|
||||
socket.on(globals.CLIENT_COMMANDS.FETCH_GAME_STATE, (accessCode, personId, ackFn) => {
|
||||
handleRequestForGameState(this.namespace, this.logger, this.activeGameRunner, accessCode, personId, ackFn, socket);
|
||||
handleRequestForGameState(
|
||||
this.namespace,
|
||||
this.logger,
|
||||
this.activeGameRunner,
|
||||
accessCode,
|
||||
personId,
|
||||
ackFn,
|
||||
socket
|
||||
);
|
||||
});
|
||||
|
||||
socket.on(globals.CLIENT_COMMANDS.GET_ENVIRONMENT, (ackFn) => {
|
||||
ackFn(this.environment);
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -26,13 +41,18 @@ class GameManager {
|
||||
return Promise.reject('Tried to create game with invalid options: ' + gameParams);
|
||||
} else {
|
||||
const newAccessCode = this.generateAccessCode();
|
||||
let moderator = initializeModerator(gameParams.moderatorName, gameParams.hasDedicatedModerator);
|
||||
this.activeGameRunner.activeGames[newAccessCode] = new Game(
|
||||
globals.STATUS.LOBBY,
|
||||
initializePeopleForGame(gameParams.moderatorName, gameParams.deck),
|
||||
initializePeopleForGame(gameParams.deck),
|
||||
gameParams.deck,
|
||||
gameParams.hasTimer,
|
||||
moderator,
|
||||
gameParams.timerParams
|
||||
);
|
||||
if (!gameParams.hasDedicatedModerator) {
|
||||
this.activeGameRunner.activeGames[newAccessCode].people.push(moderator);
|
||||
}
|
||||
return Promise.resolve(newAccessCode);
|
||||
}
|
||||
}
|
||||
@@ -69,14 +89,16 @@ function getRandomInt (max) {
|
||||
return Math.floor(Math.random() * Math.floor(max));
|
||||
}
|
||||
|
||||
function initializeModerator(name) {
|
||||
return new Person(createRandomUserId(), name, globals.USER_TYPES.MODERATOR)
|
||||
function initializeModerator(name, hasDedicatedModerator) {
|
||||
const userType = hasDedicatedModerator
|
||||
? globals.USER_TYPES.MODERATOR
|
||||
: globals.USER_TYPES.TEMPORARY_MODERATOR;
|
||||
return new Person(createRandomUserId(), name, userType)
|
||||
}
|
||||
|
||||
function initializePeopleForGame(modName, uniqueCards) {
|
||||
function initializePeopleForGame(uniqueCards) {
|
||||
let people = [];
|
||||
let cards = []; // this will contain copies of each card equal to the quantity.
|
||||
people.push(initializeModerator(modName));
|
||||
let numberOfRoles = 0;
|
||||
for (let card of uniqueCards) {
|
||||
for (let i = 0; i < card.quantity; i ++) {
|
||||
@@ -88,7 +110,7 @@ function initializePeopleForGame(modName, uniqueCards) {
|
||||
cards = shuffleArray(cards); // The deck should probably be shuffled, ey?.
|
||||
|
||||
for(let j = 0; j < numberOfRoles; j ++) {
|
||||
people.push(new Person(createRandomUserId(), null, globals.USER_TYPES.PLAYER, cards[j].role, cards[j].description))
|
||||
people.push(new Person(createRandomUserId(), UsernameGenerator.generate(), globals.USER_TYPES.PLAYER, cards[j].role, cards[j].description))
|
||||
}
|
||||
|
||||
return people;
|
||||
@@ -113,10 +135,10 @@ function createRandomUserId () {
|
||||
}
|
||||
|
||||
class Singleton {
|
||||
constructor (logger) {
|
||||
constructor (logger, environment) {
|
||||
if (!Singleton.instance) {
|
||||
logger.log('CREATING SINGLETON GAME MANAGER');
|
||||
Singleton.instance = new GameManager(logger);
|
||||
Singleton.instance = new GameManager(logger, environment);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,16 +159,19 @@ function handleRequestForGameState(namespace, logger, gameRunner, accessCode, pe
|
||||
const game = gameRunner.activeGames[accessCode];
|
||||
if (game) {
|
||||
let matchingPerson = game.people.find((person) => person.id === personId);
|
||||
if (!matchingPerson && game.moderator.id === personId) {
|
||||
matchingPerson = game.moderator;
|
||||
}
|
||||
if (matchingPerson) {
|
||||
if (matchingPerson.socketId === socket.id) {
|
||||
logger.debug("matching person found with an established connection to the room: " + matchingPerson.name);
|
||||
ackFn(getGameStateFromPerspectiveOfPerson(game, matchingPerson));
|
||||
ackFn(GameStateCurator.getGameStateFromPerspectiveOfPerson(game, matchingPerson));
|
||||
} else {
|
||||
if (!roomContainsSocketOfMatchingPerson(namespace, matchingPerson, logger, accessCode)) {
|
||||
logger.debug("matching person found with a new connection to the room: " + matchingPerson.name);
|
||||
socket.join(accessCode);
|
||||
matchingPerson.socketId = socket.id;
|
||||
ackFn(getGameStateFromPerspectiveOfPerson(game, matchingPerson));
|
||||
ackFn(GameStateCurator.getGameStateFromPerspectiveOfPerson(game, matchingPerson));
|
||||
} else {
|
||||
rejectClientRequestForGameState(ackFn);
|
||||
}
|
||||
@@ -155,15 +180,21 @@ function handleRequestForGameState(namespace, logger, gameRunner, accessCode, pe
|
||||
let personWithMatchingSocketId = findPersonWithMatchingSocketId(game.people, socket.id);
|
||||
if (personWithMatchingSocketId) {
|
||||
logger.debug("matching person found whose cookie got cleared after establishing a connection to the room: " + personWithMatchingSocketId.name);
|
||||
ackFn(getGameStateFromPerspectiveOfPerson(game, personWithMatchingSocketId));
|
||||
ackFn(GameStateCurator.getGameStateFromPerspectiveOfPerson(game, personWithMatchingSocketId));
|
||||
} else {
|
||||
let unassignedPerson = game.people.find((person) => person.assigned === false);
|
||||
let unassignedPerson = game.moderator.assigned === false
|
||||
? game.moderator
|
||||
: game.people.find((person) => person.assigned === false);
|
||||
if (unassignedPerson) {
|
||||
logger.debug("completely new person with a first connection to the room: " + unassignedPerson.name);
|
||||
socket.join(accessCode);
|
||||
unassignedPerson.assigned = true;
|
||||
unassignedPerson.socketId = socket.id;
|
||||
ackFn(getGameStateFromPerspectiveOfPerson(game, unassignedPerson));
|
||||
ackFn(GameStateCurator.getGameStateFromPerspectiveOfPerson(game, unassignedPerson));
|
||||
socket.to(accessCode).emit(
|
||||
globals.EVENTS.PLAYER_JOINED,
|
||||
{ name: unassignedPerson.name }
|
||||
);
|
||||
} else {
|
||||
rejectClientRequestForGameState(ackFn);
|
||||
}
|
||||
@@ -174,10 +205,6 @@ function handleRequestForGameState(namespace, logger, gameRunner, accessCode, pe
|
||||
}
|
||||
}
|
||||
|
||||
function getGameStateFromPerspectiveOfPerson(game, person) {
|
||||
return person;
|
||||
}
|
||||
|
||||
// in socket.io 2.x , the rooms property is an object. in 3.x and 4.x, it is a javascript Set.
|
||||
function roomContainsSocketOfMatchingPerson(namespace, matchingPerson, logger, accessCode) {
|
||||
return namespace.adapter
|
||||
|
||||
84
server/modules/GameStateCurator.js
Normal file
84
server/modules/GameStateCurator.js
Normal file
@@ -0,0 +1,84 @@
|
||||
const globals = require("../config/globals")
|
||||
|
||||
const GameStateCurator = {
|
||||
getGameStateFromPerspectiveOfPerson: (game, person) => {
|
||||
return getGameStateBasedOnPermissions(game, person);
|
||||
}
|
||||
}
|
||||
|
||||
function getGameStateBasedOnPermissions(game, person) {
|
||||
let client = game.status === globals.STATUS.LOBBY // people won't be able to know their role until past the lobby stage.
|
||||
? { name: person.name, id: person.id }
|
||||
: {
|
||||
name: person.name,
|
||||
id: person.id,
|
||||
gameRole: person.gameRole,
|
||||
roleDescription: person.roleDescription
|
||||
}
|
||||
switch (person.userType) {
|
||||
case globals.USER_TYPES.PLAYER:
|
||||
return {
|
||||
status: game.status,
|
||||
moderator: mapPerson(game.moderator),
|
||||
userType: globals.USER_TYPES.PLAYER,
|
||||
client: client,
|
||||
deck: game.deck,
|
||||
people: game.people
|
||||
.filter((person) => {
|
||||
return person.assigned === true && person.id !== client.id && person.userType !== globals.USER_TYPES.MODERATOR
|
||||
})
|
||||
.map((filteredPerson) => ({ name: filteredPerson.name, userType: filteredPerson.userType })),
|
||||
timerParams: game.timerParams
|
||||
}
|
||||
case globals.USER_TYPES.MODERATOR:
|
||||
return {
|
||||
status: game.status,
|
||||
moderator: mapPerson(game.moderator),
|
||||
userType: globals.USER_TYPES.MODERATOR,
|
||||
client: client,
|
||||
deck: game.deck,
|
||||
people: mapPeopleForModerator(game.people, client),
|
||||
timerParams: game.timerParams
|
||||
}
|
||||
case globals.USER_TYPES.TEMPORARY_MODERATOR:
|
||||
return {
|
||||
status: game.status,
|
||||
moderator: mapPerson(game.moderator),
|
||||
userType: globals.USER_TYPES.TEMPORARY_MODERATOR,
|
||||
client: client,
|
||||
deck: game.deck,
|
||||
people: mapPeopleForTempModerator(game.people, client),
|
||||
timerParams: game.timerParams
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function mapPeopleForModerator(people, client) {
|
||||
return people
|
||||
.filter((person) => {
|
||||
return person.assigned === true && person.id !== client.id
|
||||
})
|
||||
.map((person) => ({
|
||||
name: person.name,
|
||||
gameRole: person.gameRole,
|
||||
gameRoleDescription: person.gameRoleDescription
|
||||
}));
|
||||
}
|
||||
|
||||
function mapPeopleForTempModerator(people, client) {
|
||||
return people
|
||||
.filter((person) => {
|
||||
return person.assigned === true && person.id !== client.id
|
||||
})
|
||||
.map((person) => ({
|
||||
name: person.name,
|
||||
}));
|
||||
}
|
||||
|
||||
function mapPerson(person) {
|
||||
return { name: person.name };
|
||||
}
|
||||
|
||||
module.exports = GameStateCurator;
|
||||
@@ -15,6 +15,12 @@ module.exports = function (debugMode = false) {
|
||||
if (!debugMode) return;
|
||||
const now = new Date();
|
||||
console.error('ERROR ', now.toGMTString(), ': ', message);
|
||||
},
|
||||
|
||||
warn (message = '') {
|
||||
if (!debugMode) return;
|
||||
const now = new Date();
|
||||
console.error('WARNING ', now.toGMTString(), ': ', message);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
1338
server/modules/UsernameGenerator.js
Normal file
1338
server/modules/UsernameGenerator.js
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user