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無題Plain Text
<!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