Spaces:
Running
Running
<html> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Jewel Match</title> | |
<style> | |
body { | |
margin: 0; | |
background: #111; | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
min-height: 100vh; | |
font-family: Arial, sans-serif; | |
} | |
#gameContainer { | |
position: relative; | |
} | |
canvas { | |
border: 2px solid #333; | |
box-shadow: 0 0 20px rgba(0,0,0,0.5); | |
} | |
#ui { | |
position: absolute; | |
top: 10px; | |
left: 10px; | |
color: white; | |
font-size: 20px; | |
} | |
#score { | |
margin-bottom: 10px; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="gameContainer"> | |
<canvas id="gameCanvas"></canvas> | |
<div id="ui"> | |
<div id="score">Score: 0</div> | |
<div id="moves">Moves: 30</div> | |
</div> | |
</div> | |
<script> | |
const canvas = document.getElementById('gameCanvas'); | |
const ctx = canvas.getContext('2d'); | |
const GRID_SIZE = 8; | |
const CELL_SIZE = 70; | |
const ANIMATION_SPEED = 0.15; | |
canvas.width = GRID_SIZE * CELL_SIZE; | |
canvas.height = GRID_SIZE * CELL_SIZE; | |
const GEMS = { | |
RUBY: {color: '#ff0000', highlight: '#ff6666'}, | |
SAPPHIRE: {color: '#0000ff', highlight: '#6666ff'}, | |
EMERALD: {color: '#00ff00', highlight: '#66ff66'}, | |
DIAMOND: {color: '#ffffff', highlight: '#aaaaaa'}, | |
TOPAZ: {color: '#ffff00', highlight: '#ffff66'}, | |
AMETHYST: {color: '#ff00ff', highlight: '#ff66ff'} | |
}; | |
let score = 0; | |
let moves = 30; | |
let board = []; | |
let selectedGem = null; | |
let animations = []; | |
let isAnimating = false; | |
class Gem { | |
constructor(type, row, col) { | |
this.type = type; | |
this.row = row; | |
this.col = col; | |
this.x = col * CELL_SIZE; | |
this.y = row * CELL_SIZE; | |
this.targetX = this.x; | |
this.targetY = this.y; | |
this.scale = 1; | |
this.alpha = 1; | |
} | |
draw() { | |
ctx.save(); | |
ctx.globalAlpha = this.alpha; | |
ctx.translate(this.x + CELL_SIZE/2, this.y + CELL_SIZE/2); | |
ctx.scale(this.scale, this.scale); | |
// Draw gem base | |
ctx.beginPath(); | |
ctx.arc(0, 0, CELL_SIZE/2 - 10, 0, Math.PI * 2); | |
ctx.fillStyle = GEMS[this.type].color; | |
ctx.fill(); | |
// Draw highlight | |
ctx.beginPath(); | |
ctx.arc(-10, -10, 10, 0, Math.PI * 2); | |
ctx.fillStyle = GEMS[this.type].highlight; | |
ctx.fill(); | |
ctx.restore(); | |
} | |
update() { | |
if(this.x !== this.targetX) { | |
this.x += (this.targetX - this.x) * ANIMATION_SPEED; | |
} | |
if(this.y !== this.targetY) { | |
this.y += (this.targetY - this.y) * ANIMATION_SPEED; | |
} | |
} | |
} | |
function initBoard() { | |
for(let row = 0; row < GRID_SIZE; row++) { | |
board[row] = []; | |
for(let col = 0; col < GRID_SIZE; col++) { | |
const gemTypes = Object.keys(GEMS); | |
const randomType = gemTypes[Math.floor(Math.random() * gemTypes.length)]; | |
board[row][col] = new Gem(randomType, row, col); | |
} | |
} | |
} | |
function draw() { | |
ctx.clearRect(0, 0, canvas.width, canvas.height); | |
// Draw board | |
for(let row = 0; row < GRID_SIZE; row++) { | |
for(let col = 0; col < GRID_SIZE; col++) { | |
if(board[row][col]) { | |
board[row][col].draw(); | |
} | |
} | |
} | |
// Draw selected gem highlight | |
if(selectedGem) { | |
ctx.strokeStyle = '#fff'; | |
ctx.lineWidth = 3; | |
ctx.strokeRect( | |
selectedGem.col * CELL_SIZE, | |
selectedGem.row * CELL_SIZE, | |
CELL_SIZE, | |
CELL_SIZE | |
); | |
} | |
requestAnimationFrame(draw); | |
} | |
function update() { | |
for(let row = 0; row < GRID_SIZE; row++) { | |
for(let col = 0; col < GRID_SIZE; col++) { | |
if(board[row][col]) { | |
board[row][col].update(); | |
} | |
} | |
} | |
} | |
function handleClick(e) { | |
if(isAnimating) return; | |
const rect = canvas.getBoundingClientRect(); | |
const x = e.clientX - rect.left; | |
const y = e.clientY - rect.top; | |
const col = Math.floor(x / CELL_SIZE); | |
const row = Math.floor(y / CELL_SIZE); | |
if(col >= 0 && col < GRID_SIZE && row >= 0 && row < GRID_SIZE) { | |
if(!selectedGem) { | |
selectedGem = board[row][col]; | |
} else { | |
// Check if adjacent | |
const rowDiff = Math.abs(selectedGem.row - row); | |
const colDiff = Math.abs(selectedGem.col - col); | |
if((rowDiff === 1 && colDiff === 0) || (rowDiff === 0 && colDiff === 1)) { | |
swapGems(selectedGem, board[row][col]); | |
} | |
selectedGem = null; | |
} | |
} | |
} | |
function swapGems(gem1, gem2) { | |
const tempX = gem1.targetX; | |
const tempY = gem1.targetY; | |
const tempRow = gem1.row; | |
const tempCol = gem1.col; | |
gem1.targetX = gem2.targetX; | |
gem1.targetY = gem2.targetY; | |
gem1.row = gem2.row; | |
gem1.col = gem2.col; | |
gem2.targetX = tempX; | |
gem2.targetY = tempY; | |
gem2.row = tempRow; | |
gem2.col = tempCol; | |
board[gem1.row][gem1.col] = gem1; | |
board[gem2.row][gem2.col] = gem2; | |
moves--; | |
document.getElementById('moves').textContent = `Moves: ${moves}`; | |
setTimeout(checkMatches, 300); | |
} | |
function checkMatches() { | |
let matches = []; | |
// Check horizontal matches | |
for(let row = 0; row < GRID_SIZE; row++) { | |
let matchCount = 1; | |
let matchType = board[row][0]?.type; | |
for(let col = 1; col < GRID_SIZE; col++) { | |
if(board[row][col]?.type === matchType) { | |
matchCount++; | |
} else { | |
if(matchCount >= 3) { | |
for(let i = col-matchCount; i < col; i++) { | |
matches.push({row, col: i}); | |
} | |
} | |
matchCount = 1; | |
matchType = board[row][col]?.type; | |
} | |
} | |
if(matchCount >= 3) { | |
for(let i = GRID_SIZE-matchCount; i < GRID_SIZE; i++) { | |
matches.push({row, col: i}); | |
} | |
} | |
} | |
// Check vertical matches | |
for(let col = 0; col < GRID_SIZE; col++) { | |
let matchCount = 1; | |
let matchType = board[0][col]?.type; | |
for(let row = 1; row < GRID_SIZE; row++) { | |
if(board[row][col]?.type === matchType) { | |
matchCount++; | |
} else { | |
if(matchCount >= 3) { | |
for(let i = row-matchCount; i < row; i++) { | |
matches.push({row: i, col}); | |
} | |
} | |
matchCount = 1; | |
matchType = board[row][col]?.type; | |
} | |
} | |
if(matchCount >= 3) { | |
for(let i = GRID_SIZE-matchCount; i < GRID_SIZE; i++) { | |
matches.push({row: i, col}); | |
} | |
} | |
} | |
if(matches.length > 0) { | |
removeMatches(matches); | |
} | |
} | |
function removeMatches(matches) { | |
isAnimating = true; | |
// Remove matched gems | |
matches.forEach(({row, col}) => { | |
if(board[row][col]) { | |
score += 10; | |
document.getElementById('score').textContent = `Score: ${score}`; | |
board[row][col] = null; | |
} | |
}); | |
// Drop remaining gems | |
for(let col = 0; col < GRID_SIZE; col++) { | |
let emptySpaces = 0; | |
for(let row = GRID_SIZE-1; row >= 0; row--) { | |
if(!board[row][col]) { | |
emptySpaces++; | |
} else if(emptySpaces > 0) { | |
const gem = board[row][col]; | |
gem.row += emptySpaces; | |
gem.targetY = gem.row * CELL_SIZE; | |
board[gem.row][col] = gem; | |
board[row][col] = null; | |
} | |
} | |
// Fill empty spaces with new gems | |
for(let row = emptySpaces-1; row >= 0; row--) { | |
const gemTypes = Object.keys(GEMS); | |
const randomType = gemTypes[Math.floor(Math.random() * gemTypes.length)]; | |
const newGem = new Gem(randomType, row, col); | |
newGem.y = -CELL_SIZE; | |
newGem.targetY = row * CELL_SIZE; | |
board[row][col] = newGem; | |
} | |
} | |
setTimeout(() => { | |
isAnimating = false; | |
checkMatches(); | |
}, 500); | |
} | |
canvas.addEventListener('click', handleClick); | |
function gameLoop() { | |
update(); | |
requestAnimationFrame(gameLoop); | |
} | |
initBoard(); | |
gameLoop(); | |
draw(); | |
</script> | |
</body> | |
</html> |