Compare commits
40 commits
leaderboar
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c1a32c1a30 | ||
|
|
adc971f587 | ||
|
|
28a3c48cdb | ||
| 7e2ac3f488 | |||
| 6c80829a0b | |||
| 41223dd786 | |||
|
|
e06ffa51a3 | ||
|
|
5dfbc6d39c | ||
| ad117b4d2c | |||
|
|
f4eeb11c68 | ||
|
|
c2bb6c104a | ||
| 6a0e200905 | |||
| 2ce3ed9cd7 | |||
| 438184f759 | |||
| 6a94e967e1 | |||
|
|
3befee6fdd | ||
|
|
dc3c2e0982 | ||
|
|
ec6d2201eb | ||
| 4c7875aa0d | |||
| 7c4ccfd12c | |||
| cb60e3d1ff | |||
| 5298d2a169 | |||
| 03facecc58 | |||
| edead5a412 | |||
| 3e8b9e141d | |||
| 3b034edc52 | |||
| da018a5c73 | |||
|
|
4c33b827c3 | ||
| e02555234f | |||
|
|
269faca609 | ||
| c052d08e0e | |||
|
|
8a538bb85f | ||
| a0ab1b7f56 | |||
|
|
b6ad404555 | ||
|
|
6fe71383b3 | ||
|
|
fe1375124b | ||
|
|
030189aa52 | ||
|
|
00b1f6a8e2 | ||
| 0135654a1e | |||
| c52f6018a6 |
25 changed files with 887 additions and 291 deletions
|
|
@ -1,5 +1,6 @@
|
||||||
POSTGRES_PORT=5432
|
POSTGRES_PORT=5432
|
||||||
EXPRESS_PORT=3000
|
EXPRESS_PORT=3000
|
||||||
|
FRONTEND_PORT=8080
|
||||||
|
|
||||||
POSTGRES_USER=postgres
|
POSTGRES_USER=postgres
|
||||||
POSTGRES_PASSWORD=postgres
|
POSTGRES_PASSWORD=postgres
|
||||||
|
|
|
||||||
183
README.md
183
README.md
|
|
@ -1,163 +1,30 @@
|
||||||
# RaspberryRocketeer
|
# RaspberryRocketeer
|
||||||
|
|
||||||
## Class Diagram of all classes
|
## How to run
|
||||||
|
|
||||||
```mermaid
|
### Copy .env
|
||||||
classDiagram
|
First you need to copy the `.env.example`.
|
||||||
direction BT
|
|
||||||
class Collidable {
|
|
||||||
collides(o: Entity) boolean
|
|
||||||
}
|
|
||||||
class Database {
|
|
||||||
any _db
|
|
||||||
any db
|
|
||||||
}
|
|
||||||
class Entity {
|
|
||||||
constructor(position: Position, width: number, height: number, fill: number)
|
|
||||||
Position _position
|
|
||||||
number _width
|
|
||||||
number _height
|
|
||||||
number fill
|
|
||||||
boolean _showHitbox
|
|
||||||
update() void
|
|
||||||
draw() void
|
|
||||||
Position position
|
|
||||||
number width
|
|
||||||
number height
|
|
||||||
boolean showHitbox
|
|
||||||
}
|
|
||||||
class Game {
|
|
||||||
number id
|
|
||||||
number score
|
|
||||||
string playtime
|
|
||||||
Date date
|
|
||||||
number userId
|
|
||||||
}
|
|
||||||
class GamePgPromiseRepository {
|
|
||||||
insert(game: Game) Promise~Game~
|
|
||||||
serialize(raw: any) Game
|
|
||||||
}
|
|
||||||
class GameRepository {
|
|
||||||
insert(game: Game) Promise~Game~
|
|
||||||
}
|
|
||||||
class HighscoreLeaderboard
|
|
||||||
class HighscoreLeaderboardPgPromiseRepository {
|
|
||||||
getAll() Promise~HighscoreLeaderboard~
|
|
||||||
serialize(raw: any) HighscoreLeaderboard
|
|
||||||
}
|
|
||||||
class HighscoreLeaderboardRepository {
|
|
||||||
getAll() Promise~HighscoreLeaderboard~
|
|
||||||
}
|
|
||||||
class Leaderboard
|
|
||||||
class LeaderboardEntry {
|
|
||||||
number username
|
|
||||||
number rank
|
|
||||||
T score
|
|
||||||
}
|
|
||||||
class Obstacle {
|
|
||||||
constructor(position: Position, obstacleWidth: number, obstacleHeight: number, pipeImagePath: string)
|
|
||||||
Pipe pipeTop
|
|
||||||
Pipe pipeBottom
|
|
||||||
number padding
|
|
||||||
number speed
|
|
||||||
number _distanceBetweenPipes
|
|
||||||
number _startX
|
|
||||||
resetPosition() void
|
|
||||||
randomizeHeight() void
|
|
||||||
randomRange(min: number, max: number) number
|
|
||||||
update() void
|
|
||||||
draw() void
|
|
||||||
collides(o: Entity) boolean
|
|
||||||
any startX
|
|
||||||
any distanceBetweenPipes
|
|
||||||
}
|
|
||||||
class Pipe {
|
|
||||||
constructor(positionX: number, width: number, height: number)
|
|
||||||
any _image
|
|
||||||
update() void
|
|
||||||
draw() void
|
|
||||||
move(speed: number) void
|
|
||||||
collides(o: Entity) boolean
|
|
||||||
any image
|
|
||||||
}
|
|
||||||
class Position {
|
|
||||||
constructor(x: number, y: number)
|
|
||||||
number _x
|
|
||||||
number _y
|
|
||||||
number x
|
|
||||||
number y
|
|
||||||
}
|
|
||||||
class Raspberry {
|
|
||||||
constructor(image: string)
|
|
||||||
number lift
|
|
||||||
number gravity
|
|
||||||
number _velocity
|
|
||||||
any _image
|
|
||||||
Position position
|
|
||||||
number maxVelocity
|
|
||||||
number WIDTH
|
|
||||||
number HEIGHT
|
|
||||||
number FILL
|
|
||||||
update() void
|
|
||||||
applyGravity() void
|
|
||||||
forceBoundaries() void
|
|
||||||
boost() void
|
|
||||||
draw() void
|
|
||||||
number velocity
|
|
||||||
any image
|
|
||||||
}
|
|
||||||
class TimeLeaderboard
|
|
||||||
class TimeLeaderboardPgPromiseRepository {
|
|
||||||
getAll() Promise~TimeLeaderboard~
|
|
||||||
serialize(raw: any) TimeLeaderboard
|
|
||||||
}
|
|
||||||
class TimeLeaderboardRepository {
|
|
||||||
getAll() Promise~TimeLeaderboard~
|
|
||||||
}
|
|
||||||
class User {
|
|
||||||
number id
|
|
||||||
string name
|
|
||||||
}
|
|
||||||
class UserPgPromiseRepository {
|
|
||||||
getById(id: number) Promise~User~
|
|
||||||
getByName(name: string) Promise~User~
|
|
||||||
withIdExists(id: number) Promise~boolean~
|
|
||||||
withNameExists(name: string) Promise~boolean~
|
|
||||||
insert(user: Omit~User, "id"~) Promise~User~
|
|
||||||
serialize(raw: any) User
|
|
||||||
}
|
|
||||||
class UserRepository {
|
|
||||||
getById(id: number) Promise~User~
|
|
||||||
getByName(name: string) Promise~User~
|
|
||||||
withIdExists(userId: number) Promise~boolean~
|
|
||||||
withNameExists(username: string) Promise~boolean~
|
|
||||||
insert(user: Omit~User, "id"~) Promise~User~
|
|
||||||
}
|
|
||||||
class UserScores {
|
|
||||||
number userId
|
|
||||||
number highscore
|
|
||||||
number totalScore
|
|
||||||
string totalPlaytime
|
|
||||||
number averageScore
|
|
||||||
number gamesPlayed
|
|
||||||
}
|
|
||||||
class UserScoresPgPromiseRepository {
|
|
||||||
getById(id: number) Promise~UserScores~
|
|
||||||
serialize(raw: any) UserScores
|
|
||||||
}
|
|
||||||
class UserScoresRepository {
|
|
||||||
getById(userId: number) Promise~UserScores~
|
|
||||||
}
|
|
||||||
|
|
||||||
GamePgPromiseRepository --> GameRepository
|
|
||||||
HighscoreLeaderboardPgPromiseRepository --> HighscoreLeaderboardRepository
|
|
||||||
Obstacle ..> Collidable
|
|
||||||
Obstacle --> Entity
|
|
||||||
Pipe ..> Collidable
|
|
||||||
Pipe --> Entity
|
|
||||||
Raspberry --> Entity
|
|
||||||
TimeLeaderboardPgPromiseRepository --> TimeLeaderboardRepository
|
|
||||||
UserPgPromiseRepository --> UserRepository
|
|
||||||
UserScoresPgPromiseRepository --> UserScoresRepository
|
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cp .env.example .env
|
||||||
```
|
```
|
||||||
|
|
||||||
|
<small>Note: It is recommended to change the values for the database user.</small>
|
||||||
|
|
||||||
|
### Install node packages
|
||||||
|
Go into the frontend folder using
|
||||||
|
```shell
|
||||||
|
cd frontend
|
||||||
|
```
|
||||||
|
and run:
|
||||||
|
```shell
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Start the container (in the project root)
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker compose up --build
|
||||||
|
```
|
||||||
|
|
||||||
|
You can then access the website on `localhost:8080`
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,9 @@ export const gameRoute = express.Router()
|
||||||
|
|
||||||
gameRoute.use(express.json())
|
gameRoute.use(express.json())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test
|
||||||
|
*/
|
||||||
gameRoute.post(
|
gameRoute.post(
|
||||||
'/add',
|
'/add',
|
||||||
body('playtime')
|
body('playtime')
|
||||||
|
|
@ -18,6 +21,8 @@ gameRoute.post(
|
||||||
body('userId')
|
body('userId')
|
||||||
.isInt({min: 1})
|
.isInt({min: 1})
|
||||||
.custom(userWithIdExists),
|
.custom(userWithIdExists),
|
||||||
|
body('score')
|
||||||
|
.isInt({min: 0}),
|
||||||
/**
|
/**
|
||||||
* After processing the errors of express-validator, inserts the game into the DB
|
* After processing the errors of express-validator, inserts the game into the DB
|
||||||
* @param req
|
* @param req
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ leaderboardRoute.get('/highscore',
|
||||||
//endregion
|
//endregion
|
||||||
const highscoreLeaderboardRepo: HighscoreLeaderboardRepository = new HighscoreLeaderboardPgPromiseRepository;
|
const highscoreLeaderboardRepo: HighscoreLeaderboardRepository = new HighscoreLeaderboardPgPromiseRepository;
|
||||||
let highscoreLeaderboard: HighscoreLeaderboard;
|
let highscoreLeaderboard: HighscoreLeaderboard;
|
||||||
if (req.query.pagination) {
|
if (req.query.pagination == true) {
|
||||||
const entriesPerPage = req.query.entriesPerPage;
|
const entriesPerPage = req.query.entriesPerPage;
|
||||||
const page = req.query.page;
|
const page = req.query.page;
|
||||||
highscoreLeaderboard = await highscoreLeaderboardRepo.getPage(entriesPerPage, page);
|
highscoreLeaderboard = await highscoreLeaderboardRepo.getPage(entriesPerPage, page);
|
||||||
|
|
@ -53,6 +53,7 @@ leaderboardRoute.get('/totalplaytime',
|
||||||
*/
|
*/
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
console.log(req.query)
|
||||||
//region validate parameters
|
//region validate parameters
|
||||||
const errors = validationResult(req);
|
const errors = validationResult(req);
|
||||||
if (!errors.isEmpty()) {
|
if (!errors.isEmpty()) {
|
||||||
|
|
@ -61,7 +62,8 @@ leaderboardRoute.get('/totalplaytime',
|
||||||
//endregion
|
//endregion
|
||||||
const timeLeaderboardRepo: TimeLeaderboardRepository = new TimeLeaderboardPgPromiseRepository;
|
const timeLeaderboardRepo: TimeLeaderboardRepository = new TimeLeaderboardPgPromiseRepository;
|
||||||
let timeLeaderboard: TimeLeaderboard;
|
let timeLeaderboard: TimeLeaderboard;
|
||||||
if (req.query.pagination) {
|
console.log(req.query)
|
||||||
|
if (req.query.pagination == true) {
|
||||||
const entriesPerPage = req.query.entriesPerPage;
|
const entriesPerPage = req.query.entriesPerPage;
|
||||||
const page = req.query.page;
|
const page = req.query.page;
|
||||||
timeLeaderboard = await timeLeaderboardRepo.getPage(entriesPerPage, page);
|
timeLeaderboard = await timeLeaderboardRepo.getPage(entriesPerPage, page);
|
||||||
|
|
|
||||||
|
|
@ -84,3 +84,30 @@ userRoute.get('/:userId/scores',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
userRoute.get('/:name',
|
||||||
|
param('name')
|
||||||
|
.isString()
|
||||||
|
.isLength({min: 3, max: 32})
|
||||||
|
.matches(USERNAME_VALIDATION_REGEX),
|
||||||
|
|
||||||
|
async (req, res) => {
|
||||||
|
const errors = validationResult(req);
|
||||||
|
if (!errors.isEmpty()) {
|
||||||
|
return res.status(400).json({ errors: errors.array() });
|
||||||
|
}
|
||||||
|
|
||||||
|
const name: string = req.params.name;
|
||||||
|
console.log(name)
|
||||||
|
|
||||||
|
try {
|
||||||
|
// get & return data
|
||||||
|
const userRepo: UserPgPromiseRepository = new UserPgPromiseRepository();
|
||||||
|
const user = await userRepo.getByName(name);
|
||||||
|
res.json(user);
|
||||||
|
} catch (error) {
|
||||||
|
// handle errors
|
||||||
|
console.log(error)
|
||||||
|
res.status(500).json({ errors: [{msg: "Internal server error"}]})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
insert into "user" (name) values ('dpettus0');
|
|
||||||
insert into "user" (name) values ('egreetland1');
|
|
||||||
insert into "user" (name) values ('smontford2');
|
|
||||||
insert into "user" (name) values ('idagwell3');
|
|
||||||
insert into "user" (name) values ('lgagan4');
|
|
||||||
insert into "user" (name) values ('acarmont5');
|
|
||||||
insert into "user" (name) values ('kjermyn6');
|
|
||||||
insert into "user" (name) values ('dokieran7');
|
|
||||||
insert into "user" (name) values ('pdrinkel8');
|
|
||||||
|
|
||||||
insert into game (user_id, score, playtime, date) values ('1', 74, '19:59', '2022-07-19');
|
|
||||||
insert into game (user_id, score, playtime, date) values ('1', 86, '20:32', '2022-11-24');
|
|
||||||
insert into game (user_id, score, playtime, date) values ('1', 68, '10:41', '2022-03-24');
|
|
||||||
insert into game (user_id, score, playtime, date) values ('2', 39, '5:55', '2022-06-01');
|
|
||||||
insert into game (user_id, score, playtime, date) values ('2', 20, '9:23', '2022-03-12');
|
|
||||||
insert into game (user_id, score, playtime, date) values ('2', 28, '23:45', '2022-04-01');
|
|
||||||
insert into game (user_id, score, playtime, date) values ('2', 44, '18:43', '2022-06-24');
|
|
||||||
insert into game (user_id, score, playtime, date) values ('3', 92, '14:54', '2022-11-06');
|
|
||||||
insert into game (user_id, score, playtime, date) values ('3', 73, '0:45', '2022-07-26');
|
|
||||||
insert into game (user_id, score, playtime, date) values ('3', 27, '2:49', '2022-02-03');
|
|
||||||
insert into game (user_id, score, playtime, date) values ('4', 26, '2:32', '2022-07-19');
|
|
||||||
insert into game (user_id, score, playtime, date) values ('4', 12, '17:03', '2022-04-25');
|
|
||||||
insert into game (user_id, score, playtime, date) values ('4', 6, '8:49', '2021-12-03');
|
|
||||||
insert into game (user_id, score, playtime, date) values ('4', 22, '22:27', '2022-03-02');
|
|
||||||
insert into game (user_id, score, playtime, date) values ('5', 94, '1:04', '2022-10-19');
|
|
||||||
insert into game (user_id, score, playtime, date) values ('5', 2, '2:14', '2022-04-06');
|
|
||||||
insert into game (user_id, score, playtime, date) values ('5', 21, '17:18', '2022-06-03');
|
|
||||||
insert into game (user_id, score, playtime, date) values ('6', 33, '16:01', '2022-02-02');
|
|
||||||
insert into game (user_id, score, playtime, date) values ('6', 27, '7:03', '2022-02-06');
|
|
||||||
insert into game (user_id, score, playtime, date) values ('6', 62, '0:45', '2022-11-15');
|
|
||||||
insert into game (user_id, score, playtime, date) values ('7', 12, '8:54', '2022-06-29');
|
|
||||||
insert into game (user_id, score, playtime, date) values ('7', 63, '16:01', '2022-11-05');
|
|
||||||
insert into game (user_id, score, playtime, date) values ('7', 29, '0:46', '2022-10-01');
|
|
||||||
insert into game (user_id, score, playtime, date) values ('8', 67, '1:27', '2022-09-29');
|
|
||||||
insert into game (user_id, score, playtime, date) values ('8', 84, '10:37', '2021-12-18');
|
|
||||||
insert into game (user_id, score, playtime, date) values ('8', 14, '19:14', '2022-01-31');
|
|
||||||
insert into game (user_id, score, playtime, date) values ('9', 21, '19:04', '2022-03-08');
|
|
||||||
insert into game (user_id, score, playtime, date) values ('9', 46, '2:34', '2022-04-18');
|
|
||||||
insert into game (user_id, score, playtime, date) values ('9', 78, '9:33', '2022-09-10');
|
|
||||||
insert into game (user_id, score, playtime, date) values ('9', 82, '11:19', '2022-11-29');
|
|
||||||
|
|
@ -21,3 +21,12 @@ services:
|
||||||
ports:
|
ports:
|
||||||
- "${EXPRESS_PORT}:3000"
|
- "${EXPRESS_PORT}:3000"
|
||||||
|
|
||||||
|
vue:
|
||||||
|
build: frontend
|
||||||
|
container_name: frontend
|
||||||
|
ports:
|
||||||
|
- "${FRONTEND_PORT}:8080"
|
||||||
|
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- ./frontend:/app
|
||||||
|
|
|
||||||
3
frontend/.dockerignore
Normal file
3
frontend/.dockerignore
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
node_modules
|
||||||
|
.gitignore
|
||||||
|
README.md
|
||||||
11
frontend/Dockerfile
Normal file
11
frontend/Dockerfile
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
FROM node:18
|
||||||
|
|
||||||
|
COPY . /app
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
ENTRYPOINT ["npm", "run", "serve"]
|
||||||
|
|
||||||
22
frontend/package-lock.json
generated
22
frontend/package-lock.json
generated
|
|
@ -8,9 +8,7 @@
|
||||||
"name": "raspberryrocketeer",
|
"name": "raspberryrocketeer",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/p5": "^1.4.3",
|
|
||||||
"bootstrap": "^5.2.3",
|
"bootstrap": "^5.2.3",
|
||||||
"p5": "^1.5.0",
|
|
||||||
"vue": "^3.2.13"
|
"vue": "^3.2.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
@ -831,11 +829,6 @@
|
||||||
"integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==",
|
"integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/p5": {
|
|
||||||
"version": "1.4.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/p5/-/p5-1.4.3.tgz",
|
|
||||||
"integrity": "sha512-UjgBi1/VU0Dz1/JxzRZrDKlC54rMELWW+6oHx1pRKzKkVgwzvP+dEjZ1JbeMjK12VJMUjDx4lhGUmnUL2jBASQ=="
|
|
||||||
},
|
|
||||||
"node_modules/@types/parse-json": {
|
"node_modules/@types/parse-json": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmmirror.com/@types/parse-json/-/parse-json-4.0.0.tgz",
|
"resolved": "https://registry.npmmirror.com/@types/parse-json/-/parse-json-4.0.0.tgz",
|
||||||
|
|
@ -6194,11 +6187,6 @@
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/p5": {
|
|
||||||
"version": "1.5.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/p5/-/p5-1.5.0.tgz",
|
|
||||||
"integrity": "sha512-zZFMVUmGkXe2G5H6Sw7xsVhgdxMyEN/6SZnZqYdQ51513kTqPslLnukkwTbGf8YtW0RetTU0FTjYQMXnFD7KnQ=="
|
|
||||||
},
|
|
||||||
"node_modules/param-case": {
|
"node_modules/param-case": {
|
||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
"resolved": "https://registry.npmmirror.com/param-case/-/param-case-3.0.4.tgz",
|
"resolved": "https://registry.npmmirror.com/param-case/-/param-case-3.0.4.tgz",
|
||||||
|
|
@ -9881,11 +9869,6 @@
|
||||||
"integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==",
|
"integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/p5": {
|
|
||||||
"version": "1.4.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/p5/-/p5-1.4.3.tgz",
|
|
||||||
"integrity": "sha512-UjgBi1/VU0Dz1/JxzRZrDKlC54rMELWW+6oHx1pRKzKkVgwzvP+dEjZ1JbeMjK12VJMUjDx4lhGUmnUL2jBASQ=="
|
|
||||||
},
|
|
||||||
"@types/parse-json": {
|
"@types/parse-json": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmmirror.com/@types/parse-json/-/parse-json-4.0.0.tgz",
|
"resolved": "https://registry.npmmirror.com/@types/parse-json/-/parse-json-4.0.0.tgz",
|
||||||
|
|
@ -14205,11 +14188,6 @@
|
||||||
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
|
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"p5": {
|
|
||||||
"version": "1.5.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/p5/-/p5-1.5.0.tgz",
|
|
||||||
"integrity": "sha512-zZFMVUmGkXe2G5H6Sw7xsVhgdxMyEN/6SZnZqYdQ51513kTqPslLnukkwTbGf8YtW0RetTU0FTjYQMXnFD7KnQ=="
|
|
||||||
},
|
|
||||||
"param-case": {
|
"param-case": {
|
||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
"resolved": "https://registry.npmmirror.com/param-case/-/param-case-3.0.4.tgz",
|
"resolved": "https://registry.npmmirror.com/param-case/-/param-case-3.0.4.tgz",
|
||||||
|
|
|
||||||
|
|
@ -9,14 +9,12 @@
|
||||||
"lint": "vue-cli-service lint"
|
"lint": "vue-cli-service lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/p5": "^1.4.3",
|
|
||||||
"bootstrap": "^5.2.3",
|
"bootstrap": "^5.2.3",
|
||||||
"p5": "^1.5.0",
|
"vue": "^3.2.13",
|
||||||
"vue": "^3.2.13"
|
"@vue/cli-service": "~5.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vue/cli-plugin-typescript": "~5.0.0",
|
"@vue/cli-plugin-typescript": "~5.0.0",
|
||||||
"@vue/cli-service": "~5.0.0",
|
|
||||||
"typescript": "~4.5.5"
|
"typescript": "~4.5.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
519
frontend/public/game.js
Normal file
519
frontend/public/game.js
Normal file
|
|
@ -0,0 +1,519 @@
|
||||||
|
var __extends = (this && this.__extends) || (function(){
|
||||||
|
var extendStatics = function(d, b){
|
||||||
|
extendStatics = Object.setPrototypeOf ||
|
||||||
|
({__proto__: []} instanceof Array && function(d, b){
|
||||||
|
d.__proto__ = b;
|
||||||
|
}) ||
|
||||||
|
function(d, b){
|
||||||
|
for(var p in b) if(Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p];
|
||||||
|
};
|
||||||
|
return extendStatics(d, b);
|
||||||
|
};
|
||||||
|
return function(d, b){
|
||||||
|
if(typeof b !== "function" && b !== null)
|
||||||
|
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
|
||||||
|
extendStatics(d, b);
|
||||||
|
|
||||||
|
function __(){
|
||||||
|
this.constructor = d;
|
||||||
|
}
|
||||||
|
|
||||||
|
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
var PIPE_IMAGE_PATH = "resources/dell-pc-min-min-small.png";
|
||||||
|
var BACKGROUND_IMAGE_PATH = "resources/htl-steyr-front.jpg";
|
||||||
|
var RASPBERRY_IMAGE_PATH = "resources/raspberry-rocket.png";
|
||||||
|
var FLOOR_IMAGE_PATH = "resources/table-min-min.png";
|
||||||
|
var FONT_PATH = "resources/PressStart2P-Regular.ttf";
|
||||||
|
var OBSTACLE_COUNT = 3;
|
||||||
|
var BOOST_KEYS = ["k", " "];
|
||||||
|
var floorHeight;
|
||||||
|
var obstacleWidth;
|
||||||
|
var obstacleOffset;
|
||||||
|
var backgroundImage;
|
||||||
|
var pipeImage;
|
||||||
|
var floorImage;
|
||||||
|
var font;
|
||||||
|
var obstacles = [];
|
||||||
|
var raspberry;
|
||||||
|
var startTime;
|
||||||
|
var playTime;
|
||||||
|
var score = 0;
|
||||||
|
var paused;
|
||||||
|
var hasAlreadyScored = false;
|
||||||
|
var hasDied = true;
|
||||||
|
var ready = true;
|
||||||
|
|
||||||
|
function preload(){
|
||||||
|
font = loadFont(FONT_PATH);
|
||||||
|
backgroundImage = loadImage(BACKGROUND_IMAGE_PATH);
|
||||||
|
pipeImage = loadImage(PIPE_IMAGE_PATH);
|
||||||
|
floorImage = loadImage(FLOOR_IMAGE_PATH);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setup(){
|
||||||
|
createCanvas(1085, 600);
|
||||||
|
floorHeight = height / 5;
|
||||||
|
setupObstacleConsts();
|
||||||
|
setupFont();
|
||||||
|
setupGame();
|
||||||
|
var originalSetItem = localStorage.setItem;
|
||||||
|
localStorage.setItem = function(key, value){
|
||||||
|
var event = new Event('itemInserted');
|
||||||
|
|
||||||
|
event.value = value; // Optional..
|
||||||
|
event.key = key; // Optional..
|
||||||
|
window.dispatchEvent(event);
|
||||||
|
originalSetItem.apply(this, arguments);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupObstacleConsts(){
|
||||||
|
obstacleOffset = width / OBSTACLE_COUNT;
|
||||||
|
obstacleWidth = width / 22.727272727272727272;
|
||||||
|
Obstacle.distanceBetweenPipes = height / 2.5;
|
||||||
|
Obstacle.startX = width;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupFont(){
|
||||||
|
textSize(75);
|
||||||
|
textAlign(CENTER);
|
||||||
|
textFont(font);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupGame(){
|
||||||
|
paused = true;
|
||||||
|
raspberry = new Raspberry(RASPBERRY_IMAGE_PATH);
|
||||||
|
setupObstacles();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupObstacles(){
|
||||||
|
obstacles = [];
|
||||||
|
instantiateObstacles(OBSTACLE_COUNT);
|
||||||
|
obstacles.forEach(function(obstacle){
|
||||||
|
return obstacle.randomizeHeight();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function instantiateObstacles(number){
|
||||||
|
for(var i = 0; i < number; i++){
|
||||||
|
obstacles.push(new Obstacle(new Position(width + obstacleOffset * i, 0), obstacleWidth, height, pipeImage));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function draw(){
|
||||||
|
update();
|
||||||
|
gameLoop();
|
||||||
|
drawGame();
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawGame(){
|
||||||
|
drawScenery();
|
||||||
|
drawEntities();
|
||||||
|
displayScore();
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawScenery(){
|
||||||
|
background(backgroundImage);
|
||||||
|
drawFloor();
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawFloor(){
|
||||||
|
push();
|
||||||
|
noFill();
|
||||||
|
image(floorImage, 0, height - floorHeight, width, floorHeight);
|
||||||
|
rect(0, height - floorHeight, width, floorHeight);
|
||||||
|
pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawEntities(){
|
||||||
|
raspberry.draw();
|
||||||
|
drawObstacles();
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawObstacles(){
|
||||||
|
obstacles.forEach(function(obstacle){
|
||||||
|
obstacle.draw();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function gameLoop(){
|
||||||
|
if(!paused){
|
||||||
|
collisionCheck(obstacles[0]);
|
||||||
|
checkRaspberryScore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function collisionCheck(o){
|
||||||
|
if(o.collides(raspberry)){
|
||||||
|
die();
|
||||||
|
setupGame();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function die(){
|
||||||
|
if(localStorage.getItem("frontend-ready") == "false")
|
||||||
|
return;
|
||||||
|
|
||||||
|
ready = false;
|
||||||
|
hasDied = true;
|
||||||
|
playTime = Date.now() - startTime;
|
||||||
|
exportToLocalStorage();
|
||||||
|
setTimeout(function(){
|
||||||
|
return ready = true;
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function exportToLocalStorage(){
|
||||||
|
localStorage.setItem("game-playTime", String(playTime));
|
||||||
|
localStorage.setItem("game-score", String(score));
|
||||||
|
localStorage.setItem("game-isRunning", String(!hasDied));
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayScore(){
|
||||||
|
push();
|
||||||
|
fill(195, 33, 34);
|
||||||
|
text(score, 0, height / 8, width, height);
|
||||||
|
pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
function update(){
|
||||||
|
if(!paused){
|
||||||
|
raspberry.update();
|
||||||
|
}
|
||||||
|
obstacles.forEach(function(obstacle){
|
||||||
|
if(!paused){
|
||||||
|
obstacle.update();
|
||||||
|
checkObstacleReset(obstacle);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkObstacleReset(obstacle){
|
||||||
|
if(obstacle.position.x < -obstacleWidth){
|
||||||
|
obstacle.resetPosition();
|
||||||
|
obstacles.shift();
|
||||||
|
obstacles.push(obstacle);
|
||||||
|
hasAlreadyScored = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkRaspberryScore(){
|
||||||
|
if((obstacles[0].position.x + obstacles[0].width / 2) < (raspberry.position.x + raspberry.width / 2)
|
||||||
|
&& !hasAlreadyScored){
|
||||||
|
score += 1;
|
||||||
|
hasAlreadyScored = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetScore(){
|
||||||
|
if(!hasDied || localStorage.getItem("frontend-ready") == "false")
|
||||||
|
return;
|
||||||
|
|
||||||
|
hasDied = false;
|
||||||
|
score = 0;
|
||||||
|
hasAlreadyScored = false;
|
||||||
|
startTime = Date.now();
|
||||||
|
exportToLocalStorage();
|
||||||
|
}
|
||||||
|
|
||||||
|
function keyPressed(){
|
||||||
|
if(!ready)
|
||||||
|
return;
|
||||||
|
if(BOOST_KEYS.includes(key.toLowerCase())){
|
||||||
|
resetScore();
|
||||||
|
raspberry.boost();
|
||||||
|
}
|
||||||
|
if(key == "Escape"){
|
||||||
|
paused = !paused;
|
||||||
|
} else if(paused){
|
||||||
|
paused = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var Entity = (function(){
|
||||||
|
function Entity(position, width, height, fill){
|
||||||
|
this.position = position;
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
this.fill = fill;
|
||||||
|
this._showHitbox = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.defineProperty(Entity.prototype, "position", {
|
||||||
|
get: function(){
|
||||||
|
return this._position;
|
||||||
|
},
|
||||||
|
set: function(value){
|
||||||
|
this._position = value;
|
||||||
|
},
|
||||||
|
enumerable: false,
|
||||||
|
configurable: true
|
||||||
|
});
|
||||||
|
Object.defineProperty(Entity.prototype, "width", {
|
||||||
|
get: function(){
|
||||||
|
return this._width;
|
||||||
|
},
|
||||||
|
set: function(value){
|
||||||
|
this._width = value;
|
||||||
|
},
|
||||||
|
enumerable: false,
|
||||||
|
configurable: true
|
||||||
|
});
|
||||||
|
Object.defineProperty(Entity.prototype, "height", {
|
||||||
|
get: function(){
|
||||||
|
return this._height;
|
||||||
|
},
|
||||||
|
set: function(value){
|
||||||
|
this._height = value;
|
||||||
|
},
|
||||||
|
enumerable: false,
|
||||||
|
configurable: true
|
||||||
|
});
|
||||||
|
Object.defineProperty(Entity.prototype, "showHitbox", {
|
||||||
|
get: function(){
|
||||||
|
return this._showHitbox;
|
||||||
|
},
|
||||||
|
set: function(value){
|
||||||
|
this._showHitbox = value;
|
||||||
|
},
|
||||||
|
enumerable: false,
|
||||||
|
configurable: true
|
||||||
|
});
|
||||||
|
Entity.prototype.draw = function(){
|
||||||
|
push();
|
||||||
|
fill(this.fill);
|
||||||
|
rect(this.position.x, this.position.y, this.width, this.height);
|
||||||
|
pop();
|
||||||
|
};
|
||||||
|
return Entity;
|
||||||
|
}());
|
||||||
|
var Obstacle = (function(_super){
|
||||||
|
__extends(Obstacle, _super);
|
||||||
|
|
||||||
|
function Obstacle(position, obstacleWidth, obstacleHeight, image){
|
||||||
|
var _this = _super.call(this, position, obstacleWidth, obstacleHeight, 0) || this;
|
||||||
|
_this.speed = 3;
|
||||||
|
_this.padding = height / 6.6666666666666666;
|
||||||
|
_this.createPipes(position, obstacleHeight, obstacleWidth, image);
|
||||||
|
return _this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.defineProperty(Obstacle, "startX", {
|
||||||
|
set: function(value){
|
||||||
|
this._startX = value;
|
||||||
|
},
|
||||||
|
enumerable: false,
|
||||||
|
configurable: true
|
||||||
|
});
|
||||||
|
Object.defineProperty(Obstacle, "distanceBetweenPipes", {
|
||||||
|
set: function(value){
|
||||||
|
this._distanceBetweenPipes = value;
|
||||||
|
},
|
||||||
|
enumerable: false,
|
||||||
|
configurable: true
|
||||||
|
});
|
||||||
|
Obstacle.prototype.createPipes = function(position, obstacleHeight, obstacleWidth, pipeImage){
|
||||||
|
this.pipeTop = new Pipe(position.x, obstacleWidth, obstacleHeight, pipeImage);
|
||||||
|
this.pipeBottom = new Pipe(position.x, obstacleWidth, obstacleHeight, pipeImage);
|
||||||
|
};
|
||||||
|
Obstacle.prototype.resetPosition = function(){
|
||||||
|
this.randomizeHeight();
|
||||||
|
this.pipeBottom.position.x = Obstacle._startX;
|
||||||
|
this.pipeTop.position.x = Obstacle._startX;
|
||||||
|
};
|
||||||
|
Obstacle.prototype.randomizeHeight = function(){
|
||||||
|
this.pipeTop.height = this.randomRange(this.padding, height - this.padding - Obstacle._distanceBetweenPipes);
|
||||||
|
this.pipeBottom.position.y = this.pipeTop.height + Obstacle._distanceBetweenPipes;
|
||||||
|
this.pipeBottom.height = height - this.pipeTop.height - this.padding;
|
||||||
|
};
|
||||||
|
Obstacle.prototype.randomRange = function(min, max){
|
||||||
|
return Math.random() * (max - min) + min;
|
||||||
|
};
|
||||||
|
Obstacle.prototype.update = function(){
|
||||||
|
this.pipeTop.move(this.speed);
|
||||||
|
this.pipeBottom.move(this.speed);
|
||||||
|
this.position.x = this.pipeTop.position.x;
|
||||||
|
};
|
||||||
|
Obstacle.prototype.draw = function(){
|
||||||
|
this.pipeTop.draw();
|
||||||
|
this.pipeBottom.draw();
|
||||||
|
};
|
||||||
|
Obstacle.prototype.collides = function(o){
|
||||||
|
return this.pipeTop.collides(o) || this.pipeBottom.collides(o);
|
||||||
|
};
|
||||||
|
return Obstacle;
|
||||||
|
}(Entity));
|
||||||
|
var Pipe = (function(_super){
|
||||||
|
__extends(Pipe, _super);
|
||||||
|
|
||||||
|
function Pipe(positionX, width, height, image){
|
||||||
|
var _this = _super.call(this, new Position(positionX, 0), width, height, 0) || this;
|
||||||
|
_this.image = image;
|
||||||
|
return _this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Pipe.prototype.update = function(){
|
||||||
|
};
|
||||||
|
Pipe.prototype.draw = function(){
|
||||||
|
push();
|
||||||
|
noFill();
|
||||||
|
var imageAspectRatio = this.image.height / this.image.width;
|
||||||
|
var computedImageHeight = imageAspectRatio * this.width;
|
||||||
|
this.drawImage(computedImageHeight, imageAspectRatio);
|
||||||
|
rect(this.position.x, this.position.y, this.width, this.height);
|
||||||
|
pop();
|
||||||
|
};
|
||||||
|
Pipe.prototype.drawImage = function(computedImageHeight, imageAspectRatio){
|
||||||
|
if(this.height > computedImageHeight){
|
||||||
|
var maxImageYPos = Math.ceil(this.height / computedImageHeight) * computedImageHeight;
|
||||||
|
for(var imageYPosition = 0; imageYPosition < maxImageYPos; imageYPosition += computedImageHeight){
|
||||||
|
if(imageYPosition + computedImageHeight >= maxImageYPos){
|
||||||
|
this.cropLastImage(imageYPosition, computedImageHeight, imageAspectRatio);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
image(this.image, this.position.x, this.position.y + imageYPosition, this.width, computedImageHeight);
|
||||||
|
}
|
||||||
|
} else{
|
||||||
|
image(this.image, this.position.x, this.position.y, this.width, this.height);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Pipe.prototype.cropLastImage = function(imageYPosition, computedImageHeight, imageAspectRatio){
|
||||||
|
var amountOfImages = Math.floor(imageYPosition / computedImageHeight);
|
||||||
|
var heightToEdge = this.height - (amountOfImages * computedImageHeight);
|
||||||
|
var croppedImage = this.image.get(0, 0, this.image.width, this.image.height - (heightToEdge * imageAspectRatio));
|
||||||
|
image(croppedImage, this.position.x, this.position.y + imageYPosition, this.width, heightToEdge);
|
||||||
|
};
|
||||||
|
Pipe.prototype.move = function(speed){
|
||||||
|
this.position.x -= speed;
|
||||||
|
};
|
||||||
|
Pipe.prototype.collides = function(o){
|
||||||
|
return this.position.x < (o.position.x + o.width) &&
|
||||||
|
(this.position.x + this.width) > o.position.x &&
|
||||||
|
this.position.y < (o.position.y + o.height) &&
|
||||||
|
(this.position.y + this.height) > o.position.y;
|
||||||
|
};
|
||||||
|
return Pipe;
|
||||||
|
}(Entity));
|
||||||
|
var Position = (function(){
|
||||||
|
function Position(x, y){
|
||||||
|
this._x = x;
|
||||||
|
this._y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.defineProperty(Position.prototype, "x", {
|
||||||
|
get: function(){
|
||||||
|
return this._x;
|
||||||
|
},
|
||||||
|
set: function(value){
|
||||||
|
this._x = value;
|
||||||
|
},
|
||||||
|
enumerable: false,
|
||||||
|
configurable: true
|
||||||
|
});
|
||||||
|
Object.defineProperty(Position.prototype, "y", {
|
||||||
|
get: function(){
|
||||||
|
return this._y;
|
||||||
|
},
|
||||||
|
set: function(value){
|
||||||
|
this._y = value;
|
||||||
|
},
|
||||||
|
enumerable: false,
|
||||||
|
configurable: true
|
||||||
|
});
|
||||||
|
return Position;
|
||||||
|
}());
|
||||||
|
var Raspberry = (function(_super){
|
||||||
|
__extends(Raspberry, _super);
|
||||||
|
|
||||||
|
function Raspberry(image){
|
||||||
|
var _this = this;
|
||||||
|
Raspberry.position = new Position(width / 6, height / 2);
|
||||||
|
Raspberry.height = height / 14.2857142857142857;
|
||||||
|
Raspberry.width = width / 11.1111111111111111;
|
||||||
|
_this = _super.call(this, Raspberry.position, Raspberry.width, Raspberry.height, Raspberry.FILL) || this;
|
||||||
|
_this.lift = -15;
|
||||||
|
_this.gravity = 0.45;
|
||||||
|
_this._velocity = 0;
|
||||||
|
Raspberry.bottomFloorOffset = (height / 5) - (height / 15 / 2);
|
||||||
|
_this.image = image;
|
||||||
|
return _this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.defineProperty(Raspberry.prototype, "velocity", {
|
||||||
|
get: function(){
|
||||||
|
return this._velocity;
|
||||||
|
},
|
||||||
|
set: function(value){
|
||||||
|
this._velocity = (Math.abs(this.velocity) > Raspberry.maxVelocity) ? Raspberry.maxVelocity : value;
|
||||||
|
},
|
||||||
|
enumerable: false,
|
||||||
|
configurable: true
|
||||||
|
});
|
||||||
|
Object.defineProperty(Raspberry.prototype, "image", {
|
||||||
|
get: function(){
|
||||||
|
return this._image;
|
||||||
|
},
|
||||||
|
set: function(path){
|
||||||
|
this._image = loadImage(path);
|
||||||
|
},
|
||||||
|
enumerable: false,
|
||||||
|
configurable: true
|
||||||
|
});
|
||||||
|
Raspberry.prototype.update = function(){
|
||||||
|
this.applyGravity();
|
||||||
|
this.forceBoundaries();
|
||||||
|
};
|
||||||
|
Raspberry.prototype.applyGravity = function(){
|
||||||
|
this.velocity += this.gravity;
|
||||||
|
this.position.y += this.velocity;
|
||||||
|
};
|
||||||
|
Raspberry.prototype.forceBoundaries = function(){
|
||||||
|
this.boundaryTop();
|
||||||
|
this.boundaryBottom();
|
||||||
|
};
|
||||||
|
Raspberry.prototype.boundaryTop = function(){
|
||||||
|
if(this.position.y < 0){
|
||||||
|
this.position.y = 0;
|
||||||
|
this.velocity = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Raspberry.prototype.boundaryBottom = function(){
|
||||||
|
if(this.position.y + this.height + Raspberry.bottomFloorOffset > height){
|
||||||
|
this.position.y = height - this.height - Raspberry.bottomFloorOffset;
|
||||||
|
this.velocity = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Raspberry.prototype.boost = function(){
|
||||||
|
this.velocity += this.lift;
|
||||||
|
};
|
||||||
|
Raspberry.prototype.draw = function(){
|
||||||
|
push();
|
||||||
|
noFill();
|
||||||
|
this.setPose();
|
||||||
|
this.drawObject();
|
||||||
|
pop();
|
||||||
|
};
|
||||||
|
Raspberry.prototype.drawObject = function(){
|
||||||
|
this.drawHitBox();
|
||||||
|
this.drawRocket();
|
||||||
|
};
|
||||||
|
Raspberry.prototype.drawRocket = function(){
|
||||||
|
image(this.image, 0, 0, this.width, this.height);
|
||||||
|
rect(0, 0, this.width, this.height);
|
||||||
|
};
|
||||||
|
Raspberry.prototype.drawHitBox = function(){
|
||||||
|
if(!this.showHitbox){
|
||||||
|
noStroke();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Raspberry.prototype.setPose = function(){
|
||||||
|
translate(this.position.x, this.position.y);
|
||||||
|
rotate((PI / 2) * (this.velocity / Raspberry.maxVelocity));
|
||||||
|
};
|
||||||
|
Raspberry.maxVelocity = 75;
|
||||||
|
Raspberry.FILL = 0;
|
||||||
|
return Raspberry;
|
||||||
|
}(Entity));
|
||||||
|
//# sourceMappingURL=build.js.map
|
||||||
|
|
@ -10,8 +10,8 @@
|
||||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
|
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
|
||||||
|
|
||||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||||
<script src="./game.js"></script>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/p5@1.5.0/lib/p5.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/p5@1.5.0/lib/p5.js"></script>
|
||||||
|
<script src="./game.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body style="background-color: beige;">
|
<body style="background-color: beige;">
|
||||||
<noscript>
|
<noscript>
|
||||||
|
|
|
||||||
BIN
frontend/public/resources/dell-pc-min-min-small.png
Normal file
BIN
frontend/public/resources/dell-pc-min-min-small.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 144 KiB |
BIN
frontend/public/resources/htl-steyr-front.jpg
Normal file
BIN
frontend/public/resources/htl-steyr-front.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 618 KiB |
BIN
frontend/public/resources/rickroll.png
Normal file
BIN
frontend/public/resources/rickroll.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.6 KiB |
BIN
frontend/public/resources/table-min-min.png
Normal file
BIN
frontend/public/resources/table-min-min.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 MiB |
|
|
@ -4,11 +4,17 @@
|
||||||
<Header></Header>
|
<Header></Header>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<UserScores :userScores="userScores" ></UserScores>
|
<UserScores :userScores="userScores"></UserScores>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<Game class="col">
|
<Game :class="user ? '' : 'hidden'" :user-id="this.user?.id"
|
||||||
|
@gameFinished="this.updateUserScores()" ref="game">
|
||||||
</Game>
|
</Game>
|
||||||
|
<Login v-if="!user" @userChange="(event) => {this.updateUser(event);}">
|
||||||
|
</Login>
|
||||||
|
<div v-if="user" class="logout-wrapper offset-10 col-2">
|
||||||
|
<RRButton @click="logOut()" text="Logout"></RRButton>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-4">
|
<div class="col-4">
|
||||||
|
|
@ -22,7 +28,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import {defineComponent} from 'vue';
|
||||||
import Leaderboard from './components/Leaderboard.vue';
|
import Leaderboard from './components/Leaderboard.vue';
|
||||||
import UserScores from './components/UserScores.vue';
|
import UserScores from './components/UserScores.vue';
|
||||||
import Game from './components/Game.vue';
|
import Game from './components/Game.vue';
|
||||||
|
|
@ -30,10 +36,16 @@ import Header from './components/Header.vue';
|
||||||
|
|
||||||
import "bootstrap/dist/css/bootstrap.min.css";
|
import "bootstrap/dist/css/bootstrap.min.css";
|
||||||
import "bootstrap/dist/js/bootstrap.min.js";
|
import "bootstrap/dist/js/bootstrap.min.js";
|
||||||
|
import Login from "@/components/Login.vue";
|
||||||
|
import {Rest} from "@/model/Rest";
|
||||||
|
import {User} from "@/model/User";
|
||||||
|
import RRButton from "@/components/RRButton.vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'App',
|
name: 'App',
|
||||||
components: {
|
components: {
|
||||||
|
RRButton,
|
||||||
|
Login,
|
||||||
UserScores,
|
UserScores,
|
||||||
Leaderboard,
|
Leaderboard,
|
||||||
Game,
|
Game,
|
||||||
|
|
@ -42,21 +54,42 @@ export default defineComponent({
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
userScores: {},
|
userScores: {},
|
||||||
userId: 1,
|
userId: -1,
|
||||||
|
user: null as User | null,
|
||||||
|
leaderboardEvent: new Event('reloadLeaderboard')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
created() {
|
||||||
|
localStorage.setItem("frontend-ready", "false");
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
async fetchFromApi(path: string, method: "GET" | "POST") {
|
async fetchFromApi(path: string, method: "GET" | "POST") {
|
||||||
let res: Response = await fetch("http://localhost:3000" + path, {method: method,});
|
let res: Response = await fetch(Rest.URL + path, {method: method,});
|
||||||
return await res.json();
|
return await res.json();
|
||||||
},
|
},
|
||||||
async fetchUserScores() {
|
async fetchUserScores() {
|
||||||
|
if (this.userId == -1) return;
|
||||||
return await this.fetchFromApi(`/user/${this.userId}/scores`, "GET");
|
return await this.fetchFromApi(`/user/${this.userId}/scores`, "GET");
|
||||||
},
|
},
|
||||||
|
async updateUserScores() {
|
||||||
|
this.userScores = await this.fetchUserScores();
|
||||||
|
},
|
||||||
|
async updateUser(user: User) {
|
||||||
|
if (user) {
|
||||||
|
this.user = user;
|
||||||
|
this.userId = user.id ?? -1;
|
||||||
|
await this.updateUserScores();
|
||||||
|
}
|
||||||
|
window.dispatchEvent(this.leaderboardEvent);
|
||||||
|
},
|
||||||
|
logOut(){
|
||||||
|
this.user = null;
|
||||||
|
this.userId = -1;
|
||||||
|
this.userScores = {};
|
||||||
|
localStorage.setItem('frontend-ready', 'false');
|
||||||
|
}
|
||||||
},
|
},
|
||||||
async created() {
|
|
||||||
this.userScores = await this.fetchUserScores();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -68,10 +101,19 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
.row {
|
.row {
|
||||||
margin-top:2em;
|
margin-top: 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.everything {
|
.everything {
|
||||||
margin-bottom: 4em;
|
margin-bottom: 4em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: right;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,66 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<main></main>
|
<main></main>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script>
|
||||||
|
import App from "@/App.vue";
|
||||||
|
import {Rest} from "@/model/Rest";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Game"
|
name: "Game",
|
||||||
|
props: {
|
||||||
|
userId: null
|
||||||
|
},
|
||||||
|
emits: ['gameFinished'],
|
||||||
|
created() {
|
||||||
|
window.addEventListener('itemInserted', event => this.localStorageHandler(event), false);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
localStorageHandler(event) {
|
||||||
|
if (event.key !== 'game-isRunning') return;
|
||||||
|
|
||||||
|
if (event.value === 'false') { //means game is over
|
||||||
|
let playTime = this.msToHMS(Number(localStorage.getItem('game-playTime')));
|
||||||
|
let score = Number(localStorage.getItem('game-score'));
|
||||||
|
this.submitGame(score, playTime);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
msToHMS(ms) {
|
||||||
|
// 1- Convert to seconds:
|
||||||
|
let seconds = ms / 1000;
|
||||||
|
// 2- Extract hours:
|
||||||
|
const hours = parseInt(String(seconds / 3600)); // 3,600 seconds in 1 hour
|
||||||
|
seconds = seconds % 3600; // seconds remaining after extracting hours
|
||||||
|
// 3- Extract minutes:
|
||||||
|
const minutes = parseInt(String(seconds / 60)); // 60 seconds in 1 minute
|
||||||
|
// 4- Keep only seconds not extracted to minutes:
|
||||||
|
seconds = seconds % 60;
|
||||||
|
return ((hours < 10) ? "0" : "") + hours + ":" + ((minutes < 10) ? "0" : "") + minutes + ":" + ((seconds < 10) ? "0" : "") + Math.floor(seconds);
|
||||||
|
},
|
||||||
|
|
||||||
|
async submitGame(score, playTime) {
|
||||||
|
let body = {
|
||||||
|
score: score,
|
||||||
|
playtime: playTime,
|
||||||
|
date: new Date().toISOString().substring(0, 10),
|
||||||
|
userId: this.userId,
|
||||||
|
}
|
||||||
|
let header = {
|
||||||
|
Accept: "application/json",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
};
|
||||||
|
await fetch(Rest.URL + '/game/add', {method: 'POST', body: JSON.stringify(body), headers: header});
|
||||||
|
this.$emit('gameFinished');
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#p5_loading {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -3,14 +3,17 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<h3 class="col-10"><strong>{{ this.title() }}</strong></h3>
|
<h3 class="col-10"><strong>{{ this.title() }}</strong></h3>
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
<Button @click="prevPage" text="<"></Button>
|
<RRButton @click="prevPage" text="<"></RRButton>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
<Button @click="nextPage" text=">"></Button>
|
<RRButton @click="nextPage" text=">"></RRButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row" v-for="entry in this.page" :key="entry.rank" >
|
<div class="row" v-if="this.page.length > 0" v-for="entry in this.page" :key="entry.rank">
|
||||||
<LeaderboardEntry :entry="entry" ></LeaderboardEntry>
|
<LeaderboardEntry :entry="entry"></LeaderboardEntry>
|
||||||
|
</div>
|
||||||
|
<div class="row" v-else>
|
||||||
|
Loading...
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -18,13 +21,14 @@
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
import LeaderboardEntry from "@/components/LeaderboardEntry.vue";
|
import LeaderboardEntry from "@/components/LeaderboardEntry.vue";
|
||||||
import Button from "@/components/Button.vue";
|
import RRButton from "@/components/RRButton.vue";
|
||||||
|
import {Rest} from "@/model/Rest";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Leaderboard",
|
name: "Leaderboard",
|
||||||
components: {
|
components: {
|
||||||
LeaderboardEntry,
|
LeaderboardEntry,
|
||||||
Button,
|
RRButton,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
@ -34,37 +38,47 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
type: "totalplaytime" | "highscore",
|
type: "totalplaytime" | "highscore",
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.updatePage();
|
this.updatePage();
|
||||||
},
|
window.addEventListener('itemInserted', event => this.onItemInserted(event), false);
|
||||||
updated() {
|
|
||||||
this.updatePage()
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async fetchPage() {
|
async fetchPage() {
|
||||||
let res = await fetch(`http://localhost:3000/leaderboard/${this.type}?pagination=true&entriesPerPage=${this.entriesPerPage}&page=${this.pageNumber}`, {method: "GET"});
|
let res = await fetch(`${Rest.URL}/leaderboard/${this.type}?pagination=true&entriesPerPage=${this.entriesPerPage}&page=${this.pageNumber}`, {method: "GET"});
|
||||||
return await res.json();
|
return await res.json();
|
||||||
},
|
},
|
||||||
title() {
|
title() {
|
||||||
return this.type === "totalplaytime" ? "Total Playtime" : "Highscore";
|
return this.type === "totalplaytime" ? "Total Playtime" : "Highscore";
|
||||||
},
|
},
|
||||||
nextPage() {
|
async nextPage() {
|
||||||
|
if (this.page.length !== this.entriesPerPage) return;
|
||||||
|
|
||||||
this.pageNumber++;
|
this.pageNumber++;
|
||||||
this.updatePage();
|
await this.updatePage();
|
||||||
|
if (this.page.length === 0) {
|
||||||
|
this.prevPage();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
prevPage() {
|
prevPage() {
|
||||||
if (this.pageNumber > 0) this.pageNumber--;
|
if (this.pageNumber <= 0) return;
|
||||||
|
|
||||||
|
this.pageNumber--;
|
||||||
this.updatePage();
|
this.updatePage();
|
||||||
},
|
},
|
||||||
async updatePage() {
|
async updatePage() {
|
||||||
let tempPage = await this.fetchPage();
|
let tempPage = await this.fetchPage();
|
||||||
for (let i = 0; i < this.entriesPerPage; i++) {
|
for (let i = 0; i < this.entriesPerPage; i++) {
|
||||||
this.page.pop()
|
this.page.pop();
|
||||||
}
|
}
|
||||||
for (const entry of tempPage) {
|
for (const entry of tempPage) {
|
||||||
this.page.push(entry)
|
this.page.push(entry);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onItemInserted(event) {
|
||||||
|
if (event.key === 'game-isRunning' && event.value === 'false') {
|
||||||
|
this.updatePage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,18 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="col-1 text-left">{{this.entry.rank}}</div>
|
<div class="col-1 text-right">{{this.entry.rank}}</div>
|
||||||
<div class="col text-left">{{this.entry.username}}</div>
|
<span class="col offset-1 text-left username">{{this.entry.username}}</span>
|
||||||
<div class="col text-right">{{this.entry.score}}</div>
|
<div class="col-2 text-right">{{this.entry.score}}</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {defineComponent} from 'vue';
|
import {defineComponent, PropType} from 'vue';
|
||||||
|
import {Leaderboard, LeaderboardEntry} from "@/model/Leaderboard";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "LeaderboardEntry",
|
name: "LeaderboardEntry",
|
||||||
props: {
|
props: {
|
||||||
entry: {
|
entry: {
|
||||||
type: Object,
|
type: Object as PropType<LeaderboardEntry<string>>,
|
||||||
default: () => ({}),
|
default: () => ({}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -22,4 +23,9 @@ export default defineComponent({
|
||||||
* {
|
* {
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
}
|
}
|
||||||
|
.username {
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
65
frontend/src/components/Login.vue
Normal file
65
frontend/src/components/Login.vue
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
<template>
|
||||||
|
<h2>Enter a username</h2>
|
||||||
|
<div class="form-floating mb-3">
|
||||||
|
<input class="form-control" id="floatingInput" placeholder="example name" v-model="username">
|
||||||
|
<label for="floatingInput">Username</label>
|
||||||
|
<RRButton @click="setUser()" text="Confirm"></RRButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {User} from "@/model/User";
|
||||||
|
import RRButton from "@/components/RRButton.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Login",
|
||||||
|
components: {
|
||||||
|
RRButton
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
username: '',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['userChange'],
|
||||||
|
methods: {
|
||||||
|
async setUser() {
|
||||||
|
if (this.username === '') return;
|
||||||
|
|
||||||
|
let user;
|
||||||
|
user = await User.getByName(this.username);
|
||||||
|
if(user.errors){
|
||||||
|
user = await User.create(this.username);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(user.errors){
|
||||||
|
console.error("Something when wrong when logging in, please contact admin!")
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
this.$emit('userChange', user);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
input {
|
||||||
|
border: 3px solid black;
|
||||||
|
border-radius: 0;
|
||||||
|
background-color: beige;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus {
|
||||||
|
background: beige;
|
||||||
|
border-color: rgba(184,134,11, 0.8);
|
||||||
|
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 8px rgba(184,134,11, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export default {
|
export default {
|
||||||
name: "Button",
|
name: "RRButton",
|
||||||
props: {
|
props: {
|
||||||
text: {
|
text: {
|
||||||
type: String
|
type: String
|
||||||
|
|
@ -17,5 +17,6 @@ export default {
|
||||||
button {
|
button {
|
||||||
border: 3px solid black;
|
border: 3px solid black;
|
||||||
background-color: beige;
|
background-color: beige;
|
||||||
|
width: auto;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
3
frontend/src/model/Rest.ts
Normal file
3
frontend/src/model/Rest.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
export class Rest {
|
||||||
|
static readonly URL = 'http://localhost:3000';
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,34 @@
|
||||||
export interface User {
|
import {Rest} from "@/model/Rest";
|
||||||
id?: number,
|
|
||||||
name: string,
|
export class User {
|
||||||
|
id?: number;
|
||||||
|
name?: string;
|
||||||
|
|
||||||
|
|
||||||
|
constructor(id: number, name: string) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getByName(name: string): Promise<User> {
|
||||||
|
let res: Response = await fetch(Rest.URL + '/user/' + name, {method: 'GET'});
|
||||||
|
|
||||||
|
return await res.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
static async create(name: string): Promise<User> {
|
||||||
|
let body = {
|
||||||
|
name: name
|
||||||
|
};
|
||||||
|
let header = {
|
||||||
|
Accept: "application/json",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
};
|
||||||
|
|
||||||
|
let res: Response = await fetch( Rest.URL + '/user/register', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
headers: header,
|
||||||
|
});
|
||||||
|
return await res.json();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue