Как избежать повторения при определении победителя в игре Ti c Ta c Toe? - PullRequest
0 голосов
/ 09 мая 2020

Примечание: я новичок в Java (2–3 месяца опыта).

Реализую проект на JetBrains / Hyperskill по созданию игры Ti c Ta c Toe, я обнаружил, что повторяю довольно много кода, пытаясь определить победителя в игре. Чтобы представить игру в виде системы координат (1,1 - внизу слева, а 3,3 - вверху справа), я использую двумерный массив. Это функция для определения победителя:

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";
}

В настоящее время код позволяет вам установить стартовую доску (начальное расположение для всех O и X), а затем позволяет сделать 1 ход. После этого игра решает, кто победит, или это будет ничья и т. Д. не могу придумать никаких способов сократить его.

Есть ли у кого-нибудь советы? Или какие-либо рекомендации, применимые ко всему коду?

1 Ответ

3 голосов
/ 11 мая 2020

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: извините, если мой ответ стал неаккуратным к концу. Кроме того, внизу у меня есть код, показывающий все, о чем я говорил, в действии.

Я думаю, что самое простое, что я могу сказать, - это использовать больше методов и, возможно, классов. Во-первых, один из способов избежать повторения во всех ваших кодах - написать их с помощью объектно-ориентированного программирования. Это идея наличия нескольких классов, которые все взаимодействуют с основным классом, чтобы помочь в написании кода. Я не буду говорить об этом здесь, но если вы заинтересованы в том, чтобы ваш код был аккуратным и «чистым», я настоятельно рекомендую поискать это. Кроме того, есть отличная книга по этой теме под названием Чистый код Роберта C. Мартин . Я просто покажу, как можно воспользоваться преимуществами методов для сокращения кода и его очистки. Одна из вещей, которые вы повторяете чаще всего, - это

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

Ваши countX и countO каждый раз разные, поэтому вы переписали их. Более простой и эффективный способ сделать это - использовать метод. Я бы посоветовал вам изучить синтаксис для Java, если вы не знаете, как создавать методы или классы, но вы используете синтаксис для метода defineWinner (), поэтому я предполагаю, что вы его понимаете. Вы можете создать у функций параметры, которые, по сути, являются входными данными, к которым можно получить доступ и изменить их во всей функции. (Между прочим, вы не можете создавать методы внутри методов в Java, поэтому вам нужно будет разместить этот следующий метод вне где-то еще в классе.)

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

* Вы хотите проверить, не возвращает «N / A» каждый раз, когда вы используете метод с оператором if. Если это так, просто проигнорируйте его, так как никто не выиграл.

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! символ означает «нет», иначе, если whoWon НЕ равен «N / A», вернуть whoWon.

Таким образом, в любое время, когда вам нужно написать, что код оператора if, вы можете просто написать checkCounts и вставить две переменные что вы только что получили из своего массива. Вы бы написали checkCounts (); в таком случае. Теперь, если вы просто скажете return checkCounts (); тогда код будет запускать все эти операторы if без необходимости вводить их все и возвращать результат. На самом деле вы тоже часто повторяете что-то еще. Эти пары строк

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

очень похожи на эти строки

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

и эти строки

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

, поэтому вы можете объединить их все в один метод. с тремя разными входами. Метод вернет 0, 1 или 2. Цель состоит в том, чтобы проверить, какой из них он возвращает с заданным строковым вводом, а затем преобразовать это значение в переменную, к которой нужно добавить 1.

Если это 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 могут быть немного продвинутыми, но они довольно просты по концепции. Это сразу несколько операторов if с очень удобным синтаксисом. Значение внутри скобок - это ваш ввод или то, что нужно проверить. Случаи говорят, что когда это равно этому, делайте это. Когда вам нужно увеличить значение countX или countY внутри циклов for, вы должны написать

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

case 1 говорит, что если addToCount () возвращает 1, то сделайте то, что находится справа от стрелки, а case 2 говорит если он вернет 2 предмету справа от этой стрелки. В ваших циклах for значения corre1 и corre2 могут быть любыми от [x] [y] до [i] [i] до [i] [2-i], так что вы можете изменить это в любое время, когда вы делаете оператор switch.

Кроме того, вы можете превратить сам оператор switch в метод.

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

Вы также можете убрать пару строк, сократив операторы if. Если внутри оператора if есть только одна строка, которую вы можете просто вставить рядом с ней.

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

Еще одна вещь, которую вы часто повторяете, - это

countX = 0;
countO = 0;

Я просто сделал очень простой метод, который делает это без параметров.

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

Это в значительной степени для повторения, но я бы сказал, что ваш метод defineWinner все еще слишком велик. Даже если вы больше не повторяете код, внесение в него больших изменений и разделение на более мелкие части может упростить чтение и понимание.

Я добавил кучу методов, которые просто содержали ваши циклы for . Они будут в самом низу этого заключительного класса, который я придумал. Это 85 строк, так что технически это улучшение всего на 4 строки, но оно намного чище. Кроме того, если бы вы встроили это в свой фактический класс, а не только в один метод (потому что вы не можете поместить все в один метод), это было бы еще более эффективно, потому что у вас был бы доступ ко всем классам. глобальные переменные. Вот код, который я придумал, но я настоятельно рекомендую провести дополнительные исследования по объектно-ориентированному программированию, чтобы действительно улучшить ваш код.

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");}
}

Что касается объектно-ориентированного программирования , лучший пример, который вы можете применить на практике в этом примере, - это абстракция. Это очень общая концепция, но я думаю, что в этом случае она очень поможет. В моей программе выше у меня есть класс TicTacToe и весь мой код в нем. Проблема в том, что вы видите много шаблонов для запуска кода. Самый большой пример - это имеющийся у вас объект 2D-массив. Вы должны сделать так много вещей, чтобы получить от этого крестики или минусы. Было бы намного лучше (мнение) сделать новый класс, может быть, под названием Board. Он будет содержать частный объект 2D-массива и методы publi c для получения значений из этого объекта. Вдобавок (это действительно только мое мнение) я бы рекомендовал использовать перечисление вместо строк для значений массива. Например,

public enum BoardValues {
    X,
    O,
    EMPTY
}

Затем вы можете создать класс для размещения этих значений платы по существу в сетке 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;
    }
}

Теперь вместо использования этого надоедливого старого 2D-массива вы просто создаете board и при необходимости установите и получите его значения. Кроме того, я не добавил диагоналей, но вы все равно можете легко это сделать, мой просто для подтверждения концепции. Это абстракция, вероятно, самая простая из концепций OOP для gr asp, потому что она настолько общая. Я просто скрываю информацию, которую вам не нужно видеть, когда вы пытаетесь кодировать свою игру.

...