├── .env ├── .gitignore ├── Pipfile ├── Pipfile.lock ├── README.md ├── config.py ├── gameplay.png ├── scrabble.db ├── server ├── __init__.py ├── api │ ├── __init__.py │ ├── auth.py │ ├── errors.py │ └── routes.py ├── build │ ├── asset-manifest.json │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ ├── robots.txt │ └── static │ │ ├── css │ │ ├── main.0ac7eade.chunk.css │ │ └── main.0ac7eade.chunk.css.map │ │ └── js │ │ ├── 2.104ca3f2.chunk.js │ │ ├── 2.104ca3f2.chunk.js.LICENSE.txt │ │ ├── 2.104ca3f2.chunk.js.map │ │ ├── 3.1b5ba067.chunk.js │ │ ├── 3.1b5ba067.chunk.js.map │ │ ├── main.c7fc86e0.chunk.js │ │ ├── main.c7fc86e0.chunk.js.map │ │ ├── runtime-main.cab1df26.js │ │ └── runtime-main.cab1df26.js.map ├── data.py ├── main │ ├── __init__.py │ └── routes.py ├── models.py ├── socketio.py ├── templates │ └── index.html └── utils.py └── wsgi.py /.env: -------------------------------------------------------------------------------- 1 | ENV="PROD" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | __pycache__/ 3 | __init__.pyc 4 | 5 | client/ -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | flask = "*" 8 | flask-socketio = "*" 9 | flask-sqlalchemy = "*" 10 | gevent = "*" 11 | flask-cors = "*" 12 | python-dotenv = "*" 13 | flask-httpauth = "*" 14 | eventlet = "*" 15 | 16 | [dev-packages] 17 | pytest = "*" 18 | coverage = "*" 19 | 20 | [requires] 21 | python_version = "3.6" 22 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "f713dba35ecf4a4dd9e45b23b76dcf015e6685804f681cc9598a1a90750f5052" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.6" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "bidict": { 20 | "hashes": [ 21 | "sha256:3ac67daa353ecf853a1df9d3e924f005e729227a60a8dbada31a4c31aba7f654", 22 | "sha256:42c84ffbe6f8de898af6073b4be9ea7ccedcd78d3474aa844c54e49d5a079f6f" 23 | ], 24 | "markers": "python_version >= '3.6'", 25 | "version": "==0.21.4" 26 | }, 27 | "click": { 28 | "hashes": [ 29 | "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3", 30 | "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b" 31 | ], 32 | "markers": "python_version >= '3.6'", 33 | "version": "==8.0.3" 34 | }, 35 | "dnspython": { 36 | "hashes": [ 37 | "sha256:95d12f6ef0317118d2a1a6fc49aac65ffec7eb8087474158f42f26a639135216", 38 | "sha256:e4a87f0b573201a0f3727fa18a516b055fd1107e0e5477cded4a2de497df1dd4" 39 | ], 40 | "markers": "python_version >= '3.6'", 41 | "version": "==2.1.0" 42 | }, 43 | "eventlet": { 44 | "hashes": [ 45 | "sha256:2f0bb8ed0dc0ab21d683975d5d8ab3c054d588ce61def9faf7a465ee363e839b", 46 | "sha256:a3a67b02f336e97a1894b277bc33b695831525758781eb024f4da00e75ce5e25" 47 | ], 48 | "index": "pypi", 49 | "version": "==0.32.0" 50 | }, 51 | "flask": { 52 | "hashes": [ 53 | "sha256:7b2fb8e934ddd50731893bdcdb00fc8c0315916f9fcd50d22c7cc1a95ab634e2", 54 | "sha256:cb90f62f1d8e4dc4621f52106613488b5ba826b2e1e10a33eac92f723093ab6a" 55 | ], 56 | "index": "pypi", 57 | "version": "==2.0.2" 58 | }, 59 | "flask-cors": { 60 | "hashes": [ 61 | "sha256:74efc975af1194fc7891ff5cd85b0f7478be4f7f59fe158102e91abb72bb4438", 62 | "sha256:b60839393f3b84a0f3746f6cdca56c1ad7426aa738b70d6c61375857823181de" 63 | ], 64 | "index": "pypi", 65 | "version": "==3.0.10" 66 | }, 67 | "flask-httpauth": { 68 | "hashes": [ 69 | "sha256:395040fda2854df800d15e84bc4a81a5f32f1d4a5e91eee554936f36f330aa29", 70 | "sha256:e16067ba3378ea366edf8de4b9d55f38c0a0cbddefcc0f777a54b3fce1d99392" 71 | ], 72 | "index": "pypi", 73 | "version": "==4.5.0" 74 | }, 75 | "flask-socketio": { 76 | "hashes": [ 77 | "sha256:07e1899e3b4851978b2ac8642080156c6294f8d0fc5212b4e4bcca713830306a", 78 | "sha256:1efdaacc7a26e94f2b197a80079b1058f6aa644a6094c0a322349e2b9c41f6b1" 79 | ], 80 | "index": "pypi", 81 | "version": "==5.1.1" 82 | }, 83 | "flask-sqlalchemy": { 84 | "hashes": [ 85 | "sha256:2bda44b43e7cacb15d4e05ff3cc1f8bc97936cc464623424102bfc2c35e95912", 86 | "sha256:f12c3d4cc5cc7fdcc148b9527ea05671718c3ea45d50c7e732cceb33f574b390" 87 | ], 88 | "index": "pypi", 89 | "version": "==2.5.1" 90 | }, 91 | "gevent": { 92 | "hashes": [ 93 | "sha256:02d1e8ca227d0ab0b7917fd7e411f9a534475e0a41fb6f434e9264b20155201a", 94 | "sha256:0c7b4763514fec74c9fe6ad10c3de62d8fe7b926d520b1e35eb6887181b954ff", 95 | "sha256:1c9c87b15f792af80edc950a83ab8ef4f3ba3889712211c2c42740ddb57b5492", 96 | "sha256:23077d87d1589ac141c22923fd76853d2cc5b7e3c5e1f1f9cdf6ff23bc9790fc", 97 | "sha256:37a469a99e6000b42dd0b9bbd9d716dbd66cdc6e5738f136f6a266c29b90ee99", 98 | "sha256:3b600145dc0c5b39c6f89c2e91ec6c55eb0dd52dc8148228479ca42cded358e4", 99 | "sha256:3f5ba654bdd3c774079b553fef535ede5b52c7abd224cb235a15da90ae36251b", 100 | "sha256:43e93e1a4738c922a2416baf33f0afb0a20b22d3dba886720bc037cd02a98575", 101 | "sha256:473f918bdf7d2096e391f66bd8ce1e969639aa235e710aaf750a37774bb585bd", 102 | "sha256:4c94d27be9f0439b28eb8bd0f879e6142918c62092fda7fb96b6d06f01886b94", 103 | "sha256:55ede95f41b74e7506fab293ad04cc7fc2b6f662b42281e9f2d668ad3817b574", 104 | "sha256:6cad37a55e904879beef2a7e7c57c57d62fde2331fef1bec7f2b2a7ef14da6a2", 105 | "sha256:72d4c2a8e65bbc702db76456841c7ddd6de2d9ab544a24aa74ad9c2b6411a269", 106 | "sha256:75c29ed5148c916021d39d2fac90ccc0e19adf854626a34eaee012aa6b1fcb67", 107 | "sha256:84e1af2dfb4ea9495cb914b00b6303ca0d54bf0a92e688a17e60f6b033873df2", 108 | "sha256:8d8655ce581368b7e1ab42c8a3a166c0b43ea04e59970efbade9448864585e99", 109 | "sha256:90131877d3ce1a05da1b718631860815b89ff44e93c42d168c9c9e8893b26318", 110 | "sha256:9d46bea8644048ceac5737950c08fc89c37a66c34a56a6c9e3648726e60cb767", 111 | "sha256:a8656d6e02bf47d7fa47728cf7a7cbf408f77ef1fad12afd9e0e3246c5de1707", 112 | "sha256:aaf1451cd0d9c32f65a50e461084a0540be52b8ea05c18669c95b42e1f71592a", 113 | "sha256:afc877ff4f277d0e51a1206d748fdab8c1e0256f7a05e1b1067abbed71c64da9", 114 | "sha256:b10c3326edb76ec3049646dc5131608d6d3733b5adfc75d34852028ecc67c52c", 115 | "sha256:ceec7c5f15fb2f9b767b194daa55246830db6c7c3c2f0b1c7e9e90cb4d01f3f9", 116 | "sha256:e00dc0450f79253b7a3a7f2a28e6ca959c8d0d47c0f9fa2c57894c7974d5965f", 117 | "sha256:e91632fdcf1c9a33e97e35f96edcbdf0b10e36cf53b58caa946dca4836bb688c", 118 | "sha256:f39d5defda9443b5fb99a185050e94782fe7ac38f34f751b491142216ad23bc7" 119 | ], 120 | "index": "pypi", 121 | "version": "==21.8.0" 122 | }, 123 | "greenlet": { 124 | "hashes": [ 125 | "sha256:00e44c8afdbe5467e4f7b5851be223be68adb4272f44696ee71fe46b7036a711", 126 | "sha256:013d61294b6cd8fe3242932c1c5e36e5d1db2c8afb58606c5a67efce62c1f5fd", 127 | "sha256:049fe7579230e44daef03a259faa24511d10ebfa44f69411d99e6a184fe68073", 128 | "sha256:14d4f3cd4e8b524ae9b8aa567858beed70c392fdec26dbdb0a8a418392e71708", 129 | "sha256:166eac03e48784a6a6e0e5f041cfebb1ab400b394db188c48b3a84737f505b67", 130 | "sha256:17ff94e7a83aa8671a25bf5b59326ec26da379ace2ebc4411d690d80a7fbcf23", 131 | "sha256:1e12bdc622676ce47ae9abbf455c189e442afdde8818d9da983085df6312e7a1", 132 | "sha256:21915eb821a6b3d9d8eefdaf57d6c345b970ad722f856cd71739493ce003ad08", 133 | "sha256:288c6a76705dc54fba69fbcb59904ae4ad768b4c768839b8ca5fdadec6dd8cfd", 134 | "sha256:32ca72bbc673adbcfecb935bb3fb1b74e663d10a4b241aaa2f5a75fe1d1f90aa", 135 | "sha256:356b3576ad078c89a6107caa9c50cc14e98e3a6c4874a37c3e0273e4baf33de8", 136 | "sha256:40b951f601af999a8bf2ce8c71e8aaa4e8c6f78ff8afae7b808aae2dc50d4c40", 137 | "sha256:572e1787d1460da79590bf44304abbc0a2da944ea64ec549188fa84d89bba7ab", 138 | "sha256:58df5c2a0e293bf665a51f8a100d3e9956febfbf1d9aaf8c0677cf70218910c6", 139 | "sha256:64e6175c2e53195278d7388c454e0b30997573f3f4bd63697f88d855f7a6a1fc", 140 | "sha256:7227b47e73dedaa513cdebb98469705ef0d66eb5a1250144468e9c3097d6b59b", 141 | "sha256:7418b6bfc7fe3331541b84bb2141c9baf1ec7132a7ecd9f375912eca810e714e", 142 | "sha256:7cbd7574ce8e138bda9df4efc6bf2ab8572c9aff640d8ecfece1b006b68da963", 143 | "sha256:7ff61ff178250f9bb3cd89752df0f1dd0e27316a8bd1465351652b1b4a4cdfd3", 144 | "sha256:833e1551925ed51e6b44c800e71e77dacd7e49181fdc9ac9a0bf3714d515785d", 145 | "sha256:8639cadfda96737427330a094476d4c7a56ac03de7265622fcf4cfe57c8ae18d", 146 | "sha256:8c790abda465726cfb8bb08bd4ca9a5d0a7bd77c7ac1ca1b839ad823b948ea28", 147 | "sha256:8d2f1fb53a421b410751887eb4ff21386d119ef9cde3797bf5e7ed49fb51a3b3", 148 | "sha256:903bbd302a2378f984aef528f76d4c9b1748f318fe1294961c072bdc7f2ffa3e", 149 | "sha256:93f81b134a165cc17123626ab8da2e30c0455441d4ab5576eed73a64c025b25c", 150 | "sha256:95e69877983ea39b7303570fa6760f81a3eec23d0e3ab2021b7144b94d06202d", 151 | "sha256:9633b3034d3d901f0a46b7939f8c4d64427dfba6bbc5a36b1a67364cf148a1b0", 152 | "sha256:97e5306482182170ade15c4b0d8386ded995a07d7cc2ca8f27958d34d6736497", 153 | "sha256:9f3cba480d3deb69f6ee2c1825060177a22c7826431458c697df88e6aeb3caee", 154 | "sha256:aa5b467f15e78b82257319aebc78dd2915e4c1436c3c0d1ad6f53e47ba6e2713", 155 | "sha256:abb7a75ed8b968f3061327c433a0fbd17b729947b400747c334a9c29a9af6c58", 156 | "sha256:aec52725173bd3a7b56fe91bc56eccb26fbdff1386ef123abb63c84c5b43b63a", 157 | "sha256:b11548073a2213d950c3f671aa88e6f83cda6e2fb97a8b6317b1b5b33d850e06", 158 | "sha256:b1692f7d6bc45e3200844be0dba153612103db241691088626a33ff1f24a0d88", 159 | "sha256:b92e29e58bef6d9cfd340c72b04d74c4b4e9f70c9fa7c78b674d1fec18896dc4", 160 | "sha256:be5f425ff1f5f4b3c1e33ad64ab994eed12fc284a6ea71c5243fd564502ecbe5", 161 | "sha256:dd0b1e9e891f69e7675ba5c92e28b90eaa045f6ab134ffe70b52e948aa175b3c", 162 | "sha256:e30f5ea4ae2346e62cedde8794a56858a67b878dd79f7df76a0767e356b1744a", 163 | "sha256:e6a36bb9474218c7a5b27ae476035497a6990e21d04c279884eb10d9b290f1b1", 164 | "sha256:e859fcb4cbe93504ea18008d1df98dee4f7766db66c435e4882ab35cf70cac43", 165 | "sha256:eb6ea6da4c787111adf40f697b4e58732ee0942b5d3bd8f435277643329ba627", 166 | "sha256:ec8c433b3ab0419100bd45b47c9c8551248a5aee30ca5e9d399a0b57ac04651b", 167 | "sha256:eff9d20417ff9dcb0d25e2defc2574d10b491bf2e693b4e491914738b7908168", 168 | "sha256:f0214eb2a23b85528310dad848ad2ac58e735612929c8072f6093f3585fd342d", 169 | "sha256:f276df9830dba7a333544bd41070e8175762a7ac20350786b322b714b0e654f5", 170 | "sha256:f3acda1924472472ddd60c29e5b9db0cec629fbe3c5c5accb74d6d6d14773478", 171 | "sha256:f70a9e237bb792c7cc7e44c531fd48f5897961701cdaa06cf22fc14965c496cf", 172 | "sha256:f9d29ca8a77117315101425ec7ec2a47a22ccf59f5593378fc4077ac5b754fce", 173 | "sha256:fa877ca7f6b48054f847b61d6fa7bed5cebb663ebc55e018fda12db09dcc664c", 174 | "sha256:fdcec0b8399108577ec290f55551d926d9a1fa6cad45882093a7a07ac5ec147b" 175 | ], 176 | "markers": "python_version >= '3' and platform_python_implementation == 'CPython' and platform_machine == 'aarch64' or (platform_machine == 'ppc64le' or (platform_machine == 'x86_64' or (platform_machine == 'amd64' or (platform_machine == 'AMD64' or (platform_machine == 'win32' or platform_machine == 'WIN32')))))", 177 | "version": "==1.1.2" 178 | }, 179 | "itsdangerous": { 180 | "hashes": [ 181 | "sha256:5174094b9637652bdb841a3029700391451bd092ba3db90600dea710ba28e97c", 182 | "sha256:9e724d68fc22902a1435351f84c3fb8623f303fffcc566a4cb952df8c572cff0" 183 | ], 184 | "markers": "python_version >= '3.6'", 185 | "version": "==2.0.1" 186 | }, 187 | "jinja2": { 188 | "hashes": [ 189 | "sha256:827a0e32839ab1600d4eb1c4c33ec5a8edfbc5cb42dafa13b81f182f97784b45", 190 | "sha256:8569982d3f0889eed11dd620c706d39b60c36d6d25843961f33f77fb6bc6b20c" 191 | ], 192 | "markers": "python_version >= '3.6'", 193 | "version": "==3.0.2" 194 | }, 195 | "markupsafe": { 196 | "hashes": [ 197 | "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298", 198 | "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64", 199 | "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b", 200 | "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194", 201 | "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567", 202 | "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff", 203 | "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724", 204 | "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74", 205 | "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646", 206 | "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35", 207 | "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6", 208 | "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a", 209 | "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6", 210 | "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad", 211 | "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26", 212 | "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38", 213 | "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac", 214 | "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7", 215 | "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6", 216 | "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047", 217 | "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75", 218 | "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f", 219 | "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b", 220 | "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135", 221 | "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8", 222 | "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a", 223 | "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a", 224 | "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1", 225 | "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9", 226 | "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864", 227 | "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914", 228 | "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee", 229 | "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f", 230 | "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18", 231 | "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8", 232 | "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2", 233 | "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d", 234 | "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b", 235 | "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b", 236 | "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86", 237 | "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6", 238 | "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f", 239 | "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb", 240 | "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833", 241 | "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28", 242 | "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e", 243 | "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415", 244 | "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902", 245 | "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f", 246 | "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d", 247 | "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9", 248 | "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d", 249 | "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145", 250 | "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066", 251 | "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c", 252 | "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1", 253 | "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a", 254 | "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207", 255 | "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f", 256 | "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53", 257 | "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd", 258 | "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134", 259 | "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85", 260 | "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9", 261 | "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5", 262 | "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94", 263 | "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509", 264 | "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51", 265 | "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872" 266 | ], 267 | "markers": "python_version >= '3.6'", 268 | "version": "==2.0.1" 269 | }, 270 | "python-dotenv": { 271 | "hashes": [ 272 | "sha256:14f8185cc8d494662683e6914addcb7e95374771e707601dfc70166946b4c4b8", 273 | "sha256:bbd3da593fc49c249397cbfbcc449cf36cb02e75afc8157fcc6a81df6fb7750a" 274 | ], 275 | "index": "pypi", 276 | "version": "==0.19.1" 277 | }, 278 | "python-engineio": { 279 | "hashes": [ 280 | "sha256:ad06a975f7e14cb3bb7137cbf70fd883804484d29acd58004d1db1e2a7fc0ad3", 281 | "sha256:fed35eeacfa21f53f1fc05ef0cadd65a50780364da3a2be7650eb92f928fdb11" 282 | ], 283 | "markers": "python_version >= '3.6'", 284 | "version": "==4.3.0" 285 | }, 286 | "python-socketio": { 287 | "hashes": [ 288 | "sha256:d84fa319e943aa18328280c8fbc4e2ba03cf9e96ff905b294b8b482af64532c9", 289 | "sha256:ef4e273ddfebb421144a228cbab1e7e27ffe8d372514fa561e57d590ea6627b0" 290 | ], 291 | "markers": "python_version >= '3.6'", 292 | "version": "==5.4.1" 293 | }, 294 | "six": { 295 | "hashes": [ 296 | "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", 297 | "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" 298 | ], 299 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 300 | "version": "==1.16.0" 301 | }, 302 | "sqlalchemy": { 303 | "hashes": [ 304 | "sha256:07ac4461a1116b317519ddf6f34bcb00b011b5c1370ebeaaf56595504ffc7e84", 305 | "sha256:090536fd23bf49077ee94ff97142bc5ee8bad24294c3d7c8d5284267c885dde7", 306 | "sha256:1dee515578d04bc80c4f9a8c8cfe93f455db725059e885f1b1da174d91c4d077", 307 | "sha256:1ef37c9ec2015ce2f0dc1084514e197f2f199d3dc3514190db7620b78e6004c8", 308 | "sha256:295b90efef1278f27fe27d94a45460ae3c17f5c5c2b32c163e29c359740a1599", 309 | "sha256:2ce42ad1f59eb85c55c44fb505f8854081ee23748f76b62a7f569cfa9b6d0604", 310 | "sha256:2feb028dc75e13ba93456a42ac042b255bf94dbd692bf80b47b22653bb25ccf8", 311 | "sha256:31f4426cfad19b5a50d07153146b2bcb372a279975d5fa39f98883c0ef0f3313", 312 | "sha256:3c0c5f54560a92691d54b0768d67b4d3159e514b426cfcb1258af8c195577e8f", 313 | "sha256:463ef692259ff8189be42223e433542347ae17e33f91c1013e9c5c64e2798088", 314 | "sha256:4a882dedb9dfa6f33524953c3e3d72bcf518a5defd6d5863150a821928b19ad3", 315 | "sha256:4c185c928e2638af9bae13acc3f70e0096eac76471a1101a10f96b80666b8270", 316 | "sha256:5039faa365e7522a8eb4736a54afd24a7e75dcc33b81ab2f0e6c456140f1ad64", 317 | "sha256:5c6774b34782116ad9bdec61c2dbce9faaca4b166a0bc8e7b03c2b870b121d94", 318 | "sha256:6bc7f9d7d90ef55e8c6db1308a8619cd8f40e24a34f759119b95e7284dca351a", 319 | "sha256:7e8ef103eaa72a857746fd57dda5b8b5961e8e82a528a3f8b7e2884d8506f0b7", 320 | "sha256:7ef421c3887b39c6f352e5022a53ac18de8387de331130481cb956b2d029cad6", 321 | "sha256:908fad32c53b17aad12d722379150c3c5317c422437e44032256a77df1746292", 322 | "sha256:91efbda4e6d311812f23996242bad7665c1392209554f8a31ec6db757456db5c", 323 | "sha256:a6506c17b0b6016656783232d0bdd03fd333f1f654d51a14d93223f953903646", 324 | "sha256:a95bf9c725012dcd7ea3cac16bf647054e0d62b31d67467d228338e6a163e4ff", 325 | "sha256:ad7e403fc1e3cb76e802872694e30d6ca6129b9bc6ad4e7caa48ca35f8a144f8", 326 | "sha256:b86f762cee3709722ab4691981958cbec475ea43406a6916a7ec375db9cbd9e9", 327 | "sha256:ba84026e84379326bbf2f0c50792f2ae56ab9c01937df5597b6893810b8ca369", 328 | "sha256:bca660b76672e15d70a7dba5e703e1ce451a0257b6bd2028e62b0487885e8ae9", 329 | "sha256:c24c01dcd03426a5fe5ee7af735906bec6084977b9027a3605d11d949a565c01", 330 | "sha256:c2f2114b0968a280f94deeeaa31cfbac9175e6ac7bd3058b3ce6e054ecd762b3", 331 | "sha256:c46f013ff31b80cbe36410281675e1fb4eaf3e25c284fd8a69981c73f6fa4cb4", 332 | "sha256:c757ba1279b85b3460e72e8b92239dae6f8b060a75fb24b3d9be984dd78cfa55", 333 | "sha256:cc6b21f19bc9d4cd77cbcba5f3b260436ce033f1053cea225b6efea2603d201e", 334 | "sha256:dbf588ab09e522ac2cbd010919a592c6aae2f15ccc3cd9a96d01c42fbc13f63e", 335 | "sha256:de996756d894a2d52c132742e3b6d64ecd37e0919ddadf4dc3981818777c7e67", 336 | "sha256:e700d48056475d077f867e6a36e58546de71bdb6fdc3d34b879e3240827fefab", 337 | "sha256:f1e97c5f36b94542f72917b62f3a2f92be914b2cf33b80fa69cede7529241d2a", 338 | "sha256:fb2aa74a6e3c2cebea38dd21633671841fbe70ea486053cba33d68e3e22ccc0a", 339 | "sha256:ff8f91a7b1c4a1c7772caa9efe640f2768828897044748f2458b708f1026e2d4" 340 | ], 341 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", 342 | "version": "==1.4.26" 343 | }, 344 | "werkzeug": { 345 | "hashes": [ 346 | "sha256:63d3dc1cf60e7b7e35e97fa9861f7397283b75d765afcaefd993d6046899de8f", 347 | "sha256:aa2bb6fc8dee8d6c504c0ac1e7f5f7dc5810a9903e793b6f715a9f015bdadb9a" 348 | ], 349 | "markers": "python_version >= '3.6'", 350 | "version": "==2.0.2" 351 | }, 352 | "zope.event": { 353 | "hashes": [ 354 | "sha256:2666401939cdaa5f4e0c08cf7f20c9b21423b95e88f4675b1443973bdb080c42", 355 | "sha256:5e76517f5b9b119acf37ca8819781db6c16ea433f7e2062c4afc2b6fbedb1330" 356 | ], 357 | "version": "==4.5.0" 358 | }, 359 | "zope.interface": { 360 | "hashes": [ 361 | "sha256:08f9636e99a9d5410181ba0729e0408d3d8748026ea938f3b970a0249daa8192", 362 | "sha256:0b465ae0962d49c68aa9733ba92a001b2a0933c317780435f00be7ecb959c702", 363 | "sha256:0cba8477e300d64a11a9789ed40ee8932b59f9ee05f85276dbb4b59acee5dd09", 364 | "sha256:0cee5187b60ed26d56eb2960136288ce91bcf61e2a9405660d271d1f122a69a4", 365 | "sha256:0ea1d73b7c9dcbc5080bb8aaffb776f1c68e807767069b9ccdd06f27a161914a", 366 | "sha256:0f91b5b948686659a8e28b728ff5e74b1be6bf40cb04704453617e5f1e945ef3", 367 | "sha256:15e7d1f7a6ee16572e21e3576d2012b2778cbacf75eb4b7400be37455f5ca8bf", 368 | "sha256:17776ecd3a1fdd2b2cd5373e5ef8b307162f581c693575ec62e7c5399d80794c", 369 | "sha256:194d0bcb1374ac3e1e023961610dc8f2c78a0f5f634d0c737691e215569e640d", 370 | "sha256:1c0e316c9add0db48a5b703833881351444398b04111188069a26a61cfb4df78", 371 | "sha256:205e40ccde0f37496904572035deea747390a8b7dc65146d30b96e2dd1359a83", 372 | "sha256:273f158fabc5ea33cbc936da0ab3d4ba80ede5351babc4f577d768e057651531", 373 | "sha256:2876246527c91e101184f63ccd1d716ec9c46519cc5f3d5375a3351c46467c46", 374 | "sha256:2c98384b254b37ce50eddd55db8d381a5c53b4c10ee66e1e7fe749824f894021", 375 | "sha256:2e5a26f16503be6c826abca904e45f1a44ff275fdb7e9d1b75c10671c26f8b94", 376 | "sha256:334701327f37c47fa628fc8b8d28c7d7730ce7daaf4bda1efb741679c2b087fc", 377 | "sha256:3748fac0d0f6a304e674955ab1365d515993b3a0a865e16a11ec9d86fb307f63", 378 | "sha256:3c02411a3b62668200910090a0dff17c0b25aaa36145082a5a6adf08fa281e54", 379 | "sha256:3dd4952748521205697bc2802e4afac5ed4b02909bb799ba1fe239f77fd4e117", 380 | "sha256:3f24df7124c323fceb53ff6168da70dbfbae1442b4f3da439cd441681f54fe25", 381 | "sha256:469e2407e0fe9880ac690a3666f03eb4c3c444411a5a5fddfdabc5d184a79f05", 382 | "sha256:4de4bc9b6d35c5af65b454d3e9bc98c50eb3960d5a3762c9438df57427134b8e", 383 | "sha256:5208ebd5152e040640518a77827bdfcc73773a15a33d6644015b763b9c9febc1", 384 | "sha256:52de7fc6c21b419078008f697fd4103dbc763288b1406b4562554bd47514c004", 385 | "sha256:5bb3489b4558e49ad2c5118137cfeaf59434f9737fa9c5deefc72d22c23822e2", 386 | "sha256:5dba5f530fec3f0988d83b78cc591b58c0b6eb8431a85edd1569a0539a8a5a0e", 387 | "sha256:5dd9ca406499444f4c8299f803d4a14edf7890ecc595c8b1c7115c2342cadc5f", 388 | "sha256:5f931a1c21dfa7a9c573ec1f50a31135ccce84e32507c54e1ea404894c5eb96f", 389 | "sha256:63b82bb63de7c821428d513607e84c6d97d58afd1fe2eb645030bdc185440120", 390 | "sha256:66c0061c91b3b9cf542131148ef7ecbecb2690d48d1612ec386de9d36766058f", 391 | "sha256:6f0c02cbb9691b7c91d5009108f975f8ffeab5dff8f26d62e21c493060eff2a1", 392 | "sha256:71aace0c42d53abe6fc7f726c5d3b60d90f3c5c055a447950ad6ea9cec2e37d9", 393 | "sha256:7d97a4306898b05404a0dcdc32d9709b7d8832c0c542b861d9a826301719794e", 394 | "sha256:7df1e1c05304f26faa49fa752a8c690126cf98b40b91d54e6e9cc3b7d6ffe8b7", 395 | "sha256:8270252effc60b9642b423189a2fe90eb6b59e87cbee54549db3f5562ff8d1b8", 396 | "sha256:867a5ad16892bf20e6c4ea2aab1971f45645ff3102ad29bd84c86027fa99997b", 397 | "sha256:877473e675fdcc113c138813a5dd440da0769a2d81f4d86614e5d62b69497155", 398 | "sha256:8892f89999ffd992208754851e5a052f6b5db70a1e3f7d54b17c5211e37a98c7", 399 | "sha256:9a9845c4c6bb56e508651f005c4aeb0404e518c6f000d5a1123ab077ab769f5c", 400 | "sha256:a1e6e96217a0f72e2b8629e271e1b280c6fa3fe6e59fa8f6701bec14e3354325", 401 | "sha256:a8156e6a7f5e2a0ff0c5b21d6bcb45145efece1909efcbbbf48c56f8da68221d", 402 | "sha256:a9506a7e80bcf6eacfff7f804c0ad5350c8c95b9010e4356a4b36f5322f09abb", 403 | "sha256:af310ec8335016b5e52cae60cda4a4f2a60a788cbb949a4fbea13d441aa5a09e", 404 | "sha256:b0297b1e05fd128d26cc2460c810d42e205d16d76799526dfa8c8ccd50e74959", 405 | "sha256:bf68f4b2b6683e52bec69273562df15af352e5ed25d1b6641e7efddc5951d1a7", 406 | "sha256:d0c1bc2fa9a7285719e5678584f6b92572a5b639d0e471bb8d4b650a1a910920", 407 | "sha256:d4d9d6c1a455d4babd320203b918ccc7fcbefe308615c521062bc2ba1aa4d26e", 408 | "sha256:db1fa631737dab9fa0b37f3979d8d2631e348c3b4e8325d6873c2541d0ae5a48", 409 | "sha256:dd93ea5c0c7f3e25335ab7d22a507b1dc43976e1345508f845efc573d3d779d8", 410 | "sha256:f44e517131a98f7a76696a7b21b164bcb85291cee106a23beccce454e1f433a4", 411 | "sha256:f7ee479e96f7ee350db1cf24afa5685a5899e2b34992fb99e1f7c1b0b758d263" 412 | ], 413 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 414 | "version": "==5.4.0" 415 | } 416 | }, 417 | "develop": { 418 | "attrs": { 419 | "hashes": [ 420 | "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1", 421 | "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb" 422 | ], 423 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 424 | "version": "==21.2.0" 425 | }, 426 | "coverage": { 427 | "hashes": [ 428 | "sha256:04560539c19ec26995ecfb3d9307ff154fbb9a172cb57e3b3cfc4ced673103d1", 429 | "sha256:1549e1d08ce38259de2bc3e9a0d5f3642ff4a8f500ffc1b2df73fd621a6cdfc0", 430 | "sha256:1db67c497688fd4ba85b373b37cc52c50d437fd7267520ecd77bddbd89ea22c9", 431 | "sha256:30922626ce6f7a5a30bdba984ad21021529d3d05a68b4f71ea3b16bda35b8895", 432 | "sha256:36e9040a43d2017f2787b28d365a4bb33fcd792c7ff46a047a04094dc0e2a30d", 433 | "sha256:381d773d896cc7f8ba4ff3b92dee4ed740fb88dfe33b6e42efc5e8ab6dfa1cfe", 434 | "sha256:3bbda1b550e70fa6ac40533d3f23acd4f4e9cb4e6e77251ce77fdf41b3309fb2", 435 | "sha256:3be1206dc09fb6298de3fce70593e27436862331a85daee36270b6d0e1c251c4", 436 | "sha256:424c44f65e8be58b54e2b0bd1515e434b940679624b1b72726147cfc6a9fc7ce", 437 | "sha256:4b34ae4f51bbfa5f96b758b55a163d502be3dcb24f505d0227858c2b3f94f5b9", 438 | "sha256:4e28d2a195c533b58fc94a12826f4431726d8eb029ac21d874345f943530c122", 439 | "sha256:53a294dc53cfb39c74758edaa6305193fb4258a30b1f6af24b360a6c8bd0ffa7", 440 | "sha256:60e51a3dd55540bec686d7fff61b05048ca31e804c1f32cbb44533e6372d9cc3", 441 | "sha256:61b598cbdbaae22d9e34e3f675997194342f866bb1d781da5d0be54783dce1ff", 442 | "sha256:6807947a09510dc31fa86f43595bf3a14017cd60bf633cc746d52141bfa6b149", 443 | "sha256:6a6a9409223a27d5ef3cca57dd7cd4dfcb64aadf2fad5c3b787830ac9223e01a", 444 | "sha256:7092eab374346121805fb637572483270324407bf150c30a3b161fc0c4ca5164", 445 | "sha256:77b1da5767ed2f44611bc9bc019bc93c03fa495728ec389759b6e9e5039ac6b1", 446 | "sha256:8251b37be1f2cd9c0e5ccd9ae0380909c24d2a5ed2162a41fcdbafaf59a85ebd", 447 | "sha256:9f1627e162e3864a596486774876415a7410021f4b67fd2d9efdf93ade681afc", 448 | "sha256:a1b73c7c4d2a42b9d37dd43199c5711d91424ff3c6c22681bc132db4a4afec6f", 449 | "sha256:a82d79586a0a4f5fd1cf153e647464ced402938fbccb3ffc358c7babd4da1dd9", 450 | "sha256:abbff240f77347d17306d3201e14431519bf64495648ca5a49571f988f88dee9", 451 | "sha256:ad9b8c1206ae41d46ec7380b78ba735ebb77758a650643e841dd3894966c31d0", 452 | "sha256:bbffde2a68398682623d9dd8c0ca3f46fda074709b26fcf08ae7a4c431a6ab2d", 453 | "sha256:bcae10fccb27ca2a5f456bf64d84110a5a74144be3136a5e598f9d9fb48c0caa", 454 | "sha256:c9cd3828bbe1a40070c11fe16a51df733fd2f0cb0d745fb83b7b5c1f05967df7", 455 | "sha256:cd1cf1deb3d5544bd942356364a2fdc8959bad2b6cf6eb17f47d301ea34ae822", 456 | "sha256:d036dc1ed8e1388e995833c62325df3f996675779541f682677efc6af71e96cc", 457 | "sha256:db42baa892cba723326284490283a68d4de516bfb5aaba369b4e3b2787a778b7", 458 | "sha256:e4fb7ced4d9dec77d6cf533acfbf8e1415fe799430366affb18d69ee8a3c6330", 459 | "sha256:e7a0b42db2a47ecb488cde14e0f6c7679a2c5a9f44814393b162ff6397fcdfbb", 460 | "sha256:f2f184bf38e74f152eed7f87e345b51f3ab0b703842f447c22efe35e59942c24" 461 | ], 462 | "index": "pypi", 463 | "version": "==6.0.2" 464 | }, 465 | "iniconfig": { 466 | "hashes": [ 467 | "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", 468 | "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32" 469 | ], 470 | "version": "==1.1.1" 471 | }, 472 | "packaging": { 473 | "hashes": [ 474 | "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7", 475 | "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14" 476 | ], 477 | "markers": "python_version >= '3.6'", 478 | "version": "==21.0" 479 | }, 480 | "pluggy": { 481 | "hashes": [ 482 | "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159", 483 | "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3" 484 | ], 485 | "markers": "python_version >= '3.6'", 486 | "version": "==1.0.0" 487 | }, 488 | "py": { 489 | "hashes": [ 490 | "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3", 491 | "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a" 492 | ], 493 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 494 | "version": "==1.10.0" 495 | }, 496 | "pyparsing": { 497 | "hashes": [ 498 | "sha256:09fa60a2e3bc3c6a23c2ed23332199d0e57b2c676b9193750cfc22a4358b69c2", 499 | "sha256:9366e57ad2d2faa8dd89775ef284ecae0fc4012c24717f3705613b262d712090" 500 | ], 501 | "markers": "python_version >= '3.6'", 502 | "version": "==3.0.2" 503 | }, 504 | "pytest": { 505 | "hashes": [ 506 | "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89", 507 | "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134" 508 | ], 509 | "index": "pypi", 510 | "version": "==6.2.5" 511 | }, 512 | "toml": { 513 | "hashes": [ 514 | "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", 515 | "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" 516 | ], 517 | "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", 518 | "version": "==0.10.2" 519 | } 520 | } 521 | } 522 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Scrabble 2 | A strictly multi-player Scrabble game that allows gameplay between people on a network. The game is based on [Hasbro's Scrabble](https://scrabble.hasbro.com/en-us), and uses a Scrabble words database from 2018. 3 | 4 | ### Extra Features 5 | - Time to play: Game host can configure a time within which each player must play before the turn is automatically skipped. 6 | - Push to Talk: Allows players talk to each other via PTT audio. Only works over HTTPS or `localhost` due to security policies. 7 | - 3-Try-Validation: Each player has at most three tries to play a valid word (since challenging plays isn't possible), before which the turn is automatically skipped. 8 | - Game Logs: See history of in-game plays. 9 | - AutoSave: Game automatically saves, so you can always resume it. 10 | - Resume Game: If you leave a game session (by using the in-game `Leave` button), you can resume it using the session ID and the names of the players who played in the session. 11 | 12 | ### Requirements 13 | - A laptop, or any device that allows dragging web elements with a mouse. 14 | - Please note that development was tailored specifically towards laptops/desktops, as they fully support the [HTML5 Draggable](https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API) API, unlike mobile devices or tablets. 15 | - Err...just use a laptop and adjust the zoom as needed. **See the Known Issues section below for game layout issues.** 16 | - Python 3.6+ 17 | 18 | ### Usage 19 | To play the game, you're either the **host** or not (a mere **player**). 20 | 21 | The host is the person who, well, hosts a game session to which other players connect to using the host's **Session ID**. 22 | The following instructions are meant for the host, as [s]he has to host the server on which the game would run. 23 | 24 | #### Dependency Installation 25 | As a host, you need the following: 26 | - A Python virtual environment. If you don't already know how to create one, see [here](https://realpython.com/lessons/creating-virtual-environment/). 27 | - Pipenv: In the activated virtual environment, simply run `pip3 install pipenv`. 28 | 29 | #### Game Procedure 30 | To host a game session: 31 | 32 | - Clone and checkout this `master` branch; or alternatively, simply download it as a zip file. 33 | - Ensure the virtual environment is activated. Then, using `pipenv`, install the game's requirements: `pipenv install`. 34 | - When that's done, simply run `pipenv run python3 wsgi.py`. This starts the game server. 35 | - By default, the application tries to get your local private IP address (not `localhost`) to run on. However, if you're using a VPN or are on a weird network, it may get it wrong. Feel free to modify the `host` variable in the `wsgi.py` file to your actual local private IP address. The only caveat is that you have to host the app on the private IP address and not `localhost`. This is in order to allow other players on your network to visit the application. 36 | - Modify firewall permissions to allow incoming traffic from port `5005`. 37 | - Navigate to your local private IP address (`192.168...:5005`) and get playing. 38 | - Host a game session. During this, a Session ID would be automatically generated for you. Share this with the people you want to play with. 39 | 40 | ### Sneak-Peak 41 | Gameplay sneak-peak 42 | ![](gameplay.png) 43 | 44 | ### Known Issues / Ongoing Bug Fixes 45 | - Certain in-rack drags of pieces cause them to disappear. Only happens if it's not your turn to play. 46 | - The final scoring pipeline hasn't been updated to add the scores of other players to the player who emptied his/her rack to end the game. 47 | - Zoom needs to be adjusted (reduced or increased) to get proper game layout. My display is 1920x1080 and it was developed for that. This isn't anything but my [very] poor CSS skills. I should fix this soon 48 | 49 | ### Collaborations and Development 50 | - Development is done in the `dev` branch: `git checkout dev`. Current issues are in the `DevFlow.md` file. 51 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from server.utils import token_generator 4 | from dotenv import load_dotenv, find_dotenv 5 | 6 | # Load environment variables 7 | load_dotenv(find_dotenv()) 8 | 9 | class CommonConfig: 10 | """Shared configuration across all environments""" 11 | 12 | ENV = os.getenv("ENV") 13 | ASYNC_MODE = 'eventlet' 14 | SECRET_KEY = token_generator() 15 | 16 | SQLALCHEMY_ECHO = False 17 | SQLALCHEMY_TRACK_MODIFICATIONS = False 18 | SQLALCHEMY_DATABASE_URI = f'sqlite:///{os.getcwd()}/scrabble.db' 19 | 20 | class Dev(CommonConfig): 21 | """Development Config""" 22 | DEBUG = TESTING = True 23 | 24 | class Prod(CommonConfig): 25 | """Production Config""" 26 | DEBUG = TESTING = False -------------------------------------------------------------------------------- /gameplay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olumidesan/scrabble/4c1b07bf09e23b82d5afeca4438078e5f5e233e8/gameplay.png -------------------------------------------------------------------------------- /scrabble.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olumidesan/scrabble/4c1b07bf09e23b82d5afeca4438078e5f5e233e8/scrabble.db -------------------------------------------------------------------------------- /server/__init__.py: -------------------------------------------------------------------------------- 1 | # Begin with monkey-patch 2 | import eventlet 3 | eventlet.monkey_patch() 4 | 5 | from flask import Flask 6 | from flask_cors import CORS 7 | from flask_socketio import SocketIO 8 | from flask_sqlalchemy import SQLAlchemy 9 | 10 | from .utils import token_generator 11 | 12 | sio = SocketIO() 13 | db = SQLAlchemy() 14 | 15 | CLIENT_TOKEN = token_generator() 16 | 17 | 18 | def create_app(config_class): 19 | app = Flask(__name__, 20 | static_folder='build', 21 | static_url_path='/build') 22 | 23 | app.config.from_object(config_class) 24 | 25 | from .api import api_bp 26 | from .main import main_bp 27 | 28 | # Register DB models 29 | from . import models 30 | 31 | # Register SocketIO 32 | from . import socketio 33 | 34 | # Register blueprints 35 | app.register_blueprint(api_bp) 36 | app.register_blueprint(main_bp) 37 | 38 | CORS(app) 39 | db.init_app(app) 40 | sio.init_app(app, 41 | cors_allowed_origins='*', 42 | async_mode=app.config['ASYNC_MODE']) 43 | 44 | return app -------------------------------------------------------------------------------- /server/api/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint 2 | 3 | api_bp = Blueprint('api', __name__, url_prefix='/api/v1') 4 | 5 | from . import routes 6 | -------------------------------------------------------------------------------- /server/api/auth.py: -------------------------------------------------------------------------------- 1 | from flask_socketio import emit 2 | from flask import current_app 3 | from server import CLIENT_TOKEN 4 | from .errors import error_response 5 | from flask_httpauth import HTTPTokenAuth 6 | 7 | token_auth = HTTPTokenAuth(scheme='Bearer') 8 | 9 | 10 | @token_auth.verify_token 11 | def verify_token(token): 12 | # Accept all requests if in development 13 | if current_app.config.get("ENV") == "DEV": 14 | return True 15 | return token == CLIENT_TOKEN 16 | 17 | 18 | @token_auth.error_handler 19 | def token_auth_error(): 20 | return error_response(401) -------------------------------------------------------------------------------- /server/api/errors.py: -------------------------------------------------------------------------------- 1 | 2 | from flask import jsonify 3 | from werkzeug.http import HTTP_STATUS_CODES 4 | 5 | 6 | def error_response(status_code, message=None): 7 | payload = {'error': HTTP_STATUS_CODES.get(status_code, 'Unknown error')} 8 | if message: 9 | payload['message'] = message 10 | response = jsonify(payload) 11 | response.status_code = status_code 12 | return response 13 | 14 | 15 | def bad_request(message): 16 | return error_response(400, message) 17 | -------------------------------------------------------------------------------- /server/api/routes.py: -------------------------------------------------------------------------------- 1 | 2 | from time import sleep 3 | from threading import RLock 4 | 5 | from . import api_bp as api 6 | from .auth import token_auth 7 | from flask import jsonify, request 8 | 9 | from server import sio 10 | from server.models import Word 11 | from server.socketio import game_rooms 12 | 13 | GameLock = RLock() 14 | router = api.route 15 | 16 | 17 | """Server ping route""" 18 | @router('/ping') 19 | @token_auth.login_required 20 | def ping(): return dict(status="pingSuccess") 21 | 22 | 23 | @router('/room/') 24 | @token_auth.login_required 25 | def sio_rooms(room_id): 26 | """ 27 | Returns the contents of a game room, if it exists 28 | """ 29 | 30 | name = request.args.get("name") 31 | mode = request.args.get("mode") 32 | room = game_rooms.get_room(room_id) 33 | 34 | if room: 35 | if mode == "join": 36 | # Check if name is already being used 37 | existing_names = list(filter(lambda x: x['name'].lower() == name.lower(), room.get_connected_players())) 38 | 39 | if existing_names: 40 | response = dict(status="nameError", message="Name is already being used by another player") 41 | else: 42 | response = dict(status="success", room=room.serialize()) 43 | 44 | elif mode == "resume": 45 | name = name.capitalize() 46 | 47 | if name not in [p.name for p in room.get_all_players()]: 48 | response = dict(status="nameError", message="No such player in game session") 49 | else: 50 | player = room.get_player(name) 51 | if player.is_active: 52 | response = dict(status="nameError", message=f"{name} has already joined the game session") 53 | else: 54 | response = dict(status="success", limit=room.limit, player=room.get_player(name).serialize()) 55 | 56 | else: 57 | response = dict(status="error", message="Invalid game mode") 58 | 59 | else: 60 | response = dict(status="error", message="No such game room") 61 | 62 | return response 63 | 64 | 65 | @router('/bag/') 66 | @token_auth.login_required 67 | def bag(amount): 68 | """ 69 | Returns the requested number of pieces 70 | from the bag in the room 71 | """ 72 | room_id = request.args.get('roomID') 73 | game_room = game_rooms.get_room(room_id) 74 | 75 | if not game_room: 76 | return dict(status="error", message="No such game room") 77 | 78 | # Synchronize fetch 79 | with GameLock: 80 | pieces = game_room.bag.get_pieces(amount) 81 | 82 | return dict(status="success", pieces=pieces) 83 | 84 | 85 | @router('/cache', methods=['POST']) 86 | @token_auth.login_required 87 | def cache(): 88 | """ 89 | Caches state of the game 90 | """ 91 | payload = request.get_json() 92 | 93 | rack = payload.get('rack') 94 | room_id = payload.get('roomID') 95 | game_room = game_rooms.get_room(room_id) 96 | player_name = payload.get('player').get('name') 97 | is_player_turn = payload.get('player').get('turn') 98 | 99 | if not game_room: 100 | return dict(status="error", message="No such game room") 101 | 102 | game_room.update_board(payload.get('board')) 103 | player = game_room.get_player(player_name) 104 | player.set_rack(rack) 105 | 106 | if is_player_turn: 107 | player.set_turn(True) 108 | else: 109 | player.set_turn(False) 110 | 111 | return dict(status="success") 112 | 113 | 114 | @router('/logs/') 115 | @token_auth.login_required 116 | def logs(room_id): 117 | """ 118 | Returns game history in a game room 119 | """ 120 | game_room = game_rooms.get_room(room_id) 121 | 122 | if not game_room: 123 | return dict(status="error", message="No such game room") 124 | 125 | return dict(status="success", logs=game_room.get_logs()) 126 | 127 | 128 | @router('/validate', methods=['POST']) 129 | @token_auth.login_required 130 | def validate(): 131 | """ 132 | Validates that all the posted words are valid 133 | """ 134 | # Get the words to be validated 135 | words = request.get_json().get('words') 136 | 137 | # Default response: Success, with all words valid 138 | response = dict(status="success", message="All words are valid") 139 | 140 | # Validate all. If any is invalid, return an error 141 | for word in words: 142 | word = word.upper() 143 | 144 | valid = Word.query.filter_by(word=word).first() 145 | if not valid: 146 | response = dict(status="error", message=f"'{word}' is not a valid Scrabble word") 147 | break # Shortcircuit 148 | 149 | return response 150 | 151 | 152 | @router('/scores', methods=['POST']) 153 | @token_auth.login_required 154 | def scores(): 155 | """ 156 | Updates the players' scores in a room 157 | """ 158 | 159 | payload = request.get_json() 160 | 161 | name = payload.get('name') 162 | score = payload.get('score') 163 | room_id = payload.get('roomID') 164 | 165 | game_room = game_rooms.get_room(room_id) 166 | 167 | # Get player in room and update score 168 | player = game_room.get_player(name) 169 | player.set_score(score) 170 | 171 | # If all players have sent, announce winner to all 172 | if game_room.has_game_ended(): 173 | sleep(1) 174 | sio.emit("gameEnd", game_room.get_connected_players(), room=room_id) 175 | 176 | return dict(status="success") -------------------------------------------------------------------------------- /server/build/asset-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": { 3 | "main.css": "/build/static/css/main.0ac7eade.chunk.css", 4 | "main.js": "/build/static/js/main.c7fc86e0.chunk.js", 5 | "main.js.map": "/build/static/js/main.c7fc86e0.chunk.js.map", 6 | "runtime-main.js": "/build/static/js/runtime-main.cab1df26.js", 7 | "runtime-main.js.map": "/build/static/js/runtime-main.cab1df26.js.map", 8 | "static/js/2.104ca3f2.chunk.js": "/build/static/js/2.104ca3f2.chunk.js", 9 | "static/js/2.104ca3f2.chunk.js.map": "/build/static/js/2.104ca3f2.chunk.js.map", 10 | "static/js/3.1b5ba067.chunk.js": "/build/static/js/3.1b5ba067.chunk.js", 11 | "static/js/3.1b5ba067.chunk.js.map": "/build/static/js/3.1b5ba067.chunk.js.map", 12 | "index.html": "/build/index.html", 13 | "static/css/main.0ac7eade.chunk.css.map": "/build/static/css/main.0ac7eade.chunk.css.map", 14 | "static/js/2.104ca3f2.chunk.js.LICENSE.txt": "/build/static/js/2.104ca3f2.chunk.js.LICENSE.txt" 15 | }, 16 | "entrypoints": [ 17 | "static/js/runtime-main.cab1df26.js", 18 | "static/js/2.104ca3f2.chunk.js", 19 | "static/css/main.0ac7eade.chunk.css", 20 | "static/js/main.c7fc86e0.chunk.js" 21 | ] 22 | } -------------------------------------------------------------------------------- /server/build/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olumidesan/scrabble/4c1b07bf09e23b82d5afeca4438078e5f5e233e8/server/build/favicon-16x16.png -------------------------------------------------------------------------------- /server/build/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olumidesan/scrabble/4c1b07bf09e23b82d5afeca4438078e5f5e233e8/server/build/favicon-32x32.png -------------------------------------------------------------------------------- /server/build/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olumidesan/scrabble/4c1b07bf09e23b82d5afeca4438078e5f5e233e8/server/build/favicon.ico -------------------------------------------------------------------------------- /server/build/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olumidesan/scrabble/4c1b07bf09e23b82d5afeca4438078e5f5e233e8/server/build/logo192.png -------------------------------------------------------------------------------- /server/build/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olumidesan/scrabble/4c1b07bf09e23b82d5afeca4438078e5f5e233e8/server/build/logo512.png -------------------------------------------------------------------------------- /server/build/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Scrabble", 3 | "name": "Scrabble", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": " 32x32 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /server/build/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /server/build/static/css/main.0ac7eade.chunk.css: -------------------------------------------------------------------------------- 1 | /*! tailwindcss v2.2.16 | MIT License | https://tailwindcss.com */ 2 | 3 | /*! modern-normalize v1.1.0 | MIT License | https://github.com/sindresorhus/modern-normalize */html{-moz-tab-size:4;tab-size:4;line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0;font-family:system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"}hr{height:0;color:inherit}abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Consolas,"Liberation Mono",Menlo,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,select{text-transform:none}[type=button],button{-webkit-appearance:button}legend{padding:0}progress{vertical-align:baseline}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}button{background-color:transparent;background-image:none}fieldset,ol,ul{margin:0;padding:0}ol,ul{list-style:none}html{font-family:Roboto,Helvetica,Arial,sans-serif;line-height:1.5}body{font-family:inherit;line-height:inherit}*,:after,:before{box-sizing:border-box;border:0 solid}hr{border-top-width:1px}img{border-style:solid}textarea{resize:vertical}input::-webkit-input-placeholder,textarea::-webkit-input-placeholder{opacity:1;color:#9ca3af}input:-ms-input-placeholder,textarea:-ms-input-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button{cursor:pointer}table{border-collapse:collapse}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}button,input,optgroup,select,textarea{padding:0;line-height:inherit;color:inherit}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,:after,:before{--tw-border-opacity:1;border-color:rgba(229,231,235,var(--tw-border-opacity))}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.pointer-events-none{pointer-events:none}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-0{right:0;left:0}.inset-0,.inset-y-0{top:0;bottom:0}.right-0{right:0}.right-1{right:.25rem}.right-0\.5{right:.125rem}.right-1\.5{right:.375rem}.bottom-0{bottom:0}.bottom-0\.5{bottom:.125rem}.z-40{z-index:40}.z-50{z-index:50}.mx-3{margin-left:.75rem;margin-right:.75rem}.mx-4{margin-left:1rem;margin-right:1rem}.mx-auto{margin-left:auto;margin-right:auto}.-mx-3{margin-left:-.75rem;margin-right:-.75rem}.my-4{margin-top:1rem;margin-bottom:1rem}.my-6{margin-top:1.5rem;margin-bottom:1.5rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mr-2{margin-right:.5rem}.mb-0{margin-bottom:0}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-5{margin-bottom:1.25rem}.mb-6{margin-bottom:1.5rem}.block{display:block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.hidden{display:none}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-12{height:3rem}.h-14{height:3.5rem}.h-16{height:4rem}.h-96{height:24rem}.h-3\/4{height:75%}.h-full{height:100%}.h-screen{height:100vh}.w-5{width:1.25rem}.w-8{width:2rem}.w-12{width:3rem}.w-14{width:3.5rem}.w-32{width:8rem}.w-36{width:9rem}.w-80{width:20rem}.w-96{width:24rem}.w-450{width:450px}.w-auto{width:auto}.w-1\/2{width:50%}.w-1\/5{width:20%}.w-4\/5{width:80%}.w-1\/6{width:16.666667%}.w-full{width:100%}.w-screen{width:100vw}.max-w-sm{max-width:24rem}.max-w-3xl{max-width:48rem}.flex-1{flex:1 1 0%}.flex-none{flex:none}.table-auto{table-layout:auto}@-webkit-keyframes spin{to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes spin{to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@-webkit-keyframes ping{75%,to{-webkit-transform:scale(2);transform:scale(2);opacity:0}}@keyframes ping{75%,to{-webkit-transform:scale(2);transform:scale(2);opacity:0}}@-webkit-keyframes pulse{50%{opacity:.5}}@keyframes pulse{50%{opacity:.5}}@-webkit-keyframes bounce{0%,to{-webkit-transform:translateY(-25%);transform:translateY(-25%);-webkit-animation-timing-function:cubic-bezier(.8,0,1,1);animation-timing-function:cubic-bezier(.8,0,1,1)}50%{-webkit-transform:none;transform:none;-webkit-animation-timing-function:cubic-bezier(0,0,.2,1);animation-timing-function:cubic-bezier(0,0,.2,1)}}@keyframes bounce{0%,to{-webkit-transform:translateY(-25%);transform:translateY(-25%);-webkit-animation-timing-function:cubic-bezier(.8,0,1,1);animation-timing-function:cubic-bezier(.8,0,1,1)}50%{-webkit-transform:none;transform:none;-webkit-animation-timing-function:cubic-bezier(0,0,.2,1);animation-timing-function:cubic-bezier(0,0,.2,1)}}.animate-spin{-webkit-animation:spin 1s linear infinite;animation:spin 1s linear infinite}.cursor-pointer{cursor:pointer}.cursor-help{cursor:help}.cursor-not-allowed{cursor:not-allowed}.appearance-none{-webkit-appearance:none;appearance:none}.flex-col{flex-direction:column}.flex-col-reverse{flex-direction:column-reverse}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.justify-around{justify-content:space-around}.space-x-0>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(0px*var(--tw-space-x-reverse));margin-left:calc(0px*(1 - var(--tw-space-x-reverse)))}.space-x-1>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(0.25rem*var(--tw-space-x-reverse));margin-left:calc(0.25rem*(1 - var(--tw-space-x-reverse)))}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(0.5rem*var(--tw-space-x-reverse));margin-left:calc(0.5rem*(1 - var(--tw-space-x-reverse)))}.space-x-6>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(1.5rem*var(--tw-space-x-reverse));margin-left:calc(1.5rem*(1 - var(--tw-space-x-reverse)))}.space-x-10>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(2.5rem*var(--tw-space-x-reverse));margin-left:calc(2.5rem*(1 - var(--tw-space-x-reverse)))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem*var(--tw-space-y-reverse))}.self-end{align-self:flex-end}.self-center{align-self:center}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.overflow-x-hidden{overflow-x:hidden}.rounded{border-radius:.25rem}.rounded-md{border-radius:.375rem}.rounded-lg{border-radius:.5rem}.rounded-t{border-top-left-radius:.25rem}.rounded-r,.rounded-t{border-top-right-radius:.25rem}.rounded-r{border-bottom-right-radius:.25rem}.border-0{border-width:0}.border-2{border-width:2px}.border-4{border-width:4px}.border-8{border-width:8px}.border{border-width:1px}.border-t{border-top-width:1px}.border-r-2{border-right-width:2px}.border-b{border-bottom-width:1px}.border-solid{border-style:solid}.border-black{--tw-border-opacity:1;border-color:rgba(0,0,0,var(--tw-border-opacity))}.border-gray-200{--tw-border-opacity:1;border-color:rgba(229,231,235,var(--tw-border-opacity))}.border-gray-300{--tw-border-opacity:1;border-color:rgba(209,213,219,var(--tw-border-opacity))}.border-gray-500{--tw-border-opacity:1;border-color:rgba(107,114,128,var(--tw-border-opacity))}.border-red-300{--tw-border-opacity:1;border-color:rgba(252,165,165,var(--tw-border-opacity))}.border-red-700{--tw-border-opacity:1;border-color:rgba(185,28,28,var(--tw-border-opacity))}.border-yellow-300{--tw-border-opacity:1;border-color:rgba(252,211,77,var(--tw-border-opacity))}.border-yellow-400{--tw-border-opacity:1;border-color:rgba(251,191,36,var(--tw-border-opacity))}.border-green-300{--tw-border-opacity:1;border-color:rgba(110,231,183,var(--tw-border-opacity))}.border-green-600{--tw-border-opacity:1;border-color:rgba(5,150,105,var(--tw-border-opacity))}.border-green-700{--tw-border-opacity:1;border-color:rgba(4,120,87,var(--tw-border-opacity))}.border-blue-300{--tw-border-opacity:1;border-color:rgba(147,197,253,var(--tw-border-opacity))}.border-blue-600{--tw-border-opacity:1;border-color:rgba(37,99,235,var(--tw-border-opacity))}.border-blue-700{--tw-border-opacity:1;border-color:rgba(29,78,216,var(--tw-border-opacity))}.hover\:border-yellow-200:hover{--tw-border-opacity:1;border-color:rgba(253,230,138,var(--tw-border-opacity))}.hover\:border-yellow-300:hover{--tw-border-opacity:1;border-color:rgba(252,211,77,var(--tw-border-opacity))}.hover\:border-green-500:hover{--tw-border-opacity:1;border-color:rgba(16,185,129,var(--tw-border-opacity))}.hover\:border-green-600:hover{--tw-border-opacity:1;border-color:rgba(5,150,105,var(--tw-border-opacity))}.hover\:border-blue-700:hover{--tw-border-opacity:1;border-color:rgba(29,78,216,var(--tw-border-opacity))}.focus\:border-gray-500:focus{--tw-border-opacity:1;border-color:rgba(107,114,128,var(--tw-border-opacity))}.border-opacity-70{--tw-border-opacity:0.7}.border-opacity-100{--tw-border-opacity:1}.bg-black{--tw-bg-opacity:1;background-color:rgba(0,0,0,var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity:1;background-color:rgba(255,255,255,var(--tw-bg-opacity))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgba(243,244,246,var(--tw-bg-opacity))}.bg-gray-300{--tw-bg-opacity:1;background-color:rgba(209,213,219,var(--tw-bg-opacity))}.bg-gray-500{--tw-bg-opacity:1;background-color:rgba(107,114,128,var(--tw-bg-opacity))}.bg-gray-700{--tw-bg-opacity:1;background-color:rgba(55,65,81,var(--tw-bg-opacity))}.bg-red-100{--tw-bg-opacity:1;background-color:rgba(254,226,226,var(--tw-bg-opacity))}.bg-red-600{--tw-bg-opacity:1;background-color:rgba(220,38,38,var(--tw-bg-opacity))}.bg-red-700{--tw-bg-opacity:1;background-color:rgba(185,28,28,var(--tw-bg-opacity))}.bg-yellow-100{--tw-bg-opacity:1;background-color:rgba(254,243,199,var(--tw-bg-opacity))}.bg-yellow-200{--tw-bg-opacity:1;background-color:rgba(253,230,138,var(--tw-bg-opacity))}.bg-yellow-300{--tw-bg-opacity:1;background-color:rgba(252,211,77,var(--tw-bg-opacity))}.bg-yellow-500{--tw-bg-opacity:1;background-color:rgba(245,158,11,var(--tw-bg-opacity))}.bg-green-100{--tw-bg-opacity:1;background-color:rgba(209,250,229,var(--tw-bg-opacity))}.bg-green-500{--tw-bg-opacity:1;background-color:rgba(16,185,129,var(--tw-bg-opacity))}.bg-green-600{--tw-bg-opacity:1;background-color:rgba(5,150,105,var(--tw-bg-opacity))}.bg-blue-100{--tw-bg-opacity:1;background-color:rgba(219,234,254,var(--tw-bg-opacity))}.bg-blue-400{--tw-bg-opacity:1;background-color:rgba(96,165,250,var(--tw-bg-opacity))}.bg-blue-600{--tw-bg-opacity:1;background-color:rgba(37,99,235,var(--tw-bg-opacity))}.bg-blue-800{--tw-bg-opacity:1;background-color:rgba(30,64,175,var(--tw-bg-opacity))}.hover\:bg-gray-700:hover{--tw-bg-opacity:1;background-color:rgba(55,65,81,var(--tw-bg-opacity))}.hover\:bg-red-700:hover{--tw-bg-opacity:1;background-color:rgba(185,28,28,var(--tw-bg-opacity))}.hover\:bg-yellow-300:hover{--tw-bg-opacity:1;background-color:rgba(252,211,77,var(--tw-bg-opacity))}.hover\:bg-green-700:hover{--tw-bg-opacity:1;background-color:rgba(4,120,87,var(--tw-bg-opacity))}.hover\:bg-blue-700:hover{--tw-bg-opacity:1;background-color:rgba(29,78,216,var(--tw-bg-opacity))}.focus\:bg-white:focus{--tw-bg-opacity:1;background-color:rgba(255,255,255,var(--tw-bg-opacity))}.p-3{padding:.75rem}.p-4{padding:1rem}.p-10{padding:2.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.px-8{padding-left:2rem;padding-right:2rem}.px-10{padding-left:2.5rem;padding-right:2.5rem}.py-0{padding-top:0;padding-bottom:0}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pt-1{padding-top:.25rem}.pt-2{padding-top:.5rem}.pt-4{padding-top:1rem}.pt-5{padding-top:1.25rem}.pt-6{padding-top:1.5rem}.pt-7{padding-top:1.75rem}.pt-8{padding-top:2rem}.pt-28{padding-top:7rem}.pr-1{padding-right:.25rem}.pr-2{padding-right:.5rem}.pr-4{padding-right:1rem}.pr-5{padding-right:1.25rem}.pr-8{padding-right:2rem}.pb-1{padding-bottom:.25rem}.pb-2{padding-bottom:.5rem}.pb-3{padding-bottom:.75rem}.pb-6{padding-bottom:1.5rem}.pb-7{padding-bottom:1.75rem}.pb-9{padding-bottom:2.25rem}.pb-10{padding-bottom:2.5rem}.pl-1{padding-left:.25rem}.pl-2{padding-left:.5rem}.text-left{text-align:left}.text-xs{font-size:.75rem;line-height:1rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-lg{font-size:1.125rem}.text-lg,.text-xl{line-height:1.75rem}.text-xl{font-size:1.25rem}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.font-normal{font-weight:400}.font-medium{font-weight:500}.font-semibold{font-weight:600}.font-bold{font-weight:700}.uppercase{text-transform:uppercase}.capitalize{text-transform:capitalize}.italic{font-style:italic}.leading-tight{line-height:1.25}.tracking-wide{letter-spacing:.025em}.text-black{--tw-text-opacity:1;color:rgba(0,0,0,var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgba(255,255,255,var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgba(156,163,175,var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgba(107,114,128,var(--tw-text-opacity))}.text-gray-700{--tw-text-opacity:1;color:rgba(55,65,81,var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity:1;color:rgba(31,41,55,var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgba(17,24,39,var(--tw-text-opacity))}.text-red-500{--tw-text-opacity:1;color:rgba(239,68,68,var(--tw-text-opacity))}.text-red-600{--tw-text-opacity:1;color:rgba(220,38,38,var(--tw-text-opacity))}.text-red-800{--tw-text-opacity:1;color:rgba(153,27,27,var(--tw-text-opacity))}.text-yellow-800{--tw-text-opacity:1;color:rgba(146,64,14,var(--tw-text-opacity))}.text-green-800{--tw-text-opacity:1;color:rgba(6,95,70,var(--tw-text-opacity))}.text-blue-400{--tw-text-opacity:1;color:rgba(96,165,250,var(--tw-text-opacity))}.text-blue-500{--tw-text-opacity:1;color:rgba(59,130,246,var(--tw-text-opacity))}.text-blue-900{--tw-text-opacity:1;color:rgba(30,58,138,var(--tw-text-opacity))}.hover\:text-gray-700:hover{--tw-text-opacity:1;color:rgba(55,65,81,var(--tw-text-opacity))}.opacity-75{opacity:.75}*,:after,:before{--tw-shadow:0 0 transparent}.shadow-lg{--tw-shadow:0 10px 15px -3px rgba(0,0,0,0.1),0 4px 6px -2px rgba(0,0,0,0.05)}.shadow-2xl,.shadow-lg{box-shadow:var(--tw-ring-offset-shadow,0 0 transparent),var(--tw-ring-shadow,0 0 transparent),var(--tw-shadow)}.shadow-2xl{--tw-shadow:0 25px 50px -12px rgba(0,0,0,0.25)}.focus\:outline-none:focus,.outline-none{outline:2px solid transparent;outline-offset:2px}*,:after,:before{--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,0.5);--tw-ring-offset-shadow:0 0 transparent;--tw-ring-shadow:0 0 transparent}.filter{--tw-blur:var(--tw-empty,/*!*/ /*!*/);--tw-brightness:var(--tw-empty,/*!*/ /*!*/);--tw-contrast:var(--tw-empty,/*!*/ /*!*/);--tw-grayscale:var(--tw-empty,/*!*/ /*!*/);--tw-hue-rotate:var(--tw-empty,/*!*/ /*!*/);--tw-invert:var(--tw-empty,/*!*/ /*!*/);--tw-saturate:var(--tw-empty,/*!*/ /*!*/);--tw-sepia:var(--tw-empty,/*!*/ /*!*/);--tw-drop-shadow:var(--tw-empty,/*!*/ /*!*/);-webkit-filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}input:checked+div{--tw-border-opacity:1;border-color:rgba(59,130,246,var(--tw-border-opacity))}input:checked+div svg{display:block}.blink{-webkit-animation:blinker 1s step-start infinite;animation:blinker 1s step-start infinite}@-webkit-keyframes blinker{50%{opacity:0}}@keyframes blinker{50%{opacity:0}}@media (min-width:768px){.md\:flex{display:flex}.md\:items-center{align-items:center}}.App{text-align:center}.App-logo{height:40vmin;pointer-events:none}@media (prefers-reduced-motion:no-preference){.App-logo{-webkit-animation:App-logo-spin 20s linear infinite;animation:App-logo-spin 20s linear infinite}}.App-header{background-color:#282c34;min-height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:center;font-size:calc(10px + 2vmin);color:#fff}.App-link{color:#61dafb}@-webkit-keyframes App-logo-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes App-logo-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}} 4 | /*# sourceMappingURL=main.0ac7eade.chunk.css.map */ -------------------------------------------------------------------------------- /server/build/static/css/main.0ac7eade.chunk.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["webpack://src/index.css","","main.0ac7eade.chunk.css","webpack://src/App.css"],"names":[],"mappings":"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","file":"main.0ac7eade.chunk.css","sourcesContent":["@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\ninput:checked + div {\n @apply border-blue-500;\n}\ninput:checked + div svg {\n @apply block;\n}\n\n.blink {\n animation: blinker 1s step-start infinite;\n }\n \n @keyframes blinker {\n 50% {\n opacity: 0;\n }\n }",null,"/*! tailwindcss v2.2.16 | MIT License | https://tailwindcss.com */\n\n/*! modern-normalize v1.1.0 | MIT License | https://github.com/sindresorhus/modern-normalize */\n\n/*\nDocument\n========\n*/\n\n/**\nUse a better box model (opinionated).\n*/\n\n*,\n::before,\n::after {\n box-sizing: border-box;\n}\n\n/**\nUse a more readable tab size (opinionated).\n*/\n\nhtml {\n -moz-tab-size: 4;\n tab-size: 4;\n}\n\n/**\n1. Correct the line height in all browsers.\n2. Prevent adjustments of font size after orientation changes in iOS.\n*/\n\nhtml {\n line-height: 1.15; /* 1 */\n -webkit-text-size-adjust: 100%; /* 2 */\n}\n\n/*\nSections\n========\n*/\n\n/**\nRemove the margin in all browsers.\n*/\n\nbody {\n margin: 0;\n}\n\n/**\nImprove consistency of default fonts in all browsers. (https://github.com/sindresorhus/modern-normalize/issues/3)\n*/\n\nbody {\n font-family:\n\t\tsystem-ui,\n\t\t-apple-system, /* Firefox supports this but not yet `system-ui` */\n\t\t'Segoe UI',\n\t\tRoboto,\n\t\tHelvetica,\n\t\tArial,\n\t\tsans-serif,\n\t\t'Apple Color Emoji',\n\t\t'Segoe UI Emoji';\n}\n\n/*\nGrouping content\n================\n*/\n\n/**\n1. Add the correct height in Firefox.\n2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)\n*/\n\nhr {\n height: 0; /* 1 */\n color: inherit; /* 2 */\n}\n\n/*\nText-level semantics\n====================\n*/\n\n/**\nAdd the correct text decoration in Chrome, Edge, and Safari.\n*/\n\nabbr[title] {\n -webkit-text-decoration: underline dotted;\n text-decoration: underline dotted;\n}\n\n/**\nAdd the correct font weight in Edge and Safari.\n*/\n\nb,\nstrong {\n font-weight: bolder;\n}\n\n/**\n1. Improve consistency of default fonts in all browsers. (https://github.com/sindresorhus/modern-normalize/issues/3)\n2. Correct the odd 'em' font sizing in all browsers.\n*/\n\ncode,\nkbd,\nsamp,\npre {\n font-family:\n\t\tui-monospace,\n\t\tSFMono-Regular,\n\t\tConsolas,\n\t\t'Liberation Mono',\n\t\tMenlo,\n\t\tmonospace; /* 1 */\n font-size: 1em; /* 2 */\n}\n\n/**\nAdd the correct font size in all browsers.\n*/\n\nsmall {\n font-size: 80%;\n}\n\n/**\nPrevent 'sub' and 'sup' elements from affecting the line height in all browsers.\n*/\n\nsub,\nsup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n}\n\nsub {\n bottom: -0.25em;\n}\n\nsup {\n top: -0.5em;\n}\n\n/*\nTabular data\n============\n*/\n\n/**\n1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)\n2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)\n*/\n\ntable {\n text-indent: 0; /* 1 */\n border-color: inherit; /* 2 */\n}\n\n/*\nForms\n=====\n*/\n\n/**\n1. Change the font styles in all browsers.\n2. Remove the margin in Firefox and Safari.\n*/\n\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n font-family: inherit; /* 1 */\n font-size: 100%; /* 1 */\n line-height: 1.15; /* 1 */\n margin: 0; /* 2 */\n}\n\n/**\nRemove the inheritance of text transform in Edge and Firefox.\n1. Remove the inheritance of text transform in Firefox.\n*/\n\nbutton,\nselect { /* 1 */\n text-transform: none;\n}\n\n/**\nCorrect the inability to style clickable types in iOS and Safari.\n*/\n\nbutton,\n[type='button'] {\n -webkit-appearance: button;\n}\n\n/**\nRemove the inner border and padding in Firefox.\n*/\n\n/**\nRestore the focus styles unset by the previous rule.\n*/\n\n/**\nRemove the additional ':invalid' styles in Firefox.\nSee: https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737\n*/\n\n/**\nRemove the padding so developers are not caught out when they zero out 'fieldset' elements in all browsers.\n*/\n\nlegend {\n padding: 0;\n}\n\n/**\nAdd the correct vertical alignment in Chrome and Firefox.\n*/\n\nprogress {\n vertical-align: baseline;\n}\n\n/**\nCorrect the cursor style of increment and decrement buttons in Safari.\n*/\n\n/**\n1. Correct the odd appearance in Chrome and Safari.\n2. Correct the outline style in Safari.\n*/\n\n/**\nRemove the inner padding in Chrome and Safari on macOS.\n*/\n\n/**\n1. Correct the inability to style clickable types in iOS and Safari.\n2. Change font properties to 'inherit' in Safari.\n*/\n\n/*\nInteractive\n===========\n*/\n\n/*\nAdd the correct display in Chrome and Safari.\n*/\n\nsummary {\n display: list-item;\n}\n\n/**\n * Manually forked from SUIT CSS Base: https://github.com/suitcss/base\n * A thin layer on top of normalize.css that provides a starting point more\n * suitable for web applications.\n */\n\n/**\n * Removes the default spacing and border for appropriate elements.\n */\n\nblockquote,\ndl,\ndd,\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\nhr,\nfigure,\np,\npre {\n margin: 0;\n}\n\nbutton {\n background-color: transparent;\n background-image: none;\n}\n\nfieldset {\n margin: 0;\n padding: 0;\n}\n\nol,\nul {\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n/**\n * Tailwind custom reset styles\n */\n\n/**\n * 1. Use the user's configured `sans` font-family (with Tailwind's default\n * sans-serif font stack as a fallback) as a sane default.\n * 2. Use Tailwind's default \"normal\" line-height so the user isn't forced\n * to override it to ensure consistency even when using the default theme.\n */\n\nhtml {\n font-family: Roboto, Helvetica, Arial, sans-serif; /* 1 */\n line-height: 1.5; /* 2 */\n}\n\n/**\n * Inherit font-family and line-height from `html` so users can set them as\n * a class directly on the `html` element.\n */\n\nbody {\n font-family: inherit;\n line-height: inherit;\n}\n\n/**\n * 1. Prevent padding and border from affecting element width.\n *\n * We used to set this in the html element and inherit from\n * the parent element for everything else. This caused issues\n * in shadow-dom-enhanced elements like
where the content\n * is wrapped by a div with box-sizing set to `content-box`.\n *\n * https://github.com/mozdevs/cssremedy/issues/4\n *\n *\n * 2. Allow adding a border to an element by just adding a border-width.\n *\n * By default, the way the browser specifies that an element should have no\n * border is by setting it's border-style to `none` in the user-agent\n * stylesheet.\n *\n * In order to easily add borders to elements by just setting the `border-width`\n * property, we change the default border-style for all elements to `solid`, and\n * use border-width to hide them instead. This way our `border` utilities only\n * need to set the `border-width` property instead of the entire `border`\n * shorthand, making our border utilities much more straightforward to compose.\n *\n * https://github.com/tailwindcss/tailwindcss/pull/116\n */\n\n*,\n::before,\n::after {\n box-sizing: border-box; /* 1 */\n border-width: 0; /* 2 */\n border-style: solid; /* 2 */\n border-color: currentColor; /* 2 */\n}\n\n/*\n * Ensure horizontal rules are visible by default\n */\n\nhr {\n border-top-width: 1px;\n}\n\n/**\n * Undo the `border-style: none` reset that Normalize applies to images so that\n * our `border-{width}` utilities have the expected effect.\n *\n * The Normalize reset is unnecessary for us since we default the border-width\n * to 0 on all elements.\n *\n * https://github.com/tailwindcss/tailwindcss/issues/362\n */\n\nimg {\n border-style: solid;\n}\n\ntextarea {\n resize: vertical;\n}\n\ninput::-webkit-input-placeholder, textarea::-webkit-input-placeholder {\n opacity: 1;\n color: #9ca3af;\n}\n\ninput:-ms-input-placeholder, textarea:-ms-input-placeholder {\n opacity: 1;\n color: #9ca3af;\n}\n\ninput::placeholder,\ntextarea::placeholder {\n opacity: 1;\n color: #9ca3af;\n}\n\nbutton {\n cursor: pointer;\n}\n\n/**\n * Override legacy focus reset from Normalize with modern Firefox focus styles.\n *\n * This is actually an improvement over the new defaults in Firefox in our testing,\n * as it triggers the better focus styles even for links, which still use a dotted\n * outline in Firefox by default.\n */\n\ntable {\n border-collapse: collapse;\n}\n\nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n font-size: inherit;\n font-weight: inherit;\n}\n\n/**\n * Reset links to optimize for opt-in styling instead of\n * opt-out.\n */\n\na {\n color: inherit;\n text-decoration: inherit;\n}\n\n/**\n * Reset form element properties that are easy to forget to\n * style explicitly so you don't inadvertently introduce\n * styles that deviate from your design system. These styles\n * supplement a partial reset that is already applied by\n * normalize.css.\n */\n\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n padding: 0;\n line-height: inherit;\n color: inherit;\n}\n\n/**\n * Use the configured 'mono' font family for elements that\n * are expected to be rendered with a monospace font, falling\n * back to the system monospace stack if there is no configured\n * 'mono' font family.\n */\n\npre,\ncode,\nkbd,\nsamp {\n font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n}\n\n/**\n * 1. Make replaced elements `display: block` by default as that's\n * the behavior you want almost all of the time. Inspired by\n * CSS Remedy, with `svg` added as well.\n *\n * https://github.com/mozdevs/cssremedy/issues/14\n * \n * 2. Add `vertical-align: middle` to align replaced elements more\n * sensibly by default when overriding `display` by adding a\n * utility like `inline`.\n *\n * This can trigger a poorly considered linting error in some\n * tools but is included by design.\n * \n * https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210\n */\n\nimg,\nsvg,\nvideo,\ncanvas,\naudio,\niframe,\nembed,\nobject {\n display: block; /* 1 */\n vertical-align: middle; /* 2 */\n}\n\n/**\n * Constrain images and videos to the parent width and preserve\n * their intrinsic aspect ratio.\n *\n * https://github.com/mozdevs/cssremedy/issues/14\n */\n\nimg,\nvideo {\n max-width: 100%;\n height: auto;\n}\n\n/**\n * Ensure the default browser behavior of the `hidden` attribute.\n */\n\n[hidden] {\n display: none;\n}\n\n*, ::before, ::after {\n --tw-border-opacity: 1;\n border-color: rgba(229, 231, 235, var(--tw-border-opacity));\n}\n\n.container {\n width: 100%;\n}\n\n@media (min-width: 640px) {\n .container {\n max-width: 640px;\n }\n}\n\n@media (min-width: 768px) {\n .container {\n max-width: 768px;\n }\n}\n\n@media (min-width: 1024px) {\n .container {\n max-width: 1024px;\n }\n}\n\n@media (min-width: 1280px) {\n .container {\n max-width: 1280px;\n }\n}\n\n@media (min-width: 1536px) {\n .container {\n max-width: 1536px;\n }\n}\n\n.pointer-events-none {\n pointer-events: none;\n}\n\n.fixed {\n position: fixed;\n}\n\n.absolute {\n position: absolute;\n}\n\n.relative {\n position: relative;\n}\n\n.inset-0 {\n top: 0px;\n right: 0px;\n bottom: 0px;\n left: 0px;\n}\n\n.inset-y-0 {\n top: 0px;\n bottom: 0px;\n}\n\n.right-0 {\n right: 0px;\n}\n\n.right-1 {\n right: 0.25rem;\n}\n\n.right-0\\.5 {\n right: 0.125rem;\n}\n\n.right-1\\.5 {\n right: 0.375rem;\n}\n\n.bottom-0 {\n bottom: 0px;\n}\n\n.bottom-0\\.5 {\n bottom: 0.125rem;\n}\n\n.z-40 {\n z-index: 40;\n}\n\n.z-50 {\n z-index: 50;\n}\n\n.mx-3 {\n margin-left: 0.75rem;\n margin-right: 0.75rem;\n}\n\n.mx-4 {\n margin-left: 1rem;\n margin-right: 1rem;\n}\n\n.mx-auto {\n margin-left: auto;\n margin-right: auto;\n}\n\n.-mx-3 {\n margin-left: -0.75rem;\n margin-right: -0.75rem;\n}\n\n.my-4 {\n margin-top: 1rem;\n margin-bottom: 1rem;\n}\n\n.my-6 {\n margin-top: 1.5rem;\n margin-bottom: 1.5rem;\n}\n\n.mt-1 {\n margin-top: 0.25rem;\n}\n\n.mt-2 {\n margin-top: 0.5rem;\n}\n\n.mr-2 {\n margin-right: 0.5rem;\n}\n\n.mb-0 {\n margin-bottom: 0px;\n}\n\n.mb-1 {\n margin-bottom: 0.25rem;\n}\n\n.mb-2 {\n margin-bottom: 0.5rem;\n}\n\n.mb-3 {\n margin-bottom: 0.75rem;\n}\n\n.mb-4 {\n margin-bottom: 1rem;\n}\n\n.mb-5 {\n margin-bottom: 1.25rem;\n}\n\n.mb-6 {\n margin-bottom: 1.5rem;\n}\n\n.block {\n display: block;\n}\n\n.inline {\n display: inline;\n}\n\n.flex {\n display: flex;\n}\n\n.inline-flex {\n display: inline-flex;\n}\n\n.table {\n display: table;\n}\n\n.hidden {\n display: none;\n}\n\n.h-5 {\n height: 1.25rem;\n}\n\n.h-6 {\n height: 1.5rem;\n}\n\n.h-12 {\n height: 3rem;\n}\n\n.h-14 {\n height: 3.5rem;\n}\n\n.h-16 {\n height: 4rem;\n}\n\n.h-96 {\n height: 24rem;\n}\n\n.h-3\\/4 {\n height: 75%;\n}\n\n.h-full {\n height: 100%;\n}\n\n.h-screen {\n height: 100vh;\n}\n\n.w-5 {\n width: 1.25rem;\n}\n\n.w-8 {\n width: 2rem;\n}\n\n.w-12 {\n width: 3rem;\n}\n\n.w-14 {\n width: 3.5rem;\n}\n\n.w-32 {\n width: 8rem;\n}\n\n.w-36 {\n width: 9rem;\n}\n\n.w-80 {\n width: 20rem;\n}\n\n.w-96 {\n width: 24rem;\n}\n\n.w-450 {\n width: 450px;\n}\n\n.w-auto {\n width: auto;\n}\n\n.w-1\\/2 {\n width: 50%;\n}\n\n.w-1\\/5 {\n width: 20%;\n}\n\n.w-4\\/5 {\n width: 80%;\n}\n\n.w-1\\/6 {\n width: 16.666667%;\n}\n\n.w-full {\n width: 100%;\n}\n\n.w-screen {\n width: 100vw;\n}\n\n.max-w-sm {\n max-width: 24rem;\n}\n\n.max-w-3xl {\n max-width: 48rem;\n}\n\n.flex-1 {\n flex: 1 1 0%;\n}\n\n.flex-none {\n flex: none;\n}\n\n.table-auto {\n table-layout: auto;\n}\n\n@-webkit-keyframes spin {\n to {\n -webkit-transform: rotate(360deg);\n transform: rotate(360deg);\n }\n}\n\n@keyframes spin {\n to {\n -webkit-transform: rotate(360deg);\n transform: rotate(360deg);\n }\n}\n\n@-webkit-keyframes ping {\n 75%, 100% {\n -webkit-transform: scale(2);\n transform: scale(2);\n opacity: 0;\n }\n}\n\n@keyframes ping {\n 75%, 100% {\n -webkit-transform: scale(2);\n transform: scale(2);\n opacity: 0;\n }\n}\n\n@-webkit-keyframes pulse {\n 50% {\n opacity: .5;\n }\n}\n\n@keyframes pulse {\n 50% {\n opacity: .5;\n }\n}\n\n@-webkit-keyframes bounce {\n 0%, 100% {\n -webkit-transform: translateY(-25%);\n transform: translateY(-25%);\n -webkit-animation-timing-function: cubic-bezier(0.8,0,1,1);\n animation-timing-function: cubic-bezier(0.8,0,1,1);\n }\n\n 50% {\n -webkit-transform: none;\n transform: none;\n -webkit-animation-timing-function: cubic-bezier(0,0,0.2,1);\n animation-timing-function: cubic-bezier(0,0,0.2,1);\n }\n}\n\n@keyframes bounce {\n 0%, 100% {\n -webkit-transform: translateY(-25%);\n transform: translateY(-25%);\n -webkit-animation-timing-function: cubic-bezier(0.8,0,1,1);\n animation-timing-function: cubic-bezier(0.8,0,1,1);\n }\n\n 50% {\n -webkit-transform: none;\n transform: none;\n -webkit-animation-timing-function: cubic-bezier(0,0,0.2,1);\n animation-timing-function: cubic-bezier(0,0,0.2,1);\n }\n}\n\n.animate-spin {\n -webkit-animation: spin 1s linear infinite;\n animation: spin 1s linear infinite;\n}\n\n.cursor-pointer {\n cursor: pointer;\n}\n\n.cursor-help {\n cursor: help;\n}\n\n.cursor-not-allowed {\n cursor: not-allowed;\n}\n\n.appearance-none {\n -webkit-appearance: none;\n appearance: none;\n}\n\n.flex-col {\n flex-direction: column;\n}\n\n.flex-col-reverse {\n flex-direction: column-reverse;\n}\n\n.flex-wrap {\n flex-wrap: wrap;\n}\n\n.items-start {\n align-items: flex-start;\n}\n\n.items-end {\n align-items: flex-end;\n}\n\n.items-center {\n align-items: center;\n}\n\n.justify-end {\n justify-content: flex-end;\n}\n\n.justify-center {\n justify-content: center;\n}\n\n.justify-between {\n justify-content: space-between;\n}\n\n.justify-around {\n justify-content: space-around;\n}\n\n.space-x-0 > :not([hidden]) ~ :not([hidden]) {\n --tw-space-x-reverse: 0;\n margin-right: calc(0px * var(--tw-space-x-reverse));\n margin-left: calc(0px * calc(1 - var(--tw-space-x-reverse)));\n}\n\n.space-x-1 > :not([hidden]) ~ :not([hidden]) {\n --tw-space-x-reverse: 0;\n margin-right: calc(0.25rem * var(--tw-space-x-reverse));\n margin-left: calc(0.25rem * calc(1 - var(--tw-space-x-reverse)));\n}\n\n.space-x-2 > :not([hidden]) ~ :not([hidden]) {\n --tw-space-x-reverse: 0;\n margin-right: calc(0.5rem * var(--tw-space-x-reverse));\n margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse)));\n}\n\n.space-x-6 > :not([hidden]) ~ :not([hidden]) {\n --tw-space-x-reverse: 0;\n margin-right: calc(1.5rem * var(--tw-space-x-reverse));\n margin-left: calc(1.5rem * calc(1 - var(--tw-space-x-reverse)));\n}\n\n.space-x-10 > :not([hidden]) ~ :not([hidden]) {\n --tw-space-x-reverse: 0;\n margin-right: calc(2.5rem * var(--tw-space-x-reverse));\n margin-left: calc(2.5rem * calc(1 - var(--tw-space-x-reverse)));\n}\n\n.space-y-4 > :not([hidden]) ~ :not([hidden]) {\n --tw-space-y-reverse: 0;\n margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse)));\n margin-bottom: calc(1rem * var(--tw-space-y-reverse));\n}\n\n.self-end {\n align-self: flex-end;\n}\n\n.self-center {\n align-self: center;\n}\n\n.overflow-auto {\n overflow: auto;\n}\n\n.overflow-hidden {\n overflow: hidden;\n}\n\n.overflow-y-auto {\n overflow-y: auto;\n}\n\n.overflow-x-hidden {\n overflow-x: hidden;\n}\n\n.rounded {\n border-radius: 0.25rem;\n}\n\n.rounded-md {\n border-radius: 0.375rem;\n}\n\n.rounded-lg {\n border-radius: 0.5rem;\n}\n\n.rounded-t {\n border-top-left-radius: 0.25rem;\n border-top-right-radius: 0.25rem;\n}\n\n.rounded-r {\n border-top-right-radius: 0.25rem;\n border-bottom-right-radius: 0.25rem;\n}\n\n.border-0 {\n border-width: 0px;\n}\n\n.border-2 {\n border-width: 2px;\n}\n\n.border-4 {\n border-width: 4px;\n}\n\n.border-8 {\n border-width: 8px;\n}\n\n.border {\n border-width: 1px;\n}\n\n.border-t {\n border-top-width: 1px;\n}\n\n.border-r-2 {\n border-right-width: 2px;\n}\n\n.border-b {\n border-bottom-width: 1px;\n}\n\n.border-solid {\n border-style: solid;\n}\n\n.border-black {\n --tw-border-opacity: 1;\n border-color: rgba(0, 0, 0, var(--tw-border-opacity));\n}\n\n.border-gray-200 {\n --tw-border-opacity: 1;\n border-color: rgba(229, 231, 235, var(--tw-border-opacity));\n}\n\n.border-gray-300 {\n --tw-border-opacity: 1;\n border-color: rgba(209, 213, 219, var(--tw-border-opacity));\n}\n\n.border-gray-500 {\n --tw-border-opacity: 1;\n border-color: rgba(107, 114, 128, var(--tw-border-opacity));\n}\n\n.border-red-300 {\n --tw-border-opacity: 1;\n border-color: rgba(252, 165, 165, var(--tw-border-opacity));\n}\n\n.border-red-700 {\n --tw-border-opacity: 1;\n border-color: rgba(185, 28, 28, var(--tw-border-opacity));\n}\n\n.border-yellow-300 {\n --tw-border-opacity: 1;\n border-color: rgba(252, 211, 77, var(--tw-border-opacity));\n}\n\n.border-yellow-400 {\n --tw-border-opacity: 1;\n border-color: rgba(251, 191, 36, var(--tw-border-opacity));\n}\n\n.border-green-300 {\n --tw-border-opacity: 1;\n border-color: rgba(110, 231, 183, var(--tw-border-opacity));\n}\n\n.border-green-600 {\n --tw-border-opacity: 1;\n border-color: rgba(5, 150, 105, var(--tw-border-opacity));\n}\n\n.border-green-700 {\n --tw-border-opacity: 1;\n border-color: rgba(4, 120, 87, var(--tw-border-opacity));\n}\n\n.border-blue-300 {\n --tw-border-opacity: 1;\n border-color: rgba(147, 197, 253, var(--tw-border-opacity));\n}\n\n.border-blue-600 {\n --tw-border-opacity: 1;\n border-color: rgba(37, 99, 235, var(--tw-border-opacity));\n}\n\n.border-blue-700 {\n --tw-border-opacity: 1;\n border-color: rgba(29, 78, 216, var(--tw-border-opacity));\n}\n\n.hover\\:border-yellow-200:hover {\n --tw-border-opacity: 1;\n border-color: rgba(253, 230, 138, var(--tw-border-opacity));\n}\n\n.hover\\:border-yellow-300:hover {\n --tw-border-opacity: 1;\n border-color: rgba(252, 211, 77, var(--tw-border-opacity));\n}\n\n.hover\\:border-green-500:hover {\n --tw-border-opacity: 1;\n border-color: rgba(16, 185, 129, var(--tw-border-opacity));\n}\n\n.hover\\:border-green-600:hover {\n --tw-border-opacity: 1;\n border-color: rgba(5, 150, 105, var(--tw-border-opacity));\n}\n\n.hover\\:border-blue-700:hover {\n --tw-border-opacity: 1;\n border-color: rgba(29, 78, 216, var(--tw-border-opacity));\n}\n\n.focus\\:border-gray-500:focus {\n --tw-border-opacity: 1;\n border-color: rgba(107, 114, 128, var(--tw-border-opacity));\n}\n\n.border-opacity-70 {\n --tw-border-opacity: 0.7;\n}\n\n.border-opacity-100 {\n --tw-border-opacity: 1;\n}\n\n.bg-black {\n --tw-bg-opacity: 1;\n background-color: rgba(0, 0, 0, var(--tw-bg-opacity));\n}\n\n.bg-white {\n --tw-bg-opacity: 1;\n background-color: rgba(255, 255, 255, var(--tw-bg-opacity));\n}\n\n.bg-gray-100 {\n --tw-bg-opacity: 1;\n background-color: rgba(243, 244, 246, var(--tw-bg-opacity));\n}\n\n.bg-gray-300 {\n --tw-bg-opacity: 1;\n background-color: rgba(209, 213, 219, var(--tw-bg-opacity));\n}\n\n.bg-gray-500 {\n --tw-bg-opacity: 1;\n background-color: rgba(107, 114, 128, var(--tw-bg-opacity));\n}\n\n.bg-gray-700 {\n --tw-bg-opacity: 1;\n background-color: rgba(55, 65, 81, var(--tw-bg-opacity));\n}\n\n.bg-red-100 {\n --tw-bg-opacity: 1;\n background-color: rgba(254, 226, 226, var(--tw-bg-opacity));\n}\n\n.bg-red-600 {\n --tw-bg-opacity: 1;\n background-color: rgba(220, 38, 38, var(--tw-bg-opacity));\n}\n\n.bg-red-700 {\n --tw-bg-opacity: 1;\n background-color: rgba(185, 28, 28, var(--tw-bg-opacity));\n}\n\n.bg-yellow-100 {\n --tw-bg-opacity: 1;\n background-color: rgba(254, 243, 199, var(--tw-bg-opacity));\n}\n\n.bg-yellow-200 {\n --tw-bg-opacity: 1;\n background-color: rgba(253, 230, 138, var(--tw-bg-opacity));\n}\n\n.bg-yellow-300 {\n --tw-bg-opacity: 1;\n background-color: rgba(252, 211, 77, var(--tw-bg-opacity));\n}\n\n.bg-yellow-500 {\n --tw-bg-opacity: 1;\n background-color: rgba(245, 158, 11, var(--tw-bg-opacity));\n}\n\n.bg-green-100 {\n --tw-bg-opacity: 1;\n background-color: rgba(209, 250, 229, var(--tw-bg-opacity));\n}\n\n.bg-green-500 {\n --tw-bg-opacity: 1;\n background-color: rgba(16, 185, 129, var(--tw-bg-opacity));\n}\n\n.bg-green-600 {\n --tw-bg-opacity: 1;\n background-color: rgba(5, 150, 105, var(--tw-bg-opacity));\n}\n\n.bg-blue-100 {\n --tw-bg-opacity: 1;\n background-color: rgba(219, 234, 254, var(--tw-bg-opacity));\n}\n\n.bg-blue-400 {\n --tw-bg-opacity: 1;\n background-color: rgba(96, 165, 250, var(--tw-bg-opacity));\n}\n\n.bg-blue-600 {\n --tw-bg-opacity: 1;\n background-color: rgba(37, 99, 235, var(--tw-bg-opacity));\n}\n\n.bg-blue-800 {\n --tw-bg-opacity: 1;\n background-color: rgba(30, 64, 175, var(--tw-bg-opacity));\n}\n\n.hover\\:bg-gray-700:hover {\n --tw-bg-opacity: 1;\n background-color: rgba(55, 65, 81, var(--tw-bg-opacity));\n}\n\n.hover\\:bg-red-700:hover {\n --tw-bg-opacity: 1;\n background-color: rgba(185, 28, 28, var(--tw-bg-opacity));\n}\n\n.hover\\:bg-yellow-300:hover {\n --tw-bg-opacity: 1;\n background-color: rgba(252, 211, 77, var(--tw-bg-opacity));\n}\n\n.hover\\:bg-green-700:hover {\n --tw-bg-opacity: 1;\n background-color: rgba(4, 120, 87, var(--tw-bg-opacity));\n}\n\n.hover\\:bg-blue-700:hover {\n --tw-bg-opacity: 1;\n background-color: rgba(29, 78, 216, var(--tw-bg-opacity));\n}\n\n.focus\\:bg-white:focus {\n --tw-bg-opacity: 1;\n background-color: rgba(255, 255, 255, var(--tw-bg-opacity));\n}\n\n.p-3 {\n padding: 0.75rem;\n}\n\n.p-4 {\n padding: 1rem;\n}\n\n.p-10 {\n padding: 2.5rem;\n}\n\n.px-2 {\n padding-left: 0.5rem;\n padding-right: 0.5rem;\n}\n\n.px-3 {\n padding-left: 0.75rem;\n padding-right: 0.75rem;\n}\n\n.px-4 {\n padding-left: 1rem;\n padding-right: 1rem;\n}\n\n.px-6 {\n padding-left: 1.5rem;\n padding-right: 1.5rem;\n}\n\n.px-8 {\n padding-left: 2rem;\n padding-right: 2rem;\n}\n\n.px-10 {\n padding-left: 2.5rem;\n padding-right: 2.5rem;\n}\n\n.py-0 {\n padding-top: 0px;\n padding-bottom: 0px;\n}\n\n.py-2 {\n padding-top: 0.5rem;\n padding-bottom: 0.5rem;\n}\n\n.py-3 {\n padding-top: 0.75rem;\n padding-bottom: 0.75rem;\n}\n\n.py-4 {\n padding-top: 1rem;\n padding-bottom: 1rem;\n}\n\n.py-8 {\n padding-top: 2rem;\n padding-bottom: 2rem;\n}\n\n.pt-1 {\n padding-top: 0.25rem;\n}\n\n.pt-2 {\n padding-top: 0.5rem;\n}\n\n.pt-4 {\n padding-top: 1rem;\n}\n\n.pt-5 {\n padding-top: 1.25rem;\n}\n\n.pt-6 {\n padding-top: 1.5rem;\n}\n\n.pt-7 {\n padding-top: 1.75rem;\n}\n\n.pt-8 {\n padding-top: 2rem;\n}\n\n.pt-28 {\n padding-top: 7rem;\n}\n\n.pr-1 {\n padding-right: 0.25rem;\n}\n\n.pr-2 {\n padding-right: 0.5rem;\n}\n\n.pr-4 {\n padding-right: 1rem;\n}\n\n.pr-5 {\n padding-right: 1.25rem;\n}\n\n.pr-8 {\n padding-right: 2rem;\n}\n\n.pb-1 {\n padding-bottom: 0.25rem;\n}\n\n.pb-2 {\n padding-bottom: 0.5rem;\n}\n\n.pb-3 {\n padding-bottom: 0.75rem;\n}\n\n.pb-6 {\n padding-bottom: 1.5rem;\n}\n\n.pb-7 {\n padding-bottom: 1.75rem;\n}\n\n.pb-9 {\n padding-bottom: 2.25rem;\n}\n\n.pb-10 {\n padding-bottom: 2.5rem;\n}\n\n.pl-1 {\n padding-left: 0.25rem;\n}\n\n.pl-2 {\n padding-left: 0.5rem;\n}\n\n.text-left {\n text-align: left;\n}\n\n.text-xs {\n font-size: 0.75rem;\n line-height: 1rem;\n}\n\n.text-sm {\n font-size: 0.875rem;\n line-height: 1.25rem;\n}\n\n.text-lg {\n font-size: 1.125rem;\n line-height: 1.75rem;\n}\n\n.text-xl {\n font-size: 1.25rem;\n line-height: 1.75rem;\n}\n\n.text-2xl {\n font-size: 1.5rem;\n line-height: 2rem;\n}\n\n.text-3xl {\n font-size: 1.875rem;\n line-height: 2.25rem;\n}\n\n.text-4xl {\n font-size: 2.25rem;\n line-height: 2.5rem;\n}\n\n.font-normal {\n font-weight: 400;\n}\n\n.font-medium {\n font-weight: 500;\n}\n\n.font-semibold {\n font-weight: 600;\n}\n\n.font-bold {\n font-weight: 700;\n}\n\n.uppercase {\n text-transform: uppercase;\n}\n\n.capitalize {\n text-transform: capitalize;\n}\n\n.italic {\n font-style: italic;\n}\n\n.leading-tight {\n line-height: 1.25;\n}\n\n.tracking-wide {\n letter-spacing: 0.025em;\n}\n\n.text-black {\n --tw-text-opacity: 1;\n color: rgba(0, 0, 0, var(--tw-text-opacity));\n}\n\n.text-white {\n --tw-text-opacity: 1;\n color: rgba(255, 255, 255, var(--tw-text-opacity));\n}\n\n.text-gray-400 {\n --tw-text-opacity: 1;\n color: rgba(156, 163, 175, var(--tw-text-opacity));\n}\n\n.text-gray-500 {\n --tw-text-opacity: 1;\n color: rgba(107, 114, 128, var(--tw-text-opacity));\n}\n\n.text-gray-700 {\n --tw-text-opacity: 1;\n color: rgba(55, 65, 81, var(--tw-text-opacity));\n}\n\n.text-gray-800 {\n --tw-text-opacity: 1;\n color: rgba(31, 41, 55, var(--tw-text-opacity));\n}\n\n.text-gray-900 {\n --tw-text-opacity: 1;\n color: rgba(17, 24, 39, var(--tw-text-opacity));\n}\n\n.text-red-500 {\n --tw-text-opacity: 1;\n color: rgba(239, 68, 68, var(--tw-text-opacity));\n}\n\n.text-red-600 {\n --tw-text-opacity: 1;\n color: rgba(220, 38, 38, var(--tw-text-opacity));\n}\n\n.text-red-800 {\n --tw-text-opacity: 1;\n color: rgba(153, 27, 27, var(--tw-text-opacity));\n}\n\n.text-yellow-800 {\n --tw-text-opacity: 1;\n color: rgba(146, 64, 14, var(--tw-text-opacity));\n}\n\n.text-green-800 {\n --tw-text-opacity: 1;\n color: rgba(6, 95, 70, var(--tw-text-opacity));\n}\n\n.text-blue-400 {\n --tw-text-opacity: 1;\n color: rgba(96, 165, 250, var(--tw-text-opacity));\n}\n\n.text-blue-500 {\n --tw-text-opacity: 1;\n color: rgba(59, 130, 246, var(--tw-text-opacity));\n}\n\n.text-blue-900 {\n --tw-text-opacity: 1;\n color: rgba(30, 58, 138, var(--tw-text-opacity));\n}\n\n.hover\\:text-gray-700:hover {\n --tw-text-opacity: 1;\n color: rgba(55, 65, 81, var(--tw-text-opacity));\n}\n\n.opacity-75 {\n opacity: 0.75;\n}\n\n*, ::before, ::after {\n --tw-shadow: 0 0 #0000;\n}\n\n.shadow-lg {\n --tw-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);\n box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);\n}\n\n.shadow-2xl {\n --tw-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);\n box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);\n}\n\n.outline-none {\n outline: 2px solid transparent;\n outline-offset: 2px;\n}\n\n.focus\\:outline-none:focus {\n outline: 2px solid transparent;\n outline-offset: 2px;\n}\n\n*, ::before, ::after {\n --tw-ring-inset: var(--tw-empty,/*!*/ /*!*/);\n --tw-ring-offset-width: 0px;\n --tw-ring-offset-color: #fff;\n --tw-ring-color: rgba(59, 130, 246, 0.5);\n --tw-ring-offset-shadow: 0 0 #0000;\n --tw-ring-shadow: 0 0 #0000;\n}\n\n.filter {\n --tw-blur: var(--tw-empty,/*!*/ /*!*/);\n --tw-brightness: var(--tw-empty,/*!*/ /*!*/);\n --tw-contrast: var(--tw-empty,/*!*/ /*!*/);\n --tw-grayscale: var(--tw-empty,/*!*/ /*!*/);\n --tw-hue-rotate: var(--tw-empty,/*!*/ /*!*/);\n --tw-invert: var(--tw-empty,/*!*/ /*!*/);\n --tw-saturate: var(--tw-empty,/*!*/ /*!*/);\n --tw-sepia: var(--tw-empty,/*!*/ /*!*/);\n --tw-drop-shadow: var(--tw-empty,/*!*/ /*!*/);\n -webkit-filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);\n filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);\n}\n\ninput:checked + div {\n --tw-border-opacity: 1;\n border-color: rgba(59, 130, 246, var(--tw-border-opacity));\n}\n\ninput:checked + div svg {\n display: block;\n}\n\n.blink {\n -webkit-animation: blinker 1s step-start infinite;\n animation: blinker 1s step-start infinite;\n}\n\n@-webkit-keyframes blinker {\n 50% {\n opacity: 0;\n }\n}\n\n@keyframes blinker {\n 50% {\n opacity: 0;\n }\n}\n\n@media (min-width: 640px) {\n}\n\n@media (min-width: 768px) {\n .md\\:flex {\n display: flex;\n }\n\n .md\\:items-center {\n align-items: center;\n }\n}\n\n@media (min-width: 1024px) {\n}\n\n@media (min-width: 1280px) {\n}\n\n@media (min-width: 1536px) {\n}\n/* purgecss start ignore */\n\n.App {\n text-align: center;\n}\n\n.App-logo {\n height: 40vmin;\n pointer-events: none;\n}\n\n@media (prefers-reduced-motion: no-preference) {\n .App-logo {\n -webkit-animation: App-logo-spin infinite 20s linear;\n animation: App-logo-spin infinite 20s linear;\n }\n}\n\n.App-header {\n background-color: #282c34;\n min-height: 100vh;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n font-size: calc(10px + 2vmin);\n color: white;\n}\n\n.App-link {\n color: #61dafb;\n}\n\n@-webkit-keyframes App-logo-spin {\n from {\n -webkit-transform: rotate(0deg);\n transform: rotate(0deg);\n }\n\n to {\n -webkit-transform: rotate(360deg);\n transform: rotate(360deg);\n }\n}\n\n@keyframes App-logo-spin {\n from {\n -webkit-transform: rotate(0deg);\n transform: rotate(0deg);\n }\n\n to {\n -webkit-transform: rotate(360deg);\n transform: rotate(360deg);\n }\n}\n\n/* purgecss end ignore */\n\n",".App {\n text-align: center;\n}\n\n.App-logo {\n height: 40vmin;\n pointer-events: none;\n}\n\n@media (prefers-reduced-motion: no-preference) {\n .App-logo {\n animation: App-logo-spin infinite 20s linear;\n }\n}\n\n.App-header {\n background-color: #282c34;\n min-height: 100vh;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n font-size: calc(10px + 2vmin);\n color: white;\n}\n\n.App-link {\n color: #61dafb;\n}\n\n@keyframes App-logo-spin {\n from {\n transform: rotate(0deg);\n }\n to {\n transform: rotate(360deg);\n }\n}\n"]} -------------------------------------------------------------------------------- /server/build/static/js/2.104ca3f2.chunk.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /* 2 | object-assign 3 | (c) Sindre Sorhus 4 | @license MIT 5 | */ 6 | 7 | /*! 8 | * The buffer module from node.js, for the browser. 9 | * 10 | * @author Feross Aboukhadijeh 11 | * @license MIT 12 | */ 13 | 14 | /*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh */ 15 | 16 | /** @license React v0.20.2 17 | * scheduler.production.min.js 18 | * 19 | * Copyright (c) Facebook, Inc. and its affiliates. 20 | * 21 | * This source code is licensed under the MIT license found in the 22 | * LICENSE file in the root directory of this source tree. 23 | */ 24 | 25 | /** @license React v17.0.2 26 | * react-dom.production.min.js 27 | * 28 | * Copyright (c) Facebook, Inc. and its affiliates. 29 | * 30 | * This source code is licensed under the MIT license found in the 31 | * LICENSE file in the root directory of this source tree. 32 | */ 33 | 34 | /** @license React v17.0.2 35 | * react-jsx-runtime.production.min.js 36 | * 37 | * Copyright (c) Facebook, Inc. and its affiliates. 38 | * 39 | * This source code is licensed under the MIT license found in the 40 | * LICENSE file in the root directory of this source tree. 41 | */ 42 | 43 | /** @license React v17.0.2 44 | * react.production.min.js 45 | * 46 | * Copyright (c) Facebook, Inc. and its affiliates. 47 | * 48 | * This source code is licensed under the MIT license found in the 49 | * LICENSE file in the root directory of this source tree. 50 | */ 51 | -------------------------------------------------------------------------------- /server/build/static/js/3.1b5ba067.chunk.js: -------------------------------------------------------------------------------- 1 | (this.webpackJsonpclient=this.webpackJsonpclient||[]).push([[3],{150:function(t,e,n){"use strict";n.r(e),n.d(e,"getCLS",(function(){return d})),n.d(e,"getFCP",(function(){return S})),n.d(e,"getFID",(function(){return F})),n.d(e,"getLCP",(function(){return k})),n.d(e,"getTTFB",(function(){return C}));var i,a,r,o,u=function(t,e){return{name:t,value:void 0===e?-1:e,delta:0,entries:[],id:"v1-".concat(Date.now(),"-").concat(Math.floor(8999999999999*Math.random())+1e12)}},c=function(t,e){try{if(PerformanceObserver.supportedEntryTypes.includes(t)){if("first-input"===t&&!("PerformanceEventTiming"in self))return;var n=new PerformanceObserver((function(t){return t.getEntries().map(e)}));return n.observe({type:t,buffered:!0}),n}}catch(t){}},f=function(t,e){var n=function n(i){"pagehide"!==i.type&&"hidden"!==document.visibilityState||(t(i),e&&(removeEventListener("visibilitychange",n,!0),removeEventListener("pagehide",n,!0)))};addEventListener("visibilitychange",n,!0),addEventListener("pagehide",n,!0)},s=function(t){addEventListener("pageshow",(function(e){e.persisted&&t(e)}),!0)},m="function"==typeof WeakSet?new WeakSet:new Set,p=function(t,e,n){var i;return function(){e.value>=0&&(n||m.has(e)||"hidden"===document.visibilityState)&&(e.delta=e.value-(i||0),(e.delta||void 0===i)&&(i=e.value,t(e)))}},d=function(t,e){var n,i=u("CLS",0),a=function(t){t.hadRecentInput||(i.value+=t.value,i.entries.push(t),n())},r=c("layout-shift",a);r&&(n=p(t,i,e),f((function(){r.takeRecords().map(a),n()})),s((function(){i=u("CLS",0),n=p(t,i,e)})))},v=-1,l=function(){return"hidden"===document.visibilityState?0:1/0},h=function(){f((function(t){var e=t.timeStamp;v=e}),!0)},g=function(){return v<0&&(v=l(),h(),s((function(){setTimeout((function(){v=l(),h()}),0)}))),{get timeStamp(){return v}}},S=function(t,e){var n,i=g(),a=u("FCP"),r=function(t){"first-contentful-paint"===t.name&&(f&&f.disconnect(),t.startTime=0&&a1e12?new Date:performance.now())-t.timeStamp;"pointerdown"==t.type?function(t,e){var n=function(){w(t,e),a()},i=function(){a()},a=function(){removeEventListener("pointerup",n,y),removeEventListener("pointercancel",i,y)};addEventListener("pointerup",n,y),addEventListener("pointercancel",i,y)}(e,t):w(e,t)}},b=function(t){["mousedown","keydown","touchstart","pointerdown"].forEach((function(e){return t(e,T,y)}))},F=function(t,e){var n,r=g(),d=u("FID"),v=function(t){t.startTime=0&&(n||u.has(t)||\"hidden\"===document.visibilityState)&&(t.delta=t.value-(i||0),(t.delta||void 0===i)&&(i=t.value,e(t)))}},s=function(e,t){var n,i=a(\"CLS\",0),u=function(e){e.hadRecentInput||(i.value+=e.value,i.entries.push(e),n())},s=r(\"layout-shift\",u);s&&(n=f(e,i,t),o((function(){s.takeRecords().map(u),n()})),c((function(){i=a(\"CLS\",0),n=f(e,i,t)})))},m=-1,p=function(){return\"hidden\"===document.visibilityState?0:1/0},v=function(){o((function(e){var t=e.timeStamp;m=t}),!0)},d=function(){return m<0&&(m=p(),v(),c((function(){setTimeout((function(){m=p(),v()}),0)}))),{get timeStamp(){return m}}},l=function(e,t){var n,i=d(),o=a(\"FCP\"),s=function(e){\"first-contentful-paint\"===e.name&&(p&&p.disconnect(),e.startTime=0&&t1e12?new Date:performance.now())-e.timeStamp;\"pointerdown\"==e.type?function(e,t){var n=function(){y(e,t),a()},i=function(){a()},a=function(){removeEventListener(\"pointerup\",n,h),removeEventListener(\"pointercancel\",i,h)};addEventListener(\"pointerup\",n,h),addEventListener(\"pointercancel\",i,h)}(t,e):y(t,e)}},w=function(e){[\"mousedown\",\"keydown\",\"touchstart\",\"pointerdown\"].forEach((function(t){return e(t,E,h)}))},L=function(n,s){var m,p=d(),v=a(\"FID\"),l=function(e){e.startTime=0&&t<15:"bottom"===e?t>209&&t<225:"left"===e?t%15===0:t%15===14},U=function(){var e,t,r,n=[],c=Object(l.a)(T.current);try{for(c.s();!(r=c.n()).done;){var a=r.value,s=Y(a).getPiece(),o=V(a,"top"),u=V(a,"left"),d=V(a,"bottom"),b=V(a,"right");e=[].concat(Object(i.a)(o),[s],Object(i.a)(d)),t=[].concat(Object(i.a)(u),[s],Object(i.a)(b)),e.length>1&&n.push(e),t.length>1&&n.push(t)}}catch(j){c.e(j)}finally{c.f()}return n.filter((function(e,t,r){return r.findIndex((function(t){return JSON.stringify(t)===JSON.stringify(e)}))===t}))},V=function(e,t){var r,n,c=[];if(n="right"===t||"left"===t?1:15,M(t,e))return c;for(;;){var a=Y(r="right"===t||"bottom"===t?e+n:e-n);if(!a.hasPiece())break;if("right"===t||"bottom"===t?c.push(a.getPiece()):c.unshift(a.getPiece()),n+="right"===t||"left"===t?1:15,M(t,r))break}return c},K=function(){var e,t=[],r=Object(l.a)(D.current);try{for(r.s();!(e=r.n()).done;){var n=e.value;t.push(Y(n).getPiece())}}catch(c){r.e(c)}finally{r.f()}k(t)};return Object(n.useEffect)((function(){var e=setInterval((function(){K()}),m);return o(e),function(){return clearInterval(s)}}),[]),Object(n.useEffect)((function(){y.current&&K()}),[y.current]),Object(n.useEffect)((function(){if(D.current.length>0){var e,t=Object(l.a)(D.current);try{for(t.s();!(e=t.n()).done;){var r=e.value,n=Object(N.jsx)(E,{id:r.id,onBoard:!0,isPlayed:!0,char:r.char,weight:r.weight,tileID:r.tileID,isTransformed:r.isTransformed});J(r.tileID,n)}}catch(c){t.e(c)}finally{t.f()}w(D.current.map((function(e){return e.tileID})))}}),[]),Object(N.jsxs)("div",{className:"space-y-4 ",children:[Object(N.jsxs)("div",{style:{height:"fit-content"},className:"block border-8 border-black rounded-md",children:[" ",t," "]}),Object(N.jsx)(S,{}),Object(N.jsx)(L,{modalHandler:function(e){var t=Object(N.jsx)(E,{weight:0,char:e,onBoard:!0,isTransformed:!0,id:x.current.pieceID,tileID:x.current.tileID});J(x.current.tileID,t),R([].concat(Object(i.a)(T.current),[x.current.tileID])),d.emit("inPlayEvent",{roomID:v.current.roomID,pieceData:{weight:0,char:e,onBoard:!0,isTransformed:!0,id:x.current.pieceID,tileID:x.current.tileID}}),f({tileID:"",pieceID:"",status:!1})},mode:"chooseLetter",show:x.current.status})]})},J=r(118),M=r(119),U=r(120),V=function(e){var t,r,c=Object(n.useState)(1),a=Object(u.a)(c,2),s=a[0],i=a[1],o=Object(n.useState)("on"),l=Object(u.a)(o,2),d=l[0],b=l[1],j=Object(n.useState)(!1),f=Object(u.a)(j,2),x=f[0],h=f[1],O=Object(n.useState)(),v=Object(u.a)(O,2),g=v[0],y=v[1],w=Object(n.useContext)(p),k=w.player,D=w.boardState,I=w.rackState;Object(n.useEffect)((function(){var e=setInterval((function(){C(),T()}),m);return y(e),function(){return clearInterval(g)}}),[]);var C=function(){var e=Object(G.a)(W.a.mark((function e(){var t,r,n,c;return W.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return t=new Date,e.next=3,Y({requestType:"get",url:"/ping",payload:{}});case 3:(r=e.sent)&&"pingSuccess"===r.status?(n=new Date,c=n.getTime()-t.getTime(),i(c),b(c<120?"on":c<500?"warning":"off")):b("off");case 5:case"end":return e.stop()}}),e)})));return function(){return e.apply(this,arguments)}}(),T=function(){var e=Object(G.a)(W.a.mark((function e(){var t,r;return W.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return h(!0),t={player:k.current,rack:I.current,board:D.current,roomID:k.current.roomID},e.next=4,Y({requestType:"post",url:"/cache",payload:t});case 4:(r=e.sent)&&r.status,setTimeout((function(){h(!1)}),2e3);case 7:case"end":return e.stop()}}),e)})));return function(){return e.apply(this,arguments)}}();return"on"===d?(r="Connection is good",t=Object(N.jsx)(J.a,{size:26,color:"#1fe03d"})):"off"===d?(r="Server disconnected. Trying to reconnect",t=Object(N.jsx)(M.a,{size:26,color:"#f32b0c"})):(t=Object(N.jsx)(J.a,{size:26,color:"#ffd100"}),r="Connection is poor. Delays may occur"),Object(N.jsxs)("div",{className:"flex justify-between items-center",children:[Object(N.jsx)("div",{className:"flex space-x-1 flex-none text-gray-400",children:x?Object(N.jsxs)(N.Fragment,{children:[Object(N.jsx)("div",{className:"cursor-help flex-none",children:Object(N.jsx)(U.a,{size:23})}),Object(N.jsx)("div",{children:"Saving..."})]}):null}),Object(N.jsxs)("div",{className:"flex space-x-1",children:[Object(N.jsx)("div",{title:r,className:"cursor-help flex-none",children:t}),Object(N.jsxs)("div",{title:"Server ping",className:"cursor-help flex-none text-gray-500",children:[s,"ms"]})]})]})},K=r(121),_=r(122),Z=function(){var e=B([]),t=Object(u.a)(e,3),r=(t[0],t[1]),c=t[2],a=Object(n.useContext)(p),s=a.player,i=a.players;return Object(n.useEffect)((function(){var e,t=[],n=Object(l.a)(i.current.entries());try{for(n.s();!(e=n.n()).done;){var c=Object(u.a)(e.value,2),a=c[0],o=c[1],d=Object(N.jsxs)("tr",{children:[Object(N.jsxs)("td",{className:"text-left capitalize border ".concat(o.turn?"font-bold":""," border-gray-300 px-4 py-2 text-black-600 font-medium"),children:[o.name,Object(N.jsx)("span",{className:"inline pl-1",children:s.current.name===o.name?Object(N.jsxs)("span",{title:"This is You".concat(o.turn?". It's also your turn to play":""),children:[Object(N.jsx)(K.a,{title:"You",size:22,className:"inline pb-1",fill:"black",strokeWidth:1})," "]}):null}),Object(N.jsx)("span",{className:"inline",children:o.isSpeaking?Object(N.jsxs)("span",{title:"This person is currently recording",children:[" ",Object(N.jsx)(_.a,{size:22,className:"inline pb-1"})]}):null})]}),Object(N.jsx)("td",{className:"border border-gray-300 px-4 py-2 text-black-600 font-medium",children:I(o.score)})]},a);t.push(d)}}catch(b){n.e(b)}finally{n.f()}r(t)}),[s.current,i.current]),Object(N.jsx)("div",{className:"flex justify-end",children:Object(N.jsxs)("table",{className:"table-auto w-80",children:[Object(N.jsx)("thead",{children:Object(N.jsxs)("tr",{className:"bg-gray-100",children:[Object(N.jsx)("th",{className:"text-left border border-gray-300 px-4 py-2 text-black-600",children:"Player"}),Object(N.jsx)("th",{className:"border border-gray-300 px-4 py-2 text-black-600",children:"Score"})]})}),Object(N.jsx)("tbody",{children:c.current})]})})},$=r(123),Q=r(124),X=function(e){var t=Object(n.useContext)(g).notifications,r=B([]),c=Object(u.a)(r,3),a=(c[0],c[1]),s=c[2];return Object(n.useEffect)((function(){var e,r=Object(l.a)(t.current.entries());try{for(r.s();!(e=r.n()).done;){var n=Object(u.a)(e.value,2),c=n[0],s=n[1],i=void 0,o=void 0;"info"===s.type?(o=Object(N.jsx)($.a,{size:22}),i="bg-blue-100 flex items-center border border-blue-300 text-blue-900 px-4 py-4 rounded relative"):"success"===s.type?(o=Object(N.jsx)(Q.a,{size:22}),i="bg-green-100 flex items-center border border-green-300 text-green-800 px-4 py-4 rounded relative"):"error"===s.type?(o=Object(N.jsx)($.a,{size:22}),i="bg-red-100 flex items-center border border-red-300 text-red-800 px-4 py-4 rounded relative"):"warning"===s.type?(o=Object(N.jsx)($.a,{size:22}),i="bg-yellow-100 flex items-center border border-yellow-300 text-yellow-800 px-4 py-4 rounded relative"):(o=Object(N.jsx)(Q.a,{size:22}),i="bg-center-100 flex items-center text-white center-500 text-center-800 px-4 py-4 rounded relative");var d=Object(N.jsxs)("div",{children:[Object(N.jsxs)("div",{className:i,children:[Object(N.jsx)("span",{className:"inline pr-4",children:o}),Object(N.jsx)("span",{className:"inline text-left",children:s.message})]}),Object(N.jsx)("div",{className:"pb-2"})]},c);a([d])}}catch(b){r.e(b)}finally{r.f()}}),[t.current]),s.current},ee=r(136),te=r(125),re=r(126);function ne(e){var t=Object(n.useRef)([]),r=B(!1),c=Object(u.a)(r,3),a=(c[0],c[1]),s=c[2];Object(n.useEffect)((function(){t.current=Array(e.pieces.length).fill().map((function(e,r){return t.current[r]}))}),[e.pieces.length]);var i=function(e){t.current.filter((function(e){return!0===e.checked})).length>0?a(!0):a(!1)};return e.show?Object(N.jsxs)(N.Fragment,{children:[Object(N.jsx)("div",{className:"justify-center items-center flex overflow-x-hidden overflow-y-auto fixed inset-0 z-50 outline-none focus:outline-none",children:Object(N.jsx)("div",{className:"relative w-auto my-6 mx-auto max-w-3xl",children:Object(N.jsxs)("div",{className:"border-0 rounded-lg shadow-lg relative flex flex-col w-full bg-white outline-none focus:outline-none",children:[Object(N.jsxs)("div",{className:"flex justify-center flex-col pt-7 p-3 border-b border-solid border-blueGray-200 rounded-t",children:[Object(N.jsx)("h3",{className:"text-2xl font-semibold",children:"Exchange Pieces for a Turn"}),Object(N.jsx)("div",{className:"text-md pt-1 text-gray-500",children:"Use the checkboxes to select the pieces to swap"})]}),Object(N.jsxs)("div",{className:"relative flex flex-col p-10 pb-9 pt-8 flex justify-center flex-wrap",children:[Object(N.jsx)("div",{className:"flex justify-center block",children:e.pieces.map((function(e,r){return Object(N.jsxs)("div",{className:"mx-4 my-4 mt-2",children:[Object(N.jsx)(E,{isStatic:!0,char:e.piece,weight:e.weight}),Object(N.jsx)("div",{className:"self-center mt-1 text-xs text-gray-900",children:Object(N.jsx)("label",{className:"inline-flex items-center",children:Object(N.jsx)("input",{onChange:i,id:e.id,ref:function(e){return t.current[r]=e},type:"checkbox"})})})]},r)}))}),Object(N.jsxs)("div",{className:"my-4 mb-2 space-x-6 block",children:[Object(N.jsxs)("button",{onClick:function(){a(!1),e.swapCancelHandler()},className:"w-32 bg-red-600 hover:bg-red-700 text-white inline font-bold py-2 px-4 border border-red-700 rounded",children:[Object(N.jsx)(te.a,{className:"inline",strokeWidth:3,size:22}),Object(N.jsx)("span",{className:"pl-1 pr-2 text-md",children:"Cancel"})]}),s.current?Object(N.jsxs)("button",{onClick:function(){e.swapConfirmHandler(function(){var e,r=[],n=Object(l.a)(t.current);try{for(n.s();!(e=n.n()).done;){var c=e.value;!0===c.checked&&r.push(c.id)}}catch(a){n.e(a)}finally{n.f()}return r}())},className:"w-36 bg-green-600 hover:bg-green-700 text-white font-bold py-2 px-4 border border-green-700 rounded",children:[Object(N.jsx)(re.a,{className:"inline",strokeWidth:3,size:22}),Object(N.jsx)("span",{className:"pl-1 pr-2 text-md",children:"Confirm"})]}):null]})]})]})})}),Object(N.jsx)("div",{className:"opacity-75 fixed inset-0 z-40 bg-black"})]}):null}var ce=r(127),ae=r(128),se=r(129),ie=r(130),oe=r(131),le=r(132),ue={play:"Play as turn",shuffle:"Shuffle pieces on Rack",recall:"Recall played pieces",swap:"Swap pieces in place of turn",skip:"Skip turn",draw:"Start the game"},de=function(e){var t,r,n=e.type,c=e.size,a=e.handler;r="play"===n?"flex justify-center items-center hover:border-l-2 hover:border-green-600 bg-green-500 hover:bg-green-700 text-white font-bold w-12 h-12 border-green-600 hover:border-green-500":"draw"===n?"flex justify-center items-center hover:border-l-2 hover:border-yellow-300 bg-yellow-200 hover:bg-yellow-300 text-black font-normal px-10 w-12 h-12 border-2 border-yellow-300 hover:border-yellow-300":"flex justify-center items-center bg-blue-600 border-r-2 border-blue-700 hover:bg-blue-700 text-white font-bold w-12 h-12 border-blue-600 hover:border-blue-700","recall"===n?t=Object(N.jsx)(ce.a,{strokeWidth:"2.5",size:c}):"play"===n?t=Object(N.jsx)(ae.a,{fill:"white",strokeWidth:"2.5",size:c}):"shuffle"===n?t=Object(N.jsx)(se.a,{strokeWidth:"2.5",size:c}):"swap"===n?t=Object(N.jsx)(ie.a,{strokeWidth:"2.5",size:c}):"skip"===n?t=Object(N.jsx)(oe.a,{strokeWidth:"2.5",size:c}):"draw"===n&&(t="Draw");"".concat(r),le.a;return Object(N.jsx)("button",{title:ue[n],className:"".concat(r),onClick:a,children:t})},be=r(55),je=r(133),fe=r(134),me=function(){var e=Object(be.useReactMediaRecorder)({audio:{type:"audio/ogg"},askPermissionOnMount:!0}),t=e.mediaBlobUrl,r=e.clearBlobUrl,c=e.stopRecording,a=e.startRecording,s=Object(n.useContext)(O),i=B(!0),d=Object(u.a)(i,3),b=(d[0],d[1]),j=d[2],f=B(null),m=Object(u.a)(f,3),x=(m[0],m[1]),h=m[2],v=B(""),g=Object(u.a)(v,3),y=(g[0],g[1]),w=g[2],k=Object(n.useContext)(p),D=k.player,I=k.setPlayer,C=k.players,T=k.setPlayers;return Object(n.useEffect)((function(){j.current?(c(),I(Object(o.a)(Object(o.a)({},D.current),{},{isSpeaking:!1}))):(r(),a(),c(),I(Object(o.a)(Object(o.a)({},D.current),{},{isSpeaking:!0}))),s.emit("radioEvent",{audioBlob:t,roomID:D.current.roomID,speakerName:D.current.name,isSpeaking:D.current.isSpeaking})}),[t,j.current]),Object(n.useEffect)((function(){s.on("audioTransmission",(function(e){var t,r=[],n=Object(l.a)(C.current);try{for(n.s();!(t=n.n()).done;){var c=t.value;e.speakerName===c.name&&(e.isSpeaking?c.isSpeaking=!0:c.isSpeaking=!1),r.push(c)}}catch(a){n.e(a)}finally{n.f()}x(null),T(r),x(e.audioBlob),y(e.speakerName)}))}),[]),Object(N.jsxs)("div",{children:[Object(N.jsx)("button",{title:j.current?"Audio off. Click to record":"Audio is recording. Click to stop",onClick:function(){!1===j.current?b(!0):b(!1)},className:"flex justify-center items-center hover:border bg-yellow-200 hover:bg-yellow-300 text-white font-bold w-12 h-12 border border-yellow-400 hover:border-yellow-200",children:j.current?Object(N.jsx)(je.a,{color:"black",strokeWidth:"2.5",size:20}):Object(N.jsx)(fe.a,{color:"black",strokeWidth:"2.5",size:20})}),!D.current.isSpeaking&&h.current&&w.current!==D.current.name?Object(N.jsx)("audio",{className:"hidden",src:h.current,autoPlay:!0}):null]})},xe=function(e){var t=Object(n.useContext)(p),r=t.player,c=t.gameStarted,a=t.allowAudio,s=B([]),i=Object(u.a)(s,3),o=(i[0],i[1]),d=i[2];return Object(n.useEffect)((function(){var t,n=[],a=Object(l.a)(["recall","shuffle","swap","skip","play"].entries());try{for(a.s();!(t=a.n()).done;){var s=Object(u.a)(t.value,2),i=s[0],d=s[1];n.push(Object(N.jsx)(de,{handler:e[d],type:d,size:20},i))}}catch(b){a.e(b)}finally{a.f()}r.current.isHost&&!c.current&&n.push(Object(N.jsx)(de,{handler:e.draw,type:"draw",size:20},6)),o(n)}),[c.current,r.current]),Object(N.jsxs)("div",{className:"flex items-center justify-center space-x-0 bg-blue-600",children:[a.current?Object(N.jsx)(me,{}):null,d.current]})},he=r(135),pe=function(e){var t,r=e.bag,n=B(!1),c=Object(u.a)(n,3),a=(c[0],c[1]),s=c[2];return Object(N.jsxs)("div",{title:"".concat(r.length," pieces remaining"),onClick:function(){return a(!s.current)},className:"flex items-end space-x-0",children:[Object(N.jsx)("div",{className:"flex-none",children:Object(N.jsx)(he.a,{className:"cursor-pointer",fill:"gray",size:36})}),Object(N.jsx)("div",{className:"flex-none cursor-pointer text-gray-500 text-sm",children:(t=r.length,Object(N.jsx)(w.a,{start:t+7,end:t,duration:1.5}))}),Object(N.jsx)(L,{show:s.current})]})},Oe=function(e){var t=Object(n.useRef)(),r=Object(n.useState)(),c=Object(u.a)(r,2),a=c[0],s=c[1],d=B(0),b=Object(u.a)(d,3),j=(b[0],b[1]),f=b[2],x=B([]),h=Object(u.a)(x,3),y=(h[0],h[1]),w=h[2],k=B(!1),D=Object(u.a)(k,3),I=(D[0],D[1]),C=D[2],S=B(""),P=Object(u.a)(S,3),z=(P[0],P[1]),L=P[2],R=B([]),A=Object(u.a)(R,3),F=(A[0],A[1]),H=A[2],q=Object(n.useContext)(O),J=Object(n.useContext)(v).setValidDrag,M=Object(n.useContext)(g),U=M.notifications,V=M.setNotifications,K=Object(n.useContext)(p),_=K.player,Z=K.gameResumed,$=K.gameExited,Q=K.rackState,X=K.setRackState,te=K.setPlayer,re=K.bag,ce=K.setPlayedWords,ae=K.setPlayFlag,se=K.gameStarted,ie=K.timeToPlay,oe=K.playedTiles,le=K.playedWords,ue=K.usedTiles,de=K.gameEnded,be=K.setRecallFlag,je=B(ie.current),fe=Object(u.a)(je,3),me=(fe[0],fe[1]),he=fe[2];Object(n.useEffect)((function(){function e(){return(e=Object(G.a)(W.a.mark((function e(){return W.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(0!==w.current.length){e.next=6;break}return e.t0=y,e.next=4,ye(7);case 4:e.t1=e.sent,(0,e.t0)(e.t1);case 6:case"end":return e.stop()}}),e)})))).apply(this,arguments)}0!==Q.current.length?(y(Q.current),setTimeout((function(){q.emit("resumeEvent",{roomID:_.current.roomID}),X([])}),1e3)):function(){e.apply(this,arguments)}()}),[]),Object(n.useEffect)(Object(G.a)(W.a.mark((function e(){var t,r,n,c;return W.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(!de.current){e.next=8;break}t=_.current.score,r=Object(l.a)(w.current);try{for(r.s();!(n=r.n()).done;)c=n.value,t-=c.weight}catch(a){r.e(a)}finally{r.f()}return e.next=6,Y({requestType:"post",url:"/scores",payload:{score:t,name:_.current.name,roomID:_.current.roomID}});case 6:"success"===e.sent.status&&te(Object(o.a)(Object(o.a)({},_.current),{},{turn:!1}));case 8:case"end":return e.stop()}}),e)}))),[de.current]),Object(n.useEffect)(Object(G.a)(W.a.mark((function e(){var t,r,n,c,a,s;return W.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(!(le.current.length>0)){e.next=25;break}if(!(f.current>=3)){e.next=5;break}return Pe(),j(0),e.abrupt("return");case 5:return t=le.current.map((function(e){return e.map((function(e){return e.char})).join("")})),e.next=8,ge(t);case 8:if("error"!==(r=e.sent).status){e.next=14;break}j(f.current+1),V([{type:"error",message:r.message}]),e.next=25;break;case 14:return n=0===re.current.length,c=0===w.current.length,a=Oe(t,H.current),s=ve(le.current,7===H.current.length),e.next=20,we(H.current.length);case 20:F([]),q.emit("playEvent",{score:s,word:a,bagIsEmpty:n,rackIsEmpty:c,name:_.current.name,roomID:_.current.roomID}),clearInterval(L.current),ce([]),j(0);case 25:case"end":return e.stop()}}),e)}))),[le.current]),Object(n.useEffect)((function(){if(me(ie.current),ie.current&&_.current.turn){var e=setInterval((function(){0===he.current?Pe():me(he.current-1)}),1e3);return z(e),function(){return clearInterval(L.current)}}}),[_.current.turn]);var Oe=function(e,t){var r,n,c=0,a=Object(l.a)(e);try{for(a.s();!(n=a.n()).done;){var s,i=n.value,o=0,u=Object(l.a)(t);try{for(u.s();!(s=u.n()).done;){var d=s.value;i.includes(d.piece)&&(o+=1)}}catch(b){u.e(b)}finally{u.f()}o>c&&(r=i,c=o)}}catch(b){a.e(b)}finally{a.f()}return r},ve=function(e,t){var r,n=0,c=Object(l.a)(e);try{for(c.s();!(r=c.n()).done;){var a,s=r.value,i=0,o=1,u=Object(l.a)(s);try{for(u.s();!(a=u.n()).done;){var d=a.value,b=0,j=d.tileID,f=d.tileType;ue.current.includes(j)?b=d.weight:"doubleLetter"===f?b=2*d.weight:"doubleWord"===f||"startTile"===f?(o*=2,b=d.weight):"tripleWord"===f?(o*=3,b=d.weight):b="tripleLetter"===f?3*d.weight:d.weight,i+=b}}catch(m){u.e(m)}finally{u.f()}n+=i*=o}}catch(m){c.e(m)}finally{c.f()}return n=t?n+50:n},ge=function(){var e=Object(G.a)(W.a.mark((function e(t){var r;return W.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,Y({url:"/validate",requestType:"post",payload:{words:t}});case 2:return r=e.sent,e.abrupt("return",r);case 4:case"end":return e.stop()}}),e)})));return function(t){return e.apply(this,arguments)}}(),ye=function(){var e=Object(G.a)(W.a.mark((function e(t){var r,n,c,a,s,i,o;return W.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,Y({payload:{},requestType:"get",url:"/bag/".concat(t,"?roomID=").concat(_.current.roomID)});case 2:for(r=e.sent,n=r.pieces,c=0,a=Object.entries(n);c0)){e.next=9;break}r=[],n=Object(l.a)(t);try{for(n.s();!(c=n.n()).done;)a=c.value,s=Le(a,w.current),ke(s),r.push(s)}catch(i){n.e(i)}finally{n.f()}return clearInterval(L.current),I(!1),e.next=8,we(t.length);case 8:q.emit("playEvent",{piecesSwapped:r,isTurnSwapped:!0,name:_.current.name,roomID:_.current.roomID});case 9:case"end":return e.stop()}}),e)})));return function(t){return e.apply(this,arguments)}}(),Ce=function(e,t,r){var n,c=!0,a=0,s=function(e,t,r){for(var n=[],c=[],a="horizontal"===t?De(e):function(e){return(e+=1)%15}(e),s="horizontal"===t?1:15,i="horizontal"===t?15*(a-1):a-1,o=0;o<15;o++)r.includes(i)&&n.push(i),c.push(i),i+=s;var l=c.indexOf(n[0]),u=c.indexOf(n[n.length-1]);return c.slice(l,u+1)}(e[e.length-1],r,e),i=Object(l.a)(s);try{for(i.s();!(n=i.n()).done;){var o=n.value;if(!ue.current.includes(o)&&!e.includes(o))return c=!1}}catch(p){i.e(p)}finally{i.f()}var u,d=Object(l.a)(e);try{for(d.s();!(u=d.n()).done;){var b,j,f,m,x=u.value,h=[];f=x-1,j=x+15,m=x+1,(b=x-15)>=0&&h.push(b),f>=0&&h.push(f),j<=224&&h.push(j),m<=224&&h.push(m),h.forEach((function(e){!t&&ue.current.includes(e)&&(a+=1)}))}}catch(p){d.e(p)}finally{d.f()}return!t&&a<1&&(c=!1),c},Te=function(e,t){var r=function(e){var t,r=(e=e.sort((function(e,t){return e-t})))[0],n=De(r),c=e.length;if(e.every((function(e){return De(e)===n})))t="horizontal";else for(var a=0;a0||H.current.length>0)&&X([].concat(Object(i.a)(w.current),Object(i.a)(H.current)))};Object(n.useEffect)((function(){var e=setInterval((function(){Ae()}),m);return s(e),function(){return clearInterval(a)}}),[]),Object(n.useEffect)((function(){$.current&&Ae()}),[$.current]);var Fe=function(e){return e.preventDefault()},Ye=function(e){return e.preventDefault()},Be=function(e){return e.preventDefault()};Object(n.useEffect)((function(){return t.current.addEventListener("drop",Ge)})),Object(n.useEffect)((function(){return t.current.addEventListener("dragover",Fe)})),Object(n.useEffect)((function(){return t.current.addEventListener("dragleave",Be)})),Object(n.useEffect)((function(){return t.current.addEventListener("dragenter",Ye)}));var He,qe=se.current||Z.current?"flex justify-between items-end":"flex justify-end items-end";return Object(N.jsxs)("div",{className:"block",children:[Object(N.jsx)("div",{ref:t,className:"flex justify-end",children:Object(N.jsx)("div",{style:{width:"25rem"},className:"h-16 flex border-4 bg-gray-300 border-gray-500 rounded-md",children:w.current?w.current.map((function(e,t){if(e)return Object(N.jsx)(E,{id:e.id,onBoard:!1,char:e.piece,weight:e.weight,dragEndHandler:Ne},"".concat(e.id,"_").concat(t))})):null})}),Object(N.jsx)("div",{className:"py-2"}),Object(N.jsxs)("div",{className:qe,children:[se.current||Z.current?Object(N.jsx)(pe,{bag:re.current}):null,Object(N.jsx)(xe,{gameStarted:se,shuffle:function(){return y(Object(i.a)(function(e){for(var t=e.length-1;t>0;t--){var r=Math.floor(Math.random()*(t+1)),n=e[t];e[t]=e[r],e[r]=n}return e}(w.current)))},recall:Se,skip:function(){de.current?V([{type:"info",message:"The game has ended. No more actions are possible."}]):_.current.turn&&(se.current||Z.current)?window.confirm("Are you sure you want to skip your turn?")&&Pe():V([].concat(Object(i.a)(U.current),[{message:"You can not skip turn until it is your turn to play. Kindly wait your turn",overwrite:!1,type:"warning",timeout:5}]))},play:function(){if(de.current)V([{type:"info",message:"The game has ended. No more actions are possible."}]);else if(_.current.turn&&(se.current||Z.current))if(0===H.current.length)V([{type:"warning",message:"You haven't played anything. You can alternatively skip your turn"}]);else{var e=Ee();if(e&&!oe.current.includes(112))return void V([{type:"error",message:"Invalid start move. You must play on the starred tile"}]);Te(oe.current,e)}},draw:function(){return q.emit("drawEvent",{roomID:_.current.roomID})},swap:function(){de.current?V([{type:"info",message:"The game has ended. No more actions are possible."}]):_.current.turn&&(se.current||Z.current)?re.current.length<=7?V([{message:"Swapping pieces is no longer possible, as the bag has ".concat(re.current.length," pieces left. "),type:"info"}]):(Se(),I(!0)):V([{message:"You can not swap pieces until it is your turn to play. Kindly wait your turn",overwrite:!1,type:"warning",timeout:5}])}})]}),Object(N.jsx)("div",{className:"py-2"}),_.current.turn&&ie.current?Object(N.jsxs)("div",{title:"Time left to play",className:"".concat(he.current<10?"blink":""," cursor-help flex text-gray-400 justify-end items-center"),children:[Object(N.jsx)(ee.a,{size:19,className:"inline"}),Object(N.jsx)("span",{className:"pl-1",children:(He=he.current,new Date(1e3*He).toISOString().substr(14,5))})]}):null,Object(N.jsx)(ne,{show:C.current,pieces:w.current,swapCancelHandler:function(){return I(!1)},swapConfirmHandler:Ie})]})},ve=r(137),ge=r(138),ye=r(139);function we(e){var t=e.logs,r=e.close,n=e.show,c=Object(N.jsxs)(N.Fragment,{children:[Object(N.jsx)("div",{onClick:function(){return r()},className:"justify-center items-center flex overflow-x-hidden overflow-y-auto fixed inset-0 z-50 outline-none focus:outline-none",children:Object(N.jsx)("div",{className:"relative w-screen my-6 mx-auto max-w-3xl",children:Object(N.jsxs)("div",{className:"h-96 border-0 rounded-lg shadow-lg relative flex flex-col w-full bg-white outline-none focus:outline-none",children:[Object(N.jsxs)("div",{className:"flex justify-center flex-col pt-7 p-3 border-b border-solid border-blueGray-200 rounded-t",children:[Object(N.jsx)("h3",{className:"text-2xl font-semibold",children:"Logs"}),Object(N.jsx)("div",{className:"text-md pt-1 text-gray-500",children:"History of in-game plays"})]}),Object(N.jsx)("div",{className:"overflow-auto py-0 justify-center",children:t.length>0?Object(N.jsxs)("table",{className:"table-auto w-full mb-5",children:[Object(N.jsx)("thead",{children:Object(N.jsxs)("tr",{className:"bg-gray-100",children:[Object(N.jsx)("th",{className:"border border-gray-300 px-4 py-2 text-black-600",children:"Time"}),Object(N.jsx)("th",{className:"border border-gray-300 px-4 py-2 text-black-600",children:"Event"})]})}),Object(N.jsx)("tbody",{children:t.map((function(e,t){return Object(N.jsxs)("tr",{children:[Object(N.jsx)("td",{className:"w-1/5 text-gray-500 capitalize border border-gray-300 px-4 py-2 text-black-600 font-medium",children:e.time}),Object(N.jsx)("td",{className:"border border-gray-300 px-4 py-2 text-black-600 font-medium",children:e.event})]},t)}))})]}):Object(N.jsx)("div",{className:"flex pt-28 text-gray-500 justify-center",children:"Nothing to show :)"})})]})})}),Object(N.jsx)("div",{className:"opacity-75 fixed inset-0 z-40 bg-black"})]});return n.current?c:null}var Ne=function(e){var t=Object(n.useContext)(O),r=B(!1),c=Object(u.a)(r,3),a=(c[0],c[1]),s=c[2],o=B([]),l=Object(u.a)(o,3),d=(l[0],l[1]),b=l[2],j=Object(n.useContext)(p),f=j.player,m=j.rackState,x=j.boardState,h=j.setGameExited,v=function(){var e=Object(G.a)(W.a.mark((function e(){return W.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(h(!0),!window.confirm("Are you sure you want to leave the game?")){e.next=9;break}return e.next=5,g();case 5:alert("Note that you can still resume this game session using your name and the session ID."),setTimeout((function(){t.emit("leave",{roomID:f.current.roomID,name:f.current.name}),window.location.reload()}),700),e.next=10;break;case 9:h(!1);case 10:case"end":return e.stop()}}),e)})));return function(){return e.apply(this,arguments)}}(),g=function(){var e=Object(G.a)(W.a.mark((function e(){var t;return W.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return t={player:f.current,rack:m.current,board:x.current,roomID:f.current.roomID},e.next=3,Y({requestType:"post",url:"/cache",payload:t});case 3:case"end":return e.stop()}}),e)})));return function(){return e.apply(this,arguments)}}();return Object(n.useEffect)((function(){t.on("leftRoom",function(){var e=Object(G.a)(W.a.mark((function e(t){return W.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(t.name===f.current.name){e.next=5;break}return e.next=3,g();case 3:alert("".concat(t.name," has left the game session. Note that you can still resume this game session using your name and the session ID.")),setTimeout((function(){window.location.reload()}),1700);case 5:case"end":return e.stop()}}),e)})));return function(t){return e.apply(this,arguments)}}())}),[]),Object(n.useEffect)(Object(G.a)(W.a.mark((function e(){var t;return W.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(!s.current){e.next=5;break}return e.next=3,Y({requestType:"get",url:"/logs/".concat(f.current.roomID)});case 3:"success"===(t=e.sent).status&&d(Object(i.a)(t.logs));case 5:case"end":return e.stop()}}),e)}))),[s.current]),Object(N.jsxs)(N.Fragment,{children:[Object(N.jsxs)("div",{className:"flex justify-between items-center",children:[Object(N.jsxs)("div",{onClick:function(){return a(!0)},className:"cursor-pointer flex justify-end items-end space-x-1 text-gray-500",children:[Object(N.jsx)("div",{className:"flex-none",children:Object(N.jsx)(ve.a,{size:22})}),Object(N.jsx)("div",{className:"flex-none text-sm",children:"Game Logs"})]}),Object(N.jsx)("div",{className:"flex justify-end items-end space-x-1",children:Object(N.jsxs)("div",{className:"font-bold pt-2 text-sm text-gray-500",children:[Object(N.jsx)(ge.a,{size:22,className:"inline pb-1 pr-1"}),f.current.roomID]})}),Object(N.jsx)("div",{onClick:v,className:"flex cursor-pointer justify-end items-end space-x-1",children:Object(N.jsxs)("div",{className:"font-bold pt-2 text-sm text-red-500",children:[Object(N.jsx)(ye.a,{size:22,className:"inline pb-1 pr-1"}),"Leave"]})})]}),Object(N.jsx)(we,{logs:b.current,close:function(){return a(!1)},show:s})]})},ke=function(e){return Object(N.jsxs)("div",{className:"flex flex-col pt-4 pb-10 justify-around w-450",children:[Object(N.jsx)("div",{className:"block",children:Object(N.jsx)(X,{})}),Object(N.jsx)("div",{className:"block py-8",children:Object(N.jsx)(V,{status:"off"})}),Object(N.jsx)("div",{className:"block py-8",children:Object(N.jsx)(Z,{})}),Object(N.jsx)("div",{className:"block py-8",children:Object(N.jsx)("div",{className:"flex justify-end",children:Object(N.jsx)(Oe,{})})}),Object(N.jsx)("div",{className:"block",children:Object(N.jsx)("div",{className:"pt-1",children:Object(N.jsx)(Ne,{})})})]})},De=r(140),Ie=r(141),Ce=r(142),Te=function(e){return Object(N.jsxs)("div",{className:"h-full w-full flex flex-col items-center justify-center",children:[Object(N.jsxs)("div",{className:"block flex justify-center text-gray-800 text-bold text-3xl space-x-2",children:[Object(N.jsx)("div",{className:"text-4xl",children:"Scrabble"}),Object(N.jsx)("div",{className:"bg-yellow-200 px-2 font-normal rounded-md text-black self-center text-sm border",children:"v2.4"})]}),Object(N.jsxs)("div",{className:"flex my-6 mb-2 space-x-6 block justify-center",children:[Object(N.jsxs)("button",{title:"Create a new game session",onClick:function(){return e.setGameChoice("new")},className:"h-12 w-36 bg-green-600 hover:bg-green-700 text-white font-bold px-4 border border-green-700 rounded inline-flex justify-center items-center",children:[Object(N.jsx)(De.a,{strokeWidth:3,size:26}),Object(N.jsx)("span",{className:"pl-1 pr-2 text-md",children:"Host"})]}),Object(N.jsxs)("button",{onClick:function(){return e.setGameChoice("join")},className:"h-12 w-36 bg-blue-600 hover:bg-blue-700 text-white font-bold px-4 border border-blue-700 rounded inline-flex justify-center items-center",children:[Object(N.jsx)(Ie.a,{strokeWidth:3,size:26}),Object(N.jsx)("span",{title:"Join a game session",className:"pl-1 pr-2 text-md",children:"Join"})]}),Object(N.jsxs)("button",{onClick:function(){return e.setGameChoice("resume")},className:"h-12 w-36 bg-red-600 hover:bg-red-700 text-white font-bold px-4 border border-red-700 rounded inline-flex justify-center items-center",children:[Object(N.jsx)(Ce.a,{strokeWidth:3,size:22}),Object(N.jsx)("span",{title:"Resume a game session",className:"pl-1 pr-1 text-md",children:"Resume"})]})]}),Object(N.jsx)("div",{className:"flex my-4 mb-2 space-x-6 block justify-center",children:Object(N.jsxs)("div",{className:"text-gray-400",children:["Familiarize yourself with the ",Object(N.jsx)("a",{className:"text-blue-500",href:"https://github.com/olumidesan/scrabble",target:"_blank",referrerPolicy:"no-referrer",children:"Game Notes"})," before you begin"]})})]})},Se=r(143),Ee=r(144),Pe=function(e){var t=Object(n.useContext)(O),r=Object(n.useContext)(g).setNotifications,c=B([]),a=Object(u.a)(c,3),s=(a[0],a[1]),i=a[2],o=B("Preparing game room..."),l=Object(u.a)(o,3),d=(l[0],l[1]),b=l[2],j=Object(n.useContext)(p),f=j.player,m=j.setRackState,x=j.setGameStarted,h=j.setGameResumed,v=j.setGameCreated,y=j.setAllowAudio,w=j.setPlayers,k=j.setUsedTiles,D=j.setTimeToPlay;return Object(n.useEffect)((function(){s([f.current]),t.on("joinedRoom",(function(r){D(r.timeToPlay),y(r.enableAudio),s(r.connectedPlayers),i.current.length===e.numPlayers&&f.current.isHost&&t.emit("create"===r.mode?"gameCreateEvent":"gameResumeEvent",{roomID:e.roomID})})),t.on("gameCreate",(function(e){var t=f.current.isHost?"Make a draw using the yellow button on your button rack. You'll":"The host will make a draw, and you'll";r([{message:"Welcome, ".concat(f.current.name,"! ").concat(t," be notified (just like this) of who gets to play first. Good luck! \ud83c\udf40"),overwrite:!1,type:"info"}]),w(e.allPlayers),setTimeout((function(){d("Fetching game bag...")}),1500),setTimeout((function(){v(!0)}),3500)}),[]),t.on("gameResume",(function(e){r([{message:"Welcome back, ".concat(f.current.name,". Again, Good luck! \ud83c\udf40"),overwrite:!1,type:"info"}]),w(e.allPlayers),k(e.usedTiles),m(e.rack[f.current.name]),setTimeout((function(){d("Fetching your pieces...")}),1500),setTimeout((function(){d("Resuming board state...")}),3500),setTimeout((function(){x(!0),h(!0)}),5500)}),[])}),[]),Object(N.jsx)("div",{className:"h-full w-full flex flex-col items-center justify-center",children:Object(N.jsxs)("div",{className:"max-w-sm pt-4 w-96 rounded overflow-hidden shadow-2xl",children:[Object(N.jsxs)("div",{className:"px-8 flex justify-between items-center w-full py-4",children:[Object(N.jsx)("div",{className:"font-bold text-left text-2xl",children:"Waiting Room"}),Object(N.jsxs)("div",{title:"Share this ID with players you want to join your game session",className:"cursor-help font-bold pt-2 text-sm text-gray-400",children:[Object(N.jsx)(ge.a,{size:23,className:"inline pb-1 pr-1"}),e.roomID]})]}),Object(N.jsxs)("div",{className:"py-8 px-6 border-t border",children:[Object(N.jsx)(Se.a,{size:22,className:"inline"})," Connected players: ",I(i.current.length),"/",Object(N.jsx)("span",{className:"font-bold",children:e.numPlayers})]}),i.current.length===e.numPlayers?Object(N.jsx)("div",{className:"py-4 border-t border",children:Object(N.jsx)("div",{className:"text-sm italic text-gray-500",children:b.current})}):null,Object(N.jsx)("div",{className:"py-4 border-t border",children:i.current.map((function(e,t){return Object(N.jsxs)("div",{className:"text-sm text-gray-500",children:[e.name===f.current.name?"You (".concat(e.name,")"):e.name," joined the game room"]},t)}))}),f.current.isHost?Object(N.jsx)("div",{className:"h-14 border-t border",children:Object(N.jsxs)("button",{onClick:e.handleDestroyGameSession,className:"h-full w-full bg-red-600 hover:bg-red-700 text-white font-bold border border-red-700 inline-flex justify-center items-center",children:[Object(N.jsx)(Ee.a,{size:22}),Object(N.jsx)("span",{className:"pl-1 pr-2 text-md",children:"Delete Session"})]})}):null]})})},ze=function(e){var t=Object(n.useState)(0),r=Object(u.a)(t,2),c=r[0],a=r[1],s=Object(n.useState)(!1),i=Object(u.a)(s,2),l=i[0],d=i[1],b=Object(n.useState)({valid:!0,message:"",roomID:""}),j=Object(u.a)(b,2),f=j[0],m=j[1],x=Object(n.useState)({valid:!0,message:"",name:""}),h=Object(u.a)(x,2),v=h[0],g=h[1],y=Object(n.useContext)(O),w=Object(n.useContext)(p).setPlayer,k=function(){var e=Object(G.a)(W.a.mark((function e(t,r){return W.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,Y({requestType:"get",url:"/room/".concat(t,"?name=").concat(v.name,"&mode=").concat(r),payload:{}});case 2:return e.abrupt("return",e.sent);case 3:case"end":return e.stop()}}),e)})));return function(t,r){return e.apply(this,arguments)}}(),I=function(){var e=Object(G.a)(W.a.mark((function e(){var t,r;return W.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(!(v.name.length<2||v.name.length>10)){e.next=3;break}return g(Object(o.a)(Object(o.a)({},v),{},{valid:!1,message:"Name must be between 2 and 10 characters"})),e.abrupt("return");case 3:if(D(v.name)){e.next=6;break}return g(Object(o.a)(Object(o.a)({},v),{},{valid:!1,message:"Name must be alphanumeric"})),e.abrupt("return");case 6:if(!(f.roomID.length<11)){e.next=9;break}return m(Object(o.a)(Object(o.a)({},f),{},{valid:!1,message:"Session ID must be 11 characters"})),e.abrupt("return");case 9:return e.next=11,k(f.roomID,"resume");case 11:"error"===(t=e.sent).status?m(Object(o.a)(Object(o.a)({},f),{},{valid:!1,message:t.message})):"nameError"===t.status?g(Object(o.a)(Object(o.a)({},v),{},{valid:!1,message:t.message})):(r={turn:!1,isSpeaking:!1,roomID:f.roomID,score:t.player.score,isHost:t.player.isHost,name:v.name.capitalize()},y.emit("resume",{roomID:f.roomID,player:r}),a(t.limit),w(r),d(!0));case 13:case"end":return e.stop()}}),e)})));return function(){return e.apply(this,arguments)}}(),C="".concat(v.valid?"mb-4":"mb-0"," w-80 appearance-none block bg-gray-100 text-gray-700 border border-gray-200 rounded py-3 px-4 leading-tight focus:outline-none focus:bg-white focus:border-gray-500");return l?Object(N.jsx)(Pe,{roomID:f.roomID,numPlayers:c}):Object(N.jsxs)("div",{className:"h-full w-full flex flex-col items-center justify-center",children:[Object(N.jsx)("div",{className:"font-medium text-xl pb-3",children:"Resume a Game Session"}),Object(N.jsxs)("div",{className:"justify-center flex flex-col pt-4 w-96 rounded overflow-hidden shadow-2xl",children:[Object(N.jsx)("div",{className:"flex self-center pt-6 mx-3 mb-1",children:Object(N.jsxs)("div",{className:"px-3",children:[Object(N.jsx)("label",{className:"block uppercase text-left tracking-wide text-gray-700 text-xs font-bold mb-1",children:"Previous Session Name"}),Object(N.jsx)("input",{minLength:3,maxLength:10,onChange:function(e){g(Object(o.a)(Object(o.a)({},v),{},{valid:!0,name:e.target.value.trim()}))},title:"Your name",className:C,type:"text",placeholder:"E.g. Joey"})]})}),Object(N.jsx)("div",{className:"flex self-center wrap mx-3",children:v.valid?null:Object(N.jsx)("div",{className:"w-full px-3",children:Object(N.jsx)("div",{className:"w-80 md:flex md:items-center",children:Object(N.jsx)("span",{className:"text-sm text-red-600 mb-4",children:v.message})})})}),Object(N.jsx)("div",{className:"flex self-center mx-3 mb-0",children:Object(N.jsxs)("div",{className:"px-3",children:[Object(N.jsx)("label",{className:"block uppercase text-left tracking-wide text-gray-700 text-xs font-bold mb-2",children:"Previous Session ID"}),Object(N.jsx)("input",{onChange:function(e){m(Object(o.a)(Object(o.a)({},f),{},{valid:!0,roomID:e.target.value.trim()}))},maxLength:11,className:"w-80 appearance-none block bg-gray-100 text-gray-700 border border-gray-200 rounded py-3 px-4 mb-1 leading-tight focus:outline-none focus:bg-white focus:border-gray-500",type:"text",placeholder:"E.g. 41828-11860"})]})}),Object(N.jsx)("div",{className:"flex self-center wrap mx-3 mb-6",children:f.valid?null:Object(N.jsx)("div",{className:"w-full px-3",children:Object(N.jsx)("div",{className:"w-80 md:flex md:items-center",children:Object(N.jsx)("span",{className:"text-sm text-red-600 pb-2",children:f.message})})})}),Object(N.jsxs)("div",{className:"h-14 border-t flex border",children:[Object(N.jsx)("div",{className:"block w-1/2",children:Object(N.jsxs)("button",{onClick:function(){return e.setGameChoice("cancel")},className:"h-full w-full bg-red-600 hover:bg-red-700 text-white font-bold border border-red-700 inline-flex justify-center items-center",children:[Object(N.jsx)(te.a,{size:26}),Object(N.jsx)("span",{className:"pl-1 pr-2 text-md",children:"Cancel"})]})}),Object(N.jsx)("div",{className:"block w-1/2",children:Object(N.jsxs)("button",{onClick:I,className:"h-full w-full bg-green-600 hover:bg-green-700 text-white font-bold border border-green-700 inline-flex justify-center items-center",children:[Object(N.jsx)(Ce.a,{className:"pb",strokeWidth:3,size:26}),Object(N.jsx)("span",{className:"pl-1 pr-2 text-md",children:"Resume"})]})})]})]})]})},Le=function(e){var t=Object(n.useState)(0),r=Object(u.a)(t,2),c=r[0],a=r[1],s=Object(n.useState)(!1),i=Object(u.a)(s,2),l=i[0],d=i[1],b=Object(n.useState)({valid:!0,message:"",roomID:""}),j=Object(u.a)(b,2),f=j[0],m=j[1],x=Object(n.useState)({valid:!0,message:"",name:""}),h=Object(u.a)(x,2),v=h[0],g=h[1],y=Object(n.useContext)(O),w=Object(n.useContext)(p).setPlayer,k=function(){var e=Object(G.a)(W.a.mark((function e(t,r){return W.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,Y({requestType:"get",url:"/room/".concat(t,"?name=").concat(v.name,"&mode=").concat(r),payload:{}});case 2:return e.abrupt("return",e.sent);case 3:case"end":return e.stop()}}),e)})));return function(t,r){return e.apply(this,arguments)}}(),I=function(){var e=Object(G.a)(W.a.mark((function e(){var t,r;return W.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(!(v.name.length<2||v.name.length>10)){e.next=3;break}return g(Object(o.a)(Object(o.a)({},v),{},{valid:!1,message:"Name must be between 2 and 10 characters"})),e.abrupt("return");case 3:if(D(v.name)){e.next=6;break}return g(Object(o.a)(Object(o.a)({},v),{},{valid:!1,message:"Name must be alphanumeric"})),e.abrupt("return");case 6:if(!(f.roomID.length<11)){e.next=9;break}return m(Object(o.a)(Object(o.a)({},f),{},{valid:!1,message:"Session ID must be 11 characters"})),e.abrupt("return");case 9:return e.next=11,k(f.roomID,"join");case 11:"error"===(t=e.sent).status?m(Object(o.a)(Object(o.a)({},f),{},{valid:!1,message:t.message})):"nameError"===t.status?g(Object(o.a)(Object(o.a)({},v),{},{valid:!1,message:t.message})):t.room.joinable?(r={score:0,turn:!1,isHost:!1,isSpeaking:!1,roomID:f.roomID,name:v.name.capitalize()},y.emit("join",{roomID:f.roomID,player:r}),a(t.room.limit),w(r),d(!0)):m({valid:!1,message:"Game session has started and cannot be joined"});case 13:case"end":return e.stop()}}),e)})));return function(){return e.apply(this,arguments)}}(),C="".concat(v.valid?"mb-4":"mb-0"," w-80 appearance-none block bg-gray-100 text-gray-700 border border-gray-200 rounded py-3 px-4 leading-tight focus:outline-none focus:bg-white focus:border-gray-500");return l?Object(N.jsx)(Pe,{roomID:f.roomID,numPlayers:c}):Object(N.jsxs)("div",{className:"h-full w-full flex flex-col items-center justify-center",children:[Object(N.jsx)("div",{className:"font-medium text-xl pb-3",children:"Join a Game Session"}),Object(N.jsxs)("div",{className:"justify-center flex flex-col pt-4 w-96 rounded overflow-hidden shadow-2xl",children:[Object(N.jsx)("div",{className:"flex self-center pt-6 mx-3 mb-1",children:Object(N.jsxs)("div",{className:"px-3",children:[Object(N.jsx)("label",{className:"block uppercase text-left tracking-wide text-gray-700 text-xs font-bold mb-1",children:"Your Name"}),Object(N.jsx)("input",{minLength:3,maxLength:10,onChange:function(e){g(Object(o.a)(Object(o.a)({},v),{},{valid:!0,name:e.target.value.trim()}))},title:"Your name",className:C,type:"text",placeholder:"E.g. Joey"})]})}),Object(N.jsx)("div",{className:"flex self-center wrap mx-3",children:v.valid?null:Object(N.jsx)("div",{className:"w-full px-3",children:Object(N.jsx)("div",{className:"w-80 md:flex md:items-center",children:Object(N.jsx)("span",{className:"text-sm text-red-600 mb-4",children:v.message})})})}),Object(N.jsx)("div",{className:"flex self-center mx-3 mb-0",children:Object(N.jsxs)("div",{className:"px-3",children:[Object(N.jsx)("label",{className:"block uppercase text-left tracking-wide text-gray-700 text-xs font-bold mb-2",children:"Session ID"}),Object(N.jsx)("input",{onChange:function(e){m(Object(o.a)(Object(o.a)({},f),{},{valid:!0,roomID:e.target.value.trim()}))},maxLength:11,className:"w-80 appearance-none block bg-gray-100 text-gray-700 border border-gray-200 rounded py-3 px-4 mb-1 leading-tight focus:outline-none focus:bg-white focus:border-gray-500",type:"text",placeholder:"E.g. 41828-11860"})]})}),Object(N.jsx)("div",{className:"flex self-center wrap mx-3 mb-6",children:f.valid?null:Object(N.jsx)("div",{className:"w-full px-3",children:Object(N.jsx)("div",{className:"w-80 md:flex md:items-center",children:Object(N.jsx)("span",{className:"text-sm text-red-600 pb-2",children:f.message})})})}),Object(N.jsxs)("div",{className:"h-14 border-t flex border",children:[Object(N.jsx)("div",{className:"block w-1/2",children:Object(N.jsxs)("button",{onClick:function(){return e.setGameChoice("cancel")},className:"h-full w-full bg-red-600 hover:bg-red-700 text-white font-bold border border-red-700 inline-flex justify-center items-center",children:[Object(N.jsx)(te.a,{size:26}),Object(N.jsx)("span",{className:"pl-1 pr-2 text-md",children:"Cancel"})]})}),Object(N.jsx)("div",{className:"block w-1/2",children:Object(N.jsxs)("button",{onClick:I,className:"h-full w-full bg-green-600 hover:bg-green-700 text-white font-bold border border-green-700 inline-flex justify-center items-center",children:[Object(N.jsx)(Ie.a,{className:"pb",strokeWidth:3,size:26}),Object(N.jsx)("span",{className:"pl-1 pr-2 text-md",children:"Join"})]})})]})]})]})},Re=r(145),We=r(146),Ge=r(147),Ae="".concat(k(5),"-").concat(k(5)),Fe=function(e){var t=Object(n.useState)(!1),r=Object(u.a)(t,2),c=r[0],a=r[1],s=Object(n.useState)(2),i=Object(u.a)(s,2),l=i[0],d=i[1],b=Object(n.useState)(null),j=Object(u.a)(b,2),f=j[0],m=j[1],x=Object(n.useState)(!1),h=Object(u.a)(x,2),v=h[0],g=h[1],y=Object(n.useState)(!1),w=Object(u.a)(y,2),I=w[0],C=w[1],T=Object(n.useState)({valid:!0,message:"",name:""}),S=Object(u.a)(T,2),E=S[0],P=S[1],z=Object(n.useRef)(null),L=Object(n.useContext)(O),R=Object(n.useContext)(p).setPlayer,W="".concat(E.valid?"mb-4":"mb-0"," w-80 appearance-none block bg-gray-100 text-gray-700 border border-gray-200 rounded py-3 px-4 leading-tight focus:outline-none focus:bg-white focus:border-gray-500");return v?Object(N.jsx)(Pe,{roomID:Ae,numPlayers:l,handleDestroyGameSession:function(){window.confirm("Are you sure you want to delete this game session?")&&(d(2),C(!1),g(!1),e.setGameChoice("cancel"),P({message:"",valid:!0,name:""}),L.emit("leave",{roomID:Ae}),Ae="".concat(k(5),"-").concat(k(5)))}}):Object(N.jsxs)("div",{className:"h-full w-full flex flex-col items-center justify-center",children:[Object(N.jsx)("div",{className:"font-medium text-xl pb-3",children:"Host a Game Session"}),Object(N.jsxs)("div",{className:"justify-center flex flex-col pt-4 w-96 rounded overflow-hidden shadow-2xl",children:[Object(N.jsx)("div",{className:"flex self-center pt-6 mx-3 mb-2",children:Object(N.jsxs)("div",{className:"px-3",children:[Object(N.jsx)("label",{className:"block uppercase text-left tracking-wide text-gray-700 text-xs font-bold mb-2",children:"Your Session ID"}),Object(N.jsxs)("div",{className:"relative",children:[Object(N.jsx)("input",{ref:z,title:"Automatically-generated session ID",disabled:!0,value:Ae,className:"w-80 cursor-not-allowed appearance-none block bg-gray-100 text-gray-700 border border-gray-200 rounded py-3 px-4 mb-3 leading-tight focus:outline-none focus:bg-white focus:border-gray-500",type:"text",placeholder:"E.g. Joey"}),"https:"===window.location.protocol||"localhost"===window.location.hostname?Object(N.jsx)("div",{className:"absolute inset-y-0 right-0 w-1/6",children:Object(N.jsx)("button",{title:"Copy",onClick:function(){navigator.clipboard.writeText(z.current.value),a(!0),setTimeout((function(){a(!1)}),1500)},className:"h-full w-full border- rounded-r bg-gray-700 hover:bg-gray-700 text-white font-bold inline-flex justify-center items-center",children:c?Object(N.jsx)(Re.a,{strokeWidth:3,size:20}):Object(N.jsx)(We.a,{strokeWidth:3,size:20})})}):null]})]})}),Object(N.jsx)("div",{className:"flex self-center mx-3 mb-1",children:Object(N.jsxs)("div",{className:"px-3",children:[Object(N.jsx)("label",{className:"block uppercase text-left tracking-wide text-gray-700 text-xs font-bold mb-1",children:"Your Name"}),Object(N.jsx)("input",{minLength:3,maxLength:10,onChange:function(e){P(Object(o.a)(Object(o.a)({},E),{},{valid:!0,name:e.target.value.trim()}))},title:"Your name",className:W,type:"text",placeholder:"E.g. Phoebe"})]})}),Object(N.jsx)("div",{className:"flex self-center wrap mx-3",children:E.valid?null:Object(N.jsx)("div",{className:"w-full px-3",children:Object(N.jsx)("div",{className:"w-80 md:flex md:items-center",children:Object(N.jsx)("span",{className:"text-sm text-red-600 mb-4",children:E.message})})})}),Object(N.jsx)("div",{className:"flex self-center flex-wrap -mx-3 mt-1 mb-4",children:Object(N.jsxs)("div",{className:"w-full px-3",children:[Object(N.jsx)("label",{className:"block uppercase text-left tracking-wide text-gray-700 text-xs font-bold mb-2",children:"Number of Players"}),Object(N.jsxs)("div",{className:"relative",children:[Object(N.jsxs)("select",{onChange:function(e){d(parseInt(e.target.value))},className:"block cursor-pointer appearance-none w-80 bg-gray-100 border border-gray-200 text-gray-700 py-3 px-4 pr-8 rounded leading-tight focus:outline-none focus:bg-white focus:border-gray-500",children:[Object(N.jsx)("option",{defaultValue:!0,value:2,children:"Two"}),Object(N.jsx)("option",{value:3,children:"Three"}),Object(N.jsx)("option",{value:4,children:"Four"})]}),Object(N.jsx)("div",{className:"pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700",children:Object(N.jsx)(Ge.a,{size:20})})]})]})}),Object(N.jsx)("div",{className:"flex self-center flex-wrap -mx-3 mt-1 mb-6",children:Object(N.jsxs)("div",{className:"w-full px-3",children:[Object(N.jsx)("label",{title:"Required time to play before turn is automatically skipped",className:"cursor-help block uppercase text-left tracking-wide text-gray-700 text-xs font-bold mb-2",children:"Time to Play"}),Object(N.jsxs)("div",{className:"relative",children:[Object(N.jsxs)("select",{onChange:function(e){"Disabled"!==e.target.value?m(parseInt(e.target.value)):m(null)},className:"block cursor-pointer appearance-none w-80 bg-gray-100 border border-gray-200 text-gray-700 py-3 px-4 pr-8 rounded leading-tight focus:outline-none focus:bg-white focus:border-gray-500",children:[Object(N.jsx)("option",{defaultValue:!0,value:null,children:"Disabled"}),Object(N.jsx)("option",{value:300,children:"Five minutes"}),Object(N.jsx)("option",{value:240,children:"Four minutes"}),Object(N.jsx)("option",{value:180,children:"Three minutes"}),Object(N.jsx)("option",{value:120,children:"Two minutes"}),Object(N.jsx)("option",{value:60,children:"One minute"})]}),Object(N.jsx)("div",{className:"pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700",children:Object(N.jsx)(Ge.a,{size:20})})]})]})}),"https"===window.location.protocol||"localhost"===window.location.hostname?Object(N.jsx)("div",{className:"flex self-center wrap mx-3 mb-4",children:Object(N.jsx)("div",{className:"w-full px-3",children:Object(N.jsxs)("div",{title:"If checked, push-to-talk audio between players will be allowed. That is, of course, if the player allows it",className:"w-80 cursor-help md:flex md:items-center mb-2",children:[Object(N.jsx)("label",{className:"pb-1 block text-gray-500 font-bold",children:Object(N.jsx)("input",{onChange:function(e){C(e.target.checked)},className:"mr-2 leading-tight h-5 w-5",type:"checkbox"})}),Object(N.jsx)("span",{className:"text-sm pb-2",children:"Enable Audio Chat (Push-To-Talk)"})]})})}):null,Object(N.jsxs)("div",{className:"h-14 border-t flex border",children:[Object(N.jsx)("div",{className:"block w-1/2",children:Object(N.jsxs)("button",{onClick:function(){return e.setGameChoice("cancel")},className:"h-full w-full bg-red-600 hover:bg-red-700 text-white font-bold border border-red-700 inline-flex justify-center items-center",children:[Object(N.jsx)(te.a,{size:26}),Object(N.jsx)("span",{className:"pl-1 pr-2 text-md",children:"Cancel"})]})}),Object(N.jsx)("div",{className:"block w-1/2",children:Object(N.jsxs)("button",{onClick:function(){if(E.name.length<2||E.name.length>10)P(Object(o.a)(Object(o.a)({},E),{},{valid:!1,message:"Name must be between 2 and 10 characters"}));else if(D(E.name)){var e={roomID:Ae,score:0,turn:!1,isHost:!0,isSpeaking:!1,name:E.name.capitalize()};L.emit("join",{roomID:Ae,enableAudio:I,timeToPlay:f,player:e,limit:l}),R(e),g(!0)}else P(Object(o.a)(Object(o.a)({},E),{},{valid:!1,message:"Name must be alphanumeric"}))},className:"h-full w-full bg-green-600 hover:bg-green-700 text-white font-bold border border-green-700 inline-flex justify-center items-center",children:[Object(N.jsx)(De.a,{className:"pb",strokeWidth:3,size:26}),Object(N.jsx)("span",{className:"pl-1 pr-2 text-md",children:"Host"})]})})]})]})]})},Ye=function(e){var t=Object(n.useState)(!1),r=Object(u.a)(t,2),c=r[0],a=r[1],s=Object(n.useState)(!1),i=Object(u.a)(s,2),o=i[0],l=i[1],d=Object(n.useState)(!1),b=Object(u.a)(d,2),j=b[0],f=b[1],m=function(e){"new"===e?(a(!0),l(!1),f(!1)):"join"===e?(l(!0),a(!1),f(!1)):"resume"===e?(a(!1),l(!1),f(!0)):(a(!1),l(!1),f(!1))};return c?Object(N.jsx)(Fe,{setGameChoice:m}):o?Object(N.jsx)(Le,{setGameChoice:m}):j?Object(N.jsx)(ze,{setGameChoice:m}):Object(N.jsx)(Te,{setGameChoice:m})},Be=r(148);function He(e){var t=B(!1),r=Object(u.a)(t,3),c=(r[0],r[1]),a=r[2],s=Object(n.useState)(""),i=Object(u.a)(s,2),o=i[0],d=i[1],b=Object(n.useContext)(O),j=Object(n.useContext)(p),f=j.player,m=j.setPlayers;Object(n.useEffect)((function(){b.on("gameEnd",(function(e){var t,r,n={name:"",score:0},a=Object(l.a)(e);try{for(a.s();!(r=a.n()).done;){var s=r.value;s.score>n.score&&(n.name=s.name,n.score=s.score)}}catch(b){a.e(b)}finally{a.f()}t=f.current.name===n.name?"Congratulations, ".concat(n.name,"! You are the winner with ").concat(n.score," points. You've earned the trophy!"):"".concat(n.name," is the winner with ").concat(n.score," points. Good game, ").concat(f.current.name,". You still earn a medal \ud83c\udfc5");var i,o=Object(l.a)(e);try{for(o.s();!(i=o.n()).done;){var u=i.value;u.name===n.name?u.name="".concat(u.name," \ud83c\udfc6"):u.name="".concat(u.name," \ud83c\udfc5")}}catch(b){o.e(b)}finally{o.f()}d(t),m(e),c(!0)}))}),[]);var x=Object(N.jsxs)(N.Fragment,{children:[Object(N.jsx)("div",{className:"justify-center items-center flex overflow-x-hidden overflow-y-auto fixed inset-0 z-50 outline-none focus:outline-none",children:Object(N.jsx)("div",{className:"relative w-screen my-6 mx-auto max-w-3xl",children:Object(N.jsxs)("div",{className:"border-0 rounded-lg shadow-lg relative flex flex-col w-full bg-white outline-none focus:outline-none",children:[Object(N.jsx)("div",{className:"flex justify-center flex-col pt-7 p-3 border-b border-solid border-blueGray-200 rounded-t",children:Object(N.jsx)("h3",{className:"text-2xl font-semibold",children:"\ud83c\udfc6 The Game Has Ended \ud83c\udfc6"})}),Object(N.jsxs)("div",{className:"flex-col relative text-lg pt-5 pb-7 flex justify-center flex-wrap",children:[Object(N.jsx)("div",{className:"pb-6",children:o}),Object(N.jsxs)("button",{onClick:function(){c(!1)},className:"self-center bg-green-600 hover:bg-green-700 text-white inline py-2 px-4 border border-green-700 rounded",children:[Object(N.jsx)(Be.a,{className:"inline pb-1",strokeWidth:2,size:24}),Object(N.jsx)("span",{className:"pl-1",children:"Cool, Thanks"})]})]})]})})}),Object(N.jsx)("div",{className:"opacity-75 fixed inset-0 z-40 bg-black"})]});return a.current?x:null}var qe=r(149),Je=function(){var e=B(!1),t=Object(u.a)(e,3),r=(t[0],t[1]),c=t[2],a=B(!1),s=Object(u.a)(a,3),d=(s[0],s[1]),b=s[2],j=B(!1),f=Object(u.a)(j,3),m=(f[0],f[1]),x=f[2],h=B(!1),y=Object(u.a)(h,3),w=(y[0],y[1]),k=y[2],D=B(!1),I=Object(u.a)(D,3),C=(I[0],I[1]),T=I[2],S=B(!1),E=Object(u.a)(S,3),P=(E[0],E[1]),z=E[2],L=B(!1),R=Object(u.a)(L,3),W=(R[0],R[1]),G=R[2],A=B(!1),F=Object(u.a)(A,3),Y=(F[0],F[1]),H=F[2],J=B(null),M=Object(u.a)(J,3),U=(M[0],M[1]),V=M[2],K=B([]),_=Object(u.a)(K,3),Z=(_[0],_[1]),$=_[2],Q=B([]),X=Object(u.a)(Q,3),ee=(X[0],X[1]),te=X[2],re=B([]),ne=Object(u.a)(re,3),ce=(ne[0],ne[1]),ae=ne[2],se=B([]),ie=Object(u.a)(se,3),oe=(ie[0],ie[1]),le=ie[2],ue=B([]),de=Object(u.a)(ue,3),be=(de[0],de[1]),je=de[2],fe=B(!0),me=Object(u.a)(fe,3),xe=(me[0],me[1]),he=me[2],pe=B([]),Oe=Object(u.a)(pe,3),ve=(Oe[0],Oe[1]),ge=Oe[2],ye=B({}),we=Object(u.a)(ye,3),Ne=(we[0],we[1]),De=we[2],Ie=B({length:0,pieces:{}}),Ce=Object(u.a)(Ie,3),Te=(Ce[0],Ce[1]),Se=Ce[2],Ee=B([]),Pe=Object(u.a)(Ee,3),ze=(Pe[0],Pe[1]),Le=Pe[2],Re=Object(n.useContext)(O),We={bag:Se,setBag:Te,player:De,setPlayer:Ne,players:ge,setPlayers:ve,playFlag:z,setPlayFlag:P,gameEnded:c,setGameEnded:r,usedTiles:je,setUsedTiles:be,rackState:ae,setRackState:ce,boardState:te,setBoardState:ee,allowAudio:H,setAllowAudio:Y,recallFlag:G,setRecallFlag:W,timeToPlay:V,setTimeToPlay:U,gameExited:x,setGameExited:m,gameStarted:T,setGameStarted:C,gameCreated:b,setGameCreated:d,gameResumed:k,setGameResumed:w,playedTiles:$,setPlayedTiles:Z,playedWords:le,setPlayedWords:oe};Object(n.useEffect)((function(){Re.on("resumeDone",(function(e){Te(e.bag),w(!0),C(!0);var t,r=[],n=e.playerToPlay,c="",a=Object(l.a)(ge.current);try{for(a.s();!(t=a.n()).done;){var s=t.value;s.name===n&&(s.turn=!0),r.push(s)}}catch(i){a.e(i)}finally{a.f()}ve(r),n===De.current.name?(c="You were to play before the game was paused",Ne(Object(o.a)(Object(o.a)({},De.current),{},{turn:!0}))):c="".concat(n," was to play before the game was paused"),ze([{message:"".concat(c,". The game has officially resumed."),overwrite:!1,type:"info",timeout:5}])})),Re.on("drawDone",(function(e){Te(e.bag),C(!0);var t,r,n=[],c=e.players[0],a="",s=Object(l.a)(ge.current);try{for(s.s();!(r=s.n()).done;){var i=r.value;i.name===c&&(i.turn=!0),n.push(i)}}catch(u){s.e(u)}finally{s.f()}ve(n),c===De.current.name?(t="You get to play first",Ne(Object(o.a)(Object(o.a)({},De.current),{},{turn:!0}))):t="".concat(c," gets to play first"),e.players.forEach((function(t,r){t===De.current.name&&(t="".concat(t," (You)")),r+1===e.players.length?a+=t:a+="".concat(t,", then ")})),ze([{message:"".concat(t,". Also note that the turn order is, ").concat(a),overwrite:!1,type:"info",timeout:5}])})),Re.on("validPlay",(function(e){Ge(e.playerToPlay.name),e.name===De.current.name&&Ne(Object(o.a)(Object(o.a)({},De.current),{},{score:e.updatedScore})),ve(Ae(e)),Be(e),be(Object(i.a)(new Set([].concat(Object(i.a)(je.current),Object(i.a)($.current))))),Te(e.bag),Fe(e.bag.length),Z([]),(6===e.turnSkips||e.bagIsEmpty&&e.rackIsEmpty)&&r(!0)}))}),[]);var Ge=function(e){e===De.current.name?Ne(Object(o.a)(Object(o.a)({},De.current),{},{turn:!0})):Ne(Object(o.a)(Object(o.a)({},De.current),{},{turn:!1}))},Ae=function(e){var t,r=[],n=Object(l.a)(ge.current);try{for(n.s();!(t=n.n()).done;){var c=t.value;c.name===e.playerToPlay.name?c.turn=!0:c.turn=!1,c.name===e.name&&(c.score=e.updatedScore),r.push(c)}}catch(a){n.e(a)}finally{n.f()}return r},Fe=function(e){var t;e<=7&&(t=0===e?"No pieces are left in the bag":1===e?"Only one piece is left in the bag":"Only ".concat(e," pieces are left in the bag."),setTimeout((function(){ze([{message:"Heads up! ".concat(t),type:"warning"}])}),7e3))},Be=function(e){var t,r="It's ".concat(e.playerToPlay.name===De.current.name?"your":"".concat(e.playerToPlay.name,"'s"));e.isTurnSkipped?(t=e.name===De.current.name?"You skipped your turn.":"Turn skipped by ".concat(e.name,"."),t+=" ".concat(r," turn to play")):e.isTurnSwapped?(t=e.name===De.current.name?"You swapped pieces for your turn.":"".concat(e.name," swapped pieces for turn."),t+=" ".concat(r," turn to play")):(t=e.name===De.current.name?"You played '".concat(e.word,"' worth ").concat(e.score," points."):"".concat(e.name," played '").concat(e.word,"' worth ").concat(e.score," points."),t+=" ".concat(r," turn to play")),ze([{type:"success",message:t}])},Je=Object(N.jsxs)("div",{className:"flex space-x-10",children:[Object(N.jsx)(q,{}),Object(N.jsx)("div",{className:"px-10"}),Object(N.jsx)(ke,{})]});return Object(N.jsx)("div",{className:"container mx-auto p-10 flex h-screen justify-center",children:/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)?Object(N.jsxs)("div",{className:"flex flex-col justify-center items-center",children:[Object(N.jsx)("div",{children:Object(N.jsx)(qe.a,{size:48})}),Object(N.jsx)("div",{className:"pt-2",children:"Mobile Device Detected. Sorry, this game cannot be played on a mobile device. Kindly use a laptop/desktop."})]}):Object(N.jsx)(g.Provider,{value:{notifications:Le,setNotifications:ze},children:Object(N.jsx)(p.Provider,{value:We,children:Object(N.jsxs)(v.Provider,{value:{validDrag:he,setValidDrag:xe},children:[b.current||k.current?Je:Object(N.jsx)(Ye,{}),Object(N.jsx)(He,{})]})})})})};var Me=function(){return Object(N.jsx)(O.Provider,{value:h,children:Object(N.jsx)("div",{className:"App",children:Object(N.jsx)(Je,{})})})},Ue=function(e){e&&e instanceof Function&&r.e(3).then(r.bind(null,150)).then((function(t){var r=t.getCLS,n=t.getFID,c=t.getFCP,a=t.getLCP,s=t.getTTFB;r(e),n(e),c(e),a(e),s(e)}))};s.a.render(Object(N.jsx)(c.a.StrictMode,{children:Object(N.jsx)(Me,{})}),document.getElementById("root")),Ue()},60:function(e,t,r){},61:function(e,t,r){}},[[116,1,2]]]); 2 | //# sourceMappingURL=main.c7fc86e0.chunk.js.map -------------------------------------------------------------------------------- /server/build/static/js/runtime-main.cab1df26.js: -------------------------------------------------------------------------------- 1 | !function(e){function t(t){for(var n,i,a=t[0],c=t[1],l=t[2],s=0,p=[];s None: 67 | self.number += 1 68 | 69 | def decrement(self) -> None: 70 | if self.number > 0: 71 | self.number -= 1 72 | 73 | def serialize(self) -> dict: 74 | return { 75 | "id": self._id, 76 | "piece": self.piece, 77 | "weight": self.weight, 78 | "number": self.number 79 | } 80 | 81 | def __repr__(self) -> str: 82 | return f"ScrabblePiece " 83 | 84 | 85 | class ScrabbleBag: 86 | 87 | def __init__(self) -> None: 88 | self.pieces = {piece: ScrabblePiece(index, piece, weight, number) 89 | for index, (piece, (weight, number)) in enumerate(scrabble_pieces.items())} 90 | 91 | def __len__(self) -> int: 92 | return sum([i.number for i in self.pieces.values()]) 93 | 94 | def _get_remaining_pieces(self) -> list: 95 | return [i for i in self.pieces.values() if i.number > 0] 96 | 97 | def get_piece_by_id(self, pid: str) -> list: 98 | return list(filter(lambda piece: piece.id == pid, self.pieces.values()))[0] 99 | 100 | def get_pieces(self, amount) -> list: 101 | """ 102 | Gets pieces from the bag 103 | and updates the bag, of course 104 | """ 105 | 106 | # Storage for the requested new pieces 107 | new_pieces = [] 108 | 109 | # Get the number of the remaining pieces 110 | pieces_left = self._get_remaining_pieces() 111 | num_pieces_left = sum([i.number for i in pieces_left]) 112 | 113 | # If the requested amount is less than the number of 114 | # pieces in the bag, re-assign the amount to the remainder 115 | amount = num_pieces_left if num_pieces_left <= amount else amount 116 | 117 | # Fill up the requested new pieces 118 | while len(new_pieces) != amount: 119 | # Get a random piece 120 | piece = choice(pieces_left) 121 | 122 | if piece.number > 0: 123 | piece.decrement() 124 | new_pieces.append(piece.serialize()) 125 | 126 | return new_pieces 127 | 128 | def serialize(self) -> dict: 129 | return { 130 | "length": len(self), 131 | "pieces": {name: piece.serialize() for name, piece in sorted(self.pieces.items())} 132 | } 133 | 134 | def __repr__(self) -> str: 135 | return f"ScrabbleBag " 136 | 137 | 138 | class Player: 139 | def __init__(self, name, room_id, score, turn, is_host, is_speaking): 140 | self.name = name 141 | self.turn = turn 142 | self.score = score 143 | self.room_id = room_id 144 | self.is_host = is_host 145 | self.has_score_set = False 146 | self.is_speaking = is_speaking 147 | 148 | self._rack = [] 149 | self._is_active = False 150 | 151 | @property 152 | def is_active(self) -> bool: 153 | return self._is_active 154 | 155 | def activate(self) -> None: 156 | self._is_active = True 157 | 158 | def deactivate(self) -> None: 159 | self._is_active = False 160 | 161 | def get_rack(self) -> None: 162 | return self._rack 163 | 164 | def set_rack(self, rack) -> None: 165 | self._rack = rack 166 | 167 | def set_turn(self, turn) -> None: 168 | self.turn = turn 169 | 170 | def get_turn(self) -> bool: 171 | return self.turn 172 | 173 | def get_score(self) -> int: 174 | return self.score 175 | 176 | def set_score(self, score): 177 | self.score = score 178 | self.has_score_set = True 179 | 180 | def update_score(self, score): 181 | self.score += score 182 | 183 | def serialize(self) -> dict: 184 | return { 185 | "name": self.name, 186 | "turn": self.turn, 187 | "roomID": self.room_id, 188 | "isHost": self.is_host, 189 | "score": self.get_score(), 190 | "isSpeaking": self.is_speaking, 191 | } 192 | 193 | def __repr__(self) -> str: 194 | return f"Player " 195 | 196 | 197 | class GameRoom: 198 | def __init__(self, id, limit=4, time_to_play=None, audio_is_enabled=False): 199 | 200 | self.id = id 201 | self.limit = limit 202 | 203 | self._logs = [] 204 | self._board = [] 205 | self._players = {} 206 | self._turn_skips = 0 207 | self._is_joinable = True 208 | self._bag = ScrabbleBag() 209 | self._player_turns = None 210 | self._time_to_play = time_to_play 211 | self._audio_is_enabled = audio_is_enabled 212 | 213 | @property 214 | def bag(self): 215 | return self._bag 216 | 217 | @property 218 | def time_to_play(self): 219 | return self._time_to_play 220 | 221 | @property 222 | def audio_is_enabled(self): 223 | return self._audio_is_enabled 224 | 225 | def get_turn_skips(self) -> int: 226 | return self._turn_skips 227 | 228 | def log(self, data) -> None: 229 | self._logs.append(data) 230 | 231 | def get_logs(self) -> list: 232 | return self._logs 233 | 234 | def update_board(self, board) -> None: 235 | self._board = board 236 | 237 | def reset_turn_skips(self) -> None: 238 | self._turn_skips = 0 239 | 240 | def increment_turn_skips(self) -> None: 241 | self._turn_skips += 1 242 | 243 | def get_board(self) -> list: 244 | return self._board 245 | 246 | def get_bag(self) -> dict: 247 | return self._bag.serialize() 248 | 249 | def add_player(self, player: Player): 250 | self._players[player.name] = player 251 | 252 | def remove_player(self, player: Player): 253 | self._players.pop(player.name) 254 | 255 | def get_player(self, name) -> Player: 256 | return self._players.get(name) 257 | 258 | def get_player_to_play(self) -> dict: 259 | return next(self._player_turns).serialize() 260 | 261 | def get_host(self) -> Player: 262 | return [i.is_host for i in self._players.values()][0] 263 | 264 | def get_all_players(self): 265 | return self._players.values() 266 | 267 | def has_game_ended(self) -> bool: 268 | return all([p.has_score_set for p in self._players.values()]) 269 | 270 | def is_joinable(self) -> bool: 271 | return len(self._players) != self.limit and self._is_joinable 272 | 273 | def get_player_turns(self) -> list: 274 | return [i.name for i in seeded_shuffle(self._players.values())] 275 | 276 | def serialize(self) -> dict: 277 | return dict(id=self.id, limit=self.limit, joinable=self.is_joinable()) 278 | 279 | def get_connected_players(self): 280 | return [i.serialize() for i in self._players.values() if i.is_active] 281 | 282 | def close(self): 283 | if self._is_joinable: 284 | self._is_joinable = False 285 | 286 | # Ready for round-robining 287 | players = seeded_shuffle(self._players.values()) 288 | 289 | # Turn to round-robin 290 | self._player_turns = cycle(players) 291 | self.get_player_to_play() # Start 292 | 293 | def __repr__(self) -> str: 294 | return f"GameRoom <{self.id}>" 295 | 296 | 297 | class GameRooms(dict): 298 | def __init__(self, *args, **kwargs): 299 | self.update(*args, **kwargs) 300 | 301 | def get_room(self, room_id: str) -> GameRoom: 302 | return self.get(room_id) 303 | 304 | def add_room(self, room: GameRoom): 305 | return self.__setitem__(room.id, room) 306 | 307 | def __repr__(self) -> str: 308 | room_ids = [i for i in self.keys()] 309 | return f"GameRooms <{room_ids}>" 310 | -------------------------------------------------------------------------------- /server/main/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint 2 | 3 | main_bp = Blueprint('main', __name__) 4 | 5 | from . import routes -------------------------------------------------------------------------------- /server/main/routes.py: -------------------------------------------------------------------------------- 1 | 2 | from server import CLIENT_TOKEN 3 | 4 | from . import main_bp as main 5 | from flask import request, render_template 6 | 7 | # Entry point to serve client 8 | @main.route('/', defaults={'path': ''}) 9 | @main.route('/') 10 | def serve_client(path): 11 | host_url = request.host_url[:-1] if request.host_url.endswith('/') else request.host_url 12 | return render_template('index.html', 13 | ip=host_url, 14 | b2ctk=CLIENT_TOKEN) -------------------------------------------------------------------------------- /server/models.py: -------------------------------------------------------------------------------- 1 | 2 | from server import db 3 | 4 | class Word(db.Model): 5 | """Schema definition for Scrabble words""" 6 | 7 | __tablename__ = 'word' 8 | 9 | id = db.Column(db.Integer, primary_key=True) 10 | word = db.Column(db.String(80), unique=True, nullable=False) 11 | 12 | def __repr__(self): 13 | return 'Scrabble Words Database (2018)' -------------------------------------------------------------------------------- /server/socketio.py: -------------------------------------------------------------------------------- 1 | 2 | from server import sio 3 | from datetime import datetime 4 | 5 | from .data import GameRoom, GameRooms, Player 6 | from flask_socketio import join_room, leave_room, emit 7 | 8 | 9 | game_rooms = GameRooms() 10 | 11 | 12 | @sio.on('join') 13 | def on_join(data): 14 | """Event handler for room joins""" 15 | 16 | limit = data.get('limit') 17 | player = data.get('player') 18 | room_id = data.get('roomID') 19 | time_to_play = data.get('timeToPlay') 20 | enable_audio = data.get('enableAudio') 21 | 22 | # Create new player from player data 23 | new_player = Player(player['name'], 24 | player['roomID'], 25 | player['score'], 26 | player['turn'], 27 | player['isHost'], 28 | player['isSpeaking']) 29 | 30 | # Get exisitng game room, if any 31 | existing_game_room = game_rooms.get_room(room_id) 32 | 33 | if existing_game_room: 34 | # Join room if the game hasn't started and the room is not yet full 35 | if existing_game_room.is_joinable(): 36 | existing_game_room.add_player(new_player) 37 | join_room(room_id) 38 | 39 | else: # If room_id doesn't exist, then create new with required params 40 | new_game_room = GameRoom(room_id, limit, time_to_play, enable_audio) 41 | game_rooms.add_room(new_game_room) 42 | existing_game_room = new_game_room 43 | existing_game_room.add_player(new_player) 44 | join_room(room_id) 45 | 46 | new_player.activate() # Make player object usable 47 | 48 | # Tell client of connected players, so they can know 49 | data['mode'] = "create" 50 | data['timeToPlay'] = existing_game_room.time_to_play 51 | data['enableAudio'] = existing_game_room.audio_is_enabled 52 | data['connectedPlayers'] = existing_game_room.get_connected_players() 53 | 54 | # Not Implemented 55 | # If it's not a reconnection event 56 | # if not data.get('isReconnection'): 57 | emit('joinedRoom', data, room=room_id) 58 | 59 | 60 | @sio.on('resume') 61 | def on_resume(data): 62 | """Event handler for room joins""" 63 | 64 | room_id = data.get('roomID') 65 | game_room = game_rooms.get_room(room_id) 66 | player_name = data.get('player').get('name') 67 | 68 | if game_room: 69 | join_room(room_id) 70 | 71 | player = game_room.get_player(player_name) 72 | player.activate() 73 | 74 | # Tell client of connected players, so they can know 75 | data['mode'] = "resume" 76 | data['timeToPlay'] = game_room.time_to_play 77 | data['enableAudio'] = game_room.audio_is_enabled 78 | data['connectedPlayers'] = game_room.get_connected_players() 79 | 80 | emit('joinedRoom', data, room=room_id) 81 | 82 | 83 | @sio.on('leave') 84 | def on_leave(data): 85 | """Event handler for room exits""" 86 | 87 | room_id = data['roomID'] # Get room 88 | game_room = game_rooms.get_room(room_id) 89 | 90 | if game_room: 91 | # Will use this if the option to kill sessions 92 | # is enabled. It currently isn't implemented 93 | # game_rooms.pop(room_id) # Remove room 94 | 95 | leave_room(room_id) # Remove sio room 96 | 97 | # Make all players inactive (so game can be resumed) 98 | for player in game_room.get_all_players(): 99 | player.deactivate() 100 | 101 | emit('leftRoom', data, room=room_id) # Announce 102 | 103 | 104 | @sio.on('gameCreateEvent') 105 | def on_create(data): 106 | """ 107 | Event handler for game official creation 108 | """ 109 | room_id = data.get('roomID') 110 | game_room = game_rooms.get_room(room_id) 111 | 112 | # Close the room 113 | game_room.close() # Game has started 114 | 115 | # Tell client of all connected players 116 | data['allPlayers'] = game_room.get_connected_players() 117 | 118 | emit('gameCreate', data, room=room_id) 119 | 120 | 121 | @sio.on('gameResumeEvent') 122 | def on_resume(data): 123 | """ 124 | Event handler for game official resumption 125 | """ 126 | room_id = data.get('roomID') 127 | game_room = game_rooms.get_room(room_id) 128 | 129 | # Tell client of all connected players 130 | data['rack'] = { p.name:p.get_rack() for p in game_room.get_all_players() } 131 | data['allPlayers'] = game_room.get_connected_players() 132 | data['usedTiles'] = game_room.get_board() 133 | 134 | emit('gameResume', data, room=room_id) 135 | 136 | 137 | @sio.on('inPlayEvent') 138 | def in_play_event(data): 139 | """ 140 | Event handler for in-play happenings (board-drags, etc) 141 | """ 142 | emit('inPlay', data, room=data.get('roomID')) 143 | 144 | 145 | @sio.on('radioEvent') 146 | def radio(data): 147 | """ 148 | Event handler for voice chatting 149 | """ 150 | emit('audioTransmission', data, room=data.get('roomID')) 151 | 152 | 153 | @sio.on('recallEvent') 154 | def recall_event(data): 155 | """ 156 | Event handler for when pieces are recalled on the board 157 | """ 158 | emit('recallPieces', data, room=data.get('roomID')) 159 | 160 | 161 | @sio.on('playEvent') 162 | def play_event(data): 163 | """ 164 | Event handler for an actual valid play 165 | """ 166 | 167 | name = data.get('name') 168 | score = data.get('score') 169 | room_id = data.get('roomID') 170 | played_word = data.get('word') 171 | turn_skipped = data.get('isTurnSkipped') 172 | turn_swapped = data.get('isTurnSwapped') 173 | game_room = game_rooms.get_room(room_id) 174 | 175 | # Get bag and player in room 176 | room_bag = game_room.bag 177 | player = game_room.get_player(name) 178 | 179 | # Update payload 180 | # If turn wasn't skipped nor pieces swapped 181 | if not turn_swapped and not turn_skipped: 182 | player.update_score(score) # Update score 183 | log = f"{name} played {played_word} worth {score} points" 184 | 185 | # Track turn skips 186 | if turn_skipped: 187 | game_room.increment_turn_skips() 188 | log = f"{name} skipped turn" 189 | 190 | else: 191 | game_room.reset_turn_skips() 192 | 193 | if turn_swapped: 194 | pieces_swapped = data.get('piecesSwapped') 195 | for piece_data in pieces_swapped: 196 | 197 | # Cut off client index to get id 198 | piece_id = piece_data.get('id')[2:] 199 | 200 | # Get scrabble piece and increment number 201 | bag_piece = room_bag.get_piece_by_id(piece_id) 202 | bag_piece.increment() 203 | 204 | log = f"{name} swapped {len(pieces_swapped)} pieces" 205 | 206 | data['bag'] = game_room.get_bag() 207 | data['updatedScore'] = player.get_score() 208 | data['turnSkips'] = game_room.get_turn_skips() 209 | data['playerToPlay'] = game_room.get_player_to_play() 210 | 211 | # Log event to game room 212 | game_room.log({"time": datetime.now().strftime("%H:%M:%S"), "event": log}) 213 | 214 | emit('validPlay', data, room=room_id) 215 | 216 | 217 | @sio.on('drawEvent') 218 | def draw_event(data): 219 | """ 220 | Event handler for draw play 221 | """ 222 | room_id = data.get('roomID') 223 | game_room = game_rooms.get(room_id) 224 | 225 | # Get player turns from room 226 | players = game_room.get_player_turns() 227 | 228 | data['players'] = players 229 | data['bag'] = game_room.get_bag() 230 | 231 | emit('drawDone', data, room=room_id) 232 | 233 | 234 | @sio.on('resumeEvent') 235 | def draw_event(data): 236 | """ 237 | Event handler for draw play 238 | """ 239 | room_id = data.get('roomID') 240 | game_room = game_rooms.get(room_id) 241 | 242 | # Get the player whose turn it was to play 243 | player_to_play = list(filter(lambda p: p.get_turn(), game_room.get_all_players()))[0] 244 | data['playerToPlay'] = player_to_play.name 245 | 246 | data['bag'] = game_room.get_bag() 247 | 248 | emit('resumeDone', data, room=room_id) -------------------------------------------------------------------------------- /server/templates/index.html: -------------------------------------------------------------------------------- 1 | Scrabble
-------------------------------------------------------------------------------- /server/utils.py: -------------------------------------------------------------------------------- 1 | 2 | import socket 3 | import string 4 | import random 5 | 6 | 7 | def get_local_ip_address(): 8 | """Returns the private IP address of the system""" 9 | 10 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 11 | try: 12 | s.connect(("8.8.8.8", 80)) 13 | except OSError: 14 | return "127.0.0.1" # Localhost 15 | 16 | return s.getsockname()[0] 17 | 18 | 19 | def token_generator(size=32, chars=string.ascii_uppercase + string.digits): 20 | """Generates Random tokens""" 21 | return ''.join(random.SystemRandom().choice(chars) for _ in range(size)) 22 | -------------------------------------------------------------------------------- /wsgi.py: -------------------------------------------------------------------------------- 1 | from config import Dev, Prod 2 | from server import create_app, sio 3 | from server.utils import get_local_ip_address 4 | 5 | 6 | PORT = 5005 7 | IP_ADDRESS = get_local_ip_address() 8 | 9 | env_type = Dev.ENV 10 | app_env = Dev # Default 11 | 12 | if env_type == 'PROD': 13 | app_env = Prod 14 | 15 | app = create_app(app_env) 16 | 17 | if __name__ == '__main__': 18 | print(f"Running in {env_type} environment at http://{IP_ADDRESS}:{PORT}.") 19 | sio.run(app, host=IP_ADDRESS, port=PORT) 20 | --------------------------------------------------------------------------------