Help us understand the problem. What is going on with this article?

TypeScript(の型システム)のみを使ってリバーシを作ってみる

この記事は「〇〇(言語)のみを使って、今△△(アプリ)を作るとしたら」参加記事のつもりです。

概要

「TypeScript でゲームを作る」と聞いた時、普通に考えれば Node.js 環境でブラウザ向けバンドルを吐くコードだったりとかを書くような流れだとは思うのですが、イベントの概要によれば

〇〇にはあなたの好きな言語、△△にはアプリを入れ、「自分だったらこうやって作る!」という記事を投稿してください。
※言語は1つに限定させていただきます。

ということらしいので、HTML/CSS(およびオルタナティブ言語やオブジェクトモデル)を使用した場合レギュレーションを満たさない可能性があると考え、純粋に TypeScript のみを使用した実装を行うために TypeScript の型システム内で完結するアプリを作ることにします。

今回はシンプルすぎず複雑すぎずバランスの取れた簡単なゲームとして、リバーシを作ってみます。なお、オリジナルルールをそのまま実装すると記事中のコードがかなり肥大化するため、手順で示す実装では盤面のサイズを 4x4 に縮小します(読み進めるとわかると思いますが、4x4 でもかなりのサイズなので 8x8 での膨れ上がりようはお察しください)。

また、リバーシとはなんぞやの説明はここでは割愛させて頂きます。要するに世間一般でオセ○と呼ばれているボードゲームです(商標が絡むので念のためオ○ロの名前は使用しないことにします)。

手順

適宜型の解説を加えながら手順を説明していきます。

構想

const result = game
  .blackPutsAt('F5')
  .whitePutsAt('D6')
// ...
  .whiteSkips()
// ...

といった感じに指した手をメソッドチェーンで入力していくと最終的に result に対局結果を表す型が入るコードを目標形とします。リバーシは二人零和有限確定完全情報ゲームでかつプレイヤーの意思によって自由にスキップできないので、「誰が打つ」や「誰がスキップする」などの情報は厳密には入力として与える必要はありませんが、実際に遊ぶ際に人間側の勘違いを防ぐためあえて入力情報として含めることにします。

また、プロパティ名を乱用活用することで現在の盤の状態を視覚的に表せると楽しそうです。

image.png

果たしてこれはアプリなのか?

まあ VSCode さえあれば組み込みの TypeScript エンジンがあるので環境構築は不要だし、VSLS とかで友達とリアルタイム対戦できるし、VSCode をブラウザのようなプレイ環境の一種と解釈すれば最低要件は満たしてるんじゃないですかね(ほんまか)。VSLS でなくても Git で交互に手をコミットしていくような遊び方もありかもしれません。

ともかく DOM/CSSOM まで縛るとありがちな CLI でやるか、あるいはこういう形しかないと思うので後者を選択しました。これをアプリとみなすかは読者の皆様の解釈にお任せします。そもそもネタ記事だしね。

その代わり、VSCode のみで環境構築なしに動作する必要があるので strict モードはオフにします。

モデル

ロジックを組む前に仕込みをします。

まずは石の型を作ってみましょう。

declare const BLACK: unique symbol
declare const WHITE: unique symbol
declare const EMPTY: unique symbol

/**
 * 黒石
 */
type BlackStone = typeof BLACK

/**
 * 白石
 */
type WhiteStone = typeof WHITE

/**
 * 黒石か白石のどちらか
 */
type Stone =
  | BlackStone
  | WhiteStone

/**
 * 相手の石
 */
type AnotherStoneMap = {
  readonly [BLACK]: WhiteStone
  readonly [WHITE]: BlackStone
}

簡単ですね。

続いて盤も作っていきます。

/**
 * 空
 */
type Empty = typeof EMPTY

/**
 * 盤の外側
 */
type Outer = undefined

/**
 * 盤上のポイントの状態
 */
type PointState =
  | Stone
  | Empty

/**
 * 要素 4 のタプル
 */
type Quartet<T> = readonly [T, T, T, T]

/**
 * 盤の一筋
 */
type BoardLine = Quartet<PointState>

/**
 * 盤(行のタプルを内包する列のタプル)
 */
type BoardMatrix = Quartet<BoardLine>
/**
 * 盤上かはみ出た位置のポイントの状態
 */
type PointOverhangableState =
  | PointState
  | Outer

/**
 * 一部がはみ出る可能性のある盤の一筋
 */
type BoardOverhangableLine = Quartet<PointOverhangableState>

/**
 * 八方向の筋
 */
type PutResult = readonly [
  /* 左から右 */ BoardLine,
  /* 右から左 */ BoardLine,
  /* 上から下 */ BoardLine,
  /* 下から上 */ BoardLine,
  /* 右上方向 */ BoardOverhangableLine,
  /* 左下方向 */ BoardOverhangableLine,
  /* 右下方向 */ BoardOverhangableLine,
  /* 左上方向 */ BoardOverhangableLine,
]

/**
 * 盤の行列に適用可能なインデックス
 */
type BoardIndex =
  | 0
  | 1
  | 2
  | 3

/**
 * 加算ではみ出るインデックス
 */
type BoardAddictionOverhangedIndex =
  | 4
  | 5
  | 6

/**
 * 減算ではみ出るインデックス
 */
type BoardSubstractionOverhangedIndex =
  | -1
  | -2
  | -3

またまだ序の口です。

コントローラー

一通り準備ができたのでロジック部分にも手を出していきましょう。

位置

盤の位置に関する操作を行う型から作ります。

/**
 * インデックスのインクリメント用マップ
 */
type BoardIndexIncrementMap = readonly [
  1,
  2,
  3,
  null, // インクリメント不能
]

/**
 * インデックスのデクリメント用マップ
 */
type BoardIndexDecrementMap = readonly [
  null, // デクリメント不能
  0,
  1,
  2,
  3,
]

/**
 * インデックスの反転用マップ
 */
type BoardIndexReverseMap = readonly [
  3,
  2,
  1,
  0,
]

/**
 * 指定子から列と行のインデックスマップ
 */
type SelectorMap = {
  readonly A1: readonly [0, 0]
  readonly A2: readonly [0, 1]
  readonly A3: readonly [0, 2]
  readonly A4: readonly [0, 3]
  readonly B1: readonly [1, 0]
  readonly B2: readonly [1, 1]
  readonly B3: readonly [1, 2]
  readonly B4: readonly [1, 3]
  readonly C1: readonly [2, 0]
  readonly C2: readonly [2, 1]
  readonly C3: readonly [2, 2]
  readonly C4: readonly [2, 3]
  readonly D1: readonly [3, 0]
  readonly D2: readonly [3, 1]
  readonly D3: readonly [3, 2]
  readonly D4: readonly [3, 3]
}

/**
 * 指定子
 */
type Selector = keyof SelectorMap

/**
 * インデックスから指定子への復元
 */
type IndexesToSelectorMatrix = readonly [
  readonly ['A1', 'B1', 'C1', 'D1'],
  readonly ['A2', 'B2', 'C2', 'D2'],
  readonly ['A3', 'B3', 'C3', 'D3'],
  readonly ['A4', 'B4', 'C4', 'D4'],
]

Map 類は BoardIndexIncrementMap[3] // -> 4 のように使用します。かなり愚直に書いていますが、愚直に書けるところはあえて愚直に書いた方がパフォーマンス的に良かったりします。

加減算

/**
 * `BoardIndex` の加算
 */
type BoardIndexAddictionMatrix = readonly [
  readonly [0, 1, 2, 3],
  readonly [1, 2, 3, 4],
  readonly [2, 3, 4, 5],
  readonly [3, 4, 5, 6],
]

/**
 * `BoardIndex` の減算
 */
type BoardIndexSubstractionMatrix = readonly [
  readonly [0, -1, -2, -3],
  readonly [1, 0, -1, -2],
  readonly [2, 1, 0, -1],
  readonly [3, 2, 1, 0],
]

これらは BoardIndexAddictionMatrix[2][3] // -> 5 のように使用します。

盤の読み書き

満を期してメインロジックを作っていきます。

切り出し

/**
 * 行のインデックスから取得できる昇順タプル
 */
type GetBoardLineByRowIndex<
  TIndex extends BoardIndex,
  TMatrix extends BoardMatrix,
> = TMatrix[TIndex]

/**
 * 列のインデックスから取得できる昇順タプル
 */
type GetBoardLineByColumnIndex<
  TIndex extends BoardIndex,
  TMatrix extends BoardMatrix,
> = readonly [
  TMatrix[0][TIndex],
  TMatrix[1][TIndex],
  TMatrix[2][TIndex],
  TMatrix[3][TIndex],
]

/**
 * 行のインデックスから取得できる降順タプル
 */
type GetReversedBoardLineByRowIndex<
  TIndex extends BoardIndex,
  TMatrix extends BoardMatrix,
> = readonly [
  TMatrix[TIndex][3],
  TMatrix[TIndex][2],
  TMatrix[TIndex][1],
  TMatrix[TIndex][0],
]

/**
 * 列のインデックスから取得できる降順タプル
 */
type GetReversedBoardLineByColumnIndex<
  TIndex extends BoardIndex,
  TMatrix extends BoardMatrix,
> = readonly [
  TMatrix[3][TIndex],
  TMatrix[2][TIndex],
  TMatrix[1][TIndex],
  TMatrix[0][TIndex],
]

/**
 * 行のインデックスから取得できる右肩上がりの昇順タプル
 */
type GetRisingBoardLineByRowIndex<
  TIndex extends BoardIndex,
  TMatrix extends BoardMatrix,
> = readonly [
  TMatrix[TIndex][BoardIndexSubstractionMatrix[TIndex][0]],
  TMatrix[TIndex][BoardIndexSubstractionMatrix[TIndex][1]],
  TMatrix[TIndex][BoardIndexSubstractionMatrix[TIndex][2]],
  TMatrix[TIndex][BoardIndexSubstractionMatrix[TIndex][3]],
]

/**
 * 行のインデックスから取得できる右肩上がりの昇順タプル
 */
type GetAddictingBoardLineByRowIndex<
  TIndex extends BoardIndex,
  TMatrix extends BoardMatrix,
> = readonly [
  TMatrix[TIndex][BoardIndexAddictionMatrix[TIndex][0]],
  TMatrix[TIndex][BoardIndexAddictionMatrix[TIndex][1]],
  TMatrix[TIndex][BoardIndexAddictionMatrix[TIndex][2]],
  TMatrix[TIndex][BoardIndexAddictionMatrix[TIndex][3]],
]

/**
 * 行のインデックスから取得できる右肩下がりの昇順タプル
 */
type GetSubstractingBoardLineByRowIndex<
  TIndex extends BoardIndex,
  TMatrix extends BoardMatrix,
> = readonly [
  TMatrix[TIndex][BoardIndexSubstractionMatrix[TIndex][0]],
  TMatrix[TIndex][BoardIndexSubstractionMatrix[TIndex][1]],
  TMatrix[TIndex][BoardIndexSubstractionMatrix[TIndex][2]],
  TMatrix[TIndex][BoardIndexSubstractionMatrix[TIndex][3]],
]

/**
 * 行のインデックスから取得できる右肩上がりの降順タプル
 */
type GetReversedAddictingBoardLineByRowIndex<
  TIndex extends BoardIndex,
  TMatrix extends BoardMatrix,
> = readonly [
  TMatrix[TIndex][BoardIndexAddictionMatrix[TIndex][3]],
  TMatrix[TIndex][BoardIndexAddictionMatrix[TIndex][2]],
  TMatrix[TIndex][BoardIndexAddictionMatrix[TIndex][1]],
  TMatrix[TIndex][BoardIndexAddictionMatrix[TIndex][0]],
]

/**
 * 行のインデックスから取得できる右肩下がりの降順タプル
 */
type GetReversedSubstractingBoardLineByRowIndex<
  TIndex extends BoardIndex,
  TMatrix extends BoardMatrix,
> = readonly [
  TMatrix[TIndex][BoardIndexSubstractionMatrix[TIndex][3]],
  TMatrix[TIndex][BoardIndexSubstractionMatrix[TIndex][2]],
  TMatrix[TIndex][BoardIndexSubstractionMatrix[TIndex][1]],
  TMatrix[TIndex][BoardIndexSubstractionMatrix[TIndex][0]],
]

