Remote Control Snake

An Embedded Systems Take on a Classic Arcade Game
A Project By Alex Zhou and Joshua Castellano


Demonstration Video


Introduction

The basis of the remotely controlled arcade game is a Pi Zero W connected by bluetooth to a Raspberry Pi 3. The Pi Zero W has a keyboard which the player uses to control the game. The Pi Zero W sends the player’s keystrokes to the Raspberry Pi 3 which will can change the snake direction, pause the game, resume the game, or quit the game. The output of the snake game is then drawn on an LED matrix (updated about 4 times a second) for the user to see. The end result is a small, remote controlled snake game with an arcade-like feel provided by the LED matrix.


Generic placeholder image
An initial high-level design for the project

Project Objectives:

  • Establish a bluetooth or other wireless connection between two microcontrollers.
  • Drive an LED matrix display with one of the microcontrollers.
  • Implement the Snake Arcade game on the LED matrix with the following features:
    • A Model M keyboarder controller
    • Simple direction controls
    • Pause and resume functions
    • High score and previous score displays
    • A simple loading screen

Design

Initial Software Development

Our first step was writing the code for the snake game. We chose to use Python because object-oriented programming languages are well-suited to write games and we’ve been using python on our RPis for the whole semester. Initially, we developed two different classes, a Snake class and a Map class. This was our first version of the Snake game. Running the snake.py script yields sample_output.txt.

The game map is a two-dimensional array of blocks, which are represented by integers, where 0 is for empty space, 1 is for the body of the snake, 2 is for a fruit, and 3 is a wall. The growth variable is incremented by two each time the snake eats a fruit. The point (0, 0) is the bottom left block and the point (height - 1, width - 1) is the upper right block. The two-dimensional array is implemented as a one-dimensional array where a two-dimensional point (x, y) in the array is converted to a one-dimensional point with the equation

i = (y * width) + x

Where i is the one-dimensional coordinate. Modeling the game as a two-dimensional array makes it easier to calculate changes in direction and map the array to an LED matrix. In the sample_output.txt example, a height and width of 10 was used, which are variable depending on the arguments to the Map class. When writing the initial code for the snake game, we tried to closely adhere to two principles:

  1. The code must be made sufficiently extensible for further changes.
  2. The output must be trivially mapped to an LED matrix.

These principles were the motivating factors for the two-dimensional array abstraction and the object-oriented basis of the game. After completion of the framework code for the snake game, it became clear that the Snake and Map classes had to be combined. The methods of the Map class heavily relied on the Snake class, so a Snake object had to be passed into most of the methods of the Map class. Since the Map and Snake classes were so closely intertwined, we decided to combine into a single Game class that would have all of their fields and methods. This made it easier to move and draw the snake based on its direction. The code for this updated software can be seen in the Code Appendix under snake_v2.py.

Connecting the LED Matrix to the Raspberry Pi

A 32 x 16 LED matrix, its breakout cable, and its power source were graciously provided to us for this project. Connecting the LED matrix consists of two basic steps:

  1. Chose a suitable library for the LED matrix.
  2. Wire the LED matrix to the Raspberry Pi 3 according to the library

On the Adafruit overview page for setting up the LED matrixes, the first paragraph is a warning stating that the guide only works for certain Adafruit micro controllers, none of which were the Raspberry Pi 3 that we were using. Unfortunately, even after following the link, we didn't see any library suggestions for our microcontroller, so we went hunting for a library ourselves. We quickly found this library that is compatible with all versions of the Raspberry Pi and downloaded it onto our Pi. On the github page for the library, we found a link to wiring instructions for the library. Since the diagram for the connections was laid out by physical pin number location and we didn’t need the PiTFT for this project, we removed the PiTFT (which we had used during the semester) from our Raspberry Pi 3 so we could directly connect the LED matrix and our Pi. Following the diagram under the “Wiring” section, we connected all of the corresponding pins. Since we were only using one LED matrix and therefore one chain, we wired the components only according to the smiley face icons.

LED matrix connections
The completed wiring of the LED matrix and the Raspberry Pi 3.

We then compiled the library examples and ran one, which yielded a test animation of some overlapping squares which confirmed we had wired the LED matrix and compiled the libraries correctly. With the example working, we had proven that we could control the LED matrix with our Raspberry Pi and moved onto the next step of our project.

Connecting the Raspberry Pi 3 to the Raspberry Pi Zero W with Bluetooth

To implement the wireless communication part of the project, we needed another microcontroller. Initially, we considered using another Raspberry Pi 3, however, due to cost considerations, we had to look for a less advanced microcontroller. The Pi Zero W was a good fit. It was less than a third of the cost of a Raspberry Pi 3, and it had bluetooth and wireless capabilities for downloading libraries and communicating with another microcontroller. We considered two methods for a wireless connection: bluetooth and internet. We decided to first try making a connection over bluetooth because the connection would likely have less latency and be easier to implement.

The next step was finding a bluetooth library that both Pis could use. After investigating a number of libraries, the Blue Dot library looked the most promising. We followed the steps exactly as they appear in the link, with the Raspberry Pi 3 as the first Pi and the Pi Zero W as the second Pi (it ended up not mattering which Pi was which, the Pis still get paired). After completing these steps, we were slightly confused by the given examples and neglected to follow the link to the to the Blue Dot API (which would have been helpful). Amidst the confusion, we repeated the initial steps a few times, all of which ended with this error:

Failed to connect: org.bluez.Error.AlreadyExists

Eventually we realized that we had already paired the two Pis, what we now needed was to establish a connection between them. On the Raspberry Pi 3 we wrote the bluetooth_server.py script and on the Pi Zero W we wrote the bluetooth_client.py script. We launched both scripts on their respective Pis and the Raspberry Pi 3 started receiving bluetooth messages from the Pi Zero W. Part of our confusion earlier was because we thought there must have been more steps to connecting two Pis over bluetooth, so we searched for answers that didn’t exist. In the end, it really was that simple, it just takes a minute to follow the steps, then you write your server and client scripts and that’s it.

Final Software Development

With the bluetooth connection established, we had a working proof-of-concept for all of our components, the rest is was software development to combine everything into a snake game. Our first versions of the bluetooth_client.py script used the standard python raw_input command to detect key presses. This was good enough for testing, but became annoying when playing the game, because the user had to press enter to send whatever keystroke they just pressed. We needed a way to directly detect key presses. One of the reasons python is a great language is that there are so many external libraries at your disposal, including the keyboard library, which has functions that can detect when a key is pressed down. To test this library, we wrote a small keyboard test script called keyboard.py, which we used to test the functions later used in our final version of the client script, snake_client.py. We also implemented our own debouncing method in snake_client.py, which is described in the code.

