#概要
またまたコンソールゲームプログラミングですね。
もう3つ目なので、勘所が分かってきたように思います。
https://qiita.com/Chomolungma/items/52bd8b133b747cb603e7
https://qiita.com/Chomolungma/items/6cf6a76ebfb6201ea7fa
以下を参考にしながらオセロを作成しました。
館長、ありがとう!!!
https://youtu.be/cPDCpk9ZhDM
とりあえずうまく動作していそうです。
以前あったバグは修正できました。
そのバグとは、置く石の色で挟んだ先の石までひっくり返してしまうというものでした。
その場合、赤矢印の白石だけが黒石にひっくり返されるべきです
しかし以前は青矢印の白石も黒石にひっくり返されてしまっていました
お、修正がうまくいったようですね
余計なひっくり返しはありません、めでたしめでたし
#所感
checkCanPut(int argColor, int argCursorX, int argCursorY, bool turnOver)
この関数の中で2つの処理を行っています。
1.石を置くことができるのか判定する
2.石を置いた時に、指定の色に挟まれているの色をひっくり返す
どちらも共通の処理を行う箇所があります。
turnOver
で上記2つの処理をスイッチできるようにしています。
1.turnOver = false
2.turnOver = true
2つの関数に分けるのではなく、引数によってスイッチするという素晴らしいI/F
です。
これくらいの小規模な処理分岐であれば、引数の値によってスイッチさせることで共通処理をうまく活用できますね。
これが大規模な処理分岐となれば、仮想関数を使うことになるのでしょう。
こちらについて、初めは「関数のI/F
としてアリなのか?」と疑問を抱いていました。
しかしある時Excel
のVLOOKUP
関数を見て「むしろメチャクチャイイI/F
なのか!」と理解しました。
https://support.microsoft.com/ja-jp/office/vlookup-%E9%96%A2%E6%95%B0-0bbc8083-26fe-4963-8ab8-93a18ad188a1
VLOOKUP
関数では、第4引数が検索方法の指定(近似/完全一致)となっています。
微妙に違う用途への対応を1つの関数で実現できるのは、優れたI/F
と評していいのではないでしょうか。
2023/01/29追記:
設計に関して色々勉強していく中で、このI/Fが悪手であると理解しました。
1つのI/Fが表現するのは、1つの目的だけにすべきですね。
#コード
せっかくなので、コードを公開します。
ご自由にお試し下さい。
ホントはコメントとかネーミングとかもうちょっと考えて、初学者の方が分かるようにしたいと思っていますが・・・
C++
的に記述できていないところも多い(それどころかカーソル移動に対する防御的プログラミングできてないけど大丈夫!?)ので、ちょこちょこ修正していこうと思います。
回帰テストせなアカンで!!!
因みにバグの箇所は、石のひっくり返し処理を行う関数に仕込まれていました。
参考動画を脳死状態で見て、写経しているとこういうことになります。
全てを疑え!!
bool checkCanPut(int argColor, int argX, int argY, bool turnOver)
なんとbreak
文を1つ入れていなかっただけでした。
少しのミスが、全てを台無しにするのです!!
for (int i = 0; i < DIRECTION_MAX; ++i)
{
int x = argX;
int y = argY;
x += directions[i][0];
y += directions[i][1];
if ((argColor ^ 1) != cells[x][y])
{
continue;
}
while (true)
{
x += directions[i][0];
y += directions[i][1];
if ((x < 0) || (x >= BOARD_WIDTH) || (y < 0) || (y >= BOARD_HEIGHT))
{
break;
}
if (COLOR_NONE == cells[x][y])
{
break;
}
if (argColor == cells[x][y])
{
if (false == turnOver)
{
return true;
}
int turnOverX = argX;
int turnOverY = argY;
while (true)
{
cells[turnOverX][turnOverY] = argColor;
turnOverX += directions[i][0];
turnOverY += directions[i][1];
if ((x == turnOverX) && (y == turnOverY))
{
break;
}
}
break; // ここでbreakしないと余計なところまでひっくり返すバグが発生する.
}
}
}
※コードレビューコメント頂けますと幸いです
#include <iostream>
#include <conio.h>
static const int BOARD_WIDTH = 8;
static const int BOARD_HEIGHT = 8;
enum
{
COLOR_NONE = -1,
COLOR_BLACK = 0,
COLOR_WHITE = 1,
COLOR_MAX
};
enum
{
DIRECTION_UP,
DIRECTION_UP_LEFT,
DIRECTION_LEFT,
DIRECTION_DOWN_LEFT,
DIRECTION_DOWN,
DIRECTION_DOWN_RIGHT,
DIRECTION_RIGHT,
DIRECTION_UP_RIGHT,
DIRECTION_MAX
};
char colorNames[][5 + 1] =
{
"Black",
"White"
};
int directions[][2] =
{
{0, -1},
{-1, -1},
{-1, 0},
{-1, 1},
{0, 1},
{1, 1},
{1, 0},
{1, -1}
};
int cells[BOARD_WIDTH][BOARD_HEIGHT];
int cursorX;
int cursorY;
int turn;
bool checkCanPut(int argColor, int argX, int argY, bool turnOver);
bool checkCanPutAll(int argColor);
void display();
int main()
{
for (int y = 0; y < BOARD_HEIGHT; ++y)
{
for (int x = 0; x < BOARD_WIDTH; ++x)
{
cells[x][y] = COLOR_NONE;
}
}
cells[3][3] = COLOR_WHITE;
cells[4][4] = COLOR_WHITE;
cells[3][4] = COLOR_BLACK;
cells[4][3] = COLOR_BLACK;
bool canPut = true;
while (true)
{
display();
std::cout << "turn:" <<colorNames[turn] << std::endl;
if (!canPut)
{
std::cout << "Can't put!" << std::endl;
}
canPut = true;
switch (_getch())
{
case 'w':
--cursorY;
break;
case 's':
++cursorY;
break;
case 'a':
--cursorX;
break;
case 'd':
++cursorX;
break;
default:
if (checkCanPut(turn, cursorX, cursorY, false))
{
checkCanPut(turn, cursorX, cursorY, true);
cells[cursorX][cursorY] = turn;
turn ^= 1;
if (!checkCanPutAll(turn))
{
std::cout << "Pass:" << colorNames[turn] << std::endl;
turn ^= 1;
}
}
else
{
canPut = false;
}
break;
}
if ((!checkCanPutAll(COLOR_BLACK)) && (!checkCanPutAll(COLOR_WHITE)))
{
int count[COLOR_MAX] = {};
for (int y = 0; y < BOARD_HEIGHT; ++y)
{
for (int x = 0; x < BOARD_WIDTH; ++x)
{
if (COLOR_NONE != cells[x][y])
{
++count[cells[x][y]];
}
}
}
display();
std::cout << "Game Set!" << std::endl;
for (int i = 0; i < COLOR_MAX; ++i)
{
std::cout << colorNames[i] << ":" << count[i] << std::endl;
}
if (count[COLOR_BLACK] > count[COLOR_WHITE])
{
std::cout << colorNames[COLOR_BLACK] << ":Won!" << std::endl;
}
else if (count[COLOR_BLACK] < count[COLOR_WHITE])
{
std::cout << colorNames[COLOR_WHITE] << ":Won!" << std::endl;
}
else
{
std::cout << "Draw!" << std::endl;
}
_getch();
break;
}
}
return 0;
}
bool checkCanPut(int argColor, int argX, int argY, bool turnOver)
{
if (COLOR_NONE != cells[argX][argY])
{
return false;
}
for (int i = 0; i < DIRECTION_MAX; ++i)
{
int x = argX;
int y = argY;
x += directions[i][0];
y += directions[i][1];
if ((argColor ^ 1) != cells[x][y])
{
continue;
}
while (true)
{
x += directions[i][0];
y += directions[i][1];
if ((x < 0) || (x >= BOARD_WIDTH) || (y < 0) || (y >= BOARD_HEIGHT))
{
break;
}
if (COLOR_NONE == cells[x][y])
{
break;
}
if (argColor == cells[x][y])
{
if (false == turnOver)
{
return true;
}
int turnOverX = argX;
int turnOverY = argY;
while (true)
{
cells[turnOverX][turnOverY] = argColor;
turnOverX += directions[i][0];
turnOverY += directions[i][1];
if ((x == turnOverX) && (y == turnOverY))
{
break;
}
}
break;
}
}
}
return false;
}
bool checkCanPutAll(int argColor)
{
for (int y = 0; y < BOARD_HEIGHT; ++y)
{
for (int x = 0; x < BOARD_WIDTH; ++x)
{
if (checkCanPut(argColor, x, y, false))
{
return true;
}
}
}
return false;
}
void display()
{
system("cls");
for (int y = 0; y < BOARD_HEIGHT; ++y)
{
for (int x = 0; x < BOARD_WIDTH; ++x)
{
if ((cursorX == x) && (cursorY == y))
{
std::cout << "◎";
}
else
{
switch (cells[x][y])
{
case COLOR_BLACK:
std::cout << "〇";
break;
case COLOR_WHITE:
std::cout << "●";
break;
case COLOR_NONE:
std::cout << "・";
break;
}
}
}
std::cout << std::endl;
}
}