Miners (採掘放置ゲー)

0G

view source

JavaScript

let gold = 0;
let minerCount = 0;
let minerLevel = 1;
let stage = 1;
let playerStamina = 20;
let staminaMax = minerLevel * 20;
let minerUpgradeCost = 200; // 初期コスト


const tileSize = 40;
const tilesX = 7;
const tilesY = 10;
let map = [];
let mapDepth = tilesY;

// DOM Elements
const goldDisplay = document.getElementById('gold');
const minerCountDisplay = document.getElementById('minerCount');
const minerLevelDisplay = document.getElementById('minerLevel');
const mineButton = document.getElementById('mineButton');
const buyMinerButton = document.getElementById('buyMinerButton');
const upgradeMinerButton = document.getElementById('upgradeMinerButton');
const demo = document.getElementById('demo');

// Canvas setup
const canvas = document.createElement('canvas');
canvas.width = tileSize * tilesX;
canvas.height = tileSize * tilesY;
demo.appendChild(canvas);
const ctx = canvas.getContext('2d');

// マップ生成(鉄1, 金2, ダイヤ3)
function generateMap() {
  map = [];
  for (let y = 0; y < mapDepth; y++) {
    let row = [];
    for (let x = 0; x < tilesX; x++) {
      const rand = Math.random();
      if (rand < 0.7) row.push(1);         // 鉄(普通)
      else if (rand < 0.9) row.push(2);    // 金(レア)
      else row.push(3);                    // ダイヤ(超レア)
    }
    map.push(row);
  }
}

generateMap();

// 採掘者たち
let miners = [];

// タイル掘り処理
function dig(miner, index) {
  if (miner.depth >= map.length) return;
  if (miner.stamina <= 0) return;

  const x = miner.x;
  const y = miner.depth;
  const ore = map[y]?.[x];

  if (ore && ore !== 0) {
    const rewardTable = [0, 5, 20, 100];
    gold += rewardTable[ore] * minerLevel;
    map[y][x] = 0;
  }

  miner.depth += 1;
  miner.stamina -= 1;

  // 💀 体力尽きたら削除
  if (miner.stamina <= 1) {
    console.log("採掘者消滅 at x:", miner.x);
    miners.splice(index, 1);     // 配列から削除
    minerCount -= 1;             // カウンターも減らす!
  }
  if(miner.depth > mapDepth-1){
    miner.depth = 0;
  }
}



function isColumnCleared(x) {
  console.log("current map snapshot:", map);

  if (x < 0 || x >= tilesX) return false; // 範囲チェック

  for (let y = 0; y < map.length; y++) {
    if (map[y]?.[x] !== 0) return false;
  }
  return true;
}



// 採掘者生成
function addMiner() {
  if (miners.length >= tilesX) return;
  const x = miners.length % tilesX;
  miners.push({
    x: x,
    depth: 0,
    stamina: minerLevel * 20,
    staminaMax: minerLevel * 20
  });

  minerCount += 1;
  return true;
}

function resetMiners() {
  miners.forEach(miner => {
    miner.depth = 0;
  });
}



// 自動採掘処理(ループ)
function startMining() {
  setInterval(() => {
    for (let i = miners.length - 1; i >= 0; i--) {
      dig(miners[i], i);
    }
    if (playerStamina === staminaMax) {
      mineButton.click();
    }
    if (playerStamina < staminaMax) {
      playerStamina = Math.min(playerStamina + minerLevel, staminaMax);
    }
    // 👇 掘るたびにステージクリア判定
    if (!checkStageClear()) {
      updateDisplay();
      render();
    }
  }, 2000 / minerLevel); // レベルアップで高速化

}

startMining();

// 画面中央で掘るカーソル的なプレイヤー位置
let player = {
  x: Math.floor(tilesX / 2),
  depth: 0
};

mineButton.addEventListener('click', () => {
  if (playerStamina <= 0) return; // ✋体力がないと掘れない
  const x = player.x;
  const y = player.depth;

  if (y >= map.length) return;

  const rewardTable = [0, 5, 20, 100];
  const ore = map[y][x];

  if (ore && ore !== 0) {
    gold += rewardTable[ore] * minerLevel;
    map[y][x] = 0;
  }
  //
  // 🔥 掘った後、即判定!
  if (isColumnCleared(x) || player.depth > mapDepth-2) {
    stage += 1;
    player.depth = 0;

    resetMiners();     // 👈 追加!!
    generateMap();
    // 👇 自分が掘ったあとも判定
    if (!checkStageClear()) {
      updateDisplay();
      render();
    }
    return; // 💥 ここで終了しないと depth++ が走ってしまう
  }

  playerStamina -= 1; // 💔掘るたび体力消費
  player.depth += 1;
  updateDisplay();
  render();
});

function checkStageClear() {
  for (let x = 0; x < tilesX; x++) {
    if (isColumnCleared(x)) {
      stage += 1;
      player.depth = 0;
      resetMiners();       // 採掘者リセット
      generateMap();
      //alert(`ステージ ${stage} に進みました!`);
      updateDisplay();
      render();
      return true;
    }
  }
  return false;
}





buyMinerButton.addEventListener('click', () => {
  const hireCost = minerLevel * 100;
  if (gold >= hireCost) {
    gold -= hireCost;
    addMiner();
    updateDisplay();
    render();
  }
});


upgradeMinerButton.addEventListener('click', () => {
  if (gold >= minerUpgradeCost) {
    gold -= minerUpgradeCost;
    minerLevel += 1;
    minerUpgradeCost *= 2; // 倍々に!
    staminaMax = minerLevel * 20; // プレイヤー体力上限更新
    updateDisplay();
  }
});


const energyDrinkButton = document.getElementById('energyDrinkButton');

energyDrinkButton.addEventListener('click', () => {
  if (gold >= 50) {
    gold -= 50;
    playerStamina = staminaMax;
    updateDisplay();
  }
});



document.addEventListener('keydown', (e) => {
  if (e.key === 'ArrowLeft') {
    player.x = Math.max(0, player.x - 1);
    render();
  } else if (e.key === 'ArrowRight') {
    player.x = Math.min(tilesX - 1, player.x + 1);
    render();
  } else if (e.code === 'Space') {
    e.preventDefault(); // スクロール防止(大事)
    mineButton.click(); // 掘るボタンの処理を実行!
  }
});

const moveLeftButton = document.getElementById('moveLeft');
const moveRightButton = document.getElementById('moveRight');

moveLeftButton.addEventListener('click', () => {
  player.x = Math.max(0, player.x - 1);
  render();
});

moveRightButton.addEventListener('click', () => {
  player.x = Math.min(tilesX - 1, player.x + 1);
  render();
});

// 表示更新
const staminaDisplay = document.getElementById('stamina');
const staminaMaxDisplay = document.getElementById('staminaMax');
const hireCostDisplay = document.getElementById('hireCost');
const upgradeCostDisplay = document.getElementById('upgradeCost');

function updateDisplay() {
  goldDisplay.textContent = gold;
  minerCountDisplay.textContent = minerCount;
  minerLevelDisplay.textContent = minerLevel;
  //stageDisplay.textContent = stage;
  staminaDisplay.textContent = playerStamina;
  staminaMaxDisplay.textContent = staminaMax;

  hireCostDisplay.textContent = minerLevel * 100;
  upgradeCostDisplay.textContent = minerUpgradeCost;

  // 👇 ボタンの有効/無効制御 👇
  mineButton.disabled = playerStamina <= 0;
  buyMinerButton.disabled = gold < minerLevel * 100 || miners.length >= tilesX;
  upgradeMinerButton.disabled = gold < minerUpgradeCost;
  energyDrinkButton.disabled = gold < 50;
}


function expandMap(rows = 10) {
  for (let i = 0; i < rows; i++) {
    const row = [];
    for (let x = 0; x < tilesX; x++) {
      const rand = Math.random();
      if (rand < 0.7) row.push(1);
      else if (rand < 0.9) row.push(2);
      else row.push(3);
    }
    map.push(row);
  }
  mapDepth += rows;
}

// 描画
function render() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  // 描画範囲のマップだけ表示(深さ0-9)
  for (let y = 0; y < tilesY; y++) {
    for (let x = 0; x < tilesX; x++) {
      const mapY = y;
      const type = map[mapY]?.[x] || 0;

      switch (type) {
        case 1:
          ctx.fillStyle = "#888"; // 鉄
          break;
        case 2:
          ctx.fillStyle = "gold"; // 金
          break;
        case 3:
          ctx.fillStyle = "cyan"; // ダイヤ
          break;
        default:
          ctx.fillStyle = "#222"; // 掘られた
      }

      ctx.fillRect(x * tileSize, y * tileSize, tileSize, tileSize);
      ctx.strokeStyle = "#000";
      ctx.strokeRect(x * tileSize, y * tileSize, tileSize, tileSize);
    }
  }

  // 採掘者の青い下向き三角(三角形のサイズ小さめ)
  miners.forEach((miner) => {
    if (miner.stamina <= 0) return;

    const ratio = miner.stamina / miner.staminaMax;
    let size = 12;

    if (ratio < 0.1) size = 1;         // 体力ほぼゼロなら描かない(保険)
    else if (ratio < 0.5) size = 5;  // 小サイズ
    else if (ratio < 0.8) size = 8;  // 中サイズ
    else size = 12;                 // 通常サイズ

    const drawY = miner.depth < tilesY ? miner.depth : tilesY - 1;
    const mx = miner.x * tileSize + tileSize / 2;
    const my = drawY * tileSize + tileSize / 2;

    ctx.fillStyle = "blue";
    ctx.beginPath();
    ctx.moveTo(mx, my + size / 2);
    ctx.lineTo(mx - size / 2, my - size / 2);
    ctx.lineTo(mx + size / 2, my - size / 2);
    ctx.closePath();
    ctx.fill();
  });


  // ドリル型プレイヤー表示(赤い下向き三角)
  if (player.depth < tilesY) {
    const px = player.x * tileSize + tileSize / 2;
    const py = player.depth * tileSize + tileSize / 2;

    ctx.fillStyle = "red";
    ctx.beginPath();
    ctx.moveTo(px, py + 10);        // 下の頂点
    ctx.lineTo(px - 10, py - 10);   // 左上
    ctx.lineTo(px + 10, py - 10);   // 右上
    ctx.closePath();
    ctx.fill();
  }


}

// 初回描画
render();
updateDisplay();

CSS

HTML

ページのソースを表示 : Ctrl+U , DevTools : F12

view-source:https://hi0a.com/demo/-js/js-game-miner/

ABOUT

hi0a.com 「ひまアプリ」は無料で遊べるミニゲームや便利ツールを公開しています。

プログラミング言語の動作デモやWEBデザイン、ソースコード、フロントエンド等の開発者のための技術を公開しています。

必要な機能の関数をコピペ利用したり勉強に活用できます。

プログラムの動作サンプル結果は画面上部に出力表示されています。

環境:最新のブラウザ GoogleChrome / Windows / Android / iPhone 等の端末で動作確認しています。

画像素材や音素材は半分自作でフリー素材配布サイトも利用しています。LINK参照。

動く便利なものが好きなだけで技術自体に興味はないのでコードは汚いです。

途中放置や実験状態、仕様変更、API廃止等で動かないページもあります。