Page 1 of 1

Creating Tic-Tac-Toe A simple walk-through

#1 Hyper  Icon User is offline

  • Banned

Reputation: 108
  • View blog
  • Posts: 2,129
  • Joined: 15-October 08

Posted 24 March 2009 - 03:18 PM

PREFACE: You require this header file (TextControl) to compile THIS (Tic-Tac-Toe) program (game)

Hello, first and foremost, my name is Dani (Danielle, to be proper) but you may call me Hyper.
I'm not very good at writting tutorials or thinking of how to start them and word them and everything else, but I'll try my best!

First things first when it comes to designing a game (no matter how simple, complex, large or small):
Look at everything we're going to need to do. In Tic-Tac-Toe, it's simple:
  • A game board
  • Two players
.

Step 1. Creating the Game-board
We could go about creating a game board a thousand ways, but I'll use the most popular (at least I think it is) way I know of: A 2-Dimensional array.
int GameBoard[3][3] = {
    { 0, 0, 0 },
    { 0, 0, 0 },
    { 0, 0, 0 }
};


The Rows are represented from "North to South" and columns are "East" to "West"
If you forget that, then adding onto Tic-Tac-Toe will become very confusing and difficult.
Now that you have a 2-Dimensional array, how will you display it (visually)? The answer is simple! Use DrawBox and manually add the criss-crosses in:
void DrawBoard(const int foreground, const int background) {

    SetColor(foreground, background);

    /* Very cryptic - do not attempt to read, simply know that it draws a Tic-Tac-Toe box */
    /* NOTE: BOARD_X and BOARD_Y are #define'd values so you can easily change the positioning of the board */
    DrawBox(7, 7, BOARD_X, BOARD_Y);

    PlaceCursor(BOARD_X + 2, BOARD_Y); WriteConsole(InHandle, "", 1, 0, NULL);
    PlaceCursor(BOARD_X + 4, BOARD_Y); WriteConsole(InHandle, "", 1, 0, NULL);
    PlaceCursor(BOARD_X + 2, BOARD_Y + 1); WriteConsole(InHandle, "", 1, 0, NULL);
    PlaceCursor(BOARD_X + 4, BOARD_Y + 1); WriteConsole(InHandle, "", 1, 0, NULL);
    PlaceCursor(BOARD_X + 2, BOARD_Y + 3); WriteConsole(InHandle, "", 1, 0, NULL);
    PlaceCursor(BOARD_X + 4, BOARD_Y + 3); WriteConsole(InHandle, "", 1, 0, NULL);
    PlaceCursor(BOARD_X + 2, BOARD_Y + 5); WriteConsole(InHandle, "", 1, 0, NULL);
    PlaceCursor(BOARD_X + 4, BOARD_Y + 5); WriteConsole(InHandle, "", 1, 0, NULL);
    PlaceCursor(BOARD_X + 2, BOARD_Y + 6); WriteConsole(InHandle, "", 1, 0, NULL);
    PlaceCursor(BOARD_X + 4, BOARD_Y + 6); WriteConsole(InHandle, "", 1, 0, NULL);

    PlaceCursor(BOARD_X, BOARD_Y + 2);
    WriteConsole(InHandle, "", 1, 0, NULL);
    WriteConsole(InHandle, "", 1, 0, NULL);
    WriteConsole(InHandle, "", 1, 0, NULL);
    WriteConsole(InHandle, "", 1, 0, NULL);
    WriteConsole(InHandle, "", 1, 0, NULL);
    WriteConsole(InHandle, "", 1, 0, NULL);
    WriteConsole(InHandle, "", 1, 0, NULL);

    PlaceCursor(BOARD_X, BOARD_Y + 4);
    WriteConsole(InHandle, "", 1, 0, NULL);
    WriteConsole(InHandle, "", 1, 0, NULL);
    WriteConsole(InHandle, "", 1, 0, NULL);
    WriteConsole(InHandle, "", 1, 0, NULL);
    WriteConsole(InHandle, "", 1, 0, NULL);
    WriteConsole(InHandle, "", 1, 0, NULL);
    WriteConsole(InHandle, "", 1, 0, NULL);

    /* Place the letters (rows) */
    PlaceCursor(BOARD_X - 2, BOARD_Y + 1);
    WriteConsole(InHandle, "A", 1, 0, NULL);
    PlaceCursor(BOARD_X - 2, BOARD_Y + 3);
    WriteConsole(InHandle, "B", 1, 0, NULL);
    PlaceCursor(BOARD_X - 2, BOARD_Y + 5);
    WriteConsole(InHandle, "C", 1, 0, NULL);

    /* Place the numbers (columns) */
    PlaceCursor(BOARD_X + 1, BOARD_Y - 1);
    WriteConsole(InHandle, "1", 1, 0, NULL);
    PlaceCursor(BOARD_X + 3, BOARD_Y - 1);
    WriteConsole(InHandle, "2", 1, 0, NULL);
    PlaceCursor(BOARD_X + 5, BOARD_Y - 1);
    WriteConsole(InHandle, "3", 1, 0, NULL);

    return;
}


