前回:麻雀で学ぶTypeScript 6 チー続き
ソースコード:https://github.com/bonetaro/mahjong-ts
前回の記事で自分が何がわからなかったのが少しわかりかけたので、「安全」を意識して既存コードを見直しました。
export const ManduChar = "m"; // 萬子 Characters
export const PinduChar = "p"; // 筒子 Wheels
export const SouduChar = "s"; // 索子 Bamboos
export const KazehaiChar = "w"; // 風牌 Winds
export const SangenpaiChar = "d"; // 三元牌 Dragons
export const EastWindChar = "e"; // 東
export const SouthWindChar = "s"; // 南
export const WestWindChar = "w"; // 西
export const NorthWindChar = "n"; // 北
export const WhiteDragonChar = "w"; // 白 WhiteDragon
export const GreenDragonChar = "g"; // 發 GreenDragon
export const RedDragonChar = "r"; // 中 RedDragon
export const TerminalDigits = [1, 9] as const; // 老頭牌の数
export const SimpleDigits = [2, 3, 4, 5, 6, 7, 8] as const; // 中張牌の数
export const Digits = [...TerminalDigits, ...SimpleDigits] as const;
export const PlayerIndexList = [0, 1, 2, 3] as const;
export const PlayerDirectionList = ["self", "toTheRight", "opposite", "toTheLeft"] as const;
export const WindChars = [EastWindChar, SouthWindChar, WestWindChar, NorthWindChar] as const;
export const WindNameList = ["東", "南", "西", "北"] as const;
export const DragonChars = [WhiteDragonChar, GreenDragonChar, RedDragonChar] as const;
export type 萬子 = `${typeof ManduChar}`; // 英語はCharacters
export type 筒子 = `${typeof PinduChar}`; // 英語はWheels,dots
export type 索子 = `${typeof SouduChar}`; // 英語はBamboos
export type 風 = `${typeof KazehaiChar}`; // Wind
export type 元 = `${typeof SangenpaiChar}`; // Dragon
export type 牌種 = 萬子 | 筒子 | 索子 | 風 | 元;
export type 数牌の数 = (typeof Digits)[number];
export type 数牌の色 = 萬子 | 筒子 | 索子;
export type 萬子牌 = `${数牌の数}${萬子}`;
export type 筒子牌 = `${数牌の数}${筒子}`;
export type 索子牌 = `${数牌の数}${索子}`;
type 老頭牌の数 = (typeof TerminalDigits)[number];
type 中張牌の数 = (typeof SimpleDigits)[number];
export type 中張牌 = `${中張牌の数}${数牌の色}`;
export type 老頭牌 = `${老頭牌の数}${数牌の色}`;
export type 数牌 = 萬子牌 | 筒子牌 | 索子牌; // Suits
export type 東 = `${typeof EastWindChar}${風}`; // EastWind
export type 南 = `${typeof SouthWindChar}${風}`; // SouthWind
export type 西 = `${typeof WestWindChar}${風}`; // WestWind
export type 北 = `${typeof NorthWindChar}${風}`; // NorthWind
export type 四風牌 = 東 | 南 | 西 | 北; // Winds
export type 白 = `${typeof WhiteDragonChar}${元}`; // WhiteDragon
export type 發 = `${typeof GreenDragonChar}${元}`; // GreenDragon
export type 中 = `${typeof RedDragonChar}${元}`; // RedDragon
export type 三元牌 = 白 | 發 | 中;
export type 字牌 = 四風牌 | 三元牌;
export type 么九牌 = 老頭牌 | 字牌;
export type 牌 = 数牌 | 字牌;
export type PlayerIndex = (typeof PlayerIndexList)[number];
export type PlayerDirection = (typeof PlayerDirectionList)[number];
上記コードだけでは伝わらないとは思いますが、以前の実装と比べずいぶん無駄が減りました。
コード前半では、数値、文字列、配列でconstを定義して、そのconstを活かして後半で麻雀のデータ型を定義しています。C#で上記のようなことをしたかったら、ValueObjectなどを使って値の仕様をモデルに落とし込みますが、それがTypeScriptではデータ型で縛れるのはうれしいですね。
今回のプログラムは4人麻雀なので、要素数が4の配列が登場することが多いのですが、c#でいえばindexの変数がintで値が3を超えないようIndexOutOfRangeExceptionに気を付ける必要があるのに比べ、PlayerIndexのようなデータ型があるとその心配がずいぶん減ります。
データ型を定義した場合は、セットで型ガード関数を作るのが必須のようですね。当たり前のことですが、型ガードを通った値は型を保証してくれます。Webアプリケーションでは、validationロジックを通すことでデータが仕様通りかを担保しますが、それに比べてデータ型で保証するほうが宣言的な感じが安心できますね。変数もできるだけイミュータブルにするほうが、型安全として有利になるのも腑に落ちやすいです。
今回は、上記のようなページが勉強になりました。
また用語として、型クラスという言葉を知りました。
学べば学ぶほど知らないことが増えていく・・・。無知の知をかみしめます。