C++实现的贪吃蛇游戏
[ 来源:SOHO-IT论坛 点击数:218 作者:FishsoulStudio ]
win32版,真正原创.注意有三个文件.
SnakeWin32.cpp SnakeWin32.rc resource.h
命令行编译:
-----visual c++-----
rc SnakeWin32.rc
cl /MT /nologo SnakeWin32.cpp SnakeWin32.res gdi32.lib user32.lib
-----Gcc(Cygwin/Mingw32)-----
windres -i SnakeWin32.rc --input-format=rc -0 coff -o SnakeWin32.res
g++ SnakeWin32.cpp SnakeWin32.res -mwindows -lgdi32 -luser32 -o SnakeWin32 -s
/
----------SnakeWin32.cpp------
#include <iostream>
#include <vector>
#include <list>
#include <algorithm>
#include <functional>
#include <iterator>
#include <time.h>
#include <windows.h>
#include <windowsX.h>
#include "resource.h"
class point_t : public POINT
{
public:
point_t()
{
this->x = 0;
this->y = 0;
}
point_t(long x, long y)
{
this->x = x;
this->y = y;
}
};
inline bool operator==(point_t const& a, point_t const& b)
{
return a.x == b.x && a.y == b.y;
}
inline bool operator!=(point_t const& a, point_t const& b)
{
return !(a==b);
}
/*!生成横向连续的点
*/
class SequenceGen
{
point_t priv_;
public:
typedef point_t result_type;
SequenceGen(size_t width, size_t height)
{
priv_.x = static_cast<long>(width / 2);
priv_.y = static_cast<long>(height / 2);
}
point_t operator()()
{
priv_.x++;
return priv_;
}
};
struct Drawer
{
virtual void DrawBox(size_t w, size_t h) = 0;
virtual void DrawSnakeBody(size_t x, size_t y, size_t block) =0;
virtual void DrawSnakeFood(size_t x, size_t y, size_t block) =0;
};
/*! 贪吃的蛇
*/
template <typename Random>
class Snake //: public std::unary_function<point_t, bool>
{
public:
enum Direction { NO_CHANGE, UP, DOWN, LEFT, RIGHT};
typedef std::list<point_t> PointList;
typedef PointList::iterator PointListIter;
private:
PointList body_;
Direction direction_;
Direction newDirection_;
PointList foods_;
bool directionChanged_;
long width_;
long height_;
long step_;
public:
static size_t MAX_FOODS;
static size_t INITIAL_SIZE;
Snake(long width, long height, long step = 5)
: direction_(LEFT), newDirection_(NO_CHANGE), width_(width - 1), height_(height - 1), step_(step)
{
CreateFoods();
InitialSnake();
}
bool StepLeft()
{
return DirectTo(LEFT, direction_ == RIGHT);
}
bool StepRight()
{
return DirectTo(RIGHT, direction_ == LEFT);
}
bool StepUp()
{
return DirectTo(UP, direction_ == DOWN);
}
bool StepDown()
{
return DirectTo(DOWN, direction_ == UP);
}
bool DirectTo(Direction direct, bool go_back)
{
if(!go_back && newDirection_ == NO_CHANGE)
{
newDirection_ = direct;
return true;
}
return false;
}
point_t GetNextStep()
{
point_t nextStep = body_.front();
Direction direction =
(newDirection_ == NO_CHANGE) ? direction_ : newDirection_;
switch(direction)
{
case UP:
nextStep.x; nextStep.y--;
break;
case DOWN:
nextStep.x; nextStep.y++;
break;
case LEFT:
nextStep.x--; nextStep.y;
break;
case RIGHT:
nextStep.x++; nextStep.y;
break;
default:
//assert(!"Should not go here");
break;
}
direction_ = direction;
newDirection_ = NO_CHANGE;
return nextStep;
}
//heart of snake, call this every times!
bool Engine()
{
point_t thisPoint = GetNextStep();
if(IsDead(thisPoint))
{
return false;
}
if(NeedFeed(thisPoint))
{
//NOTE: feed means append one, so I did not call pop_back.
if(foods_.empty())
{
CreateFoods();
}
}
else
{
body_.pop_back();
}
//go ahead
body_.push_front(thisPoint);
return true;
}
void Draw(Drawer* myDrawer)
{
myDrawer->DrawBox(width_, height_);
for(PointListIter i = body_.begin(); i != body_.end(); i++)
{
myDrawer->DrawSnakeBody(i->x, i->y, step_);
}
for(PointListIter i = foods_.begin(); i != foods_.end(); i++)
{
myDrawer->DrawSnakeFood(i->x, i->y, step_);
}
}
int GetScore()
{
return body_.size();
}
protected:
void CreateFoods()
{
foods_.clear();
std::generate_n(std::back_inserter(foods_), MAX_FOODS, Random(width_, height_));
}
void InitialSnake()
{
body_.clear();
std::generate_n(std::back_inserter(body_), INITIAL_SIZE, SequenceGen(width_, height_));
direction_ = LEFT;
}
bool NeedFeed(point_t& thisPoint)
{
PointListIter p = std::find(foods_.begin(), foods_.end(), thisPoint);
if(p != foods_.end())
{
foods_.erase(p);
return true;
}
return false;
}
bool IsDead(point_t& point)
{
return (IsPointInBox(point) || IsPointOnBody(point));
}
bool IsPointInBox(point_t& point)
{
return point.x < 0 || point.y < 0 || point.x >= width_ || point.y >= height_;
}
bool IsPointOnBody(point_t& point)
{
//skip header
return find(++body_.begin(), body_.end(), point) != body_.end();
}
};
template <typename T> size_t Snake<T>::MAX_FOODS = 20;
template <typename T> size_t Snake<T>::INITIAL_SIZE = 13;
#define ID_TIMER 10001
BOOL ResizeClient(HWND hWnd, int nWidth, int nHeight, BOOL bRedraw = TRUE);
/*! 随机生成一个点
*/
class RandomPoint
{
public:
typedef point_t result_type;
private:
size_t width_, height_;
public:
RandomPoint(size_t width, size_t height) : width_(width), height_(height)
{
static bool seed = false;
if(!seed)
{
srand(static_cast<unsigned int>(time(0)));
seed = true;
}
}
point_t operator()()
{
point_t res;
res.x = static_cast<long>(Random() % width_);
res.y = static_cast<long>(Random() % height_);
return res;
}
protected:
int Random()
{
return rand();
}
};
class WindowGame : public Drawer, Snake<RandomPoint>
{
HWND hwnd_;
int iSelection_ ;
int width_, height_;
bool pause_;
HDC hdc_;
HBITMAP oldbmp_, hbmp_;
public:
explicit WindowGame(int w = 300, int h = 300)
: Snake<RandomPoint>(w / 5, h / 5), width_(w), height_(h), iSelection_(1), pause_(false)
{
}
void Create(HINSTANCE hInst)
{
static LPSTR szClass = "SnakeWndClass";
if(!InitialWindowClass(hInst, szClass, WindowGame::WindowProcedure))
{
return;
}
hwnd_ = CreateWindowEx(0, szClass, "Snake", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, width_, height_,
0, NULL, hInst, NULL );
ResizeClient(hwnd_, width_, height_ + 20, FALSE);
ShowWindow(hwnd_, SW_SHOW);
}
void MessageLoop()
{
MSG messages;
while (GetMessage(&messages, NULL, 0, 0))
{
DispatchMessage(&messages);
}
}
bool InitialWindowClass(HINSTANCE hInst, LPSTR szClassName, WNDPROC proc)
{
WNDCLASSEX wincl;
/* The Window structure */
wincl.hInstance = hInst;
wincl.lpszClassName = szClassName;
wincl.lpfnWndProc = proc; /* This function is called by windows */
wincl.style = 0;
wincl.cbSize = sizeof(WNDCLASSEX);
/* Use default icon and mouse-pointer */
wincl.hIcon = LoadIcon(hInst, (LPCSTR) IDI_ICON1);
wincl.hIconSm = LoadIcon(hInst, (LPCSTR) IDI_ICON1);
wincl.hCursor = LoadCursor(NULL, IDC_ARROW);
wincl.lpszMenuName = (LPCSTR) IDR_MENU1; /* Have a menu */
wincl.cbClsExtra = 0; /* No extra bytes after the window class */
wincl.cbWndExtra = 0; /* structure or the window instance */
/* Use Windows's default color as the background of the window */
wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND;
return RegisterClassEx(&wincl) != 0;
}
static WindowGame* Instance()
{
static WindowGame instance;
return &instance;
}
static LRESULT CALLBACK WindowProcedure(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
Instance()->hwnd_ = hwnd;
return Instance()->Proc(hwnd, msg, wparam, lparam);
}
virtual void DrawBox(size_t w, size_t h)
{
//background
SelectObject(hdc_, GetStockObject(LTGRAY_BRUSH));
Rectangle(hdc_, 0, 0, w * 5 + 2, h * 5 + 2);
}
virtual void DrawSnakeBody(size_t x, size_t y, size_t block)
{
//SelectObject(hdc_, GetStockObject(WHITE_BRUSH));
//Rectangle(hdc_, x * block - 1, y * block - 1, (x + 1) * block + 1, (y + 1) * block + 1);
RECT rc;
rc.left = x * block + 1;
rc.right = rc.left + block;
rc.top = y * block + 1;
rc.bottom = rc.top + block;
FillRect(hdc_, &rc, (HBRUSH)GetStockObject(WHITE_BRUSH));
}
virtual void DrawSnakeFood(size_t x, size_t y, size_t block)
{
SelectObject(hdc_, GetStockObject(GRAY_BRUSH));
Rectangle(hdc_, x * block + 1, y * block + 1, (x + 1) * block + 1, (y + 1) * block + 1);
}
LRESULT Proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg) /* handle the messages */
{
HANDLE_MSG (hwnd_, WM_COMMAND, Game_OnCommand);
HANDLE_MSG (hwnd_, WM_KEYDOWN, Game_onKeyDown);
HANDLE_MSG (hwnd_, WM_PAINT, Game_OnPaint);
HANDLE_MSG (hwnd_, WM_TIMER, Game_OnTimer);
HANDLE_MSG (hwnd_, WM_CREATE, Game_OnCreate);
HANDLE_MSG (hwnd_, WM_CLOSE, Game_OnClose);
case WM_SIZE:
width_ = LOWORD(lParam);
height_ = HIWORD(lParam);
break;
case WM_DESTROY:
KillTimer(hwnd_, ID_TIMER); //To kill the Timer let the window not accept timer any more.
PostQuitMessage(0); /* send a WM_QUIT to the message queue */
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return TRUE;
}
BOOL Game_OnCreate(HWND hwnd_, LPCREATESTRUCT)
{
InitialSnake();
SetSpeed(iSelection_);
HDC wndDC = ::GetWindowDC(hwnd_);
hdc_ = ::CreateCompatibleDC(wndDC);
hbmp_ = ::CreateCompatibleBitmap(wndDC, width_ - 3, height_- 3);
oldbmp_ = SelectBitmap(hdc_, hbmp_);
::ReleaseDC(hwnd_, wndDC);
return TRUE;
}
VOID Game_OnClose(HWND hwnd_)
{
DeleteBitmap(SelectBitmap(hdc_, oldbmp_));
DeleteDC(hdc_);
DestroyWindow(hwnd_);
}
void Game_OnCommand(HWND hwnd_, int id, HWND hwndCtl, UINT codeNotify)
{
switch (id)
{
case ID_Start:
InitialSnake();
SetSpeed(iSelection_);
break;
case ID_EXIT:
SendMessage(hwnd_, WM_DESTROY, 0, 0);
break;
case ID_SLOW:
SetSpeed(0);
break;
case ID_NORMAL:
SetSpeed(1);
break;
case ID_FAST:
SetSpeed(2);
break;
case ID_HOWTOPLAY:
MessageBox(hwnd_,
TEXT("Use UP, DOWN, LEFT, KEY to control the snake and tryn")
TEXT("to eat the small gray spot so the snake can grows. nn")
TEXT("Press SPACE key to pause or n")
TEXT("restart the game. "), "HELP", MB_ICONINFORMATION);
break;
case ID_about:
MessageBox(hwnd_, TEXT("Snake 1.0 test version!nn By: nyra@sohu.com(2005.4.15)"), "ABOUT", MB_ICONINFORMATION);
break;
case ID_PAUSE:
Pause();
break;
}
}
void Game_onKeyDown(HWND hwnd_, UINT vk, BOOL fDown, int cRepeat, UINT flags)
{
switch (vk)
{
case VK_LEFT:
this->StepLeft();
break;
case VK_RIGHT:
this->StepRight();
break;
case VK_UP:
this->StepUp();
break;
case VK_DOWN:
this->StepDown();
break;
case VK_SPACE:
this->Pause();
break;
}
InvalidateRect(hwnd_, 0, false);
}
void Game_OnPaint(HWND hwnd_)
{
Draw(this);
char buf[] = "Score: xxxxx";
sprintf(buf, "Score: %5d" , this->GetScore());
PAINTSTRUCT ps;
HDC dc = BeginPaint(hwnd_, &ps);
LPRECT lprc = &ps.rcPaint;
::BitBlt(dc, lprc->left, lprc->top,
lprc->right - lprc->left, lprc->bottom - lprc->top,
hdc_, lprc->left, lprc->top, SRCCOPY);
SetBkColor(dc, GetSysColor(COLOR_BTNFACE));
TextOut(dc, 0, width_, buf, sizeof(buf) - 1);
EndPaint(hwnd_, &ps);
}
void Game_OnTimer(HWND hwnd_, UINT id)
{
if(!Engine())
{
Pause();
MessageBox(hwnd_, "You lose", "Game", MB_OK);
InitialSnake();
Pause();
}
InvalidateRect(hwnd_, 0, false);
}
void SetSpeed(int idx)
{
HMENU hMenu = GetMenu(hwnd_);
const int speedmap[3] = { 250, 150, 70};
CheckMenuItem(hMenu, ID_SLOW + iSelection_, MF_UNCHECKED);
iSelection_ = idx;
CheckMenuItem(hMenu, ID_SLOW + iSelection_, MF_CHECKED);
KillTimer(hwnd_, ID_TIMER);
SetTimer(hwnd_, ID_TIMER, speedmap[idx], NULL);
}
void Pause()
{
if (pause_)
{
pause_ = false;
SetSpeed(iSelection_);
}
else
{
pause_ = true;
KillTimer(hwnd_, ID_TIMER);
}
}
};
BOOL ResizeClient(HWND hWnd, int nWidth, int nHeight, BOOL bRedraw /*=TRUE*/)
{
RECT rcWnd;
if(!GetClientRect(hWnd, &rcWnd))
{
return FALSE;
}
if(nWidth != -1)
{
rcWnd.right = nWidth;
}
if(nHeight != -1)
{
rcWnd.bottom = nHeight;
}
LONG style = ::GetWindowLong(hWnd, GWL_STYLE);
LONG exStyle = ::GetWindowLong(hWnd, GWL_EXSTYLE);
BOOL hasMenu = !(style & WS_CHILD) && (::GetMenu(hWnd) != NULL);
if(!::AdjustWindowRectEx(&rcWnd, style, hasMenu, exStyle))
return FALSE;
UINT uFlags = SWP_NOZORDER | SWP_NOMOVE;
if(!bRedraw)
{
uFlags |= SWP_NOREDRAW;
}
return SetWindowPos(hWnd, NULL, 0, 0, rcWnd.right - rcWnd.left, rcWnd.bottom - rcWnd.top, uFlags);
}
//int _tmain(int argc, _TCHAR* argv[])
INT WINAPI WinMain(IN HINSTANCE hInstance, IN HINSTANCE hPrevInstance, IN LPSTR lpCmdLine, IN int nShowCmd)
{
WindowGame* game = WindowGame::Instance();
game->Create(hInstance);
game->MessageLoop();
return 0;
}
---------SnakeWin32.rc-----------
#include "resource.h"
/
//
// Menu
//
IDR_MENU1 MENU
BEGIN
POPUP "&Game"
BEGIN
MENUITEM "&Resart", ID_Start
MENUITEM "&Pause", 40019
MENUITEM SEPARATOR
MENUITEM "&Exit", ID_EXIT
END
POPUP "&Record", GRAYED
BEGIN
MENUITEM "&Show...", ID_SHOW
MENUITEM "&Clear", ID_CLEAR
END
POPUP "&Speed"
BEGIN
MENUITEM "S&low", ID_SLOW
MENUITEM "&Normal", ID_NORMAL, CHECKED
MENUITEM "&Fast", ID_FAST
END
POPUP "&Help"
BEGIN
MENUITEM "How to play...", ID_HOWTOPLAY
MENUITEM SEPARATOR
MENUITEM "About Snake...", ID_ABOUT
END
END
-----------resource.h--------------
#define IDR_MENU1 101
#define IDI_ICON1 102
#define ID_Start 40001
#define ID_EXIT 40002
#define ID_SLOW 40004
#define ID_NORMAL 40005
#define ID_FAST 40006
#define ID_SHOW 40007
#define ID_CLEAR 40008
#define ID_TIPS 40009
#define ID_ABOUT 40010
#define ID_NONE 40012
#define ID_FEW 40013
#define ID_MEDIUM 40014
#define ID_LOT 40015
#define ID_MAPLOAD 40017
#define ID_HOWTOPLAY 40018
#define ID_PAUSE 40019