CSC270 Team 4 project 2019

From CSclasswiki
Jump to: navigation, search

Sherri Lin and Catherine Zeng's Game of Life

Gol.png

Introduction

Project Description

For this project, we run Conway’s Game of Life on the Raspberry Pi and displaying it on a 8x8 LED matrices. We have four types of initialized world, and use a switch to let the user decide which world the program would run. We also have a LCD to show the instruction.

We connect the switch to the Raspberry Pi, and the LED matrix and the LED to the Arduino. The basic idea is that the Raspberry would let the Arduino to display four different initialized world on the LED Matrix. We let the user to choose which world would be run on the Raspberry pi by a switch. When the switch is pressed, the world shown in the LED Matrix will be run by the Raspberry Pi. Then, the Raspberry Pi send each row of the new world to the Arduino through Serial Communication. Because the rows are first an array like this: [0,0,0,0,1,0,0,0,0], the Raspberry Pi would first transfer it into a binary number, like 00001000. Then, the Arduino would accept byte, which is the binary number, and it would modify the matrix accordingly, and display it on the LED Matrix. We add a LCD because we think it would be necessary to instruct the user to select a world by pressing the switch for 3 seconds.

Rule of Game of Life

This introduction of Conway's Game of Life can be found in: https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life.

The universe of the Game of Life is an infinite, two-dimensional orthogonal grid of square cells, each of which is in one of two possible states, alive or dead, (or populated and unpopulated, respectively). Every cell interacts with its eight neighbours, which are the cells that are horizontally, vertically, or diagonally adjacent. At each step in time, the following transitions occur:

-Any live cell with fewer than two live neighbours dies, as if by underpopulation.
-Any live cell with two or three live neighbours lives on to the next generation.
-Any live cell with more than three live neighbours dies, as if by overpopulation.
-Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.

The initial pattern constitutes the seed of the system. The first generation is created by applying the above rules simultaneously to every cell in the seed; births and deaths occur simultaneously, and the discrete moment at which this happens is sometimes called a tick. Each generation is a pure function of the preceding one. The rules continue to be applied repeatedly to create further generations.

Other Projects

Project 1:Arduino LED Matrix Game of Life:

Description: This project create a Conway's Game of Life base on Arduino and displayed on a RGB LED Matrix.

Our project would be significantly different in that we will also be using a Raspberry Pi and the Raspberry Pi and Arduino are connected. This project randomly initialize the world for the first step, but we will have an input sensor that would allow the user to initialize the world. Moreover, we would also have a switch that would terminate the project.


Project 2: Python Game of Life

Description: This project created a Python pile that uses Pygame to simulate The Game of Life.

Our project would be greatly different from this project in that this project does not utilize an Arduino and therefore does not display its output on a LED Matrix. Moreover, we will create a C file, but this project only creates a Python file.

Materials

language

We use Python 3 and C. We use Python 3 for creating a Game of Life simulation on the Raspberry Pi, and C for the Arduino to display the results.

Hardware

- Arduino Mega2560
- Raspberry Pi 3 Model b+
- Wiring Kit
- 220 Ohm resistor
- Potentiometer
- LED Matrix(MAX7219)
- LCD display LCD (LCD1602A)

Sensors:

- A switch (as an input sensor) or a keypad (as an input sensor)

(It seems that switch does not have a datasheet, but there is a tutorial for implementing it: https://www.arduino.cc/en/Tutorial/InputPullupSerial.)

Libraries

For the Arduino, we used a library called "LedControl" for the LED Matrix which has a more detailed description here: https://playground.arduino.cc/Main/LedControl/

We also used the library called "LiquidCrystal" for the LCD. Its URL is: https://www.arduino.cc/en/Tutorial/HelloWorld


For the Raspberry Pi, we used the gpio zero library: https://gpiozero.readthedocs.io/en/stable/index.html#

We used the Random library. Although we did not use the source code, its source code is: https://github.com/python/cpython/blob/3.7/Lib/random.py.

We also used the time library for the sleep() function.

The Project

Plan of Attack

(This section is our initial plan, but our final project has changed a lot from the plan of attack)

Step 1:

Create a Python file that runs a Conway's Game of Life simulation. It will not take any input and we will determine the initial world. The point of the first step is to test our Python code and make sure that we can make the simulation work on our laptop.

Step 2:

Create the Game of Life in C and make sure it displays on the Arduino LED matrix.

Step 3:

Create simple programs in Python on the RPi and in C on the Arduino to make sure serial communication is working. Integrate serial communication code into Python Game of Life and Arduino to display the LED matrix.

Step 4:

Make sure the Arduino is receiving the correct matrix and the LED matrix is displaying the correct Game of Life.

Step 5:

Connect the Arduino to the Raspberry Pi and a switch. Let the Arduino blink each LED of the Matrix, and if the user presses the button when a LED is blinking, then it is a sign that this position would be alive.

Testing:

1) Write a program to make sure we can use the switch and the LED Matrix to initialize the first world.

