Search code examples
c++c++11codeblockscliongame-development

Edit: Fill a column without overwriting what's already inside C++


I'm trying to build a 2 player game in which the program rolls the dice and a player has to choose what column to play in, out of 12 columns.

My problem is when the dice rolled (for example: 5) and Player 1 chooses their column. It works fine until Player 2 chooses the same column and overwrites what player 1 had in that column, instead of playing in the available space above it.

Here's my code:

#include <iostream>
#include <windows.h>
#include <ctime>
#include <iomanip>
#include <algorithm>
#include "TheRebuild.h"

using namespace std;
int k = 0;
auto dice = 0;
int col_in = 0;
int col_space = 0;
int x = 0;
int y = 0;
int counter = 0;
char space = '-';

char board[gridrow][gridcol];

    //accessors
    string Players::getname(Users play[], int k) const {
        return play[k].name;
    }

    char Players::gettoken(Users play[], int k) const {
        return play[k].token;
    }

    int Players::getscore(Users play[], int k) const {
        return play[k].score;
    }

    //mutator
    void Players::RecInfo(Users play[], int k, string id, char piece, int scr) {
        play[k].name = id;
        play[k].token = piece;
        play[k].score = scr;

    }

    Players::Players() {
        for (int k = 0; k < arrsize; k++) {
            play[k].name = "";
            play[k].token = ' ';
            play[k].score = 0;
        }
    }


void Players::askUser(Players::Users *play)
{
    string id = " ";
    char piece = ' ';
    int scr = 0;

    //cin.get();
    for (int k = 0; k <= arrsize; k++) {

        cout << "Player " << k + 1 << "! Enter your Name: ";
        getline(cin, id);

        cout << "Player " << k + 1 << " Enter a character to play with: ";
        cin >> piece;

        cout << endl;

        system("pause");

        RecInfo(play, k, id, piece, scr);
        system("cls");
        cin.get();
    }
}

int Players::DiceRoll()
{
    int dice = 0;
    srand(time(nullptr));
    dice = (rand() % 6) + 1;
    return dice;
}

void Players::Player1() {
    dice = DiceRoll();
    cout << "Dice stopped rolling at: " << dice << endl;
    cout << play[0].name <<" Enter column to play in: ";
    cin >> col_in;

    if ((col_in < 1) && (col_in > 12)) // failsafe - player does not play anything out of 1-12 range
    {
        cin.get();
        cout << "Turn lost!";
        return;
    }
    else
    {
        col_space = col_in-1;
    }

    if (board[x][gridcol] != space)
    {
            x++;
            for (x = gridcol; x > gridcol - (dice +1); x--) // this loop allows player pieces to start at "11" of the board rather than at the "0"
            {
                board[x][col_space] = play[0].token;
                Sleep(50);
            }
    }
    else
    {
        for (x = gridcol; x > gridcol - (dice +1); x--) // this loop allows player pieces to start at "11" of the board rather than at the "0"
        {
            board[x][col_space] = play[0].token;
            Sleep(50);
        }
    }

}

void Players::Player2() {
    dice = DiceRoll();
    cout << "Dice stopped rolling at: " << dice << endl;
    cout << play[1].name <<" Enter column to play in: ";
    cin >> col_in;

    if ((col_in < 1) && (col_in > 12)) // failsafe - player does not play anything out of 1-12 range
    {
        cin.get();
        cout << "Turn lost!";
        return;
    }
    else
    {
        col_space = col_in - 1;
    }

    for (x = gridcol; x > gridcol-(dice +1); x--) // this loop allows player pieces to start at the bottom of the board rather than at the top
    {
        board[x][col_space] = play[1].token;
        Sleep(50);
    }
}

void Players::Header() {
    //---------------------------- Print --------------------------------
    // this prints the board with all the changes made by the player... this is OK
    string border(48, '-');
    system("cls");

    cout << border << endl
         << "|                 Fill 'Em Up                  | \n"
         << border << endl
         << "  1   2   3   4   5   6   7   8   9  10  11  12  \n"
         << border << endl; // header for the board
}

void Players::Sub_UI() {
    //Basic UI elements - Displays Player Name, Their Score (number of tokens on the board), and the token they chose
    // I plan to add colors for player 1 and player 2 tokens, but the gameplay is more important

    string sub(15, '^');
    cout << endl << sub << "\t\t\t " << sub << endl; // top boarder for player information
    cout << "Player: " << play[0].name << "\t\t\t" << " Player: " << play[1].name << endl; // names of players
}

bool Players::endgame() {
    return false;
}