八方向あるのがややこしいですが、そこさえ理解してしまえばかなり愚直なだけのコードであることがわかると思います。

検出

/**
 * `TIndex` 以降の `TLine` 上で `TStone` が一番最初に現れる位置を取得します。
 */
type IndexOf<
  TIndex extends BoardIndex,
  TLine extends BoardOverhangableLine,
  TStone extends Stone,
> = TLine[TIndex] extends Empty | Outer
  ? never
  : TLine[TIndex] extends TStone
    ? TIndex
    : {
        readonly 0: IndexOf<1, TLine, TStone>
        readonly 1: IndexOf<2, TLine, TStone>
        readonly 2: IndexOf<3, TLine, TStone>
        readonly 3: never
      }[TIndex]

少しずつらしくなってきましたね。例えば IndexOf<1, readonly [Empty, WhiteStone, BlackStone, Empty], BlackStone> と書くと、1 以降で BlackStonereadonly [Empty, WhiteStone, BlackStone, Empty] のどこにあるかを返すので、2 が返ります。

筋への書き込み

/**
 * `[手前のインデックス][奥のインデックス]` とした際に石が裏返るかどうか
 */
type PuttingBox<
  TLine extends BoardOverhangableLine,
  TStone extends Stone,
> = readonly [
  readonly [
    never,
    never,
    readonly [TStone, TStone, TStone, TLine[3]],
    readonly [TStone, TStone, TStone, TStone],
  ],
  readonly [
    never,
    never,
    never,
    readonly [TLine[0], TStone, TStone, TStone],
  ],
  readonly [
    never,
    never,
    never,
    never,
  ],
  readonly [
    never,
    never,
    never,
    never,
  ],
]

かなり単純ですが、8x8 になると普通に大きくなります。

盤への書き込み

/**
 * `TContent` から `TFallback` を漉し取って、残った `TContent` かあるいは `TFallback` を返す。
 */
type Fallback<
  TContent,
  TFallback,
> =
  (
    TContent extends TFallback
      ? never
      : TContent
  ) extends never
    ? TFallback
    : TContent

/**
 * `TMatrix` を基に `TPutResult` で置き換えた `BoardMatrix` を返す。
 */
type ReplacingBox<
  TMatrix extends BoardMatrix,
  TPutResult extends PutResult,
> = readonly [
  readonly [
    readonly [
      readonly [
        Fallback<
          | TPutResult[0][0]
          | TPutResult[1][3]
          | TPutResult[2][0]
          | TPutResult[3][3]
          | TPutResult[4][0]
          | TPutResult[5][3]
          | TPutResult[6][0]
          | TPutResult[7][3]
        , TMatrix[0][0]>,
        Fallback<
          | TPutResult[0][1]
        , TMatrix[0][1]>,
        Fallback<
          | TPutResult[0][2]
        , TMatrix[0][2]>,
        Fallback<
          | TPutResult[0][3]
        , TMatrix[0][3]>,
      ],
      readonly [
        Fallback<
          | TPutResult[2][1]
        , TMatrix[1][0]>,
        Fallback<
          | TPutResult[6][1]
        , TMatrix[1][1]>,
        Fallback<
          never
        , TMatrix[1][2]>,
        Fallback<
          never
        , TMatrix[1][3]>,
      ],
      readonly [
        Fallback<
          | TPutResult[2][2]
        , TMatrix[2][0]>,
        Fallback<
          never
        , TMatrix[2][1]>,
        Fallback<
          | TPutResult[6][2]
        , TMatrix[2][2]>,
        Fallback<
          never
        , TMatrix[2][3]>,
      ],
      readonly [
        Fallback<
          | TPutResult[2][3]
        , TMatrix[3][0]>,
        Fallback<
          never
        , TMatrix[3][1]>,
        Fallback<
          never
        , TMatrix[3][2]>,
        Fallback<
          | TPutResult[6][3]
        , TMatrix[3][3]>,
      ],
    ],
    readonly [
      readonly [
        Fallback<
          | TPutResult[1][3]
        , TMatrix[0][0]>,
        Fallback<
          | TPutResult[0][1]
          | TPutResult[1][2]
          | TPutResult[2][0]
          | TPutResult[3][3]
          | TPutResult[4][1]
          | TPutResult[5][2]
          | TPutResult[6][1]
          | TPutResult[7][2]
        , TMatrix[0][1]>,
        Fallback<
          | TPutResult[0][2]
        , TMatrix[0][2]>,
        Fallback<
          | TPutResult[0][3]
        , TMatrix[0][3]>,
      ],
      readonly [
        Fallback<
          | TPutResult[5][3]
        , TMatrix[1][0]>,
        Fallback<
          | TPutResult[2][1]
        , TMatrix[1][1]>,
        Fallback<
          | TPutResult[6][2]
        , TMatrix[1][2]>,
        Fallback<
          never
        , TMatrix[1][3]>,
      ],
      readonly [
        Fallback<
          never
        , TMatrix[2][0]>,
        Fallback<
          | TPutResult[2][2]
        , TMatrix[2][1]>,
        Fallback<
          never
        , TMatrix[2][2]>,
        Fallback<
          | TPutResult[6][3]
        , TMatrix[2][3]>,
      ],
      readonly [
        Fallback<
          never
        , TMatrix[3][0]>,
        Fallback<
          | TPutResult[2][3]
        , TMatrix[3][1]>,
        Fallback<
          never
        , TMatrix[3][2]>,
        Fallback<
          never
        , TMatrix[3][3]>,
      ],
    ],
    readonly [
      readonly [
        Fallback<
          | TPutResult[1][3]
        , TMatrix[0][0]>,
        Fallback<
          | TPutResult[1][2]
        , TMatrix[0][1]>,
        Fallback<
          | TPutResult[0][2]
          | TPutResult[1][1]
          | TPutResult[2][0]
          | TPutResult[3][3]
          | TPutResult[4][2]
          | TPutResult[5][1]
          | TPutResult[6][2]
          | TPutResult[7][1]
        , TMatrix[0][2]>,
        Fallback<
          | TPutResult[0][3]
        , TMatrix[0][3]>,
      ],
      readonly [
        Fallback<
          never
        , TMatrix[1][0]>,
        Fallback<
          | TPutResult[5][2]
        , TMatrix[1][1]>,
        Fallback<
          | TPutResult[2][1]
        , TMatrix[1][2]>,
        Fallback<
          | TPutResult[6][3]
        , TMatrix[1][3]>,
      ],
      readonly [
        Fallback<
          | TPutResult[5][3]
        , TMatrix[2][0]>,
        Fallback<
          never
        , TMatrix[2][1]>,
        Fallback<
          | TPutResult[2][2]
        , TMatrix[2][2]>,
        Fallback<
          never
        , TMatrix[2][3]>,
      ],
      readonly [
        Fallback<
          never
        , TMatrix[3][0]>,
        Fallback<
          never
        , TMatrix[3][1]>,
        Fallback<
          | TPutResult[2][3]
        , TMatrix[3][2]>,
        Fallback<
          never
        , TMatrix[3][3]>,
      ],
    ],
    readonly [
      readonly [
        Fallback<
          | TPutResult[1][3]
        , TMatrix[0][0]>,
        Fallback<
          | TPutResult[1][2]
        , TMatrix[0][1]>,
        Fallback<
          | TPutResult[1][1]
        , TMatrix[0][2]>,
        Fallback<
          | TPutResult[0][3]
          | TPutResult[1][0]
          | TPutResult[2][0]
          | TPutResult[3][3]
          | TPutResult[4][3]
          | TPutResult[5][0]
          | TPutResult[6][3]
          | TPutResult[7][0]
        , TMatrix[0][3]>,
      ],
      readonly [
        Fallback<
          never
        , TMatrix[1][0]>,
        Fallback<
          never
        , TMatrix[1][1]>,
        Fallback<
          | TPutResult[5][1]
        , TMatrix[1][2]>,
        Fallback<
          | TPutResult[2][1]
        , TMatrix[1][3]>,
      ],
      readonly [
        Fallback<
          never
        , TMatrix[2][0]>,
        Fallback<
          | TPutResult[5][2]
        , TMatrix[2][1]>,
        Fallback<
          never
        , TMatrix[2][2]>,
        Fallback<
          | TPutResult[2][2]
        , TMatrix[2][3]>,
      ],
      readonly [
        Fallback<
          | TPutResult[5][3]
        , TMatrix[3][0]>,
        Fallback<
          never
        , TMatrix[3][1]>,
        Fallback<
          never
        , TMatrix[3][2]>,
        Fallback<
          | TPutResult[2][3]
        , TMatrix[3][3]>,
      ],
    ],
  ],
  readonly [
    readonly [
      readonly [
        Fallback<
          | TPutResult[3][3]
        , TMatrix[0][0]>,
        Fallback<
          | TPutResult[4][1]
        , TMatrix[0][1]>,
        Fallback<
          never
        , TMatrix[0][2]>,
        Fallback<
          never
        , TMatrix[0][3]>,
      ],
      readonly [
        Fallback<
          | TPutResult[0][0]
          | TPutResult[1][3]
          | TPutResult[2][1]
          | TPutResult[3][2]
          | TPutResult[4][0]
          | TPutResult[5][3]
          | TPutResult[6][0]
          | TPutResult[7][3]
        , TMatrix[1][0]>,
        Fallback<
          | TPutResult[0][1]
        , TMatrix[1][1]>,
        Fallback<
          | TPutResult[0][2]
        , TMatrix[1][2]>,
        Fallback<
          | TPutResult[0][3]
        , TMatrix[1][3]>,
      ],
      readonly [
        Fallback<
          | TPutResult[2][2]
        , TMatrix[2][0]>,
        Fallback<
          | TPutResult[6][1]
        , TMatrix[2][1]>,
        Fallback<
          never
        , TMatrix[2][2]>,
        Fallback<
          never
        , TMatrix[2][3]>,
      ],
      readonly [
        Fallback<
          | TPutResult[2][3]
        , TMatrix[3][0]>,
        Fallback<
          never
        , TMatrix[3][1]>,
        Fallback<
          | TPutResult[6][2]
        , TMatrix[3][2]>,
        Fallback<
          never
        , TMatrix[3][3]>,
      ],
    ],
    readonly [
      readonly [
        Fallback<
          | TPutResult[7][3]
        , TMatrix[0][0]>,
        Fallback<
          | TPutResult[3][3]
        , TMatrix[0][1]>,
        Fallback<
          | TPutResult[4][2]
        , TMatrix[0][2]>,
        Fallback<
          never
        , TMatrix[0][3]>,
      ],
      readonly [
        Fallback<
          | TPutResult[1][3]
        , TMatrix[1][0]>,
        Fallback<
          | TPutResult[0][1]
          | TPutResult[1][2]
          | TPutResult[2][1]
          | TPutResult[3][2]
          | TPutResult[4][1]
          | TPutResult[5][2]
          | TPutResult[6][1]
          | TPutResult[7][2]
        , TMatrix[1][1]>,
        Fallback<
          | TPutResult[0][2]
        , TMatrix[1][2]>,
        Fallback<
          | TPutResult[0][3]
        , TMatrix[1][3]>,
      ],
      readonly [
        Fallback<
          | TPutResult[5][3]
        , TMatrix[2][0]>,
        Fallback<
          | TPutResult[2][2]
        , TMatrix[2][1]>,
        Fallback<
          | TPutResult[6][2]
        , TMatrix[2][2]>,
        Fallback<
          never
        , TMatrix[2][3]>,
      ],
      readonly [
        Fallback<
          never
        , TMatrix[3][0]>,
        Fallback<
          | TPutResult[2][3]
        , TMatrix[3][1]>,
        Fallback<
          never
        , TMatrix[3][2]>,
        Fallback<
          | TPutResult[6][3]
        , TMatrix[3][3]>,
      ],
    ],
    readonly [
      readonly [
        Fallback<
          never
        , TMatrix[0][0]>,
        Fallback<
          | TPutResult[7][2]
        , TMatrix[0][1]>,
        Fallback<
          | TPutResult[3][3]
        , TMatrix[0][2]>,
        Fallback<
          | TPutResult[4][3]
        , TMatrix[0][3]>,
      ],
      readonly [
        Fallback<
          | TPutResult[1][3]
        , TMatrix[1][0]>,
        Fallback<
          | TPutResult[1][2]
        , TMatrix[1][1]>,
        Fallback<
          | TPutResult[0][2]
          | TPutResult[1][1]
          | TPutResult[2][1]
          | TPutResult[3][2]
          | TPutResult[4][2]
          | TPutResult[5][1]
          | TPutResult[6][2]
          | TPutResult[7][1]
        , TMatrix[1][2]>,
        Fallback<
          | TPutResult[0][3]
        , TMatrix[1][3]>,
      ],
      readonly [
        Fallback<
          never
        , TMatrix[2][0]>,
        Fallback<
          | TPutResult[5][2]
        , TMatrix[2][1]>,
        Fallback<
          | TPutResult[2][2]
        , TMatrix[2][2]>,
        Fallback<
          | TPutResult[6][3]
        , TMatrix[2][3]>,
      ],
      readonly [
        Fallback<
          | TPutResult[5][3]
        , TMatrix[3][0]>,
        Fallback<
          never
        , TMatrix[3][1]>,
        Fallback<
          | TPutResult[2][3]
        , TMatrix[3][2]>,
        Fallback<
          never
        , TMatrix[3][3]>,
      ],
    ],
    readonly [
      readonly [
        Fallback<
          never
        , TMatrix[0][0]>,
        Fallback<
          never
        , TMatrix[0][1]>,
        Fallback<
          | TPutResult[7][1]
        , TMatrix[0][2]>,
        Fallback<
          | TPutResult[3][3]
        , TMatrix[0][3]>,
      ],
      readonly [
        Fallback<
          | TPutResult[1][3]
        , TMatrix[1][0]>,
        Fallback<
          | TPutResult[1][2]
        , TMatrix[1][1]>,
        Fallback<
          | TPutResult[1][1]
        , TMatrix[1][2]>,
        Fallback<
          | TPutResult[0][3]
          | TPutResult[1][0]
          | TPutResult[2][1]
          | TPutResult[3][2]
          | TPutResult[4][3]
          | TPutResult[5][0]
          | TPutResult[6][3]
          | TPutResult[7][0]
        , TMatrix[1][3]>,
      ],
      readonly [
        Fallback<
          never
        , TMatrix[2][0]>,
        Fallback<
          never
        , TMatrix[2][1]>,
        Fallback<
          | TPutResult[5][1]
        , TMatrix[2][2]>,
        Fallback<
          | TPutResult[2][2]
        , TMatrix[2][3]>,
      ],
      readonly [
        Fallback<
          never
        , TMatrix[3][0]>,
        Fallback<
          | TPutResult[5][2]
        , TMatrix[3][1]>,
        Fallback<
          never
        , TMatrix[3][2]>,
        Fallback<
          | TPutResult[2][3]
        , TMatrix[3][3]>,
      ],
    ],
  ],
  readonly [
    readonly [
      readonly [
        Fallback<
          | TPutResult[3][3]
        , TMatrix[0][0]>,
        Fallback<
          never
        , TMatrix[0][1]>,
        Fallback<
          | TPutResult[4][2]
        , TMatrix[0][2]>,
        Fallback<
          never
        , TMatrix[0][3]>,
      ],
      readonly [
        Fallback<
          | TPutResult[3][2]
        , TMatrix[1][0]>,
        Fallback<
          | TPutResult[4][1]
        , TMatrix[1][1]>,
        Fallback<
          never
        , TMatrix[1][2]>,
        Fallback<
          never
        , TMatrix[1][3]>,
      ],
      readonly [
        Fallback<
          | TPutResult[0][0]
          | TPutResult[1][3]
          | TPutResult[2][2]
          | TPutResult[3][1]
          | TPutResult[4][0]
          | TPutResult[5][3]
          | TPutResult[6][0]
          | TPutResult[7][3]
        , TMatrix[2][0]>,
        Fallback<
          | TPutResult[0][1]
        , TMatrix[2][1]>,
        Fallback<
          | TPutResult[0][2]
        , TMatrix[2][2]>,
        Fallback<
          | TPutResult[0][3]
        , TMatrix[2][3]>,
      ],
      readonly [
        Fallback<
          | TPutResult[2][3]
        , TMatrix[3][0]>,
        Fallback<
          | TPutResult[6][1]
        , TMatrix[3][1]>,
        Fallback<
          never
        , TMatrix[3][2]>,
        Fallback<
          never
        , TMatrix[3][3]>,
      ],
    ],
    readonly [
      readonly [
        Fallback<
          never
        , TMatrix[0][0]>,
        Fallback<
          | TPutResult[3][3]
        , TMatrix[0][1]>,
        Fallback<
          never
        , TMatrix[0][2]>,
        Fallback<
          | TPutResult[4][3]
        , TMatrix[0][3]>,
      ],
      readonly [
        Fallback<
          | TPutResult[7][3]
        , TMatrix[1][0]>,
        Fallback<
          | TPutResult[3][2]
        , TMatrix[1][1]>,
        Fallback<
          | TPutResult[4][2]
        , TMatrix[1][2]>,
        Fallback<
          never
        , TMatrix[1][3]>,
      ],
      readonly [
        Fallback<
          | TPutResult[1][3]
        , TMatrix[2][0]>,
        Fallback<
          | TPutResult[0][1]
          | TPutResult[1][2]
          | TPutResult[2][2]
          | TPutResult[3][1]
          | TPutResult[4][1]
          | TPutResult[5][2]
          | TPutResult[6][1]
          | TPutResult[7][2]
        , TMatrix[2][1]>,
        Fallback<
          | TPutResult[0][2]
        , TMatrix[2][2]>,
        Fallback<
          | TPutResult[0][3]
        , TMatrix[2][3]>,
      ],
      readonly [
        Fallback<
          | TPutResult[5][3]
        , TMatrix[3][0]>,
        Fallback<
          | TPutResult[2][3]
        , TMatrix[3][1]>,
        Fallback<
          | TPutResult[6][2]
        , TMatrix[3][2]>,
        Fallback<
          never
        , TMatrix[3][3]>,
      ],
    ],
    readonly [
      readonly [
        Fallback<
          | TPutResult[7][3]
        , TMatrix[0][0]>,
        Fallback<
          never
        , TMatrix[0][1]>,
        Fallback<
          | TPutResult[3][3]
        , TMatrix[0][2]>,
        Fallback<
          never
        , TMatrix[0][3]>,
      ],
      readonly [
        Fallback<
          never
        , TMatrix[1][0]>,
        Fallback<
          | TPutResult[7][2]
        , TMatrix[1][1]>,
        Fallback<
          | TPutResult[3][2]
        , TMatrix[1][2]>,
        Fallback<
          | TPutResult[4][3]
        , TMatrix[1][3]>,
      ],
      readonly [
        Fallback<
          | TPutResult[1][3]
        , TMatrix[2][0]>,
        Fallback<
          | TPutResult[1][2]
        , TMatrix[2][1]>,
        Fallback<
          | TPutResult[0][2]
          | TPutResult[1][1]
          | TPutResult[2][2]
          | TPutResult[3][1]
          | TPutResult[4][2]
          | TPutResult[5][1]
          | TPutResult[6][2]
          | TPutResult[7][1]
        , TMatrix[2][2]>,
        Fallback<
          | TPutResult[0][3]
        , TMatrix[2][3]>,
      ],
      readonly [
        Fallback<
          never
        , TMatrix[3][0]>,
        Fallback<
          | TPutResult[5][2]
        , TMatrix[3][1]>,
        Fallback<
          | TPutResult[2][3]
        , TMatrix[3][2]>,
        Fallback<
          | TPutResult[6][3]
        , TMatrix[3][3]>,
      ],
    ],
    readonly [
      readonly [
        Fallback<
          never
        , TMatrix[0][0]>,
        Fallback<
          | TPutResult[7][2]
        , TMatrix[0][1]>,
        Fallback<
          never
        , TMatrix[0][2]>,
        Fallback<
          | TPutResult[3][3]
        , TMatrix[0][3]>,
      ],
      readonly [
        Fallback<
          never
        , TMatrix[1][0]>,
        Fallback<
          never
        , TMatrix[1][1]>,
        Fallback<
          | TPutResult[7][1]
        , TMatrix[1][2]>,
        Fallback<
          | TPutResult[3][2]
        , TMatrix[1][3]>,
      ],
      readonly [
        Fallback<
          | TPutResult[1][3]
        , TMatrix[2][0]>,
        Fallback<
          | TPutResult[1][2]
        , TMatrix[2][1]>,
        Fallback<
          | TPutResult[1][1]
        , TMatrix[2][2]>,
        Fallback<
          | TPutResult[0][3]
          | TPutResult[1][0]
          | TPutResult[2][2]
          | TPutResult[3][1]
          | TPutResult[4][3]
          | TPutResult[5][0]
          | TPutResult[6][3]
          | TPutResult[7][0]
        , TMatrix[2][3]>,
      ],
      readonly [
        Fallback<
          never
        , TMatrix[3][0]>,
        Fallback<
          never
        , TMatrix[3][1]>,
        Fallback<
          | TPutResult[5][1]
        , TMatrix[3][2]>,
        Fallback<
          | TPutResult[2][3]
        , TMatrix[3][3]>,
      ],
    ],
  ],
  readonly [
    readonly [
      readonly [
        Fallback<
          | TPutResult[3][3]
        , TMatrix[0][0]>,
        Fallback<
          never
        , TMatrix[0][1]>,
        Fallback<
          never
        , TMatrix[0][2]>,
        Fallback<
          | TPutResult[4][3]
        , TMatrix[0][3]>,
      ],
      readonly [
        Fallback<
          | TPutResult[3][2]
        , TMatrix[1][0]>,
        Fallback<
          never
        , TMatrix[1][1]>,
        Fallback<
          | TPutResult[4][2]
        , TMatrix[1][2]>,
        Fallback<
          never
        , TMatrix[1][3]>,
      ],
      readonly [
        Fallback<
          | TPutResult[3][1]
        , TMatrix[2][0]>,
        Fallback<
          | TPutResult[4][1]
        , TMatrix[2][1]>,
        Fallback<
          never
        , TMatrix[2][2]>,
        Fallback<
          never
        , TMatrix[2][3]>,
      ],
      readonly [
        Fallback<
          | TPutResult[0][0]
          | TPutResult[1][3]
          | TPutResult[2][3]
          | TPutResult[3][0]
          | TPutResult[4][0]
          | TPutResult[5][3]
          | TPutResult[6][0]
          | TPutResult[7][3]
        , TMatrix[3][0]>,
        Fallback<
          | TPutResult[0][1]
        , TMatrix[3][1]>,
        Fallback<
          | TPutResult[0][2]
        , TMatrix[3][2]>,
        Fallback<
          | TPutResult[0][3]
        , TMatrix[3][3]>,
      ],
    ],
    readonly [
      readonly [
        Fallback<
          never
        , TMatrix[0][0]>,
        Fallback<
          | TPutResult[3][3]
        , TMatrix[0][1]>,
        Fallback<
          never
        , TMatrix[0][2]>,
        Fallback<
          never
        , TMatrix[0][3]>,
      ],
      readonly [
        Fallback<
          never
        , TMatrix[1][0]>,
        Fallback<
          | TPutResult[3][2]
        , TMatrix[1][1]>,
        Fallback<
          never
        , TMatrix[1][2]>,
        Fallback<
          | TPutResult[4][3]
        , TMatrix[1][3]>,
      ],
      readonly [
        Fallback<
          | TPutResult[7][3]
        , TMatrix[2][0]>,
        Fallback<
          | TPutResult[3][1]
        , TMatrix[2][1]>,
        Fallback<
          | TPutResult[4][2]
        , TMatrix[2][2]>,
        Fallback<
          never
        , TMatrix[2][3]>,
      ],
      readonly [
        Fallback<
          | TPutResult[1][3]
        , TMatrix[3][0]>,
        Fallback<
          | TPutResult[0][1]
          | TPutResult[1][2]
          | TPutResult[2][3]
          | TPutResult[3][0]
          | TPutResult[4][1]
          | TPutResult[5][2]
          | TPutResult[6][1]
          | TPutResult[7][2]
        , TMatrix[3][1]>,
        Fallback<
          | TPutResult[0][2]
        , TMatrix[3][2]>,
        Fallback<
          | TPutResult[0][3]
        , TMatrix[3][3]>,
      ],
    ],
    readonly [
      readonly [
        Fallback<
          never
        , TMatrix[0][0]>,
        Fallback<
          never
        , TMatrix[0][1]>,
        Fallback<
          | TPutResult[3][3]
        , TMatrix[0][2]>,
        Fallback<
          never
        , TMatrix[0][3]>,
      ],
      readonly [
        Fallback<
          | TPutResult[7][3]
        , TMatrix[1][0]>,
        Fallback<
          never
        , TMatrix[1][1]>,
        Fallback<
          | TPutResult[3][2]
        , TMatrix[1][2]>,
        Fallback<
          never
        , TMatrix[1][3]>,
      ],
      readonly [
        Fallback<
          never
        , TMatrix[2][0]>,
        Fallback<
          | TPutResult[7][2]
        , TMatrix[2][1]>,
        Fallback<
          | TPutResult[3][1]
        , TMatrix[2][2]>,
        Fallback<
          | TPutResult[4][3]
        , TMatrix[2][3]>,
      ],
      readonly [
        Fallback<
          | TPutResult[1][3]
        , TMatrix[3][0]>,
        Fallback<
          | TPutResult[1][2]
        , TMatrix[3][1]>,
        Fallback<
          | TPutResult[0][2]
          | TPutResult[1][1]
          | TPutResult[2][3]
          | TPutResult[3][0]
          | TPutResult[4][2]
          | TPutResult[5][1]
          | TPutResult[6][2]
          | TPutResult[7][1]
        , TMatrix[3][2]>,
        Fallback<
          | TPutResult[0][3]
        , TMatrix[3][3]>,
      ],
    ],
    readonly [
      readonly [
        Fallback<
          | TPutResult[7][3]
        , TMatrix[0][0]>,
        Fallback<
          never
        , TMatrix[0][1]>,
        Fallback<
          never
        , TMatrix[0][2]>,
        Fallback<
          | TPutResult[3][3]
        , TMatrix[0][3]>,
      ],
      readonly [
        Fallback<
          never
        , TMatrix[1][0]>,
        Fallback<
          | TPutResult[7][2]
        , TMatrix[1][1]>,
        Fallback<
          never
        , TMatrix[1][2]>,
        Fallback<
          | TPutResult[3][2]
        , TMatrix[1][3]>,
      ],
      readonly [
        Fallback<
          never
        , TMatrix[2][0]>,
        Fallback<
          never
        , TMatrix[2][1]>,
        Fallback<
          | TPutResult[7][1]
        , TMatrix[2][2]>,
        Fallback<
          | TPutResult[3][1]
        , TMatrix[2][3]>,
      ],
      readonly [
        Fallback<
          | TPutResult[1][3]
        , TMatrix[3][0]>,
        Fallback<
          | TPutResult[1][2]
        , TMatrix[3][1]>,
        Fallback<
          | TPutResult[1][1]
        , TMatrix[3][2]>,
        Fallback<
          | TPutResult[0][3]
          | TPutResult[1][0]
          | TPutResult[2][3]
          | TPutResult[3][0]
          | TPutResult[4][3]
          | TPutResult[5][0]
          | TPutResult[6][3]
          | TPutResult[7][0]
        , TMatrix[3][3]>,
      ],
    ],
  ],
]

今までのコードと比にならないくらいのサイズになってしまいました。ここでは PutResult から実際に置き換えるべき場所の置き換えを担います。

石を置く

/**
 * 筋上で石を置いてみて、石が裏返る場合はその筋を返すが、石が裏返らない場合は `never` を返す。
 */
type PutOnLineAt<
  TIndex extends BoardIndex,
  TLine extends BoardOverhangableLine,
  TStone extends Stone,
> =
  TLine[BoardIndexIncrementMap[TIndex]] extends AnotherStoneMap[TStone]
    ? IndexOf<BoardIndexIncrementMap[TIndex], TLine, TStone> extends never
      ? never // `(readonly [never, 42])[never]` が `42` になるのを回避する。
      : PuttingBox<TLine, TStone>[TIndex][IndexOf<BoardIndexIncrementMap[TIndex], TLine, TStone>]
    : never

/**
 * 座標に基づいて石を置いてみて、その位置に石を置いてどこかが裏返るなら石を置いた後の盤を返し、そうでない場合は `never` を返す。
 */
type PutAt<
  TColumnIndex extends BoardIndex,
  TRowIndex extends BoardIndex,
  TMatrix extends BoardMatrix,
  TStone extends Stone,
> = readonly [
  PutOnLineAt<TColumnIndex, GetBoardLineByRowIndex<TRowIndex, TMatrix>, TStone>,
  PutOnLineAt<BoardIndexReverseMap[TColumnIndex], GetReversedBoardLineByRowIndex<TRowIndex, TMatrix>, TStone>,
  PutOnLineAt<TRowIndex, GetBoardLineByColumnIndex<TColumnIndex, TMatrix>, TStone>,
  PutOnLineAt<BoardIndexReverseMap[TRowIndex], GetReversedBoardLineByColumnIndex<TColumnIndex, TMatrix>, TStone>,
  PutOnLineAt<TColumnIndex, GetAddictingBoardLineByRowIndex<TRowIndex, TMatrix>, TStone>,
  PutOnLineAt<BoardIndexReverseMap[TColumnIndex], GetReversedAddictingBoardLineByRowIndex<TRowIndex, TMatrix>, TStone>,
  PutOnLineAt<TColumnIndex, GetSubstractingBoardLineByRowIndex<TRowIndex, TMatrix>, TStone>,
  PutOnLineAt<BoardIndexReverseMap[TColumnIndex], GetReversedSubstractingBoardLineByRowIndex<TRowIndex, TMatrix>, TStone>,
] extends infer OPutResult
  ? OPutResult extends readonly never[]
    ? never
    : ReplacingBox<
        TMatrix,
        // @ts-expect-error inferred type is poor
        OPutResult,
      >[TRowIndex][TColumnIndex]
  : never

/**
 * 置ける場所全てに対して置いてみる
 */
type PuttableMatrix<
  TMatrix extends BoardMatrix,
  TStone extends Stone,
> = readonly [
  readonly [
    TMatrix[0][0] extends Empty ? PutAt<0, 0, TMatrix, TStone> : never,
    TMatrix[0][1] extends Empty ? PutAt<1, 0, TMatrix, TStone> : never,
    TMatrix[0][2] extends Empty ? PutAt<2, 0, TMatrix, TStone> : never,
    TMatrix[0][3] extends Empty ? PutAt<3, 0, TMatrix, TStone> : never,
  ],
  readonly [
    TMatrix[1][0] extends Empty ? PutAt<0, 1, TMatrix, TStone> : never,
    TMatrix[1][1] extends Empty ? PutAt<1, 1, TMatrix, TStone> : never,
    TMatrix[1][2] extends Empty ? PutAt<2, 1, TMatrix, TStone> : never,
    TMatrix[1][3] extends Empty ? PutAt<3, 1, TMatrix, TStone> : never,
  ],
  readonly [
    TMatrix[2][0] extends Empty ? PutAt<0, 2, TMatrix, TStone> : never,
    TMatrix[2][1] extends Empty ? PutAt<1, 2, TMatrix, TStone> : never,
    TMatrix[2][2] extends Empty ? PutAt<2, 2, TMatrix, TStone> : never,
    TMatrix[2][3] extends Empty ? PutAt<3, 2, TMatrix, TStone> : never,
  ],
  readonly [
    TMatrix[3][0] extends Empty ? PutAt<0, 3, TMatrix, TStone> : never,
    TMatrix[3][1] extends Empty ? PutAt<1, 3, TMatrix, TStone> : never,
    TMatrix[3][2] extends Empty ? PutAt<2, 3, TMatrix, TStone> : never,
    TMatrix[3][3] extends Empty ? PutAt<3, 3, TMatrix, TStone> : never,
  ],
]

/**
 * 筋の石の数を数える。
 */
type CountLine<
  TLine extends BoardLine,
> = TLine[0] extends BlackStone
  ? TLine[1] extends BlackStone
    ? TLine[2] extends BlackStone
      ? TLine[3] extends BlackStone
        ? readonly [4, 0]
        : TLine[3] extends WhiteStone
          ? readonly [3, 1]
          : readonly [3, 0]
      : TLine[2] extends WhiteStone
        ? TLine[3] extends BlackStone
          ? readonly [3, 1]
          : TLine[3] extends WhiteStone
            ? readonly [2, 2]
            : readonly [2, 1]
        : TLine[3] extends BlackStone
          ? readonly [3, 0]
          : TLine[3] extends WhiteStone
            ? readonly [2, 1]
            : readonly [2, 0]
    : TLine[1] extends WhiteStone
      ? TLine[2] extends BlackStone
        ? TLine[3] extends BlackStone
          ? readonly [3, 1]
          : TLine[3] extends WhiteStone
            ? readonly [2, 2]
            : readonly [2, 1]
        : TLine[2] extends WhiteStone
          ? TLine[3] extends BlackStone
            ? readonly [2, 2]
            : TLine[3] extends WhiteStone
              ? readonly [1, 3]
              : readonly [1, 2]
          : TLine[3] extends BlackStone
            ? readonly [2, 1]
            : TLine[3] extends WhiteStone
              ? readonly [1, 2]
              : readonly [1, 1]
      : TLine[2] extends BlackStone
        ? TLine[3] extends BlackStone
          ? readonly [3, 0]
          : TLine[3] extends WhiteStone
            ? readonly [2, 1]
            : readonly [2, 0]
        : TLine[2] extends WhiteStone
          ? TLine[3] extends BlackStone
            ? readonly [2, 1]
            : TLine[3] extends WhiteStone
              ? readonly [1, 2]
              : readonly [1, 1]
          : TLine[3] extends BlackStone
            ? readonly [2, 0]
            : TLine[3] extends WhiteStone
              ? readonly [1, 1]
              : readonly [1, 0]
  : TLine[0] extends WhiteStone
    ? TLine[1] extends BlackStone
      ? TLine[2] extends BlackStone
        ? TLine[3] extends BlackStone
          ? readonly [3, 1]
          : TLine[3] extends WhiteStone
            ? readonly [2, 2]
            : readonly [2, 1]
        : TLine[2] extends WhiteStone
          ? TLine[3] extends BlackStone
            ? readonly [2, 2]
            : TLine[3] extends WhiteStone
              ? readonly [1, 3]
              : readonly [1, 2]
          : TLine[3] extends BlackStone
            ? readonly [2, 1]
            : TLine[3] extends WhiteStone
              ? readonly [1, 2]
              : readonly [1, 1]
      : TLine[1] extends WhiteStone
        ? TLine[2] extends BlackStone
          ? TLine[3] extends BlackStone
            ? readonly [2, 2]
            : TLine[3] extends WhiteStone
              ? readonly [1, 3]
              : readonly [1, 2]
          : TLine[2] extends WhiteStone
            ? TLine[3] extends BlackStone
              ? readonly [1, 3]
              : TLine[3] extends WhiteStone
                ? readonly [0, 4]
                : readonly [0, 3]
            : TLine[3] extends BlackStone
              ? readonly [1, 2]
              : TLine[3] extends WhiteStone
                ? readonly [0, 3]
                : readonly [0, 2]
        : TLine[2] extends WhiteStone
          ? TLine[3] extends BlackStone
            ? readonly [2, 1]
            : TLine[3] extends WhiteStone
              ? readonly [1, 2]
              : readonly [1, 1]
          : TLine[2] extends WhiteStone
            ? TLine[3] extends BlackStone
              ? readonly [1, 2]
              : TLine[3] extends WhiteStone
                ? readonly [0, 3]
                : readonly [0, 2]
            : TLine[3] extends BlackStone
              ? readonly [1, 1]
              : TLine[3] extends WhiteStone
                ? readonly [0, 2]
                : readonly [0, 1]
    : TLine[1] extends BlackStone
      ? TLine[2] extends BlackStone
        ? TLine[3] extends BlackStone
          ? readonly [3, 0]
          : TLine[3] extends WhiteStone
            ? readonly [2, 1]
            : readonly [2, 0]
        : TLine[2] extends WhiteStone
          ? TLine[3] extends BlackStone
            ? readonly [2, 1]
            : TLine[3] extends WhiteStone
              ? readonly [1, 2]
              : readonly [1, 1]
          : TLine[3] extends BlackStone
            ? readonly [2, 0]
            : TLine[3] extends WhiteStone
              ? readonly [1, 1]
              : readonly [1, 0]
      : TLine[1] extends WhiteStone
        ? TLine[2] extends BlackStone
          ? TLine[3] extends BlackStone
            ? readonly [2, 1]
            : TLine[3] extends WhiteStone
              ? readonly [1, 2]
              : readonly [1, 1]
          : TLine[2] extends WhiteStone
            ? TLine[3] extends BlackStone
              ? readonly [1, 2]
              : TLine[3] extends WhiteStone
                ? readonly [0, 3]
                : readonly [0, 2]
            : TLine[3] extends BlackStone
              ? readonly [1, 1]
              : TLine[3] extends WhiteStone
                ? readonly [0, 2]
                : readonly [0, 1]
        : TLine[2] extends BlackStone
          ? TLine[3] extends BlackStone
            ? readonly [2, 0]
            : TLine[3] extends WhiteStone
              ? readonly [1, 1]
              : readonly [1, 0]
          : TLine[2] extends WhiteStone
            ? TLine[3] extends BlackStone
              ? readonly [1, 1]
              : TLine[3] extends WhiteStone
                ? readonly [0, 2]
                : readonly [0, 1]
            : TLine[3] extends BlackStone
              ? readonly [1, 0]
              : TLine[3] extends WhiteStone
                ? readonly [0, 1]
                : readonly [0, 0]

