目录
实现目的
游戏玩法介绍
实现流程与作品架构
任务列表及贡献度
总结感想
作品源码与仓库地址(附页)
资料引用与出处(附页)
实现目的
2048,作为一款极其经典的游戏,从发行到现在,已经有了极多的版本和玩法,各种优秀应用游戏层出不穷。
本游戏注重"视觉展现效果和更快速的动画"、注重数据储存,是一种对于算法和 UI 设计有一定要求的应用设计。
本小组秉承课程所学与外部优秀知识技术相结合的观念,多人合作,共同研究学习。
以 C
语言为基础、C++
语言库函数为辅助、Windows
程式应用编程和状态机思想为框架、搜索和多线程运行等算法知识为技术核心。
游戏玩法介绍
- 选择上下左右任何一个方向去滑动
- 每滑动一次,所有的数字方块都会往滑动的方向靠拢外
- 系统在滑动之后会在空白的地方随机出现一个数字方块(一般是2或4)
- 相同数字的方块会相加成一个数字
- 不断的叠加最终拼凑出2048这个数字就算成功。
实现流程与作品架构
- 使用
Windows
窗口编程,创建窗口 - 在消息循环中创建
Game
程序实现主体,使用C++
语言中new
创建Game
类,用于游戏的初始化 -
Game
类包含以下内容- 窗口创建时的一些属性
- 游戏初始化部分
- 游戏运行时四个方向的移动函数
- 移动判断函数
- 图片的绘制函数
- 定时器的程序运行
- 游戏移动消息循环接受和对应操作的实现
- 在不断进行的消息循环中,进行绘图的操作(不停的擦除和重绘)
任务列表及贡献度
任务内容 | 任务说明 | 任务贡献度 | 任务截至日期 |
---|---|---|---|
游戏进程 | 游戏主体进程的详细讨论,包括详细的子项 | 5 | 2019.5.12 |
美术大纲 | 讨论游戏整体美术风格和单块素材美术风格 | 5 | 2019.5.12 |
素材收集及制作 | 按照美术大纲,收集或制作全部要求的素材 | 30 | 2019.5.25 |
状态机实现 | 根据游戏进程,实现个状态之间的无素材转换 | 15 | 2019.5.15 |
Windows子窗口控件实现 | 根据游戏进程、美术大纲,实现个状态之间无素材子窗口内容 | 20 | 2019.5.20 |
游戏框架制作 | 收集前期全部工作成果,写出游戏框架 | 25 | 2019.6.1 |
游戏实例代码编写 | 根据游戏框架编写全部所需子代码 | 10/子代码 | 2019.6.5 |
游戏总制作 | 收集全部代码,写出总程序 | 15 | 2019.6.7 |
制作完成项目总结及成果整理 | 项目总结 | 3 | 2019.6.9 |
项目报告 | 用于C语言课程设计和应用程序设计比赛 | 5 | 2019.6.10 |
总结感想
JingWenxing's
这次课程设计,是编程生涯的第一次多人合作。虽然之前对于多人合作有一定的感想和思考,但是实践出真知,我还是发现了很多问题。
作为一个多人合作的队伍,首先需要的就是任务的细化和代码沟通的通常。这点我们并没有做的很好。任务一直没有很好的定下来,并且个人任务混杂,互相干涉,效率不高。代码沟通上之前并没有使用“码云”,导致每次都要自行寻找各版本中的不同之处。
除此之外就是任务逾期的问题。几乎所有的项目,都没有在规定的时间内完成,都有多多少少的拖沓情况。这在多人合作中是致命的。这也是今后需要极为注意的地方
自身的知识水平和技术也是一大问题。“现学现用”在本次合作中可以说是贯穿全场。不论是C++、Windows,还是EasyX,状态机。虽然学习了新的东西收获慢慢,但是仍然发现了自己技术上的不足。
暑假里我将完成以下的学习,继续深造:
学习任务 | 学习任务 | 学习任务 |
---|---|---|
计算机网络 | Linux | DirectX 3D 11 |
Google test | Java | C / C++ |
算法 | 英语 | 数学 |
Crush‘s
在这次以小组为单位的课程设计中,我觉得有收获也有所欠缺,课程设计本身就是一个问题,需要我们小组成员一起去解决的问题,我们大家给出自己的想法和思路怎么去把这个课程设计完美的做出来,因此每个人可能需要分配一些各自的任务,然后各自去完成自己负责的部分,起初我认为这样是比较合理的。
但是在完成各自任务的过程中,我觉得还是需要大家一起去讨论才能够选出最佳方案,比如游戏的美工和界面,就需要征集大家的意见,才能确定一个适合大众口味的风格。
确定好之后再分配到个人去搜集素材并整理好,这个过程中,需要用到一些相关软件,如ps,但我们对ps的操作还是有很多不会的地方,所以还需要加强这方面的学习。
还有就是大家的积极性可能会因为无法实现自己想要表达的效果而减退,很多时候无法在规定的时间内完成任务,导致进度过慢,这点在今后的小组课程设计中一定要改正,小组成员要时刻保持绝对的积极性,面对困难不退缩,迎难而上,认真完成课程设计任务。
LILI’s
这次的课程设计是我学习专业以来,自己的第一次实际操作,在这次小组课程设计中,我学到了许多东西,也发现了许多问题,和自己的许多不足之处。
在这次设计中,我发觉最大的问题就是我们对游戏的设计并不坚定,总是一次又一次的推翻自己之前的想法。在知道要课程设计一个游戏时,首先我们对游戏的主题结构进行了构思,在第一次游戏构思时,队长和我通过天马行空的想象,将一个完整的故事线构思好了,再将其细节也进行了细化,但在实际操作的过程中,发现游戏所需素材太多,操作困难,然后迅速放弃,一切重头再来。在这期间我们浪费了许多的时间。
在游戏设计的过程中,发现自己不懂的实在是太多,所以我也在一直不断的学习(因为我就是菜鸡本菜),除了对c的知识更加深入的学习以外,我还学习了c++类等知识。虽然我还是有很多的不足,但会继续努力。
作品源码与仓库地址
宏与头文件集成
// framework.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#define WIN32_LEAN_AND_MEAN // 从 Windows 头文件中排除极少使用的内容
#define COUTENDL(_text_) do \
{ \
std::cout << _text_ << std::endl; \
} while (false) \
#define COUT(_text_) do \
{ \
std::cout << _text_ ; \
} while (false) \
#include <SDKDDKVer.h>
// Windows 头文件
#include <windows.h>
// C 运行时头文件
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <tchar.h>
#include <time.h>
// TODO: 在此处引用程序需要的其他头文件
#include "Utils.h"
#include "Game.h"
// Resource.h
#define IDC_MYICON 2
#define IDD_MY2048_DIALOG 102
#define IDD_MY2048_INFO 102
#define IDS_APP_TITLE 103
#define IDD_ABOUTBOX 103
#define IDM_ABOUT 104
#define IDM_EXIT 105
#define IDI_MY2048 107
#define IDI_SMALL 108
#define IDC_MY2048 109
#define IDR_MAINFRAME 128
#define IDQUIT 1000
#define ID_sadf 1000
#define IDC_STATIC -1
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NO_MFC 1
#define _APS_NEXT_RESOURCE_VALUE 131
#define _APS_NEXT_COMMAND_VALUE 32771
#define _APS_NEXT_CONTROL_VALUE 1002
#define _APS_NEXT_SYMED_VALUE 110
#endif
#endif
// targetver.h
#include <SDKDDKVer.h>
2048 Game头与其源文件
// 2048 Game.h
#pragma once
#include "resource.h"
// 2048 Game.cpp
// 2048 Game.cpp : 定义应用程序的入口点。
//
#include "framework.h"
#include "2048 Game.h"
#include <cstdio>
#define MAX_LOADSTRING 100
#define MAX_TIME 15 * 60 * 100
// 全局变量:
HINSTANCE hInst; // 当前实例
TCHAR szTitle[MAX_LOADSTRING]; // 标题栏文本
TCHAR szWindowClass[MAX_LOADSTRING]; // 主窗口类名
Game* game; // 游戏主程序
UINT timerID; // 定时器ID
UINT times; // 倒计时
// 此代码模块中包含的函数的前向声明:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK Info(HWND, UINT, WPARAM, LPARAM);
VOID CALLBACK TimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime);
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;
// 初始化全局字符串
LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadString(hInstance, IDC_MY2048, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance); // 注册窗口类
执行应用程序初始化:
if (!InitInstance(hInstance, nCmdShow)) // 保存实例句柄并创建主窗口
{
return FALSE;
}
hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_MY2048));
// 主消息循环:
while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int)msg.wParam;
}
//
// 函数: MyRegisterClass()
//
// 目的: 注册窗口类。
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_MY2048));
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
// wcex.lpszMenuName = MAKEINTRESOURCE(IDC_MY2048);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
return RegisterClassEx(&wcex);
}
//
// 函数: InitInstance(HINSTANCE, int)
//
// 目的: 保存实例句柄并创建主窗口
//
// 注释:
//
// 在此函数中,我们在全局变量中保存实例句柄并
// 创建和显示主程序窗口。
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
HWND hWnd;
hInst = hInstance; // 将实例句柄存储在全局变量中
hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
CW_USEDEFAULT, 0, 450, 600, NULL, NULL, hInstance, NULL);
if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
//
// 函数: 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_CREATE:
// Init game
game = new Game();
// Init time
times = MAX_TIME;
// Set timer
timerID = SetTimer(hWnd, NULL, 1000, TimerProc);
break;
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_KEYDOWN:
switch (wParam)
{
case 'A':
case 'H':
case VK_LEFT:
if (!game->canMove(MoveCommandLeft)) break;
game->doMove(MoveCommandLeft);
InvalidateRect(hWnd, &game->chessboardRect, true);
InvalidateRect(hWnd, &game->scoreLabelRect, false);
break;
case 'D':
case 'L':
case VK_RIGHT:
if (!game->canMove(MoveCommandRight)) break;
game->doMove(MoveCommandRight);
InvalidateRect(hWnd, &game->chessboardRect, true);
InvalidateRect(hWnd, &game->scoreLabelRect, false);;
break;
case 'W':
case 'K':
case VK_UP:
if (!game->canMove(MoveCommandUp)) break;
game->doMove(MoveCommandUp);
InvalidateRect(hWnd, &game->chessboardRect, true);
InvalidateRect(hWnd, &game->scoreLabelRect, false);
break;
case 'S':
case 'J':
case VK_DOWN:
if (!game->canMove(MoveCommandDown)) break;
game->doMove(MoveCommandDown);
InvalidateRect(hWnd, &game->chessboardRect, true);
InvalidateRect(hWnd, &game->scoreLabelRect, false);
break;
case 'R':
if (game->isTerminated()) game->restart();
break;
default:
break;
}
if (game->isTerminated()) {
KillTimer(hWnd, timerID); // Kill timer
DialogBox(hInst, MAKEINTRESOURCE(IDD_MY2048_DIALOG), hWnd, Info); // Game over or win
}
break;
case WM_PAINT: {
hdc = BeginPaint(hWnd, &ps);
// TODO: 在此添加任意绘图代码...
// Get client rect
RECT rect;
GetClientRect(hWnd, &rect);
game->setRect(rect);
game->draw(ps.hdc);
// Draw time
RECT timeRect = game->scoreLabelRect;
long width = getRectWidth(timeRect);
timeRect.left -= width;
timeRect.right -= width;
// Draw time
WCHAR buffer[10];
getTimeStringByValue(times, buffer);
drawNumberOnTimeLabel(ps.hdc, timeRect, buffer, RGB(249, 246, 242));
EndPaint(hWnd, &ps);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
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;
}
// “提醒”框的消息处理程序。
INT_PTR CALLBACK Info(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (message)
{
case WM_INITDIALOG: {
HWND stDisplay = GetDlgItem(hDlg, IDC_STATIC);
wchar_t buffer[256];
if (game->isWon()) {
wsprintfW(buffer, L"You win!!!\nYour score: %d", game->getScore());
}
else {
if (times == 0) {
wsprintfW(buffer, L"Time is over! You lose :(\nYour score: %d", game->getScore());
}
else {
wsprintfW(buffer, L"You lose :(\nYour score: %d", game->getScore());
}
}
SendMessage(stDisplay, WM_SETTEXT, NULL, (WPARAM)buffer);
}
return (INT_PTR)TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDRETRY) {
HWND hParentWnd = GetParent(hDlg);
times = MAX_TIME;
timerID = SetTimer(hParentWnd, NULL, 1000, TimerProc); // Reset timer
// Restart the game
game->restart(); // Restart
EndDialog(hDlg, LOWORD(wParam)); // Close dialog
InvalidateRect(hParentWnd, &game->chessboardRect, true); // Repaint chessboard
InvalidateRect(hParentWnd, &game->scoreLabelRect, false); // Repaint score label
RECT timeRect = game->scoreLabelRect;
long width = getRectWidth(timeRect);
timeRect.left -= width;
timeRect.right -= width;
InvalidateRect(hParentWnd, &timeRect, false); // Repatin timer
return (INT_PTR)TRUE;
}
else if (LOWORD(wParam) == IDNO) {
HWND hParentWnd = GetParent(hDlg);
EndDialog(hDlg, LOWORD(wParam));
DestroyWindow(hParentWnd); // Close main window, exit
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}
VOID CALLBACK TimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime) {
if (game->isTerminated()) return;
if (times == 0) {
game->stop(); // Stop game
return;
}
times -= 100;
RECT timeRect = game->scoreLabelRect;
long width = getRectWidth(timeRect);
timeRect.left -= width;
timeRect.right -= width;
InvalidateRect(hwnd, &timeRect, false);
}
Game头与其源文件
// Game.h
#pragma once
#ifndef ___048__Game__
#define ___048__Game__
#include "framework.h"
typedef enum {
MoveCommandUp,
MoveCommandDown,
MoveCommandLeft,
MoveCommandRight
} MoveCommand;
class Game {
private:
int score;
public:
int getScore();
private:
bool over;
bool won;
int numberOfEmptyCells;
static const int size = 4;
static const int numberOfStartCells = 2;
long chessboardWidth;
int chessboard[4][4];
public:
RECT chessboardRect;
RECT scoreLabelRect;
private:
RECT topBarRect;
private:
void initChessboard();
void initStartCells();
void addRandomCell();
int getRandomAvailableCell() const;
void clearCell(const int& row, const int& col);
void getCellColor(const int& value, LPCOLORREF fontColor, LPCOLORREF cellBgColor) const;
#pragma mark Can Move?
public:
bool canMove() const;
bool canMove(const MoveCommand cmd) const;
private:
bool canMoveUp() const;
bool canMoveDown() const;
bool canMoveLeft() const;
bool canMoveRight() const;
#pragma mark -
#pragma mark Constructor & Destructor
public:
Game();
Game(RECT clientRect);
~Game();
#pragma mark -
#pragma mark Actions
public:
void doMove(const MoveCommand cmd);
private:
void doUp();
void doDown();
void doLeft();
void doRight();
public:
void restart();
void stop();
#pragma mark -
#pragma mark View
public:
void print() const;
void printHelpInfo() const;
void draw(HDC hdc) const;
private:
void drawTopBar(HDC hdc) const;
void drawLogoText(HDC hdc) const;
void drawScoreLabel(HDC hdc) const;
void drawChessboard(HDC hdc) const;
void drawCell(HDC hdc, const int& row, const int& col, const int& value) const;
public:
void setRect(const RECT clientRect);
#pragma mark -
#pragma mark Is over or won or terminated?
public:
bool isOver() const;
bool isWon() const;
bool isTerminated() const;
};
#endif /* defined(___048__Game__) */
// Game.cpp
#include "framework.h"
#include <iostream>
#include <cstdio>
#include <ctime>
int Game::getScore() {
return this->score;
}
void Game::initChessboard() {
this->score = 0;
this->over = false;
this->won = false;
this->numberOfEmptyCells = 16;
// Init chessboard
memset(this->chessboard, 0, 4 * 4 * sizeof(int));
// Add the initial cells
this->initStartCells();
// Print
this->print();
}
void Game::initStartCells() {
for (int i = 0; i < this->numberOfStartCells; i++) {
this->addRandomCell();
}
}
void Game::addRandomCell() {
int random = rand();
int value = ((double)random / RAND_MAX) < 0.9 ? 2 : 4;
int index = this->getRandomAvailableCell();
this->chessboard[index / this->size][index % this->size] = value;
this->numberOfEmptyCells--;
}
int Game::getRandomAvailableCell() const {
while (true) {
int random = rand() % (this->size * this->size);
int row = random / this->size;
int col = random % this->size;
if (this->chessboard[row][col] == 0)
return random;
}
return -1;
}
void Game::clearCell(const int& row, const int& col) {
this->chessboard[row][col] = 0;
this->numberOfEmptyCells++;
}
void Game::getCellColor(const int& value, LPCOLORREF pFontColor, LPCOLORREF pCellBgColor) const {
switch (value) {
case 0: {
int bgR = 187, bgG = 173, bgB = 160;
int R = 238, G = 228, B = 218;
int alpha = 90;
R = R * alpha / 255 + bgR * (255 - alpha) / 255;
G = G * alpha / 255 + bgG * (255 - alpha) / 255;
B = B * alpha / 255 + bgB * (255 - alpha) / 255;
*pCellBgColor = RGB(R, G, B);
}
break;
case 2:
*pFontColor = RGB(119, 110, 101);
*pCellBgColor = RGB(238, 228, 218);
break;
case 4:
*pFontColor = RGB(119, 110, 101);
*pCellBgColor = RGB(237, 224, 200);
break;
case 8:
*pFontColor = RGB(249, 246, 242);
*pCellBgColor = RGB(242, 177, 121);
break;
case 16:
*pFontColor = RGB(249, 246, 242);
*pCellBgColor = RGB(245, 149, 99);
break;
case 32:
*pFontColor = RGB(249, 246, 242);
*pCellBgColor = RGB(246, 124, 95);
break;
case 64:
*pFontColor = RGB(249, 246, 242);
*pCellBgColor = RGB(246, 94, 59);
break;
case 128:
*pFontColor = RGB(249, 246, 242);
*pCellBgColor = RGB(237, 207, 114);
break;
case 256:
*pFontColor = RGB(249, 246, 242);
*pCellBgColor = RGB(237, 204, 97);
break;
case 512:
*pFontColor = RGB(249, 246, 242);
*pCellBgColor = RGB(237, 200, 80);
break;
case 1024:
*pFontColor = RGB(249, 246, 242);
*pCellBgColor = RGB(237, 197, 63);
break;
case 2048:
*pFontColor = RGB(249, 246, 242);
*pCellBgColor = RGB(237, 194, 46);
break;
default:
*pFontColor = RGB(249, 246, 242);
*pCellBgColor = RGB(60, 58, 50);
break;
}
}
#pragma mark -
#pragma mark Can Move?
bool Game::canMove() const {
return this->canMoveUp() || this->canMoveDown() || this->canMoveLeft() || this->canMoveRight();
}
bool Game::canMove(const MoveCommand cmd) const {
switch (cmd) {
case MoveCommandLeft:
return this->canMoveLeft();
break;
case MoveCommandUp:
return this->canMoveUp();
break;
case MoveCommandRight:
return this->canMoveRight();
break;
case MoveCommandDown:
return this->canMoveDown();
break;
default:
break;
}
return false;
}
bool Game::canMoveUp() const {
for (int j = 0; j < this->size; j++) {
bool result = false;
for (int i = 0; i < this->size; i++) {
if (this->chessboard[i][j] == 0)
result = true;
else if ((i != 0 && this->chessboard[i][j] == this->chessboard[i - 1][j]) || result)
return true;
}
}
return false;
}
bool Game::canMoveDown() const {
for (int j = 0; j < this->size; j++) {
bool result = false;
for (int i = this->size - 1; i >= 0; i--) {
if (this->chessboard[i][j] == 0)
result = true;
else if ((i != this->size - 1 && this->chessboard[i][j] == this->chessboard[i + 1][j]) || (result))
return true;
}
}
return false;
}
bool Game::canMoveLeft() const {
for (int i = 0; i < this->size; i++) {
bool result = false;
for (int j = 0; j < this->size; j++) {
if (this->chessboard[i][j] == 0)
result = true;
else if ((j != 0 && this->chessboard[i][j] == this->chessboard[i][j - 1]) || (result))
return true;
}
}
return false;
}
bool Game::canMoveRight() const {
for (int i = 0; i < this->size; i++) {
bool result = false;
for (int j = this->size - 1; j >= 0; j--) {
if (this->chessboard[i][j] == 0)
result = true;
else if ((j != this->size - 1 && this->chessboard[i][j] == this->chessboard[i][j + 1]) || (result))
return true;
}
}
return false;
}
#pragma mark -
#pragma mark Constructor & Destructor
Game::Game() {
this->printHelpInfo(); // Help info
srand((unsigned)time(0));
this->initChessboard(); // Init chessboard
}
Game::Game(RECT clientRect) {
srand((unsigned)time(0));
this->initChessboard(); // Init chessboard
this->setRect(clientRect); // Set chessboard rect
}
Game::~Game() {
}
#pragma mark -
#pragma mark Actions
void Game::doMove(const MoveCommand cmd) {
// if (!this->canMove(cmd)) return;
switch (cmd) {
case MoveCommandLeft:
this->doLeft();
break;
case MoveCommandUp:
this->doUp();
break;
case MoveCommandRight:
this->doRight();
break;
case MoveCommandDown:
this->doDown();
break;
default:
break;
}
std::cout << (cmd == MoveCommandUp ? "Move Up" : (cmd == MoveCommandDown ? "Move Down" : (cmd == MoveCommandLeft ? "Move Left" : "Move Right"))) << std::endl;
this->addRandomCell();
if (this->numberOfEmptyCells == 0 && !this->canMove()) this->over = true; // Game over
this->print();
}
void Game::doUp() {
for (int j = 0; j < this->size; j++) {
int l = 0, m = -1, n = -1;
for (int i = 0; i < this->size; i++) {
if (this->chessboard[i][j] != 0) {
if (m == -1) {
m = i;
}
else {
if (n == -1) {
n = i;
if (this->chessboard[m][j] == this->chessboard[n][j]) {
// Merge
this->chessboard[l][j] = this->chessboard[m][j] * 2;
if (this->chessboard[l][j] == 2048) this->won = true; // Win!!!
if (m != l) {
this->numberOfEmptyCells--;
this->clearCell(m, j);
}
this->clearCell(n, j);
// Add score
this->score += this->chessboard[l][j];
m = -1;
}
else {
// Move
if (m != l) {
this->chessboard[l][j] = this->chessboard[m][j];
this->numberOfEmptyCells--;
this->clearCell(m, j);
}
m = n;
}
l++;
n = -1;
}
}
}
}
if (m != -1 && m != l) {
this->chessboard[l][j] = this->chessboard[m][j];
this->numberOfEmptyCells--;
this->clearCell(m, j);
}
}
}
void Game::doDown() {
for (int j = 0; j < this->size; j++) {
int l = this->size - 1, m = -1, n = -1;
for (int i = this->size - 1; i >= 0; i--) {
if (this->chessboard[i][j] != 0) {
if (m == -1) {
m = i;
}
else {
if (n == -1) {
n = i;
if (this->chessboard[m][j] == this->chessboard[n][j]) {
// Merge
this->chessboard[l][j] = this->chessboard[m][j] * 2;
if (this->chessboard[l][j] == 2048) this->won = true; // Win!!!
if (m != l) {
this->numberOfEmptyCells--;
this->clearCell(m, j);
}
this->clearCell(n, j);
// Add score
this->score += this->chessboard[l][j];
m = -1;
}
else {
// Move
if (m != l) {
this->chessboard[l][j] = this->chessboard[m][j];
this->numberOfEmptyCells--;
this->clearCell(m, j);
}
m = n;
}
l--;
n = -1;
}
}
}
}
if (m != -1 && m != l) {
this->chessboard[l][j] = this->chessboard[m][j];
this->numberOfEmptyCells--;
this->clearCell(m, j);
}
}
}
void Game::doLeft() {
for (int i = 0; i < this->size; i++) {
int l = 0, m = -1, n = -1;
for (int j = 0; j < this->size; j++) {
if (this->chessboard[i][j] != 0) {
if (m == -1) {
m = j;
}
else {
if (n == -1) {
n = j;
if (this->chessboard[i][m] == this->chessboard[i][n]) {
// Merge
this->chessboard[i][l] = this->chessboard[i][m] * 2;
if (this->chessboard[i][l] == 2048) this->won = true; // Win!!!
if (m != l) {
this->numberOfEmptyCells--;
this->clearCell(i, m);
}
this->clearCell(i, n);
// Add score
this->score += this->chessboard[i][l];
m = -1;
}
else {
// Move
if (m != l) {
this->chessboard[i][l] = this->chessboard[i][m];
this->numberOfEmptyCells--;
this->clearCell(i, m);
}
m = n;
}
l++;
n = -1;
}
}
}
}
if (m != -1 && m != l) {
this->chessboard[i][l] = this->chessboard[i][m];
this->numberOfEmptyCells--;
this->clearCell(i, m);
}
}
}
void Game::doRight() {
for (int i = 0; i < this->size; i++) {
int l = this->size - 1, m = -1, n = -1;
for (int j = this->size - 1; j >= 0; j--) {
if (this->chessboard[i][j] != 0) {
if (m == -1) {
m = j;
}
else {
if (n == -1) {
n = j;
if (this->chessboard[i][m] == this->chessboard[i][n]) {
// Merge
this->chessboard[i][l] = this->chessboard[i][m] * 2;
if (this->chessboard[i][l] == 2048) this->won = true; // Win!!!
if (m != l) {
this->numberOfEmptyCells--;
this->clearCell(i, m);
}
this->clearCell(i, n);
// Add score
this->score += this->chessboard[i][l];
m = -1;
}
else {
// Move
if (m != l) {
this->chessboard[i][l] = this->chessboard[i][m];
this->numberOfEmptyCells--;
this->clearCell(i, m);
}
m = n;
}
l--;
n = -1;
}
}
}
}
if (m != -1 && m != l) {
this->chessboard[i][l] = this->chessboard[i][m];
this->numberOfEmptyCells--;
this->clearCell(i, m);
}
}
}
void Game::restart() {
this->initChessboard(); // Init chessboard
}
void Game::stop() {
this->over = true;
}
#pragma mark -
#pragma mark Is over or won?
bool Game::isOver() const {
return this->over;
}
bool Game::isWon() const {
return this->won;
}
bool Game::isTerminated() const {
return this->over || this->won;
}
#pragma mark -
#pragma mark View
void Game::print() const {
std::cout << "================================================================" << std::endl;
std::cout << "Score: " << this->score << std::endl;
std::cout << "Empty: " << this->numberOfEmptyCells << std::endl;
if (this->isTerminated()) {
if (this->won)
std::cout << "YOU WIN!!!" << std::endl;
else if (this->over)
std::cout << "YOU LOSE!!!" << std::endl;
}
for (int i = 0; i < this->size; i++) {
for (int j = 0; j < this->size; j++) {
printf("%4d ", this->chessboard[i][j]);
}
std::cout << std::endl;
}
std::cout << "================================================================" << std::endl << std::endl;
}
void Game::printHelpInfo() const {
// 2048
std::cout << std::endl;
COUTENDL(" #### #### ## ###### ");
COUTENDL(" ########## ######## ## ### ### ");
COUTENDL(" ##### ##### ### ### ## ### ### ### ");
COUTENDL(" ### #### ### ### ## ### ### ### ");
COUTENDL(" #### ### ### ## ### ### ### ");
COUTENDL(" #### ### ### ## ### ### ### ");
COUTENDL(" #### ### ### ## ### ###### ");
COUTENDL(" #### ### ### ## ### #### #### ");
COUTENDL(" #### ### ### ### ### ### ### ");
COUTENDL(" #### ### ### ############## ### ### ");
COUTENDL(" #### ### ### ############## ### ### ");
COUTENDL(" #### ### ### ### ### ### ");
COUTENDL(" #### ### ### ### ### ### ");
COUTENDL(" ############## ######## ### ### ### ");
COUTENDL(" ############## #### ### ###### ");
std::cout << std::endl;
// Help
COUTENDL("Control:");
COUTENDL("WASD: A - Left, S - Down, W - Up, D - Right");
COUTENDL("Vim: H - Left, J - Down, K - Up, L - Right");
std::cout << std::endl;
}
void Game::draw(HDC hdc) const {
this->drawChessboard(hdc); // Draw chessboard grid
// Draw cells
for (int i = 0; i < this->size * this->size; i++) {
int row = i / this->size;
int col = i % this->size;
// if (this->chessboard[row][col] == 0) continue;
this->drawCell(hdc, row, col, this->chessboard[row][col]);
}
}
void Game::drawTopBar(HDC hdc) const {
// Draw 2048
this->drawLogoText(hdc);
// Draw score label
this->drawScoreLabel(hdc);
// Draw time rect bg
RECT timeRect = this->scoreLabelRect;
long width = getRectWidth(timeRect);
timeRect.left -= width;
timeRect.right -= width;
// Draw bg round rect
drawRoundRect(hdc, timeRect, RGB(187, 173, 160));
}
void Game::drawLogoText(HDC hdc) const {
RECT logoRect = this->topBarRect;
logoRect.right = (logoRect.left + logoRect.right) / 2;
drawNumberOnRect(hdc, logoRect, 2048, RGB(119, 110, 101));
}
void Game::drawScoreLabel(HDC hdc) const {
RECT scoreLabelRect = this->scoreLabelRect;
// Draw bg round rect
drawRoundRect(hdc, this->scoreLabelRect, RGB(187, 173, 160));
// Draw 'SCORE' text
long scoreLabelHeight = getRectHeight(scoreLabelRect);
scoreLabelRect.bottom -= (scoreLabelHeight * 0.7);
HFONT font = createFont(getRectHeight(scoreLabelRect) * 0.66);
SetTextColor(hdc, 0xeee4da); // Font color
SetBkMode(hdc, TRANSPARENT); // Background color transparentk
SelectObject(hdc, font); // Set font
DrawText(hdc, TEXT("SCORE"), -1, &scoreLabelRect, DT_CENTER | DT_SINGLELINE | DT_VCENTER);
DeleteObject(font);
// Draw score
scoreLabelRect.top += (scoreLabelHeight * 0.3);
scoreLabelRect.bottom += (scoreLabelHeight * 0.7);
drawNumberOnRect(hdc, scoreLabelRect, this->score, RGB(249, 246, 242));
}
void Game::drawChessboard(HDC hdc) const {
// Draw top bar
this->drawTopBar(hdc);
// Draw chessboard bg
drawRoundRect(hdc, this->chessboardRect, RGB(187, 173, 160));
}
void Game::drawCell(HDC hdc, const int& row, const int& col, const int& value) const {
long spacing = this->chessboardWidth / 4;
long cellWidth = spacing * 0.8;
long cellMargin = (spacing - cellWidth) / 2;
POINT cellTopLeftPoint = { cellMargin + this->chessboardRect.left + col * spacing, cellMargin + this->chessboardRect.top + row * spacing };
RECT cellRect = { cellTopLeftPoint.x, cellTopLeftPoint.y, cellTopLeftPoint.x + cellWidth, cellTopLeftPoint.y + cellWidth };
COLORREF bgColor;
COLORREF fontColor;
this->getCellColor(value, &fontColor, &bgColor);
// Draw cell bg
drawRoundRect(hdc, cellRect, bgColor);
if (0 == value) return;
// Draw cell number
drawNumberOnCell(hdc, cellRect, value, fontColor);
}
void Game::setRect(const RECT clientRect) {
long clientWidth = getRectWidth(clientRect);
long clientHeight = getRectHeight(clientRect);
long gameWidth, gameHeight;
if (clientWidth < clientHeight) {
gameHeight = min(clientWidth / 3 * 4, clientHeight);
gameWidth = gameHeight / 4 * 3;
}
else {
gameHeight = clientHeight;
gameWidth = gameHeight / 4 * 3;
}
this->chessboardWidth = gameWidth;
long topBarHeight = gameHeight - gameWidth;
long marginTop = (clientHeight - gameHeight) / 2;
long marginLeft = (clientWidth - gameWidth) / 2;
// Top bar rect
this->topBarRect = { marginLeft, marginTop, marginLeft + gameWidth, marginTop + topBarHeight };
// Score label rect
this->scoreLabelRect = this->topBarRect;
this->scoreLabelRect.left += (gameWidth * 3 / 4);
this->scoreLabelRect.bottom -= (getRectHeight(this->scoreLabelRect) / 2);
this->scoreLabelRect.top += (topBarHeight * 0.1);
this->scoreLabelRect.bottom += (topBarHeight * 0.1);
// Chessboard rect
this->chessboardRect = { marginLeft, marginTop + topBarHeight, marginLeft + gameWidth, marginTop + gameHeight };
}
Utils头与其源文件
// Utils.h
#pragma once
long getRectWidth(RECT rect);
long getRectHeight(RECT rect);
void drawLine(HDC hdc, int x1, int y1, int x2, int y2);
void drawRoundRect(HDC hdc, RECT cellRect, COLORREF bgColor);
void drawNumberOnCell(HDC hdc, RECT cellRect, const int& number, COLORREF fontColor);
void drawNumberOnRect(HDC hdc, RECT rect, const int& number, COLORREF fontColor);
void drawNumberOnTimeLabel(HDC hdc, RECT rect, LPCWSTR pWStr, COLORREF fontColor);
HFONT createFont(int height);
void getTimeStringByValue(UINT time, LPCWSTR result);
// Utils.cpp
#include "framework.h"
#include <cmath>
long getRectWidth(RECT rect) {
return rect.right - rect.left;
}
long getRectHeight(RECT rect) {
return rect.bottom - rect.top;
}
// 画线
void drawLine(HDC hdc, int x1, int y1, int x2, int y2) {
MoveToEx(hdc, x1, y1, NULL);
LineTo(hdc, x2, y2);
}
// 画圆角矩形
void drawRoundRect(HDC hdc, RECT cellRect, COLORREF bgColor) {
HBRUSH brush = CreateSolidBrush(bgColor);
HPEN pen = CreatePen(PS_NULL, 0, NULL);
SelectObject(hdc, brush);
SelectObject(hdc, pen);
RoundRect(hdc, cellRect.left, cellRect.top, cellRect.right, cellRect.bottom, 10, 10);
DeleteObject(brush);
DeleteObject(pen);
}
// 创建固定字体,高度不同
HFONT createFont(int height) {
return CreateFont(height, 0, 0, 0, FW_BLACK, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_CHARACTER_PRECIS, PROOF_QUALITY, FF_ROMAN, TEXT("Arial"));
}
// 将时间(单位:百分之一秒)转换成宽字符串(长度为8位)
void getTimeStringByValue(UINT time, LPCWSTR result) {
// int hSeconds = time % 100;
time /= 100;
int seconds = time % 60;
time /= 60;
int minutes = time % 60;
WCHAR buffer[10];
wsprintfW(buffer, L"%02d:%02d", minutes, seconds);
//if (seconds < 10) {
// buffer[5] = buffer[4];
// buffer[4] = buffer[3];
// buffer[3] = L'0';
//}
memcpy((void*)result, buffer, sizeof(buffer));
}
// 画数字
void drawNumberOnCell(HDC hdc, RECT cellRect, const int& number, COLORREF fontColor) {
int fontSize = log2(number);
double factor = fontSize < 7 ? 1 : (fontSize < 10 ? 1.5 : 2);
HFONT font = createFont(getRectHeight(cellRect) * 0.66 / factor);
SetTextColor(hdc, fontColor); // Font color
SetBkMode(hdc, TRANSPARENT); // Background color transparentk
SelectObject(hdc, font); // Set font
wchar_t buffer[256];
wsprintfW(buffer, L"%d", number);
DrawText(hdc, buffer, -1, &cellRect, DT_CENTER | DT_SINGLELINE | DT_VCENTER);
DeleteObject(font);
}
void drawNumberOnRect(HDC hdc, RECT rect, const int& number, COLORREF fontColor) {
HFONT font = createFont(getRectHeight(rect) * 0.66);
SetTextColor(hdc, fontColor); // Font color
SetBkMode(hdc, TRANSPARENT); // Background color transparentk
SelectObject(hdc, font); // Set font
wchar_t buffer[256];
wsprintfW(buffer, L"%d", number);
DrawText(hdc, buffer, -1, &rect, DT_CENTER | DT_SINGLELINE | DT_VCENTER);
DeleteObject(font);
}
void drawNumberOnTimeLabel(HDC hdc, RECT rect, LPCWSTR pWStr, COLORREF fontColor) {
HFONT font = createFont(getRectHeight(rect) * 0.66 / 2);
SetTextColor(hdc, fontColor); // Font color
SetBkMode(hdc, TRANSPARENT); // Background color transparentk
SelectObject(hdc, font); // Set font
DrawText(hdc, pWStr, -1, &rect, DT_CENTER | DT_SINGLELINE | DT_VCENTER);
DeleteObject(font);
}
资料引用与出处
- 游戏开发素材 | indienova 独立游戏
- 奔跑骚年 - 简书
- Windows 窗体控件 | Microsoft Docs
- 章节概述 - 2D 游戏开发教程
- 精通Win32 API高级界面编程 - 网易云课堂
- 创建窗口 - Windows应用程序| Microsoft Docs
- Windows程序设计 - 标签 - wid - 博客园
- [因为我不懂啊]-什么是状态机编程(设计模式)(0) - 简书
- 游戏开发之状态机的实现与优化-GameRes游资网
- Cocos2d-X手游开发课程【实用技能,学完即飞升脱离饿鬼界】_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili