# Chip's Challenge on Fruit Jam and Metro RP2350

## Overview

![](https://cdn-learn.adafruit.com/assets/assets/000/136/233/medium800/gaming_Main_image.jpg?1744151039)

While there are [several guides](https://learn.adafruit.com/category/gaming-1) on making some basic games in CircuitPython, they are meant to get you started with writing your own games. In this guide, I'll go over making a more complex tile-based game that had originally been available for the Lynx and Microsoft Windows 3.1 as part of the Microsoft Entertainment Pack 4. The version that this code is based on is the Microsoft version.

Instead of building upon displayio for the most part, this game takes a more traditional approach and uses CircuitPython's `bitmaptools` module to draw the graphics directly to the screen buffer. This includes some more unique features, such as keyboard input from the keyboard buffer. This allows input from either the serial terminal or using an attached keyboard for a standalone setup.

The code is mostly based on&nbsp;[Pocket Chips Challenge](https://github.com/makermelissa/PocketChipsChallenge), which I had originally written for the Pocket PC using C++ in the early 2000s, and [Tile World](https://www.muppetlabs.com/~breadbox/software/tworld/), which was written in C. Pocket Chips Challenge was never completed, and rewriting it in CircuitPython allowed me to finish the game. I could reuse most of the graphics I had made at the time, which were written for a 240x320 display. This includes a custom set of 24x24 pixel tiles, which I had redrawn based on the original 32x32 pixel tiles. The only graphics from the original game were the digits displayed on the right side.

| Original Game | Remade Game |
|---|---|
| [![Original Game](https://cdn-learn.adafruit.com/assets/assets/000/136/220/large1024/gaming_original_game.png?1744148582)](https://learn.adafruit.com/assets/136220) | [![Remade Game](https://cdn-learn.adafruit.com/assets/assets/000/136/232/large1024/gaming_remade_game_small.png?1744149286)](https://learn.adafruit.com/assets/136232) |

Because of the limitations of writing it for a microcontroller, I had to find some creative ways to get the game to operate as quickly as possible. Originally I had written the game to use Double Buffering, and although the graphics were smoother, it just took too long to write to 2 buffers per frame and the game felt laggy. I ended up writing the game to keep track of tiles that changed and only performed partial screen updates.

The most challenging aspect of writing the game in CircuitPython was the lack of prebuilt dialogs and I ended up making these myself. I leaned fairly heavily on the [adafruit\_display\_text](https://github.com/adafruit/Adafruit_CircuitPython_Display_Text) library for displaying the text. It has come a long way since it was originally written, including bitmap labels which allow putting the text right onto a bitmap and text boxes which improve upon that by allowing horizontal alignment of the bitmap labels.

There are many more techniques that I used to make this, including working with paletted bitmaps, which had challenges of their own. This and many more techniques will be covered in more detail. This ended up being one of the largest pieces of code written specifically for CircuitPython that I'm aware of, so the code will not be listed in its entirety, but there are many great techniques to cover.

## Parts

Because the size of this game is so large, you will need the version of the Metro RP2350 with 8MB of PSRAM or a Fruit Jam. If you already have [the version of the Metro RP2350 without the PSRAM](https://www.adafruit.com/product/6003) and are comfortable with surface mount soldering, it is possible to solder on [a PSRAM chip](https://www.adafruit.com/product/4677) yourself to upgrade it.

If using a Fruit Jam, you will need:

### Adafruit Fruit Jam - Mini RP2350 Computer

[Adafruit Fruit Jam - Mini RP2350 Computer](https://www.adafruit.com/product/6200)
We were catching up on a recent [hackaday hackchat with eben upton](https://hackaday.io/event/202122-raspberry-pi-hack-chat-with-eben-upton)&nbsp;and learned some fun facts: such as the DVI hack for the RP2040 was inspired by <a...></a...>

Out of Stock
[Buy Now](https://www.adafruit.com/product/6200)
[Related Guides to the Product](https://learn.adafruit.com/products/6200/guides)
![Angled shot of assembled mini computer PCB with plate.](https://cdn-shop.adafruit.com/640x480/6200-10.jpg)

or if using a Metro RP2350:

### Adafruit Metro RP2350 with PSRAM

[Adafruit Metro RP2350 with PSRAM](https://www.adafruit.com/product/6267)
Choo! Choo! This is the RP2350 Metro Line, making all station stops at "Dual Cortex M33 mountain", "528K RAM round-about" and "16 Megabytes of Flash town" and a bonus stop at "8 Megabytes of PSRAM village". This train is piled high with hardware that...

In Stock
[Buy Now](https://www.adafruit.com/product/6267)
[Related Guides to the Product](https://learn.adafruit.com/products/6267/guides)
![Angled shot of black, credit card-sized microcontroller.](https://cdn-shop.adafruit.com/640x480/6267-00.jpg)

### Adafruit RP2350 22-pin FPC HSTX to DVI Adapter for HDMI Displays

[Adafruit RP2350 22-pin FPC HSTX to DVI Adapter for HDMI Displays](https://www.adafruit.com/product/6055)
You may have noticed that our [RP2350 Feather](https://www.adafruit.com/product/6000) has an FPC output connector on the end&nbsp;for accessing the HSTX (High Speed Transmission)&nbsp;peripheral. This new capability, not available on the RP2040, is specifically designed to allow the...

In Stock
[Buy Now](https://www.adafruit.com/product/6055)
[Related Guides to the Product](https://learn.adafruit.com/products/6055/guides)
![black, square-shaped breakout board with DVI and 22-pin FPC connectors connected to a black, rectangular microcontroller.](https://cdn-shop.adafruit.com/640x480/6055-01.jpg)

### 22-pin 0.5mm pitch FPC Flex Cable for DSI CSI or HSTX - 10cm

[22-pin 0.5mm pitch FPC Flex Cable for DSI CSI or HSTX - 10cm](https://www.adafruit.com/product/6035)
Connect this to that when a 22-pin FPC connector is needed. This 10 cm long cable is made of a flexible PCB. It's A-B style, meaning that pin one on one side will match with pin one on the other. How handy!

[We're stocking this to...](https://www.adafruit.com/category/360)

In Stock
[Buy Now](https://www.adafruit.com/product/6035)
[Related Guides to the Product](https://learn.adafruit.com/products/6035/guides)
![Angled shot of 10cm long 22-pin FPC cable.](https://cdn-shop.adafruit.com/640x480/6035-00.jpg)

For either:

### HDMI Cable - 1 meter

[HDMI Cable - 1 meter](https://www.adafruit.com/product/608)
Connect two HDMI devices together with this basic HDMI cable. It has nice molded grips for easy installation, and is 1 meter long (about 3 feet). This is a HDMI 1.3 cable.

We're now stocking a very fancy Official Raspberry Pi cable with overmolding and a Pi logo. Please note...

In Stock
[Buy Now](https://www.adafruit.com/product/608)
[Related Guides to the Product](https://learn.adafruit.com/products/608/guides)
![Official Raspberry Pi HDMI Cable - 1 meter](https://cdn-shop.adafruit.com/640x480/608-03.jpg)

### Optional Parts

You will need a display with an HDMI input capable of displaying resolutions as low as 640x480.

### 7" Display 1280x800 (720p) IPS + Speakers - HDMI/VGA/NTSC/PAL

[7" Display 1280x800 (720p) IPS + Speakers - HDMI/VGA/NTSC/PAL](https://www.adafruit.com/product/1667)
Yes, this is an adorable small HDMI television with incredibly high resolution **and built in 3W stereo speakers**! We tried to get the smallest possible HDMI/VGA display with high-res, high-contrast visibility. The visible display measures only 7" (17.8cm) diagonal, and the TFT comes...

In Stock
[Buy Now](https://www.adafruit.com/product/1667)
[Related Guides to the Product](https://learn.adafruit.com/products/1667/guides)
![Front view of assembled and powered on HDMI 4 Pi - 7" Display. The monitor displays a desktop background with a raspberry logo.](https://cdn-shop.adafruit.com/640x480/1667-00.jpg)

or

### HDMI 5" Display Backpack - Without Touch

[HDMI 5" Display Backpack - Without Touch](https://www.adafruit.com/product/2232)
It's a mini panel-mountable HDMI monitor! So small and simple, you can use this display with any computer that has HDMI output, and the shape makes it easy to attach to a case or rail. This backpack features the TFP401 for decoding video and includes the attached display, so it's...

Out of Stock
[Buy Now](https://www.adafruit.com/product/2232)
[Related Guides to the Product](https://learn.adafruit.com/products/2232/guides)
![Top down view of a HDMI 5" Display Backpack - Without Touch connected to a Raspberry Pi powered by a USB. The HDMI screen displays a desktop image including the Raspberry Pi logo. ](https://cdn-shop.adafruit.com/640x480/2232-07.jpg)

If you would like to save the state of your game, you will need a microSD card.

### 512MB micro SD Memory Card

[512MB micro SD Memory Card](https://www.adafruit.com/product/5252)
Add storage in a jiffy using this **512MB** microSD card. Preformatted to FAT32, so it works out of the packaging with our projects. Works great with any device in the Adafruit shop that uses microSD cards. Ideal for use with Feathers, data loggers, or small Linux SBCs (not good...

Out of Stock
[Buy Now](https://www.adafruit.com/product/5252)
[Related Guides to the Product](https://learn.adafruit.com/products/5252/guides)
![Angel shot of Small microSD card 512mb](https://cdn-shop.adafruit.com/640x480/5252-00.jpg)

### USB Type A to Type C Cable - approx 1 meter / 3 ft long

[USB Type A to Type C Cable - approx 1 meter / 3 ft long](https://www.adafruit.com/product/4474)
As technology changes and adapts, so does Adafruit. This&nbsp;&nbsp; **USB Type A to Type C** cable will help you with the transition to USB C, even if you're still totin' around a USB Type A hub, computer or laptop.

USB C is the latest industry-standard connector for...

In Stock
[Buy Now](https://www.adafruit.com/product/4474)
[Related Guides to the Product](https://learn.adafruit.com/products/4474/guides)
![Angled shot of a coiled black, USB-C to USB-A cable.](https://cdn-shop.adafruit.com/640x480/4474-02.jpg)

### Audio Output Parts

If you are using the Metro RP2350, to add Audio Output, you will need a Digital-to-Analog-Converter and some extra parts to connect it

### Adafruit TLV320DAC3100 - I2S DAC with Headphone and Speaker Out

[Adafruit TLV320DAC3100 - I2S DAC with Headphone and Speaker Out](https://www.adafruit.com/product/6309)
We&nbsp;stock a lot of chips and development boards&nbsp;that are able to do high quality digital I2S out, which makes for great quality audio playback. That's great when you have enough processing power to decode WAVs or MP3s in real time. However, most give you stereo...

In Stock
[Buy Now](https://www.adafruit.com/product/6309)
[Related Guides to the Product](https://learn.adafruit.com/products/6309/guides)
![Angled shot of black, square-shaped DAC breakout board.](https://cdn-shop.adafruit.com/640x480/6309-00.jpg)

### Tiny Premium Breadboard

[Tiny Premium Breadboard](https://www.adafruit.com/product/65)
This is a tiny little breadboard... half the size of a half-size breadboard!&nbsp;

**As of Sep 8, 2022** - This Tiny breadboard has been updated to make plugging and un-plugging boards and headers a buttery-smooth&nbsp;operation. Updated design also includes a metal...

In Stock
[Buy Now](https://www.adafruit.com/product/65)
[Related Guides to the Product](https://learn.adafruit.com/products/65/guides)
![Angled shot of tiny breadboard.](https://cdn-shop.adafruit.com/640x480/65-03.jpg)

### Stereo 3.5mm Plug/Plug Audio Cable - 6 feet

[Stereo 3.5mm Plug/Plug Audio Cable - 6 feet](https://www.adafruit.com/product/876)
This basic cable comes with two 3.5mm (1/8" headphone jack size) stereo connectors. It's fairly straight forward, you'll commonly need these to connect two audio devices together.  
  
 Cable is 6 ft long.

In Stock
[Buy Now](https://www.adafruit.com/product/876)
[Related Guides to the Product](https://learn.adafruit.com/products/876/guides)
![Stereo 3.5mm Plug/Plug Audio Cable](https://cdn-shop.adafruit.com/640x480/876-01.jpg)

### Premium Male/Male Jumper Wires - 20 x 3" (75mm)

[Premium Male/Male Jumper Wires - 20 x 3" (75mm)](https://www.adafruit.com/product/1956)
Handy for making wire harnesses or jumpering between headers on PCB's. These premium jumper wires are 3" (75mm) long and come in a 'strip' of 20 (2&nbsp;pieces of each of ten rainbow colors). They have 0.1" male header contacts on either end and fit cleanly next to each...

In Stock
[Buy Now](https://www.adafruit.com/product/1956)
[Related Guides to the Product](https://learn.adafruit.com/products/1956/guides)
![Angled shot of Premium Male/Male Jumper Wires - 20 x 12 (300mm)](https://cdn-shop.adafruit.com/640x480/1956-02.jpg)

### Additional Parts for Standalone Project

To make this project a standalone system, you will need the following additional parts.

For the Metro RP2350, you'll need the following to add a USB port:

### Solderless Press-Fit Male Pin Header - 2.54mm / 0.1" Pitch

[Solderless Press-Fit Male Pin Header - 2.54mm / 0.1" Pitch](https://www.adafruit.com/product/5938)
If your soldering isn't quite up to scratch, or you yet don't own a soldering iron, then these nifty **0.1"/2.54mm&nbsp;standard pitch&nbsp;press-fit headers** might be just what you need.&nbsp;

**Absolutely no soldering required!** All that's...

In Stock
[Buy Now](https://www.adafruit.com/product/5938)
[Related Guides to the Product](https://learn.adafruit.com/products/5938/guides)
![Angled shot of press-fit header.](https://cdn-shop.adafruit.com/640x480/5938-00.jpg)

### USB Type A Jack Breakout Cable with Premium Female Jumpers

[USB Type A Jack Breakout Cable with Premium Female Jumpers](https://www.adafruit.com/product/4449)
If you'd like to connect a USB-host-capable chip to your USB peripheral, this cable will make the task very simple.&nbsp; **There is no converter chip in this cable!** &nbsp;It's basically a plain USB cable that's cut in half and with jumper sockets on the power and data...

In Stock
[Buy Now](https://www.adafruit.com/product/4449)
[Related Guides to the Product](https://learn.adafruit.com/products/4449/guides)
![USB Type A Socket Breakout Cable with Premium Female Jumpers](https://cdn-shop.adafruit.com/640x480/4449-02.jpg)

For both, you'll need a keyboard and power supply:

### Mini Chiclet Keyboard - USB Wired - Black

[Mini Chiclet Keyboard - USB Wired - Black](https://www.adafruit.com/product/1736)
Add a good quality, slim keyboard to your&nbsp;Raspberry Pi, Beagle Bone Black, or other single-board-computer with this sleek black chiclet keyboard. It's a full QWERTY keyboard with a USB cable and is compatible with all operating systems. We tried many keyboards to find one that felt...

In Stock
[Buy Now](https://www.adafruit.com/product/1736)
[Related Guides to the Product](https://learn.adafruit.com/products/1736/guides)
![Angled shot of a Black woman's silver-blue manicured hands on a slim, black keyboard.](https://cdn-shop.adafruit.com/640x480/1736-04.jpg)

or

### Miniature Keyboard- Microcontroller-Friendly PS/2 and USB

[Miniature Keyboard- Microcontroller-Friendly PS/2 and USB](https://www.adafruit.com/product/857)
Add a typing interface to your project with this microcontroller-friendly miniature keyboard. We found the smallest PS/2+USB keyboard available, a mere 8.75" x 4.65" x 0.6" (220mm x 118mm x 16mm)! It's small but usable to make a great accompaniment to either a...

In Stock
[Buy Now](https://www.adafruit.com/product/857)
[Related Guides to the Product](https://learn.adafruit.com/products/857/guides)
![Miniature Keyboard- Microcontroller in use](https://cdn-shop.adafruit.com/640x480/857-03.jpg)

### Official Raspberry Pi Power Supply 5.1V 3A with USB C

[Official Raspberry Pi Power Supply 5.1V 3A with USB C](https://www.adafruit.com/product/4298)
The official Raspberry Pi USB-C power supply is here! And of course, we have 'em in classic Adafruit black! Superfast with just the right amount of cable length to get your Pi 4 projects up and running!

Best for use with Pi 4 series, [Pi...](https://www.adafruit.com/product/5814)

In Stock
[Buy Now](https://www.adafruit.com/product/4298)
[Related Guides to the Product](https://learn.adafruit.com/products/4298/guides)
![Angled shot of Official Raspberry Pi Power Supply 5.1V 3A with USB C with Power plug facing down. ](https://cdn-shop.adafruit.com/640x480/4298-04.jpg)

# Chip's Challenge on Fruit Jam and Metro RP2350

## Game Structure

The game structure mainly consists of **code.py** , **game.py** , **gamelogic.py** , and **level.py**. Here is the import structure of just the game files to help you navigate the code.

![](https://cdn-learn.adafruit.com/assets/assets/000/136/235/medium800/gaming_Import_Map.jpg?1744151525)

 **Code.py** is at the root of the tree, and everything else is based on what is being imported. You may have noticed there are some duplicates, such as **definitions.py** and **point.py,** because they are reused several times.

# Chip's Challenge on Fruit Jam and Metro RP2350

## 256-Color Graphics

With this game, I decided to go with 256-color graphics for a couple of reasons. Mostly that the graphics I was using had very few colors, and that it would conserve memory. The first thing I needed to do was make sure that all of the graphics used the same palette. I used Photoshop for all images and used the following steps:

I set the image mode to use indexed color and then changed the Palette Setting to "System (Windows)".

![](https://cdn-learn.adafruit.com/assets/assets/000/136/083/medium640/gaming_Screenshot_2025-03-28_at_12.39.00%E2%80%AFPM.png?1743190845)

![](https://cdn-learn.adafruit.com/assets/assets/000/136/084/medium640/gaming_Screenshot_2025-03-28_at_12.34.54%E2%80%AFPM.png?1743190927)

It's important to change this from the default setting of Exact, which means that it only uses the exact colors in that particular image. Since CircuitPython only allows a single palette to be active at a time, if another image has different colors, the palettes will be different and likely won't display correctly together. For more details on how palettes work in CircuitPython, check out the [Bitmap and Palette](https://learn.adafruit.com/circuitpython-display-support-using-displayio/bitmap-and-palette) section of the [CircuitPython Display Support Using displayio](https://learn.adafruit.com/circuitpython-display-support-using-displayio) guide which does an excellent job explaining the graphics.

For the purposes of the game programming, you have to keep in mind that a palette is a list of the colors that are used and the bitmap is a grid of indexes that refer to this palette. The palette is sometimes referred to as the shader, which is an umbrella term that encompasses palettes as well as Color Converters used with higher resolution graphics. One of the challenges is to specify a color to use, rather than assigning the color directly, the color index needs to be used. To do that, I wrote a simple function that would scan through the palette and return the color index if found:

```python
def get_color_index(self, color, shader=None):
    if shader is None:
        shader = self._shader
    for index, palette_color in enumerate(shader):
        if palette_color == color:
            return index
    return None
```

Another challenge was working with the labels. They return a 2-color palette with 0 being the background color and 1 being the foreground color. To get the text to show the correct color within the palette, the foreground and background indices need to be reassigned. A new 256-color bitmap is created because displayio does not provide a mechanism to change the number of available colors in a bitmap, which is likely due to memory reallocation. Then the bitmap is scanned pixel by pixel and the new bitmap has the index set to match the correct color index.

```python
def reassign_indices(self, bitmap, foreground_color_index, background_color_index):
    # This will reassign the indices in the bitmap to match the palette
    new_bitmap = displayio.Bitmap(bitmap.width, bitmap.height, len(self.shader))
    if background_color_index is not None:
        for x in range(bitmap.width):
            for y in range(bitmap.height):
                if bitmap[(x, y)] == 0:
                    new_bitmap[(x, y)] = background_color_index
    if foreground_color_index is not None:
        for x in range(bitmap.width):
            for y in range(bitmap.height):
                if bitmap[(x, y)] == 1:
                    new_bitmap[(x, y)] = foreground_color_index
    return new_bitmap
```

With those 2 challenges solved, the rest of the graphics work was fairly straightforward. The only other place where the color index was used was in specifying the key color for **bitmaptools** `blit` function, which it avoids drawing. This allows 2 different tiles to be drawn on top of each other.

This spritesheet includes all of the tiles used as well as a duplicate set of keyed tiles (the ones with a light green background) for the creatures that need to be drawn on top of different backgrounds. The reason for having 2 different sets is for speed because most of the time, the creatures are drawn on top of empty floor, but occasionally (such as sliding on ice), the creature needs to be drawn on top of empty floor. This is to speed up the game, as most of the time, only one sprite needs to be drawn instead of 2.

![](https://cdn-learn.adafruit.com/assets/assets/000/136/126/medium640/gaming_spritesheet_24_keyed.jpg?1743554225)

# Chip's Challenge on Fruit Jam and Metro RP2350

## Partial Screen Updating

In order to speed up the game, I ended up only redrawing the display where necessary. This meant keeping track of several screen locations. To do this, I wrote a special data buffer class. It works similar to a dictionary with a few changes. It keeps track of a default set of data and allows resetting either all of the data or selected keys to the default value, which makes updating certain area or resetting the level much easier.

https://github.com/adafruit/Adafruit_Learning_System_Guides/blob/main/Metro/Metro_RP2350_Chips_Challenge/databuffer.py

One of the places that partial updating excels is when there are a lot of the same tiles on the screen. This is because as the viewport changes, the tile that needs to be displayed may not actually need to be redrawn such as with empty floor tiles. If the game were using displayio alone, the entire viewport would be redrawn because it only knows that a new values were written in a location and marks the areas as dirty.

![](https://cdn-learn.adafruit.com/assets/assets/000/136/237/medium800/gaming_screen_update.png?1744152783)

However, when the game has a lot of different tiles, you may notice more lag because it has to redraw more tiles in the viewport as you move around. However, with the game as optimized as it is, the increased lag is barely perceptible.

Another strategy that was used was using displayio for the dialogs. By keeping all of the dialogs in a separate layer and everything else in either of the 2 main areas of the game (the tiles or the info box), the background is rarely redrawn for the entirety of the gameplay.

# Chip's Challenge on Fruit Jam and Metro RP2350

## Keyboard Input

One of the biggest challenges with this game was getting the keyboard input correct. Normally, the way a game handles keyboard input is by keeping track of which keys are pressed by monitoring Key Down and Key Up events. The way that CircuitPython handles the game is by allowing only a single keypress at a time and if you continue holding the key, it waits about half a second to send additional keypresses. Because of this limitation, you can move a bit faster and more accurately by rapidly tapping the arrow key that you want to use.

Additionally, for many of the keys such as the arrows, it adds a multibyte key sequence to the keyboard buffer. I wrote a Keyboard Input class that allows a set of valid key sequences to be set and it will only return the key once it has a full packet and it will discard anything else. It works quite well and includes the ability to even work with control characters. In order to keep the game simple, I decided to only use hotkeys instead of a menu system, so all input is done using the keyboard.

https://github.com/adafruit/Adafruit_Learning_System_Guides/blob/main/Metro/Metro_RP2350_Chips_Challenge/keyboard.py

I created 3 sets of valid inputs which map the key sequence to the return value and the game switches between them as needed. These are found in the **definitions.py** file and&nbsp;include:&nbsp;

- **GAMEPLAY\_COMMANDS:** Used for normal gameplay and includes the arrow keys as well as any hotkeys.
- **MESSAGE\_COMMANDS:** Only allows either the enter key or spacebar and is used to dismiss informational dialogs.
- **PASSWORD\_COMMANDS:** Used for typing in the level number or password into the the password boxes.

```python
# Command Constants
UP = const(0)
LEFT = const(1)
DOWN = const(2)
RIGHT = const(3)
NEXT_LEVEL = const(4)
PREVIOUS_LEVEL = const(5)
RESTART_LEVEL = const(6)
GOTO_LEVEL = const(7)
PAUSE = const(8)
QUIT = const(9)
OK = const(10)
CANCEL = const(11)
CHANGE_FIELDS = const(12)
DELCHAR = const(13)

# Keycode Constants
UP_ARROW = const("\x1b[A")
DOWN_ARROW = const("\x1b[B")
RIGHT_ARROW = const("\x1b[C")
LEFT_ARROW = const("\x1b[D")
SPACE = const(" ")
CTRL_G = const("\x07")  # Ctrl+G
CTRL_N = const("\x0E")  # Ctrl+N
CTRL_P = const("\x10")  # Ctrl+P
CTRL_Q = const("\x11")  # Ctrl+Q
CTRL_R = const("\x12")  # Ctrl+R
BACKSPACE = const("\x08")
TAB = const("\x09")
ENTER = const("\n")
ESC = const("\x1b")

# Mapping Buttons to Commands for different modes
GAMEPLAY_COMMANDS = {
    UP_ARROW: UP,
    LEFT_ARROW: LEFT,
    DOWN_ARROW: DOWN,
    RIGHT_ARROW: RIGHT,
    SPACE: PAUSE,
    CTRL_G: GOTO_LEVEL,
    CTRL_N: NEXT_LEVEL,
    CTRL_P: PREVIOUS_LEVEL,
    CTRL_Q: QUIT,
    CTRL_R: RESTART_LEVEL,
}

MESSAGE_COMMANDS = {
    ENTER: OK,
    SPACE: OK,
}

# Password commands include only letters, enter, tab, and backspace
PASSWORD_COMMANDS = {
     ESC: CANCEL,
     TAB: CHANGE_FIELDS,
     ENTER: OK,
     BACKSPACE: DELCHAR,
}

# The rest are input characters
for i in range(65, 91):
    PASSWORD_COMMANDS[chr(i)] = chr(i)
for i in range(97, 123):
    PASSWORD_COMMANDS[chr(i)] = chr(i)
for i in range(48, 58):
    PASSWORD_COMMANDS[chr(i)] = chr(i)
```

# Chip's Challenge on Fruit Jam and Metro RP2350

## Drawing with bitmaptools

For drawing the graphics for the game itself, I decided to use [bitmaptools](https://docs.circuitpython.org/en/latest/shared-bindings/bitmaptools/index.html) to make the code easier to convert. The code I was porting from used a `bitblt` function (pronounced bit blit and is short for **BIT**  **BL** ock **T** ransfer), and the equivalent **bitmaptools** function is [blit()](https://docs.circuitpython.org/en/latest/shared-bindings/bitmaptools/index.html#bitmaptools.blit). Essentially, this function copies all or part of a bitmap and draws it onto another, kind of like copying and pasting. The reason I went with this is that it was much easier to optimize and ran faster.

For the winning animation, I also used [rotozoom()](https://docs.circuitpython.org/en/latest/shared-bindings/bitmaptools/index.html#bitmaptools.rotozoom). This function allows scaling and rotating the image in a single operation. However, for this game, only the scaling aspect was used. To learn more about how this was used, be sure to check out the [Animations](https://learn.adafruit.com/256-color-gaming-on-the-metro-rp2350/animations) page of this guide.

bitmaptools also has quite a few additional functions that can be useful in creating games.&nbsp;For the rest of the game, I ended up going with displayio to display the background, messages, and loading info box. This was because it allowed me to easily dismiss the dialogs without needing to redraw the display.

# Chip's Challenge on Fruit Jam and Metro RP2350

## Dynamically Loading Data

One of the most important features I wanted to include with this game was the ability to load level set data files. As the name implies, a data file represents a set of levels. The reason I wanted to include this is because since the game's release, a community of people have created hundreds of custom level sets that can be found using a web search. These were made by using some custom level editors written by other community members.

The level file can be changed in **code.py** by altering the `DATA_FILE` setting. Although this should load most custom levels, some of the ones using more advanced coding techniques that take advantage of glitches in the Microsoft version of the game, though this functionality could probably be added without too much trouble.

To achieve loading the data file dynamically, the file is parsed byte by byte and loaded into a useable data structure for the game. An explanation of the file structure is explained in [Chips Challenge File Layout](https://www.seasip.info/ccfile.html) and was the basis of how the dynamic loading took place. If you would like to take a closer look at the code, it is contained in the **level.py** file.

The `load()` function reads the binary file byte by byte to extract the necessary data. It starts by validating the file header, finding the starting position of the level data, and then extracting it out to instance variables that can be read by the game logic.

```python
def load(self, level_number):
    #pylint: disable=too-many-branches, too-many-locals
    # Reset the data prior to loading
    self._reset_data()
    # Read the file and fill in the variables
    with open(self._data_file, "rb") as file:
        # Read the first 4 bytes in little endian format
        if read_int(file, 4) not in (0x0002AAAC, 0x0102AAAC):
            raise ValueError("Not a CHIP file")
        self.last_level = read_int(file, 2)
        if not 0 &lt; level_number &lt;= self.last_level:
            raise ValueError("Invalid level number")
        self.level_number = level_number
        # Seek to the start of the level data for the specified level
        while True:
            level_bytes = read_int(file, 2)
            if read_int(file, 2) == level_number:
                break
            # Go to next level
            file.seek(level_bytes - 2, 1)

        # Read the level data
        self.time_limit = read_int(file, 2)
        self.chips_required = read_int(file, 2)
        compression = read_int(file, 2)
        if compression == COMPRESSED:
            raise ValueError("Compressed levels not supported")

        # Process the top map data
        layer_bytes = read_int(file, 2)
        map_data = file.read(layer_bytes)
        self._process_map_data(map_data, "top")

        # Process the bottom map data
        layer_bytes = read_int(file, 2)
        map_data = file.read(layer_bytes)
        self._process_map_data(map_data, "bottom")

        remaining_bytes = read_int(file, 2)
        while remaining_bytes &gt; 0:
            field_type = read_int(file, 1)
            field_size = read_int(file, 1)
            remaining_bytes -= (2 + field_size)
            if field_type == FIELD_TITLE:
                self.title = file.read(field_size).decode("utf-8").replace("\x00", "")
            elif field_type == FIELD_HINT:
                self.hint = file.read(field_size).decode("utf-8").replace("\x00", "")
            elif field_type == FIELD_PASSWORD:
                self.password = (
                    "".join([chr(c ^ 0x99) for c in file.read(field_size)]).replace("\x99", "")
                )
            elif field_type == FIELD_BEAR_TRAPS:
                trap_count = field_size // 10
                for _ in range(trap_count):
                    button = Point(read_int(file, 2), read_int(file, 2))
                    device = Point(read_int(file, 2), read_int(file, 2))
                    self.traps.append(Device(button, device))
                    file.seek(2, 1)
            elif field_type == FIELD_CLONING_MACHINES:
                cloner_count = field_size // 8
                for _ in range(cloner_count):
                    button = Point(read_int(file, 2), read_int(file, 2))
                    device = Point(read_int(file, 2), read_int(file, 2))
                    self.cloners.append(Device(button, device))
            elif field_type == FIELD_MOVING_CREATURES:
                creature_count = field_size // 2
                for _ in range(creature_count):
                    self.creatures.append(Point(
                        read_int(file, 1),
                        read_int(file, 1)
                    ))

        # Load passwords if not already loaded
        if len(self.passwords) == 0:
            self._load_passwords(file)
```

The&nbsp;`read_int()`&nbsp;function is a helper function which reads a certain number of in&nbsp; **little endian** &nbsp;format and converts them to an integer. Little endian means a multi-byte value has the least significant bytes written first in the file.

```python
def read_int(file, byte_count):
    return int.from_bytes(file.read(byte_count), "little")
```

At the end of loading the level file, on the first time the file is loaded, the level passwords are also extracted out. This is so the game can check if a particular level password is correct without discarding the current level. This is done by moving&nbsp;the pointer back to the beginning of the file and then going level by level to extract each password and decode it.

```python
def _load_passwords(self, file):
    file.seek(6)    # Skip the file header
    while True:
        file.seek(2, 1)
        level_number = read_int(file, 2)
        file.seek(6, 1)
        layer_bytes = read_int(file, 2)   # Number of bytes in the top layer
        file.seek(layer_bytes, 1)   # Skip top layer
        layer_bytes = read_int(file, 2)   # Number of bytes in the top layer
        file.seek(layer_bytes, 1)   # Skip bottom layer
        remaining_bytes = read_int(file, 2)
        while remaining_bytes &gt; 0:
            field_type = read_int(file, 1)
            field_size = read_int(file, 1)
            remaining_bytes -= (2 + field_size)
            if field_type == FIELD_PASSWORD:
                password = file.read(field_size)
                self.passwords[level_number] = (
                    "".join([chr(c ^ 0x99) for c in password]).replace("\x99", "")
                )
                file.seek(remaining_bytes, 1)
                break
            file.seek(field_size, 1)
        if len(self.passwords) == self.last_level:
            break
```

# Chip's Challenge on Fruit Jam and Metro RP2350

## Save States

Warning: Do not insert or remove an SD Card while the game is running. It is may corrupt the file system and the game will need to be reloaded. It may also corrupt the save file and all progress will be lost.

Progress of the game is automatically saved as you complete or unlock each level. This includes the password for the level, the score, and the amount of time left when you complete the level.&nbsp;The amount of space required is too much to be stored in nvram, so an SD card is used. It is automatically remounted as read/write when the game is loaded. You can still play the game without an SD card, but the state is not saved between loads of the game.

The purposes of the `SaveState` class include:

- Handling automatically mounting the SD Card and marking it as unavailable if it fails to load
- Handling loading the data and saving it as it is updated
- Keeping track of the passwords and scores as the game is played

By default, the save file is named **chips.json** , but can be renamed. If you do not have an SD card, that is detected when the game is first loaded and the game will continue to function like normal for the duration of the session. The only difference is that progress with regards to scoring and unlocking levels is not saved. You can tell if the SD Card has successfully loaded by watching the serial output.

If it loads successfully, you should see "SD Card detected" and if not, you should see "SD Card not detected. Level data will NOT be saved."

https://github.com/adafruit/Adafruit_Learning_System_Guides/blob/main/Metro/Metro_RP2350_Chips_Challenge/savestate.py

# Chip's Challenge on Fruit Jam and Metro RP2350

## Dialogs

There are three kinds of dialogs used in Chips Challenge. These are the simple dialog, the message dialog, and the password dialogs.

## Simple Dialog

The simple dialog just contains text and no buttons. It is used to display the level title, the hint, and the pause screen overlay. The hint and title are shown automatically and dismissed automatically based on the gameplay and the pause overlay is shown or hidden when either the game is paused or the user tries to go to a level that has not been unlocked yet.

The simple dialog, such as the hint dialog, is easily hidden within the user interface and only shows a message.

![](https://cdn-learn.adafruit.com/assets/assets/000/136/226/medium640/gaming_simple_dialog.png?1744139973)

## Message Dialog

The message dialog is basically the same as the simple dialog, except it has a button drawn below the text. Input from the keyboard is read to dismiss the dialog. This is used to display messages such as why Chip died or to display a summary of the level upon winning. Every 10 levels or so, an additional message (known as a decade message) will be added to the summary as well and at the end of the game some additional messages are shown.

The buttons are merely aesthetic and you can't actually click them because there is no touch input being utilized in the game. Instead, the clicking is simulated with keypresses. Either the spacebar or Enter key can be pressed to dismiss the dialog.

The Message dialog is handled by the `show_message()` function inside of **game.py**. It handles the drawing of the dialog, listening to keyboard input, and settings/unsetting the valid keyboard command sets.

The message dialog is used to show messages to the user such as why Chip died.

![](https://cdn-learn.adafruit.com/assets/assets/000/136/225/medium640/gaming_message_dialog.png?1744139943)

## Password Dialogs

The password dialogs are used when you need to go to a specific level or just type in a password. This is the most complex type because the input fields need to be kept track of so that they can be updated if there is any input and the fields redrawn.

When they are displayed, input from the keyboard is read and the controls are updated accordingly. The fields have a type that allows for just alphabetic characters, numeric characters, or anything. This allows for easier filtering of the keys. This way you aren't able to type a letter for a level number. It also has a maximum length property in order to limit the number of characters and having it go off the screen.

Just like with the message dialog, there are some purely visual buttons at the bottom. These include OK and Cancel buttons, which are selected by pressing the Enter and Escape keys respectively.

The Password dialog is handled by the `request_password()` function in **game.py**. It handles the drawing of the dialog, listening to keyboard input, adjusting the field parameters, and settings/unsetting the valid keyboard command sets. It also handles what to do when the user selects either OK or CANCEL.

The password dialog is to let the user input a level number and password (or in some cases, just the password). The Tab key allows switching to the next dialog and the darker border indicates the active field. Both fields allow a maximum of 9 characters, though this could be set to a smaller value. The number field only allows numerical values to be entered, whereas the password field allows alphanumeric values.

Partial updating is used to only redraw the active field when a value is changed.

![](https://cdn-learn.adafruit.com/assets/assets/000/136/223/medium640/gaming_input_dialog.png?1744139881)

## Stacking Dialogs

Dialogs are kept in their own layer using a displayio group. They are able to be stacked by adding dialogs to the group and removing them as they are dismissed.

Here is an example of stacked dialogs using every type. The pause screen and hint dialogs are simple dialogs. The password dialog is shown here as well as an error message. Dialogs are removed in the reverse order that they are added.

![](https://cdn-learn.adafruit.com/assets/assets/000/136/224/medium640/gaming_stacked_dialogs.png?1744139925)

# Chip's Challenge on Fruit Jam and Metro RP2350

## Animations

While most of the gameplay is a matter of moving the different tiles around and would not really be considered animation, the end game includes an animation. This is done by defining the animation sequences and then running them in a loop. The animations can have operations done such as zooming in on them or moving them around.

The animation implemented in this game very closely matches the original game. Because of the way it is drawn, with Chip enlarging, each new frame just covers the existing graphics below it, so there is no need to deal with the previously drawn graphics. As such, I ended up just using&nbsp;`bitmaptools` to draw the animations on top of the game layer. You can check out the&nbsp;[Blinka Jump PyBadge Game](https://learn.adafruit.com/blinka-jump-pybadge-game) and [Halloween Countdown Display Matrix](https://learn.adafruit.com/halloween-countdown-display-matrix) learn guides to see examples of animation using displayio, which does a great job handling automatically erasing the old graphics.

![](https://cdn-learn.adafruit.com/assets/assets/000/136/227/medium800thumb/gaming_Ending.jpg?1744142370)

The animation sequence shown above is handled by the `_show_winning_sequence()` inside of **game.py**. It starts off by performing some calculations to get the screen coordinates necessary for drawing. After that frames are defined with the top tile and bottom tile. Then the sequences are created using the&nbsp;`get_frame_image()` sub-function to hold the bitmaps to be drawn.

In the first for loop, the zoom sequence is played while it is enlarged using `bitmaptools.rotozoom()` in 32 steps and if the scaled image would go outside of the viewport, it is moved back inside. This is in case the exit is at the edge of the screen.

Finally, the cheer sequence is played a random number of times between 16-20 times with a random delay between 0.25 and 0.75 seconds. Without actually seeing the source code of the original game, this appears to be almost indistinguishable from the original sequence.

Finally, and ending bitmap is displayed along with a message.

```python
def _show_winning_sequence(self):
    #pylint: disable=too-many-locals
    self._gamelogic.set_game_mode(GM_GAMEWON)

    def get_frame_image(frame):
        # Create a tile sized bitmap
        tile_buffer = displayio.Bitmap(self._tile_size, self._tile_size, 256)
        self._draw_tile(tile_buffer, 0, 0, frame[0], frame[1])
        return tile_buffer

    # Get chips coordinates
    chip = self._gamelogic.get_chip_coords_in_viewport()
    viewport_size = self._tile_size * 9

    # Get centered screen coordinates of chip
    chip_position = Point(
        VIEWPORT_OFFSET[0] + chip.x * self._tile_size + self._tile_size // 2,
        VIEWPORT_OFFSET[1] + chip.y * self._tile_size + self._tile_size // 2
    )

    viewport_center = Point(
        VIEWPORT_OFFSET[0] + viewport_size // 2 - 1,
        VIEWPORT_OFFSET[1] + viewport_size // 2 - 1
    )

    # Chip Frames
    frames = {
        "cheering": (TYPE_EXITED_CHIP, TYPE_EMPTY),
        "standing_1": (TYPE_CHIP + DOWN, TYPE_EXIT),
        "standing_2": (TYPE_CHIP + DOWN, TYPE_EXIT_EXTRA_1),
        "standing_3": (TYPE_CHIP + DOWN, TYPE_EXIT_EXTRA_2),
    }

    # Chip Sequences
    zoom_sequence = (
        get_frame_image(frames["standing_1"]),
        get_frame_image(frames["standing_2"]),
        get_frame_image(frames["standing_3"]),
    )

    cheer_sequence = (
        get_frame_image(frames["cheering"]),
        get_frame_image(frames["standing_1"]),
    )

    viewport_upper_left = Point(
        VIEWPORT_OFFSET[0],
        VIEWPORT_OFFSET[1]
    )
    viewport_lower_right = Point(
        VIEWPORT_OFFSET[0] + viewport_size,
        VIEWPORT_OFFSET[1] + viewport_size
    )

    for i in range(32):
        source_bmp = zoom_sequence[i % len(zoom_sequence)]
        scale = 1 + ((i + 1) / 32) * 8
        scaled_tile_size = math.ceil(self._tile_size * scale)
        x = chip_position.x
        y = chip_position.y

        # Make sure the scaled tile is within the viewport
        scaled_tile_upper_left = Point(
            x - scaled_tile_size // 2,
            y - scaled_tile_size // 2
        )
        scaled_tile_lower_right = Point(
            x + scaled_tile_size // 2,
            y + scaled_tile_size // 2
        )
        if scaled_tile_upper_left.y &lt; viewport_upper_left.y:
            y += viewport_upper_left.y - scaled_tile_upper_left.y
        elif scaled_tile_lower_right.y &gt; viewport_lower_right.y:
            y -= scaled_tile_lower_right.y - viewport_lower_right.y
        if scaled_tile_upper_left.x &lt; viewport_upper_left.x:
            x += viewport_upper_left.x - scaled_tile_upper_left.x
        elif scaled_tile_lower_right.x &gt; viewport_lower_right.x:
            x -= scaled_tile_lower_right.x - viewport_lower_right.x

        bitmaptools.rotozoom(self._buffers["main"], source_bmp, ox=x, oy=y, scale=scale)
        sleep(0.1)

    for i in range(randint(16, 20)):
        source_bmp = cheer_sequence[i % len(cheer_sequence)]
        bitmaptools.rotozoom(
            self._buffers["main"],
            source_bmp,
            ox=viewport_center.x,
            oy=viewport_center.y,
            scale=9
        )
        sleep(random() * 0.5 + 0.25) # Sleep for a random time between 0.25 and 0.75 seconds

    bitmaptools.blit(
        self._buffers["main"],
        self._images["chipend"],
        VIEWPORT_OFFSET[0],
        VIEWPORT_OFFSET[1],
    )
    self.show_message("Great Job Chip! You did it! You finished the challenge!")
```

# Chip's Challenge on Fruit Jam and Metro RP2350

## Audio

Getting the audio to work in such a large game had a few challenges. Most of the challenges was centered around getting the audio module to load in memory before the rest of the game. This is accomplished by initializing the audio first and attempting to play a sound. If this was not done, the video would briefly cut out while the audio was playing.

The audio board this project is centered around is the&nbsp;[TLV320DAC3100](https://www.adafruit.com/product/6309) breakout board, but you can use any I2S DAC that is supported by CircuitPython. For more information, check out the [Wiring page](https://learn.adafruit.com/256-color-gaming-on-the-metro-rp2350/wiring). The audio device is initialized in **code.py** and is passed in to the `Audio` class along with a dictionary of sound effects. The sound effects are provided in **code.py** so that you can customize them with your own sounds.

The Audio class is pretty simple and involved initializing the class and a single public method to play a sound by passing a key from the `SOUND_EFFECTS` dictionary.

https://github.com/adafruit/Adafruit_Learning_System_Guides/blob/main/Metro/Metro_RP2350_Chips_Challenge/audio.py

# Chip's Challenge on Fruit Jam and Metro RP2350

## Preparing the Metro RP2350

Primary: If you have a Fruit Jam instead of a Metro RP2350, you can go to the next page.

The USB Host port is the only part of this project that required soldering and only if you use standard header pins. If you use the [Solderless Press-Fit Male Pin Header](https://www.adafruit.com/product/5938) in the Parts list, soldering may not be required if they are installed correctly.

The USB Host pin connections are highlighted on the Metro image to the left. You will need a small piece of 0.1 inch male header, with 4 pins, to fit the holes.

You can cut header with diagonal cutters or break them with pliers or even your fingers. Just be sure to wear eye protection as they can fly when cut.&nbsp;

![The Metro RP2350 board with the USB Host connection highlighted](https://cdn-learn.adafruit.com/assets/assets/000/135/532/medium640/raspberry_pi_adafruit_products_pinouts_usbhost_highlight.png?1740704493)

![Male header strip, full, "hammer" in](https://cdn-learn.adafruit.com/assets/assets/000/136/240/medium640/gaming_four.png?1744210071)

Put the short end of the header into the holes in the Metro marked USB Host.&nbsp;

If you are using solderless header then they are press fit into the holes. You will need some pressure to get them in if they are the Press-Fit version, pliers will be required. While they are designed to make electrical contact, you might want to solder them to be sure.

If using standard header, secure them with putty, blutack, tape, etc.

Turn the Metro over and you should see the header barely poking out of the bottom of the board. If the pins stick through a great deal you may have the header pins upside down, double check the short end is sticking into the board.

Solder the 4 pin "nubbins" to the board.

![A Hakko soldering iron, a roll of solder and a Metro RP2350 board](https://cdn-learn.adafruit.com/assets/assets/000/135/534/medium640/raspberry_pi_20250226_110701.jpg?1740705385)

![The back of the Metro RP2350 board showing the short end of the header pins sticking through the board](https://cdn-learn.adafruit.com/assets/assets/000/135/535/medium640/raspberry_pi_20250226_110712.jpg?1740705653)

![The Metro back off the PC board with the pins soldered](https://cdn-learn.adafruit.com/assets/assets/000/135/536/medium640/gaming_raspberry_pi_20250226_110952.jpg?1754938894)

Turn the board over and remove the material securing the pins. Now there is a new 4-pin header.&nbsp;

Get the USB Host cable and wire as follows:

**GRD** to **Black**

**D+** to **Green**

**D-** to **White**

**5V** to **Red**

![The Metro, right side up, with the USB Host pins installed](https://cdn-learn.adafruit.com/assets/assets/000/135/537/medium640/raspberry_pi_20250226_111019.jpg?1740705869)

![An Adafruit Metro RP2350 board with a USB Host port cable assembly attached in the middle and an HSTX cable on the left extending to an HSTX to HDMI breakout board](https://cdn-learn.adafruit.com/assets/assets/000/135/538/medium800/raspberry_pi_20250227_192553.jpg?1754939038 )

## HSTX Connection to DVI
![An Adafruit Metro RP2350 board with an HSTX cable on the left extending to an HSTX to HDMI breakout board](https://cdn-learn.adafruit.com/assets/assets/000/135/597/medium800/gaming_hstx.png?1754939026 )

Get the HSTX cable. Any length Adafruit sells is fine. CAREFULLY lift the dark grey bar up on the Metro, insert the cable silver side down, blue side up, then put the bar CAREFULLY down, ensuring it locks. If it feels like it doesn't want to go, do not force it.

Do the same with the other end and the DVI breakout. Note that the DVI breakout will be inverted/upside down when compared to the Metro - this is normal for these boards and the Adafruit cables.

# Chip's Challenge on Fruit Jam and Metro RP2350

## Wiring the Audio

Primary: If you have a Fruit Jam instead of a Metro RP2350, you can go to the next page.

Most of the wiring involves the audio output. For this project, I chose the [TLV320DAC3100](https://www.adafruit.com/product/6309), but other I2S Digital Audio Converters such as the [PCM5100](https://www.adafruit.com/product/6251) should work fine. The main difference is you will need to update the initialization code in **code.py**.

For the output, you can connect the headphone jack to a pair of headphones or if you have a monitor with audio input, you could connect a 3.5mm stereo audio cable between the two. Additionally, if you have a speaker, you could either connect it to the JST connector or the speaker pins on the breakout.

- **Board 3.3V** &nbsp;to&nbsp;**DAC VIN (red wire)**
- **Board GND** &nbsp;to&nbsp;**DAC GND (black wire)**
- **Board SCL** &nbsp;to&nbsp;**DAC SCL (yellow wire)**
- **Board SDA** &nbsp;to&nbsp;**DAC SDA (green wire)**
- **Board D9** &nbsp;to&nbsp;**DAC BCK (blue wire)**
- **Board D10** &nbsp;to&nbsp;**DAC WSEL (purple wire)**
- **Board D11** &nbsp;to&nbsp;**DAC DIN (orange wire)**
- **DAC 3.5mm output** &nbsp;to&nbsp; **headphones or monitor**

![](https://cdn-learn.adafruit.com/assets/assets/000/136/201/medium640/gaming_Audio_Wiring_bb.jpg?1744066069)

# Chip's Challenge on Fruit Jam and Metro RP2350

## CircuitPython for the Metro RP2350

[CircuitPython](https://github.com/adafruit/circuitpython) is a derivative of [MicroPython](https://micropython.org) designed to simplify experimentation and education on low-cost microcontrollers. It makes it easier than ever to get prototyping by requiring no upfront desktop software downloads. Simply copy and edit files on the **CIRCUITPY** drive to iterate.

## CircuitPython Quickstart

Follow this step-by-step to quickly get CircuitPython running on your board.

[Download the latest version of CircuitPython for this board via circuitpython.org](https://circuitpython.org/board/adafruit_metro_rp2350/)
 **Click the link above to download the latest CircuitPython UF2 file.**

Save it wherever is convenient for you.

![install_circuitpython_on_rp2040_RP2040_UF2_downloaded.jpg](https://cdn-learn.adafruit.com/assets/assets/000/101/655/medium640/install_circuitpython_on_rp2040_RP2040_UF2_downloaded.jpg?1618943202)

![](https://cdn-learn.adafruit.com/assets/assets/000/135/292/medium800/adafruit_products_boot_reset_btn_highlights.png?1739467365)

To enter the bootloader, hold down the **BOOT/**** BOOTSEL button**(highlighted in red above), and while continuing to hold it (don't let go!), press and release the**reset button**(highlighted in red or blue above).&nbsp;**Continue to hold the BOOT/BOOTSEL button until the RP2350 drive appears!**

If the drive does not appear, release all the buttons, and then repeat the process above.

You can also start with your board unplugged from USB, press and hold the BOOTSEL button (highlighted in red above), continue to hold it while plugging it into USB, and wait for the drive to appear before releasing the button.

A lot of people end up using charge-only USB cables and it is very frustrating! **Make sure you have a USB cable you know is good for data sync.**

You will see a new disk drive appear called **RP2350**.

&nbsp;

Drag the **adafruit-circuitpython-_boardname_-_language_-_version_.uf2** file to **RP2350.**

![install_circuitpython_on_rp2350_Screenshot_2024-09-11_111518.png](https://cdn-learn.adafruit.com/assets/assets/000/132/253/medium640/install_circuitpython_on_rp2350_Screenshot_2024-09-11_111518.png?1726067809)

![install_circuitpython_on_rp2350_Screenshot_2024-09-11_111742.png](https://cdn-learn.adafruit.com/assets/assets/000/132/254/medium640/install_circuitpython_on_rp2350_Screenshot_2024-09-11_111742.png?1726067866)

The **RP2350** drive will disappear and a new disk drive called **CIRCUITPY** will appear.

That's it, you're done! :)

![install_circuitpython_on_rp2350_Screenshot_2024-09-11_111843.png](https://cdn-learn.adafruit.com/assets/assets/000/132/255/medium640/install_circuitpython_on_rp2350_Screenshot_2024-09-11_111843.png?1726067932)

## Safe Mode

You want to edit your **code.py** or modify the files on your **CIRCUITPY** drive, but find that you can't. Perhaps your board has gotten into a state where **CIRCUITPY** is read-only. You may have turned off the **CIRCUITPY** drive altogether. Whatever the reason, safe mode can help.

Safe mode in CircuitPython does not run any user code on startup, and disables auto-reload. This means a few things. First, safe mode _bypasses any code in_ **boot.py** (where you can set **CIRCUITPY** read-only or turn it off completely). Second, _it does not run the code in_ **code.py**. And finally, _it does not automatically soft-reload when data is written to the_ **CIRCUITPY** _drive_.

Therefore, whatever you may have done to put your board in a non-interactive state, safe mode gives you the opportunity to correct it without losing all of the data on the **CIRCUITPY** drive.

### Entering Safe Mode
To enter safe mode when using CircuitPython, plug in your board or hit reset (highlighted in red above). Immediately after the board starts up or resets, it waits 1000ms. On some boards, the onboard status LED (highlighted in green above) will blink yellow during that time. If you press reset during that 1000ms, the board will start up in safe mode. It can be difficult to react to the yellow LED, so you may want to think of it simply as a slow double click of the reset button. (Remember, a fast double click of reset enters the bootloader.)

### In Safe Mode

If you successfully enter safe mode on CircuitPython, the LED will intermittently blink yellow three times.

If you connect to the serial console, you'll find the following message.

```terminal
Auto-reload is off.
Running in safe mode! Not running saved code.

CircuitPython is in safe mode because you pressed the reset button during boot. Press again to exit safe mode.

Press any key to enter the REPL. Use CTRL-D to reload.
```

You can now edit the contents of the **CIRCUITPY** drive. Remember, _your code will not run until you press the reset button, or unplug and plug in your board, to get out of safe mode._

## Flash Resetting UF2

If your board ever gets into a really _weird_ state and CIRCUITPY doesn't show up as a disk drive after installing CircuitPython, try loading this 'nuke' UF2 to RP2350. which will do a 'deep clean' on your Flash Memory. **You will lose all the files on the board** , but at least you'll be able to revive it! After loading this UF2, follow the steps above to re-install CircuitPython.

[Download flash erasing "nuke" UF2 for RP2350](https://cdn-learn.adafruit.com/assets/assets/000/132/526/original/rp2350_flash_nuke.uf2)
# Chip's Challenge on Fruit Jam and Metro RP2350

## CircuitPython for the Fruit Jam

[CircuitPython](https://github.com/adafruit/circuitpython) is a derivative of [MicroPython](https://micropython.org) designed to simplify experimentation and education on low-cost microcontrollers. It makes it easier than ever to get prototyping by requiring no upfront desktop software downloads. Simply copy and edit files on the **CIRCUITPY** drive to iterate.

## CircuitPython Quickstart

Follow this step-by-step to quickly get CircuitPython running on your board.

Warning: Please use the latest release of 10.x or higher for the Fruit Jam. Also use the latest libraries for the best functionality.

[Download the latest version of CircuitPython for this board via circuitpython.org](https://circuitpython.org/board/adafruit_fruit_jam/)
 **Click the link above to download the latest CircuitPython UF2 file.**

Save it wherever is convenient for you.

![install_circuitpython_on_rp2040_RP2040_UF2_downloaded.jpg](https://cdn-learn.adafruit.com/assets/assets/000/101/655/medium640/install_circuitpython_on_rp2040_RP2040_UF2_downloaded.jpg?1618943202)

![reset and boot highlighted](https://cdn-learn.adafruit.com/assets/assets/000/138/708/medium800/adafruit_products_Resetboot.jpg?1754331128 )

To enter the bootloader, hold down the **BOOT/**** BOOTSEL button**(highlighted in red above), and while continuing to hold it (don't let go!), press and release the**reset button**(highlighted in red or blue above).&nbsp;**Continue to hold the BOOT/BOOTSEL button until the RP2350 drive appears!**

If the drive does not appear, release all the buttons, and then repeat the process above.

You can also start with your board unplugged from USB, press and hold the BOOTSEL button (highlighted in red above), continue to hold it while plugging it into USB, and wait for the drive to appear before releasing the button.

A lot of people end up using charge-only USB cables and it is very frustrating! **Make sure you have a USB cable you know is good for data sync.**

You will see a new disk drive appear called **RP2350**.

&nbsp;

Drag the **adafruit-circuitpython-_boardname_-_language_-_version_.uf2** file to **RP2350.**

![install_circuitpython_on_rp2350_Screenshot_2024-09-11_111518.png](https://cdn-learn.adafruit.com/assets/assets/000/132/253/medium640/install_circuitpython_on_rp2350_Screenshot_2024-09-11_111518.png?1726067809)

![install_circuitpython_on_rp2350_Screenshot_2024-09-11_111742.png](https://cdn-learn.adafruit.com/assets/assets/000/132/254/medium640/install_circuitpython_on_rp2350_Screenshot_2024-09-11_111742.png?1726067866)

The **RP2350** drive will disappear and a new disk drive called **CIRCUITPY** will appear.

That's it, you're done! :)

![install_circuitpython_on_rp2350_Screenshot_2024-09-11_111843.png](https://cdn-learn.adafruit.com/assets/assets/000/132/255/medium640/install_circuitpython_on_rp2350_Screenshot_2024-09-11_111843.png?1726067932)

## Safe Mode

You want to edit your **code.py** or modify the files on your **CIRCUITPY** drive, but find that you can't. Perhaps your board has gotten into a state where **CIRCUITPY** is read-only. You may have turned off the **CIRCUITPY** drive altogether. Whatever the reason, safe mode can help.

Safe mode in CircuitPython does not run any user code on startup, and disables auto-reload. This means a few things. First, safe mode _bypasses any code in_ **boot.py** (where you can set **CIRCUITPY** read-only or turn it off completely). Second, _it does not run the code in_ **code.py**. And finally, _it does not automatically soft-reload when data is written to the_ **CIRCUITPY** _drive_.

Therefore, whatever you may have done to put your board in a non-interactive state, safe mode gives you the opportunity to correct it without losing all of the data on the **CIRCUITPY** drive.

### Entering Safe Mode
To enter safe mode when using CircuitPython, plug in your board or hit reset (highlighted in red above). Immediately after the board starts up or resets, it waits 1000ms. On some boards, the onboard status LED (highlighted in green above) will blink yellow during that time. If you press reset during that 1000ms, the board will start up in safe mode. It can be difficult to react to the yellow LED, so you may want to think of it simply as a slow double click of the reset button. (Remember, a fast double click of reset enters the bootloader.)

### In Safe Mode

If you successfully enter safe mode on CircuitPython, the LED will intermittently blink yellow three times.

If you connect to the serial console, you'll find the following message.

```terminal
Auto-reload is off.
Running in safe mode! Not running saved code.

CircuitPython is in safe mode because you pressed the reset button during boot. Press again to exit safe mode.

Press any key to enter the REPL. Use CTRL-D to reload.
```

You can now edit the contents of the **CIRCUITPY** drive. Remember, _your code will not run until you press the reset button, or unplug and plug in your board, to get out of safe mode._

## Flash Resetting UF2

If your board ever gets into a really _weird_ state and CIRCUITPY doesn't show up as a disk drive after installing CircuitPython, try loading this 'nuke' UF2 to RP2350. which will do a 'deep clean' on your Flash Memory. **You will lose all the files on the board** , but at least you'll be able to revive it! After loading this UF2, follow the steps above to re-install CircuitPython.

[Download flash erasing "nuke" UF2 for RP2350](https://cdn-learn.adafruit.com/assets/assets/000/132/526/original/rp2350_flash_nuke.uf2)
# Chip's Challenge on Fruit Jam and Metro RP2350

## Create Your settings.toml File

CircuitPython works with WiFi-capable boards to enable you to make projects that have network connectivity. This means working with various passwords and API keys. As of [CircuitPython 8](https://circuitpython.org/downloads), there is support for a **settings.toml** file. This is a file that is stored on your **CIRCUITPY** drive, that contains all of your secret network information, such as your SSID, SSID password and any API keys for IoT services. It is designed to separate your sensitive information from your **code.py** file so you are able to share your code without sharing your credentials.

CircuitPython previously used a **secrets.py** file for this purpose. The **settings.toml** file is quite similar.

Warning: Your **settings.toml** file should be stored in the main directory of your **CIRCUITPY** drive. It should not be in a folder.

## CircuitPython **settings.toml** File

This section will provide a couple of examples of what your **settings.toml** file should look like, specifically for CircuitPython WiFi projects in general.

The most minimal **settings.toml** file must contain your WiFi SSID and password, as that is the minimum required to connect to WiFi. Copy this example, paste it into your **settings.toml** , and update:

- `your_wifi_ssid`
- `your_wifi_password`

```auto
CIRCUITPY_WIFI_SSID = "your_wifi_ssid"
CIRCUITPY_WIFI_PASSWORD = "your_wifi_password"
```

Many CircuitPython network-connected projects on the Adafruit Learn System involve using Adafruit IO. For these projects, you must _also_ include your Adafruit IO username and key. Copy the following example, paste it into your settings.toml file, and update:

- `your_wifi_ssid`
- `your_wifi_password`
- `your_aio_username`
- `your_aio_key`

```auto
CIRCUITPY_WIFI_SSID = "your_wifi_ssid"
CIRCUITPY_WIFI_PASSWORD = "your_wifi_password"
ADAFRUIT_AIO_USERNAME = "your_aio_username"
ADAFRUIT_AIO_KEY = "your_aio_key"
```

Some projects use different variable names for the entries in the **settings.toml** file. For example, a project might use `ADAFRUIT_AIO_ID` in the place of `ADAFRUIT_AIO_USERNAME`. **If you run into connectivity issues, one of the first things to check is that the names in the settings.toml file match the names in the code.**

Warning: Not every project uses the same variable name for each entry in the **settings.toml** file! Always verify it matches the code.

## **settings.toml** File Tips
Here is an example **settings.toml** file.

```auto
# Comments are supported
CIRCUITPY_WIFI_SSID = "guest wifi"
CIRCUITPY_WIFI_PASSWORD = "guessable"
CIRCUITPY_WEB_API_PORT = 80
CIRCUITPY_WEB_API_PASSWORD = "passw0rd"
test_variable = "this is a test"
thumbs_up = "\U0001f44d"
```

In a **settings.toml** file, it's important to keep these factors in mind:

- Strings are wrapped in double quotes; ex: `"your-string-here"`
- Integers are _ **not** _ quoted and may be written in decimal with optional sign (`+1`, `-1`, `1000`) or hexadecimal (`0xabcd`).
  - Floats (decimal numbers), octal (`0o567`) and binary (`0b11011`) are not supported.

- Use `\u` escapes for weird characters, `\x` and `\ooo` escapes are not available in **.toml** files
  - Example: `\U0001f44d` for 👍 (thumbs up emoji) and `\u20ac` for € (EUR sign)

- Unicode emoji, and non-ASCII characters, stand for themselves as long as you're careful to save in "UTF-8 without BOM" format

&nbsp;

&nbsp;

When your&nbsp; **settings.toml&nbsp;** file is ready, you can save it in your text editor with the **.toml** &nbsp;extension.

![adafruit_products_dotToml.jpg](https://cdn-learn.adafruit.com/assets/assets/000/117/071/medium640/adafruit_products_dotToml.jpg?1671034293)

## Accessing Your **settings.toml** Information in **code.py**
In your **code.py** file, you'll need to `import` the `os` library to access the **settings.toml** file. Your settings are accessed with the `os.getenv()` function. You'll pass your settings entry to the function to import it into the **code.py** file.

```python
import os

print(os.getenv("test_variable"))
```

![](https://cdn-learn.adafruit.com/assets/assets/000/117/072/medium800/adafruit_products_tomlOutput.jpg?1671034496)

In the upcoming CircuitPython WiFi examples, you'll see how the **settings.toml&nbsp;** file is used for connecting to your SSID and accessing your API keys.

# Chip's Challenge on Fruit Jam and Metro RP2350

## Software Setup

## CircuitPython Usage

To use the game, you need to add the&nbsp;game program files to the&nbsp; **CIRCUITPY** drive. The game consists of a handful of Python files, graphics, sounds, and fonts.

Thankfully, installing everything be done in one go. In the example below, click the **Download Project Bundle** button below to download the necessary libraries and game files&nbsp;in a zip file.

Connect your board to your computer via a known good data+power USB cable. The board should show up in your File Explorer/Finder (depending on your operating system) as a flash drive named **CIRCUITPY**.

Extract the contents of the zip file, copy the **lib** directory files to **CIRCUITPY/lib**. Copy the entire contents of the appropriate CircuitPython folder to your **CIRCUITPY** drive. The program should self start.

## The settings.toml File

Included in the game files is a **settings.toml** file. If you already have a **settings.toml** file that you want to keep, be sure to skip that file and add the following line:

```python
CIRCUITPY_PYSTACK_SIZE = 2400
```

This increases the depth of the CircuitPython Stack, which is necessary to run this large game.

## Drive Structure

After copying the files, your drive should look like the listing below. It can contain other files as well, but must contain these at a minimum.

![CIRCUITPY](https://raw.githubusercontent.com/adafruit/Adafruit_Learning_System_Guides/refs/heads/folder-images/Metro_Metro_RP2350_Chips_Challenge.png )

## Code

The **code.py** for the game is shown below.

https://github.com/adafruit/Adafruit_Learning_System_Guides/blob/main/Metro/Metro_RP2350_Chips_Challenge/code.py

# Chip's Challenge on Fruit Jam and Metro RP2350

## Playing the Game

The goal of Chip's Challenge is to collect enough chips to make it to the exit at the end. This involves collecting various keys and boots to allow you to get past obstacles and monsters. Most of the keys are 1-time use except for the green key and boots stay in your inventory unless you step on the thief tile, in which case all collected boots are removed. In the default level set, there are 144 regular levels along with 5 bonus levels that you can only play by using the "Go to level" function along with the appropriate password.

![](https://cdn-learn.adafruit.com/assets/assets/000/136/229/medium800/gaming_playing.png?1744144730)

## Customizing the Game

This game can be customized in quite a few ways with most of the settings inside of&nbsp; **code.py**. Below are some fun places to customize the game along with the locations of where to change them.

### Changing the Data File

You can choose a different level set by changing the `DATA_FILE` variable inside of **code.py** to point to the file location. This is so that you can have multiple files on the **CIRCUITPY** drive without needing to overwrite a single file.

### Disabling Auto-reload

If you would like to play while having the board connected to a computer, some Operating Systems such as MacOS may periodically write to the drive and reset the game. By disabling Auto-reload, it will prevent the game from resetting. If you do edit any files with this setting active and want to reload, just unplug and replug in the board. You can do this by uncommenting the last two lines in following code inside of&nbsp; **code.py**. This is done by removing the # from `import supervisor` and the line after that.

```python
# Disable auto-reload to prevent the game from restarting
#import supervisor
#supervisor.runtime.autoreload = False
```

Additionally, you may just be able to just eject the drive if you don't need to modify files.

### Sound Effects

The included sound effects are the same sounds inside of the original game. If you would like to change these to you own, just place you files onto the **CIRCUITPY** drive and edit the `SOUND_EFFECTS` dictionary inside of **code.py**. Just be sure if you change the keys, to update them in **gamelogic.py** or the game will not run properly.

### Playing Without Sound

If you would prefer the sounds don't play, you can change the `PLAY_SOUNDS` variable inside of **definitions.py** to **False**.

### Timing Constants

If you would like to change the timing constants for the game, which control the speed of the game, you can change the `TICKS_PER_SECOND` and `SECOND_LENGTH` variables. Because of the limitations of the microcontroller, attempting to speed up the game likely won't have much effect, but slowing it down would in case you need to get past a section with tight timing.

### Savestate File

If you would like to change the filename of the savestate file, you can change the `SAVESTATE_FILE` variable inside of **savestate.py**. By default, this is **chips.json** , but could be renamed to something else to allow switching between multiple savestate files. If you would like to edit or backup the savestate file, you can place the SD card into a computer and open the file for editing. However, be certain you are using valid JSON or the file will be overwritten.

## Hotkeys

The game is played using the arrow keys as well as a few other hotkeys. A hotkey is just a keyboard shortcut to perform an action. Here are the ones used during normal gameplay:

- **Ctrl+R** : Restart Level
- **Ctrl+N** : Next Level
- **Ctrl+P** : Previous Level
- **Ctrl+Q** : Quit
- **Ctrl+G** : Go to Specific Level
- **Spacebar** : Pause/Unpause Game

When typing in passwords to go to a specific level, you can press the TAB key to go to the next field.

## Passwords

Levels can be unlocked with passwords. If you would like to jump to a specific level, you can find lists of passwords around the web.

## Custom Data Files

If you would like to play levels beyond the included ones, a web search will yield many options. To play these custom level, just copy the files to the **CIRCUITPY** drive and edit the `DATA_FILE` variable in&nbsp; **code.py** to point to the correct file location.

## Editing Data Files

If you would like to create or modify your own levels, there are a number of editors available that allow you to make your own level sets or modify any existing ones.

## Possible Issues

It's possible you may encounter a **PYSTACK exhausted** error and the solution to that is to go into the **settings.toml** file and increase the value of `CIRCUITPY_PYSTACK_SIZE`. I tried setting it at a reasonable value, but increasing this value too much could result in having too little RAM to run the game.

If you find a bug and would like to submit a fix, the files can be found inside of the [Adafruit\_Learning\_System\_Guides](https://github.com/adafruit/Adafruit_Learning_System_Guides) repository on GitHub.

## Improvements

This game could likely be further optimized just by using displayio as many of the things handled manually could be implemented with displayio such as the partial screen updates and better use of controls such as buttons and text boxes. This was left out due to time constraints as well as needing some additional features that were not currently available in the libraries such as the dialogs themselves.


## Featured Products

### Adafruit Fruit Jam - Mini RP2350 Computer

[Adafruit Fruit Jam - Mini RP2350 Computer](https://www.adafruit.com/product/6200)
We were catching up on a recent [hackaday hackchat with eben upton](https://hackaday.io/event/202122-raspberry-pi-hack-chat-with-eben-upton)&nbsp;and learned some fun facts: such as the DVI hack for the RP2040 was inspired by <a...></a...>

Out of Stock
[Buy Now](https://www.adafruit.com/product/6200)
[Related Guides to the Product](https://learn.adafruit.com/products/6200/guides)
### Adafruit Metro RP2350 with PSRAM

[Adafruit Metro RP2350 with PSRAM](https://www.adafruit.com/product/6267)
Choo! Choo! This is the RP2350 Metro Line, making all station stops at "Dual Cortex M33 mountain", "528K RAM round-about" and "16 Megabytes of Flash town" and a bonus stop at "8 Megabytes of PSRAM village". This train is piled high with hardware that...

In Stock
[Buy Now](https://www.adafruit.com/product/6267)
[Related Guides to the Product](https://learn.adafruit.com/products/6267/guides)
### Adafruit RP2350 22-pin FPC HSTX to DVI Adapter for HDMI Displays

[Adafruit RP2350 22-pin FPC HSTX to DVI Adapter for HDMI Displays](https://www.adafruit.com/product/6055)
You may have noticed that our [RP2350 Feather](https://www.adafruit.com/product/6000) has an FPC output connector on the end&nbsp;for accessing the HSTX (High Speed Transmission)&nbsp;peripheral. This new capability, not available on the RP2040, is specifically designed to allow the...

In Stock
[Buy Now](https://www.adafruit.com/product/6055)
[Related Guides to the Product](https://learn.adafruit.com/products/6055/guides)
### 22-pin 0.5mm pitch FPC Flex Cable for DSI CSI or HSTX - 10cm

[22-pin 0.5mm pitch FPC Flex Cable for DSI CSI or HSTX - 10cm](https://www.adafruit.com/product/6035)
Connect this to that when a 22-pin FPC connector is needed. This 10 cm long cable is made of a flexible PCB. It's A-B style, meaning that pin one on one side will match with pin one on the other. How handy!

[We're stocking this to...](https://www.adafruit.com/category/360)

In Stock
[Buy Now](https://www.adafruit.com/product/6035)
[Related Guides to the Product](https://learn.adafruit.com/products/6035/guides)
### 7" Display 1280x800 (720p) IPS + Speakers - HDMI/VGA/NTSC/PAL

[7" Display 1280x800 (720p) IPS + Speakers - HDMI/VGA/NTSC/PAL](https://www.adafruit.com/product/1667)
Yes, this is an adorable small HDMI television with incredibly high resolution **and built in 3W stereo speakers**! We tried to get the smallest possible HDMI/VGA display with high-res, high-contrast visibility. The visible display measures only 7" (17.8cm) diagonal, and the TFT comes...

In Stock
[Buy Now](https://www.adafruit.com/product/1667)
[Related Guides to the Product](https://learn.adafruit.com/products/1667/guides)
### HDMI 5" Display Backpack - Without Touch

[HDMI 5" Display Backpack - Without Touch](https://www.adafruit.com/product/2232)
It's a mini panel-mountable HDMI monitor! So small and simple, you can use this display with any computer that has HDMI output, and the shape makes it easy to attach to a case or rail. This backpack features the TFP401 for decoding video and includes the attached display, so it's...

Out of Stock
[Buy Now](https://www.adafruit.com/product/2232)
[Related Guides to the Product](https://learn.adafruit.com/products/2232/guides)
### Miniature Keyboard- Microcontroller-Friendly PS/2 and USB

[Miniature Keyboard- Microcontroller-Friendly PS/2 and USB](https://www.adafruit.com/product/857)
Add a typing interface to your project with this microcontroller-friendly miniature keyboard. We found the smallest PS/2+USB keyboard available, a mere 8.75" x 4.65" x 0.6" (220mm x 118mm x 16mm)! It's small but usable to make a great accompaniment to either a...

In Stock
[Buy Now](https://www.adafruit.com/product/857)
[Related Guides to the Product](https://learn.adafruit.com/products/857/guides)
### Mini Chiclet Keyboard - USB Wired - Black

[Mini Chiclet Keyboard - USB Wired - Black](https://www.adafruit.com/product/1736)
Add a good quality, slim keyboard to your&nbsp;Raspberry Pi, Beagle Bone Black, or other single-board-computer with this sleek black chiclet keyboard. It's a full QWERTY keyboard with a USB cable and is compatible with all operating systems. We tried many keyboards to find one that felt...

In Stock
[Buy Now](https://www.adafruit.com/product/1736)
[Related Guides to the Product](https://learn.adafruit.com/products/1736/guides)

## Related Guides

- [Adafruit RP2350 22-pin FPC HSTX to DVI Adapter](https://learn.adafruit.com/adafruit-rp2350-22-pin-fpc-hstx-to-dvi-adapter.md)
- [Adafruit Metro RP2350](https://learn.adafruit.com/adafruit-metro-rp2350.md)
- [Adafruit TLV320DAC3100 I2S DAC](https://learn.adafruit.com/adafruit-tlv320dac3100-i2s-dac.md)
- [Using a Keyboard with USB Host](https://learn.adafruit.com/using-a-keyboard-with-usb-host.md)
- [Adafruit Fruit Jam](https://learn.adafruit.com/adafruit-fruit-jam.md)
- [Using DVI Video in CircuitPython](https://learn.adafruit.com/using-dvi-video-in-circuitpython.md)
- [Tile-Matching Game on the Fruit Jam and Metro RP2350](https://learn.adafruit.com/tile-matching-game-on-the-adafruit-metro-rp2350.md)
- [Programming with Scratch 2 or 3 on Raspberry Pi](https://learn.adafruit.com/programming-with-scratch-on-raspberry-pi.md)
- [Create a Memory Game on Fruit Jam Metro RP2350](https://learn.adafruit.com/create-a-memory-game-on-metro-rp2350.md)
- [Snake Game on Metro RP2350](https://learn.adafruit.com/snake-game-on-metro-rp2350.md)
- [Larsio Paint Music](https://learn.adafruit.com/larsio-paint-music.md)
- [Flappy Nyan Cat Game on Fruit Jam and Metro RP2350](https://learn.adafruit.com/flappy-nyan-cat-game-on-metro-rp2350.md)
- [Breakout Game on the Metro RP2350 and Fruit Jam](https://learn.adafruit.com/breakout-game-on-metro-rp2350-and-fruit-jam.md)
- [Windows IoT Core Application Development: Headed Blinky](https://learn.adafruit.com/windows-iot-application-development-headed-blinky.md)
- [Match3 Game on the Fruit Jam and  Adafruit Metro RP2350](https://learn.adafruit.com/match3-game-on-metro-rp2350.md)
- [RGB Matrix Slot Machine](https://learn.adafruit.com/rgb-matrix-slot-machine.md)
- [Adafruit Wiz5500 Ethernet Co-Processor Breakout Board](https://learn.adafruit.com/adafruit-wiz5500-ethernet-co-processor-breakout-board.md)
- [CircuitPython Made Easy on Circuit Playground Express and Bluefruit](https://learn.adafruit.com/circuitpython-made-easy-on-circuit-playground-express.md)