void Players::mainStage(Players::Users *play) {

    for(x = 0; x <gridrow; x++){
        for (y = 0; y < gridcol; y++){
            board[x][y] = space;
        }
    }

    do // loop for the whole game - see "while" at the end
    {
        Header();

        for (x = 0; x < gridrow; x++)  // actual grid being printed x is row, y is col, just like a normal x and y axis
        {
            for (y = 0; y < gridcol; y++) {
                cout << "  " << board[x][y] << " ";
            }
            cout << endl;
        }

        //Token Counter
        int count1 = count(*board, *board+144, play[0].token); // used to count how many tokens on the board belong to Player 1
        int count2 = count(*board, *board+144, play[1].token); // used to count how many tokens on the board belong to Player 2

        //Small UI elements
        Sub_UI();
        cout << "Score: " << count1 << " -> " << play[0].token << "\t\t\t" << " Score: " << count2 << " -> " << play[1].token << endl << endl; // scores and tokens respectively

        //Player1
        if (counter % 2 == 0)
        {
           Player1();
        }
        else //Player2
        {
            Player2();
        }counter++;


    }while(!endgame());
}


Anyone have any ideas on correcting this?

It should look like this once complete

------------------------------------------------
|                 Game Title                   |
------------------------------------------------
  1   2   3   4   5   6   7   8   9  10  11  12
------------------------------------------------
  -   -   -   -   -   -   -   -   -   -   -   -
  -   -   -   -   -   -   -   -   -   -   -   -
  -   -   -   -   -   -   -   -   -   -   -   -
  -   -   -   -   -   -   -   -   -   -   -   -
  -   -   -   -   -   -   -   -   -   -   -   -
  -   -   -   -   -   -   -   -   -   -   -   -
  -   -   -   -   -   -   -   -   -   -   -   -
  -   $   -   -   -   -   -   -   $   -   -   -
  -   $   -   -   -   -   -   -   $   -   -   -
  -   $   -   -   -   -   -   -   $   -   -   -
  -   $   -   -   -   -   -   -   @   -   -   @
  -   $   -   -   -   -   -   -   @   -   -   @

^^^^^^^^^^^^^^^                  ^^^^^^^^^^^^^^^
Player: Player1                  Player: Player2
Score: 4 -> @                    Score: 8 -> $

Dice stopped rolling at: 2
Player1 Enter column to play in:

Note how the Player 2 Entered 3 tokens above Player 1's tokens instead of overwriting them


