|
Source listing: echo $file; ?>. Return to
the main page
/* -*- C++ -*-
*******************************************************************
*
*
* Shisen-Sho for Windows
*
* board.cpp - Implementation of the Board class
*
*
*******************************************************************
*
* A japanese game similar to mahjongg
*
*******************************************************************
*
* Created 2000-12-28 by Jim Mason <jmason@sirius.com>
*
* Game engine ported from Mario Weilguni's <mweilguni@sime.com>
* original game for KDE, KSHISEN. If you are interested in the
* KDE version of the game, see http://www.kde.org/kdegames/
*
*******************************************************************
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
*******************************************************************
*/
#include "stdafx.h"
#include "shisen.h"
#include "board.h"
#include "bmputil.h"
#define EMPTY 0
#define XBORDER 20
#define YBORDER 20
#define DEFAULTDELAY 500
#define DEFAULTSHUFFLE 4
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////////////
// Board
Board::Board() {
m_bNoEvents = FALSE;
m_hOldTiles = NULL;
m_hBmpPaused = NULL;
paused = FALSE;
trying = FALSE;
_solvable_flag = TRUE;
grav_col_1 = -1;
grav_col_2 = -1;
setGravityFlag(FALSE);
// randomize
srand((unsigned)time(NULL));
starttime = time((time_t *)0);
setDelay(DEFAULTDELAY);
field = 0;
highlighted_tile = -1;
m_nTimer = 0;
}
BOOL Board::PreCreateWindow(CREATESTRUCT& cs) {
cs.dwExStyle |= WS_EX_CLIENTEDGE;
cs.style &= ~WS_BORDER;
// Register the class if it does not exist
WNDCLASSEX wcex;
if(!GetClassInfoEx(App::GetHInstance(), cs.lpszClass, &wcex)) {
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = NULL; // Window::RegisterClassEx will set this
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = App::GetHInstance();
wcex.hIcon = NULL;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = cs.lpszClass;
wcex.hIconSm = NULL;
return RegisterClassEx(&wcex);
}
return TRUE;
}
int Board::x_tiles() {
return _x_tiles;
}
int Board::y_tiles() {
return _y_tiles;
}
void Board::setField(int x, int y, int value) {
if(x < x_tiles() && y < y_tiles())
field[y * x_tiles() + x] = value;
else {
CStdString s;
s.Format(IDS_INVALID_FIELD, x, y);
fprintf(stderr, (LPCSTR)s);
exit(1);
}
}
int Board::getField(int x, int y) {
if(x == x_tiles() || x == -1)
return EMPTY;
else if(y == y_tiles() || y == -1)
return EMPTY;
else if(x < -1 || x > x_tiles() || y < -1 || y > y_tiles()) {
return EMPTY;
} else
return field[y * x_tiles() + x];
}
void Board::setSize(int x, int y) {
if(x == x_tiles() && y == y_tiles())
return;
if(field != 0)
free(field);
field = (int*)malloc(sizeof(int) * x * y);
_x_tiles = x;
_y_tiles = y;
for(int i = 0; i < x; i++)
for(int j = 0; j < y; j++)
setField(i, j, EMPTY);
newGame();
}
void Board::newGame() {
int i, j, x, y, k;
mark_x = -1;
mark_y = -1;
while(_undo.size())
_undo.pop_front();
while(_redo.size())
_redo.pop_front();
clearHistory();
for(i = 0; i < x_tiles(); i++)
for(j = 0; j < y_tiles(); j++)
setField(i, j, EMPTY);
// distribute all tiles on board
int cur_tile = 0;
for(i = 0; i < x_tiles() * y_tiles() * 12; i++) {
// map the tileindex to a tile
// not all tiles from the pixmap are really used, only
// 36 out of 45 are used. This maps and index to
// the "real" index.
int tile;
if(m_bUniqueSeasons) {
// Include two seasons and all flowers
if(cur_tile == 28)
tile = 30;
else if(cur_tile >= 29 && cur_tile <= 35)
tile = cur_tile + 7;
else
tile = cur_tile;
cur_tile++;
if(cur_tile == 36)
cur_tile = 0;
} else {
// Include only one season and one flower
if(cur_tile >= 28 && cur_tile <= 31)
tile = cur_tile + 8;
else
tile = cur_tile;
cur_tile++;
if(cur_tile == 32)
cur_tile = 0;
}
x = i % x_tiles();
y = i / x_tiles() * 4;
tile++;
for(k = 0; k < 4 && k + y < y_tiles(); k++)
setField(x, y+k, tile);
}
if(getShuffle() == 0) {
if(!trying) {
if(m_hWnd)
InvalidateRect(NULL, FALSE);
starttime = time((time_t *)0);
}
return;
}
// shuffle the field
int tx = x_tiles();
int ty = y_tiles();
for(i = 0; i < x_tiles() * y_tiles() * getShuffle(); i++) {
int x1 = random(tx);
int y1 = random(ty);
int x2 = random(tx);
int y2 = random(ty);
int t = getField(x1, y1);
setField(x1, y1, getField(x2, y2));
setField(x2, y2, t);
}
// do not make solvable if _solvable_flag is FALSE
if(!_solvable_flag) {
if(!trying) {
if(m_hWnd)
InvalidateRect(NULL, FALSE);
starttime = time((time_t *)0);
}
return;
}
// rearrange tiles so game is solvable
int fsize = x_tiles() * y_tiles() * sizeof(int);
int *oldfield = new int[x_tiles() * y_tiles()];
memcpy(oldfield, field, fsize); // save field
int *tiles = new int[x_tiles() * y_tiles()];
int *pos = new int[x_tiles() * y_tiles()];
while(!solvable(TRUE)) {
// generate a list of free tiles and positions
int num_tiles = 0;
for(i = 0; i < x_tiles() * y_tiles(); i++)
if(field[i] != EMPTY) {
pos[num_tiles] = i;
tiles[num_tiles] = field[i];
num_tiles++;
}
// restore field
memcpy(field, oldfield, fsize);
// redistribute unsolved tiles
while(num_tiles > 0) {
// get a random tile
int r1 = random(num_tiles);
int r2 = random(num_tiles);
int tile = tiles[r1];
int apos = pos[r2];
// truncate list
tiles[r1] = tiles[num_tiles-1];
pos[r2] = pos[num_tiles-1];
num_tiles--;
// put this tile on the new position
field[apos] = tile;
}
// remember field
memcpy(oldfield, field, fsize);
}
// restore field
memcpy(field, oldfield, fsize);
delete tiles;
delete pos;
delete oldfield;
if(!trying) {
if(m_hWnd)
InvalidateRect(NULL, FALSE);
starttime = time((time_t *)0);
}
}
void Board::updateField(int x, int y, BOOL bErase) {
if(trying)
return;
// Add the specified tile to the update region
// This allows invalidated tiles to accumulate
// and be repainted together on the next WM_PAINT.
// Note that if an immediate repaint is required,
// you must call UpdateWindow() immediately after
// updateField().
RECT r;
int x0 = XBORDER + x * m_tileWidth;
int y0 = YBORDER + y * m_tileHeight;
SetRect(&r, x0, y0,
x0 + m_tileWidth,
y0 + m_tileHeight);
InvalidateRect(&r, bErase);
}
int MIN(int a, int b) {
if(a < b)
return a;
else
return b;
}
int MAX(int a, int b) {
if(a > b)
return a;
else
return b;
}
void Board::loadMahjonggTileset(LPCSTR szTileset) {
// Release the current tileset
releaseTileset();
HBITMAP bmpTileSrc;
if(!strlen(szTileset) || !CBmpUtil::LoadBmpImage(szTileset, &bmpTileSrc)) {
loadDefaultTileset();
return;
}
// Initialize new tileset bitmaps. We create two bitmaps: one for
// the unselected tiles, and one for the tiles in the selected state.
BITMAP bm;
HDC dcTemp;
HBITMAP hBmpTemp;
HDC hDC = GetDC();
GetObject(bmpTileSrc, sizeof(BITMAP), &bm);
m_bmpTiles = CreateCompatibleBitmap(hDC, bm.bmWidth, bm.bmHeight);
m_bmpTilesSel = CreateCompatibleBitmap(hDC, bm.bmWidth, bm.bmHeight);
dcTemp = CreateCompatibleDC(hDC);
hBmpTemp = (HBITMAP)SelectObject(dcTemp, m_bmpTilesSel);
ReleaseDC(hDC);
m_tileWidth = bm.bmWidth / 9;
m_tileHeight = bm.bmHeight / 5;
initCacheDC();
m_bUniqueSeasons = FALSE;
// Erase background
RECT rcTiles;
SetRect(&rcTiles, 0, 0, bm.bmWidth, bm.bmHeight);
SetBkColor(m_dcTiles, RGB(0,0,0));
ExtTextOut(m_dcTiles, 0, 0, ETO_OPAQUE, &rcTiles, NULL, 0, NULL);
BitBlt(dcTemp, 0, 0, bm.bmWidth, bm.bmHeight, m_dcTiles, 0, 0, SRCCOPY);
// Render first tile background
// Unselected background is at position 43; selected background is at 44
CBmpUtil::DrawBitmap(m_dcTiles, bmpTileSrc, 0, 0,
m_tileWidth, m_tileHeight,
(43%9)*m_tileWidth, (43/9)*m_tileHeight);
CBmpUtil::DrawBitmap(dcTemp, bmpTileSrc, 0, 0,
m_tileWidth, m_tileHeight,
(44%9)*m_tileWidth, (44/9)*m_tileHeight);
// Replicate background to all tile positions
for(int i = 0; i < 9; i++)
for(int j = 0; j < 5; j++) {
if((i == 0) && (j == 0))
continue;
BitBlt(m_dcTiles, i*m_tileWidth, j*m_tileHeight,
m_tileWidth, m_tileHeight,
m_dcTiles,
0, 0, SRCCOPY);
BitBlt(dcTemp, i*m_tileWidth, j*m_tileHeight,
m_tileWidth, m_tileHeight,
dcTemp,
0, 0, SRCCOPY);
}
// Render the tile faces
for(int ii = 0; ii < 9; ii++)
for(int jj = 0; jj < 5; jj++) {
CBmpUtil::DrawBitmap(m_dcTiles, bmpTileSrc,
ii*m_tileWidth+5, jj*m_tileHeight+1,
m_tileWidth-6, m_tileHeight-6,
ii*m_tileWidth+5, jj*m_tileHeight+1);
CBmpUtil::DrawBitmap(dcTemp, bmpTileSrc,
ii*m_tileWidth+5, jj*m_tileHeight+1,
m_tileWidth-6, m_tileHeight-6,
ii*m_tileWidth+5, jj*m_tileHeight+1);
}
// Restore the temp DC
SelectObject(dcTemp, hBmpTemp);
DeleteDC(dcTemp);
DeleteObject(bmpTileSrc);
}
void Board::loadDefaultTileset() {
// Setup the default tileset
BITMAP bm;
m_bmpTiles = LoadBitmap(App::GetHInstance(), MAKEINTRESOURCE(IDB_SHISEN));
GetObject(m_bmpTiles, sizeof(BITMAP), &bm);
m_tileWidth = bm.bmWidth / 9;
m_tileHeight = bm.bmHeight / 5;
initCacheDC();
// Create the default "selected" tileset
//
// The default selected tileset consists of the default
// tileset or'ed with a highlight brush.
HDC dcTemp;
HBITMAP hOldBmp;
dcTemp = CreateCompatibleDC(m_dcTiles);
m_bmpTilesSel = CreateCompatibleBitmap(m_dcTiles, bm.bmWidth, bm.bmHeight);
hOldBmp = (HBITMAP)SelectObject(dcTemp, m_bmpTilesSel);
// Create the highlight brush
HBRUSH brush;
brush = CreateSolidBrush(RGB(48, 48, 48));
HBRUSH hOldBrush = (HBRUSH)SelectObject(dcTemp, brush);
// Blit the tiles into the highlight DC
BitBlt(dcTemp, 0, 0, bm.bmWidth, bm.bmHeight, m_dcTiles,
0, 0,
/*PSo*/0x00FC008A);
// Restore the temp DC
SelectObject(dcTemp, hOldBrush);
DeleteObject(brush);
SelectObject(dcTemp, hOldBmp);
DeleteDC(dcTemp);
m_bUniqueSeasons = TRUE;
}
void Board::initCacheDC() {
// Setup the cached tile DC for fast blitting
HDC hDC = GetDC();
m_dcTiles = CreateCompatibleDC(hDC);
m_hOldTiles = (HBITMAP)SelectObject(m_dcTiles, m_bmpTiles);
ReleaseDC(hDC);
}
void Board::releaseTileset() {
// Release GDI resources
if(m_hOldTiles) {
SelectObject(m_dcTiles, m_hOldTiles);
DeleteObject(m_bmpTiles);
DeleteObject(m_bmpTilesSel);
DeleteDC(m_dcTiles);
m_hOldTiles = NULL;
}
}
void Board::loadBackground(LPCSTR szBackground) {
// Release the current background
releaseBackground();
if(strlen(szBackground) && CBmpUtil::LoadBmpImage(szBackground, &m_bmpBkgnd)) {
BITMAP bm;
GetObject(m_bmpBkgnd, sizeof(BITMAP), &bm);
m_sizeBkgnd.cx = bm.bmWidth;
m_sizeBkgnd.cy = bm.bmHeight;
} else
loadDefaultBackground();
}
void Board::loadDefaultBackground() {
BITMAP bm;
m_bmpBkgnd = LoadBitmap(App::GetHInstance(), MAKEINTRESOURCE(IDB_BGND));
GetObject(m_bmpBkgnd, sizeof(BITMAP), &bm);
m_sizeBkgnd.cx = bm.bmWidth;
m_sizeBkgnd.cy = bm.bmHeight;
}
void Board::releaseBackground() {
DeleteObject(m_bmpBkgnd);
}
void Board::eraseBackground(HDC hDC, RECT& rcUpdate) {
HDC memdc;
HBITMAP hBmpOld;
// Prepare the background bitmap
memdc = CreateCompatibleDC(hDC);
hBmpOld = (HBITMAP)SelectObject(memdc, m_bmpBkgnd);
// Tile the background bitmap into the DC
RECT rc;
GetClientRect(&rc);
// Lay it out from the origin so it is always in the same
// position regardless of where the update region is located.
for(int y=0; y < (rc.bottom - rc.top); y += m_sizeBkgnd.cy)
for(int x=0; x < (rc.right - rc.left); x += m_sizeBkgnd.cx) {
RECT ri, r;
SetRect(&r, x, y, x + m_sizeBkgnd.cx, y + m_sizeBkgnd.cy);
// Only bother to draw background if it is in the update region
if(IntersectRect(&ri, &rcUpdate, &r))
BitBlt(hDC, x-rcUpdate.left, y-rcUpdate.top,
m_sizeBkgnd.cx, m_sizeBkgnd.cy, memdc, 0, 0, SRCCOPY);
}
// Restore the memory DC
SelectObject(memdc, hBmpOld);
DeleteDC(memdc);
}
void Board::drawTile(HDC hDC, int iTile, int xpos, int ypos, BOOL bLighten) {
if(bLighten)
SelectObject(m_dcTiles, m_bmpTilesSel);
// Calculate location of the tile within the source
int x = iTile % 9;
int y = iTile / 9;
// Blit the bitmap into the DC
BitBlt(hDC, xpos, ypos, m_tileWidth, m_tileHeight, m_dcTiles,
x*m_tileWidth, y*m_tileHeight,
SRCCOPY);
if(bLighten)
SelectObject(m_dcTiles, m_bmpTiles);
}
// returns a random number < max
int Board::random(int max) {
return ::rand() % max; // don't depend on RAND_MAX...
}
void Board::marked(int x, int y) {
// make sure that the last arrow is correctly undrawn
undrawArrow();
if(getField(x, y) == EMPTY)
return;
if(x == mark_x && y == mark_y) {
// unmark the piece
mark_x = -1;
mark_y = -1;
updateField(x, y);
return;
}
if(mark_x == -1) {
mark_x = x;
mark_y = y;
updateField(x, y);
return;
} else {
int fld1 = getField(mark_x, mark_y);
int fld2 = getField(x, y);
// both field same?
if(fld1 != fld2)
return;
// trace
if(findPath(mark_x, mark_y, x, y)) {
madeMove(mark_x, mark_y, x, y);
drawArrow(mark_x, mark_y, x, y);
setField(mark_x, mark_y, EMPTY);
setField(x, y, EMPTY);
grav_col_1 = x;
grav_col_2 = mark_x;
mark_x = -1;
mark_y = -1;
int dummyx;
History dummyh[4];
// game is over?
if(!getHint_I(dummyx,dummyx,dummyx,dummyx,dummyh)) {
time_for_game = (int)time((time_t*)0) - starttime;
PostMessage(GetParent(m_hWnd), WM_END_OF_GAME, 0, 0);
}
} else
clearHistory();
}
}
BOOL Board::canMakePath(int x1, int y1, int x2, int y2) {
int i;
if(x1 == x2) {
for(i = MIN(y1, y2)+1; i < MAX(y1, y2); i++)
if(getField(x1, i) != EMPTY)
return FALSE;
return TRUE;
}
if(y1 == y2) {
for(i = MIN(x1, x2)+1; i < MAX(x1, x2); i++)
if(getField(i, y1) != EMPTY)
return FALSE;
return TRUE;
}
return FALSE;
}
BOOL Board::findPath(int x1, int y1, int x2, int y2) {
clearHistory();
if(findSimplePath(x1, y1, x2, y2))
return TRUE;
else {
// find 3-way path
int dx[4] = {1, 0, -1, 0};
int dy[4] = {0, 1, 0, -1};
int i;
for(i = 0; i < 4; i++) {
int newx = x1 + dx[i], newy = y1 + dy[i];
while(getField(newx, newy) == EMPTY &&
newx >= -1 && newx <= x_tiles() &&
newy >= -1 && newy <= y_tiles()) {
if(findSimplePath(newx, newy, x2, y2)) {
// make place for history point
for(int j = 3; j > 0; j--)
history[j] = history[j-1];
// insert history point
history[0].x = x1;
history[0].y = y1;
return TRUE;
}
newx += dx[i];
newy += dy[i];
}
}
clearHistory();
return FALSE;
}
return FALSE;
}
BOOL Board::findSimplePath(int x1, int y1, int x2, int y2) {
BOOL r = FALSE;
// find direct line
if(canMakePath(x1, y1, x2, y2)) {
history[0].x = x1;
history[0].y = y1;
history[1].x = x2;
history[1].y = y2;
r = TRUE;
} else {
if(!(x1 == x2 || y1 == y2)) // requires complex path
if(getField(x2, y1) == EMPTY &&
canMakePath(x1, y1, x2, y1) && canMakePath(x2, y1, x2, y2)) {
history[0].x = x1;
history[0].y = y1;
history[1].x = x2;
history[1].y = y1;
history[2].x = x2;
history[2].y = y2;
r = TRUE;
} else if(getField(x1, y2) == EMPTY &&
canMakePath(x1, y1, x1, y2) && canMakePath(x1, y2, x2, y2)) {
history[0].x = x1;
history[0].y = y1;
history[1].x = x1;
history[1].y = y2;
history[2].x = x2;
history[2].y = y2;
r = TRUE;
}
}
return r;
}
void Board::gravity(int col, BOOL update) {
if(gravity_flag) {
int rptr = y_tiles()-1, wptr = y_tiles()-1;
while(rptr >= 0) {
if(getField(col, wptr) != EMPTY) {
rptr--;
wptr--;
} else if(getField(col, rptr) != EMPTY) {
setField(col, wptr, getField(col, rptr));
setField(col, rptr, EMPTY);
if(update) {
updateField(col, rptr);
updateField(col, wptr);
}
wptr--;
rptr--;
} else
rptr--;
}
}
}
void Board::drawArrow(int x1, int y1, int x2, int y2) {
if(trying)
return;
// find out number of array
int num = 0;
while(num < 4 && history[num].x != -2)
num++;
// lighten the fields
// remember mark_x,mark_y
int mx = mark_x, my = mark_y;
mark_x = x1; mark_y = y1;
updateField(x1, y1);
UpdateWindow();
mark_x = x2; mark_y = y2;
updateField(x2, y2);
UpdateWindow();
// restore the mark
mark_x = mx;
mark_y = my;
// draw the arrow
HDC dc = GetDC();
HPEN hOldPen, p = CreatePen(PS_SOLID, 6, RGB(255,0,0));
hOldPen = (HPEN)SelectObject(dc, p);
num = 0;
while(num < 3 && history[num+1].x != -2) {
POINT pt = midCoord(history[num].x, history[num].y);
MoveToEx(dc, pt.x, pt.y, NULL);
pt = midCoord(history[num+1].x, history[num+1].y);
LineTo(dc, pt.x, pt.y);
num++;
}
SelectObject(dc, hOldPen);
DeleteObject(p);
ReleaseDC(dc);
// set the timer to force undraw later
m_nTimer = SetTimer(m_hWnd, 1, getDelay(), 0);
}
void Board::undrawArrow() {
if(trying)
return;
if(grav_col_1 != -1 || grav_col_2 != -1) {
gravity(grav_col_1, TRUE);
gravity(grav_col_2, TRUE);
grav_col_1 = -1;
grav_col_2 = -1;
}
// is already undrawn?
if(history[0].x == -2)
return;
// redraw all affected fields
int num = 0;
while(num < 3 && history[num+1].x != -2) {
if(history[num].y == history[num+1].y)
for(int i = MIN(history[num].x, history[num+1].x);
i <= MAX(history[num].x, history[num+1].x); i++)
updateField(i, history[num].y, TRUE);
else
for(int i = MIN(history[num].y, history[num+1].y);
i <= MAX(history[num].y, history[num+1].y); i++)
updateField(history[num].x, i, TRUE);
num++;
}
clearHistory();
}
POINT Board::midCoord(int x, int y) {
POINT p;
int w = m_tileWidth;
int h = m_tileHeight;
if(x == -1)
p.x = (XBORDER/2 - w/2);
else if(x == x_tiles())
p.x = (XBORDER/2 + w * x_tiles());
else
p.x = (XBORDER + w * x);
if(y == -1)
p.y = (YBORDER/2 - h/2);
else if(y == y_tiles())
p.y = (YBORDER/2 + h * y_tiles());
else
p.y = (YBORDER + h * y);
p.x = (p.x + w/2);
p.y = (p.y + h/2);
return p;
}
void Board::setDelay(int newvalue) {
_delay = newvalue;
}
int Board::getDelay() {
return _delay;
}
void Board::madeMove(int x1, int y1, int x2, int y2) {
Move *m = new Move(x1, y1, x2, y2, getField(x1, y1));
_undo.push_back(*m);
while(_redo.size())
_redo.pop_front();
}
BOOL Board::canUndo() {
return (_undo.size() > 0);
}
BOOL Board::canRedo() {
return (_redo.size() > 0);
}
void Board::undo() {
if(canUndo()) {
undrawArrow();
Move m = _undo.back();
_undo.pop_back();
if(gravityFlag()) {
int y;
int delta = 1;
if(m.x1 == m.x2) {
delta++;
/* damned, I hate this. This undo/redo stuff is really complicated
* when used with gravity. This avoids a bug when both tiles reside
* in the same row, but not adjascent y positions. In that case, the
* order of undo is important
*/
if(m.y1>m.y2) {
int t = m.x1;
m.x1 = m.x2;
m.x2 = t;
t = m.y1;
m.y1 = m.y2;
m.y2 = t;
}
}
for(y = 0; y <= m.y1-1; y++) {
setField(m.x1, y, getField(m.x1, y+delta));
updateField(m.x1, y);
}
if(m.x1 != m.x2) {
for(y = 0; y < m.y2; y++) {
setField(m.x2, y, getField(m.x2, y+1));
updateField(m.x2, y);
}
}
}
setField(m.x1, m.y1, m.tile);
setField(m.x2, m.y2, m.tile);
updateField(m.x1, m.y1);
updateField(m.x2, m.y2);
_redo.push_front(m);
}
}
void Board::redo() {
if(canRedo()) {
undrawArrow();
Move m = _redo.front();
_redo.pop_front();
setField(m.x1, m.y1, EMPTY);
setField(m.x2, m.y2, EMPTY);
updateField(m.x1, m.y1);
updateField(m.x2, m.y2);
gravity(m.x1, TRUE);
gravity(m.x2, TRUE);
_undo.push_back(m);
}
}
SIZE Board::sizeHint() {
SIZE sz;
sz.cx = x_tiles() * m_tileWidth + 2 * XBORDER;
sz.cy = y_tiles() * m_tileHeight + 2 * YBORDER;
return sz;
}
void Board::clearHistory() {
// init history
for(int i = 0; i < 4; i++) {
history[i].x = -2;
history[i].y = -2;
}
}
void Board::getHint() {
int x1, y1, x2, y2;
History h[4];
if(getHint_I(x1, y1, x2, y2, h)) {
undrawArrow();
for(int i = 0; i < 4; i++)
history[i] = h[i];
int old_delay = getDelay();
setDelay(1000);
drawArrow(x1, y1, x2, y2);
setDelay(old_delay);
}
}
BOOL Board::getHint_I(int &x1, int &y1, int &x2, int &y2, History h[4]) {
short done[45];
for( short index = 0; index < 45; index++ )
done[index] = 0;
// remember old history
History old[4];
for(int i = 0; i < 4; i++)
old[i] = history[i];
// initial no hint
x1 = -1;
x2 = -1;
y1 = -1;
y2 = -1;
for(int x = 0; x < x_tiles(); x++)
for(int y = 0; y < y_tiles(); y++)
if(getField(x, y) != EMPTY && done[getField(x, y)] != 4) {
int tile = getField(x, y);
// for all these types of tile search path's
for(int xx = 0; xx < x_tiles(); xx++)
for(int yy = 0; yy < y_tiles(); yy++)
if(xx != x || yy != y)
if(getField(xx, yy) == tile)
if(findPath(x, y, xx, yy)) {
for(int i = 0; i < 4; i++)
h[i] = history[i];
x1 = x;
x2 = xx;
y1 = y;
y2 = yy;
for(int ii = 0; ii < 4; ii++)
history[ii] = old[ii];
return TRUE;
}
clearHistory();
done[tile]++;
}
for(int ii = 0; ii < 4; ii++)
history[ii] = old[ii];
return FALSE;
}
void Board::setShuffle(int newvalue) {
_shuffle = newvalue;
}
int Board::getShuffle() {
return _shuffle;
}
int Board::tilesLeft() {
int left = 0;
for(int i = 0; i < x_tiles(); i++)
for(int j = 0; j < x_tiles(); j++)
if(getField(i, j) != EMPTY)
left++;
return left;
}
int Board::getCurrentTime() {
return (int)(time((time_t *)0) - starttime);
}
int Board::getTimeForGame() {
if(tilesLeft() == 0)
return time_for_game;
else if(paused)
return (int)(pause_start - starttime);
else
return (int)(time((time_t *)0) - starttime);
}
BOOL Board::solvable(BOOL norestore) {
int x1, y1, x2, y2;
History h[4];
int *oldfield = 0;
if(!norestore) {
oldfield = (int *)malloc(x_tiles() * y_tiles() * sizeof(int));
memcpy(oldfield, field, x_tiles() * y_tiles() * sizeof(int));
}
while(getHint_I(x1, y1, x2, y2, h)) {
setField(x1, y1, EMPTY);
setField(x2, y2, EMPTY);
}
int left = tilesLeft();
if(!norestore) {
memcpy(field, oldfield, x_tiles() * y_tiles() * sizeof(int));
free(oldfield);
}
return (left == 0);
}
BOOL Board::getSolvableFlag() {
return _solvable_flag;
}
void Board::setSolvableFlag(BOOL value) {
_solvable_flag = value;
}
void Board::setGravityFlag(BOOL b) {
gravity_flag = b;
}
BOOL Board::gravityFlag() {
return gravity_flag;
}
BOOL Board::pause() {
paused = !paused;
if(paused)
pause_start = time((time_t *)0);
else
starttime += (time_t)(time((time_t *)0) - pause_start);
InvalidateRect(NULL, TRUE);
return paused;
}
#ifdef _DEBUG
void Board::finish() {
MSG msg;
int x1, y1, x2, y2;
History h[4];
BOOL ready = FALSE;
// Loop as long as there are tiles to remove
while(!ready && getHint_I(x1, y1, x2, y2, h)) {
mark_x = -1;
mark_y = -1;
if(tilesLeft() == 2)
ready = TRUE;
marked(x1, y1);
marked(x2, y2);
UpdateWindow();
Sleep(getDelay());
// Handle any event(s) in the message queue
while(PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) {
if(!GetMessage(&msg, NULL, 0, 0)) {
// Close the application
PostQuitMessage(0);
ready = TRUE;
break;
}
TranslateMessage(&msg);
DispatchMessage(&msg);
switch(msg.message) {
case WM_END_OF_GAME:
case WM_COMMAND:
case WM_LBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_CHAR:
// Terminate loop if the user selects a
// menu item, presses a key, or clicks
// a mouse button, or when game is over.
ready = TRUE;
break;
}
}
}
}
#endif // _DEBUG
/////////////////////////////////////////////////////////////////////////////
// Board message handlers
LRESULT Board::WndProc(UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch(uMsg) {
case WM_PAINT:
OnPaint();
return 0;
case WM_ERASEBKGND:
return OnEraseBkgnd((HDC)wParam);
case WM_TIMER:
OnTimer(wParam);
return 0;
case WM_LBUTTONDOWN:
{
POINT pt;
pt.x = LOWORD(lParam);
pt.y = HIWORD(lParam);
OnLButtonDown(wParam, pt);
return 0;
}
case WM_RBUTTONDOWN:
{
POINT pt;
pt.x = LOWORD(lParam);
pt.y = HIWORD(lParam);
OnRButtonDown(wParam, pt);
return 0;
}
}
return Window::WndProc(uMsg, wParam, lParam);
}
int Board::OnCreate(LPCREATESTRUCT lpCreateStruct) {
// Let the superclass do its thing
Window::OnCreate(lpCreateStruct);
// Setup the default background and tileset
loadDefaultBackground();
loadDefaultTileset();
return 0;
}
void Board::OnDestroy() {
// Release tileset and background resources
releaseTileset();
releaseBackground();
// Delete the paused bitmap, if any
if(m_hBmpPaused)
DeleteObject(m_hBmpPaused);
m_hBmpPaused = NULL;
// Kill the undraw timer, if any
if(m_nTimer)
KillTimer(m_hWnd, m_nTimer);
m_nTimer = 0;
// Let the superclass do its thing
Window::OnDestroy();
}
BOOL Board::OnEraseBkgnd(HDC hDC) {
return FALSE; // OnPaint will take care of the background
}
void Board::OnPaint() {
RECT rcUpdate;
GetUpdateRect(&rcUpdate, FALSE);
int rcUpdateWidth = rcUpdate.right - rcUpdate.left;
int rcUpdateHeight = rcUpdate.bottom - rcUpdate.top;
PAINTSTRUCT ps;
BeginPaint(&ps);
// Create the backing DC
HDC dcBacking = CreateCompatibleDC(ps.hdc);
HBITMAP bmpBacking, hOldBacking;
bmpBacking = CreateCompatibleBitmap(ps.hdc, rcUpdateWidth, rcUpdateHeight);
hOldBacking = (HBITMAP)SelectObject(dcBacking, bmpBacking);
// Prepare DC for painting
if(ps.fErase)
eraseBackground(dcBacking, rcUpdate);
else
BitBlt(dcBacking, 0, 0, rcUpdateWidth,
rcUpdateHeight, ps.hdc, rcUpdate.left, rcUpdate.top, SRCCOPY);
if(paused) {
// Game is paused; draw the paused bitmap
// Load paused bitmap, if it is not already loaded
if(!m_hBmpPaused) {
m_hBmpPaused = LoadBitmap(App::GetHInstance(), MAKEINTRESOURCE(IDB_PAUSED));
}
// Centre bitmap on screen
BITMAP bm;
RECT rcClient;
GetClientRect(&rcClient);
GetObject(m_hBmpPaused, sizeof(BITMAP), &bm);
int x = (rcClient.right - rcClient.left - bm.bmWidth)/2;
int y = (rcClient.bottom - rcClient.top - bm.bmHeight)/2;
// Draw the bitamp
CBmpUtil::DrawBitmap(dcBacking, m_hBmpPaused,
x-rcUpdate.left, y-rcUpdate.top,
bm.bmWidth, bm.bmHeight);
} else {
// Draw the tiles
for(int i = 0; i < x_tiles(); i++)
for(int j = 0; j < y_tiles(); j++) {
if(getField(i, j) == EMPTY)
continue;
int xpos = XBORDER + i * m_tileWidth;
int ypos = YBORDER + j * m_tileHeight;
RECT ri, r;
SetRect(&r, xpos, ypos, xpos + m_tileWidth, ypos + m_tileHeight);
// Only bother to draw a tile if it is in the update region
if(IntersectRect(&ri, &rcUpdate, &r))
drawTile(dcBacking, getField(i, j)-1,
xpos-rcUpdate.left, ypos-rcUpdate.top,
(i == mark_x && j == mark_y));
}
}
// Blit the backing DC to the window
BitBlt(ps.hdc, rcUpdate.left, rcUpdate.top, rcUpdateWidth, rcUpdateHeight,
dcBacking, 0, 0, SRCCOPY);
// Cleanup
EndPaint(&ps);
SelectObject(dcBacking, hOldBacking);
DeleteObject(bmpBacking);
DeleteDC(dcBacking);
}
void Board::OnLButtonDown(UINT nFlags, POINT point) {
if(!m_bNoEvents && !paused) {
// calculate position
int pos_x = (point.x - XBORDER <0)?-1:
(point.x - XBORDER) / m_tileWidth;
int pos_y = (point.y - YBORDER <0)?-1:
(point.y - YBORDER) / m_tileHeight;
// Mark tile
if(highlighted_tile != -1) {
int oldmarkx = mark_x;
int oldmarky = mark_y;
mark_x=-1; mark_y=-1;
for(int i = 0; i < x_tiles(); i++)
for(int j = 0; j < y_tiles(); j++) {
if( highlighted_tile == getField(i, j))
updateField(i, j);
}
mark_x = oldmarkx;
mark_y = oldmarky; // no tile selected
highlighted_tile = -1;
}
if(pos_x >= 0 && pos_x < x_tiles() && pos_y >= 0 && pos_y < y_tiles())
marked(pos_x, pos_y);
}
}
void Board::OnRButtonDown(UINT nFlags, POINT point) {
if(m_bNoEvents)
return;
// calculate position
int pos_x = (point.x - XBORDER <0)?-1:
(point.x - XBORDER) / m_tileWidth;
int pos_y = (point.y - YBORDER <0)?-1:
(point.y - YBORDER) / m_tileHeight;
int field = getField(pos_x,pos_y);
if(paused || (field == EMPTY)) {
// Right clicked on background; display popup menu
HMENU hGameMenu = GetSubMenu(GetMenu(App::GetMainHWnd()), 1);
ClientToScreen(m_hWnd, &point);
TrackPopupMenu(hGameMenu, TPM_LEFTALIGN|TPM_RIGHTBUTTON,
point.x, point.y, 0, GetParent(m_hWnd), NULL);
return;
}
// Assist by lighting all tiles of same type
highlighted_tile = field;
BOOL bUpdateWindow;
for(int i = 0; i < x_tiles(); i++)
for(int j = 0; j < y_tiles(); j++) {
if( field == getField(i, j)) {
mark_x=i; mark_y=j;
bUpdateWindow = TRUE;
} else {
bUpdateWindow = FALSE;
mark_x=-1; mark_y=-1;
}
updateField(i, j);
if(bUpdateWindow)
UpdateWindow();
}
mark_x=-1; mark_y=-1; // no tile selected
}
void Board::OnTimer(UINT nIDEvent) {
// Kill the undraw timer and undraw the arrow
KillTimer(m_hWnd, m_nTimer);
m_nTimer = 0;
undrawArrow();
}
<< Back to Shisen-Sho
|
|