Compare commits

..

250 commits
main ... dev

Author SHA1 Message Date
Oliver Akins
30042a1152
Add common module stuff 2022-12-25 17:41:07 -06:00
Oliver Akins
ee9d9ca6ed
Add python script to generate a hex SVG points 2022-12-25 17:41:03 -06:00
Oliver Akins
4991f0eac8
backup 2022-12-25 2022-12-25 17:40:13 -06:00
Oliver Akins
720170f867
Add FuelCard to the imports 2022-07-30 17:46:11 -06:00
Oliver Akins
0045cd4184
Add a state that allows changing the controls bar 2022-07-30 17:45:38 -06:00
Oliver Akins
4c318a9aeb
Add the player order and e-stop panels 2022-07-30 17:44:58 -06:00
Oliver Akins
4601ed480c
Allow half-ing the card for the order-preview 2022-07-30 17:44:17 -06:00
Oliver Akins
e4dfc8aa3f
Add the fuel card to the PlayerData 2022-07-30 17:43:49 -06:00
Oliver Akins
69aa91e632
Wrap the space shuttle icon in a div 2022-07-30 17:43:33 -06:00
Oliver Akins
60215ec328
Allow rendering a notice as well as a preview board 2022-07-25 23:56:03 -06:00
Oliver Akins
296aa7f298
Modify the board to accept the position array as a prop instead of accessing the store directly. 2022-07-25 23:55:30 -06:00
Oliver Akins
dd3662560d
Bubble or stop hover events depending on if the card button is visible. 2022-07-25 23:54:59 -06:00
Oliver Akins
b44417084c
Emit events when the user hovers over the card. 2022-07-25 23:54:11 -06:00
Oliver Akins
8ffffd8834
Add a writable store the player's hand 2022-07-25 21:38:42 -06:00
Oliver Akins
bd70c2caac
Add the player hand to the movement view 2022-07-25 21:38:23 -06:00
Oliver Akins
a36d85ae74
Correct the code to work with the new data structure 2022-07-25 21:38:05 -06:00
Oliver Akins
6e51bf7656
Add a component for a player's hand 2022-07-25 21:37:31 -06:00
Oliver Akins
a6788232e7
Make a card component 2022-07-25 21:37:02 -06:00
Oliver Akins
71fed3f107
Moar particles 2022-07-25 21:36:42 -06:00
Oliver Akins
23a80ccc28
Add a circle SVG for a11y reasons 2022-07-25 21:36:32 -06:00
Oliver Akins
ab3e4eb0ff
Fix the gosh darned particles 2022-07-23 19:29:26 -06:00
Oliver Akins
33d304ef5b
Add a ID property to PlayerData 2022-07-22 19:45:18 -06:00
Oliver Akins
5b2072b473
Add the ability to change the buttons text colour. 2022-07-22 19:45:04 -06:00
Oliver Akins
1b011d4a3b
Tweak animation lengths slightly 2022-07-22 19:44:43 -06:00
Oliver Akins
8d5a02f9a2
Correct the conditionals to display the ships 2022-07-22 18:47:39 -06:00
Oliver Akins
2eba630e39
Add a default value to the players store 2022-07-20 22:56:27 -06:00
Oliver Akins
b26911184f
Properly subscribe to the board store, and index the players data for proper information 2022-07-20 22:24:04 -06:00
Oliver Akins
885bebee71
Add super basic setup script 2022-07-20 22:14:56 -06:00
Oliver Akins
5c47a967dd
Have make build the module once before watching it 2022-07-20 22:13:49 -06:00
Oliver Akins
cfa5d7aaad
Remove p from the production run make recipe 2022-07-20 22:13:05 -06:00
Oliver Akins
e757168351
Replace nether portal warp gate with custom icon 2022-07-20 22:12:42 -06:00
Oliver Akins
8684f9d6f6
Create custom warpgate container for Cyber'ss icon 2022-07-20 22:12:11 -06:00
Oliver Akins
0a5f83704d
Add more exclude directories 2022-07-20 21:25:10 -06:00
Oliver Akins
f179188618
Update packages because why not? 2022-07-20 21:24:48 -06:00
Oliver Akins
5f0f101361
Update the comment for the ships data 2022-07-20 21:24:09 -06:00
Oliver Akins
b883c00247
Remove message properties that were causing problems 2022-07-20 21:23:38 -06:00
Oliver Akins
13711e5986
Remove use of the log since it doesn't exist yet. 2022-07-20 21:21:54 -06:00
Oliver Akins
25aeb8bc9e
Remove the rotation from the sailboat to prevent weird pointing direction 2022-07-20 21:16:03 -06:00
Oliver Akins
a8d9a363cb
Update the type of the players store 2022-07-20 21:14:02 -06:00
Oliver Akins
9508a7e66a
Move the board into a writable store 2022-07-20 21:13:41 -06:00
Oliver Akins
4ca083e570
Make the code only compute the proper main area index one time 2022-07-20 21:12:20 -06:00
Oliver Akins
d4bc98ff77
Add the class to style the side boxes with 2022-07-20 21:11:46 -06:00
Oliver Akins
12531fce67
Update the CSS 2022-07-20 21:09:17 -06:00
Oliver Akins
628ce29dad
Upgrade packages so I can use @const 2022-07-20 21:07:40 -06:00
Oliver Akins
599a4140a3
Add a basic movement phase screen with a board. 2022-07-19 22:54:49 -06:00
Oliver Akins
378e4315ba
Tweak the hexagon component to use the SVG background image 2022-07-19 22:54:13 -06:00
Oliver Akins
6f7477eecf
Move the onMount function call 2022-07-19 22:53:49 -06:00
Oliver Akins
4ab85924a9
Add a custom hexagon SVG 2022-07-19 22:53:20 -06:00
Oliver Akins
57135c4fad
Optimize the SVGs 2022-07-19 22:53:06 -06:00
Oliver Akins
dc510e00b0
Provide a fix for the failing test-case 2022-06-29 22:22:01 -06:00
Oliver Akins
32119625b7
Add test cases for when the closest ships are on the edge 2022-06-29 22:21:42 -06:00
Oliver Akins
08df4187fb
Remove commented out definition that isn't used 2022-06-29 00:01:24 -06:00
Oliver Akins
5fbbf09144
Add a GamePiece type 2022-06-29 00:00:21 -06:00
Oliver Akins
be50ff62d9
Fix the problems the tests found 2022-06-28 23:59:56 -06:00
Oliver Akins
af5e74d4af
Add tests for the tractorBeam function 2022-06-28 23:59:40 -06:00
Oliver Akins
a7344e36e1
Begin implementing the functions that actually modify the game board 2022-06-28 00:44:44 -06:00
Oliver Akins
54ffc0a0f4
Add a Board type for easier type consistency 2022-06-28 00:43:52 -06:00
Oliver Akins
a4aa70f992
Add tests for the Player object 2022-06-23 14:05:34 -06:00
Oliver Akins
abba594522
Add a host flag 2022-06-23 13:46:45 -06:00
Oliver Akins
1f6c72ee8a
Add tests for the Deck object 2022-06-23 11:41:01 -06:00
Oliver Akins
040a7ae804
Add a bunch of dependencies for data validation and testing 2022-06-23 11:40:46 -06:00
Oliver Akins
448b089bf9
Create a makefile for ease of use 2022-06-23 11:40:25 -06:00
Oliver Akins
be25b95789
Add data validation to the config 2022-06-23 11:40:09 -06:00
Oliver Akins
4506b97101
Prevent cards that aren't in the unknown from being discarded 2022-06-23 11:39:39 -06:00
Oliver Akins
1aa3e2b8ac
Strip Internal API definitions 2022-06-23 11:39:08 -06:00
Oliver Akins
449a4b25d9
Don't compile the .spec.ts files 2022-06-23 11:38:47 -06:00
Oliver Akins
726067b1e5
Add the test and watch* rules to the PHONY list 2022-06-22 00:40:10 -06:00
Oliver Akins
28a5c81241
Adjust the echo used in the test command 2022-06-22 00:39:27 -06:00
Oliver Akins
699c07f59e
Tweak it from == 1 to <= 1 just in case 2022-06-22 00:38:23 -06:00
Oliver Akins
7b97beaeb1
Rename the function in the test description 2022-06-22 00:38:08 -06:00
Oliver Akins
670729c892
Apply a fix to the determineDirection function that makes it conform to the rule specification 2022-06-22 00:35:03 -06:00
Oliver Akins
3b78f224df
Add tests for the movementDirection function 2022-06-22 00:34:13 -06:00
Oliver Akins
4feafba99b
Add the stripInternal option 2022-06-19 12:58:27 -06:00
Oliver Akins
7af5cf0ce8
Fix bug with the countShips function 2022-06-19 12:42:24 -06:00
Oliver Akins
0f76e83fac
Add test make recipe 2022-06-19 12:41:57 -06:00
Oliver Akins
1167c10707
Allow TS to import countShips internally 2022-06-19 12:41:37 -06:00
Oliver Akins
762e2bdb1b
Add a tests for countShips 2022-06-19 12:41:02 -06:00
Oliver Akins
18f58a6221
Rename the package template to the normal package file and install the required dev deps for testing purposes 2022-06-19 12:40:29 -06:00
Oliver Akins
ed715b14e4
Make it so that the game link gets added to the browser URL when joining a game 2022-03-15 23:25:56 -06:00
Oliver Akins
7d39b41b6d
Add functionality to the Copy Game Link button 2022-03-15 23:25:27 -06:00
Oliver Akins
05a5bfc3af
Don't echo the mkdir command 2022-03-15 23:10:00 -06:00
Oliver Akins
a7a7560201
Add watch rules to the makefile 2022-03-15 23:09:39 -06:00
Oliver Akins
b0e2a9e6d4
Instead of using onDestroy, return a function from onMount 2022-03-15 22:38:09 -06:00
Oliver Akins
018ae4fe06
Add a no-op button to copy the game link to the user's clipboard 2022-03-15 01:41:05 -06:00
Oliver Akins
3f96b97194
Add some sanity comments and a full lobby check 2022-03-15 01:37:59 -06:00
Oliver Akins
c340755ba2
Make the JoinLobby modal nicer 2022-03-15 01:34:09 -06:00
Oliver Akins
bcc0664342
Move lobby creation/hosting into it's own modal 2022-03-15 01:07:02 -06:00
Oliver Akins
2dd456897f
Move the error style to be global 2022-03-15 00:21:51 -06:00
Oliver Akins
31cd9a1558
Default the store values so we don't get undefined appearing in the text entries 2022-03-15 00:04:43 -06:00
Oliver Akins
ffc759e267
Tweak import statement 2022-03-13 20:14:31 -06:00
Oliver Akins
9708be3f1e
Tweak imports 2022-03-13 20:14:07 -06:00
Oliver Akins
1b9f3d780e
Add code to handle the lobby info responses 2022-03-13 18:32:15 -06:00
Oliver Akins
2c4c7c38b0
Add code to handle the join lobby response 2022-03-13 18:31:58 -06:00
Oliver Akins
be3a1c97c9
Allow possibly not finding the game object in the DB 2022-03-13 18:31:44 -06:00
Oliver Akins
c5eb73ef4b
Add the game's status to the object 2022-03-13 18:31:25 -06:00
Oliver Akins
4c9d0b272c
Begin listening for lobby join events 2022-03-13 18:31:14 -06:00
Oliver Akins
88b07fdf1d
Implement a primitive join lobby method 2022-03-13 18:31:02 -06:00
Oliver Akins
5da30d572f
Add a Forbidden status 2022-03-13 18:30:46 -06:00
Oliver Akins
af37e3b5f5
Make the colour appear correctly even when it isn't our slot 2022-03-13 18:30:33 -06:00
Oliver Akins
4821f7a971
Indicate the user as the host when they create the lobby 2022-03-13 01:58:03 -06:00
Oliver Akins
c68849fd1a
Add docstrings to the IGameOption type 2022-03-13 01:48:23 -06:00
Oliver Akins
8eb4a3c48f
Add descriptions to the option objects 2022-03-13 01:45:32 -06:00
Oliver Akins
ee40351d1b
Update option info modal to only display non-hidden options 2022-03-13 01:45:06 -06:00
Oliver Akins
6e9ff8b751
Add the list of game options to the common module and make the multiplayer lobby begin listening to lobby info events + some style tweaks. 2022-03-13 01:33:38 -06:00
Oliver Akins
4b12a5a1a0
Adjust stores available.
- Remove default player objects
  - Remove Player interface
  - Make players store writable
  - Use PlayerData type from the common module
  - Add a gameCode store
2022-03-12 23:16:49 -06:00
Oliver Akins
e3e5dbb7bf
Begin adding socket communication for lobby creating/joining 2022-03-12 23:15:19 -06:00
Oliver Akins
dbce447297
Add the host boolean to the playerdata type 2022-03-12 23:14:57 -06:00
Oliver Akins
dd5a1e8cb9
Make the lobby more compatible with the colour/ship objects 2022-03-12 23:14:42 -06:00
Oliver Akins
6f38c7e2a3
Add documentation for the game.state event 2022-03-12 22:15:15 -06:00
Oliver Akins
df6b8a0875
Add code bodies to the hostGame and joinGame methods 2022-03-12 22:14:37 -06:00
Oliver Akins
826b118548
Tweak the order of events that's happening in the main site processor 2022-03-12 22:14:04 -06:00
Oliver Akins
794f0a62c6
Update the gitignore so I don't have to ignore the web-vue folder manually 2022-03-12 22:12:21 -06:00
Oliver Akins
2ee16b2b8a
Make it so that the events actually register properly 2022-03-12 22:10:54 -06:00
Oliver Akins
1e65515009
Add a ping event for development purposes 2022-03-12 22:09:03 -06:00
Oliver Akins
d6a4f5b0f3
Tweak the docstring 2022-03-12 22:07:07 -06:00
Oliver Akins
b3d157d1e6
Add a method to get the JSON-compatible representation of the object 2022-03-12 22:06:47 -06:00
Oliver Akins
d9a96f2061
Load the ship style from data into the actual class 2022-03-12 22:06:27 -06:00
Oliver Akins
5a9dfbb864
Return the player data in the object 2022-03-12 22:04:19 -06:00
Oliver Akins
5fb5f5d457
Default the user to not being the host 2022-03-08 13:28:28 -06:00
Oliver Akins
330125efbe
Add a check to make sure that the player's socket also isn't undefined 2022-03-08 13:26:44 -06:00
Oliver Akins
f4396a1c42
Add a cleanup method stub on the game manager 2022-03-08 13:24:49 -06:00
Oliver Akins
96d6e0b503
Finish implementing the create lobby event 2022-03-08 13:24:37 -06:00
Oliver Akins
1b01ef971d
Add documentation for the generic error event 2022-03-08 13:23:59 -06:00
Oliver Akins
02dece1af0
Use the data constants from the common module 2022-03-07 23:42:15 -06:00
Oliver Akins
a6935d9d96
Begin working on the lobby creation event 2022-03-07 23:41:48 -06:00
Oliver Akins
3dfb8b877f
Make the log object readonly 2022-03-07 23:40:01 -06:00
Oliver Akins
eab458bb6b
Add proper game code generation 2022-03-07 23:39:22 -06:00
Oliver Akins
6088128b7d
Load the cards based on the config 2022-03-07 23:37:20 -06:00
Oliver Akins
6bbd8d68c0
Add a game manager to the server 2022-03-07 23:36:55 -06:00
Oliver Akins
0ddf5d8a14
Add a game count to the server info response 2022-03-07 23:36:41 -06:00
Oliver Akins
f9f14422d8
Fix the exports in the common module 2022-03-07 23:36:09 -06:00
Oliver Akins
d1e37d8fec
Add ship design constants in the shared module 2022-03-07 23:35:56 -06:00
Oliver Akins
ad9ad389b6
Add the module-alias package to the dependencies 2022-03-07 22:23:28 -06:00
Oliver Akins
e434aea6ac
Add an import alias for shorter filepath imports 2022-03-07 22:21:30 -06:00
Oliver Akins
1f86b68e4c
Tweak log level 2022-03-07 22:16:36 -06:00
Oliver Akins
1b1812f9f5
Use a config property for CORS headers 2022-03-07 22:16:29 -06:00
Oliver Akins
9ce0db9327
Have the server info use the version constant 2022-03-07 22:15:45 -06:00
Oliver Akins
cbf7a98bb3
Add more properties for the config 2022-03-07 22:15:21 -06:00
Oliver Akins
ef9e47e401
Change where the common module is linked from 2022-03-07 18:02:54 -06:00
Oliver Akins
c2e66f9f73
Add some scripts 2022-03-07 18:02:37 -06:00
Oliver Akins
8cc748db2b
Add a constant version for use in the server info 2022-03-07 18:02:21 -06:00
Oliver Akins
6c8e323d35
Add ship design properties 2022-03-07 18:01:31 -06:00
Oliver Akins
70bf932964
Allow using sailboat, and remove comment that won't happen.
The comment is being removed because the colours/ships will not be getting fetched from the server because it doesn't make sense for the server to be handling that list since it's entirely client-side.
2022-03-07 18:01:10 -06:00
Oliver Akins
49da524260
Add a sailboat icon 2022-03-07 17:59:13 -06:00
Oliver Akins
0e453e5bee
Tweak raw event list 2022-03-07 17:58:46 -06:00
Oliver Akins
d747f2c005
Add docs for more events 2022-03-07 17:58:33 -06:00
Oliver Akins
1ccc3b6c33
Update types to be in-line with the events 2022-03-07 17:58:23 -06:00
Oliver Akins
c760b0f36d
Add a type that has genericized request data 2022-03-07 17:57:48 -06:00
Oliver Akins
4476f3e18c
Tweak the documentation for events 2022-03-07 00:51:30 -06:00
Oliver Akins
a8311a6834
Update the server info interface to be inline with my new format for interfaces 2022-03-07 00:49:59 -06:00
Oliver Akins
06cfea7564
Remove old event data interfaces 2022-03-07 00:49:16 -06:00
Oliver Akins
281686dfef
Add another possible spaceship idea 2022-03-07 00:48:38 -06:00
Oliver Akins
22ed95983e
Add steps relating to the weirdness that is the common module 2022-03-07 00:01:00 -06:00
Oliver Akins
e253a71b67
Add the cjs and ems module builds to the gitignore 2022-03-07 00:00:33 -06:00
Oliver Akins
263950b5f1
import format to be consistent 2022-03-07 00:00:10 -06:00
Oliver Akins
4e61f0283d
Add a makefile to build both ESM and CJS 2022-03-06 23:06:18 -06:00
Oliver Akins
dc3f1a8b1d
Change the common lib from es2020 to commonjs 2022-03-05 00:35:30 -06:00
Oliver-Akins
baf74969b4 Begin work on the Game class 2022-01-09 01:21:00 -06:00
Oliver-Akins
1edd3a042e Add a bare bones Player class, with socket and name data 2022-01-09 01:20:43 -06:00
Oliver-Akins
8f671d33cf Add a jsdoc to the reset method 2022-01-09 01:20:24 -06:00
Oliver-Akins
34eb19e340 Add the Deck object made for Phantom-Ink-Online 2022-01-09 01:02:57 -06:00
Oliver-Akins
ab4bad308a Adjust the TS config 2022-01-09 01:01:40 -06:00
Oliver-Akins
ca6279d2f6 Move the cards definition 2022-01-09 01:01:29 -06:00
Oliver-Akins
e246b16385 Add uuid as a dependency 2022-01-09 00:31:09 -06:00
Oliver-Akins
15da1158c7 Add a type for the fuel card representation 2022-01-09 00:30:47 -06:00
Oliver-Akins
ec37881de7 Make the board argument an array of any type. 2022-01-09 00:30:29 -06:00
Oliver-Akins
b109c0d082 Add the algorithm for helping with ship movement 2022-01-08 21:34:03 -06:00
Oliver-Akins
5c7afc7668 Make the cards only have 2 types instead of 3 2022-01-08 21:28:34 -06:00
Oliver-Akins
f2a0106c30 Add generic error catching to the event handling 2022-01-08 20:05:41 -07:00
Oliver-Akins
f25d1ef965 Remove unused file 2022-01-06 16:28:48 -07:00
Oliver-Akins
0dc55876cf Add players.update event documentation 2022-01-06 16:26:58 -07:00
Oliver-Akins
e6293ec30f Remove "I" prefix from interface names 2022-01-06 16:26:41 -07:00
Oliver-Akins
05978da3ea Rename game.* events to lobby.* 2022-01-06 16:26:21 -07:00
Oliver-Akins
c5449f1153 Update the type structure, and rename the "game.*" events to "lobby.*" 2022-01-06 16:25:38 -07:00
Oliver-Akins
3b5e643232 Add docs and types for the game.join event 2022-01-05 23:06:22 -07:00
Oliver-Akins
885c9c9e71 Update the request handler to make it conform to the docs 2022-01-05 01:26:48 -07:00
Oliver-Akins
054fa71b18 Add exports for the new types 2022-01-05 01:26:03 -07:00
Oliver-Akins
005b2c929f Make the ServerResponse use the Status enum as the type 2022-01-05 01:25:49 -07:00
Oliver-Akins
c93b903094 Add docs and types for the game join event 2022-01-05 01:25:21 -07:00
Oliver-Akins
81d9f0a5bf Remove the IServerInfoRequest interface since it wasn't in use 2022-01-05 01:24:34 -07:00
Oliver-Akins
10a2d67907 Add a bit more detail about the Broadcasted events 2022-01-04 21:35:04 -07:00
Oliver-Akins
b51f6edcc9 Connect to the websocket server when loading the site 2022-01-04 21:32:37 -07:00
Oliver-Akins
a6b99c2afd Fix the event handler to actually return data 2022-01-04 21:31:10 -07:00
Oliver-Akins
9c5e0ab4d9 Add CORS policy on the websocket server 2022-01-04 21:17:07 -07:00
Oliver-Akins
76442fddce Add socket.io client to the website 2022-01-04 21:16:21 -07:00
Oliver-Akins
e7b16143ec Ignore all TOML files, excluding templates 2022-01-04 20:32:42 -07:00
Oliver-Akins
8156085ef3 Begin on the docs for the code that resides in it's own folder 2022-01-04 20:32:03 -07:00
Oliver-Akins
642e1b2de6 Reorder export to make the heirarchy more clear 2022-01-04 20:31:14 -07:00
Oliver-Akins
442230deb6 Add types for the server.info websocket events 2022-01-04 20:30:46 -07:00
Oliver-Akins
cb09d4a9df Create a super primitive websocket server 2022-01-04 20:30:11 -07:00
Oliver-Akins
7e5a4ffe86 Add config files for pnpm and tsc 2022-01-04 20:29:46 -07:00
Oliver-Akins
231f63c4bf Adjust the focus style for the buttons 2022-01-04 12:31:24 -07:00
Oliver-Akins
e7533f60aa Rename the ColourChoice modal to ShipDesigner 2022-01-04 12:31:01 -07:00
Oliver-Akins
174add9e49 Adjust modal title 2022-01-04 12:29:14 -07:00
Oliver-Akins
eb9cbe48b6 Tweak the styling of the inner components to be max width 2022-01-04 12:29:01 -07:00
Oliver-Akins
81ea8e5f9d Style the select elements 2022-01-04 12:28:39 -07:00
Oliver-Akins
5a8a8425a8 Add some steps on how to start a dev environment 2022-01-03 22:02:37 -07:00
Oliver-Akins
3d3104ef69 Make the common package a peerDependency 2022-01-03 21:41:45 -07:00
Oliver-Akins
20ea2b9fa0 Make the common package actually work with Vite 2022-01-03 21:41:09 -07:00
Oliver-Akins
c5878a8334 Begin implementing game joining 2022-01-03 18:51:11 -07:00
Oliver-Akins
7db70bc4ec Rename the event identifier in the type documentation 2022-01-03 18:50:35 -07:00
Oliver-Akins
988810b9eb Add some more event documentation for the server 2022-01-03 18:47:46 -07:00
Oliver-Akins
9d11b9eb6f Begin implementing an error when the request fails 2022-01-03 18:42:47 -07:00
Oliver-Akins
1e18179cc6 Convert exports to wildcard instead of named 2022-01-03 18:40:48 -07:00
Oliver-Akins
dbedecc900 Prefix interfaces for data with a capital I 2022-01-03 18:40:09 -07:00
Oliver-Akins
fae2fe3837 Add some documentation to the status enum 2022-01-03 18:35:54 -07:00
Oliver-Akins
9f77f255a3 Remove type file 2022-01-03 18:25:33 -07:00
Oliver-Akins
8ea5b506f4 Add common module as a dependency 2022-01-03 18:25:22 -07:00
Oliver-Akins
c2dfd2fd3a Convert from the symlink format to a local package 2022-01-03 18:24:59 -07:00
Oliver-Akins
4c957cce07 Put types and enums that both the server and web need into a common area 2021-12-28 20:08:42 -07:00
Oliver-Akins
526b4c5771 Add a document detailing all the events that the client will send to the server 2021-12-25 23:58:16 -07:00
Oliver-Akins
0edf74ab7f Add the ship type to the player data 2021-12-25 23:57:26 -07:00
Oliver-Akins
c282ec0378 Add a background to each of the options 2021-12-25 23:56:54 -07:00
Oliver-Akins
1e8ae8e99f Remove the taken property from Colour 2021-12-25 23:56:13 -07:00
Oliver-Akins
7de3f614da Add a Hexagon component 2021-12-25 23:55:54 -07:00
Oliver-Akins
72db6ae4d0 Update the ColourChoice to be better UI & UX 2021-12-25 23:55:44 -07:00
Oliver-Akins
19a4d909f8 Add a component that allows for easier ship svg usage 2021-12-25 23:55:17 -07:00
Oliver-Akins
f3af5a77d7 Add the ColourChoice modal to the lobby 2021-12-24 03:22:38 -07:00
Oliver-Akins
36487500e1 Add a type for the colour object 2021-12-24 03:22:15 -07:00
Oliver-Akins
2530e90774 Make player component use the isHost store 2021-12-24 03:21:59 -07:00
Oliver-Akins
6dc0ed2ab5 Add type annocations, change hostName to isHost, add players list and type 2021-12-24 03:21:31 -07:00
Oliver-Akins
0fb2d986a9 Change way the images are marked up, and correct some alt text 2021-12-24 03:20:32 -07:00
Oliver-Akins
0925b6d59f Begin work on a modal for changing the player's colour 2021-12-24 03:20:00 -07:00
Oliver-Akins
7b4826dfbc Make the button components use the click listen instead of the handler 2021-12-24 03:19:33 -07:00
Oliver-Akins
4bdbe8f231 Update the button to not take a handler function and just bubble the click event 2021-12-24 03:18:25 -07:00
Oliver-Akins
ba093cdfa7 Add the space shuttle SVG that I'm going to use 2021-12-24 03:17:59 -07:00
Oliver-Akins
c305551175 Use an each block for option populating 2021-12-22 19:39:21 -07:00
Oliver-Akins
8e568d355c Update option descriptions and add Fate option 2021-12-22 19:31:35 -07:00
Oliver-Akins
8683f50bd4 Adjust the code format and add the ability to disable the checkbox 2021-12-22 19:31:05 -07:00
Oliver-Akins
ffc3c80378 Edit the SCSS to be more SCSSesque 2021-12-22 19:30:17 -07:00
Oliver-Akins
52412ce914 load global CSS, and import the Google Material icons 2021-12-17 01:46:31 -06:00
Oliver-Akins
21bb53fba2 Add a JSON file representing ever card we need 2021-12-17 01:46:11 -06:00
Oliver-Akins
9308c984ca Add modals to the z-index list 2021-12-17 01:45:56 -06:00
Oliver-Akins
a9fa614a79 Tweak header to be good on mobile devices 2021-12-17 01:45:37 -06:00
Oliver-Akins
9105639bd9 Add options to the lobby screen 2021-12-17 01:45:09 -06:00
Oliver-Akins
9a43b1c17f Add mixins for the size breakpoints 2021-12-17 01:44:34 -06:00
Oliver-Akins
cef8234b85 Add a custom checkbox component 2021-12-17 01:44:17 -06:00
Oliver-Akins
64a29437d1 Allow custom classes to be on the button, make the button change the cursor type, and adjust indent on the script to make it consistent with other components 2021-12-17 01:43:45 -06:00
Oliver-Akins
dc15f29401 tweak player rows to work better on mobile devices 2021-12-17 01:41:54 -06:00
Oliver-Akins
52c6866817 Add a modal for the option info 2021-12-17 01:41:25 -06:00
Oliver-Akins
64fbf66e63 Add a base modal that other modals can expand off of 2021-12-17 01:41:14 -06:00
Oliver-Akins
8035a3afe2 Add some global CSS 2021-12-17 01:40:59 -06:00
Oliver-Akins
4b5444ead6 Add player related data for the lobby viewing 2021-12-15 02:44:11 -06:00
Oliver-Akins
11cdf71203 Add the multiplayer lobby view to the state list 2021-12-15 02:43:24 -06:00
Oliver-Akins
9bc5fd1ad1 Begin work on the multiplayer lobby view 2021-12-15 02:42:58 -06:00
Oliver-Akins
9541bf144f Tweak the button to allow fixed-colour background when using it with icons 2021-12-15 02:42:45 -06:00
Oliver-Akins
cfa6dff9b9 Add a Player component for player data in the lobby 2021-12-15 02:42:15 -06:00
Oliver-Akins
a81a1f983a Add icons for buttons 2021-12-15 02:41:34 -06:00
Oliver-Akins
b2fe90c7a5 Add Svelte/Vite stuff to the gitignore 2021-12-15 00:15:42 -06:00
Oliver-Akins
90819f50e4 Begin implementation of the site with Svelte 2021-12-15 00:14:12 -06:00
99 changed files with 8052 additions and 1 deletions