Step 2. Displaying players moves
OK! Now we have our board, so what? What good does it do for us if we can't use it for users to play on? You can't simply printf() the players move.
Plus, what if the user decides to change the placement or size of the Tic-Tac-Toe board? Obviously just printf'ing would be bad.
A better idea (solution) would be to do something more on the lines of this:
void DrawPlayers() {

    SetColor(WHITE, BLACK);

    for (int x = 0; x < 3; x++) {
        for (int y = 0; y < 3; y++) {
            PlaceCursor(BOARD_X + (x * 2) + 1, BOARD_Y + (y * 2) + 1);

            switch (GameBoard[x][y]) {
                case 0: break; /* Do absolutely nothing - unoccupied space */
                case 1: WriteConsole(InHandle, "X", 1, 0, NULL); break;
                case 2: WriteConsole(InHandle, "O", 1, 0, NULL); break;

                default : WriteConsole(InHandle, "?", 1, 0, NULL);
            };
        }
    }

    return;
}


That will display an X, O, a blank space or ? if something is terribly wrong. In-case you're wondering what the formula is or where it came from, the answer is simple: Me! Algebra can be useful in situations like this. The board can be placed on X location (represented by BOARD_X). The players move is recorded on "x", if you simply went PlaceCursor(x, y); you'd be placing their letter in the upper left corner side-by-side. So what we did was placed it on the upper left corner of the box (BOARD_X & BOARD_Y), then we added one to each (so now it's in the upper left corner where it should be). Next, so it's in the proper square, we multiply it by 2.
The logic on that is a little fuzzy, but if you sit there and think about it for a while, it might all come together for you.

Step 3. Getting and validating user input
When I wrote this (Tic-Tac-Toe), I intentionally did not rid of all possible bugs (typing a letter instead of a number for a row).
Or as I say, "Making it Polac proof." We ask the user for a row (1-3) and a column (A-C). Like so:
    int Row = 0;
    char Column = '\0';
    enum { PLAYER_1 = 1, PLAYER_2 = 2 } PlayersTurn = PLAYER_1;

        PlaceCursor(0, 0);
        printf("Player %i - Your turn\n", PlayersTurn);
        printf("Enter a row (1-3): ");
        scanf("%d", &Row);
        _flushall();

        printf("Enter a column (A-C): ");
        scanf("%c", &Column);
        _flushall();


How do we use the character to determine the row the user selected? You might wonder. But that's very simple! We convert their answer (single character) to upper case (so it'll always be one of three values) and we switch statement that value and modify it accordingly:
        switch (toupper(Column)) {
            case 'A': Column = 0; break;
            case 'B': Column = 1; break;
            case 'C': Column = 2; break;
        };


This might not be the most effecient method of doing it, but there's always more than one way to skin a cat. ;)
Now that we have their row and column, how do we know it wasn't "Out of bounds" or on an already occupied area? That's simple! We create and use a function called: ValidMove()! :) The function is as follows:
bool ValidMove(const int Row, const char Column) {

    /* If the move made was in an already occupied square; Return false */
    if (GameBoard[Row][Column] != 0) { return false; }

    return true;
}


The reason the function is a boolean one, is so we can if statement it. Such as: If Player 1's move was valid, proceed; Else ask again.
        if (ValidMove(Row - 1, Column)) {
            GameBoard[Row - 1][Column] = PlayersTurn;
            DrawPlayers();


We remove one from the Row value because the user enters 1-3, rather than 0-2. The reasoning is because arrays follow base-10 (0-9 digits). Normally people "logically" go 1-10, but computers follow 0-9. So we must decrease (thus 1 becomes 0) the value by one.

Step 4. Changing from Player 1 to Player 2
We now have the ability to: Draw a board, draw player moves, check if input was valid or not - But we have no method for changing from Player 1 to Player 2! :( An easy solution is to use an enum (enumeration data-type)! With an enum, we can easily switch from "Player" to "Player" and I have used it above earlier in the code without mentioning it. But before I can explain how to switch users, I must show the entire game as a whole, only then will you understand. With that said, here is Tic-Tac-Toe!
#include <iostream>
#include <conio.h> /* For getch() */
#include <TextControl.h>
using namespace std;

#define BOARD_X 32
#define BOARD_Y 7

HANDLE InHandle = GetStdHandle(STD_OUTPUT_HANDLE);
CREATE_MENU Menu;

/* This is our gameboard (the Tic-Tac-Toe board) */
/* Rows are "North to South" and columns are East to West */
int GameBoard[3][3] = {
    { 0, 0, 0 },
    { 0, 0, 0 },
    { 0, 0, 0 }
};

int Victor() {

    for (int x = 1; x <= 2; x++) {
        if (GameBoard[0][0] == x && GameBoard[0][1] == x && GameBoard[0][2] == x) { return x; }
        if (GameBoard[0][1] == x && GameBoard[0][4] == x && GameBoard[0][7] == x) { return x; }
        if (GameBoard[0][2] == x && GameBoard[0][5] == x && GameBoard[0][8] == x) { return x; }
        if (GameBoard[0][0] == x && GameBoard[1][0] == x && GameBoard[2][0] == x) { return x; }
        if (GameBoard[0][3] == x && GameBoard[0][4] == x && GameBoard[0][5] == x) { return x; }
        if (GameBoard[0][6] == x && GameBoard[0][7] == x && GameBoard[0][8] == x) { return x; }
        if (GameBoard[0][0] == x && GameBoard[1][1] == x && GameBoard[2][2] == x) { return x; }
        if (GameBoard[0][2] == x && GameBoard[1][1] == x && GameBoard[2][0] == x) { return x; }
    }

    return 0;
}

void DrawPlayers() {

    SetColor(WHITE, BLACK);

    for (int x = 0; x < 3; x++) {
        for (int y = 0; y < 3; y++) {
            PlaceCursor(BOARD_X + (x * 2) + 1, BOARD_Y + (y * 2) + 1);

            switch (GameBoard[x][y]) {
                case 0: break; /* Do absolutely nothing - unoccupied space */
                case 1: WriteConsole(InHandle, "X", 1, 0, NULL); break;
                case 2: WriteConsole(InHandle, "O", 1, 0, NULL); break;

                default : WriteConsole(InHandle, "?", 1, 0, NULL);
            };
        }
    }

    return;
}

void DrawBoard(const int foreground, const int background) {

    SetColor(foreground, background);

    /* Very cryptic - do not attempt to read, simply know that it draws a Tic-Tac-Toe box */
    DrawBox(7, 7, BOARD_X, BOARD_Y);
    PlaceCursor(BOARD_X + 2, BOARD_Y); WriteConsole(InHandle, "", 1, 0, NULL);
    PlaceCursor(BOARD_X + 4, BOARD_Y); WriteConsole(InHandle, "", 1, 0, NULL);
    PlaceCursor(BOARD_X + 2, BOARD_Y + 1); WriteConsole(InHandle, "", 1, 0, NULL);
    PlaceCursor(BOARD_X + 4, BOARD_Y + 1); WriteConsole(InHandle, "", 1, 0, NULL);
    PlaceCursor(BOARD_X + 2, BOARD_Y + 3); WriteConsole(InHandle, "", 1, 0, NULL);
    PlaceCursor(BOARD_X + 4, BOARD_Y + 3); WriteConsole(InHandle, "", 1, 0, NULL);
    PlaceCursor(BOARD_X + 2, BOARD_Y + 5); WriteConsole(InHandle, "", 1, 0, NULL);
    PlaceCursor(BOARD_X + 4, BOARD_Y + 5); WriteConsole(InHandle, "", 1, 0, NULL);
    PlaceCursor(BOARD_X + 2, BOARD_Y + 6); WriteConsole(InHandle, "", 1, 0, NULL);
    PlaceCursor(BOARD_X + 4, BOARD_Y + 6); WriteConsole(InHandle, "", 1, 0, NULL);

    PlaceCursor(BOARD_X, BOARD_Y + 2);
    WriteConsole(InHandle, "", 1, 0, NULL);
    WriteConsole(InHandle, "", 1, 0, NULL);
    WriteConsole(InHandle, "", 1, 0, NULL);
    WriteConsole(InHandle, "", 1, 0, NULL);
    WriteConsole(InHandle, "", 1, 0, NULL);
    WriteConsole(InHandle, "", 1, 0, NULL);
    WriteConsole(InHandle, "", 1, 0, NULL);

    PlaceCursor(BOARD_X, BOARD_Y + 4);
    WriteConsole(InHandle, "", 1, 0, NULL);
    WriteConsole(InHandle, "", 1, 0, NULL);
    WriteConsole(InHandle, "", 1, 0, NULL);
    WriteConsole(InHandle, "", 1, 0, NULL);
    WriteConsole(InHandle, "", 1, 0, NULL);
    WriteConsole(InHandle, "", 1, 0, NULL);
    WriteConsole(InHandle, "", 1, 0, NULL);

    /* Place the letters (rows) */
    PlaceCursor(BOARD_X - 2, BOARD_Y + 1);
    WriteConsole(InHandle, "A", 1, 0, NULL);
    PlaceCursor(BOARD_X - 2, BOARD_Y + 3);
    WriteConsole(InHandle, "B", 1, 0, NULL);
    PlaceCursor(BOARD_X - 2, BOARD_Y + 5);
    WriteConsole(InHandle, "C", 1, 0, NULL);

    /* Place the numbers (columns) */
    PlaceCursor(BOARD_X + 1, BOARD_Y - 1);
    WriteConsole(InHandle, "1", 1, 0, NULL);
    PlaceCursor(BOARD_X + 3, BOARD_Y - 1);
    WriteConsole(InHandle, "2", 1, 0, NULL);
    PlaceCursor(BOARD_X + 5, BOARD_Y - 1);
    WriteConsole(InHandle, "3", 1, 0, NULL);

    return;
}

void ResetBoard() {

    for (int x = 0; x < 3; x++) {
        for (int y = 0; y < 3; y++) {
            GameBoard[x][y] = 0;
        }
    }

    return;
}

bool ValidMove(const int Row, const char Column) {

    /* If the move made was in an already occupied square; Return false */
    if (GameBoard[Row][Column] != 0) { return false; }

    return true;
}

void TicTacToe() {

    PlaceCursor(BOARD_X - 2, BOARD_Y - 4);
    SetColor(WHITE);
    printf("TIC-TAC-TOE");

    DrawBoard(WHITE, BLACK);
    DrawPlayers();

    int Row = 0;
    char Column = '\0';
    enum { PLAYER_1 = 1, PLAYER_2 = 2 } PlayersTurn = PLAYER_1;

    while (true) {
        PlaceCursor(0, 0);
        printf("Player %i - Your turn\n", PlayersTurn);
        printf("Enter a row (1-3): ");
        scanf("%d", &Row);
        _flushall();

        printf("Enter a column (A-C): ");
        scanf("%c", &Column);
        _flushall();

        switch (toupper(Column)) {
            case 'A': Column = 0; break;
            case 'B': Column = 1; break;
            case 'C': Column = 2; break;
        };

        if (ValidMove(Row - 1, Column)) {
            GameBoard[Row - 1][Column] = PlayersTurn;
            DrawPlayers();

            if (PlayersTurn == PLAYER_1) { PlayersTurn = PLAYER_2; } else { PlayersTurn = PLAYER_1; }
            PlaceCursor(0, 4); printf("             ");
        } else { PlaceCursor(0, 4); printf("Invalid move!"); }

        PlaceCursor(19, 1); printf(" ");
        PlaceCursor(22, 2); printf(" ");

        if (Victor() == 1) { PlaceCursor(30, 20); printf("Player 1 is the winner!"); break; } else
        if (Victor() == 2) { PlaceCursor(30, 20); printf("Player 2 is the winner!"); break; }
    }

    ResetBoard();
    _flushall();
    _getch();

    ClearConsole(BLACK, BLACK);
    Menu.DisplayMenu(CYAN, BLACK);
    return;
}

int Callback(int CurrItem, bool EXIT_MENU, int Key) {

    switch (CurrItem) {
        case 0: { if (Key == 5) { Menu.Clear(BLACK, BLACK); TicTacToe(); } } break;
        case 2: { if (Key == 5) { EXIT_MENU = true; } } break;
        case 666: { EXIT_MENU = true; }
    }

    return EXIT_MENU;
}

void MainMenu() {

    Menu.SetAttributes(30, 15, 20, 5, "TIC-TAC-TOE");
    Menu.AddOptions(3, "Play", "Options", "Quit");
    Menu.DisplayMenu(CYAN, BLACK);
    Menu.Loop(Callback, false);
    return;
}

int main() {

    RemoveCursor();
    ClearConsole(BLACK, BLACK);

    MainMenu();

    return 0;
}


The two (minor) details I left out were: ResetBoard (to remove all player moves) and the menu (which is easy and self-explainitory).
A few suggestions (to "fix" or improve the game) to you as a reader:
- Ensure a user does not type a number for a row, vise-versa
- Create an entire "Options" tree, have the options be the following:
1. Change the color of the board
2. Change the color of a player
3. Change opponent type (Computer vs. Player or Player vs. Player)
4. Change your "piece" type (so you can have more than just X and O)
5. Smart or dumb computer AI
- Add the ability to grow/shrink the board size (simple multiplication, but! Can you do it?)
- Add the ability to use arrow keys to move your piece around the board and enter to place it
- Add computer AI ("smart" AI (determines where to go based on your move) and "dumb" AI (randomly moves)
- Convert the massive if statements for victory into a for loop

Hope this was helpful to somebody, enjoy! :)

This post has been edited by Hyper: 24 March 2009 - 06:48 PM


Is This A Good Question/Topic? 0
  • +

Replies To: Creating Tic-Tac-Toe

#2 ChemistrY  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 3
  • Joined: 21-March 09

Posted 30 March 2009 - 10:24 AM

Nice, I just told my friend about that, He used ur code, edited it a little bit, and it turned out awesome, thanks bro :rolleyes:
Was This Post Helpful? 0
  • +
  • -

#3 Ankurgarg  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 1
  • Joined: 31-March 09

Posted 11 April 2009 - 10:18 AM

can I know how this code can be changed so that two players can play it over the internet? :blink:
Was This Post Helpful? 0
  • +
  • -

#4 alexya  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 1
  • Joined: 27-December 09

Posted 31 December 2009 - 01:01 AM

I've seen this game been made only for a board of 3x3. But I need some help for creating this game for nxn board. And a player can win the game only if there is a sequence of 5 "X" or "O" on the board. Can anybody help me? If u have any ideas please let me know.
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1