鱼C论坛

 找回密码
 立即注册
查看: 29|回复: 3

[技术交流] 研究win16实现2048小游戏(基于windows1.03sdk+msc4.0)

[复制链接]
发表于 6 小时前 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
本帖最后由 健康快乐 于 2026-6-24 12:40 编辑

刚开始学习Windows编程给自己找了个最难的环境286+dos3.33+Windows1.03
很多win32程序设计的思路在win16时代都能找到萌芽!!!这并不割裂!!!!
但是还是一如既往的难受单单R&K语法我就研究了半天!!!!!
简单的出版本2048:
  1. /* 2048.c - Windows 1.03 Edition~ */
  2. /* 基于Microsoft C 5.0 + Windows SDK 1.03 */
  3. /* 基于K&R C style - 夏媛媛手敲~ */

  4. #include <windows.h>
  5. #include <stdlib.h>
  6. #include <stdio.h>    /* sprintf */
  7. #include <string.h>   /* strlen */
  8. #include "2048.h"     /* 资源ID定义(IDM_NEWGAME, IDM_HOWTOPLAY, IDM_ABOUT, IDSYS_ABOUT, DLG_ABOUT, DLG_HOWTOPLAY) */

  9. void SaveGame();
  10. int  LoadGame();

  11. /* ======= 分数历史记录(二进制文件,防玩家修改) ======= */
  12. typedef struct {
  13.     int highscore;          /* 历史最高分 */
  14.     int recent[5];          /* 最近 5 局分数(下标 0 为最新) */
  15.     int count;              /* 实际记录局数(<=5) */
  16. } ScoreHistory;

  17. /* 分数历史文件操作(K&R 旧式声明,只写参数名) */
  18. BOOL GetScoreHistory();
  19. void SetScoreHistory();
  20. void UpdateScoreHistory();

  21. /* 分数板对话框过程(K&R 旧式声明) */
  22. BOOL FAR PASCAL ScoreboardDlg();

  23. #define BOARD_SIZE 4
  24. #define CELL_W     55
  25. #define CELL_H     55
  26. #define MARGIN     8

  27. /* 全局数据段变量 */
  28. int  board[BOARD_SIZE][BOARD_SIZE];
  29. int  score;
  30. int  game_over;
  31. int g_bDirty = 0; /* 新增:脏标记,0=状态与存档一致,1=有未保存改动 */
  32. int g_bPlayerMoved = 0;  /* 玩家是否进行过主动操作 */
  33. HBRUSH hBrush, hOldBrush;
  34. HWND hWndMain;
  35. HANDLE hInst;                  /* 实例句柄 */
  36. FARPROC lpfnAboutDlg;          /* About 对话框过程实例地址 */
  37. FARPROC lpfnHowToPlayDlg;      /* HowToPlay 对话框过程实例地址 */
  38. FARPROC lpfnScoreboardDlg;      /* 分数板对话框过程实例地址 */

  39. /* GDI对象缓存 */
  40. HBRUSH hBrushes[13];   /* 0=空白,1=2,2=4,3=8...12=2048+ */
  41. HPEN   hPenBlack;
  42. HPEN   hPenWhite;
  43. RECT   rcGame;   /* 游戏区域 */

  44. /* K&R 函数前置声明 */
  45. void InitGame();
  46. void AddRandomTile();
  47. int  MoveLeft();
  48. int  MoveRight();
  49. int  MoveUp();
  50. int  MoveDown();
  51. int  CanMove();
  52. void DrawBoard();
  53. void DrawCell();
  54. void InitGDI();
  55. void CleanupGDI();
  56. int  ValueToIndex();
  57. long FAR PASCAL WndProc();
  58. BOOL FAR PASCAL AboutDlg();
  59. BOOL FAR PASCAL HowToPlayDlg();

  60. /* ============================================================
  61.    窗口过程
  62.    ============================================================ */
  63. long FAR PASCAL WndProc(hWnd, message, wParam, lParam)
  64. HWND     hWnd;
  65. unsigned message;
  66. WORD     wParam;
  67. LONG     lParam;
  68. {
  69.     PAINTSTRUCT ps;
  70.     HDC         hDC;
  71.     RECT        rect;
  72.     char        buf[64];
  73.     int         i, j;

  74.     switch (message) {

  75.         case WM_CREATE:
  76.             hWndMain = hWnd;
  77.             InitGame();
  78.             InitGDI();
  79.             rcGame.left   = 0;
  80.             rcGame.top    = 0;
  81.             rcGame.right  = 280;
  82.             rcGame.bottom = 320;
  83.             break;

  84.         case WM_PAINT:
  85.             hDC = BeginPaint(hWnd, &ps);
  86.             GetClientRect(hWnd, &rect);

  87.             /* 刷白背景 */
  88.             hBrush = GetStockObject(WHITE_BRUSH);
  89.             hOldBrush = SelectObject(hDC, hBrush);
  90.             PatBlt(hDC, rect.left, rect.top,
  91.                    rect.right - rect.left,
  92.                    rect.bottom - rect.top, PATCOPY);
  93.             SelectObject(hDC, hOldBrush);

  94.             SetBkMode(hDC, TRANSPARENT);
  95.             SetTextColor(hDC, RGB(0, 0, 0));

  96.             /* 标题栏 */
  97.             sprintf(buf, "2048  Score: %d", score);
  98.             TextOut(hDC, MARGIN, 4, buf, strlen(buf));

  99.             if (game_over) {
  100.                 TextOut(hDC, MARGIN, 20,
  101.                         "GAME OVER - Press N for New Game", 32);
  102.             } else {
  103.                 TextOut(hDC, MARGIN, 20,
  104.                         "Arrows: Move   N: New Game", 26);
  105.             }

  106.             DrawBoard(hDC);
  107.             EndPaint(hWnd, &ps);
  108.             break;

  109.         case WM_KEYDOWN:
  110.             if (game_over && wParam != 'N' && wParam != 'n')
  111.                 break;

  112.             switch (wParam) {
  113.                 case VK_LEFT:
  114.                     if (MoveLeft()) goto MOVED;
  115.                     break;
  116.                 case VK_RIGHT:
  117.                     if (MoveRight()) goto MOVED;
  118.                     break;
  119.                 case VK_UP:
  120.                     if (MoveUp()) goto MOVED;
  121.                     break;
  122.                 case VK_DOWN:
  123.                     if (MoveDown()) goto MOVED;
  124.                     break;
  125.                 case 'N':
  126.                 case 'n':
  127.                         remove("2048.sav");
  128.                         g_bPlayerMoved = 0;
  129.                         InitGame();
  130.                                         /* 修正:新游戏后,脏标记应为 1 */
  131.                     g_bDirty = 1;
  132.                     InvalidateRect(hWnd, &rcGame, FALSE);
  133.                     break;
  134.             }
  135.             break;

  136. MOVED:
  137.             /* 如果玩家是第一次操作,删除旧的存档文件 */
  138.                 if (!g_bPlayerMoved) {
  139.                         remove("2048.sav");    /* 旧存档已无效 */
  140.                         g_bPlayerMoved = 1;
  141.                 }
  142.                 AddRandomTile();
  143.                         /* 修正:任何移动成功并添加了随机块,都应视为状态改变 */
  144.             g_bDirty = 1;
  145.                 if (!CanMove()){
  146.                 game_over = 1;
  147.                 /* 游戏结束 → 更新分数历史并弹出分数板 */
  148.                 UpdateScoreHistory(score);
  149.             DialogBox(hInst, MAKEINTRESOURCE(DLG_SCOREBOARD),
  150.                       hWnd, lpfnScoreboardDlg);
  151.             }
  152.             InvalidateRect(hWnd, &rcGame, FALSE);
  153.             break;

  154.         /* 新增:处理菜单命令(Game、Help菜单) */
  155.         case WM_COMMAND:
  156.             switch (wParam) {
  157.                 case IDM_NEWGAME:
  158.                         remove("2048.sav");
  159.                         g_bPlayerMoved = 0;
  160.                         InitGame();
  161.                         g_bDirty = 1;   /* 修正:新游戏后,脏标记应为 1 */
  162.                     InvalidateRect(hWnd, &rcGame, FALSE);
  163.                     break;
  164.                 case IDM_SAVE:
  165.             SaveGame();
  166.             g_bDirty = 0;
  167.             InvalidateRect(hWnd, &rcGame, FALSE);
  168.             break;
  169.                 case IDM_SCOREBOARD:
  170.             /* 玩家随时查看分数板,当前分数来自全局变量 score */
  171.             DialogBox(hInst, MAKEINTRESOURCE(DLG_SCOREBOARD),
  172.                       hWnd, lpfnScoreboardDlg);
  173.             break;
  174.                 case IDM_HOWTOPLAY:
  175.                     DialogBox(hInst, MAKEINTRESOURCE(DLG_HOWTOPLAY),
  176.                               hWnd, lpfnHowToPlayDlg);
  177.                     break;
  178.                 case IDM_ABOUT:
  179.                     DialogBox(hInst, MAKEINTRESOURCE(DLG_ABOUT),
  180.                               hWnd, lpfnAboutDlg);
  181.                     break;
  182.                 default:
  183.                     return DefWindowProc(hWnd, message, wParam, lParam);
  184.             }
  185.             break;

  186.         /* 新增:处理系统菜单(左上角 About…) */
  187.         case WM_SYSCOMMAND:
  188.             if (wParam == IDSYS_ABOUT) {
  189.                 DialogBox(hInst, MAKEINTRESOURCE(DLG_ABOUT),
  190.                           hWnd, lpfnAboutDlg);
  191.                 return 0;
  192.             }
  193.             /* 其他系统命令(最大化、最小化、移动、关闭等)必须交给默认处理 */
  194.             return DefWindowProc(hWnd, message, wParam, lParam);

  195.         case WM_DESTROY:
  196.             /* ============================================================
  197.                修正:退出时检查是否需要存档提醒
  198.                设计思路:
  199.                1. 如果游戏已经结束 (game_over == 1),直接退出,不提醒。
  200.                2. 如果游戏未结束,但有未保存的改动 (g_bDirty == 1),则触发提醒。
  201.                3. 如果游戏未结束且没有改动 (g_bDirty == 0),说明用户已手动保存过,
  202.                   直接退出,不打扰用户。
  203.             ============================================================ */
  204.             if (!game_over && g_bDirty && g_bPlayerMoved) {
  205.                 /* 弹出可爱的英文提示框 */
  206.                 if (MessageBox(hWnd,
  207.             "Ooh! Meow ^v^ You haven't saved your game!",
  208.             "Save Game?",
  209.             MB_YESNO | MB_ICONQUESTION) == IDYES) {
  210.                     /* 用户选择保存 */
  211.                     SaveGame();
  212.                     g_bDirty = 0;
  213.                 }
  214.             }
  215.                         /* 清理GDI对象和实例地址 */
  216.                         CleanupGDI();
  217.             FreeProcInstance(lpfnAboutDlg);
  218.             FreeProcInstance(lpfnHowToPlayDlg);
  219.                 FreeProcInstance(lpfnScoreboardDlg);
  220.             PostQuitMessage(0);
  221.             break;

  222.         default:
  223.             return DefWindowProc(hWnd, message, wParam, lParam);
  224.     }
  225.     return 0L;
  226. }

  227. /* ============================================================
  228.    WinMain - 程序入口
  229.    ============================================================ */
  230. int PASCAL WinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow)
  231. HANDLE hInstance;
  232. HANDLE hPrevInstance;
  233. LPSTR  lpCmdLine;
  234. int    nCmdShow;
  235. {
  236.     MSG        msg;
  237.     WNDCLASS   wc;
  238.     HWND       hWnd;
  239.     HMENU      hMenu;
  240.     HMENU      hSysMenu;

  241.     srand((unsigned)GetCurrentTime());

  242.     if (!hPrevInstance) {
  243.         wc.style         = CS_HREDRAW | CS_VREDRAW;
  244.         wc.lpfnWndProc   = WndProc;
  245.         wc.cbClsExtra    = 0;
  246.         wc.cbWndExtra    = 0;
  247.         wc.hInstance     = hInstance;
  248.         wc.hIcon = LoadIcon(hInstance, (LPSTR)"AppIcon");
  249.         wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
  250.         wc.hbrBackground = GetStockObject(WHITE_BRUSH);
  251.         wc.lpszMenuName  = NULL;
  252.         wc.lpszClassName = "2048W103";

  253.         if (!RegisterClass(&wc))
  254.             return FALSE;
  255.     }

  256.     hInst = hInstance;

  257.     /* 创建对话框过程的实例地址(必须在创建窗口前准备好) */
  258.     lpfnAboutDlg = MakeProcInstance((FARPROC)AboutDlg, hInstance);
  259.     lpfnHowToPlayDlg = MakeProcInstance((FARPROC)HowToPlayDlg, hInstance);
  260.         lpfnScoreboardDlg = MakeProcInstance((FARPROC)ScoreboardDlg, hInstance);

  261.     /* 加载菜单栏(资源名为 "GAME_MENU",在 2048.rc 中定义) */
  262.     hMenu = LoadMenu(hInstance, (LPSTR)"GAME_MENU");
  263.     if (!hMenu) {
  264.         MessageBox(NULL, "Menu load failed!", NULL, MB_OK);
  265.         return FALSE;
  266.     }

  267.     /* Windows 1.03 兼容样式:WS_TILEDWINDOW(等同于 WS_TILED|WS_CAPTION|WS_SYSMENU|WS_SIZEBOX) */
  268.     /* CreateWindow 只传 10 个参数,无最后的 lpParam */
  269.     hWnd = CreateWindow("2048W103",
  270.                         "2048 - Windows 1.03",
  271.                         WS_TILEDWINDOW,
  272.                         10, 10, 280, 320,
  273.                         NULL,
  274.                         hMenu,
  275.                         hInstance,
  276.                         NULL);

  277.     if (!hWnd)
  278.         return FALSE;

  279.     /* ============================================================
  280.        修正:启动时检测并加载存档
  281.        设计思路:
  282.        1. 尝试加载存档。如果成功,游戏状态恢复,存档文件被立即删除。
  283.        2. 如果加载失败(没有存档或文件损坏),则开始一个全新的游戏。
  284.        3. 无论是加载成功还是开始新游戏,都设置脏标记 g_bDirty = 1,
  285.           因为有新状态产生,应视为未保存。
  286.     ============================================================ */
  287.     if (LoadGame()) {
  288.         /* 成功加载存档,存档文件已被 LoadGame 内部删除 */
  289.         InvalidateRect(hWnd, &rcGame, FALSE);
  290.         g_bDirty = 0;   /* 恢复后的状态,视为未保存的新状态 */
  291.         g_bPlayerMoved = 0;     /* 加载存档不算玩家操作 */
  292.     } else {
  293.         /* 没有存档,开始新游戏 */
  294.         InitGame();
  295.         g_bDirty = 1;   /* 新游戏视为未保存 */
  296.         g_bPlayerMoved = 0;     /* 无存档启动,自动新游戏,视为玩家“开始了一局” */
  297.     }
  298.        
  299.         /* 在系统菜单(左上角)添加分隔线后追加 About… */
  300.     /* 先获取系统菜单副本 */
  301.     hSysMenu = GetSystemMenu(hWnd, FALSE);
  302.     if (hSysMenu) {
  303.         /* 添加分隔线(横杠) */
  304.         ChangeMenu(hSysMenu, 0, (LPSTR)NULL, 0,
  305.                    MF_APPEND | MF_SEPARATOR);
  306.         /* 添加 "About..." 命令 */
  307.         ChangeMenu(hSysMenu, 0, (LPSTR)"About...",
  308.                    IDSYS_ABOUT, MF_APPEND | MF_STRING);
  309.     }
  310.        
  311.     ShowWindow(hWnd, nCmdShow);
  312.     UpdateWindow(hWnd);

  313.     while (GetMessage(&msg, NULL, 0, 0)) {
  314.         TranslateMessage(&msg);
  315.         DispatchMessage(&msg);
  316.     }

  317.     return msg.wParam;
  318. }

  319. /* ============================================================
  320.    游戏逻辑
  321.    ============================================================ */

  322. void InitGame()
  323. {
  324.     int i, j;
  325.     for (i = 0; i < BOARD_SIZE; i++)
  326.         for (j = 0; j < BOARD_SIZE; j++)
  327.             board[i][j] = 0;

  328.     score = 0;
  329.     game_over = 0;

  330.     AddRandomTile();
  331.     AddRandomTile();
  332. }

  333. void AddRandomTile()
  334. {
  335.     int empty[BOARD_SIZE * BOARD_SIZE][2];
  336.     int count, pos;
  337.     int i, j;

  338.     count = 0;
  339.     for (i = 0; i < BOARD_SIZE; i++)
  340.         for (j = 0; j < BOARD_SIZE; j++)
  341.             if (board[i][j] == 0) {
  342.                 empty[count][0] = i;
  343.                 empty[count][1] = j;
  344.                 count++;
  345.             }

  346.     if (count == 0) return;

  347.     pos = rand() % count;
  348.     i = empty[pos][0];
  349.     j = empty[pos][1];
  350.     board[i][j] = (rand() % 10 == 0) ? 4 : 2;
  351. }

  352. int MoveLeft()
  353. {
  354.     int moved;
  355.     int i, j, k;

  356.     moved = 0;

  357.     for (i = 0; i < BOARD_SIZE; i++) {
  358.         /* 压缩 */
  359.         for (j = 0; j < BOARD_SIZE; j++) {
  360.             if (board[i][j] == 0) {
  361.                 for (k = j + 1; k < BOARD_SIZE; k++) {
  362.                     if (board[i][k] != 0) {
  363.                         board[i][j] = board[i][k];
  364.                         board[i][k] = 0;
  365.                         moved = 1;
  366.                         break;
  367.                     }
  368.                 }
  369.             }
  370.         }

  371.         /* 合并 */
  372.         for (j = 0; j < BOARD_SIZE - 1; j++) {
  373.             if (board[i][j] != 0 && board[i][j] == board[i][j+1]) {
  374.                 board[i][j] *= 2;
  375.                 score += board[i][j];
  376.                 board[i][j+1] = 0;
  377.                 moved = 1;
  378.             }
  379.         }

  380.         /* 再压缩 */
  381.         for (j = 0; j < BOARD_SIZE; j++) {
  382.             if (board[i][j] == 0) {
  383.                 for (k = j + 1; k < BOARD_SIZE; k++) {
  384.                     if (board[i][k] != 0) {
  385.                         board[i][j] = board[i][k];
  386.                         board[i][k] = 0;
  387.                         moved = 1;
  388.                         break;
  389.                     }
  390.                 }
  391.             }
  392.         }
  393.     }

  394.     return moved;
  395. }

  396. int MoveRight()
  397. {
  398.     int moved;
  399.     int i, j;
  400.     int tmp;

  401.     for (i = 0; i < BOARD_SIZE; i++)
  402.         for (j = 0; j < BOARD_SIZE / 2; j++) {
  403.             tmp = board[i][j];
  404.             board[i][j] = board[i][BOARD_SIZE - 1 - j];
  405.             board[i][BOARD_SIZE - 1 - j] = tmp;
  406.         }

  407.     moved = MoveLeft();

  408.     for (i = 0; i < BOARD_SIZE; i++)
  409.         for (j = 0; j < BOARD_SIZE / 2; j++) {
  410.             tmp = board[i][j];
  411.             board[i][j] = board[i][BOARD_SIZE - 1 - j];
  412.             board[i][BOARD_SIZE - 1 - j] = tmp;
  413.         }

  414.     return moved;
  415. }

  416. int MoveUp()
  417. {
  418.     int moved;
  419.     int i, j;
  420.     int tmp;

  421.     for (i = 0; i < BOARD_SIZE; i++)
  422.         for (j = i + 1; j < BOARD_SIZE; j++) {
  423.             tmp = board[i][j];
  424.             board[i][j] = board[j][i];
  425.             board[j][i] = tmp;
  426.         }

  427.     moved = MoveLeft();

  428.     for (i = 0; i < BOARD_SIZE; i++)
  429.         for (j = i + 1; j < BOARD_SIZE; j++) {
  430.             tmp = board[i][j];
  431.             board[i][j] = board[j][i];
  432.             board[j][i] = tmp;
  433.         }

  434.     return moved;
  435. }

  436. int MoveDown()
  437. {
  438.     int moved;
  439.     int i, j;
  440.     int tmp;

  441.     /* 转置 */
  442.     for (i = 0; i < BOARD_SIZE; i++)
  443.         for (j = i + 1; j < BOARD_SIZE; j++) {
  444.             tmp = board[i][j];
  445.             board[i][j] = board[j][i];
  446.             board[j][i] = tmp;
  447.         }

  448.     /* 水平翻转 */
  449.     for (i = 0; i < BOARD_SIZE; i++)
  450.         for (j = 0; j < BOARD_SIZE / 2; j++) {
  451.             tmp = board[i][j];
  452.             board[i][j] = board[i][BOARD_SIZE - 1 - j];
  453.             board[i][BOARD_SIZE - 1 - j] = tmp;
  454.         }

  455.     moved = MoveLeft();

  456.     /* 翻转回来 */
  457.     for (i = 0; i < BOARD_SIZE; i++)
  458.         for (j = 0; j < BOARD_SIZE / 2; j++) {
  459.             tmp = board[i][j];
  460.             board[i][j] = board[i][BOARD_SIZE - 1 - j];
  461.             board[i][BOARD_SIZE - 1 - j] = tmp;
  462.         }

  463.     /* 转置回来 */
  464.     for (i = 0; i < BOARD_SIZE; i++)
  465.         for (j = i + 1; j < BOARD_SIZE; j++) {
  466.             tmp = board[i][j];
  467.             board[i][j] = board[j][i];
  468.             board[j][i] = tmp;
  469.         }

  470.     return moved;
  471. }

  472. int CanMove()
  473. {
  474.     int i, j;

  475.     for (i = 0; i < BOARD_SIZE; i++)
  476.         for (j = 0; j < BOARD_SIZE; j++)
  477.             if (board[i][j] == 0)
  478.                 return 1;

  479.     for (i = 0; i < BOARD_SIZE; i++)
  480.         for (j = 0; j < BOARD_SIZE - 1; j++)
  481.             if (board[i][j] == board[i][j+1])
  482.                 return 1;

  483.     for (i = 0; i < BOARD_SIZE - 1; i++)
  484.         for (j = 0; j < BOARD_SIZE; j++)
  485.             if (board[i][j] == board[i+1][j])
  486.                 return 1;

  487.     return 0;
  488. }

  489. /* ============================================================
  490.    GDI绘图
  491.    ============================================================ */

  492. void DrawCell(hDC, value, x, y, w, h)
  493. HDC hDC;
  494. int value, x, y, w, h;
  495. {
  496.     char buf[16];
  497.     int  len;
  498.     int  idx;
  499.     HBRUSH hBrush, hOldBrush;
  500.     HPEN   hPen, hOldPen;

  501.     idx = ValueToIndex(value);
  502.     hBrush = hBrushes[idx];

  503.     if (value >= 8 && value < 128)
  504.         hPen = hPenWhite;
  505.     else
  506.         hPen = hPenBlack;

  507.     hOldBrush = SelectObject(hDC, hBrush);
  508.     hOldPen   = SelectObject(hDC, hPen);

  509.     Rectangle(hDC, x, y, x + w, y + h);

  510.     SelectObject(hDC, hOldBrush);
  511.     SelectObject(hDC, hOldPen);

  512.     if (value != 0) {
  513.         SetBkMode(hDC, TRANSPARENT);
  514.         SetTextColor(hDC, (value >= 8 && value < 128) ?
  515.                           RGB(255,255,255) : RGB(0,0,0));
  516.         sprintf(buf, "%d", value);
  517.         len = strlen(buf);
  518.         TextOut(hDC, x + (w - len * 8) / 2, y + (h - 14) / 2, buf, len);
  519.     }
  520. }

  521. void DrawBoard(hDC)
  522. HDC hDC;
  523. {
  524.     int i, j;
  525.     int x, y;
  526.     int top = 45;

  527.     for (i = 0; i < BOARD_SIZE; i++) {
  528.         for (j = 0; j < BOARD_SIZE; j++) {
  529.             x = MARGIN + j * (CELL_W + MARGIN);
  530.             y = top + i * (CELL_H + MARGIN);
  531.             DrawCell(hDC, board[i][j], x, y, CELL_W, CELL_H);
  532.         }
  533.     }
  534. }

  535. /* 预创建所有GDI对象 */
  536. void InitGDI()
  537. {
  538.     hBrushes[0]  = GetStockObject(LTGRAY_BRUSH);
  539.     hBrushes[1]  = CreateSolidBrush(RGB(238, 228, 218));  /* 2 */
  540.     hBrushes[2]  = CreateSolidBrush(RGB(237, 224, 200));  /* 4 */
  541.     hBrushes[3]  = CreateSolidBrush(RGB(242, 177, 121));  /* 8 */
  542.     hBrushes[4]  = CreateSolidBrush(RGB(245, 149, 99));   /* 16 */
  543.     hBrushes[5]  = CreateSolidBrush(RGB(246, 124, 95));   /* 32 */
  544.     hBrushes[6]  = CreateSolidBrush(RGB(246, 94, 59));    /* 64 */
  545.     hBrushes[7]  = CreateSolidBrush(RGB(237, 207, 114));  /* 128 */
  546.     hBrushes[8]  = CreateSolidBrush(RGB(237, 207, 114));  /* 256 */
  547.     hBrushes[9]  = CreateSolidBrush(RGB(237, 207, 114));  /* 512 */
  548.     hBrushes[10] = CreateSolidBrush(RGB(237, 207, 114));  /* 1024 */
  549.     hBrushes[11] = CreateSolidBrush(RGB(237, 207, 114));  /* 2048 */
  550.     hBrushes[12] = CreateSolidBrush(RGB(237, 207, 114));  /* 更大 */

  551.     hPenBlack = GetStockObject(BLACK_PEN);
  552.     hPenWhite = GetStockObject(WHITE_PEN);
  553. }

  554. /* 程序退出时统一清理 */
  555. void CleanupGDI()
  556. {
  557.     int i;
  558.     for (i = 1; i < 13; i++) {
  559.         if (hBrushes[i])
  560.             DeleteObject(hBrushes[i]);
  561.     }
  562. }

  563. int ValueToIndex(value)
  564. int value;
  565. {
  566.     switch (value) {
  567.         case 0:    return 0;
  568.         case 2:    return 1;
  569.         case 4:    return 2;
  570.         case 8:    return 3;
  571.         case 16:   return 4;
  572.         case 32:   return 5;
  573.         case 64:   return 6;
  574.         case 128:  return 7;
  575.         case 256:  return 8;
  576.         case 512:  return 9;
  577.         case 1024: return 10;
  578.         case 2048: return 11;
  579.         default:   return 12;
  580.     }
  581. }

  582. /* ============================================================
  583.    对话框过程
  584.    ============================================================ */

  585. /* About 对话框过程 */
  586. BOOL FAR PASCAL AboutDlg(hDlg, message, wParam, lParam)
  587. HWND     hDlg;
  588. unsigned message;
  589. WORD     wParam;
  590. LONG     lParam;
  591. {
  592.     if (message == WM_COMMAND && wParam == IDOK) {
  593.         EndDialog(hDlg, TRUE);
  594.         return TRUE;
  595.     }
  596.     if (message == WM_INITDIALOG)
  597.         return TRUE;
  598.     return FALSE;
  599. }

  600. /* HowToPlay 对话框过程(新增) */
  601. BOOL FAR PASCAL HowToPlayDlg(hDlg, message, wParam, lParam)
  602. HWND     hDlg;
  603. unsigned message;
  604. WORD     wParam;
  605. LONG     lParam;
  606. {
  607.     if (message == WM_COMMAND && wParam == IDOK) {
  608.         EndDialog(hDlg, TRUE);
  609.         return TRUE;
  610.     }
  611.     if (message == WM_INITDIALOG)
  612.         return TRUE;
  613.     return FALSE;
  614. }

  615. /* ============================================================
  616.    新增:存档文件读写函数
  617.    设计思路:
  618.    1. SaveGame() 将当前棋盘状态写入文本文件 2048.sav。
  619.       文本格式:简单可靠,第一行为分数和游戏状态,随后为棋盘矩阵。
  620.    2. LoadGame() 从 2048.sav 读取状态。
  621.       成功返回 1,失败返回 0。
  622.       成功读取后,应立即删除存档文件,避免下次启动干扰。
  623.    3. 存档仅保存最后一次手动保存的状态。
  624.    4. 游戏启动时会检查并加载存档,加载后文件被删除。
  625.       之后的游戏过程都不会再使用该文件,直到用户再次手动存档。
  626.    ============================================================ */

  627. void SaveGame()
  628. {
  629.     FILE *fp;
  630.     int i, j;

  631.     fp = fopen("2048.sav", "w");
  632.     if (fp == NULL)
  633.         return;

  634.     fprintf(fp, "%d %d\n", score, game_over);
  635.     for (i = 0; i < BOARD_SIZE; i++) {
  636.         for (j = 0; j < BOARD_SIZE; j++)
  637.             fprintf(fp, "%d ", board[i][j]);
  638.         fprintf(fp, "\n");
  639.     }
  640.     fclose(fp);
  641. }

  642. int LoadGame()
  643. {
  644.     FILE *fp;
  645.     int i, j;

  646.     fp = fopen("2048.sav", "r");
  647.     if (fp == NULL)
  648.         return 0;   /* 不存在存档 */

  649.     if (fscanf(fp, "%d %d", &score, &game_over) != 2) {
  650.         fclose(fp);
  651.         return 0;
  652.     }
  653.     for (i = 0; i < BOARD_SIZE; i++)
  654.         for (j = 0; j < BOARD_SIZE; j++) {
  655.             if (fscanf(fp, "%d", &board[i][j]) != 1) {
  656.                 fclose(fp);
  657.                 return 0;
  658.             }
  659.         }
  660.     fclose(fp);

  661.     /* 修正:成功加载后,立即删除存档文件,避免下次启动干扰 */
  662.     /* remove("2048.sav"); */
  663.     return 1;
  664. }

  665. /* ============================================================
  666.    分数历史记录读写函数(二进制文件,彻底避免玩家修改)
  667.    ============================================================ */

  668. /* 从文件读取历史,成功返回 TRUE,失败返回 FALSE */
  669. BOOL GetScoreHistory(pHist)
  670. ScoreHistory *pHist;
  671. {
  672.     FILE *fp;

  673.     fp = fopen("2048.his", "rb");
  674.     if (fp == NULL)
  675.         return FALSE;

  676.     if (fread(pHist, sizeof(ScoreHistory), 1, fp) != 1) {
  677.         fclose(fp);
  678.         return FALSE;
  679.     }
  680.     fclose(fp);
  681.     return TRUE;
  682. }

  683. /* 将历史写入文件(若文件不存在则创建) */
  684. void SetScoreHistory(pHist)
  685. ScoreHistory *pHist;
  686. {
  687.     FILE *fp;

  688.     fp = fopen("2048.his", "wb");
  689.     if (fp == NULL)
  690.         return;

  691.     fwrite(pHist, sizeof(ScoreHistory), 1, fp);
  692.     fclose(fp);
  693. }

  694. /* 游戏结束时调用,传入本局得分,自动更新历史文件 */
  695. void UpdateScoreHistory(newScore)
  696. int newScore;
  697. {
  698.     ScoreHistory hist;
  699.     int i;

  700.     /* 若文件不存在,则初始化空历史 */
  701.     if (!GetScoreHistory(&hist)) {
  702.         hist.highscore = 0;
  703.         hist.count = 0;
  704.         for (i = 0; i < 5; i++)
  705.             hist.recent[i] = 0;
  706.     }

  707.     /* 更新最高分 */
  708.     if (newScore > hist.highscore)
  709.         hist.highscore = newScore;

  710.     /* 将新分数插入 recent[0],后移原有分数 */
  711.     for (i = 4; i > 0; i--)
  712.         hist.recent[i] = hist.recent[i - 1];
  713.     hist.recent[0] = newScore;
  714.     if (hist.count < 5)
  715.         hist.count++;

  716.     SetScoreHistory(&hist);
  717. }

  718. /* ============================================================
  719.    分数板对话框过程
  720.    ============================================================ */
  721. BOOL FAR PASCAL ScoreboardDlg(hDlg, message, wParam, lParam)
  722. HWND     hDlg;
  723. unsigned message;
  724. WORD     wParam;
  725. LONG     lParam;
  726. {
  727.     ScoreHistory hist;
  728.     char buf[512];
  729.     char line[64];
  730.     int i;

  731.     switch (message) {

  732.     case WM_INITDIALOG:
  733.         /* 读取历史,若失败则显示空数据 */
  734.         if (!GetScoreHistory(&hist)) {
  735.             hist.highscore = 0;
  736.             hist.count = 0;
  737.         }

  738.         /* 构建多行文本(注意换行用 \r\n) */
  739.         buf[0] = '\0';
  740.         sprintf(buf + strlen(buf), "Best Score: %d\r\n", hist.highscore);
  741.         sprintf(buf + strlen(buf), "------------------------\r\n");
  742.         sprintf(buf + strlen(buf), "Recent Scores:\r\n");
  743.         for (i = 0; i < hist.count && i < 5; i++) {
  744.             sprintf(line, "%d. %d\r\n", i + 1, hist.recent[i]);
  745.             strcat(buf, line);
  746.         }
  747.         sprintf(buf + strlen(buf), "------------------------\r\n");
  748.         sprintf(buf + strlen(buf), "Current Score: %d", score);

  749.         /* 设置到编辑框(IDC_SCOREBOARD_EDIT) */
  750.         SetDlgItemText(hDlg, IDC_SCOREBOARD_EDIT, buf);
  751.                 /* 设置完文本后,将焦点转移到 OK 按钮,避免编辑框文字被选中 */
  752.         SetFocus(GetDlgItem(hDlg, IDOK));
  753.         return FALSE;   /* 返回 FALSE 表示你主动设置了焦点,系统不再自动设置 */

  754.     case WM_COMMAND:
  755.         if (wParam == IDOK) {
  756.             EndDialog(hDlg, TRUE);
  757.             return TRUE;
  758.         }
  759.         break;
  760.     }
  761.     return FALSE;
  762. }
复制代码

有趣的是win1sdk缺少叠加窗口类的api但是为win1写的程序却可以巧妙的运行!!!在添加256色驱动后有种古早Android风格!!!!
效果图:

win1运行效果1

win1运行效果1

win1运行效果2

win1运行效果2

win1运行效果3

win1运行效果3

win1运行效果4

win1运行效果4

win2运行效果

win2运行效果

win1 256色运行效果

win1 256色运行效果

评分

参与人数 1荣誉 +5 收起 理由
liuhongrun2022 + 5

查看全部评分

小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 6 小时前 | 显示全部楼层
看到你用 Windows 1.03 SDK + MSC 4.0 写 2048,真是勇气可嘉!Win16 时代的 API 确实与 Win32 有差异,但核心思想相通,这段代码里藏着不少早期 Windows 编程的“独门绝技”。下面我帮你梳理一下代码中需要注意的地方,以及一些可以改进的点。

  • 数组声明与使用不匹配:代码里出现了 board = 0 之类的写法,但 board 没有被定义成二维数组。应该统一声明为 int board[4][4];,之后所有访问都要带下标。
  • 随机放置方块的逻辑有误:AddRandomTileempty 数组只记录了最后一个空位,应该用数组存储所有空位坐标再随机选取。
  • GDI 画刷数组 InitGDIhBrushes 被重复赋值,导致只创建了最后一个画刷,前面的全部泄漏。应该声明为 HBRUSH hBrushes[13];
  • Move 函数里的转置和翻转操作中,数组索引错误(例如 board = board; 缺少 [i][j][j][i])。正确写法需要明确指定两个下标。
  • 对话框过程的类型转换:Win16 下 MakeProcInstance 返回的是 FARPROC,但 DialogBox 最后一个参数期望的是 DLGPROC 类型,需要强制转换。
  • 资源 ID 宏未定义:代码中引用了 IDM_NEWGAMEDLG_ABOUT 等,但头文件 2048.h 没有给出,需要确认 .rc 文件中的定义。


