Octo

A Beginner’s Guide to Programming with Chip8

This document is meant to allow someone with little to no experience programming to ease into working with Octo and Chip8. It introduces basic programming concepts as well as the features of the Octo programming language. If you’d like to, follow along with examples using Octo’s Web IDE.

First Steps

Chip8 is a very simple computer. It is similar in many ways to the real computers you work with every day. This guide will teach you how to write programs for Chip8. Programs are a sequence of instructions that the computer follows to accomplish some task, much like a recipe or a how-to guide. In order for computers to follow instructions, they must be written in a language the computer understands. The language we will learn is called Octo. The instructions that Chip8 understands work with numbers- adding them together, comparing them to one another and so on- to draw pictures and animate them.

Chip8 has 16 registers. They are named v0 through vf- that is v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, va, vb, vc, vd, ve, and vf. Each register can contain a number between 0 and 255. To put a number into a register, we use an instruction like this:

v5 := 34

The symbols := can be read as “becomes”. This instruction, also called a statement, would store the number 34 in the register v5. We can also add numbers to registers:

v3 += 15

This statement adds the number 15 to whatever number was stored in v3 and stores the result in v3. You can also add the contents of one register to another or copy the contents of one register to another:

v5 += v9

v2 := v1

Instructions are carried out in sequence, one after another. To write programs we will have to combine many simple statements like this to achieve our goals. Let’s look at a sequence of instructions. Can you work out what will be in register v2 when they are done?

v0 := 5
v1 := 3
v2 := v1
v2 += 10
v2 += v0

First we store 5 in register v0, then we store 3 in register v1. v2 := v1 will store the contents of v1 (which is presently 3) in v2. Then we add 10 to v2, giving a total of 13. Finally, we add the contents of v0 (which is presently 5) to the contents of v2, giving a total of 18.

A First Program

Now we understand registers and some of the instructions which manipulate them. There are a few more things we must learn before we can write a program that shows an image on the screen.

The instructions that make up a program for Chip8 are stored in Chip8’s memory. Memory is like a series of registers- each storing a number between 0 and 255- but there are over 3000 of them. An address is a number which indicates one of the spaces in memory. Chip8 has a special register called i which is used for storing addresses. There are differences between what we can do with i and what we can do with a v-register which we will discuss later, but we can store numbers in i:

i := 47

A label is a colon (:) followed by a name. Names must be all one word- no spaces. Labels allow us to refer to the address of a position in our program. Programs must always have a label called “main”, which is where the program begins.

To draw things on the screen, we use an instruction called sprite. Chip8’s screen is 64 pixels wide and 32 pixels high. To use sprite we must indicate a register containing a horizontal position in pixels (counting from left to right), a register containing a vertical position in pixels (counting from top to bottom) and how tall the image is, in pixels. The tallest sprite we can draw is 15 pixels tall and the shortest we can draw is 1 pixel tall. Sprites are always 8 pixels wide. The i register must already contain the address of the image we want to draw. Let’s look at an example:

: image
	60 126 219 255 189 195 126 60

: main
	v0 := 20
	v1 := 5
	i := image
	sprite v0 v1 8

First we have a label called image. After it are some numbers- in the next section we will talk about how those numbers make up the picture we want to draw. Then we have a label called main, which is where the instructions of our program begin. We set v0 and v1 to 20 and 5, respectively, and then store the address of image in i. Finally, sprite draws the image at the horizontal position given by v0 (20) and the vertical position given by v1 (5) which is 8 pixels tall. Try it!

If we change the values in v0 or v1 and say sprite again, we will see multiple copies of the image:

: image
	60 126 219 255 189 195 126 60

: main
	v0 := 20
	v1 := 5
	i := image
	sprite v0 v1 8
	v0 += 20
	v1 += 4
	sprite v0 v1 8

And we could give those sprites different images if we changed the value stored in i between drawing them:

: image
	60 126 219 255 189 195 126 60

: otherimage
	255 255 219 255 129 129 195 255

