diff options
Diffstat (limited to 'src/main.c')
-rw-r--r-- | src/main.c | 1542 |
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(); +} |