食べて大きくなれ!スネークゲーム
katayamawp
コード公開
CSS
#snake-game * {
box-sizing: border-box;
}
.game-container {
font-family: "Hiragino Kaku Gothic Pro", "メイリオ", sans-serif;
max-width: 600px;
margin: 0 auto;
padding: 20px;
background: linear-gradient(135deg, #1a2a6c, #b21f1f, #fdbb2d);
background-size: 400% 400%;
animation: gradientBG 15s ease infinite;
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
}
@keyframes gradientBG {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
.game-header {
text-align: center;
margin-bottom: 20px;
color: white;
}
.game-header h2 {
margin-bottom: 15px;
font-size: 32px;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
}
.score-info {
display: flex;
justify-content: center;
gap: 30px;
}
.score-container,
.highscore-container {
background-color: rgba(255, 255, 255, 0.9);
padding: 8px 20px;
border-radius: 50px;
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2);
}
.score-label {
font-weight: bold;
color: #333;
}
.score-value {
font-weight: bold;
color: #e74c3c;
font-size: 18px;
}
.game-board-container {
position: relative;
width: 100%;
max-width: 400px;
height: 400px;
margin: 0 auto;
border-radius: 5px;
overflow: hidden;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
}
#gameBoard {
background-color: #f8f8f8;
display: block;
}
.overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.7);
display: flex;
justify-content: center;
align-items: center;
z-index: 10;
}
.overlay-content {
background-color: white;
padding: 30px;
border-radius: 10px;
text-align: center;
max-width: 80%;
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.3);
}
.overlay-content h3 {
margin-bottom: 15px;
color: #2c3e50;
font-size: 24px;
}
.overlay-content p {
margin-bottom: 15px;
color: #34495e;
font-size: 16px;
}
.btn {
background-color: #2ecc71;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
font-weight: bold;
transition: all 0.3s ease;
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.1);
}
.btn:hover {
background-color: #27ae60;
transform: translateY(-2px);
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
}
.btn:active {
transform: translateY(1px);
}
.hidden {
display: none;
}
.game-controls {
margin-top: 20px;
padding: 15px;
background-color: rgba(255, 255, 255, 0.9);
border-radius: 10px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
}
.control-info {
text-align: center;
margin-bottom: 15px;
color: #34495e;
font-weight: bold;
}
.mobile-controls {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
margin-bottom: 15px;
}
.mobile-controls-row {
display: flex;
gap: 10px;
justify-content: center;
}
.control-btn {
width: 60px;
height: 60px;
background-color: #3498db;
color: white;
border: none;
border-radius: 10px;
font-size: 24px;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.1);
transition: all 0.2s ease;
user-select: none;
}
.control-btn:active {
background-color: #2980b9;
transform: translateY(2px);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.game-options {
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 20px;
margin-top: 15px;
}
.speed-control,
.grid-control {
display: flex;
align-items: center;
gap: 8px;
}
.select-css {
padding: 6px 10px;
border: 1px solid #bdc3c7;
border-radius: 5px;
font-size: 14px;
background-color: white;
}
.select-css:focus {
outline: none;
border-color: #3498db;
}
/* レスポンシブデザイン */
@media (max-width: 600px) {
.game-container {
padding: 10px;
}
.game-header h2 {
font-size: 24px;
}
.game-board-container {
height: 300px;
}
#gameBoard {
width: 300px;
height: 300px;
}
.mobile-controls {
display: flex;
}
.control-btn {
width: 50px;
height: 50px;
font-size: 20px;
}
.score-info {
flex-direction: column;
gap: 10px;
align-items: center;
}
}
JS
document.addEventListener('DOMContentLoaded', function() {
// キャンバスの設定
const canvas = document.getElementById('gameBoard');
const ctx = canvas.getContext('2d');
// DOM要素
const startButton = document.getElementById('startButton');
const restartButton = document.getElementById('restartButton');
const overlay = document.getElementById('overlay');
const gameOverOverlay = document.getElementById('gameOverOverlay');
const scoreElement = document.getElementById('score');
const highScoreElement = document.getElementById('highScore');
const finalScoreElement = document.getElementById('finalScore');
const gameSpeedSelect = document.getElementById('gameSpeed');
const showGridCheckbox = document.getElementById('showGrid');
// モバイルコントロール
const upButton = document.getElementById('upButton');
const leftButton = document.getElementById('leftButton');
const downButton = document.getElementById('downButton');
const rightButton = document.getElementById('rightButton');
// ゲーム設定
const gameConfig = {
gridSize: 20, // グリッドの一区画のサイズ
gridWidth: canvas.width / 20,
gridHeight: canvas.height / 20,
initialSnakeLength: 3,
speeds: {
slow: 150,
normal: 100,
fast: 70
}
};
// ゲーム状態
let gameState = {
snake: [],
direction: 'right',
nextDirection: 'right',
food: {},
gameActive: false,
score: 0,
highScore: localStorage.getItem('snakeHighScore') || 0,
speed: gameConfig.speeds.normal,
showGrid: true,
gameLoopTimeout: null
};
// 高得点を初期表示
highScoreElement.textContent = gameState.highScore;
// ゲーム初期化
function initGame() {
// 蛇の初期位置
const centerX = Math.floor(gameConfig.gridWidth / 2);
const centerY = Math.floor(gameConfig.gridHeight / 2);
gameState.snake = [];
for (let i = 0; i < gameConfig.initialSnakeLength; i++) {
gameState.snake.push({
x: centerX - i,
y: centerY
});
}
// 方向の初期化
gameState.direction = 'right';
gameState.nextDirection = 'right';
// スコアの初期化
gameState.score = 0;
scoreElement.textContent = '0';
// 最初の餌の配置
placeFood();
// スピード設定の適用
gameState.speed = gameConfig.speeds[gameSpeedSelect.value];
// グリッド表示設定の適用
gameState.showGrid = showGridCheckbox.checked;
// ゲーム状態の更新
gameState.gameActive = true;
// メインゲームループの開始
if (gameState.gameLoopTimeout) {
clearTimeout(gameState.gameLoopTimeout);
}
gameLoop();
}
// 餌の配置
function placeFood() {
const validPosition = false;
let foodPos;
// 蛇の体と重ならない位置に餌を配置
while (!validPosition) {
foodPos = {
x: Math.floor(Math.random() * gameConfig.gridWidth),
y: Math.floor(Math.random() * gameConfig.gridHeight)
};
// 蛇の体と重なっていないかチェック
if (!gameState.snake.some(segment => segment.x === foodPos.x && segment.y === foodPos.y)) {
break;
}
}
gameState.food = foodPos;
}
// ゲームのメインループ
function gameLoop() {
if (!gameState.gameActive) return;
// 蛇の移動
moveSnake();
// 衝突のチェック
if (checkCollision()) {
gameOver();
return;
}
// 餌を食べたかチェック
checkFood();
// 画面の描画
drawGame();
// 次のフレーム
gameState.gameLoopTimeout = setTimeout(gameLoop, gameState.speed);
}
// 蛇の移動
function moveSnake() {
// 次のフレームで向かう方向を現在の方向に設定
gameState.direction = gameState.nextDirection;
// 頭の現在位置をコピー
const head = {
...gameState.snake[0]
};
// 方向に応じて頭の位置を更新
switch (gameState.direction) {
case 'up':
head.y--;
break;
case 'down':
head.y++;
break;
case 'left':
head.x--;
break;
case 'right':
head.x++;
break;
}
// 新しい頭の位置を追加
gameState.snake.unshift(head);
// 餌を食べていない場合、尻尾を取り除く
const foodEaten = head.x === gameState.food.x && head.y === gameState.food.y;
if (!foodEaten) {
gameState.snake.pop();
}
}
// 衝突のチェック
function checkCollision() {
const head = gameState.snake[0];
// 壁との衝突チェック
if (head.x < 0 || head.x >= gameConfig.gridWidth ||
head.y < 0 || head.y >= gameConfig.gridHeight) {
return true;
}
// 自分の体との衝突チェック
for (let i = 1; i < gameState.snake.length; i++) {
if (head.x === gameState.snake[i].x && head.y === gameState.snake[i].y) {
return true;
}
}
return false;
}
// 餌のチェック
function checkFood() {
const head = gameState.snake[0];
// 頭が餌の位置と一致したら
if (head.x === gameState.food.x && head.y === gameState.food.y) {
// スコアを増やす
gameState.score += 10;
scoreElement.textContent = gameState.score;
// 新しい餌を配置
placeFood();
}
}
// ゲームオーバー
function gameOver() {
gameState.gameActive = false;
// ハイスコアの更新
if (gameState.score > gameState.highScore) {
gameState.highScore = gameState.score;
localStorage.setItem('snakeHighScore', gameState.highScore);
highScoreElement.textContent = gameState.highScore;
}
// ゲームオーバー画面の表示
finalScoreElement.textContent = gameState.score;
gameOverOverlay.classList.remove('hidden');
}
// ゲームの描画
function drawGame() {
// キャンバスをクリア
ctx.clearRect(0, 0, canvas.width, canvas.height);
// グリッドの描画(オプション)
if (gameState.showGrid) {
drawGrid();
}
// 蛇の描画
drawSnake();
// 餌の描画
drawFood();
}
// グリッドの描画
function drawGrid() {
ctx.strokeStyle = 'rgba(0, 0, 0, 0.1)';
for (let x = 0; x <= canvas.width; x += gameConfig.gridSize) {
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, canvas.height);
ctx.stroke();
}
for (let y = 0; y <= canvas.height; y += gameConfig.gridSize) {
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(canvas.width, y);
ctx.stroke();
}
}
// 蛇の描画
function drawSnake() {
ctx.fillStyle = '#2ecc71';
for (const segment of gameState.snake) {
ctx.fillRect(
segment.x * gameConfig.gridSize,
segment.y * gameConfig.gridSize,
gameConfig.gridSize,
gameConfig.gridSize
);
}
}
// 餌の描画
function drawFood() {
ctx.fillStyle = '#e74c3c';
ctx.beginPath();
const x = gameState.food.x * gameConfig.gridSize + gameConfig.gridSize / 2;
const y = gameState.food.y * gameConfig.gridSize + gameConfig.gridSize / 2;
const radius = gameConfig.gridSize / 2 - 2;
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.fill();
}
// キーボード操作
document.addEventListener('keydown', e => {
const key = e.key.toLowerCase();
const newDir = {
'arrowup': 'up',
'w': 'up',
'arrowdown': 'down',
's': 'down',
'arrowleft': 'left',
'a': 'left',
'arrowright': 'right',
'd': 'right'
} [key];
if (newDir && isValidDirection(newDir)) {
gameState.nextDirection = newDir;
}
});
// 有効な方向かどうか(逆走防止)
function isValidDirection(newDir) {
const opposite = {
'up': 'down',
'down': 'up',
'left': 'right',
'right': 'left'
};
return gameState.direction !== opposite[newDir];
}
// モバイルボタン操作
upButton.addEventListener('click', () => updateDirection('up'));
downButton.addEventListener('click', () => updateDirection('down'));
leftButton.addEventListener('click', () => updateDirection('left'));
rightButton.addEventListener('click', () => updateDirection('right'));
function updateDirection(dir) {
if (isValidDirection(dir)) {
gameState.nextDirection = dir;
}
}
// スタートボタンと再スタートボタンのイベント
startButton.addEventListener('click', () => {
overlay.classList.add('hidden');
initGame();
});
restartButton.addEventListener('click', () => {
gameOverOverlay.classList.add('hidden');
initGame();
});
document.addEventListener('keydown', function(e) {
if (!gameState.gameActive) return;
const keysToPrevent = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'w', 'a', 's', 'd', 'W', 'A', 'S', 'D'];
if (keysToPrevent.includes(e.key)) {
e.preventDefault();
}
}, {
passive: false
});
});