The beginner's guide to coding

Code

You don't learn to ride a bike by reading books. No one can become a pilot by listening to someone else talk about plane journeys they've been on. Instead, we learn by doing, by trying, by failing and - most importantly - by succeeding. Because when you feel like you're winning, you get confidence in your skills and know that you can do anything.

Programming your computer is no different: it might seem hard, confusing or perhaps even boring, but it isn't. In fact, programming is hugely rewarding, because you exercise complete control over your computer. You can make it do things the way you want them to happen. And, if you're good, it can also lead to a whole new career.

This tutorial will teach you programming by making you program. Along the way you'll learn some theory, you'll learn some jargon, but most importantly you'll write your own program. More specifically, you'll write your own game. Even more specifically, you'll write your own super-cool game for Linux. Excited yet? You should be. Let's not hang around any more - it's time to get started...

Your toolkit

Fundamentally the software you need for this project comes down to two things: Mono and MonoDevelop. Mono is a free implementation of Microsoft's .NET development platform. What's more, it runs on Linux, Windows, Mac and many other platforms, making it a powerful way to develop your apps.

Our game will run just as well on Windows as it will on Linux.

Our game will run just as well on Windows as it will on Linux.

The primary programming language for Mono is known as C#, which is a very new programming language with a strong focus on making you productive. If you've never learnt to program before, you should count yourself lucky - C# makes it easy to do things that used to be very hard! MonoDevelop is the name of the most popular integrated development environment (IDE) for Mono, and has everything we need to make our programs.

IDE

An integrated development environment (IDE) is a program that includes all the tools coders need to work. MonoDevelop lets you edit your code, highlights it neatly, includes code completion to remind you of how things work, and even helps you build and run your programs. Just about everything you want to do with Mono can be done through MonoDevelop.

Before you can write any code, you need to install all the software required for this project. If you're using Ubuntu, go to System > Administration > Synaptic Package Manager and enter your password when prompted. In the Quick Search box type "libsdl" and hit Enter to filter the available package list. You need to make sure the following are installed:

  • libsdl1.2-dev
  • libsdl-gfx1.2-dev
  • libsdl-image1.2-dev
  • libsdl-mixer1.2-dev
  • libsdl-net1.2-dev
  • libsdl-ttf2.0-dev
  • monodevelop

When you install those, a lot of other software will be pulled in as dependencies, so it may take a little time. If you're not using Ubuntu, your package manager should have "monodevelop" and "libsdl" in there somewhere if you hunt around a little.

To get started with a new solution, click on File > New Solution, then choose C# from the list of templates. You'll see various options appear (Console Project, Empty Project, etc); just choose Console Project for now. Give it the name TroutWars, then make sure the box Create Separate Solution Subdirectory is not checked.

Creating a new solution in MonoDevelop.

Creating a new solution in MonoDevelop.

Solution

One project is designed to solve one problem, and a solution is a collection of projects. For example, you might have a project that contains code to play Trout Wars. You might have another project that contains code to create Trout Wars levels. You might have a third project that is a special version of the game for mobile phones. But you have a single solution, called TroutWars, that contains all those projects, and lets you manage them from one place.

The next step is to click on Forward, then, on the next screen, click OK. Don't worry about all those options; we don't need any of them. What happens now is that MonoDevelop creates a new solution on your external drive, then adds two files to it: AssemblyInfo.cs and Main.cs.

You can see these on the pane in the left-hand side of the window, which is known as the Solution Pane. You'll also see References and Resources in there, but you an ignore them for now. You can also ignore AssemblyInfo.cs, because that's just used to set information about your program such as its name or who created it. For now, forget about it: you should care about Main.cs.

Instant code

Computer programming is lots of fun, trust us, but one of its problems is that it sounds dull. Who wants a program a computer? In an effort to make it sound more interesting, most programmers refer to computer programming as 'coding', as if we're part of a secret society with a secret language and a secret handshake.

Still, 'code' (the stuff we write) and 'coding' (the process of writing code) are nice, short words, and we like nice, short words.

