/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/*
* Hitori
* Copyright (C) Philip Withnall 2007-2009 <philip@tecnocode.co.uk>
*
* Hitori 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.
*
* Hitori 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 Hitori. If not, see <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdlib.h>
#include <gtk/gtk.h>
#include <glib/gi18n.h>
#include <glib/gprintf.h>
#include "main.h"
#include "interface.h"
#include "generator.h"
static void constructed (GObject *object);
static void get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
static void set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec);
static void startup (GApplication *application);
static void activate (GApplication *application);
static gint handle_command_line (GApplication *application, GApplicationCommandLine *command_line);
typedef struct {
/* Command line parameters. */
gboolean debug;
guint seed;
} HitoriApplicationPrivate;
typedef enum {
PROP_DEBUG = 1,
PROP_SEED
} HitoriProperty;
G_DEFINE_TYPE_WITH_PRIVATE (HitoriApplication, hitori_application, GTK_TYPE_APPLICATION)
static void
hitori_application_class_init (HitoriApplicationClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
GApplicationClass *gapplication_class = G_APPLICATION_CLASS (klass);
gobject_class->constructed = constructed;
gobject_class->get_property = get_property;
gobject_class->set_property = set_property;
gapplication_class->startup = startup;
gapplication_class->activate = activate;
gapplication_class->command_line = handle_command_line;
g_object_class_install_property (gobject_class, PROP_DEBUG,
g_param_spec_boolean ("debug",
"Debugging Mode", "Whether debugging mode is active.",
FALSE,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_SEED,
g_param_spec_uint ("seed",
"Generation Seed", "Seed controlling generation of the board.",
0, G_MAXUINT, 0,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
}
static void
hitori_application_init (HitoriApplication *self)
{
HitoriApplicationPrivate *priv;
priv = hitori_application_get_instance_private (self);
priv->debug = FALSE;
priv->seed = 0;
}
static void
constructed (GObject *object)
{
/* Set various properties up */
g_application_set_application_id (G_APPLICATION (object), "org.gnome.Hitori");
g_application_set_flags (G_APPLICATION (object), G_APPLICATION_HANDLES_COMMAND_LINE);
/* Localisation */
bindtextdomain (PACKAGE, PACKAGE_LOCALE_DIR);
bind_textdomain_codeset (PACKAGE, "UTF-8");
textdomain (PACKAGE);
g_set_application_name (_("Hitori"));
gtk_window_set_default_icon_name ("org.gnome.Hitori");
/* Chain up to the parent class */
G_OBJECT_CLASS (hitori_application_parent_class)->constructed (object);
}
static void
get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
{
HitoriApplicationPrivate *priv;
priv = hitori_application_get_instance_private (HITORI_APPLICATION (object));
switch (property_id) {
case PROP_DEBUG:
g_value_set_boolean (value, priv->debug);
break;
case PROP_SEED:
g_value_set_uint (value, priv->seed);
break;
default:
/* We don't have any other property... */
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
{
HitoriApplicationPrivate *priv;
priv = hitori_application_get_instance_private (HITORI_APPLICATION (object));
switch (property_id) {
case PROP_DEBUG:
priv->debug = g_value_get_boolean (value);
break;
case PROP_SEED:
priv->seed = g_value_get_uint (value);
break;
default:
/* We don't have any other property... */
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
debug_handler (const char *log_domain, GLogLevelFlags log_level, const char *message, HitoriApplication *self)
{
HitoriApplicationPrivate *priv;
priv = hitori_application_get_instance_private (self);
/* Only display debug messages if we've been run with --debug */
if (priv->debug == TRUE) {
g_log_default_handler (log_domain, log_level, message, NULL);
}
}
static void
startup (GApplication *application)
{
/* Chain up. */
G_APPLICATION_CLASS (hitori_application_parent_class)->startup (application);
/* Debug log handling */
g_log_set_handler (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, (GLogFunc) debug_handler, application);
}
static void
activate (GApplication *application)
{
HitoriApplication *self = HITORI_APPLICATION (application);
HitoriApplicationPrivate *priv;
priv = hitori_application_get_instance_private (self);
/* Create the interface. */
if (self->window == NULL) {
GdkRectangle geometry;
HitoriUndo *undo;
gboolean window_maximized;
gchar *size_str;
/* Setup */
self->debug = priv->debug;
self->settings = g_settings_new ("org.gnome.hitori");
size_str = g_settings_get_string (self->settings, "board-size");
self->board_size = g_ascii_strtoull (size_str, NULL, 10);
g_free (size_str);
if (self->board_size > MAX_BOARD_SIZE) {
GVariant *default_size = g_settings_get_default_value (self->settings, "board-size");
g_variant_get (default_size, "s", &size_str);
g_variant_unref (default_size);
self->board_size = g_ascii_strtoull (size_str, NULL, 10);
g_free (size_str);
g_assert (self->board_size <= MAX_BOARD_SIZE);
}
undo = g_new0 (HitoriUndo, 1);
undo->type = UNDO_NEW_GAME;
self->undo_stack = undo;
/* Showtime! */
hitori_create_interface (self);
hitori_generate_board (self, self->board_size, priv->seed);
/* Restore window position and size */
window_maximized = g_settings_get_boolean (self->settings,
"window-maximized");
g_settings_get (self->settings,
"window-position", "(ii)",
&geometry.x, &geometry.y);
g_settings_get (self->settings,
"window-size", "(ii)",
&geometry.width, &geometry.height);
if (window_maximized) {
gtk_window_maximize (GTK_WINDOW (self->window));
} else {
if (geometry.x > -1 && geometry.y > -1) {
gtk_window_move (GTK_WINDOW (self->window),
geometry.x, geometry.y);
}
if (geometry.width >= 0 && geometry.height >= 0) {
gtk_window_resize (GTK_WINDOW (self->window),
geometry.width, geometry.height);
}
}
gtk_window_set_application (GTK_WINDOW (self->window), GTK_APPLICATION (self));
gtk_widget_show_all (self->window);
}
/* Bring it to the foreground */
gtk_window_present (GTK_WINDOW (self->window));
}
static gint
handle_command_line (GApplication *application, GApplicationCommandLine *command_line)
{
HitoriApplicationPrivate *priv = hitori_application_get_instance_private (HITORI_APPLICATION (application));
GOptionContext *context;
GError *error = NULL;
gchar **args, **argv;
gint argc, i, status = 0;
const GOptionEntry options[] = {
{ "debug", 0, 0, G_OPTION_ARG_NONE, &(priv->debug), N_("Enable debug mode"), NULL },
/* Translators: This means to choose a number as the "seed" for random number generation used when creating a board */
{ "seed", 0, 0, G_OPTION_ARG_INT, &(priv->seed), N_("Seed the board generation"), NULL },
{ NULL }
};
args = g_application_command_line_get_arguments (command_line, &argc);
/* We have to make an extra copy of the array, since g_option_context_parse() assumes that it can remove strings from the array without
* freeing them. */
argv = g_new (gchar*, argc + 1);
for (i = 0; i <= argc; i++) {
argv[i] = args[i];
}
/* Options */
context = g_option_context_new (_("- Play a game of Hitori"));
g_option_context_set_translation_domain (context, PACKAGE);
g_option_context_add_main_entries (context, options, PACKAGE);
if (g_option_context_parse (context, &argc, &argv, &error) == TRUE) {
/* Activate the remote instance */
g_application_activate (application);
status = 0;
} else {
/* Print an error */
g_application_command_line_printerr (command_line, _("Command line options could not be parsed: %s\n"), error->message);
g_error_free (error);
status = 1;
}
g_option_context_free (context);
g_free (argv);
g_strfreev (args);
return status;
}
HitoriApplication *
hitori_application_new (void)
{
return HITORI_APPLICATION (g_object_new (HITORI_TYPE_APPLICATION, NULL));
}
void
hitori_new_game (Hitori *hitori, guint board_size)
{
hitori->made_a_move = FALSE;
hitori_generate_board (hitori, board_size, 0);
hitori_clear_undo_stack (hitori);
gtk_widget_queue_draw (hitori->drawing_area);
hitori_reset_timer (hitori);
hitori_start_timer (hitori);
/* Reset the cursor position */
hitori->cursor_position.x = 0;
hitori->cursor_position.y = 0;
}
void
hitori_clear_undo_stack (Hitori *hitori)
{
/* Clear the undo stack */
if (hitori->undo_stack != NULL) {
while (hitori->undo_stack->redo != NULL)
hitori->undo_stack = hitori->undo_stack->redo;
while (hitori->undo_stack->undo != NULL) {
hitori->undo_stack = hitori->undo_stack->undo;
g_free (hitori->undo_stack->redo);
if (hitori->undo_stack->type == UNDO_NEW_GAME)
break;
}
/* Reset the "new game" item */
hitori->undo_stack->undo = NULL;
hitori->undo_stack->redo = NULL;
}
g_simple_action_set_enabled (hitori->undo_action, FALSE);
g_simple_action_set_enabled (hitori->redo_action, FALSE);
}
void
hitori_set_board_size (Hitori *hitori, guint board_size)
{
/* Ask the user if they want to stop the current game, if they're playing at the moment */
if (hitori->processing_events == TRUE && hitori->made_a_move == TRUE) {
GtkWidget *dialog = gtk_message_dialog_new (GTK_WINDOW (hitori->window),
GTK_DIALOG_MODAL,
GTK_MESSAGE_QUESTION,
GTK_BUTTONS_NONE,
_("Do you want to stop the current game?"));
gtk_dialog_add_buttons (GTK_DIALOG (dialog),
_("Keep _Playing"), GTK_RESPONSE_REJECT,
_("_New Game"), GTK_RESPONSE_ACCEPT,
NULL);
if (gtk_dialog_run (GTK_DIALOG (dialog)) != GTK_RESPONSE_ACCEPT) {
gtk_widget_destroy (dialog);
return;
}
gtk_widget_destroy (dialog);
}
/* Kill the current game and resize the board */
hitori_new_game (hitori, board_size);
}
void
hitori_print_board (Hitori *hitori)
{
if (hitori->debug) {
HitoriVector iter;
for (iter.y = 0; iter.y < hitori->board_size; iter.y++) {
for (iter.x = 0; iter.x < hitori->board_size; iter.x++) {
if ((hitori->board[iter.x][iter.y].status & CELL_PAINTED) == FALSE)
g_printf ("%u ", hitori->board[iter.x][iter.y].num);
else
g_printf ("X ");
}
g_printf ("\n");
}
}
}
void
hitori_free_board (Hitori *hitori)
{
guint i;
if (hitori->board == NULL)
return;
for (i = 0; i < hitori->board_size; i++)
g_slice_free1 (sizeof (HitoriCell) * hitori->board_size, hitori->board[i]);
g_free (hitori->board);
}
void
hitori_enable_events (Hitori *hitori)
{
hitori->processing_events = TRUE;
if (hitori->undo_stack->redo != NULL)
g_simple_action_set_enabled (hitori->redo_action, TRUE);
if (hitori->undo_stack->undo != NULL)
g_simple_action_set_enabled (hitori->redo_action, TRUE);
g_simple_action_set_enabled (hitori->hint_action, TRUE);
hitori_start_timer (hitori);
}
void
hitori_disable_events (Hitori *hitori)
{
hitori->processing_events = FALSE;
g_simple_action_set_enabled (hitori->redo_action, FALSE);
g_simple_action_set_enabled (hitori->undo_action, FALSE);
g_simple_action_set_enabled (hitori->hint_action, FALSE);
hitori_pause_timer (hitori);
}
static void
set_timer_label (Hitori *hitori)
{
gchar *text = g_strdup_printf ("%02u∶\xE2\x80\x8E%02u", hitori->timer_value / 60, hitori->timer_value % 60);
gtk_label_set_text (hitori->timer_label, text);
g_free (text);
}
static gboolean
update_timer_cb (Hitori *hitori)
{
hitori->timer_value++;
set_timer_label (hitori);
return TRUE;
}
void
hitori_start_timer (Hitori *hitori)
{
// Remove any old timeout
hitori_pause_timer (hitori);
set_timer_label (hitori);
hitori->timeout_id = g_timeout_add_seconds (1, (GSourceFunc) update_timer_cb, hitori);
}
void
hitori_pause_timer (Hitori *hitori)
{
if (hitori->timeout_id > 0) {
g_source_remove (hitori->timeout_id);
hitori->timeout_id = 0;
}
}
void
hitori_reset_timer (Hitori *hitori)
{
hitori->timer_value = 0;
set_timer_label (hitori);
}
void
hitori_quit (Hitori *hitori)
{
static gboolean quitting = FALSE;
if (quitting == TRUE)
return;
quitting = TRUE;
hitori_free_board (hitori);
hitori_clear_undo_stack (hitori);
g_free (hitori->undo_stack); /* Clear the new game element */
if (hitori->window != NULL)
gtk_widget_destroy (hitori->window);
if (hitori->normal_font_desc != NULL)
pango_font_description_free (hitori->normal_font_desc);
if (hitori->painted_font_desc != NULL)
pango_font_description_free (hitori->painted_font_desc);
g_object_unref (hitori->settings);
g_application_quit (G_APPLICATION (hitori));
}
int
main (int argc, char *argv[])
{
HitoriApplication *app;
int status;
#if !GLIB_CHECK_VERSION (2, 35, 0)
g_type_init ();
#endif
app = hitori_application_new ();
status = g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);
return status;
}