* 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
This commit is contained in:
Axel Dörfler 2007-08-25 17:39:46 +00:00
parent 227fb2b11c
commit d578543aa6
19 changed files with 2769 additions and 5 deletions

View File

@ -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

View File

@ -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 ;

16
src/apps/sudoku/Jamfile Normal file
View File

@ -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
;

View File

@ -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 <Autolock.h>
#include <Button.h>
#include <MessageRunner.h>
#include <Screen.h>
#include <StatusBar.h>
#include <stdio.h>
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);
}
}

View File

@ -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 <Window.h>
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

78
src/apps/sudoku/Stack.h Normal file
View File

@ -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 <SupportDefs.h>
template<class T> 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 */

101
src/apps/sudoku/Sudoku.cpp Normal file
View File

@ -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 <stdlib.h>
#include <Alert.h>
#include <Application.h>
#include <TextView.h>
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;
}

10
src/apps/sudoku/Sudoku.h Normal file
View File

@ -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

View File

@ -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

View File

@ -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 <new>
#include <stdio.h>
#include <Message.h>
#include <OS.h>
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];
}

View File

@ -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 <Archivable.h>
#include <SupportDefs.h>
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

View File

@ -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(&copy);
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(&copy);
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);
}

View File

@ -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 <vector>
#include <Messenger.h>
#include <SupportDefs.h>
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

View File

@ -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<SolutionStep*> 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<SolutionStep*> 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];
}

View File

@ -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 <vector>
#include <SupportDefs.h>
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<SudokuField*> SudokuList;
SudokuField* fField;
SudokuList fSolutions;
};
#endif // SUDOKU_SOLVER_H

View File

@ -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 <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <Application.h>
#include <File.h>
#include <Path.h>
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));
}
}
}

View File

@ -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 <View.h>
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

View File

@ -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 <stdio.h>
#include <Alert.h>
#include <Application.h>
#include <File.h>
#include <FilePanel.h>
#include <FindDirectory.h>
#include <Menu.h>
#include <MenuBar.h>
#include <MenuItem.h>
#include <Path.h>
#include <Roster.h>
#include <be_apps/Tracker/RecentItems.h>
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;
}

View File

@ -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 <Window.h>
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