Javaで普通のオセロをつくった話


<img src="https://qiita-image-store.s3.amazonaws.com/0/134076/2de42b80-995f-4e2a-7a95-934dfcb5c39e.gif" width=700px>

## はじめに

こんにちは！
私事になりますが、4月から上京して新社会人になりました。
現在研修の真っ只中です。(研修は3ヶ月あります。)
研修では、エンジニア全員がJavaを学ぶことになっています。
どのプログラミング言語においてもオブジェクト指向の考え方が重要になりますが、
Java はオブジェクト指向が前提に設計された言語で、最初に学ぶには良い言語だと思います。
また、Mac、Windows、Linuxで同じように動作するので、パソコンに依存することなく広く使うことができるという特徴もあります。
配列、forループ、メソッド、オブジェクト指向 を勉強したものの、
実際にこれらがソフトウェア上でどう協調しているかのイメージがいまいち掴みにくい人も多いのではないでしょうか？
そこで今回はCLIで動くオセロアプリケーションをつくって、学習した知識を実践に活かしていきたいと思います。
私は、プログラミングの最も効率の良い勉強方法は、「自分だけの力で1個アプリケーションをつくって公開すること」だと思っています。
では、さっそく本題に入りたいと思います！

## Hello, Othello
みなさんオセロはやったことあるでしょうか？
白と黒のコマがあり、相手のコマをはさむと自分のコマの色に変わります。
最終的に自分の色のコマが多いほうが勝ちです。
オセロのアプリケーションをつくるに当たって、以下のようにクラスを分けるのが妥当ではないでしょうか？
白か黒かの状態、コマの位置などを保持するコマクラス
コマの引っくり返し判定をするフィールドクラス
ゲーム全体をコントロールするゲームクラス
雰囲気をつかむために、まずは盤面にコマを置いて表示するところまでやってみましょう！

### コマクラスを作成
オセロの１つ１つのコマの情報はコマクラスで扱います。
コマの状態(白か、黒か、空か)の管理はString型のstate変数で行います。
ここでは、黒がB、白がW、空がEと取り決めています。
状態を変更するsetState()メソッド、位置を取得するためのgetPosition()メソッドを実装します。

``` java:Koma.java
public class Koma{
	private String state; // オセロの色が黒…B、白…W、空…E
	private int x;
	private int y;

	public Koma(int x,int y){
		this.state = "E";
		this.x = x;
		this.y = y;
	}

	public String getState(){
		return this.state;
	}

	public void setState(String state){
		this.state = state;
	}

	public int[] getPosition(){
		int[]pos = {this.x, this.y};
		return pos;
	}
}

```

### フィールドクラスを作成
フィールドクラスでは、盤面上にコマを置いたりコマを引っくり返したりできます。
盤面上にすべてのコマを状態Eで初期化するprepare()メソッドや、
盤面上の状況を可視化してコマンドラインに出力するfeature()メソッドを実装します。

```java:Field.java
import java.util.ArrayList;
import java.util.List;

public class Field{
  private List<Koma> komalist;
  private int ynum = 0;
  private int xnum = 0;

  public Field(int xnum, int ynum){
    this.xnum = xnum;
    this.ynum = ynum;
  }
  public void prepare(){
    this.komalist = new ArrayList<>();
    for(int y=0;y<this.ynum;y++){
      for(int x=0;x<this.xnum;x++){
        Koma koma = new Koma(x,y);
        this.komalist.add(koma);
      }
    }
  }

  public Koma getKoma(int y, int x){
    for(Koma koma : this.komalist){
      int[]pos = koma.getPosition();
      if(pos[0]==y && pos[1]==x){
        return koma;
      }
    }
    return null;
  }

  public void putKoma(int x, int y, String state){
    Koma koma = this.getKoma(x,y);
    koma.setState(state);
  }

  public void feature(){
    String [][] board = new String[ynum][xnum];
    for(Koma koma : this.komalist){
      int[] pos = koma.getPosition();
      String state = koma.getState();
      board[pos[1]][pos[0]] = state;
    }
    System.out.println("\n\t0\t1\t2\t3\t4\t5\n");
    for(int y=0;y<board.length;y++){
      System.out.print(y+"\t");
      for(int x=0;x<board[0].length;x++){
        String b = board[y][x];
        System.out.print(b+"\t");
      }
      System.out.println("\n");
    }
  }
}

```

### ゲームクラスを作成
フィールドクラスを呼び出し、x=2,y=2 の位置に黒いコマを置いてみます。

```java:Game.java

public class Game{
  public static void main(String[] args){
    Field field = new Field(6,6);
    field.prepare();
    field.putKoma(2,2,"B");
    field.feature();
  }
}

```

