下面是一个简单的Tic Tac脚趾游戏。我想知道如何进一步改进这段代码。
#include <iostream>
#include <cctype>
#include <array>
#include <random>
enum struct Player : char
{
none = '-',
first = 'X',
second = 'O'
};
std::ostream& operator<<(std::ostream& os, Player const& p)
{
return os << std::underlying_type<Player>::type(p);
}
enum struct Type : int
{
row,
column,
diagonal
};
enum struct Diagonals : int
{
leftTopRightBottom,
rightTopleftBottom
};
template<std::size_t DIM>
class TicTacToe
{
public:
TicTacToe();
bool isFull() const;
void draw() const;
bool isWinner(Player player) const;
bool applyMove(Player player, std::size_t row, std::size_t column);
private:
std::size_t mRemain = DIM * DIM;
std::array<Player, DIM * DIM> mGrid;
};
template<int DIM>
struct Match
{
Match(Type t, int i)
: mCategory(t)
, mNumber(i)
{}
bool operator() (int number) const
{
switch (mCategory)
{
case Type::row:
return (std::abs(number / DIM) == mNumber);
case Type::column:
return (number % DIM == mNumber);
case Type::diagonal:
if (mNumber == static_cast<int>(Diagonals::leftTopRightBottom))
{
return ((std::abs(number / DIM) - number % DIM) == mNumber);
}
if (mNumber == static_cast<int>(Diagonals::rightTopleftBottom))
{
return ((std::abs(number / DIM) + number % DIM) == DIM - mNumber);
}
default:
return false;
}
}
Type mCategory;
int mNumber;
};
template<std::size_t DIM>
TicTacToe<DIM>::TicTacToe()
{
mGrid.fill(Player::none);
}
template<std::size_t DIM>
bool TicTacToe<DIM>::applyMove(Player player, std::size_t row, std::size_t column)
{
std::size_t position = row + DIM * column;
if ((position > mGrid.size()) || (mGrid[position] != Player::none))
{
return true;
}
--mRemain;
mGrid[position] = player;
return false;
}
template<std::size_t DIM>
bool TicTacToe<DIM>::isFull() const
{
return (mRemain == 0);
}
template<std::size_t DIM>
bool TicTacToe<DIM>::isWinner(Player player) const
{
std::array<bool, 2 * (DIM + 1)> win;
win.fill(true);
int j = 0;
for (auto i : mGrid)
{
int x = j++;
for (auto k = 0; k < DIM; ++k)
{
if (Match<DIM>(Type::column, k)(x))
{
win[k] &= i == player;
}
if (Match<DIM>(Type::row, k)(x))
{
win[DIM + k] &= i == player;
}
if (Match<DIM>(Type::diagonal, k)(x))
{
win[2 * DIM + k] &= i == player;
}
}
}
for (auto i : win)
{
if (i)
{
return true;
}
}
return false;
}
template<std::size_t DIM>
void TicTacToe<DIM>::draw() const
{
std::cout << ' ';
for (auto i = 1; i <= DIM; ++i)
{
std::cout << " " << i;
}
int j = 0;
char A = 'A';
for (auto i : mGrid)
{
if (j == 0)
{
std::cout << "\n " << A++;
j = DIM;
}
--j;
std::cout << ' ' << i << ' ';
}
std::cout << "\n\n";
}
struct Random
{
Random(int min, int max)
: mUniformDistribution(min, max)
{}
int operator()()
{
return mUniformDistribution(mEngine);
}
std::default_random_engine mEngine{ std::random_device()() };
std::uniform_int_distribution<int> mUniformDistribution;
};
class Game
{
public:
void run();
private:
void showResult() const;
void turn();
static const std::size_t mDim = 4;
int mNumberOfPlayers = 2;
TicTacToe<mDim> mGame;
std::array<Player, mNumberOfPlayers> mPlayers{ { Player::first, Player::second } };
int mPlayer = 1;
Random getRandom{ 0, mDim - 1 };
};
void Game::run()
{
while (!mGame.isWinner(mPlayers[mPlayer]) && !mGame.isFull())
{
mPlayer ^= 1;
mGame.draw();
turn();
}
showResult();
}
void Game::showResult() const
{
mGame.draw();
if (mGame.isWinner(mPlayers[mPlayer]))
{
std::cout << "\n" << mPlayers[mPlayer] << " is the Winner!\n";
}
else
{
std::cout << "\nTie game!\n";
}
}
void Game::turn()
{
char row = 0;
char column = 0;
for (bool pending = true; pending;)
{
switch (mPlayers[mPlayer])
{
case Player::first:
std::cout << "\n" << mPlayers[mPlayer] << ": Please play. \n";
std::cout << "Row(1,2,3,...): ";
std::cin >> row;
std::cout << mPlayers[mPlayer] << ": Column(a,b,c,...): ";
std::cin >> column;
column = std::toupper(column) - 'A';
row -= '1';
pending = column < 0 || row < 0 || mGame.applyMove(mPlayers[mPlayer], row, column);
if (pending)
{
std::cout << "Invalid position. Try again.\n";
}
break;
case Player::second:
row = getRandom();
column = getRandom();
pending = mGame.applyMove(mPlayers[mPlayer], row, column);
break;
}
}
std::cout << "\n\n";
}
int main()
{
Game game;
game.run();
}发布于 2015-07-10 15:28:48
我认为有几件事值得指出。
对于Match来说,没有理由选择一个类别或其他任何东西。您不需要Type或Diagonals枚举。给定移动时,sohuld可以确定该移动是否完成了行、列或主要对角线。
// return true if given player won with the given move,
// false otherwise
bool won(Player, size_t row, size_t column);您可能会发现,只使用二维数组来表示一个二维对象就更容易了。这条路:
bool applyMove(Player player, size_t row, size_t col)
{
Player& square = mGrid.at(row).at(col);
if (square == Player::none) {
square = player;
return true;
}
else {
return false;
}
}此外,您还将注意到,我翻转了返回类型的applyMove。返回成功的true更有意义。此外,这将抛出std::out_of_range,而不是静默地什么也不做--这样您就可以在Game中捕获它并记录适当的消息。这就区分了无效移动和试图在坏方块上玩的行为。
让getRandom成为成员变量而不是成员函数是令人困惑的。我会改的。我花了一段时间才找到第一次。现在,turn()应该只服从于适当的用户:
void turn() {
if (mPlayers[mPlayer] == Player::first) {
userTurn();
}
else {
compTurn();
}
}挂起的循环很奇怪。做完每一次子案例都要返回。
同样,对于compTurn部分来说,每次选择一个随机正方形是非常低效率的。如果只剩下一个开放的平方,那么您需要花费平均的N^2循环来尝试找到它。相反,首先找到空方块,然后随机选择一个:
std::vector<std::pair<size_t, size_t>> open;
for (size_t r = 0; r < mDim; ++r) {
for (size_t c = 0; c < mDim; ++c) {
if (mGame.isOpen(r, c)) {
open.emplace_back(r, c);
}
}
}
auto choice = chooseRandom(open);
applyMove(Player::second, choice.first, choice.second);另外,TicTacToe成员对象应该真正命名为board。毕竟这不是Game。Game已经是Game了。
最后,为什么TicTacToe是类模板?如果将维度设置为变量并使用vector<vector<Player>>,则可以预先请求维度作为输入。
https://codereview.stackexchange.com/questions/96484
复制相似问题