/* Life at 76x75 on a torrodial world. It only just squeezes into 1kb ram. (hence the mucking about with PROGMEM strings) It uses a set of 64 characters which have been designed to hold every combination of 6 pixels - arranged in a 2x3 grid. It therefore needs a TellyMate Shield that's got the 'pixel patterns' fontbank. (available for download from the batsocks website) v1.0.14+ of the TellyMate firmware allows the fontbank(s) to be reprogrammed - see the the TellyMate user guide at www.batsocks.co.uk A bitmap on the Arduino is used to store the world. At each iteration, the rules of 'life' are applied to each pixel. The whole bitmap is then converted to 'characters' and sent to the TellyMate. It runs at about 6 frames per second. Simple routines to plot and read pixels in the buffer are included in the code, but they're not used in the 'life' engine (I'm not sure why I did it the hard way!). Note that this buffered method of plotting to the TellyMate is only optimal if you're updating lots of the screen each frame (e.g. more than 1/8th of the screen at a time). If you're only plotting a few pixels each frame, then plotting directly to the TellyMate is better - see the Scribble demo. If you have a multi-fontbank TellyMate, you'll need to change the GRAPHICS_FONTBANK macro to match the fontbank that contains the graphics characters. Many thanks to 'Andrew' on the Arduino forums for providing the inspiration in the form of a character-based 38x25 version. */ #define BAUD_RATE 57600 #define CHAR_ESC '\x1B' #define CHAR_DLE '\x10' #define GRAPHICS_FONTBANK 0 #include // bitmap array that holds the 'world'. byte g_display[75][10]; void setup( void ) { Serial.begin( BAUD_RATE ) ; Serial.print( CHAR_ESC ) ; Serial.print( 'E' ) ; // clear screen Serial.print( CHAR_ESC) ; Serial.print( 'f' ) ; // turn off cursor tm_cursor_move( 5, 7 ) ; print_P( PSTR("Life")) ; Serial.print( CHAR_ESC ) ; Serial.print( '_' ) ; Serial.print( '4' ) ; tm_cursor_move( 6, 7 ) ; print_P( PSTR("Life")) ; Serial.print( CHAR_ESC ) ; Serial.print( '_' ) ; Serial.print( '5' ) ; delay(3000) ; tm_cursor_move( 12,4 ) ; print_P( PSTR("Conway's game of life at 76x75")) ; tm_cursor_move( 14,3 ) ; print_P( PSTR("on an Arduino & TellyMate Shield")) ; tm_cursor_move( 24,9 ) ; print_P( PSTR("www.batsocks.co.uk")); delay(7000) ; Serial.print( CHAR_ESC ) ; Serial.print( 'E' ) ; // clear screen // initialise the world randomly. for ( byte y = 0 ; y < 75 ; y++ ) { for( byte b = 0 ; b < 10 ; b++ ) { g_display[y][b] = (byte)random(0,256) ; } } // initialise a glider... (useful for testing boundaries) /* setpixel( g_display, 70,10 ) ; setpixel( g_display, 71,10 ) ; setpixel( g_display, 72,10 ) ; setpixel( g_display, 72,9 ) ; setpixel( g_display, 71,8 ) ; */ // initialise the screen so that every row is set to use the graphics fontbank. // (only needed if this isn't fontbank 0, which is the default.) if (GRAPHICS_FONTBANK != 0) { for ( byte row = 0 ; row < 25 ; row++ ) { tm_cursor_move( row , 0 ) ; // move the cursor the required row (any column would do) Serial.print( CHAR_ESC ); Serial.print( '_' ); Serial.print( ((byte)('6' + GRAPHICS_FONTBANK))) ; // use the specified fontbank } } } void loop( void ) { life_CreateNextGeneration( g_display ); tm_WritePixels( g_display ) ; } void print_P(const char str[]) { // print a flash-memory based string char c ; while((c = pgm_read_byte(str++))) Serial.print(c,BYTE); } void setpixel( byte display[75][10], byte x , byte y ) { // set the pixel at x,y in the display buffer. byte subpos = x & 0b111 ; byte pos = x >> 3 ; byte bitmask = 0b10000000 >> subpos ; display[y][pos] |= bitmask ; } void clearpixel( byte display[75][10], byte x , byte y ) { // clear the pixel at x,y in the display buffer. byte subpos = x & 0b111 ; byte pos = x >> 3 ; byte bitmask = 0b10000000 >> subpos ; display[y][pos] &= ~bitmask ; } bool readpixel( byte display[75][10], byte x, byte y ) { // read the pixel at x,y in the display buffer. Returns true if it's set. (untested code!) byte subpos = x & 0b11 ; byte pos = x >> 3 ; byte bitmask = 0b10000000 >> subpos ; return( (display[y][pos] & bitmask) != 0 ) ; } void tm_cursor_move( uint8_t row , uint8_t col ) { // Yrc Serial.print( CHAR_ESC ) ; Serial.print( 'Y' ) ; Serial.print((unsigned char)(32 + row)) ; Serial.print((unsigned char)(32 + col)) ; } void tm_WritePixels( byte display[75][10] ) { // this function outputs the whole of the display array to the TellyMate. // It's not trivial, because the pixels are converted to characters. // Each character is 2 pixels wide by 3 pixels tall. for( byte r = 0 ; r < 75 ; r += 3 ) { tm_cursor_move( (r / 3) , 0 ) ; byte *p_a = display[ r + 0 ]; byte *p_b = display[ r + 1 ]; byte *p_c = display[ r + 2 ]; for ( byte col = 0 ; col < 38 ; col += 4 ) { byte a = *p_a++; byte b = *p_b++; byte c = *p_c++; byte v0 = 0; byte v1 = 0 ; byte v2 = 0 ; byte v3 = 0 ; v0 |= (0b00000001 & (a >> 7)) ; v0 |= (0b00000010 & (a >> 5)) ; v1 |= (0b00000001 & (a >> 5)) ; v1 |= (0b00000010 & (a >> 3)) ; v2 |= (0b00000001 & (a >> 3)) ; v2 |= (0b00000010 & (a >> 1)) ; v3 |= (0b00000001 & (a >> 1)) ; v3 |= (0b00000010 & (a << 1)) ; v0 |= (0b00000100 & (b >> 5)) ; v0 |= (0b00001000 & (b >> 3)) ; v1 |= (0b00000100 & (b >> 3)) ; v1 |= (0b00001000 & (b >> 1)) ; v2 |= (0b00000100 & (b >> 1)) ; v2 |= (0b00001000 & (b << 1)) ; v3 |= (0b00000100 & (b << 1)) ; v3 |= (0b00001000 & (b << 3)) ; v0 |= (0b00010000 & (c >> 3)) ; v0 |= (0b00100000 & (c >> 1)) ; v1 |= (0b00010000 & (c >> 1)) ; v1 |= (0b00100000 & (c << 1)) ; v2 |= (0b00010000 & (c << 1)) ; v2 |= (0b00100000 & (c << 3)) ; v3 |= (0b00010000 & (c << 3)) ; v3 |= (0b00100000 & (c << 5)) ; v0 |= 0b11000000 ; v1 |= 0b11000000 ; v2 |= 0b11000000 ; v3 |= 0b11000000 ; Serial.write( v0 ) ; Serial.write( v1 ) ; if (col < 36) { Serial.write( v2 ) ; Serial.write( v3 ) ; } } } } void life_CreateNextGeneration( byte d[75][10] ) { byte topline[10] ; byte templine[2][10] ; copydisplayline( d[0], topline ) ; copydisplayline( d[74], templine[1] ) ; for( byte y = 0 ; y < 75 ; y++ ) { copydisplayline( d[y], templine[ y & 0b1 ] ) ; life_processline( templine[ (y+1) & 0b1 ], d[y], (y==74)?topline:d[y+1] ) ; } } void copydisplayline( byte src[], byte tgt[] ) { /* note that these lines must not overlap! */ for( byte i = 0 ; i < 10 ; i++ ){ *tgt++ = *src++ ; } } void life_processline( byte row_prev[], byte row_this[], byte row_next[] ) { byte prev0 = row_prev[9] & 0b00010000 ; byte this0 = row_this[9] & 0b00010000 ; byte next0 = row_next[9] & 0b00010000 ; byte bitmask_X1 = 0b00000001 ; byte bitmask_X2 = 0b10000000 ; byte leftmostbyte = row_this[0] ; for( byte x = 0 ; x < 76 ; x++ ) { bitmask_X1 >>= 1 ; bitmask_X2 >>= 1 ; if (bitmask_X1 == 0) bitmask_X1 = 0b10000000 ; if (bitmask_X2 == 0) bitmask_X2 = 0b10000000 ; byte pos1 = (x & 0b11111000) >> 3 ; byte pos2 = ((x+1) & 0b11111000) >> 3 ; byte prev1 = row_prev[pos1] ; byte this1 = row_this[pos1] ; byte next1 = row_next[pos1] ; byte prev2 ; byte this2 ; byte next2 ; if (x == 75) { // wraparound for the last values ; bitmask_X2 = 0b10000000 ; prev2 = row_prev[0]; this2 = leftmostbyte ; // we have to use the stored version, as the first bit will have been overwritten. next2 = row_next[0] ; } else { prev2 = row_prev[pos2] ; this2 = row_this[pos2] ; next2 = row_next[pos2] ; } // calculate the number of neighbours that are alive... byte livecount = 0 ; if (prev0) livecount++ ; if (prev1 & bitmask_X1) livecount++ ; if (prev2 & bitmask_X2) livecount++ ; if (this0) livecount++ ; if (this2 & bitmask_X2) livecount++ ; if (next0) livecount++ ; if (next1 & bitmask_X1) livecount++ ; if (next2 & bitmask_X2) livecount++ ; switch( livecount ) { case 2 : // unchanged break ; case 3 : // alive row_this[ pos1 ] = this1 | bitmask_X1 ; break ; case 0 : // dies case 1 : // dies default : // dies row_this[ pos1 ] = this1 & ~bitmask_X1 ; } prev0 = prev1 & bitmask_X1 ; this0 = this1 & bitmask_X1 ; next0 = next1 & bitmask_X1 ; } }