Learning Basic For Total Beginners (BlitzMax NG)

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

Previous topic - Next topic

Midimaster

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/
...On the way to Norway to watch Polar Lights

Midimaster

#1
Lesson II: A Minimum Template For All Your Apps

Our next example expands the app to the typical 3-part structure:

  • pre-code-zone use this zone for: defining variables, open connections, load images , etc...

  • main loop-zone use this zone for: moving objects, playing sounds, etc..

  • finish code-zone use this zone for: save files at the end, close connections, etc...
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:

  • DrawLine

  • DrawRect

  • DrawOval

  • DrawText
and two more complex:

  • DrawPoly

  • DrawImage

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/
...On the way to Norway to watch Polar Lights

Midimaster

#2
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/
...On the way to Norway to watch Polar Lights

Hotshot

#3
.

Midimaster

#4
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/
...On the way to Norway to watch Polar Lights

Midimaster

#5
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:

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/

...On the way to Norway to watch Polar Lights

Midimaster

#6
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:

  • left of the rectangle

  • between cX and the right end of the rectangle (cX+60)

  • right of the rectangle.

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/

...On the way to Norway to watch Polar Lights

Midimaster

#7
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/


...On the way to Norway to watch Polar Lights

Midimaster

#8
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/

...On the way to Norway to watch Polar Lights

Midimaster

#9
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/

...On the way to Norway to watch Polar Lights

Midimaster

#10
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/




...On the way to Norway to watch Polar Lights

Midimaster

#11
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:

  • direction 0° is "to the right"
  • direction 90° is "to the bottom"
  • direction 180° is "to the left"
  • direction 270° is "to the top"
...and...
  • Cos() is always used for movements in x-direction , means "horizontal" part
  • Sin() is always used for movements in y-direction , means "vertical" part.


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/



...On the way to Norway to watch Polar Lights

Midimaster

#12
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/


...On the way to Norway to watch Polar Lights

Midimaster

#13
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/


...On the way to Norway to watch Polar Lights

Midimaster

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



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/


...On the way to Norway to watch Polar Lights