11
.gitignore vendored
View file

@ -1,3 +1,14 @@
node_modules/
dist/
/common/cjs/
/common/esm/
.vscode/
.DS_Store
*.toml
!*.template.toml
/web-*
!/web-svelte
# Logs
logs
*.log

View file

@ -1 +1,17 @@
# Gravwell-Online
## Development:
Below are the steps required to run the code in development mode:
1. Clone the git repository
2. Go into the common code module: `cd common`
3. Build the code for commonjs and ES module distribution with: `make`
4. In a new terminal: `cd web-svelte`
5. Install the required dependencies: `pnpm install`
- If the `common` module fails to import, link it with `pnpm link ../common/esm`
6. Run Vite: `pnpm dev`
7. In a new terminal: `cd server`
8. Install the required dependencies: `pnpm install`
- If the `common` modules fails to import, link it with `pnpm link ../common/cjs`
9. Compile the server code with `tsc`
10. Run the server code with `pnpm start`

31
common/makefile Normal file
View file

@ -0,0 +1,31 @@
.PHONY: all esm cjs test watch watch-esm watch-cjs
ESM_LOCATION=./esm
CJS_LOCATION=./cjs
all: esm cjs
esm:
@mkdir $(ESM_LOCATION) --parents
tsc --module es6 --outDir $(ESM_LOCATION)
cp ./package.json $(ESM_LOCATION)/package.json
cjs:
@mkdir $(CJS_LOCATION) --parents
tsc --module commonjs --outDir $(CJS_LOCATION)
cp ./package.json $(CJS_LOCATION)/package.json
watch:
@echo To have Typescript auto-rebuild, run the following commands in new terminals
@echo "\tmake watch-esm"
@echo "\tmake watch-cjs"
@echo These processes will not terminate, which is why they cannot be run in the same rule
watch-esm: esm
tsc --module es6 --outDir $(ESM_LOCATION) --watch
watch-cjs: cjs
tsc --module commonjs --outDir $(CJS_LOCATION) --watch
test:
node_modules/mocha/bin/_mocha -r ts-node/register src/**/*.spec.ts

21
common/package.json Normal file
View file

@ -0,0 +1,21 @@
{
"name": "common",
"version": "1.0.0",
"description": "",
"module": "index.js",
"main": "index.js",
"scripts": {
"test": "echo \"To run the tests use: make test\" && exit 1"
},
"author": "Oliver Akins",
"license": "UNLICENSED",
"devDependencies": {
"@types/chai": "^4.3.1",
"@types/mocha": "^9.1.1",
"@types/node": "^18.0.6",
"chai": "^4.3.6",
"mocha": "^10.0.0",
"ts-node": "^10.9.1",
"typescript": "^4.7.4"
}
}

705
common/pnpm-lock.yaml generated Normal file
View file

@ -0,0 +1,705 @@
lockfileVersion: 5.4
specifiers:
'@types/chai': ^4.3.1
'@types/mocha': ^9.1.1
'@types/node': ^18.0.6
chai: ^4.3.6
mocha: ^10.0.0
ts-node: ^10.9.1
typescript: ^4.7.4
devDependencies:
'@types/chai': 4.3.1
'@types/mocha': 9.1.1
'@types/node': 18.0.6
chai: 4.3.6
mocha: 10.0.0
ts-node: 10.9.1_tdn3ypgnfy6bmey2q4hu5jonwi
typescript: 4.7.4
packages:
/@cspotcode/source-map-support/0.8.1:
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
engines: {node: '>=12'}
dependencies:
'@jridgewell/trace-mapping': 0.3.9
dev: true
/@jridgewell/resolve-uri/3.1.0:
resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==}
engines: {node: '>=6.0.0'}
dev: true
/@jridgewell/sourcemap-codec/1.4.14:
resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==}
dev: true
/@jridgewell/trace-mapping/0.3.9:
resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
dependencies:
'@jridgewell/resolve-uri': 3.1.0
'@jridgewell/sourcemap-codec': 1.4.14
dev: true
/@tsconfig/node10/1.0.9:
resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==}
dev: true
/@tsconfig/node12/1.0.11:
resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==}
dev: true
/@tsconfig/node14/1.0.3:
resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==}
dev: true
/@tsconfig/node16/1.0.3:
resolution: {integrity: sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==}
dev: true
/@types/chai/4.3.1:
resolution: {integrity: sha512-/zPMqDkzSZ8t3VtxOa4KPq7uzzW978M9Tvh+j7GHKuo6k6GTLxPJ4J5gE5cjfJ26pnXst0N5Hax8Sr0T2Mi9zQ==}
dev: true
/@types/mocha/9.1.1:
resolution: {integrity: sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==}
dev: true
/@types/node/18.0.6:
resolution: {integrity: sha512-/xUq6H2aQm261exT6iZTMifUySEt4GR5KX8eYyY+C4MSNPqSh9oNIP7tz2GLKTlFaiBbgZNxffoR3CVRG+cljw==}
dev: true
/@ungap/promise-all-settled/1.1.2:
resolution: {integrity: sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==}
dev: true
/acorn-walk/8.2.0:
resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==}
engines: {node: '>=0.4.0'}
dev: true
/acorn/8.7.1:
resolution: {integrity: sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==}
engines: {node: '>=0.4.0'}
hasBin: true
dev: true
/ansi-colors/4.1.1:
resolution: {integrity: sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==}
engines: {node: '>=6'}
dev: true
/ansi-regex/5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
dev: true
/ansi-styles/4.3.0:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
dependencies:
color-convert: 2.0.1
dev: true
/anymatch/3.1.2:
resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==}
engines: {node: '>= 8'}
dependencies:
normalize-path: 3.0.0
picomatch: 2.3.1
dev: true
/arg/4.1.3:
resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
dev: true
/argparse/2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
dev: true
/assertion-error/1.1.0:
resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
dev: true
/balanced-match/1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
dev: true
/binary-extensions/2.2.0:
resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
engines: {node: '>=8'}
dev: true
/brace-expansion/1.1.11:
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
dependencies:
balanced-match: 1.0.2
concat-map: 0.0.1
dev: true
/brace-expansion/2.0.1:
resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
dependencies:
balanced-match: 1.0.2
dev: true
/braces/3.0.2:
resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
engines: {node: '>=8'}
dependencies:
fill-range: 7.0.1
dev: true
/browser-stdout/1.3.1:
resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==}
dev: true
/camelcase/6.3.0:
resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
engines: {node: '>=10'}
dev: true
/chai/4.3.6:
resolution: {integrity: sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==}
engines: {node: '>=4'}
dependencies:
assertion-error: 1.1.0
check-error: 1.0.2
deep-eql: 3.0.1
get-func-name: 2.0.0
loupe: 2.3.4
pathval: 1.1.1
type-detect: 4.0.8
dev: true
/chalk/4.1.2:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
dependencies:
ansi-styles: 4.3.0
supports-color: 7.2.0
dev: true
/check-error/1.0.2:
resolution: {integrity: sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==}
dev: true
/chokidar/3.5.3:
resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
engines: {node: '>= 8.10.0'}
dependencies:
anymatch: 3.1.2
braces: 3.0.2
glob-parent: 5.1.2
is-binary-path: 2.1.0
is-glob: 4.0.3
normalize-path: 3.0.0
readdirp: 3.6.0
optionalDependencies:
fsevents: 2.3.2
dev: true
/cliui/7.0.4:
resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==}
dependencies:
string-width: 4.2.3
strip-ansi: 6.0.1
wrap-ansi: 7.0.0
dev: true
/color-convert/2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
dependencies:
color-name: 1.1.4
dev: true
/color-name/1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
dev: true
/concat-map/0.0.1:
resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=}
dev: true
/create-require/1.1.1:
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
dev: true
/debug/4.3.4_supports-color@8.1.1:
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
dependencies:
ms: 2.1.2
supports-color: 8.1.1
dev: true
/decamelize/4.0.0:
resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==}
engines: {node: '>=10'}
dev: true
/deep-eql/3.0.1:
resolution: {integrity: sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==}
engines: {node: '>=0.12'}
dependencies:
type-detect: 4.0.8
dev: true
/diff/4.0.2:
resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
engines: {node: '>=0.3.1'}
dev: true
/diff/5.0.0:
resolution: {integrity: sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==}
engines: {node: '>=0.3.1'}
dev: true
/emoji-regex/8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
dev: true
/escalade/3.1.1:
resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==}
engines: {node: '>=6'}
dev: true
/escape-string-regexp/4.0.0:
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
engines: {node: '>=10'}
dev: true
/fill-range/7.0.1:
resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
engines: {node: '>=8'}
dependencies:
to-regex-range: 5.0.1
dev: true
/find-up/5.0.0:
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
engines: {node: '>=10'}
dependencies:
locate-path: 6.0.0
path-exists: 4.0.0
dev: true
/flat/5.0.2:
resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==}
hasBin: true
dev: true
/fs.realpath/1.0.0:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
dev: true
/fsevents/2.3.2:
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
requiresBuild: true
dev: true
optional: true
/get-caller-file/2.0.5:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
engines: {node: 6.* || 8.* || >= 10.*}
dev: true
/get-func-name/2.0.0:
resolution: {integrity: sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==}
dev: true
/glob-parent/5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'}
dependencies:
is-glob: 4.0.3
dev: true
/glob/7.2.0:
resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==}
dependencies:
fs.realpath: 1.0.0
inflight: 1.0.6
inherits: 2.0.4
minimatch: 3.1.2
once: 1.4.0
path-is-absolute: 1.0.1
dev: true
/has-flag/4.0.0:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
dev: true
/he/1.2.0:
resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
hasBin: true
dev: true
/inflight/1.0.6:
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
dependencies:
once: 1.4.0
wrappy: 1.0.2
dev: true
/inherits/2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
dev: true
/is-binary-path/2.1.0:
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
engines: {node: '>=8'}
dependencies:
binary-extensions: 2.2.0
dev: true
/is-extglob/2.1.1:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'}
dev: true
/is-fullwidth-code-point/3.0.0:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
engines: {node: '>=8'}
dev: true
/is-glob/4.0.3:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'}
dependencies:
is-extglob: 2.1.1
dev: true
/is-number/7.0.0:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'}
dev: true
/is-plain-obj/2.1.0:
resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==}
engines: {node: '>=8'}
dev: true
/is-unicode-supported/0.1.0:
resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==}
engines: {node: '>=10'}
dev: true
/js-yaml/4.1.0:
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
hasBin: true
dependencies:
argparse: 2.0.1
dev: true
/locate-path/6.0.0:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'}
dependencies:
p-locate: 5.0.0
dev: true
/log-symbols/4.1.0:
resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==}
engines: {node: '>=10'}
dependencies:
chalk: 4.1.2
is-unicode-supported: 0.1.0
dev: true
/loupe/2.3.4:
resolution: {integrity: sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==}
dependencies:
get-func-name: 2.0.0
dev: true
/make-error/1.3.6:
resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
dev: true
/minimatch/3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
dependencies:
brace-expansion: 1.1.11
dev: true
/minimatch/5.0.1:
resolution: {integrity: sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==}
engines: {node: '>=10'}
dependencies:
brace-expansion: 2.0.1
dev: true
/mocha/10.0.0:
resolution: {integrity: sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA==}
engines: {node: '>= 14.0.0'}
hasBin: true
dependencies:
'@ungap/promise-all-settled': 1.1.2
ansi-colors: 4.1.1
browser-stdout: 1.3.1
chokidar: 3.5.3
debug: 4.3.4_supports-color@8.1.1
diff: 5.0.0
escape-string-regexp: 4.0.0
find-up: 5.0.0
glob: 7.2.0
he: 1.2.0
js-yaml: 4.1.0
log-symbols: 4.1.0
minimatch: 5.0.1
ms: 2.1.3
nanoid: 3.3.3
serialize-javascript: 6.0.0
strip-json-comments: 3.1.1
supports-color: 8.1.1
workerpool: 6.2.1
yargs: 16.2.0
yargs-parser: 20.2.4
yargs-unparser: 2.0.0
dev: true
/ms/2.1.2:
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
dev: true
/ms/2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
dev: true
/nanoid/3.3.3:
resolution: {integrity: sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
dev: true
/normalize-path/3.0.0:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'}
dev: true
/once/1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
dependencies:
wrappy: 1.0.2
dev: true
/p-limit/3.1.0:
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
engines: {node: '>=10'}
dependencies:
yocto-queue: 0.1.0
dev: true
/p-locate/5.0.0:
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
engines: {node: '>=10'}
dependencies:
p-limit: 3.1.0
dev: true
/path-exists/4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'}
dev: true
/path-is-absolute/1.0.1:
resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
engines: {node: '>=0.10.0'}
dev: true
/pathval/1.1.1:
resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}
dev: true
/picomatch/2.3.1:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'}
dev: true
/randombytes/2.1.0:
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
dependencies:
safe-buffer: 5.2.1
dev: true
/readdirp/3.6.0:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'}
dependencies:
picomatch: 2.3.1
dev: true
/require-directory/2.1.1:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'}
dev: true
/safe-buffer/5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
dev: true
/serialize-javascript/6.0.0:
resolution: {integrity: sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==}
dependencies:
randombytes: 2.1.0
dev: true
/string-width/4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
dependencies:
emoji-regex: 8.0.0
is-fullwidth-code-point: 3.0.0
strip-ansi: 6.0.1
dev: true
/strip-ansi/6.0.1:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'}
dependencies:
ansi-regex: 5.0.1
dev: true
/strip-json-comments/3.1.1:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
dev: true
/supports-color/7.2.0:
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
engines: {node: '>=8'}
dependencies:
has-flag: 4.0.0
dev: true
/supports-color/8.1.1:
resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==}
engines: {node: '>=10'}
dependencies:
has-flag: 4.0.0
dev: true
/to-regex-range/5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
dependencies:
is-number: 7.0.0
dev: true
/ts-node/10.9.1_tdn3ypgnfy6bmey2q4hu5jonwi:
resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==}
hasBin: true
peerDependencies:
'@swc/core': '>=1.2.50'
'@swc/wasm': '>=1.2.50'
'@types/node': '*'
typescript: '>=2.7'
peerDependenciesMeta:
'@swc/core':
optional: true
'@swc/wasm':
optional: true
dependencies:
'@cspotcode/source-map-support': 0.8.1
'@tsconfig/node10': 1.0.9
'@tsconfig/node12': 1.0.11
'@tsconfig/node14': 1.0.3
'@tsconfig/node16': 1.0.3
'@types/node': 18.0.6
acorn: 8.7.1
acorn-walk: 8.2.0
arg: 4.1.3
create-require: 1.1.1
diff: 4.0.2
make-error: 1.3.6
typescript: 4.7.4
v8-compile-cache-lib: 3.0.1
yn: 3.1.1
dev: true
/type-detect/4.0.8:
resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==}
engines: {node: '>=4'}
dev: true
/typescript/4.7.4:
resolution: {integrity: sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==}
engines: {node: '>=4.2.0'}
hasBin: true
dev: true
/v8-compile-cache-lib/3.0.1:
resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
dev: true
/workerpool/6.2.1:
resolution: {integrity: sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==}
dev: true
/wrap-ansi/7.0.0:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
engines: {node: '>=10'}
dependencies:
ansi-styles: 4.3.0
string-width: 4.2.3
strip-ansi: 6.0.1
dev: true
/wrappy/1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
dev: true
/y18n/5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'}
dev: true
/yargs-parser/20.2.4:
resolution: {integrity: sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==}
engines: {node: '>=10'}
dev: true
/yargs-unparser/2.0.0:
resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==}
engines: {node: '>=10'}
dependencies:
camelcase: 6.3.0
decamelize: 4.0.0
flat: 5.0.2
is-plain-obj: 2.1.0
dev: true
/yargs/16.2.0:
resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==}
engines: {node: '>=10'}
dependencies:
cliui: 7.0.4
escalade: 3.1.1
get-caller-file: 2.0.5
require-directory: 2.1.1
string-width: 4.2.3
y18n: 5.0.8
yargs-parser: 20.2.4
dev: true
/yn/3.1.1:
resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
engines: {node: '>=6'}
dev: true
/yocto-queue/0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
dev: true

View file

