## Learning Basic For Total Beginners (BlitzMax NG)

Started by Midimaster, December 22, 2021, 18:29:29

#### Midimaster

#15
Lesson XV: Some Mazes need Bits

When you compare picture of maze boards with our maze from the last lesson, you can find those, where the walls are very thin.

Here the walls are no standalone elements in the array: they now share a field with other game elements like player or gold or hidden keys at the same time.

Here two walls and a object sharing the same field:

but how can it be that a single variable stores two or three values?

Variables are organized in BITs

A INTEGER variables consists of 32bits. We can use them indiviually to store 32 different game elements.

test this:
Code (BlitzMax) Select
`Global a:Int = 4+1Print Bin(a)`
`output:00000000000000000000000000000101`

Bin(n:Int) shows the content of a variable as BITs

You can see that the number "1" got the very right BIT and the number "4" got the third bit.
As long as you use only this numbers: 1 , 2 , 4 , 8 , 16 , 32 , 64 ,... the system works without affecting the other bits.

In our new system we would not longer define the elements like here:
`1 = Wall2 = Player3 = Gold4 = Exit`
but now this way:
` 1 = Top Wal 2 = Left Wall 4 = Player 8 = Gold16 = Exit...`

So we can store a wall and a gold (1+4) together into the same field. But we are not allowed to use mathematic operations like "+" or "-" to manipulate such bits!!!

Therefore we use...

BIT-Operations

BIT-wise OR adds this element to the variable
Code (BlitzMax) Select
`Variable = Variable | element`

BIT-wise AND checks whether this element is in the variable
Code (BlitzMax) Select
`If  (Variable & element)=element`

BIT-wise AND XOR removes this element from the the variable
Code (BlitzMax) Select
`variable = Variable & ~ element`

for Bitwise operations we need this three letters:
& = BIT AND
| = BIT OR
~ = BIT XOR
&~ = (BIT REMOVE)

We can simplify this for us by defining three functions:
Code (BlitzMax) Select
`Function Add(X:Int, Y:Int, element:Int) Board[x,y] = Board[x,y] | elementEnd Function Function Remove(X:Int, Y:Int, element:Int) Board[x,y]=Board[x,y] &~ elementEnd Function Function Check:Int(X:Int, Y:Int, element:Int) Return (Board[x,y] & element) = elementEnd Function `

New Version of Our Maze

Now we have to re-write the maze from last lesson.

Walls are now made this way:
Code (BlitzMax) Select
`' elements:' 1 = top wall' 2 = left wall' 4 = player' 8 = goldFor Local I:Int=1 To 10 Add i, 1 , 1 add i,11 , 1Next For Local I:Int=1 To 10 Add 1, i , 2 add 11,i , 2Next For Local i:Int=0 To 40 Add  Rand(1,10) , Rand(1,10)  , 1 Add  Rand(1,10) , Rand(1,10)  , 2Next `

The Drawing look like this:

Code (BlitzMax) Select
`If Check(x,y , 1) SetColor 105,80,65 DrawRect x*size , y*size-3, size, 6EndIf If Check(x,y , 2) SetColor 105,80,65 DrawRect x*size-3 , y*size, 6, sizeEndIf If Check(x,y , 4) SetColor 255,255,255 DrawImage Player, x*size , y*sizeEndIf If Check(x,y , 8) SetColor 200,150,0 DrawOval x*size+10 , y*size+10,20,20 EndIf `

And the moving of the player has changed:

Code (BlitzMax) Select
`Function FindAndMovePlayer(moveX:Int, moveY:Int) For Local y:Int= 1 To 10 For Local x:Int= 1 To 10 If Check (x,y,4) If     moveX=-1 If Check(   x,y   ,2 ) Return ElseIf moveX=1 If Check( 1+x,y   ,2 ) Return ElseIf moveY=-1 If Check(   x,y   ,1 ) Return ElseIf MoveY=1 If Check(   x,y+1 ,1 ) Return EndIf Add x+moveX ,y+moveY,4 Remove x,y, 4 Return EndIf Next Next End Function `

The whole code:

Code (BlitzMax) Select
`SuperStrictGlobal Board:Int[12,12]Graphics 800,650SetBlend alphablendGlobal Player:TImage=LoadImage("player.png")   ' elements:' 1 = top wall' 2 = left wall' 4 = player' 8 = goldFor Local I:Int=1 To 10 Add i, 1 , 1 add i,11 , 1Next For Local I:Int=1 To 10 Add 1, i , 2 add 11,i , 2Next For Local i:Int=1 To 40 Add  Rand(1,10) , Rand(1,10)  , 1 Add  Rand(1,10) , Rand(1,10)  , 2Next Add 5,5 , 4Add 6,8 , 8Global Size:Int=50Repeat Cls For Local y:Int= 1 To 11 For Local x:Int= 1 To 11 If Check(x,y , 1) SetColor 105,80,65 DrawRect x*size , y*size-3, size, 6 EndIf If Check(x,y , 2) SetColor 105,80,65 DrawRect x*size-3 , y*size, 6, size EndIf If Check(x,y , 4) SetColor 255,255,255 DrawImage Player, x*size , y*size EndIf If Check(x,y , 8) SetColor 200,150,0 DrawOval x*size+10 , y*size+10,20,20 EndIf Next Next CheckPlayer FlipUntil AppTerminate()Function CheckPlayer() If KeyHit(KEY_LEFT) FindAndMovePlayer(-1,0) ElseIf KeyHit(KEY_RIGHT) FindAndMovePlayer(+1,0) ElseIf KeyHit(KEY_UP) FindAndMovePlayer(0,-1) ElseIf KeyHit(KEY_DOWN) FindAndMovePlayer(0,+1) EndIfEnd Function Function FindAndMovePlayer(moveX:Int, moveY:Int) For Local y:Int= 1 To 10 For Local x:Int= 1 To 10 If Check (x,y,4) If     moveX=-1 If Check(   x,y   ,2 ) Return ElseIf moveX=1 If Check( 1+x,y   ,2 ) Return ElseIf moveY=-1 If Check(   x,y   ,1 ) Return ElseIf MoveY=1 If Check(   x,y+1 ,1 ) Return EndIf Add x+moveX ,y+moveY,4 Remove x,y, 4 Return EndIf Next Next End Function Function Add(X:Int, Y:Int, element:Int) Board[x,y] = Board[x,y] | elementEnd Function Function Remove(X:Int, Y:Int, element:Int) Board[x,y]=Board[x,y] &~ elementEnd Function Function Check:Int(X:Int, Y:Int, element:Int) Return (Board[x,y] & element) = elementEnd Function `

Challenge I: Snake-Game

Code a snake game. In an empty maze a short worm becomes longer and longer like a snake, while it is moving. The user trys to steer the snake, that it not touches the bounds of the maze or the sanke body.

Chess II: Agressive Pawns And A King

Expand your Chess game towards "hitting" and add the actor type "King"..

Critics to this tutorial are welcome, but please do not post here, but there:

...back from Egypt

#### Midimaster

#16
Lesson XVI: More Maze Elements
Today we will add a couple of new actors and elements to our maze. Enemies, hidden doors and their keys and food for the player. Also we need a bomb!

Before we add a lot of different elements in our maze we should stop working with numbers. The code is more readable when we use symbolic CONSTANTs:

Let us have a look on our code part, where we decided what we will draw:
`For Local y:Int= 1 To 10 For Local x:Int= 1 To 10 If Check(x,y , 1) ... If Check(x,y , 2) ... If Check(x,y , 4) ... If Check(x,y , 8) ... NextNext `

A third person cannot understand what this code does and which purpose has the 1, the 2 the 4 the 8 or the 11! How much better would be a code like this:
`For Local y:Int= 1 To 10 For Local x:Int= 1 To 10 If Check(x,y , TOP_WALL) ... If Check(x,y , LEFT_WALL) ... If Check(x,y , PLAYER) ... If Check(x,y , GOLD) ... NextNext `
Even we have an advantage in reading after our number of elements increased at the end of the day.
For more elements we now define additional variables. But we already know that their value will never change. We call this type CONSTANTs:

Code (BlitzMax) Select
`Const TOP_WALL:Int   = 1Const LEFT_WALL:Int  = 2Const THE_PLAYER:Int = 4Const GOLD:Int       = 8Const DOOR:Int       = 16Const KEY:Int        = 32Const BOMB:Int       = 64Const COOKIE:Int     =128`

Const GOLD:Int=8 defines a symbolic constant. You use it instead of a number. Constants are often written with capital letters

and you see a first effect here. Instead of writing...
`For Local I:Int=1 To 10 Add i, 1 , 1 Add i,11 , 1Next For Local I:Int=1 To 10 Add 1, i , 2 Add 11,i , 2Next `
...we now can write...
Code (BlitzMax) Select
`For Local I:Int=1 To 10 Add i, 1 , TOP_WALL Add i, 11 , TOP_WALLNext For Local I:Int=1 To 10 Add 1, i , LEFT_WALL Add 11 , i , LEFT_WALLNext `

Current state of our code:

Code (BlitzMax) Select
`SuperStrictGraphics 800,650Const TOP_WALL:Int   = 1Const LEFT_WALL:Int  = 2Const THE_PLAYER:Int = 4Const GOLD:Int       = 8Const DOOR:Int       = 16Const KEY:Int        = 32Const BOMB:Int       = 64Const COOKIE:Int     =128Global P_Image:TImage = LoadImage("player.png") Global Board:Int[12,12]For Local I:Int=1 To 10 Add i, 1 , TOP_WALL Add i, 11 , TOP_WALLNext For Local I:Int=1 To 10 Add 1, i , LEFT_WALL Add 11, i , LEFT_WALLNext For Local i:Int=0 To 40 Add  Rand(1,10) , Rand(1,10)  , TOP_WALL Add  Rand(1,10) , Rand(1,10)  , LEFT_WALLNext Add 5,5 , THE_PLAYERAdd 6,8 , GOLDGlobal Size:Int=50Repeat Cls For Local y:Int= 1 To 11 For Local x:Int= 1 To 11 If Check(x,y ,  TOP_WALL) SetColor 105,80,65 DrawRect x*size , y*size-3, size, 6 EndIf If Check(x,y , LEFT_WALL) SetColor 105,80,65 DrawRect x*size-3 , y*size, 6, size EndIf If Check(x,y , THE_PLAYER) SetColor 255,255,255 DrawImage P_Image, x*size , y*size EndIf If Check(x,y , GOLD) SetColor 200,150,0 DrawOval x*size+10 , y*size+10,20,20 EndIf Next Next CheckPlayer FlipUntil AppTerminate()Function CheckPlayer() If KeyHit(KEY_LEFT) FindAndMovePlayer(-1,0) ElseIf KeyHit(KEY_RIGHT) FindAndMovePlayer(+1,0) ElseIf KeyHit(KEY_UP) FindAndMovePlayer(0,-1) ElseIf KeyHit(KEY_DOWN) FindAndMovePlayer(0,+1) EndIfEnd Function Function FindAndMovePlayer(moveX:Int, moveY:Int) For Local y:Int= 1 To 10 For Local x:Int= 1 To 10 If Check (x,y, THE_PLAYER) If     moveX=-1 If Check(   x,y   ,LEFT_WALL ) Return ElseIf moveX=1 If Check( x+1,y   ,LEFT_WALL ) Return ElseIf moveY=-1 If Check(   x,y   ,TOP_WALL ) Return ElseIf MoveY=1 If Check(   x,y+1 ,TOP_WALL ) Return EndIf Add x+moveX ,y+moveY , THE_PLAYER Remove x,y, THE_PLAYER Return EndIf Next Next End Function Function Add(X:Int, Y:Int, element:Int) Board[x,y] = Board[x,y] | elementEnd Function Function Remove(X:Int, Y:Int, element:Int) Board[x,y]=Board[x,y] &~ elementEnd Function Function Check:Int(X:Int, Y:Int, element:Int) Return (Board[x,y] & element) = elementEnd Function  `

