aboutsummaryrefslogtreecommitdiffstats
path: root/src/main.c
diff options
context:
space:
mode:
authoryzrh <yzrh@noema.org>2020-10-10 17:16:14 +0000
committeryzrh <yzrh@noema.org>2020-10-10 17:37:53 +0000
commitb9b74e88028f81e7b169a5e168102138e8c2c46c (patch)
tree2b8f9f8896db3a6871c56215acf71cb502fe7be4 /src/main.c
downloadsnake-sdl-b9b74e88028f81e7b169a5e168102138e8c2c46c.tar.gz
snake-sdl-b9b74e88028f81e7b169a5e168102138e8c2c46c.tar.zst
Initial commit.
Diffstat (limited to 'src/main.c')
-rw-r--r--src/main.c1542
1 files changed, 1542 insertions, 0 deletions
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..48b8fc8
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,1542 @@
+/*
+ * Copyright (c) 2019-2020, yzrh <yzrh@noema.org>
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <pthread.h>
+#include <unistd.h>
+
+#ifdef __linux__
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <poll.h>
+
+#endif /* __linux__ */
+
+#include <SDL2/SDL.h>
+#include <SDL2/SDL_ttf.h>
+
+#ifdef SOUND
+
+#include <SDL2/SDL_mixer.h>
+
+#endif /* SOUND */
+
+#include "version.h"
+#include "extern.h"
+#include "screen.h"
+
+#ifdef GPIO
+
+#include "gpio.h"
+
+#endif /* GPIO */
+
+static pthread_mutex_t mutex;
+static pthread_cond_t cond;
+
+static SDL_Renderer *renderer;
+static TTF_Font *font_20;
+static TTF_Font *font_24;
+
+/*
+ * Default to keyboard,
+ * GPIO inputs are mapped to controller keys
+ */
+static Snake_Device device = SNAKE_KEYBOARD;
+static int selection = 0;
+
+static Snake_Text text_title;
+static Snake_Text text_help_top;
+static Snake_Text text_help_0;
+static Snake_Text text_help_1;
+static Snake_Text text_help_2;
+static Snake_Text text_help_3;
+static Snake_Text text_help_4;
+static Snake_Text text_help_5;
+static Snake_Text text_help_6;
+static Snake_Text text_help_7;
+
+static Snake_Text text_score;
+
+static Snake_Text text_prompt_start;
+static Snake_Text text_prompt_resume;
+
+static Snake_Text text_prompt_0;
+static Snake_Text text_prompt_1;
+static Snake_Text text_prompt_2;
+
+static bool start = 1;
+static bool end = 0;
+static bool begin = 0;
+
+static bool god = 0;
+static bool reset = 0;
+static bool wait = 0;
+static bool multiplayer = 0;
+static bool flappy = 0;
+
+static struct timespec delay = {
+ .tv_sec = 0,
+ .tv_nsec = TICK_TIME_INIT
+};
+
+static int score;
+static int result;
+static int distance;
+static int speed;
+
+static Snake_Input input = SNAKE_EMPTY;
+static Snake_Input head[2][2];
+
+static Snake_Pos *snake0 = NULL;
+static Snake_Pos *snake1 = NULL;
+
+static int fruit[2];
+
+static Snake_Pos *gap = NULL;
+
+static void*
+handler_rendering(void *args)
+{
+ const SDL_Color fg = {
+ COLOUR_FOREGROUND_R,
+ COLOUR_FOREGROUND_G,
+ COLOUR_FOREGROUND_B
+ };
+ const SDL_Color bg = {
+ COLOUR_BACKGROUND_R,
+ COLOUR_BACKGROUND_G,
+ COLOUR_BACKGROUND_B
+ };
+ const SDL_Color bg_s = {
+ COLOUR_BACKGROUND_SHADE_R,
+ COLOUR_BACKGROUND_SHADE_G,
+ COLOUR_BACKGROUND_SHADE_B
+ };
+
+ char result_str[23];
+ char score_str[11];
+
+ Snake_Text text_result;
+
+ Snake_Text text_welcome;
+ Snake_Text text_clear;
+ Snake_Text text_gameover;
+ Snake_Text text_pause;
+
+ while (!end) {
+ draw_clear(renderer);
+ draw_colour(renderer, SNAKE_FOREGROUND);
+
+ draw_wall(renderer);
+
+ draw_text(renderer,
+ font_20,
+ SCREEN_HEIGHT + 4 * SCREEN_UNIT_X,
+ SCREEN_UNIT_Y,
+ "Snake",
+ 1,
+ 0,
+ fg,
+ bg,
+ &text_title);
+ draw_text(renderer,
+ font_20,
+ SCREEN_HEIGHT + 4 * SCREEN_UNIT_X,
+ 2 * SCREEN_UNIT_Y,
+ "Help",
+ 1,
+ 0,
+ fg,
+ bg,
+ &text_help_top);
+ if (god)
+ draw_text(renderer,
+ font_20,
+ SCREEN_HEIGHT + SCREEN_UNIT_X / 4,
+ 2.5 * SCREEN_UNIT_Y,
+ "0. Don't forget to cheat",
+ 0,
+ 0,
+ fg,
+ bg,
+ &text_help_0);
+ if (device == SNAKE_KEYBOARD) {
+ draw_text(renderer,
+ font_20,
+ SCREEN_HEIGHT + SCREEN_UNIT_X / 4,
+ 3 * SCREEN_UNIT_Y,
+ "1. Classic: use arrow keys to move",
+ 0,
+ 0,
+ fg,
+ bg,
+ &text_help_1);
+ draw_text(renderer,
+ font_20,
+ SCREEN_HEIGHT + SCREEN_UNIT_X / 4,
+ 3.5 * SCREEN_UNIT_Y,
+ "2. Multiplayer: use s, e, d, f keys",
+ 0,
+ 0,
+ fg,
+ bg,
+ &text_help_2);
+ draw_text(renderer,
+ font_20,
+ SCREEN_HEIGHT + SCREEN_UNIT_X / 4,
+ 4 * SCREEN_UNIT_Y,
+ "3. Flappy: SPACE key to jump",
+ 0,
+ 0,
+ fg,
+ bg,
+ &text_help_3);
+ draw_text(renderer,
+ font_20,
+ SCREEN_HEIGHT + SCREEN_UNIT_X / 4,
+ 4.5 * SCREEN_UNIT_Y,
+ "4. ESCAPE key to pause",
+ 0,
+ 0,
+ fg,
+ bg,
+ &text_help_4);
+ draw_text(renderer,
+ font_20,
+ SCREEN_HEIGHT + SCREEN_UNIT_X / 4,
+ 5 * SCREEN_UNIT_Y,
+ "5. r key to reset",
+ 0,
+ 0,
+ fg,
+ bg,
+ &text_help_5);
+ draw_text(renderer,
+ font_20,
+ SCREEN_HEIGHT + SCREEN_UNIT_X / 4,
+ 5.5 * SCREEN_UNIT_Y,
+ "6. m key to toggle music",
+ 0,
+ 0,
+ fg,
+ bg,
+ &text_help_6);
+ } else if (device == SNAKE_TOUCHSCREEN) {
+ draw_text(renderer,
+ font_20,
+ SCREEN_HEIGHT + SCREEN_UNIT_X / 4,
+ 3 * SCREEN_UNIT_Y,
+ "1. Classic: swipe to move",
+ 0,
+ 0,
+ fg,
+ bg,
+ &text_help_1);
+ draw_text(renderer,
+ font_20,
+ SCREEN_HEIGHT + SCREEN_UNIT_X / 4,
+ 3.5 * SCREEN_UNIT_Y,
+ "2. Multiplayer: use different side",
+ 0,
+ 0,
+ fg,
+ bg,
+ &text_help_2);
+ draw_text(renderer,
+ font_20,
+ SCREEN_HEIGHT + SCREEN_UNIT_X / 4,
+ 4 * SCREEN_UNIT_Y,
+ "3. Flappy: tap to jump",
+ 0,
+ 0,
+ fg,
+ bg,
+ &text_help_3);
+ draw_text(renderer,
+ font_20,
+ SCREEN_HEIGHT + SCREEN_UNIT_X / 4,
+ 4.5 * SCREEN_UNIT_Y,
+ "4. Tap here to pause",
+ 0,
+ 1,
+ fg,
+ bg,
+ &text_help_4);
+ draw_text(renderer,
+ font_20,
+ SCREEN_HEIGHT + SCREEN_UNIT_X / 4,
+ 5 * SCREEN_UNIT_Y,
+ "5. Tap here to reset",
+ 0,
+ 1,
+ fg,
+ bg,
+ &text_help_5);
+ draw_text(renderer,
+ font_20,
+ SCREEN_HEIGHT + SCREEN_UNIT_X / 4,
+ 5.5 * SCREEN_UNIT_Y,
+ "6. Tap here to toggle music",
+ 0,
+ 1,
+ fg,
+ bg,
+ &text_help_6);
+ } else if (device == SNAKE_CONTROLLER) {
+ draw_text(renderer,
+ font_20,
+ SCREEN_HEIGHT + SCREEN_UNIT_X / 4,
+ 3 * SCREEN_UNIT_Y,
+ "1. Classic: use D-pad to move",
+ 0,
+ 0,
+ fg,
+ bg,
+ &text_help_1);
+ draw_text(renderer,
+ font_20,
+ SCREEN_HEIGHT + SCREEN_UNIT_X / 4,
+ 3.5 * SCREEN_UNIT_Y,
+ "2. Multiplayer: second controller",
+ 0,
+ 0,
+ fg,
+ bg,
+ &text_help_2);
+ draw_text(renderer,
+ font_20,
+ SCREEN_HEIGHT + SCREEN_UNIT_X / 4,
+ 4 * SCREEN_UNIT_Y,
+ "3. Flappy: Press A to jump",
+ 0,
+ 0,
+ fg,
+ bg,
+ &text_help_3);
+ draw_text(renderer,
+ font_20,
+ SCREEN_HEIGHT + SCREEN_UNIT_X / 4,
+ 4.5 * SCREEN_UNIT_Y,
+ "4. Press BACK to pause",
+ 0,
+ 0,
+ fg,
+ bg,
+ &text_help_4);
+ draw_text(renderer,
+ font_20,
+ SCREEN_HEIGHT + SCREEN_UNIT_X / 4,
+ 5 * SCREEN_UNIT_Y,
+ "5. Press X to reset",
+ 0,
+ 0,
+ fg,
+ bg,
+ &text_help_5);
+ draw_text(renderer,
+ font_20,
+ SCREEN_HEIGHT + SCREEN_UNIT_X / 4,
+ 5.5 * SCREEN_UNIT_Y,
+ "6. Press Y to toggle music",
+ 0,
+ 0,
+ fg,
+ bg,
+ &text_help_6);
+ }
+ draw_text(renderer,
+ font_20,
+ SCREEN_HEIGHT + SCREEN_UNIT_X / 4,
+ 6 * SCREEN_UNIT_Y,
+ "7. Have fun",
+ 0,
+ 0,
+ fg,
+ bg,
+ &text_help_7);
+
+ snprintf(score_str, 11, "Score: %d", score);
+
+ draw_text(renderer,
+ font_20,
+ SCREEN_HEIGHT + 4 * SCREEN_UNIT_X,
+ 7 * SCREEN_UNIT_Y,
+ score_str,
+ 1,
+ 0,
+ fg,
+ bg,
+ &text_score);
+
+ draw_snake(renderer, &snake0);
+
+ if (flappy) {
+ draw_pipe(renderer, &gap);
+ } else if (multiplayer)
+ draw_snake(renderer, &snake1);
+ else if (!start)
+ draw_fruit(renderer, fruit);
+
+ if (start || reset) {
+ if (multiplayer) {
+ if (result == 3)
+ snprintf(result_str, 23, "Draw");
+ else if (result > 0)
+ snprintf(result_str, 23,
+ "%s is the winner",
+ result == 1 ?
+ "Player 2" : "Player 1");
+ else
+ snprintf(result_str, 23, "Reset");
+ text_result.cache = 0;
+
+ draw_text(renderer,
+ font_24,
+ SCREEN_HEIGHT / 2,
+ SCREEN_HEIGHT / 2 -
+ 0.75 * SCREEN_UNIT_Y,
+ result_str,
+ 1,
+ 0,
+ fg,
+ bg_s,
+ &text_result);
+ }
+ if (start)
+ draw_text(renderer,
+ font_24,
+ SCREEN_HEIGHT / 2,
+ SCREEN_HEIGHT / 2,
+ "Welcome",
+ 1,
+ 0,
+ fg,
+ bg_s,
+ &text_welcome);
+ else if (!flappy && !multiplayer &&
+ score == SCREEN_BLOCK / 2)
+ draw_text(renderer,
+ font_24,
+ SCREEN_HEIGHT / 2,
+ SCREEN_HEIGHT / 2,
+ "Why don't you have legs?",
+ 1,
+ 0,
+ fg,
+ bg_s,
+ &text_clear);
+ else
+ draw_text(renderer,
+ font_24,
+ SCREEN_HEIGHT / 2,
+ SCREEN_HEIGHT / 2,
+ "Game over!",
+ 1,
+ 0,
+ fg,
+ bg_s,
+ &text_gameover);
+ if (device == SNAKE_KEYBOARD)
+ draw_text(renderer,
+ font_20,
+ SCREEN_HEIGHT / 2,
+ SCREEN_HEIGHT / 2 +
+ 0.75 * SCREEN_UNIT_Y,
+ "Press RETURN key for selection",
+ 1,
+ 0,
+ fg,
+ bg_s,
+ &text_prompt_start);
+ else if (device == SNAKE_TOUCHSCREEN)
+ draw_text(renderer,
+ font_20,
+ SCREEN_HEIGHT / 2,
+ SCREEN_HEIGHT / 2 +
+ 0.75 * SCREEN_UNIT_Y,
+ "Tap the option to select",
+ 1,
+ 0,
+ fg,
+ bg_s,
+ &text_prompt_start);
+ else if (device == SNAKE_CONTROLLER)
+ draw_text(renderer,
+ font_20,
+ SCREEN_HEIGHT / 2,
+ SCREEN_HEIGHT / 2 +
+ 0.75 * SCREEN_UNIT_Y,
+ "Press START to select",
+ 1,
+ 0,
+ fg,
+ bg_s,
+ &text_prompt_start);
+ draw_text(renderer,
+ font_20,
+ SCREEN_HEIGHT / 2,
+ SCREEN_HEIGHT / 2 + 1.5 * SCREEN_UNIT_Y,
+ "Classic",
+ 1,
+ device == SNAKE_TOUCHSCREEN ||
+ selection == 0 ? 1 : 0,
+ fg,
+ bg_s,
+ &text_prompt_0);
+ draw_text(renderer,
+ font_20,
+ SCREEN_HEIGHT / 2,
+ SCREEN_HEIGHT / 2 + 2.25 * SCREEN_UNIT_Y,
+ "Multiplayer",
+ 1,
+ device == SNAKE_TOUCHSCREEN ||
+ selection == 1 ? 1 : 0,
+ fg,
+ bg_s,
+ &text_prompt_1);
+ draw_text(renderer,
+ font_20,
+ SCREEN_HEIGHT / 2,
+ SCREEN_HEIGHT / 2 + 3 * SCREEN_UNIT_Y,
+ "Flappy",
+ 1,
+ device == SNAKE_TOUCHSCREEN ||
+ selection == 2 ? 1 : 0,
+ fg,
+ bg_s,
+ &text_prompt_2);
+ } else if (wait) {
+ draw_text(renderer,
+ font_24,
+ SCREEN_HEIGHT / 2,
+ SCREEN_HEIGHT / 2,
+ "Paused",
+ 1,
+ 0,
+ fg,
+ bg_s,
+ &text_pause);
+ if (device == SNAKE_KEYBOARD)
+ draw_text(renderer,
+ font_20,
+ SCREEN_HEIGHT / 2,
+ SCREEN_HEIGHT / 2 +
+ 0.75 * SCREEN_UNIT_Y,
+ "Press ESCAPE key to resume",
+ 1,
+ 0,
+ fg,
+ bg_s,
+ &text_prompt_resume);
+ else if (device == SNAKE_TOUCHSCREEN)
+ draw_text(renderer,
+ font_20,
+ SCREEN_HEIGHT / 2,
+ SCREEN_HEIGHT / 2 +
+ 0.75 * SCREEN_UNIT_Y,
+ "Tap here to resume",
+ 1,
+ 1,
+ fg,
+ bg_s,
+ &text_prompt_resume);
+ else if (device == SNAKE_CONTROLLER)
+ draw_text(renderer,
+ font_20,
+ SCREEN_HEIGHT / 2,
+ SCREEN_HEIGHT / 2 +
+ 0.75 * SCREEN_UNIT_Y,
+ "Press START to resume",
+ 1,
+ 0,
+ fg,
+ bg_s,
+ &text_prompt_resume);
+ }
+
+ SDL_RenderPresent(renderer);
+ }
+ return 0;
+}
+
+static void*
+handler_logic(void *args)
+{
+ struct timespec clock_start;
+ struct timespec clock_end;
+ double clock_pass = 0;
+ int pipe_dim[6];
+ int pipe_pos = 0;
+
+ clock_gettime(CLOCK_MONOTONIC, &clock_start);
+
+ for (;;) {
+ pthread_mutex_lock(&mutex);
+ while (start || reset || wait) {
+ if (!wait) {
+ clock_pass = 0;
+ pipe_pos = 0;
+ }
+ pthread_cond_wait(&cond, &mutex);
+ clock_gettime(CLOCK_MONOTONIC, &clock_start);
+ }
+ pthread_mutex_unlock(&mutex);
+
+ if (end)
+ return 0;
+
+ if (flappy) {
+ pthread_mutex_lock(&mutex);
+ while (!begin)
+ pthread_cond_wait(&cond, &mutex);
+ pthread_mutex_unlock(&mutex);
+
+ game_pipe_bound(&gap, pipe_dim);
+
+ if (pipe_pos - SCREEN_UNIT > pipe_dim[0]) {
+ score++;
+ text_score.cache = 0;
+ }
+ pipe_pos = pipe_dim[0];
+
+ if (!god && (collision(
+ (int[]) {snake0->next->x, snake0->next->y,
+ 2 * SCREEN_UNIT, SCREEN_UNIT},
+ (int[]) {pipe_dim[0], pipe_dim[2],
+ pipe_dim[1], pipe_dim[3]}) ||
+ collision(
+ (int[]) {snake0->next->x, snake0->next->y,
+ 2 * SCREEN_UNIT, SCREEN_UNIT},
+ (int[]) {pipe_dim[0], pipe_dim[4],
+ pipe_dim[1], pipe_dim[5]}) ||
+ snake0->y < 0 ||
+ snake0->y + SCREEN_UNIT > SCREEN_HEIGHT)) {
+ pthread_mutex_lock(&mutex);
+ reset = 1;
+ pthread_mutex_unlock(&mutex);
+ } else {
+ game_pipe_move(&gap);
+
+ game_pipe_pos(&gap, &result);
+ if (result == 3) {
+ game_pipe_del(&gap);
+ game_pipe_add(&gap);
+ } else if (result == 2) {
+ game_pipe_add(&gap);
+ } else if (result == 1) {
+ game_pipe_del(&gap);
+ }
+ }
+ } else if (!multiplayer) {
+ game_snake_move(&snake0, NULL,
+ head[0], !god, &result);
+
+ if (result == 0 || god) {
+ head[1][0] = head[0][0];
+
+ if (collision_block(fruit,
+ (int[]) {snake0->x, snake0->y})) {
+ score++;
+ text_score.cache = 0;
+ game_snake_grow(&snake0);
+
+ if (score == SCREEN_BLOCK / 2) {
+ pthread_mutex_lock(&mutex);
+ reset = 1;
+ pthread_mutex_unlock(&mutex);
+ }
+
+ game_fruit(fruit, &snake0);
+
+ if (delay.tv_nsec - TICK_TIME_DEC >=
+ TICK_TIME_MIN)
+ delay.tv_nsec -= TICK_TIME_DEC;
+ }
+ } else {
+ pthread_mutex_lock(&mutex);
+ reset = 1;
+ pthread_mutex_unlock(&mutex);
+ }
+ } else {
+ game_snake_move(&snake0, &snake1,
+ head[0], !god, &result);
+
+ if (result == 0 || god) {
+ head[1][0] = head[0][0];
+ head[1][1] = head[0][1];
+
+ clock_gettime(CLOCK_MONOTONIC, &clock_end);
+ clock_pass += (clock_end.tv_sec -
+ clock_start.tv_sec) +
+ (clock_end.tv_nsec -
+ clock_start.tv_nsec) * 0.000000001;
+ clock_gettime(CLOCK_MONOTONIC, &clock_start);
+
+ score = clock_pass;
+ text_score.cache = 0;
+ game_snake_grow(&snake0);
+ game_snake_grow(&snake1);
+ } else {
+ pthread_mutex_lock(&mutex);
+ reset = 1;
+ pthread_mutex_unlock(&mutex);
+ }
+ }
+
+ nanosleep(&delay, NULL);
+ }
+}
+
+static void*
+handler_gravity(void *args)
+{
+ const struct timespec delay = {
+ .tv_sec = 0,
+ .tv_nsec = 1000000000 / SCREEN_UNIT
+ };
+
+ for (;;) {
+ pthread_mutex_lock(&mutex);
+ while (!flappy || !begin || start || reset || wait)
+ pthread_cond_wait(&cond, &mutex);
+ pthread_mutex_unlock(&mutex);
+
+ if (end)
+ return 0;
+
+ if (distance > 0) {
+ game_snake_fall(&snake0, 0,
+ distance > speed ? speed : distance);
+ speed++;
+
+ pthread_mutex_lock(&mutex);
+ distance -= distance > speed ? speed : distance;
+ if (distance == 0)
+ speed = 1;
+ pthread_mutex_unlock(&mutex);
+ } else {
+ game_snake_fall(&snake0, 1, speed);
+ speed++;
+ }
+
+ nanosleep(&delay, NULL);
+ }
+}
+
+#ifdef GPIO
+
+#ifdef __FreeBSD__
+
+static void*
+handler_gpio(void *args)
+{
+ const struct timespec delay = {
+ .tv_sec = 0,
+ .tv_nsec = 50000000
+ };
+
+ SDL_Event event;
+ event.type = SDL_CONTROLLERBUTTONUP;
+
+ while (!end) {
+ if (gpio_read(GPIO_PIN_IN0) == 0) {
+ event.cbutton.button =
+ SDL_CONTROLLER_BUTTON_DPAD_LEFT;
+ event.cbutton.which = 0;
+ SDL_PushEvent(&event);
+ } else if (gpio_read(GPIO_PIN_IN1) == 0) {
+ event.cbutton.button =
+ SDL_CONTROLLER_BUTTON_DPAD_UP;
+ event.cbutton.which = 0;
+ SDL_PushEvent(&event);
+ } else if (gpio_read(GPIO_PIN_IN2) == 0) {
+ event.cbutton.button =
+ SDL_CONTROLLER_BUTTON_DPAD_DOWN;
+ event.cbutton.which = 0;
+ SDL_PushEvent(&event);
+ } else if (gpio_read(GPIO_PIN_IN3) == 0) {
+ event.cbutton.button =
+ SDL_CONTROLLER_BUTTON_DPAD_RIGHT;
+ event.cbutton.which = 0;
+ SDL_PushEvent(&event);
+ } else if (gpio_read(GPIO_PIN_IN4) == 0) {
+ event.cbutton.button =
+ SDL_CONTROLLER_BUTTON_BACK;
+ event.cbutton.which = 0;
+ SDL_PushEvent(&event);
+ } else if (gpio_read(GPIO_PIN_IN5) == 0) {
+ event.cbutton.button =
+ SDL_CONTROLLER_BUTTON_GUIDE;
+ event.cbutton.which = 0;
+ SDL_PushEvent(&event);
+ } else if (gpio_read(GPIO_PIN_IN6) == 0) {
+ event.cbutton.button =
+ SDL_CONTROLLER_BUTTON_START;
+ event.cbutton.which = 0;
+ SDL_PushEvent(&event);
+ } else if (gpio_read(GPIO_PIN_IN7) == 0) {
+ event.cbutton.button =
+ SDL_CONTROLLER_BUTTON_A;
+ event.cbutton.which = 0;
+ SDL_PushEvent(&event);
+ } else if (gpio_read(GPIO_PIN_IN8) == 0) {
+ event.cbutton.button =
+ SDL_CONTROLLER_BUTTON_B;
+ event.cbutton.which = 0;
+ SDL_PushEvent(&event);
+ } else if (gpio_read(GPIO_PIN_IN9) == 0) {
+ event.cbutton.button =
+ SDL_CONTROLLER_BUTTON_X;
+ event.cbutton.which = 0;
+ SDL_PushEvent(&event);
+ } else if (gpio_read(GPIO_PIN_IN10) == 0) {
+ event.cbutton.button =
+ SDL_CONTROLLER_BUTTON_Y;
+ event.cbutton.which = 0;
+ SDL_PushEvent(&event);
+ } else if (gpio_read(GPIO_PIN_IN11) == 0) {
+ event.cbutton.button =
+ SDL_CONTROLLER_BUTTON_LEFTSHOULDER;
+ event.cbutton.which = 0;
+ SDL_PushEvent(&event);
+ } else if (gpio_read(GPIO_PIN_IN12) == 0) {
+ event.cbutton.button =
+ SDL_CONTROLLER_BUTTON_RIGHTSHOULDER;
+ event.cbutton.which = 0;
+ SDL_PushEvent(&event);
+ } else if (gpio_read(GPIO_PIN_IN13) == 0) {
+ event.cbutton.button =
+ SDL_CONTROLLER_BUTTON_LEFTSTICK;
+ event.cbutton.which = 0;
+ SDL_PushEvent(&event);
+ } else if (gpio_read(GPIO_PIN_IN14) == 0) {
+ event.cbutton.button =
+ SDL_CONTROLLER_BUTTON_RIGHTSTICK;
+ event.cbutton.which = 0;
+ SDL_PushEvent(&event);
+ } else if (gpio_read(GPIO_PIN_IN15) == 0) {
+ event.cbutton.button =
+ SDL_CONTROLLER_BUTTON_DPAD_LEFT;
+ event.cbutton.which = 1;
+ SDL_PushEvent(&event);
+ } else if (gpio_read(GPIO_PIN_IN16) == 0) {
+ event.cbutton.button =
+ SDL_CONTROLLER_BUTTON_DPAD_UP;
+ event.cbutton.which = 1;
+ SDL_PushEvent(&event);
+ } else if (gpio_read(GPIO_PIN_IN17) == 0) {
+ event.cbutton.button =
+ SDL_CONTROLLER_BUTTON_DPAD_DOWN;
+ event.cbutton.which = 1;
+ SDL_PushEvent(&event);
+ } else if (gpio_read(GPIO_PIN_IN18) == 0) {
+ event.cbutton.button =
+ SDL_CONTROLLER_BUTTON_DPAD_RIGHT;
+ event.cbutton.which = 1;
+ SDL_PushEvent(&event);
+ }
+
+ nanosleep(&delay, NULL);
+ }
+ return 0;
+}
+
+#elif __linux__
+
+static void*
+handler_gpio(void *args)
+{
+ struct pollfd pfd[19];
+ char path[29];
+
+ for (int i = 0; i < 19; i++)
+ pfd[i].events = POLLPRI | POLLERR;
+
+ snprintf(path, 29, "/sys/class/gpio/gpio%d/value", GPIO_PIN_IN0);
+ pfd[0].fd = open(path, O_RDONLY);
+ snprintf(path, 29, "/sys/class/gpio/gpio%d/value", GPIO_PIN_IN1);
+ pfd[1].fd = open(path, O_RDONLY);
+ snprintf(path, 29, "/sys/class/gpio/gpio%d/value", GPIO_PIN_IN2);
+ pfd[2].fd = open(path, O_RDONLY);
+ snprintf(path, 29, "/sys/class/gpio/gpio%d/value", GPIO_PIN_IN3);
+ pfd[3].fd = open(path, O_RDONLY);
+ snprintf(path, 29, "/sys/class/gpio/gpio%d/value", GPIO_PIN_IN4);
+ pfd[4].fd = open(path, O_RDONLY);
+ snprintf(path, 29, "/sys/class/gpio/gpio%d/value", GPIO_PIN_IN5);
+ pfd[5].fd = open(path, O_RDONLY);
+ snprintf(path, 29, "/sys/class/gpio/gpio%d/value", GPIO_PIN_IN6);
+ pfd[6].fd = open(path, O_RDONLY);
+ snprintf(path, 29, "/sys/class/gpio/gpio%d/value", GPIO_PIN_IN7);
+ pfd[7].fd = open(path, O_RDONLY);
+ snprintf(path, 29, "/sys/class/gpio/gpio%d/value", GPIO_PIN_IN8);
+ pfd[8].fd = open(path, O_RDONLY);
+ snprintf(path, 29, "/sys/class/gpio/gpio%d/value", GPIO_PIN_IN9);
+ pfd[9].fd = open(path, O_RDONLY);
+ snprintf(path, 29, "/sys/class/gpio/gpio%d/value", GPIO_PIN_IN10);
+ pfd[10].fd = open(path, O_RDONLY);
+ snprintf(path, 29, "/sys/class/gpio/gpio%d/value", GPIO_PIN_IN11);
+ pfd[11].fd = open(path, O_RDONLY);
+ snprintf(path, 29, "/sys/class/gpio/gpio%d/value", GPIO_PIN_IN12);
+ pfd[12].fd = open(path, O_RDONLY);
+ snprintf(path, 29, "/sys/class/gpio/gpio%d/value", GPIO_PIN_IN13);
+ pfd[13].fd = open(path, O_RDONLY);
+ snprintf(path, 29, "/sys/class/gpio/gpio%d/value", GPIO_PIN_IN14);
+ pfd[14].fd = open(path, O_RDONLY);
+ snprintf(path, 29, "/sys/class/gpio/gpio%d/value", GPIO_PIN_IN15);
+ pfd[15].fd = open(path, O_RDONLY);
+ snprintf(path, 29, "/sys/class/gpio/gpio%d/value", GPIO_PIN_IN16);
+ pfd[16].fd = open(path, O_RDONLY);
+ snprintf(path, 29, "/sys/class/gpio/gpio%d/value", GPIO_PIN_IN17);
+ pfd[17].fd = open(path, O_RDONLY);
+ snprintf(path, 29, "/sys/class/gpio/gpio%d/value", GPIO_PIN_IN18);
+ pfd[18].fd = open(path, O_RDONLY);
+
+ SDL_Event event;
+ event.type = SDL_CONTROLLERBUTTONUP;
+
+ char val[2];
+
+ while (!end) {
+ poll(pfd, 19, -1);
+
+ for (int i = 0; i < 19; i++) {
+ if (pfd[i].revents & (POLLPRI | POLLERR)) {
+ pfd[i].revents &= ~(POLLPRI | POLLERR);
+ read(pfd[i].fd, val, 2);
+ lseek(pfd[i].fd, 0, SEEK_SET);
+
+ if (atoi(val) != 0)
+ continue;
+
+ input_gpio(i % 15, &event.cbutton.button);
+
+ if (i < 15)
+ event.cbutton.which = 0;
+ else
+ event.cbutton.which = 1;
+
+ SDL_PushEvent(&event);
+ break;
+ }
+ }
+ }
+ return 0;
+}
+
+#endif /* __FreeBSD__ */
+
+#endif /* GPIO */
+
+int
+main(void)
+{
+ printf("Snake " VERSION "." RELEASE "." PATCH EXTRA "\n"
+ "Copyright (c) 2019-2020, yzrh <yzrh@noema.org>\n");
+
+ if (SDL_Init(SDL_INIT_VIDEO |
+ SDL_INIT_AUDIO |
+ SDL_INIT_GAMECONTROLLER) != 0) {
+ fprintf(stderr, "SDL initialisation failure: %s\n",
+ SDL_GetError());
+ return 1;
+ }
+
+ SDL_Window *window = SDL_CreateWindow(
+ "Snake " VERSION "." RELEASE "." PATCH EXTRA,
+ SDL_WINDOWPOS_UNDEFINED,
+ SDL_WINDOWPOS_UNDEFINED,
+ SCREEN_WIDTH,
+ SCREEN_HEIGHT,
+ SDL_WINDOW_OPENGL);
+
+ if (window == NULL) {
+ fprintf(stderr, "Window creation failure: %s\n",
+ SDL_GetError());
+ return 1;
+ }
+
+ renderer = SDL_CreateRenderer(
+ window,
+ -1,
+ SDL_RENDERER_ACCELERATED |
+ SDL_RENDERER_PRESENTVSYNC);
+
+ if (renderer == NULL) {
+ fprintf(stderr, "Renderer creation failure: %s\n",
+ SDL_GetError());
+ return 1;
+ }
+
+ if (TTF_Init() != 0) {
+ fprintf(stderr, "TTF initialisation failure: %s\n",
+ TTF_GetError());
+ return 1;
+ }
+
+ font_20 = TTF_OpenFont("../contrib/font.ttf", 20);
+ font_24 = TTF_OpenFont("../contrib/font.ttf", 24);
+
+ if (font_20 == NULL) {
+ font_20 = TTF_OpenFont("/usr/local/share/snake/font.ttf", 20);
+ font_24 = TTF_OpenFont("/usr/local/share/snake/font.ttf", 24);
+ }
+
+ if (font_20 == NULL) {
+ font_20 = TTF_OpenFont("/usr/share/snake/font.ttf", 20);
+ font_24 = TTF_OpenFont("/usr/share/snake/font.ttf", 24);
+ }
+
+ if (font_20 == NULL) {
+ fprintf(stderr, "%s\n", TTF_GetError());
+ return 1;
+ }
+
+#ifdef SOUND
+
+ if ((Mix_Init(MIX_INIT_FLAC) & MIX_INIT_FLAC) != MIX_INIT_FLAC)
+ fprintf(stderr, "Mixer initialisation failure: %s\n",
+ Mix_GetError());
+
+ if (Mix_OpenAudio(48000, AUDIO_S16SYS, 2, 4096) == -1)
+ fprintf(stderr, "%s\n", Mix_GetError());
+
+ Mix_Music *music = Mix_LoadMUS("../contrib/music.flac");
+
+ if (music == NULL)
+ music = Mix_LoadMUS("/usr/local/share/snake/music.flac");
+
+ if (music == NULL)
+ music = Mix_LoadMUS("/usr/share/snake/music.flac");
+
+ if (music == NULL)
+ fprintf(stderr, "%s\n", Mix_GetError());
+
+ if (Mix_PlayMusic(music, -1) == -1)
+ fprintf(stderr, "Audio playback failure: %s\n",
+ Mix_GetError());
+
+#endif /* SOUND */
+
+#ifdef GPIO
+
+#ifdef __linux__
+
+ gpio_export(GPIO_PIN_IN0);
+ gpio_export(GPIO_PIN_IN1);
+ gpio_export(GPIO_PIN_IN2);
+ gpio_export(GPIO_PIN_IN3);
+ gpio_export(GPIO_PIN_IN4);
+ gpio_export(GPIO_PIN_IN5);
+ gpio_export(GPIO_PIN_IN6);
+ gpio_export(GPIO_PIN_IN7);
+ gpio_export(GPIO_PIN_IN8);
+ gpio_export(GPIO_PIN_IN9);
+ gpio_export(GPIO_PIN_IN10);
+ gpio_export(GPIO_PIN_IN11);
+ gpio_export(GPIO_PIN_IN12);
+ gpio_export(GPIO_PIN_IN13);
+ gpio_export(GPIO_PIN_IN14);
+ gpio_export(GPIO_PIN_IN15);
+ gpio_export(GPIO_PIN_IN16);
+ gpio_export(GPIO_PIN_IN17);
+ gpio_export(GPIO_PIN_IN18);
+
+ /* Wait for sysfs directory to show up */
+ sleep(1);
+
+#endif /* __linux__ */
+
+ gpio_config(GPIO_PIN_IN0, GPIO_IN, GPIO_INT);
+ gpio_config(GPIO_PIN_IN1, GPIO_IN, GPIO_INT);
+ gpio_config(GPIO_PIN_IN2, GPIO_IN, GPIO_INT);
+ gpio_config(GPIO_PIN_IN3, GPIO_IN, GPIO_INT);
+ gpio_config(GPIO_PIN_IN4, GPIO_IN, GPIO_INT);
+ gpio_config(GPIO_PIN_IN5, GPIO_IN, GPIO_INT);
+ gpio_config(GPIO_PIN_IN6, GPIO_IN, GPIO_INT);
+ gpio_config(GPIO_PIN_IN7, GPIO_IN, GPIO_INT);
+ gpio_config(GPIO_PIN_IN8, GPIO_IN, GPIO_INT);
+ gpio_config(GPIO_PIN_IN9, GPIO_IN, GPIO_INT);
+ gpio_config(GPIO_PIN_IN10, GPIO_IN, GPIO_INT);
+ gpio_config(GPIO_PIN_IN11, GPIO_IN, GPIO_INT);
+ gpio_config(GPIO_PIN_IN12, GPIO_IN, GPIO_INT);
+ gpio_config(GPIO_PIN_IN13, GPIO_IN, GPIO_INT);
+ gpio_config(GPIO_PIN_IN14, GPIO_IN, GPIO_INT);
+ gpio_config(GPIO_PIN_IN15, GPIO_IN, GPIO_INT);
+ gpio_config(GPIO_PIN_IN16, GPIO_IN, GPIO_INT);
+ gpio_config(GPIO_PIN_IN17, GPIO_IN, GPIO_INT);
+ gpio_config(GPIO_PIN_IN18, GPIO_IN, GPIO_INT);
+
+#endif /* GPIO */
+
+ int seed;
+ int random = open("/dev/random", O_RDONLY);
+ if (read(random, &seed, sizeof(int)) > 0)
+ srand(seed);
+ close(random);
+
+ SDL_JoystickID player = 0;
+ SDL_Event event;
+
+ pthread_mutex_init(&mutex, NULL);
+ pthread_cond_init(&cond, NULL);
+ pthread_t th_renderer;
+ pthread_t th_logic;
+ pthread_t th_gravity;
+
+ #ifdef GPIO
+
+ pthread_t th_gpio;
+
+ #endif /* GPIO */
+
+ pthread_create(&th_renderer, NULL, handler_rendering, NULL);
+ pthread_create(&th_logic, NULL, handler_logic, NULL);
+ pthread_create(&th_gravity, NULL, handler_gravity, NULL);
+
+ #ifdef GPIO
+
+ pthread_create(&th_gpio, NULL, handler_gpio, NULL);
+
+ #endif /* GPIO */
+
+ while (SDL_WaitEvent(&event)) {
+ if (event.type == SDL_QUIT)
+ break;
+
+ switch (event.type) {
+ case SDL_KEYUP:
+ if (device != SNAKE_KEYBOARD) {
+ device = SNAKE_KEYBOARD;
+ text_help_1.cache = 0;
+ text_help_2.cache = 0;
+ text_help_3.cache = 0;
+ text_help_4.cache = 0;
+ text_help_5.cache = 0;
+ text_help_6.cache = 0;
+ text_prompt_start.cache = 0;
+ text_prompt_resume.cache = 0;
+ }
+ input_keyboard(event.key.keysym.sym, &input);
+ if (multiplayer &&
+ (event.key.keysym.sym ==
+ SDLK_s ||
+ event.key.keysym.sym ==
+ SDLK_e ||
+ event.key.keysym.sym ==
+ SDLK_d ||
+ event.key.keysym.sym ==
+ SDLK_f))
+ player = 1;
+ else
+ player = 0;
+ break;
+ case SDL_FINGERUP:
+ if (device != SNAKE_TOUCHSCREEN) {
+ device = SNAKE_TOUCHSCREEN;
+ text_help_1.cache = 0;
+ text_help_2.cache = 0;
+ text_help_3.cache = 0;
+ text_help_4.cache = 0;
+ text_help_5.cache = 0;
+ text_help_6.cache = 0;
+ text_prompt_start.cache = 0;
+ text_prompt_resume.cache = 0;
+ }
+ if (start || reset || wait) {
+ if (collision_bound(
+ event.tfinger.x,
+ event.tfinger.y,
+ &text_help_5)) {
+ input = SNAKE_RESET;
+ } else if (collision_bound(
+ event.tfinger.x,
+ event.tfinger.y,
+ &text_help_6)) {
+ input = SNAKE_MUSIC;
+ } else if (wait) {
+ if (collision_bound(
+ event.tfinger.x,
+ event.tfinger.y,
+ &text_prompt_resume))
+ input = SNAKE_PAUSE;
+ } else {
+ if (collision_bound(
+ event.tfinger.x,
+ event.tfinger.y,
+ &text_prompt_0)) {
+ selection = 0;
+ input = SNAKE_SELECT;
+ } else if (collision_bound(
+ event.tfinger.x,
+ event.tfinger.y,
+ &text_prompt_1)) {
+ selection = 1;
+ input = SNAKE_SELECT;
+ } else if (collision_bound(
+ event.tfinger.x,
+ event.tfinger.y,
+ &text_prompt_2)) {
+ selection = 2;
+ input = SNAKE_SELECT;
+ }
+ }
+ } else if (collision_bound(
+ event.tfinger.x,
+ event.tfinger.y,
+ &text_help_4)) {
+ input = SNAKE_PAUSE;
+ } else if (collision_bound(
+ event.tfinger.x,
+ event.tfinger.y,
+ &text_help_5)) {
+ input = SNAKE_RESET;
+ } else if (collision_bound(
+ event.tfinger.x,
+ event.tfinger.y,
+ &text_help_6)) {
+ input = SNAKE_MUSIC;
+ } else if (collision_bound(
+ event.tfinger.x,
+ event.tfinger.y,
+ &text_help_top)) {
+ input = SNAKE_GOD;
+ } else if (collision_bound(
+ event.tfinger.x,
+ event.tfinger.y,
+ &text_score)) {
+ input = SNAKE_GROW;
+ } else if (collision_bound(
+ event.tfinger.x,
+ event.tfinger.y,
+ &text_title)) {
+ input = SNAKE_TELEPORT;
+ } else if (collision_bound(
+ event.tfinger.x,
+ event.tfinger.y,
+ &text_help_1)) {
+ input = SNAKE_TICK_DECREASE;
+ } else if (collision_bound(
+ event.tfinger.x,
+ event.tfinger.y,
+ &text_help_7)) {
+ input = SNAKE_TICK_INCREASE;
+ } else if (flappy) {
+ input = SNAKE_JUMP;
+ } else {
+ input = SNAKE_EMPTY;
+ }
+ break;
+ case SDL_FINGERMOTION:
+ if (device != SNAKE_TOUCHSCREEN) {
+ device = SNAKE_TOUCHSCREEN;
+ text_help_1.cache = 0;
+ text_help_2.cache = 0;
+ text_help_3.cache = 0;
+ text_help_4.cache = 0;
+ text_help_5.cache = 0;
+ text_help_6.cache = 0;
+ text_prompt_start.cache = 0;
+ text_prompt_resume.cache = 0;
+ }
+ if ((event.tfinger.dx *
+ event.tfinger.dx) >
+ (event.tfinger.dy *
+ event.tfinger.dy)) {
+ if (event.tfinger.dx > 1)
+ input = SNAKE_RIGHT;
+ else if (event.tfinger.dx < -1)
+ input = SNAKE_LEFT;
+ } else {
+ if (event.tfinger.dy > 1)
+ input = SNAKE_DOWN;
+ else if (event.tfinger.dy < -1)
+ input = SNAKE_UP;
+ }
+ if (multiplayer &&
+ event.tfinger.x > SCREEN_HEIGHT / 2)
+ player = 1;
+ else
+ player = 0;
+ break;
+ case SDL_CONTROLLERBUTTONUP:
+ if (device != SNAKE_CONTROLLER) {
+ device = SNAKE_CONTROLLER;
+ text_help_1.cache = 0;
+ text_help_2.cache = 0;
+ text_help_3.cache = 0;
+ text_help_4.cache = 0;
+ text_help_5.cache = 0;
+ text_help_6.cache = 0;
+ text_prompt_start.cache = 0;
+ text_prompt_resume.cache = 0;
+ }
+ input_controller(event.cbutton.button, &input);
+ player = event.cbutton.which;
+ break;
+ default:
+ break;
+ }
+
+ if (!start && !reset && !wait) {
+ switch (input) {
+ case SNAKE_LEFT:
+ if (head[1][player] != SNAKE_RIGHT)
+ head[0][player] = SNAKE_LEFT;
+ break;
+ case SNAKE_UP:
+ if (head[1][player] != SNAKE_DOWN)
+ head[0][player] = SNAKE_UP;
+ break;
+ case SNAKE_DOWN:
+ if (head[1][player] != SNAKE_UP)
+ head[0][player] = SNAKE_DOWN;
+ break;
+ case SNAKE_RIGHT:
+ if (head[1][player] != SNAKE_LEFT)
+ head[0][player] = SNAKE_RIGHT;
+ break;
+ case SNAKE_JUMP:
+ if (!begin) {
+ pthread_mutex_lock(&mutex);
+ begin = 1;
+ pthread_cond_broadcast(&cond);
+ pthread_mutex_unlock(&mutex);
+ }
+ if (flappy) {
+ pthread_mutex_lock(&mutex);
+ distance += PIPE_GAP / 2 *
+ SCREEN_UNIT;
+ if (distance == 0)
+ speed = 1;
+ pthread_mutex_unlock(&mutex);
+ }
+ break;
+ case SNAKE_RESET:
+ pthread_mutex_lock(&mutex);
+ reset = 1;
+ pthread_mutex_unlock(&mutex);
+ break;
+ case SNAKE_GOD:
+ god = 1;
+ break;
+ case SNAKE_GROW:
+ if (god && !flappy && !multiplayer)
+ game_snake_grow(&snake0);
+ break;
+ case SNAKE_TELEPORT:
+ if (god) {
+ snake0->x = snake0->y =
+ SCREEN_HEIGHT / 2;
+ if (flappy) {
+ snake0->next->x =
+ SCREEN_HEIGHT /
+ 2 - SCREEN_UNIT;
+ snake0->next->y =
+ SCREEN_HEIGHT /
+ 2;
+ }
+ }
+ break;
+ case SNAKE_MUSIC:
+#ifdef SOUND
+ if (Mix_PausedMusic())
+ Mix_ResumeMusic();
+ else
+ Mix_PauseMusic();
+#endif /* SOUND */
+ break;
+ case SNAKE_TICK_DECREASE:
+ if (god && !flappy && delay.tv_nsec -
+ TICK_TIME_DEC >= TICK_TIME_MIN)
+ delay.tv_nsec -= TICK_TIME_DEC;
+ break;
+ case SNAKE_TICK_INCREASE:
+ if (god && !flappy)
+ delay.tv_nsec += TICK_TIME_DEC;
+ break;
+ case SNAKE_PAUSE:
+ wait = 1;
+ break;
+ default:
+ break;
+ }
+ } else {
+ switch (input) {
+ case SNAKE_RESET:
+ wait = 0;
+
+ pthread_mutex_lock(&mutex);
+ reset = 1;
+ pthread_mutex_unlock(&mutex);
+ break;
+ case SNAKE_MUSIC:
+#ifdef SOUND
+ if (Mix_PausedMusic())
+ Mix_ResumeMusic();
+ else
+ Mix_PauseMusic();
+#endif /* SOUND */
+ break;
+ case SNAKE_SELECT:
+ if (wait)
+ break;
+
+ if (selection == 0) {
+ flappy = 0;
+ multiplayer = 0;
+ } else if (selection == 1) {
+ flappy = 0;
+ multiplayer = 1;
+ } else if (selection == 2) {
+ multiplayer = 0;
+ flappy = 1;
+ }
+
+ delay.tv_nsec = TICK_TIME_INIT;
+
+ god = 0;
+
+ head[0][0] = head[0][1] =
+ SNAKE_UP;
+ head[1][0] = head[1][1] =
+ SNAKE_EMPTY;
+
+ score = 0;
+ text_score.cache = 0;
+
+ if (flappy) {
+ delay.tv_nsec = TICK_TIME_INIT /
+ SCREEN_UNIT;
+
+ game_snake_init(&snake0, 0);
+
+ snake0->next->x =
+ SCREEN_HEIGHT / 2 -
+ SCREEN_UNIT;
+ snake0->next->y =
+ SCREEN_HEIGHT / 2;
+
+ game_pipe_init(&gap);
+
+ pthread_mutex_lock(&mutex);
+ begin = 0;
+ distance = 0;
+ speed = 1;
+ pthread_mutex_unlock(&mutex);
+ } else if (!multiplayer) {
+ game_snake_init(
+ &snake0, 0);
+ game_fruit(
+ fruit,
+ &snake0);
+ } else {
+ game_snake_init(
+ &snake0,
+ -4 *
+ SCREEN_UNIT);
+ game_snake_init(
+ &snake1,
+ 4 *
+ SCREEN_UNIT);
+ }
+ start = 0;
+
+ pthread_mutex_lock(&mutex);
+ reset = 0;
+ pthread_cond_broadcast(&cond);
+ pthread_mutex_unlock(&mutex);
+ break;
+ case SNAKE_PAUSE:
+ wait = 0;
+
+ pthread_mutex_lock(&mutex);
+ pthread_cond_broadcast(&cond);
+ pthread_mutex_unlock(&mutex);
+ break;
+ case SNAKE_UP:
+ if ((start || reset) &&
+ selection - 1 >= 0)
+ selection--;
+ break;
+ case SNAKE_DOWN:
+ if ((start || reset) &&
+ selection + 1 <= 2)
+ selection++;
+ break;
+ default:
+ break;
+ }
+ }
+ input = SNAKE_EMPTY;
+ }
+
+ start = 0;
+ end = 1;
+
+ wait = 0;
+ flappy = 1;
+
+ pthread_mutex_lock(&mutex);
+ reset = 0;
+ pthread_cond_broadcast(&cond);
+ pthread_mutex_unlock(&mutex);
+
+ #ifdef GPIO
+
+ pthread_join(th_gpio, NULL);
+
+ #endif /* GPIO */
+
+ pthread_join(th_gravity, NULL);
+ pthread_join(th_logic, NULL);
+ pthread_join(th_renderer, NULL);
+ pthread_cond_destroy(&cond);
+ pthread_mutex_destroy(&mutex);
+
+#ifdef GPIO
+
+#ifdef __linux__
+
+ gpio_unexport(GPIO_PIN_IN0);
+ gpio_unexport(GPIO_PIN_IN1);
+ gpio_unexport(GPIO_PIN_IN2);
+ gpio_unexport(GPIO_PIN_IN3);
+ gpio_unexport(GPIO_PIN_IN4);
+ gpio_unexport(GPIO_PIN_IN5);
+ gpio_unexport(GPIO_PIN_IN6);
+ gpio_unexport(GPIO_PIN_IN7);
+ gpio_unexport(GPIO_PIN_IN8);
+ gpio_unexport(GPIO_PIN_IN9);
+ gpio_unexport(GPIO_PIN_IN10);
+ gpio_unexport(GPIO_PIN_IN11);
+ gpio_unexport(GPIO_PIN_IN12);
+ gpio_unexport(GPIO_PIN_IN13);
+ gpio_unexport(GPIO_PIN_IN14);
+ gpio_unexport(GPIO_PIN_IN15);
+ gpio_unexport(GPIO_PIN_IN16);
+ gpio_unexport(GPIO_PIN_IN17);
+ gpio_unexport(GPIO_PIN_IN18);
+
+#endif /* __linux__ */
+
+#endif /* GPIO */
+
+#ifdef SOUND
+ Mix_Quit();
+#endif /* SOUND */
+
+ TTF_Quit();
+ SDL_DestroyRenderer(renderer);
+ SDL_DestroyWindow(window);
+ SDL_Quit();
+}