| Weitere Artikel aus dem Elo-Magazin |
Labyrinth auf der Pong-Platine
von Piotr Platek
Links, rechts, gerade, wieder rechts und ... Sackgasse, die Ecke scheint bekannt zu sein. Wie lange bin ich schon unterwegs? Wer hat diese verdammten Stollen gebaut? Und wo ist die Bombe, die es zu entschärfen gilt? Die Zeit rennt und die Welt ist in der Gefahr. Ein Klassisches Szenario, oder?
Ganzen Artikel lesen...

Viele Computerspiele der unterschiedlichsten Genres, sogenannte „Retro"-Spiele von damals, als noch niemand überhaupt an 3D-Features dachte, gleichermaßen wie heutige Spiele mit 3D-Unterstüztung, nutzen immer wieder die gleiche elementare Komponente, nämlich eine Karte. Die Karte repräsentiert die "Welt" des Spiels, d.h. sie beschreibt die Wege, der Spielhelden und bestimmt das „Universum" des Spiels. In der Kategorie "Labyrinth-Spiele" ist die Karte quasi ausschließliches Hauptelement. Hier muss sich der Spieler durch einen Irrgarten bewegen, um Schätze zu finden, Feinde zu verfolgen oder zu umgehen, etc. Die berühmtesten Spiele dieser Kategorie sind „Pac-Man" und „Bulder-Dash".
Kann die Pong-Platine Labyrinth-Spiele unterstützen? Aber natürlich! Das Spielkonzept wurde ja schon im Vorwort umrissen. Dazu müssen allerdings noch ein paar Dinge näher bestimmt werden.
-- Das Spiel soll mehrere Labyrinthe in unterschiedlichen Spielniveaus anbieten.
-- Die Dimension des Labyrinths soll klar über die 10x12 Punkte der LED-Matrix (Pong-Platine) hinausgehen.
-- Ein neues Labyrinth soll relativ einfach zu definieren sein.
Die Steuerung des Spieles erfolgt über den Joystick des Wii-Nunchuks. Es wird aber auch noch eine vereinfachte Version des Spieles geben, welche die Standard-Pong-Platine mit ihren beiden Potentiometern nutzt, und sich damit an die „ungeduldigen" Tester richtet, die weder Wii-Nunchuk noch I2C Pegelwandler zur Verfügung haben.
Die Pong-Platine zeigt mit ihren 12x10 Punkten nur einen Ausschnitt des deutlich größeren Gesamtfelds des Labyrinths. Das Ausschnitts- bzw. Sichtfenster soll entsprechend den Bewegungen des Spielers über das gesamte Spielfeld (Labyrinth) wandern. Der Satz an verwendeten Labyrinthen wird in einer Tabelle ("gPrgBoard" im Programmspeicher/Flash) gespeichert, wobei jedes Labyrinth als eine Konstantenmenge abgelegt wird.
prog_uint16_t gPrgBoard[SizeY*BOARDN][SizeX]=
{
{0x3FF,0x200,0x2FB,0x282,0x2AE,0x2AA,0x2A2,0x2B8,0x2AF,0x202,0x2FE,0x228,0x2A2,0x2EE,0x282,0x2BA,0x2AA,0x388,0x2AB,0x23A,0x292,0x2F6,0x254,0x210,0x2DF,0x388,0x23A,0x2AA,0x28A,0x2AB,0x2BA,0x288,0x2FA,0x282,0x22A,0x3FF}, ...
Jede „Zelle" der Tabelle repräsentiert einen zehnstelligen Binärwert und beschreibt damit eine Spalte (10 LEDs) des Sichtfensters (Pong-Platine). Die „Wände" des Labyrinths (kein Durchgang) sind mit dem Bitwert „1" (LED=ein) kodiert, die „Wege" (Durchgang) mit dem Bitwert „0" (LED=aus). Die Tabelle beinhaltet mehre Labyrinthe, und die Konstante „BOARDN" enthält die Anzahl der Labyrinthe.
Die Position des „Sichtfensters" (sichtbarer Teil des Labyrinths repräsentiert durch die 12x10 LEDs) bzw. die Position seiner linken, unteren Ecke wird über die beiden Koordinaten „sx" und „sy" („s" wie Screen) relativ zur linken, unteren Ecke des Gesamtspielfelds angegeben.
Die Scroll-Funktion verschiebt die Fläche des Sichtfensters immer dann, wenn der Spieler in die Nähe der Begrenzung (links, rechts, oben und unten) des Sichtfensters (LED-Platine) kommt. Die Entfernung zum Rand, ab der eine Verschiebung ausgelöst wird, ist in der „GAP-Konstante" in der Präprozessoranweisung definiert.
Mit einfarbigen LEDs können (pro LED) an sich nur 2 Zustände abgebildet werden, nämlich „an" bzw. „aus". Beide Zustände werden zur Darstellung von „Wand" bzw. „Weg" genutzt. Um die Position (px, py - p wie Player) des Spielers (dritter Zustand) abgesetzt darzustellen, blinkt die entsprechende LED. Das Gleiche gilt für die Position (hx, hy - h wie Hatz) der Bombe bzw. des Ziels.
Die Bewegung der Spieler und damit auch die Verschiebung des Sichtfensters wird über den Analogjoystick bzw. in der Version ohne Wii-Nunchuk über die zwei Potentiometer der Platine gesteuert.
Die manuelle Erarbeitung eines Labyrinths bzw. die Erstellung der zugehörigen Daten kann sehr aufwendig sein. Natürlich kann ein individuelles Tool für die Entwicklung eines Labyrinths programmiert werden, allerdings ist dies gar nicht nötig. Wir bedienen uns einfach eines Arbeitsblatts einer Standard-Tabellenkalkulation. Die Wände werden mittels Einsen („1") markiert bzw. „gemalt" und die Werte in entsprechenden Hex-Daten konvertiert. Die farbige Darstellung der Wände wird mittels „bedingter Formatierung" erreicht. Das zum Download beigefügte Arbeitsblatt beinhaltet den Aufbau von drei Labyrinthen in verschiedenen Schwierigkeitsstufen. Anschließend müssen die Daten noch im CSV-Format exportiert und wieder in die „main.h"-Datei importiert werden. Die fehlenden Klammern für eine adäquate „C"-Tabellendefinition sollte keine Herausforderung darstellen.
Bei der ersten Version des Programms, habe ich festgestellt, dass die Testanwender gerne mehrere Spielniveaus hätten, und glücklicherweise ist der Flash-Speicher groß genug, um mehrere Spielniveaus zu fassen. Das Spiel wurde dann entsprechend angepasst. Jetzt enthält die Tabelle Labyrinthdaten für 3 verschiedenen Labyrinthe. Die Anzahl der Labyrinthe wird mit der Konstante BOARDN bestimmt und kann jeder Zeit geändert werden. Selbstverständlich müssen die fehlenden Labyrinthe zusätzlich definiert werden.
Das Spiel fängt immer in der linken, unteren Ecke am Punkt mit den Koordinaten (1,1) an. Sowohl die Koordinaten des Zieles (die Bombe) wie auch die Zeit (in Sekunden) für jedes Spielniveau sind in der „main.h"-Datei in der Tabelle „gPrgHunt" definiert.
Die entsprechenden Programme liegen in „C" als AVR-Studio-Projekte und als MS Excel bzw. Open Office Calc zum Download vor.
Dieses Spiel kann durchaus Grundlage für weitere Entwicklungen sein. Eine echte Herausforderung wäre eine Anwendung im Bereich der Künstlichen Intelligenz, wie z.B. die Steuerung eines Roboters. Das könnte ein Staubsauger sein, der die Karte der eigenen Wohnung im seinem Speicher intus hat und alle Ecken bei der Reinigung „besuchen" bzw. abfahren muss.
Wer also programmiert nun den nächsten Algorithmus für eine maschinelle Labyrinthanwendung unter Nutzung der Pong-Platine?
Download: Beide Hex-Files
Download: Quelltexte
/*
* labyrinth/Maze with wii nnchuck for Ping-Pong Board from FRANZIS / conrad.de
*
*
* Ver. Date Author Comments
* ------- ---------- -------------- ------------------------------
* 1.00 07.02.2010 Piotr Platek initial
*
*
* Feel free to provide me any feedback
***************************************************************************
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public License
** as published by the Free Software Foundation; either version 2
** of the License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software Foundation,
** Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
***************************************************************************
*/
#include "main.h"
uint8_t buffor[40] = ""; // buffor for text display & manipulation
void DrawBoard(uint8_t bx, uint8_t by)
{
uint8_t x, xend, mody, divy, colx;
uint16_t screen;
xend = bx+SWIDTH;
// if (by > SizeY*SHIGH-1 ) by = SizeY*SHIGH-1;
// if (bx > SizeX-SWIDTH-1) bx = SizeX-SWIDTH-1;
divy = by / SHIGH;
mody = by % SHIGH;
colx = 0;
for (x=bx; x < xend; x++)
{
screen = pgm_read_word(&gPrgBoard[BoardNumber+divy][x]) << mody;
if (mody)
{
screen |= (pgm_read_word(&gPrgBoard[BoardNumber+divy+1][x]) >> (SHIGH-mody));
}
gLEDs[colx++]=screen;
}
}
int8_t GetPixelBoard(uint8_t bx, uint8_t by)
{
uint8_t mody, divy;
divy = by / SHIGH;
mody = by % SHIGH;
if (pgm_read_word(&gPrgBoard[BoardNumber+divy][bx]) & ( 1 << (SHIGH-1 - mody)))
return 1 ;
else
return 0 ;
}
int main(void)
{
cli();
#ifdef DEBUGON
USART_Init();
#endif
DDRC = 0xFF; // port B, C, D as Output
DDRB = 0xFF; // TWI/C2I will be set by nunchuckInit/TWI funnctions
DDRD = 0xFF;
/* initialize timers */
TCCR0 |= _BV(CS01) | _BV(CS00); // clk prescale (clk/64)
TIMSK |= _BV(TOIE0); // enable timer/counter0 overflow interrupt
TCNT0 = 0; // reset the timer
sei();
nunchuckInit(); // initialize nunchuck
_delay_ms(100); // wait a bit for nunchuck gets ready
nunchuckRead(); // read the data from nunchuck
// srandom((accelX*accelY)/accelZ);// in order to start/seed the random generator
int8_t sx,sy, px, py, tx, ty, rx, ry, dx, dy, lx, ly, hx, hy, BoardIndex, found;
uint16_t GameTimeDef;
BoardIndex = BOARDN-1;
while (1)
{
found = 0;
sx = 0;
sy = 0;
px = 1;
py = 1;
lx = 1;
ly = 1;
huntblink = HUNTBLINKS;
playerblink = PLAYERBLINKS;
hx = (int8_t)(pgm_read_word(&gPrgHunt[BoardIndex][0]));
hy = (int8_t)(pgm_read_word(&gPrgHunt[BoardIndex][1]));
GameTimeDef = pgm_read_word(&gPrgHunt[BoardIndex][2]);
LedCLS();
BoardNumber = BoardIndex*SizeY;
DrawBoard(sx,sy);
gametime = GameTimeDef;
while(gametime && !(found))
{
nunchuckRead(); // read data from nunchuck
// sprintf(buffor, "%d,%d\n\r",joyX, joyY);
// rsprint(buffor);
dx = 0;
dy = 0;
if (joyX < -40) dx = -1; // in case of small movement keep the same position
if (joyX > 40) dx = +1;
if (joyY < -40) dy = -1;
if (joyY > 40) dy = +1;
tx = px+dx;
ty = py+dy;
if ( ( ((tx >= 0) || (tx < SizeX)) && ( (ty>=0) || (ty<SizeY) ) ) && (dx || dy))
{
rx = lx;
ry = ly;
if (!GetPixelBoard(tx,py))
{
if ((lx+dx < GAP) && (dx < 0) && (px+dx >= GAP) )
{
sx += dx;
}
else if ((lx+dx >= SWIDTH-GAP) && (dx > 0) && (px+dx<SizeX-GAP))
{
sx += dx;
}
else
{
lx += dx;
}
px += dx;
}
if (!GetPixelBoard(px,ty))
{
if ((ly+dy < GAP) && (dy < 0) && (py+dy >= GAP) )
{
sy += dy;
}
else if ((ly+dy >= SHIGH-GAP) && (dy > 0) && (py+dy<SizeY*SHIGH-GAP))
{
sy += dy;
}
else
{
ly += dy;
}
py += dy;
}
if (sy > (SizeY-1)*SHIGH) sy = (SizeY-1)*SHIGH;
if (sx > SizeX-SWIDTH) sx = SizeX-SWIDTH;
if (sy < 0) sy = 0;
if (sx < 0) sx = 0;
DrawBoard(sx,sy);
ClearPixel(rx, ry);
SetPixel(lx,ly);
if ( ( px == hx ) && ( py == hy ) )
{
found = 1;
}
}
if (!(playerblink)) // small counter to blink player position
{
XorPixel(lx, ly); // inverse curent ball status
playerblink = PLAYERBLINKS; // restart timer
}
if ( (abs(hx-sx) <= SWIDTH-1) && (abs(hy-sy)<=SHIGH) && (hx-sx >= 0) && (hy-sy >= 0))
{
if (!(huntblink)) // small counter to blink player position
{
XorPixel(abs(hx-sx), abs(hy-sy)); // inverse curent ball status
huntblink = HUNTBLINKS; // restart timer
}
}
if (!butC) // if C pressed move to the initial position
{
sx = 0;
sy = 0;
px = 1;
py = 1;
lx = 1;
ly = 1;
gametime = GameTimeDef;
DrawBoard(sx,sy);
}
_delay_ms(100); // wait a bit
}
LedCLS();
if (found)
{
sprintf((char*)(buffor), "OK!");
BoardIndex--;
if (BoardIndex <0) BoardIndex = BOARDN-1;
}
else
sprintf((char*)(buffor), " :(");
LedPrint(buffor, 0,2);
_delay_ms(5000);
}
return 0;
}















