PureColorLines

Programmation avancée de jeux en PureBasic
Avatar de l’utilisateur
Mindphazer
Messages : 716
Inscription : mer. 24/août/2005 10:42

PureColorLines

Message par Mindphazer »

Bonjour à tous,
je (avec un peu d'aide de mon nouvel ami ChatGPT) me suis amusé à recréer un vieux petit jeu des années 90 : "color lines"
Le jeu se joue sur un plateau de 9x9
Le but du jeu est de créer des alignements d'au moins 5 boules de même couleur.
À chaque tour, 3 boules de couleurs apparaissent à des emplacements libres.
On déplace une boule en la sélectionnant, puis en indiquent la case où on veut qu'elle aille, si un chemin libre existe entre les deux cases.
Quand on aligne 5 boules ou plus de la même couleur en ligne droite (horizontale, verticale ou diagonale), elles disparaissent et on gagne des points.
Après chaque déplacement non gagnant, 3 nouvelles boules apparaissent à des positions aléatoires
Le jeu se termine quand la grille est complètement remplie et qu’il n’est plus possible de déplacer une boule.
Plus il y a de boules dans l'alignement, plus on gagne de points
On gagne aussi plus de points si on crée une intersection de deux lignes de la même couleur

Le jeu a été développé sur MacOS, mais fonctionne également sous Windows (et peut-être Linux)
Il -devrait- être pleinement DPI aware

Have fun !

Code : Tout sélectionner

; ┌──────────────────────────────────────────────────────────────────────────┐
; │  _____                 _____      _            _      _                  │         
; │ |  __ \               / ____|    | |          | |    (_)                 │
; │ | |__) |   _ _ __ ___| |     ___ | | ___  _ __| |     _ _ __   ___  ___  │
; │ |  ___/ | | | '__/ _ \ |    / _ \| |/ _ \| '__| |    | | '_ \ / _ \/ __| │
; │ | |   | |_| | | |  __/ |___| (_) | | (_) | |  | |____| | | | |  __/\__ \ │
; │ |_|    \__,_|_|  \___|\_____\___/|_|\___/|_|  |______|_|_| |_|\___||___/ │
; │                                                                          │                                                                                                                                                 │
; │                                                                          │
; │                         Code by Mindphazer                               │
; │                                                                          │
; │                          v 1.1.0 (c) 2026                                │
; │                                                                          │
; └──────────────────────────────────────────────────────────────────────────┘ 

; ================== CONSTANTES ==================
#GRID_SIZE = 9
#CELL_SIZE = 48
#MARGIN    = 10
#NB_COLORS = 7
#NEW_BALLS = 3

#MOVE_STEP_DURATION = 50
#REMOVE_DURATION = 200
#TIMER_ANIM = 1

#Version = "1.1.0"

Enumeration
  #MainWindow
  #MainCanvas
  #NewGame
  #QuitGame
  #KeyESC
  #MainMenu
EndEnumeration

Enumeration #PB_Event_FirstCustomValue
  #Event_TerminateRequested
EndEnumeration


Structure Cell
  color.i
EndStructure


Global Dim Grid.Cell(#GRID_SIZE - 1, #GRID_SIZE - 1)

Global Dim Colors(#NB_COLORS)

Colors(1) = RGB(255, 85, 85)
Colors(2) = RGB(80, 250, 120)
Colors(3) = RGB(90, 170, 255)
Colors(4) = RGB(255, 210, 90)
Colors(5) = RGB(190, 130, 255)
Colors(6) = RGB(80, 220, 210)
Colors(7) = RGB(255, 150, 60)

#BALL_RADIUS     = #CELL_SIZE / 2 - 6
#BALL_IMG_SIZE   = (#BALL_RADIUS + 10) * 2
#COULEUR_GRILLE  = $A0A0A0
#COULEUR_PLATEAU = $787878
#COULEUR_JEU     = $505050
#SCORE_POPUP_DURATION = 800

Global Dim BallImage(#NB_COLORS)
Global Dim NextBalls(#NEW_BALLS - 1)

Global SelectedX = -1, SelectedY = -1, HoverX = -1, HoverY = -1
Global Score = 0
Global HighScore
Global NewHiscore = #False
Global HighScoreFile$ = GetHomeDirectory() + "ColorLinesHigh.fic"
CompilerIf #PB_Compiler_OS = #PB_OS_MacOS
  Global ScoreFont      = LoadFont(#PB_Any, "Arial", 28, #PB_Font_Bold)
  Global EndFont        = LoadFont(#PB_Any, "Arial", 42, #PB_Font_Bold)
  Global PopupFont      = LoadFont(#PB_Any, "Arial", 20, #PB_Font_Bold)
  Global ButtonFont     = LoadFont(#PB_Any, "Gill Sans MT", 20)
CompilerElse
  Global ScoreFont      = LoadFont(#PB_Any, "Arial", 20 , #PB_Font_Bold)
  Global EndFont        = LoadFont(#PB_Any, "Arial", 28, #PB_Font_Bold)
  Global PopupFont      = LoadFont(#PB_Any, "Arial", 12, #PB_Font_Bold)
  Global ButtonFont     = LoadFont(#PB_Any, "Gill Sans MT", 14)
CompilerEndIf
Global PulsePhase.f = 0.0
Global PulseSpeed.f = 0.12 
Global RemoveCount = 0
Global ScorePopupActive = 0
Global ScorePopupValue = 0
Global ScorePopupX.f, ScorePopupY.f
Global ScorePopupStartTime

Global GameOver = 0
Global ReplayX, ReplayY, ReplayW, ReplayH
Global RemovedLineCount
Global RemovedBallCount
Global PerfectLine

; ================== BFS ==================
Global Dim Visited(#GRID_SIZE - 1,#GRID_SIZE - 1)
Global Dim ParentX(#GRID_SIZE - 1,#GRID_SIZE - 1)
Global Dim ParentY(#GRID_SIZE - 1,#GRID_SIZE - 1)
Global Dim QueueX(#GRID_SIZE * #GRID_SIZE)
Global Dim QueueY(#GRID_SIZE * #GRID_SIZE)
Global Dim PathX(1)
Global Dim PathY(1)

Global AnimatingMove = 0
Global AnimColor
Global AnimX.f, AnimY.f
Global AnimX1.f, AnimY1.f, AnimX2.f, AnimY2.f
Global MoveStep, MoveStepStart

Global Removing = 0
Global RemoveStart
Global Dim RemoveMask(#GRID_SIZE - 1, #GRID_SIZE - 1)

Structure Bouton
  X.i
  Y.i
  Length.i
  Height.i
  Libelle.s
  BackColor.i
EndStructure
Global NewMap GadgetB.Bouton()

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)
    PostEvent(#Event_TerminateRequested)
    If AppShouldTerminate_
      ProcedureReturn AppShouldTerminate_(Object, Selector, Sender)
    EndIf

  EndProcedure  
CompilerEndIf

Procedure Min(a, b)
  If a < b
    ProcedureReturn a
  Else
    ProcedureReturn b
  EndIf
EndProcedure

Procedure Max(a, b)
  If a > b
    ProcedureReturn a
  Else
    ProcedureReturn b
  EndIf
EndProcedure


Procedure RGBtoHSL(color, *h.Float, *s.Float, *l.Float)
  ; by ChatGPT
  Protected r.f = Red(color)   / 255.0
  Protected g.f = Green(color) / 255.0
  Protected b.f = Blue(color)  / 255.0

  Protected max.f = r
  If g > max : max = g : EndIf
  If b > max : max = b : EndIf

  Protected min.f = r
  If g < min : min = g : EndIf
  If b < min : min = b : EndIf

  Protected d.f = max - min
  *l\f = (max + min) / 2.0

  If d = 0
    *h\f = 0
    *s\f = 0
  Else
    If *l\f < 0.5
      *s\f = d / (max + min)
    Else
      *s\f = d / (2.0 - max - min)
    EndIf

    If max = r
      *h\f = (g - b) / d + (Bool(g < b) * 6)
    ElseIf max = g
      *h\f = (b - r) / d + 2
    Else
      *h\f = (r - g) / d + 4
    EndIf

    *h\f / 6.0
  EndIf
EndProcedure


Procedure.f HueToRGB(p.f, q.f, t.f)
  ; by ChatGPT
  If t < 0 : t + 1 : EndIf
  If t > 1 : t - 1 : EndIf

  If t < 1.0/6.0 : ProcedureReturn p + (q - p) * 6 * t : EndIf
  If t < 1.0/2.0 : ProcedureReturn q : EndIf
  If t < 2.0/3.0 : ProcedureReturn p + (q - p) * (2.0/3.0 - t) * 6 : EndIf
  ProcedureReturn p
EndProcedure

Procedure HSLtoRGB(h.f, s.f, l.f)
  Protected r.f, g.f, b.f
  If s = 0
    r = l : g = l : b = l
  Else
    Protected q.f
    If l < 0.5
      q = l * (1 + s)
    Else
      q = l + s - l * s
    EndIf
    Protected p.f = 2 * l - q
    r = HueToRGB(p, q, h + 1.0/3.0)
    g = HueToRGB(p, q, h)
    b = HueToRGB(p, q, h - 1.0/3.0)
  EndIf
  ProcedureReturn RGB(r * 255, g * 255, b * 255)
EndProcedure


Procedure AdjustBrightness(color, amount)
  ; by ChatGPT
  ; amount : -100 --> +100
  Protected h.f, s.f, l.f
  RGBtoHSL(color, @h, @s, @l)
  l + amount / 100.0
  If l < 0 : l = 0 : EndIf
  If l > 1 : l = 1 : EndIf
  ProcedureReturn HSLtoRGB(h, s, l)
EndProcedure


Procedure RedrawButton(Gadget, Mode = 0)
  Protected Key.s, Contour, Interieur
  Key = Str(Gadget)
  If StartDrawing(CanvasOutput(Gadget))
    If Mode = 0
      Interieur = AdjustBrightness(GadgetB(Key)\BackColor, 15)
      Contour = GadgetB(Key)\BackColor
    Else
      Interieur = GadgetB(Key)\BackColor ;$C9FFC4
      Contour = AdjustBrightness(GadgetB(Key)\BackColor, -15)
    EndIf 
    DrawingMode(#PB_2DDrawing_Gradient)
    BackColor(Contour)
    FrontColor(AdjustBrightness(Contour, 25))
    LinearGradient(0, 0, OutputWidth(), OutputHeight())
    Box(0, 0, OutputWidth(), OutputHeight(), Contour)
    
    BackColor(Interieur)
    FrontColor(AdjustBrightness(Interieur, 25))
    LinearGradient(DesktopScaledX(1), DesktopScaledY(1), OutputWidth() - DesktopScaledX(2), OutputHeight() - DesktopScaledY(2))
    Box(DesktopScaledX(1), DesktopScaledY(1), OutputWidth() - DesktopScaledX(2), OutputHeight() - DesktopScaledY(2))
    DrawingMode(#PB_2DDrawing_Transparent)
    DrawingFont(FontID(ButtonFont))
    DrawText(DesktopScaledX(GadgetB(Key)\Length * 0.5) - (TextWidth(GadgetB(Key)\Libelle) * 0.5) + Mode, DesktopScaledY(GadgetB(Key)\Height * 0.5) - (TextHeight(GadgetB(Key)\Libelle) * 0.5) + Mode, GadgetB(Key)\Libelle, $222222)
    StopDrawing()
  EndIf
EndProcedure

Procedure Button(Gadget, Xb, Yb, L, H, Texte.s, Couleur = $D7ED9F)
  Protected Key.s
  If CanvasGadget(Gadget, Xb, Yb, L, H)
    Key = Str(Gadget)
    AddMapElement(GadgetB(), Str(Gadget))
    GadgetB(Key)\X         = Xb
    GadgetB(Key)\Y         = Yb
    GadgetB(Key)\Length    = L 
    GadgetB(Key)\Height    = H
    GadgetB(Key)\Libelle   = Texte
    GadgetB(key)\BackColor = Couleur
    RedrawButton(Gadget, 1)
  EndIf
EndProcedure


Procedure HasFreeCells()
  Protected x,y
  For y = 0 To #GRID_SIZE - 1
    For x = 0 To #GRID_SIZE - 1
      If Grid(x,y)\color = 0
        ProcedureReturn #True
      EndIf
    Next
  Next
  ProcedureReturn #False
EndProcedure


Procedure CreateBallImage(Size, color)
  ; By ChatGPT
  Protected img = CreateImage(#PB_Any, DesktopScaledX(size), DesktopScaledY(size), 32, #PB_Image_Transparent)
  Protected cx.f = size / 2
  Protected cy.f = size / 2
  Protected r.f  = size / 2 - 4
  Protected cr = Red(color)
  Protected cg = Green(color)
  Protected cb = Blue(color)

  If StartVectorDrawing(ImageVectorOutput(img))
    ; =========================
    ; BILLE DE BASE (PLEINE)
    ; =========================
    VectorSourceColor(RGBA(cr, cg, cb, 210))
    AddPathCircle(DesktopScaledX(cx), DesktopScaledY(cy), DesktopScaledX(r))
    FillPath()

    ; =========================
    ; REFLET PRINCIPAL
    ; =========================
    VectorSourceCircularGradient(DesktopScaledX(cx - 5), DesktopScaledY(cy - 5), DesktopScaledX(r * 0.9))
    VectorSourceGradientColor(RGBA(255, 255, 255, 120), 0.0)
    VectorSourceGradientColor(RGBA(255, 255, 255, 0), 1.0)

    AddPathCircle(DesktopScaledX(cx), DesktopScaledY(cy), DesktopScaledX(r))
    FillPath()

    ; =========================
    ; POINT DE BRILLANCE
    ; =========================
    VectorSourceColor(RGBA(255, 255, 255, 110))
    AddPathCircle(DesktopScaledX(cx - 6), DesktopScaledY(cy - 6), DesktopScaledX(r * 0.22))
    FillPath()

    ; =========================
    ; CONTOUR FIN
    ; =========================
    VectorSourceColor(RGBA(255, 255, 255, 80))
    AddPathCircle(DesktopScaledX(cx), DesktopScaledY(cy), DesktopScaledX(r))
    StrokePath(1.1)

    StopVectorDrawing()
  EndIf
  ProcedureReturn img
EndProcedure

Procedure GenerateNextBalls()
  Protected i
  For i = 0 To #NEW_BALLS - 1
    NextBalls(i) = Random(#NB_COLORS - 1) + 1
  Next
EndProcedure

Procedure AddRandomBalls()
  Protected i, x, y
  For i = 0 To #NEW_BALLS - 1
    If HasFreeCells() = 0
      GameOver = 1
      ProcedureReturn
    EndIf
    
    Repeat
      x = Random(#GRID_SIZE - 1)
      y = Random(#GRID_SIZE - 1)
    Until Grid(x,y)\color = 0
    Grid(x,y)\color = NextBalls(i)
  Next
  GenerateNextBalls()
EndProcedure

Procedure ResetGame()
  Protected x, y
  For y = 0 To #GRID_SIZE - 1
    For x = 0 To #GRID_SIZE - 1
      Grid(x,y)\color = 0
    Next
  Next
  Score = 0
  GameOver = 0
  SelectedX = -1 : SelectedY = -1
  AnimatingMove = 0
  Removing = 0
  GenerateNextBalls()
  AddRandomBalls()
EndProcedure


; ================== BFS AVEC CHEMIN ==================
Procedure PathFind(sx, sy, tx, ty)
  Protected head, tail, x, y, nx, ny, i, saved, px, py, steps

  saved = Grid(sx, sy)\color
  Grid(sx, sy)\color = 0

  FillMemory(@Visited(0, 0), #GRID_SIZE * #GRID_SIZE * SizeOf(Integer), 0)
  FillMemory(@ParentX(0, 0), #GRID_SIZE * #GRID_SIZE * SizeOf(Integer), -1)
  FillMemory(@ParentY(0, 0), #GRID_SIZE * #GRID_SIZE * SizeOf(Integer), -1)

  head=0 : tail=0
  QueueX(0) = sx : QueueY(0) = sy
  Visited(sx, sy) = 1 

  While head <= tail
    x = QueueX(head) 
    y = QueueY(head) 
    head + 1
    If x = tx And y = ty
      steps = 0
      px = tx : py = ty
      ReDim PathX(0) : ReDim PathY(0)
      While px <> -1
        ReDim PathX(steps) : ReDim PathY(steps)
        PathX(steps) = px : PathY(steps) = py
        steps + 1
        nx = ParentX(px, py) : ny = ParentY(px, py)
        px = nx : py = ny
      Wend
      For i = 0 To steps/2 - 1
        Swap PathX(i), PathX(steps - 1 - i)
        Swap PathY(i), PathY(steps - 1 - i)
      Next
      Grid(sx,sy)\color = saved
      ProcedureReturn #True
    EndIf

    For i = 0 To 3
      Select i
        Case 0 : nx = x + 1 : ny = y
        Case 1 : nx = x - 1 : ny = y
        Case 2 : nx = x : ny = y + 1
        Case 3 : nx = x : ny = y - 1
      EndSelect
      If nx >= 0 And nx < #GRID_SIZE And ny >= 0 And ny < #GRID_SIZE
        If Visited(nx, ny) = 0 And Grid(nx, ny)\color = 0
          Visited(nx, ny) = 1
          ParentX(nx, ny) = x : ParentY(nx, ny) = y
          tail + 1
          QueueX(tail) = nx : QueueY(tail) = ny
        EndIf
      EndIf
    Next
  Wend
  Grid(sx, sy)\color = saved
  ProcedureReturn #False
EndProcedure

; ================== LIGNES ==================

Procedure DetectLines()
  Protected x,y,c,i,count,found
  Protected dx,dy
  Protected sx, sy, n

  RemovedLineCount = 0
  RemovedBallCount = 0
  found = 0

  FillMemory(@RemoveMask(0,0), #GRID_SIZE * #GRID_SIZE * SizeOf(Integer), 0)

  Dim dirX(3)
  Dim dirY(3)
  dirX(0)=1  : dirY(0)=0   ; horizontal
  dirX(1)=0  : dirY(1)=1   ; vertical
  dirX(2)=1  : dirY(2)=1   ; diag \
  dirX(3)=1  : dirY(3)=-1  ; diag /

  For y = 0 To #GRID_SIZE - 1
    For x = 0 To #GRID_SIZE - 1

      c = Grid(x,y)\color
      If c = 0 : Continue : EndIf

      For i = 0 To 3
        dx = dirX(i)
        dy = dirY(i)

        ; évite de détecter la même ligne deux fois
        If x-dx >= 0 And x-dx < #GRID_SIZE And y-dy >= 0 And y-dy < #GRID_SIZE
          If Grid(x-dx, y-dy)\color = c
            Continue
          EndIf
        EndIf

        count = 1

        While x + dx*count >= 0 And x + dx*count < #GRID_SIZE And
              y + dy*count >= 0 And y + dy*count < #GRID_SIZE And
              Grid(x + dx*count, y + dy*count)\color = c
          count + 1
        Wend

        If count >= 5
          found = 1
          RemovedLineCount + 1
          ; marquage des billes
          For n = 0 To count-1
            RemoveMask(x + dx*n, y + dy*n) = 1
          Next
        EndIf

      Next
    Next
  Next

  ; calcul du nombre de billes supprimées + centre popup
  If found
    Removing = 1
    RemoveStart = ElapsedMilliseconds()

    sx = 0 : sy = 0 : n = 0

    For y = 0 To #GRID_SIZE - 1
      For x = 0 To #GRID_SIZE - 1
        If RemoveMask(x,y)
          RemovedBallCount + 1
          sx + x
          sy + y
          n + 1
        EndIf
      Next
    Next

    If n > 0
      ScorePopupX = #MARGIN + (sx / n) * #CELL_SIZE + #CELL_SIZE / 2
      ScorePopupY = #MARGIN + (sy / n) * #CELL_SIZE + #CELL_SIZE / 2
    EndIf
  EndIf
  ProcedureReturn found
EndProcedure


Procedure StartMove(tx, ty)
  If Not PathFind(SelectedX, SelectedY, tx, ty)
    SelectedX = -1 : SelectedY = -1 : ProcedureReturn
  EndIf
  AnimColor = Grid(SelectedX, SelectedY)\color
  Grid(SelectedX, SelectedY)\color = 0
  MoveStep = 1
  MoveStepStart = ElapsedMilliseconds()
  AnimatingMove = 1
  AnimX1 = #MARGIN + PathX(0) * #CELL_SIZE + #CELL_SIZE / 2
  AnimY1 = #MARGIN + PathY(0) * #CELL_SIZE + #CELL_SIZE / 2
  AnimX2 = #MARGIN + PathX(1) * #CELL_SIZE + #CELL_SIZE / 2
  AnimY2 = #MARGIN + PathY(1) * #CELL_SIZE + #CELL_SIZE / 2
EndProcedure

Procedure UpdateMove()
  Protected t.f
  If AnimatingMove = 0 : ProcedureReturn : EndIf

  t = (ElapsedMilliseconds() - MoveStepStart) / #MOVE_STEP_DURATION
  If t > 1 : t = 1 : EndIf
  AnimX = AnimX1 + (AnimX2 - AnimX1) * t
  AnimY = AnimY1 + (AnimY2 - AnimY1) * t

  If t = 1
    MoveStep + 1
    If MoveStep > ArraySize(PathX())
      AnimatingMove = 0
      Grid(PathX(ArraySize(PathX())), PathY(ArraySize(PathY())))\color = AnimColor
      If DetectLines() = 0
        If HasFreeCells() = 0
         GameOver = 1
        Else
          AddRandomBalls()
          If HasFreeCells() = 0
            GameOver = 1
          Else
            DetectLines()
          EndIf
        EndIf
      EndIf
    Else
      AnimX1 = AnimX2 : AnimY1 = AnimY2
      AnimX2 = #MARGIN + PathX(MoveStep) * #CELL_SIZE + #CELL_SIZE / 2
      AnimY2 = #MARGIN + PathY(MoveStep) * #CELL_SIZE + #CELL_SIZE / 2
      MoveStepStart = ElapsedMilliseconds()
    EndIf
  EndIf
EndProcedure


Procedure DrawBallCached(x, y, colorIndex)
  DrawingMode(#PB_2DDrawing_AlphaBlend)
  ;DrawAlphaImage(ImageID(BallImage(colorIndex)), DesktopScaledX(x - #BALL_IMG_SIZE/2), DesktopScaledY(y - #BALL_IMG_SIZE/2))
  DrawImage(ImageID(BallImage(colorIndex)), DesktopScaledX(x), DesktopScaledY(y))
  DrawingMode(#PB_2DDrawing_Default)
EndProcedure

Procedure CenterText(x, y, l, h, Text$, Color = $FFFFFF, Shadow = $333333)
  Protected xt, yt
  xt = x + (l / 2)
  yt = y + (h / 2)
  DrawText(DesktopScaledX(xt + 1) - (TextWidth(Text$) / 2), DesktopScaledY(yt + 1) - (TextHeight(Text$) / 2), Text$, Shadow)
  DrawText(DesktopScaledX(xt) - (TextWidth(Text$) / 2), DesktopScaledY(yt) - (TextHeight(Text$) / 2), Text$, Color)
EndProcedure

Procedure GameOver()
  Protected HighScoreFile
  DrawingMode(#PB_2DDrawing_AlphaBlend)
  Box(0, 0, OutputWidth(), OutputHeight(), RGBA(0, 0, 0, 160))
  DrawingMode(#PB_2DDrawing_Transparent)
  DrawingFont(FontID(EndFont))
  CenterText(0, 0, #GRID_SIZE * #CELL_SIZE + 2 * #MARGIN, DesktopUnscaledY(OutputHeight()), "GAME OVER", RGBA(255, 80, 80, 255), RGBA(30, 30, 30, 255))
  If NewHiscore = #True
    HighScoreFile = OpenFile(#PB_Any, HighScoreFile$)
    If HighScoreFile
      WriteStringN(HighScoreFile, Str(Score))
      CloseFile(HighScoreFile)
    EndIf
  EndIf  
EndProcedure

Procedure Scores()
  Protected x
  DrawingMode(#PB_2DDrawing_AlphaBlend)
  RoundBox(DesktopScaledX(#GRID_SIZE * #CELL_SIZE + 2 * #MARGIN + 10), DesktopScaledY(180), DesktopScaledX(130), DesktopScaledY(30), DesktopScaledX(5), DesktopScaledY(5), RGBA(255, 255, 255, 20))
  RoundBox(DesktopScaledX(#GRID_SIZE * #CELL_SIZE + 2 * #MARGIN + 10), DesktopScaledY(250), DesktopScaledX(130), DesktopScaledY(30) ,DesktopScaledX(5), DesktopScaledY(5), RGBA(255, 255, 255, 20))
  RoundBox(DesktopScaledX(#GRID_SIZE * #CELL_SIZE + 2 * #MARGIN + 10), DesktopScaledY(400), DesktopScaledX(130), DesktopScaledY(30), DesktopScaledX(5), DesktopScaledY(5), RGBA(255, 255, 255, 20))
  DrawingMode(#PB_2DDrawing_Transparent)
  DrawingFont(FontID(ButtonFont))
  CenterText(#GRID_SIZE * #CELL_SIZE + 2 * #MARGIN + 10, 155, 130, 30, "Score")
  CenterText(#GRID_SIZE * #CELL_SIZE + 2 * #MARGIN + 10, 225, 130, 30, "Best")
  CenterText(#GRID_SIZE * #CELL_SIZE + 2 * #MARGIN + 10, 375, 130, 30, "Next")
  DrawingFont(FontID(scoreFont))
  CenterText(#GRID_SIZE * #CELL_SIZE + 2 * #MARGIN + 10, 180, 130, 30, RSet(Str(Score), 4, "0"), $2BDA34)
  CenterText(#GRID_SIZE * #CELL_SIZE + 2 * #MARGIN + 10, 250, 130, 30, RSet(Str(HighScore), 4, "0"), $2B81F0)
  DrawingMode(#PB_2DDrawing_AlphaBlend)
  For x = 0 To #NEW_BALLS - 1
     DrawImage(ImageID(BallImage(NextBalls(x))), DesktopScaledX(#GRID_SIZE * #CELL_SIZE + 2 * #MARGIN + 30 + (30 * x)), DesktopScaledY(401), DesktopScaledX(28), DesktopScaledY(28))
  Next
EndProcedure

Procedure Encadre(x, y , x1, y1, Color = #Yellow)
  For i = 0  To DesktopScaledX(1)  
    LineXY(DesktopScaledX(x) + i, DesktopScaledY(y) + i, DesktopScaledX(x1) - i, DesktopScaledY(y) + i, Color)
    LineXY(DesktopScaledX(x1) - i, DesktopScaledY(y) + i, DesktopScaledX(x1 )- i, DesktopScaledY(y1) - i, Color)
    LineXY(DesktopScaledX(x1) - i, DesktopScaledY(y1) - i, DesktopScaledX(x) + i, DesktopScaledY(y1) - i, Color)
    LineXY(DesktopScaledX(x) + i, DesktopScaledY(y1) - i, DesktopScaledX(x) + i, DesktopScaledY(y) + i, Color)
  Next i
EndProcedure


Procedure DrawGrid()
  Protected x, y, cx, cy, r, t.f, L, H
  StartDrawing(CanvasOutput(#MainCanvas))
  Box(0, 0, OutputWidth(), OutputHeight(), #COULEUR_JEU)
  D = AdjustBrightness(#COULEUR_PLATEAU, 10)
  For y = 0 To #GRID_SIZE - 1
    For x = 0 To #GRID_SIZE - 1
      cx = #MARGIN + x * #CELL_SIZE
      cy = #MARGIN + y * #CELL_SIZE
      Box(DesktopScaledX(cx), DesktopScaledY(cy), DesktopScaledX(#CELL_SIZE), DesktopScaledY(#CELL_SIZE), #COULEUR_GRILLE)
      DrawingMode(#PB_2DDrawing_Gradient)
      BackColor(#COULEUR_PLATEAU)
      FrontColor(D)
      LinearGradient(DesktopScaledX(cx + 1), DesktopScaledY(cy + 1), DesktopScaledX(cx + #CELL_SIZE - 2), DesktopScaledY(cy + #CELL_SIZE -2))
      Box(DesktopScaledX(cx + 1), DesktopScaledY(cy + 1), DesktopScaledX(#CELL_SIZE - 2), DesktopScaledY(#CELL_SIZE - 2), #COULEUR_PLATEAU)
      DrawingMode(#PB_2DDrawing_Default)
      If x = HoverX And y = HoverY
        Encadre(cx + 1, cy + 1, #CELL_SIZE + cx - 2, #CELL_SIZE + cy - 2, #Yellow)
      EndIf
      If Grid(x, y)\color
        ; === Halo de sélection ===
        If x = SelectedX And y = SelectedY
          DrawingMode(#PB_2DDrawing_AlphaBlend)
          Protected pulse.f
          pulse = Sin(PulsePhase) + 1   ; 0 → 1
          Box(DesktopScaledX(cx + (pulse * 4)), DesktopScaledY(cy + (pulse * 4)), DesktopScaledX(#CELL_SIZE - (pulse * 8)), DesktopScaledY(#CELL_SIZE - (pulse * 8)), RGBA(210, 220, 20, 250 - (pulse * 100)))
        EndIf
        DrawBallCached(cx, cy, Grid(x,y)\color)
      EndIf
    Next
  Next

  If AnimatingMove
    DrawBallCached(AnimX - #CELL_SIZE / 2, AnimY - #CELL_SIZE / 2, AnimColor)
  EndIf

  If Removing
    t = (ElapsedMilliseconds() - RemoveStart) / #REMOVE_DURATION
    If t > 1 : t = 1 : EndIf
    For y = 0 To #GRID_SIZE - 1
      For x = 0 To #GRID_SIZE - 1
        If RemoveMask(x, y)
          r = (#CELL_SIZE / 2 - 6) * (1 - t)
          Circle(DesktopScaledX(#MARGIN + x * #CELL_SIZE + #CELL_SIZE / 2), DesktopScaledY(#MARGIN + y * #CELL_SIZE + #CELL_SIZE / 2), DesktopScaledX(r), Colors(Grid(x, y)\color))
          If t = 1
            Grid(x, y)\color = 0
            RemoveCount + 1
          EndIf
        EndIf
      Next
    Next
    If t = 1
      ScorePopupValue = RemoveCount * RemoveCount
      If RemoveCount = #GRID_SIZE And RemovedLineCount = 1
        ScorePopupValue + 100
        PerfectLine = #True
      EndIf
      
      Score + (ScorePopupValue * RemovedLineCount)
      ScorePopupStartTime = ElapsedMilliseconds()
      ScorePopupActive = 1
      Removing = 0 
      RemoveCount = 0
    EndIf
  EndIf
  ;RemovedLineCount = 2
  ;PerfectLine = #True
  If ScorePopupActive
    Protected alpha, yOffset.f
    t = (ElapsedMilliseconds() - ScorePopupStartTime) / #SCORE_POPUP_DURATION
    If t > 1
      ScorePopupActive = 0
      PerfectLine = #False
    Else
      yOffset = -20 * t
      alpha = 255 * (1 - t)
      PopupScore$ = "+" + Str(ScorePopupValue)
      Combo$      = "Combo x" + Str(RemovedLineCount)
      
      DrawingMode(#PB_2DDrawing_Transparent | #PB_2DDrawing_AlphaBlend)
      DrawingFont(FontID(PopupFont))
      L = TextWidth(Combo$)
      H = TextHeight(Combo$)
      CenterText(ScorePopupX, ScorePopupY + yOffset, DesktopUnscaledX(L), DesktopUnscaledY(H), PopupScore$, RGBA(255, 215, 0, alpha), RGBA(0, 0, 0, alpha))
      If RemovedLineCount > 1
        If ScorePopupX < 0 : ScorePopupX = 0 : EndIf
        ;CenterText(ScorePopupX, ScorePopupY + yOffset + DesktopUnscaledY(TextHeight(PopupScore$)), DesktopUnscaledX(L), DesktopUnscaledY(H), Combo$, RGBA(255, 215, 0, alpha), RGBA(0, 0, 0, alpha))
        CenterText(ScorePopupX, ScorePopupY + yOffset + DesktopUnscaledY(TextHeight(PopupScore$)), DesktopUnscaledX(L), DesktopUnscaledY(H), Combo$, RGBA(208, 117, 211, alpha), RGBA(0, 0, 0, alpha))
      EndIf
      If PerfectLine
        CenterText(ScorePopupX, ScorePopupY + yOffset + DesktopUnscaledY(TextHeight(PopupScore$)), DesktopUnscaledX(L), DesktopUnscaledY(H), "Line +100", RGBA(208, 117, 211, alpha), RGBA(0, 0, 0, alpha))
      EndIf
    EndIf
  EndIf

  If Score > HighScore
    NewHiscore = #True
    HighScore = Score
  EndIf
  Scores()
  If GameOver
    GameOver()  
  EndIf
  StopDrawing()
EndProcedure

Procedure InitBallImages()
  Protected i
  For i = 1 To #NB_COLORS
    BallImage(i) = CreateBallImage(#CELL_SIZE, Colors(i))
  Next
EndProcedure

Procedure NewGame()
  If GameOver = 0
    If MessageRequester("Question", "Do you want to restart the game ?", #PB_MessageRequester_YesNo | #PB_MessageRequester_Warning) = #PB_MessageRequester_Yes
      ResetGame()
    EndIf
  Else
    ResetGame()
  EndIf
EndProcedure


Procedure TimerHandle()
  PulsePhase + PulseSpeed
  If PulsePhase > 2 * #PI
    PulsePhase - 2 * #PI
  EndIf
  UpdateMove()
  DrawGrid()
EndProcedure

Procedure HandleCanvasEvents()
  GadgetID = EventGadget()
  Select EventType()
    Case #PB_EventType_LeftClick
      Select GadgetID
        Case #QuitGame : PostEvent(#PB_Event_CloseWindow, #MainWindow, #Null) 
        Case #NewGame : NewGame()
      EndSelect   
    Case #PB_EventType_MouseEnter
      SetGadgetAttribute(GadgetID, #PB_Canvas_Cursor, #PB_Cursor_Hand)
      RedrawButton(GadgetID, 0)
    Case #PB_EventType_MouseLeave
      SetGadgetAttribute(GadgetID, #PB_Canvas_Cursor, #PB_Cursor_Default)
      RedrawButton(GadgetID, 1)   
    EndSelect
EndProcedure
  
Procedure MenuEvents()
  Select EventMenu()
    CompilerIf #PB_Compiler_OS = #PB_OS_MacOS   
      Case #PB_Menu_About      
      Case #PB_Menu_Preferences
      Case #PB_Menu_Quit
        PostEvent(#PB_Event_CloseWindow, #MainWindow, #Null)      
    CompilerEndIf
  Case #KeyESC
    NewGame()
  EndSelect
EndProcedure


OpenWindow(#MainWindow, 0, 0, #GRID_SIZE * #CELL_SIZE + 2 * #MARGIN + 160, #GRID_SIZE * #CELL_SIZE + 20, "Pure ColorLines - v " + #Version + "  -  by Mindphazer", #PB_Window_ScreenCentered | #PB_Window_SystemMenu | #PB_Window_MinimizeGadget)
CanvasGadget(#MainCanvas, 0, 0, WindowWidth(#MainWindow), WindowHeight(#MainWindow), #PB_Canvas_Container)
Button(#NewGame, #GRID_SIZE * #CELL_SIZE + 2 * #MARGIN + 10, 20, 130, 30, "New Game", $BFBF3F)
Button(#QuitGame, #GRID_SIZE * #CELL_SIZE + 2 * #MARGIN + 10, 60, 130, 30, "Quit Game", $4096F1)
AddWindowTimer(#MainWindow, #TIMER_ANIM, 10)
HighScoreFile = ReadFile(#PB_Any, HighScoreFile$)
If HighScoreFile
  HighScore = Val(ReadString(HighScoreFile))
  CloseFile(HighScoreFile)
EndIf
AddKeyboardShortcut(#MainWindow, #PB_Shortcut_Escape, #KeyESC)
CreateImageMenu(#MainMenu, WindowID(#MainWindow))
CompilerIf #PB_Compiler_OS = #PB_OS_MacOS
  MenuItem(#PB_Menu_About, "")
  MenuItem(#PB_Menu_Preferences, "")
  MenuItem(#PB_Menu_Quit, "")
CompilerEndIf
    
BindEvent(#PB_Event_Menu, @MenuEvents(), #MainWindow)
BindEvent(#PB_Event_Timer, @TimerHandle())
BindGadgetEvent(#QuitGame, @HandleCanvasEvents())
BindGadgetEvent(#NewGame, @HandleCanvasEvents())

InitBallImages()
GenerateNextBalls()
AddRandomBalls()

Repeat
  Event = WaitWindowEvent()
  Select Event
    Case #PB_Event_CloseWindow, #Event_TerminateRequested
      If GameOver = 0
        If MessageRequester("Warning", "You have a game in progress." + #CRLF$ + "Are  you sure you want to leave ?", #PB_MessageRequester_YesNo) = #PB_MessageRequester_Yes
          Quit = #True
        EndIf
      Else
        Quit = #True
      EndIf
    Case #PB_Event_Gadget
      If EventGadget() = #MainCanvas
        mx = Round((GetGadgetAttribute(#MainCanvas, #PB_Canvas_MouseX) - DesktopScaledX(#MARGIN)) / DesktopScaledX(#CELL_SIZE), #PB_Round_Down)
        my = Round((GetGadgetAttribute(#MainCanvas, #PB_Canvas_MouseY) - DesktopScaledY(#MARGIN)) / DesktopScaledY(#CELL_SIZE), #PB_Round_Down)
        If EventType() = #PB_EventType_LeftClick And AnimatingMove = 0 And Removing = 0
          ;mx = Round((GetGadgetAttribute(#MainCanvas, #PB_Canvas_MouseX) - DesktopScaledX(#MARGIN)) / DesktopScaledX(#CELL_SIZE), #PB_Round_Down)
          ;my = Round((GetGadgetAttribute(#MainCanvas, #PB_Canvas_MouseY) - DesktopScaledY(#MARGIN)) / DesktopScaledY(#CELL_SIZE), #PB_Round_Down)
          If mx >= 0 And mx < #GRID_SIZE And my >= 0 And my < #GRID_SIZE
            If Grid(mx, my)\color And SelectedX = -1
              SelectedX = mx 
              SelectedY = my
            ElseIf Grid(mx, my)\color = 0 And SelectedX <> -1
              StartMove(mx, my)
              SelectedX = -1 : SelectedY = -1
            Else
              SelectedX = -1 : SelectedY = -1
            EndIf
          EndIf
        Else
          ;mx = Round((GetGadgetAttribute(#MainCanvas, #PB_Canvas_MouseX) - DesktopScaledX(#MARGIN)) / DesktopScaledX(#CELL_SIZE), #PB_Round_Down)
          ;my = Round((GetGadgetAttribute(#MainCanvas, #PB_Canvas_MouseY) - DesktopScaledY(#MARGIN)) / DesktopScaledY(#CELL_SIZE), #PB_Round_Down)
          If mx >= 0 And mx < #GRID_SIZE And my >= 0 And my < #GRID_SIZE
            HoverX = mx
            HoverY = my
          Else
            HoverX = -1 : HoverY = -1
          EndIf
        EndIf
      EndIf
  EndSelect
Until Quit = #True
Bureau : Win11 64bits
Maison : Macbook Pro M4 16" SSD 512 Go / Ram 24 Go - iPad Air 128 Go (pour madame) - iPhone 17 Pro Max 256 Go
Avatar de l’utilisateur
SPH
Messages : 5033
Inscription : mer. 09/nov./2005 9:53

Re: PureColorLines

Message par SPH »

Salut,

on est vite coincé par les nouvelles boules :lol:

!i!i!i!i!i!i!i!i!i!
!i!i!i!i!i!i!
!i!i!i!
//// Informations ////
Intel Core i7 4770 64 bits - GTX 650 Ti
Version de PB : 6.12LTS- 64 bits
Avatar de l’utilisateur
Mindphazer
Messages : 716
Inscription : mer. 24/août/2005 10:42

Re: PureColorLines

Message par Mindphazer »

Ouais elles arrivent vite (trop vite :mrgreen: )
Bureau : Win11 64bits
Maison : Macbook Pro M4 16" SSD 512 Go / Ram 24 Go - iPad Air 128 Go (pour madame) - iPhone 17 Pro Max 256 Go
Avatar de l’utilisateur
SPH
Messages : 5033
Inscription : mer. 09/nov./2005 9:53

Re: PureColorLines

Message par SPH »

En tout cas, félicitation pour une chose en particulier : trouver le chemin (le plus court ?) pour transporter une boule !! :idea:

!i!i!i!i!i!i!i!i!i!
!i!i!i!i!i!i!
!i!i!i!
//// Informations ////
Intel Core i7 4770 64 bits - GTX 650 Ti
Version de PB : 6.12LTS- 64 bits
Avatar de l’utilisateur
falsam
Messages : 7361
Inscription : dim. 22/août/2010 15:24
Localisation : IDF (Yvelines)
Contact :

Re: PureColorLines

Message par falsam »

Parfois je n'arrive pas à déplacer une boule. Dans ce GIF, je déplace une boule violette pour terminer une barre verticale de 5 boules violettes. La boule ne se déplace pas.

Image
Configuration : Windows 11 Famille 64-bit - PB 6.23 x64 - AMD Ryzen 7 - 16 GO RAM
Vidéo NVIDIA GeForce GTX 1650 Ti - Résolution 1920x1080 - Mise à l'échelle 125%
Avatar de l’utilisateur
Mindphazer
Messages : 716
Inscription : mer. 24/août/2005 10:42

Re: PureColorLines

Message par Mindphazer »

Bonjour falsam,

ton gif ne s'affiche pas :?:
Bureau : Win11 64bits
Maison : Macbook Pro M4 16" SSD 512 Go / Ram 24 Go - iPad Air 128 Go (pour madame) - iPhone 17 Pro Max 256 Go
Avatar de l’utilisateur
falsam
Messages : 7361
Inscription : dim. 22/août/2010 15:24
Localisation : IDF (Yvelines)
Contact :

Re: PureColorLines

Message par falsam »

Ha mince ! Je le vois sur le forum.

le lien https://falsam.com/Download/images/PureColorLInes.gif
Configuration : Windows 11 Famille 64-bit - PB 6.23 x64 - AMD Ryzen 7 - 16 GO RAM
Vidéo NVIDIA GeForce GTX 1650 Ti - Résolution 1920x1080 - Mise à l'échelle 125%
Avatar de l’utilisateur
Mindphazer
Messages : 716
Inscription : mer. 24/août/2005 10:42

Re: PureColorLines

Message par Mindphazer »

C'est normal : il n'y a pas de chemin dégagé pour que ta bille violette rejoigne ses copines (elle ne se déplace qu'en ligne droite, pas en diagonale)
Si tu déplaces la bille bleue en haut pour libérer un passage, ça fonctionnera
Bureau : Win11 64bits
Maison : Macbook Pro M4 16" SSD 512 Go / Ram 24 Go - iPad Air 128 Go (pour madame) - iPhone 17 Pro Max 256 Go
Avatar de l’utilisateur
falsam
Messages : 7361
Inscription : dim. 22/août/2010 15:24
Localisation : IDF (Yvelines)
Contact :

Re: PureColorLines

Message par falsam »

Ok. Apparemment je n'ai pas du comprendre l'univers de ce jeu 🤪
Merci pour ta réponse.
Configuration : Windows 11 Famille 64-bit - PB 6.23 x64 - AMD Ryzen 7 - 16 GO RAM
Vidéo NVIDIA GeForce GTX 1650 Ti - Résolution 1920x1080 - Mise à l'échelle 125%
Répondre