Сабвей Серф | Хомяки-монеты
by SparkHawk26491 lines18.5 KB
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>Сабвей Серф | Хомяки-монеты</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
user-select: none;
}
body {
background: linear-gradient(145deg, #0a2f3a 0%, #05161c 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
font-family: 'Courier New', 'Segoe UI', monospace;
touch-action: manipulation;
}
.game-container {
padding: 20px;
border-radius: 40px;
background: rgba(0,0,0,0.4);
box-shadow: 0 20px 35px rgba(0,0,0,0.5);
}
canvas {
display: block;
margin: 0 auto;
border-radius: 28px;
box-shadow: 0 0 0 4px #f5a623, 0 15px 30px black;
cursor: pointer;
}
.info {
display: flex;
justify-content: space-between;
align-items: baseline;
margin-top: 16px;
background: #1e2a2e;
padding: 12px 25px;
border-radius: 60px;
color: #f7d44a;
text-shadow: 0 2px 0 #6b2e00;
font-weight: bold;
font-size: 1.4rem;
gap: 15px;
flex-wrap: wrap;
}
.score-box, .best-box, .coins-box {
background: #000000aa;
padding: 5px 20px;
border-radius: 40px;
backdrop-filter: blur(4px);
}
.coins-box {
background: #c97e2caa;
color: #ffec80;
}
.controls {
margin-top: 15px;
display: flex;
gap: 15px;
justify-content: center;
}
button {
background: #2c3e2f;
border: none;
color: #f1c40f;
font-size: 1.7rem;
font-weight: bold;
padding: 8px 25px;
border-radius: 50px;
box-shadow: 0 5px 0 #1a2a1a;
cursor: pointer;
transition: 0.07s linear;
font-family: monospace;
}
button:active {
transform: translateY(3px);
box-shadow: 0 2px 0 #1a2a1a;
}
.restart-btn {
background: #9b2c1d;
color: #ffd966;
}
@media (max-width: 700px) {
.info { font-size: 1rem; padding: 8px 12px;}
button { font-size: 1.2rem; padding: 6px 18px;}
}
</style>
</head>
<body>
<div>
<div class="game-container">
<canvas id="gameCanvas" width="800" height="550"></canvas>
<div class="info">
<span>🏃♂️ СКЕЙТБОРД</span>
<span class="score-box">📊 Счёт: <span id="scoreValue">0</span></span>
<span class="coins-box">🐹 Хомяки: <span id="coinsValue">0</span></span>
<span class="best-box">🏆 Рекорд: <span id="bestValue">0</span></span>
</div>
<div class="controls">
<button id="leftBtn">◀ ЛЕВО</button>
<button id="rightBtn">ПРАВО ▶</button>
<button id="restartBtn" class="restart-btn">🔄 НОВАЯ ИГРА</button>
</div>
<p style="text-align:center; margin-top: 12px; color:#bbd4ce; font-weight: bold;">⬅️ ➡️ стрелки | собирай крутящихся хомяков 🐹 | уклоняйся от поездов 🚆</p>
</div>
</div>
<script>
(function(){
// ----- Холст -----
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
// ----- Игровые параметры -----
const LANES = 3;
const LANE_WIDTH = 110;
const BASE_X = 180;
const PLAYER_WIDTH = 42;
const PLAYER_HEIGHT = 52;
const TRAIN_WIDTH = 48;
const TRAIN_HEIGHT = 56;
const HAMSTER_SIZE = 32; // размер хомяка
// ----- Состояние -----
let gameRunning = true;
let score = 0;
let coins = 0; // количество собранных хомяков
let bestScore = localStorage.getItem('subwayBest') ? parseInt(localStorage.getItem('subwayBest')) : 0;
let frame = 0;
let playerLane = 1;
let trains = [];
let hamsters = []; // массив хомяков-монет { lane, y, rotationAngle }
let baseSpeed = 4.2;
let currentSpeed = baseSpeed;
// Вспомогательные функции UI
function updateBestUI() {
document.getElementById('bestValue').innerText = bestScore;
}
function updateScoreUI() {
document.getElementById('scoreValue').innerText = Math.floor(score);
}
function updateCoinsUI() {
document.getElementById('coinsValue').innerText = coins;
}
function endGame() {
if(!gameRunning) return;
gameRunning = false;
let finalScore = Math.floor(score);
if(finalScore > bestScore) {
bestScore = finalScore;
localStorage.setItem('subwayBest', bestScore);
updateBestUI();
}
}
function restartGame() {
gameRunning = true;
score = 0;
coins = 0;
frame = 0;
trains = [];
hamsters = [];
playerLane = 1;
currentSpeed = baseSpeed;
updateScoreUI();
updateCoinsUI();
bestScore = localStorage.getItem('subwayBest') ? parseInt(localStorage.getItem('subwayBest')) : 0;
updateBestUI();
}
// Спавн поезда
function spawnTrain() {
let lane = Math.floor(Math.random() * LANES);
trains.push({
lane: lane,
y: -TRAIN_HEIGHT - Math.random() * 40,
width: TRAIN_WIDTH,
height: TRAIN_HEIGHT
});
}
// Спавн хомяка-монетки (крутящийся)
function spawnHamster() {
let lane = Math.floor(Math.random() * LANES);
hamsters.push({
lane: lane,
y: -HAMSTER_SIZE - Math.random() * 70,
rotation: Math.random() * Math.PI * 2, // начальный угол
size: HAMSTER_SIZE
});
}
function updateGame() {
if(!gameRunning) return;
// сложность + скорость
currentSpeed = baseSpeed + Math.floor(score / 450) * 0.55;
if(currentSpeed > 12) currentSpeed = 12;
// движение поездов
for(let i=0; i<trains.length; i++) {
trains[i].y += currentSpeed;
}
trains = trains.filter(t => t.y < canvas.height + 100);
// движение хомяков
for(let i=0; i<hamsters.length; i++) {
hamsters[i].y += currentSpeed;
// вращение: увеличиваем угол с каждым кадром
hamsters[i].rotation = (hamsters[i].rotation + 0.12) % (Math.PI * 2);
}
hamsters = hamsters.filter(h => h.y < canvas.height + 80);
// спавн (поезда + хомяки)
let spawnRateTrains = Math.max(18, 42 - Math.floor(score / 180));
let spawnRateHamsters = Math.max(12, 28 - Math.floor(score / 130));
if(frame % spawnRateTrains === 0 && gameRunning) {
spawnTrain();
if(score > 700 && Math.random() < 0.25) spawnTrain();
}
if(frame % spawnRateHamsters === 0 && gameRunning) {
spawnHamster();
// иногда два хомяка подряд
if(Math.random() < 0.2 && gameRunning) spawnHamster();
}
// ----- КОЛЛИЗИЯ ИГРОК + ПОЕЗД -----
const playerX = BASE_X + playerLane * LANE_WIDTH + (LANE_WIDTH/2) - PLAYER_WIDTH/2;
const playerRect = {
x: playerX,
y: canvas.height - PLAYER_HEIGHT - 18,
w: PLAYER_WIDTH,
h: PLAYER_HEIGHT
};
for(let i=0; i<trains.length; i++) {
const t = trains[i];
const trainX = BASE_X + t.lane * LANE_WIDTH + (LANE_WIDTH/2) - TRAIN_WIDTH/2;
const trainRect = {
x: trainX,
y: t.y,
w: TRAIN_WIDTH,
h: TRAIN_HEIGHT
};
if(playerRect.x < trainRect.x + trainRect.w &&
playerRect.x + playerRect.w > trainRect.x &&
playerRect.y < trainRect.y + trainRect.h &&
playerRect.y + playerRect.h > trainRect.y) {
endGame();
break;
}
}
// ----- СБОР ХОМЯКОВ (монет) -----
for(let i=0; i<hamsters.length; i++) {
const h = hamsters[i];
const hamsterX = BASE_X + h.lane * LANE_WIDTH + (LANE_WIDTH/2) - HAMSTER_SIZE/2;
const hamsterRect = {
x: hamsterX,
y: h.y,
w: HAMSTER_SIZE,
h: HAMSTER_SIZE
};
if(playerRect.x < hamsterRect.x + hamsterRect.w &&
playerRect.x + playerRect.w > hamsterRect.x &&
playerRect.y < hamsterRect.y + hamsterRect.h &&
playerRect.y + playerRect.h > hamsterRect.y) {
// собираем хомяка!
coins++;
updateCoinsUI();
hamsters.splice(i,1);
i--; // коррекция индекса
}
}
// очки за выживание
if(gameRunning) {
score += 0.27;
updateScoreUI();
}
frame++;
}
// ---- ОТРИСОВКА КРУТЯЩЕГОСЯ ХОМЯКА ----
function drawRotatingHamster(x, y, size, angle) {
ctx.save();
ctx.translate(x + size/2, y + size/2);
ctx.rotate(angle);
// тело хомяка (круглое)
ctx.fillStyle = "#c28a4a";
ctx.beginPath();
ctx.ellipse(0, 0, size/2, size/2.2, 0, 0, Math.PI*2);
ctx.fill();
// брюшко светлое
ctx.fillStyle = "#e7bc8b";
ctx.beginPath();
ctx.ellipse(0, size*0.1, size/2.5, size/3, 0, 0, Math.PI*2);
ctx.fill();
// ушки
ctx.fillStyle = "#aa6f3c";
ctx.beginPath();
ctx.ellipse(-size*0.25, -size*0.3, size*0.22, size*0.2, 0, 0, Math.PI*2);
ctx.fill();
ctx.beginPath();
ctx.ellipse(size*0.25, -size*0.3, size*0.22, size*0.2, 0, 0, Math.PI*2);
ctx.fill();
// глазки
ctx.fillStyle = "#2f1e0f";
ctx.beginPath();
ctx.arc(-size*0.15, -size*0.05, size*0.07, 0, Math.PI*2);
ctx.fill();
ctx.beginPath();
ctx.arc(size*0.15, -size*0.05, size*0.07, 0, Math.PI*2);
ctx.fill();
ctx.fillStyle = "white";
ctx.beginPath();
ctx.arc(-size*0.18, -size*0.09, size*0.025, 0, Math.PI*2);
ctx.fill();
ctx.beginPath();
ctx.arc(size*0.12, -size*0.09, size*0.025, 0, Math.PI*2);
ctx.fill();
// носик
ctx.fillStyle = "#ff6f61";
ctx.beginPath();
ctx.ellipse(0, size*0.08, size*0.09, size*0.07, 0, 0, Math.PI*2);
ctx.fill();
// щёчки розовые
ctx.fillStyle = "#ffaaaa";
ctx.beginPath();
ctx.ellipse(-size*0.24, size*0.12, size*0.09, size*0.06, 0, 0, Math.PI*2);
ctx.fill();
ctx.beginPath();
ctx.ellipse(size*0.24, size*0.12, size*0.09, size*0.06, 0, 0, Math.PI*2);
ctx.fill();
// маленькая монетка сверху (эффект ценности)
ctx.fillStyle = "#f5bc70";
ctx.beginPath();
ctx.ellipse(0, -size*0.28, size*0.15, size*0.1, 0, 0, Math.PI*2);
ctx.fill();
ctx.fillStyle = "#f7d44a";
ctx.font = `${Math.floor(size*0.6)}px monospace`;
ctx.shadowBlur = 2;
ctx.fillText("🐹", -size*0.15, -size*0.22);
ctx.restore();
}
// ---- ОТРИСОВКА ВСЕГО ----
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// фон
const grad = ctx.createLinearGradient(0, 0, 0, canvas.height);
grad.addColorStop(0, "#183c42");
grad.addColorStop(1, "#212f2c");
ctx.fillStyle = grad;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// рельсы
for(let i=0; i<=LANES; i++) {
let railX = BASE_X + i * LANE_WIDTH - 8;
ctx.beginPath();
ctx.lineWidth = 5;
ctx.strokeStyle = "#cab577";
for(let s=-10; s<canvas.height+20; s+=30) {
ctx.beginPath();
ctx.moveTo(railX, s);
ctx.lineTo(railX+12, s+15);
ctx.stroke();
}
ctx.strokeStyle = "#6b4c2c";
ctx.lineWidth = 4;
ctx.beginPath();
ctx.moveTo(railX+2, 0);
ctx.lineTo(railX+2, canvas.height);
ctx.stroke();
}
// шпалы
ctx.fillStyle = "#714623";
for(let i=0; i<LANES; i++) {
for(let y=30; y<canvas.height; y+=48) {
ctx.fillRect(BASE_X + i*LANE_WIDTH + 15, y, 70, 10);
}
}
// поезда
for(let t of trains) {
const trainX = BASE_X + t.lane * LANE_WIDTH + (LANE_WIDTH/2) - TRAIN_WIDTH/2;
ctx.fillStyle = "#3a2819";
ctx.fillRect(trainX, t.y, TRAIN_WIDTH, TRAIN_HEIGHT);
ctx.fillStyle = "#bc9a6c";
ctx.fillRect(trainX+6, t.y+8, TRAIN_WIDTH-12, 12);
ctx.fillStyle = "#e6c394";
ctx.fillRect(trainX+8, t.y+24, 10, 18);
ctx.fillRect(trainX+TRAIN_WIDTH-18, t.y+24, 10, 18);
ctx.fillStyle = "#ffaa33";
ctx.beginPath();
ctx.arc(trainX+TRAIN_WIDTH/2, t.y+12, 7, 0, Math.PI*2);
ctx.fill();
ctx.fillStyle = "#98d9ff";
ctx.fillRect(trainX+12, t.y+32, 24, 16);
}
// ХОМЯКИ (крутящиеся монетки)
for(let h of hamsters) {
const hamsterX = BASE_X + h.lane * LANE_WIDTH + (LANE_WIDTH/2) - HAMSTER_SIZE/2;
drawRotatingHamster(hamsterX, h.y, HAMSTER_SIZE, h.rotation);
}
// ИГРОК
const playerDrawX = BASE_X + playerLane * LANE_WIDTH + (LANE_WIDTH/2) - PLAYER_WIDTH/2;
const playerY = canvas.height - PLAYER_HEIGHT - 18;
ctx.fillStyle = "#1e88e5";
ctx.fillRect(playerDrawX, playerY, PLAYER_WIDTH, PLAYER_HEIGHT);
ctx.fillStyle = "#ffb74d";
ctx.beginPath();
ctx.ellipse(playerDrawX+PLAYER_WIDTH/2, playerY-8, 18, 12, 0, 0, Math.PI*2);
ctx.fill();
ctx.fillStyle = "#4e342e";
ctx.fillRect(playerDrawX+8, playerY+28, 8, 18);
ctx.fillRect(playerDrawX+PLAYER_WIDTH-16, playerY+28, 8, 18);
ctx.fillStyle = "#f5f5dc";
ctx.beginPath();
ctx.arc(playerDrawX+PLAYER_WIDTH/2, playerY+12, 12, 0, Math.PI*2);
ctx.fill();
ctx.fillStyle = "#2c2c2c";
ctx.fillRect(playerDrawX+12, playerY+18, 6, 6);
ctx.fillRect(playerDrawX+PLAYER_WIDTH-18, playerY+18, 6, 6);
ctx.fillStyle = "#d4380d";
ctx.fillRect(playerDrawX-5, playerY+PLAYER_HEIGHT-6, PLAYER_WIDTH+10, 7);
ctx.fillStyle = "#f0a020";
ctx.beginPath();
ctx.arc(playerDrawX-2, playerY+PLAYER_HEIGHT-2, 6, 0, Math.PI*2);
ctx.arc(playerDrawX+PLAYER_WIDTH+2, playerY+PLAYER_HEIGHT-2, 6, 0, Math.PI*2);
ctx.fill();
// конец игры
if(!gameRunning) {
ctx.font = "bold 34monospace";
ctx.fillStyle = "#ffd966";
ctx.shadowBlur = 0;
ctx.fillText("💥 GAME OVER", canvas.width/2-150, canvas.height/2-40);
ctx.font = "22monospace";
ctx.fillStyle = "#f7eac5";
ctx.fillText("нажми «НОВАЯ ИГРА»", canvas.width/2-130, canvas.height/2+30);
}
// красивый счётчик хомяков
ctx.font = "bold 20monospace";
ctx.fillStyle = "#ffdd99";
ctx.fillText("🐹 x"+coins, canvas.width-100, 35);
}
// управление
function moveLeft() { if(gameRunning && playerLane > 0) playerLane--; }
function moveRight() { if(gameRunning && playerLane < LANES-1) playerLane++; }
function handleKey(e) {
if(e.key === 'ArrowLeft') { moveLeft(); e.preventDefault();}
else if(e.key === 'ArrowRight') { moveRight(); e.preventDefault();}
}
function gameLoop() {
updateGame();
draw();
requestAnimationFrame(gameLoop);
}
window.addEventListener('load', () => {
restartGame();
document.getElementById('leftBtn').addEventListener('click', moveLeft);
document.getElementById('rightBtn').addEventListener('click', moveRight);
document.getElementById('restartBtn').addEventListener('click', () => restartGame());
window.addEventListener('keydown', handleKey);
gameLoop();
});
})();
</script>
</body>
</html>Game Source: Сабвей Серф | Хомяки-монеты
Creator: SparkHawk26
Libraries: none
Complexity: complex (491 lines, 18.5 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: -sparkhawk26" to link back to the original. Then publish at arcadelab.ai/publish.