/* * Copyright (c) 2019-2020, yzrh * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include #ifdef __linux__ #include #include #include #endif /* __linux__ */ #include #include #ifdef SOUND #include #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 \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(); }