You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1776 lines
48 KiB
1776 lines
48 KiB
/******************************************************************************/
|
|
/* */
|
|
/* PACMAN GAME FOR ARDUINO DUE */
|
|
/* */
|
|
/* Adapted to Teensy 3.6 and 4.0 */
|
|
/* Currently requires the libraries */
|
|
/* ili9341_t3n that can be located: https://github.com/KurtE/ILI9341_t3n */
|
|
/* spin: https://github.com/KurtE/SPIN */
|
|
/* */
|
|
/******************************************************************************/
|
|
/* Copyright (c) 2014 Dr. NCX (mirracle.mxx@gmail.com) */
|
|
/* */
|
|
/* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL */
|
|
/* WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED */
|
|
/* WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR */
|
|
/* BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES */
|
|
/* OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, */
|
|
/* WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, */
|
|
/* ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS */
|
|
/* SOFTWARE. */
|
|
/* */
|
|
/* MIT license, all text above must be included in any redistribution. */
|
|
/******************************************************************************/
|
|
/* ILI9341: */
|
|
/*----------------------------------------------------------------------------*/
|
|
/* Teensy 3.6 Pins */
|
|
/* 8 = RST */
|
|
/* 9 = D/C */
|
|
/* 10 = CS */
|
|
/* */
|
|
/* Teensy 4.0 Beta Pins */
|
|
/* 23 = RST (Marked MCLK on T4 beta breakout) */
|
|
/* 10 = D/C (Marked CS) */
|
|
/* 9 = CS (Marked MEMCS) */
|
|
/* */
|
|
/*----------------------------------------------------------------------------*/
|
|
/* VGA: */
|
|
/*----------------------------------------------------------------------------*/
|
|
/* 41 = R --[470R]-- } */
|
|
/* 40 = R --[ 1K ]-- } = VGA 1 (RED) */
|
|
/* 39 = R --[ 2K2]-- } */
|
|
/* */
|
|
/* 38 = G --[470R]-- } */
|
|
/* 37 = G --[ 1K ]-- } = VGA 2 (GREEN) */
|
|
/* 36 = G --[ 2K2]-- } */
|
|
/* */
|
|
/* 35 = B --[390R]-- } = VGA 3 (BLUE) */
|
|
/* 34 = B --[820R]-- } */
|
|
/* */
|
|
/* 43 = Hsync --[ 82R]-- = VGA 13 */
|
|
/* 42 = Vsync --[ 82R]-- = VGA 14 */
|
|
/* */
|
|
/*----------------------------------------------------------------------------*/
|
|
/* KEYPAD: */
|
|
/*----------------------------------------------------------------------------*/
|
|
/* 38 = button START */
|
|
/* 40 = button SELECT */
|
|
/* 44 = button A */
|
|
/* 42 = button B */
|
|
/* 52 = button UP */
|
|
/* 50 = button DOWN */
|
|
/* 48 = button LEFT */
|
|
/* 46 = button RIGHT */
|
|
/* */
|
|
/******************************************************************************/
|
|
|
|
#define USE_ILI // For ILI9341 leave uncommented
|
|
#include <ILI9341_t3n.h>
|
|
byte SPEED = 2; // 1=SLOW 2=NORMAL 4=FAST //do not try other values!!!
|
|
|
|
/******************************************************************************/
|
|
/* MAIN GAME VARIABLES */
|
|
/******************************************************************************/
|
|
|
|
#define BONUS_INACTIVE_TIME 600
|
|
#define BONUS_ACTIVE_TIME 300
|
|
|
|
#define START_LIFES 2
|
|
#define START_LEVEL 1
|
|
|
|
byte MAXLIFES = 5;
|
|
byte 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;
|
|
|
|
/******************************************************************************/
|
|
/* LIBRARIES INCLUDES */
|
|
/******************************************************************************/
|
|
|
|
//Library for Gamepad
|
|
#include <USBHost_t36.h>
|
|
USBHost myusb;
|
|
USBHub hub1(myusb);
|
|
USBHIDParser hid1(myusb);
|
|
JoystickController joystick1(myusb);
|
|
|
|
//BluetoothController bluet(myusb, true, "0000"); // Version does pairing to device
|
|
BluetoothController bluet(myusb); // version assumes it already was paired
|
|
USBDriver *drivers[] = {&hub1, &joystick1, &bluet, &hid1};
|
|
#define CNT_DEVICES (sizeof(drivers)/sizeof(drivers[0]))
|
|
const char * driver_names[CNT_DEVICES] = {"Hub1", "JOY1D", "Bluet", "HID1"};
|
|
|
|
bool driver_active[CNT_DEVICES] = {false, false, false, false};
|
|
|
|
// Lets also look at HID Input devices
|
|
USBHIDInput *hiddrivers[] = {&joystick1};
|
|
|
|
bool hid_driver_active[CNT_DEVICES] = {false, false, false};
|
|
bool show_changed_only = false;
|
|
bool show_raw_data = false;
|
|
bool show_changed_data = false;
|
|
uint32_t buttons;
|
|
int psAxis[64];
|
|
bool first_joystick_message = true;
|
|
uint8_t last_bdaddr[6] = {0, 0, 0, 0, 0, 0};
|
|
/* END JOYSTICK DEFINES */
|
|
|
|
|
|
// SPI Library
|
|
#include <SPI.h>
|
|
|
|
|
|
// Connection konfiguration of ILI9341 LCD TFT
|
|
#if defined(__MK66FX1M0__)
|
|
#define TFT_RST 8
|
|
#define TFT_DC 9
|
|
#define TFT_CS 10
|
|
#elif defined(__IMXRT1052__) || defined(__IMXRT1062__)
|
|
// On Teensy 4 beta with Paul's breakout out:
|
|
// Using pins (MOSI, MISO, SCK which are labeled on Audio board breakout location
|
|
// which are not in the Normal processor positions
|
|
// Also DC=10(CS), CS=9(BCLK) and RST 23(MCLK)
|
|
#define TFT_RST 23
|
|
#define TFT_DC 10
|
|
#define TFT_CS 9
|
|
#else
|
|
#error "This example App will only work with Teensy 3.6 or Teensy 4."
|
|
#endif
|
|
ILI9341_t3n tft = ILI9341_t3n(TFT_CS, TFT_DC, TFT_RST);
|
|
/******************************************************************************/
|
|
/* Controll KEYPAD LOOP */
|
|
/******************************************************************************/
|
|
|
|
boolean but_START = false; //38 //8, 168
|
|
boolean but_SELECT = false; //40 //1 , 328
|
|
boolean but_A = false; //44 //32768 sqr, 262152
|
|
boolean but_B = false; //42 //8192 circ,
|
|
boolean but_UP = false; //52 //16, 0
|
|
boolean but_DOWN = false; //50 //64, 4
|
|
boolean but_LEFT = false; //48 //128, 6
|
|
boolean but_RIGHT = false; //46 //32, 2
|
|
|
|
void ClearKeys() {
|
|
but_START = false;
|
|
but_SELECT = false;
|
|
but_A = false;
|
|
but_B = false;
|
|
but_UP = false;
|
|
but_DOWN = false;
|
|
but_LEFT = false;
|
|
but_RIGHT = false;
|
|
}
|
|
|
|
void KeyPadLoop() {
|
|
buttons = joystick1.getButtons();
|
|
|
|
Serial.println(buttons);
|
|
switch (joystick1.joystickType()) {
|
|
case JoystickController::UNKNOWN:
|
|
case JoystickController::PS4:
|
|
{
|
|
if (buttons == 168 ) {
|
|
ClearKeys(); //else but_START=false;
|
|
but_START = true;
|
|
delay(300);
|
|
}
|
|
if (buttons == 328 ) {
|
|
ClearKeys();
|
|
but_SELECT = true;
|
|
delay(300);
|
|
} else but_SELECT = false;
|
|
if (buttons == 262152 ) {
|
|
ClearKeys();
|
|
but_A = true;
|
|
} else but_A = false;
|
|
if (buttons == 262152) {
|
|
ClearKeys();
|
|
but_B = true;
|
|
} else but_B = false;
|
|
if (buttons == 0) {
|
|
ClearKeys(); //else but_UP=false;
|
|
but_UP = true;
|
|
}
|
|
if (buttons == 4) {
|
|
ClearKeys(); //else but_DOWN=false;
|
|
but_DOWN = true;
|
|
}
|
|
if (buttons == 6) {
|
|
ClearKeys(); // else but_LEFT=false;
|
|
but_LEFT = true;
|
|
}
|
|
if (buttons == 2) {
|
|
ClearKeys(); //else but_RIGHT=false;
|
|
but_RIGHT = true;
|
|
}
|
|
}
|
|
break;
|
|
case JoystickController::PS3:
|
|
{
|
|
if (buttons == 8 ) {
|
|
ClearKeys(); //else but_START=false;
|
|
but_START = true;
|
|
delay(300);
|
|
}
|
|
if (buttons == 1 ) {
|
|
ClearKeys();
|
|
but_SELECT = true;
|
|
delay(300);
|
|
} else but_SELECT = false;
|
|
if (buttons == 32768 ) {
|
|
ClearKeys();
|
|
but_A = true;
|
|
} else but_A = false;
|
|
if (buttons == 8192) {
|
|
ClearKeys();
|
|
but_B = true;
|
|
} else but_B = false;
|
|
if (buttons == 16) {
|
|
ClearKeys(); //else but_UP=false;
|
|
but_UP = true;
|
|
}
|
|
if (buttons == 64) {
|
|
ClearKeys(); //else but_DOWN=false;
|
|
but_DOWN = true;
|
|
}
|
|
if (buttons == 128) {
|
|
ClearKeys(); // else but_LEFT=false;
|
|
but_LEFT = true;
|
|
}
|
|
if (buttons == 32) {
|
|
ClearKeys(); //else but_RIGHT=false;
|
|
but_RIGHT = true;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
yield();
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/* 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 C16(_rr,_gg,_bb) (((ushort)(((_rr & 0xE0)) | ((_gg & 0xE0) >> 3) | ((_bb & 0xC0) >> 6)) <<8 ))
|
|
|
|
// 8 bit palette for ILI9341
|
|
uint16_t _paletteW[] =
|
|
{
|
|
C16(0, 0, 0),
|
|
C16(255, 0, 0), // 1 red
|
|
C16(222, 151, 81), // 2 brown
|
|
C16(255, 0, 255), // 3 pink
|
|
|
|
C16(0, 0, 0),
|
|
C16(0, 255, 255), // 5 cyan
|
|
C16(71, 84, 255), // 6 mid blue
|
|
C16(255, 184, 81), // 7 lt brown
|
|
|
|
C16(0, 0, 0),
|
|
C16(255, 255, 0), // 9 yellow
|
|
C16(0, 0, 0),
|
|
C16(33, 33, 255), // 11 blue
|
|
|
|
C16(0, 255, 0), // 12 green
|
|
C16(71, 84, 174), // 13 aqua
|
|
C16(255, 184, 174), // 14 lt pink
|
|
C16(222, 222, 255), // 15 whiteish
|
|
};
|
|
|
|
#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 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(random(20), random(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
|
|
f = pgm_read_byte(_pacVAnim + f);
|
|
if (dir == 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);
|
|
return 0; //
|
|
}
|
|
|
|
// 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;
|
|
|
|
|
|
// Should be a direct Graphics call
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
#ifdef USE_ILI
|
|
drawIndexedmap(tile, x, y, 8, 8); // Call Custom function in ILI9341_DUE.h library
|
|
#endif
|
|
//------------------------------------------------------------------------------
|
|
#ifdef USE_VGA
|
|
byte n = tile[0];
|
|
for (byte tmpY = 0; tmpY < 8; tmpY++) {
|
|
|
|
word width = 1;
|
|
|
|
for (byte tmpX = 0; tmpX < 8; tmpX++) {
|
|
|
|
n = tile[++i];
|
|
|
|
word next_color = (word)_paletteW[n];
|
|
|
|
if (x + tmpX > 224 + (240 - 224) / 2) {
|
|
break;
|
|
}
|
|
|
|
if (tmpX == 7 && width == 1 ) {
|
|
VGA.drawPixel(x + tmpX, y + tmpY, color);
|
|
color = next_color;
|
|
width = 0;
|
|
}
|
|
else if (tmpX == 7 && width > 1 ) {
|
|
VGA.drawHLine( y + tmpY, x + tmpX - width + 1, x + tmpX + 1, color);
|
|
color = next_color;
|
|
width = 0;
|
|
}
|
|
else if (color != next_color && width == 1) {
|
|
VGA.drawPixel(x + tmpX, y + tmpY, color);
|
|
color = next_color;
|
|
width = 0;
|
|
}
|
|
else if (color != next_color && width > 1) {
|
|
VGA.drawHLine( y + tmpY, x + tmpX - width + 1, x + tmpX + 1, color);
|
|
color = next_color;
|
|
width = 0;
|
|
}
|
|
width++;
|
|
|
|
}
|
|
}
|
|
#endif
|
|
//------------------------------------------------------------------------------
|
|
}
|
|
|
|
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;
|
|
else if (DEMO == 0 && choice[1] < 0x7FFF && s->who == PACMAN && dir == MLeft) dir = MLeft;
|
|
else if (DEMO == 0 && choice[2] < 0x7FFF && s->who == PACMAN && dir == MDown) dir = MDown;
|
|
else if (DEMO == 0 && choice[3] < 0x7FFF && s->who == PACMAN && dir == MRight) dir = 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; //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) {
|
|
GAMEOVER = 1;
|
|
LEVEL = START_LEVEL;
|
|
LIFES = START_LIFES;
|
|
DEMO = 1;
|
|
Init();
|
|
} else {
|
|
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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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 {
|
|
LEVEL = START_LEVEL;
|
|
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
|
|
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) {
|
|
LEVEL++;
|
|
Init();
|
|
}
|
|
|
|
// Start GAME
|
|
if (but_START && DEMO == 1 && GAMEPAUSED == 0) {
|
|
but_START = false;
|
|
DEMO = 0;
|
|
Init();
|
|
} else if (but_START && DEMO == 0 && GAMEPAUSED == 0) { // Or PAUSE GAME
|
|
but_START = false;
|
|
GAMEPAUSED = 1;
|
|
}
|
|
|
|
if (GAMEPAUSED && but_START && DEMO == 0) {
|
|
but_START = false;
|
|
GAMEPAUSED = 0;
|
|
for (byte tmpX = 11; tmpX < 17; tmpX++) Draw(tmpX, 20, false);
|
|
}
|
|
|
|
// Reset / Start GAME
|
|
if (but_SELECT) {
|
|
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();
|
|
}
|
|
};
|
|
|
|
/******************************************************************************/
|
|
/* SETUP */
|
|
/******************************************************************************/
|
|
|
|
#define BLACK 0x0000 // 16bit BLACK Color
|
|
|
|
void setup() {
|
|
|
|
randomSeed(analogRead(0));
|
|
|
|
// TFT 2.2" SPI
|
|
Serial1.begin(2000000);
|
|
Serial.begin(9600);
|
|
|
|
Serial.println("\n\nUSB Host Testing");
|
|
Serial.println(sizeof(USBHub), DEC);
|
|
myusb.begin();
|
|
delay(500);
|
|
|
|
//------------------------------------------------------------------------------
|
|
#ifdef USE_ILI
|
|
// SETUP TFT LCD
|
|
if (TFT_RST < 255) {
|
|
pinMode(TFT_RST, OUTPUT);
|
|
digitalWrite(TFT_RST, HIGH);
|
|
delay(5);
|
|
digitalWrite(TFT_RST, LOW);
|
|
delay(20);
|
|
digitalWrite(TFT_RST, HIGH);
|
|
delay(150);
|
|
}
|
|
|
|
tft.begin();
|
|
delay(100);
|
|
tft.setRotation(2); // 180
|
|
delay(100);
|
|
tft.fillScreen(BLACK);
|
|
tft.setTextColor(ILI9341_YELLOW);
|
|
tft.setTextSize(2);
|
|
tft.println("Waiting for Joystick to connect...");
|
|
#endif
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/* LOOP */
|
|
/******************************************************************************/
|
|
|
|
Playfield _game;
|
|
|
|
void loop() {
|
|
if (joystick1.available()) {
|
|
if (first_joystick_message) {
|
|
tft.fillScreen(BLACK);
|
|
Serial.printf("*** First Joystick message %x:%x ***\n",
|
|
joystick1.idVendor(), joystick1.idProduct());
|
|
first_joystick_message = false;
|
|
|
|
const uint8_t *psz = joystick1.manufacturer();
|
|
if (psz && *psz) Serial.printf(" manufacturer: %s\n", psz);
|
|
psz = joystick1.product();
|
|
if (psz && *psz) Serial.printf(" product: %s\n", psz);
|
|
psz = joystick1.serialNumber();
|
|
if (psz && *psz) Serial.printf(" Serial: %s\n", psz);
|
|
|
|
// lets try to reduce number of fields that update
|
|
joystick1.axisChangeNotifyMask(0xFFFFFl);
|
|
}
|
|
|
|
for (uint8_t i = 0; i < 64; i++) {
|
|
psAxis[i] = joystick1.getAxis(i);
|
|
}
|
|
|
|
KeyPadLoop();
|
|
|
|
_game.Step();
|
|
yield();
|
|
}
|
|
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------------
|
|
#define __C16(_rr,_gg,_bb) ((uint16_t)(((_rr & 0xF8) << 8) | ((_gg & 0xFC) << 3) | ((_bb & 0xF8) >> 3)))
|
|
|
|
// 8 bit palette - in RAM because of graphics driver
|
|
uint16_t __paletteW[] =
|
|
{
|
|
__C16(0,0,0),
|
|
__C16(255,0,0), // 1 red
|
|
__C16(222,151,81), // 2 brown
|
|
__C16(255,128,255), // 3 pink
|
|
|
|
__C16(0,0,0),
|
|
__C16(0,255,255), // 5 cyan
|
|
__C16(71,84,255), // 6 mid blue
|
|
__C16(255,184,81), // 7 lt brown
|
|
|
|
__C16(0,0,0),
|
|
__C16(255,255,0), // 9 yellow
|
|
__C16(0,0,0),
|
|
__C16(33,33,255), // 11 blue
|
|
|
|
__C16(0,255,0), // 12 green
|
|
__C16(71,84,174), // 13 aqua
|
|
__C16(255,184,174), // 14 lt pink
|
|
__C16(222,222,255), // 15 whiteish
|
|
};
|
|
|
|
void drawIndexedmap(const uint8_t *indexmap, int16_t x, int16_t y, uint16_t w, uint16_t h) {
|
|
|
|
byte i = 0;
|
|
word color = (word)__paletteW[indexmap[0]];
|
|
|
|
for (byte tmpY = 0; tmpY < 8; tmpY++) {
|
|
|
|
byte width = 1;
|
|
|
|
for (byte tmpX = 0; tmpX < 8; tmpX++) {
|
|
word next_color = (word)__paletteW[indexmap[++i]];
|
|
|
|
if ((color != next_color && width >= 1) || tmpX == 7 ) {
|
|
|
|
///drawPixel_cont(x+tmpX, y+tmpY, color);
|
|
tft.drawFastHLine(x + tmpX - width + 1, y + tmpY, width, color);
|
|
color = next_color;
|
|
width = 0;
|
|
}
|
|
width++;
|
|
}
|
|
}
|
|
} |