michals-silly-game-frontend/src/app/mario/mario.component.ts
2025-04-08 15:09:58 +02:00

219 lines
No EOL
7.6 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';
@Component({
selector: 'app-mario',
standalone: true,
imports: [FormsModule, CommonModule],
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'];
characterColor = 'red';
skyColor = '#87CEEB';
groundColor = this.pick(this.groundColors);
@HostBinding('style.--sky-color') skyBackground = this.pick(this.skyColors);
mario = {
x: 50,
y: 0,
width: 40,
height: 60,
dx: 0,
dy: 0,
speed: 4,
jumpStrength: 20,
gravity: 0.5,
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 }
];
constructor(
private jumpService: JumpService,
private logger: LoggerService,
private http: HttpClient
) {}
ngOnInit(): void {
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');
this.logger.log(`New colors: character=${this.characterColor}, sky=${this.skyColor}, ground=${this.groundColor}`);
}
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.');
}
});
}
}