SyntaxBomb - Indie Coders

General Category => Tutorials => Topic started by: Midimaster on December 22, 2021, 18:29:29

Title: Learning Basic For Total Beginners (BlitzMax NG)
Post by: Midimaster on December 22, 2021, 18:29:29
What do i have to be able to do?

Nothing...
This will be a tutorial in a lot of very small lessions. Each step can be learned in 10 Minutes. If you don't know anything about writing games or apps you will be right here!  It would help if you know a little bit about Mathematics and Computers.


What do I need to install?

Nothing!
We will use BlitzMax NG, because the installation is super easy: You need to download and (unzip) nothing more than one single file.
Download:
https://blitzmax.org/downloads/

Installation Manual: 
https://blitzmax.org/docs/en/setup/get_started/


Where can I get help?

Here in the Forum!
Please do not post into this tutorial. Feel free to start as many additional threads here:
https://www.syntaxbomb.com/blitzmax-blitzmax-ng/ 


Now lesson 1

Open BlitzMax, write the following lines and then press F5 to start your first program:

Advise:
User TomToad tells us, that the very first start (direct after installation of BlitzMax) can last 2 minutes. The future starts with F5 will only need some seconds until your app appears. Background: BlitzMax needs to prepare hundred of libraries and the reference manual in the very first moment.)


Code (BlitzMax) Select
Graphics 600,400
DrawText "Hello World", 100,200
Flip
WaitKey


Graphics opens a window with a size of 600x400 Pixel
DrawText writes a text at the position 100 pixel from left, 200 pixel from top into the window.  You can read BASIC instruction like normal "english sentences": "Please draw the text 'Hello World' at the coordinate (100,200) "
Flip is a must-have and makes the content of the window visible (more later)
WaitKey waits for a keypress of the user

After WaitKey there is no further codeline. So, when the user press any key your app stops.



Challenge I: Write in every corner
Try to write a app, that writes a word in every corner of the screen.



Challenge II: Small App
Try to create a window, that has only 200x200pix.



Challenge III: Test the limits
Now play around with this commands, change parameters or text content and observe, what happens. You can add as many DrawTexts as you like. But Graphics and Flip are only allowed one time an app!!



Advise:
Critics to this tutorial are welcome, but please do not post here, but there:
https://www.syntaxbomb.com/blitzmax-blitzmax-ng/critics-and-advises-to-blitzmax-tutorial/
Title: A Typical Main Loop
Post by: Midimaster on December 22, 2021, 22:59:11
Lesson II: A Minimum Template For All Your Apps

Our next example expands the app to the typical 3-part structure:
Code (BlitzMax) Select
'pre-code-zone:
Graphics 600,400
Global x=5

'main-loop-zone:
Repeat
Cls
DrawRect X,200,5,5
x = x+1
Flip
Until AppTerminate()

' finish-code-zone:
Print  x


Global defines a variable. In BlitzMax NG you can use new Variablename only, when you defined it. This prevents typos.
Advise:
User Hotshot tells us, that the BlitzMax NG forces us to define new variablename before using it. This can be done with a Local or a Global command (more later...)



Repeat ... Until defines beginning and end of a code loop. The code between this two markers will execute again and again.

Until AppTerminate() enables to leave the main loop, when the user clicked on the "Top-Right-X-In-Window"

Cls (clear screen) will clean the window for the next round of drawing.

Print sends a log text to the BlitzMax Output-TAB. This enables the developer to observe the program-flow and the variable's contents.

DrawRect is another of the Draw...commands and draws a rectangle. Other commands are:
and two more complex:
If you want to know more about these commands and how they are used, write the command in a code line and tab twice on F1-key. The BlitzMax HELP will open the reference of this command


The Program Flow

Programs run the code top down. But commands like Repeat...Until enable to jump again to a top marker. When the program reaches the Until it checks a "condition" and then normaly jumps up to the line with Repeat. But if the condition is true then it leaves the loop and works the next code line below the Until. In our example the "condition" is AppTerminate(), a function that checks whether the user closed the window. So the loop keeps looping until the user closes the window. You can read BASIC instruction like normal "english sentences": "Please repeat until the user closes the window!"

Within the main loop we paint the whole screen again and again. It start with a black screen Cls followed by all the elements to want to show. This can be hundreds ore thousands... it does not matter, BlitzMax is fast enough to do it in lightspeed. To prevent that the user might watch and notice this sequential painting, all the painting happens in the background until Flip brings it to the foreground.


Variables

This...
... X=5
... looks like you know it from Mathematics (spoken: "X equals 5"), but there is a difference... In BASIC we should speak "Let X now be 5". So this is not an equation, but more like a command.

When thinking this way you can understand this line:
X=X+1
Mathemathical nonsense, but as a command it says: "from now on let x be the result of (x+1)". This means if x is still 5, after this command it will be 6. And in our example (because of the Repeat-Until-Loop) it will become 7 and 8 and so on...

You can use single letters as variable name, but also long words, except these words that are already commands like DrawRect

In our case this X is counted up in the main loop. And as a part of the DrawRect command line it moves the rectangle from left to right side.


And these lines like ' pre-code-zone ?

all line with an apostrophe ' at the beginning are no commands but comments. You can write whatever you want, it will not become a part of the app. We use this only for developers notice.



Challenge I: Move the Rectangle other ways
Try to change the code, that the rectangle move from top to bottom



Challenge II: Resize A Circle
Try to change the code, that a Circle (Oval)  increases in size, while moving from right bottom to left top



Challenge III: Play with Draw... commands
Now try to add other Draw...commands and learn by watching what happens.


Advise:
Critics to this tutorial are welcome, but please do not post here, but there:
https://www.syntaxbomb.com/blitzmax-blitzmax-ng/critics-and-advises-to-blitzmax-tutorial/
Title: If This Is True Then....
Post by: Midimaster on December 24, 2021, 10:36:16
Lesson III: If This Is True Then....

In this chapter we talk about Comparisons, Decisions and Truth. On of the most relevant job of a app is to make decisions. "Condition" will become a keyword for you.

Let us first have a look on a typical decision:
Code (BlitzMax) Select
Global X=4
Global Y=3
If X=Y Then
     Print "are equal"
     ' now any stupid reaction:
     X=Y+1
Else
     Print "are not equal"
     ' now any stupid reaction:
    X=Y
Endif
Print "Now X=" + X


This part of a code checks if two variables are the same or not. Depending on the result it jumps into one of the two branches. At the EndIf both branches get back together and work both the rest of the code below EndIf

If....Then...Else...Endif divides the program flow into two branches related to the result of the check.


TRUE versus FALSE
Computers know two Conditions: TRUE or FALSE.  Both can be the result of an comparision, but also a TRUE returned by a function is good enough for a decision.

f.e. AppTerminate() return True if the use wants to close the window. Or MouseHit() checks if the mouse button is pressed. So also this would be good for a IF...decision:
' example 1:
If MouseHit(1) Then
     ' do something when  mouse button is pressed
Endif

' example 2:
If AppTerminate() Then
     ' do something when  user wants to close the window
Endif

' example 3 nonsense but possible:
If TRUE Then
     ' do something always
Endif 


MouseHit(1) checks whether the user has pressed LEFT mouse button



Now back to our first example:

Code (BlitzMax) Select
Global X=4
Global Y=3
If X=Y Then
     Print "are equal"
     ' now any stupid reaction:
     X=Y+1
Else
     Print "are not equal"
     ' now any stupid reaction:
    X=Y
Endif
Print "Now X=" + X


As you can see inside the branches the X will be already changed into someting new. This is aloowed and does not linger affect the prior decision. In our example we make X unequal to Y in the case where it was equal. And we make it equal in the case, where it was unequal. Nonsense... but allowed. You can do whatever you want inside a IF-branch.

You can also combine more than one decision:

Code (BlitzMax) Select
Global X=3

If X=3 Then
     X=44
ElseIf X>20
     Print "X is big!"
ElseIf X=44
    Print "You will become millionaire now!"
Else
    Print "none of them all"
Endif
Print "Now X=" + X


Don't forget the fact that in branch "3",  after X becomes 44 the program flow jumps directly to the Endif. This means you will not reach the check against 44, although this code line is written below branch "3".

Now a task for you: How do you need to change the first code line...
X=3
...to achieve  that the branch 44 can be reached and you will become millionaire?

Do you think...
x=44
... is the right answer? No! No number can reach the branch "44" because all numbers higher than 20 always jump
already to the branch "X is big" and will never reach the branch "44"


Challenge I: Change direction

Rewrite the little app in lesson two with a change: The rectangle should change its direction, when X reaches 400. Now the rectangle should move (vertical ) downwards.


Advanced Challenge II: Change direction again

Rewrite the little app in lesson two with two changes: The rectangle should change its direction, when X reaches 400. Now the rectangle should move downwards. When Y reaches 300 the rectangle should change the direction again: now moves horizontal to the right until it leaves the screen.




Advise:
Critics to this tutorial are welcome, but please do not post here, but there:
https://www.syntaxbomb.com/blitzmax-blitzmax-ng/critics-and-advises-to-blitzmax-tutorial/
Title: Re: Learning Basic For Total Beginners (BlitzMax NG)
Post by: Hotshot on December 24, 2021, 16:23:56
.
Title: SuperStrict - A Good Coding Style
Post by: Midimaster on December 26, 2021, 03:51:09
Lesson IV: SuperStrict - A Good Coding Style

Please write code, that prevents bugs and is readable for others too. Also you should understand, what you have written, when you re-open your  project one year later. BlitzMax  helps you to keep to strict rules. This will prevent typos and bugs and save a lot of time for you.

So our standard template  gets a new "look":
Code (BlitzMax) Select
SuperStrict
Graphics 600,400
Global x:Int=5
Global t:String=" the result of X/600 ="
Repeat
Cls
DrawRect X,200,5,5
Local result:Double = x/600.0
DrawText t + result , 100 , 100
x = x+1
Flip
Until AppTerminate()
Print  x


SuperStrict forces the user to define variables clearly. Is always the first line of every BlitzMax code!!!

Global x:Int=... defines a variable with name "x" as a INTEGER (whole number) variable

Global t:String=... defines a variable with name "t" as a STRING (text only) variable

Local result:Double =... defines a variable with name "result" as a DOUBLE-FLOAT (floating point number) variable

Global or Local?
GLOBAL defines a variable as everywhere accessible. This means everywhere in your code you can refer to this variable. The variable x is used before the loop, inside the loop and also after the loop. So you need to set it to GLOBAL.

LOCAL defines a variable as accessible only inside it's surrounding loop or function. This means outside the loop the variable is unknown. The variable result appears only inside the Repeat/Until-loop, so you can define it as LOCAL.

For beginners it is difficult to determine whether GLOBAL or LOCAL is right. So you need to know, that in your first steps is not important: Define all variables as GLOBAL until you know, why you would decide a certain variable as LOCAL. During the following lessons you will again and again get more informations about LOCAL use.



INT FLOAT STRING?
Define a variable as INT, if you are sure, that it will only contain whole numbers like 0, 333 or -2345.

Define a variable as DOUBLE, if you already know, that it will contain floating point numbers like 0.7 or  3.33 or -234.5.

Define a variable as STRING, if it will contain text and you will not use it in calculations. Text means also numbers or special letters like a question mark.  Examples: "Hello" or "1-2-3" or "<!>"


INT = INTEGER

Integers can only contain whole numbers. If you add a floating point number the result will be a integer truncate and mathematical wrong:
Global x:Int=4
x = x + 4.5
' now x will be 8

INT's can contain numbers from -2Mio to +2Mio. If you need bigger number use the definition :LONG


DOUBLE = FLOATING POINT NUMBER
DOUBLE's can calculte with floating points. The result is because of "double precision" fast and exact.
In former times we often used FLOAT (a single precision floating point) but it is not that accurate:
Global tt:Float=123450
Print tt/12345.0
' you would expect the result is 10, but it is wrongly 4.69129229



STRING = TEXT
In BlitzMax Strings ca be "added", but  this means the letters get connected:
Global a:String="01"
Global b:String="22"
Global c:String= a + b
' c is now "0123"




