筏作りゲーム

Pasted Oct 15, 2026, 2:32 AM1 file386 lines

Scan to open on your phone

Point a camera at the QR code - it opens this exact paste, no app needed.

https://codepastes.com/zzgsw5wxe1w0mvjj
無題
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>リバークラフト・アドベンチャー</title>
    <style>
        body {
            margin: 0;
            background: #1a1a2e;
            color: #fff;
            font-family: sans-serif;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            min-height: 100vh;
            overflow: hidden;
        }
        h1 { margin: 5px 0; font-size: 24px; color: #4ecca3; }
        #game-container {
            position: relative;
            box-shadow: 0 10px 30px rgba(0,0,0,0.5);
            background: #16213e;
            border-radius: 8px;
            overflow: hidden;
        }
        canvas { display: block; background: #0f3460; cursor: pointer; }
        .ui-panel {
            display: flex;
            gap: 15px;
            margin-top: 10px;
            background: #1f4068;
            padding: 10px 20px;
            border-radius: 8px;
            align-items: center;
        }
        button {
            background: #e94560;
            color: white;
            border: none;
            padding: 8px 16px;
            font-size: 16px;
            font-weight: bold;
            border-radius: 4px;
            cursor: pointer;
            transition: 0.2s;
        }
        button:hover { background: #ff6b6b; }
        button:disabled { background: #555; cursor: not-allowed; }
        .palette {
            display: flex;
            gap: 8px;
        }
        .palette-btn {
            background: #0f3460;
            border: 2px solid #4ecca3;
            padding: 6px 12px;
            font-size: 14px;
            border-radius: 4px;
        }
        .palette-btn.active {
            background: #4ecca3;
            color: #1a1a2e;
        }
    </style>
</head>
<body>

    <h1>リバークラフト・アドベンチャー</h1>
    
    <div id="game-container">
        <canvas id="cvs" width="800" height="600"></canvas>
    </div>

    <div class="ui-panel" id="build-ui">
        <div>パーツ選択:</div>
        <div class="palette" id="palette"></div>
        <button onclick="startSailing()">出港!</button>
    </div>

    <div class="ui-panel" id="sailing-ui" style="display:none;">
        <button onclick="triggerRocket()">🚀 ロケット点火 (Space)</button>
        <button onclick="returnToBuild()" style="background:#e8a87c;">リタイアして改修</button>
    </div>

<script>
const cvs = document.getElementById('cvs');
const ctx = cvs.getContext('2d');

// ゲーム状態
let state = 'BUILD'; // BUILD, SAIL, GAMEOVER
let money = 100;
let distance = 0;
let boatParts = []; // {x, y, type}
let activePart = 'wood';
let isRocketActive = false;
let riverScroll = 0;

// ボートのグリッド (幅9 x 高さ9)
const GRID_SIZE = 40;
const GRID_W = 9;
const GRID_H = 9;
const OFFSET_X = (800 - GRID_W * GRID_SIZE) / 2;
const OFFSET_Y = 120;

// パーツ定義
const PARTS = {
    wood: { name: '木ブロック', color: '#8b5a2b', hp: 50, weight: 10, cost: 10, desc: '標準的な船体ブロック' },
    iron: { name: '鉄ブロック', color: '#708090', hp: 150, weight: 30, cost: 30, desc: '高耐久・重いブロック' },
    engine: { name: 'エンジン', color: '#ff4500', hp: 30, weight: 15, cost: 50, desc: '前方に推進力を生む' },
    rocket: { name: 'ロケット', color: '#dc143c', hp: 20, weight: 10, cost: 70, desc: '一時的に超加速する' },
    balloon: { name: '風船', color: '#00bfff', hp: 10, weight: -20, cost: 40, desc: '船体を軽くする(低強度)' },
    turret: { name: 'タレット', color: '#32cd32', hp: 40, weight: 20, cost: 100, desc: '前方の障害物を自動攻撃する' }
};

// 初期パーツ配置(中心に木ブロック1つ)
boatParts = [{ x: 4, y: 4, type: 'wood' }];

// パレット生成
function initPalette() {
    const paletteDiv = document.getElementById('palette');
    paletteDiv.innerHTML = '';
    for (let key in PARTS) {
        const p = PARTS[key];
        const btn = document.createElement('button');
        btn.className = 'palette-btn' + (activePart === key ? ' active' : '');
        btn.innerHTML = `${p.name} ($${p.cost})`;
        btn.onclick = () => {
            activePart = key;
            document.querySelectorAll('.palette-btn').forEach(b => b.classList.remove('active'));
            btn.classList.add('active');
        };
        paletteDiv.appendChild(btn);
    }
}
initPalette();

// グリッドクリック(パーツ配置/削除)
cvs.addEventListener('click', (e) => {
    if (state !== 'BUILD') return;
    const rect = cvs.getBoundingClientRect();
    const mx = e.clientX - rect.left;
    const my = e.clientY - rect.top;

    const gx = Math.floor((mx - OFFSET_X) / GRID_SIZE);
    const gy = Math.floor((my - OFFSET_Y) / GRID_SIZE);

    if (gx >= 0 && gx < GRID_W && gy >= 0 && gy < GRID_H) {
        // 既存パーツがあるか確認
        const index = boatParts.findIndex(p => p.x === gx && p.y === gy);
        if (index >= 0) {
            // 削除(ただし最後の1つは消せない)
            if (boatParts.length > 1) {
                boatParts.splice(index, 1);
            }
        } else {
            // 配置
            const cost = PARTS[activePart].cost;
            if (money >= cost) {
                money -= cost;
                boatParts.push({ x: gx, y: gy, type: activePart });
            } else {
                alert('お金が足りません!');
            }
        }
    }
});

// セイリング開始
function startSailing() {
    if (boatParts.length === 0) return;
    state = 'SAIL';
    distance = 0;
    riverScroll = 0;
    document.getElementById('build-ui').style.display = 'none';
    document.getElementById('sailing-ui').style.display = 'flex';

    // 障害物や敵の初期化
    obstacles = [];
    spawnTimer = 0;
}

function returnToBuild() {
    state = 'BUILD';
    document.getElementById('build-ui').style.display = 'flex';
    document.getElementById('sailing-ui').style.display = 'none';
    initPalette();
}

function triggerRocket() {
    isRocketActive = true;
    setTimeout(() => { isRocketActive = false; }, 2000);
}

// 障害物管理
let obstacles = [];
let spawnTimer = 0;

// ゲームループ
let boatX = 400;
let boatY = 450;
let velocityY = 0;

function update() {
    if (state === 'SAIL') {
        distance += 0.1;
        riverScroll += 3 + (isRocketActive ? 5 : 0);

        // スキル・パーツ効果計算
        let totalWeight = 0;
        let enginePower = 0;
        let hasTurret = false;

        boatParts.forEach(p => {
            const def = PARTS[p.type];
            totalWeight += def.weight;
            if (p.type === 'engine') enginePower += 1.5;
            if (p.type === 'turret') hasTurret = true;
        });

        // 障害物スポーン
        spawnTimer++;
        if (spawnTimer > 40) {
            spawnTimer = 0;
            obstacles.push({
                x: OFFSET_X + Math.random() * (GRID_W * GRID_SIZE - 30),
                y: -50,
                w: 40,
                h: 40,
                hp: 40,
                type: Math.random() < 0.3 ? 'log' : 'rock'
            });
        }

        // 障害物移動
        obstacles.forEach(obs => {
            obs.y += 3;
            // タレット攻撃
            if (hasTurret && obs.y > 100 && obs.y < 450) {
                obs.hp -= 1.5;
            }
        });
        obstacles = obstacles.filter(obs => obs.y < 650 && obs.hp > 0);

        // 衝突判定簡易処理(中央付近の船体と接触)
        const bRect = { x: boatX - 40, y: boatY - 40, w: 80, h: 80 };
        obstacles.forEach(obs => {
            if (obs.y + obs.h > bRect.y && obs.y < bRect.y + bRect.h &&
                obs.x + obs.w > bRect.x && obs.x < bRect.x + bRect.w) {
                // ダメージ:一番外側のパーツが壊れるなど
                if (boatParts.length > 0) {
                    boatParts.pop(); // 被弾するとパーツが剥がれる
                    obs.y = 800; // 障害物を消す
                } else {
                    gameOver();
                }
            }
        });

        if (boatParts.length === 0) {
            gameOver();
        }
    }
}

function gameOver() {
    state = 'GAMEOVER';
    const earned = Math.floor(distance * 5);
    money += earned;
    alert(`船が破壊されました!\n進んだ距離: ${Math.floor(distance)}m\n獲得賞金: $${earned}`);
    returnToBuild();
}

function draw() {
    ctx.clearRect(0, 0, cvs.width, cvs.height);

    if (state === 'BUILD') {
        // ビルドモードの描画
        ctx.fillStyle = '#0f3460';
        ctx.fillRect(0, 0, cvs.width, cvs.height);

        // グリッド背景
        ctx.strokeStyle = '#1f4068';
        ctx.lineWidth = 1;
        for (let x = 0; x <= GRID_W; x++) {
            ctx.beginPath();
            ctx.moveTo(OFFSET_X + x * GRID_SIZE, OFFSET_Y);
            ctx.lineTo(OFFSET_X + x * GRID_SIZE, OFFSET_Y + GRID_H * GRID_SIZE);
            ctx.stroke();
        }
        for (let y = 0; y <= GRID_H; y++) {
            ctx.beginPath();
            ctx.moveTo(OFFSET_X, OFFSET_Y + y * GRID_SIZE);
            ctx.lineTo(OFFSET_X + GRID_W * GRID_SIZE, OFFSET_Y + y * GRID_SIZE);
            ctx.stroke();
        }

        // 配置済みパーツ描画
        boatParts.forEach(p => {
            const def = PARTS[p.type];
            ctx.fillStyle = def.color;
            ctx.fillRect(OFFSET_X + p.x * GRID_SIZE + 2, OFFSET_Y + p.y * GRID_SIZE + 2, GRID_SIZE - 4, GRID_SIZE - 4);
            ctx.fillStyle = '#fff';
            ctx.font = '10px sans-serif';
            ctx.fillText(def.name[0], OFFSET_X + p.x * GRID_SIZE + 15, OFFSET_Y + p.y * GRID_SIZE + 25);
        });

        // タイトル・情報
        ctx.fillStyle = '#fff';
        ctx.font = '18px sans-serif';
        ctx.fillText(`所持金: $${money}`, 30, 40);
        ctx.fillText('船を組み立ててください(マスをクリックで配置/削除)', 30, 70);

    } else if (state === 'SAIL') {
        // 川の背景(スクロール)
        ctx.fillStyle = '#224957';
        ctx.fillRect(0, 0, cvs.width, cvs.height);
        
        // 川の両岸
        ctx.fillStyle = '#2e8b57';
        ctx.fillRect(0, 0, 100, cvs.height);
        ctx.fillRect(700, 0, 100, cvs.height);

        // 川の流れ線
        ctx.strokeStyle = '#4aa3a2';
        ctx.lineWidth = 2;
        for (let i = 0; i < 10; i++) {
            let sy = (i * 70 - (riverScroll % 70));
            ctx.beginPath();
            ctx.moveTo(150, sy);
            ctx.lineTo(200, sy + 30);
            ctx.stroke();
            ctx.beginPath();
            ctx.moveTo(650, sy);
            ctx.lineTo(600, sy + 30);
            ctx.stroke();
        }

        // 障害物描画
        obstacles.forEach(obs => {
            ctx.fillStyle = obs.type === 'rock' ? '#696969' : '#8b4513';
            ctx.fillRect(obs.x, obs.y, obs.w, obs.h);
        });

        // ボート描画
        const bCenterX = boatX;
        const bCenterY = boatY;
        boatParts.forEach(p => {
            const def = PARTS[p.type];
            const px = bCenterX + (p.x - Math.floor(GRID_W/2)) * GRID_SIZE;
            const py = bCenterY + (p.y - Math.floor(GRID_H/2)) * GRID_SIZE;
            ctx.fillStyle = def.color;
            ctx.fillRect(px, py, GRID_SIZE - 2, GRID_SIZE - 2);
        });

        // UI情報
        ctx.fillStyle = '#fff';
        ctx.font = '20px sans-serif';
        ctx.fillText(`距離: ${Math.floor(distance)} m`, 30, 40);
        ctx.fillText(`パーツ耐久残数: ${boatParts.length}`, 30, 70);
        if (isRocketActive) {
            ctx.fillStyle = '#ff4500';
            ctx.fillText('🚀 ロケット加速中!', 300, 40);
        }
    }
}

// メインループ
function loop() {
    update();
    draw();
    requestAnimationFrame(loop);
}
loop();

// キーボード操作対応
window.addEventListener('keydown', (e) => {
    if (e.code === 'Space' && state === 'SAIL') {
        triggerRocket();
    }
});
</script>

</body>
</html>

11,277 chars · codepastes.com