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;
}