Programming_Crash_Course - hpgDesigns/hpgdesigns-dev.io GitHub Wiki
So, you want to learn to code in EDL? This page serves as a primer for those who are beginning to learn a programming language.
Just like any written or spoken language, the purpose of a programming language is to convey ideas from one person to another. In this case, a programming language is meant to be used for conveying ideas from a human to a computer. In essence, a programming language is a method of telling the computer what you want it to do. Think back to first grade when your teacher asked you to explain how to make a peanut butter and jelly sandwich. For those who are not familiar with this problem, it is as follows:
You attempt to explain to the teacher that she needs to start by taking some bread. However, your teacher pretends to be confused, because he or she does not know how to obtain this bread, or exactly how much to obtain. So you specify that she should get it from the cupboard and take two slices. However, the teacher also must know how to open the cupboard, and how to remove the bread from the bag. The problem is designed to teach students to elaborate on their ideas.
The same concept applies in computer programming, if anything, more deeply than in the example. While your teacher is smart enough to figure out that a sandwich requires two slices of bread without you telling him or her, a computer genuinely is not. As clear a picture as you may have of what you want, it will not aid you in the slightest if you cannot adequately express the idea in the form of code.
Before you continue in this document, it is encouraged that you abandon any illusion of the computer possessing any degree of intelligence.
Now that we have established that the computer is not intelligent, how can a computer run something as complicated as a video game? Everything you see in a game is stored somewhere in the computer's memory as a number. The text you are reading right now is a big string of numbers. The pixels that form the shapes of the text are a giant matrix of numbers. Everything from the score and health points in a game to the position of the character and enemies to the images that are drawn for them are nothing but numbers.
But don't despair! Thanks to the magic of programming languages, you, the human, do not have to think of them as numbers. Instead, you will refer to them through variables, which allow you to abstract what exactly your resources are in memory. I will explain much later in this document how to understand their numeric representations.
Think back to algebra. Maybe you never took algebra: don't worry, it's
not that hard. In algebra, and in programming, you can use variables to
stand for numbers. If you have even a little mathematical background,
you know that pi, or π, is a variable corresponding to approximately
3.14. Pi, being a constant, lacks one of the fundamental properties of a
variable, and one of the reasons best justifying their use in
programming languages: Variables can change from moment to moment in a
game, and often from instant to instant. One of the simplest examples of
a variable in a game is the score. When you squash a baddie, or grab a
pickup, your score goes up. When you die, or do something wrong, your
score goes down. Numbers such as this are also placed in variables. In
ENIGMA, the score variable is simply called score
.
Variables can represent a number of things. They can be integers, such as 0, 1, 50, 99, 212, and -459; they can be real numbers, such as 1.5, 10, -3.14, -459.67; they can be strings, such as "hello, world!" or "Score:"; they can be booleans, being true or false; and they can even be arrays or sets of any of the above. In ENIGMA, they can represent entire animated images (known as sprites), or sounds, or any number of other resources.
Very importantly, variables are also used to represent instances of
objects and their positions. So each block or coin you see in a
Mario-like game would be an instance with a
unique ID of an object with a unique ID, having an
x-coordinate and a y-coordinate. All of these can be stored and accessed
using variables. To move the player, you simply change the player
instance's x and y coordinates, which are simply the variables x
and
y
.
Constants are simply variables which do not change during the game. Their value may be changed by the programmer before running the game, but they will not change during the game. An example of this might be the name of the main character, which might be left "Bob" until the game is ready for release and a more exciting name has been thought up. No offense to any of you who are named "Bob."
Literals are constants which are named by value instead of given a
label. For instance, pi
is a constant, but 3.1415927
is a literal.
The difference is that the value of pi
can be changed, eg, from
3.1415927
to 3.1415926535898
without editing the code in which it
appears. To use the example from before, "Bob"
is a literal, while
player_name
, where player_name = "Bob", is a constant.
When you read text in English—for example, when you read this page—you do so by starting at the top-left, reading right until you reach the end of the line, and then repeating on the next line. A compiler—the device which reads and understands your code—works in quite the same way.
Code is read and is executed starting on the first line and proceeding down the page, unless a control statement (explained shortly) is encountered, which can change what is interpreted next.
Before we continue, let's define a couple vocabulary words.
A statement is, just as in English, "a definite or clear expression of something." In the context of a programming language, a statement is something you are telling the compiler you want to happen. For example, a statement might tell the program to move the player to a different position, or to create an explosion, or to add to the score. A statement is, in general, some action you want to have taken.
Statements often set variables to new values. For example, a statement that moves the player may do so by setting the player's x and y variables to new coordinate values. This kinds of statements are simply called assignments. For types of assignments, see the table of assignment operators below.
An "expression" is a part of a statement which names variables,
literals, or constants related with zero or more operators. Examples
include 10
, a
, a + 10
, ((6*a + 50) * 11 << 1) + 4
.
In mathematics, operators are used to give a relationship between two
expressions. For example, 10 + 20 == 30
contains two operators. First,
the addition operator, +
, which takes the expression to the left,
10
, and adds it to the expression to the right, 20
. The second
operator is the comparison operator, ==
, which takes the expression to
the left, 10 + 20
, and compares it to the expression to the right,
30
. The comparison operator returns a boolean,
true or false, instead of an integer.
A question you may be asking is, why is the right expression for
+
only
20
, while the left expression for ==
is `10
- 20
? To answer that, recall order of operations from algebra. PEMDAS, you may have learned. In computer science, order of operations is known more generally as "operator precedence." In layman's terms, the
+operator is simply defined to have a higher *precedence* than the
==` operator. The operators table below gives precedence values for all operators in EDL.
Operator | Equivalent | Function | Precedence |
---|---|---|---|
a = b |
Elementary | Simple assignment; a = b sets a to the value of b . |
2 |
a -= b |
a = a - b |
Subtraction assignment; subtracts the value of b from the value of a . |
2 |
a *= b |
a = a * b |
Multiplication assignment; multiplies the value of a by the value of b . |
2 |
a /= b |
a = a / b |
Division assignment; divides the value of a by the value of b . |
2 |
a %= b |
a = a % b |
Modulo assignment; assigns the value of a modulo b to a . |
2 |
a &= b |
a = a & b |
Bitwise AND assignment; bitwise ANDs the value of a by the value of b . |
2 |
a |= b |
a = a | b |
Bitwise OR assignment; bitwise ORs the value of a by the value of b . |
2 |
a ^= b |
a = a | b |
Bitwise XOR assignment; bitwise XORs the value of a by the value of b . |
2 |
a <<= b |
a = a << b |
Left shift assignment; bitwise SHIFTs the value of a left by b bits. |
2 |
a >>= b |
a = a >> b |
Right shift assignment; bitwise SHIFTs the value of a right by b bits. |
2 |
Operator | Name | Function | Precedence |
---|---|---|---|
a < b |
Less Than | Gives true if a is less than b , false otherwise. |
9 |
a > b |
Greater Than | Gives true if a is greater than b , false otherwise. |
9 |
a <= b |
Less Than or Equal | Gives true if a is less than or equal to b , false otherwise. |
9 |
a >= b |
Greater Than or Equal | Gives true if a is greater than or equal to b , false otherwise. |
9 |
a == b |
Equal To | Gives true if a is equal to b , false otherwise. |
8 |
a != b |
Not Equal | Gives true if a is not equal to b , false otherwise. |
8 |
Operator | Name | Function | Precedence |
---|---|---|---|
a + b |
Plus | Gives the sum of a added to b . |
11 |
a - b |
Minus | Gives the difference of a minus b . |
11 |
a * b |
Times | Gives the product of a times b . |
12 |
a / b |
Floating Point Division | Gives the dividend of a divided by b , as real numbers. |
12 |
a div b
|
Integer Division | Gives the dividend of a divided by b , as integers. 10 div 3 is 3. |
12 |
a % b |
Modulo | Gives the modulus of a modulo b , which is the remainder of a divided by b . |
12 |
a mod b
|
Modulo | Same as the % operator. | 12 |
a << b |
Left Shift | Gives the result of shifting a left b bits. |
10 |
a >> b |
Right Shift | Gives the result of shifting a right b bits. |
10 |
!a |
Not | Gives the logical NOT of a : false (zero) if a is true (nonzero), and vice-versa. |
14 |
~a |
Negate | Gives the bitwise negation of a . That is, takes the NOT of each bit. |
14 |
+a |
Positive | Gives the value of a . Does nothing. |
14 |
-a |
Negative | Gives the negative value of a . Flips the sign of a . |
14 |
++a |
Prefix Increment | Adds one to a , and gives that value. |
14 |
--a |
Prefix Decrement | Subtracts one from a , and gives that value. |
14 |
a++ |
Postfix Increment | Adds one to a , but gives the original value (before adding one). |
15 |
a-- |
Postfix Decrement | Subtracts one from a , but gives the original value (before subtracting one). |
15 |
a[b] |
Subscript | Where a is an array, gives the b th element of a . |
15 |
a[b,c] |
Subscript | Where a is a matrix, gives the b,c th element of a . |
15 |
a(b,c...) |
Subscript | Where a is a matrix, calls a with parameters b , c , .... |
15 |
a & b |
Bitwise AND | Gives the bitwise AND of a and b . |
7 |
a | b |
Bitwise OR | Gives the bitwise OR of a and b . |
6 |
a ^ b |
Bitwise XOR | Gives the bitwise XOR of a and b . |
5 |
a && b |
Logical AND | Gives the logical AND of a and b ; True if and only if both expressions are true. |
4 |
a |
b | Logical OR | Gives the logical OR of a and b ; True if and only if at least one expression is true. |
a ^^ b |
Logical XOR | Gives the logical XOR of a and b ; True if and only if exactly one expression is true. |
2 |
a ? b : c |
Ternary Conditional | Gives b if a is true, or c otherwise. |
1 |
a, b |
Comma | Logically separates individual expressions a and b . |
0 |
So, now that we have outlined some operators, and we know the direction code is read, let's outline a sample code of simple statements. First, let's introduce one element of EDL syntax early: Statements are usually separated by semicolons. For now, don't let this bother you, as statements in this example are also separated by newlines and so are easy to follow.
a = 1;
b = 2;
c = a + b;
d = a * a + 2 * a * b + b * b;
e = c * c == d;
In the above code, a value of 1 is assigned to a
, and a value of 2
assigned to b
. The sum of those two numbers is then placed in c
,
while a more complicated expression of the two is placed in d
. You
might notice that the expression is actually the expanded form of (a + b) * (a + b)
, which is expressed mathematically as a² + 2ab + b². So,
at this point, c
contains (a+b), while d
contains the equivalent of
(a+b)². The final line compares c * c
, or c², to d
. This comparison
should always return true, and so e
should be assigned to true
.
EDL syntax permits grouping expressions and statements alike.
In expressions, as you may have noticed in an earlier section, different operations can be grouped using parentheses. Consider the expression `4
- 2 / 2`. In this expression, there is no grouping, so order of execution is determined by operator precedence. We can group any number of expressions within this expression. Here is a complete list of all possible groupings of this expression:
4 + 2 / 2
4 + 2 / (2)
4 + (2) / 2
4 + (2) / (2)
(4) + 2 / 2
(4) + 2 / (2)
(4) + (2) / 2
(4) + (2) / (2)
4 + (2 / 2)
4 + (2 / (2))
4 + ((2) / 2)
4 + ((2) / (2))
(4) + (2 / 2)
(4) + (2 / (2))
(4) + ((2) / 2)
(4) + ((2) / (2))
(4 + 2) / 2
(4 + 2) / (2)
(4 + (2)) / 2
(4 + (2)) / (2)
((4) + 2) / 2
((4) + 2) / (2)
((4) + (2)) / 2
((4) + (2)) / (2)
As a quick exercise, can you tell which of the above expressions have the same result? If you are unsure of precedence, refer to the table below. For the answer, look under the Grouping statements section below.
The point of this exercise is to show that expressions can generally be
seen as operations on smaller expressions. So, 2
is an expression the
same as 2 + 2
is.
In EDL, statements can be logically grouped using braces. Since statements are executed in the order they are read, this will not change the order of execution. They are grouped logically to set them together when working with control statements.
Answer: Expressions 1 through 16 all equate to 5. Expressions 17-24 instead equate to 3.
To make your life easier, ENIGMA provides a comprehensive set of functions which take certain actions for you. You can call these functions by naming them as you would any other variable, and passing them various arguments. An argument is a value that you give the function to tell it exactly what you want. For example, consider this code that draws a red circle:
draw_set_color(c_red);
draw_circle(x,y,16,true);
The code uses two functions. The first is draw_set_color
, a function
with one parameter: the color to use. A parameter is a variable in
the definition of a function which accepts an argument. So here, the
parameter is color
, while the argument is c_red
, a constant defined
by ENIGMA to be the color red.
The second function in this example, draw_circle
, is more complicated.
ENIGMA's draw_circle
function has four parameters; the x
: the
x-coordinate, y
: the y-coordinate, rad
: the radius of the circle
(how big the circle is from the center), and outline
: whether the
circle should be drawn as an outline instead of a full disc. In this
case, we passed arguments comprising our own x- and y-coordinates, x
and y
, a radius of 16
, and true
to denote we just want an outline.
A control statement is a special kind of statement which tells the computer what statement to execute next. Usually, the computer simply moves to the next statement after the current statement in the same way you read the next sentence after the current sentence. A control statement tells the computer to continue from somewhere else, in the same way as I might tell you to read this section again.
Control statements are usually followed by one or more related statements. These statements can, together, be considered as a group of statements. I will explain what this means after we define some statements for you to use.
ENIGMA defines several control statements.
One of the simplest controls, the if statement tells the compiler to execute the next statement group of statements if the supplied condition is met. In English, you use if statements all the time. Let me give you an example I was given when I was learning to program: If I ruled the world, then I would own a monkey.
Since I do not rule the world, it does not follow that I own a monkey. I could go purchase one, but that would not mean that I ruled the world. But you can expect, from the statement above, that if some day I do rule the world, I will own a monkey.
In EDL, this works basically the same way. Assume we have a variable
called i_own_the_world
, which represents whether or not I own the
world. Assume we also have a variable i_own_a_monkey
which represents
that I own a monkey. The above statement would look like this:
if (i_rule_the_world)
i_own_a_monkey = true;
Now, let's say that the reason I do not own a monkey is because I am poor. So while I do not rule the world, I will settle for a dog instead. I might say, then, "if I rule the world, then I would own a monkey; otherwise, I will own a dog." We can express that in EDL as follows:
if (i_rule_the_world)
i_own_a_monkey = true;
else
i_own_a_dog = true;
The else statement can follow an if statement only. Without a matching if, it has no meaning.
The simplest loop in ENIGMA is known as the repeat statement. This statement takes the next statement, or group of statements, and repeats them the given number of times. So, imagine you want to create three particles at your current position. You could simply use this code:
instance_create(x,y,obj_explosion);
instance_create(x,y,obj_explosion);
instance_create(x,y,obj_explosion);
Or, you can use a repeat statement:
repeat (3)
instance_create(x,y,obj_explosion);
Both codes behave the same way.
While statements are loops with conditions. A while loop is like an if statement that repeats while the given condition is true. The syntax is similar:
while (instance_number(obj_mine) < 5)
instance_create(random_range(obj_mine_topleft.x, obj_mine_bottomright.y), random_range(obj_mine_topleft.y, obj_mine_bottomright.y), obj_mine);
An until loop looks identical, but instead loops while the condition is false, stopping only when it is true. In other words, it runs until the given condition is true.
A slightly more complicated loop, a do-while statement is identical to a while statement except the loop code is performed once before the condition is checked. An until can be used in place of the matching while. Note that this statement, unlike the others, should be followed by a semicolon:
do
x = random(room_width), y = random(room_height);
until (place_free(x,y));
The most complicated loop, a for() loop takes three parameters, separated by semicolons.
- The first parameter is code to perform at the very beginning of the loop. It should be one statement, usually setting a variable or iterator.
- The second parameter is the expression to loop on, such as the ones used in while() loops.
- The third parameter is code to run at the end of each loop iteration; this code normally increments the variable or iterator.
An example of a for statement is as follows:
for (int i = 1; i <= 10; ++i)
show_message("I can count to ten! " + string(i));
The above loop will show these messages, in order:
I can count to ten! 1
I can count to ten! 2
I can count to ten! 3
I can count to ten! 4
I can count to ten! 5
I can count to ten! 6
I can count to ten! 7
I can count to ten! 8
I can count to ten! 9
I can count to ten! 10
Another simple loop, with() statements can be used to iterate over instances. The loop code in a with statement is executed for each instance matching the id you specify. Code in the statement will use local variables from the object being iterated, automatically.
So, consider the following code:
with (obj_tile_right)
x += 24;
The above code moves every instance of the object obj_tile_right
to
the right by 24 pixels.
You may remember from the variables section that a variable can have a number of types. A data type is just a format that the compiler can expect your variables to have. ENIGMA inherits its primitive data types from C. In EDL, these types can be given in the code explicitly, in what is called a declaration.
EDL defines the following types for use in code:
Primitive | Size | Range |
---|---|---|
bool | 1 | A boolean value; true (1) or false (0). |
char | 1 | Integers from -128 to 127; often representing a character such as 'a' or 'Z' or '1' or '='. |
short (int) | 2 | Short integer from -32768 to 32767 |
int | 4 | A standard, 32-bit integer; any number between -2147483648 and 2147483647 |
long (int) | 8 | A long integer; any number between -9223372036854775808 and 9223372036854775807 |
float | 4 | A 32-bit floating point number; a wide range of real numbers, such as 10.2 and 3.141592. Not as precise as double. |
double | 8 | A 64-bit floating point number; basically, any number you can think of, so long as it's not ridiculously huge or tiny (order 10308). |
long double | 16 | A 64-bit floating point number; any real number which can be represented by the IEEE floating point specification |
instance_t | 4 | The type needed to refer to an instance in ENIGMA. |
string | 8* | An alias of std::string; any reasonably sized string of characters |
variant | 24* | Any value that can be stored by a double, or any string |
var | 8* | A matrix of any positive dimension storing any number of values representable as double along with any number of strings. See below. |
array | Planned | This will represent an arbitrary array of stuff. |
- The sizes of var, variant, and string above do not include the size of data stored outside of the structure. In practice, var is bigger than variant, and variant is bigger than string.
Declarations are done by stating the scope, followed by the data type, followed by a list of name-initializer pairs. The initializer is optional, and if no scope is specified, then the current scope is used.
Here is a list of example declarations:
var a; // Declares 'a' as a var
variant b; // Declares 'b' as a variant
var c = 10; // Declares c as a var, then sets it to 10
variant d = c, e = 11, f = 12.5; // Declares d, e, and f as variants, setting them, in order, to c (10), 11, and 12.5, respectively.
int g = 1000; // Declares g as an integer and sets it to 1,000.
char h = 'i'; // Declares h as a character and sets it to be the letter 'i'.
long j = 50000000000, k; // Declares j as a long integer and sets it to 50 billion, and declares k as a long integer without initializing it.
You may be wondering why var is called a matrix in the table above. As it happens, var is an array of arrays of variants.
An array is a collection of values which can be accessed by an index. These values are called elements of the array.
In EDL, as in many other C-family languages, arrays are accessed on a
per-element basis using []
to denote a subscript. So, in
array[11]
, array is an array, [11] is a subscript, and 11 is
an index.
var a;
for (int i = 0; i < 10; ++i)
a[i] = i * 111;
show_message(string(a[7]));
The above code will print the number 777. Each element in a from 0 to 9 is that index times 111, so a[1] = 111, a[2] = 222, and in general, a[#] = ###.
The var type is actually a little more complicated than that, as it is guaranteed to be two-dimensional. A two-dimensional array is called a matrix. Consider the following example:
var a;
a = 0; // Sets a[0,0] to 0
a[1] = 1; // Sets a[1,0] to 1
a[1,1] = 3; // Sets a[1,1] to 3
a[4] = 4; // Sets a[4,0] to 4