source: rtems-graphics-toolkit/fltk-1.3.0/test/sudoku.cxx @ 46c28a1

Last change on this file since 46c28a1 was f5c9e9c, checked in by Alexandru-Sever Horin <alex.sever.h@…>, on 07/05/12 at 09:33:03

Aded FLTK 1.3.0

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