From d578543aa6da5eda36934f512fb9ef272d6cb34e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Axel=20D=C3=B6rfler?= Date: Sat, 25 Aug 2007 17:39:46 +0000 Subject: [PATCH] * Added a first game to the image: Sudoku. It comes with a solver and generator for three different levels (from "Very Easy" to "Hard"). Have fun! * Feel free to do a nicer icon! * Stack.h is actually verbatim copy of a kernel header which we might want to move to a more public place (like shared). * ProgressWindow is taken from ShowImage, but adapted to suit different needs. * It seems to trigger a bug in the interface kit or app_server: when moving the mouse around, the right border of a field is sometimes lost. This does not happen in BeOS, and there is actually no code that looks responsible for this - it might be an off by one error in the region code, though? git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@22056 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- build/jam/HaikuImage | 10 +- src/apps/Jamfile | 1 + src/apps/sudoku/Jamfile | 16 + src/apps/sudoku/ProgressWindow.cpp | 134 +++++ src/apps/sudoku/ProgressWindow.h | 34 ++ src/apps/sudoku/Stack.h | 78 +++ src/apps/sudoku/Sudoku.cpp | 101 ++++ src/apps/sudoku/Sudoku.h | 10 + src/apps/sudoku/Sudoku.rdef | 45 ++ src/apps/sudoku/SudokuField.cpp | 419 +++++++++++++++ src/apps/sudoku/SudokuField.h | 74 +++ src/apps/sudoku/SudokuGenerator.cpp | 171 ++++++ src/apps/sudoku/SudokuGenerator.h | 30 ++ src/apps/sudoku/SudokuSolver.cpp | 214 ++++++++ src/apps/sudoku/SudokuSolver.h | 35 ++ src/apps/sudoku/SudokuView.cpp | 804 ++++++++++++++++++++++++++++ src/apps/sudoku/SudokuView.h | 98 ++++ src/apps/sudoku/SudokuWindow.cpp | 459 ++++++++++++++++ src/apps/sudoku/SudokuWindow.h | 41 ++ 19 files changed, 2769 insertions(+), 5 deletions(-) create mode 100644 src/apps/sudoku/Jamfile create mode 100644 src/apps/sudoku/ProgressWindow.cpp create mode 100644 src/apps/sudoku/ProgressWindow.h create mode 100644 src/apps/sudoku/Stack.h create mode 100644 src/apps/sudoku/Sudoku.cpp create mode 100644 src/apps/sudoku/Sudoku.h create mode 100644 src/apps/sudoku/Sudoku.rdef create mode 100644 src/apps/sudoku/SudokuField.cpp create mode 100644 src/apps/sudoku/SudokuField.h create mode 100644 src/apps/sudoku/SudokuGenerator.cpp create mode 100644 src/apps/sudoku/SudokuGenerator.h create mode 100644 src/apps/sudoku/SudokuSolver.cpp create mode 100644 src/apps/sudoku/SudokuSolver.h create mode 100644 src/apps/sudoku/SudokuView.cpp create mode 100644 src/apps/sudoku/SudokuView.h create mode 100644 src/apps/sudoku/SudokuWindow.cpp create mode 100644 src/apps/sudoku/SudokuWindow.h diff --git a/build/jam/HaikuImage b/build/jam/HaikuImage index 10aa36f0e1..e64770bb99 100644 --- a/build/jam/HaikuImage +++ b/build/jam/HaikuImage @@ -46,10 +46,10 @@ BEOS_BIN = "[" addattr alert arp basename bc beep bison bzip2 cat cardctl catatt ; -BEOS_APPS = AboutSystem CodyCam DeskCalc DiskProbe CDPlayer Expander Icon-O-Matic - Installer LaunchBox Magnify Mail MediaPlayer MidiPlayer NetworkStatus - People PowerStatus ProcessController ShowImage SoundRecorder StyledEdit - Terminal TV Workspaces +BEOS_APPS = AboutSystem CodyCam DeskCalc DiskProbe CDPlayer Expander + Icon-O-Matic Installer LaunchBox Magnify Mail MediaPlayer MidiPlayer + NetworkStatus People PowerStatus ProcessController ShowImage SoundRecorder + StyledEdit Terminal TV Workspaces ; BEOS_PREFERENCES = Appearance Backgrounds DataTranslations E-mail FileTypes Fonts Keyboard Keymap Media Menu Mouse Network Printers Screen ScreenSaver @@ -57,7 +57,7 @@ BEOS_PREFERENCES = Appearance Backgrounds DataTranslations E-mail FileTypes ; BEOS_DEMOS = BitmapDrawing Chart Clock $(X86_ONLY)Cortex FontDemo $(X86_ONLY)GLDirectMode $(X86_ONLY)GLTeapot Mandelbrot PictureTest - Playground Pulse + Playground Pulse Sudoku ; BEOS_SYSTEM_LIB = libbe.so $(HAIKU_LIBSTDC++) libmedia.so libtracker.so libtranslation.so libnetwork.so libdebug.so libbsd.so libmail.so diff --git a/src/apps/Jamfile b/src/apps/Jamfile index 1cfd396a9c..efab9ee4a6 100644 --- a/src/apps/Jamfile +++ b/src/apps/Jamfile @@ -30,6 +30,7 @@ SubInclude HAIKU_TOP src apps resedit ; SubInclude HAIKU_TOP src apps showimage ; SubInclude HAIKU_TOP src apps soundrecorder ; SubInclude HAIKU_TOP src apps stylededit ; +SubInclude HAIKU_TOP src apps sudoku ; SubInclude HAIKU_TOP src apps terminal ; SubInclude HAIKU_TOP src apps tracker ; SubInclude HAIKU_TOP src apps tv ; diff --git a/src/apps/sudoku/Jamfile b/src/apps/sudoku/Jamfile new file mode 100644 index 0000000000..d52e5ca4f4 --- /dev/null +++ b/src/apps/sudoku/Jamfile @@ -0,0 +1,16 @@ +SubDir HAIKU_TOP src apps sudoku ; + +SetSubDirSupportedPlatformsBeOSCompatible ; + +Application Sudoku : + ProgressWindow.cpp + Sudoku.cpp + SudokuField.cpp + SudokuGenerator.cpp + SudokuSolver.cpp + SudokuView.cpp + SudokuWindow.cpp + + : be tracker + : Sudoku.rdef + ; diff --git a/src/apps/sudoku/ProgressWindow.cpp b/src/apps/sudoku/ProgressWindow.cpp new file mode 100644 index 0000000000..88697c5694 --- /dev/null +++ b/src/apps/sudoku/ProgressWindow.cpp @@ -0,0 +1,134 @@ +/* + * Copyright 2007, Axel Dörfler, axeld@pinc-software.de. All rights reserved. + * Distributed under the terms of the MIT License. + */ + + +#include "ProgressWindow.h" + +#include +#include +#include +#include +#include + +#include + + +static const uint32 kMsgShow = 'show'; + + +ProgressWindow::ProgressWindow(BWindow* referenceWindow, + BMessage* abortMessage) + : BWindow(BRect(0, 0, 250, 100), "Progress Monitor", + B_MODAL_WINDOW_LOOK, B_FLOATING_APP_WINDOW_FEEL, + B_NOT_ZOOMABLE | B_NOT_RESIZABLE | B_ASYNCHRONOUS_CONTROLS), + fRunner(NULL) +{ + BRect rect = Bounds(); + + BView *view = new BView(rect, NULL, B_FOLLOW_ALL, B_WILL_DRAW); + view->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + AddChild(view); + + rect = view->Bounds().InsetByCopy(8, 8); + fStatusBar = new BStatusBar(rect, "status", NULL, NULL); + float width, height; + fStatusBar->GetPreferredSize(&width, &height); + fStatusBar->ResizeTo(rect.Width(), height); + fStatusBar->SetResizingMode(B_FOLLOW_TOP | B_FOLLOW_LEFT_RIGHT); + view->AddChild(fStatusBar); + + if (abortMessage != NULL && referenceWindow) { + rect.top += height + 8; + BButton* button = new BButton(rect, "abort", "Abort", + abortMessage); + button->ResizeToPreferred(); + button->MoveBy((rect.Width() - button->Bounds().Width()) / 2, 0); + view->AddChild(button); + button->SetTarget(referenceWindow); + height = button->Frame().bottom; + } + + ResizeTo(Bounds().Width(), height + 8); + BRect frame; + if (referenceWindow != NULL) + frame = referenceWindow->Frame(); + else + frame = BScreen().Frame(); + + MoveTo(frame.left + (frame.Width() - Bounds().Width()) / 2, + frame.top + (frame.Height() - Bounds().Height()) / 2); + + Run(); +} + + +ProgressWindow::~ProgressWindow() +{ + delete fRunner; +} + + +void +ProgressWindow::Start() +{ + BAutolock _(this); + + fRetrievedUpdate = false; + fRetrievedShow = false; + delete fRunner; + + BMessage show(kMsgShow); + fRunner = new BMessageRunner(this, &show, 1000000, 1); +} + + +void +ProgressWindow::Stop() +{ + BAutolock _(this); + + delete fRunner; + fRunner = NULL; + + if (!IsHidden()) + Hide(); +} + + +void +ProgressWindow::MessageReceived(BMessage *message) +{ + switch (message->what) { + case kMsgShow: + if (fRetrievedUpdate && IsHidden()) { + Show(); + Minimize(false); + } + + fRetrievedShow = true; + break; + + case kMsgProgressStatusUpdate: + float percent; + if (message->FindFloat("percent", &percent) == B_OK) + fStatusBar->Update(percent - fStatusBar->CurrentValue()); + + const char *text; + if (message->FindString("message", &text) == B_OK) + fStatusBar->SetText(text); + + fRetrievedUpdate = true; + + if (fRetrievedShow && IsHidden()) { + Show(); + Minimize(false); + } + break; + + default: + BWindow::MessageReceived(message); + } +} + diff --git a/src/apps/sudoku/ProgressWindow.h b/src/apps/sudoku/ProgressWindow.h new file mode 100644 index 0000000000..5b4dcb909e --- /dev/null +++ b/src/apps/sudoku/ProgressWindow.h @@ -0,0 +1,34 @@ +/* + * Copyright 2007, Axel Dörfler, axeld@pinc-software.de. All rights reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef PROGRESS_WINDOW_H +#define PROGRESS_WINDOW_H + + +#include + +class BMessageRunner; +class BStatusBar; + + +class ProgressWindow : public BWindow { + public: + ProgressWindow(BWindow* referenceWindow, BMessage* abortMessage = NULL); + virtual ~ProgressWindow(); + + virtual void MessageReceived(BMessage *message); + + void Start(); + void Stop(); + + private: + BStatusBar* fStatusBar; + BMessageRunner* fRunner; + bool fRetrievedUpdate; + bool fRetrievedShow; +}; + +static const uint32 kMsgProgressStatusUpdate = 'SIup'; + +#endif // PROGRESS_WINDOW_H diff --git a/src/apps/sudoku/Stack.h b/src/apps/sudoku/Stack.h new file mode 100644 index 0000000000..dc832aabf1 --- /dev/null +++ b/src/apps/sudoku/Stack.h @@ -0,0 +1,78 @@ +/* Stack - a template stack class (plus some handy methods) + * + * Copyright 2001-2005, Axel Dörfler, axeld@pinc-software.de. + * This file may be used under the terms of the MIT License. + */ +#ifndef KERNEL_UTIL_STACK_H +#define KERNEL_UTIL_STACK_H + + +#include + + +template class Stack { + public: + Stack() + : + fArray(NULL), + fUsed(0), + fMax(0) + { + } + + ~Stack() + { + free(fArray); + } + + bool IsEmpty() const + { + return fUsed == 0; + } + + void MakeEmpty() + { + // could also free the memory + fUsed = 0; + } + + status_t Push(T value) + { + if (fUsed >= fMax) { + fMax += 16; + T *newArray = (T *)realloc(fArray, fMax * sizeof(T)); + if (newArray == NULL) + return B_NO_MEMORY; + + fArray = newArray; + } + fArray[fUsed++] = value; + return B_OK; + } + + bool Pop(T *value) + { + if (fUsed == 0) + return false; + + *value = fArray[--fUsed]; + return true; + } + + T *Array() + { + return fArray; + } + + int32 CountItems() const + { + return fUsed; + } + + private: + T *fArray; + int32 fUsed; + int32 fMax; +}; + +#endif /* KERNEL_UTIL_STACK_H */ diff --git a/src/apps/sudoku/Sudoku.cpp b/src/apps/sudoku/Sudoku.cpp new file mode 100644 index 0000000000..193368d7be --- /dev/null +++ b/src/apps/sudoku/Sudoku.cpp @@ -0,0 +1,101 @@ +/* + * Copyright 2007, Axel Dörfler, axeld@pinc-software.de. All rights reserved. + * Distributed under the terms of the MIT License. + */ + + +#include "SudokuWindow.h" + +#include + +#include +#include +#include + + +class Sudoku : public BApplication { +public: + Sudoku(); + virtual ~Sudoku(); + + virtual void ReadyToRun(); + + virtual void RefsReceived(BMessage *message); + virtual void MessageReceived(BMessage *message); + + virtual void AboutRequested(); + +private: + SudokuWindow* fWindow; +}; + + +const char* kSignature = "application/x-vnd.Haiku-Sudoku"; + + +Sudoku::Sudoku() + : BApplication(kSignature) +{ +} + + +Sudoku::~Sudoku() +{ +} + + +void +Sudoku::ReadyToRun() +{ + fWindow = new SudokuWindow(); + fWindow->Show(); +} + + +void +Sudoku::RefsReceived(BMessage* message) +{ + fWindow->PostMessage(message); +} + + +void +Sudoku::MessageReceived(BMessage* message) +{ + BApplication::MessageReceived(message); +} + + +void +Sudoku::AboutRequested() +{ + BAlert *alert = new BAlert("about", "Sudoku\n" + "\twritten by Axel Dörfler\n" + "\tCopyright 2007, pinc Software.\n", "Ok"); + BTextView *view = alert->TextView(); + BFont font; + + view->SetStylable(true); + + view->GetFont(&font); + font.SetSize(18); + font.SetFace(B_BOLD_FACE); + view->SetFontAndColor(0, 6, &font); + + alert->Go(); +} + + +// #pragma mark - + + +int +main(int /*argc*/, char** /*argv*/) +{ + srand(system_time()); + + Sudoku sudoku; + sudoku.Run(); + + return 0; +} diff --git a/src/apps/sudoku/Sudoku.h b/src/apps/sudoku/Sudoku.h new file mode 100644 index 0000000000..cd5b6ccce2 --- /dev/null +++ b/src/apps/sudoku/Sudoku.h @@ -0,0 +1,10 @@ +/* + * Copyright 2007, Axel Dörfler, axeld@pinc-software.de. All rights reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef SUDOKU_H +#define SUDOKU_H + +extern const char* kSignature; + +#endif // SUDOKU_H diff --git a/src/apps/sudoku/Sudoku.rdef b/src/apps/sudoku/Sudoku.rdef new file mode 100644 index 0000000000..98a9b7baf1 --- /dev/null +++ b/src/apps/sudoku/Sudoku.rdef @@ -0,0 +1,45 @@ + +resource(1, "BEOS:APP_SIG") #'MIMS' "application/x-vnd.Haiku-Sudoku"; + +resource app_version { + major = 1, + middle = 0, + minor = 0, + + variety = B_APPV_BETA, + internal = 3, + + short_info = "Sudoku", + long_info = "Sudoku ©2007 Haiku, Inc." +}; + +resource app_flags B_MULTIPLE_LAUNCH; + +#ifdef HAIKU_TARGET_PLATFORM_HAIKU + +resource vector_icon { + $"6E636966050500020006023B2B47BB18653D0FA53D225148297046CA1900FFEC" + $"4BFFF0A506020006023B3049396B0ABA90833C646E4A101543299500FFFFFFFF" + $"FFF289020006023A1DA6393F04BBB5BC3C6B074AEA3648091100F99B05FFFCB2" + $"3D040174110404EA29BA52C57A49C4A1C455B663BB2BB63CBADEB689BB770A04" + $"BF8D4AC804C24A58BFE6BFFFBE9A0A06302C303E40454C3C4C2A3C250A04302C" + $"303E404540320A04302C40324C2A3C250A04403240454C3C4C2A0A0338423C4D" + $"3C440A0622422254325C3E513E402E3A0A0422422254325C32490A0422423249" + $"3E402E3A0A043249325C3E513E400A063E423E544E5C5A505A3F4A390A06C785" + $"BF40C354C2764E495A3F4A39C391BD6F0A043E42C354C276C785BF40C391BD6F" + $"0A054151C08BC8834E5C4E49C35DC27A0A053E423E54C08BC8834151C35DC27A" + $"0A044E494E5C5A505A3E0E0A040101000A040100023FFDB4B8243D38243D3FFD" + $"B443BDB54769F50A04010002B580AB3F95F9C0DD76B427F54CDA2F47CD160A04" + $"010002BC39BD3F60BDC00371BBAC3B4BF11B4704060A04010002403517B500E8" + $"35926F3EF37647087242923F0A000100024073E92EB44DAF711940084AC5F7BE" + $"466E720A00010002B580AB3F95F9C0DD76B427F54CBB2F47CD160A00010002BC" + $"39BD3F60BDC00371BBAC3B4BBF1B46D4060A00010002403517B500E835926F3E" + $"F37646487242D23F0A0001021A3EF8780000000000003F1885465E1E46C13215" + $"FF01178400040A0001021A3EF8780000000000003F1885465E1E46C132001501" + $"178600040A010103023EF8780000000000003F1885465E1E46C1320A02010402" + $"3EF8780000000000003F1885465E1E46C1320A030105023EF878000000000000" + $"3F1885465E1E46C132" +}; + +#endif // HAIKU_TARGET_PLATFORM_HAIKU + diff --git a/src/apps/sudoku/SudokuField.cpp b/src/apps/sudoku/SudokuField.cpp new file mode 100644 index 0000000000..1b9dfbf95f --- /dev/null +++ b/src/apps/sudoku/SudokuField.cpp @@ -0,0 +1,419 @@ +/* + * Copyright 2007, Axel Dörfler, axeld@pinc-software.de. All rights reserved. + * Distributed under the terms of the MIT License. + */ + + +#include "SudokuField.h" + +#include +#include + +#include +#include + + +const char* +mask(uint32 set) +{ + static char text[64]; + uint32 i = 0; + for (int32 number = 9; number > 0; number--) { + text[i++] = set & (1UL << (number - 1)) ? number + '0' : '-'; + } + + text[i] = '\0'; + return text; +} + + +// #pragma mark - + + +SudokuField::field::field() + : + hint_mask(0), + valid_mask(~0), + flags(0), + value(0) +{ +} + + +// #pragma mark - + + +SudokuField::SudokuField(uint32 size) + : + fSize(size * size), + fBlockSize(size) +{ + fFields = new (std::nothrow) field[fSize * fSize]; + fMaxMask = (1UL << fSize) - 1; +} + + +SudokuField::SudokuField(const BMessage* archive) +{ + if (archive->FindInt32("block size", (int32*)&fBlockSize) != B_OK) + return; + + fSize = fBlockSize * fBlockSize; + fMaxMask = (1UL << fSize) - 1; + + uint32 count = fSize * fSize; + fFields = new (std::nothrow) field[count]; + if (fFields == NULL) + return; + + for (uint32 i = 0; i < count; i++) { + struct field& field = fFields[i]; + + if (archive->FindInt32("value", i, (int32*)&field.value) != B_OK + || archive->FindInt32("valid mask", i, + (int32*)&field.valid_mask) != B_OK + || archive->FindInt32("hint mask", i, + (int32*)&field.hint_mask) != B_OK + || archive->FindInt32("flags", i, (int32*)&field.flags) != B_OK) + break; + } +} + + +SudokuField::SudokuField(const SudokuField& other) + : BArchivable(other) +{ + fSize = other.fSize; + fBlockSize = other.fBlockSize; + fMaxMask = other.fMaxMask; + + fFields = new (std::nothrow) field[fSize * fSize]; + if (fFields != NULL) + memcpy(fFields, other.fFields, sizeof(field) * fSize * fSize); +} + + +SudokuField::~SudokuField() +{ + delete[] fFields; +} + + +status_t +SudokuField::InitCheck() +{ + if (fBlockSize == 0) + return B_BAD_VALUE; + return fFields == NULL ? B_NO_MEMORY : B_OK; +} + + +status_t +SudokuField::Archive(BMessage* archive, bool deep) const +{ + status_t status = BArchivable::Archive(archive, deep); + if (status == B_OK) + archive->AddInt32("block size", fBlockSize); + if (status < B_OK) + return status; + + uint32 count = fSize * fSize; + for (uint32 i = 0; i < count && status == B_OK; i++) { + struct field& field = fFields[i]; + status = archive->AddInt32("value", field.value); + if (status == B_OK) + status = archive->AddInt32("valid mask", field.valid_mask); + if (status == B_OK) + status = archive->AddInt32("hint mask", field.hint_mask); + if (status == B_OK) + status = archive->AddInt32("flags", field.flags); + } + + return status; +} + + +/*static*/ SudokuField* +SudokuField::Instantiate(BMessage* archive) +{ + if (!validate_instantiation(archive, "SudokuField")) + return NULL; + + return new SudokuField(archive); +} + + +void +SudokuField::Reset() +{ + for (uint32 y = 0; y < fSize; y++) { + for (uint32 x = 0; x < fSize; x++) { + struct field& field = _FieldAt(x, y); + field.value = 0; + field.flags = 0; + field.hint_mask = 0; + field.valid_mask = fMaxMask; + } + } +} + + +status_t +SudokuField::SetTo(char base, const char* data) +{ + if (data != NULL && strlen(data) < fSize * fSize) + return B_BAD_VALUE; + + Reset(); + + if (data == NULL) + return B_OK; + + uint32 i = 0; + + for (uint32 y = 0; y < fSize; y++) { + for (uint32 x = 0; x < fSize; x++) { + uint32 value = data[i++] - base; + if (value) { + struct field& field = _FieldAt(x, y); + field.value = value; + field.flags = kInitialValue; + } + } + } + + for (uint32 y = 0; y < fSize; y++) { + for (uint32 x = 0; x < fSize; x++) { + _ComputeValidMask(x, y, false); + } + } + + return B_OK; +} + + +void +SudokuField::SetTo(const SudokuField* field) +{ + if (field == NULL) { + Reset(); + return; + } + + for (uint32 y = 0; y < fSize; y++) { + for (uint32 x = 0; x < fSize; x++) { + _FieldAt(x, y) = field->_FieldAt(x, y); + } + } +} + + +void +SudokuField::Dump() +{ + for (uint32 y = 0; y < fSize; y++) { + for (uint32 x = 0; x < fSize; x++) { + if (x != 0 && x % fBlockSize == 0) + putchar(' '); + printf("%lu", ValueAt(x, y)); + } + putchar('\n'); + } +} + + +bool +SudokuField::IsSolved() +{ + for (uint32 y = 0; y < fSize; y++) { + for (uint32 x = 0; x < fSize; x++) { + if (!_ValidValueAt(x, y)) + return false; + } + } + + return true; +} + + +void +SudokuField::SetHintMaskAt(uint32 x, uint32 y, uint32 hintMask) +{ + _FieldAt(x, y).hint_mask = hintMask; +} + + +uint32 +SudokuField::HintMaskAt(uint32 x, uint32 y) const +{ + return _FieldAt(x, y).hint_mask; +} + + +void +SudokuField::SetValidMaskAt(uint32 x, uint32 y, uint32 validMask) +{ + _FieldAt(x, y).valid_mask = validMask & fMaxMask; +} + + +uint32 +SudokuField::ValidMaskAt(uint32 x, uint32 y) const +{ + return _FieldAt(x, y).valid_mask; +} + + +void +SudokuField::SetFlagsAt(uint32 x, uint32 y, uint32 flags) +{ + _FieldAt(x, y).flags = flags; +} + + +uint32 +SudokuField::FlagsAt(uint32 x, uint32 y) const +{ + return _FieldAt(x, y).flags; +} + + +void +SudokuField::SetValueAt(uint32 x, uint32 y, uint32 value, bool setSolved) +{ + _FieldAt(x, y).value = value; + _FieldAt(x, y).hint_mask = 0; + _UpdateValidMaskChanged(x, y, setSolved); +} + + +uint32 +SudokuField::ValueAt(uint32 x, uint32 y) const +{ + return _FieldAt(x, y).value; +} + + +bool +SudokuField::_ValidValueAt(uint32 x, uint32 y) const +{ + uint32 value = _FieldAt(x, y).value; + if (!value) + return false; + + value = 1UL << (value - 1); + return (_FieldAt(x, y).valid_mask & value) != 0; +} + + +void +SudokuField::_ComputeValidMask(uint32 x, uint32 y, bool setSolved) +{ + if (ValueAt(x, y)) + return; + + // check row + + uint32 foundMask = 0; + for (uint32 i = 0; i < fSize; i++) { + uint32 value = ValueAt(i, y); + if (value && _ValidValueAt(i, y)) + foundMask |= 1UL << (value - 1); + } + + // check column + + for (uint32 i = 0; i < fSize; i++) { + uint32 value = ValueAt(x, i); + if (value && _ValidValueAt(x, i)) + foundMask |= 1UL << (value - 1); + } + + // check block + + uint32 offsetX = x / fBlockSize * fBlockSize; + uint32 offsetY = y / fBlockSize * fBlockSize; + + for (uint32 partY = 0; partY < fBlockSize; partY++) { + for (uint32 partX = 0; partX < fBlockSize; partX++) { + uint32 value = ValueAt(partX + offsetX, partY + offsetY); + if (value && _ValidValueAt(partX + offsetX, partY + offsetY)) + foundMask |= 1UL << (value - 1); + } + } + + SetValidMaskAt(x, y, ~foundMask); + + if (setSolved) { + // find the one set bit, if not more + uint32 value = 0; + for (uint32 i = 0; i < fSize; i++) { + if ((foundMask & (1UL << i)) == 0) { + if (value != 0) { + value = 0; + break; + } + + value = i + 1; + } + } + if (value != 0) + SetValueAt(x, y, value, true); + } +} + + +void +SudokuField::_UpdateValidMaskChanged(uint32 x, uint32 y, bool setSolved) +{ + // update row + + for (uint32 i = 0; i < fSize; i++) { + _ComputeValidMask(i, y, setSolved); + } + + // update column + + for (uint32 i = 0; i < fSize; i++) { + if (i == y) + continue; + + _ComputeValidMask(x, i, setSolved); + } + + // update block + + uint32 offsetX = x / fBlockSize * fBlockSize; + uint32 offsetY = y / fBlockSize * fBlockSize; + + for (uint32 partY = 0; partY < fBlockSize; partY++) { + for (uint32 partX = 0; partX < fBlockSize; partX++) { + if (partX + offsetX == x || partY + offsetY == y) + continue; + + _ComputeValidMask(partX + offsetX, partY + offsetY, setSolved); + } + } +} + + +const SudokuField::field& +SudokuField::_FieldAt(uint32 x, uint32 y) const +{ + if (x >= fSize || y >= fSize) + debugger("field outside bounds"); + + return fFields[x + y * fSize]; +} + + +SudokuField::field& +SudokuField::_FieldAt(uint32 x, uint32 y) +{ + if (x >= fSize || y >= fSize) + debugger("field outside bounds"); + + return fFields[x + y * fSize]; +} + + diff --git a/src/apps/sudoku/SudokuField.h b/src/apps/sudoku/SudokuField.h new file mode 100644 index 0000000000..10e84f0921 --- /dev/null +++ b/src/apps/sudoku/SudokuField.h @@ -0,0 +1,74 @@ +/* + * Copyright 2007, Axel Dörfler, axeld@pinc-software.de. All rights reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef SUDOKU_FIELD_H +#define SUDOKU_FIELD_H + + +#include +#include + + +enum { + kInitialValue = 0x01, +}; + +class SudokuField : public BArchivable { +public: + SudokuField(uint32 size); + SudokuField(const BMessage* archive); + SudokuField(const SudokuField& other); + virtual ~SudokuField(); + + status_t InitCheck(); + + virtual status_t Archive(BMessage* archive, bool deep) const; + static SudokuField* Instantiate(BMessage* archive); + + status_t SetTo(char base, const char* data); + void SetTo(const SudokuField* other); + void Reset(); + + bool IsSolved(); + + uint32 Size() const { return fSize; } + uint32 BlockSize() const { return fBlockSize; } + + void SetHintMaskAt(uint32 x, uint32 y, uint32 hintMask); + uint32 HintMaskAt(uint32 x, uint32 y) const; + + void SetValidMaskAt(uint32 x, uint32 y, uint32 validMask); + uint32 ValidMaskAt(uint32 x, uint32 y) const; + + void SetFlagsAt(uint32 x, uint32 y, uint32 flags); + uint32 FlagsAt(uint32 x, uint32 y) const; + + void SetValueAt(uint32 x, uint32 y, uint32 value, bool setSolved = false); + uint32 ValueAt(uint32 x, uint32 y) const; + + void Dump(); + +private: + struct field { + field(); + + uint32 hint_mask; + uint32 valid_mask; + uint32 flags; + uint32 value; + }; + + bool _ValidValueAt(uint32 x, uint32 y) const; + void _ComputeValidMask(uint32 x, uint32 y, bool setSolved); + void _UpdateValidMaskChanged(uint32 x, uint32 y, bool setSolved); + const field& _FieldAt(uint32 x, uint32 y) const; + field& _FieldAt(uint32 x, uint32 y); + + uint32 fSize; + uint32 fBlockSize; + uint32 fMaxMask; + field* fFields; +}; + +#endif // SUDOKU_FIELD_H diff --git a/src/apps/sudoku/SudokuGenerator.cpp b/src/apps/sudoku/SudokuGenerator.cpp new file mode 100644 index 0000000000..06d650a8d9 --- /dev/null +++ b/src/apps/sudoku/SudokuGenerator.cpp @@ -0,0 +1,171 @@ +/* + * Copyright 2007, Axel Dörfler, axeld@pinc-software.de. All rights reserved. + * Distributed under the terms of the MIT License. + */ + + +#include "SudokuGenerator.h" + +#include "ProgressWindow.h" +#include "SudokuField.h" +#include "SudokuSolver.h" + + +SudokuGenerator::SudokuGenerator() +{ +} + + +SudokuGenerator::~SudokuGenerator() +{ +} + + +bool +SudokuGenerator::_HasOnlyOneSolution(SudokuField& field) +{ + SudokuSolver solver(&field); + solver.ComputeSolutions(); + + return solver.CountSolutions() == 1; +} + + +void +SudokuGenerator::_Progress(BMessenger progress, const char* text, + float percent) +{ + BMessage update(kMsgProgressStatusUpdate); + if (text) + update.AddString("message", text); + update.AddFloat("percent", percent); + progress.SendMessage(&update); +} + + +void +SudokuGenerator::Generate(SudokuField* target, uint32 fieldsLeft, + BMessenger progress, volatile bool *quit) +{ + // first step: generate a solved field - random brute force style + + SudokuField field(target->BlockSize()); + uint32 inputCount = field.Size() * field.Size() / 3; + _Progress(progress, "Creating solvable field", 5.f); + + while (!*quit) { + field.Reset(); + + // generate input field + + uint32 validMask = 0; + + for (uint32 i = 0; i < inputCount; i++) { + uint32 x; + uint32 y; + do { + x = rand() % field.Size(); + y = rand() % field.Size(); + } while (!*quit && field.ValueAt(x, y) != 0); + + validMask = field.ValidMaskAt(x, y); + if (validMask == 0) + break; + + uint32 value; + do { + value = rand() % field.Size(); + } while (!*quit && (validMask & (1UL << value)) == 0); + + field.SetValueAt(x, y, value + 1); + } + + if (validMask == 0) + continue; + + // try to solve it + + SudokuSolver solver(&field); + solver.ComputeSolutions(); + if (solver.CountSolutions() > 0) { + // choose a random solution + field.SetTo(solver.SolutionAt(rand() % solver.CountSolutions())); + break; + } + } + + if (*quit) + return; + + // next step: try to remove as many fields as possible (and wished) + // that still have only a single solution + + int32 removeCount = field.Size() * field.Size() - fieldsLeft; + bool tried[field.Size() * field.Size()]; + int32 tries = field.Size() * field.Size() * 3 / 4; + memset(tried, 0, sizeof(tried)); + _Progress(progress, "Searching for removable values", 30.f); + + while (!*quit && removeCount > 0 && tries-- > 0) { + SudokuField copy(field); + uint32 x; + uint32 y; + do { + x = rand() % field.Size(); + y = rand() % field.Size(); + } while (copy.ValueAt(x, y) == 0 || tried[x + y * field.Size()]); + + tried[x + y * field.Size()] = true; + copy.SetValueAt(x, y, 0); + + if (_HasOnlyOneSolution(copy)) { + _Progress(progress, NULL, 100.f - (70.f * removeCount / 70.f)); + field.SetTo(©); + removeCount--; + } + } + + if (*quit) + return; + + if (tries <= 0) { + puts("check all remove"); + for (uint32 y = 0; y < field.Size(); y++) { + for (uint32 x = 0; x < field.Size(); x++) { + if (tried[x + y * field.Size()]) + continue; + + SudokuField copy(field); + copy.SetValueAt(x, y, 0); + + if (_HasOnlyOneSolution(copy)) { + _Progress(progress, NULL, + 100.f - (70.f * removeCount / 70.f)); + field.SetTo(©); + + if (--removeCount <= 0 || *quit) + break; + } + } + + if (removeCount <= 0 || *quit) + break; + } + printf(" remove count = %ld\n", removeCount); + } + + // set the remaining values to be initial values + + for (uint32 y = 0; y < field.Size(); y++) { + for (uint32 x = 0; x < field.Size(); x++) { + if (field.ValueAt(x, y)) + field.SetFlagsAt(x, y, kInitialValue); + } + } + + if (*quit) + return; + + target->SetTo(&field); +} + diff --git a/src/apps/sudoku/SudokuGenerator.h b/src/apps/sudoku/SudokuGenerator.h new file mode 100644 index 0000000000..fbdb8f8eb1 --- /dev/null +++ b/src/apps/sudoku/SudokuGenerator.h @@ -0,0 +1,30 @@ +/* + * Copyright 2007, Axel Dörfler, axeld@pinc-software.de. All rights reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef SUDOKU_GENERATOR_H +#define SUDOKU_GENERATOR_H + + +#include + +#include +#include + +class SudokuField; + + +class SudokuGenerator { +public: + SudokuGenerator(); + ~SudokuGenerator(); + + void Generate(SudokuField* field, uint32 fieldsLeft, + BMessenger progress, volatile bool *quit); + +private: + void _Progress(BMessenger progress, const char* text, float percent); + bool _HasOnlyOneSolution(SudokuField& field); +}; + +#endif // SUDOKU_GENERATOR_H diff --git a/src/apps/sudoku/SudokuSolver.cpp b/src/apps/sudoku/SudokuSolver.cpp new file mode 100644 index 0000000000..cd05d34bee --- /dev/null +++ b/src/apps/sudoku/SudokuSolver.cpp @@ -0,0 +1,214 @@ +/* + * Copyright 2007, Axel Dörfler, axeld@pinc-software.de. All rights reserved. + * Distributed under the terms of the MIT License. + */ + + +#include "SudokuSolver.h" + +#include "SudokuField.h" +#include "Stack.h" + + +struct SolutionStep { +public: + SolutionStep(const SudokuField* field); + SolutionStep(const SolutionStep& other); + ~SolutionStep(); + + void ToFirstUnset(); + bool ToNextUnset(); + + void SetSolvedFields(); + + SudokuField* Field() { return fField; } + uint32 X() { return fX; } + uint32 Y() { return fY; } + +private: + SudokuField* fField; + uint32 fX; + uint32 fY; +}; + +typedef vector StepList; + + +uint32 +bit_count(uint32 value) +{ + uint32 count = 0; + while (value > 0) { + if (value & 1) + count++; + value >>= 1; + } + return count; +} + + +// #pragma mark - + + +SolutionStep::SolutionStep(const SudokuField* _field) +{ + fField = new SudokuField(*_field); + fX = 0; + fY = 0; +} + + +SolutionStep::SolutionStep(const SolutionStep& other) +{ + fField = new SudokuField(*other.fField); + fX = other.fX; + fY = other.fY; +} + + +SolutionStep::~SolutionStep() +{ + delete fField; +} + + +void +SolutionStep::ToFirstUnset() +{ + for (uint32 y = 0; y < fField->Size(); y++) { + for (uint32 x = 0; x < fField->Size(); x++) { + if (!fField->ValueAt(x, y)) { + uint32 validMask = fField->ValidMaskAt(x, y); + if (bit_count(validMask) == 1) { + // If the chosen value is already solved, we set its + // value here and go on - this makes sure the first + // unset we return has actually more than one possible + // value + uint32 value = 0; + while ((validMask & (1UL << value)) == 0) { + value++; + } + fField->SetValueAt(x, y, value + 1, true); + continue; + } + + fX = x; + fY = y; + return; + } + } + } + +} + + +bool +SolutionStep::ToNextUnset() +{ + for (uint32 y = fY; y < fField->Size(); y++) { + for (uint32 x = 0; x < fField->Size(); x++) { + if (y == fY && x == 0) { + x = fX; + continue; + } + + if (!fField->ValueAt(x, y)) { + fX = x; + fY = y; + return true; + } + } + } + + return false; +} + + +// #pragma mark - + + +SudokuSolver::SudokuSolver(SudokuField* field) + : + fField(field) +{ +} + + +SudokuSolver::SudokuSolver() + : + fField(NULL) +{ +} + + +SudokuSolver::~SudokuSolver() +{ + // we don't own the field but the solutions + + for (uint32 i = 0; i < fSolutions.size(); i++) { + delete fSolutions[i]; + } +} + + +void +SudokuSolver::SetTo(SudokuField* field) +{ + fField = field; +} + + +void +SudokuSolver::ComputeSolutions() +{ + Stack stack; + SolutionStep* step = new SolutionStep(fField); + step->ToFirstUnset(); + + stack.Push(step); + uint32 count = 0; + + // brute force version + + while (stack.Pop(&step)) { + uint32 x = step->X(); + uint32 y = step->Y(); + uint32 validMask = step->Field()->ValidMaskAt(x, y); + + count++; + + if (step->ToNextUnset()) { + if (validMask != 0) { + // generate further steps + for (uint32 i = 0; i < fField->Size(); i++) { + if ((validMask & (1UL << i)) == 0) + continue; + + SolutionStep* next = new SolutionStep(*step); + next->Field()->SetValueAt(x, y, i + 1, true); + stack.Push(next); + } + } + } else if (step->Field()->IsSolved()) + fSolutions.push_back(new SudokuField(*step->Field())); + + delete step; + } + + //printf("evaluated %lu steps\n", count); +} + + +uint32 +SudokuSolver::CountSolutions() +{ + return fSolutions.size(); +} + + +SudokuField* +SudokuSolver::SolutionAt(uint32 index) +{ + return fSolutions[index]; +} + diff --git a/src/apps/sudoku/SudokuSolver.h b/src/apps/sudoku/SudokuSolver.h new file mode 100644 index 0000000000..ec37569781 --- /dev/null +++ b/src/apps/sudoku/SudokuSolver.h @@ -0,0 +1,35 @@ +/* + * Copyright 2007, Axel Dörfler, axeld@pinc-software.de. All rights reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef SUDOKU_SOLVER_H +#define SUDOKU_SOLVER_H + + +#include + +#include + +class SudokuField; + +class SudokuSolver { +public: + SudokuSolver(SudokuField* field); + SudokuSolver(); + ~SudokuSolver(); + + void SetTo(SudokuField* field); + + void ComputeSolutions(); + + uint32 CountSolutions(); + SudokuField* SolutionAt(uint32 index); + +private: + typedef vector SudokuList; + + SudokuField* fField; + SudokuList fSolutions; +}; + +#endif // SUDOKU_SOLVER_H diff --git a/src/apps/sudoku/SudokuView.cpp b/src/apps/sudoku/SudokuView.cpp new file mode 100644 index 0000000000..53438ea970 --- /dev/null +++ b/src/apps/sudoku/SudokuView.cpp @@ -0,0 +1,804 @@ +/* + * Copyright 2007, Axel Dörfler, axeld@pinc-software.de. All rights reserved. + * Distributed under the terms of the MIT License. + */ + + +#include "SudokuView.h" + +#include "SudokuField.h" +#include "SudokuSolver.h" + +#include +#include +#include +#include + +#include +#include +#include + + +const uint32 kMsgCheckSolved = 'chks'; + +const uint32 kStrongLineSize = 2; + + +SudokuView::SudokuView(BRect frame, const char* name, + const BMessage& settings, uint32 resizingMode) + : BView(frame, name, resizingMode, + B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS), + fField(NULL), + fShowHintX(~0UL), + fLastHintValue(~0UL), + fLastField(~0UL), + fShowKeyboardFocus(false), + fEditable(true) +{ + BMessage field; + if (settings.FindMessage("field", &field) == B_OK) { + fField = new SudokuField(&field); + if (fField->InitCheck() != B_OK) { + delete fField; + fField = NULL; + } else if (fField->IsSolved()) + ClearAll(); + } + if (fField == NULL) + fField = new SudokuField(3); + + fBlockSize = fField->BlockSize(); + + if (settings.FindInt32("hint flags", (int32*)&fHintFlags) != B_OK) + fHintFlags = kMarkInvalid; + if (settings.FindBool("show cursor", &fShowCursor) != B_OK) + fShowCursor = false; + + SetViewColor(255, 255, 240); + SetLowColor(ViewColor()); + FrameResized(0, 0); +} + + +SudokuView::~SudokuView() +{ + delete fField; +} + + +status_t +SudokuView::SaveState(BMessage& state) +{ + BMessage field; + status_t status = fField->Archive(&field, true); + if (status == B_OK) + status = state.AddMessage("field", &field); + if (status == B_OK) + status = state.AddInt32("hint flags", fHintFlags); + if (status == B_OK) + status = state.AddBool("show cursor", fShowCursor); + + return status; +} + + +status_t +SudokuView::_FilterString(const char* data, size_t dataLength, char* buffer, + uint32& out, bool& ignore) +{ + uint32 maxOut = fField->Size() * fField->Size(); + + for (uint32 i = 0; data[i] && i < dataLength; i++) { + if (data[i] == '#') + ignore = true; + else if (data[i] == '\n') + ignore = false; + + if (ignore || isspace(data[i])) + continue; + + if (!_ValidCharacter(data[i])) { + return B_BAD_DATA; + } + + buffer[out++] = data[i]; + if (out == maxOut) + break; + } + + buffer[out] = '\0'; + return B_OK; +} + + +status_t +SudokuView::SetTo(entry_ref& ref) +{ + BPath path; + status_t status = path.SetTo(&ref); + if (status < B_OK) + return status; + + FILE* file = fopen(path.Path(), "r"); + if (file == NULL) + return errno; + + uint32 maxOut = fField->Size() * fField->Size(); + char buffer[1024]; + char line[1024]; + bool ignore = false; + uint32 out = 0; + + while (fgets(line, sizeof(line), file) != NULL + && out < maxOut) { + status = _FilterString(line, sizeof(line), buffer, out, ignore); + if (status < B_OK) { + fclose(file); + return status; + } + } + + status = fField->SetTo(_BaseCharacter(), buffer); + Invalidate(); + return status; +} + + +status_t +SudokuView::SetTo(const char* data) +{ + if (data == NULL) + return B_BAD_VALUE; + + char buffer[1024]; + bool ignore = false; + uint32 out = 0; + + status_t status = _FilterString(data, 65536, buffer, out, ignore); + if (status < B_OK) + return B_BAD_VALUE; + + status = fField->SetTo(_BaseCharacter(), buffer); + Invalidate(); + return status; +} + + +status_t +SudokuView::SetTo(SudokuField* field) +{ + if (field == NULL || field == fField) + return B_BAD_VALUE; + + delete fField; + fField = field; + + fBlockSize = fField->BlockSize(); + FrameResized(0, 0); + Invalidate(); + return B_OK; +} + + +status_t +SudokuView::SaveTo(entry_ref& ref, bool asText) +{ + BFile file; + status_t status = file.SetTo(&ref, B_WRITE_ONLY | B_CREATE_FILE + | B_ERASE_FILE); + if (status < B_OK) + return status; + + if (asText) { + char line[1024]; + strcpy(line, "# Written by Sudoku\n\n"); + file.Write(line, strlen(line)); + + uint32 i = 0; + + for (uint32 y = 0; y < fField->Size(); y++) { + for (uint32 x = 0; x < fField->Size(); x++) { + if (x != 0 && x % fBlockSize == 0) + line[i++] = ' '; + _SetText(&line[i++], fField->ValueAt(x, y)); + } + line[i++] = '\n'; + } + + file.Write(line, i); + } else { + } + + return status; +} + + +void +SudokuView::ClearChanged() +{ + for (uint32 y = 0; y < fField->Size(); y++) { + for (uint32 x = 0; x < fField->Size(); x++) { + if ((fField->FlagsAt(x, y) & kInitialValue) == 0) + fField->SetValueAt(x, y, 0); + } + } + + Invalidate(); +} + + +void +SudokuView::ClearAll() +{ + fField->Reset(); + Invalidate(); +} + + +void +SudokuView::SetHintFlags(uint32 flags) +{ + if (flags == fHintFlags) + return; + + if ((flags & kMarkInvalid) ^ (fHintFlags & kMarkInvalid)) + Invalidate(); + + fHintFlags = flags; +} + + +void +SudokuView::SetEditable(bool editable) +{ + fEditable = editable; +} + + +void +SudokuView::AttachedToWindow() +{ + MakeFocus(true); +} + + +void +SudokuView::_FitFont(BFont& font, float fieldWidth, float fieldHeight) +{ + font.SetSize(100); + + font_height fontHeight; + font.GetHeight(&fontHeight); + + float width = font.StringWidth("W"); + float height = ceilf(fontHeight.ascent) + ceilf(fontHeight.descent); + + float factor = fieldWidth != fHintWidth ? 4.f / 5.f : 1.f; + float widthFactor = fieldWidth / (width / factor); + float heightFactor = fieldHeight / (height / factor); + font.SetSize(100 * min_c(widthFactor, heightFactor)); +} + + +void +SudokuView::FrameResized(float /*width*/, float /*height*/) +{ + // font for numbers + + uint32 size = fField->Size(); + fWidth = (Bounds().Width() - kStrongLineSize * (fBlockSize - 1)) / size; + fHeight = (Bounds().Height() - kStrongLineSize * (fBlockSize - 1)) / size; + _FitFont(fFieldFont, fWidth - 2, fHeight - 2); + + font_height fontHeight; + fFieldFont.GetHeight(&fontHeight); + fBaseline = ceilf(fontHeight.ascent) / 2 + + (fHeight - ceilf(fontHeight.descent)) / 2; + + // font for hint + + fHintWidth = (fWidth - 2) / fBlockSize; + fHintHeight = (fHeight - 2) / fBlockSize; + _FitFont(fHintFont, fHintWidth, fHintHeight); + + fHintFont.GetHeight(&fontHeight); + fHintBaseline = ceilf(fontHeight.ascent) / 2 + + (fHintHeight - ceilf(fontHeight.descent)) / 2; +} + + +BPoint +SudokuView::_LeftTop(uint32 x, uint32 y) +{ + return BPoint(x * fWidth + x / fBlockSize * kStrongLineSize + 1, + y * fHeight + y / fBlockSize * kStrongLineSize + 1); +} + + +BRect +SudokuView::_Frame(uint32 x, uint32 y) +{ + BPoint leftTop = _LeftTop(x, y); + BPoint rightBottom = leftTop + BPoint(fWidth - 2, fHeight - 2); + + return BRect(leftTop, rightBottom); +} + + +void +SudokuView::_InvalidateHintField(uint32 x, uint32 y, uint32 hintX, + uint32 hintY) +{ + BPoint leftTop = _LeftTop(x, y); + leftTop.x += hintX * fHintWidth; + leftTop.y += hintY * fHintHeight; + BPoint rightBottom = leftTop; + rightBottom.x += fHintWidth; + rightBottom.y += fHintHeight; + + Invalidate(BRect(leftTop, rightBottom)); +} + + +void +SudokuView::_InvalidateField(uint32 x, uint32 y) +{ + Invalidate(_Frame(x, y)); +} + + +void +SudokuView::_InvalidateKeyboardFocus(uint32 x, uint32 y) +{ + BRect frame = _Frame(x, y); + frame.InsetBy(-1, -1); + Invalidate(frame); +} + + +bool +SudokuView::_GetHintFieldFor(BPoint where, uint32 x, uint32 y, + uint32& hintX, uint32& hintY) +{ + BPoint leftTop = _LeftTop(x, y); + hintX = (uint32)floor((where.x - leftTop.x) / fHintWidth); + hintY = (uint32)floor((where.y - leftTop.y) / fHintHeight); + + if (hintX >= fBlockSize || hintY >= fBlockSize) + return false; + + return true; +} + + +bool +SudokuView::_GetFieldFor(BPoint where, uint32& x, uint32& y) +{ + float block = fWidth * fBlockSize + kStrongLineSize; + x = (uint32)floor(where.x / block); + uint32 offsetX = (uint32)floor((where.x - x * block) / fWidth); + x = x * fBlockSize + offsetX; + + block = fHeight * fBlockSize + kStrongLineSize; + y = (uint32)floor(where.y / block); + uint32 offsetY = (uint32)floor((where.y - y * block) / fHeight); + y = y * fBlockSize + offsetY; + + if (offsetX >= fBlockSize || offsetY >= fBlockSize + || x >= fField->Size() || y >= fField->Size()) + return false; + + return true; +} + + +void +SudokuView::_RemoveHint() +{ + if (fShowHintX == ~0UL) + return; + + uint32 x = fShowHintX; + uint32 y = fShowHintY; + fShowHintX = ~0; + fShowHintY = ~0; + + _InvalidateField(x, y); +} + + +void +SudokuView::MouseDown(BPoint where) +{ + uint32 x, y; + if (!fEditable || !_GetFieldFor(where, x, y)) + return; + + int32 buttons = B_PRIMARY_MOUSE_BUTTON; + int32 clicks = 1; + if (Looper() != NULL && Looper()->CurrentMessage() != NULL) { + Looper()->CurrentMessage()->FindInt32("buttons", &buttons); + Looper()->CurrentMessage()->FindInt32("clicks", &clicks); + } + + uint32 hintX, hintY; + if (!_GetHintFieldFor(where, x, y, hintX, hintY)) + return; + + uint32 value = hintX + hintY * fBlockSize; + uint32 field = x + y * fField->Size(); + + if (clicks == 2 && fLastHintValue == value && fLastField == field + || (buttons & (B_SECONDARY_MOUSE_BUTTON + | B_TERTIARY_MOUSE_BUTTON)) != 0) { + // double click + if ((fField->FlagsAt(x, y) & kInitialValue) == 0) { + if (fField->ValueAt(x, y) > 0) { + fField->SetValueAt(x, y, 0); + fShowHintX = x; + fShowHintY = y; + } else { + fField->SetValueAt(x, y, value + 1); + BMessenger(this).SendMessage(kMsgCheckSolved); + } + + _InvalidateField(x, y); + } + return; + } + + uint32 hintMask = fField->HintMaskAt(x, y); + uint32 valueMask = 1UL << value; + if (hintMask & valueMask) + hintMask &= ~valueMask; + else + hintMask |= valueMask; + + fField->SetHintMaskAt(x, y, hintMask); + _InvalidateHintField(x, y, hintX, hintY); + + fLastHintValue = value; + fLastField = field; +} + + +void +SudokuView::MouseMoved(BPoint where, uint32 transit, + const BMessage* dragMessage) +{ + if (transit == B_EXITED_VIEW || dragMessage != NULL) { + _RemoveHint(); + return; + } + + if (fShowKeyboardFocus) { + fShowKeyboardFocus = false; + _InvalidateKeyboardFocus(fKeyboardX, fKeyboardY); + } + + uint32 x, y; + if (!_GetFieldFor(where, x, y) + || (fField->FlagsAt(x, y) & kInitialValue) != 0 + || !fShowCursor && fField->ValueAt(x, y) != 0) { + _RemoveHint(); + return; + } + + if (fShowHintX == x && fShowHintY == y) + return; + + _RemoveHint(); + fShowHintX = x; + fShowHintY = y; + _InvalidateField(x, y); +} + + +void +SudokuView::_InsertKey(char rawKey, int32 modifiers) +{ + if (!fEditable || !_ValidCharacter(rawKey) + || (fField->FlagsAt(fKeyboardX, fKeyboardY) & kInitialValue) != 0) + return; + + uint32 value = rawKey - _BaseCharacter(); + + if (modifiers & (B_SHIFT_KEY | B_OPTION_KEY)) { + // set or remove hint + if (value == 0) + return; + + uint32 hintMask = fField->HintMaskAt(fKeyboardX, fKeyboardY); + uint32 valueMask = 1UL << (value - 1); + if (modifiers & B_OPTION_KEY) + hintMask &= ~valueMask; + else + hintMask |= valueMask; + + fField->SetValueAt(fKeyboardX, fKeyboardY, 0); + fField->SetHintMaskAt(fKeyboardX, fKeyboardY, hintMask); + } else { + fField->SetValueAt(fKeyboardX, fKeyboardY, value); + if (value) + BMessenger(this).SendMessage(kMsgCheckSolved); + } +} + + +void +SudokuView::KeyDown(const char *bytes, int32 /*numBytes*/) +{ + be_app->ObscureCursor(); + + uint32 x = fKeyboardX, y = fKeyboardY; + + switch (bytes[0]) { + case B_UP_ARROW: + if (fKeyboardY == 0) + fKeyboardY = fField->Size() - 1; + else + fKeyboardY--; + break; + case B_DOWN_ARROW: + if (fKeyboardY == fField->Size() - 1) + fKeyboardY = 0; + else + fKeyboardY++; + break; + + case B_LEFT_ARROW: + if (fKeyboardX == 0) + fKeyboardX = fField->Size() - 1; + else + fKeyboardX--; + break; + case B_RIGHT_ARROW: + if (fKeyboardX == fField->Size() - 1) + fKeyboardX = 0; + else + fKeyboardX++; + break; + + case B_BACKSPACE: + case B_DELETE: + case B_SPACE: + // clear value + _InsertKey(_BaseCharacter(), 0); + break; + + default: + int32 rawKey = bytes[0]; + int32 modifiers = 0; + if (Looper() != NULL && Looper()->CurrentMessage() != NULL) { + Looper()->CurrentMessage()->FindInt32("raw_char", &rawKey); + Looper()->CurrentMessage()->FindInt32("modifiers", &modifiers); + } + + _InsertKey(rawKey, modifiers); + break; + } + + if (!fShowKeyboardFocus && fShowHintX != ~0UL) { + // always start at last mouse position, if any + fKeyboardX = fShowHintX; + fKeyboardY = fShowHintY; + } + + _RemoveHint(); + + // remove old focus, if any + if (fShowKeyboardFocus && (x != fKeyboardX || y != fKeyboardY)) + _InvalidateKeyboardFocus(x, y); + + fShowKeyboardFocus = true; + _InvalidateKeyboardFocus(fKeyboardX, fKeyboardY); +} + + +void +SudokuView::MessageReceived(BMessage* message) +{ + switch (message->what) { + case kMsgCheckSolved: + if (fField->IsSolved()) { + // notify window + Looper()->PostMessage(kMsgSudokuSolved); + } + break; + + case kMsgSolveSudoku: + { + SudokuSolver solver; + solver.SetTo(fField); + bigtime_t start = system_time(); + solver.ComputeSolutions(); + printf("found %ld solutions in %g msecs\n", + solver.CountSolutions(), (system_time() - start) / 1000.0); + if (solver.CountSolutions() > 0) { + fField->SetTo(solver.SolutionAt(0)); + Invalidate(); + } + break; + } + + case kMsgSolveSingle: + { + SudokuSolver solver; + solver.SetTo(fField); + bigtime_t start = system_time(); + solver.ComputeSolutions(); + printf("found %ld solutions in %g msecs\n", + solver.CountSolutions(), (system_time() - start) / 1000.0); + if (solver.CountSolutions() > 0) { + // find free spot + uint32 x, y; + do { + x = rand() % fField->Size(); + y = rand() % fField->Size(); + } while (fField->ValueAt(x, y)); + + fField->SetValueAt(x, y, + solver.SolutionAt(0)->ValueAt(x, y)); + _InvalidateField(x, y); + } + break; + } + + default: + BView::MessageReceived(message); + break; + } +} + + +char +SudokuView::_BaseCharacter() +{ + return fField->Size() > 9 ? '@' : '0'; +} + + +bool +SudokuView::_ValidCharacter(char c) +{ + char min = _BaseCharacter(); + char max = min + fField->Size(); + return c >= min && c <= max; +} + + +void +SudokuView::_SetText(char* text, uint32 value) +{ + text[0] = value + _BaseCharacter(); + text[1] = '\0'; +} + + +void +SudokuView::_DrawKeyboardFocus() +{ + BRect frame = _Frame(fKeyboardX, fKeyboardY); + SetHighColor(ui_color(B_KEYBOARD_NAVIGATION_COLOR)); + StrokeRect(frame); + frame.InsetBy(-1, -1); + StrokeRect(frame); + frame.InsetBy(2, 2); + StrokeRect(frame); +} + + +void +SudokuView::_DrawHints(uint32 x, uint32 y) +{ + bool showAll = fShowHintX == x && fShowHintY == y; + uint32 hintMask = fField->HintMaskAt(x, y); + if (hintMask == 0 && !showAll) + return; + + uint32 validMask = fField->ValidMaskAt(x, y); + BPoint leftTop = _LeftTop(x, y); + SetFont(&fHintFont); + + for (uint32 j = 0; j < fBlockSize; j++) { + for (uint32 i = 0; i < fBlockSize; i++) { + uint32 value = j * fBlockSize + i; + if (hintMask & (1UL << value)) + SetHighColor(200, 0, 0); + else { + if (!showAll) + continue; + + if ((fHintFlags & kMarkValidHints) == 0 + || validMask & (1UL << value)) + SetHighColor(110, 110, 80); + else + SetHighColor(180, 180, 120); + } + + char text[2]; + _SetText(text, value + 1); + DrawString(text, leftTop + BPoint((i + 0.5f) * fHintWidth + - StringWidth(text) / 2, floorf(j * fHintHeight) + + fHintBaseline)); + } + } +} + + +void +SudokuView::Draw(BRect /*updateRect*/) +{ + // draw lines + + uint32 size = fField->Size(); + + SetHighColor(0, 0, 0); + + float width = fWidth; + for (uint32 x = 1; x < size; x++) { + if (x % fBlockSize == 0) { + FillRect(BRect(width, 0, width + kStrongLineSize, + Bounds().Height())); + width += kStrongLineSize; + } else { + StrokeLine(BPoint(width, 0), BPoint(width, Bounds().Height())); + } + width += fWidth; + } + + float height = fHeight; + for (uint32 y = 1; y < size; y++) { + if (y % fBlockSize == 0) { + FillRect(BRect(0, height, Bounds().Width(), + height + kStrongLineSize)); + height += kStrongLineSize; + } else { + StrokeLine(BPoint(0, height), BPoint(Bounds().Width(), height)); + } + height += fHeight; + } + + // draw text + + for (uint32 y = 0; y < size; y++) { + for (uint32 x = 0; x < size; x++) { + if ((fShowCursor && x == fShowHintX && y == fShowHintY + || fShowKeyboardFocus && x == fKeyboardX + && y == fKeyboardY) + && (fField->FlagsAt(x, y) & kInitialValue) == 0) { + //SetLowColor(tint_color(ViewColor(), B_DARKEN_1_TINT)); + SetLowColor(255, 255, 210); + FillRect(_Frame(x, y), B_SOLID_LOW); + } else + SetLowColor(ViewColor()); + + if (fShowKeyboardFocus && x == fKeyboardX && y == fKeyboardY) + _DrawKeyboardFocus(); + + uint32 value = fField->ValueAt(x, y); + if (value == 0) { + _DrawHints(x, y); + continue; + } + + SetFont(&fFieldFont); + if (fField->FlagsAt(x, y) & kInitialValue) + SetHighColor(0, 0, 0); + else { + if ((fHintFlags & kMarkInvalid) == 0 + || fField->ValidMaskAt(x, y) & (1UL << (value - 1))) + SetHighColor(0, 0, 200); + else + SetHighColor(200, 0, 0); + } + + char text[2]; + _SetText(text, value); + DrawString(text, _LeftTop(x, y) + + BPoint((fWidth - StringWidth(text)) / 2, fBaseline)); + } + } +} + + diff --git a/src/apps/sudoku/SudokuView.h b/src/apps/sudoku/SudokuView.h new file mode 100644 index 0000000000..f384ace9d9 --- /dev/null +++ b/src/apps/sudoku/SudokuView.h @@ -0,0 +1,98 @@ +/* + * Copyright 2007, Axel Dörfler, axeld@pinc-software.de. All rights reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef SUDOKU_VIEW_H +#define SUDOKU_VIEW_H + + +#include + +class SudokuField; + + +enum { + kMarkValidHints = 0x01, + kMarkInvalid = 0x02, +}; + + +class SudokuView : public BView { +public: + SudokuView(BRect frame, const char* name, const BMessage& settings, + uint32 resizingMode); + virtual ~SudokuView(); + + status_t SaveState(BMessage& state); + + status_t SetTo(entry_ref& ref); + status_t SetTo(const char* data); + status_t SetTo(SudokuField* field); + + status_t SaveTo(entry_ref& ref, bool asText); + + void ClearChanged(); + void ClearAll(); + + void SetHintFlags(uint32 flags); + uint32 HintFlags() const { return fHintFlags; } + + SudokuField* Field() { return fField; } + + void SetEditable(bool editable); + bool Editable() const { return fEditable; } + +protected: + virtual void AttachedToWindow(); + + virtual void FrameResized(float width, float height); + virtual void MouseDown(BPoint where); + virtual void MouseMoved(BPoint where, uint32 transit, + const BMessage* dragMessage); + virtual void KeyDown(const char *bytes, int32 numBytes); + + virtual void MessageReceived(BMessage* message); + + virtual void Draw(BRect updateRect); + +private: + status_t _FilterString(const char* data, size_t dataLength, char* buffer, + uint32& out, bool& ignore); + void _SetText(char* text, uint32 value); + char _BaseCharacter(); + bool _ValidCharacter(char c); + BPoint _LeftTop(uint32 x, uint32 y); + BRect _Frame(uint32, uint32 y); + void _InvalidateHintField(uint32 x, uint32 y, uint32 hintX, uint32 hintY); + void _InvalidateField(uint32 x, uint32 y); + void _InvalidateKeyboardFocus(uint32 x, uint32 y); + void _InsertKey(char rawKey, int32 modifiers); + void _RemoveHint(); + bool _GetHintFieldFor(BPoint where, uint32 x, uint32 y, + uint32& hintX, uint32& hintY); + bool _GetFieldFor(BPoint where, uint32& x, uint32& y); + void _FitFont(BFont& font, float width, float height); + void _DrawKeyboardFocus(); + void _DrawHints(uint32 x, uint32 y); + + SudokuField* fField; + uint32 fBlockSize; + float fWidth, fHeight, fBaseline; + BFont fFieldFont; + BFont fHintFont; + float fHintHeight, fHintWidth, fHintBaseline; + uint32 fShowHintX, fShowHintY; + uint32 fLastHintValue; + uint32 fLastField; + uint32 fKeyboardX, fKeyboardY; + uint32 fHintFlags; + bool fShowKeyboardFocus; + bool fShowCursor; + bool fEditable; +}; + +static const uint32 kMsgSudokuSolved = 'susl'; +static const uint32 kMsgSolveSudoku = 'slvs'; +static const uint32 kMsgSolveSingle = 'slsg'; + +#endif // SUDOKU_VIEW_H diff --git a/src/apps/sudoku/SudokuWindow.cpp b/src/apps/sudoku/SudokuWindow.cpp new file mode 100644 index 0000000000..559acaea33 --- /dev/null +++ b/src/apps/sudoku/SudokuWindow.cpp @@ -0,0 +1,459 @@ +/* + * Copyright 2007, Axel Dörfler, axeld@pinc-software.de. All rights reserved. + * Distributed under the terms of the MIT License. + */ + + +#include "SudokuWindow.h" + +#include "ProgressWindow.h" +#include "Sudoku.h" +#include "SudokuField.h" +#include "SudokuGenerator.h" +#include "SudokuView.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +const uint32 kMsgOpenFilePanel = 'opfp'; +const uint32 kMsgGenerateVeryEasySudoku = 'gnsv'; +const uint32 kMsgGenerateEasySudoku = 'gnse'; +const uint32 kMsgGenerateHardSudoku = 'gnsh'; +const uint32 kMsgAbortSudokuGenerator = 'asgn'; +const uint32 kMsgSudokuGenerated = 'sugn'; +const uint32 kMsgMarkInvalid = 'minv'; +const uint32 kMsgMarkValidHints = 'mvht'; +const uint32 kMsgNew = 'new '; +const uint32 kMsgStartAgain = 'stag'; +const uint32 kMsgExportAsText = 'extx'; + + +class GenerateSudoku { +public: + GenerateSudoku(SudokuField& target, int32 level, BMessenger progress, + BMessenger target); + ~GenerateSudoku(); + + void Abort(); + +private: + void _Generate(); + static status_t _GenerateThread(void* self); + + SudokuField fField; + BMessenger fTarget; + BMessenger fProgress; + thread_id fThread; + int32 fLevel; + bool fQuit; +}; + + +GenerateSudoku::GenerateSudoku(SudokuField& field, int32 level, + BMessenger progress, BMessenger target) + : + fField(field), + fTarget(target), + fProgress(progress), + fLevel(level), + fQuit(false) +{ + fThread = spawn_thread(_GenerateThread, "sudoku generator", + B_LOW_PRIORITY, this); + if (fThread >= B_OK) + resume_thread(fThread); + else + _Generate(); +} + + +GenerateSudoku::~GenerateSudoku() +{ + Abort(); +} + + +void +GenerateSudoku::Abort() +{ + fQuit = true; + + status_t status; + wait_for_thread(fThread, &status); +} + + +void +GenerateSudoku::_Generate() +{ + SudokuGenerator generator; + + bigtime_t start = system_time(); + generator.Generate(&fField, 40 - fLevel * 5, fProgress, &fQuit); + printf("generated in %g msecs\n", + (system_time() - start) / 1000.0); + + BMessage done(kMsgSudokuGenerated); + if (!fQuit) { + BMessage field; + if (fField.Archive(&field, true) == B_OK) + done.AddMessage("field", &field); + } + + fTarget.SendMessage(&done); +} + + +/*static*/ status_t +GenerateSudoku::_GenerateThread(void* _self) +{ + GenerateSudoku* self = (GenerateSudoku*)_self; + self->_Generate(); + return B_OK; +} + + +// #pragma mark - + + +SudokuWindow::SudokuWindow() + : BWindow(BRect(100, 100, 500, 520), "Sudoku", B_TITLED_WINDOW, + B_ASYNCHRONOUS_CONTROLS), + fGenerator(NULL) +{ + BMessage settings; + _LoadSettings(settings); + + BRect frame; + if (settings.FindRect("window frame", &frame) == B_OK) { + MoveTo(frame.LeftTop()); + ResizeTo(frame.Width(), frame.Height()); + frame.OffsetTo(B_ORIGIN); + } else + frame = Bounds(); + + // create GUI + + BMenuBar* menuBar = new BMenuBar(Bounds(), "menu"); + AddChild(menuBar); + + frame.top = menuBar->Frame().bottom; + + BView* top = new BView(frame, NULL, B_FOLLOW_ALL, B_WILL_DRAW); + top->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + AddChild(top); + + fSudokuView = new SudokuView(top->Bounds().InsetByCopy(10, 10), + "sudoku view", settings, B_FOLLOW_ALL); + top->AddChild(fSudokuView); + + // add menu + + BMenu* menu = new BMenu("File"); + menu->AddItem(new BMenuItem("New", new BMessage(kMsgNew))); + menu->AddItem(new BMenuItem("Start Again", new BMessage(kMsgStartAgain))); + menu->AddSeparatorItem(); + BMenu* recentsMenu = BRecentFilesList::NewFileListMenu( + "Open File" B_UTF8_ELLIPSIS, NULL, NULL, this, 10, false, NULL, + kSignature); + BMenuItem *item; + menu->AddItem(item = new BMenuItem(recentsMenu, + new BMessage(kMsgOpenFilePanel))); + item->SetShortcut('O', B_COMMAND_KEY); + BMenu* subMenu = new BMenu("Generate"); + subMenu->AddItem(new BMenuItem("Very Easy", + new BMessage(kMsgGenerateVeryEasySudoku))); + subMenu->AddItem(new BMenuItem("Easy", + new BMessage(kMsgGenerateEasySudoku))); + subMenu->AddItem(new BMenuItem("Hard", + new BMessage(kMsgGenerateHardSudoku))); + menu->AddItem(subMenu); + + menu->AddSeparatorItem(); + + menu->AddItem(new BMenuItem("Export As Text" B_UTF8_ELLIPSIS, + new BMessage(kMsgExportAsText))); + + menu->AddSeparatorItem(); + + menu->AddItem(item = new BMenuItem("About Sudoku" B_UTF8_ELLIPSIS, + new BMessage(B_ABOUT_REQUESTED))); + menu->AddSeparatorItem(); + + menu->AddItem(new BMenuItem("Quit", new BMessage(B_QUIT_REQUESTED), + 'Q', B_COMMAND_KEY)); + menu->SetTargetForItems(this); + item->SetTarget(be_app); + menuBar->AddItem(menu); + + menu = new BMenu("View"); + menu->AddItem(item = new BMenuItem("Mark Invalid Values", + new BMessage(kMsgMarkInvalid))); + if (fSudokuView->HintFlags() & kMarkInvalid) + item->SetMarked(true); + menu->AddItem(item = new BMenuItem("Mark Valid Hints", + new BMessage(kMsgMarkValidHints))); + if (fSudokuView->HintFlags() & kMarkValidHints) + item->SetMarked(true); + menu->SetTargetForItems(this); + menuBar->AddItem(menu); + + menu = new BMenu("Help"); + menu->AddItem(new BMenuItem("Solve", new BMessage(kMsgSolveSudoku))); + menu->AddItem(new BMenuItem("Solve Single Field", + new BMessage(kMsgSolveSingle))); + menu->SetTargetForItems(fSudokuView); + menuBar->AddItem(menu); + + fOpenPanel = new BFilePanel(B_OPEN_PANEL); + fOpenPanel->SetTarget(this); + fSavePanel = new BFilePanel(B_SAVE_PANEL); + fSavePanel->SetTarget(this); + + fProgressWindow = new ProgressWindow(this, + new BMessage(kMsgAbortSudokuGenerator)); +} + + +SudokuWindow::~SudokuWindow() +{ + delete fOpenPanel; + delete fSavePanel; + delete fGenerator; + + if (fProgressWindow->Lock()) + fProgressWindow->Quit(); +} + + +status_t +SudokuWindow::_OpenSettings(BFile& file, uint32 mode) +{ + BPath path; + if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK) + return B_ERROR; + + path.Append("pinc.Sudoku settings"); + + return file.SetTo(path.Path(), mode); +} + + +status_t +SudokuWindow::_LoadSettings(BMessage& settings) +{ + BFile file; + status_t status = _OpenSettings(file, B_READ_ONLY); + if (status < B_OK) + return status; + + return settings.Unflatten(&file); +} + + +status_t +SudokuWindow::_SaveSettings() +{ + BFile file; + status_t status = _OpenSettings(file, B_WRITE_ONLY | B_CREATE_FILE + | B_ERASE_FILE); + if (status < B_OK) + return status; + + BMessage settings('sudo'); + status = settings.AddRect("window frame", Frame()); + if (status == B_OK) + status = fSudokuView->SaveState(settings); + if (status == B_OK) + status = settings.Flatten(&file); + + return status; +} + + +void +SudokuWindow::_MessageDropped(BMessage* message) +{ + status_t status = B_MESSAGE_NOT_UNDERSTOOD; + bool hasRef = false; + + entry_ref ref; + if (message->FindRef("refs", &ref) != B_OK) { + const void* data; + ssize_t size; + if (message->FindData("text/plain", B_MIME_TYPE, &data, + &size) == B_OK) { + status = fSudokuView->SetTo((const char*)data); + } else + return; + } else { + status = fSudokuView->SetTo(ref); + if (status == B_OK) + be_roster->AddToRecentDocuments(&ref, kSignature); + + BEntry entry(&ref); + entry_ref parent; + if (entry.GetParent(&entry) == B_OK + && entry.GetRef(&parent) == B_OK) + fSavePanel->SetPanelDirectory(&parent); + + hasRef = true; + } + + if (status < B_OK) { + char buffer[1024]; + if (hasRef) { + snprintf(buffer, sizeof(buffer), + "Could not open \"%s\":\n" + "%s", ref.name, strerror(status)); + } else { + snprintf(buffer, sizeof(buffer), "Could not set Sudoku:\n%s", + strerror(status)); + } + + (new BAlert("Sudoku request", + buffer, "Ok", NULL, NULL, + B_WIDTH_AS_USUAL, B_STOP_ALERT))->Go(); + } +} + + +void +SudokuWindow::_Generate(int32 level) +{ + if (fGenerator != NULL) + delete fGenerator; + + fSudokuView->SetEditable(false); + fProgressWindow->Start(); + fGenerator = new GenerateSudoku(*fSudokuView->Field(), level, + fProgressWindow, this); +} + + +void +SudokuWindow::MessageReceived(BMessage* message) +{ + if (message->WasDropped()) { + _MessageDropped(message); + return; + } + + switch (message->what) { + case kMsgOpenFilePanel: + fOpenPanel->Show(); + break; + + case B_REFS_RECEIVED: + case B_SIMPLE_DATA: + _MessageDropped(message); + break; + + case kMsgGenerateVeryEasySudoku: + _Generate(0); + break; + case kMsgGenerateEasySudoku: + _Generate(2); + break; + case kMsgGenerateHardSudoku: + _Generate(4); + break; + case kMsgAbortSudokuGenerator: + if (fGenerator != NULL) + fGenerator->Abort(); + break; + case kMsgSudokuGenerated: + { + BMessage archive; + if (message->FindMessage("field", &archive) == B_OK) { + SudokuField* field = new SudokuField(&archive); + fSudokuView->SetTo(field); + } + fSudokuView->SetEditable(true); + fProgressWindow->Stop(); + + delete fGenerator; + fGenerator = NULL; + break; + } + + case kMsgExportAsText: + fSavePanel->Show(); + break; + + case B_SAVE_REQUESTED: + { + entry_ref directoryRef; + const char* name; + if (message->FindRef("directory", &directoryRef) != B_OK + || message->FindString("name", &name) != B_OK) + break; + + BDirectory directory(&directoryRef); + BEntry entry(&directory, name); + + entry_ref ref; + if (entry.GetRef(&ref) == B_OK) + fSudokuView->SaveTo(ref, true); + break; + } + + case kMsgNew: + fSudokuView->ClearAll(); + break; + + case kMsgStartAgain: + fSudokuView->ClearChanged(); + break; + + case kMsgMarkInvalid: + case kMsgMarkValidHints: + { + BMenuItem* item; + if (message->FindPointer("source", (void**)&item) != B_OK) + return; + + uint32 flag = message->what == kMsgMarkInvalid + ? kMarkInvalid : kMarkValidHints; + + item->SetMarked(!item->IsMarked()); + if (item->IsMarked()) + fSudokuView->SetHintFlags(fSudokuView->HintFlags() | flag); + else + fSudokuView->SetHintFlags(fSudokuView->HintFlags() & ~flag); + break; + } + + case kMsgSudokuSolved: + (new BAlert("Sudoku request", + "Sudoku solved - congratulations!", "Ok", NULL, NULL, + B_WIDTH_AS_USUAL, B_IDEA_ALERT))->Go(); + break; + + default: + BWindow::MessageReceived(message); + break; + } +} + + +bool +SudokuWindow::QuitRequested() +{ + _SaveSettings(); + be_app->PostMessage(B_QUIT_REQUESTED); + return true; +} diff --git a/src/apps/sudoku/SudokuWindow.h b/src/apps/sudoku/SudokuWindow.h new file mode 100644 index 0000000000..2d067f3b45 --- /dev/null +++ b/src/apps/sudoku/SudokuWindow.h @@ -0,0 +1,41 @@ +/* + * Copyright 2007, Axel Dörfler, axeld@pinc-software.de. All rights reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef SUDOKU_WINDOW_H +#define SUDOKU_WINDOW_H + + +#include + +class BFile; +class BFilePanel; +class GenerateSudoku; +class ProgressWindow; +class SudokuView; + + +class SudokuWindow : public BWindow { +public: + SudokuWindow(); + virtual ~SudokuWindow(); + + virtual void MessageReceived(BMessage* message); + virtual bool QuitRequested(); + +private: + status_t _OpenSettings(BFile& file, uint32 mode); + status_t _LoadSettings(BMessage& settings); + status_t _SaveSettings(); + + void _MessageDropped(BMessage *message); + void _Generate(int32 level); + + BFilePanel* fOpenPanel; + BFilePanel* fSavePanel; + ProgressWindow* fProgressWindow; + SudokuView* fSudokuView; + GenerateSudoku* fGenerator; +}; + +#endif // SUDOKU_WINDOW_H