Je vous présente Pure1010!, un clone du jeu 1010! (https://en.namu.wiki/w/1010!) développé avec l'aide de Claude AI (!!)
Le principe est simple : sur un plateau de 10 X 10, combinez des blocs et videz le plateau en les plaçant de manière stratégique pour créer des lignes
Le jeu est fourni avec deux langages (français et anglais), mais il est facile de rajouter des langues supplémentaires (dans la datasection à la fin du listing et dans la procédure InitLang())
Le listing est assez gros, aussi j'ai dû le couper en plusieurs parties
Pour un fonctionnement optimum, j'utilise une police de caractères spéciale (Nunito) et un fichier .png fournis ici :
https://workupload.com/file/3eSymrMP2aD
Il suffit de les télécharger et de les mettre dans le même répertoire que le listing source
Néanmoins, le jeu fonctionne sans ces fichiers
Normalement, le jeu est multi-plateforme (Développé sur MacOS, testé sur Windows 11 et Ubuntu Desktop)
Amusez-vous bien !
Une petite image du jeu (merci Venom pour la suggestion !)

Partie 1
Code : Tout sélectionner
; ┌──────────────────────────────────────────────────────────────────────┐
; │ │
; │ ▄▄▄▄▄▄▄ ▄▄▄▄ ▄▄▄▄ ▄▄▄▄ ▄▄▄▄ ▄▄ │
; │ ███▀▀███▄ ▄█████ ▄██████▄ ▄█████ ▄██████▄ ██ │
; │ ███▄▄███▀ ██ ██ ████▄ ▄█▀█▄ ███ ███ ███ ███ ███ ███ ██ │
; │ ███▀▀▀▀ ██ ██ ██ ▀▀ ██▄█▀ ███ ███▄▄███ ███ ███▄▄███ ▀▀ │
; │ ███ ▀██▀█ ██ ▀█▄▄▄ ███ ▀████▀ ███ ▀████▀ ██ │
; │ │
; │ │
; │ Code by Mindphazer & Claude AI │
; │ │
; │ v 1.6 (c) 2026 │
; │ │
; │ Compatibilité OS : │
; │ [x] MacOS - Main development platform │
; │ [x] Windows - [x] DPI aware (hopefully!) │
; │ [x] Linux - Tested on Ubuntu Desktop │
; └──────────────────────────────────────────────────────────────────────┘
EnableExplicit
Declare CanPlace(pi.i, gr.i, gc.i)
UsePNGImageDecoder()
RegisterFontFile("Nunito-Regular.ttf")
RegisterFontFile("Nunito-Bold.ttf")
;-============== CONSTANTES ==================
#BASE_WINDOW_W = 650
#BASE_WINDOW_H = 800
#BASE_CELL_SIZE = 46
#BASE_GRID_X = 30
#BASE_GRID_Y = 90
#GRID_COLS = 10
#GRID_ROWS = 10
#BASE_SLOT_Y = 595
#BASE_PIECE_CELL = 32
#TIMED_DURATION = 180 ; secondes pour le mode contre-la-montre
#SURVIVAL_PERIOD = 30 ; secondes entre chaque rangée montante
#SHAPE_COUNT = 19 ; nombre de formes
#COL_BG = $1A1A2E
#COL_GRID_BG = $16213E
#COL_GRID_LN = $0F3460
#COL_TEXT = $E0E0E0
#COL_SCORE_BG = $0F3460
#COL_GAMEOVER = $CC3333
#COL_GHOST_NO = $444444
; Normal : bleu foncé marine
#BTN_N_TOP = $13458B ; haut dégradé normal → bleu clair-moyen
#BTN_N_BOT = $03154B ; bas dégradé normal → bleu très foncé
; Hover : un cran plus clair
#BTN_H_TOP = $2868C0 ; haut hover
#BTN_H_BOT = $183870 ; bas hover
; Pressed : sombre, inversé
#BTN_P_TOP = $081030 ; haut pressed (le plus sombre)
#BTN_P_BOT = $304890 ; bas pressed
; disabled
#BTN_D_TOP = $444444
#BTN_D_BOT = $888888
; Bordures
#BTN_BORDER = $000820 ; contour externe très sombre
#BTN_SHINE = $5888D0 ; reflet haut (biseau clair)
#BTN_SHADE = $000418 ; ombre bas (biseau sombre)
; Rayon des coins (unités logiques)
#BTN_RADIUS = 5
; Animation effacement lignes/colonnes
#ANIM_STEPS = 8 ; frames de flash + fondu
#ANIM_DELAY = 35 ; ms par frame
;-============== ENUMERATIONS ==================
; --- Textes
Enumeration
#TXT_SCORE
#TXT_HIGHSCORE
#TXT_TIME
#TXT_PIECES
#TXT_GAMEOVER
#TXT_FINAL
#TXT_BEST
#TXT_REPLAY
#TXT_CLASSIC
#TXT_TIMED
#TXT_CLEARANCE
#TXT_SURVIVAL
#TXT_UNDO
#TXT_QUIT
#TXT_STOP_GAME
#TXT_GAMEINPROGRESS
#TXT_STARTGAME
#TXT_MODE
EndEnumeration
Enumeration
#MainWindow
#Canvas
#TimerAnimation
#TimerTime
#ButtonNewClassic
#ButtonNewTimed
#ButtonNewClearance
#ButtonNewSurvival
#ButtonUndo
#ButtonStop
#ButtonQuit
#Font_Big
#Font_Med
#Font_Over
#Font_Small
EndEnumeration
Enumeration
#MODE_CLASSIC ; mode classique
#MODE_TIMED ; compte à rebours
#MODE_CLEARANCE ; plateau pré-rempli
#MODE_SURVIVAL ; rangées qui montent
EndEnumeration
;-============== STRUCTURES ==================
Structure Cell
filled.b
color.i
colorindex.i
EndStructure
Structure PieceShape
rows.i
cols.i
cells.b[25] ; 5x5 max
color.i
valid.b
colorindex.i
EndStructure
Structure ShapeDef
r.i
c.i
d.s
EndStructure
;-============== VARIABLES GLOBALES ==================
; ============================================================
; Variables globales DPI (calculées une fois au démarrage)
; ============================================================
Global WINDOW_W.i, WINDOW_H.i
Global CELL_SIZE.i
Global GRID_X.i, GRID_Y.i
Global SLOT_Y.i
Global PIECE_CELL.i
Global SLOT_W.i ; largeur d'un emplacement de pièce (1/3 de la zone)
Global SLOT_SQUARE.i ; côté du carré de centrage (= min de la largeur et hauteur dispo)
Global SLOT_ZONE_H.i ; hauteur disponible pour les pièces (WINDOW_H - SLOT_Y - marge)
CompilerIf #PB_Compiler_OS = #PB_OS_MacOS
Global FontFactor.f = 1.5
CompilerElse
Global FontFactor.f = 1.0
CompilerEndIf
Global Dim Textes.s(40)
Global Dim PieceColors.i(10)
Global Dim PieceImages.i(10)
Global Dim Grid.Cell(9, 9)
Global Dim Undo.Cell(9, 9)
Global Dim Pieces.PieceShape(2)
Global Logo = LoadImage(#PB_Any, "Logo.png")
;Global Logo = CatchImage(#PB_Any, ?Logo_png_start)
;Global Cube = LoadImage(#PB_Any, "Cube.png")
Global Cube = CatchImage(#PB_Any, ?Cube_png_start)
Global HS_FILE.s = GetHomeDirectory() + "1010_highscore.dat"
Global Version.s = "1.6"
Global GameMode.i = -1
Global GameBoard.i
Global Dim Shapes.ShapeDef(#SHAPE_COUNT - 1)
Global Score.i = 0
Global Dim HiScore.i(3)
Global GameOver.b = #False
Global QuitGame.b = #False
Global GameInProgress.b = #False
Global PlayingTime
; Drag & Drop
Global DragActive.b = #False
Global DragPiece.i = -1
Global DragX.i, DragY.i
Global DragOffX.i, DragOffY.i
; Animation effacement
Global AnimActive.b = #False
Global AnimStep.i = 0
Global AnimTimer.i = 0
Global AnimBonus.i = 0
Global Dim AnimRow.b(9)
Global Dim AnimCol.b(9)
Global Dim AnimColor.i(9, 9)
Global UndoAvailable.b = #False
Global UndoScore.i
Global UndoPlayingTime.i
Global UndoGameOver.b
Global UndoGameInProgress.b
Global Dim UndoPieces.PieceShape(2)
Procedure.s GetLanguage()
;
; Cette fonction permet de récupérer la langue native du système, selon le type d'OS
;
Protected Result.s
CompilerIf #PB_Compiler_OS = #PB_OS_MacOS ; Thanks to Danilo
Protected NSUserDefaults_defs, NSString_locale
NSUserDefaults_defs = CocoaMessage(0, 0, "NSUserDefaults standardUserDefaults")
If NSUserDefaults_defs
NSString_locale = CocoaMessage(0, NSUserDefaults_defs, "objectForKey:$", @"AppleLocale")
If NSString_locale
Result = Left(PeekS(CocoaMessage(0, NSString_locale, "UTF8String"), -1, #PB_UTF8), 2)
EndIf
EndIf
ProcedureReturn Result
CompilerElseIf #PB_Compiler_OS = #PB_OS_Windows ; Thanks to Rescator
Define dll.i, text$, len.i, *GetLocaleInfo
dll = OpenLibrary(#PB_Any,"kernel32.dll")
If dll
CompilerIf #PB_Compiler_Unicode
*GetLocaleInfo = GetFunction(dll, "GetLocaleInfoW")
CompilerElse
*GetLocaleInfo = GetFunction(dll, "GetLocaleInfoA")
CompilerEndIf
If *GetLocaleInfo
len = CallFunctionFast(*GetLocaleInfo, #LOCALE_USER_DEFAULT, $0059, #Null, #Null)
If len
text$ = Space(len - 1)
If CallFunctionFast(*GetLocaleInfo, #LOCALE_USER_DEFAULT, $0059, @text$, len)
Result = text$
EndIf
EndIf
EndIf
CloseLibrary(dll)
EndIf
ProcedureReturn Result
CompilerElseIf #PB_Compiler_OS = #PB_OS_Linux ; Thanks to AZJIO
If ExamineEnvironmentVariables()
While NextEnvironmentVariable()
If Left(EnvironmentVariableName(), 4) = "LANG"
Result = Left(EnvironmentVariableValue(), 2)
Break
EndIf
Wend
EndIf
ProcedureReturn Result
CompilerEndIf
EndProcedure
Procedure InitLang()
Protected id, l.s, lang.s = GetLanguage()
Select lang
Case "fr"
Restore FRA
Case "us" , "en"
Restore US
Case "ru"
Restore RUS
Default
Restore FRA
EndSelect
Repeat
Read.s l
If l <> "@@@"
Textes(id) = l
id + 1
EndIf
Until l = "@@@"
EndProcedure
Procedure InitDPI()
WINDOW_W = DesktopScaledX(#BASE_WINDOW_W)
WINDOW_H = DesktopScaledY(#BASE_WINDOW_H)
CELL_SIZE = DesktopScaledX(#BASE_CELL_SIZE)
GRID_X = DesktopScaledX(#BASE_GRID_X)
GRID_Y = DesktopScaledY(#BASE_GRID_Y)
SLOT_Y = DesktopScaledY(#BASE_SLOT_Y)
PIECE_CELL = DesktopScaledX(#BASE_PIECE_CELL)
SLOT_W = (WINDOW_W - GRID_X * 2) / 3
SLOT_ZONE_H = WINDOW_H - SLOT_Y - DesktopScaledY(20)
Protected slotPad.i = DesktopScaledX(6)
Protected idealSide.i = 5 * PIECE_CELL + slotPad * 2
SLOT_SQUARE = idealSide
If SLOT_W < SLOT_SQUARE : SLOT_SQUARE = SLOT_W : EndIf
If SLOT_ZONE_H < SLOT_SQUARE : SLOT_SQUARE = SLOT_ZONE_H : EndIf
EndProcedure
Procedure InitColors()
PieceColors(0) = $FF0000 ; rouge
PieceColors(1) = $00FF00 ; vert
PieceColors(2) = $0000FF ; bleu
PieceColors(3) = $FFFF00 ; jaune
PieceColors(4) = $00FFFF ; cyan
PieceColors(5) = $FF8800 ; orange
PieceColors(6) = $387814 ; vert foncé
PieceColors(7) = $0088FF ; bleu azur
PieceColors(8) = $D30094 ; violet
PieceColors(9) = $9314FF ; rose (très distinct)
PieceColors(10) = $444466 ; marron
EndProcedure
Procedure InitShapeDefs()
Protected r.s, c.s, d.s, i
Restore shapes
For i = 0 To #SHAPE_COUNT - 1
Read.s r
Read.s c
Read.s d
shapes(i)\c = Val(c)
shapes(i)\r = Val(r)
shapes(i)\d = d
Next i
EndProcedure
DataSection
shapes:
Data.s "1", "1", "1"
Data.s "1", "2", "11"
Data.s "2", "1", "11"
Data.s "1", "3", "111"
Data.s "3", "1", "111"
Data.s "2", "2", "1111"
Data.s "2", "2", "1110"
Data.s "2", "2", "1101"
Data.s "2", "2", "1011"
Data.s "2", "2", "0111"
Data.s "1", "4", "1111"
Data.s "4", "1", "1111"
Data.s "1", "5", "11111"
Data.s "5", "1", "11111"
Data.s "3", "3", "111111111"
Data.s "3", "3", "111100100"
Data.s "3", "3", "100100111"
Data.s "3", "3", "111001001"
Data.s "3", "3", "001001111"
EndDataSection
Procedure GeneratePiece(idx.i)
Protected s.i, r.i, c.i, ci.i, Color
s = Random(#SHAPE_COUNT - 1)
Color = Random(9)
Pieces(idx)\rows = Shapes(s)\r
Pieces(idx)\cols = Shapes(s)\c
Pieces(idx)\color = PieceColors(Color)
Pieces(idx)\valid = #True
Pieces(idx)\colorindex = Color
ci = 0
For r = 0 To Shapes(s)\r - 1
For c = 0 To Shapes(s)\c - 1
If Mid(Shapes(s)\d, ci + 1, 1) = "1"
Pieces(idx)\cells[r * 5 + c] = 1
Else
Pieces(idx)\cells[r * 5 + c] = 0
EndIf
ci + 1
Next
Next
EndProcedure
Procedure GenerateAllPieces()
Protected i.i
For i = 0 To 2 : GeneratePiece(i) : Next
EndProcedure
CompilerIf #PB_Compiler_OS = #PB_OS_MacOS
Prototype Proto_AppShouldTerminate(Object, Selector, Sender)
DeclareC AppShouldTerminate(Object, Selector, Sender)
Global AppDelegate, AppShouldTerminate_.Proto_AppShouldTerminate
AppDelegate = CocoaMessage(0, CocoaMessage(0, 0, "NSApplication sharedApplication"), "delegate")
AppShouldTerminate_ = class_replaceMethod_(CocoaMessage(0, AppDelegate, "class"), sel_registerName_("applicationShouldTerminate:"), @AppShouldTerminate(), "v@:@")
ProcedureC AppShouldTerminate(Object, Selector, Sender)
QuitGame = #True
If AppShouldTerminate_
ProcedureReturn AppShouldTerminate_(Object, Selector, Sender)
EndIf
EndProcedure
CompilerEndIf
Procedure Max(a, b)
If a > b
ProcedureReturn a
Else
ProcedureReturn b
EndIf
EndProcedure
Procedure Min(a, b)
If a < b
ProcedureReturn a
Else
ProcedureReturn b
EndIf
EndProcedure
; ------------------------------------------------------------
; FindBestClearZone
; Cherche la zone rows x cols la plus dense dans la grille
; et retourne son coin supérieur gauche via *pgr/*pgc.
; "Plus dense" = celle qui contient le plus de cellules
; remplies, c'est là qu'il est le plus utile de faire de la place.
; ------------------------------------------------------------
Procedure FindBestClearZone(rows.i, cols.i, *pgr, *pgc)
Protected r.i, c.i, br.i, bc.i, best.i = -1, count.i
For r = 0 To 9 - rows
For c = 0 To 9 - cols
count = 0
Protected rr.i, cc.i
For rr = r To r + rows - 1
For cc = c To c + cols - 1
If Grid(cc, rr)\filled : count + 1 : EndIf
Next
Next
If count > best
best = count
br = r : bc = c
EndIf
Next
Next
PokeI(*pgr, br)
PokeI(*pgc, bc)
EndProcedure
; ------------------------------------------------------------
; ClearZoneForPiece
; Vide une zone de taille rows x cols à la position gr, gc.
; On ne vide que le strict minimum : on cherche d'abord si
; la pièce peut déjà se placer dans cette zone après un
; vidage partiel (on retire cellule par cellule jusqu'au
; premier placement valide).
; ------------------------------------------------------------
Procedure ClearZoneForPiece(pi.i, gr.i, gc.i)
Protected r.i, c.i, rr.i, cc.i
; Collecter les cellules remplies dans la zone, mélangées
Protected Dim zr.i(24)
Protected Dim zc.i(24)
Protected zCount.i = 0
For rr = gr To gr + Pieces(pi)\rows - 1
For cc = gc To gc + Pieces(pi)\cols - 1
If rr <= 9 And cc <= 9 And Grid(cc, rr)\filled
zr(zCount) = rr : zc(zCount) = cc : zCount + 1
EndIf
Next
Next
; Vider une à une jusqu'à ce que le placement soit possible
Protected idx.i
For idx = 0 To zCount - 1
Grid(zc(idx), zr(idx))\filled = #False
Grid(zc(idx), zr(idx))\color = 0
If CanPlace(pi, gr, gc) : Break : EndIf
Next
EndProcedure
; ------------------------------------------------------------
; EnsurePieceCanBePlaced
; Vérifie qu'une pièce peut se placer quelque part sur la grille.
; Si non, ouvre une zone dans la zone la plus dense.
; Recommence jusqu'à 5 fois si nécessaire (sécurité).
; ------------------------------------------------------------
Procedure EnsurePieceCanBePlaced(pi.i)
Protected r.i, c.i, gr.i, gc.i, attempt.i
For attempt = 0 To 4
; Vérifier si au moins une position est libre
For r = 0 To 9
For c = 0 To 9
If CanPlace(pi, r, c) : ProcedureReturn : EndIf
Next
Next
; Aucune position valide : trouver la zone la plus dense
; de la taille de la pièce et y ouvrir de la place
FindBestClearZone(Pieces(pi)\rows, Pieces(pi)\cols, @gr, @gc)
ClearZoneForPiece(pi, gr, gc)
Next
EndProcedure
; ------------------------------------------------------------
; FillGridPartially
; Remplit la grille à environ pct% de façon aléatoire,
; puis garantit que chaque pièce de départ est plaçable.
; pct recommandé : 35-45 pour le mode déblayage.
; ------------------------------------------------------------
Procedure FillGridPartially(pct.i)
Protected r.i, c.i, i.i, Color.i
; 1. Remplissage aléatoire
For r = 0 To 9
For c = 0 To 9
If Random(99) < pct
Color = Random(9)
Grid(c, r)\filled = #True
Grid(c, r)\color = PieceColors(Color)
grid(c, r)\colorindex = Color
Else
Grid(c, r)\filled = #False
Grid(c, r)\color = 0
EndIf
Next
Next
; 2. Générer les pièces de départ
GenerateAllPieces()
; 3. Garantir que chaque pièce peut être placée
For i = 0 To 2
EnsurePieceCanBePlaced(i)
Next
EndProcedure
; Fait monter toutes les rangées d'un cran et insère une
; nouvelle rangée partiellement remplie en bas
Procedure PushRowUp()
Protected r.i, c.i, i.i
; Décaler vers le haut (la rangée 0 disparaît)
For r = 0 To 8
For c = 0 To 9
Grid(c, r)\filled = Grid(c, r + 1)\filled
Grid(c, r)\color = Grid(c, r + 1)\color
Grid(c, r)\colorindex = Grid(c, r + 1)\colorindex
Next
Next
; Nouvelle rangée en bas avec des trous (jouable)
Protected holes.i = 2 + Random(2) ; 2 à 4 trous
For c = 0 To 9
Grid(c, 9)\filled = #True
Grid(c, 9)\color = $444466
Grid(c, 9)\colorindex = 10
Next
For i = 1 To holes
Grid(Random(9), 9)\filled = #False
Next
EndProcedure
; ============================================================
; DrawCanvasButton
; ParentBG : couleur de fond du parent pour masquer les coins
; State : 0 = normal, 1 = hover, 2 = pressed
; ============================================================
Procedure DrawCanvasButton(Gadget, Image, State = 0, ParentBG = #COL_BG)
Protected dx.i = DesktopScaledX(GadgetWidth(Gadget))
Protected dy.i = DesktopScaledY(GadgetHeight(Gadget))
Protected rad.i = DesktopScaledX(#BTN_RADIUS)
Protected offX.i = 0, offY.i = 0
Protected topCol.i, botCol.i
Select State
Case 0 : topCol = #BTN_N_TOP : botCol = #BTN_N_BOT
Case 1 : topCol = #BTN_H_TOP : botCol = #BTN_H_BOT
Case 2 : topCol = #BTN_P_TOP : botCol = #BTN_P_BOT : offX = DesktopScaledX(1) : offY = DesktopScaledY(1)
Case 3 : topCol = #BTN_D_TOP : botCol = #BTN_D_BOT
EndSelect
If Not StartDrawing(CanvasOutput(Gadget)) : ProcedureReturn : EndIf
; --- 1. Effacement propre du fond ---
DrawingMode(#PB_2DDrawing_Default)
Box(0, 0, dx, dy, ParentBG)
; --- 2. Dégradé appliqué directement sur RoundBox ---
DrawingMode(#PB_2DDrawing_Gradient)
If State < 2
LinearGradient(0, 0, 0, dy) ; haut → bas
Else
LinearGradient(0, dy, 0, 0) ; inversé pour pressed
EndIf
GradientColor(0.0, topCol)
GradientColor(1.0, botCol)
RoundBox(0, 0, dx, dy, rad, rad, topCol)
DrawingMode(#PB_2DDrawing_Default)
; --- 3. Reflets de bord (biseau) ---
If State < 2
Line(rad, 0, dx - rad * 2, 0, #BTN_SHINE)
Line(0, rad, 0, dy - rad * 2, #BTN_SHINE)
Line(rad, dy - 1, dx - rad * 2, 0, #BTN_SHADE)
Line(dx - 1, rad, 0, dy - rad * 2, #BTN_SHADE)
Else
Line(rad, 0, dx - rad * 2, 0, #BTN_SHADE)
Line(0, rad, 0, dy - rad * 2, #BTN_SHADE)
Line(rad, dy - 1, dx - rad * 2, 0, #BTN_SHINE)
Line(dx - 1, rad, 0, dy - rad * 2, #BTN_SHINE)
EndIf
; --- 4. Contour arrondi ---
DrawingMode(#PB_2DDrawing_Outlined)
RoundBox(0, 0, dx, dy, rad, rad, #BTN_BORDER)
; --- 5. Image centrée ---
DrawingMode(#PB_2DDrawing_AlphaBlend)
If IsImage(Image)
Protected imgW.i = ImageWidth(Image)
Protected imgH.i = ImageHeight(Image)
Protected imgX.i = (dx - imgW) / 2 + offX
Protected imgY.i = (dy - imgH) / 2 + offY
DrawImage(ImageID(Image), imgX, imgY)
EndIf
StopDrawing()
EndProcedure
Procedure DoCanvasButtonEvent()
Protected Gadget = EventGadget()
Protected Image = GetGadgetData(Gadget)
Select EventType()
Case #PB_EventType_MouseEnter : DrawCanvasButton(Gadget, Image, 1)
Case #PB_EventType_MouseLeave : DrawCanvasButton(Gadget, Image, 0)
Case #PB_EventType_LeftButtonDown: DrawCanvasButton(Gadget, Image, 2)
Case #PB_EventType_LeftButtonUp : DrawCanvasButton(Gadget, Image, 1)
EndSelect
EndProcedure
; ============================================================
; CreateButtonVectorImage — image transparente texte centré
; Le texte est rendu en vecteur avec ombre subtile.
; Si IconID > 0 : icône 20x20 à gauche, texte à droite.
; ============================================================
Procedure CreateButtonImage(Image, Width, Height, Text.s, IconID = 0)
Static FontBtn.i
Protected dx.i = DesktopScaledX(Width)
Protected dy.i = DesktopScaledY(Height)
Protected r1.i
Protected IconWidth.i = 20
If Not FontBtn
Protected fontSize.f = 11 * (DesktopResolutionY() / FontFactor)
FontBtn = LoadFont(#PB_Any, "Nunito", fontSize, #PB_Font_HighQuality)
EndIf
r1 = CreateImage(Image, dx, dy, 32, #PB_Image_Transparent)
If Image = #PB_Any : Image = r1 : EndIf
If r1
If StartDrawing(ImageOutput(Image))
DrawingFont(FontID(FontBtn))
Protected tw.f = TextWidth(Text)
Protected th.f = TextHeight(Text)
Protected tx.f, ty.f
If IconID > 0
Protected iSz.f = DesktopScaledX(IconWidth)
Protected Factor.f = ImageWidth(IconID) / IconWidth
Protected iSzH.f = DesktopScaledY(ImageHeight(IconID) / Factor)
Protected gap.f = DesktopScaledX(6)
Protected totW.f = iSz + gap + tw
Protected ix.f = (dx - totW) / 2
Protected iy.f = (dy - iSzH) / 2
DrawingMode(#PB_2DDrawing_AlphaBlend)
DrawImage(ImageID(IconID), ix, iy, iSz, iSzH)
tx = ix + iSz + gap
Else
tx = (dx - tw) / 2
EndIf
ty = (dy - th) / 2
; Ombre portée légère
DrawingMode(#PB_2DDrawing_AlphaBlend | #PB_2DDrawing_Transparent)
DrawText(tx + DesktopScaledX(1), ty + DesktopScaledY(1), Text, RGBA(0, 0, 0, 140))
; Texte blanc cassé
DrawText(tx, ty, Text, RGBA(235, 235, 235, 255))
StopDrawing()
EndIf
EndIf
ProcedureReturn r1
EndProcedure
Procedure CanvasButtonGadget(Gadget, x, y, Width, Height, Texte.s, Icone = #False)
Protected Image = CreateButtonImage(#PB_Any, Width, Height, Texte, Cube * Icone)
Protected r1 = CanvasGadget(Gadget, x, y, Width, Height)
If Gadget = #PB_Any : Gadget = r1 : EndIf
If r1
SetGadgetData(Gadget, Image)
DrawCanvasButton(Gadget, Image, 0)
BindGadgetEvent(Gadget, @DoCanvasButtonEvent())
EndIf
ProcedureReturn r1
EndProcedure
Procedure CanPlace(pi.i, gr.i, gc.i)
Protected r.i, c.i, nr.i, nc.i
For r = 0 To Pieces(pi)\rows - 1
For c = 0 To Pieces(pi)\cols - 1
If Pieces(pi)\cells[r * 5 + c]
nr = gr + r : nc = gc + c
If nr < 0 Or nr > 9 Or nc < 0 Or nc > 9 : ProcedureReturn #False : EndIf
If Grid(nc, nr)\filled : ProcedureReturn #False : EndIf
EndIf
Next
Next
ProcedureReturn #True
EndProcedure
Procedure PlacePiece(pi.i, gr.i, gc.i)
Protected r.i, c.i
For r = 0 To Pieces(pi)\rows - 1
For c = 0 To Pieces(pi)\cols - 1
If Pieces(pi)\cells[r * 5 + c]
Grid(gc + c, gr + r)\filled = #True
Grid(gc + c, gr + r)\color = Pieces(pi)\color
Grid(gc + c, gr + r)\colorindex = Pieces(pi)\colorindex
EndIf
Next
Next
Pieces(pi)\valid = #False
EndProcedure
Procedure ClearLines()
Protected r.i, c.i, full.b, count.i, bonus.i
For r = 0 To 9 : AnimRow(r) = #False : Next
For c = 0 To 9 : AnimCol(c) = #False : Next
count = 0
For r = 0 To 9
full = #True
For c = 0 To 9 : If Not Grid(c, r)\filled : full = #False : Break : EndIf : Next
If full : AnimRow(r) = #True : count + 1 : EndIf
Next
For c = 0 To 9
full = #True
For r = 0 To 9 : If Not Grid(c, r)\filled : full = #False : Break : EndIf : Next
If full : AnimCol(c) = #True : count + 1 : EndIf
Next
If count = 0 : ProcedureReturn 0 : EndIf
If count = 1 : bonus = 10
ElseIf count = 2 : bonus = 20
ElseIf count >= 3 : bonus = 30 + (count - 3) * 15
EndIf
For r = 0 To 9
For c = 0 To 9
AnimColor(c, r) = Grid(c, r)\color
Next
Next
AnimActive = #True
AnimStep = 0
ProcedureReturn bonus
EndProcedure
Procedure ApplyClear()
Protected r.i, c.i
For r = 0 To 9
If AnimRow(r)
For c = 0 To 9 : Grid(c, r)\filled = #False : Grid(c, r)\color = 0 : Grid(c, r)\colorindex = 0 : Next
EndIf
Next
For c = 0 To 9
If AnimCol(c)
For r = 0 To 9 : Grid(c, r)\filled = #False : Grid(c, r)\color = 0 : Grid(c, r)\colorindex = 0 : Next
EndIf
Next
AnimActive = #False
EndProcedure
Procedure CheckGameOver()
Protected i.i, r.i, c.i
For i = 0 To 2
If Not Pieces(i)\valid : Continue : EndIf
For r = 0 To 9
For c = 0 To 9
If CanPlace(i, r, c) : ProcedureReturn #False : EndIf
Next
Next
Next
ProcedureReturn #True
EndProcedure
; ============================================================
; Conversion coordonnées souris -> cellule grille
; Les coordonnées souris du CanvasGadget sont déjà en pixels
; physiques sur Windows DPI-aware, donc on utilise directement
; les variables GRID_X, CELL_SIZE déjà mises à l'échelle.
; ============================================================
Procedure GetDropCell(mx.i, my.i, *pgr, *pgc)
Protected snX.i = mx - DragOffX
Protected snY.i = my - DragOffY
PokeI(*pgr, (snY - GRID_Y + CELL_SIZE / 2) / CELL_SIZE)
PokeI(*pgc, (snX - GRID_X + CELL_SIZE / 2) / CELL_SIZE)
EndProcedure
Procedure LoadHiScore()
Protected f.i, i.i
If FileSize(HS_FILE) > 0
f = ReadFile(#PB_Any, HS_FILE)
If f
Repeat
HiScore(i) = Val(ReadString(f))
i + 1
Until Eof(f) = #True Or i > 3
CloseFile(f)
EndIf
EndIf
EndProcedure
Procedure SaveHiScore()
Protected i.i
Protected f.i = CreateFile(#PB_Any, HS_FILE)
If f
For i = 0 To 3
WriteStringN(f, Str(HiScore(i)))
Next i
CloseFile(f)
EndIf
EndProcedure
Procedure ResetGame()
Protected r.i, c.i
For r = 0 To 9
For c = 0 To 9
Grid(c, r)\filled = #False
Grid(c, r)\color = 0
Grid(c, r)\colorindex = 0
Next
Next
Score = 0
GameOver = #False
DragActive = #False
DragPiece = -1
Dim Pieces.PieceShape(2)
PlayingTime = 0
AnimActive = #False
AnimStep = 0
UndoAvailable = #False
DisableGadget(#ButtonUndo, #True)
DrawCanvasButton(#ButtonUndo, GetGadgetData(#ButtonUndo), 3)
EndProcedure
Procedure NewGame()
ResetGame()
Select GameMode
Case #MODE_TIMED : PlayingTime = #TIMED_DURATION : GenerateAllPieces() ; compte à rebours
Case #MODE_CLEARANCE : PlayingTime = 0 : FillGridPartially(35) ; ~35% rempli
Case #MODE_SURVIVAL : PlayingTime = 0 : GenerateAllPieces()
Default : PlayingTime = 0 : GenerateAllPieces()
EndSelect
EndProcedure
Procedure Clamp255(v.i)
If v < 0 : ProcedureReturn 0 : EndIf
If v > 255 : ProcedureReturn 255 : EndIf
ProcedureReturn v
EndProcedure
Procedure MixColor(col1.i, col2.i, f.f)
Protected r = Red(col1) + (Red(col2) - Red(col1)) * f
Protected g = Green(col1) + (Green(col2) - Green(col1)) * f
Protected b = Blue(col1) + (Blue(col2) - Blue(col1)) * f
ProcedureReturn RGB(Clamp255(r), Clamp255(g), Clamp255(b))
EndProcedure
Procedure DrawCell(x.i, y.i, sz.i, col.i)
Protected r = Red(col), g = Green(col), b = Blue(col)
Protected light1 = RGB(Clamp255(r * 1.25), Clamp255(g * 1.25), Clamp255(b * 1.15))
Protected light2 = RGB(Clamp255(r * 1.08), Clamp255(g * 1.08), Clamp255(b * 1.05))
Protected dark1 = RGB(r * 0.72, g * 0.72, b * 0.78)
Protected dark2 = RGB(r * 0.42, g * 0.42, b * 0.48)
Protected border = RGB(r * 0.30, g * 0.30, b * 0.35)
Protected inner = sz - 4
; Fond principal, dégradé diagonal doux
DrawingMode(#PB_2DDrawing_Gradient)
LinearGradient(x, y, x + sz, y + sz)
GradientColor(0.00, light1)
GradientColor(0.45, light2)
GradientColor(1.00, dark1)
Box(x, y, sz, sz)
; Reflet supérieur discret
DrawingMode(#PB_2DDrawing_AlphaBlend | #PB_2DDrawing_Gradient)
LinearGradient(x, y, x, y + sz / 2)
GradientColor(0.0, RGBA(255, 255, 255, 90))
GradientColor(1.0, RGBA(255, 255, 255, 0))
Box(x + 2, y + 2, sz - 4, sz / 2)
; Ombre intérieure bas
LinearGradient(x, y + sz / 2, x, y + sz)
GradientColor(0.0, RGBA(0, 0, 0, 0))
GradientColor(1.0, RGBA(0, 0, 0, 80))
Box(x + 2, y + sz / 2, sz - 4, sz / 2 - 2)
DrawingMode(#PB_2DDrawing_Default)
; Biseau fin, 1 pixel seulement
Line(x + 1, y + 1, sz - 2, DesktopScaledY(1), MixColor(light1, $FFFFFF, 0.35)) ; haut
Line(x + 1, y + 1, DesktopScaledX(1), sz - 2, MixColor(light1, $FFFFFF, 0.20)) ; gauche
Line(x + 1, y + sz - 2, sz - 2, 1, dark2) ; bas
Line(x + sz - 2, y + 1, 1, sz - 2, dark2) ; droite
; Contour externe
Line(x, y, sz, 1, border)
Line(x, y, 1, sz, border)
Line(x, y + sz - 1, sz, 1, RGB(r * 0.18, g * 0.18, b * 0.22))
Line(x + sz - 1, y, 1, sz, RGB(r * 0.18, g * 0.18, b * 0.22))
EndProcedure
Procedure CenterText(x, y, w, h, texte.s)
Protected tw = TextWidth(texte)
Protected th = TextHeight(texte)
DrawingMode(#PB_2DDrawing_Transparent)
DrawText(x + (w / 2) - (tw / 2) + 1, y + (h / 2) - (th / 2) + 1, texte, #Black)
DrawText(x + (w / 2) - (tw / 2), y + (h / 2) - (th / 2), texte)
DrawingMode(#PB_2DDrawing_Default)
EndProcedure
Procedure FlashAnimation()
Protected aStep.i = AnimStep
Protected aMax.i = #ANIM_STEPS / 2 ; 4 frames par phase
Protected aAlpha.i, aShrink.i, aOff.i, aT.i, c.i, r.i
Protected aX.i, aY.i, aSz.i
Protected pad2x.i = DesktopScaledX(2)
Protected pad2y.i = DesktopScaledY(2)
Protected cellInner.i = CELL_SIZE - pad2x * 2 ; taille intérieure d'une cellule
; Intensité globale 0..255 : monte en phase flash, descend en phase fondu
Protected aIntens.i
If aStep < aMax
aIntens = (aStep * 255) / (aMax - 1)
Else
aIntens = 255 - ((aStep - aMax) * 255) / (aMax - 1)
EndIf
If aIntens < 0 : aIntens = 0 : EndIf
If aIntens > 255 : aIntens = 255 : EndIf
DrawingMode(#PB_2DDrawing_AlphaBlend)
Protected haloLayers.i = DesktopScaledX(10) ; épaisseur totale du halo en px
Protected hLayer.i, hAlpha.i
Protected hR.i = 255, hG.i = 238, hB.i = 200 ; teinte blanc-cyan chaude
For r = 0 To 9
For c = 0 To 9
If Not (AnimRow(r) Or AnimCol(c)) : Continue : EndIf
If Not Grid(c, r)\filled : Continue : EndIf
aX = GRID_X + c * CELL_SIZE + pad2x
aY = GRID_Y + r * CELL_SIZE + pad2y
aSz = cellInner
; Couronnes du plus externe au plus interne
For hLayer = haloLayers To 1 Step -1
; Opacité : max au bord de la cellule, nulle à l'extrémité externe
; = aIntens * (1 - hLayer/haloLayers) → 0 au bord ext, aIntens au bord cell
hAlpha = (aIntens * (haloLayers - hLayer + 1)) / (haloLayers + 1)
If hAlpha > 0
; Rectangle encadrant la cellule, élargi de hLayer px de chaque côté
Box(aX - hLayer, aY - hLayer, aSz + hLayer * 2, aSz + hLayer * 2, RGBA(hR, hG, hB, hAlpha))
EndIf
Next hLayer
Next
Next
For r = 0 To 9
For c = 0 To 9
If Not (AnimRow(r) Or AnimCol(c)) : Continue : EndIf
If Not Grid(c, r)\filled : Continue : EndIf
aX = GRID_X + c * CELL_SIZE + pad2x
aY = GRID_Y + r * CELL_SIZE + pad2y
aSz = cellInner
If aStep < aMax
; Phase flash : overlay blanc croissant sur la couleur d'origine
DrawCell(aX, aY, aSz, AnimColor(c, r))
Box(aX, aY, aSz, aSz, RGBA(255, 255, 255, aIntens))
Else
; Phase fondu : carré blanc qui rétrécit vers le centre
aT = aStep - aMax
aShrink = (aT * (aSz / 2)) / (aMax - 1)
aOff = aShrink
Box(aX, aY, aSz, aSz, #COL_GRID_BG)
If aSz - 2 * aOff > 0
Box(aX + aOff, aY + aOff, aSz - 2 * aOff, aSz - 2 * aOff, RGBA(255, 255, 255, aIntens))
EndIf
EndIf
Next
Next
DrawingMode(#PB_2DDrawing_Transparent)
EndProcedure
Procedure DrawGhost()
Protected ghostCol.i
Protected r.i, c.i, x.i, y.i, i.i
Protected gr.i, gc.i, sx.i, sy.i, pr.i, pc.i
GetDropCell(DragX, DragY, @gr, @gc)
If CanPlace(DragPiece, gr, gc)
ghostCol = Pieces(DragPiece)\color
Else
ghostCol = #COL_GHOST_NO
EndIf
DrawingMode(#PB_2DDrawing_AlphaBlend)
For pr = 0 To Pieces(DragPiece)\rows - 1
For pc = 0 To Pieces(DragPiece)\cols - 1
If Pieces(DragPiece)\cells[pr * 5 + pc]
r = gr + pr : c = gc + pc
If r >= 0 And r <= 9 And c >= 0 And c <= 9
x = GRID_X + c * CELL_SIZE + DesktopScaledX(2)
y = GRID_Y + r * CELL_SIZE + DesktopScaledY(2)
Box(x, y, CELL_SIZE - DesktopScaledX(4), CELL_SIZE - DesktopScaledY(4), RGBA(Red(ghostCol), Green(ghostCol), Blue(ghostCol), 70))
EndIf
EndIf
Next
Next
EndProcedure
Procedure DrawGame()
Protected r.i, c.i, x.i, y.i, i.i
Protected gr.i, gc.i, sx.i, sy.i, pr.i, pc.i
Protected slotBorderCol.i = $223366 ; couleur du cadre du slot
Protected slotBgCol.i = $0D1A33 ; fond du carré légèrement plus sombre que la grille
If Not StartDrawing(CanvasOutput(#Canvas)) : ProcedureReturn : EndIf
If IsImage(GameBoard)
DrawImage(ImageID(GameBoard), 0, 0)
Else
; Fond
Box(0, 0, OutputWidth(), OutputHeight(), #COL_BG)
If IsImage(Logo)
DrawingMode(#PB_2DDrawing_AlphaBlend)
DrawImage(ImageID(logo), GRID_X, DesktopScaledX(3), DesktopScaledX(200), DesktopScaledY(83))
Else
DrawingFont(FontID(#Font_Big))
DrawingMode(#PB_2DDrawing_Transparent)
DrawText(GRID_X, DesktopScaledY(12), "Pure1010!", #COL_TEXT)
EndIf
DrawingFont(FontID(#Font_Small))
FrontColor($555555)
CenterText(0, OutputHeight() - DesktopScaledY(20), OutputWidth(), DesktopScaledY(20), "Version " + Version + " © 2026 Mindphazer")
RoundBox(DesktopScaledX(260), DesktopScaledY(8), DesktopScaledX(180), DesktopScaledY(60), 4, 4, #COL_SCORE_BG)
RoundBox(DesktopScaledX(450), DesktopScaledY(8), DesktopScaledX(180), DesktopScaledY(60), 4, 4, #COL_SCORE_BG)
RoundBox(OutputWidth() - DesktopScaledX(140), DesktopScaledY(490), DesktopScaledX(120), DesktopScaledY(62), 4, 4, #COL_SCORE_BG)
DrawingMode(#PB_2DDrawing_Transparent)
DrawingFont(FontID(#Font_Med))
DrawText(DesktopScaledX(270), DesktopScaledY(12), Textes(#TXT_SCORE), $AACCFF)
DrawText(DesktopScaledX(455), DesktopScaledY(12), Textes(#TXT_HIGHSCORE), $FFCC88)
DrawText(OutputWidth() - DesktopScaledX(130), DesktopScaledY(496), Textes(#TXT_TIME), $90EE90)
DrawText(GRID_X + DesktopScaledX(20), SLOT_Y - DesktopScaledY(20), Textes(#TXT_PIECES), $888888)
DrawingMode(#PB_2DDrawing_Default)
; Bordure + fond grille
Box(GRID_X - DesktopScaledX(2), GRID_Y - DesktopScaledY(2), #GRID_COLS * CELL_SIZE + DesktopScaledX(4), #GRID_ROWS * CELL_SIZE + DesktopScaledY(4), #COL_GRID_LN)
Box(GRID_X, GRID_Y, #GRID_COLS * CELL_SIZE, #GRID_ROWS * CELL_SIZE, #COL_GRID_BG)
; Lignes de grille
For r = 0 To #GRID_ROWS
Line(GRID_X, GRID_Y + r * CELL_SIZE, #GRID_COLS * CELL_SIZE, DesktopScaledY(1), #COL_GRID_LN)
Next
For c = 0 To #GRID_COLS
Line(GRID_X + c * CELL_SIZE, GRID_Y, DesktopScaledX(1), #GRID_ROWS * CELL_SIZE, #COL_GRID_LN)
Next
; Dessin des carrés de slot
For i = 0 To 2
sx = GRID_X + i * SLOT_W + (SLOT_W - SLOT_SQUARE) / 2
sy = SLOT_Y + (SLOT_ZONE_H - SLOT_SQUARE) / 2
Box(sx, sy, SLOT_SQUARE, SLOT_SQUARE, slotBorderCol)
Box(sx + DesktopScaledX(1), sy + DesktopScaledY(1), SLOT_SQUARE - DesktopScaledX(2), SLOT_SQUARE - DesktopScaledY(2), slotBgCol)
Next i
GrabDrawingImage(GameBoard, 0, 0, OutputWidth(), OutputHeight())
EndIf
DrawingMode(#PB_2DDrawing_Transparent)
DrawingFont(FontID(#Font_Big))
DrawText(DesktopScaledX(270), DesktopScaledY(29), FormatNumber(Score, 0, "", " "), #COL_TEXT)
If GameMode >= 0
DrawText(DesktopScaledX(455), DesktopScaledY(29), FormatNumber(HiScore(GameMode), 0, "", " "), #COL_TEXT)
EndIf
DrawText(OutputWidth() - DesktopScaledX(130), DesktopScaledY(513), RSet(Str(PlayingTime / 60), 2, "0") + " : " + RSet(Str(PlayingTime % 60), 2, "0"), #COL_TEXT)
DrawingMode(#PB_2DDrawing_Transparent | #PB_2DDrawing_AlphaBlend)
DrawingFont(FontID(#Font_Over))
Protected GameModeTXT.s = ""
Select GameMode
Case #MODE_CLASSIC
GameModeTXT = Textes(#TXT_CLASSIC)
Case #MODE_TIMED
GameModeTXT = Textes(#TXT_TIMED)
Case #MODE_CLEARANCE
GameModeTXT = Textes(#TXT_CLEARANCE)
Case #MODE_SURVIVAL
GameModeTXT = Textes(#TXT_SURVIVAL)
EndSelect
DrawText(GRID_X + ((CELL_SIZE * #GRID_COLS) / 2) - (TextWidth(GameModeTXT) / 2), GRID_Y + ((CELL_SIZE * #GRID_ROWS) / 2) - (TextHeight(GameModeTXT) / 2), GameModeTXT, RGBA(44, 125, 161, 80))
; Cellules remplies
For r = 0 To 9
For c = 0 To 9
If Grid(c, r)\filled
DrawImage(ImageID(PieceImages(Grid(c, r)\colorindex)), GRID_X + c * CELL_SIZE + DesktopScaledX(1), GRID_Y + r * CELL_SIZE + DesktopScaledY(1), CELL_SIZE - DesktopScaledX(2), CELL_SIZE - DesktopScaledY(2))
EndIf
Next
Next
;--- Animation effacement ---
If AnimActive
FlashAnimation()
EndIf
; Ghost (aperçu de dépôt)
If DragActive And DragPiece >= 0
DrawGhost()
EndIf
For i = 0 To 2
; Coin supérieur gauche du carré de slot, centré verticalement dans la zone
sx = GRID_X + i * SLOT_W + (SLOT_W - SLOT_SQUARE) / 2
sy = SLOT_Y + (SLOT_ZONE_H - SLOT_SQUARE) / 2
If Not Pieces(i)\valid : Continue : EndIf
If DragActive And DragPiece = i : Continue : EndIf
; Taille en pixels de la pièce dans les slots
Protected piecePxW.i = Pieces(i)\cols * PIECE_CELL
Protected piecePxH.i = Pieces(i)\rows * PIECE_CELL
; Coin de départ pour centrer la pièce dans le carré
Protected drawX.i = sx + (SLOT_SQUARE - piecePxW) / 2
Protected drawY.i = sy + (SLOT_SQUARE - piecePxH) / 2
For pr = 0 To Pieces(i)\rows - 1
For pc = 0 To Pieces(i)\cols - 1
If Pieces(i)\cells[pr * 5 + pc]
DrawImage(ImageID(PieceImages(Pieces(i)\colorindex)), drawX + pc * PIECE_CELL + DesktopScaledX(1), drawY + pr * PIECE_CELL + DesktopScaledY(1), PIECE_CELL - DesktopScaledX(2), PIECE_CELL - DesktopScaledY(2))
EndIf
Next pc
Next pr
Next i
; Pièce en cours de drag (suit le curseur à taille grille)
If DragActive And DragPiece >= 0
If DragY < GRID_Y + CELL_SIZE * #GRID_ROWS
ClipOutput(GRID_X, GRID_Y, CELL_SIZE * #GRID_COLS, WINDOW_H - GRID_Y)
EndIf
For pr = 0 To Pieces(DragPiece)\rows - 1
For pc = 0 To Pieces(DragPiece)\cols - 1
If Pieces(DragPiece)\cells[pr * 5 + pc]
x = DragX - DragOffX + pc * CELL_SIZE
y = DragY - DragOffY + pr * CELL_SIZE
DrawImage(ImageID(PieceImages(Pieces(DragPiece)\colorindex)), x, y, CELL_SIZE - DesktopScaledX(2), CELL_SIZE - DesktopScaledY(2))
EndIf
Next pc
Next pr
UnclipOutput()
EndIf
; Overlay Game Over
If GameOver
DrawingMode(#PB_2DDrawing_AlphaBlend)
Box(0, 0, OutputWidth(), OutputHeight(), RGBA(0, 0, 0, 185))
DrawingMode(#PB_2DDrawing_Transparent)
DrawingFont(FontID(#Font_Over))
FrontColor(#COL_GAMEOVER)
CenterText(0, 0, WINDOW_W, WINDOW_H, Textes(#TXT_GAMEOVER))
DrawingFont(FontID(#Font_Med))
FrontColor(#COL_TEXT)
CenterText(0, WINDOW_H / 2 + DesktopScaledY(50), WINDOW_W, 20, Textes(#TXT_FINAL) + Str(Score))
CenterText(0, WINDOW_H / 2 + DesktopScaledY(80), WINDOW_W, 20, Textes(#TXT_BEST) + Str(HiScore(GameMode)))
CenterText(0, WINDOW_H / 2 + DesktopScaledY(110), WINDOW_W, 20, Textes(#TXT_REPLAY))
EndIf
StopDrawing()
EndProcedure
Procedure SaveUndoState()
CopyArray(Grid(), Undo())
CopyArray(Pieces(), UndoPieces())
UndoScore = Score
UndoPlayingTime = PlayingTime
UndoGameOver = GameOver
UndoGameInProgress = GameInProgress
UndoAvailable = #True
DisableGadget(#ButtonUndo, #False)
DrawCanvasButton(#ButtonUndo, GetGadgetData(#ButtonUndo), 0)
EndProcedure
Procedure RestoreUndoState()
CopyArray(Undo(), Grid())
CopyArray(UndoPieces(), Pieces())
Score = UndoScore
PlayingTime = UndoPlayingTime
GameOver = UndoGameOver
GameInProgress = UndoGameInProgress
DragActive = #False
DragPiece = -1
AnimActive = #False
AnimStep = 0
AnimBonus = 0
UndoAvailable = #False
DisableGadget(#ButtonUndo, #True)
DrawCanvasButton(#ButtonUndo, GetGadgetData(#ButtonUndo), 3)
DrawGame()
EndProcedure
Procedure QuitGame()
Protected Quit = #PB_MessageRequester_Yes
If Not GameOver And GameInProgress
Quit = MessageRequester("Attention", Textes(#TXT_GAMEINPROGRESS), #PB_MessageRequester_Warning | #PB_MessageRequester_YesNo)
EndIf
If Quit = #PB_MessageRequester_Yes
QuitGame = #True
EndIf
EndProcedure
Procedure InitPieces()
Protected i, Img
For i = 0 To 10
Img = CreateImage(#PB_Any, CELL_SIZE, CELL_SIZE, 32) ; ,#PB_Image_TransparentBlack)
If StartDrawing(ImageOutput(Img))
DrawCell(0, 0, CELL_SIZE, PieceColors(i))
StopDrawing()
EndIf
PieceImages(i) = Img
Next i
EndProcedure
; ============================================================
; Gestionnaires d'événements souris
; ============================================================
Procedure HandleMouseDown(mx.i, my.i)
Protected i.i, hitPR.i, hitPC.i, sx.i, sy.i
Protected piecePxW.i, piecePxH.i, drawX.i, drawY.i
If GameOver Or AnimActive : ProcedureReturn : EndIf
For i = 0 To 2
If Not Pieces(i)\valid : Continue : EndIf
; Coin du carré de slot (identique au rendu)
sx = GRID_X + i * SLOT_W + (SLOT_W - SLOT_SQUARE) / 2
sy = SLOT_Y + (SLOT_ZONE_H - SLOT_SQUARE) / 2
; Coin de la pièce centrée dans le carré
piecePxW = Pieces(i)\cols * PIECE_CELL
piecePxH = Pieces(i)\rows * PIECE_CELL
drawX = sx + (SLOT_SQUARE - piecePxW) / 2
drawY = sy + (SLOT_SQUARE - piecePxH) / 2
If mx >= drawX And mx < drawX + piecePxW And my >= drawY And my < drawY + piecePxH
hitPC = (mx - drawX) / PIECE_CELL
hitPR = (my - drawY) / PIECE_CELL
If hitPR >= 0 And hitPR < Pieces(i)\rows And hitPC >= 0 And hitPC < Pieces(i)\cols And Pieces(i)\cells[hitPR * 5 + hitPC]
DragActive = #True
DragPiece = i
DragOffX = hitPC * CELL_SIZE + CELL_SIZE / 2
DragOffY = hitPR * CELL_SIZE + CELL_SIZE / 2
; On calcule l'offset en pixels de la pièce en slot, puis on le convertit en taille grille
DragX = mx
DragY = my
EndIf
Break
EndIf
Next
EndProcedure
Procedure HandleMouseMove(mx.i, my.i)
If DragActive
If (mx < GRID_X + CELL_SIZE * #GRID_COLS And mx > GRID_X) Or (my > GRID_Y + CELL_SIZE * #GRID_ROWS)
DragX = mx
EndIf
If my > GRID_Y
DragY = my
EndIf
EndIf
EndProcedure
Procedure HandleMouseUp(mx.i, my.i)
Protected gr.i, gc.i, cellsPlaced.i, pr.i, pc.i, i.i, allUsed.b, bonus.i
If Not DragActive : ProcedureReturn : EndIf
GetDropCell(mx, my, @gr, @gc)
If CanPlace(DragPiece, gr, gc)
cellsPlaced = 0
For pr = 0 To Pieces(DragPiece)\rows - 1
For pc = 0 To Pieces(DragPiece)\cols - 1
If Pieces(DragPiece)\cells[pr * 5 + pc] : cellsPlaced + 1 : EndIf
Next
Next
SaveUndoState()
PlacePiece(DragPiece, gr, gc)
Score + cellsPlaced
bonus = ClearLines()
AnimBonus = bonus
If Not AnimActive
Score + AnimBonus
If Score > HiScore(GameMode) : HiScore(GameMode) = Score : SaveHiScore() : EndIf
allUsed = #True
For i = 0 To 2
If Pieces(i)\valid : allUsed = #False : Break : EndIf
Next
If allUsed : GenerateAllPieces() : EndIf
If CheckGameOver() : GameOver = #True : EndIf
EndIf
EndIf
DragActive = #False
DragPiece = -1
EndProcedure
Procedure HandleCanvasEvents()
Protected EventType = EventType()
Select EventType
Case #PB_EventType_MouseMove
HandleMouseMove(GetGadgetAttribute(#Canvas, #PB_Canvas_MouseX), GetGadgetAttribute(#Canvas, #PB_Canvas_MouseY))
If DragActive : DrawGame() : EndIf
Case #PB_EventType_LeftButtonDown
HandleMouseDown(GetGadgetAttribute(#Canvas, #PB_Canvas_MouseX), GetGadgetAttribute(#Canvas, #PB_Canvas_MouseY))
SetGadgetAttribute(#Canvas, #PB_Canvas_Cursor, #PB_Cursor_Hand)
DrawGame()
Case #PB_EventType_LeftButtonUp
HandleMouseUp(GetGadgetAttribute(#Canvas, #PB_Canvas_MouseX), GetGadgetAttribute(#Canvas, #PB_Canvas_MouseY))
SetGadgetAttribute(#Canvas, #PB_Canvas_Cursor, #PB_Cursor_Default)
DrawGame()
Case #PB_EventType_KeyDown
If GetGadgetAttribute(#Canvas, #PB_Canvas_Key) = #PB_Shortcut_R
NewGame()
DrawGame()
ElseIf GetGadgetAttribute(#Canvas, #PB_Canvas_Key) = #PB_Shortcut_Q
GameOver = #True
EndIf
EndSelect
EndProcedure
Procedure HandleTimerEvents()
Select EventTimer()
Case #TimerAnimation
If AnimActive
Protected aAllUsed.b = #True
Protected aIdx.i
AnimStep + 1
If AnimStep >= #ANIM_STEPS
ApplyClear()
Score + AnimBonus
If Score > HiScore(GameMode) : HiScore(GameMode) = Score : SaveHiScore() : EndIf
For aIdx = 0 To 2
If Pieces(aIdx)\valid : aAllUsed = #False : Break : EndIf
Next
If aAllUsed : GenerateAllPieces() : EndIf
If CheckGameOver() : GameOver = #True : EndIf
EndIf
DrawGame()
EndIf
Case #TimerTime
Select GameMode
Case #MODE_TIMED
If GameInProgress And Not GameOver
PlayingTime - 1 ; décompte
If PlayingTime <= 0
PlayingTime = 0
GameOver = #True ; temps écoulé
EndIf
DrawGame()
EndIf
Case #MODE_SURVIVAL
If GameInProgress And Not GameOver
PlayingTime + 1
If PlayingTime % #SURVIVAL_PERIOD = 0
PushRowUp() ; fait monter une rangée
If CheckGameOver() : GameOver = #True : EndIf
EndIf
DrawGame()
EndIf
Default ; classique
If GameInProgress And Not GameOver
PlayingTime + 1 : DrawGame()
EndIf
EndSelect
EndSelect
EndProcedure
Procedure HandleButtonEvents()
Protected EvtGadget = EventGadget()
Protected Quit = #PB_MessageRequester_Yes
If EventType() = #PB_EventType_LeftClick
Select EvtGadget
Case #ButtonNewClassic, #ButtonNewTimed, #ButtonNewClearance, #ButtonNewSurvival
If Not GameOver And GameInProgress
Quit = MessageRequester("Attention", Textes(#TXT_GAMEINPROGRESS), #PB_MessageRequester_Warning | #PB_MessageRequester_YesNo)
EndIf
If Quit = #PB_MessageRequester_Yes
Select EvtGadget
Case #ButtonNewClassic : GameMode = #MODE_CLASSIC
Case #ButtonNewTimed : GameMode = #MODE_TIMED
Case #ButtonNewClearance : GameMode = #MODE_CLEARANCE
Case #ButtonNewSurvival : GameMode = #MODE_SURVIVAL
EndSelect
NewGame()
DrawGame()
GameInProgress = #True
EndIf
Case #ButtonUndo
RestoreUndoState()
Case #ButtonStop
Protected r.i, c.i
If Not GameOver And GameInProgress
Quit = MessageRequester("Attention", Textes(#TXT_GAMEINPROGRESS), #PB_MessageRequester_Warning | #PB_MessageRequester_YesNo)
EndIf
If Quit = #PB_MessageRequester_Yes
GameMode = -1
ResetGame()
GameInProgress = #False
DrawGame()
EndIf
Case #ButtonQuit
QuitGame()
EndSelect
EndIf
EndProcedure
; ============================================================
; Point d'entrée principal
; ============================================================
InitLang()
InitDPI() ; <-- calcule toutes les dimensions DPI en premier
InitColors()
InitPieces()
InitShapeDefs()
LoadHiScore()
LoadFont(#Font_Big, "Nunito", 22 * (DesktopResolutionY() / FontFactor), #PB_Font_Bold)
LoadFont(#Font_Med, "Nunito", 13 * (DesktopResolutionY() / FontFactor))
LoadFont(#Font_Over, "Nunito", 32 * (DesktopResolutionY() / FontFactor), #PB_Font_Bold)
LoadFont(#Font_Small, "Nunito", 10 * (DesktopResolutionY() / FontFactor))
OpenWindow(#MainWindow, 0, 0, #BASE_WINDOW_W, #BASE_WINDOW_H, "Pure1010!", #PB_Window_SystemMenu | #PB_Window_ScreenCentered | #PB_Window_MinimizeGadget)
CanvasGadget(#Canvas, 0, 0, #BASE_WINDOW_W, #BASE_WINDOW_H, #PB_Canvas_Keyboard | #PB_Canvas_Container)
CanvasButtonGadget(#ButtonNewClassic, #BASE_WINDOW_W - 140, 90, 120, 40, RemoveString(Textes(#TXT_CLASSIC), Textes(#TXT_MODE)), #True)
GadgetToolTip(#ButtonNewClassic, Textes(#TXT_STARTGAME) + " " + Textes(#TXT_CLASSIC))
CanvasButtonGadget(#ButtonNewTimed, #BASE_WINDOW_W - 140, 140, 120, 40, RemoveString(Textes(#TXT_TIMED), Textes(#TXT_MODE)), #True)
GadgetToolTip(#ButtonNewTimed, Textes(#TXT_STARTGAME) + " " + Textes(#TXT_TIMED))
CanvasButtonGadget(#ButtonNewClearance, #BASE_WINDOW_W - 140, 190, 120, 40, RemoveString(Textes(#TXT_CLEARANCE), Textes(#TXT_MODE)), #True)
GadgetToolTip(#ButtonNewClearance, Textes(#TXT_STARTGAME) + " " + Textes(#TXT_CLEARANCE))
CanvasButtonGadget(#ButtonNewSurvival, #BASE_WINDOW_W - 140, 240, 120, 40, RemoveString(Textes(#TXT_SURVIVAL), Textes(#TXT_MODE)), #True)
GadgetToolTip(#ButtonNewSurvival, Textes(#TXT_STARTGAME) + " " + Textes(#TXT_SURVIVAL))
CanvasButtonGadget(#ButtonUndo, #BASE_WINDOW_W - 140, 300, 120, 40, Textes(#TXT_UNDO))
GadgetToolTip(#ButtonUndo, "Annule le dernier coup joué")
DisableGadget(#ButtonUndo, #True)
DrawCanvasButton(#ButtonUndo, GetGadgetData(#ButtonUndo), 3)
CanvasButtonGadget(#ButtonStop, #BASE_WINDOW_W - 140, 390, 120, 40, Textes(#TXT_STOP_GAME))
CanvasButtonGadget(#ButtonQuit, #BASE_WINDOW_W - 140, 440, 120, 40, textes(#TXT_QUIT))
AddWindowTimer(#MainWindow, #TimerAnimation, #ANIM_DELAY)
AddWindowTimer(#MainWindow, #TimerTime, 1000)
BindGadgetEvent(#Canvas, @HandleCanvasEvents())
BindGadgetEvent(#ButtonNewClassic, @HandleButtonEvents())
BindGadgetEvent(#ButtonNewTimed, @HandleButtonEvents())
BindGadgetEvent(#ButtonNewSurvival, @HandleButtonEvents())
BindGadgetEvent(#ButtonNewClearance, @HandleButtonEvents())
BindGadgetEvent(#ButtonStop, @HandleButtonEvents())
BindGadgetEvent(#ButtonQuit, @HandleButtonEvents())
BindGadgetEvent(#ButtonUndo, @HandleButtonEvents())
BindEvent(#PB_Event_Timer, @HandleTimerEvents())
BindEvent(#PB_Event_CloseWindow, @QuitGame())
SetActiveGadget(#Canvas)
DrawGame()
Define Event
Repeat
Event = WaitWindowEvent()
If Event = #PB_Event_Menu
CompilerIf #PB_Compiler_OS = #PB_OS_MacOS
If EventMenu() = #PB_Menu_Quit
QuitGame()
EndIf
CompilerEndIf
EndIf
Until QuitGame
SaveHiScore()
;- =================== DataSection
DataSection
FRA:
Data.s "SCORE"
Data.s "MEILLEUR"
Data.s "TEMPS"
Data.s "PIÈCES DISPONIBLES"
Data.s "PARTIE TERMINÉE"
Data.s "Score final : "
Data.s "Meilleur : "
Data.s "Appuyez sur [R] pour rejouer"
Data.s "Mode Classique"
Data.s "Mode Chrono"
Data.s "Mode Déblayage"
Data.s "Mode Survie"
Data.s "Annuler coup"
Data.s "Quitter"
Data.s "Arrêter le jeu"
Data.s "Vous avez une partie en cours, " + #CRLF$ + "Voulez-vous vraiment quitter ?"
Data.s "Démarrer une partie"
Data.s "Mode"
Data.s "@@@"
US:
Data.s "SCORE"
Data.s "HI-SCORE"
Data.s "TIME"
Data.s "AVAILABLE BLOCKS"
Data.s "GAME OVER"
Data.s "Final score : "
Data.s "High Score : "
Data.s "Press [R] to play again"
Data.s "Classic Mode"
Data.s "Timed Mode"
Data.s "Clearance Mode"
Data.s "Survival Mode"
Data.s "Undo last turn"
Data.s "Quit game"
Data.s "Stop game"
Data.s "You have a game in progress, " + #CRLF$ + "do you really want to quit ?"
Data.s "Start a game"
Data.s "Mode"
Data.s "@@@"
RUS:
Data.s "СЧЕТ"
Data.s "РЕКОРДЫ"
Data.s "ВРЕМЯ"
Data.s "ДОСТУПНЫЕ БЛОКИ"
Data.s "ИГРА ОКОНЧЕНА"
Data.s "Финальный счет : "
Data.s "Рекорд : "
Data.s "Нажмите [R], чтобы продолжить игру"
Data.s "Режим Классический"
Data.s "Режим На время"
Data.s "Режим Очистка"
Data.s "Режим Выживание"
Data.s "Отменить ход"
Data.s "Выйти из игры"
Data.s "Остановить игру"
Data.s "У вас продолжается игра" + #CRLF$ + "вы действительно хотите выйти из игры?"
Data.s "Начать игру"
Data.s "Режим"
Data.s "@@@"
EndDataSection


