| Weitere Artikel aus dem Elo-Magazin |
Ping-Pong Wettbewerb
DSTP - Don't Shoot the Puppy
Außerirdische bedrohen die Geranienbeete in Ihrem Vorgarten. Zerstören Sie deren Bomben bevor Ihre Blumen nach unten wachsen. Doch Vorsicht, lassen Sie Nachbars Hündchen leben! Die Bomben müssen schnellstmöglich nach Auftauchen durch Drücken des entsprechenden Tasters abgeschossen werden. Doch das Drücken des anderen Tasters, oder der Schuß auf Nachbars Hündchen werden gnadenlos durch Punktabzug bestraft.
von Hans Eckardt
Außerirdische bedrohen die Geranienbeete in Ihrem Vorgarten.
Zerstören Sie deren Bomben bevor Ihre Blumen nach unten wachsen.
Doch Vorsicht, lassen Sie Nachbars Hündchen leben!
Die Bomben müssen schnellstmöglich nach Auftauchen durch Drücken des entsprechenden Tasters abgeschossen werden.
Doch das Drücken des anderen Tasters, oder der Schuß auf Nachbars Hündchen werden gnadenlos durch Punktabzug bestraft.
Dann wird der leider kleiner gewordene Punktestand kurz angezeigt.
Bei zu langem Drücken eines Tasters erscheint „Key!" und es geht erst weiter wenn Sie den Taster loslassen. Nach 20 Bomben ist das Spiel auch schon zu Ende, und nach Anzeige des Punktestands geht der ATMEGA in den Standby-Modus.
Durch Drücken des linken Tasters kann ein neues Spiel beginnen.

Für dieses Spiel muss am original Pong eine kleine Veränderung vorgenommen werden. Schließen Sie zwei Taster wie gezeigt an - den linken parallel an K1 und K2, den rechten zwischen K1 und D3.
Ich habe in mein vorhandenes Gehäuse einfach kleine Löcher für die Leiterplatten-Taster gebohrt, die Drähte von unten ein Stück durchgefädelt und an den Tastern angelötet, und dann die Taster mit ein bißchen Kleber durchgesteckt.
Die Lötfahnen und der Kleber geben genug Halt. Das geht auch mit dem original Pong-Pappgehäuse.
Die Potis werden fürs Spielen nicht benötigt. Bei der Initialisierung wird jedoch vom rechten Poti der Spannungswert zum Initialisieren des Zufallsgenerators verwendet. Wenn Sie das Spiel nur mit den Tastern (ohne Potis) aufbauen wollen, können Sie diese Schaltung verwenden um eine zufällige Initialisierung zu erreichen.
Der LDR ist ein Fotowiderstand, der z.B. durch ein kleines Loch vom Umgebungslicht beschienen wird. Da sollte jeder Typ gehen.
Download: Projektdateien und Hexfile
// Don't shot the puppy reaction game
// using ATMEGA8 on the Conrad Pong PCB
// revision (newest first)
// 2010-03-28: avoid start with puppy
// 2010-03-27: finished the code, all times and scores are now tweaked for good game playing
// 2010-03-26: initial taken from the code simulated in PongSimu
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>
#include <avr/sleep.h>
#include <util/delay.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include "my_avr_defs.h"
#include "smallfont.h"
#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[HEIGHT-1-y][WIDTH-1-x]= v;
#else
pixbuf[WIDTH-1-x][y]= v;
#endif
}
static inline byte GetPixel(byte x, byte y)
{
#ifdef LANDSCAPE
return pixbuf[HEIGHT-1-y][WIDTH-1-x];
#else
return pixbuf[WIDTH-1-x][y];
#endif
}
const byte PROGMEM bomb[4]=
{
0b101,
0b111,
0b111,
0b010,
};
const uint PROGMEM puppy[10]=
{
//012345678901
0b110111111011,
0b101000000101,
0b100000000001,
0b101100001101,
0b100000000001,
0b100000000001,
0b101001100101,
0b100111111001,
0b010000000010,
0b001111111100,
};
static void DrawBomb0(byte x)
{
for (byte b= 0; b < _countof(bomb); ++b) {
byte mask= 0b100;
byte val= pgm_read_byte(&bomb[b]);
for (byte i= 0; i < 3; ++i) {
SetPixel(x + i, HEIGHT-1-b, val & mask ? 2 : 0);
mask /= 2;
}
}
}
static void DrawBomb(byte which)
{
if (which & 0x1)
DrawBomb0(0);
if (which & 0x2)
DrawBomb0(9);
}
static void EraseBomb(byte x)
{
for (byte b= 0; b < _countof(bomb); ++b) {
for (byte i= 0; i < 3; ++i) {
SetPixel(x + i, HEIGHT-1-b, 0);
}
}
}
static void DrawPuppy(void)
{
for (byte b= 0; b < _countof(puppy); ++b) {
byte mask= 0b1000;
byte val= pgm_read_byte(1+(byte*)&puppy[b]);
for (byte i= 0; i < 4; ++i) {
SetPixel(i, HEIGHT-1-b, val & mask ? 2 : 0);
mask /= 2;
}
mask= 0b10000000;
val= pgm_read_byte((byte*)&puppy[b]);
for (byte i= 4; i < 12; ++i) {
SetPixel(i, HEIGHT-1-b, val & mask ? 2 : 0);
mask /= 2;
}
}
}
static void DrawShooter(byte which)
{
if (which & 0x1)
for (byte y= 0; y < 2; ++y)
SetPixel(1, y, 2);
if (which & 0x2)
for (byte y= 0; y < 2; ++y)
SetPixel(10, y, 2);
}
static void EraseShooter(byte which)
{
if (which & 0x1)
for (byte y= 0; y < 2; ++y)
SetPixel(1, y, 0);
if (which & 0x2)
for (byte y= 0; y < 2; ++y)
SetPixel(10, y, 0);
}
static void
erase_gfx(void) {
for (byte y=0; y<HEIGHT; y++)
for (byte x=0; x<WIDTH; x++)
SetPixel(x, y, 0);
}
static void
draw_char(byte x0, byte y0, byte c)
{
const byte* fnt= &font4x6[3*(c - 32)];
for (byte y= 0; y < 6; y+=2) {
byte mask= 0x80;
byte fntval= pgm_read_byte(fnt);
++fnt;
for (byte y2= 0; y2 < 2; ++y2) {
byte ys= HEIGHT-1-y0-(y+y2);
if (ys > HEIGHT) break; // allow column special tweaking
for (byte x= 0; x < 4; ++x) {
if (x+x0 < WIDTH) // allow last char in a row special tweaking
SetPixel(x+x0,ys, (fntval & mask)? 2 : 0);
mask/=2;
}
}
}
}
// draw a string
static void
draw_text(byte x0, byte y0, const char* txt)
{
for(;;) {
byte c= *txt;
if (c==0) break;
++txt;
draw_char(x0, y0, c);
x0+= 4;
}
}
#if 0
// draw a string, for progmem string
static void
draw_text_P(byte x0, byte y0, const char* txt)
{
for(;;) {
byte c= pgm_read_byte(txt);
if (c==0) break;
++txt;
draw_char(x0, y0, c);
x0+= 4;
}
}
#endif
// return bit 0 left key, bit 1 right key
static byte getKeys(void)
{
return (~PIND >> 2) & 0x3;
}
static uint GetTimeToBomb(void)
{
return 100 + 120 * (rand() % 6);
}
static void DrawScore(uint score)
{
erase_gfx();
char buf[6];
itoa(score/10, buf, 10);
draw_text(12-4*strlen(buf),4, buf);
_delay_ms(1000);
erase_gfx();
}
static void EndScore(uint score)
{
for (byte b= 0; b < 6; ++b) {
DrawScore(score);
_delay_ms(200);
}
}
/* fall ray pattern (2 to 7 are the times they are drawn)
012345678901
0 111000000000
1 111000000000
2 111230000000
3 022200400000
4 030330005000
5 040404000600
6 050050500000
7 060060060007
8 070007007000
*/
#define FALLRAYS 4
#define FALLCNT 6
const byte PROGMEM fallen[FALLRAYS][FALLCNT][2]=
{
{ {1,3},{1,4},{1,5},{1,6},{1,7},{ 1,8} },
{ {2,3},{3,4},{3,5},{4,6},{4,7},{ 5,8} },
{ {3,3},{4,4},{5,5},{6,6},{7,7},{ 8,8} },
{ {3,2},{4,2},{6,3},{8,4},{9,5},{10,7} },
};
static void BombDestroy(byte bomb)
{
_delay_ms(30);
byte x0;
char dx;
if (bomb & 0x01) {
x0= 0;
dx= 1;
}
else {
x0= 11;
dx= -1;
}
// previous pixels
byte xp[FALLRAYS]= { 4,4,4,4 };
byte yp[FALLRAYS]= { 0 };
for (byte f= 0; f < FALLCNT; ++f) {
if (!getKeys()) {
EraseShooter(bomb);
}
for (byte r= 0; r < FALLRAYS; ++r) {
SetPixel(xp[r], HEIGHT-1-yp[r], 0);
xp[r]= x0 + dx * pgm_read_byte(&fallen[r][f][0]);
yp[r]= pgm_read_byte(&fallen[r][f][1]);
SetPixel(xp[r], HEIGHT-1-yp[r], 1);
}
if (f==1) {
EraseBomb(bomb & 0x01 ? 0 : 9);
}
_delay_ms(14);
}
erase_gfx();
}
// 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); // Port D Pin 2 set low for Poti GND
// 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) | (1u << MUX0); // input is ADC7 (right poti) initial
ADCSRA= (1u << ADEN) | // enable the ADC
(1u << ADSC) | // start conversion
// 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 void WaitForADC(void)
{
while ((ADCSRA & (1u << ADIF)) == 0); // spin until adc result is available
}
static void StandBy(void)
{
wdt_disable();
TIMSK= 0; // stop timer interrupt
_delay_ms(1); // wait any pending Interrupt
ADCSRA= 0; // disable adc
// bitbang ones for cathodes through the shift register
PORTB |= (1u<<4);
for (byte b= 0; b < 12; ++b) {
// impulses for the shift registers (CLK)
PORTB |= (1u<<3);
PORTB &= ~(1u<<3);
}
// impulses for the shift registers (strobe)
PORTB |= (1u<<2);
PORTB &= ~(1u<<2);
// all ports input , internal pullup at all ports (also LED's anodes to avoid any rest current)
DDRB= 0;
DDRC= 0;
DDRD= 0;
PORTB= 0xff;
PORTC= 0xff;
PORTD= 0xff;
MCUCR &= ((1u << ISC01) | (1u << ISC00)); // rising edge of INT0 generates interrupt
GICR |= (1u<<INT0); // Enable interrupt for INT0
set_sleep_mode(SLEEP_MODE_PWR_DOWN); // setup power save mode as sleep mode
sleep_mode(); // go into sleep mode
// back from sleep mode
MCUCR |= ~((1u << ISC01) | (1u << ISC00)); // no INT0 generates interrupt
erase_gfx();
AVR_Init();
}
static void ReInit(void)
{
DrawPuppy();
_delay_ms(500);
erase_gfx();
DrawShooter(0x03);
DrawBomb(0x03);
_delay_ms(500);
erase_gfx();
WaitForADC();
srand(ADC+2);
// Do NOT activate internal pullup as next Reinit time it will be switched to output
//PORTD |= (1u<<2);
DDRD &= ~(1u<<2); // Port D Pin 2 is now input
}
int __attribute__ ((OS_main)) main(void)
{
AVR_Init();
sei(); // we want interrupts globally now
ReInit();
// Main Loop
uint score= 10; // start with a small positive score...
uint shots= 20;
byte shooter= 0;
uint shooterOn= 0;
byte bomb= 0;
uint timeToBomb0= GetTimeToBomb();
uint timeToBomb= timeToBomb0;
uint timeToKillBomb= 0;
bool noPuppy= false;
for(;;) { // Typical loop for µControler
byte shooter_new= getKeys();
if (shooter_new != shooter) {
EraseShooter(shooter);
shooter= shooter_new;
DrawShooter(shooter);
}
if (shooter) {
if (bomb == 0xff) { // puppy was killed
bomb = 0;
if (score > 2000)
score-= 2000;
else
score= 0;
DrawScore(score);
while (getKeys()); // wait for no keypress...
}
if (shooter == bomb) { // the bomb was killed
BombDestroy(bomb);
bomb= 0;
// score should max be 500 for one shot
if (200 - (int)timeToKillBomb > 0) {
if (80 - (int)timeToKillBomb > 0) // very small times are worthy (timeToKillBomb < 50 is very good...)
score+= (200-80) + 6*(80 - timeToKillBomb);
else
score+= 200 - timeToKillBomb;
}
//debug only, show score after every kill
//DrawScore(score);
--shots;
}
else if (bomb) { // the bomb was not killed (other key or both keys pressed)
bomb0:
bomb = 0;
if (score > 600)
score-= 600;
else
score= 0;
_delay_ms(500);
DrawScore(score);
--shots;
}
else if (timeToBomb0 != 0 ) {// a pressed key does not count down time to bomb
timeToBomb= timeToBomb0;
}
++shooterOn;
if (shooterOn > 500) {
shooter= 0;
shooterOn= 0;
erase_gfx();
draw_text(0,2,"K");
draw_text(3,2,"ey");
draw_text(10,2,"!");
while (getKeys());
erase_gfx();
DrawBomb(bomb);
}
if (shots == 0) {
standby:
EndScore(score);
StandBy();
ReInit();
score= 10;
shots= 20;
shooter= 0;
shooterOn= 0;
bomb= 0;
timeToBomb0= GetTimeToBomb();
timeToBomb= timeToBomb0;
timeToKillBomb= 0;
noPuppy= false;
continue;
}
}
else if (!bomb) {
--timeToBomb;
if (timeToBomb == 0) {
timeToKillBomb= 0;
if (noPuppy && rand() < RAND_MAX / 20) { // puppy every 20th bomb
DrawPuppy();
bomb= 0xff;
timeToBomb0= 0;
timeToBomb= 300; // time to show puppy
noPuppy= false;
}
else {
timeToBomb0= GetTimeToBomb();
timeToBomb= timeToBomb0;
bomb= rand() > RAND_MAX/2 ? 0x1 : 0x2;
DrawBomb(bomb);
noPuppy= true;
}
}
}
else if (bomb == 0xff) {
--timeToBomb;
if (timeToBomb == 0) { // erase puppy
erase_gfx();
bomb= 0;
timeToBomb0= GetTimeToBomb();
timeToBomb= timeToBomb0;
}
}
else {
++timeToKillBomb;
if (timeToKillBomb > 1500) {
if (shots == 0) goto standby;
else goto bomb0;
}
}
_delay_ms(3);
}
}
// 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
ISR (TIMER0_OVF_vect)
{
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;
}
ISR(INT0_vect)
{
// Interupt handler for T0 button in standby mode
GICR &= ~(1u<< INT0); // Disable interrupt for Int0 for after standby mode
}
![]() | Franzis Ping-Pong Produktart: Softwarebox 29,95 € |















