简体   繁体   English

在井字游戏中确定获胜者时,如何避免重复?

[英]When determining the winner in a Tic Tac Toe game, how do I avoid repetition?

Note: I am a beginner in Java (2 - 3 months of experience).注意:我是 Java 的初学者(2 - 3 个月的经验)。

Doing a project on JetBrains/Hyperskill about making a Tic Tac Toe game, I found myself repeating quite a bit of code when trying to determine the winner of the game.在 JetBrains/Hyperskill 上做一个关于制作井字游戏的项目时,我发现自己在尝试确定游戏的获胜者时重复了很多代码。 To represent the game as a coordinate system (Thus 1,1 being at the bottom left and 3,3 at the top right) I am using a two-dimensional array.为了将游戏表示为坐标系(因此 1,1 在左下角,3,3 在右上角)我使用了一个二维数组。 This is the function for determining the winner:这是用于确定获胜者的 function:

public String determineWinner() {
    int countX = 0; // amount of X's in a row
    int countO = 0; // amount of O's in a row

    for (int y = 0; y <= 2; y++) { // for all horizontal rows
        countX = 0;
        countO = 0;
        for (int x = 0; x <= 2; x++) { // loop through all x-coordinates
            String value = this.field[x][y]; 
            if (value.equals("X")) { // if the value at that coordinate equals "X", add 1 to the count
                countX++;
            }
            if (value.equals("O")) { // same here
                countO++;
            }
        }
        if (countX == 3) { // if the count is 3 (thus 3 X's in a row), X has won
            return "X wins";
        }
        if (countO == 3) { // same here
            return "O wins";
        }
    }

    // Same thing, but for all vertical columns
    for (int x = 0; x <= 2; x++) {
        countX = 0;
        countO = 0;
        for (int y = 0; y <= 2; y++) {
            String value = this.field[x][y];
            if (value.equals("X")) {
                countX++;
            }
            if (value.equals("O")) {
                countO++;
            }
        }
        if (countX == 3) {
            return "X wins";
        }
        if (countO == 3) {
            return "O wins";
        }
    }

    // Same thing, but for diagonal
    countX = 0;
    countO = 0;
    for (int i = 0; i <= 2; i++) {
        String value = this.field[i][i];
        if (value.equals("X")) {
            countX++;
        }
        if (value.equals("O")) {
            countO++;
        }
    }
    if (countX == 3) {
        return "X wins";
    }
    if (countO == 3) {
        return "O wins";
    }

    // Same thing, but for other diagonal
    countX = 0;
    countO = 0;
    for (int i = 0; i <= 2; i++) {
        String value = this.field[i][2-i];
        if (value.equals("X")) {
            countX++;
        }
        if (value.equals("O")) {
            countO++;
        }
    }
    if (countX == 3) {
        return "X wins";
    }
    if (countO == 3) {
        return "O wins";
    }

    if (this.getNumberOfMoves() == 9) { // if the number of moves equals 9, the game is over and it is a draw
        return "draw";
    }

    return "game not finished";
}

Currently, the code allows you to set a starting board (a starting arrangement for all the O's and X's) and then lets you do 1 move.目前,该代码允许您设置起始板(所有 O 和 X 的起始排列),然后让您进行 1 次移动。 After this, the game decides who is the winner or if it is a draw etc.在此之后,游戏决定谁是赢家,或者是否是平局等。

As one quickly notices, the function is way too long and it has quite a portion of repetition, yet I am unable to come up with any ways to shorten it.很快就会注意到,function 太长了,而且有相当一部分重复,但我无法想出任何方法来缩短它。

Does anyone have any tips?有没有人有任何提示? Or any guidelines that apply to all code?或者适用于所有代码的任何准则?

DISCLAIMER: Sorry if my answer started getting sloppy towards the end.免责声明:对不起,如果我的回答在最后开始变得草率。 Also, I have a code at the bottom showing all the things I talked about in action.另外,我在底部有一个代码,显示了我在行动中谈到的所有事情。

I think the simplest thing I can say is to use more methods and possibly classes.我认为我能说的最简单的事情是使用更多的方法和可能的类。 Firstly, one of the ways to avoid repetition in all of your codes is to write them using object-oriented programming.首先,避免所有代码重复的方法之一是使用面向对象编程来编写它们。 This is the idea of having multiple classes that all interact with the main class to assist in writing code.这是让多个类都与主要的 class 交互以帮助编写代码的想法。 I won't talk about that here, but if you are interested in making your code neat and "clean", I highly advise looking that up.我不会在这里谈论这个,但如果你有兴趣让你的代码整洁和“干净”,我强烈建议你去看看。 Also, there is a great book on the subject called Clean Code by Robert C.此外,还有一本由 Robert C 撰写的名为 Clean Code 的好书。 Martin .马丁 I will simply be showing how you can take advantage of methods to shorten your code and clean it up.我将简单地展示如何利用方法来缩短和清理代码。 One of the things you repeat the most is this您重复最多的一件事是

if (countX == 3) {
        return "X wins";
}
if (countO == 3) {
    return "O wins";
}

Your countX and countO are different each time, so you rewrote it.你的 countX 和 countO 每次都不一样,所以你重写了。 I simpler and more efficient way to do this is to use a method.我更简单、更有效的方法是使用方法。 I would advise you to research the syntax for Java in you don't know how to make methods or classes, but you do use the syntax for the determineWinner() method so I will assume you understand it.我建议您研究 Java 的语法,因为您不知道如何创建方法或类,但您确实使用了 determineWinner() 方法的语法,所以我假设您理解它。 You can make functions have parameters that are essentially inputs that can be accessed and modified throughout the function.您可以使函数具有本质上是可以在整个 function 中访问和修改的输入的参数。 (By the way, you cannot make methods inside methods in Java so you would need to place this next method outside somewhere else in the class.) (顺便说一句,您不能在 Java 中的方法内部创建方法,因此您需要将下一个方法放在 class 的其他地方之外。)

public String checkCounts() {
    if (countX == 3) {
        return "X wins";
    }
    if (countO == 3) {
        return "O wins";
    }
    else return "N/A";
}

*You want to check to see if it returns "N/A" anytime you use the method with an if statement. *您想在使用带有 if 语句的方法时检查它是否返回“N/A”。 If so, you should just ignore it since no one won.如果是这样,你应该忽略它,因为没有人赢。

whoWon = checkCounts();
//In the code I put at the bottom I will make whoWon a global variable, which is why I'm not defining it here.
//It will be already defined at the top of the code.
if (!whoWon.equals("N/A")) return whoWon;

*The, symbol means not. *符号表示不是。 ak,a if whoWon does NOT equal "N/A". ak,a 如果 whoWon 不等于“N/A”。 return whoWon.返回谁赢了。

This way, anytime you need to write out that if statement code, you can just write checkCounts and plug in the two variables that you just got from your Array.这样,当您需要写出 if 语句代码时,您只需编写 checkCounts 并插入您刚刚从数组中获得的两个变量。 You would write checkCounts();你会写 checkCounts(); in this case.在这种情况下。 Now if you just say return checkCounts();现在,如果您只是说 return checkCounts(); then the code will run all those if statements without you having to type them all and return the result.然后代码将运行所有这些 if 语句,而无需您全部键入并返回结果。 You actually repeat something else a lot too.您实际上也重复了很多其他事情。 These couple of lines这几行

String value = this.field[x][y];
if (value.equals("X")) {
    countX++;
}
if (value.equals("O")) {
    countO++;
}

are quite similar to these lines与这些行非常相似

String value = this.field[i][i];
if (value.equals("X")) {
    countX++;
}
if (value.equals("O")) {
    countO++;
}

and these lines和这些线

String value = this.field[i][2-i];
 if (value.equals("X")) {
     countX++;
 }
 if (value.equals("O")) {
     countO++;
 }

so you can condense them all down into one method with three different inputs.因此,您可以将它们全部浓缩为一种具有三种不同输入的方法。 The method will return either 0, 1, or 2. The goal is to check which one it returns with the given string input and then translate that to which variable to add 1 to.该方法将返回 0、1 或 2。目标是检查它使用给定的字符串输入返回哪个,然后将其转换为要添加 1 的变量。

If it's 0, ignore, if it's 1, countX++, and if it's 2, countY++.如果是0,忽略,如果是1,countX++,如果是2,countY++。

public int checkString(String value) {
    int whichCount = 0;
    //if whichCount is 1, it means X
    //if whichCount is 2, it means O
    if (value.equals("X")) {
        whichCount = 1;
    }
    if (value.equals("O")) {
        whichCount = 2;
    }
    return whichCount;
}