@ -0,0 +1,157 @@
import { Board } from "../types/GameBoard";
import { expect } from "chai";
import "mocha";
import { countShips, shipLocation } from "./movement";
function stringifyBoard(b: Board) {
return (
b.singularity.join(`\t`)
+ ` > `
+ [...b.path].map(x => x != null ? x : `-`).join(`\t`)
+ ` > `
+ b.warpgate.join(`\t`)
);
};
describe("The shipLocation function", () => {
const tests = [
{
name: `Should correctly identify the singularity location`,
args: ["a"],
input: {
singularity: ["a"],
path: [],
warpgate: [],
},
expect: "singularity",
},
{
name: `Should correctly identify the path location`,
args: ["a"],
input: {
singularity: [],
path: ["a"],
warpgate: [],
},
expect: "path",
},
{
name: `Should correctly identify the warpgate location`,
args: ["a"],
input: {
singularity: [],
path: [],
warpgate: ["a"],
},
expect: "warpgate",
},
{
name: `Should correctly identify the ship doesn't exist`,
args: ["a"],
input: {
singularity: [],
path: [],
warpgate: [],
},
expect: null,
},
];
for (const test of tests) {
it(test.name, () => {
let b = JSON.parse(JSON.stringify(test.input));
let location = shipLocation(b, test.args[0]);
expect(location).to.equal(test.expect);
});
};
});
describe("The countShips function", () => {
const tests = [
{
name: `shouldn't could ships in the singularity or warpgate`,
args: [3],
input: {
singularity: ["a"],
path: [null, null, null, "b", null, null, null],
warpgate: ["c"],
},
shouldError: false,
expect: { left: 0, right: 0 },
},
{
name: `should count the ships in the path correctly when location is in the middle`,
args: [3],
input: {
singularity: ["a"],
path: ["d", null, "c", "b", null, null, "e"],
warpgate: [],
},
shouldError: false,
expect: { left: 2, right: 1 },
},
{
name: `should count the ships in the path correctly when location is on the low edge`,
args: [0],
input: {
singularity: ["a"],
path: ["d", null, "c", "b", null, null, "e"],
warpgate: [],
},
shouldError: false,
expect: { left: 0, right: 3 },
},
{
name: `should count the ships in the path correctly when location is on the high edge`,
args: [6],
input: {
singularity: ["a"],
path: ["d", null, "c", "b", null, null, "e"],
warpgate: [],
},
shouldError: false,
expect: { left: 3, right: 0 },
},
{
name: `should error when location is negative`,
args: [-1],
input: {
singularity: [],
path: ["d", null, "c", "b", null, null, "e"],
warpgate: [],
},
expect: new Error("location can't be negative"),
},
{
name: `should error when location is too high`,
args: [100],
input: {
singularity: [],
path: ["d", null, "c", "b", null, null, "e"],
warpgate: [],
},
expect: new Error("location can't be larger than the path"),
},
];
for (const test of tests) {
it(test.name, () => {
let b = JSON.parse(JSON.stringify(test.input));
if (test.expect instanceof Error) {
expect(countShips(b, test.args[0])).to.throw(test.expect);
} else {
let count = countShips(b, test.args[0]);
expect(count).to.have.all.keys(Object.keys(test.expect));
expect(count.left).to.equal(test.expect.left);
expect(count.right).to.equal(test.expect.right);
};
});
};
});

View file

@ -0,0 +1,201 @@
import { FuelCard } from "../index";
import { GamePiece, Board } from "../index";
/** @internal */
export function shipLocation(board: Board, ship: GamePiece) {
if (board.path.includes(ship)) {
return "path";
};
if (board.singularity.includes(ship)) {
return "singularity";
};
if (board.warpgate.includes(ship)) {
return "warpgate";
};
return null;
};
/** @internal */
export function countShips(board: Board, location: number) {
if (location < 0) {
throw new Error("location can't be negative");
};
if (location > board.path.length) {
throw new Error("location can't be larger than the path");
};
let left = 0;
if (location > 0) {
left = board.path.slice(0, location - 1).filter(x => x != null).length;
};
return {
left,
right: board.path.slice(location + 1).filter(x => x != null).length,
};
};
/** @internal */
export function determineDirection(board: Board, location: number): number {
/*
Edge-Case check for when the board only has a single ship on it because any
ships that hit the warp gate get removed from the game, and ships in the
singularity don't create gravity wells, not affecting other ships.
*/
let shipCount = 0;
for (const location of board.path) {
if (location != null) {
shipCount++;
if (shipCount >= 2) {
break;
};
};
};
if (shipCount <= 1) {
return 1;
};
let delta = 1;
while (true) {
if (location - delta < 0) {
return 1;
} else if (location + delta >= board.path.length) {
return -1;
} else {
let left = board.path[location - delta];
let right = board.path[location + delta];
if (left != null && right != null) {
let sum = countShips(board, location)
return Math.sign(sum.right - sum.left);
} else if (left != null) {
return -1;
} else if (right != null) {
return 1;
};
delta++;
};
};
};
/** @internal */
export function tractorBeam(board: Board, ship: GamePiece, card: FuelCard) {
let delta = 0;
while (true) {
delta++;
let behind = origin - delta;
let ahead = origin + delta;
let negativeNewPosition = behind;
let positiveNewPosition = ahead;
let behindShip = board[behind];
let aheadShip = board[ahead];
/*
Check for a ship behind the origin, if there is a ship there then pull
it towards the origin (forwards). If it were to collide with another
ship, then drift until it no longer collides.
*/
if (
behind >= 0
&& behindShip != null
) {
let localMagnitude = magnitude;
while (board[behind + localMagnitude] != null) {
localMagnitude++;
};
negativeNewPosition = behind + localMagnitude;
board[behind] = null;
};
/*
Check for a ship ahead of the origin, if there is a ship there then
pull it towards the origin (backwards). If it were to collide with
another ship, then drift until it no longer collides.
*/
if (
ahead < board.length
&& aheadShip
) {
let localMagnitude = magnitude;
while (board[ahead - localMagnitude] != null) {
localMagnitude++;
};
positiveNewPosition = ahead - localMagnitude;
board[ahead] = null;
};
// Simultaneous movement
if (aheadShip) {
board[positiveNewPosition] = aheadShip;
};
if (behindShip) {
board[negativeNewPosition] = behindShip;
};
if (behind < 0 && ahead >= board.length) {
break;
};
};
};
/** @internal */
export function moveShip(board: Board, ship: GamePiece, card: FuelCard) {
let locale = shipLocation(board, ship);
if (locale == "singularity") {
let newPos = card.magnitude;
// Used a purple card for some reason...
if (newPos < 0) { return };
board.path[card.magnitude] = ship;
}
else if (locale == "path") {
let oldPos = board.path.indexOf(ship);
let direction = determineDirection(board, oldPos)
let newPos = oldPos * direction * card.magnitude;
if (newPos < 0) {
board.singularity.push(ship);
board.path[oldPos] = null;
} else if (newPos >= board.path.length) {
board.warpgate.push(ship);
board.path[oldPos] = null;
} else {
// Drift the ship
while (board.path[newPos] != null) {
newPos += direction;
};
board.path[oldPos] = null;
board.path[newPos] = ship;
};
};
};
/**
* Applies a card to the board as it was played by the ship. This modifies the
* board in-place, if wanting to not modify the board in place, pass a deep-copy
* of the board into the function.
*
* @param board The game board
* @param ship The ship that is playing the card
* @param card The card that is being played
*/
export function processCard(board: Board, ship: GamePiece, card: FuelCard) {
if (card.type == "movement") {
moveShip(board, ship, card);
} else {
tractorBeam(board, ship, card);
};
};

View file

@ -0,0 +1,193 @@
import { countShips, determineDirection } from "./movementDirection"
import { expect } from "chai";
import "mocha";
describe("The countShips function", () => {
it("should count the ships correctly when unbalanced", () => {
let r = countShips(["p3", null, "p1", "p2", null, "p4"], 2);
expect(r).to.have.keys(`left`, `right`);
expect(r.left).to.equal(1);
expect(r.right).to.equal(2);
});
it("should count the ships correctly when balanced", () => {
let r = countShips(["p3", null, "p1", "p2", null], 2);
expect(r).to.have.keys(`left`, `right`);
expect(r.left).to.equal(1);
expect(r.right).to.equal(1);
});
it("should count the ships correctly when shipLocation is at the start of the array", () => {
let r = countShips(["p3", null, "p1", "p2", null], 0);
expect(r).to.have.keys(`left`, `right`);
expect(r.left).to.equal(0);
expect(r.right).to.equal(2);
});
it("should count the ships correctly when shipLocation is at the end of the array", () => {
let r = countShips(["p3", null, "p1", "p2", null, "p4"], 5);
expect(r).to.have.keys(`left`, `right`);
expect(r.left).to.equal(3);
expect(r.right).to.equal(0);
});
it("should throw an error when shipLocation is larger than length", () => {
try {
countShips(["p3", null, "p1", "p2", null, "p4"], 8);
throw "Function didn't throw an error";
} catch (_) {}
});
it("should throw an error when shipLocation is less than 0", () => {
try {
countShips(["p3", null, "p1", "p2", null, "p4"], -3);
throw "Function didn't throw an error";
} catch (_) {}
});
});
describe("The determineDirection function", () => {
it("should return positive when no other ships present (0-index)", () => {
let b = [
"p1", null, null, null, null,
null, null, null, null, null,
null, null, null, null, null,
];
let p = b.findIndex(x => x == "p1");
expect(determineDirection(b, p)).to.be.greaterThan(0);
});
it("should return positive when no other ships present (low-index)", () => {
let b = [
null, null, "p1", null, null,
null, null, null, null, null,
null, null, null, null, null,
];
let p = b.findIndex(x => x == "p1");
expect(determineDirection(b, p)).to.be.greaterThan(0);
});
it("should return positive when no other ships present (mid-index)", () => {
let b = [
null, null, null, null, null,
null, null, "p1", null, null,
null, null, null, null, null,
];
let p = b.findIndex(x => x == "p1");
expect(determineDirection(b, p)).to.be.greaterThan(0);
});
it("should return positive when no other ships present (high-index)", () => {
let b = [
null, null, null, null, null,
null, null, null, null, null,
null, null, "p1", null, null,
];
let p = b.findIndex(x => x == "p1");
expect(determineDirection(b, p)).to.be.greaterThan(0);
});
it("should return positive when no other ships present (max-index)", () => {
let b = [
null, null, null, null, null,
null, null, null, null, null,
null, null, null, null, "p1",
];
let p = b.findIndex(x => x == "p1");
expect(determineDirection(b, p)).to.be.greaterThan(0);
});
it("should return negative when the only other ship is at a lower index", () => {
let b = [
null, "p2", null, null, null,
null, null, "p1", null, null,
null, null, null, null, null,
];
let p = b.findIndex(x => x == "p1");
expect(determineDirection(b, p)).to.be.lessThan(0);
});
it("should return negative when the closest ship is on the 0th index", () => {
let b = [ "p2", null, "p1", null, null, "p3", null ];
let p = b.findIndex(x => x == "p1");
expect(determineDirection(b, p)).to.be.lessThan(0);
});
it("should return negative when the closest ship is on the highest index", () => {
let b = [ null, "p2", null, null, "p1", null, "p3" ];
let p = b.findIndex(x => x == "p1");
expect(determineDirection(b, p)).to.be.greaterThan(0);
});
it("should return positive when the only other ship is at a higher index", () => {
let b = [
null, null, null, null, null,
null, null, "p1", null, null,
null, null, null, "p2", null,
];
let p = b.findIndex(x => x == "p1");
expect(determineDirection(b, p)).to.be.greaterThan(0);
});
it("should return positive when the closest ship is at a higher index", () => {
let b = [
null, "p3", null, null, null,
null, null, "p1", null, null,
null, null, "p2", null, null,
];
let p = b.findIndex(x => x == "p1");
expect(determineDirection(b, p)).to.be.greaterThan(0);
});
it("should return negative when the closest ship is at a lower index", () => {
let b = [
null, null, "p3", null, null,
null, null, "p1", null, null,
null, null, null, "p2", null,
];
let p = b.findIndex(x => x == "p1");
expect(determineDirection(b, p)).to.be.lessThan(0);
});
it("should return 0 when there is a tie between the closest ships (no extra ships)", () => {
let b = [
null, null, "p3", null, null,
null, null, "p1", null, null,
null, null, "p2", null, null,
];
let p = b.findIndex(x => x == "p1");
expect(determineDirection(b, p)).to.equal(0);
});
it("should return negative when there is a tie between the closest ships (extra ships in negative direction)", () => {
let b = [
null, "p4", "p3", null, null,
null, null, "p1", null, null,
null, null, "p2", null, null,
];
let p = b.findIndex(x => x == "p1");
expect(determineDirection(b, p)).to.be.lessThan(0);
});
it("should return positive when there is a tie between the closest ships (extra ships in positive direction)", () => {
let b = [
null, null, "p3", null, null,
null, null, "p1", null, null,
null, null, "p2", "p4", null,
];
let p = b.findIndex(x => x == "p1");
expect(determineDirection(b, p)).to.be.greaterThan(0);
});
it("should return positive when there is a tie between the closest ships (extra ships tied)", () => {
let b = [
null, "p5", "p3", null, null,
null, null, "p1", null, null,
null, null, "p2", "p4", null,
];
let p = b.findIndex(x => x == "p1");
expect(determineDirection(b, p)).to.equal(0);
});
});

View file

@ -0,0 +1,115 @@
/**
* @internal
* A helper method for counting how many ships are on each side of a specific
* ship on the board.
*
* @param board The array of spaces that represents the Gravwell board
* @param shipLocation The index of the ship that we are counting the number of
* ships on each side for.
* @returns An object containing the number of ships that are on each side of
* the target ship
*/
export function countShips(board: any[], shipLocation: number) {
if (shipLocation < 0) {
throw new Error("shipLocation must be >= 0");
};
// Ensure that when shipLocation we don't assume all ships are to the left
// of it due to negative indexing in .slice() being supported
let left = 0;
if (shipLocation > 0) {
left = board.slice(0, shipLocation - 1).filter(x => x != null).length;
};
return {
left,
right: board.slice(shipLocation + 1).filter(x => x != null).length
};
};
/**
* The algorithm to determine which direction the closest ship is, this uses an
* integer that can be multiplied by the player's fuel card magnitude in order
* to determine the delta for the board index.
*
* ---
*
* Possible Return Values:
* - `-1` = Away from the Warp Gate
* - `0` = Not moving
* - `1` = Towards the Warp Gate
*/
export function determineDirection(board: any[], shipLocation: number) {
/*
Edge-Case check for when the board only has a single ship on it because any
ships that hit the warp gate get removed from the game, and ships in the
singularity don't create gravity wells, not affecting other ships.
*/
let shipCount = 0;
for (const location of board) {
if (location != null) {
shipCount++;
if (shipCount >= 2) {
break;
};
};
};
if (shipCount <= 1) {
return 1;
};
let delta = 1;
while (true) {
// The left side of the board is out of range, nearest ship is always
// right
if (shipLocation - delta < 0) {
return 1;
}
// The right side of the board is out of range, nearest ship is always
// left
else if (shipLocation + delta >= board.length) {
return -1;
}
// Both sides are still in range, continue
else {
let left = board[shipLocation - delta];
let right = board[shipLocation + delta];
// Nearest ships are equidistant, fallback to sum
if (left != null && right != null) {
let sum = countShips(board, shipLocation);
// More ships on the left, going backwards
if (sum.left > sum.right) {
return -1;
}
// more ships on the right, going forward
else if (sum.left < sum.right) {
return 1;
}
// Equal number of ships, staying still
else {
return 0;
};
}
// Left side of ship is closer
else if (left != null) {
return -1;
}
// Right side of ship is closer
else if (right != null) {
return 1;
};
delta++;
};
};
};

View file

@ -0,0 +1,233 @@
import { tractorBeam, moveShip } from "./processCard";
import { Board } from "../types/GameBoard";
import { expect } from "chai";
import "mocha";
import { FuelCard } from "../types/FuelCard";
function stringifyBoard(b: Board) {
return [...b].map(x => x != null ? x : `-`).join(`\t`);
};
interface BaseTest {
name: string;
input: Board;
expect: Board;
}
interface tractorTest extends BaseTest {
args: [number, number];
}
interface moveTest extends BaseTest {
args: [number, FuelCard] | [number];
}
describe("The tractorBeam function", () => {
const tests: tractorTest[] = [
{
name: `should move the ships closer when equidistant`,
args: [4, 1],
input: [ null, "p1", null, null, "p2", null, null, "p3", null ],
expect: [ null, null, "p1", null, "p2", null, "p3", null, null ]
},
{
name: `should move the ships closer when not equidistant`,
args: [4, 1],
input: [ null, "p1", null, null, "p2", null, null, null, "p3" ],
expect: [ null, null, "p1", null, "p2", null, null, "p3", null ]
},
{
name: `should move the ships closer when only on the left side`,
args: [4, 1],
input: [ null, "p1", null, null, "p2", null, null, null, null ],
expect: [ null, null, "p1", null, "p2", null, null, null, null ]
},
{
name: `should move the ships closer when only on the right side`,
args: [4, 1],
input: [ null, null, null, null, "p2", null, null, "p3", null ],
expect: [ null, null, null, null, "p2", null, "p3", null, null ]
},
{
name: `should move the ships closer when pulling the ship from the left edge`,
args: [4, 1],
input: [ "p1", null, null, null, "p2", null, null, null, null ],
expect: [ null, "p1", null, null, "p2", null, null, null, null ]
},
{
name: `should move the ships closer when pulling the ship from the right edge`,
args: [4, 1],
input: [ null, null, null, null, "p2", null, null, null, "p3" ],
expect: [ null, null, null, null, "p2", null, null, "p3", null ]
},
{
name: `should move the ship closer when the origin is on the left edge`,
args: [0, 1],
input: [ "p1", null, null, null, "p2", null, null, null, null ],
expect: [ "p1", null, null, "p2", null, null, null, null, null ]
},
{
name: `should move the ship closer when the origin is on the right edge`,
args: [8, 1],
input: [ null, null, null, null, "p2", null, null, null, "p1" ],
expect: [ null, null, null, null, null, "p2", null, null, "p1" ]
},
{
name: `should move the ship correctly when it needs to drift (single ship drift)`,
args: [4, 1],
input: [ null, null, null, "p1", "p2", null, null, null, null ],
expect: [ null, null, null, null, "p2", "p1", null, null, null ]
},
{
name: `should move the ship correctly when it needs to drift (multi-ship drift)`,
args: [4, 1],
input: [ null, null, null, "p1", "p2", null, "p3", null, null ],
expect: [ null, null, null, "p3", "p2", "p1", null, null, null ]
},
{
name: `should move the ship correctly when it needs to drift (multi-ship drift unbalanced)`,
args: [4, 1],
input: [ null, null, null, "p1", "p2", null, "p3", "p4", null ],
expect: [ null, null, null, "p3", "p2", "p1", "p4", null, null ]
},
{
name: `should move the ship correctly when it needs to cross-over`,
args: [4, 1],
input: [ null, null, null, "p1", "p2", "p3", null, null, null ],
expect: [ null, null, null, "p3", "p2", "p1", null, null, null ]
},
];
for (const test of tests) {
it(test.name, () => {
let b = [...test.input];
tractorBeam(b, ...test.args);
expect(b).to.have.length(test.expect.length);
expect(stringifyBoard(b)).to.equal(stringifyBoard(test.expect));
});
};
});
describe.only("The moveShip function [non-repulsor tests]", () => {
const tests: moveTest[] = [
{
name: `should move forward when no other ships present`,
args: [2],
input: [ null, null, "p1", null, null, null, null ],
expect: [ null, null, null, "p1", null, null, null ],
},
{
name: `should move forwards when closest is ahead of the ship`,
args: [0],
input: [ "p1", null, "p2" ],
expect: [ null, "p1", "p2" ],
},
{
name: `should move backwards when closest is behind the ship`,
args: [2],
input: [ "p2", null, "p1" ],
expect: [ "p2", "p1", null ],
},
{
name: `should drift forwards over a single ship`,
args: [2],
input: [ null, null, "p1", "p2", null, null, null, ],
expect: [ null, null, null, "p2", "p1", null, null, ],
},
{
name: `should drift backwards over a single ship`,
args: [4],
input: [ null, null, null, "p2", "p1", null, null, ],
expect: [ null, null, "p1", "p2", null, null, null, ],
},
{
name: `should drift forwards over multiple ships`,
args: [2],
input: [ null, null, "p1", "p2", "p3", null, null, ],
expect: [ null, null, null, "p2", "p3", "p1", null, ],
},
{
name: `should drift backwards over multiple ships`,
args: [4],
input: [ null, null, "p3", "p2", "p1", null, null, ],
expect: [ null, "p1", "p3", "p2", null, null, null, ],
},
{
name: `shouldn't affect other ships`,
args: [4],
expect: [ null, "p2", null, null, "p1", null, "p3" ],
input: [ null, "p2", null, null, null, "p1", "p3" ],
},
{
name: `should move away from lower the edge going forwards`,
args: [0],
input: [ "p1", null, "p2", null ],
expect: [ null, "p1", "p2", null ],
},
{
name: `should move away from upper the edge going backwards`,
args: [3],
input: [ null, "p2", null, "p1" ],
expect: [ null, "p2", "p1", null ],
},
];
for (const test of tests) {
it(test.name, () => {
let b = [...test.input];
if (test.args.length == 1) {
moveShip(
b,
test.args[0],
{
"magnitude": 1,
"symbol": "Ar",
"name": "Argon",
"type": "movement"
}
);
} else {
moveShip(b, ...test.args);
};
expect(b).to.have.length(test.expect.length);
expect(stringifyBoard(b)).to.equal(stringifyBoard(test.expect));
});
};
});
describe.only("The moveShip function [repulsor tests]", () => {
const tests: moveTest[] = [
{
name: `should move backwards when no other ships present`,
args: [3],
input: [ null, null, null, "p1", null, null, null ],
expect: [ null, null, "p1", null, null, null, null ],
},
];
for (const test of tests) {
it(test.name, () => {
let b = [...test.input];
if (test.args.length == 1) {
moveShip(
b,
test.args[0],
{
"magnitude": -1,
"symbol": "Ol",
"name": "Oliverium",
"type": "movement"
}
);
} else {
moveShip(b, ...test.args);
};
expect(b).to.have.length(test.expect.length);
expect(stringifyBoard(b)).to.equal(stringifyBoard(test.expect));
});
};
})

View file

@ -0,0 +1,83 @@
import { determineDirection } from "./movementDirection";
import { Board, GamePiece } from "../types/GameBoard";
import { FuelCard } from "../types/FuelCard";
/** @internal */
export function tractorBeam(board: Board, origin: number, magnitude: number) {
let delta = 0;
while (true) {
delta++;
let behind = origin - delta;
let ahead = origin + delta;
let negativeNewPosition = behind;
let positiveNewPosition = ahead;
let behindShip = board[behind];
let aheadShip = board[ahead];
/*
Check for a ship behind the origin, if there is a ship there then pull
it towards the origin (forwards). If it were to collide with another
ship, then drift until it no longer collides.
*/
if (
behind >= 0
&& behindShip != null
) {
let localMagnitude = magnitude;
while (board[behind + localMagnitude] != null) {
localMagnitude++;
};
negativeNewPosition = behind + localMagnitude;
board[behind] = null;
};
/*
Check for a ship ahead of the origin, if there is a ship there then
pull it towards the origin (backwards). If it were to collide with
another ship, then drift until it no longer collides.
*/
if (
ahead < board.length
&& aheadShip
) {
let localMagnitude = magnitude;
while (board[ahead - localMagnitude] != null) {
localMagnitude++;
};
positiveNewPosition = ahead - localMagnitude;
board[ahead] = null;
};
// Simultaneous movement
if (aheadShip) {
board[positiveNewPosition] = aheadShip;
};
if (behindShip) {
board[negativeNewPosition] = behindShip;
};
if (behind < 0 && ahead >= board.length) {
break;
};
};
};
/** @internal */
export function moveShip(board: Board, origin: number, fuel: FuelCard) {
let direction = determineDirection(board, origin);
if (direction == 0) { return };
let newIndex = origin + ( fuel.magnitude * direction );
while (board[newIndex] != null) {
newIndex += direction;
};
board[newIndex] = board[origin];
board[origin] = null;
};
export function processCard(board: Board, shipLocation: number, fuel: FuelCard) {};

View file

