- CPUを相手に対戦するオセロがプレイできる
- コンソール上にオセロ盤を表示する
- 座標入力でコマを配置する
- 置くことのできない座標を入力した場合は再入力させる
- CPUは2秒経過すると置くことのできる座標からランダムで1つを選びコマを配置する
- 手番中に置くことのできるマスが存在しない場合は自動でスキップする
- 対戦終了後にお互いのコマの総数・戦績が見られる
- 対戦終了後にもう一度プレイすることができる
- クラス・メソッドをなるべく切り分けることで可読性・保守性の高いコードにする
- 説明変数を使うことで可読性の高いコードにする
- テストを書いてみる
- そこそこ切り分けられた気がする
- 見ただけで中身がわからない変数を潰せた(はず)
- テスト、一応体験ぐらいはできた
- 多少のアルゴリズム(ロジック?)を必要とするオセロを作れた
- Boardクラスが突出してコード量が多くなってしまったが、これ以上切り分ける方法が思いつかなかった
- テストが結局後書きになってしまった
- ちゃんとテストがかけたかというと、ぱっと思いつくT/Fパターンを書いたのとバグを見つけたときにそれを直すためのデバッグに使ったくらいで理想とされる使い方じゃなかった
- 結局保守性を高いまま維持できたかというと、そんなでもない気がする
- GUIでの実装
- モンテカルロ木探索でのCPU実装
- ヘルプ機能(置けるマスを教えてもらえる、プレイヤーの手番でも上記探索での最善手を教えてもらえる)
- 待った!機能
- 盤面再現機能
- 配列やオブジェクトが参照渡しであるため、メソッドの呼び出し先で書き換えた値が呼び出し元でも反映されるということをちゃんと認識した(気付かずに詰まってた)
- テストのきほんのkぐらいは学べた
- 最初にちゃんと設計しておくことの大切さ
main(){ ゲームを進行し、終了後はもう一度プレイするかを確認する }
- board
- input
- computer
- inGame
- matchResult = new int[3]
- CPU = 2
- static playerTurn
Game(){
ゲーム進行に必要なインスタンスを生成
}
play(){
ゲーム中フラグをtrueに、手番プレイヤーを1に設定
ゲーム中フラグがtrueの間は順番にコマを置き続け、そのたびに勝利判定(盤上でいずれかのプレイヤーのコマが0個になるか、オセロ盤がコマで埋まった状態になる)を行う。
}
playTurn(){
オセロ盤と、どちらのプレイヤーの手番であるかを出力する。
CPUの番であれば配置可能なランダムな位置にコマを置く。
プレイヤーの番であれば配置可能な位置が入力されるまでコマを置く位置の入力を求め続ける。
その後手番を終了する。
}
endTurn(){
手番の終了処理をする。
}
judgeWinner(){
コマの数を数え、盤上でいずれかのプレイヤーのコマが0個になるか、オセロ盤がコマで埋まった状態になればゲーム中フラグをfalseにし、
盤上のコマが多い方を勝者として出力し、戦績を反映させ、それを表示する
}
getMatchResult(){
戦績を表示する
}
checkRestart(){
もう一度プレイするかの入力を受け付けるメソッドを呼び出す
}
- BOARD_SIZE = 8
- squareList = new int[8][8]
Board(){
ゲーム開始時のオセロ盤を生成
}
void display(){
現在のオセロ盤をコンソールに表示する
}
boolean putPiece(int[] position){
任意の位置にコマを置けるかを確認し、
置ければコマを置き、その周囲の対応コマを裏返し、trueを返す
置けなければその旨を表示しfalseを返す
}
boolean validateSpecifiedPosition(int[] position){
任意の位置がオセロ盤に存在するか、
任意の位置に既にコマが置かれていないか、
任意の位置にコマを置いたときに周囲に裏返せるコマがあるかを確認し、
そのすべてを満たす場合はtrueを返す
満たさない場合はfalseを返す
}
boolean checkSurroundedSquare(int[] position){
任意の位置の周囲8方向に対してcheckSpecifiedDirectionを呼び出し、
1方向でもtrueを返した場合はtrueを返す
いずれも満たさない場合はfalseを返す
}
boolean checkSpecifiedDirection(int[] position, int directionX, int directionY){
任意の位置と方向を受け取り、その方向の隣接したマスがオセロ盤に存在しないか、
そのマスにコマが置かれているか、
そのマスが自分のコマの色と同じ色であるかを確認し、
いずれかを満たす場合はfalseを返す
いずれにも該当しない場合は以下を繰り返す
確認対象のマスを1つ先に進め、そこがオセロ盤に存在するかを確認し、存在しなければfalseを返す。
自分のコマの色と同じ色が見つかればtrueを返す。
}
void reverseSurroundedSquare(int[] position){
任意の位置の周囲8方向に対してreverseSpecifiedDirectionを呼び出す。
checkedPositionListの初期値として任意の位置を格納する
}
void reverseSpecifiedDirection(ArrayList<int[]> checkedPositionList, int directionX, int directionY){
位置を要素とする配列と方向を受け取り、配列の末尾に格納された位置からその方向に隣接したマスがオセロ盤に存在しないか、
そのマスにコマが置かれているかを確認し、
いずれかを満たす場合はfalseを返す
いずれにも該当しない場合はそのマスが自分のコマの色と異なる色であるかを確認し、
異なる色であれば配列の末尾にそのマスを追加し、その配列を引数とした上でもう一度このメソッドを呼び出す
同じ色であれば現在の配列を引数としてreversePiecesを呼び出す
}
void reversePieces(ArrayList<int[]> checkedPositionList){
渡された配列の先頭の要素を削除(コマを置いた位置)し、
配列に残ったマスすべてを自分の色に変更する
}
ArrayList<int[]> findPlaceablePosition(){
オセロ盤のすべてのマスを検証し、現在手番のプレイヤーが置くことのできるマスを配列で返す
}
boolean isBoardFilled(){
オセロ盤のすべてのマスに対してコマが置かれていないかを確認し、1マスでも空いているマスがあればfalseを返す
そうでなければtrueを返す
}
int[] countPieces(){
オセロ盤のすべてのマスを確認し、いずれかのプレイヤーの色であればそれをカウントし、配列として返す
}
boolean isInRange(int x, int y){
任意のx,y座標がオセロ盤に存在するかを確認し、true/falseを返す
}
void setSquareList(int[] position){
任意の位置を現在手番のプレイヤーの色に変更する
}
int[][] getSquareList(){
オセロ盤を返す
}
- EXPECTED DIGIT = 2;
- s = new Scanner(System.in);
int[] receivePosition(){
コマを置く位置の入力を受け取る
}
int[] getNumericPosition(String pos){
受け取った入力が2桁でなければ {-1,-1}(不正な値)を返す
そうでなければ入力を数字に変換し配列で返す
}
int getXPosition(char pos){
A~Hを0~7に変換して返す
それ以外の場合は-1を返す
}
int getYPosition(char pos){
受け取った1桁の数字を1つ減らし、オセロ盤の範囲外であれば-1を返す
そうでなければその数字を返す
}
boolean playAnotherGame(){
もう一度プレイするかどうかの入力を受け取る
}
- board
Computer(){
使用しているオセロ盤を受け取る
}
putOnRandomPlace(){
配置可能なマスを配列で受け取り、そのサイズが0ならターンを終える
サイズが1以上であればマスをランダムで選び2秒待った後にコマを置く
}
static <E> E getLastElement(List<E> list){
渡したリストの最後の要素を返す
}