home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Windoware
/
WINDOWARE_1_6.iso
/
source
/
bagosrc
/
bago.c
< prev
next >
Wrap
C/C++ Source or Header
|
1992-02-26
|
183KB
|
5,006 lines
/* Notes to Localizers:
* This program was designed to work specifically with English-language
* words, but it is possible to adapt it to run in any given language
* with an 8-bit character set. Making this work with a 16-bit character
* set would require redefining some basic structures.
* A level 1 modification will make the game playable in another language:
* 1) Translate the help file from BAGO.DOC, then recompile into BAGO.HLP.
* 2) Assign new letters to the dice[][] array with a suitable distribution
* for your language.
* 3) Change hardwired strings [use your editor to search for double-
* quote (") marks] to your language.
* 4) Check that the string functions such as strupr(), toupper(), and
* isupper() work properly for your language.
* 5) You must discard the BAGO.DIC dictionary, but can generate a new
* one simply by playing the game.
* 6) Generate bitmaps for any additional characters you need. See the
* WM_PAINT case of the CubeButton window procedure.
* A level 2 modification would involve making the suffixing routines work
* properly in your language.
* 6) Rewrite AddSuffix().
* 7) Rewrite RemoveSuffix().
* 8) May need to rewrite macros isdoub() and isvowel().
* 9) May wish to rewrite isword(), or discard the function completely.
* 10) Change AddQu() and RemoveQu() routines if necessary. This is
* because in English, 'U' always follows 'Q'. You may need to write
* some of your own routines to do similar things.
* 11) Be aware that some of the 'reward' bitmaps may be illegal in some
* countries, or at least illegal for distribution to minors. You
* may wish to discard this feature.
*
* You may freely modify this source at no charge. However, I would be
* interested in seeing your results if you do.
*/
/****************************************************************************
PROGRAM: Bago.c
PURPOSE: Boggle game for MicroSoft Windows
Author: Roderick Young. (Pen name H.G. Wrekshun)
FUNCTIONS:
WinMain() - calls initialization function, processes message loop
BagoInit() - initializes window data and registers window
BagoWndProc() - processes messages
About() - processes messages for "About" dialog box
Modifications:
Q.00.00 Hello, World.
Q.00.01 First playable game.
Q.00.02 Added game timer.
Q.00.03 Added tree structure for dictionary.
Q.00.04 Added capability to write out dictionary.
Q.00.05 Added capability to read dictionary. Added Resize function.
Q.00.06 Converted Egg Timer to its own window.
Q.00.07 Added dialog box for editing words in dictionary.
Q.00.08 Added routine to compute suffixes of words.
Q.00.09 Added Prev and Next to Edit dictionary dialog box
Q.00.10 Added Delete to Edit dictionary dialog box
Q.00.11 Added dictionary optimization.
Q.00.12 Added beep sound at end of game.
Q.00.13 Converted Egg Timer to autonomous Graphic hourglass window.
Q.01.00 Added window for computer play.
Q.01.01 Deleted rarely used -OUS, -ION, -ITY suffixes
Q.01.02 Added RemoveSuffix. Added Learn mode.
Q.02.00 Added SearchBoard and psearch for computer word search.
Q.02.01 Eliminated flicker in updating of computer word list.
Q.02.02 Improved efficiency of psearch by 34% (killed more branches)
Q.02.03 Added suffix capability to computer search.
Words found increased by 44%
Q.02.04 Changed optimize to use windows global memory.
Search routine yield made to pass through keystrokes.
Q.02.05 A fixed bug in search algorithm. Now finding 12% more words,
search length up to 300% of previous.
Q.02.06 Checking for word validity added.
Q.02.07 Processing of Qu cube added. Stored as 'Q' internally.
Q.02.08 General input routine added. Dictionary cull added.
Q.02.09 Added Bago Cube font.
Q.02.10 Added variable difficulty level (smartness). Improved font.
Q.02.11 Added rack numbers and ability to go back to certain rack.
Also made general input box set default values.
Added HELP reference card.
Q.02.12 Added statistics display and clear statistics.
Q.02.13 Added load and save rack.
Q.02.14 Added reset frequencies.
Fixed problem of input words > 10 characters.
Q.02.15 Fixed deleting hTreeTop word bug. Edit Dict dialog now
initializes with a starter word.
Q.02.16 Avoid reading full dictionary if not enough heap space.
Avoid learning words if not enough heap space.
Save rack now includes both player and computer word lists.
Q.02.17 Question last user word if incomplete. Fix cube spacing
to work with Bago font on EGA.
Q.02.18 Computer search aborts when game over.
A.01.00 First release as Freeware. New ABOUTBOX.
Prev, Next, Virgin edit no longer blank current word.
A.01.01 Check for success on all GlobalAlloc.
Width of Word list boxes now relative to character width.
FindLeaf checks suffixes as well as root word.
A.01.02 Save and restore settings from WIN.INI
2.00 New version convention. Accelerators added.
2.01 Clicking on hourglass now starts new game.
3.00 Port to windows 3.0. ES_UPPERCASE added to edit window.
3.01 Fixed listbox windows to handle proportional fonts.
Private profile file is now used.
3.02 Added isword() routine to loosely check for bogus words.
3.03 Added bitmap of eyes to menu, later to use for rewards.
3.04 Added progress box while loading dictionary.
3.05 Changed from special font to bitmapped controls for cubes.
Cubes are still output only.
3.06 Moved RackNumber display from main window to title bar.
3.07 Added WinHelp file.
3.08 Made cube controls work for input. Disable dictionary
functions during a game.
3.09 Automatic prompts to save dictionary in places.
Progress box added for Cull and Optimize. Some message
boxes taken out.
3.10 Got rid of annoying flash on STOP control. Autodetect
color/mono and do displays to suit.
3.11 Changed InputBox to use DialogBoxParam. With 2.0 version,
needed to use a global to pass input data.
3.12 Added Pic box to display reward pictorals. Release to BBSes.
3.13 Fixed bug: main title getting messed up, not static.
3.14 Added tabstops so that scores line up right justified.
3.15 Added -ERS, -INGS suffixes.
3.16 Dict edit bug fixed - deleting leaf w/ no inferiors.
Added DictChanged flag. Distributed to BBses.
3.17 Added rand() when player types, so that computer won't
always find the same words in a given game.
3.18 Feature of showing the path by which a word is formed.
3.19 Patch color depth detection to handle Super VGA and above.
Compute initial sizing of window if none in .INI file.
Focus set to word list after game (for keyboard-only players)
TAB moves between list boxes (for KB-only users)
****************************************************************************/
#include <ctype.h>
#include <stdlib.h>
#include <math.h> /* MUST have this, for drawing egg timer */
#include <io.h>
#include <windows.h> /* required for all Windows applications */
#include "bago.h" /* specific to this program */
/* vowels include Y and W for my purpose */
#define isvowel(c) (c=='A' || c=='E' || c=='I' || c=='O' || c=='U' || c=='W' || c=='Y')
/* letters which can be doubled */
#define isdoub(c) (!isvowel(c) && c!='H' && c!='J' && c!='Q' && c!='W' && c!='X' && c!='Y')
HANDLE hInst; /* current instance of main window */
HWND hUEdit; /* edit window of words typed by player */
HWND hUList = NULL; /* listbox for player's words */
HWND hCList = NULL; /* Computer's word list window */
HWND hEgg = NULL; /* Handle for Egg Timer window */
HWND hEnter; /* Enter and STOP keys */
HWND hStop;
HANDLE hAccTable; /* Handle for Menu accelerator table */
/* All the words in the dictionary are kept in a tree structure. */
HANDLE hTreeTop; /* the top of the tree containing the words */
HANDLE hCListTop; /* top of tree for list of words computer found */
HCURSOR hHourGlass; /* standard hourglass cursor */
HBITMAP hEyes; /* bitmap of eyes */
HBRUSH hGrayBrush0, hGrayBrush1, hGrayBrush2;
/* DEBUG make this a static local later, and pass to functions needing it */
HANDLE hWord; /* Used by EditWord dialog box. */
/* Handle to record for word being processed */
/* Global variables for the game */
/* A master list of the dice and what they look like on each face. */
char dice[NDICE][7] = {
{0,'A','A','A','F','R','S'},
{0,'A','A','F','I','R','S'},
{0,'A','D','E','N','N','N'},
{0,'A','E','E','E','E','M'},
{0,'A','E','E','E','E','R'},
{0,'A','E','E','G','M','U'},
{0,'A','E','G','M','N','N'},
{0,'A','F','I','R','S','Y'},
{0,'B','J','K','Q','X','Z'},
{0,'C','C','E','N','S','T'},
{0,'C','E','I','I','L','T'},
{0,'C','E','I','L','P','T'},
{0,'C','E','I','P','S','T'},
{0,'D','D','H','N','O','T'},
{0,'D','H','H','L','O','R'},
{0,'D','H','L','N','O','R'},
{0,'D','H','L','N','O','R'},
{0,'E','I','I','I','T','T'},
{0,'E','M','O','T','T','T'},
{0,'E','N','S','S','S','U'},
{0,'F','I','P','R','S','Y'},
{0,'G','O','R','R','V','W'},
{0,'I','P','R','R','R','Y'},
{0,'N','O','O','T','U','W'},
{0,'O','O','O','T','T','U'} };
/* The playing board. The master dice are shuffled into positions on
* this board at the start of each game.
*/
BOARD board[NROWS][NCOLS];
BOOL UseTimer; /* True if games are to be timed */
BOOL CPlay; /* True if computer play option is enabled */
BOOL Learn; /* True if computer is to learn words from user */
BOOL RotCubes; /* True if cubes are to be rotated */
BOOL Sound; /* True if sound enabled */
BOOL Rewards; /* True if pictoral rewards are given */
BOOL Mono; /* True if we are on monochrome display */
BOOL GameOver; /* True if game not presently in play */
int EndTime; /* Game duration in seconds */
int Smartness; /* Level of challenge */
int NGames = 0; /* Number of games played */
int RUScore = 0; /* Running Player score */
int RCScore = 0; /* Running Computer score */
int RUWords = 0; /* Running Player # of words found */
int RCWords = 0; /* Running Computer # of words found */
int RackNumber, NextRackNumber; /* Allows consistent regeneration of racks */
/* by forcing the RNG initialization */
char NextVal[NROWS][NCOLS]; /* These hold the values of the playing cubes */
char NextOrient[NROWS][NCOLS]; /* for the next game. This funny scheme is */
/* needed in order to restore an arbitrary */
/* game from a file */
BOOL OptimizeFail; /* TRUE if optimize GlobalAlloc failed */
int TScoreWidth; /* Width of the string, "Total Score: " */
int ListWinWidth; /* Width of User and Computer list boxes */
int TabStops[3]; /* For list box windows */
CUBELOC CubeStack[NDICE]; /* stack to keep track of cubes picked by user */
int CubeStackPtr = 0; /* points to first free space in stack */
BOOL DictChanged; /* TRUE if dictionary has changed since loading */
/****************************************************************************
FUNCTION: WinMain(HANDLE, HANDLE, LPSTR, int)
PURPOSE: calls initialization function, processes message loop
COMMENTS:
This will initialize the window class if it is the first time this
application is run. It then creates the window, and processes the
message loop until a PostQuitMessage is received. It exits the
application by returning the value passed by the PostQuitMessage.
****************************************************************************/
int PASCAL WinMain(hInstance, hPrevInstance, lpszCmdLine, nCmdShow)
HANDLE hInstance; /* current instance */
HANDLE hPrevInstance; /* previous instance */
LPSTR lpszCmdLine; /* command line */
int nCmdShow; /* show-window type (open/icon) */
{
HWND hWnd; /* window handle */
MSG msg; /* message */
char ProString[80]; /* Holds profile string */
int x0, y0, x1, y1;
int ColorDepth; /* true color depth */
int VertRes; /* Kludge way to determine EGA/VGA */
HWND hDesk;
HDC hDeskDC;
/* find out some system metrics for sizing window and choosing bitmaps */
hDesk = GetDesktopWindow(); /* any window will do */
hDeskDC = GetDC(hDesk);
/* for sizing */
TScoreWidth = LOWORD(GetTextExtent(hDeskDC, "Total Score: ", 16));
ListWinWidth = TScoreWidth + LOWORD(GetTextExtent(hDeskDC, "0000", 4))
+ GetSystemMetrics(SM_CXVSCROLL);
/* find out what kind of monitor we're on */
ColorDepth = GetDeviceCaps(hDeskDC, PLANES) * GetDeviceCaps(hDeskDC, BITSPIXEL);
VertRes = GetDeviceCaps(hDeskDC, VERTRES);
ReleaseDC(hDesk, hDeskDC);
Mono = (ColorDepth <= 2);
/* must have brushes BEFORE setting window class */
if (ColorDepth < 3)
{
hGrayBrush0 = GetStockObject(WHITE_BRUSH);
hGrayBrush1 = hGrayBrush0;
hGrayBrush2 = GetStockObject(BLACK_BRUSH);
}
else if (VertRes < 480) /* probably EGA */
{
hGrayBrush0 = CreateSolidBrush(GRAY1);
hGrayBrush1 = hGrayBrush0;
hGrayBrush2 = CreateSolidBrush(GRAY2);
}
else /* assume VGA or better */
{
hGrayBrush0 = CreateSolidBrush(GRAY0);
hGrayBrush1 = CreateSolidBrush(GRAY1);
hGrayBrush2 = CreateSolidBrush(GRAY2);
}
if (!hPrevInstance) /* Has application been initialized? */
if (!BagoInit(hInstance))
return (NULL); /* Exits if unable to initialize */
hInst = hInstance; /* Saves the current instance */
/* Restore the last position, if any */
/* if no last position, compute a reasonable size and position */
if (GetPrivateProfileString("Bago", "Window", "", ProString, 80, BAGOINI))
sscanf(ProString, "%d %d %d %d", &x0, &y0, &x1, &y1);
else
{
x0 = 5;
y0 = 5;
x1 = 5 + 2*GetSystemMetrics(SM_CXFRAME)
+ 5*32 /* cubes */
+ 37 /* egg timer */
+ 140 /* slop around egg timer */
+ 2*ListWinWidth;
y1 = 5 + 2*GetSystemMetrics(SM_CYFRAME)
+ GetSystemMetrics(SM_CYCAPTION)
+ GetSystemMetrics(SM_CYMENU)
+ 5*32; /* cubes */
}
hWnd = CreateWindow("Bago", /* window class */
"Bago", /* window name */
WS_OVERLAPPEDWINDOW, /* window style */
x0, /* x position */
y0, /* y position */
x1-x0, /* width */
y1-y0, /* height */
NULL, /* parent handle */
NULL, /* menu or child ID */
hInstance, /* instance */
NULL); /* additional info */
if (!hWnd) /* Was the window created? */
return (FALSE);
ShowWindow(hWnd, nCmdShow); /* Shows the window */
while (GetMessage(&msg, /* message structure */
NULL, /* handle of window receiving the message */
NULL, /* lowest message to examine */
NULL)) /* highest message to examine */
{
if (!TranslateAccelerator(hWnd, hAccTable, &msg))
{
TranslateMessage(&msg); /* Translates virtual key codes */
DispatchMessage(&msg); /* Dispatches message to window */
}
}
return (msg.wParam); /* Returns the value from PostQuitMessage */
}
/****************************************************************************
FUNCTION: BagoInit(HANDLE)
PURPOSE: Initializes window data and registers window class
COMMENTS:
Sets up a structure to register the window class. Structure includes
such information as what function will process messages, what cursor
and icon to use, etc.
****************************************************************************/
BOOL BagoInit(hInstance)
HANDLE hInstance; /* current instance */
{
HANDLE hMemory; /* handle to allocated memory */
PWNDCLASS pWndClass; /* structure pointer */
BOOL bSuccess; /* RegisterClass() result */
OFSTRUCT OfStruct;
/* the reason we alloc instead of just having automatic variable */
/* is to get the whole structure zeroed out */
hMemory = LocalAlloc(LPTR, sizeof(WNDCLASS));
pWndClass = (PWNDCLASS) LocalLock(hMemory);
pWndClass->style = NULL;
pWndClass->lpfnWndProc = BagoWndProc;
pWndClass->hInstance = hInstance;
pWndClass->hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(BAGOICON));
pWndClass->hCursor = LoadCursor(NULL, IDC_ARROW);
pWndClass->hbrBackground = hGrayBrush0;
pWndClass->lpszMenuName = MAKEINTRESOURCE(BAGOMENU);
pWndClass->lpszClassName = (LPSTR) "Bago";
bSuccess = RegisterClass(pWndClass);
if (!bSuccess)
{
LocalUnlock(hMemory); /* unlock memory and return it to windows */
LocalFree(hMemory);
return(FALSE); /* registering window failed */
}
/* now register a class for the Egg Timer window */
pWndClass->style = NULL;
pWndClass->lpfnWndProc = EggWndProc;
pWndClass->hInstance = hInstance;
pWndClass->hIcon = NULL;
pWndClass->hCursor = LoadCursor(hInstance, MAKEINTRESOURCE(BAGOCUR));
pWndClass->hbrBackground = hGrayBrush1;
pWndClass->lpszMenuName = (LPSTR) NULL;
pWndClass->lpszClassName = (LPSTR) "Egg";
bSuccess = RegisterClass(pWndClass);
if (!bSuccess)
{
LocalUnlock(hMemory); /* unlock memory and return it to windows */
LocalFree(hMemory);
return(FALSE); /* registering window failed */
}
/* The Progress Box class */
pWndClass->style = NULL;
pWndClass->lpfnWndProc = ProWndProc;
pWndClass->hInstance = hInstance;
pWndClass->hIcon = NULL;
pWndClass->hCursor = NULL;
pWndClass->hbrBackground = hGrayBrush1;
pWndClass->lpszMenuName = NULL;
pWndClass->lpszClassName = "Pro";
bSuccess = RegisterClass(pWndClass);
if (!bSuccess)
{
LocalUnlock(hMemory); /* unlock memory and return it to windows */
LocalFree(hMemory);
return(FALSE); /* registering window failed */
}
/* Cube button class */
pWndClass->style = NULL;
pWndClass->lpfnWndProc = CubeWndProc;
pWndClass->hInstance = hInstance;
pWndClass->hIcon = NULL;
pWndClass->hCursor = LoadCursor(hInstance, MAKEINTRESOURCE(BAGOCUR));
pWndClass->hbrBackground = NULL;
pWndClass->lpszMenuName = NULL;
pWndClass->lpszClassName = "CubeButton";
bSuccess = RegisterClass(pWndClass);
if (!bSuccess)
{
LocalUnlock(hMemory); /* unlock memory and return it to windows */
LocalFree(hMemory);
return(FALSE); /* registering window failed */
}
/* Pictoral display class */
pWndClass->style = NULL;
pWndClass->lpfnWndProc = PicWndProc;
pWndClass->hInstance = hInstance;
pWndClass->hIcon = NULL;
pWndClass->hCursor = LoadCursor(hInstance, MAKEINTRESOURCE(BAGOCUR));
pWndClass->hbrBackground = hGrayBrush2;
pWndClass->lpszMenuName = NULL;
pWndClass->lpszClassName = "Pic";
bSuccess = RegisterClass(pWndClass);
LocalUnlock(hMemory); /* unlock memory and return it to windows */
LocalFree(hMemory);
return (bSuccess); /* Returns result of registering the window */
}
/****************************************************************************
FUNCTION: BagoWndProc(HWND, unsigned, WORD, LONG)
PURPOSE: Processes messages
MESSAGES:
WM_CREATE - create window
WM_PAINT - repaint window
WM_DESTROY - destroy window
COMMENTS:
****************************************************************************/
long FAR PASCAL BagoWndProc(hWnd, message, wParam, lParam)
HWND hWnd; /* window handle */
unsigned message; /* type of message */
WORD wParam; /* additional information */
LONG lParam; /* additional information */
{
switch (message) {
case WM_SYSCOMMAND: /* message: command from system menu */
if (wParam == MN_REWARDS)
{
ProcessBagoRewards(hWnd, wParam);
break;
}
else /* Lets Windows process it */
return (DefWindowProc(hWnd, message, wParam, lParam));
case WM_CREATE: /* message: window being created */
ProcessBagoCreate(hWnd, wParam, lParam);
break;
case BAGOM_INIT:
/* A call to initialize. This is only called once per instance. */
/* These actions are done here rather than at WM_CREATE message, */
/* because I want the window to appear immediately, rather than */
/* have an annoying pause while everything is initializing */
LoadDictionary(hWnd);
break;
case WM_COMMAND: /* process menu selection */
ProcessBagoCommand(hWnd, wParam, lParam);
break;
case WM_PAINT:
ProcessBagoPaint(hWnd);
break;
case WM_SIZE:
ProcessBagoSize(hWnd, wParam, lParam);
break;
case WM_SETFOCUS:
/* This is in case the player came back from some other task */
if (!GameOver) SetFocus(hUEdit);
else
{
if (CPlay) SetFocus(hCList); /* so that keyboard only user */
else SetFocus(hUList); /* can see word paths */
}
break;
case BAGOM_CUBEDN:
/* given when a cube pressed down by player */
/* wparam contains CUBELOC of cube */
PushCube(wParam);
EnableAroundCube(wParam);
break;
case BAGOM_CUBEUP:
/* given when a cube popped up by player */
EnableAroundCube(PopCube());
break;
case WM_DRAWITEM:
ProcessBagoDrawItem(hWnd, lParam);
break;
case BAGOM_ENDGAME:
/* do not end the game if it is already over */
if (!GameOver) ProcessBagoEndGame(hWnd);
break;
case WM_CLOSE: /* message BEFORE window is destroyed */
ProcessBagoClose(hWnd);
DestroyWindow(hWnd);
break;
case WM_DESTROY: /* message: window being destroyed */
PostQuitMessage(0);
break;
default: /* Passes it on if unproccessed */
return (DefWindowProc(hWnd, message, wParam, lParam));
}
return (NULL);
}
/* Return a near pointer to a copy of a far string.
* Warnings: will die on strings of length > 20.
* will destroy the near copy of the string on subsequent calls
*/
char NEAR *FarToNearStr(FarStr)
LPSTR FarStr;
{
static char NEAR NearStr[20];
char *NearPtr;
NearPtr = NearStr;
while (*NearPtr++ = *FarStr++);
return(NearStr);
}
/* CubeStackPtr points to first free space in the stack */
PushCube(cube)
CUBELOC cube;
{
if (CubeStackPtr < NDICE)
{
CubeStack[CubeStackPtr] = cube;
CubeStackPtr++;
}
}
/* If the stack is empty, returns a virtual cube whose neighbors cannot */
/* be in the rack. This is important, as it will be used by the */
/* EnableAroundCube() routine. This is not a normal type pop routine */
/* WARNING: returns the element REMAINING on the top of the stack, NOT */
/* the element that was just popped off */
CUBELOC PopCube()
{
CUBELOC OffBoard;
if (CubeStackPtr > 0) CubeStackPtr--;
if (CubeStackPtr > 0) return(CubeStack[CubeStackPtr-1]);
else
{
OffBoard.row = -1; OffBoard.col = -1;
return(OffBoard);
}
}
/* This enables or disables all cube windows at once, according to the */
/* value of the parameter passed. */
EnableCubes(bEnable)
BOOL bEnable;
{
int row, col;
for (row=0; row < NROWS; row++)
for (col=0; col < NCOLS; col++)
{
EnableWindow(board[row][col].hWindow, bEnable);
}
}
/* This enables all neighbors to a cube except those which are */
/* already pressed down. Any other cubes in the rack are disabled */
/* If the cube specified is not on the board, all cubes are enabled */
EnableAroundCube(cube)
CUBELOC cube;
{
int row, col;
if (cube.row < 0 || cube.col < 0)
EnableCubes(TRUE);
else
{
for (row=0; row < NROWS; row++)
for (col=0; col < NCOLS; col++)
{
if (cube.col==col && cube.row==row) EnableWindow(board[row][col].hWindow, TRUE);
else if ((cube.col==col-1 || cube.col==col+1 || cube.col==col) &&
(cube.row==row-1 || cube.row==row+1 || cube.row==row))
EnableWindow(board[row][col].hWindow, !board[row][col].Uused);
else
EnableWindow(board[row][col].hWindow, FALSE);
}
}
}
/* This routine sets up everything for a new game. It scraps any existing */
/* information in the word list windows, and displays a new board. */
NewGame(hWnd)
HWND hWnd;
{
int row, col; /* used to step through board array */
HANDLE hOldBuffer; /* the User's word list */
static char Title[20]; /* static because will be in title */
GameOver = FALSE;
CubeStackPtr = 0; /* empty the Cube stack */
/* disallow dictionary menu picks while game in progress */
EnableMenuItem(GetMenu(hWnd), 2, MF_BYPOSITION | MF_DISABLED | MF_GRAYED);
DrawMenuBar(hWnd);
/* set up the rack, and report it in title bar */
RackNumber = NextRackNumber;
if (RackNumber >= 0)
sprintf(Title, "Bago - Game #%d", RackNumber);
else
/* Some games do not have rack numbers, like restored games */
strcpy(Title, "Bago");
/* not using SetWindowText because no rush */
PostMessage(hWnd, WM_SETTEXT, 0, (LONG)(LPSTR) Title);
/* Now draw the new cubes */
for (row=0; row < NROWS; row++)
for (col=0; col < NCOLS; col++)
{
board[row][col].val = NextVal[row][col];
board[row][col].orient = NextOrient[row][col];
board[row][col].Uused = FALSE;
board[row][col].Cused = FALSE;
InvalidateRect(board[row][col].hWindow, NULL, FALSE);
}
EnableCubes(TRUE);
EnableWindow(hEnter, TRUE);
EnableWindow(hStop, TRUE);
EnableWindow(hUEdit, TRUE);
/* Clear out the old word list from player's edit */
hOldBuffer = SendMessage(hUEdit, EM_GETHANDLE, 0, 0L);
if (hOldBuffer) LocalFree(hOldBuffer);
/* Allocate small buffer - edit control will expand if needed */
hOldBuffer = LocalAlloc(LMEM_MOVEABLE | LMEM_ZEROINIT, USERWORDLEN);
SendMessage(hUEdit, EM_SETHANDLE, hOldBuffer, 0L);
ShowWindow(hUEdit, SW_SHOW);
ShowWindow(hUList, SW_HIDE);
SetFocus(hUEdit); /* direct input to the word list */
/* Clear player's list (it is under the edit window) */
SendMessage(hUList, LB_RESETCONTENT, 0, 0L);
/* Start game timer if there is one */
if (hEgg) SendMessage(hEgg, EGGM_START, 0, 0L);
/* If the computer is playing */
if (CPlay)
{
/* clear the computer's list */
SendMessage(hCList, LB_RESETCONTENT, 0, 0L);
DestroyLeaf(hCListTop);
hCListTop = NULL;
/* start computer search */
for (row=0; row < NROWS; row++)
for (col=0; col < NCOLS; col++)
SearchBoard(&board[row][col]);
}
}
/* General Input */
/* Prompt is the prompt string for the input, target output goes to integer
* OutI if Mode is TRUE, else string goes to OutString
* Values are returned in OutString and OutI. Note: OutString must have
* space for 80 characters.
*/
/* Status is returned as a WORD */
WORD InputBox(hWnd, Prompt, OutString, OutI, IntMode)
HWND hWnd;
LPSTR Prompt;
LPSTR OutString;
LPINT OutI;
BOOL IntMode;
{
FARPROC lpInputDiag;
INPUTSTRUCT InputStruct;
WORD result; /* either OK or CANCEL */
InputStruct.Prompt = Prompt;
InputStruct.OutString = OutString; /* default value for input */
InputStruct.OutI = OutI;
InputStruct.IntMode = IntMode;
lpInputDiag = MakeProcInstance((FARPROC) InputDiag, hInst);
result = DialogBoxParam(hInst, MAKEINTRESOURCE(INPUTDIALOG), hWnd, lpInputDiag, (LONG) (LPSTR) &InputStruct);
FreeProcInstance(lpInputDiag);
return(result);
}
/* Tricky use of lParam -> Mode: upon entry, indicates integer or text mode */
/* Upon exit, indicates the button (OK or CANCEL) pressed */
BOOL FAR PASCAL InputDiag(hDlg, message, wParam, lParam)
HWND hDlg;
unsigned message;
WORD wParam;
LONG lParam;
{
BOOL TransOK;
static LPINPUTSTRUCT InputStruct;
switch (message)
{
case WM_INITDIALOG: /* message: initialize dialog box */
InputStruct = (LPINPUTSTRUCT) lParam; /* remember our structure for later */
SetDlgItemText(hDlg, ID_IMSG, InputStruct -> Prompt);
SetFocus(GetDlgItem(hDlg, ID_INPUT));
/* set default value and select entire string */
SetDlgItemText(hDlg, ID_INPUT, InputStruct -> OutString);
SendMessage(GetDlgItem(hDlg, ID_INPUT),
EM_SETSEL, 0, MAKELONG(0, 32767));
return (FALSE); /* so windows won't arbitrarily set focus */
break;
case WM_COMMAND: /* message: received a command */
switch (wParam)
{
case ID_OK:
if (InputStruct -> IntMode)
{
*(InputStruct -> OutI) = GetDlgItemInt(hDlg, ID_INPUT, &TransOK, TRUE);
if (TransOK) EndDialog(hDlg, wParam);
}
else
{
GetDlgItemText(hDlg, ID_INPUT, InputStruct -> OutString, 80);
EndDialog(hDlg, wParam);
}
break;
case ID_CANCEL:
EndDialog(hDlg, wParam); /* Exits the dialog box */
break;
}
return (TRUE);
break;
default:
return (FALSE); /* Didn't process a message */
}
}
/**************** Bago Processing Routines ****************/
/* Creates the egg timer and puts it up on the screen */
/* Warning: if the egg timer already exists, a second copy will be created */
HWND CreateEggTimer(hWnd)
HWND hWnd;
{
RECT Rect;
POINT Point;
HWND hEgg;
GetClientRect(hWnd, (LPRECT) &Rect);
Point.x = Rect.left + 280;
Point.y = Rect.top;
ClientToScreen(hWnd, &Point);
/* Would like to have WS_THICKFRAME, but that forces the */
/* resulting window to have an undesirably large minimum size */
hEgg = CreateWindow("Egg", /* window class */
"", /* window name */
WS_POPUP | WS_CAPTION | WS_BORDER | WS_VISIBLE,
Point.x, /* x position */
Point.y, /* y position */
37, /* width */
120, /* height */
hWnd, /* parent handle */
NULL, /* no ID for popup window */
hInst, /* instance */
NULL); /* additional info */
if (!hEgg)
MessageBox(hWnd, "Unable to create timer window", "ERROR",
MB_ICONQUESTION | MB_OK);
return(hEgg);
}
ProcessBagoCommand (hWnd, wParam, lParam)
HWND hWnd;
WORD wParam;
LONG lParam;
{
FARPROC lpEditWord; /* proc instance for opening dialog box */
FARPROC lpProcAbout; /* pointer to the "About" function */
char OutBuf[80];
int NewValue;
switch (wParam)
{
case MN_DD_GAME + MN_GAM_PLAY:
NewGame(hWnd);
break;
case MN_DD_GAME + MN_GAM_END:
if (UseTimer) PostMessage(hEgg, EGGM_STOP, 0, 0L);
else PostMessage(hWnd, BAGOM_ENDGAME, 0, 0L);
break;
case MN_DD_GAME + MN_GAM_STATS:
ProcessBagoStats(hWnd);
break;
case MN_DD_GAME + MN_GAM_CSTATS:
RUScore = 0; RUWords = 0;
RCScore = 0; RCWords = 0;
NGames = 0;
break;
case MN_DD_GAME + MN_GAM_TOURN:
sprintf(OutBuf, "%d", NextRackNumber);
if (InputBox(hWnd, "Set Rack Number for Next Game (0 - 32767)",
OutBuf, &NewValue, TRUE) != ID_CANCEL)
{
if (NewValue < 0) NewValue = 0;
if (NewValue > 32767) NewValue = 32767;
NextRackNumber = NewValue;
SetNextRack(NextRackNumber);
}
break;
case MN_DD_GAME + MN_GAM_LOAD:
ProcessBagoGLoad(hWnd);
break;
case MN_DD_GAME + MN_GAM_SAVE:
ProcessBagoGSave(hWnd);
break;
case MN_DD_GAME + MN_GAM_QUIT:
ProcessBagoClose(hWnd);
DestroyWindow(hWnd);
break;
/* Set the computer's skill level */
case MN_DD_OPTIONS + MN_OPT_LEVEL:
sprintf(OutBuf, "%d", Smartness);
if (InputBox(hWnd, "Difficulty (0 - 100)",
OutBuf, &NewValue, TRUE) != ID_CANCEL)
{
if (NewValue < 0) NewValue = 0;
if (NewValue > 100) NewValue = 100;
Smartness = NewValue;
}
break;
/* Realistically rotated cubes option */
case MN_DD_OPTIONS + MN_OPT_ROTATE:
ProcessBagoRotCubes(hWnd, wParam);
break;
case MN_DD_OPTIONS + MN_OPT_SOUND:
ProcessBagoSound(hWnd, wParam);
break;
/* Set game duration */
case MN_DD_OPTIONS + MN_OPT_GTIME:
sprintf(OutBuf, "%d", EndTime);
if (InputBox(hWnd, "Play Time in seconds (30 - 600)",
OutBuf, &NewValue, TRUE) != ID_CANCEL)
{
if (NewValue < 30) NewValue = 30;
if (NewValue > 600) NewValue = 600;
EndTime = NewValue;
}
break;
/* enable/disable egg timer */
case MN_DD_OPTIONS + MN_OPT_TIMER:
ProcessBagoTimer(hWnd, wParam);
break;
case MN_DD_OPTIONS + MN_OPT_LEARN:
ProcessBagoLearn(hWnd, wParam);
break;
/* Enable Computer Play */
case MN_DD_OPTIONS + MN_OPT_CPLAY:
ProcessBagoCPlay(hWnd, wParam);
break;
/* display heap size */
case MN_DD_OPTIONS + MN_SEL_8:
sprintf(OutBuf, "Local Heap: %u\nGlobal Heap: %lu", LocalCompact(64000), GlobalCompact(1200000L));
MessageBox(hWnd, OutBuf, "Mem Free", MB_OK);
break;
#ifdef ignore
case MN_DD_OPTIONS + MN_SEL_9:
/* Test the search algorithm */
GameOver = FALSE;
DestroyLeaf(hCListTop);
hCListTop = NULL;
DebugCount = 0L;
/* start computer search */
for (Debugrow=0; Debugrow < NROWS; Debugrow++)
for (Debugcol=0; Debugcol < NCOLS; Debugcol++)
SearchBoard(&board[Debugrow][Debugcol]);
sprintf(OutBuf, "Iterations: %ld", DebugCount);
MessageBox(hWnd, OutBuf, "Test PSearch", MB_OK);
GameOver = TRUE;
#endif
break;
case MN_DD_DICT + MN_DIC_LOAD:
if (!LoadDictionary(hWnd))
MessageBox(hWnd, "Cannot find "BAGODIC, "Error",
MB_OK | MB_ICONQUESTION);
break;
case MN_DD_DICT + MN_DIC_SAVE:
ProcessBagoSaveDict(hWnd);
break;
case MN_DD_DICT + MN_DIC_SHOW:
ProcessBagoShowDict(hWnd);
InvalidateRect(hUEdit, NULL, TRUE);
break;
case MN_DD_DICT + MN_DIC_EDIT:
lpEditWord = MakeProcInstance((FARPROC) EditWord, hInst);
DialogBox(hInst, MAKEINTRESOURCE(EDITWORDBOX), hWnd, lpEditWord);
FreeProcInstance(lpEditWord);
break;
case MN_DD_DICT + MN_DIC_OPT:
ProcessBagoOptDict(hWnd);
break;
case MN_DD_DICT + MN_DIC_CULL:
strcpy(OutBuf, "0");
if (InputBox(hWnd, "Min Freq on words to KEEP (0-32767)",
OutBuf, &NewValue, TRUE) == ID_CANCEL)
break;
CullDictionary(hWnd, NewValue);
break;
case MN_DD_DICT + MN_DIC_RFREQ:
ResetFreqLeaf(hTreeTop);
break;
case MN_DD_HELP + MN_HLP_REFCARD:
MessageBox(hWnd,
"Object of game: To make as many words as possible\n"
" by following paths through adjacent letters.\n\n"
"Scoring:\n\n"
" Word Size\tScore\n"
" 4 letters\t 1 point\n"
" 5 letters\t 2 points\n"
" 6 letters\t 3 points\n"
" 7 letters\t 5 points\n"
" 8 or more\t11 points\n\n"
"Disqualification marks on words:\n\n"
"(indent)\tFound by both players\n"
" \"\tDuplicate of word listed earlier\n"
" <\tLess than 4 letters\n"
" ?\tNot in rack (check again)\n"
" -\tNot a word\n\n\n"
"... select Help -> Index for more details",
"Reference Card", MB_OK);
break;
case MN_DD_HELP + MN_HLP_INDEX:
if (!WinHelp(hWnd, BAGOHLP, HELP_INDEX, 0L))
MessageBox(hWnd, "Sorry, can't run help.", "Error", MB_OK);
break;
case MN_DD_HELP + MN_HLP_ABOUT:
lpProcAbout = MakeProcInstance(About, hInst);
DialogBox(hInst, /* current instance */
MAKEINTRESOURCE(ABOUTBOX), /* resource to use */
hWnd, /* parent handle */
lpProcAbout); /* About() instance address */
FreeProcInstance(lpProcAbout);
break;
case BAGOM_TAB:
/* This command is so that keyboard-only users can swap between */
/* lists after a game to see the word paths */
if (GameOver && CPlay)
{
if (GetFocus()==hCList) SetFocus(hUList);
else SetFocus(hCList);
}
break;
case ID_STOP:
/* Same as the endgame menu pick or accelerator */
if (UseTimer) PostMessage(hEgg, EGGM_STOP, 0, 0L);
else PostMessage(hWnd, BAGOM_ENDGAME, 0, 0L);
break;
case ID_ENTER:
/* Enter key pressed. If game in progress, send enter to */
/* the user wordlist, and enable all cubes */
if (!GameOver) ProcessEnter();
break;
case ID_USERWORDS:
/* Randomize slightly so that computer plays differently based */
/* on how player plays */
rand();
break;
case ID_PLIST:
case ID_CPLAY:
/* click on word to display path on cubes */
ProcessBagoPList(hWnd, lParam);
break;
}
}
/* Process a notification message from a list box. */
/* Presently, this only shows that path (if any) that forms the selected */
/* word. Process the message that selection changed. */
ProcessBagoPList(hWnd, lParam)
HWND hWnd;
LONG lParam;
{
if (HIWORD(lParam)==LBN_SELCHANGE)
{
HWND hListBox;
int SelIndex;
CUBELOC cube;
int col, row;
BOOL found;
RECT rect;
char WordSought[80];
int tStackPtr;
/* clean off any previous path from buttons */
for (tStackPtr=CubeStackPtr; tStackPtr >= 0; tStackPtr--)
{
cube = CubeStack[tStackPtr];
InvalidateRect(board[cube.row][cube.col].hWindow, NULL, FALSE);
}
/* get the requested word */
hListBox = LOWORD(lParam);
SelIndex = SendMessage(hListBox, LB_GETCURSEL, 0, 0L);
if (SelIndex != LB_ERR)
{
SendMessage(hListBox, LB_GETTEXT, SelIndex, (LONG)(LPSTR) WordSought);
TrimWord(WordSought, strlen(WordSought));
RemoveQu(WordSought);
/* Find the word on the board */
found = FALSE;
for (row=0; row<NROWS; row++)
{
for (col=0; col<NCOLS; col++)
{
CubeStackPtr = 0;
if (verify(WordSought, &board[row][col], strlen(WordSought)))
{
found = TRUE;
break;
}
}
if (found) break;
}
if (found)
/* tell main window to go and draw the path */
{
rect.left=0; rect.top=0;
rect.right=32*NCOLS; rect.bottom=32*NROWS;
InvalidateRect(hWnd, &rect, FALSE);
}
}
}
}
/* Enter control pressed */
ProcessEnter()
{
CUBELOC cube;
/* pop up any cubes that are selected */
if (CubeStackPtr > 0)
while (CubeStackPtr > 0)
{
cube = CubeStack[--CubeStackPtr];
board[cube.row][cube.col].Uused = FALSE;
InvalidateRect(board[cube.row][cube.col].hWindow, NULL, FALSE);
}
EnableCubes(TRUE);
PostMessage(hUEdit, WM_CHAR, '\r', 1L);
SetFocus(hUEdit);
}
/* Process WM_DRAWITEM message from STOP and ENTER controls */
ProcessBagoDrawItem(hWnd, lParam)
HWND hWnd;
LPDRAWITEMSTRUCT lParam;
{
HDC hDC;
HBITMAP hBitmap, hOldBitmap;
HDC hMemoryDC;
hDC = lParam -> hDC;
switch (lParam -> CtlID)
{
case ID_STOP:
if ((lParam -> itemState) & ODS_SELECTED)
hBitmap = LoadBitmap(hInst, MAKEINTRESOURCE(STOPDNBMP));
else
hBitmap = LoadBitmap(hInst, MAKEINTRESOURCE(STOPUPBMP));
hMemoryDC = CreateCompatibleDC(hDC);
hOldBitmap = SelectObject(hMemoryDC, hBitmap);
if (hOldBitmap)
{
BitBlt(hDC, 0, 0, 32, 32, hMemoryDC, 0, 0, SRCCOPY);
SelectObject(hMemoryDC, hOldBitmap);
}
DeleteObject(hBitmap);
DeleteDC(hMemoryDC);
break;
case ID_ENTER:
if ((lParam -> itemState) & ODS_SELECTED)
hBitmap = LoadBitmap(hInst, MAKEINTRESOURCE(ENTERDNBMP));
else
hBitmap = LoadBitmap(hInst, MAKEINTRESOURCE(ENTERUPBMP));
hMemoryDC = CreateCompatibleDC(hDC);
hOldBitmap = SelectObject(hMemoryDC, hBitmap);
if (hOldBitmap)
{
BitBlt(hDC, 0, 0, 64, 32, hMemoryDC, 0, 0, SRCCOPY);
SelectObject(hMemoryDC, hOldBitmap);
}
DeleteObject(hBitmap);
DeleteDC(hMemoryDC);
break;
}
}
/* The only thing that is painted in the main window is the path that */
/* shows how a word was made. And that feature is only available when */
/* the game is over */
ProcessBagoPaint(hWnd)
HWND hWnd;
{
HDC hDC;
PAINTSTRUCT ps;
HPEN hPathPen, hOldPen;
int row, col;
int tStackPtr;
CUBELOC cube;
hDC = BeginPaint(hWnd, (LPPAINTSTRUCT) &ps);
if (GameOver && CubeStackPtr > 0)
{
/* Clear any previous path off the cubes if necessary */
/* Must do all cubes, in case we are restoring a minimized window */
for (row=0; row < NROWS; row++)
for (col=0; col < NCOLS; col++)
UpdateWindow(board[row][col].hWindow);
hPathPen = CreatePen(PS_SOLID, 1, RED);
if (hPathPen)
{
hOldPen = SelectObject(hDC, hPathPen);
tStackPtr = CubeStackPtr - 1;
cube = CubeStack[tStackPtr];
MoveTo(hDC, cube.col*32+16, cube.row*32+16);
while (tStackPtr > 0)
{
cube = CubeStack[--tStackPtr];
LineTo(hDC, cube.col*32+16, cube.row*32+16);
}
SelectObject(hDC, hOldPen);
DeleteObject(hPathPen);
}
/* Note: tell ALL the buttons not to repaint */
/* it is faster to just validate them all. Selective validation */
/* of the ones that the path went through may still allow some */
/* of the buttons to repaint, taking up time */
for (row=0; row < NROWS; row++)
for (col=0; col < NCOLS; col++)
ValidateRect(board[row][col].hWindow, NULL);
}
EndPaint(hWnd, (LPPAINTSTRUCT) &ps);
}
/* Process the Resize message by sizing the controls and child windows */
ProcessBagoSize(hWnd, wParam, lParam)
HWND hWnd;
WORD wParam;
LONG lParam;
{
MoveWindow(hUEdit, LOWORD(lParam) - 2*ListWinWidth, 0,
ListWinWidth, HIWORD(lParam), TRUE);
MoveWindow(hUList, LOWORD(lParam) - 2*ListWinWidth, 0,
ListWinWidth, HIWORD(lParam), TRUE);
MoveWindow(hCList, LOWORD(lParam) - ListWinWidth, 0,
ListWinWidth, HIWORD(lParam), TRUE);
InvalidateRect(hWnd, NULL, TRUE);
}
/* Converts all lowercase characters to uppercase, then eliminates any */
/* characters that are not strictly alphabetic */
TrimWord(w, len)
char w[80];
int len;
{
int source = 0, dest = 0;
strupr(w); /* convert to uppercase */
for (source=0; source < len; source++)
if (isupper(w[source]))
w[dest++] = w[source];
w[dest] = NULL;
}
/* Converts 'Q' in a string to 'QU' */
AddQu(w)
char w[];
{
char temp[30];
int source, dest;
int len;
len = strlen(w);
dest = 0;
for (source=0; source<len; source++)
{
temp[dest++] = w[source];
if (w[source] == 'Q')
temp[dest++] = 'U';
}
temp[dest] = NULL;
strcpy(w, temp);
}
/* Converts 'QU' in a string to 'Q' */
RemoveQu(w)
char w[];
{
int source, dest;
int len;
len = strlen(w);
dest = 0;
for (source=0; source<len; source++)
{
w[dest++] = w[source];
if (w[source] == 'Q' && w[source+1] == 'U')
source++;
}
w[dest] = NULL;
}
/* This procedure is used to add a leaf to the dictionary tree */
/* Recursive, to match the tree search and sort */
/* Procedure is more complicated than a normal tree sort because
* I have chosen to use Windows local heap, instead of direct pointers.
* This will make the program slower in execution, but the program may
* be much faster than it needs to be, anyway.
* If the word is already in the structure, its frequency count is
* incremented. If not, the word is added to the structure.
* When a word is added, a back link is made, too, so that the tree
* may be easily traversed in either direction
*/
AddLeaf(s, hLeafPtr, freq, suffix, hSuperior)
char *s;
HANDLE *hLeafPtr;
int freq;
unsigned suffix;
HANDLE hSuperior; /* handle of superior leaf */
{
PDW LeafPtr;
int comp; /* results of string compare */
if (*hLeafPtr)
/* A record exists at this handle. Check it. */
{
LeafPtr = (PDW) LocalLock(*hLeafPtr);
if (!(comp = strcmp(s, LeafPtr -> w)))
/* words are equal, combine the relevant data */
{
/* don't want to overflow 16-bit integer */
if ((LONG) (LeafPtr -> Freq) + freq < 32767L) LeafPtr -> Freq += freq;
LeafPtr -> Suffix1 |= suffix;
}
else
{
if (comp < 0)
/* the new word is less than the word at this leaf */
{
AddLeaf(s, &(LeafPtr -> lt), freq, suffix, *hLeafPtr);
}
else
/* the new word is greater than the word at this leaf */
{
AddLeaf(s, &(LeafPtr -> gt), freq, suffix, *hLeafPtr);
}
}
LocalUnlock(*hLeafPtr);
}
else
/* No record exists at this handle. Allocate memory and add word */
{
*hLeafPtr = LocalAlloc(LMEM_MOVEABLE | LMEM_ZEROINIT,
sizeof(DW));
LeafPtr = (PDW) LocalLock(*hLeafPtr);
strcpy(LeafPtr -> w, s);
LeafPtr -> Freq = freq;
LeafPtr -> Suffix1 = suffix;
LeafPtr -> up = hSuperior;
LocalUnlock(*hLeafPtr);
}
}
/* Relocate a leaf. The assumption is made that hSource is the
* handle of a valid record, and that this record does not already
* exist in the tree. hLeafPtr is the target destination for the
* leaf. If there is already something in hLeafPtr, the routine
* recursively finds a free place. hSuperior is a handle to the
* leaf that the up link of hLeafPtr should point to.
* This routine preserves any existing gt and lt links.
*/
RelocateLeaf(hSource, hLeafPtr, hSuperior)
HANDLE hSource, *hLeafPtr, hSuperior;
{
PDW LeafPtr, Source;
if (*hLeafPtr)
/* A record exists at this handle. Check it. */
{
LeafPtr = (PDW) LocalLock(*hLeafPtr);
Source = (PDW) LocalLock(hSource);
if (strcmp(Source -> w, LeafPtr -> w) < 0)
/* the new word is less than the word at this leaf */
RelocateLeaf(hSource, &(LeafPtr -> lt), *hLeafPtr);
else
/* the new word is greater than the word at this leaf */
RelocateLeaf(hSource, &(LeafPtr -> gt), *hLeafPtr);
LocalUnlock(*hLeafPtr);
LocalUnlock(hSource);
}
else
/* No record exists at this handle. Put the record here */
{
*hLeafPtr = hSource;
Source = (PDW) LocalLock(hSource);
Source -> up = hSuperior;
LocalUnlock(hSource);
}
}
/* Similar to AddLeaf. This procedure adds a leaf to a tree, giving
* first sort priority to frequency, rather than the collating order
* of the word. This routine is used only to optimize the dictionary;
* it is not used during normal play of the game. Note that this
* procedure does not make use of all the pointer fields in the DW
* record, and uses lt and gt in a different manner than AddLeaf.
* Count is incremented for each leaf added.
*/
AddLeafO(hSource, hLeafPtr)
HANDLE hSource;
LPHANDLE hLeafPtr;
{
LPDW LeafPtr;
PDW SourcePtr;
if (*hLeafPtr)
/* A record exists at this handle. Check it. */
{
LeafPtr = (LPDW) GlobalLock(*hLeafPtr);
SourcePtr = (PDW) LocalLock(hSource);
if (SourcePtr -> Freq == LeafPtr -> Freq)
/* Frequencies equal, sort on basis of word comparison */
{
if (lstrcmp((LPSTR) (SourcePtr -> w), LeafPtr -> w) < 0)
AddLeafO(hSource, &(LeafPtr -> lt));
else
AddLeafO(hSource, &(LeafPtr -> gt));
}
else
{
if (SourcePtr -> Freq > LeafPtr -> Freq)
/* the new word is more frequent than the word at this leaf */
/* we want more frequent to print first so use lt link */
AddLeafO(hSource, &(LeafPtr -> lt));
else
/* the new word is less frequent than the word at this leaf */
AddLeafO(hSource, &(LeafPtr -> gt));
}
LocalUnlock(hSource);
GlobalUnlock(*hLeafPtr);
}
else
/* No record exists here. Allocate memory and copy entire record */
{
*hLeafPtr = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT,
(DWORD) sizeof(DW));
if (*hLeafPtr)
{
LeafPtr = (LPDW) GlobalLock(*hLeafPtr);
SourcePtr = (PDW) LocalLock(hSource);
lstrcpy(LeafPtr -> w, (LPSTR) (SourcePtr -> w));
LeafPtr -> Freq = SourcePtr -> Freq;
LeafPtr -> Suffix1 = SourcePtr -> Suffix1;
LocalUnlock(hSource);
GlobalUnlock(*hLeafPtr);
}
else
/* Ouch! GlobalAlloc failed! */
OptimizeFail = TRUE;
}
}
/* This procedure will print the entire tree out to a listbox */
/* Note that hLeafPtr is not passed by reference, as in Addleaf */
PrintLeaf(hList, hLeafPtr)
HWND hList;
HANDLE hLeafPtr;
{
PDW LeafPtr;
char lbuf[40]; /* local printing buffer */
if (hLeafPtr)
{
LeafPtr = (PDW) LocalLock(hLeafPtr);
PrintLeaf(hList, LeafPtr -> lt); /* print all less than */
sprintf(lbuf, "%-10s %2d %4.4X \r\n",
LeafPtr -> w, LeafPtr -> Freq, LeafPtr -> Suffix1);
SendMessage(hList, LB_INSERTSTRING, -1, (LONG) (LPSTR) lbuf);
PrintLeaf(hList, LeafPtr -> gt); /* print all greater than */
LocalUnlock(hLeafPtr);
}
}
/* Count the number of leaves below and including the one pointed to */
/* by hLeafPtr, and return it */
int CountLeaf(hLeafPtr)
HANDLE hLeafPtr;
{
PDW LeafPtr;
int n;
if (!hLeafPtr) return(0);
else
{
LeafPtr = (PDW) LocalLock(hLeafPtr);
n = CountLeaf(LeafPtr -> lt) + 1 + CountLeaf(LeafPtr -> gt);
LocalUnlock(hLeafPtr);
}
return(n);
}
/* Similar to PrintLeaf. This procedure copies a leaf and all its
* inferior leaves into a second tree pointed to by hLeafPtr.
* Used only to Re-Sort the tree into a different order for optimization
*/
ReSortLeaf(hSource, hLeafPtr, count, hPro)
HANDLE hSource;
LPHANDLE hLeafPtr;
int *count;
HWND hPro;
{
PDW SourcePtr;
if (hSource && !OptimizeFail)
/* Quick exit if pointing to null leaf, or GlobalAlloc failed in AddLeafO */
{
SourcePtr = (PDW) LocalLock(hSource);
ReSortLeaf(SourcePtr -> lt, hLeafPtr, count, hPro); /* copy all less than */
AddLeafO(hSource, hLeafPtr);
(*count)++;
SendMessage(hPro, PRO_SETPOS, *count, 0L);
ReSortLeaf(SourcePtr -> gt, hLeafPtr, count, hPro); /* copy all greater than */
LocalUnlock(hSource);
}
}
/* Leaf handle and all inferiors are copied in sorted order to a linear array.
* Note that no data is copied; only handles. hSource is the source handle,
* LinArray is the destination, an array of handles. Index specifies which
* element of the array to copy to. Index is automatically updated.
*/
CopyTreeToLin(hSource, LinArray, index)
HANDLE hSource;
LPHANDLE LinArray;
int *index;
{
LPDW SourcePtr;
if (hSource)
{
SourcePtr = (LPDW) GlobalLock(hSource);
CopyTreeToLin(SourcePtr->lt, LinArray, index);
LinArray[*index] = hSource;
(*index)++;
CopyTreeToLin(SourcePtr->gt, LinArray, index);
GlobalUnlock(hSource);
}
}
/* Attaches a range of elements in the linear array of handles in
* such an order that they form a balanced binary tree. first and
* last are the bounds of the array to output, hLeafPtr is the
* destination tree, and LinArray is the linear array of handles to
* output.
*/
Balance(hLeafPtr, LinArray, first, last)
HANDLE *hLeafPtr;
LPHANDLE LinArray;
int first, last;
{
int mid; /* midpoint between ends of segment */
LPDW LeafPtr; /* pointer to record being added */
mid = (first+last)/2;
/* Add the data from the leaf back into the dictionary */
/* be careful, far to near copying */
LeafPtr = (LPDW) GlobalLock(LinArray[mid]);
AddLeaf(FarToNearStr(LeafPtr->w), hLeafPtr, LeafPtr->Freq,
LeafPtr->Suffix1, NULL);
GlobalUnlock(LinArray[mid]);
if (first < mid) Balance(hLeafPtr, LinArray, first, mid-1);
if (mid < last) Balance(hLeafPtr, LinArray, mid+1, last);
}
/* This procedure will print the entire tree out to the dictionary file */
/* Note that hLeafPtr is not passed by reference, as in Addleaf */
/* This is almost the same as the PrintLeaf function, except that */
/* the tree is printed in storage-order rather than collate-order */
BOOL PrintDict(hDictFile, hLeafPtr)
int hDictFile;
HANDLE hLeafPtr;
{
PDW LeafPtr;
char lbuf[40]; /* local printing buffer */
int IOStatus;
BOOL retval; /* return value */
if (hLeafPtr)
{
LeafPtr = (PDW) LocalLock(hLeafPtr);
sprintf(lbuf, "%-10s %2d %4.4X\r\n",
LeafPtr -> w, LeafPtr -> Freq, LeafPtr -> Suffix1);
IOStatus = write(hDictFile, lbuf, strlen(lbuf));
/* return FALSE if error */
retval = (IOStatus == strlen(lbuf));
/* print all less than */
if (!PrintDict(hDictFile, (LeafPtr -> lt))) retval = FALSE;
/* print all greater than */
if (!PrintDict(hDictFile, (LeafPtr -> gt))) retval = FALSE;
LocalUnlock(hLeafPtr);
return(retval);
}
}
/* Find the string passed as a parameter in the tree */
/* If the string is found, a handle to the record is returned */
/* Else, NULL is returned */
HANDLE FindLeaf(Word, hLeafPtr)
char *Word;
HANDLE hLeafPtr;
{
PDW LeafPtr;
int comp; /* comparison result from strcmp */
HANDLE retval; /* return value */
int nSuffix; /* used for suffixing words */
char FullWord[13];
unsigned BitMask;
if (!hLeafPtr) return(NULL);
else
{
LeafPtr = (PDW) LocalLock(hLeafPtr);
/* 2 character comparison only, for reasons outlined in psearch */
comp = strncmp(Word, LeafPtr -> w, 2);
if (comp < 0)
/* the word is less than the word at this leaf */
retval = FindLeaf(Word, LeafPtr -> lt);
else if (comp > 0)
/* the word is greater than the word at this leaf */
retval = FindLeaf(Word, LeafPtr -> gt);
else
/* comp == 0, we could have a match */
{
if (!strcmp(Word, LeafPtr -> w))
/* exact match */
retval = hLeafPtr;
else
{
/* must look at both branches */
retval = FindLeaf(Word, LeafPtr -> lt);
if (!retval) retval = FindLeaf(Word, LeafPtr -> gt);
if (!retval && !strncmp(Word, LeafPtr->w, strlen(LeafPtr->w)-1))
/* Last resort. If enough letters in word match, try suffixes */
{
BitMask = 1;
for (nSuffix=ID_FIRSTSUF; nSuffix <= ID_LASTSUF; nSuffix++)
{
if (BitMask & (LeafPtr->Suffix1))
/* if the suffix is legal, try it */
{
AddSuffix(LeafPtr->w, nSuffix, FullWord);
if (!strcmp(Word, FullWord))
{
/* matches suffixed word exactly */
retval = hLeafPtr;
break;
}
}
BitMask <<= 1; /* move to the next bit */
}
}
}
}
LocalUnlock(hLeafPtr);
return(retval);
}
}
/* This is used only in dictionary editing. Finds exact word, ignoring
* suffixes. Returns the handle of the word. If cannot find word,
* returns NULL. Otherwise, like FindLeaf()
*/
HANDLE FindExactLeaf(Word, hLeafPtr)
char *Word;
HANDLE hLeafPtr;
{
PDW LeafPtr;
int comp;
HANDLE retval;
if (!hLeafPtr) return(NULL);
LeafPtr = (PDW) LocalLock(hLeafPtr);
comp = strcmp(Word, LeafPtr -> w);
if (comp < 0)
retval = FindExactLeaf(Word, LeafPtr -> lt);
else if (comp > 0)
retval = FindExactLeaf(Word, LeafPtr -> gt);
else
/* exact match */
retval = hLeafPtr;
LocalUnlock(hLeafPtr);
return(retval);
}
/* Follows the lt chain all the way down from the present record
* and returns the last one. Used to find the least word in
* a chain.
*/
HANDLE FindLeast(hLeafPtr)
HANDLE hLeafPtr;
{
PDW LeafPtr;
HANDLE retval;
LeafPtr = (PDW) LocalLock(hLeafPtr);
if (LeafPtr -> lt) retval = FindLeast(LeafPtr -> lt);
else retval = hLeafPtr;
LocalUnlock(hLeafPtr);
return(retval);
}
/* Follows the up chain until it finds a word that is less
* than the word corresponding to hLeafPtr. If no such word is
* found, NULL is returned.
* WARNING: this routine assumes that hLeafPtr != NULL
*/
HANDLE FindUpLess(hLeafPtr)
HANDLE hLeafPtr;
{
PDW LeafPtr;
PDW SuperPtr;
HANDLE retval;
LeafPtr = (PDW) LocalLock(hLeafPtr);
if (LeafPtr -> up)
/* if there is a leaf above this one, examine it */
{
SuperPtr = (PDW) LocalLock(LeafPtr -> up);
if (strcmp(SuperPtr -> w, LeafPtr -> w) < 0)
/* found one */
retval = LeafPtr -> up;
else
/* look at next link up */
retval = FindUpLess(LeafPtr -> up);
LocalUnlock(LeafPtr -> up);
}
else
/* there is no superior leaf, return null */
retval = NULL;
LocalUnlock(hLeafPtr);
return(retval);
}
/* Follows the up chain until it finds a word that is greater
* than the word corresponding to hLeafPtr. If no such word is
* found, NULL is returned.
* WARNING: this routine assumes that hLeafPtr != NULL
*/
HANDLE FindUpGreater(hLeafPtr)
HANDLE hLeafPtr;
{
PDW LeafPtr;
PDW SuperPtr;
HANDLE retval;
LeafPtr = (PDW) LocalLock(hLeafPtr);
if (LeafPtr -> up)
/* if there is a leaf above this one, examine it */
{
SuperPtr = (PDW) LocalLock(LeafPtr -> up);
if (strcmp(SuperPtr -> w, LeafPtr -> w) > 0)
/* found one */
retval = LeafPtr -> up;
else
/* look at next link up */
retval = FindUpGreater(LeafPtr -> up);
LocalUnlock(LeafPtr -> up);
}
else
/* there is no superior leaf, return null */
retval = NULL;
LocalUnlock(hLeafPtr);
return(retval);
}
/* Follows the gt chain all the way down from the present record
* and returns the last one. Used to find the greatest word in
* a chain.
*/
HANDLE FindGreatest(hLeafPtr)
HANDLE hLeafPtr;
{
PDW LeafPtr;
HANDLE retval;
LeafPtr = (PDW) LocalLock(hLeafPtr);
if (LeafPtr -> gt) retval = FindGreatest(LeafPtr -> gt);
else retval = hLeafPtr;
LocalUnlock(hLeafPtr);
return(retval);
}
/* Finds the previous leaf (collating order) in the tree, and
* returns a handle to it. If no leaf found, NULL is returned
* Return value is in this priority:
* 1. If hLeafPtr is NULL, return NULL
* 2. If hLeafPtr has a lt link, return the greatest along this chain
* 3. If hLeafPtr has an up link, and the uplink was a gt link originally,
* return the uplink.
* 4. If none of the above, return NULL
*/
HANDLE FindPrev(hLeafPtr)
HANDLE hLeafPtr;
{
PDW LeafPtr;
PDW SuperiorPtr;
HANDLE retval;
if (!hLeafPtr) return(NULL);
else
{
LeafPtr = (PDW) LocalLock(hLeafPtr);
if (LeafPtr -> lt)
/* if there is a lt link, look for the greatest along the chain */
retval = FindGreatest(LeafPtr -> lt);
else
/* look higher in the tree to see if there is a lesser word */
retval = FindUpLess(hLeafPtr);
LocalUnlock(hLeafPtr);
return(retval);
}
}
/* Finds the next leaf (collating order) in the tree, and
* returns a handle to it. If no leaf found, NULL is returned
* Return value is in this priority:
* 1. If hLeafPtr is NULL, return NULL
* 2. If hLeafPtr has a gt link, return the least along this chain
* 3. If hLeafPtr has an up link, and the uplink was a lt link originally,
* return the uplink.
* 4. If none of the above, return NULL
*/
HANDLE FindNext(hLeafPtr)
HANDLE hLeafPtr;
{
PDW LeafPtr;
PDW SuperiorPtr;
HANDLE retval;
if (!hLeafPtr) return(NULL);
else
{
LeafPtr = (PDW) LocalLock(hLeafPtr);
if (LeafPtr -> gt)
/* if there is a greater than link, look for the least along the chain */
retval = FindLeast(LeafPtr -> gt);
else
/* look higher in the tree to see if there is a greater word */
retval = FindUpGreater(hLeafPtr);
LocalUnlock(hLeafPtr);
return(retval);
}
}
/* Finds a virgin leaf in the tree, and returns the handle. A virgin leaf is
* one which has never been viewed by the dictionary editor. This function is
* to aid in finding words which do not yet have suffixes defined.
*/
HANDLE FindVirgin(hLeafPtr)
HANDLE hLeafPtr;
{
PDW LeafPtr;
HANDLE retval;
if (!hLeafPtr) return(NULL);
else
{
LeafPtr = (PDW) LocalLock(hLeafPtr);
if ((LeafPtr -> Suffix1) & 0x8000)
/* this leaf is virgin */
retval = hLeafPtr;
else
{
/* try the inferior leaves */
if (!(retval = FindVirgin(LeafPtr -> lt)))
retval = FindVirgin(LeafPtr -> gt);
}
LocalUnlock(hLeafPtr);
return(retval);
}
}
/* This will free up the memory allocated to a leaf, and all leaves further
* down the tree, whether on the greater-than or less-than side
* The only time this should be called is to dispose of the entire dictionary
* tree
*/
DestroyLeaf(hLeafPtr)
HANDLE hLeafPtr;
{
PDW LeafPtr;
if (hLeafPtr)
{
LeafPtr = (PDW) LocalLock(hLeafPtr);
DestroyLeaf(LeafPtr -> lt); /* Destroy all less than */
DestroyLeaf(LeafPtr -> gt); /* Destroy all greater than */
LocalUnlock(hLeafPtr);
LocalFree(hLeafPtr); /* Destroy self */
}
}
/* Like DestroyLeaf, but operates on a tree in Global (long pointer) memory */
DestroyGLeaf(hLeafPtr)
HANDLE hLeafPtr;
{
LPDW LeafPtr;
if (hLeafPtr)
{
LeafPtr = (LPDW) GlobalLock(hLeafPtr);
DestroyGLeaf(LeafPtr -> lt); /* Destroy all less than */
DestroyGLeaf(LeafPtr -> gt); /* Destroy all greater than */
GlobalUnlock(hLeafPtr);
GlobalFree(hLeafPtr); /* Destroy self */
}
}
/* Not 100% foolproof, but tries to take a guess as to the root word, given
* a word which may have a suffix attached.
* The guess is returned in the Dest parameter.
* Returns handle to the word in dictionary if it was able to find the word
* Warning: may barf on words shorter than 3 letters
*/
HANDLE RemoveSuffix(FullWord, Dest)
char FullWord[], Dest[];
{
int len;
HANDLE hFound;
PDW LeafPtr;
strcpy(Dest, FullWord);
len = strlen(Dest);
/* First check the word by itself. Many will have no suffix at all */
hFound = FindLeaf(Dest, hTreeTop);
if (hFound)
{
/* return only the root word */
LeafPtr = (PDW) LocalLock(hFound);
strcpy(Dest, LeafPtr -> w);
LocalUnlock(hFound);
return(hFound);
}
/* -S */
if (Dest[len-1] == 'S')
{
/* -SSES */
if (len > 3 && Dest[len-2] == 'E' && Dest[len-3] == 'S' & Dest[len-4] == 'S')
{
Dest[len-2] = NULL;
}
/* -IES */
else if (Dest[len-2] == 'E' && Dest[len-3] == 'I')
{
Dest[len-3] = 'Y';
Dest[len-2] = NULL;
}
/* -HES, -XES */
else if (Dest[len-2] == 'E' && (Dest[len-3] == 'H' || Dest[len-3] == 'X'))
{
Dest[len-2] = NULL;
}
/* anything else but -SS */
else if (Dest[len-2] != 'S')
{
Dest[len-1] = NULL;
}
}
/* -ED, -ER, -EN*/
else if (len > 4 && (Dest[len-1] == 'D' || Dest[len-1] == 'R' || Dest[len-1] == 'N') && Dest[len-2] == 'E')
{
/* ignore words less than 5 letters - probably not suffixed */
/* -IED, -IER, -IEN */
if (Dest[len-3] == 'I')
{
Dest[len-3] = 'Y';
Dest[len-2] = NULL;
}
/* -XXED, -XXER, -XXEN, X=doubled letter */
else if (Dest[len-3] == Dest[len-4] && Dest[len-3] != 'S' && Dest[len-3] != 'L')
{
Dest[len-3] = NULL;
}
/* -CVCED, -CVCER, -CVCEN, C=consonant, V=vowel */
/* or -EED, -EER, -EEN */
/* or -xSED, -xSER, -xSEN , x != S */
else if (Dest[len-3] == 'E' || (Dest[len-3] == 'S' && Dest[len-4] != 'S') || (!isvowel(Dest[len-3]) && isvowel(Dest[len-4]) && !isvowel(Dest[len-5])))
{
Dest[len-1] = NULL;
}
else
{
Dest[len-2] = NULL;
}
}
/* -Y, -LY, but not -AY */
else if (Dest[len-1] == 'Y' && Dest[len-2] != 'A')
{
/* -LY */
if (Dest[len-2] == 'L')
{
Dest[len-2] = NULL;
}
/* -XXY, X=doubled letter */
else if (Dest[len-2] == Dest[len-3])
{
Dest[len-2] = NULL;
}
/* -CVCY, C=consonant, V=vowel */
else if (len > 3 && !isvowel(Dest[len-2]) && isvowel(Dest[len-3]) && !isvowel(Dest[len-4]))
{
Dest[len-1] = 'E';
}
else
{
Dest[len-1] = NULL;
}
}
/* -EST */
/* do not consider small words like best, west, crest ... */
else if (len > 5 && Dest[len-1] == 'T' && Dest[len-2] == 'S' && Dest[len-3] == 'E')
{
/* -IEST */
if (Dest[len-4] == 'I')
{
Dest[len-4] = 'Y';
Dest[len-3] = NULL;
}
/* -XXEST, X=doubled letter */
else if (Dest[len-4] == Dest[len-5] && Dest[len-4] != 'L' && Dest[len-4] != 'S')
{
Dest[len-4] = NULL;
}
/* -CVCEST, C=consonant, V=vowel */
/* or -EEST i.e. FREEST */
else if (Dest[len-4] == 'E' || (!isvowel(Dest[len-4]) && isvowel(Dest[len-5]) && !isvowel(Dest[len-6])))
{
Dest[len-2] = NULL;
}
else
{
Dest[len-3] = NULL;
}
}
/* -ING, -ISH */
/* check len first so as not to process short words like sing, fish, bring */
else if (len > 5 && ((Dest[len-1] == 'G' && Dest[len-2] == 'N') || (Dest[len-1] == 'H' && Dest[len-2] == 'S')) && Dest[len-3] == 'I')
{
/* -XXING, -XXISH, X=doubled letter, except -SSING, -SSISH */
if (Dest[len-4] == Dest[len-5] && Dest[len-4] != 'S' && Dest[len-4] != 'L')
{
Dest[len-4] = NULL;
}
/* -CVCING, -CVCISH, C=consonant, V=vowel */
else if (!isvowel(Dest[len-4]) && isvowel(Dest[len-5]) && !isvowel(Dest[len-6]))
{
Dest[len-3] = 'E';
Dest[len-2] = NULL;
}
else
{
Dest[len-3] = NULL;
}
}
return(FindLeaf(Dest, hTreeTop));
}
/* increments the frequency of the word at hLeaf */
IncFreq(hLeaf)
HANDLE hLeaf;
{
PDW LeafPtr;
LeafPtr = (PDW) LocalLock(hLeaf);
if (LeafPtr -> Freq < 32767) (LeafPtr -> Freq)++;
LocalUnlock(hLeaf);
}
/* Verify that a word is actually on the board, returning TRUE if it is */
/* Searches the word BACKWARDS (no reason) */
/* Something like SearchBoard, only much faster */
/* This routine also doubles as a path-finder for displaying where a given */
/* word is on the board. To use in this manner, first clear out the cube */
/* stack by setting CubeStackPtr = 0. If Verify returns TRUE, then the */
/* cubes forming the word are left on the stack, in order */
BOOL Verify(TrialW, CubePtr, TrialLen)
char *TrialW;
BOARD *CubePtr;
int TrialLen;
{
int Neighbor;
CUBELOC cube;
if (CubePtr == NULL || (CubePtr -> Cused))
return(FALSE);
if (TrialLen==1)
{
if (*TrialW == CubePtr -> val)
{
cube.row = CubePtr -> r; cube.col = CubePtr -> c;
PushCube(cube);
return(TRUE);
}
else
return(FALSE);
}
else
{
if (TrialW[TrialLen-1] == CubePtr -> val)
{
cube.row = CubePtr -> r; cube.col = CubePtr -> c;
PushCube(cube);
CubePtr -> Cused = TRUE;
for (Neighbor=0; Neighbor < 8; Neighbor++)
{
if (Verify(TrialW, CubePtr -> link[Neighbor], TrialLen-1))
{
CubePtr -> Cused = FALSE;
return(TRUE);
}
}
CubePtr -> Cused = FALSE;
PopCube();
}
return(FALSE);
}
}
/* Score the list of words whose handle is hList. */
/* Running score and word count are updated */
/* This routine also updates the window to display the scores */
/* Return value is the score for the round */
/* Running score and running word count are updated */
int Score(hList, RScore, RWords)
HWND hList;
int *RScore, *RWords;
{
char CurLine[30];
char OutBuf[30];
int CurScore;
int count; /* count of lines in the list */
int wcount; /* count of good words */
int Tscore; /* Total score for this round*/
int LineNo;
HDC hDC; /* to calclulate width of word */
MSG msg; /* for PeekMessage yield */
hDC = GetDC(hList);
Tscore = 0;
wcount = 0;
count = SendMessage(hList, LB_GETCOUNT, 0, 0L);
for (LineNo=0; LineNo < count; LineNo++)
{
SendMessage(hList, LB_GETTEXT, LineNo, (LONG) (LPSTR) CurLine);
if (*CurLine >= 'A')
{
wcount++;
switch (strlen(CurLine))
{
case 4:
CurScore=1; break;
case 5:
CurScore=2; break;
case 6:
CurScore=3; break;
case 7:
CurScore=5; break;
default:
CurScore=11; break;
}
/* Format score for display */
if (LOWORD(GetTextExtent(hDC, CurLine, strlen(CurLine))) < TScoreWidth)
{
/* the only possible word scores have 1 and 2 digits */
if (CurScore < 10)
sprintf(OutBuf, "%s\t\t\t%d", CurLine, CurScore); /* 1 digit */
else
sprintf(OutBuf, "%s\t\t%d", CurLine, CurScore); /* 2 digits */
}
else /* Word is too wide, do what we can */
sprintf(OutBuf, "%s %d", CurLine, CurScore);
SendMessage(hList, LB_DELETESTRING, LineNo, 0L);
SendMessage(hList, LB_INSERTSTRING, LineNo, (LONG) (LPSTR) OutBuf);
Tscore += CurScore;
}
}
*RScore += Tscore;
if (Tscore < 10)
sprintf(OutBuf, "Total Score:\t\t\t%d", Tscore);
else if (Tscore < 100)
sprintf(OutBuf, "Total Score:\t\t%d", Tscore);
else
/* Assume that there are no score totals of 4 digits */
sprintf(OutBuf, "Total Score:\t%d", Tscore);
SendMessage(hList, LB_INSERTSTRING, -1, (LONG) (LPSTR) OutBuf);
if (wcount != 1)
sprintf(OutBuf, "(%d Words)", wcount);
else
strcpy(OutBuf, "(1 Word)");
*RWords += wcount;
SendMessage(hList, LB_INSERTSTRING, -1, (LONG) (LPSTR) OutBuf);
/* set selection in order to scroll last line of window into view */
count = SendMessage(hList, LB_GETCOUNT, 0, 0L);
SendMessage(hList, LB_SETCURSEL, count-1, 0L); /* to get bottom in view */
SendMessage(hList, LB_SETCURSEL, count-2, 0L); /* highlight score line */
ReleaseDC(hList, hDC);
return(Tscore);
}
/* Process the End-of-game signal */
ProcessBagoEndGame(hWnd)
HWND hWnd;
{
int Pwcount; /* how many words the user entered */
int Pwi; /* the index of the word we're looking at */
int Pwl; /* length of the word we're looking at */
int Cwcount; /* same for computer's list */
int Cwi;
BOOL Bogus; /* true if any bogus words entered */
int RewardLevel; /* How well the computer was beat */
int PicNumber; /* Resource number of reward picture */
int Uscore, Cscore; /* User, Computer score for round */
char CurWord[30]; /* a word from User word list */
char CurWord1[30]; /* a word from User word list */
char RootWord[30]; /* the root of the above (could be equal) */
char OutBuf[30]; /* buffer for string output */
HANDLE hOldBuffer; /* The raw buffer from the User Word list */
HANDLE hCurWord; /* Handle, if any to current word in dictionary */
BOOL BadWord; /* Set to true if current word is disqualified */
HCURSOR hOldCursor;
MSG msg; /* for PeekMessage Yield */
GameOver = TRUE; /* This global will abort computer word search */
/* Sound the bell! Open and close the sound device every time */
/* it doesn't take too much time, and also don't want to hog the */
/* sound generator away from other windows */
if (Sound)
{
OpenSound();
SetVoiceAccent(1, 120, 50, S_STACCATO, 0);
SetVoiceNote(1, 52, 32, 0);
SetVoiceNote(1, 48, 32, 0);
SetVoiceNote(1, 52, 32, 0);
SetVoiceNote(1, 48, 32, 0);
SetVoiceNote(1, 52, 32, 0);
StartSound();
}
hOldCursor = SetCursor(hHourGlass); /* could be lengthy operation */
EnableCubes(FALSE); /* Stop player input! */
EnableWindow(hUEdit, FALSE);
/* Game over, allow dictionary functions again */
EnableMenuItem(GetMenu(hWnd), 2, MF_BYPOSITION | MF_ENABLED);
DrawMenuBar(hWnd);
/* get all the words from the user list and process */
Pwcount = SendMessage(hUEdit, EM_GETLINECOUNT, 0, 0L);
Bogus = FALSE;
/* Capitalize, trim, and copy all words from Edit to List */
for (Pwi=0; Pwi<Pwcount; Pwi++)
{
CurWord[0] = 30; CurWord[1] = 0; /* windows wants a max byte count */
Pwl = SendMessage(hUEdit, EM_GETLINE, Pwi, (LONG) (LPSTR) CurWord);
TrimWord(CurWord, Pwl); /* make into a valid word */
/* For each reasonable word, display in user's buffer, and optionally */
/* add it into the dictionary */
if (strlen(CurWord) > 3)
{
/* Remove suffix twice, for things like JAILERS, PAIRINGS... */
RemoveSuffix(CurWord, RootWord);
hCurWord = RemoveSuffix(RootWord, RootWord);
if (hCurWord)
/* if word is known, update frequencies */
IncFreq(hCurWord);
else if (Learn && strlen(RootWord) <= 10)
/* if in Learn mode, and word is short enough, try to add */
{
if (LocalCompact(USERWORDLEN) >= USERWORDLEN)
/* Do not learn words if low on heap space */
{
sprintf(OutBuf, "Add %s to dictionary?", RootWord);
if (IDYES == MessageBox(hWnd, OutBuf, "Learn Mode", MB_YESNO))
/* 0x8000 suffix indicates word is new and should */
/* have a human look it over for suffixes */
{
RemoveQu(RootWord);
AddLeaf(RootWord, &hTreeTop, 1, 0x8000, NULL);
DictChanged = TRUE;
}
}
else
{
MessageBox(hWnd, "Oops, running out of heap space (memory).\n"
"Turning word learning feature off.",
"Warning", MB_OK | MB_ICONASTERISK);
ProcessBagoLearn(hWnd, MN_DD_OPTIONS + MN_OPT_LEARN);
}
}
}
SendMessage(hUList, LB_INSERTSTRING, -1, (LONG) (LPSTR) CurWord);
}
/* Now all words are copied to player LIST */
/* Exchange the windows */
ShowWindow(hUList, SW_SHOW);
ShowWindow(hUEdit, SW_HIDE);
/* If the computer is playing, display it's list */
if (CPlay)
{
/* eliminate flicker */
SendMessage(hCList, WM_SETREDRAW, FALSE, 0L);
DisplayClist(hCListTop);
/* force update */
SendMessage(hCList, LB_INSERTSTRING, 0, (LONG) (LPSTR) "");
/* turn flicker back on so that scoring will be dramatic */
SendMessage(hCList, WM_SETREDRAW, TRUE, 0L);
SendMessage(hCList, LB_DELETESTRING, 0, 0L);
Cwcount = SendMessage(hCList, LB_GETCOUNT, 0, 0L);
}
/* Now start cancelling and disqualifying words */
for (Pwi=0; Pwi < Pwcount; Pwi++)
{
/* select word from player list and get it */
SendMessage(hUList, LB_SETCURSEL, Pwi, 0L);
SendMessage(hUList, LB_GETTEXT, Pwi, (LONG) (LPSTR) CurWord);
if (strlen(CurWord)) /* ignore blank lines */
{
BadWord = FALSE;
if (strlen(CurWord) < 4)
/* word too short */
{
strcpy(OutBuf, "<");
strcat(OutBuf, CurWord);
SendMessage(hUList, LB_DELETESTRING, Pwi, 0L);
SendMessage(hUList, LB_INSERTSTRING, Pwi, (LONG) (LPSTR) OutBuf);
BadWord = TRUE;
}
if (!BadWord && CPlay)
{
/* see if the word is duplicated in the computer's list */
for (Cwi=0; Cwi < Cwcount; Cwi++)
{
SendMessage(hCList, LB_GETTEXT, Cwi, (LONG) (LPSTR) CurWord1);
if (!strcmp(CurWord, CurWord1))
/* duplicate found between Player and Computer */
{
strcpy(OutBuf, " ");
strcat(OutBuf, CurWord);
SendMessage(hUList, LB_DELETESTRING, Pwi, 0L);
SendMessage(hUList, LB_INSERTSTRING, Pwi, (LONG) (LPSTR) OutBuf);
SendMessage(hCList, LB_DELETESTRING, Cwi, 0L);
SendMessage(hCList, LB_INSERTSTRING, Cwi, (LONG) (LPSTR) OutBuf);
BadWord = TRUE;
break;
}
}
}
if (!BadWord)
{
/* See if player listed this word twice */
/* Tricky, since could match a word already disqualified */
for (Cwi=0; Cwi < Pwi; Cwi++)
{
SendMessage(hUList, LB_GETTEXT, Cwi, (LONG) (LPSTR) CurWord1);
if ((*CurWord1 >= 'A' && !strcmp(CurWord, CurWord1))
|| (*CurWord1 < 'A' && !strcmp(CurWord, &CurWord1[1])))
/* word was listed twice - disqualify the second one */
{
strcpy(OutBuf, "\"");
strcat(OutBuf, CurWord);
SendMessage(hUList, LB_DELETESTRING, Pwi, 0L);
SendMessage(hUList, LB_INSERTSTRING, Pwi, (LONG) (LPSTR) OutBuf);
BadWord = TRUE;
break;
}
}
}
if (!BadWord)
/* Make sure the word does appear on the board */
{
BOOL found;
int row, col;
RemoveQu(CurWord); /* convert Qu to Q for verifying purposes */
found = FALSE;
for (row=0; row<NROWS; row++)
{
for (col=0; col<NCOLS; col++)
{
if (verify(CurWord, &board[row][col], strlen(CurWord)))
{
found = TRUE;
break;
}
}
if (found) break;
}
AddQu(CurWord); /* put back Qu for display */
if (!found)
{
strcpy(OutBuf, "?");
strcat(OutBuf, CurWord);
SendMessage(hUList, LB_DELETESTRING, Pwi, 0L);
SendMessage(hUList, LB_INSERTSTRING, Pwi, (LONG) (LPSTR) OutBuf);
BadWord = TRUE;
}
}
if (!BadWord && (!isword(CurWord) || Pwi == Pwcount-1))
/* 1. Possibly a partial line is typed as the last entry */
/* 2. Somebody trying to enter bogus words */
{
/* do we know the word? */
RemoveSuffix(CurWord, OutBuf); /* remove suffix twice in case */
RemoveSuffix(OutBuf, OutBuf); /* MAKERS, MAKINGS, etc */
RemoveQu(OutBuf);
if (!FindLeaf(OutBuf, hTreeTop))
{
sprintf(OutBuf, "Is %s really a word?", CurWord);
if (MessageBox(hWnd, OutBuf, "", MB_YESNO) == IDNO)
{
strcpy(OutBuf, "-");
strcat(OutBuf, CurWord);
SendMessage(hUList, LB_DELETESTRING, Pwi, 0L);
SendMessage(hUList, LB_INSERTSTRING, Pwi, (LONG) (LPSTR) OutBuf);
BadWord = TRUE;
}
/* Don't penalize for a badly typed last line */
else if (Pwi != Pwcount-1) Bogus = TRUE;
}
}
}
}
/* Show the score and update stats*/
NGames++;
Uscore = Score(hUList, &RUScore, &RUWords);
if (CPlay) Cscore = Score(hCList, &RCScore, &RCWords);
/* Set up for the next game */
NextRackNumber = rand();
SetNextRack(NextRackNumber);
SetCursor(hOldCursor); /* end of lengthy operation */
/* Disable stop and enter buttons */
EnableWindow(hEnter, FALSE);
EnableWindow(hStop, FALSE);
/* do the rewards here */
if (Uscore > Cscore)
{
RewardLevel = 0;
if (Bogus)
RewardLevel = -1; /* The cheater's reward */
else
{
if (Smartness >= 50 && CountLeaf(hTreeTop) >=400)
/* no big rewards if no challenge */
{
if (Uscore >= Cscore+5) RewardLevel++;
if (Uscore >= Cscore+10) RewardLevel++;
if (Uscore >= Cscore+15) RewardLevel++;
if (Smartness == 100) RewardLevel++;
}
}
/* Select appropriate reward for winner */
switch (RewardLevel)
{
case -1:
strcpy(OutBuf, "Good!"); /* no reward for cheaters */
PicNumber = 0;
break;
case 0:
case 1:
strcpy(OutBuf, "Good!");
PicNumber = REWARD1BMP;
break;
case 2:
strcpy(OutBuf, "Excellent!");
PicNumber = REWARD2BMP;
break;
case 3:
strcpy(OutBuf, "Superb!");
PicNumber = REWARD3BMP;
break;
case 4:
strcpy(OutBuf, "Outstanding!");
PicNumber = REWARD4BMP;
break;
}
if (!Rewards) PicNumber = 0; /* supress the picture, usually */
CreatePicBox(hWnd, PicNumber, OutBuf);
}
else
{
/* Set focus to appropriate word list so keyboard-only user can */
/* see word paths */
/* note: if reward is given, focus is set upon CreatePicBox completion */
PostMessage(hWnd, WM_SETFOCUS, 0, 0L);
}
/* wait for music to complete */
if (Sound)
{
WaitSoundState(S_QUEUEEMPTY);
CloseSound();
}
}
/* Cleanup when window closed before program termination */
ProcessBagoClose(hWnd)
HWND hWnd;
{
RECT Rect;
char ProString[40];
BOOL junk;
/* Offer to save the dictionary if it changed size */
if (DictChanged)
if (MessageBox(hWnd, "Dictionary has changed. Save it?", "Bago", MB_YESNO)==IDYES)
ProcessBagoSaveDict(hWnd);
/* If a help window is open, kill it */
WinHelp(hWnd, BAGOHLP, HELP_QUIT, 0L);
/* Save window positions and option settings */
if (!IsIconic(hWnd))
{
/* but don't save ridiculous iconic dimensions */
GetWindowRect(hWnd, (LPRECT) &Rect);
sprintf(ProString, "%d %d %d %d", Rect.left, Rect.top, Rect.right, Rect.bottom);
WritePrivateProfileString("Bago", "Window", ProString, BAGOINI);
}
sprintf(ProString, "%d", Smartness);
WritePrivateProfileString("Bago", "Computer Smartness", ProString, BAGOINI);
sprintf(ProString, "%d", EndTime);
WritePrivateProfileString("Bago", "Game Duration", ProString, BAGOINI);
sprintf(ProString, "%d", Sound);
WritePrivateProfileString("Bago", "Sound", ProString, BAGOINI);
sprintf(ProString, "%d", UseTimer);
WritePrivateProfileString("Bago", "Timed Game", ProString, BAGOINI);
sprintf(ProString, "%d", Learn);
WritePrivateProfileString("Bago", "Learn Words", ProString, BAGOINI);
sprintf(ProString, "%d", CPlay);
WritePrivateProfileString("Bago", "Computer Plays", ProString, BAGOINI);
sprintf(ProString, "%d", RotCubes);
WritePrivateProfileString("Bago", "Rotatable Cubes", ProString, BAGOINI);
/* don't want to show string in BAGOINI unless it is enabled */
if (Rewards)
{
WritePrivateProfileString("Bago", "Pictoral", "1", BAGOINI);
DeleteObject(hEyes);
}
else
WritePrivateProfileString("Bago", "Pictoral", NULL, BAGOINI);
DestroyLeaf(hTreeTop); /* return all allocated memory */
DeleteObject(hGrayBrush0); /* put away our pens */
DeleteObject(hGrayBrush1);
DeleteObject(hGrayBrush2);
}
/* Load the dictionary from disk, if any */
/* return FALSE if dictionary not found */
BOOL LoadDictionary(hWnd)
{
int hDictFile; /* handle of dictionary file */
HANDLE hLeafPtr;
char linebuf[80];
DW TempWord; /* used to hold a single word at a time from dict */
char *bufptr;
OFSTRUCT OfStruct;
HWND hPro;
unsigned charcount;
HCURSOR hOldCursor;
/* possibly lengthy operation */
hOldCursor = SetCursor(hHourGlass);
/* Get rid of whatever dictionary was in core */
DestroyLeaf(hTreeTop);
hTreeTop = NULL;
if ((hDictFile = OpenFile(BAGODIC, &OfStruct, OF_READ)) < 0)
{
/* cannot open file */
return(FALSE);
}
else
{
hPro = CreateProBox(hWnd);
/* wParam is range, lParam is message string */
SendMessage(hPro, PRO_INIT, filelength(hDictFile), (LONG) (LPSTR) "Loading Dictionary");
charcount = 0;
while (!eof(hDictFile))
{
/* Stop reading if we run out of heap space. Leave enough for */
/* the user's edit window */
if (LocalCompact(USERWORDLEN) < USERWORDLEN)
{
MessageBox(hWnd, BAGODIC " is too big. Only part of it was read.",
"Warning", MB_OK | MB_ICONASTERISK | MB_SYSTEMMODAL);
break;
}
/* Read a line. There is no formatted read from a file handle */
/* Therefore, must build the line ourselves */
bufptr = linebuf;
while (read(hDictFile, bufptr, 1) > 0)
{
charcount++;
if (*bufptr == '\n') break; /* stop at end of line */
bufptr++;
}
*(++bufptr) = NULL; /* terminate the string */
if (strlen(linebuf))
{
TempWord.Freq = 0; /* default values in case only a */
TempWord.Suffix1 = 0; /* word is included on the line */
/* get word and add it to data structure */
sscanf(linebuf, "%s %d %X\r\n",
TempWord.w, &(TempWord.Freq), &(TempWord.Suffix1));
TrimWord(TempWord.w, strlen(TempWord.w));
AddLeaf(TempWord.w, &hTreeTop, TempWord.Freq,
TempWord.Suffix1, NULL);
}
SendMessage(hPro, PRO_SETPOS, charcount, 0L);
}
DestroyWindow(hPro);
close(hDictFile);
}
DictChanged = FALSE;
SetCursor(hOldCursor);
return(TRUE);
}
/* Takes input from dictionary on disk, and eliminates words with frequencies
* less than the Fence. Results written to a different file.
*/
BOOL CullDictionary(hWnd, Fence)
HWND hWnd;
int Fence;
{
OFSTRUCT OfStructD;
OFSTRUCT OfStructO;
int hDictFile; /* handle of dictionary file */
int hOutFile;
char linebuf[80];
int Freq;
char *bufptr;
int OverCount, UnderCount; /* Count of words over and under fence */
HWND hPro;
unsigned charcount;
HCURSOR hOldCursor;
/* possibly lengthy operation */
hOldCursor = SetCursor(hHourGlass);
if ((hDictFile = OpenFile(BAGODIC, &OfStructD,
OF_PROMPT | OF_CANCEL | OF_READ)) < 0)
{
/* cannot open file */
return(FALSE);
}
if ((hOutFile = OpenFile(BAGONEW, &OfStructO,
OF_PROMPT | OF_CANCEL | OF_CREATE | OF_WRITE)) < 0)
{
/* cannot open file */
close(hDictFile);
return(FALSE);
}
OverCount = 0;
UnderCount = 0;
hPro = CreateProBox(hWnd);
SendMessage(hPro, PRO_INIT, filelength(hDictFile), (LONG) (LPSTR) "Culling Dictionary");
charcount = 0;
while (!eof(hDictFile))
{
/* Read a line. There is no formatted read from a file handle */
/* Therefore, must build the line ourselves */
bufptr = linebuf;
while (read(hDictFile, bufptr, 1) > 0)
{
charcount++;
if (*bufptr == '\n') break; /* stop at end of line */
bufptr++;
}
*(++bufptr) = NULL; /* terminate the string */
if (strlen(linebuf))
{
/* Copy the line only if it has frequency >= fence */
Freq = 0; /* default frequency if none found */
sscanf(linebuf, "%*s %d", &Freq);
if (Freq >= Fence)
{
write(hOutFile, linebuf, strlen(linebuf));
OverCount++;
}
else
{
UnderCount++;
}
}
SendMessage(hPro, PRO_SETPOS, charcount, 0L);
}
DestroyWindow(hPro);
close(hDictFile);
close(hOutFile);
SetCursor(hOldCursor);
sprintf(linebuf, "Total Words: %3d\nKept: %3d\nDiscarded: %3d",
OverCount+UnderCount, OverCount, UnderCount);
MessageBox(hWnd, linebuf, "Results of Culling", MB_OK);
return(TRUE);
}
/* This procedure will reset the usage frequencies of a leaf and */
/* all its inferior leaves to zero */
ResetFreqLeaf(hLeafPtr)
HANDLE hLeafPtr;
{
PDW LeafPtr;
if (hLeafPtr)
{
LeafPtr = (PDW) LocalLock(hLeafPtr);
ResetFreqLeaf(LeafPtr -> lt); /* do all less than */
LeafPtr -> Freq = 0;
ResetFreqLeaf(LeafPtr -> gt); /* do all greater than */
LocalUnlock(hLeafPtr);
}
}
ProcessBagoCreate(hWnd, wParam, lParam)
HWND hWnd;
WORD wParam;
LONG lParam;
{
HMENU hMenu; /* handle to the System menu */
int row, col; /* used to address board array */
HDC hDC;
hHourGlass = LoadCursor(NULL, IDC_WAIT);
/* Rewards: a semi-hidden menu pick */
hMenu = GetSystemMenu(hWnd, FALSE);
AppendMenu(hMenu, MF_SEPARATOR, 0, 0L);
AppendMenu(hMenu, MF_STRING, MN_REWARDS, " ");
/* Load accelerators */
hAccTable = LoadAccelerators(hInst, MAKEINTRESOURCE(BAGOACCEL));
/* Initialize game variables */
/* The contents of the dice, and the linkages between cubes */
for (row=0; row < NROWS; row++)
for (col=0; col < NCOLS; col++)
{
board[row][col].val = '@'; /* This is the only time a cube has */
board[row][col].orient = 0; /* this null face */
board[row][col].link[0] = (row-1 >= 0 && col-1 >= 0) ? &(board[row-1][col-1]) : NULL;
board[row][col].link[1] = (row-1 >= 0) ? &(board[row-1][col]) : NULL;
board[row][col].link[2] = (row-1 >= 0 && col+1 < NCOLS) ? &(board[row-1][col+1]) : NULL;
board[row][col].link[3] = (col-1 >= 0) ? &(board[row][col-1]) : NULL;
board[row][col].link[4] = (col+1 < NCOLS) ? &(board[row][col+1]) : NULL;
board[row][col].link[5] = (row+1 < NROWS && col-1 >= 0) ? &(board[row+1][col-1]) : NULL;
board[row][col].link[6] = (row+1 < NROWS) ? &(board[row+1][col]) : NULL;
board[row][col].link[7] = (row+1 < NROWS && col+1 < NCOLS) ? &(board[row+1][col+1]) : NULL;
board[row][col].r = row;
board[row][col].c = col;
/* now make a corresponding control */
board[row][col].hWindow = CreateWindow("CubeButton",
"",
WS_CHILD | WS_DISABLED | WS_CLIPSIBLINGS | WS_VISIBLE,
col*32, row*32, 32, 32,
hWnd, /* parent handle */
ID_FIRSTCUBE + row*NCOLS + col, /* ID is function of position */
hInst, /* instance */
NULL); /* additional info */
if (!board[row][col].hWindow)
{
MessageBox(hWnd, "Unable to create cube controls", "BAD ERROR",
MB_ICONQUESTION | MB_OK);
break;
}
}
hTreeTop = NULL; /* There is no dictionary */
hCListTop = NULL; /* The computer has not found any words */
RackNumber = -1; /* No rack has been assigned, yet */
/* Set up the first rack (it is not shown until first NewGame() */
srand(GetCurrentTime());
NextRackNumber = rand();
SetNextRack(NextRackNumber);
/* Make controls for ENTER and STOP */
hEnter = CreateWindow("Button",
NULL,
WS_CHILD | WS_DISABLED | WS_CLIPSIBLINGS | WS_VISIBLE
| BS_OWNERDRAW | BS_PUSHBUTTON,
176, 64, 64, 32,
hWnd,
ID_ENTER,
hInst,
NULL);
hStop = CreateWindow("Button",
NULL,
WS_CHILD | WS_DISABLED | WS_CLIPSIBLINGS | WS_VISIBLE
| BS_OWNERDRAW | BS_PUSHBUTTON,
208, 112, 32, 32,
hWnd,
ID_STOP,
hInst,
NULL);
if (!hStop || !hEnter)
MessageBox(hWnd, "Unable to create cube controls", "BAD ERROR",
MB_ICONQUESTION | MB_OK);
/* Make the windows for user's word input */
/* Same location, but only one appears at a time */
hUEdit = CreateWindow("Edit", /* predefined EDIT type */
"", /* initial text for EDIT */
WS_CHILD | ES_MULTILINE | ES_UPPERCASE | WS_BORDER | WS_CLIPSIBLINGS
| WS_VSCROLL | ES_AUTOVSCROLL, /* window style */
0, 0, 0, 0, /* leave sizing to WM_SIZE */
hWnd, /* parent handle */
ID_USERWORDS, /* menu or child ID */
hInst, /* instance */
NULL); /* additional info */
hUList = CreateWindow("Listbox", /* Predefined type LISTBOX */
"", /* No title */
WS_CHILD | WS_BORDER | WS_CLIPSIBLINGS | LBS_USETABSTOPS | LBS_NOTIFY
| LBS_NOINTEGRALHEIGHT | WS_VSCROLL, /* window style */
0, 0, 0, 0, /* leave sizing to WM_SIZE */
hWnd, /* parent handle */
ID_PLIST, /* menu or child ID */
hInst, /* instance */
NULL); /* additional info */
if (!hUList || !hUEdit)
MessageBox(hWnd, "Unable to create window for player's words", "BAD ERROR",
MB_ICONQUESTION | MB_OK);
/* compute size and tab stops for list boxes */
hDC = GetDC(hWnd);
TabStops[0] = TScoreWidth * 4 / LOWORD(GetDialogBaseUnits());
TabStops[1] = (TScoreWidth + LOWORD(GetTextExtent(hDC, "0", 1)))
* 4 / LOWORD(GetDialogBaseUnits());
TabStops[2] = (TScoreWidth + LOWORD(GetTextExtent(hDC, "00", 2)))
* 4 / LOWORD(GetDialogBaseUnits());
ReleaseDC(hWnd, hDC);
/* Do the options */
Smartness = GetPrivateProfileInt("Bago", "Computer Smartness", 15, BAGOINI);
EndTime = GetPrivateProfileInt("Bago", "Game Duration", 180, BAGOINI);
Sound = GetPrivateProfileInt("Bago", "Sound", TRUE, BAGOINI);
UseTimer = GetPrivateProfileInt("Bago", "Timed Game", TRUE, BAGOINI);
Learn = GetPrivateProfileInt("Bago", "Learn Words", FALSE, BAGOINI);
CPlay = GetPrivateProfileInt("Bago", "Computer Plays", TRUE, BAGOINI);
RotCubes = GetPrivateProfileInt("Bago", "Rotatable Cubes", FALSE, BAGOINI);
Rewards = GetPrivateProfileInt("Bago", "Pictoral", FALSE, BAGOINI);
if (UseTimer)
{
UseTimer = FALSE; /* set up for routine which toggles */
ProcessBagoTimer(hWnd, MN_DD_OPTIONS + MN_OPT_TIMER);
}
if (CPlay)
{
CPlay = FALSE; /* set up for routine which toggles */
ProcessBagoCPlay(hWnd, MN_DD_OPTIONS + MN_OPT_CPLAY);
}
if (Learn)
{
Learn = FALSE; /* set up for routine which toggles */
ProcessBagoLearn(hWnd, MN_DD_OPTIONS + MN_OPT_LEARN);
}
if (RotCubes)
{
RotCubes = FALSE; /* set up for routine which toggles */
ProcessBagoRotCubes(hWnd, MN_DD_OPTIONS + MN_OPT_ROTATE);
}
if (Sound)
{
Sound = FALSE; /* set up for routine which toggles */
ProcessBagoSound(hWnd, MN_DD_OPTIONS + MN_OPT_SOUND);
}
if (Rewards)
{
Rewards = FALSE; /* set up for routine which toggles */
ProcessBagoRewards(hWnd, MN_DD_OPTIONS + MN_OPT_REWARDS);
}
/* Tell the User and computer lists where to put tabs */
PostMessage(hUList, LB_SETTABSTOPS, 3, (LONG)(LPSTR) TabStops);
/* Post message to start the rest of the initialization */
/* But first, we want to allow the window to be shown */
PostMessage(hWnd, BAGOM_INIT, 0, 0L);
}
/* Load a rack from a file. The first 25 alphabetic characters */
/* in the file are used. Note that this loads the NEXT rack, not the */
/* actual game board that is being displayed */
ProcessBagoGLoad(hWnd)
HWND hWnd;
{
int hInFile;
char InBuf[30];
OFSTRUCT OfStruct;
char *BufPtr;
int row, col; /* used to step through board array */
char InChar; /* We input characters one at a time */
int nchar; /* How many valid characters we have read */
char FileName[30];
int dummy;
strcpy(FileName, BAGOSAVE);
if (InputBox(hWnd, "Load rack from:",
FileName, &dummy, FALSE) == ID_CANCEL) return;
if ((hInFile = OpenFile(FileName, &OfStruct,
OF_PROMPT | OF_CANCEL | OF_READ)) >= 0)
{
nchar = 0;
BufPtr = InBuf;
while (nchar < NDICE && read(hInFile, &InChar, 1))
{
if (isupper(*BufPtr = toupper(InChar)))
{
BufPtr++;
nchar++;
}
}
close(hInFile);
if (nchar == NDICE)
{
BufPtr = InBuf;
for (row=0; row < NROWS; row++)
{
for (col=0; col < NCOLS; col++)
{
NextVal[row][col] = *BufPtr;
BufPtr++;
}
}
}
else
{
MessageBox(hWnd, "Not enough letters in file.", "Error",
MB_ICONQUESTION | MB_OK);
}
NextRackNumber = -1; /* we have no idea what rack number is, now */
}
}
/* Save the current rack into a file */
/* Also save's both the user's and computer's word lists, if any */
ProcessBagoGSave(hWnd)
HWND hWnd;
{
int hOutFile;
char OutBuf[40];
OFSTRUCT OfStruct;
char *BufPtr;
int row, col; /* used to step through board array */
int nlines; /* for writing out list box */
char FileName[30];
int dummy;
strcpy(FileName, BAGOSAVE);
if (InputBox(hWnd, "Save this rack to:",
FileName, &dummy, FALSE) == ID_CANCEL) return;
if ((hOutFile = OpenFile(FileName, &OfStruct,
OF_PROMPT | OF_CANCEL | OF_CREATE | OF_WRITE)) >= 0)
{
BufPtr = OutBuf;
for (row=0; row < NROWS; row++)
{
for (col=0; col < NCOLS; col++)
{
*BufPtr = board[row][col].val;
BufPtr++;
}
*BufPtr = '\r'; BufPtr++;
*BufPtr = '\n'; BufPtr++;
}
*BufPtr = NULL; /* terminating null */
write(hOutFile, OutBuf, strlen(OutBuf));
/* Copy user word list, if any */
if (hUList)
{
strcpy(OutBuf, "\r\nYour Words ----\r\n");
write(hOutFile, OutBuf, strlen(OutBuf));
nlines = SendMessage(hUList, LB_GETCOUNT, 0, 0L);
for (row=0; row < nlines; row++)
{
SendMessage(hUList, LB_GETTEXT, row, (LONG)(LPSTR) OutBuf);
strcat(OutBuf, "\r\n");
write(hOutFile, OutBuf, strlen(OutBuf));
}
}
/* Copy computer word list, if any */
if (hCList)
{
strcpy(OutBuf, "\r\nBago's Words ----\r\n");
write(hOutFile, OutBuf, strlen(OutBuf));
nlines = SendMessage(hCList, LB_GETCOUNT, 0, 0L);
for (row=0; row < nlines; row++)
{
SendMessage(hCList, LB_GETTEXT, row, (LONG) (LPSTR) OutBuf);
strcat(OutBuf, "\r\n");
write(hOutFile, OutBuf, strlen(OutBuf));
}
}
close(hOutFile);
}
}
/* Enable/disable feature of learning new words from user's play */
ProcessBagoLearn(hWnd, wParam)
HWND hWnd;
WORD wParam;
{
if (!Learn)
/* Not in learn mode, enable */
{
CheckMenuItem(GetMenu(hWnd), wParam, MF_BYCOMMAND | MF_CHECKED);
Learn = TRUE;
}
else
/* In learn mode now, disable */
{
CheckMenuItem(GetMenu(hWnd), wParam, MF_BYCOMMAND | MF_UNCHECKED);
Learn = FALSE;
}
}
/* Enable or disable the egg timer option. Check on the menu item is updated */
ProcessBagoTimer(hWnd, wParam)
HWND hWnd;
WORD wParam;
{
if (!UseTimer)
/* Egg timer does not exist, create it. */
{
CheckMenuItem(GetMenu(hWnd), wParam, MF_BYCOMMAND | MF_CHECKED);
hEgg = CreateEggTimer(hWnd);
UseTimer = TRUE;
}
else
{
CheckMenuItem(GetMenu(hWnd), wParam, MF_BYCOMMAND | MF_UNCHECKED);
DestroyWindow(hEgg);
hEgg = NULL;
UseTimer = FALSE;
}
}
/* The Computer play menu selection enables/disables computer from playing */
ProcessBagoCPlay(hWnd, wParam)
HWND hWnd;
WORD wParam;
{
HMENU hMenu;
hMenu = GetMenu(hWnd);
if (!CPlay)
/* If not in computer play, make the window */
{
RECT Rect;
CheckMenuItem(hMenu, wParam, MF_BYCOMMAND | MF_CHECKED);
hCList = CreateWindow("Listbox", /* Predefined type LISTBOX */
"", /* No title */
WS_CHILD | WS_VISIBLE | WS_BORDER | WS_CLIPSIBLINGS | LBS_NOTIFY
| LBS_USETABSTOPS | LBS_NOINTEGRALHEIGHT | WS_VSCROLL,
0, 0, 0, 0, /* let WM_SIZE handle */
hWnd, /* parent handle */
ID_CPLAY, /* menu or child ID */
hInst, /* instance */
NULL); /* additional info */
if (!hCList)
{
MessageBox(hWnd, "Unable to create Computer's window", "ERROR",
MB_ICONQUESTION | MB_OK);
}
else
{
/* so initial size will be set */
GetClientRect(hWnd, (LPRECT) &Rect);
SendMessage(hWnd, WM_SIZE, SIZENORMAL,
MAKELONG(Rect.right-Rect.left, Rect.bottom-Rect.top));
PostMessage(hCList, LB_SETTABSTOPS, 3, (LONG)(LPSTR) TabStops);
CPlay = TRUE;
}
}
else
/* Turn off Computer Play */
{
CheckMenuItem(hMenu, wParam, MF_BYCOMMAND | MF_UNCHECKED);
DestroyWindow(hCList); hCList = NULL;
CPlay = FALSE;
}
}
/* Generate statistics display */
ProcessBagoStats(hWnd)
HWND hWnd;
{
char outstring[200];
char compstring[50];
int RS; /* Recommended smartness level */
strcpy(outstring, "\t\tYou\t\Computer\n\n");
sprintf(compstring, "Total Score:\t%d\t%d\n", RUScore, RCScore);
strcat(outstring, compstring);
sprintf(compstring, "Total Words:\t%d\t%d\n", RUWords, RCWords);
strcat(outstring, compstring);
if (NGames > 0)
{
sprintf(compstring, "Words/Game:\t%d\t%d\n", RUWords/NGames, RCWords/NGames);
strcat(outstring, compstring);
}
if (NGames != 1)
sprintf(compstring, "\n%d Rounds Played.\n", NGames);
else
strcpy(compstring, "\n1 Round Played.\n");
strcat(outstring, compstring);
if (NGames > 0)
{
RS = (RCScore > 0) ? (1.0 * RUScore * Smartness) / RCScore : 100;
if (RS > 100) RS = 100;
sprintf(compstring, "Difficulty = %d; try %d for challenge.\n", Smartness, RS);
strcat(outstring, compstring);
}
else
{
sprintf(compstring, "Difficulty = %d.\n", Smartness);
strcat(outstring, compstring);
}
MessageBox(hWnd, outstring, "Statistics", MB_OK);
}
/* Enable/disable feature of realistically rotated cubes */
ProcessBagoRotCubes(hWnd, wParam)
HWND hWnd;
WORD wParam;
{
HMENU hMenu;
int row, col;
hMenu = GetMenu(hWnd);
if (!RotCubes)
/* Not in RotCube mode, enable */
{
CheckMenuItem(hMenu, wParam, MF_BYCOMMAND | MF_CHECKED);
RotCubes = TRUE;
}
else
/* In RotCube mode now, disable */
{
CheckMenuItem(hMenu, wParam, MF_BYCOMMAND | MF_UNCHECKED);
RotCubes = FALSE;
}
/* Need to redisplay all the cubes */
for (row=0; row<NROWS; row++)
for (col=0; col<NCOLS; col++)
InvalidateRect(board[row][col].hWindow, NULL, FALSE);
}
/* Enable/disable feature of pictoral rewards */
ProcessBagoRewards(hWnd, wParam)
HWND hWnd;
WORD wParam;
{
HMENU hMenu;
hMenu = GetSystemMenu(hWnd, FALSE);
if (!Rewards)
/* Not in Reward mode, enable */
{
hEyes = LoadBitmap(hInst, MAKEINTRESOURCE(EYESBMP));
ModifyMenu(hMenu, MN_REWARDS,
MF_BYCOMMAND | MF_BITMAP, MN_REWARDS,
(LPSTR) (LONG) hEyes);
Rewards = TRUE;
}
else
/* In Reward mode now, disable */
{
ModifyMenu(hMenu, MN_REWARDS,
MF_BYCOMMAND | MF_STRING, MN_REWARDS,
"");
DeleteObject(hEyes);
Rewards = FALSE;
}
}
/* Enable/disable sound */
ProcessBagoSound(hWnd, wParam)
HWND hWnd;
WORD wParam;
{
HMENU hMenu;
hMenu = GetMenu(hWnd);
if (!Sound)
/* Sound off, enable */
{
CheckMenuItem(hMenu, wParam, MF_BYCOMMAND | MF_CHECKED);
Sound = TRUE;
}
else
/* Sound on, disable */
{
CheckMenuItem(hMenu, wParam, MF_BYCOMMAND | MF_UNCHECKED);
Sound = FALSE;
}
}
/* saves the entire dictionary to disk */
ProcessBagoSaveDict(hWnd)
HWND hWnd;
{
OFSTRUCT OfStruct;
int hDictFile; /* handle of dictionary file */
HCURSOR hOldCursor;
if ((hDictFile = OpenFile(BAGODIC, &OfStruct,
OF_PROMPT | OF_CANCEL | OF_CREATE | OF_WRITE)) < 0)
{
MessageBox(hWnd, "Cannot open dictionary file for writing.", "ERROR",
MB_ICONQUESTION | MB_OK);
}
else
{
/* show hourglass for long file I/O */
hOldCursor = SetCursor(hHourGlass);
if (!PrintDict(hDictFile, hTreeTop))
MessageBox(hWnd, "Error writing dictionary file.", "ERROR",
MB_ICONQUESTION | MB_OK | MB_SYSTEMMODAL);
close(hDictFile);
DictChanged = FALSE;
SetCursor(hOldCursor);
}
}
/* Puts the entire dictionary into the user window */
ProcessBagoShowDict(hWnd)
HWND hWnd;
{
HANDLE hOldBuffer, hNewBuffer;
char *pNewBuffer;
SendMessage(hCList, WM_SETREDRAW, FALSE, 0L);
PrintLeaf(hCList, hTreeTop);
SendMessage(hCList, LB_INSERTSTRING, 0, (LONG) (LPSTR) "");
SendMessage(hCList, WM_SETREDRAW, TRUE, 0L);
SendMessage(hCList, LB_DELETESTRING, 0, 0L);
}
/* Optimize the dictionary */
/* The progress box is brought up to inform the user of our progress */
ProcessBagoOptDict(hWnd)
HWND hWnd;
{
HANDLE hFTreeTop; /* handle to top of frequency-sorted tree */
HANDLE hLinArray; /* handle to sorted linear array of HANDLES */
LPHANDLE LinArray; /* sorted linear array of HANDLES */
int nWords; /* count of number of words in tree */
int i; /* used to index LinArray */
LPDW pLinArray; /* used to retrieve data from LinArray */
int first, last; /* array bounds of a set with equal frequency */
int BaseFreq; /* the frequency of the set above */
int CurFreq; /* frequency of word under inspection */
HCURSOR hOldCursor;
HWND hPro;
OptimizeFail = FALSE;
hFTreeTop = NULL;
hOldCursor = SetCursor(hHourGlass);
nWords = CountLeaf(hTreeTop);
hPro = CreateProBox(hWnd);
SendMessage(hPro, PRO_INIT, nWords, (LONG) (LPSTR) "Re-sorting Dictionary");
i=0;
ReSortLeaf(hTreeTop, (LPHANDLE) &hFTreeTop, &i, hPro);
/* allocate enough space for linear array */
if (!OptimizeFail)
{
hLinArray = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT,
(LONG) nWords*sizeof(HANDLE));
if (!hLinArray) OptimizeFail = TRUE;
}
if (!OptimizeFail)
{
LinArray = (LPHANDLE) GlobalLock(hLinArray);
/* Get rid of old tree to free up space */
DestroyLeaf(hTreeTop);
hTreeTop = NULL;
/* copy the F-tree into array */
i = 0;
CopyTreeToLin(hFTreeTop, LinArray, &i);
/* Scan the linear array for sets of words with the same frequency */
/* and output a balanced tree for each set */
SendMessage(hPro, PRO_INIT, nWords, (LONG) (LPSTR) "Balancing Dictionary Tree");
i=0;
while (i < nWords)
{
first = i;
pLinArray = (LPDW) GlobalLock(LinArray[i]);
BaseFreq = pLinArray -> Freq;
LocalUnlock(LinArray[i]);
do {
i++;
if (i < nWords)
{
pLinArray = (LPDW) GlobalLock(LinArray[i]);
CurFreq = pLinArray -> Freq;
GlobalUnlock(LinArray[i]);
}
SendMessage(hPro, PRO_SETPOS, i, 0L);
} while (i < nWords && CurFreq == BaseFreq);
last = i - 1;
Balance(&hTreeTop, LinArray, first, last);
}
DestroyWindow(hPro);
if (MessageBox(hWnd, "Save optimized dictionary to disk?", "Optimize", MB_YESNO) == IDYES)
ProcessBagoSaveDict(hWnd);
/* Get rid of global memory used for sort */
GlobalUnlock(hLinArray);
GlobalFree(hLinArray);
}
else
/* OptimizeFail: not enough global memory */
{
/* print warning message */
MessageBox(hWnd, "Out of memory - can't optimize.", "ERROR",
MB_ICONQUESTION | MB_OK);
DestroyWindow(hPro);
}
/* This must be done whether or not GlobalAlloc failed */
DestroyGLeaf(hFTreeTop);
SetCursor(hOldCursor);
}
/****************************************************************************
FUNCTION: About(HWND, unsigned, WORD, LONG)
PURPOSE: Processes messages for "About" dialog box
MESSAGES:
WM_INITDIALOG - initialize dialog box
WM_COMMAND - Input received
COMMENTS:
No initialization is needed for this particular dialog box, but TRUE
must be returned to Windows.
Wait for user to click on "Ok" button, then close the dialog box.
****************************************************************************/
BOOL FAR PASCAL About(hDlg, message, wParam, lParam)
HWND hDlg;
unsigned message;
WORD wParam;
LONG lParam;
{
switch (message) {
case WM_INITDIALOG:
return (TRUE); /* returning TRUE sets focus to OK button */
case WM_COMMAND:
if (wParam == IDOK) { /* "OK" box selected? */
EndDialog(hDlg, NULL); /* Exits the dialog box */
return (TRUE);
}
break;
}
return (FALSE); /* Didn't process a message */
}
/****************************************************************************
FUNCTION: EditWord(HWND, unsigned, WORD, LONG)
PURPOSE: Processes messages for "EditWord" dialog box
MESSAGES:
WM_INITDIALOG - initialize dialog box
WM_COMMAND - Input received
COMMENTS:
No initialization is needed for this particular dialog box, but TRUE
must be returned to Windows.
Wait for user to click on "Ok" button, then close the dialog box.
****************************************************************************/
BOOL FAR PASCAL EditWord(hDlg, message, wParam, lParam)
HWND hDlg;
unsigned message;
WORD wParam;
LONG lParam;
{
HWND hControl; /* handle to a control */
switch (message) {
case WM_INITDIALOG: /* message: initialize dialog box */
hWord = hTreeTop; /* good a starting point as any */
UpdateControls(hDlg);
return (FALSE); /* so windows won't arbitrarily set focus */
break;
case WM_COMMAND: /* message: received a command */
ProcessEditWordCommand(hDlg, wParam, lParam);
return (TRUE);
break;
default:
return (FALSE); /* Didn't process a message */
}
}
/* returns the Suffix mask, set according to the current buttons */
/* This automatically returns the virgin bit (MSB) set to 0, since */
/* ID_LASTSUF - ID_FIRSTSUF does not do all 16 bits */
unsigned GetButtons(hDlg)
HWND hDlg;
{
int nControl;
unsigned BitMask;
BitMask = 0;
for (nControl=ID_LASTSUF; nControl >= ID_FIRSTSUF; nControl--)
{
BitMask <<= 1; /* move to the next bit */
if (IsDlgButtonChecked(hDlg, nControl)) BitMask |= 1;
}
return(BitMask);
}
/* Posts the frequency and suffix information from the dialog box to the
* dictionary. The word is assumed to be in global handle hWord.
* Returns TRUE if hWord is NULL, or the posting succeneds.
* Returns FALSE if the posting fails. Only possible reason for failure
* at present is invalid frequency.
*/
BOOL PostWord(hDlg)
{
PDW pWord; /* pointer to record corresponding to current word */
BOOL TransOk; /* flag to see if integer translation succeeded */
int TempFreq; /* frequency in the dialog box, if any */
unsigned TempSuffix;
if (hWord)
{
TempFreq = GetDlgItemInt(hDlg, ID_FREQ, (BOOL FAR *)&TransOk, FALSE);
if (TransOk)
{
/* rewrite an existing leaf */
pWord = (PDW) LocalLock(hWord);
/* pWord -> w should already be right */
pWord -> Freq = TempFreq;
/* user may have changed buttons, so get latest settings */
TempSuffix = GetButtons(hDlg);
/* note that GetButtons() by nature kills the virgin bit */
if (pWord -> Suffix1 != TempSuffix)
{
pWord -> Suffix1 = TempSuffix;
DictChanged = TRUE;
}
LocalUnlock(hWord);
return(TRUE);
}
else
{
MessageBox(hDlg, "Need non-negative number for word frequency", "Error",
MB_OK);
/* restore word in case it was altered */
pWord = (PDW) LocalLock(hWord);
SetDlgItemText(hDlg, ID_WORD, (LPSTR) pWord -> w);
LocalUnlock(hWord);
/* Give the focus to the frequency */
SetFocus(GetDlgItem(hDlg, ID_FREQ));
return(FALSE);
}
}
else
return(TRUE);
}
/* The processing of the dialog box for the dictionary editor is complex */
/* Processing of ID_OK:
* 1. Try to put all the current information back into the dictionary.
* 2. If the word has been changed, try to look it up in the dictionary.
* a. If the word exists, display the information.
* b. If the word is NULL, disable most of the editing fields in box.
* c. If the word is not in the dictionary, see if the user wants to add it.
* Processing of ID_DELETE:
* 1. Try to put all the current information back into the dictionary.
* 2. If the word given is in the dictionary
* a. Delete the word.
* b. Relocate any words which may have been in the linked list under
* the deleted word.
*/
ProcessEditWordCommand(hDlg, wParam, lParam)
HWND hDlg;
WORD wParam;
LONG lParam;
{
int NewWordLen;
PDW pWord; /* pointer to record corresponding to current word */
PDW pSuper; /* pointer to record superior to current word */
BOOL TransOk; /* flag to see if integer translation succeeded */
HANDLE hNextWord; /* next word handle temp storage */
static DW TempWord; /* temp copy of word begin considered */
switch (wParam)
{
case ID_OK:
if (!PostWord(hDlg)) return;
if (SendDlgItemMessage(hDlg, ID_WORD, EM_GETMODIFY, 0, 0L))
/* word has been changed, take action */
{
/* Remember the present word, in case user declines */
/* adding a new word */
hNextWord = hWord;
/* get the changed word (which may be null) */
NewWordLen = GetDlgItemText(hDlg, ID_WORD, (LPSTR) TempWord.w, 10);
TrimWord(TempWord.w, NewWordLen);
if (strlen(TempWord.w))
/* valid word - is it already in dictionary? */
{
RemoveQu(TempWord.w);
/* Fetch the word from the tree if we can */
if (hWord = FindExactLeaf(TempWord.w, hTreeTop))
{
pWord = (PDW) LocalLock(hWord);
TempWord.Suffix1 = pWord -> Suffix1;
TempWord.Freq = pWord -> Freq;
LocalUnlock(hWord);
}
else
/* add a word that is not already in the dictionary */
{
TempWord.Suffix1 = 0;
TempWord.Freq = 0;
if (MessageBox(hDlg, "Add the word?", "Not in Dictionary",
MB_YESNO) == IDYES)
{
AddLeaf(TempWord.w, &hTreeTop, 0, 0x8000, NULL);
/* retrieve handle for word we just added */
hWord = FindExactLeaf(TempWord.w, hTreeTop);
}
else
/* if adding new word declined, go back to old */
hWord = hNextWord;
}
}
else
/* if No word entered, go back to previous word */
hWord = hNextWord;
UpdateControls(hDlg);
}
/* else word has not changed. Do nothing. */
break;
case ID_CANCEL:
if (!PostWord(hDlg)) return;
EndDialog(hDlg, NULL); /* Exits the dialog box */
break;
case ID_DELETE:
if (!PostWord(hDlg)) return;
NewWordLen = GetDlgItemText(hDlg, ID_WORD, (LPSTR) TempWord.w, 10);
TrimWord(TempWord.w, NewWordLen);
RemoveQu(TempWord.w);
if (strlen(TempWord.w))
/* valid word - look up in dictionary */
{
/* if can find leaf, get rid of it */
if (hWord = FindExactLeaf(TempWord.w, hTreeTop))
{
/* first determine what next word diplayed will be */
/* must do this BEFORE modifying the tree */
hNextWord = FindPrev(hWord); /* could be null */
if (!hNextWord) hNextWord = FindNext(hWord); /* could be null */
pWord = (PDW) LocalLock(hWord);
/* delete reference in parent leaf */
if (pWord -> up)
{
pSuper = (PDW) LocalLock(pWord -> up);
/* null out reference to the word */
if (pSuper->lt == hWord) pSuper->lt = NULL;
if (pSuper->gt == hWord) pSuper->gt = NULL;
LocalUnlock(pWord -> up);
}
else
/* rare case that this word has no superior, it IS TreeTop */
{
hTreeTop = NULL;
}
/* relocate inferior leaves, if any, from deleted word */
if (pWord -> lt) RelocateLeaf(pWord -> lt, &hTreeTop, NULL);
if (pWord -> gt) RelocateLeaf(pWord -> gt, &hTreeTop, NULL);
LocalUnlock(hWord);
LocalFree(hWord);
hWord = hNextWord;
DictChanged = TRUE;
}
UpdateControls(hDlg);
}
break;
case ID_PREV:
if (!PostWord(hDlg)) return;
hNextWord = FindPrev(hWord);
/* Avoid running off end of list */
if (hNextWord) hWord = hNextWord;
UpdateControls(hDlg);
break;
case ID_NEXT:
if (!PostWord(hDlg)) return;
hNextWord = FindNext(hWord);
/* Avoid running off end of list */
if (hNextWord) hWord = hNextWord;
UpdateControls(hDlg);
break;
case ID_VIRGIN:
if (!PostWord(hDlg)) return;
hNextWord = FindVirgin(hTreeTop);
if (hNextWord)
hWord = hNextWord;
else
MessageBox(hDlg, "No virgin words in dictionary.",
"", MB_OK | MB_ICONASTERISK);
UpdateControls(hDlg);
break;
default:
break;
}
}
/* Enable and update the fields in the dialog box to match the word
* corresponding to handle hWord. If hWord is null, disable the
* appropriate fields.
*/
UpdateControls(hDlg)
HWND hDlg;
{
PDW LeafPtr;
int nControl; /* used for hiding and unhiding windows */
HWND hControl; /* used for hiding and unhiding windows */
if (hWord)
{
LeafPtr = (PDW) LocalLock(hWord);
/* display the word and its frequency */
AddQu(LeafPtr -> w);
SetDlgItemText(hDlg, ID_WORD, (LPSTR) LeafPtr -> w);
SetDlgItemInt(hDlg, ID_FREQ, LeafPtr -> Freq, FALSE);
/* display the suffixes with proper check marks */
UpdateButtons(hDlg, LeafPtr -> w, LeafPtr -> Suffix1);
/* show controls to allow editing */
for (nControl=ID_FIRSTEDIT; nControl <= ID_LASTEDIT; nControl++)
{
hControl = GetDlgItem(hDlg, nControl);
SetWindowLong(hControl, GWL_STYLE,
~WS_DISABLED & GetWindowLong(hControl, GWL_STYLE));
InvalidateRect(hControl, NULL, FALSE);
}
RemoveQu(LeafPtr -> w);
LocalUnlock(hWord);
}
else
{
/* blank out the word */
SetDlgItemText(hDlg, ID_WORD, "");
SetDlgItemText(hDlg, ID_FREQ, "");
/* blank out the suffix check boxes */
for (nControl=ID_FIRSTSUF; nControl <= ID_LASTSUF; nControl++)
{
SetDlgItemText(hDlg, nControl, "");
CheckDlgButton(hDlg, nControl, 0);
}
/* disable controls which are only appropriate when word is selected */
for (nControl=ID_FIRSTEDIT; nControl <= ID_LASTEDIT; nControl++)
{
hControl = GetDlgItem(hDlg, nControl);
SetWindowLong(hControl, GWL_STYLE,
WS_DISABLED | GetWindowLong(hControl, GWL_STYLE));
InvalidateRect(hControl, NULL, FALSE);
}
/* Give the focus to the word to enter */
SetFocus(GetDlgItem(hDlg, ID_WORD));
}
}
/* update all the buttons with their proper checks */
UpdateButtons(hDlg, RootWord, Suffixes)
HWND hDlg;
char RootWord[];
unsigned Suffixes;
{
int nControl;
char FullWord[13];
unsigned BitMask;
BitMask = 1;
for (nControl=ID_FIRSTSUF; nControl <= ID_LASTSUF; nControl++)
{
AddSuffix(RootWord, nControl, FullWord);
SetDlgItemText(hDlg, nControl, FullWord);
if (BitMask & Suffixes)
/* if the bit is set, check the box */
CheckDlgButton(hDlg, nControl, 1);
else
CheckDlgButton(hDlg, nControl, 0);
BitMask <<= 1; /* move to the next bit */
}
}
/* Add the suffix specified by suf_id to the RootWord. Store result in dest */
/* Warning: this routine may barf if given words shorter than 3 letters */
AddSuffix(RootWord, suf_id, Dest)
char RootWord[], Dest[];
int suf_id;
{
int len;
strcpy(Dest, RootWord);
len = strlen(Dest);
switch (suf_id)
{
case ID_SUFS:
if (Dest[len-1] == 'Y' && Dest[len-2] != 'A')
{
Dest[len-1] = NULL;
strcat(Dest, "IES");
}
else if (Dest[len-1] == 'S' || Dest[len-1] == 'H' || Dest[len-1] == 'X')
strcat(Dest, "ES");
else if (Dest[len-1] == 'F')
{
Dest[len-1] = NULL;
strcat(Dest, "VES");
}
else
strcat(Dest, "S");
break;
case ID_SUFED:
if (Dest[len-1] == 'E')
strcat(Dest, "D");
else if (Dest[len-1] == 'Y' && Dest[len-2] != 'A')
{
Dest[len-1] = 'I';
strcat(Dest, "ED");
}
else if (isdoub(Dest[len-1]) && isvowel(Dest[len-2]) && !isvowel(Dest[len-3]))
/* if word ends in consonant-vowel-consonant, double last letter */
{
Dest[len] = Dest[len-1];
Dest[len+1] = NULL;
strcat(Dest, "ED");
}
else
strcat(Dest, "ED");
break;
case ID_SUFER:
if (Dest[len-1] == 'E')
strcat(Dest, "R");
else if (Dest[len-1] == 'Y' && Dest[len-2] != 'A')
{
Dest[len-1] = 'I';
strcat(Dest, "ER");
}
else if (isdoub(Dest[len-1]) && isvowel(Dest[len-2]) && !isvowel(Dest[len-3]))
/* if word ends in consonant-vowel-consonant, double last letter */
{
Dest[len] = Dest[len-1];
Dest[len+1] = NULL;
strcat(Dest, "ER");
}
else
strcat(Dest, "ER");
break;
case ID_SUFEN:
if (Dest[len-1] == 'E')
strcat(Dest, "N");
else if (isdoub(Dest[len-1]) && isvowel(Dest[len-2]) && !isvowel(Dest[len-3]))
/* if word ends in consonant-vowel-consonant, double last letter */
{
Dest[len] = Dest[len-1];
Dest[len+1] = NULL;
strcat(Dest, "EN");
}
else
strcat(Dest, "EN");
break;
case ID_SUFY:
if (Dest[len-1] == 'E')
Dest[len-1] = 'Y';
else if (isdoub(Dest[len-1]) && isvowel(Dest[len-2]) && !isvowel(Dest[len-3]))
/* if word ends in consonant-vowel-consonant, double last letter */
{
Dest[len] = Dest[len-1];
Dest[len+1] = 'Y';
Dest[len+2] = NULL;
}
else
strcat(Dest, "Y");
break;
case ID_SUFLY:
strcat(Dest, "LY");
break;
case ID_SUFEST:
if (Dest[len-1] == 'E')
strcat(Dest, "ST");
else if (Dest[len-1] == 'Y' && Dest[len-2] != 'A')
{
Dest[len-1] = 'I';
strcat(Dest, "EST");
}
else if (isdoub(Dest[len-1]) && isvowel(Dest[len-2]) && !isvowel(Dest[len-3]))
/* if word ends in consonant-vowel-consonant, double last letter */
{
Dest[len] = Dest[len-1];
Dest[len+1] = NULL;
strcat(Dest, "EST");
}
else
strcat(Dest, "EST");
break;
case ID_SUFISH:
if (Dest[len-1] == 'E') Dest[len-1] = NULL;
strcat(Dest, "ISH");
break;
case ID_SUFING:
/* drop the e before ing */
if (Dest[len-1] == 'E')
{
Dest[len-1] = NULL;
strcat(Dest, "ING");
}
else if (isdoub(Dest[len-1]) && isvowel(Dest[len-2]) && !isvowel(Dest[len-3]))
/* if word ends in consonant-vowel-consonant, double last letter */
{
Dest[len] = Dest[len-1];
Dest[len+1] = NULL;
strcat(Dest, "ING");
}
else
strcat(Dest, "ING");
break;
case ID_SUFERS:
if (Dest[len-1] == 'E')
strcat(Dest, "RS");
else if (Dest[len-1] == 'Y' && Dest[len-2] != 'A')
{
Dest[len-1] = 'I';
strcat(Dest, "ERS");
}
else if (isdoub(Dest[len-1]) && isvowel(Dest[len-2]) && !isvowel(Dest[len-3]))
/* if word ends in consonant-vowel-consonant, double last letter */
{
Dest[len] = Dest[len-1];
Dest[len+1] = NULL;
strcat(Dest, "ERS");
}
else
strcat(Dest, "ERS");
break;
case ID_SUFINGS:
/* drop the e before ing */
if (Dest[len-1] == 'E')
{
Dest[len-1] = NULL;
strcat(Dest, "INGS");
}
else if (isdoub(Dest[len-1]) && isvowel(Dest[len-2]) && !isvowel(Dest[len-3]))
/* if word ends in consonant-vowel-consonant, double last letter */
{
Dest[len] = Dest[len-1];
Dest[len+1] = NULL;
strcat(Dest, "INGS");
}
else
strcat(Dest, "INGS");
break;
}
}
/* number of bad combinations to search */
#define NBADCHARS 274
static char badchars[NBADCHARS][3] = {
"AA","BC","BD","BF","BG","BH",
"BK","BN","BP","BQ","BV","BW","BX","BZ",
"CB","CD","CF","CG","CJ","CP","CQ",
"CV","CW","CX","CZ","DC","DF","DH","DK",
"DP","DQ","DX","DZ","FB","FC","FD",
"FG","FH","FJ","FK","FM","FN","FP","FQ","FV",
"FW","FX","FZ","GB","GC","GD","GF","GJ","GK","GP",
"GQ","GT","GV","GW","GX","GZ","HB","HC","HD",
"HF","HG","HH","HJ","HK","HL","HP","HQ",
"HS","HV","HW","HX","HZ","IH","II","IW","IY",
"JB","JC","JD","JF","JG","JH","JJ","JK","JL","JM",
"JN","JP","JQ","JR","JS","JT","JV","JW","JX","JY",
"JZ","KB","KC","KD","KF","KG","KH","KJ","KK","KM",
"KP","KQ","KR","KT","KV","KW","KX","KZ","LH",
"LJ","LQ","LR","LX","LZ","MC","MD",
"MG","MH","MJ","MK","ML","MQ","MR","MV",
"MW","MX","MZ","NQ",
"OJ","OQ","PC","PF","PG","PJ",
"PK","PM","PN","PQ","PV","PX","PZ","QA","QB",
"QC","QD","QE","QF","QG","QH","QI","QJ","QK","QL",
"QM","QN","QO","QP","QQ","QR","QS","QT","QV","QW",
"QX","QY","QZ","RJ","RX","RZ","SB",
"SF","SG","SJ","SR","SV","SX","SZ","TB","TD","TG",
"TJ","TK","TP","TQ","TV","TX","UH",
"UJ","UQ","UV","UW","VB","VC","VD",
"VF","VG","VH","VJ","VK","VL","VM","VN","VP","VQ",
"VR","VS","VT","VU","VV","VW","VX","VZ","WC",
"WG","WJ","WP","WQ","WV",
"WW","WX","WZ","XB","XD","XF","XG","XJ",
"XK","XM","XN","XQ","XR","XS","XV",
"XW","XX","XZ","YF","YH","YJ","YK",
"YQ","YU","YV","YY","YZ","ZB","ZC","ZD",
"ZF","ZG","ZH","ZJ","ZK","ZM","ZN","ZP","ZQ","ZR",
"ZS","ZT","ZV","ZW","ZX"
};
/* A cheap, inexact algorithm to cull out strings of letters */
/* that are probably not English words */
/* Based on finding unlikely two-character sequences in the string */
/* Warning: expects input to be capital-letters ONLY. Lowercase letters */
/* make the routine asssume the word is valid */
BOOL isword(TheWord)
char TheWord[];
{
int pos;
int EndCount;
char tstr[3];
int VowelCount;
/* check vowel count - don't want all vowels, or no vowels */
EndCount = strlen(TheWord);
VowelCount = 0;
for (pos=0; pos<EndCount; pos++)
if (isvowel(TheWord[pos])) VowelCount++;
if (VowelCount==0 || VowelCount==EndCount) return(FALSE);
EndCount -= 2;
if (EndCount < 0) return(FALSE);
for (pos=0; pos <= EndCount; pos++)
{
tstr[0] = TheWord[pos];
tstr[1] = TheWord[pos+1];
tstr[2] = 0;
if (bsearch(tstr, badchars, NBADCHARS, 3, strcmp)) return(FALSE);
}
return(TRUE);
}
/* These variables are applicable to the timer window only. */
/* If this were an object-oriented language, they would be in */
/* the domain of the set of timer subroutines */
WORD idGameTimer; /* ID of the timer which ticks every second */
int GameTime; /* used by Egg Timer window */
/* Pens stay around until window closed */
HPEN SandPen, FallingPen, EraserPen, GlassPen;
HBRUSH SandBrush, EraserBrush;
double K1; /* constant used to compute drawing of sand */
/****************************************************************************
FUNCTION: EggWndProc(HWND, unsigned, WORD, LONG)
PURPOSE: Processes messages
MESSAGES:
WM_CREATE - create window
WM_PAINT - repaint window
WM_TIMER - timer tick
EGGM_SET - sets timeout value
EGGM_START - start timer
EGGM_STOP - stop timer (force to end state)
WM_TIMER - 1 second game timer tick
WM_DESTROY - destroy window
COMMENTS:
This is the windows procedure for the Egg Timer window.
Once started, the egg timer automatically updates itself
every second until the timeout value is reached. A message
is then sent to the parent window to indicate that the timer
has run out.
****************************************************************************/
long FAR PASCAL EggWndProc(hEgg, message, wParam, lParam)
HWND hEgg; /* window handle */
unsigned message; /* type of message */
WORD wParam; /* additional information */
LONG lParam; /* additional information */
{
PAINTSTRUCT ps; /* used by paint procedure */
HDC hDC; /* used by paint procedure */
switch (message) {
case WM_CREATE: /* message: window being created */
ProcessEggCreate(hEgg);
break;
case WM_PAINT:
hDC = BeginPaint(hEgg, (LPPAINTSTRUCT) &ps);
ProcessEggPaint(hEgg, hDC);
EndPaint(hEgg, (LPPAINTSTRUCT) &ps);
break;
case EGGM_SET:
/* wParam is timeout value in seconds. Refuse nonpositive values */
EndTime = max(wParam, 1);
K1 = 60. * pow(10. / EndTime, 0.3333);
break;
case WM_LBUTTONDOWN:
PostMessage(GetParent(hEgg), WM_COMMAND, MN_DD_GAME + MN_GAM_PLAY, 0L);
break;
case EGGM_START:
ProcessEggStart(hEgg);
break;
case EGGM_STOP:
if (!GameOver)
{
if (idGameTimer) KillTimer(hEgg, ID_GT); idGameTimer = 0;
/* Reset the picture, too */
GameTime = EndTime;
/* Want timer to be redrawn immediately, especially before scoring */
InvalidateRect(hEgg, NULL, TRUE);
UpdateWindow(hEgg);
/* Send message to parent, saying timer popped */
PostMessage(GetParent(hEgg), BAGOM_ENDGAME, 0, 0L);
}
break;
case WM_TIMER:
ProcessEggTimer(hEgg);
break;
case WM_DESTROY: /* message: window being destroyed */
if (idGameTimer) KillTimer(hEgg, ID_GT); idGameTimer = 0;
/* put away our pens and brushes */
DeleteObject(SandPen);
DeleteObject(FallingPen);
DeleteObject(SandBrush);
PostMessage(GetParent(hEgg), WM_SETFOCUS, hEgg, 0L);
break;
default: /* Passes it on if unproccessed */
return (DefWindowProc(hEgg, message, wParam, lParam));
}
return (NULL);
}
/* Egg Timer processing routines */
ProcessEggCreate(hEgg)
HWND hEgg;
{
GlassPen = GetStockObject(BLACK_PEN);
SandPen = CreatePen(0, 1, RED);
SandBrush = CreateSolidBrush(RED);
FallingPen = CreatePen(2, 1, RED);
EraserPen = CreatePen(0, 1, Mono ? WHITE : GRAY1);
EraserBrush = hGrayBrush1;
GameTime = EndTime;
/* This is also recomputed every time the game length is set */
K1 = 60. * pow(10. / EndTime, 0.3333);
SetFocus(hEgg);
}
/* This routine is called every time the 1-second timer goes off */
ProcessEggTimer(hEgg)
HWND hEgg;
{
if (GameTime >= EndTime)
/* This round is over */
{
if (idGameTimer) KillTimer(hEgg, ID_GT); idGameTimer = 0; /* stop the timer */
/* Send message to parent, saying timer popped */
PostMessage(GetParent(hEgg), BAGOM_ENDGAME, 0, 0L);
}
else
GameTime++;
InvalidateRect(hEgg, NULL, FALSE);
UpdateWindow(hEgg);
}
/* Set up device context for Egg Timer window. */
/* Regardless of physical dimensions of window, want to map a grid */
/* of +-70 wide and +-290 high */
SetupEggDC (hEgg, hDC)
HWND hEgg;
HDC hDC;
{
RECT rClient;
GetClientRect(hEgg, &rClient);
SetMapMode (hDC, MM_ANISOTROPIC);
SetWindowOrg (hDC, -70, -290);
SetWindowExt( hDC, 140, 580);
SetViewportOrg (hDC, rClient.left, rClient.bottom);
SetViewportExt (hDC, rClient.right-rClient.left, rClient.top-rClient.bottom);
SetPolyFillMode(hDC, ALTERNATE);
SetBkMode(hDC, TRANSPARENT);
return(TRUE);
}
/* This draws the picture of the Egg timer and sand.
* The routine is written with low flicker as the first priority.
* Speed is important, too, but it is second priority.
* In trying to simulate the real way sand looks in an hourglass,
* the height of the sand varies linearly with time as long as the
* top of the sand is in the cylindrical part of the glass. When the
* top of the sand is in the funnel part of the glass, the height varies
* as the cube root of time. In other words, the sand level drops faster
* and faster once it reaches the funnel.
*/
/* The outline of the Egg Timer glass */
static int Glass[11][2] = {
{-10, 0},
{-50, 60},
{-50, 270},
{ 50, 270},
{ 50, 60},
{ 10, 0},
{ 50, -60},
{ 50, -270},
{-50, -270},
{-50, -60},
{-10, 0} };
/* The sand on the top. Some y-values get overwritten in execution */
static int TopSand[5][2] = {
{-40, 240},
{ 40, 240},
{ 40, 60},
{ 0, 0},
{-40, 60} } ;
/* The air in the top section */
static int TopAir[6][2] = {
{-40, 240},
{ 40, 240},
{ 40, 60},
{ 0, 0},
{ 0, 0},
{-40, 60} } ;
/* The sand in the funnel section only */
static int FunnelSand[3][2] = {
{-40, 60},
{ 40, 60},
{ 0, 0} } ;
ProcessEggPaint(hEgg, hDC)
HWND hEgg;
HDC hDC;
{
HPEN hOldPen;
HBRUSH hBrush, hOldBrush;
int yTemp; /* temporary storage to avoid floating point recomputation */
SetupEggDC(hEgg, hDC);
hOldPen = SelectObject(hDC, GlassPen);
hOldBrush = SelectObject(hDC, EraserBrush);
Polyline(hDC, (LPPOINT) Glass, 11);
/* On top, Draw Sand first, then Air (avoid flicker )*/
SelectObject(hDC, EraserPen);
SelectObject(hDC, SandBrush);
if (10*GameTime < 9*EndTime)
/* Both in the tube and funnel section on top */
{
yTemp = 240 - 200L * GameTime / EndTime;
/* Only the top of the sand changes */
TopSand[0][1] = TopSand[1][1] = yTemp;
Polygon(hDC, (LPPOINT) TopSand, 5);
SelectObject(hDC, EraserBrush);
Rectangle(hDC, -40, 240,
40, yTemp);
}
else if (GameTime < EndTime)
/* Exclusively in the funnel section */
{
yTemp = K1 * pow(EndTime - GameTime, 0.3333);
FunnelSand[0][1] = FunnelSand[1][1] = yTemp;
FunnelSand[0][0] = 0.6667 * yTemp;
FunnelSand[1][0] = -0.6667 * yTemp;
Polygon(hDC, (LPPOINT) FunnelSand, 3);
SelectObject(hDC, EraserBrush);
TopAir[3][1] = TopAir[4][1] = yTemp;
TopAir[3][0] = 0.6667 * yTemp;
TopAir[4][0] = -0.6667 * yTemp;
Polygon(hDC, (LPPOINT) TopAir, 6);
}
else
/* GameTime >= EndTime (Game over) */
{
SelectObject(hDC, EraserBrush);
TopSand[0][1] = TopSand[1][1] = 240;
Polygon(hDC, (LPPOINT) TopSand, 5);
}
/* show the falling sand except at start and end */
if (0 < GameTime && GameTime < EndTime)
SelectObject(hDC, FallingPen);
else
SelectObject(hDC, EraserPen);
yTemp = -260 + 200L * GameTime / EndTime;
MoveTo(hDC, 0, 0);
LineTo(hDC, 0, yTemp);
/* Now draw the sand on the bottom. */
SelectObject(hDC, SandPen);
SelectObject(hDC, SandBrush);
Rectangle(hDC, -40, yTemp,
40, -260);
SelectObject(hDC, hOldBrush);
SelectObject(hDC, hOldPen);
}
/* Start the Egg Timer */
ProcessEggStart(hEgg)
HWND hEgg;
{
GameTime = 0;
idGameTimer = SetTimer(hEgg, ID_GT, 1000, NULL);
if (idGameTimer)
InvalidateRect(hEgg, NULL, TRUE);
else
/* timer not created */
{
MessageBox(hEgg, "Can't get windows timer.", "ERROR",
MB_ICONQUESTION | MB_OK);
DestroyWindow(hEgg);
}
}
/********* Computer Play Routines *********/
/* global used to build trial word */
char TrialWord[25] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
int TrialLen;
/* Parital string search. Search the dictionary and return TRUE if the
* string TrialWord could possibly be part of a known word. Also saves
* TrialWord in a list if it is exactly a word. Written for search
* efficiency, terminating branches which would clearly be fruitless.
* psearch tries to return FALSE whenever possible, as a TRUE return
* will cause as many as 8 more psearch calls, not including recursion.
* If most of a dictionary word matches TrialWord, this routine tries
* appending suffixes to the dictionary word on the fly.
* Global TrialLen is actual length of TrialWord. Should be set
* before calling psearch. Not always equal to strlen(TrialWord),
* because Qu counts as 2 letters. Careful: in some cases, you
* actually want strlen(TrialWord)
*/
BOOL psearch(hLeaf)
HANDLE hLeaf;
{
PDW LeafPtr;
int result; /* result of string compare */
BOOL retval; /* return value */
int nSuffix; /* used for suffixing words */
char FullWord[13];
unsigned BitMask;
/* DBCount++; */
if (!hLeaf) return(FALSE);
/* assumption that some word will begin with any given single letter */
if (strlen(TrialWord) < 2) return(TRUE);
LeafPtr = (PDW) LocalLock(hLeaf);
result = strncmp(TrialWord, LeafPtr -> w, 2);
/* check 1st 2 characters to start. */
/* if trial=RATS, and current leaf is RATE, we would never go */
/* to RAT to try suffixes otherwise. This greatly degrades the */
/* searching efficiency, but provides enables the above case to */
/* be found */
if (result < 0)
/* trial word definitely less than dictionary word. */
{
retval = psearch(LeafPtr -> lt);
}
else if (result > 0)
{
retval = psearch(LeafPtr -> gt);
}
else
/* result == 0; first 2 characters match between trial and dict */
{
result = strcmp(TrialWord, LeafPtr -> w);
if (!result)
/* Perfect match. Record word and exit */
/* We know that we can return TRUE, because we already match the */
/* root word, and Trial is probably a subset of some suffixed version */
{
if (TrialLen > 3)
/* only record words of legal size */
{
if ((rand() % 100) < Smartness)
/* According to skill level, computer may miss some words */
{
AddLeaf(TrialWord, &hCListTop, 0, 0, NULL);
/* computer find counts as a usage */
if ((LeafPtr -> Freq) < 32767) (LeafPtr -> Freq)++;
}
}
retval = TRUE;
}
else
/* Non-perfect match. Must still consider lower leaves on */
/* both gt and lt leaves */
{
/* if trial is a subset of dict word, return TRUE */
retval = !strncmp(TrialWord, LeafPtr->w, strlen(TrialWord));
/* only search further if there's a chance we can find a */
/* legal word, or we are not sure whether this TrialWord */
/* will match part of some dictionary word */
if (TrialLen > 3 || !retval)
if (psearch(LeafPtr -> lt)) retval = TRUE;
if (TrialLen > 3 || !retval)
if (psearch(LeafPtr -> gt)) retval = TRUE;
if ((TrialLen > 3 || !retval) &&
!strncmp(TrialWord, LeafPtr->w, strlen(LeafPtr->w)-1))
/* As last resort, try suffixes. Costly operation. */
{
BitMask = 1;
for (nSuffix=ID_FIRSTSUF; nSuffix <= ID_LASTSUF; nSuffix++)
{
if (BitMask & (LeafPtr->Suffix1))
/* if the suffix is legal, try it */
{
AddSuffix(LeafPtr->w, nSuffix, FullWord);
if (!strcmp(TrialWord, FullWord))
/* matches suffixed word exactly */
{
if ((rand() % 100) < Smartness)
/* Force computer to miss some according to level */
{
AddLeaf(TrialWord, &hCListTop, 0, 0, NULL);
/* computer find counts as a usage */
if ((LeafPtr -> Freq) < 32767) (LeafPtr -> Freq)++;
}
}
else
/* possibility of trial="MAKI", return true for "MAKING" */
{
if (!retval && !strncmp(TrialWord, FullWord, strlen(TrialWord)))
retval = TRUE;
}
}
BitMask <<= 1; /* move to the next bit */
}
}
}
}
LocalUnlock(hLeaf);
return(retval);
}
/* This routine searches all possible words on paths starting from the
* die pointed to by CubePtr
* It is assumed that a subroutine will be called from this routine to
* determine whether any search path leads to a dead end. Otherwise,
* this routine will search all 25! + 24! + 23! ... + 1! combinations!
* The trial path is built in a global, TrialWord, for speed reasons.
*/
SearchBoard(CubePtr)
struct tagBOARD *CubePtr;
{
int l; /* length of trial string so far */
int neighbor;
MSG msg; /* for PeekMessage yield */
char RealWord[11]; /* TrialWord with Qu expanded */
if (GameOver) return; /* abort search if game over */
/* We want to yield, so as not to hog CPU resources with the word search */
/* Allow the message queue to clear */
/* Otherwise, window will appear to hang while the computer is searching */
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
if (CubePtr == NULL || (CubePtr -> Cused)) return; /* not a viable path */
CubePtr -> Cused = TRUE;
l = strlen(TrialWord);
TrialWord[l] = CubePtr -> val; /* append letter to search string */
strcpy(RealWord, TrialWord); /* find length of real word with Qu */
AddQu(RealWord);
TrialLen = strlen(RealWord);
if (psearch(hTreeTop))
{
for (neighbor=0; neighbor < 8; neighbor++)
SearchBoard(CubePtr -> link[neighbor]);
}
TrialWord[l] = 0; /* remove the character we appended */
CubePtr -> Cused = FALSE;
}
/* This routine recursively adds a leaf and all leaves below it in a
* tree to the computer word list. Used to display the words that the
* computer found when the game is over.
*/
DisplayCList(hLeafPtr)
HANDLE hLeafPtr;
{
PDW LeafPtr;
if (hLeafPtr)
{
LeafPtr = (PDW) LocalLock(hLeafPtr);
DisplayCList(LeafPtr -> lt); /* print all less than */
AddQu(LeafPtr -> w); /* Change Q to Qu for display purposes */
SendMessage(hCList, LB_INSERTSTRING, -1, (LONG) (LPSTR) LeafPtr->w);
DisplayCList(LeafPtr -> gt); /* print all greater than */
LocalUnlock(hLeafPtr);
}
}
/* Sets up the next rack, which will be copied to the board on the next */
/* call to NewGame. First, all the dice in the master list (dice[]) are */
/* set to the unused state. Then, a die is randomly selected and placed */
/* on the board, with a random face up. This routine insures by design */
/* that no master die is used more than once */
SetNextRack(Rack)
unsigned Rack;
{
int row, col; /* used to step through board array */
int cubeno; /* used to index master list of cubes */
srand(Rack);
for (cubeno=0; cubeno < NDICE; cubeno++)
dice[cubeno][0] = FALSE;
/* fill every space on the board */
for (row=0; row < NROWS; row++)
for (col=0; col < NCOLS; col++)
{
/* keep trying until a free cube is found */
do { cubeno = rand() % NDICE; } while (dice[cubeno][0]);
/* pick a face from the die, and an orientation */
NextVal[row][col] = dice[cubeno][(rand() % NFACES)+1];
NextOrient[row][col] = rand() % NORIENT;
dice[cubeno][0] = TRUE; /* mark the cube as used */
}
}
/* This function creates the progress box window. Do NOT call this */
/* function again before destroying the corresponding window */
/* Parameter passed in is handle of parent window */
/* The handle to the progress box window is returned */
HWND CreateProBox(hWnd)
HWND hWnd;
{
HWND hPro;
hPro = CreateWindow("Pro", /* window class */
"", /* window name */
WS_POPUP | WS_VISIBLE | WS_DLGFRAME,
0, 0, 0, 0, /* Leave sizing to window proc */
hWnd, /* parent handle */
NULL, /* menu or child ID */
hInst, /* instance */
NULL); /* additional info */
BringWindowToTop(hPro);
return(hPro);
}
/****************************************************************************
FUNCTION: ProWndProc(HWND, unsigned, WORD, LONG)
PURPOSE: Processes messages
COMMENTS:
This is the windows procedure for the Progress Box.
Similar to the progress bar. Used to placate user while
a lengthy operation is going on. WARNING: only accomodates
range to the limit of unsigned int (probably 64k).
****************************************************************************/
long FAR PASCAL ProWndProc(hPro, message, wParam, lParam)
HWND hPro; /* window handle */
unsigned message; /* type of message */
WORD wParam; /* additional information */
LONG lParam; /* additional information */
{
static LPSTR ProMsg; /* the message to be displayed by progress box */
static unsigned ProRange; /* range of bar will be 0 - ProRange */
static unsigned ProPos; /* current position */
PAINTSTRUCT ps; /* used by paint procedure */
HDC hDC; /* used by paint procedure */
static TEXTMETRIC tmFont; /* All this for initial size, place */
int x, y; /* placement of ProBox */
static int height, width; /* calculated size of ProBox */
HWND hWnd; /* handle of parent */
RECT Rect;
POINT Point;
static HBRUSH hMagBrush;
char OutBuf[5]; /* for the percentage */
switch (message) {
/* PRO_INIT should come in via SendMessage just after creation */
/* The wParam sets the range of the Progress bar. */
/* lParam sets the message text */
/* This code will make a progress box big enough for the text, */
/* and center it in the client area of the caller */
case PRO_INIT:
ProMsg = (LPSTR) lParam;
ProRange = wParam;
ProPos = 0;
hWnd = GetParent(hPro);
GetClientRect(hWnd, (LPRECT) &Rect);
hDC = GetDC(hPro);
GetTextMetrics(hDC, &tmFont); /* size the window */
height = tmFont.tmHeight * 6;
width = 2*tmFont.tmAveCharWidth + LOWORD(GetTextExtent(hDC, ProMsg, lstrlen(ProMsg)));
x = (Rect.right + Rect.left - width) >> 1; /* center the window */
y = (Rect.bottom + Rect.top - height) >> 1;
ReleaseDC(hPro, hDC);
Point.x = x; /* convert to screen coordinates for popup */
Point.y = y;
ClientToScreen(hWnd, &Point);
Point.x = max(Point.x, 0); /* in case parent too small */
Point.y = max(Point.y, 0);
MoveWindow(hPro, Point.x, Point.y, width, height, TRUE);
/* the invalidate is needed in case of init more than once */
InvalidateRect(hPro, NULL, TRUE);
/* adjust size slightly: client rect <> window size */
GetClientRect(hPro, (LPRECT) &Rect);
height = Rect.bottom - Rect.top;
width = Rect.right - Rect.left;
hMagBrush = CreateSolidBrush(MAGENTA);
break;
case PRO_SETPOS:
ProPos = wParam;
InvalidateRect(hPro, NULL, FALSE);
break;
case WM_PAINT:
/* This falls through to the paint code following */
break;
case WM_DESTROY:
DeleteObject(hMagBrush);
return(NULL);
break;
default: /* Passes it on if unproccessed */
return (DefWindowProc(hPro, message, wParam, lParam));
}
/* We do a paint on any message, as we cannot wait for posted WM_PAINT */
hDC = BeginPaint(hPro, (LPPAINTSTRUCT) &ps);
SetBkColor(hDC, Mono ? WHITE : GRAY1);
SetTextAlign(hDC, TA_CENTER | TA_TOP);
TextOut(hDC, (ps.rcPaint.right - ps.rcPaint.left) >>1,
tmFont.tmHeight, ProMsg, lstrlen(ProMsg));
sprintf(OutBuf, "%d%%", 100L*ProPos/ProRange);
TextOut(hDC, (ps.rcPaint.right - ps.rcPaint.left) >>1,
(tmFont.tmHeight *5) >>1, OutBuf, strlen(OutBuf));
/* the filled part of the bar */
SelectObject(hDC, hMagBrush); /* filled part of bar */
x = (long) (width - 2*tmFont.tmAveCharWidth) *ProPos/ProRange + tmFont.tmAveCharWidth;
Rectangle(hDC, tmFont.tmAveCharWidth, tmFont.tmHeight <<2,
x, tmFont.tmHeight * 5);
SelectObject(hDC, hGrayBrush0); /* empty part of bar */
Rectangle(hDC, x, tmFont.tmHeight <<2,
width-tmFont.tmAveCharWidth, tmFont.tmHeight * 5);
EndPaint(hPro, (LPPAINTSTRUCT) &ps);
return (NULL);
}
/****************************************************************************
FUNCTION: CubeWndProc(HWND, unsigned, WORD, LONG)
PURPOSE: Processes messages
COMMENTS:
This is the windows procedure for the the playing cube buttons.
The buttons change their appearance when pressed down.
Information on whether the button is pressed or not is contained
in the global data structure board[x][y].
****************************************************************************/
long FAR PASCAL CubeWndProc(hCube, message, wParam, lParam)
HWND hCube; /* window handle */
unsigned message; /* type of message */
WORD wParam; /* additional information */
LONG lParam; /* additional information */
{
PAINTSTRUCT ps; /* used by paint procedure */
HDC hDC; /* used by paint procedure */
int row, col, CubeNumber;
switch (message) {
case WM_LBUTTONDOWN:
case WM_MBUTTONDOWN:
case WM_RBUTTONDOWN:
ProcessCubeButton(hCube);
/* no break - fall through to paint */
case WM_PAINT:
hDC = BeginPaint(hCube, (LPPAINTSTRUCT) &ps);
ProcessCubePaint(hCube, hDC);
EndPaint(hCube, (LPPAINTSTRUCT) &ps);
break;
case WM_MOUSEACTIVATE:
/* Don't pass this to DefWindowProc, else parent gets it */
break;
default: /* Passes it on if unproccessed */
return (DefWindowProc(hCube, message, wParam, lParam));
}
return (NULL);
}
/* This is called when the player clicks on an enabled cube */
/* Toggles the flag saying whether button is down or not */
ProcessCubeButton(hWnd)
HWND hWnd;
{
int CubeNumber; /* this is used to decode which control is responding */
int row, col;
WORD cubeloc;
CubeNumber = GetWindowWord(hWnd, GWW_ID) - ID_FIRSTCUBE; /* which control? */
row = CubeNumber / NCOLS;
col = CubeNumber % NCOLS;
if (board[row][col].Uused)
{
board[row][col].Uused = FALSE;
/* if popping up cube, erase last entry in player list */
PostMessage(hUEdit, WM_CHAR, '\b', 1L);
PostMessage(GetParent(hWnd), BAGOM_CUBEUP, 0, 0L);
/* To delete a Q, must also delete the U */
if (board[row][col].val == 'Q')
PostMessage(hUEdit, WM_CHAR, '\b', 1L);
}
else
{
board[row][col].Uused = TRUE;
PostMessage(hUEdit, WM_CHAR, board[row][col].val, 1L);
cubeloc = col << 8 | row;
PostMessage(GetParent(hWnd), BAGOM_CUBEDN, cubeloc, 0L);
/* Q has automatic U following */
if (board[row][col].val == 'Q')
PostMessage(hUEdit, WM_CHAR, 'U', 1L);
}
InvalidateRect(hWnd, NULL, FALSE);
}
/* Paint procedure for cube button controls */
ProcessCubePaint(hWnd, hDC)
HWND hWnd;
HDC hDC;
{
HDC hMemoryDC, hMemoryDC1;
HBITMAP hOldBitmap, hOldBitmap1, hLetterBitmap, hBackBitmap;
int CubeNumber; /* this is used to decode which control is responding */
int row, col;
char letter; /* the letter shown on this cube */
int rot; /* the rotation of the letter */
HBITMAP hShade; /* used only for mono */
HBRUSH hShadeBrush, hOldBrush;
CubeNumber = GetWindowWord(hWnd, GWW_ID) - ID_FIRSTCUBE; /* which control? */
row = CubeNumber / NCOLS; /* what is on face? */
col = CubeNumber % NCOLS;
letter = board[row][col].val;
rot = (RotCubes ? board[row][col].orient : 0);
hMemoryDC = CreateCompatibleDC(hDC);
hMemoryDC1 = CreateCompatibleDC(hDC);
hLetterBitmap = LoadBitmap(hInst, MAKEINTRESOURCE(STARBMP+letter-'@'+rot*26));
hOldBitmap = SelectObject(hMemoryDC, hLetterBitmap);
if (hOldBitmap)
{
if (board[row][col].Uused) hBackBitmap = LoadBitmap(hInst, MAKEINTRESOURCE(DOWNBMP));
else hBackBitmap = LoadBitmap(hInst, MAKEINTRESOURCE(UPBMP));
hOldBitmap1 = SelectObject(hMemoryDC1, hBackBitmap);
if (Mono && board[row][col].Uused)
{
hShade = LoadBitmap(hInst, MAKEINTRESOURCE(SHADEBMP));
hShadeBrush = CreatePatternBrush(hShade);
hOldBrush = SelectObject(hMemoryDC1, hShadeBrush);
BitBlt(hMemoryDC1, 0, 0, 32, 32, hMemoryDC, 0, 0, 0x008003E9); /* DPSaa */
SelectObject(hMemoryDC1, hOldBrush);
DeleteObject(hShadeBrush);
DeleteObject(hShade);
}
else
BitBlt(hMemoryDC1, 0, 0, 32, 32, hMemoryDC, 0, 0, SRCAND);
/* copy to display only once */
BitBlt(hDC, 0, 0, 32, 32, hMemoryDC1, 0, 0, SRCCOPY);
SelectObject(hMemoryDC1, hOldBitmap1);
DeleteObject(hBackBitmap);
SelectObject(hMemoryDC, hOldBitmap);
}
DeleteObject(hLetterBitmap);
DeleteDC(hMemoryDC);
DeleteDC(hMemoryDC1);
}
/* Creates a pictoral display box. */
/* Displays the bitmap nicely, with the text centered underneath */
/* If no bitmap to display, specify PicNumber = 0 */
/* The box becomes system modal */
CreatePicBox(hWnd, PicNumber, PicMsg)
HWND hWnd;
int PicNumber;
LPSTR PicMsg;
{
HWND hPic;
PICSTRUCT PicStruct; /* init structure */
PicStruct.PicMsg = PicMsg;
PicStruct.PicNumber = PicNumber;
hPic = CreateWindow("Pic", /* window class */
"", /* window name */
WS_POPUP | WS_BORDER | WS_VISIBLE,
0, 0, 0, 0, /* window will size itself */
hWnd, /* parent handle */
NULL, /* no ID for popup window */
hInst, /* instance */
(LPSTR) &PicStruct); /* additional info */
SetSysModalWindow(hPic);
}
/****************************************************************************
FUNCTION: PicWndProc(HWND, unsigned, WORD, LONG)
PURPOSE: Processes messages
COMMENTS:
This is the window procedure for a very simple window which
displays a bitmap and some text. The bitmap and text are
passed in a structure upon initialization.
****************************************************************************/
long FAR PASCAL PicWndProc(hPic, message, wParam, lParam)
HWND hPic; /* window handle */
unsigned message; /* type of message */
WORD wParam; /* additional information */
LONG lParam; /* additional information */
{
static HBITMAP hBitmap; /* the picture to be displayed */
static LPPICSTRUCT lpPic; /* the parameter passed upon create */
static BITMAP Bitmap; /* to retrieve bitmap dimensions */
static char PicMsg[80];
PAINTSTRUCT ps; /* used by paint procedure */
HDC hDC; /* used by paint procedure */
int x, y; /* placement of PicBox */
/* TrueBmWidth compensates for aspect ratio of monitor */
static int height, width, TrueBmWidth;
static TEXTMETRIC tmFont;
int textwidth;
HWND hWnd; /* handle of parent */
RECT Rect;
POINT Point;
HBITMAP hOldBitmap; /* for drawing picture */
HDC hMemoryDC;
switch (message) {
case WM_CREATE:
lpPic = (LPPICSTRUCT) ((LPCREATESTRUCT) lParam) -> lpCreateParams;
if (lpPic -> PicNumber)
{
hBitmap = LoadBitmap(hInst, MAKEINTRESOURCE(lpPic -> PicNumber));
GetObject(hBitmap, sizeof(BITMAP), (LPSTR) &Bitmap);
}
else /* no bitmap specified */
{
Bitmap.bmHeight = 0;
Bitmap.bmWidth = 0;
hBitmap = 0;
}
lstrcpy(PicMsg, lpPic -> PicMsg);
hWnd = GetParent(hPic);
GetClientRect(hWnd, (LPRECT) &Rect);
hDC = GetDC(hPic);
GetTextMetrics(hDC, &tmFont); /* size the window */
height = Bitmap.bmHeight + tmFont.tmHeight * 2 + 2; /* +2 for thin border */
TrueBmWidth = MulDiv(Bitmap.bmWidth, GetDeviceCaps(hDC, ASPECTY), GetDeviceCaps(hDC, ASPECTX));
width = TrueBmWidth + 2;
textwidth = LOWORD(GetTextExtent(hDC, PicMsg, lstrlen(PicMsg))) + 2*tmFont.tmAveCharWidth;
/* width is larger of picture width or text width */
if (width < textwidth) width = textwidth;
x = (Rect.right + Rect.left - width) >> 1; /* center the window */
y = (Rect.bottom + Rect.top - height) >> 1;
ReleaseDC(hPic, hDC);
Point.x = x; /* convert to screen coordinates for popup */
Point.y = y;
ClientToScreen(hWnd, &Point);
Point.x = max(Point.x, 0); /* in case parent too small */
Point.y = max(Point.y, 0);
MoveWindow(hPic, Point.x, Point.y, width, height, TRUE);
break;
case WM_PAINT:
hDC = BeginPaint(hPic, (LPPAINTSTRUCT) &ps);
SetBkMode(hDC, TRANSPARENT);
SetTextAlign(hDC, TA_CENTER | TA_TOP);
SetTextColor(hDC, WHITE);
x = (width - 2) >> 1;
y = height - 2 - 3*tmFont.tmHeight/2; /* -2 because thin border */
TextOut(hDC, x, y, PicMsg, lstrlen(PicMsg));
/* now draw the picture, if there is one */
if (hBitmap)
{
x=(width-TrueBmWidth) >>1; /* center the picture */
y=0;
hMemoryDC = CreateCompatibleDC(hDC);
hOldBitmap = SelectObject(hMemoryDC, hBitmap);
SetStretchBltMode(hDC, COLORONCOLOR);
StretchBlt(hDC, x, y, TrueBmWidth, Bitmap.bmHeight,
hMemoryDC, 0, 0, Bitmap.bmWidth, Bitmap.bmHeight, SRCCOPY);
SelectObject(hDC, hOldBitmap);
DeleteDC(hMemoryDC);
}
EndPaint(hPic, (LPPAINTSTRUCT) &ps);
break;
/* close the window with almost any excuse */
case WM_CHAR:
case WM_LBUTTONDOWN:
case WM_MBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_CLOSE:
if (hBitmap) DeleteObject(hBitmap);
DestroyWindow(hPic);
break;
default: /* Passes it on if unproccessed */
return (DefWindowProc(hPic, message, wParam, lParam));
}
return (NULL);
}