Solution

  • First, I noticed an error in the code:

    if ((col_in < 1) && (col_in > 12)) 
    

    A number can never be less than 1 and greater than 12. You need to use || there.


    You refactored your code into smaller functions, which is good, but you put all those functions into one giant class. I re-did the code with more classes. Each one has its own job. Here are the classes I used:

    • game_board Manages the board
    • die Simple class to roll a dice (uses newer random funcs)
    • player Keeps player data. Will have 2 objects of this class - 1 for each player
    • game Main class that handles all the game play logic

    Some other notes:

    • Created a helper function to read an int or char from cin. It will make sure users enters correct type and will consume any extra chars - including newline
    • I use a std::vector instead of an array

    Here's my code. Note, this is not meant to be the best or only way to do it. But hopefully it will show the advantages of breaking the code up into smaller pieces.

    #include <iostream>
    #include <iomanip>
    #include <vector>
    #include <string>
    #include <random>
    #include <limits>
    #include <stdexcept>
    #include <sstream>
    
    using std::cout;
    using std::endl;
    using std::string;
    using std::vector;
    
    // Helper function to get user inputs. Keeps trying until the right type is entered
    // Also clears any extra chars
    template<typename T>
    T get_input()
    {
        T i{};
        while (!(std::cin >> i)) {
            std::cin.clear();
            std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
        }
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
        return i;
    }
    
    class column
    {
    protected:
        vector<char> data;
    
    public:
        static const char SPACE_CHAR = '-';
    
        column(int rows) :
            data(rows, SPACE_CHAR)
        {}
    
        bool is_full() const
        {
            return data[0] != SPACE_CHAR;
        }
    
        void add(char token)
        {
            auto result = std::find_if(data.rbegin(), data.rend(), [this](char c) { return c == SPACE_CHAR; });
            if (data.rend() == result) {
                cout << "ERROR";
                return;
            }
            *result = token;
        }
    
        int free_spaces() const
        {
            auto result = std::find_if(data.begin(), data.end(), [this](char c) { return c != SPACE_CHAR; });
            return std::distance(data.begin(), result);
        }
    
        int count_tokens(char token) const
        {
            return std::count(data.begin(), data.end(), token);
        }
    
        int num_rows() const { return data.size(); }
    
        // Needed for std::max_element
        bool operator<(const column& other) const { return data.size() < other.data.size(); }
    
        // Index operator. Does not throw on index out of bounds.
        char operator[](int i) const { return i < data.size() ? data[i] : ' '; }
    };
    
    class game_board
    {
    protected:
        vector<column> board;
    
    public:
        game_board(int rows, int cols)
        {
            for (int c = 0; c < cols; c++) {
                board.push_back(column(rows));
            }
        }
    
        void print() const
        {
            ::system("CLS");
            for (int c = 1; c <= board.size(); c++) {
                cout << std::right << std::setw(3) << c << ' ';
            }
            cout << endl;
    
            static char c1 = 0;
    
            // The game currently supports columns with different sizes
            // For printing, need to know max number of rows
            int rows = (*std::max_element(board.begin(), board.end())).num_rows();
    
            for (int r = 0; r < rows; r++) {
                for (auto c : board) {
                    cout << "  " << c[r] << ' ';
                }
                cout << endl;
            }
        }
    
        bool column_is_full(int col) const
        {
            return board[col].is_full();
        }
    
        // Adds a token to the vector in the column given
        void add_to_col(int col, char token)
        {
            board[col].add(token);
        }
    
        bool is_full()
        {
            auto result = std::find_if(board.begin(), board.end(), [this](column& c) { return !c.is_full(); });
            return result == board.end();
        }
    
        int free_spaces(int col) const
        {
            return board[col].free_spaces();
        }
    
        // Get a complete count of all tokens on the board
        int get_count(char token) const
        {
            int count = 0;
            for (auto c : board) {
                count += c.count_tokens(token);
            }
            return count;
        }
    
        int get_num_rows(int col) const { return board[col].num_rows(); }
        int get_num_cols() const { return board.size(); }
    };
    
    class die {
    protected:
        std::random_device rd;
        std::mt19937 mt;
        std::uniform_real_distribution<double> dist;
    
    public:
        die(int num_sides) :
            mt(rd()), dist(1, num_sides + 1)
        {
        }
    
        int roll() {
            return (int)dist(mt);
        }
    };
    
    class player
    {
    protected:
        string name;
        char token;
    
    public:
        player(int id)
        {
            cout << "Player " << id << "! Enter your Name: ";
            std::getline(std::cin, name);
            cout << "Player " << id << " Enter a character to play with: ";
            token = get_input<char>();
        }
    
        string get_name() const { return name; }
        char get_token() const { return token; }
    };
    
    class game
    {
    protected:
        player player1, player2;
        game_board board;
        die the_die;
    
        // Take one players turn
        void player_turn(player& p)
        {
            int dice = the_die.roll();
            cout << "Dice stopped rolling at: " << dice << endl;
    
            // Keep trying until the player selects a column that is not full
            while (true) {
                cout << p.get_name() << " Enter column to play in: ";
                int col_in = get_input<int>() - 1;
    
                // If the choose a column outside of the board, end turn
                if ((col_in < 0) || (col_in >= board.get_num_cols()))
                {
                    cout << "Turn lost!" << endl;
                    return;
                }
                // Only play if column is not full
                //if (!board.column_is_full(col_in)) {
                if (board.free_spaces(col_in) >= dice) {
                    // Fill in the column with the player's token
                    // Note: this allows to use a column with less than dice number of free spaces
                    //   Ex: column has 3 open spaces, but dice roll is 5. Will fill column to top
                    for (int i = 0; !board.column_is_full(col_in) && i < dice; i++) {
                        board.add_to_col(col_in, p.get_token());
                    }
                    return;
                }
                cout << "Not enough space. Try again..." << endl;
            }
        }
    
        void print_score() const
        {
            const player players[] = { player1, player2 };
            for (int i = 0; i < 2; i++) {
                cout << "Player: " << std::left << std::setw(12) << players[i].get_name();
            }
            cout << endl;
    
            for (int i = 0; i < 2; i++) {
                std::stringstream ss;
                ss << "Score: " << players[i].get_token() << " -> " << board.get_count(players[i].get_token());
                cout << std::left << std::setw(20) << ss.str();
            }
            cout << endl;
        }
    
    public:
        game(int rows, int cols) :
            player1(1), player2(2), board(rows, cols), the_die(6)
        {
        }
    
        void play_game()
        {
            // Keep playing until board is full
            while (!board.is_full()) {
                board.print();
                print_score();
                player_turn(player1);
                if (!board.is_full()) {
                    board.print();
                    print_score();
                    player_turn(player2);
                }
            }
            cout << endl << "Final Score:" << endl;
            print_score();
        }
    };
    
    int main() {
        game g(12, 12);
        g.play_game();
    
        return 0;
    }