Skip to content

Commit

Permalink
Portalicious: Easter egg Snake (#6399)
Browse files Browse the repository at this point in the history
* Draw snake

* Basic movement

* Eat food

* Intersection detection

* Score dialog

* translations

* cleanup

* chore snake facts (#6401)

* chore snake facts

* Snake translations

* Update interfaces/Portalicious/src/app/pages/snake/game/snake.component.ts

Co-authored-by: Peter Smallenbroek <PeterSmallenbroek@users.noreply.github.com>

* Update interfaces/Portalicious/src/app/pages/snake/game/snake.component.ts

Co-authored-by: Peter Smallenbroek <PeterSmallenbroek@users.noreply.github.com>

* Update interfaces/Portalicious/src/app/pages/snake/game/snake.component.ts

Co-authored-by: Peter Smallenbroek <PeterSmallenbroek@users.noreply.github.com>

* Update interfaces/Portalicious/src/app/pages/snake/game/snake.component.html

Co-authored-by: Peter Smallenbroek <PeterSmallenbroek@users.noreply.github.com>

---------

Co-authored-by: Ruben <vandervalk@geoit.nl>
Co-authored-by: Peter Smallenbroek <PeterSmallenbroek@users.noreply.github.com>

---------

Co-authored-by: RubenGeo <34537157+RubenGeo@users.noreply.github.com>
Co-authored-by: Ruben <vandervalk@geoit.nl>
  • Loading branch information
3 people authored Jan 20, 2025
1 parent 98bbcb9 commit 2277311
Show file tree
Hide file tree
Showing 7 changed files with 358 additions and 1 deletion.
6 changes: 6 additions & 0 deletions interfaces/Portalicious/src/app/app.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export enum AppRoutes {
projects = 'projects',
projectTeam = 'team',
registrationLookup = 'registration-lookup',
snake = 'snake',
userRoles = 'user-roles',
users = 'users',
}
Expand All @@ -40,6 +41,11 @@ export const routes: Routes = [
(x) => x.AuthCallbackPageComponent,
),
},
{
path: AppRoutes.snake,
loadComponent: () =>
import('~/pages/snake/snake.page').then((x) => x.SnakePageComponent),
},
{
path: AppRoutes.changePassword,
title: $localize`:@@page-title-change-password:Change password`,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<p-dialog
header="Game over!"
i18n-header
[modal]="true"
[dismissableMask]="true"
[closeOnEscape]="false"
[(visible)]="isGameOver"
[closable]="false"
>
<b
i18n
class="mb-4 block"
>Your score is {{ score() }}.
</b>
<div class="flex justify-end gap-2">
<p-button
label="Okay"
i18n-label
(click)="initialize()"
/>
</div>
</p-dialog>

<p-dialog
header="Let's start!"
i18n-header
[modal]="true"
[dismissableMask]="true"
[closeOnEscape]="false"
[visible]="!isGameStarted()"
[closable]="false"
>
<b
i18n
class="mb-4 block"
>Use the arrow keys or W A S D to play the game</b
>
<div class="flex justify-end gap-2">
<p-button
label="Start!"
i18n-label
(click)="startButtonClick()"
/>
</div>
</p-dialog>

<div
#board
id="board"
class="border-1 grid aspect-square h-full w-full border-black bg-green-500 bg-opacity-35"
style="
grid-template-rows: repeat(21, 1fr);
grid-template-columns: repeat(21, 1fr);
"
></div>
<!-- Random Fact Section -->
<div class="mt-4 rounded-lg border bg-white p-4 shadow-md">
<h3
i18n
class="mb-2 text-lg font-semibold"
>
Did you know?
</h3>
<p class="text-gray-700">{{ random121Fact() }}</p>
</div>
220 changes: 220 additions & 0 deletions interfaces/Portalicious/src/app/pages/snake/game/snake.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
import {
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ElementRef,
HostListener,
signal,
ViewChild,
} from '@angular/core';

import { ButtonModule } from 'primeng/button';
import { DialogModule } from 'primeng/dialog';

interface Vector {
x: number;
y: number;
}

@Component({
selector: 'app-snake',
imports: [ButtonModule, DialogModule],
templateUrl: './snake.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SnakeComponent implements AfterViewInit {
@HostListener('document:keydown', ['$event'])
handleKeyboardEvent(event: KeyboardEvent) {
switch (event.key) {
case 'w':
case 'ArrowUp':
if (this.lastInputDirection.y !== 0) break;
this.inputDirection = { x: 0, y: -1 };
break;
case 's':
case 'ArrowDown':
if (this.lastInputDirection.y !== 0) break;
this.inputDirection = { x: 0, y: 1 };
break;
case 'a':
case 'ArrowLeft':
if (this.lastInputDirection.x !== 0) break;
this.inputDirection = { x: -1, y: 0 };
break;
case 'd':
case 'ArrowRight':
if (this.lastInputDirection.x !== 0) break;
this.inputDirection = { x: 1, y: 0 };
break;
}
}

@ViewChild('board') board: ElementRef<HTMLDivElement>;
constructor(private changeDetectorRef: ChangeDetectorRef) {}

public isGameStarted = signal(false);
public isGameOver = signal(false);
public score = signal(0);
public random121Fact = signal('');

private lastRenderTime = 0;
private inputDirection: Vector;
private lastInputDirection: Vector;
private snakeBody: Vector[];
private foodPosition: Vector;
private SNAKE_SPEED = 6;
private EXPANSION_RATE = 1;

ngAfterViewInit(): void {
this.initialize();
}

startButtonClick() {
this.isGameStarted.set(true);
this.inputDirection = { x: 0, y: -1 };
this.lastInputDirection = { x: 0, y: -1 };
window.requestAnimationFrame(this.gameLoop.bind(this));
}

private gameLoop(currentTime: number) {
if (!this.isGameStarted() || this.isGameOver()) return;
window.requestAnimationFrame(this.gameLoop.bind(this));
const secondsSinceLastRender = (currentTime - this.lastRenderTime) / 1000;
if (secondsSinceLastRender < 1 / this.SNAKE_SPEED) return;

this.lastRenderTime = currentTime;

this.updateSnake();
this.updateFood();
this.drawSnake();
this.drawFood();
this.checkGameOver();
}

initialize() {
this.isGameStarted.set(false);
this.isGameOver.set(false);
this.inputDirection = { x: 0, y: 0 };
this.lastInputDirection = { x: 0, y: 0 };
this.snakeBody = [
{ x: 11, y: 11 },
{ x: 11, y: 12 },
{ x: 11, y: 13 },
];
this.foodPosition = this.getRandomPositionOnGrid();

this.drawSnake();
this.drawFood();
this.changeDetectorRef.detectChanges();
this.showRandom121Fact();
}

private showRandom121Fact() {
const random121Facts = [
'The number 121 is the sum of the first 11 prime numberss',
'121 is the 11th number in the Fibonacci sequence',
'121 is the 15th number in the Pell sequence',
'121 is a centered octagonal number',
'121 is easy',
'121 is safe',
'121 is fast',
'Did you know that you can share your registration table filter by copying the URL?',
'Have you ever typed "Henry Dunant" in the search bar?',
'By recording all data entries and changes, 121 enhances accountability and security, making auditing easier through a privacy-by-design system.',
];
this.random121Fact.set(
random121Facts[Math.floor(Math.random() * random121Facts.length)],
);
}

private checkGameOver() {
if (
this.isSnakeOutsideBoard() ||
this.isSnakeIntersecting({
position: this.snakeBody[0],
ignoreHead: true,
})
) {
this.score.set(this.snakeBody.length - 3);
this.isGameOver.set(true);
}
}

private updateFood() {
if (this.isSnakeIntersecting({ position: this.foodPosition })) {
this.expandSnake();
this.foodPosition = this.getRandomPositionOnGrid();
this.showRandom121Fact();
}
}

private drawFood() {
const foodElement = document.createElement('div');
foodElement.style.gridRowStart = this.foodPosition.y.toString();
foodElement.style.gridColumnStart = this.foodPosition.x.toString();
foodElement.classList.add('bg-red-500', 'border-red-700', 'border-2');
this.board.nativeElement.appendChild(foodElement);
}

private updateSnake() {
const inputDirection = this.getInputDirection();
for (let i = this.snakeBody.length - 2; i >= 0; i--) {
this.snakeBody[i + 1] = { ...this.snakeBody[i] };
}

this.snakeBody[0].x += inputDirection.x;
this.snakeBody[0].y += inputDirection.y;
}

private drawSnake() {
this.board.nativeElement.innerHTML = '';
this.snakeBody.forEach((segment) => {
const snakeElement = document.createElement('div');
snakeElement.style.gridRowStart = segment.y.toString();
snakeElement.style.gridColumnStart = segment.x.toString();
snakeElement.classList.add('bg-grey-500', 'border-black', 'border');
this.board.nativeElement.appendChild(snakeElement);
});
}

private isSnakeIntersecting({
position,
ignoreHead = false,
}: {
position: { x: number; y: number };
ignoreHead?: boolean;
}): boolean {
return this.snakeBody.some((segment, index) => {
if (ignoreHead && index === 0) return false;
return segment.x === position.x && segment.y === position.y;
});
}

private expandSnake() {
for (let i = 0; i < this.EXPANSION_RATE; i++) {
this.snakeBody.push({ ...this.snakeBody[this.snakeBody.length - 1] });
}
}

private isSnakeOutsideBoard() {
return (
this.snakeBody[0].x < 1 ||
this.snakeBody[0].x > 21 ||
this.snakeBody[0].y < 1 ||
this.snakeBody[0].y > 21
);
}

private getInputDirection() {
this.lastInputDirection = this.inputDirection;
return this.inputDirection;
}

private getRandomPositionOnGrid() {
return {
x: Math.floor(Math.random() * 21) + 1,
y: Math.floor(Math.random() * 21) + 1,
};
}
}
5 changes: 5 additions & 0 deletions interfaces/Portalicious/src/app/pages/snake/snake.page.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<app-page-layout>
<div class="mx-auto max-w-[47rem]">
<app-snake />
</div>
</app-page-layout>
12 changes: 12 additions & 0 deletions interfaces/Portalicious/src/app/pages/snake/snake.page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';

import { PageLayoutComponent } from '~/components/page-layout/page-layout.component';
import { SnakeComponent } from '~/pages/snake/game/snake.component';

@Component({
selector: 'app-snake-page',
imports: [PageLayoutComponent, SnakeComponent],
templateUrl: './snake.page.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SnakePageComponent {}
28 changes: 28 additions & 0 deletions interfaces/Portalicious/src/locale/messages.nl.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -1835,6 +1835,34 @@
<source>Registration details</source>
<target>Registratie details</target>
</trans-unit>
<trans-unit id="7924647147478852112" datatype="html">
<source>Game over!</source>
<target state="new">Game over!</target>
</trans-unit>
<trans-unit id="2077783620698115246" datatype="html">
<source>Your score is <x equiv-text="{{ score() }}" id="INTERPOLATION"/>.</source>
<target state="new">Your score is <x equiv-text="{{ score() }}" id="INTERPOLATION"/>.</target>
</trans-unit>
<trans-unit id="1579692722565712588" datatype="html">
<source>Okay</source>
<target state="new">Okay</target>
</trans-unit>
<trans-unit id="2981043932069425956" datatype="html">
<source>Let&apos;s start!</source>
<target state="new">Let&apos;s start!</target>
</trans-unit>
<trans-unit id="5595826100350402634" datatype="html">
<source>Use the arrow keys or W A S D to play the game</source>
<target state="new">Use the arrow keys or W A S D to play the game</target>
</trans-unit>
<trans-unit id="2438590582556677910" datatype="html">
<source>Start!</source>
<target state="new">Start!</target>
</trans-unit>
<trans-unit id="8220517666190714329" datatype="html">
<source>Did you know?</source>
<target state="new">Did you know?</target>
</trans-unit>
</body>
</file>
</xliff>
23 changes: 22 additions & 1 deletion interfaces/Portalicious/src/locale/messages.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -1378,6 +1378,27 @@
<trans-unit id="page-title-project-registration-details" datatype="html">
<source>Registration details</source>
</trans-unit>
<trans-unit id="1579692722565712588" datatype="html">
<source>Okay</source>
</trans-unit>
<trans-unit id="2077783620698115246" datatype="html">
<source>Your score is <x equiv-text="{{ score() }}" id="INTERPOLATION"/>.</source>
</trans-unit>
<trans-unit id="2438590582556677910" datatype="html">
<source>Start!</source>
</trans-unit>
<trans-unit id="2981043932069425956" datatype="html">
<source>Let&apos;s start!</source>
</trans-unit>
<trans-unit id="5595826100350402634" datatype="html">
<source>Use the arrow keys or W A S D to play the game</source>
</trans-unit>
<trans-unit id="7924647147478852112" datatype="html">
<source>Game over!</source>
</trans-unit>
<trans-unit id="8220517666190714329" datatype="html">
<source>Did you know?</source>
</trans-unit>
</body>
</file>
</xliff>
</xliff>

0 comments on commit 2277311

Please sign in to comment.