diff --git a/backend/index.js b/backend/index.js index 56e468f..6daf717 100644 --- a/backend/index.js +++ b/backend/index.js @@ -84,49 +84,56 @@ const client = new MongoClient(process.env.URI, { useUnifiedTopology: true }); }); }); + async function drawCard() { + let card; + // Тянем карты и отбрасываем их в соответствии с их вероятностью отбрасывания + do { + /* eslint-disable no-await-in-loop */ + card = await cardsCollection.aggregate([ + { + $sample: { size: 1 }, + }, { + $unset: ['link', 'date'], + }, + ]).toArray(); + /* eslint-enable no-await-in-loop */ + [card] = card; + } while (Math.random() < dropProb[card.name]); + return card; + } + + let players = {} + let oldPlayers = {} + let score = {} + let card = await drawCard() + let oldCard = null; + let answers = 0 + app.post('/api/auth', async (req, res) => { - if (req.session.loggedIn) { - res.status(200).send('Logged in'); + const { pass, username } = req.body; + + if (username && !Object.keys(players).includes(username)) { + players[username] = null; + emitter.emit('connection', Object.keys(players)) + } + + if (pass && pass.toLowerCase() === process.env.PASSWORD) { + req.session.loggedIn = true; + return res.status(200).send('Logged in'); } else { - try { - const { pass } = req.body; - if (pass && pass.toLowerCase() === process.env.PASSWORD) { - req.session.loggedIn = true; - res.status(200).send('Logged in'); - } else { - res.status(401).send('Wrong password'); - } - } catch (e) { - console.log(`Error: ${e}`); - res.status(500).send(); - } + return res.status(401).send('Wrong password'); } }); app.get('/api/card', async (req, res) => { - async function drawCard() { - let card; - // Тянем карты и отбрасываем их в соответствии с их вероятностью отбрасывания - do { - /* eslint-disable no-await-in-loop */ - card = await cardsCollection.aggregate([ - { - $sample: { size: 1 }, - }, { - $unset: ['name', 'link', 'date'], - }, - ]).toArray(); - /* eslint-enable no-await-in-loop */ - [card] = card; - } while (Math.random() < dropProb[card.name]); - return card; + if (!req.session.loggedIn) { + return res.status(403).send(); } - if (req.session.loggedIn) { - res.status(200).send(await drawCard()); - } else { - res.status(403).send(); - } + return res.status(200).send({ + ...card, + name: undefined + }); }); app.get('/api/meme', async (req, res) => { @@ -142,7 +149,6 @@ const client = new MongoClient(process.env.URI, { useUnifiedTopology: true }); } }); - const state = {} app.post('/api/answer', async (req, res) => { if (!req.session.loggedIn) { return res.status(403).send(); @@ -168,8 +174,11 @@ const client = new MongoClient(process.env.URI, { useUnifiedTopology: true }); selected: req.body.data.name, }); - state[req.body.data.username] = true - emitter.emit('answer', req.body.data.username); + players[req.body.data.username] = req.body.data.name; + emitter.emit('answer', { + username: req.body.data.username, + selected: req.body.data.name + }); // return res.status(200).send({ // correct, @@ -193,31 +202,33 @@ const client = new MongoClient(process.env.URI, { useUnifiedTopology: true }); }); app.get('/api/stats', async (req, res) => { - if (req.session.loggedIn) { - answersCollection.aggregate([ - { - $group: { - _id: '$selected', - correct: { - $sum: { - $cond: [ - '$correct', 1, 0, - ], - }, + if (!req.session.loggedIn) { + return res.status(403).send(); + } + + const stats = await answersCollection.aggregate([ + { + $group: { + _id: '$selected', + correct: { + $sum: { + $cond: [ + '$correct', 1, 0, + ], }, - wrong: { - $sum: { - $cond: [ - '$correct', 0, 1, - ], - }, + }, + wrong: { + $sum: { + $cond: [ + '$correct', 0, 1, + ], }, }, }, - ]).toArray().then((stats) => res.status(200).send(stats)); - } else { - res.status(403).send(); - } + }, + ]).toArray(); + + return res.status(200).send(stats); }); app.get('/api/options', async (req, res) => { @@ -228,27 +239,20 @@ const client = new MongoClient(process.env.URI, { useUnifiedTopology: true }); } }); - let usernames = new Set(); - app.post('/api/connect', async (req, res) => { + app.get('/api/players', async (req, res) => { if (!req.session.loggedIn) { return res.status(403).send(); } - const { username } = req.body; - usernames.add(username); - state[username] = false; - emitter.emit('connection', usernames) - - return res.status(200).send(); - }); + return res.status(200).send(Object.keys(players)); + }) app.post('/api/end', async (req, res) => { if (!req.session.loggedIn) { return res.status(403).send(); } - usernames = new Set(); - state = {} + players = {} return res.status(200).send(); }); @@ -261,21 +265,45 @@ const client = new MongoClient(process.env.URI, { useUnifiedTopology: true }); }); res.flushHeaders(); - emitter.on('connection', (usernames) => { - const data = usernames + emitter.on('connection', (players) => { + const data = players res.write(`data: ${JSON.stringify(data)}\nevent: userlist\n\n`); }) - emitter.on('answer', (username) => { + emitter.on('answer', async ({ + username, selected + }) => { const data = { username, - state + players, + selected } res.write(`data: ${JSON.stringify(data)}\nevent: answer\n\n`); + + answers += 1 + + if (answers === Object.keys(players).length) { + Object.keys(players).forEach((key) => { + if (card.name === players[key]) { + score[key] = (score[key] ?? 0) + 1 + } + }) + + Object.keys(players).forEach((key) => { + players[key] = null + }) + answers = 0 + oldCard = { ...card } + card = await drawCard() + emitter.emit('allDone'); + } }); emitter.on('allDone', () => { - const data = {} + const data = { + correctAnswer: oldCard.name, + score + } res.write(`data: ${JSON.stringify(data)}\nevent: reveal\n\n`); }); @@ -284,17 +312,5 @@ const client = new MongoClient(process.env.URI, { useUnifiedTopology: true }); }); }); - let answers = 0; - emitter.on('answer', () => { - answers += 1; - - if (answers === usernames.length) { - Object.keys(state).forEach((key) => { - state[key] = false - }) - emitter.emit('allDone'); - } - }); - app.listen(process.env.PORT, () => console.log(`Server started on ${process.env.PORT}`)); })(); diff --git a/frontend/public/index.html b/frontend/index.html similarity index 66% rename from frontend/public/index.html rename to frontend/index.html index 9c03737..f632fd8 100644 --- a/frontend/public/index.html +++ b/frontend/index.html @@ -7,13 +7,14 @@ - - + + Флекспатруль мультиплеер +
diff --git a/frontend/package.json b/frontend/package.json index cb0666d..463658c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -4,33 +4,31 @@ "version": "0.1.0", "private": true, "scripts": { - "serve": "vue-cli-service serve", - "build": "vue-cli-service build", - "lint": "vue-cli-service lint" + "dev": "vite dev", + "build": "vite build" }, "dependencies": { "axios": "^0.21.1", - "core-js": "^3.6.5", "pinia": "^2.1.4", + "pinia-plugin-persistedstate": "^3.1.0", "vue": "^3.3.4", "vue-peel": "^0.1.1", "vue-router": "4", "vue-spinner": "^1.0.4" }, "devDependencies": { - "@vue/cli-plugin-babel": "~4.5.0", - "@vue/cli-plugin-eslint": "~4.5.0", - "@vue/cli-service": "~4.5.0", - "@vue/compiler-sfc": "^3.0.0", - "babel-eslint": "^10.1.0", + "@vitejs/plugin-vue": "^4.2.3", "dotenv-webpack": "^7.0.2", - "eslint": "^6.7.2", - "eslint-plugin-vue": "^7.0.0" + "eslint": "8", + "eslint-plugin-vue": "8", + "sass": "^1.63.4", + "vite": "^4.3.9" }, "eslintConfig": { "root": true, "env": { - "node": true + "node": true, + "es2022": true }, "extends": [ "plugin:vue/vue3-recommended", diff --git a/frontend/public/fonts/PPNeueMontreal-Bold.otf b/frontend/public/fonts/PPNeueMontreal-Bold.otf new file mode 100644 index 0000000..6019f60 Binary files /dev/null and b/frontend/public/fonts/PPNeueMontreal-Bold.otf differ diff --git a/frontend/public/fonts/PPNeueMontreal-Medium.otf b/frontend/public/fonts/PPNeueMontreal-Medium.otf new file mode 100644 index 0000000..315ac60 Binary files /dev/null and b/frontend/public/fonts/PPNeueMontreal-Medium.otf differ diff --git a/frontend/public/fonts/PPPangramSansRounded-Bold.otf b/frontend/public/fonts/PPPangramSansRounded-Bold.otf new file mode 100644 index 0000000..b20eecc Binary files /dev/null and b/frontend/public/fonts/PPPangramSansRounded-Bold.otf differ diff --git a/frontend/public/fonts/PPPangramSansRounded-Light.otf b/frontend/public/fonts/PPPangramSansRounded-Light.otf new file mode 100644 index 0000000..4771e5f Binary files /dev/null and b/frontend/public/fonts/PPPangramSansRounded-Light.otf differ diff --git a/frontend/public/fonts/PPPangramSansRounded-Medium.otf b/frontend/public/fonts/PPPangramSansRounded-Medium.otf new file mode 100644 index 0000000..a688467 Binary files /dev/null and b/frontend/public/fonts/PPPangramSansRounded-Medium.otf differ diff --git a/frontend/public/fonts/PPPangramSansRounded-Semibold.otf b/frontend/public/fonts/PPPangramSansRounded-Semibold.otf new file mode 100644 index 0000000..5239e14 Binary files /dev/null and b/frontend/public/fonts/PPPangramSansRounded-Semibold.otf differ diff --git a/frontend/public/fonts/donner.ttf b/frontend/public/fonts/donner.ttf new file mode 100644 index 0000000..2594f38 Binary files /dev/null and b/frontend/public/fonts/donner.ttf differ diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 1d53710..ead1d04 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -1,15 +1,5 @@ diff --git a/frontend/src/components/SourceCodeLink.vue b/frontend/src/components/SourceCodeLink.vue new file mode 100644 index 0000000..6cbd42c --- /dev/null +++ b/frontend/src/components/SourceCodeLink.vue @@ -0,0 +1,39 @@ + + + \ No newline at end of file diff --git a/frontend/src/composables/useServerEvents.js b/frontend/src/composables/useServerEvents.js index e493358..7d0666b 100644 --- a/frontend/src/composables/useServerEvents.js +++ b/frontend/src/composables/useServerEvents.js @@ -3,7 +3,7 @@ export default () => { // const store = useStore() - const evtSource = new EventSource(`${process.env.VUE_APP_BACKEND}/stream`); + const evtSource = new EventSource(`${import.meta.env.VITE_APP_BACKEND}/stream`); function addAnswerListener(handler) { evtSource.addEventListener('answer', (event) => handler(JSON.parse(event.data))) @@ -13,5 +13,9 @@ export default () => { evtSource.addEventListener('userlist', (event) => handler(JSON.parse(event.data))) } - return { addAnswerListener, addUserlistListener } + function addRevealListener(handler) { + evtSource.addEventListener('reveal', (event) => handler(JSON.parse(event.data))) + } + + return { addAnswerListener, addUserlistListener, addRevealListener } } diff --git a/frontend/src/main.js b/frontend/src/main.js index c5d424a..3ed611e 100644 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -1,8 +1,13 @@ import { createApp } from 'vue' +import { createPinia } from 'pinia' +import piniaPluginPersistedstate from "pinia-plugin-persistedstate"; import App from './App.vue' import router from './router' const app = createApp(App) +const pinia = createPinia() +pinia.use(piniaPluginPersistedstate); app.use(router) +app.use(pinia) app.mount('#app') diff --git a/frontend/src/router.js b/frontend/src/router.js index 7cf4cc0..113a4d7 100644 --- a/frontend/src/router.js +++ b/frontend/src/router.js @@ -4,8 +4,8 @@ import Login from './views/Login/Index.vue' import Screen from './views/Screen/Index.vue' const routes = [ - { path: '/', component: Game }, - { path: '/login', component: Login }, + { path: '/game', component: Game }, + { path: '/', component: Login }, { path: '/screen', component: Screen } ] diff --git a/frontend/src/store.js b/frontend/src/store.js index 5630ebd..7518660 100644 --- a/frontend/src/store.js +++ b/frontend/src/store.js @@ -1,6 +1,9 @@ import { defineStore } from 'pinia' -export default defineStore('store', { +export default defineStore({ + id: 'store', + persist: true, + state: () => ({ username: null }), diff --git a/frontend/src/styles/_prepend.scss b/frontend/src/styles/_prepend.scss new file mode 100644 index 0000000..47df0cf --- /dev/null +++ b/frontend/src/styles/_prepend.scss @@ -0,0 +1,9 @@ +@mixin filled-shadow($distance) { + $resulting-shadow: null; + + @for $i from 1 through $distance { + $resulting-shadow: $resulting-shadow, #{$i}px #{$i}px 0 black; + } + + box-shadow: $resulting-shadow; +} diff --git a/frontend/src/views/Game/EndGame.vue b/frontend/src/views/Game/EndGame.vue index 9c221cc..31ed0a8 100644 --- a/frontend/src/views/Game/EndGame.vue +++ b/frontend/src/views/Game/EndGame.vue @@ -1,6 +1,9 @@