| Weitere Artikel aus dem Elo-Magazin |
Memo-Spiel mit der Ping-Pong-Platine
von Michael Gaus
Das Gedächtnistrainingsspiel "Memory", hier "Memo" genannt, gehört zu den Spieleklassikern und wird fast jedem bekannt sein. Dieses Spiel wurde hier elektronisch zur Verwendung auf der Ping-Pong-Platine umgesetzt.
Ganzen Artikel lesen...

Spielregeln:
Auszug aus Wikipedia unter http://de.wikipedia.org/wiki/Pairs
Eine Anzahl von Karten, auf denen jeweils paarweise gleiche Bilder oder Symbole abgebildet sind, wird gemischt und verdeckt ausgelegt, so dass am Anfang nicht bekannt ist wo welche Karte liegt. Einer der Mitspieler beginnt, zwei beliebige Karten umzudrehen, so dass diese für eine kurze Zeit für alle Mitspieler sichtbar sind. Sind die Bilder nicht gleich, muss er die beiden Karten wieder umdrehen und der nächste Mitspieler ist dran. Wenn die Bilder der beiden Karten identisch sind, darf der Mitspieler diese beiden Karten behalten. In diesem Fall darf er außerdem erneut beginnen, zwei weitere Karten umzudrehen. Gespielt wird, bis alle Karten erfolgreich aufgedeckt sind. Gewonnen hat der Spieler, der die meisten Pärchen aufdecken konnte.
Umsetzung:
Hier wird mit 2 Spielern und insgesamt 30 Karten, also 15 Pärchen gespielt, angeordnet in 5 Reihen mit jeweils 6 Karten. Jede noch nicht aufgedeckte Karte wird durch eine leuchtende LED symbolisiert. Eine Karte kann durch die beiden Potis (rechts/links sowie hoch/runter) angewählt werden, wobei die momentan angewählte Karte blinkt. Zur Bestätigung der Auswahl wird der Taster benötigt, wodurch dann die Karte aufgedeckt wird und das entsprechende Kartensymbol dargestellt wird.
Es wird ein Taster (1-poliger Schließer) benötigt, der zwischen PortD.3 (also Lötpad D3) und GND angeschlossen wird. Der benötigte Pullup-Widerstand kann im Controller intern hinzugeschaltet werden. Solch ein Taster kann leicht in das Ping-Pong-Gehäuse eingebaut werden, indem vorsichtig ein Loch in den Deckel gebohrt wird, dann der Taster hindurchgesteckt und per Mutter festgeschraubt wird. Mit 2 Stückchen Drahtlitze werden dann die beiden Anschlüsse mit D3 und GND verlötet.
Bedienung des Spiels:
Gestartet wird das Spiel durch Einwerfen einer Münze in den Münzschlitz. Es erscheint zunächst für ca. 2 Sekunden ein Logo "Memo" als Begrüßungsbildschirm. Anschließend erscheint ein blinkender Pfeil auf der rechten oder linken Seite des Displays, der den gerade angwählten Spieler symbolisiert. Durch Drehen des rechten Potis kann ausgewählt werden, welcher Spieler beginnen soll. Durch Drehen nach rechts (im Uhrzeigersinn) wird Spieler 2 gewählt, durch Drehen nach links (gegen den Uhrzeigersinn) wird Spieler 1 gewählt. Diese Auswahl muss durch einen Tastendruck bestätigt werden.
Nun werden per Zufallsgenerator die Karten zufällig angeordnet und es erscheint das Spielfeld mit 30 noch nicht aufgedeckten Karten, symbolisiert durch 30 leuchtende LEDs. Eine blinkende LED kennzeichnet die momentan durch die Potis angewählte Karte. In der untersten LED-Zeile wird der Spieler angezeigt, der gerade an der Reihe ist: ein horizontaler Balken links steht für Spieler 1, ein horizontaler Balken rechts steht für Spieler 2.
Der beginnende Spieler darf nun die erste Karte aufdecken. Hierzu wählt er mit den Potis die gewünschte Karte aus und bestätigt durch einen Tastendruck. Das Spielfeld wird nun ausgeblendet und stattdessen die aufgedeckte Karte mittig auf dem Display angezeigt, sodass beide Spieler diese sehen und sich merken können. Durch einen weiteren Tastendruck wird wieder das Spielfeld eingeblendet. Der Spieler wählt nun mit den Potis eine zweite Karte aus und bestätigt wieder durch einen Tastendruck. Das Spielfeld wird wieder ausgeblendet und stattdessen die beiden aufgedeckten Karten nebeneinander auf dem Display angezeigt. Falls es sich um ein Pärchen handelt, blinkt die Anzeige, der Spieler erhält einen Punkt und ist erneut an der Reihe. Falls es sich nicht um ein Pärchen handelt, ist der andere Spieler an der Reihe. Durch einen Tastendruck wird wieder das Spielfeld angezeigt.
Dies wiederholt sich nun solange, bis alle Karten erfolgreich aufgedeckt wurden. Abschließend wird das Ergebnis dargestellt, wobei die Punkteanzahl des Gewinners blinkt. Durch einen Tastendruck wird das Display abgeschaltet und der Standby-Modus aktiviert. Ein neues Spiel kann durch Einwerfen einer Münze gestartet werden.
Hinweis:
Bei der Auswahl einer Karte über die Potis werden auch freie Felder (also Karte schon als Pärchen aufgedeckt und deshalb nicht mehr vorhanden) durch eine blinkende LED angezeigt. Der Grund liegt darin, dass man ansonsten bei bereits vielen aufgedeckten Pärchen "blind" mit den Potis durchs Spielfeld fahren müsste, bis wieder eine noch nicht aufgedeckte Karte selektiert wäre. Durch die blinkende LED hat man immer einen Überblick, wo man gerade "navigiert". Falls man ein freies Feld angewählt hat, wird jedoch der Tastendruck zum Aufdecken ignoriert, da ja keine Karte auf dem gewählten Feld vorhanden ist. Nur bei vorhandener, also noch nicht aufgedeckter Karte, funktioniert das Aufdecken per Tastendruck.
Quellcode:
Der Code wurde mit dem C-Compiler CodeVisionAVR (Version 2.04.6 Evaluation) erstellt. Diese Evaluation-Version kann für den privaten nicht-kommerziellen Gebrauch kostenlos verwendet werden und ist auf eine Codegröße von 3 kB beschränkt, was für diese Anwendung ausreicht. Das komplette Projekt ist in der ZIP-Datei enthalten. Die erforderliche Projektdatei, um die Software in CodeVisonAVR zu öffnen ist memogame.prj, der eigentliche Quellcode ist in main.c und memo.c enthalten.
Im Array leds[] wird für jede LED der gerade aktive Zustand "ein" oder "aus" gespeichert. Zusätzlich gibt es noch das Array ledsBlink, in dem für jede LED gespeichert wird, ob diese blinken soll oder nicht. In der Interruptroutine des Timers2, die ca. alle 1 ms aufgerufen wird, werden diese Arrays ausgewertet und die LEDs entsprechend angesteuert. In jedem Durchlauf wird die Variable cBlinkcnt erhöht und bei Erreichen des Wertes 250 werden die blinkenden LEDs umgeschaltet, die Blinkfrequenz beträgt also ca. 2 Hz.
Im Array arCards[] wird für jede Karte der gerade aktive Zustand gespeichert: leeres Feld (d.h. Karte wurde schon als Pärchen gefunden), noch nicht aufgedeckte Karte, Karte angewählt bzw. nicht angewählt. Die Kartenmotive sind im Array arImages[] gespeichert.
Download: Quelltext und Hex-File
Quellen/Links:
Wikipedia: http://de.wikipedia.org/wiki/Pairs
C-Compiler CodeVision AVR: http://www.hpinfotech.ro/html/download.htm
/*****************************************************
Compiler : CodeVisionAVR 2.04.6 Evaluation
Chip type : ATmega8
Clock frequency : 8 MHz (int. RC-OSC)
*****************************************************/
//***************************************************
// Spiel "Memo"
// auf Basis des Franzis-Pingpong
// http://www.elo-web.de/ping-pong-start
// 1 zusätzlicher Taster erforderlich,
// Anschluss zwischen PORTD.3 und GND
//***************************************************
#include <mega8.h>
#include <stdlib.h>
#include <delay.h>
#include "memo.h"
#include "main.h"
#define GAME_NOT_STARTED 0
#define GAME_RUNNING 1
#define GAME_OVER 2
unsigned char gameStatus = GAME_NOT_STARTED; // current game status
#define COLUMNS 6 // number of columns for cards
#define ROWS 5 // number of rows for cards
#define MAX_ROW (ROWS-1) // max row for cards
#define MAX_COLUMN (COLUMNS-1) // max column for cards
#define CARDSELECTED 0x01 // status which each card can have
#define CARDPRESENT 0x02
#define CARDHIDDEN 0x04
#define BLINKING 1
unsigned char cYpos = 0;
unsigned char cXpos = 0;
unsigned char cCurrentXpos = 0;
unsigned char cCurrentYpos = 0;
unsigned char cLastYpos = 0;
unsigned char cLastXpos = 0;
typedef struct
{
flash unsigned char *image;
unsigned char status;
} T_CARD;
T_CARD arCards[ROWS][COLUMNS]; // array containing status of each card
unsigned char cScorePlayer1 = 0;
unsigned char cScorePlayer2 = 0;
unsigned char cCurrentPlayer = 1;
unsigned char cRemainingPairs = ROWS*COLUMNS/2;
extern unsigned int leds[]; // current state of each LED (organized in columns)
extern unsigned int ledsBlink[]; // current blink state of each LED (organized in columns)
flash unsigned char arImages[] = // card images
{
0x00, 0x7F, 0x3E, 0x1C, 0x08,
0x08, 0x1C, 0x3E, 0x7F, 0x00,
0x1C, 0x3E, 0x3E, 0x3E, 0x1C,
0x10, 0x38, 0x54, 0x10, 0x1F,
0x04, 0x02, 0x7F, 0x02, 0x04,
0x10, 0x20, 0x7F, 0x20, 0x10,
0x08, 0x08, 0x2A, 0x1C, 0x08,
0x08, 0x1C, 0x2A, 0x08, 0x08,
0x14, 0x08, 0x3E, 0x08, 0x14,
0x60, 0x60, 0x7F, 0x02, 0x0C,
0x10, 0x1E, 0x3F, 0x1E, 0x10,
0x5C, 0x62, 0x02, 0x62, 0x5C,
0x1C, 0x3E, 0x7C, 0x3E, 0x1C,
0x7C, 0x4A, 0x49, 0x4A, 0x7C,
0x63, 0x14, 0x08, 0x14, 0x63
};
flash unsigned int logoMemo[WIDTH] = // start logo
{
0x01F, 0x3E2, 0x044, 0x082, 0x05f, 0x3E0, 0x01F, 0x1D5, 0x235, 0x235, 0x1C0, 0x000
};
flash unsigned int arrowLeftLogo[WIDTH] = // logo for player selection left
{ 0x0010, 0x0038, 0x007C, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 };
flash unsigned int arrowRightLogo[WIDTH] = // logo for player selection right
{ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x007C, 0x0038, 0x0010 };
// show a logo on screen
// pLogo: pointer to array with logo data
// state: 0 = static logo, BLINKING = blinking logo
void showLogo(flash unsigned int *pLogo, unsigned char state)
{
unsigned char i;
for(i = 0; i < WIDTH; i++)
{
leds[i] = pLogo[i];
if(state == BLINKING)
ledsBlink[i] = 0xFFFF;
else
ledsBlink[i] = 0;
}
}
// show a field as selected (blinking ON)
// row: row of field
// col: column postion of field
void selectField(unsigned char row, unsigned char col)
{
leds[col*2] |= ((unsigned int)0x0001 << (row*2));
ledsBlink[col*2] |= ((unsigned int)0x0001 << (row*2));
}
// show a field as unselected (blinking OFF)
// row: row of field
// col: column postion of field
void unselectField(unsigned char row, unsigned char col)
{
ledsBlink[col*2] &= (~((unsigned int)0x0001 << (row*2)));
}
// show an empty field (LED OFF)
// row: row of field
// col: column postion of field
void showEmptyField(unsigned char row, unsigned char col)
{
leds[col*2] &= (~((unsigned int)0x0001 << (row*2)));
}
// show a hidden field (LED ON)
// row: row of field
// col: column postion of field
void showHiddenField(unsigned char row, unsigned char col)
{
leds[col*2] |= ((unsigned int)0x0001 << (row*2));
}
// show an image
// row: row of field
// col: column postion of field
// *image: pointer to image array
void showImage(unsigned char row, unsigned char col, flash unsigned char *image)
{
unsigned int value, mask;
unsigned char i, j;
for(i = 0; i < 5; i++)
{
value = *image++;
mask = 0x7F;
for(j = 0; j < row; j++)
{
value <<= 1;
mask <<= 1;
}
leds[col] &= (~mask);
leds[col] |= value;
col++;
}
}
// show which player may currently play
// (6 LED pixels on left or right side in last display line)
void showCurrentPlayer(void)
{
unsigned char i;
if(cCurrentPlayer == 1)
{
for(i = 6; i < 12; i++)
{
leds[i] &= (~0x200);
}
for(i = 0; i < 6; i++)
{
leds[i] |= 0x200;
}
}
else
{
for(i = 0; i < 6; i++)
{
leds[i] &= (~0x200);
}
for(i = 6; i < 12; i++)
{
leds[i] |= 0x200;
}
}
}
// show each players score
void showScore(void)
{
unsigned char col, col2, i;
clearScreen();
showDecimal(cScorePlayer1, 2, 0);
if(cScorePlayer1 >= 10)
{
col = 6;
col2 = 8;
}
else if(cScorePlayer2 >= 10)
{
col = 4;
col2 = 6;
}
else
{
col = 5;
col2 = 8;
}
leds[col] = 0x28; // show colon :
showDecimal(cScorePlayer2, 2, col2);
if(cScorePlayer1 > cScorePlayer2)
{
for(i = 0; i < col; i++)
ledsBlink[i] = 0xFFFF;
}
else
{
for(i = col+1; i < WIDTH; i++)
ledsBlink[i] = 0xFFFF;
}
}
// redraw all cards dependent on their states
void redrawCards(void)
{
unsigned char cStatus;
unsigned char row, col;
for(row = 0; row < ROWS; row++)
{
for(col = 0; col < COLUMNS; col++)
{
cStatus = arCards[row][col].status;
if(cStatus & CARDSELECTED)
{
selectField(row, col);
}
else
{
unselectField(row, col);
}
if( (cStatus & CARDPRESENT) == 0 )
{
if( (cStatus & CARDSELECTED) == 0 )
{
showEmptyField(row, col);
}
}
else
{
if(cStatus & CARDHIDDEN)
showHiddenField(row, col);
}
}
}
}
// show start logo
void showStartLogo(void)
{
unsigned char i;
flash unsigned int *pImage;
pImage = logoMemo;
for(i = 0; i < 12; i++)
{
leds[i] = *pImage++;
}
}
// handle x/y-positions of potis: unselect previous field, select current field
void select(void)
{
if( (cCurrentXpos != cXpos) || (cCurrentYpos != cYpos) )
{
if(cCurrentXpos != 0xFF)
{
arCards[cCurrentYpos][cCurrentXpos].status &= (~CARDSELECTED);
}
arCards[cYpos][cXpos].status |= CARDSELECTED;
redrawCards();
}
}
// handle keypress after a card was selected: show selected card, check for matching cards
void checkCards(void)
{
static unsigned char cCardSelected = 0;
unsigned char i;
bit bCardsMatch = 0;
if( (arCards[cYpos][cXpos].status & CARDPRESENT) == 0 )
return;
if(cCardSelected)
{ // one card already shown
if( (cXpos == cLastXpos) && (cYpos == cLastYpos) )
return;
arCards[cYpos][cXpos].status &= (~CARDHIDDEN);
arCards[cYpos][cXpos].status &= (~CARDSELECTED);
redrawCards();
if(arCards[cYpos][cXpos].image == arCards[cLastYpos][cLastXpos].image)
{ // cards match
bCardsMatch = 1;
arCards[cLastYpos][cLastXpos].status &= (~CARDPRESENT);
arCards[cYpos][cXpos].status &= (~CARDPRESENT);
redrawCards();
if(cCurrentPlayer == 1)
cScorePlayer1++;
else
cScorePlayer2++;
cRemainingPairs--;
if(cRemainingPairs == 0)
{
gameStatus = GAME_OVER;
}
}
else
{ // cards do not match
arCards[cLastYpos][cLastXpos].status |= CARDHIDDEN;
arCards[cYpos][cXpos].status |= CARDHIDDEN;
redrawCards();
cCurrentPlayer++;
if(cCurrentPlayer > 2)
cCurrentPlayer = 1;
}
cCardSelected = 0;
redrawCards();
clearScreen();
showImage(1, 0, arCards[cLastYpos][cLastXpos].image);
showImage(1, 7, arCards[cYpos][cXpos].image);
if(bCardsMatch)
{
for(i = 0; i < WIDTH; i++)
ledsBlink[i] = 0xFFFF;
}
}
else
{ // first card to show
arCards[cYpos][cXpos].status &= (~CARDHIDDEN);
arCards[cYpos][cXpos].status &= (~CARDSELECTED);
redrawCards();
clearScreen();
showImage(1, 4, arCards[cYpos][cXpos].image);
cCardSelected = 1;
}
cLastXpos = cXpos;
cLastYpos = cYpos;
waitUntilKeyreleased();
waitUntilKeypressed();
arCards[cYpos][cXpos].status |= (CARDSELECTED);
clearScreen();
if(bCardsMatch)
{
for(i = 0; i < WIDTH; i++)
ledsBlink[i] = 0;
}
redrawCards();
}
// setup variables, initializations
void setupGame (void)
{
unsigned char row,col;
unsigned char cnt = 0;
unsigned char cImages;
flash unsigned char *pImage = arImages;
unsigned char i;
unsigned char cValue;
//initialize random generator and reset score
srand(TCNT2);
for(col = 0; col < COLUMNS; col++)
{
for(row = 0; row < ROWS; row++)
{
arCards[row][col].status = CARDPRESENT | CARDHIDDEN;
arCards[row][col].image = 0;
}
}
row = 0;
col = 0;
cnt = 0;
for(cImages = 0; cImages < COLUMNS*ROWS; cImages++)
{
cValue = rand() % 100;
cValue++;
for(i = 0; i < cValue; i++)
{
do
{
if(row < MAX_ROW)
row++;
else
{
row = 0;
if(col < MAX_COLUMN)
col++;
else
col = 0;
}
}
while(arCards[row][col].image != 0);
}
arCards[row][col].image = pImage;
cnt++;
if(cnt == 2)
{
cnt = 0;
pImage += 5;
}
}
clearScreen();
cScorePlayer1 = 0;
cScorePlayer2 = 0;
cRemainingPairs = ROWS*COLUMNS/2;
cXpos = 0;
cYpos = 0;
cCurrentXpos = 0xFF;
redrawCards();
showCurrentPlayer();
}
// show startup screen
void showStartupScreen(void)
{
clearScreen();
showStartLogo();
}
// control loop for memo game
void playMemo(void)
{
unsigned char x, i;
showStartupScreen();
delay_ms(2000);
clearScreen();
do
{
x = selectX();
if(x < 3)
{
cCurrentPlayer = 1;
showLogo(arrowLeftLogo, BLINKING);
}
else
{
cCurrentPlayer = 2;
showLogo(arrowRightLogo, BLINKING);
}
}
while(!keyPressed());
for(i = 0; i < WIDTH; i++)
{
ledsBlink[i] = 0;
}
setupGame();
gameStatus = GAME_RUNNING;
waitUntilKeyreleased();
do
{
cXpos = selectX();
cYpos = selectY();
select();
cCurrentXpos = cXpos;
cCurrentYpos = cYpos;
if(keyPressed())
{
checkCards();
waitUntilKeyreleased();
showCurrentPlayer();
}
}
while(gameStatus != GAME_OVER);
arCards[cYpos][cXpos].status &= (~CARDSELECTED);
redrawCards();
showScore();
waitUntilKeypressed();
waitUntilKeyreleased();
for(i = 0; i < WIDTH; i++)
ledsBlink[i] = 0;
}