虽然代码有不少 bug,但你选择的这个环境本身就是“地狱难度”——K&R 函数定义、无重叠窗口的 API、全局数据段、MakeProcInstance……这些在 Win32 中早已消失。能跑起来并截图,说明你已经克服了很大的挑战。

建议先修正数组声明和 GDI 泄漏,然后逐步调试 Move 逻辑。如果你愿意,我可以针对某个具体问题(比如 MoveLeft 的实现)给出在 Win16 下正确的 K&R 写法。继续加油,老平台能带来很多底层理解!

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 5 小时前 | 显示全部楼层
FishC 发表于 2026-6-24 12:35
看到你用 Windows 1.03 SDK + MSC 4.0 写 2048,真是勇气可嘉!Win16 时代的 API 确实与 Win32 有差异,但 ...

ok记录了!!!
我有点不太敢动它了!!!每次修改之后一堆报错或者出现莫名其妙的问题!!!
特别是不小心用现代编辑器打开后被插入的各种隐形字符!头大!!!!
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 5 小时前 | 显示全部楼层
健康快乐 发表于 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 命令查看原始文件是否正常。例如:

  1. TYPE 2048.C | MORE
复制代码


如果看到奇怪字符,说明编辑器污染了。这时直接恢复备份,或者用 DEBUG 手动清除。

你已经在非常苛刻的环境下跑起来了,这是很多现代程序员做不到的。放轻松,每次只解决一个小问题,比如先把数组 board 改成二维的、修好画刷数组。祝你成功!

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2026-6-24 18:39

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表