Challenge I: Stop at 234

Change the code above, that also X is diplayed in the sentence (and the result is displayed too), stop the app exactly at x=234 and compare your text with mine here:
the result of 234/600 = 0.39000001549720764
Is it exactly the same?



Challenge II: Sum of Floating Values

Calculate the sum of all results. Display this result as an integer. Stop at x=234 and compare the final result with my result:   Is your result the same?
"the result of 234/600 = 45"
Do you see the same number?


Challenge III: Sum of all integer numbers

Write an app that outputs (PRINT) text lines like this. Each time the user presses any key the list should become one line longer:
0 + 1 = 1
0 + 1 + 2 = 3
0 + 1 + 2 + 3 = 6
0 + 1 + 2 + 3 + 4 = 10
0 + 1 + 2 + 3 + 4 + 5 = 15
0 + 1 + 2 + 3 + 4 + 5 + 6 = 21
...



Advise:
Critics to this tutorial are welcome, but please do not post here, but there:
https://www.syntaxbomb.com/blitzmax-blitzmax-ng/critics-and-advises-to-blitzmax-tutorial/
Title: Re: Learning Basic For Total Beginners (BlitzMax NG)
Post by: Midimaster on December 28, 2021, 01:29:42
Lesson V: Moorhuhn Lets Do Our first Game

One of the biggest problem of beginners is not to understand the commands but the knowing how to solve a problem. Today we try to learn how to start a game, see the single steps and transfer them to code.

do you know the old "Moorhuhn" game from the 90th? A landscape, a funny chicken and somebody with a gun. The aim of the game is to shoot the chicken. Thats all. But how to start?


The template

We always start with this empty template:
Code (BlitzMax) Select
SuperStrict
Graphics 800,600

Repeat
Cls

Flip
Until AppTerminate()


You can choose a different size of your window, but you should still be able to watch the OUTPUT window of the IDE when your game runs! We are not playing! We are developing.

Create a new folder for our project and save the code as "moor.bmx" into it.


The landscape

As long as you use your game only for your private home-use you can make use of nice landscape fotos in the internet. Or you take this free png in the attachment:
(https://www.syntaxbomb.com/index.php?action=dlattach;topic=8765.0;attach=5098;image)
Download it and save it as "land.png" into our new project folder next to the code.

Code (BlitzMax) Select
SuperStrict
Graphics 800,600
Global Land:Timage = LoadImage("land.png")
Repeat
Cls
DrawImage Land , 0 , 0
Flip
Until AppTerminate()



Global Land:TImage beside the INTEGER, DOUBLE and STRING variable types there are a lot more "special" types. Here: A variable from type Timage can contain a Images.

LoadImage("...") loads a image file into a special TImage-variable.

DrawImage image , x, y draws the image at coordinates (x/y)

You should test each step we coded. So run now F5 to check whether the app starts. Starting your app as often as possible means, that if a problem occurs you always know, that it has to do with the last commands you wrote. It is no good idea to write dozends of code line without testing intermediate steps.



The moorhuhn

This works nearly the same. Find a chicken and add it to the code like before. If you do not find any picture you can also use placeholder for the moment. Often this is not a bad idea, because your team is not ready with the paintings but you already can continue coding. So I demonstrate this way now:


Code (BlitzMax) Select
SuperStrict
Graphics 800,600
Global Land:TImage    = LoadImage("land.png")
Global Chicken:TImage = LoadImage("chicken.png")
Global cX:Int, cY:Int=300
Repeat
Cls
DrawImage Land    , 0 , 0
cX = cX +2
'DrawImage Chicken , cX , cY
'   comment out the original command, and use DrawRect instead:
DrawRect cX, cY, 60,40

Flip
Until AppTerminate()


As you see, we move the "chicken" from left to right by adding 2 to chicken-variable cX.



The crosshairs

We connect two "lines" with the mouse:


Code (BlitzMax) Select
SuperStrict
Graphics 800,600
Global Land:TImage    = LoadImage("land.png")
Global Chicken:TImage = LoadImage("chicken.png")
Global cX:Int, cY:Int=300
Global mX:Int, mY:Int
Repeat
Cls
DrawImage Land    , 0 , 0
cX = cX +2

'DrawImage Chicken , cX , cY
'   comment out the original command, and use DrawRect instead:
SetColor 111,0,0
DrawRect cX, cY, 60,40

mX=MouseX()
mY=MouseY()
SetColor 255,255,255
DrawRect mX-25, mY-1,50,3
DrawRect mX-1, mY-25,3,50

'HideMouse
Flip
Until AppTerminate()
ShowMouse


MouseX() and MouseY() return the mouse coordinates into a variable

Setcolor R,G,B changes the Draw Color for all Draw... commands


HideMouse Hides the standard mouse icon

ShowMouse Shows the standard Mouse (here: before leaving the app )

We produce the lines of the crosshairs with DrawRect. As it has a length of 50pixels we have to start 25pixel left of the mouse to get a symetric design.


Color R=Red G=Green B=Blue

The Setcolor command changes the drawing color for the subsequent Draw... commands.You have to set three parameters to define a color: R G B. Colors are a combination of this three subcolors. Each value can be between 0 (dark)and 255 (bright). Here are some examples:


SetColor 255, 0 , 0  = red
SetColor  0 ,255, 0  = green
SetColor  0 , 0 ,255 = blue
SetColor 255,255, 0  = yellow
SetColor 255,255,255 = white
SetColor  1 , 1 , 1  = black


to be continued tomorrow....



Challenge I: Follow the Mouse

Write an app, where a rectangle slowly follows the mouse. Means if you move the Mouse quickly to the right, the rectangle follows, but slowly.



Challenge II: Dia-Show

Write an app, where the user can display fotos and switch on with pressing any key.



Challenge III: Nocturnal Hunt

Modify the Moorhuhn and change it into a "night-version". We are hunting in the dark



Advise:
Critics to this tutorial are welcome, but please do not post here, but there:
https://www.syntaxbomb.com/blitzmax-blitzmax-ng/critics-and-advises-to-blitzmax-tutorial/

Title: Moorhuhn II Collision and Sound
Post by: Midimaster on December 28, 2021, 09:33:22
Lesson VI: Moorhuhn Collision and Sound

Today we learn how to outsource code lines from a main loop into a selfmade Function() . This keeps our main loop short and clean. You will make some noise and kill some chicken.


Let's shoot at the poor animal.

Code (BlitzMax) Select
SuperStrict
...
Global Shot:TSound=LoadSound("fire.ogg")
...
Repeat
...
cX = cX +2
GunFire()     
SetColor 255,255,255
DrawRect mX-25, mY-1,50,3
...
Until AppTerminate()
...
Function GunFire:Int()
mX=MouseX()
mY=MouseY()
If MouseHit(1)
PlaySound Shot
EndIf
Return FALSE
End Function


All the code related to Mouse and  shooting is now outsourced. This can be done by creating a new function. In the original place we "call" the new function. Each time, when the program flow reaches that point, it will jump into the function and process all code lines there. Then it will return to the prior point to continue in the main loop.

Functions can use GLOBAL variables. This is not a good coder style, but it is possible, so we do it for now. As you already know from the function AppTerminate() or from function MouseX() the functions can return values. So can our selfmade function too. This is the reason why the function-name contains the appendix :Int. From the "outside" the function looks like an INTEGER value.

Function Name:Int() returns an INT value to the calling code line

Global Shot:TSound TSound is a special variable Type, that can contain sounds

LoadSound("...") loads a sound from disk and returns it to a variable of type TSounds

PlaySound xyz Plays the sound stored in a variable xyz

Return xyz returns the content of the variable xyz to the calling line. Also expressions like FALSE are possible values

Search for a "Shoot"-noise in the internet or use the sound here in attachment. Copy it into the project folder. Switch your speaker on, start the app with F5... and now it is getting loud.


Collision

Finding out if two coordinates are at the same place is very easy. We only have to test whether X and Y in both coordinates are the same:
If (mX=cX) AND (mY=cY) Then
     ' means collision
     Return TRUE
Endif


But if you want to find out, whether a coordinate is inside a rectangle it is more complicate. The mX can be:

Now try to express this in a "condition" code line.

We are interested in case 2: Between. If you do not know how to transfer "between" to a IF-code line perhaps you could tell me case 1 and 3?

If mX<cX Then
     ' outside left
     Return FALSE
Endif
If mX>(cX+60) Then
    ' outside right
    Return FALSE
Endif
' the same for the y-coordinates:
If mY<cY Then
     ' outside above
     Return FALSE
Endif
If mY>(cY+60) Then
    ' outside below
    Return FALSE
Endif
' the program flow reaches this point only in those cases,
' where the coordinates are inside the rectangle.
'  That is, what you have been looking for !!!
Return TRUE


There are always several ways to get to a solution!

Of course you can code it directly:
If (mX>cX) And (mx<cX+60)
If (mY>cY) And (mY<cY+40)
' collision
Return True
EndIf
EndIf



Now the reaction in the main loop:
If GunFire() =True Then
cX=-100
Points=Points+1
EndIf

this moves the chicken to the very left, so that it disappears
We count the Hits

The whole app:
Code (BlitzMax) Select
SuperStrict
Graphics 800,600
Global Land:TImage    = LoadImage("land.png")
Global Chicken:TImage = LoadImage("chicken.png")
Global Shot:TSound=LoadSound("fire.ogg")
Global cX:Int, cY:Int=300
Global mX:Int, mY:Int
Global Points:Int
Repeat
Cls
DrawImage Land    , 0 , 0
'DrawImage Chicken , cX , cY
'   comment out the original command, and use DrawRect instead:
SetColor 111,0,0
DrawRect cX, cY, 60,40
cX = cX +2
If GunFire() =True Then
cX=-100
Points=Points+1
EndIf
SetColor 255,255,255
DrawRect mX-25, mY-1,50,3
DrawRect mX-1, mY-25,3,50
'HideMouse
DrawText "Points: " + points, 700,550
Flip
Until AppTerminate()
ShowMouse


Function GunFire:Int()
mX=MouseX()
mY=MouseY()
If MouseHit(1)
PlaySound Shot
If (mX>cX) And (mx<cX+60)
If (mY>cY) And (mY<cY+40)
' collision
Return True
EndIf
EndIf
EndIf
Return False
End Function



Challenge I: Play a drum machine

In the attachment you find two drum sound: a SnareDrum SD2.ogg and a BassDrum BD2.ogg. With this you can build a drum computer. Draw two circles on the screen. When the user clicks in the left the BassDrum sound plays, when clicking in the right the  SnareDrum sound plays.


Challenge II: Forest Background Sound

Add a forest background sound to the Moorhuhn game. You will find several examples in the internet. As you need a WAV or a OGG file, use a internet file converter to convert the file as you need it.



Challenge III: Painting App

Write an app, where the user can draw on the screen, switch colors and clear the screen when pressing a "NEW" button.




Advise:
Critics to this tutorial are welcome, but please do not post here, but there:
https://www.syntaxbomb.com/blitzmax-blitzmax-ng/critics-and-advises-to-blitzmax-tutorial/

Title: Moorhuhn Random Movements
Post by: Midimaster on December 30, 2021, 00:09:15
Lesson VII: Moorhuhn Random Movements

In our last version the chicken had no chance. It always comes from the same corner, goes the same way and same speed. Today  i'll stand by the moorhuhn... give them a chance..

To make the game varied and entertaining we change the moorhuhn's moves by random.

We replace the old movement by a new functionname:

main loop:
Code (BlitzMax) Select
...
' some new chicken variables:
Global cX:Double=9999, cY:Double, cAddX:Double, cAddY:Double

Repeat
Cls
DrawImage Land    , 0 , 0
...
SetColor 111,0,0

        ' --------  n e w   p a r t ---------------
TheChicken()
Print "c:" + Int(cx) + " " + Int(cy)
        ' ----------------------------------------

DrawRect cX, cY, 60,40
If GunFire() =True Then
cx=9999           ' <--- NEW
Points=Points+1
EndIf
...
Until AppTerminate()

Function TheChicken()

End Function



and at the end of the code we add a the new empty function function:


We have to think about two situations.

1. send a new chicken when it is was shot down or flew outside the screen
2. move the chicken as long as it is visible on the screen.

Function TheChicken()
If (cY<-101) Or (cY>601) Or (cX <-201) Or (cX>1001)
' chicken is out of screen
Else
' chicken is visible
Endif
End Function



Perhaps you ask yourself, why we not simply use the screen-dimension 800 and 600 in our condition?
If (cY<0) Or (cY>600) Or (cX <0) Or (cX>800)

There are several reasons. A chicken is already (or still) visible at position -50, when it's width is 60!!! So we need to do this...
If (cY<-61) Or (cY>601) Or (cX <-61) Or (cX>801)
...to guarantee that is is really gone.

But additonal we do not want to start the next chicken in the very moment, when the last has gone. So we start them at a distance of -200 to the left and +200 to the right. Now the next chicken needs a moment until it reaches the screen border. But this means that we cannot determine a "gone chicken" by simply determine a position left of 0, because this could also be a "coming chicken". So we enlarge the area to:

If (cY<-101) Or (cY>601) Or (cX <-201) Or (cX>1001)


To force a fresh chicken at the game start or when we shot it down, we simply set the chicken far outside the screen:
Global cX:Double=9999, cY:Double, cAddX:Double, cAddY:Double
If GunFire() =True Then
cx=9999

This forces the function TheChicken() to start a new chicken

to observe what happens with our variables cX and cY we log them to the DEBUG (OUTPUT) window of BlitzMax
Print "c:" + Int(cx) + " " + Int(cy)
Use this PRINTs extensively during develpoment. You will often save a lot of time in finding bugs by recognizing, that the variables are not doing, what you expected.
 
Starting A Chicken

Code (BlitzMax) Select
Function TheChicken()
If (cY<-100) Or (cY>600) Or (cX <-201) Or (cX>1001)
'send a new the chicken
cY=Rand(100,500)
If Rand(0,1)=0
'coming from right
cX=1000
cAddX=Rnd(-7 , -4)
Else
'coming from left
cX=-200
cAddX=Rnd(4 , 7)
EndIf
cAddY=Rnd(-3 , +3)
Else
....


Rand(From, To) A function that returns an INTEGER random number in the range of FROM to TO.

Rand(10,20)
produces a random number between 10 and 20.



Rnd(From, To) A function that returns an random DOUBLE FLOATING POINT number in the range of FROM to TO.

Rnd(0.3 , 1.8)
produces a random number between 0.3000 and 1.7999.


If Rand(0,1)=0   ' 50:50 joker
'coming from right
cX=1000
cAddX=Rnd(-7 , -4)
 
A chicken coming from the right side will start at x=1000pixels and has a negative speed  between 4 and 7 pixels.


Else
'coming from left
cX=-200
cAddX=Rnd(4 , 7)
 
When the chicken is coming from the left side it will start at x=-200pixels and has a positive speed between 4 and 7 pixels.

To get also diagonal movements we also define a light y-speed:
cAddY=Rnd(-3 , +3)


The Movement

Code (BlitzMax) Select
Function TheChicken()
If (cY<-100) Or (cY>600) Or (cX <-201) Or (cX>1001)
...
Else
cX = cX + cAddX
cY = cY + cAddY
EndIf
End Function



Challenge I: Display the outer space

Write an app that display something like a look to the sky with a lot of stars.


Challenge Ib: Produce Snow
As an variant write now an app that display something like moving snow.


Challenge II: Roll A Dice

Write an app that display a new dice result (1-6) each time the user presses any key.


Challenge IIb: Show three dices

Now expand the app to display the last three dices the user rolled.


Advise:
Critics to this tutorial are welcome, but please do not post here, but there:
https://www.syntaxbomb.com/blitzmax-blitzmax-ng/critics-and-advises-to-blitzmax-tutorial/


Title: Moorhuhn Animation
Post by: Midimaster on December 31, 2021, 01:13:32
Lesson VIII: Moorhuhn Animation

today we remove the stupid rectangle and replace it by a "real" flying chicken: You will learn how to draw animated Sprites.


Preparation

For animations we need a PNG-file, which contains a long strip with a number of "frames". Each frame contains the "same" picture, but shows a different step of the animation.

Step 1:
I found a GIF-file for you with a nice chicken. You need to download it and save it into our project folder with the exact same name  the browser offers: moorhuhn01.gif
http://www.ollis-page-online.de/specials/moorhuhn_x/moorhuhn01.gif

Step 2:
BlitzMax cannot process this file-typ. So we need an app, that transforms this into our PNG-file format. In the attachment of this post you find a BMX-file that can do this. It is a automated app, which automatically transforms moorhuhn01.gif  to chicken.png.


Step 3:
Copy this BMX-files to our project folder, load it into BlitzMax and start it once with F5. It will start, run and open a window. If you can see the animated chicken, everything was processed perfect. Close the app. You will find a new file chicken.png next to the moorhuhn01.gif. Now you can close BlitzMax and return to our project. Please do not hand over or publish the image-files  because of the copyright.

Step 4:
For a better understanding you can open the PNG file as a regular TImage or in a paint-app to see that there are 12 frames with only little differences. It is like in a thumb cinema: When we display the frames very fast it will look like a movement.



This was the code version of lesson VII:

Code (BlitzMax) Select
SuperStrict
Graphics 800,600
Global Land:TImage    = LoadImage("land.png")
Global Chicken:TImage = LoadImage("chicken.png")
Global Shot:TSound=LoadSound("fire.ogg")
Global cX:Double=9999, cY:Double, cAddX:Double, cAddY:Double
Global mX:Int, mY:Int
Global Points:Int
Repeat
Cls
DrawImage Land    , 0 , 0
'DrawImage Chicken , cX , cY
'   comment out the original command, and use DrawRect instead:
SetColor 111,0,0

TheChicken
Print "c:" + Int(cx) + " " + Int(cy)
DrawRect cX, cY, 60,40

If GunFire() =True Then
cx=9999
Points=Points+1
EndIf
SetColor 255,255,255
DrawRect mX-25, mY-1,50,3
DrawRect mX-1, mY-25,3,50
'HideMouse
DrawText "Points: " + points, 700,550
Flip
Until AppTerminate()
ShowMouse


Function GunFire:Int()
mX=MouseX()
mY=MouseY()
If MouseHit(1)
PlaySound Shot
If (mX>cX) And (mx<cX+60)
If (mY>cY) And (mY<cY+40)
' collision
Return True
EndIf
EndIf
EndIf
Return False
End Function


Function TheChicken()
If (cY<-100) Or (cY>600) Or (cX <-201) Or (cX>1001)
'reset the chicken
cY=Rand(100,500)
If Rand(0,1)=0
'coming from right
cX=1000
cAddX=Rnd(-7 , -4)
Else
'coming from left
cX=-200
cAddX=Rnd(4 , 7)
EndIf
cAddY=Rnd(-3 , +3)
Else
cX = cX + cAddX
cY = cY + cAddY
EndIf
End Function



Now we change to animated Images:

We need only four new lines:

Code (BlitzMax) Select
SuperStrict
Graphics 800,600
Global Land:TImage    = LoadImage("land.png")
Global Chicken:TImage = LoadAnimImage("chicken.png",140,140,0,12)  ' <------- NEW LINE
Global AnimStep:Int                                                ' <------- NEW LINE
Global Shot:TSound=LoadSound("fire.ogg")
...
Repeat
Cls
DrawImage Land    , 0 , 0
DrawImage Chicken , cX , cY, AnimStep          ' <------- NEW LINE
AnimStep = (AnimStep +1) Mod 12                ' <------- NEW LINE
TheChicken
If GunFire() =True Then
.....




LoadAnimImage( URL, Width, Height, 0, Count)  loads a stripped PNG into a variable of type TImage.

You have to declare the Height and Width of a single frame. The third parameter 0 means animation starts from frame 0 (most left frame). The 12 is our number of frames.

If you do not know exactly the frame-sizes you could ask for them with this trick:

Code (BlitzMax) Select
...
Global tempImage:TImage = LoadImage("chicken.png")
Global H:Int = tempImage.Height
Global W:Int = tempImage.Width/12
Print "Frames size:  Width=" + W + "   Height=" + H
Global Chicken:TImage = LoadAnimImage("chicken.png", W , H , 0 , 12)


Image.Width()  return the Width of a loaded Image
Image.Height()  return the Height of a loaded Image

Some variables have additional "properties", you can aks for them with adding a dot and an appendage. Later you will learn that these properties are called FIELDS in BlitzMax. More later....


DrawImage Chicken , cX , cY, AnimStep
Here you see, that the DrawImage function can have 4 parameters. This forth parameter defines which of the 12 frames will be displayed.

AnimStep = (AnimStep +1) Mod 12 The MOD function works like "remainder" in Mathematics. It divides numbers, but it does not show the result, but the remainder.

13 divided by  5 is 2 remainder 3
14 divided by  5 is 2 remainder 4
15 divided by  5 is 3 remainder 0
....


In coding we use this for circular jobs. Each time, when the variable AnimStep reaches 12 it will be reseted to 0.
AnimStep = (AnimStep +1) Mod 12

You could also do this in four code lines:
AnimStep = AnimStep +1
If AnimStep =12
     AnimStep=0
Endif



Now try out the new fantantic flying chicken game !

everything ok? No?


We Have To Mirror Vertically

Code (BlitzMax) Select
Repeat
Cls
DrawImage Land    , 0 , 0
If cAddX>0                     ' <------- NEW LINE
SetScale -1,1          ' <------- NEW LINE
EndIf
DrawImage Chicken , cX , cY, AnimStep
SetScale 1,1                 ' <------- NEW LINE



SetScale X,Y is a function with two "jobs". We can zoom single objects and define independent zooms for the X and the  direction. And we can change drawing direction by using negativ values.


SetScale 2  2          ' object is displayd with double size
SetScale 0.5 , 0.5     ' object is displayd with half size

SetScale 2 , 0.5     ' object is horzontaly streched

SetScale -1 , 1     ' object is verticaly mirored
SetScale  1 , -1     ' object is horizontaly mirored
SetScale  1 , -1     ' object looks like turned 180°
...



Dont' forget to reset the Scaling with a SetScale 1,1 after the desired objects are drawn.



Challenge I: Show them all

Write an app that shows all 12 Frames of the animated chicken at the same time sorted in a grid of 4x3 pictures



Challenge Ib: Slow Motion

Write an app that shows a "slow motion" of the animated chicken.  The "movie" is frozen and continues only one frame each time the user press any key.



Challenge II: Childlike Division

Write an app that displays the result of a division (of two random numbers) not as floating point but in a childlike way: "whole number quotient" followed by "remainder": 26 : 7 = 3 remainder 5



No Challenge

If you now play the game you can observe a bug. But can you find out why it happens? (please do not write a solution to the forum). How could we prove what happens? so next chapter will be : "How to find bugs"

more tomorrow....

Advise:
Critics to this tutorial are welcome, but please do not post here, but there:
https://www.syntaxbomb.com/blitzmax-blitzmax-ng/critics-and-advises-to-blitzmax-tutorial/

Title: How to find bugs
Post by: Midimaster on January 04, 2022, 01:23:17
Lesson IX: How To Find Bugs

One of the hardest jobs is to find a selfmade bug in your code. Of course you already did find out, that the gun does not shoot the chicken anymore. But what happened? At first it worked.. and now with the animated chicken it fails....

Additional you will learn more about "functions" and BlitzMax's "commands"


Stop The Game

A helpful tool is to interrupt the games movements and "freeze" the game. We can simply do this by prevent all addition to movement related variables. When you press a shortcut the chicken will freeze:
Code (BlitzMax) Select
...
Function TheChicken()
If KeyDown(KEY_S) Return   'check the keyboard key S and end the function
...

We need only one line to hold the chicken in the middle of the screen. Now you can shoot as often as you like to find out what happens.

Return stopps the function and returns immediately  to the calling code line.


KeyDown(KEY_CODE)  return the state of a keyboard key. You have to know the KEY_CODE of the key you want to check. The KEY_CODEs are listed in BlitzMax at (the right menu) HOME-USER INPUT-KEYCODES. Or guess them, the names are intuitive:
KEY_A ... KEY_Z   for ABC-keys,
KEY_1..KEY_9      for the numbers
KEY_ESCAPE        ESC-button
KEY_LEFT          left cursor key
....




Create New Drawing Commands

To make visible how our gun checks the hits, we will show it's sensitive area on the screen. Therefore we use a new BlitzMax Drawing command, that displays non-filled rectangles. So... what is the difference between BlitzMax commands and "our" (selfmade) functions? None! Both are functions(). Function can get parameters and can return results. A function like
DrawRect 100,100,200,40
has 4 parameters: X Y Width and Height. The function will need this 4 values to build the rectangle on the screen.

We can do the same:
DrawNonFilledRect 100,100,200,40
will Draw a non filled rectangle at 100,100 with width=200 and height=40. As there is no BltzMax function for that, we write our own:

Code (BlitzMax) Select
Function DrawNonFilledRect(X:Int, Y:Int, Width:Int, Height:Int)
DrawLine x,y,x+width,y
DrawLine x,y,x,y+Height
DrawLine x+width,y,x+width,y+height
DrawLine x,y+Height,x+width,y+Height
End Function

We draw 4 lines and calculate the start and the end points


and here the same with a more structured text:
Code (BlitzMax) Select
Function DrawNonFilledRect(X:Int, Y:Int, Width:Int, Height:Int)
DrawLine x       , y         , x+width , y        ' top line
DrawLine x       , y         , x       , y+Height '  left line
DrawLine x+width , y         , x+width , y+height ' right line
DrawLine x       , y +Height , x+width , y+Height ' bottom line
End Function

BlitzMax allows as many SPACE characters in code lines as you need to display the code comfortable for you


and here the same with a complete different code:
Code (BlitzMax) Select
Function DrawNonFilledRect(X:Int, Y:Int, W:Int, H:Int)
Local r:Int = X + W   'right
Local b:Int = Y + H   'bottom

DrawRect x , y , W , 1   ' top line
DrawRect x , y , 1 , H   '  left line
DrawRect r , y , 1 , H   ' right line
DrawRect x , b , W , 1   ' bottom line
End Function

Here we coded a solution with DrawRects instead of DrawLines. You decide how to solve a problem.

Local defines a variable only existing inside a function.
If you need additional variables inside your functions you always declare them LOCAL. This means they do not exist outside this function. They are temporary. This enables to use the the same name for variables inside and outside the function or in a third function. And they all do not affect each other.


Now add one of the DrawNonFilledRect() functions to your game.
And change our  gun-Function to:
Code (BlitzMax) Select
Function GunFire:Int()
mX=MouseX()
mY=MouseY()
If KeyDown(KEY_S)                        ' <-- NEW CODE LINE
SetColor 255,0,0                     ' <-- NEW CODE LINE
'make the sensitive area visible:     ' <-- NEW CODE LINE
DrawNonFilledRect(cX, cY, 60, 40)  ' <-- NEW CODE LINE
EndIf                                                   ' <-- NEW CODE LINE
If MouseHit(1)
PlaySound Shot
If (mX>cX) And (mx<cX+60)          ' <-- sensitive area
If (mY>cY) And (mY<cY+40)      ' <-- sensitive area
' collision
Return True
....


and start the game with F5. Press <S> when the chicken is in the middle of the screen


Now We See...
Now we can see, that the chicken is not at that place, where the shoot-sensitive area thinks it is. This happened, when we replaced the early Chicken-Rectangle with the Chicken-Image. The Chicken-Image has complete different dimensions. But we still look for a 60x40pix rectangle.

It is never a good idea to code absolute numbers in games. Better is to use variables or functions which know the current dimensions.

Code (BlitzMax) Select
SuperStrict
Graphics 800,600
Global Land:TImage    = LoadImage("land.png")
Global Chicken:TImage = LoadAnimImage("chicken.png",140,140,0,12)
Global cWidth:Int  = Chicken.Width                               '  <------- New LINE
Global cHeight:Int = Chicken.Height                               '  <------- New LINE
.....
Function GunFire:Int()
mX=MouseX()
mY=MouseY()
If KeyDown(KEY_S)                     
SetColor 255,0,0                   
'make the sensitive area visible:
DrawNonFilledRect(cX, cY, cWidth, cHeight)  ' <-- NEW CODE LINE
EndIf
If MouseHit(1)
PlaySound Shot
If (mX>cX) And (mx<cX+cWidth)               ' <-- NEW CODE LINE
If (mY>cY) And (mY<cY+cHeight)          ' <-- NEW CODE LINE



Image.Width knows the image's width.

Image.Height knows the image's height.

Change the code and test with KEY S

Now the rectangle is around the chicken, but the size is too big. The reason is, that the chicken.png is bigger than the chicken itself. So now we should adjust the true size:

Code (BlitzMax) Select
SuperStrict
Graphics 800,600
Global Land:TImage    = LoadImage("land.png")
Global Chicken:TImage = LoadAnimImage("chicken.png",140,140,0,12)
Global cOffsetX:Int = 20                        '  <------- New LINE
Global cOffsetY:Int  = 20                       '  <------- New LINE
Global cWidth:Int  = Chicken.Width  -50         '  <------- New LINE
Global cHeight:Int = Chicken.Height  -50         '  <------- New LINE
....

Function GunFire:Int()
mX=MouseX()
mY=MouseY()
If KeyDown(KEY_S)                     
SetColor 255,0,0                   
'make the sensitive area visible:
DrawNonFilledRect(cX+cOffsetX, cY+COffsetY, cWidth, cHeight)  ' <-- NEW CODE LINE
EndIf
If MouseHit(1)
PlaySound Shot
If (mX>cX+cOffsetX) And (mx<cX+cOffsetX+cWidth)               ' <-- NEW CODE LINE
If (mY>cY+cOffsetY) And (mY<cY+cOffsetY+cHeight)          ' <-- NEW CODE LINE
' collision
Return True
...




Change again the code and test with KEY S

Now the rectangle is closer around the chicken, but the chicken coming from the left has a completely wrong sensitive area. This is because we are drawing the (left coming) chicken with SetScale(-1,1). This mirrors the chicken at its left bounds. There is a BlitzMax function to force images to use the middle of the image as a reference point.
Code (BlitzMax) Select
MidHandleImage chicken

MidHandleImage move the reference point of an image to its center


but this needs again a new findout of the dimensions:
Code (BlitzMax) Select
SuperStrict
Graphics 800,600
Global Land:TImage    = LoadImage("land.png")
Global Chicken:TImage = LoadAnimImage("chicken.png",140,140,0,12)
MidHandleImage chicken
Global cOffsetX:Int = -40                        '  <------- New LINE
Global cOffsetY:Int  = -50                       '  <------- New LINE
Global cWidth:Int  = Chicken.Width  -50         
Global cHeight:Int = Chicken.Height  -50       
...



And this is the complete code:

Code (BlitzMax) Select
SuperStrict
Graphics 800,600
Global Land:TImage    = LoadImage("land.png")
Global Chicken:TImage = LoadAnimImage("chicken.png",140,140,0,12)
MidHandleImage chicken
Global cOffsetX:Int = -40                        '  <------- New LINE
Global cOffsetY:Int  = -50                       '  <------- New LINE
Global cWidth:Int  = Chicken.Width  -50         '  <------- New LINE
Global cHeight:Int = Chicken.Height  -50         '  <------- New LINE

Global AnimStep:Int                                                 
Global Shot:TSound=LoadSound("fire.ogg")
Global cX:Double=9999, cY:Double, cAddX:Double, cAddY:Double
Global mX:Int, mY:Int
Global Points:Int

Repeat
Cls

DrawImage Land    , 0 , 0
If cAddX>0
SetScale -1,1
EndIf
DrawImage Chicken , cX , cY, AnimStep          ' <------- NEW LINE
SetScale 1,1
AnimStep = (AnimStep +1) Mod 12                ' <------- NEW LINE
TheChicken
If GunFire() =True Then
cx=9999
Points=Points+1
EndIf
SetColor 255,255,255
DrawRect mX-25, mY-1,50,3
DrawRect mX-1, mY-25,3,50
HideMouse
DrawText "Points: " + points, 700,550
Flip
Until AppTerminate()
ShowMouse


Function GunFire:Int()
mX=MouseX()
mY=MouseY()
If KeyDown(KEY_S)                     ' <-- NEW CODE LINE
SetColor 255,0,0                   ' <-- NEW CODE LINE
'make the sensitive area visible:
DrawNonFilledRect(cX+cOffsetX, cY+COffsetY, cWidth, cHeight)  ' <-- NEW CODE LINE
EndIf
If MouseHit(1)
PlaySound Shot
If (mX>cX+cOffsetX) And (mx<cX+cOffsetX+cWidth)               ' <-- NEW CODE LINE
If (mY>cY+cOffsetY) And (mY<cY+cOffsetY+cHeight)          ' <-- NEW CODE LINE
' collision
Return True
EndIf
EndIf
EndIf
Return False
End Function


Function TheChicken()
If KeyDown(KEY_S) Return ' <-- NEW CODE LINE

If (cY<-100) Or (cY>600) Or (cX <-201) Or (cX>1001)
'reset the chicken
cY=Rand(100,500)
If Rand(0,1)=0
'coming from right
cX=1000
cAddX=Rnd(-7 , -4)
Else
'coming from left
cX=-200
cAddX=Rnd(4 , 7)
EndIf
cAddY=Rnd(-3 , +3)
Else
cX = cX + cAddX
cY = cY + cAddY
EndIf
End Function

'  NEW CODE LINES:
Function DrawNonFilledRect(X:Int, Y:Int, W:Int, H:Int)
Local r:Int = X + W   'right
Local b:Int = Y + H   'bottom

DrawRect x , y , W , 1   ' top line
DrawRect x , y , 1 , H   '  left line
DrawRect r , y , 1 , H   ' right line
DrawRect x , b , W , 1   ' bottom line
End Function



Challenge I: Number Input Function

Write an app where the user can use the number keys of his keyboards to enter numbers. Additional he can use RETURN to start a new number


Challenge I: Write a Gadget

Expand the Challenge I to an universal function: A box ("window") appears and the user can enter a number. On RETURN the box dissappears and returns the number to the main app. Additional you can add more gadgets like. Buttons, Sliders, etc..


Challenge III: DrawTriangle

Write a function that draws outline triangles. If you wantyou can try to fill them.



Advise:
Critics to this tutorial are welcome, but please do not post here, but there:
https://www.syntaxbomb.com/blitzmax-blitzmax-ng/critics-and-advises-to-blitzmax-tutorial/

Title: Millisecs() and Timer functions
Post by: Midimaster on January 08, 2022, 02:29:43
Lesson X: Time related action in games

Today you will learn how to do things exactly in a given time, speed or at a given moment. At the moment our games speed is related to the performance of the computer. As faster the computer is as faster runs your game. This is not what we want.

Here is a simple demo, that show three element doing something on the screen. Copy the code and test it several times to get a feeling how fast this all happens:
Code (BlitzMax) Select
SuperStrict
Graphics 800,600

Global  TextVisible:Int
Global  X:Int
Global  Y:Int
Repeat
Cls
' a blinking text:
TextVisible = Not TextVisible
If TextVisible=True
DrawText "This is a blinking text", 100,100
EndIf

' a moving rectangle:
X=X+1
DrawRect x,200,60,30

' a falling ball:
Y=Y+10
DrawOval 500,Y,30,30

Flip
Until AppTerminate()



XYZ = Not XYZ Turns a condition to the opposite. If XYZ was TRUE, now it becomes FALSE, if it was FALSE it returns to TRUE. This enable to switch between two states and is often used to switch  ON/OFF or TRUE/FALSE or FORWARD/BACKWARD, etc...


Now simply change the Flip with a Flip 0 to feel what could happen if the performance is higher:
Code (BlitzMax) Select
...
DrawOval 500,Y,30,30
Flip 0
Until AppTerminate()


Flip 0 rises the speed of our app to maximum performance. You need not to know what happens, but you can see, that the performance of an app can vary. Go back to old Flip.

So we need a controller, that manage actions on the screen time-based.


Code (BlitzMax) Select
...
Global  TextVisible:Int
...
Global  Future:Int=MilliSecs() + 500
Repeat
Cls
' a blinking text:
If Future<MilliSecs()
TextVisible = Not TextVisible
Future= MilliSecs() + 500
EndIf
If TextVisible=True
DrawText "This is a blinking text", 100,100
EndIf
...



Millisecs() returns the time in milliseconds since we started the computer. This is often used to store a "Now"-moment. Later you can compare it with a next Millisecs() and measure the time that has passed. Here we use it to set a variable Future:INT to Now+500msec

In the main loop we check whether Future is still bigger than "Now". 500msec later it happens: Millisecs() becomes bigger than Future and the variable TextVisible swaps from TRUE to FALSE. At the same moment we set again Future to "Now"+500msec for a next turn.

Now test how the text is blinking. Replace the 500 with several values to get various blinking speeds.

We can do the same with the moving rectangle
Code (BlitzMax) Select
...
Global  Future:Int=MilliSecs() + 500
Global  RectFuture:Int=MilliSecs()
Repeat
Cls
...
If RectFuture<MilliSecs()
X=X+1
RectFuture= MilliSecs() + 50
EndIf
...
DrawRect x,200,60,30
[code=BlitzMax]


At the beginning the rectangle will start immediately, then it will add X=X+1 every 50msec


now lets fall the ball after 4sec:
Code (BlitzMax) Select
...
Global  Future:Int=MilliSecs() + 500
Global  RectFuture:Int=MilliSecs()
Global  BallFuture:Int=MilliSecs()+ 4*1000
Repeat
Cls
...
If BallFuture<MilliSecs()
Y=Y+10
BallFuture= MilliSecs() + 15
EndIf
...
DrawOval 500,Y,30,30
[code=BlitzMax]



Challenge I: Speed Chicken

Now try to adjust the speed of the animation of the chicken in our Moorhuhn game.


Challenge II: Traffic Lights

Try to code a traffic light that changes the colors every 5sec  from RED to YELLOW to GREEN to YELLOW and back to RED


Challenge III: Drum Computer

Try to code a drum-computer playing a rock- rhythm 
Instrument: | BD      SD  BD  BD     SD       |
     Count: | 1   +   2   +   3   +   4   +   |

I added two Drumsounds (see attachment)




Advise:
Critics to this tutorial are welcome, but please do not post here, but there:
https://www.syntaxbomb.com/blitzmax-blitzmax-ng/critics-and-advises-to-blitzmax-tutorial/




Title: Sinus and Cosinus in Games
Post by: Midimaster on January 10, 2022, 00:42:48
Lesson XI: Sinus and Cosinus in Games

For moving objects in free directions on the screen we need to use rotations  and the two related function Sin() und Cos(). No fear! We only use them, no need for understanding...

Both function enable us to  calculate the X- and the Y-part of a movement  from a degree-based "direction".

Things getting very easy when we define:

...and...


But let us watch a first example:

Code (BlitzMax) Select
SuperStrict
Graphics 800,600
Global X:Double=400, Y:Double=300

Repeat
Cls
X = X + Cos(0)
Y = Y + Sin(0)
DrawOval X,Y,30,30
Flip
Until AppTerminate()


Cos:Double(Angle) returns a x-value between -1 and 1 related to the angle (0°-360°)

Sin:Double(Angle) returns a y-value between -1 and 1 related on the angle (0°-360°)

You see that both functions together generate a movement to the right when the angle=0°. If you now exchange the 0 with a 90 the movement will change to "downwards". This works for all angles between 0° and 360°

Code (BlitzMax) Select
SuperStrict
Graphics 800,600
Global X:Double=400, Y:Double=300
Global Degree:Int=45
Repeat
Cls
X = X + Cos(Degree)
Y = Y + Sin(Degree)


DrawOval X,Y,30,30
Flip
Until AppTerminate()


Now do you experiments with various values between 0° and 360°

To rotate the object into the same direction we need a new command:

Code (BlitzMax) Select
...
Repeat
Cls
SetRotation Degree
DrawRect 400,300,100,30
Flip
Until AppTerminate()


SetRotation(Angle) changing the BlitzMax drawing direction

Now we can combine both to something like a "moving car"

Code (BlitzMax) Select
SuperStrict
Graphics 800,600
Global X:Double=400, Y:Double=300
Global Degree:Int=145
Repeat
Cls
X = X + Cos(Degree)
Y = Y + Sin(Degree)
SetRotation Degree
DrawRect X,Y,60,30
Flip
Until AppTerminate()



We connect a "steering wheel" with the mouse-wheel:

Code (BlitzMax) Select
SuperStrict
Graphics 800,600
Global X:Double=400, Y:Double=300
Global Degree:Int=145

Repeat
Cls
Degree=degree + MouseZSpeed()*4
X = X + Cos(Degree)*2
Y = Y + Sin(Degree)*2
SetRotation Degree
DrawRect X,Y,30,15
Flip
Until AppTerminate()


MouseZSpeed:Int() returns -1 or +1 related to movements of the middle mouse wheel


Now we can exchange the DrawRect with the image of a car (see attachment) and care about the bounds od the screen, where the car should stop:

Code (BlitzMax) Select
SuperStrict
Graphics 800,600
Global Car:TImage=LoadImage("car.png")
MidHandleImage Car
Global X:Double=400, Y:Double=300
Global lastX:Double, lastY:Double
Global Degree:Int=290

Repeat
Cls
Degree=degree + MouseZSpeed()*4
X = X + Cos(Degree)*2
Y = Y + Sin(Degree)*2
If X<30 Or x>770 Or Y<30 Or Y>570
X = lastX
Y = lastY
Else
lastX = X
lastY = Y
EndIf

SetRotation Degree
DrawImage Car,X,Y
Flip
Until AppTerminate()



Challenge I: Car Racing: Speed and Course
Try to add a background image, which shows a top-down-view of a racing course. Try to add a accelerator. When the user press the "S" button the car gets faster, pressing "B" works like a break.


Challenge II:Ping Pong
Try to code a ping-pong-game: A ball plays back and forth  while the mouse moves a racket to turn the ball back



Challenge III: Outline Circle
Try code a function that draws a not filled circle. You need a formula like this and a For/Next loop over 360°:
X = middleX + Cos(Angle)* Radius




Advise:
Critics to this tutorial are welcome, but please do not post here, but there:
https://www.syntaxbomb.com/blitzmax-blitzmax-ng/critics-and-advises-to-blitzmax-tutorial/



Title: For/Next Loops
Post by: Midimaster on January 13, 2022, 02:08:26
Lesson XII: For/Next-Loops

In computer programs we often need to do the same action for several times. Therefore we do not need to write the commands a dozend time, but we can do this in a counting loop:

Code (BlitzMax) Select
For Local I:Int=1 to 10
     Print "Hello. This is round " + i
Next


For I=Start To End defines the beginning a code loop with a fixed number of rounds. You need to define a counting variable (often called "i", often an INTEGER) and a first value, where i should start. Third parameter is the final value. When i reaches this, the loop will be done for a last time but not repeated any longer.

Next defines the end of the code loop. Now i gets incremented and the program jumps back to the code line below For... or continues the regular program flow,when i is bigger than the final value.

For I=1 to 10
will beginn with i=1 and loop 10 times until i=10

For I=10 to 15
will beginn with i=10 and loop 6 times until i=15. Yes! 6 times: 10...11...12...13...14...15

N=3
For I=0 to N

will beginn with i=0 and loop 4 times until i=3: 0...1...2...3


Practical use

We paint staves:

Code (BlitzMax) Select
Graphics 800,600
Repeat
Cls
For Local i:Int=1 To 5
DrawLine 50, 100+i*10, 750, 100+i*10
Next
Flip
Until AppTerminate()



Or A Bundle Of Staves:

Code (BlitzMax) Select
Graphics 800,600
Repeat
Cls
For Local j:Int= 1 To 4
For Local i:Int=1 To 5
DrawLine 50, (j*100)+(i*10), 750, (j*100)+(i*10)
Next
Next
Flip
Until AppTerminate()

Here two loops are nested to draw a complex design. The outer loop turns 4 times to draw the rows, the inner loop draws 5 times the lines within one row.


Some Confetti:

Code (BlitzMax) Select
Graphics 800,600
Cls
For Local i:Int=1 To 1000
SetColor Rand(255), Rand(255), Rand(255)
DrawOval Rand(800), Rand(600),11,11
Next
Flip
WaitKey

With For/Next you can paint thousands or tenthousands of objects.


A Pseudo-3D-Scene:

Code (BlitzMax) Select
Graphics 800,600
SetClsColor 88,55,0
Repeat
Cls
' 3D Z-lines:
SetColor 111,151,1
For Local i:Int=0 To 180 Step 10
DrawLine 400, 250, 400+Cos(i)*800, 250+Sin(i)*600
Next
' 3D X-Lines:
Local y:Double =3
For Local i:Int=1 To 30
y=y*1.2
DrawLine 0, 250+y, 800, 250+y
Next
' sky:
For Local i:Int=0 To 255
SetColor i,i,155
DrawLine 0,i, 800, i
Next
Flip
Until AppTerminate()


SetClsColor R,G,B defines the background color

Here we have again two nice ways to use For/Next:

- Draw rays comming from the middle (Z-lines)
- Draw a color gradient sky by often changing the SetColor together with a lot of DrawLines


Road-Movie

Here we draw a piece of road 8 times to get a whole road. 
Code (BlitzMax) Select
Graphics 800,600
Global Road:TImage=LoadImage("road.png")
MidHandleImage road
SetClsColor 0,88,0
Global StreetWidth:Int = 134 
Repeat
Cls
For Local I:Int=0 To 7
DrawImage road,i*StreetWidth, 300
Next
DrawImage road,100, 100
Flip
Until AppTerminate()



Now we add a movement:

Code (BlitzMax) Select
Graphics 800,600
Global Road:TImage=LoadImage("road.png")
MidHandleImage road
SetClsColor 0,88,0
Global StreetWidth:Int = 134 

Global ScrollX:Int
Repeat
Cls
ScrollX = (ScrollX-2) Mod StreetWidth
For Local I:Int=0 To 7
DrawImage road, i*StreetWidth + ScrollX , 300
Next
DrawImage road,100, 100
Flip
Until AppTerminate()

To understand what happens, set the StreetWidth to 140 and reduce For/Next to 6 loops:
Global StreetWidth:Int = 140       '<----CHANGE HERE
....
Repeat
....
For Local I:Int=0 To 6       '<----CHANGE HERE
... and now start F5. Each image has a width of 134. We draw 8 streets (too many), so one street is always outside the right screen bound. Each Flip we move the starting point  for the drawing ScrollX 2 to the left: All streets will be drawn 2pixel  more to the left. The 8th street will appear slowly on the right bound. Exactly when Scroll is -134 we set it to 0 with the MOD command and the procedure starts again.



At last we add a car:

Code (BlitzMax) Select
Graphics 800,600
Global Road:TImage=LoadImage("road.png")
Global Car:TImage=LoadImage("car.png")
MidHandleImage road
SetClsColor 0,88,0
SetBlend alphablend
Global ScrollX:Int, CarY:Double, Add:Double

Global StreetWidth:Int = 134 
Repeat
Cls
ScrollX = (ScrollX-2) Mod StreetWidth
For Local I:Int=0 To 7
DrawImage road,i*StreetWidth+ScrollX, 300
Next
DrawImage car, 400,CarY
CarMove
Flip
Until AppTerminate()

Function CarMove()
If Rand(0,9)=0
Add =  Rand(-1,1)
EndIf
CarY=CarY+Add/10
If CarY>265 Then CarY=265
If CarY<230 Then CarY=230
End Function


It looks like the car is driving, but it stand still at X=400. We "shake" the car a little bit by moving it seldom up and down.



Challenge I: Calculate
Write an app that calculates the sum of all numbers from 1 to 1000 with a For/Next


Challenge I: Jesu's Saving  Book
If the parents of Jesus would have invested 1Cent (=0.01$) at his birth. And the bank would have paid only 1% interest a year ... How many dollars would now be on his saving book? Write an app that calculates anual interests and loops through 2121 years with For/Next .


Challenge III: Draw a chessboard
Write an app, that draws a chessboard by using nested For/Next loops. A chess has  8x8 fields with two different colors: dark wood and pale wood.



Advise:
Critics to this tutorial are welcome, but please do not post here, but there:
https://www.syntaxbomb.com/blitzmax-blitzmax-ng/critics-and-advises-to-blitzmax-tutorial/


Title: Arrays: 100 Variables In One Go
Post by: Midimaster on January 19, 2022, 07:36:57
Lesson XIII: Arrays... 100 Variables In One Go

In the last lesson we learned about a loop with 100 turns. Now we add a possibility to call hundred different variables within one loop with one code line.

Lets say we want to move a lot of identical things around: 10 cars in a racing game or 100 enemies in a game or 1000 snowflakes falling down or 9999 stars in the outer space. Each should move individual. How ca we do this?

With 10 cars you still would have the chance to code like that:
Global carX1:Int, carX2:Int, carX3:Int, carX4:Int, carX5:Int, carX6:Int, ....
Global carY1:Int, carY2:Int, carY3:Int, carY4:Int, carY5:Int, carY6:Int, ....
carX1=252
carX2=524
carX3=523
carX4=274
....
If CarX1 <0 then CarX1=0
If CarX2 <0 then CarX2=0
....
DrawImage CarImage1, CarX1, CarY1
DrawImage CarImage2, CarX2, CarY2
....


But that will be a lot of code lines! And what, if you need 100 or 1000? Or 10000?

Therefore we have the feature Array which offers us a bundle of variables all with the same name and a individual number (called "Index")

Code (BlitzMax) Select
Global carX:Int[100], carY:Int[100]
carX[1]=253
carX[2]=524
...


Global Array:Int[100] defines 100 variables with index in square brackets. The first variable is adressed Array[0], the last with Array[99]

You can adress them individual as before with their individual names ( a combination of the name and a index in the bracket):
Code (BlitzMax) Select
carX[1]=253

But now we also can call all 10 together with a For/Next-loop:
Code (BlitzMax) Select
For local i:Int=1 to 10
     If carX[i]<0 then carX[i]=0
Next
....
For local i:Int=1 to 10
     DrawImage carImage[i], carX[i], carY[i]
Next


The variable "i" takes the job of being the index in the variable names. So we call carX[1], then carX[2] ... until carX[10]...

Balls jumping aroud

So lets do a first example:

We define 10 balls with 10 individual positions and speeds

Code (BlitzMax) Select
Global ballY:Double[10], ballSpeed:Double[10]
For Local i:Int=0 To 9
ballY[i] = Rnd(100,500)
ballSpeed[i] =Rnd(0.5, 5)
Next


Now we can move them within the main loop:
Code (BlitzMax) Select
...
Repeat
Cls
For Local i:Int=0 To 9
ballY[i] = ballY[i] + ballSpeed[i]
If ballY[i]> 580
ballSpeed[i] = -ballSpeed[i]
EndIf
Next
...

We also check if they reached the bottom of the screen. In this case we would turn around the direction but inverting the speed


Here is a complete code

I added a nice feature "Gravitation" by manipulating the variables ballSpeed[...] each turn.
Code (BlitzMax) Select
SuperStrict
Graphics 800,600

Global ballY:Double[10], ballSpeed:Double[10]
For Local i:Int=0 To 9
ballY[i] = Rnd(-100,300)
ballSpeed[i] =Rnd(0.5, 5)
Next

Repeat
Cls
For Local i:Int=0 To 9
ballY[i] = ballY[i] + ballSpeed[i]

ballSpeed[i] = ballSpeed[i]*0.998 + 0.1    ' <------ feature: Gravitation

If ballY[i]> 580
ballSpeed[i] = -ballSpeed[i]
EndIf
Next

For Local i:Int=0 To 9
DrawOval 100+i*60, ballY[i], 21,21
Next
Flip
Until AppTerminate()




Challenge I: Snow Falls
Try to code a snowfall, where 2000 snowflakes starting at random positions above the screen, then falling down. When they reach the bottom, the start again above the screen.



Challenge II: 10 Crazy Cars
Try to code a demo, where 10 individual cars driving in all direction. When they left the screen they turn around with a random angle to come back.


Challenge III: Sort Numbers
Try to code a sorting algorithm: An array gets filled with 100 random numbers. Now try to find the highest number and copy it to the first index of a second (empty) array, the second highest number to the second index... and so on. Hint: mark the already copied values (in the first array) by setting them to "-1" so that they are to low to be taken in acount again.



Advise:
Critics to this tutorial are welcome, but please do not post here, but there:
https://www.syntaxbomb.com/blitzmax-blitzmax-ng/critics-and-advises-to-blitzmax-tutorial/


Title: 2-dimensional Arrays are like board games
Post by: Midimaster on January 24, 2022, 00:53:12
Lesson XIV: 2-dimensional Arrays are like board games

If you have a look on chess or checkers you can see a 2-dimensional field like a quad paper. This is also the base for a lot of computer games. Before we code a chess we want to have a look on maze (labyrinth) game like pacman or like bolder dash. Also 3D games like dungeon master are maze-based and not real 3D

Let us think of a gameboard of 12x12 fields:

Code (BlitzMax) Select
SuperStrict

Graphics 800,650

Global Size:Int=50
SetOrigin 25,25
Repeat
Cls
For Local y:Int= 0 To 11
For Local x:Int= 0 To 11
DrawRect x*size , y*size, size-2, size-2
Next
Next
Flip
Until AppTerminate()


SetOrigin x,y moves the Drawing origin to the pixel position x,y. From now on all drawings are painted 25 more left and more down.


To fill a gameboard like this you need 12x12 = 144 variables. We can store them in an array of 144 elements. But the array can be organize already in 12 row with 12 elements

Code (BlitzMax) Select
Global Board:Int[12,12]

Global Array:Int[n,m] defines a array of n*n elements organized in m rows, each with n elements. Caution: The indices of arrays always start with "0" and end at "n-1" or "m-1"


The left top corner variable is Board[0,0] the right neighbors are called Board[0,1] ...Board[0,11].
The second row starts with Board[1,0] and goes upto Board[1,11]
This continues until row 12:
The 12th row starts left side with Board[11,0] and goes upto Board[11,11]

Inside the variables we store, what is on this field. At the beginning all fields are 0. Means: "nothing in it"

Now lets define a "wall". "1" now stands for "wall". If you want to set a wall on position Board[0,0] you write
Code (BlitzMax) Select
Board[0,0]=1

or if you want to fill a complete row with walls:
Code (BlitzMax) Select
For Local I:Int=0 To 11
Board[i,0] = 1
Next



We build walls on all 4 bounds of the game bord:
Code (BlitzMax) Select
SuperStrict
Global Board:Int[12,12]

Graphics 800,650

'top row & bottom row:
For Local I:Int=0 To 11
Board[i,0] = 1
Board[i,11] = 1
Next

'left column & right column:
For Local I:Int=0 To 11
Board[0,i] = 1
Board[11,i] = 1
Next

Global Size:Int=50
Repeat
Cls
SetOrigin 25,25
For Local y:Int= 0 To 11
For Local x:Int= 0 To 11
SetColor 255,255,255
DrawRect x*size , y*size, size-2, size-2
If Board[x,y]=1
SetColor 105,80,65
DrawRect x*size , y*size, size-2, size-2
EndIf
Next
Next
Flip
Until AppTerminate()



Now let us define a "player". The "2" stands for player. We put him anywhere, but not in a wall
Code (BlitzMax) Select
...
Graphics 800,650
Global Player:TImage=LoadImage("player.png")     ' <----- NEW LINE
...
Global Size:Int=50
Board[ 1+Rand(9) , 1+Rand(9) ] =2     ' <----- NEW LINE
Repeat
...
If Board[x,y]=1
SetColor 105,80,65
DrawRect x*size , y*size, size-2, size-2
ElseIf board[x,y]=2                   ' <----- NEW LINE
SetColor 255,255,255         ' <----- NEW LINE
DrawImage player,  x*size , y*size     ' <----- NEW LINE
EndIf

...

You find the little collegue player.png in the attachment.

The For/Next loops scans all 144 elements of the array and paints something related to the content of the element. If the array variable at (X,Y) has the content 2 it draws the image of the player at (x*50,y*50)

Now lets move the player

We us the keyboard cursor keys to move the player 1 step to the left (:
Code (BlitzMax) Select
Function CheckPlayer()
If KeyHit(KEY_LEFT)
FindAndMovePlayer(-1)   
Else
....
EndIf
End Function


Function FindAndMovePlayer(moveX:Int)
For Local y:Int= 0 To 11
For Local x:Int= 0 To 11
If board[x,y]=2
Print "found at " + x+ " " + y
board[x+moveX ,y]=2
board[x,y]=0
Return
EndIf
Next
Next
End Function


First we search for the "player's" element, then mark the field left of it with a "2". Do not forget to mark the prior field with "0" because now it is empty. If you forget it you suddenly will have two players . Then leave the function immediately.

Now we need to do this four all 4 directions:
Code (BlitzMax) Select
SuperStrict
Global Board:Int[12,12]
Graphics 800,650
Global Player:TImage=LoadImage("player.png")   

'top row & bottom row:
For Local I:Int=0 To 11
Board[i,0] = 1
Board[i,11] = 1
Next

'left column & right column:
For Local I:Int=0 To 11
Board[0,i] = 1
Board[11,i] = 1
Next



Global Size:Int=50
Board[ 1+Rand(9) , 1+Rand(9) ] =2
Repeat
Cls
SetOrigin 25,25
For Local y:Int= 0 To 11
For Local x:Int= 0 To 11
SetColor 255,255,255
DrawRect x*size , y*size, size-2, size-2
If Board[x,y]=1
SetColor 105,80,65
DrawRect x*size , y*size, size-2, size-2
ElseIf board[x,y]=2
SetColor 255,255,255
DrawImage player,  x*size , y*size
EndIf
Next
Next
CheckPlayer
Flip
Until 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)
EndIf
End Function


Function FindAndMovePlayer(moveX:Int, moveY:Int)
Print "move"
For Local y:Int= 0 To 11
For Local x:Int= 0 To 11
If board[x,y]=2
Print "found at " + x+ " " + y
board[x+moveX ,y+moveY]=2
board[x,y]=0
Return
EndIf
Next
Next
End Function



to prevent that the player move on a wall-field, we should check, that the new target field is empty, before we moe the player:

Code (BlitzMax) Select
Function FindAndMovePlayer(moveX:Int, moveY:Int)
Print "move"
For Local y:Int= 0 To 11
For Local x:Int= 0 To 11
If board[x,y]=2
Print "found at " + x+ " " + y
If board[x+moveX ,y+moveY]<>0 Then Return    ' <----- NEW LINE
board[x+moveX ,y+moveY]=2
board[x,y]=0
Return
EndIf
Next
Next
End Function


When the player "finds" the gold (="3") a hole (="4") in the wall enables to exit end enter the next level

Final Version

Code (BlitzMax) Select
SuperStrict
Global Board:Int[12,12]
Graphics 800,650
SetBlend alphablend
Global Player:TImage=LoadImage("player.png")   

'top row & bottom row:
For Local I:Int=0 To 11
Board[i,0] = 1
Board[i,11] = 1
Next

'left column & right column:
For Local I:Int=0 To 11
Board[0,i] = 1
Board[11,i] = 1
Next
'some additional walls:
For Local I:Int=0 To 7
Board[4,i+3] = 1
Board[i+2,6] = 1
Next
For Local I:Int=0 To 3
Board[7,i+1] = 1
Board[i+2,2] = 1
Next


Global Size:Int=50
Board[2,4] =2
Board[6,8] =3
SetClsColor 125,125,100
Repeat
Cls
SetOrigin 25,25

' gray floor:
For Local y:Int= 0 To 11
For Local x:Int= 0 To 11
SetColor 155,155,155
DrawRect x*size , y*size, size-2, size-2
SetColor 1,1,1
DrawRect x*size+2 , y*size+2, size-2, size-2
SetColor 111,111,111
DrawRect x*size+2 , y*size+2, size-4, size-4
Next
Next

'shadows:
SetColor 1,1,1
For Local y:Int= 0 To 11
For Local x:Int= 0 To 11
If Board[x,y]=1
SetAlpha 0.05
For Local i:Int=0 To 6
DrawRect x*size+i*3 , y*size+i*2, size, size
Next
ElseIf board[x,y]=2
SetAlpha 0.1
DrawOval x*size+18 , y*size+38,30,15
EndIf
Next
Next
SetAlpha 1

'all other elements:
For Local y:Int= 0 To 11
For Local x:Int= 0 To 11
If Board[x,y]=1
SetColor 145,120,95
DrawRect x*size , y*size, size-2, size-2
SetColor 105,80,65
DrawRect x*size+2 , y*size+2, size-4, size-4

ElseIf board[x,y]=2
SetColor 255,255,255
DrawImage player,  x*size , y*size
ElseIf board[x,y]=3
SetColor 200,150,0
DrawOval x*size+10 , y*size+10,20,20
EndIf
Next
Next
CheckPlayer
Flip
Until 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)
EndIf
End Function


Function FindAndMovePlayer(moveX:Int, moveY:Int)
For Local y:Int= 0 To 11
For Local x:Int= 0 To 11
If board[x,y]=2
If board[x+moveX ,y+moveY]=3
board[0,8]=4                  ' "4" is exit-field
ElseIf board[x+moveX ,y+moveY]=4
End
Else If board[x+moveX ,y+moveY]<>0
Return
EndIf
board[x+moveX ,y+moveY]=2
board[x,y]=0
Return
EndIf
Next
Next
End Function




Challenge I: Start A Chess Game
Code a chess board, where only the pawns exist. Try to move them "ahead" by random every 2 seconds.


Challenge II: Connect Four
Code a complete Connect Four game (Captain's mistress). Two players throw alternately a chip into the maze. The chips fall down to the deepest possible position. The game is over if a player has 4 chips of his color in one row, column or also diagonal.
(https://www.syntaxbomb.com/tutorials/learning-basic-for-total-beginners-blitzmax-ng/?action=dlattach;attach=5210)


Challenge III: Expand our maze
Try to add new feature to the maze like "doors" and "keys" inside the room. Try to create a complete new room.



Advise:
Critics to this tutorial are welcome, but please do not post here, but there:
https://www.syntaxbomb.com/blitzmax-blitzmax-ng/critics-and-advises-to-blitzmax-tutorial/


Title: Some Mazes need Bits
Post by: Midimaster on January 25, 2022, 00:36:58
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.
(https://www.syntaxbomb.com/tutorials/learning-basic-for-total-beginners-blitzmax-ng/?action=dlattach;attach=5174)


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:

(https://www.syntaxbomb.com/tutorials/learning-basic-for-total-beginners-blitzmax-ng/?action=dlattach;attach=5176)



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+1
Print 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 = Wall
2 = Player
3 = Gold
4 = Exit

but now this way:

1 = Top Wal
2 = Left Wall
4 = Player
8 = Gold
16 = 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] | element
End Function


Function Remove(X:Int, Y:Int, element:Int)
Board[x,y]=Board[x,y] &~ element
End Function


Function Check:Int(X:Int, Y:Int, element:Int)
Return (Board[x,y] & element) = element
End 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 = gold

For Local I:Int=1 To 10
Add i, 1 , 1
add i,11 , 1
Next
For Local I:Int=1 To 10
Add 1, i , 2
add 11,i , 2
Next

For Local i:Int=0 To 40
Add  Rand(1,10) , Rand(1,10)  , 1
Add  Rand(1,10) , Rand(1,10)  , 2
Next



The Drawing look like this:

Code (BlitzMax) Select
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



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
SuperStrict
Global Board:Int[12,12]
Graphics 800,650
SetBlend alphablend
Global Player:TImage=LoadImage("player.png")   

' elements:
' 1 = top wall
' 2 = left wall
' 4 = player
' 8 = gold

For Local I:Int=1 To 10
Add i, 1 , 1
add i,11 , 1
Next
For Local I:Int=1 To 10
Add 1, i , 2
add 11,i , 2
Next

For Local i:Int=1 To 40
Add  Rand(1,10) , Rand(1,10)  , 1
Add  Rand(1,10) , Rand(1,10)  , 2
Next


Add 5,5 , 4
Add 6,8 , 8

Global Size:Int=50
Repeat
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
Flip
Until 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)
EndIf
End 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] | element
End Function


Function Remove(X:Int, Y:Int, element:Int)
Board[x,y]=Board[x,y] &~ element
End Function


Function Check:Int(X:Int, Y:Int, element:Int)
Return (Board[x,y] & element) = element
End 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"..





Advise:
Critics to this tutorial are welcome, but please do not post here, but there:
https://www.syntaxbomb.com/blitzmax-blitzmax-ng/critics-and-advises-to-blitzmax-tutorial/

Title: Re: Learning Basic For Total Beginners (BlitzMax NG)
Post by: Midimaster on January 28, 2022, 09:45:33
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!

Constants instead of number

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)
...
Next
Next


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)
...
Next
Next

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   = 1
Const LEFT_WALL:Int  = 2
Const THE_PLAYER:Int = 4
Const GOLD:Int       = 8
Const DOOR:Int       = 16
Const KEY:Int        = 32
Const BOMB:Int       = 64
Const 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 , 1
Next
For Local I:Int=1 To 10
Add 1, i , 2
Add 11,i , 2
Next

...we now can write...
Code (BlitzMax) Select
For Local I:Int=1 To 10
Add i, 1 , TOP_WALL
Add i, 11 , TOP_WALL
Next
For Local I:Int=1 To 10
Add 1, i , LEFT_WALL
Add 11 , i , LEFT_WALL
Next



Current state of our code:


Code (BlitzMax) Select
SuperStrict
Graphics 800,650

Const TOP_WALL:Int   = 1
Const LEFT_WALL:Int  = 2
Const THE_PLAYER:Int = 4
Const GOLD:Int       = 8
Const DOOR:Int       = 16
Const KEY:Int        = 32
Const BOMB:Int       = 64
Const COOKIE:Int     =128

Global 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_WALL
Next

For Local I:Int=1 To 10
Add 1, i , LEFT_WALL
Add 11, i , LEFT_WALL
Next

For Local i:Int=0 To 40
Add  Rand(1,10) , Rand(1,10)  , TOP_WALL
Add  Rand(1,10) , Rand(1,10)  , LEFT_WALL
Next


Add 5,5 , THE_PLAYER
Add 6,8 , GOLD


Global Size:Int=50
Repeat
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
Flip
Until 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)
EndIf
End 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] | element
End Function


Function Remove(X:Int, Y:Int, element:Int)
Board[x,y]=Board[x,y] &~ element
End Function