2) Then, test if we can make the program work on our laptop

3) Connect it to the Arduino.

Step 6:

Keep the random generation of the initial world part, so that we can let the user to decide whether they want to initialize the first world. (we might be using the keypad for this part).

Other things we could do:

We could also use a Potentialmeter which would control the speed of displaying the Game of Life on the LED matrix.

Final Project Look

The final project will basically be 2 8x8 LED matrices connected together in order to display Game of Life. We would fulfill the museum requirement by having the user input switch outside of the display case, and the LED should light up as soon as power is connected and allow for the user to pick which positions are active.

Game of Life code on Raspberry Pi

We are using Python 3

# gameoflife.py
# Sherri Lin and Catherine Zeng
# May 9, 2019
# This program implements Conway's Game of Life and sends bytes to the Arduino to display on LED
# Works on Python 3.7
# To compile and run: python gameoflife.py

from __future__ import print_function
from gpiozero import Button
import serial
from time import sleep
import random

class Game(object):

    def __init__(self, state, infinite_board = True):

        self.state = state
        self.width = state.width
        self.height = state.height
        self.infinite_board = infinite_board

    def step(self, count = 1):

        for generation in range(count):

            new_board = [[False] * self.width for row in range(self.height)]

            for y, row in enumerate(self.state.board):
                for x, cell in enumerate(row):
                    neighbours = self.neighbours(x, y)
                    previous_state = self.state.board[y][x]
                    should_live = neighbours == 3 or (neighbours == 2 and previous_state == True)
                    new_board[y][x] = should_live

            self.state.board = new_board

    def neighbours(self, x, y):

        count = 0

        for hor in [-1, 0, 1]:
            for ver in [-1, 0, 1]:
                if not hor == ver == 0 and (self.infinite_board == True or (0 <= x + hor < self.width and 0 <= y + ver < self.height)):
                    count += self.state.board[(y + ver) % self.height][(x + hor) % self.width]

        return count

    def display(self):
        return self.state.display()

class State(object):

    def __init__(self, positions, x, y, width, height):

        active_cells = []

        for y, row in enumerate(positions):
            for x, cell in enumerate(row):
                if cell == 1:
                    active_cells.append((x,y))

        board = [[False] * width for row in range(height)]

        for cell in active_cells:

            board[cell[1]][cell[0]] = True

        self.board = board
        self.width = width
        self.height = height

    # turning the board into 8 bytes
    def display(self):

        for y, row in enumerate(self.board):
            for x, cell in enumerate(row):
                if self.board[y][x]:
                    self.board[y][x] = 1
                else:
                    self.board[y][x] = 0
                    
        toSend = []
        for i in range(len(self.board)):
            string = ''
            for j in range(len(self.board[i])):
                string += str(self.board[i][j])
            toSend.append(int(string, 2))
            
        return toSend

def main():        
    # initializing the generations
    glider = [ [[0, 0, 0, 0, 0, 0, 0, 0],
              [0, 0, 0, 0, 0, 0, 0, 0],
              [0, 0, 0, 1, 0, 0, 0, 0],
              [0, 0, 0, 1, 0, 0, 0, 0],
              [0, 0, 0, 1, 0, 0, 0, 0],
              [0, 0, 0, 0, 0, 0, 0, 0],
              [0, 0, 0, 0, 0, 0, 0, 0],
              [0, 0, 0, 0, 0, 0, 0, 0]],
                [[0, 0, 0, 0, 0, 0, 0, 0],
              [0, 0, 0, 0, 0, 0, 0, 0],
              [0, 0, 0, 0, 0, 0, 0, 0],
              [0, 0, 0, 1, 1, 0, 0, 0],
              [0, 0, 0, 1, 1, 0, 0, 0],
              [0, 0, 0, 0, 0, 0, 0, 0],
              [0, 0, 0, 0, 0, 0, 0, 0],
              [0, 0, 0, 0, 0, 0, 0, 0]],
               [[0, 0, 0, 0, 0, 0, 0, 0],
              [0, 0, 1, 0, 0, 1, 0, 0],
              [0, 0, 0, 0, 0, 0, 1, 0],
              [0, 0, 1, 0, 0, 0, 1, 0],
              [0, 0, 0, 1, 1, 1, 1, 0],
              [0, 0, 0, 0, 0, 0, 0, 0],
              [0, 0, 0, 0, 0, 0, 0, 0],
              [0, 0, 0, 0, 0, 0, 0, 0]],
               [[0, 0, 0, 0, 0, 0, 0, 0],
              [0, 0, 0, 0, 0, 0, 0, 0],
              [0, 0, 0, 0, 0, 0, 0, 0],
              [0, 1, 0, 0, 0, 1, 0, 0],
              [0, 1, 0, 0, 0, 1, 0, 0],
              [0, 1, 0, 0, 0, 1, 0, 0],
              [0, 0, 0, 0, 0, 0, 0, 0],
              [0, 0, 0, 0, 0, 0, 0, 0]] ]

    port = "/dev/ttyACM0"
    baudrate = 9600
    ser = serial.Serial( port, baudrate )
    button = Button(2)
    start_gen = None

    # allowing user the pick first world
    while not start_gen:
        for gen in glider:
            send = []
            for i in range(len(gen)):
                s = ''
                for j in range(len(gen[i])):
                    string += str(gen[i][j])
                send.append(int(s, 2))
            for n in send:
                ser.write( chr(n) )
                n = n % 255
            sleep( 2 )
                
            if button.is_pressed:
                start_gen = gen
                break

    my_game = Game(State(start_gen, x = 0, y = 0, width = 8, height = 8))

    # sending bytes to the Arduino
    while True:
        for n in my_game.display():
            ser.write( chr( n ) )
            n = n % 255
            print( "sent", n )
        sleep( 1 )
        my_game.step(1)
        

main()

Arduino Side

(Code for a Game of Life that receives bytes from the Raspberry Pi)

#include "LedControl.h"
#include <LiquidCrystal.h>

// "DIN" data in pin
#define DIN_PIN 12
// "CLK" clock pin
#define CLK_PIN 11
// "CS" pin
#define CS_PIN 10
// grid dimensions. should not be larger than 8x8
#define MAX_Y 8
#define MAX_X 8
// time to wait between turns
#define TURN_DELAY 1000
// how many turns per game before starting a new game
// you can also use the reset button on the board
#define TURNS_MAX 60
// how many turns to wait if there are no changes before starting a new game
#define NO_CHANGES_RESET 10

int TURNS = 0;      // counter for turns
int NO_CHANGES = 0; // counter for turns without changes
int start, stop, bit[8], led_13 = 0; 
LedControl lc = LedControl(DIN_PIN, CS_PIN, CLK_PIN, 1);

const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

boolean grid[MAX_Y][MAX_X] = {
    {0,0,0,0,0,0,0,0},
    {0,0,0,0,0,0,0,0},
    {0,0,0,0,0,0,0,0},
    {0,0,0,0,0,0,0,0},
    {0,0,0,0,0,0,0,0},
    {0,0,0,0,0,0,0,0},
    {0,0,0,0,0,0,0,0},
    {0,0,0,0,0,0,0,0},
};

/*
 * Debug: print the gird
 */
void print_grid() {
  for (int y = 0; y < MAX_X; y++) {
      for (int x = 0; x < MAX_Y; x++) {
        Serial.print(grid[y][x]);
        }
        Serial.println(" ");
    }
}


void setup() {
  // seed random from unused analog pin
  randomSeed(analogRead(0));

  Serial.begin(9600);
  
  // initialise the LED matrix
  lc.shutdown(0, false);
  lc.setIntensity(0, 0);
  lc.clearDisplay(0);

  // set up the LCD's number of columns and rows:
  lcd.begin(16, 2);
  // Print a message to the LCD.
  lcd.print("Select a World!");
  //receive();
  //display_grid();
  lcd.setCursor(0,1);
  lcd.print("Press for 3 sec");

  pinMode(8, INPUT); // input with serial data coming in
}

/*
 *Display one generation of cells on the LED matrix 
 */
void display_grid() {
    for (int y = 0; y < MAX_X; y++) {
      for (int x = 0; x < MAX_Y; x++) {
        lc.setLed(0, y, x, grid[y][x]);
      }
    }
}