If you've followed the steps so far, what you'll see before you in MonoDevelop is some code. Seventeen lines of it, to be precise. But relax, that's not a lot really, becaaaause:

  • Those five lines at the top that are highlighted in blue? They're comments, which means they are completely ignored by the computer. You can write whatever you want in there, as long as each line starts with two forward slashes: //. By default, MonoDevelop puts your name and the creation date in there; other people put contact information in there or the type of licence they are using. For now, just be happy that those five lines are easy: they mean nothing at all!
  • One line (just beneath using System; is blank. There's nothing there, and it gets ignored by the computer. Hurrah!
  • Six lines contain only { or }, and nothing else. These are used to mark little islands of information known as code blocks. Every { has to be followed by a } at some point, because together they say 'this code block starts here... and ends here'. You'll learn more about code blocks soon.

So, once you forget about the lines that are comments, empty lines or block markers, you're actually down to just five lines of code. That's nothing at all, really, but the magic is this: the only line that actually has any influence on the program - the only line that actually does anything - is the one that starts Console.WriteLine.

All the rest of it is what's known as framework code, which means it's just some scaffolding that holds the actual doing bit together. All that namespace, class, Main and args stuff can safely be ignored for now, because what matters is the Console.WriteLine part of the code.

Console

The console is the text-based interface that sits at the core of Linux. It's also known as the terminal, the command line, the shell, the DOS prompt (best not to use that one among Linuxheads though), and the "where did Gnome go?" prompt.

Methods in the madness

Press F5 now. What happens is that MonoDevelop converts those five lines of code into a real program that the computer can understand, then runs it. If you look at the bottom of the MonoDevelop window, you'll see the Application Output pane, and in there it should say "Hello World!", which is about all the program does right now.

In fact, if you open up a terminal window (go to Applications > Accessories > Terminal) then change directory to where the program was created, you can run it yourself. By default, the program lives in your solutions directory (for us, that's /home/hudzilla/TroutWars) under bin/Debug. To get there, you'd type this:

cd /home/hudzilla/TroutWars/bin/Debug

You can now run mono TroutWars.exe to have your program run straight from the command line. The output will be the same cheery greeting, but this does at least show you that you have a real, working program now - you don't need MonoDevelop to make it work.

The "Hello World!" message comes from the single action line of code: Console.WriteLine. Now that you've seen the output from your program, it shouldn't be too much of a leap for you to figure out what that line of code does - it tells the console to write a line of text.

Before we go ahead and learn any new code, the best thing to do now is fiddle around with what little we have: Console.WriteLine. So, modify the code to read this:

Console.WriteLine("Hello World!"); 
Console.WriteLine("It's a lovely day!"); 
Console.WriteLine("Wow, all this stuff just works!"); 
Console.WriteLine("I have the poweeeeeeeeeeer!");
Console.WriteLine("Goodbye, cruel world!");

If you press F5 now, you'll see all five lines being printed out. What's more, you'll see them printed out on individual lines, rather than as "Hello World!It's a lovely day!Wow, all this stuff just works!" and so on. That's because WriteLine means "write me one line of text", and that means Mono will automatically end the line for you and start a new line.

If you explicitly don't want to start a new line each time, then you need to use something other than WriteLine. Go on: have a guess what you might want to use.

No, don't just skip ahead to see what the answer is.

No, really: have a guess.

If you guessed WriteWords, you'd be wrong. If you guessed WriteBitOfLine, you'd also be wrong. The answer you're looking for is much simpler: it's just Write. So, you could write your code like this:

Console.Write("Hello ");
Console.WriteLine("World!");

That will output "Hello World!" on a single line. But you need to be careful to use WriteLine for the last line of text being printed, otherwise what happens is that there's no line break between "World!" and the command line prompt, which looks sloppy.

All this magic is happening through Write and WriteLine, which are called methods. There are hundreds of methods in Mono that have been programmed for you, and they do things such as write to the console, connect to a network, read and write files, and more.

WriteLine is a method that accepts all sorts of different parameter values, of which one is a piece of text you want to print. So when you say Console.WriteLine("Hello World!") you're actually calling the WriteLine method and sending it the text 'Hello World' to print.

Method

A chunk of code that you or someone else has written to perform one particular function. These methods are called by other parts of your code, and can return information back to the part of the program that called them. You can also send data into the method for it to use; this data is known as parameters, and you can send as many as you want.

String theory

So, Write and WriteLine are both methods. To make this clear, we usually print () after method names, which means from now on we'll be referring to them as Write() and WriteLine(). Inside the brackets is where you send your data to the method to use, and in our examples so far we've been sending text such as "Hello World!".

The technical name for this text is a string, and in this example we've basically typed the string directly into our program so that it never changes.

String

The name 'string' is used for text because it is literally some letters and numbers strung together. A string can be a single character or thousands of characters; it doesn't matter. Also, Mono strings are designed to handle any type of letters, including Chinese symbols and other non-English languages. This is all done for you - you just need to know that a string is any text in your program.

Let's change that by creating our first variable: amend your code to be this:

public static void Main(string[] args) 
{ 
	string greeting = "Hello World!"; 
	Console.WriteLine(greeting); 
}

Rather than forcing "Hello World!" directly into Console.WriteLine(), we're now putting it into a variable greeting and sending that to WriteLine(). No, there is no difference; the output is the same. But note that we can't just write this:

greeting = "Hello World!";

You need to put "string" before "greeting" because otherwise Mono won't know what "greeting" is. It could be a number, it could be a network connection or even a database. What this new line of code does is ask Mono to create a variable called greeting that will hold a string.

Public

The "public" keyword means that any code can read the variables and methods inside your class. If you don't specify that, or specify "private" rather than "public", then only methods inside the class can reach them.

Behind the scenes, Mono talks to Linux and gets enough memory together to hold the text "Hello World!"; again, however, this is all magic pixie dust, and not for us mere mortals to worry about.

Now, there is one big advantage to telling Mono that our "greeting" is a string: it can do some magic known as enforcing type safety. For example, what is 2 times 2? That should be easy: 4. But what is "hello" times "hello"? Well, that's a bit of a silly question, because clearly you can't multiply letters.

And that's where type safety comes in: because when Mono sees you trying to multiply greeting by itself, it will think, "wait... what type of data does greeting hold again? Hmm... a string... well, that doesn't work, because you can't multiply a string by a string, so I'm not going to build this program."

At this point you probably don't see the benefit of this type safety fanciness, because you have just one variable and it's called something that would only ever be a string. But when you have a big program with thousands of variables, you start to realise that any help Mono can give you is quite welcome!

Variable

A variable is something that holds a value. What's more, that value is - unsurprisingly - variable, which means it can change. For example, you might say "hey Mono, give me a variable called 'a' and make it hold the value 5." Then ten minutes later, you might say, "oh, you know that 'a' variable? I want it to hold 10 now."

Before we move on, there's one more thing you need to know, and that is escape sequences. As you've seen, strings start and end with double quotes, but what if you want a string that contains double quotes? For example:

string greeting = "Then the man said, "Are you really Tim O'Reilly?"";

In that situation, Mono sees the first quote and thinks, "OK, I've got a string here." Then it reads up until the second quote just before Are and thinks that's the ending quote for the string. So in effect it thinks you want a string containing "Then the main said, ".

So far, so good - it's not correct, but it's not going to explode either. But then the problem occurs: the next thing it reads is "Are you really Tim O'Reilly?", and, because we already told Mono the string ended, it will consider this text to be code, and will complain.

Here's where the escape sequence comes in handy. If you ever want to have a double quote inside a string, you just need to put a backslash before it. So, rather than typing " you would type \". If you ever find you want to type a backslash in your strings, you need to escape that too - type \\.

OK, enough with strings - let's make our own method...

Discourse on methods

Typing lots of code is rubbish. It's rubbish because it lets errors creep in, it's rubbish because it means if you change something you need to change it in lots of places, and it's rubbish simply because it gives you RSI. Instead, if you want to re-use some functionality you should create your own method. That way, you can just call your method to have Mono perform the functionality for you.

Let's start with a simple example. Don't worry about what it means just yet; it will all become clear soon:

public static void Main(string[] args) 
{ 
	PrintGreeting(); 
} 
		 
static void PrintGreeting() { 
	string greeting = "Hello World!"; 
	Console.WriteLine(greeting);			 
}

If you use that code, you'll get exactly the same result as the first program: it prints "Hello World!" But now PrintGreeting() is a method all by itself, so you can call it as many times as you want without rewriting any code. What's more, you could decide later that PrintGreeting() should read its greeting text from an external source, convert it to Klingon, then save it to a log file before printing it out - and all you'd need to do is change PrintGreeting() rather than any other code.

Now let's bump up the complexity a little by making PrintGreeting() accept a parameter for it to print, just as with Console.WriteLine(). To do this, you need to modify the method like this:

static void PrintGreeting(string greeting) {

Now we're saying that PrintGreeting() must have a string passed to it and that string will be known as "greeting". It doesn't mean that the string being passed in must be called greeting, just that inside PrintGreeting() it will be referred to as greeting.

If you press F5 now, you'll see an error message along the lines of "A local variable named 'greeting' cannot be declared". That means "your method wants a string, and you've asked for it to be called greeting. OK. But on the very next line you're asking me to create a new string called greeting to hold "Hello World!" That's probably not what you want, so I'm going to stop and complain."

To fix this error, remove the line starting "string greeting", because that's no longer needed. But if you press F5 now, you still get an error - this time because PrintGreeting() demands a string being passed in as an argument, and we're not giving it anything. The fix is simply to give PrintGreeting() a parameter, like this:

PrintGreeting("Hello World!");

And now the program will work, printing the same message as before. So why bother? Why not just Console.WriteLine()? Again, this is a very simple example, so it doesn't really matter either way. But once you're working with complex code, it becomes imperative to keep your code neatly organised.

A class act

You've now learnt enough theory to make a start on the game, so right-click on the TroutWars project in the Solution pane (it's the one in bold) and select Add > New File. When you're asked to choose the type of file, select Empty Class and give it the name TroutWars. When you click the New button, it will appear in the project and automatically be opened for editing.

And now you'll start to see some similarities from the earlier code: this too has "namespace TroutWars", as well as that little 'class' word. I don't want to get bogged down into classes and namespaces, but I do want to say that what we've just created - a "TroutWars" class - is a special data type that we can create in our program.

Class

Technically speaking, a class is a definition of a data type. You tell Mono you want it to hold various integers and that it has various methods, but it's just a schematic - nothing actually exists. Once you create a variable of that class type, it's known as an object". Don't worry about the distinction; you can get by without really caring.

So, before we had the "string" data type to hold text, and now we have a TroutWars data type that will be our game. As standard, MonoDevelop gives all new classes a special method with the same name as the class. So, as our class is called TroutWars, it will create a method like this one:

public TroutWars() {
}

This is different to the methods we've been using previously because it's not marked as either "static" or "void". You don't really need to understand what "static" does, but if you're curious read the box below. But the void thing is important, because that tells Mono what kind of data you want to return.

Static explained

The "static" keyword is one of the few places where the distinction between a class and an object matters. When we call the Update() method on a laser, it changes its X property so that it moves to the right of the screen. When you reference a variable like this, Mono automatically knows you mean the X variable that belongs to the current laser, and not all the other lasers in the game. But when you use the "static" keyword, what happens is that the variable (or method) is shared with all objects of that class.

To give you an example in SDL, we'll be using the keyboard to move the player. To do that, we don't write code like this:

Keyboard mykeyboard = new Keyboard();

That's not required, because there's only one keyboard attached to a computer, so SDL just sets up its methods as being static so they are shared. When something is static like this, you don't need to create an object to use its methods, because the methods are shared.

For example, let's say you wrote a method that accepts one string as its parameters then converts that string to uppercase. To get the uppercased string back to the program, you need to return a value, so you need to tell Mono that a value should be (and will be) passed back by your code. What void says is "this method returns nothing at all; if it does return something, please warn me that I've made a mistake."

When MonoDevelop gave us this TroutWars() method for our TroutWars class, it wasn't declared as void, and that makes it special. You see, if you have a method in a class that has the same name as the class, it's called a constructor method, and has a special meaning.

We saw previously that we can create variables that are strings, and Mono will automatically find memory for them. Well, when you ask Mono to create a variable of type TroutWars, it will first find enough memory for it, then it will run the constructor method of that TroutWars variable.

This is very helpful to us, because games have lots of things to do when they start up - they need to load their pictures and their sounds, they need to set the screen up for drawing, they need to check whether any controllers are plugged in, and so on. All this can be done inside the constructor method of the TroutWars class, which means that as soon as you create a TroutWars variable it will all be done.

Constructor

A special method that executes whenever a new class is created. These aren't required, so if you don't need anything done when your class is created, just don't have a constructor.

You can test all this by putting a Console.WriteLine() call into the TroutWars() constructor, like this:

public TroutWars() 
{ 
	Console.WriteLine("Hello World!"); 
}

Now go back to Main.cs (look for it in the list of tabs just above the text editor in MonoDevelop), then edit the code to read this:

public static void Main(string[] args) 
{ 
	TroutWars game = new TroutWars(); 
}

So all that code is telling MonoDevelop to allocate enough memory to create a variable of the TroutWars class, then it uses "new TroutWars()" to actually create the object. If you just said "TroutWars game;" it would allocate enough memory but not fill it with anything!

If you run the project now (press F5), you'll see the same "Hello World!" message again, but this time it's coming from the TroutWars class. That constructor method is a method like any other, so you can pass parameters to it if you need to.

When our game starts, it needs to do a darn sight more than just printing some text. Fortunately, all the game features we need are handled through a special system called SDL - the Simple Directmedia Layer. If you followed the installation guide at the beginning of this tutorial, you already have everything you need to make SDL work, with the exception of two files: SdlDotNet.dll and Tao.Sdl.Dll. These are both included with the source code for this project.

To get SDL into your project, copy the files SdlDotNet.dll and Tao.Sdl.dll into your solution directory, then go back to MonoDevelop and right-click on References in the Solution pane. From the menu that appears, choose Edit References, then scroll down the list of packages and make sure the box next to System.Drawing is checked.

You need to add references to System.Drawing, SdlDotNet.dll, Tao.Sdl.Dll.

You need to add references to System.Drawing, SdlDotNet.dll, Tao.Sdl.Dll.

Now change to the .Net Assembly tab, then double-click on SdlDotNet.dll and Tao.Sdl.dll to add them to your project. When you click OK, you should see four items in the References list in the Solution pane: SdlDotNet.dll, Tao.Sdl.Dll, System and System.Drawing.

What the references do

These references are basically huge amounts of functionality that we can now draw on in our game, but if you actually want to use them you need to tell Mono which bits you want to use. This is what the "using System;" line means in your MonoDevelop code - it means "I want to use the most basic bits the system provides me with", such as strings.

If you want to use SDL, you need to tell Mono which parts you want to use, so change the first few lines of your code to read this:

using SdlDotNet; 
using SdlDotNet.Core; 
using SdlDotNet.Graphics; 
using System; 
using System.Drawing;

So: we want to use Core and Graphics from SdlDotNet, then use System and System.Drawing from Mono. System.Drawing is useful because it gives us a new data type, Point, that holds an X and Y co-ordinate for drawing things.

Anyway, with the references and using lines in place, we can finally make use of SDL. To create a window that you can draw to, amend the TroutWars() constructor method to read this:

public TroutWars() {
	Video.SetVideoMode(1024, 768); 
	Events.Quit += new EventHandler<QuitEventArgs>(Quit); 
	Events.Run(); 
}

There are three new lines there:

  1. The "SetVideoMode()" line creates a window of a specific width and height; in this case, that's 1024 pixels wide by 768 pixels high.
  2. The "Events.Quit" line tells SDL what to do when the user wants to quit the program.
  3. The "Events.Run" line tells SDL to start the game.

The middle line is the most complicated because it uses something called an event handler. In this case, we're asking Mono to run the Quit() method of our program when the user wants to quit. Of course, we don't actually have a Quit() method yet, but that's easily rectified by putting code directly after the constructor method:

void Quit(object sender, QuitEventArgs e) { 
	Events.QuitApplication(); 
}

Now you're able to press F5 and run the game for the first time. Sure, you'll only actually see an empty black window, but it's a step forward!

This plain black screen is actually a good sign - it's proof that SDL is working!

This plain black screen is actually a good sign - it's proof that SDL is working!

Event handler

An event handler is a special method that will run when a special event happens. For example, we can say "I have this method that I'd like to be run when the user quits; make sure that happens, please". You can attach many methods to a single event by using += as many times as you want.

Generic collections

Let's make that black screen more interesting by adding a new class: Starfield. This will be used to draw stars in the background that fly past to simulate movement, and so in turn it needs to contain a collection of stars that will be drawn on the screen.

This introduces a whole swathe of cool programming techniques all at once, so strap in - we're switching up a gear! And please don't think you can just skip by the starfield code because it looks long and hard: we've chosen to implement the starfield first because it introduces so many concepts, so even though there's a lot of code, you'll find that everything else after the starfield is easy!

First, add two new classes called Starfield and Star. The Starfield class needs to load the little coloured boxes we'll be using for our stars and store those pictures in a special data type called a list. A list is pretty much what you would expect - a shopping list, for example, is just one thing, but it contains many things you want to buy.

In Mono, a list is one variable, but it holds lots of data that you can access. It's like being able to store many variables in just one variable, which has lots of uses. For example, in our starfield we want to able to hold different coloured stars so that there are brighter and darker stars. Using a list, we can just add pictures to the list and draw them straight from there.

So, in Starfield.cs, set the lines at the top to read this:

using SdlDotNet.Graphics; 
using System; 
using System.Collections.Generic; 
using System.Drawing; 

System.Collections.Generic is new - that's where the list data type is stored. Now, we need to tell Mono that we want a list to hold all the star pictures. In SDL-speak, a picture is called a "surface", because it's something that can be drawn or be drawn to. So to make a list containing all our stars, we have to tell Mono we want a list of surfaces, like this:

namespace TroutWars { 
	class Starfield { 
		List<Surface> sfcStars = new List<Surface>(); 
	} 
} 

Note that both List and Surface need capital letters - if you use "list" and "surface" you'll get an error! Now that we have created a list to hold star surfaces, we can put in a constructor method for the starfield to load all the star pictures when it's created. To make SDL load a picture from a file, you just tell it the name of the file to load, like this:

Surface mypic = new Surface("/my/file/is/here.bmp");

SDL can read all sorts of files - including BMP, PNG, JPG to name just a few - so you just tell it where to find the file, and it does the rest. You can find the star pictures in the source code for this project. In your solution directory, create a new subdirectory called "content" then copy the files stardark.bmp, starmedium.bmp and starbright.bmp into there.

You should also change the location MonoDevelop creates your finished program so that it can find that content directory - go to Project > Options, then choose Configurations > Debug (Active) > Output and remove the bin/Debug part from the end of Output Path. This means your TroutWars.exe file will be in the main solution directory alongside the content directory, so it will all work.

So with all that done, let's load the pictures into SDL using the Add() method of our list. Here's how the starfield method constructor should look:

public Starfield() { 
	sfcStars.Add(new Surface("content/stardark.bmp")); 
	sfcStars.Add(new Surface("content/starmedium.bmp")); 
	sfcStars.Add(new Surface("content/starbright.bmp")); 
} 

When you use Add() it lets you store one more SDL surface in the sfcStars list. That list can hold as many star surfaces as you want, but right now we only need three. The nice thing about lists is that we can tell Mono exactly what kind of data it should hold and it will ensure that nothing else can be sent into the Add() method. So if you try writing this...

sfcStars.Add("Hello World!");

...it won't work.

This list data type is known as a generic collection, because it can hold lots of different variable data (that's the "collection" part) and it's capable of holding any type of data that you want it to (that's the "generic" part). You can have lists of stars, of strings, or indeed of any data type in Mono!

What stars are

So now we have a starfield class that has a list of pictures we can use to draw stars. But we don't actually have any stars, and that's where the new Star class comes in. This holds all the information a star needs to know about itself in order to be drawn to the screen, which really comes down to three things:

  1. X co-ordinate Where it exists horizontally.
  2. Y co-ordinate Where it exists vertically.
  3. Type What surface in sfcStars it should use to draw itself.

Normally X and Y co-ordinates are put together, but we've made them separate here for a reason: CPU-independent performance. Think about it for a minute: all our stars have a position on the screen. We want them to move across the screen to simulate movement.

If we say, "OK, it's time to update the game - move left 1 pixel", then all the stars will move 1 pixel left every time the game updates. On a quad-core machine that update might happen 100 times a second, which means every second the stars will move 100 pixels to the left. But on an ancient PC with a 486 CPU, that update might happen just 10 times a second, which means every second the stars will move 10 pixels to the left.

For background stuff, like these stars, that's not a huge problem, but what if it were something important, like how fast enemies moved? It would mean that people with fast machines would have super-fast enemies to combat, whereas people with slower machines would have it easy!

So, the solution is to say "move the stars by an amount proportionate to how much time has elapsed." That might be 5 pixels, 50 pixels or even 0.5 pixels, and that's why we have X and Y separate: the X co-ordinate needs to be precise enough to hold fractions of a pixel, whereas the Y co-ordinate will never change, so it doesn't need that kind of accuracy. Putting that into programming terms, X needs to be a data type called "float" and Y needs to be a data type called "int".

float

A "float" variable is accurate to fractions of a number. For example, 1.1, 1.11111, 3.141592654 and even 1.0 are all values that you can give to a float. If you were wondering, it's called "float" because the position of the decimal point is floating as opposed to being fixed. A fixed-point number must always have the same number of digits before and after the decimal point, whereas a floating-point number can put the decimal point wherever it wants.

With X and Y co-ordinates out of the way, the third thing each star needs to know is what type of star it is, which decides how it should be drawn. In the Starfield class we've added three surfaces to sfStars, and Mono is kind enough to let us read that list by using numbers. For example, we can say sfcStars[1] to read the surface at that position. So all the stars need to do is store their type as an integer, which will be used to find the right surface in sfcStars.

Integers

An "integer" variable (usually just called "int") is one that holds only whole numbers, such as 3, 10 and 3453491. It's short for "integer", which is where we get our word "entire" from, because integers never hold any fractions of numbers, such as 3.1.

There's one small catch here, and it's something a lot of people find confusing: Mono counts from 0. That is, if you have three items in sfcStars like we do, they are at positions 0, 1 and 2. This count-from-zero approach has a long history of confuzzling newbies, particularly when they write some code that counts from 1 to 3 and wonder why their program explodes.

Don't worry about why it's the case (it gets rather complicated, to be honest!), just remember that everything starts at 0 rather than 1.

Fooled by randomness

That's all the information a star needs to know to appear on the screen, but there's still one more thing to do before we're done: give every star a random location on the screen. At the beginning, every star needs a random X and Y co-ordinate, then as they disappear off the screen we have to move them back to the right of the screen at a new random Y co-ordinate so the starfield is always changing.

Generating random numbers is very easy, but, as with many things in programming, it does have one warning: you should, generally speaking, only have one random number generator in your entire game. This should be created when the game starts, and be used for all your random number generation until the game finishes. Some people don't do this, instead preferring to create a new random number generator each time they need it.

The problem with that approach is that random number generators aren't really random, they just appear to be, so if you create two at the same time it's possible they will generate the same pseudo-random numbers, thus making your game completely predictable!

Pseudo-randomness explained

Computers are entirely predictable machines: if you put the same piece of data into one ten times, you will get the same output every time. This is a problem for games, because it means that players can learn the correct answers/moves/button presses to win, so we rely on pseudo-random number generation.

Using this method, Mono creates a random number generator and starts it with something fairly random, such as the current time and date. Whenever you call Next() on a random number generator, it will generate a new number based on its previous number, so it's important that the first number be as hard to guess as possible! On the flip side, this means that you can make your game predictable if you want to - just pass a new number to the constructor method for Random, like this:

Random Rand = new Random(556);

Using that code, Rand.Next() will always return the same "random" numbers to you.

So, what we're going to do is create a random number generator and make it publicly available for any code in the program to use. In Main.cs, add this just before the line "static void Main":

static public Random Rand = new Random();

This creates a new instance of the Random class and calls it Rand. We can now use that anywhere in the game to ensure random numbers. If you want to get a random number out of Rand, you need to use the Next() method. This accepts one parameter, which is the maximum number to generate. So if you say Rand.Next(100), you'll get a number back that's guaranteed to be between 0 and 100.

In Star.cs, MonoDevelop auto-created an empty constructor method for us, and now we can use the random number generator to give each star a position. Edit the code so it reads this:

using System; 

namespace TroutWars { 
	class Star { 
		public float X; 
		public int Y; 

		public Star() { 
			X = MainClass.Rand.Next(1024); 
			Y = MainClass.Rand.Next(768); 
		}
	}
}

As discussed, X is a float, Y is an integer, and we assign them both when the star is created so they have positions between 0 and 1024 for X and 0 and 768 for Y - the same as our screen dimensions. NB: our example code uses 800x600 rather than 1024x768 to ensure compatibility with more computers.

Looping the loop

We have star pictures, we know how stars should work, so now we must string them together. In Starfield.cs, add a new list:

List<Star> Stars = new List<Star>();

That will hold all the actual stars on the screen. To make a good-looking starfield, we'll need at least 300 stars, so rather than typing the same code 300 times we'll use something faster, neater, and less-RSI-inducing: a loop. This means we can write some code once and tell Mono how many times we want it run, like this:

for (int i = 0; i < 300; i = i + 1) {
	DoStuff();
}

That will create a new integer variable called "i" and give it the value 0. It will then execute all the code that you put in place of "DoStuff()". Next, it will add one to the "i" variable (that's the "i = i + 1" part), and execute the code. Each time it runs the loop, it compares "i" against 300, and will only run the loop if "i" is less than 300 - that's what the "i < 300" part means.

All together, the loop starts at 0 and counts up until 299, executing the DoStuff() method each time. Once 300 is reached the loop finishes and Mono carries on with any code after it.

To use this loop technique with stars, we just need to change DoStuff() so that it creates new stars and adds them to the Stars list, like this:

for (int i = 0; i < 300; ++i) { 
	Star star = new Star(); 
	Stars.Add(star);
}

You can run the code now if you want, but nothing will have changed just yet, because there is still one task outstanding: we've loaded the star pictures, we've created stars and given them random positions, but what we haven't done is actually draw the stars. To do that, we need to be a bit more clever...

Incrementing numbers

If you want to add 1 to a number, the obvious way is to use "number = number + 1", but there's a faster way: the ++ operator. This means exactly the same thing (add 1), but is much easier to read and write. Use ++ like this:

for (int i = 0; i < 300; i++)

The opposite of ++ is - (that's minus minus), which subtracts 1 from a number.

Starfield, draw thyself

When you use the method Video.SetVideoMode(), SDL creates a big black window for you to draw into. That black area is actually a Surface like the stars we loaded, meaning that we can draw to it. But before we can draw to it, we first need to store that Surface in a variable somewhere so that we can reference it in code. So, go to Main.cs and add this line of code before the constructor method:

Surface sfcScreen;

Now modify the Video.SetVideoMode() call to this:

sfcScreen = Video.SetVideoMode(1024, 768);

The SetVideoMode() method call is the same, but now we catch its return value - which is the big black Surface we can draw to - and store it in sfcScreen for later use. Next, we need to tell SDL how to update our game. Internally, deep down in SDL, this is done using ticks, which is essentially just one big loop like this:

for (int i = 0; i < 9999999999999999; i = i + 1) {
	ExecuteTick();
}

Obviously it's not really 9999999999999999, because this is a loop that never ends. In fact, as soon as you call Events.Run(), SDL enters into a so-called infinite loop, meaning that its loop will run and run and run, and will never end. That's why we need an event handler to hook into the Quit event.

When the user requests to quit the game, the Quit event happens and we execute Events.QuitApplication() to tell SDL to stop its infinite loop.

Infinite loop

This is a special loop that never ends unless something tells it to stop. These are very common when you're working with things out of your control. For example, SDL enters an infinite loop and won't quit until you ask it to.

So: what is a tick? It's just one cycle of that loop. Literally, SDL says, "OK, Mr Game Programmer, I've done all my stuff, now what do you want to do?" Then you do all your game updates and drawing. Then SDL does anything it needs to do, and says, "OK, back to you again," and so on. This "tick" is one game update, and will be called many, many times each second for you to make changes to your game world.

To take control of what happens when your game ticks, you need a new event handler similar to the Quit one you made earlier. This time around you type this:

Events.Tick += new EventHandler<TickEventArgs>(Tick);

Put that just above the Events.Quit line in the constructor method in TroutWars.cs.

Each time your game "ticks" you need to update everything then draw everything. Right now let's focus on the drawing part so we can start to see some output from the game - add this code code to TroutWars.cs:

void Tick(object sender, TickEventArgs e) { 
	Render(); 
}

void Render() { 
	sfcScreen.Fill(Color.Black); 
	sfcScreen.Update(); 
}

The Tick() method is called every time we should update and draw the game, and it then calls another method called Render(), which fills the screen with black then calls its Update() method to actually make the change happen. The Update() call is needed because of the way that drawing happens: to remove any flickering on the screen, SDL draws all its images to a special location in RAM known as the backbuffer, then puts that on the screen only when it's finished drawing everything. For now the Tick() method is a bit pointless, but it will do more soon - promise!

Backbuffer

A copy of the window surface stored in RAM. This is used for all drawing so that users only see the finished picture. Once drawing is completed, the backbuffer is switched with the main window, so that users can see what you've drawn.

Now we have a Render() method drawing things to the screen, the final step is to make the starfield draw itself. This can be broken down into four parts:

  1. Telling Mono we want a Starfield object.
  2. Creating said object.
  3. Creating a Starfield.Render() method.
  4. Calling that method inside our main Render() method.

Let's start with #1 - add this line beneath "Surface sfcScreen":

Starfield TheStarfield;

#2 is solved by putting this one line into the constructor method:

TheStarfield = new Starfield();

Remember, we've already written the Starfield constructor method so that it loads the star surfaces and creates 300 stars for its list, so all that's left was to put it into TheStarfield.

And now we're on to #3: giving the Starfield class a new method to draw itself to sfcScreen. This requires a few more bits of magic, but relax: it will all make sense. First we'll show you the code that needs to go into Starfield.cs:

public void Render(Surface screen) { 
	foreach (Star star in Stars) { 
		screen.Blit(sfcStars[star.Type], new Point(Convert.ToInt32(star.X), star.Y)); 
	} 
}

Let's break that down so that it makes more sense.

public void Render(Surface screen) { 

That defines our new method: it's called Render(), it returns void (that means "nothing", remember), and it accepts just one parameter: an SDL Surface that will be known as "screen". In practice, we'll be sending sfcScreen into this method so that the starfield can draw itself to our window.

foreach (Star star in Stars) {

Ah! This is some real Mono magic in action, because it's like a super-efficient loop that's specially tuned for going over lists. In this case, we're telling Mono to execute the loop one time for every Star that's in the Stars list, and, each time it goes over the loop, to give us that Star's information in a variable called, er, "star". So, the first time the loop executes, "star" will be equivalent to Stars[0]. The second time, "star" will be Stars[1], and so on.

screen.Blit(sfcStars[star.Type], blah));

OK, so it doesn't say "blah" in the code, but we want to tackle this line in two parts. The first part is this one: Blit means to copy pixels from one place to another, so what happens here is that we're copying the pixels from sfcStars[star.Type] into the "screen" variable, which is of course our SDL window.

Blit

Blitting is the process of combining two surfaces, and the word "blit" comes from an ancient computer that called the operation "Bit Block Transfer".

You'll remember that we gave each Star a Type integer that stores whether it's dark, medium or bright. We're not giving it a value yet, so Mono will just use the default value for all integers: 0. So what this line really means is "take the pixels from sfcStars[0] (the first Surface in sfcStars) and copy it to the screen". Not so hard after all!

Now for the second part:

new Point(Convert.ToInt32(star.X), star.Y));

The second part of screen.Blit() is where we can specify the location the pixels should be copied to. Obviously we want each star to be drawn to its X and Y co-ordinates, so these are used here. But there are two small complications.

First, the X co-ordinate is a float, which means it holds numbers like 5.5. Now, there is no such thing as half a pixel - you need to specify the exact, whole number of pixels, which means SDL gets annoyed if you try to specify half pixels. The solution is to ask Mono to convert our float to an integer when drawing the star.

Behind the scenes, the stars all have floats for their positions which means they can be very accurate and have smooth movement, but when they are drawn we convert the float to an integer by using Convert.ToInt32(). The star's Y co-ordinate is already an integer, and so doesn't need conversion.

SDL expects us to tell it where to draw using a Point data type, not just X and Y co-ordinates sent individually. Fortunately, a Point is X and Y co-ordinates, just bundled together into one value. So to convert our star's X and Y co-ordinates into a Point we just use new Point() then specify the X and Y co-ordinates of that Point.

Altogether, this code loops over Stars and draws every Star object at its correct place, automatically converting the X co-ordinate to an integer as it goes.

Converting data types

If you have a string you want to be an int, or a float you want to be a bool, you should use the Convert class. This can convert most of the common data types to all the others using something similar to Convert.ToInt32(). To see what's available, type "Convert." (remember the full stop!) and you'll see a list of options.

That leaves one final part: we need to call the Starfield's Render() method from inside the main Render() method in TroutWars.cs. Amend the method to read this:

sfcScreen.Fill(Color.Black);
TheStarfield.Render(sfcScreen);
sfcScreen.Update();

And now you should be able to press F5 and watch a field of dark grey stars sitting idly on your screen. It's not perfect, but hopefully you can see where it's going now!

Stars v2.0

From here, the going is really easy: we need to make each star have a different Type value so that light- and medium-coloured stars are drawn alongside dark ones. This can be tackled by amending the Star constructor method to accept a star type.

We already have a list of star surfaces in Starfield.cs, and we can access these by their position in the list (0, 1 and 2). So, all we have to do is give each star a random Type value between 0 and 2 to make them use the different star images.

To get started, edit the Star constructor method in Star.cs:

public Star(int type) { 
	X = MainClass.Rand.Next(1024); 
	Y = MainClass.Rand.Next(768); 
	Type = type; 
}

That change means that the Starfield can't create new stars unless it gives them a type. So the next step is to go back to Starfield.cs and amend the star creation loop to this:

for (int i = 0; i < 300; ++i) { 
	int type = MainClass.Rand.Next(sfcStars.Count); 
	Star star = new Star(type); 
	Stars.Add(star);
}

This modification creates a new integer called "type", and assigns it a random number between 0 and sfcStars.Count, which is the total number of items in the sfcStars list. The random number generator works with an exclusive upper boundary, which means if you use Rand.Next(4) you will get a value between 0 and 3 back, and never 4. So this "type" integer will contain a number that equates to a particular star picture in sfcStars; that all gets passed into the Star constructor method so that it will be saved by the star for drawing later.

If you recall (or indeed just scan your eyes up a few hundred words) we used sfcStars[star.Type] for the screen.Blit() call, which means we're already using the star's type to select which surface to draw. As such, the code already works: press F5 and you'll see the same starfield, but now it uses all three star shades to provide a little depth.

Stars v3.0

The final step for the starfield is to update it so that the stars move across the screen, and this requires a little more thinking. The first step is to go right back to the Tick() method in TroutWars.cs. Remember, a game tick should update the world then draw it all, and right now we're only drawing. To update the world, we need to insert change Tick() to this:

void Tick(object sender, TickEventArgs e) { 
	Update(e.TicksElapsed); 
	Render(); 
}

So before Render() is called, a new method, Update(), is called, and we pass into it the value e.TicksElapsed. The "e" comes from the second parameter that's passed in, and contains various pieces of information. The bit we really care about, though, is TicksElapsed, which will automatically be set to the number of milliseconds that have passed since the last Tick() call finished.

Milliseconds

One millisecond is 1/1000th of a second, which is a very short time indeed. Well, at least it's short to us humans, but to a modern computer it's a huge amount of time, so your computer spends a lot of its time sitting around bored wishing it was part of something like SETI@Home.

Remember: the goal is to have the game run at the same speed no matter what machine it's running on, and that means we really care about how many milliseconds have elapsed because that tells us how far we should make things move.

The Update() method doesn't exist yet, but for now it's really simple - it just needs to tell the Starfield to update itself, passing on the number of ticks that have elapsed. Put this somewhere in TroutWars.cs:

void Update(int elapsed) { 
	TheStarfield.Update(elapsed); 
}

The Starfield Update() method is equally simple, because all the Starfield does is tell all its stars to update themselves, like this:

public void Update(int elapsed) { 
	foreach (Star star in Stars) { 
		star.Update(elapsed); 
	} 
}

Put that into Starfield.cs somewhere; it doesn't really matter where. Again, note that the number of milliseconds that have elapsed gets passed on to every star. And that's where the real code comes in: each star has to move itself based on how much time has passed, then, if it disappears off the left edge, it has to be moved back to the right edge so that it re-appears as a new star.

Here's the code for the Update() method for stars; this should go into Star.cs:

public void Update(int elapsed) { 
	X -= elapsed * 0.1f; 

	if (X < 0) { 
		X = 1024 + MainClass.Rand.Next(100); 
		Y = MainClass.Rand.Next(768); 
	} 
}

Again, that's quite a lot to take in at once, but if you think about it line by line it's very simple. First, we subtract a number from the star's X position so that it moves to the left (X 0 is the absolute left of the screen). The number we subtract is equal to "elapsed" multiplied by 0.1f (the "f" can be used here to mean "float").

The reason we multiply by 0.1 is to make the movement smaller - without that the stars whizz by at warp speed! If you were wondering, -= is a special operator that means "subtract from myself", so "X -= 5" would take X, subtract 5 from it, then place the result back into X.

Operator

An operator is a mathematical function such as + (adding things), - (subtracting things) * (multipying things) or / (dividing things).

The code works as it is, but there's one extra little tweak we want to throw in there before we can leave the starfield for good.

If you run the code now you'll see that all the stars move at the same speed irrespective of whether they are light or dark. With a simple little change we can make it so that the lightest stars move faster than the darker stars. To do that, change the star movement code to be this:

X -= elapsed * (Type + 1) * 0.1f;

The new part there is "(Type + 1)" and that's what introduces the speed variance. As you know, every star now has a Type value between 0 and 2 that decides what kind of star should be drawn. Because of the order we added the star surfaces, the darkest picture is as sfcStars[0], the brightest at sfcStars[2] and the middle picture at sfcStars[1].

When we calculate how far each star should move, we now multiply the result by the star's type, which means multiplying by 0 for the darkest stars and multiplying by 2 for the brightest stars. While that works OK, multiplying anything by 0 gives the result 0, so the darkest stars don't actually move at all. The solution is to use "(Type + 1)" which means we multiply by 1, 2 or 3 rather than 0, 1 or 2.

If you run the code now, you'll see that the darkest stars move slowly and the brightest stars move the quickest, making the starfield look a lot for not much effort on our behalf!

The finished starfield has stars whizzing by at different speeds.

The finished starfield has stars whizzing by at different speeds.

Er... next?

In the process of adding a starfield, you've learned lots and lots of stuff, including lists, image loading, ints and floats, random numbers, loops, ticks, blits and more. If you've made it this far, you should be proud, but there's still a lot more to do.

The next step is to add a player to the screen, which requires a new class called Player that holds the X and Y position of the player as well a Surface containing its spaceship picture. So, create a new class called Player and give it this code:

using SdlDotNet.Graphics; 
using SdlDotNet.Input; 
using System; 
using System.Drawing; 

namespace TroutWars { 
   class Player { 
      Surface sfcPlayer; 

      public float X; 
      public float Y; 

      public Player() { 
         sfcPlayer = new Surface("content/spaceship.bmp"); 
      }

      public void Update(int elapsed) {

      }

      public void Render(Surface screen) { 
         screen.Blit(sfcPlayer, new Point(Convert.ToInt32(X), Convert.ToInt32(Y))); 
      }
   }
}

All of that should be recognisable to you: all the "using" statements are old news, the floats for X and Y co-ordinates aren't anything special (although we need floats for both because the player moves in both directions), the constructor method loads a picture for the spaceship, the Update() method takes a parameter to tell it how many milliseconds have passed since it was last updated, and the Render() method takes a Surface to draw to as its only parameter then draws the spaceship there using Blit().

The spaceship picture we'll be drawing to the screen.

The spaceship picture we'll be drawing to the screen.

Using that simple class, we can go ahead and add a player to our game with four simple changes. First, add this line into TroutWars.cs, just beneath "Starfield TheStarfield;":

Player ThePlayer;

Now add this into the TroutWars() constructor method, just beneath the "TheStarfield = new Starfield()" line:

ThePlayer = new Player();

And now put this into Update():

ThePlayer.Update(elapsed);

Finally, this goes into Render():

ThePlayer.Render(sfcScreen);

As you can see, the code for the player has a huge amount in common with the code for the starfield: we define the variable, allocate memory and create the object, update it, then render it. With that done you should copy the file spaceship.bmp into your solution directory, then press F5 to run the game.

This time you'll see the stars as before, but now you'll see the spaceship in the top-left corner, surrounded by a magenta box - that was probably not what you were expecting!

The spaceship has a magenta box around it - we need to make that transparent.

The spaceship has a magenta box around it - we need to make that transparent.

The problem here is that our spaceship isn't a perfectly square shape - it has parts that need to be transparent. The easiest way to mark transparent areas is to colour them using a colour you're unlikely to want to use, then tell SDL that you want that colour to be transparent. You'll want to use transparent colours a lot in your own games, so we're going to make a method to load surfaces and automatically apply their transparent colour.

In Main.cs, add this method:

static public Surface LoadImage(string image) { 
	Surface sfc = new Surface(image); 
	sfc.TransparentColor = Color.Magenta; 
	sfc.Transparent = true; 
	return sfc; 
}

So, it accepts the name of the image to load, then uses new Surface() to load it normally. But then there's the important part: it sets the TransparentColor value to be Color.Magenta, and the Transparent value to be 'true' so that the magenta colour is automatically hidden by SDL.

Working with transparency

The simplest way to use transparency is to have a key colour, which is a colour that won't be drawn on the screen. In our examples, that's magenta, which means that any pixels containing the colour magenta is drawn as transparent. If you want to use a key colour, make sure you save your pictures in a lossless format - that means BMP or PNG, and definitely not JPEG.

An alternative is to use transparency in the original image, and for that you'll need to use the PNG format. You'll also need an image editor capable of supporting alpha transparency - the Gimp on Linux is perfect.

Once the transparent colour has been applied, it uses "return sfc;" to send the transparent surface back to the code that called it. To use this method, go back to Player.cs and modify the constructor method to be this:

sfcPlayer = MainClass.LoadImage("content/spaceship.bmp");

Using that code, SDL will automatically load the BMP file and make it transparent - if you run the code now, the spaceship will no longer have its purple border.

Now, there is one point of interest here that you may have noticed, and it comes down to code ordering. What's the difference between these two code examples?

private void RenderPlaying() { 
	TheStarfield.Render(sfcScreen); 
	ThePlayer.Render(sfcScreen); 
}

...and...

private void RenderPlaying() { 
	ThePlayer.Render(sfcScreen); 
	TheStarfield.Render(sfcScreen); 
}

Yes, obviously one of them draws the starfield first then the player second while the other one is vice versa, but it actually makes a real difference. You see, if you draw the player first, then the stars are drawn over the player, as if they are closer on the screen.

Whereas if you draw the starfield first, then the player is drawn over the stars, as if the player is closer. Given the relative size of star vs player, and the actual size of the stars on the screen, you probably want to draw the player last so that it appears on top of everything else.

Movin' on up

The player starts in the top-left of the screen because floats, like ints, have a default value of 0. What we really want to happen is for the player to be able to move based on presses on the keyboard, and that's very easy to do thanks to SDL - we can just check which key is pressed and modify X and Y appropriately.

As with star movement, we use the "elapsed" variable to ensure CPU-independent performance, so change your Update() method in Player.cs to be this:

if (Keyboard.IsKeyPressed(Key.UpArrow)) { 
	Y -= elapsed * 0.5f; 
} 

if (Keyboard.IsKeyPressed(Key.DownArrow)) { 
	Y += elapsed * 0.5f; 
} 

if (Keyboard.IsKeyPressed(Key.LeftArrow)) { 
	X -= elapsed * 0.5f; 
} 

if (Keyboard.IsKeyPressed(Key.RightArrow)) { 
	X += elapsed * 0.5f; 
}

This chunk of code demonstrates a new programming technique: conditional statements. A conditional statement is one that will only execute code inside the curly brackets if the condition turns out to be true. These particular conditional statements are actually very easy to read because you can read them aloud and they should make sense!

True

Whether something is true or false might be hard to prove in the real world, but to computers it's just another data type known as "bool" (short for "Boolean"). A bool value can accept only two values: true or false.

Literally, we ask the Keyboard class whether each key is pressed, and, if it is, modify the appropriate value. So if UpArrow (the Up cursor key) is pressed, we subtract something from Y to make the ship move upwards. This Update() method gets called every time the game ticks, which means players can just hold down the cursor keys to make the ship move.

Keyboard

You can read any of the keys from the keyboard by typing "Key." and seeing what appears. It's also easy to read from the mouse by using Mouse.

Go ahead and run the code now (just press F5), and you'll see you can now move your spaceship around using the Up, Down, Left and Right cursor keys. It's not perfect, though, because you can move your ship off the screen, which is bad because it means players can lose their ship by accident, and also it means they can cheat by avoiding all the obstacles we're going to throw at them!

The solution is to add more conditional statements that automatically force the ship to be in the right position. This time we need to check whether the ship is off any of the four edges, and, if it is, force it back into play. Add this code to the end of the Update() method in Player.cs:

if (X < 0) { 
	X = 0; 
} else if (X + sfcPlayer.Width > 1024) { 
	X = 1024 - sfcPlayer.Width; 
} 

if (Y < 0) { 
	Y = 0; 
} else if (Y + sfcPlayer.Height > 768) { 
	Y = 768 - sfcPlayer.Height; 
}

There are two new things in there that are worth noting: the "else if" statement means "if that first "if" statement was false, try this one instead". You can have as many "else if"s as you want, or even just a general "else" that executes if the "if" is not true.

All Surfaces have a Width and Height value that we an read and use to force the player back into the screen area. Both halves of that code work in the same way, so it's only really worth explaining one. Keep in mind that all screen measurements start at (0, 0), which is the top-left of any surface.

So, if the player's X position is less than 0 it means they have gone at least partly off the left edge of the screen. The solution is simple: if that happens, force the X position to be 0. If the X position isn't less than 0, then it might be over the size of the screen (1024), which means the player is off the right edge of the screen.

Now, because the X position is really the top-left of the player surface, we need to add to it the width of the surface to find whether the right edge of the player's ship is off the right edge of the screen. If it is, we set the player's X position to be the width of the screen minus the width of the spaceship.

That's it: your player can now zip around the screen without escaping on the edges. We told you it was going to get easier after the starfield!

Don't laze me, bro!

It's time for the fun bit: the shooting. Even the nice folks in Star Trek who claimed to be a mission of peace and exploration were smart enough to pack some phasers on their spaceship, and ours is going to be no different.

Now that you've added a starfield and a player, you should almost be able to write the lasers code yourself, but just to make sure you fully understand we're going to guide you through the new parts.

First, though, these are the things you should be able to do yourself by borrowing code from the Player and Starfield classes. If you're not sure, take a sneak peek at the source code for this project, but please do try by yourself first!

  • Create a new Laser class.
  • Give it a Surface sfcLaser that will be drawn on the screen
  • Give it X and Y floats for its position.
  • Change its constructor method so that sfcLaser is loaded from laser.bmp - don't forget to use the new LoadImage() method we made to automatically make magenta transparent.
  • Give it an Update() method that accepts an "elapsed" integer and moves lasers to the right every tick.
  • Give it a Render() method that accepts a Surface "screen" and draws sfcLaser at the laser's X and Y co-ordinates, making sure to use Convert.ToInt32() because those co-ordinates are floats.

Once you've done that, it's time for some new code. In summary, we need to:

  • Create a list of Laser objects that will track all the "live" lasers.
  • Allow players to fire lasers by pressing Space.
  • Call Update() and Render() for our lasers.
  • Remove any lasers that go off the screen.

We'll tackle them in order. First, go to TroutWars.cs and add a "using" line for System.Collections.Generic, because we need to create a list of lasers. While you're there, add another using line for "SdlDotNet.Input" - we'll explain why shortly. Now add this line directly beneath "Player ThePlayer":

List<Laser> Lasers = new List<Laser>();

That's where all our lasers will be stored.

Tracking when Space is pressed isn't quite as easy as you think, because the code we're using simply tells us when a key is being held down. If we used that, players would fire many times just by holding down Space, when really we want to press it as and when they need it.

The reason you added the new "using" line for SdlDotNet.Input is so that we can track input directly in TroutWars.cs, because we can use an event handler to track when a key is pressed as opposed to when it is held down. To attach a method to be run when a key is pressed, put this code inside the constructor method, just beneath the Events.Quit line:

Events.KeyboardDown += new EventHandler<KeyboardEventArgs>(KeyDown);

That will execute a method called KeyDown (as yet unwritten) every time a key is pressed. What that method needs to do is check whether Space was the key that was pressed, and, if it was, fire a laser. For now we're just going to put in an empty FireLaser() method so you can see how the chain of events works:

void KeyDown(object sender, KeyboardEventArgs e) { 
	if (e.Key == Key.Space) { 
		FireLaser(); 
	} 
} 
		 
void FireLaser() { 
}

Hopefully you've spotted the new operator there: ==. This is two equals signs side-by-side, and means "is equal to". So in this case, it means "if the key that was pressed is equal to the Space bar, fire a laser".

KeyDown

We're catching key presses here, but you can also catch key releases - it's down to you!

Actually firing a laser is quite straightforward: we just create a new laser object, set its X and Y co-ordinates to the same as the player's, then add it to the Lasers list, like this:

Laser laser = new Laser(); 
laser.X = ThePlayer.X; 
laser.Y = ThePlayer.Y;
Lasers.Add(laser);

If you press F5 now you may get an error along the lines of "Laser.X is inaccessible due to its protection level." This gives us a chance to explain a little bit more programming jargon: the "public" keyword. This is used in various places in our code, and what it means is "let anyone access this variable or method."

If you don't use "public" then no one can access that value outside of the class it's declared in. What that error message means is that the Laser class has an X variable, but it's not declared as being public, so the TroutWars class isn't allowed to read it.

This is actually a beneficial feature, because sometimes you don't want other code (specifically code written by other people!) to read certain data, because it means you have complete control over it. If you get that error, go back to Laser.cs and change "float X" to "public float X" and the same for Y - it should work now.

That's the second problem solved, so now on to #3: updating and rendering the lasers. You should already have created Update() and Render() methods for the Laser class, so all we have to do is edit the Update() and Render() methods in TroutWars.cs to update and draw the lasers. This is done with two simple foreach loops. Here's the one for the Update() method:

foreach(Laser laser in Lasers) { 
	laser.Update(elapsed); 
}

And here's the almost-identical one for Render():

foreach(Laser laser in Lasers) { 
	laser.Render(sfcScreen); 
}

If you try that, you'll notice there's one minor problem - because we're using the player's X and Y position for the laser, the laser appears from the top-left of the spaceship rather than from anywhere sensible. You can fix that by typing in the correct X/Y offset to make the laser appear from a slightly different location - change FireLaser() so that the laser's X and Y co-ordinates are modified like this:

laser.X = ThePlayer.X + 40; 
laser.Y = ThePlayer.Y + 28;

You'll get the best results if you draw the player after drawing the lasers, so that the lasers appear to come out from the ship rather than just appearing.

The final step is to remove any lasers that go off the screen to the right. This might seem fairly simple at first because you think you can write code like this:

foreach (Laser laser in Lasers) {
	laser.Update(elapsed);
	if (laser.X > 1024) Lasers.Remove(laser);
}

All that code is valid, but it's very likely to crash because Mono doesn't let you change a list during a foreach loop. That is, if we find a laser has gone off screen, we remove it from the Lasers list using the Remove() method. But once that happens, the Lasers list has been changed, so Mono will throw up an error rather than continue in the foreach loop, because the number of items has changed.

Your second attempt might look like this:

for (int i = 0; i < Lasers.Count; i = i + 1) {
	Lasers[i].Update();
	if (Lasers[i].X > 1024) Lasers.RemoveAt(i);
}

Again, all that code is valid. The RemoveAt() method is like Remove(), except it takes a position in the list that you want to get rid of. But the problem is that if you remove an item from a list, all the other items in the list move down one place, which means you can't just continue counting up the way.

So, now that you've seen what doesn't work, here's what does work: you need to count backwards through the list, because if you use RemoveAt() on a list, all the items will move down one place, but you've already gone through those items so it doesn't matter where they are. In Mono, you need this:

for (int i = Lasers.Count - 1; i >= 0; i = i - 1) {
	Lasers[i].Update();
	if (Lasers[i].X > 1024) {
		Lasers.RemoveAt(i);
		continue;
	}
}

That will ensure that as soon as lasers go past 1024 pixels across, they are removed. Magic! We added one sneaky little extra in there, which is the "continue" keyword - this means "we're done with this laser now; stop executing this loop and go to the next laser".

Fire at will! With lasers in place, the game is starting to take shape.

Fire at will! With lasers in place, the game is starting to take shape.

The enemy of my enemy

With stars, a player and some lasers, the exciting climax is finally at hand: we can create enemies to destroy. Again, you should be able to do a large chunk of this for yourself, but if not just download our example code.

Here's the enemy picture we'll be using in the game.

Here's the enemy picture we'll be using in the game.

Using the Laser class, you should be able to create an enemy class that works in exactly the same way as a laser except for two difference: sfcLaser should be sfcEnemy, and the enemy should move to the left in its Update() method rather than to the right.

Once that's done, go back to TroutWars.cs and make the same changes as we made for the lasers - create a list called Enemies that holds Enemy objects, then put a foreach loop into Update() and Render() that updates and renders each enemy, just as we did with the lasers.

Once that's done, we need to add something to ensure a new enemy is created once every few seconds. To do that, we need to define an integer that tracks the last time an enemy was created, and, if that's below a certain threshold, create a new enemy. We're going to make that threshold another variable so that we can make enemies be created faster and faster over time. So, add these two lines of code beneath the List of Enemies:

int LastEnemyCreation; 
int EnemyCreationSpeed = 2500;

Now what we can do is put the time test in the Tick() method, like this:

if (LastEnemyCreation + EnemyCreationSpeed < Timer.TicksElapsed) { 
	CreateEnemy(); 
}

Timer.TicksElapsed is new, but very helpful - it stores the number of milliseconds since the game was started. So, we can compare that against the time the last enemy was created, and if enough time has passed (2500 milliseconds), create a new enemy.

The CreateEnemy() method isn't too different to the FireLaser() method, except now we use a random Y position and sets its X position to be just off the screen. It also needs to end by resetting the LastEnemyCreation time to be the current Timer.TicksElapsed value so that another enemy isn't created for another 2.5 seconds.

You can also, if you want to, use this method to decrease EnemyCreationSpeed by 50 each time it's called, which will make enemies be created faster and faster over time.

Here's how you'd write CreateEnemy():

void CreateEnemy() { 
	Enemy enemy = new Enemy(); 
	enemy.X = 1024; 
	enemy.Y = MainClass.Rand.Next(720); 
	Enemies.Add(enemy); 
	LastEnemyCreation = Timer.TicksElapsed; 
	EnemyCreationSpeed -= 50; 
}

Now we're down to one last thing: making our brand-new enemies die horribly as they are zapped by lasers!

We can fire lasers and there are enemies to shoot at - almost done!

We can fire lasers and there are enemies to shoot at - almost done!

When fish and lasers collide

We now have a list of lasers and a list of enemies, so the next step is to destroy enemies when they get hit by lasers. To do that, we need to loop through all the lasers, and, for each laser, loop through all the enemies to see whether they collide. If we get a collision, we need to remove the laser and the enemy, remembering to loop backwards through the arrays. So, in the Update() method that we already have, change the lasers loop to this:

for (int i = Lasers.Count - 1; i >= 0; i = i - 1) { 
	Lasers[i].Update(elapsed); 
	if (Lasers[i].X > 1024) { 
		Lasers.RemoveAt(i); 
		continue; 
	} 
				 
	for (int j = Enemies.Count - 1; j >= 0; j = j - 1) { 

		Rectangle laser = new Rectangle(Convert.ToInt32(Lasers[i].X), Convert.ToInt32(Lasers[i].Y),
                         Lasers[i].sfcLaser.Width, Lasers[i].sfcLaser.Height); 

		Rectangle enemy = new Rectangle(Convert.ToInt32(Enemies[j].X), Convert.ToInt32(Enemies[j].Y),
                         Enemies[j].sfcEnemy.Width, Enemies[j].sfcEnemy.Height); 
					 
		if (laser.IntersectsWith(enemy)) { 
			Enemies.RemoveAt(j); 
			Lasers.RemoveAt(i); 
			break; 
		}				 
	} 
}

There are two new parts there, but neither of them are tricky. First, there's a new data type: Rectangle. When you create a new rectangle, you need to tell Mono the X and Y co-ordinates (as integers, hence the Convert.ToInt32() calls), then the width and height.

Once we do that for both the laser and the enemy, we can use a special Intersects() method of rectangles that tells us whether one rectangle crosses another. Now, clearly the laser and fish aren't rectangular, but they aren't far off, either, which makes rectangle-to-rectangle collision detection fast and easy.

If a laser collides with an enemy, we remove them both from their respective arrays and use the "break" keyword, which means "get out of this loop entirely". That's just a little bit different from the "continue" keyword, which means "go back to the top of the loop with the next item".

Collision detection

One of the most important things in any game is collision detection, which is how you check when one thing touches (or overlaps) with another. We're using rectangle/rectangle collision detection here (known as bounding box collision detection), but another very fast way is to use circles because then you only need to check how far one item is from the centre of another.

What's next?

At this point you have a fully working game: your spaceship can fly around, fire lasers, and destroy enemies. What's more, you've also learned the absolute fundamentals of programming, which is enough to help you get started with some of the harder projects in this magazine.

Remember: programming should be fun. If you find yourself getting frustrated, take a break and come back later. Sometimes your programs get complicated enough that it's hard to get your brain around it all; the best solution to that problem is to break the code up into small pieces.

For example, in TroutWars we don't make all the stars move in TroutWars.cs. Instead, we tell the Starfield class to update itself, and that in turn tells each Star to update itself. The reason this helps is because you can write the Update() method for stars once, then forget about it because it just works. Whereas if you put all your code in one place, it gets cluttered, it's easy to break, and it makes your brain work harder.

So create your own classes to do everything. Wherever you can, make these classes update and draw themselves. If possible, try not to mark your variables and methods as being "public", because that means any other code can mess with them. Remember to move things based on how much time has elapsed, because that future-proofs your work.

Remember to use ints when you want whole numbers and floats when you need more precision. And most important of all: remember to relax and enjoy yourself. We chose to make a game here because learning theory by itself is dull, whereas having some laughs while you learn means you can advance faster and get your teeth into the real stuff!

From here you have two choices: continue working on Trout Wars, or visit our Code section to find more tutorials to tackle!

First published in Linux Format

First published in Linux Format magazine

You should follow us on Identi.ca or Twitter


Your comments

So programming is like being a dominatrix to your pc?

the title says it all.

Language choice

While Mono may be the darling of the Novell world these days, I'd suggest retooling this to use python instead. Really, if nothing else, enforcing indentation as a syntactic element is GREAT for teaching newbies good indentation discipline (which you don't even seem to talk about :'( ).

Re: language choice

Wow... you've just read over a gigantic tutorial to help teach newbies to code... and your response is that they didn't talk about "indentation discipline"?!

I'll put some indentation discipline into them...

send your newbies to me! :)

Re: Language choice

I agree with the previous poster, wtf up with that attitude? This is a fantastic article, breaking down barriers and making something rather interesting. Then you, the troll, comes in and talks about indentation discipline. C'mon, crawl back under the rock you came from and stay there.

Please, you the author of this article, don't even begin to formulate a response to the above poster.

Error running TroutWars

Hi,
Good tutorial, little over my head though. Get this error:

Unhandled Exception: System.TypeInitializationException: An exception was thrown by the type initializer for SdlDotNet.Graphics.Surface ---> System.DllNotFoundException: SDL_image.dll
at (wrapper managed-to-native) Tao.Sdl.SdlImage:IMG_Load_RW (intptr,int)
at SdlDotNet.Graphics.Surface..ctor (System.Byte[] array) [0x00000]
at SdlDotNet.Graphics.Surface.Initialize () [0x00000]
at SdlDotNet.Graphics.Surface..cctor () [0x00000] --- End of inner exception stack trace ---

at SdlDotNet.Graphics.Video.SetVideoMode (Int32 width, Int32 height, Int32 bitsPerPixel, Boolean resizable, Boolean openGL, Boolean fullScreen, Boolean hardwareSurface, Boolean frame) [0x00000]
at SdlDotNet.Graphics.Video.SetVideoMode (Int32 width, Int32 height) [0x00000]
at TroutWarsNG.TroutWars..ctor () [0x0002e] in /home/siggi/csharp/TroutWarsNG/TroutWars.cs:24
at TroutWarsNG.MainClass.Main (System.String[] args) [0x00000] in /home/siggi/csharp/TroutWarsNG/Main.cs:15

Any ideas?

Re: Error running TroutWars

That error occurred because it was missing SDL_Image. Did you follow the install guide? Make sure you have libsdl-image1.2-dev installed and the problem should go away.

mono?!?!

screw mono, this should be a python, perl, or bash tutorial. not freakin' mono!

Somebody should take their

Somebody should take their head out of their ass and stop complaining like some lazy bitches that it's written in mono.
If you're so smart take the tutorial and write it in python and share.
FFS

Visual Studio - Linux craptastic edition

I didn't come to Linux to code with MS bastardized projects.

C'mon, guys. Programming is programming.

This is a very helpful article. Yes, Mono has issues. Yes, OSS fans wouldn't use it. No, that doesn't make this an evil article.

Perhaps the writer only has expertise in Mono. All credit to h[er|im] for sharing. Taking the considerable time and effort to produce this article. It will help many people who just need to get started.

Writer; this is VERY well pitched at the novice programmer. Well done; few can offer this mixture of fun and information, and will make readers look further.

Readers; DO look at alternative programming languages, and carefully choose some to try that give you what you need. There are MANY to choose from, and each offers some unique advantages and disadvantages over others. It's disheartening to invest lots of effort into learning one just to find a show-stopping impediment to achieving what you want. For example, you write a killer program that you could sell for thousands - then find that commercial licensing issues will cost you those thousands.
Good luck.

AND . .

Yes, please - one of you whinging self righteous programmers write a comparable version in something else - Python or Ruby would be good. Prove that your language is as good or better.

Mono? Take your Mono (disease) to a Windoze Dev site

Mono is a patent laden load of @$%4! Perl, Python, Java, and others will do the job, and without the "patent suit" threat from the Monopoly.

It's about PROGRAMMING, not about religion.

You miss the point. Forget the language. Get people into programming. You are making a real bad show for programming noobs and putting FOSS into a bad light with your attitude.
I agree about Mono and patents and that stuff, but that's for another forum. You're worse than wasting your words here, you're exacerbating what puts people's backs up about FOSS.
Get with the program :^) and back on topic.
You could list your idea of better alternative languages, and why, and point to other tutorials. THAT would be positive.

Ya it may be

Yes it may be about programming and learning to program. But learning a language that nobody needs because there's better, faster, more portable versions all around seems a little stupid. I like where your going but maybe Java would be a better language to use. It's fairly simply and doesn't fail to build on Sun, FreeBSD, Minx, 2.4 kernels etc...

mono-gmcs package needed

just to point out, the mono-gmcs package is also needed to be installed to follow the tutorial...

nice work... keep them coming :)

this is a good start for someone who is looking for an easy-to-follow tutorial

I liked it...

I don't like Mono but as a developer who has only written utilities so far, I found it informative and interesting... I may actually take a stab at writing a simple game now...

Seriously, an "intro to programming" that starts out with a game for the first project... Damn... We've come a long way...

Keep it up!

Agree. Mono is a viral

Agree. Mono is a viral infection from Windows to get people to work with Windows code and software. It is not of the Unix philosophy of how to do coding and should be rejected.

Claiming this article is for beginners to learn how to code is a facade. (Who is this anonymous author anyway?)

Ignore the Jihadists

Please ignore the Windows Jihadists. This is an excellent article. There are also Python tutorials on this site, which are also excellent, so there's no problem with a lack of choice in choosing your programming tutorial.

On a tangent here, but does anyone else think that the swivel-eyed obsessives really give the more pragmatic Linux users a bad name. Presumably you'd all prefer there was no information available online about installing Flash and MP3 support?

erorr

mono TroutWarsNG.exe

Unhandled Exception: System.TypeInitializationException: An exception was thrown by the type initializer for SdlDotNet.Graphics.Surface ---> System.DllNotFoundException: SDL_image.dll
at (wrapper managed-to-native) Tao.Sdl.SdlImage:IMG_Load_RW (intptr,int)
at SdlDotNet.Graphics.Surface..ctor (System.Byte[] array) [0x00000]
at SdlDotNet.Graphics.Surface.Initialize () [0x00000]
at SdlDotNet.Graphics.Surface..cctor () [0x00000] --- End of inner exception stack trace ---

at SdlDotNet.Graphics.Video.SetVideoMode (Int32 width, Int32 height, Int32 bitsPerPixel, Boolean resizable, Boolean openGL, Boolean fullScreen, Boolean hardwareSurface, Boolean frame) [0x00000]
at SdlDotNet.Graphics.Video.SetVideoMode (Int32 width, Int32 height) [0x00000]
at TroutWarsNG.TroutWars..ctor () [0x00000]
at TroutWarsNG.MainClass.Main (System.String[] args) [0x00000]

solved

small problem with synaptic
apt-get install solved that !
sorry for post !

ps: error to libpng12-dev

Could you give more

Could you give more information on what is needed in the
above atp get routine ... Using mono/monodevelop in Ubuntu
9.04 ... 32/64 bit versions

Hxray

I'm stuck, I just want to learn

where do I put code,
that's the one of the biggest reasons I give up on coding
so quickly:
public TroutWars()
{
Console.WriteLine("Hello World!");
}

It's under
the title:A class act

A good way to answer my question is by showing the full code
as it should be after applying it.

Here is the code so far:
// TroutWars.cs created with MonoDevelop
// User: ray at 12:33 AMĀ 9/29/2009
//
// To change standard headers go to Edit->Preferences->Coding->Standard Headers
//

using System;

namespace TroutWars
{

public class TroutWars
{

public TroutWars()
{
}
}
}

Wow

Great tutorial.

Personally, despite being a Linux user (Ubuntu, Suse and Xubuntu at home), I do think that the one thing that Microsoft do well is languages. C# is Java done properly.

The fact that I can write something in C# (using SDL.Net) at work on Windows, take it home and run it almost out of the box on any of my linux machines is a godsend to me.

Distributing Tao without licence files.

Just to let you know inform you that you are required under the terms of the Tao Framework licence to keep the MIT licence alongside them.

Alongside this you are also distributing SDL.Net without the accompanying LGPL text.

This is probably not a good example to set.

Good Concept, needs some corrections

Nice program to start learning. There are a few errors in this program that pushes you to troubleshoot as you go along.

In the paragraph 'Starfield, draw thyself' the last sentence states:

go to Main.cs and add this line of code before the constructor method:

Surface sfcScreen;

To get the program to work had to add 'Surface sfcScreen' to Troutwars.cs instead.

Code:

screen.Blit(sfcStars[star.Type], new Point(Convert.ToInt32(star.X), star.Y));

causes program not to work until 'Stars 2.0' is completes and in stars.cs 'public int Type;' is added after 'public int Y;'

I used to program in C and Assembly over 10 years ago, so this is a fun jumpstart program. Hope corrections can be made to the program so the journey is not hard for others
.

Availability of SPECIAL EDITIONS of LINUX FORMAT

How can I get a copy of a number of your special editions??? The CODING special was great - I gave it as a present to my friend in Nanjing, CHINA - he heard about it but the cost was prohibitive. I would really like another copy of that as well as two other specials. Is there a section in LINUX FORMAT for such an issue?????

LF is the most well written computer periodical I have encountered. Thank you.

Bob

Can this guide be used for windows?

I really enjoy how your guide is written! Could this same guide be used with Windows? or are alot of the fundamentals different?

well done

thanks for sharing this tutorial. for all other complaining ladies ; the reason of their complaining must be jeaolosy.

I *was* going to join FLOSS, but now...

After seeing the FLOSS comments, I am quite annoyed. ALL of them said how other languages would be better, but NONE of them has given advice, or links as to how this would be done.

I absolutely HATE Microsoft's (and apple's) monopoly(s), and especially the cheek of restricting UFEI to be always on, but especially the way choice has smashed (and subsequently flew) out the windows! (tee-hee) and I would go for trisquel, except gnash doesn't work at all, and because a few other reasons. And I would love to end it at any costs.

BUT! there is no reason at all to FORCE people to do anything, because isn't that what FLOSS is all about? Promoting choice?

think about that...

PS. great article!

The beginner's guide to coding

using Mint 13 mate, MonoDevelop wouldn't even do "hello world" till Xterm was installed
thats not on the list of requirements presumably ubuntu installs as standard ?

the name Quit does not exist in the current context

So, I'm a beginner and don't really understand. I have rhis so far... :
using SdlDotNet;
using SdlDotNet.Core;
using SdlDotNet.Graphics;
using System;
using System.Drawing;

namespace TroutWars
{
public class TroutWars
{
public TroutWars() {
Video.SetVideoMode(1024, 768);
Events.Quit += new EventHandler<QuitEventArgs>(Quit);
Events.Run();
}
}
}

I don't understand whats wrong with the quit line and i don't know where to input the line "void Quit(object sender, QuitEventArgs e) {
Events.QuitApplication();
}"

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Post new comment

CAPTCHA
We can't accept links (unless you obfuscate them). You also need to negotiate the following CAPTCHA...

Username:   Password:
Create Account | About TuxRadar