Durch Bewegen des rechten Potis wird ein neuer Zufallswert erzeugt, und das Spiel beginnt von Neuem. Durch Bewegen des linken Potis erhalten Sie eine Anfangsbelegung, die aus bekannten stabilen Zellmustern eines auswählt. Diese Zellmuster sind stabil, d.h. nach einer Anzahl von Generationen ist das Anfangs-Muster wieder da. Leider ist im AVR das Spielfeld begrenzt (theoretisch wird ein unbegrenztes Spielfeld vorausgesetzt) so daß nicht alle Zellmuster stabil sind. Experimentieren Sie ein wenig. Das AVR Programm beinhaltet um das sichtbaren Feld einen Randbereich, der auch mit einer Breite von vier Zellen enthalten kann, die fünfte Zelle ist eine tote Zelle da kein unbegrenztes Spielfeld implementiert ist. Unterbrechen Sie die Stromversorgung, wenn Sie genug Game of Life „gespielt" haben. Ein Video des Ganzen können Sie auf youtube sehen.
Das Gehäuse meines Pong ist aus einem gerade vorhandenem Plastikgehäuse gemacht, ich habe eine Farbfolie auf die LED's angebracht um den Kontrast zu erhöhen.
// app to "play" the game of life
// using ATMEGA8 on the Conrad Pong PCB
// This code is released to the Public Domain. No warranty for any purpose is given.
// revision (newest first)
// 2010-03-17: ADU treshold enlarge
// 2010-03-16: running on the AVR
// 2010-03-15: initial taken from the PongSimu code
// This define is for Visual Studio intellisense, type before any stystem header includes
// It must match the MCU name (MCU = atmega8) in the makefile else intellisense can take you to the wrong definition
#ifndef __GNUC__ // only for the Visual Studio, GCC generates it from MCU name
#define __AVR_ATmega8__
#endif
// Intellisense can be triggered update by ctr+space, or Save all, or at last deleting prj.ncb file and restarting Visual Studio
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>
#include <util/delay.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include "my_avr_defs.h"
#include "patterns.h"
// display orientation landscape if defined, else portrait
#define LANDSCAPE
#ifdef LANDSCAPE
// width of display (LED count)
#define WIDTH 12
// height of display (LED count)
#define HEIGHT 10
#else
// width of display (LED count)
#define WIDTH 10
// height of display (LED count)
#define HEIGHT 12
#endif
#define CYCLE1_TIME 1 // // cycle 1 is very little of the time of cycle 2, so the brightness is way lower
#define CYCLE2_TIME 32
// cycle 1 is 0.008 millisec
// Time = prescale * counter range / clock frequency
// 0.064 ms = 64 * 1 (CYCLE1_TIME) / 8 000 000 Hz
// cycle 2 is 0.25 millisec
// Time = prescale * counter range / clock frequency
// 2.048 ms = 64 * 32 (CYCLE2_TIME) / 8 000 000 Hz
// global Pixel buffer (always anode, cathode order as this is optimal for the multiplexer)
static byte pixbuf[10][12];
// v is the brightness code, use only 0, 1, and 2, while 2 be the brightest
static inline void SetPixel(byte x, byte y, byte v)
{
#ifdef LANDSCAPE
pixbuf[y][x]= v;
#else
pixbuf[x][y]= v;
#endif
}
static inline byte GetPixel(byte x, byte y)
{
#ifdef LANDSCAPE
return pixbuf[y][x];
#else
return pixbuf[x][y];
#endif
}
// the visible playfield size
enum { MAXSIZEX = 12, MAXSIZEY = 10 };
// Conways field is infinite, but we use a fixed size for our simple algorithm, by adding an invisible border
// invisible border must be at least 1, greater 1 is needed for some predefined patterns
// invisible=5 is greatest to fit in data space of ATMEGA8, this all could be optimized
// (bits instead of bools, no separate display pixbuf)
enum { INIVISIBLE= 5, INIVISIBLE2= 2*INIVISIBLE };
// the cell array
bool cells[MAXSIZEX + INIVISIBLE2][MAXSIZEY + INIVISIBLE2];
// clear cell array
static void clear(void)
{
memset(cells, 0, sizeof(cells)); // zero out
}
// set cell alive (only the visible cells can be set)
static void setCell( uint i, uint j )
{
if ( i >= MAXSIZEX || j >= MAXSIZEY )
return;
cells[i+INIVISIBLE][j+INIVISIBLE] = true;
}
// next gen of cells
static void nextGeneration(void)
{
// next generation array (static to save stack space)
static bool cells_new[MAXSIZEX + INIVISIBLE2][MAXSIZEY + INIVISIBLE2];
memset(cells_new, 0, sizeof(cells_new)); // zero out the border
for ( uint i = 1; i < MAXSIZEX+INIVISIBLE2-1; i++ ) {
for ( uint j = 1; j < MAXSIZEY+INIVISIBLE2-1; j++ ) {
uint t = cells[i - 1][j - 1]
+ cells[i - 1][j]
+ cells[i - 1][j + 1]
+ cells[i][j - 1]
+ cells[i][j + 1]
+ cells[i + 1][j - 1]
+ cells[i + 1][j]
+ cells[i + 1][j + 1];
cells_new[i][j] = t == 3 || (t == 2 && cells[i][j]);
}
}
memcpy(cells, cells_new, sizeof(cells));
}
// initial random gen of cells
static void randomInit(void)
{
uint PlayExt= MAXSIZEX * MAXSIZEY;
for (uint i = 0; i < PlayExt/2; ++i) {
uint pos= rand();
setCell(pos % MAXSIZEX, pos / (RAND_MAX/MAXSIZEX));
}
}
// sync the pixels with the life game board
static void UpdatePixels(void)
{
for ( uint i = 0; i < MAXSIZEX; i++ )
for ( uint j = 0; j < MAXSIZEY; j++ )
SetPixel(i,j,cells[i+INIVISIBLE][j+INIVISIBLE] ? 2 : 0);
//avrTriggerRepaint();
}
// get a pattern to the life game
static void getPattern( uint pat )
{
uint i = pat % NPATS;
signed char col;
signed char* patptr = &patterns[i][0];
while ( (col = pgm_read_byte(patptr++)) != 127 ) {
signed char row = pgm_read_byte(patptr++);
col += MAXSIZEX / 2;
row += MAXSIZEY / 2;
setCell( col, row );
}
}
// init all stuff of the AVR for safe operation
static void AVR_Init(void)
{
//cli(); // interrupts are disabled at startup
// watchdog should reset after 15 millisec (protect the LEDs, although the max current is OK for always on)
wdt_enable(WDTO_15MS);
// activate internal pullups to avoid floating input pins
PORTB= (1u<<6)|(1u<<7);
PORTC= (1u<<4)|(1u<<5)/*|(1u<<6)|(1u<<7)*/; // Port C Pin 6,7 are the ADC inputs, no need for pullups here
PORTD= (1u<<0)|(1u<<1)|/*(1u<<2)|*/(1u<<3);
// set output ports
DDRB= (1u<<0)|(1u<<1)|(1u<<2)|(1u<<3)|(1u<<4);
DDRC= (1u<<0)|(1u<<1)|(1u<<2)|(1u<<3);
DDRD= (1u<<2)|(1u<<4)|(1u<<5)|(1u<<6)|(1u<<7); // Port D Pin 2 is the Poti GND so must be output and Low
// 8bit timer0 init, internal clock / 64
// LED multiplexing occurs on overflow0 Intr
TCCR0= (1u<<CS01)|(1u<<CS00);
TIMSK= (1u<<TOIE0); // interrupt on overflow
// init the analog-digital converter
ADMUX=
// no REFS0, REFS1: voltage ref is external connected to AVcc, and we can't change this
(1u << MUX2) | (1u << MUX1); // input is ADC6 (left poti) initial
ADCSRA= (1u << ADEN) | // enable the ADC
(1u << ADSC) | // start conversion (free running mode)
// no ADFR: free running mode is not needed, we explicitly start a new ADU cycle
(1u << ADPS2) | (1u << ADPS1) | (1u << ADPS0); // prescaler 128 (8MHz / 128 = 62.5 kHz, could max use 200 kHz for 10bit)
}
static bool rightADC;
static uint avrGetXADC(void)
{
// spin until adc result is available
while ((ADCSRA & (1u << ADIF)) == 0);
// read the value
uint val= ADC;
// switch to other channel
rightADC= !rightADC;
if (rightADC)
ADMUX= (1u << MUX2) | (1u << MUX1) | (1u << MUX0); // input is ADC7 (right poti)
else
ADMUX= (1u << MUX2) | (1u << MUX1); // input is ADC6 (left poti)
// start a new conversion
ADCSRA |= (1u << ADSC);
return val;
}
int __attribute__ ((OS_main)) main(void)
{
SetPixel(0,9,2);
AVR_Init();
sei(); // we want interrupts globally now
// get current adc values
uint LPotiP = avrGetXADC();
uint RPotiP = avrGetXADC() + 21; // begin with a random pattern selection
// wait for start of game
_delay_ms(500);
// Main Loop
for (;;) {
UpdatePixels();
uint Poti = avrGetXADC();
if ((!rightADC) && abs(RPotiP - Poti) > 20) { // new random pattern selection
RPotiP = Poti;
srand(Poti+2); // +2 to avoid same pattern
clear(); // clear the life board
randomInit(); // init with random values
UpdatePixels();
_delay_ms(100);
continue;
}
else if (rightADC && abs(LPotiP - Poti) > 20) { // known patterns selection
LPotiP = Poti;
clear(); // clear the life board
getPattern( (1023-Poti) / 80 ); // get a known pattern (11 patterns, so 1023/11=93 divide by 80)
UpdatePixels();
_delay_ms(800);
continue;
}
else
nextGeneration();
_delay_ms(250);
}
}
// interrupt routine
// will put the pixel data to the LED matrix
// select actual cathode line by the shift register, then put the anodes with the ports to high for the on pixel
//
// Note:
// Because CLK connected to MOSI, not SCK, we cannot bitbang with SPI
SIGNAL (SIG_OVERFLOW0)
{
static bool firstcy;
static byte anode;
static byte shift= 1;
firstcy = !firstcy;
if (firstcy) {
TCNT0= 256-CYCLE1_TIME;
anode++;
shift*=2;
if (anode == 8)
shift= 1;
else if (anode == 10) {
anode= 0;
shift= 1;
}
}
else
TCNT0= 256-CYCLE2_TIME;
byte cmp= firstcy ? 1 : 2;
// bitbang the cathode pattern through the shift register
for (byte b= 0; b < 12; ++b) {
if (pixbuf[anode][b] == cmp)
PORTB &= ~(1u<<4); // first cathode line put to low, this low is bitbanged through the registers
else
PORTB |= (1u<<4); /// next ones put to high
// impulses for the shift registers (CLK)
PORTB |= (1u<<3);
PORTB &= ~(1u<<3);
}
wdt_reset(); // keep watchdog watching...
// reset all anode lines
PORTB &= ~((1u<<0)|(1u<<1));
PORTC &= ~((1u<<0)|(1u<<1)|(1u<<2)|(1u<<3));
PORTD &= ~((1u<<4)|(1u<<5)|(1u<<6)|(1u<<7));
// impulses for the shift registers (strobe)
PORTB |= (1u<<2);
PORTB &= ~(1u<<2);
// set according anode line
if (anode < 4)
PORTC |= shift;
else if (anode < 8)
PORTD |= shift;
else
PORTB |= shift;
}