GitLab repository storage has been migrated to hashed layout. Please contact Infrastructure team if you notice any issues with repositories or hooks.

Commit f31ba67a authored by Arnaud B.'s avatar Arnaud B.

Simplify history strings, part 1.

parent 28a8b459
......@@ -20,8 +20,6 @@
namespace AI
{
private enum Difficulty { EASY, MEDIUM, HARD; }
/* Here NEGATIVE_INFINITY is supposed to be the lowest possible value.
Do not forget int16.MIN ≠ - int16.MAX. */
private const int16 POSITIVE_INFINITY = 32000;
......@@ -39,11 +37,10 @@ namespace AI
\*/
/* returns the column number in which the next move has to be made. Returns uint8.MAX if the board is full. */
internal static uint8 playgame (string vstr)
internal static uint8 playgame (Difficulty level, string vstr)
{
Difficulty level;
Player [,] board;
init_from_string (vstr, out level, out board);
init_board_from_string (vstr, out board);
/* if AI can win by making a move immediately, make that move */
uint8 temp = immediate_win (Player.OPPONENT, ref board);
......@@ -65,11 +62,10 @@ namespace AI
}
/* utility function for testing purposes */
internal static uint8 playandcheck (string vstr)
internal static uint8 playandcheck (Difficulty level, string vstr)
{
Difficulty level;
Player [,] board;
init_from_string (vstr, out level, out board);
init_board_from_string (vstr, out board);
uint8 temp = immediate_win (Player.OPPONENT, ref board);
if (temp < BOARD_COLUMNS)
......@@ -91,12 +87,8 @@ namespace AI
\*/
/* vstr is the sequence of moves made until now; */
private static void init_from_string (string vstr,
out Difficulty level,
out Player [,] board)
private static void init_board_from_string (string vstr, out Player [,] board)
{
set_level (vstr, out level);
/* empty board */
board = new Player [BOARD_ROWS, BOARD_COLUMNS];
for (uint8 i = 0; i < BOARD_ROWS; i++)
......@@ -104,29 +96,18 @@ namespace AI
board [i, j] = Player.NOBODY;
/* AI will make the first move */
if (vstr.length == 2)
if (vstr.length == 1)
return;
/* update board from current string */
update_board (vstr, ref board);
}
private static inline void set_level (string vstr, out Difficulty level)
{
switch (vstr [0])
{
case 'a': level = Difficulty.EASY; return;
case 'b': level = Difficulty.MEDIUM; return;
case 'c': level = Difficulty.HARD; return;
default : assert_not_reached ();
}
}
private static inline void update_board (string vstr, ref Player [,] board)
{
Player move = vstr.length % 2 == 0 ? Player.OPPONENT : Player.HUMAN;
Player move = vstr.length % 2 == 0 ? Player.HUMAN : Player.OPPONENT;
for (uint8 i = 1; i < vstr.length - 1; i++)
for (uint8 i = 0; i < vstr.length - 1; i++)
{
uint8 column = (uint8) int.parse (vstr [i].to_string ()) - 1;
......
......@@ -27,11 +27,9 @@ private class FourInARow : Gtk.Application
/* Translators: application name, as used in the window manager, the window title, the about dialog... */
private const string PROGRAM_NAME = _("Four-in-a-row");
private const uint8 SIZE_VSTR = 53;
private const uint SPEED_BLINK = 150;
private const uint SPEED_MOVE = 35;
private const uint SPEED_DROP = 20;
private const char vlevel [] = { '0','a','b','c' };
private const uint COMPUTER_INITIAL_DELAY = 1200;
private const uint COMPUTER_MOVE_DELAY = 600;
......@@ -50,7 +48,7 @@ private class FourInARow : Gtk.Application
private Player last_first_player = Player.NOBODY;
private Board game_board = new Board ();
private bool one_player_game;
private uint8 ai_level;
private Difficulty ai_level;
/**
* score:
*
......@@ -68,7 +66,7 @@ private class FourInARow : Gtk.Application
private MenuButton history_button_2;
// game state
private char vstr [/* SIZE_VSTR */ 53];
private char [] vstr;
private uint8 moves;
private uint8 column;
private uint8 column_moveto;
......@@ -115,11 +113,15 @@ private class FourInARow : Gtk.Application
return new FourInARow ().run (args);
}
construct
{
vstr = new char [BOARD_ROWS * BOARD_COLUMNS + /* last chars are '0' and '\0', for something and for casting as string */ 2];
clear_board ();
}
private FourInARow ()
{
Object (application_id: "org.gnome.Four-in-a-row", flags: ApplicationFlags.FLAGS_NONE);
clear_board ();
}
protected override void startup ()
......@@ -331,7 +333,13 @@ private class FourInARow : Gtk.Application
player = settings.get_string ("first-player") == "computer" ? Player.OPPONENT : Player.HUMAN;
// we keep inverting that, because it would be surprising that all people use the "next round" thing
settings.set_string ("first-player", player == Player.HUMAN ? "computer" : "human");
ai_level = (uint8) settings.get_int ("opponent");
switch (settings.get_int ("opponent"))
{
case 1 : ai_level = Difficulty.EASY; break;
case 2 : ai_level = Difficulty.MEDIUM; break;
case 3 : ai_level = Difficulty.HARD; break;
default: assert_not_reached ();
}
}
else
switch_players ();
......@@ -353,9 +361,8 @@ private class FourInARow : Gtk.Application
prompt_player ();
if (!is_player_human ())
{
vstr [0] = vlevel [ai_level];
playgame_timeout = Timeout.add (COMPUTER_INITIAL_DELAY, () => {
uint8 c = AI.playgame ((string) vstr);
uint8 c = AI.playgame (ai_level, (string) vstr);
if (c >= BOARD_COLUMNS) // c could be uint8.MAX if board is full
return Source.REMOVE;
process_move ((uint8) c);
......@@ -498,8 +505,9 @@ private class FourInARow : Gtk.Application
{
play_sound (SoundID.DROP);
vstr [++moves] = '1' + (char) c;
vstr [moves + 1] = '0';
vstr [moves] = (char) c /* string indicates columns between 1 and BOARD_COLUMNS */ + '1';
moves++;
vstr [moves] = '0';
check_game_state ();
......@@ -517,8 +525,7 @@ private class FourInARow : Gtk.Application
if (!is_player_human ())
{
playgame_timeout = Timeout.add (COMPUTER_MOVE_DELAY, () => {
vstr [0] = vlevel [ai_level];
uint8 col = AI.playgame ((string) vstr);
uint8 col = AI.playgame (ai_level, (string) vstr);
if (col >= BOARD_COLUMNS) // c could be uint8.MAX if the board is full
set_gameover (true);
var nm = new NextMove ((uint8) col, this);
......@@ -652,13 +659,11 @@ private class FourInARow : Gtk.Application
private void clear_board ()
{
game_board.clear ();
moves = 0;
for (uint8 i = 0; i < SIZE_VSTR; i++)
vstr [0] = '0';
for (uint8 i = 1; i < BOARD_ROWS * BOARD_COLUMNS + 2; i++)
vstr [i] = '\0';
vstr [0] = vlevel [/* weak */ 1];
vstr [1] = '0';
moves = 0;
}
private inline void blink_tile (uint8 row, uint8 col, Player tile, uint8 n)
......@@ -779,8 +784,7 @@ private class FourInARow : Gtk.Application
/* Translators: text *briefly* displayed in the headerbar/actionbar, when a hint is requested */
set_status_message (_("I’m Thinking…"));
vstr [0] = vlevel [/* strong */ 3];
uint8 c = AI.playgame ((string) vstr);
uint8 c = AI.playgame (Difficulty.HARD, (string) vstr);
if (c >= BOARD_COLUMNS)
assert_not_reached (); // c could be uint8.MAX if the board if full
......@@ -807,11 +811,11 @@ private class FourInARow : Gtk.Application
if (timeout != 0)
return;
uint8 c = vstr [moves] - '0' - 1;
moves--;
uint8 c = vstr [moves] - '0' /* string indicates columns between 1 and BOARD_COLUMNS */ - 1;
uint8 r = game_board.first_empty_row (c) + 1;
vstr [moves] = '0';
vstr [moves + 1] = '\0';
moves--;
if (gameover)
{
......@@ -831,11 +835,11 @@ private class FourInARow : Gtk.Application
&& !is_player_human ()
&& moves > 0)
{
c = vstr [moves] - '0' - 1;
moves--;
c = vstr [moves] - '0' /* string indicates columns between 1 and BOARD_COLUMNS */ - 1;
r = game_board.first_empty_row (c) + 1;
vstr [moves] = '0';
vstr [moves + 1] = '\0';
moves--;
swap_player ();
move_cursor (c);
game_board [r, c] = Player.NOBODY;
......
......@@ -30,3 +30,10 @@ private enum Player
HUMAN,
OPPONENT;
}
private enum Difficulty
{
EASY,
MEDIUM,
HARD;
}
......@@ -36,7 +36,7 @@ private int main (string [] args)
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 AI relative ranking
// test AI relative ranking; FIXME I think these tests are crazy
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);
......@@ -52,104 +52,104 @@ private int main (string [] args)
private static inline 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_true (AI.playgame ("a1727370") == 3);
assert_true (AI.playgame ("a7315651311324420") == 5);
assert_true (AI.playgame ("a232225657223561611133440") == 3);
assert_true (AI.playgame ("a242215322574255543341746677453337710") == 0);
assert_true (AI.playgame (Difficulty.EASY, "1727370") == 3);
assert_true (AI.playgame (Difficulty.EASY, "7315651311324420") == 5);
assert_true (AI.playgame (Difficulty.EASY, "232225657223561611133440") == 3);
assert_true (AI.playgame (Difficulty.EASY, "242215322574255543341746677453337710") == 0);
}
/* Tests if the AI makes moves so as to take up immediate vertical wins.*/
private static inline void test_vertical_win ()
{
assert_true (AI.playgame ("a1213140") == 0);
assert_true (AI.playgame ("a14456535526613130") == 0);
assert_true (AI.playgame ("a432334277752576710") == 6);
assert_true (AI.playgame ("a547477454544323321712116260") == 1);
assert_true (AI.playgame (Difficulty.EASY, "1213140") == 0);
assert_true (AI.playgame (Difficulty.EASY, "14456535526613130") == 0);
assert_true (AI.playgame (Difficulty.EASY, "432334277752576710") == 6);
assert_true (AI.playgame (Difficulty.EASY, "547477454544323321712116260") == 1);
}
/* Tests if the AI makes moves so as to take up immediate forward diagonal wins.*/
private static inline void test_forward_diagonal_win ()
{
assert_true (AI.playgame ("a54221164712446211622157570") == 6);
assert_true (AI.playgame ("a4256424426621271412117175776343330") == 2);
assert_true (AI.playgame ("a132565522322662666775443351131113540") == 3);
assert_true (AI.playgame ("a4571311334541225544112245262577767733360") == 5);
assert_true (AI.playgame (Difficulty.EASY, "54221164712446211622157570") == 6);
assert_true (AI.playgame (Difficulty.EASY, "4256424426621271412117175776343330") == 2);
assert_true (AI.playgame (Difficulty.EASY, "132565522322662666775443351131113540") == 3);
assert_true (AI.playgame (Difficulty.EASY, "4571311334541225544112245262577767733360") == 5);
}
/* Tests if the AI makes moves so as to take up immediate backward diagonal wins.*/
private static inline void test_backward_diagonal_win ()
{
assert_true (AI.playgame ("a5422327343142110") == 0);
assert_true (AI.playgame ("a1415113315143220") == 1);
assert_true (AI.playgame ("a547323452213345110") == 0);
assert_true (AI.playgame ("a4256424426621271412117175776343330") == 2);
assert_true (AI.playgame (Difficulty.EASY, "5422327343142110") == 0);
assert_true (AI.playgame (Difficulty.EASY, "1415113315143220") == 1);
assert_true (AI.playgame (Difficulty.EASY, "547323452213345110") == 0);
assert_true (AI.playgame (Difficulty.EASY, "4256424426621271412117175776343330") == 2);
}
/* 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 static inline void test_avoid_vertical_loss ()
{
assert_true (AI.playgame ("a42563117273430") == 2);
assert_true (AI.playgame ("a3642571541322340") == 3);
assert_true (AI.playgame ("a144566264475171137750") == 4);
assert_true (AI.playgame ("a54747745454432332171210") == 0);
assert_true (AI.playgame (Difficulty.EASY, "42563117273430") == 2);
assert_true (AI.playgame (Difficulty.EASY, "3642571541322340") == 3);
assert_true (AI.playgame (Difficulty.EASY, "144566264475171137750") == 4);
assert_true (AI.playgame (Difficulty.EASY, "54747745454432332171210") == 0);
}
/* Tests if the AI makes moves which prevents HUMAN from taking immediate forward diagonal victories*/
private static inline void test_avoid_forward_diagonal_loss ()
{
assert_true (AI.playgame ("a34256477331566570") == 6);
assert_true (AI.playgame ("a1445662644751711370") == 6);
assert_true (AI.playgame ("a43442235372115113340") == 3);
assert_true (AI.playgame ("a4143525567766443543125411170") == 6);
assert_true (AI.playgame (Difficulty.EASY, "34256477331566570") == 6);
assert_true (AI.playgame (Difficulty.EASY, "1445662644751711370") == 6);
assert_true (AI.playgame (Difficulty.EASY, "43442235372115113340") == 3);
assert_true (AI.playgame (Difficulty.EASY, "4143525567766443543125411170") == 6);
}
/* Tests if the AI makes moves which prevents HUMAN from taking immediate backward diagonal victories*/
private static inline void test_avoid_backward_diagonal_loss ()
{
assert_true (AI.playgame ("a47465234222530") == 2);
assert_true (AI.playgame ("a4344223537211510") == 0);
assert_true (AI.playgame ("a4141311525513520") == 1);
assert_true (AI.playgame ("a1445662644751711377553330") == 2);
assert_true (AI.playgame (Difficulty.EASY, "47465234222530") == 2);
assert_true (AI.playgame (Difficulty.EASY, "4344223537211510") == 0);
assert_true (AI.playgame (Difficulty.EASY, "4141311525513520") == 1);
assert_true (AI.playgame (Difficulty.EASY, "1445662644751711377553330") == 2);
}
/* Tests if the AI makes moves which prevents HUMAN from taking immediate horizontal victories*/
private static inline void test_avoid_horizontal_loss ()
{
assert_true (AI.playgame ("a445360") == 6);
assert_true (AI.playgame ("a745534131117114777720") == 1);
assert_true (AI.playgame ("a243466431217112323350") == 4);
assert_true (AI.playgame ("a24147356465355111336631615240") == 3);
assert_true (AI.playgame (Difficulty.EASY, "445360") == 6);
assert_true (AI.playgame (Difficulty.EASY, "745534131117114777720") == 1);
assert_true (AI.playgame (Difficulty.EASY, "243466431217112323350") == 4);
assert_true (AI.playgame (Difficulty.EASY, "24147356465355111336631615240") == 3);
}
/* Tests if AI can detect full boards, and thus draw games. */
private static inline void test_draw ()
{
assert_true (AI.playgame ("a1311313113652226667224247766737374455445550") == uint8.MAX);
assert_true (AI.playgame ("a6121151135432322433425566474425617635677770") == uint8.MAX);
assert_true (AI.playgame ("a4226111412113275256335534443264375577676670") == uint8.MAX);
assert_true (AI.playgame ("a4212116575717754775221133434432366655342660") == uint8.MAX);
assert_true (AI.playgame (Difficulty.EASY, "1311313113652226667224247766737374455445550") == uint8.MAX);
assert_true (AI.playgame (Difficulty.EASY, "6121151135432322433425566474425617635677770") == uint8.MAX);
assert_true (AI.playgame (Difficulty.EASY, "4226111412113275256335534443264375577676670") == uint8.MAX);
assert_true (AI.playgame (Difficulty.EASY, "4212116575717754775221133434432366655342660") == uint8.MAX);
}
/* Tests if AI makes valid moves, i.e., between column 1 and column 7. */
private static inline void test_random ()
{
uint8 x = AI.playgame ("a443256214350");
uint8 x = AI.playgame (Difficulty.EASY, "443256214350");
assert_true (x <= 6);
x = AI.playgame ("a241473564653551113366316150");
x = AI.playgame (Difficulty.EASY, "241473564653551113366316150");
assert_true (x <= 6);
x = AI.playgame ("a24357315461711177416622623350");
x = AI.playgame (Difficulty.EASY, "24357315461711177416622623350");
assert_true (x <= 6);
x = AI.playgame ("a1445662644751711377553333665775446110");
x = AI.playgame (Difficulty.EASY, "1445662644751711377553333665775446110");
assert_true (x <= 6);
}
/* Pits two AI's of varying difficulty levels against each other and returns the number of games won by easier AI.*/
private static inline uint8 test_ai_vs_ai (string easier, string harder)
private static inline uint8 test_ai_vs_ai (Difficulty easier_AI, Difficulty harder_AI)
{
uint8 easier_wins = 0;
uint8 draw = 0;
......@@ -157,15 +157,15 @@ private static inline uint8 test_ai_vs_ai (string easier, string harder)
for (uint8 i = 0; i < NUMBER_GAMES; i++)
{
StringBuilder e = new StringBuilder ();
e.append (easier);
StringBuilder easier = new StringBuilder ();
easier.append ("0");
StringBuilder m = new StringBuilder ();
m.append (harder);
StringBuilder harder = new StringBuilder ();
harder.append ("0");
while (true)
{
uint8 move = AI.playandcheck (e.str);
uint8 move = AI.playandcheck (easier_AI, easier.str);
if (move == uint8.MAX)
{
draw++;
......@@ -178,10 +178,10 @@ private static inline uint8 test_ai_vs_ai (string easier, string harder)
break;
}
e.insert (e.str.length - 1, (move + 1).to_string ());
m.insert (m.str.length - 1, (move + 1).to_string ());
easier.insert (easier.str.length - 1, (move + 1).to_string ());
harder.insert (harder.str.length - 1, (move + 1).to_string ());
move = AI.playandcheck (m.str);
move = AI.playandcheck (harder_AI, harder.str);
if (move == uint8.MAX)
{
......@@ -194,8 +194,8 @@ private static inline uint8 test_ai_vs_ai (string easier, string harder)
harder_wins++;
break;
}
e.insert (e.str.length - 1, (move + 1).to_string ());
m.insert (m.str.length - 1, (move + 1).to_string ());
easier.insert (easier.str.length - 1, (move + 1).to_string ());
harder.insert (harder.str.length - 1, (move + 1).to_string ());
}
}
return easier_wins;
......@@ -203,14 +203,14 @@ private static inline uint8 test_ai_vs_ai (string easier, string harder)
/* Repeatedly contest between the two AI until either easier win ratio is less than a threshold
or maximum numbers of contests have been played.*/
private static inline void repeat_contests (string easier, string harder, out uint8 games_contested, out uint8 easy_wins)
private static inline void repeat_contests (Difficulty easier_AI, Difficulty harder_AI, out uint8 games_contested, out uint8 easy_wins)
{
easy_wins = test_ai_vs_ai (easier, harder);
easy_wins = test_ai_vs_ai (easier_AI, harder_AI);
games_contested = NUMBER_GAMES;
while (games_contested <= MAXIMUM_GAMES && easy_wins > games_contested / THRESHOLD_DENOMINATOR)
{
easy_wins += test_ai_vs_ai (easier, harder);
easy_wins += test_ai_vs_ai (easier_AI, harder_AI);
games_contested += NUMBER_GAMES;
}
}
......@@ -219,7 +219,7 @@ private static inline void test_easy_vs_medium ()
{
uint8 easy_wins;
uint8 games_contested;
repeat_contests ("a0", "b0", out games_contested, out easy_wins);
repeat_contests (Difficulty.EASY, Difficulty.MEDIUM, out games_contested, out easy_wins);
assert_true (easy_wins <= games_contested / THRESHOLD_DENOMINATOR);
}
......@@ -228,7 +228,7 @@ private static inline void test_easy_vs_hard ()
{
uint8 easy_wins;
uint8 games_contested;
repeat_contests ("a0", "c0", out games_contested, out easy_wins);
repeat_contests (Difficulty.EASY, Difficulty.HARD, out games_contested, out easy_wins);
assert_true (easy_wins <= games_contested / THRESHOLD_DENOMINATOR);
}
......@@ -237,7 +237,7 @@ private static inline void test_medium_vs_hard ()
{
uint8 medium_wins;
uint8 games_contested;
repeat_contests ("b0", "c0", out games_contested, out medium_wins);
repeat_contests (Difficulty.MEDIUM, Difficulty.HARD, out games_contested, out medium_wins);
assert_true (medium_wins <= games_contested / THRESHOLD_DENOMINATOR);
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment