GHang

Gtk+ 2.0

Description

Hangman game written with Gtk+ 2.0 library


Source

#include <gtk/gtk.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#if !defined(WIN32)
#include <sys/stat.h>
#include <fcntl.h>
#endif

#define GHANG_WORD ghang_get_word ()
#if !defined(WIN32)
# define xrand devrand
#else
# define xrand rand
#endif

static void press_letter (GtkWidget *, GtkLabel *);
static gboolean expose_event (GtkWidget *, GdkEventExpose *, gpointer);

char *ghang_current_word = NULL;
int ghang_current_word_len = 0;
int ghang_current_err = 0;
int ghang_max_word = 0;
int ghang_word_processing = 0;
GSList *list = NULL;

GtkWidget *window, *draw;

#if !defined(WIN32)
gint64 devrand(void)
{
 gint64 c;
 int fd;

 if ((fd = open("/dev/random", O_RDONLY)) == -1) {
    fprintf(stderr, "Cannot open `%s'.\n"
                    "Error: %s.\n", "/dev/random", strerror(errno));
    exit(EXIT_FAILURE);
 }

 if ( read(fd, &c, sizeof(gint64)) < 1 ) {
      fprintf(stderr, "Cannot read %d byte from `%s'.\n"
                      "Error: %s.\n", sizeof(gint64),
                      "/dev/random", strerror(errno));
      exit(EXIT_FAILURE);
 }

 close(fd);

 return c;
}
#endif

static void
ghang_win (void)
{
  GtkWidget *dialog;

  dialog = gtk_message_dialog_new_with_markup (GTK_WINDOW (window), 0,
                                               GTK_MESSAGE_INFO,
                                               GTK_BUTTONS_CLOSE,
                                               "<big><b>You Win</b></big>");
  gtk_window_set_title (GTK_WINDOW (dialog), "Win");
  gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
  gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (window));
  gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER_ON_PARENT);
  gtk_dialog_run (GTK_DIALOG (dialog));
  gtk_widget_destroy (dialog);
  gtk_main_quit();
}

static void
ghang_lose (void)
{
  GtkWidget *dialog;

  dialog = gtk_message_dialog_new_with_markup (GTK_WINDOW (window), 0,
                                               GTK_MESSAGE_ERROR,
                                               GTK_BUTTONS_CLOSE,
                                               "<big><b>You Lose</b></big>");
  gtk_window_set_title (GTK_WINDOW (dialog), "Lose");
  gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
  gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (window));
  gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER_ON_PARENT);
  gtk_dialog_run (GTK_DIALOG (dialog));
  gtk_widget_destroy (dialog);
  gtk_main_quit();
}

gpointer
_error (gchar * message)
{
  GtkWidget *dialog;

  dialog = gtk_message_dialog_new_with_markup (GTK_WINDOW (window), 0,
                                               GTK_MESSAGE_ERROR,
                                               GTK_BUTTONS_CLOSE, message);
  gtk_window_set_title (GTK_WINDOW (dialog), "Error");
  gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
  gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (window));
  gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER_ON_PARENT);
  gtk_dialog_run (GTK_DIALOG (dialog));
  gtk_widget_destroy (dialog);

  return NULL;
}

static void
ghang_load_word (void)
{
  GIOStatus res;
  char *line = NULL;
  GError *error = NULL;
  GString *str_error, *message;
  GIOChannel *of;

  /* Opens the file to read */
  if (!(of = g_io_channel_new_file ("GHang.word", "r", &error)))
    {
      message = g_string_new ("<big><b>Error while opening the file</b></big>\n\n");
      switch (error->code)
        {
        case G_FILE_ERROR_PERM:
        case G_FILE_ERROR_ACCES:
          g_string_append (message, "File cannot be read because you don't have\n"
                                    "sufficient rights.");
          break;
        case G_FILE_ERROR_NOENT:
        case G_FILE_ERROR_NXIO:
        case G_FILE_ERROR_NODEV:
          g_string_append (message, "File cannot be open because it wasn't found.");
          break;
        case G_FILE_ERROR_NAMETOOLONG:
          g_string_append (message, "File cannot be read because the name is too\n"
                                    "long. Usually max length is 255.");
          break;
        case G_FILE_ERROR_TXTBSY:
          g_string_append (message, "File cannot be open because is used by other\n"
                                    "operations or processes.");
          break;
        case G_FILE_ERROR_LOOP:
          g_string_append (message, "The file you're trying to open is a symbolic\n"
                                    "link to another symbolic link.\n"
                                    "This then creates an endless cycle of\n"
                                    "links and makes it impossible to determine\n"
                                    "the final file to be opened.");
          break;
        case G_FILE_ERROR_NOMEM:
          g_string_free (message, TRUE);
          message = g_string_new ("<b><big>Space in memory exhausted!</big></b>\n\n"
                                  "Space in virtual memory is exhausted, this makes\n"
                                  "it impossible to load the file into RAM.");
          break;
        case G_FILE_ERROR_IO:
          g_string_append (message, "File cannot be read because a I/O error has occurred.\n"
                                    "Check that the device you are trying to read from\n"
                                    "isn't damaged.");
          break;
        case G_FILE_ERROR_FAILED:
        default:
          g_string_append (message, "An unknown error has occurred while opening the file.");
          break;
        }

      _error (message->str);
      g_string_free (message, TRUE);
      g_error_free (error);
      exit (EXIT_FAILURE);
    }

  /* Reading all rows */
  do
    {
      res = g_io_channel_read_line (of, &line, NULL, NULL, &error);

      /* Checks that no error occurred, if there were,
       * returns a dialog error and exit from while.
       */
      switch (res)
        {
        case G_IO_STATUS_ERROR:
          str_error =
            g_string_new ("<big><b>Error in file type recognition</b></big>\n\n");

          switch (error->code)
            {
            case G_IO_CHANNEL_ERROR_IO:
              g_string_append (str_error, "An I/O error does not allow you to read the\n"
                                          "file where magic numbers and relative descriptions\n"
                                          "are stored.");
              break;
            case G_IO_CHANNEL_ERROR_ISDIR:
              g_string_append (str_error, "File containing the magic numbers and relative\n"
                                          "descriptions cannot be read for an unknown error.");
              break;
            default:
              g_string_append (str_error, "File containing the magic numbers and relative\n"
                                          "descriptions cannot be read for a unknown error.");
              break;
            }

          _error (str_error->str);

          g_string_free (str_error, TRUE);
          g_error_free (error);
          exit (EXIT_FAILURE);
          break;
        case G_IO_STATUS_AGAIN:
          str_error =
            g_string_new ("<big><b>Error in file type recognition</b></big>");
          g_string_append (str_error, "File containing the magic numbers and relative\n"
                                      "descriptions is temporarily unavailable");
          _error (str_error->str);

          g_string_free (str_error, TRUE);
          g_error_free (error);
          exit (EXIT_FAILURE);
          break;
        case G_IO_STATUS_NORMAL:
          break;
        case G_IO_STATUS_EOF:
          break;
        }

      if (res == G_IO_STATUS_AGAIN || res == G_IO_STATUS_ERROR)
        {
          break;
        }

      if (res != G_IO_STATUS_EOF)
        {
          if ( line && *line != '\0' && strlen(line) > 3 ) {
               line[strlen(line)-1] = 0x00;
               list = g_slist_append (list, (gpointer) g_strdup (line));
               ghang_max_word++;
          }
          g_free (line);
        }
    }
  while (res != G_IO_STATUS_EOF);

  g_io_channel_close (of);
}

static char *
ghang_word(int i)
{
 GSList *current;

current = g_slist_nth(list, i);
 return ((char *) current->data);
}

static char *
ghang_get_word (void)
{
 char first, end;
 gboolean chk = FALSE;
 GString *str;
 int i;

 if ( !ghang_word_processing )
      ghang_load_word ();
 i = (unsigned int) xrand()%ghang_max_word;
 ghang_current_word = g_ascii_strup (ghang_word(i), -1);

 first = *ghang_current_word;
 ghang_current_word_len = strlen (ghang_current_word);
 end = ghang_current_word[ghang_current_word_len-1];
 str = g_string_new (g_strdup_printf("%c", first));

 for ( i = 1; i < ghang_current_word_len-1; ++i ) {
       if ( ghang_current_word[i] == first ||
            ghang_current_word[i] == end ) {
            g_string_append_c (str, ghang_current_word[i]);
            continue;
       }
      chk = TRUE;
       g_string_append_c (str, '_');
 }
 g_string_append_c (str, end);

 if ( !chk ) {
      if ( ghang_word_processing == ghang_max_word ) {
           fprintf(stderr, "No valid string found.\n");
           exit (EXIT_FAILURE);
      }
      ghang_word_processing++;
      return ghang_get_word();
 }

 g_slist_free (list);
 return g_strdup_printf("<big><b>%s</b></big>", str->str);
}

int
main(int argc, char **argv)
{
 GtkWidget *title, *vbox, *text;
 GtkWidget *hbox, *letter;
 int i;

 gtk_init (&argc, &argv);

 /* Window */
 window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
 gtk_window_set_default_size (GTK_WINDOW(window), 300, 430);
 gtk_window_set_title (GTK_WINDOW(window), "GHang");

 /* Vertical Box */
 vbox = gtk_vbox_new (FALSE, 2);
 gtk_container_add (GTK_CONTAINER(window), vbox);

 /* Title */
 title = gtk_label_new (NULL);
 gtk_label_set_markup (GTK_LABEL(title),
                       "<big><b>GHang</b></big>\n"
                       "<i>Semplice gioco dell'impiccato</i>");
 gtk_label_set_justify (GTK_LABEL(title), GTK_JUSTIFY_CENTER);
 gtk_box_pack_start (GTK_BOX(vbox), title, FALSE, TRUE, 2);

 /* Separator */
 gtk_box_pack_start (GTK_BOX(vbox), gtk_hseparator_new(), FALSE, TRUE, 0);

 /* Drawing Area */
 draw = gtk_drawing_area_new ();
 GTK_WIDGET_SET_FLAGS (draw, GTK_CAN_FOCUS);
 gtk_box_pack_start (GTK_BOX(vbox), draw, TRUE, TRUE, 0);

 /* Text */
 text = gtk_label_new (NULL);
 gtk_label_set_markup (GTK_LABEL(text), GHANG_WORD);
 gtk_box_pack_start (GTK_BOX(vbox), text, FALSE, TRUE, 0);

 /* Separator */
 gtk_box_pack_start (GTK_BOX(vbox), gtk_hseparator_new(), FALSE, TRUE, 0);

 /* HBox */
 hbox = gtk_hbox_new (FALSE, 2);
 gtk_box_pack_start (GTK_BOX(vbox), hbox, FALSE, TRUE, 2);

 /* Letter Button */
 for ( i = 0; i < 13; ++i ) {
       letter = gtk_button_new_with_label (g_strdup_printf ("%c", i + 65));
       gtk_button_set_relief (GTK_BUTTON(letter), GTK_RELIEF_NONE);
       gtk_box_pack_start (GTK_BOX(hbox), letter, FALSE, TRUE, 0);
       gtk_widget_set_usize (letter, 30, -1);
       if ( i == (ghang_current_word[0] - 65) ||
            i == (ghang_current_word[ghang_current_word_len-1] - 65) ) {
            gtk_widget_set_sensitive (letter, FALSE);
       }
       else {
            g_signal_connect (G_OBJECT (letter), "clicked",
                              G_CALLBACK (press_letter), (gpointer) GTK_LABEL(text));
       }
 }

 hbox = gtk_hbox_new (FALSE, 2);
 gtk_box_pack_start (GTK_BOX(vbox), hbox, FALSE, TRUE, 2);

 for ( i = 13; i < 26; ++i ) {
       letter = gtk_button_new_with_label (g_strdup_printf ("%c", i + 65));
       gtk_button_set_relief (GTK_BUTTON(letter), GTK_RELIEF_NONE);
       gtk_box_pack_start (GTK_BOX(hbox), letter, FALSE, TRUE, 0);
       gtk_widget_set_usize (letter, 30, -1);
       if ( i == (ghang_current_word[0] - 65) ||
            i == (ghang_current_word[ghang_current_word_len-1] - 65) ) {
            gtk_widget_set_sensitive (letter, FALSE);
       }
       else {
            g_signal_connect (G_OBJECT (letter), "clicked",
                              G_CALLBACK (press_letter), (gpointer) GTK_LABEL(text));
       }
 }

 g_signal_connect (G_OBJECT (window), "delete_event",
                   G_CALLBACK (gtk_main_quit), NULL);
 g_signal_connect (G_OBJECT (draw), "expose-event",
                   G_CALLBACK (expose_event), NULL);
 g_signal_connect (G_OBJECT (window), "destroy",
                   G_CALLBACK (gtk_main_quit), NULL);

 gtk_widget_show_all (window);
 gtk_main();

 return 0;
}

static void
ghang_write_err (void)
{
 GdkWindow *window = draw->window;
 GdkGC *gc = draw->style->fg_gc[GTK_WIDGET_STATE(draw)];
 int i;

 switch ( ghang_current_err ) {
  case 1: gdk_draw_arc (window, gc, TRUE, 203, 95, 45, 45, 0, 64 * 360);
          break;
  case 2: for ( i = 0; i < 7; ++i )
                gdk_draw_line (window, gc, 222 + i, 135, 222 + i, 210);
          break;
  case 3: for ( i = 0; i < 7; ++i )
                gdk_draw_line (window, gc, 228, 150 + i, 260, 170 + i);
          break;
  case 4: for ( i = 0; i < 7; ++i )
                gdk_draw_line (window, gc, 223, 150 + i, 191, 170 + i);
          break;
  case 5: for ( i = 0; i < 11; ++i )
                gdk_draw_line (window, gc, 228, 198 + i, 250, 233 + i);
          break;
  case 6: for ( i = 0; i < 11; ++i )
                gdk_draw_line (window, gc, 223, 198 + i, 201, 233 + i);
          break;
 }
}

static void
press_letter (GtkWidget *button, GtkLabel *text)
{
 char c, *t;
 int i;
 gboolean chk[2] = { FALSE };
 GString *str;

c = *((char *) gtk_button_get_label (GTK_BUTTON (button)));
 t = (char *) gtk_label_get_text (text);

 str = g_string_new ("");
 for ( i = 0; i < ghang_current_word_len; ++i ) {
       if ( ghang_current_word[i] == c ) {
            g_string_append_c(str, c);
            chk[0] = TRUE;
            continue;
       }
       switch ( t[i] ) {
        case '_': g_string_append_c(str, '_');
                  chk[1] = TRUE;
                  break;
         default: g_string_append_c(str, t[i]);
                  break;
       }
 }

 gtk_label_set_markup (text, g_strdup_printf("<big><b>%s</b></big>", str->str));
 g_string_free (str, TRUE);

 gtk_widget_set_sensitive (button, FALSE);

 if ( !chk[0] && chk[1] ) {
      ghang_current_err++;
      ghang_write_err ();
      if ( ghang_current_err == 6 ) {
           ghang_lose();
      }
 }
 else if ( !chk[1] ) {
           ghang_win();
 }
}

static gboolean
expose_event (GtkWidget *draw, GdkEventExpose *event, gpointer data)
{
 GdkWindow *window = draw->window;
 GdkGC *gc = draw->style->fg_gc[GTK_WIDGET_STATE(draw)];
 int i, c;

 gdk_draw_rectangle (window, gc, FALSE, 10, 10, 390, 290);

 for ( i = 0; i < 10; ++i ) {
       gdk_draw_line (window, gc, 100 + i, 30, 100 + i, 280);
       gdk_draw_line (window, gc, 30, 275 + i, 200, 275 + i);
       gdk_draw_line (window, gc, 100, 30 + i, 230, 30 + i);
       gdk_draw_line (window, gc, 221 + i, 30, 221 + i, 100);
       for ( c = i; c < i+5; ++c )
             gdk_draw_line (window, gc, 150 + c, 39, 110, 79 + c);
 }

 return TRUE;
}