home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Collection of Education
/
collectionofeducationcarat1997.iso
/
GAMES
/
CAROM10.ZIP
/
CAROMSRC.ZIP
/
CAROM.C
< prev
next >
Wrap
C/C++ Source or Header
|
1994-08-14
|
82KB
|
2,570 lines
/*
Copyright (c) 1994 Csaba Mßrkus. All rights reserved.
E-mail: ethcms@duna.ericsson.se
Addr.: H-9600 Sßrvßr, Szatmßr u. 4, Hungary
Permission to use, copy, modify, and distribute this software and its
documentation for any purpose, without fee, and without written agreement
is hereby granted, provided that the above copyright notice and the
following two paragraphs appear in all copies of this software.
IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR
DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE AUTHOR HAS
BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
THE AUTHOR DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE AUTHOR HAS
NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS
OR MODIFICATIONS.
*/
/* File: CAROM.C
Purpose: Windows program to implement the standard "carom"
billiard game
*/
#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include "carom.h"
#include "ballfull.h"
#include "trigo.h"
#define SPIN_MIN -10
#define SPIN_MAX 10
#define POWER_MIN 1
#define POWER_MAX 100
#define POWER_LOW_VALUE 20
#define POWER_MEDIUM_VALUE 50
#define POWER_HIGH_VALUE 80
/* Number of restorable views:
*/
#define VIEW_STACK_SIZE 10
/* Size of the shot history buffer:
*/
#define SHOT_BUFFER_SIZE (128L * 1024L)
/* There is a limit of magnification:
*/
#define MAX_MAGNIFY 2.0
/* Max. length of the help file name:
*/
#define EXE_NAME_MAX_SIZE 80
/* Macro to clamp a number into a given range */
#define clamp(x, l, h) ((x) < (l) ? (l) : ((x) > (h) ? (h) : (x)))
/* This type contains coordinates in millimeter units:
*/
typedef struct tagFPOINT {
float x;
float y;
} FPOINT;
// data initialized by first instance
typedef struct tagSETUPDATA {
char szAppName[20]; // name of application
char szParWinName[20]; // name of parameter window
} SETUPDATA;
SETUPDATA SetUpData;
/* The various states of the game are listed below. At any time, one
of hese values is stored in the GameState variable.
*/
enum game_states {
BEFORE_GAME = 0,
AFTER_GAME,
SETTING_SHOT,
ZOOMING_IN,
BALLS_MOVING
};
// Data that can be referenced throughout the
// program but not passed to other instances
HINSTANCE hInst; // hInstance of application
HWND hwnd; // hWnd of main window
DLGPROC lpDlgProc; // ptr to proc for dialog box
DLGPROC lpNewGameDlgProc; // ptr to proc for new game settings
DLGPROC lpParDlgProc; // ptr to proc for shot parameters
HWND hParDlg; // Handle of Parameters window
COLORREF ScreenColor[10]; // color array
#define TITLE_LENGTH 30
/* Place global variables here...
*/
int prev_horizontal_spin = 0;
int horizontal_spin = 0, horizontal_spin_save;
int prev_vertical_spin = 0;
int vertical_spin = 0, vertical_spin_save;
int shot_power = POWER_MEDIUM_VALUE, shot_power_save;
int spin_being_set = FALSE; /* True if the ball-spin-setting has the capture */
HWND old_capture_ball; /* Storage of previous capture */
HCURSOR old_cursor_ball; /* Handle of active cursor when changing to spin-setting */
int sight_line_being_set = FALSE; /* True if sight-line setting is in progress */
HWND old_capture_sight; /* Storage of previous capture */
HCURSOR old_cursor_sight; /* Handle of active cursor when changing to zoom cursor */
int zoom_being_set; /* True if zoom-rectangle is being set */
HWND old_capture_zoom; /* Storage of previous capture at zooming */
HCURSOR old_cursor_zoom; /* Handle of active cursor when changing to zoom cursor */
POINT zoom_upper_left; /* Upper left corner of zoom rectangle */
POINT zoom_lower_right; /* Lower right corner of zoom rectangle */
int zoom_rectangle_present; /* True if rectangle is already drawn */
int zoom_rectangle_new; /* True if UpdateZoom should draw new rectangle */
int GameState; /* Current state of the game */
int client_width; /* Width of drawable client area */
int client_height; /* Height of drawable client area */
int first_shot; /* True if this is going to be the first shot */
int first_shot_save; /* Storage for undo purposes */
/* The following variables contain the currently valid coordinates of the end of
the sight-line.
*/
FPOINT sight_line_end; /* Other end of sight-line */
FPOINT sight_line_end_save; /* Storage for undo purposes */
FPOINT sight_extra[2][2];
FPOINT sight_extra_save[2][2];
FPOINT old_sight_extra[2][2];
int sight_extra_drawn[2] = { 0, 0 };
int sight_extra_drawn_save[2];
int sight_extra_enabled[2] = { 1, 1 };
int sight_extra_present;
/* This variable contains the physical coordinates (millimeter units) of the point that
is located in the upper left corner of the main windows's client area when displayed.
*/
FPOINT upper_left;
/* Together with the previous upper_left variable, the magnification defines the visible
part of the carom-table:
The physical coordinates (p.x; p.y) are translated to client window coordinates
(c.x; c;y) with the following formulas:
c.x = floor((p.x - upper_left.x) * magnify + 0.5);
c.y = (upper_left.y - p.y) * magnify;
The other direction of the conversions, according to these formulas, is the following:
p.x = (c.x / magnify) + upper_left.x;
p.y = upper_left.y - (c.y / magnify);
*/
float magnify;
/* How many pixel the radius of the ball is depends only on the current magnification
value. Therefore, the radius is always computed when the magnification changes;
*/
int RADIUS;
/* Client window coordinates of spots:
*/
int SPOT_X1, SPOT_X2, SPOT_X3;
int SPOT_Y1, SPOT_Y2, SPOT_Y3;
/* This variable contains which player is to play:
0 : First player
1 : Second player
*/
int cueball;
int cueball_save; /* Storage for undo purposes */
/* Stack storage for the view parameters:
*/
struct {
float ulx;
float uly;
float mgn;
} view_stack[VIEW_STACK_SIZE]; /* Array to contain numbers */
int vs_next_free = 0; /* Index of next available place in the array */
int view_stack_elements = 0; /* Number of items already placed in the array */
/* Variables needed for displaying, animating the shot:
*/
/* Handle of global memory object which stores the history of the last
shot.
*/
HGLOBAL hglbShotHistory;
DWORD dwTakeShotTime; /* System time when shot is taken */
unsigned huge *shot_history; /* Pointer to shot-history buffer */
unsigned last_displayed_frame; /* Index of frame last drawn */
UINT globalidTimer1 = 0; /* Identifier of timer used for shot animation */
UINT globalidTimer2 = 0; /* ID of other timer used for shot animation */
/* Global variables that contain handles of graphics device objects:
*/
HRGN my_client_region; /* Always contains the (0,0)-(cli_w, cli_h) region */
HRGN my_inside_table_region; /* Region where balls can be located */
HPEN my_wood_pen; /* Pen to draw wooden frame of table */
HBRUSH my_wood_brush; /* Brush to draw wooden frame of table */
HPEN my_cush_pen; /* Pen to draw cushions */
HBRUSH my_cush_brush; /* Brush to draw cushions */
HPEN my_cloth_pen; /* Pen to draw a chuck of the cloth beneath balls */
HBRUSH my_cloth_brush; /* Brush to draw the cloth */
HPEN my_rectangle_pen; /* Pen to draw zoom-rectangle */
HPEN my_sight_line_pen; /* Pen to draw the sight line */
HPEN my_sight_line_extra_pen; /* Pen to draw the sight line */
HPEN my_ball_pen[N]; /* Different pens to draw outline of diff. balls */
HBRUSH my_ball_brush[N]; /* Brushes to draw the interior of balls */
int pens_brushes_present = FALSE; /* True if pens are already created */
/* State variable to indicate whether the path of the balls should be
kept displayed:
*/
int draw_path = CM_PATH_NONE;
/* Storage of information on all the balls, for undo purposes:
*/
ball_data ball_save[N];
/* Variables that are set at the beginning of a new game:
*/
char player1_name[40];
char player2_name[40];
int GameStyle = DL_NG_GAME0;
int WinningScore = 15;
int CushionPoints = TRUE;
int score[2] = { 0, 0};
int score_save[2];
int WhoStarts = DL_NG_STARTR;
int WhoStarted;
int EnableUndo = TRUE;
int EnableBreak = TRUE;
HBITMAP hbmpTemp = NULL; /* Ball-sized bitmap for temporary usage */
HBITMAP hbmpBallMask = NULL; /* Outside white, inside black */
HBITMAP hbmpClothBall = NULL; /* Outside black, inside cloth colour */
HBITMAP hbmpBall[N] = { NULL, NULL, NULL }; /* Outside black, inside ball[k] colour */
/* Help system variables:
*/
char szHelpFileName[EXE_NAME_MAX_SIZE+1]; /* Help file name*/
/* Flag to indicate that the system's timer should be used
to animate the shots:
*/
int TimeredShotDisplay = TRUE;
/* Storage of address of timer procedure:
*/
FARPROC lpShotTimerProc;
/* Macro to convert a fixed-point unsigned number to float. Inverse of FIXED(x) in
the ballfull module.
*/
#define UNFIXED(x) (((float) (x)) / FIXED_MULT)
/* Here are the conversion macros:
*/
#define X_PHYTOCLI(px) floor(((px) - upper_left.x) * magnify + 0.5)
#define Y_PHYTOCLI(py) floor((upper_left.y - (py)) * magnify + 0.5)
#define X_CLITOPHY(cx) (((cx) / magnify) + upper_left.x)
#define Y_CLITOPHY(cy) (upper_left.y - ((cy) / magnify))
// function prototypes
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpszCmdLine, int cmdShow);
void Init(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpszCmdLine, int cmdShow);
void InitScrollBar(HWND hDlg, WORD scroll, WORD edit,
int mi, int ma, int value);
LRESULT CALLBACK WndProc(HWND hWnd, UINT message,
WPARAM wParam, LPARAM lParam);
BOOL CALLBACK DlgBoxProc(HWND hDlg, UINT message,
WPARAM wParam, LPARAM lParam);
BOOL CALLBACK ParDlgBoxProc(HWND hDlg, UINT message,
WPARAM wParam, LPARAM lParam);
void CALLBACK tprcShotTimerProc(HWND hWnd, UINT msg, UINT idTimer, DWORD dwTime);
void wmCreate(void);
void UnCreate(void);
void cmAbout(void);
int cmNewGame(void);
void DoPaint(void);
void DoPaintDlg(HWND hDlg, BOOL fDrawEntire);
//*******************************************************************
// WinMain - Carom main
//
// parameters:
// hInstance - The instance of this instance of this
// application.
// hPrevInstance - The instance of the previous instance
// of this application. This will be 0
// if this is the first instance.
// lpszCmdLine - A long pointer to the command line that
// started this application.
// cmdShow - Indicates how the window is to be shown
// initially. ie. SW_SHOWNORMAL, SW_HIDE,
// SW_MIMIMIZE.
//
// returns:
// wParam from last message.
//
//*******************************************************************
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpszCmdLine, int cmdShow)
{
MSG msg;
// Go init this application. Do not allow multiple instances...
if (hPrevInstance != 0) {
char msg[100], ttl[50];
LoadString(hPrevInstance, IDS_ONLYONE_TTL, ttl, 50);
LoadString(hPrevInstance, IDS_ONLYONE_MSG, msg, 100);
MessageBox(NULL, msg, ttl, MB_OK | MB_ICONEXCLAMATION);
return 0;
}
Init(hInstance, hPrevInstance, lpszCmdLine, cmdShow);
// Get and dispatch messages for this applicaton.
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
} // while
return(msg.wParam);
} // end of WinMain()
/* Re-computes which part of the client area contains the "inside" of the table,
that is, where balls can be located:
*/
void UpdateInsideTableRegion(void)
{
DeleteObject(my_inside_table_region);
my_inside_table_region = CreateRectRgn(
max(0, X_PHYTOCLI(LEFT) + 1),
max(0, Y_PHYTOCLI(TOP) + 1),
min(client_width, X_PHYTOCLI(RIGHT) - 1),
min(client_height, Y_PHYTOCLI(BOTTOM) - 1));
}
/* This function updates the radius in pixels according to the current magnification:
*/
void UpdateRadius(void)
{
int D, i;
HDC hDC;
RADIUS = floor(BALL_RADIUS * magnify + 0.5);
D = 2 * RADIUS + 1;
/* Update bitmaps associated with the balls.
Delete them, and then re-create them:
*/
if (hbmpTemp) {
DeleteObject(hbmpTemp);
}
if (hbmpBallMask) {
DeleteObject(hbmpBallMask);
}
if (hbmpClothBall) {
DeleteObject(hbmpClothBall);
}
for (i = 0; i < N; i++) {
if (hbmpBall[i]) {
DeleteObject(hbmpBall[i]);
}
}
hDC = GetDC(hwnd);
hbmpTemp = CreateCompatibleBitmap(hDC, D, D);
hbmpBallMask = CreateCompatibleBitmap(hDC, D, D);
hbmpClothBall = CreateCompatibleBitmap(hDC, D, D);
for (i = 0; i < N; i++) {
hbmpBall[i] = CreateCompatibleBitmap(hDC, D, D);
}
ReleaseDC(hwnd, hDC);
hDC = CreateCompatibleDC(NULL);
SelectObject(hDC, hbmpBallMask);
SelectObject(hDC, GetStockObject(WHITE_PEN));
SelectObject(hDC, GetStockObject(WHITE_BRUSH));
Rectangle(hDC, 0, 0, D, D);
SelectObject(hDC, GetStockObject(BLACK_PEN));
SelectObject(hDC, GetStockObject(BLACK_BRUSH));
Ellipse(hDC, 0, 0, D, D);
SelectObject(hDC, hbmpClothBall);
Rectangle(hDC, 0, 0, D, D);
for (i = 0; i < N; i++) {
SelectObject(hDC, hbmpBall[i]);
Rectangle(hDC, 0, 0, D, D);
}
SelectObject(hDC, hbmpClothBall);
SelectObject(hDC, my_cloth_pen);
SelectObject(hDC, my_cloth_brush);
Ellipse(hDC, 0, 0, D, D);
for (i = 0; i < N; i++) {
SelectObject(hDC, hbmpBall[i]);
SelectObject(hDC, my_ball_pen[i]);
SelectObject(hDC, my_ball_brush[i]);
Ellipse(hDC, 0, 0, D, D);
}
DeleteDC(hDC);
/* Re-calculate the spot locations:
*/
SPOT_X1 = X_PHYTOCLI(BOTTOMM_SPOT_X);
SPOT_X2 = X_PHYTOCLI(MIDDLE_SPOT_X);
SPOT_X3 = X_PHYTOCLI(TOP_SPOT_X);
SPOT_Y1 = Y_PHYTOCLI(BOTTOMR_SPOT_Y);
SPOT_Y2 = Y_PHYTOCLI(BOTTOMM_SPOT_Y);
SPOT_Y3 = Y_PHYTOCLI(BOTTOML_SPOT_Y);
}
/* The following functions set the viewing parameters.
*/
/* This function saves the current view-parameters into the view stack.
*/
void PushView(void)
{
view_stack[vs_next_free].ulx = upper_left.x;
view_stack[vs_next_free].uly = upper_left.y;
view_stack[vs_next_free].mgn = magnify;
vs_next_free = (vs_next_free + 1) % VIEW_STACK_SIZE;
if (view_stack_elements < VIEW_STACK_SIZE) {
view_stack_elements++;
}
}
/* Inverse of the previous function: Restores the last saved view from the view stack.
Returns TRUE if successful, FALSE if the view stack is empty.
*/
int PopView(void)
{
if (view_stack_elements == 0) {
return FALSE;
}
vs_next_free = (vs_next_free == 0 ? VIEW_STACK_SIZE - 1 : vs_next_free - 1);
upper_left.x = view_stack[vs_next_free].ulx;
upper_left.y = view_stack[vs_next_free].uly;
magnify = view_stack[vs_next_free].mgn;
view_stack_elements--;
UpdateRadius();
return TRUE;
}
void ComputeFullTableParams(void)
{
float magn_hor, magn_ver;
magn_hor = client_width / (TABLE_LENGTH + 2 * (WOOD_WIDTH + CUSHION_WIDTH));
magn_ver = client_height / (TABLE_WIDTH + 2 * (WOOD_WIDTH + CUSHION_WIDTH));
magnify = min(magn_hor, magn_ver);
upper_left.x = MIDDLE_SPOT_X - (client_width / 2.0) / magnify;
upper_left.y = MIDDLE_SPOT_Y + (client_height / 2.0) / magnify;
UpdateRadius();
}
void ComputeLeftTableParams(void)
{
magnify = client_height / TABLE_WIDTH;
upper_left.x = min(LEFT, MIDDLE_SPOT_X - (client_width / 2.0) / magnify);
upper_left.y = TOP;
UpdateRadius();
}
void ComputeRightTableParams(void)
{
magnify = client_height / TABLE_WIDTH;
upper_left.x = max(RIGHT - client_width / magnify,
MIDDLE_SPOT_X - (client_width / 2.0) / magnify);
upper_left.y = TOP;
UpdateRadius();
}
void ComputeZoomParams(void)
{
float magn_hor, magn_ver, new_magnify;
magn_hor = magnify * (client_width / fabs(zoom_lower_right.x - zoom_upper_left.x));
magn_ver = magnify * (client_height / fabs(zoom_lower_right.y - zoom_upper_left.y));
new_magnify = min(magn_hor, magn_ver);
new_magnify = min(new_magnify, MAX_MAGNIFY);
upper_left.x = X_CLITOPHY((zoom_upper_left.x + zoom_lower_right.x) / 2.0) -
(client_width / 2.0) / new_magnify;
upper_left.y = Y_CLITOPHY((zoom_upper_left.y + zoom_lower_right.y) / 2.0) +
(client_height / 2.0) / new_magnify;
magnify = new_magnify;
UpdateRadius();
}
void UpdateZoom(HWND hwnd, POINT FAR *pcurpos)
{
HDC hDC; /* Device context of main window */
HRGN hrgn; /* Handle of region to be created */
HPEN old_pen; /* Handle of old pen */
/* Get device context, only client area is needed:
*/
hDC = GetDC(hwnd);
SelectObject(hDC, my_client_region);
/* Clear previous rectangle if needed and draw the new one if needed.
Drawing mode: white dotted pen, XOR logic between background and pen colour.
*/
SetROP2(hDC, R2_XORPEN);
SetBkMode(hDC, TRANSPARENT);
old_pen = SelectObject(hDC, my_rectangle_pen);
SelectObject(hDC, GetStockObject(NULL_BRUSH));
/* Clear old rectangle:
*/
if (zoom_rectangle_present) {
Rectangle(hDC, zoom_upper_left.x, zoom_upper_left.y,
zoom_lower_right.x, zoom_lower_right.y);
}
/* Update lower right coordinates:
*/
zoom_lower_right.x = pcurpos->x;
zoom_lower_right.y = pcurpos->y;
/* Draw new rectangle:
*/
if (zoom_rectangle_new) {
Rectangle(hDC, zoom_upper_left.x, zoom_upper_left.y,
zoom_lower_right.x, zoom_lower_right.y);
zoom_rectangle_present = TRUE;
}
else {
zoom_rectangle_present = FALSE;
}
/* Deactivate the new pen:
*/
SelectObject(hDC, old_pen);
/* Release device context:
*/
ReleaseDC(hwnd, hDC);
}
/****************************************************************************
FUNCTION: MakeHelpPathName
PURPOSE: HelpEx assumes that the .HLP help file is in the same
directory as the HelpEx executable.This function derives
the full path name of the help file from the path of the
executable.
****************************************************************************/
void MakeHelpPathName(char* szFileName)
{
char *pcFileName;
int nFileNameLen;
nFileNameLen = GetModuleFileName(hInst, szFileName, EXE_NAME_MAX_SIZE);
pcFileName = szFileName + nFileNameLen;
while (pcFileName > szFileName) {
if (*pcFileName == '\\' || *pcFileName == ':') {
*(++pcFileName) = '\0';
break;
}
nFileNameLen--;
pcFileName--;
}
if ((nFileNameLen + 13) < EXE_NAME_MAX_SIZE) {
char hfn[20];
LoadString(hInst, IDS_HELPFILE, hfn, 20);
lstrcat(szFileName, hfn);
}
else {
lstrcat(szFileName, "?");
}
return;
}
/* The following functions are used to update the scoreboard and the
game status:
*/
void UpdateNames(void)
{
char buf[100];
sprintf(buf, "%s : %s", player1_name, player2_name);
SetDlgItemText(hParDlg, DL_NAMES, buf);
}
void UpdateScore(void)
{
char buf[20];
sprintf(buf, "%d : %d", score[0], score[1]);
SetDlgItemText(hParDlg, DL_SCORE, buf);
}
void UpdateGameType(void)
{
char buf[100];
LoadString(hInst, GameStyle == DL_NG_GAME0 ? IDS_GAME0_NAME :
GameStyle == DL_NG_GAME1 ? IDS_GAME1_NAME :
IDS_GAME3_NAME,
buf, 100);
SetDlgItemText(hParDlg, DL_TYPE, buf);
}
void UpdateWinningScore(void)
{
char buf[30];
LoadString(hInst, IDS_WIN_SCORE, buf, 20);
sprintf(buf + strlen(buf), " %d", WinningScore);
SetDlgItemText(hParDlg, DL_WIN_SCORE, buf);
}
/* Callback function of initialization of trigonometric functions.
The code parameter is
positive if this is the first call: number of intermediate calls;
0 if this is an intermediate call; and
-1 if this is the very last call of this function.
*/
void DrawBar(int code)
{
static RECT box;
static int status;
char buf[60];
HDC hDC;
DWORD extent;
RECT lr;
switch(code) {
case 0:
/* Intermediate call.
*/
hDC = GetDC(hwnd);
SelectObject(hDC, GetStockObject(NULL_PEN));
SelectObject(hDC, GetStockObject(LTGRAY_BRUSH));
lr.left = box.left + ++status;
lr.right = lr.left + 2;
lr.top = box.top + 1;
lr.bottom = box.bottom;
Rectangle(hDC, lr.left, lr.top, lr.right, lr.bottom);
ReleaseDC(hwnd, hDC);
break;
case -1:
/* Last call.
*/
box.left = box.top = 0;
box.right = client_width;
box.bottom = client_height;
InvalidateRect(hwnd, &box, FALSE);
ReleaseCapture();
break;
default:
/* First call. Draw the border of the box and write text:
*/
SetCapture(hwnd);
SetCursor(LoadCursor(NULL, IDC_WAIT));
hDC = GetDC(hwnd);
LoadString(hInst, IDS_PLEASEWAIT, buf, 60);
extent = GetTextExtent(hDC, buf, strlen(buf));
box.left = (client_width - code - 2) / 2;
box.right = box.left + code + 2;
box.top = (client_height - 16 - 2 - HIWORD(extent)) / 3 +
HIWORD(extent) + 2;
box.bottom = box.top + 16;
SetTextColor(hDC, RGB(255, 255, 255));
SetBkMode(hDC, TRANSPARENT);
TextOut(hDC, (client_width - LOWORD(extent)) / 2,
(client_height - 16 - 2 - HIWORD(extent)) / 3,
buf, strlen(buf));
SelectObject(hDC, GetStockObject(BLACK_PEN));
SelectObject(hDC, GetStockObject(WHITE_BRUSH));
Rectangle(hDC, box.left, box.top, box.right, box.bottom);
ReleaseDC(hwnd, hDC);
status = 0;
break;
}
}
//*******************************************************************
// Init - init the Carom Biliard application
//
// parameters:
// hInstance - The instance of this instance of this
// application.
// hPrevInstance - The instance of the previous instance
// of this application. This will be 0
// if this is the first instance.
// lpszCmdLine - A long pointer to the command line that
// started this application.
// cmdShow - Indicates how the window is to be shown
// initially. ie. SW_SHOWNORMAL, SW_HIDE,
// SW_MIMIMIZE.
//
//*******************************************************************
#pragma argsused
void Init(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpszCmdLine, int cmdShow)
{
WNDCLASS wcCaromClass, wcShotClass;
char title[TITLE_LENGTH+1];
RECT wrect;
hInst = hInstance; // save for use by window procs
// Get string from resource with application name.
LoadString(hInstance, IDS_NAME, (LPSTR) SetUpData.szAppName, 20);
// Define the window class for this application.
wcCaromClass.style = CS_HREDRAW | CS_VREDRAW;
wcCaromClass.lpfnWndProc = WndProc;
wcCaromClass.cbClsExtra = 0;
wcCaromClass.cbWndExtra = 0;
wcCaromClass.hInstance = hInstance;
wcCaromClass.hIcon = LoadIcon(hInstance, SetUpData.szAppName);
wcCaromClass.hCursor = LoadCursor(hInst, "Carom");
wcCaromClass.hbrBackground = (HBRUSH)GetStockObject(GRAY_BRUSH);
wcCaromClass.lpszClassName = SetUpData.szAppName;
wcCaromClass.lpszMenuName = SetUpData.szAppName;
// Register the class
RegisterClass(&wcCaromClass);
// fetch in the window title from the stringtable resource
LoadString(hInstance, IDS_TITLE, (LPSTR)title, TITLE_LENGTH);
// Create applications main window.
hwnd = CreateWindow(SetUpData.szAppName, // window class name
(LPSTR)title, // window title
WS_OVERLAPPEDWINDOW, // window style
10, // x
16, // y
600, // cx
480, // cy
NULL, // no parent for this window
NULL, // use the class menu
hInstance, // who created this window
NULL // no parms to pass on
);
// Get string from resource with application name.
LoadString(hInstance, IDS_PARWINNAME, (LPSTR) SetUpData.szParWinName, 20);
// Create a thunk for the parameters dialog box proc function
lpParDlgProc = MakeProcInstance((FARPROC)ParDlgBoxProc, hInst);
hParDlg = CreateDialog(hInst, SetUpData.szParWinName /*"Parameters"*/, hwnd, lpParDlgProc);
GetClientRect(hwnd, &wrect);
client_width = wrect.right;
client_height = wrect.bottom - DL_HEIGHT * 2 + 1;
/* We are going to start with the view of the full table.
The parameters of this view will be computed with this:
*/
ComputeFullTableParams();
/* Create region that exactly covers the free area of the main window,
and a region that contains the inside of the table.
*/
my_client_region = CreateRectRgn(0, 0, client_width, client_height);
my_inside_table_region = CreateRectRgn(
max(0, X_PHYTOCLI(LEFT) + 1),
max(0, Y_PHYTOCLI(TOP) + 1),
min(client_width, X_PHYTOCLI(RIGHT) - 1),
min(client_height, Y_PHYTOCLI(BOTTOM) - 1));
sight_line_end.x = RIGHT - BALL_RADIUS;
sight_line_end.y = WOOD_WIDTH + CUSHION_WIDTH + (TABLE_WIDTH / 2.0);
cueball = 0; /* First player to play */
GameState = BEFORE_GAME;
MoveWindow(hParDlg, -1, client_height,
client_width + 2, DL_HEIGHT * 2, TRUE);
/* Set up initial state of scroll bars */
InitScrollBar(hParDlg, DL_HOR_SCROLL, DL_HOR_EDIT,
SPIN_MIN, SPIN_MAX, horizontal_spin);
InitScrollBar(hParDlg, DL_VER_SCROLL, DL_VER_EDIT,
SPIN_MIN, SPIN_MAX, vertical_spin);
InitScrollBar(hParDlg, DL_POWER_SCROLL, DL_POWER_EDIT,
POWER_MIN, POWER_MAX, shot_power);
/* Load default name of players:
*/
LoadString(hInst, IDS_PLAYER1_NAME, player1_name, sizeof(player1_name));
LoadString(hInst, IDS_PLAYER2_NAME, player2_name, sizeof(player2_name));
/* Set up initial state of scoreboard and game status:
*/
UpdateNames();
UpdateScore();
UpdateGameType();
UpdateWinningScore();
InvalidateRect(hwnd, &wrect, TRUE);
ShowWindow(hwnd, SW_SHOWMAXIMIZED);
UpdateWindow(hwnd);
/* Check the appropriate items in the menu:
*/
CheckMenuItem(GetMenu(hwnd),
draw_path, MF_BYCOMMAND | MF_CHECKED);
EnableMenuItem(GetMenu(hwnd),
CM_UNDO_SHOT, MF_BYCOMMAND | MF_GRAYED);
if (sight_extra_enabled[1]) {
CheckMenuItem(GetMenu(hwnd), CM_BROKEN_LINE, MF_BYCOMMAND | MF_CHECKED);
}
/* Init random number generator:
*/
randomize();
/* Init Help:
*/
MakeHelpPathName(szHelpFileName);
/* Create prolog address for timer procedure used for shot animation:
*/
lpShotTimerProc = MakeProcInstance((FARPROC) tprcShotTimerProc, hInst);
/* Init sine, cosine and arctan tables:
*/
init_trigo(DrawBar);
/* Start with dialog box so that the users type in their name,
the required game type, winning score and so on.
*/
PostMessage(hwnd, WM_COMMAND, CM_NEW_GAME, NULL);
} // end of Init()
//*******************************************************************
// InitScrollBar - Initializes one scroll bar and its edit box with
// a range and a starting value
// parameters:
// hDlg - The dialog handle (in which dlg is the SB)
// scroll - The identifier (not handle) of the SB
// edit - The identifier (not handle) of the edit box
// mi - The minimum value of the scroll bar
// ma - The maximum value of the scroll bar
// value - The starting value of the scroll bar
//
// returns:
// nothing
//
//*******************************************************************
void InitScrollBar(HWND hDlg, WORD scroll, WORD edit,
int mi, int ma, int value)
{
WORD hwndscr;
SetScrollRange(hwndscr = GetDlgItem(hDlg, scroll), SB_CTL, mi, ma, FALSE);
SetScrollPos(hwndscr, SB_CTL, value, TRUE);
SetDlgItemInt(hDlg, edit, value, TRUE);
}
void FindEndPoint(FPOINT *start, FPOINT *on, FPOINT *end)
{
int end_point_found = FALSE;
FPOINT cueball;
FPOINT phycurpos;
FPOINT cross;
cueball.x = start->x;
cueball.y = start->y;
phycurpos.x = on->x;
phycurpos.y = on->y;
/* Test bottom cushion
*/
if (phycurpos.y < cueball.y) {
cross.x = cueball.x + (cueball.y - C_BOTTOM) *
(phycurpos.x - cueball.x) / (cueball.y - phycurpos.y);
if (cross.x >= C_LEFT && cross.x <= C_RIGHT) {
cross.y = C_BOTTOM;
end_point_found = TRUE;
}
}
/* Test top cushion
*/
if (!end_point_found && phycurpos.y > cueball.y) {
cross.x = cueball.x + (C_TOP - cueball.y) *
(phycurpos.x - cueball.x) / (phycurpos.y - cueball.y);
if (cross.x >= C_LEFT && cross.x <= C_RIGHT) {
cross.y = C_TOP;
end_point_found = TRUE;
}
}
/* Test for left hand border
*/
if (!end_point_found && phycurpos.x < cueball.x) {
cross.y = cueball.y + (cueball.x - C_LEFT) *
(phycurpos.y - cueball.y) / (cueball.x - phycurpos.x);
if (cross.y >= C_BOTTOM && cross.y <= C_TOP) {
cross.x = C_LEFT;
end_point_found = TRUE;
}
}
/* Test for right hand border
*/
if (!end_point_found && phycurpos.x > cueball.x) {
cross.y = cueball.y + (C_RIGHT - cueball.x) *
(phycurpos.y - cueball.y) / (phycurpos.x - cueball.x);
if (cross.y >= C_BOTTOM && cross.y <= C_TOP) {
cross.x = C_RIGHT;
end_point_found = TRUE;
}
}
if (!end_point_found) {
/* Nearly impossible branch... Set horizontal direction:
*/
cross.x = C_RIGHT;
cross.y = C_TOP;
}
end->x = cross.x;
end->y = cross.y;
}
void DrawSightLines(HDC hDC, FPOINT *extra, int *drawn)
{
int i;
SetROP2(hDC, R2_XORPEN);
SetBkMode(hDC, TRANSPARENT);
SelectObject(hDC, my_sight_line_pen);
MoveTo(hDC, X_PHYTOCLI(ball[cueball].x), Y_PHYTOCLI(ball[cueball].y));
LineTo(hDC, X_PHYTOCLI(sight_line_end.x), Y_PHYTOCLI(sight_line_end.y));
for (i = 0; i < 2; i++) {
if (drawn[i]) {
SelectObject(hDC, i == 0 ? my_sight_line_extra_pen : my_sight_line_pen);
MoveTo(hDC, X_PHYTOCLI(extra[2 * i].x),
Y_PHYTOCLI(extra[2 * i].y));
LineTo(hDC, X_PHYTOCLI(extra[2 * i + 1].x),
Y_PHYTOCLI(extra[2 * i + 1].y));
}
}
}
void UpdateSightLine(HWND hwnd, POINT *pcurpos, int erase_old)
{
FPOINT phycurpos; /* Position of cursor */
FPOINT cross; /* Position of new end-of-line */
HDC hDC; /* Device context of main window */
HRGN hrgn; /* Handle of region to be created */
FPOINT temp;
int j;
/* Make local copy of cursor position (client coords). Translate it to physical
coordinates:
*/
phycurpos.x = X_CLITOPHY(pcurpos->x);
phycurpos.y = Y_CLITOPHY(pcurpos->y);
/* Avoid the case that the cursor points exactly to the centre of the cue ball:
*/
if (phycurpos.x == ball[cueball].x && phycurpos.y == ball[cueball].y) {
phycurpos.x += 1.0;
}
/* Find the place where the sight-line crosses the window frame:
*/
temp.x = ball[cueball].x;
temp.y = ball[cueball].y;
FindEndPoint(&temp, &phycurpos, &cross);
/* Check first collision:
*/
{
int i = cueball, j, jj;
float r = ball[i].r;
float a, ca;
float ox = ball[i].x;
float oy = ball[i].y;
float cosa;
float sina;
float t, t1;
float dx, dy, q, nx, ny, discr;
a = atan2(cross.y - oy, cross.x - ox);
if (a < 0) a += 2 * M_PI;
sina = sin(a);
cosa = cos(a);
memcpy(old_sight_extra, sight_extra, sizeof(old_sight_extra));
sight_extra_present = FALSE;
/* Check collision with other ball
*/
for (t = RIGHT - LEFT, jj = -1, j = 0; j < N; j++) {
if (j != i) {
dx = ball[j].x - ox;
dy = ball[j].y - oy;
q = dx * cosa + dy * sina;
discr = (q * q) - ((dx * dx) + (dy * dy) -
(r + ball[j].r) * (r + ball[j].r));
if (discr > 0 && (t1 = q - sqrt(discr)) > 0 && t1 < t) {
t = t1;
jj = j;
}
}
}
if (jj >= 0) {
j = jj;
/* Compute exact place of collision (nx,ny). This position, however,
may be outside of the valid area:
*/
nx = ox + (t * cosa);
ny = oy + (t * sina);
if (nx - r >= LEFT && nx + r <= RIGHT &&
ny - r >= BOTTOM && ny + r <= TOP) {
/* Place of collision is inside:
Yes, there is a collision:
*/
/* Distances:
*/
dx = ball[j].x - nx;
dy = ball[j].y - ny;
/* Angle of collision line:
*/
ca = atan2(dy, dx);
sight_extra[0][0].x = nx;
sight_extra[0][0].y = ny;
if (sight_extra[0][0].x - r >= LEFT &&
sight_extra[0][0].x + r <= RIGHT &&
sight_extra[0][0].y - r >= BOTTOM &&
sight_extra[0][0].y + r <= TOP) {
sight_extra[0][1].x = cross.x;
sight_extra[0][1].y = cross.y;
}
else {
sight_extra[0][0].x =
sight_extra[0][0].y =
sight_extra[0][1].x =
sight_extra[0][1].y = 0;
}
ca -= M_PI_2;
if (!(fabs(ca - a) < M_PI_2 ||
(fabs(ca - a) > M_PI && fabs(fabs(ca - a) - 2 * M_PI) < M_PI_2))) {
ca += M_PI;
}
sight_extra[1][0].x = nx;
sight_extra[1][0].y = ny;
temp.x = nx + 10 * r * cos(ca);
temp.y = ny + 10 * r * sin(ca);
FindEndPoint(sight_extra[1] + 0, &temp, sight_extra[1] + 1);
sight_extra_present = TRUE;
if (sight_extra_enabled[0]) {
cross.x = nx;
cross.y = ny;
}
}
}
}
/* Get device context, only client area is needed:
*/
hDC = GetDC(hwnd);
SelectObject(hDC, my_client_region);
/* Clear previous sight-line and draw the new one.
*/
if (erase_old) {
DrawSightLines(hDC, (FPOINT *) old_sight_extra, sight_extra_drawn);
}
for (j = 0; j < 2; j ++) {
sight_extra_drawn[j] = sight_extra_enabled[j] && sight_extra_present;
}
sight_line_end.x = cross.x;
sight_line_end.y = cross.y;
DrawSightLines(hDC, (FPOINT *) sight_extra, sight_extra_drawn);
/* Release device context:
*/
ReleaseDC(hwnd, hDC);
}
//*******************************************************************
// WndProc - handles messages for this application
//
// parameters:
// hWnd - The window handle for this message
// message - The message number
// wParam - The WPARAM parameter for this message
// lParam - The LPARAM parameter for this message
//
// returns:
// depends on message.
//
//*******************************************************************
LRESULT CALLBACK WndProc(HWND hWnd, UINT message,
WPARAM wParam, LPARAM lParam)
{
RECT wrect;
static int first_size_message = TRUE;
switch (message) {
case WM_CREATE:
wmCreate();
break;
case WM_SIZE:
client_width = LOWORD(lParam);
client_height = HIWORD(lParam) - DL_HEIGHT * 2 + 1;
if (first_size_message) {
first_size_message = FALSE;
ComputeFullTableParams();
}
DeleteObject(my_client_region);
my_client_region = CreateRectRgn(0, 0, client_width, client_height);
UpdateInsideTableRegion();
if (IsWindow(hParDlg)) {
MoveWindow(hParDlg,
-1, client_height,
client_width + 2, DL_HEIGHT * 2, TRUE);
}
break;
case WM_LBUTTONDOWN:
if (GameState == SETTING_SHOT) {
/* Muose button pressed inside of the table-image area.
Start moving the destination point - changing the sight_line_end
global variable.
*/
sight_line_being_set = TRUE;
old_capture_sight = SetCapture(hWnd);
old_cursor_sight = SetCursor(LoadCursor(hInst, "Set_Dir"));
UpdateSightLine(hWnd, &MAKEPOINT(lParam), TRUE);
}
else if (GameState == ZOOMING_IN) {
/* User pressed the mouse button in order to start selecting the area
into which she wants to zoom in.
*/
zoom_being_set = TRUE;
zoom_upper_left.x = MAKEPOINT(lParam).x;
zoom_upper_left.y = MAKEPOINT(lParam).y;
zoom_rectangle_present = FALSE;
zoom_rectangle_new = TRUE;
}
break;
case WM_MOUSEMOVE:
if (sight_line_being_set) {
UpdateSightLine(hWnd, &MAKEPOINT(lParam), TRUE);
}
else if (zoom_being_set) {
UpdateZoom(hWnd, &MAKEPOINT(lParam));
}
break;
case WM_LBUTTONUP:
if (sight_line_being_set) {
if (old_capture_sight) {
SetCapture(old_capture_sight);
}
else {
ReleaseCapture();
}
SetCursor(old_cursor_sight);
sight_line_being_set = FALSE;
}
else if (zoom_being_set) {
zoom_rectangle_new = FALSE;
UpdateZoom(hWnd, &MAKEPOINT(lParam)); /* Be nice, clear rectangle */
if (old_capture_zoom) {
SetCapture(old_capture_zoom);
}
else {
ReleaseCapture();
}
SetCursor(old_cursor_zoom);
zoom_being_set = FALSE;
GameState = SETTING_SHOT;
if (abs(zoom_lower_right.x - zoom_upper_left.x) > 2 &&
abs(zoom_lower_right.y - zoom_upper_left.y) > 2 &&
max(zoom_upper_left.x, zoom_lower_right.x) >= X_PHYTOCLI(LEFT) &&
min(zoom_upper_left.x, zoom_lower_right.x) <= X_PHYTOCLI(RIGHT) &&
min(zoom_upper_left.y, zoom_lower_right.y) <= Y_PHYTOCLI(BOTTOM) &&
max(zoom_upper_left.y, zoom_lower_right.y) >= Y_PHYTOCLI(TOP)) {
RECT wrect;
PushView();
ComputeZoomParams();
wrect.left = wrect.top = 0;
wrect.right = client_width;
wrect.bottom = client_height;
InvalidateRect(hWnd, &wrect, TRUE);
UpdateInsideTableRegion();
}
}
break;
case WM_COMMAND:
switch (wParam) {
case CM_ABOUT:
cmAbout();
break;
case CM_EXIT:
// Tell windows to destroy our window.
DestroyWindow(hWnd);
break;
case CM_PATH_NONE:
case CM_PATH_LAST:
case CM_PATH_ALL:
CheckMenuItem(GetMenu(hwnd),
draw_path, MF_BYCOMMAND | MF_UNCHECKED);
draw_path = wParam;
CheckMenuItem(GetMenu(hwnd),
wParam, MF_BYCOMMAND | MF_CHECKED);
if (draw_path == CM_PATH_NONE) {
RECT wrect;
wrect.left = wrect.top = 0;
wrect.right = client_width;
wrect.bottom = client_height;
InvalidateRect(hwnd, &wrect, TRUE);
}
break;
case CM_BROKEN_LINE: {
POINT p;
sight_extra_enabled[0] ^= 1;
sight_extra_enabled[1] ^= 1;
CheckMenuItem(GetMenu(hwnd), CM_BROKEN_LINE,
MF_BYCOMMAND | sight_extra_enabled[1] ?
MF_CHECKED : MF_UNCHECKED);
p.x = X_PHYTOCLI(sight_line_end.x);
p.y = Y_PHYTOCLI(sight_line_end.y);
UpdateSightLine(hwnd, &p, TRUE);
break;
}
case CM_NEW_GAME:
/* Replace balls, Reset point counters,
gray undo menu item.
*/
if (GameState != BALLS_MOVING) {
if (GameState != BEFORE_GAME &&
GameState != AFTER_GAME) {
char ttl[40], msg[200];
LoadString(hInst, IDS_IN_GAME_TTL, ttl, 40);
LoadString(hInst, IDS_IN_GAME_MSG, msg, 200);
MessageBeep(MB_ICONINFORMATION);
MessageBox(hwnd, msg, ttl, MB_ICONINFORMATION | MB_OK);
}
if (cmNewGame()) {
POINT p;
/* Replace balls into starting position:
*/
ball[cueball].x = BOTTOMR_SPOT_X;
ball[cueball].y = BOTTOMR_SPOT_Y;
ball[1-cueball].x = BOTTOMM_SPOT_X;
ball[1-cueball].y = BOTTOMM_SPOT_Y;
ball[2].x = TOP_SPOT_X;
ball[2].y = TOP_SPOT_Y;
/* Reset point counters:
*/
score[0] = score[1] = 0;
/* Indicate that this will be the first shot:
*/
first_shot = TRUE;
/* Disable undo menu item:
*/
EnableMenuItem(GetMenu(hwnd),
CM_UNDO_SHOT,
MF_BYCOMMAND | MF_GRAYED);
EnableMenuItem(GetMenu(hwnd),
CM_BROKEN_LINE,
MF_BYCOMMAND | EnableBreak ? MF_ENABLED : MF_GRAYED);
if (!EnableBreak) {
sight_extra_enabled[0] = sight_extra_enabled[1] = FALSE;
CheckMenuItem(GetMenu(hwnd), CM_BROKEN_LINE,
MF_BYCOMMAND | MF_UNCHECKED);
}
InvalidateRgn(hwnd, my_inside_table_region, TRUE);
/* Update scoreboard and game status:
*/
UpdateNames();
UpdateScore();
UpdateGameType();
UpdateWinningScore();
/* Store the id of the player who begins the game:
*/
WhoStarted = cueball;
/* Change game state to "playing":
*/
GameState = SETTING_SHOT;
/* Redraw cue ball colour:
*/
p.x = X_PHYTOCLI(sight_line_end.x);
p.y = Y_PHYTOCLI(sight_line_end.y);
UpdateSightLine(hwnd, &p, TRUE);
PostMessage(hParDlg, WM_COMMAND, DL_NO_SPIN, NULL);
}
}
break;
case CM_UNDO_SHOT:
/* User wants to undo the last shot. The balls may be moving when
this command arrives!
If shot timer is still active, remove timer!
*/
if (globalidTimer1) {
KillTimer(NULL, globalidTimer1);
KillTimer(NULL, globalidTimer2);
globalidTimer1 = globalidTimer2 = 0;
}
GameState = SETTING_SHOT;
memcpy(ball, ball_save, sizeof(ball_save));
memcpy(&sight_line_end, &sight_line_end_save, sizeof(sight_line_end));
memcpy(sight_extra, sight_extra_save, sizeof(sight_extra));
memcpy(sight_extra_drawn, sight_extra_drawn_save, sizeof(sight_extra_drawn));
cueball = cueball_save;
horizontal_spin = horizontal_spin_save;
vertical_spin = vertical_spin_save;
shot_power = shot_power_save;
memcpy(score, score_save, sizeof(score));
first_shot = first_shot_save;
InvalidateRgn(hwnd, my_inside_table_region, TRUE);
SetDlgItemInt(hParDlg, DL_HOR_EDIT, horizontal_spin, TRUE);
SetDlgItemInt(hParDlg, DL_VER_EDIT, -vertical_spin, TRUE);
SetDlgItemInt(hParDlg, DL_POWER_EDIT, shot_power, TRUE);
SetScrollPos(GetDlgItem(hParDlg, DL_HOR_SCROLL), SB_CTL,
horizontal_spin, TRUE);
SetScrollPos(GetDlgItem(hParDlg, DL_VER_SCROLL), SB_CTL,
vertical_spin, TRUE);
DoPaintDlg(hParDlg, TRUE);
UpdateScore();
if (EnableBreak) {
EnableMenuItem(GetMenu(hwnd), CM_BROKEN_LINE,
MF_BYCOMMAND | MF_ENABLED);
}
EnableMenuItem(GetMenu(hwnd), DL_ZOOM_IN,
MF_BYCOMMAND | MF_ENABLED);
EnableMenuItem(GetMenu(hwnd), DL_TAKE_SHOT,
MF_BYCOMMAND | MF_ENABLED);
EnableMenuItem(GetMenu(hwnd), CM_NEW_GAME,
MF_BYCOMMAND | MF_ENABLED);
break;
case DL_VIEW_LEFT:
case DL_VIEW_FULL:
case DL_VIEW_RIGHT:
case DL_ZOOM_IN:
case DL_ZOOM_OUT:
case DL_TAKE_SHOT:
ParDlgBoxProc(hParDlg, message, wParam, lParam);
break;
case CM_HELP_CONTENTS:
WinHelp(hWnd, szHelpFileName, HELP_INDEX, 0L);
break;
default:
break;
} // switch wParam
break;
case WM_PAINT:
DoPaint();
break;
case WM_GETMINMAXINFO:
// Limit the minimum size of the window to 300x250
((POINT far *)lParam)[3].x = 300;
((POINT far *)lParam)[3].y = 250;
break;
case WM_DESTROY:
// This is the end if we were closed by a DestroyWindow call.
/* Clean up:
*/
UnCreate();
PostQuitMessage(0);
break;
default:
return(DefWindowProc(hWnd, message, wParam, lParam));
} // switch message
return(0L);
} // end of WndProc()
//*******************************************************************
// DlgBoxProc - handle dialog messages
//
// parameters:
// hDlg - The window handle for this message
// message - The message number
// wParam - The WPARAM parameter for this message
// lParam - The LPARAM parameter for this message
//
//*******************************************************************
#pragma argsused
BOOL CALLBACK DlgBoxProc(HWND hDlg, UINT message,
WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_COMMAND:
// regardless of the command (only ID_OK should arrive)
// we want to exit the dialog
EndDialog(hDlg, TRUE);
break;
default:
return FALSE;
} // switch
return(TRUE);
} // end of DlgBoxProc()
//*******************************************************************
// NewGameDlgBoxProc - handle dialog messages
//
// parameters:
// hDlg - The window handle for this message
// message - The message number
// wParam - The WPARAM parameter for this message
// lParam - The LPARAM parameter for this message
//
//*******************************************************************
#pragma argsused
BOOL CALLBACK NewGameDlgBoxProc(HWND hDlg, UINT message,
WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_INITDIALOG:
/* Set starting values:
*/
SetDlgItemText(hDlg, DL_NG_NAME1, player1_name);
SetDlgItemText(hDlg, DL_NG_NAME2, player2_name);
CheckRadioButton(hDlg, DL_NG_GAME0, DL_NG_GAME3, GameStyle);
SetDlgItemInt(hDlg, DL_NG_WIN_SCORE, WinningScore, TRUE);
CheckDlgButton(hDlg, DL_NG_CUSHION_P, CushionPoints);
CheckRadioButton(hDlg, DL_NG_START1, DL_NG_STARTR, WhoStarts);
CheckDlgButton(hDlg, DL_NG_UNDO, EnableUndo);
CheckDlgButton(hDlg, DL_NG_BREAK, EnableBreak);
break;
case WM_COMMAND:
switch (wParam) {
case DL_NG_CANCEL:
/* Exit dialog with no modifications
*/
EndDialog(hDlg, wParam);
break;
case DL_NG_OK: {
char name1[40], name2[40];
int ws, trans;
char ttl[40], msg[100];
/* User wants to exit the dialog with the
parameters that are currently set.
We check the validity of these parameters:
*/
GetDlgItemText(hDlg, DL_NG_NAME1, name1, 40);
GetDlgItemText(hDlg, DL_NG_NAME2, name2, 40);
if (strlen(name1) == 0 || strlen(name2) == 0) {
LoadString(hInst, IDS_EMPTY_NAME_TTL, ttl, 40);
LoadString(hInst, IDS_EMPTY_NAME_MSG, msg, 100);
MessageBox(hDlg, msg, ttl, MB_OK | MB_ICONEXCLAMATION);
break;
}
ws = GetDlgItemInt(hDlg, DL_NG_WIN_SCORE, &trans, TRUE);
if (ws < 5 || ws > 1000) {
LoadString(hInst, IDS_BAD_WS_TTL, ttl, 40);
LoadString(hInst, IDS_BAD_WS_MSG, msg, 100);
MessageBox(hDlg, msg, ttl, MB_OK | MB_ICONEXCLAMATION);
break;
}
/* Everything is OK, copy parameters back to global
variables:
*/
strcpy(player1_name, name1);
strcpy(player2_name, name2);
WinningScore = ws;
CushionPoints = IsDlgButtonChecked(hDlg, DL_NG_CUSHION_P);
if (IsDlgButtonChecked(hDlg, DL_NG_GAME0)) {
GameStyle = DL_NG_GAME0;
}
else if (IsDlgButtonChecked(hDlg, DL_NG_GAME1)) {
GameStyle = DL_NG_GAME1;
}
else {
GameStyle = DL_NG_GAME3;
}
if (IsDlgButtonChecked(hDlg, DL_NG_START1)) {
WhoStarts = DL_NG_START1;
cueball = 0;
}
else if (IsDlgButtonChecked(hDlg, DL_NG_START2)) {
WhoStarts = DL_NG_START2;
cueball = 1;
}
else {
WhoStarts = DL_NG_STARTR;
cueball = random(2);
}
EnableUndo = IsDlgButtonChecked(hDlg, DL_NG_UNDO);
EnableBreak = IsDlgButtonChecked(hDlg, DL_NG_BREAK);
/* End this dialog box:
*/
EndDialog(hDlg, wParam);
break;
}
}
default:
return FALSE;
} // switch
return(TRUE);
} // end of DlgBoxProc()
void DoPaintDlgAction(HDC hDC, BOOL fDrawEntire)
{
if (fDrawEntire) {
SelectObject(hDC, GetStockObject(BLACK_PEN));
SelectObject(hDC, GetStockObject(NULL_BRUSH));
Rectangle(hDC, 0, 0, 52, 52);
SelectObject(hDC, GetStockObject(WHITE_BRUSH));
Ellipse(hDC, 8, 8, 45, 45);
}
else {
SelectObject(hDC, GetStockObject(WHITE_PEN));
MoveTo(hDC, 26 - 3 + prev_horizontal_spin, 26 + prev_vertical_spin);
LineTo(hDC, 26 + 4 + prev_horizontal_spin, 26 + prev_vertical_spin);
MoveTo(hDC, 26 + prev_horizontal_spin, 26 - 3 + prev_vertical_spin);
LineTo(hDC, 26 + prev_horizontal_spin, 26 + 4 + prev_vertical_spin);
SelectObject(hDC, GetStockObject(BLACK_PEN));
}
MoveTo(hDC, 26 - 3 + horizontal_spin, 26 + vertical_spin);
LineTo(hDC, 26 + 4 + horizontal_spin, 26 + vertical_spin);
MoveTo(hDC, 26 + horizontal_spin, 26 - 3 + vertical_spin);
LineTo(hDC, 26 + horizontal_spin, 26 + 4 + vertical_spin);
prev_horizontal_spin = horizontal_spin;
prev_vertical_spin = vertical_spin;
}
void DoPaintDlg(HWND hDlg, BOOL fDrawEntire)
{
HWND hwndch = GetDlgItem(hDlg, DL_BALL_IMAGE);
HDC hDC;
hDC = GetWindowDC(hwndch);
DoPaintDlgAction(hDC, fDrawEntire);
ReleaseDC(hwndch, hDC);
}
void UpdateSpin(HWND hDlg, HWND hwndch)
{
POINT curpos; /* Position of cursor */
RECT wrect; /* Position of ball-image window */
int new_hor_spin; /* temporary storage for new spin values */
int new_ver_spin; /* -||- */
GetCursorPos(&curpos);
GetWindowRect(hwndch, &wrect);
new_hor_spin = clamp(curpos.x - (wrect.left + 26), SPIN_MIN, SPIN_MAX);
new_ver_spin = clamp(curpos.y - (wrect.top + 26), SPIN_MIN, SPIN_MAX);
SetDlgItemInt(hDlg, DL_HOR_EDIT, new_hor_spin, TRUE);
SetDlgItemInt(hDlg, DL_VER_EDIT, - new_ver_spin, TRUE);
DoPaintDlg(hDlg, FALSE);
}
//*******************************************************************
// ParDlgBoxProc - handle shot parameter window messages
//
// parameters:
// hDlg - The window handle for this message
// message - The message number
// wParam - The WPARAM parameter for this message
// lParam - The LPARAM parameter for this message
//
//*******************************************************************
#pragma argsused
BOOL CALLBACK ParDlgBoxProc(HWND hDlg, UINT message,
WPARAM wParam, LPARAM lParam)
{
RECT wrect;
int x, y, w, h;
switch (message)
{
case WM_DRAWITEM: {
/* This message is processed to redraw the ball-image in the
bottom left corner. This message arrivers whenever the
button assigned to that area needs redrawing. It is like
a WM_PAINT message...
*/
DRAWITEMSTRUCT FAR *dis = (DRAWITEMSTRUCT FAR*) lParam;
switch(dis->itemAction) {
case ODA_DRAWENTIRE:
if (dis->CtlID == DL_BALL_IMAGE) {
DoPaintDlgAction(dis->hDC, TRUE);
}
break;
case ODA_SELECT:
if (dis->itemState & ODS_SELECTED) {
HWND hwndch;
hwndch = GetDlgItem(hDlg, DL_BALL_IMAGE);
/* Muose button pressed inside of the ball-image area.
Start moving the hit point - changing the horizontal_spin
and vertical_spin global variables.
*/
spin_being_set = TRUE;
old_capture_ball = SetCapture(hDlg);
old_cursor_ball = SetCursor(LoadCursor(hInst, "Set_Spin"));
UpdateSpin(hDlg, hwndch);
}
break;
}
return TRUE;
}
case WM_MOUSEMOVE:
if (spin_being_set) {
UpdateSpin(hDlg, GetDlgItem(hDlg, DL_BALL_IMAGE));
}
break;
case WM_LBUTTONUP:
if (spin_being_set) {
if (old_capture_ball) {
SetCapture(old_capture_ball);
}
else {
ReleaseCapture();
}
SetCursor(old_cursor_ball);
spin_being_set = FALSE;
PostMessage(GetDlgItem(hDlg, DL_BALL_IMAGE),
message, wParam, lParam);
}
break;
case WM_COMMAND:
/* The buttons of the dialog box send this message to their
parents when they are activated (pressed and released).
The wParam contains the control identifier of the button
that sent this message.
*/
switch (wParam) {
case DL_ZOOM_IN:
if (GameState == SETTING_SHOT) {
/* User wants to zoom in a visible area.
*/
GameState = ZOOMING_IN;
old_capture_zoom = SetCapture(hwnd);
old_cursor_zoom = SetCursor(LoadCursor(hInst, "Zoom_In"));
}
break;
case DL_ZOOM_OUT:
/* User wants to go back to the previous sight.
*/
if (PopView() == TRUE) {
RECT wrect;
wrect.left = wrect.top = 0;
wrect.right = client_width;
wrect.bottom = client_height;
InvalidateRect(hwnd, &wrect, TRUE);
UpdateInsideTableRegion();
}
else {
char msg[100], ttl[50];
LoadString(hInst, IDS_NOBACKZ_MSG, msg, 100);
LoadString(hInst, IDS_NOBACKZ_TTL, ttl, 50);
MessageBox(hwnd, msg, ttl, MB_OK | MB_ICONEXCLAMATION);
}
break;
case DL_VIEW_LEFT: {
/* User wants to see the left-hand part of the
carom-table.
*/
RECT wrect;
PushView();
ComputeLeftTableParams();
wrect.left = wrect.top = 0;
wrect.right = client_width;
wrect.bottom = client_height;
InvalidateRect(hwnd, &wrect, TRUE);
UpdateInsideTableRegion();
break;
}
case DL_VIEW_RIGHT: {
/* User wants to see the right-hand part of the
carom-table.
*/
RECT wrect;
PushView();
ComputeRightTableParams();
wrect.left = wrect.top = 0;
wrect.right = client_width;
wrect.bottom = client_height;
InvalidateRect(hwnd, &wrect, TRUE);
UpdateInsideTableRegion();
break;
}
case DL_VIEW_FULL: {
/* User wants to see the whole of the carom-table.
*/
RECT wrect;
PushView();
ComputeFullTableParams();
wrect.left = wrect.top = 0;
wrect.right = client_width;
wrect.bottom = client_height;
InvalidateRect(hwnd, &wrect, TRUE);
UpdateInsideTableRegion();
break;
}
case DL_POWER_EDIT:
if (GameState == SETTING_SHOT) {
/* User is modifying the text-window that contains
the actual shot-power. We read the new value that
she typed in. We also store the new value if it
is valid (it is a number, not letters).
*/
int value, ok;
value = GetDlgItemInt(hDlg, DL_POWER_EDIT, &ok, TRUE);
if (!ok) {
return 1L;
}
value = clamp(value, POWER_MIN, POWER_MAX);
SetScrollPos(GetDlgItem(hDlg, DL_POWER_SCROLL), SB_CTL,
value, TRUE);
shot_power = value;
}
break;
case DL_POWER_LOW:
case DL_POWER_MEDIUM:
case DL_POWER_HIGH:
if (GameState == SETTING_SHOT) {
/* User clicked on one of the set-exact-power buttons.
We store the value corresponding to that button.
*/
int value;
value = wParam == DL_POWER_LOW ? POWER_LOW_VALUE :
(wParam == DL_POWER_MEDIUM ? POWER_MEDIUM_VALUE :
POWER_HIGH_VALUE);
SetDlgItemInt(hDlg, DL_POWER_EDIT, value, TRUE);
}
break;
case DL_HOR_EDIT:
if (GameState == SETTING_SHOT) {
/* User is modifying the text window that contains the
actual value of the horizontal component of the spin.
We read and store the new value if it is valid.
*/
int value, ok;
value = GetDlgItemInt(hDlg, DL_HOR_EDIT, &ok, TRUE);
if (!ok) {
return 1L;
}
value = clamp(value, SPIN_MIN, SPIN_MAX);
SetScrollPos(GetDlgItem(hDlg, DL_HOR_SCROLL), SB_CTL,
value, TRUE);
horizontal_spin = value;
/* Redraw the ball-image as well...
*/
DoPaintDlg(hDlg, FALSE);
}
break;
case DL_VER_EDIT:
if (GameState == SETTING_SHOT) {
/* Same as at the horizontal component above, but we
use the inverted value of the number visible in the
text window to set the scroll bar position.
This is because it looks better if positive numbers
refer to top spin instead of back spin.
*/
int value, ok;
value = GetDlgItemInt(hDlg, DL_VER_EDIT, &ok, TRUE);
if (!ok) {
return 1L;
}
SetScrollPos(GetDlgItem(hDlg, DL_VER_SCROLL), SB_CTL,
-value, TRUE);
vertical_spin = -value;
/* Redraw the ball-image as well...
*/
DoPaintDlg(hDlg, FALSE);
}
break;
case DL_NO_SPIN:
if (GameState == SETTING_SHOT) {
/* User clicked on the "NO SPIN" button which is meant
to set the cue to hit the centre of the cue ball.
We set the edit boxes (which set the scroll bars)
and redraw the image of the cue ball.
*/
SetDlgItemInt(hDlg, DL_HOR_EDIT, 0, TRUE);
SetDlgItemInt(hDlg, DL_VER_EDIT, 0, TRUE);
DoPaintDlg(hDlg, FALSE);
}
break;
case DL_TAKE_SHOT:
if (GameState == BEFORE_GAME) {
char ttl[40], msg[200];
LoadString(hInst, IDS_BEGIN_FIRST_TTL, ttl, 40);
LoadString(hInst, IDS_BEGIN_FIRST_MSG, msg, 200);
MessageBox(hwnd, msg, ttl, MB_ICONEXCLAMATION | MB_OK);
}
else if (GameState == AFTER_GAME) {
char ttl[40], msg[200];
LoadString(hInst, IDS_BEGIN_NEW_TTL, ttl, 40);
LoadString(hInst, IDS_BEGIN_NEW_MSG, msg, 200);
MessageBox(hwnd, msg, ttl, MB_ICONEXCLAMATION | MB_OK);
}
else if (GameState == SETTING_SHOT) {
/* User pressed the "TAKE SHOT" button. According to
the current settings, we must compute and display all
the states of the balls after the shot is taken.
Meanwhile the cursor is changed to a sand-watch shape.
*/
float direction;
int good_shot, extra_points, first_ball, i;
HDC hDC;
/* First of all, save current parameters so that undo will
be possible:
*/
memcpy(ball_save, ball, sizeof(ball_save));
memcpy(&sight_line_end_save, &sight_line_end, sizeof(sight_line_end));
memcpy(sight_extra_save, sight_extra, sizeof(sight_extra_save));
memcpy(sight_extra_drawn_save, sight_extra_drawn, sizeof(sight_extra_drawn_save));
cueball_save = cueball;
horizontal_spin_save = horizontal_spin;
vertical_spin_save = vertical_spin;
shot_power_save = shot_power;
memcpy(score_save, score, sizeof(score_save));
first_shot_save = first_shot;
/* Capture mouse and change icon to sand-watch:
*/
SetCapture(hwnd);
SetCursor(LoadCursor(NULL, IDC_WAIT));
/* Clear table if path for only last shot is
required:
*/
if (draw_path == CM_PATH_LAST) {
InvalidateRgn(hwnd,
my_inside_table_region,
TRUE);
SendMessage(hwnd, WM_PAINT, NULL, NULL);
}
hDC = GetDC(hwnd);
SelectObject(hDC, my_client_region);
DrawSightLines(hDC, (FPOINT *) sight_extra, sight_extra_drawn);
ReleaseDC(hwnd, hDC);
/* Compute this shot:
*/
direction = atan2(sight_line_end.y - ball[cueball].y,
sight_line_end.x - ball[cueball].x);
if (direction < 0) {
direction += 2 * M_PI;
}
compute_all_phases(vertical_spin, horizontal_spin, shot_power,
direction, cueball, &good_shot, &extra_points,
&first_ball);
ReleaseCapture();
switch (GameStyle) {
case DL_NG_GAME0:
if (good_shot &&
((first_shot == FALSE) || (first_ball == 2))) {
score[cueball] += 1 + CushionPoints * extra_points;
}
else {
cueball = 1 - cueball;
}
break;
case DL_NG_GAME1:
if (good_shot && (extra_points >= 1) &&
((first_shot == FALSE) || (first_ball == 2))) {
score[cueball] += 1 + CushionPoints * (extra_points - 1);
}
else {
cueball = 1 - cueball;
}
break;
case DL_NG_GAME3:
if (good_shot && (extra_points >= 3) &&
((first_shot == FALSE) || (first_ball == 2))) {
score[cueball] += 1 + CushionPoints * (extra_points - 3);
}
else {
cueball = 1 - cueball;
}
break;
}
first_shot = FALSE;
if (EnableUndo) {
EnableMenuItem(GetMenu(hwnd),
CM_UNDO_SHOT,
MF_BYCOMMAND | MF_ENABLED);
}
EnableMenuItem(GetMenu(hwnd), CM_BROKEN_LINE,
MF_BYCOMMAND | MF_GRAYED);
EnableMenuItem(GetMenu(hwnd), DL_ZOOM_IN,
MF_BYCOMMAND | MF_GRAYED);
EnableMenuItem(GetMenu(hwnd), DL_TAKE_SHOT,
MF_BYCOMMAND | MF_GRAYED);
EnableMenuItem(GetMenu(hwnd), CM_NEW_GAME,
MF_BYCOMMAND | MF_GRAYED);
shot_history = (unsigned huge *) GlobalLock(hglbShotHistory);
GameState = BALLS_MOVING;
last_displayed_frame = 0;
dwTakeShotTime = GetTickCount();
for (i = 0; i < 2; i++) {
MessageBeep(-1);
}
if (TimeredShotDisplay) {
globalidTimer1 = SetTimer(NULL, 1994, 50,
(TIMERPROC) lpShotTimerProc);
globalidTimer2 = SetTimer(NULL, 1995, 50,
(TIMERPROC) lpShotTimerProc);
}
else {
while (GameState == BALLS_MOVING) {
tprcShotTimerProc(NULL, NULL, NULL, GetTickCount());
}
}
}
break;
default:
/* MessageBeep(MB_ICONEXCLAMATION); */
break;
}
break;
case WM_VSCROLL:
case WM_HSCROLL:
if (GameState == SETTING_SHOT) {
/* User performed some actions with one of the three scroll
bars located in this dialog box.
We compute the new value, store it, set the scroll bar to
the new value, set the appropriate text box to the new
value and redraw the image of the cue ball.
*/
WORD wScrollCode = wParam; /* scroll bar code */
int nPos = LOWORD(lParam); /* current position of scroll box */
HWND hwndCtl = (HWND) HIWORD(lParam); /* handle of the control */
int mi, ma, delta, invertflag, ballredraw;
int *storage;
WORD edit;
switch (wScrollCode) {
case SB_LINEDOWN: /* Scroll one line down. */
delta = 1;
break;
case SB_LINEUP: /* Scroll one line up. */
delta = -1;
break;
case SB_PAGEDOWN: /* Scroll one page down. */
delta = 10;
break;
case SB_PAGEUP: /* Scroll one page up. */
delta = -10;
break;
case SB_THUMBPOSITION: /* Scroll to abs. position. */
case SB_THUMBTRACK: /* Scroll to abs. Position. */
/* No calibration action is needed! */
break;
default:
return 1L;
}
if (hwndCtl == GetDlgItem(hDlg, DL_HOR_SCROLL)) {
mi = SPIN_MIN;
ma = SPIN_MAX;
storage = &horizontal_spin;
edit = DL_HOR_EDIT;
invertflag = 1;
ballredraw = 1;
}
else if (hwndCtl == GetDlgItem(hDlg, DL_VER_SCROLL)) {
mi = SPIN_MIN;
ma = SPIN_MAX;
storage = &vertical_spin;
edit = DL_VER_EDIT;
invertflag = -1;
ballredraw = 1;
}
else if (hwndCtl == GetDlgItem(hDlg, DL_POWER_SCROLL)) {
mi = POWER_MIN;
ma = POWER_MAX;
storage = &shot_power;
edit = DL_POWER_EDIT;
invertflag = 1;
ballredraw = 0;
}
else {
/* Unknown Scroll Bar. Do nothing: */
return 1L;
}
/* Set new value to both scroll bar and edit window */
if (wScrollCode != SB_THUMBPOSITION &&
wScrollCode != SB_THUMBTRACK) {
nPos = *storage + delta;
}
nPos = clamp(nPos, mi, ma);
*storage = nPos;
SetScrollPos(hwndCtl, SB_CTL, nPos, TRUE);
SetDlgItemInt(hDlg, edit, nPos * invertflag, TRUE);
if (ballredraw) {
DoPaintDlg(hDlg, FALSE);
}
}
break;
default:
return 0L;
} // switch
return 1L;
} // end of ParDlgBoxProc()
/*************************************************************************
DoPaint -- this is the WM_PAINT action routine for the main window.
The visible part of the carom-table and the visible balls are redrawn
according to the current settings.
*************************************************************************/
void DoPaint()
{
PAINTSTRUCT PaintInfo;
HDC hDC;
int i;
BeginPaint(hwnd, &PaintInfo);
hDC = PaintInfo.hdc;
// save device context; easiest way to preserve current state
SaveDC(hDC);
/* Create rectangular region for the drawable area:
*/
SelectObject(hDC, my_client_region);
/* Set drawing mode to COPY:
*/
SetROP2(hDC, R2_COPYPEN);
/* Draw the table:
*/
SelectObject(hDC, my_wood_pen);
SelectObject(hDC, my_wood_brush);
Rectangle(hDC, X_PHYTOCLI(0), Y_PHYTOCLI(TOP + CUSHION_WIDTH + WOOD_WIDTH),
X_PHYTOCLI(RIGHT + CUSHION_WIDTH + WOOD_WIDTH), Y_PHYTOCLI(0));
SelectObject(hDC, my_cush_pen);
SelectObject(hDC, my_cush_brush);
Rectangle(hDC, X_PHYTOCLI(WOOD_WIDTH), Y_PHYTOCLI(TOP + CUSHION_WIDTH),
X_PHYTOCLI(RIGHT + CUSHION_WIDTH), Y_PHYTOCLI(WOOD_WIDTH));
SelectObject(hDC, my_cloth_brush);
Rectangle(hDC, X_PHYTOCLI(LEFT), Y_PHYTOCLI(TOP),
X_PHYTOCLI(RIGHT), Y_PHYTOCLI(BOTTOM));
/* Draw the spots:
*/
SetPixel(hDC, SPOT_X1, SPOT_Y1, RGB(255, 255, 255));
SetPixel(hDC, SPOT_X1, SPOT_Y2, RGB(255, 255, 255));
SetPixel(hDC, SPOT_X1, SPOT_Y3, RGB(255, 255, 255));
SetPixel(hDC, SPOT_X2, SPOT_Y2, RGB(255, 255, 255));
SetPixel(hDC, SPOT_X3, SPOT_Y2, RGB(255, 255, 255));
if (GameState != BALLS_MOVING) {
/* Draw balls and sight_line to the inside of the table:
*/
SelectObject(hDC, my_inside_table_region);
/* Then get a "black pen - white brush" for drawing the first ball.
*/
for (i = 0; i < N; i++) {
SelectObject(hDC, my_ball_pen[i]);
SelectObject(hDC, my_ball_brush[i]);
Ellipse(hDC, X_PHYTOCLI(ball[i].x) - RADIUS,
Y_PHYTOCLI(ball[i].y) - RADIUS,
X_PHYTOCLI(ball[i].x) + RADIUS + 1,
Y_PHYTOCLI(ball[i].y) + RADIUS + 1);
}
DrawSightLines(hDC, (FPOINT *) sight_extra, sight_extra_drawn);
}
RestoreDC(hDC, -1);
EndPaint(hwnd, &PaintInfo);
} // end of DoPaint()
/*************************************************************************
wmCreate -- action routine for the WM_CREATE message. This routine
sets up colors for use in the painting routines.
*************************************************************************/
void wmCreate(void)
{
LOGBRUSH logbrush;
static COLORREF ball_colours[N] = {
RGB(255, 255, 255),
RGB(255, 255, 0),
RGB(255, 0, 0)
};
int i;
/* Allocate global memory for shot buffer:
*/
hglbShotHistory = GlobalAlloc(GMEM_MOVEABLE, SHOT_BUFFER_SIZE);
if (hglbShotHistory == NULL) {
char msg[100], ttl[50];
LoadString(hInst, IDS_MEMORY_MSG, msg, 100);
LoadString(hInst, IDS_MEMORY_TTL, ttl, 50);
MessageBox(hwnd, msg, ttl, MB_OK | MB_ICONEXCLAMATION);
DestroyWindow(hwnd);
return;
}
/* All the brushes will be SOLID:
*/
logbrush.lbStyle = BS_SOLID;
my_wood_pen = CreatePen(PS_SOLID, 1, RGB(0, 0, 0));
logbrush.lbColor = RGB(128, 0, 0);
my_wood_brush = CreateBrushIndirect(&logbrush);
my_cush_pen = CreatePen(PS_SOLID, 1, RGB(0, 32, 0));
logbrush.lbColor = RGB(0, 73, 0);
my_cush_brush = CreateBrushIndirect(&logbrush);
my_cloth_pen = CreatePen(PS_SOLID, 1, RGB(0, 128, 0));
logbrush.lbColor = RGB(0, 128, 0);
my_cloth_brush = CreateBrushIndirect(&logbrush);
my_rectangle_pen = CreatePen(PS_DOT, 1, RGB(255, 255, 255));
my_sight_line_pen = CreatePen(PS_SOLID, 1, RGB(0, 128, 0));
my_sight_line_extra_pen = CreatePen(PS_DOT, 1, RGB(255, 255, 255));
for (i = 0; i < N; i++) {
my_ball_pen[i] = CreatePen(PS_SOLID, 1, RGB(0, 0, 0));
logbrush.lbColor = ball_colours[i];
my_ball_brush[i] = CreateBrushIndirect(&logbrush);
}
pens_brushes_present = TRUE;
} // end of wmCreate()
void UnCreate(void)
{
int i;
/* If shot timer is still active, remove timer!
*/
if (globalidTimer1) {
KillTimer(NULL, globalidTimer1);
KillTimer(NULL, globalidTimer2);
globalidTimer1 = globalidTimer2 = 0;
}
if (pens_brushes_present) {
DeleteObject(my_wood_pen);
DeleteObject(my_wood_brush);
DeleteObject(my_cush_pen);
DeleteObject(my_cush_brush);
DeleteObject(my_cloth_pen);
DeleteObject(my_cloth_brush);
DeleteObject(my_rectangle_pen);
DeleteObject(my_sight_line_pen);
DeleteObject(my_sight_line_extra_pen);
for (i = 0; i < N; i++) {
DeleteObject(my_ball_pen[i]);
DeleteObject(my_ball_brush[i]);
}
pens_brushes_present = FALSE;
}
/* Delete ball bitmaps:
*/
if (hbmpTemp) {
DeleteObject(hbmpTemp);
}
if (hbmpBallMask) {
DeleteObject(hbmpBallMask);
}
if (hbmpClothBall) {
DeleteObject(hbmpClothBall);
}
for (i = 0; i < N; i++) {
if (hbmpBall[i]) {
DeleteObject(hbmpBall[i]);
}
}
/* Free shot history buffer:
*/
if (hglbShotHistory) {
GlobalFree(hglbShotHistory);
hglbShotHistory = NULL;
}
/* Free sine, cosine and arctan tables:
*/
uninit_trigo();
/* Close Help window:
*/
WinHelp(hwnd, szHelpFileName, HELP_QUIT, 0L);
}
/*********************************************************************
cmXxxx routines -- these are action routines for menu commands
*********************************************************************/
// *************************************************************************
// cmAbout -- invoke the "About" dialog. Its return code is ignored, since
// the About dialog doesn't return anything to the program.
// *************************************************************************
void cmAbout(void)
{
lpDlgProc = (DLGPROC) MakeProcInstance( (FARPROC) DlgBoxProc, hInst);
DialogBox(hInst, "About", hwnd, lpDlgProc);
FreeProcInstance ((FARPROC) lpDlgProc);
} // end of cmAbout()
// *************************************************************************
// cmNewGame -- invoke the "New game" dialog.
// *************************************************************************
int cmNewGame(void)
{
int result;
lpNewGameDlgProc = (DLGPROC) MakeProcInstance( (FARPROC) NewGameDlgBoxProc, hInst);
result = DialogBox(hInst, "NewGame", hwnd, lpNewGameDlgProc);
FreeProcInstance ((FARPROC) lpNewGameDlgProc);
return result == DL_NG_OK;
} // end of cmAbout()
/* Timer procedure: When the user takes the shot, this callback function is initialised and
it will be called once every 1/25 seconds.
*/
#pragma argsused
void CALLBACK tprcShotTimerProc(HWND hWnd, UINT msg, UINT idTimer, DWORD dwTime)
{
unsigned i, j, k;
int beep_cush = 0;
int beep_ball = 0;
unsigned frame_size = shot_history[0] * 2 + 1;
unsigned huge *prev_frame;
unsigned huge *this_frame;
unsigned utmp;
int end_animation = FALSE;
HDC hDC, hdctmp, hdctmp2;
POINT prev, curr;
/* Compute frame number to be displayed:
*/
i = (dwTime - dwTakeShotTime) / (TIME_SLOT * 1000);
//!!!i = last_displayed_frame + 1;
if (i >= shot_history[1] - 1) {
/* End of shot. Go display the very last frame, which may not have been displayed
yet.
*/
i = shot_history[1] - 1;
end_animation = TRUE;
}
/* Examine whether there were collisions since the the last drawn frame:
*/
j = last_displayed_frame;
for (k = j + 1; k <= i; k++) {
utmp = shot_history[2 + k * frame_size + frame_size - 1];
beep_cush += LOBYTE(utmp);
beep_ball += HIBYTE(utmp);
}
/* Beep if there have been some collisions:
*/
if (beep_ball > 0) {
MessageBeep(-1);
}
/* Locate the frame to be drawn and the previous frame:
*/
prev_frame = shot_history + (2 + j * frame_size);
this_frame = shot_history + (2 + i * frame_size);
/* Get device context:
*/
hDC = GetDC(hwnd);
/* Select rectangular region:
*/
SelectObject(hDC, my_inside_table_region);
/* Process all the balls: Clear one from previous position and draw it to new position.
Then go to next ball.
*/
hdctmp = CreateCompatibleDC(hDC);
hdctmp2 = CreateCompatibleDC(hDC);
SelectObject(hdctmp, hbmpTemp);
for (k = 0; k < shot_history[0]; k++) {
/* Process kth ball:
*/
/* Determine client coordinates for both previous and current position:
*/
prev.x = X_PHYTOCLI(UNFIXED(*prev_frame++));
prev.y = Y_PHYTOCLI(UNFIXED(*prev_frame++));
curr.x = X_PHYTOCLI(UNFIXED(*this_frame++));
curr.y = Y_PHYTOCLI(UNFIXED(*this_frame++));
if (draw_path == CM_PATH_NONE) {
/* Delete ball from previous location:
*/
/* Copy area of previous position from screen:
*/
BitBlt(hdctmp, 0, 0, 2 * RADIUS + 1, 2 * RADIUS + 1,
hDC, prev.x - RADIUS, prev.y - RADIUS, SRCCOPY);
/* Fill the previous ball-shape with cloth colour:
*/
SelectObject(hdctmp2, hbmpBallMask);
BitBlt(hdctmp, 0, 0, 2 * RADIUS + 1, 2 * RADIUS + 1,
hdctmp2, 0, 0, SRCAND);
SelectObject(hdctmp2, hbmpClothBall);
BitBlt(hdctmp, 0, 0, 2 * RADIUS + 1, 2 * RADIUS + 1,
hdctmp2, 0, 0, SRCPAINT);
/* Draw the ball to new position:
*/
SelectObject(hdctmp2, hbmpBallMask);
BitBlt(hdctmp, curr.x - prev.x, curr.y - prev.y,
2 * RADIUS + 1, 2 * RADIUS + 1,
hdctmp2, 0, 0, SRCAND);
SelectObject(hdctmp2, hbmpBall[k]);
BitBlt(hdctmp, curr.x - prev.x, curr.y - prev.y,
2 * RADIUS + 1, 2 * RADIUS + 1,
hdctmp2, 0, 0, SRCPAINT);
/* Put result back to the screen (no flash!)
*/
BitBlt(hDC, prev.x - RADIUS, prev.y - RADIUS,
2 * RADIUS + 1, 2 * RADIUS + 1,
hdctmp, 0, 0, SRCCOPY);
}
/* Get area of new position of the ball:
*/
BitBlt(hdctmp, 0, 0, 2 * RADIUS + 1, 2 * RADIUS + 1,
hDC, curr.x - RADIUS, curr.y - RADIUS, SRCCOPY);
/* Draw the ball to new position:
*/
SelectObject(hdctmp2, hbmpBallMask);
BitBlt(hdctmp, 0, 0, 2 * RADIUS + 1, 2 * RADIUS + 1,
hdctmp2, 0, 0, SRCAND);
SelectObject(hdctmp2, hbmpBall[k]);
BitBlt(hdctmp, 0, 0, 2 * RADIUS + 1, 2 * RADIUS + 1,
hdctmp2, 0, 0, SRCPAINT);
/* Put result back to the screen (no flash!)
*/
BitBlt(hDC, curr.x - RADIUS, curr.y - RADIUS,
2 * RADIUS + 1, 2 * RADIUS + 1,
hdctmp, 0, 0, SRCCOPY);
}
/* Draw the sposts:
*/
if (draw_path == CM_PATH_NONE) {
COLORREF colour;
colour = GetPixel(hDC, SPOT_X1, SPOT_Y1);
if (GetGValue(colour) && GetRValue(colour) + GetBValue(colour) == 0) {
SetPixel(hDC, SPOT_X1, SPOT_Y1, RGB(255, 255, 255));
}
colour = GetPixel(hDC, SPOT_X1, SPOT_Y2);
if (GetGValue(colour) && GetRValue(colour) + GetBValue(colour) == 0) {
SetPixel(hDC, SPOT_X1, SPOT_Y2, RGB(255, 255, 255));
}
colour = GetPixel(hDC, SPOT_X1, SPOT_Y3);
if (GetGValue(colour) && GetRValue(colour) + GetBValue(colour) == 0) {
SetPixel(hDC, SPOT_X1, SPOT_Y3, RGB(255, 255, 255));
}
colour = GetPixel(hDC, SPOT_X2, SPOT_Y2);
if (GetGValue(colour) && GetRValue(colour) + GetBValue(colour) == 0) {
SetPixel(hDC, SPOT_X2, SPOT_Y2, RGB(255, 255, 255));
}
colour = GetPixel(hDC, SPOT_X3, SPOT_Y2);
if (GetGValue(colour) && GetRValue(colour) + GetBValue(colour) == 0) {
SetPixel(hDC, SPOT_X3, SPOT_Y2, RGB(255, 255, 255));
}
}
DeleteDC(hdctmp);
DeleteDC(hdctmp2);
/* Note that the ith frame is the one last displayed:
*/
last_displayed_frame = i;
/* Every ball is now redrawn.
Terminate timer procedure if this has been the last displayed frame:
Kill timer, unlock memory object, draw sight line and update state of game:
*/
if (end_animation) {
POINT p;
int erase_old_sight = FALSE;
KillTimer(NULL, globalidTimer1);
KillTimer(NULL, globalidTimer2);
globalidTimer1 = globalidTimer2 = 0;
GlobalUnlock(hglbShotHistory);
UpdateScore();
if (EnableBreak) {
EnableMenuItem(GetMenu(hwnd), CM_BROKEN_LINE,
MF_BYCOMMAND | MF_ENABLED);
}
EnableMenuItem(GetMenu(hwnd), DL_ZOOM_IN,
MF_BYCOMMAND | MF_ENABLED);
EnableMenuItem(GetMenu(hwnd), DL_TAKE_SHOT,
MF_BYCOMMAND | MF_ENABLED);
EnableMenuItem(GetMenu(hwnd), CM_NEW_GAME,
MF_BYCOMMAND | MF_ENABLED);
GameState = SETTING_SHOT;
/* Has somebody won the game?
*/
if ((cueball == cueball_save && cueball != WhoStarted &&
score[cueball] >= WinningScore &&
score[1 - cueball] < WinningScore) ||
(cueball != cueball_save && cueball_save != WhoStarted &&
score[cueball] >= WinningScore) ||
(score[0] >= WinningScore && score[1] >= WinningScore)) {
char ttl[40], msg[200];
LoadString(hInst, IDS_WINNER_TTL, ttl, 40);
if (score[0] >= WinningScore && score[1] >= WinningScore) {
LoadString(hInst, IDS_DRAW_GAME_MSG, msg, 200);
}
else {
strcpy(msg, score[0] >= WinningScore ? player1_name :
player2_name);
strcat(msg, ", ");
LoadString(hInst, IDS_WINNER_MSG,
msg + strlen(msg), 200 - strlen(msg));
}
MessageBox(hwnd, msg, ttl, MB_ICONINFORMATION | MB_OK);
GameState = AFTER_GAME;
}
else if (cueball == cueball_save && cueball == WhoStarted &&
score[cueball] >= WinningScore) {
/* The player who started the game has reached the number of points
to win. But, he/she has not won yet, because the other player
has had one less visit than this player and he/she will have the
final visit. If he/she also succeeds to reach enough points in
this visit, the game will be drawn. If he/she remains under the
number of points required to win the game, the player who started
will be the winner.
So we are going to start the final visit now:
*/
char ttl[40], msg[200];
p.x = X_PHYTOCLI(sight_line_end.x);
p.y = Y_PHYTOCLI(sight_line_end.y);
UpdateSightLine(hwnd, &p, FALSE);
erase_old_sight = TRUE;
LoadString(hInst, IDS_LAST_VISIT_TTL, ttl, 40);
strcpy(msg, cueball == 1 ? player1_name : player2_name);
strcat(msg, ", ");
LoadString(hInst, IDS_LAST_VISIT_MSG,
msg + strlen(msg), 200 - strlen(msg));
MessageBox(hwnd, msg, ttl, MB_ICONINFORMATION | MB_OK);
/* Switch to the other player and replace the balls
into starting position as if this were the first shot:
*/
cueball = 1 - cueball;
ball[cueball].x = BOTTOMR_SPOT_X;
ball[cueball].y = BOTTOMR_SPOT_Y;
ball[1-cueball].x = BOTTOMM_SPOT_X;
ball[1-cueball].y = BOTTOMM_SPOT_Y;
ball[2].x = TOP_SPOT_X;
ball[2].y = TOP_SPOT_Y;
first_shot = TRUE;
InvalidateRgn(hwnd, my_inside_table_region, TRUE);
}
p.x = X_PHYTOCLI(sight_line_end.x);
p.y = Y_PHYTOCLI(sight_line_end.y);
UpdateSightLine(hwnd, &p, erase_old_sight);
PostMessage(hParDlg, WM_COMMAND, DL_NO_SPIN, NULL);
}
ReleaseDC(hWnd,hDC);
}