|
Source listing: echo $file; ?>. Return to
the main page
/* -*- C++ -*-
*******************************************************************
*
*
* Shisen-Sho for Windows
*
* frame.cpp - Implementation of the Frame 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 <math.h>
#include <commctrl.h>
#include "shisen.h"
#include "frame.h"
#include "registry.h"
#include "dialogs.h"
static int size_x[5] = {14, 18, 24, 26, 30};
static int size_y[5] = { 6, 8, 12, 14, 16};
static int DELAY[5] = {125, 250, 500, 750, 1000};
Frame::Frame() {
m_nTimer = 0;
// Load the cursors
m_hWait = LoadCursor(NULL, IDC_WAIT);
m_hArrow = LoadCursor(NULL, IDC_ARROW);
// Cache this resource, as it is accessed often
m_szStatus.LoadString(IDS_STATUS);
}
Frame::~Frame() {
}
BOOL Frame::PreCreateWindow(CREATESTRUCT& cs) {
// 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 = LoadIcon(App::GetHInstance(), (LPCTSTR)IDR_MAINFRAME);
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = (LPCSTR)IDR_MAINFRAME;
wcex.lpszClassName = cs.lpszClass;
wcex.hIconSm = wcex.hIcon;
return RegisterClassEx(&wcex);
}
return TRUE;
}
void Frame::BeginWaitCursor() {
SetCursor(m_hWait);
}
void Frame::EndWaitCursor() {
SetCursor(m_hArrow);
}
void Frame::UpdateScore() {
CStdString s;
int t = m_board.getTimeForGame();
s.Format(m_szStatus, t/3600, (t/60)%60, t%60);
SendMessage(m_hStatus, SB_SETTEXT, 0|SBT_NOBORDERS, (LPARAM)(LPCTSTR)s);
}
void Frame::SetCheatMode(BOOL bCheat) {
CStdString szCheatMode;
szCheatMode.LoadString(IDS_INDICATOR_CHEAT);
SendMessage(m_hStatus, SB_SETTEXT, 1|SBT_NOBORDERS,
(LPARAM)(LPCTSTR)(bCheat?szCheatMode:_T("")));
}
void Frame::LoadDefaults() {
// We must load the initial tileset *before* we call
// OnSizeChange/OnLevelChange, as otherwise, the
// m_bUniqueSeasons flag may not be correct when the
// tiles are laid out.
CStdString szImage;
szImage = App::GetProfile()->GetProfileString(_T("Tilesets"), _T("Tileset"), _T(""));
m_board.loadMahjonggTileset(szImage);
szImage = App::GetProfile()->GetProfileString(_T("Tilesets"), _T("Background"), _T(""));
m_board.loadBackground(szImage);
UINT nID = App::GetProfile()->GetProfileInt(_T("Options"), _T("Speed"), ID_OSPEED3);
OnSpeedChange(nID);
nID = App::GetProfile()->GetProfileInt(_T("Options"), _T("Size"), ID_OSIZE2);
OnSizeChange(nID);
nID = App::GetProfile()->GetProfileInt(_T("Options"), _T("Level"), ID_OLEVEL2);
OnLevelChange(nID);
BOOL b = App::GetProfile()->GetProfileInt(_T("Options"), _T("Solvable"), TRUE);
m_board.setSolvableFlag(b);
HMENU hOptions = GetSubMenu(GetMenu(m_hWnd), 2);
CheckMenuItem(hOptions, ID_OSOLVABLE, MF_BYCOMMAND|
(b?MF_CHECKED:MF_UNCHECKED));
b = App::GetProfile()->GetProfileInt(_T("Options"), _T("Gravity"), FALSE);
m_board.setGravityFlag(b);
CheckMenuItem(hOptions, ID_OGRAVITY, MF_BYCOMMAND|
(b?MF_CHECKED:MF_UNCHECKED));
ReadHighscore();
}
CStdString Frame::GetPlayerName() {
PlayerName dlgName(this);
if(dlgName.DoModal() == IDOK)
return dlgName.m_szName;
else
return _T("");
}
int Frame::GetScore(HighScore &hs) {
double ntiles = hs.x*hs.y;
double tilespersec = ntiles/(double)hs.seconds;
double sizebonus = sqrt(ntiles/(double)(14.0 * 6.0));
double points = tilespersec / 0.14 * 100.0;
////return (int)(points * sizebonus * (hs.gravity?2.0:1.0));
return (int)(points * sizebonus);
}
BOOL Frame::IsBetter(HighScore &hs, HighScore &than) {
return (GetScore(hs) > GetScore(than));
}
int Frame::InsertHighscore(HighScore &hs) {
int i;
if(m_highscore.size() == 0) {
m_highscore.resize(1);
m_highscore[0] = hs;
WriteHighscore();
return 0;
} else {
HighScore last = m_highscore[m_highscore.size() - 1];
if(IsBetter(hs, last) || (m_highscore.size() < HIGHSCORE_MAX)) {
if(m_highscore.size() == HIGHSCORE_MAX)
m_highscore[HIGHSCORE_MAX - 1] = hs;
else {
m_highscore.resize(m_highscore.size()+1);
m_highscore[m_highscore.size() - 1] = hs;
}
// sort in new entry
int bestsofar = m_highscore.size() - 1;
for(i = m_highscore.size() - 1; i > 0; i--)
if(IsBetter(m_highscore[i], m_highscore[i-1])) {
// swap entries
HighScore temp = m_highscore[i-1];
m_highscore[i-1] = m_highscore[i];
m_highscore[i] = temp;
bestsofar = i - 1;
}
WriteHighscore();
return bestsofar;
}
}
return -1;
}
void Frame::ReadHighscore() {
int i;
CStdString s, e;
m_highscore.resize(0);
i = 0;
while ((i < (int)HIGHSCORE_MAX)) {
s.Format(_T("Highscore_%d"), i);
e = App::GetProfile()->GetProfileString(_T("Hall of Fame"), s, _T(""));
if(!e.IsEmpty()) {
m_highscore.resize(i+1);
HighScore hs;
hs.gravity = 0;
memset(hs.name, 0, sizeof(hs.name));
sscanf((const char *)e, _T("%d %d %d %ld %30c"),
&hs.x, &hs.y, &hs.seconds, &hs.date, (char*)&hs.name);
if(hs.x < 0) {
hs.x = -hs.x;
hs.gravity = 1;
}
m_highscore[i] = hs;
} else
break;
i++;
}
}
void Frame::WriteHighscore() {
int i;
CStdString s, e;
for(i = 0; i < m_highscore.size(); i++) {
s.Format(_T("Highscore_%d"), i);
HighScore hs = m_highscore[i];
e.Format(_T("%d %d %d %ld %30s"),
hs.gravity?-hs.x:hs.x, hs.y, hs.seconds, hs.date, hs.name);
App::GetProfile()->WriteProfileString(_T("Hall of Fame"), s, e);
}
}
void Frame::ShowHighscore(int focusitem) {
ShowScores dlg(this);
dlg.m_pHighScore = &m_highscore;
dlg.m_iHotItem = focusitem;
dlg.DoModal();
}
/////////////////////////////////////////////////////////////////////////////
// Frame message handlers
LRESULT Frame::WndProc(UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch(uMsg) {
case WM_GETMINMAXINFO:
OnGetMinMaxInfo((LPMINMAXINFO)lParam);
break;
case WM_SIZE:
OnSize(wParam, LOWORD(lParam), HIWORD(lParam));
break;
case WM_TIMER:
OnTimer(wParam);
break;
case WM_INITMENUPOPUP:
OnInitMenuPopup((HMENU)wParam, LOWORD(lParam), HIWORD(lParam));
break;
case WM_END_OF_GAME:
OnEndOfGame(wParam, lParam);
break;
default:
return Window::WndProc(uMsg, wParam, lParam);
}
return 0L;
}
int Frame::OnCreate(LPCREATESTRUCT lpCreateStruct) {
// Let the superclass do its thing
Window::OnCreate(lpCreateStruct);
// Create the board
RECT rc;
SetRect(&rc, 0, 0, 0, 0);
m_board.Create(0, _T("Shisen Board"), _T("Shisen Board"),
WS_CHILD|WS_VISIBLE, rc, m_hWnd, IDW_MAIN);
// Create the status bar
m_hStatus = CreateStatusWindow(WS_CHILD|WS_VISIBLE, _T(""), m_hWnd, 10);
// Setup the status bar with two segments
// (actual geometry will be set in OnSize)
int iWidths[2] = {-1, -1};
SendMessage(m_hStatus, SB_SETPARTS, 2, (LPARAM)&iWidths);
SendMessage(m_hStatus, SB_SIMPLE, 0, 0);
// Prime the status display
UpdateScore();
SetCheatMode(FALSE);
// Setup the elapsed time refresh timer
m_nTimer = SetTimer(m_hWnd, 1, 500, 0);
// Setup the defaults
LoadDefaults();
return 0;
}
void Frame::OnDestroy() {
if(m_nTimer)
KillTimer(m_hWnd, m_nTimer);
m_nTimer = 0;
PostQuitMessage(0);
// Let the superclass do its thing
Window::OnDestroy();
}
BOOL Frame::OnCommand(UINT nID, UINT nEvent, HWND hSource) {
// Parse the menu selections
if(nID >= ID_OLEVEL1 && nID <= ID_OLEVEL3)
OnLevelChange(nID);
else if(nID >= ID_OSIZE1 && nID <= ID_OSIZE5)
OnSizeChange(nID);
else if(nID >= ID_OSPEED1 && nID <= ID_OSPEED5)
OnSpeedChange(nID);
else switch (nID)
{
case ID_FEXIT:
DestroyWindow(m_hWnd);
break;
case ID_GUNDO:
OnUndo();
break;
case ID_GREDO:
OnRedo();
break;
case ID_GHINT:
OnGetHint();
break;
case ID_GNEW:
OnNewGame();
break;
case ID_GRESTART:
OnRestartGame();
break;
case ID_GPAUSE:
OnPauseGame();
break;
case ID_GISSOLVABLE:
OnIsSolvable();
break;
case ID_GHOF:
OnHallOfFame();
break;
case ID_GFINISH:
OnFinish();
break;
case ID_OGRAVITY:
OnGravity();
break;
case ID_OSOLVABLE:
OnSolvable();
break;
case ID_OTILESET:
OnSelectTileset();
break;
case ID_HABOUT:
OnAppAbout();
break;
default:
return Window::OnCommand(nID, nEvent, hSource);
}
return TRUE;
}
void Frame::OnGetMinMaxInfo(LPMINMAXINFO lpMMI) {
// Calculate window size relative to board dimensions
// Get board size
SIZE sizeBoard = m_board.sizeHint();
// Account for borders and menu
sizeBoard.cx += GetSystemMetrics(SM_CXEDGE)*4;
sizeBoard.cy += GetSystemMetrics(SM_CYEDGE)*4;
sizeBoard.cx += GetSystemMetrics(SM_CXFRAME)*2;
sizeBoard.cy += GetSystemMetrics(SM_CYFRAME)*2;
sizeBoard.cy += GetSystemMetrics(SM_CYMENU)+GetSystemMetrics(SM_CYCAPTION);
// Account for status bar
if(m_hStatus) {
RECT rcCtl;
::GetWindowRect(m_hStatus, &rcCtl);
sizeBoard.cy += rcCtl.bottom - rcCtl.top;
}
lpMMI->ptMaxTrackSize.x = sizeBoard.cx;
lpMMI->ptMaxTrackSize.y = sizeBoard.cy;
lpMMI->ptMinTrackSize.x = sizeBoard.cx;
lpMMI->ptMinTrackSize.y = sizeBoard.cy;
}
void Frame::OnSize(UINT nType, int cx, int cy) {
// Reposition status bar
RECT rcClient, rcCtl;
GetClientRect(&rcClient);
::GetClientRect(m_hStatus, &rcCtl);
MapWindowPoints(m_hStatus, m_hWnd, (LPPOINT)&rcCtl, 2);
::MoveWindow(m_hStatus, rcCtl.left, rcClient.bottom - rcCtl.bottom + rcCtl.top,
rcClient.right - rcClient.left,
rcCtl.bottom - rcCtl.top, TRUE);
// Calculate geometry of status bar segments
RECT rcText;
int iWidths[2];
HDC hDC = GetDC();
CStdString szCheatMode;
szCheatMode.LoadString(IDS_INDICATOR_CHEAT);
SetRect(&rcText, 0, 0, 0, 0);
DrawText(hDC, (LPCTSTR)szCheatMode, -1, &rcText, DT_CALCRECT|DT_LEFT);
ReleaseDC(hDC);
::GetClientRect(m_hStatus, &rcCtl);
iWidths[0] = rcCtl.right - rcText.right;
iWidths[1] = -1;
// Reposition the status bar segments
SendMessage(m_hStatus, SB_SETPARTS, 2, (LPARAM)&iWidths);
// Resize board to fit
m_board.MoveWindow(0, 0,
rcClient.right-rcClient.left, rcClient.bottom-rcClient.top - rcCtl.bottom + rcCtl.top, FALSE);
// Invalidate the client area
InvalidateRect(NULL, TRUE);
}
void Frame::OnTimer(UINT nIDEvent) {
UpdateScore();
}
void Frame::OnUndo() {
if(m_board.canUndo()) {
m_board.undo();
SetCheatMode(TRUE);
}
}
void Frame::OnRedo() {
if(m_board.canRedo())
m_board.redo();
}
void Frame::OnGetHint() {
m_board.getHint();
SetCheatMode(TRUE);
}
void Frame::OnNewGame() {
BeginWaitCursor();
m_board.newGame();
EndWaitCursor();
SetCheatMode(FALSE);
}
void Frame::OnRestartGame() {
while(m_board.canUndo())
m_board.undo();
}
void Frame::OnPauseGame() {
CStdString szPause, szResume;
szPause.LoadString(IDS_PAUSEGAME);
szResume.LoadString(IDS_RESUMEGAME);
BOOL bPaused = m_board.pause();
HMENU hGame = GetSubMenu(GetMenu(m_hWnd), 1);
ModifyMenu(hGame, ID_GPAUSE, MF_BYCOMMAND, ID_GPAUSE,
bPaused?szResume:szPause);
}
void Frame::OnIsSolvable() {
CStdString s;
s.Format(m_board.solvable()?IDS_SOLVABLE:IDS_NOTSOLVABLE);
MessageBox(m_hWnd, (LPCTSTR)s, App::GetAppName(), MB_ICONINFORMATION);
}
void Frame::OnHallOfFame() {
ShowHighscore();
}
void Frame::OnFinish() {
#ifdef _DEBUG
SetCheatMode(TRUE);
m_board.finish();
#endif // _DEBUG
}
void Frame::OnSolvable() {
m_board.setSolvableFlag(!m_board.getSolvableFlag());
HMENU hOptions = GetSubMenu(GetMenu(m_hWnd), 2);
CheckMenuItem(hOptions, ID_OSOLVABLE, MF_BYCOMMAND|
(m_board.getSolvableFlag()?MF_CHECKED:MF_UNCHECKED));
// Save option to the registry
App::GetProfile()->WriteProfileInt(_T("Options"), _T("Solvable"), m_board.getSolvableFlag());
}
void Frame::OnGravity() {
m_board.setGravityFlag(!m_board.gravityFlag());
HMENU hOptions = GetSubMenu(GetMenu(m_hWnd), 2);
CheckMenuItem(hOptions, ID_OGRAVITY, MF_BYCOMMAND|
(m_board.gravityFlag()?MF_CHECKED:MF_UNCHECKED));
// Save option to the registry
App::GetProfile()->WriteProfileInt(_T("Options"), _T("Gravity"), m_board.gravityFlag());
}
void Frame::OnLevelChange(UINT nID) {
// Update the menu items
HMENU hOptions = GetSubMenu(GetMenu(m_hWnd), 2);
for(UINT i = ID_OLEVEL1; i <= ID_OLEVEL3; i++)
CheckMenuItem(hOptions, i, MF_BYCOMMAND|((i == nID)?MF_CHECKED:MF_UNCHECKED));
// Create a new game with the changed level
m_board.setShuffle((nID - ID_OLEVEL1) * 4 + 1);
OnNewGame();
// Save option to the registry
App::GetProfile()->WriteProfileInt(_T("Options"), _T("Level"), nID);
}
void Frame::OnSizeChange(UINT nID) {
HMENU hOptions = GetSubMenu(GetMenu(m_hWnd), 2);
// Create a new game with the changed size
BeginWaitCursor();
m_board.setSize(size_x[nID-ID_OSIZE1], size_y[nID-ID_OSIZE1]);
OnNewGame();
// Update the menu items
for(UINT i = ID_OSIZE1; i <= ID_OSIZE5; i++)
CheckMenuItem(hOptions, i, MF_BYCOMMAND|((i == nID)?MF_CHECKED:MF_UNCHECKED));
// Frame must be resized; call MoveWindow to force WM_GETMINMAXINFO
RECT rcWnd;
GetWindowRect(&rcWnd);
MoveWindow(rcWnd, TRUE);
EndWaitCursor();
// Save option to the registry
App::GetProfile()->WriteProfileInt(_T("Options"), _T("Size"), nID);
}
void Frame::OnSpeedChange(UINT nID) {
HMENU hOptions = GetSubMenu(GetMenu(m_hWnd), 2);
m_board.setDelay(DELAY[nID - ID_OSPEED1]);
for(UINT i = ID_OSPEED1; i <= ID_OSPEED5; i++)
CheckMenuItem(hOptions, i, MF_BYCOMMAND|((i == nID)?MF_CHECKED:MF_UNCHECKED));
// Save option to the registry
App::GetProfile()->WriteProfileInt(_T("Options"), _T("Speed"), nID);
}
void Frame::OnSelectTileset() {
SelectTileset dlg(this);
if(dlg.DoModal() == IDOK) {
// Select new tileset
m_board.loadMahjonggTileset(dlg.GetTileset());
// Select new background
m_board.loadBackground(dlg.GetBackground());
// Re-layout tiles (necessary, as the m_bUniqueSeasons
// flag may have changed)
OnNewGame();
// Mark board for update
m_board.InvalidateRect(NULL, TRUE);
}
}
void Frame::OnInitMenuPopup(HMENU hMenu, UINT nIndex, BOOL bSysMenu) {
int iSize = GetMenuItemCount(hMenu);
for(int i=0; i<iSize; i++) {
int id = GetMenuItemID(hMenu, i);
if(id >= ID_GFIRST && id <= ID_OLAST)
OnUpdateMenu(hMenu, (UINT)id);
}
}
void Frame::OnUpdateMenu(HMENU hMenu, UINT nID) {
BOOL bEnable;
switch(nID) {
case ID_GUNDO:
case ID_GREDO:
case ID_GRESTART:
bEnable = m_board.canUndo() && !m_board.isPaused();
break;
case ID_OSOLVABLE:
case ID_OGRAVITY:
bEnable = !m_board.canUndo() && !m_board.isPaused();
break;
case ID_GPAUSE:
bEnable = TRUE;
break;
default:
bEnable = !m_board.isPaused();
}
EnableMenuItem(hMenu, nID, MF_BYCOMMAND|(bEnable?MF_ENABLED:MF_GRAYED));
}
void Frame::OnAppAbout() {
About dlg(this);
dlg.DoModal();
}
LRESULT Frame::OnEndOfGame(WPARAM wParam, LPARAM lParam) {
if(m_board.tilesLeft() > 0) {
CStdString s;
s.LoadString(IDS_NOMOREMOVES);
MessageBox(m_hWnd, (LPCTSTR)s, App::GetAppName(), MB_ICONEXCLAMATION);
} else {
// create highscore entry
HighScore hs;
hs.seconds = m_board.getTimeForGame();
hs.x = m_board.x_tiles();
hs.y = m_board.y_tiles();
hs.gravity = m_board.gravityFlag();
// check if we made it into Top10
BOOL isHighscore = FALSE;
if(m_highscore.size() < HIGHSCORE_MAX)
isHighscore = TRUE;
else if(IsBetter(hs, m_highscore[HIGHSCORE_MAX-1]))
isHighscore = TRUE;
if(isHighscore) {
CStdString name = GetPlayerName();
if(!name.IsEmpty()) {
strncpy(hs.name, (LPCSTR)name, sizeof(hs.name) - 1);
hs.date = time((time_t*)0);
hs.x = m_board.x_tiles();
hs.y = m_board.y_tiles();
int rank = InsertHighscore(hs);
ShowHighscore(rank);
}
} else {
CStdString s;
int t = m_board.getTimeForGame();
s.Format(IDS_GAMEOVER, t/3600, (t/60)%60, t%60);
MessageBox(m_hWnd, (LPCTSTR)s, App::GetAppName(), MB_ICONINFORMATION);
}
}
OnNewGame();
return 0L;
}
<< Back to Shisen-Sho
|
|