# NeoTrellis M4 Memory Game

## Overview

![](https://cdn-learn.adafruit.com/assets/assets/000/067/521/medium800thumb/circuitpython_demo.jpg?1544657183)

Matching pairs of tiles is a very common memory game that has appeared at various time in various motifs. It's also a good game for the NeoTrellis. In this context we can match colors on the buttons.

At the beginning of a game, all buttons are dark. The player presses a dark button (pressing lit buttons is ignored) and it's color is shown. Now the player presses another dark button with the goal of matching the color of the first. If they don't match, both go dark once more. If they do match, both buttons flash and turn white. Once all 16 pairs have been found, the game is over and there's a small light show.&nbsp;

There are 6 different colors used, so there are multiple pairs for each color.

There's also a demo mode that runs when the code starts.

This project was inspired by a comment Yannick Mauray made in Discord during an episode of [John Park's Workshop](https://www.youtube.com/watch?v=O15UwMsS0ik&list=PLjF7R1fz_OOVvPjQU_KXsLPVB0pG_kdxW).

## Parts
### Adafruit NeoTrellis M4 with Enclosure and Buttons Kit Pack

[Adafruit NeoTrellis M4 with Enclosure and Buttons Kit Pack](https://www.adafruit.com/product/4020)
So you've got a cool/witty name for your band, a Soundcloud account,&nbsp;[a 3D-printed Daft Punk helmet](https://learn.adafruit.com/3d-printed-daft-punk-helmet-with-bluetooth)...&nbsp;so what could be missing from your road to stardom? The **NeoTrellis M4 Kit...**

In Stock
[Buy Now](https://www.adafruit.com/product/4020)
[Related Guides to the Product](https://learn.adafruit.com/products/4020/guides)
![Demo Video of Hands pressing buttons on lit up NeoTrellis M4.](https://cdn-shop.adafruit.com/product-videos/640x480/4020-00.jpg)

### USB cable - USB A to Micro-B

[USB cable - USB A to Micro-B](https://www.adafruit.com/product/592)
This here is your standard A to micro-B USB cable, for USB 1.1 or 2.0. Perfect for connecting a PC to your Metro, Feather, Raspberry Pi or other dev-board or microcontroller

Approximately 3 feet / 1 meter long

Out of Stock
[Buy Now](https://www.adafruit.com/product/592)
[Related Guides to the Product](https://learn.adafruit.com/products/592/guides)
![USB cable - USB A to Micro-B - 3 foot long](https://cdn-shop.adafruit.com/640x480/592-01.jpg)

### 5V 2.5A Switching Power Supply with 20AWG MicroUSB Cable

[5V 2.5A Switching Power Supply with 20AWG MicroUSB Cable](https://www.adafruit.com/product/1995)
Our all-in-one 5V 2.5 Amp + MicroUSB cable power adapter is the perfect choice for powering single-board computers like Raspberry Pi, BeagleBone, or anything else that's power-hungry!

This adapter was specifically designed to provide 5.25V, not 5V, but we still call it a 5V USB...

In Stock
[Buy Now](https://www.adafruit.com/product/1995)
[Related Guides to the Product](https://learn.adafruit.com/products/1995/guides)
![MicroUSB power supply with bundled cable and U.S. plugs.](https://cdn-shop.adafruit.com/640x480/1995-02.jpg)

# NeoTrellis M4 Memory Game

## Code

![](https://cdn-learn.adafruit.com/assets/assets/000/067/378/medium800/circuitpython_blinka-small.png?1544408961)

We'll be using CircuitPython for this project. Are you new to using CircuitPython? No worries,&nbsp;[there is a full getting started guide here](https://learn.adafruit.com/welcome-to-circuitpython).

Adafruit suggests using the Mu editor to edit your code and have an interactive REPL in CircuitPython.&nbsp;[You can learn about Mu and its installation in this tutorial](https://learn.adafruit.com/welcome-to-circuitpython/installing-mu-editor).

There's a guide to get you up and running with&nbsp;[CircuitPython specifically for the NeoTrellis M4](https://learn.adafruit.com/adafruit-neotrellis-m4/circuitpython). You should read it before starting to get the most recent CircuitPython build for the NeoTrellis M4 installed and running along with the required libraries.

## Setting up your the NeoTrellis

To get your NeoTrellis M4 setup to run this project's code, first follow these steps:

1) Update the&nbsp;[bootloader for NeoTrellis&nbsp;](https://learn.adafruit.com/adafruit-neotrellis-m4/update-bootloader)from the&nbsp;NeoTrellis M4 guide

2) Install the&nbsp;[latest CircuitPython for NeoTrellis&nbsp;](https://learn.adafruit.com/adafruit-neotrellis-m4/circuitpython)from the NeoTrellis M4 guide

For this project you will need the following libraries:

- **adafruit\_trellism4.mpy**
- **neopixel.mpy**
- **adafruit\_matrixkeypad.mpy**

When the libraries and code is installed **CIRCUITPY** should look like:

![](https://cdn-learn.adafruit.com/assets/assets/000/126/249/medium800/gaming_one_one.png?1701121406)

Click Download Project Bundle below to download a Zip file of the code and up to date libraries. Unzip the file and copy the files to your NeoTrellis **CIRCUITPY** drive.

If the game does not play, check your NeoTrellis setup at the top of the page and ensure the three library **.mpy** files are in the **/lib** directory on the **CIRCUITPY** drive.

https://github.com/adafruit/Adafruit_Learning_System_Guides/blob/main/NeoTrellis/NeoTrellis_M4_Memory_Game/code.py

# NeoTrellis M4 Memory Game

## Code Walkthrough

## Setup

As is always the case with a Python program, we start with imports and initialization:

```
import time
import random
import adafruit_trellism4

COLORS = [0xFF0000, 0xFFFF00, 0x00FF00, 0x00FFFF, 0x0000FF, 0xFF00FF]

trellis = adafruit_trellism4.TrellisM4Express(rotation=0)
trellis.pixels.brightness = 0.1
trellis.pixels.fill(0)

pixel_colors = [None] * 32
found_pairs = 0
previously_pressed = set([])
first_pixel = None
```

`COLORS` contains the possible colors for the pairs. Feel free to add to or change these as you desire, but keep in mind that they should be clearly differentiated.

`pixel_colors` holds the assigned color of each pixel on the NeoTrellis,&nbsp;`found_pairs` tracks how many pairs have been found in the current game, `first_pixel` contains the coordinates of the first pixel of a potential pair (and currently lit in its color), None if there isn't one yet. `previously_pressed` keeps track of what buttons were pressed the last time we checked (so we can notice new presses). We'll see how these are used below.&nbsp;

## Helper

There's an `index_of` helper function that converts the&nbsp;`(x, y)` coordinate tuple of a pixel to its NeoPixel index.&nbsp;

```
def index_of(coord):
    x, y = coord
    return y * 8 + x
```

## Completion Splash

Next is a group of functions and generators that together generate a rainbow color splash that happens when all pairs are found.&nbsp; A [page](https://learn.adafruit.com/hacking-ikea-lamps-with-circuit-playground-express/generate-your-colors#wheel-explained) in the [Hacking Ikea Lamps with Circuit Playground Express](https://learn.adafruit.com/hacking-ikea-lamps-with-circuit-playground-express/lamp-it-up)&nbsp;guide describes how this code works.

```
def wheel(pos):
    # Input a value 0 to 255 to get a color value.
    # The colours are a transition r - g - b - back to r.
    if pos &lt; 0 or pos &gt; 255:
        return 0, 0, 0
    if pos &lt; 85:
        return int(255 - pos*3), int(pos*3), 0
    if pos &lt; 170:
        pos -= 85
        return 0, int(255 - pos*3), int(pos*3)
    pos -= 170
    return int(pos * 3), 0, int(255 - (pos*3))

def cycle_sequence(seq):
    while True:
        for elem in seq:
            yield elem

def rainbow_lamp(seq):
    g = cycle_sequence(seq)
    while True:
        trellis.pixels.fill(wheel(next(g)))
        yield

def splash():
    rainbow = rainbow_lamp(range(0, 256, 8))
    for _ in range(64):
        next(rainbow)
        time.sleep(0.005)
```

## Generating Pairs to Find

Now we get into the components of the game itself. The `assign_colors` function assigns random color pairs. It does this by constructing a list of all possible pixel coordinates using a list comprehension. This is the list of pixels remaining unassigned.&nbsp; While it's not empty, we grab a random coordinate and remove it. Then we grab another random coordinate from what's left, and remove it. A random color is picked and applied to the two pixels. This continues until all random pairs of pixels have been assigned.

```
def assign_colors():
    remaining = [(x, y) for x in range(8) for y in range(4)]
    while remaining:
        first = random.choice(remaining)
        remaining.remove(first)
        second = random.choice(remaining)
        remaining.remove(second)
        c = random.choice(COLORS)
        pixel_colors[index_of(first)] = c
        pixel_colors[index_of(second)] = c
```

## Handling Input

The crux of the game happens in response to button presses by the player (or the demo mode).&nbsp; The `handle_key` function handles them and implements the game mechanics. It takes the coordinate tuple of the pixel button that was pressed (`key`), and the current value of the`found_pairs` and `first_pixel` variables we created earlier. The function returns new values for those variables.

The first thing that happens is that the key is checked to see if it's an actual press. If `key` is `None`, then it isn't which causes an immediate return, changing nothing.

Since we now know it's a legitimate key press, its color is checked. `None` means it's part of a pair that has been found already, and so it's ignored.

If we get to this point we know that a valid unmatched key has been pressed. The color of the corresponding NeoTrellis NeoPixel is set to the pixel's color for half a second.

Now we check to see if this is the first pixel of a pair, or a possibly second one. Note that we handle the case of pressing the same key twice.

If this is the second in a possible pair and the colors match, it is noted that the pair of pixels has been matched and both are flashed white 5 times before being set to white. Values are returned indicating that another pair was found, and resetting `first_key` to `None`.

If the colors didn't match, both NeoPixels are turned off and the return values clear `first_pixel`.

Finally if this is the first key of a potential pair, it is assigned to `first_pixel` via the function's return values. The final return handles the case where a pixel from a previously matched pair is pressed: `first_pixel` is cleared.

```
def handle_key(key, _found_pairs, _first_pixel):
    if key is None:
        return _found_pairs, _first_pixel
    key_color = pixel_colors[index_of(key)]
    if key_color is not None:
        trellis.pixels[key] = pixel_colors[index_of(key)]
        time.sleep(0.5)
        if _first_pixel and _first_pixel != key:
            if key_color == pixel_colors[index_of(_first_pixel)]:
                pixel_colors[index_of(_first_pixel)] = None
                pixel_colors[index_of(key)] = None
                for _ in range(5):
                    trellis.pixels[_first_pixel] = 0xFFFFFF
                    trellis.pixels[key] = 0xFFFFFF
                    time.sleep(0.1)
                    trellis.pixels[_first_pixel] = 0x000000
                    trellis.pixels[key] = 0x000000
                    time.sleep(0.1)
                trellis.pixels[_first_pixel] = 0x444444
                trellis.pixels[key] = 0x444444
                return _found_pairs + 1, None
            else:
                trellis.pixels[_first_pixel] = 0x000000
                trellis.pixels[key] = 0x000000
                return _found_pairs, None
        else:
            return _found_pairs, key
    return _found_pairs, None
```

Later, in the main game loop, we'll see lines like

```
found_pairs, first_pixel = handle_key(..., found_pairs, first_pixel)
```

Note that both `found_pairs` and `first_pixel` are global variables. Why not just change them directly in the `handle_key` function? That would be considered somewhat bad form. Globals are best avoided in general, and by default Python assumes you want to create a local variable the first time you assign to something inside a function. You can use the `global` statement to tell the compiler that what you really want to do is to assign to the global variable instead of creating a new local:

```
global first_pixel
```

Even though this is available, it's use is bad form and pylint will complain about it. This technique of returning new values for globals can be a handy way to avoid changing globals from inside a function. Additionally, you can see we've taken the next step of passing the values of those globals into function to avoid directly referencing the globals and completely decoupling&nbsp;`handle_key` from them.

## Detecting Keypresses

The next function compares the currently pressed keys with those that were pressed last time it checked, returning the one that was new since last time. "One that was new" means an arbitrary one that wasn't pressed before but is now. Because we're using Sets for the comparison, the comparison result (set difference) is converted to a List, and the first one in that list is returned, if there is more than one now pressed the one that is returned depends on the Set to List conversion.

Similarly to `handle_key`, `check_for_key` takes the set of keys previously pressed as an argument and returns its new value along with a key pressed (or `None`)

```
def check_for_key(last_pressed):
    now_pressed = set(trellis.pressed_keys)
    new_presses = now_pressed - last_pressed
    if new_presses:
        return now_pressed, list(new_presses)[0]
    return now_pressed, None
```

## Main Game Loop

This is actually a loop within a loop. Each pass through is a game: either demo or playable. The game starts in demo mode. The display is cleared, color pairs assigned, and game variables initialized.

Then the inner loop starts. if left to run to completion it continues until all pairs have been found. What happens in this loop depends on whether it's in demo or play mode.

Play mode is simple, if a button was pressed, it's handled (as detailed above).

Demo mode is a bit more involved. First it picks a random unmatched pixel, handles it, then picks a random pixel of the same color as the first and handles it. That will take care of matching pairs and the NeoPixel manipulation involved. Before each random pick, a keypress is checked for and if there is any, the demo stops and play mode is entered.&nbsp; If the inner loop completes (all 16 pairs matched) a color splash is played. The value of `found_pairs` is checked to see if all were found because the loop can be exited early when a button is pressed in demo mode (to switch into play mode).

```
demo_mode_enabled = True
while True:
    trellis.pixels.fill(0x000000)
    assign_colors()
    found_pairs = 0
    first_pixel = None
    remaining = [(x, y) for x in range(8) for y in range(4)]
    while found_pairs &lt; 16:
        if demo_mode_enabled:
            previously_pressed, key_pressed = check_for_key(previously_pressed)
            if key_pressed:
                demo_mode_enabled = False
                break
            first = random.choice(remaining)
            remaining.remove(first)
            found_pairs, first_pixel = handle_key(first, found_pairs, first_pixel)
            previously_pressed, key_pressed = check_for_key(previously_pressed)
            if key_pressed:
                demo_mode_enabled = False
                break
            c = pixel_colors[index_of(first)]
            match = random.choice([x for x in remaining if pixel_colors[index_of(x)] == c])
            found_pairs, first_pixel = handle_key(match, found_pairs, first_pixel)
            remaining.remove(match)
        else:
            previously_pressed, key_pressed = check_for_key(previously_pressed)
            found_pairs, first_pixel = handle_key(key_pressed, found_pairs, first_pixel)
    if found_pairs == 16:
        splash()
```

That's it.&nbsp; Quite simple code when you look at the individual pieces, but a fairly complex and fun game. One interesting bit is how the same underlying code is used for bot play and demo, differing only by how "pressed" pixels are supplied.&nbsp;


## Featured Products

### Adafruit NeoTrellis M4 with Enclosure and Buttons Kit Pack

[Adafruit NeoTrellis M4 with Enclosure and Buttons Kit Pack](https://www.adafruit.com/product/4020)
So you've got a cool/witty name for your band, a Soundcloud account,&nbsp;[a 3D-printed Daft Punk helmet](https://learn.adafruit.com/3d-printed-daft-punk-helmet-with-bluetooth)...&nbsp;so what could be missing from your road to stardom? The **NeoTrellis M4 Kit...**

In Stock
[Buy Now](https://www.adafruit.com/product/4020)
[Related Guides to the Product](https://learn.adafruit.com/products/4020/guides)
### Adafruit NeoTrellis M4 Mainboard - featuring SAMD51

[Adafruit NeoTrellis M4 Mainboard - featuring SAMD51](https://www.adafruit.com/product/3938)
We got a big ol' blender and tossed in an ItsyBitsy M4, two NeoTrellis boards and an electret mic amp - turned on the 'mix' button and out came the NeoTrellis M4 - a super fun dev board for anyone who likes to squish buttons, see pretty lights and maybe make a tune or...

Out of Stock
[Buy Now](https://www.adafruit.com/product/3938)
[Related Guides to the Product](https://learn.adafruit.com/products/3938/guides)
### NeoTrellis M4 Acrylic Enclosure Kit

[NeoTrellis M4 Acrylic Enclosure Kit](https://www.adafruit.com/product/3963)
So you've got your&nbsp;[Adafruit NeoTrellis M4](https://www.adafruit.com/product/3938), a cool/witty name for your band, a Soundcloud account,&nbsp;[a 3D-printed Daft Punk helmet](https://learn.adafruit.com/3d-printed-daft-punk-helmet-with-bluetooth)...&nbsp;so what...

In Stock
[Buy Now](https://www.adafruit.com/product/3963)
[Related Guides to the Product](https://learn.adafruit.com/products/3963/guides)
### Silicone Elastomer 4x4 Button Keypad - for 3mm LEDs

[Silicone Elastomer 4x4 Button Keypad - for 3mm LEDs](https://www.adafruit.com/product/1611)
So squishy! These silicone elastomer keypads are just waiting for your fingers to press them. Go ahead, squish all you like! (They're durable and easy to clean, just wipe with mild soap and water) These are just like the light up rubber buttons you find on stuff like appliances and tools,...

In Stock
[Buy Now](https://www.adafruit.com/product/1611)
[Related Guides to the Product](https://learn.adafruit.com/products/1611/guides)
### 5V 2.5A Switching Power Supply with 20AWG MicroUSB Cable

[5V 2.5A Switching Power Supply with 20AWG MicroUSB Cable](https://www.adafruit.com/product/1995)
Our all-in-one 5V 2.5 Amp + MicroUSB cable power adapter is the perfect choice for powering single-board computers like Raspberry Pi, BeagleBone, or anything else that's power-hungry!

This adapter was specifically designed to provide 5.25V, not 5V, but we still call it a 5V USB...

In Stock
[Buy Now](https://www.adafruit.com/product/1995)
[Related Guides to the Product](https://learn.adafruit.com/products/1995/guides)
### 5V 2A Switching Power Supply w/ USB-A Connector

[5V 2A Switching Power Supply w/ USB-A Connector](https://www.adafruit.com/product/1994)
Our 5V 2A USB power adapter is the perfect choice for powering single-board computers like Raspberry Pi, BeagleBone, or anything else that's power-hungry!

This adapter was specifically designed to provide 5.25V, not 5V, but we still call it a 5V USB adapter. We did this on purpose to...

In Stock
[Buy Now](https://www.adafruit.com/product/1994)
[Related Guides to the Product](https://learn.adafruit.com/products/1994/guides)
### USB cable - USB A to Micro-B

[USB cable - USB A to Micro-B](https://www.adafruit.com/product/592)
This here is your standard A to micro-B USB cable, for USB 1.1 or 2.0. Perfect for connecting a PC to your Metro, Feather, Raspberry Pi or other dev-board or microcontroller

Approximately 3 feet / 1 meter long

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

## Related Guides

- [Adafruit NeoTrellis M4 Express](https://learn.adafruit.com/adafruit-neotrellis-m4.md)
- [Launch Deck Trellis M4](https://learn.adafruit.com/launch-deck-trellis-m4.md)
- [AdaBox 010](https://learn.adafruit.com/adabox010.md)
- [Wireless NeoPixel Controller](https://learn.adafruit.com/neotrellis-neopixel-controller.md)
- [Trellis M4 Audio Filter Visualizer](https://learn.adafruit.com/trellis-m4-audio-visualizer-and-filter.md)
- [Trellis 3D Printed Enclosure](https://learn.adafruit.com/trellis-3d-printed-enclosure.md)
- [Bringing Back THE VOICE of Speak & Spell](https://learn.adafruit.com/bringing-back-the-voice-of-speak-spell.md)
- [Mini UNTZtrument: 3D Printed MIDI Controller](https://learn.adafruit.com/mini-untztrument-3d-printed-midi-controller.md)
- [Neotrellis M4 Live Launcher](https://learn.adafruit.com/neotrellis-live-launcher.md)
- [iPad Pro Bumper](https://learn.adafruit.com/ipad-pro-bumper.md)
- [Christmas Soundboard with NeoTrellis M4](https://learn.adafruit.com/xmas-sound-board.md)
- [UNTZtrument: a Trellis MIDI Instrument](https://learn.adafruit.com/untztrument-trellis-midi-instrument.md)
- [NeoTrellis Tabletop RPG Soundboard](https://learn.adafruit.com/neotrellis-dungeon-crawl-soundboard.md)
- [Trellis Feather DSP-G1 Synthesizer](https://learn.adafruit.com/feather-trellis-dsp-g1-synthesizer.md)
- [Adafruit microSD Card BFF](https://learn.adafruit.com/adafruit-microsd-card-bff.md)