### 実行

```
$ javac Game.java
$ java Game
```

Javaのプログラムをコンパイルして、実行します。

<img src="https://qiita-image-store.s3.amazonaws.com/0/134076/359e8262-369e-4244-1aba-7afcc107cee2.png" width="500px">

x=2,y=2 の位置がB、その他の場所はEになっていますね！

## ひっくり返す判定
続いて、コマを置いたときに、相手のコマをひっくり返す処理を追加していきます。
具体的にオセロをイメージしてみましょう。(説明のため4✕4の簡易的な状況を想定しています)
<img src="https://qiita-image-store.s3.amazonaws.com/0/134076/9eadafc9-132c-4eea-e598-bcffaebab6aa.png" width=500px>


図のような状況の場合、B-1に白コマを置いたらどうなるでしょうか？
とても簡単ですね！あいだにある黒コマがすべて白コマに反転します。
B-2と、B-3 にある黒コマが白コマに変わります。
<img src="https://qiita-image-store.s3.amazonaws.com/0/134076/76efdbab-fc1a-442f-5e67-5a23aa6443a7.png" width=500px>

では、このような場合ではどうでしょう？
今回は反転する箇所が多いですね！
D-1にある白コマのおかげで、C-1にある黒コマが反転します。
D-3にある白コマのおかげで、C-2にある黒コマが反転します。
B-4にある白コマのおかげで、B-2、B-3にある黒コマが反転します。

さてさて、これをアルゴリズムに落とし込みましょう。
ちょっと微妙に複雑そうですね。
上の2つの例をみてもらうと分かる通り、コマをひっくり返すときの共通のルールがあります。
それは、ある方向において、白コマと白コマが黒コマをはさんでいるということです。
盤面上は方向は8方向(北、北東、東、東南、南、南西、西、北西)あるので、すべての方角に関して判定の処理を行わなければいけません。
白コマと白コマが黒コマをはさむ、というのはすこしザックリしています。
プログラムの流れで記述出来るようにもう少し噛み砕くと以下のようになります。
これから置く予定のコマが白コマの場合、

1. ある方向において、近くからコマを順に取得する。
2. 最も近い白コマのインデックスを取得する。
3. そのインデックスまで黒コマが途切れなく続いている場合はtrue、そうでない場合はfalse

先程の例を使って考えていきましょう。
B-1の位置から8方向(北、北東、東、東南、南、南西、西、北西)のコマリストを取得すると以下のようになります。

|方角|コマリスト|
|---|---|
|北|-|
|北東|-|
|東|黒-白|
|東南|黒-白|
|南|黒-黒-白|
|南西|黒|
|西|白|
|北西|-|

<img src="https://qiita-image-store.s3.amazonaws.com/0/134076/6cb5f5e9-df9d-0bd2-2aaf-a72af65ac32c.png" width=500px>

次に置く予定のコマ色が白なので、東・東南・南 の3方向でひっくり返しが発生します。

## CPUをつくる

ネットワーク通信をさせて友達と対戦するようにできれば面白いですが、
それはちょっとレベルが高そうな感じがするので、今回はCPUと対戦しましょう。
強いCPUをつくるのはすこし大変ですが、弱いCPUならすぐにできます。
弱いCPUは以下のような実装でできます。

1. コマを置きうる場所のリストを取得する。
2. その中からランダムに1個を選択する。

たったこれだけです。

オセロには、ひっくり返しが発生する場所にしか置いてはいけないというルールがあります(確か)
例えば、下の画像のような状況で次置くコマが白色の場合、
置きうる場所は、オレンジ色(A-3、B-4、C-1、D-2)の4箇所となります。
この4箇所からランダムで1個選んで、そこにコマを配置する動作を
「CPUがコマを置いた」ということにしましょう。

<img src="https://qiita-image-store.s3.amazonaws.com/0/134076/d547ca40-c3a1-29e2-af0b-a88a023d123f.png" width=500px>

## おわりに

以上、オセロのプログラムを説明しました。
大切なのは、オブジェクト指向のありがたみを感じながらプログラムを書くことだと思います！
今回は

* コマクラス
* フィールドクラス
* ゲームクラス

の3つに分けることで人間が読みやすいコードになっています。
もしオブジェクト指向がなく、これらを手続き型でかいたらどうでしょうか？
それはとてもとても大変だと思います。
大事なのは、自分の頭で考えてクラスを設計してコーディングすることです。
是非是非、オセロでもいいし、オセロじゃなくてもいいし、ブラックジャックとかでもいいし、Webサーバーとかでもいいので、プログラミング言語を覚えた際には、1から何かをつくるという体験をしてみましょう！