🐭 Extreme Chase · Smart Cats
by SonicBear35883 lines32.6 KB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>🐭 Extreme Chase · Smart Cats</title>
<style>
* {
box-sizing: border-box;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
body {
margin: 0;
min-height: 100vh;
background: #0b1219;
display: flex;
justify-content: center;
align-items: center;
font-family: system-ui, 'Segoe UI', Roboto, sans-serif;
touch-action: none;
}
.game-wrapper {
background: #1f2a36;
padding: 18px 14px 22px;
border-radius: 48px 48px 32px 32px;
box-shadow: 0 16px 30px rgba(0,0,0,0.8);
max-width: 520px;
width: 100%;
margin: 10px;
}
.status-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 6px 14px 6px;
color: #f2e8d5;
font-weight: bold;
font-size: 1.1rem;
flex-wrap: wrap;
gap: 6px 10px;
}
.status-item {
background: #0f1a24;
padding: 4px 14px;
border-radius: 40px;
box-shadow: inset 0 2px 4px #2d4155;
display: flex;
align-items: center;
gap: 6px;
}
.status-item span {
color: #f7d44a;
}
canvas {
display: block;
width: 100%;
aspect-ratio: 1 / 1;
background: #131f2b;
border-radius: 28px;
box-shadow: inset 0 0 0 2px #2f4459, 0 8px 0 #0d141c;
touch-action: none;
cursor: grab;
}
.controls {
display: flex;
justify-content: center;
gap: 12px;
margin-top: 16px;
padding: 4px 0;
}
.ctrl-btn {
background: #2a3e50;
border: none;
border-bottom: 6px solid #111d28;
color: #f0e6d0;
font-size: 1.4rem;
padding: 10px 18px;
border-radius: 40px;
box-shadow: 0 4px 0 #0a1118;
transition: all 0.06s ease;
touch-action: manipulation;
cursor: pointer;
font-weight: bold;
min-width: 80px;
text-align: center;
}
.ctrl-btn:active {
transform: translateY(4px);
border-bottom-width: 2px;
background: #1d2f3d;
}
.reset-area {
display: flex;
justify-content: center;
margin-top: 12px;
gap: 12px;
}
.action-btn {
background: #3d2c2c;
border-bottom: 6px solid #201717;
padding: 8px 20px;
font-size: 1.1rem;
min-width: 100px;
color: #ffdbb5;
}
.action-btn:active {
transform: translateY(4px);
border-bottom-width: 2px;
}
.message {
text-align: center;
font-size: 1.3rem;
font-weight: bold;
color: #ffd966;
margin-top: 6px;
min-height: 2.4rem;
padding: 0 6px;
}
.swipe-hint {
color: #8aaec9;
font-size: 0.9rem;
text-align: center;
margin-top: 4px;
}
@media (max-width: 480px) {
.ctrl-btn { padding: 6px 12px; min-width: 60px; font-size: 1.2rem; }
.status-bar { font-size: 0.85rem; }
}
</style>
</head>
<body>
<div class="game-wrapper">
<div class="status-bar">
<div class="status-item">🏆 <span id="levelDisplay">1</span></div>
<div class="status-item">⏱️ <span id="timerDisplay">0:45</span></div>
<div class="status-item">🐱 <span id="catCountDisplay">1</span></div>
<div class="status-item">🍎 <span id="pelletCount">0</span></div>
</div>
<canvas id="gameCanvas" width="400" height="400"></canvas>
<div class="controls">
<button class="ctrl-btn" id="resetBtn">↺ restart</button>
<button class="ctrl-btn" id="nextBtn">▶ next</button>
</div>
<div id="messageBox" class="message">👆 swipe to move · survive the time!</div>
<div class="swipe-hint">⬆️ ⬇️ ⬅️ ➡️ swipe on the maze</div>
</div>
<script>
(function(){
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const levelSpan = document.getElementById('levelDisplay');
const timerSpan = document.getElementById('timerDisplay');
const catCountSpan = document.getElementById('catCountDisplay');
const pelletCountSpan = document.getElementById('pelletCount');
const messageBox = document.getElementById('messageBox');
const GRID_SIZE = 21;
const CELL_SIZE = canvas.width / GRID_SIZE;
// ---------- maze: open walls with dead ends ----------
let maze = [];
function generateMaze(rows, cols) {
const m = [];
for (let r = 0; r < rows; r++) {
const row = [];
for (let c = 0; c < cols; c++) {
if (r === 0 || r === rows-1 || c === 0 || c === cols-1) {
row.push(1);
} else {
row.push(1);
}
}
m.push(row);
}
function carve(r, c) {
m[r][c] = 0;
const dirs = [
[0, -2], [0, 2], [-2, 0], [2, 0]
];
for (let i = dirs.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[dirs[i], dirs[j]] = [dirs[j], dirs[i]];
}
for (let d of dirs) {
const nr = r + d[0];
const nc = c + d[1];
if (nr > 0 && nr < rows-1 && nc > 0 && nc < cols-1 && m[nr][nc] === 1) {
m[r + d[0]/2][c + d[1]/2] = 0;
carve(nr, nc);
}
}
}
carve(1, 1);
// add dead ends and loops
for (let i = 0; i < 40; i++) {
const r = 1 + Math.floor(Math.random() * (rows-2));
const c = 1 + Math.floor(Math.random() * (cols-2));
if (m[r][c] === 1) {
m[r][c] = 0;
}
}
// ensure start positions open
m[10][10] = 0;
m[5][5] = 0;
m[15][15] = 0;
m[10][5] = 0;
m[5][10] = 0;
m[15][10] = 0;
m[10][15] = 0;
return m;
}
// ---------- game state ----------
let level = 1;
let timeLeft = 45;
let maxTime = 45;
let gameActive = true;
let winFlag = false;
let timerInterval = null;
let catMoveInterval = null;
let pelletSpawnInterval = null;
let celebrationTimeout = null;
let player = { x: 10, y: 10 };
let cats = [];
let pellets = [];
let showCelebration = false;
// swipe detection
let touchStartX = 0, touchStartY = 0;
let isSwiping = false;
// ---------- helpers ----------
function randomInt(max) { return Math.floor(Math.random() * max); }
function isWalkable(x, y) {
if (x < 0 || x >= GRID_SIZE || y < 0 || y >= GRID_SIZE) return false;
return maze[y][x] === 0;
}
// ---------- spawn cats ----------
function spawnCats(count) {
const newCats = [];
const occupied = new Set();
occupied.add(`${player.x},${player.y}`);
for (let i = 0; i < count * 5 && newCats.length < count; i++) {
const x = 1 + randomInt(GRID_SIZE-2);
const y = 1 + randomInt(GRID_SIZE-2);
if (isWalkable(x, y) && !occupied.has(`${x},${y}`) && !(x === player.x && y === player.y)) {
occupied.add(`${x},${y}`);
newCats.push({ x, y, size: 1.0, pelletsEaten: 0, happy: false });
}
}
while (newCats.length < count) {
const x = 1 + randomInt(GRID_SIZE-2);
const y = 1 + randomInt(GRID_SIZE-2);
if (isWalkable(x, y) && !occupied.has(`${x},${y}`) && !(x === player.x && y === player.y)) {
occupied.add(`${x},${y}`);
newCats.push({ x, y, size: 1.0, pelletsEaten: 0, happy: false });
}
}
return newCats;
}
// ---------- pellet management ----------
function spawnPellets(count) {
const newPellets = [];
const occupied = new Set();
occupied.add(`${player.x},${player.y}`);
for (let cat of cats) {
occupied.add(`${cat.x},${cat.y}`);
}
for (let p of pellets) {
occupied.add(`${p.x},${p.y}`);
}
for (let i = 0; i < count * 5 && newPellets.length < count; i++) {
const x = 1 + randomInt(GRID_SIZE-2);
const y = 1 + randomInt(GRID_SIZE-2);
if (isWalkable(x, y) && !occupied.has(`${x},${y}`)) {
occupied.add(`${x},${y}`);
newPellets.push({ x, y, eaten: false });
}
}
while (newPellets.length < count) {
const x = 1 + randomInt(GRID_SIZE-2);
const y = 1 + randomInt(GRID_SIZE-2);
if (isWalkable(x, y) && !occupied.has(`${x},${y}`)) {
occupied.add(`${x},${y}`);
newPellets.push({ x, y, eaten: false });
}
}
return newPellets;
}
function formatTime(sec) {
const m = Math.floor(sec / 60);
const s = Math.floor(sec % 60);
return `${m}:${s.toString().padStart(2, '0')}`;
}
// ---------- level setup ----------
function setupLevel(lvl) {
maze = generateMaze(GRID_SIZE, GRID_SIZE);
maze[10][10] = 0;
let baseTime = 45 + (lvl - 1) * 10;
if (baseTime > 300) baseTime = 300;
maxTime = baseTime;
timeLeft = maxTime;
// More cats at higher levels: level 1=1, level 2=2, level 3=3, etc.
let catCount = lvl;
if (catCount > 12) catCount = 12;
player.x = 10; player.y = 10;
cats = spawnCats(catCount);
pellets = spawnPellets(15 + level * 4);
winFlag = false;
gameActive = true;
showCelebration = false;
updateUI();
messageBox.textContent = `🐀 level ${lvl} · survive until time ends!`;
messageBox.style.color = '#ffd966';
if (timerInterval) {
clearInterval(timerInterval);
timerInterval = null;
}
timerInterval = setInterval(() => {
if (!gameActive) return;
timeLeft--;
timerSpan.textContent = formatTime(timeLeft);
if (timeLeft <= 0) {
timeLeft = 0;
timerSpan.textContent = '0:00';
if (gameActive) {
gameActive = false;
winFlag = true;
messageBox.textContent = `🎉 YOU SURVIVED! level ${level} completed 🎉`;
messageBox.style.color = '#7bed9f';
clearInterval(timerInterval);
timerInterval = null;
if (catMoveInterval) {
clearInterval(catMoveInterval);
catMoveInterval = null;
}
if (pelletSpawnInterval) {
clearInterval(pelletSpawnInterval);
pelletSpawnInterval = null;
}
drawGame();
}
}
drawGame();
}, 1000);
// Cat movement: extremely fast and smart
if (catMoveInterval) {
clearInterval(catMoveInterval);
catMoveInterval = null;
}
// Very fast: level 1=350ms, level 12=80ms
const speed = Math.max(80, 350 - (level - 1) * 25);
catMoveInterval = setInterval(() => {
if (gameActive) moveCats();
}, speed);
// Spawn new pellets frequently
if (pelletSpawnInterval) {
clearInterval(pelletSpawnInterval);
pelletSpawnInterval = null;
}
pelletSpawnInterval = setInterval(() => {
if (!gameActive) return;
const count = 3 + Math.floor(Math.random() * 4);
const newPellets = spawnPellets(count);
pellets = pellets.concat(newPellets);
updateUI();
drawGame();
}, 6000);
drawGame();
}
function updateUI() {
levelSpan.textContent = level;
catCountSpan.textContent = cats.length;
timerSpan.textContent = formatTime(timeLeft);
const remaining = pellets.filter(p => !p.eaten).length;
pelletCountSpan.textContent = remaining;
}
// ---------- win / lose ----------
function loseGame() {
if (!gameActive) return;
gameActive = false;
winFlag = false;
if (timerInterval) {
clearInterval(timerInterval);
timerInterval = null;
}
if (catMoveInterval) {
clearInterval(catMoveInterval);
catMoveInterval = null;
}
if (pelletSpawnInterval) {
clearInterval(pelletSpawnInterval);
pelletSpawnInterval = null;
}
// Set cats to happy state
for (let cat of cats) {
cat.happy = true;
}
showCelebration = true;
messageBox.textContent = `😸 CATS ARE HAPPY! They caught you! 😸`;
messageBox.style.color = '#ff6b6b';
drawGame();
// Clear celebration after 2 seconds
if (celebrationTimeout) {
clearTimeout(celebrationTimeout);
}
celebrationTimeout = setTimeout(() => {
showCelebration = false;
for (let cat of cats) {
cat.happy = false;
}
drawGame();
}, 2500);
}
// ---------- player movement (swipe) ----------
function tryMovePlayer(dx, dy) {
if (!gameActive) return false;
const nx = player.x + dx;
const ny = player.y + dy;
if (!isWalkable(nx, ny)) return false;
player.x = nx;
player.y = ny;
for (let cat of cats) {
if (cat.x === player.x && cat.y === player.y) {
loseGame();
drawGame();
return true;
}
}
drawGame();
return true;
}
// ---------- cat AI: EXTREMELY SMART pathfinding ----------
function moveCats() {
if (!gameActive) return;
// First, cats eat any pellets they're on
for (let cat of cats) {
for (let p of pellets) {
if (!p.eaten && p.x === cat.x && p.y === cat.y) {
p.eaten = true;
cat.pelletsEaten++;
cat.size = Math.min(4.0, 1.0 + cat.pelletsEaten * 0.12);
updateUI();
break;
}
}
}
// Smart chase using BFS pathfinding for each cat
for (let cat of cats) {
// BFS to find shortest path to player
const queue = [{ x: cat.x, y: cat.y, path: [] }];
const visited = new Set();
visited.add(`${cat.x},${cat.y}`);
let bestPath = null;
let found = false;
while (queue.length > 0 && !found) {
const current = queue.shift();
const dirs = [
{ dx: 0, dy: -1 }, { dx: 0, dy: 1 },
{ dx: -1, dy: 0 }, { dx: 1, dy: 0 }
];
// Shuffle for variety
for (let i = dirs.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[dirs[i], dirs[j]] = [dirs[j], dirs[i]];
}
for (let d of dirs) {
const nx = current.x + d.dx;
const ny = current.y + d.dy;
const key = `${nx},${ny}`;
if (nx === player.x && ny === player.y) {
// Found player!
bestPath = [...current.path, { dx: d.dx, dy: d.dy }];
found = true;
break;
}
if (isWalkable(nx, ny) && !visited.has(key)) {
// Check if another cat is there
let blocked = false;
for (let other of cats) {
if (other === cat) continue;
if (other.x === nx && other.y === ny) { blocked = true; break; }
}
if (!blocked) {
visited.add(key);
queue.push({
x: nx,
y: ny,
path: [...current.path, { dx: d.dx, dy: d.dy }]
});
}
}
}
}
// If we found a path, take the first step
if (bestPath && bestPath.length > 0) {
const firstStep = bestPath[0];
const nx = cat.x + firstStep.dx;
const ny = cat.y + firstStep.dy;
// Double-check the move is valid
if (isWalkable(nx, ny)) {
let blocked = false;
for (let other of cats) {
if (other === cat) continue;
if (other.x === nx && other.y === ny) { blocked = true; break; }
}
if (!blocked) {
cat.x = nx;
cat.y = ny;
continue;
}
}
}
// Fallback: if no path found, move toward player greedily
const dirs = [
{ dx: 0, dy: -1 }, { dx: 0, dy: 1 },
{ dx: -1, dy: 0 }, { dx: 1, dy: 0 }
];
let validMoves = [];
for (let d of dirs) {
const nx = cat.x + d.dx;
const ny = cat.y + d.dy;
if (isWalkable(nx, ny)) {
let blocked = false;
for (let other of cats) {
if (other === cat) continue;
if (other.x === nx && other.y === ny) { blocked = true; break; }
}
if (!blocked) {
validMoves.push({ nx, ny, dx: d.dx, dy: d.dy });
}
}
}
if (validMoves.length > 0) {
validMoves.sort((a, b) => {
const da = Math.abs(a.nx - player.x) + Math.abs(a.ny - player.y);
const db = Math.abs(b.nx - player.x) + Math.abs(b.ny - player.y);
return da - db;
});
const chosen = validMoves[0];
cat.x = chosen.nx;
cat.y = chosen.ny;
}
}
// Check collision after all cats move
for (let cat of cats) {
if (cat.x === player.x && cat.y === player.y) {
loseGame();
drawGame();
return;
}
}
drawGame();
}
// ---------- drawing ----------
function drawGame() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
const wallColors = [
'#4a6a7f', '#5d7f8f', '#6a8f9f', '#7f9faf', '#8fafbf',
'#a0b8c9', '#b0c8d9', '#7a9aad', '#5a7a8f', '#6a8aa0'
];
for (let r = 0; r < GRID_SIZE; r++) {
for (let c = 0; c < GRID_SIZE; c++) {
const x = c * CELL_SIZE, y = r * CELL_SIZE;
if (maze[r][c] === 1) {
const idx = (r + c) % wallColors.length;
ctx.fillStyle = wallColors[idx];
ctx.fillRect(x, y, CELL_SIZE, CELL_SIZE);
ctx.fillStyle = 'rgba(255,255,255,0.10)';
ctx.fillRect(x+2, y+2, CELL_SIZE-4, CELL_SIZE-4);
} else {
ctx.fillStyle = '#1c2b38';
ctx.fillRect(x, y, CELL_SIZE, CELL_SIZE);
ctx.strokeStyle = '#2a3d4d';
ctx.lineWidth = 0.5;
ctx.strokeRect(x, y, CELL_SIZE, CELL_SIZE);
}
}
}
// Draw pellets
for (let p of pellets) {
if (p.eaten) continue;
const x = p.x * CELL_SIZE + CELL_SIZE/2;
const y = p.y * CELL_SIZE + CELL_SIZE/2;
ctx.beginPath();
ctx.arc(x, y, CELL_SIZE/6, 0, Math.PI*2);
ctx.fillStyle = '#ff6b6b';
ctx.shadowColor = '#ff6b6b';
ctx.shadowBlur = 12;
ctx.fill();
ctx.shadowBlur = 0;
}
// Draw cats
for (let cat of cats) {
const x = cat.x * CELL_SIZE, y = cat.y * CELL_SIZE;
const sizeMultiplier = cat.size || 1.0;
const radius = (CELL_SIZE/2.2) * sizeMultiplier;
const isHappy = cat.happy || showCelebration;
ctx.shadowColor = isHappy ? '#ffdd44' : '#ff884d';
ctx.shadowBlur = isHappy ? 30 : 16;
// Body
ctx.fillStyle = isHappy ? '#ffcc00' : '#e67e22';
ctx.beginPath();
ctx.arc(x + CELL_SIZE/2, y + CELL_SIZE/2, radius, 0, Math.PI*2);
ctx.fill();
// Ears
const earOffset = 4 * sizeMultiplier;
ctx.fillStyle = isHappy ? '#ff9900' : '#d35400';
ctx.beginPath();
ctx.moveTo(x + earOffset, y + earOffset);
ctx.lineTo(x + earOffset*3, y - earOffset);
ctx.lineTo(x + earOffset*5, y + earOffset*1.5);
ctx.fill();
ctx.beginPath();
ctx.moveTo(x + CELL_SIZE - earOffset, y + earOffset);
ctx.lineTo(x + CELL_SIZE - earOffset*3, y - earOffset);
ctx.lineTo(x + CELL_SIZE - earOffset*5, y + earOffset*1.5);
ctx.fill();
// Eyes
const eyeSize = 4 + (cat.pelletsEaten * 0.3);
if (isHappy) {
// Happy eyes = curved (^_^)
ctx.fillStyle = '#1a0000';
ctx.shadowBlur = 0;
ctx.beginPath();
ctx.arc(x + CELL_SIZE/2 - 6 - (sizeMultiplier-1)*2, y + CELL_SIZE/2 - 3, eyeSize*0.7, 0, Math.PI);
ctx.fill();
ctx.beginPath();
ctx.arc(x + CELL_SIZE/2 + 6 + (sizeMultiplier-1)*2, y + CELL_SIZE/2 - 3, eyeSize*0.7, 0, Math.PI);
ctx.fill();
// Happy mouth
ctx.strokeStyle = '#1a0000';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(x + CELL_SIZE/2, y + CELL_SIZE/2 + 6, 8, 0, Math.PI);
ctx.stroke();
} else {
// Angry glowing red eyes
ctx.fillStyle = '#ff0000';
ctx.shadowColor = '#ff0000';
ctx.shadowBlur = 25;
ctx.beginPath();
ctx.arc(x + CELL_SIZE/2 - 6 - (sizeMultiplier-1)*2, y + CELL_SIZE/2 - 3, eyeSize, 0, Math.PI*2);
ctx.fill();
ctx.beginPath();
ctx.arc(x + CELL_SIZE/2 + 6 + (sizeMultiplier-1)*2, y + CELL_SIZE/2 - 3, eyeSize, 0, Math.PI*2);
ctx.fill();
// Pupils
ctx.fillStyle = '#1a0000';
ctx.shadowBlur = 0;
ctx.beginPath();
ctx.arc(x + CELL_SIZE/2 - 4 - (sizeMultiplier-1)*2, y + CELL_SIZE/2 - 2, eyeSize*0.5, 0, Math.PI*2);
ctx.fill();
ctx.beginPath();
ctx.arc(x + CELL_SIZE/2 + 8 + (sizeMultiplier-1)*2, y + CELL_SIZE/2 - 2, eyeSize*0.5, 0, Math.PI*2);
ctx.fill();
}
ctx.shadowBlur = 0;
}
// Draw player (rat)
const px = player.x * CELL_SIZE, py = player.y * CELL_SIZE;
ctx.shadowColor = '#b0b0b0';
ctx.shadowBlur = 14;
ctx.fillStyle = '#b0b0b0';
ctx.beginPath();
ctx.ellipse(px + CELL_SIZE/2, py + CELL_SIZE/2, CELL_SIZE/2.3, CELL_SIZE/2.8, 0, 0, Math.PI*2);
ctx.fill();
ctx.strokeStyle = '#8a8a8a';
ctx.lineWidth = 4;
ctx.beginPath();
ctx.moveTo(px+4, py+CELL_SIZE-4);
ctx.quadraticCurveTo(px-6, py+CELL_SIZE+8, px+2, py+CELL_SIZE+14);
ctx.stroke();
ctx.fillStyle = '#d4a0a0';
ctx.beginPath();
ctx.arc(px+6, py+6, 5, 0, Math.PI*2);
ctx.fill();
ctx.beginPath();
ctx.arc(px+CELL_SIZE-6, py+6, 5, 0, Math.PI*2);
ctx.fill();
ctx.fillStyle = 'white';
ctx.beginPath();
ctx.arc(px+8, py+10, 3, 0, Math.PI*2);
ctx.fill();
ctx.beginPath();
ctx.arc(px+CELL_SIZE-8, py+10, 3, 0, Math.PI*2);
ctx.fill();
ctx.fillStyle = '#1a1a1a';
ctx.beginPath();
ctx.arc(px+7, py+9, 1.5, 0, Math.PI*2);
ctx.fill();
ctx.beginPath();
ctx.arc(px+CELL_SIZE-9, py+9, 1.5, 0, Math.PI*2);
ctx.fill();
ctx.shadowBlur = 0;
if (!gameActive && !showCelebration) {
ctx.fillStyle = 'rgba(0,0,0,0.45)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.font = 'bold 28px system-ui';
ctx.fillStyle = winFlag ? '#7bed9f' : '#ff6b6b';
ctx.shadowColor = '#000000';
ctx.shadowBlur = 20;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(winFlag ? '✨ SURVIVED ✨' : '☠️ GAME OVER', canvas.width/2, canvas.height/2-8);
ctx.shadowBlur = 0;
}
}
// ---------- navigation ----------
function resetGame() {
if (timerInterval) {
clearInterval(timerInterval);
timerInterval = null;
}
if (catMoveInterval) {
clearInterval(catMoveInterval);
catMoveInterval = null;
}
if (pelletSpawnInterval) {
clearInterval(pelletSpawnInterval);
pelletSpawnInterval = null;
}
if (celebrationTimeout) {
clearTimeout(celebrationTimeout);
celebrationTimeout = null;
}
showCelebration = false;
level = 1;
setupLevel(level);
}
function nextLevel() {
if (!gameActive && winFlag) {
level++;
setupLevel(level);
} else if (!gameActive && !winFlag) {
setupLevel(level);
} else {
messageBox.textContent = '⚡ survive the current level first!';
setTimeout(() => { messageBox.textContent = `🐀 level ${level} · survive until time ends!`; }, 1200);
}
}
// ---------- swipe controls ----------
function initSwipe() {
canvas.addEventListener('touchstart', (e) => {
e.preventDefault();
const touch = e.touches[0];
touchStartX = touch.clientX;
touchStartY = touch.clientY;
isSwiping = true;
}, { passive: false });
canvas.addEventListener('touchmove', (e) => {
e.preventDefault();
if (!isSwiping || !gameActive) return;
const touch = e.touches[0];
const dx = touch.clientX - touchStartX;
const dy = touch.clientY - touchStartY;
const threshold = 15;
if (Math.abs(dx) < threshold && Math.abs(dy) < threshold) return;
let moveX = 0, moveY = 0;
if (Math.abs(dx) > Math.abs(dy)) {
moveX = dx > 0 ? 1 : -1;
} else {
moveY = dy > 0 ? 1 : -1;
}
touchStartX = touch.clientX;
touchStartY = touch.clientY;
tryMovePlayer(moveX, moveY);
}, { passive: false });
canvas.addEventListener('touchend', (e) => {
e.preventDefault();
isSwiping = false;
}, { passive: false });
// Mouse drag fallback
let mouseDown = false;
let mouseStartX = 0, mouseStartY = 0;
canvas.addEventListener('mousedown', (e) => {
mouseDown = true;
mouseStartX = e.clientX;
mouseStartY = e.clientY;
});
canvas.addEventListener('mousemove', (e) => {
if (!mouseDown || !gameActive) return;
const dx = e.clientX - mouseStartX;
const dy = e.clientY - mouseStartY;
const threshold = 15;
if (Math.abs(dx) < threshold && Math.abs(dy) < threshold) return;
let moveX = 0, moveY = 0;
if (Math.abs(dx) > Math.abs(dy)) {
moveX = dx > 0 ? 1 : -1;
} else {
moveY = dy > 0 ? 1 : -1;
}
mouseStartX = e.clientX;
mouseStartY = e.clientY;
tryMovePlayer(moveX, moveY);
});
canvas.addEventListener('mouseup', () => { mouseDown = false; });
canvas.addEventListener('mouseleave', () => { mouseDown = false; });
}
// ---------- button controls ----------
function initButtons() {
document.getElementById('resetBtn').addEventListener('click', (e) => {
e.preventDefault();
resetGame();
});
document.getElementById('nextBtn').addEventListener('click', (e) => {
e.preventDefault();
nextLevel();
});
canvas.addEventListener('click', () => {
if (!gameActive && !showCelebration) {
if (winFlag) nextLevel();
else resetGame();
}
});
}
// ---------- start ----------
setupLevel(1);
initSwipe();
initButtons();
})();
</script>
</body>
</html>Game Source: 🐭 Extreme Chase · Smart Cats
Creator: SonicBear35
Libraries: none
Complexity: complex (883 lines, 32.6 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: extreme-chase-smart-cats-sonicbear35" to link back to the original. Then publish at arcadelab.ai/publish.