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 = (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: 21, 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.'); } }); } }