/**
 * 全行のカウントの加算
 */
type CountAddiction = readonly [
  readonly [
    readonly [
      readonly [0, 1, 2, 3, 4],
      readonly [1, 2, 3, 4, 5],
      readonly [2, 3, 4, 5, 6],
      readonly [3, 4, 5, 6, 7],
      readonly [4, 5, 6, 7, 8],
    ],
    readonly [
      readonly [1, 2, 3, 4, 5],
      readonly [2, 3, 4, 5, 6],
      readonly [3, 4, 5, 6, 7],
      readonly [4, 5, 6, 7, 8],
      readonly [5, 6, 7, 8, 9],
    ],
    readonly [
      readonly [2, 3, 4, 5, 6],
      readonly [3, 4, 5, 6, 7],
      readonly [4, 5, 6, 7, 8],
      readonly [5, 6, 7, 8, 9],
      readonly [6, 7, 8, 9, 10],
    ],
    readonly [
      readonly [3, 4, 5, 6, 7],
      readonly [4, 5, 6, 7, 8],
      readonly [5, 6, 7, 8, 9],
      readonly [6, 7, 8, 9, 10],
      readonly [7, 8, 9, 10, 11],
    ],
    readonly [
      readonly [4, 5, 6, 7, 8],
      readonly [5, 6, 7, 8, 9],
      readonly [6, 7, 8, 9, 10],
      readonly [7, 8, 9, 10, 11],
      readonly [8, 9, 10, 11, 12],
    ],
  ],
  readonly [
    readonly [
      readonly [1, 2, 3, 4, 5],
      readonly [2, 3, 4, 5, 6],
      readonly [3, 4, 5, 6, 7],
      readonly [4, 5, 6, 7, 8],
      readonly [5, 6, 7, 8, 9],
    ],
    readonly [
      readonly [2, 3, 4, 5, 6],
      readonly [3, 4, 5, 6, 7],
      readonly [4, 5, 6, 7, 8],
      readonly [5, 6, 7, 8, 9],
      readonly [6, 7, 8, 9, 10],
    ],
    readonly [
      readonly [3, 4, 5, 6, 7],
      readonly [4, 5, 6, 7, 8],
      readonly [5, 6, 7, 8, 9],
      readonly [6, 7, 8, 9, 10],
      readonly [7, 8, 9, 10, 11],
    ],
    readonly [
      readonly [4, 5, 6, 7, 8],
      readonly [5, 6, 7, 8, 9],
      readonly [6, 7, 8, 9, 10],
      readonly [7, 8, 9, 10, 11],
      readonly [8, 9, 10, 11, 12],
    ],
    readonly [
      readonly [5, 6, 7, 8, 9],
      readonly [6, 7, 8, 9, 10],
      readonly [7, 8, 9, 10, 11],
      readonly [8, 9, 10, 11, 12],
      readonly [9, 10, 11, 12, 13],
    ],
  ],
  readonly [
    readonly [
      readonly [2, 3, 4, 5, 6],
      readonly [3, 4, 5, 6, 7],
      readonly [4, 5, 6, 7, 8],
      readonly [5, 6, 7, 8, 9],
      readonly [6, 7, 8, 9, 10],
    ],
    readonly [
      readonly [3, 4, 5, 6, 7],
      readonly [4, 5, 6, 7, 8],
      readonly [5, 6, 7, 8, 9],
      readonly [6, 7, 8, 9, 10],
      readonly [7, 8, 9, 10, 11],
    ],
    readonly [
      readonly [4, 5, 6, 7, 8],
      readonly [5, 6, 7, 8, 9],
      readonly [6, 7, 8, 9, 10],
      readonly [7, 8, 9, 10, 11],
      readonly [8, 9, 10, 11, 12],
    ],
    readonly [
      readonly [5, 6, 7, 8, 9],
      readonly [6, 7, 8, 9, 10],
      readonly [7, 8, 9, 10, 11],
      readonly [8, 9, 10, 11, 12],
      readonly [9, 10, 11, 12, 13],
    ],
    readonly [
      readonly [6, 7, 8, 9, 10],
      readonly [7, 8, 9, 10, 11],
      readonly [8, 9, 10, 11, 12],
      readonly [9, 10, 11, 12, 13],
      readonly [10, 11, 12, 13, 14],
    ],
  ],
  readonly [
    readonly [
      readonly [3, 4, 5, 6, 7],
      readonly [4, 5, 6, 7, 8],
      readonly [5, 6, 7, 8, 9],
      readonly [6, 7, 8, 9, 10],
      readonly [7, 8, 9, 10, 11],
    ],
    readonly [
      readonly [4, 5, 6, 7, 8],
      readonly [5, 6, 7, 8, 9],
      readonly [6, 7, 8, 9, 10],
      readonly [7, 8, 9, 10, 11],
      readonly [8, 9, 10, 11, 12],
    ],
    readonly [
      readonly [5, 6, 7, 8, 9],
      readonly [6, 7, 8, 9, 10],
      readonly [7, 8, 9, 10, 11],
      readonly [8, 9, 10, 11, 12],
      readonly [9, 10, 11, 12, 13],
    ],
    readonly [
      readonly [6, 7, 8, 9, 10],
      readonly [7, 8, 9, 10, 11],
      readonly [8, 9, 10, 11, 12],
      readonly [9, 10, 11, 12, 13],
      readonly [10, 11, 12, 13, 14],
    ],
    readonly [
      readonly [7, 8, 9, 10, 11],
      readonly [8, 9, 10, 11, 12],
      readonly [9, 10, 11, 12, 13],
      readonly [10, 11, 12, 13, 14],
      readonly [11, 12, 13, 14, 15],
    ],
  ],
  readonly [
    readonly [
      readonly [4, 5, 6, 7, 8],
      readonly [5, 6, 7, 8, 9],
      readonly [6, 7, 8, 9, 10],
      readonly [7, 8, 9, 10, 11],
      readonly [8, 9, 10, 11, 12],
    ],
    readonly [
      readonly [5, 6, 7, 8, 9],
      readonly [6, 7, 8, 9, 10],
      readonly [7, 8, 9, 10, 11],
      readonly [8, 9, 10, 11, 12],
      readonly [9, 10, 11, 12, 13],
    ],
    readonly [
      readonly [6, 7, 8, 9, 10],
      readonly [7, 8, 9, 10, 11],
      readonly [8, 9, 10, 11, 12],
      readonly [9, 10, 11, 12, 13],
      readonly [10, 11, 12, 13, 14],
    ],
    readonly [
      readonly [7, 8, 9, 10, 11],
      readonly [8, 9, 10, 11, 12],
      readonly [9, 10, 11, 12, 13],
      readonly [10, 11, 12, 13, 14],
      readonly [11, 12, 13, 14, 15],
    ],
    readonly [
      readonly [8, 9, 10, 11, 12],
      readonly [9, 10, 11, 12, 13],
      readonly [10, 11, 12, 13, 14],
      readonly [11, 12, 13, 14, 15],
      readonly [12, 13, 14, 15, 16],
    ],
  ],
]

/**
 * `[A][B]` で A > B なら `true`、 A < B なら `false`、A = B なら `null`。
 */
type CountComparerMatrix = readonly [
  readonly [null, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false],
  readonly [true, null, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false],
  readonly [true, true, null, false, false, false, false, false, false, false, false, false, false, false, false, false, false],
  readonly [true, true, true, null, false, false, false, false, false, false, false, false, false, false, false, false, false],
  readonly [true, true, true, true, null, false, false, false, false, false, false, false, false, false, false, false, false],
  readonly [true, true, true, true, true, null, false, false, false, false, false, false, false, false, false, false, false],
  readonly [true, true, true, true, true, true, null, false, false, false, false, false, false, false, false, false, false],
  readonly [true, true, true, true, true, true, true, null, false, false, false, false, false, false, false, false, false],
  readonly [true, true, true, true, true, true, true, true, null, false, false, false, false, false, false, false, false],
  readonly [true, true, true, true, true, true, true, true, true, null, false, false, false, false, false, false, false],
  readonly [true, true, true, true, true, true, true, true, true, true, null, false, false, false, false, false, false],
  readonly [true, true, true, true, true, true, true, true, true, true, true, null, false, false, false, false, false],
  readonly [true, true, true, true, true, true, true, true, true, true, true, true, null, false, false, false, false],
  readonly [true, true, true, true, true, true, true, true, true, true, true, true, true, null, false, false, false],
  readonly [true, true, true, true, true, true, true, true, true, true, true, true, true, true, null, false, false],
  readonly [true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, null, false],
  readonly [true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, null],
]

/**
 * `PuttableMatrix` から石を置ける場所の `Selector` を導く。
 */
type PuttableSelector<
  TPuttableMatrix extends Quartet<Quartet<BoardMatrix>>,
> =
  | (TPuttableMatrix[0][0] extends never ? never : 'A1')
  | (TPuttableMatrix[0][1] extends never ? never : 'A2')
  | (TPuttableMatrix[0][2] extends never ? never : 'A3')
  | (TPuttableMatrix[0][3] extends never ? never : 'A4')
  | (TPuttableMatrix[1][0] extends never ? never : 'B1')
  | (TPuttableMatrix[1][1] extends never ? never : 'B2')
  | (TPuttableMatrix[1][2] extends never ? never : 'B3')
  | (TPuttableMatrix[1][3] extends never ? never : 'B4')
  | (TPuttableMatrix[2][0] extends never ? never : 'C1')
  | (TPuttableMatrix[2][1] extends never ? never : 'C2')
  | (TPuttableMatrix[2][2] extends never ? never : 'C3')
  | (TPuttableMatrix[2][3] extends never ? never : 'C4')
  | (TPuttableMatrix[3][0] extends never ? never : 'D1')
  | (TPuttableMatrix[3][1] extends never ? never : 'D2')
  | (TPuttableMatrix[3][2] extends never ? never : 'D3')
  | (TPuttableMatrix[3][3] extends never ? never : 'D4')

メインロジックはこれで完成です。
ところで、個人的にタプルのインデックスに never を与えると全要素が返ってくる挙動は非直感的な気がしています(never が返ってきてほしい)が、いかがでしょうか?知見のある方是非ご教授いただければ幸いです。

ビュー

やっとビューにも触れていきます。

ビューの生成

/**
 * 1 行目の筋の状態からビュー用のプロパティ名を返す。
 */
type BoardRow1View<
  TLine extends BoardLine,
