pacman from dindinx
[soft/lpc122x/tigp] / pacman / main.c
diff --git a/pacman/main.c b/pacman/main.c
new file mode 100644 (file)
index 0000000..5f7a77b
--- /dev/null
@@ -0,0 +1,1192 @@
+/****************************************************************************
+ *   apps/.*./main.c
+ *
+ * LPC1224/SSD1306 implementation of the famous pacman game
+ *
+ * Copyright 2017 Nathael Pajani <nathael.pajani@ed3l.fr>
+ *
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ *************************************************************************** */
+
+#define ALLOW_SCREENSHOT
+
+#include "core/pio.h"
+#include "core/system.h"
+#include "core/systick.h"
+#include "lib/string.h"
+#include "drivers/gpio.h"
+#include "drivers/ssp.h"
+#ifdef ALLOW_SCREENSHOT
+#include "lib/stdio.h"
+#include "drivers/serial.h"
+#endif
+
+#include "extdrv/status_led.h"
+#include "extdrv/ssd130x_oled_driver.h"
+#include "extdrv/ssd130x_oled_buffer.h"
+
+#include "data.h"
+
+#define MODULE_VERSION    0x01
+#define MODULE_NAME "Pacman"
+
+#define SELECTED_FREQ  FREQ_SEL_48MHz
+
+/***************************************************************************** */
+/* Pins configuration */
+/* pins blocks are passed to set_pins() for pins configuration.
+ * Unused pin blocks can be removed safely with the corresponding set_pins() call
+ * All pins blocks may be safelly merged in a single block for single set_pins() call..
+ */
+const struct pio_config common_pins[] = {
+#ifdef ALLOW_SCREENSHOT
+       /* UART 0 : Config / Debug / USB */
+       { LPC_UART0_RX_PIO_0_1,  LPC_IO_DIGITAL },
+       { LPC_UART0_TX_PIO_0_2,  LPC_IO_DIGITAL },
+#endif
+       /* SPI : Display */
+       { LPC_SSP0_SCLK_PIO_0_14, LPC_IO_DIGITAL },
+       { LPC_SSP0_MISO_PIO_0_16, LPC_IO_DIGITAL },
+       { LPC_SSP0_MOSI_PIO_0_17, LPC_IO_DIGITAL },
+       /* buttons */
+       { LPC_GPIO_0_12, LPC_IO_DIGITAL }, /* ISP / User button OK */
+
+       { LPC_GPIO_0_6, LPC_IO_DIGITAL }, /* User button B4 */
+       { LPC_GPIO_0_5, LPC_IO_DIGITAL }, /* User button B3 */
+       { LPC_GPIO_0_4, LPC_IO_DIGITAL }, /* User button B2 */
+       { LPC_GPIO_0_3, LPC_IO_DIGITAL }, /* User button B1 */
+       ARRAY_LAST_PIO,
+};
+
+const struct pio status_led_green = LPC_GPIO_0_27;
+const struct pio status_led_red = LPC_GPIO_0_28;
+
+/* Buttons */
+const struct pio button_up    = LPC_GPIO_0_3; // B1
+const struct pio button_down  = LPC_GPIO_0_6; // B4
+const struct pio button_left  = LPC_GPIO_0_4; // B2
+const struct pio button_right = LPC_GPIO_0_5; // B3
+const struct pio button_ok    = LPC_GPIO_0_12; // BOK
+
+/***************************************************************************** */
+/* Basic system init and configuration */
+
+void system_init()
+{
+       /* Stop the Watchdog */
+       startup_watchdog_disable(); /* Do it right now, before it gets a chance to break in */
+       system_set_default_power_state();
+       clock_config(SELECTED_FREQ);
+       set_pins(common_pins);
+       gpio_on();
+       status_led_config(&status_led_green, &status_led_red);
+       /* System tick timer MUST be configured and running in order to use the sleeping
+        * functions */
+       systick_timer_on(1); /* 1ms */
+       systick_start();
+}
+
+/****************************************************************************/
+/* Oled Display */
+#define DISPLAY_ADDR   0x78
+static uint8_t gddram[ 4 + GDDRAM_SIZE ];
+struct oled_display display = {
+       .bus_type = SSD130x_BUS_SPI,
+       .address = DISPLAY_ADDR,
+       .bus_num = SSP_BUS_0,
+       .charge_pump = SSD130x_INTERNAL_PUMP,
+       .video_mode = SSD130x_DISP_NORMAL,
+       .contrast = 128,
+       .scan_dir = SSD130x_SCAN_BOTTOM_TOP,
+       .read_dir = SSD130x_RIGHT_TO_LEFT,
+       .display_offset_dir = SSD130x_MOVE_TOP,
+       .display_offset = 4,
+       .gddram = gddram,
+       .gpio_cs = LPC_GPIO_1_2,
+       .gpio_dc = LPC_GPIO_1_1,
+       .gpio_rst = LPC_GPIO_1_3,
+
+};
+
+#ifdef ALLOW_SCREENSHOT
+static void data_rx(uint8_t __attribute((unused))c)
+{
+}
+
+volatile int take_screenshot = 0;
+static void set_screenshot(uint32_t __attribute__((unused))gpio)
+{
+  take_screenshot = 1;
+}
+
+static void send_screenshot(void)
+{
+  int y, x, xx;
+  uprintf(UART0, "P4\n128 64\n");
+  for (y = 0; y < 64; y++)
+    for (x = 0; x < 128; x += 8)
+    {
+      uint32_t pattern = 0;
+      for (xx = 0; xx < 8; xx++)
+      {
+        pattern <<= 1;
+        if ((gddram[4 + (y/8)*128 + x + xx] >> (y & 7)) & 1)
+        {
+          pattern |= 1;
+        }
+      }
+      uprintf(UART0, "%c", pattern);
+    }
+  uprintf(UART0, "\n");
+}
+#endif
+
+/****************************************************************************/
+
+enum GhostNames
+{
+  GN_BLINKY,
+  GN_PINKY,
+  GN_INKY,
+  GN_CLYDE,
+  GN_NB_GHOSTS
+};
+
+enum GhostModes
+{
+  GM_CORNERS,
+  GM_FRIGHTENED,
+  GM_CHASE,
+  GM_EATEN,
+  GM_WAITING,
+  GM_IN_PEN
+};
+
+static uint32_t current_pills[31];
+
+struct Vec2
+{
+  int x, y;
+};
+
+struct Ghost
+{
+  struct Vec2 position;  /* */
+  struct Vec2 direction; /* */
+  struct Vec2 offset;    /* */
+  struct Vec2 target;    /* */
+  enum GhostModes mode;  /* */
+  int stopped;
+};
+
+struct Ghost ghosts[4] = 
+{
+  {
+    { 16, 11 }, { 1, 0 }, { 0, 0 }, { 28, -2 }, GM_CORNERS, 0
+  },
+  {
+    { 13, 14 }, { 1, 0 }, { 0, 0 }, { 5, -2 }, GM_IN_PEN, 0
+
+  },
+  {
+    { 15, 14 }, { 1, 0 }, { 0, 0 }, { 5, 33 }, GM_IN_PEN, 0
+
+  },
+  {
+    { 17, 14 }, { 1, 0 }, { 0, 0 }, { 28, 33 }, GM_IN_PEN, 0
+  }
+};
+
+struct Pacman
+{
+  struct Vec2 position;
+  struct Vec2 direction;
+  struct Vec2 sub_step;
+  struct Vec2 next_dir;
+  int shape, anim;
+};
+
+struct Pacman pacman =
+{
+  { 15, 23 },
+  { 0, 0 },
+  { 0, 0 },
+  { 0, 0 },
+  F_PACMAN_LEFT_0, 1
+};
+
+static int screen_offset = 0;
+static int current_frame = 0;
+static uint8_t score[5];
+static uint8_t ghost_score = 0;
+
+static volatile uint8_t button_up_pressed = 0;
+static volatile uint8_t button_down_pressed = 0;
+static volatile uint8_t button_left_pressed = 0;
+static volatile uint8_t button_right_pressed = 0;
+
+static void handle_buttons(uint32_t gpio)
+{ // FIXME: ugly
+  if (gpio == button_up.pin)
+  {
+    if (gpio_read(button_up) == 0)
+      button_up_pressed = 1;
+    else
+      button_up_pressed = 0;
+  }
+  if (gpio == button_left.pin)
+  {
+    if (gpio_read(button_left) == 0)
+      button_left_pressed = 1;
+    else
+      button_left_pressed = 0;
+  }
+  if (gpio == button_down.pin)
+  {
+    if (gpio_read(button_down) == 0)
+      button_down_pressed = 1;
+    else
+      button_down_pressed = 0;
+  }
+  if (gpio == button_right.pin)
+  {
+    if (gpio_read(button_right) == 0)
+      button_right_pressed = 1;
+    else
+      button_right_pressed = 0;
+  }
+}
+
+/*==========================================================================*\
+ * my_rand                                                                  *
+\*==========================================================================*/
+static uint32_t my_rand()
+{ 
+  static uint32_t x = 123456789;
+  static uint32_t y = 234567891;
+  static uint32_t z = 345678912;
+  static uint32_t w = 456789123;
+  static uint32_t c = 0;
+  int t;
+
+  y ^= (y << 5);
+  y ^= (y >> 7);
+  y ^= (y << 22); 
+  t = z + w + c;
+  z = w;
+  c = t < 0;
+  w = t & 2147483647; 
+  x += 1411392427; 
+
+  return x + y + w; 
+}
+
+/*==========================================================================*\
+ * draw_glyph                                                               *
+\*==========================================================================*/
+static void draw_glyph(int x, int y, uint8_t glyph)
+{
+  int i;
+  for (i = 0; i < 8; i++)
+    gddram[4 + y * 128 + x + i] = font[glyph * 8 + i];
+}
+
+/*==========================================================================*\
+ * score_add_10000                                                          *
+\*==========================================================================*/
+static void score_add_10000(int digit)
+{
+  score[1] += digit;
+  if (score[1] >9)
+  {
+    score[1] -= 10;
+    score[0]++;
+  }
+}
+
+/*==========================================================================*\
+ * score_add_1000                                                           *
+\*==========================================================================*/
+static void score_add_1000(int digit)
+{
+  score[2] += digit;
+  if (score[2] > 9)
+  {
+    score[2] -= 10;
+    score_add_10000(1);
+  }
+}
+/*==========================================================================*\
+ * score_add_100                                                            *
+\*==========================================================================*/
+static void score_add_100(int digit)
+{
+  score[3] += digit;
+  if (score[3] > 9)
+  {
+    score[3] -= 10;
+    score_add_1000(1);
+  }
+}
+
+/*==========================================================================*\
+ * score_add_10                                                             *
+\*==========================================================================*/
+static void score_add_10(int digit)
+{
+  score[4] += digit;
+  if (score[4] > 9)
+  {
+    score[4] -= 10;
+    score_add_100(1);
+  }
+}
+
+/*==========================================================================*\
+ * draw_score                                                               *
+\*==========================================================================*/
+static void draw_score(void)
+{
+  // Note this should be called last, so this can hide a ghost in the tunnel
+  draw_glyph(0, 0, F_BLANK);
+  draw_glyph(0, 1, F_DIGIT_0 + score[0]);
+  draw_glyph(0, 2, F_DIGIT_0 + score[1]);
+  draw_glyph(0, 3, F_DIGIT_0 + score[2]);
+  draw_glyph(0, 4, F_DIGIT_0 + score[3]);
+  draw_glyph(0, 5, F_DIGIT_0 + score[4]);
+  draw_glyph(0, 6, F_DIGIT_0);
+  draw_glyph(0, 7, F_BLANK);
+
+  // lifes left
+  draw_glyph(120, 0, F_BLANK);
+  draw_glyph(120, 1, F_PACMAN_RIGHT_1);
+  draw_glyph(120, 2, F_PACMAN_RIGHT_1);
+  draw_glyph(120, 3, F_PACMAN_RIGHT_1);
+  // fruits
+  draw_glyph(120, 4, F_BLANK);
+  draw_glyph(120, 5, F_CHERRY);
+  draw_glyph(120, 6, F_BLANK);
+  draw_glyph(120, 7, F_BLANK);
+}
+
+/*==========================================================================*\
+ * draw_sprite8x8                                                           *
+\*==========================================================================*/
+static void draw_sprite8x8(int x, int y, const uint8_t *sprite)
+{
+  int i, offset = 4 + 128 * (y / 8) + x;
+
+  if (y >= 0 && y < 64)
+    for (i = 0; i < 8; i++)
+      gddram[offset + i] |= sprite[i] << (y & 7);
+  if (y >= -8 && y < 56)
+  {
+    offset += 128;
+    for (i = 0; i < 8; i++)
+      gddram[offset + i] |= sprite[i] >> (8 - (y & 7));
+  }
+}
+
+/*==========================================================================*\
+ * draw_sprite8x8_with_mask                                                 *
+\*==========================================================================*/
+static void draw_sprite8x8_with_mask(int x, int y,
+                                     const uint8_t *sprite,
+                                     const uint8_t *mask)
+{
+  int i, offset = 4 + 128 * (y / 8) + x;
+
+  if (y >= 0 && y < 64)
+    for (i = 0; i < 8; i++)
+    {
+      gddram[offset + i] &= ~(mask[i] << (y & 7));
+      gddram[offset + i] |= sprite[i] << (y & 7);
+    }
+  if (y >= -8 && y < 56)
+  {
+    offset += 128; // on the next line...
+    for (i = 0; i < 8; i++)
+    {
+      gddram[offset + i] &= ~(mask[i] >> (8 - (y % 8)));
+      gddram[offset + i] |= sprite[i] >> (8 - (y % 8));
+    }
+  }
+}
+
+/*==========================================================================*\
+ * draw_pills                                                               *
+\*==========================================================================*/
+static void draw_pills(void)
+{ // FIXME: rewrite this mess!
+  int x, y;
+  for (y = 1; y < 31; y++) {
+    uint32_t line = current_pills[y];
+    if (line) {
+      for (x = 0; x < 32; x++)
+      if (line >> x & 1) {
+        int yp = 4 * y - screen_offset;
+        if ((x == 3 || x == 28) && (y == 3 || y == 23)) {
+          if (current_frame & 8) {
+            if (yp == -3) {
+              gddram[4 + x * 4 + 1] |= 0x01;
+              gddram[4 + x * 4 + 2] |= 0x01;
+            } else if (yp == -2) {
+              gddram[4 + x * 4 + 0] |= 0x01;
+              gddram[4 + x * 4 + 1] |= 0x03;
+              gddram[4 + x * 4 + 2] |= 0x03;
+              gddram[4 + x * 4 + 3] |= 0x01;
+            } else if (yp == -1) {
+              gddram[4 + x * 4 + 0] |= 0x03;
+              gddram[4 + x * 4 + 1] |= 0x07;
+              gddram[4 + x * 4 + 2] |= 0x07;
+              gddram[4 + x * 4 + 3] |= 0x03;
+            }
+            else if (yp >= 0 && yp < 64) {
+              int offset = 4 + 128 * (yp >> 3) + x * 4;
+              switch (yp & 7) {
+                case 0: case 1: case 2: case 3: case 4:
+                  gddram[offset + 0] |= (0x06 << (yp & 7));
+                  gddram[offset + 1] |= (0x0F << (yp & 7));
+                  gddram[offset + 2] |= (0x0F << (yp & 7));
+                  gddram[offset + 3] |= (0x06 << (yp & 7));
+                  break;
+                case 5:
+                  gddram[offset + 0] |= 0xC0;
+                  gddram[offset + 1] |= 0xE0;
+                  gddram[offset + 2] |= 0xE0;
+                  gddram[offset + 3] |= 0xC0;
+                  if (yp < 56) {
+                    gddram[offset + 128 + 1] |= 0x01;
+                    gddram[offset + 128 + 2] |= 0x01;
+                  }
+                  break;
+                case 6:
+                  gddram[offset + 0] |= 0x80;
+                  gddram[offset + 1] |= 0xC0;
+                  gddram[offset + 2] |= 0xC0;
+                  gddram[offset + 3] |= 0x80;
+                  if (yp < 56) {
+                    gddram[offset + 128 + 0] |= 0x01;
+                    gddram[offset + 128 + 1] |= 0x03;
+                    gddram[offset + 128 + 2] |= 0x03;
+                    gddram[offset + 128 + 3] |= 0x01;
+                  }
+                  break;
+                case 7:
+                  gddram[offset + 1] |= 0x80;
+                  gddram[offset + 2] |= 0x80;
+                  if (yp < 56) {
+                    gddram[offset + 128 + 0] |= 0x03;
+                    gddram[offset + 128 + 1] |= 0x07;
+                    gddram[offset + 128 + 2] |= 0x07;
+                    gddram[offset + 128 + 3] |= 0x03;
+                  }
+                  break;
+              }
+            }
+          }
+        }
+        else {
+          if (yp == -1) {
+            gddram[4 + x * 4 + 1] |= 0x03;
+            gddram[4 + x * 4 + 2] |= 0x03;
+          } else if (yp == -2) {
+            gddram[4 + x * 4 + 1] |= 0x01;
+            gddram[4 + x * 4 + 2] |= 0x01;
+          }
+          else if (yp >= 0 && yp < 64) {
+            int offset = 4 + 128 * (yp / 8) + x * 4;
+            switch (yp & 7)
+            {
+              case 0: case 1: case 2: case 3: case 4: case 5:
+                gddram[offset + 1] |= (0x06 << (yp & 7));
+                gddram[offset + 2] |= (0x06 << (yp & 7));
+                break;
+              case 6:
+                gddram[offset + 1] |= 0x80;
+                gddram[offset + 2] |= 0x80;
+                if (yp < 56)
+                {
+                  gddram[offset + 128 + 1] |= 0x01;
+                  gddram[offset + 128 + 2] |= 0x01;
+                }
+                break;
+              case 7:
+                if (yp < 56) {
+                  gddram[offset + 128 + 1] |= 0x03;
+                  gddram[offset + 128 + 2] |= 0x03;
+                }
+                break;
+            }
+          }
+        }
+      }
+    }
+  }
+}
+
+/*==========================================================================*\
+ * move_ghosts                                                              *
+\*==========================================================================*/
+static void move_ghosts(void)
+{
+  enum Direction { LEFT, RIGHT, UP, DOWN };
+  int checkxdir[4] = { -1,1,0,0 };
+  int checkydir[4] = { 0,0,-1,1 };
+
+  int i;
+
+  for (i = 0; i < 4; i++)
+  {
+    if (ghosts[i].mode == GM_EATEN)
+    {
+      ghosts[i].offset.x = ghosts[i].offset.y = 0;
+      ghosts[i].position.x += ghosts[i].direction.x;
+      ghosts[i].position.y += ghosts[i].direction.y;
+    } else
+    {
+      if (ghosts[i].mode == GM_FRIGHTENED)
+      { // pause every 4 moves
+        if (ghosts[i].stopped == 4)
+        {
+          ghosts[i].stopped = 0;
+          continue;
+        } else ghosts[i].stopped++;
+      }
+      ghosts[i].offset.x += ghosts[i].direction.x;
+      ghosts[i].offset.y += ghosts[i].direction.y;
+    }
+
+    if (ghosts[i].offset.x >= 4 || ghosts[i].offset.x <= -4) {
+      ghosts[i].offset.x = 0;
+      ghosts[i].position.x += ghosts[i].direction.x;
+    }
+    if (ghosts[i].offset.y >= 4 || ghosts[i].offset.y <= -4) {
+      ghosts[i].offset.y = 0;
+      ghosts[i].position.y += ghosts[i].direction.y;
+    }
+
+    if (ghosts[i].offset.y == 0 && ghosts[i].offset.x == 0)
+    {
+      int u, a = 9999, b = -1;
+
+      for (u = 0; u < 4; u++)
+      {
+        int forbidden = walls_mask[ghosts[i].position.y+checkydir[u]] >> (ghosts[i].position.x+checkxdir[u]) & 1;
+        // U turns are forbidden by game rules
+        if (ghosts[i].direction.x == -checkxdir[u] &&
+            ghosts[i].direction.y == -checkydir[u])
+          forbidden = 1;
+        // Ghosts are not allowed reenter the pen (unless eaten)
+        if (u == 3 &&
+            (ghosts[i].position.x == 15 || ghosts[i].position.x == 16) &&
+            ghosts[i].position.y == 11 &&
+            ghosts[i].mode != GM_EATEN)
+          forbidden = 1;
+        // compute (squared) distance to target
+        int dx = ghosts[i].position.x + checkxdir[u];
+        dx = ghosts[i].target.x - dx;
+        int dy = ghosts[i].position.y + checkydir[u];
+        dy = ghosts[i].target.y - dy;
+        int distance = dx * dx + dy * dy;
+        // Store the minimal distance
+        if (!forbidden && distance <= a)
+        {
+          a = distance;
+          b = u;
+        }
+      }
+      ghosts[i].direction.x = checkxdir[b];
+      ghosts[i].direction.y = checkydir[b];
+    }
+    // Resurect!
+    if (ghosts[i].position.x == 15 && ghosts[i].position.y == 13)
+      ghosts[i].mode = GM_IN_PEN;
+    // out of pen
+    if (ghosts[i].mode == GM_IN_PEN && ghosts[i].position.y == 11)
+      ghosts[i].mode = GM_CORNERS;
+    // handle tunnel for ghosts
+    if (ghosts[i].position.x == 0 && ghosts[i].direction.x == -1)
+      ghosts[i].position.x = 31;
+    if (ghosts[i].position.x == 31 && ghosts[i].direction.x == 1)
+      ghosts[i].position.x = 0;
+  }
+}
+
+/*==========================================================================*\
+ * draw_ghosts                                                              *
+\*==========================================================================*/
+static void draw_ghosts(void)
+{
+  int i;
+  for (i = 0; i < 4; i++)
+  {
+    enum FontEntries ghost_sprite = F_DIGIT_0;
+    switch (ghosts[i].mode)
+    {
+      case GM_EATEN:
+        ghost_sprite = F_GHOST_EYES;
+        break;
+      case GM_FRIGHTENED:
+        ghost_sprite = F_GHOST_FRIGHTENED;
+        break;
+      case GM_CORNERS:
+      case GM_CHASE:
+      case GM_IN_PEN:
+        if (ghosts[i].direction.x == -1) ghost_sprite = F_GHOST_LEFT;
+        if (ghosts[i].direction.x == 1) ghost_sprite = F_GHOST_RIGHT;
+        if (ghosts[i].direction.y == -1) ghost_sprite = F_GHOST_UP;
+        if (ghosts[i].direction.y == 1) ghost_sprite = F_GHOST_DOWN;
+        break;
+      case GM_WAITING:
+        ghost_sprite = F_GHOST_STILL;
+        break;
+
+    }
+    draw_sprite8x8_with_mask(ghosts[i].position.x * 4 + ghosts[i].offset.x - 1,
+                             ghosts[i].position.y * 4 + ghosts[i].offset.y - 1 - screen_offset,
+                             &font[ghost_sprite * 8],
+                             &font[F_GHOST_MASK * 8]);
+  }
+}
+
+/*==========================================================================*\
+ * move_pacman                                                              *
+\*==========================================================================*/
+static void move_pacman(void)
+{
+  if (pacman.direction.x || pacman.direction.y)
+  {
+    // animation
+    if (current_frame & 1)
+      pacman.anim = (pacman.anim + 1) & 3;
+    if (pacman.direction.x == -1) pacman.shape = F_PACMAN_LEFT_0;
+    if (pacman.direction.x ==  1) pacman.shape = F_PACMAN_RIGHT_0;
+    if (pacman.direction.y == -1) pacman.shape = F_PACMAN_UP_0;
+    if (pacman.direction.y ==  1) pacman.shape = F_PACMAN_DOWN_0;
+    
+    // sub steps moves
+    pacman.sub_step.x += pacman.direction.x;
+    pacman.sub_step.y += pacman.direction.y;
+  }
+  if (pacman.sub_step.x >= 4 || pacman.sub_step.x <= -4)
+  {
+    pacman.sub_step.x = 0;
+    pacman.position.x += pacman.direction.x;
+  }
+  if (pacman.sub_step.y >= 4 || pacman.sub_step.y <= -4)
+  {
+    pacman.sub_step.y = 0;
+    pacman.position.y += pacman.direction.y;
+  }
+  // if in full tile
+  if (pacman.sub_step.x == 0 && pacman.sub_step.y == 0)
+  {
+    int x = pacman.position.x, y = pacman.position.y;
+    if (current_pills[y] >> x & 1) // hum, this should be 31-x (or not)
+    {
+      current_pills[y] -= 1 << x; // ditto
+      // remaining_dots--;
+      if ((x == 3 || x == 28) && (y == 3 || y == 23))
+      {
+        ghosts[GN_BLINKY].mode = GM_FRIGHTENED;
+        ghosts[GN_BLINKY].stopped = 0;
+        ghosts[GN_PINKY].mode = GM_FRIGHTENED;
+        ghosts[GN_PINKY].stopped = 0;
+        ghosts[GN_INKY].mode = GM_FRIGHTENED;
+        ghosts[GN_INKY].stopped = 0;
+        ghosts[GN_CLYDE].mode = GM_FRIGHTENED;
+        ghosts[GN_CLYDE].stopped = 0;
+        score_add_10(5); // energizers worth 50 points
+        ghost_score = 1;
+      }
+      else score_add_10(1); // normal pills are worth 10 points
+    }
+
+    // Where can we go next ?
+    x = pacman.position.x + pacman.next_dir.x;
+    y = pacman.position.y + pacman.next_dir.y;
+
+    if (!(walls_mask[y] >> x & 1))
+    {
+      pacman.direction.x = pacman.next_dir.x;
+      pacman.direction.y = pacman.next_dir.y;
+    }
+    x = pacman.position.x + pacman.direction.x;
+    y = pacman.position.y + pacman.direction.y;
+    if (walls_mask[y] >> x & 1)
+    {
+      pacman.direction.x = 0;
+      pacman.direction.y = 0;
+    }
+    // handle tunnel for pacman (ghosts are way slower in this)
+    if (pacman.position.x == 1 && pacman.direction.x == -1)
+      pacman.position.x = 30;
+    if (pacman.position.x == 30 && pacman.direction.x == 1)
+      pacman.position.x = 1;
+  }
+}
+
+/*==========================================================================*\
+ * draw_pacman                                                              *
+\*==========================================================================*/
+static void draw_pacman(void)
+{
+  draw_sprite8x8(pacman.position.x * 4 + pacman.sub_step.x - 2,
+                 pacman.position.y * 4 + pacman.sub_step.y - 2 - screen_offset,
+                 font + 8 * (pacman.shape + pacman.anim));
+}
+
+/*==========================================================================*\
+ * draw_walls                                                               *
+\*==========================================================================*/
+static void draw_walls(int screen_offset)
+{
+#include "image.h0"
+#include "image.h1"
+#include "image.h2"
+#include "image.h3"
+#include "image.h4"
+#include "image.h5"
+#include "image.h6"
+#include "image.h7"
+  const uint8_t *backgrounds[8] =
+  {
+    image0, image1, image2, image3, image4, image5, image6, image7
+  };
+  const uint16_t *offsets[8] =
+  {
+    image_offsets0, image_offsets1, image_offsets2, image_offsets3,
+    image_offsets4, image_offsets5, image_offsets6, image_offsets7
+  };
+  uncompress_image(backgrounds[screen_offset & 7] +
+                   offsets[screen_offset & 7][screen_offset >> 3],
+                   gddram + 4);
+}
+
+/*==========================================================================*\
+ * draw_sprite_16x16                                                        *
+\*==========================================================================*/
+static void draw_sprite_16x16(int x, int y, const uint8_t *sprite)
+{
+  int i, offset = 4 + 128 * y + x;
+  int start = 0;
+  int end = 16;
+  if (x < 0) start = -x;
+  if (x > 112) end = 128 - x;
+  for (i = start; i < end; i++)
+    gddram[offset + i] |= sprite[i];
+  for (i = start; i < end; i++)
+    gddram[offset + i + 128] |= sprite[i + 16];
+}
+
+/*==========================================================================*\
+ * draw_sprite_16x16_reverse                                                *
+\*==========================================================================*/
+static void draw_sprite_16x16_reverse(int x, int y, const uint8_t *sprite)
+{
+  int i, offset = 4 + 128 * y + x;
+  int start = 0;
+  int end = 16;
+  if (x < 0) start = -x;
+  if (x > 112) end = 128 - x;
+  for (i = start; i < end; i++)
+    gddram[offset + i] |= sprite[15 - i];
+  for (i = start; i < end; i++)
+    gddram[offset + i + 128] |= sprite[31 - i];
+}
+
+/*==========================================================================*\
+ * update_screen                                                            *
+\*==========================================================================*/
+static void update_screen(void)
+{
+#ifdef ALLOW_SCREENSHOT
+  if (take_screenshot)
+  {
+    send_screenshot();
+    //      take_screenshot = 0;
+  }
+#endif
+  ssd130x_display_full_screen(&display);
+}
+
+/*==========================================================================*\
+ * draw_sprite_16x16_with_mask                                              *
+\*==========================================================================*/
+static void draw_sprite_16x16_with_mask(int x, int y,
+                                        const uint8_t *sprite,
+                                        const uint8_t *mask)
+{
+  int i, offset = 4 + 128 * y + x;
+
+  for (i = 0; i < 16; i++)
+  {
+    gddram[offset + i] &= ~mask[i];
+    gddram[offset + i] |= sprite[i];
+  }
+  offset = 4 + 128 * (y + 1) + x;
+  for (i = 0; i < 16; i++)
+  {
+    gddram[offset + i] &= ~mask[16 + i];
+    gddram[offset + i] |= sprite[16 + i];
+  }
+}
+
+/*==========================================================================*\
+ * show_intermission2                                                       *
+\*==========================================================================*/
+static void show_intermission2(void)
+{
+  int frame = 0;
+  while (1)
+  {
+    ssd130x_buffer_set(gddram, 0xff);
+    int anim = (frame / 2) % 8;
+    switch (anim)
+    {
+      case 0: // 1
+        draw_sprite_16x16_with_mask(50, 4,
+                                    pacmania_se_01, pacmania_se_01_mask);
+        break;
+      case 1: case 7: // 2
+        draw_sprite_16x16_with_mask(50, 4,
+                                    pacmania_se_02, pacmania_se_02_mask);
+        break;
+      case 2: case 6: // 3
+        draw_sprite_16x16_with_mask(50, 4,
+                                    pacmania_se_03, pacmania_se_03_mask);
+        break;
+      case 3: case 4: // 4
+        draw_sprite_16x16_with_mask(50, 4,
+                                    pacmania_se_04, pacmania_se_04_mask);
+        break;
+      case 5:
+        draw_sprite_16x16_with_mask(50, 4,
+                                    pacmania_se_05, pacmania_se_05_mask);
+        break;
+    }
+    frame++;
+    //
+    update_screen();
+    //
+    if (button_right_pressed || button_left_pressed ||
+        button_up_pressed || button_down_pressed)
+      return;
+  }
+}
+
+/*==========================================================================*\
+ * show_intermission                                                        *
+\*==========================================================================*/
+static void show_intermission(void)
+{
+#include "intermission-background.h"
+  int frame = 0, stop = 0;
+  int pac_x = -10;
+  int ghost1 = pac_x - 26;
+  int ghost2 = ghost1 - 18;
+  int ghost3 = ghost2 - 18;
+  int ghost4 = ghost3 - 18;
+  int sequence = 0;
+  int ghost_eaten = 0;
+  while (1)
+  {
+    const uint8_t *ghost_sprite;
+    //const uint8_t *pacman_sprite;
+    uncompress_image(background_intermission, gddram + 4);
+
+    switch (sequence)
+    {
+      case 0: // pacman is chased by the ghost
+        ghost_sprite = frame & 2 ? big_ghost_0 : big_ghost_1;
+        switch ((frame / 2) & 3)
+        {
+          case 0: draw_sprite_16x16(pac_x, 4, big_pacman_0); break;
+          case 1: draw_sprite_16x16(pac_x, 4, big_pacman_1); break;
+          case 2: draw_sprite_16x16(pac_x, 4, big_pacman_2); break;
+          case 3: draw_sprite_16x16(pac_x, 4, big_pacman_1); break;
+        }
+        draw_sprite_16x16(ghost1, 4, ghost_sprite);
+        draw_sprite_16x16(ghost2, 4, ghost_sprite);
+        draw_sprite_16x16(ghost3, 4, ghost_sprite);
+        draw_sprite_16x16(ghost4, 4, ghost_sprite);
+        frame++;
+        if (frame & 15) pac_x++;
+        ghost1++;
+        ghost2 = ghost1 - 18;
+        ghost3 = ghost2 - 18;
+        ghost4 = ghost3 - 18;
+        if (pac_x == 256) {
+          sequence++;
+          frame = 0;
+          //pac_x = -10;
+          //ghost1 = pac_x - 26;
+        }
+        break;
+      case 1: // Pacman eats an energizer : screen is flashing
+        ssd130x_buffer_set(gddram, 0xFF);
+        frame++;
+        if (frame == 3) {
+          sequence++;
+          frame = 0;
+          ghost1 = 130;
+          ghost2 = ghost1 + 18;
+          ghost3 = ghost2 + 18;
+          ghost4 = ghost3 + 18;
+          pac_x = ghost4 + 80;
+        }
+        break;
+      case 2: // The ghost are going back left, pacman eating them
+        ghost_sprite = frame & 2 ? big_frightened_ghost_0 : big_frightened_ghost_1;
+        if (stop >= 0)
+        switch ((frame / 2) & 3)
+        {
+          case 0: draw_sprite_16x16_reverse(pac_x, 4, big_pacman_0); break;
+          case 1: draw_sprite_16x16_reverse(pac_x, 4, big_pacman_1); break;
+          case 2: draw_sprite_16x16_reverse(pac_x, 4, big_pacman_2); break;
+          case 3: draw_sprite_16x16_reverse(pac_x, 4, big_pacman_1); break;
+        }
+        draw_sprite_16x16(ghost1, 4, ghost_sprite);
+        draw_sprite_16x16(ghost2, 4, ghost_sprite);
+        draw_sprite_16x16(ghost3, 4, ghost_sprite);
+        draw_sprite_16x16(ghost4, 4, ghost_sprite);
+        if (stop >= 0)
+        {
+          frame++;
+          ghost1--;
+          ghost2--;
+          ghost3--;
+          ghost4--;
+          pac_x -= 2;
+        }
+        else
+        {
+          int i = 0;
+          switch (ghost_eaten)
+          {
+            case 1:
+              gddram[4 + 128 * 4 + pac_x + i++] = 0xf2;
+              gddram[4 + 128 * 4 + pac_x + i++] = 0x92;
+              gddram[4 + 128 * 4 + pac_x + i++] = 0x92;
+              gddram[4 + 128 * 4 + pac_x + i++] = 0x9e;
+              break;
+            case 2:
+              gddram[4 + 128 * 4 + pac_x + i++] = 0x1e;
+              gddram[4 + 128 * 4 + pac_x + i++] = 0x10;
+              gddram[4 + 128 * 4 + pac_x + i++] = 0x10;
+              gddram[4 + 128 * 4 + pac_x + i++] = 0xfe;
+              break;
+            case 3:
+              gddram[4 + 128 * 4 + pac_x + i++] = 0xfe;
+              gddram[4 + 128 * 4 + pac_x + i++] = 0x92;
+              gddram[4 + 128 * 4 + pac_x + i++] = 0x92;
+              gddram[4 + 128 * 4 + pac_x + i++] = 0xfe;
+              break;
+            case 4:
+              gddram[4 + 128 * 4 + pac_x + i++] = 0xfe;
+              i++;
+              gddram[4 + 128 * 4 + pac_x + i++] = 0xfe;
+              gddram[4 + 128 * 4 + pac_x + i++] = 0x92;
+              gddram[4 + 128 * 4 + pac_x + i++] = 0x92;
+              gddram[4 + 128 * 4 + pac_x + i++] = 0xf2;
+              break;
+          }
+          i++;
+          gddram[4 + 128 * 4 + pac_x + i++] = 0xfe;
+          gddram[4 + 128 * 4 + pac_x + i++] = 0x82;
+          gddram[4 + 128 * 4 + pac_x + i++] = 0x82;
+          gddram[4 + 128 * 4 + pac_x + i++] = 0xfe;
+          i++;
+          gddram[4 + 128 * 4 + pac_x + i++] = 0xfe;
+          gddram[4 + 128 * 4 + pac_x + i++] = 0x82;
+          gddram[4 + 128 * 4 + pac_x + i++] = 0x82;
+          gddram[4 + 128 * 4 + pac_x + i++] = 0xfe;
+
+        }
+        stop ++;
+        if (pac_x - ghost4 < 6) { stop = -10; ghost4 = -100; ghost_eaten++; }
+        if (pac_x - ghost3 < 6) { stop = -10; ghost3 = -100; ghost_eaten++; }
+        if (pac_x - ghost2 < 6) { stop = -10; ghost2 = -100; ghost_eaten++; }
+        if (pac_x - ghost1 < 6) { stop = -10; ghost1 = -100; ghost_eaten++; }
+        if (pac_x < 0)
+        {
+          sequence = 0;
+          frame = 0; stop = 0;
+          pac_x = -64;
+          ghost1 = pac_x - 26;
+          ghost2 = ghost1 - 18;
+          ghost3 = ghost2 - 18;
+          ghost4 = ghost3 - 18;
+          ghost_eaten = 0;
+        }
+        break;
+    }
+    //
+    update_screen();
+    //
+    if (button_right_pressed || button_left_pressed ||
+        button_up_pressed || button_down_pressed)
+      return;
+  }
+}
+
+/****************************************************************************/
+int main(void)
+{
+       system_init();
+#ifdef ALLOW_SCREENSHOT
+       uart_on(UART0, 1152000, data_rx);
+#endif
+       ssp_master_on(SSP_BUS_0, LPC_SSP_FRAME_SPI, 8, 4*1000*1000);
+
+  set_gpio_callback(handle_buttons, &button_up, EDGES_BOTH);
+  set_gpio_callback(handle_buttons, &button_down, EDGES_BOTH);
+  set_gpio_callback(handle_buttons, &button_left, EDGES_BOTH);
+  set_gpio_callback(handle_buttons, &button_right, EDGES_BOTH);
+
+#ifdef ALLOW_SCREENSHOT
+  set_gpio_callback(set_screenshot, &button_ok, EDGE_FALLING);
+#endif
+
+       status_led(green_only);
+
+       /* Configure and start display */
+       ssd130x_display_on(&display);
+
+  show_intermission2();
+
+  memcpy(current_pills, all_pills, sizeof all_pills);
+
+       while (1) {
+    screen_offset = 4 * (pacman.position.y - 8) + pacman.sub_step.y;
+    if (screen_offset < 0) screen_offset = 0;
+    if (screen_offset > 64) screen_offset = 64;
+
+    draw_walls(screen_offset);
+    draw_pills();
+
+    if (button_up_pressed)
+    {
+      pacman.next_dir.x = 0;
+      pacman.next_dir.y = -1;
+    }
+    if (button_down_pressed)
+    {
+      pacman.next_dir.x = 0;
+      pacman.next_dir.y = 1;
+    }
+    if (button_left_pressed)
+    {
+      pacman.next_dir.x = -1;
+      pacman.next_dir.y = 0;
+    }
+    if (button_right_pressed)
+    {
+      pacman.next_dir.x = 1;
+      pacman.next_dir.y = 0;
+    }
+    move_ghosts();
+    draw_ghosts();
+    move_pacman();
+    draw_pacman();
+    draw_score();
+#ifdef ALLOW_SCREENSHOT
+    if (take_screenshot)
+    {
+      send_screenshot();
+//      take_screenshot = 0;
+    }
+#endif
+    ssd130x_display_full_screen(&display);
+//    msleep(100);
+
+    int i;
+    int px = pacman.position.x * 4 + pacman.sub_step.x;
+    int py = pacman.position.y * 4 + pacman.sub_step.y;
+    for (i = 0; i < 4; i++)
+    {
+      int gx = ghosts[i].position.x * 4 + ghosts[i].offset.y;
+      int gy = ghosts[i].position.y * 4 + ghosts[i].offset.y;
+      if (px <= gx + 2 && px >= gx - 2 &&
+          py <= gy + 2 && py >= gy - 2)
+      {
+        if (ghosts[i].mode == GM_FRIGHTENED)
+        {
+          int s;
+          for (s = 0; s < ghost_score; s++)
+            score_add_100(2);
+          ghost_score *= 2;
+          ghosts[i].mode = GM_EATEN;
+        }
+      }
+    }
+
+    for (i = 0; i < GN_NB_GHOSTS; i++)
+      switch (ghosts[i].mode)
+      {
+        case GM_EATEN:
+          ghosts[i].target.x = 15;
+          ghosts[i].target.y = 13;
+          break;
+        case GM_CORNERS: // FIXME! ugly
+          if (i == GN_BLINKY)
+          {
+            ghosts[0].target.x = 28; ghosts[0].target.y = -2;
+          }
+          else if (i == GN_PINKY)
+          {
+            ghosts[1].target.x = 5; ghosts[1].target.y = -2;
+          }
+          else if (i == GN_INKY)
+          {
+            ghosts[2].target.x = 0; ghosts[2].target.y = 31;
+          }
+          else
+          {
+            ghosts[3].target.x = 31; ghosts[3].target.y = 31;
+          }
+          break;
+        case GM_FRIGHTENED:
+          ghosts[i].target.x = my_rand() & 31;
+          ghosts[i].target.y = my_rand() & 31;
+          break;
+        case GM_CHASE:
+          break;
+        case GM_IN_PEN:
+          ghosts[i].target.x = 16;
+          ghosts[i].target.y = 11;
+          break;
+        case GM_WAITING:
+          break;
+      }
+    current_frame++;
+    
+    static unsigned tt; 
+    unsigned t = systick_get_tick_count();
+    static unsigned fps = 0;
+    ++fps;
+    if (t > tt) {
+//      uprintf(UART0, "%u ", fps);
+      fps = 0;
+      tt += 1000;
+    }
+       }
+       return 0;
+}
+
+
+