c++填字游戏求解_一个方便的井字游戏求解器第1部分

c++填字游戏求解

问题:(Problem:)

Create a Tic-Tac-Toe solver!

创建一个井字游戏求解器!

说什么…? (Say What…?)

Many coding interviews these days take place at the comfort of your own home (and laptop, and dev environment…). The interviews focus on live coding skills than abstract data-structures/algorithms knowledge.

如今,许多编码采访都在您自己的家中(以及笔记本电脑和开发环境…)进行。 访谈的重点是实时编码技能,而不是抽象的数据结构/算法知识。

  1. The interviewer is interested in how good you are at CODING! You better be at home in your own IDE of choice and build system. Whiteboard/algorithmic interviews aren’t equipped to test these skills.

    面试官对您在编码方面的表现很感兴趣! 您最好在家中选择和构建自己的IDE。 白板/算法面试无法测试这些技能。
  2. You must pay a close attention to syntax — A whiteboard interview can be extremely forgiving when a missing semicolon is concerned. Not so much for live coding! Your code better compile (for compiler based languages), and produce a correct output.

    您必须密切注意语法-如果担心缺少分号,则白板采访可能会非常宽容。 现场编码没有那么多! 您的代码可以更好地编译(对于基于编译器的语言),并产生正确的输出。
  3. You should be skilled in developing a mini-system, with good abstractions, interfaces and objects. Writing a single function is sufficient for most algorithmic interviews. A live coding requires you to build a good object-oriented program.

    您应该熟练开发具有良好抽象,接口和对象的小型系统。 编写单个函数就足以满足大多数算法面试的需要。 实时编码要求您构建一个良好的面向对象程序。
  4. Efficiency isn’t too important. You can ignore the big-O notation for most of the coding exercises :). As long as your program runs in a reasonable time, the interviewer (and you!) will be happy 🙂

    效率不是太重要。 对于大多数编码练习,您都可以忽略big-O符号:)。 只要您的程序在合理的时间内运行,面试官(和您!)就会很高兴🙂
  5. Testing is EXTREMELY important! Make sure you write good test cases from the get-go. This is where you can stand out compared to many other candidates.

    测试非常重要! 确保从一开始就编写好的测试用例。 与许多其他候选人相比,这是您可以脱颖而出的地方。
  6. There is an IMMENSE time-pressure. Of course, there is time pressure for whiteboard interviews as well. However, you do get partial credit for coming up with the algorithm, analyzing it, and writing a working pseudocode on the board. You don’t have that luxury for live coding! You better finish your code, and your code better work!

    存在IMMENSE时间压力。 当然,白板采访也有时间压力。 但是,您确实会得到部分功劳,因为他提出了该算法,对其进行了分析并在板上编写了有效的伪代码。 您没有那么奢侈的实时编码! 您最好完成您的代码,并且您的代码可以更好地工作!

With that in mind, let’s run through a coding exercise — developing a Tic-Tac-Toe solver. The initial problem statement is always vague, but many companies ask you to develop a step-by-step solution. Let’s go through these steps and come up with a quick and good object-oriented implementation.

考虑到这一点,让我们进行一次编码练习-开发一个Tic-Tac-Toe解算器。 最初的问题陈述总是含糊不清,但是许多公司要求您开发逐步的解决方案。 让我们完成这些步骤,并提出一个快速且良好的面向对象的实现。

代表董事会 (Representing the Board)

The most important component of our program is the Tic Tac Toe board itself! Let’s assume that we are starting with a standard 3X3 board, but be prepared to extend your program to a general nXn board. In this post, we have decided to represent the board with a standard 2-D array. The value types in the array come from an enum = {NONE, USER, COMPUTER}. You can use a more packed representation using bits/ints/1D arrays etc., but be prepared to manipulate them efficiently.

我们程序中最重要的组件是Tic Tac Toe板本身! 假设我们从一个标准的3X3板开始,但是准备将您的程序扩展到一个普通的nXn板。 在这篇文章中,我们决定用标准的2D阵列代表电路板。 数组中的值类型来自enum = {NONE, USER, COMPUTER} 。 您可以使用使用bit / ints / 1D数组等的更紧凑的表示形式,但要做好有效地操作它们的准备。

In addition, the actual representation of the board is an implementation detail. It makes sense to make it a private variable of our class. (along with the size of the board itself).

此外,董事会的实际代表是实施细节。 使其成为我们类的私有变量是有意义的。 (以及董事会本身的规模)。

enum Player {
  NONE = 0,
  USER = 1,
  COMPUTER = 2,
};
class TicTacToe {
public:
  TicTacToe() {
    for (int i = 0; i < sz; i++) {
      std::vector<Player> row(sz, NONE);
      board.push_back(row);
    }
  }
private:
  const unsigned sz = 3;
  std::vector<std::vector<Player> > board;
};

Note that we are using dynamically sized vectors instead of fixed size arrays Player[3][3] or std::array<Player, 3> so that we can extend the game to an arbitrary n X n board.

请注意,我们使用的是动态大小的向量,而不是固定大小的数组Player[3][3]std::array<Player, 3>以便我们可以将游戏扩展到任意n X n面板。

问题一: (Problem 1:)

编写功能以显示当前板。 (Write a function to display the current board.)

Always prefer writing side-effect free functions, and use a driver program in main.cc to add a side-effect: display the string output on the screen. (Let's also assume that user always has a cross X, and the computer O. You should be able to change it later).

总是喜欢编写无副作用的函数,并使用main.cc的驱动程序添加副作用:在屏幕上显示输出的字符串。 (让我们假设用户始终有一个十字X ,而计算机O您以后应该可以对其进行更改)。

tic_tac_toe.h:

tic_tac_toe.h:

class TicTacToe {
 public:
  ...
  std::string Display() const {
    std::string ret = "";
    for (int i = 0; i < sz; i++) {
      for (int j = 0; j < sz; j++) {
        if (board[i][j] == NONE) ret += "-";
        else if (board[i][j] == USER) ret += "X";
        else ret += "O";
      }
      ret += "\n";
    }
    return ret;
  }
  ...
};

main.cc: (Right now simply instantiates an instance of TicTacToe and displays it).

main.cc :(现在只需实例化TicTacToe的实例并显示它)。

#incude "tic_tac_toe.h"


int main (int argc, char *argv[]) {
  TicTacToe t;
  std::cout << t.Display();
}

It’s also a good idea to start unit tests early.

尽早开始单元测试也是一个好主意。

tic_tac_toe_test.cc:

tic_tac_toe_test.cc:

GTEST("Empty board") {
  TicTacToe t;
  EXPECT_EQ("---\n---\n---\n", t.Display());
}

问题2:(Problem 2:)

编写函数让用户动弹 (Write a function to let a user make a move)

A user should be able to place a cross on any unoccupied square. We assume that the board is ready — i.e. no player has already won the game. Like previous problem, we keep the class method side-effect free — Use the std::cin and std::cout I/O methods from main.cc. Moreover, we return a boolean flag from the method to indicate whether the move is valid.

用户应该能够在任何未占用的正方形上放置一个十字架。 我们假设棋盘已经准备好,即没有玩家赢得比赛。 像前面的问题一样,我们使类方法没有副作用-使用main.ccstd::cinstd::cout I / O方法。 此外,我们从方法中返回一个布尔值标志,以指示移动是否有效。

tic_tac_toe.h:

tic_tac_toe.h:

class TicTacToe {
 public:
  ...
  bool UserMove(int i, int j) {
    if (i < 0 || j < 0 || i >= sz || j >= sz) return false;
    if (board[i][j] != NONE) return false;
    board[i][j] = USER;
    return true;
  }
  ...
}

main.cc:

main.cc:

#include "tic_tac_toe.h"
#include <iostream>


int main (int argc, char *argv[]) {
  TicTacToe t;
  std::cout << t.Display();
  while (true) {
    std::cout << "Enter row and column number ->\n";
    int i, j;
    std::cin >> i >> j;
    if (!t.UserMove(i, j)) {
      std::cout << "That move is not valid. ";
    } else {
      std::cout << t.Display();
    }
  } 
}

Don’t forget to add a few test cases to test all conditions in the UserMove method.

不要忘记添加一些测试用例来测试UserMove方法中的所有条件。

tic_tac_toe_test.cc:

tic_tac_toe_test.cc:

GTEST("Out of bound moves") {
  TicTacToe t;
  EXPECT_FALSE(t.UserMove(1, -1));
  EXPECT_FALSE(t.UserMove(-4, 0));
  EXPECT_FALSE(t.UserMove(0, 4));
  EXPECT_FALSE(t.UserMove(6, 2));
}


GTEST("Already Occupied") {
  TicTacToe t;
  EXPECT_TRUE(t.UserMove(1, 1));
  EXPECT_EQ("---\n-X-\n---\n", t.Display());
  EXPECT_FALSE(t.UserMove(1, 1));
}

At this point, we have a functional program that basically does nothing! :). It lets the user make moves ad-infinitum, without letting the computer make a move. It also does not check for a winning condition :). The following movie shows the partially running program

至此,我们有了一个基本不执行任何操作的功能程序! :)。 它使用户可以无限制地进行移动而无需计算机进行移动。 它还不检查获胜条件:)。 以下电影显示了部分运行的程序

Partially running TicTacToe in an infinite loop

问题三:(Problem 3:)

检查用户或计算机是否赢了! (Check whether the User or the Computer has won!)

This can best be done by a const class method that takes a player type, iterates over the board and checks whether either a row, or a column or a diagonal contains all cells with the player type!. In this implementation, we have used one double-loop with auxiliary arrays to check these conditions. Of course, there are efficient ways of checking there, by caching these variables in methods like UserMove that we defined above.

最好使用const类方法来完成此操作,该方法采用播放器类型,在板上迭代并检查行,列或对角线是否包含所有具有播放器类型的单元! 在此实现中,我们使用了一个带有辅助数组的双循环来检查这些条件。 当然,通过在我们上面定义的UserMove方法中缓存这些变量,可以找到有效的检查方法。

tic_tac_toe.h

tic_tac_toe.h

class TicTacToe {
 public:
  ...
  bool TicTacToe::IsWinner(Player p) const {
    std::vector<int> r_count, c_count;
    r_count.assign(sz, 0);
    c_count.assign(sz, 0);
    int left_diag = 0, right_diag = 0;
    for (int r = 0; r < sz; r++) {
      for (int c = 0; c < sz; c++) {
        if (board[r] == p) {
          r_count[r]++;
          if (r_count[r] == sz) return true;
          c_count++;
          if (c_count == sz) return true;
          if (r == c) {
            left_diag++;
            if (left_diag == sz) return true;
          }
          if (r+c == sz-1) {
            right_diag++;
            if (right_diag == sz) return true;
          }
        }
      }
    }
    return false;
  }
  ...
};

Our main.cc finally looks close to being done! — it lets user make a move, and exits with an appropriate message once either the user or the computer wins. Of course, it still does not make any automated moves from the computer 🙂

我们的main.cc终于完成了! —它使用户可以移动,并在用户或计算机获胜后退出并显示相应的消息。 当然,它仍然不会从计算机上进行任何自动移动

main.cc:

main.cc:

#include "tic_tac_toe.h"
#include <iostream>


int main (int argc, char *argv[]) {
  TicTacToe t;
  std::cout << t.Display();
  while (true) {
    std::cout << "Enter row and column number ->\n";
    int i, j;
    std::cin >> i >> j;
    if (!t.UserMove(i, j)) {
      std::cout << "That move is not valid. ";
    } else {
      std::cout << t.Display();
      if (t.IsWinner(USER)) {
        std::cout << "Congratulations! You won!\n";
        break;
      }
    }
  }
}

This is also a good opportunity to test different winning conditions by different players. We are going to show a couple of these conditions in this post. Feel free to write your own test cases.

这也是测试不同玩家获胜条件的好机会。 我们将在这篇文章中展示其中几个条件。 随意编写自己的测试用例。

tic_tac_toe_test.cc:

tic_tac_toe_test.cc:

GTEST("Player wins diagonal") {
  TicTacToe t;
  EXPECT_TRUE(t.UserMove(0, 0));
  EXPECT_FALSE(t.IsWinner(USER));
  EXPECT_TRUE(t.UserMove(1, 1));
  EXPECT_FALSE(t.IsWinner(USER));
  EXPECT_TRUE(t.UserMove(2, 2));
  EXPECT_TRUE(t.IsWinner(USER));
}


GTEST("Player wins top row") {
  TicTacToe t;
  EXPECT_TRUE(t.UserMove(0, 0));
  EXPECT_TRUE(t.UserMove(0, 1));
  EXPECT_TRUE(t.UserMove(0, 2));
  EXPECT_TRUE(t.IsWinner(USER));
}

At this point, our program is getting shape, with a proper winning condition and termination. The last missing piece is adding a computer strategy and taking turns between the user and the computer. Play this movie to see the program in action:

至此,我们的计划正在逐步成形,有适当的获胜条件和终止条件。 最后缺失的部分是添加计算机策略,并轮流在用户和计算机之间。 播放此电影以查看正在运行的程序:

Image for post

This concludes the part 1 of our series on coding a TicTacToe game in real-time. In this part, we tackled the problems of (1) creating and displaying a TicTacToe board, (2) Letting user make a move, and checking its validity, and (3) Checking whether the user or the computer has won the game. Stay tuned for part 2 of this series where we develop the strategy for computer to compete with the user.

至此,我们的系列的第1部分结束了有关实时编写TicTacToe游戏的文章。 在这一部分中,我们解决了以下问题:(1)创建和显示TicTacToe棋盘,(2)让用户移动并检查其有效性,以及(3)检查用户或计算机是否赢得了比赛。 请继续关注本系列的第2部分,在该部分中,我们将开发计算机与用户竞争的策略。

Originally published at https://cppcodingzen.com on September 15, 2020.

最初于2020年9月15日发布在https://cppcodingzen.com上。

翻译自: https://medium.com/swlh/a-handy-tic-tac-toe-solver-part-1-a20cb9fd557f

c++填字游戏求解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值