解数独,回溯法即可。从左到右,从上到下,检查每种数字是否可以放进格子,如果可以就放入,一直搜索直到解数独完成。
class Solution{
public:
bool finish = false;
bool check(vector<vector<char> >& a,int i,int j,char k)
{
for(int p = 0; p < 9; p++)
if(a[p][j] == k || a[i][p] == k)
return false;
for(int p = i/3*3; p < i/3*3+3; p++)
for(int q = j/3*3; q < j/3*3+3; q++)
if(a[p][q] == k)
return false;
return true;
}
void solve(vector<vector<char> >& board,int i = 0, int j = 0)
{
if( j == 9 )
i++, j = 0;
if( i == 9 )
{
finish = true;
return;
}
if( board[i][j] == '.' )
{
for(char k = '1'; k <= '9'; k++) {
if(check(board,i,j,k))
{
board[i][j] = k;
solve(board,i,j+1);
if(finish == true)
return;
board[i][j] = '.';
}
}
}
else
solve(board,i,j+1);
}
void solveSudoku(vector<vector<char> >& board) {
solve(board);
finish = false;
}
};
既然数据部分有了,就简单写个界面来调试一下。由于绘图内容简单,直接用windows.h里的函数来画,最终大概是这个样子。
记录一下核心代码,以后怕是不想用windows.h来写了。
原来的解数独面板是字符串的,在改动后做成纯数字面板,遇到无效数独会异常退出,所以需要加个有效数独的检测。
游戏类,所有部件都放在这里了。
class SudokuGame
{
public:
SudokuGame();
~SudokuGame();
void drawBoard(HDC hdc);
void drawDigit(HDC hdc,int num,int i,int j);
void drawData(HDC hdc);
void clearData(HDC hdc);
void calculateAndPrint(HDC hdc);
void lButtonDownEvent(HWND& hwnd,HDC hdc);
void keyDownEvent(HDC hdc,int input);
bool waitInput();
void showHelpWindow();
void setWindowTop(HWND hwnd);
private:
void drawCurBase(HDC hdc);
void drawLine(HDC hdc,int x,int y,int target_x,int target_y);
void drawCurLine(HDC hdc);
void clearCurLine(HDC hdc);
POINT clickpToInputp(POINT);
bool PointInBoard(POINT);
void initFont(HDC hdc);
int data[9][9];
int block_size;
int board_x_offset, board_y_offset; //棋盘左上角的偏移量
int digit_x_offset, digit_y_offset; //格子里数字的偏移量
int sum_x_offset, sum_y_offset;
int cur_line_length;
bool can_input, is_top;
HFONT hfont;
POINT click_point, input_point;
SudokuSolve* solver;
};
构造函数和析构函数,负责变量的初始化。
#define COLOR_BOARD_BK RGB(219,210,106)
#define COLOR_BOARD_LINE RGB(0,0,0)
SudokuGame::SudokuGame()
: block_size(60), can_input(false), is_top(false), cur_line_length(block_size/3*2),
digit_x_offset(20), digit_y_offset(10),
board_x_offset(20), board_y_offset(30),
sum_x_offset(digit_x_offset+board_x_offset),
sum_y_offset(digit_y_offset+board_y_offset)
{
solver = new SudokuSolve();
memset(data,0,sizeof(data));
}
SudokuGame::~SudokuGame()
{
delete solver;
}
画线函数
void SudokuGame::drawLine(HDC hdc,int x,int y,int target_x,int target_y)
{
MoveToEx(hdc,x,y,0);
LineTo(hdc,target_x,target_y);
}
绘制棋盘格子
void SudokuGame::drawBoard(HDC hdc)
{
initFont(hdc);
SetBkColor(hdc,COLOR_BOARD_BK);
int width = block_size*9, height = block_size*9;
//画棋盘
int left = board_x_offset, right = width+board_x_offset;
int top = board_y_offset, bottom = height+board_y_offset;
RECT rect = { left,top,right,bottom };
FillRect(hdc,&rect,CreateSolidBrush(COLOR_BOARD_BK));
//画横竖线
for(int i = top; i <= bottom; i += block_size)
drawLine(hdc,left,i,right,i);
for(int i = left; i <= right; i += block_size)
drawLine(hdc,i,top,i,bottom);
//画粗分隔线
HPEN pen = CreatePen(0,3,RGB(0,0,0));
HPEN oldpen = (HPEN)SelectObject(hdc,pen);
for(int i = top; i <= bottom; i += block_size*3)
drawLine(hdc,left,i,right,i);
for(int i = left; i <= right; i += block_size*3)
drawLine(hdc,i,top,i,bottom);
SelectObject(hdc,oldpen);
}
绘制数字部分
void SudokuGame::drawDigit(HDC hdc,int num,int i,int j)
{
int digit_x = i*block_size+sum_x_offset;
int digit_y = j*block_size+sum_y_offset;
if( num >= 10 || num < 0 || i < 0 || i >= 9 || j < 0 || j >= 9 ) return;
char output[2];
output[0] = num + '0';
output[1] = '\0';
if( num )
TextOut(hdc,digit_x,digit_y,output,1);
else
TextOut(hdc,digit_x,digit_y," ",1);
}
void SudokuGame::drawData(HDC hdc)
{
for(int i = 0; i < 9; i++)
for(int j = 0; j < 9; j++)
drawDigit(hdc,data[i][j],i,j);
}
WinMain部分
SudokuGame* game;
//WinMain函数中
game = new SudokuGame();
hdc = GetDC(hwnd);
game->drawBoard(hdc);
字体部分
void SudokuGame::initFont(HDC hdc)
{
hfont = CreateFont( 40, 0,
0, 0,
FW_NORMAL,
0,0,0,
GB2312_CHARSET,
OUT_CHARACTER_PRECIS,
CLIP_DEFAULT_PRECIS,
DEFAULT_QUALITY,
DEFAULT_PITCH |FF_DONTCARE,
"sudokuFont"
);
SelectObject(hdc,hfont);
}
回调函数部分,省略了最大最小化重绘的代码。
LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_LBUTTONDOWN:
game->lButtonDownEvent(hwnd,hdc);
break;
case WM_RBUTTONDOWN:
game->clearData(hdc);
break;
case WM_KEYDOWN:
if( GetAsyncKeyState('T') )
game->setWindowTop(hwnd);
else if( game->waitInput() )
game->keyDownEvent(hdc,wParam);
break;
case WM_DESTROY:
PostQuitMessage (0);
break;
default:
return DefWindowProc (hwnd, message, wParam, lParam);
}
回调函数响应函数
POINT SudokuGame::clickpToInputp(POINT p)
{
POINT res = { p.x-board_x_offset, p.y-board_y_offset };
res.x = res.x >= 0 ? res.x / block_size : -1;
res.y = res.y >= 0 ? res.y / block_size : -1;
return res;
}
void SudokuGame::lButtonDownEvent(HWND& hwnd,HDC hdc)
{
if( can_input )
clearCurLine(hdc);
GetCursorPos(&click_point);
ScreenToClient(hwnd,&click_point);
input_point = clickpToInputp(click_point);
drawCurLine(hdc);
can_input = true;
}
void SudokuGame::keyDownEvent(HDC hdc,int input)
{
if( input < 48 || input > 57) return;
clearCurLine(hdc);
if( input_point.x == -1 || input_point.y == -1 ) return;
drawDigit(hdc,input-48,input_point.x,input_point.y);
data[input_point.x][input_point.y] = input-48;
}
主要的处理代码就这么多,剩下一些没什么大影响的菜单部分。
本以为做个调试窗口能快速搞定的,但写了好久还只完成了基础的功能,代码也有很多需要修改的部分,还要多学习呢。