代码:
;*********************************************************************************
; 程  序: snake.exe                                                             *
; 源文件: snake.asm                                                             *
; 环  境: masmplus 1.2                                                          *
; 编  译: ml /c /coff /Fo"snake.obj" ".\snake.asm"                              *
; 链  接: link /out:snake.exe /subsystem:windows /merge:.rdata=.text snake.obj  *
; 作  者: hiroyukki                                                             *
; 日  期: 2011.03.04                                                            *
; 玩  法: P: 暂停和继续 C: 切换颜色模式 上下左右WASD键: 调整蛇头方向            *
;          Q或ESC: 退出游戏 N: 开始新游戏                                        *
; 说  明:     模型如下,因为总共的格子是有限的,所以我采取了静态内存的方法,以  *
;          避免每吃一个食物就申请一次内存的麻烦,在程序初始化时,就把所有空格上  *
;          的结点初始化好了,类似内存池的机制。                                  *
;              程序共 23 * 17 = 391个格子,故在 data? 域申请 391 个 DWORD。这些  *
;          DWORD的4个字节从高到低分别表示一个结点的 x位置 y位置 结点类型(食物还  *
;          是蛇身,绘制时根据此指定不同颜色) 有效标志(即蛇到达的位置的结点才是   *
;          有效的)。                                                             *
;              程序时刻记录着当前头的结点,在每次移动时根据方向不同来获取下一个  *
;          将要到达的位置,如果该位置是墙或蛇身,则撞死,否则把下一个位置上的结  *
;          点标志为有效(同时应该把当前蛇尾标志为无效)。                          *
;              当前的所有有效结点都存放在另一个数组中,此数组看做一个结点的链表  *
;          数组的第一个元素是蛇尾,最后一个非0元素是蛇头。每当吃掉一个食物或者   *
;          前进一步,视做换掉了蛇头。但是不一样的是,吃掉食物时只简单地把新结点  *
;          放到数组的第一个0元素,即成为新的蛇头。而前进的情况下,在数组最后加   *
;          入新结点后,要把蛇尾,即数组中第一个结点移除,做法是把数组后面的结点  *
;          依次左移。绘制时其实是使用这个链表里的元素进行绘制,所以要保持链表里  *
;          元素与全局结点列表里的相应元素同步。                                  *
;              共 23 * 17个结点,第一个结点位置为 (1, 1),最后一个为(23, 17)。   *
;          这些结点在一个一维数组中,则由 (x, y)得到相应结点在一维数组中的索引的 *
;          公式为 (y - 1) * 23 + (x - 1)                                         *
;              为了进一步减小可执行文件体积,合并了只读数据段和代码段,然后使用  *
;          upx 压,EXE为 4096 bytes 。                                           *
;*********************************************************************************

    .386
    .model flat, stdcall
    option casemap:none
;***************************************************************************
; Include
;***************************************************************************
include    windows.inc
include    gdi32.inc
includelib  gdi32.lib
include    user32.inc
includelib  user32.lib
include    kernel32.inc
includelib  kernel32.lib
includelib  Coredll.lib

;***************************************************************************
; 这个结构并未使用,只是大致描述下一个结点的样子,一个节点是一个DWORD      *
; 从高位到低位分别是: x坐标 y坐标 类型(食物或蛇身) 有效标志               *
; 每个字段点一个字节                                                       *
;***************************************************************************
Node  struct
  x    BYTE    ?
  y    BYTE    ?
  ntype  BYTE    ?
  bValid  BYTE        ?
Node  ends

TIMER      equ    1
FOOD      equ    0
BODY      equ    1
RIGHT      equ    2
LEFT      equ    3
UP        equ    4
DOWN      equ    5
;***************************************************************************
; 数据段
;***************************************************************************

    .data?
hInstance    dd      ?
hWinMain    dd      ?
hDc        dd      ?
isPaused    dd      ?
isAlive      dd      ?
bRanColor    dd      ?
bTurnPending  dd      ?
direction    dd      ?
food      dd          ?
nodes           dd     391 dup (?)
nodeList    dd    391 dup (?)
curX      dd      ?
curY      dd      ?
foodX      dd      ?
foodY      dd      ?
score      dd      ?
rClient      RECT    <>
wndWidth    dd      ?
wndHeight    dd      ?
szScoreBuffer  db    32 dup (?)
seed      dd      ?
speed      dd      ?
timerCounts    dd      ?
eatCounts    dd      ?

    .const
szClassName      db    '1', 0
szCaptionMain    db    '贪食蛇', 0
szCaptionPause    db    '已暂停', 0
szCaptionDie    db    '游戏结束,请按N键开始新游戏', 0
szDown        db    '下', 0
szUp        db    '上', 0
szRight        db    '右', 0
szLeft        db    '左', 0
szScore        db    '得分:%d', 0

;***************************************************************************
; 代码段
;***************************************************************************
    .code
;***************************************************************************
; 随机数生成
;***************************************************************************
_NextInt    proc uses edx ebx  max
  push edx
  invoke  GetTickCount
  add eax, seed
  add seed, eax
  xor edx, edx
  mov ebx, max
  div ebx
  xor seed, eax
  mov eax, edx
  inc eax
  pop edx
  ret
_NextInt    endp

;***************************************************************************
; 初始化所有蛇身结点把前三个置为有效
;***************************************************************************
_InitNodes    proc
  push edi
  push edx
  push ebx
  push ecx
  xor edi, edi
@@:
  xor edx, edx
  mov eax, edi
  mov ebx, 17h
  div ebx
  ; 商是 y 坐标,余数是 x 坐标
  xor ecx, ecx
  or ecx, edx
  inc ecx
  shl ecx, 8
  or ecx, eax
  inc ecx
  shl ecx, 8
  or ecx, BODY
  shl ecx, 8
  ; ecx 为结点, ebx 为有效结点列表
  ; nodeList 数组首元素即蛇尾,最后一个不为0的元素为蛇头
  xor ebx, ebx
  .if edi < 4
    or ecx, 1
    mov ebx, ecx
  .endif
  mov [offset nodes + edi * 4], ecx
  mov [offset nodeList + edi * 4], ebx
  inc edi
  .if edi >= 187h
    jmp @F
  .endif
  jmp @B
@@:
  pop ecx
  pop ebx
  pop edx
  pop edi
  ret
_InitNodes    endp

;***************************************************************************
; 节点构造函数,参数分别是X坐标,Y坐标,类型(食物还是身体),有效标志
;***************************************************************************
_CreateNode    proc  nx, ny, nntype, nInvalid
  xor eax, eax
  or eax, nx
  shl eax, 8
  or eax, ny
  shl eax, 8
  or eax, nntype
  shl eax, 8
  or eax, nInvalid
  ret
_CreateNode    endp

;***************************************************************************
; 查看指定结点是否在(x, y)
;***************************************************************************
_IsNodeAt  proc node, x, y
  push ebx
  push ecx

  mov eax, node
  mov ebx, eax
  mov ecx, eax
  shr ebx, 24
  and ebx, 0ffh
  sub ebx, x

  shr ecx, 16
  and ecx, 0ffh
  sub ecx, y

  .if ecx == 0 && ebx == 0
    xor eax, eax
  .endif

  pop ecx
  pop ebx
  ret
_IsNodeAt  endp

;***************************************************************************
; 绘制单个节点
;***************************************************************************
_DrawNode  proc  uses ebx ecx _hDC, hNode
  local @stColor: COLORREF
  local @hBrush: HBRUSH
  local @hOldBrush: HBRUSH
  local @stRect: RECT
  push ecx
  push ebx

  mov  eax, hNode
  ; 先取标志状态,如果是无效,直接退出
  and eax, 000000ffh
  .if !eax
    jmp @F
  .endif
  invoke _IsNodeAt, hNode, curX, curY
  mov ecx, eax
  mov eax, hNode
  and eax, 0000ff00h
  shr eax, 8
  .if  (eax == BODY) && (ecx == 0)
    mov @stColor, 00ffffffh
  .elseif eax == BODY
    .if bRanColor
      invoke _NextInt, 255
      mov ecx, eax
      shl ecx, 8
      invoke _NextInt, 255
      or ecx, eax
      shl ecx, 8
      invoke _NextInt, 255
      or ecx, eax
      mov @stColor, ecx
    .else
      mov @stColor, 000000ffh
    .endif
  .else
    mov  @stColor, 0000ff00h
  .endif
  invoke  CreateSolidBrush, @stColor
  mov  @hBrush, eax
  invoke  SelectObject, _hDC, @hBrush
  mov @hOldBrush, eax
  ; 算出左右要画的地方
  mov  ecx, hNode
  and ecx, 0ff000000h
  shr ecx, 24
  xor eax, eax
  mov  al, cl
  mov  bl, 20
  mul  bl
  sub  eax, 4
  mov  @stRect.left, eax
  add  eax, 18
  mov  @stRect.right, eax
  ; 算出上下要画的地方
  mov  ecx, hNode
  and ecx, 00ff0000h
  shr ecx, 16
  xor eax, eax
  mov  al, cl
  mov  bl, 20
  mul  bl
  sub eax, 4
  mov  @stRect.top, eax
  add  eax, 18
  mov  @stRect.bottom, eax
  invoke  FillRect, _hDC, addr @stRect, NULL
  invoke SelectObject, _hDC, @hOldBrush
  ; 狂他妈漏啊……
  invoke DeleteObject, @hBrush

@@:
  pop ebx
  pop ecx
  ret
_DrawNode  endp

;***************************************************************************
; 画蛇身,其实就是遍历所有结点画,绘画函数会自己根据结点的状态来选择绘
; 制方式。
;***************************************************************************
_DrawBody  proc  uses ecx edi _hDC
  push edi
  push ecx
  push eax
  xor edi, edi
  mov ecx, 187h
@@:
  mov eax, [offset nodeList + edi * 4]
  .if !eax
    jmp @F
  .endif
  invoke _DrawNode, _hDC, eax
  inc edi
  loop @B

@@:
  pop eax
  pop ecx
  pop edi
  ret
_DrawBody  endp

;***************************************************************************
; 绘制分数
;***************************************************************************
_DrawScore  proc  _hDC
  local @oldColor: COLORREF
  local @oldBkMode: DWORD
  local @szBuffer[128]: BYTE
  local @hFont: HFONT
  local @hOldFont: HFONT
  local @hPen: HPEN
  local @hOldPen: HPEN
  local @strLen: DWORD

  push ebx
  invoke wsprintf, offset szScoreBuffer, offset szScore, score
  ; 求字符串长度
  xor eax, eax
@@:
  mov bl, BYTE ptr [offset szScoreBuffer + eax]
  .if bl != 0
    inc eax
    jmp @B
  .endif
  mov @strLen, eax

  invoke SetBkMode, _hDC, TRANSPARENT
  mov @oldBkMode, eax
  invoke SetTextColor, _hDC, 0000ffffh
  mov @oldColor, eax

  invoke CreateFont, 14, 7, 0, 0, 8, FALSE, FALSE, FALSE, GB2312_CHARSET, OUT_CHARACTER_PRECIS, CLIP_CHARACTER_PRECIS, 5, DEFAULT_PITCH, NULL
  mov @hFont, eax
  invoke SelectObject, _hDC, eax
  mov @hOldFont, eax
  invoke CreatePen, PS_SOLID, 1, 00ffffffh
  mov @hPen, eax
  invoke SelectObject, _hDC, eax
  mov @hOldPen, eax

  invoke TextOut, _hDC, 410, 15, offset szScoreBuffer, @strLen
  invoke MoveToEx, _hDC, 10, 10, 0
  invoke LineTo, _hDC, 480, 10
  invoke LineTo, _hDC, 480, 360
  invoke LineTo, _hDC, 10, 360
  invoke LineTo, _hDC, 10, 10

  invoke SetBkMode, _hDC, @oldBkMode
  invoke SetTextColor, _hDC, @oldColor
  invoke SelectObject, _hDC, @hOldFont
  invoke SelectObject, _hDC, @hOldPen
  invoke DeleteObject, @hFont
  invoke DeleteObject, @hOldPen

  pop ebx
  ret
_DrawScore  endp

;***************************************************************************
; 获得指定位置上的结点
;***************************************************************************
_GetNodeAt  proc  xLocation, yLocation
  push ebx
  mov eax, yLocation
  dec eax
  mov ebx, 17h
  mul ebx
  add eax, xLocation
  dec eax
  mov eax, [offset nodes + eax * 4]
  pop ebx
  ret
_GetNodeAt  endp

;***************************************************************************
; 由传入的X Y 位置查询目标结点的状态(是否激活)
;***************************************************************************
_QueryNodeStat  proc  xLocation, yLocation
  invoke _GetNodeAt, xLocation, yLocation
  and eax, 000000ffh
  ret
_QueryNodeStat  endp

;***************************************************************************
; 激活或压制一个已处于激活状态的结点
; 参数分别是 x 坐标, y坐标, 是否激活的布尔变量
;***************************************************************************
_Activate  proc  xLocation, yLocation, bActivate
  push ebx
  push edi
  mov eax, yLocation
  dec eax
  mov ebx, 17h
  mul ebx
  add eax, xLocation
  dec eax
  mov edi, eax

  mov eax, [offset nodes + eax * 4]
  .if bActivate
    or eax, 0000001h
  .else
    and eax, 0ffffff00h
  .endif
  mov [offset nodes + edi * 4], eax

  pop edi
  pop ebx
  ret
_Activate  endp

;***************************************************************************
; 下一个食物,总是随机出现。并且不会出现在蛇身上
; 这种实现似乎比较2,更好的方法应该是把所有结点串成两个链表,蛇身链表和非
; 蛇身链表。每次随机地从非蛇身链表里取出结点插入蛇身链表中,这样就不会造成
; 随机生成的位置在蛇身上从而重新生成了。
; 重新生巢呗陨在蛇身将要填充满整个区域时性能急剧下降。
;***************************************************************************
_NextFood    proc uses ebx
  invoke _NextInt, 17
  mov foodY, eax
  invoke _NextInt, 23
  mov foodX, eax
  invoke _QueryNodeStat, foodX, foodY
  .if eax
    invoke _NextFood
  .endif
  invoke  _CreateNode, foodX, foodY, FOOD, 1
  mov  food, eax
  ret
_NextFood    endp

;***************************************************************************
; 往队列头加一个结点,刚吃了一顿或走了一步后都会调用此函数。
; 第一个参数是要加入的结点,第二个是指示是否移除队列尾。正常移动时需要
; 在队列头加结点的同时移除队列尾,以达到好像在移动的假象……
;***************************************************************************
_AddBody  proc  node, bRmvTail
  push edi
  push ebx
  push ecx

  ; 如果是刚吃的,先把类型改成 BODY
  mov eax, node
  or eax, 00000100h
  xor edi, edi
@@:
  mov ebx, [offset nodeList + edi * 4]
  .if ebx != 0
    inc edi
    jmp @B
  .endif
  mov [offset nodeList + edi * 4], eax

  ; 如果要移除队列尾,则先把队列尾的激活状态压下去,然后集体左移一个索引位置
  ; 此时edi里保存着队列应有的长度
  .if bRmvTail
    ; 取第一个元素,这是队列头,先反激活它
    mov eax, [nodeList]
    mov ebx, eax
    and ebx, 0ff000000h
    shr ebx, 24
    mov ecx, eax
    and ecx, 00ff0000h
    shr ecx, 16
    invoke _Activate, ebx, ecx, FALSE

    ; 然后移动这个队列,把后面的都左移
    mov ecx, edi
    xor edi, edi
@@:
    mov eax, [offset nodeList + edi * 4 + 4]
    mov [offset nodeList + edi * 4], eax
    inc edi
    loop @B

    mov [offset nodeList + edi * 4], 0
  .endif

  pop ecx
  pop ebx
  pop edi
  ret
_AddBody  endp

;***************************************************************************
; 吃掉食物,吃掉时候分数增加,并且生成下一个食物
;***************************************************************************
_PieFromSky  proc
  mov eax, score
  add eax, 10
  mov score, eax
  mov eax, eatCounts
  .if eax != 3
    inc eatCounts
  .else
    mov eax, speed
    .if eax > 1
      dec speed
    .endif
    mov eatCounts, 0
  .endif
  invoke _GetNodeAt, foodX, foodY
  invoke _AddBody, eax, FALSE
  invoke _NextFood
  ret
_PieFromSky  endp

;***************************************************************************
; 新游戏,初始化各种状态
;***************************************************************************
_NewGame    proc
  invoke _InitNodes
  invoke _NextFood
  mov bTurnPending, FALSE
  mov  isPaused, 0
  mov  isAlive, 1
  mov score, 0
  mov speed, 8
  mov timerCounts, 0
  mov eatCounts, 0
  mov curX, 4
  mov curY, 1
  mov direction, RIGHT
  ret
_NewGame    endp

;***************************************************************************
; 蛇身移动
;***************************************************************************
_Move    proc
  local @nextX: DWORD
  local @nextY: DWORD
  push ebx
  push curX
  push curY
  pop @nextY
  pop @nextX
  mov bTurnPending, FALSE
  .if direction == RIGHT
    inc @nextX
  .elseif direction == DOWN
    inc @nextY
  .elseif direction == LEFT
    dec @nextX
  .elseif direction == UP
    dec @nextY
  .endif

  .if (@nextX == 0) || (@nextX > 23) || (@nextY == 0) || (@nextY > 17)
    mov isAlive, 0
    jmp @F
  .endif
  invoke _QueryNodeStat, @nextX, @nextY
  .if eax
    mov isAlive, 0
    jmp @F
  .endif

  mov eax, foodX
  mov ebx, foodY
  .if (@nextX == eax) && (@nextY == ebx)
    invoke _PieFromSky
  .endif

  ; 头的处理,下一个位置即将激活
  invoke _Activate, @nextX, @nextY, 1
  push @nextX
  push @nextY
  pop curY
  pop curX

  ; 把新头加入队列头,并指示移除队列尾
  invoke _GetNodeAt, curX, curY
  invoke _AddBody, eax, TRUE

@@:
  pop ebx
  ret
_Move    endp

;***************************************************************************
; 窗口过程
;***************************************************************************
_ProcWinMain  proc hWnd, uMsg, wParam, lParam
  local @stPs:PAINTSTRUCT
  local @stRect:RECT
  local @hBmp: HBITMAP
  local @hOldBmp: HBITMAP
  local @memDC: HDC
  local @hDC: HDC

  mov    eax, uMsg
  .if  eax == WM_PAINT
    invoke BeginPaint, hWnd, addr @stPs
    mov @hDC, eax
    invoke CreateCompatibleBitmap, @hDC, wndWidth, wndHeight
    mov @hBmp, eax
    invoke CreateCompatibleDC, @hDC
    mov @memDC, eax
    invoke SelectObject, @memDC, @hBmp
    mov @hOldBmp, eax
    invoke _DrawBody, @memDC
    invoke _DrawNode, @memDC, food
    invoke _DrawScore, @memDC
    invoke BitBlt, @hDC, rClient.left, rClient.top, wndWidth, wndHeight, @memDC, rClient.left, rClient.top, SRCCOPY
    invoke SelectObject, @memDC, @hOldBmp
    invoke DeleteObject, @hBmp
    invoke DeleteDC, @memDC
    invoke EndPaint, hWnd, addr @stPs
  .elseif  eax == WM_TIMER
    .if  !isAlive
      invoke  SetWindowText, hWnd, offset szCaptionDie
      jmp @F
    .endif
    .if  isPaused
      jmp @F
    .endif

    mov eax, timerCounts
    .if speed != eax
      inc timerCounts
      jmp @F
    .endif
    mov timerCounts, 0
    invoke _Move
    invoke  InvalidateRect, hWnd, addr rClient, FALSE

  .elseif eax == WM_CLOSE
    invoke  KillTimer, hWnd, TIMER
    invoke DestroyWindow, hWinMain
    invoke PostQuitMessage, NULL
  .elseif  eax == WM_CREATE
    invoke  SetTimer, hWnd, TIMER, 100, NULL
    invoke GetClientRect, hWnd, offset rClient
    mov bRanColor, TRUE
    mov eax, rClient.right
    sub eax, rClient.left
    mov wndWidth, eax
    mov eax, rClient.bottom
    sub eax, rClient.top
    mov wndHeight, eax
    invoke  _NewGame
  .elseif  eax == WM_KEYDOWN
    mov  eax, wParam
    ; 按P键暂停游戏
    .if  eax == 'P'
      .if isPaused
        and  isPaused, 0
        invoke  SetWindowText, hWnd, offset szCaptionMain
      .else
        or  isPaused, 1
        invoke  SetWindowText, hWnd, offset szCaptionPause
      .endif
    ; 按了上键
    .elseif  eax == 'W' || eax == VK_UP
      .if isPaused || bTurnPending
        jmp @F
      .endif
      mov  eax, direction
      .if  eax == RIGHT || eax == LEFT
        push  UP
        pop  direction
        invoke  SetWindowText, hWnd, offset szUp
        mov bTurnPending, TRUE
      .endif
    ; 按了下键
    .elseif  eax == 'S' || eax == VK_DOWN
      .if isPaused || bTurnPending
        jmp @F
      .endif
      mov  eax, direction
      .if  eax == RIGHT || eax == LEFT
        push  DOWN
        pop  direction
        invoke  SetWindowText, hWnd, offset szDown
        mov bTurnPending, TRUE
      .endif
    ; 按了左键
    .elseif  eax == 'A' || eax == VK_LEFT
      .if isPaused || bTurnPending
        jmp @F
      .endif
      mov  eax, direction
      .if  eax == UP || eax == DOWN
        push  LEFT
        pop  direction
        invoke  SetWindowText, hWnd, offset szLeft
        mov bTurnPending, TRUE
      .endif
    ; 按了右键
    .elseif  eax == 'D' || eax == VK_RIGHT
      .if isPaused || bTurnPending
        jmp @F
      .endif
      mov  eax, direction
      .if  eax == UP || eax == DOWN
        push  RIGHT
        pop  direction
        invoke  SetWindowText, hWnd, offset szRight
        mov bTurnPending, TRUE
      .endif
    .elseif eax == 'C' 
      .if bRanColor
        and bRanColor, 0
      .else
        or bRanColor, 1
      .endif
    ; Q或者ESC退出游戏
    .elseif eax == 'Q' || eax == VK_ESCAPE
      invoke  PostQuitMessage, 0
    ; 开始新游戏
    .elseif  eax == 'N'
      invoke  _NewGame
      invoke  SetWindowText, hWnd, offset szCaptionMain
    .endif
  .else
    invoke DefWindowProc, hWnd, uMsg, wParam, lParam
    ret
  .endif

@@:
  xor eax, eax
  ret
_ProcWinMain  endp

;***************************************************************************
; 入口函数
;***************************************************************************
_Main  proc
  local  @stWndClass:WNDCLASSEX
  local  @stMsg:MSG

  mov  food, FALSE
  invoke RtlZeroMemory, addr @stWndClass, sizeof @stWndClass

  invoke GetModuleHandle, NULL
  mov @stWndClass.hInstance, eax
  invoke LoadCursor, NULL, IDC_ARROW
  mov @stWndClass.hCursor, eax
  invoke LoadIcon, NULL, IDI_INFORMATION
  mov @stWndClass.hIcon, eax
  mov @stWndClass.cbSize, sizeof WNDCLASSEX
  mov @stWndClass.style, CS_HREDRAW or CS_VREDRAW
  mov @stWndClass.lpfnWndProc, offset _ProcWinMain
  mov @stWndClass.hbrBackground, COLOR_WINDOW
  mov @stWndClass.lpszClassName, offset szClassName
  invoke RegisterClassEx, addr @stWndClass

  ; 显示窗口
  invoke CreateWindowEx, WS_EX_CLIENTEDGE, offset szClassName, offset szCaptionMain,\
       WS_OVERLAPPEDWINDOW and not WS_MAXIMIZEBOX and not WS_THICKFRAME, 200, 100, \
       500, 404, NULL, NULL, hInstance, NULL
  mov hWinMain, eax
  invoke ShowWindow, hWinMain, SW_SHOWNORMAL
  invoke UpdateWindow, hWinMain

  ; 消息循环
  .while TRUE
    invoke GetMessage, addr @stMsg, NULL, 0, 0
    .break .if eax == 0
    invoke TranslateMessage, addr @stMsg
    invoke DispatchMessage, addr @stMsg
  .endw
  ret
_Main  endp

start:
  call _Main
  invoke ExitProcess, NULL
end start