Switch statements might be a little advanced, but they're pretty simple in concept. Switch 语句可能有点高级,但它们在概念上非常简单。 It's a bunch of if statements all at once in a very convenient syntax.它是一堆 if 语句,语法非常方便。 The value inside the parenthesis is your input, or what to check.括号内的值是您的输入,或要检查的内容。 The cases say, when its equal to this, do this.案例说,当它等于这个时,这样做。 When you needed to increment either countX or countY inside your for loops, you would write当您需要在 for 循环中增加 countX 或 countY 时,您可以编写

switch (checkString(this.field[coord1][coord2])) {
    case 1 -> countX++;
    case 2 -> countO++;
}

case 1 says, if addToCount() returns 1 then do the thing to the right of the arrow and case 2 says if it returns 2 to the thing to the right of that arrow.案例 1 表示,如果 addToCount() 返回 1,则执行箭头右侧的操作,案例 2 表示如果它返回 2 到该箭头右侧的操作。 In your for loops, coord1 and coord2 could be anything from [x][y] to [i][i] to [i][2-i] so you can change that anytime you make the switch statement.在您的 for 循环中,coord1 和 coord2 可以是从 [x][y] 到 [i][i] 到 [i][2-i] 的任何内容,因此您可以在任何时候进行 switch 语句进行更改。

Additionally, you can turn that switch statement itself into a method.此外,您可以将该 switch 语句本身转换为方法。

public void adjustCounts(String stringFromArray) {
    switch (checkString(stringFromArray)) {
        case 1 -> countX++;
        case 2 -> countO++;
    }
}

You can also take a couple of lines off by shorting your if statements.您还可以通过缩短 if 语句来减少几行。 If the thing inside the if statement is only one line long than you can just put in next to it.如果 if 语句中的内容只有一行长,那么您可以将其放在旁边。

if (bool) {
   doSomething();
}
//Change that to this
if (bool) doSomething();

Another thing you repeat a lot is this你经常重复的另一件事是

countX = 0;
countO = 0;

I just made a very simple method that does that with no parameters.我只是做了一个非常简单的方法,它没有参数。

public void resetCounts() {
    countX = 0;
    countO = 0;
}

That's pretty much it for repetition, but I would argue your determineWinner method is still far too large.这几乎是重复的内容,但我认为您的 determineWinner 方法仍然太大。 Even if you don't repeat any more code, taking large changes of it and separating it into smaller bites can make it easier to read and understand.即使您不再重复任何代码,对其进行大量更改并将其分成更小的部分也可以使其更易于阅读和理解。

I added in a bunch of methods that just contained your for loops.我添加了一堆只包含你的 for 循环的方法。 They will be at the very bottom of this final class I came up with.它们将位于我想出的最终 class 的最底部。 It's 85 lines long so it's technically only a 4 line improvement but it's a lot cleaner.它有 85 行长,所以从技术上讲,它只改进了 4 行,但它更简洁。 Additionally, if you were to embed this in your actual class, and not just in a single method (because you can't put it all in one method) then it would be even more efficient because you would have access to all of the classes global variables.此外,如果您要将它嵌入到您的实际 class 中,而不仅仅是在一个方法中(因为您不能将其全部放在一个方法中),那么它会更加高效,因为您可以访问所有类全局变量。 Here is the code I came up with, but I would highly recommend doing extra research on object-oriented programming to really improve your code.这是我想出的代码,但我强烈建议您对面向对象编程进行额外研究,以真正改进您的代码。

public class TicTacToe {
    String[][] field = new String[3][3];
    int countX, countO = 0; // amount of X's and O's in a row
    String whoWon = "N/A";

