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