新入社員研修用にWebアプリを作ろうとしているお話【TDDでじゃんけん実装】

# 目的

久々に本社に戻ったら新入社員がいて、一応研修という名目で勉強していたが、
教える側も学ぶ側もどうすればよいのかわかっていなかったように見えた。

そのため、新人教育と自分の勉強も兼ねて、Spring FrameworkでじゃんけんができるWebアプリを実装してみる。

単に自分の興味を引くテーマだったっていうのと個人で勝手にやっているということから、
業務外のプロジェクトなのでかなりの低速進行になりそう。


あと、本人のJavaは7年前に行ったAndroid開発で止まっているので、
今のJavaの書き方と比較しておかしいところがあれば教えていただけると助かります。
特にパッケージの切り方とか。

# 仕様考察

とりあえず、話を聞いていたらJava+Spring Frameworkは確定で、できればSQLも使いたいアプリを作ろうとしているとのことだった。
それを踏まえたら下記のような仕様のアプリを作れば良さそうだ。

+ Webでじゃんけんができるようにする
  + 使用環境はJava + Spring Framework(多分Spring Boots)
  + 対戦相手はCPUで、CPUはランダムな手を出す
  + ローカルマシンでじゃんけんできれば良い(ユーザとか、セッションとか考える必要はないとする)
+ 過去の成績を表示できるようにする(SQLで)
  + 全期間の通算成績を表示できるようにする
  + 過去N戦(とりあえずN=10)の戦績も表示できるようにする

# TDDでじゃんけんを実装する

せっかくだし、TDD(テスト駆動開発)でじゃんけんのロジックを実装する。
参考にしたのは下記書籍。

https://www.amazon.co.jp/dp/B077D2L69C/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1

# じゃんけんのToDoりすと

今回、下記のToDoリストでスタートした。

## じゃんけんのToDoリスト

+ [ ] じゃんけんを行う
  + [ ] CPUの手をセットする
    + [ ] 数字を指定すれば対応する手になる
    + [ ] 数字を指定しなければランダムに出す
  + [ ] プレイヤーの手をセットする
    + [ ] じゃんけんの3すくみを行う
    + [ ] あいこを実装する
+ [ ] ヘルプを表示する
  + [ ] 3つの手があることをわかるようにする
+ [ ] 過去の勝率を表示する
  + [ ] 通算成績を表示する
  + [ ] 最近10戦を表示する

## WebGUIのToDoリスト

+ [ ] じゃんけんを行う画面の実装
  + [ ] じゃんけん前の画面の実装
  + [ ] 勝敗を表示する画面の実装
+ [ ] 過去の勝率を表示する画面の実装

# じゃんけんの手

## テスト

まずは下記の2つのテストを作成しよう。

+ [ ] CPUの手をセットする
+ [ ] 3つの手があることをわかるようにする

じゃんけんの手はグー、チョキ、パーの3つであり、それ以外は含まれないから、その2点をテストに落とし込む。
また、じゃんけんの勝敗などの管理はJankenModelクラスを作成し、そこに実装することにした。

アノテーションについては下記の記事がわかりやすい、かもしれない。

https://qiita.com/niwasawa/items/cfcd37a3c2a795c336ba

```java
package personal.mickie.janken.Model;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.Arrays;

public class JankenModelTest {

	private JankenModel model;
	private final String ROCK = "グー";
	private final String SCISSORS = "チョキ";
	private final String PAPER = "パー";
	
	@BeforeEach
	void SetUp() {
		model = new JankenModel();
	}
	
	@Test
	void testHandList() {
		// じゃんけんの手は3つ
		assertEquals(model.getHands().length, 3);
		// グー、チョキ、パーが含まれる
		assertTrue(Arrays.asList(model.getHands()).contains(ROCK));
		assertTrue(Arrays.asList(model.getHands()).contains(PAPER));
		assertTrue(Arrays.asList(model.getHands()).contains(SCISSORS));	
	}
	
	@Test
	void testSetHand(){
		// グー：1、チョキ:2、パー:3として実装する
		model.setCpuHand(1);
		assertEquals(model.getCpuHandName(), ROCK);
		model.setCpuHand(2);
		assertEquals(model.getCpuHandName(), SCISSORS);
		model.setCpuHand(3);
		assertEquals(model.getCpuHandName(), PAPER);
	}
}

```

## 実装

じゃんけんの手を示すためのクラスとCPUの手を示すためのクラスと２つが必要そうなので、テストを満たすように実装を行う。
じゃんけんの手は下記の通り、列挙型を作っておいた。

