Téléverser les fichiers vers "Sources"

This commit is contained in:
Bateast 2025-03-09 15:11:51 +00:00
commit 236b5b8d67
3 changed files with 1172 additions and 0 deletions

902
Sources/game.js Normal file
View File

@ -0,0 +1,902 @@
class Game {
constructor() {
this.menuScreen = document.getElementById('menuScreen');
this.gameScreen = document.getElementById('gameScreen');
this.canvas = document.getElementById('gameCanvas');
// Stats initialization
this.stats = {
beersServed: 0,
totalMoney: 0,
perfectPours: 0,
maxStreak: 0
};
if (!this.canvas) {
console.error('Game canvas not found!');
return;
}
this.ctx = this.canvas.getContext('2d');
// Set canvas size
this.canvas.width = 600;
this.canvas.height = 600;
// Colors
this.backgroundColor = '#1a1a1a';
this.foregroundColor = '#e0e0e0';
// Game state
this.money = 0;
this.timer = 60;
this.currentLevel = 0;
this.targetLevel = 0;
this.isPouring = false;
this.gameActive = false;
this.lastTime = 0;
this.feedbackMessage = '';
this.feedbackTimer = 0;
this.perfectStreak = 0;
// Keg system
this.kegCapacity = 20;
this.currentKeg = this.kegCapacity;
this.kegPrice = 10;
this.kegsInStock = 0; // Add this line
// Level system
this.level = 1;
this.levelTarget = 10;
this.currentLevelMoney = 0;
this.upgrades = {
pourSpeed: { level: 1, cost: 5, name: "Pour Speed" },
accuracy: { level: 1, cost: 8, name: "Accuracy" },
timer: { level: 1, cost: 10, name: "Extra Time" }
};
this.pourSpeed = 0.005;
// Background canvas setup
this.bgCanvas = document.getElementById('backgroundCanvas');
if (this.bgCanvas) {
this.bgCtx = this.bgCanvas.getContext('2d');
this.bgCanvas.width = window.innerWidth;
this.bgCanvas.height = window.innerHeight;
// Bubbles setup
this.bubbles = Array.from({length: 50}, () => this.createBubble());
this.backgroundAnimation();
}
// Initialize instructions
const instructionsCanvas = document.getElementById('instructionsCanvas');
if (instructionsCanvas) {
instructionsCanvas.width = 100;
instructionsCanvas.height = 80;
const ictx = instructionsCanvas.getContext('2d');
this.drawMouseIcon(ictx, 50, 40);
}
this.setupEventListeners();
}
startGame() {
if (!this.menuScreen || !this.gameScreen) return;
if (!this.canvas || !this.ctx) return;
this.menuScreen.classList.add('hidden');
this.gameScreen.classList.remove('hidden');
this.canvas.style.display = 'block';
this.gameActive = true;
this.timer = 60 + (this.upgrades.timer.level - 1) * 10;
this.money = 0;
this.currentLevel = 0;
this.currentLevelMoney = 0;
this.level = 1;
this.levelTarget = 10;
this.lastTime = Date.now();
this.resetLevel();
this.draw();
this.gameLoop();
}
draw() {
this.ctx.fillStyle = this.backgroundColor;
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
// Dessiner le contour arrondi
this.ctx.strokeStyle = this.foregroundColor;
this.ctx.lineWidth = 4;
const radius = 15;
this.ctx.beginPath();
this.ctx.moveTo(radius, 0);
this.ctx.lineTo(this.canvas.width - radius, 0);
this.ctx.quadraticCurveTo(this.canvas.width, 0, this.canvas.width, radius);
this.ctx.lineTo(this.canvas.width, this.canvas.height - radius);
this.ctx.quadraticCurveTo(this.canvas.width, this.canvas.height, this.canvas.width - radius, this.canvas.height);
this.ctx.lineTo(radius, this.canvas.height);
this.ctx.quadraticCurveTo(0, this.canvas.height, 0, this.canvas.height - radius);
this.ctx.lineTo(0, radius);
this.ctx.quadraticCurveTo(0, 0, radius, 0);
this.ctx.stroke();
// Draw game elements
this.drawTap();
this.drawGlass();
this.drawMoneyBag();
this.drawTimer();
this.drawPrice();
this.drawKegMeter(); // Add this line
this.drawKegStock(); // Ajout de l'indicateur de fûts en stock
// Draw level info
this.ctx.font = '12px "Press Start 2P"';
this.ctx.fillStyle = this.foregroundColor;
this.ctx.fillText(`Level ${this.level}`, 10, 30);
this.ctx.fillText(`Target: $${this.levelTarget}`, 10, 50);
this.ctx.fillText(`Progress: $${this.currentLevelMoney}`, 10, 70);
if (this.isPouring) {
this.drawPourStream();
}
if (this.feedbackTimer > 0) {
this.drawFeedback();
}
}
drawFeedback() {
const animationProgress = 1 - (this.feedbackTimer / 1.5);
this.ctx.font = '16px "Press Start 2P"';
const centerX = this.canvas.width / 2;
const textWidth = this.ctx.measureText(this.feedbackMessage).width;
const feedbackY = 450;
let shakeOffset = 0;
let verticalBounce = 0;
if (this.feedbackMessage.includes('PERFECT!')) {
const shakeSpeed = Math.max(10, 30 - this.perfectStreak * 2);
const shakeAmount = 2 + (this.perfectStreak * 0.8);
verticalBounce = Math.sin(Date.now() / 200) * (this.perfectStreak * 2);
shakeOffset = Math.sin(Date.now() / shakeSpeed) * shakeAmount;
// Ajout d'une rotation avec le streak
this.ctx.save();
this.ctx.translate(centerX, feedbackY);
this.ctx.rotate(Math.sin(Date.now() / 200) * (this.perfectStreak * 0.02));
this.ctx.translate(-centerX, -feedbackY);
}
if (this.feedbackMessage.includes('PERFECT!')) {
const goldIntensity = Math.min(255, 218 + (this.perfectStreak * 5));
this.ctx.fillStyle = `rgb(${goldIntensity}, ${Math.floor(goldIntensity * 0.84)}, 0)`;
} else {
this.ctx.fillStyle = `rgba(224, 224, 224, ${1 - animationProgress * 0.7})`;
}
this.ctx.fillText(this.feedbackMessage, centerX - textWidth/2 + shakeOffset, feedbackY + verticalBounce);
if (this.feedbackMessage.includes('PERFECT!')) {
this.ctx.restore();
}
}
drawTap() {
this.ctx.fillStyle = this.foregroundColor;
this.ctx.strokeStyle = this.foregroundColor;
this.ctx.lineWidth = 2;
// Corps principal du robinet
this.ctx.beginPath();
this.ctx.rect(285, 25, 30, 25); // Ajusté pour le nouveau centre
this.ctx.fill();
// Bec verseur
this.ctx.beginPath();
this.ctx.moveTo(295, 50); // Ajusté
this.ctx.lineTo(305, 50); // Ajusté
this.ctx.lineTo(300, 60); // Ajusté
this.ctx.closePath();
this.ctx.fill();
// Base décorative
this.ctx.beginPath();
this.ctx.rect(290, 20, 20, 5); // Ajusté
this.ctx.fill();
// Poignée latérale avec style rétro
const handleRotation = this.isPouring ? Math.PI / 6 : 0;
this.ctx.save();
this.ctx.translate(320, 35); // Ajusté pour aligner avec le nouveau corps du robinet
this.ctx.rotate(handleRotation);
// Tige de la poignée
this.ctx.beginPath();
this.ctx.rect(0, -3, 15, 6);
this.ctx.fill();
// Embout de la poignée
this.ctx.beginPath();
this.ctx.rect(15, -5, 5, 10);
this.ctx.fill();
this.ctx.restore();
// Détails décoratifs
this.ctx.beginPath();
this.ctx.rect(285, 32, 30, 2); // Ajusté pour aligner avec le nouveau corps
this.ctx.rect(285, 40, 30, 2); // Ajusté pour aligner avec le nouveau corps
this.ctx.fill();
}
checkResult() {
const accuracy = Math.abs(this.currentLevel - this.targetLevel);
const accuracyBonus = this.upgrades.accuracy.level * 0.005;
this.stats.beersServed++;
if (accuracy < (0.02 + accuracyBonus)) {
const streakBonus = this.perfectStreak; // Get current streak before incrementing
this.money += 2 + streakBonus; // Add streak bonus to money
this.currentLevelMoney += 2 + streakBonus;
this.perfectStreak++;
this.stats.perfectPours++;
this.stats.maxStreak = Math.max(this.stats.maxStreak, this.perfectStreak);
this.stats.totalMoney += 2 + streakBonus;
this.feedbackMessage = this.perfectStreak > 1 ?
`PERFECT! x${this.perfectStreak} +$${2 + streakBonus}` :
'PERFECT! +$2';
} else {
this.perfectStreak = 0;
if (accuracy < (0.05 + accuracyBonus)) {
this.money += 1;
this.currentLevelMoney += 1;
this.feedbackMessage = 'GOOD! +$1';
if (this.currentKeg <= 0) {
this.showKegEmptyMessage();
return;
}
this.currentKeg--;
} else if (this.currentLevel > this.targetLevel) {
this.money = Math.max(0, this.money - 1);
this.feedbackMessage = 'TOO MUCH! -$1';
} else {
this.feedbackMessage = 'NOT ENOUGH!';
}
}
this.feedbackTimer = 1.5;
setTimeout(() => {
if (this.currentLevelMoney >= this.levelTarget) {
this.showShop();
} else {
this.resetLevel();
}
}, 1000);
}
showKegEmptyMessage() {
this.gameActive = false;
const kegScreen = document.createElement('div');
kegScreen.style.position = 'absolute';
kegScreen.style.top = '50%';
kegScreen.style.left = '50%';
kegScreen.style.transform = 'translate(-50%, -50%)';
kegScreen.style.backgroundColor = this.backgroundColor;
kegScreen.style.border = `2px solid ${this.foregroundColor}`;
kegScreen.style.padding = '20px';
kegScreen.style.textAlign = 'center';
kegScreen.style.color = this.foregroundColor;
kegScreen.style.fontFamily = '"Press Start 2P", monospace';
kegScreen.innerHTML = `
<h2>KEG EMPTY!</h2>
<p>You need a new keg to continue.</p>
<p>Cost: $${this.kegPrice}</p>
${this.kegsInStock > 0 ? `
<button class="button" onclick="game.useStoredKeg()">
Use Keg from Stock
</button>
` : `
<button class="button" onclick="game.buyNewKeg()"
${this.money < this.kegPrice ? 'disabled' : ''}>
Buy New Keg
</button>
`}
`;
this.gameScreen.appendChild(kegScreen);
window.game = this;
}
buyNewKeg() {
if (this.money >= this.kegPrice) {
this.money -= this.kegPrice;
this.currentKeg = this.kegCapacity;
this.gameScreen.querySelector('div[style*="position: absolute"]')?.remove();
this.gameActive = true;
this.lastTime = Date.now();
this.gameLoop();
}
}
resetLevel() {
this.currentLevel = 0;
// Cible plus aléatoire avec des valeurs plus extrêmes
this.targetLevel = Math.random() * 0.8 + 0.1;
// Calcul du bonus de vitesse basé sur le niveau
const levelSpeedBonus = Math.floor((this.level - 1) / 2) * 0.1; // Adjusted to reduce speed increase
// Vitesses réduites pour les deux modes
const baseSpeed = Math.random() > 0.5 ?
(Math.random() * 0.004 + 0.003) : // Vitesse lente (était 0.008 + 0.005)
(Math.random() * 0.01 + 0.008); // Vitesse rapide (était 0.02 + 0.015)
this.pourSpeed = baseSpeed *
(1 + levelSpeedBonus) *
(1 + (this.upgrades.pourSpeed.level - 1) * 0.1); // Adjusted to reduce speed increase
}
showShop() {
this.gameActive = false;
// Remove any existing shop screen first
const existingShop = this.gameScreen.querySelector('div[style*="position: absolute"]');
if (existingShop) {
existingShop.remove();
}
const shopScreen = document.createElement('div');
shopScreen.style.position = 'absolute';
shopScreen.style.top = '50%';
shopScreen.style.left = '50%';
shopScreen.style.transform = 'translate(-50%, -50%)';
shopScreen.style.backgroundColor = this.backgroundColor;
shopScreen.style.border = `2px solid ${this.foregroundColor}`;
shopScreen.style.padding = '20px';
shopScreen.style.textAlign = 'center';
shopScreen.style.color = this.foregroundColor;
shopScreen.style.fontFamily = '"Press Start 2P", monospace';
shopScreen.innerHTML = `
<h2>LEVEL ${this.level} COMPLETE!</h2>
<p>Money: $${this.money}</p>
<div style="margin: 20px 0;">
${Object.entries(this.upgrades).map(([key, upgrade]) => `
<button class="button" style="margin: 5px"
onclick="game.buyUpgrade('${key}')"
${this.money < upgrade.cost ? 'disabled' : ''}>
${upgrade.name} (Lvl ${upgrade.level}) - $${upgrade.cost}
</button>
`).join('')}
<button class="button" style="margin: 5px"
onclick="game.buyExtraKeg()"
${this.money < this.kegPrice ? 'disabled' : ''}>
Buy Extra Keg - $${this.kegPrice}
</button>
</div>
<button class="button" onclick="game.nextLevel()">NEXT LEVEL</button>
`;
this.gameScreen.appendChild(shopScreen);
window.game = this;
}
// Corriger la méthode buyUpgrade en retirant la méthode buyExtraKeg imbriquée
buyUpgrade(type) {
const upgrade = this.upgrades[type];
if (this.money >= upgrade.cost) {
this.money -= upgrade.cost;
upgrade.level++;
upgrade.cost = Math.floor(upgrade.cost * 1.5);
// Mettre à jour l'affichage de la boutique
this.showShop(); // Refresh the shop display after purchase
}
}
// Ajouter buyExtraKeg comme méthode séparée de la classe
buyExtraKeg() {
if (this.money >= this.kegPrice) {
this.money -= this.kegPrice;
this.kegsInStock++;
// Update shop display
const shopScreen = this.gameScreen.querySelector('div[style*="position: absolute"]');
if (shopScreen) {
shopScreen.querySelector('p').textContent = `Money: $${this.money}`;
const buttons = shopScreen.querySelectorAll('button');
buttons.forEach(button => {
if (button.textContent.includes('Buy Extra Keg')) {
button.disabled = this.money < this.kegPrice;
}
});
}
}
}
// Ajouter useStoredKeg comme méthode séparée de la classe
useStoredKeg() {
if (this.kegsInStock > 0) {
this.kegsInStock--;
this.currentKeg = this.kegCapacity;
this.gameScreen.querySelector('div[style*="position: absolute"]')?.remove();
this.gameActive = true;
this.lastTime = Date.now();
this.gameLoop();
}
}
nextLevel() {
// Prevent multiple executions
if (!this.gameActive) {
const shopScreen = this.gameScreen.querySelector('div[style*="position: absolute"]');
if (shopScreen) {
shopScreen.remove();
}
this.level++;
this.levelTarget = Math.floor(this.levelTarget * 1.5);
this.currentLevelMoney = 0;
this.timer = 60 + (this.upgrades.timer.level - 1) * 10;
this.gameActive = true;
this.resetLevel();
this.lastTime = Date.now();
this.gameLoop();
}
}
backgroundAnimation() {
this.updateBubbles();
this.drawBackground();
requestAnimationFrame(() => this.backgroundAnimation());
}
createBubble() {
return {
x: Math.random() * this.bgCanvas.width,
y: this.bgCanvas.height + Math.random() * 20,
size: Math.random() * 8 + 4,
speed: Math.random() * 3 + 1.5, // Vitesse augmentée (était 1.5 + 0.5)
opacity: Math.random() * 0.5 + 0.1
};
}
updateBubbles() {
this.bubbles.forEach(bubble => {
bubble.y -= bubble.speed;
if (bubble.y < -20) {
Object.assign(bubble, this.createBubble());
}
});
}
drawBackground() {
this.bgCtx.clearRect(0, 0, this.bgCanvas.width, this.bgCanvas.height);
// Draw bubbles
this.bubbles.forEach(bubble => {
this.bgCtx.beginPath();
this.bgCtx.strokeStyle = `rgba(224, 224, 224, ${bubble.opacity})`;
this.bgCtx.lineWidth = 2;
this.bgCtx.arc(bubble.x, bubble.y, bubble.size, 0, Math.PI * 2);
this.bgCtx.stroke();
});
}
update() {
if (!this.gameActive) return;
const currentTime = Date.now();
const deltaTime = (currentTime - this.lastTime) / 1000;
this.lastTime = currentTime;
this.timer -= deltaTime;
if (this.timer <= 0) {
this.timer = 0;
this.gameActive = false;
this.showGameOver();
return;
}
if (this.isPouring) {
this.currentLevel += this.pourSpeed;
if (this.currentLevel >= 1) {
this.currentLevel = 1;
this.isPouring = false;
this.checkResult();
}
}
if (this.feedbackTimer > 0) {
this.feedbackTimer -= deltaTime;
}
}
showGameOver() {
const gameOverScreen = document.createElement('div');
gameOverScreen.style.position = 'absolute';
gameOverScreen.style.top = '50%';
gameOverScreen.style.left = '50%';
gameOverScreen.style.transform = 'translate(-50%, -50%)';
gameOverScreen.style.backgroundColor = this.backgroundColor;
gameOverScreen.style.border = `2px solid ${this.foregroundColor}`;
gameOverScreen.style.padding = '20px';
gameOverScreen.style.textAlign = 'center';
gameOverScreen.style.color = this.foregroundColor;
gameOverScreen.style.fontFamily = '"Press Start 2P", monospace';
gameOverScreen.innerHTML = `
<h2>GAME OVER</h2>
<p>Money earned: $${this.money}</p>
<div style="margin: 20px 0;">
<h3>STATS</h3>
<p>Beers Served: ${this.stats.beersServed}</p>
<p>Total Money: $${this.stats.totalMoney}</p>
<p>Perfect Pours: ${this.stats.perfectPours}</p>
<p>Best Streak: ${this.stats.maxStreak}</p>
</div>
<button class="button" onclick="location.reload()">PLAY AGAIN</button>
`;
this.gameScreen.appendChild(gameOverScreen);
}
gameLoop() {
if (this.gameActive) {
this.update();
this.draw();
requestAnimationFrame(() => this.gameLoop());
}
}
setupEventListeners() {
const startButton = document.getElementById('startButton');
const patchNotesButton = document.getElementById('patchNotesButton');
if (startButton) {
startButton.addEventListener('click', () => this.startGame());
}
if (patchNotesButton) {
patchNotesButton.addEventListener('click', () => {
document.getElementById('patchNotesOverlay').classList.remove('hidden');
});
}
if (!this.canvas) return;
this.canvas.addEventListener('mousedown', () => this.startPouring());
this.canvas.addEventListener('mouseup', () => this.stopPouring());
this.canvas.addEventListener('touchstart', (e) => {
e.preventDefault();
this.startPouring();
});
this.canvas.addEventListener('touchend', (e) => {
e.preventDefault();
this.stopPouring();
});
}
startPouring() {
if (this.gameActive && this.currentLevel < 1) {
this.isPouring = true;
}
}
stopPouring() {
if (this.isPouring && this.gameActive) {
this.isPouring = false;
this.checkResult();
}
}
drawMouseIcon(ctx, x, y) {
ctx.strokeStyle = this.foregroundColor;
ctx.fillStyle = this.foregroundColor;
ctx.lineWidth = 2;
// Draw mouse outline
ctx.beginPath();
ctx.moveTo(x - 12, y - 15);
ctx.lineTo(x - 12, y + 10);
ctx.quadraticCurveTo(x - 12, y + 20, x, y + 20);
ctx.quadraticCurveTo(x + 12, y + 20, x + 12, y + 10);
ctx.lineTo(x + 12, y - 15);
ctx.quadraticCurveTo(x + 12, y - 20, x, y - 20);
ctx.quadraticCurveTo(x - 12, y - 20, x - 12, y - 15);
ctx.stroke();
// Draw scroll wheel
ctx.beginPath();
ctx.moveTo(x, y - 20);
ctx.lineTo(x, y - 5);
ctx.stroke();
// Draw left button
ctx.beginPath();
ctx.moveTo(x - 12, y - 15);
ctx.lineTo(x, y - 15);
ctx.lineTo(x, y - 5);
ctx.lineTo(x - 12, y - 5);
ctx.closePath();
ctx.fill();
// Draw scroll indicator
ctx.beginPath();
ctx.rect(x - 2, y - 12, 4, 4);
ctx.stroke();
// Draw click animation
ctx.beginPath();
ctx.moveTo(x, y - 20);
ctx.quadraticCurveTo(x, y - 35, x - 5, y - 35);
ctx.stroke();
}
drawGlass() {
const centerX = this.canvas.width / 2;
const glassTop = 200;
const glassBottom = 400; // Réduit pour un verre plus harmonieux
const glassWidth = 150;
// Draw glass outline
this.ctx.strokeStyle = this.foregroundColor;
this.ctx.lineWidth = 2;
this.ctx.beginPath();
this.ctx.moveTo(centerX - glassWidth/2, glassTop);
this.ctx.lineTo(centerX - glassWidth/2, glassBottom);
this.ctx.lineTo(centerX + glassWidth/2, glassBottom);
this.ctx.lineTo(centerX + glassWidth/2, glassTop);
this.ctx.stroke();
// Draw enlarged glass handle
const handleWidth = 60;
const handleHeight = 150;
this.ctx.beginPath();
this.ctx.arc(centerX + glassWidth/2 + handleWidth/4, glassTop + handleHeight/2, handleWidth/2, Math.PI / 2, -Math.PI / 2, true);
this.ctx.stroke();
// Draw target line
this.ctx.setLineDash([4, 4]);
this.ctx.beginPath();
const targetY = glassBottom - ((glassBottom - glassTop) * this.targetLevel);
this.ctx.moveTo(centerX - glassWidth/2, targetY);
this.ctx.lineTo(centerX + glassWidth/2, targetY);
this.ctx.stroke();
this.ctx.setLineDash([]);
// Draw liquid
if (this.currentLevel > 0) {
const liquidHeight = (glassBottom - glassTop) * this.currentLevel;
this.drawBeerPattern(
centerX - glassWidth/2,
glassBottom - liquidHeight,
glassWidth,
liquidHeight
);
// Adjust foam height based on pour speed
const baseFoamHeight = 10;
const maxFoamHeight = 25;
const foamHeight = baseFoamHeight + (maxFoamHeight - baseFoamHeight) * (this.pourSpeed / 0.01);
const foamWidth = glassWidth;
const foamY = glassBottom - liquidHeight - foamHeight;
this.ctx.fillStyle = this.foregroundColor;
// Draw foam pattern with adjusted alignment
const startX = Math.floor(centerX - foamWidth/2) + 2;
for (let y = Math.floor(foamY); y < foamY + foamHeight; y += 4) {
for (let x = startX; x < centerX + foamWidth/2 - 2; x += 4) {
this.ctx.beginPath();
this.ctx.arc(x, y, 2, 0, Math.PI * 2);
this.ctx.fill();
}
}
}
}
drawBeerPattern(x, y, width, height) {
this.ctx.fillStyle = this.foregroundColor;
const dotSize = 2;
const spacing = 4;
for (let i = 0; i < width; i += spacing) {
for (let j = 0; j < height; j += spacing) {
this.ctx.fillRect(x + i, y + j, dotSize, dotSize);
}
}
}
drawMoneyBag() {
this.ctx.fillStyle = this.foregroundColor;
this.ctx.font = '24px "Press Start 2P"'; // Agrandi
this.ctx.fillText(`$${this.money}`, 50, 300); // Ajusté
}
drawTimer() {
const centerX = 500; // Ajusté
const centerY = 300; // Ajusté
const radius = 35; // Agrandi
this.ctx.strokeStyle = this.foregroundColor;
this.ctx.lineWidth = 2;
this.ctx.beginPath();
this.ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
this.ctx.stroke();
this.ctx.fillStyle = this.foregroundColor;
this.ctx.font = '16px "Press Start 2P"';
const timerText = Math.ceil(this.timer).toString();
const textWidth = this.ctx.measureText(timerText).width;
this.ctx.fillText(timerText, centerX - textWidth/2, centerY + 6);
}
drawPrice() {
this.ctx.fillStyle = this.foregroundColor;
this.ctx.font = '16px "Press Start 2P"'; // Agrandi
this.ctx.fillText('$1', this.canvas.width / 2 - 15, 180); // Ajusté
}
drawPourStream() {
this.ctx.fillStyle = this.foregroundColor;
const centerX = this.canvas.width / 2;
const glassTop = 200;
const liquidTop = glassTop + ((400 - glassTop) * (1 - this.currentLevel)); // Ajusté pour la nouvelle hauteur
this.ctx.beginPath();
this.ctx.moveTo(centerX, 35);
this.ctx.lineTo(centerX + 4, liquidTop);
this.ctx.lineTo(centerX - 4, liquidTop);
this.ctx.closePath();
this.ctx.fill();
}
drawKegMeter() {
const y = 480;
const x = this.canvas.width / 2 - 100;
const width = 200;
const height = 30;
// Draw keg outline and fill
this.ctx.strokeStyle = this.foregroundColor;
this.ctx.lineWidth = 2;
this.ctx.strokeRect(x, y, width, height);
const fillWidth = (this.currentKeg / this.kegCapacity) * width;
this.ctx.fillStyle = this.foregroundColor;
this.ctx.fillRect(x, y, fillWidth, height);
// Draw keg count above
this.ctx.font = '12px "Press Start 2P"';
this.ctx.fillStyle = this.foregroundColor;
const countText = `${this.currentKeg}/${this.kegCapacity}`;
const countWidth = this.ctx.measureText(countText).width;
this.ctx.fillText(countText, x + (width/2) - (countWidth/2), y - 5);
// Add "KEG METER" text below
const meterText = "KEG METER";
const meterWidth = this.ctx.measureText(meterText).width;
this.ctx.fillText(meterText, x + (width/2) - (meterWidth/2), y + height + 20);
}
// Ajouter cette nouvelle méthode
drawKegStock() {
if (this.kegsInStock > 0) {
const x = 500; // Garde l'alignement vertical avec le timer (centerX)
const y = 480; // Même hauteur que la jauge de fût
const kegWidth = 20;
const kegHeight = 30;
const lineSpacing = 8;
// Dessiner l'icône du fût (rectangle simple)
this.ctx.strokeStyle = this.foregroundColor;
this.ctx.lineWidth = 2;
// Rectangle principal
this.ctx.strokeRect(x - kegWidth/2, y, kegWidth, kegHeight);
// Lignes horizontales intérieures
this.ctx.beginPath();
this.ctx.moveTo(x - kegWidth/2, y + lineSpacing);
this.ctx.lineTo(x + kegWidth/2, y + lineSpacing);
this.ctx.moveTo(x - kegWidth/2, y + kegHeight - lineSpacing);
this.ctx.lineTo(x + kegWidth/2, y + kegHeight - lineSpacing);
this.ctx.stroke();
// Afficher le nombre
this.ctx.font = '12px "Press Start 2P"';
this.ctx.fillStyle = this.foregroundColor;
this.ctx.fillText(`x${this.kegsInStock}`, x + 15, y + 20);
}
}
drawBeerPattern(x, y, width, height) {
this.ctx.fillStyle = this.foregroundColor;
const dotSize = 2;
const spacing = 4;
for (let i = 0; i < width; i += spacing) {
for (let j = 0; j < height; j += spacing) {
this.ctx.fillRect(x + i, y + j, dotSize, dotSize);
}
}
}
drawMoneyBag() {
this.ctx.fillStyle = this.foregroundColor;
this.ctx.font = '24px "Press Start 2P"'; // Agrandi
this.ctx.fillText(`$${this.money}`, 50, 300); // Ajusté
}
drawTimer() {
const centerX = 500; // Ajusté
const centerY = 300; // Ajusté
const radius = 35; // Agrandi
this.ctx.strokeStyle = this.foregroundColor;
this.ctx.lineWidth = 2;
this.ctx.beginPath();
this.ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
this.ctx.stroke();
this.ctx.fillStyle = this.foregroundColor;
this.ctx.font = '16px "Press Start 2P"';
const timerText = Math.ceil(this.timer).toString();
const textWidth = this.ctx.measureText(timerText).width;
this.ctx.fillText(timerText, centerX - textWidth/2, centerY + 6);
}
drawPrice() {
this.ctx.fillStyle = this.foregroundColor;
this.ctx.font = '16px "Press Start 2P"'; // Agrandi
this.ctx.fillText('$1', this.canvas.width / 2 - 15, 180); // Ajusté
}
drawPourStream() {
this.ctx.fillStyle = this.foregroundColor;
const centerX = this.canvas.width / 2;
const glassTop = 200;
const liquidTop = glassTop + ((400 - glassTop) * (1 - this.currentLevel)); // Ajusté pour la nouvelle hauteur
this.ctx.beginPath();
this.ctx.moveTo(centerX, 35);
this.ctx.lineTo(centerX + 4, liquidTop);
this.ctx.lineTo(centerX - 4, liquidTop);
this.ctx.closePath();
this.ctx.fill();
}
drawKegMeter() {
const y = 480;
const x = this.canvas.width / 2 - 100;
const width = 200;
const height = 30;
// Draw keg outline and fill
this.ctx.strokeStyle = this.foregroundColor;
this.ctx.lineWidth = 2;
this.ctx.strokeRect(x, y, width, height);
const fillWidth = (this.currentKeg / this.kegCapacity) * width;
this.ctx.fillStyle = this.foregroundColor;
this.ctx.fillRect(x, y, fillWidth, height);
// Draw keg count above
this.ctx.font = '12px "Press Start 2P"';
this.ctx.fillStyle = this.foregroundColor;
const countText = `${this.currentKeg}/${this.kegCapacity}`;
const countWidth = this.ctx.measureText(countText).width;
this.ctx.fillText(countText, x + (width/2) - (countWidth/2), y - 5);
// Add "KEG METER" text below
const meterText = "KEG METER";
const meterWidth = this.ctx.measureText(meterText).width;
this.ctx.fillText(meterText, x + (width/2) - (meterWidth/2), y + height + 20);
}
}
window.addEventListener('load', () => {
new Game();
});

47
Sources/index.html Normal file
View File

@ -0,0 +1,47 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Perfect Pour</title>
<link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap" rel="stylesheet">
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="menuScreen">
<canvas id="backgroundCanvas"></canvas>
<div class="menu-content">
<h1 class="game-title floating">PERFECT<br>POUR</h1>
<p class="game-instructions">Fill the glass to<br>the dotted line</p>
<canvas id="instructionsCanvas"></canvas>
<button id="startButton" class="button">START GAME</button>
<div style="height: 10px"></div>
<button id="patchNotesButton" class="button">PATCH NOTES</button>
</div>
</div>
<div id="gameScreen" class="hidden">
<canvas id="gameCanvas"></canvas>
</div>
<div id="patchNotesOverlay" class="hidden">
<div class="patch-notes-content">
<h2>PATCH NOTES</h2>
<div class="patch-notes-list">
<h3>Version 1.0.2</h3>
<ul>
<li>🎮 Initial game release</li>
<li>🍺 Basic beer pouring mechanics</li>
<li>💰 Money and upgrade system</li>
<li>🛢️ Keg management system</li>
<li>⏱️ Timer and scoring system</li>
</ul>
</div>
<button class="button" onclick="this.parentElement.parentElement.classList.add('hidden')">CLOSE</button>
</div>
</div>
<div id="patchNotesScreen" class="hidden"></div>
<script src="game.js"></script>
<footer class="footer">
© <a href="https://www.baptisteleforestier.com" target="_blank">Baptiste Leforestier</a> 2025 - Made with HTML,CSS et JS - V.1.0.2
</footer>
</body>
</html>

223
Sources/style.css Normal file
View File

@ -0,0 +1,223 @@
body {
margin: 0;
padding: 0;
min-height: 100vh;
display: flex;
flex-direction: column;
background-color: #1a1a1a;
font-family: 'Press Start 2P', monospace;
color: #e0e0e0;
}
#menuScreen, #gameScreen {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
position: relative;
width: 100%;
padding: 20px;
box-sizing: border-box;
}
.menu-content {
position: relative;
z-index: 1;
text-align: center;
width: 100%;
max-width: 600px;
padding: 20px;
}
.game-title {
font-size: clamp(2rem, 8vw, 4rem);
line-height: 1.4;
margin-bottom: 2rem;
}
.game-instructions {
font-size: clamp(0.8rem, 3vw, 1.2rem);
line-height: 1.6;
margin-bottom: 2rem;
}
#gameCanvas {
max-width: 100%;
height: auto;
aspect-ratio: 1;
}
#backgroundCanvas {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
}
#instructionsCanvas {
margin: 1rem auto;
display: block;
max-width: 100%;
height: auto;
}
.button {
font-family: 'Press Start 2P', monospace;
background-color: transparent;
border: 2px solid #e0e0e0;
color: #e0e0e0;
padding: clamp(0.5rem, 2vw, 1rem) clamp(1rem, 4vw, 2rem);
cursor: pointer;
font-size: clamp(0.8rem, 2.5vw, 1rem);
transition: all 0.3s ease;
}
.button:hover {
background-color: #e0e0e0;
color: #1a1a1a;
}
.button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.hidden {
display: none !important;
}
.footer {
text-align: center;
padding: 1rem;
font-size: clamp(0.4rem, 1.5vw, 0.6rem);
width: 100%;
box-sizing: border-box;
}
.footer a {
color: #e0e0e0;
text-decoration: none;
}
.footer a:hover {
text-decoration: underline;
}
@media (max-width: 768px) {
.menu-content {
padding: 10px;
}
.button {
width: 80%;
max-width: 300px;
}
}
.floating {
animation: float 6s cubic-bezier(0.37, 0, 0.63, 1) infinite;
filter: drop-shadow(0 0 15px rgba(224, 224, 224, 0.2));
transform-origin: center;
perspective: 1000px;
}
@keyframes float {
0% {
transform: translateY(0) rotateX(0deg) scale(1);
filter: drop-shadow(0 0 15px rgba(224, 224, 224, 0.2));
}
25% {
transform: translateY(-10px) rotateX(2deg) scale(1.02);
filter: drop-shadow(0 0 20px rgba(224, 224, 224, 0.3));
}
50% {
transform: translateY(0) rotateX(0deg) scale(1);
filter: drop-shadow(0 0 15px rgba(224, 224, 224, 0.2));
}
75% {
transform: translateY(5px) rotateX(-1deg) scale(0.98);
filter: drop-shadow(0 0 10px rgba(224, 224, 224, 0.1));
}
100% {
transform: translateY(0) rotateX(0deg) scale(1);
filter: drop-shadow(0 0 15px rgba(224, 224, 224, 0.2));
}
}
#patchNotesOverlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.8);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
#patchNotesOverlay.hidden {
display: none;
}
.patch-notes-content {
background-color: #1a1a1a;
border: 2px solid #e0e0e0;
padding: 20px;
max-width: 600px;
width: 90%;
max-height: 80vh;
overflow-y: auto;
color: #e0e0e0;
font-family: 'Press Start 2P', monospace;
}
.patch-notes-content h2 {
text-align: center;
margin-bottom: 20px;
}
.patch-notes-list {
margin: 20px 0;
}
.patch-notes-list ul {
list-style-type: none;
padding: 0;
}
.patch-notes-list li {
margin: 10px 0;
}
.patch-notes-content .button {
display: block;
margin: 20px auto 0;
}
.button {
font-family: 'Press Start 2P', monospace;
background-color: #1a1a1a;
border: 2px solid #e0e0e0;
color: #e0e0e0;
padding: 10px 20px;
cursor: pointer;
font-size: 16px;
width: 250px;
transition: all 0.3s ease;
display: inline-block;
text-align: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.button:hover {
background-color: #e0e0e0;
color: #1a1a1a;
}
.button:disabled {
opacity: 0.5;
cursor: not-allowed;
}