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