diff options
-rw-r--r-- | CHANGE.md | 83 | ||||
-rw-r--r-- | COPYING | 202 | ||||
-rw-r--r-- | README.md | 42 | ||||
-rw-r--r-- | src/Makefile | 50 | ||||
-rw-r--r-- | src/collision.c | 42 | ||||
-rw-r--r-- | src/extern.h | 54 | ||||
-rw-r--r-- | src/game.c | 317 | ||||
-rw-r--r-- | src/game.h | 18 | ||||
-rw-r--r-- | src/gpio.c | 194 | ||||
-rw-r--r-- | src/gpio.h | 66 | ||||
-rw-r--r-- | src/input.c | 186 | ||||
-rw-r--r-- | src/input.h | 29 | ||||
-rw-r--r-- | src/main.c | 1542 | ||||
-rw-r--r-- | src/renderer.c | 244 | ||||
-rw-r--r-- | src/renderer.h | 25 | ||||
-rw-r--r-- | src/screen.h | 52 | ||||
-rw-r--r-- | src/version.h | 10 |
17 files changed, 3156 insertions, 0 deletions
diff --git a/CHANGE.md b/CHANGE.md new file mode 100644 index 0000000..9284077 --- /dev/null +++ b/CHANGE.md @@ -0,0 +1,83 @@ +0.6.2_3 (2020-10-10) +==================== + +* Update contrib/IBM-Plex-Sans to 5.1.3 + +0.6.2_2 (2019-10-31) +==================== + +* Make sound support optional + +0.6.2_1 (2019-10-30) +==================== + +* Make GPIO support optional + +0.6.2 (2019-03-27) +================== + +* Fix GPIO interrupt + +0.6.1 (2019-03-25) +================== + +* Fix GPIO mapping + +0.6.0 (2019-03-18) +================== + +* Change timer resolution to nanosecond +* Add colour selection to renderer +* Add GPIO support + +0.5.1 (2019-03-12) +================== + +* Add a pause at the start of flappy mode + +0.5.0 (2019-03-09) +================== + +* Change keyboard key map +* Change maximum speed in classic mode to a lower value +* Change snake initial position to be centred +* Add flappy bird like game mode + +0.4.1 (2019-03-06) +================== + +* Fix swipe threshold + +0.4.0 (2019-03-04) +================== + +* Change game loop to be threaded +* Change menu to be event driven +* Change font size +* Add font texture caching +* Add option to toggle music +* Add condition for clearing game +* Add touchscreen support + +0.3.1 (2019-02-22) +================== + +* Fix pause logic + +0.3.0 (2019-02-22) +================== + +* Change memory allocation method +* Change difficult scaling +* Add pause function + +0.2.0 (2019-02-21) +================== + +* Add multiplayer mode +* Add key press driven menu + +0.1.0 (2019-02-20) +================== + +* Initial release @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..51ea1a3 --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +Snake +===== + +Dependency +---------- + +1. sdl2 with OpenGL support +2. sdl2\_ttf +3. sdl2\_mixer with FLAC support (optional) + +Build +----- + +Download contrib resources and extract +to contrib/ + +`cd src` + +`make` + +With GPIO support: + +`make -DGPIO` + +With SOUND support: + +`make -DSOUND` + +Run +--- + +`./snake` + +Installation +------------ + +`make install` + +To-do +----- + +* Leaderboard (SQLite) diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..37533bd --- /dev/null +++ b/src/Makefile @@ -0,0 +1,50 @@ +# Copyright (c) 2019-2020, yzrh <yzrh@noema.org> +# +# SPDX-License-Identifier: Apache-2.0 + +src = main.c collision.c game.c input.c renderer.c +inc = extern.h game.h input.h renderer.h screen.h version.h + +.ifdef GPIO + +src += gpio.c +inc += gpio.h + +.endif + +obj = ${src:.c=.o} + +PREFIX = /usr/local + +CFLAGS = -O3 -march=native -pipe -flto=thin -Wall -Wno-unused-result +LDFLAGS = -Wl,-O3 -lpthread -lSDL2 -lSDL2_ttf + +.ifdef SOUND + +LDFLAGS += -lSDL2_mixer + +.endif + +LDFLAGS += -Wl,--as-needed + +CFLAGS += -I/usr/local/include +LDFLAGS += -L/usr/local/lib + +all: ${obj} ${inc} + ${CC} ${LDFLAGS} -o snake ${obj} + +clean: + rm -f snake ${obj} + +install: + install -d ${PREFIX}/bin + install -d ${PREFIX}/share/snake + install snake ${PREFIX}/bin + install -m644 ../contrib/font.ttf ${PREFIX}/share/snake + install -m644 ../contrib/music.flac ${PREFIX}/share/snake + +deinstall: + rm -f ${PREFIX}/bin/snake + rm -rf ${PREFIX}/share/snake + +.PHONY: all clean install deinstall diff --git a/src/collision.c b/src/collision.c new file mode 100644 index 0000000..5810c84 --- /dev/null +++ b/src/collision.c @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2019-2020, yzrh <yzrh@noema.org> + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include <stdbool.h> + +#include <SDL2/SDL.h> + +#include "screen.h" +#include "renderer.h" + +bool +collision(int *a, int *b) +{ + if (a[0] < b[0] + b[2] && + a[0] + a[2] > b[0] && + a[1] < b[1] + b[3] && + a[1] + a[3] > b[1]) + return 1; + return 0; +} + +bool +collision_block(int *a, int *b) +{ + return collision( + (int[]) {a[0], a[1], SCREEN_UNIT, SCREEN_UNIT}, + (int[]) {b[0], b[1], SCREEN_UNIT, SCREEN_UNIT}); +} + +bool +collision_bound(float x, float y, Snake_Text *message) +{ + if (x > message->pos.x && + x < message->pos.x + message->pos.w && + y > message->pos.y && + y < message->pos.y + message->pos.h) + return 1; + return 0; +} diff --git a/src/extern.h b/src/extern.h new file mode 100644 index 0000000..482b9ed --- /dev/null +++ b/src/extern.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2019-2020, yzrh <yzrh@noema.org> + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include <SDL2/SDL.h> +#include <SDL2/SDL_ttf.h> + +#include "renderer.h" +#include "game.h" +#include "input.h" + +bool collision(int *a, int *b); +bool collision_block(int *a, int *b); +bool collision_bound(float x, float y, Snake_Text *message); + +void draw_clear(SDL_Renderer *renderer); +void draw_colour(SDL_Renderer *renderer, Snake_Colour colour); +void draw_text(SDL_Renderer *renderer, TTF_Font *font, + int text_x, int text_y, char *text, + bool centre, bool select, SDL_Color fg, SDL_Color bg, + Snake_Text *message); +void draw_wall(SDL_Renderer *renderer); +void draw_snake(SDL_Renderer *renderer, Snake_Pos **snake); +void draw_fruit(SDL_Renderer *renderer, int *fruit); +void draw_pipe(SDL_Renderer *renderer, Snake_Pos **pipe); + +void game_snake_init(Snake_Pos **snake, int dx); +void game_snake_move(Snake_Pos **snake0, Snake_Pos **snake1, + Snake_Input *head, bool lawi, int *legal); +void game_snake_grow(Snake_Pos **snake); +void game_fruit(int *fruit, Snake_Pos **snake); +void game_snake_fall(Snake_Pos **snake, bool norm, int speed); +void game_pipe_init(Snake_Pos **pipe); +void game_pipe_move(Snake_Pos **pipe); +void game_pipe_add(Snake_Pos **pipe); +void game_pipe_del(Snake_Pos **pipe); +void game_pipe_pos(Snake_Pos **pipe, int *state); +void game_pipe_bound(Snake_Pos **pipe, int *pos); + +void input_keyboard(SDL_Keycode sym, Snake_Input *code); +void input_controller(uint8_t button, Snake_Input *code); +void input_gpio(int gpio, uint8_t *button); + +#ifdef GPIO + +int gpio_export(int pin); +int gpio_unexport(int pin); +int gpio_config(int pin, int direction, int interrupt); +int gpio_write(int pin, int value); +int gpio_read(int pin); + +#endif /* GPIO */ diff --git a/src/game.c b/src/game.c new file mode 100644 index 0000000..1a836d0 --- /dev/null +++ b/src/game.c @@ -0,0 +1,317 @@ +/* + * Copyright (c) 2019-2020, yzrh <yzrh@noema.org> + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include <stdbool.h> + +#include "extern.h" +#include "screen.h" + +static bool +_collision(int *fruit, Snake_Pos **snake) +{ + Snake_Pos *ptr = *snake; + while (ptr != NULL) { + if (collision_block(fruit, (int[]) {ptr->x, ptr->y})) + return 1; + ptr = ptr->next; + } + return 0; +} + +static bool +_bound(Snake_Pos **snake) +{ + if ((*snake)->x + SCREEN_UNIT / 2 > SCREEN_HEIGHT) + return 1; + else if ((*snake)->y + SCREEN_UNIT / 2 > SCREEN_HEIGHT) + return 1; + else if ((*snake)->x - SCREEN_UNIT / 2 < -SCREEN_UNIT) + return 1; + else if ((*snake)->y - SCREEN_UNIT / 2 < -SCREEN_UNIT) + return 1; + return 0; +} + +static int +_overlap(Snake_Pos **snake0, Snake_Pos **snake1) +{ + Snake_Pos *ptr0 = *snake0; + Snake_Pos *ptr1; + + int is_overlap[2] = {0, 0}; + + while (ptr0->next->next != NULL) { + if (collision_block( + (int[]) {(*snake0)->x, (*snake0)->y}, + (int[]) {ptr0->next->x, ptr0->next->y})) { + is_overlap[0] = 1; + break; + } + ptr0 = ptr0->next; + } + + if (snake1 != NULL) { + ptr0 = *snake0; + ptr1 = *snake1; + while (ptr0->next->next != NULL && + ptr1->next->next != NULL && + (is_overlap[0] != 1 || is_overlap[1] != 2)) { + if (collision_block( + (int[]) {(*snake0)->x, (*snake0)->y}, + (int[]) {ptr1->next->x, ptr1->next->y})) + is_overlap[0] = 1; + if (collision_block( + (int[]) {(*snake1)->x, (*snake1)->y}, + (int[]) {ptr0->next->x, ptr0->next->y})) + is_overlap[1] = 2; + if (collision_block( + (int[]) {(*snake1)->x, (*snake1)->y}, + (int[]) {ptr1->next->x, ptr1->next->y})) + is_overlap[1] = 2; + ptr0 = ptr0->next; + ptr1 = ptr1->next; + } + } + + return is_overlap[0] + is_overlap[1]; +} + +void +game_snake_init(Snake_Pos **snake, int dx) +{ + Snake_Pos *ptr; + while ((ptr = *snake) != NULL) { + *snake = (*snake)->next; + free(ptr); + } + + *snake = malloc(sizeof(Snake_Pos)); + (*snake)->next = malloc(sizeof(Snake_Pos)); + + (*snake)->x = (*snake)->next->x = SCREEN_HEIGHT / 2 + dx; + (*snake)->y = SCREEN_HEIGHT / 2; + (*snake)->next->y = SCREEN_HEIGHT / 2 + SCREEN_UNIT; + (*snake)->next->next = NULL; +} + +void +game_snake_move(Snake_Pos **snake0, Snake_Pos **snake1, + Snake_Input *head, bool law, int *legal) +{ + bool bound_snake0; + bool bound_snake1; + int overlap; + + Snake_Pos *ptr0; + Snake_Pos *ptr1; + + ptr0 = malloc(sizeof(Snake_Pos)); + ptr0->x = (*snake0)->x; + ptr0->y = (*snake0)->y; + ptr0->next = *snake0; + + switch (head[0]) { + case SNAKE_LEFT: + ptr0->x -= SCREEN_UNIT; + break; + case SNAKE_UP: + ptr0->y -= SCREEN_UNIT; + break; + case SNAKE_DOWN: + ptr0->y += SCREEN_UNIT; + break; + case SNAKE_RIGHT: + ptr0->x += SCREEN_UNIT; + break; + default: + break; + } + + bound_snake0 = _bound(&ptr0); + + if (snake1 == NULL) { + overlap = _overlap(&ptr0, NULL); + + if (bound_snake0 || overlap == 1) + *legal = 1; + else + *legal = 0; + } else { + ptr1 = malloc(sizeof(Snake_Pos)); + ptr1->x = (*snake1)->x; + ptr1->y = (*snake1)->y; + ptr1->next = *snake1; + + switch (head[1]) { + case SNAKE_LEFT: + ptr1->x -= SCREEN_UNIT; + break; + case SNAKE_UP: + ptr1->y -= SCREEN_UNIT; + break; + case SNAKE_DOWN: + ptr1->y += SCREEN_UNIT; + break; + case SNAKE_RIGHT: + ptr1->x += SCREEN_UNIT; + break; + default: + break; + } + + bound_snake1 = _bound(&ptr1); + overlap = _overlap(&ptr0, &ptr1); + + if ((bound_snake0 && bound_snake1) || overlap == 3) + *legal = 3; + else if (bound_snake1 || overlap == 2) + *legal = 2; + else if (bound_snake0 || overlap == 1) + *legal = 1; + else + *legal = 0; + } + + if (*legal == 0 || !law) { + *snake0 = ptr0; + + while (ptr0->next->next != NULL) + ptr0 = ptr0->next; + free(ptr0->next); + ptr0->next = NULL; + + if (snake1 != NULL) { + *snake1 = ptr1; + + while (ptr1->next->next != NULL) + ptr1 = ptr1->next; + free(ptr1->next); + ptr1->next = NULL; + } + } else { + free(ptr0); + + if (snake1 != NULL) + free(ptr1); + } +} + +void +game_snake_grow(Snake_Pos **snake) +{ + Snake_Pos *ptr = *snake; + while (ptr->next != NULL) + ptr = ptr->next; + ptr->next = malloc(sizeof(Snake_Pos)); + ptr->next->x = ptr->x; + ptr->next->y = ptr->y; + ptr->next->next = NULL; +} + +void +game_fruit(int *fruit, Snake_Pos **snake) +{ + do { + fruit[0] = rand() % SCREEN_SIDE * SCREEN_UNIT; + fruit[1] = rand() % SCREEN_SIDE * SCREEN_UNIT; + } while (_collision(fruit, snake)); +} + +void +game_snake_fall(Snake_Pos **snake, bool norm, int speed) +{ + Snake_Pos *ptr = *snake; + while(ptr != NULL) { + ptr->y += norm ? speed : -speed; + ptr = ptr->next; + } +} + +void +game_pipe_init(Snake_Pos **pipe) +{ + Snake_Pos *ptr; + while ((ptr = *pipe) != NULL) { + *pipe = (*pipe)->next; + free(ptr); + } + + *pipe = malloc(sizeof(Snake_Pos)); + (*pipe)->x = SCREEN_HEIGHT; + (*pipe)->y = (rand() % (SCREEN_SIDE - PIPE_GAP) + PIPE_GAP) * + SCREEN_UNIT; + (*pipe)->next = NULL; +} + +void +game_pipe_move(Snake_Pos **pipe) +{ + Snake_Pos *ptr = *pipe; + while(ptr != NULL) { + ptr->x -= 1; + ptr = ptr->next; + } +} + +void +game_pipe_add(Snake_Pos **pipe) +{ + Snake_Pos *ptr = *pipe; + while(ptr->next != NULL) + ptr = ptr->next; + ptr->next = malloc(sizeof(Snake_Pos)); + ptr->next->x = SCREEN_HEIGHT; + ptr->next->y = (rand() % (SCREEN_SIDE - PIPE_GAP) + PIPE_GAP) * + SCREEN_UNIT; + ptr->next->next = NULL; +} + +void +game_pipe_del(Snake_Pos **pipe) +{ + Snake_Pos *ptr = *pipe; + *pipe = (*pipe)->next; + free(ptr); +} + +void +game_pipe_pos(Snake_Pos **pipe, int *state) +{ + Snake_Pos *ptr = *pipe; + *state = 0; + *state += ptr->x + SCREEN_UNIT < 0 ? 1 : 0; + while(ptr->next != NULL) + ptr = ptr->next; + *state += ptr->x < SCREEN_HEIGHT - PIPE_DISTANCE * SCREEN_UNIT ? 2 : 0; +} + +/* + * This function returns the dimension of pipes + * that snake is currently passing through, + * so that we can avoid checking all pipes for + * collision + * + * Array elements: x, w, y0, h0, y1, h1 + */ +void +game_pipe_bound(Snake_Pos **pipe, int *pos) +{ + Snake_Pos *ptr = *pipe; + while(ptr != NULL) { + if (ptr->x < SCREEN_HEIGHT / 2 && + ptr->x + SCREEN_UNIT > SCREEN_HEIGHT / 2) { + pos[0] = ptr->x; + pos[1] = SCREEN_UNIT; + pos[2] = 0; + pos[3] = ptr->y - PIPE_GAP / 2 * SCREEN_UNIT; + pos[4] = ptr->y + PIPE_GAP / 2 * SCREEN_UNIT; + pos[5] = SCREEN_HEIGHT - ptr->y - SCREEN_UNIT; + return; + } + ptr = ptr->next; + } + pos[0] = 0; +} diff --git a/src/game.h b/src/game.h new file mode 100644 index 0000000..a510c6c --- /dev/null +++ b/src/game.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2019-2020, yzrh <yzrh@noema.org> + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define TICK_TIME_INIT 250000000 +#define TICK_TIME_DEC 10000000 +#define TICK_TIME_MIN 100000000 + +#define PIPE_GAP 4 +#define PIPE_DISTANCE 10 + +typedef struct _Snake_Pos { + int x; + int y; + struct _Snake_Pos *next; +} Snake_Pos; diff --git a/src/gpio.c b/src/gpio.c new file mode 100644 index 0000000..9d4cde1 --- /dev/null +++ b/src/gpio.c @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2019-2020, yzrh <yzrh@noema.org> + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "gpio.h" + +#ifdef __FreeBSD__ + +int +gpio_config(int pin, int direction, int interrupt) +{ + int ret; + int fd = open("/dev/gpioc0", O_RDWR); + + if (fd == -1) { + fprintf(stderr, "GPIO configuration failure\n"); + return 1; + } + + struct gpio_pin pin_req = { + .gp_pin = pin, + .gp_caps = interrupt, + .gp_flags = direction + }; + + ret = ioctl(fd, GPIOSETCONFIG, &pin_req) == 0 ? 0 : 1; + close(fd); + return ret; +} + +int +gpio_write(int pin, int value) +{ + int ret; + int fd = open("/dev/gpioc0", O_RDWR); + + if (fd == -1) { + fprintf(stderr, "GPIO output failure\n"); + return 1; + } + + struct gpio_req req = { + .gp_pin = pin, + .gp_value = value + }; + + ret = ioctl(fd, GPIOSET, &req) == 0 ? 0 : 1; + close(fd); + return ret; +} + +int +gpio_read(int pin) +{ + int fd = open("/dev/gpioc0", O_RDWR); + + if (fd == -1) { + fprintf(stderr, "GPIO input failure\n"); + return 1; + } + + struct gpio_req req = { + .gp_pin = pin + }; + + ioctl(fd, GPIOGET, &req); + + close(fd); + return req.gp_value; +} + +#elif __linux__ + +int +gpio_export(int pin) +{ + int export = open("/sys/class/gpio/export", O_RDWR); + + if (export == -1) { + fprintf(stderr, "GPIO export failure\n"); + return 1; + } + + char buf[3]; + int buf_size = snprintf(buf, 3, "%d", pin); + write(export, buf, buf_size); + + close(export); + return 0; +} + +int +gpio_unexport(int pin) +{ + int unexport = open("/sys/class/gpio/unexport", O_RDWR); + + if (unexport == -1) { + fprintf(stderr, "GPIO unexport failure\n"); + return 1; + } + + char buf[3]; + int buf_size = snprintf(buf, 3, "%d", pin); + write(unexport, buf, buf_size); + + close(unexport); + return 0; +} + +int +gpio_config(int pin, int direction, int interrupt) +{ + char path[33]; + snprintf(path, 33, "/sys/class/gpio/gpio%d/direction", pin); + + int config = open(path, O_RDWR); + + if (config == -1) { + fprintf(stderr, "GPIO configuration failure\n"); + return 1; + } + + char buf[8]; + int buf_size = snprintf(buf, 8, "%s", direction == 0 ? "in" : "out"); + write(config, buf, buf_size); + + close(config); + + snprintf(path, 33, "/sys/class/gpio/gpio%d/edge", pin); + + config = open(path, O_RDWR); + + if (config == -1) { + fprintf(stderr, "GPIO configuration failure\n"); + return 1; + } + + buf_size = snprintf(buf, 8, "%s", + interrupt == 0 ? "falling" : "rising"); + write(config, buf, buf_size); + + close(config); + return 0; +} + +int +gpio_write(int pin, int value) +{ + char path[29]; + snprintf(path, 29, "/sys/class/gpio/gpio%d/value", pin); + + int config = open(path, O_RDWR); + + if (config == -1) { + fprintf(stderr, "GPIO output failure\n"); + return 1; + } + + char buf[2]; + int buf_size = snprintf(buf, 2, "%d", value); + write(config, buf, buf_size); + + close(config); + return 0; +} + +int +gpio_read(int pin) +{ + char path[29]; + snprintf(path, 29, "/sys/class/gpio/gpio%d/value", pin); + + int config = open(path, O_RDONLY); + + if (config == -1) { + fprintf(stderr, "GPIO input failure\n"); + return 1; + } + + char buf[2]; + read(config, buf, 2); + + close(config); + return atoi(buf); +} + +#endif /* __FreeBSD__ */ diff --git a/src/gpio.h b/src/gpio.h new file mode 100644 index 0000000..0b6b75d --- /dev/null +++ b/src/gpio.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2019-2020, yzrh <yzrh@noema.org> + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define GPIO_LOW 0x00 +#define GPIO_HIGH 0x01 + +#ifdef __FreeBSD__ + +#include <stdint.h> + +#include <sys/gpio.h> + +#define GPIO_IN GPIO_PIN_INPUT +#define GPIO_OUT GPIO_PIN_OUTPUT +#define GPIO_INT GPIO_INTR_EDGE_FALLING + +#elif __linux__ + +#define GPIO_IN 0x00 +#define GPIO_OUT 0x01 +/* + * 0: falling + * 1: rising + */ +#define GPIO_INT 0 + +#endif /* __FreeBSD__ */ + +/* Raspberry Pi 3 */ +#define GPIO_PIN_IN0 4 /* 7: DPAD_LEFT */ +#define GPIO_PIN_IN1 14 /* 8: DPAD_UP */ +#define GPIO_PIN_IN2 15 /* 10: DPAD_DOWN */ +#define GPIO_PIN_IN3 17 /* 11: DPAD_RIGHT */ + +#define GPIO_PIN_IN4 18 /* 12: BACK */ +#define GPIO_PIN_IN5 27 /* 13: GUIDE */ +#define GPIO_PIN_IN6 22 /* 15: START */ + +#define GPIO_PIN_IN7 23 /* 16: A */ +#define GPIO_PIN_IN8 24 /* 18: B */ +#define GPIO_PIN_IN9 10 /* 19: X */ +#define GPIO_PIN_IN10 9 /* 21: Y */ + +#define GPIO_PIN_IN11 25 /* 22: LEFTSHOULDER */ +#define GPIO_PIN_IN12 11 /* 23: RIGHTSHOULDER */ + +#define GPIO_PIN_IN13 8 /* 24: LEFTSTICK */ +#define GPIO_PIN_IN14 7 /* 26: RIGHTSTICK */ + +/* Second controller */ +#define GPIO_PIN_IN15 0 /* 27: DPAD_LEFT */ +#define GPIO_PIN_IN16 1 /* 28: DPAD_UP */ +#define GPIO_PIN_IN17 5 /* 29: DPAD_DOWN */ +#define GPIO_PIN_IN18 6 /* 31: DPAD_RIGHT */ + +/* Unused */ +#define GPIO_PIN_IN19 12 /* 32: NULL */ +#define GPIO_PIN_IN20 13 /* 33: NULL */ +#define GPIO_PIN_IN21 19 /* 35: NULL */ +#define GPIO_PIN_IN22 16 /* 36: NULL */ +#define GPIO_PIN_IN23 26 /* 37: NULL */ +#define GPIO_PIN_IN24 20 /* 38: NULL */ +#define GPIO_PIN_IN25 21 /* 40: NULL */ diff --git a/src/input.c b/src/input.c new file mode 100644 index 0000000..46ba24a --- /dev/null +++ b/src/input.c @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2019-2020, yzrh <yzrh@noema.org> + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include <SDL2/SDL.h> + +#include "input.h" + +void +input_keyboard(SDL_Keycode sym, Snake_Input *code) +{ + switch (sym) { + case SDLK_LEFT: + *code = SNAKE_LEFT; + break; + case SDLK_UP: + *code = SNAKE_UP; + break; + case SDLK_DOWN: + *code = SNAKE_DOWN; + break; + case SDLK_RIGHT: + *code = SNAKE_RIGHT; + break; + case SDLK_SPACE: + *code = SNAKE_JUMP; + break; + case SDLK_RETURN: + *code = SNAKE_SELECT; + break; + case SDLK_ESCAPE: + *code = SNAKE_PAUSE; + break; + case SDLK_r: + *code = SNAKE_RESET; + break; + case SDLK_g: + *code = SNAKE_GOD; + break; + case SDLK_h: + *code = SNAKE_GROW; + break; + case SDLK_t: + *code = SNAKE_TELEPORT; + break; + case SDLK_m: + *code = SNAKE_MUSIC; + break; + case SDLK_COMMA: + *code = SNAKE_TICK_DECREASE; + break; + case SDLK_PERIOD: + *code = SNAKE_TICK_INCREASE; + break; + case SDLK_s: + *code = SNAKE_LEFT; + break; + case SDLK_e: + *code = SNAKE_UP; + break; + case SDLK_d: + *code = SNAKE_DOWN; + break; + case SDLK_f: + *code = SNAKE_RIGHT; + break; + default: + *code = SNAKE_EMPTY; + break; + } +} + +void +input_controller(uint8_t button, Snake_Input *code) +{ + switch (button) { + case SDL_CONTROLLER_BUTTON_DPAD_LEFT: + *code = SNAKE_LEFT; + break; + case SDL_CONTROLLER_BUTTON_DPAD_UP: + *code = SNAKE_UP; + break; + case SDL_CONTROLLER_BUTTON_DPAD_DOWN: + *code = SNAKE_DOWN; + break; + case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: + *code = SNAKE_RIGHT; + break; + case SDL_CONTROLLER_BUTTON_START: + *code = SNAKE_SELECT; + break; + case SDL_CONTROLLER_BUTTON_BACK: + *code = SNAKE_PAUSE; + break; + case SDL_CONTROLLER_BUTTON_GUIDE: + *code = SNAKE_GOD; + break; + case SDL_CONTROLLER_BUTTON_A: + *code = SNAKE_JUMP; + break; + case SDL_CONTROLLER_BUTTON_B: + break; + case SDL_CONTROLLER_BUTTON_X: + *code = SNAKE_RESET; + break; + case SDL_CONTROLLER_BUTTON_Y: + *code = SNAKE_MUSIC; + break; + case SDL_CONTROLLER_BUTTON_LEFTSTICK: + *code = SNAKE_TELEPORT; + break; + case SDL_CONTROLLER_BUTTON_RIGHTSTICK: + *code = SNAKE_GROW; + break; + case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: + *code = SNAKE_TICK_DECREASE; + break; + case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: + *code = SNAKE_TICK_INCREASE; + break; + default: + *code = SNAKE_EMPTY; + break; + } +} + +#ifdef GPIO + +void +input_gpio(int gpio, uint8_t *button) +{ + switch (gpio) { + case 0: + *button = SDL_CONTROLLER_BUTTON_DPAD_LEFT; + break; + case 1: + *button = SDL_CONTROLLER_BUTTON_DPAD_UP; + break; + case 2: + *button = SDL_CONTROLLER_BUTTON_DPAD_DOWN; + break; + case 3: + *button = SDL_CONTROLLER_BUTTON_DPAD_RIGHT; + break; + case 4: + *button = SDL_CONTROLLER_BUTTON_BACK; + break; + case 5: + *button = SDL_CONTROLLER_BUTTON_GUIDE; + break; + case 6: + *button = SDL_CONTROLLER_BUTTON_START; + break; + case 7: + *button = SDL_CONTROLLER_BUTTON_A; + break; + case 8: + *button = SDL_CONTROLLER_BUTTON_B; + break; + case 9: + *button = SDL_CONTROLLER_BUTTON_X; + break; + case 10: + *button = SDL_CONTROLLER_BUTTON_Y; + break; + case 11: + *button = SDL_CONTROLLER_BUTTON_LEFTSHOULDER; + break; + case 12: + *button = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER; + break; + case 13: + *button = SDL_CONTROLLER_BUTTON_LEFTSTICK; + break; + case 14: + *button = SDL_CONTROLLER_BUTTON_RIGHTSTICK; + break; + default: + *button = SDL_CONTROLLER_BUTTON_INVALID; + break; + } +} + +#endif /* GPIO */ diff --git a/src/input.h b/src/input.h new file mode 100644 index 0000000..0446474 --- /dev/null +++ b/src/input.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2019-2020, yzrh <yzrh@noema.org> + * + * SPDX-License-Identifier: Apache-2.0 + */ + +typedef enum { + SNAKE_EMPTY, + SNAKE_LEFT, + SNAKE_UP, + SNAKE_DOWN, + SNAKE_RIGHT, + SNAKE_JUMP, + SNAKE_SELECT, + SNAKE_PAUSE, + SNAKE_RESET, + SNAKE_GOD, + SNAKE_GROW, + SNAKE_TELEPORT, + SNAKE_MUSIC, + SNAKE_TICK_DECREASE, + SNAKE_TICK_INCREASE +} Snake_Input; + +typedef enum { + SNAKE_KEYBOARD, + SNAKE_TOUCHSCREEN, + SNAKE_CONTROLLER +} Snake_Device; 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(); +} diff --git a/src/renderer.c b/src/renderer.c new file mode 100644 index 0000000..c742e18 --- /dev/null +++ b/src/renderer.c @@ -0,0 +1,244 @@ +/* + * Copyright (c) 2019-2020, yzrh <yzrh@noema.org> + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include <stdbool.h> + +#include <SDL2/SDL.h> +#include <SDL2/SDL_ttf.h> + +#include "renderer.h" +#include "screen.h" +#include "game.h" + +void +draw_clear(SDL_Renderer *renderer) +{ + SDL_SetRenderDrawColor(renderer, + COLOUR_BACKGROUND_R, + COLOUR_BACKGROUND_G, + COLOUR_BACKGROUND_B, + 255); + SDL_RenderClear(renderer); +} + +void +draw_colour(SDL_Renderer *renderer, Snake_Colour colour) +{ + int r; + int g; + int b; + + switch (colour) { + case SNAKE_FOREGROUND: + r = COLOUR_FOREGROUND_R; + g = COLOUR_FOREGROUND_G; + b = COLOUR_FOREGROUND_B; + break; + case SNAKE_BACKGROUND: + r = COLOUR_BACKGROUND_R; + g = COLOUR_BACKGROUND_G; + b = COLOUR_BACKGROUND_B; + break; + case SNAKE_BACKGROUND_SHADE: + r = COLOUR_BACKGROUND_SHADE_R; + g = COLOUR_BACKGROUND_SHADE_G; + b = COLOUR_BACKGROUND_SHADE_B; + break; + case SNAKE_RED: + r = COLOUR_RED_R; + g = COLOUR_RED_G; + b = COLOUR_RED_B; + break; + case SNAKE_GREEN: + r = COLOUR_GREEN_R; + g = COLOUR_GREEN_G; + b = COLOUR_GREEN_B; + break; + case SNAKE_BLUE: + r = COLOUR_RED_R; + g = COLOUR_RED_G; + b = COLOUR_RED_B; + break; + case SNAKE_YELLOW: + r = COLOUR_RED_R; + g = COLOUR_RED_G; + b = COLOUR_RED_B; + break; + case SNAKE_MAGENTA: + r = COLOUR_RED_R; + g = COLOUR_RED_G; + b = COLOUR_RED_B; + break; + case SNAKE_CYAN: + r = COLOUR_RED_R; + g = COLOUR_RED_G; + b = COLOUR_RED_B; + break; + case SNAKE_WHITE: + r = COLOUR_RED_R; + g = COLOUR_RED_G; + b = COLOUR_RED_B; + break; + case SNAKE_BLACK: + r = COLOUR_RED_R; + g = COLOUR_RED_G; + b = COLOUR_RED_B; + break; + default: + break; + } + + SDL_SetRenderDrawColor(renderer, + r, + g, + b, + 255); +} + +void +draw_text(SDL_Renderer *renderer, TTF_Font *font, + int text_x, int text_y, char *text, + bool centre, bool select, SDL_Color fg, SDL_Color bg, + Snake_Text *message) +{ + if (!message->cache) { + if (message->texture != NULL) + SDL_DestroyTexture(message->texture); + + SDL_Surface *surface = + TTF_RenderUTF8_Shaded(font, text, fg, bg); + message->texture = + SDL_CreateTextureFromSurface(renderer, surface); + SDL_FreeSurface(surface); + + message->pos.x = text_x; + message->pos.y = text_y; + TTF_SizeText(font, text, &message->pos.w, &message->pos.h); + if (centre) + message->pos.x -= message->pos.w / 2; + message->pos.y -= message->pos.h; + + message->cache = 1; + } + + SDL_RenderCopy(renderer, message->texture, NULL, &message->pos); + + if (select) { + SDL_SetRenderDrawColor(renderer, + COLOUR_FOREGROUND_R, + COLOUR_FOREGROUND_G, + COLOUR_FOREGROUND_B, + 255); + SDL_RenderDrawLine(renderer, + message->pos.x, message->pos.y, + message->pos.x, message->pos.y + message->pos.h); + SDL_RenderDrawLine(renderer, + message->pos.x, message->pos.y, + message->pos.x + message->pos.w, message->pos.y); + SDL_RenderDrawLine(renderer, + message->pos.x, message->pos.y + message->pos.h, + message->pos.x + message->pos.w, + message->pos.y + message->pos.h); + SDL_RenderDrawLine(renderer, + message->pos.x + message->pos.w, message->pos.y, + message->pos.x + message->pos.w, + message->pos.y + message->pos.h); + } +} + +void +draw_wall(SDL_Renderer *renderer) +{ + SDL_RenderDrawLine(renderer, + 0, 0, + 0, SCREEN_HEIGHT); + SDL_RenderDrawLine(renderer, + 0, 0, + SCREEN_HEIGHT, 0); + SDL_RenderDrawLine(renderer, + 0, SCREEN_HEIGHT - 1, + SCREEN_HEIGHT, SCREEN_HEIGHT - 1); + SDL_RenderDrawLine(renderer, + SCREEN_HEIGHT, 0, + SCREEN_HEIGHT, SCREEN_HEIGHT); +} + +void +draw_snake(SDL_Renderer *renderer, Snake_Pos **snake) +{ + Snake_Pos *ptr = *snake; + + SDL_Rect pos; + while (ptr != NULL) { + pos.x = ptr->x; + pos.y = ptr->y; + pos.w = SCREEN_UNIT; + pos.h = SCREEN_UNIT; + SDL_RenderFillRect(renderer, &pos); + ptr = ptr->next; + } +} + +void +draw_fruit(SDL_Renderer *renderer, int *fruit) +{ + int rx = fruit[0] + SCREEN_UNIT / 2; + int ry = fruit[1] + SCREEN_UNIT / 2; + + /* Midpoint circle algorithm */ + int x = SCREEN_UNIT / 2; + int y = 0; + int dx = 1; + int dy = 1; + int err = dx - ((SCREEN_UNIT / 2) << 1); + while (x >= y) { + SDL_RenderDrawLine(renderer, + rx - y, ry + x, + rx + y, ry + x); + SDL_RenderDrawLine(renderer, + rx - x, ry + y, + rx + x, ry + y); + SDL_RenderDrawLine(renderer, + rx - x, ry - y, + rx + x, ry - y); + SDL_RenderDrawLine(renderer, + rx - y, ry - x, + rx + y, ry - x); + + if (err <= 0) { + y++; + err += dy; + dy += 2; + } + + if (err > 0) { + x--; + dx += 2; + err += dx - ((SCREEN_UNIT / 2) << 1); + } + } +} + +void +draw_pipe(SDL_Renderer *renderer, Snake_Pos **pipe) +{ + Snake_Pos *ptr = *pipe; + + SDL_Rect pos; + while (ptr != NULL) { + pos.x = ptr->x; + pos.y = 0; + pos.w = SCREEN_UNIT; + if (ptr->x + SCREEN_UNIT > SCREEN_HEIGHT) + pos.w -= ptr->x + SCREEN_UNIT - SCREEN_HEIGHT; + pos.h = ptr->y - PIPE_GAP / 2 * SCREEN_UNIT; + SDL_RenderFillRect(renderer, &pos); + pos.y = ptr->y + PIPE_GAP / 2 * SCREEN_UNIT; + pos.h = SCREEN_HEIGHT - ptr->y - SCREEN_UNIT; + SDL_RenderFillRect(renderer, &pos); + ptr = ptr->next; + } +} diff --git a/src/renderer.h b/src/renderer.h new file mode 100644 index 0000000..b6de4d8 --- /dev/null +++ b/src/renderer.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2019-2020, yzrh <yzrh@noema.org> + * + * SPDX-License-Identifier: Apache-2.0 + */ + +typedef struct { + SDL_Texture *texture; + SDL_Rect pos; + bool cache; +} Snake_Text; + +typedef enum { + SNAKE_FOREGROUND, + SNAKE_BACKGROUND, + SNAKE_BACKGROUND_SHADE, + SNAKE_RED, + SNAKE_GREEN, + SNAKE_BLUE, + SNAKE_YELLOW, + SNAKE_MAGENTA, + SNAKE_CYAN, + SNAKE_WHITE, + SNAKE_BLACK +} Snake_Colour; diff --git a/src/screen.h b/src/screen.h new file mode 100644 index 0000000..158cdaa --- /dev/null +++ b/src/screen.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2019-2020, yzrh <yzrh@noema.org> + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define SCREEN_WIDTH 800 +#define SCREEN_HEIGHT 480 +#define SCREEN_UNIT 20 + +#define COLOUR_FOREGROUND_R 51 +#define COLOUR_FOREGROUND_G 255 +#define COLOUR_FOREGROUND_B 51 +#define COLOUR_BACKGROUND_R 40 +#define COLOUR_BACKGROUND_G 40 +#define COLOUR_BACKGROUND_B 40 +#define COLOUR_BACKGROUND_SHADE_R 80 +#define COLOUR_BACKGROUND_SHADE_G 80 +#define COLOUR_BACKGROUND_SHADE_B 80 + +#define COLOUR_RED_R 255 +#define COLOUR_RED_G 0 +#define COLOUR_RED_B 0 +#define COLOUR_GREEN_R 0 +#define COLOUR_GREEN_G 255 +#define COLOUR_GREEN_B 0 +#define COLOUR_BLUE_R 0 +#define COLOUR_BLUE_G 0 +#define COLOUR_BLUE_B 255 + +#define COLOUR_YELLOW_R 255 +#define COLOUR_YELLOW_G 255 +#define COLOUR_YELLOW_B 0 +#define COLOUR_MAGENTA_R 255 +#define COLOUR_MAGENTA_G 0 +#define COLOUR_MAGENTA_B 255 +#define COLOUR_CYAN_R 0 +#define COLOUR_CYAN_G 255 +#define COLOUR_CYAN_B 255 + +#define COLOUR_WHITE_R 255 +#define COLOUR_WHITE_G 255 +#define COLOUR_WHITE_B 255 + +#define COLOUR_BLACK_R 0 +#define COLOUR_BLACK_G 0 +#define COLOUR_BLACK_B 0 + +#define SCREEN_UNIT_X ((SCREEN_WIDTH - SCREEN_HEIGHT) / 8) +#define SCREEN_UNIT_Y (SCREEN_HEIGHT / 8) +#define SCREEN_SIDE (SCREEN_HEIGHT / SCREEN_UNIT - 1) +#define SCREEN_BLOCK (SCREEN_SIDE * SCREEN_SIDE) diff --git a/src/version.h b/src/version.h new file mode 100644 index 0000000..4cdcebb --- /dev/null +++ b/src/version.h @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2019-2020, yzrh <yzrh@noema.org> + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define VERSION "0" +#define RELEASE "6" +#define PATCH "2" +#define EXTRA "_3" |