Jump to content

A New Dimension for Snake Game: The Object-Oriented Version


Recommended Posts


Sometimes, the most interesting discoveries happen by accident. That's how I stumbled upon an old post presenting a classic version of the game Snake. However, rather than simply appreciating the game as it was, I saw an opportunity for improvement by using AutoItObject.au3 , a powerful tool that didn't exist at the time of the original version's creation. In this article, I'll introduce you to my object-oriented version of Snake Game, which brings flexibility and modernity to this timeless classic.


AutoItObject.au3 is an incredibly flexible tool that allows for modular and efficient application and game development. With this library, I was able to completely rethink the structure of the Snake game, transforming it into a rich and dynamic object-oriented experience. This means that every entity in the game, from the snake to the food to the user interface, is represented as a distinct object, offering extraordinary flexibility and extensibility.


One of the main features of my version of Snake Game is the ability for players to express their own preferences and customize the gaming experience to their liking. Whether adjusting the score, modifying the difficulty level, or adding new game elements, players have the freedom to shape the game to suit their preferences.


Despite its underlying complexity, using AutoItObject.au3 makes the code for Snake Game surprisingly simple and easy to understand. Each game object is carefully encapsulated and modeled to accurately reflect its behavior in the real world. This means that developers can easily add new features or modify the game without compromising its stability or quality.


By reimagining Snake Game through the lens of object-oriented programming with AutoItObject.au3, I was able to create a version of the game that combines the simplicity of the original classic with the flexibility and modernity of object-oriented programming. I look forward to seeing how this version evolves over time, and I hope it inspires other developers to rethink their own projects using this powerful library.

 

#include-once
#AutoIt3Wrapper_Au3Check_Parameters=-d -w 1 -w 2 -w 3 -w 4 -w 5 -w 6
; #INDEX# =======================================================================================================================
; Title .........: Snake Game
; AutoIt Version : 3.3
; AutoItObject Version : v1.2.8.2
; Language ......: English
; Description ...: A simple Snake game implementation using AutoIt->AutoItObject.au3.
; Dependencies ..: AutoItObject.au3
; Author ........: Numeric
; ===============================================================================================================================
#include <GUIConstantsEx.au3>
#include "AutoItObject.au3" 
#include <Print.au3>
#include <Misc.au3>

Global $oErrorHandler = ObjEvent("AutoIt.Error", "_ErrFunc")

_AutoItObject_Startup()

Func Snake($oPoint = 0, $Board = Default)
    Local $aRandomDirectionTab = [-1, 0, 1]
    Local $dx = $aRandomDirectionTab[Int(Random(0, 2, 1))]
    Local $dy = $aRandomDirectionTab[Int(Random(0, 2, 1))]
    Local $iLowSpeed = 100
    Local $foodCounter = 0

    If $Board = Default Then $Board = GUICreate("Snake", 400, 400)
    GUISetBkColor(0x000000)

    GUISetState()
    Local $aBoardPos = WinGetClientSize($Board)  
    If @error Then Return SetError(1, 0, False) ; window not found
    Local $iBoardWidth = $aBoardPos[0]
    Local $iBoardHeight = $aBoardPos[1]

    Local $X = Random(10, $iBoardWidth - 10, 1)
    Local $Y = Random(10, $iBoardHeight - 10, 1)

    If $oPoint = 0 Then $oPoint = Point($X, $Y)
    Local $aMap[]
    MapAppend($aMap, $oPoint)
    Local $oFood = Point(Random(10, $iBoardWidth - 10), Random(10, $iBoardHeight - 10), 39219)

    Local $sClass = _AutoItObject_Class()
    With $sClass
        .Create()
        .AddProperty("head", $ELSCOPE_PRIVATE, $oPoint)
        .AddProperty("aMap", $ELSCOPE_PRIVATE, $aMap)
        .AddProperty("food", $ELSCOPE_PRIVATE, $oFood)
        .AddProperty("foodCounter", $ELSCOPE_PRIVATE, $foodCounter)
        .AddProperty("dx", $ELSCOPE_PRIVATE, $dx)
        .AddProperty("dy", $ELSCOPE_PRIVATE, $dy)
        .AddProperty("Board", $ELSCOPE_PRIVATE, $Board)
        .AddProperty("speedLevel", $ELSCOPE_PRIVATE, $iLowSpeed)
        .AddProperty("iBoardWidth", $ELSCOPE_PRIVATE, $iBoardWidth)
        .AddProperty("iBoardHeight", $ELSCOPE_PRIVATE, $iBoardHeight)

        .AddMethod("getBoard", "_getBoard")
        .AddMethod("setSpeedLevel", "_setSpeedLevel")
        .AddMethod("getSpeedLevel", "_getSpeedLevel")
        .AddMethod("runGameLoop", "_runGameLoop")


        .AddMethod("setdx", "_setdx")
        .AddMethod("setdy", "_setdy")
        .AddMethod("getdx", "_getdx")
        .AddMethod("getdy", "_getdy")

        .AddMethod("sleepW", "_sleepW")
        .AddMethod("getMap", "_getMap")
        .AddMethod("setMap", "_setMap")
        .AddMethod("getHead", "_getHead")
        .AddMethod("getBoardHeight", "_getBoardHeight")
        .AddMethod("getBoardWidth", "_getBoardWidth")
        .AddMethod("getFoodCounter", "_getFoodCounter")
        .AddMethod("setFoodCounter", "_setFoodCounter", True)
        .AddMethod("move", "_move", True)
        .AddMethod("generateFood", "_generateFood")
        .AddMethod("setFood", "_setFood")
        .AddMethod("getFood", "_getFood")
        .AddDestructor("_destructor")
    EndWith
    Return $sClass.Object
EndFunc   ;==>Snake

Func _destructor($this)
    ConsoleWrite("Destructor ....." & @CRLF)
    Local $aMap = $this.getMap()
    For $oPoint In $aMap
        $oPoint = 0
    Next
    $aMap = 0
    $this.setMap(0)
EndFunc   ;==>_destructor


Func _getFoodCounter($this)
    Return $this.foodCounter
EndFunc   ;==>_getFoodCounter

Func _setFoodCounter($this, $foodCounter)
    $this.foodCounter = $foodCounter
EndFunc   ;==>_setFoodCounter

Func _setSpeedLevel($this, $iLevel)
    $this.speedLevel = $iLevel
EndFunc   ;==>_setSpeedLevel

Func _getSpeedLevel($this)
    Return $this.speedLevel
EndFunc   ;==>_getSpeedLevel

Func _getBoard($this)
    Return $this.Board
EndFunc   ;==>_getBoard

Func _runGameLoop($this)
    While 1
        If GUIGetMsg() = $GUI_EVENT_CLOSE Then ExitLoop
        If _IsPressed(25) Then
            $this.setdx(-1)
            $this.setdy(0)
        EndIf
        If _IsPressed(27) Then
            $this.setdx(1)
            $this.setdy(0)
        EndIf
        If _IsPressed(26) Then
            $this.setdx(0)
            $this.setdy(-1)
        EndIf
        If _IsPressed(28) Then
            $this.setdx(0)
            $this.setdy(1)
        EndIf

        If _IsPressed(53) Then
            $this.sleepW()
        EndIf
        $this.move()
        Sleep($this.getSpeedLevel())
    WEnd

EndFunc   ;==>_runGameLoop

Func _getBoardWidth($this)
    Return $this.iBoardWidth
EndFunc   ;==>_getBoardWidth

Func _getBoardHeight($this)
    Return $this.iBoardHeight
EndFunc   ;==>_getBoardHeight

Func _generateFood($this)
    Local $oFood = Point(Random(10, $this.getBoardWidth() - 10), Random(10, $this.getBoardHeight() - 10), 39219)
    $this.setFood($oFood)
EndFunc   ;==>_generateFood

Func _setFood($this, $oFood)
    $this.food = $oFood
EndFunc   ;==>_setFood

Func _getFood($this)
    Return $this.food
EndFunc   ;==>_getFood

Func _getHead($this)
    Return $this.head
EndFunc   ;==>_getHead

Func _setdx($this, $dx)
    $this.dx = $dx
EndFunc   ;==>_setdx

Func _setdy($this, $dy)
    $this.dy = $dy
EndFunc   ;==>_setdy

Func _getdx($this)
    Return $this.dx
EndFunc   ;==>_getdx

Func _getdy($this)
    Return $this.dy
EndFunc   ;==>_getdy

Func _getMap($this)
    Return $this.aMap
EndFunc   ;==>_getMap

Func _setMap($this, $aMap)
    $this.aMap = $aMap
EndFunc   ;==>_setMap

Func _move($this)
    Local $aMap = $this.getMap()
    Local $head = $aMap[0]
    Local $newX = $head.getXPos() + $this.getdx() * 10
    Local $newY = $head.getYPos() + $this.getdy() * 10

    ; Vérifier si la tête du serpent sort de l'écran
    If $newX <= 0 Or $newX >= $this.getBoardWidth() - 10 Or $newY <= 0 Or $newY >= $this.getBoardHeight() - 10 Then
        Return SetError(1, 0, False)
    EndIf

    Local $oFood = $this.getFood()
    If $head.checkCollision($oFood) Then
        $this.setFoodCounter($this.getFoodCounter() + 1)
        Local $lastSegment = $aMap[UBound($aMap) - 1]
        Local $lastX = $lastSegment.getXPos() + $this.getdx() * 10
        Local $lastY = $lastSegment.getYPos() + $this.getdy() * 10
        MapAppend($aMap, Point($lastX, $lastY))

        $this.setMap($aMap)
        If $this.getFoodCounter() = 4 Then
            Local $newSpeedLevel = $this.getSpeedLevel() - 20
            If $newSpeedLevel < 0 Then $newSpeedLevel = 1
            $this.setSpeedLevel($newSpeedLevel)
            $this.setFoodCounter(0)
        EndIf

        GUICtrlDelete($oFood.getID())
        $this.generateFood()
    EndIf

    ; Mettre à jour la position de la tête du serpent
    $head.setXPos($newX)
    $head.setYPos($newY)

    ; Déplacer les segments du serpent
    For $i = UBound($aMap) - 1 To 1 Step -1
        $aMap[$i].setXPos($aMap[$i - 1].getXPos())
        $aMap[$i].setYPos($aMap[$i - 1].getYPos())
    Next
EndFunc   ;==>_move

;bug
Func _sleepW(ByRef $this)
    Do
        Do
            Sleep(100)
        Until _IsPressed(25) Or _IsPressed(26) Or _IsPressed(27) Or _IsPressed(28)
        If _IsPressed(25) Then
            $this.setdx(-1)
            $this.setdy(0)
        EndIf
        If _IsPressed(27) Then
            $this.setdx(1)
            $this.setdy(0)
        EndIf
        If _IsPressed(26) Then
            $this.setdx(0)
            $this.setdy(-1)
        EndIf
        If _IsPressed(28) Then
            $this.setdx(0)
            $this.setdy(1)
        EndIf
    Until _IsPressed(25) Or _IsPressed(26) Or _IsPressed(27) Or _IsPressed(28)
EndFunc   ;==>_sleepW

Func Point($X = 0, $Y = 0, $iClolor = 16777215)
    Local $idLabel = GUICtrlCreateLabel("", $X, $Y, 10, 10, 0x0001) ; $BS_CENTER
    GUICtrlSetBkColor($idLabel, $iClolor)

    Local $cPoint = _AutoItObject_Class()
    With $cPoint
        .Create()
        .AddProperty("___sType___", $ELSCOPE_PRIVATE, "Point")
        .AddProperty("X", $ELSCOPE_PRIVATE, $X)
        .AddProperty("Y", $ELSCOPE_PRIVATE, $Y)
        .AddProperty("iColor", $ELSCOPE_PRIVATE, $iClolor)
        .AddProperty("idLabel", $ELSCOPE_PRIVATE, $idLabel)
        .AddMethod("getObjType", "_getObjType")
        .AddMethod("setXPos", "_setXPos")
        .AddMethod("setYPos", "_setYPos")
        .AddMethod("getID", "_getID")
        .AddMethod("setID", "_setID")
        .AddMethod("getXPos", "_getXPos")
        .AddMethod("getYPos", "_getYPos")
        .AddMethod("checkCollision", "_checkCollision")
    EndWith
    Return $cPoint.Object
EndFunc   ;==>Point

Func _getObjType($this)
    Return $this.___sType___
EndFunc   ;==>_getObjType

Func _checkCollision($this, $oPoint)
    If Not IsObj($oPoint) Then Return SetError(1, 0, False)
    Local $sType = $oPoint.getObjType()
    If $sType <> "Point" Then Return SetError(2, 0, False)

    Local $aPos1 = ControlGetPos("", "", $this.getID())
    Local $aPos2 = ControlGetPos("", "", $oPoint.getID())
    If $aPos1[0] + $aPos1[2] >= $aPos2[0] And $aPos1[0] <= $aPos2[0] + $aPos2[2] And $aPos1[1] + $aPos1[3] >= $aPos2[1] And $aPos1[1] <= $aPos2[1] + $aPos2[3] Then
        Return True
    EndIf
    Return False
EndFunc   ;==>_checkCollision

Func _setXPos($this, $iX)
    $this.X = $iX
    GUICtrlSetPos($this.idLabel, $iX, $this.getYPos())
EndFunc   ;==>_setXPos

Func _setYPos($this, $iY)
    $this.Y = $iY
    GUICtrlSetPos($this.idLabel, $this.getXPos(), $iY)
EndFunc   ;==>_setYPos

Func _getXPos($this)
    Return $this.X
EndFunc   ;==>_getXPos

Func _getYPos($this)
    Return $this.Y
EndFunc   ;==>_getYPos

Func _getID($this)
    Return $this.idLabel
EndFunc   ;==>_getID

Func _setID($this, $idLabel)
    $this.idLabel = $idLabel
EndFunc   ;==>_setID


;***************************************************
Global $sNake = Snake()
$sNake.runGameLoop()
$sNake = 0
;**************************************************

_AutoItObject_Shutdown()

 

Link to comment
Share on other sites

The script doesn't run since you use some unknown script called Print.au3 (at least in the latest AutoIt version). If you comment the line then the script run but the collision doesn't seems to work. Also the flicker is present even when the length of the snake it's just one and get worse when the length increase. Mabye drawing the objects using GDI+ will fix this issue. Overall it's a good start but it leaves room for improvements.

Untitled.jpg

 

When the words fail... music speaks.

Link to comment
Share on other sites

Thank you very much, Andreik, for your constructive feedback. Indeed, the adoption of GDI+ has significantly improved the fluidity of the snake's movements in its eternal quest for food. This major change was accomplished in record time (including a 5-minute coffee break) thanks to the flexibility of the object-oriented paradigm offered by the AutoItObject.au3 UDF. With this tool, you can literally build an empire, brick by brick.

Now, you have the freedom to adjust the snake's speed, change its color on the fly, even during gameplay, and even generate fake prey of different colors to try to trap "Kaa," Mowgli's old companion. In other words, you can unleash your imagination and push the boundaries of what's possible.

 

#include-once
#AutoIt3Wrapper_Au3Check_Parameters=-d -w 1 -w 2 -w 3 -w 4 -w 5 -w 6
; #INDEX# =======================================================================================================================
; Title .........: Snake Game
; AutoIt Version : 3.3
; AutoItObject Version : v1.2.8.2
; Language ......: English
; Description ...: A simple Snake game implementation using AutoIt->AutoItObject.au3.
; Dependencies ..: AutoItObject.au3
; Author ........: Numeric
; ===============================================================================================================================
#include <GUIConstantsEx.au3>
#include "AutoItObject.au3"
#include <Misc.au3>
#include <GDIPlus.au3>
#include <GUIConstantsEx.au3>
#include <WinAPIGdi.au3>


Global $oErrorHandler = ObjEvent("AutoIt.Error", "_ErrFunc")

_GDIPlus_Startup()
_AutoItObject_Startup()

Func Snake($oPoint = 0, $Board = Default, $iBgColor = 0xFF0000FF)
    Local $aRandomDirectionTab = [-1, 0, 1]
    Local $dx = $aRandomDirectionTab[Int(Random(0, 2, 1))]
    Local $dy = $aRandomDirectionTab[Int(Random(0, 2, 1))]
    Local $iLowSpeed = 100
    Local $foodCounter = 0

    If $Board = Default Then $Board = GUICreate("Snake", 400, 400)
    GUISetBkColor(0x303030)
    GUISetState()
    Local $aBoardPos = WinGetClientSize($Board)
    If @error Then Return SetError(1, 0, False) ; window not found
    Local $iBoardWidth = $aBoardPos[0]
    Local $iBoardHeight = $aBoardPos[1]

    Local $X = Random(10, $iBoardWidth - 10, 1)
    Local $Y = Random(10, $iBoardHeight - 10, 1)

    Local $aMap[]
    Local $oFood = Point(Random(10, $iBoardWidth - 10), Random(10, $iBoardHeight - 10))
    MapAppend($aMap, $oFood)

    If $oPoint = 0 Then $oPoint = Point($X, $Y)
    MapAppend($aMap, $oPoint)

    Local $hGraphics = _GDIPlus_GraphicsCreateFromHWND($Board)
    Local $hBitmap = _GDIPlus_BitmapCreateFromGraphics($iBoardWidth, $iBoardHeight, $hGraphics)
    Local $hGraphicsCtxt = _GDIPlus_ImageGetGraphicsContext($hBitmap)
    _GDIPlus_GraphicsSetSmoothingMode($hGraphicsCtxt, $GDIP_SMOOTHINGMODE_HIGHQUALITY)
    Local $hBrush = _GDIPlus_BrushCreateSolid($iBgColor)
    Local $hFoodBrush = _GDIPlus_BrushCreateSolid(0xFF00FF00) ;(0x00FF00)


    Local $sClass = _AutoItObject_Class()
    With $sClass
        .Create()
        .AddProperty("hGraphics", $ELSCOPE_PRIVATE, $hGraphics)
        .AddProperty("hBitmap", $ELSCOPE_PRIVATE, $hBitmap)
        .AddProperty("hGraphicsCtxt", $ELSCOPE_PRIVATE, $hGraphicsCtxt)
        .AddProperty("hBrush", $ELSCOPE_PRIVATE, $hBrush)
        .AddProperty("iColor", $ELSCOPE_PRIVATE, 0xFF8080FF)
        .AddProperty("hFoodBrush", $ELSCOPE_PRIVATE, $hFoodBrush)
        .AddProperty("head", $ELSCOPE_PRIVATE, $oPoint)
        .AddProperty("aMap", $ELSCOPE_PRIVATE, $aMap)
        .AddProperty("food", $ELSCOPE_PRIVATE, $oFood)
        .AddProperty("foodCounter", $ELSCOPE_PRIVATE, $foodCounter)
        .AddProperty("dx", $ELSCOPE_PRIVATE, $dx)
        .AddProperty("dy", $ELSCOPE_PRIVATE, $dy)
        .AddProperty("Board", $ELSCOPE_PRIVATE, $Board)
        .AddProperty("speedLevel", $ELSCOPE_PRIVATE, $iLowSpeed)
        .AddProperty("iBoardWidth", $ELSCOPE_PRIVATE, $iBoardWidth)
        .AddProperty("iBoardHeight", $ELSCOPE_PRIVATE, $iBoardHeight)


        .AddMethod("getBoard", "_getBoard")
        .AddMethod("setSpeedLevel", "_setSpeedLevel")
        .AddMethod("getSpeedLevel", "_getSpeedLevel")
        .AddMethod("runGameLoop", "_runGameLoop")
        .AddMethod("getFoodBrush", "_getFoodBrush")
        .AddMethod("setdx", "_setdx")
        .AddMethod("setdy", "_setdy")
        .AddMethod("getdx", "_getdx")
        .AddMethod("getdy", "_getdy")
        .AddMethod("sleepW", "_sleepW")
        .AddMethod("getMap", "_getMap")
        .AddMethod("setMap", "_setMap")
        .AddMethod("getHead", "_getHead")
        .AddMethod("getBoardHeight", "_getBoardHeight")
        .AddMethod("getBoardWidth", "_getBoardWidth")
        .AddMethod("getFoodCounter", "_getFoodCounter")
        .AddMethod("setFoodCounter", "_setFoodCounter", True)
        .AddMethod("move", "_move", True)
        .AddMethod("generateFood", "_generateFood")
        .AddMethod("setFood", "_setFood")
        .AddMethod("getFood", "_getFood")
        .AddMethod("getSColor", "_getSColor")
        .AddMethod("setSColor", "_setSColor")
        .AddMethod("getGraphics", "_getGraphics")
        .AddMethod("getBitmap", "_getBitmap")
        .AddMethod("getGraphicsCtxt", "_getGraphicsCtxt")
        .AddMethod("getBrush", "_getBrush")
        .AddMethod("drawStage", "_drawStage")
        .AddMethod("checkBorderCollision", "_checkBorderCollision")
        .AddMethod("checkSelfCollision", "_checkSelfCollision")
        .AddMethod("checkFoodCollision", "_checkFoodCollision")
        .AddMethod("resetGame", "_resetGame")
        .AddMethod("cleanUpResources", "_cleanUpResources")
        .AddDestructor("_cleanUpResources")
    EndWith
    Return $sClass.Object

EndFunc   ;==>Snake

Func _getFoodBrush($this)
    Return $this.hFoodBrush
EndFunc   ;==>_getFoodBrush

Func _getGraphics($this)
    Return $this.hGraphics
EndFunc   ;==>_getGraphics

Func _getBitmap($this)
    Return $this.hBitmap
EndFunc   ;==>_getBitmap

Func _getGraphicsCtxt($this)
    Return $this.hGraphicsCtxt
EndFunc   ;==>_getGraphicsCtxt

Func _getBrush($this)
    Return $this.hBrush
EndFunc   ;==>_getBrush

Func _drawStage($this, $aPoints)
    Local $hGraphicsCtxt = $this.getGraphicsCtxt()
    $this.move()

    ; Effacer l'arrière-plan
    _GDIPlus_GraphicsClear($hGraphicsCtxt, 0xFF000000)

    ; Dessiner la nourriture
    Local $oFood = $this.getMap()[0]
    _GDIPlus_GraphicsFillRect($hGraphicsCtxt, $oFood.getXPos(), $oFood.getYPos(), $oFood.getWidth(), $oFood.getHeight(), $this.getFoodBrush())

    ; Dessiner les segments du serpent
    For $i = 1 To UBound($aPoints) - 1
        Local $oPoint = $aPoints[$i]
        _GDIPlus_GraphicsFillRect($hGraphicsCtxt, $oPoint.getXPos(), $oPoint.getYPos(), $oPoint.getWidth(), $oPoint.getHeight(), $this.getBrush())
    Next

    ; Mettre à jour l'affichage
    _GDIPlus_GraphicsDrawImageRect($this.getGraphics(), $this.getBitmap(), 0, 0, $this.getBoardWidth(), $this.getBoardHeight())
EndFunc   ;==>_drawStage


Func _drawStage2($this, $aPoints)
    _GDIPlus_GraphicsClear($this.getGraphicsCtxt(), 0xFF000000)
    $this.move()
    Local $oFood = $this.getMap()[0]
    _GDIPlus_GraphicsFillRect($this.getGraphicsCtxt(), $oFood.getXPos(), $oFood.getYPos(), $oFood.getWidth(), $oFood.getHeight(), $this.getFoodBrush())

    For $i = 1 To UBound($aPoints) - 1
        Local $oPoint = $aPoints[$i]
        _GDIPlus_GraphicsFillRect($this.getGraphicsCtxt(), $oPoint.getXPos(), $oPoint.getYPos(), $oPoint.getWidth(), $oPoint.getHeight(), $this.getBrush())
    Next
    _GDIPlus_GraphicsDrawImageRect($this.getGraphics(), $this.getBitmap(), 0, 0, $this.getBoardWidth(), $this.getBoardHeight())
EndFunc   ;==>_drawStage2

Func _getFoodCounter($this)
    Return $this.foodCounter
EndFunc   ;==>_getFoodCounter

Func _setFoodCounter($this, $foodCounter)
    $this.foodCounter = $foodCounter
EndFunc   ;==>_setFoodCounter

Func _setSpeedLevel($this, $iLevel)
    $this.speedLevel = $iLevel
EndFunc   ;==>_setSpeedLevel

Func _getSpeedLevel($this)
    Return $this.speedLevel
EndFunc   ;==>_getSpeedLevel

Func _getBoard($this)
    Return $this.Board
EndFunc   ;==>_getBoard

Func _runGameLoop($this)

    While 1
        If GUIGetMsg() = $GUI_EVENT_CLOSE Then ExitLoop
        If _IsPressed(25) Then
            $this.setdx(-1)
            $this.setdy(0)
        EndIf
        If _IsPressed(27) Then
            $this.setdx(1)
            $this.setdy(0)
        EndIf
        If _IsPressed(26) Then
            $this.setdx(0)
            $this.setdy(-1)
        EndIf
        If _IsPressed(28) Then
            $this.setdx(0)
            $this.setdy(1)
        EndIf

        If _IsPressed(53) Then
            $this.sleepW()
        EndIf
        $this.drawStage($this.getMap())
        Sleep($this.getSpeedLevel())
    WEnd

EndFunc   ;==>_runGameLoop

Func _getBoardWidth($this)
    Return $this.iBoardWidth
EndFunc   ;==>_getBoardWidth

Func _getBoardHeight($this)
    Return $this.iBoardHeight
EndFunc   ;==>_getBoardHeight

Func _generateFood($this)
    Local $aMap = $this.getMap()
    Local $oFood = $aMap[0]
    $oFood.setXPos(Random(10, $this.getBoardWidth() - 10))
    $oFood.setXPos(Random(10, $this.getBoardHeight() - 10))
    $this.setMap($aMap)
EndFunc   ;==>_generateFood

Func _setFood($this, $oFood)
    $this.food = $oFood
EndFunc   ;==>_setFood

Func _getFood($this)
    Return $this.food
EndFunc   ;==>_getFood

Func _getHead($this)
    Return $this.head
EndFunc   ;==>_getHead

Func _setdx($this, $dx)
    $this.dx = $dx
EndFunc   ;==>_setdx

Func _setdy($this, $dy)
    $this.dy = $dy
EndFunc   ;==>_setdy

Func _getdx($this)
    Return $this.dx
EndFunc   ;==>_getdx

Func _getdy($this)
    Return $this.dy
EndFunc   ;==>_getdy

Func _getMap($this)
    Return $this.aMap
EndFunc   ;==>_getMap

Func _setMap($this, $aMap)
    $this.aMap = $aMap
EndFunc   ;==>_setMap

Func _checkBorderCollision($this)
    Local $aMap = $this.getMap()
    Local $head = $aMap[1]
    Local $hX = $head.getXPos() + $this.getdx() * 10
    Local $hY = $head.getYPos() + $this.getdy() * 10

    If $hX <= 0 Or $hX >= $this.getBoardWidth() - 10 Or $hY <= 0 Or $hY >= $this.getBoardHeight() - 10 Then
        Return True
    EndIf

    Return False
EndFunc   ;==>_checkBorderCollision

Func _checkSelfCollision($this)
    Local $aMap = $this.getMap()
    Local $head = $aMap[1]
    Local $hX = $head.getXPos()
    Local $hY = $head.getYPos()
    Local $hW = $hX + $this.getdx() * 10
    Local $hH = $hY + $this.getdy() * 10

    For $i = 2 To UBound($aMap) - 1
        Local $segment = $aMap[$i]
        Local $segX = $segment.getXPos()
        Local $segY = $segment.getYPos()
        Local $segW = $segX + $segment.getWidth()
        Local $segH = $segY + $segment.getHeight()

        If $hX < $segW And $hW > $segX And $hY < $segH And $hH > $segY Then
            Return True
        EndIf
    Next
    Return False

EndFunc   ;==>_checkSelfCollision

Func _checkFoodCollision($this)
    Local $aMap = $this.getMap()
    Local $head = $aMap[1]
    Local $hX = $head.getXPos() + $this.getdx() * 10
    Local $hY = $head.getYPos() + $this.getdy() * 10
    Local $hW = $hX + $head.getWidth()
    Local $hH = $hY + $head.getHeight()

    Local $food = $aMap[0]
    Local $fX = $food.getXPos()
    Local $fY = $food.getYPos()
    Local $fW = $fX + $food.getWidth()
    Local $fH = $fY + $food.getHeight()

    If $hX < $fW And $hW > $fX And $hY < $fH And $hH > $fY Then
        $this.generateFood()
        Local $lastSegment = $aMap[UBound($aMap) - 1]
        Local $lastX = $lastSegment.getXPos() + $this.getdx() * 10
        Local $lastY = $lastSegment.getYPos() + $this.getdy() * 10
        MapAppend($aMap, Point($lastX, $lastY))
        $this.setMap($aMap)
        Return True
    EndIf

    Return False
EndFunc   ;==>_checkFoodCollision


Func _resetGame($this)
    Local $aMap = $this.getMap()
    For $e In $aMap
        $e = 0
    Next
    $aMap = 0

    Local $aNewMap[]
    MapAppend($aNewMap, Point())
    $this.setMap($aNewMap)

    $this.generateFood()
    MapAppend($aNewMap, Point(Random(10, $this.getBoardWidth() - 10), Random(10, $this.getBoardHeight() - 10)))
    $this.setMap($aNewMap)

    Local $aRandomDirectionTab = [-1, 0, 1]
    Local $dx = $aRandomDirectionTab[Int(Random(0, 2, 1))]
    Local $dy = $aRandomDirectionTab[Int(Random(0, 2, 1))]
    $this.setdx($dx)
    $this.setdy($dy)
    $this.setSpeedLevel(100)
    $this.setFoodCounter(0)
    $this.drawStage($aNewMap)
EndFunc   ;==>_resetGame

Func _move($this)
    Local $aMap = $this.getMap()
    Local $head = $aMap[1]
    Local $newX = $head.getXPos() + $this.getdx() * 10
    Local $newY = $head.getYPos() + $this.getdy() * 10

    If $this.checkBorderCollision() Or $this.checkSelfCollision() Then
        MsgBox(64, "Game Over", "Collision! Game Over.")
        Local $iResponse = MsgBox(36, "Restart Game", "Do you want to restart the game?")
        If $iResponse = 6 Then
            _resetGame($this)
            Return True
        Else
            $this.cleanUpResources()
            _GDIPlus_Shutdown()
            _AutoItObject_Shutdown()
            Exit
        EndIf
    EndIf

    If $this.checkFoodCollision() Then
        $this.setFoodCounter($this.getFoodCounter() + 1)
        Local $lastSegment = $aMap[UBound($aMap) - 1]
        Local $lastX = $lastSegment.getXPos() + $this.getdx() * 10
        Local $lastY = $lastSegment.getYPos() + $this.getdy() * 10
        MapAppend($aMap, Point($lastX, $lastY))

        If $this.getFoodCounter() = 4 Then
            Local $newSpeedLevel = $this.getSpeedLevel() - 20
            If $newSpeedLevel < 0 Then $newSpeedLevel = 1
            $this.setSpeedLevel($newSpeedLevel)
            $this.setFoodCounter(0)
        EndIf
        $this.generateFood()
    EndIf

    $head.setXPos($newX)
    $head.setYPos($newY)

    For $i = UBound($aMap) - 1 To 2 Step -1
        $aMap[$i].setXPos($aMap[$i - 1].getXPos())
        $aMap[$i].setYPos($aMap[$i - 1].getYPos())
    Next
    $this.setMap($aMap)
EndFunc   ;==>_move


; #FUNCTION# ====================================================================================================================
; Name ..........: _sleepW
; Description ...: Waits for keyboard input to change the direction of movement in the Snake game.
; Syntax ........: _sleepW(ByRef $this)
; Parameters ....: $this                - [in/out] A reference to the Snake object.
; Return values .: None
; Modified ......:
; Remarks .......: This function is triggered when the player presses the 'S' key in the Snake game. It waits for directional input
;                  (arrow keys) to change the movement direction of the snake. The function updates the movement direction properties
;                  of the Snake object accordingly. Note: There seems to be a crashing issue with this function that needs further investigation.
; Related .......: Snake
; Link ..........:
; Example .......: No
; ===============================================================================================================================
Func _sleepW($this)
    Do
        Do
            Sleep(100)
        Until _IsPressed(25) Or _IsPressed(26) Or _IsPressed(27) Or _IsPressed(28)
        If _IsPressed(25) Then
            $this.setdx(-1)
            $this.setdy(0)
        EndIf
        If _IsPressed(27) Then
            $this.setdx(1)
            $this.setdy(0)
        EndIf
        If _IsPressed(26) Then
            $this.setdx(0)
            $this.setdy(-1)
        EndIf
        If _IsPressed(28) Then
            $this.setdx(0)
            $this.setdy(1)
        EndIf
    Until _IsPressed(25) Or _IsPressed(26) Or _IsPressed(27) Or _IsPressed(28)
EndFunc   ;==>_sleepW

; #FUNCTION# ====================================================================================================================
; Name ..........: _cleanUpResources
; Description ...: Cleans up all resources used in the Snake game.
; Syntax ........: _cleanUpResources()
; Parameters ....: None
; Return values .: None
; Remarks .......: This function deallocates all resources used in the Snake game, including graphics resources, objects, and variables.
; Related .......: Snake
; ===============================================================================================================================
Func _cleanUpResources($this)
    ConsoleWrite("cleanUpRessources" & @CRLF)
    Local $aMap = $this.getMap()
    For $oPoint In $aMap
        $oPoint = 0
    Next
    $aMap = 0
    $this.setMap(0)
    ; Deallocate GDI+ resources
    _GDIPlus_GraphicsDispose($this.getGraphicsCtxt())
    _GDIPlus_BrushDispose($this.getBrush())
    _GDIPlus_BrushDispose($this.getFoodBrush())
    _GDIPlus_BitmapDispose($this.getBitmap())
    _GDIPlus_Shutdown()
EndFunc   ;==>_cleanUpResources






; #FUNCTION# ====================================================================================================================
; Name ..........: Point
; Description ...: Creates a Point object used in the Snake game to represent coordinates.
; Syntax ........: Point([$X = 0[, $Y = 0]])
; Parameters ....: $X                   - [optional] The X coordinate value. Default is 0.
;                  $Y                   - [optional] The Y coordinate value. Default is 0.
; Return values .: Returns a Point object with specified coordinates.
; Remarks .......: This function is used to create a Point object representing coordinates on the game board for the Snake game.
;                  It is used to manage positions of the snake segments and the food items.
; Related .......: Snake, _AutoItObject_Class
; ===============================================================================================================================
Func Point($X = 0, $Y = 0)
    Local $cPoint = _AutoItObject_Class()

    Local Const $iWidth = 10
    Local Const $iHeight = 10

    With $cPoint
        .Create()
        .AddProperty("X", $ELSCOPE_PRIVATE, $X)
        .AddProperty("Y", $ELSCOPE_PRIVATE, $Y)
        .AddProperty("iWidth", $ELSCOPE_PRIVATE, $iWidth)
        .AddProperty("iHeight", $ELSCOPE_PRIVATE, $iHeight)
        .AddMethod("setXPos", "_setXPos")
        .AddMethod("setYPos", "_setYPos")
        .AddMethod("getXPos", "_getXPos")
        .AddMethod("getYPos", "_getYPos")
        .AddMethod("setWidth", "_setWidth", True)
        .AddMethod("setHeight", "_setHeight", True)
        .AddMethod("getWidth", "_getWidth")
        .AddMethod("getHeight", "_getHeight")
        .AddDestructor("_pointDestructor")
    EndWith
    Return $cPoint.Object
EndFunc   ;==>Point

Func _setXPos($this, $iX)
    $this.X = $iX
EndFunc   ;==>_setXPos

Func _setYPos($this, $iY)
    $this.Y = $iY
EndFunc   ;==>_setYPos

Func _getXPos($this)
    Return $this.X
EndFunc   ;==>_getXPos

Func _getYPos($this)
    Return $this.Y
EndFunc   ;==>_getYPos

Func _getWidth($this)
    Return $this.iWidth
EndFunc   ;==>_getWidth

Func _getHeight($this)
    Return $this.iHeight
EndFunc   ;==>_getHeight

Func _setColor($this, $iClolor)
    $this.iColor = $iClolor
EndFunc   ;==>_setColor

Func _getColor($this)
    Return $this.iColor
EndFunc   ;==>_getColor

Func _ErrFunc($oError)
    ConsoleWrite(@ScriptName & " (" & $oError.scriptline & ") : ==> COM Error intercepted !" & @CRLF & _
            @TAB & "err.number is: " & @TAB & @TAB & "0x" & Hex($oError.number) & @CRLF & _
            @TAB & "err.windescription:" & @TAB & $oError.windescription & @CRLF & _
            @TAB & "err.description is: " & @TAB & $oError.description & @CRLF & _
            @TAB & "err.source is: " & @TAB & @TAB & $oError.source & @CRLF & _
            @TAB & "err.helpfile is: " & @TAB & $oError.helpfile & @CRLF & _
            @TAB & "err.helpcontext is: " & @TAB & $oError.helpcontext & @CRLF & _
            @TAB & "err.lastdllerror is: " & @TAB & $oError.lastdllerror & @CRLF & _
            @TAB & "err.scriptline is: " & @TAB & $oError.scriptline & @CRLF & _
            @TAB & "err.retcode is: " & @TAB & "0x" & Hex($oError.retcode) & @CRLF & @CRLF)
EndFunc   ;==>_ErrFunc


;*****************************************************************************************************************************************
Global $sNake = Snake()
$sNake.runGameLoop()
$sNake = 0
;*****************************************************************************************************************************************

_AutoItObject_Shutdown()

 

Link to comment
Share on other sites

Congrats, it works way better. The double buffering fixed the flickers and border collisions works now but self collision still doesn't work. Look what snake I managed to create. :lol:

image.png.ea27ec42178bb2ec6fd563111888fc53.png

When the words fail... music speaks.

Link to comment
Share on other sites

I've come up with a simplified version of the code where I've addressed the issue of the snake's self-collision. Additionally, you have the freedom to choose the color of the snake. This showcases the versatility of the tool I'm using.

 

#include-once
#AutoIt3Wrapper_Au3Check_Parameters=-d -w 1 -w 2 -w 3 -w 4 -w 5 -w 6
; #INDEX# =======================================================================================================================
; Title .........: Snake Game
; AutoIt Version : 3.3
; AutoItObject Version : v1.2.8.2
; Language ......: English
; Description ...: A simple Snake game implementation using AutoIt->AutoItObject.au3.
; Dependencies ..: AutoItObject.au3
; Author ........: Numeric
; ===============================================================================================================================

#include <GUIConstantsEx.au3>
#include "AutoItObject.au3"
#include <GDIPlus.au3>
#include <Misc.au3>

Global $oErrorHandler = ObjEvent("AutoIt.Error", "_ErrFunc")
_GDIPlus_Startup()
_AutoItObject_Startup()


Func Snake($iBgColor = 0xFF0000FF)

    Local $dx = Random(-1, 1, 1)
    Local $dy = Random(-1, 1, 1)

    Local $aMap[]
    MapAppend($aMap, Point(Random(10, 380, 1), Random(10, 380, 1))) ;food
    MapAppend($aMap, Point(Random(10, 380, 1), Random(10, 380, 1))) ; head

    Local $Board = GUICreate("Snake", 400, 400)
    GUISetState()
    Local $aBoardPos = WinGetClientSize($Board)
    Local $iBoardWidth = $aBoardPos[0]
    Local $iBoardHeight = $aBoardPos[1]

    Local $hGraphics = _GDIPlus_GraphicsCreateFromHWND($Board)
    Local $hBitmap = _GDIPlus_BitmapCreateFromGraphics($iBoardWidth, $iBoardHeight, $hGraphics)
    Local $hGraphicsCtxt = _GDIPlus_ImageGetGraphicsContext($hBitmap)
    _GDIPlus_GraphicsSetSmoothingMode($hGraphicsCtxt, $GDIP_SMOOTHINGMODE_HIGHQUALITY)
    Local $hBrush = _GDIPlus_BrushCreateSolid($iBgColor)
    Local $hFoodBrush = _GDIPlus_BrushCreateSolid(0xFF00FF00) ;(0x00FF00)

    Local $aGDIMap[]
    $aGDIMap["hGraphics"] = $hGraphics
    $aGDIMap["hBitmap"] = $hBitmap
    $aGDIMap["hGraphicsCtxt"] = $hGraphicsCtxt
    $aGDIMap["hBrush"] = $hBrush
    $aGDIMap["hFoodBrush"] = $hFoodBrush

    Local $sClass = _AutoItObject_Class()
    With $sClass
        .Create()
        .AddProperty("dx", $ELSCOPE_PUBLIC, $dx)
        .AddProperty("dy", $ELSCOPE_PUBLIC, $dy)
        .AddProperty("speedLevel", $ELSCOPE_PUBLIC, 100)
        .AddProperty("foodCounter", $ELSCOPE_PUBLIC, 0)
        .AddProperty("Map", $ELSCOPE_PUBLIC, $aMap)
        .AddProperty("gdiMap", $ELSCOPE_PUBLIC, $aGDIMap)
        .AddProperty("Board", $ELSCOPE_PUBLIC, $Board)
        .AddProperty("iBoardWidth", $ELSCOPE_PUBLIC, $iBoardWidth)
        .AddProperty("iBoardHeight", $ELSCOPE_PUBLIC, $iBoardHeight)

        .AddMethod("move", "_move")
        .AddMethod("drawStage", "_drawStage")
        .AddMethod("resetGame", "_resetGame")
        .AddMethod("runGameLoop", "_runGameLoop")
        .AddMethod("cleanUpResources", "_cleanUpResources")
        .AddDestructor("_cleanUpResources")
    EndWith
    Return $sClass.Object
EndFunc   ;==>Snake

Func _runGameLoop($this)
    While 1
        If GUIGetMsg() = $GUI_EVENT_CLOSE Then ExitLoop
        If _IsPressed(25) Then
            $this.dx = -1
            $this.dy = 0
        EndIf
        If _IsPressed(27) Then
            $this.dx = 1
            $this.dy = 0
        EndIf
        If _IsPressed(26) Then
            $this.dx = 0
            $this.dy = -1
        EndIf
        If _IsPressed(28) Then
            $this.dx = 0
            $this.dy = 1
        EndIf
        If _IsPressed(53) Then
            While 1
                If _IsPressed(25) Or _IsPressed(26) Or _IsPressed(27) Or _IsPressed(28) Then ExitLoop
                Sleep(100)
            WEnd
        EndIf
        $this.drawStage()
        Sleep($this.speedLevel)
    WEnd
EndFunc   ;==>_runGameLoop

Func _move($this)
    Local $aMap = $this.Map
    Local $head = $aMap[1]
    Local $newX = $head.getXPos() + $this.dx * 10
    Local $newY = $head.getYPos() + $this.dy * 10

    Local $hW = $newX + 10
    Local $hH = $newY + 10

    If $newX <= 0 Or $newX >= 390 Or $newY <= 0 Or $newY >= 390 Then
        MsgBox(64, "Game Over", "Collision! Game Over.")
        Local $iResponse = MsgBox(36, "Restart Game", "Do you want to restart the game?")
        If $iResponse = 6 Then
            $this.resetGame()
            Return True
        Else
            $this.cleanUpResources()
            _GDIPlus_Shutdown()
            _AutoItObject_Shutdown()
            Exit
        EndIf
    EndIf

    For $i = 2 To UBound($aMap) - 1
        Local $cX = $aMap[$i].getXPos()
        Local $cY = $aMap[$i].getYPos()

        Local $cW = $cX + 10
        Local $cH = $cY + 10

        If $newX < $cW And $hW > $cX And $newY < $cH And $hH > $cY Then
            MsgBox(64, "Game Over", " SELF!!! Collision! Game Over.")
            $iResponse = MsgBox(36, "Restart Game", "Do you want to restart the game?")
            If $iResponse = 6 Then
                $this.resetGame()
                Return True
            Else
                $this.cleanUpResources()
                _GDIPlus_Shutdown()
                _AutoItObject_Shutdown()
                Exit
            EndIf
        EndIf
    Next

    Local $food = $aMap[0]
    Local $fX = $food.getXPos()
    Local $fY = $food.getYPos()
    Local $fW = $fX + 10
    Local $fH = $fY + 10

    If $newX < $fW And $hW > $fX And $newY < $fH And $hH > $fY Then
        ;generate food
        $aMap[0].setXPos(Random(10, 390, 1))
        $aMap[0].setYPos(Random(10, 390, 1))

        Local $lastSegment = $aMap[UBound($aMap) - 1]
        Local $lastX = $lastSegment.getXPos() + $this.dx() * 10
        Local $lastY = $lastSegment.getYPos() + $this.dy() * 10

        MapAppend($aMap, Point($lastX, $lastY))
        $this.Map = $aMap

        $this.foodCounter += 1
        If $this.foodCounter = 4 Then
            $this.speedLevel -= 20
            If $this.speedLevel < 0 Then
                $this.speedLevel = 1
            EndIf
            $this.foodCounter = 0
        EndIf
    EndIf

    $head.setXPos($newX)
    $head.setYPos($newY)

    For $i = UBound($aMap) - 1 To 2 Step -1
        $aMap[$i].setXPos($aMap[$i - 1].getXPos())
        $aMap[$i].setYPos($aMap[$i - 1].getYPos())
    Next

EndFunc   ;==>_move

Func _drawStage($this)
    Local $gdiMap = $this.gdiMap
    Local $aMap = $this.Map

    Local $hGraphics = $gdiMap["hGraphics"]
    Local $hBrush = $gdiMap["hBrush"]
    Local $hFoodBrush = $gdiMap["hFoodBrush"]
    $this.move()

    _GDIPlus_GraphicsClear($hGraphics, 0xFF000000)
    _GDIPlus_GraphicsFillRect($hGraphics, $aMap[0].getXPos(), $aMap[0].getYPos(), 10, 10, $hFoodBrush)

    For $i = 1 To UBound($aMap) - 1
        _GDIPlus_GraphicsFillRect($hGraphics, $aMap[$i].getXPos(), $aMap[$i].getYPos(), 10, 10, $hBrush)
    Next
EndFunc   ;==>_drawStage

Func _resetGame($this)
    For $item In $this.Map
        $item = 0
    Next
    $this.Map = 0
    Local $aNewMap[]
    MapAppend($aNewMap, Point(Random(10, $this.iBoardWidth - 10), Random(10, $this.iBoardHeight - 10)))
    MapAppend($aNewMap, Point(Random(10, $this.iBoardWidth - 10), Random(10, $this.iBoardHeight - 10)))
    $this.Map = $aNewMap
    $this.dx = Random(-1, 1, 1)
    $this.dy = Random(-1, 1, 1)
    $this.speedLevel = 100
    $this.foodCounter = 0
    $this.drawStage()
EndFunc   ;==>_resetGame

Func _cleanUpResources($this)
    Local $gdiMap = $this.gdiMap
    Local $hGraphics = $gdiMap["hGraphics"]
    Local $hBrush = $gdiMap["hBrush"]
    Local $hFoodBrush = $gdiMap["hFoodBrush"]

    _GDIPlus_GraphicsDispose($hGraphics)
    _GDIPlus_BrushDispose($hBrush)
    _GDIPlus_BrushDispose($hFoodBrush)
    _GDIPlus_Shutdown()
    For $gdih In $gdiMap
        $gdih = 0
    Next
    $this.gdiMap = 0
    For $oPoint In $this.Map
        $oPoint = 0
    Next
    $this.Map = 0
    _AutoItObject_Shutdown()
EndFunc   ;==>_cleanUpResources


Func Point($X = 0, $Y = 0)
    Local $cPoint = _AutoItObject_Class()
    With $cPoint
        .Create()
        .AddProperty("X", $ELSCOPE_PRIVATE, $X)
        .AddProperty("Y", $ELSCOPE_PRIVATE, $Y)
        .AddMethod("setXPos", "_setXPos")
        .AddMethod("setYPos", "_setYPos")
        .AddMethod("getXPos", "_getXPos")
        .AddMethod("getYPos", "_getYPos")
    EndWith
    Return $cPoint.Object
EndFunc   ;==>Point

Func _setXPos($this, $iX)
    $this.X = $iX
EndFunc   ;==>_setXPos

Func _setYPos($this, $iY)
    $this.Y = $iY
EndFunc   ;==>_setYPos

Func _getXPos($this)
    Return $this.X
EndFunc   ;==>_getXPos

Func _getYPos($this)
    Return $this.Y
EndFunc   ;==>_getYPos


Func _ErrFunc($oError)
    ConsoleWrite(@ScriptName & " (" & $oError.scriptline & ") : ==> COM Error intercepted !" & @CRLF & _
            @TAB & "err.number is: " & @TAB & @TAB & "0x" & Hex($oError.number) & @CRLF & _
            @TAB & "err.windescription:" & @TAB & $oError.windescription & @CRLF & _
            @TAB & "err.description is: " & @TAB & $oError.description & @CRLF & _
            @TAB & "err.source is: " & @TAB & @TAB & $oError.source & @CRLF & _
            @TAB & "err.helpfile is: " & @TAB & $oError.helpfile & @CRLF & _
            @TAB & "err.helpcontext is: " & @TAB & $oError.helpcontext & @CRLF & _
            @TAB & "err.lastdllerror is: " & @TAB & $oError.lastdllerror & @CRLF & _
            @TAB & "err.scriptline is: " & @TAB & $oError.scriptline & @CRLF & _
            @TAB & "err.retcode is: " & @TAB & "0x" & Hex($oError.retcode) & @CRLF & @CRLF)
EndFunc   ;==>_ErrFunc


;*****************************************************************************************************************************************
Global $sNake = Snake(0xFF8B4513)
$sNake.runGameLoop()
$sNake = 0
;*****************************************************************************************************************************************

_AutoItObject_Shutdown()

 