@ -0,0 +1,17 @@
import { IColour } from "../types/Colour";
/**
* The colours that can be used by the user to make their ship unique. Each
* player is assigned a random, unused, colour when they join the lobby.
*/
export const colours: IColour[] = [
{ name: "Green", hex: "#00aa00" },
{ name: "Blue", hex: "#0077b6" },
{ name: "Red", hex: "#cb0b0a" },
{ name: "Purple", hex: "#7400b8" },
{ name: "Orange", hex: "#faa307" },
{ name: "Light Green", hex: "#9ef01a" },
{ name: "Magenta", hex: "#b7094c" },
{ name: "Pink", hex: "#f72585" },
{ name: "Yellow", hex: "#f9c80e" },
];

View file

@ -0,0 +1,40 @@
import { IGameOption } from "../types/GameOption";
/** The options that games can support. */
export const gameOptions: IGameOption[] = [
{
name: `No Secrets`,
id: `no-secrets`,
active: false,
hidden: true,
description: `During the mining phase of each round, all cards remain face up, including the piles that are available for choosing from and the cards each player has chosen, stay visible until the Movement Phase begins.`,
},
{
name: `Warp Gate Activated`,
id: `warp-gate-activated`,
active: false,
hidden: false,
description: `With this option enabled, when a spaceship enters the Warp Gate space (#54), it is immediately removed from the game, but the Warp Gate remains open. This allows more spaceships to enter the gate before the end of the round, any additional spaceships that enter the Warp Gate before the round ends share in the victory because they escaped from the black hole.`,
},
{
name: `Chaos Theory`,
id: `chaos-theory`,
active: false,
hidden: false,
description: `The first card played during each movement phase is chosen randomly. You still have the opportunity to use your Emergency Stop if you have it available.`,
},
{
name: `Hardcore`,
id: `hardcore`,
active: false,
hidden: false,
description: `When Hardcore Mode is enabled, you can only use the Emergency Stop <b>one time</b> for the entire game!`,
},
{
name: `Fate`,
id: `fate`,
active: false,
hidden: true,
description: `Every player must decide on the order that their cards will be played in prior to the first movement happening in the round. Then the cards will be resolved as normal, but with the player not being able to dynamically react. The Emergency Stop is able to be used as normal.`,
},
];

View file

@ -0,0 +1,21 @@
import { ISpaceship } from "../types/Spaceship";
/**
* The spaceships that are supported in the system, the "id" property **must**
* exist in the spaceship.svelte file as an icon. The first icon in this list
* will be used by the server to assign new players' ship icon.
*/
export const spaceships: ISpaceship[] = [
{
name: "Space Shuttle",
id: "space-shuttle"
},
{
name: "Rocket",
id: "rocket"
},
{
name: "Sailboat",
id: "sailboat"
},
];

View file

@ -0,0 +1,34 @@
export enum Status {
/** The request was successful */
Success = 200,
/**
* The request contains malformed data and cannot be processed.
*/
BadRequest = 400,
/**
* Some data is out of date and needs to be updated before another request
* can be made. Check the "message" for additional information.
*/
OutOfDate = 401,
/**
* The resource that is being requested cannot be accessed at the current
* point in time for some reason. Check the "message" for additional details.
*/
Forbidden = 403,
/**
* Something in the request couldn't be found. Check the "message" for
* additional information.
*/
NotFound = 404,
/**
* Something went wrong in the server that we don't have a more specific
* error for. Check the "message" for additional details. This may be
* fixable by the client who made the request, it may not be.
*/
UnknownError = 500,
}

31
common/src/index.ts Normal file
View file

@ -0,0 +1,31 @@
// Enums
export { Status } from "./enums/Status";
// Algorithms
export * from "./algorithms/processCard";
export * from "./algorithms/movementDirection";
// Data
export * from "./data/colours";
export * from "./data/spaceships";
export * from "./data/game_options";
// Data Structures
export * from "./types/Colour";
export * from "./types/FuelCard";
export * from "./types/GameBoard";
export * from "./types/Spaceship";
export * from "./types/PlayerData";
export * from "./types/GameOption";
// Server-Client Communications
export * from "./types/ServerResponse";
export * from "./types/events/server_info";
// Lobby events
export * from "./types/events/lobby/info";
export * from "./types/events/lobby/create";
export * from "./types/events/lobby/delete";
export * from "./types/events/lobby/players/join";
export * from "./types/events/lobby/players/leave";
export * from "./types/events/lobby/players/update";

View file

@ -0,0 +1,4 @@
export interface IColour {
name: string;
hex: string;
}

View file

@ -0,0 +1,6 @@
export interface FuelCard {
magnitude: number;
symbol: string;
name: string;
type: "movement" | "stationary";
}

View file

@ -0,0 +1,7 @@
export type GamePiece = string | null;
export interface Board {
singularity: GamePiece[];
path: GamePiece[];
warpgate: GamePiece[];
}

View file

@ -0,0 +1,26 @@
/**
* The object structure that represents an option that can be applied to the
* game's logic
*/
export interface IGameOption {
/** The user-friendly name of the option */
name: string;
/**
* The ID that is used in the code to change what options are active in the
* game
*/
id: string;
/** Whether or not the host has activated this option */
active: boolean;
/**
* Whether or not the option should be displayed to the users in the lobby
*/
hidden: boolean;
/** The description of the option that is put into the info modal. */
description: string;
}

View file

@ -0,0 +1,30 @@
import { IColour } from "./Colour";
import { FuelCard } from "./FuelCard";
import { ISpaceship } from "./Spaceship";
/**
* The data structure that represents a Player in the game
*/
export interface PlayerData {
/** The name the player chose while joining the lobby */
name: string;
/**
* The colour that the player's spaceship will be in the game. This must be
* unique among all players, this uniqueness is enforced by the server.
*/
colour: IColour;
/** The spaceship icon that the player will appear as in-game. */
ship: ISpaceship;
/** Whether or not the player is the host of the game. */
host: boolean;
/** The unique ID of the player */
id: string;
/** The fuel that the player is going to be playing this turn */
fuel?: FuelCard
}

View file

@ -0,0 +1,6 @@
/** The data that is common to most requests to the server. */
export interface ServerRequest {
/** The ID for the game that the action is being taken on. */
game_code: string;
}

View file

@ -0,0 +1,10 @@
import { Status } from "../enums/Status";
export interface ServerResponse {
/** The state indicator for responses from the server */
status: Status;
/** Additional information that is provided by the server */
message?: string;
}

View file

@ -0,0 +1,4 @@
export interface ISpaceship {
name: string;
id: string;
}

View file

@ -0,0 +1,5 @@
/** The data required for the `lobby.create` event to function correctly. */
export interface ICreateLobby {
/** The name of the user who is creating the lobby */
name: string;
}

View file

@ -0,0 +1,4 @@
import { ServerRequest } from "../../ServerRequest";
/** The data required by the server in order to delete the lobby */
export interface IDeleteLobby extends ServerRequest {}

View file

@ -0,0 +1,13 @@
import { PlayerData } from "../../PlayerData";
import { ServerResponse } from "../../ServerResponse";
/** The data sent as a response to `lobby.info`. */
export interface ILobbyInfo extends ServerResponse {
/**
* The lobby ID that can be used by clients to connect to the server with.
*/
game_code?: string;
/** The data associated with each player in the game. */
players?: PlayerData[];
}

View file

@ -0,0 +1,8 @@
import { ServerRequest } from "../../../ServerRequest";
/** The data required for a player to join a game lobby */
export interface IJoinLobby extends ServerRequest {
/** The name that the user wants to go by in the game */
name: string;
}

View file

@ -0,0 +1,8 @@
import { ServerRequest } from "../../../ServerRequest";
/** The required data for leaving a game, or kicking a player from a game. */
export interface ILeaveLobby extends ServerRequest {
/** The name of the player being removed from the lobby */
name: string;
}

View file

@ -0,0 +1,25 @@
import { IColour } from "../../../Colour";
import { ServerRequest } from "../../../ServerRequest";
import { ISpaceship } from "../../../Spaceship";
/** The required data for updating a player's information */
export interface IUpdatePlayer extends ServerRequest {
/** The name of the player that is being updated. */
name: string;
/** The player's spaceship design */
design: {
/**
* The icon used for the player's space ship. This can be the same for
* multiple players.
*/
ship: ISpaceship;
/**
* The colour of the user's ship. This is unique across all players,
* meaning all players must have a different colour.
*/
colour: IColour;
};
}

View file

@ -0,0 +1,10 @@
import { ServerResponse } from "../ServerResponse";
/** The data sent as a response to `server.info`. */
export interface IServerInfo extends ServerResponse {
/** The version that the server is currently running. */
version?: string;
/** The count of how many games are currently stored as active in the Database */
game_count?: number;
}

80
common/tsconfig.json Normal file
View file

@ -0,0 +1,80 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
// "lib": [], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
"declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "./dist", /* Redirect output structure to the directory. */
"rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
/* Advanced Options */
"skipLibCheck": true, /* Skip type checking of declaration files. */
"forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */
"stripInternal": true
},
"exclude": [
"src/**/*.spec.ts",
"esm/**/*",
"cjs/**/*",
"node_modules/**/*"
]
}

161
docs/events.md Normal file
View file

@ -0,0 +1,161 @@
# Events
Every event in this document has two forms, a request and a response, the event
type can be differentiated by the event prefix, `req` is an event going to the
server, and `res` is a response coming from the server to the client. Some events
may only have one of these types, if this is the case, it will be denoted in the
relevant section.
**Important**: Events with the `Broadcasted` event type, must be listened to
even if you don't send any `Request` events of that type. This is because other
clients connected to the server may trigger the server in such a way that the
event response gets sent to multiple clients. **Broadcasted events will use the**
`res` **event type prefix.**
Most request payload types inherit from `ServerRequest`, and all response
payloads inherit from the `ServerResponse` type.
---
## `error`
The event that is sent to clients when they cause a significant error to happen
in the server. When this is sent, the status property will never be set to a
value in the success range.
### Supported Event Types
| Request | Response | Broadcasted
| ------- | -------- | -----------
| No | Yes | No
### Payloads
Request Payload: N/A
Response Payload: `ServerResponse`
---
## `server.info`
Retrieves information about the server that the client is currently connected
to.
### Supported Event Types
| Request | Response | Broadcasted
| ------- | -------- | -----------
| Yes | Yes | No
### Payloads
Request Payload: N/A
Response Payload: `IServerInfo`
---
## `lobby.info`
Retrieves information about the lobby. In order for this event to return without
error, the client requesting the information **must** be in the lobby already.
This event is broadcasted to clients who are already in the lobby when the
player list or a player's design gets updated.
### Supported Event Types
| Request | Response | Broadcasted
| ------- | -------- | -----------
| Yes | Yes | Yes
### Payloads
Request Payload: `IGetLobbyInfo`
Response Payload: `ILobbyInfo`
---
## `lobby.create`
Creates a game lobby that allows for online play.
### Supported Event Types
| Request | Response | Broadcasted
| ------- | -------- | -----------
| Yes | Yes | No
### Payloads
Request Payload: `ICreateLobby`
Response Payload: `ILobbyInfo`
---
## `lobby.delete`
Deletes the game lobby, clearing all players from the lobby and preventing
others from joining the lobby. This endpoint errors whenever a client that isn't
the host tries to call the endpoint.
### Supported Event Types
| Request | Response | Broadcasted
| ------- | -------- | -----------
| Yes | Yes | Yes
### Payloads
Request Payload: `IDeleteLobby`
Response Payload: `ServerResponse`
---
## `lobby.players.join`
Allows a player to join a lobby based on the lobby identifier.
### Supported Event Types
| Request | Response | Broadcasted
| ------- | -------- | -----------
| Yes | Yes | No, causes `lobby.info` broadcast
### Payloads
Request Payload: `IJoinLobby`
Response Payload: `ILobbyInfo` (or `IGameState` if the game is in progress)
---
## `lobby.players.leave`
This allows non-host players to leave the lobby without destroying it entirely.
This endpoint errors when the client that created the lobby attempts to use the
endpoint.
### Supported Event Types
| Request | Response | Broadcasted
| ------- | -------- | -----------
| Yes | Yes | No, causes `lobby.info` broadcast
### Payloads
Request Payload: `ILeaveLobby`
Response Payload: `ServerResponse`
---
## `lobby.players.update`
Allows the player to update their own information, this is primarily used for
updating the ship design.
### Supported Event Types
| Request | Response | Broadcasted
| ------- | -------- | -----------
| Yes | Yes | No, causes `lobby.info` broadcast
### Payloads
Request Payload: `IUpdatePlayer`
Response Payload: `ServerResponse`
---
## `game.state`
Provides information about the current game state, used to help keep clients in
the correct state.
### Supported Event Types
| Request | Response | Broadcasted
| ------- | -------- | -----------
| No | Yes | Yes
### Payloads
Request Payload: N/A
Response Payload: `IGameState`

27
docs/raw_events_list.md Normal file
View file

@ -0,0 +1,27 @@
# Lobby management
Lobby info: `lobby.info`
Create lobby: `lobby.create`
Delete lobby: `lobby.delete`
Join lobby: `lobby.players.join`
leave lobby: `lobby.players.leave`
kick player from lobby: `lobby.players.leave`
change lobby option(s): `lobby.options`
update player data (including ship design): `lobby.players.update`
# Informational
server info: `server.info`
# In-Game
current game state: `game.state`
alerting the players who's turn it is: `game.turn`
distributing card piles: `game.mining.piles`
picking a card pile: `game.mining.choose`
asking if player wants to e-stop & response: `game.movement.use-estop`
telling clients to move a ship: `game.movement.ship`
round end: `game.round.next`
game finished: `game.finished` (includes what player(s) won)

19
hex.py Normal file
View file

@ -0,0 +1,19 @@
from math import cos, sin, radians
h = int(input("Total hexagon height allowed: "))
canvas_size = int(input("SVG size: "))
move_by = abs(canvas_size - h) / 2
m = h // 2
p = {
"x": 0,
"y": m
}
print("\nPoints: ")
for _ in range(6):
print(f"{round(p['x'] + m + move_by)},{round(p['y'] + m + move_by)}", end=" ")
x, y = p["x"], p["y"]
p["x"] = round(x*cos(radians(60)) - y*sin(radians(60)), 5)
p["y"] = round(x*sin(radians(60)) + y*cos(radians(60)), 5)
print()

158
server/cards.json Normal file
View file

@ -0,0 +1,158 @@
[
{
"magnitude": 1,
"symbol": "Ar",
"name": "Argon",
"type": "movement"
},
{
"magnitude": 2,
"symbol": "B",
"name": "Boron",
"type": "movement"
},
{
"magnitude": 3,
"symbol": "C",
"name": "Carbon",
"type": "movement"
},
{
"magnitude": 5,
"symbol": "Dy",
"name": "Dysprosium",
"type": "movement"
},
{
"magnitude": 2,
"symbol": "Es",
"name": "Einsteinium",
"type": "movement"
},
{
"magnitude": 6,
"symbol": "F",
"name": "Fluorine",
"type": "movement"
},
{
"magnitude": 5,
"symbol": "Ga",
"name": "Gallium",
"type": "movement"
},
{
"magnitude": 4,
"symbol": "H",
"name": "Hydrogen",
"type": "movement"
},
{
"magnitude": 6,
"symbol": "Ir",
"name": "Iridium",
"type": "movement"
},
{
"magnitude": 2,
"symbol": "Jo",
"name": "Jodium",
"type": "stationary"
},
{
"magnitude": -2,
"symbol": "Kr",
"name": "Krypton",
"type": "movement"
},
{
"magnitude": 4,
"symbol": "Li",
"name": "Lithium",
"type": "movement"
},
{
"magnitude": 10,
"symbol": "Mg",
"name": "Magnesium",
"type": "movement"
},
{
"magnitude": -6,
"symbol": "Ne",
"name": "Neon",
"type": "movement"
},
{
"magnitude": 7,
"symbol": "O",
"name": "Oxygen",
"type": "movement"
},
{
"magnitude": 5,
"symbol": "Pu",
"name": "Plutonium",
"type": "movement"
},
{
"magnitude": 3,
"symbol": "Qt",
"name": "Sydnium",
"type": "stationary"
},
{
"magnitude": 9,
"symbol": "Ra",
"name": "Radium",
"type": "movement"
},
{
"magnitude": 9,
"symbol": "Si",
"name": "Silicon",
"type": "movement"
},
{
"magnitude": 2,
"symbol": "Th",
"name": "Thorium",
"type": "movement"
},
{
"magnitude": -5,
"symbol": "U",
"name": "Uranium",
"type": "movement"
},
{
"magnitude": 7,
"symbol": "V",
"name": "Vanadium",
"type": "movement"
},
{
"magnitude": 8,
"symbol": "W",
"name": "Tungsten",
"type": "movement"
},
{
"magnitude": -3,
"symbol": "Xe",
"name": "Xenon",
"type": "movement"
},
{
"magnitude": 8,
"symbol": "Y",
"name": "Yttrium",
"type": "movement"
},
{
"magnitude": 7,
"symbol": "Zr",
"name": "Zirconium",
"type": "movement"
}
]

50
server/events.md Normal file
View file

@ -0,0 +1,50 @@
This is a list of all events that the server must support in order at the bare
minimum for the front-end to work with getting all the data that it needs.
---
`get.game` (incoming)
Get's a game's current information, this includes player list, and options
`game.info` (outgoing)
The game data that was requested.
---
`delete.game` (incoming)
Deletes a game from the system, kicking all the players from the lobby.
`game.deleted` (outgoing)
Alert for all the players that the game got deleted and to reset all state.
---
`get.system.colours` (incoming)
Retrieves a list of all colours that are currently supported for players to pick
from for their spaceship.
`get.system.icons` (incoming)
Retrieves a list of all the supported icons that players can pick from for their
spaceship.
`system.colours` (outgoing)
The list of all colours that the players can pick from.
`system.icons` (outgoing)
The list of all the icons that players can pick from.
---
`put.player.design` (incoming)
Updates a player's design that will be used in the game for their spaceship.
`game.player.update` (outgoing)
Tells everyone in the game that one of the players has updated something about
their data and to refresh the local data to keep it up to date.
---
`put.game.option` (incoming)
Updates the game's options that affect how the game flow works.
---

19
server/makefile Normal file
View file

@ -0,0 +1,19 @@
.PHONY: build dev prod test
build:
tsc
dev: build
NODE_ENV=development node dist/main.js
prod: build
NODE_ENV=production node dist/main.js
rund:
NODE_ENV=development node dist/main.js
run:
NODE_ENV=production node dist/main.js
test:
node_modules/mocha/bin/_mocha -r ts-node/register tests/**/*.spec.ts

38
server/package.json Normal file
View file

@ -0,0 +1,38 @@
{
"name": "gravwell-online-server",
"version": "1.0.0",
"description": "",
"main": "dist/main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "tsc",
"start": "node dist/main.js"
},
"author": "Oliver Akins",
"license": "UNLICENSED",
"dependencies": {
"common": "link:../common",
"glob": "^7.2.0",
"joi": "^17.6.0",
"module-alias": "^2.2.2",
"socket.io": "^4.4.0",
"toml": "^3.0.0",
"tslog": "^3.3.1",
"uuid": "^8.3.2"
},
"devDependencies": {
"@types/chai": "^4.3.1",
"@types/glob": "^7.2.0",
"@types/mocha": "^9.1.1",
"@types/module-alias": "^2.0.1",
"@types/node": "^18.0.0",
"@types/uuid": "^8.3.4",
"chai": "^4.3.6",
"mocha": "^10.0.0",
"ts-node": "^10.8.1",
"typescript": "^4.7.4"
},
"_moduleAliases": {
"@": "./dist"
}
}

962
server/pnpm-lock.yaml generated Normal file
View file

