commit
7ac1b4c110
17 changed files with 236 additions and 156 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -5,7 +5,8 @@ node_modules
|
|||
server/server.toml
|
||||
server/built/*
|
||||
server/dist/*
|
||||
server/resources/games
|
||||
server/resources/*
|
||||
*.log
|
||||
|
||||
#=============================================================================#
|
||||
# The files that were auto-generated into a .gitignore by Vue-cli
|
||||
|
|
|
|||
52
server/README.md
Normal file
52
server/README.md
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# Setup:
|
||||
1. `cd` into this `server` directory.
|
||||
2. Run `pnpm install` to install all of the required dependencies.
|
||||
3. Create a copy of the `template.toml` file, and name it `server.toml`.
|
||||
4. Edit the `server.toml` file to adjust the
|
||||
5. Run `tsc` to compile the TypeScript into Javascript. This should create a
|
||||
`dist` directory.
|
||||
|
||||
|
||||
## Using systemd to manage the server: (Not currently implemented)
|
||||
This app comes with a `ghost-writer.service` file which is already set up to
|
||||
manage the server, it just requires a little bit of additional setup. If you
|
||||
change any of the symlinking in the steps below, it is your responsibility to
|
||||
figure it out, I will not guarantee support for people who attempt to modify
|
||||
the service file.
|
||||
|
||||
6. Create a symlink named `server` in the server root (`/`) pointing to the
|
||||
server folder in the Ghost Writer git repository.
|
||||
(Ex: `sudo ln -s ~/Ghost-Writer-Online/server /server`)
|
||||
7. Create a symlink named `ghost-writer.service` in `/etc/systemd/system`
|
||||
pointing to the service file in the `server` folder of the Ghost Writer Online
|
||||
repository. (Ex: `sudo ln -s /etc/systemd/system/ghost-writer.service /server/ghost-writer.service`)
|
||||
8. Start the websocket server with `sudo systemctl start ghost-writer`.
|
||||
9. Make sure the server is started by running `systemctl status ghost-writer`
|
||||
|
||||
* To restart the server, run `sudo systemctl restart ghost-writer`.
|
||||
* To stop the server, run `sudo systemctl stop ghost-writer`.
|
||||
* To access the server logs, run `sudo journalctl -u ghost-writer`
|
||||
|
||||
|
||||
## Using a detached terminal:
|
||||
If you would prefer that the server does not automatically restart and you can
|
||||
more easily output log output to a file, you can run the server by following
|
||||
the below steps:
|
||||
|
||||
6. Make sure you know how to invoke a single command with your preferred method
|
||||
of detaching terminals.
|
||||
7. Have the detached terminal's working directory be in the `server` folder of
|
||||
this repository.
|
||||
8. Run `node dist/main.js` for logging directly to the CLI, or
|
||||
`node dist/main.js > output.log` for logging to a file named `output.log`.
|
||||
9. Detach your terminal.
|
||||
|
||||
|
||||
---
|
||||
|
||||
# Configuration:
|
||||
All of the configuration for the server is done in the `server.toml` with
|
||||
explanations in the file. When a value is changed in the config, you will need
|
||||
to restart the server. This is either through systemd
|
||||
(`sudo systemctl restart ghost-writer`) or through your preferred terminal
|
||||
detacher, by stopping then re-starting the process.
|
||||
9
server/ghost-writer.service
Normal file
9
server/ghost-writer.service
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
[Unit]
|
||||
Description=Ghost Writer websocket server
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory=/server
|
||||
ExecStart=node /server/dist/main.js
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
Answer 1,Answer 2,Answer 3,Answer 4,Answer 5
|
||||
microphone,lamp,chair,crown,brain
|
||||
screwdriver,chainsaw,sunglasses,dinosaur,tree
|
||||
flag,ship,pie,pineapple,helmet
|
||||
ruler,pen,guitar,skateboard,tomato
|
||||
bathtub,computer,whale,telescope,submarine
|
||||
flower,blanket,pacifier,harp,acorn
|
||||
window,doll,stove,skeleton,vacuum
|
||||
printer,shoe,bed,fork,shield
|
||||
catapult,apple,envelope,broom,windmill
|
||||
flashlight,pyramid,brick,bicycle,truck
|
||||
train,horse,knife,sponge,shirt
|
||||
newspaper,clock,basketball,mirror,backpack
|
||||
phone,car,dragon,platypus,scissors
|
||||
broccoli,shovel,scorpion,sandwich,piano
|
||||
pillow,pencil,ladder,laptop,headphones
|
||||
tent,toothbrush,hammer,coin,peanut
|
||||
bottle,key,pants,fish,pizza
|
||||
bowl,bread,wheelchair,rope,giraffe
|
||||
toilet,house,cloak,soap,banana
|
||||
painting,tractor,sword,book,umbrella
|
||||
|
|
|
@ -1,82 +0,0 @@
|
|||
Question Text
|
||||
Where would I be most likely to find it?
|
||||
What continent/region would I find the most of these?
|
||||
What country am I most likely to find it in?
|
||||
What city am I most likely to find it in?
|
||||
Where in my house am I most likely to find it?
|
||||
What kind of store am I most likely to find it in?
|
||||
What era did it first appear in?
|
||||
What's something that's about as dangerous as it?
|
||||
What's a variety it comes in?
|
||||
What subcategory does it fall under?
|
||||
What's something that's like it?
|
||||
What color is it most commonly?
|
||||
What material is it made of?
|
||||
What does it feel like if I touch it?
|
||||
How is it made?
|
||||
What is it used for?
|
||||
What's a common brand of it?
|
||||
What's it about the same size as?
|
||||
What's it about the same weight as?
|
||||
What does it cost about the same as?
|
||||
What would happen if I ate it?
|
||||
What's your favorite version/variety of it?
|
||||
Who's a fictional character that has/uses it?
|
||||
Who's a celebrity that has/uses it?
|
||||
What kind of people commonly have or use it?
|
||||
What abstract concept is associated with it?
|
||||
What does it smell like?
|
||||
What does it taste like?
|
||||
What noise does it make when dropped?
|
||||
Who at this table likes it the most?
|
||||
"If it was an animal, what animal would it be?"
|
||||
What's a book or movie/TV that it appears prominently in?
|
||||
What age group likes it the most?
|
||||
How do you feel about it?
|
||||
Who dislikes it?
|
||||
What time of day is it used?
|
||||
What do I wear when I use it?
|
||||
Why do people use it?
|
||||
With what frequency do most people use it?
|
||||
What part of the body do I use it with?
|
||||
What do I clean it with?
|
||||
How is it transported?
|
||||
Where is it stored?
|
||||
Where do you use it?
|
||||
What holiday is it most associated with?
|
||||
What would I use to write on it?
|
||||
What's a game that it appears in?
|
||||
What climate is it most associated with?
|
||||
What website can I find it on?
|
||||
How long will it last if left alone?
|
||||
How does it affect the things around it?
|
||||
Where would I get one?
|
||||
How would it react to being pushed down a hill?
|
||||
What happens if I light it on fire?
|
||||
What happens if I put it under water?
|
||||
What superpower would this thing want?
|
||||
How would I use it as a weapon?
|
||||
How do I feel after using it?
|
||||
What would happen if I bent it?
|
||||
What would I use to destroy it?
|
||||
What's inside it?
|
||||
What's its opposite?
|
||||
What profession works with it?
|
||||
"If it were a musical instrument, what instrument would it be?"
|
||||
"If it was a social media website, what site would it be?"
|
||||
What genre of movie/book is most likely to feature it?
|
||||
"If it had a favorite food, what would it be?"
|
||||
What's something you can make using it?
|
||||
What powers it?
|
||||
What container would I keep it in?
|
||||
What's the secondary material it's made of? (not its main material)
|
||||
Who or what can lift it?
|
||||
What ancient Greek/Roman god is it most associated with?
|
||||
What habitat would I be most like to find it in?
|
||||
What is a group of them called?
|
||||
Who or what makes it?
|
||||
What field of science studies it?
|
||||
"Where does it go when it dies, breaks, or is no longer useful?"
|
||||
What superhero is it most related to?
|
||||
What month would I find it or use it in?
|
||||
"If it was an alcoholic drink, what would it be?"
|
||||
|
|
|
@ -40,20 +40,16 @@ export default (io: Server, socket: Socket, data: JoinGame) => {
|
|||
game.log.silly(`${host.name} is one of the team's guessers`);
|
||||
hand = team.hand;
|
||||
team.guessers.push(host);
|
||||
socket.join([
|
||||
`${game.id}:*:guesser`,
|
||||
`${game.id}:${team.id}:guesser`
|
||||
]);
|
||||
break;
|
||||
case "writer":
|
||||
game.log.silly(`${host.name} is the team's writer`);
|
||||
team.writer = host;
|
||||
socket.join([
|
||||
`${game.id}:*:writer`,
|
||||
`${game.id}:${team.id}:writer`
|
||||
]);
|
||||
break;
|
||||
};
|
||||
socket.join([
|
||||
`${game.id}:*:${host.role}`,
|
||||
`${game.id}:${host.team}:${host.role}`
|
||||
]);
|
||||
game.log.debug(`Host assigned to team`);
|
||||
};
|
||||
|
||||
|
|
@ -103,14 +99,20 @@ export default (io: Server, socket: Socket, data: JoinGame) => {
|
|||
|
||||
if (!sameName.socket?.connected) {
|
||||
sameName.socket = socket;
|
||||
let rooms: string[] = [game.id];
|
||||
game.log.info(`Player Reconnected to the game (name=${data.name})`);
|
||||
|
||||
// Get the hand of the player's team
|
||||
let hand: string[] = [];
|
||||
if (sameName.team && sameName.role == `guesser`) {
|
||||
hand = game.teams[sameName.team - 1].hand;
|
||||
rooms.push(
|
||||
`${game.id}:*:${sameName.role}`,
|
||||
`${game.id}:${sameName.team}:${sameName.role}`
|
||||
);
|
||||
};
|
||||
|
||||
socket.join(rooms);
|
||||
socket.emit(`GameRejoined`, {
|
||||
status: 200,
|
||||
ingame: game.ingame,
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ export default (io: Server, socket: Socket, data: SelectObject) => {
|
|||
|
||||
game.log.debug(`Object has been chosen: ${data.choice}`);
|
||||
game.object = data.choice;
|
||||
io.to(`${game.id}:*:writer`).emit(`ChosenObject`, {
|
||||
io.to(game.id).emit(`ChosenObject`, {
|
||||
status: 200,
|
||||
choice: data.choice,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ export var hibernatedGames: string[] = [];
|
|||
export var games: {[index: string]: Game} = {};
|
||||
|
||||
export const log: Logger = new Logger({
|
||||
displayDateTime: conf.log.datetime,
|
||||
displayFunctionName: false,
|
||||
displayLoggerName: true,
|
||||
displayFilePath: `hidden`,
|
||||
|
|
|
|||
1
server/src/types/config.d.ts
vendored
1
server/src/types/config.d.ts
vendored
|
|
@ -1,6 +1,7 @@
|
|||
interface config {
|
||||
log: {
|
||||
level: `silly` | `debug` | `info` | `error` | `warn` | `fatal` | `trace`;
|
||||
datetime: boolean;
|
||||
};
|
||||
websocket: {
|
||||
port: number;
|
||||
|
|
|
|||
|
|
@ -47,18 +47,10 @@ column = 0
|
|||
fingerprint = ''
|
||||
|
||||
|
||||
[webserver]
|
||||
# whether or not to enable the integrated webserver.
|
||||
enabled = false
|
||||
|
||||
# The port the web server should run on
|
||||
port = 8080
|
||||
|
||||
|
||||
[websocket]
|
||||
# The port the websocket server should run on. This must also be duplicated
|
||||
# into the `main.js` file within the `web` folder so that the client attempts
|
||||
# to connect to the correct port on the server.
|
||||
# The port the websocket server should run on. This is the port that you will
|
||||
# need to target for the web server's reverse proxy, or the specific URI in the
|
||||
# web code itself.
|
||||
port = 8081
|
||||
|
||||
# The hostnames (and protocols) that are allowed to connect to the server. For
|
||||
|
|
@ -71,6 +63,21 @@ port = 8081
|
|||
permitted_hosts = "http://localhost:8080"
|
||||
|
||||
|
||||
[datastores]
|
||||
|
||||
# Whether or not to save games that are active to the server's hard drive when
|
||||
# it crashes for whatever reason. This will also enable loading of those games
|
||||
# on server startup and allows game clients to reconnect to the game.
|
||||
enabled = false
|
||||
|
||||
# The file extension used to save and load the datastore files.
|
||||
filetype = "game"
|
||||
|
||||
# The filepath pointing to where to save the datastore files to. This can
|
||||
# either be a relative or absolute filepath.
|
||||
directory = ""
|
||||
|
||||
|
||||
[log]
|
||||
# The log level to output to the CLI, this can be one of the following:
|
||||
# - "silly"
|
||||
|
|
@ -80,4 +87,7 @@ permitted_hosts = "http://localhost:8080"
|
|||
# - "error"
|
||||
# - "trace"
|
||||
# any other value will prevent the server from starting at runtime.
|
||||
level = "silly"
|
||||
level = "info"
|
||||
|
||||
# Whether or not the log should output the date and time information in the log
|
||||
datetime = true
|
||||
|
|
@ -1,19 +1,73 @@
|
|||
## Project setup
|
||||
```
|
||||
pnpm install
|
||||
```
|
||||
# Setup:
|
||||
1. `cd` into this `web` directory.
|
||||
2. Run `pnpm install` to install all of the required dependencies for the site.
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
pnpm run serve
|
||||
```
|
||||
Now jump to the [Production](#Production) or [Development](#Development)
|
||||
section and continue the steps there.
|
||||
|
||||
### Compiles and minifies for production
|
||||
```
|
||||
pnpm run build
|
||||
```
|
||||
---
|
||||
|
||||
### Lints and fixes files
|
||||
## Production
|
||||
3. Run `pnpm build` to create the folder with all the files for the webserver.
|
||||
4. Create a symlink pointing to the `dist/` directory that was created in the
|
||||
last step.
|
||||
5. Tell your favourite web server (I'd recommend nginx) for serving the files
|
||||
from that directory.
|
||||
6. In the webserver of your choice, you must also setup an
|
||||
`example.com/socket.io` route that reverse proxies the websocket connection
|
||||
through to the Node.js server. If you do not want to set up this proxy, you can
|
||||
bypass it by changing the URI in the `serc/main.js` file to have a specific URL and port.
|
||||
|
||||
Example: Change
|
||||
```js
|
||||
// This is the default value that will require a
|
||||
// "/socket.io" proxy in your web server.
|
||||
let websocket_uri = `/`;
|
||||
```
|
||||
pnpm run lint
|
||||
to:
|
||||
```js
|
||||
// this is the specific domain and port to connect to, as an example on how to
|
||||
// bypass setting up a reverse proxy.
|
||||
let websocket_uri = `http://example.com:1234`;
|
||||
```
|
||||
7. Once the reverse proxy or specific URI is setup, connect to your website through the appropriate domain and check to see if it worked.
|
||||
|
||||
|
||||
## Development:
|
||||
3. Make sure the websocket server is started either through a terminal or
|
||||
through the systemd service on port `8081` for local development. (if you want
|
||||
to connect to a websocket server that is not on port 8081 for development, you
|
||||
will need to change the port value in `src/main.js` to the correct port number)
|
||||
4. Run `pnpm serve`, this will start a local hot-reloading server on
|
||||
`localhost:8080`. **DO NOT** reverse-proxy this server. It serves files that
|
||||
are not optimized nor minified.
|
||||
5. Go to `localhost:8080` in your web browser of choice.
|
||||
|
||||
|
||||
---
|
||||
|
||||
# Configuration:
|
||||
Configuring the site is pretty simple, the most complicated part is already
|
||||
done because you have a functional web server.
|
||||
|
||||
In order to edit any of the following settings, go to `src/store/index.js` and
|
||||
find the respective value in the section that allows editing. **Important**:
|
||||
Any values edited in this file will require re-building the site using
|
||||
`pnpm build`, and the changes will only take place after players refresh their
|
||||
web browser.
|
||||
|
||||
| Variable | Type | Description
|
||||
| -------- | ---- | -----------
|
||||
| survey_link | string | This is the link to the survey that will appear in the player's hand once the object has been correctly guessed and in the attributions modal. This can be left empty or `null` without any problems, the buttons will be hidden.
|
||||
| name* | string | The name of the team.
|
||||
| icon* | string | The filename of the icon used for the team reminder for the team. This assumes the file is in the `public/assets/` directory.
|
||||
| eyes* | other | This is an object of numbers used to indicate how many eyes appear on that slot of the game board for the team.
|
||||
| writer_name | string | How the interface should refer to the players who are receiving cards and writing.
|
||||
| writer_card_button | string | The text that appears on the cards that are given to the writer by the guessers.
|
||||
| writer_object_choose_button | string | The text that appears on the object choices for the writers to select one for them to use before being allowed to start giving clues to the guessers.
|
||||
| guesser_name | string | How the interface should refer to the players who are sending the cards and guessing what the object is.
|
||||
| guesser_card_button | string | The text that appears on the button for the cards in the guesser's hand in order to give the card to the writer.
|
||||
| eye_icon | string | The filename of the eye icon. This assumes the file is in the `public/assets/` directory.
|
||||
| discard_hand_icon | string | The filename of the icon used on the guesser's discard hand button. This assumes the file is in the `public/assets/` directory.
|
||||
|
||||
\* _Note_: These exist for teams 1 and 2, they can each have their own values (and it is recommended that they don't have the same values).
|
||||
|
|
@ -12,7 +12,21 @@
|
|||
>
|
||||
<h2 class="centre">Attributions:</h2>
|
||||
<p class="centre">
|
||||
Made By: Oliver Akins
|
||||
Ghost Writer is designed and created by
|
||||
<a href="https://resonym.com" target="_blank" rel="noopener">Resonym</a>
|
||||
<br>
|
||||
Online Prototype Made By: Oliver Akins (Alkali Metal)
|
||||
<br>
|
||||
<a
|
||||
v-if="$store.state.survey_link"
|
||||
:href="$store.state.survey_link"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<button class="clickable">
|
||||
Complete The Survey
|
||||
</button>
|
||||
</a>
|
||||
</p>
|
||||
<hr>
|
||||
<p>
|
||||
|
|
@ -77,6 +91,15 @@ export default {
|
|||
height: 100%;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: var(--background2);
|
||||
color: var(--background2-text);
|
||||
border-radius: 7px;
|
||||
font-size: large;
|
||||
padding: 10px;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
a, a:visited {
|
||||
color: var(--light-font-colour);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,17 +62,6 @@ export default {
|
|||
*/
|
||||
this.objects = data.objects;
|
||||
},
|
||||
ChosenObject(data) {
|
||||
/**
|
||||
* Sent to all clients so that they can set their store data and in
|
||||
* turn stay synchronized on what object they are trying to get
|
||||
* their teammate to guess.
|
||||
*/
|
||||
if (data.status < 200 || 300 <= data.status) {
|
||||
this.$emit(`error`, data);
|
||||
};
|
||||
this.$store.commit(`setObject`, data.choice);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.getObjects();
|
||||
|
|
|
|||
|
|
@ -243,9 +243,9 @@ input[type="text"] {
|
|||
border-width: 2px;
|
||||
font-size: larger;
|
||||
outline: none;
|
||||
margin: 7px 0;
|
||||
padding: 7px;
|
||||
width: 90%;
|
||||
margin: 0;
|
||||
}
|
||||
input[type="text"]:focus {
|
||||
border-color: var(--board-background-text);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,16 @@
|
|||
{{ mostRecentQuestion }}
|
||||
</div>
|
||||
<div class="flex-center" v-else-if="gameOver">
|
||||
<a
|
||||
v-if="$store.state.survery_link"
|
||||
:href="$store.state.survey_link"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<button class="clickable">
|
||||
Complete The Survey
|
||||
</button>
|
||||
</a>
|
||||
<button
|
||||
class="clickable"
|
||||
@click.stop="endGame"
|
||||
|
|
@ -62,9 +72,12 @@ export default {
|
|||
return this.$store.state.questions;
|
||||
},
|
||||
gameOver() {
|
||||
if (this.$store.state.chosen_object) {
|
||||
let targetAnswer = this.$store.state.chosen_object.toLowerCase()+`.`;
|
||||
return this.$store.state.answers.team_1.includes(targetAnswer)
|
||||
|| this.$store.state.answers.team_2.includes(targetAnswer);
|
||||
};
|
||||
return false;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -129,6 +142,18 @@ export default {
|
|||
console.error(`Server returned an unsupported mode: ${data.mode}`);
|
||||
};
|
||||
},
|
||||
GameReset(data) {
|
||||
if (data.status < 200 || 300 <= data.status) {
|
||||
return this.$emit(`error`, data);
|
||||
};
|
||||
this.$store.commit(`setAnswers`, {
|
||||
team_1: new Array(8).fill(``),
|
||||
team_2: new Array(8).fill(``),
|
||||
});
|
||||
this.$store.commit(`replaceHand`, []);
|
||||
this.$store.commit(`setObject`, null);
|
||||
this.$store.commit(`view`, `lobby`);
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ Vue.use(Vuex);
|
|||
|
||||
export default new Vuex.Store({
|
||||
state: {
|
||||
survey_link: ``,
|
||||
|
||||
team_1: {
|
||||
name: `Sun`,
|
||||
icon: `sun.svg`,
|
||||
|
|
@ -31,6 +33,7 @@ export default new Vuex.Store({
|
|||
|
||||
guesser_name: `Medium`,
|
||||
guesser_card_button: `Ask Spirit`,
|
||||
|
||||
eye_icon: `eye.svg`,
|
||||
|
||||
discard_hand_icon: `trash.svg`,
|
||||
|
|
|
|||
|
|
@ -48,6 +48,19 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {},
|
||||
sockets: {
|
||||
ChosenObject(data) {
|
||||
/**
|
||||
* Sent to all clients so that they can set their store data and in
|
||||
* turn stay synchronized on what object they are trying to get
|
||||
* their teammate to guess.
|
||||
*/
|
||||
if (data.status < 200 || 300 <= data.status) {
|
||||
this.$emit(`error`, data);
|
||||
};
|
||||
this.$store.commit(`setObject`, data.choice);
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue