[four-in-a-row] Replaced Velena based old AI with a new AI.
- From: Mario Wenzel <mariowenzel src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [four-in-a-row] Replaced Velena based old AI with a new AI.
- Date: Wed, 11 Jun 2014 16:54:12 +0000 (UTC)
commit 14b0b68a98c26ca25858f57e06b300a813d5e6ff
Author: Nikhar Agrawal <nikharagrawal2006 gmail com>
Date: Wed Jun 11 16:19:58 2014 +0530
Replaced Velena based old AI with a new AI.
This AI scales according to the difficulty
levels and is beatable. Much more appropriate
for our target audience.
configure.ac | 7 +-
src/Makefile.am | 58 ++-
src/adjmtrx.c | 284 ------------
src/ai.vala | 540 ++++++++++++++++++++++
src/bintree.c | 158 -------
src/con4vals.h | 37 --
src/connect4.c | 403 ----------------
src/connect4.h | 195 --------
src/evaluate.c | 1340 ------------------------------------------------------
src/heurist.c | 611 -------------------------
src/heurist.h | 25 -
src/ia_main.c | 1174 -----------------------------------------------
src/main.c | 15 +-
src/pbsolver.c | 318 -------------
src/playgame.c | 103 -----
src/pnsearch.h | 89 ----
src/proto.h | 59 ---
src/test-ai.vala | 219 +++++++++
18 files changed, 814 insertions(+), 4821 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 5afee38..4427c0b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -7,6 +7,7 @@ AM_MAINTAINER_MODE
GNOME_MAINTAINER_MODE_DEFINES
AC_CONFIG_HEADERS([config.h])
+AM_PROG_VALAC([0.22])
AM_PROG_CC_C_O
GLIB_GSETTINGS
@@ -23,12 +24,16 @@ PKG_CHECK_MODULES(FOUR_IN_A_ROW, [
gtk+-3.0 >= $GTK_REQUIRED
librsvg-2.0 >= $RSVG_REQUIRED
libcanberra-gtk3 >= $CANBERRA_GTK_REQUIRED
- zlib
])
AC_PATH_PROG([APPDATA_VALIDATE], [appdata-validate], [/bin/true])
AC_PATH_PROG([DESKTOP_FILE_VALIDATE], [desktop-file-validate], [/bin/true])
+AC_PATH_PROG([GTESTER], [gtester])
+if ! test -x "${GTESTER}" ; then
+ AC_MSG_ERROR([Please install gtester])
+fi
+
dnl ###########################################################################
dnl Internationalization
dnl ###########################################################################
diff --git a/src/Makefile.am b/src/Makefile.am
index 2cd8ff9..4b25d91 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,5 +1,7 @@
bin_PROGRAMS = four-in-a-row
+check_PROGRAMS = ai-test
+
four_in_a_row_SOURCES = main.h \
main.c \
gfx.h \
@@ -8,30 +10,18 @@ four_in_a_row_SOURCES = main.h \
prefs.c \
theme.c \
theme.h \
- adjmtrx.c \
- bintree.c \
- con4vals.h \
- connect4.h \
- connect4.c \
- evaluate.c \
games-controls.c \
games-controls.h \
games-gridframe.c \
games-gridframe.h \
games-stock.c \
games-stock.h \
- heurist.h \
- heurist.c \
- ia_main.c \
- pbsolver.c \
- playgame.c \
- pnsearch.h \
- proto.h \
- rules.h
+ rules.h \
+ $(ai_C) \
+ $(ai_HEADER)
four_in_a_row_CPPFLAGS = \
- -I$(top_srcdir) \
- $(AM_CPPFLAGS)
+ -I$(top_srcdir)
four_in_a_row_CFLAGS = \
-DDATA_DIRECTORY=\"$(datadir)/four-in-a-row\" \
@@ -42,6 +32,40 @@ four_in_a_row_CFLAGS = \
four_in_a_row_LDADD = \
$(FOUR_IN_A_ROW_LIBS)
- -lz
+
+ai_VALA = ai.vala
+
+ai_HEADER = ai.h
+
+ai_C = $(ai_VALA:.vala=.c)
+
+$(ai_HEADER): $(ai_VALA)
+ $(V_VALAC) $(VALAC) -C --basedir=$(srcdir) --directory=$(builddir) --header=ai.h \
+ --pkg glib-2.0 --pkg gio-2.0 --pkg posix --use-header $(VALAFLAGS) $^
+
+$(ai_C) : $(ai_HEADER)
+
+ai_test_SOURCES = \
+ ai.vala \
+ test-ai.vala
+
+ai_test_CPPFLAGS = \
+ $(FOUR_IN_A_ROW_CFLAGS)
+
+ai_test_LDADD = \
+ $(FOUR_IN_A_ROW_LIBS)
+
+check-local: $(check_PROGRAMS)
+ $(GTESTER) --keep-going --verbose $(check_PROGRAMS)
+
+BUILT_SOURCES = \
+ $(ai_C) \
+ $(ai_HEADER)
+
+EXTRA_DIST = $(BUILT_SOURCES)
+
+MAINTAINERCLEANFILES = \
+ $(BUILT_SOURCES) \
+ four_in_a_row_vala.stamp
-include $(top_srcdir)/git.mk
diff --git a/src/ai.vala b/src/ai.vala
new file mode 100644
index 0000000..247f474
--- /dev/null
+++ b/src/ai.vala
@@ -0,0 +1,540 @@
+/* -*- Mode: vala; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * Copyright © 2014 Nikhar Agrawal
+ *
+ * This file is part of Four-in-a-row.
+ *
+ * Four-in-a-row is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Four-in-a-row is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Four-in-a-row. If not, see <http://www.gnu.org/licenses/>.*/
+
+
+/*Here NEG_INF is supposed to be the lowest possible int value. int.MIN
+MAX_HEURIST_VALUE is the maximum value that the heuristic functions can return.
+It is returned when AI wins. -1*MAX_HEURIST_VALUE is returned when Human wins
+MAX_HEURIST_VALUE < NEG_INF/plies */
+const int NEG_INF = -100000;
+const int MAX_HEURIST_VALUE = 10000;
+const int BOARD_ROWS = 6;
+const int BOARD_COLUMNS = 7;
+enum Player { NONE, HUMAN, AI;}
+enum Difficulty {EASY, MEDIUM, HARD;}
+
+int playgame (string moves_until_now)
+{
+ var t = new DecisionTree();
+ return t.playgame (moves_until_now);
+}
+
+public class DecisionTree
+{
+ /* to mantain the status of the board, to be used by the heuristic function, the top left cell is 0,0
*/
+ private Player[,] board = new Player [BOARD_ROWS, BOARD_COLUMNS];
+ /* plies - depth of the DecisionTree*/
+ private int plies = 8;
+ /* last_moving_player - The player who made the last move, set to Player.NONE if no one has made a
move yet*/
+ private Player last_moving_player = Player.NONE;
+ /* next_move_in_column - stores the column number in which next move is to be made*/
+ private int next_move_in_column = -1;
+ /* stores the difficulty level of the game*/
+ private Difficulty level;
+
+ /* Initializes an empty board*/
+ public DecisionTree ()
+ {
+ for (int i=0; i < BOARD_ROWS; i++)
+ {
+ for (int j=0; j < BOARD_COLUMNS; j++)
+ {
+ board[i,j] = Player.NONE;
+ }
+ }
+ }
+
+ /* utility function for debugging purposes, prints a snapshot of current status of the board*/
+ public void print_board ()
+ {
+ for (int i=0; i< BOARD_ROWS; i++)
+ {
+ for (int j = 0; j < BOARD_COLUMNS; j++)
+ {
+ stdout.printf("%d\t",board[i,j]);
+ }
+ stdout.printf("\n");
+ }
+ stdout.printf("\n");
+ }
+
+ /* Recursively implements a negamax tree in memory with alpha-beta pruning. The function is first
called for the root node.
+ It returns the value of the current node. For nodes at height == 0, the value is determined by a
heuristic function.*/
+ private int negamax (int height, int alpha, int beta)
+ {
+
+ /* base case of recursive function , returns if we have reached the lowest depth of
DecisionTree or the board if full*/
+ if (height == 0 || board_full())
+ {
+ if (last_moving_player == Player.HUMAN)
+ return heurist();
+ else if (last_moving_player == Player.AI)
+ return -1 * heurist();
+ else
+ return 0;
+ }
+
+ /* Local variable that stores the maximum value returned by the node's children so far.
+ None of the children have returned anything so far. Hence, it is initialized with NEG_INF*/
+ int max = NEG_INF;
+
+ /* Local variable that stores the column number in which next move is to be made.
+ Initialized with -1 because we do not know the column number yet.*/
+ int next = -1;
+
+ for (int i=0; i < BOARD_COLUMNS; i++)
+ {
+ /* make a move into the i'th column*/
+ if (move(i))
+ {
+ int temp;
+
+ /* victory() checks if making a move in the i'th column results in a victory
for someone. */
+ /* Multiply by a height factor to avoid closer threats first */
+ if (last_moving_player == Player.HUMAN)
+ temp = -1 * victory(i) * height;
+ else
+ temp = victory(i) * height;
+
+ /* if making a move in this column resulted in a victory for someone,
temp!=0, we do not need to go
+ further down the negamax tree*/
+ if (temp == 0)
+ temp = -1 * negamax(height - 1, -1 * beta, -1 * alpha);
+
+ unmove(i);
+
+ if (temp > max)
+ {
+ next = i;
+ max = temp;
+ }
+
+
+ if (temp > alpha)
+ {
+ alpha = temp;
+ }
+
+
+ if (alpha >= beta)
+ {
+ break;
+ }
+
+ }
+ }
+
+ /* If it's the root node, asign the value of next to next_move_in_column */
+ if (height == plies)
+ next_move_in_column = next;
+
+
+ return max;
+ }
+
+ /* returns MAX_HEURIST_VALUE if AI has won as a result of the last move made, NEG_INF if HUMAN has
won, 0 if no one has won
+ the game yet. The argument i is the column in which the last move was made. */
+ private int victory (int i)
+ {
+ int cell;
+
+ /* board[cell,i] would now be the cell on which the last move was made */
+ for (cell = 0; cell < BOARD_ROWS && board[cell,i] == Player.NONE; cell++);
+
+ int temp = 0;
+
+ temp = vertical_win(cell,i);
+ if (temp != 0) return temp;
+
+ temp = horizontal_win(cell,i);
+ if (temp != 0) return temp;
+
+ temp = backward_diagonal_win(cell,i);
+ if (temp != 0) return temp;
+
+ temp = forward_diagonal_win(cell,i);
+ return temp;
+
+ }
+
+ /* all the win functions that follow return 0 is no one wins, MAX_HEURIST_VALUE if AI wins, -1 *
MAX_HEURIST_VALUE if human wins */
+ private int forward_diagonal_win (int i, int j)
+ {
+ int count = 0;
+
+ for (int k = i, l = j; k >= 0 && l < BOARD_COLUMNS && board[k,l] == last_moving_player; k--,
l++, count++);
+ for (int k = i + 1, l = j - 1; k < BOARD_ROWS && l >= 0 && board[k,l] == last_moving_player;
k++, l--, count++);
+
+ if (count >= 4)
+ {
+ if (last_moving_player == Player.HUMAN)
+ return -1 * MAX_HEURIST_VALUE;
+ else
+ return MAX_HEURIST_VALUE;
+ }
+
+ return 0;
+ }
+
+ private int backward_diagonal_win (int i, int j)
+ {
+ int count = 0;
+
+ for (int k = i, l = j; k >= 0 && l >= 0 && board[k,l] == last_moving_player; k--, l--,
count++);
+ for (int k = i + 1, l = j + 1; k < BOARD_ROWS && l < BOARD_COLUMNS && board[k,l] ==
last_moving_player; k++, l++, count++);
+
+ if (count >= 4)
+ {
+ if (last_moving_player == Player.HUMAN)
+ return -1 * MAX_HEURIST_VALUE;
+ else
+ return MAX_HEURIST_VALUE;
+ }
+
+ return 0;
+ }
+
+ private int horizontal_win (int i, int j)
+ {
+ int count = 0;
+
+ for (int k = j; k >= 0 && board[i,k] == last_moving_player; k--, count++);
+ for (int k = j+1; k < BOARD_COLUMNS && board[i,k] == last_moving_player; k++, count++);
+
+ if (count >= 4)
+ {
+ if (last_moving_player == Player.HUMAN)
+ return -1 * MAX_HEURIST_VALUE;
+ else
+ return MAX_HEURIST_VALUE;
+ }
+
+ return 0;
+ }
+
+ private int vertical_win (int i, int j)
+ {
+ int count = 0;
+
+ for (int k = i; k < BOARD_ROWS && board[k,j] == last_moving_player; k++ , count++);
+
+ if (count >= 4)
+ {
+ if (last_moving_player == Player.HUMAN)
+ return -1 * MAX_HEURIST_VALUE;
+ else
+ return MAX_HEURIST_VALUE;
+ }
+
+ return 0;
+ }
+
+ /*returns true if the board is full, false if not*/
+ private bool board_full ()
+ {
+ bool empty = false;
+ for (int i = 0 ; i < BOARD_COLUMNS ; i++)
+ {
+ if (board[0,i] == Player.NONE)
+ {
+ empty = true;
+ break;
+ }
+ }
+ return !empty;
+ }
+
+ /* makes a move into the i'th column. Returns true if the move was succesful, false if it wasn't*/
+ private bool move (int i)
+ {
+ int cell;
+
+ for(cell = BOARD_ROWS - 1; cell >= 0 && board[cell,i] != Player.NONE; cell -= 1);
+
+ if(cell < 0)
+ return false;
+
+ /*if it is AI's first move or the last move was made by human */
+ if (last_moving_player == Player.NONE || last_moving_player == Player.HUMAN)
+ {
+ board[cell,i] = Player.AI;
+ last_moving_player = Player.AI;
+ }
+ else
+ {
+ board[cell,i] = Player.HUMAN;
+ last_moving_player = Player.HUMAN;
+ }
+
+ return true;
+ }
+
+ /* unmove the last move made in the i'th column.*/
+ private void unmove (int i)
+ {
+ int cell;
+
+ for (cell = BOARD_ROWS - 1; cell >= 0 && board[cell,i] != Player.NONE; cell -= 1);
+
+ board[cell + 1,i] = Player.NONE;
+
+ if (last_moving_player == Player.AI)
+ last_moving_player = Player.HUMAN;
+ else if (last_moving_player == Player.HUMAN)
+ last_moving_player = Player.AI;
+
+ }
+
+ /* vstr is the sequence of moves made until now. We update DecisionTree::board to reflect these
sequence of moves.*/
+ public void update_board (string vstr)
+ {
+ next_move_in_column = -1;
+
+ /* AI will make the first move, nothing to add to the board*/
+ if (vstr.length == 2) return;
+
+ Player move;
+
+ if (vstr.length % 2 == 0)
+ move = Player.AI;
+ else
+ move = Player.HUMAN;
+
+ for (int i = 1; i < vstr.length - 1; i++)
+ {
+ int column = int.parse(vstr[i].to_string()) -1;
+
+ int cell;
+
+ for (cell = BOARD_ROWS - 1; cell >= 0 && board[cell,column] != Player.NONE; cell -=
1);
+
+ board[cell, column] = move;
+
+ if (move == Player.HUMAN)
+ move = Player.AI;
+ else
+ move = Player.HUMAN;
+ }
+
+ last_moving_player = Player.HUMAN;
+ }
+
+ /* check for immediate win of AI/HUMAN. It checks the current state of the board. Returns -1 if no
immediate win for Player P.
+ Otherwise returns the column number in which Player P should move to win.*/
+ private int immediate_win (Player p)
+ {
+ Player old_last_moving_player = last_moving_player;
+
+ if (p == Player.AI)
+ last_moving_player = Player.HUMAN;
+ else
+ last_moving_player = Player.AI;
+
+ for (int i = 0; i < BOARD_COLUMNS; i++)
+ {
+ if (move(i))
+ {
+ if (victory(i) != 0)
+ {
+ unmove(i);
+ last_moving_player = old_last_moving_player;
+ return i;
+ }
+
+ unmove(i);
+ }
+ }
+
+ last_moving_player = old_last_moving_player;
+
+ /* returns -1 if no immediate win for Player p*/
+ return -1;
+ }
+
+ /* returns the column number in which the next move has to be made. Returns -1 if the board is full.
*/
+ public int playgame (string vstr)
+ {
+ /* set the Difficulty level */
+ set_level(vstr);
+
+ /* update DecisionTree::board to reflect the moves made until now*/
+ update_board(vstr);
+
+ /* check if AI can win by making a move immediately*/
+ int temp = immediate_win (Player.AI);
+
+ /* if the AI can win, then make that move. main.c has indexing beginning from 1 instead of 0,
hence, we add 1.*/
+ if (temp != -1)
+ return temp + 1;
+
+ /*check if HUMAN can win by making a move immediately*/
+ temp = immediate_win (Player.HUMAN);
+
+ /* if the HUMAN can win, we make AI move in that column so as avoid loss.*/
+ if (temp != -1)
+ return temp + 1;
+
+ /*call negamax tree on the current state of the board.*/
+ negamax(plies, NEG_INF, -1 * NEG_INF);
+
+ /* return the column number in which next move should be made.*/
+ return next_move_in_column + 1;
+
+ }
+
+ /* The evaluation function to be called when we have reached the maximum depth in the DecisionTree*/
+ private int heurist ()
+ {
+ if (level == Difficulty.EASY)
+ return heurist_easy();
+
+ else if (level == Difficulty.MEDIUM)
+ return heurist_medium();
+
+ else
+ return heurist_hard();
+
+ }
+
+ private int heurist_easy ()
+ {
+ return -1 * heurist_hard();
+ }
+
+ private int heurist_medium ()
+ {
+ return Random.int_range(1,49);
+ }
+
+ private int heurist_hard ()
+ {
+ int count = 0;
+
+ count = count_3_in_a_row(Player.AI);
+
+ count -= count_3_in_a_row(Player.HUMAN);
+
+ count = count*100;
+
+ if (count == 0)
+ count = Random.int_range(1,49);
+
+ return count;
+ }
+
+ /* Count the number of threes in a row for Player P. It counts all those 3 which have an empty cell
in the vicinity to make it
+ four in a row*/
+ private int count_3_in_a_row (Player p)
+ {
+ int count = 0;
+
+ Player old_last_moving_player = last_moving_player;
+
+ last_moving_player = p;
+
+ for (int j = 0; j < BOARD_COLUMNS; j++)
+ {
+ for (int i = 0; i < BOARD_ROWS; i++)
+ {
+ if (board[i,j] != Player.NONE)
+ break;
+
+ if (all_adjacent_empty(i,j))
+ continue;
+
+ board[i,j] = p;
+
+ if (victory(j) != 0)
+ count++;
+
+ board[i,j] = Player.NONE;
+
+ }
+ }
+ last_moving_player = old_last_moving_player;
+ return count;
+ }
+
+ /* checks if all adjacent cells to board[i,j] are empty*/
+ private bool all_adjacent_empty (int i, int j)
+ {
+ for (int k = -1 ; k <= 1; k++)
+ {
+ for (int l = -1; l <=1; l++)
+ {
+ if (k == 0 && l == 0)
+ continue;
+ if ((i+k) >= 0 && (i+k) < BOARD_ROWS && (j+l) >= 0 && (j+l) < BOARD_COLUMNS)
+ {
+ if(board[i+k,j+l] != Player.NONE)
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /* set the number of plies and the difficulty level*/
+ private void set_level (string vstr)
+ {
+
+ if (vstr[0] == 'a')
+ {
+ level = Difficulty.EASY;
+ plies = 4;
+ }
+ else if (vstr[0] == 'b')
+ {
+ level = Difficulty.MEDIUM;
+ plies = 8;
+ }
+ else
+ {
+ level = Difficulty.HARD;
+ plies = 8;
+ }
+ }
+
+ /* utility function for testing purposes*/
+ public int playandcheck (string vstr)
+ {
+ set_level(vstr);
+
+ update_board(vstr);
+
+ int temp = immediate_win (Player.AI);
+
+ if (temp != -1)
+ {
+ return 1000;
+ }
+
+ temp = immediate_win (Player.HUMAN);
+
+ if (temp != -1)
+ return temp + 1;
+
+ negamax(plies, NEG_INF, -1 * NEG_INF);
+
+ return next_move_in_column + 1;
+
+ }
+}
+
diff --git a/src/main.c b/src/main.c
index ddc6234..ff171f7 100644
--- a/src/main.c
+++ b/src/main.c
@@ -29,7 +29,7 @@
#include <gtk/gtk.h>
#include <canberra-gtk.h>
-#include "connect4.h"
+#include "ai.h"
#include "main.h"
#include "theme.h"
#include "prefs.h"
@@ -100,6 +100,9 @@ static void process_move2 (gint c);
static void process_move3 (gint c);
+
+
+
static void
clear_board (void)
{
@@ -370,7 +373,6 @@ static void
game_init (void)
{
g_random_set_seed ((guint) time (NULL));
- vboard = veleng_init ();
anim = ANIM_NONE;
gameover = TRUE;
@@ -420,7 +422,7 @@ game_reset (void)
} else {
vstr[0] = vlevel[p.level[PLAYER2]];
}
- game_process_move (playgame (vstr, vboard) - 1);
+ game_process_move (playgame (vstr) - 1);
}
}
@@ -429,7 +431,6 @@ game_reset (void)
static void
game_free (void)
{
- veleng_free (vboard);
gfx_free ();
}
@@ -632,7 +633,7 @@ on_game_hint (GSimpleAction *action, GVariant *parameter, gpointer data)
set_status_message (_("I’m Thinking…"));
vstr[0] = vlevel[LEVEL_STRONG];
- c = playgame (vstr, vboard) - 1;
+ c = playgame (vstr) - 1;
column_moveto = c;
while (timeout)
@@ -1077,7 +1078,7 @@ process_move3 (gint c)
} else {
vstr[0] = vlevel[p.level[PLAYER2]];
}
- c = playgame (vstr, vboard) - 1;
+ c = playgame (vstr) - 1;
if (c < 0)
gameover = TRUE;
g_timeout_add (SPEED_DROP,
@@ -1311,7 +1312,7 @@ main (int argc, char *argv[])
g_error_free (error);
exit (1);
}
-
+
settings = g_settings_new ("org.gnome.four-in-a-row");
g_set_application_name (_(APPNAME_LONG));
diff --git a/src/test-ai.vala b/src/test-ai.vala
new file mode 100644
index 0000000..2b50b89
--- /dev/null
+++ b/src/test-ai.vala
@@ -0,0 +1,219 @@
+/* -*- Mode: vala; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * Copyright © 2014 Michael Catanzaro
+ * Copyright © 2014 Nikhar Agrawal
+ *
+ * This file is part of Four-in-a-row.
+ *
+ * Four-in-a-row is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Four-in-a-row is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Four-in-a-row. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+const int NUMBER_GAMES = 5;
+
+/* Tests if the AI makes moves so as to take up immediate horizontal wins. The argument to playgame function
is the sequence of moves
+ made until now. The return value of playgame function is the column number in which the AI should move.*/
+private void test_horizontal_win ()
+{
+ /*In the first statement below, the AI has made moves into the 1st, 2nd and 3rd columns. To win, AI must
move in the 4th column.*/
+ assert (playgame ("a1727370") == 4);
+ assert (playgame ("a7315651311324420") == 6);
+ assert (playgame ("a232225657223561611133440") == 4);
+ assert (playgame ("a242215322574255543341746677453337710") == 1);
+}
+
+/* Tests if the AI makes moves so as to take up immediate vertical wins.*/
+private void test_vertical_win ()
+{
+ assert (playgame ("a1213140") == 1);
+ assert (playgame ("a14456535526613130") == 1);
+ assert (playgame ("a432334277752576710") == 7);
+ assert (playgame ("a547477454544323321712116260") == 2);
+}
+
+/* Tests if the AI makes moves so as to take up immediate forward diagonal wins.*/
+private void test_forward_diagonal_win ()
+{
+ assert (playgame ("a54221164712446211622157570") == 7);
+ assert (playgame ("a4256424426621271412117175776343330") == 3);
+ assert (playgame ("a132565522322662666775443351131113540") == 4);
+ assert (playgame ("a4571311334541225544112245262577767733360") == 6);
+}
+
+/* Tests if the AI makes moves so as to take up immediate backward diagonal wins.*/
+private void test_backward_diagonal_win ()
+{
+ assert (playgame ("5422327343142110") == 1);
+ assert (playgame ("a1415113315143220") == 2);
+ assert (playgame ("a547323452213345110") == 1);
+ assert (playgame ("a4256424426621271412117175776343330") == 3);
+}
+
+/* Tests if the AI makes moves which prevents HUMAN from taking immediate vertical victories. Consider that
a HUMAN has 3 balls in the
+ first column. The AI's next move should be in the 1st column or else, HUMAN will claim victory on his
next turn.*/
+private void test_avoid_vertical_loss ()
+{
+ assert (playgame ("a42563117273430") == 3);
+ assert (playgame ("a3642571541322340") == 4);
+ assert (playgame ("a144566264475171137750") == 5);
+ assert (playgame ("a54747745454432332171210") == 1);
+}
+
+/* Tests if the AI makes moves which prevents HUMAN from taking immediate forward diagonal victories*/
+private void test_avoid_forward_diagonal_loss ()
+{
+ assert (playgame ("a34256477331566570") == 7);
+ assert (playgame ("a1445662644751711370") == 7);
+ assert (playgame ("a43442235372115113340") == 4);
+ assert (playgame ("a4143525567766443543125411170") == 7);
+}
+
+/* Tests if the AI makes moves which prevents HUMAN from taking immediate backward diagonal victories*/
+private void test_avoid_backward_diagonal_loss ()
+{
+ assert (playgame ("a47465234222530") == 3);
+ assert (playgame ("a4344223537211510") == 1);
+ assert (playgame ("a4141311525513520") == 2);
+ assert (playgame ("a1445662644751711377553330") == 3);
+
+}
+
+/* Tests if the AI makes moves which prevents HUMAN from taking immediate horizontal victories*/
+private void test_avoid_horizontal_loss ()
+{
+ assert (playgame ("a445360") == 7);
+ assert (playgame ("a745534131117114777720") == 2);
+ assert (playgame ("a243466431217112323350") == 5);
+ assert (playgame ("a24147356465355111336631615240") == 4);
+}
+
+/* Tests if AI can detect full boards, and thus draw games.*/
+private void test_draw ()
+{
+ assert (playgame ("a1311313113652226667224247766737374455445550") == 0);
+ assert (playgame ("a6121151135432322433425566474425617635677770") == 0);
+ assert (playgame ("a4226111412113275256335534443264375577676670") == 0);
+ assert (playgame ("a4212116575717754775221133434432366655342660") == 0);
+}
+
+/* Tests if AI makes valid moves, i.e., between column 1 and column 7*/
+private void test_random ()
+{
+ int x = playgame ("a443256214350");
+ assert (x >= 1 && x <= 7);
+
+ x = playgame ("a241473564653551113366316150");
+ assert (x >= 1 && x <= 7);
+
+ x = playgame ("a24357315461711177416622623350");
+ assert (x >= 1 && x <= 7);
+
+ x = playgame ("a1445662644751711377553333665775446110");
+ assert (x >= 1 && x <= 7);
+}
+
+/* Pits two AI's of varying difficulty levels against each other and returns the number of games won by
easier AI.*/
+private int test_ai_vs_ai (string easier, string harder)
+{
+ int easier_wins = 0;
+ int draw = 0;
+ int harder_wins = 0;
+
+ for (int i = 0; i < NUMBER_GAMES;i++)
+ {
+ var e = new StringBuilder();
+ e.append(easier);
+
+ var m = new StringBuilder();
+ m.append(harder);
+
+ while (true)
+ {
+ int move;
+ DecisionTree t = new DecisionTree();
+ move = t.playandcheck(e.str);
+ if (move == 0)
+ {
+ draw++;
+ break;
+ }
+
+ if (move == 1000)
+ {
+ easier_wins++;
+ break;
+ }
+
+ e.insert(e.str.length - 1, move.to_string());
+ m.insert(m.str.length - 1, move.to_string());
+
+ DecisionTree d = new DecisionTree();
+ move = d.playandcheck(m.str);
+
+ if (move == 0)
+ {
+ draw++;
+ break;
+ }
+
+ if (move == 1000)
+ {
+ harder_wins++;
+ break;
+ }
+ e.insert(e.str.length - 1, move.to_string());
+ m.insert(m.str.length - 1, move.to_string());
+ }
+ }
+ return easier_wins;
+}
+
+private void test_easy_vs_medium ()
+{
+ int easy_wins = test_ai_vs_ai ("a0","b0");
+
+ assert (easy_wins <= NUMBER_GAMES/2);
+}
+
+private void test_easy_vs_hard ()
+{
+ int easy_wins = test_ai_vs_ai ("a0","c0");
+
+ assert (easy_wins <= NUMBER_GAMES/2);
+}
+
+private void test_medium_vs_hard ()
+{
+ int medium_wins = test_ai_vs_ai ("b0","c0");
+
+ assert (medium_wins <= NUMBER_GAMES/2);
+}
+
+public int main (string[] args)
+{
+ Test.init (ref args);
+ Test.add_func ("/AI/Take Win/Horizontal Win", test_horizontal_win);
+ Test.add_func ("/AI/Take Win/Vertical Win", test_vertical_win);
+ Test.add_func ("/AI/Take Win/Forward Diagonal Win", test_forward_diagonal_win);
+ Test.add_func ("/AI/Take Win/Backward Diagonal Win", test_backward_diagonal_win);
+ Test.add_func ("/AI/Avoid Loss/Horizontal Loss", test_avoid_horizontal_loss);
+ Test.add_func ("/AI/Avoid Loss/Vertical Loss", test_avoid_vertical_loss);
+ Test.add_func ("/AI/Avoid Loss/Forward Diagonal Loss", test_avoid_forward_diagonal_loss);
+ Test.add_func ("/AI/Avoid Loss/Backward Diagonal Loss", test_avoid_backward_diagonal_loss);
+ Test.add_func ("/AI/AI vs AI/Easy vs Medium", test_easy_vs_medium);
+ Test.add_func ("/AI/AI vs AI/Easy vs Hard", test_easy_vs_hard);
+ Test.add_func ("/AI/AI vs AI/Medium vs Hard", test_medium_vs_hard);
+ Test.add_func ("/AI/Draw", test_draw);
+ Test.add_func ("/AI/Random", test_random);
+ return Test.run ();
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]