Now we needed to draw the game Map on the LED matrix. Using the examples in the LED matrix library we downloaded, we created our own on C script, draw_led_snake.c, to draw the game on the LED matrix. Since the game is written in python and the LED drawing functions are written in C, we decided it would be easiest if the python script periodically called a C script that would draw a single frame. The draw_led_snake.c script takes in a single string argument that is the flattened two-dimensional map of the game. Since the map in python is an array of values ranging from 0-3, the string sent to the C script is just a long string consisting of 0s, 1s, 2s, and 3s. When the C script is launched with this argument, it converts each digit to a color (0 to nothing, 1 to green, 2 to red, and 3 to blue). When the python script is ready to draw another frame, it kills the previous draw_led_snake.c script that it launched to clear the LED matrix and and launches the same script with a new argument to draw another frame.

To make the game more user-friendly, we added pause and resume functions, a quit button, and a short display that shows the user’s high score on the left and the score for the previous game on the right when the player finishes the game. The display score function is another C script, display_snake_scores.c, which is just a slightly modified example script from the library. To make the overall presentation more appealing, we display the flashing text “Snake” in yellow before and after each game, while the program listens for user input to start playing. This is also implemented by a slightly modified piece of example code found in the library, display_snake_main.c. Both the display_snake_scores.c script and the display_snake_main.c script are called by the main python script of the game, in a similar fashion as the draw_led_snake.c call.

Score Display
The score display shown at the end of the game. The score on the left is the high score and the score on the right is the previous score. The game score is the length of the snake at the end of the game. In this photo, the snake collided with an obstacle when it was 26 pixels long.
Intro Display
The opening display of the game. The text periodically flashes.

The result of all of these modifications to the game was snake_v3.py, which is the version we used in our final demo. With these additions, we had completed our final server and client scripts.

The setup clearly is not ideal, and we discuss the problems and possible improvements in later sections. By the time we had implemented these changes, it was time to present our work. Given more time we could have definitely improved some of the software and maybe even some of the hardware configurations.


Results

Fortunately, we met almost all of the goals we gave ourselves for this project. By the end of the semester, we had a working remote control snake game that was easy to play and had all the features of the classic snake game. Here are some more photos of the finished product.

Gameplay
This is what the game looks like. The snake starts at a length of 4, so this game the snake has eaten just one fruit.
Game in progress
Another shot of the game in progress.
Final Demo
The whole setup during the final demonstration. The keyboard is offscreen to the left.

Our original plans had called for the keyboard to be an IBM Model M keyboard, for an added classic feel. Since it is an older piece of hardware, it draws much more current than modern, flimsier keyboards (about ten times as much). The original design was developed based on the plan for using another Raspberry Pi 3 as the remote controller hooked up to the keyboard. The Raspberry Pi 3 could easily provide the needed current to run the Model M keyboard. However, when we switched to using the Pi Zero W, we could no longer use the Model M keyboard because the Pi Zero W could not provide nearly as much current as the Raspberry Pi 3. After doing the math, we might have exceeded the maximum current limit for the Pi Zero W if we had used the Model M keyboard (according to the given specifications). The current limit specifications are likely conservative, so there is a good chance we could have used the Model M without a problem. However, there is also a good chance that we could have exceeded a hard maximum current limit and fried the Pi Zero W. We decided not to risk an entire microcontroller for pure aesthetics and used a modern keyboard for the duration of the project.

Model M Keyboard
The Model M keyboard that was slated for use in the project. It was replaced by a more modern keyboard over concerns that it would draw too much current.

Conclusions

Overall, we were really glad to have chosen this project! It allowed us to work on something we both cared about (video games) and gave us the reward of having to actually play the game once we finished. We were also very pleased with the look of the game on an LED matrix. In development, we had played the game on the command line where the map was drawn on the standard output. While it was the exact same game, there was something about playing the game on an LED matrix that was much more appealing. If you ever have to opportunity to code simple arcade game such as snake, we highly recommend you use an LED matrix!

Along the way, we realized it’s important to try everything you can think of and not get discouraged by a single bug. Don’t be scared to google the problem and hunt through the forums to find your answer. Even if you don’t solve it immediately, your research might give you some other ideas or you might end up hacking something together from multiple sources. We also learned that you should always make it work first, then make it better. Fortunately, we didn’t go too far down any rabbit holes of that kind to the detriment of the project. Although, we did lose some time trying to improve the frame animation for the LED matrix when the frames weren’t being drawn as smoothly as we would have hoped. Our attempted solution using FIFO files is snake_v4.py, which was a failed attempt to improve the animation. After these attempts, we decided to abandon our efforts as this was merely an improvement on an existing feature. Instead of improving the frame drawing mechanisms, we added some other necessary features such as the “Snake” text display and the high and current score displays (these improvements were a part of our software solution used in the final demo, snake_v3.py). If we had tried to improve our animation, we probably would have run out of time.


Possible Improvements

While we were pleased with our final product, there are definitely improvements that can be made.

While playing the game, there is a noticeable gap between each frame. This is because the python script has to kill the C process running the previous frame and call another C process with new frame data. The killing and calling are system calls, which require a context switch to the kernel. This context switching between the kernel and our program takes much longer than normal program execution, which is probably at least partly why we see this gap.

Gap Frame
This picture captures the noticeable gap between frames and was taken with a phone camera, whose normal shutter speeds would not be able capture gaps in properly implemented LED matrix frame animations. Notice the lingering or emerging pixels of the incomplete frame.

There are a few solutions to this problem:

Our game has a quit button which will exit both the client process running on the Pi Zero W and the server process running on the Raspberry Pi 3. To restart the game, both Pis must be completely rebooted. To rerun the game without a reboot, you would have to login to both Pis and manually launch the game scripts, which would require mice, keyboards, or even monitors. However, this is an inconvenience because we want to make this project completely embedded, meaning that any I/O devices (except for the keyboard which is needed) such as mice or monitors should be excluded if possible. The solution would be to make the quit button to not only end the game but also shutdown the machines. This way when we want to play again, we don’t have to plug in monitors or ssh into either Pi so safely shut them down, if the quit button shuts both Pis down, we can simply power cycle both Pis to restart the game.

Fortunately or unfortunately (depending on your perspective), we ran out of time before we could implement these improvements because we made sure to adhere to an important guiding principle: make it work first, then make it better.


Work Distribution

Josh

jvc56@cornell.edu

Worked on the initial software development, establishing a bluetooth connection, final software development additions such as the intro display, score display, and attempted improvements with FIFO files.

Alex

zz642@cornell.edu

Worked on the initial software development, assembling the hardware, wiring the LED matrix and ensuring its correctness, and debugging the snake client script.


Parts List

Total: $81.00


References

Overview for the 32 x 16 LED Matrix
LED Matrix C Library
Wiring Guide for the 32 x 16 LED Matrix
BlueDot Library
BlueDot API
Power Limitations for the Raspberry Pi
Python Keyboard Module

Code Appendix

bluetooth_client.py


from bluedot.btcomm import BluetoothClient
import time

# This is the mac address of our Raspberry Pi 3
# If you were to use this code you would have to
# replace this line with your own device's mac address
c = BluetoothClient("B8:27:EB:8F:33:C7", data_received)

while True:
  c.send('test')
  time.sleep(1)


              

bluetooth_server.py


from bluedot.btcomm import BluetoothServer
from signal import pause


# It really is this simple

def data_received(data):
    print (data)

s = BluetoothServer(data_received)
pause()

              

keyboard.py


import keyboard

# A simple script to test the
# python keyboard module

last_key_pressed = ""

while True:
    try:
        if keyboard.is_pressed('a') and not last_key_pressed == 'a':
            print ('You pressed the a key')
            last_key_pressed = 'a'
        else:
            pass
    except:
        break


              

snake_client.py


from bluedot.btcomm import BluetoothClient
import keyboard


# This is the bluetooth client script
# that runs on the Raspberry Pi Zero W
# It sends the user keystrokes to the Raspberry
# Pi 3 for processing

def data_received(data):
        print(data)

c = BluetoothClient("B8:27:EB:8F:33:C7", data_received)

# We declare a 'last_key' variable for debouncing the input
# We are constantly running in a while loop, only checking
# if each key is pressed. When a human strikes a key, hundreds
# of keypresses will be registered. To prevent sending hundreds
# of requests to the server when we really only want one, we set
# the last_key variable to whatever we last sent, and before sending
# it again, we check if it was the last_key. If it is, we don't send it
# In this way we only send a keystroke if it was different from the last
# keystroke

last_key = ""

while True:
  try:
    # Directional keys
    if keyboard.is_pressed('w') and last_key != 'w':
      last_key = 'w'
      c.send('w')
    elif keyboard.is_pressed('a') and last_key != 'a':
      last_key = 'a'
      c.send('a')
    elif keyboard.is_pressed('s') and last_key != 's':
      last_key = 's'
      c.send('s')
    elif keyboard.is_pressed('d') and last_key != 'd':
      last_key = 'd'
      c.send('d')
    # Pause the game
    elif keyboard.is_pressed('p') and last_key != 'p':
      last_key = 'p'
      c.send('p')
    # Resume the game
    elif keyboard.is_pressed('o') and last_key != 'o':
      last_key = 'o'
      c.send('o')
    # Quit the game
    elif keyboard.is_pressed('q') and last_key != 'q':
      last_key = 'q'
      c.send('q')
      exit(0)
    # Start the game
    elif keyboard.is_pressed('g') and last_key != 'g':
      last_key = 'g'
      c.send('g')
    else:
        pass
  except  Exception as e:
      break




              

snake.py


import random
import time, threading

# This is the bluetooth server script that
# handles the snake game. It receives messages
# from the client, processes them, and launches
# a C script to draw the Map on the LED matrix
# based on it's internal state

class Snake:

    # starting_direction is
    #   0
    # 3 * 1
    #   2
    #
  def __init__(self, length, starting_location):

    x = starting_location[0]
    y = starting_location[1]
    loc = [starting_location]
    for i in range(length-1):
      y -= 1
      loc.append([x, y])

    # Length of the snake
    self.length    = length
    # An array that stores the
    # body of the snake, each
    # block of the snake is a
    # coordinate pair (x, y)
    self.location  = loc
    # Direction of the snake
      # where 0 is up, 1 is right,
      # 2 is down, and 3 is left 
      #   0
      # 3 * 1
      #   2
    self.direction = 0
    # How much the snake should
    # grow. Increments by 2 for
    # every fruit eaten
    self.growth    = 0
    # Boolean that inidicates
    # the snake has collided with
    # something, either a wall
    # or itself
    self.collided  = False

  # Changes the current direction of the snake
  # Does not allow the snake to turn back on itself
  def changeDirection(self, new_direction):
    if not (self.direction % 2 == new_direction % 2):
      self.direction = new_direction
      print "Direction changed to " + str(new_direction)

  # Advances the snake one unit, depending on
  # its direction. We do this by adding an element
  # on the head of the array that represents the snake
  # and pop an element off at the end. If the growth > 0
  # we skip the popping part, so the length of the snake
  # increases by 1. We also check for a self-collision
  def move(self):
    head = self.location[0]
    neck = self.location[1]

    if (self.direction == 0):
      next_head = [head[0], head[1]+1]
    elif (self.direction == 1):
      next_head = [head[0]+1, head[1]]
    elif (self.direction == 2):
      next_head = [head[0], head[1]-1]
    elif (self.direction == 3):
      next_head = [head[0]-1, head[1]]

    self.location.insert(0, next_head)
    if (self.growth > 0):
      self.growth -= 1
      self.length += 1
    else:
      self.location.pop()

    print "Snake location: " + str(self.location)
    print "growth: " + str(self.growth)
    for i in range(1, self.length):
      if (next_head[0] == self.location[i][0] and next_head[1] == self.location[i][1]):
        self.collided =  True


class Map:

  def __init__(self, width, height):

        # 0 - nothing
        # 1 - snake body
        # 2 - fruit
        # 3 - wall

    self.width = width
    self.height = height
    self.grid = [0] * (width * height)
    # We initialize all points to 0, an empty space
    # Except for the walls which are designated as 3
    for i in range(height):
      for j in range(width):
        if (i == 0 or i == height - 1 or j == 0 or j == width - 1):
          self.grid[i*width + j] = 3

  # Remove the snake from the Map
  def removeSnake(self):
    for i in range(self.width * self.height):
      if (self.grid[i] == 1):
        self.grid[i] = 0

  # Draw the snake on the Map, checking for
  # Collisions with the walls
  def drawSnake(self, snake):
    ate_fruit = False
    hit_wall  = False
    for i in range(snake.length):
      co = snake.location[i]
      if (self.grid[co[0] + co[1]*self.width] == 3):
        hit_wall = True
      if (self.grid[co[0] + co[1]*self.width] == 2):
        snake.growth += 2
        ate_fruit = True
      self.grid[co[0] + co[1]*self.width] = 1

    if (ate_fruit):
      self.spawnFruit(snake)

    snake.collided = hit_wall

  # Spawn a fruit in a random spot
  # This function is called when the game
  # Starts and everytime a fruit is eaten
  def spawnFruit(self, snake):
    fruit_loc = random.randint(0, self.width * self.height - 1)
    while (self.grid[fruit_loc] == 1):
      fruit_loc = random.randint(0, self.width * self.height - 1)
    self.grid[fruit_loc] = 2

  # Convert the map into a string
  # We send this string in a bluetooth message
  # to the C script that will draw the Map
  # on the LED matrix
  def toString(self):
    return_string = ""
    for i in range(self.height):
      for j in range(self.width):
        return_string += str((self.grid[self.width * (self.height - i - 1) + j])) + " "
      return_string += "\n"
    return return_string