/*
 * Arduino receives one byte at a time through serial communication and then modify the matrix
 * The byte is a number that represents a row in binary number.
 * For exmaple, if the row is [0,0,0,0,0,1,0,0], the byte would be 8. 
 * y denotes the number of row.
 */
void receive(int *y) {
    if (Serial.available()) {
      int byte = Serial.read(); // receive byte by serial communication (from the RPi) 
      // modify the matrix
      for (int i =0; i<8; i++){
        // look at the least significant bit
        if (byte%2 == 0){
          grid[*y][i] = 0; 
        } else {
          grid[*y][i] = 1; 
        }
        byte = byte/2; // get rid of the current least significant bit
      }
      *y = *y + 1; // increment y (go to the next row)
    }
}

void loop() {

  int y = 0; // y is the number of rows
  // receive one generation of cells
  
  while (y<8) { 
    receive(&y);// recieve 8 rows 
  }  
  display_grid(); // 
  delay(1000);

}


Progress

Week 1

4/14: Project Proposal and Webpage: We met to discuss our final project proposal. We decided to implement a game of life using Arduino and Raspberry Pi, and brainstormed how we could implement the game of life. We discussed our proposal with professor Thiebaut through Piazza.

4/16: Project Proposal and Webpage: We worked on our proposal together, made a plan of attack and continued working on our webpage.

4/18: Displaying Game of Life on Arduino We connected the LED matrix to the Arduino and found a version of Game of Life for Arduino: https://github.com/tobyoxborrow/gameoflife-arduino. Game of Life is now displayed on a single 8x8 LED matrix. We learned how the code for game of life works. We also learned how to write a C code for the Arduino to display generations of game of life on a 8*8 LED matrix, and we imported a "LedControl.h" library.

LEDmatrix.jpg

Week 2

4/21: Python Program: We met in the lab and we learned how Game of Life works. We then found a python code of game of life that works in the Raspberry Pi.

4/23: Working Game of Life!: We have both Game of Life on the Arduino and Python and are trying to modify the Python code to fit the 8x8 LED matrix. We modified the default matrix, and now the new Python program works in the Raspberry Pi! Then, we discussed how we were to decide the C code and the Python code. Our conclusion is that the Python code would run the Game of Life, and then send generations to the Arduino which controls the LED Matrix.

We would turn the rows of the matrix (which is an array of integers) into a bytes, and sen one byte to the Arduino at a time. For example, if the first row is 0000 1000, the python code would send 8 to the Arduino. Then, the Arduino would receive this byte through serial communication, and then turn it to an array of boolean values, like [0,0,0,0,1,0,0,0], in order to display this array on the LED Matrix. After that, the Raspberry Pi would send another byte to the Arduino.

4/25: Serial Communication: We now started to connect the Arduino and the Raspberry Pi to work on the serial communication. We first tried to connect them using a USB, and we used the code posted my our classmates on Piazza. Unfortunately, it did not work out well. We discussed our problem with professor Thiebaut, and professor Thiebaut suggested that we should use the example from our homework.

Week 3

4/28: We met to write the code using examples from our homework solution. We connected the Arduino with the LED Matrix using the wiring kit.

/*
 * Receive a byte (8 bit) from the RPi and modify the grid; 
 * After we received the START bit, which is the first 0, 
 * receive the other 8 bits and the stop bit; 
 */
void receive() {
    // wait till we find the START bit
    if ( digitalRead( 8 ) == 1 ){
      return;
    }
       
    // we found the stop bit
    start = digitalRead( 8 );
 
    // wait till middle of first data bit
    delayMicroseconds( 104+52 );
    bit[0] = digitalRead( 8 );
    
    delayMicroseconds( 104 );
    bit[1] = digitalRead( 8 );

    delayMicroseconds( 104 );
    bit[2] = digitalRead( 8 );

    delayMicroseconds( 104 );
    bit[3] = digitalRead( 8 );

    delayMicroseconds( 104 );
    bit[4] = digitalRead( 8 );

    delayMicroseconds( 104 );
    bit[5] = digitalRead( 8);

    delayMicroseconds( 104 );
    bit[6] = digitalRead( 8 );


    delayMicroseconds( 104 );
    bit[7] = digitalRead( 8 );
    grid[y][7] = bit[7];

    delayMicroseconds( 104 );
    stop = digitalRead( 8 );

    // modify the grid
    grid[y][0] = bit[0];
    grid[y][1] = bit[1];
    grid[y][2] = bit[2];
    grid[y][3] = bit[3];
    grid[y][4] = bit[4];
    grid[y][5] = bit[5];
    grid[y][6] = bit[6];
    grid[y][7] = bit[7];
    return;
}