: main
	v0 := 20
	v1 := 5
	i := image
	sprite v0 v1 8
	v0 += 20
	v1 += 4
	i := otherimage
	sprite v0 v1 8

Drawing sprites flips the color of pixels. This means that if you draw a sprite twice in the same position, it will be erased. Try drawing some partially overlapped sprites to see how it looks.

Hexadecimal and Binary

Numbers can have different bases. This means how large a digit becomes before you carry. Most of the numbers you see on a daily basis are decimal, which means base 10- digits range from 0 to 9. When programming computers we often use hexadecimal (base 16- digits range from 0 to F) and binary (base 2- digits range from 0 to 1) numbers. In fact, hexadecimal numbers are the reason the v registers get their names.

When using Octo, numbers are decimal by default. If you start a number with “0x” you mean it is in hexadecimal:

0xA9

If you start a number with “0b” you mean it is in binary:

0b10100101

The largest two-digit number in hexadecimal (or hex for short), 0xFF, is 255 in decimal. That means the v registers can hold any two-digit hex number. The largest eight-digit number in binary, 0b11111111, is also 255 in decimal. That means the v registers can hold any 8 digit number in binary. Binary digits are often called bits, and since Chip8 works with 8 bits at a time it would be called an 8-bit computer. A byte is a common name for 8 bits.

Now that we understand hex and binary, we can explain how the images in the previous section worked. if we convert the decimal numbers for the first “image” into binary, they look like this:

0b00111100
0b01111110
0b11011011
0b11111111
0b10111101
0b11000011
0b01111110
0b00111100

If you squint, you can probably make out the smiley face! Every pixel that is turned on in the image when we draw it is a 1 and every pixel that is turned off is a 0 when we look at the number in binary, and every number is an 8-pixel row of a sprite.

Try making your own sprite images using binary, or use the sprite editor built into Octo to draw a sprite and get the corresponding hex numbers (or vice versa).

Animation

We know how to draw sprites on the screen. To make animations we draw one or more sprites on the screen, erase some or all of the screen, redraw a slightly different scene and then repeat. You’ll want to set Octo’s emulation speed to “7 Cycles/Frame”, it’s lowest speed, so you can clearly see the animation take place in very short programs.

To erase everything on the screen, use the statement clear. The statements loop and again are how we write a program that does something over and over. Normally, statements are carried out one after another, from the top of the program to the bottom. When again is encountered, Chip8 will skip back to the matching loop and resume from that point. Let’s look at an example:

: arrow
	0x38 0x38 0x38 0xFE 0x7C 0x38 0x10

: main
	i  := arrow
	va := 30 # the arrow's horizontal position
	vb :=  5 # the arrow's vertical position

	loop
		clear
		sprite va vb 7
		vb += 1
	again

Two of the lines in this program have a # symbol- this is called a comment. The computer ignores the rest of a line after the # and anything written there is for a human reader. Comments can be very helpful for explaining the meaning behind parts of your program or leaving yourself reminders for later. You can also “comment out” sections of statements by placing # symbols in front of them if you wish to temporarily (and reversibly) remove them from your program.

This program first sets up registers to get ready to draw an arrow at an initial position on the screen (30 pixels over, 5 pixels down). Then we see a loop which marks the beginning of the instructions we will carry out repeatedly. clear erases the screen, sprite draws an arrow and then the number in register vb is increased by 1. Then we encounter again and we resume with clear. Since we change vb (the vertical position of the arrow) each time, the arrow appears to move from the top of the screen to the bottom. You’ll also notice that as the sprite is drawn off the edge of the screen it automatically “wraps” around to the opposite edge.

If we were to change the contents of the registers differently we could make the arrow move in a different direction:

: arrow
	0x20 0x71 0xFB 0x7F 0x3F 0x1F 0x3F 0x7F

: main
	i  := arrow
	va :=  0 # the arrow's horizontal position
	vb :=  0 # the arrow's vertical position

	loop
		clear
		sprite va vb 8
		va += 2
		vb += 1
	again