Some Cookies = Energie for the player

Let us define a new game rule:

The player consums energy when running through the maze. Lets say each step costs 1 energy. So he periodically needs cookies to refuel his energy level. Eating a cookie brings 10 energie.

For each new element we will add now we need always the same seven changes in our code:

Change 1: Define a Constant:

Code (BlitzMax) Select
`Const COOKIE:Int     =128`
So we can code with a symbolic constant instead of the number 128

Change 2: Define a related Player's variable:

Code (BlitzMax) Select
`Global P_Energy:Int `
In this variable we will add 10, when the player eats a cookie, and substract 1 on each step the player makes.

Code (BlitzMax) Select
`For Local i:Int=0 To 10 Add  Rand(1,10) , Rand(1,10)  , COOKIENext `
We code this below the wall creation code

Change 4: Display the element

Code (BlitzMax) Select
`...For Local y:Int= 1 To 11 For Local x:Int= 1 To 11.... If Check(x,y , COOKIE) SetColor 100,50,0 DrawOval x*size+20 , y*size+20,7,7 EndIf ....`

Change 5: Display the player's state:

Code (BlitzMax) Select
`... DrawText "ENERGY=" + P_Energy,100,5FLIP`
This displays how much energy the player still has. The best place to draw this is shortly before the FLIP

Change 6: Define what happens when the player "finds" any element at his new position:

Code (BlitzMax) Select
`Function CheckNewPlayerPosition(X:Int, Y:Int) If Check(x,y , COOKIE ) Remove x,y, COOKIE P_Energy = P_Energy+10 EndIf  Return`
Therefore we add a new function CheckNewPlayerPosition() which we call each time the player moved.

Change 7:  Substract the energie

Code (BlitzMax) Select
`Function FindAndMovePlayer(moveX:Int, moveY:Int) For Local y:Int= 1 To 10 For Local x:Int= 1 To 10... P_Energy = P_Energy-1     '                    <------ HERE If P_Energy<1 Return        '                    <----- stop the player when empty Add x+moveX ,y+moveY , THE_PLAYER Remove x,y, THE_PLAYER CheckNewPlayerPosition ( x+moveX ,y+moveY)   '  <-------- NEW AFTER-MOVE-FUNCTION Return ...`
The best point will be where we already exchanged the the player's field. This is also the best point for call the CheckNewPlayerPosition() function

So our new version looks like this:

Code (BlitzMax) Select
`SuperStrictGraphics 800,650Const TOP_WALL:Int   = 1Const LEFT_WALL:Int  = 2Const THE_PLAYER:Int = 4Const GOLD:Int       = 8Const DOOR:Int       = 16Const KEY:Int        = 32Const BOMB:Int       = 64Const COOKIE:Int     =128Global P_Image:TImage = LoadImage("player.png") Global P_Energy:Int  = 10Global Board:Int[12,12]For Local I:Int=1 To 10 Add i, 1 , TOP_WALL Add i, 11 , TOP_WALLNext For Local I:Int=1 To 10 Add 1, i , LEFT_WALL Add 11, i , LEFT_WALLNext For Local i:Int=0 To 40 Add  Rand(1,10) , Rand(1,10)  , TOP_WALL Add  Rand(1,10) , Rand(1,10)  , LEFT_WALLNext For Local i:Int=0 To 10 Add  Rand(1,10) , Rand(1,10)  , COOKIENext Add 5,5 , THE_PLAYERAdd 6,8 , GOLDGlobal Size:Int=50Repeat Cls For Local y:Int= 1 To 11 For Local x:Int= 1 To 11 If Check(x,y ,  TOP_WALL) SetColor 105,80,65 DrawRect x*size , y*size-3, size, 6 EndIf If Check(x,y , LEFT_WALL) SetColor 105,80,65 DrawRect x*size-3 , y*size, 6, size EndIf If Check(x,y , THE_PLAYER) SetColor 255,255,255 DrawImage P_Image, x*size , y*size EndIf If Check(x,y , GOLD) SetColor 200,150,0 DrawOval x*size+10 , y*size+10,20,20 EndIf If Check(x,y , COOKIE) SetColor 100,50,0 DrawOval x*size+20 , y*size+20,7,7 EndIf Next Next CheckPlayer SetColor 255,255,0 DrawText "ENERGY=" + P_Energy,100,5 If P_Energy<1 DrawText " G A M E   O V E R   ! ! ! ! ! ! ", 300,5 EndIf FlipUntil AppTerminate()Function CheckPlayer() If KeyHit(KEY_LEFT) FindAndMovePlayer(-1,0) ElseIf KeyHit(KEY_RIGHT) FindAndMovePlayer(+1,0) ElseIf KeyHit(KEY_UP) FindAndMovePlayer(0,-1) ElseIf KeyHit(KEY_DOWN) FindAndMovePlayer(0,+1) EndIfEnd Function Function FindAndMovePlayer(moveX:Int, moveY:Int) For Local y:Int= 1 To 10 For Local x:Int= 1 To 10 If Check (x,y, THE_PLAYER) If     moveX=-1 If Check(   x,y   ,LEFT_WALL ) Return ElseIf moveX=1 If Check( x+1,y   ,LEFT_WALL ) Return ElseIf moveY=-1 If Check(   x,y   ,TOP_WALL ) Return ElseIf MoveY=1 If Check(   x,y+1 ,TOP_WALL ) Return EndIf P_Energy = P_Energy-1 If P_Energy<1 Return Add x+moveX ,y+moveY , THE_PLAYER Remove x,y, THE_PLAYER CheckNewPlayerPosition ( x+moveX ,y+moveY) Return EndIf Next Next End Function Function CheckNewPlayerPosition(X:Int, Y:Int) If Check(x,y , COOKIE ) Remove x,y, COOKIE P_Energy=P_Energy+10 EndIf  End Function Function Add(X:Int, Y:Int, element:Int) Board[x,y] = Board[x,y] | elementEnd Function Function Remove(X:Int, Y:Int, element:Int) Board[x,y]=Board[x,y] &~ elementEnd Function Function Check:Int(X:Int, Y:Int, element:Int) Return (Board[x,y] & element) = elementEnd Function `

Challenge I : PacMan

Write a pacman game. Player is walking through a maze and eating pills, while ghoosts are hunting him.

Challenge II : Expand Chess

Critics to this tutorial are welcome, but please do not post here, but there:

...back from Egypt

#### Midimaster

#17
Lesson XVII: More Maze Elements II

Doors and Keys

New game rule:
The maze has closed rooms with a door to enter them. The player cannot open the door until he found a key.

So today we have to add two new game elements and one new player's variable which can transport a key until we need it.

This are our seven steps:

Change 1: Define the Constants:

Code (BlitzMax) Select
`Const DOOR:Int       = 16Const KEY:Int        = 32`

Change 2: Define a related Player's variable:

Code (BlitzMax) Select
`Global P_Keys:Int     =  0Global Key_Image:TImage=LoadImage("key.png")`
additional we need to load the image of a key for displaying it in the maze (see attachment)

Change 3: Bring a door and a key into the maze:

Code (BlitzMax) Select
`Add 4,8 , KEYAdd 11,4 , DOOR`

Change 4: Display the elements

Code (BlitzMax) Select
`If Check(x,y , DOOR) SetColor 105,180,65 DrawRect x*size-3 , y*size, 6, sizeEndIf If Check(x,y , KEY) SetColor 255,255,255 DrawImage Key_Image, x*size , y*size EndIf `

Change 5: Display the player's state:

Code (BlitzMax) Select
` DrawText "YOU HAVE " + P_Keys + " KEY ", 200,5`

Change 6: Define what happens when the player "finds" the elements:

Code (BlitzMax) Select
`Function CheckNewPlayerPosition(X:Int, Y:Int) ... If Check(x,y , KEY ) Remove x,y, KEY P_Keys = P_Keys+1 EndIf  If Check(x+1,y , DOOR ) If P_Keys>0 Remove x+1,y, DOOR Remove x+1,y, LEFT_WALL P_Keys = P_Keys-1 EndIf EndIf  End Function `
dont forget to remove also the wall in this field!

Change 7: already done in change 6

Additional Change 8: leave the level when the player exits the maze

Code (BlitzMax) Select
`Function CheckNewPlayerPosition(X:Int, Y:Int) If x=11 End ...End Function `
The player can only reach column 11, when he stepped through the exit door. So "beeing on 11" is a good signal for "beeing ready".

Our Wall-System

Some words about our wall system... It looks like we have left and right walls and also top and bottom walls. But we defined only LEFT_WALL and TOP_WALL. So, how did we get the right walls?

The trueth is....

The right walls are in truth LEFT_WALLs of the right neighbor field. Also the bottom walls, they are the TOP_WALL of the lower neighbor field.

The neighbor walls next to the players at X,Y can be found at:
`left wall     :  Board[x,y]top wall     :  Board[x,y]right wall   :  Board[x+1,y]bottom wall:  Board[x,y+1] `

This is the reason why we check the neighbor walls like this:
`left wall   : Check(  x,y  ,LEFT_WALL)top wall    : Check(  x,y  ,TOP_WALL )right wall  : Check(1+x,y  ,LEFT_WALL) bottom wall : Check(  x,y+1,TOP_WALL ) `

These neighbor-checks are always a reason for runtime errors. As long as the player is not at the bounds of our maze everything works fine. But what happen if we put him at one of the the most right fields [11,0] to [11,11]?

The position is okay. The player is still inside the maze. but if we would now check the neighbors, we would also check the right neighbor at [12,...] and this fires a runtime error, because we are out of our array-dimensions.

This means we have to prevent, that the player can never reach the rows 0 and 11 and also never the columns 0 and 11. Or in other words, we have to define  mazes 2 fields bigger than we want to use them. That is the reason, why we only play inside the fields 1 to 10, while our maze is dimensioned from 0 to 11. With this security buffers we can asks all neighbors of all walkable field.

Random without Random

If you start the game again and again you get each time completely different walls in the game. This is because we use the RAND() function for creating the walls. Also your end user would see a complete unknown situation. This is funny, but can cause a unplayable game. Think about a situation the the player is (randomly) sourrounded by 4 walls! He cannot walk.

As a game designer you want to present the user a "defined" random-build maze, where you ensure, that it is 100% playable. Therefore we have the command SeedRnd()

SeedRnd() Seed:Int repeats a random-sequence defined by a seed-value.
RndSeed:Int() shows you the Seed from the current random-sequence.

As a developer you do not define a seed, but print the current seed. If you like the resulting maze, you note down the RndSeed() value and use this for the users version:

Developers version:

Code (BlitzMax) Select
`SuperStrictGraphics 800,650' SeedRnd 34567Print "This Seed=" + RndSeed()....`

Users version:

Code (BlitzMax) Select
`SuperStrictGraphics 800,650SeedRnd 34567Print "This Seed=" + RndSeed()....`

This way I found out, that 34567 always produces a nice looking and playable maze.

Challenge I : Add A Bomb

New game rule:
The player can collect a bomb. With a bomb he can blast away upto 4 wall next to him.

This needs exactly the same 7 steps like "key". Additional you need to define a KeyHit(), where the user can "throw" the bomb.

Challenge II : Tetris

Write a maze game, where 4-block-objects fall from the top unil they meet other objects. The user can turn and move the objects. Ff a row at he bottom gets "full" it disappears.

Critics to this tutorial are welcome, but please do not post here, but there:

...back from Egypt

#### Midimaster

#18
Lesson XVIII: Enemies will find you

Today we have our last chapter of mazes. We define enemies and how they search the player.

Enemies

we follow the same 7 step procedure to establish an element "enemy":

Change 1: Define the Constants:

Code (BlitzMax) Select
`Const ENEMY:Int       = 256.....Global Enemy_Image:TImage=LoadImage("enemy.png")`

Of course we have to add also a image of an enemy.

Change 2: Define a related Player's variable:

not necessary, because if the enemy finds the player the player is dead immediately.

Change 3: Bring some enemies into the maze:

Code (BlitzMax) Select
`Add 1,1 , ENEMYAdd 1,10 , ENEMYAdd 10,10 , ENEMY`

Change 4: Display the elements

Code (BlitzMax) Select
`If Check(x,y , ENEMY) SetColor 255,255,255 DrawImage Enemy_Image, x*size , y*size EndIf `

Change 5: Display the player's state:

not necessary, because if the enemy finds the player the player is dead immediately.

Change 6: Define what happens when the player "meets" the elements:

Code (BlitzMax) Select
`Function CheckNewPlayerPosition(X:Int, Y:Int) ... If Check(x,y , ENEMY ) P_Energy=0 EndIf  End Function `

This is easy, we set the players energy to 0 and the player is dead.

Additional Change 8: Move the enemies

This is a the only new topic. The best point to move the enemies is once a FLIP in the main loop. We add a new function FindAndMoveEnemies() in the line before CheckPlayer

Code (BlitzMax) Select
`... Next Next FindAndMoveEnemies()   '   <------ HERE CheckPlayer SetColor 255,255,0 ... FlipUntil AppTerminate()  `

We can immediately add this function. The fist part allows ony to move the enemies every 500msec. The second part scan the maze to find all enemies.
Code (BlitzMax) Select
`Function FindAndMoveEnemies() Global EnemyTime:Int If EnemyTime>MilliSecs() Return EnemyTime=MilliSecs()+500 For Local y:Int= 0 To 10 For Local x:Int= 0 To 10 If Check (x,y,ENEMY) MoveEnemy(x,y) EndIf Next Next End Function   `

If you add a GLOBAL variable inside a function it works like a GLOBAL variable, but it is only known inside the function. We use this to store a timestamp for the enemies' intervalls.

Current version of the game:

Code (BlitzMax) Select
`SuperStrictGraphics 800,650SeedRnd 34567Print "This Seed=" + RndSeed()Const TOP_WALL:Int   = 1Const LEFT_WALL:Int  = 2Const THE_PLAYER:Int = 4Const GOLD:Int       = 8Const DOOR:Int       = 16Const KEY:Int        = 32Const BOMB:Int       = 64Const COOKIE:Int     =128Const ENEMY:Int       = 256Global P_Image:TImage = LoadImage("player.png") Global P_Energy:Int  = 10Global P_Keys:Int     =  0Global Key_Image:TImage=LoadImage("key.png")Global bomb_Image:TImage=LoadImage("bomb.png")Global Enemy_Image:TImage=LoadImage("enemy.png")Global Board:Int[12,12]For Local I:Int=1 To 10 Add i, 1 , TOP_WALL Add i, 11 , TOP_WALLNext For Local I:Int=1 To 10 Add 1, i , LEFT_WALL Add 11, i , LEFT_WALLNext For Local i:Int=0 To 20 Add  Rand(1,10) , Rand(1,10)  , TOP_WALL Add  Rand(1,10) , Rand(1,10)  , LEFT_WALLNext For Local i:Int=0 To 10 Add  Rand(1,10) , Rand(1,10)  , COOKIENext Add 5,5 , THE_PLAYERAdd 6,8 , GOLDAdd 4,8 , KEYAdd 11,4 , DOORAdd 1,1 , ENEMYAdd 1,10 , ENEMYAdd 10,10 , ENEMYGlobal Size:Int=50Repeat Cls For Local y:Int= 1 To 11 For Local x:Int= 1 To 11 If Check(x,y ,  TOP_WALL) SetColor 105,80,65 DrawRect x*size , y*size-3, size, 6 EndIf If Check(x,y , LEFT_WALL) SetColor 105,80,65 DrawRect x*size-3 , y*size, 6, size EndIf If Check(x,y , THE_PLAYER) SetColor 255,255,255 DrawImage P_Image, x*size , y*size EndIf If Check(x,y , GOLD) SetColor 200,150,0 DrawOval x*size+10 , y*size+10,20,20 EndIf If Check(x,y , COOKIE) SetColor 100,50,0 DrawOval x*size+20 , y*size+20,7,7 EndIf If Check(x,y , DOOR) SetColor 105,180,65 DrawRect x*size-3 , y*size, 6, size EndIf If Check(x,y , KEY) SetColor 255,255,255 DrawImage key_Image, x*size , y*size EndIf If Check(x,y , ENEMY) SetColor 255,255,255 DrawImage Enemy_Image, x*size , y*size EndIf Next Next FindAndMoveEnemies() CheckPlayer SetColor 255,255,0 DrawText "ENERGY=" + P_Energy,100,5 If P_Energy<1 DrawText " G A M E   O V E R   ! ! ! ! ! ! ", 350,5 SetClsColor 150,0,0 EndIf DrawText "YOU HAVE " + P_Keys + " KEY ", 200,5 FlipUntil AppTerminate()Function CheckPlayer() If KeyHit(KEY_LEFT) FindAndMovePlayer(-1,0) ElseIf KeyHit(KEY_RIGHT) FindAndMovePlayer(+1,0) ElseIf KeyHit(KEY_UP) FindAndMovePlayer(0,-1) ElseIf KeyHit(KEY_DOWN) FindAndMovePlayer(0,+1) EndIfEnd Function Function FindAndMovePlayer(moveX:Int, moveY:Int) For Local y:Int= 1 To 10 For Local x:Int= 1 To 10 If Check (x,y,THE_PLAYER) If     moveX=-1 If Check(   x,y   ,LEFT_WALL ) Return ElseIf moveX=1 If Check( x+1,y   ,LEFT_WALL ) Return ElseIf moveY=-1 If Check(   x,y   ,TOP_WALL ) Return ElseIf MoveY=1 If Check(   x,y+1 ,TOP_WALL ) Return EndIf P_Energy = P_Energy-1 If P_Energy<1 Return Add x+moveX ,y+moveY , THE_PLAYER Remove x,y, THE_PLAYER CheckNewPlayerPosition ( x+moveX ,y+moveY) Return EndIf Next Next End Function Function FindAndMoveEnemies() Global EnemyTime:Int If EnemyTime>MilliSecs() Return EnemyTime=MilliSecs()+500 For Local y:Int= 0 To 10 For Local x:Int= 0 To 10 If Check (x,y,ENEMY) MoveEnemy(x,y) EndIf Next Next End Function   Function MoveEnemy(X:Int, y:Int) 'part I decision: Local direction:Int=Rand(1,4) 'part II check possible? If     direction=1 If Check(   x,y   ,LEFT_WALL ) Return Add x-1 ,y , ENEMY Remove x,y, ENEMY ElseIf direction=2 If Check( x+1,y   ,LEFT_WALL ) Return Add x+1 ,y , ENEMY Remove x,y, ENEMY ElseIf direction=3 If Check(   x,y   ,TOP_WALL ) Return Add x ,y-1 , ENEMY Remove x,y, ENEMY ElseIf direction=4 If Check(   x,y+1 ,TOP_WALL ) Return Add x ,y+1 , ENEMY Remove x,y, ENEMY EndIf If check(p_X , p_Y, ENEMY)=True P_Energy=0 EndIf  End Function Function CheckNewPlayerPosition(X:Int, Y:Int) If x=11 End If Check(x,y , COOKIE ) Remove x,y, COOKIE P_Energy = P_Energy+10 EndIf If Check(x,y , KEY ) Remove x,y, KEY P_Keys = P_Keys+1 EndIf  If Check(x+1,y , DOOR ) If P_Keys>0 Print p_keys Remove x+1,y, DOOR Remove x+1,y, LEFT_WALL P_Keys = P_Keys-1 EndIf EndIf If Check(x,y , ENEMY ) P_Energy=0 EndIf  End Function Function Add(X:Int, Y:Int, element:Int) Board[x,y] = Board[x,y] | elementEnd Function Function Remove(X:Int, Y:Int, element:Int) Board[x,y]=Board[x,y] &~ elementEnd Function Function Check:Int(X:Int, Y:Int, element:Int) Return (Board[x,y] & element) = elementEnd Function   `

Moving an enemy

By Random

The easy way to move an enemy is to do it by random. This moves will look very uncoordinated.
We need only one code line:
Code (BlitzMax) Select
`Function MoveEnemy(X:Int, y:Int) 'part I decision: Local direction:Int=Rand(1,4) 'part II check possible? If     direction=1 If Check(   x,y   ,LEFT_WALL ) Return Add x-1 ,y , ENEMY Remove x,y, ENEMY ElseIf direction=2 If Check( x+1,y   ,LEFT_WALL ) Return Add x+1 ,y , ENEMY Remove x,y, ENEMY ElseIf direction=3 If Check(   x,y   ,TOP_WALL ) Return Add x ,y-1 , ENEMY Remove x,y, ENEMY ElseIf direction=4 If Check(   x,y+1 ,TOP_WALL ) Return Add x ,y+1 , ENEMY Remove x,y, ENEMY EndIf If check(p_X , p_Y, ENEMY)=True P_Energy=0 EndIf End Function `
the second part is the check whether this new field would be free. It will be the same for all following moving strategies

Move by Finding the player

first step is to store the players last position in two GLOBAL variables: p_x:int and p_y:int . We can do this in the function CheckNewPlayerPosition():
Code (BlitzMax) Select
`Function CheckNewPlayerPosition(X:Int, Y:Int) P_X = X  '   <---- NEW P_Y = Y  '   <---- NEW If x=11 End ...`
(do not forget to define the two new variables at the top of our code)

Now the enemies can prefer the direction to the player:

Code (BlitzMax) Select
` 'part I decision: Local direction:Int Local random:Int=Rand(1,4) If Random=1 If p_X>X Then direction=2 ElseIf Random=2 If p_X<X Then direction=1 ElseIf Random=3 If p_Y>Y Then direction=4 ElseIf Random=4 If p_Y<Y Then direction=3 EndIf 'part II check possible? .....  `
A random part selects to check one of the four possible constellations and then decide to move in this direction. The resulting moves already make the impression of dangerous enemies. but if the move into to dead end corridor in our maze they are lost.

Combination of Random and Search

This make the enemies moving "crazy". Sometime they are straight, sometime they do stupid things. The random produces 8 states, only 4 are intelligent and may change the random direction, the other 4 keep the random direction.

Code (BlitzMax) Select
`Function MoveEnemy(X:Int, y:Int) 'part I decision: Local direction:Int = Rand(1,4) Local random:Int = Rand(1,8) If Random=1 If p_X>X Then direction=2 ElseIf Random=2 If p_X<X Then direction=1 ElseIf Random=3 If p_Y>Y Then direction=4 ElseIf Random=4 If p_Y<Y Then direction=3 EndIf 'part II check possible?...`

Challenge I: PacMan with Intelligence

Try to improve your PacMan game. Add a finding algorithm for the ghoosts.

Challenge II: Maze Editor.

Try to write an editor where we can "build" new mazes by secting a element typ, then move it into the maze. [/b]

Challenge III: Pseudo 3D Maze.

Try to write a algorithm that displays a active "wall" field in a maze as a 3D-Cube. Do not use a 3D-Api, but teach yourself the command DrawPoly()

Critics to this tutorial are welcome, but please do not post here, but there:

...back from Egypt

#### Midimaster

#19
Lesson XIX: User Text Input: Keycodes ASCIIs &  Strings

Today we will have a look on possibilties to enter texts or numbers by the user. Or to enter text via files. Also saving text to files will be our theme. Therefor we need more knowledge about STRINGS.

KeyCodes Or ASCII?

The BlitzMax keyboard related commands can deliver two different things. Keycodes or ASCII. Each key-button has a number and we can ask it. We did this already with the commands KEYHIT() and KEYDOWN(). That is called the KeyCode(). They are often used in games to manage the game features.

On the other side you know, that on every key there is printed more than one letter or sign. With a combination of the key together with SHIFT or ALT or CTRL you can access them. This approach delivers ASCII. We use them to enter names and write text.

The Keyboard sends KeyCodes

As we already know we can check Keyboard keys with KEYHIT(KEY_CODE). This function returns TRUE or FALSE depending whether the users pressed the key or not. But what happens if we do not check only one KeyCode but all 255 avaiable?

Code (BlitzMax) Select
`SuperStrict Graphics 800,600Repeat Cls Local value:Int = CheckAllKeys() If value>0 Print "Key pressed = " + value EndIf FlipUntil AppTerminate()Function CheckAllKeys:Int() For Local i:Int=1 To 255 If KeyHit(i) Return i Next Return 0End Function `

This function continously returns zero if nothing happens.

But what happens if the user presses any key? As it checks all 255 theoretic avaiable KeyCodes in a loop it will "find" the KeyCode that report TRUE. In this moment the function reports the i,which caused the success and so we discover the KeyCode of this user key. Test it and play with it.

The Keyboard sends ASCII

To return the letters printed on a keyboard key we use the function GetChar(). Now weget back three different values for the same key, depending whether we pressed SHIFT or ALT or CTRL in combination with this key:

Code (BlitzMax) Select
`SuperStrict Graphics 800,600Global letter:IntRepeat Cls Local value:Int = GetChar() If value>0 letter=value EndIf DrawText "ASCII = " + letter , 100 , 100 FlipUntil AppTerminate()`

This function continously returns zero if nothing happens.

But what happens if the user presses any key? Then it returns the ASCII-code related to the key. Test it and play with it.

GetChar()  returns the ASCII of a key or zero if nothing happened.

ASCII-Codes

The functions GetChar() returns numbers from 1 to 255. This are respresentatives for the letters. We can use those numbers directly in STRINGS.

Here are some important ASCIIs:

`ASCI       LETTERS           Description--------------------------------------------------65 -  90   A B C .. X Y Z    from 65 to 90 are all capital letter of the ABC97 - 122   a b c .. x y z    from 97 to 122 are all lower case of the ABC48 -  57   0 1 2 .. 7 8 9    the 10 digits from  "0" to "9"`

Write this extended code and save it to a project folder.

Code (BlitzMax) Select
`SuperStrict Graphics 800,600Global MyFont:TimageFont=LoadImageFont("arial.ttf",140)SetImageFont MyFontGlobal letter:IntRepeat Cls Local value:Int = GetChar() If value>0 Print "Key pressed = " + value + " -> character:"  +Chr(value) letter=value EndIf DrawText Chr(letter),100,100 FlipUntil AppTerminate()`

Chr(value:Int)  converts a ASCII-number into a String.

MyFont:TimageFont  TImageFont is a variable type for Fonts in BlitzMax

LoadImageFont("arial.ttf",40)  Loads a TrueTypeFont from your project folder into a variable TImageFont. The second parameter defines the size of the letters on the screen.

SetImageFont MyFont  Forces DrawText() now to use this font. You can change this as often as you want.

A Better Font for DrawText

A "Font" is an image collection of letters to draw them with DrawText(). The default BlitzMax Font contains only a reduced number of letters(signs). So this is the best opportunity to learn how to use better fonts.

You already have a lot of Fonts on your computer. You can use them in BlitzMax by copying them into your project folder. All Windows fonts are in "C:\Windows\Fonts\". For BlitzMax we can use only True-Type-Fonts, you recognize them by the extension ".TTF" Be careful to really "copy" your font and not "move" it!  For our todays example we need the Font arial.ttf.

Strings Are Arrays?
Perhaps you think that STRINGS are harmless variable type? But STRINGs can more: STRINGS can have the size of one byte upto millions of byte. So I would more compare them with ARRAYS. And really!!! A STRING is more like an array than like a variable.

String do not save "words", but all letters (of our word) as ASCII values in an array. Try this:
Code (BlitzMax) Select
`Global Word:String= "ABCDEFGHIJ"For Local i:Int=0 Until Word.Length Print Word[i]Next `

This word[0] with its brackets look like an array? The numbers you will see are the ASCII-Numbers of the letters (or as we say " of the characters")

new:
FOR I... UNTIL array.length
finishes the loop 1 step before it reaches the value of array.Length. An array of 10 elements contains the elements 0...9. so we need to stop a loop not at 10 but already at 9! with a FOR..TO..loop we ould run upto 10!

word[0] is called a SLICE. It a way to handle STRINGS with brackets like ARRAYS.

Try this:

Code (BlitzMax) Select
`Global Word:String= "ABCDEFGHIJ"Word[2]=57Print word`

It cuts our the "C" (third letter: [2] ! ) and replaces it by a "9" (ASCII=57)

Write an app that displays a complete name in big letters on the screen. When the user presses RETURN it start new with an empty screen

Challenge II: Diffrent text size

Write an app that displays two texts in different sizes and color. One text should rotate.

Challenge II: Write a calculator app

Write an app that displays a calculator. The user can enter the number and + - * and / and on ENTER it displays the result

Critics to this tutorial are welcome, but please do not post here, but there:

...back from Egypt

QuoteThe keyboards can deliver two different things. Keycodes or ASCII.

NO Completely wrong and factually wrong too!

Keycodes are the internal way of interfacing with the event system - it sends codes and these are then translated by the language into keycodes
These keycodes are not fixed - so they could be anything. but are usually referenced via something sensible. E.G.

KEY_A should generally equal A.

But. This is also not strictly true as A and a are both KEY_A. to get the other variants you have to track (or the language tracks for you) things like shift presses, etc.

The keyboard interface itself just sends a code - this is sent into the event system and then the language translates that into a keycode!

----------------

And next complete rubbish spouted by the author. The keyboard DOES NOT SEND OR HAVE ANYTHING TO DO WITH ASCII!

Yes GetChar() will convert stuff to ASCII. But this is a helper that has been coded into blitz. and has NOTHING to do with ASCII

What is ASCII?
ASCII codes represent text in computers - it is an extension of Telegraph codes which was 7bit based and not uniform between manufacturers.
ASCII was first proposed in 1961, first released in 1963 and finalized in 1968.

ASCII was developed for teletype systems and retains all of the inbuild system codes - delete, enter, etc in the first 31 characters. The rest filling the alpha and numeric characters that were required. IBM further Standarized its mainframe system to the ANSI ASCII standard in 1965.

Why is this important? because it tells you that ASCII and keyboards (although related) don't have any connection. ASCII was and is used a core character set. that is standard and can be used by manufacturers to make teletype systems and mini computers use the same basic characters. these fit into 7bits with a single bit parity.

The key thing here is IBM. They are the standard that everyone followed. They stuck with the ANSI variation of ASCII and so ASCII became the defacto standard.

This brings us to characters and strings. A character (Char) is an 8bit number generally relating to the ASCII standard. Although fonts relate differently as the upper 127 characters can be anything - graphics, other needed character for different languages etc.

So 32 = SPACE, 65 = A, 66 = B, etc

a string is defined as a series of Char(s)
Hence you can think of a string as an array of single Char(s) too.

But THE KEYBOARD DOES NOT SEND ASCII - EVER!!!!!

-------------------------------------

Telling people - especially someone you think is attempting to learn these this is not good. It's not how these things work and it only makes their learning harder as they will have to figure out why something is not working they way they were told.

This is VERY POOR TEACHING!!!!

ALSO:
stuff like ctrl, shift/option, etc will have to be parsed by the end user and they will have to decide how and what to do with this information.

Remember that different keyboards will give completely results - further showing the real disconnect from Keycodes and ASCII.

Another caveat for you and your 'learners'.

blitz is designed as an even based system, with general class/app based frame work.

To teach any new person about using globals as the core variable is not just poor teaching. it wouldn't be accepted in any school or work environment.

You might be better to use a simpler basic to teach beginner level stuff. and use Blitz to actually teach people how to use that properly?

#### Midimaster

#23
Lesson XX: Text-Files and String Manipualtion

Today you will learn how to load and save informations to the hard disc. This is often used to get words and sentences into your game. And a important aspect is to define different levels and get the informations from a file. This has the advantage, that you can write "levels" directlty as a txt-file with a simple txt-editor.

A TXT-File

Txt-Files are clear readable texts on a hard disk. you can read, write an manipulate them directly with a text-editor. Afterward your app tries to open them and set variables depending on what you have written in the txt-file.

You can use every external Editor as long as it produces file with the extension .TXT. A app like WORD would not be usefull, because WORD saves additional styling informations into the file. But we need pure ASCII-Code.

You can directly use the BlitzMax IDE to write such a text file. Open a new code-window and write:
`hello world`
But now save the content as "test.txt" into your project folder.

Open another code-window and write this app:
Code (BlitzMax) Select
`SuperStrictGlobal All:String = LoadText("test.txt")Print All`

LoadText:String( FileName:String) loads a txt-file and returns the complete content as one string into a variable.

Now change our text to:
`Hello WorldThis is line twoAnd this another one`
Ignore that fact, that the IDE highlights the word "And" and save the file as test.txt.

Now re-start our app an observe that the variable All contain all lines of our text in one variable.

Cut text into different variables

The variable type STRING knows various internal functions to manipulate strings. Those internal functions are only avaiable for strings, we call them Methods. You call Methods by adding a dot to your variable name followed by the method name:

Code (BlitzMax) Select
`... = All.Split("?")`

The method Split:String[] (Separator) splits the content of a STRING into a STRING array. Each element of the array contains only a part of the original text All. Here the method splits the text every time it finds a ? letter.

But we do it this way:

Code (BlitzMax) Select
`SuperStrictGlobal All:String\$ = LoadText("test.txt")Global word:String[] = All.Split(" ")  '  <---- there is a SPACE between the quotation marksPrint word[6]`
This splits the text into single strings. Because the separator is a SPACE letter, we get single words as result.

Cut text into lines

We can use this Method Split() to split a text into lines. We detect "a new line" where two special ASCIIs are in the text. BlitzMax know them as "~r" and "~n"

Code (BlitzMax) Select
`SuperStrictGlobal All:String = LoadText("test.txt")Global Line:String[] = All.Split("~r~n")Print ">" + Line[1] +"<"If Line[1] = "This is line two" Then Print "Is the same"`

~r (RETURN) and ~n (NEWLINE) are BlitzMax-Shortcuts for the ASCIIs 13 and 10, which are also known as CRLF (Carriage Return + Line Feeld)

Now let us create our first INI-file:

Ini-files are often used to initialize a game with parameters, get values and sentences into your game. A common aspect is to define different levels and fetch the informations from a file during the game.

Save this new plain text as "Ini.txt"

`Name=PeterAge=25Health=3.235' now a empty line:GameGadgets=4|123|0|4|-7`

This is a typical INI-file. Our Ini.txt contains different problematics: A String like Peter, values like 25, comments like ' now a empty line: and a real empty line.

To open it we do the same like before. Separate the lines:

Code (BlitzMax) Select
`SuperStrictGlobal All:String = LoadText("ini.txt")Global Line:String[] = All.Split("~r~n")For Local i:Int=0 Until Line.length Print "line " + i + " :" + Line[i] Next `

This works fine. All lines are accepted.

Now we do a second split action with each line to get left side and right side separated. We search for the  letter =

Code (BlitzMax) Select
`...For Local i:Int=0 Until Lines.length Local Parts:String[] = Lines[i].Split("=") Print "Variable " + Parts[0]  + "  has the value " + Parts[1]Next `

Our app fails at the 4th line (the line with the comment). The reason is there is no "=" in this line. So the second array element Parts[1] does not exist

A little change will help

Code (BlitzMax) Select
`...For Local i:Int=0 Until Lines.length Local Parts:String[] = Lines[i].Split("=") If Parts.Length>1 Print "Variable " + Parts[0]  + "  has the value " + Parts[1] EndifNext `

AnyText.Contains("=") is another Method. It checks whether a letter (here "=") is inside a string and returns TRUE or FALSE

Now the app runs perfect. Lets see what we can do with this knowledge....

Code (BlitzMax) Select
`SuperStrictGlobal Ini:String[]IniLoad("ini.txt")Global PlayerName:String= IniRead("Name")Print "Players Name is " + PlayerNameFunction IniLoad(FileName:String) Local All:String = LoadText("ini.txt") Ini = All.Split("~r~n")End FunctionFunction IniRead:String(Key:String) For Local i:Int=0 Until Ini.Length Local Parts:String[] = Ini[i].Split("=") If Parts[0]=Key Then Return Parts[1] Next Return Null End Function `

Do you recognize our old code? The loading of the INI-File moved into a function. The temporary variable All becomes LOCAL, because we do not need the text file as a whole outside the function.

The second function IniRead() scans all lines of the Ini-Array and returns a value if it find the key "Name".

Code (BlitzMax) Select
`Global PlayerAge:Int = IniRead("Age")`

If we try to do the same for the players age, this would cause a runtime error, because PlayerAge is a INTEGER, but our function always returns STRINGs

So we have to convert the returned STRING into a INTEGER

Code (BlitzMax) Select
`SuperStrictGlobal Ini:String[]IniLoad("ini.txt")Global PlayerName:String   = IniRead("Name")Global PlayerAge:Int       = IniRead("Age").ToIntGlobal PlayerHealth:Double = IniRead("Health").ToDoublePrint "Players name is "        + PlayerNamePrint "Players age is "         + PlayerAgePrint "Players health is "      + PlayerHealth....`

... = String.ToInt is another STRING METHOD and converts a STRING into an INTEGER

... = String:ToDouble is another STRING METHOD and converts into an DOUBLE floating point

Because the type that IniRead() returns is STRING we can handle the function like a string and extend the STRING METHODs: IniRead(...).ToInt

for the last text line...
....we have to convert the right side of the line into an array. It contains 5 INTEGER values separated by a pipe letter: |. We separate them again with the Split()-Method

Code (BlitzMax) Select
`....Global PlayerGadget:Int[]  = SplitToArray( IniRead("GameGadgets") )....Print "Players  2nd Gadget is " + PlayerGadget[1]....Function SplitToArray:Int[](RightSide:String) Local Parts:String[] = RightSide.Split("|") Local Integer:Int[Parts.Length] For Local I:Int=0 Until Parts.Length Integer[i]  = Parts[i].ToInt Next Return IntegerEnd Function`

As we cannot cast STRING[]-arrays directly into INTEGER[]-arrays we do it in three steps:

• First split the content into a local STRING[]-array.

• Then define an local INTEGER-array with the same number of element like the STRING[]-array

• Then copy each single element from STRING[]-array to INTEGER[]-array with the ToInt()-Method

• At the end we return the INTEGER-array to the main app.

Two code line may look tricky for you:

RETURN ARRAY

Functions can return arrays:

Code (BlitzMax) Select
`Function SplitToArray:Int[] (...) Local Integer:Int[...] .... Return IntegerEnd Function `
We cannot only return single variables, but also complete arrays

NEST FUNCTIONS

We can nest function calls into function calls:

Code (BlitzMax) Select
`SplitToArray( IniRead("GameGadgets") )`

IniRead() is a function that needs "GameGadgets" as parameter. It returns the right side of a text line of our Ini-file.

SplitToArray() is a function that needs the right side of a text line as parameter. It returns this right side splitted into an array.

So to say the result of IniRead() is the parameter for SplitToArray().

You can nest as many function calls as you like... as long as you understand your code yourself

Critics to this tutorial are welcome, but please do not post here, but there:

...back from Egypt

#### Midimaster

#24

Today we will learn, why a function without GLOBAL variables is more useable. This is a step ahead in your skills. And we expand the INI-file to SECTIONS to be able to load the same set of variables with different values. This is neccessary when we have different levels in our game.

GLOBAL LOCAL and Encapsuled Functions

The main target of creating functions is to use them often in your code and use them them in more than one app.

Therefore the function needs to be absolut independent from the main code. The function is not allowed to use (refer) to a GLOBAL variable. The only allowed way to communicate is the parameter list for input and the return value for output.

You can easily check with BlitzMax, whether your function  meets these requirements. Copy them to a new empty TAB in the BlitzMax IDE and the start F5. We can do this with the loading function IniLoad() from our last chapter:

Code (BlitzMax) Select
`Function IniLoad(FileName:String)        Local All:String = LoadText("ini.txt")        Ini = All.Split("~r~n")End Function`

You will receive a runtime error because the function still refers (needs) a global array Int:String[]

Now let us modify the function toward a more universal usability:

Code (BlitzMax) Select
`Function IniLoad:String[] (FileName:String) Local All:String = LoadText("ini.txt") Local Ini:String[] = All.Split("~r~n") Return IniEnd Function `

Now we use a LOCAL array Ini:String[] to do our splitting. At the end we return it to the main app. This also needs changes in the header of the function. Her we define what kind of variable the function will return.

Code (BlitzMax) Select
`Function IniLoad:String[] (...)`

...means the function returns a String-array.

You can test the function in a separate TAB by "starting" it F5 and you will see, that there is no more runtime error.

As we modified the function, we now need to edit the main code from:

Code (BlitzMax) Select
`SuperStrictGlobal MyIni:String[]IniLoad("ini.txt")...`

towards...

Code (BlitzMax) Select
`SuperStrictGlobal MyIni:String[] = IniLoad("ini.txt")...`

And now I can demonstrate you the advantage of that new encapsuled function. Let's say we need two Ini-Files, one for the levels and one for the design. we now can use the function IniLoad() twice:

Code (BlitzMax) Select
`SuperStrictGlobal LevelIni:String[] = IniLoad("LevelIni.txt")Global DesignIni:String[] = IniLoad("DesignIni.txt")...`

this was not possible before.

So now lets check the second function IniRead()

Code (BlitzMax) Select
`SuperStrict old version:Function IniRead:String(Key:String) For Local i:Int=0 Until Ini.Length Local Parts:String[] = Ini[i].Split("=") If Parts[0]=Key Then Return Parts[1] Next Return Null End Function `

It does not survive the F5-test. Runtime error because it uses the global Ini[]-array. But in this case we need the informations of the global Ini[]-array inside the function. The solution is to send the whole array as a parameter to the function:

new version:
Code (BlitzMax) Select
`Function IniRead:String(Key:String, Ini:String[]) For Local i:Int=0 Until Ini.Length Local Parts:String[] = Ini[i].Split("=") If Parts[0]=Key Then Return Parts[1] Next Return Null End Function `

As we modified the function, we now need to edit the main code from:

Code (BlitzMax) Select
`Global PlayerName:String   = IniRead("Name")Global PlayerAge:Int       = IniRead("Age").ToIntGlobal PlayerHealth:Double = IniRead("Health").ToDoubleGlobal PlayerGadget:Int[]  = SplitToArray(IniRead("GameGadgets"))...`

towards...

Code (BlitzMax) Select
`Global PlayerName:String   = IniRead("Name", MyIni)Global PlayerAge:Int       = IniRead("Age", MyIni).ToIntGlobal PlayerHealth:Double = IniRead("Health", MyIni).ToDoubleGlobal PlayerGadget:Int[]  = SplitToArray(IniRead("GameGadgets", MyIni))...`

Ini-Files for Levels

If you have different levels in your game, this means you will to restart your game, but the values of the variables will differ from level to level. In Level 1 the number of enemies will be 10, but in Level 2 you will start with 20 and the moving speed of the enemies could be higher, etc..

This would mean we need to have several version of the settings in one INI-file:

`Name=PeterAge=25Health=3.235GameGadgets=4|123|0|4|-7Name=TomAge=41Health=4.111   GameGadgets=1|2|3|4|5Name=LindaAge=18Health=12.25GameGadgets=22|11|33|77|0`

This would not work with our function IniRead(), because it always immendiately returns, when it finds the first existence of a key like "Name".

Sections

Modify the Ini.Txt like this:

`[Level 1]Name=PeterAge=25Health=3.235GameGadgets=4|123|0|4|-7[Level 2]Name=TomAge=41Health=4.111   GameGadgets=1|2|3|4|5[Level 3]Name=LindaAge=18Health=12.25GameGadgets=22|11|33|77|0`

The lines with keys in brackets are called sections. They mark the beginning of a new chapter. Lets see how we handle them in the ReadIni()

Code (BlitzMax) Select
`PlayerName = IniRead("Name", MyIni, "Level 1")...Function IniRead:String(Key:String ,Ini:String[] , Section:String) Section = "[" + section + "]" Local Found:Int=False For Local i:Int=0 Until Ini.Length Local Parts:String[] = Ini[i].Split("=") If Parts[0]=Section Then Found=True If Found=True Then If Parts[0]=Key Then Return Parts[1] EndIf Next Return Null End Function `

The function now needs three parameters. The new parameter is the name of the section. Inside the function we join the brackets to the 3rd parameter. Level 1 becomes [Level 1]

Then we scan the list of lines until we find this expression. The scan for the key word Name is prevented because the variable Found is not TRUE at the moment

From this moment on we allow also the scan for the key word Name. It will be found within the next iterations.

The complete code

Code (BlitzMax) Select
`SuperStrictGraphics 800,600Global MyIni:String[] =IniLoad("ini.txt")Global Level:Int=1Global PlayerName:String, PlayerAge:Int, PlayerHealth:Double, PlayerGadget:Int[]ChangeLevel LevelRepeat Cls If KeyHit(KEY_L) Level = Level +1 ChangeLevel Level EndIf DrawText "current level is " + Level, 100,100 DrawText "Players name is "        + PlayerName      , 100, 200 DrawText "Players age is "         + PlayerAge       , 100, 230 DrawText "Players health is "      + PlayerHealth    , 100, 260 DrawText "Players  2nd Gadget is " + PlayerGadget[1] , 100, 290 DrawText "press L to change level", 100,400 FlipUntil AppTerminate()Function ChangeLevel(Level:Int) If Level=4 End PlayerName    = IniRead("Name", MyIni, "Level " +Level) PlayerAge     = IniRead("Age", MyIni, "Level " +Level)ToInt PlayerHealth  = IniRead("Health", MyIni, "Level " +Level).ToDouble PlayerGadget  = SplitToArray(IniRead("GameGadgets", MyIni, "Level " +Level))End Function Function IniRead:String(Key:String , Ini:String[] , Section:String) Section = "[" + section + "]" Local Found:Int=False For Local i:Int=0 Until Ini.Length Local Parts:String[] = Ini[i].Split("=") If Parts[0]=Section Then Found=True If Found=True Then If Parts[0]=Key Then Return Parts[1] EndIf Next Return Null End Function Function IniLoad:String[](FileName:String) Local All:String = LoadText("ini.txt") Local Ini:String[] = All.Split("~r~n") Return IniEnd Function Function SplitToArray:Int[](RightSide:String) Local Parts:String[] = RightSide.Split("|") Local Integer:Int[Parts.Length] For Local I:Int=0 Until Parts.Length Integer[i]  = Parts[i].ToInt Next Return IntegerEnd Function `

Challenge I: Encapsuled Functions

Scan all lesson from VI to XX and check all function you can see. Find those functions, which meet the new conditions and are already universal.

Challenge II: Optimize The Functions

Scan all lesson from VI to XX and check all function you can see. Find those functions, which DO NOT meet the new conditions. Try to convert them into a universal style

Challenge III: Quiz Game

Write a quiz game, where the user has to answer 10 questions by selceting one of three multiple choice answers. Add a Ini-file that contains the 10 questions and 3 possible answers and also a flag, which answer is the right.

Critics to this tutorial are welcome, but please do not post here, but there:

...back from Egypt

#### Midimaster

#25
Lesson XXII: Saving Variables And Complete Levels To INI-Files

Today you will learn how to save someting to your INI-File. You can use this for saving variables or the current state of a game to continue it tomorrow. Or we can use it to save a complete maze after you created it in your editor or every time when the random generator produces a nice looking maze.

Target of my lessons are never to code together with you a "perfect INI-system", but teach you strategies to expand app in little steps, find bugs and react on them. So we develop here a very restriced INI-system, which not enables to add new keys or new sections from within the app. Means: we overwrite only already existing keys.

IniSave()

Is a easy function with another nice STRING METHOD. We have to join the lines of our array back to a single text file, than save it in one go. It looks a little bit like the IniLoad():

Code (BlitzMax) Select
`Function IniSave(FileName:String,Ini:String[]) Local All:String = "~r~n".Join(ini) SaveText All, FileName End Function `

Text:String = SEPARATOR.Join(Array[]) is the reverse function to the method Split(). It connects all member of a string array and inserts the Separator ASCII between them.

As we want to have indepenent lines also in our text-file we use again the separator "~r~n" (CRLF).

IniWrite()

The IniWrite() will have a lot of the code line our IniRead() already has, because also IniWrite() needs to find the correct line. So we could copy the function IniRead() and modify the copy until it writes values:

Code (BlitzMax) Select
`Function IniWrite(NewValue:String, Key:String , Ini:String[] , Section:String) Section = "[" + section + "]" Local Found:Int=False For Local i:Int=0 Until Ini.Length Local Parts:String[] = Ini[i].Split("=") If Parts[0]=Section Then Found=True If Found=True If Parts[0]=Key Part[1] = NewValue     ' <------- THE ONLY CHANGE Return EndIf EndIf Next Return NullEnd Function `

You see it is nearly the same. We have a new parameter NewValue:String which contains, what we want to write into the INI-line. And the codeline "after we detected the key" now does not return a value but changes Part[1].

No double code

But a better idea would be to encapsul the feature "finding" into a new function and not have double code passages. Therefore we write a third function SearchLine() which returns the line number, where we found the key.

Code (BlitzMax) Select
`Function SearchLine:Int(Key:String ,Ini:String[] , Section:String) Section = "[" + section + "]" Local Found:Int=False For Local i:Int=0 Until Ini.Length Local Parts:String[] = Ini[i].Split("=") If Parts[0]=Section Then Found=True If Found=True If Parts[0]=Key Return i ' <------- THE ONLY CHANGE EndIf EndIf Next Return NullEnd Function `

It is again nearly the same like IniRead(), but it does not return the content of the line, but the line number.

Now the IniRead() can be changed an becomes a much shorter:

Code (BlitzMax) Select
`Function IniRead:String(Key:String , Ini:String[] , Section:String) Local LineNumber:Int = SearchLine(Key, Ini, Section) Local Parts:String[] = Ini[LineNumber].Split("=") If Parts[0]=Key Then Return Parts[1] Return NullEnd Function `

And also the IniWrite is now massive shorter:

Code (BlitzMax) Select
`Function IniWrite(NewValue:String, Key:String , Ini:String[] , Section:String) Local LineNumber:Int = SearchLine(Key, Ini, Section) Local Parts:String[] = Ini[LineNumber].Split("=") If Parts[0]=Key Parts[1] = NewValue EndIfEnd Function `

You may think that it is useless to extract double code passages into a new function. But this solves one of the main problems beginners have: They cannot understand their own code after it grows to a monster. Think about the moment when we find out, that our "finding algorithm" is not perfect or has mistakes... In our old code you wouldd need to make changes at several places. With the new code we change it in one functions and all the calling code lines will profit immediately!!! And I can say already now: Our "finding algo" has massiv problems, but we cannot see them at the moment!

Really changing the INI?

Here is our playground: Try the new version of our app:

Code (BlitzMax) Select
`SuperStrictGraphics 800,600Global MyIni:String[] =IniLoad("ini.txt")Global Level:Int=1Global PlayerName:String, PlayerAge:Int, PlayerHealth:Double, PlayerGadget:Int[]ChangeLevel LevelRepeat Cls If KeyHit(KEY_L) Level = Level +1 ChangeLevel Level EndIf If KeyHit(KEY_C) IniWrite "HORST", "Name" , MyIni, "Level " + Level EndIf DrawText "current level is " + Level, 100,100 DrawText "Players name is "        + PlayerName      , 100, 200 DrawText "Players age is "         + PlayerAge       , 100, 230 DrawText "Players health is "      + PlayerHealth    , 100, 260 DrawText "Players  2nd Gadget is " + PlayerGadget[1] , 100, 290 DrawText "press L to change level", 100,400 DrawText "press C to change name to HORST", 100,430 FlipUntil AppTerminate()Function ChangeLevel(Level:Int) If Level=4 Then Level=1 PlayerName    = IniRead("Name", MyIni, "Level " +Level) PlayerAge     = IniRead("Age", MyIni, "Level " +Level).ToInt PlayerHealth  = IniRead("Health", MyIni, "Level " +Level).ToDouble PlayerGadget  = SplitToArray(IniRead("GameGadgets", MyIni, "Level " +Level))End Function Function SearchLine:Int(Key:String ,Ini:String[] , Section:String) Section = "[" + section + "]" Local Found:Int=False For Local i:Int=0 Until Ini.Length Local Parts:String[] = Ini[i].Split("=") If Parts[0]=Section Then Found=True If Found=True If Parts[0]=Key Return i ' <------- THE ONLY CHANGE EndIf EndIf Next Return NullEnd Function Function IniRead:String(Key:String , Ini:String[] , Section:String) Local LineNumber:Int = SearchLine(Key, Ini, Section) Local Parts:String[] = Ini[LineNumber].Split("=") If Parts[0]=Key Then Return Parts[1] Return NullEnd Function Function IniWrite(NewValue:String, Key:String , Ini:String[] , Section:String) Local LineNumber:Int = SearchLine(Key, Ini, Section) Local Parts:String[] = Ini[LineNumber].Split("=") If Parts[0]=Key Parts[1] = NewValue EndIfEnd Function Function IniLoad:String[](FileName:String) Local All:String = LoadText("ini.txt") Local Ini:String[] = All.Split("~r~n") Return IniEnd Function Function SplitToArray:Int[](RightSide:String) Local Parts:String[] = RightSide.Split("|") Local Integer:Int[Parts.Length] For Local I:Int=0 Until Parts.Length Integer[i]  = Parts[i].ToInt Next Return IntegerEnd Function `

In this version you can test the IniWrite() with pressing key <C> (Change). This will change the current players name to "HORST". But if you playaround you will notice that nothing happens in the display.

the current IniWrite() changes the variable Part[1]. But this is only a local array inside the function, this would not change the INI permanent. To change the INI we need to manipulate the array Ini[] with the original lines!!! So we need this in the IniWrite()

Code (BlitzMax) Select
`Function IniWrite(NewValue:String, Key:String , Ini:String[] , Section:String) Local LineNumber:Int = SearchLine(Key, Ini, Section) Local Parts:String[] = Ini[LineNumber].Split("=") Print "IniWrite at " + linenumber + " ->" + Ini[LineNumber] If Parts[0]=Key Ini[LineNumber] = Parts[0] + "=" + NewValue   ' <------- THE ONLY CHANGE Print "changed to ->" + Ini[LineNumber] EndIfEnd Function`

At lot of PRINT commands are the best friend of a developer. With PRINTs you can see what really happens. Add this PRINT commands to the IniWrite(). The first PRINT...
Code (BlitzMax) Select
`Print "IniWrite at " + linenumber + " ->" + Ini[LineNumber]`
... verifies that we truely entered the function. The second...
Code (BlitzMax) Select
`Print "changed to ->" + Parts[1]`
... verifies that we really change the Ini[LineNumber]

When you run the app you can see, that the line content changed. But the displayed name not! What happened? But it becomes even more obscure: Press key <L> 3 times!

Challenge I: Find out what happened and try to fix it

Find out what happened and try to fix it. Add as many PRINT as you like.

Challenge II: A Ini for The Maze

Try to add an ini system for our maze game. A random generated maze can be stored to a INI. When it is good looking press "S" and store it for eternity. Press a number between 1 and 9 to re-call previous stored mazes.

Challenge III: Expand the INI-System

Expand the INI-system towards adding new sections and keys. This means increasing the size of an array... difficult!
Or much easier: Create a bigger empty array and copy the contents of Ini[] array into it, while adding lines for the new keys and sections... At the end rebuild the Ini[] array from this.

Critics to this tutorial are welcome, but please do not post here, but there:

...back from Egypt

#### Midimaster

#26
Lesson XXIII: Three little step towards OOP... Step 1

I was asked to write some lessons about Object Oriented Programming. There are a lot of very good tutorials, even Brucey wrote a very good one:

https://blitzmax.org/docs/en/tutorials/oop_tutorial

The problem is... they move too fast. As it is a big step for beginner, beginners need to see a reason for OOP and need to feel the advantage of using it. So we will proceed very very slow and with a lot of typical usages: The lessons XXIII to XXV will be  "3 little steps" and lesson XXVI will code a home made GUI.

First Little Step: A User Defined Type

In our games the actors often need two coordinates X and Y. We need them to descripe the position of players and enemies, etc...
`Global PlayerX:Int = 20Global PlayerY:Int = 100`

And for 100 Enemies we could realize this in two arrays:

`Global EnemyX:Double[100] = Rand(0,800)Global EnemyY:Double[100] = Rand(0,600)`

And with a personal moving speed we are at 4 arrays, if we add "health" we get a 5th array,  and so on...

`Global EnemyX:Double[100] = Rand(0,800)Global EnemyAddX:Double[100] = Rnd(0,1)Global EnemyY::Double[100] = Rnd(0,600)Global EnemyAddY:Double[100] = Rnd(0,1)Global EnemyHealth:Int[100] = 100`

Not nice.. but acceptable. But what, if the enemies should also get individual names and images and magazine and.... Still acceptable?

Think about a game situation, where we need to compare two enemies, what a staple of code lines...

New rule:

Always when your app needs a couple of individual objects of the same type you should use User Defined Types.

Those objects can be: Enemies, Balls, Snowflakes, chess pieces, cards, jigsaws pieces. But also buttons, sliders, data records, notes, tables...

A User Defines Type is a author-made variable type, that can contain any number of variables (often called properties or fields)

At first
...we need to descripe our home-made new Type:

Code (BlitzMax) Select
`SuperStrictType TActor     Field X:Double, Y:Double, addX:Double, addY:Double, Name:String End Type`

Type TName ... End Type defines begin and end of the description block. You can select a free name for your new type. But it is common to start the name with a T: TName, TActor, TImage, TSound... (Yes you already heard this. Also TImage is such a User Defines Type defined by a prior user. Later it became part of BlitzMax)

Field variable, variable, etc... defines the inside variables of your type.

After this definition TActor is a well known variable type like Int or String.

In a second step
...we create a first game figure by using the new variable type:

Code (BlitzMax) Select
`...Global Player:TActor = New TActor`

This is the parallel use to...
`...Global Value:Int = 123`

Because User Defines Types have more than one field, we cannot immediately set a value like =123. So we first write =New TActor (more later). This is the birth of the first member of Actors.

As last step
...we set it's 5 values:

Code (BlitzMax) Select
`SuperStrictType TActor     Field X:Double, Y:Double, addX:Double, addY:Double, Name:String End TypeGlobal Player:TActor = New TActorPlayer.X = 20Player.addX = 0.5...Player.Name = "Killer"`

We can use the TActor-Type also for our enemies. Also they move and live like players:

Code (BlitzMax) Select
`SuperStrictType TActor     Field X:Double, Y:Double, addX:Double, addY:Double, Name:String End Type....Global Enemy:TActor[100]For local i:Int=0 until 100     Enemy[i] = New TActor     Enemy[i].X = Rand(0,800)     Enemy[i].addX = Rnd(0,1)     ...     Player.Name = "Enemy no " + iNext `

It is important to start each new member with the =New TActor command. With Global Enemy:TActor[100] the 100 enemies are not born yet. The birth is the individual New-command for each member!!!

From now on you can use the coordinates like two properties of the actor. Use the dot to join member and field:

Code (BlitzMax) Select
`SuperStrictType TActor     Field X:Double, Y:Double, addX:Double, addY:Double, Name:String End Type....Player.X = Player.X + Player.addXIf Player.X< 0 then      ' left screen border     Player.X=0Endif ...DrawText Player.Name, 100, 100For local i:Int=....     If (Enemy[i].X > Player.X) And (Enemy[i].Y> Player.Y)...          ' kill an enemy           Enemy[i] =NULL     Endif ....`

Try to re-write the ping pong game. Exchange all ball related variables to OOP. Instead of BallX use now Ball.X, etc...

Challenge II: Contact & Adress Book

Try to write an app, which manage adresses and contact details of your friends. Invent a TContact type and think about, which FIELDS are necessary. If you want you can combine it with your INI-skills

Challenge III: BreakOut Arkanoid

Start to write a Breakout/Arkanoid-Game. 50 stones in 5 rows need to be destroyed by a ball. Use your type TBrick

Critics to this tutorial are welcome, but please do not post here, but there:

...back from Egypt

#### Midimaster

#27
Lesson XXIV: A Type Is A Class? (OOP Step 2)

If you ever want to use your code in more than one app or if you plan to give the code away to others  as a library, you need to encapsul the code, that it never touches the main code.

Touches?

You could accidentally use variable names in your library, that also are used in the main code. This would cause unpredictable effect on the main code. The same happens with function names.

We already learned that function should only use LOCAL variables. But often a library need to have common variables over all their functions. Here a CLASS would help. A Class is a library with a unique namespace, means all variable names and function names are guaranteed unique. But how to quarantee this?

Put It Into A Type

We can put GLOBAL variables and Functions into a TYPE definition. And then they are ancapsuled from the rest of our app.

Here we have a main code and a class that both use the same variable name and the same function name, but BlitzMax can handle it. If you run the app (F5) the IDE does not report syntax-Errors. This means the code is valid:

Code (BlitzMax) Select
`SuperStrict Global Name:String = "Peter"ReName()Print Name Function ReName() Name="Rosi"End Function Type MyClass Global Name:String="Tom " Function ReName() Name = Name + "Dooley" End Function End Type`

How to use this libraries?

If you want to call a library function, write a compound word ClassName + Dot + FunctionName

If the inside function uses a GLOBAL variable, it preferes the inside GLOBAL if it exists.

Code (BlitzMax) Select
`SuperStrict Global Name:String = "Peter"MyClass.Rename()   '   <--------- CALL a library functionPrint Name Print MyClass.Name '   <--------- CALL a library variableFunction ReName() Name="Rosi"End Function Type MyClass Global Name:String="Tom " Function ReName() Name = Name + "Dooley" End Function End Type`

To check what happend we ask for the inside global variable Name: MyClass.Name

If you want to work with a library variable write a compound word ClassName + Dot + VariableName

Change the Class Name in your main app

If you do not like the given library name you can also exchange it. We call this trick: Create an instance of the Type:
Code (BlitzMax) Select
`SuperStrict Global HisClass:NameallocationInfinitvWorkGroup = New NameallocationInfinitvWorkGroupHisClass.Rename()Print HisClass.Name' here a class with a terrible name:Type NameallocationInfinitvWorkGroup Global Name:String="Tom " Function ReName() Name = Name + "Dooley" End Function End Type`

A Universal Stop Watch

This stop watch can be used in any main code. It is completely indepenent from the main development. At the moment it knows only one job: reporting the seconds

Code (BlitzMax) Select
`SuperStrict Graphics 200,200Repeat Cls DrawText TStopWatch.Tick(),100,100 Flip 0Until AppTerminate()Type TStopWatch Global Ticks:Int, NextTime:Int=Millisecs(), TimeIntervall:Int=1000 Function Tick:Int() If NextTime<MilliSecs() NextTime = MilliSecs()+TimeIntervall Ticks=Ticks+1 EndIf Return ticks End Function End Type`

But we can adjust it to shorter intervals:

Code (BlitzMax) Select
`SuperStrict Graphics 200,200Repeat Cls DrawText TStopWatch.Tick(),100,100 DrawText "Press R to RESET", 30,150 If KeyHit(KEY_R) TStopWatch.Reset 10  ' now faster every 10msec EndIf Flip 0Until AppTerminate()Type TStopWatch Global Ticks:Int, NextTime:Int=MilliSecs(), TimeIntervall:Int=1000 Function Reset(Intervall:Int) TimeIntervall = Intervall Ticks=0 NextTime=MilliSecs() End Function Function Tick:Int() If NextTime<MilliSecs() NextTime = NextTime+TimeIntervall Ticks=Ticks+1 EndIf Return ticks End Function End Type`

Another  inside function Reset() manipulates the inside variables

Do whatever you want: A Clock Gadget

Now you can code whatever you want. The TTimer is a closed world like an app in the app:

Code (BlitzMax) Select
`SuperStrict Graphics 300,250TStopWatch.Reset 100SetClsColor 0,33,66Repeat Cls SetColor 0,66,99 DrawOval 50,20,200,200 Local Degree:Double, x:Double, y:Double TStopWatch.Tick() SetColor 255,0,0 Degree=TStopWatch.Degree() x=Cos(Degree)*100 + 150 y=Sin(Degree)*100 + 120 SetLineWidth 2 DrawLine 150,120,x,y SetColor 0,255,0 Degree=TStopWatch.SecondDegree() x=Cos(Degree)*80 + 150 y=Sin(Degree)*80 + 120 SetLineWidth 2 DrawLine 150,120,x,y SetColor 255,255,255 Degree=TStopWatch.MinuteDegree() x=Cos(Degree)*60 + 150 y=Sin(Degree)*60 + 120 SetLineWidth 3 DrawLine 150,120,x,y DrawText TStopWatch.TimeString(), 130,230 Flip 0Until AppTerminate()Type TStopWatch Global Ticks:Int, NextTime:Int=MilliSecs(), TimeIntervall:Int=1000 Function Reset(Intervall:Int) TimeIntervall = Intervall Ticks=0 NextTime=MilliSecs() End Function Function Tick:Int() If NextTime<MilliSecs() NextTime = NextTime+TimeIntervall Ticks=Ticks+1 EndIf Return ticks End Function Function Degree:Int() Return (ticks Mod 10) *36 -90 End Function  Function SecondDegree:Int() Return (Int(ticks/10) Mod 60) *6-90 End Function  Function MinuteDegree:Int() Return (Int(ticks/600) Mod 60) *6-90 End Function  Function TimeString:String() Local m:String = Int(ticks/600) Mod 60 Local s:String = Int(ticks/10) Mod 60 Local h:String = ticks Mod 10 Return m +":" + s + ":" + h End Function End Type`
...back from Egypt