# Function to listen for user input
# This runs in a separate thread that
# interrupts the main thread of the game
# to change the snake's direction
def listen(snake):
  while (not snake.collided):
    user_input = raw_input()
    if (user_input == "w"):
      snake.changeDirection(0)
    elif (user_input == "d"):
      snake.changeDirection(1)
    elif (user_input == "s"):
      snake.changeDirection(2)
    elif (user_input == "a"):
      snake.changeDirection(3)
    elif (user_input == "q"):
      exit(0)

# The main thread of the game
# While the snake hasn't collided yet
# continuing moving and drawing it
if __name__ == "__main__":

  print "Starting snake!"

  snake = Snake(4, [5, 5])
  gamemap = Map(10, 10)
  gamemap.spawnFruit(snake)
  threading.Timer(0, listen, args=(snake,)).start()
  while not snake.collided:
    time.sleep(1)
    gamemap.removeSnake()
    snake.move()
    gamemap.drawSnake(snake)
    print gamemap.toString()
  print "You died!"


              

sample_output.txt


Starting snake!
Snake location: [[5, 6], [5, 5], [5, 4], [5, 3]]
growth: 0
3 3 3 3 3 3 3 3 3 3 
3 2 0 0 0 0 0 0 0 3 
3 0 0 0 0 0 0 0 0 3 
3 0 0 0 0 1 0 0 0 3 
3 0 0 0 0 1 0 0 0 3 
3 0 0 0 0 1 0 0 0 3 
3 0 0 0 0 1 0 0 0 3 
3 0 0 0 0 0 0 0 0 3 
3 0 0 0 0 0 0 0 0 3 
3 3 3 3 3 3 3 3 3 3 

Snake location: [[5, 7], [5, 6], [5, 5], [5, 4]]
growth: 0
3 3 3 3 3 3 3 3 3 3 
3 2 0 0 0 0 0 0 0 3 
3 0 0 0 0 1 0 0 0 3 
3 0 0 0 0 1 0 0 0 3 
3 0 0 0 0 1 0 0 0 3 
3 0 0 0 0 1 0 0 0 3 
3 0 0 0 0 0 0 0 0 3 
3 0 0 0 0 0 0 0 0 3 
3 0 0 0 0 0 0 0 0 3 
3 3 3 3 3 3 3 3 3 3 

d
Direction changed to 1
Snake location: [[6, 7], [5, 7], [5, 6], [5, 5]]
growth: 0
3 3 3 3 3 3 3 3 3 3 
3 2 0 0 0 0 0 0 0 3 
3 0 0 0 0 1 1 0 0 3 
3 0 0 0 0 1 0 0 0 3 
3 0 0 0 0 1 0 0 0 3 
3 0 0 0 0 0 0 0 0 3 
3 0 0 0 0 0 0 0 0 3 
3 0 0 0 0 0 0 0 0 3 
3 0 0 0 0 0 0 0 0 3 
3 3 3 3 3 3 3 3 3 3 

sSnake location: [[7, 7], [6, 7], [5, 7], [5, 6]]
growth: 0
3 3 3 3 3 3 3 3 3 3 
3 2 0 0 0 0 0 0 0 3 
3 0 0 0 0 1 1 1 0 3 
3 0 0 0 0 1 0 0 0 3 
3 0 0 0 0 0 0 0 0 3 
3 0 0 0 0 0 0 0 0 3 
3 0 0 0 0 0 0 0 0 3 
3 0 0 0 0 0 0 0 0 3 
3 0 0 0 0 0 0 0 0 3 
3 3 3 3 3 3 3 3 3 3 


Direction changed to 2
Snake location: [[7, 6], [7, 7], [6, 7], [5, 7]]
growth: 0
3 3 3 3 3 3 3 3 3 3 
3 2 0 0 0 0 0 0 0 3 
3 0 0 0 0 1 1 1 0 3 
3 0 0 0 0 0 0 1 0 3 
3 0 0 0 0 0 0 0 0 3 
3 0 0 0 0 0 0 0 0 3 
3 0 0 0 0 0 0 0 0 3 
3 0 0 0 0 0 0 0 0 3 
3 0 0 0 0 0 0 0 0 3 
3 3 3 3 3 3 3 3 3 3 

Snake location: [[7, 5], [7, 6], [7, 7], [6, 7]]
growth: 0
3 3 3 3 3 3 3 3 3 3 
3 2 0 0 0 0 0 0 0 3 
3 0 0 0 0 0 1 1 0 3 
3 0 0 0 0 0 0 1 0 3 
3 0 0 0 0 0 0 1 0 3 
3 0 0 0 0 0 0 0 0 3 
3 0 0 0 0 0 0 0 0 3 
3 0 0 0 0 0 0 0 0 3 
3 0 0 0 0 0 0 0 0 3 
3 3 3 3 3 3 3 3 3 3 

              

snake_v2.py


import random
import time, threading

# This is basically the same as snake.py
# except the Snake and Map classes are
# combined into a single Game class

class Game:


  def __init__(self, length, starting_location, width, height):

    x = starting_location[0]
    y = starting_location[1]
    loc = [starting_location]
    for i in range(length-1):
      y -= 1
      loc.append([x, y])

    self.length    = length
    self.location  = loc
    self.direction = 0
    self.growth    = 0
    self.collided  = False
    self.quit      = False
    self.paused    = False
    self.debug     = False
        # 0 - nothing
        # 1 - snake body
        # 2 - fruit
        # 3 - wall

    self.width = width
    self.height = height
    self.grid = [0] * (width * height)
    
    for i in range(height):
      for j in range(width):
        if (i == 0 or i == height - 1 or j == 0 or j == width - 1):
          self.grid[i*width + j] = 3

  def changeDirection(self, new_direction):
    if not (self.direction % 2 == new_direction % 2):
      self.direction = new_direction
      if (self.debug): print "Direction changed to " + str(new_direction)

  def moveSnake(self):
    head = self.location[0]
    neck = self.location[1]

    if (self.direction == 0):
      next_head = [head[0], head[1]+1]
    elif (self.direction == 1):
      next_head = [head[0]+1, head[1]]
    elif (self.direction == 2):
      next_head = [head[0], head[1]-1]
    elif (self.direction == 3):
      next_head = [head[0]-1, head[1]]

    self.location.insert(0, next_head)
    if (self.growth > 0):
      self.growth -= 1
      self.length += 1
    else:
      self.location.pop()

    if (self.debug): print "Snake location: " + str(self.location)
    if (self.debug): print "growth: " + str(self.growth)
    for i in range(1, self.length):
      if (next_head[0] == self.location[i][0] and next_head[1] == self.location[i][1]):
        self.collided =  True


  def removeSnake(self):
    for i in range(self.width * self.height):
      if (self.grid[i] == 1):
        self.grid[i] = 0

  def drawSnake(self):
    ate_fruit = False
    for i in range(self.length):
      co = self.location[i]
      if (self.grid[co[0] + co[1]*self.width] == 3):
        self.collided  = True
      if (self.grid[co[0] + co[1]*self.width] == 2):
        self.growth += 2
        ate_fruit = True
      self.grid[co[0] + co[1]*self.width] = 1

    if (ate_fruit):
      self.spawnFruit()


  def spawnFruit(self):
    fruit_loc = random.randint(0, self.width * self.height - 1)
    while (self.grid[fruit_loc] != 0):
      fruit_loc = random.randint(0, self.width * self.height - 1)
    self.grid[fruit_loc] = 2

  def toString(self):
    return_string = ""
    for i in range(self.height):
      for j in range(self.width):
        return_string += str((self.grid[self.width * (self.height - i - 1) + j])) + " "
      return_string += "\n"
    return return_string


def listen(game):
  while (not game.collided):
    user_input = raw_input()
    if (user_input == "w"):
      game.changeDirection(0)
    elif (user_input == "d"):
      game.changeDirection(1)
    elif (user_input == "s"):
      game.changeDirection(2)
    elif (user_input == "a"):
      game.changeDirection(3)
    elif (user_input == "p"):
      game.paused = not game.paused
      if (game.paused):
        if (self.debug): print "Paused"
      else:
        if (self.debug): print "Resumed"
    elif (user_input == "q"):
      game.quit = True
      exit(0)

if __name__ == "__main__":

  print "Starting snake!"

  game = Game(4, [5, 5], 20, 20)
  game.spawnFruit()
  threading.Timer(0, listen, args=(game,)).start()
  while not game.collided and not game.quit:
    time.sleep(.15)
    if (not game.paused):
      game.removeSnake()
      game.moveSnake()
      game.drawSnake()
      print game.toString()

  if (game.quit):
    print "Quit"
  else:
    print "You died!"


              

snake_v3.py


import random
import time, threading
from bluedot.btcomm import BluetoothServer
import os
import signal
import subprocess
import datetime

# The full working version of our snake server


# Here we declared global high score and current
# score variables
high_score = 0
current_score = 0

# The game implementation is the same as the previous version
# The changes here are in the main function where we now
# send the output of the game to another C script

class Game:


  def __init__(self, length, starting_location, width, height):

    x = starting_location[0]
    y = starting_location[1]
    loc = [starting_location]
    for i in range(length-1):
      y -= 1
      loc.append([x, y])

    self.length    = length
    self.location  = loc
    self.direction = 0
    self.growth    = 0
    self.collided  = False
    self.quit      = False
                self.started    = False
    self.paused    = False
    self.debug     = False
                current_score  = 0
        # 0 - nothing
        # 1 - snake body
        # 2 - fruit
        # 3 - wall

    self.width = width
    self.height = height
    self.grid = [0] * (width * height)
    
    for i in range(height):
      for j in range(width):
        if (i == 0 or i == height - 1 or j == 0 or j == width - 1):
          self.grid[i*width + j] = 3

  def changeDirection(self, new_direction):
    if not (self.direction % 2 == new_direction % 2):
      self.direction = new_direction
      if (self.debug): print "Direction changed to " + str(new_direction)

  def moveSnake(self):
    head = self.location[0]
    neck = self.location[1]

    if (self.direction == 0):
      next_head = [head[0], head[1]+1]
    elif (self.direction == 1):
      next_head = [head[0]+1, head[1]]
    elif (self.direction == 2):
      next_head = [head[0], head[1]-1]
    elif (self.direction == 3):
      next_head = [head[0]-1, head[1]]

    self.location.insert(0, next_head)
    if (self.growth > 0):
      self.growth -= 1
      self.length += 1
    else:
      self.location.pop()

    if (self.debug): print "Snake location: " + str(self.location)
    if (self.debug): print "growth: " + str(self.growth)
    for i in range(1, self.length):
      if (next_head[0] == self.location[i][0] and next_head[1] == self.location[i][1]):
        self.collided =  True


  def removeSnake(self):
    for i in range(self.width * self.height):
      if (self.grid[i] == 1):
        self.grid[i] = 0

  def drawSnake(self):
    ate_fruit = False
    for i in range(self.length):
      co = self.location[i]
      if (self.grid[co[0] + co[1]*self.width] == 3):
        self.collided  = True
      if (self.grid[co[0] + co[1]*self.width] == 2):
        self.growth += 2
        ate_fruit = True
      self.grid[co[0] + co[1]*self.width] = 1

    if (ate_fruit):
      self.spawnFruit()


  def spawnFruit(self):
    fruit_loc = random.randint(0, self.width * self.height - 1)
    while (self.grid[fruit_loc] != 0):
      fruit_loc = random.randint(0, self.width * self.height - 1)
    self.grid[fruit_loc] = 2

  def toString(self):
    return_string = ""
    for i in range(self.height):
      for j in range(self.width):
        return_string += str((self.grid[self.width * (self.height - i - 1) + j]))
    return return_string

def data_received(data):
    global game
    if (data == "w"):
  game.changeDirection(0)
    elif (data == "d"):
  game.changeDirection(1)
    elif (data == "s"):
  game.changeDirection(2)
    elif (data == "a"):
  game.changeDirection(3)
    elif (data == "p"):
  game.paused = True
    elif (data == "o"):
        game.paused = False
    elif (data == "g"):
        game.started = True
    elif (data == "q"):
  game.quit = True
  exit(0)


# Draw a single frame of the Snake game
# This means we have to kill the processes
# displaying the previous frame and start a
# new process with a new argument
def led_draw(data):
    sub_cmd = '"kill "'
    cmd = "ps axf | grep draw_led_snake | grep -v grep | awk '{print " + sub_cmd + "  $1}' | sh" 
    os.system(cmd)
    subprocess.Popen(["./display_code/rpi-rgb-led-matrix/examples-api-use/draw_led_snake", "--led-no-hardware-pulse", data, "--led-rows=16"])

# Display the high and current scores
def display_scores():

    os.system("./display_code/rpi-rgb-led-matrix/examples-api-use/display_snake_scores -f ./display_code/rpi-rgb-led-matrix/fonts/6x13.bdf --led-no-hardware-pulse --led-rows=16 '" + str(high_score) + " " + str(current_score) + "'")


if __name__ == "__main__":

  # The print statements are only for debugging
  # The user will never see them

  # Log startup for debugging
    up = open("server.log", 'a')
    up.write(str(datetime.datetime.now()) + "\n") 
    up.close()
  print "Press g to start"
    quit_flag = False
    s = BluetoothServer(data_received)
    while not quit_flag: # If the player hasn't quit, start a new game as soon as a game finishes
      main_screen_on = True
      print "Another game"
      game = Game(4, [5, 5], 32, 16)
      game.spawnFruit()
      while not game.collided and not game.quit:
    if (not game.paused and game.started):
      game.removeSnake()
      game.moveSnake()
      game.drawSnake()
            print "Drawing"
        led_draw(game.toString())
            time.sleep(0.15)
            current_score = game.length
            else: # If there is no game running, display the opening screen
                os.system("./display_code/rpi-rgb-led-matrix/examples-api-use/display_snake_main -f ./display_code/rpi-rgb-led-matrix/fonts/6x13.bdf --led-no-hardware-pulse --led-rows=16 'Snake' ")
                time.sleep(.1)

      quit_flag = game.quit
      if (game.quit):
    print "Quit"
            time.sleep(1)
      else:
    print "You died!"
            time.sleep(1)
            if (current_score > high_score):
                high_score = current_score
            display_scores()


              

snake_v4.py


import random
import time, threading
from bluedot.btcomm import BluetoothServer
import os
import signal
import subprocess
import posix

# A failed attempt at improving
# snake_v3.py, the differences are in main

high_score = 0
current_score = 0


class Game:


  def __init__(self, length, starting_location, width, height):

    x = starting_location[0]
    y = starting_location[1]
    loc = [starting_location]
    for i in range(length-1):
      y -= 1
      loc.append([x, y])

    self.length    = length
    self.location  = loc
    self.direction = 0
    self.growth    = 0
    self.collided  = False
    self.quit      = False
                self.started    = False
    self.paused    = False
    self.debug     = False
                current_score  = 0
        # 0 - nothing
        # 1 - snake body
        # 2 - fruit
        # 3 - wall

    self.width = width
    self.height = height
    self.grid = [0] * (width * height)
    
    for i in range(height):
      for j in range(width):
        if (i == 0 or i == height - 1 or j == 0 or j == width - 1):
          self.grid[i*width + j] = 3

  def changeDirection(self, new_direction):
    if not (self.direction % 2 == new_direction % 2):
      self.direction = new_direction
      if (self.debug): print "Direction changed to " + str(new_direction)

  def moveSnake(self):
    head = self.location[0]
    neck = self.location[1]

    if (self.direction == 0):
      next_head = [head[0], head[1]+1]
    elif (self.direction == 1):
      next_head = [head[0]+1, head[1]]
    elif (self.direction == 2):
      next_head = [head[0], head[1]-1]
    elif (self.direction == 3):
      next_head = [head[0]-1, head[1]]

    self.location.insert(0, next_head)
    if (self.growth > 0):
      self.growth -= 1
      self.length += 1
    else:
      self.location.pop()

    if (self.debug): print "Snake location: " + str(self.location)
    if (self.debug): print "growth: " + str(self.growth)
    for i in range(1, self.length):
      if (next_head[0] == self.location[i][0] and next_head[1] == self.location[i][1]):
        self.collided =  True


  def removeSnake(self):
    for i in range(self.width * self.height):
      if (self.grid[i] == 1):
        self.grid[i] = 0

  def drawSnake(self):
    ate_fruit = False
    for i in range(self.length):
      co = self.location[i]
      if (self.grid[co[0] + co[1]*self.width] == 3):
        self.collided  = True
      if (self.grid[co[0] + co[1]*self.width] == 2):
        self.growth += 2
        ate_fruit = True
      self.grid[co[0] + co[1]*self.width] = 1

    if (ate_fruit):
      self.spawnFruit()


  def spawnFruit(self):
    fruit_loc = random.randint(0, self.width * self.height - 1)
    while (self.grid[fruit_loc] != 0):
      fruit_loc = random.randint(0, self.width * self.height - 1)
    self.grid[fruit_loc] = 2

  def toString(self):
    return_string = ""
    for i in range(self.height):
      for j in range(self.width):
        return_string += str((self.grid[self.width * (self.height - i - 1) + j]))
    return return_string

def data_received(data):
    global game
    if (data == "w"):
  game.changeDirection(0)
    elif (data == "d"):
  game.changeDirection(1)
    elif (data == "s"):
  game.changeDirection(2)
    elif (data == "a"):
  game.changeDirection(3)
    elif (data == "p"):
  game.paused = True
    elif (data == "o"):
        game.paused = False
    elif (data == "g"):
        game.started = True
    elif (data == "q"):
  game.quit = True
  exit(0)

def led_listener():
          print "Launching c script"
          subprocess.Popen(["./display_code/rpi-rgb-led-matrix/examples-api-use/draw_led_snake_v2", "--led-no-hardware-pulse", "--led-rows=16"])
def led_draw(data):
    os.system("cat " + data + " < " + fifo_name)
    #f.flush()

def display_scores():

    os.system("./display_code/rpi-rgb-led-matrix/examples-api-use/display_snake_scores -f ./display_code/rpi-rgb-led-matrix/fonts/6x13.bdf --led-no-hardware-pulse --led-rows=16 '" + str(high_score) + " " + str(current_score) + "'")

if __name__ == "__main__":

  print "Press g to start"
        quit_flag = False
        s = BluetoothServer(data_received)
        while not quit_flag:
          print "Another game"
          game = Game(4, [5, 5], 32, 16)
          game.spawnFruit()

          # Here we attempt to use fifo files to
          # communicate between scripts
          # Unfortunately, the blocking on
          # opening a fifo file proved problematic
          # the line that opens the fifo file was
          # removed at some point
          fifo_name = "fifo"
          os.system("sudo rm " + fifo_name)
          os.mkfifo(fifo_name)
          print "Made fifo"
          threading.Timer(3, led_listener).start()
        led_draw(game.toString())
          print "Wrote to fifo"
          while not game.collided and not game.quit:
        if (not game.paused and game.started):
          game.removeSnake()
          game.moveSnake()
          game.drawSnake()
                        print "Drawing"
                led_draw(game.toString())
                        time.sleep(0.15)
                        current_score = game.length
                else:
                    print "Waiting for g"
                    time.sleep(.2)
    
          sub_cmd = '"kill "'
          cmd = "ps axf | grep draw_led_snake | grep -v grep | awk '{print " + sub_cmd + "  $1}' | sh" 
          os.system(cmd)
          quit_flag = game.quit
          if (game.quit):
        print "Quit"
          else:
        print "You died!"
                if (current_score > high_score):
                    high_score = current_score
                display_scores()


              

draw_led_snake.c


/* -*- mode: c; c-basic-offset: 2; indent-tabs-mode: nil; -*-
 *
 * Using the C-API of this library.
 *
 */
#include "led-matrix-c.h"

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>

static volatile int keepRunning = 1;

void intHandler(int dummy)
{
  // Do nothing
  fprintf(stderr, "There was an interrupt");
}

int main(int argc, char **argv) {

  signal (SIGINT, intHandler);

  struct RGBLedMatrixOptions options;
  struct RGBLedMatrix *matrix;
  struct LedCanvas *offscreen_canvas;
  struct LedCanvas *offscreen_canvas2;
  int width, height;
  int x, y;

  char *array_string = argv[2];
  fprintf(stderr, "%s  ", array_string);

  memset(&options, 0, sizeof(options));
  options.rows = 32;
  options.chain_length = 1;

  /* This supports all the led commandline options. Try --led-help */
  matrix = led_matrix_create_from_options(&options, &argc, &argv);
  if (matrix == NULL)
    return 1;

  /* Let's do an example with double-buffering. We create one extra
   * buffer onto which we draw, which is then swapped on each refresh.
   * This is typically a good aproach for animations and such.
   */
  offscreen_canvas = led_matrix_create_offscreen_canvas(matrix);
  offscreen_canvas2 = led_matrix_create_offscreen_canvas(matrix);

  led_canvas_get_size(offscreen_canvas, &width, &height);

  fprintf(stderr, "Size: %dx%d. Hardware gpio mapping: %s\n",
          width, height, options.hardware_mapping);

  for (y = 0; y < height; ++y)
  {
    for (x = 0; x < width; ++x)
    {
  int RED   = 0;
  int GREEN = 0;
  int BLUE  = 0;
  char c = array_string[y*width + x];
  if (c == '1')
  {
    GREEN = 255;
  }
  else if (c == '2')
  {
    RED = 255;
  }
  else if (c == '3')
  {
    BLUE = 255;
  }
        led_canvas_set_pixel(offscreen_canvas, x, y, RED, GREEN, BLUE);
      }
  }

  /* Now, we swap the canvas. We give swap_on_vsync the buffer we
   * just have drawn into, and wait until the next vsync happens.
   * we get back the unused buffer to which we'll draw in the next
   * iteration.
   */
  led_canvas_fill(offscreen_canvas2, 0, 0, 0);
  offscreen_canvas2 = led_matrix_swap_on_vsync(matrix, offscreen_canvas2);
  offscreen_canvas = led_matrix_swap_on_vsync(matrix, offscreen_canvas);
  
  sleep(1);
  //pause();  
  /*
   * Make sure to always call led_matrix_delete() in the end to reset the
   * display. Installing signal handlers for defined exit is a good idea.
   */
  led_matrix_delete(matrix);

  return 0;
}

              

display_snake_scores.c


// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
// Small example how write text.
//
// This code is public domain
// (but note, that the led-matrix library this depends on is GPL v2)

#include "led-matrix.h"
#include "graphics.h"

#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

using namespace rgb_matrix;

static int usage(const char *progname) {
  fprintf(stderr, "usage: %s [options]\n", progname);
  fprintf(stderr, "Reads text from stdin and displays it. "
          "Empty string: clear screen\n");
  fprintf(stderr, "Options:\n");
  rgb_matrix::PrintMatrixFlags(stderr);
  fprintf(stderr,
          "\t-f <font-file>    : Use given font.\n"
          "\t-b <brightness>   : Sets brightness percent. Default: 100.\n"
          "\t-x <x-origin>     : X-Origin of displaying text (Default: 0)\n"
          "\t-y <y-origin>     : Y-Origin of displaying text (Default: 0)\n"
          "\t-S <spacing>      : Spacing pixels between letters (Default: 0)\n"
          "\t-C <r,g,b>        : Color. Default 255,255,0\n"
          "\t-B <r,g,b>        : Background-Color. Default 0,0,0\n"
          "\t-O <r,g,b>        : Outline-Color, e.g. to increase contrast.\n"
          );
  return 1;
}

static bool parseColor(Color *c, const char *str) {
  return sscanf(str, "%hhu,%hhu,%hhu", &c->r, &c->g, &c->b) == 3;
}

static bool FullSaturation(const Color &c) {
    return (c.r == 0 || c.r == 255)
        && (c.g == 0 || c.g == 255)
        && (c.b == 0 || c.b == 255);
}

int main(int argc, char *argv[]) {
  RGBMatrix::Options matrix_options;
  rgb_matrix::RuntimeOptions runtime_opt;
  if (!rgb_matrix::ParseOptionsFromFlags(&argc, &argv,
                                         &matrix_options, &runtime_opt)) {
    return usage(argv[0]);
  }

  char *message = argv[5];

  Color color(255, 255, 0);
  Color bg_color(0, 0, 0);
  Color outline_color(0,0,0);
  bool with_outline = false;

  const char *bdf_font_file = NULL;
  int x_orig = 0;
  int y_orig = 0;
  int brightness = 100;
  int letter_spacing = 0;

  int opt;
  while ((opt = getopt(argc, argv, "x:y:f:C:B:O:b:S:")) != -1) {
    switch (opt) {
    case 'b': brightness = atoi(optarg); break;
    case 'x': x_orig = atoi(optarg); break;
    case 'y': y_orig = atoi(optarg); break;
    case 'f': bdf_font_file = strdup(optarg); break;
    case 'S': letter_spacing = atoi(optarg); break;
    case 'C':
      if (!parseColor(&color, optarg)) {
        fprintf(stderr, "Invalid color spec: %s\n", optarg);
        return usage(argv[0]);
      }
      break;
    case 'B':
      if (!parseColor(&bg_color, optarg)) {
        fprintf(stderr, "Invalid background color spec: %s\n", optarg);
        return usage(argv[0]);
      }
      break;
    case 'O':
      if (!parseColor(&outline_color, optarg)) {
        fprintf(stderr, "Invalid outline color spec: %s\n", optarg);
        return usage(argv[0]);
      }
      with_outline = true;
      break;
    default:
      return usage(argv[0]);
    }
  }

  if (bdf_font_file == NULL) {
    fprintf(stderr, "Need to specify BDF font-file with -f\n");
    return usage(argv[0]);
  }

  /*
   * Load font. This needs to be a filename with a bdf bitmap font.
   */
  rgb_matrix::Font font;
  if (!font.LoadFont(bdf_font_file)) {
    fprintf(stderr, "Couldn't load font '%s'\n", bdf_font_file);
    return 1;
  }

  /*
   * If we want an outline around the font, we create a new font with
   * the original font as a template that is just an outline font.
   */
  rgb_matrix::Font *outline_font = NULL;
  if (with_outline) {
      outline_font = font.CreateOutlineFont();
  }

  if (brightness < 1 || brightness > 100) 
    fprintf(stderr, "Brightness is outside usable range.\n");
    return 1;
  }

  RGBMatrix *canvas = rgb_matrix::CreateMatrixFromOptions(matrix_options,
                                                          runtime_opt);
  if (canvas == NULL)
    return 1;

  canvas->SetBrightness(brightness);

  const bool all_extreme_colors = (brightness == 100)
      && FullSaturation(color)
      && FullSaturation(bg_color)
      && FullSaturation(outline_color);
  if (all_extreme_colors)
    canvas->SetPWMBits(1);

  const int x = x_orig;
  int y = y_orig;

  if (isatty(STDIN_FILENO)) {
    // Only give a message if we are interactive. If connected via pipe, be quiet
    printf("Enter lines. Full screen or empty line clears screen.\n"
           "Supports UTF-8. CTRL-D for exit.\n");
  }

    if ((y + font.height() > canvas->height())) {
      canvas->Clear();
      y = y_orig;
    }
    if (outline_font) {
      // The outline font, we need to write with a negative (-2) text-spacing,
      // as we want to have the same letter pitch as the regular text that
      // we then write on top.
      rgb_matrix::DrawText(canvas, *outline_font,
                           x - 1, y + font.baseline(),
                           outline_color, &bg_color, message, letter_spacing - 2);
    }
    // The regular text. Unless we already have filled the background with
    // the outline font, we also fill the background here.
    rgb_matrix::DrawText(canvas, font, x, y + font.baseline(),
                         color, outline_font ? NULL : &bg_color, message,
                         letter_spacing);
    y += font.height();

  
  sleep(5);
  // Finished. Shut down the RGB matrix.
  canvas->Clear();
  delete canvas;

  return 0;
}

              

