Match24 Game Code
Match24 Game Code
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Make 24 - Math Puzzle Game</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
user-select: none;
-webkit-user-select: none;
-webkit-touch-callout: none;
}
body {
font-family: 'Arial', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
height: 100vh;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
}
#gameContainer {
width: 100vw;
height: 100vh;
max-width: 400px;
max-height: 711px;
position: relative;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 20px;
overflow: hidden;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
}
#gameCanvas {
width: 100%;
height: 100%;
display: block;
touch-action: none;
}
.particle {
position: absolute;
pointer-events: none;
border-radius: 50%;
animation: particle-burst 1s ease-out forwards;
}
@keyframes particle-burst {
0% {
transform: scale(1) translate(0, 0);
opacity: 1;
}
100% {
transform: scale(0) translate(var(--dx), var(--dy));
opacity: 0;
}
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-5px); }
75% { transform: translateX(5px); }
}
.shake {
animation: shake 0.5s ease-in-out;
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.1); }
100% { transform: scale(1); }
}
.pulse {
animation: pulse 0.3s ease-in-out;
}
#startScreen, #gameOverScreen {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: white;
z-index: 1000;
}
.screen-title {
font-size: 3em;
font-weight: bold;
margin-bottom: 20px;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
}
.screen-subtitle {
font-size: 1.2em;
margin-bottom: 30px;
text-align: center;
padding: 0 20px;
}
.btn {
background: linear-gradient(45deg, #ff6b6b, #ff8e8e);
color: white;
border: none;
padding: 15px 30px;
font-size: 1.2em;
border-radius: 25px;
cursor: pointer;
margin: 10px;
min-width: 120px;
min-height: 50px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
transition: all 0.3s ease;
}
.btn:hover, .btn:active {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.4);
}
.hidden {
display: none !important;
}
</style>
</head>
<body>
<div id="gameContainer">
<div id="startScreen">
<div class="screen-title">Make 24</div>
<div class="screen-subtitle">Drag numbers and operators to create an
equation that equals 24!</div>
<button class="btn" onclick="startGame()">Start Game</button>
</div>
<canvas id="gameCanvas"></canvas>
</div>
<script>
class Make24Game {
constructor() {
this.canvas = document.getElementById('gameCanvas');
this.ctx = this.canvas.getContext('2d');
this.setupCanvas();
this.draggedItem = null;
this.dragOffset = { x: 0, y: 0 };
this.isTouch = false;
this.numbers = [];
this.operators = ['+', '-', '×', '÷'];
this.equationSlots = [];
this.selectedItems = [];
this.previewResult = null;
this.setupLevel();
this.setupEventListeners();
this.gameLoop();
}
setupCanvas() {
const container = document.getElementById('gameContainer');
const rect = container.getBoundingClientRect();
this.ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
this.width = rect.width;
this.height = rect.height;
}
setupLevel() {
// Generate random numbers that can make 24
const solutions = [
[1, 1, 8, 3], [1, 2, 3, 4], [1, 3, 4, 6], [1, 4, 5, 6],
[2, 2, 6, 2], [2, 3, 4, 4], [2, 4, 4, 3], [3, 3, 8, 1],
[4, 4, 2, 3], [6, 6, 1, 3], [8, 8, 1, 2], [9, 3, 2, 4]
];
this.selectedItems = [];
this.previewResult = null;
this.startTime = Date.now();
}
setupEventListeners() {
// Mouse events
this.canvas.addEventListener('mousedown', (e) =>
this.handleStart(e, false));
this.canvas.addEventListener('mousemove', (e) => this.handleMove(e,
false));
this.canvas.addEventListener('mouseup', (e) => this.handleEnd(e,
false));
// Touch events
this.canvas.addEventListener('touchstart', (e) => {
e.preventDefault();
this.handleStart(e.touches[0], true);
});
this.canvas.addEventListener('touchmove', (e) => {
e.preventDefault();
this.handleMove(e.touches[0], true);
});
this.canvas.addEventListener('touchend', (e) => {
e.preventDefault();
this.handleEnd(e.changedTouches[0], true);
});
}
getEventPos(e) {
const rect = this.canvas.getBoundingClientRect();
return {
x: e.clientX - rect.left,
y: e.clientY - rect.top
};
}
handleStart(e, isTouch) {
if (this.gameState !== 'playing') return;
// Check numbers
for (let item of this.numbers) {
if (this.isPointInRect(pos, item) && !item.isDragging) {
if (this.isTouch) {
this.handleSelection(item);
} else {
this.startDrag(item, pos);
}
return;
}
}
// Check operators
for (let item of this.operatorObjects) {
if (this.isPointInRect(pos, item) && !item.isDragging) {
if (this.isTouch) {
this.handleSelection(item);
} else {
this.startDrag(item, pos);
}
return;
}
}
}
handleSelection(item) {
// Add visual selection feedback
item.selected = !item.selected;
if (item.selected) {
this.selectedItems.push(item);
}
// Check if we have number -> operator -> number pattern
this.checkCalculationPreview();
// Haptic feedback
if (navigator.vibrate) {
navigator.vibrate(10);
}
}
checkCalculationPreview() {
// Clear previous preview
this.previewResult = null;
startDrag(item, pos) {
this.draggedItem = item;
item.isDragging = true;
this.dragOffset.x = pos.x - item.x;
this.dragOffset.y = pos.y - item.y;
// Haptic feedback
if (navigator.vibrate) {
navigator.vibrate(10);
}
}
handleMove(e, isTouch) {
if (!this.draggedItem || this.gameState !== 'playing' ||
this.isTouch) return;
handleEnd(e, isTouch) {
if (!this.draggedItem || this.gameState !== 'playing' ||
this.isTouch) return;
// Haptic feedback
if (navigator.vibrate) {
navigator.vibrate(20);
}
break;
}
}
if (!droppedOnSlot) {
// Return to original position and size
item.x = item.originalX;
item.y = item.originalY;
item.width = 60;
item.height = 60;
}
item.isDragging = false;
this.draggedItem = null;
isPointInRect(point, rect) {
return point.x >= rect.x && point.x <= rect.x + rect.width &&
point.y >= rect.y && point.y <= rect.y + rect.height;
}
checkEquation() {
// Check if all slots are filled
let allFilled = true;
for (let slot of this.equationSlots) {
if (!slot.occupied) {
allFilled = false;
break;
}
}
if (!allFilled) return;
try {
const result = this.evaluateExpression(equation);
if (Math.abs(result - 24) < 0.0001) {
this.levelComplete();
} else {
this.showError();
}
} catch (e) {
this.showError();
}
}
evaluateExpression(expr) {
// Simple expression evaluator with proper order of operations
const tokens = expr.match(/\d+|\+|\-|\*|\//g);
const numbers = [];
const operators = [];
numbers.splice(i, 2, result);
operators.splice(i, 1);
i--;
}
}
return result;
}
levelComplete() {
this.timer = (Date.now() - this.startTime) / 1000;
this.score += Math.max(1000 - Math.floor(this.timer * 10), 100);
// Particle effects
this.createParticleExplosion(this.width / 2, this.height / 2);
// Haptic feedback
if (navigator.vibrate) {
navigator.vibrate([100, 50, 100]);
}
setTimeout(() => {
this.gameState = 'gameover';
document.getElementById('gameOverText').textContent =
`Time: ${this.timer.toFixed(1)}s | Score: ${this.score}`;
document.getElementById('gameOverScreen').classList.remove('hidden');
}, 1000);
}
showError() {
// Shake animation and error feedback
const canvas = document.getElementById('gameCanvas');
canvas.classList.add('shake');
setTimeout(() => canvas.classList.remove('shake'), 500);
// Haptic feedback
if (navigator.vibrate) {
navigator.vibrate([50, 50, 50]);
}
}
createParticleExplosion(x, y) {
const colors = ['#ff6b6b', '#4ecdc4', '#45b7d1', '#96ceb4',
'#feca57'];
const container = document.getElementById('gameContainer');
container.appendChild(particle);
setTimeout(() => {
container.removeChild(particle);
}, 1000);
}
}
draw() {
// Clear canvas
this.ctx.fillStyle = 'rgba(255, 255, 255, 0.1)';
this.ctx.fillRect(0, 0, this.width, this.height);
// Draw header
this.drawHeader();
// Draw numbers
for (let number of this.numbers) {
this.drawItem(number, '#4ecdc4');
}
// Draw operators
for (let op of this.operatorObjects) {
this.drawItem(op, '#ff6b6b');
}
drawHeader() {
this.ctx.fillStyle = 'white';
this.ctx.font = 'bold 24px Arial';
this.ctx.textAlign = 'center';
this.ctx.fillText(`Level ${this.level}`, this.width / 2, 40);
// Target display
this.ctx.font = 'bold 32px Arial';
this.ctx.fillStyle = '#feca57';
this.ctx.fillText('Make 24!', this.width / 2, 110);
}
drawItem(item, color) {
const x = item.x;
const y = item.y;
const size = item.width;
// Selection highlight
if (item.selected) {
this.ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
this.ctx.beginPath();
if (item.type === 'number') {
this.ctx.arc(x + size/2, y + size/2, size/2 + 4, 0, Math.PI
* 2);
} else {
this.ctx.roundRect(x - 4, y - 4, size + 8, size + 8, 12);
}
this.ctx.fill();
}
// Shadow
this.ctx.fillStyle = 'rgba(0, 0, 0, 0.2)';
this.ctx.beginPath();
if (item.type === 'number') {
this.ctx.arc(x + size/2 + 2, y + size/2 + 2, size/2, 0, Math.PI
* 2);
} else {
this.ctx.roundRect(x + 2, y + 2, size, size, 8);
}
this.ctx.fill();
// Main shape
this.ctx.fillStyle = item.isDragging ?
this.lightenColor(color, 20) : color;
this.ctx.beginPath();
if (item.type === 'number') {
this.ctx.arc(x + size/2, y + size/2, size/2, 0, Math.PI * 2);
} else {
this.ctx.roundRect(x, y, size, size, 8);
}
this.ctx.fill();
// Border
this.ctx.strokeStyle = item.selected ?
'rgba(255, 255, 255, 0.8)' : 'rgba(255, 255, 255, 0.3)';
this.ctx.lineWidth = item.selected ? 3 : 2;
this.ctx.stroke();
// Text
this.ctx.fillStyle = 'white';
this.ctx.font = 'bold 24px Arial';
this.ctx.textAlign = 'center';
this.ctx.fillText(item.value, x + size/2, y + size/2 + 8);
}
drawEquationArea() {
// Background
this.ctx.fillStyle = 'rgba(0, 0, 0, 0.2)';
this.ctx.roundRect(10, this.height - 130, this.width - 20, 70, 10);
this.ctx.fill();
// Equation label
this.ctx.fillStyle = 'white';
this.ctx.font = 'bold 14px Arial';
this.ctx.textAlign = 'left';
this.ctx.fillText('Build equation:', 20, this.height - 135);
// Draw slots
for (let i = 0; i < this.equationSlots.length; i++) {
const slot = this.equationSlots[i];
this.ctx.strokeStyle = slot.occupied ?
'rgba(255, 255, 255, 0.8)' : 'rgba(255, 255, 255, 0.3)';
this.ctx.lineWidth = 2;
this.ctx.setLineDash(slot.occupied ? [] : [4, 4]);
this.ctx.fillStyle = 'white';
this.ctx.font = 'bold 18px Arial';
this.ctx.textAlign = 'center';
this.ctx.fillText('=', equalsX, lastSlot.y + lastSlot.height/2 +
6);
// Draw 24 in a circle
const twentyFourX = equalsX + 25;
this.ctx.fillStyle = '#feca57';
this.ctx.beginPath();
this.ctx.arc(twentyFourX, lastSlot.y + lastSlot.height/2, 18, 0,
Math.PI * 2);
this.ctx.fill();
this.ctx.fillStyle = 'white';
this.ctx.font = 'bold 16px Arial';
this.ctx.fillText('24', twentyFourX, lastSlot.y + lastSlot.height/2
+ 5);
this.ctx.setLineDash([]);
}
drawCalculationPreview() {
if (!this.previewResult) return;
// Border
this.ctx.strokeStyle = 'rgba(255, 255, 255, 0.8)';
this.ctx.lineWidth = 2;
this.ctx.stroke();
// Result text
this.ctx.fillStyle = 'white';
this.ctx.font = 'bold 16px Arial';
this.ctx.textAlign = 'center';
const displayValue = Number.isInteger(preview.value) ?
preview.value.toString() : preview.value.toFixed(1);
this.ctx.fillText(displayValue, preview.x + radius, preview.y +
radius + 5);
// Connection line
this.ctx.strokeStyle = 'rgba(252, 202, 87, 0.6)';
this.ctx.lineWidth = 2;
this.ctx.setLineDash([5, 5]);
this.ctx.beginPath();
this.ctx.moveTo(preview.targetItem.x + preview.targetItem.width,
preview.targetItem.y + preview.targetItem.height/2);
this.ctx.lineTo(preview.x, preview.y + radius);
this.ctx.stroke();
this.ctx.setLineDash([]);
}
lightenColor(color, percent) {
const num = parseInt(color.replace("#",""), 16);
const amt = Math.round(2.55 * percent);
const R = (num >> 16) + amt;
const G = (num >> 8 & 0x00FF) + amt;
const B = (num & 0x0000FF) + amt;
return "#" + (0x1000000 + (R < 255 ? R < 1 ? 0 : R : 255) * 0x10000
+
(G < 255 ? G < 1 ? 0 : G : 255) * 0x100 +
(B < 255 ? B < 1 ? 0 : B : 255)).toString(16).slice(1);
}
gameLoop() {
this.draw();
requestAnimationFrame(() => this.gameLoop());
}
}
let game;
function startGame() {
document.getElementById('startScreen').classList.add('hidden');
game = new Make24Game();
game.gameState = 'playing';
}
function nextLevel() {
document.getElementById('gameOverScreen').classList.add('hidden');
game.level++;
game.setupLevel();
game.gameState = 'playing';
}
function restartGame() {
document.getElementById('gameOverScreen').classList.add('hidden');
game.level = 1;
game.score = 0;
game.setupLevel();
game.gameState = 'playing';
}