研究win16实现2048小游戏(基于windows1.03sdk+msc4.0)
本帖最后由 健康快乐 于 2026-6-24 12:40 编辑刚开始学习Windows编程给自己找了个最难的环境286+dos3.33+Windows1.03
很多win32程序设计的思路在win16时代都能找到萌芽!!!这并不割裂!!!!
但是还是一如既往的难受单单R&K语法我就研究了半天!!!!!
简单的出版本2048:
/* 2048.c - Windows 1.03 Edition~ */
/* 基于Microsoft C 5.0 + Windows SDK 1.03 */
/* 基于K&R C style - 夏媛媛手敲~ */
#include <windows.h>
#include <stdlib.h>
#include <stdio.h> /* sprintf */
#include <string.h> /* strlen */
#include "2048.h" /* 资源ID定义(IDM_NEWGAME, IDM_HOWTOPLAY, IDM_ABOUT, IDSYS_ABOUT, DLG_ABOUT, DLG_HOWTOPLAY) */
void SaveGame();
intLoadGame();
/* ======= 分数历史记录(二进制文件,防玩家修改) ======= */
typedef struct {
int highscore; /* 历史最高分 */
int recent; /* 最近 5 局分数(下标 0 为最新) */
int count; /* 实际记录局数(<=5) */
} ScoreHistory;
/* 分数历史文件操作(K&R 旧式声明,只写参数名) */
BOOL GetScoreHistory();
void SetScoreHistory();
void UpdateScoreHistory();
/* 分数板对话框过程(K&R 旧式声明) */
BOOL FAR PASCAL ScoreboardDlg();
#define BOARD_SIZE 4
#define CELL_W 55
#define CELL_H 55
#define MARGIN 8
/* 全局数据段变量 */
intboard;
intscore;
intgame_over;
int g_bDirty = 0; /* 新增:脏标记,0=状态与存档一致,1=有未保存改动 */
int g_bPlayerMoved = 0;/* 玩家是否进行过主动操作 */
HBRUSH hBrush, hOldBrush;
HWND hWndMain;
HANDLE hInst; /* 实例句柄 */
FARPROC lpfnAboutDlg; /* About 对话框过程实例地址 */
FARPROC lpfnHowToPlayDlg; /* HowToPlay 对话框过程实例地址 */
FARPROC lpfnScoreboardDlg; /* 分数板对话框过程实例地址 */
/* GDI对象缓存 */
HBRUSH hBrushes; /* 0=空白,1=2,2=4,3=8...12=2048+ */
HPEN hPenBlack;
HPEN hPenWhite;
RECT rcGame; /* 游戏区域 */
/* K&R 函数前置声明 */
void InitGame();
void AddRandomTile();
intMoveLeft();
intMoveRight();
intMoveUp();
intMoveDown();
intCanMove();
void DrawBoard();
void DrawCell();
void InitGDI();
void CleanupGDI();
intValueToIndex();
long FAR PASCAL WndProc();
BOOL FAR PASCAL AboutDlg();
BOOL FAR PASCAL HowToPlayDlg();
/* ============================================================
窗口过程
============================================================ */
long FAR PASCAL WndProc(hWnd, message, wParam, lParam)
HWND hWnd;
unsigned message;
WORD wParam;
LONG lParam;
{
PAINTSTRUCT ps;
HDC hDC;
RECT rect;
char buf;
int i, j;
switch (message) {
case WM_CREATE:
hWndMain = hWnd;
InitGame();
InitGDI();
rcGame.left = 0;
rcGame.top = 0;
rcGame.right= 280;
rcGame.bottom = 320;
break;
case WM_PAINT:
hDC = BeginPaint(hWnd, &ps);
GetClientRect(hWnd, &rect);
/* 刷白背景 */
hBrush = GetStockObject(WHITE_BRUSH);
hOldBrush = SelectObject(hDC, hBrush);
PatBlt(hDC, rect.left, rect.top,
rect.right - rect.left,
rect.bottom - rect.top, PATCOPY);
SelectObject(hDC, hOldBrush);
SetBkMode(hDC, TRANSPARENT);
SetTextColor(hDC, RGB(0, 0, 0));
/* 标题栏 */
sprintf(buf, "2048Score: %d", score);
TextOut(hDC, MARGIN, 4, buf, strlen(buf));
if (game_over) {
TextOut(hDC, MARGIN, 20,
"GAME OVER - Press N for New Game", 32);
} else {
TextOut(hDC, MARGIN, 20,
"Arrows: Move N: New Game", 26);
}
DrawBoard(hDC);
EndPaint(hWnd, &ps);
break;
case WM_KEYDOWN:
if (game_over && wParam != 'N' && wParam != 'n')
break;
switch (wParam) {
case VK_LEFT:
if (MoveLeft()) goto MOVED;
break;
case VK_RIGHT:
if (MoveRight()) goto MOVED;
break;
case VK_UP:
if (MoveUp()) goto MOVED;
break;
case VK_DOWN:
if (MoveDown()) goto MOVED;
break;
case 'N':
case 'n':
remove("2048.sav");
g_bPlayerMoved = 0;
InitGame();
/* 修正:新游戏后,脏标记应为 1 */
g_bDirty = 1;
InvalidateRect(hWnd, &rcGame, FALSE);
break;
}
break;
MOVED:
/* 如果玩家是第一次操作,删除旧的存档文件 */
if (!g_bPlayerMoved) {
remove("2048.sav"); /* 旧存档已无效 */
g_bPlayerMoved = 1;
}
AddRandomTile();
/* 修正:任何移动成功并添加了随机块,都应视为状态改变 */
g_bDirty = 1;
if (!CanMove()){
game_over = 1;
/* 游戏结束 → 更新分数历史并弹出分数板 */
UpdateScoreHistory(score);
DialogBox(hInst, MAKEINTRESOURCE(DLG_SCOREBOARD),
hWnd, lpfnScoreboardDlg);
}
InvalidateRect(hWnd, &rcGame, FALSE);
break;
/* 新增:处理菜单命令(Game、Help菜单) */
case WM_COMMAND:
switch (wParam) {
case IDM_NEWGAME:
remove("2048.sav");
g_bPlayerMoved = 0;
InitGame();
g_bDirty = 1; /* 修正:新游戏后,脏标记应为 1 */
InvalidateRect(hWnd, &rcGame, FALSE);
break;
case IDM_SAVE:
SaveGame();
g_bDirty = 0;
InvalidateRect(hWnd, &rcGame, FALSE);
break;
case IDM_SCOREBOARD:
/* 玩家随时查看分数板,当前分数来自全局变量 score */
DialogBox(hInst, MAKEINTRESOURCE(DLG_SCOREBOARD),
hWnd, lpfnScoreboardDlg);
break;
case IDM_HOWTOPLAY:
DialogBox(hInst, MAKEINTRESOURCE(DLG_HOWTOPLAY),
hWnd, lpfnHowToPlayDlg);
break;
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(DLG_ABOUT),
hWnd, lpfnAboutDlg);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
/* 新增:处理系统菜单(左上角 About…) */
case WM_SYSCOMMAND:
if (wParam == IDSYS_ABOUT) {
DialogBox(hInst, MAKEINTRESOURCE(DLG_ABOUT),
hWnd, lpfnAboutDlg);
return 0;
}
/* 其他系统命令(最大化、最小化、移动、关闭等)必须交给默认处理 */
return DefWindowProc(hWnd, message, wParam, lParam);
case WM_DESTROY:
/* ============================================================
修正:退出时检查是否需要存档提醒
设计思路:
1. 如果游戏已经结束 (game_over == 1),直接退出,不提醒。
2. 如果游戏未结束,但有未保存的改动 (g_bDirty == 1),则触发提醒。
3. 如果游戏未结束且没有改动 (g_bDirty == 0),说明用户已手动保存过,
直接退出,不打扰用户。
============================================================ */
if (!game_over && g_bDirty && g_bPlayerMoved) {
/* 弹出可爱的英文提示框 */
if (MessageBox(hWnd,
"Ooh! Meow ^v^ You haven't saved your game!",
"Save Game?",
MB_YESNO | MB_ICONQUESTION) == IDYES) {
/* 用户选择保存 */
SaveGame();
g_bDirty = 0;
}
}
/* 清理GDI对象和实例地址 */
CleanupGDI();
FreeProcInstance(lpfnAboutDlg);
FreeProcInstance(lpfnHowToPlayDlg);
FreeProcInstance(lpfnScoreboardDlg);
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0L;
}
/* ============================================================
WinMain - 程序入口
============================================================ */
int PASCAL WinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow)
HANDLE hInstance;
HANDLE hPrevInstance;
LPSTRlpCmdLine;
int nCmdShow;
{
MSG msg;
WNDCLASS wc;
HWND hWnd;
HMENU hMenu;
HMENU hSysMenu;
srand((unsigned)GetCurrentTime());
if (!hPrevInstance) {
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(hInstance, (LPSTR)"AppIcon");
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = GetStockObject(WHITE_BRUSH);
wc.lpszMenuName= NULL;
wc.lpszClassName = "2048W103";
if (!RegisterClass(&wc))
return FALSE;
}
hInst = hInstance;
/* 创建对话框过程的实例地址(必须在创建窗口前准备好) */
lpfnAboutDlg = MakeProcInstance((FARPROC)AboutDlg, hInstance);
lpfnHowToPlayDlg = MakeProcInstance((FARPROC)HowToPlayDlg, hInstance);
lpfnScoreboardDlg = MakeProcInstance((FARPROC)ScoreboardDlg, hInstance);
/* 加载菜单栏(资源名为 "GAME_MENU",在 2048.rc 中定义) */
hMenu = LoadMenu(hInstance, (LPSTR)"GAME_MENU");
if (!hMenu) {
MessageBox(NULL, "Menu load failed!", NULL, MB_OK);
return FALSE;
}
/* Windows 1.03 兼容样式:WS_TILEDWINDOW(等同于 WS_TILED|WS_CAPTION|WS_SYSMENU|WS_SIZEBOX) */
/* CreateWindow 只传 10 个参数,无最后的 lpParam */
hWnd = CreateWindow("2048W103",
"2048 - Windows 1.03",
WS_TILEDWINDOW,
10, 10, 280, 320,
NULL,
hMenu,
hInstance,
NULL);
if (!hWnd)
return FALSE;
/* ============================================================
修正:启动时检测并加载存档
设计思路:
1. 尝试加载存档。如果成功,游戏状态恢复,存档文件被立即删除。
2. 如果加载失败(没有存档或文件损坏),则开始一个全新的游戏。
3. 无论是加载成功还是开始新游戏,都设置脏标记 g_bDirty = 1,
因为有新状态产生,应视为未保存。
============================================================ */
if (LoadGame()) {
/* 成功加载存档,存档文件已被 LoadGame 内部删除 */
InvalidateRect(hWnd, &rcGame, FALSE);
g_bDirty = 0; /* 恢复后的状态,视为未保存的新状态 */
g_bPlayerMoved = 0; /* 加载存档不算玩家操作 */
} else {
/* 没有存档,开始新游戏 */
InitGame();
g_bDirty = 1; /* 新游戏视为未保存 */
g_bPlayerMoved = 0; /* 无存档启动,自动新游戏,视为玩家“开始了一局” */
}
/* 在系统菜单(左上角)添加分隔线后追加 About… */
/* 先获取系统菜单副本 */
hSysMenu = GetSystemMenu(hWnd, FALSE);
if (hSysMenu) {
/* 添加分隔线(横杠) */
ChangeMenu(hSysMenu, 0, (LPSTR)NULL, 0,
MF_APPEND | MF_SEPARATOR);
/* 添加 "About..." 命令 */
ChangeMenu(hSysMenu, 0, (LPSTR)"About...",
IDSYS_ABOUT, MF_APPEND | MF_STRING);
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
/* ============================================================
游戏逻辑
============================================================ */
void InitGame()
{
int i, j;
for (i = 0; i < BOARD_SIZE; i++)
for (j = 0; j < BOARD_SIZE; j++)
board = 0;
score = 0;
game_over = 0;
AddRandomTile();
AddRandomTile();
}
void AddRandomTile()
{
int empty;
int count, pos;
int i, j;
count = 0;
for (i = 0; i < BOARD_SIZE; i++)
for (j = 0; j < BOARD_SIZE; j++)
if (board == 0) {
empty = i;
empty = j;
count++;
}
if (count == 0) return;
pos = rand() % count;
i = empty;
j = empty;
board = (rand() % 10 == 0) ? 4 : 2;
}
int MoveLeft()
{
int moved;
int i, j, k;
moved = 0;
for (i = 0; i < BOARD_SIZE; i++) {
/* 压缩 */
for (j = 0; j < BOARD_SIZE; j++) {
if (board == 0) {
for (k = j + 1; k < BOARD_SIZE; k++) {
if (board != 0) {
board = board;
board = 0;
moved = 1;
break;
}
}
}
}
/* 合并 */
for (j = 0; j < BOARD_SIZE - 1; j++) {
if (board != 0 && board == board) {
board *= 2;
score += board;
board = 0;
moved = 1;
}
}
/* 再压缩 */
for (j = 0; j < BOARD_SIZE; j++) {
if (board == 0) {
for (k = j + 1; k < BOARD_SIZE; k++) {
if (board != 0) {
board = board;
board = 0;
moved = 1;
break;
}
}
}
}
}
return moved;
}
int MoveRight()
{
int moved;
int i, j;
int tmp;
for (i = 0; i < BOARD_SIZE; i++)
for (j = 0; j < BOARD_SIZE / 2; j++) {
tmp = board;
board = board;
board = tmp;
}
moved = MoveLeft();
for (i = 0; i < BOARD_SIZE; i++)
for (j = 0; j < BOARD_SIZE / 2; j++) {
tmp = board;
board = board;
board = tmp;
}
return moved;
}
int MoveUp()
{
int moved;
int i, j;
int tmp;
for (i = 0; i < BOARD_SIZE; i++)
for (j = i + 1; j < BOARD_SIZE; j++) {
tmp = board;
board = board;
board = tmp;
}
moved = MoveLeft();
for (i = 0; i < BOARD_SIZE; i++)
for (j = i + 1; j < BOARD_SIZE; j++) {
tmp = board;
board = board;
board = tmp;
}
return moved;
}
int MoveDown()
{
int moved;
int i, j;
int tmp;
/* 转置 */
for (i = 0; i < BOARD_SIZE; i++)
for (j = i + 1; j < BOARD_SIZE; j++) {
tmp = board;
board = board;
board = tmp;
}
/* 水平翻转 */
for (i = 0; i < BOARD_SIZE; i++)
for (j = 0; j < BOARD_SIZE / 2; j++) {
tmp = board;
board = board;
board = tmp;
}
moved = MoveLeft();
/* 翻转回来 */
for (i = 0; i < BOARD_SIZE; i++)
for (j = 0; j < BOARD_SIZE / 2; j++) {
tmp = board;
board = board;
board = tmp;
}
/* 转置回来 */
for (i = 0; i < BOARD_SIZE; i++)
for (j = i + 1; j < BOARD_SIZE; j++) {
tmp = board;
board = board;
board = tmp;
}
return moved;
}
int CanMove()
{
int i, j;
for (i = 0; i < BOARD_SIZE; i++)
for (j = 0; j < BOARD_SIZE; j++)
if (board == 0)
return 1;
for (i = 0; i < BOARD_SIZE; i++)
for (j = 0; j < BOARD_SIZE - 1; j++)
if (board == board)
return 1;
for (i = 0; i < BOARD_SIZE - 1; i++)
for (j = 0; j < BOARD_SIZE; j++)
if (board == board)
return 1;
return 0;
}
/* ============================================================
GDI绘图
============================================================ */
void DrawCell(hDC, value, x, y, w, h)
HDC hDC;
int value, x, y, w, h;
{
char buf;
intlen;
intidx;
HBRUSH hBrush, hOldBrush;
HPEN hPen, hOldPen;
idx = ValueToIndex(value);
hBrush = hBrushes;
if (value >= 8 && value < 128)
hPen = hPenWhite;
else
hPen = hPenBlack;
hOldBrush = SelectObject(hDC, hBrush);
hOldPen = SelectObject(hDC, hPen);
Rectangle(hDC, x, y, x + w, y + h);
SelectObject(hDC, hOldBrush);
SelectObject(hDC, hOldPen);
if (value != 0) {
SetBkMode(hDC, TRANSPARENT);
SetTextColor(hDC, (value >= 8 && value < 128) ?
RGB(255,255,255) : RGB(0,0,0));
sprintf(buf, "%d", value);
len = strlen(buf);
TextOut(hDC, x + (w - len * 8) / 2, y + (h - 14) / 2, buf, len);
}
}
void DrawBoard(hDC)
HDC hDC;
{
int i, j;
int x, y;
int top = 45;
for (i = 0; i < BOARD_SIZE; i++) {
for (j = 0; j < BOARD_SIZE; j++) {
x = MARGIN + j * (CELL_W + MARGIN);
y = top + i * (CELL_H + MARGIN);
DrawCell(hDC, board, x, y, CELL_W, CELL_H);
}
}
}
/* 预创建所有GDI对象 */
void InitGDI()
{
hBrushes= GetStockObject(LTGRAY_BRUSH);
hBrushes= CreateSolidBrush(RGB(238, 228, 218));/* 2 */
hBrushes= CreateSolidBrush(RGB(237, 224, 200));/* 4 */
hBrushes= CreateSolidBrush(RGB(242, 177, 121));/* 8 */
hBrushes= CreateSolidBrush(RGB(245, 149, 99)); /* 16 */
hBrushes= CreateSolidBrush(RGB(246, 124, 95)); /* 32 */
hBrushes= CreateSolidBrush(RGB(246, 94, 59)); /* 64 */
hBrushes= CreateSolidBrush(RGB(237, 207, 114));/* 128 */
hBrushes= CreateSolidBrush(RGB(237, 207, 114));/* 256 */
hBrushes= CreateSolidBrush(RGB(237, 207, 114));/* 512 */
hBrushes = CreateSolidBrush(RGB(237, 207, 114));/* 1024 */
hBrushes = CreateSolidBrush(RGB(237, 207, 114));/* 2048 */
hBrushes = CreateSolidBrush(RGB(237, 207, 114));/* 更大 */
hPenBlack = GetStockObject(BLACK_PEN);
hPenWhite = GetStockObject(WHITE_PEN);
}
/* 程序退出时统一清理 */
void CleanupGDI()
{
int i;
for (i = 1; i < 13; i++) {
if (hBrushes)
DeleteObject(hBrushes);
}
}
int ValueToIndex(value)
int value;
{
switch (value) {
case 0: return 0;
case 2: return 1;
case 4: return 2;
case 8: return 3;
case 16: return 4;
case 32: return 5;
case 64: return 6;
case 128:return 7;
case 256:return 8;
case 512:return 9;
case 1024: return 10;
case 2048: return 11;
default: return 12;
}
}
/* ============================================================
对话框过程
============================================================ */
/* About 对话框过程 */
BOOL FAR PASCAL AboutDlg(hDlg, message, wParam, lParam)
HWND hDlg;
unsigned message;
WORD wParam;
LONG lParam;
{
if (message == WM_COMMAND && wParam == IDOK) {
EndDialog(hDlg, TRUE);
return TRUE;
}
if (message == WM_INITDIALOG)
return TRUE;
return FALSE;
}
/* HowToPlay 对话框过程(新增) */
BOOL FAR PASCAL HowToPlayDlg(hDlg, message, wParam, lParam)
HWND hDlg;
unsigned message;
WORD wParam;
LONG lParam;
{
if (message == WM_COMMAND && wParam == IDOK) {
EndDialog(hDlg, TRUE);
return TRUE;
}
if (message == WM_INITDIALOG)
return TRUE;
return FALSE;
}
/* ============================================================
新增:存档文件读写函数
设计思路:
1. SaveGame() 将当前棋盘状态写入文本文件 2048.sav。
文本格式:简单可靠,第一行为分数和游戏状态,随后为棋盘矩阵。
2. LoadGame() 从 2048.sav 读取状态。
成功返回 1,失败返回 0。
成功读取后,应立即删除存档文件,避免下次启动干扰。
3. 存档仅保存最后一次手动保存的状态。
4. 游戏启动时会检查并加载存档,加载后文件被删除。
之后的游戏过程都不会再使用该文件,直到用户再次手动存档。
============================================================ */
void SaveGame()
{
FILE *fp;
int i, j;
fp = fopen("2048.sav", "w");
if (fp == NULL)
return;
fprintf(fp, "%d %d\n", score, game_over);
for (i = 0; i < BOARD_SIZE; i++) {
for (j = 0; j < BOARD_SIZE; j++)
fprintf(fp, "%d ", board);
fprintf(fp, "\n");
}
fclose(fp);
}
int LoadGame()
{
FILE *fp;
int i, j;
fp = fopen("2048.sav", "r");
if (fp == NULL)
return 0; /* 不存在存档 */
if (fscanf(fp, "%d %d", &score, &game_over) != 2) {
fclose(fp);
return 0;
}
for (i = 0; i < BOARD_SIZE; i++)
for (j = 0; j < BOARD_SIZE; j++) {
if (fscanf(fp, "%d", &board) != 1) {
fclose(fp);
return 0;
}
}
fclose(fp);
/* 修正:成功加载后,立即删除存档文件,避免下次启动干扰 */
/* remove("2048.sav"); */
return 1;
}
/* ============================================================
分数历史记录读写函数(二进制文件,彻底避免玩家修改)
============================================================ */
/* 从文件读取历史,成功返回 TRUE,失败返回 FALSE */
BOOL GetScoreHistory(pHist)
ScoreHistory *pHist;
{
FILE *fp;
fp = fopen("2048.his", "rb");
if (fp == NULL)
return FALSE;
if (fread(pHist, sizeof(ScoreHistory), 1, fp) != 1) {
fclose(fp);
return FALSE;
}
fclose(fp);
return TRUE;
}
/* 将历史写入文件(若文件不存在则创建) */
void SetScoreHistory(pHist)
ScoreHistory *pHist;
{
FILE *fp;
fp = fopen("2048.his", "wb");
if (fp == NULL)
return;
fwrite(pHist, sizeof(ScoreHistory), 1, fp);
fclose(fp);
}
/* 游戏结束时调用,传入本局得分,自动更新历史文件 */
void UpdateScoreHistory(newScore)
int newScore;
{
ScoreHistory hist;
int i;
/* 若文件不存在,则初始化空历史 */
if (!GetScoreHistory(&hist)) {
hist.highscore = 0;
hist.count = 0;
for (i = 0; i < 5; i++)
hist.recent = 0;
}
/* 更新最高分 */
if (newScore > hist.highscore)
hist.highscore = newScore;
/* 将新分数插入 recent,后移原有分数 */
for (i = 4; i > 0; i--)
hist.recent = hist.recent;
hist.recent = newScore;
if (hist.count < 5)
hist.count++;
SetScoreHistory(&hist);
}
/* ============================================================
分数板对话框过程
============================================================ */
BOOL FAR PASCAL ScoreboardDlg(hDlg, message, wParam, lParam)
HWND hDlg;
unsigned message;
WORD wParam;
LONG lParam;
{
ScoreHistory hist;
char buf;
char line;
int i;
switch (message) {
case WM_INITDIALOG:
/* 读取历史,若失败则显示空数据 */
if (!GetScoreHistory(&hist)) {
hist.highscore = 0;
hist.count = 0;
}
/* 构建多行文本(注意换行用 \r\n) */
buf = '\0';
sprintf(buf + strlen(buf), "Best Score: %d\r\n", hist.highscore);
sprintf(buf + strlen(buf), "------------------------\r\n");
sprintf(buf + strlen(buf), "Recent Scores:\r\n");
for (i = 0; i < hist.count && i < 5; i++) {
sprintf(line, "%d. %d\r\n", i + 1, hist.recent);
strcat(buf, line);
}
sprintf(buf + strlen(buf), "------------------------\r\n");
sprintf(buf + strlen(buf), "Current Score: %d", score);
/* 设置到编辑框(IDC_SCOREBOARD_EDIT) */
SetDlgItemText(hDlg, IDC_SCOREBOARD_EDIT, buf);
/* 设置完文本后,将焦点转移到 OK 按钮,避免编辑框文字被选中 */
SetFocus(GetDlgItem(hDlg, IDOK));
return FALSE; /* 返回 FALSE 表示你主动设置了焦点,系统不再自动设置 */
case WM_COMMAND:
if (wParam == IDOK) {
EndDialog(hDlg, TRUE);
return TRUE;
}
break;
}
return FALSE;
}
有趣的是win1sdk缺少叠加窗口类的api但是为win1写的程序却可以巧妙的运行!!!在添加256色驱动后有种古早Android风格!!!!
效果图:
看到你用 Windows 1.03 SDK + MSC 4.0 写 2048,真是勇气可嘉!Win16 时代的 API 确实与 Win32 有差异,但核心思想相通,这段代码里藏着不少早期 Windows 编程的“独门绝技”。下面我帮你梳理一下代码中需要注意的地方,以及一些可以改进的点。
[*]数组声明与使用不匹配:代码里出现了 board = 0 之类的写法,但 board 没有被定义成二维数组。应该统一声明为 int board;,之后所有访问都要带下标。
[*]随机放置方块的逻辑有误:AddRandomTile 中 empty 数组只记录了最后一个空位,应该用数组存储所有空位坐标再随机选取。
[*]GDI 画刷数组 InitGDI 中 hBrushes 被重复赋值,导致只创建了最后一个画刷,前面的全部泄漏。应该声明为 HBRUSH hBrushes;。
[*]Move 函数里的转置和翻转操作中,数组索引错误(例如 board = board; 缺少 和 )。正确写法需要明确指定两个下标。
[*]对话框过程的类型转换:Win16 下 MakeProcInstance 返回的是 FARPROC,但 DialogBox 最后一个参数期望的是 DLGPROC 类型,需要强制转换。
[*]资源 ID 宏未定义:代码中引用了 IDM_NEWGAME、DLG_ABOUT 等,但头文件 2048.h 没有给出,需要确认 .rc 文件中的定义。
虽然代码有不少 bug,但你选择的这个环境本身就是“地狱难度”——K&R 函数定义、无重叠窗口的 API、全局数据段、MakeProcInstance……这些在 Win32 中早已消失。能跑起来并截图,说明你已经克服了很大的挑战。
建议先修正数组声明和 GDI 泄漏,然后逐步调试 Move 逻辑。如果你愿意,我可以针对某个具体问题(比如 MoveLeft 的实现)给出在 Win16 下正确的 K&R 写法。继续加油,老平台能带来很多底层理解!
以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。 FishC 发表于 2026-6-24 12:35
看到你用 Windows 1.03 SDK + MSC 4.0 写 2048,真是勇气可嘉!Win16 时代的 API 确实与 Win32 有差异,但 ...
ok记录了!!!
我有点不太敢动它了!!!每次修改之后一堆报错或者出现莫名其妙的问题!!!
特别是不小心用现代编辑器打开后被插入的各种隐形字符!头大!!!! 健康快乐 发表于 2026-06-24 12:46
FishC 发表于 2026-6-24 12:35
看到你用 Windows 1.0 ...
看到你的困扰,完全理解——在 DOS + Windows 1.03 环境下调试,每次修改都像在踩地雷。这里给你几个“保命”建议,能最大程度减少意外:
[*] 用“纯文本”编辑器:不要用现代 IDE 或 Word 类工具。推荐 Windows 自带的 Notepad(记事本)或者 DOS 下的 EDIT。它们不会自动插入格式化字符。
[*] 文件换行格式:MS-DOS 使用 CR+LF(0x0D, 0x0A)。如果你在 Windows 10/11 上编辑,保存时请选择“ANSI”编码和“Windows (CRLF)”换行。很多现代编辑器默认用 LF(Unix),放进 MSC 5.0 编译器会报错。
[*] 分步修改,局部测试:每次只改一个函数或一行逻辑,改完立即编译。不要一次改动太多。错误定位难,往往是因为多个改动相互影响。
[*] 保留前一个版本:在 DOS 下可以用 COPY 命令备份,比如 copy 2048.c 2048_prev.c。出了问题立刻恢复。
[*] 编译前检查大括号:K&R 风格要求函数左大括号在单独一行,且不能省略。如果某个函数报错“syntax error”,大概率是括号不匹配。
关于“隐形字符”——主要是“非断行空格”和“全角符号”,现代编辑器误操作可能插入。最保险的手段:用 DOS 下的 TYPE 命令查看原始文件是否正常。例如:
TYPE 2048.C | MORE
如果看到奇怪字符,说明编辑器污染了。这时直接恢复备份,或者用 DEBUG 手动清除。
你已经在非常苛刻的环境下跑起来了,这是很多现代程序员做不到的。放轻松,每次只解决一个小问题,比如先把数组 board 改成二维的、修好画刷数组。祝你成功!
以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。 学到了 学习了 确实不错在学了 光看就感觉好难呀。 强者就是什么环境都那么牛掰 学习新知识 学习了 学到了 学习了 虽然看不懂,但是觉得很厉害 学到了 学到了 这也太强了吧 不明觉厉,学习了 还是学Python吧,Windows程序好难,做游戏给难 Type 回到30年前学习dos的时候了