diff --git a/frontend/game.ts b/frontend/game.ts index f99cb76..4e251cf 100644 --- a/frontend/game.ts +++ b/frontend/game.ts @@ -1,52 +1,77 @@ -const PIPE_IMAGE_PATH: string = "resources/raspberry-low-res.png"; -const BACKGROUND_IMAGE_PATH: string = "resources/raspberry-low-res.png"; +const PIPE_IMAGE_PATH: string = "resources/dell-pc-min-min-small.png"; +const BACKGROUND_IMAGE_PATH: string = "resources/htl-steyr-front.jpg"; const RASPBERRY_IMAGE_PATH: string = "resources/raspberry-rocket.png"; -const OBSTACLE_WIDTH: number = 88; +const FLOOR_IMAGE_PATH: string = "resources/table-min-min.png"; +const FONT_PATH: string = "resources/PressStart2P-Regular.ttf"; const OBSTACLE_COUNT: number = 3; const BOOST_KEYS = ["k", " "]; +let floorHeight: number; +let obstacleWidth: number; let obstacleOffset: number; -let backgroundImage: any; +let backgroundImage: p5.Image; +let pipeImage: p5.Image; +let floorImage: p5.Image; +let font: p5.Font; let obstacles: Obstacle[] = []; let raspberry: Raspberry; -let paused: boolean; +let startTime: number; +let playTime: number; let score: number = 0; +let paused: boolean; let hasAlreadyScored: boolean = false; -let hasDied: boolean = false; +let hasDied: boolean = true; let ready: boolean = true; +/** + * p5 preload function + */ +function preload() { + font = loadFont(FONT_PATH); + backgroundImage = loadImage(BACKGROUND_IMAGE_PATH); + pipeImage = loadImage(PIPE_IMAGE_PATH); + floorImage = loadImage(FLOOR_IMAGE_PATH); +} + /** * p5 setup function. */ function setup() { - backgroundImage = loadImage(BACKGROUND_IMAGE_PATH); createCanvas(2000, 1000); + floorHeight = height / 5; setupObstacleConsts(); setupFont(); setupGame(); + let originalSetItem = localStorage.setItem; + localStorage.setItem = function () { + document.createEvent('Event').initEvent('itemInserted', true, true); + originalSetItem.apply(this, arguments); + } } /** * Sets up the constants needed for the game. */ -function setupObstacleConsts() { +function setupObstacleConsts(): void { obstacleOffset = width / OBSTACLE_COUNT; - Obstacle.distanceBetweenPipes = height / 2.5 + obstacleWidth = width / 22.727272727272727272; + Obstacle.distanceBetweenPipes = height / 2.5; Obstacle.startX = width; } /** * Set up the font. */ -function setupFont() { +function setupFont(): void { textSize(150); - textFont(loadFont("resources/PressStart2P-Regular.ttf")); + textAlign(CENTER); + textFont(font); } /** * Sets up everything needed for the game. */ -function setupGame() { +function setupGame(): void { paused = true; raspberry = new Raspberry(RASPBERRY_IMAGE_PATH); setupObstacles(); @@ -55,7 +80,7 @@ function setupGame() { /** * Clears the obstacles and reinitializes them. */ -function setupObstacles() { +function setupObstacles(): void { obstacles = []; instantiateObstacles(OBSTACLE_COUNT); obstacles.forEach((obstacle) => obstacle.randomizeHeight()); @@ -65,10 +90,11 @@ function setupObstacles() { * Instantiates a certain amount of obstacles. * @param number */ -function instantiateObstacles(number: number) { +function instantiateObstacles(number: number): void { for (let i = 0; i < number; i++) { obstacles.push( - new Obstacle(new Position(width + obstacleOffset * i, 0), OBSTACLE_WIDTH, height, PIPE_IMAGE_PATH)); + new Obstacle(new Position(width + obstacleOffset * i, 0), obstacleWidth, height, pipeImage) + ); } } @@ -84,24 +110,54 @@ function draw() { /** * Draws the game's elements. */ -function drawGame() { - background(backgroundImage); +function drawGame(): void { + drawScenery(); drawEntities(); displayScore(); } /** - * Draws the game's enities. + * Draws the: + * - background + * - floor */ -function drawEntities() { +function drawScenery(): void { + background(backgroundImage); + drawFloor(); +} + +/** + * Draws the floor with the corresponding image + */ +function drawFloor(): void { + push(); + noFill(); + image(floorImage, 0, height - floorHeight, width, floorHeight); + rect(0, height - floorHeight, width, floorHeight); + pop(); +} + +/** + * Draws the game's entities. + */ +function drawEntities(): void { raspberry.draw(); drawObstacles(); } +/** + * Draws the obstacles. + */ +function drawObstacles(): void { + obstacles.forEach((obstacle) => { + obstacle.draw(); + }); +} + /** * Operations for the game's functionality. */ -function gameLoop() { +function gameLoop(): void { if (!paused) { collisionCheck(obstacles[0]); checkRaspberryScore(); @@ -112,7 +168,7 @@ function gameLoop() { * Checks the collision between an obstacle and the raspberry. * @param o */ -function collisionCheck(o: Obstacle) { +function collisionCheck(o: Obstacle): void { if (o.collides(raspberry)) { die(); setupGame(); @@ -122,26 +178,37 @@ function collisionCheck(o: Obstacle) { /** * Timeouts key events. */ -function die() { +function die(): void { ready = false; hasDied = true; + playTime = Date.now() - startTime; + exportToLocalStorage(); setTimeout(() => ready = true, 1000); } +/** + * Exports playTime, Score and if the game is running into localStorage + */ +function exportToLocalStorage() { + localStorage.setItem("game-playTime", String(playTime)); + localStorage.setItem("game-score", String(score)); + localStorage.setItem("game-isRunning", String(!hasDied)); +} + /** * Displays the game score. */ -function displayScore() { +function displayScore(): void { push(); - fill(200, 100, 60); - text(score, width / 2, height / 10, width, height); + fill(195, 33, 34); + text(score, 0, height / 10, width, height); pop(); } /** * Updates all objects. */ -function update() { +function update(): void { if (!paused) { raspberry.update(); } @@ -154,22 +221,12 @@ function update() { }) } -/** - * Draws the obstacles. - */ -function drawObstacles() { - obstacles.forEach((obstacle) => { - obstacle.draw(); - }); -} - - /** * Check if obstacle positions should be reset and reset if so * @param obstacle obstacle to check */ -function checkObstacleReset(obstacle: Obstacle) { - if (obstacle.position.x < -OBSTACLE_WIDTH) { +function checkObstacleReset(obstacle: Obstacle): void { + if (obstacle.position.x < -obstacleWidth) { obstacle.resetPosition(); obstacles.shift(); obstacles.push(obstacle); @@ -180,7 +237,7 @@ function checkObstacleReset(obstacle: Obstacle) { /** * Check if the raspberry should score and set score */ -function checkRaspberryScore() { +function checkRaspberryScore(): void { if ((obstacles[0].position.x + obstacles[0].width / 2) < (raspberry.position.x + raspberry.width / 2) && !hasAlreadyScored) { score += 1; @@ -192,11 +249,13 @@ function checkRaspberryScore() { * Resets the score if game is started */ function resetScore(): void { - if (hasDied) { - hasDied = false; - score = 0; - hasAlreadyScored = false; - } + if (!hasDied || localStorage.getItem("frontend-ready") == "false") return; + + hasDied = false; + score = 0; + hasAlreadyScored = false; + startTime = Date.now(); + exportToLocalStorage(); } /** diff --git a/frontend/model/Obstacle.ts b/frontend/model/Obstacle.ts index 01d165d..7b96ece 100644 --- a/frontend/model/Obstacle.ts +++ b/frontend/model/Obstacle.ts @@ -4,7 +4,7 @@ class Obstacle extends Entity implements Collidable { private pipeTop: Pipe; private pipeBottom: Pipe; - private readonly padding: number = 150; + private readonly padding: number; private readonly speed: number = 3; private static _distanceBetweenPipes: number; @@ -23,11 +23,12 @@ class Obstacle extends Entity implements Collidable { * @param position starting position of the obstacle * @param obstacleWidth width of the obstacle * @param obstacleHeight height of the obstacle - * @param pipeImagePath path to the image to be used + * @param image the image to be used */ - constructor(position: Position, obstacleWidth: number, obstacleHeight: number, pipeImagePath: string) { + constructor(position: Position, obstacleWidth: number, obstacleHeight: number, image: p5.Image) { super(position, obstacleWidth, obstacleHeight, 0); - this.createPipes(position, obstacleHeight, obstacleWidth, pipeImagePath); + this.padding = height / 6.6666666666666666; + this.createPipes(position, obstacleHeight, obstacleWidth, image); } /** @@ -35,12 +36,12 @@ class Obstacle extends Entity implements Collidable { * @param position * @param obstacleHeight * @param obstacleWidth - * @param pipeImagePath + * @param pipeImage * @private */ - private createPipes(position: Position, obstacleHeight: number, obstacleWidth: number, pipeImagePath: string) { - this.pipeTop = new Pipe(position.x, obstacleWidth, obstacleHeight, pipeImagePath); - this.pipeBottom = new Pipe(position.x, obstacleWidth, obstacleHeight, pipeImagePath); + private createPipes(position: Position, obstacleHeight: number, obstacleWidth: number, pipeImage: p5.Image) { + this.pipeTop = new Pipe(position.x, obstacleWidth, obstacleHeight, pipeImage); + this.pipeBottom = new Pipe(position.x, obstacleWidth, obstacleHeight, pipeImage); } /** diff --git a/frontend/model/Pipe.ts b/frontend/model/Pipe.ts index 016f3a8..6e0185d 100644 --- a/frontend/model/Pipe.ts +++ b/frontend/model/Pipe.ts @@ -6,33 +6,16 @@ class Pipe extends Entity implements Collidable { * Pipe's image. * @private */ - private _image: p5.Image; - - //region Getter & Setter - /** - * Gets the image. - */ - get image(): p5.Image { - return this._image; - } - - /** - * Sets the image. - * @param path Path to image - */ - set image(path: any) { - this._image = loadImage(path); - } - //endregion + private readonly image: p5.Image; /** * Constructs the pipe. * @param positionX starting x-Position * @param width pipe width * @param height pipe height - * @param image path to image. + * @param image image object */ - constructor(positionX: number, width: number, height: number, image: string) { + constructor(positionX: number, width: number, height: number, image: p5.Image) { super(new Position(positionX, 0), width, height, 0); this.image = image; } @@ -40,7 +23,8 @@ class Pipe extends Entity implements Collidable { /** * YAGNI. */ - public update(): void {} + public update(): void { + } /** * Draws the pipe. @@ -48,11 +32,50 @@ class Pipe extends Entity implements Collidable { public draw(): void { push(); noFill(); - image(this.image, this.position.x, this.position.y, this.width, this.height); + + let imageAspectRatio = this.image.height / this.image.width; + let computedImageHeight = imageAspectRatio * this.width; + this.drawImage(computedImageHeight, imageAspectRatio); + rect(this.position.x, this.position.y, this.width, this.height); pop(); } + /** + * Draws the image of the pipe into it (tiling and not stretching) + * @param computedImageHeight image height on screen + * @param imageAspectRatio aspect ratio of the image + * @private + */ + private drawImage(computedImageHeight: number, imageAspectRatio: number): void { + if (this.height > computedImageHeight) { + let maxImageYPos = Math.ceil(this.height / computedImageHeight) * computedImageHeight; + for (let 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); + } + } + + /** + * Crops the last image in the pipe so that is doesn't get stretched or compressed + * @param imageYPosition y-Position of the image + * @param computedImageHeight image height on screen + * @param imageAspectRatio aspect ratio of the image + * @private + */ + private cropLastImage(imageYPosition: number, computedImageHeight: number, imageAspectRatio: number): void { + let amountOfImages = Math.floor(imageYPosition / computedImageHeight); + let heightToEdge = this.height - (amountOfImages * computedImageHeight); + let 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); + } + /** * Moves the pipe to the lift with the given speed * @param speed how fast the pipe moves diff --git a/frontend/model/Raspberry.ts b/frontend/model/Raspberry.ts index c42840c..34508df 100644 --- a/frontend/model/Raspberry.ts +++ b/frontend/model/Raspberry.ts @@ -6,13 +6,13 @@ class Raspberry extends Entity { * Amount of lift applied when boosting. * @private */ - private readonly lift: number = -20; + private readonly lift: number = -15; /** * Gravity applied. * @private */ - private readonly gravity: number = 1.314159265358979323846264338; + private readonly gravity: number = 0.45; /** * Current speed. @@ -36,19 +36,25 @@ class Raspberry extends Entity { * Maximum velocity, so the raspberry doesn't get to infinite speed when boosting. * @private */ - private static readonly maxVelocity: number = 100; + private static readonly maxVelocity: number = 75; /** * Width. * @private */ - private static readonly WIDTH: number = 180; + private static width: number; /** * Height. * @private */ - private static readonly HEIGHT: number = 70; + private static height: number; + + /** + * Offset off of the floor so that the raspberry looks like it's falling on the floor + * @private + */ + private static bottomFloorOffset: number; /** * Color. @@ -96,7 +102,11 @@ class Raspberry extends Entity { */ constructor(image: string) { Raspberry.position = new Position(width / 6, height / 2); - super(Raspberry.position, Raspberry.WIDTH, Raspberry.HEIGHT, Raspberry.FILL); + Raspberry.height = height / 14.2857142857142857; + Raspberry.width = width / 11.1111111111111111; + + super(Raspberry.position, Raspberry.width, Raspberry.height, Raspberry.FILL); + Raspberry.bottomFloorOffset = (height / 5) - (height / 15 / 2); this.image = image; } @@ -117,7 +127,7 @@ class Raspberry extends Entity { } /** - * Limits the raspberry's movement to the shown canvas. + * Limits the Raspberry's movement to the shown canvas. * @private */ private forceBoundaries(): void { @@ -141,8 +151,8 @@ class Raspberry extends Entity { * @private */ private boundaryBottom(): void { - if (this.position.y + this.height > height) { - this.position.y = height - this.height; + if (this.position.y + this.height + Raspberry.bottomFloorOffset > height) { + this.position.y = height - this.height - Raspberry.bottomFloorOffset; this.velocity = 0; } } diff --git a/frontend/resources/dell-pc-min-min-small.png b/frontend/resources/dell-pc-min-min-small.png new file mode 100644 index 0000000..f082471 Binary files /dev/null and b/frontend/resources/dell-pc-min-min-small.png differ diff --git a/frontend/resources/htl-steyr-front.jpg b/frontend/resources/htl-steyr-front.jpg new file mode 100644 index 0000000..6323bfc Binary files /dev/null and b/frontend/resources/htl-steyr-front.jpg differ diff --git a/frontend/resources/rickroll.png b/frontend/resources/rickroll.png new file mode 100644 index 0000000..8aaa2f7 Binary files /dev/null and b/frontend/resources/rickroll.png differ diff --git a/frontend/resources/table-min-min.png b/frontend/resources/table-min-min.png new file mode 100644 index 0000000..c2e2dfe Binary files /dev/null and b/frontend/resources/table-min-min.png differ