Mosaic: Time and moves
The statusbar will show the time you've been playing and the number of moves you have made.
13.1 - Variables
In your .data?:
LastH dd ?
LastM dd ?
LastS dd ?
UserMoves dd ?
StartTime dd ?
These variables are used. LastUM, LastH, LastM and LastS hold the last usermove (counts the # of moves) and the last hour, minute and second of playing displayed. Because the time you are playing and the number of moves you've made so far are constantly shown, constantly updating it would cause screen flickering. If the values need to be updated, they are first compared to the last values and only if a value has changed it needs to be updated. This reduces flickering. UserMoves is a variable that counts the number of moves the user has made, StartTime holds -1 if the user has not yet started playing, or the result of GetTickCount when the user started playing. If GetTickCount is called again and StartTime is substracted from the result, you have the number of miliseconds since the user started playing.
13.2 - Timer
The updating is done with a timer. A timer sends a WM_TIMER message at a given interval. Each time we receive a WM_TIMER message, the program checks if an update is needed.
In mosaic.inc:
Add this line to the WM_CREATE handler of WndProc:
Add this as first line in the WM_DESTROY handler of WndProc:
Add this to the messagehandler in Wndproc
mov eax, wParam
.IF eax==ID_TIMER1
invoke UpdateInfo
.ENDIF
The timer is created with SetTimer. It creates a timer with ID: ID_TIMER1, and sets it's interval to 500ms (0.5 s). So twice a second a WM_TIMER message is sent. KillTimer deletes the timer when the program is closed. The WM_TIMER handler checks if the message comes from the timer with id ID_TIMER1, and if yes, calls UpdateInfo (see below).
13.3 - UpdateInfo
UpdateInfo calculates the number of moves the user has made and the time the user has been playing. If it has changed since the last time, the statusbar is filled with new information.
In your .data:
TimeFormat db "%02lu:%02lu:%02lu",0
NullString db 0
In your .code:
[code=asm]
UpdateInfo PROTO STDCALL
;================================================================================
; UpdateInfo
;================================================================================
UpdateInfo proc uses ebx edi esi
LOCAL hours:DWORD
LOCAL minutes:DWORD
LOCAL seconds:DWORD
.IF StartTime==-1 || UserMoves==0
invoke SendMessage, hStatus, SB_SETTEXT, 1, ADDR NullString
invoke SendMessage, hStatus, SB_SETTEXT, 2, ADDR NullString
.ELSE
mov eax, UserMoves
.IF eax!=LastUM
invoke wsprintf, ADDR Buffer, ADDR MovesFormat, UserMoves
invoke SendMessage, hStatus, SB_SETTEXT, 1, ADDR Buffer
.ENDIF
invoke GetTickCount
sub eax, StartTime
mov ecx, 1000
cdq
div ecx
; eax is number of seconds
mov ecx, 3600
cdq
div ecx
; eax is number of hours, edx is remainder (remaining number of seconds)
mov hours, eax
mov eax, edx
mov ecx, 60
cdq
div ecx
; eax is number of minutes, edx is remaining number of seconds,
mov minutes, eax
mov seconds, edx
xor eax, eax
mov ecx, LastH
.IF ecx!=hours
inc eax
.ENDIF
mov ecx, LastM
.IF ecx!=minutes
inc eax
.ENDIF
mov ecx, LastS
.IF ecx!=seconds
inc eax
.ENDIF
.IF eax!=NULL
invoke wsprintf, ADDR Buffer, ADDR TimeFormat, hours, minutes, seconds
invoke SendMessage, hStatus, SB_SETTEXT, 2, ADDR Buffer
.ENDIF
push hours
push minutes
push seconds
pop LastS
pop LastM
pop LastH
push UserMoves
pop LastUM
.ENDIF
ret
UpdateInfo endp
Examination
If the starttime = -1 (user has not yet started), or the user has not made any moves yet, the statusbar is filled with Nullstrings:
invoke SendMessage, hStatus, SB_SETTEXT, 1, ADDR NullString
invoke SendMessage, hStatus, SB_SETTEXT, 2, ADDR NullString
If the user has started:
Is the UserMoves variable changed since the last update?
.IF eax!=LastUM
If yes, print the UserMoves as string in Buffer (see your win32 programmer's reference on wsprintf for more info about wsprintf), and put the text in the buffer on the second part of the statusbar:
invoke wsprintf, ADDR Buffer, ADDR MovesFormat, UserMoves
.ENDIF
Get the current tickcount (nr of ms since startup), and substract the StartTime from it:
sub eax, StartTime
Eax holds the number of ms playing, divide by 1000 to get the number of seconds:
cdq
div ecx
; eax is number of seconds
Divide by 3600 to get hours:
cdq
div ecx
Eax is number of hours, edx is remainder (remaining number of seconds). Store hours.
Divide remaining seconds by 60 to get minutes
mov ecx, 60
cdq
div ecx
eax is number of minutes, edx is remaining number of seconds. Store both:
mov seconds, edx
Here eax is used as 'changed since last update flag'. With the xor eax, eax it is set to 0, but if any of the values (hours, minutes, seconds) has changed since the last update, eax is increased by 1.
mov ecx, LastH
.IF ecx!=hours
inc eax
.ENDIF
mov ecx, LastM
.IF ecx!=minutes
inc eax
.ENDIF
mov ecx, LastS
.IF ecx!=seconds
inc eax
.ENDIF
If eax is 1 or more (1 or more values changed), thus eax!=0, the time is converted to a string with wsprintf and the 3rd part of the statusbar is filled with the time
invoke wsprintf, ADDR Buffer, ADDR TimeFormat, hours, minutes, seconds
invoke SendMessage, hStatus, SB_SETTEXT, 2, ADDR Buffer
.ENDIF
Make the current values the last values for the next time:
push minutes
push seconds
pop LastS
pop LastM
pop LastH
push UserMoves
pop LastUM
.ENDIF
ret
UpdateInfo endp[/ex]
[h2]13.3 - More updates[/h2]
Sometimes an update should be forced, for example when the user moved a tile:
In ProcessClick, before .BREAK:
[code=asm]
inc UserMoves
.IF UserMoves==1 ;first move?
invoke GetTickCount
mov StartTime, eax
.ENDIF
invoke UpdateInfo
This increases the UserMoves everytime a tile is clicked. The compare of UserMoves with 1 is to find out if this is the user's first move. If it is, the StartTime is set to current tickcount. So the clock starts ticking when the user makes it's first move.
Initializing everything
What also has to be done, is initializing all values:
Add this to NewGame (just before invoke ShuffleTiles):
mov StartTime, -1
mov La[ex]s[/ex]tUM, -1
mov LastH, -1
mov LastM, -1
mov LastS, -1
When a new game is started, the starttime is reset, number of moves is set to 0 and all the lastXX values are set to -1.
Solved?
Now we can add a new 'solved'-message:
In your .data:
New CheckIfSolved, replace the old one:
; Check if puzzle is solved
;================================================================================
CheckIfSolved proc uses ebx hWnd:DWORD
LOCAL hours:DWORD
LOCAL minutes:DWORD
LOCAL seconds:DWORD
mov eax, offset TileTable
.IF dword ptr [eax]==04030201h
.IF dword ptr [eax+4]==08070605h
.IF dword ptr [eax+8]==0C0B0A09h
.IF dword ptr [eax+0Ch]==000F0E0Dh
mov dword ptr [eax+0Ch], 100F0E0Dh
;GET TIME
invoke GetTickCount
sub eax, StartTime
mov ecx, 1000
cdq
div ecx
; eax is number of seconds
mov ecx, 3600
cdq
div ecx
; eax is number of hours, edx is remainder (remaining number of seconds)
mov hours, eax
mov eax, edx
mov ecx, 60
cdq
div ecx
; eax is number of minutes, edx is remaining number of seconds,
mov minutes, eax
mov seconds, edx
mov StartTime, -1
mov ebx, UserMoves
mov UserMoves, 0
invoke InvalidateRect, hWnd, ADDR RectUpdate, FALSE
invoke wsprintf, ADDR Buffer, ADDR SolvedFormat,\
hours, minutes, seconds, ebx
invoke MessageBox, NULL, ADDR Buffer, ADDR AppName, MB_OK + MB_ICONEXCLAMATION
.ENDIF
.ENDIF
.ENDIF
.ENDIF
ret
CheckIfSolved endp
The procedure is the same as the previous one, but instead of the simple messagebox, the time is calculated (same code as in UpdateInfo) and together with the UserMoves shown in the messagebox.
13.4 - Done
You should now see a clock ticking and your moves count when you play the game:
Current project code here: mosaic10.zip.