```java:JankenHands.java

package personal.mickie.janken.Model;

import java.util.ArrayList;
import java.util.List;

public enum JankenHands {
	ROCK("グー", 1),
	SCISSORS("チョキ",2),
	PAPER("パー",3);
	
	private String handName;
	private int handId;
	
	private JankenHands(String handName, int handId) {
		this.handName = handName;
		this.handId = handId;
	}
	
	public String getHandName() {
		return this.handName;
	}
	
	public int getHandId() {
		return this.handId;
	}
	
	public static final String[] getHandsName() {
		List<String> result = new ArrayList<String>();
		for (JankenHands hand : JankenHands.values()) {
			result.add(hand.getHandName());
		}
		return result.toArray(new String[result.size()]);
	}
	
	public static final JankenHands GetHandFromCode(int code) {
		for (JankenHands hand : JankenHands.values()) {
			if (hand.handId == code) {
				return hand;
			}
		}
		throw new IllegalArgumentException("じゃんけんの手は1～3までの数でお願いします。");
	}
	
}

```

CPUの手はモデルに持たせることにする。この実装は下記の通り。

```java:JankenModel.java
package personal.mickie.janken.Model;

public class JankenModel {

	public String[] getHands() {
		String[] result =  JankenHands.getHandsName();
		return result;
	}

	private JankenHands cpuHand;
	
	public void setCpuHand(int handCode) {
		setCpuHand(JankenHands.GetHandFromCode(handCode));
	}
	
	public void setCpuHand(JankenHands hand) {
		cpuHand = hand;
	}
	
	public String getCpuHandName() {
		return cpuHand.getHandName();
	}
	
	public JankenModel() {
		cpuHand = null;
	}
	
}
```

# じゃんけんの勝敗

## テスト

じゃんけんの勝ち負けの実装は注意しなければ勝ち負け逆になりそうだ。
最低限の全組み合わせを出すなら次のとおりだろうか。

CheckHandは、プレイヤー目線でCPUの手と勝負した結果とした。

```java
	@Test
	void testHandStrength() {
		// CPUがグーを出している状態で、
		model.setCpuHand(JankenHands.ROCK);
		
		// プレイヤーがパーを出していれば勝ち
		JankenHands playerHand = JankenHands.PAPER;
		assertEquals(model.CheckResult(playerHand), JankenResult.Win);
		
		// プレイヤーもグーなら引き分け
		playerHand = JankenHands.ROCK;
		assertEquals(model.CheckResult(playerHand), JankenResult.Draw);
		
		// プレイヤーがチョキなら負け
		playerHand = JankenHands.SCISSORS;
		assertEquals(model.CheckResult(playerHand), JankenResult.Lose);
		
		// 上記組み合わせで足りていない手の組み合わせを実装
		model.setCpuHand(JankenHands.PAPER);
		assertEquals(model.CheckResult(playerHand), JankenResult.Win);
		
		// CPUとプレイヤーで逆の手を出したら勝ち負けも逆になる。(ただし引き分け以外)
		playerHand = JankenHands.ROCK;
		assertEquals(model.CheckResult(playerHand), JankenResult.Lose);
	}
```

## 実装

テストではJankenModelの結果を確認しているが、じゃんけんの手の強弱なのだから、
強弱の実装はJankenHandsにもたせてJankenModelは利用するだけにする。

そうなってくると、JankenHands.javaにこのような実装を追加し、

```java

	public JankenResult getResult(JankenHands oppornentHand) {
		if (this.getHandId() == oppornentHand.getHandId()) {
			return JankenResult.Draw;
		}else if (this.getHandId() % 3 == (oppornentHand.getHandId() - 1) % 3) {
			return JankenResult.Win;
		}else {
			return JankenResult.Lose;
		}
	}
	
```

JankenModel.javaにはこのメソッドを呼び出すための実装をもたせる。

```java
	
	public JankenResult CheckResult(JankenHands playerHand) {
		return playerHand.getResult(cpuHand);
	}

```

# 現時点での実装状況

じゃんけんのToDoリストは下記の通り。

+ [ ] じゃんけんを行う
  + [ ] CPUの手をセットする
    + [x] 数字を指定すれば対応する手になる
    + [ ] 数字を指定しなければランダムに出す
  + [x] プレイヤーの手をセットする
    + [x] じゃんけんの3すくみを行う
    + [x] あいこを実装する
+ [ ] ヘルプを表示する
  + [x] 3つの手があることをわかるようにする
+ [ ] 過去の勝率を表示する
  + [ ] 通算成績を表示する
  + [ ] 最近10戦を表示する

この記事の時点での実装状況は下記においておいた。
※この時点ではまだWebアプリとしては動かないはず。

https://github.com/mickie895/JankenApp/tree/a4bb40cfb49acd8eaabd8845c5ca1d14b38933ff

次回はWebアプリ側の実装に手を出す予定。(ランダムのテストってどうやったものかな…)