開発日記⑤ ツモで必要な点数を計算する

前回までの進捗

前回は、目標順位を達成するために必要なロン点を計算できるようにした。
現在の点数から、誰から何点アガれば目標順位に届くのかを、実際の和了点候補を順番に試しながら探している。
今回はその続きとして、ツモの場合に必要な点数を計算していく。
ロンでは放銃者によって条件が変わったけれど、ツモではほかの3人全員から点数を受け取る。
一見するとロンより単純そうに見える。
ただ、実際に処理を考えてみると、親と子で支払い方が違うため、こちらも別々に考える必要があった。

親のツモと子のツモ

自分が親の場合、ほかの3人は同じ点数を支払う。
たとえば親の1,000点オールなら、ほかの3人から1,000点ずつ受け取る。
一方、自分が子の場合は、親と子で支払う点数が異なる。
たとえば400・700なら、

  • 親が700点
  • 子2人が400点ずつ

を支払う。
そのため、ツモ条件を一つの数字だけで表すのは少し分かりにくい。
親の場合は「1人あたり何点」、子の場合は「親が何点、子が何点」という形で管理することにした。

ツモ後の点数を計算する

まずは、ツモが発生した後の点数を計算する処理を作る。
流れとしては、ほかの3人を順番に確認し、それぞれの支払いを減らして、同じ金額をアガった人へ加える。
イメージとしてはこんな感じ。

for (const seat of seats) {
  if (seat === winner) {
    continue; //和了者はスキップ
  }

  //各席の支払い点数を求める
  const payment = getPayment(seat);

  //点数更新
  nextScores[seat] -= payment;
  nextScores[winner] += payment;
}

アガった本人は支払わないので、処理を飛ばす。
残りの3人について、自分が親か子か、支払う側が親か子かを見ながら点数を決める。
ロンでは放銃者一人との点数移動だった。
ツモでは3人分を処理する必要があるので、点数の合計がずれていないか少し不安になる。
こういう処理は、あとでテストも追加して確認したい。

親の場合の必要点数を探す

自分が親の場合は、親ツモの支払い候補を低い順に試していく。
処理の考え方は、前回のロン条件とほぼ同じ。

  1. 親ツモの候補を一つ選ぶ
  2. ほかの3人から点数を移動する
  3. ツモ後の順位を計算する
  4. 目標順位を達成できたか確認する
  5. 最初に達成できた候補を必要点数とする

コードのイメージは次のようになる。

for (const payment of dealerTsumoPayments) {
  //ツモ点数計算
  const nextScores = applyTsumo(
    scores,
    selfSeat,
    payment,
    payment,
    honba,
    kyotaku,
  );

  //順位計算
  const ranking = calculateRanking(nextScores);

  //条件を満たしたか
  if (judgeGoal(ranking, selfSeat, goal)) {
    return payment;
  }
}

親ツモでは全員の支払いが同じなので、比較的分かりやすい。
画面には「○点オール」の形で表示できそう。

子の場合は支払いを二つ持つ

自分が子の場合は、親の支払いと子の支払いをセットで扱う。

type ChildTsumoPayment = {
  dealerPayment: number;
  childPayment: number;
};

たとえば400・700なら、次のようなデータになる。

{
  dealerPayment: 700,
  childPayment: 400,
}

この候補も低いものから順番に試す。
ツモ後の順位が目標に届いた時点で、その組み合わせを必要なツモ条件として返す。

for (const payment of childTsumoPayments) {
  const nextScores = applyTsumo(
    scores,
    selfSeat,
    payment.dealerPayment,  //親の支払い
    payment.childPayment,  //この支払い
    honba,
    kyotaku,
  );

  if (
    judgeGoal(
      calculateRanking(nextScores),
      selfSeat,
      goal,
    )
  ) {
    return payment;
  }
}

ロンの場合は、放銃者ごとに結果が三つ出る。
ツモの場合は放銃者を選ばないので、必要な条件は一つだけになる。
その代わり、子の場合は親と子の支払いを分けて表示する必要がある。

本場と供託を加える

ツモの場合、本場1本につき、それぞれの支払いへ100点ずつ追加される。
たとえば、子の400・700ツモで1本場なら、

  • 親の支払いは800点
  • 子の支払いは500点ずつ

になる。
供託リーチ棒がある場合は、ロンと同じようにアガった人へ1本1,000点を加える。
処理としては、

各自の支払い
= 基本の支払い + 本場 × 100点

を3人分反映し、最後に供託をアガった人へ加える。
本場は支払う3人全員に影響するため、ツモでは合計すると1本につき300点分の点数移動になる。
同じ基本点でも、本場や供託によって目標順位へ届くことがあるので、必要点数の判定にも反映させる。

戻り値の形を分ける

親ツモと子ツモでは、結果として返したい情報が違う。
親なら1人あたりの支払いだけでいい。
子なら親と子、それぞれの支払いが必要になる。
そこで、結果の型も二つに分けた。

type RequiredTsumo =
  | {
      type: "dealer";
      paymentPerPlayer: number;
    }
  | {
      type: "child";
      dealerPayment: number;
      childPayment: number;
    }
  | null;

typeを見ることで、親ツモの結果なのか、子ツモの結果なのかを判別できる。
画面へ表示するときも、

  • 親なら「○点オール」
  • 子なら「子○点・親○点」

と切り替えられる。
TypeScriptでは、同じ結果でも状況によって必要な情報が違う場合に、こういう形で型を分けられるらしい。

ロンとツモの最低条件を比べる

ロンとツモの両方を計算できるようになったので、最も低い達成条件も探せるようになった。
ロンの場合は、3人それぞれからアガった場合の必要点数を出す。
ツモの場合は、支払いの合計を計算する。
その中から最も低い条件を選べば、目標順位を達成するための最低条件として表示できる。
ただし、ロン点とツモの合計点を単純に比べるだけで分かりやすいのかは、まだ少し迷っている。
画面上では、最低条件だけを強調しつつ、ロンとツモの内訳も確認できる形にしたい。

今回できたこと

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

  • ツモ後の点数移動を計算できるようにした
  • 親ツモの必要点数を探索できるようにした
  • 子ツモの親・子それぞれの支払いを管理した
  • ツモ後の順位から目標達成を判定した
  • 本場と供託をツモ条件へ反映した
  • 親ツモと子ツモで結果の型を分けた
  • ロンとツモの最低条件を比較できるようにした
目標順位達成に必要なロン点とツモ点を比較表示する画面

これで、ロンとツモの両方について、目標順位に必要な点数を出せるようになった。
まだ画面は条件を確認するための計算ツールに近い。
次は、実際に起きたと仮定する終局結果を入力し、その結果によって点数と順位がどう変わるかを表示できるようにしたい。
ロン・ツモだけでなく、流局時の点数移動も扱う予定。

牛一。

コメント

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