2025-04-07 09:20:49 +00:00
|
|
|
import { Component, HostListener, OnInit } from '@angular/core';
|
2025-04-07 12:02:55 +00:00
|
|
|
import { JumpService } from '../jump.service';
|
2025-04-08 12:41:52 +00:00
|
|
|
import { LoggerService } from '../logger.service';
|
|
|
|
import { FormsModule } from '@angular/forms';
|
|
|
|
import { HttpClient } from '@angular/common/http';
|
|
|
|
import { CommonModule } from '@angular/common';
|
2025-04-07 09:20:49 +00:00
|
|
|
|
|
|
|
@Component({
|
|
|
|
selector: 'app-mario',
|
|
|
|
standalone: true,
|
2025-04-08 12:41:52 +00:00
|
|
|
imports: [FormsModule, CommonModule],
|
2025-04-07 09:20:49 +00:00
|
|
|
templateUrl: './mario.component.html',
|
|
|
|
styleUrl: './mario.component.scss'
|
|
|
|
})
|
|
|
|
|
|
|
|
export class MarioComponent implements OnInit {
|
|
|
|
mario = {
|
|
|
|
x: 50,
|
|
|
|
y: 0,
|
|
|
|
width: 40,
|
|
|
|
height: 60,
|
|
|
|
dx: 0,
|
|
|
|
dy: 0,
|
|
|
|
speed: 4,
|
2025-04-08 07:51:15 +00:00
|
|
|
jumpStrength: 20,
|
2025-04-07 09:20:49 +00:00
|
|
|
gravity: 0.5,
|
|
|
|
onGround: false,
|
2025-04-08 07:51:15 +00:00
|
|
|
color: 'orange'
|
2025-04-07 09:20:49 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
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 }
|
|
|
|
];
|
2025-04-07 12:02:55 +00:00
|
|
|
|
2025-04-08 12:41:52 +00:00
|
|
|
constructor(
|
|
|
|
private jumpService: JumpService,
|
|
|
|
private logger: LoggerService,
|
|
|
|
private http: HttpClient
|
|
|
|
) {}
|
|
|
|
|
2025-04-07 09:20:49 +00:00
|
|
|
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();
|
2025-04-08 12:41:52 +00:00
|
|
|
this.logger.log('Game initialized');
|
2025-04-07 09:20:49 +00:00
|
|
|
}
|
2025-04-07 12:02:55 +00:00
|
|
|
|
|
|
|
totalJumps = 0;
|
2025-04-07 09:20:49 +00:00
|
|
|
|
|
|
|
@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;
|
2025-04-07 12:02:55 +00:00
|
|
|
this.jumpService.registerJump().subscribe({
|
|
|
|
next: (res) => this.totalJumps = res.totalJumps,
|
|
|
|
error: (err) => console.error('Jump API error:', err)
|
|
|
|
});
|
2025-04-07 09:20:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@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
|
2025-04-08 07:51:15 +00:00
|
|
|
this.ctx.fillStyle = '#044d00';
|
2025-04-07 09:20:49 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Mario
|
|
|
|
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;
|
|
|
|
}
|
2025-04-08 09:05:09 +00:00
|
|
|
|
|
|
|
restartGame() {
|
2025-04-08 12:41:52 +00:00
|
|
|
this.logger.log('Game restarted');
|
2025-04-08 09:05:09 +00:00
|
|
|
window.location.reload();
|
|
|
|
}
|
|
|
|
|
|
|
|
inviteFriend() {
|
2025-04-08 12:41:52 +00:00
|
|
|
this.logger.log('Fiend invited');
|
|
|
|
const inviteLink = window.location.href;
|
2025-04-08 09:05:09 +00:00
|
|
|
navigator.clipboard.writeText(inviteLink)
|
|
|
|
.then(() => alert('Invite link copied to clipboard!'))
|
|
|
|
.catch(() => alert('Failed to copy invite link.'));
|
|
|
|
}
|
2025-04-08 12:41:52 +00:00
|
|
|
|
|
|
|
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.');
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2025-04-07 09:20:49 +00:00
|
|
|
}
|