Try changing this program to move a sprite in a different direction, or to draw more than one moving object.

Making Decisions

Computers are good at doing arithmetic. The other thing they’re good at is making simple decisions. By combining these abilities, we can write programs that carry out complex tasks.

Making decisions uses the if statement. The word if is followed by a comparison between registers or a register and number, the word then and then some other statement. If the comparison is true then the other statement is carried out. If the comparison is false then the other statement is skipped. Here are some examples:

if v0 == 4  then v1 += 1
if v2 != v9 then clear

The symbols == are read as “is equal to”. This first if statement will be true if the number in the register v0 is equal to the number 4, and if this is the case we will add 1 to the value in register v1. The symbols != are read as “is not equal to”. This second if statement will be true if the number in the register v2 is not the same as the number in the register v9, and if this is the case then we will clear the screen.

Let’s try to make a program similar to our earlier animations, with a ball that moves from left to right. The ball will bounce up and down, reversing direction every time it ‘hits’ the top or bottom of the screen.

: ball
	0x3C 0x7A 0xFD 0xFF 0xFF 0xFF 0x7E 0x3C

: main
	i  := ball
	va := 32 # the ball's horizontal position
	vb := 12 # the ball's vertical position
	vd :=  1 # the ball's vertical direction

	loop
		clear
		sprite va vb 8

		va += 1
		vb += vd
		if vb == 24 then vd := 255
		if vb ==  0 then vd :=   1
	again

Our program starts the same way as the earlier one- we set up registers to position the ball. We also use the register vd to keep track of whether the ball is moving up or down. In our “animation loop” we clear the screen, draw the sprite and then update the ball’s position. The horizontal position always increases by 1, but the vertical position increases by a number stored in vd. If vd contains 1, (as it does initially), the ball moves down. if the ball, which is 8 pixels tall, reaches the 24th pixel down it will appear to be touching the bottom of the screen. This is where the first if statement comes in. When the ball appears to be touching the bottom of the screen, vd is set to 255. Adding 255 to a register is the same as subtracting 1, because numbers wrap around. This means that once we change vd to 255 the ball will appear to move up. The second if statement will trigger when the ball appears to be touching the top of the screen and sets vd back to 1 so that the ball moves down again.

Try modifying this program so that the ball bounces back and forth horizontally instead of vertically.

Player Control

The final thing we need to know how to do in order to make simple games is input- when the player presses a key on the keyboard they should be able to make something happen.

There are two ways to do this in Chip8. The first way waits for a player to press a key on the keyboard and stores a number in a register. Then we can examine that register to make something happen. The statement for reading a key looks like this:

v0 := key

The number that will be stored in a register will be a hex digit from 0-F corresponding to the Chip8 keypad. Here’s how Chip8 keys are represented on your keyboard:

Chip8 Key   Keyboard
---------   ---------
 1 2 3 C     1 2 3 4
 4 5 6 D     q w e r
 7 8 9 E     a s d f
 A 0 B F     z x c v

This means that if you pressed the “d” key on your keyboard it would leave the number 9 in the register you chose. Let’s make a program which draws a person and makes them face left or right when A or D is pressed:

: facing-left
	0x18 0x38 0x18 0x3C 0x7E 0x7E 0xBD 0xBD 0xA5 0x24 0x24 0x6C

: facing-right
	0x18 0x1C 0x18 0x3C 0x7E 0x7E 0xBD 0xBD 0xA5 0x24 0x24 0x36

: main
	i  := facing-left
	v0 := 30
	v1 := 12
	loop
		clear
		sprite v0 v1 12
		v2 := key
		if v2 == 7 then i := facing-left
		if v2 == 9 then i := facing-right
	again

The other way to handle player input allows you to check whether a given key is pressed or not. This is useful when you want to make an action game where the world continues to animate even if you stop pressing keys or if you want to allow multiple keys to be pressed at once.

This type of checking can be used directly in an if statement by stating a register and then key or -key:

if v0  key then v1 += 1
if v0 -key then v3 := 5

A comparison like v0 key is true if the key corresponding to the number in the register v0 is currently pressed down. The comparison v0 -key will be true if the key corresponding to the number in the register v0 is not pressed down.

Here is a simple program which allows the player to draw a line on the screen by moving a cursor with ASWD on the keyboard:

: dot
	0b10000000

: main
	i  := dot
	v0 := 10
	v1 := 10

	loop
		sprite v0 v1 1
		v2 := 7 if v2 key then v0 += 255 # left
		v2 := 9 if v2 key then v0 +=   1 # right
		v2 := 5 if v2 key then v1 += 255 # up
		v2 := 8 if v2 key then v1 +=   1 # down
	again

Note that the line is left behind because the clear statement is not used. Since we check each key individually, diagonal lines can be drawn if two directional keys are held at once.

Subroutines

So far, we’ve seen two reasons for using a label: saying where our program begins with a label called main, and giving a name to data. Another way we can use labels is to give a name to a subroutine. A subroutine is a small program that we can “call” from our main program. When the subroutine is finished, the computer will “return” to the place from which the subroutine was called.

That probably sounds a bit abstract, so let’s look at an example:

: image
	0xFF 0x81 0xA5 0x81 0xBD 0x81 0xFF

: draw
	i := image
	sprite v0 v1 7
	v0 += 9
	v1 += 2
;

: main
	draw
	draw
	draw

When you run this program, you will see three images drawn along a diagonal line. Starting from main, using the word draw will call the subroutine beginning at that label. The subroutine sets the i register, draws a sprite, and then adds to v0 and v1. The ; statement (a semicolon, not to be confused with :!) indicates the end of the subroutine, returning us back to “main”. Having completed the first draw, our program will proceed to the second, carrying out all the same steps, and when the subroutine returns we can proceed to the third.

Defining a subroutine has given us a different way of repeating things than loops!

Now, consider this alternate program:

: image1
	0xFF 0x81 0xA5 0x81 0xBD 0x81 0xFF

: image2
	0xFF 0x81 0xE7 0x81 0x81 0x99 0xFF

: draw1
	i := image1
	sprite v0 v1 7
	v0 += 9
	v1 += 2
;

: draw2
	i := image2
	sprite v0 v1 7
	v0 += 9
	v1 += 2
;

: main
	draw1
	draw1
	draw2
	draw1

Now we have two subroutines, each for drawing a different object on the screen. The subroutines draw1 and draw2 are identical except for their first instruction. A different way of writing this program might look something like the following:

: image1
	0xFF 0x81 0xA5 0x81 0xBD 0x81 0xFF

: image2
	0xFF 0x81 0xE7 0x81 0x81 0x99 0xFF

: draw
	sprite v0 v1 7
	v0 += 9
	v1 += 2
;

: main
	i := image1
	draw
	draw
	i := image2
	draw
	i := image1
	draw

There are often many different ways to break down repeated parts of your program into subroutines- experiment, and see what seems clearest!

Remember: every subroutine starts with a label and ends with a semicolon. The statement return is identical in meaning to ;. That last draw subroutine could also be written as:

: draw
	sprite v0 v1 7
	v0 += 9
	v1 += 2
	return

Using the name of a subroutine “calls” it. If you wish, you can also be more explicit by using the :call instruction, followed by the name of the subroutine. Both approaches are equivalent:

:call my-subroutine
my-subroutine

Summing Up

We have learned about bits, bytes, registers, sprites, loops, conditionals, and subroutines. If N is a 4-bit number (0-15), NN is an 8-bit number (0-255), NNN is a 12-bit number (0-4095), and vx and vy are any v-register, the following is a summary of all the Chip8 instructions we have learned so far:

Chip8 has a few more instructions that we haven’t covered yet, but you now know everything essential for making games with Octo. The next steps are up to you: