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):
(https://i.postimg.cc/zvNRXgtJ/1.png)
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:
(https://i.postimg.cc/SQGnfj1M/2.png)
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 (https://masm32.com/board/index.php?msg=137653) 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:
Works fine here :thumbsup:
I wonder if the strange behaviour is caused by WndProc using esi and edi but not saving them?
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:
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!
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.
(https://i.postimg.cc/ZYpM6N3W/untitled.png)
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:
Compliments, it works like a charm :thumbsup:
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:
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.
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 (https://masm32.com/board/index.php?topic=12668.0) (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.
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:
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.
(https://i.postimg.cc/hG0tqdmh/demo-lg.gif)
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 (https://m.youtube.com/watch?v=dj9WACh_e7U&pp=0gcJCWIABgo59PVc)
Some information regarding puzzle games of this type: https://en.m.wikipedia.org/wiki/15_puzzle (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".
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:
Quote from: jj2007 on March 31, 2025, 08:06:53 PMQuote 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: