railsで戦争ゲーム作成

太平洋戦線で静的なマップ。近い都市から都市の移動で航空機であれば1ターンで移動、攻撃、移動。戦車、歩兵、戦艦、だとそれぞれ移動。移動中にも地形により部隊数の上下がある。

また、都市攻撃では部隊数の減少、経験値の上昇などを自動計算し勝敗をつける。都市占領は部隊の数が0になれば占領完了。資源(ボーキサイト等)は都市毎に保有しており占領により使えるようにして武器製造可能になる。1ターンは1ヶ月。

上記の条件で簡単な操作の趣味レーションゲームをrailsとjavascriptで作成してみよう。



🔹 ゲームの基本構成


1. ゲームの流れ

1. ターン開始(1ターン=1ヶ月)

各プレイヤー(国)がユニットを移動

航空機は「移動 → 攻撃 → 移動」が可能

地形の影響で部隊の損耗が発生

2. 戦闘処理

都市攻撃では「部隊の減少」「経験値上昇」「勝敗判定」

敵部隊が0になれば都市占領(資源獲得)

3. 資源と生産

都市の資源を使用して武器・ユニットを生産

4. ターン終了 → 次ターンへ

🔹 開発技術

Rails(バックエンド)

ユーザー認証(プレイヤー管理)

ユニット・都市・資源のデータ管理(ActiveRecord)

ゲームロジック処理(戦闘、占領、ターン進行)

APIエンドポイント(JSONでフロントにデータ提供)

JavaScript(フロントエンド)

地図の表示(Canvas + クリックイベント)

ユニットの移動・攻撃処理

都市の占領、資源管理、UI制御


🔹 データモデル設計

# 都市情報
class City < ApplicationRecord
  has_many :units
  belongs_to :owner, class_name: 'Player', optional: true
  has_many :resources
end
# ユニット情報(戦艦・戦車・航空機・歩兵)
class Unit < ApplicationRecord
  belongs_to :player
  belongs_to :city
  enum unit_type: { infantry: 0, tank: 1, battleship: 2, aircraft: 3 }
  attr_accessor :movement_points, :attack_power, :defense_power, :experience
end
# プレイヤー
class Player < ApplicationRecord
  has_many :units
  has_many :cities
  has_many :resources
end
# 資源情報(ボーキサイト・鉄など)
class Resource < ApplicationRecord
  belongs_to :city
  belongs_to :player, optional: true
end


🔹 Rails APIエンドポイント


都市情報の取得

# app/controllers/cities_controller.rb
class CitiesController < ApplicationController
  def index
    cities = City.includes(:resources, :owner).all
    render json: cities.as_json(include: [:resources, :owner])
  end
end

エンドポイント: GET /cities


ユニットの移動

# app/controllers/units_controller.rb
class UnitsController < ApplicationController
  def move
    unit = Unit.find(params[:id])
    destination = City.find(params[:city_id])
    if unit.movement_points > 0
      unit.city = destination
      unit.movement_points -= 1
      unit.save
      render json: { status: "moved", unit: unit }
    else
      render json: { status: "no movement points" }, status: 400
    end
  end
end

エンドポイント: POST /units/:id/move


🔹 JavaScriptフロントエンド(マップ表示)

document.addEventListener("DOMContentLoaded", function () {
  fetch('/cities')
    .then(response => response.json())
    .then(cities => {
      const mapCanvas = document.getElementById('mapCanvas');
      const ctx = mapCanvas.getContext('2d');
      cities.forEach(city => {
        ctx.fillStyle = city.owner ? "red" : "blue"; // 所有者で色分け
        ctx.fillRect(city.x * 10, city.y * 10, 20, 20);
        // クリックイベントで移動
        mapCanvas.addEventListener('click', (event) => {
          const clickX = event.offsetX;
          const clickY = event.offsetY;
          if (clickX >= city.x * 10 && clickX <= city.x * 10 + 20 &&
              clickY >= city.y * 10 && clickY <= city.y * 10 + 20) {
            moveUnitToCity(city.id);
          }
        });
      });
    });
  function moveUnitToCity(cityId) {
    fetch(`/units/1/move`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ city_id: cityId })
    }).then(response => response.json())
      .then(data => console.log("Unit moved:", data));
  }
});


🔹 戦闘処理


戦闘計算(Rails側)

class BattlesController < ApplicationController
  def attack
    attacker = Unit.find(params[:attacker_id])
    defender = Unit.find(params[:defender_id])
    # 攻撃力、経験値によるダメージ計算
    damage = (attacker.attack_power * (1 + attacker.experience * 0.1)) - defender.defense_power
    damage = 1 if damage < 1 # 最小1ダメージ
    defender.health -= damage
    attacker.experience += 1 if defender.health <= 0
    # 戦闘結果の更新
    if defender.health <= 0
      defender.destroy
      message = "Defender defeated!"
    else
      defender.save
      message = "Attack successful!"
    end
    render json: { status: message, attacker: attacker, defender: defender }
  end
end

エンドポイント: POST /battles/attack


🔹 データモデル設計(カラム構成)

1️⃣ プレイヤー(players)

プレイヤーの基本情報を管理します。

0