1140 lines
24 KiB
C++
1140 lines
24 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* 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 3 of the License, 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. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "bagel/afxwin.h"
|
|
#include "bagel/hodjnpodj/hnplibs/sprite.h"
|
|
#include "bagel/hodjnpodj/hnplibs/stdinc.h"
|
|
#include "bagel/boflib/misc.h"
|
|
#include "bagel/hodjnpodj/hnplibs/gamedll.h"
|
|
#include "bagel/hodjnpodj/hnplibs/mainmenu.h"
|
|
#include "bagel/hodjnpodj/hnplibs/cmessbox.h"
|
|
#include "bagel/hodjnpodj/globals.h"
|
|
#include "bagel/hodjnpodj/pdq/game.h"
|
|
#include "bagel/hodjnpodj/pdq/usercfg.h"
|
|
#include "bagel/hodjnpodj/pdq/main.h"
|
|
#include "bagel/hodjnpodj/hodjnpodj.h"
|
|
|
|
namespace Bagel {
|
|
namespace HodjNPodj {
|
|
namespace PDQ {
|
|
|
|
/*
|
|
*
|
|
* Local data types
|
|
*
|
|
*/
|
|
typedef struct {
|
|
char text[MAX_PLENGTH_S + 1];
|
|
byte order[MAX_PLENGTH];
|
|
} PHRASES;
|
|
|
|
typedef struct {
|
|
unsigned int gameSpeed; // 1 to 5 = 2.5 to .5 seconds
|
|
unsigned int nShown; // 1 to 9 as of 9/9/94 0 to 6
|
|
bool bRandomLetters; // true if letter ordering shall be random
|
|
bool bShowNames; // true if we are to display category names
|
|
} USERCFG;
|
|
|
|
typedef struct {
|
|
CSprite *sprite;
|
|
bool bUsed;
|
|
} SPRITE_LIST;
|
|
|
|
|
|
STATIC const char *pszCategorySounds[N_CATEGORIES] = {
|
|
".\\SOUND\\TGG5.WAV",
|
|
".\\SOUND\\TGG6.WAV",
|
|
".\\SOUND\\TGG7.WAV",
|
|
".\\SOUND\\TGG8.WAV"
|
|
};
|
|
|
|
|
|
/*
|
|
*
|
|
* Local prototypes
|
|
*
|
|
*/
|
|
ERROR_CODE CleanScreen(CDC *);
|
|
ERROR_CODE LoadNewPhrase();
|
|
ERROR_CODE BuildSpriteList(CDC *);
|
|
void KillCurPhrase();
|
|
void BuildRandomPhraseOrder();
|
|
bool RevealNextLetter();
|
|
void CALLBACK GameTimerHook(HWND, unsigned int, uintptr, uint32);
|
|
int StrLenNoSpaces(const char *);
|
|
int GetIndex(CSprite *);
|
|
void LoadGameCfg();
|
|
void SaveGameCfg();
|
|
ERROR_CODE ValidatePhrase(PHRASES *);
|
|
void UpdateScore(unsigned int, unsigned int, unsigned int, unsigned int);
|
|
int NumLinkedSprites();
|
|
|
|
#define TIMER_ID 50
|
|
#define START_X_ODD 318
|
|
#define START_X_EVEN 334
|
|
#define LETTER_SPACING 3
|
|
#define SIGN_LENGTH 460
|
|
#define SIGN_START_X 93
|
|
#define LETTER_START_Y 78
|
|
#define DATA_FILE "PHRASES.DAT"
|
|
|
|
#define TYPE_PERSON 0
|
|
#define TYPE_PLACE 1
|
|
#define TYPE_PHRASE 2
|
|
#define TYPE_TITLE 3
|
|
|
|
#define START_TITLE 0
|
|
#define START_PERSON 103
|
|
#define START_PHRASE 172
|
|
#define START_PLACE 197
|
|
|
|
extern CPalette *pMyGamePalette;
|
|
extern bool bInGame;
|
|
extern CMainWindow *gMain;
|
|
extern LPGAMESTRUCT pGameParams;
|
|
|
|
/*
|
|
* Globals
|
|
*/
|
|
USERCFG gGameCfg;
|
|
PHRASES *curPhrase, gPhrase;
|
|
SPRITE_LIST spriteList[MAX_PLENGTH];
|
|
int iNextLetter;
|
|
int timerInterval;
|
|
HWND gGameWnd;
|
|
bool bPause;
|
|
unsigned int gLeftAvg, gTotalAvg;
|
|
const char *INI_SECTION = "ThGesngGme";
|
|
int nPhrasePixelLength;
|
|
|
|
|
|
/**
|
|
* name LoadNewPhrase - loads a new phrase from the data store
|
|
*
|
|
* synopsis LoadNewPhrase()
|
|
*
|
|
*
|
|
* purpose To randomly choose a new phrase for the game
|
|
*
|
|
*
|
|
* returns errCode - Error return code
|
|
*
|
|
**/
|
|
ERROR_CODE LoadNewPhrase() {
|
|
STATIC int nLast;
|
|
char *p, buf[MAX_PLENGTH_S + 2];
|
|
int i, n, nType;
|
|
ERROR_CODE errCode;
|
|
|
|
/* assume no error */
|
|
errCode = ERR_NONE;
|
|
|
|
/* reset letter index */
|
|
iNextLetter = 0;
|
|
|
|
curPhrase = &gPhrase;
|
|
|
|
/*
|
|
* Load a new randomly selected phrase from the data store.
|
|
*/
|
|
|
|
/* determine the number of phrases in the data store */
|
|
n = (int)(FileLength(DATA_FILE) / sizeof(PHRASES));
|
|
|
|
if (n > 0) {
|
|
|
|
/*
|
|
* pick one at random, but not same one twice in a row
|
|
*/
|
|
do {
|
|
i = brand() % n;
|
|
} while (i == nLast);
|
|
nLast = i;
|
|
|
|
nType = 0;
|
|
if (i >= START_TITLE && i < START_PERSON) {
|
|
nType = TYPE_TITLE;
|
|
|
|
} else if (i >= START_PERSON && i < START_PHRASE) {
|
|
nType = TYPE_PERSON;
|
|
|
|
} else if (i >= START_PHRASE && i < START_PLACE) {
|
|
nType = TYPE_PHRASE;
|
|
|
|
} else if (i >= START_PLACE) {
|
|
nType = TYPE_PLACE;
|
|
}
|
|
|
|
// Are we supposed to show the category names?
|
|
//
|
|
if (gGameCfg.bShowNames) {
|
|
|
|
// display category
|
|
gMain->PaintCategory(nType);
|
|
|
|
// play category narration
|
|
sndPlaySound(pszCategorySounds[nType], SND_ASYNC);
|
|
}
|
|
|
|
ifstream inFile;
|
|
inFile.open(DATA_FILE, ios::binary); // open the data store
|
|
|
|
inFile.seekg(i * sizeof(PHRASES)); // seek to the phrase we want
|
|
|
|
inFile.read((char *)curPhrase, sizeof(PHRASES)); // load that phrase
|
|
if (inFile.gcount() != sizeof(PHRASES))
|
|
errCode = ERR_FREAD;
|
|
inFile.close(); // close the data store
|
|
|
|
if (!errCode) {
|
|
|
|
if ((errCode = ValidatePhrase(curPhrase)) == ERR_NONE) {
|
|
|
|
/*
|
|
* convert all instances of "the", "an", and "a" to upper case
|
|
*/
|
|
Common::sprintf_s(p = buf, MAX_PLENGTH_S + 2, " %s",
|
|
strLower(curPhrase->text));
|
|
|
|
StrUprStr(p, " the ");
|
|
StrUprStr(p, " an ");
|
|
StrUprStr(p, " a ");
|
|
p++;
|
|
Common::strcpy_s(curPhrase->text, p);
|
|
|
|
/*
|
|
* if user wants a random ordering
|
|
*/
|
|
if (gGameCfg.bRandomLetters)
|
|
BuildRandomPhraseOrder();
|
|
}
|
|
}
|
|
} else {
|
|
errCode = ERR_FFIND;
|
|
}
|
|
|
|
return errCode;
|
|
}
|
|
|
|
|
|
/**
|
|
* name BuildRandomPhraseOrder - randomizes the order of the letters to
|
|
* be revealed.
|
|
*
|
|
* synopsis BuildRandomPhraseOrder()
|
|
*
|
|
*
|
|
* purpose To provide a random ordering of letters that are revealed with
|
|
* RevealNextLetter() instead of the fixed order prescribed in the
|
|
* data store.
|
|
*
|
|
* returns Nothing
|
|
*
|
|
**/
|
|
void BuildRandomPhraseOrder() {
|
|
byte *curPhraseOrder;
|
|
int i, j, n, newIndex;
|
|
bool use;
|
|
|
|
/*
|
|
* if there is a current active game
|
|
*/
|
|
if (curPhrase != nullptr) {
|
|
|
|
curPhraseOrder = curPhrase->order;
|
|
|
|
memset(curPhraseOrder, 0, MAX_PLENGTH * sizeof(byte));
|
|
|
|
/*
|
|
* each entry must be a unique index into the spriteList
|
|
*/
|
|
n = StrLenNoSpaces(curPhrase->text);
|
|
for (i = 0; i < n; i++) {
|
|
do {
|
|
use = true;
|
|
newIndex = brand() % n + 1;
|
|
for (j = 0; j < i; j++) {
|
|
if (curPhraseOrder[j] == newIndex) {
|
|
use = false;
|
|
break;
|
|
}
|
|
}
|
|
} while (!use);
|
|
|
|
curPhraseOrder[i] = (byte)newIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* name BuildSpriteList - builds a sprite list from curPhrase
|
|
*
|
|
* synopsis BuildSpriteList(pDC)
|
|
* CDC *pDC pointer to this game's Device Context
|
|
*
|
|
*
|
|
* purpose To fill an array of pointers to sprites that represent letters
|
|
* from the currently loaded phrase.
|
|
*
|
|
* returns errCode = Error return code
|
|
*
|
|
**/
|
|
ERROR_CODE BuildSpriteList(CDC *pDC) {
|
|
CSprite *pNewSprite;
|
|
char *pText;
|
|
int i;
|
|
ERROR_CODE errCode;
|
|
|
|
/* can't access null pointers */
|
|
assert(pDC != nullptr);
|
|
|
|
/* assume no error */
|
|
errCode = ERR_NONE;
|
|
|
|
/*
|
|
* if there is a game currently active
|
|
*/
|
|
if (curPhrase != nullptr) {
|
|
|
|
if (pDC == nullptr) {
|
|
errCode = ERR_UNKNOWN;
|
|
|
|
} else {
|
|
|
|
/* use local pointer to this global object */
|
|
pText = curPhrase->text;
|
|
|
|
memset(spriteList, 0, sizeof(SPRITE_LIST) * MAX_PLENGTH);
|
|
|
|
/*
|
|
* build a sprite list for this phrase
|
|
*/
|
|
i = 0;
|
|
while (*pText != '\0') {
|
|
|
|
/*
|
|
* don't include spaces
|
|
*/
|
|
if (*pText != ' ') {
|
|
|
|
/*
|
|
* create a new sprite for this letter
|
|
*/
|
|
if ((spriteList[i].sprite = pNewSprite = new CSprite) == nullptr) {
|
|
errCode = ERR_MEMORY;
|
|
break;
|
|
|
|
} else {
|
|
|
|
spriteList[i].bUsed = false;
|
|
|
|
/*
|
|
* load this letter's bitmap into the sprite
|
|
*/
|
|
if (pNewSprite->LoadResourceSprite(pDC, toupper(*pText) +36) == false) {
|
|
errCode = ERR_UNKNOWN;
|
|
break;
|
|
|
|
} else {
|
|
|
|
if (pNewSprite->SharePalette(pMyGamePalette) == false) {
|
|
errCode = ERR_UNKNOWN;
|
|
break;
|
|
|
|
} else {
|
|
|
|
pNewSprite->SetMasked(true);
|
|
pNewSprite->SetMobile(true);
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
pText++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return errCode;
|
|
}
|
|
|
|
|
|
/**
|
|
* name KillCurPhrase - destroys all valid sprites from the sprite list
|
|
*
|
|
* synopsis KillCurPhrase()
|
|
*
|
|
*
|
|
* purpose To de-allocate any sprites in the sprite list that have not yet
|
|
* been revealed. NOTE: this must be called to de-allocate
|
|
* sprites that were allocated via BuildSpriteList().
|
|
*
|
|
* returns Nothing
|
|
*
|
|
**/
|
|
void KillCurPhrase() {
|
|
int i, n;
|
|
|
|
if (curPhrase != nullptr) {
|
|
|
|
/*
|
|
* delete each of the remaining letters that have not yet been revealed
|
|
*/
|
|
n = StrLenNoSpaces(curPhrase->text);
|
|
for (i = 0; i < n; i++) {
|
|
if ((spriteList[i].sprite != nullptr) && !spriteList[i].bUsed) {
|
|
delete spriteList[i].sprite;
|
|
spriteList[i].sprite = nullptr;
|
|
}
|
|
}
|
|
curPhrase = nullptr;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* name RevealNextLetter() - reveals the next letter in the phrase
|
|
*
|
|
* synopsis done = RevealNextLetter()
|
|
*
|
|
*
|
|
* purpose To display the next available letter from the sprite list
|
|
*
|
|
*
|
|
* returns false if this was the last letter to be revealed, else true
|
|
*
|
|
**/
|
|
bool RevealNextLetter() {
|
|
CSize size;
|
|
int index;
|
|
bool lastLetter;
|
|
|
|
lastLetter = false;
|
|
if (curPhrase != nullptr) {
|
|
|
|
/* get next valid letter to reveal */
|
|
index = curPhrase->order[iNextLetter++] - 1;
|
|
|
|
/* validate the index */
|
|
assert((index >= 0) && (index < MAX_PLENGTH));
|
|
|
|
/* validate this sprite */
|
|
assert(spriteList[index].sprite != nullptr);
|
|
assert(spriteList[index].bUsed != true);
|
|
|
|
/*
|
|
* add this letter to the list
|
|
*/
|
|
spriteList[index].sprite->LinkSprite();
|
|
spriteList[index].bUsed = true;
|
|
size = spriteList[index].sprite->GetSize();
|
|
nPhrasePixelLength += size.cx + LETTER_SPACING;
|
|
|
|
if (iNextLetter >= StrLenNoSpaces(curPhrase->text))
|
|
lastLetter = true;
|
|
}
|
|
|
|
return (lastLetter);
|
|
}
|
|
|
|
|
|
/**
|
|
* name RecalcDisplay - re-paints all sprites that are visible
|
|
*
|
|
* synopsis RecalcDisplay(pDC)
|
|
* CDC *pDC pointer to this game's Device Context
|
|
*
|
|
* purpose To re-fresh all on screen sprites
|
|
*
|
|
*
|
|
* returns errCode = Error return code
|
|
*
|
|
**/
|
|
ERROR_CODE RecalcDisplay(CDC *pDC) {
|
|
CSize size;
|
|
int i, gap, last, n;
|
|
ERROR_CODE errCode;
|
|
|
|
/* can't use a null pointer */
|
|
assert(pDC != nullptr);
|
|
|
|
/* assume no error */
|
|
errCode = ERR_NONE;
|
|
|
|
/*
|
|
* as long as there is a currently active unpaused game, we can paint some letters
|
|
*/
|
|
if (bInGame) {
|
|
|
|
if (pDC == nullptr) {
|
|
errCode = ERR_UNKNOWN;
|
|
} else {
|
|
|
|
assert(nPhrasePixelLength > 0);
|
|
|
|
if (nPhrasePixelLength > 0) {
|
|
|
|
/* clean up the screan so we can re-paint the sprites */
|
|
CSprite::EraseSprites(pDC);
|
|
|
|
n = NumLinkedSprites();
|
|
|
|
gap = (SIGN_LENGTH - nPhrasePixelLength) / 2;
|
|
last = SIGN_START_X + gap;
|
|
for (i = 0; i < MAX_PLENGTH; i++) {
|
|
if (spriteList[i].bUsed) {
|
|
assert(spriteList[i].sprite != nullptr);
|
|
spriteList[i].sprite->PaintSprite(pDC, last, LETTER_START_Y);
|
|
size = spriteList[i].sprite->GetSize();
|
|
|
|
// letter spacing is greater when there are less letters
|
|
last += size.cx + (LETTER_SPACING + 5 - (n + 4) / 5);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return errCode;
|
|
}
|
|
|
|
ERROR_CODE RepaintSpriteList(CDC *pDC) {
|
|
CSprite *pSprite;
|
|
ERROR_CODE errCode;
|
|
|
|
/* can't use a null pointer */
|
|
assert(pDC != nullptr);
|
|
|
|
/* assume no error */
|
|
errCode = ERR_NONE;
|
|
|
|
if (pDC == nullptr) {
|
|
errCode = ERR_UNKNOWN;
|
|
} else {
|
|
|
|
/*
|
|
* Paint each sprite
|
|
*/
|
|
pSprite = CSprite::GetSpriteChain();
|
|
while (pSprite) {
|
|
|
|
pSprite->ClearBackground();
|
|
pSprite->RefreshSprite(pDC);
|
|
|
|
pSprite = pSprite->GetNextSprite();
|
|
}
|
|
}
|
|
return errCode;
|
|
}
|
|
|
|
int GetIndex(CSprite *pSprite) {
|
|
int i;
|
|
|
|
assert(pSprite != nullptr);
|
|
|
|
for (i = 0; i < MAX_PLENGTH; i++) {
|
|
if (pSprite == spriteList[i].sprite) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
assert(i >= 0 && i < MAX_PLENGTH);
|
|
|
|
if (i >= MAX_PLENGTH)
|
|
i = -1;
|
|
|
|
return (i);
|
|
}
|
|
|
|
|
|
/**
|
|
* name InitGame - Initialize the game parameters
|
|
*
|
|
* synopsis status = InitGame(pDc);
|
|
* CDC *pDC pointer to this game's Device Context
|
|
*
|
|
*
|
|
* purpose To initilize game parameters - This must be done before each new
|
|
* game is played
|
|
*
|
|
* returns errCode = Error return code
|
|
*
|
|
**/
|
|
ERROR_CODE InitGame(HWND hWnd, CDC *pDC) {
|
|
int i;
|
|
ERROR_CODE errCode;
|
|
|
|
assert(pDC != nullptr);
|
|
|
|
/* assume no error */
|
|
errCode = ERR_NONE;
|
|
|
|
/* keep a copy of this game's window handle */
|
|
gGameWnd = hWnd;
|
|
|
|
if (pDC == nullptr) {
|
|
errCode = ERR_UNKNOWN;
|
|
|
|
} else {
|
|
|
|
/* clear the screen of any previous games */
|
|
EndGame(pDC);
|
|
|
|
/* load INI settings */
|
|
LoadGameCfg();
|
|
|
|
/*
|
|
* load a new randomly selected phrase as the current phrase
|
|
*/
|
|
if ((errCode = LoadNewPhrase()) == ERR_NONE) {
|
|
|
|
if ((errCode = BuildSpriteList(pDC)) == ERR_NONE) {
|
|
// Remove category name from screen
|
|
if (gGameCfg.bShowNames)
|
|
gMain->EraseCategory();
|
|
|
|
/* reveal 1st 3 letters */
|
|
for (i = 0; i < (int)gGameCfg.nShown; i++) {
|
|
RevealNextLetter();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return errCode;
|
|
}
|
|
|
|
|
|
/**
|
|
* name CleanScreen - removes all sprites from the screen
|
|
*
|
|
* synopsis CleanScreen(pDC)
|
|
* CDC *pDC pointer to this game's Device Context
|
|
*
|
|
* purpose To refresh the screen by erasing all sprites
|
|
*
|
|
*
|
|
* returns errCode = Error return code
|
|
*
|
|
**/
|
|
ERROR_CODE CleanScreen(CDC *pDC) {
|
|
ERROR_CODE errCode;
|
|
|
|
assert(pDC != nullptr);
|
|
|
|
/* assume no error */
|
|
errCode = ERR_NONE;
|
|
|
|
if (pDC == nullptr) {
|
|
errCode = ERR_UNKNOWN;
|
|
} else {
|
|
|
|
KillCurPhrase();
|
|
CSprite::EraseSprites(pDC);
|
|
CSprite::FlushSpriteChain();
|
|
}
|
|
|
|
return errCode;
|
|
}
|
|
|
|
|
|
/**
|
|
* name GameTimerHook - hooks WM_TIMER message for PDQ
|
|
*
|
|
* synopsis GameTimerHook(hWnd, nMsg, nEventID, dwTime)
|
|
* HWND hWnd Handle of window that received WM_TIMER
|
|
* unsigned int nMsg WM_TIMER
|
|
* unsigned int nEventID TIMER_ID
|
|
* uint32 dwTime time that event occured
|
|
*
|
|
* purpose To provide a callback routine for hooking the WM_TIMER.
|
|
* Usage: SetTimer(hWnd, tID, time, GameTimerHook);
|
|
* We do not call this routine, Windows does.
|
|
*
|
|
* returns Nothing
|
|
*
|
|
**/
|
|
void CALLBACK GameTimerHook(HWND hWnd, unsigned int, uintptr nEventID, uint32) {
|
|
CDC *pDC;
|
|
HDC hDC;
|
|
bool done;
|
|
unsigned int nLeft, nTotal, nLeftAvg, nTotalAvg;
|
|
|
|
assert(nEventID == TIMER_ID);
|
|
|
|
if (bInGame && !bPause) {
|
|
|
|
hDC = GetDC(hWnd);
|
|
|
|
pDC = CDC::FromHandle(hDC);
|
|
|
|
/* beep once for each new letter revealed */
|
|
if (pGameParams->bSoundEffectsEnabled)
|
|
sndPlaySound(WAV_REVEAL, SND_ASYNC);
|
|
|
|
/* reveal another letter */
|
|
done = RevealNextLetter();
|
|
|
|
/* re-calculate letter positions */
|
|
|
|
/* refresh screen by displaying all letters/sprites in list */
|
|
RecalcDisplay(pDC);
|
|
|
|
GameGetScore(&nLeft, &nTotal, &nLeftAvg, &nTotalAvg);
|
|
UpdateScore(nLeft, nTotal, nLeftAvg, nTotalAvg);
|
|
|
|
/*
|
|
* if this was the last letter to be revealed, then user has lost the game
|
|
*/
|
|
if (done) {
|
|
gTotalAvg += StrLenNoSpaces(curPhrase->text);
|
|
|
|
GameGetScore(&nLeft, &nTotal, &nLeftAvg, &nTotalAvg);
|
|
UpdateScore(nLeft, nTotal, nLeftAvg, nTotalAvg);
|
|
|
|
bInGame = false;
|
|
GameStopTimer();
|
|
if (pGameParams->bSoundEffectsEnabled)
|
|
sndPlaySound(WAV_GAMEOVER, SND_ASYNC);
|
|
|
|
CMessageBox dlgGameOver((CWnd *)gMain, pMyGamePalette, "Game over.", "You have lost.");
|
|
gMain->SetFocus();
|
|
|
|
CleanScreen(pDC);
|
|
|
|
pGameParams->lScore += (100 * nLeft) / nTotal;
|
|
|
|
// if in metagame then quit dll when game is over
|
|
//
|
|
if ((pGameParams->bPlayingMetagame) && (gMain->m_nTurnCount == MAX_TURNS)) {
|
|
PostMessage(hWnd, WM_CLOSE, 0, 0);
|
|
} else {
|
|
gMain->PlayGame();
|
|
}
|
|
}
|
|
|
|
ReleaseDC(hWnd, hDC);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* name GameStopTimer - stops the timer for the current game
|
|
*
|
|
* synopsis GameStopTimer()
|
|
*
|
|
* purpose To stop or pause the Game Timer (TIMER_ID)
|
|
*
|
|
*
|
|
* returns Nothing
|
|
*
|
|
**/
|
|
void GameStopTimer() {
|
|
KillTimer(gGameWnd, TIMER_ID);
|
|
}
|
|
|
|
|
|
ERROR_CODE GameStartTimer() {
|
|
ERROR_CODE errCode = ERR_NONE;
|
|
|
|
if (curPhrase != nullptr) {
|
|
if (SetTimer(gGameWnd, TIMER_ID, timerInterval, GameTimerHook) != TIMER_ID)
|
|
errCode = ERR_UNKNOWN;
|
|
}
|
|
|
|
return errCode;
|
|
}
|
|
|
|
void GamePauseTimer() {
|
|
bPause = true;
|
|
}
|
|
|
|
void GameResumeTimer() {
|
|
bPause = false;
|
|
}
|
|
|
|
|
|
/**
|
|
* name StartGame - starts the game by activating the timer
|
|
*
|
|
* synopsis StartGame(hWnd, pDC)
|
|
* HWND hWnd
|
|
* CDC *pDC
|
|
*
|
|
* purpose
|
|
*
|
|
*
|
|
* returns errCode = Error return code
|
|
*
|
|
**/
|
|
ERROR_CODE StartGame(CDC *pDC) {
|
|
ERROR_CODE errCode;
|
|
|
|
assert(pDC != nullptr);
|
|
|
|
/*
|
|
* start the timer
|
|
*/
|
|
if ((errCode = GameStartTimer()) == ERR_NONE) {
|
|
|
|
/* show the first 3 letters */
|
|
RecalcDisplay(pDC);
|
|
}
|
|
|
|
return errCode;
|
|
}
|
|
|
|
|
|
/**
|
|
* name EndGame - ends the currently loaded game (performs cleanup, etc...)
|
|
*
|
|
* synopsis EndGame(pDC)
|
|
* CDC *pDC
|
|
*
|
|
* purpose To Halt, and clean up the current game
|
|
*
|
|
*
|
|
* returns errCode = Error return code
|
|
*
|
|
**/
|
|
ERROR_CODE EndGame(CDC *pDC) {
|
|
ERROR_CODE errCode;
|
|
|
|
assert(pDC != nullptr);
|
|
|
|
/* assume no error */
|
|
errCode = ERR_NONE;
|
|
|
|
if (pDC == nullptr) {
|
|
errCode = ERR_UNKNOWN;
|
|
} else {
|
|
|
|
/* reset phrase length in pixels */
|
|
nPhrasePixelLength = 0;
|
|
|
|
/* stop timer */
|
|
GameStopTimer();
|
|
|
|
/* clean up the screen */
|
|
CleanScreen(pDC);
|
|
}
|
|
|
|
return errCode;
|
|
}
|
|
|
|
void WinGame() {
|
|
unsigned int n;
|
|
|
|
// update the final score
|
|
//
|
|
gTotalAvg += (n = StrLenNoSpaces(curPhrase->text));
|
|
gLeftAvg += n - iNextLetter;
|
|
}
|
|
|
|
|
|
/**
|
|
* name GetGameParams - Retrieves game preferences from the user
|
|
*
|
|
* synopsis GetGameParams()
|
|
*
|
|
*
|
|
* purpose To get user input for the game parameters (via a dialog box)
|
|
*
|
|
*
|
|
* returns Nothing
|
|
*
|
|
**/
|
|
void CALLBACK GetGameParams(CWnd *pParentWnd) {
|
|
/*
|
|
* Our user preference dialog box is self contained in this object
|
|
*/
|
|
CUserCfgDlg dlgUserCfg(pParentWnd, pMyGamePalette, IDD_USERCFG);
|
|
|
|
//dlgUserCfg.DoModal();
|
|
}
|
|
|
|
|
|
/**
|
|
* name LoadGameCfg - Loads game configuration info from INI file
|
|
*
|
|
* synopsis LoadGameCfg();
|
|
*
|
|
*
|
|
* purpose To load configuration information from this game's INI file
|
|
*
|
|
*
|
|
* returns Nothing
|
|
*
|
|
**/
|
|
void LoadGameCfg() {
|
|
char buf[10];
|
|
int n;
|
|
|
|
if (pGameParams->bPlayingMetagame) {
|
|
|
|
// set defaults
|
|
//
|
|
gGameCfg.bRandomLetters = false;
|
|
gGameCfg.nShown = SHOWN_DEF;
|
|
gGameCfg.bShowNames = true;
|
|
|
|
switch (pGameParams->nSkillLevel) {
|
|
|
|
case SKILLLEVEL_LOW:
|
|
gGameCfg.gameSpeed = 3; //2;
|
|
break;
|
|
|
|
case SKILLLEVEL_MEDIUM:
|
|
gGameCfg.gameSpeed = 6; //5;
|
|
break;
|
|
|
|
case SKILLLEVEL_HIGH:
|
|
gGameCfg.gameSpeed = 9; //8;
|
|
break;
|
|
|
|
default:
|
|
assert(0);
|
|
break;
|
|
}
|
|
|
|
} else {
|
|
|
|
/*
|
|
* User can specify if he/she wants the letters to appear in a random order
|
|
* or in the predefined fixed order set by the MetaGame
|
|
*/
|
|
GetPrivateProfileString(INI_SECTION, "RandomLetters", "No", buf, 10, INI_FILENAME);
|
|
assert(strlen(buf) < 10);
|
|
gGameCfg.bRandomLetters = false;
|
|
if (!scumm_stricmp(buf, "Yes"))
|
|
gGameCfg.bRandomLetters = true;
|
|
|
|
/*
|
|
* get the number of letters that are intially displayed (default is SHOWN_DEF = 3)
|
|
*/
|
|
n = gGameCfg.nShown = GetPrivateProfileInt(INI_SECTION, "NumStartingLetters", SHOWN_DEF, INI_FILENAME);
|
|
|
|
/* validate this setting */
|
|
if ((n < SHOWN_MIN) || (n > SHOWN_MAX))
|
|
gGameCfg.nShown = SHOWN_DEF;
|
|
|
|
/*
|
|
* Get the game speed (1..10) (default is SPEED_DEF = 8)
|
|
*/
|
|
n = gGameCfg.gameSpeed = GetPrivateProfileInt(INI_SECTION, "GameSpeed", SPEED_DEF, INI_FILENAME);
|
|
|
|
/* validate this setting */
|
|
if ((n < SPEED_MIN) || (n > SPEED_MAX))
|
|
gGameCfg.gameSpeed = SPEED_DEF;
|
|
|
|
GetPrivateProfileString(INI_SECTION, "ShowCategoryNames", "Yes", buf, 10, INI_FILENAME);
|
|
assert(strlen(buf) < 10);
|
|
gGameCfg.bShowNames = false;
|
|
if (!scumm_stricmp(buf, "Yes"))
|
|
gGameCfg.bShowNames = true;
|
|
}
|
|
|
|
timerInterval = (10 - gGameCfg.gameSpeed) * 500 + 500;
|
|
}
|
|
|
|
|
|
/**
|
|
* name SaveGameCfg - Saves configuraton info
|
|
*
|
|
* synopsis SaveGameCfg();
|
|
*
|
|
*
|
|
* purpose To Save this game's configuration info to an INI file.
|
|
* The INI file contains user defineable aspects of this game.
|
|
*
|
|
* returns Nothing
|
|
*
|
|
**/
|
|
void SaveGameCfg() {
|
|
WritePrivateProfileString(INI_SECTION, "RandomLetters",
|
|
gGameCfg.bRandomLetters ? "Yes" : "No", INI_FILENAME);
|
|
WritePrivateProfileString(INI_SECTION, "NumStartingLetters",
|
|
Common::String::format("%d", gGameCfg.nShown).c_str(),
|
|
INI_FILENAME);
|
|
WritePrivateProfileString(INI_SECTION, "GameSpeed",
|
|
Common::String::format("%d", gGameCfg.gameSpeed).c_str(),
|
|
INI_FILENAME);
|
|
WritePrivateProfileString(INI_SECTION, "ShowCategoryNames",
|
|
gGameCfg.bShowNames ? "Yes" : "No",
|
|
INI_FILENAME);
|
|
}
|
|
|
|
|
|
/**
|
|
* name CheckUserGuess - compares user's guess with current phrase
|
|
*
|
|
* synopsis winStatus = CheckUserGuess(guess)
|
|
* const char *guess users guess to check vs phrase
|
|
*
|
|
* purpose
|
|
*
|
|
*
|
|
* returns match/unmatch condition (True if users guess matches the phrase)
|
|
*
|
|
**/
|
|
bool CheckUserGuess(const char *guess) {
|
|
return (StrCompare(curPhrase->text, guess, MAX_PLENGTH_S + 1));
|
|
}
|
|
|
|
/**
|
|
* name StrLenNoSpaces - gets the length of a string after stripping
|
|
* out any spaces.
|
|
*
|
|
* synopsis StrLenNoSpaces(string)
|
|
* const char *string string to get length of
|
|
*
|
|
* purpose To determine the length of a string while not counting spaces
|
|
*
|
|
*
|
|
* returns len = length of string without spaces
|
|
*
|
|
**/
|
|
int StrLenNoSpaces(const char *str) {
|
|
int len;
|
|
|
|
/* can't access a null pointer */
|
|
assert(str != nullptr);
|
|
|
|
len = 0;
|
|
while (*str) {
|
|
if (*str++ != ' ') {
|
|
len++;
|
|
}
|
|
}
|
|
return (len);
|
|
}
|
|
|
|
|
|
ERROR_CODE ValidatePhrase(PHRASES *phrase) {
|
|
bool bList[MAX_PLENGTH];
|
|
int i, n, order;
|
|
char c;
|
|
ERROR_CODE errCode;
|
|
|
|
assert(phrase != nullptr);
|
|
|
|
/* set all entries to false */
|
|
memset(bList, 0, sizeof(bool)*MAX_PLENGTH);
|
|
|
|
/* assume no error */
|
|
errCode = ERR_NONE;
|
|
|
|
if ((n = strlen(phrase->text)) > MAX_PLENGTH_S) {
|
|
errCode = ERR_FTYPE;
|
|
} else {
|
|
for (i = 0; i < n; i++) {
|
|
|
|
c = phrase->text[i];
|
|
|
|
/*
|
|
* verify that all characters in this phrase are valid.
|
|
* valid chars are are '\0', ' ' or a letter
|
|
*/
|
|
if ((c != 0) && (c != 32) && !Common::isAlpha(c)) {
|
|
errCode = ERR_FTYPE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* continues as long as there was no error
|
|
*/
|
|
if (errCode == ERR_NONE) {
|
|
|
|
if ((n = StrLenNoSpaces(phrase->text)) > MAX_PLENGTH) {
|
|
|
|
errCode = ERR_FTYPE;
|
|
} else {
|
|
|
|
/*
|
|
* check to make sure that the indexing order values are valid
|
|
*/
|
|
for (i = 0; i < n; i++) {
|
|
order = (int)phrase->order[i] - 1;
|
|
|
|
if ((order >= n) || (order < 0) || bList[order]) {
|
|
|
|
errCode = ERR_FTYPE;
|
|
break;
|
|
}
|
|
bList[order] = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return errCode;
|
|
}
|
|
|
|
|
|
void GameGetScore(unsigned int *nLeft, unsigned int *nTotal, unsigned int *nLeftAvg, unsigned int *nTotalAvg) {
|
|
/* can't write to null pointers */
|
|
assert(nLeft != nullptr);
|
|
assert(nTotal != nullptr);
|
|
assert(nLeftAvg != nullptr);
|
|
assert(nTotalAvg != nullptr);
|
|
|
|
*nLeft = *nTotal = 0;
|
|
if (bInGame) {
|
|
|
|
*nTotal = StrLenNoSpaces(curPhrase->text);
|
|
*nLeft = *nTotal - iNextLetter;
|
|
}
|
|
*nLeftAvg = gLeftAvg;
|
|
*nTotalAvg = gTotalAvg;
|
|
}
|
|
|
|
/**
|
|
* name NumLinkedSprites - gets number of sprites currently in the sprite
|
|
* chain
|
|
*
|
|
* synopsis nSprites = NumLinkedSprites()
|
|
*
|
|
*
|
|
* purpose To return the number of linked sprites
|
|
*
|
|
*
|
|
* returns nSprites = number of sprites in linked list
|
|
*
|
|
**/
|
|
int NumLinkedSprites() {
|
|
CSprite *pSprite;
|
|
int i = 0;
|
|
|
|
pSprite = CSprite::GetSpriteChain();
|
|
while (pSprite) {
|
|
pSprite = pSprite->GetNextSprite();
|
|
i++;
|
|
}
|
|
|
|
return (i);
|
|
}
|
|
|
|
} // namespace PDQ
|
|
} // namespace HodjNPodj
|
|
} // namespace Bagel
|