Hello.
NOTE:This entire point of this post is for educational purposes only; not to do anything malicious.
Please do note: Using bots or hacks or external 3rd party programs (even AutoIt3) is against the ToS / EULA on Diablo II / Battle.net.
Today, we're going to create a genetic algorithm with AutoIt3 for the sole purpose of logging onto a character with Diablo II.
We're going to use three include files with our script. We'll call our script "AI.au3."
#include <MsgBoxConstants.au3> ; Message box constants (for "magic numbers" for icons selected)
#include <math.au3> ; For deciding if a number is an EVEN or ODD value
#include <Array.au3> ; For _ArrayXYZ() functions
We're going to structure this after a procedural C program.
Then we're going to sit down and
design this program instead of randomly copying and pasting stuff that we found on an internet forum on the internet.
Perhaps I'll post the source code (script code?) at the very end, just so you can copy/paste it and run it/use it/whatever it is you choose to do.
Let's first address the problems and outline our basic criteria of the problem. We need to ask ourselves a lot of questions and provide ourselves with a lot of answers.
Here's a small criteria list outlining what we desire this particular *.au3 script to do:
- Select a desired character by referencing a 'character slot' (1-8) with random pathing of N number of moves at maximum (no minimum)
Sounds like a tall order to be made! Let's get to it, shall we?
We're going to need some basic data to start off with. What will we need inside of the main() function?
- Starting character position
- Target character to stop on (referenced by slot 1-8)
- An array to hold all the individual moves to be made
These sound like simple variables and not functions. Let's fill in the gaps and see what we have so far (because we're kids from 2000-2005 and demand instant gratification):
main()
Func main()
Local $curSlot = 1
Local $targetSlot = 8
Local $maxMoves = 16
Local $filteredPath
EndFunc
This looks fairly simple so far. Let's add some more complexity, shall we? We'll let our imagination fly initially.
; This function returns an array of Nth elements before ending on the "targetSlot" of the character desired
Local $path = generateRandomPath($curSlot, $targetSlot, $maxMoves)
Now we need to describe what generateRandomPath() should actually do. I think we already did that, though. I'll do it again for completeness.
- Return an array of moves to make at random (the last Nth moves should place you on the target slot)
Okay. What now? We're in a bind! We need to think about
how generateRandomPath() will generate random moves.
First: We'll need an array. That sounds in order. We'll also need to loop through this array, assigning each element a value that represents a move-to-make. Then return the array to the caller.
The data that the array should hold should be some form of encoded data that represents moves. We can use a global enumeration data-type to represent the individual moves.
Let's get crackin'!
Global Enum $MOVE_UP = 1, $MOVE_DOWN = 2, $MOVE_LEFT = 3, $MOVE_RIGHT = 4
...
Func generateRandomPath(ByRef $curSlot, Const ByRef $targetSlot, Const ByRef $maxMoves)
; Temporary array to return to the caller
Local $path[$maxMoves]
For $n = 0 To $maxMoves
; do stuff
Next
Return $path
EndFunc
Great. We have an array of Nth elements. We loop through our entire array. We return our array. Now all we need to do is modify the contents of the array.
We could just return a random value from: $MOVE_UP to $MOVE_RIGHT, but that would be boring! Watch:
For $n = 0 To $maxMoves
; Randomly assign a movement direction (this will try to run you into a wall a lot / other 'invalid' moves)
$path[$n] = Random($MOVE_UP, $MOVE_RIGHT)
Next
That's a solution, but it's too low quality for myself. I prefer to write things that actually
work. I don't like writing dirty things anymore than I like
being dirty!
We should probably write another function to deal with generating random moves. I'll call it: generateRandomMove()! It only needs to know our current slot.
This function will return a random and legal (sanitized data) move to the caller. This in turn will populate the array of random moves.
For $n = 0 To $maxMoves
; Randomly assign a movement direction (this will try to run you into a wall a lot / other 'invalid' moves)
$path[$n] = generateRandomMove($curSlot)
Next
That's better. Now we're tasked with describing what generateRandomMove($curSlot) should do. How do we prevent invalid (illegal) moves?
We can review the data we're working with. This is kind of a quirky thing to do, but hey! It works. It's math.
We have 1-8 character slots:
[1, 2, 3, 4, 5, 6, 7, 8]
[1, 2]
[3, 4]
[5, 6]
[7, 8]
We can clearly see that characters on the
left side of the wall are all
odd numbers. Those on the opposing side are
even numbers.
We can then say: For if a number is
odd: It can not travel left anymore. For if a number is
even: It cannot travel right anymore.
We must also declare that: For if a number is on the location of
2 or less: It cannot travel up. For if a number is
7 or more: It cannot travel down.
These rules can be cut up into functions themselves! We will call these:
Helper functions. We can describe a function per rule.
Global Enum $ODD_NUM = 1, $EVEN_NUM = 2
...
Func canMoveUp(Const ByRef $curSlot)
If ($curSlot > 2) Then
Return True
EndIf
Return False
EndFunc
Func canMoveDown(Const ByRef $curSlot)
If ($curSlot < 7) Then
Return True
EndIf
Return False
EndFunc
Func canMoveRight(Const ByRef $curSlot)
Local Const $retValue = _MathCheckDiv($curSlot, 2)
Switch ($retValue)
Case $ODD_NUM
If ($curSlot < 8) Then
Return True
EndIf
EndSwitch
Return False
EndFunc
Func canMoveLeft(Const ByRef $curSlot)
Local Const $retValue = _MathCheckDiv($curSlot, 2)
Switch ($retValue)
Case $EVEN_NUM
If ($curSlot > 1) Then
Return True
EndIf
EndSwitch
Return False
EndFunc
Now we have a quick way of deciding what is or is not a valid move. Let's re-examine our function body:
Func generateRandomMove(ByRef Const $curSlot)
EndFunc
Not too exciting (yet)... We should perhaps use an array to hold all valid moves, then select one randomly and return it. Sounds like a good time for yet, another function!
What should we name
this function? I'm thinking: getValidMoves()! Sounds very explicit and unambiguous.
It definitely needs to know our current standing position, so we'll pass it that data. It doesn't look like we'll need anything else.
Func getValidMoves(ByRef Const $curSlot)
EndFunc
We have a clear-cut objective within this function. It needs to return all possible moves we can take from our current position and return this array of data to the caller.
Func getValidMoves(ByRef Const $curSlot)
Local $validMoves[0] ; An empty array
If (canMoveUp($curSlot)) Then
_ArrayAdd($MOVE_UP, $validMoves)
EndIf
If (canMoveDown($curSlot)) Then
_ArrayAdd($MOVE_DOWN, $validMoves)
EndIf
; We can only move LEFT or RIGHT! Not both!
If (canMoveLeft($curSlot)) Then
_ArrayAdd($MOVE_LEFT, $validMoves)
ElseIf (canMoveRight($curSlot)) Then
_ArrayAdd($MOVE_RIGHT, $validMoves)
EndIf
Return $validMoves
EndFunc
Now we can see a little better of how generateRandomMove() might look:
Func generateRandomMove(ByRef Const $curSlot)
Local $validMoves = getValidMoves($curSlot) ; Array of possibly valid moves from our current position
EndFunc
This is excellent! So to fall back on some basic math and diagram (because everyone loves diagrams) to help explain this.
If we're currently on slot #1:
Our valid options are only: $MOVE_RIGHT and $MOVE_DOWN (slot #2 and slot #3 respectively).
The array will be populated like so: $validMoves = [ $MOVE_DOWN, $MOVE_RIGHT ]
What else is left to do in: generateRandomMove() you may wonder? Of course! We need to randomly select from the array and return it.
Func generateRandomMove(ByRef Const $curSlot)
Local $validMoves = getValidMoves($curSlot) ; Array of possibly valid moves from our current position
; Return a randomly valid move stored in our validated bucket (1 - 4)
Return $validMoves[Random(0, UBound($validMoves) - 1, 1)]
EndFunc
Then: We need to update our player's current position in "Bob's" brain (I've decided to refer to this as a human named "Bob").
For $n = 0 To $maxMoves
; Randomly assign a movement direction (this will try to run you into a wall a lot / other 'invalid' moves)
$path[$n] = generateRandomMove($curSlot)
; We need to update Bob's brain here
Next
What sounds like a good function name? I'll go with... updateCurSlotPos()! Seems logical. We will need to pass it the last made move ($path[$n]) and our current slot position:
For $n = 0 To $maxMoves
; Randomly assign a movement direction (this will try to run you into a wall a lot / other 'invalid' moves)
$path[$n] = generateRandomMove($curSlot)
updateCurSlotPos($curSlot, $path[$n])
Next
Oh wait! Wrong one. We need to create a
helper function named: updateCurSlotPos() first, huh?
Func updateCurSlotPos(ByRef $curSlot, ByRef Const $move)
Switch ($move)
Case $MOVE_UP
$curSlot -= 2
Case $MOVE_DOWN
$curSlot += 2
Case $MOVE_LEFT
$curSlot -= 1
Case $MOVE_RIGHT
$curSlot += 1
EndSwitch
EndFunc
This about wraps everything up! We've a complete script that generates a random path starting from Nth position and stops at... Oh! No! It doesn't stop on the desired slot.
We merely need to append to the array that holds our path and redirect it to our desired target slot,
then return the array!
Let's re-cap on what we have in total so far (from top to bottom):
#include <MsgBoxConstants.au3> ; Message box constants (for "magic numbers" for icons selected)
#include <math.au3> ; For deciding if a number is an EVEN or ODD value
#include <Array.au3> ; For _ArrayXYZ() functions
Global Enum $MOVE_UP = 1, $MOVE_DOWN = 2, $MOVE_LEFT = 3, $MOVE_RIGHT = 4
Global Enum $ODD_NUM = 1, $EVEN_NUM = 2
main()
Func main()
Local $curSlot = 1
Local $targetSlot = 8
Local $maxMoves = 16
Local $path = generateRandomPath($curSlot, $targetSlot, $maxMoves)
EndFunc
Func generateRandomPath(ByRef $curSlot, Const ByRef $targetSlot, Const ByRef $maxMoves)
Local $path[$maxMoves]
For $n = 0 To $maxMoves - 1
$path[$n] = generateRandomMove($curSlot)
updateCurSlotPos($curSlot, $path[$n])
Next
Return $path
EndFunc
Func generateRandomMove(ByRef Const $curSlot)
Local $validMoves = getValidMoves($curSlot)
Return Random($validMoves[0], $validMoves[UBound($validMoves) - 1])
EndFunc
Func getValidMoves(ByRef Const $curSlot)
Local $validMoves[0]
If (canMoveUp($curSlot)) Then
_ArrayAdd($validMoves, $MOVE_UP)
EndIf
If (canMoveDown($curSlot)) Then
_ArrayAdd($validMoves, $MOVE_DOWN)
EndIf
If (canMoveLeft($curSlot)) Then
_ArrayAdd($validMoves, $MOVE_LEFT)
ElseIf (canMoveRight($curSlot)) Then
_ArrayAdd($validMoves, $MOVE_RIGHT)
EndIf
Return $validMoves
EndFunc
Func updateCurSlotPos(ByRef $curSlot, ByRef Const $move)
Switch ($move)
Case $MOVE_UP
$curSlot -= 2
Case $MOVE_DOWN
$curSlot += 2
Case $MOVE_LEFT
$curSlot -= 1
Case $MOVE_RIGHT
$curSlot += 1
EndSwitch
EndFunc
Func canMoveUp(Const ByRef $curSlot)
If ($curSlot > 2) Then
Return True
EndIf
Return False
EndFunc
Func canMoveDown(Const ByRef $curSlot)
If ($curSlot < 7) Then
Return True
EndIf
Return False
EndFunc
Func canMoveRight(Const ByRef $curSlot)
Local Const $retValue = _MathCheckDiv($curSlot, 2)
Switch ($retValue)
Case $EVEN_NUM
If ($curSlot < 8) Then
Return True
EndIf
EndSwitch
Return False
EndFunc
Func canMoveLeft(Const ByRef $curSlot)
Local Const $retValue = _MathCheckDiv($curSlot, 2)
Switch ($retValue)
Case $ODD_NUM
If ($curSlot > 1) Then
Return True
EndIf
EndSwitch
Return False
EndFunc
To find the shortest path to the target slot is fairly simple. We need to know how many elements we need to add to the array first (we also need to know if we're already on it, by chance).
Round((Abs($curSlot - $targetSlot) / 2))
This will return the distance (in terms of "moves" needed to be made; which is perfect) between your current slot and the target slot.
Ready for another diagram? Nah! You can scroll up. If we're on slot #1 at the end and we're trying to get to slot #8: We need to make: 4 moves (RIGHT, DOWN, DOWN, DOWN; or any variation).
This feels so straight-forward: We only need to loop N amount of times and call on _AddArray() per random move towards the target slot.
Local $movesToTarget = (Round((Abs($curSlot - $targetSlot) / 2)) - 1)
If ($movesToTarget <> 0) Then
For $i = 0 To $movesToTarget
; Are we "above" the target slot?
If ($targetSlot < $curSlot) Then
If (canMoveUp($curSlot)) Then
_ArrayAdd($path, $MOVE_UP)
updateCurSlotPos($curSlot, $MOVE_UP)
ElseIf (canMoveLeft($curSlot) Then
_ArrayAdd($path, $MOVE_LEFT)
updateCurSlotPos($curSlot, $MOVE_LEFT)
EndIf
EndIf
; Are we "below" the target slot?
If ($targetSlot > $curSlot) Then
If (canMoveDown($curSlot)) Then
_ArrayAdd($path, $MOVE_DOWN)
updateCurSlotPos($curSlot, $MOVE_DOWN)
ElseIf (canMoveRight($curSlot)) Then
_ArrayAdd($path, $MOVE_RIGHT)
updateCurSlotPos($curSlot, $MOVE_RIGHT)
EndIf
EndIf
Next
EndIf
Add a dash of randomization to it:
_ArrayShuffle($path, Abs($maxMoves - $movesToTarget))
... Voila! We're done.
The program now looks like this (the entire thing):
#include <MsgBoxConstants.au3> ; Message box constants (for "magic numbers" for icons selected)
#include <math.au3> ; For deciding if a number is an EVEN or ODD value
#include <Array.au3> ; For _ArrayXYZ() functions
Global Enum $MOVE_UP = 1, $MOVE_DOWN = 2, $MOVE_LEFT = 3, $MOVE_RIGHT = 4
main()
Func main()
Local $curSlot = 1
Local $targetSlot = 8
Local $maxMoves = 1
Local $path = generateRandomPath($curSlot, $targetSlot, $maxMoves)
_ArrayDisplay($path, "FULL PATH")
EndFunc
Func generateRandomPath(ByRef $curSlot, Const ByRef $targetSlot, Const ByRef $maxMoves)
Local $path[$maxMoves]
For $n = 0 To $maxMoves - 1
$path[$n] = generateRandomMove($curSlot)
updateCurSlotPos($curSlot, $path[$n])
Next
If ($curSlot <> $targetSlot) Then
Local $pathToTarget[0]
Local Const $targetSide = _MathCheckDiv($targetSlot, 2)
If ($targetSide == $MATH_ISDIVISIBLE) Then
If (canMoveRight($curSlot)) Then
_ArrayAdd($pathToTarget, $MOVE_RIGHT)
updateCurSlotPos($curSlot, $MOVE_RIGHT)
EndIf
ElseIf ($targetSide == $MATH_ISNOTDIVISIBLE) Then
If (canMoveLeft($curSlot)) Then
_ArrayAdd($pathToTarget, $MOVE_LEFT)
updateCurSlotPos($curSlot, $MOVE_LEFT)
EndIf
EndIf
While ($curSlot <> $targetSlot)
; Are we "above" the target slot?
If ($targetSlot < $curSlot) Then
If (canMoveUp($curSlot)) Then
_ArrayAdd($pathToTarget, $MOVE_UP)
updateCurSlotPos($curSlot, $MOVE_UP)
EndIf
EndIf
; Are we "below" the target slot?
If ($targetSlot > $curSlot) Then
If (canMoveDown($curSlot)) Then
_ArrayAdd($pathToTarget, $MOVE_DOWN)
updateCurSlotPos($curSlot, $MOVE_DOWN)
EndIf
EndIf
WEnd
_ArrayShuffle($pathToTarget)
_ArrayConcatenate($path, $pathToTarget)
EndIf
Return $path
EndFunc
Func generateRandomMove(ByRef Const $curSlot)
Local $validMoves = getValidMoves($curSlot)
Return $validMoves[Random(0, UBound($validMoves) - 1, 1)]
EndFunc
Func getValidMoves(ByRef Const $curSlot)
Local $validMoves[0]
If (canMoveUp($curSlot)) Then
_ArrayAdd($validMoves, $MOVE_UP)
EndIf
If (canMoveDown($curSlot)) Then
_ArrayAdd($validMoves, $MOVE_DOWN)
EndIf
If (canMoveLeft($curSlot)) Then
_ArrayAdd($validMoves, $MOVE_LEFT)
ElseIf (canMoveRight($curSlot)) Then
_ArrayAdd($validMoves, $MOVE_RIGHT)
EndIf
Return $validMoves
EndFunc
Func updateCurSlotPos(ByRef $curSlot, ByRef Const $move)
Switch ($move)
Case $MOVE_UP
$curSlot -= 2
Case $MOVE_DOWN
$curSlot += 2
Case $MOVE_LEFT
$curSlot -= 1
Case $MOVE_RIGHT
$curSlot += 1
EndSwitch
EndFunc
Func canMoveUp(Const ByRef $curSlot)
If ($curSlot > 2) Then
Return True
EndIf
Return False
EndFunc
Func canMoveDown(Const ByRef $curSlot)
If ($curSlot < 7) Then
Return True
EndIf
Return False
EndFunc
Func canMoveRight(Const ByRef $curSlot)
Local Const $retValue = _MathCheckDiv($curSlot, 2)
Switch ($retValue)
Case $MATH_ISDIVISIBLE
If ($curSlot < 8) Then
Return True
EndIf
EndSwitch
Return False
EndFunc
Func canMoveLeft(Const ByRef $curSlot)
Local Const $retValue = _MathCheckDiv($curSlot, 2)
Switch ($retValue)
Case $MATH_ISNOTDIVISIBLE
If ($curSlot > 1) Then
Return True
EndIf
EndSwitch
Return False
EndFunc
Of course: This is only the portion of a script that would be utilized
after you've successfully entered the character selection screen.
This could be used on Single Player just as well as it could be used on Open Battle.net or even Battle.net itself.
This is a sample of the output with 20 maxMoves being made (starting position: slot #1; ending positon: slot #8):
[0]|4 - "MOVE_RIGHT" (1 -> 2)
[1]|3 - "MOVE LEFT" (2 -> 1)
[2]|2 - "MOVE_DOWN" (1 -> 3)
[3]|4 - "MOVE_RIGHT" (3 -> 5)
[4]|2 - "MOVE_DOWN" (5 -> 6)
[5]|1 - "MOVE UP" (6 -> 4)
[6]|2 - "MOVE_DOWN" (4 -> 6)
[7]|2 - "MOVE_DOWN" (6 -> 8)
[8]|3 - "MOVE LEFT" (8 -> 7)
[9]|1 - "MOVE UP" (7 -> 5)
[10]|4 - "MOVE_RIGHT" (5 -> 6)
[11]|1 - "MOVE UP" (6 -> 4)
[12]|1 - "MOVE UP" (4 -> 2)
[13]|3 - "MOVE LEFT" (2 -> 1)
[14]|2 - "MOVE_DOWN" (1 -> 3)
[15]|4 - "MOVE_RIGHT" (3 -> 4)
[16]|1 - "MOVE UP" (4 -> 2)
[17]|2 - "MOVE_DOWN" (2 -> 4)
[18]|1 - "MOVE UP" (4 -> 2)
[19]|2 - "MOVE_DOWN" (2 -> 4)
[20]|2 - "MOVE_DOWN" (4 -> 6)
[21]|2 - "MOVE_DOWN" (6 -> 8 (our target))