...
 
Commits (15)
......@@ -16,7 +16,6 @@ config.h
config.h.in
*.log
*.status
# these may be useful one day if we complicates the build system
*.m4
build/
aclocal.m4
**/build/m4
**/build/aux
......@@ -5,13 +5,15 @@
public class Bot : OVCCClient.Client
{
private MainLoop loop = null;
private MainLoop loop = null;
private bool interactive = false;
private OVCC.SigQueue sigqueue = new OVCC.SigQueue ();
public async bool join (OVCCClient.Server server)
throws Error
{
uint player_suffix = 7;
int game_to_join = -1;
/* connect to the server */
yield bind_to (server);
......@@ -34,10 +36,29 @@ public class Bot : OVCCClient.Client
}
debug ("Logged in with player_suffix = %u", player_suffix);
/* join any open table */
debug ("Trying to join a table...");
/* join an open game */
var list = yield server.enumerate_games (OVCC.GameState.NEW | OVCC.GameState.PLAYER_WAITING);
print ("List of open games:\n");
var idx = 0;
foreach (var d in list) {
print (" - %02i: %s\n", idx, d.to_string());
idx++;
}
if (list.length == 0) {
print ("(currently no game)\n");
}
print ("What game index to join? (-1 or empty for any open game) ");
string? line = interactive ? stdin.read_line () : "";
if (line != null && line != "") {
game_to_join = int.parse (line);
}
debug ("Trying to join game %i...", game_to_join);
try {
yield join_table (-1);
yield join_game (game_to_join);
} catch (Error e4) {
leave ();
throw e4;
......@@ -82,7 +103,6 @@ public class Bot : OVCCClient.Client
case 3: npos.x--; break; /* left */
}
for (var j = 0; j < 4; j++) {
/* FIXME: this should probably be asynchronous... */
if (player.place_tile (npos)) {
debug ("Placed tile ID %u at %d %d angle %u", tile.id,
npos.x, npos.y, tile.rotation);
......@@ -144,9 +164,10 @@ public class Bot : OVCCClient.Client
loop.quit ();
}
public Bot (MainLoop l)
public Bot (MainLoop l, bool i = false)
{
loop = l;
interactive = i;
}
}
......@@ -179,21 +200,34 @@ private static void setup_signal_handlers ()
Bot bot = null;
// has to be outside of main to be const ?!
unowned string host = "localhost";
uint16 port = 0xDEAD;
bool interactive = false;
public int main (string[] args)
{
var host = "localhost";
uint16 port = 0xdead;
if (args.length > 1) {
host = args[1];
}
if (args.length > 2) {
port = (uint16)int.parse (args[2]);
const OptionEntry[] options = {
{ "server", 's', 0, OptionArg.STRING, ref host, "Use this server", "SERVER" },
{ "port", 'p', 0, OptionArg.INT, ref port, "Use this port number", "PORT" },
{ "interactive", 'i', 0, OptionArg.NONE, ref interactive, "Be interactive", null },
{ null }
};
try {
var opt_context = new OptionContext ("- OVCC Bot");
opt_context.set_help_enabled (true);
opt_context.add_main_entries (options, null);
opt_context.parse (ref args);
} catch (OptionError e) {
printerr ("error: %s\n", e.message);
printerr ("Run '%s --help' to see a full list of available command line options.\n", args[0]);
return 1;
}
var loop = new MainLoop ();
var server = new OVCCClient.Server (host, port);
bot = new Bot (loop);
bot = new Bot (loop, interactive);
#if HAVE_POSIX
setup_signal_handlers ();
......
......@@ -38,277 +38,6 @@
#define GAME_TILESET "tileset.xml"
/* bad copy of the bot implemented in libovcc's test */
static gboolean
do_try_place_tile (OVCCBoard *board,
OVCCPosition *pos,
OVCCTile *tile,
gpointer user_data)
{
gsize i;
gboolean keep_doing = TRUE;
OVCCTile *new_tile = user_data;
g_return_val_if_fail (new_tile != NULL, FALSE);
for (i = 0; keep_doing && i < 4; i++) {
OVCCPosition npos = *pos;
int r;
switch (i) {
case 0: npos.y--; break;
case 1: npos.y++; break;
case 2: npos.x--; break;
case 3: npos.x++; break;
}
for (r = 0; keep_doing && r < 4; r++) {
ovcc_tile_rotate (new_tile, 1);
if (ovcc_board_add_tile_check (board, new_tile, &npos)) {
ovcc_board_add_tile (board, new_tile, &npos);
keep_doing = FALSE;
}
}
}
return keep_doing;
}
static gboolean
try_place_tile (OVCCBoard *board,
OVCCTile *tile)
{
return ovcc_board_foreach (board, do_try_place_tile, tile) == FALSE;
}
static gboolean
test_board (OVCCBoard *board,
OVCCStack *stack)
{
OVCCTile *tile;
gsize n_success = 0;
gsize n_failed = 0;
while (NULL != (tile = ovcc_stack_pop (stack))) {
if (! try_place_tile (board, tile)) {
g_warning ("Failed to place tile %u", ovcc_tile_get_id (tile));
n_failed ++;
} else {
n_success ++;
}
g_object_unref (tile);
}
g_debug ("%zu tiles over %zu placed (%.3u%%)",
n_success, (n_success + n_failed),
n_success * 100 / (n_success + n_failed));
return n_failed == 0;
}
static gboolean
game_do_try_place_tile (OVCCBoard *board,
OVCCPosition *pos,
OVCCTile *tile,
gpointer user_data)
{
gsize i;
gboolean keep_doing = TRUE;
OVCCGame *game = user_data;
OVCCTile *new_tile = ovcc_game_get_current_tile (game);
g_return_val_if_fail (new_tile != NULL, FALSE);
for (i = 0; keep_doing && i < 4; i++) {
OVCCPosition npos = *pos;
int r;
switch (i) {
case 0: npos.y--; break;
case 1: npos.y++; break;
case 2: npos.x--; break;
case 3: npos.x++; break;
}
for (r = 0; keep_doing && r < 4; r++) {
if (ovcc_game_place_tile (game, ovcc_game_get_current_player (game), &npos)) {
keep_doing = FALSE;
} else {
ovcc_tile_rotate (new_tile, 1);
}
}
}
return keep_doing;
}
static gboolean
game_try_place_tile (OVCCGame *game,
OVCCTile *tile)
{
OVCCBoard *board;
board = ovcc_game_get_board (game);
return ovcc_board_foreach (board, game_do_try_place_tile, game) == FALSE;
}
struct _PlaceNextData {
OVCCGame *game;
guint n_success;
guint n_failed;
};
static gboolean
place_next (gpointer data)
{
struct _PlaceNextData *pdata = data;
OVCCTile *tile;
tile = ovcc_game_get_current_tile (pdata->game);
if (! tile) {
g_debug ("%u tiles over %u placed (%.3u%%)",
pdata->n_success, (pdata->n_success + pdata->n_failed),
(pdata->n_success + pdata->n_failed) > 0
? pdata->n_success * 100 / (pdata->n_success + pdata->n_failed)
: 0);
return FALSE;
}
if (! game_try_place_tile (pdata->game, tile)) {
g_warning ("Failed to place tile %u", ovcc_tile_get_id (tile));
pdata->n_failed ++;
} else {
pdata->n_success ++;
}
return TRUE;
}
static void
test_game (OVCCGame *game)
{
struct _PlaceNextData *pdata;
pdata = g_malloc (sizeof *pdata);
pdata->game = game;
pdata->n_success = 0;
pdata->n_failed = 0;
g_timeout_add_full (G_PRIORITY_DEFAULT, 100, place_next, pdata, g_free);
}
static OVCCStack *
create_stack (void)
{
gchar *path;
OVCCTilesDef *tiles;
GFile *tiles_file;
OVCCTileSet *set;
GFile *set_file;
OVCCStack *stack = NULL;
GError *err = NULL;
tiles = ovcc_tiles_def_new ();
path = data_get_path (GAME_TILES);
tiles_file = g_file_new_for_path (path);
g_free (path);
if (! ovcc_tiles_def_load (tiles, tiles_file, &err)) {
msg_error (_("Failed to load tiles: %s"), err->message);
g_error_free (err);
} else {
set = ovcc_tile_set_new ();
path = data_get_path (GAME_TILESET);
set_file = g_file_new_for_path (path);
g_free (path);
if (! ovcc_tile_set_load (set, tiles, set_file, &err)) {
msg_error (_("Failed to load tile set: %s"), err->message);
g_error_free (err);
} else {
stack = ovcc_stack_new_from_tileset (set);
}
g_object_unref (set_file);
g_object_unref (set);
}
g_object_unref (tiles_file);
g_object_unref (tiles);
return stack;
}
static OVCCBoard *
create_board (void)
{
OVCCStack *stack;
OVCCBoard *board = NULL;
stack = create_stack ();
if (stack) {
OVCCTile *tile;
tile = ovcc_stack_pop (stack);
board = ovcc_board_new (tile);
g_object_unref (tile);
test_board (board, stack);
}
g_object_unref (stack);
return board;
}
static void
on_unplaceable_tile (OVCCGame *game,
OVCCTile *tile,
gpointer data)
{
g_warning ("Cannot place tile #%u, reshaking.", ovcc_tile_get_id (tile));
}
static OVCCGame *
create_game (void)
{
gchar *path;
OVCCTilesDef *tiles;
GFile *tiles_file;
OVCCTileSet *set;
GFile *set_file;
OVCCGame *game = NULL;
GError *err = NULL;
tiles = ovcc_tiles_def_new ();
path = data_get_path (GAME_TILES);
tiles_file = g_file_new_for_path (path);
g_free (path);
if (! ovcc_tiles_def_load (tiles, tiles_file, &err)) {
msg_error (_("Failed to load tiles: %s"), err->message);
g_error_free (err);
} else {
set = ovcc_tile_set_new ();
path = data_get_path (GAME_TILESET);
set_file = g_file_new_for_path (path);
g_free (path);
if (! ovcc_tile_set_load (set, tiles, set_file, &err)) {
msg_error (_("Failed to load tile set: %s"), err->message);
g_error_free (err);
} else {
game = ovcc_game_new (set, NULL);
g_signal_connect (game, "unplaceable-tile",
G_CALLBACK (on_unplaceable_tile), NULL);
ovcc_game_add_player (game, ovcc_player_new ("A"), NULL);
ovcc_game_add_player (game, ovcc_player_new ("B"), NULL);
}
g_object_unref (set_file);
g_object_unref (set);
}
g_object_unref (tiles_file);
g_object_unref (tiles);
return game;
}
/* end of bad game fake */
typedef struct _App App;
struct _App
......@@ -447,9 +176,9 @@ update_tile_image (App *app)
gtk_image_set_from_pixbuf (GTK_IMAGE (app->current_tile_image), pix);
g_object_unref (pix);
} else {
gtk_image_set_from_stock (GTK_IMAGE (app->current_tile_image),
GTK_STOCK_MISSING_IMAGE,
GTK_ICON_SIZE_LARGE_TOOLBAR);
gtk_image_set_from_icon_name (GTK_IMAGE (app->current_tile_image),
"image-missing",
GTK_ICON_SIZE_LARGE_TOOLBAR);
}
}
......@@ -555,7 +284,7 @@ G_MODULE_EXPORT void
game_start_activate_handler (GtkAction *action,
App *app)
{
if (app->game && ovcc_game_get_state (app->game) == OVCC_GAME_STATE_STOPPED) {
if (app->game && ovcc_game_state_can_start (ovcc_game_get_state (app->game))) {
GError *err = NULL;
if (! ovcc_game_start (app->game, &err)) {
......@@ -677,8 +406,8 @@ on_game_state_changed (OVCCGame *game,
started = TRUE;
break;
case OVCC_GAME_STATE_STOPPED:
app_set_status (app, _("Game stopped"));
case OVCC_GAME_STATE_PLAYER_WAITING:
app_set_status (app, _("Game now waiting player"));
can_start = TRUE;
break;
}
......
......@@ -756,13 +756,6 @@ draw_tile_cb (OVCCBoard *board,
g_object_unref (pix);
draw_pawns (self, pos, tile, dest_x, dest_y, size);
if (pos->x == 0 && pos->y == 0) {
cairo_set_source_rgb (self->priv->cr, 0.0, 0.0, 0.0);
cairo_arc (self->priv->cr, dest_x + size / 2.0, dest_y + size / 2.0,
size / 4.0, 0.0, G_PI * 2);
cairo_stroke (self->priv->cr);
}
}
return TRUE;
......
......@@ -141,16 +141,16 @@ ovccgtk_client_new (const gchar *name)
static void
ovccgtk_client_join_table_ready (GObject *object,
GAsyncResult *result,
gpointer data)
ovccgtk_client_join_game_ready (GObject *object,
GAsyncResult *result,
gpointer data)
{
GTask *task = data;
GError *error = NULL;
g_debug ("ovccclient_client_join_table_finish()");
if (! ovccclient_client_join_table_finish (OVCCCLIENT_CLIENT (object),
result, &error)) {
g_debug ("ovccclient_client_join_game_finish()");
if (! ovccclient_client_join_game_finish (OVCCCLIENT_CLIENT (object),
result, &error)) {
ovccclient_client_leave (OVCCCLIENT_CLIENT (object), NULL);
g_task_return_error (task, error);
g_object_unref (task);
......@@ -176,10 +176,10 @@ ovccgtk_client_login_ready (GObject *object,
g_task_return_error (task, error);
g_object_unref (task);
} else {
g_debug ("ovccclient_client_join_table()");
ovccclient_client_join_table (OVCCCLIENT_CLIENT (object), -1,
g_task_get_cancellable (task),
ovccgtk_client_join_table_ready, task);
g_debug ("ovccclient_client_join_game()");
ovccclient_client_join_game (OVCCCLIENT_CLIENT (object), -1,
g_task_get_cancellable (task),
ovccgtk_client_join_game_ready, task);
}
}
......
*.c
!src/test.c
/ovcc.pc
/src/ovcc.vapi
/src/ovcc.h
/src/test
/docs/devhelp/
/docs/html/
/docs/valadoc/
/docs/gtkdoc/
......@@ -29,4 +29,9 @@ AC_DEFUN([OVCC_VALADOC_CHECK],
[AS_IF([test "x$enable_valadoc" = xyes],
[AC_MSG_ERROR([valadoc not found or too old])])])])
AM_CONDITIONAL([ENABLE_VALADOC], [test "x$have_valadoc" = xyes])
# check for gtkdoc needed by gtkdoc doclet
AS_IF([test "x$have_valadoc" = xyes],
[AC_PATH_PROG([GTKDOC], [gtkdoc-scan], [NONE])])
AM_CONDITIONAL([ENABLE_VALADOC_GTKDOC], [test "x$GTKDOC" != xNONE])
])
......@@ -19,10 +19,10 @@ valadoc_real_flags = --no-protected \
--package-version $(VALADOC_MODULE_VERSION) \
$(VALADOC_FILES)
EXTRA_DIST = html devhelp gtkdoc
EXTRA_DIST = valadoc devhelp gtkdoc
doc: html devhelp gtkdoc
dist-hook: html devhelp gtkdoc
doc: valadoc devhelp gtkdoc
dist-hook: valadoc devhelp gtkdoc
# Removing the target before building the documentation avoids conflicts
# between doclet and directory names, since valadoc first tries to find the
......@@ -30,32 +30,37 @@ dist-hook: html devhelp gtkdoc
# For the gtkdoc doclet, it also prevents leftover files to be used and
# produce wrong results, e.g. if a file got renamed.
html: $(VALADOC_FILES)
$(RM) -r $@
$(VALADOC) \
valadoc: $(VALADOC_FILES)
$(AM_V_at)$(RM) -r $@
$(AM_V_GEN)$(VALADOC) \
-o $@ \
--doclet html \
$(valadoc_real_flags)
devhelp: $(VALADOC_FILES)
$(RM) -r $@
$(VALADOC) \
$(AM_V_at)$(RM) -r $@
$(AM_V_GEN)$(VALADOC) \
-o $@ \
--doclet devhelp \
$(valadoc_real_flags)
if ENABLE_VALADOC_GTKDOC
gtkdoc: $(VALADOC_FILES)
$(RM) -r $@
$(VALADOC) \
$(AM_V_at)$(RM) -r $@
$(AM_V_GEN)$(VALADOC) \
-X -l -X $(VALADOC_LIB) \
-X $(VALADOC_CHEADER) \
-o $@ \
--doclet gtkdoc \
$(valadoc_real_flags)
else
gtkdoc:
$(AM_V_at)mkdir $@
endif
clean-local:
$(RM) -r html devhelp gtkdoc
$(RM) -r valadoc devhelp gtkdoc
uninstall-doc-devhelp:
$(RM) -r $(DESTDIR)$(datadir)/devhelp/books/$(VALADOC_MODULE_NAME)
......@@ -65,12 +70,20 @@ install-doc-devhelp: devhelp
$(INSTALL_DATA) -t $(DESTDIR)$(datadir)/devhelp/books/$(VALADOC_MODULE_NAME) devhelp/$(VALADOC_MODULE_NAME)/*.*
$(INSTALL_DATA) -t $(DESTDIR)$(datadir)/devhelp/books/$(VALADOC_MODULE_NAME)/img devhelp/$(VALADOC_MODULE_NAME)/img/*
if ENABLE_VALADOC_GTKDOC
uninstall-doc-gtkdoc:
$(RM) -r $(DESTDIR)$(datadir)/gtk-doc/html/$(VALADOC_MODULE_NAME)
else
uninstall-doc-gtkdoc:
endif
if ENABLE_VALADOC_GTKDOC
install-doc-gtkdoc: gtkdoc
$(MKDIR_P) $(DESTDIR)$(datadir)/gtk-doc/html/$(VALADOC_MODULE_NAME)
$(INSTALL_DATA) -t $(DESTDIR)$(datadir)/gtk-doc/html/$(VALADOC_MODULE_NAME) gtkdoc/html/*.*
else
install-doc-gtkdoc:
endif
uninstall-hook: uninstall-doc-devhelp uninstall-doc-gtkdoc
......
......@@ -32,7 +32,8 @@ libovcc_la_SOURCES = ovcc-board.vala \
network/ovcc-network-signals.vala \
network/ovcc-network-disconnect-message.vala \
network/ovcc-network-welcome-message.vala \
network/ovcc-network-error-message.vala
network/ovcc-network-error-message.vala \
network/ovcc-network-list-games-message.vala
ovccinclude_HEADERS = ovcc.h
vapi_DATA = ovcc.vapi
......
......@@ -25,7 +25,7 @@ public class OVCC.Network.JoinMessage : VariantMessage
{
public override MessageType message_type { get { return MessageType.JOIN; } }
public int table_index { get; set; default = -1; }
public int game_index { get; set; default = -1; }
public State status { get; set; default = State.QUERY; }
public enum State
......@@ -38,12 +38,12 @@ public class OVCC.Network.JoinMessage : VariantMessage
public JoinMessage (int idx)
{
Object (table_index: idx);
Object (game_index: idx);
}
protected override Variant build_variant ()
{
return new Variant ("(ii)", status, table_index);
return new Variant ("(ii)", status, game_index);
}
protected override void parse_variant (uint8[] data)
{
......@@ -51,6 +51,6 @@ public class OVCC.Network.JoinMessage : VariantMessage
int idx;
parse_data (data, "(ii)", out s, out idx);
status = s;
table_index = idx;
game_index = idx;
}
}
/*
*
* Copyright (C) 2019 Colomban Wendling <ban@herbesfolles.org>
* Jonathan Michalon <studios.chalmion@no-log.org>
*
* This program 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 3 of the License, or
* (at your option) any later version.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
public class OVCC.Network.ListGamesMessage : VariantMessage
{
public override MessageType message_type { get { return MessageType.LIST_GAMES; } }
public GameDescription[] descriptions;
public ListGamesMessage (GameDescription[] descriptions = {})
{
this.descriptions = descriptions;
}
protected override Variant build_variant ()
{
Variant[] list = {};
foreach (var d in descriptions) {
list += new Variant ("(si^as)", d.name, d.state, d.player_nicks);
}
Variant v = list; // triggers internal vala magics with VariantBuilder etc.
return v;
}
protected override void parse_variant (uint8[] data)
{
Variant[] list;
GameDescription[] tmplist = {};
list = (Variant[]) Variant.new_from_data<Object> (new VariantType ("av"), data, false, this);
foreach (var v in list) {
string name = v.get_child_value (0).get_string ();
GameState state = (GameState) v.get_child_value (1).get_int32 ();
string[] player_nicks = v.get_child_value (2).get_strv ();
tmplist += new GameDescription (name, state, player_nicks);
}
descriptions = tmplist;
}
}
......@@ -18,9 +18,10 @@
*
*/
/**
/*
* How to add a new message:
* 1) Subclass OVCC.Network.Message (or one of its subclass)
* 1) Subclass OVCC.Network.Message (or one of its subclass);
* don't forget to append your filename to Makefile.am's libovcc_la_SOURCES
* 2) Implement the abstract methods and properties
* 3) Append the message type in OVCC.Network.MessageType
* 4) Append the type of your class in OVCC.Network.Message.message_types
......@@ -45,6 +46,7 @@ namespace OVCC.Network
GAMEDATA,
SIGNAL,
DISCONNECT,
LIST_GAMES,
INVALID
}
......@@ -58,7 +60,8 @@ namespace OVCC.Network
typeof (JoinMessage),
typeof (GamedataMessage),
typeof (SignalMessage),
typeof (DisconnectMessage)
typeof (DisconnectMessage),
typeof (ListGamesMessage)
};
public abstract MessageType message_type { get; }
......@@ -92,27 +95,6 @@ namespace OVCC.Network
return message;
}
public static async Message receive_async (DataInputStream stream,
Cancellable? cancel = null)
throws Error
{
Message result = null;
Error? err = null;
Thread.create<void> (() => {
try {
result = Message.receive (stream, cancel);
} catch (Error e) {
err = e;
}
Idle.add (receive_async.callback);
}, false);
yield;
if (err != null) {
throw err;
}
return result;
}
public bool send (DataOutputStream stream,
Cancellable? cancel = null)
throws Error
......@@ -121,40 +103,21 @@ namespace OVCC.Network
this.serialize (stream, cancel);
return true;
}
public async bool send_async (DataOutputStream stream,
Cancellable? cancel = null)
throws Error
{
bool result = true;
Error? err = null;
Thread.create<void> (() => {
try {
result = this.send (stream, cancel);
} catch (Error e) {
err = e;
}
Idle.add (send_async.callback);
}, false);
yield;
if (err != null) {
throw err;
}
return result;
}
protected uint8[] read_buffer (DataInputStream stream,
Cancellable? cancel = null)
throws Error
{
var len = stream.read_uint16 (cancel);
if (len < 1 || len > 0x4000 /* arbitrary limit not to fulfill memory */) {
if (len < 0 || len > 0x4000 /* arbitrary limit not to fulfill memory */) {
throw new MessageError.MALFORMED_MESSAGE ("Invalid message length %u",
len);
}
var buf = new uint8[len];
stream.read (buf, cancel);
var buf = new uint8[len];
if (len > 0) {
stream.read (buf, cancel);
}
return buf;
}
......@@ -164,7 +127,11 @@ namespace OVCC.Network
throws Error
{
stream.put_uint16 ((uint16)buf.length, cancel);
return stream.write (buf, cancel) == buf.length;
if (buf.length > 0) {
return stream.write (buf, cancel) == buf.length;
} else {
return true;
}
}
}
}
......@@ -165,8 +165,9 @@ namespace OVCC.Network
critical ("Got signal to start game but couldn't: %s", e.message);
}
break;
case OVCC.GameState.NEW:
case OVCC.GameState.PLAYER_WAITING:
case OVCC.GameState.FINISHED:
case OVCC.GameState.STOPPED:
break; /* should be automatic */
case OVCC.GameState.ABORTED:
game_object.abort();
......
......@@ -168,12 +168,12 @@ namespace OVCC
/**
* Checks whether a tile is placeable on the board
*
* @param tile a tile
* @param tile a {@link tile}
* @return true if the tile can be placed, false otherwise.
*/
public bool is_tile_placeable (Tile t)
public bool is_tile_placeable (Tile tile)
{
var tile = t.dup();
var tiledup = tile.dup();
return false == this.foreach ((b, p, t) => {
for (var i = 0; i < 4; i++) {
var npos = p;
......@@ -185,10 +185,10 @@ namespace OVCC
case 3: npos.x--; break; /* left */
}
for (var j = 0; j < 4; j++) {
if (this.add_tile_check (tile, npos)) {
if (this.add_tile_check (tiledup, npos)) {
return false;
} else {
tile.rotate (1);
tiledup.rotate (1);
}
}
}
......@@ -466,13 +466,18 @@ namespace OVCC
*/
public bool @foreach (BoardForeachFunc f)
{
bool keep_going = true;
this._board.foreach ((k, v) => {
if (keep_going) {
keep_going = f (this, k, v);
var iter = HashTableIter<Position?,Tile> (this._board);
Position? pos;
Tile tile;
bool went_through = true;
while (iter.next (out pos, out tile)) {
if (f (this, pos, tile) == false) {
went_through = false;
break;
}
});
return keep_going;
}
return went_through;
}
/**
......
......@@ -23,10 +23,10 @@ namespace OVCC
/**
* The errors of the GameError domain.
*
* @param STARTED The game is started and the action therefore cannot be done.
* @param PLAYER_ALREADY_ADDED Player is already on the game.
* @param DUPLICATED_NICK Player have the same nick than an another player.
* @param FAILED Something failed...
* || STARTED || The game is started and the action therefore cannot be done. ||
* || PLAYER_ALREADY_ADDED || Player is already on the game. ||
* || DUPLICATED_NICK || Player have the same nick than an another player. ||
* || FAILED || Something failed... ||
*/
public errordomain GameError
{
......@@ -38,18 +38,59 @@ namespace OVCC
/**
* Possible states of a game.
* Only one state at a time. "Flags" type is used for filtering only.
*
* @param STOPPED The game is stopped (not started or finished)
* @param STARTED The game is started
* @param FINISHED The game is finished
* @param ABORTED The game is stopped, but not finished
* || NEW || The game is just created but no player joined ||
* || PLAYER_WAITING || The game has players but is not started yet ||
* || STARTED || The game is started ||
* || FINISHED || The game is finished ||
* || ABORTED || The game is stopped, but not finished ||
*/
[Flags]
public enum GameState
{
STOPPED,
NEW,
PLAYER_WAITING,
STARTED,
FINISHED,
ABORTED
ABORTED;
public bool is_open()
{
return this in (NEW | PLAYER_WAITING);
}
public bool is_done()
{
return this in (FINISHED | ABORTED);
}
public bool can_start()
{
return this in PLAYER_WAITING;
}
}
/**
* A class providing the description of a Game.
* This is a stripped-down group of data typically to descibe a Game over network.
*/
[Compact]
public class GameDescription : Object
{
public string name { get; construct; }
public GameState state { get; construct; }
public string[] player_nicks { get; construct; }
public GameDescription (string name, GameState state, string[] player_nicks)
{
Object(name: name, state: state, player_nicks: player_nicks);
}
public string to_string ()
{
return "Game '%s' in '%s' state with players '%s'".printf (name,
state.to_string(),
string.joinv(", ", player_nicks));
}
}
/**
......@@ -60,7 +101,9 @@ namespace OVCC
{
private SList<Player> _players = null;
public GameState state { get; private set; default = GameState.STOPPED; }
// FIXME implement UUIDs for reference over network instead of mere list index
// public string uuid { get; construct; default = Uuid.string_random(); }
public GameState state { get; private set; default = GameState.NEW; }
public Stack stack { private get; construct; }
public Board board { get; construct; }
public unowned SList<Player> players { get { return this._players; } }
......@@ -107,9 +150,15 @@ namespace OVCC
});
this.player_added.connect ((p) => {
this.notify_property ("players");
if (players.length() == 1 && this.state == GameState.NEW) {
this.state = GameState.PLAYER_WAITING;
}
});
this.player_removed.connect ((p) => {
this.notify_property ("players");
if (players.length() < 1 && this.state == GameState.PLAYER_WAITING) {
this.state = GameState.NEW;
}
});
this.turn_finished.connect (() => next_player ());
......@@ -488,5 +537,16 @@ namespace OVCC
}
return nicks;
}
/**
* Generate the {@link GameDescription} corresponding to this Game instance.
*
* @return A {@link GameDescription} describing us.
*/
public GameDescription describe ()
{
/* FIXME game name */
return new GameDescription ("Unnamed Game", state, get_player_nicks());
}
}
}
......@@ -19,8 +19,8 @@
*/
static const int N_NORMAL = 7;
static const int N_DOUBLE = 1;
const int N_NORMAL = 7;
const int N_DOUBLE = 1;
namespace OVCC
......
......@@ -18,7 +18,6 @@
*
*/
[compact]
public class OVCC.SigQueue
{
private struct Entry {
......
......@@ -40,50 +40,17 @@ namespace OVCC
}
}
/* FIXME: update the doc */
/**
* This module manages Tile Sets. Tile Sets are list of tiles and the number of
* them in the set.
* Tile Sets works together with tiles definitions to provide a set of tiles.
* This module manages tile sets which are a list of tiles and the amount of
* each of them in the set.
* TileSet works together with {@link Tile} to provide a set of tiles.
*
* Tile Sets are represented by #OVCCTileSet objects.
*
* A Tile Set is created with ovcc_tileset_new() and freed using
* ovcc_tileset_unref(). They can be loaded from XML definitions with
* ovcc_tileset_load();
* Adding a tile to a Tile Set is done using ovcc_tileset_add(),
* removing one is done using ovcc_tileset_remove(), and removing all tiles from
* TileSets can be loaded from XML definitions with load() or
* from memory with load_from_string();
* Adding a tile to a Tile Set is done using add(),
* removing one is done using remove(), and removing all tiles from
* a set is done using ovcc_tileset_clear().
*
* =Simple example showing the load of a Tile Set from an URI=
*
* {{{
* #include <glib.h>
* #include <gio/gio.h>
* #include "tileset.h"
*
* OVCCTileSet *
* load_our_tileset (const gchar *uri,
* OVCCTilesDef *available_tiles)
* {
* OVCCTileSet *set;
* GFile *file;
* GError *err = NULL;
*
* file = g_file_new_for_uri (uri);
* set = ovcc_tileset_new ();
* if (! ovcc_tileset_load (set, available_tiles, file, &err)) {
* g_critical ("Failed to load Tile Set: %s", err->message);
* g_error_free (err);
* // Return NULL if the tile set cannot be loaded
* ovcc_tileset_unref (set), set = NULL;
* }
* g_object_unref (file);
*
* return set;
* }
* }}}
*
* =Example of a tileset XML file=
*
* {{{
......
......@@ -4,5 +4,5 @@
/src/ovccclient.h
/src/ovccclient.vapi
/docs/devhelp/
/docs/html/
/docs/valadoc/
/docs/gtkdoc/
......@@ -29,4 +29,9 @@ AC_DEFUN([OVCC_VALADOC_CHECK],
[AS_IF([test "x$enable_valadoc" = xyes],
[AC_MSG_ERROR([valadoc not found or too old])])])])
AM_CONDITIONAL([ENABLE_VALADOC], [test "x$have_valadoc" = xyes])
# check for gtkdoc needed by gtkdoc doclet
AS_IF([test "x$have_valadoc" = xyes],
[AC_PATH_PROG([GTKDOC], [gtkdoc-scan], [NONE])])
AM_CONDITIONAL([ENABLE_VALADOC_GTKDOC], [test "x$GTKDOC" != xNONE])
])
......@@ -19,10 +19,10 @@ valadoc_real_flags = --no-protected \
--package-version $(VALADOC_MODULE_VERSION) \
$(VALADOC_FILES)
EXTRA_DIST = html devhelp gtkdoc
EXTRA_DIST = valadoc devhelp gtkdoc
doc: html devhelp gtkdoc
dist-hook: html devhelp gtkdoc
doc: valadoc devhelp gtkdoc
dist-hook: valadoc devhelp gtkdoc
# Removing the target before building the documentation avoids conflicts
# between doclet and directory names, since valadoc first tries to find the
......@@ -30,32 +30,37 @@ dist-hook: html devhelp gtkdoc
# For the gtkdoc doclet, it also prevents leftover files to be used and
# produce wrong results, e.g. if a file got renamed.
html: $(VALADOC_FILES)
$(RM) -r $@
$(VALADOC) \
valadoc: $(VALADOC_FILES)
$(AM_V_at)$(RM) -r $@
$(AM_V_GEN)$(VALADOC) \
-o $@ \
--doclet html \
$(valadoc_real_flags)
devhelp: $(VALADOC_FILES)
$(RM) -r $@
$(VALADOC) \
$(AM_V_at)$(RM) -r $@
$(AM_V_GEN)$(VALADOC) \
-o $@ \
--doclet devhelp \
$(valadoc_real_flags)
if ENABLE_VALADOC_GTKDOC
gtkdoc: $(VALADOC_FILES)
$(RM) -r $@
$(VALADOC) \
$(AM_V_at)$(RM) -r $@
$(AM_V_GEN)$(VALADOC) \
-X -l -X $(VALADOC_LIB) \
-X $(VALADOC_CHEADER) \
-o $@ \
--doclet gtkdoc \
$(valadoc_real_flags)
else
gtkdoc:
$(AM_V_at)mkdir $@
endif
clean-local:
$(RM) -r html devhelp gtkdoc
$(RM) -r valadoc devhelp gtkdoc
uninstall-doc-devhelp:
$(RM) -r $(DESTDIR)$(datadir)/devhelp/books/$(VALADOC_MODULE_NAME)
......@@ -65,12 +70,20 @@ install-doc-devhelp: devhelp
$(INSTALL_DATA) -t $(DESTDIR)$(datadir)/devhelp/books/$(VALADOC_MODULE_NAME) devhelp/$(VALADOC_MODULE_NAME)/*.*
$(INSTALL_DATA) -t $(DESTDIR)$(datadir)/devhelp/books/$(VALADOC_MODULE_NAME)/img devhelp/$(VALADOC_MODULE_NAME)/img/*
if ENABLE_VALADOC_GTKDOC
uninstall-doc-gtkdoc:
$(RM) -r $(DESTDIR)$(datadir)/gtk-doc/html/$(VALADOC_MODULE_NAME)
else
uninstall-doc-gtkdoc:
endif
if ENABLE_VALADOC_GTKDOC
install-doc-gtkdoc: gtkdoc
$(MKDIR_P) $(DESTDIR)$(datadir)/gtk-doc/html/$(VALADOC_MODULE_NAME)
$(INSTALL_DATA) -t $(DESTDIR)$(datadir)/gtk-doc/html/$(VALADOC_MODULE_NAME) gtkdoc/html/*.*
else
install-doc-gtkdoc:
endif
uninstall-hook: uninstall-doc-devhelp uninstall-doc-gtkdoc
......
......@@ -111,9 +111,13 @@ namespace OVCCClient
throws ServerError
{
sigqueue.remove_all ();
game.abort();
server.disconnect_from ();
if (game != null) {
game.abort ();
}
if (server.is_connected ()) {
server.disconnect_from ();
}
/* destroy server */
server = null;
game = null;
......@@ -143,17 +147,17 @@ namespace OVCCClient
}
/**
* Joins a table asynchronously
* Joins a game asynchronously
*
* @param index The index of the table to join
* @param index The index of the game to join, -1 means any open game
* @param cancellable a Cancellable object or null
* @return The game corresponding to that table
* @return The joined Game
*/
public async Game join_table (int index,
Cancellable? cancellable = null)
public async Game join_game (int index,
Cancellable? cancellable = null)
throws ServerError, IOError
{
game = yield server.join_table (index, player, cancellable);
game = yield server.join_game (index, player, cancellable);
sigqueue.add (game, game.notify["current-player"].connect (() => {
debug ("Current player changed to \"%s\"", game.current_player.nick);
......@@ -167,7 +171,10 @@ namespace OVCCClient
sigqueue.add (game.board, game.board.tile_added.connect ((t, p) => {
if (game.current_player == player) {
this.place_pawn (p);
Idle.add (() => {
this.place_pawn (p);
return false;
});
}
}));
......
......@@ -63,9 +63,9 @@ namespace OVCCClient
*/
MISSING_AUTHENTICATION,
/**
* No open table available
* No open game available
*/
NO_OPEN_TABLE,
NO_OPEN_GAME,
/**
* Data could not be reached where it was searched for
*/
......@@ -75,32 +75,7 @@ namespace OVCCClient
*/
FAILED
}
/**
* Flags for filtering table list
*/
[Flags]
public enum TablesFilter
{
/**
* All tables
*/
ALL,
/**
* Only open tables
*/
OPEN,
/**
* Only tables with people waiting
*/
PEOPLE_WAITING,
/**
* Only tables where people ask for other players
*/
ASKING_FOR_PEOPLE
}
/**
* A class representing the server side of the network clients
*
......@@ -126,14 +101,14 @@ namespace OVCCClient
/**
* A signal emitted when a message was received from server
*
* @param msg A {@link Message} instance containing what the server sent us
* @param msg A {@link OVCC.Network.Message} instance containing what the server sent us
*/
[Signal (detailed = true)]
public signal void message_received (Message msg);
/**
* A signal emitted when a message in queue was sent to the server
*
* @param msg A {@link Message} instance which was just sent to the server
* @param msg A {@link OVCC.Network.Message} instance which was just sent to the server
* @param err An optional error raised when sending
*/
public signal void message_sent (Message msg,
......@@ -235,10 +210,10 @@ namespace OVCCClient
}
/**
* Asynchronous method allowing to pause until a given {@link MessageType}
* Asynchronous method allowing to pause until a given {@link OVCC.Network.MessageType}
* get received.
*
* @param mtype The {@link MessageType} to wait for
* @param mtype The {@link OVCC.Network.MessageType} to wait for
* @param cancellable a Cancellable object or null
* @return The message received
*/
......@@ -277,7 +252,7 @@ namespace OVCCClient
/**
* Asynchronous method to send messages to the remote server.
*
* @param msg The {@link Message} to send
* @param msg The {@link OVCC.Network.Message} to send
* @param cancellable a Cancellable object or null
* @return Whether the operation succeeded.
*/
......@@ -343,9 +318,9 @@ namespace OVCCClient
output = new DataOutputStream (connection.output_stream);
listen_loop_cancel = new Cancellable ();
send_loop_cancel = new Cancellable ();
listen_loop_thread = Thread.create<bool> (listen_loop, true);
listen_loop_thread = new Thread<bool>.try ("listen loop", listen_loop);
send_loop_running = 1;
send_loop_thread = Thread.create<bool> (send_loop, true);
send_loop_thread = new Thread<bool>.try ("send loop", send_loop);
} catch (IOError.CANCELLED c) {
throw c;
} catch (Error e) {
......@@ -438,43 +413,74 @@ namespace OVCCClient
}
/**
* Enumerate some tables available on the server
* Enumerate some games available on the server
*
* This method retrieves a list of available tables currently on the server
* This method retrieves a list of available games currently on the server
* matching the given filter
*
* @param filter the {@link TablesFilter} to use
* @return A list of table indexes on the remote server matching //filter//
* @param filter a mask of {@link OVCC.GameState}s to filter in
* @return A list of {@link OVCC.GameDescription} on the remote server matching //filter//
*/
public List<int>? enumerate_tables (TablesFilter filter)
public async GameDescription[]? enumerate_games (GameState? filter = null,
Cancellable? cancellable = null)
throws ServerError, IOError
requires (connection != null)
{
return null;
/* ask the server to send the list */
yield send_message (new ListGamesMessage (), cancellable);
/* wait for answer */
Message msg = yield receive_type (MessageType.LIST_GAMES, cancellable);
ListGamesMessage list = msg as ListGamesMessage;
/* filter the list */
GameDescription[] filtered = {};
if (filter != null) {
foreach (var d in list.descriptions) {
if (d.state in filter) {
filtered += d;
}
}
} else {
filtered = list.descriptions;
}
return filtered;
}
/**
* Join a table, index -1 means any open table
* Join a game, index -1 means any open game
*
* The index is typically within the list returned by enumerate_tables()
* The index is typically within the list returned by enumerate_games()
*
* @param index The table index
* @param index The game index
* @param player The player to make join
* @param cancellable a Cancellable object or null
* @return The game instance corresponding to the one joint on the remote server
*/
public async Game join_table (int index,
Player player,
Cancellable? cancellable = null)
public async Game join_game (int index,
Player player,
Cancellable? cancellable = null)
throws ServerError, IOError
requires (connection != null)
{
/* FIXME load right tileset and do something with stack... */
TilesDef tiles = new TilesDef ();
TileSet tileset = new TileSet ();
/* call the server */
yield send_message (new JoinMessage (index), cancellable);
/* wait for answer */
Message msg = yield receive_type (MessageType.JOIN, cancellable);
JoinMessage join = msg as JoinMessage;
if (join.status != JoinMessage.State.OK) {
if (index == -1) {
throw new ServerError.NO_OPEN_GAME ("Server said that no open game is available");
} else {
throw new ServerError.NO_OPEN_GAME ("Server said that it is not an open game");
}
}
/* wait for data */
Message msg2 = yield receive_type (MessageType.GAMEDATA, cancellable);
GamedataMessage data = msg2 as GamedataMessage;
......
......@@ -56,7 +56,10 @@ public class Client: Object
requires (connection != null)
{
signal_handle = null; /* to break circular reference */
game.remove_player (player);
if (game != null) {
game.remove_player (player);
}
return connection.socket.close ();
}
......
......@@ -21,15 +21,15 @@
using OVCC;
using OVCC.Network;
static const string TILES_FILE = "tiles.xml";
static const string TILESET_FILE = "tileset.xml";
const string TILES_FILE = "tiles.xml";
const string TILESET_FILE = "tileset.xml";
/* TODO: add a Source to be notified of incoming messages asynchronously */
public class Server: ThreadedSocketService
{
private List<Client> clients = null;
private List<Game> tables = null;
private List<Game> games = null;
private TilesDef tiles = new TilesDef ();
private TileSet tileset = new TileSet ();
private string tiles_data;
......@@ -37,42 +37,42 @@ public class Server: ThreadedSocketService
public string name { get; construct; }
public uint port { get; construct; default = 0xdead; }
public uint max_tables { get; set; default = 1; }
public uint max_games { get; set; default = 1; }
public signal void stopped ();
/* removes old tables */
private void cleanup_tables ()
/* removes old games */
private void cleanup_games ()
{
lock (tables) {
foreach (var game in tables) {
if (game.state != GameState.STOPPED && game.players.length() < 1) {
tables.remove (game);
debug ("Cleaned up table %p", game);
lock (games) {
foreach (var game in games) {
if (game.players.length () < 1 && !game.state.is_open ()) {
games.remove (game);
debug ("Cleaned up game %p", game);
}
}
}
}
/* get the most populated open table available, maybe creating one */
private Game? pick_open_table ()
/* get the most populated open game available, maybe creating one */
private Game? pick_open_game ()
{
Game? candidate = null;
uint n_open_tables = 0;
uint n_open_games = 0;
lock (tables) {
foreach (var game in tables) {
if (game.state == GameState.STOPPED &&
lock (games) {
foreach (var game in games) {
if (game.state.is_open () &&
(candidate == null || candidate.players.length() < game.players.length())) {
candidate = game;
}
n_open_tables++;
n_open_games++;
}
/* if there is no open tables but room for more, create a new one */
if (candidate == null && n_open_tables < max_tables) {
/* if there is no open games but room for more, create a new one */
if (candidate == null && n_open_games < max_games) {
candidate = new Game (tileset, null);
tables.prepend (candidate);
debug ("Added table %p", candidate);
games.prepend (candidate);
debug ("Added game %p, which describes as '%s'", candidate, candidate.describe().to_string());
}
}
......@@ -137,16 +137,28 @@ public class Server: ThreadedSocketService
private bool handle_join (JoinMessage msg, Client client)
throws Error
{
Game? table = pick_open_table ();
if (table == null) {
warning ("A player tried to join, but there is no open table. " +
"We should tell her, but we just do nothing yet. " +
"She's pretty much fucked.");
Game? game = null;
if (msg.game_index == -1) {
game = pick_open_game ();
} else {
game = games.nth_data (msg.game_index);
}
if (game == null || !game.state.is_open ()) {
debug ("Bad game %d, sending fail join message", msg.game_index);
var join = new JoinMessage (msg.game_index);
join.status = JoinMessage.State.FAILED;
client.send (join);
return false;
}
client.join (table);
debug ("Accepting game join");
var join = new JoinMessage (msg.game_index);
join.status = JoinMessage.State.OK;
client.send (join);
client.join (game);