Первая консольная игра: Tic-Tac-Toe

Я только что закончил свою первую настоящую консольную игру, используя C ++. Мне бы очень хотелось, если бы я мог получить некоторые отзывы о моей программе, у меня есть несколько конкретных комментариев в моем коде, на которые у меня есть вопросы, и перечислю еще несколько, на которые я столкнулся при создании этой игры.

  1. Есть ли у main.cpp достаточная абстракция данных /любые комментарии к моей абстракции, где она может использовать улучшение?
  2. Как новый программист, я, скорее всего, не обратил внимания на некоторые вещи, которые могут быть общими конструктивными недостатками, поэтому любые общие критические замечания, которые вы могли бы мне дать, которые улучшили бы мою программу, были бы высоко оценены, а также личные предпочтения с объяснением того, улучшение было бы очень оценено.
  3. В моем файле Board.cpp я оставил комментарий о том, как структурировать мои условия для тестирования выигрышных комбинаций.
  4. В моем файле Board.hpp я оставил комментарий о попытке использовать вектор из STL, однако, когда я пытаюсь реализовать вектор вместо массива, я получаю сообщение об ошибке. Я скопировал свой код для вектора в файл main.cpp, и он работал нормально, поэтому я не уверен, что я упускаю из виду на этом.
  5. Если вы могли бы прокомментировать структуру моего файла Board.hpp, в частности, нужна организация моих функций-членов? Где он мог использовать работу? Я также хотел бы знать, должен ли я создать отдельный класс и файл игрока? Также любые комментарии о том, почему или почему не добавлять дополнительный класс.

main.cpp

//
//  Tic-Tac-Toe
//

#include "Board.hpp"
#include <iostream>
#include <string>

using namespace std;

int main() {
    bool newgame{true};
    while(newgame == true) {
        Board board;
        board.reset_winner();
        string username,playAgain;

        //Select player gamepieces.
        cout << "Enter player X username: ";
        cin >> username;
        board.set_playerX(username);
        cout << "Enter player O username: ";
        cin >> username;
        board.set_playerO(username);

        cout << "\n\nPlayer X: " << board.get_playerX() << "     " << "Player O: " << board.get_playerO() << endl;
        cout << "\nPlayers select a game space 1 - 9 to select a play." << endl;
        board.displayGrid();

        //Gameplay
        while (board.get_isWinner() == false) {
            int selection{0};

            //Player X's move.
            board.set_validSelection(false);
            while (board.get_validSelection() == false) {
                cout << board.get_playerX() << "'s move:";
                cin >> selection;
                board.selection_playerX(selection);
            }
            board.display();
            board.winOrTie('X');

            //Player O's move.
            if (board.get_isWinner() == false) {
                board.set_validSelection(false);
                while (board.get_validSelection() == false) {
                    cout << board.get_playerO() << "'s move:";
                    cin >> selection;
                    board.selection_playerO(selection);
                }
                board.display();
                board.winOrTie('O');
            }
        }
        //Announce winner.
        if (board.get_winner() == 'X') {
            cout << board.get_playerX() << " is the winner!" << endl;
        }
        else if (board.get_winner() == 'O') {
            cout << board.get_playerO() << " is the winner!" << endl;
        }
        else if (board.get_winner() == 'T') {
            cout << "Game ends in a tie." << endl;
        }
        //Play again?
        cout << "Would you like to play again?" << "\nType: \"Y\" or \"N\"" << endl;
        cin >> playAgain;
        if (cin.fail()) {
            cin.clear();
            cout << "Please type \"Y\" or \"N\":" << endl;
            cin >> playAgain;
        }
        else if (cin.good()) {
            if (playAgain == "Y" || playAgain == "y") {
                newgame = true;
            }
            else if (playAgain == "N" || playAgain == "n") {
                newgame = false;
            }
            else {
                newgame = false;
            }
        }
    }

    return(0);
}

Board.hpp

//
//  Board.hpp
//  tictactoe
//

