[four-in-a-row] Simplify history strings, part 1.
- From: Arnaud B. <arnaudb src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [four-in-a-row] Simplify history strings, part 1.
- Date: Thu, 26 Dec 2019 17:13:10 +0000 (UTC)
commit f31ba67a87525470fb2f9e06986c4f0910c039b4
Author: Arnaud Bonatti <arnaud bonatti gmail com>
Date: Wed Dec 25 18:03:35 2019 +0100
Simplify history strings, part 1.
src/ai.vala | 35 ++++-----------
src/four-in-a-row.vala | 52 ++++++++++++----------
src/player.vala | 7 +++
src/test-ai.vala | 116 ++++++++++++++++++++++++-------------------------
4 files changed, 101 insertions(+), 109 deletions(-)
---
diff --git a/src/ai.vala b/src/ai.vala
index 3736c53..a7cdc46 100644
--- a/src/ai.vala
+++ b/src/ai.vala
@@ -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;
diff --git a/src/four-in-a-row.vala b/src/four-in-a-row.vala
index a25e05e..e24be25 100644
--- a/src/four-in-a-row.vala
+++ b/src/four-in-a-row.vala
@@ -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;
diff --git a/src/player.vala b/src/player.vala
index c6e36de..82e41cd 100644
--- a/src/player.vala
+++ b/src/player.vala
@@ -30,3 +30,10 @@ private enum Player
HUMAN,
OPPONENT;
}
+
+private enum Difficulty
+{
+ EASY,
+ MEDIUM,
+ HARD;
+}
diff --git a/src/test-ai.vala b/src/test-ai.vala
index 33f13d7..ac160e6 100644
--- a/src/test-ai.vala
+++ b/src/test-ai.vala
@@ -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);
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]