worm.c (10501B)
1 /* See LICENSE file for copyright and license details. 2 * 3 * The Hermit Worm - Commodore64 snake-like game. 4 */ 5 6 #include <stdio.h> 7 #include <curses.h> 8 #include <time.h> 9 #include <stdlib.h> 10 #include <string.h> 11 #include <unistd.h> 12 13 #define HEAD 'o' /* normal head */ 14 #define WORM 'w' /* worm's body */ 15 #define EATH 'O' /* eating head */ 16 17 #define MINROW 21 18 #define MINCOL 80 19 20 #define LEVEAT 5 /* Food eated befor to increment the level */ 21 #define LEVMAX 9 /* Max speed (to preserve the refresh delay in auto-mode) */ 22 23 /* Recursive list */ 24 typedef struct list_t { 25 int x; 26 int y; 27 struct list_t *next; 28 } List; 29 30 /* Routes */ 31 enum { R_UP = 1, R_LEFT, R_DOWN, R_RIGHT } route; 32 33 /* function declarations */ 34 void newnode(List **listp, int y, int x); 35 void wormshow(List *worm, char ch, char chhead); 36 void moveworm(List **worm, int way); 37 int isout(List *worm); 38 int isbite(List *worm); 39 int iseat(List *worm, int row, int col); 40 void killworm(List **worm); 41 42 /* function implementations */ 43 int 44 main(int argc, char **argv) { 45 int ch = 0, n, eating, trick = 0; 46 int erow, ecol; /* eat position */ 47 int level, tricks, moves, score; 48 49 WINDOW *wstat, *wpos, *wepos; /* Status and positions windows */ 50 List *worm = NULL, *current; /* THe LiST */ 51 52 int auto_mode = 0; /* Play without user interaction */ 53 54 level = tricks = moves = score = 0; 55 srand( time(NULL) ); 56 57 /* check the flag for auto-mode */ 58 if( argc == 2 && argv[1][0] == '-' && argv[1][1] == 'a' ) 59 auto_mode = 1; 60 61 initscr(); /* start of session */ 62 curs_set(0); /* hide the cursor */ 63 keypad(stdscr, TRUE); /* enable the function keys */ 64 cbreak(); 65 noecho(); 66 nodelay(stdscr, TRUE); /* unlock getch(3) */ 67 68 /* Check the terminal size */ 69 if( LINES < MINROW || COLS < MINCOL ) { 70 endwin(); 71 printf("This game require a terminal size of %dx%d pixel.\n", 72 MINROW, MINCOL); 73 printf("Your terminal size is: %dx%d\n", LINES, COLS); 74 return -1; 75 } 76 77 /* Create the windows */ 78 wstat = subwin(stdscr, 3, COLS - COLS / 3, 2, COLS / 6); /* status */ 79 wpos = subwin(stdscr, 3, 9, 2, 3); /* worm position */ 80 wepos = subwin(stdscr, 3, 9, 2, COLS - 9 - 3); /* eat position */ 81 82 /* Add the first position */ 83 newnode(&worm, 1 + rand() % (COLS - 2), 6 + rand() % (LINES - 7)); 84 85 /* start point for the eat */ 86 erow = 6 + rand() % (LINES - 7); 87 ecol = 1 + rand() % (COLS - 2); 88 89 /* choose a random route */ 90 route = 1 + rand() % 4; 91 92 wormshow(worm, WORM, HEAD); /* put the first WORM */ 93 mvprintw(erow, ecol, "%d", n = 1); /* put the first eat */ 94 95 /* main loop */ 96 while( (auto_mode ? getch() : (ch = getch())) != 'q' ) { 97 /* 98 * Auto-mode play 99 * 100 * NOTE: the worm can to bite himself losing the 101 * game which will be auto-restarted again. 102 * 103 */ 104 if( auto_mode ) { 105 ch = 0; /* don't touch the user moves */ 106 107 /* choose the route, if needed */ 108 if( worm->x < erow ) { 109 if( route != R_DOWN ) 110 ch = 'j'; 111 } 112 else if( worm->x > erow ) { 113 if( route != R_UP ) 114 ch = 'k'; 115 } 116 else if( worm->y < ecol ) { 117 if( route != R_RIGHT ) 118 ch = 'l'; 119 } 120 else { 121 if( route != R_LEFT ) 122 ch = 'h'; 123 } 124 } 125 126 switch(ch) { 127 case 'h': 128 case KEY_LEFT: 129 route = R_LEFT; 130 ++moves; 131 break; 132 case 'j': 133 case KEY_DOWN: 134 route = R_DOWN; 135 ++moves; 136 break; 137 case 'k': 138 case KEY_UP: 139 route = R_UP; 140 ++moves; 141 break; 142 case 'l': 143 case KEY_RIGHT: 144 route = R_RIGHT; 145 ++moves; 146 break; 147 case 'p': 148 case ' ': /* space bar */ 149 /* Pause */ 150 nodelay(stdscr, FALSE); /* lock the getch() */ 151 152 mvprintw(LINES / 2, COLS / 2, "Pause!"); 153 while( (ch = getch()) ) { 154 if( ch == 'p' || ch == ' ' ) 155 break; 156 157 clear(); 158 mvprintw(LINES / 2, COLS / 2, "Pause!"); 159 } 160 161 nodelay(stdscr, TRUE); /* unlock the getch() */ 162 clear(); 163 164 break; 165 case 't': /* Tricks */ 166 if( trick ) { 167 nodelay(stdscr, TRUE); 168 trick = 0; 169 } 170 else { 171 nodelay(stdscr, FALSE); 172 trick = 1; 173 ++tricks; 174 } 175 break; 176 case KEY_RESIZE: /* Terminal resize */ 177 nodelay(stdscr, FALSE); 178 179 /* Check the terminal size */ 180 while( LINES < MINROW || COLS < MINCOL ) { 181 clear(); 182 mvprintw(LINES / 2, COLS / 2 - 12, "Terminal size too small!"); 183 mvprintw(LINES / 2 + 1, COLS / 2 - 16, 184 "Please, enlarge it and press a key"); 185 getch(); 186 } 187 188 nodelay(stdscr, TRUE); 189 clear(); 190 191 break; 192 } /* eof switch() */ 193 moveworm(&worm, route); /* move the worm */ 194 195 /* 196 * Check if the "new" position is already 197 * busy or it's out of the window. 198 */ 199 if( isout(worm) || isbite(worm) || (auto_mode && level >= LEVMAX) ) { 200 201 if( level >= LEVMAX ) 202 mvprintw(LINES / 2, COLS / 2 - 5, "Too fast!"); 203 else 204 mvprintw(LINES / 2, COLS / 2 - 5, "You lose!"); 205 206 mvprintw(LINES / 2 + 1, COLS / 2 - 10, "Your scores are: %.3d", score); 207 208 if( auto_mode ) { 209 mvprintw(LINES / 2 + 3, COLS / 2 - 6, "Restarting.."); 210 refresh(); 211 212 /* reset the game */ 213 killworm(&worm); 214 newnode(&worm, 1 + rand() % (COLS - 2), 6 + rand() % (LINES - 7)); 215 erow = 6 + rand() % (LINES - 7); 216 ecol = 1 + rand() % (COLS - 2); 217 score = level = eating = tricks = moves = 0; 218 n = 1; 219 220 sleep(2); /* leave that the user tastes the messsage :-) */ 221 continue; 222 } 223 else { 224 mvprintw(LINES / 2 + 2, COLS / 2 - 10, "Press a key to exit.."); 225 226 nodelay(stdscr, FALSE); 227 getch(); /* wait for input */ 228 229 break; 230 } 231 } 232 233 eating = n; /* needed for the wormshow() call */ 234 235 /* If it's eating */ 236 while( iseat(worm, erow, ecol) ) { 237 /* Add a node on the top of worm (new head) */ 238 current = worm; 239 while( current->next != NULL ) 240 current = current->next; 241 newnode(¤t->next, worm->y, worm->x); 242 243 /* Change the eat's position */ 244 erow = 6 + rand() % (LINES - 7); 245 ecol = 1 + rand() % (COLS - 2); 246 ++n; /* Change the eat's number */ 247 248 score += level + 1; /* increment the points */ 249 250 /* Increase the level */ 251 if( !(n % LEVEAT) ) { 252 ++level; 253 } 254 } 255 256 erase(); /* clear the screen */ 257 box(stdscr, ACS_VLINE, ACS_HLINE); /* create the borders */ 258 mvprintw(erow, ecol, "%d", n); /* draw the eat */ 259 260 /* Show the status window (and its elemets) */ 261 mvwprintw(stdscr, 1, COLS / 2 - 37 / 2, /* Copy */ 262 "The Hermit worm - (C) 2006 Claudio M."); /* left */ 263 box(wstat, ACS_VLINE, ACS_HLINE); /* status box */ 264 mvwhline(stdscr, 5, 1, ACS_HLINE, COLS - 2); /* new top limit */ 265 266 /* Show the worm position */ 267 box(wepos, ACS_VLINE, ACS_HLINE); /* status box */ 268 mvwprintw(stdscr, 1, 3, "Worm Curs"); 269 mvwprintw(wepos, 1, 2, "%.2dx%.2d", erow, ecol); 270 271 /* Show the eat position */ 272 box(wpos, ACS_VLINE, ACS_HLINE); /* status box */ 273 mvwprintw(stdscr, 1, COLS - 12, "Eat Curs"); 274 mvwprintw(wpos, 1, 2, "%.2dx%.2d", worm->x, worm->y); 275 276 /* Show the informations */ 277 mvwprintw(wstat, 1, 3, 278 "TS:%.2dx%.2d | " /* Terminal size */ 279 "SL:%.2d | " /* Speed level */ 280 "EM:%.2d | " /* Eats missing */ 281 "UM:%.3d | " /* User moves */ 282 "UT:%.2d | " /* User tricks */ 283 "Ss:%.3d", /* Scores */ 284 LINES, COLS, level + 1, LEVEAT - (n % LEVEAT), 285 moves, tricks, score); 286 287 /* 288 * Show the whole "new" worm 289 * NOTE: this statement is not optimized - trash statement :-) 290 */ 291 if( iseat(worm, erow + 1, ecol) || iseat(worm, erow - 1, ecol) || 292 iseat(worm, erow, ecol + 1) || iseat(worm, erow, ecol - 1) ) { 293 wormshow(worm, WORM, EATH); 294 } 295 else 296 wormshow(worm, WORM, HEAD); 297 298 usleep( 100000 - level * 10000 ); 299 } 300 301 endwin(); /* end of session */ 302 303 puts("\nEnd of game."); 304 305 return 0; 306 } /* E0F main */ 307 308 /* 309 * Returns if the worm is out of the window 310 */ 311 int isout(List *worm) 312 { 313 if( worm->x <= 5 || !worm->y || worm->x >= LINES - 1 || worm->y >= COLS - 1) 314 return 1; 315 316 return 0; 317 } /* eof isout() */ 318 319 /* 320 * Return if the worm is eating 321 */ 322 int iseat(List *worm, int row, int col) 323 { 324 return (worm->x == row && worm->y == col); 325 } /* eof iseat() */ 326 327 /* 328 * Check if the worm is biting itself 329 */ 330 int isbite(List *worm) 331 { 332 List *current = worm; 333 334 while( (current = current->next) != NULL ) { 335 if( worm->x == current->x && worm->y == current->y ) 336 return 1; 337 } 338 339 return 0; 340 } /* eof isbite() */ 341 342 /* 343 * Show the worm (call refresh(3)) 344 */ 345 void wormshow(List *worm, char ch, char chhead) 346 { 347 List *current = worm->next; 348 349 /* show the worm */ 350 while( current != NULL ) { 351 mvprintw(current->x, current->y, "%c", ch); 352 current = current->next; 353 } 354 mvprintw(worm->x, worm->y, "%c", chhead); /* show the head */ 355 356 refresh(); /* print the changes */ 357 358 } /* eof wormshow() */ 359 360 /* 361 * Add a new node to the list 362 */ 363 void newnode(List **listp, int y, int x) 364 { 365 List *newPtr; 366 367 /* Allocate the memory */ 368 if( (newPtr = malloc( sizeof(List) )) == NULL ) { 369 printf("Not enough memory to allocate the \"%d.%d\" element\n", x, y); 370 return; 371 } 372 newPtr->next = *listp; 373 newPtr->x = x; 374 newPtr->y = y; 375 376 *listp = newPtr; 377 378 } /* eof newnode() */ 379 380 /* 381 * Move the whole worm 382 */ 383 void moveworm(List **worm, int way) 384 { 385 List *current = *worm; 386 int n = 1, i; 387 388 /* count the nodes */ 389 while( (current = current->next) != NULL ) 390 ++n; 391 392 current = *worm; /* come back to the start point */ 393 while( --n ) { 394 395 current = *worm; 396 i = n; 397 398 while( --i ) 399 current = current->next; 400 401 /* update the position */ 402 current->next->x = current->x; 403 current->next->y = current->y; 404 } 405 406 /* move the head */ 407 switch(way) { 408 case R_UP: 409 --( *worm )->x; 410 break; 411 case R_LEFT: 412 --( *worm )->y; 413 break; 414 case R_DOWN: 415 ++( *worm )->x; 416 break; 417 case R_RIGHT: 418 ++( *worm )->y; 419 break; 420 } 421 422 } /* eof moveworm() */ 423 424 /* 425 * Kill the worm: free the whole worm's memory 426 */ 427 void killworm(List **worm) 428 { 429 List *current; 430 431 while(*worm != NULL) { 432 current = *worm; 433 *worm = (*worm)->next; 434 free(current); 435 } 436 437 } /* eof killworm() */ 438