#ifndef Board_hpp
#define Board_hpp

#include <stdio.h>
#include <string>
#include <vector>

#endif /* Board_hpp */

class Board {
private:
    std::string playerX;
    std::string playerO;
    char game_space[10]{'\0',' ',' ',' ',' ',' ',' ',' ',' ',' '}; //Why wont         std::vector<char> game_space(10,' ') work here? *Note: I did try adding #include   <vector> too.
    bool isWinner{false};
    bool validSelection{true};
    char winner;
protected:
public:
    //Player info
    void set_playerX(std::string username);
    void set_playerO(std::string username);
    std::string get_playerX();
    std::string get_playerO();
    //Board info
    void displayGrid();
    void display();
    void selection_playerX(int);
    void selection_playerO(int);
    //Game info
    bool get_isWinner();
    void set_isWinner(bool);
    bool get_validSelection();
    void set_validSelection(bool);
    void winOrTie(char);
    char get_winner();
    void reset_winner();

};

Board.cpp

//
//  Board.cpp
//  tictactoe
//

#include "Board.hpp"
#include <iostream>

//Player Info
void Board::set_playerX(std::string username) {
    playerX = username;
}
void Board::set_playerO(std::string username) {
    playerO = username;
}
std::string Board::get_playerX() {
    return playerX;
}
std::string Board::get_playerO() {
    return playerO;
}
//Board info
void Board::displayGrid() {
    std::cout << std::endl;
    std::cout << "          +---+---+---+" << std::endl;
    std::cout << "          | 1 | 2 | 3 |" << std::endl;
    std::cout << "          +---+---+---+" << std::endl;
    std::cout << "          | 4 | 5 | 6 |" << std::endl;
    std::cout << "          +---+---+---+" << std::endl;
    std::cout << "          | 7 | 8 | 9 |" << std::endl;
    std::cout << "          +---+---+---+" << std::endl;
    std::cout << std::endl;
}
void Board::display() {
    std::cout << std::endl;
    std::cout << "          +---+---+---+" << std::endl;
    std::cout << "          | " << game_space[1] << " | " << game_space[2] << " | " << game_space[3] << " |" << std::endl;
    std::cout << "          +---+---+---+" << std::endl;
    std::cout << "          | " << game_space[4] << " | " << game_space[5] << " | " << game_space[6] << " |" << std::endl;
    std::cout << "          +---+---+---+" << std::endl;
    std::cout << "          | " << game_space[7] << " | " << game_space[8] << " | " << game_space[9] << " | " << std::endl;
    std::cout << "          +---+---+---+" << std::endl;
    std::cout << std::endl;
}
void Board::selection_playerX(int selection) {
    if (game_space[selection] == ' ') {
        game_space[selection] = 'X';
        validSelection = true;
    }
    else {
        std::cout << "Board position already taken." << std::endl;
        validSelection = false;
    }
}
void Board::selection_playerO(int selection) {
    if (game_space[selection] == ' ') {
        game_space[selection] = 'O';
        validSelection = true;
    }
    else {
        std::cout << "Board position already taken." << std::endl;
        validSelection = false;
    }
}
//Gameplay
bool Board::get_isWinner() {
    return isWinner;
}
void Board::set_isWinner(bool kanyewest) {
    isWinner = kanyewest;
}
bool Board::get_validSelection() {
    return validSelection;
}
void Board::set_validSelection(bool selection) {
    validSelection = selection;
}
void Board::winOrTie(char gamepiece) {
    bool fullBoard{false};
    /* 1 2 3
       4 5 6
       7 8 9 */
    //Vertical Win
    if ((game_space[1] == gamepiece) && (game_space[4] == gamepiece) && (game_space[7] == gamepiece)) {
        //Question: Why wont "if ((game_space[1] && game_space[4] && game_space[7]) == gamepiece)" work as a conditional?
        //Also is there a better way to go about the conditionals presented here for combination testing?
        isWinner = true;
        winner = gamepiece;
}
    else if ((game_space[2] == gamepiece) && (game_space[5] == gamepiece) && (game_space[8] == gamepiece)) {
        isWinner = true;
        winner = gamepiece;
    }
    else if ((game_space[3] == gamepiece) && (game_space[6] == gamepiece) && (game_space[9] == gamepiece)) {
        isWinner = true;
        winner = gamepiece;
    }
    //Horizontal Win
    else if ((game_space[1] == gamepiece) && (game_space[2] == gamepiece) && (game_space[3] == gamepiece)) {
        isWinner = true;
        winner = gamepiece;
    }
    else if ((game_space[4] == gamepiece) && (game_space[5] == gamepiece) && (game_space[6] == gamepiece)) {
        isWinner = true;
        winner = gamepiece;
    }
    else if ((game_space[7] == gamepiece) && (game_space[8] == gamepiece) &&     (game_space[9] == gamepiece)) {
        isWinner = true;
        winner = gamepiece;
    }
    //Diagonal Win
    else if ((game_space[1] == gamepiece) && (game_space[5] == gamepiece) && (game_space[9] == gamepiece)) {
        isWinner = true;
        winner = gamepiece;
    }
    else if ((game_space[3] == gamepiece) && (game_space[5] == gamepiece) && (game_space[7] == gamepiece)) {
        isWinner = true;
        winner = gamepiece;
}
    else {
        //Determine if board is full
        if ((game_space[1] == ' ') || (game_space[2] == ' ') || (game_space[3] == ' ') || (game_space[4] == ' ') || (game_space[5] == ' ') || (game_space[6] == ' ')
        || (game_space[7] == ' ') || (game_space[8] == ' ') || (game_space[9]   == ' ')) {
            fullBoard = false;
        }
        else {fullBoard = true;}
        if (fullBoard == true) {
            isWinner = true;
            winner = 'T';
        }
    }
}
char Board::get_winner() {
    return(winner);
}
void Board::reset_winner() {
    winner = 'T';
}
8 голосов | спросил ChillyPenguin672 18 J0000006Europe/Moscow 2017, 04:08:04

