aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoryzrh <yzrh@noema.org>2020-10-10 17:16:14 +0000
committeryzrh <yzrh@noema.org>2020-10-10 17:37:53 +0000
commitb9b74e88028f81e7b169a5e168102138e8c2c46c (patch)
tree2b8f9f8896db3a6871c56215acf71cb502fe7be4
downloadsnake-sdl-b9b74e88028f81e7b169a5e168102138e8c2c46c.tar.gz
snake-sdl-b9b74e88028f81e7b169a5e168102138e8c2c46c.tar.zst
Initial commit.
-rw-r--r--CHANGE.md83
-rw-r--r--COPYING202
-rw-r--r--README.md42
-rw-r--r--src/Makefile50
-rw-r--r--src/collision.c42
-rw-r--r--src/extern.h54
-rw-r--r--src/game.c317
-rw-r--r--src/game.h18
-rw-r--r--src/gpio.c194
-rw-r--r--src/gpio.h66
-rw-r--r--src/input.c186
-rw-r--r--src/input.h29
-rw-r--r--src/main.c1542
-rw-r--r--src/renderer.c244
-rw-r--r--src/renderer.h25
-rw-r--r--src/screen.h52
-rw-r--r--src/version.h10
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
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/COPYING
@@ -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"