@ -0,0 +1,962 @@
lockfileVersion: 5.4
specifiers:
'@types/chai': ^4.3.1
'@types/glob': ^7.2.0
'@types/mocha': ^9.1.1
'@types/module-alias': ^2.0.1
'@types/node': ^18.0.0
'@types/uuid': ^8.3.4
chai: ^4.3.6
common: link:../common
glob: ^7.2.0
joi: ^17.6.0
mocha: ^10.0.0
module-alias: ^2.2.2
socket.io: ^4.4.0
toml: ^3.0.0
ts-node: ^10.8.1
tslog: ^3.3.1
typescript: ^4.7.4
uuid: ^8.3.2
dependencies:
common: link:../common/cjs
glob: 7.2.0
joi: 17.6.0
module-alias: 2.2.2
socket.io: 4.4.0
toml: 3.0.0
tslog: 3.3.1
uuid: 8.3.2
devDependencies:
'@types/chai': 4.3.1
'@types/glob': 7.2.0
'@types/mocha': 9.1.1
'@types/module-alias': 2.0.1
'@types/node': 18.0.0
'@types/uuid': 8.3.4
chai: 4.3.6
mocha: 10.0.0
ts-node: 10.8.1_qiyc72axg2v44xl4yovan2v55u
typescript: 4.7.4
packages:
/@cspotcode/source-map-support/0.8.1:
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
engines: {node: '>=12'}
dependencies:
'@jridgewell/trace-mapping': 0.3.9
dev: true
/@hapi/hoek/9.3.0:
resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==}
dev: false
/@hapi/topo/5.1.0:
resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==}
dependencies:
'@hapi/hoek': 9.3.0
dev: false
/@jridgewell/resolve-uri/3.0.7:
resolution: {integrity: sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA==}
engines: {node: '>=6.0.0'}
dev: true
/@jridgewell/sourcemap-codec/1.4.13:
resolution: {integrity: sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w==}
dev: true
/@jridgewell/trace-mapping/0.3.9:
resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
dependencies:
'@jridgewell/resolve-uri': 3.0.7
'@jridgewell/sourcemap-codec': 1.4.13
dev: true
/@sideway/address/4.1.4:
resolution: {integrity: sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==}
dependencies:
'@hapi/hoek': 9.3.0
dev: false
/@sideway/formula/3.0.0:
resolution: {integrity: sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg==}
dev: false
/@sideway/pinpoint/2.0.0:
resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==}
dev: false
/@tsconfig/node10/1.0.9:
resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==}
dev: true
/@tsconfig/node12/1.0.11:
resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==}
dev: true
/@tsconfig/node14/1.0.3:
resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==}
dev: true
/@tsconfig/node16/1.0.3:
resolution: {integrity: sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==}
dev: true
/@types/chai/4.3.1:
resolution: {integrity: sha512-/zPMqDkzSZ8t3VtxOa4KPq7uzzW978M9Tvh+j7GHKuo6k6GTLxPJ4J5gE5cjfJ26pnXst0N5Hax8Sr0T2Mi9zQ==}
dev: true
/@types/component-emitter/1.2.11:
resolution: {integrity: sha512-SRXjM+tfsSlA9VuG8hGO2nft2p8zjXCK1VcC6N4NXbBbYbSia9kzCChYQajIjzIqOOOuh5Ock6MmV2oux4jDZQ==}
dev: false
/@types/cookie/0.4.1:
resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==}
dev: false
/@types/cors/2.8.12:
resolution: {integrity: sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==}
dev: false
/@types/glob/7.2.0:
resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==}
dependencies:
'@types/minimatch': 3.0.5
'@types/node': 18.0.0
dev: true
/@types/minimatch/3.0.5:
resolution: {integrity: sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==}
dev: true
/@types/mocha/9.1.1:
resolution: {integrity: sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==}
dev: true
/@types/module-alias/2.0.1:
resolution: {integrity: sha512-DN/CCT1HQG6HquBNJdLkvV+4v5l/oEuwOHUPLxI+Eub0NED+lk0YUfba04WGH90EINiUrNgClkNnwGmbICeWMQ==}
dev: true
/@types/node/18.0.0:
resolution: {integrity: sha512-cHlGmko4gWLVI27cGJntjs/Sj8th9aYwplmZFwmmgYQQvL5NUsgVJG7OddLvNfLqYS31KFN0s3qlaD9qCaxACA==}
/@types/uuid/8.3.4:
resolution: {integrity: sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==}
dev: true
/@ungap/promise-all-settled/1.1.2:
resolution: {integrity: sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==}
dev: true
/accepts/1.3.7:
resolution: {integrity: sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==}
engines: {node: '>= 0.6'}
dependencies:
mime-types: 2.1.34
negotiator: 0.6.2
dev: false
/acorn-walk/8.2.0:
resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==}
engines: {node: '>=0.4.0'}
dev: true
/acorn/8.7.1:
resolution: {integrity: sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==}
engines: {node: '>=0.4.0'}
hasBin: true
dev: true
/ansi-colors/4.1.1:
resolution: {integrity: sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==}
engines: {node: '>=6'}
dev: true
/ansi-regex/5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
dev: true
/ansi-styles/4.3.0:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
dependencies:
color-convert: 2.0.1
dev: true
/anymatch/3.1.2:
resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==}
engines: {node: '>= 8'}
dependencies:
normalize-path: 3.0.0
picomatch: 2.3.1
dev: true
/arg/4.1.3:
resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
dev: true
/argparse/2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
dev: true
/assertion-error/1.1.0:
resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
dev: true
/balanced-match/1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
/base64-arraybuffer/1.0.1:
resolution: {integrity: sha512-vFIUq7FdLtjZMhATwDul5RZWv2jpXQ09Pd6jcVEOvIsqCWTRFD/ONHNfyOS8dA/Ippi5dsIgpyKWKZaAKZltbA==}
engines: {node: '>= 0.6.0'}
dev: false
/base64id/2.0.0:
resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==}
engines: {node: ^4.5.0 || >= 5.9}
dev: false
/binary-extensions/2.2.0:
resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
engines: {node: '>=8'}
dev: true
/brace-expansion/1.1.11:
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
dependencies:
balanced-match: 1.0.2
concat-map: 0.0.1
/brace-expansion/2.0.1:
resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
dependencies:
balanced-match: 1.0.2
dev: true
/braces/3.0.2:
resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
engines: {node: '>=8'}
dependencies:
fill-range: 7.0.1
dev: true
/browser-stdout/1.3.1:
resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==}
dev: true
/buffer-from/1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
dev: false
/camelcase/6.3.0:
resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
engines: {node: '>=10'}
dev: true
/chai/4.3.6:
resolution: {integrity: sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==}
engines: {node: '>=4'}
dependencies:
assertion-error: 1.1.0
check-error: 1.0.2
deep-eql: 3.0.1
get-func-name: 2.0.0
loupe: 2.3.4
pathval: 1.1.1
type-detect: 4.0.8
dev: true
/chalk/4.1.2:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
dependencies:
ansi-styles: 4.3.0
supports-color: 7.2.0
dev: true
/check-error/1.0.2:
resolution: {integrity: sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==}
dev: true
/chokidar/3.5.3:
resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
engines: {node: '>= 8.10.0'}
dependencies:
anymatch: 3.1.2
braces: 3.0.2
glob-parent: 5.1.2
is-binary-path: 2.1.0
is-glob: 4.0.3
normalize-path: 3.0.0
readdirp: 3.6.0
optionalDependencies:
fsevents: 2.3.2
dev: true
/cliui/7.0.4:
resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==}
dependencies:
string-width: 4.2.3
strip-ansi: 6.0.1
wrap-ansi: 7.0.0
dev: true
/color-convert/2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
dependencies:
color-name: 1.1.4
dev: true
/color-name/1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
dev: true
/component-emitter/1.3.0:
resolution: {integrity: sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==}
dev: false
/concat-map/0.0.1:
resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=}
/cookie/0.4.1:
resolution: {integrity: sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==}
engines: {node: '>= 0.6'}
dev: false
/cors/2.8.5:
resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==}
engines: {node: '>= 0.10'}
dependencies:
object-assign: 4.1.1
vary: 1.1.2
dev: false
/create-require/1.1.1:
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
dev: true
/debug/4.3.3:
resolution: {integrity: sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
dependencies:
ms: 2.1.2
dev: false
/debug/4.3.4_supports-color@8.1.1:
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
dependencies:
ms: 2.1.2
supports-color: 8.1.1
dev: true
/decamelize/4.0.0:
resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==}
engines: {node: '>=10'}
dev: true
/deep-eql/3.0.1:
resolution: {integrity: sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==}
engines: {node: '>=0.12'}
dependencies:
type-detect: 4.0.8
dev: true
/diff/4.0.2:
resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
engines: {node: '>=0.3.1'}
dev: true
/diff/5.0.0:
resolution: {integrity: sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==}
engines: {node: '>=0.3.1'}
dev: true
/emoji-regex/8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
dev: true
/engine.io-parser/5.0.2:
resolution: {integrity: sha512-wuiO7qO/OEkPJSFueuATIXtrxF7/6GTbAO9QLv7nnbjwZ5tYhLm9zxvLwxstRs0dcT0KUlWTjtIOs1T86jt12g==}
engines: {node: '>=10.0.0'}
dependencies:
base64-arraybuffer: 1.0.1
dev: false
/engine.io/6.1.0:
resolution: {integrity: sha512-ErhZOVu2xweCjEfYcTdkCnEYUiZgkAcBBAhW4jbIvNG8SLU3orAqoJCiytZjYF7eTpVmmCrLDjLIEaPlUAs1uw==}
engines: {node: '>=10.0.0'}
dependencies:
'@types/cookie': 0.4.1
'@types/cors': 2.8.12
'@types/node': 18.0.0
accepts: 1.3.7
base64id: 2.0.0
cookie: 0.4.1
cors: 2.8.5
debug: 4.3.3
engine.io-parser: 5.0.2
ws: 8.2.3
transitivePeerDependencies:
- bufferutil
- supports-color
- utf-8-validate
dev: false
/escalade/3.1.1:
resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==}
engines: {node: '>=6'}
dev: true
/escape-string-regexp/4.0.0:
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
engines: {node: '>=10'}
dev: true
/fill-range/7.0.1:
resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
engines: {node: '>=8'}
dependencies:
to-regex-range: 5.0.1
dev: true
/find-up/5.0.0:
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
engines: {node: '>=10'}
dependencies:
locate-path: 6.0.0
path-exists: 4.0.0
dev: true
/flat/5.0.2:
resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==}
hasBin: true
dev: true
/fs.realpath/1.0.0:
resolution: {integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8=}
/fsevents/2.3.2:
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
requiresBuild: true
dev: true
optional: true
/get-caller-file/2.0.5:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
engines: {node: 6.* || 8.* || >= 10.*}
dev: true
/get-func-name/2.0.0:
resolution: {integrity: sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==}
dev: true
/glob-parent/5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'}
dependencies:
is-glob: 4.0.3
dev: true
/glob/7.2.0:
resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==}
dependencies:
fs.realpath: 1.0.0
inflight: 1.0.6
inherits: 2.0.4
minimatch: 3.0.4
once: 1.4.0
path-is-absolute: 1.0.1
/has-flag/4.0.0:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
dev: true
/he/1.2.0:
resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
hasBin: true
dev: true
/inflight/1.0.6:
resolution: {integrity: sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=}
dependencies:
once: 1.4.0
wrappy: 1.0.2
/inherits/2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
/is-binary-path/2.1.0:
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
engines: {node: '>=8'}
dependencies:
binary-extensions: 2.2.0
dev: true
/is-extglob/2.1.1:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'}
dev: true
/is-fullwidth-code-point/3.0.0:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
engines: {node: '>=8'}
dev: true
/is-glob/4.0.3:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'}
dependencies:
is-extglob: 2.1.1
dev: true
/is-number/7.0.0:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'}
dev: true
/is-plain-obj/2.1.0:
resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==}
engines: {node: '>=8'}
dev: true
/is-unicode-supported/0.1.0:
resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==}
engines: {node: '>=10'}
dev: true
/joi/17.6.0:
resolution: {integrity: sha512-OX5dG6DTbcr/kbMFj0KGYxuew69HPcAE3K/sZpEV2nP6e/j/C0HV+HNiBPCASxdx5T7DMoa0s8UeHWMnb6n2zw==}
dependencies:
'@hapi/hoek': 9.3.0
'@hapi/topo': 5.1.0
'@sideway/address': 4.1.4
'@sideway/formula': 3.0.0
'@sideway/pinpoint': 2.0.0
dev: false
/js-yaml/4.1.0:
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
hasBin: true
dependencies:
argparse: 2.0.1
dev: true
/locate-path/6.0.0:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'}
dependencies:
p-locate: 5.0.0
dev: true
/log-symbols/4.1.0:
resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==}
engines: {node: '>=10'}
dependencies:
chalk: 4.1.2
is-unicode-supported: 0.1.0
dev: true
/loupe/2.3.4:
resolution: {integrity: sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==}
dependencies:
get-func-name: 2.0.0
dev: true
/make-error/1.3.6:
resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
dev: true
/mime-db/1.51.0:
resolution: {integrity: sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==}
engines: {node: '>= 0.6'}
dev: false
/mime-types/2.1.34:
resolution: {integrity: sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==}
engines: {node: '>= 0.6'}
dependencies:
mime-db: 1.51.0
dev: false
/minimatch/3.0.4:
resolution: {integrity: sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==}
dependencies:
brace-expansion: 1.1.11
/minimatch/5.0.1:
resolution: {integrity: sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==}
engines: {node: '>=10'}
dependencies:
brace-expansion: 2.0.1
dev: true
/mocha/10.0.0:
resolution: {integrity: sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA==}
engines: {node: '>= 14.0.0'}
hasBin: true
dependencies:
'@ungap/promise-all-settled': 1.1.2
ansi-colors: 4.1.1
browser-stdout: 1.3.1
chokidar: 3.5.3
debug: 4.3.4_supports-color@8.1.1
diff: 5.0.0
escape-string-regexp: 4.0.0
find-up: 5.0.0
glob: 7.2.0
he: 1.2.0
js-yaml: 4.1.0
log-symbols: 4.1.0
minimatch: 5.0.1
ms: 2.1.3
nanoid: 3.3.3
serialize-javascript: 6.0.0
strip-json-comments: 3.1.1
supports-color: 8.1.1
workerpool: 6.2.1
yargs: 16.2.0
yargs-parser: 20.2.4
yargs-unparser: 2.0.0
dev: true
/module-alias/2.2.2:
resolution: {integrity: sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==}
dev: false
/ms/2.1.2:
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
/ms/2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
dev: true
/nanoid/3.3.3:
resolution: {integrity: sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
dev: true
/negotiator/0.6.2:
resolution: {integrity: sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==}
engines: {node: '>= 0.6'}
dev: false
/normalize-path/3.0.0:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'}
dev: true
/object-assign/4.1.1:
resolution: {integrity: sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=}
engines: {node: '>=0.10.0'}
dev: false
/once/1.4.0:
resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=}
dependencies:
wrappy: 1.0.2
/p-limit/3.1.0:
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
engines: {node: '>=10'}
dependencies:
yocto-queue: 0.1.0
dev: true
/p-locate/5.0.0:
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
engines: {node: '>=10'}
dependencies:
p-limit: 3.1.0
dev: true
/path-exists/4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'}
dev: true
/path-is-absolute/1.0.1:
resolution: {integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18=}
engines: {node: '>=0.10.0'}
/pathval/1.1.1:
resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}
dev: true
/picomatch/2.3.1:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'}
dev: true
/randombytes/2.1.0:
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
dependencies:
safe-buffer: 5.2.1
dev: true
/readdirp/3.6.0:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'}
dependencies:
picomatch: 2.3.1
dev: true
/require-directory/2.1.1:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'}
dev: true
/safe-buffer/5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
dev: true
/serialize-javascript/6.0.0:
resolution: {integrity: sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==}
dependencies:
randombytes: 2.1.0
dev: true
/socket.io-adapter/2.3.3:
resolution: {integrity: sha512-Qd/iwn3VskrpNO60BeRyCyr8ZWw9CPZyitW4AQwmRZ8zCiyDiL+znRnWX6tDHXnWn1sJrM1+b6Mn6wEDJJ4aYQ==}
dev: false
/socket.io-parser/4.0.4:
resolution: {integrity: sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==}
engines: {node: '>=10.0.0'}
dependencies:
'@types/component-emitter': 1.2.11
component-emitter: 1.3.0
debug: 4.3.3
transitivePeerDependencies:
- supports-color
dev: false
/socket.io/4.4.0:
resolution: {integrity: sha512-bnpJxswR9ov0Bw6ilhCvO38/1WPtE3eA2dtxi2Iq4/sFebiDJQzgKNYA7AuVVdGW09nrESXd90NbZqtDd9dzRQ==}
engines: {node: '>=10.0.0'}
dependencies:
accepts: 1.3.7
base64id: 2.0.0
debug: 4.3.3
engine.io: 6.1.0
socket.io-adapter: 2.3.3
socket.io-parser: 4.0.4
transitivePeerDependencies:
- bufferutil
- supports-color
- utf-8-validate
dev: false
/source-map-support/0.5.21:
resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
dependencies:
buffer-from: 1.1.2
source-map: 0.6.1
dev: false
/source-map/0.6.1:
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
engines: {node: '>=0.10.0'}
dev: false
/string-width/4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
dependencies:
emoji-regex: 8.0.0
is-fullwidth-code-point: 3.0.0
strip-ansi: 6.0.1
dev: true
/strip-ansi/6.0.1:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'}
dependencies:
ansi-regex: 5.0.1
dev: true
/strip-json-comments/3.1.1:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
dev: true
/supports-color/7.2.0:
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
engines: {node: '>=8'}
dependencies:
has-flag: 4.0.0
dev: true
/supports-color/8.1.1:
resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==}
engines: {node: '>=10'}
dependencies:
has-flag: 4.0.0
dev: true
/to-regex-range/5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
dependencies:
is-number: 7.0.0
dev: true
/toml/3.0.0:
resolution: {integrity: sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==}
dev: false
/ts-node/10.8.1_qiyc72axg2v44xl4yovan2v55u:
resolution: {integrity: sha512-Wwsnao4DQoJsN034wePSg5nZiw4YKXf56mPIAeD6wVmiv+RytNSWqc2f3fKvcUoV+Yn2+yocD71VOfQHbmVX4g==}
hasBin: true
peerDependencies:
'@swc/core': '>=1.2.50'
'@swc/wasm': '>=1.2.50'
'@types/node': '*'
typescript: '>=2.7'
peerDependenciesMeta:
'@swc/core':
optional: true
'@swc/wasm':
optional: true
dependencies:
'@cspotcode/source-map-support': 0.8.1
'@tsconfig/node10': 1.0.9
'@tsconfig/node12': 1.0.11
'@tsconfig/node14': 1.0.3
'@tsconfig/node16': 1.0.3
'@types/node': 18.0.0
acorn: 8.7.1
acorn-walk: 8.2.0
arg: 4.1.3
create-require: 1.1.1
diff: 4.0.2
make-error: 1.3.6
typescript: 4.7.4
v8-compile-cache-lib: 3.0.1
yn: 3.1.1
dev: true
/tslog/3.3.1:
resolution: {integrity: sha512-An3uyXX95uU/X7v5H6G9OKW6ip/gVOpvsERGJ/nR4Or5TP5GwoI9nUjhNWEc8mJOWC7uhPMg2UzkrVDUtadELg==}
engines: {node: '>=10'}
dependencies:
source-map-support: 0.5.21
dev: false
/type-detect/4.0.8:
resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==}
engines: {node: '>=4'}
dev: true
/typescript/4.7.4:
resolution: {integrity: sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==}
engines: {node: '>=4.2.0'}
hasBin: true
dev: true
/uuid/8.3.2:
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
hasBin: true
dev: false
/v8-compile-cache-lib/3.0.1:
resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
dev: true
/vary/1.1.2:
resolution: {integrity: sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=}
engines: {node: '>= 0.8'}
dev: false
/workerpool/6.2.1:
resolution: {integrity: sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==}
dev: true
/wrap-ansi/7.0.0:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
engines: {node: '>=10'}
dependencies:
ansi-styles: 4.3.0
string-width: 4.2.3
strip-ansi: 6.0.1
dev: true
/wrappy/1.0.2:
resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=}
/ws/8.2.3:
resolution: {integrity: sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: ^5.0.2
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
dev: false
/y18n/5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'}
dev: true
/yargs-parser/20.2.4:
resolution: {integrity: sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==}
engines: {node: '>=10'}
dev: true
/yargs-unparser/2.0.0:
resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==}
engines: {node: '>=10'}
dependencies:
camelcase: 6.3.0
decamelize: 4.0.0
flat: 5.0.2
is-plain-obj: 2.1.0
dev: true
/yargs/16.2.0:
resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==}
engines: {node: '>=10'}
dependencies:
cliui: 7.0.4
escalade: 3.1.1
get-caller-file: 2.0.5
require-directory: 2.1.1
string-width: 4.2.3
y18n: 5.0.8
yargs-parser: 20.2.4
dev: true
/yn/3.1.1:
resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
engines: {node: '>=6'}
dev: true
/yocto-queue/0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
dev: true

2
server/src/constants.ts Normal file
View file

@ -0,0 +1,2 @@
/** The server's current version */
export const VERSION = "0.1";

View file

@ -0,0 +1,42 @@
import { Status, ICreateLobby, colours, spaceships } from "common";
import { WebsocketEvent } from "@/types/WebsocketEvent";
import { Player } from "@/objects/Player";
import { Game } from "@/objects/Game";
import { games } from "@/main";
const data: WebsocketEvent = {
name: "req:lobby.create",
handler(_, socket, data: ICreateLobby) {
// Assert that the user's name conforms to a small subset of unicode to
// help prevent weird errors from occuring due to potentially violating
// strings and running code through the name entry.
if (data.name.match(/[^A-Za-z0-9\_\-]/)) {
socket.emit(`res:lobby.create`, {
success: Status.BadRequest,
message: `Can't use special characters in your name, only A-Z, 0-9, underscores, and dashes are allowed.`,
});
};
// Create the game:
let host = new Player(socket, {
name: data.name,
colour: colours[Math.floor(Math.random() * colours.length)],
ship: spaceships[0],
host: true,
});
let game = new Game(host);
game.log.info(`New game created by ${data.name}`);
socket.join(game.id);
socket.emit(`res:lobby.create`, {
status: Status.Success,
game_code: game.id,
players: game.players.map(p => p.json()),
});
games.set(game);
games.cleanup();
},
};
export default data;

View file

@ -0,0 +1,91 @@
import { colours, IJoinLobby, spaceships, Status } from "common";
import { WebsocketEvent } from "@/types/WebsocketEvent";
import { Player } from "@/objects/Player";
import { games, log } from "@/main";
const data: WebsocketEvent = {
name: "req:lobby.players.join",
handler(_, socket, data: IJoinLobby) {
try {
log.debug(`Attempting to join a game`);
let gID = data.game_code;
let name = data.name;
let game = games.get(gID);
// Make sure the lobby exists that the player is attempting to connect to
if (!game) {
socket.emit(`res:lobby.players.join`, {
status: Status.NotFound,
message: `Game could not be found with that game code`,
});
return;
};
// Player name unique-ness check
let sameName = game.players.find(p => p.name == name);
if (sameName) {
socket.emit(`res:lobby.players.join`, {
status: Status.Forbidden,
message: `That name is already taken in the lobby, choose another name!`,
});
return;
};
// Make sure the player isn't trying to join a game part-way through
if (game.status !== `lobby`) {
socket.emit(`res:lobby.players.join`, {
status: Status.Forbidden,
message: `Can't join a game that is in-progress.`,
});
return;
};
// Lobby is full already
if (game.players.length >= 4) {
socket.emit(`res:lobby.players.join`, {
status: Status.Forbidden,
message: `Can't join a game that has all 4 spots taken already.`,
});
return;
};
let usedColours = game.players.map(p => p.colour);
let newPlayerColour = null;
do {
newPlayerColour = colours[Math.floor(Math.random() * colours.length)];
} while (usedColours.includes(newPlayerColour));
let player = new Player(socket, {
name,
colour: newPlayerColour,
ship: spaceships[0],
host: false,
});
game.players.push(player);
game.log.info(`${name} joined the game`);
socket.join(game.id);
let playerData = game.players.map(p => p.json());
socket.emit(`res:lobby.players.join`, {
status: Status.Success,
game_code: game.id,
players: playerData,
});
socket.to(game.id).emit(`res:lobby.info`, {
status: Status.Success,
players: playerData,
});
} catch (err) {
socket.emit(`res:error`, {
status: Status.UnknownError,
message: err.message,
});
};
},
};
export default data;

11
server/src/events/ping.ts Normal file
View file

@ -0,0 +1,11 @@
import { WebsocketEvent } from "@/types/WebsocketEvent";
import { log } from "@/main";
const data: WebsocketEvent = {
name: "req:ping",
handler(_, socket) {
log.debug(`Server is being pinged`);
socket.emit(`res:ping`);
},
};
export default data;

View file

@ -0,0 +1,21 @@
import { WebsocketEvent } from "@/types/WebsocketEvent";
import { VERSION } from "@/constants";
import { games, log } from "@/main";
import { Status } from "common";
const data: WebsocketEvent = {
name: "req:server.info",
handler(_, socket) {
log.debug(`Getting server information`)
socket.emit(
`res:server.info`,
{
status: Status.Success,
version: VERSION,
game_count: games.count,
}
);
},
};
export default data;

23
server/src/main.ts Normal file
View file

@ -0,0 +1,23 @@
// Filepath alias resolution
import "module-alias/register";
import startWebsocketServer from "./websocket";
import { loadConfig } from "./utils/config";
import { GameDB } from "./objects/GameDB";
import { Logger } from "tslog";
export const config: IConfig = loadConfig();
// Define the logger
export const log = new Logger({
displayFilePath: "hidden",
displayFunctionName: false,
displayDateTime: true,
displayLoggerName: false,
displayLogLevel: true,
minLevel: config.log.level,
});
export const games = new GameDB();
startWebsocketServer();

View file

@ -0,0 +1,72 @@
export class Deck<T> {
private _discard: T[];
private _unknown: T[];
private _deck: T[];
constructor(cards: T[]) {
this._deck = cards;
this._discard = [];
this._unknown = [];
};
get size(): number { return this._deck.length; }
/**
* Draws X cards from the deck
*
* @param quantity The number of cards to draw
* @throws Error If quantity is <= 0
* @throws Error If quantity > size
*/
public draw(quantity: number): T[] {
if (quantity <= 0) {
throw new Error(`Cannot get ${quantity} cards.`);
} else if (quantity > this.size) {
throw new Error(`Cannot draw more cards than there are in the deck.`);
};
let cards: T[] = [];
// Draw the cards for the player and move them into the unknown group
for (var i = 0; i < quantity; i++) {
// Determine the card for the player(s)
let index = Math.floor(Math.random() * this.size);
let card = this._deck[index];
// Move it from the arrays
cards.push(card);
this._deck.splice(index, 1);
this._unknown.push(card);
};
return cards;
};
/**
* Adds the specific card to the discard pile
*
* @param card The card to add to the discard pile
*/
public discard(card: T) {
if (!this._unknown.includes(card)) {
throw new Error("Cannot discard a card that doesn't exist in the deck");
};
this._unknown = this._unknown.filter(x => x != card);
this._discard.push(card);
};
/**
* Resets all of the cards in the deck and puts them back in the draw pile
*/
public reset() {
this._deck.push(...this._discard, ...this._unknown);
this._discard = [];
this._unknown = [];
};
};

115
server/src/objects/Game.ts Normal file
View file

@ -0,0 +1,115 @@
import { determineDirection, FuelCard } from "common";
import { config, games, log } from "@/main";
import { promises as fs } from "fs";
import { Player } from "./Player";
import { Logger } from "tslog";
import { Deck } from "./Deck";
import path from "path";
export class Game {
readonly id: string;
readonly host: Player;
readonly log: Logger;
private _status: string;
private _players: Player[];
private _deck: Deck<FuelCard>;
private board: (Player|null)[];
constructor(host: Player) {
this._status = `lobby`;
// Setup the board
this.board = new Array(55).fill(null);
this.board[26] = null;
this.board[36] = null;
// Init the player data
this.host = host;
this._players = [ host ];
// Instantiate the deck
this.loadDeck();
this.id = Game.generateID(config.game.code_length);
this.log = log.getChildLogger({
name: this.id,
displayLoggerName: true,
});
};
/**
* Loads the deck from the data file which contains an array of card
* definitions.
*/
private async loadDeck() {
if (config.game.fuel_deck.type == "file:json") {
let cards = JSON.parse(
await fs.readFile(
path.join(process.cwd(), config.game.fuel_deck.location),
`utf-8`
)
);
this._deck = new Deck(cards);
} else {
throw new Error(`Unsupported data format for the cards: "${config.game.fuel_deck.type}"`)
}
};
/** The current status of the game */
get status() { return this._status; }
/** The deck of the fuel cards */
get deck() { return this._deck; };
/** A list players of in the game */
get players() { return this._players; };
/**
* The algorithm to determine which direction the closest ship is, this
* uses an integer that can be multiplied by the player's fuel card
* magnitude in order to determine the delta for the board index.
*
* ---
*
* Possible Return Values:
* - `-1` = Away from the Warp Gate (towards the black hole)
* - `0` = Not moving
* - `1` = Towards the Warp Gate (away from the black hole)
*/
public movementDirection(player: Player): number {
let location = this.board.indexOf(player);
this.log.debug(`Calculating movement direction for ${player.name}`);
return determineDirection(this.board, location);
};
/**
* Generates a game code with the given length
*
* @param length The length of the code to generate
*/
public static generateID(length: number): string {
let code: string;
// Prevent erroneous codes from being generated
if (length <= 0) {
throw new Error("Can't code have a length <= 0");
};
// Generate a new code until we find one that isn't taken already.
do {
code = ``;
for (var i = 0; i < length; i++) {
code += Math.floor(Math.random() * 9);
};
} while (games.has(code));
return code;
}
};

View file

@ -0,0 +1,38 @@
import { Game } from "./Game";
export class GameDB {
private games: {[index: string]: Game};
constructor () {
this.games = {};
};
/** Determines if a game exists with the provided ID */
public has(id: string): boolean {
return this.games[id] != null;
};
/** Get's the Game object associated with the provided ID */
public get(id: string): Game|undefined {
return this.games[id];
};
/** Adds a Game object into the database */
public set(game: Game): void {
this.games[game.id] = game;
};
/**
* This removes all inactive games from the system, this includes any games
* that do not have any currently connected players and are in the lobby
* state. This will not remove any games that are currently in-progress so
* that players can resume the game later if they happened to all get
* disconnected for some reason.
*/
public cleanup(): void {};
/** Returns a count of how many games currently exist in the database */
public get count() {
return Object.keys(this.games).length;
};
}

View file

@ -0,0 +1,44 @@
import { PlayerData, IColour, ISpaceship } from "common";
import { Socket } from "socket.io";
export class Player {
readonly name: string;
// The player's design
public ship: ISpaceship;
public colour: IColour;
public readonly host: boolean;
constructor(socket: Socket, data: PlayerData) {
this.name = data.name;
this._socket = socket
this.ship = data.ship;
this.colour = data.colour;
this.host = data.host;
};
// The player's socket data
private _socket: Socket;
/** The socket that can be used to communicate directly with the player */
get socket() { return this._socket };
set socket(value: Socket) {
if (!this._socket?.connected) {
this._socket = value;
} else {
throw Error("Cannot overwrite a player's socket that is connected.");
};
};
/** The properties of the Player object without any of the methods. */
public json() {
return {
name: this.name,
ship: this.ship,
colour: this.colour,
host: this.host,
};
};
};

18
server/src/types/Config.d.ts vendored Normal file
View file

@ -0,0 +1,18 @@
interface IConfig {
game: {
code_length: number;
fuel_deck: {
type: "file:json" /* | "file:csv" | "web:json" | "web:csv" */;
location: string;
};
};
server: {
port: number;
cors: {
origins: string[];
};
};
log: {
level: "silly" | "trace" | "debug" | "info" | "warn" | "error" | "fatal";
};
}

6
server/src/types/WebsocketEvent.d.ts vendored Normal file
View file

@ -0,0 +1,6 @@
import { Server, Socket } from "socket.io";
export interface WebsocketEvent {
name: string;
handler: (io: Server, socket: Socket, data: any) => any;
}

View file

@ -0,0 +1,62 @@
import { readFileSync } from "fs";
import toml from "toml";
import Joi from "joi";
const schema = Joi.object({
game: Joi.object({
code_length: Joi.number().integer().positive(),
fuel_deck: Joi.object({
type: Joi
.string()
.allow(
`file:json`,
// `file:csv`,
// `web:json`,
// `web:csv`
)
.required(),
location: Joi
.string()
.required(),
}),
}),
server: Joi.object({
port: Joi
.number()
.port()
.required(),
cors: Joi.object({
origins: Joi
.array()
.items(Joi.string()),
}),
}),
log: Joi.object({
level: Joi
.string()
.allow(
`silly`,
`trace`,
`debug`,
`info`,
`warn`,
`error`,
`fatal`
)
.optional()
.default(`info`),
}),
});
export function loadConfig(): IConfig {
const data = toml.parse(readFileSync("config.toml", "utf-8"));
const { value, error } = schema.validate(data, {
abortEarly: false,
});
if (error) {
console.error(error);
process.exit(1);
};
return value;
};

33
server/src/websocket.ts Normal file
View file

@ -0,0 +1,33 @@
import { log, config } from "./main";
import { Server } from "socket.io";
// Event imports
import Ping from "./events/ping";
import ServerInfo from "./events/server_info";
import CreateLobby from "./events/lobby/create";
import JoinLobby from "./events/lobby/players/join";
export default async function() {
log.info("Starting socket.io server...");
const io = new Server();
io.on("connection", (socket) => {
log.silly(`Socket connected with ID: ${socket.id}`);
// Metadata Events
socket.on(Ping.name, (data) => Ping.handler(io, socket, data));
socket.on(ServerInfo.name, (data) => ServerInfo.handler(io, socket, data));
// Lobby Management Events
socket.on(CreateLobby.name, (data) => CreateLobby.handler(io, socket, data));
socket.on(JoinLobby.name, (data) => JoinLobby.handler(io, socket, data));
});
io.listen(config.server.port, {
cors: {
origin: config.server.cors.origins,
credentials: true,
}
});
};

View file

@ -0,0 +1,126 @@
import { expect } from "chai";
import "mocha";
import { Deck } from "../../src/objects/Deck";
describe("The Deck object", () => {
var drawSuccess = false;
it("should construct properly with strings", () => {
let d = new Deck<string>(["1", "2", "3", "4", "5"]);
expect(d.size).to.equal(5);
});
it("should construct properly with objects", () => {
let d = new Deck<any>([
{}, {}, {}, {}, {}
]);
expect(d.size).to.equal(5);
});
it("should draw a single card from the deck properly", () => {
let d = new Deck<string>(["1", "2", "3", "4", "5"]);
let c = d.draw(1);
expect(d.size).to.equal(4);
expect(c).to.have.length(1);
expect(c[0]).to.be.a("string");
expect(c[0]).to.be.oneOf(["1", "2", "3", "4", "5"]);
drawSuccess = true;
});
it("should draw multiple cards from the deck properly", () => {
let d = new Deck<string>(["1", "2", "3", "4", "5"]);
let draws = 2;
let c = d.draw(draws);
expect(d.size).to.equal(3);
expect(c).to.have.length(draws);
for (var i = 0; i < draws; i++) {
expect(c[i]).to.be.a("string");
expect(c[i]).to.be.oneOf(["1", "2", "3", "4", "5"]);
};
});
it("should draw all of the cards from the deck properly", () => {
let d = new Deck<string>(["1", "2", "3", "4", "5"]);
let draws = 5;
let c = d.draw(draws);
expect(d.size).to.equal(0);
expect(c).to.have.length(draws);
for (var i = 0; i < draws; i++) {
expect(c[i]).to.be.a("string");
expect(c[i]).to.be.oneOf(["1", "2", "3", "4", "5"]);
};
});
it("should throw an error when -1 cards are drawn", () => {
let d = new Deck<string>(["1", "2", "3", "4", "5"]);
try {
d.draw(-1);
throw "draw() didn't error with -1 given";
} catch (e) {};
});
it("should throw an error when -5 cards are drawn", () => {
let d = new Deck<string>(["1", "2", "3", "4", "5"]);
try {
d.draw(-5);
throw "draw() didn't error with -5 given";
} catch (e) {};
});
it("should throw an error when n+1 cards were requested", () => {
let d = new Deck<string>(["1", "2", "3", "4", "5"]);
try {
d.draw(6);
throw "Draw didn't error with 6 given";
} catch (e) {};
});
it("should throw an error when n+5 cards were requested", () => {
let d = new Deck<string>(["1", "2", "3", "4", "5"]);
try {
d.draw(10);
throw "Draw didn't error with 10 given";
} catch (e) {};
});
it("should error when discarding something that isn't a card in the deck", function (this: Mocha.Context) {
if (!drawSuccess) { this.skip() };
let d = new Deck<string>(["1", "2", "3", "4", "5"]);
d.draw(1);
try {
d.discard("potato");
throw "Didn't error when discarding an invalid card"
} catch (_) {};
});
it("shouldn't error when discarding a valid card", function (this: Mocha.Context) {
if (!drawSuccess) { this.skip() };
const d = new Deck<string>(["1", "2", "3", "4", "5"]);
const c = d.draw(1)[0];
d.discard(c);
});
it("should reset count when card is in unknown domain", function (this: Mocha.Context) {
if (!drawSuccess) { this.skip() };
const d = new Deck<string>(["1", "2", "3", "4", "5"]);
d.draw(1);
d.reset();
expect(d.size).to.equal(5);
});
it("should reset count when card is in discard", function (this: Mocha.Context) {
if (!drawSuccess) { this.skip() };
const d = new Deck<string>(["1", "2", "3", "4", "5"]);
const c = d.draw(1)[0];
d.discard(c);
d.reset();
expect(d.size).to.equal(5);
});
});

View file

@ -0,0 +1,82 @@
import { Socket } from "socket.io";
import { expect } from "chai";
import "mocha";
import { Player } from "../../src/objects/Player";
import { colours, spaceships } from "common";
describe("The player object", () => {
it("should construct properly", () => {
const data = {
name: "Oliver",
ship: spaceships[0],
colour: colours[0],
host: false,
}
const p = new Player(
{ connected: true } as Socket,
data
);
expect(p.socket.connected).to.be.true;
expect(p.colour).to.equal(data.colour);
expect(p.ship).to.equal(data.ship);
expect(p.name).to.equal(data.name);
expect(p.host).to.equal(data.host);
});
it("shouldn't let socket be assigned when connected", () => {
const p = new Player(
{ connected: true } as Socket,
{
name: "Oliver",
ship: spaceships[0],
colour: colours[0],
host: false,
}
);
try {
p.socket = { connected : true } as Socket;
throw "Assigned socket when it was connected"
} catch (e) {};
});
it("should let socket be assigned when not connected", () => {
const p = new Player(
{ connected: false } as Socket,
{
name: "Oliver",
ship: spaceships[0],
colour: colours[0],
host: false,
}
);
try {
p.socket = { connected : true } as Socket;
} catch (e) {
throw "Didn't let socket be set when it wasn't connected"
};
});
it("should construct the data object correctly", () => {
let data = {
name: "Oliver",
ship: spaceships[0],
colour: colours[0],
host: false,
};
const p = new Player(
{ connected: false } as Socket,
data
);
let j = p.json();
expect(j).to.have.keys(`name`, `ship`, `colour`, `host`);
expect(j.colour).to.equal(data.colour);
expect(j.ship).to.equal(data.ship);
expect(j.name).to.equal(data.name);
expect(j.host).to.equal(data.host);
});
});

79
server/tsconfig.json Normal file
View file

@ -0,0 +1,79 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
// "lib": [], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
"sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "./dist", /* Redirect output structure to the directory. */
"rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
"removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
// "strict": true, /* Enable all strict type-checking options. */
"noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
"strictNullChecks": true, /* Enable strict null checks. */
"strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
"alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
"noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
"noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
"paths": {
"@/*": ["./src/*"]
}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
/* Advanced Options */
"skipLibCheck": true, /* Skip type checking of declaration files. */
"forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */
"stripInternal": true
},
"exclude": [
"**/*.spec.ts"
]
}

21
setup.sh Normal file
View file

@ -0,0 +1,21 @@
#!/bin/bash
# Build the common module that both subsystems depend on
cd common
make
# Setup the server side of the system
cd ../server
pnpm install
pnpm build
#pnpm link file:../common/cjs
# add command to copy the systemd service file to the right location
# Setup the website for the system
cd ../web-svelte
pnpm install
#pnpm link file:../common/cjs
pnpm build
# add command to help setup nginx to serve the built files

View file

@ -0,0 +1,4 @@
Space Shuttle Icon:
https://fontawesome.com/v5.15/icons/space-shuttle
License: https://fontawesome.com/license
Changes: Recolouring

41
web-svelte/index.html Normal file
View file