Link to comment
Share on other sites

  1. Initialization:

    • Upon starting the game, a game window titled "Snake" is created with dimensions of 400x400 pixels.
    • The snake and food objects are initialized, and the game environment is set up.
  2. Game Loop:

    • The game runs in a continuous loop until the user closes the window.
    • During each iteration of the loop, the game performs the following actions:
  3. Snake Movement:

    • The snake automatically moves in the direction indicated by the arrow keys (up, down, left, or right).
    • The snake's movement speed is determined by the speedLevel property, which decreases as the snake eats more food.
  4. User Input:

    • The user can control the snake's direction using the arrow keys:
      • Up arrow: Move the snake upwards.
      • Down arrow: Move the snake downwards.
      • Left arrow: Move the snake to the left.
      • Right arrow: Move the snake to the right.
    • Pressing the 'S' key pauses the game until another arrow key is pressed.
  5. Food Generation:

    • Normal food items and poisoned food items are randomly generated on the game board.
    • Normal food items are represented by green rectangles, while poisoned food items are represented by red rectangles.
    • Normal food increases the snake's length and speed when eaten, while poisoned food triggers a warning message and restarts the game when eaten.
  6. Collision Detection:

    • The game checks for collisions between the snake and various elements:
      • Collision with the edges of the game board: Ends the game and prompts the user to restart.
      • Collision with itself (i.e., when the snake's head intersects with its body segments): Ends the game and prompts the user to restart.
      • Collision with food items: Determines whether the food is normal or poisoned, and adjusts the game accordingly.
  7. Game Over:

    • When a collision occurs, a message box appears indicating the cause of the collision (e.g., collision with the edge of the board or with the snake itself).
    • The user is given the option to restart the game or exit.
  8. Game Restart:

    • If the user chooses to restart the game, the game environment is reset, and a new game begins.
    • The snake's length, speed, and other parameters are reset to their initial values.
  9. Graphics:

    • The game board and its elements (snake segments and food items) are drawn using GDI+ graphics.
    • The graphics are refreshed continuously to reflect changes in the game state.

In summary, the game progresses as the snake moves around the board, eating food items and avoiding collisions. The objective is to survive as long as possible without colliding with obstacles. The game provides a challenging and entertaining experience for players.

 

Dependencies:

  • AutoItObject.au3: This script is essential for enabling object-oriented programming (OOP) in AutoIt. It allows the creation of custom classes, properties, and methods, which are utilized extensively in the Snake game implementation.
  • LinkedList.au3: This script provides the LinkedList data structure, which is used to manage the segments of the snake's body and the food items on the game board efficiently.

LinkedList Usage:

  • Managing Snake Segments: The LinkedList data structure is used to store the segments of the snake's body. Each segment is represented as an object with properties for its position (X and Y coordinates). The LinkedList allows easy addition, removal, and traversal of these segments.
  • Managing Food Items: Similarly, the LinkedList is utilized to manage the food items on the game board. Each food item is represented as an object with properties for its position and type (normal or poisoned). The LinkedList enables dynamic addition and removal of food items during the game.
  • Efficient Data Management: By using a LinkedList to store the snake segments and food items, the game can efficiently handle dynamic changes in the number of segments and food items. This data structure provides flexibility and scalability, allowing the game to adapt to different scenarios and game states.

 

 

#include-once
#AutoIt3Wrapper_Au3Check_Parameters=-d -w 1 -w 2 -w 3 -w 4 -w 5 -w 6
; #INDEX# =======================================================================================================================
; Title .........: Snake Game
; AutoIt Version : 3.3
; AutoItObject Version : v1.2.8.2
; Language ......: English
; Description ...: A simple Snake game implementation using AutoIt->AutoItObject.au3.
; Dependencies ..: AutoItObject.au3
; Author ........: Numeric
; ===============================================================================================================================

#include <GUIConstantsEx.au3>
#include "AutoItObject.au3"
#include <GDIPlus.au3>
#include <Misc.au3>
#include "LinkedList.au3"

Global $oErrorHandler = ObjEvent("AutoIt.Error", "_ErrFunc")
_GDIPlus_Startup()
_AutoItObject_Startup()

; Error handling function
Func _ErrFunc($oError)
    ConsoleWrite(@ScriptName & " (" & $oError.scriptline & ") : ==> COM Error intercepted !" & @CRLF & _
            @TAB & "err.number is: " & @TAB & @TAB & "0x" & Hex($oError.number) & @CRLF & _
            @TAB & "err.windescription:" & @TAB & $oError.windescription & @CRLF & _
            @TAB & "err.description is: " & @TAB & $oError.description & @CRLF & _
            @TAB & "err.source is: " & @TAB & @TAB & $oError.source & @CRLF & _
            @TAB & "err.helpfile is: " & @TAB & $oError.helpfile & @CRLF & _
            @TAB & "err.helpcontext is: " & @TAB & $oError.helpcontext & @CRLF & _
            @TAB & "err.lastdllerror is: " & @TAB & $oError.lastdllerror & @CRLF & _
            @TAB & "err.scriptline is: " & @TAB & $oError.scriptline & @CRLF & _
            @TAB & "err.retcode is: " & @TAB & "0x" & Hex($oError.retcode) & @CRLF & @CRLF)
EndFunc   ;==>_ErrFunc

; Define Segment class
Func Segment($X = 0, $Y = 0)
    Local $cSegment = _AutoItObject_Class()
    With $cSegment
        .Create()
        .AddProperty("X", $ELSCOPE_PRIVATE, $X)
        .AddProperty("Y", $ELSCOPE_PRIVATE, $Y)
        .AddMethod("setXPos", "_setXPos")
        .AddMethod("setYPos", "_setYPos")
        .AddMethod("getXPos", "_getXPos")
        .AddMethod("getYPos", "_getYPos")
    EndWith
    Return $cSegment.Object
EndFunc   ;==>Segment

; Methods for setting and getting X and Y positions of a Segment
Func _setXPos($this, $iX)
    $this.X = $iX
EndFunc   ;==>_setXPos

Func _setYPos($this, $iY)
    $this.Y = $iY
EndFunc   ;==>_setYPos

Func _getXPos($this)
    Return $this.X
EndFunc   ;==>_getXPos

Func _getYPos($this)
    Return $this.Y
EndFunc   ;==>_getYPos

; Define Food class
Func Food($X = 0, $Y = 0, $Type = "normal")
    Local $cFood = _AutoItObject_Class()
    With $cFood
        .Create()
        .AddProperty("X", $ELSCOPE_PUBLIC, $X)
        .AddProperty("Y", $ELSCOPE_PUBLIC, $Y)
        .AddProperty("Type", $ELSCOPE_PUBLIC, $Type)
    EndWith
    Return $cFood.Object
EndFunc   ;==>Food

; Define Snake class
Func Snake($iBackgroundColor = 0xFF0000FF)
    Local $dx = Random(-1, 1, 1)
    Local $dy = Random(-1, 1, 1)

    Local $oLinkedList = LinkedList()
    Local $oFoodList = LinkedList()

    $oLinkedList.insertAtIndex(Segment(Random(10, 380, 1), Random(10, 380, 1)), 0) ; head
    $oFoodList.insertAtIndex(Food(Random(10, 380, 1), Random(10, 380, 1), "normal"), 0) ; food
    Local $Board = GUICreate("Snake", 400, 400)
    GUISetState()
    Local $aBoardPos = WinGetClientSize($Board)
    Local $iBoardWidth = $aBoardPos[0]
    Local $iBoardHeight = $aBoardPos[1]

    Local $aGDIMap[]
    $aGDIMap["hGraphics"] = _GDIPlus_GraphicsCreateFromHWND($Board)
    $aGDIMap["hBitmap"] = _GDIPlus_BitmapCreateFromGraphics($iBoardWidth, $iBoardHeight, $aGDIMap["hGraphics"])
    $aGDIMap["hGraphicsContext"] = _GDIPlus_ImageGetGraphicsContext($aGDIMap["hBitmap"])
    $aGDIMap["hBrush"] = _GDIPlus_BrushCreateSolid($iBackgroundColor)
    $aGDIMap["hFoodBrush"] = _GDIPlus_BrushCreateSolid(0xFF00FF00)
    $aGDIMap["hPoisonedFoodBrush"] = _GDIPlus_BrushCreateSolid(0xFFFF0000)
    $aGDIMap["headBrush"] = _GDIPlus_HatchBrushCreate(4, 0xFF00FF00, $iBackgroundColor)

    Local $sClass = _AutoItObject_Class()
    With $sClass
        .Create()
        .AddProperty("dx", $ELSCOPE_PUBLIC, $dx)
        .AddProperty("dy", $ELSCOPE_PUBLIC, $dy)
        .AddProperty("speedLevel", $ELSCOPE_PUBLIC, 100)
        .AddProperty("foodCounter", $ELSCOPE_PUBLIC, 0)
        .AddProperty("SnakeSegments", $ELSCOPE_PUBLIC, $oLinkedList)
        .AddProperty("foodList", $ELSCOPE_PUBLIC, $oFoodList)
        .AddProperty("gdiMap", $ELSCOPE_PUBLIC, $aGDIMap)
        .AddProperty("Board", $ELSCOPE_PUBLIC, $Board)
        .AddProperty("iBoardWidth", $ELSCOPE_PUBLIC, $iBoardWidth)
        .AddProperty("iBoardHeight", $ELSCOPE_PUBLIC, $iBoardHeight)
        .AddProperty("ifreq", $ELSCOPE_PUBLIC, 0)
        .AddProperty("poisonedFoodDuration", $ELSCOPE_PUBLIC, 50)

        .AddMethod("move", "_move")
        .AddMethod("drawStage", "_drawStage")
        .AddMethod("resetGame", "_resetGame")
        .AddMethod("runGameLoop", "_runGameLoop")
        .AddMethod("cleanUpResources", "_cleanUpResources")
        .AddDestructor("_cleanUpResources")
    EndWith
    Return $sClass.Object
EndFunc   ;==>Snake

; Run the game loop
Func _runGameLoop($this)
    Local $isPoisonedFoodPresent = False
    Local $poisonedFoodDisappearTime = 900 ; Duration in milliseconds after which poisoned food disappears
    Local $timeCounter = TimerInit() ; Initialize the time counter

    While 1
        If GUIGetMsg() = $GUI_EVENT_CLOSE Then ExitLoop
        $this.ifreq += 1

        ; Check if poisoned food is present
        If Not $isPoisonedFoodPresent Then
            ; If no poisoned food is present, generate a new one after a delay
            If $this.ifreq >= 70 Then
                $this.foodList.insertAtEnd(Food(Random(10, 380, 1), Random(10, 380, 1), "poisoned")) ; Food
                $this.ifreq = 0
                $isPoisonedFoodPresent = True
                $timeCounter = TimerInit() ; Reset the time counter
            EndIf
        Else
            ; If poisoned food is present, check if it should disappear
            If TimerDiff($timeCounter) >= $poisonedFoodDisappearTime Then
                ; If elapsed time exceeds the poisoned food disappearance duration
                $this.foodList.remove_last_node()
                $isPoisonedFoodPresent = False ; Mark poisoned food as disappeared
            EndIf
        EndIf

        ; Check for user input to control the snake's movement
        If _IsPressed(25) Then
            $this.dx = -1
            $this.dy = 0
        EndIf
        If _IsPressed(27) Then
            $this.dx = 1
            $this.dy = 0
        EndIf
        If _IsPressed(26) Then
            $this.dx = 0
            $this.dy = -1
        EndIf
        If _IsPressed(28) Then
            $this.dx = 0
            $this.dy = 1
        EndIf
        If _IsPressed(53) Then
            ; If the S key is pressed, pause the game until another arrow key is pressed
            While 1
                If _IsPressed(25) Or _IsPressed(26) Or _IsPressed(27) Or _IsPressed(28) Then ExitLoop
                Sleep(100)
            WEnd
        EndIf

        ; Draw the game stage
        $this.drawStage()

        ; Pause for a short duration determined by the snake's speed level
        Sleep($this.speedLevel)
    WEnd
EndFunc   ;==>_runGameLoop

; Move the snake
Func _move($this)
    Local $oLinkedList = $this.SnakeSegments
    Local $head = $oLinkedList.getAtIndex(0)
    Local $newX = $head.getXPos() + $this.dx * 10
    Local $newY = $head.getYPos() + $this.dy * 10

    Local $hW = $newX + 10
    Local $hH = $newY + 10

    ; Check collisions with the edges of the screen
    If $newX <= 0 Or $newX >= $this.iBoardWidth - 10 Or $newY <= 0 Or $newY >= $this.iBoardHeight - 10 Then
        MsgBox(64, "Game Over", "Collision! Game Over.")
        Local $iResponse = MsgBox(36, "Restart Game", "Do you want to restart the game?")
        If $iResponse = 6 Then
            $this.resetGame()
            Return True
        Else
            $this.cleanUpResources()
            Exit
        EndIf
    EndIf

    ; Check collisions with the snake's body
    For $i = 1 To $oLinkedList.sizeOfLL() - 1
        Local $segment = $oLinkedList.getAtIndex($i)
        Local $cX = $segment.getXPos()
        Local $cY = $segment.getYPos()

        Local $cW = $cX + 10
        Local $cH = $cY + 10

        If $newX < $cW And $hW > $cX And $newY < $cH And $hH > $cY Then
            MsgBox(64, "Game Over", "Collision with self! Game Over.")
            $iResponse = MsgBox(36, "Restart Game", "Do you want to restart the game?")
            If $iResponse = 6 Then
                $this.resetGame()
                Return True
            Else
                $this.cleanUpResources()
                Exit
            EndIf
        EndIf
    Next

    ; Check collisions with food
    For $i = 0 To $this.foodList.sizeOfLL() - 1
        Local $food = $this.foodList.getAtIndex($i)
        Local $fX = $food.X
        Local $fY = $food.Y
        Local $fW = $fX + 10
        Local $fH = $fY + 10

        If $newX < $fW And $hW > $fX And $newY < $fH And $hH > $fY Then
            If $food.Type = "poisoned" Then
                MsgBox(64, "Game Restart", "Warning! You have eaten poisoned food! The game will restart for your safety.")
                $this.resetGame()
                Return True
            EndIf

            ; Generate new food
            $food.X = Random(10, $this.iBoardWidth - 10)
            $food.Y = Random(10, $this.iBoardHeight - 10)

            ; Add a new segment to the snake
            Local $lastSegment = $oLinkedList.getLastNode()

            Local $lastX = $lastSegment.getXPos()
            Local $lastY = $lastSegment.getYPos()
            $oLinkedList.insertAtEnd(Segment($lastX, $lastY))

            ; Update food counter and speed level
            $this.foodCounter += 1
            If $this.foodCounter = 4 Then
                $this.speedLevel -= 20
                If $this.speedLevel < 0 Then
                    $this.speedLevel = 1
                EndIf
                $this.foodCounter = 0
            EndIf
        EndIf
    Next

    ; Update positions of subsequent segments
    For $i = $oLinkedList.sizeOfLL() - 1 To 1 Step -1
        Local $currentSegment = $oLinkedList.getAtIndex($i)
        Local $previousSegment = $oLinkedList.getAtIndex($i - 1)
        $currentSegment.setXPos($previousSegment.getXPos())
        $currentSegment.setYPos($previousSegment.getYPos())
    Next

    ; Update head position
    $head.setXPos($newX)
    $head.setYPos($newY)

EndFunc   ;==>_move

; Reset the game
Func _resetGame($this)
    Local $oLinkedList = $this.SnakeSegments
    Local $oFoodList = $this.foodList

    ; Clear snake segments linked list
    $oLinkedList.clear()
    $oFoodList.clear()

    ; Generate new snake segments
    $oLinkedList.insertAtIndex(Segment(Random(10, $this.iBoardWidth - 10), Random(10, $this.iBoardHeight - 10)), 0)
    $oFoodList.insertAtIndex(Food(Random(10, $this.iBoardWidth - 10), Random(10, $this.iBoardHeight - 10)), 0)

    ; Generate new food
    Local $food = $this.foodList.getAtIndex(0)
    $food.X = Random(10, $this.iBoardWidth - 10)
    $food.Y = Random(10, $this.iBoardHeight - 10)

    ; Reset snake direction and other parameters
    $this.dx = Random(-1, 1, 1)
    $this.dy = Random(-1, 1, 1)
    $this.speedLevel = 100
    $this.foodCounter = 0
    $this.poisonedFoodDuration = 50

    ; Redraw game board
    $this.drawStage()
EndFunc   ;==>_resetGame

; Draw the game stage
Func _drawStage($this)
    Local $gdiMap = $this.gdiMap
    Local $oLinkedList = $this.SnakeSegments
    Local $oFoodList = $this.foodList

    Local $hGraphics = $gdiMap["hGraphics"]
    Local $hGraphicsCtx = $gdiMap["hGraphicsContext"]
    Local $hBrush = $gdiMap["hBrush"]
    Local $hFoodBrush = $gdiMap["hFoodBrush"]
    Local $headBrush = $gdiMap["headBrush"]
    Local $hImage = $gdiMap["hBitmap"]

    $this.move()

    ; Clear previous graphics content
    _GDIPlus_GraphicsClear($hGraphicsCtx, 0xFF000000)

    ; Draw food
    Local $foodListIterator = $oFoodList.iterator()
    While Not $foodListIterator.isDone()
        Local $food = $foodListIterator.next()
        If $food.Type = "poisoned" Then
            $hFoodBrush = $gdiMap["hPoisonedFoodBrush"]
        EndIf
        _GDIPlus_GraphicsFillRect($hGraphicsCtx, $food.X, $food.Y, 10, 10, $hFoodBrush)
    WEnd

    _GDIPlus_GraphicsFillRect($hGraphicsCtx, $oLinkedList.getAtIndex(0).getXPos(), $oLinkedList.getAtIndex(0).getYPos(), 10, 10, $headBrush)
    ; Draw each snake segment
    For $i = 1 To $oLinkedList.sizeOfLL() - 1
        Local $segment = $oLinkedList.getAtIndex($i)
        _GDIPlus_GraphicsFillRect($hGraphicsCtx, $segment.getXPos(), $segment.getYPos(), 10, 10, $hBrush)
    Next
    _GDIPlus_GraphicsDrawImageRect($hGraphics, $hImage, 0, 0, $this.iBoardWidth, $this.iBoardHeight)
EndFunc   ;==>_drawStage

; Clean up resources when game ends
Func _cleanUpResources($this)
    Local $gdiMap = $this.gdiMap

    ; Dispose graphics resources
    _GDIPlus_GraphicsDispose($gdiMap["hGraphics"])
    _GDIPlus_BrushDispose($gdiMap["hBrush"])
    _GDIPlus_BrushDispose($gdiMap["hFoodBrush"])
    _GDIPlus_BrushDispose($gdiMap["hPoisonedFoodBrush"])
    _GDIPlus_Shutdown()

    ; Set map and linked list items to zero
    For $gdih In $this.gdiMap
        $gdih = 0
    Next
    $this.gdiMap = 0

    Local $oLinkedList = $this.SnakeSegments
    $oLinkedList.clear() ; Clear snake segments linked list

    ; Shutdown AutoItObject
    _AutoItObject_Shutdown()
EndFunc   ;==>_cleanUpResources

; Create and run the snake game
Global $snakeGame = Snake()

$snakeGame.runGameLoop()
$snakeGame = 0

 

Link to comment
Share on other sites

When the snake is long enough the chance of food being spawn under the snake it's pretty high. It would be a good idea to check if the location where the food is spawn it's an empty space. Also it's very hard to eat a poison food (even if you want) if it's randomly spawn and the duration of being displayed it's so short. It would be more interesting if the food stays longer or if the poison food it's spawned in the proximity of the snake.

When the words fail... music speaks.

Link to comment
Share on other sites

I have taken note of your valuable suggestion. What do you think about this idea? : instead of making poisoned food appear, I thought about making a speed reducer appear to give the player more luck. What do you think ?

Link to comment
Share on other sites

  • 2 weeks later...

Final version: I wanted to demonstrate the constant evolution of this code since its inception to highlight the ease of use of object-oriented programming in AutoItObject.au3, in addition to the remarkable power already provided by AutoIt. This combination forms a single entity: maximum power. So, I effortlessly introduced a new feature in the game: a speed reducer, where the snake must do everything to swallow it in order to slightly slow down and catch its breath. You'll notice that adding new features doesn't affect the structure of the code, which remains easy to maintain. That's the magic of AutoItObject. I encourage you to explore this aspect in depth. You are free to develop this game, whose code is clean, modular, and easy to design and maintain.

#include-once
#AutoIt3Wrapper_Au3Check_Parameters=-d -w 1 -w 2 -w 3 -w 4 -w 5 -w 6
; #INDEX# =======================================================================================================================
; Title .........: Snake Game
; AutoIt Version : 3.3
; AutoItObject Version : v1.2.8.2
; Language ......: English
; Description ...: A simple Snake game implementation using AutoIt->AutoItObject.au3.
; Dependencies ..: AutoItObject.au3
; Author ........: Numeric
; ===============================================================================================================================

#include <GUIConstantsEx.au3>
#include "AutoItObject.au3"
#include <GDIPlus.au3>
#include <Misc.au3>
#include <WinAPISysWin.au3>
#include "LinkedList.au3"

Global $oErrorHandler = ObjEvent("AutoIt.Error", "_ErrFunc")
_GDIPlus_Startup()
_AutoItObject_Startup()

; Error handling function
Func _ErrFunc($oError)
    ConsoleWrite(@ScriptName & " (" & $oError.scriptline & ") : ==> COM Error intercepted !" & @CRLF & _
            @TAB & "err.number is: " & @TAB & @TAB & "0x" & Hex($oError.number) & @CRLF & _
            @TAB & "err.windescription:" & @TAB & $oError.windescription & @CRLF & _
            @TAB & "err.description is: " & @TAB & $oError.description & @CRLF & _
            @TAB & "err.source is: " & @TAB & @TAB & $oError.source & @CRLF & _
            @TAB & "err.helpfile is: " & @TAB & $oError.helpfile & @CRLF & _
            @TAB & "err.helpcontext is: " & @TAB & $oError.helpcontext & @CRLF & _
            @TAB & "err.lastdllerror is: " & @TAB & $oError.lastdllerror & @CRLF & _
            @TAB & "err.scriptline is: " & @TAB & $oError.scriptline & @CRLF & _
            @TAB & "err.retcode is: " & @TAB & "0x" & Hex($oError.retcode) & @CRLF & @CRLF)
EndFunc   ;==>_ErrFunc


; Define Segment class
Func Segment($X = 0, $Y = 0)
    Local $cSegment = _AutoItObject_Class()
    With $cSegment
        .Create()
        .AddProperty("X", $ELSCOPE_PRIVATE, $X)
        .AddProperty("Y", $ELSCOPE_PRIVATE, $Y)
        .AddMethod("setXPos", "_setXPos")
        .AddMethod("setYPos", "_setYPos")
        .AddMethod("getXPos", "_getXPos")
        .AddMethod("getYPos", "_getYPos")
    EndWith
    Return $cSegment.Object
EndFunc   ;==>Segment

; Methods for setting and getting X and Y positions of a Segment
Func _setXPos($this, $iX)
    $this.X = $iX
EndFunc   ;==>_setXPos

Func _setYPos($this, $iY)
    $this.Y = $iY
EndFunc   ;==>_setYPos

Func _getXPos($this)
    Return $this.X
EndFunc   ;==>_getXPos

Func _getYPos($this)
    Return $this.Y
EndFunc   ;==>_getYPos

; Define Food class
Func Food($X = 0, $Y = 0, $Type = "normal")
    Local $cFood = _AutoItObject_Class()
    With $cFood
        .Create()
        .AddProperty("X", $ELSCOPE_PUBLIC, $X)
        .AddProperty("Y", $ELSCOPE_PUBLIC, $Y)
        .AddProperty("Type", $ELSCOPE_PUBLIC, $Type)
    EndWith
    Return $cFood.Object
EndFunc   ;==>Food

; Define Snake class
Func Snake($iBackgroundColor = 0xFF0000FF)
    Local $dx = Random(-1, 1, 1)
    Local $dy = Random(-1, 1, 1)

    Local $oLinkedList = LinkedList()
    Local $oFoodList = LinkedList()

    $oLinkedList.insertAtIndex(Segment(Random(100, 280, 1), Random(100, 280, 1)), 0) ; head
    $oFoodList.insertAtIndex(Food(Random(10, 380, 1), Random(10, 380, 1), "normal"), 0) ; food
    Local $Board = GUICreate("Snake", 400, 400)
    GUISetState()
    Local $aBoardPos = WinGetClientSize($Board)
    Local $iBoardWidth = $aBoardPos[0]
    Local $iBoardHeight = $aBoardPos[1]

    Local $aGDIMap[]
    $aGDIMap["hGraphics"] = _GDIPlus_GraphicsCreateFromHWND($Board)
    $aGDIMap["hBitmap"] = _GDIPlus_BitmapCreateFromGraphics($iBoardWidth, $iBoardHeight, $aGDIMap["hGraphics"])
    $aGDIMap["hGraphicsContext"] = _GDIPlus_ImageGetGraphicsContext($aGDIMap["hBitmap"])
    $aGDIMap["hBrush"] = _GDIPlus_BrushCreateSolid($iBackgroundColor)
    $aGDIMap["hFoodBrush"] = _GDIPlus_BrushCreateSolid(0xFF00FF00)
    $aGDIMap["hPoisonedFoodBrush"] = _GDIPlus_BrushCreateSolid(0xFFFF0000)
    $aGDIMap["headBrush"] = _GDIPlus_HatchBrushCreate(4, 0xFF00FF00, $iBackgroundColor)
    $aGDIMap["speedPowerBrush"] = _GDIPlus_HatchBrushCreate(4, 0xFFD2691E, 0xFFFF00FF)

    Local $sClass = _AutoItObject_Class()
    With $sClass
        .Create()
        .AddProperty("dx", $ELSCOPE_PUBLIC, $dx)
        .AddProperty("dy", $ELSCOPE_PUBLIC, $dy)
        .AddProperty("speedLevel", $ELSCOPE_PUBLIC, 100)
        .AddProperty("foodCounter", $ELSCOPE_PUBLIC, 0)
        .AddProperty("SnakeSegments", $ELSCOPE_PUBLIC, $oLinkedList)
        .AddProperty("foodList", $ELSCOPE_PUBLIC, $oFoodList)
        .AddProperty("powerList", $ELSCOPE_PUBLIC, LinkedList())
        .AddProperty("gdiMap", $ELSCOPE_PUBLIC, $aGDIMap)
        .AddProperty("Board", $ELSCOPE_PUBLIC, $Board)
        .AddProperty("iBoardWidth", $ELSCOPE_PUBLIC, $iBoardWidth)
        .AddProperty("iBoardHeight", $ELSCOPE_PUBLIC, $iBoardHeight)
        .AddProperty("poisonedFoodManager", $ELSCOPE_PUBLIC, FoodManager())
        .AddProperty("speedPowerManager", $ELSCOPE_PUBLIC, FoodManager())
        .AddMethod("move", "_move")
        .AddMethod("drawStage", "_drawStage")
        .AddMethod("resetGame", "_resetGame")
        .AddMethod("runGameLoop", "_runGameLoop")
        .AddMethod("cleanUpResources", "_cleanUpResources")
        .AddDestructor("_cleanUpResources")
    EndWith
    Return $sClass.Object
EndFunc   ;==>Snake

; Creates and returns a FoodManager object with default properties.
; Note: Traps, power-ups, and food itself are all considered as "food" because the snake Kaa only wants to eat.
Func FoodManager()
    Local $sClass = _AutoItObject_Class()
    With $sClass
        .Create()
        .AddProperty("ifreq", $ELSCOPE_PUBLIC, 0)
        .AddProperty("iduration", $ELSCOPE_PUBLIC, 50)
        .AddProperty("isPresent", $ELSCOPE_PUBLIC, False)
        .AddProperty("disappearTime", $ELSCOPE_PUBLIC, 2000)
        .AddProperty("timeCounter", $ELSCOPE_PUBLIC, 0)
    EndWith
    Return $sClass.Object
EndFunc   ;==>FoodManager

; Run the game loop
Func _runGameLoop($this)

    $this.poisonedFoodManager.disappearTime = 4000 ; Duration in milliseconds after which poisoned food disappears
    $this.poisonedFoodManager.timeCounter = TimerInit() ; Initialize the time counter

    $this.speedPowerManager.disappearTime = 3000
    $this.speedPowerManager.timeCounter = TimerInit()

    While 1
        If GUIGetMsg() = $GUI_EVENT_CLOSE Then ExitLoop
        _WinAPI_SetWindowText($this.Board, "Score  :  " & $this.foodCounter & "  SpeedLevel  :  " & (100 - $this.speedLevel))
        $this.poisonedFoodManager.ifreq += 1
        $this.speedPowerManager.ifreq += 1

        ; Check if speed reducer is present
        If Not $this.speedPowerManager.isPresent Then
            ; If no speed reducer is present, generate a new one after a delay
            If $this.speedPowerManager.ifreq >= 90 Then
                $this.powerList.insertAtEnd(Food(Random(10, 380, 1), Random(10, 380, 1), "speedPower"))
                $this.speedPowerManager.ifreq = 0
                $this.speedPowerManager.isPresent = True
                $this.speedPowerManager.timeCounter = TimerInit()
            EndIf
        Else
            ; If speed reducer is present, check if it should disappear
            If TimerDiff($this.speedPowerManager.timeCounter) >= $this.speedPowerManager.disappearTime Then
                $this.powerList.clear() ;remove_last_node()
                $this.speedPowerManager.isPresent = False
            EndIf
        EndIf

        ; Check if poisoned food is present
        If Not $this.poisonedFoodManager.isPresent Then
            ; If no poisoned food is present, generate a new one after a delay
            If $this.poisonedFoodManager.ifreq >= 70 Then
                $this.foodList.insertAtEnd(Food(Random(10, 380, 1), Random(10, 380, 1), "poisoned")) ; Food
                $this.poisonedFoodManager.ifreq = 0
                $this.poisonedFoodManager.isPresent = True
                $this.poisonedFoodManager.timeCounter = TimerInit() ; Reset the time counter
            EndIf
        Else
            ; If poisoned food is present, check if it should disappear
            If TimerDiff($this.poisonedFoodManager.timeCounter) >= $this.poisonedFoodManager.disappearTime Then  ; If elapsed time exceeds the poisoned food disappearance duration
                $this.foodList.remove_last_node()
                $this.poisonedFoodManager.isPresent = False ; Mark poisoned food as disappeared
            EndIf
        EndIf

        ; Check for user input to control the snake's movement
        If _IsPressed(25) Then
            $this.dx = -1
            $this.dy = 0
        EndIf
        If _IsPressed(27) Then
            $this.dx = 1
            $this.dy = 0
        EndIf
        If _IsPressed(26) Then
            $this.dx = 0
            $this.dy = -1
        EndIf
        If _IsPressed(28) Then
            $this.dx = 0
            $this.dy = 1
        EndIf
        If _IsPressed(53) Then
            ; If the S key is pressed, pause the game until another arrow key is pressed
            While 1
                If _IsPressed(25) Or _IsPressed(26) Or _IsPressed(27) Or _IsPressed(28) Then ExitLoop
                Sleep(100)
            WEnd
        EndIf

        ; Draw the game stage
        $this.drawStage()

        ; Pause for a short duration determined by the snake's speed level
        Sleep($this.speedLevel)
    WEnd
EndFunc   ;==>_runGameLoop

; Move the snake
Func _move($this)
    Local $oLinkedList = $this.SnakeSegments
    Local $head = $oLinkedList.getAtIndex(0)
    Local $newX = $head.getXPos() + $this.dx * 10
    Local $newY = $head.getYPos() + $this.dy * 10

    Local $hW = $newX + 10
    Local $hH = $newY + 10

    ; Check collisions with the edges of the screen
    If $newX <= 0 Or $newX >= $this.iBoardWidth - 10 Or $newY <= 0 Or $newY >= $this.iBoardHeight - 10 Then
        MsgBox(64, "Game Over", "Collision! Game Over.")
        Local $iResponse = MsgBox(36, "Restart Game", "Do you want to restart the game?")
        If $iResponse = 6 Then
            $this.resetGame()
            Return True
        Else
            $this.cleanUpResources()
            Exit
        EndIf
    EndIf

    ; Check collisions with the snake's body
    For $i = 1 To $oLinkedList.sizeOfLL() - 1
        Local $segment = $oLinkedList.getAtIndex($i)
        Local $cX = $segment.getXPos()
        Local $cY = $segment.getYPos()

        Local $cW = $cX + 10
        Local $cH = $cY + 10

        If $newX < $cW And $hW > $cX And $newY < $cH And $hH > $cY Then
            MsgBox(64, "Game Over", "Collision with self! Game Over.")
            $iResponse = MsgBox(36, "Restart Game", "Do you want to restart the game?")
            If $iResponse = 6 Then
                $this.resetGame()
                Return True
            Else
                $this.cleanUpResources()
                Exit
            EndIf
        EndIf
    Next

    ; Check collisions with food
    For $i = 0 To $this.foodList.sizeOfLL() - 1
        Local $food = $this.foodList.getAtIndex($i)
        Local $fX = $food.X
        Local $fY = $food.Y
        Local $fW = $fX + 10
        Local $fH = $fY + 10

        If $newX < $fW And $hW > $fX And $newY < $fH And $hH > $fY Then
            If $food.Type = "poisoned" Then
                MsgBox(64, "Game Restart", "Warning! You have eaten poisoned food! The game will restart for your safety.")
                $this.resetGame()
                Return True
            EndIf

            ; Generate new food
            $food.X = Random(10, $this.iBoardWidth - 10)
            $food.Y = Random(10, $this.iBoardHeight - 10)

            ; Add a new segment to the snake
            Local $lastSegment = $oLinkedList.getLastNode()

            Local $lastX = $lastSegment.getXPos()
            Local $lastY = $lastSegment.getYPos()
            $oLinkedList.insertAtEnd(Segment($lastX, $lastY))

            ; Update food counter and speed level
            $this.foodCounter += 1
            If $this.foodCounter = 4 Then
                $this.speedLevel -= 20
                If $this.speedLevel < 0 Then
                    $this.speedLevel = 1
                EndIf
                $this.foodCounter = 0
            EndIf
        EndIf
    Next

    ; Check collisions with speedPower
    For $i = 0 To $this.powerList.sizeOfLL() - 1
        Local $timeReducer = $this.powerList.getAtIndex($i)
        Local $tX = $timeReducer.X
        Local $tY = $timeReducer.Y
        Local $tW = $tX + 10
        Local $tH = $tY + 10

        If $newX < $tW And $hW > $tX And $newY < $tH And $hH > $tY Then
            If $timeReducer.Type = "speedPower" Then
                $this.speedLevel += 20
                $this.powerList.clear() ;remove_last_node()
                $this.speedPowerManager.isPresent = False
                $this.speedPowerManager.timeCounter = TimerInit()
            EndIf
        EndIf
    Next

    ; Update positions of subsequent segments
    For $i = $oLinkedList.sizeOfLL() - 1 To 1 Step -1
        Local $currentSegment = $oLinkedList.getAtIndex($i)
        Local $previousSegment = $oLinkedList.getAtIndex($i - 1)
        $currentSegment.setXPos($previousSegment.getXPos())
        $currentSegment.setYPos($previousSegment.getYPos())
    Next

    ; Update head position
    $head.setXPos($newX)
    $head.setYPos($newY)

EndFunc   ;==>_move

; Reset the game
Func _resetGame($this)
    Local $oLinkedList = $this.SnakeSegments
    Local $oFoodList = $this.foodList
    Local $oPowerList = $this.powerList
    ; Clear snake segments linked list
    $oLinkedList.clear()
    $oFoodList.clear()
    $oPowerList.clear()

    ; Generate new snake segments
    $oLinkedList.insertAtIndex(Segment(Random(100, $this.iBoardWidth - 100), Random(100, $this.iBoardHeight - 100)), 0)
    $oFoodList.insertAtIndex(Food(Random(10, $this.iBoardWidth - 10), Random(10, $this.iBoardHeight - 10)), 0)

    ; Generate new food
    Local $food = $this.foodList.getAtIndex(0)
    $food.X = Random(10, $this.iBoardWidth - 10)
    $food.Y = Random(10, $this.iBoardHeight - 10)

    ; Reset snake direction and other parameters
    $this.dx = Random(-1, 1, 1)
    $this.dy = Random(-1, 1, 1)
    $this.speedLevel = 100
    $this.foodCounter = 0

    $this.poisonedFoodManager.ifreq = 0
    $this.poisonedFoodManager.disappearTime = 4000 ; Duration in milliseconds after which poisoned food disappears
    $this.poisonedFoodManager.timeCounter = TimerInit() ; Initialize the time counter

    $this.speedPowerManager.ifreq = 0
    $this.speedPowerManager.disappearTime = 3000
    $this.speedPowerManager.timeCounter = TimerInit()

    ; Redraw game board
    $this.drawStage()
EndFunc   ;==>_resetGame

; Draw the game stage
Func _drawStage($this)
    Local $gdiMap = $this.gdiMap
    Local $oLinkedList = $this.SnakeSegments
    Local $oFoodList = $this.foodList
    Local $oPowerList = $this.powerList

    Local $hGraphics = $gdiMap["hGraphics"]
    Local $hGraphicsCtx = $gdiMap["hGraphicsContext"]
    Local $hBrush = $gdiMap["hBrush"]
    Local $hFoodBrush = $gdiMap["hFoodBrush"]
    Local $headBrush = $gdiMap["headBrush"]
    Local $hImage = $gdiMap["hBitmap"]
    Local $speedPowerBrush = $gdiMap["speedPowerBrush"]

    $this.move()

    ; Clear previous graphics content
    _GDIPlus_GraphicsClear($hGraphicsCtx, 0xFF000000)

    ; Draw food
    Local $foodListIterator = $oFoodList.iterator()
    While Not $foodListIterator.isDone()
        Local $food = $foodListIterator.next()
        If $food.Type = "poisoned" Then
            $hFoodBrush = $gdiMap["hPoisonedFoodBrush"]
        EndIf
        _GDIPlus_GraphicsFillRect($hGraphicsCtx, $food.X, $food.Y, 10, 10, $hFoodBrush)
    WEnd

    Local $powerListIterator = $oPowerList.iterator()
    While Not $powerListIterator.isDone()
        Local $oPower = $powerListIterator.next()
        _GDIPlus_GraphicsFillRect($hGraphicsCtx, $oPower.X, $oPower.Y, 10, 10, $speedPowerBrush)
    WEnd

    _GDIPlus_GraphicsFillRect($hGraphicsCtx, $oLinkedList.getAtIndex(0).getXPos(), $oLinkedList.getAtIndex(0).getYPos(), 10, 10, $headBrush)
    ; Draw each snake segment
    For $i = 1 To $oLinkedList.sizeOfLL() - 1
        Local $segment = $oLinkedList.getAtIndex($i)
        _GDIPlus_GraphicsFillRect($hGraphicsCtx, $segment.getXPos(), $segment.getYPos(), 10, 10, $hBrush)
    Next
    _GDIPlus_GraphicsDrawImageRect($hGraphics, $hImage, 0, 0, $this.iBoardWidth, $this.iBoardHeight)
EndFunc   ;==>_drawStage

; Clean up resources when game ends
Func _cleanUpResources($this)
    Local $gdiMap = $this.gdiMap

    ; Dispose graphics resources
    _GDIPlus_GraphicsDispose($gdiMap["hGraphics"])
    _GDIPlus_BrushDispose($gdiMap["hBrush"])
    _GDIPlus_BrushDispose($gdiMap["hFoodBrush"])
    _GDIPlus_BrushDispose($gdiMap["hPoisonedFoodBrush"])
    _GDIPlus_Shutdown()

    ; Set map and linked list items to zero
    For $gdih In $this.gdiMap
        $gdih = 0
    Next
    $this.gdiMap = 0

    Local $oLinkedList = $this.SnakeSegments
    $oLinkedList.clear() ; Clear snake segments linked list

    ; Shutdown AutoItObject
    _AutoItObject_Shutdown()
EndFunc   ;==>_cleanUpResources

;====================================================
; Create and run the snake game
Global $snakeGame = Snake()
$snakeGame.runGameLoop()
$snakeGame = 0
;====================================================

 

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...