1 ответ


5

Основной обзор

  • Вы вызываете reset_winner() сразу после создания своего совета. Предпочтительным поведением было бы то, что он «хорошо» инициализирован в конструкторе (ну, в вашем случае, технически не в ctor)

  • Вы можете рассмотреть возможность передачи имени игрока в конструктор Board (но это только возможность)

  • У вас есть while (newgame == true), вы можете заменить его на while (newgame) (то же самое для других циклов while). Это сделает ваш код более удобочитаемым.

Вы также можете заменить

        if (playAgain == "Y" || playAgain == "y") {
            newgame = true;
        } else if (playAgain == "N" || playAgain == "n") {
            newgame = false;
        } else {
            newgame = false;
        }

простым

        if (playAgain == "Y" || playAgain == "y") {
            newgame = true;
        } else {
            newgame = false;
        }

или даже, в качестве corvus_192 указывает:

        newgame = playAgain == "Y" || playAgain == "y";

Кроме того, люди обычно предпочитают return 0; вместо return (0);, хотя это ( почти ) только вопрос мнения

Обзор форума

имена

Есть несколько вещей, которые я бы переименовал, например:

  • game_space не ясно, я бы использовал game_board.
  • В соответствии с его именем isWinner должен быть true, если кто-то выиграл, но на самом деле это правда, если игра закончилась (в случае победы ИЛИ в случае галстука). game_over или что-то в этом роде было бы лучше. Вы можете использовать winner для этого: 'X' /'O', если кто-то выиграл, '\0' если никто не выиграл, а 'T' для галстука.
  • imho, set_playerX/O должен быть set_playerX/O_name
  • get_isWinner можно назвать is_game_over. Как правило, для логического x его получатель обычно называют is_x

использовать для циклов

