Thread: Connect four game - Works and would like input

  1. #1
    Registered User
    Join Date
    Sep 2011
    Posts
    13

    Connect four game - Works and would like input

    This game was originally a Tictactoe game if you couldn't tell already.

    I know that there are things I could add to the game such as computer strategy instead of a random move, or perhaps a menu at the start. I posted this to get some input on what I have so far, so I just used the basics.

    If anyone wants to comment on programming style it would help a lot.

    * EDIT *

    The code is in the post below. When I edited the code it no longer formatted it correctly so I opened a new post to have the proper formatting.

    * EDIT *

    The game can be easily converted to a tictactoe style by changing these constructor values to:

    Code:
    xMax = 3;
    yMax = 3;
    rowSize = 3;
    droppieces = false;
    The board can be resized as seen above. You may play the computer, another human, or watch the computer play the computer. Up to 9 players can play in any human / computer configuration.

    For example, to have player 1 be human, player 2 be a computer, player 3 be human, and player 4 be a computer with player 3 starting:

    Code:
    numberPlayers = 4;      // how many players?
    player = 3;             // who starts?
    
    playerType[1] = 1;      // player # 1 is human
    playerType[2] = 0;      // player # 2 is computer
    playerType[3] = 1;      // player # 3 is human
    playerType[4] = 0;      // player # 4 is computer
    If you would like the computer to move for you press '-1', yes, it's a hack, but I just wanted to make sure it had that functionality for now.
    Last edited by simpleblue; 10-16-2011 at 07:18 PM.

  2. #2
    Registered User
    Join Date
    Sep 2011
    Posts
    13
    Code:
    // main.cpp
    #include <iostream>
    #include "tictactoe.h"
    
    int main()
    {
    
        TicTacToe Game;
    
        Game.welcome();
        Game.drawBoard();
    
        do
        {
    
            Game.printTurn();
    
            if (Game.playerHuman()) // human turn?
                Game.humanMove();
            else
                Game.computerMove();
    
            Game.drawBoard();
            Game.nextTurn();
    
        }
        while (!Game.winner() && !Game.fullBoard());
    
        return 0;
    }
    Code:
    // tictactoe.h
    #ifndef TICTACTOE_H
    #define TICTACTOE_H
    
    class TicTacToe
    {
    private:
        int board[30][30];      // board[x][y]
        bool droppieces;        // connect4 style pieces drop
        int xMax;               // max horizontal board size
        int yMax;               // max vertical board size
        int rowSize;            // need in a row to win
        int player;             // current player
        int totalTurns;         // how many turns so far?
        int maxTurns;           // full board
        int numberPlayers;      // 1 to 20
        bool playerType[9];     // true = human, false = comp
    public:
        TicTacToe();
    
        void welcome();         // welcome screen
    
        void printTurn();       // whos turn?
        bool playerHuman();     // is player human?
    
        void humanMove();       // human controls
        void computerMove();    // computer strategy
    
        void drawBoard();       // display board
    
        bool winner();          // is there a winner?
        bool fullBoard();       // is the board full?
    
        void nextTurn();        // switch turn
        
        void placePieces(int x, int y);     // move peices
        void announceWinner(int winner);    // winner!!
    
    };
    
    #endif // TICTACTOE_H
    Code:
    // tictactoe.cpp
    #include <iostream>
    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>
    #include "tictactoe.h"
    
    TicTacToe::TicTacToe()
    {
        srand (time(0));    // randomize timer
    
        player = 1;         // who starts?
    
        xMax = 15;          // x size of gameboard
        yMax = 10;          // y size of gameboard
        rowSize = 4;        // how many in a row to win?
    
        droppieces = true;     // 1 = pieces drop, 0 = stay
    
        // new player setup
        numberPlayers = 2;      // how many players?
        playerType[1] = 1;      // player # 1 is human (1)
        playerType[2] = 0;      // player # 2 is comp (0)
        //playerType[3] = 0;    // player # 3 is comp (0)
        
        totalTurns = 0;         // used for boardFull()
        maxTurns = xMax * yMax; // (total board spaces)
    
        // format game board
        for (int y = 0; y < yMax; y++)
            for (int x = 0; x < xMax; x++)
                board[x][y] = 0;
    
    }
    
    void TicTacToe::welcome()
    {
        std::cout << "Welcome to the connect " << rowSize
                  << " game!\n";
    }
    
    void TicTacToe::drawBoard()
    {
    
        std::cout << std::endl;
    
        for (int y = 0; y < yMax; y++)
        {
    
            // draw game board and pieces
            for (int x = 0; x < xMax; x++)
            {
                // no piece then just draw space
                if (!board[x][y])
                    std::cout << "   ";
                else
                    std::cout << " " << board[x][y] << " ";
    
                // draw seperator if not end
                if (x < xMax - 1)
                    std::cout << "│";
            }
    
            std::cout << std::endl;
    
            // draw seperator line between board
            for (int z = 0; z < xMax; z++)
            {
                // draw seperator if not end
                if (y < yMax - 1)
                {
                    std::cout << "───";
                    // draw connection if not end
                    if (z < xMax - 1)
                        std::cout << "┼";
                }
            }
    
            std::cout << std::endl;
        }
    }
    
    void TicTacToe::printTurn()
    {
        std::cout << "Player " << player << "'s turn.\n";
    }
    
    void TicTacToe::nextTurn()
    {
        totalTurns++;
    
        // start again at first player
        if (++player > numberPlayers)
            player = 1;
    }
    
    bool TicTacToe::playerHuman()
    {
        return playerType[player];
    }
    
    void TicTacToe::humanMove()
    {
    
        int moveX, moveY = 0;
    
        do
        {
            std::cout << "\nEnter x: ";
            std::cin >> moveX;
    
            // -1 for computer assisted move
            if (moveX == -1)
                break;
    
            if (!droppieces)
            {
                std::cout << "\nEnter y: ";
                std::cin >> moveY;
                moveY--;    // compensate for user
            }
    
            moveX--;        // compensate for user
        }
        while (moveX < 0 || moveX > xMax - 1 || moveY < 0 ||
               moveY > yMax - 1 || board[moveX][moveY]);
    
        if (moveX == -1)
            computerMove();
        else
            placePieces(moveX, moveY);
    
    }
    
    void TicTacToe::computerMove()
    {
    
        int moveX, moveY;
    
        do
        {
            moveX  = rand() % xMax; // pick a random spot
            moveY  = rand() % yMax; // pick a random spot
        }
        while (board[moveX][moveY]);    // loop if taken
    
        placePieces(moveX, moveY);
    
    }
    
    void TicTacToe::placePieces(int x, int y)
    {
        if (droppieces)
        {
            while (y++ < yMax - 1 && !board[x][y]){}
            y--;  // the last place was taken so go back one
        }
    
        board[x][y] = player;
    
    }
    
    bool TicTacToe::winner()
    {
        int matchcount = 0;
        int lastmatch = 0;
    
    
        // check x row
        for (int y = 0; y < yMax; y++)
            for (int x = 0; x + rowSize - 1 < xMax; x++)
            {
                matchcount = 0;
                lastmatch = board[x][y];
    
                // check through columns
                for (int z = 0; z < rowSize; z++)
                    if (board[x+z][y] && board[x+z][y] ==
                        lastmatch && ++matchcount == rowSize)
                    {
                        announceWinner(lastmatch);
                        return true;
                    }
            }
    
        // check y row
        for (int y = 0; y + rowSize - 1 < yMax; y++)
            for (int x = 0; x < xMax; x++)
            {
                matchcount = 0;
                lastmatch = board[x][y];
    
                // check through rows
                for (int z = 0; z < rowSize; z++)
                    if (board[x][y+z] && board[x][y+z] ==
                        lastmatch && ++matchcount == rowSize)
                    {
                        announceWinner(lastmatch);
                        return true;
                    }
            }
    
    
        // diagonal row check: top left - bottom right
    
        // 10000
        // 01000
        // 00100
        // 00000
        // 00000
    
        // move through columns
        for (int y = 0; y + rowSize - 1 < yMax; y++)
            for (int x = 0; x < xMax; x++)
            {
                matchcount = 0;
                lastmatch = board[x][y];
    
                // check through rows
                for (int z = 0; z < rowSize; z++)
                    if (board[x+z][y+z] && board[x+z][y+z] ==
                        lastmatch && ++matchcount == rowSize)
                    {
                        announceWinner(lastmatch);
                        return true;
                    }
        }
    
        // diagonal row check: top right - bottom left
    
        // 00001
        // 00010
        // 00100
        // 00000
        // 00000
    
        // move through columns
        for (int y = 0; y + rowSize <= yMax; y++)
            for (int x = xMax - 1; x - rowSize + 1 >= 0; x--)
            {
                matchcount = 0;
                lastmatch = board[x][y];
    
                // check through rows
                for (int z = 0; z < rowSize; z++)
                    if (board[x-z][y+z] && board[x-z][y+z] ==
                        lastmatch && ++matchcount == rowSize)
                    {
                        announceWinner(lastmatch);
                        return true;
                    }
        }
    
        return false;
    }
    
    void TicTacToe::announceWinner(int winner)
    {
        std::cout << "\nPlayer " << winner << " wins!\n\n";
    }
    
    bool TicTacToe::fullBoard()
    {
        if (totalTurns == maxTurns)
        {
            std::cout << "\nTie game!\n\n";
            return true;
        }
    
        return false;
    }

  3. #3
    Registered User
    Join Date
    Sep 2011
    Posts
    13
    There is no input for this post? I don't need anyone to run this program, and I know it's not the most exciting program. I am new to programming and just wanted someone to tell me if my programming is out to lunch and what I might need to improve on. For example, the last program I posted I got feedback that it was too much in the c-style format when it should have been more C++ style.

    Even if someone could just look at a few lines and say something it would be a great help.

  4. #4
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    Well first this is one of the more active forums and you can get a reply in less than an hour most times of the day. Second, if no one is responding it is either because your post is unclear could be asking too much. Many of us browse the forum in our free time and the last thing we want to do is browse through an entire application (like we do at work) when we get home. Perhaps you could rephrase your post to indicate you would like input on the final product instead of wanting people to pick apart your code.

  5. #5
    Registered User
    Join Date
    Sep 2011
    Posts
    13
    Quote Originally Posted by VirtualAce View Post
    Well first this is one of the more active forums and you can get a reply in less than an hour most times of the day. Second, if no one is responding it is either because your post is unclear could be asking too much. Many of us browse the forum in our free time and the last thing we want to do is browse through an entire application (like we do at work) when we get home. Perhaps you could rephrase your post to indicate you would like input on the final product instead of wanting people to pick apart your code.
    I can understand. It can be a lot of code to look at. I didn't expect anyone to read it all or run it. And I don't want advice on the program, just the programming style. Does it look like it would be something easy for other coders to work with? Can I use cout inside of a class or is it shunned upon. Am I using any poor programming techniques?

    I was thinking on along the lines of a very quick browse and a short comment if anything stood out.

  6. #6
    Frequently Quite Prolix dwks's Avatar
    Join Date
    Apr 2005
    Location
    Canada
    Posts
    8,057
    Personally, I really dislike indentation like
    Code:
        for (int y = 0; y + rowSize - 1 < yMax; y++)
            for (int x = 0; x < xMax; x++)
            {
                matchcount = 0;
                lastmatch = board[x][y];
     
                // check through rows
                for (int z = 0; z < rowSize; z++)
                    if (board[x][y+z] && board[x][y+z] ==
                        lastmatch && ++matchcount == rowSize)
                    {
                        announceWinner(lastmatch);
                        return true;
                    }
            }
    where curly braces are only placed whenever absolutely necessary. Sure, I sometimes leave out curly braces on short if statements or loops; but if there's going to be another whole compound block inside, I will always put curly braces in. To do otherwise seems to invite disaster. But this is just a personal opinion and if you can keep track, then no problem.

    Be careful of code like
    Code:
            while (y++ < yMax - 1 && !board[x][y]){}
            y--;  // the last place was taken so go back one
    While it may work, there's also a good chance of off-by-one errors and it's much harder to understand and debug, compared with e.g.
    Code:
    for( ; y < yMax && !board[x][y]; y++) {}
    or even
    Code:
    while(y < yMax) {
        if(board[x][y]) {
            break;
        }
        else {
            y ++;
        }
    }
    Rather than typing special characters into your program like
    Code:
                        std::cout << "┼";
    it's probably better to look up the ASCII character code for that character and write it in e.g. hex as
    Code:
    std::cout << "\xc5";
    (I think that's the right code, you'd have to try it.) Box-drawing characters - Wikipedia, the free encyclopedia
    It can be awkward opening source code that contains extended characters in editors and on other systems.

    Also:
    Code:
        // format game board
        for (int y = 0; y < yMax; y++)
            for (int x = 0; x < xMax; x++)
                board[x][y] = 0;
    This is accessing board[0][0], board[1][0], etc. The problem with this is that the elements you access in sequence are not contiguous in memory. The CPU on your computer has a cache for frequently accessed data and operations are much faster if the data you need is in the cache. If you access memory addresses contiguously you'll get a lot more cache hits [hit = good] since the first access will draw in a few successive elements anyway into the cache. If you're skipping through memory you'll probably almost always access an element that hasn't been preloaded into the cache. In this case it won't matter because the array is very small, and can probably fit into memory almost completely. But for large data structures it can make a big difference.

    Something to keep in mind when you're writing more serious code. Don't stomp all over memory; keep your accesses together if you can. It can also help to write your loops small enough so that the entire loop body fits into instruction cache, but that's a whole other topic . . . and something you shouldn't worry about most of the time. Just keep it in the back of your mind.

    Finally: if you wanted to make this program an exercise in object-orientedness, you could refactor it a little. For example, your board could be in a Board class with members like isFull(), etc. I suggest this mainly because the TicTacToe class has a lot of members, and that's often a sign of a class that's trying to do too much. Again once you move on to larger systems you'll come to dread the "God" classes with half a page of member variables and pages of functions . . . .
    dwk

    Seek and ye shall find. quaere et invenies.

    "Simplicity does not precede complexity, but follows it." -- Alan Perlis
    "Testing can only prove the presence of bugs, not their absence." -- Edsger Dijkstra
    "The only real mistake is the one from which we learn nothing." -- John Powell


    Other boards: DaniWeb, TPS
    Unofficial Wiki FAQ: cpwiki.sf.net

    My website: http://dwks.theprogrammingsite.com/
    Projects: codeform, xuni, atlantis, nort, etc.

  7. #7
    Registered User inequity's Avatar
    Join Date
    Nov 2010
    Location
    Seattle, Washington
    Posts
    59
    I tried to edit it a bit to see some things I would do, but I kind of forgot where I was at in there. Anyways, maybe I did something helpful? If you find anything in there interesting let me know and I'll explain it. The only problems I really saw were little style things, the way you're formatting your loops, and not checking array indices before attempting to access them. And you could also consolidate a lot of this code, because a lot of it is repetitive.

    Also it could use a lot more comments. Just because you know what "droppieces" stands for doesn't mean I do.

    Code:
    #define RANDOM_MOVE -1
    
    void TicTacToe::humanMove()
    {
      int moveX, moveY = 0;
    
      do
      {
        std::cout << "\nEnter x: ";
        std::cin >> moveX;
    
        if(!droppieces)
        {
          std::cout << "\nEnter y: ";
          std::cin >> moveY;
          --moveY; // compensate for user
        }
    
        --moveX; // compensate for user
      }
      while (!placePieces(moveX, moveY));
    }
    
    void TicTacToe::computerMove()
    {
      placePieces(RANDOM_MOVE, RANDOM_MOVE);
    }
     
    bool TicTacToe::placePieces(int x, int y)
    {
      // If coordinates are out bounds, it's invalid, with the exception of RANDOM_MOVE
      if((x < 0 || x >= xMax || y < 0 || y >= yMax) && x != RANDOM_MOVE)
        return false;
    
      if(x == RANDOM_MOVE)
      {
        do
        {
          moveX  = rand() % xMax;
          moveY  = rand() % yMax;
        } while (board[moveX][moveY]); // Potentially endless loop
        // A better alternative to this would be to determine all possible remaining moves,
        // get a way to index them, and then do rand() % totalMoves to pick the index
      }
    
      if(droppieces)
      {
        for(; y < yMax && !board[x][y]; ++y) {}
        
        if(y > 0 && y <= yMax)
          --y;
        else 
          return false;
      }
    
      board[x][y] = player;
      return true;
    }
    Last edited by inequity; 11-03-2011 at 08:52 PM.

  8. #8
    Registered User
    Join Date
    Oct 2006
    Posts
    250
    Obviously having the board size and number of opponents and whether they are computers or humans, hard-coded in your program is not very user-friendly. How about letting the user enter these parameters at run-time (with suitable defaults)?

    How about a save-game/load-game option?
    iMalc: Your compiler doesn't accept misspellings and bad syntax, so why should we?
    justin777: I have no idea what you are talking about sorry, I use a laptop and there is no ascii eject or something

  9. #9
    Registered User
    Join Date
    Sep 2011
    Posts
    13
    Thank you all so much for all this feedback! I'm sorry I had taken so long to reply. I didn't think anyone was going to answer this thread and I gave up on it. What a surprise it was to find it!

    Quote Originally Posted by dwks View Post
    Personally, I really dislike indentation like
    Code:
        for (int y = 0; y + rowSize - 1 < yMax; y++)
            for (int x = 0; x < xMax; x++)
            {
                matchcount = 0;
                lastmatch = board[x][y];
     
                // check through rows
                for (int z = 0; z < rowSize; z++)
                    if (board[x][y+z] && board[x][y+z] ==
                        lastmatch && ++matchcount == rowSize)
                    {
                        announceWinner(lastmatch);
                        return true;
                    }
            }
    where curly braces are only placed whenever absolutely necessary. Sure, I sometimes leave out curly braces on short if statements or loops; but if there's going to be another whole compound block inside, I will always put curly braces in. To do otherwise seems to invite disaster. But this is just a personal opinion and if you can keep track, then no problem.
    Thank you. I fixed this. I guess it wouldn't be fun when trying to add extra statements and could get confusing fast.

    Be careful of code like
    Code:
            while (y++ < yMax - 1 && !board[x][y]){}
            y--;  // the last place was taken so go back one
    While it may work, there's also a good chance of off-by-one errors and it's much harder to understand and debug, compared with e.g.
    Code:
    for( ; y < yMax && !board[x][y]; y++) {}
    or even
    Code:
    while(y < yMax) {
        if(board[x][y]) {
            break;
        }
        else {
            y ++;
        }
    }
    Thanks. I found this worked good:
    Code:
        if (droppieces)
            for( ; y < yMax - 1 && !board[x][y + 1]; y++) {}
    Though it may appear that the variable y would cause an out of bounds, it will not. I love how C++ breaks after the first false statement. It lets you do statements like this. I hope its okay to take advantage of this.

    Rather than typing special characters into your program like
    Code:
                        std::cout << "┼";
    it's probably better to look up the ASCII character code for that character and write it in e.g. hex as
    Code:
    std::cout << "\xc5";
    I heard that doing that the ASCII code is different for some systems and doing this might result in an error.

    Also:
    Code:
        // format game board
        for (int y = 0; y < yMax; y++)
            for (int x = 0; x < xMax; x++)
                board[x][y] = 0;
    This is accessing board[0][0], board[1][0], etc. The problem with this is that the elements you access in sequence are not contiguous in memory. The CPU on your computer has a cache for frequently accessed data and operations are much faster if the data you need is in the cache. If you access memory addresses contiguously you'll get a lot more cache hits [hit = good] since the first access will draw in a few successive elements anyway into the cache. If you're skipping through memory you'll probably almost always access an element that hasn't been preloaded into the cache. In this case it won't matter because the array is very small, and can probably fit into memory almost completely. But for large data structures it can make a big difference.
    This will be good to keep in mind when making a side-scroller some day.

    Finally: if you wanted to make this program an exercise in object-orientedness, you could refactor it a little. For example, your board could be in a Board class with members like isFull(), etc. I suggest this mainly because the TicTacToe class has a lot of members, and that's often a sign of a class that's trying to do too much. Again once you move on to larger systems you'll come to dread the "God" classes with half a page of member variables and pages of functions . ..
    I will try to add this on later. Right now I don't know how to use two classes together.


    I tried to edit it a bit to see some things I would do, but I kind of forgot where I was at in there. Anyways, maybe I did something helpful? If you find anything in there interesting let me know and I'll explain it. The only problems I really saw were little style things, the way you're formatting your loops, and not checking array indices before attempting to access them. And you could also consolidate a lot of this code, because a lot of it is repetitive.

    Also it could use a lot more comments. Just because you know what "droppieces" stands for doesn't mean I do.
    I had to look a the code a little before it made sense. I like the idea of using the placePieces to check and drop the piece when needed, so I added it. I've left computerMove on it's own because later on that will be a very sophisiticated method... I explained about droppieces. I'm trying not to be repetitive with code. I think it would help me to build a little more knowledge of C++ so I can have more options.

    Obviously having the board size and number of opponents and whether they are computers or humans, hard-coded in your program is not very user-friendly. How about letting the user enter these parameters at run-time (with suitable defaults)?

    How about a save-game/load-game option?
    I will be likely making the board a vector once I have knowledge of that, so then it would be able to be adjustable and have many more features. I don't want to let the user enter in the board size and other options because I wanted to focus on the more important stuff first. Later on after I've got the computer using strategy then I'll add that. I don't wanna make a save game option right now only to have to change it when the board changes. The save game would likely be one of the last options, but definately a good one to have!

    The next post will have the new game code.

  10. #10
    Registered User
    Join Date
    Sep 2011
    Posts
    13
    I've added what I can so far. I will need to learn a few more things to take advantage of all the suggestions given, but for now this is what I have:

    Code:
    // main.cpp
    #include <iostream>
    #include "tictactoe.h"
    
    int main()
    {
    
        TicTacToe Game;
    
        Game.welcome();
        Game.drawBoard();
    
        do
        {
    
            Game.printTurn();
    
            if (Game.playerHuman()) // human turn?
                Game.humanMove();
            else
                Game.computerMove();
    
            Game.drawBoard();
            Game.nextTurn();
    
        }
        while (!Game.winner() && !Game.fullBoard());
    
        return 0;
    }
    Code:
    // tictactoe.h
    #ifndef TICTACTOE_H
    #define TICTACTOE_H
    #include "vector"
    
    class TicTacToe
    {
    private:
        int board[30][30];      // board[x][y]
        bool droppieces;        // connect4 style pieces drop
        int xMax;               // max horizontal board size
        int yMax;               // max vertical board size
        int rowSize;            // need in a row to win
        int player;             // current player
        int totalTurns;         // how many turns so far?
        int maxTurns;           // full board
        int numberPlayers;      // 1 to 20
        bool playerType[9];     // true = human, false = comp
        // std::vector<double>freeSpots;    //testing vectors
    public:
        TicTacToe();
    
        void welcome();         // welcome screen
        void printTurn();       // whos turn?
    
        bool playerHuman();     // is player human?
        void humanMove();       // human moves
        void computerMove();    // computer moves
        void drawBoard();       // display board
    
        bool winner();          // is there a winner?
        bool fullBoard();       // is the board full?
        void nextTurn();        // switch turn
        
        bool placePieces(int x, int y);        // is spot taken?
        void announceWinner(int winner);    // winner!!
    };
    
    #endif // TICTACTOE_H
    Code:
    // tictactoe.cpp
    #include <iostream>
    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>
    #include "tictactoe.h"
    #include "vector"
    
    TicTacToe::TicTacToe()
    {
        srand (time(0));    // randomize timer
    
        xMax = 3;              // x size of gameboard
        yMax = 3;              // y size of gameboard
        rowSize = 3;        // how many in a row to win?
    
        // when true user only enters X value. the pieces
        // will drop to bottom or just above the piece
        // below it, like in a connect four game
        droppieces = false;
    
        player = 1;             // who starts?
        numberPlayers = 2;      // how many players?
        playerType[1] = 1;      // player # 1 is human (1)
        playerType[2] = 0;      // player # 2 is comp (0)
        //playerType[3] = 0;        // player # 3 is comp (0)
        
        totalTurns = 0;         // used for boardFull()
        maxTurns = xMax * yMax; // (total board spaces)
    
        // format new game board
        for (int y = 0; y < yMax; y++)
            for (int x = 0; x < xMax; x++)
                board[x][y] = 0;
        
        // testing out vector below
        // freeSpots.push_back(player);
        // std::cout << freeSpots[0] << std::endl;
    
    }
    
    void TicTacToe::welcome()
    {
        std::cout << "Welcome to the connect " << rowSize
                  << " game!\n";
    }
    
    void TicTacToe::drawBoard()
    {
    
        std::cout << std::endl;
    
        for (int y = 0; y < yMax; y++)
        {
    
            // draw game board and pieces
            for (int x = 0; x < xMax; x++)
            {
                // no piece then just draw space
                if (!board[x][y])
                    std::cout << "   ";
                else
                    std::cout << " " << board[x][y] << " ";
                
                // draw seperator if not end
                if (x < xMax - 1)
                    std::cout << "│";
            }
    
            std::cout << std::endl; // next line
    
            // draw seperator line between board
            for (int z = 0; z < xMax; z++)
            {
                // draw seperator if not end
                if (y < yMax - 1)
                {
                    std::cout << "───";
                    // draw connection if not end
                    if (z < xMax - 1)
                        std::cout << "┼";
                }
            }
    
            std::cout << std::endl;    // next line
        }
    }
    
    void TicTacToe::printTurn()
    {
        std::cout << "Player " << player << "'s turn.\n";
    }
    
    void TicTacToe::nextTurn()
    {
        totalTurns++;
    
        // start again at first player if last player went
        if (++player > numberPlayers)    // ++ advance player
            player = 1;
    }
    
    bool TicTacToe::playerHuman()
    {
        return playerType[player];
    }
    
    void TicTacToe::humanMove()
    {
    
        int moveX, moveY = 0;
    
        do
        {
            std::cout << "\nEnter x: ";
            std::cin >> moveX;
            
            // if 0 entered computer moves piece
            if (!moveX)
                computerMove();
    
            if (!droppieces)
            {
                std::cout << "\nEnter y: ";
                std::cin >> moveY;
                --moveY;
            }
    
        }    // '--' adjusts to be suitable for array 
        while (moveX && !placePieces(--moveX, moveY));
    
    }
    
    void TicTacToe::computerMove()
    {
    
        int moveX, moveY;
    
        do
        {
            moveX  = rand() % xMax; // pick a random spot
            moveY  = rand() % yMax; // pick a random spot
        }
        while (!placePieces(moveX, moveY));    // loop if taken
    
    }
    
    bool TicTacToe::placePieces(int x, int y)
    {
        // if within boundaries and place not taken then move    
        if (x < 0 || x > xMax - 1 || y < 0
            || y > yMax - 1 || board[x][y])
            return false;    // cannot move here
        
        // if empty spot below then keep dropping piece
        if (droppieces)
            for( ; y < yMax - 1 && !board[x][y + 1]; y++) {}
        
        // put the piece in the board
        board[x][y] = player;
        
        return true;        // move was successful
    }
    
    bool TicTacToe::winner()
    {
        int matchcount = 0;    // stores how many similiar pieces
        int lastmatch = 0;    // stores last piece checked
    
    
        // check x row
        for (int y = 0; y < yMax; y++)
        {
            for (int x = 0; x + rowSize - 1 < xMax; x++)
            {
                matchcount = 0;
                lastmatch = board[x][y];
    
                // check through columns
                for (int z = 0; z < rowSize; z++)
                {    // if enough matches then announce winner
                    if (board[x+z][y] && board[x+z][y] ==
                        lastmatch && ++matchcount == rowSize)
                    {
                        announceWinner(lastmatch);
                        return true;
                    }
                }
            }
        }
    
        // check y row
        for (int y = 0; y + rowSize - 1 < yMax; y++)
        {
            for (int x = 0; x < xMax; x++)
            {
                matchcount = 0;
                lastmatch = board[x][y];
    
                // check through rows
                for (int z = 0; z < rowSize; z++)
                {    // if enough matches then announce winner    
                    if (board[x][y+z] && board[x][y+z] ==
                        lastmatch && ++matchcount == rowSize)
                    {
                        announceWinner(lastmatch);
                        return true;
                    }
                }
            }
        }
    
    
        // diagonal row check: top left - bottom right
    
        // 10000
        // 01000
        // 00100
        // 00000
        // 00000
    
        // move through columns
        for (int y = 0; y + rowSize - 1 < yMax; y++)
        {
            for (int x = 0; x < xMax; x++)
            {
                matchcount = 0;
                lastmatch = board[x][y];
    
                // check through rows
                for (int z = 0; z < rowSize; z++)
                {    // if enough matches then announce winner
                    if (board[x+z][y+z] && board[x+z][y+z] ==
                        lastmatch && ++matchcount == rowSize)
                    {
                        announceWinner(lastmatch);
                        return true;
                    }
                }
            }
        }
    
        // diagonal row check: top right - bottom left
    
        // 00001
        // 00010
        // 00100
        // 00000
        // 00000
    
        // move through columns
        for (int y = 0; y + rowSize <= yMax; y++)
        {
            for (int x = xMax - 1; x - rowSize + 1 >= 0; x--)
            {
                matchcount = 0;
                lastmatch = board[x][y];
    
                // check through rows
                for (int z = 0; z < rowSize; z++)
                {    // if enough matches then announce winner
                    if (board[x-z][y+z] && board[x-z][y+z] ==
                        lastmatch && ++matchcount == rowSize)
                    {
                        announceWinner(lastmatch);
                        return true;
                    }
                }
            }
        }
    
        return false;    // no winners yet
    }
    
    void TicTacToe::announceWinner(int winner)
    {
        std::cout << "\nPlayer " << winner << " wins!\n\n";
    }
    
    bool TicTacToe::fullBoard()
    {
        if (totalTurns == maxTurns)
        {
            std::cout << "\nTie game!\n\n";
            return true;    // board is full
        }
    
        return false;    // board is not full
    }
    One more thing. Someone commented on keeping a index of all available moves. I love the idea and need to learn how to use vectors to make that happen. So it may be a while before that happens.

    If anyone is completely bored and would like to still help then that would be appreciated. Thanks again! I'm so happy to get so much help on this!!
    Last edited by simpleblue; 11-14-2011 at 08:01 PM. Reason: bugs, lol ^^

  11. #11
    Frequently Quite Prolix dwks's Avatar
    Join Date
    Apr 2005
    Location
    Canada
    Posts
    8,057
    Quote Originally Posted by simpleblue View Post
    Thank you all so much for all this feedback! I'm sorry I had taken so long to reply. I didn't think anyone was going to answer this thread and I gave up on it. What a surprise it was to find it!
    It's nice to give feedback to someone who appreciates it . . . . Sorry for the delay in my response. I'd meant to post this a week ago but I only half-finished it.

    I love how C++ breaks after the first false statement. It lets you do statements like this. I hope its okay to take advantage of this.
    You can always use these kinds of features as long as you know what's going on. In this case it's actually a fairly common idiom. For example, you'll see code like if(p && p->something....) to avoid dereferencing NULL pointers.

    I heard that doing that the ASCII code is different for some systems and doing this might result in an error.
    On systems which do not have the same extended ASCII code as your system, in all likelihood that particular character simply won't work. And embedding it in your source is no different from putting the ASCII character value -- since your source is itself ASCII! The only difference it could make would be if the compiler translated the symbol into the appropriate character for the system, but it would be very unlikely to do that because on such a system the character would mean something else anyway. The benefit of putting the ASCII code as a number into your source is that the file is easier to edit (for some editors which may not like the extended character), and it's easier for someone to look up the character code to find a Unicode or system-specific equivalent. (On the other hand you have to run the program to see what the character will actually be. You can search around for more discussions about this if you want other points of view.)

    If you know how to use arrays, then vectors are quite straightforward. Include the header file <vector>, pick the element type and declare a vector<int> or whatever, use push_back to make the vector larger and vec[i] to access elements -- and don't access elements that haven't been created yet. That's about all you need to know to get started. Oh, and if you pass a size to the constructor, the vector initially has that size:
    Code:
    vector<int> data(10);
    Try it + google + ask questions to learn more.

    I don't have many other comments about your code. Well, I suppose I could rant about header files. Generally, angle brackets are used for system headers (#include <vector>) and double-quotes for your own headers (#include "ttt.h"); double-quoted headers typically search a user search path first (which almost always includes ".", the current directory) and if the header isn't found there, the compiler (preprocessor) will go looking in the system header locations as well. So you can #include "vector". But it's generally confusing because it looks like a vector class that you wrote.

    Also: standard header files with a .h on the end (stdlib.h, time.h, ctype.h, etc) are technically C header files. C++ folks used to use them all the time, but more recently, when the benefit of namespaces was realized, new versions of these headers were created. They dropped the ".h" and added a "c" on the beginning to indicate it was a c header (cstdlib, ctime, and somewhat confusingly, cctype). If you include these new headers, you'll get the same functions except in the std:: namespace. So you can add std:: in front of the functions from those headers, or some using statements, and you can feel good about using namespaces. Or you can use the .h versions too, they may be deprecated but in reality they aren't going away, as long as C++ compilers still compile C code.

    My last comment: some of your code, especially the winner() function, can be modularized a bit if you think just slightly more generically. I always enjoy writing functions like this. Maybe you can see what I mean from this example.
    Code:
    /** Check for a winner.
    
        I suppose this function would be more useful it if returned the actual winner. Exercise to the reader. :)
    */
    bool findWinner() {
        for(int x = 0; x < xMax; x ++) {
            for(int y = 0; y < yMax; y++) {
                if(searchLine(x, y, +0, +1)) return true;
                if(searchLine(x, y, +1, +0)) return true;
                if(searchLine(x, y, +1, +1)) return true;
                if(searchLine(x, y, -1, +1)) return true;
            }
        }
    
        return false;
    }
    
    /** Returns true if there's a line of equal elements rowSize long starting at board[x][y] and
        proceeding in the direction given by (xAdd,yAdd).
    
        (This could be modified to return the player who has formed the line and 0 if there is no line.)
    */
    bool searchLine(int x, int y, int xAdd, int yAdd) {
        if(xEnd < 0 || yEnd < 0
            || x + xAdd*(rowSize-1) >= xMax || y + yAdd*(rowSize-1) >= yMax) {
    
            // not enough space for rowSize elements,
            // so clearly we can't have rowSize of them the same
            return false;
        }
    
        int player = board[x][y];
        if(player == 0) {
            // never mind, no player is at this spot yet
            return false;
        }
    
        // yes, we're checking player[x][y] against itself, which is inefficient, but whatever
        for(int i = 0; i < rowSize; i ++) {
            if(board[x + i*xAdd][y + i*yAdd] != player) {
                // we need rowSize elements in a row to be equal to player,
                // but this element is something else!
                return false;
            }
        }
        return true;
    }
    [Sorry for the colour, I know the board does syntax-highlighting now but I can't resist . . . .]

    A couple of suggestions to note here: first, the idea that checking rows and checking columns and checking diagonals are all more or less the same process, and can probably be combined if you think about it hard enough. Also, I tried to break up the complicated expression
    Code:
                    if (board[x-z][y+z] && board[x-z][y+z] ==
                        lastmatch && ++matchcount == rowSize)
    You can handle this by putting it into its own function and using early returns to take care of special conditions. For example, checking whether board[x][y] is nonzero (so that you have a row of actual player pieces instead of emptiness, presumably) doesn't have to be done every time, and it just complicates the code if you do. Much nicer if you can just return false in that case. And the complex bounds on the loops don't have to be there, you can just have another check for running outside the board array; you might opt to keep them in for efficiency but at the hopefully you can at least pull that code into another function which doesn't have much other complexity in it. And so on.

    (I'm not sure I completely understood what your code was doing, and mine may be different, but I hope it still illustrates some interesting points.)

    Sorry for being heavy-handed and writing my own code here, I just spent some time writing subdivision code and the (x,y,z,xDelta,yDelta,zDelta) style of programming is very much on my mind at the moment! That's it for now. Cheers.
    dwk

    Seek and ye shall find. quaere et invenies.

    "Simplicity does not precede complexity, but follows it." -- Alan Perlis
    "Testing can only prove the presence of bugs, not their absence." -- Edsger Dijkstra
    "The only real mistake is the one from which we learn nothing." -- John Powell


    Other boards: DaniWeb, TPS
    Unofficial Wiki FAQ: cpwiki.sf.net

    My website: http://dwks.theprogrammingsite.com/
    Projects: codeform, xuni, atlantis, nort, etc.

  12. #12
    Registered User
    Join Date
    Sep 2011
    Posts
    13
    On systems which do not have the same extended ASCII code as your system, in all likelihood that particular character simply won't work...
    I'm going to leave the ASCII code for later. A big reason is that it does not interest me much at this point and was not a big priority. I find I need to do exciting things or I get bored fast. I will get to it though, eventually.

    If you know how to use arrays, then vectors are quite straightforward. Include the header file <vector>, pick the element type and declare a vector<int> or whatever, use push_back to make the vector larger and vec[i] to access elements -- and don't access elements that haven't been created yet. That's about all you need to know to get started. Oh, and if you pass a size to the constructor, the vector initially has that size:
    Thanks for the push to learn vectors. I was able to get a vector list going but I have not yet looked up how to delete the entire list. As of now I am pushing back more and more elements with each turn that list is created.

    Well, I suppose I could rant about header files. Generally, angle brackets are used for system headers (#include <vector>) and double-quotes for your own headers (#include "ttt.h"); double-quoted headers typically search a user search path first (which almost always includes ".", the current directory)...
    That was a little embarrassing when I saw that. I've read enough and should have spotted that. Fixed.

    Also: standard header files with a .h on the end (stdlib.h, time.h, ctype.h, etc) are technically C header files. C++ folks used to use them all the time, but more recently, when the benefit of namespaces was realized, new versions of these headers were created. They dropped the ".h" and added a "c" on the beginning to indicate it was a c header (cstdlib, ctime, and somewhat confusingly, cctype). If you include these new headers, you'll get the same functions except in the std:: namespace.
    Thank you for that! Fixed.

    I just spent some time writing subdivision code and the (x,y,z,xDelta,yDelta,zDelta) style of programming is very much on my mind at the moment! That's it for now. Cheers.
    This is the idea I needed to save this entire project. It makes the code so much more efficient to check, as well as to use as a mechanism to move players to the next available spot. I tried to explain it as best I could with notes in the code. It took a while to figure out a way to make the findWinner function work as both a winner check and move suggester.

    Btw, please don't feel obligated to help out. A big reason why I put the code up is for a backup as well. But I do really appreciate the suggests and if you have time and want to help out then that's great!

    The code will be on my next post.

  13. #13
    Registered User
    Join Date
    Sep 2011
    Posts
    13
    This code works as far as I know:

    Code:
    // main.cpp
    #include <iostream>
    #include "tictactoe.h"
    
    int main()
    {
    
        TicTacToe Game;
    
        Game.welcome();
        Game.drawBoard();
    
        do
        {
    
            Game.printTurn();
            
            if (Game.playerHuman())    // is player human?
                Game.humanMove();
            else
                Game.computerMove();
    
            Game.drawBoard();
    
        }
        while (!Game.findWinner(1) && !Game.fullBoard());
        
        return 0;
    }

    Code:
    // tictactoe.h
    #ifndef TICTACTOE_H
    #define TICTACTOE_H
    #include <vector>
    
    class TicTacToe
    {
    private:
        int board[30][30];      // board[x][y]
        bool droppieces;        // connect4 style pieces drop
        int xMax;               // max horizontal board size
        int yMax;               // max vertical board size
        int rowSize;            // need in a row to win
        int player;             // current player
        int totalTurns;         // how many turns so far?
        int maxTurns;           // full board
        int numberPlayers;      // 1 to 20
        bool playerType[9];     // true = human, false = comp
        std::vector<int>arrangeTurns;    // list for checking moves
    public:
        TicTacToe();
    
        void welcome();         // welcome screen
        void printTurn();       // whos turn?
    
        bool playerHuman();     // is player human?
        void humanMove();       // human moves
        void computerMove();    // computer moves
        void randomMove();        // cannot find a good move so move here
        void drawBoard();       // display board
    
        bool fullBoard();       // is the board full?
        void nextTurn();        // switch turn
        
        bool placePieces(int x, int y);        // is spot taken?
        void announceWinner(int winner);    // winner!!
        bool findWinner(bool findMove);        // is there a winner?
        bool searchLine(int x, int y, int xAdd, int yAdd, int howManyToWin, int playerPiece);
    };
    #endif // TICTACTOE_H

    Code:
    // tictactoe.cpp
    #include <iostream>
    #include <cstdio>
    #include <cstdlib>
    #include <ctime>
    #include <vector>
    #include "tictactoe.h"
    
    
    TicTacToe::TicTacToe()
    {
        srand (time(0));// randomize timer
    
        xMax = 5;        // x size of gameboard
        yMax = 5;        // y size of gameboard
        rowSize = 4;    // how many in a row to win?
    
    
        /* Should the pieces drop to the bottom of the screen like in the
        game Connect Four? If you want the pieces to stay static then
        this should be set to false. */
        droppieces = false;
    
        player = 1;             // who starts?
        numberPlayers = 2;      // how many players?
        playerType[1] = 1;      // player # 1 is human (1)
        playerType[2] = 0;      // player # 2 is comp (0)
        playerType[3] = 0;      // player # 3 is comp (0)
        playerType[4] = 0;      // player # 3 is comp (0)
        
        totalTurns = 0;         // used for boardFull()
        maxTurns = xMax * yMax; // (total board spaces)
    
        // format new game board
        for (int y = 0; y < yMax; y++)
            for (int x = 0; x < xMax; x++)
                board[x][y] = 0;
    
    }
    
    void TicTacToe::welcome()
    {
        std::cout << "Welcome to the connect " << rowSize
                  << " game!\n";
    }
    
    void TicTacToe::drawBoard()
    {
    
        std::cout << std::endl;
    
        for (int y = 0; y < yMax; y++)
        {
    
            // draw game board and pieces
            for (int x = 0; x < xMax; x++)
            {
                // no piece then just draw space
                if (!board[x][y])
                    std::cout << "   ";
                else
                    std::cout << " " << board[x][y] << " ";
                
                // draw seperator if not end
                if (x < xMax - 1)
                    std::cout << "│";
            }
    
            std::cout << std::endl; // next line
    
            // draw seperator line between board
            for (int z = 0; z < xMax; z++)
            {
                // draw seperator if not end
                if (y < yMax - 1)
                {
                    std::cout << "───";
                    // draw connection if not end
                    if (z < xMax - 1)
                        std::cout << "┼";
                }
            }
    
            std::cout << std::endl;    // next line
        }
    }
    
    void TicTacToe::printTurn()
    {
        std::cout << "Player " << player << "'s turn.\n";
    }
    
    void TicTacToe::nextTurn()
    {
        totalTurns++;
    
        // start again at first player if last player went
        if (++player > numberPlayers)    // ++ advance player
            player = 1;
    }
    
    bool TicTacToe::playerHuman()
    {
        return playerType[player];
    }
    
    void TicTacToe::humanMove()
    {
    
        int moveX, moveY = 0;
    
        do
        {
            std::cout << "\nEnter x: ";
            std::cin >> moveX;
            
            // if 0 entered computer moves piece
            if (!moveX)
                computerMove();
            else if (!droppieces)
            {    // no need to enter Y value if pieces drop anyways
                std::cout << "\nEnter y: ";
                std::cin >> moveY;
                --moveY;
            }
    
        }    // '--' adjusts to be suitable for array 
        while (moveX && !placePieces(--moveX, moveY));
    
    }
    
    void TicTacToe::randomMove()
    {
    
        int moveX, moveY;
        /* will make a vector list of availables move later.
        Right now brian is swelling :s */
        do
        {
            moveX  = rand() % xMax; // pick a random spot
            moveY  = rand() % yMax; // pick a random spot
        }
        while (!placePieces(moveX, moveY));    // loop if taken
    
    }
    
    void TicTacToe::computerMove()
    {
        findWinner(0);    // (0) = don't search for winner, make a move
    }
    
    bool TicTacToe::placePieces(int x, int y)
    {
        // if within boundaries and place not taken then move    
        if (x < 0 || x > xMax - 1 || y < 0
            || y > yMax - 1 || board[x][y])
            return false;    // cannot move here
        
        // if empty spot below then keep dropping piece
        if (droppieces) // start from top and trop the piece
            for(y = 0; y < yMax - 1 && !board[x][y + 1]; y++) {}
        
        // put the piece in the board
        board[x][y] = player;
        
        return true;        // move was successful
    }
    
    bool TicTacToe::findWinner(bool findMove) // is there a way to make a default?
    {
        
        /* This function acts as both a winner checker and a game piece mover.
        
        findMove = true:
        This will look for a complete row, with no empty spaces. If it finds it
        then someone has won the game.
    
        findMove = false:
        We're not trying to find the winner because we've already checked that.
        We're going to try to move to a position that will allow us to win within
        one move; can we win now? If we find this we'll move there and win the
        game. If not, we'll go for the next best move, which will be a move that
        we can win in 1 extra turn. If no move is available then we try to find
        a move that we can move in 2 extra turns, and so on. So we're basically
        trying to find the move which IS possible to win, and has the least amount
        of moves to win, and is a row free of any other players pieces so as to
        allow room to move an entire rows worth of pieces there without
        obstruction.
    
        howManyToWin is the amount of moves it will take to win. So if howManyToWin
        is 0, then there is already a winner. If it is 1 then the winner can win
        now. if it is 2 then the winner can move now, and win on the next turn,
        provided the other player doesn't move there of course ;) ... and so on.
        */
    
    
        int playerPiece;
        int numToWin;
    
        // If we are just checking for a winner then only need to check full rows
        if (findMove)
            numToWin = 0;
        else
            numToWin = rowSize - 1;
    
    
        /* Make a sorted list of who's pieces to check for first.
        The current player will always be first. This is because maybe
        the current player can win right now. If not, the player will
        need to check the other players moves to see if they can win
        in the next turn. If they can win next turn then the player
        will move on that spot. If not, can the current player win within
        1 move?    If not, can the other players win with 1 move? If so move
        on that spot. If not, can the current player win within 2 moves.
        If not, can the others win within 2 moves... The arrangeTurns
        vector is a list of which to player to check for.
        */
    
        // need to figure out how to delete vectors as list keeps appending
    
        std::vector<int>::iterator it;
    
        for(int i = 1; i <= numberPlayers; i++)
        {
            if (player == i) // curent player at begging of vector list
                arrangeTurns.insert(arrangeTurns.begin(), player);
            else
                arrangeTurns.push_back(i); // other players later after first
        }
    
        /* check to see if list is storing correctly (debug)
        for (int i = 0; i < numberPlayers; i++)
        {
        std::cout << "\ni: " << i + 1 << " checks for player: "
        << arrangeTurns[i] << "\n\n";
        }*/
    
    
        
        for(int howManyToWin = 0; howManyToWin <= numToWin; howManyToWin++)
        {
    
            for(int i = 0; i < numberPlayers; i++)
            {
    
                playerPiece = arrangeTurns[i]; // check this piece right now
    
    
                for(int x = 0; x < xMax; x ++)
                {
                       for(int y = 0; y < yMax; y++)
                    {
                        if (searchLine(x, y, +0, +1, howManyToWin, playerPiece) ||
                            searchLine(x, y, +1, +0, howManyToWin, playerPiece) ||
                            searchLine(x, y, +1, +1, howManyToWin, playerPiece) ||
                            searchLine(x, y, -1, +1, howManyToWin, playerPiece))
                        {    
                        
                            // Are we checking or moving a game piece?
                            if (howManyToWin == 0)
                            {
                                announceWinner(player);
                                return true;
                            }else
                            {    // display the type of move made
                                if (playerPiece == player)
                                    std::cout << "\nFound offensive move!\n\n";
                                else                    
                                    std::cout << "\nFound defensive move!\n\n";
                            
                                return true;
                            }
        
                        }
                    }
                }
            }
        }
        
        // are we just checking for winners and didn't find any?
        if (findMove)
        {
            //std::cout << "\nNo winners yet!\n\n";
        }else{    // haven't found anywhere to move, so just pick a random place
            std::cout << "\nJust taking a random move!\n\n";
            // Could this mean a possible tie game is certain circumstances?
            randomMove();
        }            
    
        return false;
    
    }
    
    bool TicTacToe::searchLine(int x, int y, int xAdd, int yAdd, int howManyToWin, int playerPiece)
    {
        
        // didn't understand xEnd/yEnd, so replaced with first two statements
        if (x + xAdd*(rowSize-1) < 0 || x + xAdd*(rowSize-1) >= xMax
            || y + yAdd*(rowSize-1) < 0 || y + yAdd*(rowSize-1) >= yMax)
        {
            // not enough space for rowSize elements,
            // so clearly we can't have rowSize of them the same
            return false;
        }
        
    
        int blanks = 0;    // stores how many blank pieces found in a row
        int tempx = 0;    // holds the spot that is blank and can be moved to
        int tempy = 0;
    
        for (int i = 0; i < rowSize; i++)
        {
            
            /* This code finds the next available move by searching for
            blank spaces and making sure the rest of the pieces are
            the players. If howManyToWin = 0 then this function simply acts
            to check for a winner. */
    
            if (!board[x + i*xAdd][y + i*yAdd] && ++blanks <= howManyToWin)
            {    
                // there is a spot here that is blank and can be moved to
                tempx = x + i*xAdd;
                tempy = y + i*yAdd;
            }else if (board[x + i*xAdd][y + i*yAdd] != playerPiece)
            {
                // we need rowSize elements in a row to be equal to player,
                // but this element is something else!
                return false;
            }
    
        }
        
        // must have found a move, so move here
        placePieces(tempx, tempy);
    
        return true;
    }
    
    void TicTacToe::announceWinner(int winner)
    {
        std::cout << "\nPlayer " << winner << " wins!\n\n";
    }
    
    bool TicTacToe::fullBoard()
    {
        // next persons turn (increment totalTurns by 1)
        nextTurn();
    
        if (totalTurns == maxTurns)
        {
            std::cout << "\nTie game!\n\n";
            return true;    // board is full
        }
    
    
        return false;    // board is not full
    }

  14. #14
    Registered User
    Join Date
    Sep 2011
    Posts
    13
    So much has changed that I figured I'd post the newest code. I hope that's okay.

    Changes:

    - better notes
    - board is now a dynamic vector as people suggested
    - playerType is now a vector
    - if nobody can win then game ends in a tie game
    - board is numbered on edges so players can know which quadrants are which

    Working on:

    - storage for a list of possible moves. this would replace 'randomMove' function
    - advanced move strategy. the computer is good, but could be better
    - make human player input to allow for entering
    - allow user to 'quit', 'forefit', 'swap' players, bring up 'menu', adjust board size, all in realtime...
    - with the board being dynamic it would be able to expand/shrink, and even have row/column insertions at random intervals to make the game more exciting. so a board could look like this:

    00000
    12102
    00010
    02100
    12001

    and then a random insertion is made that stops player 2 from getting 4 in row:

    00000
    12102
    00010
    00000 <--- insertion came out of nowhere and brings more life into the game
    02100
    12001


    Code:
    // main.cpp
    #include <iostream>
    #include "tictactoe.h"
    
    int main()
    {
    
        TicTacToe Game;
    
        Game.welcome();
        Game.drawBoard();
    
        do
        {
    
            Game.printTurn();
            
            if (Game.playerHuman())    // is player human?
                Game.humanMove();
            else
                Game.computerMove();
    
            Game.drawBoard();
        }
        while (!Game.findWinner(1) && !Game.fullBoard() && Game.playersCanWin());
    
        return 0;
    }

    Code:
    // tictactoe.h
    #ifndef TICTACTOE_H
    #define TICTACTOE_H
    #include <vector>
    
    class TicTacToe
    {
    private:
        bool dropPieces;        // connect4 style pieces drop
        int xMax;               // max horizontal board size
        int yMax;               // max vertical board size
        int rowSize;            // need in a row to win
        int player;             // current player
        int totalTurns;         // how many turns so far?
        int maxTurns;           // full board
        int numPlayers;            // 1 to 20
        bool canWin;            // is winning possible? if now, why play?
        std::vector<bool>playerType;    // true = human, false = comp
        std::vector<int>arrangeTurns;    // list for checking moves
        std::vector<std::vector<int> > board;    // 2D gameboard
    public:
        TicTacToe();
    
        void welcome();         // welcome screen
        void printTurn();       // whos turn?
    
        bool playerHuman();     // is player human?
        void humanMove();       // human moves
        void computerMove();    // computer moves
        void randomMove();        // cannot find a good move so move here
        void drawBoard();       // display board
    
        bool fullBoard();       // is the board full?
        void nextTurn();        // switch turn
        bool playersCanWin();    // if all players cant win then end the game   
    
        bool placePieces(int x, int y);        // is spot taken?
        void sayWinner(int winner);            // winner!!
        bool findWinner(bool findMove);        // is there a winner?
        bool searchLine(int x, int y, int xAdd, int yAdd, int howManyToWin, int playerPiece);
    };
    #endif // TICTACTOE_H

    Code:
    // tictactoe.cpp
    #include <iostream>
    #include <cstdio>
    #include <cstdlib>
    #include <ctime>
    #include <vector>
    #include "tictactoe.h"
    
    
    TicTacToe::TicTacToe()
    {
        srand (time(0));// randomize timer
        
        // Default Connect Four boardgame is xMax 7, yMax 6
        xMax = 5;        // x size of gameboard
        yMax = 6;        // y size of gameboard
        rowSize = 4;    // how many in a row to win?
    
    
        /* Should the pieces drop to the bottom of the screen like in the
        game Connect Four? If you want the pieces to stay static then
        this should be set to false. Keep in mind that the computers move
        strategy has been (so far) designed to work with static pieces
        only */
        dropPieces = false;
    
        player = 1;            // who starts?
        numPlayers = 2;        // how many players?
        canWin = true;        // is the game winnable? if not end game
    
        playerType.push_back(0);    // player # 1 is human (1)
        playerType.push_back(0);    // player # 2 is comp (0)
        playerType.push_back(0);    // player # 3 is comp (0)
        playerType.push_back(0);    // player # 4 is comp (0)
    
        totalTurns = 0;         // used for boardFull()
        maxTurns = xMax * yMax; // (total board spaces)
    
        // Dynamic 2D Vector Array
        // format new game board
        for (int x = 0; x < xMax; x++)
        {
            board.push_back (std::vector<int>());
     
            for (int y = 0; y < yMax; y++)
                board[x].push_back (0);
        }
    
    }
    
    void TicTacToe::welcome()
    {
        std::cout <<     "\n~ Welcome to Connect Four! ~\n\n"
                          "Make " << rowSize << " in a row to win!\n\n"
                        "Press '0' for a computer assisted move\n\n"
                        "Good luck!\n";
    }
    
    void TicTacToe::drawBoard()
    {
    
        std::cout << std::endl;
    
        for (int y = 0; y < yMax + 1; y++)
        {
    
            // draw game board and pieces
            for (int x = 0; x < xMax + 1; x++)
            {
    
    
                if (x == 0)
                {
                    if (y < 10)
                        std::cout << " " << y << " ";
                    else
                        std::cout << " " << y;                    
                }else if (y == 0)
                {
                    if (x < 10)
                        std::cout << " " << x << " ";
                    else
                        std::cout << " " << x;                                
                }else{            
                // no piece then just draw space            
                if (!board[x - 1][y - 1])
                    std::cout << "   ";
                else
                    std::cout << " " << board[x - 1][y - 1] << " ";
                }
    
    
                // draw seperator if not end
                if (x < xMax)
                    std::cout << "│";
            }
    
            std::cout << std::endl; // next line
    
            // draw seperator line between board
            for (int z = 0; z < xMax + 1; z++)
            {
                // draw seperator if not end
                if (y < yMax)
                {
                    std::cout << "───";
                    // draw connection if not end
                    if (z < xMax)
                        std::cout << "┼";
                }
            }
    
            std::cout << std::endl;    // next line
        }
    }
    
    void TicTacToe::printTurn()
    {
        std::cout << "Player " << player << "'s turn.\n";
    }
    
    void TicTacToe::nextTurn()
    {
        totalTurns++;
    
        // start again at first player if last player went
        if (++player > numPlayers)    // ++ advance player
            player = 1;
    }
    
    bool TicTacToe::playerHuman()
    {
        return playerType[player - 1]; // -1; compensate for 0 vector element
    }
    
    void TicTacToe::humanMove()
    {
    
        int moveX, moveY = 0;
    
        do
        {
            std::cout << "\nEnter x: ";
            std::cin >> moveX;
            
            // if 0 entered computer moves piece
            if (!moveX)
                computerMove();
            else if (!dropPieces)
            {    // no need to enter Y value if pieces drop anyways
                std::cout << "\nEnter y: ";
                std::cin >> moveY;
                --moveY;
            }
    
        }    // '--' adjusts to be suitable for array 
        while (moveX && !placePieces(--moveX, moveY));
    
    }
    
    void TicTacToe::randomMove()
    {
    
        int moveX, moveY;
        /* Will make a vector list of availables moves later.
        Right now brain is extra swelling :s */
        do
        {
            moveX  = rand() % xMax; // pick a random spot
            moveY  = rand() % yMax; // pick a random spot
        }
        while (!placePieces(moveX, moveY));    // loop if taken
    
    }
    
    void TicTacToe::computerMove()
    {
        findWinner(0);    // (0) = don't search for winner, make a move
    }
    
    bool TicTacToe::placePieces(int x, int y)
    {
        // if within boundaries and place not taken then move    
        if (x < 0 || x > xMax - 1 || y < 0
            || y > yMax - 1 || board[x][y])
            return false;    // cannot move here
        
        // if empty spot below then keep dropping piece
        if (dropPieces) // start from top and drop the piece
            for(y = 0; y < yMax - 1 && !board[x][y + 1]; y++) {}
        
        // put the piece in the board
        board[x][y] = player;
        
        return true;        // move was successful
    }
    
    bool TicTacToe::findWinner(bool findMove) // is there a way to make a default?
    {
        
        /* This function acts as both a winner checker and a game piece mover.
        
        findMove = true:
        This will look for a complete row, with no empty spaces. If it finds it
        then someone has won the game.
    
        findMove = false:
        We're not trying to find the winner because we've already checked that.
        We're going to try to move to a position that will allow us to win within
        one move; can we win now? If we find this we'll move there and win the
        game. If not, we'll go for the next best move, which will be a move that
        we can win in 1 extra turn. If no move is available then we try to find
        a move that we can move in 2 extra turns, and so on. So we're basically
        trying to find the move which IS possible to win, and has the least amount
        of moves to win, and is a row free of any other players pieces so as to
        allow room to move an entire rows worth of pieces there without
        obstruction.
    
        howManyToWin is the amount of moves it will take to win. So if howManyToWin
        is 0, then there is already a winner. If it is 1 then the winner can win
        now. if it is 2 then the winner can move now, and win on the next turn,
        provided the other player doesn't move there of course ;) ... and so on.
        */
    
    
        int playerPiece;
        int numToWin;
    
        /* Are we just checking for a winner? If so, we only need to check for a
        full row.
    
        The 'elseif'/'else'... code is a temporary hack. Without this hack the
        computer would always make it's first move at quadrant 1,1. I hope to
        impliment a vector storage array that will make a list possible moves and
        randomly pick a move from that list. At that point this would read
        numToWin = rowSize. This would mean that even if noone was on the gameboard
        the computer would still look for a place that was possible to win. It
        make a list containing all the squares on the board, and pick one randomly.
    
        When working this gets properly then: numToWin = rowSize
        */
        if (findMove)
            numToWin = 0;
        else if (totalTurns >= numPlayers)
            numToWin = rowSize;
        else
            numToWin = rowSize - 1;
            
    
    
        /* Make a sorted list of who's pieces to check for first.
        The current player will always be first. This is because maybe
        the current player can win right now. If not, the player will
        need to check the other players moves to see if they can win
        in the next turn. If they can win next turn then the player
        will move on that spot. If not, can the current player win within
        1 move?    If not, can the other players win with 1 move? If so move
        on that spot. If not, can the current player win within 2 moves.
        If not, can the others win within 2 moves... The arrangeTurns
        vector is a list of which to player to check for.
        */
    
    
        std::vector<int>::iterator it;
        arrangeTurns.clear(); // Am I deleting the arrangeTurns vector correctly?
    
        for(int i = 1; i <= numPlayers; i++)
        {
            if (player == i) // curent player at beginning of vector list
                arrangeTurns.insert(arrangeTurns.begin(), player);
            else
                arrangeTurns.push_back(i); // other players later after first
        }
    
        
        for(int howManyToWin = 0; howManyToWin <= numToWin; howManyToWin++)
        {
    
            for(int i = 0; i < numPlayers; i++)
            {
    
                playerPiece = arrangeTurns[i]; // check this piece right now
    
    
                for(int x = 0; x < xMax; x ++)
                {
                       for(int y = 0; y < yMax; y++)
                    {
                        if (searchLine(x, y, +0, +1, howManyToWin, playerPiece) ||
                            searchLine(x, y, +1, +0, howManyToWin, playerPiece) ||
                            searchLine(x, y, +1, +1, howManyToWin, playerPiece) ||
                            searchLine(x, y, -1, +1, howManyToWin, playerPiece))
                        {    
    
                            // Are we checking or moving a game piece?
                            if (howManyToWin == 0) // they made a full row then!
                            {
                                sayWinner(player);
                                return true;
                            }else
                            {    // display the type of move made
                                if (playerPiece == player)
                                    std::cout << "\nFound offensive move!\n\n";
                                else                    
                                    std::cout << "\nFound defensive move!\n\n";
    
                                return true;
                            }
        
                        }
                    }
                }
            }
        }
        
        // are we just checking for winners and didn't find any?
        if (findMove)
        {
            //std::cout << "\nNo winners yet!\n\n";
        }else{    // haven't found anywhere to move, so just pick a random place
            
            // if its not the first move and the player cannot move to win...
            if (totalTurns >= numPlayers)        
                canWin = false;
            else
            {
                std::cout << "\nJust taking a random move!\n\n";
                randomMove();
            }
        }            
    
        return false;
    
    }
    
    bool TicTacToe::searchLine(int x, int y, int xAdd, int yAdd, int howManyToWin, int playerPiece)
    {
        
        // didn't understand xEnd/yEnd, so replaced with first two statements
        if (x + xAdd*(rowSize-1) < 0 || x + xAdd*(rowSize-1) >= xMax
            || y + yAdd*(rowSize-1) < 0 || y + yAdd*(rowSize-1) >= yMax)
        {
            // not enough space for rowSize elements,
            // so clearly we can't have rowSize of them the same
            return false;
        }
        
    
        int blanks = 0;    // stores how many blank pieces found in a row
        int tempx = 0;    // holds the spot that is blank and can be moved to
        int tempy = 0;
    
        for (int i = 0; i < rowSize; i++)
        {
            
            /* This code finds the next available move by searching for
            blank spaces and making sure the rest of the pieces are
            the current players. If howManyToWin = 0 then this function
            simply acts    to check for a winner. */
    
            if (!board[x + i*xAdd][y + i*yAdd] && ++blanks <= howManyToWin)
            {
                // there is a spot here that is blank and can be moved to
                tempx = x + i*xAdd;
                tempy = y + i*yAdd;
            }else if (board[x + i*xAdd][y + i*yAdd] != playerPiece)
            {
                // we need rowSize elements in a row to be equal to player,
                // but this element is something else!
                return false;
            }
    
        }
    
    
        /* This is where a vector struct called possibleMoves could be made to
        to grab each possible move and store it in a list. Later, when the rows
        have been scanned, the findWinner function would look at the list and
        randomly pick a move. Right now we're picking the first best available
        spot and moving the gamepiece there. This results in predictable moves,
        such as moving to quadrant 1,1 on first move if given the chance. */
    
        // must have found a move, so move here
        placePieces(tempx, tempy);
    
        return true;
    }
    
    void TicTacToe::sayWinner(int winner)
    {
        std::cout << "\nPlayer " << winner << " wins!\n\n";
    }
    
    bool TicTacToe::playersCanWin()
    {
        if (!canWin)
        {
            std::cout << "\nNoone can win. So tie game!\n\n";
            return false;
        }
        return true;
    }
    
    bool TicTacToe::fullBoard()
    {
        // next persons turn (increment totalTurns by 1)
        nextTurn();
    
        if (totalTurns >= maxTurns)
        {
            std::cout << "\nTie game!\n\n";
            return true;    // board is full
        }
    
    
        return false;    // board is not full
    }
    Anyways, that's about it

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Connect four game help
    By kenneth_888 in forum C++ Programming
    Replies: 2
    Last Post: 05-28-2007, 08:42 AM
  2. New Connect Four Game
    By PJYelton in forum Game Programming
    Replies: 4
    Last Post: 01-17-2003, 10:13 AM
  3. Connect 4 game
    By sundeeptuteja in forum Game Programming
    Replies: 6
    Last Post: 08-12-2002, 11:09 PM
  4. Advanced connect four game
    By Ion Blade in forum C++ Programming
    Replies: 10
    Last Post: 07-28-2002, 07:52 AM
  5. Connect Four game...need help
    By Ion Blade in forum C++ Programming
    Replies: 2
    Last Post: 06-18-2002, 06:18 PM