@ -0,0 +1,41 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Gravwell Online</title>
<link rel="stylesheet" href="/global.css">
<style>
@import url("https://fonts.googleapis.com/css2?family=Orbitron:wght@600&display=swap");
@import url("https://fonts.googleapis.com/icon?family=Material+Icons");
body {
padding: 0;
margin: 0;
font-family: 'Orbitron', sans-serif;
width: 100vw;
height: 100vh;
/* overflow: hidden; */
background:
radial-gradient(50% 75% at 50% 50%, #000000 35%, rgba(0, 0, 0, 0.171) 125%),
linear-gradient(335deg, #870051 15%, rgba(0, 0, 0, 0) 45%),
linear-gradient(225deg, #008505 15%, rgba(0, 0, 0, 0) 50%),
linear-gradient(170deg, #17A9A9 10%, rgba(0, 0, 0, 0) 50%),
linear-gradient(45deg, #070055 25%, rgba(0, 0, 0, 0) 50%),
black;
}
#app {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

27
web-svelte/package.json Normal file
View file

@ -0,0 +1,27 @@
{
"name": "web-svelte",
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-check --tsconfig ./tsconfig.json"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^1.0.0-next.30",
"@tsconfig/svelte": "^2.0.1",
"svelte": "^3.49.0",
"svelte-check": "^2.2.7",
"svelte-preprocess": "^4.9.8",
"tslib": "^2.3.1",
"typescript": "^4.4.4",
"vite": "^3.0.2"
},
"dependencies": {
"sass": "^1.45.0",
"socket.io-client": "^4.4.0",
"svelte-particles": "^2.1.3",
"tsparticles": "^2.1.3"
}
}

1232
web-svelte/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2" viewBox="0 0 370 331"><circle cx="232.83" cy="218.38" r="49.85" transform="translate(-132.67 -132.45) scale(1.36436)"/><path d="M185 59.19c58.67 0 106.3 47.63 106.3 106.3S243.68 271.82 185 271.82 78.69 224.17 78.69 165.5 126.33 59.19 185 59.19Zm0 24.45c45.18 0 81.86 36.68 81.86 81.86s-36.68 81.86-81.86 81.86a81.9 81.9 0 0 1-81.86-81.86A81.9 81.9 0 0 1 185 83.64Z"/><path d="M184.52 325.88c88.57 0 160.38-71.8 160.38-160.38 0-53.53-26.23-100.94-66.53-130.07a159.66 159.66 0 0 0-93.85-30.31v38.49c67.31 0 121.89 54.57 121.89 121.89s-54.58 121.89-121.9 121.89v38.5Z"/><path d="M278.37 35.43s14.94 7.23 11.28-10.04c-6.92-32.57 45.14-29.43 52.12 108.4 9.43 186.36-63.4-98.36-63.4-98.36ZM185.49 5.12C96.9 5.12 25.1 76.92 25.1 165.5c0 53.53 26.23 100.94 66.53 130.07a159.65 159.65 0 0 0 93.85 30.31V287.4c-67.31 0-121.89-54.57-121.89-121.89S118.17 43.61 185.5 43.61V5.12Z"/><path d="M91.63 295.57s-14.94-7.23-11.27 10.04c6.9 32.57-45.14 29.43-52.12-108.4-9.44-186.36 63.4 98.36 63.4 98.36Z"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 100 100">
<circle cx="50" cy="50" r="45" stroke="white" strokeWidth="2" fill="black" />
</svg>

After

Width:  |  Height:  |  Size: 170 B

View file

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 100 100">
<polygon fill="white" points="50,100 7,75 7,25 50,0 93,25 93,75" />
<polygon fill="black" points="50,98 8,74 8,26 50,2 92,26 92,74" />
</svg>

After

Width:  |  Height:  |  Size: 227 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 3a9 9 0 0 0 0 18 1.5 1.5 0 0 0 1.11-2.51A1.5 1.5 0 0 1 14.23 16H16a5 5 0 0 0 5-5c0-4.42-4.03-8-9-8zm-5.5 9a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm3-4a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm5 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm3 4a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3z"/></svg>

After

Width:  |  Height:  |  Size: 388 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="72" height="72" fill="none" viewBox="0 0 24 24"><path fill="#221b38" fill-rule="evenodd" d="M22.7 1.3a1 1 0 0 1 0 1.4l-6 6a1 1 0 1 1-1.4-1.4l6-6a1 1 0 0 1 1.4 0Z" clip-rule="evenodd"/><path fill="#221b38" fill-rule="evenodd" d="M16 2a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v5a1 1 0 1 1-2 0V3h-4a1 1 0 0 1-1-1ZM1.3 1.3a1 1 0 0 1 1.4 0l6 6a1 1 0 0 1-1.4 1.4l-6-6a1 1 0 0 1 0-1.4Z" clip-rule="evenodd"/><path fill="#221b38" fill-rule="evenodd" d="M1 2a1 1 0 0 1 1-1h5a1 1 0 0 1 0 2H3v4a1 1 0 0 1-2 0V2ZM8.7 15.3a1 1 0 0 1 0 1.4l-6 6a1 1 0 0 1-1.4-1.4l6-6a1 1 0 0 1 1.4 0Z" clip-rule="evenodd"/><path fill="#221b38" fill-rule="evenodd" d="M2 16a1 1 0 0 1 1 1v4h4a1 1 0 1 1 0 2H2a1 1 0 0 1-1-1v-5a1 1 0 0 1 1-1ZM15.3 15.3a1 1 0 0 1 1.4 0l6 6a1 1 0 0 1-1.4 1.4l-6-6a1 1 0 0 1 0-1.4Z" clip-rule="evenodd"/><path fill="#221b38" fill-rule="evenodd" d="M22 16a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1h-5a1 1 0 1 1 0-2h4v-4a1 1 0 0 1 1-1Z" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 982 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M14 8a4 4 0 1 0-8 0 4 4 0 0 0 8 0zm3 2v2h6v-2h-6zM2 18v2h16v-2c0-2.66-5.33-4-8-4s-8 1.34-8 4z"/></svg>

After

Width:  |  Height:  |  Size: 211 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" viewBox="0 0 640 512"><path fill="currentColor" d="M592.6 208.24C559.73 192.84 515.78 184 472 184H186.33c-4.96-6.55-10.59-11.98-16.72-16H376C229.16 137.75 219.4 32 96 32v128H80V32c-26.51 0-48 28.65-48 64v64c-23.2 0-32 10.03-32 24v40c0 13.98 8.82 24 32 24v16c-23.2 0-32 10.03-32 24v40c0 13.98 8.82 24 32 24v64c0 35.35 21.49 64 48 64V352h16v128c123.4 0 133.16-105.75 280-136H169.6c6.14-4.02 11.77-9.45 16.73-16H472c43.78 0 87.73-8.84 120.6-24.24C622.28 289.85 640 271.99 640 256s-17.72-33.84-47.4-47.76zM488 296a8 8 0 0 1-8-8v-64a8 8 0 0 1 8-8c31.9 0 31.94 80 0 80z"/></svg>

After

Width:  |  Height:  |  Size: 631 B

View file

@ -0,0 +1 @@
<svg viewBox="0 0 106 112" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2"><path d="M1.812 41.775c-7.559 28.21 9.182 57.206 37.392 64.764 17.048 4.568 34.384.262 47.102-10.088a52.64 52.64 0 0 0 17.663-27.304L91.71 65.863c-5.745 21.44-27.782 34.162-49.221 28.417C21.049 88.536 8.326 66.5 14.07 45.06L1.812 41.774Z"/><path d="M86.306 96.45s-3.577 4.143 2.237 4.45c10.96.577 5.52 16.887-38.973 7.347-60.155-12.898 36.736-11.796 36.736-11.796Zm17.58-26.994c7.559-28.21-9.182-57.206-37.392-64.765C49.446.123 32.11 4.43 19.393 14.78A52.64 52.64 0 0 0 1.73 42.083l12.258 3.285C19.733 23.928 41.77 11.205 63.21 16.95c21.439 5.745 34.162 27.782 28.417 49.221l12.26 3.285Z"/><path d="M19.393 14.78s3.577-4.142-2.237-4.449c-10.961-.577-5.52-16.887 38.972-7.348C116.283 15.881 19.393 14.78 19.393 14.78Z"/><path d="M52.695 108.277c29.081 0 52.657-23.575 52.657-52.657 0-17.575-8.61-33.14-21.843-42.705a52.419 52.419 0 0 0-30.814-9.952V15.6c22.102 0 40.02 17.918 40.02 40.02 0 22.102-17.918 40.02-40.02 40.02v12.637Z"/><path d="M83.509 12.915s4.906 2.373 3.702-3.298c-2.27-10.691 14.82-9.662 17.112 35.592 3.098 61.185-20.814-32.294-20.814-32.294ZM53.013 2.963C23.93 2.963.355 26.538.355 55.62c0 17.575 8.61 33.14 21.844 42.705a52.419 52.419 0 0 0 30.814 9.952V95.64c-22.103 0-40.02-17.918-40.02-40.02 0-22.102 17.917-40.02 40.02-40.02V2.963Z"/><path d="M22.198 98.325s-4.906-2.373-3.702 3.298c2.27 10.691-14.82 9.662-17.112-35.592-3.098-61.185 20.814 32.294 20.814 32.294Z"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1 @@
<svg viewBox="0 0 176 172" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2"><path d="M26.2 142.809 0 109.954l17.367-76.091L87.685 0l70.318 33.863 17.367 76.091-26.201 32.855 8.739 28.746H17.461l8.739-28.746ZM87.685 7.49l59.222 28.52 14.626 64.083-40.982 51.391H54.819l-40.983-51.391L28.463 36.01 87.685 7.49Z"/></svg>

After

Width:  |  Height:  |  Size: 410 B

View file

@ -0,0 +1,22 @@
:root {
--dark-but-not-black: #2c2f33;
--not-quite-black: #23272a;
}
.clickable {
cursor: pointer;
}
.material-icons {
user-select: none;
}
.error {
padding: 10px;
background: #ff000033;
border-radius: 10px;
border-style: solid;
border-color: red;
border-width: 2px;
text-align: center;
}

View file

@ -0,0 +1,110 @@
{
"particles": {
"number": {
"value": 100,
"density": {
"enable": true,
"value_area": 800
}
},
"color": {
"value": "#ffffff"
},
"shape": {
"type": "circle",
"stroke": {
"width": 0,
"color": "#000000"
},
"polygon": {
"nb_sides": 5
},
"image": {
"src": "img/github.svg",
"width": 100,
"height": 100
}
},
"opacity": {
"value": 0.5,
"random": false,
"anim": {
"enable": false,
"speed": 1,
"opacity_min": 0.1,
"sync": false
}
},
"size": {
"value": 2,
"random": true,
"anim": {
"enable": true,
"speed": 4,
"size_min": 0.1,
"sync": false
}
},
"line_linked": {
"enable": true,
"distance": 75,
"color": "#ffffff",
"opacity": 0.4,
"width": 1
},
"move": {
"enable": true,
"speed": 0.25,
"direction": "none",
"random": true,
"straight": false,
"out_mode": "bounce",
"bounce": false,
"attract": {
"enable": false,
"rotateX": 600,
"rotateY": 1200
}
}
},
"interactivity": {
"detect_on": "canvas",
"events": {
"onhover": {
"enable": false,
"mode": "grab"
},
"onclick": {
"enable": false,
"mode": "push"
},
"resize": true
},
"modes": {
"grab": {
"distance": 100,
"line_linked": {
"opacity": 1
}
},
"bubble": {
"distance": 400,
"size": 40,
"duration": 2,
"opacity": 8,
"speed": 3
},
"repulse": {
"distance": 200,
"duration": 0.4
},
"push": {
"particles_nb": 4
},
"remove": {
"particles_nb": 2
}
}
},
"retina_detect": true
}

39
web-svelte/src/App.svelte Normal file
View file

@ -0,0 +1,39 @@
<script lang="ts">
// Library imports
import Particles from "svelte-particles";
import { loadFull } from "tsparticles";
// View imports
import MultiplayerLobby from "./views/lobby/multiplayer.svelte";
import MovementView from "./views/movement.svelte";
import Home from "./views/home.svelte";
// Store imports
import { state } from "./stores";
async function particlesInit(engine: any) {
await loadFull(engine);
};
</script>
<!-- Static particles across all of our pages -->
<div id="particles">
<Particles url="/particlesConfig.json" {particlesInit} />
</div>
<!-- The state the game is in -->
{#if $state == "main-menu"}
<Home />
{:else if $state == "multiplayer-lobby"}
<MultiplayerLobby />
{:else if $state == "game-movement"}
<MovementView />
{/if}
<style lang="scss">
#particles {
position: fixed;
z-index: -100000;
}
</style>

View file

@ -0,0 +1,182 @@
<script lang="ts">
import Spaceship from "./icons/spaceship.svelte";
import Warpgate from "./icons/warpgate.svelte";
import Hexagon from "./Hexagon.svelte";
import { players } from "../stores";
import type { Board } from "common"
export let board: Board;
</script>
<main class="board">
<div class="singularity inner-grid"></div>
<div class="side-hex right-down-1 inner-grid">
<Hexagon>
{#if board[10] != null}
<div
class="spaceship-icon"
style="--colour: {$players[board[10]].colour.hex};"
>
<Spaceship
spaceship="{$players[board[10]].ship.id}"
/>
</div>
{/if}
</Hexagon>
</div>
<div class="side-hex left-down-1 inner-grid">
<Hexagon>
{#if board[21] != null}
<div
class="spaceship-icon"
style="--colour: {$players[board[21]].colour.hex};"
>
<Spaceship
spaceship="{$players[board[21]].ship.id}"
/>
</div>
{/if}
</Hexagon>
</div>
<div class="side-hex right-down-2 inner-grid">
<Hexagon>
{#if board[32] != null}
<div
class="spaceship-icon"
style="--colour: {$players[board[32]].colour.hex};"
>
<Spaceship
spaceship="{$players[board[32]].ship.id}"
/>
</div>
{/if}
</Hexagon>
</div>
<div class="side-hex left-down-2 inner-grid">
<Hexagon>
{#if board[43] != null}
<div
class="spaceship-icon"
style="--colour: {$players[board[43]].colour.hex};"
>
<Spaceship
spaceship="{$players[board[43]].ship.id}"
/>
</div>
{/if}
</Hexagon>
</div>
<div class="warp-gate inner-grid">
<Warpgate></Warpgate>
</div>
{#each Array(5) as _, row}
<div class="main-row {row % 2 == 1 ? `row-reverse` : ``}">
{#each Array(10) as _, col}
{@const boardIndex = col + (( row * 10 ) + (1 * row))}
<div>
<Hexagon>
{#if board[boardIndex] != null}
<div
class="spaceship-icon"
style="--colour: {$players[board[boardIndex]].colour.hex};"
>
<Spaceship
spaceship="{$players[board[boardIndex]].ship.id}"
/>
</div>
{/if}
</Hexagon>
</div>
{/each}
</div>
{/each}
</main>
<style lang="scss">
main {
width: 100%;
height: 100%;
display: grid;
grid-template-rows: repeat(5, 20fr);
grid-template-columns: 100px 100fr 100px;
}
.main-row {
display: flex;
justify-content: space-evenly;
> div {
display: flex;
justify-content: center;
align-items: center;
height: 95%;
width: 100%;
}
}
.row-reverse {
flex-direction: row-reverse;
.spaceship-icon {
transform: scaleX(-1);
}
}
.inner-grid {
display: flex;
justify-content: center;
align-items: center;
}
.spaceship-icon {
/*
Smaller version: 55px
Larger version: 75px
*/
--size: 55px;
width: var(--size);
height: var(--size);
display: flex;
color: var(--colour);
justify-content: center;
align-items: center;
}
.side-hex {
.spaceship-icon {
transform: rotate(90deg);
}
}
.singularity {
background-image: url(/assets/black-hole.svg);
background-repeat: no-repeat;
background-position: center;
grid-column: 1 / 2;
grid-row: 1 / 2;
}
.right-down-1 {
grid-column: -1 / -2;
grid-row: 1 / 3;
}
.left-down-1 {
grid-column: 1 / 2;
grid-row: 2 / 4;
}
.right-down-2 {
grid-column: -1 / -2;
grid-row: 3 / 5;
}
.left-down-2 {
grid-column: 1 / 2;
grid-row: 4 / 6;
}
.warp-gate {
border-radius: 10px;
grid-column: -1 / -2;
grid-row: -1 / -2;
width: 100%;
}
</style>

View file

@ -0,0 +1,138 @@
<script lang="ts">
import SciFiButton from "./SciFi-Button.svelte";
import { createEventDispatcher } from "svelte";
import type { FuelCard } from "common";
export let hidden: boolean = false;
export let button: boolean = true;
export let buttonText: string = "Use Fuel Card";
export let card: FuelCard;
export let half: boolean = false;
const emit = createEventDispatcher();
let colour: string;
let magnitudeShape = "";
if (card.type == "stationary") {
colour = `#008A71`;
magnitudeShape = `hexagon`;
} else {
if (card.magnitude < 0) {
colour = `#600068`;
magnitudeShape = `circle`;
} else {
colour = `#536800`;
};
};
</script>
<div
class="card {hidden ? 'hidden' : ''} {half ? 'half' : ''}"
style="--colour: {colour};"
on:mouseenter="{() => emit(`hover`, { card, visible: true })}"
on:mouseleave="{() => emit(`hover`, { card, visible: false })}"
>
{#if !hidden}
<div class="magnitude {magnitudeShape}">
{Math.abs(card.magnitude)}
</div>
<div class="main-content">
<h1>{card.symbol}</h1>
{card.name}
</div>
{#if button && !half}
<div class="button-container">
<SciFiButton
classes="use-fuel-card-button"
textColour="#FFFFFF"
hoverTextColour="#FFFFFF"
background="rgba(0,0,0,0.6)"
>
{buttonText}
</SciFiButton>
</div>
{/if}
{/if}
</div>
<style lang="scss">
$border-radius: 10px;
.card {
align-items: center;
background: var(--colour);
border-radius: $border-radius;
color: white;
display: flex;
justify-content: center;
align-items: center;
margin: 10px;
min-width: 150px;
position: relative;
user-select: none;
width: calc(100% / 9);
&.hidden {
background: #3D3D3D;
}
> .button-container {
position: absolute;
bottom: 5px;
}
> .magnitude {
$size: 40px;
background: rgba(255, 255, 255, 0.3);
border-radius: 10px 0 10px 0;
color: black;
display: flex;
left: 0;
position: absolute;
top: 0;
justify-content: center;
align-items: center;
width: $size;
height: $size;
&.hexagon {
background-image: url(/assets/hexagon.svg);
background-position: center;
background-repeat: no-repeat;
background-size: 85%;
color: white;
}
&.circle {
background-image: url(/assets/circle.svg);
background-position: center;
background-repeat: no-repeat;
background-size: 85%;
color: white;
}
}
> .main-content {
margin-bottom: 30px;
text-align: center;
h1 {
text-align: center;
margin: 0;
}
}
&.half {
padding-top: 15px;
padding-bottom: 15px;
> .main-content {
margin-bottom: unset;
}
}
}
</style>

View file

@ -0,0 +1,17 @@
<div class="hexagon">
<slot></slot>
</div>
<style lang="scss">
.hexagon {
height: 100%;
width: 100%;
background-image: url(/assets/hexagon.svg);
background-repeat: no-repeat;
background-position: center;
display: flex;
justify-content: center;
align-items: center;
color: white;
}
</style>

View file

@ -0,0 +1,122 @@
<script lang="ts">
import SciFiButton from "./SciFi-Button.svelte";
import { createEventDispatcher } from "svelte";
import { myName, isHost } from "../stores";
import type { IColour } from "common";
export let name: string;
export let colour: IColour;
const emit = createEventDispatcher();
function exitPlayer() {};
function changeColour() {
emit("changeColour");
};
let canLeave = $isHost || name == $myName;
</script>
<div class="player" style="--colour: {colour.hex};">
<!-- The player's change Colour button -->
{#if $myName == name}
<SciFiButton
background="{colour.hex}"
on:click={changeColour}
height="60px"
width="60px"
hoverBackground="{colour.hex}"
>
<div style="display: flex; align-items: center; justify-content: center;">
<img
src="/assets/icons/change_colour.svg"
alt="Change your spaceship's colour"
style="baseline: center"
>
</div>
</SciFiButton>
<!-- The placeholder element that just gets coloured for other player colours -->
{:else}
<div class="player-colour"></div>
{/if}
<!-- The player's name -->
<div class="player-name">
<span class="name">
{name}
</span>
{#if $myName == name}
<span class="player-indicator">
You
</span>
{/if}
</div>
<!-- Whether or not to show the kick Player button in the row -->
{#if canLeave}
<SciFiButton
background="#ff0000"
on:click={exitPlayer}
height="60px"
width="75px"
hoverBackground="#ff0000"
>
<div style="display: flex; align-items: center; justify-content: center;">
<img
src="/assets/icons/kick_person.svg"
alt="Remove person from the game"
style="baseline: center"
>
</div>
</SciFiButton>
{/if}
</div>
<style lang="scss">
@import "../styles/mixins";
.player {
height: 70px;
display: flex;
align-items: center;
text-align: left;
background: rgba(255, 255, 255, 0.1);
border-radius: 10px;
margin: 10px;
max-width: 95%;
> div.player-colour {
margin: 10px;
width: 40px;
height: 40px;
background: var(--colour);
display: inline-block;
border-radius: 7px;
}
> .player-name {
flex-grow: 1;
display: flex;
align-items: center;
.name {
flex-grow: 1;
}
}
.player-indicator {
border-radius: 5px;
background-color: #00aa00;
color: black;
padding: 5px 7px;
}
@include medium {
max-width: 47%;
}
}
</style>

View file

@ -0,0 +1,43 @@
<script lang="ts">
import { createEventDispatcher } from "svelte";
import Card from "./Card.svelte";
import { hand } from "../stores";
const emit = createEventDispatcher();
let canChooseFuel = true;
// Prevent rendering of a board preview when the player isn't allowed to select
// a card.
function filterHoverEvents(e: any) {
if (canChooseFuel) {
emit(`hover`, e.detail);
};
};
</script>
<div id="hand">
{#each $hand as card (card.symbol)}
<Card
on:hover="{filterHoverEvents}"
hidden="{false}"
button="{canChooseFuel}"
{card}
/>
{/each}
</div>
<style lang="scss">
#hand {
background: rgba(0,0,0, 0.6);
border-color: white;
border-radius: 15px;
border-style: solid;
border-width: 1px;
display: flex;
height: 100%;
justify-content: space-evenly;
width: 100%;
}
</style>

View file

@ -0,0 +1,117 @@
<script lang="ts">
export let height: string|number = "50px";
export let width: string|number = "unset";
export let textColour: string = "#000000";
export let hoverTextColour: string = "#000000";
export let background: string = "#00aa00";
export let hoverBackground: string = "transparent";
export let classes: string = "";
export let title: string = "";
// Add the units to the height value if not provided, and ensure that the
// provided height is not less than 50 (the default)
$: if (typeof(height) == "string") {
if (height.match(/[0-9]$/)) {
if (height.match(/^[0-4]?[0-9]$/)) {
height = `50`;
};
height = `${height}px`;
};
} else {
if (height < 50) {
height = 50;
};
height = `${height}px`;
};
// Add the units to the width value if not provided
$: if (typeof(width) == "string") {
if (width.match(/[0-9]$/)) {
width = `${width}px`;
};
} else {
width = `${width}px`;
};
</script>
<div
style="
--height: {height};
--width: {width};
--background-colour: {background};
--hover-background-colour: {hoverBackground};
--text-colour: {textColour};
--hover-text-colour: {hoverTextColour};"
class="button-container {classes}"
>
<button
class="clickable"
{title}
on:click|trusted|stopPropagation
>
<slot></slot>
</button>
</div>
<style lang="scss">
div.button-container {
display: inline-block;
position: relative;
width: var(--width);
height: var(--height);
}
$button-background: var(--background-colour);
$hover-background: var(--hover-background-colour);
$text-colour: var(--text-colour);
$hover-text-colour: var(--hover-text-colour);
button {
background: $button-background;
border-radius: 5px;
border: 3px solid $button-background;
box-sizing: border-box;
color: $text-colour;
font-family: 'Orbitron', sans-serif;
height: calc(100% - 20px);
margin: 10px;
padding: 5px 7px;
transition: all .7s ease;
transition: opacity .7s ease;
width: calc(100% - 20px);
outline: none;
&:active {
opacity: .2;
outline: none;
}
&:hover, &:focus {
background-color: $hover-background;
border-radius: 20px;
color: $hover-text-colour;
&:before {
border-radius: min(
calc(((var(--height) - 20px) / 2) + 5px),
25px
);
border: 2px $button-background solid;
}
}
&:before {
content: ' ';
border: 2px transparent solid;
position: absolute;
left: 5px;
top: 5px;
box-sizing: border-box;
width: calc(100% - 10px);
height: calc(100% - 10px); /* 100% - 20px + 10px */
transition: all .8s ease;
}
}
</style>

View file

@ -0,0 +1,119 @@
<script lang="ts">
import { createEventDispatcher } from "svelte";
const emit = createEventDispatcher();
export let disabled: boolean = false;
export let state: boolean;
export let name: string;
export let id: string;
function onToggle() {
emit(`toggle`, id);
};
</script>
<label class="container" {disabled}>
<slot></slot>
<input
type="checkbox"
{disabled}
{name}
{id}
bind:checked="{state}"
on:click="{onToggle}"
>
<span class="checkmark"></span>
</label>
<style lang="scss">
$unchecked: #ff0000;
$checked: #00aa00;
/* Create a custom checkbox */
.checkmark {
position: absolute;
top: 0;
left: 0;
height: 25px;
width: 25px;
background-color: $unchecked;
border-radius: 12px;
&:after {
content: "";
position: absolute;
display: none;
}
&:after {
left: 7px;
top: 2px;
width: 7px;
height: 14px;
border: solid black;
border-width: 0 3px 3px 0;
-webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
}
}
.container {
display: inline-flex;
align-items: center;
position: relative;
padding-left: 35px;
margin: 10px;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
height: 25px;
input {
position: absolute;
opacity: 0;
cursor: pointer;
height: 0;
width: 0;
}
/* Checked checkmark */
input:checked ~ .checkmark {
background-color: $checked;
}
/* Darken the checkmark background colour when hovering */
&:hover {
input ~ .checkmark {
background-color: darken($unchecked, 20%);
}
input:checked ~ .checkmark {
background-color: darken($checked, 20%);
}
}
/* Indicate to users when they can't actually use the options */
&[disabled="true"] {
cursor: not-allowed;
&:hover {
input ~ .checkmark {
background-color: $unchecked;
}
input:checked ~ .checkmark {
background-color: $checked;
}
}
}
/* Show the checkmark when checked */
input:checked ~ .checkmark:after {
display: block;
}
}
</style>

View file

@ -0,0 +1,172 @@
<script lang="ts">
import { players } from "../stores";
import Spaceship from "./icons/spaceship.svelte";
import Card from "./Card.svelte";
$: stopped = false;
$: sortedPlayers = Object.values($players).sort(
(p1, p2) => p1.fuel.symbol.localeCompare(p2.fuel.symbol)
);
$: activePlayer = "a";
</script>
<div id="ships-moving">
<div id="player-order-container">
<div class="player-order background">
{#each sortedPlayers as player (player.id)}
<div class="player">
<div class="card">
<Card card={player.fuel} half={true} />
</div>
<div class="meta">
<div
class="spaceship-icon"
style="--colour: {player.colour.hex};"
>
<Spaceship
spaceship="{player.ship.id}"
/>
</div>
<div class="name">
{player.name}
</div>
</div>
</div>
{/each}
</div>
</div>
<div id="e-stop-container">
<div class="e-stop background">
<h2>Emergency Stop!</h2>
<p>
Pressing this will stop you from moving
</p>
<div class="button-container">
<button
disabled={stopped}
on:click="{() => {stopped = true}}"
>
<h1>STOP{#if stopped}PED{/if}</h1>
</button>
</div>
</div>
</div>
</div>
<style lang="scss">
#ships-moving {
display: grid;
grid-template-columns: 1fr 30%;
height: 100%;
padding: 0;
width: 100%;
}
#player-order-container {
grid-column: 1 / 2;
.player-order {
color: white;
display: flex;
height: 100%;
justify-content: space-evenly;
margin-right: 5px;
width: calc(100% - 5px);
}
.card {
display: flex;
}
.player {
display: grid;
grid-template-rows: 50% 50%;
.meta {
align-items: center;
display: flex;
flex-direction: column;
justify-content: space-evenly;
text-align: center;
.name {
background: rgba(255,255,255,0.2);
border-radius: 5px;
padding: 5px;
width: 80%;
}
.spaceship-icon {
aspect-ratio: 1 / 1;
color: var(--colour);
height: 50%;
}
}
}
}
#e-stop-container {
grid-column: 2 / 3;
.e-stop {
align-items: center;
color: white;
display: flex;
flex-direction: column;
height: 100%;
margin-left: 5px;
text-align: center;
width: calc(100% - 5px);
> h2,
> p {
margin-bottom: 0;
margin-top: 10px;
width: 90%;
}
> .button-container {
align-items: center;
display: flex;
flex-grow: 1;
justify-content: center;
width: 100%;
}
button {
$height: 10px;
background: red;
border-radius: 10px;
border-style: none;
box-shadow: 0 $height 0 darken(red, 20%);
font-family: 'Orbitron', sans-serif;
margin-top: -5px;
outline: none;
width: 80%;
&[disabled],
&:active {
box-shadow: none;
transform: translateY($height);
}
&[disabled] {
color: rgba(0,0,0, 0.6);
}
}
}
}
.background {
background: rgba(0,0,0, 0.6);
border-color: white;
border-radius: 15px;
border-style: solid;
border-width: 1px;
}
</style>

View file

@ -0,0 +1,52 @@
<!--
Possible extra Spaceship icons:
https://www.flaticon.com/free-icon/spaceship_6437894
https://fontawesome.com/v5.15/icons/starfighter?style=solid
https://fontawesome.com/v5.15/icons/starship-freighter?style=solid
https://fontawesome.com/v5.15/icons/starship?style=solid
https://fontawesome.com/icons/sailboat?s=solid
-->
<script lang="ts">
export let spaceship = "space-shuttle";
</script>
{#if spaceship == "rocket"}
<div class="rocket">
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="rocket" class="svg-inline--fa fa-rocket fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M505.12019,19.09375c-1.18945-5.53125-6.65819-11-12.207-12.1875C460.716,0,435.507,0,410.40747,0,307.17523,0,245.26909,55.20312,199.05238,128H94.83772c-16.34763.01562-35.55658,11.875-42.88664,26.48438L2.51562,253.29688A28.4,28.4,0,0,0,0,264a24.00867,24.00867,0,0,0,24.00582,24H127.81618l-22.47457,22.46875c-11.36521,11.36133-12.99607,32.25781,0,45.25L156.24582,406.625c11.15623,11.1875,32.15619,13.15625,45.27726,0l22.47457-22.46875V488a24.00867,24.00867,0,0,0,24.00581,24,28.55934,28.55934,0,0,0,10.707-2.51562l98.72834-49.39063c14.62888-7.29687,26.50776-26.5,26.50776-42.85937V312.79688c72.59753-46.3125,128.03493-108.40626,128.03493-211.09376C512.07526,76.5,512.07526,51.29688,505.12019,19.09375ZM384.04033,168A40,40,0,1,1,424.05,128,40.02322,40.02322,0,0,1,384.04033,168Z"></path></svg>
</div>
{:else if spaceship == "space-shuttle"}
<div class="space-shuttle">
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="space-shuttle" class="svg-inline--fa fa-space-shuttle fa-w-20" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="currentColor" d="M592.604 208.244C559.735 192.836 515.777 184 472 184H186.327c-4.952-6.555-10.585-11.978-16.72-16H376C229.157 137.747 219.403 32 96.003 32H96v128H80V32c-26.51 0-48 28.654-48 64v64c-23.197 0-32 10.032-32 24v40c0 13.983 8.819 24 32 24v16c-23.197 0-32 10.032-32 24v40c0 13.983 8.819 24 32 24v64c0 35.346 21.49 64 48 64V352h16v128h.003c123.4 0 133.154-105.747 279.997-136H169.606c6.135-4.022 11.768-9.445 16.72-16H472c43.777 0 87.735-8.836 120.604-24.244C622.282 289.845 640 271.992 640 256s-17.718-33.845-47.396-47.756zM488 296a8 8 0 0 1-8-8v-64a8 8 0 0 1 8-8c31.909 0 31.942 80 0 80z"></path></svg>
</div>
{:else if spaceship == "sailboat"}
<div class="sailboat">
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="sailboat" class="svg-inline--fa fa-sailboat fa-w-20" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--! Font Awesome Pro 6.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path fill="currentColor" d="M256 16C256 9.018 260.5 2.841 267.2 .7414C273.9-1.358 281.1 1.105 285.1 6.826L509.1 326.8C512.5 331.7 512.9 338.1 510.2 343.4C507.4 348.7 501.1 352 496 352H272C263.2 352 256 344.8 256 336V16zM212.1 96.54C219.1 98.4 224 104.7 224 112V336C224 344.8 216.8 352 208 352H80C74.3 352 69.02 348.1 66.16 344C63.3 339.1 63.28 333 66.11 328.1L194.1 104.1C197.7 97.76 205.1 94.68 212.1 96.54V96.54zM5.718 404.3C2.848 394.1 10.52 384 21.12 384H554.9C565.5 384 573.2 394.1 570.3 404.3L566.3 418.7C550.7 473.9 500.4 512 443 512H132.1C75.62 512 25.27 473.9 9.747 418.7L5.718 404.3z"/></svg>
</div>
{/if}
<style lang="scss">
svg {
width: 100%;
height: 100%
}
.rocket {
width: 100%;
height: 100%;
transform: translate(-5px, 0px) rotate(45deg);
}
.space-shuttle {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
width: 100%;
}
.sailboat {
width: 100%;
height: 100%;
transform: translate(3px, 5px);
}
</style>

View file

@ -0,0 +1,51 @@
<div class="main">
<div class="shell"></div>
<div class="center"></div>
</div>
<style lang="scss">
div {
width: 100%;
height: 100%;
}
.main {
position: relative;
}
.shell {
background-image: url(/assets/warp-gate-shell.svg);
background-repeat: no-repeat;
background-position: center;
transform: rotate(90deg);
}
.center {
--size: 60%;
width: var(--size);
height: var(--size);
position: absolute;
background-image: url(/assets/warp-gate-center.svg);
background-repeat: no-repeat;
background-position: center;
animation: rotate 3s linear infinite;
top: 21%;
left: 21%;
}
@media (prefers-reduced-motion) {
.center {
animation: rotate 10s linear infinite;
}
}
@keyframes rotate {
to {
transform: rotate(0deg);
}
from {
transform: rotate(360deg);
}
}
</style>

View file

@ -0,0 +1,98 @@
<script lang="ts">
import SciFiButton from "../SciFi-Button.svelte";
import { fade, scale } from "svelte/transition";
import { createEventDispatcher } from "svelte";
const emit = createEventDispatcher();
export let open = false;
function closeModal() {
emit(`close`);
open = false;
};
</script>
{#if open}
<div class="modal">
<div
class="background clickable"
in:fade
out:fade="{{ delay: 200 }}"
on:click="{closeModal}"
></div>
<div
class="foreground"
in:scale="{{ delay: 400, start: 0, opacity: 1 }}"
out:scale="{{ start: 0, opacity: 1 }}"
>
<SciFiButton
classes="close-button"
on:click={closeModal}
height="60"
width="60"
on:click
>
<div class="icon-container">
<span class="material-icons">
close
</span>
</div>
</SciFiButton>
<slot></slot>
</div>
</div>
{/if}
<style lang="scss">
@import "../../styles/mixins";
.modal {
z-index: 100;
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
.background {
position: absolute;
width: 100%;
height: 100%;
background-color: rgba(0,0,0, 0.5);
}
.foreground {
z-index: 101;
max-height: 80vh;
overflow: auto;
width: 90%;
padding: 15px;
position: relative;
border-radius: 10px;
background: var(--dark-but-not-black);
color: #FFFFFF;
.icon-container {
display: flex;
justify-content: center;
align-items: center;
}
:global(.close-button) {
position: absolute;
top: 10px;
right: 10px;
}
@include medium {
width: 75%;
}
}
}
</style>

View file

@ -0,0 +1,124 @@
<script lang="ts">
import { isHost, myName, gameCode, players } from "../../stores";
import { createEventDispatcher, onMount } from "svelte";
import SciFiButton from "../SciFi-Button.svelte";
import { ILobbyInfo, Status } from "common";
import BaseModal from "./BaseModal.svelte";
import { socket } from "../../main";
const emit = createEventDispatcher();
export let open: boolean = false;
export let joining: boolean;
let errorMessage: string|null = null;
function handleLobbyConnection(data: ILobbyInfo) {
if (data.status == Status.Success) {
console.debug(`Succesfully joined lobby`);
let url = new URL(window.location.href);
let qs = url.searchParams;
qs.set(`game`, data.game_code);
window.history.replaceState(null, null, url)
gameCode.set(data.game_code);
players.set(data.players);
emit(`joined`);
} else {
console.error(data);
errorMessage = data.message;
};
};
onMount(() => {
socket.on(`res:lobby.players.join`, handleLobbyConnection);
socket.on(`res:lobby.create`, handleLobbyConnection);
return () => {
socket.off(`res:lobby.players.join`);
socket.off(`res:lobby.create`);
};
});
function connectToLobby() {
errorMessage = null;
isHost.set(!joining);
// Emit the correct event for the modal type
if (joining) {
socket.emit(`req:lobby.players.join`, {
name: $myName,
game_code: $gameCode,
});
} else {
socket.emit(`req:lobby.create`, {
name: $myName
});
};
};
</script>
<BaseModal {open} on:close>
<div class="foreground">
<h1>{joining ? "Join Game" : "Create Lobby"}</h1>
<hr>
<div class="form">
<label for="name">Username:</label>
<input
type="text"
name="name"
id="name"
bind:value="{$myName}"
>
{#if joining}
<br>
<label for="game-code">Game Code:</label>
<input
type="text"
name="game code"
id="game-code"
bind:value="{$gameCode}"
>
{/if}
</div>
<br>
<SciFiButton
on:click="{connectToLobby}"
>
{joining ? "Join Game" : "Create Lobby"}
</SciFiButton>
{#if errorMessage}
<div class="error">
{errorMessage}
</div>
{/if}
</div>
</BaseModal>
<style lang="scss">
.foreground {
text-align: center;
.form {
display: inline-block;
text-align: right;
}
input[type="text"] {
background: black;
color: white;
padding: 5px 7px;
outline: none;
border-radius: 7px;
border-width: 2px;
border-color: transparent;
border-style: solid;
margin: 10px;
}
input[type="text"]:focus {
border-color: #0a0;
}
}
</style>

View file

@ -0,0 +1,22 @@
<script lang="ts">
import BaseModal from "./BaseModal.svelte";
import type { IGameOption } from "common";
export let visibleOptions: IGameOption[];
export let open: boolean = false;
</script>
<BaseModal {open} on:close>
<div class="foreground">
<h1>Option Info</h1>
<hr>
{#each visibleOptions as opt}
{#if !opt.hidden}
<h2>{opt.name}</h2>
<p>
{opt.description}
</p>
{/if}
{/each}
</div>
</BaseModal>

View file

@ -0,0 +1,205 @@
<script lang="ts">
import { Status, colours, spaceships } from "common";
import SpaceShuttle from "../icons/spaceship.svelte";
import SciFiButton from "../SciFi-Button.svelte";
import { createEventDispatcher, onMount } from "svelte";
import { myName, players } from "../../stores";
import BaseModal from "./BaseModal.svelte";
import Hexagon from "../Hexagon.svelte";
import { socket } from "../../main";
const emit = createEventDispatcher();
export let open: boolean = false;
let player = $players.find(p => p.name == $myName);
$: myColour = player.colour;
$: takenColours = $players.map(p => p.colour);
var selectedColour = player.colour.hex;
var selectedShip = player.ship.id;
var error = null;
function designUpdate(data: any) {
if (data.status != Status.Success) {
error = data.message;
/*
TODO (Maybe): If the list of colours doesn't auto-update the available
colours based on which are already taken when another player saves, we
will need to update the list manually here on Status.OutOfDate
*/
return;
};
// Alert the parent and remove any errors
error = null;
emit(`save`, {
colour: selectedColour,
ship: selectedShip,
});
emit(`close`);
};
onMount(() => {
socket.on(`req:lobby.players.update`, designUpdate);
return () => {
socket.off(`req:lobby.players.update`);
};
});
function saveShipDesign() {
/* TODO: Send event to server, wait for confirmation */
let response: any = {
status: Status.UnknownError,
message: `Testing the error design`,
};
socket.emit(`req:lobby.players.update`, {});
};
</script>
<BaseModal {open} on:close>
<div>
<h2>Spaceship Designer</h2>
<hr>
<div class="flex-container">
<div class="options">
<div class="colour">
<h3>Colour:</h3>
<!--
Create the dropdown that allows the user to see all of
the colours but not choose ones that other users
currently have selected.
-->
<select
name="colour"
id="spaceship-colour"
bind:value="{selectedColour}"
>
<!-- Display each colour as given by the server -->
{#each colours as c}
<option
value="{c.hex}"
disabled="{
c.hex != myColour.hex
&& takenColours.includes(c)
}"
>
{c.name}
</option>
{/each}
</select>
</div>
<div class="spaceship">
<h3>Spaceship</h3>
<!--
Create the dropdown that allows users to select which
spaceship icon they want for their player.
-->
<select
name="spaceship"
id="spaceship-icon"
bind:value="{selectedShip}"
>
{#each spaceships as ship}
<option value="{ship.id}">{ship.name}</option>
{/each}
</select>
</div>
</div>
<div class="preview">
<h3>Preview:</h3>
<Hexagon>
<div
class="spaceship-icon"
style="--colour: {selectedColour};"
>
<SpaceShuttle
spaceship="{selectedShip}"
/>
</div>
</Hexagon>
<SciFiButton
on:click="{saveShipDesign}"
>
Save Design
</SciFiButton>
</div>
</div>
{#if error}
<div class="error">
{error}
</div>
{/if}
</div>
</BaseModal>
<style lang="scss">
@import "../../styles/mixins";
.flex-container {
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: center;
> div {
display: flex;
align-items: center;
width: 90%;
flex-grow: 1;
margin: 5px;
flex-direction: column;
> * {
text-align: center;
width: 100%;
}
@include medium {
width: 30%;
}
}
}
h3 {
margin-bottom: 5px;
}
select {
border-radius: 5px;
background: black;
color: white;
outline: none;
border-width: 2px;
border-style: solid;
border-color: transparent;
padding: 5px 7px;
width: 90%;
&:focus {
border-color: #00aa00;
}
}
.spaceship-icon {
/*
Smaller version: 55px
Larger version: 75px
*/
--size: 80px;
width: var(--size);
height: var(--size);
display: flex;
color: var(--colour);
justify-content: center;
align-items: center;
transform: rotate(-90deg);
}
</style>

29
web-svelte/src/main.ts Normal file
View file

@ -0,0 +1,29 @@
import type { IServerInfo } from "common";
import io from "socket.io-client";
import App from "./App.svelte";
let url = "/";
if (import.meta.env.DEV) {
url = "http://localhost:3001/";
};
export const socket = io(url);
socket.on(`res:error`, (data) => {
console.error(data);
});
socket.on("connect", () => {
console.log("Connected to websocket server. Server info below:");
socket.once("res:server.info", (data: IServerInfo) => {
console.table(data);
});
socket.emit("req:server.info");
});
const app = new App({
target: document.getElementById('app')
})
export default app

63
web-svelte/src/stores.ts Normal file
View file

@ -0,0 +1,63 @@
import type { Board, FuelCard, PlayerData } from "common";
import { writable } from "svelte/store";
export const gameCode = writable<string>("");
export const state = writable<string>("game-movement");
export const myName = writable<string>("");
export const isHost = writable<boolean>(false);
export const players = writable<{[index: string]: PlayerData}>({
a: {
id: `a`,
name: `Player 1`,
host: true,
colour: { name: `green`, hex: `#00aa00` },
ship: { name: `shuttle`, id: `space-shuttle` },
fuel: {
"magnitude": 2,
"symbol": "Jo",
"name": "Jodium",
"type": "stationary"
}
},
b: {
id: `b`,
name: `Player 4`,
host: false,
colour: { name: `green`, hex: `#00a` },
ship: { name: `shuttle`, id: `rocket` },
fuel: {
"magnitude": 1,
"symbol": "Ar",
"name": "Argon",
"type": "movement"
}
},
c: {
id: `c`,
name: `Player 3`,
host: false,
colour: { name: "Magenta", hex: "#b7094c" },
ship: { name: `shuttle`, id: `rocket` },
fuel: {
"magnitude": 10,
"symbol": "Mg",
"name": "Magnesium",
"type": "movement"
}
},
d: {
id: `d`,
name: `Player 2`,
host: false,
colour: { name: "Purple", hex: "#7400b8" },
ship: { name: `shuttle`, id: `rocket` },
fuel: {
"magnitude": -2,
"symbol": "Kr",
"name": "Krypton",
"type": "movement"
}
},
});
export const board = writable<Board>(new Array(54).fill(null));
export const hand = writable<FuelCard[]>([]);

View file

@ -0,0 +1,23 @@
@mixin small {
@media (min-width: 576px) {
@content;
}
}
@mixin medium {
@media (min-width: 768px) {
@content;
}
}
@mixin large {
@media (min-width: 992px) {
@content;
}
}
@mixin x-large {
@media (min-width: 1200px) {
@content;
}
}

View file

@ -0,0 +1,119 @@
<script lang="ts">
import LobbyModal from "../components/modals/JoinLobby.svelte";
import SciFiButton from "../components/SciFi-Button.svelte";
import { state } from "../stores";
// The modals that can appear from this component
const modal = {
joinLobby: false,
};
let joiningLobby = false;
function hostGame() {
joiningLobby = false;
modal.joinLobby = true;
};
function joinGame() {
joiningLobby = true;
modal.joinLobby = true;
};
function joinedLobby() {
modal.joinLobby = false;
setTimeout(() => {
state.set("multiplayer-lobby");
}, 750);
};
function singleplayerGame() {};
</script>
<LobbyModal
open="{modal.joinLobby}"
joining="{joiningLobby}"
on:joined={joinedLobby}
on:close="{_ => modal.joinLobby = false}"
/>
<main>
<div>
<h1>Gravwell</h1>
<div class="sci-fi-box">
<h2>Multiplayer</h2>
<div class="flex-row">
<SciFiButton
on:click={joinGame}
>
Join Game
</SciFiButton>
<SciFiButton
on:click={hostGame}
>
Host Game
</SciFiButton>
</div>
</div>
<br>
<div class="sci-fi-box">
<h2>Singleplayer</h2>
<p>
Coming Soon
</p>
<div style="display: none;" class="flex-row">
<SciFiButton
on:click={singleplayerGame}
>
Start Game
</SciFiButton>
</div>
</div>
</div>
</main>
<style lang="scss">
main {
color: #FFFFFF;
width: 100%;
height: 100%;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
}
h1 {
font-size: 4rem;
font-weight: 100;
line-height: 1.1;
}
.sci-fi-box {
border-radius: 10px;
border-width: 2px;
border-style: solid;
border-color: white;
margin: 5px;
background: rgba(0, 0, 0, 0.5);
h2 {
margin: 0px;
margin-top: 10px;
}
}
.flex-row {
display: flex;
align-items: center;
justify-content: center;
}
.flex-row > :global(div) {
flex-grow: 1;
}
@media (min-width: 480px) {
h1 {
max-width: none;
}
}
</style>

View file

@ -0,0 +1,224 @@
<script lang="ts">
import ShipDesigner from "../../components/modals/ShipDesigner.svelte";
import OptionInfo from "../../components/modals/OptionInfo.svelte";
import SciFiCheckbox from "../../components/SciFi-Checkbox.svelte";
import SciFiButton from "../../components/SciFi-Button.svelte";
import { ILobbyInfo, Status, gameOptions } from "common";
import Player from "../../components/Player.svelte";
import { isHost, players } from "../../stores";
import { socket } from "../../main";
import { onMount } from "svelte";
function handleLobbyInfo(data: ILobbyInfo) {
if (data.status == Status.Success) {
for (const player of data.players) {
$players[player.id] = player;
}
} else {
console.table(data);
};
};
onMount(() => {
socket.on(`res:lobby.info`, handleLobbyInfo);
return () => {
socket.off(`res:lobby.info`);
};
});
function tempButtonHandler() {};
// The modals that can appear in the lobby
const modal = {
options: false,
shipDesigner: false,
};
$: visibleOptions = gameOptions.filter(x => !x.hidden);
/** Copies the game link to clipboard for easy sharing */
function copyLinkToClipboard() {
navigator.clipboard.writeText(window.location.href);
};
/**
* What gets called when the host toggles one of the options in the lobby so
* that the other players can see the change happen on their screen.
*/
function toggleOption(e: CustomEvent<string>) {
let option = visibleOptions.find(x => x.id == e.detail);
console.debug(`Toggled option: ${option.name}`);
// TODO: Send websocket event to server
};
</script>
<OptionInfo
open="{modal.options}"
{visibleOptions}
on:close="{_ => modal.options = false}"
/>
<ShipDesigner
open="{modal.shipDesigner}"
on:selectColour="{tempButtonHandler}"
on:close="{_ => modal.shipDesigner = false}"
/>
<main>
<div>
<h1>Gravwell</h1>
<SciFiButton
width="69%"
on:click={copyLinkToClipboard}
>
Copy Game Link
</SciFiButton>
<div class="sci-fi-box">
<h2>Players</h2>
<div class="player-box">
{#each Object.entries($players) as [id, p], i (id)}
<Player
name="{p.name}"
colour="{p.colour}"
on:changeColour="{_ => modal.shipDesigner = true}"
/>
{#if i == 1}
<div class="divider"></div>
{/if}
{/each}
</div>
</div>
<div class="sci-fi-box">
<div class="info-button-container">
<SciFiButton
on:click={_ => modal.options = true}
height="60px"
width="60px"
>
<div style="display: flex; align-items: center; justify-content: center;">
<span class="material-icons">help_outline</span>
</div>
</SciFiButton>
</div>
<h2>Options</h2>
<div class="options-box">
{#each visibleOptions as option}
<div>
<SciFiCheckbox
name="{option.name}"
id="{option.id}"
disabled="{!$isHost}"
bind:state="{option.active}"
on:toggle="{toggleOption}"
>
{option.name}
</SciFiCheckbox>
</div>
{/each}
</div>
</div>
<div class="flex-row">
<SciFiButton
title="Starts the game"
on:click={tempButtonHandler}
>
Start Game
</SciFiButton>
<SciFiButton
title="Delete the lobby, preventing further games from being played"
background="#ff0000"
on:click={tempButtonHandler}
>
Delete Game
</SciFiButton>
</div>
</div>
</main>
<style lang="scss">
main {
color: #FFFFFF;
width: 100%;
height: 100%;
text-align: center;
display: flex;
justify-content: center;
}
h1 {
font-size: 4rem;
font-weight: 100;
line-height: 1.1;
}
.sci-fi-box {
border-radius: 10px;
border-width: 2px;
border-style: solid;
border-color: white;
margin: 5px;
background: rgba(0, 0, 0, 0.5);
position: relative;
h2 {
margin: 10px 0px;
}
}
.player-box {
display: flex;
flex-wrap: wrap;
justify-content: center;
> .divider {
width: 100%;
}
> :global(div) {
flex-grow: 1;
}
}
.options-box {
padding-bottom: 5px;
display: flex;
justify-content: space-evenly;
flex-wrap: wrap;
> div {
background: rgba(255, 255, 255, 0.1);
border-radius: 5px;
padding: 3px 7px;
margin: 5px 5px 10px 5px;
}
> div:first-child {
margin-left: 10px;
}
> div:last-child {
margin-right: 10px;
}
}
.info-button-container {
position: absolute;
top: -2px;
right: -2px;
}
.flex-row {
display: flex;
align-items: center;
justify-content: center;
}
.flex-row > :global(div) {
flex-grow: 1;
}
@media (min-width: 480px) {
h1 {
max-width: none;
}
}
</style>

View file

@ -0,0 +1,122 @@
<script lang="ts">
import ShipsMoving from "../components/ShipsMoving.svelte";
import PlayerHand from "../components/PlayerHand.svelte";
import Board from "../components/Board.svelte";
import { board } from "../stores";
const controlsState: string = "player-order";
const notice = {
visible: false,
message: null,
classes: [],
};
const preview = {
visible: false,
board: null,
};
function previewBoard(e: any) {
let { card, visible } = e.detail;
if (visible) {
notice.message = `This is a preview of what the board would look like if you played ${card.name}, it doesn't guarantee the outcome of playing the card.`;
notice.classes = [ `warning` ];
preview.board = [ ...$board ];
preview.board[Math.abs(card.magnitude)] = "a";
} else {
notice.message = null;
notice.classes = [];
preview.board = null;
};
preview.visible = visible;
notice.visible = visible;
};
</script>
<main>
{#if !preview.visible || !preview.board}
<div id="main-board">
<Board board="{$board}"/>
</div>
{:else}
<div id="preview-board">
<Board board="{preview.board}"/>
</div>
{/if}
{#if notice.visible}
<div id="notice-container">
<div class="notice {notice.classes.join(` `)}">
{notice.message}
</div>
</div>
{/if}
{#if controlsState == "player-hand"}
<div id="player-controls">
<PlayerHand
on:hover="{previewBoard}"
/>
</div>
{:else if controlsState == "player-order"}
<div id="player-controls">
<ShipsMoving />
</div>
{/if}
</main>
<style lang="scss">
main {
display: grid;
width: 100%;
height: 100%;
grid-template-rows: 15px 70fr 75px 30fr 15px;
grid-template-columns: 15px 100fr 15px;
}
#main-board {
grid-column: 2 / 3;
grid-row: 2 / 3;
}
#preview-board {
z-index: 10;
grid-column: 2 / 3;
grid-row: 2 / 3;
}
#notice-container {
align-items: center;
display: flex;
grid-column: 2 / 3;
grid-row: 3 / 4;
justify-content: center;
.notice {
text-align: center;
align-items: center;
display: flex;
justify-content: center;
width: 75%;
height: 90%;
border-radius: 10px;
&.warning {
background: yellowgreen;
}
&.error {
background: red;
}
&.info {
background: blue;
}
}
}
#player-controls {
grid-column: 2 / 3;
grid-row: 4 / 5;
}
</style>

2
web-svelte/src/vite-env.d.ts vendored Normal file
View file

@ -0,0 +1,2 @@
/// <reference types="svelte" />
/// <reference types="vite/client" />

View file

@ -0,0 +1,13 @@
import sveltePreprocess from "svelte-preprocess";
import * as sass from "sass";
export default {
// Consult https://github.com/sveltejs/svelte-preprocess
// for more information about preprocessors
preprocess: sveltePreprocess({
sass: {
sync: true,
implementation: sass
}
})
}

19
web-svelte/tsconfig.json Normal file
View file

@ -0,0 +1,19 @@
{
"extends": "@tsconfig/svelte/tsconfig.json",
"compilerOptions": {
"target": "esnext",
"useDefineForClassFields": true,
"module": "esnext",
"resolveJsonModule": true,
"baseUrl": ".",
/**
* Typecheck JS in `.svelte` and `.js` files by default.
* Disable checkJs if you'd like to use dynamic types in JS.
* Note that setting allowJs false does not prevent the use
* of JS in `.svelte` files.
*/
"allowJs": true,
"checkJs": true
},
"include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.js", "src/**/*.svelte"]
}

View file

@ -0,0 +1,7 @@
import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [svelte()]
})

4
web-svelte/z-indexes.txt Normal file
View file

@ -0,0 +1,4 @@
-100000 = Star Particle Layer
100 = Main Modal Layer
101 = Modal Content Layer