Hi,

as a learning exercise for our C++ course at my university we were supposed to program an XX0 in C++

This is my version. It is not yet ready, humans can play again humans but my first AI is still missing some functions.

UPDATE: I re-factored some of the code so it is now multiple files

actor.h

#ifndef _DEF_ACTOR_H_
#define _DEF_ACTOR_H_
#include "gamegrid.h"


class actor{
protected:
    int enemy;
    int who(int field);
public:
    int own;
    virtual MOVE nextMOVE(const gamegrid grid, const int messagecode);
    actor(int own, int enemy);
};
#endif

AI8sections.h

#ifndef _DEF_AI8SECTIONS_H_
#define _DEF_AI8SECTIONS_H_
#include "actor.h"
#include "gamegrid.h"


class AI8section : public actor{
    typedef MOVE(AI8section::*AI8func)(const gamegrid);
    typedef int sumgrid[3][3];
    /*
     *    from wikipedia:
     *    Win: If the player has two in a row, he or she can place a third to get three in a row.
     *    Block: If the [opponent] has two in a row, the player must play the third himself or herself to block them.
     *    Fork: Creation of an opportunity where the player has two threats to win (two non-blocked lines of 2).
     *    Blocking an opponent's fork:
     *        Option 1: The player should create two in a row to force the opponent into defending, as long as it doesn't result in 
     *        them creating a fork. For example, if "X" has a corner, "O" has the center, and "X" has the opposite corner as well, 
     *        "O" must not play a corner in order to win. (Playing a corner in this scenario creates a fork for "X" to win.)
     *
     *        Option 2: If there is a configuration where the opponent can fork, the player should block that fork.
     *    Center: A player marks the center. (If it is the first move of the game, playing on a corner gives "O" more opportunities 
     *    to make a mistake and may therefore be the better choice; however, it makes no difference between perfect players.)
     *    Opposite corner: If the opponent is in the corner, the player plays the opposite corner.
     *    Empty corner: The player plays in a corner square.
     *    Empty side: The player plays in a middle square on any of the 4 sides.
     */
    public:
        AI8section(int own, int enemy);
private:
    MOVE nextMOVE(const gamegrid grid, const int messagecode);
    MOVE Win(const gamegrid grid);
    MOVE Block(const gamegrid grid);
    MOVE Fork(const gamegrid grid);
    MOVE BlockFork(const gamegrid grid);
    MOVE Center(const gamegrid grid);
    MOVE OppCo(const gamegrid grid);
    MOVE EmpCo(const gamegrid grid);
    MOVE EmpSide(const gamegrid grid);
    AI8func steps[8];
    void calcsumgrid(const gamegrid grid, sumgrid sum);
    MOVE findemptyspaceinrowforplayer(const gamegrid grid, const sumgrid sum, int player);
};
#endif

gamegrid.h

#ifndef _DEF_GAMEGRID_H_
#define _DEF_GAMEGRID_H_

enum message{MSGNEXTMOVE, MSGWON, MSGLOST, MSGINVALIDMOVE, MSGDRAW};
enum fields{DRAW=0, J1=16, J1WON=48, J2=256, J2WON=768, NONE=4096};
typedef int gamegrid[3][3];

struct MOVE {
    int x;
    int y;
    MOVE(int x = -1, int y = -1);
    bool operator==(const MOVE rhs);
};

const MOVE EMPTYMOVE;
#endif

gamemaster.h

#ifndef _DEF_GAMEMASTER_H_
#define _DEF_GAMEMASTER_H_
#include "gamegrid.h"
#include "actor.h"
#include <map>


class gamemaster{
public:
    gamemaster(actor* actorJ1, actor* actorJ2);
    bool play();
private:
    int currentplayer;
    int message;
    gamegrid grid;
    std::map<int, actor* > players;
    void emptygrid(gamegrid grid);
    bool validMOVE(MOVE move);
    int evaluategrid();
    int enemy(int player);
};
#endif

human.h

#ifndef _DEF_PLAYER_HUMAN_H_
#define _DEF_PLAYER_HUMAN_H_
#include "actor.h"
#include "gamegrid.h"
#include<string>
#include<sstream>
#include<set>


class human : public actor{
public:
    human(int own, int enemy, std::string ownsymbol, std::string enemysymbol);
    MOVE nextMOVE(const gamegrid grid, const int messagecode);
private:
    std::string ownsymbol;
    std::string enemysymbol;
    MOVE askhuman(const gamegrid grid);
    std::string display(const gamegrid grid);
};
#endif

actor.cpp

#include "gamegrid.h"
#include "actor.h"


MOVE actor::nextMOVE(const gamegrid grid, const int messagecode){
    return EMPTYMOVE;
}

actor::actor(int own, int enemy){
    actor::own = own;
    actor::enemy = enemy;
}

int actor::who(const int field){
    if(field == actor::own){
        return J1;
    } else if (field == actor::enemy){
        return J2;
    } else {
        return field;
    }
}

AI8sections.cpp

#include "AI8sections.h"
#include <iostream>


AI8section::AI8section(int own, int enemy):actor(own, enemy){
    this->steps[0] = &AI8section::Win;
    this->steps[1] = &AI8section::Block;
    this->steps[2] = &AI8section::Fork;
    this->steps[3] = &AI8section::BlockFork;
    this->steps[4] = &AI8section::Center;
    this->steps[5] = &AI8section::OppCo;
    this->steps[6] = &AI8section::EmpCo;
    this->steps[7] = &AI8section::EmpSide;
}

void AI8section::calcsumgrid(const gamegrid grid, sumgrid sum){
    for(int i = 0; i < 3; i++){
        for(int j = 0; j < 3; j++){
            sum[0][i] += grid[i][j];
            sum[1][i] += grid[j][i];
        }
        sum[2][0] += grid[2 - i][i];
        sum[2][1] += grid[i][2 - i];
    }
}

MOVE AI8section::findemptyspaceinrowforplayer(const gamegrid grid, const sumgrid sum, int player){
    for(int i = 0; i < 3; i++){
        for(int j = 0; j < 3; j++){
            if(sum[i][j] == player * 2 + NONE){
                switch(i){
                    case 0:
                        for(int k = 0; k < 3; k++){
                            if(grid[j][k] == NONE){
                                return MOVE(j, k);
                            }
                        }
                    case 1:
                        for(int k = 0; k < 3; k++){
                            if(grid[k][j] == NONE){
                                return MOVE(k, j);
                            }
                        }
                    case 2:
                        for(int k = 0; k < 3; k++){
                            if(grid[2 - k][k] == NONE){
                                return MOVE(2 - k, k);
                            }
                            if(grid[k][2 - k] == NONE){
                                return MOVE(k, 2 - k);
                            }
                        }
                }
            }
        }
    }
    return EMPTYMOVE;
}

MOVE AI8section::nextMOVE(const gamegrid grid, const int messagecode){
    std::cout << "nextMOVE AI init" << std::endl;
    switch(messagecode){
        case MSGINVALIDMOVE:
            std::cout << "AI produced a invalid move" << std::endl;
        case MSGNEXTMOVE:{
            //this is still in for debugging
            std::cout << "nextMOVE" << std::endl;
            MOVE tmp = EMPTYMOVE;
            int i = 0;
            std::cout << "Int i: " << i << std::endl;
            AI8func function;
            while(tmp == EMPTYMOVE){
                tmp = (this->*(AI8section::steps[i++]))(grid);
                std::cout << i << std::endl;
            }
            std::cout << "nextMOVE exited" << std::endl;
            return tmp;}
        default:
            return EMPTYMOVE;
    }
}

MOVE AI8section::Win(const gamegrid grid){
    sumgrid sum = {0};
    this->calcsumgrid(grid, sum);
    return this->findemptyspaceinrowforplayer(grid, sum, this->own);
}
MOVE AI8section::Block(const gamegrid grid){
    sumgrid sum = {0};
    this->calcsumgrid(grid, sum);
    return this->findemptyspaceinrowforplayer(grid, sum, this->enemy);
}

MOVE AI8section::Fork(const gamegrid grid){return EMPTYMOVE;}
MOVE AI8section::BlockFork(const gamegrid grid){return EMPTYMOVE;}

MOVE AI8section::Center(const gamegrid grid){
    if(grid[1][1] == NONE){
        return MOVE(1, 1);
    }
    else{
        return EMPTYMOVE;
    }
}

MOVE AI8section::OppCo(const gamegrid grid){
    if(grid[0][0] + grid[2][2] == this->enemy + NONE){
        if(grid[0][0] == NONE){
            return MOVE(0, 0);
        }else{
            return MOVE(2, 2);
        }
    }else if(grid[2][0] + grid[0][2] == this->enemy + NONE){
        if(grid[2][0] == NONE){
            return MOVE(2, 0);
        }else{
            return MOVE(0, 2);
        }
    }else{
        return EMPTYMOVE;
    }
}

MOVE AI8section::EmpCo(const gamegrid grid){
    if(grid[0][0] == NONE){
        return MOVE(0,0);
    }else if (grid[0][2] == NONE){
        return MOVE(0,2);
    }else if (grid[2][0] == NONE){
        return MOVE(2,0);
    }else if (grid[2][2] == NONE){
        return MOVE(2,2);
    }else{
        return EMPTYMOVE;
    }
}
MOVE AI8section::EmpSide(const gamegrid grid){
    if(grid[0][1] == NONE){
        return MOVE(0,1);
    }else if (grid[1][0] == NONE){
        return MOVE(1,0);
    }else if (grid[1][2] == NONE){
        return MOVE(1,2);
    }else if (grid[2][1] == NONE){
        return MOVE(2,1);
    }else{
        return EMPTYMOVE;
    }
}

gamegrid.cpp

#include "gamegrid.h"

MOVE::MOVE(int x, int y){
    MOVE::x = x;
    MOVE::y = y;
}

bool MOVE::operator==(const MOVE rhs){
    if(MOVE::x == rhs.x && MOVE::y == rhs.y){
        return true;
    }else{
        return false;
    }
}

gamemaster.cpp

#include "gamemaster.h"

int gamemaster::enemy(int player){
    switch(player){
        case J1: return J2;
        case J2: return J1;
        default: return NONE;
    }
}

gamemaster::gamemaster(actor* actorJ1, actor* actorJ2){
    gamemaster::players[J1] = actorJ1;
    gamemaster::players[J2] = actorJ2;
    gamemaster::emptygrid(gamemaster::grid);
    gamemaster::currentplayer = J1;
    gamemaster::message = MSGNEXTMOVE;
}

void gamemaster::emptygrid(gamegrid grid){
    for(int i=0; i < 3; i++){
        for(int j = 0; j < 3; j++){
            grid[i][j] = NONE;
        }
    }
}

bool gamemaster::validMOVE(MOVE move){
    if (gamemaster::grid[move.x][move.y] == NONE){
        return true;
    }
    else{
        return false;
    }
}

int gamemaster::evaluategrid(){
    int summs[9] = {0};
    //summs[0]-summs[5] -> vertical and horizontal
    //summs[6] and summs[7] -> diagonal
    //summs[8] summ over all fields
    for(int i=0; i < 3; i++){
        for(int j = 0; j < 3; j++){
            summs[i*2] += gamemaster::grid[i][j];
            summs[i*2 + 1] += gamemaster::grid[j][i];
        }
        summs[6] += gamemaster::grid[i][i];
        summs[7] += gamemaster::grid[i][2-i];
        summs[8] += summs[i*2];
    }
    int returnvalue = DRAW;
    for(int i = 0; i<9; i++){
        switch(summs[i]){
            case J1WON: return J1WON;
            case J2WON: return J2WON;
        }
        if(NONE <= summs[i]){
            returnvalue = NONE;
        }
    }
    return returnvalue;
}

bool gamemaster::play(){
    MOVE playermove = gamemaster::players[gamemaster::currentplayer]->nextMOVE(gamemaster::grid, gamemaster::message);
    if(gamemaster::validMOVE(playermove)){
        gamemaster::grid[playermove.x][playermove.y] = gamemaster::currentplayer;
    }else{
        gamemaster::message = MSGINVALIDMOVE;
        return true;
    }
    switch(gamemaster::evaluategrid()){
        case DRAW:
            gamemaster::players[J1]->nextMOVE(gamemaster::grid, MSGDRAW);
            gamemaster::players[J2]->nextMOVE(gamemaster::grid, MSGDRAW);
            return false;
        case J1WON:
            gamemaster::players[J1]->nextMOVE(gamemaster::grid, MSGWON);
            gamemaster::players[J2]->nextMOVE(gamemaster::grid, MSGLOST);
            return false;
        case J2WON:
            gamemaster::players[J1]->nextMOVE(gamemaster::grid, MSGLOST);
            gamemaster::players[J2]->nextMOVE(gamemaster::grid, MSGWON);
            return false;
        case NONE:
            gamemaster::currentplayer = gamemaster::enemy(gamemaster::currentplayer);
            gamemaster::message = MSGNEXTMOVE;
            return true;
        default:
            return false;
    }
}

human.cpp

#include "human.h"
#include<iostream>


human::human(int own, int enemy, std::string ownsymbol, std::string enemysymbol):actor(own, enemy){
    human::ownsymbol = ownsymbol;
    human::enemysymbol = enemysymbol;
}

MOVE human::nextMOVE(const gamegrid grid, const int messagecode){
    std::cout << "Player: " << human::ownsymbol << std::endl;
    switch(messagecode){
        case MSGNEXTMOVE:
            return human::askhuman(grid);
        case MSGWON:
            std::cout << "You won!" << std::endl;
            human::display(grid);
            return EMPTYMOVE;
        case MSGLOST:
            std::cout << "You lost!" << std::endl;
            human::display(grid);
            return EMPTYMOVE;
        case MSGINVALIDMOVE:
            std::cout << "You inserted a invalid move!" << std::endl;
            return human::askhuman(grid);
        case MSGDRAW: std::cout << "You entered a draw!" << std::endl;
            human::display(grid);
            return EMPTYMOVE;
        default:
            std::cout << "Unknown error happened" << std::endl;
            return human::askhuman(grid);
    }
}

std::string human::display(const gamegrid grid){
    for(int i = 0; i < 3; i++){
        for(int j = 0; j < 3; j++){
            std::cout << "|";
            if(grid[i][j] == human::own){
                std::cout << human::ownsymbol;
            }
            else if(grid[i][j] == human::enemy){
                std::cout << human::enemysymbol;
            }
            else{
                std::cout << i * 3 + j + 1;
            }
            std::cout << "|";
        }
        std::cout << std::endl;
    }
    std::string userinput;
    std::getline(std::cin,userinput);
    return userinput;
}

MOVE human::askhuman(const gamegrid grid){
    std::set<int> possiblemoves;
    for(int i = 0; i < 3; i++){
        for(int j = 0; j < 3; j++){
            if(grid[i][j] == NONE){
                possiblemoves.insert(i * 3 + j);
            }
        }
    }
    std::string userinput = human::display(grid);
    int movenumber;
    std::stringstream(userinput) >> movenumber;
    movenumber--;
    if (possiblemoves.find(movenumber) == possiblemoves.end()){
        return human::nextMOVE(grid, MSGINVALIDMOVE);
    }
    else{
        int x,y;
        x = movenumber / 3;
        y = movenumber % 3;
        MOVE tmp(x, y);
        return tmp;
    }
}

xxo.cpp

#include "actor.h"
#include "gamegrid.h"
#include "human.h"
#include "AI8sections.h"
#include "gamemaster.h"

using namespace std;

int main(void){
    human humanJ1(J1, J2, "o", "x");
    AI8section AI8sectionJ2(J2, J1);
    actor* playerJ1 = &humanJ1;
    actor* playerJ2 = &AI8sectionJ2;
    gamemaster game(playerJ1, playerJ2);
    while (game.play());
    return 0;
}

Published

Category

snippets

Tags