game flow
This commit is contained in:
parent
c40dcd74db
commit
9a6f430e4d
2
.env
Normal file
2
.env
Normal file
@ -0,0 +1,2 @@
|
||||
VITE_LOG_LEVEL= 'debug'
|
||||
VITE_API_URL= 'http://localhost:3000/api'
|
260
package-lock.json
generated
260
package-lock.json
generated
@ -9,7 +9,10 @@
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"bulma": "^1.0.1",
|
||||
"colorette": "^2.0.20",
|
||||
"dayjs": "^1.11.11",
|
||||
"pinia": "^2.1.7",
|
||||
"pino": "^9.2.0",
|
||||
"pixi-filters": "^6.0.4",
|
||||
"pixi.js": "^8.2.1",
|
||||
"socket.io-client": "^4.7.5",
|
||||
@ -1492,6 +1495,17 @@
|
||||
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/abort-controller": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
|
||||
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
|
||||
"dependencies": {
|
||||
"event-target-shim": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.5"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.12.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.0.tgz",
|
||||
@ -1620,12 +1634,39 @@
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/atomic-sleep": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
|
||||
"integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==",
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/binary-extensions": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
||||
@ -1665,6 +1706,29 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
|
||||
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/bulma": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/bulma/-/bulma-1.0.1.tgz",
|
||||
@ -1798,6 +1862,11 @@
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/colorette": {
|
||||
"version": "2.0.20",
|
||||
"resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz",
|
||||
"integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
@ -1909,6 +1978,11 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/dayjs": {
|
||||
"version": "1.11.11",
|
||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.11.tgz",
|
||||
"integrity": "sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg=="
|
||||
},
|
||||
"node_modules/de-indent": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",
|
||||
@ -2370,11 +2444,27 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/event-target-shim": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
|
||||
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/eventemitter3": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
|
||||
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="
|
||||
},
|
||||
"node_modules/events": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
|
||||
"integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
|
||||
"engines": {
|
||||
"node": ">=0.8.x"
|
||||
}
|
||||
},
|
||||
"node_modules/execa": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz",
|
||||
@ -2450,6 +2540,14 @@
|
||||
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fast-redact": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz",
|
||||
"integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/fastq": {
|
||||
"version": "1.17.1",
|
||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
|
||||
@ -2751,6 +2849,25 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/ignore": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
|
||||
@ -3391,6 +3508,14 @@
|
||||
"integrity": "sha512-QK0sRs7MKv0tKe1+5uZIQk/C8XGza4DAnztJG8iD+TpJIORARrCxczA738awHrZoHeTjSSoHqao2teO0dC/gFQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/on-exit-leak-free": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz",
|
||||
"integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==",
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
@ -3649,6 +3774,41 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/pino": {
|
||||
"version": "9.2.0",
|
||||
"resolved": "https://registry.npmjs.org/pino/-/pino-9.2.0.tgz",
|
||||
"integrity": "sha512-g3/hpwfujK5a4oVbaefoJxezLzsDgLcNJeITvC6yrfwYeT9la+edCK42j5QpEQSQCZgTKapXvnQIdgZwvRaZug==",
|
||||
"dependencies": {
|
||||
"atomic-sleep": "^1.0.0",
|
||||
"fast-redact": "^3.1.1",
|
||||
"on-exit-leak-free": "^2.1.0",
|
||||
"pino-abstract-transport": "^1.2.0",
|
||||
"pino-std-serializers": "^7.0.0",
|
||||
"process-warning": "^3.0.0",
|
||||
"quick-format-unescaped": "^4.0.3",
|
||||
"real-require": "^0.2.0",
|
||||
"safe-stable-stringify": "^2.3.1",
|
||||
"sonic-boom": "^4.0.1",
|
||||
"thread-stream": "^3.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"pino": "bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/pino-abstract-transport": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.2.0.tgz",
|
||||
"integrity": "sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==",
|
||||
"dependencies": {
|
||||
"readable-stream": "^4.0.0",
|
||||
"split2": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pino-std-serializers": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz",
|
||||
"integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA=="
|
||||
},
|
||||
"node_modules/pixi-filters": {
|
||||
"version": "6.0.4",
|
||||
"resolved": "https://registry.npmjs.org/pixi-filters/-/pixi-filters-6.0.4.tgz",
|
||||
@ -3789,6 +3949,19 @@
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/process": {
|
||||
"version": "0.11.10",
|
||||
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
|
||||
"integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
|
||||
"engines": {
|
||||
"node": ">= 0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/process-warning": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/process-warning/-/process-warning-3.0.0.tgz",
|
||||
"integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ=="
|
||||
},
|
||||
"node_modules/proto-list": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
|
||||
@ -3836,6 +4009,11 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/quick-format-unescaped": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz",
|
||||
"integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
||||
@ -3855,6 +4033,21 @@
|
||||
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "4.5.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz",
|
||||
"integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==",
|
||||
"dependencies": {
|
||||
"abort-controller": "^3.0.0",
|
||||
"buffer": "^6.0.3",
|
||||
"events": "^3.3.0",
|
||||
"process": "^0.11.10",
|
||||
"string_decoder": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/readdirp": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||
@ -3867,6 +4060,14 @@
|
||||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/real-require": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz",
|
||||
"integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==",
|
||||
"engines": {
|
||||
"node": ">= 12.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/requires-port": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
|
||||
@ -4015,6 +4216,33 @@
|
||||
"queue-microtask": "^1.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/safe-stable-stringify": {
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz",
|
||||
"integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
@ -4150,6 +4378,14 @@
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sonic-boom": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.0.1.tgz",
|
||||
"integrity": "sha512-hTSD/6JMLyT4r9zeof6UtuBDpjJ9sO08/nmS5djaA9eozT9oOlNdpXSnzcgj4FTqpk3nkLrs61l4gip9r1HCrQ==",
|
||||
"dependencies": {
|
||||
"atomic-sleep": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
|
||||
@ -4158,6 +4394,14 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/split2": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
|
||||
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
|
||||
"engines": {
|
||||
"node": ">= 10.x"
|
||||
}
|
||||
},
|
||||
"node_modules/stackback": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
|
||||
@ -4170,6 +4414,14 @@
|
||||
"integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
|
||||
@ -4336,6 +4588,14 @@
|
||||
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/thread-stream": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz",
|
||||
"integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==",
|
||||
"dependencies": {
|
||||
"real-require": "^0.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tiny-emitter": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
|
||||
|
@ -15,7 +15,10 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"bulma": "^1.0.1",
|
||||
"colorette": "^2.0.20",
|
||||
"dayjs": "^1.11.11",
|
||||
"pinia": "^2.1.7",
|
||||
"pino": "^9.2.0",
|
||||
"pixi-filters": "^6.0.4",
|
||||
"pixi.js": "^8.2.1",
|
||||
"socket.io-client": "^4.7.5",
|
||||
|
@ -3,8 +3,10 @@ import type { Container } from 'pixi.js'
|
||||
export interface PlayerDto {
|
||||
id: string
|
||||
name: string
|
||||
score?: number
|
||||
hand?: string[]
|
||||
score: number
|
||||
hand: TileDto[]
|
||||
teamedWith: PlayerDto | null
|
||||
ready: boolean
|
||||
}
|
||||
|
||||
export interface TileDto {
|
||||
@ -16,7 +18,7 @@ export interface TileDto {
|
||||
width?: number
|
||||
height?: number
|
||||
}
|
||||
export interface GameSessionState {
|
||||
export interface MatchSessionState {
|
||||
id: string
|
||||
name: string
|
||||
creator: string
|
||||
@ -46,14 +48,9 @@ export interface GameState {
|
||||
tileSelectionPhase: boolean
|
||||
boardFreeEnds: number[]
|
||||
lastMove: Movement
|
||||
}
|
||||
|
||||
export interface PlayerState {
|
||||
id: string
|
||||
name: string
|
||||
score: number
|
||||
hand: TileDto[]
|
||||
teamedWith: string | undefined
|
||||
scoreboard: Map<string, number>
|
||||
matchWinner: PlayerDto | null
|
||||
matchInProgress: boolean
|
||||
}
|
||||
|
||||
export interface Movement {
|
||||
@ -75,3 +72,10 @@ export interface ContainerOptions {
|
||||
visible?: boolean
|
||||
parent?: Container
|
||||
}
|
||||
|
||||
export interface Dimension {
|
||||
width: number
|
||||
height: number
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
|
@ -1,17 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import type { GameSessionState, GameState, PlayerState } from '@/common/interfaces'
|
||||
import { onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import type { MatchSessionState, GameState, PlayerDto } from '@/common/interfaces'
|
||||
import { onMounted, onUnmounted, ref, watch, inject } from 'vue'
|
||||
import { Game } from '@/game/Game'
|
||||
import { useGameStore } from '@/stores/game'
|
||||
import { useEventBusStore } from '@/stores/eventBus'
|
||||
import type { LoggingService } from '@/services/LoggingService'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
const logger: LoggingService = inject<LoggingService>('logger') as LoggingService
|
||||
const emit = defineEmits(['move'])
|
||||
const props = defineProps({
|
||||
playerId: String
|
||||
})
|
||||
const socketService: any = inject('socket')
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const eventBus = useEventBusStore()
|
||||
const { playerState, sessionState } = storeToRefs(gameStore)
|
||||
|
||||
let appEl = ref<HTMLElement | null>(null)
|
||||
|
||||
const game = new Game(
|
||||
{
|
||||
width: 1200,
|
||||
@ -20,38 +25,9 @@ const game = new Game(
|
||||
handScale: 1
|
||||
},
|
||||
emit,
|
||||
props
|
||||
)
|
||||
|
||||
watch(
|
||||
() => gameStore.canMakeMove,
|
||||
(value: boolean) => {
|
||||
game.setCanMakeMove(value)
|
||||
}
|
||||
)
|
||||
watch(
|
||||
() => gameStore.gameState,
|
||||
(value: GameState | undefined) => {
|
||||
if (value === undefined) return
|
||||
game.board.setState(value, props.playerId ?? '')
|
||||
// console.log('gameState-------------------------------------- :>> ', value)
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => gameStore.sessionState,
|
||||
(value: GameSessionState | undefined) => {
|
||||
if (value === undefined) return
|
||||
// console.log('gameSessionState-------------------------------------- :>> ', value)
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => gameStore.playerState,
|
||||
(value: PlayerState | undefined) => {
|
||||
if (value === undefined) return
|
||||
game.hand.update(value as PlayerState)
|
||||
}
|
||||
socketService,
|
||||
playerState.value?.id || '',
|
||||
sessionState.value?.id || ''
|
||||
)
|
||||
|
||||
onMounted(async () => {
|
||||
@ -61,6 +37,42 @@ onMounted(async () => {
|
||||
await game.preload()
|
||||
await game.start()
|
||||
|
||||
eventBus.subscribe('game-finished', () => {
|
||||
game.gameFinished()
|
||||
})
|
||||
|
||||
watch(
|
||||
() => gameStore.canMakeMove,
|
||||
(value: boolean) => {
|
||||
game.setCanMakeMove(value)
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => gameStore.gameState,
|
||||
(value: GameState | undefined) => {
|
||||
if (value === undefined) return
|
||||
logger.debug('gameState-------------------------------------- :>> ', value)
|
||||
game.board?.setState(value, playerState.value?.id ?? '')
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => gameStore.sessionState,
|
||||
(value: MatchSessionState | undefined) => {
|
||||
if (value === undefined) return
|
||||
// logger.debug('gameSessionState-------------------------------------- :>> ', value)
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => gameStore.playerState,
|
||||
(value: PlayerDto | undefined) => {
|
||||
if (value === undefined) return
|
||||
game.hand.update(value as PlayerDto)
|
||||
}
|
||||
)
|
||||
|
||||
// mockMove(game, [6, 6], 'left')
|
||||
// mockMove(game, [6, 4], 'left')
|
||||
// mockMove(game, [4, 4], 'left')
|
||||
|
@ -14,10 +14,13 @@ import { Tile } from '@/game/Tile'
|
||||
import { DIRECTIONS, createContainer, isTilePair } from '@/common/helpers'
|
||||
import { createText } from '@/game/utilities/fonts'
|
||||
import { Dot } from '@/game/Dot'
|
||||
import { LoggingService } from '@/services/LoggingService'
|
||||
import { inject } from 'vue'
|
||||
|
||||
export class Board extends EventEmitter {
|
||||
private _scale: number = 1
|
||||
private _canMove: boolean = false
|
||||
private logger = inject<LoggingService>('logger')!
|
||||
|
||||
ticker: Ticker
|
||||
height: number
|
||||
@ -25,7 +28,7 @@ export class Board extends EventEmitter {
|
||||
grain: number = 25
|
||||
scaleY: ScaleFunction
|
||||
scaleX: ScaleFunction
|
||||
state: GameState | undefined
|
||||
state?: GameState
|
||||
container!: Container
|
||||
initialContainer!: Container
|
||||
tilesContainer!: Container
|
||||
@ -45,7 +48,7 @@ export class Board extends EventEmitter {
|
||||
leftDirection: string = 'west'
|
||||
rightDirection: string = 'east'
|
||||
playerHand: Tile[] = []
|
||||
firstTile: Tile | undefined
|
||||
firstTile?: Tile
|
||||
|
||||
constructor(app: Application) {
|
||||
super()
|
||||
@ -63,8 +66,8 @@ export class Board extends EventEmitter {
|
||||
})
|
||||
|
||||
const background = new Sprite(Assets.get('bg-1'))
|
||||
background.width = this.width
|
||||
background.height = this.height
|
||||
// background.width = this.width
|
||||
// background.height = this.height
|
||||
this.container.addChild(background)
|
||||
|
||||
this.initialContainer = createContainer({
|
||||
@ -102,7 +105,13 @@ export class Board extends EventEmitter {
|
||||
this.tilesContainer.addChild(verticalLine)
|
||||
this.tilesContainer.addChild(horizontalLine)
|
||||
|
||||
this.createTexts()
|
||||
this.textContainer = createContainer({
|
||||
width: this.width,
|
||||
height: this.height,
|
||||
parent: this.container
|
||||
})
|
||||
|
||||
this.showText('Starting game...')
|
||||
}
|
||||
|
||||
private calculateScale() {
|
||||
@ -138,20 +147,17 @@ export class Board extends EventEmitter {
|
||||
this.playerHand = tiles
|
||||
}
|
||||
|
||||
createTexts() {
|
||||
this.textContainer = new Container()
|
||||
this.textWaitForPlayers = createText('Waiting for players', this.scaleX(0), 100)
|
||||
this.textYourTurn = createText('Your turn!', this.scaleX(0), 100)
|
||||
|
||||
this.container.addChild(this.textContainer)
|
||||
this.textContainer.addChild(this.textWaitForPlayers)
|
||||
this.textContainer.addChild(this.textYourTurn)
|
||||
this.textYourTurn.visible = false
|
||||
showText(text: string) {
|
||||
this.textContainer.removeChildren()
|
||||
this.textContainer.addChild(createText(text, this.scaleX(0), 100))
|
||||
}
|
||||
|
||||
private updateCanMoveText() {
|
||||
this.textWaitForPlayers.visible = !this.canMove
|
||||
this.textYourTurn.visible = this.canMove
|
||||
if (this.canMove) {
|
||||
this.showText('Your turn!')
|
||||
} else {
|
||||
this.showText('Waiting for players')
|
||||
}
|
||||
}
|
||||
|
||||
setState(state: GameState, playerId: string) {
|
||||
@ -161,7 +167,6 @@ export class Board extends EventEmitter {
|
||||
if (lastMove === null) {
|
||||
return
|
||||
}
|
||||
console.log('lastMove :>> ', lastMove)
|
||||
if (
|
||||
lastMove !== null &&
|
||||
lastMove.tile !== undefined &&
|
||||
@ -319,7 +324,6 @@ export class Board extends EventEmitter {
|
||||
y += isEndVertical && !isNextVertical ? 0 : 1
|
||||
}
|
||||
}
|
||||
console.log('position::>>', tile.pips, x, y)
|
||||
tile.setPosition(this.scaleX(x), this.scaleY(y))
|
||||
tile.setOrientation(orientation)
|
||||
tile.reScale(this.scale)
|
||||
@ -386,7 +390,7 @@ export class Board extends EventEmitter {
|
||||
this.addTile(tile, move)
|
||||
this.setFreeEnd(move)
|
||||
} catch (error) {
|
||||
console.log('error :>> ', error)
|
||||
this.logger.error(error, 'Error updating board')
|
||||
}
|
||||
}
|
||||
|
||||
@ -395,7 +399,6 @@ export class Board extends EventEmitter {
|
||||
}
|
||||
|
||||
setValidEnds(values: boolean[], tile: TileDto) {
|
||||
console.log('validEnds')
|
||||
if (this.count === 0) {
|
||||
this.createInteractionsII('right', [[0, 0], undefined, undefined, undefined])
|
||||
return
|
||||
@ -405,15 +408,12 @@ export class Board extends EventEmitter {
|
||||
const side = 'left'
|
||||
const validInteractions = this.nextTileValidMoves(tile, side)
|
||||
const validPoints = this.nextTileValidPoints(tile, side, validInteractions)
|
||||
console.log('validInteractions :>> ', validInteractions)
|
||||
// this.createInteractions(side, tile)
|
||||
this.createInteractionsII(side, validPoints)
|
||||
}
|
||||
if (values[1]) {
|
||||
const side = 'right'
|
||||
const validInteractions = this.nextTileValidMoves(tile, side)
|
||||
const validPoints = this.nextTileValidPoints(tile, side, validInteractions)
|
||||
console.log('validInteractions :>> ', validInteractions)
|
||||
this.createInteractionsII(side, validPoints)
|
||||
}
|
||||
}
|
||||
@ -501,7 +501,6 @@ export class Board extends EventEmitter {
|
||||
dot.alpha = 0.5
|
||||
dot.interactive = true
|
||||
dot.on('pointerdown', () => {
|
||||
console.log('direction :>> ', direction)
|
||||
this.emit(`${side}Click`, direction && { direction, x, y })
|
||||
this.cleanInteractions()
|
||||
})
|
||||
@ -567,4 +566,21 @@ export class Board extends EventEmitter {
|
||||
}
|
||||
return [canPlayNorth, canPlayEast, canPlaySouth, canPlayWest]
|
||||
}
|
||||
|
||||
gameFinished() {
|
||||
this.tiles = []
|
||||
this.boneyard = []
|
||||
this.movements = []
|
||||
this.playerHand = []
|
||||
this.freeEnds = undefined
|
||||
this.leftTile = undefined
|
||||
this.rightTile = undefined
|
||||
this.nextTile = undefined
|
||||
this.leftDirection = 'west'
|
||||
this.rightDirection = 'east'
|
||||
this.firstTile = undefined
|
||||
this.tilesContainer.removeChildren()
|
||||
this.interactionContainer.removeChildren()
|
||||
this.showText('Game finished')
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import { assets } from '@/game/utilities/assets'
|
||||
import { Tile } from '@/game/Tile'
|
||||
import { Hand } from '@/game/Hand'
|
||||
import type { Movement, TileDto } from '@/common/interfaces'
|
||||
import type { SocketIoClientService } from '@/services/SocketIoClientService'
|
||||
|
||||
export class Game {
|
||||
public board!: Board
|
||||
@ -19,7 +20,9 @@ export class Game {
|
||||
height: 650
|
||||
},
|
||||
private emit: any,
|
||||
private props: any
|
||||
private socketService: SocketIoClientService,
|
||||
private playerId: string,
|
||||
private sessionId: string
|
||||
) {}
|
||||
|
||||
async setup(): Promise<HTMLCanvasElement> {
|
||||
@ -66,11 +69,18 @@ export class Game {
|
||||
const move: Movement = {
|
||||
id: '',
|
||||
type: 'pass',
|
||||
playerId: this.props.playerId ?? ''
|
||||
playerId: this.playerId
|
||||
}
|
||||
this.emit('move', move)
|
||||
this.board.updateBoard(move)
|
||||
})
|
||||
|
||||
this.hand.on('nextClick', async () => {
|
||||
await this.socketService.sendMessageWithAck('playerReady', {
|
||||
user: this.playerId,
|
||||
sessionId: this.sessionId
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
getMoves(tile: any): [boolean, boolean] {
|
||||
@ -101,7 +111,7 @@ export class Game {
|
||||
const move: Movement = {
|
||||
tile: this.selectedTile,
|
||||
type: 'left',
|
||||
playerId: this.props.playerId ?? '',
|
||||
playerId: this.playerId,
|
||||
...data
|
||||
}
|
||||
this.emit('move', move)
|
||||
@ -115,7 +125,7 @@ export class Game {
|
||||
const move: Movement = {
|
||||
tile: this.selectedTile,
|
||||
type: 'right',
|
||||
playerId: this.props.playerId ?? '',
|
||||
playerId: this.playerId,
|
||||
...data
|
||||
}
|
||||
this.emit('move', move)
|
||||
@ -124,6 +134,11 @@ export class Game {
|
||||
})
|
||||
}
|
||||
|
||||
gameFinished() {
|
||||
this.hand.gameFinished()
|
||||
this.board.gameFinished()
|
||||
}
|
||||
|
||||
private removeBoardEvents() {
|
||||
this.board.off('leftClick')
|
||||
this.board.off('rightClick')
|
||||
|
@ -9,13 +9,16 @@ import {
|
||||
Ticker
|
||||
} from 'pixi.js'
|
||||
import { Tile } from '@/game/Tile'
|
||||
import type { PlayerState, TileDto } from '@/common/interfaces'
|
||||
import type { Dimension, PlayerDto, TileDto } from '@/common/interfaces'
|
||||
import { GlowFilter } from 'pixi-filters'
|
||||
import { Scale, type ScaleFunction } from './utilities/scale'
|
||||
import { LoggingService } from '@/services/LoggingService'
|
||||
|
||||
export class Hand extends EventEmitter {
|
||||
tiles: Tile[] = []
|
||||
container: Container = new Container()
|
||||
buttonPassContainer: Container = new Container()
|
||||
buttonPass: Container = new Container()
|
||||
buttonNext: Container = new Container()
|
||||
height: number
|
||||
width: number
|
||||
ticker: Ticker
|
||||
@ -24,6 +27,10 @@ export class Hand extends EventEmitter {
|
||||
initialized: boolean = false
|
||||
_canMove: boolean = false
|
||||
scale: number = 1
|
||||
scaleY!: ScaleFunction
|
||||
scaleX!: ScaleFunction
|
||||
grain: number = 25
|
||||
logger: LoggingService = new LoggingService()
|
||||
|
||||
constructor(app: Application) {
|
||||
super()
|
||||
@ -34,24 +41,47 @@ export class Hand extends EventEmitter {
|
||||
this.container.y = app.canvas.height - this.height
|
||||
this.container.width = this.width
|
||||
this.container.height = this.height
|
||||
this.calculateScale()
|
||||
this.addBg()
|
||||
this.createPassButton()
|
||||
}
|
||||
|
||||
gameFinished() {
|
||||
this.logger.debug('gameFinished')
|
||||
this.tiles = []
|
||||
this.container.removeChildren()
|
||||
this.initialized = false
|
||||
this.buttonNext = this.createButton(
|
||||
'NEXT',
|
||||
{ x: this.width / 2 - 25, y: this.height / 2, width: 50, height: 20 },
|
||||
'nextClick'
|
||||
)
|
||||
}
|
||||
|
||||
get canMove() {
|
||||
return this._canMove
|
||||
}
|
||||
|
||||
private calculateScale() {
|
||||
const scaleXSteps = Math.floor(this.width / (this.grain * this.scale)) / 2
|
||||
const scaleYSteps = Math.floor(this.height / (this.grain * this.scale)) / 2
|
||||
this.scaleX = Scale([-scaleXSteps, scaleXSteps], [0, this.width])
|
||||
this.scaleY = Scale([-scaleYSteps, scaleYSteps], [0, this.height])
|
||||
}
|
||||
|
||||
set canMove(value: boolean) {
|
||||
this._canMove = value
|
||||
this.buttonPassContainer.eventMode = value ? 'static' : 'none'
|
||||
this.buttonPassContainer.cursor = value ? 'pointer' : 'default'
|
||||
if (value) {
|
||||
this.createPassButton()
|
||||
} else {
|
||||
this.container.removeChild(this.buttonPass)
|
||||
}
|
||||
|
||||
this.tiles.forEach((tile) => {
|
||||
tile.interactive = value
|
||||
})
|
||||
}
|
||||
|
||||
initialize(playerState: PlayerState) {
|
||||
initialize(playerState: PlayerDto) {
|
||||
this.tiles = this.createTiles(playerState)
|
||||
this.emit('handUpdated', this.tiles)
|
||||
this.initialized = this.tiles.length > 0
|
||||
@ -106,11 +136,15 @@ export class Hand extends EventEmitter {
|
||||
tile.off('pointerout')
|
||||
}
|
||||
|
||||
private createPassButton() {
|
||||
const rectangle = new Graphics().roundRect(0, 0, 80, 30, 10).fill(0xffff00)
|
||||
|
||||
private createButton(
|
||||
textStr: string,
|
||||
dimension: Dimension,
|
||||
action: string | Function
|
||||
): Container {
|
||||
const { x, y, width, height } = dimension
|
||||
const rectangle = new Graphics().roundRect(x, y, width + 4, height + 4, 5).fill(0xffff00)
|
||||
const text = new Text({
|
||||
text: 'PASS',
|
||||
text: textStr,
|
||||
style: {
|
||||
fontFamily: 'Arial',
|
||||
fontSize: 12,
|
||||
@ -120,38 +154,45 @@ export class Hand extends EventEmitter {
|
||||
}
|
||||
})
|
||||
text.anchor = 0.5
|
||||
const container = new Container()
|
||||
container.addChild(rectangle)
|
||||
container.addChild(text)
|
||||
|
||||
this.buttonPassContainer = new Container()
|
||||
text.y = y + height / 2
|
||||
text.x = x + width / 2
|
||||
|
||||
this.buttonPassContainer.addChild(rectangle)
|
||||
this.buttonPassContainer.addChild(text)
|
||||
|
||||
text.y = this.buttonPassContainer.height / 2 - 4
|
||||
text.x = this.buttonPassContainer.width / 2 - 8
|
||||
|
||||
this.buttonPassContainer.eventMode = 'none'
|
||||
this.buttonPassContainer.cursor = 'default'
|
||||
this.buttonPassContainer.x = 20
|
||||
this.buttonPassContainer.y = this.height / 2 - 10
|
||||
container.eventMode = 'static'
|
||||
container.cursor = 'pointer'
|
||||
rectangle.alpha = 0.7
|
||||
text.alpha = 0.7
|
||||
this.buttonPassContainer.on('pointerdown', () => {
|
||||
this.emit('passClick')
|
||||
container.on('pointerdown', () => {
|
||||
action instanceof Function ? action() : this.emit(action)
|
||||
})
|
||||
this.buttonPassContainer.on('pointerover', () => {
|
||||
container.on('pointerover', () => {
|
||||
rectangle.alpha = 1
|
||||
text.alpha = 1
|
||||
})
|
||||
|
||||
this.buttonPassContainer.on('pointerout', () => {
|
||||
container.on('pointerout', () => {
|
||||
rectangle.alpha = 0.7
|
||||
text.alpha = 0.7
|
||||
})
|
||||
|
||||
this.container.addChild(this.buttonPassContainer)
|
||||
this.container.addChild(container)
|
||||
return container
|
||||
}
|
||||
|
||||
update(playerState: PlayerState) {
|
||||
private createPassButton() {
|
||||
const lastTile = this.tiles[this.tiles.length - 1]
|
||||
const x = lastTile ? lastTile.x + lastTile.width : this.scaleX(0)
|
||||
this.buttonPass = this.createButton(
|
||||
'PASS',
|
||||
{ x, y: this.height / 2, width: 50, height: 20 },
|
||||
'passClick'
|
||||
)
|
||||
}
|
||||
|
||||
update(playerState: PlayerDto) {
|
||||
if (!this.initialized) {
|
||||
this.initialize(playerState)
|
||||
return
|
||||
@ -167,7 +208,7 @@ export class Hand extends EventEmitter {
|
||||
this.renderTiles()
|
||||
}
|
||||
|
||||
private createTiles(playerState: PlayerState) {
|
||||
private createTiles(playerState: PlayerDto) {
|
||||
return playerState.hand.map((tile) => {
|
||||
const newTile: Tile = new Tile(tile.id, this.ticker, tile.pips, this.scale)
|
||||
newTile.alpha = 0.7
|
||||
|
@ -9,6 +9,8 @@ import App from './App.vue'
|
||||
import router from './router'
|
||||
|
||||
import { SocketIoClientService } from '@/services/SocketIoClientService'
|
||||
import { LoggingService } from '@/services/LoggingService'
|
||||
import { AuthenticationService } from './services/AuthenticationService'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
@ -16,5 +18,7 @@ app.use(createPinia())
|
||||
app.use(router)
|
||||
|
||||
app.provide('socket', new SocketIoClientService('http://localhost:3000'))
|
||||
app.provide('logger', new LoggingService())
|
||||
app.provide('auth', new AuthenticationService())
|
||||
|
||||
app.mount('#app')
|
||||
|
@ -1,12 +1,14 @@
|
||||
import { useGameStore } from '@/stores/game'
|
||||
import { wait } from '@/common/helpers'
|
||||
import type { GameSessionState } from '@/common/interfaces'
|
||||
import type { MatchSessionState } from '@/common/interfaces'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useEventBusStore } from '@/stores/eventBus'
|
||||
|
||||
export class SocketIoEventManager {
|
||||
gameStore: any = useGameStore()
|
||||
eventBus = useEventBusStore()
|
||||
|
||||
handleSessionStateEvent(data: GameSessionState) {
|
||||
handleSessionStateEvent(data: MatchSessionState) {
|
||||
const { updateSessionState } = this.gameStore
|
||||
updateSessionState(data)
|
||||
return {
|
||||
@ -55,4 +57,8 @@ export class SocketIoEventManager {
|
||||
status: 'ok'
|
||||
}
|
||||
}
|
||||
|
||||
handleGameFinishedEvent() {
|
||||
this.eventBus.publish('game-finished')
|
||||
}
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ const router = createRouter({
|
||||
})
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
const isLoggedIn = !!localStorage.getItem('token')
|
||||
const isLoggedIn = !!sessionStorage.getItem('token')
|
||||
if (to.matched.some((record) => record.meta.requiresAuth) && !isLoggedIn) {
|
||||
next({ name: 'landing' })
|
||||
} else {
|
||||
|
61
src/services/AuthenticationService.ts
Normal file
61
src/services/AuthenticationService.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import { ServiceBase } from '@/services/ServiceBase'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { NetworkService } from '@/services/NetworkService'
|
||||
|
||||
export class AuthenticationService extends ServiceBase {
|
||||
private apiUrl = import.meta.env.VITE_API_URL
|
||||
private networkService = new NetworkService()
|
||||
|
||||
isAuthenticated() {
|
||||
const auth = useAuthStore()
|
||||
const { isLoggedIn } = storeToRefs(auth)
|
||||
return isLoggedIn
|
||||
}
|
||||
|
||||
async login(username: string, password: string) {
|
||||
try {
|
||||
const res = await this.networkService.post({
|
||||
uri: '/login',
|
||||
body: { username, password }
|
||||
})
|
||||
const { token } = res
|
||||
this.persist(token)
|
||||
return token
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
async logout() {
|
||||
const auth = useAuthStore()
|
||||
const { setJwt, setUser } = auth
|
||||
setJwt(undefined)
|
||||
setUser(undefined)
|
||||
sessionStorage.removeItem('token')
|
||||
}
|
||||
|
||||
private persist(jwt: string) {
|
||||
const auth = useAuthStore()
|
||||
const { setJwt, setUser } = auth
|
||||
const loggedUser = this.parseJwt(jwt)
|
||||
setJwt(jwt)
|
||||
setUser(loggedUser)
|
||||
sessionStorage.setItem('token', jwt)
|
||||
}
|
||||
|
||||
private parseJwt(token: string) {
|
||||
if (!token) {
|
||||
return
|
||||
}
|
||||
const base64Url = token.split('.')[1] ?? ''
|
||||
const base64 = base64Url.replace('-', '+').replace('_', '/')
|
||||
return JSON.parse(window.atob(base64))
|
||||
}
|
||||
|
||||
hasRoles(rolesToCheck: string[]) {
|
||||
const auth = useAuthStore()
|
||||
const { roles } = storeToRefs(auth)
|
||||
return roles.value.some((role) => rolesToCheck.includes(role))
|
||||
}
|
||||
}
|
86
src/services/LoggingService.ts
Normal file
86
src/services/LoggingService.ts
Normal file
@ -0,0 +1,86 @@
|
||||
import { createColors } from 'colorette'
|
||||
import dayjs from 'dayjs'
|
||||
import pino, { type BaseLogger } from 'pino'
|
||||
import { isProxy, toRaw } from 'vue'
|
||||
|
||||
const { blue, cyan, green, red } = createColors({ useColor: true })
|
||||
|
||||
export class LoggingService {
|
||||
private _logger: BaseLogger
|
||||
|
||||
constructor() {
|
||||
this._logger = pino({
|
||||
browser: {
|
||||
asObject: true,
|
||||
transmit: {
|
||||
level: import.meta.env.VITE_LOG_LEVEL || 'error',
|
||||
send: (level, logEvent) => {
|
||||
const { ts, messages } = logEvent
|
||||
const logStr: string[] = [dayjs(ts).format('HH:mm:ss.SSS')]
|
||||
logStr.push(this.colors[level](level.toUpperCase()))
|
||||
const firstMessage = messages.shift()
|
||||
if (firstMessage.type === 'Error') {
|
||||
logStr.push(red(firstMessage.message))
|
||||
logStr.push(red(firstMessage.stack || ''))
|
||||
} else if (typeof firstMessage === 'string') {
|
||||
logStr.push(cyan(firstMessage))
|
||||
} else {
|
||||
messages.unshift(firstMessage)
|
||||
}
|
||||
console.log(`${logStr.join(' ')}:`, ...messages)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private get colors(): any {
|
||||
return {
|
||||
info: green,
|
||||
debug: blue,
|
||||
error: red
|
||||
}
|
||||
}
|
||||
|
||||
debug(message: string, data?: any) {
|
||||
this._logger.debug(message, data)
|
||||
}
|
||||
|
||||
info(message: string, data?: any) {
|
||||
this._logger.info(this._getMessageWidthObject(message, data))
|
||||
}
|
||||
|
||||
warn(message: string, data?: any) {
|
||||
this._logger.warn(this._getMessageWidthObject(message, data))
|
||||
}
|
||||
|
||||
error(error: any, message?: string) {
|
||||
this._logger.error(error, message)
|
||||
}
|
||||
|
||||
fatal(message: string, data?: any) {
|
||||
this._logger.fatal(this._getMessageWidthObject(message, data))
|
||||
}
|
||||
|
||||
trace(message: string, data?: any) {
|
||||
this._logger.trace(this._getMessageWidthObject(message, data))
|
||||
}
|
||||
|
||||
object(message: any) {
|
||||
this._logger.info(this._getStringObject(message))
|
||||
}
|
||||
|
||||
_getMessageWidthObject(message: string, data?: any) {
|
||||
if (!data) {
|
||||
return message
|
||||
}
|
||||
return `${message}\n${this._getStringObject(data)}`
|
||||
}
|
||||
|
||||
_getStringObject(data: any): any {
|
||||
if (isProxy(data)) {
|
||||
return this._getStringObject(toRaw(data))
|
||||
}
|
||||
return JSON.stringify(data, null, 2)
|
||||
}
|
||||
}
|
89
src/services/NetworkService.ts
Normal file
89
src/services/NetworkService.ts
Normal file
@ -0,0 +1,89 @@
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
interface RequestOptions {
|
||||
uri: string
|
||||
params?: Record<string, string>
|
||||
body?: any
|
||||
auth?: boolean
|
||||
method?: string
|
||||
}
|
||||
|
||||
export class NetworkService {
|
||||
private API_URL = import.meta.env.VITE_API_URL
|
||||
private auth = useAuthStore()
|
||||
|
||||
async post(options: RequestOptions) {
|
||||
options.method = 'POST'
|
||||
return await this.request(options)
|
||||
}
|
||||
|
||||
async get(options: RequestOptions) {
|
||||
options.method = 'GET'
|
||||
return await this.request(options)
|
||||
}
|
||||
|
||||
async patch(options: RequestOptions) {
|
||||
options.method = 'PATCH'
|
||||
return await this.request(options)
|
||||
}
|
||||
|
||||
async delete(options: RequestOptions) {
|
||||
options.method = 'DELETE'
|
||||
return await this.request(options)
|
||||
}
|
||||
|
||||
async request(options: RequestOptions) {
|
||||
const { uri, params } = options
|
||||
if (!uri) {
|
||||
throw new Error('URL is required')
|
||||
}
|
||||
const fetchOptions = this.getFetchOptions(options)
|
||||
const urlParams = this.getURLParams(params)
|
||||
const res = await fetch(`${this.API_URL}${uri}${urlParams}`, fetchOptions)
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error('Network response was not ok')
|
||||
}
|
||||
const text = await res.text()
|
||||
|
||||
if (text === '') {
|
||||
return
|
||||
} else {
|
||||
return JSON.parse(text)
|
||||
}
|
||||
}
|
||||
|
||||
getURLParams(params: any) {
|
||||
if (!params) {
|
||||
return ''
|
||||
}
|
||||
const urlParams = new URLSearchParams()
|
||||
Object.keys(params).forEach((key) => urlParams.append(key, params[key]))
|
||||
return `?${urlParams.toString()}`
|
||||
}
|
||||
|
||||
getFetchOptions(opts: RequestOptions): any {
|
||||
const { body, auth, method = 'GET' } = opts
|
||||
const options: any = {
|
||||
method,
|
||||
headers: this.getHeaders({ auth })
|
||||
}
|
||||
if (!['GET', 'HEAD'].includes(method) && body) {
|
||||
options.body = typeof body === 'string' ? body : JSON.stringify(body)
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
getHeaders({ auth = true }): any {
|
||||
const { jwt } = storeToRefs(this.auth)
|
||||
const headers: any = {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
if (auth) {
|
||||
headers.Authorization = jwt
|
||||
}
|
||||
return headers
|
||||
}
|
||||
}
|
5
src/services/ServiceBase.ts
Normal file
5
src/services/ServiceBase.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { LoggingService } from './LoggingService'
|
||||
|
||||
export class ServiceBase {
|
||||
protected logger: LoggingService = new LoggingService()
|
||||
}
|
@ -1,11 +1,13 @@
|
||||
import type { GameSessionState, GameState, PlayerState } from '@/common/interfaces'
|
||||
import type { MatchSessionState, GameState, PlayerDto } from '@/common/interfaces'
|
||||
import { io, Socket } from 'socket.io-client'
|
||||
import { SocketIoEventManager } from '@/managers/SocketIoEventManager'
|
||||
import { LoggingService } from './LoggingService'
|
||||
|
||||
export class SocketIoClientService {
|
||||
public socket: Socket
|
||||
private isConnected = false
|
||||
private gameEventManager = new SocketIoEventManager()
|
||||
private logger: LoggingService = new LoggingService()
|
||||
|
||||
constructor(url: string) {
|
||||
this.socket = io(url)
|
||||
@ -37,7 +39,7 @@ export class SocketIoClientService {
|
||||
console.log('Failed to reconnect to server')
|
||||
})
|
||||
|
||||
this.socket.on('sessionState', (data: GameSessionState, callback: any) => {
|
||||
this.socket.on('matchState', (data: MatchSessionState, callback: any) => {
|
||||
callback(this.gameEventManager.handleSessionStateEvent(data))
|
||||
})
|
||||
|
||||
@ -45,7 +47,8 @@ export class SocketIoClientService {
|
||||
callback(this.gameEventManager.handleGameStateEvent(data))
|
||||
})
|
||||
|
||||
this.socket.on('playerState', (data: PlayerState, callback: any) => {
|
||||
this.socket.on('playerState', (data: PlayerDto, callback: any) => {
|
||||
this.logger.debug('playerState', data)
|
||||
callback(this.gameEventManager.handlePlayerStateEvent(data))
|
||||
})
|
||||
|
||||
@ -57,6 +60,11 @@ export class SocketIoClientService {
|
||||
callback(await this.gameEventManager.handleCanSelectTileEvent())
|
||||
})
|
||||
|
||||
this.socket.on('game-finished', () => {
|
||||
this.logger.debug('game-finished event received')
|
||||
this.gameEventManager.handleGameFinishedEvent()
|
||||
})
|
||||
|
||||
this.socket.on('ping', () => {
|
||||
console.log('Ping received from server')
|
||||
this.socket.emit('pong') // Send pong response
|
||||
|
33
src/stores/auth.ts
Normal file
33
src/stores/auth.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
export const useAuthStore = defineStore('auth', () => {
|
||||
const jwt = ref<string | undefined>(undefined)
|
||||
const user = ref<any | undefined>(undefined)
|
||||
const roles = ref<string[]>([])
|
||||
|
||||
const isLoggedIn = computed(() => jwt.value !== undefined)
|
||||
|
||||
function setJwt(token: string | undefined) {
|
||||
jwt.value = token
|
||||
}
|
||||
|
||||
function setUser(userIn: any | undefined) {
|
||||
user.value = userIn
|
||||
setRoles(userIn?.roles ?? [])
|
||||
}
|
||||
|
||||
function setRoles(rolesIn: string[]) {
|
||||
roles.value = rolesIn
|
||||
}
|
||||
|
||||
return {
|
||||
jwt,
|
||||
user,
|
||||
roles,
|
||||
isLoggedIn,
|
||||
setJwt,
|
||||
setUser,
|
||||
setRoles
|
||||
}
|
||||
})
|
43
src/stores/eventBus.ts
Normal file
43
src/stores/eventBus.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
|
||||
export const useEventBusStore = defineStore('eventBus', () => {
|
||||
const events = ref<{ [key: string]: { callback: (payload?: any) => void; once: boolean }[] }>({})
|
||||
|
||||
const subscribe = (event: string, callback: (payload?: any) => void) => {
|
||||
if (!events.value[event]) {
|
||||
events.value[event] = []
|
||||
}
|
||||
events.value[event].push({ callback, once: false })
|
||||
}
|
||||
|
||||
const subscribeOnce = (event: string, callback: (payload?: any) => void) => {
|
||||
if (!events.value[event]) {
|
||||
events.value[event] = []
|
||||
}
|
||||
events.value[event].push({ callback, once: true })
|
||||
}
|
||||
|
||||
const unsubscribe = (event: string, callback: (payload?: any) => void) => {
|
||||
if (events.value[event]) {
|
||||
events.value[event] = events.value[event].filter((e) => e.callback !== callback)
|
||||
}
|
||||
}
|
||||
|
||||
const publish = (event: string, payload?: any) => {
|
||||
if (events.value[event]) {
|
||||
events.value[event] = events.value[event].filter((e) => {
|
||||
e.callback(payload)
|
||||
return !e.once // Retain if it's not a one-time event
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
events,
|
||||
subscribe,
|
||||
subscribeOnce,
|
||||
unsubscribe,
|
||||
publish
|
||||
}
|
||||
})
|
@ -1,17 +1,22 @@
|
||||
import { ref } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
import type { GameSessionState, GameState, Movement, PlayerState } from '@/common/interfaces'
|
||||
import type { MatchSessionState, GameState, Movement, PlayerDto } from '@/common/interfaces'
|
||||
|
||||
export const useGameStore = defineStore('game', () => {
|
||||
const sessionState = ref<GameSessionState | undefined>(undefined)
|
||||
const sessionState = ref<MatchSessionState | undefined>(undefined)
|
||||
const gameState = ref<GameState | undefined>(undefined)
|
||||
const playerState = ref<PlayerState | undefined>(undefined)
|
||||
const playerState = ref<PlayerDto | undefined>(undefined)
|
||||
const canMakeMove = ref(false)
|
||||
const canSelectTile = ref(false)
|
||||
const gameFinished = ref(false)
|
||||
const readyForStart = ref(false)
|
||||
const moveToMake = ref<Movement | undefined>(undefined)
|
||||
const incomingFreeEnds = ref<[number, number] | undefined>(undefined)
|
||||
const showReadyButton = ref(false)
|
||||
|
||||
function updateSessionState(newState: GameSessionState) {
|
||||
const isSessionStarted = computed(() => sessionState.value !== undefined)
|
||||
|
||||
function updateSessionState(newState: MatchSessionState) {
|
||||
sessionState.value = newState
|
||||
}
|
||||
|
||||
@ -19,7 +24,7 @@ export const useGameStore = defineStore('game', () => {
|
||||
gameState.value = newState
|
||||
}
|
||||
|
||||
function updatePlayerState(newState: PlayerState) {
|
||||
function updatePlayerState(newState: PlayerDto) {
|
||||
playerState.value = newState
|
||||
}
|
||||
|
||||
@ -39,6 +44,18 @@ export const useGameStore = defineStore('game', () => {
|
||||
incomingFreeEnds.value = freeEnds
|
||||
}
|
||||
|
||||
function setShowReadyButton(value: boolean) {
|
||||
showReadyButton.value = value
|
||||
}
|
||||
|
||||
function setReadyForStart(value: boolean) {
|
||||
readyForStart.value = value
|
||||
}
|
||||
|
||||
function updateGameFinished(value: boolean) {
|
||||
gameFinished.value = value
|
||||
}
|
||||
|
||||
return {
|
||||
sessionState,
|
||||
gameState,
|
||||
@ -47,12 +64,19 @@ export const useGameStore = defineStore('game', () => {
|
||||
moveToMake,
|
||||
incomingFreeEnds,
|
||||
canSelectTile,
|
||||
showReadyButton,
|
||||
readyForStart,
|
||||
gameFinished,
|
||||
updateSessionState,
|
||||
updateGameState,
|
||||
updatePlayerState,
|
||||
updateCanMakeMove,
|
||||
setMoveToMake,
|
||||
setIncomingFreeEnds,
|
||||
updateCanSelectTile
|
||||
updateCanSelectTile,
|
||||
setShowReadyButton,
|
||||
setReadyForStart,
|
||||
updateGameFinished,
|
||||
isSessionStarted
|
||||
}
|
||||
})
|
||||
|
@ -5,28 +5,22 @@ import { storeToRefs } from 'pinia'
|
||||
import { inject, onBeforeUnmount, ref } from 'vue'
|
||||
import { onMounted } from 'vue'
|
||||
import useClipboard from 'vue-clipboard3'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const socketService: any = inject('socket')
|
||||
let data = ref('')
|
||||
let responseField = ref('')
|
||||
let statusField = ref('')
|
||||
let sessionId = ref('')
|
||||
let seed = ref('')
|
||||
let playerId = ref('')
|
||||
let selectdAction: any = undefined
|
||||
|
||||
const { toClipboard } = useClipboard()
|
||||
const gameStore = useGameStore()
|
||||
const { moveToMake, canMakeMove, sessionState, gameState } = storeToRefs(gameStore)
|
||||
const options = [
|
||||
{ value: 'createSession', default: '{"user": "arhuako"}' },
|
||||
{ value: 'startSession', default: (id: string) => `{"sessionId": "${id}"}` }
|
||||
// { value: 'joinSession', default: '{"user": "pepe", "sessionId": "arhuako"}' },
|
||||
// { value: 'leaveSession', default: '{"user": "pepe", "sessionId": "arhuako"}' },
|
||||
// { value: 'chat message', default: 'chat message' }
|
||||
]
|
||||
const { moveToMake, canMakeMove, sessionState, gameState, playerState } = storeToRefs(gameStore)
|
||||
|
||||
onMounted(async () => {})
|
||||
onMounted(async () => {
|
||||
startMatch()
|
||||
})
|
||||
|
||||
if (!playerState?.value) {
|
||||
const router = useRouter()
|
||||
router.push({ name: 'home' })
|
||||
}
|
||||
|
||||
function makeMove(move: any) {
|
||||
moveToMake.value = move
|
||||
@ -37,73 +31,19 @@ onBeforeUnmount(() => {
|
||||
// socketService.disconnect()
|
||||
})
|
||||
|
||||
function actionSelected() {
|
||||
if (selectdAction.value === 'createSession') {
|
||||
responseField.value = ''
|
||||
} else if (selectdAction.value === 'startSession') {
|
||||
data.value = selectdAction.default(sessionId.value)
|
||||
return
|
||||
}
|
||||
|
||||
data.value = selectdAction.default
|
||||
}
|
||||
|
||||
const getMessage = (msg: string) => {
|
||||
if (msg.startsWith('{') && msg.endsWith('}')) return JSON.parse(msg)
|
||||
return msg
|
||||
}
|
||||
|
||||
async function createSession() {
|
||||
const response = await socketService.sendMessageWithAck('createSession', { user: 'arhuako' })
|
||||
sessionId.value = response.sessionId
|
||||
playerId.value = response.playerId
|
||||
}
|
||||
|
||||
async function startSession() {
|
||||
if (sessionId.value) {
|
||||
async function startMatch() {
|
||||
const sessionId = sessionState?.value?.id
|
||||
const seed = sessionState?.value?.seed
|
||||
const playerId = playerState?.value?.id
|
||||
if (sessionId) {
|
||||
await socketService.sendMessageWithAck('startSession', {
|
||||
sessionId: sessionId.value,
|
||||
seed: seed.value.trim()
|
||||
sessionId: sessionId,
|
||||
playerId: playerId,
|
||||
seed: seed?.trim()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function joinSession() {
|
||||
if (sessionId.value) {
|
||||
const response = await socketService.sendMessageWithAck('joinSession', {
|
||||
user: 'pepe',
|
||||
sessionId: sessionId.value
|
||||
})
|
||||
// sessionId.value = response.sessionId
|
||||
playerId.value = response.playerId
|
||||
}
|
||||
}
|
||||
|
||||
async function sendMessage() {
|
||||
if (selectdAction && data.value.trim() !== '') {
|
||||
const response = await socketService.sendMessageWithAck(
|
||||
selectdAction.value,
|
||||
getMessage(data.value.trim())
|
||||
)
|
||||
handleResponse(response)
|
||||
}
|
||||
// socketService.emit('message', data.value)
|
||||
}
|
||||
|
||||
function handleResponse(response: any) {
|
||||
if (selectdAction.value === 'createSession') {
|
||||
sessionId.value = response.sessionId
|
||||
playerId.value = response.playerId
|
||||
}
|
||||
|
||||
data.value = ''
|
||||
const responseStr = JSON.stringify(response, null, 2)
|
||||
responseField.value = !responseField.value
|
||||
? responseStr
|
||||
: responseField.value + '\n---\n ' + responseStr
|
||||
selectdAction = undefined
|
||||
}
|
||||
|
||||
function copySeed() {
|
||||
if (sessionState?.value?.seed) toClipboard(sessionState.value.seed)
|
||||
}
|
||||
@ -116,18 +56,25 @@ function copySeed() {
|
||||
Running: {{ sessionState?.sessionInProgress }} Seed: {{ sessionState?.seed }}
|
||||
<button @click="copySeed">Copy!</button>
|
||||
</p>
|
||||
<p>FreeEnds: {{ gameState?.boardFreeEnds }} - {{ gameState?.currentPlayer?.name }}</p>
|
||||
<p v-if="sessionId">SessionID: {{ sessionId }} PlayerID: {{ playerId }}</p>
|
||||
<p>
|
||||
FreeEnds: {{ gameState?.boardFreeEnds }} - Current Player:{{
|
||||
gameState?.currentPlayer?.name
|
||||
}}
|
||||
- Score: {{ gameState?.scoreboard }}
|
||||
</p>
|
||||
<p v-if="sessionState?.id">
|
||||
SessionID: {{ sessionState.id }} PlayerID: {{ playerState?.id }}
|
||||
</p>
|
||||
</section>
|
||||
<section class="block">
|
||||
<div class="game-container">
|
||||
<GameComponent :playerId="playerId" :canMakeMove="canMakeMove" @move="makeMove" />
|
||||
<GameComponent :playerId="playerState?.id" :canMakeMove="canMakeMove" @move="makeMove" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="block">
|
||||
<!-- <section class="block">
|
||||
<div class="fixed-grid has-8-cols">
|
||||
<div class="grid" v-if="!sessionId">
|
||||
<div class="grid" v-if="!sessionState?.id">
|
||||
<div class="cell">
|
||||
<button style="width: 200px" class="button" @click="createSession">
|
||||
Create Session
|
||||
@ -156,60 +103,8 @@ function copySeed() {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-1 action-select"></div>
|
||||
</div>
|
||||
|
||||
<div class="grid" style="margin-top: 16px; display: none">
|
||||
<div>
|
||||
<!-- <ul id="messages"></ul> -->
|
||||
<form id="form" action="">
|
||||
<div class="action-select select">
|
||||
<select
|
||||
v-model="selectdAction"
|
||||
id="event"
|
||||
autocomplete="off"
|
||||
@change="actionSelected"
|
||||
>
|
||||
<option value="">Select event</option>
|
||||
<option :key="option.value" v-for="option in options" :value="option">
|
||||
{{ option.value }}
|
||||
</option>
|
||||
</select>
|
||||
<button @click.prevent.stop="sendMessage">Send</button>
|
||||
</div>
|
||||
<!-- <p><input id="room" autocomplete="off" /></p> -->
|
||||
<p>
|
||||
<textarea
|
||||
v-model="data"
|
||||
id="message"
|
||||
autocomplete="off"
|
||||
placeholder="Data"
|
||||
></textarea>
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
<div>
|
||||
<div class="grid">
|
||||
<div>
|
||||
<textarea
|
||||
:value="responseField"
|
||||
id="response"
|
||||
autocomplete="off"
|
||||
placeholder="Response"
|
||||
></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<textarea
|
||||
:value="statusField"
|
||||
id="status"
|
||||
autocomplete="off"
|
||||
placeholder="Game status"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -1,11 +1,58 @@
|
||||
<script setup lang="ts">
|
||||
import { inject, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import useClipboard from 'vue-clipboard3'
|
||||
|
||||
import { useGameStore } from '@/stores/game'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { LoggingService } from '@/services/LoggingService'
|
||||
|
||||
let seed = ref('')
|
||||
|
||||
const router = useRouter()
|
||||
const gameStore = useGameStore()
|
||||
const { toClipboard } = useClipboard()
|
||||
const socketService: any = inject('socket')
|
||||
const logger: LoggingService = inject<LoggingService>('logger') as LoggingService
|
||||
|
||||
function startGame() {
|
||||
const { readyForStart, sessionState, isSessionStarted, playerState } = storeToRefs(gameStore)
|
||||
|
||||
async function setPlayerReady() {
|
||||
logger.debug('Starting game')
|
||||
if (!sessionState.value) {
|
||||
logger.error('No session found')
|
||||
return
|
||||
}
|
||||
await socketService.sendMessageWithAck('playerReady', {
|
||||
user: 'arhuako',
|
||||
sessionId: sessionState.value.id
|
||||
})
|
||||
readyForStart.value = true
|
||||
}
|
||||
|
||||
async function createMatch() {
|
||||
logger.debug('Creating match')
|
||||
socketService.sendMessageWithAck('createSession', { user: 'arhuako' })
|
||||
}
|
||||
|
||||
async function joinMatch() {
|
||||
const sessionId = sessionState?.value?.id
|
||||
const playerId = playerState?.value?.id
|
||||
if (sessionId && playerId) {
|
||||
await socketService.sendMessageWithAck('joinSession', {
|
||||
user: 'pepe',
|
||||
sessionId: sessionId
|
||||
})
|
||||
// sessionId.value = response.sessionId
|
||||
// playerId.value = response.playerId
|
||||
}
|
||||
}
|
||||
|
||||
async function startMatch() {
|
||||
if (sessionState.value && sessionState.value.id) {
|
||||
router.push({ name: 'game' })
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -14,8 +61,22 @@ function startGame() {
|
||||
<h1 class="title is-2">Welcome to the Player's Home Page</h1>
|
||||
<div class="block">
|
||||
<p>This is a protected route.</p>
|
||||
<p>{{ sessionState || 'No session' }}</p>
|
||||
<p>{{ playerState?.ready || 'No player state' }}</p>
|
||||
<p>Session started: {{ isSessionStarted }}</p>
|
||||
</div>
|
||||
<button class="button" @click="startGame">Start Game</button>
|
||||
<div class="block">
|
||||
<input class="input" style="margin-bottom: 0" v-model="seed" placeholder="Seed" />
|
||||
</div>
|
||||
<button class="button" @click="createMatch" v-if="!isSessionStarted">
|
||||
Create Match Session
|
||||
</button>
|
||||
<button class="button" @click="setPlayerReady" v-if="isSessionStarted">
|
||||
<span v-if="!readyForStart">Ready</span><span v-else>Unready</span>
|
||||
</button>
|
||||
<button class="button" @click="startMatch" v-if="readyForStart">
|
||||
<span>Start</span>
|
||||
</button>
|
||||
</section>
|
||||
<section class="section available-sessions">
|
||||
<h2 class="title is-4">Available Sessions</h2>
|
||||
|
@ -1,15 +1,24 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { AuthenticationService } from '@/services/AuthenticationService'
|
||||
import { inject, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const router = useRouter()
|
||||
const username = ref('')
|
||||
const password = ref('')
|
||||
|
||||
function login() {
|
||||
// if (username.value === 'admin' && password.value === 'password') {
|
||||
localStorage.setItem('token', 'true')
|
||||
const authService = inject<AuthenticationService>('auth')
|
||||
|
||||
async function login() {
|
||||
try {
|
||||
await authService?.login(username.value, password.value)
|
||||
router.push({ name: 'home' })
|
||||
} catch (error) {
|
||||
alert('Invalid username or password')
|
||||
}
|
||||
// if (username.value === 'admin' && password.value === 'password') {
|
||||
// localStorage.setItem('token', 'true')
|
||||
// router.push({ name: 'home' })
|
||||
// } else {
|
||||
// alert('Invalid username or password')
|
||||
// }
|
||||
|
Loading…
x
Reference in New Issue
Block a user