portapack-mayhem/firmware/standalone/pacman/playfield.hpp
Bernd Herzog 100bea644c
Version independent external apps (standalone apps) (#2145)
This pull requests adds a new type of external app to the firmware: The standalone app.

Pros:

Will work after an upgrade.
Size of image is only limited by shared heap size of M0 (application) (64kb total).
Cons:

No full access to all functions in the main firmware. One well defined (and versioned) API handles all communication.
The Pacman app was converted to be the first the the new kind.
2024-05-12 14:55:11 +02:00

1382 lines
39 KiB
C++

#include <cstring>
/******************************************************************************/
/* MAIN GAME VARIABLES */
/******************************************************************************/
#define BONUS_INACTIVE_TIME 600
#define BONUS_ACTIVE_TIME 300
#define START_LIFES 2
#define START_LEVEL 1
typedef uint8_t byte;
typedef uint32_t word;
byte SPEED = 2;
byte MAXLIFES = 20;
size_t LIFES = START_LIFES;
byte GAMEWIN = 0;
byte GAMEOVER = 0;
byte DEMO = 1;
byte LEVEL = START_LEVEL;
byte ACTUALBONUS = 0; // actual bonus icon
byte ACTIVEBONUS = 0; // status of bonus
byte GAMEPAUSED = 0;
byte PACMANFALLBACK = 0;
bool cheat_level = false;
bool cheat_lifes = false;
#include "DrawIndexedMap.h"
/******************************************************************************/
/* Controll KEYPAD LOOP */
/******************************************************************************/
typedef bool boolean;
boolean but_A = false; // 38
boolean but_B = false; // 40
boolean but_UP = false; // 52
boolean but_DOWN = false; // 50
boolean but_LEFT = false; // 48
boolean but_RIGHT = false; // 46
enum cheat_code_keymap {
UP,
DOWN,
LEFT,
RIGHT
};
enum cheat_code_keymap cheat_code[] = {UP, DOWN, UP, DOWN, RIGHT, LEFT, RIGHT, LEFT};
byte cheat_code_index = 0;
/******************************************************************************/
/* GAME VARIABLES AND DEFINITIONS */
/******************************************************************************/
#include "PacmanTiles.h"
enum GameState {
ReadyState,
PlayState,
DeadGhostState, // Player got a ghost, show score sprite and only move eyes
DeadPlayerState,
EndLevelState
};
enum SpriteState {
PenState,
RunState,
FrightenedState,
DeadNumberState,
DeadEyesState,
AteDotState, // pacman
DeadPacmanState
};
enum {
MStopped = 0,
MRight = 1,
MDown = 2,
MLeft = 3,
MUp = 4,
};
#define ushort uint16_t
#define BINKY 0
#define PINKY 1
#define INKY 2
#define CLYDE 3
#define PACMAN 4
#define BONUS 5
const byte _initSprites[] =
{
BINKY,
14,
17 - 3,
31,
MLeft,
PINKY,
14 - 2,
17,
79,
MLeft,
INKY,
14,
17,
137,
MLeft,
CLYDE,
14 + 2,
17,
203,
MRight,
PACMAN,
14,
17 + 9,
0,
MLeft,
BONUS,
14,
17 + 3,
0,
MLeft,
};
// Ghost colors
const byte _palette2[] =
{
0, 11, 1, 15, // BINKY red
0, 11, 3, 15, // PINKY pink
0, 11, 5, 15, // INKY cyan
0, 11, 7, 15, // CLYDE brown
0, 11, 9, 9, // PACMAN yellow
0, 11, 15, 15, // FRIGHTENED
0, 11, 0, 15, // DEADEYES
0, 1, 15, 2, // cherry
0, 1, 15, 12, // strawberry
0, 7, 2, 12, // peach
0, 9, 15, 0, // bell
0, 15, 1, 2, // apple
0, 12, 15, 5, // grape
0, 11, 9, 1, // galaxian
0, 5, 15, 15, // key
};
const byte _paletteIcon2[] =
{
0, 9, 9, 9, // PACMAN
0, 2, 15, 1, // cherry
0, 12, 15, 1, // strawberry
0, 12, 2, 7, // peach
0, 0, 15, 9, // bell
0, 2, 15, 1, // apple
0, 12, 15, 5, // grape
0, 1, 9, 11, // galaxian
0, 5, 15, 15, // key
};
#define PACMANICON 1
#define BONUSICON 2
#define FRIGHTENEDPALETTE 5
#define DEADEYESPALETTE 6
#define BONUSPALETTE 7
#define FPS 60
#define CHASE 0
#define SCATTER 1
#define DOT 7
#define PILL 14
#define PENGATE 0x1B
const byte _opposite[] = {MStopped, MLeft, MUp, MRight, MDown};
#define OppositeDirection(_x) pgm_read_byte(_opposite + _x)
const byte _scatterChase[] = {7, 20, 7, 20, 5, 20, 5, 0};
const byte _scatterTargets[] = {2, 0, 25, 0, 0, 35, 27, 35}; // inky/clyde scatter targets are backwards
const char _pinkyTargetOffset[] = {4, 0, 0, 4, (char)-4, 0, (char)-4, 4}; // Includes pinky target bug
#define FRIGHTENEDGHOSTSPRITE 0
#define GHOSTSPRITE 2
#define NUMBERSPRITE 10
#define PACMANSPRITE 14
const byte _pacLeftAnim[] = {5, 6, 5, 4};
const byte _pacRightAnim[] = {2, 0, 2, 4};
const byte _pacVAnim[] = {4, 3, 1, 3};
word _BonusInactiveTimmer = BONUS_INACTIVE_TIME;
word _BonusActiveTimmer = 0;
/******************************************************************************/
/* GAME - Sprite Class */
/******************************************************************************/
class Sprite {
public:
int16_t _x, _y;
int16_t lastx, lasty;
byte cx, cy; // cell x and y
byte tx, ty; // target x and y
SpriteState state;
byte pentimer; // could be the same
byte who;
byte _speed;
byte dir;
byte lastDir;
byte phase;
// Sprite bits
byte palette2; // 4->16 color map index
byte bits; // index of sprite bits
signed char sy;
void Init(const byte* s) {
who = pgm_read_byte(s++);
cx = pgm_read_byte(s++);
cy = pgm_read_byte(s++);
pentimer = pgm_read_byte(s++);
dir = pgm_read_byte(s);
_x = lastx = (int16_t)cx * 8 - 4;
_y = lasty = (int16_t)cy * 8;
state = PenState;
_speed = 0;
Target(rand() % 20, rand() % 20);
}
void Target(byte x, byte y) {
tx = x;
ty = y;
}
int16_t Distance(byte x, byte y) {
int16_t dx = cx - x;
int16_t dy = cy - y;
return dx * dx + dy * dy; // Distance to target
}
// once per sprite, not 9 times
void SetupDraw(GameState gameState, byte deadGhostIndex) {
sy = 1;
palette2 = who;
byte p = phase >> 3;
if (who == BONUS) {
// BONUS ICONS
bits = 21 + ACTUALBONUS;
palette2 = BONUSPALETTE + ACTUALBONUS;
return;
}
if (who != PACMAN) {
bits = GHOSTSPRITE + ((dir - 1) << 1) + (p & 1); // Ghosts
switch (state) {
case FrightenedState:
bits = FRIGHTENEDGHOSTSPRITE + (p & 1); // frightened
palette2 = FRIGHTENEDPALETTE;
break;
case DeadNumberState:
palette2 = FRIGHTENEDPALETTE;
bits = NUMBERSPRITE + deadGhostIndex;
break;
case DeadEyesState:
palette2 = DEADEYESPALETTE;
break;
default:;
}
return;
}
// PACMAN animation
byte f = (phase >> 1) & 3;
if (dir == MLeft)
f = pgm_read_byte(_pacLeftAnim + f);
else if (dir == MRight)
f = pgm_read_byte(_pacRightAnim + f);
else if (dir == MDown || dir == MUp)
f = pgm_read_byte(_pacVAnim + f);
else if (dir == MStopped) {
if (lastDir == MLeft)
f = pgm_read_byte(_pacLeftAnim + f);
else if (lastDir == MRight)
f = pgm_read_byte(_pacRightAnim + f);
else if (lastDir == MDown || lastDir == MUp)
f = pgm_read_byte(_pacVAnim + f);
}
if ((dir == MUp) || (dir == MStopped && lastDir == MUp))
sy = -1;
bits = f + PACMANSPRITE;
}
// Draw this sprite into the tile at x,y
void Draw8(int16_t x, int16_t y, byte* tile) {
int16_t px = x - (_x - 4);
if (px <= -8 || px >= 16) return;
int16_t py = y - (_y - 4);
if (py <= -8 || py >= 16) return;
// Clip y
int16_t lines = py + 8;
if (lines > 16)
lines = 16;
if (py < 0) {
tile -= py * 8;
py = 0;
}
lines -= py;
// Clip in X
byte right = 16 - px;
if (right > 8)
right = 8;
byte left = 0;
if (px < 0) {
left = -px;
px = 0;
}
// Get bitmap
signed char dy = sy;
if (dy < 0)
py = 15 - py; // VFlip
byte* data = (byte*)(pacman16x16 + bits * 64);
data += py << 2;
dy <<= 2;
data += px >> 2;
px &= 3;
const byte* palette = _palette2 + (palette2 << 2);
while (lines) {
const byte* src = data;
byte d = pgm_read_byte(src++);
d >>= px << 1;
byte sx = 4 - px;
byte x = left;
do {
byte p = d & 3;
if (p) {
p = pgm_read_byte(palette + p);
if (p)
tile[x] = p;
}
d >>= 2; // Next pixel
if (!--sx) {
d = pgm_read_byte(src++);
sx = 4;
}
} while (++x < right);
tile += 8;
data += dy;
lines--;
}
}
};
/******************************************************************************/
/* GAME - Playfield Class */
/******************************************************************************/
class Playfield {
Sprite _sprites[5];
Sprite _BonusSprite; // Bonus
byte _dotMap[(32 / 4) * (36 - 6)];
GameState _state;
long _score; // 7 digits of score
long _hiscore; // 7 digits of score
long _lifescore;
signed char _scoreStr[8];
signed char _hiscoreStr[8];
byte _icons[14]; // Along bottom of screen
ushort _stateTimer;
ushort _frightenedTimer;
byte _frightenedCount;
byte _scIndex; //
ushort _scTimer; // next change of sc status
bool _inited;
byte* _dirty;
public:
Playfield()
: _inited(false) {
// Swizzle palette TODO just fix in place
// byte * p = (byte*)_paletteW;
// for (int16_t i = 0; i < 16; i++)
// {
// ushort w = _paletteW[i]; // Swizzle
// *p++ = w >> 8;
// *p++ = w;
// }
}
// Draw 2 bit BG into 8 bit icon tiles at bottom
void DrawBG2(byte cx, byte cy, byte* tile) {
byte index = 0;
signed char b = 0;
index = _icons[cx >> 1]; // 13 icons across bottom
if (index == 0) {
memset(tile, 0, 64);
return;
}
index--;
index <<= 2; // 4 tiles per icon
b = (1 - (cx & 1)) + ((cy & 1) << 1); // Index of tile
const byte* bg = pacman8x8x2 + ((b + index) << 4);
const byte* palette = _paletteIcon2 + index;
byte x = 16;
while (x--) {
byte bits = (signed char)pgm_read_byte(bg++);
byte i = 4;
while (i--) {
tile[i] = pgm_read_byte(palette + (bits & 3));
bits >>= 2;
}
tile += 4;
}
}
byte GetTile(int16_t cx, int16_t ty) {
if (_state != ReadyState && ty == 20 && cx > 10 && cx < 17) return (0); // READY TEXT ZONE
if (LEVEL % 5 == 1) return pgm_read_byte(playMap1 + ty * 28 + cx);
if (LEVEL % 5 == 2) return pgm_read_byte(playMap2 + ty * 28 + cx);
if (LEVEL % 5 == 3) return pgm_read_byte(playMap3 + ty * 28 + cx);
if (LEVEL % 5 == 4) return pgm_read_byte(playMap4 + ty * 28 + cx);
if (LEVEL % 5 == 0) return pgm_read_byte(playMap5 + ty * 28 + cx);
}
// Draw 1 bit BG into 8 bit tile
void DrawBG(byte cx, byte cy, byte* tile) {
if (cy >= 34) // DRAW ICONS BELLOW MAZE
{
DrawBG2(cx, cy, tile);
return;
}
byte c = 11;
if (LEVEL % 8 == 1) c = 11; // Blue
if (LEVEL % 8 == 2) c = 12; // Green
if (LEVEL % 8 == 3) c = 1; // Red
if (LEVEL % 8 == 4) c = 9; // Yellow
if (LEVEL % 8 == 5) c = 2; // Brown
if (LEVEL % 8 == 6) c = 5; // Cyan
if (LEVEL % 8 == 7) c = 3; // Pink
if (LEVEL % 8 == 0) c = 15; // White
byte b = GetTile(cx, cy);
const byte* bg;
// This is a little messy
memset(tile, 0, 64);
if (cy == 20 && cx >= 11 && cx < 17) {
if (DEMO == 1 && ACTIVEBONUS == 1) return;
if ((_state != ReadyState && GAMEPAUSED != 1 && DEMO != 1) || ACTIVEBONUS == 1)
b = 0; // hide 'READY!'
else if (DEMO == 1 && cx == 11)
b = 0;
else if (DEMO == 1 && cx == 12)
b = 'D';
else if (DEMO == 1 && cx == 13)
b = 'E';
else if (DEMO == 1 && cx == 14)
b = 'M';
else if (DEMO == 1 && cx == 15)
b = 'O';
else if (DEMO == 1 && cx == 16)
b = 0;
else if (GAMEPAUSED == 1 && cx == 11)
b = 'P';
else if (GAMEPAUSED == 1 && cx == 12)
b = 'A';
else if (GAMEPAUSED == 1 && cx == 13)
b = 'U';
else if (GAMEPAUSED == 1 && cx == 14)
b = 'S';
else if (GAMEPAUSED == 1 && cx == 15)
b = 'E';
else if (GAMEPAUSED == 1 && cx == 16)
b = 'D';
} else if (cy == 1) {
if (cx < 7)
b = _scoreStr[cx];
else if (cx >= 10 && cx < 17)
b = _hiscoreStr[cx - 10]; // HiScore
} else {
if (b == DOT || b == PILL) // DOT==7 or PILL==16
{
if (!GetDot(cx, cy))
return;
c = 14;
}
if (b == PENGATE)
c = 14;
}
bg = playTiles + (b << 3);
if (b >= '0')
c = 15; // text is white
for (byte y = 0; y < 8; y++) {
signed char bits = (signed char)pgm_read_byte(bg++); /// WARNING CHAR MUST BE signed !!!
byte x = 0;
while (bits) {
if (bits < 0)
tile[x] = c;
bits <<= 1;
x++;
}
tile += 8;
}
}
// Draw BG then all sprites in this cell
void Draw(uint16_t x, uint16_t y, bool sprites) {
byte tile[8 * 8];
// Fill with BG
if (y == 20 && x >= 11 && x < 17 && DEMO == 1 && ACTIVEBONUS == 1) return;
DrawBG(x, y, tile);
// Overlay sprites
x <<= 3;
y <<= 3;
if (sprites) {
for (byte i = 0; i < 5; i++)
_sprites[i].Draw8(x, y, tile);
// AND BONUS
if (ACTIVEBONUS) _BonusSprite.Draw8(x, y, tile);
}
// Show sprite block
#if 0
for (byte i = 0; i < 5; i++)
{
Sprite* s = _sprites + i;
if (s->cx == (x >> 3) && s->cy == (y >> 3))
{
memset(tile, 0, 8);
for (byte j = 1; j < 7; j++)
tile[j * 8] = tile[j * 8 + 7] = 0;
memset(tile + 56, 0, 8);
}
}
#endif
x += (240 - 224) / 2;
y += (320 - 288) / 2 + 8;
// Should be a direct Graphics call
byte n = tile[0];
byte i = 0;
word color = (word)_paletteW[n];
drawIndexedmap(tile, x, y);
}
boolean updateMap[36][28];
// Mark tile as dirty (should not need range checking here)
void Mark(int16_t x, int16_t y, byte* m) {
x -= 4;
y -= 4;
updateMap[(y >> 3)][(x >> 3)] = true;
updateMap[(y >> 3)][(x >> 3) + 1] = true;
updateMap[(y >> 3)][(x >> 3) + 2] = true;
updateMap[(y >> 3) + 1][(x >> 3)] = true;
updateMap[(y >> 3) + 1][(x >> 3) + 1] = true;
updateMap[(y >> 3) + 1][(x >> 3) + 2] = true;
updateMap[(y >> 3) + 2][(x >> 3)] = true;
updateMap[(y >> 3) + 2][(x >> 3) + 1] = true;
updateMap[(y >> 3) + 2][(x >> 3) + 2] = true;
}
void DrawAllBG() {
for (byte y = 0; y < 36; y++)
for (byte x = 0; x < 28; x++) {
Draw(x, y, false);
}
}
// Draw sprites overlayed on cells
void DrawAll() {
byte* m = _dirty;
// Mark sprite old/new positions as dirty
for (byte i = 0; i < 5; i++) {
Sprite* s = _sprites + i;
Mark(s->lastx, s->lasty, m);
Mark(s->_x, s->_y, m);
}
// Mark BONUS sprite old/new positions as dirty
Sprite* _s = &_BonusSprite;
Mark(_s->lastx, _s->lasty, m);
Mark(_s->_x, _s->_y, m);
// Animation
for (byte i = 0; i < 5; i++)
_sprites[i].SetupDraw(_state, _frightenedCount - 1);
_BonusSprite.SetupDraw(_state, _frightenedCount - 1);
for (byte tmpY = 0; tmpY < 36; tmpY++) {
for (byte tmpX = 0; tmpX < 28; tmpX++) {
if (updateMap[tmpY][tmpX] == true) Draw(tmpX, tmpY, true);
updateMap[tmpY][tmpX] = false;
}
}
}
int16_t Chase(Sprite* s, int16_t cx, int16_t cy) {
while (cx < 0) // Tunneling
cx += 28;
while (cx >= 28)
cx -= 28;
byte t = GetTile(cx, cy);
if (!(t == 0 || t == DOT || t == PILL || t == PENGATE))
return 0x7FFF;
if (t == PENGATE) {
if (s->who == PACMAN)
return 0x7FFF; // Pacman can't cross this to enter pen
if (!(InPen(s->cx, s->cy) || s->state == DeadEyesState))
return 0x7FFF; // Can cross if dead or in pen trying to get out
}
int16_t dx = s->tx - cx;
int16_t dy = s->ty - cy;
return (dx * dx + dy * dy); // Distance to target
}
void UpdateTimers() {
// Update scatter/chase selector, low bit of index indicates scatter
if (_scIndex < 8) {
if (_scTimer-- == 0) {
byte duration = pgm_read_byte(_scatterChase + _scIndex++);
_scTimer = duration * FPS;
}
}
// BONUS timmer
if (ACTIVEBONUS == 0 && _BonusInactiveTimmer-- == 0) {
_BonusActiveTimmer = BONUS_ACTIVE_TIME; // 5*FPS;
ACTIVEBONUS = 1;
}
if (ACTIVEBONUS == 1 && _BonusActiveTimmer-- == 0) {
_BonusInactiveTimmer = BONUS_INACTIVE_TIME; // 10*FPS;
ACTIVEBONUS = 0;
}
// Release frightened ghosts
if (_frightenedTimer && !--_frightenedTimer) {
for (byte i = 0; i < 4; i++) {
Sprite* s = _sprites + i;
if (s->state == FrightenedState) {
s->state = RunState;
s->dir = OppositeDirection(s->dir);
}
}
}
}
// Target closes pill, run from ghosts?
void PacmanAI() {
Sprite* pacman;
pacman = _sprites + PACMAN;
// Chase frightened ghosts
Sprite* closestGhost = NULL;
Sprite* frightenedGhost = NULL;
Sprite* closestAttackingGhost = NULL;
Sprite* DeadEyesStateGhost = NULL;
int16_t dist = 0x7FFF;
int16_t closestfrightenedDist = 0x7FFF;
int16_t closestAttackingDist = 0x7FFF;
for (byte i = 0; i < 4; i++) {
Sprite* s = _sprites + i;
int16_t d = s->Distance(pacman->cx, pacman->cy);
if (d < dist) {
dist = d;
if (s->state == FrightenedState) {
frightenedGhost = s;
closestfrightenedDist = d;
} else {
closestAttackingGhost = s;
closestAttackingDist = d;
}
closestGhost = s;
if (s->state == DeadEyesState) DeadEyesStateGhost = s;
}
}
PACMANFALLBACK = 0;
if (DEMO == 1 && !DeadEyesStateGhost && frightenedGhost) {
pacman->Target(frightenedGhost->cx, frightenedGhost->cy);
return;
}
// Under threat; just avoid closest ghost
if (DEMO == 1 && !DeadEyesStateGhost && dist <= 32 && closestAttackingDist < closestfrightenedDist) {
if (dist <= 16) {
pacman->Target(pacman->cx * 2 - closestAttackingGhost->cx, pacman->cy * 2 - closestAttackingGhost->cy);
PACMANFALLBACK = 1;
} else {
pacman->Target(pacman->cx * 2 - closestAttackingGhost->cx, pacman->cy * 2 - closestAttackingGhost->cy);
}
return;
}
if (ACTIVEBONUS == 1) {
pacman->Target(13, 20);
return;
}
// Go for the pill
if (GetDot(1, 6))
pacman->Target(1, 6);
else if (GetDot(26, 6))
pacman->Target(26, 6);
else if (GetDot(1, 26))
pacman->Target(1, 26);
else if (GetDot(26, 26))
pacman->Target(26, 26);
else {
// closest dot
int16_t dist = 0x7FFF;
for (byte y = 4; y < 32; y++) {
for (byte x = 1; x < 26; x++) {
if (GetDot(x, y)) {
int16_t d = pacman->Distance(x, y);
if (d < dist) {
dist = d;
pacman->Target(x, y);
}
}
}
}
if (dist == 0x7FFF) {
GAMEWIN = 1; // No dots, GAME WIN!
}
}
}
void Scatter(Sprite* s) {
const byte* st = _scatterTargets + (s->who << 1);
s->Target(pgm_read_byte(st), pgm_read_byte(st + 1));
}
void UpdateTargets() {
if (_state == ReadyState)
return;
PacmanAI();
Sprite* pacman = _sprites + PACMAN;
// Ghost AI
bool scatter = _scIndex & 1;
for (byte i = 0; i < 4; i++) {
Sprite* s = _sprites + i;
// Deal with returning ghost to pen
if (s->state == DeadEyesState) {
if (s->cx == 14 && s->cy == 17) // returned to pen
{
s->state = PenState; // Revived in pen
s->pentimer = 80;
} else
s->Target(14, 17); // target pen
continue; //
}
// Release ghost from pen when timer expires
if (s->pentimer) {
if (--s->pentimer) // stay in pen for awhile
continue;
s->state = RunState;
}
if (InPen(s->cx, s->cy)) {
s->Target(14, 14 - 2); // Get out of pen first
} else {
if (scatter || s->state == FrightenedState)
Scatter(s);
else {
// Chase mode targeting
signed char tx = pacman->cx;
signed char ty = pacman->cy;
switch (s->who) {
case PINKY: {
const char* pto = _pinkyTargetOffset + ((pacman->dir - 1) << 1);
tx += pgm_read_byte(pto);
ty += pgm_read_byte(pto + 1);
} break;
case INKY: {
const char* pto = _pinkyTargetOffset + ((pacman->dir - 1) << 1);
Sprite* binky = _sprites + BINKY;
tx += pgm_read_byte(pto) >> 1;
ty += pgm_read_byte(pto + 1) >> 1;
tx += tx - binky->cx;
ty += ty - binky->cy;
} break;
case CLYDE: {
if (s->Distance(pacman->cx, pacman->cy) < 64) {
const byte* st = _scatterTargets + CLYDE * 2;
tx = pgm_read_byte(st);
ty = pgm_read_byte(st + 1);
}
} break;
}
s->Target(tx, ty);
}
}
}
}
// Default to current direction
byte ChooseDir(int16_t dir, Sprite* s) {
int16_t choice[4];
choice[0] = Chase(s, s->cx, s->cy - 1); // Up
choice[1] = Chase(s, s->cx - 1, s->cy); // Left
choice[2] = Chase(s, s->cx, s->cy + 1); // Down
choice[3] = Chase(s, s->cx + 1, s->cy); // Right
if (DEMO == 0 && s->who == PACMAN && choice[0] < 0x7FFF && but_UP)
dir = MUp;
else if (DEMO == 0 && s->who == PACMAN && choice[1] < 0x7FFF && but_LEFT)
dir = MLeft;
else if (DEMO == 0 && s->who == PACMAN && choice[2] < 0x7FFF && but_DOWN)
dir = MDown;
else if (DEMO == 0 && s->who == PACMAN && choice[3] < 0x7FFF && but_RIGHT)
dir = MRight;
else if (DEMO == 0 && choice[0] < 0x7FFF && s->who == PACMAN && dir == MUp) {
dir = MUp;
s->lastDir = MUp;
} else if (DEMO == 0 && choice[1] < 0x7FFF && s->who == PACMAN && dir == MLeft) {
dir = MLeft;
s->lastDir = MLeft;
} else if (DEMO == 0 && choice[2] < 0x7FFF && s->who == PACMAN && dir == MDown) {
dir = MDown;
s->lastDir = MDown;
} else if (DEMO == 0 && choice[3] < 0x7FFF && s->who == PACMAN && dir == MRight) {
dir = MRight;
s->lastDir = MRight;
} else if ((DEMO == 0 && s->who != PACMAN) || DEMO == 1) {
// Don't choose opposite of current direction?
int16_t dist = choice[4 - dir]; // favor current direction
byte opposite = OppositeDirection(dir);
for (byte i = 0; i < 4; i++) {
byte d = 4 - i;
if ((d != opposite && choice[i] < dist) || (s->who == PACMAN && PACMANFALLBACK && choice[i] < dist)) {
if (s->who == PACMAN && PACMANFALLBACK) PACMANFALLBACK = 0;
dist = choice[i];
dir = d;
}
}
} else {
dir = MStopped;
}
return dir;
}
bool InPen(byte cx, byte cy) {
if (cx <= 10 || cx >= 18) return false;
if (cy <= 14 || cy >= 18) return false;
return true;
}
byte GetSpeed(Sprite* s) {
if (s->who == PACMAN)
return _frightenedTimer ? 90 : 80;
if (s->state == FrightenedState)
return 40;
if (s->state == DeadEyesState)
return 100;
if (s->cy == 17 && (s->cx <= 5 || s->cx > 20))
return 40; // tunnel
return 75;
}
void PackmanDied() { // Noooo... PACMAN DIED :(
if (LIFES <= 0 && !cheat_lifes) {
GAMEOVER = 1;
LEVEL = START_LEVEL;
LIFES = START_LIFES;
DEMO = 1;
Init();
} else {
if (!cheat_lifes)
LIFES--;
_inited = true;
_state = ReadyState;
_stateTimer = FPS / 2;
_frightenedCount = 0;
_frightenedTimer = 0;
const byte* s = _initSprites;
for (int16_t i = 0; i < 5; i++)
_sprites[i].Init(s + i * 5);
_scIndex = 0;
_scTimer = 1;
memset(_icons, 0, sizeof(_icons));
// AND BONUS
_BonusSprite.Init(s + 5 * 5);
_BonusInactiveTimmer = BONUS_INACTIVE_TIME;
_BonusActiveTimmer = 0;
for (byte i = 0; i < ACTUALBONUS; i++) {
_icons[13 - i] = BONUSICON + i;
}
if (!cheat_lifes) {
for (byte i = 0; i < LIFES; i++) {
_icons[0 + i] = PACMANICON;
}
} else {
for (byte i = 0; i < 14; i++) {
_icons[0 + i] = PACMANICON;
}
}
// Draw LIFE and BONUS Icons
for (byte y = 34; y < 36; y++)
for (byte x = 0; x < 28; x++) {
Draw(x, y, false);
}
DrawAllBG();
}
}
void MoveAll() {
UpdateTimers();
UpdateTargets();
// Update game state
if (_stateTimer) {
if (--_stateTimer <= 0) {
switch (_state) {
case ReadyState:
_state = PlayState;
_dirty[20 * 4 + 1] |= 0x1F; // Clear 'READY!'
_dirty[20 * 4 + 2] |= 0x80;
for (byte tmpX = 11; tmpX < 17; tmpX++) Draw(tmpX, 20, false); // ReDraw (clear) 'READY' position
break;
case DeadGhostState:
_state = PlayState;
for (byte i = 0; i < 4; i++) {
Sprite* s = _sprites + i;
if (s->state == DeadNumberState)
s->state = DeadEyesState;
}
break;
default:;
}
} else {
if (_state == ReadyState)
return;
}
}
for (byte i = 0; i < 5; i++) {
Sprite* s = _sprites + i;
// In DeadGhostState, only eyes move
if (_state == DeadGhostState && s->state != DeadEyesState)
continue;
// Calculate speed
s->_speed += GetSpeed(s);
if (s->_speed < 100)
continue;
s->_speed -= 100;
s->lastx = s->_x;
s->lasty = s->_y;
s->phase++;
int16_t x = s->_x;
int16_t y = s->_y;
if ((x & 0x7) == 0 && (y & 0x7) == 0) // cell aligned
s->dir = ChooseDir(s->dir, s); // time to choose another direction
switch (s->dir) {
case MLeft:
x -= SPEED;
break;
case MRight:
x += SPEED;
break;
case MUp:
y -= SPEED;
break;
case MDown:
y += SPEED;
break;
case MStopped:
break;
}
// Wrap x because of tunnels
while (x < 0)
x += 224;
while (x >= 224)
x -= 224;
s->_x = x;
s->_y = y;
s->cx = (x + 4) >> 3;
s->cy = (y + 4) >> 3;
if (s->who == PACMAN)
EatDot(s->cx, s->cy);
}
// Collide
Sprite* pacman = _sprites + PACMAN;
// Collide with BONUS
Sprite* _s = &_BonusSprite;
if (ACTIVEBONUS == 1 && _s->cx == pacman->cx && _s->cy == pacman->cy) {
Score(ACTUALBONUS * 50);
ACTUALBONUS++;
if (ACTUALBONUS > 7) {
ACTUALBONUS = 0;
if (LIFES < MAXLIFES) LIFES++;
// reset all icons
memset(_icons, 0, sizeof(_icons));
for (byte i = 0; i < LIFES; i++) {
_icons[0 + i] = PACMANICON;
}
}
for (byte i = 0; i < ACTUALBONUS; i++) {
_icons[13 - i] = BONUSICON + i;
}
// REDRAW LIFE and BONUS icons
for (byte y = 34; y < 36; y++)
for (byte x = 0; x < 28; x++) {
Draw(x, y, false);
}
ACTIVEBONUS = 0;
_BonusInactiveTimmer = BONUS_INACTIVE_TIME;
}
for (byte i = 0; i < 4; i++) {
Sprite* s = _sprites + i;
// if (s->cx == pacman->cx && s->cy == pacman->cy)
if (s->_x + SPEED >= pacman->_x && s->_x - SPEED <= pacman->_x && s->_y + SPEED >= pacman->_y && s->_y - SPEED <= pacman->_y)
{
if (s->state == FrightenedState) {
s->state = DeadNumberState; // Killed a ghost
_frightenedCount++;
_state = DeadGhostState;
_stateTimer = 10;
Score((1 << _frightenedCount) * 100);
} else { // pacman died
if (s->state == DeadNumberState || s->state == FrightenedState || s->state == DeadEyesState) {
} else {
PackmanDied();
}
}
}
}
}
// Mark a position dirty
void Mark(int16_t pos) {
for (byte tmp = 0; tmp < 28; tmp++)
updateMap[1][tmp] = true;
}
void SetScoreChar(byte i, signed char c) {
if (_scoreStr[i] == c)
return;
_scoreStr[i] = c;
Mark(i + 32); // Score
// Mark(i+32+10); //HiScore
}
void SetHiScoreChar(byte i, signed char c) {
if (_hiscoreStr[i] == c)
return;
_hiscoreStr[i] = c;
// Mark(i+32); //Score
Mark(i + 32 + 10); // HiScore
}
void Score(int16_t delta) {
char str[8];
_score += delta;
if (DEMO == 0 && _score > _hiscore) _hiscore = _score;
if (_score > _lifescore && _score % 10000 > 0) {
_lifescore = (_score / 10000 + 1) * 10000;
LIFES++; // EVERY 10000 points = 1UP
for (byte i = 0; i < LIFES; i++) {
_icons[0 + i] = PACMANICON;
}
// REDRAW LIFE and BONUS icons
for (byte y = 34; y < 36; y++)
for (byte x = 0; x < 28; x++) {
Draw(x, y, false);
}
_score = _score + 100;
}
sprintf(str, "%ld", _score);
byte i = 7 - strlen(str);
byte j = 0;
while (i < 7)
SetScoreChar(i++, str[j++]);
sprintf(str, "%ld", _hiscore);
i = 7 - strlen(str);
j = 0;
while (i < 7)
SetHiScoreChar(i++, str[j++]);
}
bool GetDot(byte cx, byte cy) {
return _dotMap[(cy - 3) * 4 + (cx >> 3)] & (0x80 >> (cx & 7));
}
void EatDot(byte cx, byte cy) {
if (!GetDot(cx, cy))
return;
byte mask = 0x80 >> (cx & 7);
_dotMap[(cy - 3) * 4 + (cx >> 3)] &= ~mask;
byte t = GetTile(cx, cy);
if (t == PILL) {
_frightenedTimer = 10 * FPS;
_frightenedCount = 0;
for (byte i = 0; i < 4; i++) {
Sprite* s = _sprites + i;
if (s->state == RunState) {
s->state = FrightenedState;
s->dir = OppositeDirection(s->dir);
}
}
Score(50);
} else
Score(10);
}
void Init() {
if (GAMEWIN == 1) {
GAMEWIN = 0;
} else {
if (!cheat_level) {
LEVEL = START_LEVEL;
}
if (!cheat_lifes) {
LIFES = START_LIFES;
}
ACTUALBONUS = 0; // actual bonus icon
ACTIVEBONUS = 0; // status of bonus
_score = 0;
_lifescore = 10000;
memset(_scoreStr, 0, sizeof(_scoreStr));
_scoreStr[5] = _scoreStr[6] = '0';
}
_inited = true;
_state = ReadyState;
_stateTimer = FPS / 2;
_frightenedCount = 0;
_frightenedTimer = 0;
const byte* s = _initSprites;
for (int16_t i = 0; i < 5; i++)
_sprites[i].Init(s + i * 5);
// AND BONUS
_BonusSprite.Init(s + 5 * 5);
_BonusInactiveTimmer = BONUS_INACTIVE_TIME;
_BonusActiveTimmer = 0;
_scIndex = 0;
_scTimer = 1;
memset(_icons, 0, sizeof(_icons));
// SET BONUS icons
for (byte i = 0; i < ACTUALBONUS; i++) {
_icons[13 - i] = BONUSICON + i;
}
// SET Lifes icons
if (cheat_lifes) {
for (byte i = 0; i < 14; i++) { // cuz 14 lives full fills PP's screen
_icons[0 + i] = PACMANICON;
}
} else {
for (byte i = 0; i < LIFES; i++) {
_icons[0 + i] = PACMANICON;
}
}
// Draw LIFE and BONUS Icons
for (byte y = 34; y < 36; y++)
for (byte x = 0; x < 28; x++) {
Draw(x, y, false);
}
// Init dots from rom
memset(_dotMap, 0, sizeof(_dotMap));
byte* map = _dotMap;
for (byte y = 3; y < 36 - 3; y++) // 30 interior lines
{
for (byte x = 0; x < 28; x++) {
byte t = GetTile(x, y);
if (t == 7 || t == 14) {
byte s = x & 7;
map[x >> 3] |= (0x80 >> s);
}
}
map += 4;
}
DrawAllBG();
}
void Step() {
if (GAMEWIN == 1) {
cheat_level = false;
cheat_lifes = false;
LEVEL++;
Init();
}
// Start GAME
if (but_A && DEMO == 1 && GAMEPAUSED == 0) { // start
but_A = false;
Init();
DEMO = 0;
} else if (but_A && DEMO == 0 && GAMEPAUSED == 0) { // pause
but_A = false;
GAMEPAUSED = 1;
} else if (but_LEFT && DEMO == 1 && GAMEPAUSED == 0) { // -level
if ((cheat_code[cheat_code_index] == 2) && (cheat_code_index <= 8)) {
cheat_code_index++;
}
if (cheat_code_index == 8) {
cheat_level = true;
if (LEVEL > 1) {
LEVEL--;
}
Init();
}
but_LEFT = false;
} else if (but_RIGHT && DEMO == 1 && GAMEPAUSED == 0) { // +level
if ((cheat_code[cheat_code_index] == 3) && (cheat_code_index <= 8)) {
cheat_code_index++;
}
if (cheat_code_index == 8) {
cheat_level = true;
if (LEVEL < 255) {
LEVEL++;
}
Init();
}
but_RIGHT = false;
} else if (but_UP && DEMO == 1 && GAMEPAUSED == 0) { // full of lifes
if ((cheat_code[cheat_code_index] == 0) && (cheat_code_index <= 8)) {
cheat_code_index++;
}
if (cheat_code_index == 8) {
cheat_lifes = true;
Init();
}
but_UP = false;
} else if (but_DOWN && DEMO == 1 && GAMEPAUSED == 0) { // reset
if ((cheat_code[cheat_code_index] == 1) && (cheat_code_index <= 8)) {
cheat_code_index++;
}
if (cheat_code_index == 8) {
cheat_level = false;
cheat_lifes = false;
but_DOWN = false;
LIFES = START_LIFES;
cheat_code_index = 0;
Init();
}
}
if (GAMEPAUSED && but_A && DEMO == 0) {
but_A = false;
GAMEPAUSED = 0;
for (byte tmpX = 11; tmpX < 17; tmpX++) Draw(tmpX, 20, false);
}
// Reset / Start GAME
if (but_B) {
DEMO = 0;
Init();
} else if (!_inited) {
DEMO = 1;
Init();
}
// Create a bitmap of dirty tiles
byte m[(32 / 8) * 36]; // 144 bytes
memset(m, 0, sizeof(m));
_dirty = m;
if (!GAMEPAUSED) MoveAll(); // IF GAME is PAUSED STOP ALL
if ((ACTIVEBONUS == 0 && DEMO == 1) || GAMEPAUSED == 1)
for (byte tmpX = 11; tmpX < 17; tmpX++) Draw(tmpX, 20, false); // Draw 'PAUSED' or 'DEMO' text
DrawAll();
}
};