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