0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

イベント駆動開発を学びたい(Step1:初回フロー作成)

0
Last updated at Posted at 2026-10-21

はじめに

前回は、ゲームフリークのバトルシステムを題材に、Section・Event・EventHandler の役割を整理しました。

ポケモンバトルの仕様が複雑すぎるので、ゲームフリークのイベント駆動設計を学んでみる

今回はまだイベント駆動にはしません。Section に分ける前に、バトル開始から1ターン終了までを普通の関数呼び出しで動かします。

題材はガブリアス対カバルドンの1対1、1ターンです。両者の初期HPは100、素早さは120と60、技は両方「じしん」、ダメージは固定20です。特性、持ち物、ダメージ計算、ひんし判定はありません。数値は学習用の仮値です。

シーケンス

main.ts が起動と結果表示、battle.ts がバトル開始、turn.ts がターンの順番、actions.ts が行動選択・行動順・攻撃です。状態は BattleState を渡して直接更新します。

コード

状態はターン番号、ポケモン、実行ログだけです。

export type Pokemon = {
  id: PokemonId;
  name: string;
  currentHp: number;
  maxHp: number;
  speed: number;
};

export type Action = {
  actorId: PokemonId;
  targetId: PokemonId;
  move: string;
};

export type BattleState = {
  turn: number;
  pokemon: Pokemon[];
  logs: string[];
};

バトル開始で状態を作り、2体を登場させてからターンへ進みます。

export function runBattle(): BattleState {
  const state: BattleState = {
    turn: 0,
    pokemon: [],
    logs: [],
  };

  state.logs.push("=== バトル開始 ===");

  const garchomp = createGarchomp();
  state.pokemon.push(garchomp);
  state.logs.push(`${garchomp.name}が登場`);

  const hippowdon = createHippowdon();
  state.pokemon.push(hippowdon);
  state.logs.push(`${hippowdon.name}が登場`);

  return runTurn(state);
}

ターンは、番号を1にしてから選択、順序、実行、終了です。

export function runTurn(state: BattleState): BattleState {
  state.turn += 1;
  state.logs.push(`=== ターン${state.turn} 開始 ===`);

  const actions = selectActions(state);
  const orderedActions = decideActionOrder(actions, state);
  for (const action of orderedActions) {
    executeAction(state, action);
  }

  state.logs.push(`=== ターン${state.turn} 終了 ===`);
  return state;
}

行動選択では両者とも相手に「じしん」を選びます。HPはここでは変えません。

export function selectActions(state: BattleState): Action[] {
  return state.pokemon.map((pokemon) => {
    const target = state.pokemon.find((candidate) => candidate.id !== pokemon.id);
    if (target === undefined) {
      throw new Error("対象ポケモンが見つかりません");
    }

    const action: Action = {
      actorId: pokemon.id,
      targetId: target.id,
      move: MOVE_EARTHQUAKE,
    };
    state.logs.push(`${pokemon.name}:${action.move}`);
    return action;
  });
}

行動順は素早さが高い順です。ガブリアスが先になります。

export function decideActionOrder(actions: Action[], state: BattleState): Action[] {
  const ordered = [...actions].sort((left, right) => {
    return findPokemon(state, right.actorId).speed - findPokemon(state, left.actorId).speed;
  });

  state.logs.push("行動順:");
  for (const [index, action] of ordered.entries()) {
    state.logs.push(`${index + 1}. ${findPokemon(state, action.actorId).name}`);
  }

  return ordered;
}

攻撃はログを残し、相手のHPから20を引きます。

export function executeAction(state: BattleState, action: Action): BattleState {
  const actor = findPokemon(state, action.actorId);
  const target = findPokemon(state, action.targetId);

  state.logs.push(`${actor.name}の${action.move}!`);
  target.currentHp -= DAMAGE;
  state.logs.push(`${target.name}に${DAMAGE}ダメージ!`);

  return state;
}

runBattle() ごとに新しい状態とログを作ります。ログは配列へ追記し、空行や最終HPの表示は main.ts が最後に行います。

実行結果

=== バトル開始 ===

ガブリアスが登場
カバルドンが登場

=== ターン1 開始 ===

ガブリアス:じしん
カバルドン:じしん

行動順:
1. ガブリアス
2. カバルドン

ガブリアスのじしん!
カバルドンに20ダメージ!

カバルドンのじしん!
ガブリアスに20ダメージ!

=== ターン1 終了 ===

ガブリアス HP:80 / 100
カバルドン HP:80 / 100

ターン番号は1、両者のHPは80です。ガブリアスが先に攻撃しています。

次回は、この実行結果を変えずに処理を Section へ分けます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?