> = TLine[0] extends BlackStone
  ? TLine[1] extends BlackStone
    ? TLine[2] extends BlackStone
      ? TLine[3] extends BlackStone
        ? { readonly '1|●●●●'?: undefined }
        : TLine[3] extends WhiteStone
          ? { readonly '1|●●●○'?: undefined }
          : { readonly '1|●●● '?: undefined }
      : TLine[2] extends WhiteStone
        ? TLine[3] extends BlackStone
          ? { readonly '1|●●○●'?: undefined }
          : TLine[3] extends WhiteStone
            ? { readonly '1|●●○○'?: undefined }
            : { readonly '1|●●○ '?: undefined }
        : TLine[3] extends BlackStone
          ? { readonly '1|●● ●'?: undefined }
          : TLine[3] extends WhiteStone
            ? { readonly '1|●● ○'?: undefined }
            : { readonly '1|●●  '?: undefined }
    : TLine[1] extends WhiteStone
      ? TLine[2] extends BlackStone
        ? TLine[3] extends BlackStone
          ? { readonly '1|●○●●'?: undefined }
          : TLine[3] extends WhiteStone
            ? { readonly '1|●○●○'?: undefined }
            : { readonly '1|●○● '?: undefined }
        : TLine[2] extends WhiteStone
          ? TLine[3] extends BlackStone
            ? { readonly '1|●○○●'?: undefined }
            : TLine[3] extends WhiteStone
              ? { readonly '1|●○○○'?: undefined }
              : { readonly '1|●○○ '?: undefined }
          : TLine[3] extends BlackStone
            ? { readonly '1|●○ ●'?: undefined }
            : TLine[3] extends WhiteStone
              ? { readonly '1|●○ ○'?: undefined }
              : { readonly '1|●○  '?: undefined }
      : TLine[2] extends BlackStone
        ? TLine[3] extends BlackStone
          ? { readonly '1|● ●●'?: undefined }
          : TLine[3] extends WhiteStone
            ? { readonly '1|● ●○'?: undefined }
            : { readonly '1|● ● '?: undefined }
        : TLine[2] extends WhiteStone
          ? TLine[3] extends BlackStone
            ? { readonly '1|● ○●'?: undefined }
            : TLine[3] extends WhiteStone
              ? { readonly '1|● ○○'?: undefined }
              : { readonly '1|● ○ '?: undefined }
          : TLine[3] extends BlackStone
            ? { readonly '1|●  ●'?: undefined }
            : TLine[3] extends WhiteStone
              ? { readonly '1|●  ○'?: undefined }
              : { readonly '1|●   '?: undefined }
  : TLine[0] extends WhiteStone
    ? TLine[1] extends BlackStone
      ? TLine[2] extends BlackStone
        ? TLine[3] extends BlackStone
          ? { readonly '1|○●●●'?: undefined }
          : TLine[3] extends WhiteStone
            ? { readonly '1|○●●○'?: undefined }
            : { readonly '1|○●● '?: undefined }
        : TLine[2] extends WhiteStone
          ? TLine[3] extends BlackStone
            ? { readonly '1|○●○●'?: undefined }
            : TLine[3] extends WhiteStone
              ? { readonly '1|○●○○'?: undefined }
              : { readonly '1|○●○ '?: undefined }
          : TLine[3] extends BlackStone
            ? { readonly '1|○● ●'?: undefined }
            : TLine[3] extends WhiteStone
              ? { readonly '1|○● ○'?: undefined }
              : { readonly '1|○●  '?: undefined }
      : TLine[1] extends WhiteStone
        ? TLine[2] extends BlackStone
          ? TLine[3] extends BlackStone
            ? { readonly '1|○○●●'?: undefined }
            : TLine[3] extends WhiteStone
              ? { readonly '1|○○●○'?: undefined }
              : { readonly '1|○○● '?: undefined }
          : TLine[2] extends WhiteStone
            ? TLine[3] extends BlackStone
              ? { readonly '1|○○○●'?: undefined }
              : TLine[3] extends WhiteStone
                ? { readonly '1|○○○○'?: undefined }
                : { readonly '1|○○○ '?: undefined }
            : TLine[3] extends BlackStone
              ? { readonly '1|○○ ●'?: undefined }
              : TLine[3] extends WhiteStone
                ? { readonly '1|○○ ○'?: undefined }
                : { readonly '1|○○  '?: undefined }
        : TLine[2] extends BlackStone
          ? TLine[3] extends BlackStone
            ? { readonly '1|○ ●●'?: undefined }
            : TLine[3] extends WhiteStone
              ? { readonly '1|○ ●○'?: undefined }
              : { readonly '1|○ ● '?: undefined }
          : TLine[2] extends WhiteStone
            ? TLine[3] extends BlackStone
              ? { readonly '1|○ ○●'?: undefined }
              : TLine[3] extends WhiteStone
                ? { readonly '1|○ ○○'?: undefined }
                : { readonly '1|○ ○ '?: undefined }
            : TLine[3] extends BlackStone
              ? { readonly '1|○  ●'?: undefined }
              : TLine[3] extends WhiteStone
                ? { readonly '1|○  ○'?: undefined }
                : { readonly '1|○   '?: undefined }
    : TLine[1] extends BlackStone
      ? TLine[2] extends BlackStone
        ? TLine[3] extends BlackStone
          ? { readonly '1| ●●●'?: undefined }
          : TLine[3] extends WhiteStone
            ? { readonly '1| ●●○'?: undefined }
            : { readonly '1| ●● '?: undefined }
        : TLine[2] extends WhiteStone
          ? TLine[3] extends BlackStone
            ? { readonly '1| ●○●'?: undefined }
            : TLine[3] extends WhiteStone
              ? { readonly '1| ●○○'?: undefined }
              : { readonly '1| ●○ '?: undefined }
          : TLine[3] extends BlackStone
            ? { readonly '1| ● ●'?: undefined }
            : TLine[3] extends WhiteStone
              ? { readonly '1| ● ○'?: undefined }
              : { readonly '1| ●  '?: undefined }
      : TLine[1] extends WhiteStone
        ? TLine[2] extends BlackStone
          ? TLine[3] extends BlackStone
            ? { readonly '1| ○●●'?: undefined }
            : TLine[3] extends WhiteStone
              ? { readonly '1| ○●○'?: undefined }
              : { readonly '1| ○● '?: undefined }
          : TLine[2] extends WhiteStone
            ? TLine[3] extends BlackStone
              ? { readonly '1| ○○●'?: undefined }
              : TLine[3] extends WhiteStone
                ? { readonly '1| ○○○'?: undefined }
                : { readonly '1| ○○ '?: undefined }
            : TLine[3] extends BlackStone
              ? { readonly '1| ○ ●'?: undefined }
              : TLine[3] extends WhiteStone
                ? { readonly '1| ○ ○'?: undefined }
                : { readonly '1| ○  '?: undefined }
        : TLine[2] extends BlackStone
          ? TLine[3] extends BlackStone
            ? { readonly '1|  ●●'?: undefined }
            : TLine[3] extends WhiteStone
              ? { readonly '1|  ●○'?: undefined }
              : { readonly '1|  ● '?: undefined }
          : TLine[2] extends WhiteStone
            ? TLine[3] extends BlackStone
              ? { readonly '1|  ○●'?: undefined }
              : TLine[3] extends WhiteStone
                ? { readonly '1|  ○○'?: undefined }
                : { readonly '1|  ○ '?: undefined }
            : TLine[3] extends BlackStone
              ? { readonly '1|   ●'?: undefined }
              : TLine[3] extends WhiteStone
                ? { readonly '1|   ○'?: undefined }
                : { readonly '1|    '?: undefined }

/**
 * 2 行目の筋の状態からビュー用のプロパティ名を返す。
 */
type BoardRow2View<
  TLine extends BoardLine,
> = TLine[0] extends BlackStone
  ? TLine[1] extends BlackStone
    ? TLine[2] extends BlackStone
      ? TLine[3] extends BlackStone
        ? { readonly '2|●●●●'?: undefined }
        : TLine[3] extends WhiteStone
          ? { readonly '2|●●●○'?: undefined }
          : { readonly '2|●●● '?: undefined }
      : TLine[2] extends WhiteStone
        ? TLine[3] extends BlackStone
          ? { readonly '2|●●○●'?: undefined }
          : TLine[3] extends WhiteStone
            ? { readonly '2|●●○○'?: undefined }
            : { readonly '2|●●○ '?: undefined }
        : TLine[3] extends BlackStone
          ? { readonly '2|●● ●'?: undefined }
          : TLine[3] extends WhiteStone
            ? { readonly '2|●● ○'?: undefined }
            : { readonly '2|●●  '?: undefined }
    : TLine[1] extends WhiteStone
      ? TLine[2] extends BlackStone
        ? TLine[3] extends BlackStone
          ? { readonly '2|●○●●'?: undefined }
          : TLine[3] extends WhiteStone
            ? { readonly '2|●○●○'?: undefined }
            : { readonly '2|●○● '?: undefined }
        : TLine[2] extends WhiteStone
          ? TLine[3] extends BlackStone
            ? { readonly '2|●○○●'?: undefined }
            : TLine[3] extends WhiteStone
              ? { readonly '2|●○○○'?: undefined }
              : { readonly '2|●○○ '?: undefined }
          : TLine[3] extends BlackStone
            ? { readonly '2|●○ ●'?: undefined }
            : TLine[3] extends WhiteStone
              ? { readonly '2|●○ ○'?: undefined }
              : { readonly '2|●○  '?: undefined }
      : TLine[2] extends BlackStone
        ? TLine[3] extends BlackStone
          ? { readonly '2|● ●●'?: undefined }
          : TLine[3] extends WhiteStone
            ? { readonly '2|● ●○'?: undefined }
            : { readonly '2|● ● '?: undefined }
        : TLine[2] extends WhiteStone
          ? TLine[3] extends BlackStone
            ? { readonly '2|● ○●'?: undefined }
            : TLine[3] extends WhiteStone
              ? { readonly '2|● ○○'?: undefined }
              : { readonly '2|● ○ '?: undefined }
          : TLine[3] extends BlackStone
            ? { readonly '2|●  ●'?: undefined }
            : TLine[3] extends WhiteStone
              ? { readonly '2|●  ○'?: undefined }
              : { readonly '2|●   '?: undefined }
  : TLine[0] extends WhiteStone
    ? TLine[1] extends BlackStone
      ? TLine[2] extends BlackStone
        ? TLine[3] extends BlackStone
          ? { readonly '2|○●●●'?: undefined }
          : TLine[3] extends WhiteStone
            ? { readonly '2|○●●○'?: undefined }
            : { readonly '2|○●● '?: undefined }
        : TLine[2] extends WhiteStone
          ? TLine[3] extends BlackStone
            ? { readonly '2|○●○●'?: undefined }
            : TLine[3] extends WhiteStone
              ? { readonly '2|○●○○'?: undefined }
              : { readonly '2|○●○ '?: undefined }
          : TLine[3] extends BlackStone
            ? { readonly '2|○● ●'?: undefined }
            : TLine[3] extends WhiteStone
              ? { readonly '2|○● ○'?: undefined }
              : { readonly '2|○●  '?: undefined }
      : TLine[1] extends WhiteStone
        ? TLine[2] extends BlackStone
          ? TLine[3] extends BlackStone
            ? { readonly '2|○○●●'?: undefined }
            : TLine[3] extends WhiteStone
              ? { readonly '2|○○●○'?: undefined }
              : { readonly '2|○○● '?: undefined }
          : TLine[2] extends WhiteStone
            ? TLine[3] extends BlackStone
              ? { readonly '2|○○○●'?: undefined }
              : TLine[3] extends WhiteStone
                ? { readonly '2|○○○○'?: undefined }
                : { readonly '2|○○○ '?: undefined }
            : TLine[3] extends BlackStone
              ? { readonly '2|○○ ●'?: undefined }
              : TLine[3] extends WhiteStone
                ? { readonly '2|○○ ○'?: undefined }
                : { readonly '2|○○  '?: undefined }
        : TLine[2] extends BlackStone
          ? TLine[3] extends BlackStone
            ? { readonly '2|○ ●●'?: undefined }
            : TLine[3] extends WhiteStone
              ? { readonly '2|○ ●○'?: undefined }
              : { readonly '2|○ ● '?: undefined }
          : TLine[2] extends WhiteStone
            ? TLine[3] extends BlackStone
              ? { readonly '2|○ ○●'?: undefined }
              : TLine[3] extends WhiteStone
                ? { readonly '2|○ ○○'?: undefined }
                : { readonly '2|○ ○ '?: undefined }
            : TLine[3] extends BlackStone
              ? { readonly '2|○  ●'?: undefined }
              : TLine[3] extends WhiteStone
                ? { readonly '2|○  ○'?: undefined }
                : { readonly '2|○   '?: undefined }
    : TLine[1] extends BlackStone
      ? TLine[2] extends BlackStone
        ? TLine[3] extends BlackStone
          ? { readonly '2| ●●●'?: undefined }
          : TLine[3] extends WhiteStone
            ? { readonly '2| ●●○'?: undefined }
            : { readonly '2| ●● '?: undefined }
        : TLine[2] extends WhiteStone
          ? TLine[3] extends BlackStone
            ? { readonly '2| ●○●'?: undefined }
            : TLine[3] extends WhiteStone
              ? { readonly '2| ●○○'?: undefined }
              : { readonly '2| ●○ '?: undefined }
          : TLine[3] extends BlackStone
            ? { readonly '2| ● ●'?: undefined }
            : TLine[3] extends WhiteStone
              ? { readonly '2| ● ○'?: undefined }
              : { readonly '2| ●  '?: undefined }
      : TLine[1] extends WhiteStone
        ? TLine[2] extends BlackStone
          ? TLine[3] extends BlackStone
            ? { readonly '2| ○●●'?: undefined }
            : TLine[3] extends WhiteStone
              ? { readonly '2| ○●○'?: undefined }
              : { readonly '2| ○● '?: undefined }
          : TLine[2] extends WhiteStone
            ? TLine[3] extends BlackStone
              ? { readonly '2| ○○●'?: undefined }
              : TLine[3] extends WhiteStone
                ? { readonly '2| ○○○'?: undefined }
                : { readonly '2| ○○ '?: undefined }
            : TLine[3] extends BlackStone
              ? { readonly '2| ○ ●'?: undefined }
              : TLine[3] extends WhiteStone
                ? { readonly '2| ○ ○'?: undefined }
                : { readonly '2| ○  '?: undefined }
        : TLine[2] extends BlackStone
          ? TLine[3] extends BlackStone
            ? { readonly '2|  ●●'?: undefined }
            : TLine[3] extends WhiteStone
              ? { readonly '2|  ●○'?: undefined }
              : { readonly '2|  ● '?: undefined }
          : TLine[2] extends WhiteStone
            ? TLine[3] extends BlackStone
              ? { readonly '2|  ○●'?: undefined }
              : TLine[3] extends WhiteStone
                ? { readonly '2|  ○○'?: undefined }
                : { readonly '2|  ○ '?: undefined }
            : TLine[3] extends BlackStone
              ? { readonly '2|   ●'?: undefined }
              : TLine[3] extends WhiteStone
                ? { readonly '2|   ○'?: undefined }
                : { readonly '2|    '?: undefined }

/**
 * 3 行目の筋の状態からビュー用のプロパティ名を返す。
 */
type BoardRow3View<
  TLine extends BoardLine,
> = TLine[0] extends BlackStone
  ? TLine[1] extends BlackStone
    ? TLine[2] extends BlackStone
      ? TLine[3] extends BlackStone
        ? { readonly '3|●●●●'?: undefined }
        : TLine[3] extends WhiteStone
          ? { readonly '3|●●●○'?: undefined }
          : { readonly '3|●●● '?: undefined }
      : TLine[2] extends WhiteStone
        ? TLine[3] extends BlackStone
          ? { readonly '3|●●○●'?: undefined }
          : TLine[3] extends WhiteStone
            ? { readonly '3|●●○○'?: undefined }
            : { readonly '3|●●○ '?: undefined }
        : TLine[3] extends BlackStone
          ? { readonly '3|●● ●'?: undefined }
          : TLine[3] extends WhiteStone
            ? { readonly '3|●● ○'?: undefined }
            : { readonly '3|●●  '?: undefined }
    : TLine[1] extends WhiteStone
      ? TLine[2] extends BlackStone
        ? TLine[3] extends BlackStone
          ? { readonly '3|●○●●'?: undefined }
          : TLine[3] extends WhiteStone
            ? { readonly '3|●○●○'?: undefined }
            : { readonly '3|●○● '?: undefined }
        : TLine[2] extends WhiteStone
          ? TLine[3] extends BlackStone
            ? { readonly '3|●○○●'?: undefined }
            : TLine[3] extends WhiteStone
              ? { readonly '3|●○○○'?: undefined }
              : { readonly '3|●○○ '?: undefined }
          : TLine[3] extends BlackStone
            ? { readonly '3|●○ ●'?: undefined }
            : TLine[3] extends WhiteStone
              ? { readonly '3|●○ ○'?: undefined }
              : { readonly '3|●○  '?: undefined }
      : TLine[2] extends BlackStone
        ? TLine[3] extends BlackStone
          ? { readonly '3|● ●●'?: undefined }
          : TLine[3] extends WhiteStone
            ? { readonly '3|● ●○'?: undefined }
            : { readonly '3|● ● '?: undefined }
        : TLine[2] extends WhiteStone
          ? TLine[3] extends BlackStone
            ? { readonly '3|● ○●'?: undefined }
            : TLine[3] extends WhiteStone
              ? { readonly '3|● ○○'?: undefined }
              : { readonly '3|● ○ '?: undefined }
          : TLine[3] extends BlackStone
            ? { readonly '3|●  ●'?: undefined }
            : TLine[3] extends WhiteStone
              ? { readonly '3|●  ○'?: undefined }
              : { readonly '3|●   '?: undefined }
  : TLine[0] extends WhiteStone
    ? TLine[1] extends BlackStone
      ? TLine[2] extends BlackStone
        ? TLine[3] extends BlackStone
          ? { readonly '3|○●●●'?: undefined }
          : TLine[3] extends WhiteStone
            ? { readonly '3|○●●○'?: undefined }
            : { readonly '3|○●● '?: undefined }
        : TLine[2] extends WhiteStone
          ? TLine[3] extends BlackStone
            ? { readonly '3|○●○●'?: undefined }
            : TLine[3] extends WhiteStone
              ? { readonly '3|○●○○'?: undefined }
              : { readonly '3|○●○ '?: undefined }
          : TLine[3] extends BlackStone
            ? { readonly '3|○● ●'?: undefined }
            : TLine[3] extends WhiteStone
              ? { readonly '3|○● ○'?: undefined }
              : { readonly '3|○●  '?: undefined }
      : TLine[1] extends WhiteStone
        ? TLine[2] extends BlackStone
          ? TLine[3] extends BlackStone
            ? { readonly '3|○○●●'?: undefined }
            : TLine[3] extends WhiteStone
              ? { readonly '3|○○●○'?: undefined }
              : { readonly '3|○○● '?: undefined }
          : TLine[2] extends WhiteStone
            ? TLine[3] extends BlackStone
              ? { readonly '3|○○○●'?: undefined }
              : TLine[3] extends WhiteStone
                ? { readonly '3|○○○○'?: undefined }
                : { readonly '3|○○○ '?: undefined }
            : TLine[3] extends BlackStone
              ? { readonly '3|○○ ●'?: undefined }
              : TLine[3] extends WhiteStone
                ? { readonly '3|○○ ○'?: undefined }
                : { readonly '3|○○  '?: undefined }
        : TLine[2] extends BlackStone
          ? TLine[3] extends BlackStone
            ? { readonly '3|○ ●●'?: undefined }
            : TLine[3] extends WhiteStone
              ? { readonly '3|○ ●○'?: undefined }
              : { readonly '3|○ ● '?: undefined }
          : TLine[2] extends WhiteStone
            ? TLine[3] extends BlackStone
              ? { readonly '3|○ ○●'?: undefined }
              : TLine[3] extends WhiteStone
                ? { readonly '3|○ ○○'?: undefined }
                : { readonly '3|○ ○ '?: undefined }
            : TLine[3] extends BlackStone
              ? { readonly '3|○  ●'?: undefined }
              : TLine[3] extends WhiteStone
                ? { readonly '3|○  ○'?: undefined }
                : { readonly '3|○   '?: undefined }
    : TLine[1] extends BlackStone
      ? TLine[2] extends BlackStone
        ? TLine[3] extends BlackStone
          ? { readonly '3| ●●●'?: undefined }
          : TLine[3] extends WhiteStone
            ? { readonly '3| ●●○'?: undefined }
            : { readonly '3| ●● '?: undefined }
        : TLine[2] extends WhiteStone
          ? TLine[3] extends BlackStone
            ? { readonly '3| ●○●'?: undefined }
            : TLine[3] extends WhiteStone
              ? { readonly '3| ●○○'?: undefined }
              : { readonly '3| ●○ '?: undefined }
          : TLine[3] extends BlackStone
            ? { readonly '3| ● ●'?: undefined }
            : TLine[3] extends WhiteStone
              ? { readonly '3| ● ○'?: undefined }
              : { readonly '3| ●  '?: undefined }
      : TLine[1] extends WhiteStone
        ? TLine[2] extends BlackStone
          ? TLine[3] extends BlackStone
            ? { readonly '3| ○●●'?: undefined }
            : TLine[3] extends WhiteStone
              ? { readonly '3| ○●○'?: undefined }
              : { readonly '3| ○● '?: undefined }
          : TLine[2] extends WhiteStone
            ? TLine[3] extends BlackStone
              ? { readonly '3| ○○●'?: undefined }
              : TLine[3] extends WhiteStone
                ? { readonly '3| ○○○'?: undefined }
                : { readonly '3| ○○ '?: undefined }
            : TLine[3] extends BlackStone
              ? { readonly '3| ○ ●'?: undefined }
              : TLine[3] extends WhiteStone
                ? { readonly '3| ○ ○'?: undefined }
                : { readonly '3| ○  '?: undefined }
        : TLine[2] extends BlackStone
          ? TLine[3] extends BlackStone
            ? { readonly '3|  ●●'?: undefined }
            : TLine[3] extends WhiteStone
              ? { readonly '3|  ●○'?: undefined }
              : { readonly '3|  ● '?: undefined }
          : TLine[2] extends WhiteStone
            ? TLine[3] extends BlackStone
              ? { readonly '3|  ○●'?: undefined }
              : TLine[3] extends WhiteStone
                ? { readonly '3|  ○○'?: undefined }
                : { readonly '3|  ○ '?: undefined }
            : TLine[3] extends BlackStone
              ? { readonly '3|   ●'?: undefined }
              : TLine[3] extends WhiteStone
                ? { readonly '3|   ○'?: undefined }
                : { readonly '3|    '?: undefined }

/**
 * 4 行目の筋の状態からビュー用のプロパティ名を返す。
 */
type BoardRow4View<
  TLine extends BoardLine,
> = TLine[0] extends BlackStone
  ? TLine[1] extends BlackStone
    ? TLine[2] extends BlackStone
      ? TLine[3] extends BlackStone
        ? { readonly '4|●●●●'?: undefined }
        : TLine[3] extends WhiteStone
          ? { readonly '4|●●●○'?: undefined }
          : { readonly '4|●●● '?: undefined }
      : TLine[2] extends WhiteStone
        ? TLine[3] extends BlackStone
          ? { readonly '4|●●○●'?: undefined }
          : TLine[3] extends WhiteStone
            ? { readonly '4|●●○○'?: undefined }
            : { readonly '4|●●○ '?: undefined }
        : TLine[3] extends BlackStone
          ? { readonly '4|●● ●'?: undefined }
          : TLine[3] extends WhiteStone
            ? { readonly '4|●● ○'?: undefined }
            : { readonly '4|●●  '?: undefined }
    : TLine[1] extends WhiteStone
      ? TLine[2] extends BlackStone
        ? TLine[3] extends BlackStone
          ? { readonly '4|●○●●'?: undefined }
          : TLine[3] extends WhiteStone
            ? { readonly '4|●○●○'?: undefined }
            : { readonly '4|●○● '?: undefined }
        : TLine[2] extends WhiteStone
          ? TLine[3] extends BlackStone
            ? { readonly '4|●○○●'?: undefined }
            : TLine[3] extends WhiteStone
              ? { readonly '4|●○○○'?: undefined }
              : { readonly '4|●○○ '?: undefined }
          : TLine[3] extends BlackStone
            ? { readonly '4|●○ ●'?: undefined }
            : TLine[3] extends WhiteStone
              ? { readonly '4|●○ ○'?: undefined }
              : { readonly '4|●○  '?: undefined }
      : TLine[2] extends BlackStone
        ? TLine[3] extends BlackStone
          ? { readonly '4|● ●●'?: undefined }
          : TLine[3] extends WhiteStone
            ? { readonly '4|● ●○'?: undefined }
            : { readonly '4|● ● '?: undefined }
        : TLine[2] extends WhiteStone
          ? TLine[3] extends BlackStone
            ? { readonly '4|● ○●'?: undefined }
            : TLine[3] extends WhiteStone
              ? { readonly '4|● ○○'?: undefined }
              : { readonly '4|● ○ '?: undefined }
          : TLine[3] extends BlackStone
            ? { readonly '4|●  ●'?: undefined }
            : TLine[3] extends WhiteStone
              ? { readonly '4|●  ○'?: undefined }
              : { readonly '4|●   '?: undefined }
  : TLine[0] extends WhiteStone
    ? TLine[1] extends BlackStone
      ? TLine[2] extends BlackStone
        ? TLine[3] extends BlackStone
          ? { readonly '4|○●●●'?: undefined }
          : TLine[3] extends WhiteStone
            ? { readonly '4|○●●○'?: undefined }
            : { readonly '4|○●● '?: undefined }
        : TLine[2] extends WhiteStone
          ? TLine[3] extends BlackStone
            ? { readonly '4|○●○●'?: undefined }
            : TLine[3] extends WhiteStone
              ? { readonly '4|○●○○'?: undefined }
              : { readonly '4|○●○ '?: undefined }
          : TLine[3] extends BlackStone
            ? { readonly '4|○● ●'?: undefined }
            : TLine[3] extends WhiteStone
              ? { readonly '4|○● ○'?: undefined }
              : { readonly '4|○●  '?: undefined }
      : TLine[1] extends WhiteStone
        ? TLine[2] extends BlackStone
          ? TLine[3] extends BlackStone
            ? { readonly '4|○○●●'?: undefined }
            : TLine[3] extends WhiteStone
              ? { readonly '4|○○●○'?: undefined }
              : { readonly '4|○○● '?: undefined }
          : TLine[2] extends WhiteStone
            ? TLine[3] extends BlackStone
              ? { readonly '4|○○○●'?: undefined }
              : TLine[3] extends WhiteStone
                ? { readonly '4|○○○○'?: undefined }
                : { readonly '4|○○○ '?: undefined }
            : TLine[3] extends BlackStone
              ? { readonly '4|○○ ●'?: undefined }
              : TLine[3] extends WhiteStone
                ? { readonly '4|○○ ○'?: undefined }
                : { readonly '4|○○  '?: undefined }
        : TLine[2] extends BlackStone
          ? TLine[3] extends BlackStone
            ? { readonly '4|○ ●●'?: undefined }
            : TLine[3] extends WhiteStone
              ? { readonly '4|○ ●○'?: undefined }
              : { readonly '4|○ ● '?: undefined }
          : TLine[2] extends WhiteStone
            ? TLine[3] extends BlackStone
              ? { readonly '4|○ ○●'?: undefined }
              : TLine[3] extends WhiteStone
                ? { readonly '4|○ ○○'?: undefined }
                : { readonly '4|○ ○ '?: undefined }
            : TLine[3] extends BlackStone
              ? { readonly '4|○  ●'?: undefined }
              : TLine[3] extends WhiteStone
                ? { readonly '4|○  ○'?: undefined }
                : { readonly '4|○   '?: undefined }
    : TLine[1] extends BlackStone
      ? TLine[2] extends BlackStone
        ? TLine[3] extends BlackStone
          ? { readonly '4| ●●●'?: undefined }
          : TLine[3] extends WhiteStone
            ? { readonly '4| ●●○'?: undefined }
            : { readonly '4| ●● '?: undefined }
        : TLine[2] extends WhiteStone
          ? TLine[3] extends BlackStone
            ? { readonly '4| ●○●'?: undefined }
            : TLine[3] extends WhiteStone
              ? { readonly '4| ●○○'?: undefined }
              : { readonly '4| ●○ '?: undefined }
          : TLine[3] extends BlackStone
            ? { readonly '4| ● ●'?: undefined }
            : TLine[3] extends WhiteStone
              ? { readonly '4| ● ○'?: undefined }
              : { readonly '4| ●  '?: undefined }
      : TLine[1] extends WhiteStone
        ? TLine[2] extends BlackStone
          ? TLine[3] extends BlackStone
            ? { readonly '4| ○●●'?: undefined }
            : TLine[3] extends WhiteStone
              ? { readonly '4| ○●○'?: undefined }
              : { readonly '4| ○● '?: undefined }
          : TLine[2] extends WhiteStone
            ? TLine[3] extends BlackStone
              ? { readonly '4| ○○●'?: undefined }
              : TLine[3] extends WhiteStone
                ? { readonly '4| ○○○'?: undefined }
                : { readonly '4| ○○ '?: undefined }
            : TLine[3] extends BlackStone
              ? { readonly '4| ○ ●'?: undefined }
              : TLine[3] extends WhiteStone
                ? { readonly '4| ○ ○'?: undefined }
                : { readonly '4| ○  '?: undefined }
        : TLine[2] extends BlackStone
          ? TLine[3] extends BlackStone
            ? { readonly '4|  ●●'?: undefined }
            : TLine[3] extends WhiteStone
              ? { readonly '4|  ●○'?: undefined }
              : { readonly '4|  ● '?: undefined }
          : TLine[2] extends WhiteStone
            ? TLine[3] extends BlackStone
              ? { readonly '4|  ○●'?: undefined }
              : TLine[3] extends WhiteStone
                ? { readonly '4|  ○○'?: undefined }
                : { readonly '4|  ○ '?: undefined }
            : TLine[3] extends BlackStone
              ? { readonly '4|   ●'?: undefined }
              : TLine[3] extends WhiteStone
                ? { readonly '4|   ○'?: undefined }
                : { readonly '4|    '?: undefined }

/**
 * ビュー本体を生成する。
 */
type GameView<
  TMatrix extends BoardMatrix,
  TSkipped extends boolean,
  TStone extends Stone,
> = (
  PuttableMatrix<TMatrix, TStone> extends infer OPuttableMatrix
    ? OPuttableMatrix extends never
      ? TSkipped extends true
        ? readonly [
            CountLine<OPuttableMatrix[0]>,
            CountLine<OPuttableMatrix[1]>,
            CountLine<OPuttableMatrix[2]>,
            CountLine<OPuttableMatrix[3]>,
          ] extends readonly [
            readonly [infer ORow1Black, infer ORow1White],
            readonly [infer ORow2Black, infer ORow2White],
            readonly [infer ORow3Black, infer ORow3White],
            readonly [infer ORow4Black, infer ORow4White],
          ]
          ? // @ts-expect-error inferred type is poor
            readonly [CountAddiction[ORow1Black][ORow2Black][ORow3Black][ORow4Black], CountAddiction[ORow1White][ORow2White][ORow3White][ORow4White]] extends readonly [infer OBlackCount, infer OWhiteCount]
            ? // @ts-expect-error inferred type is poor
              CountComparerMatrix[OBlackCount][OWhiteCount] extends infer OCompared
              ? {
                  readonly score: {
                    readonly [BLACK]: OBlackCount
                    readonly [WHITE]: OWhiteCount
                  }
                  readonly winner:
                    | (OCompared extends false ? never : BlackStone)
                    | (OCompared extends true ? never : WhiteStone)
                }
              : never
            : never
          : never
        : {
            readonly [BLACK]: {
              blackSkips(): GameView<TMatrix, true, WhiteStone>
            }
            readonly [WHITE]: {
              whiteSkips(): GameView<TMatrix, true, BlackStone>
            }
          }[TStone]
      : {
          readonly [BLACK]: {
            blackPutsAt<
              T extends PuttableSelector<
                // @ts-expect-error inferred type is poor
                OPuttableMatrix,
              >,
            >(_: T): SelectorMap[T] extends readonly [infer ORowIndex, infer OColumnIndex]
              ? // @ts-expect-error inferred type is poor
                OPuttableMatrix[OColumnIndex][ORowIndex] extends infer OPuttedMatrix
                ? OPuttedMatrix extends BoardMatrix
                  ? GameView<OPuttedMatrix, false, WhiteStone>
                  : never
                : never
              : never
          }
          readonly [WHITE]: {
            whitePutsAt<
              T extends PuttableSelector<
                // @ts-expect-error inferred type is poor
                OPuttableMatrix,
              >,
            >(_: T): SelectorMap[T] extends readonly [infer ORowIndex, infer OColumnIndex]
              ? // @ts-expect-error inferred type is poor
                OPuttableMatrix[OColumnIndex][ORowIndex] extends infer OPuttedMatrix
                ? OPuttedMatrix extends BoardMatrix
                  ? GameView<OPuttedMatrix, false, BlackStone>
                  : never
                : never
              : never
          }
        }[TStone]
    : never
) & {
  readonly '#|ABCD'?: undefined
  readonly '-|----'?: undefined
} & BoardRow1View<TMatrix[0]>
  & BoardRow2View<TMatrix[1]>
  & BoardRow3View<TMatrix[2]>
  & BoardRow4View<TMatrix[3]>

これで型は完成です。お疲れ様でした。

ビューの実体

declare const game: GameView<readonly [
  readonly [Empty, Empty, Empty, Empty],
  readonly [Empty, WhiteStone, BlackStone, Empty],
  readonly [Empty, BlackStone, WhiteStone, Empty],
  readonly [Empty, Empty, Empty, Empty],
], BlackStone>

このように初期状態を与えてやれば実体も完成です。

image.png

トランスパイル結果も空なのでレギュレーションセーフですね。

遊んでみる

Playground はこちらですが、Playground だとどうやら IntelliSense が完全には表示されないようです。

最後に生成した game に対してプロパティアクセスしようとすると盤の状態を見ることができ、メソッドチェーンをぶら下げていくと手が進んでいきます。

4x4.gif

しかしながら TypeScript の経験がある方は察しがつくと思いますが、手を進めていくと IntelliSense の応答速度がだんだん遅くなっていき、終いには TS2589 が発生してしまいます。そのため、どうしても定期的に GameView の仮想実体を生やして型走査回数のリセットを行ってやる必要があります。

image.png

まとめ

今回は TypeScript の型システムのみを使用して簡単なゲームシステムを開発してみました。TypeScript の型システムを効率的に扱うには、型全体の大きさが大きいほどどうしても細部を愚直に書いていく必要がありますし、ときにはそのような努力をもってしても抗えない上限が訪れます。

とはいえ TypeScript の型システムの表現力は非常に強力です。私がインターンでコードを書いている justInCase Technologies のウェブフロントエンドでも、JSON 形式レスポンスのバリデーション、加工、および TypeScript 上での型付けを行うモジュールがあるのですが、この強力な型システムのおかげで単一の API 定義情報をもとに一手に全てを担うことが可能になっています。

型走査上限の制約はハードウェア側の問題もあるので緩和は難しいですが、文字列操作であったり数値演算であったりはいつか型システム上で簡単に扱えるようになると個人的には万々歳です。

蛇足

お題を見かけたときからざっくりとしたネタ自体は思いついていたのですが、如何せん${\scriptscriptstyle ✨️}$土曜まで色々あった${\scriptscriptstyle 🗺}$ために実際にコーディングと記事執筆ができたのがここ三日ほどしかなく、かなり走り書きになってしまいました。コードがかなり大きくそもそも読みづらいですが、文章に引っかかる点等ありましたらぜひ編集リクエストしていただけると幸いです。ここまで読んでくださりありがとうございました。

めちゃくちゃ疲れたのでこういう類のネタ記事はしばらくは書きたくないです。

acid_chicken
ひねくれたコードを書くことで無名。自分のことを学生だと思い込んでいた 18 歳。
https://acid-chicken.com
justincase
テクノロジーで保険を身近にする保険スタートアップです。
https://justincase.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account