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

Commit 17b6fe42 authored by Arnaud B.'s avatar Arnaud B.

Coding style.

parent a818be7d
......@@ -20,10 +20,10 @@
private enum Difficulty { EASY, MEDIUM, HARD; }
private uint8 playgame(string moves_until_now)
private uint8 playgame (string moves_until_now)
{
DecisionTree t = new DecisionTree();
return t.playgame(moves_until_now);
DecisionTree t = new DecisionTree ();
return t.playgame (moves_until_now);
}
private class DecisionTree
......@@ -36,7 +36,7 @@ private class DecisionTree
private const int MAX_HEURIST_VALUE = 10000;
/* 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];
private Player [,] board = new Player [BOARD_ROWS, BOARD_COLUMNS];
/* plies - depth of the DecisionTree */
private uint8 plies = 8;
/* last_moving_player - The player who made the last move, set to Player.NOBODY if no one has made a move yet */
......@@ -47,7 +47,7 @@ private class DecisionTree
private Difficulty level;
/* Initializes an empty board */
internal DecisionTree()
internal DecisionTree ()
{
for (uint8 i = 0; i < BOARD_ROWS; i++)
for (uint8 j = 0; j < BOARD_COLUMNS; j++)
......@@ -55,28 +55,28 @@ private class DecisionTree
}
/* utility function for debugging purposes, prints a snapshot of current status of the board */
internal void print_board()
internal void print_board ()
{
for (uint8 i = 0; i < BOARD_ROWS; i++)
{
for (uint8 j = 0; j < BOARD_COLUMNS; j++)
stdout.printf("%d\t", board [i, j]);
stdout.printf("\n");
stdout.printf ("%d\t", board [i, j]);
stdout.printf ("\n");
}
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(uint8 height, int alpha, int beta)
private int negamax (uint8 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 (height == 0 || board_full ())
{
if (last_moving_player == Player.HUMAN)
return heurist();
return heurist ();
else if (last_moving_player == Player.OPPONENT)
return -1 * heurist();
return -1 * heurist ();
else
return 0;
}
......@@ -92,15 +92,15 @@ private class DecisionTree
for (uint8 column = 0; column < BOARD_COLUMNS; column++)
{
/* make a move into the i'th column */
if (!move(column))
if (!move (column))
continue;
/* victory() checks if making a move in the i'th column results in a victory for last_moving_player.
If so, multiply MAX_HEURIST_VALUE by a height factor to avoid closer threats first.
Or, we need to go further down the negamax tree. */
int temp = victory(column) ? MAX_HEURIST_VALUE * height : -1 * negamax(height - 1, -1 * beta, -1 * alpha);
int temp = victory (column) ? MAX_HEURIST_VALUE * height : -1 * negamax (height - 1, -1 * beta, -1 * alpha);
unmove(column);
unmove (column);
if (temp > max)
{
......@@ -123,19 +123,19 @@ private class DecisionTree
}
/* all these functions return true if last_moving_player wins, or false */
private bool victory(uint8 column)
private bool victory (uint8 column)
{
/* find the cell on which the last move was made */
uint8 row;
for (row = 0; row < BOARD_ROWS && board [row, column] == Player.NOBODY; row++);
return vertical_win(row, column) ||
horizontal_win(row, column) ||
forward_diagonal_win(row, column) ||
backward_diagonal_win(row, column);
return vertical_win (row, column)
|| horizontal_win (row, column)
|| forward_diagonal_win (row, column)
|| backward_diagonal_win (row, column);
}
private inline bool forward_diagonal_win(uint8 _i, uint8 _j)
private inline bool forward_diagonal_win (uint8 _i, uint8 _j)
{
int8 i = (int8) _i;
int8 j = (int8) _j;
......@@ -148,7 +148,7 @@ private class DecisionTree
return count >= 4;
}
private inline bool backward_diagonal_win(uint8 _i, uint8 _j)
private inline bool backward_diagonal_win (uint8 _i, uint8 _j)
{
int8 i = (int8) _i;
int8 j = (int8) _j;
......@@ -161,7 +161,7 @@ private class DecisionTree
return count >= 4;
}
private inline bool horizontal_win(uint8 _i, uint8 _j)
private inline bool horizontal_win (uint8 _i, uint8 _j)
{
int8 i = (int8) _i;
int8 j = (int8) _j;
......@@ -174,7 +174,7 @@ private class DecisionTree
return count >= 4;
}
private inline bool vertical_win(uint8 i, uint8 j)
private inline bool vertical_win (uint8 i, uint8 j)
{
uint8 count = 0;
......@@ -184,7 +184,7 @@ private class DecisionTree
}
/* returns true if the board is full, false if not */
private bool board_full()
private bool board_full ()
{
for (uint8 i = 0 ; i < BOARD_COLUMNS ; i++)
if (board [0, i] == Player.NOBODY)
......@@ -193,7 +193,7 @@ private class DecisionTree
}
/* makes a move into the column'th column. Returns true if the move was succesful, false if it wasn't */
private bool move(uint8 column)
private bool move (uint8 column)
{
/* find the cell on which to move */
int8 row;
......@@ -211,8 +211,8 @@ private class DecisionTree
}
/* unmove the last move made in the column'th column */
private void unmove(uint8 column)
requires(last_moving_player != Player.NOBODY)
private void unmove (uint8 column)
requires (last_moving_player != Player.NOBODY)
{
/* find the cell on which the last move was made */
uint8 row;
......@@ -224,7 +224,7 @@ private class DecisionTree
}
/* vstr is the sequence of moves made until now. We update DecisionTree::board to reflect these sequence of moves. */
private void update_board(string vstr)
private void update_board (string vstr)
{
next_move_in_column = uint8.MAX;
......@@ -235,7 +235,7 @@ private class DecisionTree
for (uint8 i = 1; i < vstr.length - 1; i++)
{
uint8 column = (uint8) int.parse(vstr [i].to_string()) - 1;
uint8 column = (uint8) int.parse (vstr [i].to_string ()) - 1;
/* find the cell on which the move is made */
int8 row;
......@@ -251,7 +251,7 @@ private class DecisionTree
/* Check for immediate win of HUMAN or OPPONENT. It checks the current state of the board. Returns uint8.MAX if no immediate win for Player P.
Otherwise returns the column number in which Player P should move to win. */
private uint8 immediate_win(Player p)
private uint8 immediate_win (Player p)
{
Player old_last_moving_player = last_moving_player;
......@@ -261,11 +261,11 @@ private class DecisionTree
uint8 i;
for (i = 0; i < BOARD_COLUMNS; i++)
{
if (!move(i))
if (!move (i))
continue;
player_wins = victory(i);
unmove(i);
player_wins = victory (i);
unmove (i);
if (player_wins)
break;
......@@ -278,62 +278,62 @@ private class DecisionTree
}
/* returns the column number in which the next move has to be made. Returns uint8.MAX if the board is full. */
internal uint8 playgame(string vstr)
internal uint8 playgame (string vstr)
{
/* set the Difficulty level */
set_level(vstr);
set_level (vstr);
/* update DecisionTree::board to reflect the moves made until now */
update_board(vstr);
update_board (vstr);
/* if AI can win by making a move immediately, make that move */
uint8 temp = immediate_win(Player.OPPONENT);
uint8 temp = immediate_win (Player.OPPONENT);
if (temp < BOARD_COLUMNS)
return temp;
/* if HUMAN can win by making a move immediately,
we make AI move in that column so as avoid loss */
temp = immediate_win(Player.HUMAN);
temp = immediate_win (Player.HUMAN);
if (temp < BOARD_COLUMNS)
return temp;
/* call negamax tree on the current state of the board */
negamax(plies, NEG_INF, -1 * NEG_INF);
negamax (plies, NEG_INF, -1 * NEG_INF);
/* return the column number in which next move should be made */
return next_move_in_column;
}
/* The evaluation function to be called when we have reached the maximum depth in the DecisionTree */
private int heurist()
private int heurist ()
{
if (level == Difficulty.EASY)
return heurist_easy();
return heurist_easy ();
else if (level == Difficulty.MEDIUM)
return heurist_medium();
return heurist_medium ();
else
return heurist_hard();
return heurist_hard ();
}
private int heurist_easy()
private int heurist_easy ()
{
return -1 * heurist_hard();
return -1 * heurist_hard ();
}
private int heurist_medium()
private int heurist_medium ()
{
return Random.int_range(1, 49);
return Random.int_range (1, 49);
}
private int heurist_hard()
private int heurist_hard ()
{
int count = count_3_in_a_row(Player.OPPONENT) - count_3_in_a_row(Player.HUMAN);
return count == 0 ? (int) Random.int_range(1, 49) : count * 100;
int count = count_3_in_a_row (Player.OPPONENT) - count_3_in_a_row (Player.HUMAN);
return count == 0 ? (int) Random.int_range (1, 49) : count * 100;
}
/* 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)
private int count_3_in_a_row (Player p)
{
int count = 0;
......@@ -348,12 +348,12 @@ private class DecisionTree
if (board [i, j] != Player.NOBODY)
break;
if (all_adjacent_empty(i, j))
if (all_adjacent_empty (i, j))
continue;
board [i, j] = p;
if (victory(j))
if (victory (j))
count++;
board [i, j] = Player.NOBODY;
......@@ -364,7 +364,7 @@ private class DecisionTree
}
/* checks if all adjacent cells to board [i, j] are empty */
private inline bool all_adjacent_empty(uint8 _i, uint8 _j)
private inline bool all_adjacent_empty (uint8 _i, uint8 _j)
{
int8 i = (int8) _i;
int8 j = (int8) _j;
......@@ -384,7 +384,7 @@ private class DecisionTree
}
/* set the number of plies and the difficulty level */
private void set_level(string vstr)
private void set_level (string vstr)
{
if (vstr [0] == 'a')
{
......@@ -404,20 +404,20 @@ private class DecisionTree
}
/* utility function for testing purposes */
internal uint8 playandcheck(string vstr)
internal uint8 playandcheck (string vstr)
{
set_level(vstr);
update_board(vstr);
set_level (vstr);
update_board (vstr);
uint8 temp = immediate_win(Player.OPPONENT);
uint8 temp = immediate_win (Player.OPPONENT);
if (temp < BOARD_COLUMNS)
return 100;
temp = immediate_win(Player.HUMAN);
temp = immediate_win (Player.HUMAN);
if (temp < BOARD_COLUMNS)
return temp;
negamax(plies, NEG_INF, -1 * NEG_INF);
negamax (plies, NEG_INF, -1 * NEG_INF);
return next_move_in_column;
}
......
......@@ -541,7 +541,7 @@ private class FourInARow : Gtk.Application
play_sound (SoundID.PLAYER_WIN);
window.allow_hint (false);
}
else if (moves == 42)
else if (moves == BOARD_ROWS * BOARD_COLUMNS)
{
set_gameover (true);
winner = NOBODY;
......@@ -1034,9 +1034,12 @@ private class FourInARow : Gtk.Application
private inline void on_help_contents (/* SimpleAction action, Variant? parameter */)
{
try {
try
{
show_uri_on_window (window, "help:four-in-a-row", get_current_event_time ());
} catch (Error error) {
}
catch (Error error)
{
warning ("Failed to show help: %s", error.message);
}
}
......@@ -1065,10 +1068,13 @@ private class FourInARow : Gtk.Application
private inline void init_sound ()
{
try {
try
{
sound_context = new GSound.Context ();
sound_context_state = SoundContextState.WORKING;
} catch (Error e) {
}
catch (Error e)
{
warning (e.message);
sound_context_state = SoundContextState.ERRORED;
}
......@@ -1089,27 +1095,15 @@ private class FourInARow : Gtk.Application
{
string name;
switch (id) {
case SoundID.DROP:
name = "slide";
break;
case SoundID.I_WIN:
name = "reverse";
break;
case SoundID.YOU_WIN:
name = "bonus";
break;
case SoundID.PLAYER_WIN:
name = "bonus";
break;
case SoundID.DRAWN_GAME:
name = "reverse";
break;
case SoundID.COLUMN_FULL:
name = "bad";
break;
default:
return;
switch (id)
{
case SoundID.DROP: name = "slide"; break;
case SoundID.I_WIN: name = "reverse"; break;
case SoundID.YOU_WIN: name = "bonus"; break;
case SoundID.PLAYER_WIN: name = "bonus"; break;
case SoundID.DRAWN_GAME: name = "reverse"; break;
case SoundID.COLUMN_FULL: name = "bad"; break;
default: assert_not_reached ();
}
name += ".ogg";
......@@ -1119,7 +1113,7 @@ private class FourInARow : Gtk.Application
sound_context.play_simple (null, GSound.Attribute.MEDIA_NAME, name,
GSound.Attribute.MEDIA_FILENAME, path);
} catch (Error e) {
warning(e.message);
warning (e.message);
}
}
......
......@@ -20,7 +20,8 @@
using Gtk;
private class Scorebox : Dialog {
private class Scorebox : Dialog
{
[CCode (notify = false)] internal int theme_id { private get; internal set; }
private Label label_name_top;
......@@ -30,63 +31,65 @@ private class Scorebox : Dialog {
// no change to the draw name line
private Label label_score_end;
internal Scorebox(Window parent, FourInARow application) {
internal Scorebox(Window parent, FourInARow application)
{
/* Translators: title of the Scores dialog; plural noun */
Object(title: _("Scores"),
use_header_bar: 1,
destroy_with_parent: true,
resizable: false,
border_width: 5,
application: application);
get_content_area().spacing = 2;
set_transient_for(parent);
Object (title: _("Scores"),
use_header_bar: 1,
destroy_with_parent: true,
resizable: false,
border_width: 5,
application: application);
get_content_area ().spacing = 2;
set_transient_for (parent);
modal = true;
Grid grid, grid2;
grid = new Grid();
grid = new Grid ();
grid.halign = Align.CENTER;
grid.row_spacing = 6;
grid.orientation = Orientation.VERTICAL;
grid.border_width = 5;
get_content_area().pack_start(grid);
get_content_area ().pack_start (grid);
grid2 = new Grid();
grid.add(grid2);
grid2 = new Grid ();
grid.add (grid2);
grid2.column_spacing = 6;
label_name_top = new Label(null);
grid2.attach(label_name_top, 0, 0, 1, 1);
label_name_top.xalign = 0;
label_name_top.yalign = 0.5f;
label_name_top = new Label (null);
grid2.attach (label_name_top, 0, 0, 1, 1);
label_name_top.set_xalign (0);
label_name_top.set_yalign (0.5f);
label_score_top = new Label(null);
grid2.attach(label_score_top, 1, 0, 1, 1);
label_score_top.xalign = 0;
label_score_top.yalign = 0.5f;
label_score_top = new Label (null);
grid2.attach (label_score_top, 1, 0, 1, 1);
label_score_top.set_xalign (0);
label_score_top.set_yalign (0.5f);
label_name_mid = new Label(null);
grid2.attach(label_name_mid, 0, 1, 1, 1);
label_name_mid.xalign = 0;
label_name_mid.yalign = 0.5f;
label_name_mid = new Label (null);
grid2.attach (label_name_mid, 0, 1, 1, 1);
label_name_mid.set_xalign (0);
label_name_mid.set_yalign (0.5f);
label_score_mid = new Label(null);
grid2.attach(label_score_mid, 1, 1, 1, 1);
label_score_mid.set_xalign(0);
label_score_mid.set_yalign(0.5f);
label_score_mid = new Label (null);
grid2.attach (label_score_mid, 1, 1, 1, 1);
label_score_mid.set_xalign (0);
label_score_mid.set_yalign (0.5f);
/* Translators: in the Scores dialog, label of the line where is indicated the number of tie games */
Label label_name_end = new Label(_("Drawn:"));
grid2.attach(label_name_end, 0, 2, 1, 1);
label_name_end.set_xalign(0);
label_name_end.set_yalign(0.5f);
label_score_end = new Label(null);
grid2.attach(label_score_end, 1, 2, 1, 1);
label_score_end.set_xalign(0);
label_score_end.set_yalign(0.5f);
grid.show_all();
Label label_name_end = new Label (_("Drawn:"));
grid2.attach (label_name_end, 0, 2, 1, 1);
label_name_end.set_xalign (0);
label_name_end.set_yalign (0.5f);
label_score_end = new Label (null);
grid2.attach (label_score_end, 1, 2, 1, 1);
label_score_end.set_xalign (0);
label_score_end.set_yalign (0.5f);
grid.show_all ();
}
/**
......@@ -94,47 +97,58 @@ private class Scorebox : Dialog {
*
* updates the scorebox with the latest scores
*/
internal void update(uint[] scores, bool one_player_game) {
if (one_player_game) {
if (scores[Player.HUMAN] >= scores[Player.OPPONENT]) {
internal void update (uint [] scores, bool one_player_game)
{
if (one_player_game)
{
if (scores [Player.HUMAN] >= scores [Player.OPPONENT])
{
/* Translators: in the Scores dialog, label of the line where is indicated the number of games won by the human player */
label_name_top.set_text(_("You:"));
label_name_top.set_text (_("You:"));
/* Translators: in the Scores dialog, label of the line where is indicated the number of games won by the computer player */
label_name_mid.set_text(_("Me:"));
label_name_mid.set_text (_("Me:"));
label_score_top.label = scores[Player.HUMAN].to_string();
label_score_mid.label = scores[Player.OPPONENT].to_string();
} else {
label_score_top.label = scores [Player.HUMAN].to_string ();
label_score_mid.label = scores [Player.OPPONENT].to_string ();
}
else
{
/* Translators: in the Scores dialog, label of the line where is indicated the number of games won by the computer player */
label_name_top.set_text(_("Me:"));
label_name_top.set_text (_("Me:"));
/* Translators: in the Scores dialog, label of the line where is indicated the number of games won by the human player */
label_name_mid.set_text(_("You:"));
label_name_mid.set_text (_("You:"));
label_score_top.label = scores[Player.OPPONENT].to_string();
label_score_mid.label = scores[Player.HUMAN].to_string();
label_score_top.label = scores [Player.OPPONENT].to_string ();
label_score_mid.label = scores [Player.HUMAN].to_string ();
}
} else {
if (scores[Player.HUMAN] >= scores[Player.OPPONENT]) {
label_name_top.label = theme_get_player(Player.HUMAN, (uint8) theme_id, /* with colon */ true);
label_name_mid.label = theme_get_player(Player.OPPONENT, (uint8) theme_id, /* with colon */ true);
label_score_top.label = scores[Player.HUMAN].to_string();
label_score_mid.label = scores[Player.OPPONENT].to_string();
} else {
label_name_top.label = theme_get_player(Player.OPPONENT, (uint8) theme_id, /* with colon */ true);
label_name_mid.label = theme_get_player(Player.HUMAN, (uint8) theme_id, /* with colon */ true);
label_score_top.label = scores[Player.OPPONENT].to_string();
label_score_mid.label = scores[Player.HUMAN].to_string();
}
else
{
if (scores [Player.HUMAN] >= scores [Player.OPPONENT])
{
label_name_top.label = theme_get_player (Player.HUMAN, (uint8) theme_id, /* with colon */ true);
label_name_mid.label = theme_get_player (Player.OPPONENT, (uint8) theme_id, /* with colon */ true);
label_score_top.label = scores [Player.HUMAN].to_string ();
label_score_mid.label = scores [Player.OPPONENT].to_string ();
}
else
{
label_name_top.label = theme_get_player (Player.OPPONENT, (uint8) theme_id, /* with colon */ true);
label_name_mid.label = theme_get_player (Player.HUMAN, (uint8) theme_id, /* with colon */ true);
label_score_top.label = scores [Player.OPPONENT].to_string ();
label_score_mid.label = scores [Player.HUMAN].to_string ();
}
}
label_score_end.label = scores[Player.NOBODY].to_string();
label_score_end.label = scores [Player.NOBODY].to_string ();
}
protected override bool delete_event(Gdk.EventAny event) { // TODO use hide_on_delete (Gtk3) or hide-on-close (Gtk4) 1/2
hide();
protected override bool delete_event (Gdk.EventAny event) // TODO use hide_on_delete (Gtk3) or hide-on-close (Gtk4)
{
hide ();
return true;
}
}
......@@ -23,22 +23,27 @@ private const uint8 NUMBER_GAMES = 5;
private const uint8 MAXIMUM_GAMES = 100;
private const uint8 THRESHOLD_DENOMINATOR = 4;
private int main (string[] args)
private 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 winning
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 avoiding loss
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);
// test AI relative ranking
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);
// various
Test.add_func ("/AI/Draw", /* "draw" as in tie game! */ test_draw);
Test.add_func ("/AI/Random", test_random);
// run
return Test.run ();
}
......@@ -118,7 +123,7 @@ private static inline void test_avoid_horizontal_loss ()
assert_true (playgame ("a24147356465355111336631615240") == 3);
}
/* Tests if AI can detect full boards, and thus draw games.*/
/* Tests if AI can detect full boards, and thus draw games. */
private static inline void test_draw ()
{
assert_true (playgame ("a1311313113652226667224247766737374455445550") == uint8.MAX);
......@@ -127,7 +132,7 @@ private static inline void test_draw ()
assert_true (playgame ("a4212116575717754775221133434432366655342660") == uint8.MAX);
}
/* Tests if AI makes valid moves, i.e., between column 1 and column 7*/
/* Tests if AI makes valid moves, i.e., between column 1 and column 7. */
private static inline void test_random ()
{
uint8 x = playgame ("a443256214350");
......@@ -205,7 +210,7 @@ private static inline void repeat_contests (string easier, string harder, out ui