Вы можете использовать для циклов, чтобы сделать вашу жизнь проще и сделать ваш код более читаемым при определении того, выиграл ли кто-то. Предполагая, что вы используете массив 2d (см. Предложения):

// Vertical
for (int i = 0 ; i < 3 ; ++i) {
    if (game_space[i][0] == game_space[i][1] && game_space[i][0] == game_space[i][2]) {
        winner = game_space[i][0];
        isWinner = true;
        return;
    }
}

// Vertical
for (int i = 0 ; i < 3 ; ++i) {
    if (game_space[0][i] == game_space[1][i] && game_space[0][i] == game_space[2][i]) {
        winner = game_space[0][i];
        isWinner = true;
        return;
    }
}

Однако вам все равно придется проверять «вручную» на диагонали.

Тот же комментарий касается проверки полного состава:

// if there was no winner, we'll check for a tie
for (int i = 0 ; i < 3 ; ++i) {
    for (int j = 0 ; j < 3 ; ++j) {
        // if the board isn't full, we jump out of this method
        if (game_space[i][j] == ' ') {
            return;
        }
    }
}

// if there was no winner and the board is full (ie: this is a tie):
isWinner = true;
winner = 'T';

Предложения

Используйте массив 2d

Вы можете использовать 2d std :: array вместо 1d raw-массива для game_space (то есть: std::array<std::<array<char,3>,3> game_space, используя его как char[3][3]), как tic -tac-toe имеет 2d-сетку.

Использовать перечисление

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

enum piece_color : char {
    empty = ' ',
    player_x = 'X',
    player_o = 'O',
    invalid = '\0' // for the 'winner' variable
};

вы можете даже распечатать его сразу. Использование enum (или даже лучше: enum class ) будет вводить в действие тип, содержащийся на доске, так что вы с меньшей вероятностью случайно положите недопустимое значение.

Изменить selection_player

Вы можете изменить поведение selection_playerX /O, чтобы сделать это следующим образом:

  • Возьмите три параметра: положение X, позицию Y [если вы решили использовать сетку 2d] и значение игрока («X» или «O»).
  • Вернуть bool: true, если было возможно поместить пешку, false, если это не было
  • Наконец, переименуйте его в нечто вроде play

Также измените get_playerX /O на get_player_name и заставьте игрока в качестве параметра

Зачем вам это делать? Поскольку это позволяет вам изменить это:

    //Gameplay
    while (board.get_isWinner() == false) {
        int selection{0};

        //Player X's move.
        board.set_validSelection(false);
        while (board.get_validSelection() == false) {
            cout << board.get_playerX() << "'s move:";
            cin >> selection;
            board.selection_playerX(selection);
        }
        board.display();
        board.winOrTie('X');

        //Player O's move.
        if (board.get_isWinner() == false) {
            board.set_validSelection(false);
            while (board.get_validSelection() == false) {
                cout << board.get_playerO() << "'s move:";
                cin >> selection;
                board.selection_playerO(selection);
            }
            board.display();
            board.winOrTie('O');
        }
    }

    //Announce winner.
    if (board.get_winner() == 'X') {
        cout << board.get_playerX() << " is the winner!" << endl;
    } else if (board.get_winner() == 'O') {
        cout << board.get_playerO() << " is the winner!" << endl;
    } else if (board.get_winner() == 'T') {
        cout << "Game ends in a tie." << endl;
    }

в гораздо более понятном виде:

    char player = 'X';
    int selection{0};
    while (!board.get_isWinner()) {
        do {
            cout << board.get_player_name(player) << "'s move: " << endl;
            cin >> selection;
                          //      X             Y
        } while (!board.play(selection % 3, selection / 3, player));
        // next player
        player = (player == 'X' ? 'O' : 'X');

    }

    if (board.is_tie()) {
         cout << "Game ends in a tie." << endl;
    } else {
         cout << board.get_player_name(board.get_winner()) << " is the winner!" << endl;
    }
ответил Maliafo 18 J0000006Europe/Moscow 2017, 13:44:37

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132