我在C#中做了连接4。如果你从来没有玩过连接4,它就像Tic脚趾,除了碎片落在栅格底部,而且网格通常是6x7。
这是我在C#中做的第一个大型项目,所以我不知道我是否写得很好。如何改进我的代码?
using System;
namespace Connect4
{
class Program
{
static void Main(string[] args)
{
Engine game = new Engine();
char player = '1';
int column;
bool gameLoop = true;
bool inputLoop;
while (gameLoop) {
System.Console.Clear();
game.DisplayGrid();
do {
inputLoop = true;
Console.Write("\nPlayer ");
Console.Write(player);
Console.Write(": ");
if (Int32.TryParse(Console.ReadLine(), out column)) {
if (1 <= column && column <= 7) {
if (game.DropPieceInGrid(player, column)) {
inputLoop = false;
}
else {
System.Console.Clear();
game.DisplayGrid();
Console.WriteLine("\nThat column is full.");
}
}
else {
System.Console.Clear();
game.DisplayGrid();
Console.WriteLine("\nThe integer must be between 1 and 7.");
}
}
else {
System.Console.Clear();
game.DisplayGrid();
Console.WriteLine("\nPlease enter an integer.");
}
} while (inputLoop);
if (game.FourInARow(player)) {
System.Console.Clear();
game.DisplayGrid();
Console.Write("\nPlayer ");
Console.Write(player);
Console.Write(" has won!\n");
Console.WriteLine("\nPress enter to quit.");
gameLoop = false;
}
else if (game.GridIsFull()) {
System.Console.Clear();
game.DisplayGrid();
Console.WriteLine("\nIt is a draw.");
Console.WriteLine("\nPress enter to quit.");
gameLoop = false;
}
else {
player = player == '1' ? '2' : '1';
}
}
Console.ReadKey();
}
}
class Engine
{
const int NUMBER_OF_ROWS = 6, NUMBER_OF_COLUMNS = 7;
const char EMPTY = '0', PLAYER1 = '1', PLAYER2 = '2';
private char[,] grid;
int pieceCount;
public Engine()
{
grid = new char[NUMBER_OF_ROWS, NUMBER_OF_COLUMNS];
for (int y = 0; y < NUMBER_OF_ROWS; y++)
for(int x = 0; x < NUMBER_OF_COLUMNS; x++)
grid[y, x] = EMPTY;
}
public void DisplayGrid()
{
for (int y = 0; y < NUMBER_OF_ROWS; y++) {
for (int x = 0; x < NUMBER_OF_COLUMNS; x++) {
Console.Write(grid[y, x]);
Console.Write(' ');
}
Console.Write('\n');
}
}
// Returns true if the piece can be dropped in that column.
public bool DropPieceInGrid(char player, int column)
{
column--;
if (grid[0, column] != EMPTY)
return false;
for (int y = 0; y < NUMBER_OF_ROWS; y++) {
if ((y == NUMBER_OF_ROWS - 1) || (grid[y + 1, column] != EMPTY)) {
grid[y, column] = player;
break;
}
}
pieceCount++;
return true;
}
public bool FourInARow(char player)
{
// Horizontal check:
for (int y = 0; y < NUMBER_OF_ROWS; y++)
for (int x = 0; x < 4; x++)
if (grid[y, x] == player && grid[y, x + 1] == player)
if (grid[y, x + 2] == player && grid[y, x + 3] == player)
return true;
// Vertical check:
for (int y = 0; y < 3; y++)
for (int x = 0; x < NUMBER_OF_COLUMNS; x++)
if (grid[y, x] == player && grid[y + 1, x] == player)
if (grid[y + 2, x] == player && grid[y + 3, x] == player)
return true;
// Diagonal check:
for (int y = 0; y < 3; y++) {
for (int x = 0; x < NUMBER_OF_COLUMNS; x++) {
if (grid[y, x] == player) {
// Diagonally left:
try {
if (grid[y + 1, x - 1] == player) {
if (grid[y + 2, x - 2] == player)
if (grid[y + 3, x - 3] == player)
return true;
}
}
catch (IndexOutOfRangeException) {}
// Diagonally right:
try {
if (grid[y + 1, x + 1] == player) {
if (grid[y + 2, x + 2] == player)
if (grid[y + 3, x + 3] == player)
return true;
}
}
catch (IndexOutOfRangeException) {}
}
}
}
return false;
}
public bool GridIsFull()
{
return pieceCount >= NUMBER_OF_ROWS * NUMBER_OF_COLUMNS;
}
}
}
发布于 2018-10-27 00:13:11
我假设代码按预期工作,因此不会对任何逻辑进行注释。
您需要将代码分为函数或类。for循环和Main
几乎是可读的。这是您应该做的第一件事情(在编写了一些单元测试之后,这样您就知道您没有破坏任何东西)。如果没有拆分,甚至很难对代码进行注释,因为大多数注释都需要某种形式的逻辑封装。正因为如此,我将发表一篇关于将代码重构为函数的大型文章,也许其他人也会从中受益。为了简洁起见,我将简化和省略一些事情。
从一开始,您就可以将整个while
循环提取到一个函数(甚至是一个类!)。为了简化事情,我现在只使用三个类,当你让它开始工作时,我们可以更进一步。让我们从一个小的依赖关系图开始:
上面的意思是Program
类包括包含Engine
的Game
,这将是类似父级的依赖关系。
您的主要方法应该是这样的:
public static void Main(string[] args){
var engine = new Engine();
var game = new Game(engine);
game.Start();
}
Game.Start()
将是您的主循环。注意,我是通过在构造函数中传递engine对象来向游戏注入引擎。这使您有机会删除依赖项(请参阅这个问题)。
下一个清晰的地方是画一个板。根据干原理,您应该抽象如下:
System.Console.Clear();
game.DisplayGrid();
Console.WriteLine("\nPlease enter an integer.");
对于一个函数(请记住,这发生在game
对象中):
void DrawWithMessage(string message)
{
DisplayGrid();
Console.WriteLine(message);
}
其中DisplayGrid()
看起来是这样的:
void DisplayGrid(string message)
{
System.Console.Clear();
engine.DisplayGrid();
}
为什么我要把它分成你可能会问的两个函数?原因是SRP-ish方法,如果您需要更改网格的显示方式,最好是更改一个专用函数,而不是一个包含其他内容的函数。
接下来,还应该将来自用户的输入提取为两个函数。
// below is inside the input loop
var userInput = Console.ReadLine();
if(IsInputCorrect(userInput)){
game.DropPieceInGrid(player, column); // this no longer returns bool
inputLoop = false;
}
现在,您的验证封装在一个函数中。此外,DropPieceInGrid不再验证这个位置(它已经在IsInputCorrect中完成了),因为SRP再次出现了- funcion说它会掉进网格中,验证更改不应该适用于这个函数。
现在,验证将如下所示(注意它在DrawWithMessage更改后处理得有多好):
bool IsInputCorrect(string userInput){
if (Int32.TryParse(Console.ReadLine(), out column)) {
if (1 <= column && column <= 7) {
if (CanDropOnGrid(player, column)) { // this would be a new function with validation logic from DropPieceInGrid
return true;
}
else {
DrawWithMessage("\nThat column is full.");
}
}
else {
DrawWithMessage("\nThe integer must be between 1 and 7.")
}
}
else {
DrawWithMessage("\nPlease enter an integer.")
}
}
当然,这应该进一步拆分成更小的函数,甚至移到某个Validator
类(SRP )中。
以上内容将使您全面了解如何划分代码。这是您可能希望在编程过程中应用的模式列表,以使代码在将来更加清晰:干的、接吻和Unix规则。
https://codereview.stackexchange.com/questions/206348
复制相似问题