News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests
NB: Posting URL's See here: Posted URL Change

Main Menu

Connect 4

Started by zedd151, February 24, 2025, 08:02:03 AM

Previous topic - Next topic

zedd151

Over the years, I have started then abandoned making a Connect 4 type game for Windows. In my current rendition, I am foregoing using bitmaps, but creating the pieces and board using gdi shapes instead. I have some code started, and will polish it up and add some comments before I post it. Stay tuned...

The initial code will only demonstrate the techniques use for selecting a column, and placing pieces.

I will update the code incrementally when I have ample time to devote to it, adding bits and pieces until the game is finished, including the code to automate the second player. :smiley:  The game logic is very similar to that needed for Tic-Tac-Toe, and shouldn't be very difficult.

Initial version. Only the first column is working at this time. Left click for red piece, right click for blue piece. No computer automation just yet.

Keep in mind that I am still working on the code, and I know it is not perfect. Yet! I intend to trim down the code, adding loops where appropriate. I tend to not use a lot of loops initially, until the code performs as I want. Then work on implementing loops after everything works to my liking.
        include    \masm32\include\masm32rt.inc
        include    files\drawpix.inc
        includelib files\drawpix.lib
   
        WinMain    proto :dword
        WndProc    proto :dword, :dword, :dword, :dword
        CenterMain  proto :dword, :dword, :dword, :dword, :dword
        SetSBParts  proto :dword, :dword
        drawboard  proto :dword
       
        wwidth      equ 900
        wheight    equ 600
        cwidth      equ 640 ;; desired client area size
        cheight    equ 640 ;; window size auto adjusted according to desired client size.
        stst        equ WS_CHILD or WS_VISIBLE or SBS_SIZEGRIP
        player      equ 1          ; denotes human player
        computer    equ 2          ; denotes computer
        delay      equ 40        ; animation delay

    .data
        board      db 64 dup (0)
        hWnd        dd 0
        hInstance  dd 0
        hStatus    dd 0
        hBack      dd 0
       
    .const
        DispName    db "Connect 4", 0
        Class      db "Game_Class", 0
        dn          dd DispName ;; dword as pointer
        cn          dd Class ;; dword as pointer

    .code
    start:
        invoke GetModuleHandle, 0
        mov hInstance, eax
        invoke WinMain, hInstance
        invoke ExitProcess, eax

    WndProc proc hWin:dword, uMsg:dword, wParam:dword, lParam:dword
    local hDC:dword, ps:PAINTSTRUCT, rct:RECT, mDC:dword, hBrush:dword
      .if    uMsg == WM_LBUTTONUP
        mov ecx, lParam
        xor edx, edx
        mov dx, cx
        shr ecx, 16
        lea esi, board
        .if    edx >=  40 && edx < 120
        .if byte ptr [esi] == 0 && byte ptr [esi+7] == 0
            mov byte ptr [esi], player
            invoke InvalidateRect, hWin, 0, 0
            invoke SetTimer, hWin, 1, (delay*7), 0
          .endif
        .elseif edx >= 120 && edx < 200
        .elseif edx >= 200 && edx < 280
        .elseif edx >= 280 && edx < 360
        .elseif edx >= 360 && edx < 440
        .elseif edx >= 440 && edx < 520
        .elseif edx >= 520 && edx < 600
        .endif
      .elseif uMsg == WM_RBUTTONUP
        mov ecx, lParam
        xor edx, edx
        mov dx, cx
        shr ecx, 16
        lea esi, board
        .if    edx >=  40 && edx < 120
        .if byte ptr [esi] == 0 && byte ptr [esi+7] == 0
            mov byte ptr [esi], computer
            invoke InvalidateRect, hWin, 0, 0
            invoke SetTimer, hWin, 2, (delay*7), 0
          .endif
        .elseif edx >= 120 && edx < 200
        .elseif edx >= 200 && edx < 280
        .elseif edx >= 280 && edx < 360
        .elseif edx >= 360 && edx < 440
        .elseif edx >= 440 && edx < 520
        .elseif edx >= 520 && edx < 600
        .endif
      .elseif uMsg == WM_TIMER
        .if    wParam == 1
          invoke SetTimer, hWin, 1, delay, 0
          lea esi, board
          .if    byte ptr [esi] == 1 && byte ptr [esi+7] == 0
            mov byte ptr [esi], 0
            mov byte ptr [esi+7], 1
            invoke InvalidateRect, hWin, 0, 0
            ret
          .endif
          .if byte ptr [esi+7] == 1 && byte ptr [esi+14] == 0
            mov byte ptr [esi+7], 0
            mov byte ptr [esi+14], 1
            invoke InvalidateRect, hWin, 0, 0
            ret
          .endif
          .if byte ptr [esi+14] == 1 && byte ptr [esi+21] == 0
            mov byte ptr [esi+14], 0
            mov byte ptr [esi+21], 1
            invoke InvalidateRect, hWin, 0, 0
            ret
          .endif
          .if byte ptr  [esi+21] == 1 && byte ptr [esi+28] == 0
            mov byte ptr [esi+21], 0
            mov byte ptr [esi+28], 1
            invoke InvalidateRect, hWin, 0, 0
            ret
          .endif
          .if byte ptr  [esi+28] == 1 && byte ptr [esi+35] == 0
            mov byte ptr [esi+28], 0
            mov byte ptr [esi+35], 1
            invoke InvalidateRect, hWin, 0, 0
            ret
          .endif
          .if byte ptr  [esi+35] == 1 && byte ptr [esi+42] == 0
            mov byte ptr [esi+35], 0
            mov byte ptr [esi+42], 1
            invoke InvalidateRect, hWin, 0, 0
            invoke KillTimer, hWin, 1
            ret
          .endif
        .elseif wParam == 2
          invoke SetTimer, hWin, 2, delay, 0
          lea esi, board
          .if    byte ptr [esi] == 2 && byte ptr [esi+7] == 0
            mov byte ptr [esi], 0
            mov byte ptr [esi+7], 2
            invoke InvalidateRect, hWin, 0, 0
            ret
          .endif
          .if byte ptr [esi+7] == 2 && byte ptr [esi+14] == 0
            mov byte ptr [esi+7], 0
            mov byte ptr [esi+14], 2
            invoke InvalidateRect, hWin, 0, 0
            ret
          .endif
          .if byte ptr [esi+14] == 2 && byte ptr [esi+21] == 0
            mov byte ptr [esi+14], 0
            mov byte ptr [esi+21], 2
            invoke InvalidateRect, hWin, 0, 0
            ret
          .endif
          .if byte ptr  [esi+21] == 2 && byte ptr [esi+28] == 0
            mov byte ptr [esi+21], 0
            mov byte ptr [esi+28], 2
            invoke InvalidateRect, hWin, 0, 0
            ret
          .endif
          .if byte ptr  [esi+28] == 2 && byte ptr [esi+35] == 0
            mov byte ptr [esi+28], 0
            mov byte ptr [esi+35], 2
            invoke InvalidateRect, hWin, 0, 0
            ret
          .endif
          .if byte ptr  [esi+35] == 2 && byte ptr [esi+42] == 0
            mov byte ptr [esi+35], 0
            mov byte ptr [esi+42], 2
            invoke InvalidateRect, hWin, 0, 0
            invoke KillTimer, hWin, 2
            ret
          .endif
        .endif
      .elseif uMsg == WM_PAINT
        invoke BeginPaint, hWin, addr ps
        mov hDC, eax
        invoke drawboard, hDC
        invoke EndPaint, hWin, addr ps
        mov eax, 0
        ret
      .elseif uMsg == WM_CREATE
        invoke CenterMain, hWin, cwidth, cheight, wwidth, wheight
        invoke MoveWindow, hStatus, 0, 0, 0, 0, TRUE
        invoke SetSBParts, hWnd, hStatus
      .elseif uMsg == WM_COMMAND
        .if wParam == 1900
          invoke MessageBox, 0, dn, cn, MB_OK
        .endif
      .elseif uMsg == WM_SIZE
        invoke MoveWindow, hStatus, 0, 0, 0, 0, TRUE
        invoke SetSBParts, hWnd, hStatus
      .elseif uMsg == WM_CLOSE
        invoke DeleteObject, hBack
      .elseif uMsg == WM_DESTROY
        invoke PostQuitMessage, 0
        return 0
      .endif
        invoke DefWindowProc, hWin, uMsg, wParam, lParam
        ret
    WndProc endp

    WinMain proc hInst:dword                          ; main window creation
    local wc:WNDCLASSEX, msg:MSG, Wtx:dword, Wty:dword
        mov wc.cbSize, sizeof WNDCLASSEX
        mov wc.style, 0
        mov wc.lpfnWndProc, offset WndProc
        mov wc.cbClsExtra, 0
        mov wc.cbWndExtra, 0
        mrm wc.hInstance, hInst
        invoke CreateSolidBrush, 00007F00h
        mov hBack, eax
        mov wc.hbrBackground, eax
        mov wc.lpszMenuName, 0
        mov wc.lpszClassName, offset Class
        invoke LoadIcon, 0, IDI_APPLICATION
        mov wc.hIcon, eax
        mov wc.hIconSm, eax
        invoke LoadCursor, 0, IDC_ARROW
        mov wc.hCursor, eax
        invoke RegisterClassEx, addr wc
        invoke CreateWindowEx, 0, cn, dn, WS_OVERLAPPEDWINDOW, 0, 0, wwidth, wheight, 0, 0, hInst, 0
        mov hWnd, eax
        invoke CreateStatusWindow, stst, 0, hWnd, 0
        mov hStatus, eax
        invoke SetSBParts, hWnd, hStatus
        invoke LoadMenu, hInst, 600
        invoke SetMenu, hWnd, eax
        invoke ShowWindow, hWnd, SW_SHOWNORMAL
        invoke UpdateWindow, hWnd
      StartLoop:
        invoke GetMessage, addr msg, 0, 0, 0
        cmp eax, 0
        je ExitLoop
        invoke TranslateMessage, addr msg
        invoke DispatchMessage, addr msg
        jmp StartLoop
      ExitLoop:
        return msg.wParam
    WinMain endp
   
    CenterMain proc hWin:dword, clwd:dword, clht:dword, wd:dword, ht:dword ;  does as the name suggests
    local rct:RECT, h:dword, w:dword, x:dword, y:dword
      @@: ; dynamically adjusting width of the window for proper client rect size
        invoke MoveWindow, hWin, x, y, wd, ht, 0
        invoke GetClientRect, hWin, addr rct
        mov eax, rct.right
        sub eax, rct.left
      .if eax > clwd ; desired client rect width
        dec wd
        jmp @b
      .elseif eax < clwd
        inc wd
        jmp @b
      .endif
      @@: ; dynamically adjusting height of the window for proper client rect size
        invoke MoveWindow, hWin, x, y, wd, ht, 0
        invoke GetClientRect, hWin, addr rct
        mov eax, rct.bottom
        sub eax, rct.top
      .if eax > clht ; desired client rect height
        dec ht
        jmp @b
      .elseif eax < clht
        inc ht
        jmp @b
      .endif
        add ht, 22  ; padding
        invoke GetSystemMetrics, SM_CYMENU
        add ht, eax
        invoke SystemParametersInfoA, SPI_GETWORKAREA, 0, addr rct, 0
        mov eax, rct.right
        sub eax, wd
        sar eax, 1
        mov x, eax
        mov eax, rct.bottom
        sub eax, ht
        sar eax, 1
        mov y, eax
        invoke MoveWindow, hWin, x, y, wd, ht, TRUE
        ret
    CenterMain endp
       
    SetSBParts proc hWin:dword, hStat:dword    ; set status bar parts
    local rct:RECT
        .data?
            sbParts dd 4 dup (?)
        .code
        invoke GetClientRect, hWin, addr rct
        mov eax, rct.right
        sub eax, rct.left
        shr eax, 2
        mov ecx, eax
        mov [sbParts+0], ecx
        add ecx, eax
        mov [sbParts+4], ecx
        add ecx, eax
        mov [sbParts+8], ecx
        mov [sbParts+12], -1
        invoke SendMessage, hStat, SB_SETPARTS, 4, addr sbParts
        ret
    SetSBParts endp
   
    drawboard proc hDC:dword  ; code for drawing the board
    local dim:dword          ; using drawpix.inc and drawpix.lib
        push esi              ; which contains the actual code used for the drawing functions.
        push edi
        push ebx
        xor ebx, ebx
        mov esi, 40
        mov edi, 40
        mov dim, 80
      top2:
        mov esi, 40
      @@:
      .if edi == 40
        .if byte ptr [board+ebx] == 0
          invoke SetBlank,    hDC, 0000DFDFh, 0000DFDFh,  esi, edi, dim, dim    ; set top row blank
        .elseif byte ptr [board+ebx] == 1
          invoke SetPieceTop, hDC, 0000DFDFh, 0, 000000FFh,  esi, edi, dim, dim  ; unless occupied (1 = player)
        .elseif byte ptr [board+ebx] == 2
          invoke SetPieceTop, hDC, 0000DFDFh, 0, 00FF0000h,  esi, edi, dim, dim  ; unless occupied (2 = computer)
        .endif
      .else
        .if byte ptr [board+ebx] == 0
          invoke SetBlank, hDC, 0000DFDFh, 0,            esi, edi, dim, dim  ; same rules as above
        .elseif byte ptr [board+ebx] == 1                                    ; except in the rows from here down
          invoke SetPiece, hDC, 0000DFDFh, 0, 000000FFh,  esi, edi, dim, dim  ; all the 'boxes' have a border
        .elseif byte ptr [board+ebx] == 2
          invoke SetPiece, hDC, 0000DFDFh, 0, 00FF0000h,  esi, edi, dim, dim
        .endif
      .endif
        add esi, 80
        inc ebx
        cmp esi, 560
        jb @b
        add edi, 80
        cmp edi, 560
        jb top2
        pop ebx
        pop edi
        pop esi
        ret
    drawboard endp

    end start

The delay for the 'animation' is controlled by the equate 'delay' (Appropriately named)  :biggrin: , if it is too slow or fast for you...

This game is a Work - In - Progress. I will update this post if, and when, I make substantial changes to the game. All updates will be made in this post, removing the previous post contents as necessary. A more convenient method of ensuring that the most up to date code can be easily found, without digging through any replies and comments that may follow, for the latest version.  :badgrin:
¯\_(ツ)_/¯
tictactoe_final is finished    :smiley:

zedd151

#1
I will soon be replacing the bitmaps used with gdi32 drawing functions display the game pieces, and working on the rest of the game code and most important the game logic.  :azn:
¯\_(ツ)_/¯
tictactoe_final is finished    :smiley: