<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Alien Match Game</title>
<style>
canvas {
display: block;
margin: 0 auto;
background-color: #000;
}
body {
text-align: center;
font-family: Arial, sans-serif;
background-color: #222;
color: white;
}
</style>
</head>
<body>
<h1>Alien Match Game</h1>
<canvas id="gameCanvas"></canvas>
<h2>Score: <span id="score">0</span></h2>
<h2>Level: <span id="level">1</span></h2>
<h2>Target Score: <span id="targetScore">100</span></h2>
<h2>Time Left: <span id="timer">60</span> seconds</h2>
<h2 id="gameOver" style="display:none;">Game Over!</h2>
<script>
// Game variables
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const tileSize = 60; // size of each tile
const gridWidth = 8; // 8x8 grid
const gridHeight = 8;
const alienTypes = ['👽', '👾', '🛸', '🪐']; // Alien types
const powerUpTypes = ['💣', '🚀', '🌈']; // Power-up types
const aliens = [];
let selectedTile = null; // Store the first selected tile
let score = 0; // Keep track of the score
let level = 1; // Current level
let targetScore = 100; // Target score for the current level
let timeLeft = 60; // Time per level in seconds
let animating = false; // Tracks if an animation is running
let timerInterval; // Store the timer interval
// Resize canvas dynamically to fit screen
function resizeCanvas() {
canvas.width = gridWidth * tileSize;
canvas.height = gridHeight * tileSize;
}
// Initialize the grid with random alien types
function initGrid() {
for (let y = 0; y < gridHeight; y++) {
aliens[y] = [];
for (let x = 0; x < gridWidth; x++) {
aliens[y][x] = alienTypes[Math.floor(Math.random() * alienTypes.length)];
}
}
}
// Draw the grid with aliens and power-ups
function drawGrid() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let y = 0; y < gridHeight; y++) {
for (let x = 0; x < gridWidth; x++) {
if (aliens[y][x] !== null) { // Only draw non-null tiles
ctx.fillStyle = '#fff';
ctx.font = '40px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
const alien = aliens[y][x];
const posX = x * tileSize + tileSize / 2;
const posY = y * tileSize + tileSize / 2;
ctx.fillText(alien, posX, posY); // Display either alien or power-up emoji
}
// Draw a border around the tile
ctx.strokeStyle = "#fff";
ctx.strokeRect(x * tileSize, y * tileSize, tileSize, tileSize);
}
}
}
// Get the tile (x, y) position based on mouse click
function getTileCoordinates(mouseX, mouseY) {
const tileX = Math.floor(mouseX / tileSize);
const tileY = Math.floor(mouseY / tileSize);
return { x: tileX, y: tileY };
}
// Handle mouse click on canvas for tile selection and swapping
canvas.addEventListener('click', function(event) {
if (animating) return; // Prevent interaction during animations
const rect = canvas.getBoundingClientRect();
const mouseX = event.clientX - rect.left;
const mouseY = event.clientY - rect.top;
const tile = getTileCoordinates(mouseX, mouseY);
const selectedAlien = aliens[tile.y][tile.x];
// Handle power-up activation
if (powerUpTypes.includes(selectedAlien)) {
handlePowerUp(tile.x, tile.y);
return;
}
// Normal tile selection and swapping
if (!selectedTile) {
// Select the first tile
selectedTile = tile;
highlightTile(tile.x, tile.y);
} else {
// Select the second tile and try swapping
swapTilesAnimated(selectedTile.x, selectedTile.y, tile.x, tile.y);
selectedTile = null; // Reset selection
}
});
// Highlight the selected tile
function highlightTile(x, y) {
ctx.strokeStyle = 'yellow';
ctx.lineWidth = 4;
ctx.strokeRect(x * tileSize, y * tileSize, tileSize, tileSize);
}
// Animate swapping of tiles
function swapTilesAnimated(x1, y1, x2, y2) {
if (animating) return; // Prevent simultaneous animations
animating = true;
const startTime = performance.now();
const swapDuration = 300; // Duration of the animation in ms
const startPos1 = { x: x1 * tileSize, y: y1 * tileSize };
const startPos2 = { x: x2 * tileSize, y: y2 * tileSize };
const alien1 = aliens[y1][x1];
const alien2 = aliens[y2][x2];
function animateSwap(time) {
const elapsed = time - startTime;
const progress = Math.min(elapsed / swapDuration, 1);
// Clear the current grid section
ctx.clearRect(startPos1.x, startPos1.y, tileSize, tileSize);
ctx.clearRect(startPos2.x, startPos2.y, tileSize, tileSize);
// Interpolating positions for animation
const currentPos1 = {
x: startPos1.x + (startPos2.x - startPos1.x) * progress,
y: startPos1.y + (startPos2.y - startPos1.y) * progress
};
const currentPos2 = {
x: startPos2.x + (startPos1.x - startPos2.x) * progress,
y: startPos2.y + (startPos1.y - startPos2.y) * progress
};
// Draw the moving aliens
ctx.font = '40px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = '#fff';
ctx.fillText(alien1, currentPos1.x + tileSize / 2, currentPos1.y + tileSize / 2);
ctx.fillText(alien2, currentPos2.x + tileSize / 2, currentPos2.y + tileSize / 2);
ctx.strokeStyle = "#fff";
ctx.strokeRect(startPos1.x, startPos1.y, tileSize, tileSize);
ctx.strokeRect(startPos2.x, startPos2.y, tileSize, tileSize);
if (progress < 1) {
requestAnimationFrame(animateSwap);
} else {
// Swap is complete
completeSwap(x1, y1, x2, y2);
animating = false;
}
}
requestAnimationFrame(animateSwap);
}
// Swap tiles without animation and check for matches
function completeSwap(x1, y1, x2, y2) {
const temp = aliens[y1][x1];
aliens[y1][x1] = aliens[y2][x2];
aliens[y2][x2] = temp;
drawGrid();
if (checkForMatches()) {
setTimeout(() => handleMatches(), 500);
} else {
setTimeout(() => {
swapTiles(x1, y1, x2, y2); // Swap back if no match
}, 500);
}
}
// Power-up creation logic based on match lengths (4: Bomb, 5: Rocket, 6: Rainbow)
function handleMatches() {
const tilesToRemove = [];
// Horizontal matches
for (let y = 0; y < gridHeight; y++) {
for (let x = 0; x < gridWidth - 2; x++) {
let matchLength = 1;
while (x + matchLength < gridWidth && aliens[y][x] === aliens[y][x + matchLength]) {
matchLength++;
}
if (matchLength >= 3) {
for (let i = 0; i < matchLength; i++) {
tilesToRemove.push({ x: x + i, y });
}
// Create power-ups dynamically in the middle of the match
if (matchLength === 4) aliens[y][x + Math.floor(matchLength / 2)] = '💣'; // 4: Bomb
else if (matchLength === 5) aliens[y][x + Math.floor(matchLength / 2)] = '🚀'; // 5: Rocket
else if (matchLength >= 6) aliens[y][x + Math.floor(matchLength / 2)] = '🌈'; // 6: Rainbow
x += matchLength - 1;
}
}
}
// Vertical matches
for (let x = 0; x < gridWidth; x++) {
for (let y = 0; y < gridHeight - 2; y++) {
let matchLength = 1;
while (y + matchLength < gridHeight && aliens[y][x] === aliens[y + matchLength][x]) {
matchLength++;
}
if (matchLength >= 3) {
for (let i = 0; i < matchLength; i++) {
tilesToRemove.push({ x, y: y + i });
}
// Create power-ups dynamically in the middle of the match
if (matchLength === 4) aliens[y + Math.floor(matchLength / 2)][x] = '💣';
else if (matchLength === 5) aliens[y + Math.floor(matchLength / 2)][x] = '🚀';
else if (matchLength >= 6) aliens[y + Math.floor(matchLength / 2)][x] = '🌈';
y += matchLength - 1;
}
}
}
// Remove matched tiles and update score
tilesToRemove.forEach(tile => {
aliens[tile.y][tile.x] = null;
});
score += tilesToRemove.length * 10;
document.getElementById('score').textContent = score;
setTimeout(() => {
refillGrid();
drawGrid();
checkForLevelCompletion(); // Check if the target score is reached after matches
if (checkForMatches()) handleMatches();
}, 500);
}
// Handle different power-ups
function handlePowerUp(x, y) {
const powerUp = aliens[y][x];
if (powerUp === '💣') {
clearSurroundingTiles(x, y); // Bomb: 3x3 area
} else if (powerUp === '🌈') {
const targetType = getRandomAlienType(); // Rainbow: Clear all of the same type
clearAllTilesOfType(targetType);
} else if (powerUp === '🚀') {
clearRow(y); // Rocket: Clear the row
}
score += 30; // Increment score for power-up use
document.getElementById('score').textContent = score;
// Redraw and refill grid
drawGrid();
refillGrid();
checkForLevelCompletion(); // Check if the target score is reached after using power-up
}
// Clears a 3x3 area
function clearSurroundingTiles(x, y) {
for (let i = -1; i <= 1; i++) {
for (let j = -1; j <= 1; j++) {
const newX = x + i;
const newY = y + j;
if (newX >= 0 && newX < gridWidth && newY >= 0 && newY < gridHeight) {
aliens[newY][newX] = null;
}
}
}
}
// Clears all tiles of the same type
function clearAllTilesOfType(type) {
for (let y = 0; y < gridHeight; y++) {
for (let x = 0; x < gridWidth; x++) {
if (aliens[y][x] === type) {
aliens[y][x] = null;
}
}
}
}
// Clears an entire row
function clearRow(y) {
for (let x = 0; x < gridWidth; x++) {
aliens[y][x] = null;
}
}
// Check for matches
function checkForMatches() {
let matchFound = false;
// Horizontal matches
for (let y = 0; y < gridHeight; y++) {
for (let x = 0; x < gridWidth - 2; x++) {
let matchLength = 1;
while (x + matchLength < gridWidth && aliens[y][x] === aliens[y][x + matchLength]) {
matchLength++;
}
if (matchLength >= 3) matchFound = true;
}
}
// Vertical matches
for (let x = 0; x < gridWidth; x++) {
for (let y = 0; y < gridHeight - 2; y++) {
let matchLength = 1;
while (y + matchLength < gridHeight && aliens[y][x] === aliens[y + matchLength][x]) {
matchLength++;
}
if (matchLength >= 3) matchFound = true;
}
}
return matchFound;
}
// Refill the grid after clearing
function refillGrid() {
for (let x = 0; x < gridWidth; x++) {
for (let y = gridHeight - 1; y >= 0; y--) {
if (aliens[y][x] === null) {
for (let yAbove = y - 1; yAbove >= 0; yAbove--) {
if (aliens[yAbove][x] !== null) {
aliens[y][x] = aliens[yAbove][x];
aliens[yAbove][x] = null;
break;
}
}
}
}
}
// Generate new tiles at the top
for (let x = 0; x < gridWidth; x++) {
for (let y = 0; y < gridHeight; y++) {
if (aliens[y][x] === null) {
aliens[y][x] = alienTypes[Math.floor(Math.random() * alienTypes.length)];
}
}
}
}
// Utility: Get a random alien type (for color bomb)
function getRandomAlienType() {
return alienTypes[Math.floor(Math.random() * alienTypes.length)];
}
// Start the game timer
function startTimer() {
timerInterval = setInterval(() => {
timeLeft--;
document.getElementById('timer').textContent = timeLeft;
if (timeLeft <= 0) {
clearInterval(timerInterval);
gameOver();
}
}, 1000);
}
// Check if the player has reached the target score
function checkForLevelCompletion() {
if (score >= targetScore) {
clearInterval(timerInterval); // Stop the timer
nextLevel(); // Move to the next level
}
}
// Move to the next level
function nextLevel() {
level++;
targetScore += 50; // Increase the target score for each level
timeLeft = 60; // Reset the time for the next level
document.getElementById('level').textContent = level;
document.getElementById('targetScore').textContent = targetScore;
startTimer(); // Start the timer again
}
// End the game and display "Game Over"
function gameOver() {
document.getElementById('gameOver').style.display = 'block';
clearInterval(timerInterval); // Stop the timer
}
// Initialize the game
function initGame() {
resizeCanvas();
initGrid();
drawGrid();
startTimer();
}
// Resize canvas on window resize
window.addEventListener('resize', () => {
resizeCanvas();
drawGrid(); // Redraw grid on resize
});
// Start the game
initGame();
</script>
</body>
</html>
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.