#### Midimaster

#28
Button I: Simple Buttons in Games for Total Beginners

This Tutorial show how to handle some self made buttons in your game. In the first example you will see a very easy to understand solution. The we will expand this to a TYPE-solution which can handle several buttons with one code.

User Derron points me to the problematic, that a MouseHit() call should not be inside a function, but at a central place. That is necessary, because other part of the app may also need to know the MouseState. So I define a GLOBAL variable MOUSESTATE, which keeps the state until the next FLIP

One Button

A button is a rectangle, that interact with the mouse. The image can be a PNG-file or a code painted area.
The Checkbutton() function excludes all cases, where nothing happened and returns 1 if the Mouse was inside the rectangle:

`SuperStrictGraphics 800,300Global MouseState:IntRepeat    Cls    MouseState = MouseHit(1)    DrawRect 100,200,120,80     If CheckButton()>0 Then         Print "Button pressed"    EndIf     Flip Until AppTerminate()Function CheckButton:Int()    If MouseState=0 Return 0    Local mX:Int = MouseX()-100    Local mY:Int = MouseY()-200    If mX<0 Or mX>120 Return 0    If mY<0 Or mY>80  Return 0    Return 1End Function `
Before checking we substract the start coordinate (100/200) of the rectangle to simplify the code.

Five Buttons In A Row

Nearly the same code. We only have to divide the relative MouseX by the width of the Rectangle. This returns float values between 0.00 and 5.00. When we take the INTEGER of them we get the values 0,1,2,3,4. To receive 1,2,3,4,5 we add +1.

`SuperStrictGraphics 800,300Global MouseState:IntRepeat    Cls    MouseState = MouseHit(1)    For Local I:Int=0 Until 5        DrawRect 100+i*120+1, 200,118,80    Next    Local Pressed:Int = CheckButton()    If Pressed>0 Then         Print "Button " + Pressed + " pressed"    EndIf     Flip Until AppTerminate()Function CheckButton:Int()    If MouseState=0 Return 0    Local mX:Int = MouseX() - 100    Local mY:Int = MouseY() - 200    If mX<0 Or mX>5*120 Return 0    If mY<0 Or mY>80 Return 0    Return Int(mX/120) + 1End Function `
The same would be possible with vertical buttons. Here we calculate from MouseY and the height(80).

A Simple Button Type

To be more flexible and to handle a dozend individual buttons we define a button TYPE. This enables us to give away the control of painting, checking, etc to an automated process. Via a personal ID each button will report back, if it was pressed:

`SuperStrictGraphics 800,300Global MyButton:TButton= New TButton(1,100,200,120,80)Global MouseState:IntRepeat    Cls    MouseState = MouseHit(1)    MyButton.Draw    Local Pressed:Int = MyButton.Check()    If Pressed>0 Then         Print "Button " + Pressed + " pressed"    EndIf     Flip Until AppTerminate()Type TButton    Field ID:Int, X:Int, Y:Int, W:Int, H:Int        Method New (id:Int, x:Int, y:Int, w:Int, h:Int)        Self.ID = id        Self.X  = x        Self.Y  = y        Self.W  = w        Self.H  = h    End Method          Method Draw()        DrawRect X, Y, W, H    End Method        Method Check:Int()        If MouseState=0 Return 0        Local mX:Int = MouseX() - X        Local mY:Int = MouseY() - Y        If mX<0 Or mX>W Return 0        If mY<0 Or mY>H Return 0        Return ID    End MethodEnd Type`
A Bundle of Type Buttons

With adding a Global TList we are able to self contain all buttons inside the TYPE. Now the function DrawAll() care about the painting of all buttons and the function CheckAll:Int() checks the mouse for all buttons:

`SuperStrictGraphics 800,300New TButton(1,100,200,120,80)New TButton(2,234, 45,222,33)New TButton(3,600, 100,50,180)Global MouseState:IntRepeat    Cls    MouseState = MouseHit(1)    TButton.DrawAll    Local Pressed:Int = TButton.CheckAll()    If Pressed>0 Then         Print "Button " + Pressed + " pressed"    EndIf     Flip Until AppTerminate()Type TButton    Global All:TList = New TList        Field ID:Int, X:Int, Y:Int, W:Int, H:Int        Method New (id:Int, x:Int, y:Int, w:Int, h:Int)        Self.ID = id        Self.X  = x        Self.Y  = y        Self.W  = w        Self.H  = h        All.Addlast Self    End Method          Function DrawAll()        For Local loc:TButton = EachIn All            loc.Draw        Next     End Function        Method Draw()        DrawRect X, Y, W, H        DrawText id, x,y-30    End Method            Function CheckAll:Int ()        If MouseState=0 Return 0        For Local loc:TButton = EachIn All            Local result:Int = loc.Check()            If result>0 Return result        Next        Return 0        End Function         Method Check:Int()        Local mX:Int = MouseX() - X        Local mY:Int = MouseY() - Y        If mX<0 Or mX>W Return 0        If mY<0 Or mY>H Return 0        Return ID    End MethodEnd Type`

...back from Egypt

#### Midimaster

#29
Button II: More Design and Functionality

After we switched now to TYPE buttons it is easy to add more features to the buttons. Because we have to code it only once and all buttons will react immediately.

Example: Selected Buttons

To enable the buttons to SELECTED/UNSELECTED we only have to add a new Property SELECTED and write reactions in DRAW() and CHECK(). Additionally we now return back not longer the pressed state as an INTEGER, but the whole button itself (WHICH). So we can now handle this "last action" button also outside the TYPE.

User Derron points me to the problematic, that a MouseHit() call should not be inside a function, but at a central place. That is necessary, because other part of the app may also need to know the MouseState. So I define a GLOBAL variable MOUSESTATE, which keeps the state until the next FLIP

`SuperStrictGraphics 800,300New TButton(1,100,200,120,80)New TButton(2,234, 45,222,33)New TButton(3,600, 100,50,180)SetClsColor 0,155,155Global MouseState:IntRepeat    Cls    MouseState = MouseHit(1)    TButton.DrawAll    Local Which:TButton = TButton.CheckAll()    If Which         If Which.Selected=1            Print "Button " + Which.ID + " selected"                Else             Print "Button " + Which.ID + " un-selected"        EndIf     EndIf     Flip Until AppTerminate()Type TButton    Global All:TList = New TList        Field ID:Int, X:Int, Y:Int, W:Int, H:Int    Field Selected:Int         Method New (id:Int, x:Int, y:Int, w:Int, h:Int)        Self.ID = id        Self.X  = x        Self.Y  = y        Self.W  = w        Self.H  = h        All.Addlast Self    End Method          Function DrawAll()        For Local loc:TButton = EachIn All            loc.Draw        Next     End Function        Method Draw()        SetColor 1,1,1        DrawRect X, Y, W, H        SetColor 111,111,111        DrawRect X+1, Y+1, W-2, H-2        SetColor 155,155,155        If Selected=1            SetColor 155,255,155                EndIf         DrawRect X+4, Y+4, W-7, H-7        SetColor 1,1,1        DrawText id, x + (w-TextWidth(id))/2,y + (h-TextHeight("T"))/2+2    End Method            Function CheckAll:TButton ()        If MouseState=0 Return Null        For Local loc:TButton = EachIn All            Local result:Int = loc.Check()            If result>0 Return loc        Next        Return Null        End Function         Method Check:Int()        Local mX:Int = MouseX() - X        Local mY:Int = MouseY() - Y        If mX<0 Or mX>W Return 0        If mY<0 Or mY>H Return 0        Selected=1-Selected                Return ID    End MethodEnd Type`

(the GIF animation does not show the real shading quality. Check the app!)

Every time, when a Check() is successful, it calls the method DeSelect() to reset the other buttons. Therefore, we need a variable RADIO-GROUP to know which buttons belong to the same group.

`    Method Check:Int()        Local mX:Int = MouseX() - X        Local mY:Int = MouseY() - Y        If mX<0 Or mX>W Return 0        If mY<0 Or mY>H Return 0        Selected=1-Selected        If Selected=1 Deselect RadioGroup EndIf        Return ID    End Method Method DeSelect(Group:Int) If Group=0 Return For Local loc:TButton=EachIn All If loc=Self Continue If loc.RadioGroup=Group loc.Selected=0 EndIf Next End Method `
We scan all buttons and select each, that belong to the group. To prevent switching off the newly pressed button we have to exclude SELF.

Here is the complete code:
`SuperStrictGraphics 800,400For Local i:Int= 0 To 5 New TButton(i+1,100+i*66,200,66,122, 1)Next SetClsColor 0,155,155Global MouseState:IntRepeat    Cls    MouseState = MouseHit(1)    TButton.DrawAll    Local Which:TButton = TButton.CheckAll()    If Which        If Which.Selected=1            Print "Button " + Which.ID + " selected"               Else            Print "Button " + Which.ID + " un-selected"        EndIf    EndIf    FlipUntil AppTerminate()Type TButton    Global All:TList = New TList       Field ID:Int, X:Int, Y:Int, W:Int, H:Int    Field Selected:Int, RadioGroup:Int Global Images:TImage       Method New (id:Int, x:Int, y:Int, w:Int, h:Int, group:Int) If Images=Null Images=LoadAnimImage("VintageButton.png",66,122,0,4) EndIf         Self.ID = id        Self.X  = x        Self.Y  = y        Self.W  = w        Self.H  = h Self.RadioGroup = group        All.Addlast Self    End Method        Function DrawAll()        For Local loc:TButton = EachIn All            loc.Draw        NextSetColor 255,255,255 DrawImage  Images,0,0,0    End Function       Method Draw()        SetColor 255,255,255 Local ImageNr:Int = FindBest()        DrawImage Images,X,Y,ImageNr     End Method    Method FindBest:Int() If Selected=0 Return 0 Return 3 End Method     Function CheckAll:TButton ()        If MouseState=0 Return Null        For Local loc:TButton = EachIn All            Local result:Int = loc.Check()            If result>0 Return loc        Next        Return Null       End Function       Method Check:Int()        Local mX:Int = MouseX() - X        Local mY:Int = MouseY() - Y        If mX<0 Or mX>W Return 0        If mY<0 Or mY>H Return 0        Selected=1-Selected        If Selected=1 Deselect RadioGroup EndIf        Return ID    End Method Method DeSelect(Group:Int) If RadioGroup=0 Return For Local loc:TButton=EachIn All If loc=Self Continue If loc.RadioGroup=Group loc.Selected=0 EndIf Next End Method End Type`
You need this image to run the example: