Site Logo


Sparen's Danmakufu ph3 Tutorials Lesson 5 - Danmakufu Syntax and Structures Part 2

The video for this lesson is TalosMistake's Gensei Ruri, an unreleased script with some of the most beautiful patterns ever made in Danmakufu ph3 (in my opinion).

Part 1: What will be Covered in this Lesson?

In this lesson, I will cover the following:

  • Booleans and Boolean Operations
  • Loops
  • Functions
  • If, If-Else, and Alternative-Case Statements
  • Tasks and yield;
  • Usage of the Semicolon

In the previous lesson, I covered the following:

  • Comments
  • Variables and Variable Declaration
  • Constants
  • Mathematical Operations
  • Characters
  • Strings
  • One-Dimensional Arrays

Please note that the contents of this page are partially based off of CK Crash's Danmakufu ph3 tutorial and that it may be a more suitable option for learning about how Danmakufu treats variables and the like.

Part 2: What are Booleans?

A boolean is, simply put, either true or false.

let bool = true;
let bool2 = false;

Boolean expressions are expressions that are either true or false and will be very useful when we discuss if and if-else statements in Part 3.

let test = (5 > 6); //false, since 5 is not greater than 6
let test2 = (4 == 6); //False because 4 is not equal to 6.
let test3 = (1 <= 3); //true because 1 is less than or equal to 3.
let test4 = (6 != 3); //true because 6 is not equal to 3.

The first thing to note is the double equal sign ==. This is used for logic in Danmakufu in place of the single equal sign =, which is used for assigning values to variables and constants. The next thing to note is the use of the !, which functions as 'not' and is used in negation. Please note that characters, strings, and booleans cannot use things such as < (less than) or >= (greater than or equal to) in Danmakufu. You can, however, test equality and inequality for these.

Now we will discuss boolean expressions involving multiple booleans using || (logical OR) and && (logical AND)

let bool = true;
let bool2 = false;
let test = (bool || bool2); //test is true if either bool OR bool2 is true, else false
let test2 = (bool && bool2); //test2 is true if bool AND bool2 are true, else false
let test3 = (bool || !bool2); //test3 is true if bool OR not bool2 is true. IE if bool2 is false.

As seen above, || denotes OR and && denotes AND. When we discuss if and if-else statements in the next part, we will be returning to this, as it is the foundation of script control and flow in danmakufu.

To close, be aware that the boolean true is equivalent to 1 and the boolean false is equivalent to 0. This is good to know for Computer Programming in general.

Part 3: What are If and If-Else Statements?

So now that we have all this knowledge of booleans and boolean expressions, what will we do with it? Well, if you want to only execute certain code under certain conditions, you can use booleans and boolean expressions in if, if-else, and alternative-case (switch) statements!

if(count > 30){count = 0;}

Above is an example of an if statement. If statements have a condition (a boolean or boolean expression) that is true, the code in the braces following the statement will be executed. In the above situation, the variable count will be set to 0 if its value is greater than 30. If its value is not greater than 30, the code within the braces will not be executed.

For future reference, code within {braces} is considered a block. You can have multiple statements (a single instruction) inside a block - just remember to end each statement with a semicolon.

If you want to do something else instead when a given condition is false, you can use if-else statements.

if(count > 30){
        count = 0;
}else{
        count++;
}

Now, if count is not greater than 30, count will increment by 1! You can also nest if else statements and have multiple else statements, like so.

if(count > 30){
	count = 0;
}else if(count < 0){
	count = 0;
}else{
	count++;
	if(count % 3 == 0){
	  //Do something
	}
}

In the above example, there is another case - if count is less than 0. In this manner, you can set the code to do a variety of things. In the last part, the case where count is greater than or equal to 0 AND less than or equal to 30, there is another if statement allowing the code to do yet another thing.

However, if you have a lot of different cases using the same variable, a wall of if else statements gets clunky and hard to read. As a result, there are switch statements, known as alternative-case statements in Danmakufu. They are basically shorthand for if-else statements where the condition involves equality with a specific thing.

But wait! Haven't we seen these before in @Event? Why yes. That's one of the places where you will be using them.

    alternative(x)
    case(-3){
	//Do stuff
    }
    case(2){
	//Do other stuff
    }
    case(10){
	//Do some stuff
    }
    others{
	//Do various stuff
    }

The way an alternative-case statement works is as follows: The variable inside alternative() is the variable that is being tested. The first case asks whether x == -3. If true, it will execute the stuff inside the braces for case(-3) and ignore everything else. If x is not -3, it will move on to the next case and so on. Finally, if it has exhausted its options, it will do whatever is in others{} or, if others{} is not present, simply exit the statement and do nothing. Very useful.