However, the Arduino is not receiving the right bits that we want. We tried to debug it by using serial.print(),but this slowed down the program and thus we cannot debug using serial.print().


4/29: As the Arduino is not receiving the correct bits from the RPi, we are trying to set up the serial communication between RPi and the Arduino again. We opened the Arduino on the Raspberry Pi, which allows us to debug and to make sure that the Arduino receives the right bits from the Raspberry Pi. We modified the receive() function to receive bytes from the RPi.

/*
 * Arduino receives one byte at a time through serial communication.
 * The byte is a number that represents a row in the matrix in binary number.
 * For example, if the row is [0,0,0,0,1,0,0,0], the byte would be 8. 
 * Then, modify the matrix according to the byte received;
 * y denotes the number of row.
 */
void receive(int *y) {
    if (Serial.available()) {
      int byte = Serial.read(); // receive byte by serial communication (from the RPi) 
      // modify the matrix
      for (int i =0; i<8; i++){
        // look at the least significant bit
        if (byte%2 == 0){
          grid[*y][i] = 0; 
        } else {
          grid[*y][i] = 1; 
        }
        byte = byte/2; // get rid of the current least significant bit
      }
      *y = *y + 1; // increment y (go to the next row)
    }
}

and this time the serial communication works! However, we still had trouble displaying the game of life on the LED matrix, because when we tried to run the C code for Arduino on the Raspberry Pi, we cannot import the "LEDContorl" library. We tried many ways to import it, including compiling the program for "LEDControl" on the Raspberry Pi, but it still does not work.

4/30: Presentation: We did the presentation in class, and learned from Grace and Maddy's presentation that when we cannot import the "LEDControl" library, we have to run the c code on our laptop, and then connect the Arduino to the Raspberry. As the Arduino would remember the c program, it will display the game of life.

5/4:: We tried Grace and Maddy's method, and it works! We now have our game of life displayed on the LED matrix.

Then, we connected a switch as an input to the Raspberry Pi. When the switch is pressed, the current game would be terminated and restart with another initialized world. We modified our python code and also add three different types of initialized world to it.

And it works:

54.jpeg


However, the Arduino was displaying the generations of game of life much more slowly than we want it to be...

5/5: We found out that the problem is that we have delay(5000); in the end of our Arduino code...

Week 4

5/7: Today we tried to integrate more features into our project. The first thing we wanted to do was to initialize about 4 different starting worlds, and then the user could be able to press a button to select the starting world they wanted. We had to try this several times because after we changed up the code, nothing would happen even if we pressed the button. We added a print function to check if button was pressed, and even that didn't show up when we pressed it. Finally, we figured out that it was because we had a sleep function within our generation displaying loop, so once we took that once, the button worked. However, we would have to press the button for over 3 seconds for it to be able to respond and start the game of life with the world that was selected. Next, we tried to add more LED matrices so that we could display a bigger game of life. After wiring up the Arduino and changing the Python code so that the initial worlds were 8 by 32, we realized that our method of sending 8 bytes to represent a matrix wouldn't work now, because we would still only be able to send bytes at a time, so rearranging the bytes so that a matrix was displayed would be too much of a change for our code. So we decided to stick with the original 8x8 matrix. Lastly, we tried to include an Arduino LCD display, which would display something like: "Please select a world" to the user so that the user would know what to do, and wiring up the LCD took quite a while because there were connection problems sometimes. However, we managed to solve that and the LCD did display what we wanted.

Fin.jpg

5/8: This morning we met with Professor Thiebaut and demoed our project. We didn't have the museum requirements yet: having the program run at start up, and also displaying everything on a piece of glass. So after that, we used https://www.dexterindustries.com/howto/run-a-program-on-your-raspberry-pi-at-startup/ to help. We tried the first rc.local method first, and that didn't work. We ended up using the second method, the bashrc, and that worked! So our program was able to run at startup. After that, we glued all the parts onto a glass so that it's all in one piece. This is our final product.

Fin2.jpg

Resources

We used this code for displaying a matrix on LED matrix

We used this code for our game of life on the Raspberry Pi. We just modified how it was displayed.

We used this tutorial for auto-starting the program for the museum requirement