By Zed A. Shaw
The 5 Simple Rules to the Game of Code
If you play a game like Go or Chess you know the rules are fairly simple, yet the games they enable are extremely complex. Really good games have this unique quality of simple rules with complex interactions. Programming is also a game with a few simple rules that create complex interactions, and in this exercise we're going to learn what those rules are.
Before we do that, I need to stress that you most likely won't use these rules directly when you code. There are languages that do utilize these rules directly, and your CPU uses them too, but in daily programming you'll rarely use them. If that's the case then why learn the rules?
Because these rules are everywhere, and understanding them will help you understand the code you write. It'll help you debug the code when it goes wrong. If you ever want to know how the code works you'll be able to "disassemble" it down to its basic rules and really see how it works. These rules are a cheat code. Pun totally intended.
I'm also going to warn you that you are not expected to totally understand this right away. Think of this exercise as setting you up for the rest of the exercises in this module. You're expected to study this exercise deeply, and when you get stuck, move on to the next exercises as a break. You want to bounce between this one and the next ones until the concepts "click" and it starts to make sense. You should also study these rules as deeply as you can, but don't get stuck here. Struggle for a few days, move on, come back, and keep trying. As long as you keep trying you can't actually "fail".
Rule 1: Everything is a Sequence of Instructions
All programs are a sequence of instructions which tell a computer to do something. You've seen Python doing this already when you type code like this:
x = 10
y = 20
z = x + y
This code starts at line 1, goes to line 2, and so on until the end. That's a sequence of instructions, but inside Python these 3 lines are converted into another sequence of instructions that look like this:
LOAD_CONST 0 (10) # load the number 10
STORE_NAME 0 (x) # store that in x
LOAD_CONST 1 (20) # load the number 20 STORE_NAME 1 (y) # store that in y
LOAD_NAME 0 (x) # loads x (which is 10) LOAD_NAME 1 (y) # loads y (which is 20) BINARY_ADD # adds those STORE_NAME 2 (z) # store the result in z
That looks totally different from the Python version, but I bet you could probably figure out what this sequence of instructions is doing. I've added comments to explain each instruction, and you should be able to connect it back to the Python code above.
I'm not joking. Take some time right now to connect each line of the Python code to the lines of this "byte code". Using the comments I provided I'm positive you can figure it out, and doing so might turn on a light in your head about the Python code.
It's not necessary to memorize this or even understand each of these instructions. What you should realize is your Python code is being translated into a sequence of simpler instructions that tell the computer to do something. This sequence of instructions is called a "byte code" because it's usually stored in a file as a sequence of numbers a computer understands. The output you see above is usually called an "assembly language" because it's a human "readable" (barely) version of those bytes.
These simpler instructions are processed starting at the top, do one small thing at a time, and go to the end when the program exits. That's just like your Python code but with a simpler syntax of INSTRUCTION OPTIONS
. Another way to look at this is each part of x = 10
might become its own instructions in this "byte code."
That's the first rule of The Game of Code: Everything you write eventually becomes a sequence of bytes fed to a computer as instructions for what the computer should do.
How can I get this output?
To get this output yourself, you use a module called dis which stands for "disassemble." This kind of code is traditionally called "byte code" or "assembly language", so dis
means to "dis-assemble." To use dis
you can import it and use the dis()
function like this:
from dis import dis
dis('''
x = 10
y = 20
z = x + y
''')
In this Python code I'm doing the following:
- I import the
dis()
function from thedis
module. - I run the
dis()
function, but I give it a multi-line string using'''
. - I then write the Python code I want to disassemble into this multi-line string.
- Finally, I end the multi-line string and the
dis()
function with''')
.
When you run this in Jupyter you'll see it dump the byte code like I have above, but maybe with some extras we'll cover in a minute.
Where are these bytes stored?
When you run Python (version 3) these bytes are stored in a directory named __pycache__
. If you put this code into a ex19.py
file and then run it with python ex19.py
you should see this directory.
Looking in this directory you should see a bunch of files ending in .pyc
with names similar to the code that generated them. These .pyc
files contain your compiled Python code as bytes.
When you run dis()
you're printing a human readable version of the numbers in the .pyc
file.
Rule 2: Jumps Make the Sequence Non-Linear
A sequence of simple instructions like LOAD_CONST 10
is not very useful. Yay! You can load the number 10! Amazing! Where code starts to become useful is when you add the concept of the "jump" to make this sequence non-linear. Let's look at a new piece of Python code:
while True:
x = 10
To understand this code we have to foreshadow a later exercise where you learn about the while-loop
. The code while True:
simply says "Keep running the code under me x = 10
while True
is True
." Since True
will always be True
this will loop forever. If you run this in Jupyter it will never end.
What happens when you dis()
this code? You see the new instruction JUMP_ABSOLUTE
:
dis("while True: x = 10")
0 LOAD_CONST 1 (10)
2 STORE_NAME 0 (x)
4 JUMP_ABSOLUTE 0 (to 0)
You saw the first two instructions when we covered the x = 10
code, but now at the end we have JUMP_ABSOLUTE 0
. Notice there's numbers 0
, 2
, and 4
to the left of these instructions? In the previous code I cut them out so you wouldn't be distracted, but here they're important because they represent locations in the sequence where each instruction lives. All JUMP_ABSOLUTE 0
does is tell Python to "jump to the instruction at position 0" which is LOAD_CONST 1 (10)
.
With this simple instruction we now have turned boring straight line code into a more complex loop that's not straight anymore. Later we'll see how jumps combine with tests to allow even more complex movements through the sequence of bytes.
Why is this backwards?
You may have noticed that the Python code reads as "while True is True set x equal to 10" but the dis()
output reads more like "set x equal to 10, jump to do it again." That's because of Rule #1 which says we have to produce a sequence of bytes only. There is no nested structures, or any syntax more complex than INSTRUCTION OPTIONS
allowed.
To follow this rule Python has to figure out how to translate its code into a sequence of bytes that produce the desired output. That means, moving the actual repetition part to the end of the sequence so it will be in a sequence. You'll find this "backwards" nature comes up often when looking at byte codes and assembly language.
Can a JUMP go forward?
Yes, technically a JUMP instruction is simply telling the computer to process a different instruction in the sequence. It can be the next one, a previous one, or one in the future. The way this works is the computer keeps track of the "index" of the current instruction, and it simply increments that index.
When you JUMP you're telling the computer to change this index to a new location in the code. In the code for our while loop (below) the JUMP_ABSOLUTE
is at index 4
(see the 4 to the left). After it runs, the index changes to 0
where the LOAD_CONST
is located, so the computer runs that instruction again. This loops forever.
0 LOAD_CONST 1 (10)
2 STORE_NAME 0 (x)
4 JUMP_ABSOLUTE 0 (to 0)
Rule 3: Tests Control Jumps
A JUMP is useful for looping, but what about making decisions? A common thing in programming is to ask questions like:
"If x is greater than 0 then set y to 10."
If we write this out in simple Python code it might look like this:
if x > 0:
y = 10
Once again, this is foreshadowing something you'll learn later, but this is simple enough to figure out:
- Python will test if
x
is greater than>
0. - If it is then Python will run the line
y = 10
. - You see how that line is indented under the
if x > 0:
? That is called a "block" and Python uses indentation to say "this indented code is part of the code above it." - If
x
is NOT greater than0
then Python will JUMP over they = 10
line to skip it.
To do this with our Python byte code we need a new instruction that implements the testing part. We have the JUMP. We have variables. We just need a way to compare two things and then a JUMP based on that comparison.
Let's take that code and dis()
it to see how Python does this:
dis('''
x = 1
if x > 0:
y = 10
''')
0 LOAD_CONST 0 (1) # load 1 2 STORE_NAME 0 (x) # x = 1
4 LOAD_NAME 0 (x) # load x 6 LOAD_CONST 1 (0) # load 0 8 COMPARE_OP 4 (>) # compare x > 0 10 POP_JUMP_IF_FALSE 10 (to 20) # jump if false
12 LOAD_CONST 2 (10) # not false, load 10 14 STORE_NAME 1 (y) # y = 10 16 LOAD_CONST 3 (None) # done, load None 18 RETURN_VALUE # exit
jump here if false
20 LOAD_CONST 3 (None) # load none 22 RETURN_VALUE # exit
The key part of this code is the COMPARE_OP
and POP_JUMP_IF_FALSE
:
4 LOAD_NAME 0 (x) # load x
6 LOAD_CONST 1 (0) # load 0
8 COMPARE_OP 4 (>) # compare x > 0
10 POP_JUMP_IF_FALSE 10 (to 20) # jump if false
Here's what this code does:
- Use
LOAD_NAME
to load thex
variable. - Use
LOAD_CONST
to load the0
constant. - Use
COMPARE_OP
which does the>
comparison and leaves aTrue
orFalse
result for later. - Finally,
POP_JUMP_IF_FALSE
makes theif x > 0
work. It "pops" theTrue
orFalse
value to get it, and if it readsFalse
it willJUMP
to instruction 20. - Doing that will jump over the code that set
y
if the comparison isFalse
, but if the comparison isTrue
then Python just runs the next instruction which starts they = 10
sequence.
Take some time walking through this to try to understand it. If you have a printer, try printing it out and set x
to different values manually, then trace through how the code works. What happens when you set x = -1
.
What do you mean "pop"?
In the above code I'm skipping over exactly how Python "pops" the value to read it, but it's storing it in something called a "stack." For now just think of it as a temporary storage place that you "push" values into, and then "pop" them off. You really don't need to go much deeper than that at this stage in your learning. Just understand the effect is to get the result of the last instruction.
Wait, aren't tests like COMPAREOP
used in loops too?
Yes, and you could probably figure out how that works right now based on what you know. Try to write a while-loop
and see if you can get it to work with what you know now. Don't worry if you can't though as we'll be covering this in later exercises.
Rule 4: Storage Controls Tests
You need some way to keep track of changing data while the code operates, and this is done with "storage." Usually this storage is in the computer's memory and you create names for the data you're storing in memory. You've been doing this when you write code like this:
x = 10
y = 20
z = x + y
In each of the above lines we're making a new piece of data and storing it in memory. We're also giving these pieces of memory the names x
, y
, and z
. We can then use these names to "recall" those values from memory, which is what we do in z = x + y
. We're just recalling the value of x
and y
from memory to add them together.
That's the majority of the story, but the important part of this little rule is that you almost always use memory to control tests.
Sure, you can write code like this:
if 1 < 2:
print("but...why?")
That's pointless though since it's just running the second line after a pointless test. 1
is always less than 2
so it's useless.
Where tests like COMPARE_OP
shine is when you use variables to make the tests dynamic based on calculations. That's why I consider this a "rule of the Game of Code" because code without variables isn't really playing the game.
Take the time to go back through the previous examples and identify the places where LOAD
instructions are used to load values, and STORE
instructions are used to store values into memory.
Rule 5: Input/Output Controls Storage
The final rule of the Game of Code is how your code interacts with the outside world. Having variables is great, but a program that only has data you've typed into the source file isn't very useful. What you need is input and output.
Input is how you get data into your code from things like files, the keyboard, or the network. You've already used open()
and input()
to do that in the last module. You accessed input every time you opened a file, read the contents, and did something with them. You also used input when you've use...input()
to ask the user a question.
Output is how you save or transmit the results of your program. Output can be to the screen with print()
, to a file with file.write()
, or even over a network.
The only problem with input and output at this point is the byte code output is a little more complicated. Let's look at a simple one:
from dis import dis
dis("input('Yes? ')")
0 LOAD_NAME 0 (input) 2 LOAD_CONST 0 ('Yes? ') 4 CALL_FUNCTION 1 6 RETURN_VALUE
This dis()
run doesn't help much because now we're getting into an advanced topic we'll cover later called "functions", so let's stop there and pull these rules together.
Putting it All Together
Taking the 5 Rules we have the following Game of Code:
- You read data as input to your program (Rule #5).
- You store this data in storage (variables) (Rule #4).
- You use these variables to perform tests... (Rule #3).
- ...so you can JUMP around... (Rule #2)
- ...the sequence of instructions... (Rule #1)
- ...transforming the data to new variables (Rule #4)...
- ...which you then write to output for storage or display. (Rule #5).
While this seems simple these little rules create very complicated software. Video games are a great example of very complicated software that does this. A video game reads your controller or keyboard as input, updates variables that control the models in the scene, and uses advanced instructions that render the scene to your screen as output.
Your next step is to study the Python that uses all of these rules.
The exercises after this will then reference the concepts here to explain the concepts of while-loops
, if-statements
, boolean logic, and the more advanced applications of these rules. By learning the rules it's hopefully going to make it easier to understand how each thing works, but you tell me. Did this make sense?