
【ゲーム】JavaScript:39 迷路ゲーム
「🌀 迷路ゲーム 🌀」は、ランダム生成された全3ステージの迷路をプレイヤー(オレンジ色のセル)で攻略し、各ステージのゴール(緑色のセル)を目指すタイムアタック型ゲームです。
矢印キーだけのシンプル操作ながら、ランダム壁配置による毎回異なるルート探索が楽しめます。
遊び方・操作方法
- タイトル画面で 「スタート ▶️」 をクリック
- ゲーム画面に切り替わり、ステージ1の迷路が表示
- 矢印キー(↑↓←→)でオレンジのプレイヤーセルを上下左右に移動
- 迷路のゴール(緑セル)に到達すると次ステージへ自動移行
- 全3ステージをクリアするとタイム計測終了・終了画面へ
ルール
- 壁セル(黒) は通過不可。プレイヤーセルは壁を避けて移動
- 道セル(白) のみ移動可能
- 1ステージあたり必ずスタート(1,1)からゴール(18,18)まで到達可能な迷路を自動生成
- 全3ステージクリアまでの累計タイムを競う
- クリアタイムの上位5位をブラウザの LocalStorage に保存
🎮ゲームプレイ
以下のリンク先から実際にプレイできます。
39 迷路ゲーム
素材のダウンロード
以下のリンクから使用する素材をダウンロードできます。
maze_title.png | maze_bg.png |
---|---|
![]() | ![]() |
ゲーム画面イメージ

プログラム全文(maze.html)
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>🌀 迷路ゲーム 🌀</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
/* ================= 全体リセット ================= */
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
background: url('maze_bg.png') no-repeat center center fixed;
background-size: cover;
font-family: 'Arial', sans-serif;
height: 100vh;
user-select: none;
position: relative;
color: #333;
}
.hidden { display: none; }
/* ================ オーバーレイ共通 ================ */
.overlay {
position: absolute;
top: 50%; left: 50%;
transform: translate(-50%, -50%);
background: rgba(255,255,255,0.95);
padding: 20px;
border-radius: 10px;
width: 700px; max-width: 95%;
box-shadow: 0 4px 8px rgba(0,0,0,0.3);
z-index: 10;
}
/* ================ タイトル画面 ================ */
#title-screen h1 {
font-size: 2em; text-align: center; margin-bottom: 10px;
}
#title-screen .title-image {
display: block; margin: 0 auto 10px; max-width: 80%; height: auto;
}
#title-screen .instructions-title {
text-align: center; font-weight: bold;
font-size: 1.1em; margin-top: 10px;
}
#title-screen .instructions {
text-align: left; margin: 10px 0; line-height: 1.4;
font-size: 0.95em;
}
#title-screen .btn {
display: block; margin: 20px auto 0;
}
/* ================ 共通ボタン ================ */
.btn {
padding: 10px 20px; font-size: 1em; font-weight: bold;
color: #fff; background-color: #007bff;
border: none; border-radius: 5px;
cursor: pointer; transition: background-color .2s, transform .1s;
}
.btn:hover { background-color: #0056b3; transform: translateY(-2px); }
.btn:active { transform: translateY(0); }
/* ================ ゲーム画面 ================ */
#game-screen {
width: 700px; max-width: 95%;
margin: 0 auto; padding-top: 40px;
text-align: center;
}
#game-screen .game-container {
background: rgba(255,255,255,0.85);
padding: 20px; border-radius: 10px;
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
display: inline-block;
}
#status-message {
margin-bottom: 10px; padding: 8px;
background: rgba(0,0,0,0.7); color: #fff;
border-radius: 5px; font-size: 1em;
}
.maze-grid {
display: grid;
grid-template-columns: repeat(20, 30px);
grid-template-rows: repeat(20, 30px);
gap: 2px; background: #000; padding: 2px;
}
.cell {
width: 30px; height: 30px; background: #fff;
}
.cell.wall { background: #333; }
.cell.player { background: #ff5722; }
.cell.goal { background: #4caf50; }
/* ================ 終了画面 ================ */
#end-screen { text-align: center; }
#time-message { font-size: 1.3em; margin: 10px 0; }
#ranking {
text-align: center; margin: 20px 0;
}
#ranking strong {
display: block; font-size: 1.2em; margin-bottom: 10px;
}
#ranking-list {
list-style: none; padding: 0; margin: 0 auto;
display: inline-block;
}
#ranking-list li {
font-size: 1.2em; margin: 5px 0;
}
#end-back-btn {
display: block; margin: 20px auto 0;
}
</style>
</head>
<body>
<!-- タイトル画面 -->
<div id="title-screen" class="overlay">
<img src="maze_title.png" alt="迷路ゲーム タイトル" class="title-image">
<h1>🌀 迷路ゲーム 🌀</h1>
<div class="instructions-title">📖 遊び方・ルール</div>
<div class="instructions">
<p>・矢印キーでプレイヤー(オレンジ)を操作します。</p>
<p>・壁(黒)を避けてゴール(緑)を目指してください。</p>
<p>・全3つの迷路をクリアするとゲームクリア!</p>
<p>・開始から最終クリアまでの時間を計測し、上位5位を保存します。</p>
</div>
<button id="start-btn" class="btn">スタート ▶️</button>
</div>
<!-- ゲーム画面 -->
<div id="game-screen" class="hidden">
<div class="game-container">
<div id="status-message">矢印キーで移動してください (1/3)</div>
<div id="maze" class="maze-grid"></div>
</div>
</div>
<!-- 終了画面 -->
<div id="end-screen" class="overlay hidden">
<h1>🏁 全クリア! 🏁</h1>
<p id="time-message"></p>
<div id="ranking">
<strong>🏆 ランキング (上位5位)</strong>
<ol id="ranking-list"></ol>
</div>
<button id="end-back-btn" class="btn">タイトル画面に戻る ↩️</button>
</div>
<script>
// ================= 定数・状態 =================
const ROWS = 20, COLS = 20;
const LEVELS = 3;
let mazes = [], level = 0;
let playerPos, startTime;
const titleScreen = document.getElementById('title-screen');
const gameScreen = document.getElementById('game-screen');
const endScreen = document.getElementById('end-screen');
const startBtn = document.getElementById('start-btn');
const endBackBtn = document.getElementById('end-back-btn');
const mazeEl = document.getElementById('maze');
const statusEl = document.getElementById('status-message');
const timeMsgEl = document.getElementById('time-message');
const rankingList = document.getElementById('ranking-list');
/**
* タイトル画面表示
*/
function showTitle() {
titleScreen.classList.remove('hidden');
gameScreen.classList.add('hidden');
endScreen.classList.add('hidden');
}
/**
* ゲーム開始
*/
function showGame() {
titleScreen.classList.add('hidden');
gameScreen.classList.remove('hidden');
endScreen.classList.add('hidden');
// 3つの迷路を生成
mazes = [];
for (let i = 0; i < LEVELS; i++) {
mazes.push(generateMaze());
}
level = 0;
startTime = performance.now();
startLevel();
}
/**
* 各レベル開始
*/
function startLevel() {
// 初期位置とゴール
playerPos = { x:1, y:1 };
renderMaze();
statusEl.textContent =
`レベル ${level+1} / ${LEVELS} へようこそ`;
// 少し待って案内
setTimeout(() => {
statusEl.textContent =
`矢印キーで移動してください (${level+1}/${LEVELS})`;
document.addEventListener('keydown', onKeyDown);
}, 500);
}
/**
* 終了画面表示
*/
function showEnd(elapsed) {
gameScreen.classList.add('hidden');
endScreen.classList.remove('hidden');
timeMsgEl.textContent = `総クリアタイム: ${elapsed.toFixed(2)} 秒`;
updateRanking(elapsed);
renderRanking();
}
/**
* ランダム迷路生成 (border walls + ランダム内壁 調整)
*/
function generateMaze() {
// 0=道,1=壁
let m = Array.from({length:ROWS}, () => Array(COLS).fill(0));
// 周囲を壁
for (let y=0; y<ROWS; y++) {
for (let x=0; x<COLS; x++) {
if (y===0||y===ROWS-1||x===0||x===COLS-1) m[y][x]=1;
else m[y][x] = Math.random() < 0.25 ? 1 : 0;
}
}
// 開始/ゴールは道
m[1][1] = 0;
m[ROWS-2][COLS-2] = 0;
// 通路確保
if (!isReachable(m)) return generateMaze();
return m;
}
/**
* ゴール到達可能か BFS
*/
function isReachable(m) {
let visited = Array.from({length:ROWS}, ()=>Array(COLS).fill(false));
let q = [{x:1,y:1}]; visited[1][1]=true;
while (q.length) {
let {x,y} = q.shift();
if (x===COLS-2 && y===ROWS-2) return true;
[[1,0],[-1,0],[0,1],[0,-1]].forEach(([dx,dy])=>{
let nx=x+dx, ny=y+dy;
if (nx>0&&nx< COLS-1 && ny>0&&ny< ROWS-1 &&
!visited[ny][nx] && m[ny][nx]===0) {
visited[ny][nx]=true;
q.push({x:nx,y:ny});
}
});
}
return false;
}
/**
* 迷路描画
*/
function renderMaze() {
mazeEl.innerHTML='';
const m = mazes[level];
for (let y=0; y<ROWS; y++){
for (let x=0; x<COLS; x++){
const cell = document.createElement('div');
cell.classList.add('cell');
if (m[y][x]===1) cell.classList.add('wall');
if (x===playerPos.x && y===playerPos.y) cell.classList.add('player');
if (x===COLS-2 && y===ROWS-2) cell.classList.add('goal');
mazeEl.appendChild(cell);
}
}
}
/**
* 矢印キー押下
*/
function onKeyDown(e) {
let nx=playerPos.x, ny=playerPos.y;
if (e.key==='ArrowUp') ny--;
else if (e.key==='ArrowDown') ny++;
else if (e.key==='ArrowLeft') nx--;
else if (e.key==='ArrowRight') nx++;
else return;
const m = mazes[level];
if (m[ny][nx]===0 || (nx===COLS-2 && ny===ROWS-2)) {
playerPos={x:nx,y:ny};
renderMaze();
if (nx===COLS-2 && ny===ROWS-2) {
document.removeEventListener('keydown', onKeyDown);
statusEl.textContent = `レベル ${level+1} クリア!`;
level++;
if (level<LEVELS) {
setTimeout(() => startLevel(), 1000);
} else {
const elapsed = (performance.now()-startTime)/1000;
setTimeout(()=>showEnd(elapsed), 500);
}
}
}
}
/**
* ランキング更新
*/
function updateRanking(time) {
let ranking = JSON.parse(localStorage.getItem('maze_ranking')||'[]');
ranking.push(time);
ranking.sort((a,b)=>a-b);
ranking = ranking.slice(0,5);
localStorage.setItem('maze_ranking', JSON.stringify(ranking));
}
/**
* ランキング描画
*/
function renderRanking() {
rankingList.innerHTML='';
const ranking = JSON.parse(localStorage.getItem('maze_ranking')||'[]');
ranking.forEach((t,i)=>{
const li=document.createElement('li');
li.textContent=`${i+1}位: ${t.toFixed(2)} 秒`;
rankingList.appendChild(li);
});
}
// ========== イベント登録 ==========
startBtn.addEventListener('click', showGame);
endBackBtn.addEventListener('click', showTitle);
document.addEventListener('DOMContentLoaded', showTitle);
</script>
</body>
</html>
アルゴリズムの流れ
ステップ | 処理内容 |
---|---|
1 | タイトル画面 → 「スタート」クリックで showGame() を実行 |
2 | showGame() :レベル用に3つの迷路を生成し startLevel() |
3 | startLevel() :プレイヤー初期位置設定・描画・キーイベント登録 |
4 | 矢印キー押下 → onKeyDown() :衝突判定後に移動・再描画 |
5 | ゴール到達 → レベルクリア or 全レベルクリア判定 |
6 | クリア → 経過時間を計測し showEnd() で表示・ランキング登録 |
関数の詳細解説
関数名 | 役割 |
---|---|
generateMaze() | 周囲を壁にし、内部はランダム壁配置 → 通路確保できるまで再生成 |
isReachable(m) | BFS でスタート→ゴール間の到達可能性を確認 |
renderMaze() | <div class="cell"> を動的生成し、壁・プレイヤー・ゴールを描画 |
onKeyDown(e) | 矢印キーに応じて次セル判定→移動→クリア判定 |
updateRanking(t) | LocalStorage からタイムを読み込み、ソート&上位5位保存 |
renderRanking() | 保存されたランキングを読み込み、HTMLでリスト表示 |
改造のポイント
- 迷路形状の変更:壁の出現確率や範囲チェックを調整して難易度変更
- ステージ数・サイズ:
LEVELS
やROWS
/COLS
を変更し、短い/長い迷路に対応 - 視覚演出:CSS アニメーションでプレイヤー移動時にスムーズなトランジションを追加
- タイマーモード:ステージごとのクリアタイム&合計タイムの同時表示
- ダッシュ機能:Shiftキー長押しで一時的に高速移動(通路のみ)を実装
シンプルながら毎回異なる体験ができる迷路ゲーム。ぜひコードを読み解き、自分好みに改造してみてください!