開発日記⑥ 仮の終局結果から点数と順位を判定する

前回までの進捗

前回までで、現在の点数状況から、目標順位に必要なロン条件とツモ条件を表示できるようになった。
これで「何点アガれば目標順位に届くのか」は確認できる。
ただ、表示された条件以外の結果も試してみたい。
たとえば、

  • 別の相手からロンした場合
  • 想定より低い点数でアガった場合
  • 自分以外がアガった場合
  • 流局した場合

など。
そこで今回は、仮の終局結果を入力し、その結果を反映した点数と順位を確認できるようにした。

終局方法をプルダウンで選ぶ

今回扱う終局方法は、次の3種類。

  • ロン
  • ツモ
  • 流局

プログラム上では、使える値を型として定義している。

type ResultType =
  | "ron"
  | "tsumo"
  | "draw";

画面では、プルダウンから終局方法を選択する。

<select
  value={resultType}
  onChange={(event) =>
    onChangeResultType(
      event.target.value as ResultType,
    )
  }
>
  <option value="ron">ロン</option>
  <option value="tsumo">ツモ</option>
  <option value="draw">流局</option>
</select>

選択した終局方法によって、下に表示する入力項目を切り替えるようにした。
ロンを選んだときにツモ用の入力欄まで並んでいると分かりにくいので、必要な項目だけを表示する。

ロンの場合

ロンを選んだ場合は、次の項目を入力する。

  • 和了者
  • 放銃者
  • ロン点

和了者と放銃者は、東家・南家・西家・北家から選択する。
ロン点には、本場分を含まない基本の点数を入力する。
たとえば3,900点のロンで1本場なら、放銃者が支払う点数は次のようになる。

3,900点 + 300点 = 4,200点

供託リーチ棒がある場合は、和了者へ供託分も加える。
点数計算自体は、以前作ったロン用の処理をそのまま利用する。

applyRon(
  scores,
  winner,
  loser,
  ronPoints,
  honba,
  kyotaku,
);

現在の点数と入力した終局結果を渡し、ロン後の新しい点数を受け取る。

ツモの場合

ツモを選んだ場合は、まず和了者を選ぶ。
そのうえで、ツモ時の支払いを入力する。
自分が親か子かではなく、今回選択した和了者が親かどうかによって入力欄を変える。
和了者が親の場合は、ほかの3人が同じ金額を支払う。
そのため、全員が支払う基本点を一つ入力する。
和了者が子の場合は、

  • 親が支払う基本点
  • 子が支払う基本点

の二つを入力する。
たとえば400・700のツモなら、

  • 親が700点
  • ほかの子が400点ずつ

を支払う。
ツモ後の点数計算には、ツモ用の処理を使う。

applyTsumo(
  scores,
  winner,
  dealerPayment,
  childPayment,
  honba,
  kyotaku,
);

この処理の中で、和了者が親か子か、支払う側が親か子かを判定しながら点数を動かす。
本場がある場合は、各支払いに1本あたり100点を加える。
供託がある場合は、最後に和了者へ加算する。

流局の場合

流局を選んだ場合は、和了者や放銃者を入力しない。
代わりに、誰がテンパイしているかをチェックボックスで選ぶ。

{seats.map((seat) => (
  <label key={seat}>
    <input
      type="checkbox"
      checked={tenpaiSeats.includes(seat)}
      onChange={() =>
        onToggleTenpaiSeat(seat)
      }
    />

    {seatLabels[seat]}
  </label>
))}

テンパイ者の人数に応じて、合計3,000点のノーテン罰符を移動する。

  • 0人テンパイ:点数移動なし
  • 1人テンパイ:テンパイ者が3,000点受け取る
  • 2人テンパイ:テンパイ者が1,500点ずつ受け取る
  • 3人テンパイ:テンパイ者が1,000点ずつ受け取る
  • 4人テンパイ:点数移動なし