Function Check:Int(X:Int, Y:Int, element:Int)
Return (Board[x,y] & element) = element
End 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.


Change 3: Spread some random cookies into the maze:

Code (BlitzMax) Select
For Local i:Int=0 To 10
Add  Rand(1,10) , Rand(1,10)  , COOKIE
Next

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
....

In the Maze-Drawing loop we add a branch for cookies


Change 5: Display the player's state:

Code (BlitzMax) Select
...
DrawText "ENERGY=" + P_Energy,100,5
FLIP

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
SuperStrict
Graphics 800,650

Const TOP_WALL:Int   = 1
Const LEFT_WALL:Int  = 2
Const THE_PLAYER:Int = 4
Const GOLD:Int       = 8
Const DOOR:Int       = 16
Const KEY:Int        = 32
Const BOMB:Int       = 64
Const COOKIE:Int     =128

Global P_Image:TImage = LoadImage("player.png")
Global P_Energy:Int  = 10

Global Board:Int[12,12]

For Local I:Int=1 To 10
Add i, 1 , TOP_WALL
Add i, 11 , TOP_WALL
Next

For Local I:Int=1 To 10
Add 1, i , LEFT_WALL
Add 11, i , LEFT_WALL
Next

For Local i:Int=0 To 40
Add  Rand(1,10) , Rand(1,10)  , TOP_WALL
Add  Rand(1,10) , Rand(1,10)  , LEFT_WALL
Next

For Local i:Int=0 To 10
Add  Rand(1,10) , Rand(1,10)  , COOKIE
Next

Add 5,5 , THE_PLAYER
Add 6,8 , GOLD


Global Size:Int=50
Repeat
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
Flip
Until 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)
EndIf
End 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] | element
End Function


Function Remove(X:Int, Y:Int, element:Int)
Board[x,y]=Board[x,y] &~ element
End Function


Function Check:Int(X:Int, Y:Int, element:Int)
Return (Board[x,y] & element) = element
End 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

Add Rooks and Bishops and their functionality to your chess game.



Advise:
Critics to this tutorial are welcome, but please do not post here, but there:
https://www.syntaxbomb.com/blitzmax-blitzmax-ng/critics-and-advises-to-blitzmax-tutorial/

Title: More Maze Elements II
Post by: Midimaster on January 28, 2022, 12:55:24
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       = 16
Const KEY:Int        = 32



Change 2: Define a related Player's variable:

Code (BlitzMax) Select
Global P_Keys:Int     =  0
Global 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 , KEY
Add 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, size
EndIf

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
SuperStrict
Graphics 800,650
' SeedRnd 34567
Print "This Seed=" + RndSeed()
....



Users version:

Code (BlitzMax) Select
SuperStrict
Graphics 800,650
SeedRnd 34567
Print "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.   


Advise:
Critics to this tutorial are welcome, but please do not post here, but there:
https://www.syntaxbomb.com/blitzmax-blitzmax-ng/critics-and-advises-to-blitzmax-tutorial/




Title: Enemies will find you
Post by: Midimaster on January 31, 2022, 02:03:46
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 , ENEMY
Add 1,10 , ENEMY
Add 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
...
Flip
Until 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
SuperStrict
Graphics 800,650
SeedRnd 34567
Print "This Seed=" + RndSeed()

Const TOP_WALL:Int   = 1
Const LEFT_WALL:Int  = 2
Const THE_PLAYER:Int = 4
Const GOLD:Int       = 8
Const DOOR:Int       = 16
Const KEY:Int        = 32
Const BOMB:Int       = 64
Const COOKIE:Int     =128
Const ENEMY:Int       = 256

Global P_Image:TImage = LoadImage("player.png")
Global P_Energy:Int  = 10
Global P_Keys:Int     =  0
Global 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_WALL
Next

For Local I:Int=1 To 10
Add 1, i , LEFT_WALL
Add 11, i , LEFT_WALL
Next


For Local i:Int=0 To 20
Add  Rand(1,10) , Rand(1,10)  , TOP_WALL
Add  Rand(1,10) , Rand(1,10)  , LEFT_WALL
Next

For Local i:Int=0 To 10
Add  Rand(1,10) , Rand(1,10)  , COOKIE
Next

Add 5,5 , THE_PLAYER
Add 6,8 , GOLD
Add 4,8 , KEY
Add 11,4 , DOOR
Add 1,1 , ENEMY
Add 1,10 , ENEMY
Add 10,10 , ENEMY


Global Size:Int=50
Repeat
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
Flip
Until 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)
EndIf
End 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] | element
End Function


Function Remove(X:Int, Y:Int, element:Int)
Board[x,y]=Board[x,y] &~ element
End Function


Function Check:Int(X:Int, Y:Int, element:Int)
Return (Board[x,y] & element) = element
End 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()




Advise:
Critics to this tutorial are welcome, but please do not post here, but there:
https://www.syntaxbomb.com/blitzmax-blitzmax-ng/critics-and-advises-to-blitzmax-tutorial/

Title: User Text Input: Keycodes ASCIIs & Strings
Post by: Midimaster on February 04, 2022, 10:10:13
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,600

Repeat
Cls
Local value:Int = CheckAllKeys()

If value>0
Print "Key pressed = " + value
EndIf

Flip
Until AppTerminate()

Function CheckAllKeys:Int()
For Local i:Int=1 To 255
If KeyHit(i) Return i
Next
Return 0
End 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,600
Global letter:Int
Repeat
Cls
Local value:Int = GetChar()

If value>0
letter=value
EndIf
DrawText "ASCII = " + letter , 100 , 100
Flip
Until 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 ABC
97 - 122   a b c .. x y z    from 97 to 122 are all lower case of the ABC
48 -  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,600

Global MyFont:TimageFont=LoadImageFont("arial.ttf",140)
SetImageFont MyFont
Global letter:Int
Repeat
Cls
Local value:Int = GetChar()

If value>0
Print "Key pressed = " + value + " -> character:"  +Chr(value)
letter=value
EndIf
DrawText Chr(letter),100,100
Flip
Until 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]=57
Print word


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


Challenge I: Write your name

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





Advise:
Critics to this tutorial are welcome, but please do not post here, but there:
https://www.syntaxbomb.com/blitzmax-blitzmax-ng/critics-and-advises-to-blitzmax-tutorial/

Title: Re: Learning Basic For Total Beginners (BlitzMax NG)
Post by: iWasAdam on February 04, 2022, 11:18:39
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!!!!
Title: Re: Learning Basic For Total Beginners (BlitzMax NG)
Post by: iWasAdam on February 04, 2022, 11:21:55
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.
Title: Re: Learning Basic For Total Beginners (BlitzMax NG)
Post by: iWasAdam on February 04, 2022, 11:25:30
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?

Title: Text-Files and String Manipualtion
Post by: Midimaster on February 14, 2022, 01:34:37
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
SuperStrict

Global 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 World
This is line two
And 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
SuperStrict

Global All:String$ = LoadText("test.txt")
Global word:String[] = All.Split(" ")  '  <---- there is a SPACE between the quotation marks

Print 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
SuperStrict

Global 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=Peter
Age=25
Health=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
SuperStrict

Global 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]
Endif
Next


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....


Reading a STRING

Code (BlitzMax) Select
SuperStrict
Global Ini:String[]
IniLoad("ini.txt")

Global PlayerName:String= IniRead("Name")

Print "Players Name is " + PlayerName

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


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


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".


Reading an INTEGER or DOUBLE

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
SuperStrict
Global Ini:String[]
IniLoad("ini.txt")

Global PlayerName:String   = IniRead("Name")
Global PlayerAge:Int       = IniRead("Age").ToInt
Global PlayerHealth:Double = IniRead("Health").ToDouble

Print "Players name is "        + PlayerName
Print "Players age is "         + PlayerAge
Print "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



How to read an array?

for the last text line...
QuoteGameGadgets=4|123|0|4|-7
....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 Integer
End Function



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



Two code line may look tricky for you:


RETURN ARRAY

Functions can return arrays:

Code (BlitzMax) Select
Function SplitToArray:Int[] (...)
Local Integer:Int[...]
....
Return Integer
End 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  ::)





Advise:
Critics to this tutorial are welcome, but please do not post here, but there:
https://www.syntaxbomb.com/blitzmax-blitzmax-ng/critics-and-advises-to-blitzmax-tutorial/

Title: Loading Levels From Files and Encapsuled Functions
Post by: Midimaster on February 14, 2022, 09:48:33
Lesson XXI: Loading Levels From Files and Encapsuled Functions

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 Ini
End 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
SuperStrict
Global MyIni:String[]
IniLoad("ini.txt")
...


towards...

Code (BlitzMax) Select
SuperStrict
Global 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
SuperStrict
Global 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").ToInt
Global PlayerHealth:Double = IniRead("Health").ToDouble
Global PlayerGadget:Int[]  = SplitToArray(IniRead("GameGadgets"))
...


towards...

Code (BlitzMax) Select
Global PlayerName:String   = IniRead("Name", MyIni)
Global PlayerAge:Int       = IniRead("Age", MyIni).ToInt
Global PlayerHealth:Double = IniRead("Health", MyIni).ToDouble
Global 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=Peter
Age=25
Health=3.235
GameGadgets=4|123|0|4|-7

Name=Tom
Age=41
Health=4.111   
GameGadgets=1|2|3|4|5

Name=Linda
Age=18
Health=12.25
GameGadgets=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=Peter
Age=25
Health=3.235
GameGadgets=4|123|0|4|-7

[Level 2]
Name=Tom
Age=41
Health=4.111   
GameGadgets=1|2|3|4|5

[Level 3]
Name=Linda
Age=18
Health=12.25
GameGadgets=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
SuperStrict
Graphics 800,600
Global MyIni:String[] =IniLoad("ini.txt")
Global Level:Int=1

Global PlayerName:String, PlayerAge:Int, PlayerHealth:Double, PlayerGadget:Int[]
ChangeLevel Level
Repeat

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
Flip

Until 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 Ini
End 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 Integer
End 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.





Advise:
Critics to this tutorial are welcome, but please do not post here, but there:
https://www.syntaxbomb.com/blitzmax-blitzmax-ng/critics-and-advises-to-blitzmax-tutorial/
Title: Saving Variables And Complete Levels To INI-Files
Post by: Midimaster on February 16, 2022, 10:45:20
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.

Advise:
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()

We start with the complete code from lesson XXI and add a IniWrite() function

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 Null
End 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 Null
End 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 Null
End 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
EndIf
End 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?

Perphaps you already checked that we made a mistake?

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

Code (BlitzMax) Select
SuperStrict
Graphics 800,600
Global MyIni:String[] =IniLoad("ini.txt")
Global Level:Int=1

Global PlayerName:String, PlayerAge:Int, PlayerHealth:Double, PlayerGadget:Int[]
ChangeLevel Level
Repeat

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
Flip

Until 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 Null
End 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 Null
End 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
EndIf
End Function


Function IniLoad:String[](FileName:String)
Local All:String = LoadText("ini.txt")
Local Ini:String[] = All.Split("~r~n")
Return Ini
End 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 Integer
End 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]
EndIf
End 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.





Advise:
Critics to this tutorial are welcome, but please do not post here, but there:
https://www.syntaxbomb.com/blitzmax-blitzmax-ng/critics-and-advises-to-blitzmax-tutorial/
Title: Three little step towards OOP... Step 1
Post by: Midimaster on February 21, 2022, 00:45:59
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 = 20
Global 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
SuperStrict
Type 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
SuperStrict
Type TActor
     Field X:Double, Y:Double, addX:Double, addY:Double, Name:String
End Type
Global Player:TActor = New TActor
Player.X = 20
Player.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
SuperStrict
Type 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 " + i
Next


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
SuperStrict
Type TActor
     Field X:Double, Y:Double, addX:Double, addY:Double, Name:String
End Type
....
Player.X = Player.X + Player.addX
If Player.X< 0 then
      ' left screen border
     Player.X=0
Endif
...
DrawText Player.Name, 100, 100

For local i:Int=....
     If (Enemy[i].X > Player.X) And (Enemy[i].Y> Player.Y)...
          ' kill an enemy
           Enemy[i] =NULL
     Endif
....




Challenge I: Ball.X, Ball.Y, Ball.addX, Ball.addY

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 






Advise:
Critics to this tutorial are welcome, but please do not post here, but there:
https://www.syntaxbomb.com/blitzmax-blitzmax-ng/critics-and-advises-to-blitzmax-tutorial/
Title: Re: A Type Is A Class?
Post by: Midimaster on February 25, 2022, 14:45:53
Lesson XXIV: A Type Is A Class? (OOP Step 2)


(https://www.syntaxbomb.com/tutorials/learning-basic-for-total-beginners-blitzmax-ng/?action=dlattach;attach=5282)

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 function
Print Name
Print MyClass.Name '   <--------- CALL a library variable
Function 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 NameallocationInfinitvWorkGroup

HisClass.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,200
Repeat
Cls
DrawText TStopWatch.Tick(),100,100
Flip 0
Until 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,200
Repeat
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 0
Until 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,250
TStopWatch.Reset 100
SetClsColor 0,33,66
Repeat
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 0
Until 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
Title: Buttons in Games for Total Beginners
Post by: Midimaster on April 16, 2024, 13:07:53
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.

Advice:
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:

SuperStrict
Graphics 800,300
Global MouseState:Int

Repeat
    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 1
End 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.

SuperStrict
Graphics 800,300
Global MouseState:Int

Repeat
    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) + 1
End 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:

SuperStrict
Graphics 800,300
Global MyButton:TButton= New TButton(1,100,200,120,80)
Global MouseState:Int

Repeat
    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 Method
End 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:

SuperStrict
Graphics 800,300
New TButton(1,100,200,120,80)
New TButton(2,234, 45,222,33)
New TButton(3,600, 100,50,180)
Global MouseState:Int

Repeat
    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 Method
End Type




Title: Re: Learning Basic For Total Beginners (BlitzMax NG)
Post by: Midimaster on April 16, 2024, 13:32:44
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.


Advice:
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


SuperStrict
Graphics 800,300
New TButton(1,100,200,120,80)
New TButton(2,234, 45,222,33)
New TButton(3,600, 100,50,180)
SetClsColor 0,155,155

Global MouseState:Int
Repeat
    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 Method
End Type



Example Radio Group Buttons

If you need to de-select the other buttons when pressing a new button we talk about RADIO-BUTTONS. Here is a solution, that cares about this.

RadioButton.gif
(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:
SuperStrict
Graphics 800,400
For Local i:Int= 0 To 5
New TButton(i+1,100+i*66,200,66,122, 1)
Next

SetClsColor 0,155,155

Global MouseState:Int
Repeat
    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, 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
        Next
SetColor 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:
VintageButton.png
 


Advise:
Critics to this tutorial are welcome, but please do not post here, but there:
https://www.syntaxbomb.com/blitzmax-blitzmax-ng/critics-and-advises-to-blitzmax-tutorial/
Title: Re: Learning Basic For Total Beginners (BlitzMax NG)
Post by: Midimaster on April 17, 2024, 13:39:31
Radio Group Buttons

I expanded the last lesson to demonstrate RADIO BUTTONS. Please have again a look on the previous lesson:
https://www.syntaxbomb.com/index.php?msg=347060951



The Button Neighbour Problem


If more than one button can be pressed at the same time, you need different images for the case, when two neighbours are pressed:

RadioButtonProblem.gif


This need 2 more images and a complex search algorithm:

    Method FindBest:Int()
        If Selected=0 Return 0
        If Search(ID-1)
            If Search(ID-1).Selected=True
                If Search(ID+1).Selected=True
                    Return 4
                EndIf
                Return 2           
            EndIf
        EndIf
        If Search(ID+1)
            If Search(ID+1).Selected=True
                Return 1
            EndIf
        EndIf
        Return 3
    End Method
   
    Method Search:TButton(SearchId:Int)
        For Local loc:TButton=EachIn All
            If loc.ID=SearchID
                Return loc
            EndIf
        Next
        Return Null
    End Method


Now it looks better:

RadioButtonSolved.gif


This is the whole code:

SuperStrict
Graphics 500,200
For Local i:Int= 0 To 5
    New TButton(i+1,30+i*66,30,66,122, 0)
Next

SetClsColor 0,155,155

Global MouseState:Int
Repeat
    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, RadioGroup:Int
    Global Images:TImage
  
    Method New (id:Int, x:Int, y:Int, w:Int, h:Int, group:Int)
        If Images=Null
            Images=LoadAnimImage("VintageButtonBetter.png",66,122,0,5)
        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
        Next
        SetColor 255,255,255
    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
        If Search(ID-1)
            If Search(ID-1).Selected=True
                If Search(ID+1).Selected=True
                    Return 4
                EndIf
                Return 2           
            EndIf
        EndIf
        If Search(ID+1)
            If Search(ID+1).Selected=True
                Return 1
            EndIf
        EndIf
        Return 3
    End Method
   
    Method Search:TButton(SearchId:Int)
        For Local loc:TButton=EachIn All
            If loc.ID=SearchID
                Return loc
            EndIf
        Next
        Return Null
    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


And you need this better PNG to run the app:

VintageButtonBetter.png