michals-silly-game-frontend/src/app/mario/mario.component.ts
miwr 2fa77ceef4
Some checks failed
ci / build (push) Failing after 42s
landing page done
2025-04-10 15:59:47 +02:00

259 lines
No EOL
9.1 KiB
TypeScript

import { Component, HostBinding, HostListener, OnInit } from '@angular/core';
import { JumpService } from '../jump.service';
import { LoggerService } from '../logger.service';
import { FormsModule } from '@angular/forms';
import { HttpClient } from '@angular/common/http';
import { CommonModule } from '@angular/common';
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
import { Subscription } from 'rxjs';
@Component({
selector: 'app-mario',
standalone: true,
imports: [FormsModule, CommonModule, RouterModule],
templateUrl: './mario.component.html',
styleUrl: './mario.component.scss'
})
export class MarioComponent implements OnInit {
pick = <T>(arr: T[]) => arr[Math.floor(Math.random() * arr.length)];
characterColors = ['red', 'green', 'blue', 'orange', 'purple', '#7fb1b8', '#b00b66', 'pink', 'brown'];
skyColors = ['#87CEEB', '#a0d8ef', '#c0e0ff', '#b0f0ff', '#00334f', '#ff6929'];
groundColors = ['#654321', '#7c4f2c', '#5b3a1a', '#4a2c15', '#000000', '#32a852', '#386e46', '#657569'];
groundColor = this.pick(this.groundColors);
@HostBinding('style.--sky-color') skyBackground = this.pick(this.skyColors);
jumpingStrength: number = 22 ;
gravity: number = 0.8;
speed: number = 6;
private routeSubscription: Subscription | undefined;
mario = {
x: 50,
y: 0,
width: 40,
height: 60,
dx: 0,
dy: 0,
speed: this.speed,
jumpStrength: this.jumpingStrength,
gravity: this.gravity,
onGround: false,
color: this.pick(this.characterColors)
};
private canvas!: HTMLCanvasElement;
private ctx!: CanvasRenderingContext2D;
private canvasWidth = window.innerWidth * 5;
private canvasHeight = window.innerHeight;
private platforms = [
{ x: 300, y: 500, width: 100, height: 10 },
{ x: 600, y: 400, width: 150, height: 10 },
{ x: 900, y: 350, width: 200, height: 10 },
{ x: 1300, y: 450, width: 120, height: 10 },
{ x: 1600, y: 380, width: 180, height: 10 },
{ x: 1900, y: 320, width: 120, height: 10 },
{ x: 2200, y: 300, width: 150, height: 10 },
{ x: 2500, y: 350, width: 180, height: 10 },
{ x: 2800, y: 400, width: 120, height: 10 },
{ x: 3100, y: 450, width: 150, height: 10 },
{ x: 3400, y: 390, width: 130, height: 10 },
{ x: 3700, y: 350, width: 200, height: 10 },
{ x: 4000, y: 300, width: 140, height: 10 },
{ x: 4300, y: 330, width: 180, height: 10 },
{ x: 4600, y: 380, width: 120, height: 10 },
{ x: 4900, y: 350, width: 160, height: 10 },
{ x: 5200, y: 420, width: 180, height: 10 },
{ x: 5500, y: 460, width: 120, height: 10 },
{ x: 5800, y: 500, width: 160, height: 10 },
{ x: 6100, y: 400, width: 140, height: 10 },
{ x: 6400, y: 450, width: 120, height: 10 },
{ x: 6750, y: 460, width: 180, height: 10 },
{ x: 7150, y: 480, width: 40, height: 10 },
{ x: 7300, y: 420, width: 40, height: 10 },
{ x: 7600, y: 480, width: 30, height: 10 },
{ x: 7900, y: 200, width: 30, height: 10 },
{ x: 8250, y: 210, width: 40, height: 10 },
{ x: 8500, y: 300, width: 30, height: 10 },
{ x: 8800, y: 500, width: 20, height: 10 },
{ x: 9150, y: 520, width: 10, height: 10 },
{ x: 9500, y: 300, width: 10, height: 632 }
];
constructor(
private jumpService: JumpService,
private logger: LoggerService,
private http: HttpClient,
private router: Router,
private route: ActivatedRoute
) {}
ngOnInit(): void {
this.route.queryParams.subscribe(params => {
const jump = Number(params['jump']);
if (!isNaN(jump) && jump >= 0 && jump <= 100) {
console.log(jump);
this.mario.jumpStrength = jump;
}
const gravity = Number(params['gravity']);
if (!isNaN(gravity) && gravity > 0 && gravity <= 1) {
console.log(gravity);
this.mario.gravity = gravity;
}
const speed = Number(params['speed']);
if (!isNaN(speed) && speed > 0 && speed <= 100) {
console.log(speed);
this.mario.speed = speed;
}
});
this.canvas = document.getElementById('gameCanvas') as HTMLCanvasElement;
this.ctx = this.canvas.getContext('2d')!;
this.canvas.width = this.canvasWidth;
this.canvas.height = this.canvasHeight;
this.mario.y = this.canvasHeight - 100;
this.loop();
this.logger.log('Game initialized');
}
totalJumps = 0;
@HostListener('window:keydown', ['$event'])
handleKeyDown(event: KeyboardEvent) {
if (event.key === 'ArrowRight') this.mario.dx = this.mario.speed;
if (event.key === 'ArrowLeft') this.mario.dx = -this.mario.speed;
if (event.key === ' ' && this.mario.onGround) {
this.mario.dy = -this.mario.jumpStrength;
this.mario.onGround = false;
this.jumpService.registerJump().subscribe({
next: (res) => this.totalJumps = res.totalJumps,
error: (err) => console.error('Jump API error:', err)
});
}
}
@HostListener('window:keyup', ['$event'])
handleKeyUp(event: KeyboardEvent) {
if (['ArrowRight', 'ArrowLeft'].includes(event.key)) this.mario.dx = 0;
}
private loop = () => {
this.update();
this.draw();
this.scrollToMario();
requestAnimationFrame(this.loop);
};
private update() {
this.mario.x += this.mario.dx;
this.mario.y += this.mario.dy;
// Gravity
this.mario.dy += this.mario.gravity;
this.mario.onGround = false;
const groundY = this.canvasHeight - 60;
if (this.mario.y + this.mario.height >= groundY) {
this.mario.y = groundY - this.mario.height;
this.mario.dy = 0;
this.mario.onGround = true;
}
for (const platform of this.platforms) {
if (
this.mario.x + this.mario.width > platform.x &&
this.mario.x < platform.x + platform.width &&
this.mario.y + this.mario.height <= platform.y &&
this.mario.y + this.mario.height + this.mario.dy >= platform.y
) {
this.mario.y = platform.y - this.mario.height;
this.mario.dy = 0;
this.mario.onGround = true;
}
}
// Boundaries
if (this.mario.x < 0) this.mario.x = 0;
if (this.mario.x + this.mario.width > this.canvasWidth)
this.mario.x = this.canvasWidth - this.mario.width;
}
private draw() {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
// Ground
this.ctx.fillStyle = this.groundColor;
this.ctx.fillRect(0, this.canvas.height - 60, this.canvas.width, 60);
// Platforms
this.ctx.fillStyle = '#888';
for (const p of this.platforms) {
this.ctx.fillRect(p.x, p.y, p.width, p.height);
}
// Main Character
this.ctx.fillStyle = this.mario.color;
this.ctx.fillRect(this.mario.x, this.mario.y, this.mario.width, this.mario.height);
this.ctx.fillStyle = 'white';
this.ctx.fillRect(this.mario.x + 8, this.mario.y + 15, 8, 8);
this.ctx.fillRect(this.mario.x + 24, this.mario.y + 15, 8, 8);
this.ctx.fillStyle = 'black';
this.ctx.fillRect(this.mario.x + 10, this.mario.y + 17, 4, 4);
this.ctx.fillRect(this.mario.x + 26, this.mario.y + 17, 4, 4);
this.ctx.fillStyle = 'yellow';
this.ctx.fillRect(this.canvasWidth - 60, this.canvas.height - 160, 20, 100);
this.ctx.fillStyle = 'black';
this.ctx.fillText('🏁', this.canvasWidth - 60, this.canvas.height - 170);
}
private scrollToMario() {
const scrollContainer = this.canvas.parentElement!;
const centerOffset = scrollContainer.clientWidth / 2;
scrollContainer.scrollLeft = this.mario.x - centerOffset;
}
restartGame() {
this.logger.log('Game restarted');
window.location.reload();
}
inviteFriend() {
this.logger.log('Fiend invited');
const inviteLink = window.location.href;
navigator.clipboard.writeText(inviteLink)
.then(() => alert('Invite link copied to clipboard!'))
.catch(() => alert('Failed to copy invite link.'));
}
inviteEmail = '';
showInvitePopup = false;
sendInvite() {
if (!this.inviteEmail) return;
this.logger.log(`Sending invite to ${this.inviteEmail}`);
this.http.post('/api/invite', { email: this.inviteEmail }).subscribe({
next: () => {
alert('Invite sent!');
this.inviteEmail = '';
this.showInvitePopup = false;
},
error: () => {
alert('Failed to send invite.');
}
});
}
navigateToSettings(): void {
this.router.navigate(['/settings']);
}
}