Part 4: What are Loops and How do I Use Them?

If you want to repeatedly do the same thing over and over again (and perhaps with slightly different effects), loops are the solution! There are a few types of loops in Danmakufu.

The first type of loop is the generic loop. It is a structure that takes an integer parameter n and will run the code in the following block n times. The parameter must be an integer because it makes no sense to repeat something 3.5 times. For example:

let x = 5;
loop(20){
    x++;
}

In the above code, we first declare the variable x, which is then assigned the numerical value 5. After this, x++ is called in a loop(20). What the loop does is repeat the code contained within the {} the number of times given in the parenthesis. Therefore, x will equal 25 after this code has been executed. If you had an integer variable y, you could also loop(y), which would loop the code in the following braces y times.

Now is a good time to talk about infinite loops. I.E. how to freeze Danmakufu. An infinite loop occurs when a section of code keeps on looping forever, and the only way to stop it is to terminate the program altogether. Later on in this lesson, we will discuss yield; (the best way to make an infinite loop not actually infinite but achieve similar functions until the script terminates), but as a brief note, loop immediately followed by an opening brace { (e.g. loop{blah;}) will trigger an infinite loop. Using infinite loops should be avoided until you have sufficient knowledge of yield;

Next are while loops. Basically, while a certain condition is true (booleans!), the code in the braces after it will be executed, just like an if statement except automatically repeated.

let x = 5;
while(x < 20){
    x++;
}

In the code above, we declare x and then run a while loop where, while x is less than 20, x is incremented by 1. The code in question will loop itself and x will be 20 after the code has executed. More specifically, x will continue to increment by one until it is 20, at which point the loop will end.

let x = 5;
while(x > 20){
    x++;
}

In the above scenario, x is less than 20, so the condition for the while loop is never fulfilled. Therefore, the while loop never executes and x is left as 5 afterwards.

let x = 5;
while(x > 0){
    x++;
}

To close our study of the while loop, we will consider the above code, where x is declared to be 5 and while x > 0, x increments by 1. As is easily observable, x will never actually become less than 0. Therefore, this code will cause an infinite loop and Danmakufu will freeze.

Now we will discuss the last type of loop in Danmakufu - the for loop. In Danmakufu, the for loop comes in two varieties, ascent and descent.

For a video tutorial, please consult the following: Sparen's Danmakufu Tutorials - Using Ascent Loops and More. However, please be aware that this tutorial assumes prior knowledge of bullet spawning functions.

A generic ascent loop is structured as follows:

let x = 5;
ascent(i in 0..8){
    x++;
}

What this code does is actually a little hard to understand. You can think of it like this: a local variable called i, with a variable 0, is declared. After this, the block of code inside the braces {} is executed. After that, i is incremented by 1 as long as it is less than 8. If it is less than 8 after having been incremented, the code loops again. Once the variable i is equal to 8, the loop ends. This means that i will never actually equal 8 in a run through the loop.

let x = 5;
ascent(i in 0..8){
    x = i;
}

If you do the above, x will start at 0, but it will be set to the value of i, which is 0. As the loop continues, x will be set to 1, 2, 3, and eventually, 7. After it is set to 7, the local variable i is set to 8, and the loop ends, leaving x as 7. In total, the code loops 8 times, and i has values from 0 to 7.

For reference, Drake once wrote the following equivalent code for an ascent loop:

ascent(var in 0..10){
    bla(var);
}

//Above is equivalent to below

local{
    let var = 0;
    while(var < 10){
        bla(var);
        var++;
    }
}

Please note that by convention, the values used in ascent/descent/for loops go as follows: i, j, k, l, etc. Regarding descent loops, the local variable decrements instead of incrementing. You will need descent loops for certain things but you will be using ascent loops most of the time. If you don't understand ascent loops, that's perfectly fine - they're harder to understand than some of the other things in Danmakufu, and you will not need them until you are trying to do graphic fade-ins or more complex bullet patterns.

Finally, on an important note, please remember break; - this will break you out of the innermost loop. This has a variety of uses, which will be useful later on. However, using break; outside of a loop may crash Danmakufu. Please be advised.

EXERCISE: Write various things using while loops, and then write them again using ascent loops. Compare your two implementations. Did one do the job better than the other? Which one took less space to write?

Quiz: Loops and Booleans

Consider the following code for Questions 1 and 2.

let x = 5;
let y = 10;
while(x < y){
    y--;
}
let z = x + y;

1) What is the value of z?

A. 5
B. 10
C. 0

2) let bool = (z % 2 == 0);

What is the value of bool?

A. true
B. false

3) let bool = ((true || false) && !true);

What is the value of bool?

A. true
B. false

4)

let bool = (true && !false);
let bool2 = (false || (true && !true));
let bool3 = (bool == bool2);

What is the value of bool3?

A. true
B. false

5) Momiji wants to make some pretty danmaku. However, Danmaku keeps on freezing! The following is her code:

let x = 5;
while(x < 6){
    x--;
}

What can you replace line 2 with for the code to not freeze?

A. loop{
B. ascent(i in 0..6){

6)

let x = 5;
while(x < 10){
    x++;
}
ascent(i in 6..23){
    x = i / 2;
}
if(x == (23 / 2)){
    x = 25;
}else{
    x = 23;
}

What is the value of x after this code has been executed?

A. 25
B. 23

Part 5: What are Functions?

We have referenced functions before, but now is the time to go in depth about what a function is and how to use them.

Firstly, a function is able to take and return parameters, also known as arguments or inputs. For example, you can have a function f(x)=x+5 that takes a value x, and returns that value incremented by 5. In Danmakufu, it looks like this:

function math(var){  //using 'let var' is also acceptable but nobody really does it.
    return var + 5;
}

In this code, the name of the function is math, the parameter inside the parenthesis is var, and the return value is var + 5. As for how to use it, here's a code snippet.

function math(var){
    return var + 5;
}

let y = math(6); //y will be equal to 11

Simple, right? A function takes its parameters in the form (a, b, c, ...) and returns whatever is in the return statement. A function does not have to return anything and does not require parameters. Note that you can only return one thing - alternatively, you can return an array of different things as long as they are of the same type. However, this gets clunky and can be confusing, so it's best to have a function return a single value. Functions are very useful for quick things such as returning the angle from the player to the boss, getting the player movement direction, etc. All built-in functions are executed immediately when called and will finish execution before the next line of code runs. You can also use yield; in a function and where the function was called, nothing else will execute until the function has completed its run (things not in the script, such as the bgm, background, and system will still work), but we'll get into that later when we discuss yield;. Please note that when referring to a function, people usually refer to the function as functionname(). The parenthesis indicate parameters, which is the primary feature of functions.

We will review functions, subroutines, and function libraries in much greater detail in Lesson 18.

Part 6: What are Tasks?

Now that we have functions out of the way, it's time to explain tasks, also known as coroutines. You can send parameters to a task as well, and the main difference between tasks and functions is that tasks are made to run alongside your other scripts. For example, you could create a task that creates a bullet and then makes it do something every frame for 60 frames before disappearing only for it to reappear elsewhere on the screen and split into other bullets. Tasks give Danmakufu flexibility and are excellent at organizing code. Do note that by convention, you do not put parenthesis after a task unless there are parameters, unlike the case with functions.

Tasks are also amazing at script flow. For example, if you want a sound effect to play thirty frames after a bullet is spawned, you can just call a task that waits 30 frames and then plays a sound effect. Very helpful for timing things in a script.

When I discuss various scripting styles, I will discuss the tasking style, which is very popular due to Helepolis's video tutorials. Please do note, however, that in ph3, unlike in 0.12m, the standard @MainLoop style requires externalizing commands to tasks anyways because you must manually control when bullets spawn and using return; in a routine such as @MainLoop has the potential to crash Danmakufu. That being said, using return; in a coroutine/task will stop execution of the rest of the task and will ignore everything after the return; statement, ending the task entirely. Tasks cannot return a value.

However, now that we have so much stuff about tasks and script control, we should probably go over yield;

CHECKPOINT: Can you return a value using a task?

Part 7: How do I use yield;?

The function of yield; is to pause execution of a routine, subroutine, function, or task by one frame.

That is literally the simplest way to explain what yield; does.

Please note the following, however:

  1. yield;ing in @Initialize should never be done, and calling a function or subroutine in @Initialize that uses yield;, by extension, is a bad idea. It may do nothing, or Danmakufu may crash. Please do not try this.
  2. You MUST yield; once in @MainLoop in order for tasks to run at all. Only once. Don't do more, don't do less. Always have a yield; at the end of @MainLoop regardless of whether or not you task.
  3. If you have an infinite loop, you MUST have a yield; inside it or Danmakufu will freeze. If Danmakufu freezes, it is 99.9999% of the time an issue where you forgot to yield; in a loop{} or while(true){}.

Now that the basics are out of the way, it's time to create our first function.

function wait(n){loop(n){yield;}}

This function is a must-have for everyone's function library. It yields n frames. wait(60); is the equivalent of loop(60){yield;}, but one is longer than the other, so people just use wait().

As this tutorial progresses, you will learn more ways to use yield; as well as other techniques in which yield; is a key component. But now... the semicolon.

Part 8: What Should I Know About Danmakufu Syntax?

Now we will have a brief discussion about Danmakufu syntax. As you all know, Danmakufu is a scripting language and, like programming languages, has both conventions and rules. Like other programming languages, all function, task, variable, and routine names are case sensitive. In other words, @mainloop will not be recognized because Danmakufu only recognizes @MainLoop. Calling a function Wait(n) when you have a function wait(n) will bring up a function not defined error.

Additionally, please, please, PLEASE remember to close all of your (, [, and {. Before running a script (or upon getting an error), use a ctrl-F test to make sure that the number of { is equal to the number of }. Otherwise, something is going to error. Guaranteed.

Also please make use of tabbing. I cannot emphasize this enough. In this tutorial, I use 2, 4, or 8 spaces for tabs. However, the spacing is not what matters - having white space in the first place is what's important. Having unreadable code means that others will be less likely to help you.

And finally... the bane of all existence. THE SEMICOLON.

Danmakufu requires a semicolon at the end of each statement (there are a few exceptions), and if you forget to put one after a statement, the error will reference the line afterwards and not the line with the error. If you call a function or task, remember to place a ; after the closing parenthesis. If you declare a variable, put a ; after you have declared the variable. This is how Danmakufu (and other languages) determine when you have ended a statement, and without it, Danmakufu will be confused. The two big exceptions to the semicolon rule are headers and include statements (we will cover include statements much later). The other is that you don't need a semicolon in a block containing only one statement if the statement is followed immediately by a closing brace }. However, always put the semicolon - I don't care how mkm wants you to code; This is better procedure and more relevant for computer science as a whole.

That's all for now. The quizzes below should be enough to review the information.

Quiz: Script Flow and Syntax

1) Why does Danmakufu freeze when the below code is executed?

while(!Obj_IsDeleted(obj)){ //Obj_IsDeleted() returns a boolean
}
A. There is no code between the braces
B. There is no yield;
C. The object has already been deleted

2) Marisa is testing a new laser of doom. However, her code freezes. How can she stop the freezing and also have the object delete after 300 frames? Please choose the best answer. Assume that the object does not automatically delete.

task MasterSparkEX(x, y, ang, length, width, dt, graph, delay){
    let obj = CreateStraightLaserA1(x, y, ang, length, width, dt, graph, delay);
    let objcount = 0;
    while(objcount < 300 && Obj_IsDeleted(obj) == false){
        //Laser control functions
    }
    Obj_Delete(obj);
}
A. Add a yield; in her while loop
B. Increment objcount inside the while loop
C. Increment objcount before the while loop
D. Both A and B

3) This code brings up an error. Why?

function wait(n){loop(n){yield;}}
Wait(5);
A. Wait(n) has not been defined
B. There should be no semicolon after yield

Part 9: What are the Reserved Keywords?

I will close this lesson with some notes on Reserved Keywords. Note that I obtained this information from the tutorials at Fragmented Skies, so there may be some that are missing or some that are not in fact reserved keywords.

In a programming language, a reserved keyword is a name that cannot be used for your own variables, functions, etc. For example, you cannot have a task named 'function', because 'function' is a reserved keyword in that it has a specific usage preprogrammed into the language.

The following are examples of reserved keywords:

alternative ascent break case descent else function if in let local loop others real return sub task times while yield

There are also function names built into Danmakufu that are reserved and cannot be overridden (if you try to override these with a function, you will get an error where a function with the same name is already defined. If you name a variable as one of these, it will freeze rather than erroring.

add append compare concatenate divide false index length multiply negative not pi power predecessor remainder result slice successor subtract true

On that note, please just use the mathematical operator if it is available. add(3, 5); is utterly pointless when 3 + 5 is significantly easier and less of a pain to write.

Sources and External Resources

[ 弾幕風 PH3 Tutorial ] Introduction to Danmakufu (Helepolis)
-->Should give an alternative, visual example to the contents of this lesson

Danmakufu for Easy Modos by CK Crash (CK Crash)
-->Since this tutorial was used as a source for mine, I highly suggest that you read it if you haven't already.