Reversi をつくろう！ - 2

[< 前回の記事](https://qiita.com/toranekos/items/846fbef21bb359f3c961)

前回は導入から初期状態の盤面を取得するところまで。クラスはこんな感じになりました。
![refactored_class.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113636/f1cc2f7d-5735-896b-3770-ee2d82d8f973.png)

# プレイヤーを含めて初期状態の盤面を取得する

前回の対応だけではリバーシというゲームを開始できる状態ではない。
プレイヤーなども含めて初期状態の盤面を取得できるように考えていきたい✋

## モデルを抽出する

前回と同様に、[Wiki](https://ja.wikipedia.org/wiki/%E3%82%AA%E3%82%BB%E3%83%AD_(%E3%83%9C%E3%83%BC%E3%83%89%E3%82%B2%E3%83%BC%E3%83%A0)) & [英語版Wiki](https://en.wikipedia.org/wiki/Reversi) をみてモデルを抽出する。

- ゲーム: game
    - 初期状態の盤面: start position
        -  オリジナル: original
        -  オセロ: othello
    -  プレイヤー: players
        - 白番、黒番: side
            - 白番: light
            - 黒番: dark
        - 石(32枚): disks
    -  盤: board
        - 位置: pieces
            - 横: horizontal (a - h)
            - 縦: vertical (1 - 8)
            - 石: disk
                - 色: side

宙ぶらりんだった初期状態の盤面はゲームに紐付ける。
プレイヤーはどの色なのか、自分の石を管理する。
プレイヤーが持つ石を盤に配置する。

。。。プレイヤーを含めて考えて見ると、以下のリファクタリング要素が出てきた💧

- 初期状態の盤面を original と othello にする。
    - historical, modernって表現でも良いけれど歴史としてオリジナルとオセロが出てくるのでリネーム。
- 石の色(disk color)がプレイヤーの白番黒番を統一する。
    - プレイヤーの白番黒番と石の色の定義は同じで良さそう。Sideで統一する。

というわけでIssueを起票してリファクタリングする。

https://github.com/t-sone/reversi/issues/4

https://github.com/t-sone/reversi/issues/6

最終的にこ以下のようになる見込み。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113636/0264e9ea-a7d0-ab81-b655-68652a40315b.png)

Issueを切って対応開始👍

https://github.com/t-sone/reversi/issues/8

- 対応ブランチ: [start-game](https://github.com/t-sone/reversi/tree/start-game)

## テストケースを作成する

今回はプレイヤーの石を渡してボードに配置する必要があるから、`BoardFacotry`に`Players`を渡す必要がある。
`BoardService.start(StartPosition)`は`GameService.start(StartPosition)`で作り直した方が良さそうだ🤔

```java:GameServiceTest.java
    @Autowired
    GameService service;

    Stream<Arguments> 初期状態の盤面一覧() {
        return Stream.of(arguments(StartPosition.original,
                                   32,
                                   32,
                                   "[]"),
                         arguments(StartPosition.othello,
                                   30,
                                   30,
                                   "[{light: (d, 4)}, {dark: (e, 4)}, {dark: (d, 5)}, {light: (e, 5)}]")
        );
    }

    @ParameterizedTest
    @MethodSource("初期状態の盤面一覧")
    public void 初期状態の盤面を取得する(
            StartPosition startPosition,
            int 期待する白番の石数,
            int 期待する黒番の石数,
            String 期待する盤面) {
        Game game = service.start(startPosition);

        assertThat(game.lightPlayer().diskCount(), is(期待する白番の石数));
        assertThat(game.darkPlayer().diskCount(), is(期待する黒番の石数));
        assertThat(game.board().toString(), is(期待する盤面));
    }
```

- 初期配置の状態がオリジナルだと盤はそのまま。プレイヤーの石数は初期状態である32個。
- 初期配置の状態がオセロだと盤にプレイヤーの石をセンターエリアに2つずつ配置する。石数は30個。

というテストケースを作成。
BoardServiceに関する実装はこのタイミングで削除（リファクタリングのタイミングで本来は削除。。）

実装部分は以下のように`TODO 未実装`というコメントを残して置くことで実装漏れを防ぐ。

```java:GameService.java
    public Game start(StartPosition startPosition) {
        // TODO 未実装
        return new Game();
    }
```

- コミット: [プレイヤーを含めて初期状態の盤面を取得する - テストケース作成 #8](https://github.com/t-sone/reversi/pull/9/commits/85718f45dee9408e54358f257efdb14471896c5a)

## テストケースが正しく動作するように実装する

### サービス層には業務ロジックのみ

プレイヤーの初期状態を作成、それに合わせて `初期配置の状態` に合わせて盤を作成し、それを返す。
ということで、必要のない情報を削ぎ落とすと以下になった。

```java:GameService.java
    public Game start(StartPosition startPosition) {
        final Players players = playersFactory.create();
        final Board board = boardFactory.create(startPosition, players);

        return new Game(startPosition,
                        players,
                        board);
    }
```

### プレイヤーの石32個の取得方法について

プレイヤーの石数32個という固定値は`Player`クラスに定数を作成して管理する。
そして、単純な回数分の繰り返しはラムダ式の`IntStream.rangeClosed`がおすすめ。
（ラムダ式については読みづらくならないように注意が必要）

```java:PlayersFactory.java
    private List<Disk> createDisks(Side side) {
        final List<Disk> disks = new ArrayList<>();
        IntStream.rangeClosed(1, Player.MAX_DISK_COUNT)
                 .forEach(count -> disks.add(createDisk(side)));
        return disks;
    }
```

以下とどちらが文章として読みやすいかを考えて選択していく🤔
（私の場合はなるべく不等号とか括弧は減らした方が読みやすいと思って〼✋）

```java:PlayersFactory.java
    private List<Disk> createDisks(Side side) {
        final List<Disk> disks = new ArrayList<>();
        for(int index=0; index<Player.MAX_DISK_COUNT; index++) {
            disks.add(createDisk(side))
        }
        return disks;
    }
```

※ 変数を省略（`index`→`i`）しない。省略して可読性が上がることなんてほぼない🙅‍♂️

### プレイヤーの石を盤に置く

プレイヤーの石を取って、盤に配置している箇所。
Playerに対して takeDisk() を呼ぶことで先頭の石をリストから外し、外した石を盤に配置している。
（普段はRDBを利用しているからか`List.remove(index)`って中々使わないような。。）

```java:BoardFactory.java
    private List<Piece> diagonalPieces(Players players) {
        return List.of(
                createPiece(4, 4, players.light().takeDisk()),
                createPiece(5, 4, players.dark().takeDisk()),
                createPiece(4, 5, players.dark().takeDisk()),
                createPiece(5, 5, players.light().takeDisk())
        );
    }
```

```java:Player.java
    public Disk takeDisk() {
        return disks.remove(0);
    }
```

GameServieに関わるクラスは以下となった。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113636/c6021cc8-bfca-989c-f899-eef0ea44bc7a.png)


- コミット: [プレイヤーを含めて初期状態の盤面を取得する - 実装 #8](https://github.com/t-sone/reversi/pull/9/commits/041c2cd8d80cced7f954fdc6654482166a6b75dd)

## リファクタリング

パッケージ構成がぐちゃぐちゃになっていたので整理。

- board.StartPosition → game.StartPosition
- piece.Piece → board.piece.Piece
- disk.Disk → player.disk.Disk
- disk.Side → player.Side

※ コミットする単位は、対応する内容に応じてコミットを分けるべき。
特にリファクタリングはその対応が問題ないのか細分化した状態でレビューしてもらうべき。
（今回、リファクタリングのコミットを一括でやってしまったので反省。。。💧）

- コミット: [プレイヤーを含めて初期状態の盤面を取得する - リファクタリング #8](https://github.com/t-sone/reversi/pull/9/commits/fd7fa9d62d4ed264586035eb7b95169d90b62fe8)

# 最後に

今回はここまで。
TDD方式で開発すると実際に動くアウトプットがなくても、テストによる動作確認ができるのが良いなと思いました🤔
あとは最小単位での開発（アジャイル開発）は成果物が常に発生するのでモチベーションに繋がりますね👍
TDD方式でリファクタリングする機会も多くなり品質もよりよくなってる実感あります。

TDD方式の開発がもっと流行れば良いなぁ。。。