Un utilisateur, une balle, un bureau et des rebonds : une recette simple pour un programme pas compliqué ! Boulinator est une application gadget parfaitement inutile? Mais il me fallait une idée-support pour m?essayer à l?assembleur Win32. Voilà qui est fait !
Pour information : si vous décider de lancer ce petit programme, sachez qu?une icône va s?ajouter en bas à droite dans la barre des tâches, ce qui vous permet d?accéder à un menu à tout moment. Avec un clic droit sur la balle vous aurez le même résultat. Pour le reste : cliquez gauche sur la balle, déplacez la souris et lâchez tout !
Dans ce code vous trouverez une touche de « région », un soupçon de « thread » et une larme de « floating point ».
Tout ça en assembleur MASM32.
Puisant régulièrement dans les sources de www.asmfr.fr pour apprendre les rudiments de la programmation Win32, il m?a semblé correct d?y déposer une humble participation. Voici donc Boulinator, un inoffensif petit ScreenMate.
Remarques, commentaires et optimisations bienvenu !
mov eax, salut_!
mov a_tous,eax
Source / Exemple :
Title BOULINATOR Version MASM32
.486
.model flat, stdcall
option casemap :none
include \masm32\include\windows.inc
include \masm32\include\masm32.inc
include \masm32\include\gdi32.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\shell32.inc
include \masm32\include\winmm.inc
includelib \masm32\lib\masm32.lib
includelib \masm32\lib\gdi32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\shell32.lib
includelib \masm32\lib\winmm.lib
WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
InfoDlgProc PROTO :DWORD,:DWORD,:DWORD,:DWORD
ParamDlgProc PROTO :DWORD,:DWORD,:DWORD,:DWORD
ErrMsg PROTO :DWORD,:DWORD
.const
ID_icon equ 20
ID_bonk equ 30
IDR_MENU equ 10000
IDM_stopper equ 10001
IDM_continuer equ 10002
IDM_apropos equ 10003
IDM_quitter equ 10004
IDM_parametre equ 10005
DlgInfo equ 1000
IDC_BTN1 equ 1001
DlgParam equ 2000
IDC_vitesse equ 2001
IDC_rebond equ 2002
IDC_gravit equ 2003
IDC_diam equ 2004
IDC_son equ 2005
IDC_annuler equ 2006
IDC_ok equ 2007
IDC_textson equ 2018
THREADMSG equ WM_USER+1
ICONMSG equ WM_USER+2
.data
ClassName db "Boulinator",0
AppName db "Boulinator",0
szvitesse db "La vitesse doit être comprise entre 0 et %u ",0
szrebond db "Le rebond doit être compris entre 0 et %u ",0
szgravit db "La gravité doit être comprise entre 0 et %u ",0
szdiam db "Le diamètre doit être compris entre 5 et %u ",0
szon db "on",0
szoff db "off",0
dragflag dd 0
threadflag dd 0
stopflag dd 0
sonflag dd 1
valpause dd 10
diam dd 35
fposx REAL8 0.0
fposy REAL8 0.0
vectx REAL8 0.0
vecty REAL8 0.0
rebond REAL8 0.94
frotte REAL8 0.97
gravit REAL8 2.0
ajust REAL8 0.4
cent REAL8 100.0
dix REAL8 10.0
.data?
n NOTIFYICONDATA <>
hInstance HINSTANCE ?
hwnd HWND ?
hicon HWND ?
hmenu HWND ?
hthread HWND ?
maxx dd ?
maxy dd ?
posx dd ?
posy dd ?
IDthread dd ?
sourx1 dd ?
soury1 dd ?
sourx2 dd ?
soury2 dd ?
xdiff dd ?
ydiff dd ?
vectxinit dd ?
vectyinit dd ?
oldx1 dd ?
oldx2 dd ?
oldx3 dd ?
oldy1 dd ?
oldy2 dd ?
oldy3 dd ?
.code
start:
invoke GetModuleHandle,NULL
mov hInstance,eax
invoke WinMain,hInstance,NULL,NULL,SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain PROC hInst:HINSTANCE,hPrevinst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
invoke GetSystemMetrics,SM_CYSCREEN
mov maxy,eax ; recuperer les dimensions de l'écran
invoke GetSystemMetrics,SM_CXSCREEN
mov maxx,eax ; pour fixer les limites
shr eax,1
mov ecx,diam
shr ecx,1
sub eax,ecx
mov posx,eax ; et pour positionner la balle au milieu et en bas
mov eax,maxy
sub eax,diam
mov posy,eax
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style,CS_HREDRAW or CS_VREDRAW or CS_DBLCLKS ; double clic OK
mov wc.lpfnWndProc,OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInstance
pop wc.hInstance
invoke CreateSolidBrush,000000FFh ; couleur balle = couleur de fond
mov wc.hbrBackground,eax
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,hInstance,ID_icon
mov hicon,eax
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx,addr wc ; fenetre avec styles adaptées :
invoke CreateWindowEx,WS_EX_TOPMOST+WS_EX_TOOLWINDOW,\; tjrs au dessus
ADDR ClassName,ADDR AppName,\ ; pas de barre d'icône
WS_POPUP,\ ; pas de barre de titre
posx,posy,\
maxx,maxy,\
NULL,NULL,hInstance,NULL
mov hwnd,eax
boucle_message :
invoke GetMessage,ADDR msg,NULL,0,0
cmp eax,0
je fin_boucle_message
invoke TranslateMessage,ADDR msg
invoke DispatchMessage,ADDR msg
jmp boucle_message
fin_boucle_message:
mov eax,msg.wParam
ret
WinMain ENDP
WndProc PROC hWnd:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM
LOCAL pt:POINT
LOCAL rect:RECT
.IF uMsg == WM_CREATE ; creation ?
invoke LoadMenu,hInstance,IDR_MENU ; on load le menu
invoke GetSubMenu,eax,NULL ; qu'on passe en submenu
mov hmenu,eax
invoke CreateEllipticRgn,0,0,diam,diam ; creation d'une rgn ronde
invoke SetWindowRgn,hWnd,eax,1 ; pour y associer la fenetre
invoke ShowWindow,hWnd,SW_SHOWNORMAL ; et la rendre visible
invoke UpdateWindow,hWnd
mov n.cbSize,SIZEOF NOTIFYICONDATA ; pour être "joignable"...
push hWnd
pop n.hwnd
mov n.uID,0
mov n.uFlags,NIF_ICON+NIF_MESSAGE+NIF_TIP
mov n.uCallbackMessage,ICONMSG ; ( le message de retour )
mov eax,hicon
mov n.hIcon,eax
invoke lstrcpy,ADDR n.szTip,ADDR AppName
invoke Shell_NotifyIcon,NIM_ADD,ADDR n ; création d'un tray icon
.ELSEIF uMsg == WM_DESTROY ; c'est fini ?
invoke DestroyMenu,hmenu ; menu...
invoke Shell_NotifyIcon,NIM_DELETE,ADDR n ; tray icon...
invoke PostQuitMessage,NULL
.ELSEIF uMsg == WM_KEYDOWN && wParam == VK_ESCAPE ; touche Escape ?
invoke DestroyWindow,hwnd
; clic droit ou dblclic gauche ?
.ELSEIF uMsg == WM_RBUTTONDOWN || uMsg == WM_LBUTTONDBLCLK
invoke GetCursorPos,ADDR pt ; recuperer la pos du curseur
invoke SetForegroundWindow,hwnd ; et afficher le menu
invoke TrackPopupMenu,hmenu,TPM_HORIZONTAL,pt.x,pt.y,NULL,hwnd,NULL
; clic gauche et thread off ?
.ELSEIF uMsg == WM_LBUTTONDOWN && dragflag == 0 && threadflag == 0
mov dragflag,1 ; drag commence
invoke GetCursorPos,ADDR pt
mov eax,pt.x
mov ecx,pt.y ; recuperer la position de la souris
mov sourx1,eax
mov sourx2,eax
mov soury1,ecx ; initialiser l'historique
mov soury2,ecx ; des deux dernières positions souris
mov posx,eax
mov posy,ecx ; initialiser la position de la balle
mov xdiff,eax
mov ydiff,ecx
invoke GetWindowRect,hwnd,ADDR rect
mov eax,rect.left ; recuperer la position de la fenetre
mov ecx,rect.top
sub xdiff,eax
sub ydiff,ecx ; calculer le decalage souris-fenetre
invoke SetCapture,hwnd ; garder le lien avec la souris
invoke SetTimer,hwnd,1,50,NULL ; pour suivre les mvts de souris
; clic gauche et thread on ?
.ELSEIF uMsg == WM_LBUTTONDOWN && dragflag == 0 && threadflag == 1
mov stopflag,1 ; demander au thread de s'arreter
invoke PostMessage,hwnd,WM_LBUTTONDOWN,wParam,lParam ; drag
; drag on et relachement clic gauche ?
.ELSEIF uMsg == WM_LBUTTONUP && dragflag == 1
mov dragflag,0
invoke KillTimer,hwnd,1 ; plus besoin de suivre la souris
mov eax,sourx2
sub eax,sourx1
mov ecx,soury2
sub ecx,soury1 ; deux dernières positions de souris
mov vectxinit,eax ; pour les vecteurs de deplacements
mov vectyinit,ecx
invoke ReleaseCapture ; on peut libèrer la souris
.IF threadflag == 0 ; pas déjà un thread en cours ?
mov stopflag,0
mov eax,OFFSET MonThread
invoke CreateThread,NULL,NULL,eax,NULL,NULL,ADDR IDthread
mov hthread,eax
mov threadflag,1 ; alors on le creer
.ENDIF
; drag on et mvt de souris ?
.ELSEIF uMsg == WM_MOUSEMOVE && dragflag == 1
invoke GetCursorPos,ADDR pt
mov eax,pt.x
mov ecx,pt.y ; recuperer la position de la souris
sub eax,xdiff
sub ecx,ydiff ; decalage avec la fenetre
mov posx,eax ; mettre à jour position de la balle
mov posy,ecx ; et deplacer la fenetre
invoke SetWindowPos,hwnd,HWND_TOPMOST,eax,ecx,NULL,NULL,SWP_NOSIZE
.ELSEIF uMsg == WM_TIMER ; timer ?
invoke GetCursorPos,ADDR pt
mov eax,pt.x
mov ecx,pt.y ; recuperer la position de la souris
push sourx2
pop sourx1 ; la dernière devient l'avant dernière
push soury2
pop soury1
mov sourx2,eax ; et l'actuelle devient la dernière
mov soury2,ecx
.ELSEIF uMsg == WM_COMMAND ; commande du menu ?
mov eax,wParam
.IF ax == IDM_quitter ; quitter : on sort
invoke DestroyWindow,hwnd
.ELSEIF ax == IDM_parametre ; paramètres : boite de dialogue
invoke DialogBoxParam,hInstance,DlgParam,hwnd,ADDR ParamDlgProc,NULL
.ELSEIF ax == IDM_apropos ; a propos : boite de dialogue
invoke DialogBoxParam,hInstance,DlgInfo,hwnd,ADDR InfoDlgProc,NULL
.ELSEIF ax == IDM_stopper && threadflag == 1
mov stopflag,1 ; on demande au thread de s'arreter
.ENDIF
.ELSEIF uMsg == THREADMSG ; message "j'ai fini" du thread ?
invoke WaitForSingleObject,hthread,INFINITE
invoke CloseHandle,hthread
mov threadflag,0 ; on l'attend avant de continuer
.ELSEIF uMsg == ICONMSG ; message tray icon + clic souris ?
.IF lParam == WM_LBUTTONDOWN || lParam == WM_RBUTTONDOWN
invoke GetCursorPos,ADDR pt ; recuperer la pos de la souris
invoke SetForegroundWindow,hwnd ; pour afficher le menu
invoke TrackPopupMenu,hmenu,TPM_RIGHTALIGN,pt.x,pt.y,NULL,hwnd,NULL
.ENDIF
.ELSE ; sinon c'est windows qui gère
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc ENDP
MonThread PROC
LOCAL tempx:DWORD
LOCAL tempy:DWORD
LOCAL bonk:DWORD
mov oldx1,0 ; initialisation de l'historique
mov oldx2,0 ; des trois dernières positions de la balle
mov oldx3,0
mov oldy1,0
mov oldy2,0
mov oldy3,0
fild posx ; posx et posy -> convertis en floating point
fstp fposx
fild posy
fstp fposy
fild vectxinit ; idem pour vectxinit et vectyinit
fmul ajust ; Ajuster pour eviter des vecteurs hors de proportion
fstp vectx
fild vectyinit
fmul ajust
fstp vecty
.REPEAT
mov bonk,0 ; initialiser le flag pour le son d'impact
finit
fld fposx
fadd vectx
fstp fposx ; posx = posx +vectx
fld fposx
fistp tempx
mov eax,maxx
sub eax,diam
cmp tempx,eax
jl @f ; posx > maxx ?
mov tempx,eax
fild tempx
fstp fposx ; oui : posx = maxx
fld vectx
fchs
fmul rebond
fstp vectx ; et vectx = -vectx X rebond
mov bonk,1
jmp @suite
@@: mov eax,1
cmp tempx,eax
jg @suite ; posx < minix ?
mov tempx,eax
fild tempx
fstp fposx ; oui : posx = minix
fld vectx
fchs
fmul rebond
fstp vectx ; et vectx = -vectx X rebond
mov bonk,1
@suite:
fld fposy
fadd vecty
fstp fposy ; posy = posy +vecty
fld fposy
fistp tempy
mov eax,maxy
sub eax,diam
cmp tempy,eax
jl @f ; posy > maxy ?
mov tempy,eax
fild tempy
fstp fposy ; oui : posy = maxy
fld vecty
fchs
fmul rebond
fstp vecty ; et vecty = -vecty X rebond
mov bonk,1
jmp @suite2
@@: mov eax,1
cmp tempy,eax
jg @suite2 ; posy < miniy ?
mov tempy,eax
fild tempy
fstp fposy ; oui : posy = miniy
fld vecty
fchs
fmul rebond
fstp vecty ; et vecty = -vecty X rebond
mov bonk,1
@suite2:
fld vecty
fadd gravit
fstp vecty ; vecty = vecty + gravité
push oldx2 ; mettre à jour l'histo des 3 dernières positions
pop oldx1
push oldx3
pop oldx2
mov eax,tempx
mov oldx3,eax ; x1 <- x2 <- x3 <- nouveau x
push oldy2
pop oldy1
push oldy3
pop oldy2
mov ecx,tempy
mov oldy3,ecx ; y1 <- y2 <- y3 <- nouveau y
; ; plus de mvt ?
.IF eax==oldx1 && eax==oldx2 && ecx==oldy1 && ecx==oldy2
mov stopflag,1 ; alors stop
mov bonk,0
.ELSE
mov edx,maxy
sub edx,diam
.IF ecx==oldy1 && ecx==oldy2 && ecx==edx ; ça roule en bas ?
fld vectx
fmul frotte ; vectx = vectx X frotte ( ralentissement )
fstp vectx
mov bonk,0
.ENDIF
.ENDIF
; deplacer la balle-fenetre à sa nouvelle position
invoke SetWindowPos,hwnd,HWND_TOPMOST,tempx,tempy,NULL,NULL,SWP_NOSIZE
.IF bonk==1 && sonflag==1 ; il faut produire un son ?
invoke PlaySound,ID_bonk,hInstance,SND_RESOURCE or SND_ASYNC
.ENDIF
invoke Sleep,valpause ; faire un pause bien méritée ;-)
.UNTIL stopflag==1 ; et on recommence jusqu'à ce qu'un STOP soit annoncé
fld fposx
fistp posx
fld fposy
fistp posy ; mettre à jour posx et posy ( les non floating point )
invoke PostMessage,hwnd,THREADMSG,NULL,NULL ; et prevenir
ret
MonThread endp
InfoDlgProc PROC hdlg:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM
.IF uMsg==WM_INITDIALOG ; c'est l'initialisation ?
mov eax,maxy
shr eax,2 ; on positionne la boite en haut à gauche
invoke SetWindowPos,hdlg,HWND_TOPMOST,eax,eax,NULL,NULL,SWP_NOSIZE
; c'est vu ?
.ELSEIF uMsg==WM_CLOSE || (uMsg==WM_COMMAND && wParam==IDC_BTN1)
invoke EndDialog,hdlg,NULL ; bye bye...
.ENDIF
xor eax,eax
ret
InfoDlgProc ENDP
ParamDlgProc PROC hdlg:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM
LOCAL temp:DWORD
LOCAL errflag:DWORD
.IF uMsg==WM_INITDIALOG
; positionner la boite en haut à gauche
mov eax,maxy
shr eax,2
invoke SetWindowPos,hdlg,HWND_TOPMOST,eax,eax,NULL,NULL,SWP_NOSIZE
; imposer des limites aux edits
invoke SendDlgItemMessage,hdlg,IDC_vitesse,EM_SETLIMITTEXT,3,0
invoke SendDlgItemMessage,hdlg,IDC_rebond,EM_SETLIMITTEXT,3,0
invoke SendDlgItemMessage,hdlg,IDC_gravit,EM_SETLIMITTEXT,3,0
invoke SendDlgItemMessage,hdlg,IDC_diam,EM_SETLIMITTEXT,3,0
; initialiser les items pour le son
.IF sonflag == 1
invoke SendDlgItemMessage,hdlg,IDC_son,BM_SETCHECK,BST_CHECKED,0 ; check on
invoke SetDlgItemText,hdlg,IDC_textson,ADDR szon ; text "on"
.ELSE
invoke SetDlgItemText,hdlg,IDC_textson,ADDR szoff ; text "off"
.ENDIF
; initialiser les items pour la vitesse, le rebond et la gravité
mov eax,101 ; valpause comprise entre 1 et 101
sub eax,valpause ; et inversement proportionnelle a
invoke SetDlgItemInt,hdlg,IDC_vitesse,eax,FALSE ; la vitesse
fld rebond
fmul cent ; convertir rebond en pourcent
fistp temp
invoke SetDlgItemInt,hdlg,IDC_rebond,temp,FALSE ; et transmettre
fld gravit
fmul dix ; gravité entre 0 et 10
fistp temp ; à convertir en pourcent
invoke SetDlgItemInt,hdlg,IDC_gravit,temp,FALSE
invoke SetDlgItemInt,hdlg,IDC_diam,diam,FALSE ; le diametre
; annuler ?
.ELSEIF uMsg==WM_CLOSE || (uMsg==WM_COMMAND && wParam==IDC_annuler)
invoke EndDialog,hdlg,NULL ; on ferme
.ELSEIF uMsg==WM_COMMAND && wParam==IDC_ok ; clic sur ok ?
mov errflag,0 ; au depart pas d'erreur
invoke GetDlgItemInt,hdlg,IDC_vitesse,NULL,FALSE ; la vitesse
.IF eax>100 ; si depassement de valeur
invoke ErrMsg,100,ADDR szvitesse ; on affiche msg erreur
mov errflag,1 ; et on signale l'erreur
.ELSE
mov ecx,101 ; sinon on convertit
sub ecx,eax ; et on met à jour
mov valpause,ecx
.ENDIF
invoke GetDlgItemInt,hdlg,IDC_rebond,NULL,FALSE ; idem rebond
.IF eax>100
invoke ErrMsg,100,ADDR szrebond
mov errflag,1
.ELSE
mov temp,eax
fild temp
fdiv cent
fstp rebond
.ENDIF
invoke GetDlgItemInt,hdlg,IDC_gravit,NULL,FALSE ; idem gravité
.IF eax>100
invoke ErrMsg,100,ADDR szgravit
mov errflag,1
.ELSE
mov temp,eax
fild temp
fdiv dix
fstp gravit
.ENDIF
invoke GetDlgItemInt,hdlg,IDC_diam,NULL,FALSE ; le diàmetre...
.IF eax>maxy || eax <5
invoke ErrMsg,maxy,ADDR szdiam
mov errflag,1
.ELSE
mov diam,eax ; si pas d'erreur
invoke CreateEllipticRgn,0,0,diam,diam ; rgn au format
invoke SetWindowRgn,hwnd,eax,1 ; et on y associe la fenetre
mov eax,posx
add eax,diam ; la balle sort de l'écran ?
.IF eax>maxx ; à droite ?
mov eax,maxx ; alors on ajuste
sub eax,diam
mov posx,eax
.ENDIF
mov eax,posy ; idem pour en bas
add eax,diam
.IF eax>maxy
mov eax,maxy
sub eax,diam
mov posy,eax
.ENDIF ; on change le format de la fenetre
invoke SetWindowPos,hwnd,HWND_TOPMOST,posx,posy,NULL,NULL,SWP_NOSIZE
.ENDIF
.IF errflag == 0 ; pas d'erreur ?
invoke EndDialog,hdlg,NULL ; alors on ferme la boite
.ENDIF ; sinon on reste...
.ELSEIF uMsg==WM_COMMAND && wParam==IDC_son ; check "son" ?
invoke SendDlgItemMessage,hdlg,IDC_son,BM_GETCHECK,0,0 ; état ?
.IF eax == BST_CHECKED ; checké ?
mov sonflag,1 ; oui :texte "on"
invoke SetDlgItemText,hdlg,IDC_textson,ADDR szon
.ELSE
mov sonflag,0 ; non : texte "off"
invoke SetDlgItemText,hdlg,IDC_textson,ADDR szoff
.ENDIF
.ENDIF
xor eax,eax
ret
ParamDlgProc ENDP
ErrMsg PROC valmax:DWORD,lpmsg:DWORD
LOCAL buffer[64]:BYTE
invoke wsprintf,ADDR buffer,lpmsg,valmax ; on complète le texte
invoke MessageBox,hwnd,ADDR buffer,ADDR AppName,MB_OK+MB_ICONERROR
ret ; et on affiche
ErrMsg ENDP
end start
Conclusion :
SACHEZ LE : Si vous déplacer un dossier sur votre bureau ( drag ) en même temps que la balle est en mouvement, la trace de celle-ci reste à l?écran. Le simple passage d?une fenêtre sur cette trace suffit à l?effacer? rien de grave donc, mais c?est un bug quand même ! Je n?ai pas encore trouvé la parade et suis preneur si quelqu?un à des pistes !
Boulinator
Vous n'êtes pas encore membre ?
inscrivez-vous, c'est gratuit et ça prend moins d'une minute !
Les membres obtiennent plus de réponses que les utilisateurs anonymes.
Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.
Le fait d'être membre vous permet d'avoir des options supplémentaires.