20 Hours to Go
This is a streamlined course that will help you get started with programming. The language of choice here is golang. However, you will probably be able to take what you learn here and move to other languages. The goal is to give you a pretty good foundation of knowledge so that you can be fairly productive in a short amount of time.
Why 20 hours?
There's some chatter around that suggests it takes 10,000 hours to get good at something. The original study that mentioned this determined that several experts in highly competitive fields tend to have put in about 10,000 hours. This is a high level summary of the studies conclusions, but the reality is, you don't need to be an expert. After about 20 hours, you can be fairly productive. Sure, there will probably be plenty more to learn after the 20 hour mark, but cross that bridge when we get there.
Why Go?
Go prioritizes a simple syntax with minimal keywords. The language is strongly typed, yet it still provides a level of flexibility. It provides a nice way to program while reducing ambiguity.
Course Structure
We will be building several projects. Each project will try to focus on some fundamentals covered in the course. There will be moments where you get stuck. The course community is available to help you through it. When you are done with a project please do share your experience and help others.
How To Learn This Content
This learning framework is optional. You will need to explore what works best for you. This framework is given as a suggestion.
High-level explanation
The course will provide a high level explanation (i.e. the basics).
This is a brief overview of the topic being studied that helps provide context and a general understanding of what will be learned.
You don't have to have a thorough understand of this content on first pass. The concepts will become more clear as you progress.
(examples of how to apply the concept will be included)
Expanded Explanation
The expanded section will provide details and more advanced tips for how to operate around the concepts that are taught in the course.
(examples of how to apply the concept will be included)
Additional Reference
A curated list of external references will be provided.
Feynman technique
The Feynman Technique (see Richard Feynman) involves explaining a concept as if teaching someone else, using simple language and analogies to clarify complex ideas. The Feynman technique is useful for solidifying understanding and identifying gaps in knowledge.
Who should you explain to?
- Yourself/Invisible Friend
- Your Pet
- A Friend
- The Course Community
Practice
The course provides project ideas. These projects serve as a way for you to apply your newly gained knowledge.
If you build all of the projects, you will have a decent amount of exposure to the concept.
Measure and Relearn
If you identify a gap in your knowledge, try to be diligent about taking note of that gap.
- Write down questions that you have in the moment.
- Review the material.
- Ask the course community for direction/help.
- Apply the Feynman technique again by explaining this concept to someone.
After the "relearn", go back to your project with your refreshed understanding.
Setup
Golang is a great cross platform language.
You only need to download one binary and you will be ready to write code.
Download and install Golang
Follow instructions on how to install golang on your platform.
Additional Recommended Setup
After installing Go, you can write code in any text editor and compile it at the command line. However, it is recommended to setup the golang dev tools. I typically use vscode and install the gotools associated with that environment.
Configure Visual Studio Code for Go development
Hello World
- create a file called
hello.go
- add the code from below
- run the code with
go run hello.go
// hello.go
package main
func main() {
println("Hello, World!")
}
This may be the most simple golang program. You could also compile the program to an executable binary.
To do that you would run go build hello.go -o hello
(note: on windows you would add the .exe extension)
Keywords
Keyword | Description |
---|---|
break | Leaves a loop or switch statement early. |
default | Specifies code to run in a switch statement when none of the cases match the condition. |
func | Indicates the start of a function declaration. |
interface | Declares a set of methods that a type must implement to satisfy the interface. |
select | Used to choose between different channels for communication. |
case | One of the possible options in a switch statement. |
defer | Delays the execution of a function until the surrounding function returns. |
go | Launches a new goroutine, which is a lightweight thread of execution. |
map | A built-in type that associates values with keys. |
struct | Defines a collection of fields that belong together. |
chan | A channel type used for inter-goroutine communication. |
else | Executes code if the if statement condition is false. |
goto | Jumps to a labeled statement in the same function. |
package | Declares the name of the package that the file belongs to. |
switch | Evaluates an expression and chooses a case to execute based on its value. |
const | Defines a constant value that cannot be changed. |
fallthrough | Forces execution into the next case in a switch statement (use sparingly). |
if | Begins an if statement for conditional execution. |
range | Iterates over elements in an array, slice, string, map, or channel. |
type | Declares a new type, possibly based on an existing type. |
continue | Skips the current iteration of a loop and moves to the next one. |
for | Begins a loop or range statement for repetitive execution. |
import | Imports packages from other modules. |
return | Exits the function and returns a value to the caller. |
var | Declares a variable with an optional initial value. |
don't study this list too much right now. It might be a good reference for later.
Packages
Don't focus on this too much at the moment. This is intended as a high level explanation. It will become more clear as we build more things.
What is a package?
A package
in Go allows you to have separate namespaces.
Packages are separated by directories. If a file is in the same directory as another file, you will have access to things within that file without having to call it's namespace.
Importing a package
Importing packages in Go can be done by using the import
keyword followed by the name of the package you want to import. For example, if you wanted to import the "fmt" package, you would write import "fmt"
at the beginning of your code.
Once you have imported a package, you can use the functions and variables defined in that package within your code. To call a function from an imported package, you will need to prefix the function with the name of the package. For example, if you wanted to call the Println
function from the "fmt" package, you would write fmt.Println("Hello, world!")
.
The main
Package
Go has a convention that the entry point of your application should be called main.
e.g.:
package main
...
The main
package has special significance. Mainly, that the main
package should contain a main
function. i.e. a function with the name of main
.
package main
func main() {
}
If I run this package, the main
function will execute.
There's another special function called init
.
If you declare a function named init
, it will execute when the package loads (before the main
function). The init
function is entirely optional.
package main
func init() {
println("runs when the package loads and before main runs")
}
func main() {
println("runs when the package loads, but after init")
}
Note that most packages don't have a
main
function. The main package should be kept small. Typically, you will be importing a different package and calling that code from within yourmain
function.
Private Functions
Go has a strange way of making functions private. If the function's name is lower case, it is a private function.
Private functions can't be called by external packages.
Another way to say this: To export a function, you must give the function a name with the first letter capitalized.
This rule applies to anything that a package can define such as functions, variables, constants, structs, etc...
package demo
func privateFunction() {
}
func PublicFunction() {
}
Modules
Modules are used to manage dependencies in a project. A module is essentially a collection of related Go packages that are versioned together. It allows you to import external code into your project and use it without worrying about conflicts with other dependencies or versions. You can create your own module by initializing a go.mod
file and specifying the module name and its dependencies. Once your module has been published, other projects can import it and use its packages as needed.
Creating a Go module
go mod init <module name>
The module name should follow the pattern of github.com/<username>/<project-name>
(assuming it's hosted on github).
Project Structure
Go doesn't enforce a project structure, but there is a suggested project structure for golang.
Take some time to review the project-layout repo: https://github.com/golang-standards/project-layout
Variables
In programming, a variable is a way to store information that can be used later. Think of it like a container where you can put stuff in and take stuff out.
When we write programs in Go, we use variables to store different types of data such as numbers, text, or true/false values.
Here's an example of how we can use variables to store and use numbers in a program:
Declare a variable
package main
import "fmt"
func main() {
// Declare variables to hold numbers
var x int = 5
var y int = 10
// Add the variables together and print the result
var sum int = x + y
fmt.Println(sum) // Outputs: 15
}
In this code, we declare two variables x
and y
to hold the values 5
and 10
respectively. We then add these variables together and store the result in a new variable called sum
. Finally, we print out the value of the sum
variable which is 15
.
Variables in Go are declared using the var
keyword followed by the name of the variable and the type of data it will hold. In the example above, we declared x
, y
, and sum
to be of type int
which means they can hold whole numbers.
You can also declare variables without giving them a specific value, like this:
var z int
This creates a variable named z
that has no value yet. You can assign a value to it later using the =
operator:
z = 20
Or you can declare and assign a value to a variable at the same time:
var z int = 20
In Go, you can also use shorthand notation to declare and assign a value to a variable:
z := 20
This is equivalent to writing var z int = 20
.
Overall, variables are an essential concept in programming and are used in many different ways. They allow us to store and manipulate data, which is what programs are all about!
Zero Values
If you don't assign a value to a variable (or a property on a struct), it will be the zero value for that type.
e.g.
var num int
println(num) // outputs: 0
Key Terms
Term | Explanation |
---|---|
Declaration | Creating a new variable with a specific name and data type. |
Assignment | Giving a value to a declared variable. |
Scope | The area of a program where a variable can be accessed. |
Data Type | A classification of the type of data that a variable can hold, such as int, string, etc. |
Constant | A variable whose value cannot be changed after it is assigned. |
Identifier | The name given to a declared variable. |
Pointer | An address that points to the memory location of a value. |
Value | The data held by a variable. |
Type Inference | The feature in Go that allows the compiler to determine the data type of a variable based on its initial value. |
Types
Go is a strictly typed language. However, there is some level of flexibility with interfaces.
Don't get too weighed down on learning each particular type. It will become more obvious as we build things.
There are built in types (which have some correlation to types you might have seen in other languages). For more detail on built-in types, you can skim this go101 page: https://go101.org/article/type-system-overview.html
You can also create your own types with structs.
Structs
A Struct is like a blueprint or template for creating our own types. We can think of structs as collections of related information. For example, if we are making a game and we want to keep track of the player's score, name, and level, we could create a struct called Player
that has fields for each of these pieces of information.
package game
type Player struct {
Score uint
Name string
Level uint
}
func run() {
p := Player{
Score: 0,
Name: getPlayerName(),
}
increaseScore(p, 10)
println(p.Score) // output: 10
}
func increaseScore(player Player, amount uint) {
player.Score += amount
}
note that we did not assign a value to the
Level
property See zero values for an explanation
If you try to run this code, it probably won't work. Hopefully, your gotools will show a squiggly line and provide some error message like "this value will not be modified" or "variable player is not used".
Let's look at this code step by step and explain what's going on.
package game // declare the package
type Player struct {
Score uint
Name string
Level uint
} // define the type of Player
func run() {
p := Player{
Score: 0,
Name: getPlayerName(),
} // declare the variable `p` as the type of `Player` and assign values to the properties.
increaseScore(p, 10)// call the increase score function and pass in a copy of `p` and the number `10`
println(p.Score) // output: 0
}
func increaseScore(player Player, amount uint) { // declare increaseScore
player.Score += amount // increase the value of `player.Score` by `amount`
}
Since we are passing a copy of the variable, when increaseScore
returns, the copy (i.e.player
) will be discarded and the struct that we passed will not be modified.
To accommodate, we can pass a reference to p
into the increaseScore
function.
We can use the &
operator to denote that we will be store a reference to this value. What we are actually storing is a memory address of that variable.
a := 42
b := &a
This is called a pointer. You could think of this as "this variable points to an address in memory", but I tend to prefer saying, "a
stores a value, b
stores a reference to a value".
We will need to change the signature of the function to include the *
operator. This denotes that the argument will be a pointer
.
package game // declare the package
type Player struct {
Score uint
Name string
Level uint
} // define the type of Player
func run() {
p := &Player{
Score: 0,
Name: getPlayerName(),
} // declare the variable `p` as a reference to a `Player` and assign values to the properties.
increaseScore(p, 10)// call the increase score function and pass in `p` and the number `10`
println(p.Score) // output: 10
}
func increaseScore(player *Player, amount uint) { // declare increaseScore
player.Score += amount // increase the value of `player.Score` by `amount`
}
Another way to write this code:
package game // declare the package
type Player struct {
Score uint
Name string
Level uint
} // define the type of Player
func run() {
p := Player{
Score: 0,
Name: getPlayerName(),
} // declare the variable `p` as the type of `Player` and assign values to the properties.
increaseScore(&p, 10)// call the increase score function and pass in a reference to `p` and the number `10`
println(p.Score) // output: 10
}
func increaseScore(player *Player, amount uint) { // declare increaseScore
player.Score += amount // increase the value of `player.Score` by `amount`
}
Now the score should update correctly in the run
function.
Methods
We can also define methods on a struct.
To do this, we use need to use a value receiver
or a pointer receiver
.
The general rule of thumb is that if you need to change a property on the struct, you will need to use a pointer receiver
. Which also means that when you declare a variable with that function
package game
type Player struct {
Score uint
Name string
Level uint
}
func (p Player) increaseScore(amount uint) {
p.Score += amount
}
func Run() {
p := Player{
Name: getPlayerName(),
}
p.increaseScore(10)
println(p.Score) // outputs: 0
}
Use a Pointer receiver to modify a property on a struct
package game
type Player struct {
Score uint
Name string
Level uint
}
func (p *Player) increaseScore(amount uint) {
p.Score += amount
}
func Run() {
p := &Player{
Name: getPlayerName(),
}
p.increaseScore(10)
println(p.Score) // outputs: 10
}
However, we run into the same problem here.
Interfaces
An Interface defines a set of methods that a type must implement in order to be considered that interface. Think of it like a contract - if a type implements all of the methods in an interface, it can be used wherever that interface is expected. This allows us to write more flexible and reusable code.
If an object implements the methods in an interface, it can be used as that interface.
Kind of like, if it looks like a duck and quacks like a duck, it's probably a duck.
Tools
Tortuga
Tortuga is a library that is intended for making it easy to make games. However, it's not very performant. The main method for rendering to the screen is to draw one pixel at a time. This should be fine for the tiny games that we plan to build in this course.
You will need to implement the Cart
interface. Cart
is intended to represent a game cartridge (like from back in the day).
type Cart interface {
Update()
Render()
}
Example
package main
import (
"github.com/dfirebaugh/tortuga"
"github.com/dfirebaugh/tortuga/pkg/math/geom"
)
type cart struct{}
var (
game tortuga.Console
rect geom.Rect
speed = 4.0
)
func (c cart) Update() {
if game.IsDownPressed() {
rect[1] += speed
}
if game.IsUpPressed() {
rect[1] -= speed
}
if game.IsLeftPressed() {
rect[0] -= speed
}
if game.IsRightPressed() {
rect[0] += speed
}
}
func (c cart) Render() {
game.Clear()
// render a rectangle on the given display as a certain color
// draw calls need to happen in the render loop
rect.Draw(game.GetDisplay(), game.Color(2))
}
func main() {
// create a rectangle when the app starts (so we don't create on every render loop)
rect = geom.MakeRect(20, 20, 20, 20)
// instantiate the game console
game = tortuga.New()
// run the cart (note: this is a blocking operation)
game.Run(cart{})
}
Useful Packages
name | path | description |
---|---|---|
geom | github.com/dfirebaugh/tortuga/pkg/math/geom | draw shapes and simple collision detection |
component | github.com/dfirebaugh/tortuga/pkg/component | useful structs for game components |
sprite | github.com/dfirebaugh/tortuga/pkg/sprite | encode and decode sprites from text |
Projects
Before working on a project, look back at the Learning Technique section.
Reach out to the course community if you need assistance or help understanding key concepts or if you have specific questions on how to do something.
You can also post your project in the course community for code review and feedback.
When you feel that you are completed with the project, feel free to post it in the showcase channel.
Software Is A Team Sport
Learning is your own responsibility. However, most developers are willing to support another developer's learning endeavors.
Feel free to group up with others while you work on projects.
Reviewing Code
You will likely be able to find projects that others have completed. However, try not to copy pasta the projects together.
Game of Life
Conway's Game of Life is a cellular automaton and zero-player game that involves creating patterns on a grid of cells. The simulation starts with a set of initial patterns, known as seeds, and evolves over time based on a set of rules.
Project Tasks:
- Create a two-dimensional grid to represent the cells in the Game of Life.
- Implement the initial state of the simulation by randomly setting some cells to alive and others to dead.
- Define the rules of the simulation, which determine how the state of each cell evolves over time.
- Update the state of the grid based on the defined rules and display the new state of the grid.
- Repeat step 4 for a set number of iterations or until the simulation reaches a stable state.
Clock
Create a graphical clock that displays the time in a visually appealing and fun way. The clock should have a unique and interesting design that captures the attention of users.
optional variation: make a pomodoro clock
Project Tasks:
- Create a GUI window using a graphics library like ggge.
- Implement a clock function that retrieves the current time from the system clock.
- Design an interesting and unconventional clock face that displays the time in a unique way.
- Animate the clock face so that it continuously updates as time passes.
- Add any additional features or animations that enhance the visual appeal of the clock, such as color changes, particle effects, or sound effects.
Optional tasks:
- Add user customization options, such as different clock face designs or animation settings.
- Implement alarm functionality that triggers at specified times.
- Add network functionality to synchronize the clock with other devices or servers.
Pong
Pong is a two player sports game that simulates table tennis. The game consists of two paddles and a ball. The objective of the game is to score points by hitting the ball past the opponent's paddle.
Project Tasks:
- Create a window for the game using a graphical user interface (GUI) library.
- Add two paddles to the window, one for each player.
- Add a ball to the window that can move around and bounce off the walls and paddles.
- Implement the logic for moving the paddles vertically using keyboard inputs from the players.
- Implement the collision detection between the ball and the paddles and update the ball's trajectory accordingly.
- Keep track of the scores for each player and display them on the window.
- End the game when one player reaches a certain number of points or after a set amount of time.
Bonus tasks:
- Add sound effects to the game.
- Implement different difficulty levels with varying speeds and sizes of the ball.
- Add power-ups that can affect the movement of the ball or the size of the paddles.
InkBall
InkBall is a puzzle game that involves moving balls to matching colored holes in order to score points.
Project Tasks:
- Create a two-dimensional grid to represent the playing surface for the InkBall game.
- Implement the initial state of the game by randomly placing balls and colored holes onto the grid.
- Define the rules of the game, including how balls move, how they interact with walls and each other, and how they are scored when they reach a matching colored hole.
- Implement user input by allowing the player to select the direction and speed at which the ball should be launched.
- Update the state of the game based on the defined rules and display the new state of the grid.
- If the player scores the required number of points or if all balls have been placed into holes, end the game.
Rogue
In a Rogue-like dungeon crawler the player will navigate through randomly generated levels, fight monsters, loot treasures and try to survive as long as possible.
Project Tasks:
- Generate random levels using procedural generation techniques.
- Define rules for monster behavior and combat system.
- Implement inventory system to allow players to pick up and use items.
- Create user interface with ASCII graphics for displaying the game world.
- Add permadeath feature, so when the player dies, they must start over from the beginning.