News:

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

Main Menu

4x4 Game

Started by zedd151, February 24, 2025, 03:29:56 PM

Previous topic - Next topic

zedd151

Since I am pulling out some old code to repost, here is the 4x4 Game.
Object of the game, is to use the arrow keys to move the pieces of the randomly generated puzzle (example shown here):


To acheive the numerically ordered layout as shown here:
(The Number on the Title Bar is how many moves it took to solve the puzzle.)  :wink2:


Keyboard Mappings:
Left Arrow = move left
Up Arrow = move up
Right Arrow = move right
Down Arrow = move down

Other keyboard functions:
Enter = auto solve from position
Esc = quit game
R = restart game
N = New Game

The game itself works as is. There are a few minor errors in the code that don't seem to bother the operation of the program i.e., it will not crash. Tested on Windows xp, 7 and 10. If anyone would like to address the issues in the code, go for it. This project is basically abandoned - if you think that you have found a bug, fix it yourself - it's all yours!.  :greensml:  Happy coding!  :biggrin:

I have a brand new version of this program posted below...

The file 'strings.dat' are for use with the autosolver, and must be present to re-assemble the program. At the time, it was a convenient way to store the least known number of moves from one position to another - and it seems to work pretty good.  :azn:
¯\_(ツ)_/¯   :azn:

'As we don't do "requests", show us your code first.'  -  hutch—

sinsi

Works fine here :thumbsup:

I wonder if the strange behaviour is caused by WndProc using esi and edi but not saving them?

zedd151

#2
Quote from: sinsi on February 24, 2025, 04:25:10 PMWorks fine here :thumbsup:

I wonder if the strange behaviour is caused by WndProc using esi and edi but not saving them?
No strange behavior has been noticed, else I would have acted accordingly. Mind you this code is probably six years old, and I have no intentions of doing any more work on it - since it works as-is. (read below).
There are some calls that rely on ebx for instance, having a value already set in ebx inside the called function. There may be other functions that also rely on esi and edi having preset values. It never caused any issues, so I never bothered to preserve the registers. You can do that for yourself, if you would like, since this is abandonware (read below).  :smiley:
¯\_(ツ)_/¯   :azn:

'As we don't do "requests", show us your code first.'  -  hutch—

zedd151

#3
I have had an epiphany while I slept. When I have a free weekend, I will try to decipher my old code here (my notes I had when writing it, have been lost), and bring it up to 21st century standards I.e., win32 ABI specs.  :tongue:  It of course may take some time. So, the project is no longer abandoned, but reclaimed as a project that will be enhanced/fixed/renewed.  :biggrin:

I will probably work on this concurrently with my Connect 4 and Tic-Tac-Toe code.  :smiley:  I need a break from my top secret projects.  :tongue:

P.S. I found an earlier version of the original 4x4 code... but not my notes (for auto solve, and the GUI code).  :biggrin:
4x4 console game. It 10 years old!!! Wow, time flies.
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;;                                                ;;
    ;; 4x4 game - console version - Movember 5, 2015  ;;
    ;;                    by zedd                    ;;
    ;;                                                ;;
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;;                                                ;;
    ;;  The object of the game is to use the arrow    ;;
    ;;  keys on the keyboard to arrange the numbered  ;;
    ;;  'pieces' so that they are in numerically      ;;
    ;;  ascending sequence:                          ;;
    ;;                                                ;;
    ;;  ---------------                              ;;
    ;;    1  2  3  4                              ;;
    ;;    5  6  7  8                              ;;
    ;;    9  10  11  12                              ;;
    ;;  13  14  15                                  ;;
    ;;  ---------------                              ;;
    ;;                                                ;;
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

    include \masm32\include\masm32rt.inc

        set_board      proto :dword, :dword
        randb          proto :dword
        moveit          proto :dword, :dword, :dword, :dword, :dword, :dword, :dword
        board_to_string proto :dword, :dword
        find_mt        proto :dword, :dword

    .data
        sqct            dd 4 dup (0)
        board          dd 4 dup (0)
        str1            db 16 dup (0)
        str2            db 16 dup (0)
        str3            db 16 dup (0)
        str4            db 16 dup (0)
        mt_offs        dd 0

        ; equates for key presses
        quit  = 1Bh                        ; <Esc> key
        up    = 48h                        ; 'up' arrow key
        left  = 4Bh                        ; 'left' arrow key
        right = 4Dh                        ; 'right' arrow key
        down  = 50h                        ; 'down' arrow key

    .code

    start:

        invoke set_board, addr board, addr sqct
        jmp printit                        ; skip over the move routines
    moveleft:
        invoke moveit, addr board, mt_offs,3,7,11,15,1
        jmp printit                        ; jump to print results of move
    moveup:
        invoke moveit, addr board, mt_offs,12,13,14,15,4
        jmp printit                        ; jump to print results of move
    moveright:
        invoke moveit, addr board, mt_offs,0,4,8,12,-1
        jmp printit                        ; jump to print results of move
    movedown:
        invoke moveit, addr board, mt_offs,0,1,2,3,-4
                                            ; stay here to print results of move
    printit:
        invoke board_to_string, addr board, addr str1
        invoke find_mt, addr board, addr mt_offs
        cls
        print offset str1,13,10
        print offset str2,13,10
        print offset str3,13,10
        print offset str4,13,10,13,10

    startloop:                              ; keyboard message loop
        call crt__getch                    ; retrieve keyboard char
        cmp eax, quit
            jz quitting                    ; get ouitta here!
        cmp eax, left
            jz moveleft                    ; move left
        cmp eax, up
            jz moveup                      ; move up
        cmp eax, right
            jz moveright                    ; move right
        cmp eax, down
            jz movedown                    ; move down
    jmp startloop

    quitting:
        invoke ExitProcess, 0              ; adios amigo!

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

    randb proc base:dword
    .data
        seed dd 12345678
    .code
        push edx
        push ecx
        invoke GetTickCount
        add eax, seed
        test eax, 80000000h
        jz  @F
        add eax, 7fffffffh
      @@: 
        xor edx, edx
        mov ecx, 127773
        div ecx
        mov ecx, eax
        mov eax, 16807
        mul edx
        mov edx, ecx
        mov ecx, eax
        mov eax, 2836
        mul edx
        sub ecx, eax
        xor edx, edx
        mov eax, ecx
        mov seed, ecx
        div base
        mov eax, edx
        inc eax
        pop ecx
        pop edx
        ret 4
    randb endp

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

    set_board proc pbrd:dword, psqct:dword
        push esi
        push edi
        mov esi, pbrd                      ; pointer to byte values representing the game board
        mov edi, psqct                      ; pointer to byte array acting as 'valid piece' counter
    toppa:
        push ecx
        invoke randb, 16                    ; generate random value for location
        pop ecx
        mov edx, eax
        dec edx
        push edx
        invoke randb, 15                    ; generate random value for game piece
        pop edx
        mov ecx, eax
        dec ecx
        ; both of these values must be zero to create a new valid game piece

        cmp byte ptr [edi+ecx], 0          ; check if location is empty
        jnz @f
        cmp byte ptr [esi+edx], 0          ; check if piece value is not already used
        jnz @f

        mov byte ptr [esi+edx], al          ; write new piece value to location
        mov byte ptr [edi+ecx], 1          ; set counter for piece value to 1
                                            ; indicating the value has been used already
    @@:
        xor eax, eax                        ; clear eax for the next step
        xor ecx, ecx                        ; clear ecx for the next step
    @@:
        add al, byte ptr [edi+ecx]          ; adding up all of the piece value counters
        inc ecx
        cmp ecx, 16                        ; check all 16 possible locations
        jl @b
        cmp eax, 15                        ; check all possible piece values
        jnz toppa
        pop edi
        pop esi
        ret
    set_board endp

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

    moveit proc pbrd:dword, mt:dword, i1:dword, i2:dword, i3:dword, i4:dword, v7:dword
        push esi
        push edi
        mov esi, pbrd                      ; pointer to game board
        mov ecx, mt                        ; pointer to empty square
        cmp ecx, i1                        ; check against illegal move #1
        jz @f
        cmp ecx, i2                        ; check against illegal move #2
        jz @f
        cmp ecx, i3                        ; check against illegal move #3
        jz @f
        cmp ecx, i4                        ; check against illegal move #4
        jz @f
        mov edi, ecx                        ; move empty location to edi
        add edi, v7                        ; add movement offset
        mov al, [esi+ecx]                  ; swap values of the two squares
        mov dl, [esi+edi]                  ; source square now becomes the empty square
        mov [esi+edi], al                  ; empty square now becomes filled with source value
        mov [esi+ecx], dl
    @@:
        pop edi
        pop esi
        ret
    moveit endp

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

    find_mt proc pbrd:dword, pmtoffs:dword  ; simple proc that scans all locations
        xor eax, eax                        ; to locate the empty square
        mov ecx, pbrd                      ; pointer to game board
    @@:
        cmp byte ptr [ecx+eax], 0          ; loooking for zero
        jz @f
        inc eax
        jmp @b
    @@:
        mov ecx, pmtoffs
        mov [ecx], eax                      ; saving the location of the empty square
        ret
    find_mt endp

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

    board_to_string proc brd:dword, pstr1:dword
        push esi
        push edi
        mov esi, brd                        ; pointer to game board
        mov edi, pstr1                      ; pointer to string buffer
        xor edx, edx
    top:
        xor ecx, ecx

        @@:
            xor eax, eax
            movzx eax, byte ptr [esi+ecx]  ; get hexadecimal digit from board
            aaa                            ; if greater than 9 split into 2 decimal digits
            add ax, 3030h                  ; add 30h to each byte,
                                            ; effectively converting the bytes to ascii chars

                cmp ax, 3030h              ; if pair of bytes equals ascii '00'
                jnz gg
                mov ax, 2020h              ; replace with 2 spaces
                gg:

                cmp ah, 30h                ; if leading digit is zero
                jnz hh
                mov ah, 20h                ; replace with space
                hh:

            mov byte ptr [edi+edx], ah      ; write leading ascii digit to string
            inc edx                        ; increment string index
            mov byte ptr [edi+edx], al      ; write trailing ascii digit to string
            inc edx                        ; increment string index
            mov byte ptr [edi+edx], 20h    ; write space
            inc edx                        ; increment string index
            inc ecx                        ; increment source index
            cmp ecx, 4                      ; compare to 4
        jnz @b                              ; not 4? - jmp back

        cmp edx, 3Ch                        ; compare to 40h - 4
        jz done                            ; if equal, we're done!

        add edx, 4                          ; -- else -- add 4 to string index
        add esi, 4                          ; increment source offset by 4
    jmp top                                ; loop back to top
        done:
        pop edi                            ; restore esi, edi
        pop esi
        ret                                ; return to caller
    board_to_string endp

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

end start
There is a serious issue with this early version. The random board generator generates a solvable game only 50% of the time. The later version starts with a solved game, and scrambles the tiles (many times using valid moves) randomly to ensure every game is solvable. A cute lil game nonetheless.

About 4x4 console:
about 4x4 console version (final)

Written with MASM32.
November 5, 2015
Coded by zedd in the twilight hours.
Built from the ground up, brick-by-brick.
Bugs were fully  tested - and they are
working properly!
A blast from the past!
¯\_(ツ)_/¯   :azn:

'As we don't do "requests", show us your code first.'  -  hutch—

zedd151

#4
New version of the 4x4 game.
This version uses a dialog box as program interface, it also does not use any bitmaps.

The only keyboard functions...
Keyboard Mappings:
Left Arrow = move left
Up Arrow = move up
Right Arrow = move right
Down Arrow = move down

Upon game completion a message box is displayed indicating that the game has been won. All generated games guaranteed to be winnable.

After clicking 'OK' or closing the message box a new game will start immediately.
To close the program click on the red X in upper right corner at any time.



First draft of the new version of the code:
    include \masm32\include\masm32rt.inc

        DlgProc    proto :dword, :dword, :dword, :dword
        randb      proto :dword
        set_board  proto
        scramble    proto :dword, :dword, :dword, :dword, :dword
        painting    proto :dword
       
        cwidth      equ 512    ; desired client area width
        cheight    equ 512    ; desired clien area height
        vpad        equ 16      ; vertical padding for DrawText
        hpad        equ -1      ; horizontal padding for DrawText
        reps        equ  800    ; number of times the initial game board is randomly scrambled

    .data?
    align 16
        board      label byte
        a1          db ?
        a2          db ?
        a3          db ?
        a4          db ?
        b1          db ?
        b2          db ?
        b3          db ?
        b4          db ?
        c1          db ?
        c2          db ?
        c3          db ?
        c4          db ?
        d1          db ?
        d2          db ?
        d3          db ?
        d4          db ?
       
        hInstance  dd ?
        hFont1      dd ?
        GrayPen    dd ?
        WhitePen    dd ?
        GrayBrush  dd ?
       
    .const
        gover  db "Game Over!", 0
        finito  db "Finished!", 0
        align 16
        x1      dd  0 ; board x coordinates
        x2      dd 128
        x3      dd 256
        x4      dd 384
        y1      dd  0 ; board y coordinates
        y2      dd 128
        y3      dd 256
        y4      dd 384
       
    .code

    ;; revised version of dwtoa from masm32.lib - returns string length
    dwtoa_revised proc dwValue:DWORD, lpBuffer:DWORD
        push ebx
        push esi
        push edi
        mov eax, dwValue
        mov edi, [lpBuffer]
        test eax,eax
        jnz sign
      zero:
        mov word ptr [edi],30h
        jmp dtaexit
      sign:
        jns pos
        mov byte ptr [edi],'-'
        neg eax
        add edi, 1
      pos:
        mov ecx, 3435973837
        mov esi, edi
      .while (eax > 0)
        mov ebx,eax
        mul ecx
        shr edx, 3
        mov eax,edx
        lea edx,[edx*4+edx]
        add edx,edx
        sub ebx,edx
        add bl,'0'
        mov [edi],bl
        add edi, 1
      .endw
        mov byte ptr [edi], 0
        mov eax, edi
        sub eax, lpBuffer
        push eax
      .while (esi < edi)
        sub edi, 1
        mov al, [esi]
        mov ah, [edi]
        mov [edi], al
        mov [esi], ah
        add esi, 1
      .endw
        pop eax  ;; string length returned in eax
      dtaexit:
        pop edi
        pop esi
        pop ebx
        ret
    dwtoa_revised endp

    ;; draws the game pieces
    drawcell proc mDC:dword, x:dword, y:dword, lptext:dword, count:dword
    local rcta1:RECT
        push esi
        push edi
        push ebx
        push edx
        push ecx
        mov eax, x
        mov rcta1.left, eax
        add eax, 128
        mov rcta1.right, eax
        mov eax, y
        mov rcta1.top, eax
        add eax, 128
        mov rcta1.bottom, eax
        invoke FillRect, mDC, addr rcta1, GrayBrush
        mov esi, x
        add esi, 0
        mov edi, y
        add edi, 126
        invoke SelectObject, mDC, WhitePen
        invoke MoveToEx, mDC,  esi, edi, 0
        sub edi, 126
        invoke LineTo,  mDC,  esi,  edi
        add esi, 126
        invoke LineTo,  mDC, esi,  edi
        mov esi, x
        add esi, 1
        mov edi, y
        add edi, 127
        invoke SelectObject, mDC, GrayPen
        invoke MoveToEx, mDC,  esi, edi, 0
        add esi, 126
        invoke LineTo,  mDC, esi, edi
        sub edi, 126
        invoke LineTo,  mDC, esi,  1
        invoke SelectObject, mDC, hFont1
        mov eax, x
        add eax, hpad
        mov rcta1.left, eax
        add eax, 128
        mov rcta1.right, eax
        mov eax, y
        add eax, vpad
        mov rcta1.top, eax
        add eax, 128
        mov rcta1.bottom, eax
        xor edx, edx
        mov dl, a1
        invoke SetBkMode, mDC, TRANSPARENT
        invoke DrawText, mDC, lptext, count, addr rcta1, DT_CENTER or DT_VCENTER
        pop ecx
        pop edx
        pop ebx
        pop edi
        pop esi
        ret
    drawcell endp

    ;; draw the game pieces in a loop
    painting proc hDC:dword
    local rcta1:RECT, numstring[8]:byte, cnt:dword, xx:dword, yy:dword
        lea edi, board
        lea esi, y1
        mov yy, 0
      top1:
        lea ebx, x1
        mov xx, 0
      @@:
        movzx edx, byte ptr [edi]
      .if edx > 0
        invoke dwtoa_revised, edx, addr numstring
        mov cnt, eax
        mov ecx, [ebx]
        mov edx, [esi]
        invoke drawcell, hDC, [ebx], [esi],  addr numstring, cnt
      .endif
        inc edi
        add ebx, 4
        inc xx
        cmp xx, 4
        jnz @b
        add esi, 4
        inc yy
        cmp yy, 4
        jnz top1
        ret
    painting endp
   
    start proc
        invoke GetModuleHandle, NULL
        mov hInstance, eax
        invoke DialogBoxParam, hInstance, 100, 0, addr DlgProc, 0
        invoke ExitProcess, eax
    start endp

    DlgProc proc hWin:dword, uMsg:dword, wParam:dword, lParam:dword
    local hDC:dword, ps:PAINTSTRUCT, rct:RECT, hBrush:dword, hBrush_old:dword
    local mDC:dword, hBmp:dword, hBmp_old:dword, tmp1:dword
    local x:dword, y:dword, wwid:dword, whgt:dword, cwid:dword, chgt:dword
        .if uMsg == WM_INITDIALOG
          invoke GetWindowRect, hWin, addr rct
          mov eax, rct.right
          sub eax, rct.left
          mov wwid, eax
          mov eax, rct.bottom
          sub eax, rct.top
          mov whgt, eax
          invoke GetClientRect, hWin, addr rct
          mov eax, rct.right
          sub eax, rct.left
          mov cwid, eax
          mov eax, rct.bottom
          sub eax, rct.top
          mov chgt, eax
          mov eax, cwidth
          sub eax, cwid
          add wwid, eax
          mov eax, cheight
          sub eax, chgt
          add whgt, eax
          invoke SystemParametersInfoA, SPI_GETWORKAREA, 0, addr rct, 0
          mov eax, rct.right
          sub eax, wwid
          sar eax, 1
          mov x, eax
          mov eax, rct.bottom
          sub eax, whgt
          sar eax, 1
          mov y, eax
          invoke MoveWindow, hWin, x, y, wwid, whgt, TRUE
          fn RetFontHandle, "Tahoma", 90, 400
          mov hFont1, eax
         
          invoke CreatePen, PS_SOLID, 1, 003F3F73h
          mov GrayPen, eax
          invoke GetStockObject, WHITE_PEN
          mov WhitePen, eax
          invoke GetStockObject, LTGRAY_BRUSH
          mov GrayBrush, eax
         
          invoke set_board
          invoke InvalidateRect, hWin, 0, 0
        .elseif uMsg == WM_KEYUP
          .if wParam == VK_LEFT
            .if      a1 == 0
              mov al, a2
              mov    a1, al
              mov    a2, 0
            .elseif  a2 == 0
              mov al, a3
              mov    a2, al
              mov    a3, 0
            .elseif  a3 == 0
              mov al, a4
              mov    a3, al
              mov    a4, 0
            .elseif  b1 == 0
              mov al, b2
              mov    b1, al
              mov    b2, 0
            .elseif  b2 == 0
              mov al, b3
              mov    b2, al
              mov    b3, 0
            .elseif  b3 == 0
              mov al, b4
              mov    b3, al
              mov    b4, 0
            .elseif  c1 == 0
              mov al, c2
              mov    c1, al
              mov    c2, 0
            .elseif  c2 == 0
              mov al, c3
              mov    c2, al
              mov    c3, 0
            .elseif  c3 == 0
              mov al, c4
              mov    c3, al
              mov    c4, 0
            .elseif  d1 == 0
              mov al, d2
              mov    d1, al
              mov    d2, 0
            .elseif  d2 == 0
              mov al, d3
              mov    d2, al
              mov    d3, 0
            .elseif  d3 == 0
              mov al, d4
              mov    d3, al
              mov    d4, 0
            .endif
            invoke InvalidateRect, hWin, 0, 0
          .elseif wParam == VK_UP
            .if      a1 == 0
              mov al, b1
              mov    a1, al
              mov    b1, 0
            .elseif  a2 == 0
              mov al, b2
              mov    a2, al
              mov    b2, 0
            .elseif  a3 == 0
              mov al, b3
              mov    a3, al
              mov    b3, 0
            .elseif  a4 == 0
              mov al, b4
              mov    a4, al
              mov    b4, 0
            .elseif  b1 == 0
              mov al, c1
              mov    b1, al
              mov    c1, 0
            .elseif  b2 == 0
              mov al, c2
              mov    b2, al
              mov    c2, 0
            .elseif  b3 == 0
              mov al, c3
              mov    b3, al
              mov    c3, 0
            .elseif  b4 == 0
              mov al, c4
              mov    b4, al
              mov    c4, 0
            .elseif  c1 == 0
              mov al, d1
              mov    c1, al
              mov    d1, 0
            .elseif  c2 == 0
              mov al, d2
              mov    c2, al
              mov    d2, 0
            .elseif  c3 == 0
              mov al, d3
              mov    c3, al
              mov    d3, 0
            .elseif  c4 == 0
              mov al, d4
              mov    c4, al
              mov    d4, 0
            .endif
            invoke InvalidateRect, hWin, 0, 0
          .elseif wParam == VK_RIGHT
            .if      a2 == 0
              mov al, a1
              mov    a2, al
              mov    a1, 0
            .elseif  a3 == 0
              mov al, a2
              mov    a3, al
              mov    a2, 0
            .elseif  a4 == 0
              mov al, a3
              mov    a4, al
              mov    a3, 0
            .elseif  b2 == 0
              mov al, b1
              mov    b2, al
              mov    b1, 0
            .elseif  b3 == 0
              mov al, b2
              mov    b3, al
              mov    b2, 0
            .elseif  b4 == 0
              mov al, b3
              mov    b4, al
              mov    b3, 0
            .elseif  c2 == 0
              mov al, c1
              mov    c2, al
              mov    c1, 0
            .elseif  c3 == 0
              mov al, c2
              mov    c3, al
              mov    c2, 0
            .elseif  c4 == 0
              mov al, c3
              mov    c4, al
              mov    c3, 0
            .elseif  d2 == 0
              mov al, d1
              mov    d2, al
              mov    d1, 0
            .elseif  d3 == 0
              mov al, d2
              mov    d3, al
              mov    d2, 0
            .elseif  d4 == 0
              mov al, d3
              mov    d4, al
              mov    d3, 0
            .endif
            invoke InvalidateRect, hWin, 0, 0
          .elseif wParam == VK_DOWN
            .if      b1 == 0
              mov al, a1
              mov    b1, al
              mov    a1, 0
            .elseif  b2 == 0
              mov al, a2
              mov    b2, al
              mov    a2, 0
            .elseif  b3 == 0
              mov al, a3
              mov    b3, al
              mov    a3, 0
            .elseif  b4 == 0
              mov al, a4
              mov    b4, al
              mov    a4, 0
            .elseif  c1 == 0
              mov al, b1
              mov    c1, al
              mov    b1, 0
            .elseif  c2 == 0
              mov al, b2
              mov    c2, al
              mov    b2, 0
            .elseif  c3 == 0
              mov al, b3
              mov    c3, al
              mov    b3, 0
            .elseif  c4 == 0
              mov al, b4
              mov    c4, al
              mov    b4, 0
            .elseif  d1 == 0
              mov al, c1
              mov    d1, al
              mov    c1, 0
            .elseif  d2 == 0
              mov al, c2
              mov    d2, al
              mov    c2, 0
            .elseif  d3 == 0
              mov al, c3
              mov    d3, al
              mov    c3, 0
            .elseif  d4 == 0
              mov al, c4
              mov    d4, al
              mov    c4, 0
            .endif
          .endif
              invoke InvalidateRect, hWin, 0, 0
             
              ;; check for winning condition
              .if a1 ==  1 && a2 ==  2 && a3 ==  3 && a4 ==  4
              .if b1 ==  5 && b2 ==  6 && b3 ==  7 && b4 ==  8
              .if c1 ==  9 && c2 == 10 && c3 == 11 && c4 == 12
              .if d1 == 13 && d2 == 14 && d3 == 15 && d4 ==  0
             
                ;; if winner, display messagebox
                fn MessageBox, 0, addr gover, addr finito, 0
               
                ;; if winner, reset board
                invoke set_board
                invoke InvalidateRect, hWin, 0, 0
                xor eax, eax
                ret
              .endif
              .endif
              .endif
              .endif
        .elseif uMsg == WM_PAINT
          invoke BeginPaint, hWin, addr ps
          mov hDC, eax
          invoke GetClientRect, hWin, addr rct
          invoke CreateCompatibleDC, hDC
          mov mDC, eax
          invoke CreateCompatibleBitmap, hDC, rct.right, rct.bottom
          mov hBmp, eax
          invoke SelectObject, mDC, hBmp
          mov hBmp_old, eax
          invoke CreateSolidBrush, 003F3F3Fh
          mov hBrush, eax
          invoke FillRect, mDC, addr rct, hBrush

          invoke painting, mDC  ; paint the game pieces
         
          invoke BitBlt, hDC, 0, 0, rct.right, rct.bottom, mDC, 0, 0, SRCCOPY
          invoke SelectObject, mDC, hBmp_old
          invoke DeleteObject, hBmp
          invoke DeleteObject, hBrush
          invoke DeleteDC, mDC
          invoke EndPaint, hWin, addr ps
        .elseif uMsg == WM_CLOSE
          invoke EndDialog, hWin, 0
        .endif
        xor eax, eax
        ret
    DlgProc endp

    ;; prng based on nrandom in masm32.lib
    randb proc base:dword
    .data?
        randseed dd ?
    .code
        push esi
        push edi
        push ebx
        push edx
        push ecx
        invoke GetTickCount
        add randseed, eax
        mov eax, randseed
        xor edx, edx
        mov ecx, 127773
        div ecx
        mov ecx, eax
        mov eax, 16807
        mul edx
        mov edx, ecx
        mov ecx, eax
        mov eax, 2836
        mul edx
        sub ecx, eax
        xor edx, edx
        mov eax, ecx
        mov randseed, ecx
        div base
        mov eax, edx
        pop ecx
        pop edx
        pop ebx
        pop edi
        pop esi
        ret
    randb endp

    ;; scrambles the game board somewhat randomly 1 iteration
    scramble proc dst:dword, Below:dword, Right:dword, Above:dword, Left:dword
        push esi
        push edi
        push ebx
        push edx
        push ecx
        mov ecx, dst
        mov edx, Below
        mov ebx, Right
        mov esi, Above
        mov edi, Left
        invoke randb, 4
      .if      byte ptr [ecx] == 0 && eax == 0
        mov al, byte ptr [edx]
        mov    byte ptr [ecx], al
        mov    byte ptr [edx], 0
      .elseif  byte ptr [ecx] == 0 && eax == 1
        mov al, byte ptr [ebx]
        mov    byte ptr [ecx], al
        mov    byte ptr [ebx], 0
      .elseif  byte ptr [ecx] == 0 && eax == 2
        mov al, byte ptr [esi]
        mov    byte ptr [ecx], al
        mov    byte ptr [esi], 0
      .elseif  byte ptr [ecx] == 0 && eax == 3
        mov al, byte ptr [edi]
        mov    byte ptr [ecx], al
        mov    byte ptr [edi], 0
      .endif
        pop ecx
        pop edx
        pop ebx
        pop edi
        pop esi
        ret
    scramble endp
   
    ;; scrambles the game board somewhat randomly for 'reps' number of iterations
    set_board proc
    local ctr1:dword
        mov ctr1, 0
        mov a1, 1
        mov a2, 2
        mov a3, 3
        mov a4, 4
        mov b1, 5
        mov b2, 6
        mov b3, 7
        mov b4, 8
        mov c1, 9
        mov c2, 10
        mov c3, 11
        mov c4, 12
        mov d1, 13
        mov d2, 14
        mov d3, 15
        mov d4, 0
      top:
        inc ctr1
        invoke scramble, addr a1, addr b1, addr a2, addr a1, addr a1
        invoke scramble, addr a2, addr b2, addr a3, addr a2, addr a1
        invoke scramble, addr a3, addr b3, addr a4, addr a3, addr a2
        invoke scramble, addr a4, addr b4, addr a4, addr a4, addr a3
        invoke scramble, addr b1, addr c1, addr b2, addr a1, addr b1
        invoke scramble, addr b2, addr c2, addr b3, addr a2, addr b1
        invoke scramble, addr b3, addr c3, addr b4, addr a3, addr b2
        invoke scramble, addr b4, addr c4, addr b4, addr a4, addr b3
        invoke scramble, addr c1, addr d1, addr c2, addr b1, addr c1
        invoke scramble, addr c2, addr d2, addr c3, addr b2, addr c1
        invoke scramble, addr c3, addr d3, addr c4, addr b3, addr c2
        invoke scramble, addr c4, addr d4, addr c4, addr b4, addr c3
        invoke scramble, addr d1, addr d1, addr d2, addr c1, addr d1
        invoke scramble, addr d2, addr d2, addr d3, addr c2, addr d1
        invoke scramble, addr d3, addr d3, addr d4, addr c3, addr d2
        invoke scramble, addr d4, addr d4, addr d4, addr c4, addr d3
        cmp ctr1, reps
        jl top
        ret
    set_board endp

    end

Object of the game is to arrange the game pieces in ascending numerical order like this, using the keyboard arrow keys:

 1  2  3  4
 5  6  7  8
 9 10 11 12
13 14 15   


:smiley:

Edit: I noticed that I had not preserved the registers in the 'painting' procedure. That will be fixed in the release version.  :eusa_boohoo:
¯\_(ツ)_/¯   :azn:

'As we don't do "requests", show us your code first.'  -  hutch—

jj2007

Compliments, it works like a charm :thumbsup:

zedd151

#6
Quote from: jj2007 on March 29, 2025, 10:48:02 PMCompliments, it works like a charm :thumbsup:
But judging by the screenshot, you didn't finish the game.

Just in case your comment plus screenshot was meant as sarcasm... (you are known to use sarcasm at least occasionally)   :tongue:  ....
Every generated game is guaranteed to be solvable.

When the game board is initialized in the 'set_board' procedure, the game board is set with the pieces in solved order.

 1  2  3  4
 5  6  7  8
 9 10 11 12
13 14 15 ##
where ## is the empty cell.

Then it is randomly scrambled (800 times in the code posted, but could be adjusted for more) using the same movements as if the pieces were being moved with the arrow keys on the keyboard.

Randomly the direction to move is chosen (either up, right, down or left).
A piece is only moved if the empty cell is in the chosen direction, else another random direction is selected; lather, rinse, repeat for each iteration of the loop until the number of 'reps' is reached.

It has worked this way literally for years since I worked out the insolvency issue for exactly 50 percent of the games, if random game pieces were arbitrarily place on the game board with no rhyme or reason.  :smiley:

If your post was not meant as sarcasm and I misinterpreted it, thank you.  :thumbsup:
¯\_(ツ)_/¯   :azn:

'As we don't do "requests", show us your code first.'  -  hutch—

sinsi

Works OK here, although I don't have the patience (or skill it seems) to actually complete a game.

On a quick browse of the code, you could save a bit of code when centering the text in a cell.
        invoke SelectObject, mDC, hFont1
;cut from here
        mov eax, x
        add eax, hpad
        mov rcta1.left, eax
        add eax, 128
        mov rcta1.right, eax
        mov eax, y
        add eax, vpad
        mov rcta1.top, eax
        add eax, 128
        mov rcta1.bottom, eax
        xor edx, edx
        mov dl, a1
;to here (btw, what are the two lines above?)
        invoke SetBkMode, mDC, TRANSPARENT
;add DT_SINGLELINE
        invoke DrawText, mDC, lptext, count, addr rcta1, DT_CENTER or DT_VCENTER or DT_SINGLELINE

QuoteDT_VCENTER    Centers text vertically. This value is used only with the DT_SINGLELINE value.

zedd151

#8
Quote from: sinsi on March 29, 2025, 11:52:36 PMWorks OK here, although I don't have the patience (or skill it seems) to actually complete a game.

On a quick browse of the code, you could save a bit of code when centering the text in a cell.
        invoke SelectObject, mDC, hFont1
;cut from here
        mov eax, x
        add eax, hpad
        mov rcta1.left, eax
        add eax, 128
        mov rcta1.right, eax
        mov eax, y
        add eax, vpad
        mov rcta1.top, eax
        add eax, 128
        mov rcta1.bottom, eax
        xor edx, edx
        mov dl, a1
;to here (btw, what are the two lines above?)
        invoke SetBkMode, mDC, TRANSPARENT
;add DT_SINGLELINE
        invoke DrawText, mDC, lptext, count, addr rcta1, DT_CENTER or DT_VCENTER or DT_SINGLELINE

QuoteDT_VCENTER    Centers text vertically. This value is used only with the DT_SINGLELINE value.


I found that the text was never centered exactly as needed, so I ended up using padding. I am a little OCD for such things.  :tongue:

The code
xor edx, edx
mov dl, a1
Are apparent leftovers from some test code, and of no use in the present code.  :thumbsup:  good catch.

I did see that previously, but apparently it still made it into the posted code.  :toothy:  That will be taken care of with the Release version in the coming days.

As far as not having patience, I understand.
I am going to make an avatar (kind of like a demo) of how to resolve the bottom row of the game to "13, 14, 15, empty" when there seems no apparent way to solve it. There is always a way, you just have to think of how to accomplish it.

I have already made an 'avatar friendly' version of the game (client size is 128x128, perfect for use as an avatar).  :smiley:

I just need to add the screen capture code to it, and assemble the series of bitmaps into an animated .gif. Maybe in a couple days, it will be ready.  :biggrin:  my current avatar, btw was made with the avatar version of the 4x4 game.

More errata...
For anyone interested, in post #1, that version of the game has an 'autosolve' mode.
It has a series of moves to implement the autosolver saved as ascii strings and kept in a file. "lurd" for left, up, right, down, in that order for instance.
All possible moves with the shortest path for a given position were recorded and saved to file. That had taken quite some time to complete, after a lot of trial and error over a period of endless days.
There are still shorter paths to solve many games though, since the autosolver generally considered only one game piece at a time, it would have taken much longer to compile otherwise i.e., if a set of pieces were considered at a time.
I had used this to test billions of games for solvency in a modified version of the program over a couple of days.
I had not found a single game (out of billions) to be unsolvable using the board scrambling method that I still use + used the autosolver to successfully solve each and every one of the games.

