Code:
/*
all : ttt
ttt : ttt.c
gcc -lncurses -o ttt ttt.c
test : ttt
./ttt
win : ttt
echo 72386 | ./ttt
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <curses.h>
typedef enum { PLAYER_NONE, PLAYER_X, PLAYER_O, DRAW } E_PLAYERS;
typedef struct _FIELDPOS
{
WINDOW *wnd;
struct
{
int x;
int y;
} start, end;
} FIELDPOS;
#define GETPLAYER(state, fieldid) (((state) >> (2 * fieldid)) & 3)
#define SETPLAYER(state, fieldid, player) state = ((state) & ~(3 << (2 * (fieldid)))) | ((player) << (2 * (fieldid)))
#define OTHERPLAYER(player) (((player) == PLAYER_X) ? PLAYER_O : PLAYER_X)
unsigned int checkwin_player(unsigned int state, E_PLAYERS player)
{
unsigned int winarr[] = {
// horizontally
7, 56, 448,
// vertically
73, 146, 292,
// across
84, 273
};
int i;
unsigned int checkstate = 0;
for(i = 0; i < 9; i++)
if (GETPLAYER(state, i) == player)
checkstate |= 1 << i;
for(i = 0; i < sizeof(winarr) / sizeof(winarr[1]); i++)
if ((checkstate & winarr[i]) == winarr[i])
return winarr[i];
return 0;
}
E_PLAYERS checkwin(unsigned int state)
{
int i;
if (checkwin_player(state, PLAYER_X))
return PLAYER_X;
if (checkwin_player(state, PLAYER_O))
return PLAYER_O;
for(i = 0; i < 9; i++)
if (GETPLAYER(state, i) == PLAYER_NONE)
return PLAYER_NONE;
return DRAW;
}
unsigned int ttt_ai_move(unsigned int state, E_PLAYERS ai)
{
int i;
unsigned int highest_priority;
unsigned int pos = 4;
unsigned int prioritygrid[9];
int winmoves[][8] = {
/* 0 */ {1, 2, 3, 6, 4, 8,-1,-1},
/* 1 */ {0, 2, 4, 8,-1,-1,-1,-1},
/* 2 */ {0, 1, 5, 8, 5, 6,-1,-1},
/* 3 */ {0, 6, 4, 5,-1,-1,-1,-1},
/* 4 */ {0, 1, 2, 3, 5, 6, 7, 8},
/* 5 */ {3, 4, 2, 8,-1,-1,-1,-1},
/* 6 */ {0, 3, 7, 8, 4, 2,-1,-1},
/* 7 */ {6, 8, 1, 4,-1,-1,-1,-1},
/* 8 */ {6, 7, 2, 5, 0, 4,-1,-1}
};
memset(prioritygrid, 0, sizeof(prioritygrid));
// we prefer the middle
prioritygrid[4] = 1;
// detect one-step wins
for(i = 0; i < 9; i++)
{
if (GETPLAYER(state, i) == PLAYER_NONE)
{
SETPLAYER(state, i, ai);
if (checkwin_player(state, ai))
return i;
SETPLAYER(state, i, PLAYER_NONE);
}
}
// if the other play may win with his/her next move the
// AI will try to avert this
for(i = 0; i < 9; i++)
{
if (GETPLAYER(state, i) == PLAYER_NONE)
{
SETPLAYER(state, i, OTHERPLAYER(ai));
if (checkwin_player(state, OTHERPLAYER(ai)))
return i;
SETPLAYER(state, i, PLAYER_NONE);
}
}
// create priority grid
for(i = 0; i < 9; i++)
{
if (GETPLAYER(state, i) != PLAYER_NONE)
{
int j;
for(j = 0; j < 8 && winmoves[i][j] != -1; j++)
{
if (GETPLAYER(state, winmoves[i][j]) == PLAYER_NONE)
prioritygrid[winmoves[i][j]] += 2;
}
}
}
// find best move
for(i = 0, highest_priority = 0; i < 9; i++)
{
if (GETPLAYER(state, i) == PLAYER_NONE && highest_priority < prioritygrid[i])
{
highest_priority = prioritygrid[i];
pos = i;
}
}
return pos;
}
int isgameover(unsigned int state, E_PLAYERS theplayer, WINDOW *info, FIELDPOS fieldpositions[9])
{
unsigned int fieldid;
E_PLAYERS winner;
if ((winner = checkwin(state)) != PLAYER_NONE)
{
int i;
char *winstr;
if (winner == DRAW)
{
for(i = 0; i < 9; i++)
{
wbkgd(fieldpositions[i].wnd, COLOR_PAIR(3));
wrefresh(fieldpositions[i].wnd);
}
winstr = "Draw.";
}
else if (winner == theplayer)
{
unsigned int winline = checkwin_player(state, theplayer);
for(i = 0; i < 9; i++)
{
if (winline & (1 << i))
{
wbkgd(fieldpositions[i].wnd, COLOR_PAIR(4));
wrefresh(fieldpositions[i].wnd);
}
}
winstr = "You win!";
}
else
{
unsigned int winline = checkwin_player(state, OTHERPLAYER(theplayer));
for(i = 0; i < 9; i++)
{
if (winline & (1 << i))
{
wbkgd(fieldpositions[i].wnd, COLOR_PAIR(3));
wrefresh(fieldpositions[i].wnd);
}
}
winstr = "You fail!";
}
wprintw(info, "\n\n\n----------\n%s\n", winstr);
wrefresh(info);
return 1;
}
return 0;
}
int main(void)
{
FIELDPOS fieldpositions[9];
WINDOW *wnd = 0;
WINDOW *info = 0;
int i;
int gameover = 0;
E_PLAYERS theplayer = PLAYER_X;
E_PLAYERS otherplayer = OTHERPLAYER(PLAYER_X);
int ch;
struct { int x, y; } cursorpos;
unsigned int state = 0;
char *board =
"(---------------X---------------X---------------)\n"
"|0 |1 |2 |\n"
"| | | |\n"
"| | | |\n"
"| | | |\n"
"| | | |\n"
"| 0| 1| 2|\n"
"#---------------+---------------+---------------*\n"
"|3 |4 |5 |\n"
"| | | |\n"
"| | | |\n"
"| | | |\n"
"| | | |\n"
"| 3| 4| 5|\n"
"#---------------+---------------+---------------*\n"
"|6 |7 |8 |\n"
"| | | |\n"
"| | | |\n"
"| | | |\n"
"| | | |\n"
"| 6| 7| 8|\n"
"'---------------U---------------U---------------/\n"
;
char *boardptr;
char *playermarks[4] = {
//---------------
" "
" "
" "
" "
" "
" ",
//---------------
"XXXX XXXX"
" XXXX XXXX "
" XXX "
" XXX "
" XXXX XXXX "
"XXXX XXXX",
//---------------
" OOOOOOOOO "
" OOOO OOOO "
"OO OO"
"OO OO"
" OOOO OOOO "
" OOOOOOOOO ",
//---------------
" "
" "
" state "
" ERROR "
" "
" "
//---------------
};
cursorpos.x = 0;
cursorpos.y = 0;
initscr();
noecho();
start_color();
keypad(stdscr, 1);
//raw();
init_pair(1, COLOR_WHITE, COLOR_BLACK);
init_pair(2, COLOR_WHITE, COLOR_BLUE);
init_pair(3, COLOR_WHITE, COLOR_RED);
init_pair(4, COLOR_WHITE, COLOR_GREEN);
bkgd(COLOR_PAIR(1));
memset(fieldpositions, 0, sizeof(fieldpositions));
// print the board
for(boardptr = board; *boardptr; boardptr++)
{
chtype ch = 0;
switch(*boardptr)
{
case '(': ch = ACS_ULCORNER; break;
case ')': ch = ACS_URCORNER; break;
case '\'': ch = ACS_LLCORNER; break;
case '/': ch = ACS_LRCORNER; break;
case '-': ch = ACS_HLINE; break;
case '|': ch = ACS_VLINE; break;
case 'X': ch = ACS_TTEE; break;
case '+': ch = ACS_PLUS; break;
case '#': ch = ACS_LTEE; break;
case '*': ch = ACS_RTEE; break;
case 'U': ch = ACS_BTEE; break;
case '\n':
if (!info)
{
int x, y;
getyx(stdscr, y, x);
info = newwin(20, 25, y + 1, x + 2);
//wbkgd(info, COLOR_PAIR(2));
}
// no break!
case ' ':
ch = *boardptr;
}
if (*boardptr >= '0' && *boardptr <= '9')
{
FIELDPOS *fpos = &fieldpositions[*boardptr - '0'];
if (fpos->start.x == 0)
{
getyx(stdscr, fpos->start.y, fpos->start.x);
}
else
{
getyx(stdscr, fpos->end.y, fpos->end.x);
fpos->wnd = newwin(
fpos->end.y - fpos->start.y + 1,
fpos->end.x - fpos->start.x + 1,
fpos->start.y,
fpos->start.x
);
}
ch = ' ';
}
if (ch) addch(ch);
}
refresh();
// main loop
do
{
// clear old selection
if (!gameover && wnd)
{
wbkgd(wnd, COLOR_PAIR(1));
wrefresh(wnd);
}
// highlight current selection
if (!gameover)
{
wnd = fieldpositions[3 * cursorpos.y + cursorpos.x].wnd;
wbkgd(wnd, COLOR_PAIR(2));
wrefresh(wnd);
}
// print player marks
for(i = 0; i < 9; i++)
{
int mark = GETPLAYER(state, i);
mvwaddstr(fieldpositions[i].wnd, 0, 0, playermarks[mark]);
wrefresh(fieldpositions[i].wnd);
}
switch(ch = getch())
{
case KEY_UP:
if (cursorpos.y > 0) cursorpos.y--;
break;
case KEY_DOWN:
if (cursorpos.y < 2) cursorpos.y++;
break;
case KEY_LEFT:
if (cursorpos.x > 0) cursorpos.x--;
break;
case KEY_RIGHT:
if (cursorpos.x < 2) cursorpos.x++;
break;
case '0': case '1': case '2':
case '3': case '4': case '5':
case '6': case '7': case '8':
case '\n':
if (checkwin(state) != PLAYER_NONE) break;
if (ch != '\n')
{
ch -= '0';
cursorpos.x = ch % 3;
cursorpos.y = (ch - cursorpos.x) / 3;
}
i = 3 * cursorpos.y + cursorpos.x;
if (GETPLAYER(state, i) == PLAYER_NONE)
{
unsigned int fieldid;
E_PLAYERS winner;
SETPLAYER(state, i, theplayer);
if (isgameover(state, theplayer, info, fieldpositions))
{ gameover = 1; break; }
fieldid = ttt_ai_move(state, otherplayer);
SETPLAYER(state, fieldid, otherplayer);
wprintw(info, "AI -> %d\n", fieldid);
wrefresh(info);
if (isgameover(state, theplayer, info, fieldpositions))
{ gameover = 1; break; }
}
}
}
while(ch != ' ');
endwin();
return 0;
}