PICO-8 is an excellent tool to use if you’re looking to learn game development. You can build retro, 8-bit style games and distribute them online for anyone to play.

PICO-8 offers its own sprite editor and music composer to build complete standalone games. To get familiar with how PICO-8 works, it’s best to start simple.

Running the PICO-8 Console

PICO-8 is a virtual console and game development tool. You can use it to build and distribute simple retro-style games.

Start by getting the PICO-8 app and running it:

  1. Purchase and download the software from its itch.io home page. You can download PICO-8 for Windows, macOS, or Linux.
  2. Run the app and you should see a startup screen like this:
    The PICO-8 boot screen showing white text in a pixel font on a black background. A cursor awaits user input.
    This is PICO-8’s command line which you can use to load and run cartridges, along with several other actions, documented on the PICO-8 Wiki.

Basic Use of the Code Editor

You can get an idea of how PICO-8 programming works by using the code editor to create a simple program and then running it.

  1. At the boot screen, press ESC to enter the code editor. You should see a mainly blank screen:
    The PICO-8 code editor showing an empty file.
  2. The code editor is basic but easy to use. For now, start with the most simple program:
            PRINT("HELLO, WORLD")
        
  3. When you’ve entered the above code, press ESC to return to the boot screen.
  4. You can now type RUN to run your program. It should output the text “HELLO, WORLD”.

Writing a Simple Game

Once you’re comfortable writing PICO-8 code and running it, it’s time to try out a simple game. Tic-tac-toe is an excellent choice for a starter game because:

  • It’s turn-based, so you don’t have to worry about timing anything.
  • It involves very little logic, essentially consisting of just two rules.
  • It has very simple graphical requirements.

The full code for this simple game is available in a GitHub repository. You can download this file, save it to your local cartridge folder, and open it with PICO-8.

To open your local cartridge folder in your computer’s file browser, type FOLDER on the PICO-8 command line.

Introducing the Game Loop

Whichever language you’re using, game development typically centers around a “game loop”. It consists of the following process:

  1. Get user input
  2. Update the game state
  3. Draw the game

PICO-8 has excellent built-in support for a game loop via its functions _init(), _update(), and _draw(). These functions are special because PICO-8 calls them automatically, when necessary.

You can see how these work by creating the skeleton of a game. This first revision will display a cursor on the screen that moves as you move your mouse.

Start by setting up any required actions in _init(). This function isn’t strictly part of the game loop since it only runs once, on start. For a game that requires mouse input, you’ll need to make a special call to enable it:

        function _init()
 -- enable mouse
 poke(0x5f2d, 1)
end

The _update function handles user input and updates to your game’s state. To support mouse input, this is the place to keep track of the pointer’s position.

        function _update()
 mousex = stat(32)
 mousey = stat(33)
end

Note that variables in PICO-8 are global by default, so any function in your program will have access to mousex and mousey. PICO-8 includes stat as a built-in function. It gives various details about the current environment, including the mouse, keyboard, and date/time.

Finally, define a _draw function to display a pointer on-screen. For a crosshair-style pointer, you can draw a vertical line and a horizontal line at the current mouse position:

        function _draw()
 cls()
 line(mousex, mousey - 4, mousex, mousey + 4)
 line(mousex - 4, mousey, mousex + 4, mousey)
end

The line() function is another that’s built into PICO-8. It takes four arguments to draw a line between two points: the x and y coordinates of point a, and the x and y coordinates of point b.

The cls() function clears the screen. Try removing this line to see what happens when you don’t clear the screen.

Drawing the Remaining Game Elements

Tic-tac-toe takes place on a grid of nine squares. You can recreate this using four lines: two horizontal and two vertical. Since a PICO-8 screen is 128x128 pixels, each square can be 42 pixels, leaving 1 pixel for the grid lines:

        function draw_grid()
 line(42, 0, 42, 127) -- two vertical lines
 line(85, 0, 85, 127)
 
 line(0, 42, 127, 42) -- two horizontal lines
 line(0, 85, 127, 85)
end

The next task is to draw a symbol—nought or cross—in a given square. You can use this to display both the current “board” and a “preview” symbol as the user hovers their mouse over an empty square.

Drawing a “nought” is simply a case of calling the PICO-8 circ() function:

        function draw_nought(x, y)
 circ(x, y, 10)
end

Drawing a “cross” is slightly more complicated. You’ll need to draw two intersecting diagonal lines:

        function draw_cross(x, y)
 line(x - 10, y - 10, x + 10, y + 10)
 line(x + 10, y - 10, x - 10, y + 10)
end

The remaining drawing function is draw_square. It draws the symbol (cross or nought) in a given grid square, if there is one. To complete that function, though, you’ll need to access the game state.

Keep Track of Game State

To play a meaningful game, you’ll need a way of tracking which symbol, if any, is in which square. While PICO-8 does support multi-dimensional arrays, it’s just as easy to use a one-dimensional array for simple use cases like this. You just need to keep track of nine different locations in the grid:

        p = {"", "", "", "", "", "", "", "", ""}
    

You can access elements of this array using a syntax familiar from other languages. For example:

        -- store a 0 in the top-left square
p[1] = "0"
 
-- test the value in the bottom-right square
if (p[9] == "x") then

PICO-8 arrays begin at index 1, not index 0 as is typical in many languages.

You can translate from a column & row reference to an index in this array, and back again. Using the fact that the screen is 128x128, and the grid is 3x3, you can convert x and y coordinates to grid references like this:

        mousecol=flr(mousex/42.666)
mouserow=flr((mousey/42.666) % 3)

Handling User Input

You can keep track of mouse button presses in a similar way to the mouse position. In _update, use a global variable to keep track of the state and call stat(34) to check if the left mouse button is down:

        if (down and stat(34) == 0) then
 down = false
end
 
if ((not down) and stat(34) == 1) then
 down = true
end

Keeping track of down lets you identify when the button is first clicked, since stat() just tells you whether the button is down when it runs.

Detecting the Win Condition

A game of tic-tac-toe ends when either:

  • Three instances of the same symbol exist in the same row, column, or diagonal.
  • All squares are full.

The check_for_win function works out whether the current game state fulfills either condition. There are several ways you can handle this, but for a simple game like tic-tac-toe, a brute-force approach is often the easiest:

        for a = 1,3 do
   // columns 1, 2, and 3
   if p[a] != "" and p[a] == p[a + 3] and p[a] == p[a + 6] then
     finished = true
     winner = p[a]
   end
end

This code excerpt shows the check for columns. It looks for non-empty matching values in the appropriate positions in the main grid array. If it discovers a winning line, this code sets two global variables that then drive the display of the winning message in _display().

A PICO-8 tic-tac-toe game showing a win for the player.

Packaging Your Game

There’s no point in creating a game unless others can play it. Fortunately, PICO-8 has you covered. You can build an executable by running the following on PICO-8’s command line:

  1. Type RUN to start your program.
  2. While running, press F2 to take a screenshot. PICO-8 will use this as a thumbnail for your game.
  3. Type EXPORT TICTACTOE.HTML. HTML is the quickest format to generate and the most portable.

PICO-8 also supports native binary executables for Windows, macOS, and Linux. Use EXPORT TICTACTOE.BIN to create them. Whichever format you choose, PICO-8 will export your files in the cartridge folder.

PICO-8 Might Be Just the Beginning

This simple game has plenty of potential for follow-up work. You could make improvements to the graphics or add some logic to the CPU’s moves. You could also make a two-player version and allow the player to choose their symbol.

Most PICO-8 game developers make their creations available for free. In the world of professional game development, tools like Unreal Engine, GameMaker, and Unity are more common.