source: rtems-graphics-toolkit/fltk-1.1.10/test/sudoku.cxx @ 513eea1

Last change on this file since 513eea1 was 513eea1, checked in by Joel Sherrill <joel.sherrill@…>, on 01/09/10 at 22:43:24

2010-01-08 Joel Sherrill <joel.sherrill@…>

fltk 1.1.10. imported

  • ORIGIN: Updated.
  • Property mode set to 100644
File size: 31.9 KB
Line 
1//
2// "$Id$"
3//
4// Sudoku game using the Fast Light Tool Kit (FLTK).
5//
6// Copyright 2005-2007 by Michael Sweet.
7//
8// This library is free software; you can redistribute it and/or
9// modify it under the terms of the GNU Library General Public
10// License as published by the Free Software Foundation; either
11// version 2 of the License, or (at your option) any later version.
12//
13// This library is distributed in the hope that it will be useful,
14// but WITHOUT ANY WARRANTY; without even the implied warranty of
15// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16// Library General Public License for more details.
17//
18// You should have received a copy of the GNU Library General Public
19// License along with this library; if not, write to the Free Software
20// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
21// USA.
22//
23// Please report all bugs and problems on the following page:
24//
25//     http://www.fltk.org/str.php
26//
27
28#include <FL/Fl.H>
29#include <FL/Enumerations.H>
30#include <FL/Fl_Window.H>
31#include <FL/Fl_Button.H>
32#include <FL/Fl_Group.H>
33#include <FL/fl_ask.H>
34#include <FL/fl_draw.H>
35#include <FL/Fl_Help_Dialog.H>
36#include <FL/Fl_Preferences.H>
37#include <FL/Fl_Sys_Menu_Bar.H>
38#include <FL/x.H>
39#include <stdio.h>
40#include <stdlib.h>
41#include <string.h>
42#include <time.h>
43#include <FL/math.h>
44
45#ifdef WIN32
46#  include "sudokurc.h"
47#elif !defined(__APPLE__)
48#  include "pixmaps/sudoku.xbm"
49#endif // WIN32
50
51// Audio headers...
52#include <config.h>
53
54#ifndef WIN32
55#  include <unistd.h>
56#endif // !WIN32
57
58#ifdef HAVE_ALSA_ASOUNDLIB_H
59#  define ALSA_PCM_NEW_HW_PARAMS_API
60#  include <alsa/asoundlib.h>
61#endif // HAVE_ALSA_ASOUNDLIB_H
62#ifdef __APPLE__
63#  include <CoreAudio/AudioHardware.h>
64#endif // __APPLE__
65#ifdef WIN32
66#  include <mmsystem.h>
67#endif // WIN32
68
69
70//
71// Default sizes...
72//
73
74#define GROUP_SIZE      160
75#define CELL_SIZE       50
76#define CELL_OFFSET     5
77#ifdef __APPLE__
78#  define MENU_OFFSET   0
79#else
80#  define MENU_OFFSET   25
81#endif // __APPLE__
82
83// Sound class for Sudoku...
84//
85// There are MANY ways to implement sound in a FLTK application.
86// The approach we are using here is to conditionally compile OS-
87// specific code into the application - CoreAudio for MacOS X, the
88// standard Win32 API stuff for Windows, ALSA or X11 for Linux, and
89// X11 for all others.  We have to support ALSA on Linux because the
90// current Xorg releases no longer support XBell() or the PC speaker.
91//
92// There are several good cross-platform audio libraries we could also
93// use, such as OpenAL, PortAudio, and SDL, however they were not chosen
94// for this application because of our limited use of sound.
95//
96// Many thanks to Ian MacArthur who provided sample code that led to
97// the CoreAudio implementation you see here!
98class SudokuSound {
99  // Private, OS-specific data...
100#ifdef __APPLE__
101  AudioDeviceID device;
102  AudioStreamBasicDescription format;
103  short *data;
104  int remaining;
105
106  static OSStatus audio_cb(AudioDeviceID device,
107                           const AudioTimeStamp *current_time,
108                           const AudioBufferList *data_in,
109                           const AudioTimeStamp *time_in,
110                           AudioBufferList *data_out,
111                           const AudioTimeStamp *time_out,
112                           void *client_data);
113#elif defined(WIN32)
114  HWAVEOUT      device;
115  HGLOBAL       header_handle;
116  LPWAVEHDR     header_ptr;
117  HGLOBAL       data_handle;
118  LPSTR         data_ptr;
119
120#else
121#  ifdef HAVE_ALSA_ASOUNDLIB_H
122  snd_pcm_t *handle;
123#  endif // HAVE_ALSA_ASOUNDLIB_H
124#endif // __APPLE__
125
126  // Common data...
127  static int frequencies[9];
128  static short *sample_data[9];
129  static int sample_size;
130
131  public:
132
133  SudokuSound();
134  ~SudokuSound();
135
136  void  play(char note);
137};
138
139
140// Sudoku cell class...
141class SudokuCell : public Fl_Widget {
142  bool          readonly_;
143  int           value_;
144  int           test_value_[9];
145
146  public:
147
148                SudokuCell(int X, int Y, int W, int H);
149  void          draw();
150  int           handle(int event);
151  void          readonly(bool r) { readonly_ = r; redraw(); }
152  bool          readonly() const { return readonly_; }
153  void          test_value(int v, int n) { test_value_[n] = v; redraw(); }
154  int           test_value(int n) const { return test_value_[n]; }
155  void          value(int v) {
156                  value_ = v;
157                  for (int i = 0; i < 8; i ++) test_value_[i] = 0;
158                  redraw();
159                }
160  int           value() const { return value_; }
161};
162
163
164// Sudoku window class...
165class Sudoku : public Fl_Window {
166  Fl_Sys_Menu_Bar *menubar_;
167  Fl_Group      *grid_;
168  time_t        seed_;
169  char          grid_values_[9][9];
170  SudokuCell    *grid_cells_[9][9];
171  Fl_Group      *grid_groups_[3][3];
172  int           difficulty_;
173  SudokuSound   *sound_;
174
175  static void   check_cb(Fl_Widget *widget, void *);
176  static void   close_cb(Fl_Widget *widget, void *);
177  static void   diff_cb(Fl_Widget *widget, void *d);
178  static void   update_helpers_cb(Fl_Widget *, void *);
179  static void   help_cb(Fl_Widget *, void *);
180  static void   mute_cb(Fl_Widget *widget, void *);
181  static void   new_cb(Fl_Widget *widget, void *);
182  static void   reset_cb(Fl_Widget *widget, void *);
183  static void   restart_cb(Fl_Widget *widget, void *);
184  void          set_title();
185  static void   solve_cb(Fl_Widget *widget, void *);
186
187  static Fl_Help_Dialog *help_dialog_;
188  static Fl_Preferences prefs_;
189  public:
190
191                Sudoku();
192                ~Sudoku();
193
194  void          check_game(bool highlight = true);
195  void          load_game();
196  void          new_game(time_t seed);
197  int           next_value(SudokuCell *c);
198  void          resize(int X, int Y, int W, int H);
199  void          save_game();
200  void          solve_game();
201  void          update_helpers();
202};
203
204
205// Sound class globals...
206int SudokuSound::frequencies[9] = {
207  880,  // A(5)
208  988,  // B(5)
209  1046, // C(5)
210  1174, // D(5)
211  1318, // E(5)
212  1396, // F(5)
213  1568, // G(5)
214  1760, // H (A6)
215  1976  // I (B6)
216};
217short *SudokuSound::sample_data[9] = { 0 };
218int SudokuSound::sample_size = 0;
219
220
221// Initialize the SudokuSound class
222SudokuSound::SudokuSound() {
223  sample_size = 0;
224
225#ifdef __APPLE__
226  remaining = 0;
227
228  UInt32 size = sizeof(device);
229
230  if (AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice,
231                               &size, (void *)&device) != noErr) return;
232
233  size = sizeof(format);
234  if (AudioDeviceGetProperty(device, 0, false, kAudioDevicePropertyStreamFormat,
235                             &size, &format) != noErr) return;
236
237  // Set up a format we like...
238  format.mSampleRate       = 44100.0;   // 44.1kHz
239  format.mChannelsPerFrame = 2;         // stereo
240
241  if (AudioDeviceSetProperty(device, NULL, 0, false,
242                             kAudioDevicePropertyStreamFormat,
243                             sizeof(format), &format) != noErr) return;
244
245  // Check we got linear pcm - what to do if we did not ???
246  if (format.mFormatID != kAudioFormatLinearPCM) return;
247
248  // Attach the callback
249  if (AudioDeviceAddIOProc(device, audio_cb, (void *)this) != noErr) return;
250
251  // Start the device...
252  AudioDeviceStart(device, audio_cb);
253
254  sample_size = (int)format.mSampleRate / 20;
255
256#elif defined(WIN32)
257  WAVEFORMATEX  format;
258
259  memset(&format, 0, sizeof(format));
260  format.cbSize          = sizeof(format);
261  format.wFormatTag      = WAVE_FORMAT_PCM;
262  format.nChannels       = 2;
263  format.nSamplesPerSec  = 44100;
264  format.nAvgBytesPerSec = 44100 * 4;
265  format.nBlockAlign     = 4;
266  format.wBitsPerSample  = 16;
267
268  data_handle = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, format.nSamplesPerSec / 5);
269  if (!data_handle) return;
270
271  data_ptr = (LPSTR)GlobalLock(data_handle);
272
273  header_handle = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, sizeof(WAVEHDR));
274  if (!header_handle) return;
275
276  header_ptr = (WAVEHDR *)GlobalLock(header_handle);
277
278  header_ptr->lpData         = data_ptr;
279  header_ptr->dwBufferLength = format.nSamplesPerSec / 5;
280  header_ptr->dwFlags        = 0;
281  header_ptr->dwLoops        = 0;
282
283  if (waveOutOpen(&device, WAVE_MAPPER, &format, 0, 0, WAVE_ALLOWSYNC)
284          != MMSYSERR_NOERROR) return;
285
286  waveOutPrepareHeader(device, header_ptr, sizeof(WAVEHDR));
287
288  sample_size = 44100 / 20;
289
290#else
291#  ifdef HAVE_ALSA_ASOUNDLIB_H
292  handle = NULL;
293
294  if (snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0) >= 0) {
295    // Initialize PCM sound stuff...
296    snd_pcm_hw_params_t *params;
297
298    snd_pcm_hw_params_alloca(&params);
299    snd_pcm_hw_params_any(handle, params);
300    snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
301    snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16);
302    snd_pcm_hw_params_set_channels(handle, params, 2);
303    unsigned rate = 44100;
304    int dir;
305    snd_pcm_hw_params_set_rate_near(handle, params, &rate, &dir);
306    snd_pcm_uframes_t period = (int)rate / 4;
307    snd_pcm_hw_params_set_period_size_near(handle, params, &period, &dir);
308
309    sample_size = rate / 20;
310
311    if (snd_pcm_hw_params(handle, params) < 0) {
312      sample_size = 0;
313      snd_pcm_close(handle);
314      handle = NULL;
315    }
316  }
317#  endif // HAVE_ALSA_ASOUNDLIB_H
318#endif // __APPLE__
319
320  if (sample_size) {
321    // Make each of the notes using a combination of sine and sawtooth waves
322    int attack = sample_size / 10;
323    int decay = 4 * sample_size / 5;
324
325    for (int i = 0; i < 9; i ++) {
326      sample_data[i] = new short[2 * sample_size];
327
328      short *sample_ptr = sample_data[i];
329
330      for (int j = 0; j < sample_size; j ++, sample_ptr += 2) {
331        double theta = 0.05 * frequencies[i] * j / sample_size;
332        double val = 0.5 * sin(2.0 * M_PI * theta) + theta - (int)theta - 0.5;
333
334        if (j < attack) {
335          *sample_ptr = (int)(32767 * val * j / attack);
336        } else if (j > decay) {
337          *sample_ptr = (int)(32767 * val * (sample_size - j + decay) /
338                              sample_size);
339        } else *sample_ptr = (int)(32767 * val);
340
341        sample_ptr[1] = *sample_ptr;
342      }
343    }
344  }
345}
346
347
348// Cleanup the SudokuSound class
349SudokuSound::~SudokuSound() {
350#ifdef __APPLE__
351  if (sample_size) {
352    AudioDeviceStop(device, audio_cb);
353    AudioDeviceRemoveIOProc(device, audio_cb);
354  }
355
356#elif defined(WIN32)
357  if (sample_size) {
358    waveOutClose(device);
359
360    GlobalUnlock(header_handle);
361    GlobalFree(header_handle);
362
363    GlobalUnlock(data_handle);
364    GlobalFree(data_handle);
365  }
366
367#else
368#  ifdef HAVE_ALSA_ASOUNDLIB_H
369  if (handle) {
370    snd_pcm_drain(handle);
371    snd_pcm_close(handle);
372  }
373#  endif // HAVE_ALSA_ASOUNDLIB_H
374#endif // __APPLE__
375
376  if (sample_size) {
377    for (int i = 0; i < 9; i ++) {
378      delete[] sample_data[i];
379    }
380  }
381}
382
383
384#ifdef __APPLE__
385// Callback function for writing audio data...
386OSStatus
387SudokuSound::audio_cb(AudioDeviceID device,
388                      const AudioTimeStamp *current_time,
389                      const AudioBufferList *data_in,
390                      const AudioTimeStamp *time_in,
391                      AudioBufferList *data_out,
392                      const AudioTimeStamp *time_out,
393                      void *client_data) {
394  SudokuSound *ss = (SudokuSound *)client_data;
395  int count;
396  float *buffer;
397
398  if (!ss->remaining) return noErr;
399
400  for (count = data_out->mBuffers[0].mDataByteSize / sizeof(float),
401          buffer = (float*) data_out->mBuffers[0].mData;
402       ss->remaining > 0 && count > 0;
403       count --, ss->data ++, ss->remaining --) {
404    *buffer++ = *(ss->data) / 32767.0;
405  }
406
407  while (count > 0) {
408    *buffer++ = 0.0;
409    count --;
410  }
411
412  return noErr;
413}
414#endif // __APPLE__
415
416
417// Play a note for 250ms...
418void SudokuSound::play(char note) {
419  Fl::check();
420
421#ifdef __APPLE__
422  // Point to the next note...
423  data      = sample_data[note - 'A'];
424  remaining = sample_size * 2;
425
426  // Wait for the sound to complete...
427  usleep(50000);
428
429#elif defined(WIN32)
430  if (sample_size) {
431    memcpy(data_ptr, sample_data[note - 'A'], sample_size * 4);
432
433    waveOutWrite(device, header_ptr, sizeof(WAVEHDR));
434
435    Sleep(50);
436  } else Beep(frequencies[note - 'A'], 50);
437
438#else
439#  ifdef HAVE_ALSA_ASOUNDLIB_H
440  if (handle) {
441    // Use ALSA to play the sound...
442    if (snd_pcm_writei(handle, sample_data[note - 'A'], sample_size) < 0) {
443      snd_pcm_prepare(handle);
444      snd_pcm_writei(handle, sample_data[note - 'A'], sample_size);
445    }
446    usleep(50000);
447    return;
448  }
449#  endif // HAVE_ALSA_ASOUNDLIB_H
450
451  // Just use standard X11 stuff...
452  XKeyboardState        state;
453  XKeyboardControl      control;
454
455  // Get original pitch and duration...
456  XGetKeyboardControl(fl_display, &state);
457
458  // Sound a tone for the given note...
459  control.bell_percent  = 100;
460  control.bell_pitch    = frequencies[note - 'A'];
461  control.bell_duration = 50;
462
463  XChangeKeyboardControl(fl_display,
464                         KBBellPercent | KBBellPitch | KBBellDuration,
465                         &control);
466  XBell(fl_display, 100);
467  XFlush(fl_display);
468
469  // Restore original pitch and duration...
470  control.bell_percent  = state.bell_percent;
471  control.bell_pitch    = state.bell_pitch;
472  control.bell_duration = state.bell_duration;
473
474  XChangeKeyboardControl(fl_display,
475                         KBBellPercent | KBBellPitch | KBBellDuration,
476                         &control);
477#endif // __APPLE__
478}
479
480
481// Create a cell widget
482SudokuCell::SudokuCell(int X, int Y, int W, int H)
483  : Fl_Widget(X, Y, W, H, 0) {
484  value(0);
485}
486
487
488// Draw cell
489void
490SudokuCell::draw() {
491  static Fl_Align align[8] = {
492    FL_ALIGN_TOP_LEFT,
493    FL_ALIGN_TOP,
494    FL_ALIGN_TOP_RIGHT,
495    FL_ALIGN_RIGHT,
496    FL_ALIGN_BOTTOM_RIGHT,
497    FL_ALIGN_BOTTOM,
498    FL_ALIGN_BOTTOM_LEFT,
499    FL_ALIGN_LEFT
500  };
501
502
503  // Draw the cell box...
504  if (readonly()) fl_draw_box(FL_UP_BOX, x(), y(), w(), h(), color());
505  else fl_draw_box(FL_DOWN_BOX, x(), y(), w(), h(), color());
506
507  // Draw the cell background...
508  if (Fl::focus() == this) {
509    Fl_Color c = fl_color_average(FL_SELECTION_COLOR, color(), 0.5f);
510    fl_color(c);
511    fl_rectf(x() + 4, y() + 4, w() - 8, h() - 8);
512    fl_color(fl_contrast(labelcolor(), c));
513  } else fl_color(labelcolor());
514
515  // Draw the cell value...
516  char s[2];
517
518  s[1] = '\0';
519
520  if (value_) {
521    s[0] = value_ + '0';
522
523    fl_font(FL_HELVETICA_BOLD, h() - 10);
524    fl_draw(s, x(), y(), w(), h(), FL_ALIGN_CENTER);
525  }
526
527  fl_font(FL_HELVETICA_BOLD, h() / 5);
528
529  for (int i = 0; i < 8; i ++) {
530    if (test_value_[i]) {
531      s[0] = test_value_[i] + '0';
532      fl_draw(s, x() + 5, y() + 5, w() - 10, h() - 10, align[i]);
533    }
534  }
535}
536
537
538// Handle events in cell
539int
540SudokuCell::handle(int event) {
541  switch (event) {
542    case FL_FOCUS :
543      Fl::focus(this);
544      redraw();
545      return 1;
546
547    case FL_UNFOCUS :
548      redraw();
549      return 1;
550
551    case FL_PUSH :
552      if (!readonly() && Fl::event_inside(this)) {
553        if (Fl::event_clicks()) {
554          // 2+ clicks increments/sets value
555          if (value()) {
556            if (value() < 9) value(value() + 1);
557            else value(1);
558          } else value(((Sudoku *)window())->next_value(this));
559        }
560
561        Fl::focus(this);
562        redraw();
563        return 1;
564      }
565      break;
566
567    case FL_KEYDOWN :
568      if (Fl::event_state() & FL_CTRL) break;
569      int key = Fl::event_key() - '0';
570      if (key < 0 || key > 9) key = Fl::event_key() - FL_KP - '0';
571      if (key > 0 && key <= 9) {
572        if (readonly()) {
573          fl_beep(FL_BEEP_ERROR);
574          return 1;
575        }
576
577        if (Fl::event_state() & (FL_SHIFT | FL_CAPS_LOCK)) {
578          int i;
579
580          for (i = 0; i < 8; i ++)
581            if (test_value_[i] == key) {
582              test_value_[i] = 0;
583              break;
584            }
585
586          if (i >= 8) {
587            for (i = 0; i < 8; i ++)
588              if (!test_value_[i]) {
589                test_value_[i] = key;
590                break;
591              }
592          }
593
594          if (i >= 8) {
595            for (i = 0; i < 7; i ++) test_value_[i] = test_value_[i + 1];
596            test_value_[i] = key;
597          }
598
599          redraw();
600        } else {
601          value(key);
602          do_callback();
603        }
604        return 1;
605      } else if (key == 0 || Fl::event_key() == FL_BackSpace ||
606                 Fl::event_key() == FL_Delete) {
607        if (readonly()) {
608          fl_beep(FL_BEEP_ERROR);
609          return 1;
610        }
611
612        value(0);
613        do_callback();
614        return 1;
615      }
616      break;
617  }
618
619  return Fl_Widget::handle(event);
620}
621
622
623// Sudoku class globals...
624Fl_Help_Dialog  *Sudoku::help_dialog_ = (Fl_Help_Dialog *)0;
625Fl_Preferences  Sudoku::prefs_(Fl_Preferences::USER, "fltk.org", "sudoku");
626
627
628// Create a Sudoku game window...
629Sudoku::Sudoku()
630  : Fl_Window(GROUP_SIZE * 3, GROUP_SIZE * 3 + MENU_OFFSET, "Sudoku")
631{
632  int j, k;
633  Fl_Group *g;
634  SudokuCell *cell;
635  static Fl_Menu_Item   items[] = {
636    { "&Game", 0, 0, 0, FL_SUBMENU },
637    { "&New Game", FL_COMMAND | 'n', new_cb, 0, FL_MENU_DIVIDER },
638    { "&Check Game", FL_COMMAND | 'c', check_cb, 0, 0 },
639    { "&Restart Game", FL_COMMAND | 'r', restart_cb, 0, 0 },
640    { "&Solve Game", FL_COMMAND | 's', solve_cb, 0, FL_MENU_DIVIDER },
641    { "&Update Helpers", 0, update_helpers_cb, 0, 0 },
642    { "&Mute Sound", FL_COMMAND | 'm', mute_cb, 0, FL_MENU_TOGGLE | FL_MENU_DIVIDER },
643    { "&Quit", FL_COMMAND | 'q', close_cb, 0, 0 },
644    { 0 },
645    { "&Difficulty", 0, 0, 0, FL_SUBMENU },
646    { "&Easy", 0, diff_cb, (void *)"0", FL_MENU_RADIO },
647    { "&Medium", 0, diff_cb, (void *)"1", FL_MENU_RADIO },
648    { "&Hard", 0, diff_cb, (void *)"2", FL_MENU_RADIO },
649    { "&Impossible", 0, diff_cb, (void *)"3", FL_MENU_RADIO },
650    { 0 },
651    { "&Help", 0, 0, 0, FL_SUBMENU },
652    { "&About Sudoku", FL_F + 1, help_cb, 0, 0 },
653    { 0 },
654    { 0 }
655  };
656
657
658  // Setup sound output...
659  prefs_.get("mute_sound", j, 0);
660  if (j) {
661    // Mute sound?
662    sound_ = NULL;
663    items[6].flags |= FL_MENU_VALUE;
664  } else sound_ = new SudokuSound();
665
666  // Menubar...
667  prefs_.get("difficulty", difficulty_, 0);
668  if (difficulty_ < 0 || difficulty_ > 3) difficulty_ = 0;
669
670  items[10 + difficulty_].flags |= FL_MENU_VALUE;
671
672  menubar_ = new Fl_Sys_Menu_Bar(0, 0, 3 * GROUP_SIZE, 25);
673  menubar_->menu(items);
674
675  // Create the grids...
676  grid_ = new Fl_Group(0, MENU_OFFSET, 3 * GROUP_SIZE, 3 * GROUP_SIZE);
677
678  for (j = 0; j < 3; j ++)
679    for (k = 0; k < 3; k ++) {
680      g = new Fl_Group(k * GROUP_SIZE, j * GROUP_SIZE + MENU_OFFSET,
681                       GROUP_SIZE, GROUP_SIZE);
682      g->box(FL_BORDER_BOX);
683      if ((int)(j == 1) ^ (int)(k == 1)) g->color(FL_DARK3);
684      else g->color(FL_DARK2);
685      g->end();
686
687      grid_groups_[j][k] = g;
688    }
689
690  for (j = 0; j < 9; j ++)
691    for (k = 0; k < 9; k ++) {
692      cell = new SudokuCell(k * CELL_SIZE + CELL_OFFSET +
693                                (k / 3) * (GROUP_SIZE - 3 * CELL_SIZE),
694                            j * CELL_SIZE + CELL_OFFSET + MENU_OFFSET +
695                                (j / 3) * (GROUP_SIZE - 3 * CELL_SIZE),
696                            CELL_SIZE, CELL_SIZE);
697      cell->callback(reset_cb);
698      grid_cells_[j][k] = cell;
699    }
700
701  // Set icon for window (MacOS uses app bundle for icon...)
702#ifdef WIN32
703  icon((char *)LoadIcon(fl_display, MAKEINTRESOURCE(IDI_ICON)));
704#elif !defined(__APPLE__)
705  fl_open_display();
706  icon((char *)XCreateBitmapFromData(fl_display, DefaultRootWindow(fl_display),
707                                     (char *)sudoku_bits, sudoku_width,
708                                     sudoku_height));
709#endif // WIN32
710
711  // Catch window close events...
712  callback(close_cb);
713
714  // Make the window resizable...
715  resizable(grid_);
716  size_range(3 * GROUP_SIZE, 3 * GROUP_SIZE + MENU_OFFSET, 0, 0, 5, 5, 1);
717
718  // Restore the previous window dimensions...
719  int X, Y, W, H;
720
721  if (prefs_.get("x", X, -1)) {
722    prefs_.get("y", Y, -1);
723    prefs_.get("width", W, 3 * GROUP_SIZE);
724    prefs_.get("height", H, 3 * GROUP_SIZE + MENU_OFFSET);
725
726    resize(X, Y, W, H);
727  }
728
729  set_title();
730}
731
732
733// Destroy the sudoku window...
734Sudoku::~Sudoku() {
735  if (sound_) delete sound_;
736}
737
738
739// Check for a solution to the game...
740void
741Sudoku::check_cb(Fl_Widget *widget, void *) {
742  ((Sudoku *)(widget->window()))->check_game();
743}
744
745
746// Check if the user has correctly solved the game...
747void
748Sudoku::check_game(bool highlight) {
749  bool empty = false;
750  bool correct = true;
751  int j, k, m;
752
753  // Check the game for right/wrong answers...
754  for (j = 0; j < 9; j ++)
755    for (k = 0; k < 9; k ++) {
756      SudokuCell *cell = grid_cells_[j][k];
757      int val = cell->value();
758
759      if (cell->readonly()) continue;
760
761      if (!val) empty = true;
762      else {
763        for (m = 0; m < 9; m ++)
764          if ((j != m && grid_cells_[m][k]->value() == val) ||
765              (k != m && grid_cells_[j][m]->value() == val)) break;
766
767        if (m < 9) {
768          if (highlight) {
769            cell->color(FL_YELLOW);
770            cell->redraw();
771          }
772
773          correct = false;
774        } else if (highlight) {
775          cell->color(FL_LIGHT3);
776          cell->redraw();
777        }
778      }
779    }
780
781  // Check subgrids for duplicate numbers...
782  for (j = 0; j < 9; j += 3)
783    for (k = 0; k < 9; k += 3)
784      for (int jj = 0; jj < 3; jj ++)
785        for (int kk = 0; kk < 3; kk ++) {
786          SudokuCell *cell = grid_cells_[j + jj][k + kk];
787          int val = cell->value();
788
789          if (cell->readonly() || !val) continue;
790
791          int jjj;
792
793          for (jjj = 0; jjj < 3; jjj ++) {
794            int kkk;
795
796            for (kkk = 0; kkk < 3; kkk ++)
797              if (jj != jjj && kk != kkk &&
798                  grid_cells_[j + jjj][k + kkk]->value() == val) break;
799
800            if (kkk < 3) break;
801          }
802
803          if (jjj < 3) {
804            if (highlight) {
805              cell->color(FL_YELLOW);
806              cell->redraw();
807            }
808
809            correct = false;
810          }
811        }
812
813  if (!empty && correct) {
814    // Success!
815    for (j = 0; j < 9; j ++) {
816      for (k = 0; k < 9; k ++) {
817        SudokuCell *cell = grid_cells_[j][k];
818        cell->color(FL_GREEN);
819        cell->readonly(1);
820      }
821
822      if (sound_) sound_->play('A' + grid_cells_[j][8]->value() - 1);
823    }
824  }
825}
826
827
828// Close the window, saving the game first...
829void
830Sudoku::close_cb(Fl_Widget *widget, void *) {
831  Sudoku *s = (Sudoku *)(widget->window() ? widget->window() : widget);
832
833  s->save_game();
834  s->hide();
835
836  if (help_dialog_) help_dialog_->hide();
837}
838
839
840// Set the level of difficulty...
841void
842Sudoku::diff_cb(Fl_Widget *widget, void *d) {
843  Sudoku *s = (Sudoku *)(widget->window() ? widget->window() : widget);
844  int diff = atoi((char *)d);
845
846  if (diff != s->difficulty_) {
847    s->difficulty_ = diff;
848    s->new_game(s->seed_);
849    s->set_title();
850
851    if (diff > 1)
852    {
853      // Display a message about the higher difficulty levels for the
854      // Sudoku zealots of the world...
855      int val;
856
857      prefs_.get("difficulty_warning", val, 0);
858
859      if (!val)
860      {
861        prefs_.set("difficulty_warning", 1);
862        fl_alert("Note: 'Hard' and 'Impossible' puzzles may have more than "
863                 "one possible solution.\n"
864                 "This is not an error or bug.");
865      }
866    }
867
868    prefs_.set("difficulty", s->difficulty_);
869  }
870}
871
872// Update the little marker numbers in all cells
873void
874Sudoku::update_helpers_cb(Fl_Widget *widget, void *) {
875  Sudoku *s = (Sudoku *)(widget->window() ? widget->window() : widget);
876  s->update_helpers();
877}
878
879void
880Sudoku::update_helpers() {
881  int j, k, m;
882
883  // First we delete any entries that the user may have made
884  for (j = 0; j < 9; j ++) {
885    for (k = 0; k < 9; k ++) {
886      SudokuCell *cell = grid_cells_[j][k];
887      for (m = 0; m < 8; m ++) {
888        cell->test_value(0, m);
889      }
890    }
891  }
892
893  // Now go through all cells and find out, what we can not be
894  for (j = 0; j < 81; j ++) {
895    char taken[10] = { 0 };
896    // Find our destination cell
897    int row = j / 9;
898    int col = j % 9;
899    SudokuCell *dst_cell = grid_cells_[row][col];
900    if (dst_cell->value()) continue;
901    // Find all values already taken in this row
902    for (k = 0; k < 9; k ++) {
903      SudokuCell *cell = grid_cells_[row][k];
904      int v = cell->value();
905      if (v) taken[v] = 1;
906    }
907    // Find all values already taken in this column
908    for (k = 0; k < 9; k ++) {
909      SudokuCell *cell = grid_cells_[k][col];
910      int v = cell->value();
911      if (v) taken[v] = 1;
912    }
913    // Now find all values already taken in this square
914    int ro = (row / 3) * 3;
915    int co = (col / 3) * 3;
916    for (k = 0; k < 3; k ++) {
917      for (m = 0; m < 3; m ++) {
918        SudokuCell *cell = grid_cells_[ro + k][co + m];
919        int v = cell->value();
920        if (v) taken[v] = 1;
921      }
922    }
923    // transfer our findings to the markers
924    for (m = 1, k = 0; m <= 9; m ++) {
925      if (!taken[m])
926        dst_cell->test_value(m, k ++);
927    }
928  }
929}
930
931
932// Show the on-line help...
933void
934Sudoku::help_cb(Fl_Widget *, void *) {
935  if (!help_dialog_) {
936    help_dialog_ = new Fl_Help_Dialog();
937
938    help_dialog_->value(
939        "<HTML>\n"
940        "<HEAD>\n"
941        "<TITLE>Sudoku Help</TITLE>\n"
942        "</HEAD>\n"
943        "<BODY BGCOLOR='#ffffff'>\n"
944
945        "<H2>About the Game</H2>\n"
946
947        "<P>Sudoku (pronounced soo-dough-coo with the emphasis on the\n"
948        "first syllable) is a simple number-based puzzle/game played on a\n"
949        "9x9 grid that is divided into 3x3 subgrids. The goal is to enter\n"
950        "a number from 1 to 9 in each cell so that each number appears\n"
951        "only once in each column and row. In addition, each 3x3 subgrid\n"
952        "may only contain one of each number.</P>\n"
953
954        "<P>This version of the puzzle is copyright 2005-2006 by Michael R\n"
955        "Sweet.</P>\n"
956
957        "<P><B>Note:</B> The 'Hard' and 'Impossible' difficulty\n"
958        "levels generate Sudoku puzzles with multiple possible solutions.\n"
959        "While some purists insist that these cannot be called 'Sudoku'\n"
960        "puzzles, the author (me) has personally solved many such puzzles\n"
961        "in published/printed Sudoku books and finds them far more\n"
962        "interesting than the simple single solution variety. If you don't\n"
963        "like it, don't play with the difficulty set to 'High' or\n"
964        "'Impossible'.</P>\n"
965
966        "<H2>How to Play the Game</H2>\n"
967
968        "<P>At the start of a new game, Sudoku fills in a random selection\n"
969        "of cells for you - the number of cells depends on the difficulty\n"
970        "level you use. Click in any of the empty cells or use the arrow\n"
971        "keys to highlight individual cells and press a number from 1 to 9\n"
972        "to fill in the cell. To clear a cell, press 0, Delete, or\n"
973        "Backspace. When you have successfully completed all subgrids, the\n"
974        "entire puzzle is highlighted in green until you start a new\n"
975        "game.</P>\n"
976
977        "<P>As you work to complete the puzzle, you can display possible\n"
978        "solutions inside each cell by holding the Shift key and pressing\n"
979        "each number in turn. Repeat the process to remove individual\n"
980        "numbers, or press a number without the Shift key to replace them\n"
981        "with the actual number to use.</P>\n"
982        "</BODY>\n"
983    );
984  }
985
986  help_dialog_->show();
987}
988
989
990// Load the game from saved preferences...
991void
992Sudoku::load_game() {
993  // Load the current values and state of each grid...
994  memset(grid_values_, 0, sizeof(grid_values_));
995
996  bool solved = true;
997
998  for (int j = 0; j < 9; j ++)
999    for (int k = 0; k < 9; k ++) {
1000      char name[255];
1001      int val;
1002
1003      SudokuCell *cell = grid_cells_[j][k];
1004
1005      sprintf(name, "value%d.%d", j, k);
1006      if (!prefs_.get(name, val, 0)) {
1007        j = 9;
1008        grid_values_[0][0] = 0;
1009        break;
1010      }
1011
1012      grid_values_[j][k] = val;
1013
1014      sprintf(name, "state%d.%d", j, k);
1015      prefs_.get(name, val, 0);
1016      cell->value(val);
1017 
1018      sprintf(name, "readonly%d.%d", j, k);
1019      prefs_.get(name, val, 0);
1020      cell->readonly(val);
1021
1022      if (val) cell->color(FL_GRAY);
1023      else {
1024        cell->color(FL_LIGHT3);
1025        solved = false;
1026      }
1027
1028      for (int m = 0; m < 8; m ++) {
1029        sprintf(name, "test%d%d.%d", m, j, k);
1030        prefs_.get(name, val, 0);
1031        cell->test_value(val, m);
1032      }
1033    }
1034
1035  // If we didn't load any values or the last game was solved, then
1036  // create a new game automatically...
1037  if (solved || !grid_values_[0][0]) new_game(time(NULL));
1038  else check_game(false);
1039}
1040
1041
1042// Mute/unmute sound...
1043void
1044Sudoku::mute_cb(Fl_Widget *widget, void *) {
1045  Sudoku *s = (Sudoku *)(widget->window() ? widget->window() : widget);
1046
1047  if (s->sound_) {
1048    delete s->sound_;
1049    s->sound_ = NULL;
1050    prefs_.set("mute_sound", 1);
1051  } else {
1052    s->sound_ = new SudokuSound();
1053    prefs_.set("mute_sound", 0);
1054  }
1055}
1056
1057
1058// Create a new game...
1059void
1060Sudoku::new_cb(Fl_Widget *widget, void *) {
1061  Sudoku *s = (Sudoku *)(widget->window() ? widget->window() : widget);
1062
1063  if (s->grid_cells_[0][0]->color() != FL_GREEN) {
1064    if (!fl_choice("Are you sure you want to change the difficulty level and "
1065                   "discard the current game?", "Keep Current Game", "Start New Game",
1066                   NULL)) return;
1067  }
1068
1069  s->new_game(time(NULL));
1070}
1071
1072
1073// Create a new game...
1074void
1075Sudoku::new_game(time_t seed) {
1076  int j, k, m, n, t, count;
1077
1078
1079  // Generate a new (valid) Sudoku grid...
1080  seed_ = seed;
1081  srand(seed);
1082
1083  memset(grid_values_, 0, sizeof(grid_values_));
1084
1085  for (j = 0; j < 9; j += 3) {
1086    for (k = 0; k < 9; k += 3) {
1087      for (t = 1; t <= 9; t ++) {
1088        for (count = 0; count < 20; count ++) {
1089          m = j + (rand() % 3);
1090          n = k + (rand() % 3);
1091          if (!grid_values_[m][n]) {
1092            int mm;
1093
1094            for (mm = 0; mm < m; mm ++)
1095              if (grid_values_[mm][n] == t) break;
1096
1097            if (mm < m) continue;
1098
1099            int nn;
1100
1101            for (nn = 0; nn < n; nn ++)
1102              if (grid_values_[m][nn] == t) break;
1103
1104            if (nn < n) continue;
1105
1106            grid_values_[m][n] = t;
1107            break;
1108          }
1109        }
1110
1111        if (count == 20) {
1112          // Unable to find a valid puzzle so far, so start over...
1113          k = 9;
1114          j = -3;
1115          memset(grid_values_, 0, sizeof(grid_values_));
1116        }
1117      }
1118    }
1119  }
1120
1121  // Start by making all cells editable
1122  SudokuCell *cell;
1123
1124  for (j = 0; j < 9; j ++)
1125    for (k = 0; k < 9; k ++) {
1126      cell = grid_cells_[j][k];
1127
1128      cell->value(0);
1129      cell->readonly(0);
1130      cell->color(FL_LIGHT3);
1131    }
1132
1133  // Show N cells...
1134  count = 11 * (5 - difficulty_);
1135
1136  int numbers[9];
1137
1138  for (j = 0; j < 9; j ++) numbers[j] = j + 1;
1139
1140  while (count > 0) {
1141    for (j = 0; j < 20; j ++) {
1142      k          = rand() % 9;
1143      m          = rand() % 9;
1144      t          = numbers[k];
1145      numbers[k] = numbers[m];
1146      numbers[m] = t;
1147    }
1148
1149    for (j = 0; count > 0 && j < 9; j ++) {
1150      t = numbers[j];
1151
1152      for (k = 0; count > 0 && k < 9; k ++) {
1153        cell = grid_cells_[j][k];
1154
1155        if (grid_values_[j][k] == t && !cell->readonly()) {
1156          cell->value(grid_values_[j][k]);
1157          cell->readonly(1);
1158          cell->color(FL_GRAY);
1159
1160          count --;
1161          break;
1162        }
1163      }
1164    }
1165  }
1166}
1167
1168
1169// Return the next available value for a cell...
1170int
1171Sudoku::next_value(SudokuCell *c) {
1172  int   j, k, m, n;
1173
1174
1175  for (j = 0; j < 9; j ++) {
1176    for (k = 0; k < 9; k ++)
1177      if (grid_cells_[j][k] == c) break;
1178
1179    if (k < 9) break;
1180  }
1181
1182  if (j == 9) return 1;
1183
1184  j -= j % 3;
1185  k -= k % 3;
1186
1187  int numbers[9];
1188
1189  memset(numbers, 0, sizeof(numbers));
1190
1191  for (m = 0; m < 3; m ++)
1192    for (n = 0; n < 3; n ++) {
1193      c = grid_cells_[j + m][k + n];
1194      if (c->value()) numbers[c->value() - 1] = 1;
1195    }
1196
1197  for (j = 0; j < 9; j ++)
1198    if (!numbers[j]) return j + 1;
1199
1200  return 1;
1201}
1202
1203
1204// Reset widget color to gray...
1205void
1206Sudoku::reset_cb(Fl_Widget *widget, void *) {
1207  widget->color(FL_LIGHT3);
1208  widget->redraw();
1209 
1210  ((Sudoku *)(widget->window()))->check_game(false);
1211}
1212
1213
1214// Resize the window...
1215void
1216Sudoku::resize(int X, int Y, int W, int H) {
1217  // Resize the window...
1218  Fl_Window::resize(X, Y, W, H);
1219
1220  // Save the new window geometry...
1221  prefs_.set("x", X);
1222  prefs_.set("y", Y);
1223  prefs_.set("width", W);
1224  prefs_.set("height", H);
1225}
1226
1227
1228// Restart game from beginning...
1229void
1230Sudoku::restart_cb(Fl_Widget *widget, void *) {
1231  Sudoku *s = (Sudoku *)(widget->window());
1232  bool solved = true;
1233
1234  for (int j = 0; j < 9; j ++)
1235    for (int k = 0; k < 9; k ++) {
1236      SudokuCell *cell = s->grid_cells_[j][k];
1237
1238      if (!cell->readonly()) {
1239        solved = false;
1240        int v = cell->value();
1241        cell->value(0);
1242        cell->color(FL_LIGHT3);
1243        if (v && s->sound_) s->sound_->play('A' + v - 1);
1244      }
1245    }
1246
1247  if (solved) s->new_game(s->seed_);
1248}
1249
1250
1251// Save the current game state...
1252void
1253Sudoku::save_game() {
1254  // Save the current values and state of each grid...
1255  for (int j = 0; j < 9; j ++)
1256    for (int k = 0; k < 9; k ++) {
1257      char name[255];
1258      SudokuCell *cell = grid_cells_[j][k];
1259
1260      sprintf(name, "value%d.%d", j, k);
1261      prefs_.set(name, grid_values_[j][k]);
1262
1263      sprintf(name, "state%d.%d", j, k);
1264      prefs_.set(name, cell->value());
1265
1266      sprintf(name, "readonly%d.%d", j, k);
1267      prefs_.set(name, cell->readonly());
1268
1269      for (int m = 0; m < 8; m ++) {
1270        sprintf(name, "test%d%d.%d", m, j, k);
1271        prefs_.set(name, cell->test_value(m));
1272      }
1273    }
1274}
1275
1276
1277// Set title of window...
1278void
1279Sudoku::set_title() {
1280  static const char * const titles[] = {
1281    "Sudoku - Easy",
1282    "Sudoku - Medium",
1283    "Sudoku - Hard",
1284    "Sudoku - Impossible"
1285  };
1286
1287  label(titles[difficulty_]);
1288}
1289
1290
1291// Solve the puzzle...
1292void
1293Sudoku::solve_cb(Fl_Widget *widget, void *) {
1294  ((Sudoku *)(widget->window()))->solve_game();
1295}
1296
1297
1298// Solve the puzzle...
1299void
1300Sudoku::solve_game() {
1301  int j, k;
1302
1303  for (j = 0; j < 9; j ++) {
1304    for (k = 0; k < 9; k ++) {
1305      SudokuCell *cell = grid_cells_[j][k];
1306
1307      cell->value(grid_values_[j][k]);
1308      cell->readonly(1);
1309      cell->color(FL_GRAY);
1310    }
1311
1312    if (sound_) sound_->play('A' + grid_cells_[j][8]->value() - 1);
1313  }
1314}
1315
1316
1317// Main entry for game...
1318int
1319main(int argc, char *argv[]) {
1320  Sudoku s;
1321
1322  // Show the game...
1323  s.show(argc, argv);
1324
1325  // Load the previous game...
1326  s.load_game();
1327
1328  // Run until the user quits...
1329  return (Fl::run());
1330}
1331
1332
1333//
1334// End of "$Id$".
1335//
Note: See TracBrowser for help on using the repository browser.