123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634 |
- ============================================
- Chapter 17: creating a game window with SDL2
- ============================================
- What you need to know before you read this:
- * Linux: text editors
- * Git: repositories
- * Git: clone
- * Git: commits
- * Git: checkout
- * C programming: variables
- * C programming: functions
- * C programming: function arguments
- * C programming: strings
- * C programming: enum
- * C programming: typedef
- * C programming: struct
- * C programming: whitespace
- * C preprocessor: #include
- * C statements: return
- * GCC: compile a program
- * GCC: link to external library
- * GCC: preprocessor
- * SDL2: SDL_Delay
- What this chapter will teach you:
- * Math: convert binary to decimal
- * C programming: unsigned integers
- * C operatators: bitwise
- * SDL2: SDL_CreateWindow
- * SDL2: Uint32
- Projects worked on:
- * Cuterald
- Repo: http://hqetojnktk6trragksiuszsodbuszhmekulsbtp2bfumvh3i7sfiyoyd.onion:80/Maple/cuterald.git
- Current commit: a8473aa557604daa8322d06aaa1ad6c93348486b
- Section I: chapter introduction
- -------------------------------
- Two chapters ago, before we took a detour into enums and structs, we linked to SDL2 and used one of
- the functions it provides - SDL_Delay. I showed you where the files are and what the process is of
- actually getting a C library ready for use. But we didn't really do anything useful in our program
- itself.
- We'll fix it in this chapter. Now that we're properly linked, we can use everything that SDL2
- provides without any additional effort, so getting a window on the screen will be a breeze.
- As usual, we continue with the code as we last left it. If you didn't follow along with the last
- chapter on Cuterald, or you want to have some working reference to compare to, check out the git
- repository listed above, and checkout the given commit. If you forgot how to do so, take a look at
- section I of chapter 15 for a brief refresher.
- Section II: SDL_CreateWindow example
- ------------------------------------
- I found that the approach of showing you some code and then explaining it line by line works quite
- well, so we'll be doing the same here. Don't try to copy over the code below into your program yet,
- as I'll walk you through it through the chapter.
- Here's an example on how to create a window in SDL2:
- ----------------------------------- [ example ] ---
- SDL_Window *window = SDL_CreateWindow (
- "Example SDL2 application",
- SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
- 1280,720,
- SDL_WINDOW_RESIZABLE
- );
- ---------------------------------------------------
- I've mentioned it a few times but never fully explained it, so it's worth repeating - C is very
- flexible with where you leave whitespace. If you're coming from a language like Python, this may
- seem very foreign and perhaps even 'wrong', but you've got the freedom to put newlines and spaces
- wherever you see fit. The only exceptions I can think of are after preprocessor commands (like
- #include) and of course inside of strings. I've used whitespace here to group function arguments
- in what feels like it should logically be together.
- Section III: SDL_CreateWindow parameters
- ----------------------------------------
- You can find the documentation for SDL_CreateWindow here: https://wiki.libsdl.org/SDL_CreateWindow
- I'll explain everything in this chapter, but take a moment to look at the page anyway. You should
- start to get into the habit of reading and using documentation, and the way to start is by just
- exploring it and getting comfortable with it.
- SDL_CreateWindow takes six parameters and returns a pointer to a variable of type SDL_Window.
- Don't worry yet about what pointers are, you don't need to understand them right now. Just
- remember that for the rest of the chapter, we'll use a variable called *window of type SDL_Window.
- SDL_Window is a type that SDL2 provides. While writing this, I checked the SDL2 include files to see
- what type it is exactly, but it looks like it's hidden from us. Which is fair, you don't really need
- to do anything to it directly - we'll just pass our window variable around to different functions
- that ask for it.
- Anyway, here's the declaration for SDL_CreateWindow, which I copied from the documentation I linked
- you to:
- ----------------------------- [ reference ] ---
- SDL_Window* SDL_CreateWindow(const char* title,
- int x,
- int y,
- int w,
- int h,
- Uint32 flags)
- -----------------------------------------------
- 1: const char* title
- Title for the window
- This is the text that appears on top of the window, usually to the left of the minimize,
- maximize and close buttons. Every window has a title like this, including the application
- you're using to read this. If you're reading from a shell, you'll probably see the command
- and/or directory you're running, and a browser will give you the title of the page.
- const char* is somewhat new. char* you know - that's just a string. const I'll explain
- shortly.
- 2,3: int x, int y
- Screen position where the window should be
- These are the coordinates of where the top left corner of your window will be, expressed in
- pixels. That's x pixels from the left side of the screen, and y pixels down. In the example in
- Section II I've instead passed SDL_WINDOWPOS_CENTERED, which the documentation recommends.
- It's a value that SDL2 defines, and maps to some magic value (that I didn't bother to check
- because there's no reason to know) that SDL2 recognizes as being a different behavior.
- 4,5: int w, int h
- Size of the window
- These are the width (horizontal) and height (vertical) of the window in pixels, not including
- any border decoration. This defines how much room you're giving yourself to draw in.
- You can put any trivial numbers in here, but I've gone with 1280x720, which is the standard
- HD (720p) resolution.
- Wikipedia has a pretty neat graph listing a number of resolutions for different purposes:
- https://upload.wikimedia.org/wikipedia/commons/0/0c/Vector_Video_Standards8.svg
- And you can also check your own display settings to see what would work well on your computer.
- 6: Window flags
- Flags are a very common way to switch on and off different features for anything you may
- create or call in any C (or other) program. In the example I'm passing a single enum value,
- but you can also pass multiple at the same time with bitwise operators, which I'll cover later
- in this chapter.
- If you want to test it in your program right now, then #include the SDL2/SDL_video.h header file and
- plop this call just above your SDL_Delay line. If you put it after, the window will still be
- created, but it'll be destroyed again when your program exits, which makes your window a "blink and
- you'll miss it" phantom. Make sure to pass -lSDL2 when you compile it.
- Section IV: the const modifier
- ------------------------------
- In the example code above, you saw this as one of the arguments to SDL_CreateWindow:
- const char* title
- That is what we call a "const modifier", which is one of the items that we said we were going to
- cover later. For now, know that const tells the compiler that the value should not change after
- being assigned.
- However, because this particular variable is a C string it actually *is* possible to change it,
- because the const here applies to the pointer (see the '*'?) rather than the text itself.
- So in this context it behaves the same way as a regular char*, other than some optimizations
- happening in the compiler, and adding some minor safeties. It's usually a good idea to mark
- variables as const if you don't expect them to change. When we actually come across such a
- case, we will explore it in more detail.
- Section V: unsigned integer
- ---------------------------
- Another interesting item variable is flags:
- Uint32 flags
- In the last chapter we talked about creating custom types. One of the approaches is typedef, which
- is exactly what is used to create Uint32. I did some digging in SDL2 code for this chapter, and
- I'll spare you from having through any of the code yourself.
- Here is the line in the SDL2 code that creates Uint32:
- typedef uint32_t Uint32;
- And uint32_t is part of the standard C library. You can find a reference for the header that
- defines it here:
- https://www.cplusplus.com/reference/cstdint/
- "Unsigned" is another concept you haven't really learned. It's similar to a variable modifier, like
- const, and it applies only to numbers. It makes it so that the number can't go negative, which gives
- you one extra bit to store the actual number with, which means you can store numbers twice as high
- as what a "normal" (signed) integer would have allowed.
- Section VI: binary to decimal
- -----------------------------
- If you check the documentation again for SDL_CreateWindow, you'll see in the Remarks section that
- you can pass multiple enum (which you learned last chapter) values. However, you can't pass them as
- separate arguments - they all have to be passed simultaneously. And it's not an array either, the
- variable would be decorated with pointer ('*') or array ('[]') syntax.
- The trick to understanding this requires that we dive a bit into binary. As you may remember when we
- covered variables, everything in a C program is stored in binary, and the variable data type defines
- how it's actually read and presented to you. You may see numbers and characters and text and colors
- and pixels, but in memory everything is ones and zeroes no exceptions.
- Here are some quick examples of what decimal numbers look like in binary:
- decimal number | binary number ( 1 byte )
- -----------------------------------------
- 42 | 0010 1010
- 69 | 0100 0101
- 255 | 1111 1111
- 0 | 0000 0000
- The example is with only 8-bit numbers (ie, one byte). Uint32 is 32-bit, so the binary number would
- have four times as many digits, which means there are quite a few more zeroes on the left side of
- all of these.
- The exact mechanics of converting between decimal and binary and the most efficient ways to do so
- are out of scope for this book, as it's less about programming and more about math. I'll just show
- you the most basic possible way to do it, which is all you really need to understand the concept
- of storing flags as an integer.
- For this course, grab a calculator that has a science or programmer mode. If you're running Linux
- (as you should be), both gnome-calendar and kcalc both have it. Alternatively, type something like
- '42 to binary' in duckduckgo and it'll also give you the binary version.
- The only thing you need to know about conversion for this course, is the powers of two. When given a
- binary number, every digit represents a power of two in decimal. Here's an example for 8 bits:
- Binary: 1 1 1 1 1 1 1 1
- Decimal: 128 64 32 16 8 4 2 1
- You'll notice a pattern: every time you move a digit to the left in the binary number, the
- equivalent decimal value multiplies by two. Hence, powers of two. This goes on to infinity.
- To easily convert a binary number to decimal in your head, you sum up all the decimal presentations
- where the binary is set to '1'. For the example above, that means the value is 128+64+32+16+8+4+2+1
- = 255. Since it's all ones here, you can guess that 255 is the largest unsigned number we can
- express with just eight bits.
- Let's take 10110110 as an example. You can represent the binary and decimal values using the same
- little graph:
- Binary: 1 0 1 1 0 1 1 0
- Decimal: 128 _ 32 16 _ 4 2 _
- Whenever the binary is 0, the decimal is also zero, and when it's 1, the decimal is whatever that
- spot represents. So add all the numbers from the decimal row together, and you get 182. You can
- enter that in your calculator and switch it back to binary, and you'll see that you get the same
- binary number back.
- Section VII: mapping enums to powers of two
- -------------------------------------------
- So the way this works with enum values is simple. When you create your enum, you match them to the
- decimal representations:
- --- [ example code ] ---
- enum personality_traits
- {
- friendly = 1,
- curious = 2,
- energetic = 4,
- annoying = 8
- };
- ------------------------
- Then when you say something like this:
- unsigned int my_friend = annoying;
- Then you know that the value of my_friend is 8, or - in binary - 1000.. Likewise, if you set the
- value to 6, then you know it's 4+2, so he or she is energetic and curious. Below are all of the
- combinations you can have with that particular enum:
- decimal binary flags active
- ------------------------------------------------
- 1 0001 friendly
- 2 0010 curious
- 3 0011 friendly, curious
- 4 0100 energetic
- 5 0101 energetic, friendly
- 6 0110 energetic curious
- 7 0111 friendly, energetic, curious
- 8 1000 annoying
- 9 1001 annoying, friendly
- 10 1010 annoying, curious
- 11 1011 annoying, curious, friendly
- 12 1100 annoying, energetic
- 13 1101 annoying, energetic, friendly
- 14 1110 annoying, energetic, curious
- 15 1111 all the traits!
- Are you in the mood for building a Sims clone? Because I sure am now.
- If you're confused about the logic, try using this table as a reference:
- Binary: 1 1 1 1 -> short: 1111
- Decimal: 8 4 2 1 -> total: 8+4+2+1 = 15
- Trait: annoying energetic curious friendly -> all the traits!
- The binary row is how it's stored in memory. The decimal row shows the decimal representation of
- that digit, as before. and the trait describes the personality trait we assigned to that decimal
- number, as per our enum above.
- So let's say, for example that we have binary value 1010 in memory. You can represent it as such:
- Binary: 1 0 1 0 -> short: 1010
- Decimal: 8 _ 2 _ -> total: 8+2 = 10
- Trait: annoying _ curious _ -> annoying, curious
- Like with regular conversion, each binary digit maps to a value in decimal. But now with the enum,
- we have the additional mapping of a decimal number to an enum value - our personality traits
- in this example.
- So when you see value 1010 in memory, you can figure out that the first digit represents 8, the
- third represents 2, and the remaining two don't matter because they're set to 0. Only 8 and 2 are
- active in this particular binary number, and we know from the enum that they map to 'annoying' and
- 'curious' respectively.
- You also know that 8 and 2 together makes 10. So when you see something like:
- my_friend = 10
- Then you know that it the binary representation is 1010, which represents 8 and 2, which represents
- annoying and curious.
- Section VIII: bitwise operators
- -------------------------------
- In real life you will very rarely work with numbers directly. You usually don't even known what they
- are - you just see the names. This is true for SDL_CreateWindow as well - the documentation lists
- the names only, never the numbers. That's because there is one final trick up our sleeve that lets
- you just write the names in the enum without ever knowing the corresponding numeric values. As
- usual, we went through this just so you know how things work below the surface, so you can
- confidently use the easy version without feeling like you have no idea what's really going on.
- So how do we combine words together when we can't just add the numbers together? The answer is
- bitwise operators.
- There are a couple operators in C that operate directly on binary level. You use them on variables,
- but the operation is performed directly on the binary in memory. The easiest way to understand this
- is to just see them at work.
- * The AND operation (symbol: &)
- * The OR operation (symbol: |)
- * Exclusive OR (symbol: ^)
- * Inversion operation (symbol: ~)
- All except the last one operate on two binary numbers every time, the same way that regular
- mathematical operators do.
- Before we explain these, a quick reminder that all math in C operates on binary, even if we
- see numbers. So for example, a simple addition would look like this:
- 1 0 0 1 0 1 0 1
- + 0 1 1 0 1 0 0 1
- ---------------
- = 1 1 1 1 1 1 1 0
- I assume you remember how to do this the slow way (ie, not relying on memory), by handling digits
- one at a time from right to left, and remembering carryover. It's the exact same as with regular
- decimal math, except here your maximum value for a single digit is 1, rather than 9. Bitwise
- operations can be mentally performed in similar ways. Again, this is math, not programming. You can
- ask any math teacher how to do addition, subtraction, division or multiplication in math and he'll
- be happy to walk you through it.
- The AND operation checks for each digit in original and comparison number if both digits are 1. Only
- if this is true, is the resulting digit 1 as well. You can see this happen on the third column from
- the right in the example below. That is the only column where both digits are 1. Everything else has
- at least one 0 in it, and therefor results in a final 0.
- 1 0 0 1 0 1 0 1
- & 0 0 0 0 1 1 1 0
- ---------------
- = 0 0 0 0 0 1 0 0
- In short:
- * Becomes 1 if both numbers are 1
- * Becomes 0 if either number is 0
- The OR operation is the same basic principle, but looser. It returns 1 is either digit was 1. That
- means that the only way to get a final 0 with an OR operation is if both digits were 0 to begin with.
- 1 0 0 1 0 1 0 1
- | 0 0 0 0 1 1 1 0
- ---------------
- = 1 0 0 1 1 1 1 1
- In short:
- * Becomes 0 if both numbers are 0
- * Becomes 1 if either number is 1
- The exclusive or (usually called XOR) is a bit inbetween the first two. It returns 1 if exactly one of
- the two numbers is a 1. It returns 0 in every other case - when you have two zeroes or when you have
- two ones.
- 1 0 0 1 0 1 0 1
- ^ 0 0 0 0 1 1 1 0
- ---------------
- = 1 0 0 1 1 0 1 1
- In short:
- * Becomes 1 if exactly one number is 1
- * Becomes 0 in every other situation
- The inversion (also called the NOT operator) is the only one so far that operates on a single
- number. The operation is exceedingly simple. It just flips every bit. 1 becomes 0 and 0 becomes 1.
- 1 0 0 1 0 1 0 1
- ~ ---------------
- = 0 1 1 0 1 0 1 0
- There are also shift operators, which move bits left and right. We won't cover them here unless we
- actually need them.
- Side note: Many explanations online or in books will show you the result of a bitwise operator using
- a single digit, but I don't like that approach. In reality, you will always be working on
- at least a byte of data in C, never on individual bits. You can actually write numbers in
- binary in C code, but it'll always be at least eight of them.
- Section IX: writing binary numbers in C
- ---------------------------------------
- In C, you can write numbers directly as binary, by writing the number as 0bxxxxxxxx, where the x-es
- are binary digits.
- ----- [ example code ] ---
- char binary1 = 0b10100100;
- char binary2 = 0b00011100;
- char binary3 = 0b11111111;
- --------------------------
- You can even define your enum this way, if you don't want to to the mental number conversion:
- --- [ example code ] ---
- enum binary_enum {
- value1 = 0b00011101,
- value2 = 0b11111111,
- value3 = 0b00000000
- };
- ------------------------
- For that last example, you can print the values out and see the integer values too. Just pass it as
- '%i' to printf, as you've done multiple times in the past. It doesn't matter that you used binary to
- assign it, since it always ends up as binary in memory anyway, even if you had assigned it using the
- standard decimal notation.
- ---------------- [ example code ] ---
- int main()
- {
- printf("value1 is %i\n", value1);
- printf("value2 is %i\n", value2);
- printf("value3 is %i\n", value3);
- return 0;
- }
- -------------------------------------
- You can also use shorter values. Even if you're storing in an eight bit char variable, you can still
- choose to only assign 0bxxxx to it. C will figure out that it has to fill it up with leading zeroes.
- The only problem is if you try to assign a number that's too large, such as assigning 16 bits to a
- char. Then your compiler will complain. But then it will also do so if you try to assign it a very
- large decimal value that can't be represented using the amount of bits that are available.
- Section X: combining flags
- --------------------------
- Some things you've learned are:
- * Flags are usually enums that map to powers of two
- * Powers of two can represent positions in a binary number
- * Bitwise operators work on digits in a binary number
- Let's take our example enum from a few sections ago again:
- --- [ example code ] ---
- enum personality_traits
- {
- friendly = 1,
- curious = 2,
- energetic = 4,
- annoying = 8
- };
- ------------------------
- Using what you've learned here about binary, you'll know that those values map to binary as such:
- * friendly = 1 = 0001
- * curious = 2 = 0010
- * energetic = 4 = 0100
- * annoying = 8 = 1000
- If you wanted to define a character who is curious and energetic, you'd have to combine 0010 and
- 0100. So using binary math, you'd want to do it like this:
- 0 0 1 0
- | 0 1 0 0
- -------
- = 0 1 1 0
- In C, you can write the same operation like this:
- int my_good_friend = 0b0010 || 0b0100;
- The value of my_good_friend will be 0b0110. Which translates to a decimal 6. As yo can see from the
- enum declaration, that maps to both energetic and curious.
- You can also do it with decimal numbers directly:
- int my_good_friend = 2 | 4;
- Both are exactly the same, since they're direct translations between binary and decimal
- representations of the exact same numbers.
- Now, usually when you deal with enums, you don't actually know the numeric values, unless you wrote
- it yourself. That's the main reasons that enums exist, actually - because numbers mean very little
- to the human brain if they weren't memorized. And even then, word recognition is a far faster
- process for the human brain than retrieving a number and matching that to a concept. Don't blindly
- believe me on that - I'm a programmer, not a neuroscientist.
- Because enum values map to numbers, you can just use the names directly, and it'll still be entirely
- equivalent:
- int my_good_friend = curious | energetic;
- And that's exactly what you see happen in the flags argument for SDL_CreateWindow. That's not just a
- guess - here's *exactly* how they're defined in one of the enums in the SDL2 source code:
- ---------------------------------------------- [ SDL2 source code ] ---
- SDL_WINDOW_FULLSCREEN = 0x00000001,
- SDL_WINDOW_OPENGL = 0x00000002,
- SDL_WINDOW_SHOWN = 0x00000004,
- SDL_WINDOW_HIDDEN = 0x00000008,
- SDL_WINDOW_BORDERLESS = 0x00000010,
- SDL_WINDOW_RESIZABLE = 0x00000020,
- SDL_WINDOW_MINIMIZED = 0x00000040,
- SDL_WINDOW_MAXIMIZED = 0x00000080,
- SDL_WINDOW_INPUT_GRABBED = 0x00000100,
- SDL_WINDOW_INPUT_FOCUS = 0x00000200,
- SDL_WINDOW_MOUSE_FOCUS = 0x00000400,
- SDL_WINDOW_FULLSCREEN_DESKTOP = ( SDL_WINDOW_FULLSCREEN | 0x00001000 ),
- SDL_WINDOW_FOREIGN = 0x00000800,
- SDL_WINDOW_ALLOW_HIGHDPI = 0x00002000,
- -----------------------------------------------------------------------
- Here they use hexadecimal notation rather than binary, which describes the values per byte rather
- than per bit but you still see that power of two pattern - 1, 2 4, 8, which is 0b0001, 0b0010,
- 0b0100 and 0b1000 in binary, respectively.
- You're not meant to use those hex values as they may change over time (and difficult to read
- anyway), but you can see now that it's indeed set up so that you can OR any of the enum values
- together and they'll never conflict with each other.
- Section XI: creating window with SDL_CreateWindow
- ------------------------------------------------
- After all that theory, it's time to finally make the actual call to the function. Assuming that
- you're still following along with the code - this is where we finally add something to our Cuterald
- project. Check out the commit listed at the top of the page, and you should end up with this code:
- ====================== main.c ===
- #include <SDL2/SDL_timer.h>
- int main (int argc, char** argv)
- {
- SDL_Delay(3000);
- return 0;
- }
- =================================
- Above the call to SDL_Delay, add the following:
- ======================================== main.c ===
- SDL_Window *window = SDL_CreateWindow (
- "Example SDL2 application",
- SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
- 1280,720,
- SDL_WINDOW_RESIZABLE|SDL_WINDOW_ALLOW_HIGHDPI
- );
- ===================================================
- You should be able to explain every bit of this on your own, but let's quickly go over it anyway.
- The first argument is the title. This is just a const char* and doesn't use anything too special.
- For horizontal and vertical size, we're using an special value that SDL defines and recommends in
- the documentation. I don't want to give it an exact position, so center of the screen is just fine.
- For size we'll just throw standard full HD in there for now. We'll definitely make this configurable
- at some point.
- The flags we've just spent some time on. Check the SDL documentation page for the possible values
- and their explanations. Depending on your system and drivers, you may need some different flags. See
- what works for your system and stick with that for now. Error handling isn't something we're going
- to bother with for now.
- Finally, make sure to also include 'SDL2/SDL_video.h', as that's what defines the SDL_CreateWindow
- function.
- Compile and run the code. Don't forget to pass -lSDL2 to gcc so it knows to look in the SDL2 library
- files for the implementations. If all goes well, you should see the window pop up for three seconds
- and then die. There's a good you won't see anything inside of it - it may be see-through, or you may
- see some glitchy nonsense. This is normal - we're not actually rendering anything yet, so the window
- takes whatever was present in memory or on the screen.
- Section XII: chapter summary
- ----------------------------
- I was originally planning to also set up a renderer in this chapter, but we took a bit longer on
- the binary stuff than I expected. The amount of things in the language you haven't learned yet is
- quickly decreasing even if it may not feel like it.
- In this chapter we took a typical call to SDL_CreateWindow and went in deep detail on all the
- arguments and the C syntax that comes with it. The most important part is understanding the binary,
- in terms of memory and bitwise operations. If you understand that, then you already know something
- that more than half the programmers on this planet have absolutely no clue about.
- Next chapter I hope to set up the renderer and draw a few squares. After that we'll probably
- get into loops and events.
|