Win32游戏制作之---FreakOut
Posted Loving_初衷
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Win32游戏制作之---FreakOut相关的知识,希望对你有一定的参考价值。
记得自己小时候曾经玩过这个小游戏,如今长大了,有了一定的知识就可以实现一些小时候未曾想过的事情,或者说梦想去做的事情!虽然这次实现的小游戏比较简单,但是也算游戏吧,比较自己还不是这方面的大神。
如果想要用Win32项目制作一个小游戏,那么首先你要对对C/C++语言熟悉,对Windows编程有一定的了解。其实还有一点就是你在开始制作小游戏之前,你要知道你的游戏的逻辑结构。对于一般的小游戏结构可以用下图的结构表示。
Game_main()是游戏的主体函数,不过它在主事件循环每次处理windows消息之后就会调用一次,要注意每次进入和终止该函数的时候,自动变量是瞬间变化的,如果想要一直使用的数据,建议使用全局变量,或者也可以设置为局部静态变量。
恰好最近正在学Windows编程,想试试Windows是否真的适合制作游戏编程。最后在看了《Windows游戏编程大师技巧》之后,决定将里面的FreakOut游戏实现一下。
由于平时用习惯了C/C++编程可能开始会有些不习惯,但是用多了就会发现其实最重要的就是要理解Windows编程中的消息驱动原理,简单的说其实就是Windows有其自己的基于事件的中枢神经系统。当我们按下一个键,就会有消息从按键事件中创建并且在系统中散播,直到有程序检出这个消息并使用它。当然Windows编程中的要注意的点还是挺多的,字符编码就是其中一个,不过解决起来的很简单(例如著名的“L”字符和TCHAR)。还有一点就是Windows编程中的主函数WinMain(),如果第一眼看到他,觉得很复杂,但是WinMain并不需要是一个又大又复杂的应用代码大杂烩。
相信大家都玩过FreakOut游戏(俗称打砖块游戏),游戏主要是利用小球消灭所有的砖块,所有的砖块大小相等,小球每次弹到砖块上都会发生反弹,按照物理上的说法就是发生弹性碰撞,当然,小球只能在屏幕(其实说窗口更准确)内运动,所以当小球碰到窗口的边界的时候就会发生反弹,假设小球做的是直线运动,根据速度的分解可知可以将小球的速度正交分解,分成一个X方向上的运动和一个Y方向上的运动。再碰到边界或者是砖块或者你跳板之后都会发生反弹,所以只要保证某一个方向上的速度不变,另一个方向上的速度反向即可。(不过我在具体实现的过程中没有完全按照物理学规律来制作,在碰到砖块或者跳板的时候让他们的速度有一点变化,这样有时候可以避免小球一直做同样的运动)。
下面就通过代码来解释:
首先来看下头文件FreakOut.h:
#pragma once
#include "resource.h"
//define for Windows 窗口大小定义
#define WINDOW_WIDTH 640
#define WINDOW_HEIGHT 480
//state for game loop 游戏的循环逻辑状态
#define GAME_STATE_INIT 0
#define GAME_STATE_START_LEVEL 1
#define GAME_STATE_RUN 2
#define GAME_STATE_SHUTDOWN 3
#define GAME_STATE_EXIT 4
//block defines 有关砖块的定义
#define NUM_BLOCK_ROWS 2
#define NUM_BLOCK_COLUMNS 8
#define BLOCK_WIDTH 64
#define BLOCK_HEIGHT 16
#define BLOCK_ORIGIN_X 8
#define BLOCK_ORIGIN_Y 8
#define BLOCK_X_GAP 80
#define BLOCK_Y_GAP 32
#define BLOCK_COLOR RGB(222,200,125)
//ball defines 有关小球的定义
#define BALL_COLOR RGB(255,0,0)
#define BALL_START_Y (WINDOW_HEIGHT/2)
#define BALL_SIZE 14
//paddle defines 跳板的定义
#define PADDLE_START_X (WINDOW_WIDTH/2 - 16)
#define PADDLE_START_Y (WINDOW_HEIGHT - 32 )
#define PADDLE_WIDTH 50
#define PADDLE_HEIGHT 8
#define PADDLE_COLOR RGB(0,0,255)
//these read the keyboard asynchronously 键盘的相应按键
#define KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code)&0x8000)?1:0)
#define KEY_UP(vk_code) ((GetAsyncKeyState(vk_code)&0x8000)?0:1)
//set bounds 设置关卡数
#define Game_Count 3
//basic unsigned types
typedef unsigned short USHORT;
typedef unsigned short WORD;
typedef unsigned char UCHAR;
typedef unsigned char BYTE;
头文件主要是一些游戏中一些对象的初始值的有关定义。可以按照个人喜好设置。
下面来看一下内部的实现逻辑,先贴上我的代码FreakOut.cpp:
// FreakOut.cpp : 定义应用程序的入口点。
//
#include "stdafx.h"
#include "FreakOut.h"
#define MAX_LOADSTRING 100
// 全局变量:
HINSTANCE hInst; // 当前实例
TCHAR szTitle[MAX_LOADSTRING]; // 标题栏文本
TCHAR szWindowClass[MAX_LOADSTRING]; // 主窗口类名
HWND main_window_handle = NULL; //save the window handle
HINSTANCE main_instance = NULL; //save the instance
int game_state = GAME_STATE_INIT; //starting state
int paddle_x = 0, paddle_y = 0; //tracks position of handle
int ball_x = 0, ball_y = 0; //tracks position of ball
int ball_dx = 0, ball_dy = 0; //velocity of ball
int score = 0; //the score
int level = 1; //the Current level
int block_hit = 0; //tracks number of block hit
DWORD start_clock_count = 0, now_clock = 0; //used for timing
UCHAR blocks[NUM_BLOCK_ROWS][NUM_BLOCK_COLUMNS];//this contains the game grid data
// 此代码模块中包含的函数的前向声明:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
int Game_Init(void *parms = NULL);
int Game_Shutdown(void *parms = NULL);
int Game_Main(void *parms = NULL);
DWORD Start_Clock();
DWORD Get_Clock(void);
DWORD Wait_Clock(DWORD );
void Change_Mode(HWND, DWORD, int);
int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPTSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// TODO: 在此放置代码。
MSG msg;
HACCEL hAccelTable;
WNDCLASSEX winclass;
HWND hwnd;
HDC hdc;
PAINTSTRUCT ps;
// 初始化全局字符串
LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadString(hInstance, IDC_FREAKOUT, szWindowClass, MAX_LOADSTRING);
/*MyRegisterClass(hInstance);*/ //注册窗口类
winclass.cbSize = sizeof(WNDCLASSEX);
winclass.style = CS_DBLCLKS | CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
winclass.lpfnWndProc = WndProc;
winclass.cbClsExtra = 0;
winclass.cbWndExtra = 0;
winclass.hInstance = hInstance;
winclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
winclass.hCursor = LoadCursor(NULL, IDC_ARROW);
winclass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
winclass.lpszMenuName = MAKEINTRESOURCE(IDC_FREAKOUT);
winclass.lpszClassName = szWindowClass;
winclass.hIconSm = LoadIcon(winclass.hInstance, MAKEINTRESOURCE(IDI_SMALL));
if (!RegisterClassEx(&winclass)) //注册窗口类是否成功
{
return FALSE;
}
int cxNonClient = GetSystemMetrics(SM_CXBORDER) * 2 + 10;
int cyNonClient = GetSystemMetrics(SM_CYBORDER) + GetSystemMetrics(SM_CYCAPTION) + 10;
//Create the window, note the use of WS_POPUP
hwnd = CreateWindowEx(NULL,szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, 0, 0, WINDOW_WIDTH + cxNonClient, WINDOW_HEIGHT + cyNonClient, NULL, NULL, hInstance, NULL);
// 执行应用程序初始化
if (!hwnd)
{
return FALSE;
}
ShowWindow(hwnd, nCmdShow); // WM_SIZE消息是由 ShowWindow函数发出的
UpdateWindow(hwnd);
//hide mouse
//ShowCursor(FALSE);
//save the window handle and instance in a global
main_window_handle = hwnd;
main_instance = hInstance;
hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_FREAKOUT));
//perform all game console specific initialization
Game_Init();
// 主消息循环:
while (true)
{
if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))
{
//test if this is a quit msg
if (msg.message == WM_QUIT)
{
break;
}
//translate any accelerator keys
TranslateMessage(&msg);
//send the message to the window proc
DispatchMessage(&msg);
}
//main game processing goes here
Game_Main();
Sleep(10);
}
return (int) msg.wParam;
}
/*DRAW Function ********** */
int Draw_Rectangle(int x1, int y1, int x2, int y2, int color)
{
//this function users Win32 API to draw a filled rectangle
HBRUSH hbrush;
HDC hdc;
RECT rect;
SetRect(&rect, x1, y1, x2, y2);
hbrush = CreateSolidBrush(color);
hdc = GetDC(main_window_handle);
//SelectObject(hdc, hbrush);
FillRect(hdc, &rect, hbrush);
ReleaseDC(main_window_handle, hdc);
DeleteObject(hbrush);
return 1;
}
int Draw_Ball(int x1, int y1, int x2, int y2, int color)
{
HDC hdc;
HBRUSH hbrush;
hbrush = CreateSolidBrush(color);
hdc = GetDC(main_window_handle);
SelectObject(hdc, hbrush);
Ellipse(hdc, x1, y1, x2, y2);
ReleaseDC(main_window_handle, hdc);
DeleteObject(hbrush);
return 1;
}
int DrawText_GUI(TCHAR *text, int x, int y, int color)
{
HDC hdc;
hdc = GetDC(main_window_handle);
//set color for the text up
SetTextColor(hdc, color);
//set background mode to transparent so black isn't copied
SetBkMode(hdc, TRANSPARENT); //设置为透明的
//draw the text
TextOut(hdc, x, y, text, lstrlen(text));
//release the dc
ReleaseDC(main_window_handle, hdc);
return 1;
}
/*GAME PROGRAMMING CONSOLE FUNCTIONS *********************************/
void Init_Blocks(void)
{
//Initialize the block field
int row = 0, col = 0;
for (row = 0; row < level*NUM_BLOCK_ROWS; row++)
{
for (col = 0; col < level*NUM_BLOCK_COLUMNS; col++)
{
blocks[row][col] = 1; //初始化
}
}
}
void Draw_Blocks(void)
{
//this function draws all the blocks in row major form
int x1 = BLOCK_ORIGIN_X;
int y1 = BLOCK_ORIGIN_Y;
int row = 0, col = 0;
//draw all the blocks
for ( row = 0; row < level*NUM_BLOCK_ROWS; row++)
{
x1 = BLOCK_ORIGIN_X;
for (col = 0; col < level*NUM_BLOCK_COLUMNS; col++)
{
if (blocks[row][col] != 0)
{
Draw_Rectangle(x1, y1, x1 + BLOCK_WIDTH, y1 + BLOCK_HEIGHT, BLOCK_COLOR);
}
//advance column position
x1 += BLOCK_X_GAP;
}
//advance to next row position
y1 += BLOCK_Y_GAP;
}
}
void Process_Ball(void)
{
//this function tests if the ball has hit a block or the paddle if so, the ball is bounced
//and the block is removed from the playfield note:very chessy collision algorithm:)
//first test for ball block collisions
//the algorith basically test the ball against each black's bounding box this is inefficient.
//but easy to implement,later we'll see a better way
//current rendering position
int x1 = BLOCK_ORIGIN_X;
int y1 = BLOCK_ORIGIN_Y;
//computer center of ball
int ball_cx = ball_x + (BALL_SIZE / 2);
int ball_cy = ball_y + (BALL_SIZE / 2);
//test the ball has hit the paddle
if (ball_y>(WINDOW_HEIGHT/2)&&ball_dy>0)
{
int x = ball_x + (BALL_SIZE / 2);
int y = ball_y + (BALL_SIZE / 2);
//test for collision with paddle
if ((x <= (paddle_x + PADDLE_WIDTH )) && (x >= paddle_x ) && (y >= paddle_y )&& (y <= paddle_y + PADDLE_HEIGHT/2))
{
if ( ((ball_dx)<0 && ((-ball_dx)>ball_dy))|| ((ball_dx>0) && (ball_dx>ball_dy)) )
{
//test if there are no blocks, if so send a message to game loop to start another level
if (block_hit >= (level*NUM_BLOCK_ROWS)*(level*NUM_BLOCK_COLUMNS))
{
level++;
if (level>Game_Count)
{
MessageBox(main_window_handle, L"Congraculation, you pass all customs!", L"FreakOut", MB_OK);
game_state = GAME_STATE_INIT;
}
else
{
game_state = GAME_STATE_START_LEVEL;
}
}
//make a little noise
MessageBeep(MB_OK);
return;
}
else
{
//relect ball
ball_dy = -ball_dy;
//puch ball out of paddle since it made contact
ball_y = ball_y + ball_dy;
//add a little english to ball based on mation of paddle
if (KEY_DOWN(VK_RIGHT))
{
ball_dx -= rand() % 3; //change speed
}
else if (KEY_UP(VK_LEFT))
{
ball_dx += rand() % 3;
}
else
{
ball_dx += (-1 + rand() % 3);
}
//test if there are no blocks, if so send a message to game loop to start another level
if (block_hit >= (level*NUM_BLOCK_ROWS)*(level*NUM_BLOCK_COLUMNS))
{
level++;
if (level>Game_Count)
{
MessageBox(main_window_handle, L"Congraculation, you pass all customs!", L"FreakOut", MB_OK);
game_state = GAME_STATE_INIT;
}
else
{
game_state = GAME_STATE_START_LEVEL;
}
}
//make a little noise
MessageBeep(MB_OK);
return;
}
}
}
//now scan thru all the blocks and see of ball hit blocks
int row, col;
for (row = 0; row < level*NUM_BLOCK_ROWS; row++)
{
x1 = BLOCK_ORIGIN_X;
for (col = 0; col < level*NUM_BLOCK_COLUMNS; col++)
{
if (blocks[row][col])
{
//test ball against bounding box of block
if ((ball_cx >= x1) && (ball_cx <= x1 + BLOCK_WIDTH) && (ball_cy >= y1) && (ball_cy <= y1 + BLOCK_HEIGHT))
{
//remove
blocks[row][col] = 0;
//increment global block counter, so we know when to start another level up
block_hit++;
//bounce the ball
ball_dy = -ball_dy;
//add a little english
ball_dx += (-1 + rand() % 3);
//make a little noise
MessageBeep(MB_OK);
//add some points
score += 5 * (level + (abs)(ball_dx));
return;
}
}
//advance column position
x1 += BLOCK_X_GAP;
}
//advance row position
y1 += BLOCK_Y_GAP;
}
}
int Game_Init(void *parms)
{
//this function is where you do all the initialization for your game
return 1;
}
int Game_Shutdown(void *parms)
{
//this function is where you shutdown your game and release all resources
//that you allocated
return 1;
}
int Game_Main(void *parms)
{
//this is the workhorse of your game it will be called continuously in real-time
//this is like main() in C all the calls for you game ge here!
TCHAR buffer[80];
//what state is the game in?
if (game_state == GAME_STATE_INIT)
{
//send the random number generator so game is different each play
srand((unsigned)time(NULL));
//send the paddle position here to the middle buttom
paddle_x = PADDLE_START_X;
paddle_y = PADDLE_START_Y;
//set ball position and velocity
ball_x = 8 + rand() % (WINDOW_WIDTH - 16);
ball_y = BALL_START_Y;
ball_dx = -4 + rand() % (8 + 1);
ball_dy = 6 + rand() % 2;
//transition to start level state
game_state = GAME_STATE_START_LEVEL;
}
else if (game_state == GAME_STATE_START_LEVEL)
{
//get a new level ready to run
//initialize the blocks
Init_Blocks();
//reset block counter
block_hit = 0;
//transition to run state
game_state = GAME_STATE_RUN;
}
else if (game_state == GAME_STATE_RUN)
{
//start the timing clock
Start_Clock();
//clear drawing surface for next frame of animation
Draw_Rectangle(0, 0, WINDOW_WIDTH - 1, WINDOW_HEIGHT - 1, RGB(255,255,255));
//move to paddle
if (KEY_DOWN(VK_RIGHT))
{
//move paddle to right
paddle_x += 8;
//make sure that the paddle doesn't go off screen
if (paddle_x > WINDOW_WIDTH - PADDLE_WIDTH)
{
paddle_x = WINDOW_WIDTH - PADDLE_WIDTH;
}
}
if (KEY_DOWN(VK_LEFT))
{
//move paddle to left
paddle_x -= 8;
//make sure that the paddle doesn't go off screen
if (paddle_x < 0)
{
paddle_x = 0;
}
}
//draw blocks
Draw_Blocks();
//move the ball
ball_x += ball_dx;
ball_y += ball_dy;
//keep ball on screen, if the ball hits the edge of screen then
//bounce it by reflecting its velocity
if ((ball_x < 0)||(ball_x > WINDOW_WIDTH - BALL_SIZE/2))
{
//reflect x-axis velocity
ball_dx = -ball_dx;
//update position
ball_x += ball_dx;
}
//now y-axis
if (ball_y < 0)
{
//relect y-axis velocity
ball_dy = -ball_dy;
//update position
ball_y += ball_dy;
}
else if (ball_y >WINDOW_HEIGHT - BALL_SIZE/2)
{
//reflect y-axis velocity
ball_dy = -ball_dy;
//update position
ball_y += ball_dy;
//minus the score
score -= 100;
}
//now watch out for ball velocity getting out of hand
if (ball_dx > 8)
{
ball_dx = 8;
}
else if (ball_dx < -8)
{
ball_dx = -8;
}
//test if ball hit any blocks or the paddle
Process_Ball();
//draw the paddle
Draw_Rectangle(paddle_x, paddle_y, paddle_x + PADDLE_WIDTH, paddle_y + PADDLE_HEIGHT, PADDLE_COLOR);
//draw the ball
//Draw_Rectangle(ball_x, ball_y, ball_x + BALL_SIZE, ball_y + BALL_SIZE, BALL_COLOR);
Draw_Ball(ball_x, ball_y, ball_x + BALL_SIZE, ball_y + BALL_SIZE, BALL_COLOR);
//check if it has reword
//now_clock = Get_Clock();
//Change_Mode(main_window_handle, 15000, score);
//draw the info
wsprintf(buffer, TEXT("Game Scroe %d Level %d"), score, level);
DrawText_GUI(buffer, 8, WINDOW_HEIGHT - 50, 127);
//sync to 30 fps
Wait_Clock(30);
//Sleep(30);
//check if user is trying to exit
if (KEY_DOWN(VK_ESCAPE))
{
//send to message to windows to exit
PostMessage(main_window_handle, /*WM_QUIT*/WM_DESTROY, 0, 0);
//send exit state
game_state = GAME_STATE_SHUTDOWN;
}
}
else if (game_state == GAME_STATE_SHUTDOWN)
{
//in this state shut everything down and release resources
//switch to exit state
game_state = GAME_STATE_EXIT;
}
else
{
;
}
return 1;
}
//CLOCK FUNCTIONS
DWORD Get_Clock(void)
{
//this function returns the current tickcount
//return time
return GetTickCount();
}
DWORD Start_Clock()
{
//this function starts the block, that is, saves the current count
//use in conjunction with Wait_Clock()
return (start_clock_count = Get_Clock());
}
DWORD Wait_Clock(DWORD count)
{
//this function is used to wait for a specific number of clicks since
//the call to Start_Clock
while (Get_Clock() - start_clock_count < count)
{
;
}
return Get_Clock();
}
/*void Change_Mode(HWND hWnd, DWORD Times, int score) //奖励模式
{
HDC hdc;
hdc = GetDC(hWnd);
RECT rect, wnd;
if (score >= Reword_Score)
{
while (Get_Clock() - now_clock < Times)
{
rect = { paddle_x - PADDLE_WIDTH, paddle_y, paddle_x + PADDLE_WIDTH, paddle_y + PADDLE_HEIGHT };
FillRect(hdc, &rect, CreateSolidBrush(RGB(0, 0, 255)));
wnd = { 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT - 2 * PADDLE_HEIGHT };
InvalidateRect(hWnd, &wnd, false);
}
}
score -= 50;
InvalidateRect(hWnd, NULL, false);
ReleaseDC(hWnd, hdc);
}*/
// 函数: WndProc(HWND, UINT, WPARAM, LPARAM)
//
// 目的: 处理主窗口的消息。
//
// WM_COMMAND - 处理应用程序菜单
// WM_PAINT - 绘制主窗口
// WM_DESTROY - 发送退出消息并返回
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
switch (message)
{
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// 分析菜单选择:
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: 在此添加任意绘图代码...
/*
//创建内存HDC
HDC memHDC = CreateCompatibleDC(hdc);
//获取客户区大小
RECT rectClient;
GetClientRect(hWnd,&rectClient);
//创建位图
HBITMAP bmpBuff = CreateCompatibleBitmap(hdc, RECT_WIDTH(rectClient), RECT_HEIGHT(rectClient));
HBITMAP pOldBMP = (HBITMAP)SelectObject(memHDC, bmpBuff);
// draw something
DrawBackGround(memHDC);
//拷贝内存HDC内容到实际HDC
BOOL tt = BitBlt(hdc, rectClient.left, rectClient.top, RECT_WIDTH(rectClient),
RECT_HEIGHT(rectClient), memHDC, rectClient.left, rectClient.top, SRCCOPY);
//内存回收
SelectObject(memHDC, pOldBMP);
DeleteObject(bmpBuff);
DeleteDC(memHDC);*/
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_ERASEBKGND:
//防止清除背景造成的白屏
//什么也不做,返回0使默认窗口回调不再处理这个消息
return 0;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
// “关于”框的消息处理程序。
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (message)
{
case WM_INITDIALOG:
return (INT_PTR)TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
{
EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}
下面是游戏的截图:
初始状态:
游戏状态:
游戏设计了好几关,每一关的砖块数量都不同。
一些比较关键的地方都有注释,这个游戏的基本框架可以在《Windows游戏编程大师技巧》之中找到。游戏的美工方面做的挺差的,不过逻辑基本实现了。其实为了更美观你可以添加背景。这样看起来就会更不错一些。
以上是关于Win32游戏制作之---FreakOut的主要内容,如果未能解决你的问题,请参考以下文章
在井字游戏 C++ 中使用“新游戏”按钮,win32 应用程序初学者