Проверить за победу на доске


Этот код является частью нолики игра крестики. Он использует 2Д массив для Совета.

Функционал следующий код, чтобы проверить за победу на доске, в строках, Столбцах и диагоналях. Как я могу улучшить его?

public bool WhoWon(Cell cell) 
{
    for (int i = 0; i < 3; i++) 
    {
        for (int j = 0; j < 3; j++) 
        {
            if (_board[i, 0] == cell && _board[i, 1] == cell && _board[i, 2] == cell) 
            {
                return true;
            } 
            else if (_board[0, j] == cell && _board[1, j] == cell && _board[2, j] == cell) 
            {
                return true;
            }
        }
    }

    if (_board[0, 0] == cell && _board[1,1] == cell && _board[2,2] == cell) 
    {
        return true;
    }
    else if (_board[0, 2] == cell && _board[1, 1] == cell && _board[2, 0] == cell) 
    {
        return true;
    }
    return false;
}


974
3
задан 21 февраля 2018 в 11:02 Источник Поделиться
Комментарии
3 ответа


public bool WhoWon(Cell cell) 

ИмяWhoWon говорит о том, что она возвращает игрока, который выиграл, но вместо этого он возвращает boolean. Лучшее название будет HasWon.


Когда цикл более 2-мерный массив, его хорошо использовать x и y для цикла переменные, вместо i и j, потому что они более интуитивны и координаты.


Петли являются устаревшими, и сделать то же самое проверяет несколько раз. Потому что ваш совет не просто 3х3 большой, я предлагаю просто сделать 8 проверок, что будет только 8 более читабельных строк. Если вы хотите использовать для-петли для строк и столбцов, вам нужна только одна для каждого, поэтому два последовательных петель, не вложенные. По диагонали, как ты правильно, нет никаких причин, чтобы использовать цикл.

Я добавил предложения в конце ответа о том, как правильно выполнять петли, когда размер платы не известна или может изменяться.


При проверке диагоналей, ваши условия не являются взаимоисключающими, поэтому нет необходимости else if. Также, вы возвращаете true в обоих случаях, так что можно совместить оба условия в одно выражение, и возвращает результат выражения.

return _board[0, 0] == cell && _board[1,1] == cell && _board[2,2] == cell
|| _board[0, 2] == cell && _board[1, 1] == cell && _board[2, 0] == cell;

Вы можете улучшить его (с точки зрения производительности) и снижения redundace первой проверкой средней ячейке, потому что она должна быть для обоих диагоналей. Однако, прирост производительности незначителен, и читаемость, возможно, снижается. Если вы делаете это таким образом, я рекомендую положить его в другой частный способ, где имя метода говорит вам, что она делает (электронная. г. CheckDiagonals).

return _board[1,1] == cell
&& (_board[0, 0] == cell && _board[2,2] == cell
|| _board[0, 2] == cell && _board[2, 0] == cell);


Вы говорите, что вы хотите адаптировать код для различных размеров платы. В этом случае нужен цикл, который идет по диагонали (я сначала предположила, что ты зацикливание над диагоналями в целом, я. е. для каждой диагонали этого).

Подумайте о том, что определяет диагонали, а не с точки зрения геометрии, но и координаты. Обе координаты такие же (или они имеют одинаковое абсолютное значение, в зависимости от направления). Поэтому вам нужно только одну петлю, и использовать его для обоих X и y координат. Тогда вы

for (int i = 0; i < width; i++)
{
// Use i as your x and y coordinate. (Might as well use i directly, this is for illustrative purposes.)
int x = i;
int y = i;

// We try to "disprove" that "cell" has won.
// If we couldn't disprove it until the end of the row, "cell" has won.
if (_board[x, y] != cell) { break; }
if (y == width - 1) { return true; }
}

При этом предполагается, что ширина и высота одинаковые, в противном случае она становится более сложной. Кроме того, вы должны использовать int x = i; int y = -i; когда вы петли над другой диагонали, так как направление изменения.

Делаешь то же самое для строк и столбцов, когда ширина и высота могут меняться, вы можете сделать следующее:

for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
if (_board[x, y] != cell) { continue; }
if (y == height - 1) { return true; }
}
}

5
ответ дан 21 февраля 2018 в 12:02 Источник Поделиться

Что вы сделали здесь перечислены все возможные проверки, но вы собрали ее в один способ. В идеале, вы должны разделиться на отдельные задачи, так как есть три категории подобных проверок:


  • Горизонтальные проверки

  • Вертикальные проверки

  • Проверка диагонали

Может быть, нынешний пример-это просто не достаточно, чтобы полностью оправдать его, но особенно, если у вас было много разных условия победы (например, еще одно правило, что государства, 2х2 квадрат тоже считается за победу), вы поймете, почему вы должны разорвать ваши проверки на более мелкие (связные) части.


Метод


public bool WhoWon(Cell cell) 

Я согласен с других ответов здесь. Название метода не соответствует возвращаемому типу. WhoWon должен вернуть игроку. HasWon должен возвращать логическое значение.

Я тоже не фанат "ячейку" имя. Глядя на код, клетка, кажется, содержит Письмо игрока (X или O). Что не сразу бросается в глаза, называя клетки, клетка будет поле на доске.

Е. Г. board[1,1] это клетка, так как он представляет собой поле на доске. Но я бы назвал значение board[1,1] это маркер, т. е. символ, который находится внутри (если оно не пустое).

Ты не поделился как Cell определяется. Это уже может быть близко к тому, что я бы предпочел использовать. Исходя из этого, позвольте мне предложить улучшение:

public enum Token { X, O, Empty }

public Token[,] _board = new Token[3,3];

public bool HasWon(Token token)
{
// ...
}

Обратите внимание, что вы могли бы также реализовать WhoWon() на основе HasWon():

public Token WhoWon()
{
if(HasWon(Token.X))
return Token.X;

if(HasWon(Token.O))
return Token.O;

return Token.Empty;
}


Тело метода

Я предлагаю разделить это, так как это значительно упрощает логику проверки и сделать его проще для разбора.

public enum Token { X, O, Empty }
public Token[,] _board = new Token[3,3];

public bool HasWon(Token token)
{
return
IsHorizontalVictory(token)
|| IsVerticalVictory(token)
|| IsDiagonalVictory(token);

}

private bool IsHorizontalVictory(Token token)
{
for(int y = 0; y <= 2; y++)
{
if(_board[0,y] == token && _board[1,y] == token && _board[2,y] == token)
return true;
}

//No victory condition hit = no victory.
return false;
}

private bool IsVerticalVictory(Token token)
{
for(int x = 0; x <= 2; x++)
{
if(_board[x,0] == token && _board[x,1] == token && _board[x,2] == token)
return true;
}

//No victory condition hit = no victory.
return false;
}

private bool IsDiagonalVictory(Token token)
{
if(_board[0,0] == token && _board[1,1] == token && _board[2,2] == token)
return true;

if(_board[0,2] == token && _board[1,1] == token && _board[2,0] == token)
return true;

//No victory condition hit = no victory.
return false;
}

Комментарии:


  • Можно дальше аннотация это, например, путем измерения переменной платы. Однако, крестики-нолики всегда играют на поле 3x3, так что это кажется ненужным в данный момент.

  • Вместо того, чтобы использовать многомерный массив, можно создать полноценное представительство ОО совета с его клетки. Это будет стоить некоторых усилий, чтобы положить вместе, но это будет сделать операций (например, проверка на победу) приятнее читать. Индекс на основе извлечения приятно, когда простой, но может стать чудовищно сложны, чтобы следовать, если логика становится слишком большой.

  • Некоторые вещи можно было бы переписать, например IsDiagonalVictory() способ может слили две проверки (+ в false возвращаемое значение) в один логический: return (logic_for_diag_1) || (logic_for_diag_2). Однако, это бы storngly сказалось на читабельности кода. Как разница в производительности ничтожна, поэтому я выбрал наиболее четкий вариант.


Это был headscratcher


for (int i = 0; i < 3; i++)  //A
{
for (int j = 0; j < 3; j++) //B
{
//A
if (_board[i, 0] == cell && _board[i, 1] == cell && _board[i, 2] == cell)
{
return true;
}

//B
else if (_board[0, j] == cell && _board[1, j] == cell && _board[2, j] == cell)
{
return true;
}
}
}


Мне потребовалось некоторое время, чтобы понять, что твои намерения здесь. Вы объединили две совершенно отдельные циклы for, и по какой-то причине решили intwine их.

Я пометил их A и B в коде, потому что эти две совершенно отдельные части логики. Разделив их:

for (int i = 0; i < 3; i++)  //A
{
if (_board[i, 0] == cell && _board[i, 1] == cell && _board[i, 2] == cell)
{
return true;
}
}

for (int j = 0; j < 3; j++) //B
{
if (_board[0, j] == cell && _board[1, j] == cell && _board[2, j] == cell)
{
return true;
}
}

Вам может быть интересно, разве это важно?

Да, это делает. В первую очередь, разделив их резко повышает читабельность кода.

Во-вторых, ты собираешься в конечном итоге делает меньше циклов.


  • Вложенных циклов являются мультипликативными. 3 петли за раз 3 петли для Б = 9 петель. Кроме того, каждый из 9 петель делает две проверки, в результате 18 проверок происходит.

  • Отдельно для-петли являются аддитивными. 3 петли для плюс 3 петли для B = 6 петель. Каждый петли делает только одну проверку, в результате 6 проверок происходит.

Ваши вложенные циклы были выполнять каждый уникальный проверяем трижды (18 / 6 = 3), который не служит никакой функционального назначения.

Давайте рассмотрим только вертикальную проверку:


if (_board[i, 0] == cell && _board[i, 1] == cell && _board[i, 2] == cell)

Это выполняется для каждой пары из i,jпока вы только нужно проверить каждое значение из i.

Это сравнение пар (i,j) и то, что вертикальная проверить действительно:

(0,0)   =>   Checks vertical column 0
(0,1) => Checks vertical column 1
(0,2) => Checks vertical column 2
(1,0) => Checks vertical column 0
(1,1) => Checks vertical column 1
(1,2) => Checks vertical column 2
(2,0) => Checks vertical column 0
(2,1) => Checks vertical column 1
(2,2) => Checks vertical column 2

Обратите внимание, как он повторяет одно и то же для каждого значения j (0,1,2), хотя ценность j не влияет на исход результата.

Это сравнение значений i и то, что вертикальная проверить действительно:

(0)   =>   Checks vertical column 0
(1) => Checks vertical column 1
(2) => Checks vertical column 2

Не соблюдая значения jмы еще не повторили такую же проверку для каждого отдельного значения j.

13
ответ дан 21 февраля 2018 в 01:02 Источник Поделиться

Был предыдущий вопрос, на который я ответил в отношении крестики-нолики, что мне лень искать, но это достаточно распространенная тема (как вы видимо в курсе, там даже тег для него), что вы, возможно, захотите взглянуть на другие вопросы и посмотреть, что относится к вашей программе.

Принимая широкий взгляд, чем другие ответы на ваш вопрос, почему вы даже проверял на победу в первую очередь? Если ваша программа запущена эта функция, после каждого хода, это ужасно расточительно. Все, что вам нужно сделать, это проверить, является ли новое движение создает победу. Вы только должны проверить линии новый шаг, и вы только должны проверить два места для каждой линии (вы знаете, что где бы игрок просто перенесли свой символ в нем по определению).

Кроме того, в любое время у вас есть if X then return ...вам не нужно else; если первый if удовлетворены вы не достигнете остальным все равно.

2
ответ дан 21 февраля 2018 в 11:02 Источник Поделиться