    public int getNumberOfMoves() {return 0;} //Whatever you method did that determined this. Obviously it didn't really just return 0.
    public String determineWinner() {
        String columns = checkColumnsForWinner();
        String rows = checkRowsForWinner();
        String diagonal1 = checkDiagonal(1, 0);
        String diagonal2 = checkDiagonal(-1, 2);

        if (checkForNA(columns)) return columns;
        if (checkForNA(rows)) return rows;
        if (checkForNA(diagonal1)) return diagonal1;
        if (checkForNA(diagonal2)) return diagonal2;
        if (this.getNumberOfMoves() == 9) return "draw"; // if the number of moves equals 9, the game is over and it is a draw
        return "game not finished";
    }
    public String checkCounts(int countX, int countO) {
        if (countX == 3) return "X wins";
        if (countO == 3) return "O wins";
        else return "N/A";
    }
    public int checkString(String value) {
        int whichCount = 0;
        //if whichCount is 1, it means X
        //if whichCount is 2, it means O
        if (value.equals("X")) whichCount = 1;
        if (value.equals("O")) whichCount = 2;
        return whichCount;
    }
    public void adjustCounts(String stringFromArray) {
        switch (checkString(stringFromArray)) {
            case 1 -> countX++;
            case 2 -> countO++;
        }
    }
    public void resetCounts() {
        countX = 0;
        countO = 0;
    }
    public String checkRowsForWinner() {
        for (int y = 0; y <= 2; y++) { // for all horizontal rows
            resetCounts();
            for (int x = 0; x <= 2; x++) { // loop through all x-coordinates
                adjustCounts(field[x][y]);
            }
            whoWon = checkCounts(countX, countO);
            if (!whoWon.equals("N/A")) return whoWon;
        }
        return "N/A";
    }
    public String checkColumnsForWinner() {
        for (int x = 0; x <= 2; x++) {
            resetCounts();
            for (int y = 0; y <= 2; y++) {
                adjustCounts(field[x][y]);
            }
            whoWon = checkCounts(countX, countO);
            if (!whoWon.equals("N/A")) return whoWon;
        }
        return "N/A";
    }
    public String checkDiagonal(int mutiply, int add) {
        resetCounts();
        for (int i = 0; i <= 2; i++) {
            adjustCounts(field[i][i*mutiply + add]);
        }
        whoWon = checkCounts(countX, countO);
        if (!whoWon.equals("N/A")) return whoWon;
        return "N/A";
    }
    public boolean checkForNA(String string) {return !string.equals("N/A");}
}

In regards to Object-Oriented Programming, the best example I could see you put into practice in this example is Abstraction.关于面向对象的编程,我能看到你在这个例子中实践的最好的例子是抽象。 This is a very general concept but I think it would help a lot in this case.这是一个非常笼统的概念,但我认为在这种情况下会有很大帮助。 In my program above, I have a TicTacToe class, and all of my code in it.在我上面的程序中,我有一个井字游戏 class,以及我所有的代码。 The problem is, you are seeing a lot of boilerplate to get the code to run.问题是,您会看到很多样板文件来运行代码。 The biggest example is the 2D Array object you have.最大的例子是您拥有的二维阵列 object。 You have to do so many things to get X's or O's out of it.您必须做很多事情才能从中获得 X 或 O。 It would be much better (opinion) to make a new class, maybe called Board.制作一个新的 class 会更好(意见),可能称为 Board。 It would contain a private 2D Array object, and public methods to get values from that object.它将包含一个私有二维数组 object,以及从该 object 获取值的公共方法。 Additionally, (this is really just my opinion) I would recommend using an enumeration instead of Strings for you Array values.此外,(这实际上只是我的观点)我建议您使用枚举而不是字符串作为数组值。 For example例如

public enum BoardValues {
    X,
    O,
    EMPTY
}

You could then create a class to place these board values in essentially a 3x3 Grid.然后,您可以创建一个 class 以将这些板值放置在一个 3x3 网格中。

public class Board {
    private BoardValues[][] values = new BoardValues[3][3];

    public BoardValues getValue(int x, int y) {
        return values[x][y];
    }

    public BoardValues[] getRow(int rowNumber) {
        BoardValues[] rowValues = new BoardValues[3];
        for (int i = 0; i < values.length; i++) {
            rowValues[i] = getValue(i, rowNumber);
        }
        return rowValues;
    }

    public BoardValues[] getColumn(int columnNumber) {
        BoardValues[] columnValues = new BoardValues[3];
        for (int i = 0; i < values.length; i++) {
            columnValues[i] = getValue(columnNumber, i);
        }
        return columnValues;
    }

    public void setValues(BoardValues[][] values) {
        this.values = values;
    }

    public void setValue(int x, int y, BoardValues value) {
        values[x][y] = value;
    }
}

Now instead of using that pesky old 2D Array you just create a board object and set and get it's values at will when needed.现在不再使用那个讨厌的旧二维数组,您只需创建一个板 object 并在需要时随意设置和获取它的值。 Also, I didn't add in getting diagonals but you still could quite easily, mine's just for proof of concept.此外,我没有添加对角线,但你仍然可以很容易地,我的只是为了证明概念。 This is Abstraction, probably the easiest of the OOP concepts to grasp, because it's so general.这是抽象,可能是 OOP 概念中最容易掌握的,因为它太笼统了。 I am simply obscuring information you don't need to see when you're trying to code your game.我只是在掩盖您在尝试编写游戏代码时不需要看到的信息。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM