1/*
2 * Copyright 2016, Data61
3 * Commonwealth Scientific and Industrial Research Organisation (CSIRO)
4 * ABN 41 687 119 230.
5 *
6 * This software may be distributed and modified according to the terms of
7 * the BSD 2-Clause license. Note that NO WARRANTY is provided.
8 * See "LICENSE_BSD2.txt" for details.
9 *
10 * @TAG(D61_BSD)
11 */
12
13/*! @file
14    @brief A simple port of Snake to run as a RefOS userland demo app.
15
16    This simple port of the classic game Snake serves as an demo app for the high-level RefOS
17    userland environment. It uses a UNIX-line environment, showing serial input / output
18    functionality and timer functionality.
19*/
20
21#include <stdio.h>
22#include <stdlib.h>
23#include <string.h>
24#include <stdbool.h>
25#include <assert.h>
26#include <time.h>
27#include <unistd.h>
28
29#include <refos/refos.h>
30#include <refos-io/stdio.h>
31#include <refos-util/init.h>
32#include <refos-util/dprintf.h>
33
34/* ANSI Escape sequences. */
35#define clrscr()              puts ("\e[2J\e[1;1H")
36#define gotoxy(x,y)           printf("\e[%d;%dH", y, x); fflush(stdout)
37#define hidecursor()          puts ("\e[?25l")
38#define showcursor()          puts ("\e[?25h")
39#define printblock(x)         printf ("\e[%dm  ", x); fflush(stdout)
40
41/* Game environment defitions. */
42#define NUM_ROWS 20
43#define NUM_COLS 35
44#define MAX_SNAKE_LENGTH 50
45#define INITIAL_SNAKE_LENGTH 4
46#define DELAY_AMOUNT 250
47
48/* Screen buffer state. */
49char buffer[NUM_ROWS][NUM_COLS];
50char lastbuffer[NUM_ROWS][NUM_COLS];
51
52/* Game state. */
53int snakeX[MAX_SNAKE_LENGTH];
54int snakeY[MAX_SNAKE_LENGTH];
55int snakeLen;
56int snakeDir;
57int appleX, appleY;
58
59void newGame(void);
60
61/*! @brief Pick another random apple location. */
62void
63newApple(void)
64{
65    appleX = rand() % NUM_ROWS;
66    appleY = rand() % NUM_COLS;
67}
68
69/*! @brief Step forward the game one discrete frame. */
70void
71stepGame(void)
72{
73    /* Shift the snake down one step. */
74    for (int i = (snakeLen - 1); i >= 1; i--) {
75        snakeX[i] = snakeX[i - 1];
76        snakeY[i] = snakeY[i - 1];
77    }
78    static int dr[] = {-1, 0, 1, 0 };
79    static int dc[] = { 0, 1, 0,-1 };
80    snakeX[0] += dr[snakeDir];
81    snakeY[0] += dc[snakeDir];
82
83    /* Warp around edge of screen. */
84    if (snakeX[0] < 0) snakeX[0] += NUM_ROWS;
85    if (snakeY[0] < 0) snakeY[0] += NUM_COLS;
86    snakeX[0] %= NUM_ROWS;
87    snakeY[0] %= NUM_COLS;
88
89    /* Check if we have collided ourselves. */
90    for (int i = 1; i < snakeLen; i++) {
91        if (snakeX[i] < 0) continue;
92        if (snakeX[0] == snakeX[i] && snakeY[0] == snakeY[i]) {
93            gotoxy (0, NUM_ROWS + 1);
94            printf("Your snake has died.\n");
95            newGame();
96            return;
97        }
98    }
99
100    /* Check if we have eaten the apple. */
101    if (snakeX[0] == appleX && snakeY[0] == appleY) {
102        if (snakeLen + 1 < MAX_SNAKE_LENGTH) {
103            snakeX[snakeLen] = snakeY[snakeLen] = -1;
104            snakeLen++;
105            newApple();
106        }
107    }
108}
109
110/*! @brief Render the current game state to screen.
111
112    Speeds up rendering by first checking screen buffer differences, and only re-writing the bits
113    that have changed. Much faster as every gotoxy() is quite expensive.
114*/
115void
116renderGame(void)
117{
118    /* Render the snake. */
119    memset(buffer, 0, NUM_ROWS * NUM_COLS);
120    for (int i = 0; i < snakeLen; i++) {
121        if (snakeX[i] < 0) continue;
122        buffer[snakeX[i]][snakeY[i]] = '#';
123    }
124
125    /* Render the apple. */
126    buffer[appleX][appleY] = 'O';
127
128    /* Draw the buffer to the screen. */
129    for (int i = 0; i < NUM_ROWS; i++) {
130        for (int j = 0; j < NUM_COLS; j++) {
131            if (buffer[i][j] == lastbuffer[i][j]) continue;
132            gotoxy(j, i);
133            if (buffer[i][j] == 0) {
134                printf(" ");
135            } else if (buffer[i][j] == '#') {
136                printf(COLOUR_G "���" COLOUR_RESET);
137            } else {
138                printf(COLOUR_R "���" COLOUR_RESET);
139            }
140        }
141    }
142
143    /* Save the last buffer. */
144    memcpy(lastbuffer, buffer, NUM_ROWS * NUM_COLS);
145}
146
147/*! @brief Starts (or restarts) a new game of snake, resetting snake and apple. */
148void
149newGame(void)
150{
151    /* Display new game message. */
152    printf("      Press the space bar to continue...\n");
153    while (1) {
154        int c = refos_async_getc();
155        rand();
156        if (c == ' ') break;
157    }
158
159    /* Start the game. */
160    clrscr();
161    snakeLen = INITIAL_SNAKE_LENGTH;
162    for (int i = 1; i < snakeLen; i++) {
163        snakeX[i] = snakeY[i] = -1;
164    }
165    snakeDir = rand() % 4;
166    snakeX[0] = rand() % NUM_ROWS;
167    snakeY[0] = rand() % NUM_COLS;
168    newApple();
169}
170
171/*! @brief Change the snake direction according to key pressed. */
172void
173handleInput(int c)
174{
175    if (c == 'w') {
176        snakeDir = 0;
177    } else if (c == 's') {
178        snakeDir = 2;
179    } else if (c == 'a') {
180        snakeDir = 3;
181    } else if (c == 'd') {
182        snakeDir = 1;
183    }
184}
185
186/*! @brief Output a nice big logo. */
187static void
188print_welcome_message(void)
189{
190    printf(
191        "_______ __   _ _______ _     _ _______\n"
192        "|______ | \\  | |_____| |____/  |______\n"
193        "______| |  \\_| |     | |    \\_ |______\n"
194    );
195}
196
197
198/*! @brief Snake main function. */
199int
200main()
201{
202    /* Future Work 3:
203       How the selfloader bootstraps user processes needs to be modified further. Changes were
204       made to accomodate the new way that muslc expects process's stacks to be set up when
205       processes start, but the one part of this that still needs to changed is how user processes
206       find their system call table. Currently the selfloader sets up user processes so that
207       the selfloader's system call table is used by user processes by passing the address of the
208       selfloader's system call table to the user processes via the user process's environment
209       variables. Ideally, user processes would use their own system call table.
210    */
211
212    uintptr_t address = strtoll(getenv("SYSTABLE"), NULL, 16);
213    refos_init_selfload_child(address);
214    refos_initialise();
215    srand(time(NULL));
216    clrscr();
217    hidecursor();
218    print_welcome_message();
219    newGame();
220
221    while (true) {
222        int c = refos_async_getc();
223        if (c == 'q') {
224            break;
225        }
226        handleInput(c);
227        stepGame();
228        renderGame();
229        usleep(DELAY_AMOUNT * 1000);
230    }
231
232    showcursor();
233}
234