/******************************************************************************/ /* */ /* 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 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 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 // 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++; } } }