display_snake_main.c


// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
// Small example how write text.
//
// This code is public domain
// (but note, that the led-matrix library this depends on is GPL v2)

#include "led-matrix.h"
#include "graphics.h"

#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

using namespace rgb_matrix;

static int usage(const char *progname) {
  fprintf(stderr, "usage: %s [options]\n", progname);
  fprintf(stderr, "Reads text from stdin and displays it. "
          "Empty string: clear screen\n");
  fprintf(stderr, "Options:\n");
  rgb_matrix::PrintMatrixFlags(stderr);
  fprintf(stderr,
          "\t-f <font-file>    : Use given font.\n"
          "\t-b <brightness>   : Sets brightness percent. Default: 100.\n"
          "\t-x <x-origin>     : X-Origin of displaying text (Default: 0)\n"
          "\t-y <y-origin>     : Y-Origin of displaying text (Default: 0)\n"
          "\t-S <spacing>      : Spacing pixels between letters (Default: 0)\n"
          "\t-C <r,g,b>        : Color. Default 255,255,0\n"
          "\t-B <r,g,b>        : Background-Color. Default 0,0,0\n"
          "\t-O <r,g,b>        : Outline-Color, e.g. to increase contrast.\n"
          );
  return 1;
}

static bool parseColor(Color *c, const char *str) {
  return sscanf(str, "%hhu,%hhu,%hhu", &c->r, &c->g, &c->b) == 3;
}

static bool FullSaturation(const Color &c) {
    return (c.r == 0 || c.r == 255)
        && (c.g == 0 || c.g == 255)
        && (c.b == 0 || c.b == 255);
}

int main(int argc, char *argv[]) {
  RGBMatrix::Options matrix_options;
  rgb_matrix::RuntimeOptions runtime_opt;
  if (!rgb_matrix::ParseOptionsFromFlags(&argc, &argv,
                                         &matrix_options, &runtime_opt)) {
    return usage(argv[0]);
  }

  char *message = argv[5];

  Color color(255, 255, 0);
  Color bg_color(0, 0, 0);
  Color outline_color(0,0,0);
  bool with_outline = false;

  const char *bdf_font_file = NULL;
  int x_orig = 0;
  int y_orig = 0;
  int brightness = 100;
  int letter_spacing = 0;

  int opt;
  while ((opt = getopt(argc, argv, "x:y:f:C:B:O:b:S:")) != -1) {
    switch (opt) {
    case 'b': brightness = atoi(optarg); break;
    case 'x': x_orig = atoi(optarg); break;
    case 'y': y_orig = atoi(optarg); break;
    case 'f': bdf_font_file = strdup(optarg); break;
    case 'S': letter_spacing = atoi(optarg); break;
    case 'C':
      if (!parseColor(&color, optarg)) {
        fprintf(stderr, "Invalid color spec: %s\n", optarg);
        return usage(argv[0]);
      }
      break;
    case 'B':
      if (!parseColor(&bg_color, optarg)) {
        fprintf(stderr, "Invalid background color spec: %s\n", optarg);
        return usage(argv[0]);
      }
      break;
    case 'O':
      if (!parseColor(&outline_color, optarg)) {
        fprintf(stderr, "Invalid outline color spec: %s\n", optarg);
        return usage(argv[0]);
      }
      with_outline = true;
      break;
    default:
      return usage(argv[0]);
    }
  }

  if (bdf_font_file == NULL) {
    fprintf(stderr, "Need to specify BDF font-file with -f\n");
    return usage(argv[0]);
  }

  /*
   * Load font. This needs to be a filename with a bdf bitmap font.
   */
  rgb_matrix::Font font;
  if (!font.LoadFont(bdf_font_file)) {
    fprintf(stderr, "Couldn't load font '%s'\n", bdf_font_file);
    return 1;
  }

  /*
   * If we want an outline around the font, we create a new font with
   * the original font as a template that is just an outline font.
   */
  rgb_matrix::Font *outline_font = NULL;
  if (with_outline) {
      outline_font = font.CreateOutlineFont();
  }

  if (brightness < 1 || brightness > 100) {
    fprintf(stderr, "Brightness is outside usable range.\n");
    return 1;
  }

  RGBMatrix *canvas = rgb_matrix::CreateMatrixFromOptions(matrix_options,
                                                          runtime_opt);
  if (canvas == NULL)
    return 1;

  canvas->SetBrightness(brightness);

  const bool all_extreme_colors = (brightness == 100)
      && FullSaturation(color)
      && FullSaturation(bg_color)
      && FullSaturation(outline_color);
  if (all_extreme_colors)
    canvas->SetPWMBits(1);

  const int x = x_orig;
  int y = y_orig;


    if ((y + font.height() > canvas->height())) {
      canvas->Clear();
      y = y_orig;
    }
    if (outline_font) {
      // The outline font, we need to write with a negative (-2) text-spacing,
      // as we want to have the same letter pitch as the regular text that
      // we then write on top.
      rgb_matrix::DrawText(canvas, *outline_font,
                           x - 1, y + font.baseline(),
                           outline_color, &bg_color, message, letter_spacing - 2);
    }
    // The regular text. Unless we already have filled the background with
    // the outline font, we also fill the background here.
    rgb_matrix::DrawText(canvas, font, x, y + font.baseline(),
                         color, outline_font ? NULL : &bg_color, message,
                         letter_spacing);
    y += font.height();

  
  usleep(0.5 * 1000000);
  // Finished. Shut down the RGB matrix.
  canvas->Clear();
  delete canvas;

  return 0;
}