Click here to Skip to main content
15,889,992 members
Articles / Desktop Programming / MFC
Article

XSudokuWnd - a control that implements the Sudoku puzzle

Rate me:
Please Sign up or sign in to vote.
4.96/5 (75 votes)
19 Mar 2007CPOL9 min read 208.3K   1.7K   92   56
XSudokuWnd is an MFC control that implements a solver for the popular Sudoku puzzle, based on D.E. Knuth's Dancing Links algorithm. Example projects show how to use XSudokuWnd in dialog and SDI applications.

Introduction

There seems little need to introduce Sudoku, since it is now one of the most popular puzzles in the world. Typically, people find Sudoku puzzles in newspapers such as the LA Times or online - a google search for Sudoku produces millions of hits. Sudoku rules are very simple: in each cell of a 9x9 Sudoku board, a digit (1 thru 9) may be placed. Each digit may appear only once in each row and each column. There is a further restriction that the board is divided into 9 3x3 subgrids called regions, and each digit may appear only once in each region. Finally, every valid Sudoku puzzle (or proper puzzle) has a single unique solution. More information on Sudoku can be found on Wikipedia.

I was surprised to find that Sudoku has even infiltrated CodeProject - a search brings up five articles. And there is also a Sudoku Programming Forum, where people can discuss techniques for solving Sudoku puzzles by computer. What was most interesting to me was how elaborate the manual solving techniques have become. The more famous of the manual techniques have been given suggestive names such as Naked Single, X-Wing, and Swordfish. To solve a "hard" Sudoku manually, it may be necessary to utilize several techniques in turn. In general, Sudoku solver programs (such as Sudo Cue) try to emulate the manual solving techniques.

Since most of the manual solving techniques seem to be very intricate and somewhat ad-hoc, I never really considered trying to program a Sudoku solver until I discovered that D.E. Knuth has published an algorithm he calls Dancing Links that may be applied directly to solving Sudoku puzzles. In addition to being very fast, Knuth's algorithm also has the benefit that it is guaranteed to find the solution if the puzzle is valid.

XSudokuWnd Features

Here are the main features of XSudokuWnd:

  • Implemented as an MFC extension DLL
  • Complete API allows access to all features, colors, cell values, etc.
  • Extremely fast, even hardest Sudoku solved in less than a second
  • Able to import Sudoku puzzles from files, clipboard, or via an entry dialog
  • Understands most file formats used by other Sudoku software
  • Optional message callback allows parent window to receive status messages
  • Supports unlimited number of undo/redo actions
  • User can customize all colors
  • User can show/hide pencil marks
  • User can show/hide hint for a single cell, or show solution for entire puzzle
  • Built-in facility to allow user to check entered values
  • User can highlight all cells containing a particular value, hint, or pencil mark
  • User can print bitmap of puzzle
  • User can copy bitmap of puzzle to clipboard

XSudokuWnd Implementation

XSudokuWnd is implemented as a CWnd-derived class. Because of all the files that are associated with the XSudokuWnd implementation, I decided to package it as an MFC extension DLL. The download contains the XSudokuWnd DLL as well as a sample dialog app and a sample SDI app.

Here is what the dialog app looks like:

screenshot

Some Terminology

When a Sudoku puzzle is loaded, the given values are displayed in red, as in the above screenshot. Given values are used by XSudokuWnd to solve the puzzle values for other cells; the given values cannot be changed by the user.

Pencil marks are displayed as small numbers within each cell. A pencil mark refers to one of the 9 possible values (sometimes called candidate values) of a cell, that meet all the rules for solving a Sudoku. The user may enter one of the indicated pencil marks by selecting one of the cells and hitting a number key, or by selecting a value from the right-click menu:

screenshot

Another user assistance feature is to highlight all the possible cells for a candidate value. Here all the cells for '2' are highlighted:

screenshot

The right-click menu also allows the user to get a hint for the selected cell, or to show the entire solution, as below:

screenshot

XSudokuWnd Input Formats

There are many sources of sample Sudoku puzzles on the web, including syndicated newspaper puzzles, and web sites that have large puzzle archives, with puzzles graded according to difficulty. The sample dialog app is able to read puzzles from a file, from the clipboard, or directly from puzzle entry dialog:

screenshot

Nearly all online Sudoku puzzles are available in one of two basic formats:

  1. 81-character text string - for example,
    708000300000201000500000000040000026300080000000100090090600004000070500000000000. 
  2. 9x9 grid - for example, the puzzle in (1) above could also be entered as
    708000300
    000201000
    500000000
    040000026
    300080000
    000100090
    090600004
    000070500
    000000000

    or with extra formatting as

    7.8 ... 3..
    ... 2.1 ...
    5.. ... ...
    
    .4. ... .26
    3.. .8. ...
    ... 1.. .9.
    
    .9. 6.. ..4
    ... .7. 5..
    ... ... ...

    or (.ss format)

    *-----------*
    |7.8|...|3..|
    |...|2.1|...|
    |5..|...|...|
    |---+---+---|
    |.4.|...|.26|
    |3..|.8.|...|
    |...|1..|.9.|
    |---+---+---|
    |.9.|6..|..4|
    |...|.7.|5..|
    |...|...|...|
    *-----------*

    or (.sdk format)

    [Puzzle]
    7.8...3..
    ...2.1...
    5........
    .4.....26
    3...8....
    ...1...9.
    .9.6....4
    ....7.5..
    .........

    Note that a period or zero may be used to indicate an unknown value, which the user must supply to solve the puzzle.

XSudokuWnd can read any of the above formats - from file, from clipboard, or by using puzzle entry dialog:

screenshot

Any character other than a digit or a period is ignored, and a total of 81 digits (including periods) is necessary for a valid puzzle.

How To Use XSudokuWnd

To integrate XSudokuWnd into your app, you first need to add CXSudokuWnd member variable to your dialog (or view) header file, and include XSudokuWnd.h:

C++
CXSudokuWnd  m_SudokuWnd;

You will need to add the XSudokuWndDll directory to your project's list of "Additional include directories".

Next, use resource editor to add static placeholder control to your dialog, in the position where you want XSudokuWnd window:

C++
CWnd *pWnd = GetDlgItem(IDC_STATIC_RECT);
ASSERT(pWnd);

CRect rect;
pWnd->GetWindowRect(&rect);
ScreenToClient(&rect);

CSize size = m_SudokuWnd.GetWindowSize();

rect.right  = rect.left + size.cx;
rect.bottom = rect.top + size.cy;

pWnd->ShowWindow(SW_HIDE);

and then create XSudokuWnd window:

C++
m_SudokuWnd.Create(NULL, NULL, WS_CLIPSIBLINGS | WS_CHILD | WS_VISIBLE | WS_BORDER,
    rect, this, 9999);

See XSudokuDlg.cpp for examples of how to enter a sudoku puzzle into XSudokuWnd.

Next, edit the "Additional library path" in the project settings to include the XSudokuWndD.lib or XSudokuWndR.lib directory path.

Finally, make sure the XSudokuWndD.dll or XSudokuWndR.dll is in the same directory as your app's exe.

XSudokuWnd User Interface

Navigation

The user may navigate by using the arrow keys, Page Up and Page Down keys, and Home and End keys.

Entering a Value

To enter a value in a cell, select that cell, and then press a digit key (1 - 9). To remove a user entry in a cell, you can select Remove User Entry from the right-click menu, or simply press the 0 (zero) key when the cell is selected.

You may also get a hint for a cell; this will insert the value from the Sudoku solution into the cell. To get a hint, you can select Show Hint from the right-click menu, or press F2.

When you enter (or remove) values and hints, the action is recorded in an undo list. You can use the standard Ctrl+Z and Ctrl+Y to undo/redo actions; or you can select Undo or Redo from the right-click menu.

Right-Click (Context) Menu

The right-click menu is shown above. It can be displayed by using a right click, left double click, enter key, and Shift+F10.

  • Undo - undo last user entry or hint action; Ctrl+Z may also be used.
  • Redo - repeat last user entry or hint action; Ctrl+Y may also be used.
  • "Set to" items - set the selected cell to the value
  • Show Hint - show/hide hint for the selected cell; F2 may also be used.
  • Remove All Hints - remove all hints
  • Remove User Entry - remove user entry for the selected cell
  • Remove All User Entries - remove all user entries
  • Check User Entries - check all user values
  • Show Solution - show/hide solution for all cells; F3 may also be used.
  • Show Pencil Marks - show/hide pencil marks; F4 may also be used.
  • Reset - reset puzzle to initial state
  • Print Window - print window bitmap; Ctrl+P may also be used.
  • Copy Window to Clipboard - copy window bitmap to clipboard; Ctrl+C may also be used.
  • Highlight N - highlight all cells with the same user value, hint, or candidate value as N; the keyboard shortcuts Ctrl+1, Ctrl+2, etc., may also be used. Selecting the same menu entry (or pressing the same keyboard shortcut) has the effect of removing the highlighting. The keyboard shortcut Ctrl+0 can also be used to remove any highlighting.

Displaying Color Preferences Dialog

You can change any of the XSudokuWnd colors with the color preferences dialog. which is displayed when you click on Alt+Enter:

screenshot

XSudokuWnd Functions

Here are the functions available with CXSudokuWnd:

CaptureBitmap()Copy window bitmap to clipboard
Get3x3Gridline()Get 3x3 gridline color
GetCellBackground()Get cell background color
GetCurCellBorder()Get current cell border color
GetGivens()Get color for given values
GetGivenValues()Get given values
GetHighlightNumber()Get highlight number
GetHighlightNumberColor()Get highlight color
GetHints()Get hint values
GetLabels()Get label color
GetPencilMarks()Get pencil marks color
GetShowPencilMarks()Get show state of pencil marks
GetShowSolution()Get show state of solution
GetSolution()Get solution color
GetSolutionValues()Get solution values
GetSudoku()Display Sudoku entry dialog
GetUndoEnable()Get enable state of undo facility
GetUserEntries()Get user entry values
GetUserEntry()Get user entry color
GetWindowBackground()Get window background color
GetWindowSize()Get minimum size of XSudokuWnd window
IsValid()Returns TRUE if Sudoku is valid (exactly 1 solution)
LoadFromClipboard()Load Sudoku from clipboard
LoadFromFile()Load Sudoku from file
LoadFromString()Load Sudoku from string
PrintBitmap()Print window bitmap
Set3x3Gridline()Set 3x3 gridline color
SetCellBackground()Set cell background color
SetCurCellBorder()Set current cell border color
SetGivens()Set color for given values
SetHighlightNumber()Set highlight number
SetHighlightNumberColor()Set highlight color
SetLabels()Set label color
SetMessageHwnd()Set hwnd for message callbacks
SetPencilMarks()Set pencil marks color
SetShowPencilMarks()Set show state of pencil marks
SetShowSolution()Set show state of solution values
SetSolution()Set solution color
SetUndoEnable()Set enable state of undo facility
SetUserEntry()Set user entry color
SetWindowBackground()Set window background color
ShowColorPrefsDlg()Show color prefs dialog

Acknowledgments

Revision History

Version 1.2 - 2006 January 17

  • Initial public release

Usage

This software is released into the public domain. You are free to use it in any way you like, except that you may not sell this source code. If you modify it or extend it, please consider posting new code here for everyone to share. This software is provided "as is" with no expressed or implied warranty. I accept no liability for any damage or loss of business that this software may cause.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Hans Dietrich Software
United States United States
I attended St. Michael's College of the University of Toronto, with the intention of becoming a priest. A friend in the University's Computer Science Department got me interested in programming, and I have been hooked ever since.

Recently, I have moved to Los Angeles where I am doing consulting and development work.

For consulting and custom software development, please see www.hdsoft.org.






Comments and Discussions

 
QuestionxSudokuwnd Pin
654321ROD4-Feb-12 21:56
654321ROD4-Feb-12 21:56 
QuestionPorting to VS 2008 Pin
654321ROD27-Aug-11 19:41
654321ROD27-Aug-11 19:41 
QuestionDoes enyone have ported this to VS2005 or later? Pin
sisira23-Aug-10 15:14
sisira23-Aug-10 15:14 
AnswerRe: Does enyone have ported this to VS2005 or later? Pin
Hans Dietrich23-Aug-10 16:10
mentorHans Dietrich23-Aug-10 16:10 
GeneralRe: Does enyone have ported this to VS2005 or later? Pin
sisira23-Aug-10 20:36
sisira23-Aug-10 20:36 
GeneralRe: Does enyone have ported this to VS2005 or later? Pin
654321ROD4-Feb-12 21:41
654321ROD4-Feb-12 21:41 
GeneralRe: Does enyone have ported this to VS2005 or later? Pin
Whats in a name1-Apr-13 20:30
Whats in a name1-Apr-13 20:30 
GeneralUser should be alowed to remove individual hints Pin
Chip0226509-Dec-07 4:41
Chip0226509-Dec-07 4:41 
GeneralRe: User should be alowed to remove individual hints Pin
Graham Shanks16-Sep-08 10:56
Graham Shanks16-Sep-08 10:56 
GeneralAlso please include code for generating puzzle..! Pin
ana_v1234-Sep-07 19:14
ana_v1234-Sep-07 19:14 
GeneralExcellent! Pin
Mircea Puiu2-Jan-07 21:16
Mircea Puiu2-Jan-07 21:16 
GeneralVery quick, beats our solution. Pin
ADarkGerm20-Dec-06 23:42
ADarkGerm20-Dec-06 23:42 
Generalsmall error Pin
fool6-Sep-06 1:31
fool6-Sep-06 1:31 
GeneralRe: small error Pin
Graham Shanks31-Jan-09 6:33
Graham Shanks31-Jan-09 6:33 
Generalsmall error Pin
fool6-Sep-06 1:31
fool6-Sep-06 1:31 
GeneralFantastic Article and Solution - Question [modified] Pin
Randy Friend8-Jul-06 9:57
Randy Friend8-Jul-06 9:57 
GeneralSudoku input format Pin
Nigel Greenwood13-May-06 5:09
Nigel Greenwood13-May-06 5:09 
GeneralXSudoku lister plugin for Total Commander [modified] Pin
tbeu10-May-06 9:31
tbeu10-May-06 9:31 
GeneralLarge Fonts Pin
Jerry Jeremiah5-May-06 12:55
Jerry Jeremiah5-May-06 12:55 
GeneralGreat Solution Pin
69 Bay27-Feb-06 11:29
69 Bay27-Feb-06 11:29 
GeneralRe: Great Solution Pin
Graham Shanks19-Mar-06 10:59
Graham Shanks19-Mar-06 10:59 
GeneralRe: Great Solution Pin
69 Bay20-Mar-06 0:12
69 Bay20-Mar-06 0:12 
Hi
I havnt been able to work out how to post an attachment, so I have included XSudokuWnd.cpp within this reply - sorry if this isnt the right way to go about this!

The changes that I have made are :
1 Added m_nUnPencil[9][9] to XSudokuWnd.h
2 Defined new popuup menu items ID_POPUP_UNPENCIL_1 - 9 & ID_POPUP_PENCIL_1 - 9, and added in the relevant ON_COMMAND_RANGE to the Message Map
3 Added additional functions OnPopupUnPencil(), OnPopupPencil(), GetUnPenciledCandidates()
4 Altered ShowPopup() function to display Rub Out [x], or Pencil In [X] options.

Again I apologise if I havnt posted this properly - this is my first time!

Mike

<br />
// XSudokuWnd.cpp  Version 1.2<br />
//<br />
// Author:  Hans Dietrich<br />
//          hdietrich@gmail.com<br />
//<br />
// Description:<br />
//     XSudokuWnd implements CXSudokuWnd, a class that displays a Sudoku puzzle.<br />
//<br />
// History<br />
//     Version 1.2 - 2006 January 17<br />
//     - Initial public release<br />
//<br />
// Public APIs:<br />
//          NAME                             DESCRIPTION<br />
//     ---------------------      -----------------------------------------<br />
//     CaptureBitmap()            Copy window bitmap to clipboard<br />
//     Get3x3Gridline()           Get 3x3 gridline color<br />
//     GetCellBackground()        Get cell background color<br />
//     GetCurCellBorder()         Get current cell border color<br />
//     GetGivens()                Get color for given values<br />
//     GetGivenValues()           Get given values<br />
//     GetHighlightNumber()       Get highlight number<br />
//     GetHighlightNumberColor()  Get highlight color<br />
//     GetHints()                 Get hint values<br />
//     GetLabels()                Get label color<br />
//     GetPencilMarks()           Get pencil marks color<br />
//     GetShowPencilMarks()       Get show state of pencil marks<br />
//     GetShowSolution()          Get show state of solution<br />
//     GetSolution()              Get solution color<br />
//     GetSolutionValues()        Get solution values<br />
//     GetSudoku()                Display Sudoku entry dialog<br />
//     GetUndoEnable()            Get enable state of undo facility<br />
//     GetUserEntries()           Get user entry values<br />
//     GetUserEntry()             Get user entry color<br />
//     GetWindowBackground()      Get window background color<br />
//     GetWindowSize()            Get minimum size of XSudokuWnd window<br />
//     IsValid()                  Returns TRUE if Sudoku is valid (exactly 1 solution)<br />
//     LoadFromClipboard()        Load Sudoku from clipboard<br />
//     LoadFromFile()             Load Sudoku from file<br />
//     LoadFromString()           Load Sudoku from string<br />
//     PrintBitmap()              Print window bitmap<br />
//     Set3x3Gridline()           Set 3x3 gridline color<br />
//     SetCellBackground()        Set cell background color<br />
//     SetCurCellBorder()         Set current cell border color<br />
//     SetGivens()                Set color for given values<br />
//     SetHighlightNumber()       Set highlight number<br />
//     SetHighlightNumberColor()  Set highlight color<br />
//     SetLabels()                Set label color<br />
//     SetMessageHwnd()           Set hwnd for message callbacks<br />
//     SetPencilMarks()           Set pencil marks color<br />
//     SetShowPencilMarks()       Set show state of pencil marks<br />
//     SetShowSolution()          Set show state of solution values<br />
//     SetSolution()              Set solution color<br />
//     SetUndoEnable()            Set enable state of undo facility<br />
//     SetUserEntry()             Set user entry color<br />
//     SetWindowBackground()      Set window background color<br />
//     ShowColorPrefsDlg()        Show color prefs dialog<br />
//<br />
// License:<br />
//     This software is released into the public domain.  You are free to use<br />
//     it in any way you like, except that you may not sell this source code.<br />
//<br />
//     This software is provided "as is" with no expressed or implied warranty.<br />
//     I accept no liability for any damage or loss of business that this<br />
//     software may cause.<br />
//<br />
///////////////////////////////////////////////////////////////////////////////<br />
<br />
#include "stdafx.h"<br />
#include "resource.h"<br />
#include "XSudokuWnd.h"<br />
#include "FontSize.h"<br />
#include "dance.h"<br />
#include "memdc.h"<br />
#include "clipboard.h"<br />
#include "GDIUtil.h"<br />
#include "DllInstanceSwitcher.h"<br />
#include "SudokuEntryDlg.h"<br />
#include "ColorPrefDlg.h"<br />
#include "About.h"<br />
<br />
#ifdef _DEBUG<br />
#define new DEBUG_NEW<br />
#undef THIS_FILE<br />
static char THIS_FILE[] = __FILE__;<br />
#endif<br />
<br />
IMPLEMENT_DYNAMIC(CXSudokuWnd,CWnd)<br />
<br />
UINT WM_XSUDOKU = ::RegisterWindowMessage(_T("WM_XSUDOKU"));<br />
<br />
static int g_nGridLineOffset[9] = { 0, 0, 0, 1, 1, 1, 2, 2, 2 };<br />
static TCHAR * g_szDigits = _T("0123456789");<br />
<br />
#define SetBit(x,y)		(x|=(1<<(y)))<br />
#define ClearBit(x,y)	(x&=~(1<<(y)))<br />
#define TestBit(x,y)	(x&(1<<(y)))<br />
<br />
///////////////////////////////////////////////////////////////////////////////<br />
//<br />
// undo / redo<br />
//<br />
<br />
#define UNDO_ARRAY_GROW_BY_SIZE	20<br />
<br />
#pragma pack(push,1)<br />
struct UNDO_BLOCK<br />
{<br />
	UNDO_BLOCK()<br />
	{<br />
		TRACE(_T("in UNDO_BLOCK()\n"));<br />
		action_code  = 0;<br />
		action_value = 0;<br />
		row = col    = 0;<br />
	};<br />
<br />
	BYTE action_code;<br />
		#define ACTION_CODE_USER_ENTRY				1	// action_value = 0 to remove user entry<br />
		#define ACTION_CODE_USER_ENTRY_REMOVE_ALL	2<br />
		#define ACTION_CODE_HINT					3	// action_value: 0 = hide hint; else 1-9<br />
		#define ACTION_CODE_HINT_REMOVE_ALL			4<br />
<br />
	BYTE action_value;<br />
	BYTE row, col;				// current row & column<br />
	BYTE hints[9][9];<br />
	BYTE user_entries[9][9];<br />
};<br />
#pragma pack(pop)<br />
<br />
<br />
///////////////////////////////////////////////////////////////////////////////<br />
// Popup menu items<br />
#define ID_POPUP_HIGHLIGHT_0				50000<br />
#define ID_POPUP_HIGHLIGHT_1				50001<br />
#define ID_POPUP_HIGHLIGHT_2				50002<br />
#define ID_POPUP_HIGHLIGHT_3				50003<br />
#define ID_POPUP_HIGHLIGHT_4				50004<br />
#define ID_POPUP_HIGHLIGHT_5				50005<br />
#define ID_POPUP_HIGHLIGHT_6				50006<br />
#define ID_POPUP_HIGHLIGHT_7				50007<br />
#define ID_POPUP_HIGHLIGHT_8				50008<br />
#define ID_POPUP_HIGHLIGHT_9				50009<br />
<br />
#define ID_POPUP_SET_1						50011<br />
#define ID_POPUP_SET_2						50012<br />
#define ID_POPUP_SET_3						50013<br />
#define ID_POPUP_SET_4						50014<br />
#define ID_POPUP_SET_5						50015<br />
#define ID_POPUP_SET_6						50016<br />
#define ID_POPUP_SET_7						50017<br />
#define ID_POPUP_SET_8						50018<br />
#define ID_POPUP_SET_9						50019<br />
<br />
#define ID_POPUP_UNPENCIL_1					50021<br />
#define ID_POPUP_UNPENCIL_2					50022<br />
#define ID_POPUP_UNPENCIL_3					50023<br />
#define ID_POPUP_UNPENCIL_4					50024<br />
#define ID_POPUP_UNPENCIL_5					50025<br />
#define ID_POPUP_UNPENCIL_6					50026<br />
#define ID_POPUP_UNPENCIL_7					50027<br />
#define ID_POPUP_UNPENCIL_8					50028<br />
#define ID_POPUP_UNPENCIL_9					50029<br />
<br />
#define ID_POPUP_PENCIL_1					50051<br />
#define ID_POPUP_PENCIL_2					50052<br />
#define ID_POPUP_PENCIL_3					50053<br />
#define ID_POPUP_PENCIL_4					50054<br />
#define ID_POPUP_PENCIL_5					50055<br />
#define ID_POPUP_PENCIL_6					50056<br />
#define ID_POPUP_PENCIL_7					50057<br />
#define ID_POPUP_PENCIL_8					50058<br />
#define ID_POPUP_PENCIL_9					50059<br />
<br />
#define ID_POPUP_SHOW_HINT					50030<br />
#define ID_POPUP_REMOVE_ALL_HINTS			50031<br />
#define ID_POPUP_SHOW_SOLUTION				50032<br />
#define ID_POPUP_SHOW_PENCIL_MARKS          50033<br />
#define ID_POPUP_RESET						50034<br />
#define ID_POPUP_REMOVE_USER_ENTRY			50035<br />
#define ID_POPUP_REMOVE_ALL_USER_ENTRIES	50036<br />
#define ID_POPUP_CHECK_USER_ENTRIES			50037<br />
#define ID_POPUP_PRINT						50038<br />
#define ID_POPUP_COPY						50039<br />
#define ID_POPUP_UNDO						50040<br />
#define ID_POPUP_REDO						50041<br />
<br />
///////////////////////////////////////////////////////////////////////////////<br />
// CXSudokuWnd Message Map<br />
<br />
BEGIN_MESSAGE_MAP(CXSudokuWnd, CWnd)<br />
	//{{AFX_MSG_MAP(CXSudokuWnd)<br />
	ON_WM_ERASEBKGND()<br />
	ON_WM_PAINT()<br />
	ON_WM_LBUTTONDOWN()<br />
	ON_WM_RBUTTONUP()<br />
	ON_WM_LBUTTONDBLCLK()<br />
	ON_WM_GETDLGCODE()<br />
	ON_WM_RBUTTONDOWN()<br />
	ON_COMMAND(ID_RIGHT_CLICK, OnRightClick)<br />
	ON_COMMAND(ID_COLOR_PREFS, OnColorPrefs)<br />
	ON_COMMAND(ID_LOAD_SAMPLE, OnLoadSample)<br />
	ON_COMMAND(ID_SHOW_HINT, OnShowHint)<br />
	ON_COMMAND(ID_SHOW_SOLUTION, OnShowSolution)<br />
	ON_COMMAND(ID_SHOW_PENCIL_MARKS, OnShowPencilMarks)<br />
	ON_COMMAND(ID_EDIT_UNDO, OnEditUndo)<br />
	ON_COMMAND(ID_EDIT_REDO, OnEditRedo)<br />
	ON_COMMAND(ID_EDIT_COPY, OnCopyWindow)<br />
	ON_COMMAND(ID_FILE_PRINT, OnPrintWindow)<br />
	ON_COMMAND(ID_APP_ABOUT, OnAppAbout)<br />
	//}}AFX_MSG_MAP<br />
	ON_COMMAND(ID_POPUP_UNDO, OnEditUndo)<br />
	ON_COMMAND(ID_POPUP_REDO, OnEditRedo)<br />
	ON_COMMAND(ID_POPUP_SHOW_HINT, OnPopupShowHint)<br />
	ON_COMMAND(ID_POPUP_REMOVE_ALL_HINTS, OnRemoveAllHints)<br />
	ON_COMMAND(ID_POPUP_SHOW_SOLUTION, OnShowSolution)<br />
	ON_COMMAND(ID_POPUP_SHOW_PENCIL_MARKS, OnShowPencilMarks)<br />
	ON_COMMAND(ID_POPUP_RESET, OnReset)<br />
	ON_COMMAND(ID_POPUP_PRINT, OnPrintWindow)<br />
	ON_COMMAND(ID_POPUP_COPY, OnCopyWindow)<br />
	ON_COMMAND(ID_POPUP_REMOVE_USER_ENTRY, OnRemoveUserEntry)<br />
	ON_COMMAND(ID_POPUP_REMOVE_ALL_USER_ENTRIES, OnRemoveAllUserEntries)<br />
	ON_COMMAND(ID_POPUP_CHECK_USER_ENTRIES, OnCheckUserEntries)<br />
	ON_COMMAND_RANGE(ID_POPUP_SET_1, ID_POPUP_SET_9, OnPopupSet)<br />
	ON_COMMAND_RANGE(ID_POPUP_UNPENCIL_1, ID_POPUP_UNPENCIL_9, OnPopupUnPencil)<br />
	ON_COMMAND_RANGE(ID_POPUP_PENCIL_1, ID_POPUP_PENCIL_9, OnPopupPencil)<br />
	ON_COMMAND_RANGE(ID_POPUP_HIGHLIGHT_1, ID_POPUP_HIGHLIGHT_9, OnPopupHighlight)<br />
	ON_COMMAND_RANGE(ID_HIGHLIGHT_0, ID_HIGHLIGHT_9, OnHighlight)<br />
	END_MESSAGE_MAP()<br />
<br />
///////////////////////////////////////////////////////////////////////////////<br />
//<br />
// CXSudokuWnd()<br />
//<br />
// Purpose:     Construct CXSudokuWnd object<br />
//<br />
// Parameters:  None<br />
//<br />
// Returns:     None<br />
//<br />
// Notes:       Construction is a two-step process.  First, construct the<br />
//              CXSudokuWnd object.  Second, call CXSudokuWnd::Create to create<br />
//              the CXSudokuWnd window.<br />
//<br />
CXSudokuWnd::CXSudokuWnd()<br />
{<br />
	m_bIsValid            = FALSE;<br />
	m_hWndMessage         = NULL;<br />
	m_hAccel              = NULL;<br />
	m_bPencilMarks        = FALSE;<br />
	m_bHaveGivens         = FALSE;<br />
	m_bShowSolution       = FALSE;<br />
	m_rgbWindowBackground = RGB(220,20,60);<br />
	m_rgbCellBackground   = RGB(192,192,192);<br />
	m_rgbLabels           = RGB(255,255,255);<br />
	m_rgbGivens           = RGB(220,20,60);<br />
	m_rgbSolution         = RGB(0, 0, 255);<br />
	m_rgbUserEntry        = RGB(0, 0, 0);<br />
	m_rgbPencilMarks      = RGB(0, 0, 128);<br />
	m_rgbHighlightNumber  = RGB(135, 206, 250);<br />
	m_rgbCurCellBorder    = RGB(255, 255, 0);<br />
	m_rgb3x3Gridline      = RGB(0,0,0);<br />
	m_strLabelFont        = _T("Verdana");<br />
	m_nXOffset            = 20;<br />
	m_nYOffset            = 20;<br />
	m_nCellWidth          = 45;<br />
	m_nCellHeight         = 44;<br />
	m_nHighlightNumber    = 0;<br />
	m_nCurRow = m_nCurCol = -1;<br />
	m_pointPopup          = CPoint(-1,-1);<br />
	m_strFile             = _T("");<br />
<br />
	// initialize undo facility<br />
	m_bEnableUndo         = TRUE;<br />
	m_nUndoIndex          = 0;<br />
	m_nUndoLastEntry      = 0;<br />
	m_nUndoSize           = UNDO_ARRAY_GROW_BY_SIZE;<br />
	m_Undo.SetSize(m_nUndoSize);<br />
	for (int k = 0; k < m_nUndoSize; k++)<br />
	{<br />
		UNDO_BLOCK * pUB = new UNDO_BLOCK;<br />
		ASSERT(pUB);<br />
		m_Undo[k] = pUB;<br />
	}<br />
<br />
	// initialize all arrays<br />
	for (int i = 0; i < 9; i++)<br />
	{<br />
		for (int j = 0; j < 9; j++)<br />
		{<br />
			m_nGivens[i][j] = 0;<br />
			m_nSolution[i][j] = 0;<br />
			m_nHints[i][j] = 0;<br />
			m_nUserEntries[i][j] = 0;<br />
		}<br />
	}<br />
}<br />
<br />
///////////////////////////////////////////////////////////////////////////////<br />
//<br />
// ~CXSudokuWnd()<br />
//<br />
// Purpose:     Destroy CXSudokuWnd object.<br />
//<br />
// Parameters:  None<br />
//<br />
// Returns:     None<br />
//<br />
CXSudokuWnd::~CXSudokuWnd()<br />
{<br />
	if (m_fontLabels.GetSafeHandle())<br />
		m_fontLabels.DeleteObject();<br />
<br />
	if (m_fontValues.GetSafeHandle())<br />
		m_fontValues.DeleteObject();<br />
<br />
	if (m_fontPencilMarks.GetSafeHandle())<br />
		m_fontPencilMarks.DeleteObject();<br />
<br />
	if (m_fontPrint.GetSafeHandle())<br />
		m_fontPrint.DeleteObject();<br />
<br />
	for (int j = 0; j < m_nUndoSize; j++)<br />
	{<br />
		UNDO_BLOCK * pUB = (UNDO_BLOCK *) m_Undo[j];<br />
		ASSERT(pUB);<br />
		if (pUB)<br />
		{<br />
<br />
#ifdef _DEBUG<br />
			CString str = ActionCodeToString(pUB->action_code);<br />
			if (!str.IsEmpty())<br />
			{<br />
				TRACE(_T("deleting undo action '%s'\n"), str);<br />
			}<br />
#endif<br />
			delete pUB;<br />
		}<br />
	}<br />
}<br />
<br />
///////////////////////////////////////////////////////////////////////////////<br />
//<br />
// OnGetDlgCode()<br />
//<br />
// Purpose:     The WM_GETDLGCODE message is sent to the window procedure<br />
//              associated with a control. By default, the system handles all<br />
//              keyboard input to the control; the system interprets certain<br />
//              types of keyboard input as dialog box navigation keys. To<br />
//              override this default behavior, the control can respond to the<br />
//              WM_GETDLGCODE message to indicate the types of input it wants<br />
//              to process itself.<br />
//<br />
// Parameters:  None<br />
//<br />
// Returns:     UINT - always DLGC_WANTALLKEYS<br />
//<br />
UINT CXSudokuWnd::OnGetDlgCode()<br />
{<br />
	return DLGC_WANTALLKEYS;<br />
}<br />
<br />
///////////////////////////////////////////////////////////////////////////////<br />
//<br />
// OnEraseBkgnd()<br />
//<br />
// Purpose:     The framework calls this member function when the CWnd object<br />
//              background needs erasing.<br />
//<br />
// Parameters:  pDC - pointer to device context object<br />
//<br />
// Returns:     BOOL - TRUE = background was erased;  otherwise FALSE<br />
//<br />
BOOL CXSudokuWnd::OnEraseBkgnd(CDC* pDC)<br />
{<br />
	EraseBkgnd(pDC);<br />
	return CWnd::OnEraseBkgnd(pDC);<br />
}<br />
<br />
///////////////////////////////////////////////////////////////////////////////<br />
//<br />
// EraseBkgnd()<br />
//<br />
// Purpose:     Erase background by filling client rect with control's window<br />
//              background color.<br />
//<br />
// Parameters:  pDC - pointer to device context object<br />
//<br />
// Returns:     None<br />
//<br />
void CXSudokuWnd::EraseBkgnd(CDC* pDC)<br />
{<br />
	CRect rect;<br />
	GetClientRect(&rect);<br />
	pDC->FillSolidRect(rect, m_rgbWindowBackground);<br />
}<br />
<br />
///////////////////////////////////////////////////////////////////////////////<br />
//<br />
// OnPaint()<br />
//<br />
// Purpose:     The framework calls this member function when Windows or an<br />
//              application makes a request to repaint a portion of the<br />
//              control's window.<br />
//<br />
// Parameters:  None<br />
//<br />
// Returns:     None<br />
//<br />
// Notes:       OnPaint() always first fills the entire client background with<br />
//              the control's background color, so there is never a need to call<br />
//              Invalidate() with TRUE.<br />
//<br />
void CXSudokuWnd::OnPaint()<br />
{<br />
	CPaintDC dc(this); // device context for painting<br />
<br />
	CMemDC memDC(&dc);<br />
<br />
	EraseBkgnd(&memDC);<br />
<br />
	for (int i = 0; i < 9; i++)<br />
	{<br />
		for (int j = 0; j < 9; j++)<br />
		{<br />
			PaintCell(&memDC, i, j);<br />
		}<br />
	}<br />
<br />
	Paint3x3Gridlines(&memDC);<br />
<br />
	PaintLabels(&memDC);<br />
<br />
	if (m_bHaveGivens)<br />
	{<br />
		PaintValues(&memDC);<br />
<br />
		if (m_bPencilMarks)<br />
			PaintPencilMarks(&memDC);<br />
	}<br />
<br />
	PaintCurCell(&memDC);<br />
<br />
	DefWindowProc(WM_PAINT, (WPARAM)memDC->m_hDC, (LPARAM)0);<br />
<br />
	// Do not call CWnd::OnPaint() for painting messages<br />
}<br />
<br />
///////////////////////////////////////////////////////////////////////////////<br />
//<br />
// GetWindowSize()<br />
//<br />
// Purpose:     Get size needed for XSudokuWnd client rect.<br />
//<br />
// Parameters:  None<br />
//<br />
// Returns:     CSize - size in pixels of client rect, including margins and<br />
//                      grid lines.<br />
//<br />
CSize CXSudokuWnd::GetWindowSize()<br />
{<br />
	CSize size(0, 0);<br />
<br />
	size.cx = 2 * m_nXOffset + 9 * (m_nCellWidth+1) + 2;<br />
	size.cy = 2 * m_nYOffset + 9 * (m_nCellHeight+1) + 2;<br />
<br />
	return size;<br />
}<br />
<br />
///////////////////////////////////////////////////////////////////////////////<br />
//<br />
// GetCellBackgroundColor()<br />
//<br />
// Purpose:     Get current background color of a cell<br />
//<br />
// Parameters:  row - row number (0 - 8)<br />
//              col - column number (0 - 8)<br />
//<br />
// Returns:     COLORREF - current background color for cell;  this will<br />
//                         depend on whether the cell is highlighted.<br />
//<br />
COLORREF CXSudokuWnd::GetCellBackgroundColor(int row, int col)<br />
{<br />
	COLORREF rgb = m_rgbCellBackground;<br />
<br />
	if (m_bHaveGivens && (m_nHighlightNumber != 0))<br />
	{<br />
		WORD wCandidates = GetCandidates(row, col) & GetUnPenciledCandidates(row, col);//get the possible candidates, less the rubbed out values<br />
<br />
		int nGiven     = m_nGivens[row][col];<br />
		int nSolution  = m_nSolution[row][col];<br />
		int nHint      = m_nHints[row][col];<br />
		int nUserEntry = m_nUserEntries[row][col];<br />
<br />
		if (nGiven)<br />
		{<br />
			if (nGiven == m_nHighlightNumber)<br />
				rgb = m_rgbHighlightNumber;<br />
		}<br />
		else if (nHint)<br />
		{<br />
			if (nHint == m_nHighlightNumber)<br />
				rgb = m_rgbHighlightNumber;<br />
		}<br />
		else if (nUserEntry)<br />
		{<br />
			if (nUserEntry == m_nHighlightNumber)<br />
				rgb = m_rgbHighlightNumber;<br />
		}<br />
		else if (nSolution && m_bShowSolution)<br />
		{<br />
			if (nSolution == m_nHighlightNumber)<br />
				rgb = m_rgbHighlightNumber;<br />
		}<br />
		else if (TestBit(wCandidates, m_nHighlightNumber))<br />
		{<br />
			rgb = m_rgbHighlightNumber;<br />
		}<br />
	}<br />
<br />
	return rgb;<br />
}<br />
<br />
///////////////////////////////////////////////////////////////////////////////<br />
//<br />
// PaintCell()<br />
//<br />
// Purpose:     Fill cell with correct background color<br />
//<br />
// Parameters:  pDC - pointer to device context object<br />
//              row - row number (0 - 8)<br />
//              col - column number (0 - 8)<br />
//<br />
// Returns:     None<br />
//<br />
void CXSudokuWnd::PaintCell(CDC * pDC, int row, int col)<br />
{<br />
	CRect rectCell = GetCellRect(row, col);<br />
	pDC->FillSolidRect(&rectCell, GetCellBackgroundColor(row, col));<br />
}<br />
<br />
///////////////////////////////////////////////////////////////////////////////<br />
//<br />
// Paint3x3Gridlines()<br />
//<br />
// Purpose:     Draw 3x3 grid lines<br />
//<br />
// Parameters:  pDC - pointer to device context object<br />
//<br />
// Returns:     None<br />
//<br />
void CXSudokuWnd::Paint3x3Gridlines(CDC * pDC)<br />
{<br />
	CPen pen(PS_SOLID, 1, m_rgb3x3Gridline);<br />
<br />
	CPen *pOldPen = pDC->SelectObject(&pen);<br />
<br />
	CRect rectWnd, rectLine;<br />
	GetClientRect(&rectWnd);<br />
	rectLine.top    = rectWnd.top + m_nYOffset;<br />
	rectLine.bottom = rectLine.top + 9 * m_nCellHeight + 8 + 2;<br />
<br />
	// vertical lines<br />
	rectLine.left   = rectWnd.left + m_nXOffset + 3 * m_nCellWidth + 2;<br />
	pDC->MoveTo(rectLine.left, rectLine.top);<br />
	pDC->LineTo(rectLine.left, rectLine.bottom);<br />
	pDC->MoveTo(rectLine.left+1, rectLine.top);<br />
	pDC->LineTo(rectLine.left+1, rectLine.bottom);<br />
<br />
	rectLine.left += 2 + 3 * m_nCellWidth + 2;<br />
	pDC->MoveTo(rectLine.left, rectLine.top);<br />
	pDC->LineTo(rectLine.left, rectLine.bottom);<br />
	pDC->MoveTo(rectLine.left+1, rectLine.top);<br />
	pDC->LineTo(rectLine.left+1, rectLine.bottom);<br />
<br />
	// horizontal lines<br />
	rectLine.left   = rectWnd.left + m_nXOffset;<br />
	rectLine.right  = rectLine.left + 9 * m_nCellWidth + 8 + 2;<br />
	rectLine.top    += 3 * m_nCellHeight + 2;<br />
	pDC->MoveTo(rectLine.left, rectLine.top);<br />
	pDC->LineTo(rectLine.right, rectLine.top);<br />
	pDC->MoveTo(rectLine.left, rectLine.top+1);<br />
	pDC->LineTo(rectLine.right, rectLine.top+1);<br />
<br />
	rectLine.top    += 3 * m_nCellHeight + 2 + 2;<br />
	pDC->MoveTo(rectLine.left, rectLine.top);<br />
	pDC->LineTo(rectLine.right, rectLine.top);<br />
	pDC->MoveTo(rectLine.left, rectLine.top+1);<br />
	pDC->LineTo(rectLine.right, rectLine.top+1);<br />
<br />
	pDC->SelectObject(pOldPen);<br />
}<br />
<br />
///////////////////////////////////////////////////////////////////////////////<br />
//<br />
// PaintLabels()<br />
//<br />
// Purpose:     Draw row and column labels<br />
//<br />
// Parameters:  pDC - pointer to device context object<br />
//<br />
// Returns:     None<br />
//<br />
void CXSudokuWnd::PaintLabels(CDC * pDC)<br />
{<br />
	CFont *pOldFont = pDC->SelectObject(&m_fontLabels);<br />
<br />
	pDC->SetBkColor(m_rgbWindowBackground);<br />
	pDC->SetTextColor(m_rgbLabels);<br />
<br />
	CRect rectWnd, rectLabel;<br />
	GetClientRect(&rectWnd);<br />
	rectLabel.top    = rectWnd.top + m_nYOffset - m_nLabelHeight - 2;<br />
	rectLabel.bottom = rectLabel.top + m_nLabelHeight + 2;<br />
	rectLabel.left   = rectWnd.left + m_nXOffset + m_nCellWidth/2 - 3;<br />
	rectLabel.right  = rectLabel.left + m_nLabelHeight;<br />
<br />
	static TCHAR * szXLabel = _T("ABCDEFGHI");<br />
	int i = 0;<br />
	for (i = 0; i < 9; i++)<br />
	{<br />
		rectLabel.left += g_nGridLineOffset[i];<br />
		rectLabel.right = rectLabel.left + m_nLabelHeight;<br />
<br />
		pDC->ExtTextOut(rectLabel.left, rectLabel.top, <br />
						ETO_CLIPPED|ETO_OPAQUE,<br />
						&rectLabel,<br />
						&szXLabel[i], 1,<br />
						NULL);<br />
<br />
		rectLabel.left   += m_nCellWidth;<br />
	}<br />
<br />
	rectLabel.top    = rectWnd.top + m_nYOffset + m_nCellHeight/2 - 5;<br />
	rectLabel.bottom = rectLabel.top + m_nLabelHeight + 2;<br />
	rectLabel.left   = rectWnd.left + m_nXOffset - m_nLabelHeight - 2;<br />
	rectLabel.right  = rectLabel.left + m_nLabelHeight;<br />
<br />
	for (i = 0; i < 9; i++)<br />
	{<br />
		rectLabel.top   += g_nGridLineOffset[i];<br />
		rectLabel.bottom = rectLabel.top + m_nLabelHeight + 1;<br />
<br />
		pDC->ExtTextOut(rectLabel.left, rectLabel.top, <br />
						ETO_CLIPPED|ETO_OPAQUE,<br />
						&rectLabel,<br />
						&g_szDigits[i+1], 1,<br />
						NULL);<br />
<br />
		rectLabel.top    += m_nCellHeight;<br />
	}<br />
<br />
	pDC->SelectObject(pOldFont);<br />
}<br />
<br />
///////////////////////////////////////////////////////////////////////////////<br />
//<br />
// PaintValues()<br />
//<br />
// Purpose:     Draw values in cells<br />
//<br />
// Parameters:  pDC - pointer to device context object<br />
//<br />
// Returns:     None<br />
//<br />
void CXSudokuWnd::PaintValues(CDC * pDC)<br />
{<br />
	CFont *pOldFont = pDC->SelectObject(&m_fontValues);<br />
<br />
	CSize sizeValue = pDC->GetTextExtent(_T("0"));<br />
	int x_offset = m_nCellWidth/2 - sizeValue.cx/2 + 1;<br />
	int y_offset = m_nCellHeight/2 - sizeValue.cy/2 + 1;<br />
<br />
	for (int i = 0; i < 9; i++)<br />
	{<br />
		for (int j = 0; j < 9; j++)<br />
		{<br />
			CRect rectCell = GetCellRect(i, j);<br />
<br />
			CRect rectValue;<br />
			rectValue.top    = rectCell.top + y_offset;<br />
			rectValue.bottom = rectValue.top + m_nValueHeight;<br />
			rectValue.left   = rectCell.left + x_offset;<br />
			rectValue.right  = rectValue.left + sizeValue.cx;<br />
<br />
			pDC->SetBkColor(GetCellBackgroundColor(i, j));<br />
<br />
			int nGiven     = m_nGivens[i][j];<br />
			int nSolution  = m_nSolution[i][j];<br />
			int nHint      = m_nHints[i][j];<br />
			int nUserEntry = m_nUserEntries[i][j];<br />
<br />
			COLORREF rgbValue;<br />
			int nValue;<br />
<br />
			if (nGiven)<br />
			{<br />
				rgbValue = m_rgbGivens;<br />
				nValue = nGiven;<br />
			}<br />
			else if (nHint)<br />
			{<br />
				rgbValue = m_rgbSolution;<br />
				nValue = nHint;<br />
			}<br />
			else if (nUserEntry)<br />
			{<br />
				rgbValue = m_rgbUserEntry;<br />
				nValue = nUserEntry;<br />
			}<br />
			else if (nSolution && m_bShowSolution)<br />
			{<br />
				rgbValue = m_rgbSolution;<br />
				nValue = nSolution;<br />
			}<br />
			else<br />
			{<br />
				// no value in this cell<br />
				continue;<br />
			}<br />
<br />
			pDC->SetTextColor(rgbValue);<br />
<br />
			pDC->ExtTextOut(rectValue.left, rectValue.top, <br />
							ETO_CLIPPED|ETO_OPAQUE,<br />
							&rectValue,<br />
							&g_szDigits[nValue], 1,<br />
							NULL);<br />
		}<br />
	}<br />
<br />
	pDC->SelectObject(pOldFont);<br />
}<br />
<br />
///////////////////////////////////////////////////////////////////////////////<br />
//<br />
// GetRowCandidates()<br />
//<br />
// Purpose:     Get candidates for a row<br />
//<br />
// Parameters:  row - row number (0 - 8)<br />
//<br />
// Returns:     WORD - lower 8 bits represent the candidate values that are<br />
//                     possible for a cell (bit 0 is never set).  A value is<br />
//                     possible if no other cell in the row contains the value<br />
//                     (a given, a user entered value, or a hint).<br />
//<br />
WORD CXSudokuWnd::GetRowCandidates(int row)<br />
{<br />
	WORD wBitFlags = 0x3FE;<br />
<br />
	for (int col = 0; col < 9; col++)<br />
	{<br />
		int nGiven     = m_nGivens[row][col];<br />
		int nHint      = m_nHints[row][col];<br />
		int nUserEntry = m_nUserEntries[row][col];<br />
		<br />
		if (nGiven)<br />
		{<br />
			ClearBit(wBitFlags, nGiven);<br />
		}<br />
		else if (nHint)<br />
		{<br />
			ClearBit(wBitFlags, nHint);<br />
		}<br />
		else if (nUserEntry)<br />
		{<br />
			ClearBit(wBitFlags, nUserEntry);<br />
		}<br />
	}<br />
<br />
	return wBitFlags;<br />
}<br />
<br />
///////////////////////////////////////////////////////////////////////////////<br />
//<br />
// GetColCandidates()<br />
//<br />
// Purpose:     Get candidates for a column<br />
//<br />
// Parameters:  col - column number (0 - 8)<br />
//<br />
// Returns:     WORD - lower 8 bits represent the candidate values that are<br />
//                     possible for a cell (bit 0 is never set).  A value is<br />
//                     possible if no other cell in the column contains the<br />
//                     value (a given, a user entered value, or a hint).<br />
//<br />
WORD CXSudokuWnd::GetColCandidates(int col)<br />
{<br />
	WORD wBitFlags = 0x3FE;<br />
<br />
	for (int row = 0; row < 9; row++)<br />
	{<br />
		int nGiven     = m_nGivens[row][col];<br />
		int nHint      = m_nHints[row][col];<br />
		int nUserEntry = m_nUserEntries[row][col];<br />
<br />
		if (nGiven)<br />
		{<br />
			ClearBit(wBitFlags, nGiven);<br />
		}<br />
		else if (nHint)<br />
		{<br />
			ClearBit(wBitFlags, nHint);<br />
		}<br />
		else if (nUserEntry)<br />
		{<br />
			ClearBit(wBitFlags, nUserEntry);<br />
		}<br />
	}<br />
<br />
	return wBitFlags;<br />
}<br />
<br />
///////////////////////////////////////////////////////////////////////////////<br />
//<br />
// GetRegionCandidates()<br />
//<br />
// Purpose:     Get candidates for a 3x3 region<br />
//<br />
// Parameters:  row - row number (0 - 8)<br />
//              col - column number (0 - 8)<br />
//<br />
// Returns:     WORD - lower 8 bits represent the candidate values that are<br />
//                     possible for a cell (bit 0 is never set).  A value is<br />
//                     possible if no other cell in the region contains the<br />
//                     value (a given, a user entered value, or a hint).<br />
//<br />
WORD CXSudokuWnd::GetRegionCandidates(int row, int col)<br />
{<br />
	WORD wBitFlags = 0x3FE;<br />
<br />
	int nStartRow = (row / 3) * 3;<br />
	int nStartCol = (col / 3) * 3;<br />
<br />
	for (int i = nStartRow; i < (nStartRow + 3); i++)<br />
	{<br />
		for (int j = nStartCol; j < (nStartCol + 3); j++)<br />
		{<br />
			int nGiven     = m_nGivens[i][j];<br />
			int nHint      = m_nHints[i][j];<br />
			int nUserEntry = m_nUserEntries[i][j];<br />
<br />
			if (nGiven)<br />
			{<br />
				ClearBit(wBitFlags, nGiven);<br />
			}<br />
			else if (nHint)<br />
			{<br />
				ClearBit(wBitFlags, nHint);<br />
			}<br />
			else if (nUserEntry)<br />
			{<br />
				ClearBit(wBitFlags, nUserEntry);<br />
			}<br />
		}<br />
	}<br />
<br />
	return wBitFlags;<br />
}<br />
<br />
///////////////////////////////////////////////////////////////////////////////<br />
//<br />
// GetUnPenciledCandidates()<br />
//<br />
// Purpose:     Get candidates that have been previously rubbed out<br />
//<br />
// Parameters:  row - row number (0 - 8)<br />
//              col - column number (0 - 8)<br />
// <br />
// Returns:     WORD - Inverted bits 1 - 10 (bit 0 is never set)<br />
//				representing the candidates that have previously<br />
//				been rubbed out.<br />
<br />
WORD CXSudokuWnd::GetUnPenciledCandidates(int row, int col)<br />
{<br />
	return (~m_nUnPencil[row][col])&0x3FE;	<br />
}<br />
<br />
///////////////////////////////////////////////////////////////////////////////<br />
//<br />
// GetCandidates()<br />
//<br />
// Purpose:     Get candidates for a row, column, and 3x3 region<br />
//<br />
// Parameters:  row - row number (0 - 8)<br />
//              col - column number (0 - 8)<br />
//<br />
// Returns:     WORD - lower 8 bits represent the candidate values that are<br />
//                     possible for a cell (bit 0 is never set).  A value is<br />
//                     possible if no other cell in the row, column, or region<br />
//                     contains the value (a given, a user entered value, or<br />
//                     a hint).<br />
//<br />
WORD CXSudokuWnd::GetCandidates(int row, int col)<br />
{<br />
	WORD wRowCandidates = GetRowCandidates(row);<br />
	WORD wColCandidates = GetColCandidates(col);<br />
	WORD wRegionCandidates = GetRegionCandidates(row, col);<br />
<br />
	WORD wBitFlags = (WORD) (wRowCandidates & wColCandidates & wRegionCandidates);// & wUnPenciledCandidates);<br />
	<br />
	TRACE(_T("Candidates for %d,%d:  0x%X\n"), row, col, wBitFlags);<br />
<br />
	return wBitFlags;<br />
}<br />
<br />
///////////////////////////////////////////////////////////////////////////////<br />
//<br />
// GetCellRect()<br />
//<br />
// Purpose:     Get rect for a cell<br />
//<br />
// Parameters:  row - row number (0 - 8)<br />
//              col - column number (0 - 8)<br />
//<br />
// Returns:     CRect - rect of the cell<br />
//<br />
CRect CXSudokuWnd::GetCellRect(int row, int col)<br />
{<br />
	CRect rectCell, rectWnd;<br />
	GetClientRect(&rectWnd);<br />
<br />
	rectCell.left   = rectWnd.left + m_nXOffset + col * (m_nCellWidth + 1);<br />
	rectCell.left  += g_nGridLineOffset[col];<br />
	rectCell.right  = rectCell.left + m_nCellWidth;<br />
<br />
	rectCell.top    = rectWnd.top + m_nYOffset + row * (m_nCellHeight + 1);<br />
	rectCell.top   += g_nGridLineOffset[row];<br />
	rectCell.bottom = rectCell.top + m_nCellHeight;// - 1;<br />
<br />
	return rectCell;<br />
}<br />
<br />
///////////////////////////////////////////////////////////////////////////////<br />
//<br />
// PaintPencilMarks()<br />
//<br />
// Purpose:     Draw pencil marks (aka candidate values) for a cell<br />
//<br />
// Parameters:  pDC - pointer to device context object<br />
//<br />
// Returns:     None<br />
//<br />
void CXSudokuWnd::PaintPencilMarks(CDC * pDC)<br />
{<br />
	CFont *pOldFont = pDC->SelectObject(&m_fontPencilMarks);<br />
<br />
	pDC->SetTextColor(m_rgbPencilMarks);<br />
<br />
	int nXOffset = ((m_nCellWidth - (3 * m_nLabelHeight)) / 2) + 2;<br />
<br />
	for (int i = 0; i < 9; i++)<br />
	{<br />
		for (int j = 0; j < 9; j++)<br />
		{<br />
			pDC->SetBkColor(GetCellBackgroundColor(i, j));<br />
<br />
			int nGiven     = m_nGivens[i][j];<br />
			int nSolution  = m_nSolution[i][j];<br />
			int nHint      = m_nHints[i][j];<br />
			int nUserEntry = m_nUserEntries[i][j];<br />
<br />
			if (nGiven || nUserEntry || nHint || (nSolution && m_bShowSolution))<br />
				continue;<br />
<br />
			// find all possible candidates for this cell<br />
			WORD wCandidates = GetCandidates(i, j) & GetUnPenciledCandidates(i, j);<br />
<br />
			CRect rectCell = GetCellRect(i, j);<br />
<br />
			CRect rectPencilMarks;<br />
			rectPencilMarks.left  = rectCell.left + nXOffset;<br />
			rectPencilMarks.right = rectPencilMarks.left + m_nLabelHeight + 2;<br />
<br />
			for (int nCandidate = 1; nCandidate <= 9; nCandidate++)<br />
			{<br />
				rectPencilMarks.top    = rectCell.top + 3 + ((nCandidate-1)/3) * (m_nLabelHeight+1);<br />
				rectPencilMarks.bottom = rectPencilMarks.top + m_nLabelHeight + 2;<br />
<br />
				if (TestBit(wCandidates, nCandidate))<br />
				{<br />
					pDC->ExtTextOut(rectPencilMarks.left, rectPencilMarks.top, <br />
									ETO_CLIPPED|ETO_OPAQUE,<br />
									&rectPencilMarks,<br />
									&g_szDigits[nCandidate], 1,<br />
									NULL);<br />
				}<br />
				if (nCandidate == 3 || nCandidate == 6)<br />
					rectPencilMarks.left  = rectCell.left + nXOffset;<br />
				else<br />
					rectPencilMarks.left += m_nLabelHeight;<br />
<br />
				rectPencilMarks.right  = rectPencilMarks.left + m_nLabelHeight + 2;<br />
			}<br />
		}<br />
	}<br />
<br />
	pDC->SelectObject(pOldFont);<br />
}<br />
<br />
///////////////////////////////////////////////////////////////////////////////<br />
//<br />
// PaintCurCell()<br />
//<br />
// Purpose:     Paint cell at m_nCurRow, m_nCurCol<br />
//<br />
// Parameters:  pDC - pointer to device context object<br />
//<br />
// Returns:     None<br />
//<br />
void CXSudokuWnd::PaintCurCell(CDC * pDC)<br />
{<br />
	if ((m_nCurRow != -1) && (m_nCurCol != -1))<br />
	{<br />
		CRect rectCell = GetCellRect(m_nCurRow, m_nCurCol);<br />
<br />
		CPen pen(PS_SOLID, 1, m_rgbCurCellBorder);<br />
<br />
		CPen *pOldPen = pDC->SelectObject(&pen);<br />
<br />
		pDC->MoveTo(rectCell.left, rectCell.top);<br />
		pDC->LineTo(rectCell.right, rectCell.top);<br />
		pDC->MoveTo(rectCell.left, rectCell.top+1);<br />
		pDC->LineTo(rectCell.right, rectCell.top+1);<br />
		pDC->MoveTo(rectCell.left, rectCell.top+2);<br />
		pDC->LineTo(rectCell.right, rectCell.top+2);<br />
<br />
		pDC->MoveTo(rectCell.left, rectCell.bottom-3);<br />
		pDC->LineTo(rectCell.right, rectCell.bottom-3);<br />
		pDC->MoveTo(rectCell.left, rectCell.bottom-2);<br />
		pDC->LineTo(rectCell.right, rectCell.bottom-2);<br />
		pDC->MoveTo(rectCell.left, rectCell.bottom-1);<br />
		pDC->LineTo(rectCell.right, rectCell.bottom-1);<br />
<br />
		pDC->MoveTo(rectCell.left, rectCell.top);<br />
		pDC->LineTo(rectCell.left, rectCell.bottom);<br />
		pDC->MoveTo(rectCell.left+1, rectCell.top);<br />
		pDC->LineTo(rectCell.left+1, rectCell.bottom);<br />
		pDC->MoveTo(rectCell.left+2, rectCell.top);<br />
		pDC->LineTo(rectCell.left+2, rectCell.bottom);<br />
<br />
		pDC->MoveTo(rectCell.right-1, rectCell.top);<br />
		pDC->LineTo(rectCell.right-1, rectCell.bottom);<br />
		pDC->MoveTo(rectCell.right-2, rectCell.top);<br />
		pDC->LineTo(rectCell.right-2, rectCell.bottom);<br />
		pDC->MoveTo(rectCell.right-3, rectCell.top);<br />
		pDC->LineTo(rectCell.right-3, rectCell.bottom);<br />
<br />
		pDC->SelectObject(pOldPen);<br />
	}<br />
}<br />
<br />
///////////////////////////////////////////////////////////////////////////////<br />
//<br />
// CellFromPoint()<br />
//<br />
// Purpose:     Get cell row, column for a point<br />
//<br />
// Parameters:  point - point that lies within a cell<br />
//              row   - returned row number (0 - 8)<br />
//              col   - returned column number (0 - 8)<br />
//<br />
// Returns:     BOOL - TRUE = point lies within a cell<br />
//<br />
BOOL CXSudokuWnd::CellFromPoint(CPoint point, int& nRow, int& nCol)<br />
{<br />
	nRow = nCol = -1;<br />
<br />
	BOOL bCellFound = FALSE;<br />
<br />
	CRect rectCell;<br />
<br />
	for (int i = 0; i < 9; i++)<br />
	{<br />
		for (int j = 0; j < 9; j++)<br />
		{<br />
			rectCell = GetCellRect(i, j);<br />
			if (rectCell.PtInRect(point))<br />
			{<br />
				nRow = i;<br />
				nCol = j;<br />
				bCellFound = TRUE;<br />
				break;<br />
			}<br />
		}<br />
		if (bCellFound)<br />
			break;<br />
	}<br />
<br />
	return bCellFound;<br />
}<br />
<br />
///////////////////////////////////////////////////////////////////////////////<br />
//<br />
// OnLButtonDown()<br />
//<br />
// Purpose:     Handle WM_LBUTTONDOWN message, to enable drawing the<br />
//              selection border<br />
//<br />
// Parameters:  nFlags - indicates whether various virtual keys are down<br />
//              point  - specifies the x- and y-coordinate of the cursor<br />
//<br />
// Returns:     None<br />
//<br />
void CXSudokuWnd::OnLButtonDown(UINT nFlags, CPoint point)<br />
{<br />
	TRACE(_T("in CXSudokuWnd::OnLButtonDown\n"));<br />
<br />
	// change current cell<br />
<br />
	int nOldRow = m_nCurRow;<br />
	int nOldCol = m_nCurCol;<br />
<br />
	if (CellFromPoint(point, m_nCurRow, m_nCurCol))<br />
	{<br />
		if ((nOldRow != -1) && (nOldCol != -1))<br />
		{<br />
			CRect rectOld = GetCellRect(nOldRow, nOldCol);<br />
			InvalidateRect(&rectOld, FALSE);<br />
		}<br />
		CRect rectNew = GetCellRect(m_nCurRow, m_nCurCol);<br />
		InvalidateRect(&rectNew, FALSE);<br />
		SetFocus();<br />
	}<br />
<br />
	CWnd::OnLButtonDown(nFlags, point);<br />
}<br />
<br />
///////////////////////////////////////////////////////////////////////////////<br />
//<br />
// OnLButtonDblClk()<br />
//<br />
// Purpose:     Handle WM_LBUTTONDBLCLK message, to display right-click menu<br />
//<br />
// Parameters:  nFlags - indicates whether various virtual keys are down<br />
//              point  - specifies the x- and y-coordinate of the cursor<br />
//<br />
// Returns:     None<br />
//<br />
void CXSudokuWnd::OnLButtonDblClk(UINT nFlags, CPoint point)<br />
{<br />
	TRACE(_T("in CXSudokuWnd::OnLButtonDblClk\n"));<br />
<br />
	ShowPopup(point);<br />
<br />
	CWnd::OnLButtonDblClk(nFlags, point);<br />
}<br />
<br />
///////////////////////////////////////////////////////////////////////////////<br />
//<br />
// OnRButtonDown()<br />
//<br />
// Purpose:     Handle WM_RBUTTONDOWN message, to enable drawing the<br />
//              selection border<br />
//<br />
// Parameters:  nFlags - indicates whether various virtual keys are down<br />
//              point  - specifies the x- and y-coordinate of the cursor<br />
//<br />
// Returns:     None<br />
//<br />
void CXSudokuWnd::OnRButtonDown(UINT nFlags, CPoint point)<br />
{<br />
	// change current cell<br />
<br />
	int nOldRow = m_nCurRow;<br />
	int nOldCol = m_nCurCol;<br />
<br />
	if (CellFromPoint(point, m_nCurRow, m_nCurCol))<br />
	{<br />
		if ((nOldRow != -1) && (nOldCol != -1))<br />
		{<br />
			CRect rectOld = GetCellRect(nOldRow, nOldCol);<br />
			InvalidateRect(&rectOld, FALSE);<br />
		}<br />
		CRect rectNew = GetCellRect(m_nCurRow, m_nCurCol);<br />
		InvalidateRect(&rectNew, FALSE);<br />
		SetFocus();<br />
	}<br />
<br />
	CWnd::OnRButtonDown(nFlags, point);<br />
}<br />
<br />
///////////////////////////////////////////////////////////////////////////////<br />
//<br />
// OnRButtonUp()<br />
//<br />
// Purpose:     Handle WM_RBUTTONUP message, to display right-click menu<br />
//<br />
// Parameters:  nFlags - indicates whether various virtual keys are down<br />
//              point  - specifies the x- and y-coordinate of the cursor<br />
//<br />
// Returns:     None<br />
//<br />
void CXSudokuWnd::OnRButtonUp(UINT nFlags, CPoint point)<br />
{<br />
	ShowPopup(point);<br />
<br />
	CWnd::OnRButtonUp(nFlags, point);<br />
}<br />
<br />
///////////////////////////////////////////////////////////////////////////////<br />
//<br />
// PreTranslateMessage()<br />
//<br />
// Purpose:     translate window messages before they are dispatched; used to<br />
//              catch keyboard input<br />
//<br />
// Parameters:  pMsg - points to a MSG structure that contains the message<br />
//                     to process<br />
//<br />
// Returns:     BOOL - TRUE = message was translated and should not be dispatched<br />
//<br />
BOOL CXSudokuWnd::PreTranslateMessage(MSG* pMsg)<br />
{<br />
	switch (pMsg->message)<br />
	{<br />
		case WM_KEYUP:<br />
			//TRACE(_T("WM_KEYUP\n"));<br />
			if ((pMsg->wParam >= _T('0')) && (pMsg->wParam <= _T('9')))<br />
			{<br />
				if ((m_nCurRow != -1) &&<br />
					(m_nCurCol != -1) &&<br />
					(m_nGivens[m_nCurRow][m_nCurCol] == 0))<br />
				{<br />
					// find all possible candidates for this cell<br />
					WORD wCandidates = GetCandidates(m_nCurRow, m_nCurCol) & GetUnPenciledCandidates(m_nCurRow, m_nCurCol);<br />
					int nDigit = pMsg->wParam - _T('0');<br />
					if (nDigit && TestBit(wCandidates, nDigit))<br />
					{<br />
						SWITCH_RESOURCE;		// load string from dll resource<br />
<br />
						AddUndoAction(m_nCurRow, m_nCurCol, ACTION_CODE_USER_ENTRY, nDigit);<br />
						m_nUserEntries[m_nCurRow][m_nCurCol] = nDigit;<br />
						CString str = _T("");<br />
						CString strColumns = _T("");<br />
						VERIFY(strColumns.LoadString(IDS_COLUMN_LABELS));<br />
						str.Format(IDS_USER_ENTRY, nDigit, m_nCurRow+1, strColumns[m_nCurCol]);<br />
						int nType = 0;<br />
						CString strCorrect = _T("");<br />
						if (nDigit == m_nSolution[m_nCurRow][m_nCurCol])<br />
						{<br />
							VERIFY(strCorrect.LoadString(IDS_CORRECT));<br />
						}<br />
						else<br />
						{<br />
							VERIFY(strCorrect.LoadString(IDS_INCORRECT));<br />
							nType = 1;<br />
						}<br />
						str += strCorrect;<br />
<br />
						if (m_hWndMessage && ::IsWindow(m_hWndMessage))<br />
							::SendMessage(m_hWndMessage, WM_XSUDOKU, nType,<br />
								(LPARAM)(LPCTSTR)str);<br />
					}<br />
					else if (nDigit == 0)<br />
					{<br />
						AddUndoAction(m_nCurRow, m_nCurCol, ACTION_CODE_USER_ENTRY, nDigit);<br />
						m_nUserEntries[m_nCurRow][m_nCurCol] = nDigit;<br />
					}<br />
<br />
					// NOTE:  must set hint to 0 or user entry will<br />
					//        not be displayed<br />
					m_nHints[m_nCurRow][m_nCurCol] = 0;<br />
					Invalidate(FALSE);<br />
				}<br />
				return TRUE;<br />
			}<br />
			else if (pMsg->wParam == VK_RETURN)<br />
			{<br />
				OnRightClick();<br />
			}<br />
			break;<br />
<br />
		case WM_KEYDOWN:<br />
			//TRACE(_T("WM_KEYDOWN\n"));<br />
			int nOldRow = m_nCurRow;<br />
			int nOldCol = m_nCurCol;<br />
			BOOL bUpdate = FALSE;<br />
			switch (pMsg->wParam)<br />
			{<br />
				case VK_DOWN:<br />
					TRACE(_T("VK_DOWN\n"));<br />
<br />
					if (m_nCurRow < 8)<br />
					{<br />
						m_nCurRow++;<br />
						bUpdate = TRUE;<br />
					}<br />
					break;<br />
<br />
				case VK_UP:<br />
					TRACE(_T("VK_UP\n"));<br />
<br />
					if (m_nCurRow > 0)<br />
					{<br />
						m_nCurRow--;<br />
						bUpdate = TRUE;<br />
					}<br />
					break;<br />
<br />
				case VK_RIGHT:<br />
					TRACE(_T("VK_RIGHT\n"));<br />
<br />
					if (m_nCurCol < 8)<br />
					{<br />
						m_nCurCol++;<br />
						bUpdate = TRUE;<br />
					}<br />
					break;<br />
<br />
				case VK_LEFT:<br />
					TRACE(_T("VK_LEFT\n"));<br />
<br />
					if (m_nCurCol > 0)<br />
					{<br />
						m_nCurCol--;<br />
						bUpdate = TRUE;<br />
					}<br />
					break;<br />
<br />
				case VK_HOME:<br />
					TRACE(_T("VK_HOME\n"));<br />
<br />
					if (m_nCurCol > 0)<br />
					{<br />
						m_nCurCol = 0;<br />
						bUpdate = TRUE;<br />
					}<br />
					break;<br />
<br />
				case VK_END:<br />
					TRACE(_T("VK_END\n"));<br />
<br />
					if (m_nCurCol < 8)<br />
					{<br />
						m_nCurCol = 8;<br />
						bUpdate = TRUE;<br />
					}<br />
					break;<br />
<br />
				case VK_PRIOR:<br />
					TRACE(_T("VK_PRIOR\n"));<br />
<br />
					if (m_nCurRow > 0)<br />
					{<br />
						m_nCurRow = 0;<br />
						bUpdate = TRUE;<br />
					}<br />
					break;<br />
<br />
				case VK_NEXT:<br />
					TRACE(_T("VK_NEXT\n"));<br />
<br />
					if (m_nCurRow < 8)<br />
					{<br />
						m_nCurRow = 8;<br />
						bUpdate = TRUE;<br />
					}<br />
					break;<br />
<br />
			}<br />
<br />
			if (bUpdate)<br />
			{<br />
				if ((nOldRow != -1) && (nOldCol != -1))<br />
				{<br />
					CRect rectOld = GetCellRect(nOldRow, nOldCol);<br />
					InvalidateRect(&rectOld, FALSE);<br />
				}<br />
				CRect rectNew = GetCellRect(m_nCurRow, m_nCurCol);<br />
				InvalidateRect(&rectNew, FALSE);<br />
				SetFocus();<br />
			}<br />
			break;<br />
	}<br />
<br />
	if (m_hAccel && ::TranslateAccelerator(m_hWnd, m_hAccel, pMsg))<br />
		return TRUE;<br />
<br />
	return CWnd::PreTranslateMessage(pMsg);<br />
}<br />
<br />
///////////////////////////////////////////////////////////////////////////////<br />
//<br />
// Create()<br />
//<br />
// Purpose:     Create XSudokuWnd window<br />
//<br />
// Parameters:  lpszClassName  - points to a null-terminated character string<br />
//                               that names the Windows class<br />
//              lpszWindowName - points to a null-terminated character string<br />
//                               that contains the window name<br />
//              dwStyle        - specifies the window style attributes<br />
//              rect           - the size and position of the window<br />
//              pParentWnd     - the parent window<br />
//              nID            - the ID of the child window<br />
//              pContext       - the create context of the window<br />
//<br />
// Returns:     BOOL - TRUE = window created successfully<br />
//<br />
BOOL CXSudokuWnd::Create(LPCTSTR lpszClassName,<br />
						 LPCTSTR lpszWindowName,<br />
						 DWORD dwStyle,<br />
						 const RECT& rect,<br />
						 CWnd* pParentWnd,<br />
						 UINT nID,<br />
						 CCreateContext* pContext)<br />
{<br />
	TRACE(_T("in CXSudokuWnd::Create\n"));<br />
	BOOL bRet = CWnd::Create(lpszClassName, lpszWindowName, dwStyle, rect,<br />
		pParentWnd, nID, pContext);<br />
<br />
	if (bRet)<br />
	{<br />
		SWITCH_RESOURCE;		// load dialog from dll resource<br />
<br />
		m_hAccel = ::LoadAccelerators(ExtensionDLL.hModule, <br />
						MAKEINTRESOURCE(IDR_ACCELERATOR));<br />
		ASSERT(m_hAccel);<br />
<br />
		// create fonts for XSudokuWnd;  all these must be deleted in dtor<br />
<br />
		LOGFONT lf;<br />
		memset(&lf, 0, sizeof(lf));<br />
		CFont *pFont = GetFont();<br />
		if (pFont == NULL)<br />
			pFont = GetParent()->GetFont();<br />
		pFont->GetLogFont(&lf);<br />
		_tcscpy(lf.lfFaceName, m_strLabelFont);<br />
		lf.lfWeight = FW_BOLD;<br />
<br />
		m_nLabelHeight = abs(lf.lfHeight);<br />
<br />
		if (m_fontLabels.GetSafeHandle())<br />
			m_fontLabels.DeleteObject();<br />
<br />
		m_fontLabels.CreateFontIndirect(&lf);<br />
<br />
		lf.lfWeight = FW_NORMAL;<br />
<br />
		if (m_fontPencilMarks.GetSafeHandle())<br />
			m_fontPencilMarks.DeleteObject();<br />
<br />
		m_fontPencilMarks.CreateFontIndirect(&lf);<br />
<br />
		lf.lfWeight = FW_BOLD;<br />
		_tcscpy(lf.lfFaceName, _T("Arial"));<br />
		lf.lfHeight = GetFontHeight(30);<br />
		m_nValueHeight = abs(lf.lfHeight);<br />
<br />
		if (m_fontValues.GetSafeHandle())<br />
			m_fontValues.DeleteObject();<br />
<br />
		m_fontValues.CreateFontIndirect(&lf);<br />
<br />
		SetFocus();<br />
	}<br />
<br />
	return bRet;<br />
}<br />
<br />
///////////////////////////////////////////////////////////////////////////////<br />
//<br />
// GetSudoku()<br />
//<br />
// Purpose:     Display Sudoku entry dialog<br />
//<br />
// Parameters:  lpszPuzzle - optional string containing Sudoku puzzle<br />
//<br />
// Returns:     CString - Sudoku puzzle<br />
//<br />
CString CXSudokuWnd::GetSudoku(LPCTSTR lpszPuzzle /*= NULL*/)<br />
{<br />
	SWITCH_RESOURCE;		// load dialog from dll resource<br />
<br />
	CString strSudoku = _T("");<br />
<br />
	CSudokuEntryDlg dlg;<br />
<br />
	if (lpszPuzzle)<br />
		dlg.m_strSudoku = lpszPuzzle;<br />
<br />
	if (dlg.DoModal() == IDOK)<br />
	{<br />
		strSudoku = dlg.m_strSudoku;<br />
	}<br />
<br />
	return strSudoku;<br />
}<br />
<br />
///////////////////////////////////////////////////////////////////////////////<br />
//<br />
// Reset()<br />
//<br />
// Purpose:     Reset to initial state<br />
//<br />
// Parameters:  bResetAll - TRUE  = reset to "no puzzle loaded" state<br />
//                          FALSE = reset to "no values entered" state<br />
//<br />
// Returns:     None<br />
//<br />
// Notes:       The undo list will be reset; all undo actions will be lost.<br />
//<br />
void CXSudokuWnd::Reset(BOOL bResetAll)<br />
{<br />
	m_nCurRow = m_nCurCol = -1;<br />
	m_pointPopup          = CPoint(-1,-1);<br />
	m_bShowSolution       = FALSE;<br />
	if (bResetAll)<br />
		m_bIsValid        = FALSE;<br />
<br />
	for (int i = 0; i < 9; i++)<br />
	{<br />
		for (int j = 0; j < 9; j++)<br />
		{<br />
			m_nHints[i][j] = 0;<br />
			m_nUserEntries[i][j] = 0;<br />
			if (bResetAll)<br />
			{<br />
				m_nGivens[i][j] = 0;<br />
				m_nUnPencil[i][j] = 0;<br />
				m_nSolution[i][j] = 0;<br />
			}<br />
		}<br />
	}<br />
<br />
	// reset undo facility<br />
	m_nUndoIndex     = 0;<br />
	m_nUndoLastEntry = 0;<br />
<br />
	for (int k = 0; k < m_nUndoSize; k++)<br />
	{<br />
		UNDO_BLOCK * pUB = (UNDO_BLOCK *) m_Undo[k];<br />
		ASSERT(pUB);<br />
		if (pUB)<br />
		{<br />
			pUB->action_code = 0;<br />
			pUB->action_value = 0;<br />
		}<br />
	}<br />
}<br />
<br />
///////////////////////////////////////////////////////////////////////////////<br />
//<br />
// LoadFromFile()<br />
//<br />
// Purpose:     Load Sudoku from text file specified by lpszFile<br />
//<br />
// Parameters:  lpszFile - name of file containing Sudoku puzzle<br />
//<br />
// Returns:     BOOL - TRUE = Sudoku loaded ok<br />
//<br />
BOOL CXSudokuWnd::LoadFromFile(LPCTSTR lpszFile)<br />
{<br />
	BOOL ok = FALSE;<br />
<br />
	ASSERT(lpszFile && (lpszFile[0] != _T('\0')));<br />
	if (lpszFile && (lpszFile[0] != _T('\0')))<br />
	{<br />
		CFile file;<br />
		if (file.Open(lpszFile, CFile::shareDenyNone | CFile::modeRead))<br />
		{<br />
			m_strFile = lpszFile;<br />
<br />
			DWORD dwLen = file.GetLength();	// size in bytes<br />
<br />
			// should be less than 2000 chars<br />
			if (dwLen > 2000)<br />
				dwLen = 2000;<br />
<br />
			if (dwLen)<br />
			{<br />
				TCHAR * buf = new TCHAR [dwLen+10];<br />
				memset(buf, 0, (dwLen+10) * sizeof(TCHAR));<br />
<br />
				file.Read(buf, dwLen);<br />
<br />
				ok = LoadFromString(buf, dwLen/sizeof(TCHAR));<br />
<br />
				delete [] buf;<br />
			}<br />
<br />
			file.Close();<br />
		}<br />
	}<br />
<br />
	return ok;<br />
}<br />
<br />
///////////////////////////////////////////////////////////////////////////////<br />
//<br />
// LoadFromClipboard()<br />
//<br />
// Purpose:     Load Sudoku from clipboard<br />
//<br />
// Parameters:  None<br />
//<br />
// Returns:     BOOL - TRUE = Sudoku loaded ok<br />
//<br />
BOOL CXSudokuWnd::LoadFromClipboard()<br />
{<br />
	BOOL ok = FALSE;<br />
<br />
	m_strFile = _T("");<br />
<br />
	CClipboard clip;<br />
	DWORD dwLen = clip.GetTextLength();	// size in bytes<br />
	TRACE(_T("dwLen=%d\n"), dwLen);<br />
<br />
	// should be less than 2000 chars<br />
	if (dwLen > 2000)<br />
		dwLen = 2000;<br />
<br />
	if (dwLen)<br />
	{<br />
		TCHAR * buf = new TCHAR [dwLen+10];<br />
		memset(buf, 0, (dwLen+10) * sizeof(TCHAR));<br />
<br />
		clip.GetText(buf, dwLen+2);<br />
<br />
		ok = LoadFromString(buf, dwLen/sizeof(TCHAR));<br />
<br />
		delete [] buf;<br />
	}<br />
<br />
	return ok;<br />
}<br />
<br />
///////////////////////////////////////////////////////////////////////////////<br />
//<br />
// LoadFromString()<br />
//<br />
// Purpose:     Load Sudoku from string<br />
//<br />
// Parameters:  lpszString - string buffer containing Sudoku puzzle<br />
//              dwLen      - length of string<br />
//<br />
// Returns:     BOOL - TRUE = Sudoku loaded ok<br />
//<br />
// Notes:       All the other LoadXXX() functions call this one eventually.<br />
//<br />
BOOL CXSudokuWnd::LoadFromString(LPCTSTR lpszString, DWORD dwLen)<br />
{<br />
	BOOL ok = FALSE;<br />
<br />
	Reset(TRUE);<br />
<br />
	TRACE(_T("dwLen=%d\n"), dwLen);<br />
	int nCount = 0;	// count of values read<br />
<br />
	ASSERT(dwLen);<br />
	ASSERT(lpszString && (lpszString[0] != _T('\0')));<br />
<br />
	if ((dwLen >= 81) && lpszString && (lpszString[0] != _T('\0')))<br />
	{<br />
		TRACE(_T("lpszString=<%s>\n"), lpszString);<br />
<br />
		int row = 0;<br />
		int col = 0;<br />
<br />
		for (DWORD dwIndex = 0; dwIndex < dwLen; dwIndex++)<br />
		{<br />
			TCHAR c = lpszString[dwIndex];<br />
<br />
			int nValue = 0;<br />
<br />
			if (c == _T('\0'))		// check for eol<br />
				break;<br />
<br />
			if (c == _T('.'))		// . = 0<br />
			{<br />
				nValue = 0;<br />
			}<br />
			else if (_istdigit(c))<br />
			{<br />
				nValue = c - _T('0');<br />
			}<br />
			else<br />
			{<br />
				continue;<br />
			}<br />
<br />
			m_nGivens[row][col++] = nValue;<br />
			//TRACE(_T("%d:  nValue=%d\n"), nCount, nValue);<br />
			nCount++;<br />
			if (nCount == 81)<br />
				break;<br />
			if (col >= 9)<br />
			{<br />
				row++;<br />
				col = 0;<br />
			}<br />
		} // for<br />
	}<br />
<br />
	TRACE(_T("nCount=%d\n"), nCount);<br />
<br />
	if (nCount == 81)<br />
	{<br />
		m_bHaveGivens = TRUE;<br />
<br />
		memcpy(m_nSolution, m_nGivens, sizeof(m_nSolution));<br />
<br />
		DLX dlx;<br />
		int n = dlx.sudoku_solve(m_nSolution);<br />
<br />
		SWITCH_RESOURCE;		// load string from dll resource<br />
<br />
		int nType = 0;<br />
		CString str = _T("");<br />
		if (n == 0)<br />
		{<br />
			nType = 1;<br />
			VERIFY(str.LoadString(IDS_SUDOKU_UNSOLVABLE));<br />
		}<br />
		else if (n == 1)<br />
		{<br />
			VERIFY(str.LoadString(IDS_SUDOKU_OK));<br />
			m_bIsValid = TRUE;<br />
		}<br />
		else<br />
		{<br />
			nType = 1;<br />
			VERIFY(str.LoadString(IDS_SUDOKU_INVALID));<br />
		}<br />
		if (m_hWndMessage && ::IsWindow(m_hWndMessage))<br />
			::SendMessage(m_hWndMessage, WM_XSUDOKU, nType,<br />
				(LPARAM)(LPCTSTR)str);<br />
<br />
		ok = TRUE;<br />
	}<br />
<br />
	return ok;<br />
}<br />
<br />
///////////////////////////////////////////////////////////////////////////////<br />
//<br />
// ShowColorPrefsDlg()<br />
//<br />
// Purpose:     Display color prefs dialog<br />
//<br />
// Parameters:  None<br />
//<br />
// Returns:     BOOL - TRUE = user ended color prefs dialog with OK button<br />
//<br />
BOOL CXSudokuWnd::ShowColorPrefsDlg()<br />
{<br />
	SWITCH_RESOURCE;		// load dialog from dll resource<br />
<br />
	BOOL ok = FALSE;<br />
<br />
	CColorPrefDlg dlg;<br />
<br />
	dlg.m_rgbWindowBackground = m_rgbWindowBackground;<br />
	dlg.m_rgbCellBackground = m_rgbCellBackground;<br />
	dlg.m_rgbLabels = m_rgbLabels;<br />
	dlg.m_rgbGivens = m_rgbGivens;<br />
	dlg.m_rgbSolution = m_rgbSolution;<br />
	dlg.m_rgbUserEntry = m_rgbUserEntry;<br />
	dlg.m_rgbPencilMarks = m_rgbPencilMarks;<br />
	dlg.m_rgbHighlightNumber = m_rgbHighlightNumber;<br />
	dlg.m_rgbCurCellBorder = m_rgbCurCellBorder;<br />
	dlg.m_rgb3x3Gridline = m_rgb3x3Gridline;<br />
<br />
	if (dlg.DoModal() == IDOK)<br />
	{<br />
		m_rgbWindowBackground = dlg.m_rgbWindowBackground;<br />
		m_rgbCellBackground = dlg.m_rgbCellBackground;<br />
		m_rgbLabels = dlg.m_rgbLabels;<br />
		m_rgbGivens = dlg.m_rgbGivens;<br />
		m_rgbSolution = dlg.m_rgbSolution;<br />
		m_rgbUserEntry = dlg.m_rgbUserEntry;<br />
		m_rgbPencilMarks = dlg.m_rgbPencilMarks;<br />
		m_rgbHighlightNumber = dlg.m_rgbHighlightNumber;<br />
		m_rgbCurCellBorder = dlg.m_rgbCurCellBorder;<br />
		m_rgb3x3Gridline = dlg.m_rgb3x3Gridline;<br />
<br />
		Invalidate(FALSE);<br />
<br />
		ok = TRUE;<br />
	}<br />
<br />
	return ok;<br />
}<br />
<br />
///////////////////////////////////////////////////////////////////////////////<br />
//<br />
// CaptureBitmap()<br />
//<br />
// Purpose:     Copy bitmap of XSudokuWnd window to clipboard<br />
//<br />
// Parameters:  None<br />
//<br />
// Returns:     BOOL - TRUE = bitmap copied to clipboard ok<br />
//<br />
BOOL CXSudokuWnd::CaptureBitmap()<br />
{<br />
	CDC dc;<br />
	HDC hdc = ::GetDC(m_hWnd);<br />
	dc.Attach(hdc);<br />
<br />
	CDC memDC;<br />
	memDC.CreateCompatibleDC(&dc);<br />
<br />
	CBitmap bm;<br />
	CRect r;<br />
	GetClientRect(&r);<br />
<br />
	CSize sz(r.Width(), r.Height());<br />
	bm.CreateCompatibleBitmap(&dc, sz.cx, sz.cy);<br />
	CBitmap * oldbm = memDC.SelectObject(&bm);<br />
	memDC.BitBlt(0, 0, sz.cx, sz.cy, &dc, 0, 0, SRCCOPY);<br />
<br />
	BOOL bRet = FALSE;<br />
<br />
	if (::OpenClipboard(m_hWnd))<br />
	{<br />
		::EmptyClipboard();<br />
		::SetClipboardData(CF_BITMAP, bm.m_hObject);<br />
		::CloseClipboard();<br />
		bRet = TRUE;<br />
	}<br />
<br />
	memDC.SelectObject(oldbm);<br />
	bm.Detach();  // make sure bitmap not deleted with CBitmap object<br />
	::ReleaseDC(m_hWnd, hdc);<br />
<br />
	return bRet;<br />
}<br />
<br />
///////////////////////////////////////////////////////////////////////////////<br />
//<br />
// PrintBitmap()<br />
//<br />
// Purpose:     Print bitmap of XSudokuWnd window<br />
//<br />
// Parameters:  None<br />
//<br />
// Returns:     BOOL - TRUE = bitmap printed ok<br />
//<br />
BOOL CXSudokuWnd::PrintBitmap()<br />
{<br />
	CPrintDialog printDlg(FALSE,<br />
		PD_ALLPAGES | PD_NOPAGENUMS | PD_NOSELECTION | PD_HIDEPRINTTOFILE);<br />
<br />
	if (printDlg.DoModal() == IDCANCEL)<br />
		return FALSE;<br />
<br />
	Invalidate(FALSE);<br />
<br />
	CDC dcPrint;<br />
<br />
	if (!dcPrint.Attach(printDlg.GetPrinterDC()))<br />
	{<br />
		AfxMessageBox(_T("No printer found!"));<br />
		return FALSE;<br />
	}<br />
<br />
	dcPrint.m_bPrinting = TRUE;<br />
<br />
	// Initialise print document details<br />
	DOCINFO di;<br />
	::ZeroMemory (&di, sizeof (DOCINFO));<br />
	di.cbSize = sizeof (DOCINFO);<br />
	di.lpszDocName = _T("XSudoku");<br />
<br />
	BOOL bPrintingOK = dcPrint.StartDoc(&di);    // Begin a new print job<br />
<br />
	CRect rectClient;<br />
	GetWindowRect(rectClient);<br />
	ScreenToClient(&rectClient);<br />
	CClientDC dcClient(this);<br />
<br />
	dcPrint.SetMapMode(MM_ANISOTROPIC);<br />
<br />
	CSize sz(dcClient.GetDeviceCaps(LOGPIXELSX), dcClient.GetDeviceCaps(LOGPIXELSY));<br />
	dcPrint.SetWindowExt(sz);<br />
	int nMargin = sz.cx;<br />
	sz = CSize(dcPrint.GetDeviceCaps(LOGPIXELSX), dcPrint.GetDeviceCaps(LOGPIXELSY));<br />
	dcPrint.SetViewportExt(sz);<br />
<br />
	// Get the printing extents and store in the m_rectDraw field of a<br />
	// CPrintInfo object<br />
	CPrintInfo Info;<br />
	Info.SetMaxPage(1); // just one page<br />
	int maxw = dcPrint.GetDeviceCaps(HORZRES);<br />
	int maxh = dcPrint.GetDeviceCaps(VERTRES);<br />
	Info.m_rectDraw.SetRect(0, 0, maxw, maxh);<br />
	UINT page = 1;//Info.GetMinPage();<br />
<br />
	dcPrint.StartPage();                     // begin new page<br />
	Info.m_nCurPage = page;<br />
<br />
	GHandle dib;<br />
	dib.Attach(GDIUtil::GrabDIB(&dcClient, rectClient));<br />
	if (dib.GetHandle() == NULL)<br />
		return FALSE;<br />
<br />
	GLock lock(dib);<br />
	BITMAPINFOHEADER *pBMI = (BITMAPINFOHEADER*)(LPVOID) lock;<br />
<br />
	CRect rectPrint = Info.m_rectDraw;<br />
	rectPrint.left  += nMargin;<br />
	rectPrint.right -= nMargin;<br />
	rectPrint.top   += nMargin;<br />
<br />
	CFont *pOldFont = NULL;<br />
<br />
	if (m_fontPrint.GetSafeHandle() == NULL)<br />
	{<br />
		// create font for printing<br />
<br />
		LOGFONT lf;<br />
		memset(&lf, 0, sizeof(lf));<br />
		_tcscpy(lf.lfFaceName, _T("Times New Roman"));<br />
		lf.lfCharSet = DEFAULT_CHARSET;<br />
		lf.lfPitchAndFamily = FF_ROMAN;<br />
		lf.lfWeight = FW_NORMAL;<br />
		lf.lfOutPrecision = OUT_DEFAULT_PRECIS;<br />
		lf.lfHeight = GetFontHeight(14);<br />
		m_fontPrint.CreateFontIndirect(&lf);<br />
		pOldFont = dcPrint.SelectObject(&m_fontPrint);<br />
	}<br />
<br />
	int h = dcPrint.DrawText(m_strFile, &rectPrint, DT_LEFT);<br />
	CString strTime = _T("");<br />
	CTime t = CTime::GetCurrentTime();<br />
	strTime = t.Format(_T("Printed on %A, %B %#d, %Y"));<br />
	rectPrint.top += h;<br />
	h = dcPrint.DrawText(strTime, &rectPrint, DT_LEFT);<br />
	rectPrint.top += h;<br />
<br />
	int nColors = 0;<br />
	if (pBMI->biBitCount <= 8)<br />
		nColors = (1<< pBMI->biBitCount);<br />
<br />
	::StretchDIBits(dcPrint.GetSafeHdc(),<br />
			rectPrint.left,<br />
			rectPrint.top + nMargin,<br />
			pBMI->biWidth,<br />
			pBMI->biHeight,<br />
			0,<br />
			0,<br />
			pBMI->biWidth,<br />
			pBMI->biHeight,<br />
			(LPBYTE)pBMI + (pBMI->biSize + nColors * sizeof(RGBQUAD)),<br />
			(BITMAPINFO*)pBMI,<br />
			DIB_RGB_COLORS,<br />
			SRCCOPY);<br />
<br />
	dcPrint.SelectObject(pOldFont);<br />
<br />
	bPrintingOK = (dcPrint.EndPage() > 0);   // end page<br />
<br />
	if (bPrintingOK)<br />
		dcPrint.EndDoc(); // end a print job<br />
	else<br />
		dcPrint.AbortDoc();           // abort job.<br />
<br />
	return TRUE;<br />
}<br />
<br />
///////////////////////////////////////////////////////////////////////////////<br />
//<br />
// ActionCodeToString()<br />
//<br />
// Purpose:     Convert undo action code to string<br />
//<br />
// Parameters:  action_code - undo action code, see definition of UNDO_BLOCK<br />
//<br />
// Returns:     CString - action code string;  empty if action code = 0<br />
//<br />
CString CXSudokuWnd::ActionCodeToString(BYTE action_code)<br />
{<br />
	CString str = _T("");<br />
<br />
#ifdef _DEBUG<br />
	switch (action_code)<br />
	{<br />
		case 0:		// unused entry<br />
			break;<br />
		case ACTION_CODE_USER_ENTRY:<br />
			str = _T("ACTION_CODE_USER_ENTRY");<br />
			break;<br />
		case ACTION_CODE_USER_ENTRY_REMOVE_ALL:<br />
			str = _T("ACTION_CODE_USER_ENTRY_REMOVE_ALL");<br />
			break;<br />
		case ACTION_CODE_HINT:<br />
			str = _T("ACTION_CODE_HINT");<br />
			break;<br />
		case ACTION_CODE_HINT_REMOVE_ALL:<br />
			str = _T("ACTION_CODE_HINT_REMOVE_ALL");<br />
			break;<br />
		default:<br />
			str = _T("ERROR - unknown action code");<br />
			break;<br />
	}<br />
#else<br />
	UNUSED_ALWAYS(action_code);<br />
#endif<br />
<br />
	return str;<br />
}<br />
<br />
///////////////////////////////////////////////////////////////////////////////<br />
//<br />
// AddUndoAction()<br />
//<br />
// Purpose:     Add action to undo list<br />
//<br />
// Parameters:  row          - current row number for action<br />
//              col          - current column number for action<br />
//              action_code  - what action<br />
//              action_value - value associated with action<br />
//<br />
// Returns:     BOOL - TRUE = action successfully added<br />
//<br />
BOOL CXSudokuWnd::AddUndoAction(int row,<br />
								int col,<br />
								int action_code,<br />
								int action_value)<br />
{<br />
	BOOL ok = FALSE;<br />
<br />
	if (!m_bEnableUndo)<br />
		return ok;<br />
<br />
	ASSERT((action_code >= 1) && (action_code <= 4));<br />
<br />
	UNDO_BLOCK * pUB = (UNDO_BLOCK *) m_Undo[m_nUndoIndex];<br />
	ASSERT(pUB);<br />
<br />
	if (pUB)<br />
	{<br />
		pUB->action_code  = (BYTE)action_code;<br />
		pUB->action_value = (BYTE)action_value;<br />
		pUB->row          = (BYTE)row;<br />
		pUB->col          = (BYTE)col;<br />
		int i, j;<br />
		for (i = 0; i < 9; i++)<br />
		{<br />
			for (j = 0; j < 9; j++)<br />
			{<br />
				pUB->hints[i][j] = (BYTE)m_nHints[i][j];<br />
				pUB->user_entries[i][j] = (BYTE)m_nUserEntries[i][j];<br />
			}<br />
		}<br />
<br />
		// advance undo pointers<br />
		m_nUndoIndex++;<br />
<br />
		// there may be other entries in the undo list after this one -<br />
		// the user may have undone some entries, and then entered new data.<br />
		// m_nUndoLastEntry always points to the last user action<br />
		m_nUndoLastEntry = m_nUndoIndex;<br />
<br />
		if (m_nUndoIndex >= m_nUndoSize)<br />
		{<br />
			// need to grow undo array<br />
			int nOldUndoSize = m_nUndoSize;<br />
			m_nUndoSize += UNDO_ARRAY_GROW_BY_SIZE;<br />
			BOOL bSetSizeFailed = FALSE;<br />
			TRY<br />
			{<br />
				m_Undo.SetSize(m_nUndoSize);<br />
			}<br />
			CATCH (CMemoryException, e)<br />
			{<br />
				bSetSizeFailed = TRUE;<br />
			}<br />
			END_CATCH<br />
<br />
			if (bSetSizeFailed)<br />
			{<br />
				m_nUndoSize = nOldUndoSize;<br />
				m_bEnableUndo = FALSE;<br />
				TRACE(_T("ERROR - SetSize failed\n"));<br />
				ASSERT(FALSE);<br />
			}<br />
			else<br />
			{<br />
				for (j = nOldUndoSize; j < m_nUndoSize; j++)<br />
				{<br />
					UNDO_BLOCK * pUB = new UNDO_BLOCK;<br />
					ASSERT(pUB);<br />
					m_Undo[j] = pUB;<br />
				}<br />
				ok = TRUE;<br />
			}<br />
		}<br />
		else<br />
		{<br />
			ok = TRUE;<br />
		}<br />
	}<br />
<br />
	return ok;<br />
}<br />
<br />
///////////////////////////////////////////////////////////////////////////////<br />
//<br />
// ShowPopup()<br />
//<br />
// Purpose:     Display right click (context) menu<br />
//<br />
// Parameters:  point - location for popup menu<br />
//<br />
// Returns:     None<br />
//<br />
void CXSudokuWnd::ShowPopup(CPoint point)<br />
{<br />
	m_pointPopup = point;<br />
<br />
	int row, col, i;<br />
	CString str = _T("");<br />
<br />
	if (m_bHaveGivens && CellFromPoint(point, row, col))<br />
	{<br />
		CMenu popup;<br />
		VERIFY(popup.CreatePopupMenu());<br />
<br />
		UINT nFlags = MF_STRING;<br />
<br />
		if (m_bEnableUndo)<br />
		{<br />
			if (m_nUndoIndex == 0)<br />
				nFlags |= MF_DISABLED | MF_GRAYED;<br />
			VERIFY(popup.AppendMenu(nFlags, ID_POPUP_UNDO, _T("Undo\tCtrl+Z")));<br />
<br />
			nFlags = MF_STRING;<br />
<br />
			if (m_nUndoIndex >= m_nUndoLastEntry)<br />
				nFlags |= MF_DISABLED | MF_GRAYED;<br />
			VERIFY(popup.AppendMenu(nFlags, ID_POPUP_REDO, _T("Redo\tCtrl+Y")));<br />
<br />
			VERIFY(popup.AppendMenu(MF_SEPARATOR));<br />
		}<br />
<br />
		if ((m_nGivens[row][col] == 0) &&<br />
			(m_nHints[row][col] == 0) &&<br />
			(m_nUserEntries[row][col] == 0) &&<br />
			!m_bShowSolution)<br />
		{<br />
			// find all possible candidates for this cell<br />
			WORD wCandidates = GetCandidates(row, col);<br />
<br />
			if (wCandidates)<br />
			{<br />
				for (i = 0; i < 9; i++)<br />
				{<br />
					if (TestBit(wCandidates, i+1))<br />
					{<br />
						str.Format(_T("Set to %d\t%d"), i+1, i+1);<br />
<br />
						VERIFY(popup.AppendMenu(MF_STRING, ID_POPUP_SET_1+i, str));<br />
					}<br />
				}<br />
				VERIFY(popup.AppendMenu(MF_SEPARATOR));<br />
			}<br />
<br />
			<br />
			WORD wUnPencilCandidates  = GetUnPenciledCandidates(row, col) & wCandidates;	//get the possible values that can be 'un-penciled'<br />
			WORD wPencilCandidates    = ~GetUnPenciledCandidates(row, col) & 0x3FE;			//get the possible values that can be 're-penciled' in<br />
<br />
			<br />
			for (i = 0; i < 9; i++)<br />
			{<br />
				if (TestBit(wUnPencilCandidates, i+1))<br />
				{<br />
					str.Format(_T("Rub Out %d"), i+1);<br />
<br />
					VERIFY(popup.AppendMenu(MF_STRING, ID_POPUP_UNPENCIL_1+i, str));<br />
				}<br />
				else<br />
				{<br />
					if ((TestBit(wPencilCandidates, i+1)) & (TestBit(wCandidates, i+1)))<br />
					{<br />
						str.Format(_T("Pencil In %d"), i+1);<br />
<br />
						VERIFY(popup.AppendMenu(MF_STRING, ID_POPUP_PENCIL_1+i, str));<br />
					}<br />
				}<br />
			}<br />
			<br />
			VERIFY(popup.AppendMenu(MF_SEPARATOR));<br />
<br />
<br />
		}<br />
<br />
		nFlags = MF_STRING;<br />
		if (m_nGivens[row][col])<br />
			nFlags |= MF_DISABLED | MF_GRAYED;<br />
		if (m_nHints[row][col])<br />
			nFlags |= MF_CHECKED;<br />
		else<br />
			nFlags |= MF_UNCHECKED;<br />
<br />
		VERIFY(popup.AppendMenu(nFlags, ID_POPUP_SHOW_HINT, _T("Show Hint\tF2")));<br />
		VERIFY(popup.AppendMenu(MF_STRING, ID_POPUP_REMOVE_ALL_HINTS, _T("Remove All Hints")));<br />
		VERIFY(popup.AppendMenu(MF_SEPARATOR));<br />
<br />
		nFlags = MF_STRING;<br />
		if (m_nUserEntries[row][col] == 0)<br />
			nFlags |= MF_DISABLED | MF_GRAYED;<br />
		VERIFY(popup.AppendMenu(nFlags, ID_POPUP_REMOVE_USER_ENTRY, _T("Remove User Entry\t0")));<br />
		VERIFY(popup.AppendMenu(MF_STRING, ID_POPUP_REMOVE_ALL_USER_ENTRIES, _T("Remove All User Entries")));<br />
		VERIFY(popup.AppendMenu(MF_STRING, ID_POPUP_CHECK_USER_ENTRIES, _T("Check User Entries")));<br />
		VERIFY(popup.AppendMenu(MF_SEPARATOR));<br />
<br />
		nFlags = MF_STRING;<br />
		if (m_bShowSolution)<br />
			nFlags |= MF_CHECKED;<br />
		else<br />
			nFlags |= MF_UNCHECKED;<br />
		VERIFY(popup.AppendMenu(nFlags, ID_POPUP_SHOW_SOLUTION, _T("Show Solution\tF3")));<br />
<br />
		nFlags = MF_STRING;<br />
		if (m_bPencilMarks)<br />
			nFlags |= MF_CHECKED;<br />
		else<br />
			nFlags |= MF_UNCHECKED;<br />
		VERIFY(popup.AppendMenu(nFlags, ID_POPUP_SHOW_PENCIL_MARKS, _T("Show Pencil Marks\tF4")));<br />
<br />
		VERIFY(popup.AppendMenu(MF_STRING, ID_POPUP_RESET, _T("Reset")));<br />
		VERIFY(popup.AppendMenu(MF_SEPARATOR));<br />
<br />
		VERIFY(popup.AppendMenu(MF_STRING, ID_POPUP_PRINT, _T("Print Window...\tCtrl+P")));<br />
		VERIFY(popup.AppendMenu(MF_STRING, ID_POPUP_COPY, _T("Copy Window to Clipboard")));<br />
		VERIFY(popup.AppendMenu(MF_SEPARATOR));<br />
<br />
		for (i = 0; i < 9; i++)<br />
		{<br />
			nFlags = MF_STRING;<br />
			if (m_nHighlightNumber == (i+1))<br />
				nFlags |= MF_CHECKED;<br />
			else<br />
				nFlags |= MF_UNCHECKED;<br />
<br />
			str.Format(_T("Highlight %d\tCtrl+%d"), i+1, i+1);<br />
<br />
			VERIFY(popup.AppendMenu(nFlags, ID_POPUP_HIGHLIGHT_1+i, str));<br />
		}<br />
<br />
		VERIFY(popup.AppendMenu(MF_SEPARATOR));<br />
		VERIFY(popup.AppendMenu(MF_STRING, ID_APP_ABOUT, _T("About XSudokuWndDll...\tCtrl+F1")));<br />
<br />
		// display the menu<br />
<br />
		CPoint pt = point;<br />
		ClientToScreen(&pt);<br />
		VERIFY(popup.TrackPopupMenu(TPM_LEFTALIGN | TPM_LEFTBUTTON, pt.x+1, pt.y+1, this));<br />
	}<br />
}<br />
<br />
///////////////////////////////////////////////////////////////////////////////<br />
//<br />
// popup memu handlers<br />
//<br />
///////////////////////////////////////////////////////////////////////////////<br />
<br />
void CXSudokuWnd::OnRemoveUserEntry()<br />
{<br />
	int row, col;<br />
<br />
	if (CellFromPoint(m_pointPopup, row, col))<br />
	{<br />
		AddUndoAction(row, col, ACTION_CODE_USER_ENTRY, 0);<br />
		m_nUserEntries[row][col] = 0;<br />
		Invalidate(FALSE);<br />
	}<br />
}<br />
<br />
void CXSudokuWnd::OnRemoveAllUserEntries()<br />
{<br />
	AddUndoAction(m_nCurRow, m_nCurCol, ACTION_CODE_USER_ENTRY_REMOVE_ALL, 0);<br />
<br />
	for (int i = 0; i < 9; i++)<br />
	{<br />
		for (int j = 0; j < 9; j++)<br />
		{<br />
			m_nUserEntries[i][j] = 0;<br />
		}<br />
	}<br />
	Invalidate(FALSE);<br />
}<br />
<br />
void CXSudokuWnd::OnCheckUserEntries()<br />
{<br />
	SWITCH_RESOURCE;		// load string from dll resource<br />
<br />
	CString str = _T("");<br />
	CString strColumns = _T("");<br />
	VERIFY(strColumns.LoadString(IDS_COLUMN_LABELS));<br />
<br />
	CString strFormat = _T("");<br />
	VERIFY(strFormat.LoadString(IDS_USER_ENTRY));<br />
	CString strIncorrect = _T("");<br />
	VERIFY(strIncorrect.LoadString(IDS_INCORRECT));<br />
	strFormat += strIncorrect;<br />
	strFormat += _T(".\r\n");<br />
<br />
	CString strMessage = _T("");<br />
	int nUserEntries = 0;<br />
<br />
	for (int i = 0; i < 9; i++)<br />
	{<br />
		for (int j = 0; j < 9; j++)<br />
		{<br />
			if (m_nUserEntries[i][j] != 0)<br />
			{<br />
				nUserEntries++;<br />
				if (m_nUserEntries[i][j] != m_nSolution[i][j])<br />
				{<br />
					strMessage.Format(strFormat, m_nUserEntries[i][j], i+1, strColumns[j]);<br />
					str += strMessage;<br />
				}<br />
			}<br />
		}<br />
	}<br />
<br />
	if (str.IsEmpty())<br />
	{<br />
		if (nUserEntries == 0)<br />
			AfxMessageBox(IDS_NO_USER_ENTRY);<br />
		else<br />
			AfxMessageBox(IDS_USER_ENTRY_OK, MB_ICONINFORMATION);<br />
	}<br />
	else<br />
	{<br />
		AfxMessageBox(str);<br />
	}<br />
}<br />
<br />
// called by accelerator<br />
void CXSudokuWnd::OnShowHint()<br />
{<br />
	if ((m_nCurRow != -1) && (m_nCurCol != -1))<br />
	{<br />
		if (m_bHaveGivens)<br />
		{<br />
			int nHint = (m_nHints[m_nCurRow][m_nCurCol] == 0) ?<br />
							m_nSolution[m_nCurRow][m_nCurCol] : 0;<br />
<br />
			AddUndoAction(m_nCurRow, m_nCurCol, ACTION_CODE_HINT, nHint);<br />
<br />
			m_nHints[m_nCurRow][m_nCurCol] = nHint;<br />
<br />
			Invalidate(FALSE);<br />
		}<br />
	}<br />
}<br />
<br />
// called by popup menu<br />
void CXSudokuWnd::OnPopupShowHint()<br />
{<br />
	int row, col;<br />
<br />
	if (m_bHaveGivens && CellFromPoint(m_pointPopup, row, col))<br />
	{<br />
			int nHint = (m_nHints[row][col] == 0) ?<br />
							m_nSolution[row][col] : 0;<br />
<br />
			AddUndoAction(row, col, ACTION_CODE_HINT, nHint);<br />
<br />
			m_nHints[row][col] = nHint;<br />
<br />
		Invalidate(FALSE);<br />
	}<br />
}<br />
<br />
void CXSudokuWnd::OnRemoveAllHints()<br />
{<br />
	AddUndoAction(m_nCurRow, m_nCurCol, ACTION_CODE_HINT_REMOVE_ALL, 0);<br />
<br />
	for (int i = 0; i < 9; i++)<br />
	{<br />
		for (int j = 0; j < 9; j++)<br />
		{<br />
			m_nHints[i][j] = 0;<br />
		}<br />
	}<br />
	Invalidate(FALSE);<br />
}<br />
<br />
void CXSudokuWnd::OnReset()<br />
{<br />
	SWITCH_RESOURCE;		// load string from dll resource<br />
<br />
	int rc = AfxMessageBox(IDS_RESET, MB_YESNO|MB_ICONQUESTION);<br />
<br />
	if (rc == IDYES)<br />
	{<br />
		Reset(FALSE);<br />
		Invalidate(FALSE);<br />
	}<br />
}<br />
<br />
void CXSudokuWnd::OnPrintWindow()<br />
{<br />
	PrintBitmap();<br />
}<br />
<br />
void CXSudokuWnd::OnCopyWindow()<br />
{<br />
	CaptureBitmap();<br />
}<br />
<br />
void CXSudokuWnd::OnShowSolution()<br />
{<br />
	m_bShowSolution = !m_bShowSolution;<br />
	SetShowSolution(m_bShowSolution);<br />
}<br />
<br />
void CXSudokuWnd::OnShowPencilMarks()<br />
{<br />
	m_bPencilMarks = !m_bPencilMarks;<br />
	SetShowPencilMarks(m_bPencilMarks);<br />
}<br />
<br />
void CXSudokuWnd::OnPopupSet(UINT nID)<br />
{<br />
	SWITCH_RESOURCE;		// load string from dll resource<br />
<br />
	int nValue = nID - ID_POPUP_SET_1 + 1;<br />
<br />
	int row, col;<br />
<br />
	if (CellFromPoint(m_pointPopup, row, col))<br />
	{<br />
		AddUndoAction(row, col, ACTION_CODE_USER_ENTRY, nValue);<br />
<br />
		m_nUserEntries[row][col] = nValue;<br />
<br />
		CString str = _T("");<br />
		CString strColumns = _T("");<br />
		VERIFY(strColumns.LoadString(IDS_COLUMN_LABELS));<br />
		str.Format(IDS_USER_ENTRY, nValue, row+1, strColumns[col]);<br />
		int nType = 0;<br />
<br />
		CString strCorrect = _T("");<br />
		if (nValue == m_nSolution[row][col])<br />
		{<br />
			VERIFY(strCorrect.LoadString(IDS_CORRECT));<br />
		}<br />
		else<br />
		{<br />
			VERIFY(strCorrect.LoadString(IDS_INCORRECT));<br />
			nType = 1;<br />
		}<br />
		str += strCorrect;<br />
<br />
		if (m_hWndMessage && ::IsWindow(m_hWndMessage))<br />
			::SendMessage(m_hWndMessage, WM_XSUDOKU, nType,<br />
				(LPARAM)(LPCTSTR)str);<br />
<br />
		Invalidate(FALSE);<br />
	}<br />
}<br />
<br />
void CXSudokuWnd::OnPopupUnPencil(UINT nID)	//Rub Out a nID valu<br />
{<br />
	SWITCH_RESOURCE;		// load string from dll resource<br />
<br />
	int nValue = nID - ID_POPUP_UNPENCIL_1 + 1;<br />
<br />
	int row, col;<br />
<br />
	if (CellFromPoint(m_pointPopup, row, col))<br />
	{<br />
		AddUndoAction(row, col, ACTION_CODE_USER_ENTRY, nValue);<br />
		SetBit(m_nUnPencil[row][col],nValue);<br />
		Invalidate(FALSE);<br />
	}<br />
}<br />
<br />
void CXSudokuWnd::OnPopupPencil(UINT nID)	//Pencil in a value (nID) that had previously been Rubbed Out<br />
{<br />
	SWITCH_RESOURCE;		// load string from dll resource<br />
<br />
	int nValue = nID - ID_POPUP_PENCIL_1 + 1;<br />
<br />
	int row, col;<br />
<br />
	if (CellFromPoint(m_pointPopup, row, col))<br />
	{<br />
		AddUndoAction(row, col, ACTION_CODE_USER_ENTRY, nValue);<br />
		int nBit = 0;<br />
		SetBit(nBit,nValue);<br />
		m_nUnPencil[row][col]&=~nBit;<br />
		Invalidate(FALSE);<br />
	}<br />
}<br />
<br />
// called by popup menu<br />
void CXSudokuWnd::OnPopupHighlight(UINT nID)<br />
{<br />
	int nHighlightNumber = nID - ID_POPUP_HIGHLIGHT_1 + 1;<br />
<br />
	if (m_nHighlightNumber == nHighlightNumber)<br />
		m_nHighlightNumber = 0;<br />
	else<br />
		m_nHighlightNumber = nHighlightNumber;<br />
<br />
	Invalidate(FALSE);<br />
}<br />
<br />
// called by accelerator<br />
void CXSudokuWnd::OnHighlight(UINT nID)<br />
{<br />
	int nHighlightNumber = nID - ID_HIGHLIGHT_0;<br />
<br />
	if (nHighlightNumber == 0)<br />
		m_nHighlightNumber = 0;<br />
	else if (m_nHighlightNumber == nHighlightNumber)<br />
		m_nHighlightNumber = 0;<br />
	else<br />
		m_nHighlightNumber = nHighlightNumber;<br />
<br />
	Invalidate(FALSE);<br />
}<br />
<br />
// accelerator key Shift+F10<br />
void CXSudokuWnd::OnRightClick()<br />
{<br />
	CRect rectCell = GetCellRect(m_nCurRow, m_nCurCol);<br />
	CPoint point;<br />
	point.x = rectCell.left + rectCell.Width()/2;<br />
	point.y = rectCell.top + rectCell.Height()/2;<br />
	ShowPopup(point);<br />
}<br />
<br />
void CXSudokuWnd::OnColorPrefs()<br />
{<br />
	ShowColorPrefsDlg();<br />
}<br />
<br />
// debug hack - F9<br />
void CXSudokuWnd::OnLoadSample()<br />
{<br />
#ifdef _DEBUG<br />
	CString strSudoku = _T("708000300000201000500000000040000026300080000000100090090600004000070500000000000");<br />
	LoadFromString(strSudoku, strSudoku.GetLength());<br />
	Invalidate(FALSE);<br />
#endif<br />
}<br />
<br />
void CXSudokuWnd::OnEditUndo()<br />
{<br />
	TRACE(_T("in CXSudokuWnd::OnEditUndo\n"));<br />
<br />
	if (!m_bEnableUndo)<br />
		return;<br />
<br />
	if (m_nUndoIndex > 0)<br />
	{<br />
		int index = m_nUndoIndex - 1;<br />
		UNDO_BLOCK * pUB = (UNDO_BLOCK *) m_Undo[index];<br />
		ASSERT(pUB);<br />
		if (pUB)<br />
		{<br />
			m_nCurRow = (int) pUB->row;<br />
			m_nCurCol = (int) pUB->col;<br />
			int i, j;<br />
			for (i = 0; i < 9; i++)<br />
			{<br />
				for (j = 0; j < 9; j++)<br />
				{<br />
					m_nHints[i][j] = (int) pUB->hints[i][j];<br />
					m_nUserEntries[i][j] = (int) pUB->user_entries[i][j];<br />
				}<br />
			}<br />
<br />
#ifdef _DEBUG<br />
			CString str = ActionCodeToString(pUB->action_code);<br />
			if (!str.IsEmpty())<br />
			{<br />
				TRACE(_T("undoing action '%s'\n"), str);<br />
			}<br />
#endif<br />
<br />
			m_nUndoIndex--;<br />
			m_bShowSolution = FALSE;<br />
			Invalidate(FALSE);<br />
		}<br />
	}<br />
}<br />
<br />
void CXSudokuWnd::OnEditRedo()<br />
{<br />
	TRACE(_T("in CXSudokuWnd::OnEditRedo\n"));<br />
<br />
	if (!m_bEnableUndo)<br />
		return;<br />
<br />
	if (m_nUndoIndex < m_nUndoLastEntry)<br />
	{<br />
		int index = m_nUndoIndex;<br />
		UNDO_BLOCK * pUB = (UNDO_BLOCK *) m_Undo[index];<br />
		ASSERT(pUB);<br />
		if (pUB)<br />
		{<br />
			m_nCurRow = (int) pUB->row;<br />
			m_nCurCol = (int) pUB->col;<br />
			int i, j;<br />
<br />
			switch (pUB->action_code)<br />
			{<br />
				case 0:		// unused entry<br />
					break;<br />
<br />
				case ACTION_CODE_USER_ENTRY:<br />
					m_nUserEntries[m_nCurRow][m_nCurCol] = pUB->action_value;<br />
					break;<br />
<br />
				case ACTION_CODE_USER_ENTRY_REMOVE_ALL:<br />
					for (i = 0; i < 9; i++)<br />
					{<br />
						for (j = 0; j < 9; j++)<br />
						{<br />
							m_nUserEntries[i][j] = 0;<br />
						}<br />
					}<br />
					break;<br />
<br />
				case ACTION_CODE_HINT:<br />
					m_nHints[m_nCurRow][m_nCurCol] = pUB->action_value;<br />
					break;<br />
<br />
				case ACTION_CODE_HINT_REMOVE_ALL:<br />
					for (i = 0; i < 9; i++)<br />
					{<br />
						for (j = 0; j < 9; j++)<br />
						{<br />
							m_nHints[i][j] = 0;<br />
						}<br />
					}<br />
					break;<br />
<br />
				default:<br />
					break;<br />
			}<br />
<br />
#ifdef _DEBUG<br />
			CString str = ActionCodeToString(pUB->action_code);<br />
			if (!str.IsEmpty())<br />
			{<br />
				TRACE(_T("redoing undo action '%s'\n"), str);<br />
			}<br />
#endif<br />
<br />
			m_nUndoIndex++;<br />
			m_bShowSolution = FALSE;<br />
			Invalidate(FALSE);<br />
		}<br />
	}<br />
}<br />
<br />
void CXSudokuWnd::OnAppAbout()<br />
{<br />
	SWITCH_RESOURCE;		// load dialog from dll resource<br />
	CAboutDlg dlg;<br />
	dlg.DoModal();<br />
}<br />

GeneralRe: Great Solution Pin
Hans Dietrich24-Apr-06 6:08
mentorHans Dietrich24-Apr-06 6:08 
GeneralRe: Great Solution Pin
Graham Shanks26-Apr-06 13:23
Graham Shanks26-Apr-06 13:23 
GeneralRe: Great Solution Pin
OLEDB_Novice29-Jun-06 0:37
OLEDB_Novice29-Jun-06 0:37 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.