たとえば2人テンパイなら、テンパイ者が1,500点ずつ受け取り、ノーテン者が1,500点ずつ支払う。
流局時は、供託リーチ棒を移動させない仕様にしている。

終局方法によって計算を切り替える

入力した結果から終局後の点数を計算するときは、選択中の終局方法によって処理を切り替える。

if (resultType === "ron") {
  return applyRon(
    scores,
    winner,
    loser,
    ronPoints,
    honba,
    kyotaku,
  );
}

if (resultType === "tsumo") {
  return applyTsumo(
    scores,
    winner,
    dealerPayment,
    childPayment,
    honba,
    kyotaku,
  );
}

return applyDraw(
  scores,
  tenpaiSeats,
);

ロンならapplyRon、ツモならapplyTsumo、流局ならapplyDrawを呼び出す。
画面側で点数移動を一から計算するのではなく、それぞれの計算処理を関数として分けている。
こうしておくと、画面の入力方法を変更しても、点数計算部分はそのまま使いやすい。

入力を変えると結果も更新される

終局後の点数は、入力内容から計算して表示する。
ロン点や和了者などを変更すると、新しい入力内容を使って自動的に再計算される。

const finalScores = useMemo(() => {
  if (resultType === "ron") {
    return applyRon(/* 入力内容 */);
  }

  if (resultType === "tsumo") {
    return applyTsumo(/* 入力内容 */);
  }

  return applyDraw(/* 入力内容 */);
}, [
  scores,
  resultType,
  winner,
  loser,
  ronPoints,
  dealerPayment,
  childPayment,
  honba,
  kyotaku,
  tenpaiSeats,
]);

ここではReactのuseMemoを使っている。
指定した入力内容が変更されたときに、終局後の点数を計算し直す形になっている。
まだuseMemoを完全に理解できているわけではないけれど、入力値から計算できる結果を扱うために使っている。

終局後の順位と目標達成を表示する

終局後の点数を計算したら、その点数を以前作った順位計算へ渡す。

const ranking =
  calculateRanking(finalScores);

これで、仮の結果を反映した順位を取得できる。
画面には、

  • 成功または失敗
  • 自分の席と目標順位
  • 本場と供託
  • 終局後の4人の点数
  • 終局後の順位

を表示する。
たとえば目標を「2着以上」に設定している場合、終局後に1着か2着なら成功、3着か4着なら失敗となる。
必要な点数を確認するだけでなく、自由に結果を入力して試せるようになった。

おかしな入力を判定する

自由に入力できるようにしたため、成立しない組み合わせが入力される可能性もある。
たとえば、

  • ロンの和了者と放銃者が同じ
  • 点数が100点単位になっていない
  • 本場や供託がマイナス
  • ツモ時の支払いが正しくない

といった状態。
入力内容に問題がある場合は、点数移動を行わず、エラーメッセージを表示する。
正常な結果が出たように見えてしまうより、入力がおかしいことを先に知らせる方がよさそう。

今回できたこと

今回の進捗は以下のとおり。

  • 終局方法をプルダウンから選択できるようにした
  • ロン・ツモ・流局で入力項目を切り替えた
  • ロンの和了者・放銃者・点数を入力できるようにした
  • ツモ時の親と子の支払いを入力できるようにした
  • 流局時のテンパイ者を選択できるようにした
  • 入力内容から終局後点数を計算した
  • 終局後の順位を再計算した
  • 目標順位を達成できたか判定した
  • 不正な入力にはエラーを表示した

これで、必要なアガリ点を確認するだけでなく、仮の終局結果を入力して最終順位を試せるようになった。
画面としてはまだ入力欄を並べた段階で、少し分かりにくい部分もある。
終局方法の選び方や入力した点数の見せ方は、後から改善していきたい。
次は、入力した内容をブラウザに保存し、ページを再読み込みしても設定が残るようにする。

仮のロン結果を入力し、終局後の点数・順位と目標達成可否を表示する画面
終局情報の入力と自動反映される対局結果欄

牛一。

コメント

タイトルとURLをコピーしました