The 5 Simple Rules to the Game of Code
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 the
- 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
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
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 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
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
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
xis greater than
- 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."
xis NOT greater than
0then Python will JUMP over the
y = 10line 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
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:
LOAD_NAMEto load the
LOAD_CONSTto load the
COMPARE_OPwhich does the
>comparison and leaves a
Falseresult for later.
if x > 0work. It "pops" the
Falsevalue to get it, and if it reads
JUMPto instruction 20.
- Doing that will jump over the code that set
yif the comparison is
False, but if the comparison is
Truethen Python just runs the next instruction which starts the
y = 10sequence.
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
COMPARE_OP 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
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
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
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
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
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?
More from Learn Code the Hard Way
How to Read Programmer Documentation
An excerpt from Learn Python the Hard Way, 5th Edition that explains how I analyze learn from projects with poor or no documentation (which is most of them).
The 5 Simple Rules to the Game of Code
An experimental idea to teach the basics of a Turing machine before teaching loops and branching. Feedback welcome.
Announcing _Learn Python the Hard Way_'s Next Edition
Announcing the new version of _Learn Python the Hard Way_ which will be entirely focused on Pre-Beginner Data Science and not web development.
Ten Reasons Youtube's Streaming is Awful
I did a test of Youtube and its streaming has tons of problems. Here's 10 reasons why Youtube's streaming is mostly pointless when compared to Twitch. I'll use Twitch for streaming, then post to youtube.