The code attached in post #1, also incorporates starting a new game at any time, restarting the current game from any unsolved position, and it displays the number of moves taken during game play.
Some may find the code for the autosolver is weird and I agree, but it werks.  :biggrin:

I did not include those in this version to keep the game, and code simple. But I reserve the right to add some of that functionality at a later time. Maybe.
¯\_(ツ)_/¯   :azn:

'As we don't do "requests", show us your code first.'  -  hutch—

jj2007

Quote from: zedd151 on March 29, 2025, 11:18:27 PMIf your post was not meant as sarcasm and I misinterpreted it, thank you.  :thumbsup:

It was indeed not sarcasm: I got stuck towards the end and gave up. You did a great job there :thumbsup:


zedd151

#10
Quote from: jj2007 on March 30, 2025, 03:23:08 AMI got stuck towards the end and gave up.
I wasn't sure if you were being sarcastic, so I gave my reason for why every game is solvable. I am going to make a demo to show how to solve a seemingly unsolvable bottom row. It is pretty easy really.

Here is the Full Size Demo  There are 3 different games played.

¯\_(ツ)_/¯   :azn:

'As we don't do "requests", show us your code first.'  -  hutch—

zedd151

In addition to the animated .gif demo above, I have also converted it to video and posted it on YouTube.

You can use the speed setting if it is too slow for you there.

Large 4x4 Demo on YouTube
¯\_(ツ)_/¯   :azn:

'As we don't do "requests", show us your code first.'  -  hutch—

zedd151

#12
Some information regarding puzzle games of this type: https://en.m.wikipedia.org/wiki/15_puzzle

The information regarding solvability reaffirms that my method of guaranteeing that each random puzzle produced by my program is solvable, is sound. I start with a solved puzzle and randomly scramble it using valid moves.

You cannot just randomly place the 15 tiles and expect the puzzle to be solvable. As I stated earlier, 50% of all games generated by random placement only, will be unsolvable.

Also there are several different names for this type of puzzle, btw.  :smiley:

I never knew (or had forgotten) what it was originally called, so I named my version of it simply "4x4". I only remember that they were given out at children's birthday parties as party favors (as well as other very cheap toys) when I was a youngster.
It was a plastic toy maybe 3 1/2 inches square and maybe a quarter inch thick. With interlocking numbered (1-15) sliding tiles, so they wouldnt fall out, or the player could win by simply taking out the tiles and putting them in solved order rather than actually trying to solve it by sliding the tiles.

I have also recently found that some call it a "sliding number puzzle".
¯\_(ツ)_/¯   :azn:

'As we don't do "requests", show us your code first.'  -  hutch—

jj2007

Quote from: zedd151 on March 31, 2025, 05:16:42 PMI start with a solved puzzle and randomly scramble it using valid moves

Sounds like a cute idea :thumbsup:

zedd151

Quote from: jj2007 on March 31, 2025, 08:06:53 PM
Quote from: zedd151 on March 31, 2025, 05:16:42 PMI start with a solved puzzle and randomly scramble it using valid moves

Sounds like a cute idea :thumbsup:

There is probably a more elegant way to do it based on the mathematics of the puzzle, but that was the first thing that came to mind when I first realized that 50% of all games would be unsolvable, if not done correctly.

So, have you yet had success finishing your first 4x4 puzzle?  :biggrin:
¯\_(ツ)_/¯   :azn:

'As we don't do "requests", show us your code first.'  -  hutch—