1 /*
2 * enigma/screen.c - perform screen/keyboard handling. All
3 * interaction with Curses is contained in this module, so an X
4 * front end (for example) could be implemented just by replacing
5 * this one file.
6 *
7 * Copyright 2000 Simon Tatham. All rights reserved.
8 *
9 * Enigma is licensed under the MIT licence. See the file LICENCE for
10 * details.
11 *
12 * - we are all amf -
13 */
14
15 #include <stdio.h>
16 #include <string.h>
17 #ifdef CURSES_HDR
18 # include CURSES_HDR
19 #else
20 # include "curses.h"
21 #endif
22 #include "enigma.h"
23
24 #ifndef HAS_ATTR_T
25 typedef int attr_t;
26 #endif
27
28 /*
29 * Attributes.
30 */
31 static struct {
32 int fg, bg, attrs;
33 } attrs[] = {
34 /*
35 * Attributes to use when displaying a level in play.
36 */
37 #define T_WALL_STRAIGHT 0 /* straight walls: - and | */
38 { COLOR_CYAN, COLOR_BLUE, 0 },
39 #define T_WALL_CORNER 1 /* corner walls: + */
40 { COLOR_WHITE, COLOR_BLUE, 0 },
41 #define T_WALL_AMORPH 2 /* amorphous walls: % */
42 { COLOR_CYAN, COLOR_BLUE, 0 },
43 #define T_WALL_KILLER 3 /* killer walls: & */
44 { COLOR_GREEN, COLOR_BLACK, 0 },
45 #define T_BOMB 4 /* bombs: W X Y Z */
46 { COLOR_RED, COLOR_BLACK, A_BOLD },
47 #define T_ARROW 5 /* arrows: > v < ^ */
48 { COLOR_RED, COLOR_BLACK, 0 },
49 #define T_GOLD 6 /* gold: $ */
50 { COLOR_YELLOW, COLOR_BLACK, A_BOLD },
51 #define T_EARTH 7 /* earth: . : ! = */
52 { COLOR_YELLOW, COLOR_BLACK, 0 },
53 #define T_SACK 8 /* sacks: o 8 */
54 { COLOR_CYAN, COLOR_BLACK, 0 },
55 #define T_PLAYER 9 /* the player: @ */
56 { COLOR_WHITE, COLOR_BLACK, A_BOLD },
57 #define T_TITLE 10 /* title at top of screen */
58 { COLOR_CYAN, COLOR_BLUE, A_BOLD },
59 #define T_SPACE 11 /* empty space: ' ' */
60 { COLOR_WHITE, COLOR_BLACK, 0 },
61 #define T_STATUS_1 12 /* level number and slash at bottom */
62 { COLOR_CYAN, COLOR_BLACK, 0 },
63 #define T_STATUS_2 13 /* level title and gold counters */
64 { COLOR_YELLOW, COLOR_BLACK, 0 },
65 #define T_TELEPORTER 14 /* teleporters: # */
66 { COLOR_GREEN, COLOR_BLACK, A_BOLD },
67
68 /*
69 * Attributes to use when displaying the main game menu.
70 */
71 #define T_ASCII_ART 15 /* the Enigma banner */
72 { COLOR_CYAN, COLOR_BLUE, A_BOLD },
73 #define T_LIST_ELEMENT 16 /* a playable level or saved game */
74 { COLOR_WHITE, COLOR_BLACK, 0 },
75 #define T_LIST_SELECTED 17 /* a selected level or saved game */
76 { COLOR_WHITE, COLOR_BLUE, A_BOLD },
77 #define T_LIST_ADMIN 18 /* "...more..." or "...remain unseen" */
78 { COLOR_RED, COLOR_BLACK, 0 },
79 #define T_LIST_BOX 19 /* outline of list box */
80 { COLOR_CYAN, COLOR_BLUE, 0 },
81 #define T_INSTRUCTIONS 20 /* instructions on what keys to press */
82 { COLOR_YELLOW, COLOR_BLACK, 0 },
83
84 #define T_INPUT 21 /* accepting text input from user */
85 { COLOR_WHITE, COLOR_BLACK, 0 },
86 };
87
screen_init(void)88 void screen_init(void) {
89 int i;
90
91 initscr();
92 noecho();
93 keypad(stdscr, 1);
94 move(0,0);
95 refresh();
96 if (has_colors()) {
97 start_color();
98 for (i = 0; i < (int)lenof(attrs); i++) {
99 init_pair(i+1, attrs[i].fg, attrs[i].bg);
100 attrs[i].attrs |= COLOR_PAIR(i+1);
101 }
102 }
103 }
104
screen_finish(void)105 void screen_finish(void) {
106 endwin();
107 }
108
screen_prints(int x,int y,int attr,char * string)109 void screen_prints(int x, int y, int attr, char *string) {
110 wattrset(stdscr, attrs[attr].attrs);
111 wmove(stdscr, y, x);
112 waddstr(stdscr, string);
113 }
114
screen_printc(int x,int y,int attr,int c)115 void screen_printc(int x, int y, int attr, int c) {
116 wattrset(stdscr, attrs[attr].attrs);
117 wmove(stdscr, y, x);
118 waddch(stdscr, c);
119 }
120
screen_level_init(void)121 void screen_level_init(void) {
122 /*
123 * Hook to make a windowing port of Enigma easier. This is
124 * called when the player begins to play a level, and
125 * screen_level_finish() is called when the level is finished.
126 * The idea is that these routines can be reimplemented to open
127 * and close a window of some sort.
128 */
129
130 /* In this implementation, we do nothing. */
131 }
132
screen_level_finish(void)133 void screen_level_finish(void) {
134 /*
135 * This is called after the last game position (GAME OVER,
136 * COMPLETED, ABANDONED) has been displayed. Its task is to
137 * close the window.
138 */
139
140 /* In this implementation, we do nothing. */
141 }
142
screen_level_display(gamestate * s,char * message)143 void screen_level_display(gamestate *s, char *message) {
144 int sw, sh;
145 int i, j;
146 int dx, dy;
147 char buf[20];
148
149 getmaxyx(stdscr, sh, sw);
150
151 werase(stdscr);
152
153 if (sw < s->width || sh < s->height+2) {
154 char buf[3][40];
155 int i;
156 sprintf(buf[0], "Need terminal at least");
157 sprintf(buf[1], "%dx%d characters to", s->width, s->height+2);
158 sprintf(buf[2], "to display this level!");
159
160 for (i = 0; i < 3; i++)
161 screen_prints((sw - strlen(buf[i]))/2, sh/2 + i - 1, T_BOMB,
162 buf[i]);
163
164 move(0,0);
165 refresh();
166 return;
167 }
168
169 dx = (sw - s->width) / 2;
170 dy = (sh - s->height) / 2;
171
172 for (j = 0; j < s->height; j++)
173 for (i = 0; i < s->width; i++) {
174 char c = s->leveldata[j*s->width+i];
175 attr_t attr = 0;
176 switch (c) {
177 case '-': case '|': attr = T_WALL_STRAIGHT; break;
178 case '+': attr = T_WALL_CORNER; break;
179 case '%': attr = T_WALL_AMORPH; break;
180 case '&': attr = T_WALL_KILLER; break;
181 case 'W': case 'X':
182 case 'Y': case 'Z':
183 case 'w': case 'x':
184 case 'y': case 'z': attr = T_BOMB; break;
185 case '>': case 'v':
186 case '<': case '^': attr = T_ARROW; break;
187 case '$': attr = T_GOLD; break;
188 case '.': case ':':
189 case '!': case '=': attr = T_EARTH; break;
190 case 'o': case '8': attr = T_SACK; break;
191 case '@': case 'O': attr = T_PLAYER; break;
192 case '#': attr = T_TELEPORTER; break;
193 case '~': c = ' '; attr = T_SPACE; break;
194 default: attr = T_SPACE; break;
195 }
196 screen_printc(i+dx, j+dy, attr, c);
197 }
198
199 /*
200 * Display title.
201 */
202 i = 8;
203 if (message) i += 3 + strlen(message);
204 screen_prints((sw-i)/2, dy-1, T_TITLE, " Enigma ");
205 if (message) {
206 screen_prints((sw-i)/2+8, dy-1, T_TITLE, "- ");
207 screen_prints((sw-i)/2+10, dy-1, T_TITLE, message);
208 screen_prints((sw-i)/2+i-1, dy-1, T_TITLE, " ");
209 }
210
211 /*
212 * Display status line.
213 */
214 sprintf(buf, "%d) ", s->levnum);
215 screen_prints(dx, dy+s->height, T_STATUS_1, buf);
216 screen_prints(dx+strlen(buf), dy+s->height, T_STATUS_2, s->title);
217 sprintf(buf, "%2d", s->gold_got);
218 screen_prints(dx+s->width-5, dy+s->height, T_STATUS_2, buf);
219 screen_printc(dx+s->width-3, dy+s->height, T_STATUS_1, '/');
220 sprintf(buf, "%2d", s->gold_total);
221 screen_prints(dx+s->width-2, dy+s->height, T_STATUS_2, buf);
222
223 move(0,0);
224 refresh();
225 }
226
227 /*
228 * Get a move. Can return 'h','j','k','l','x', or 'q', or '0'-'9'
229 * for saves, or 's', 'm' (enter movie mode) and 'w' (save
230 * sequence).
231 */
screen_level_getmove(int playing)232 int screen_level_getmove(int playing) {
233 int i;
234 do {
235 i = getch();
236 if (playing) {
237 if (i == KEY_UP) i = 'k';
238 if (i == KEY_DOWN) i = 'j';
239 if (i == KEY_LEFT) i = 'h';
240 if (i == KEY_RIGHT) i = 'l';
241 if (i >= 'A' && i <= 'Z')
242 i += 'a' - 'A';
243 } else if (i != 'w' && i != 'r') {
244 /* When the playing is over, most keys just quit. */
245 i = 'q';
246 }
247 } while (i != 'h' && i != 'j' && i != 'k' && i != 'l' && i != 'x' &&
248 i != 'q' && i != 's' && i != 'r' && (i < '0' || i > '9') &&
249 i != 'm' && i != 'w');
250 return i;
251 }
252
253 /*
254 * Get a keypress in movie mode. Can return 'f' or 'b', '+' or '-',
255 * '>' or '<', or 'q'.
256 */
screen_movie_getmove(void)257 int screen_movie_getmove(void) {
258 int i;
259 do {
260 i = getch();
261 if (i >= 'A' && i <= 'Z')
262 i += 'a' - 'A';
263 if (i == ' ') i = 'f';
264 if (i == KEY_LEFT) i = '-';
265 if (i == KEY_RIGHT) i = '+';
266 if (i == '\033') i = 'q';
267 } while (i != 'f' && i != 'b' && i != '+' && i != '-' &&
268 i != '>' && i != '<' && i != 'q');
269 return i;
270 }
271
272 /*
273 * Format a save slot into a string.
274 */
saveslot_fmt(char * buf,int slotnum,gamestate * gs)275 void saveslot_fmt(char *buf, int slotnum, gamestate *gs) {
276 if (gs) {
277 sprintf(buf, "%d) Level:%3d Moves:%4d ", (slotnum+1)%10,
278 gs->levnum, gs->movenum);
279 } else {
280 sprintf(buf, "%d) [empty save slot] ", (slotnum+1)%10);
281 }
282 }
283
284 /*
285 * Display the levels-or-saves main menu. Returns a level number (1
286 * or greater), a save number (0 to -9), a save number to delete
287 * (-10 to -19), or a `quit' signal (-100).
288 */
screen_main_menu(levelset * set,gamestate ** saves,int maxlev,int startlev)289 int screen_main_menu(levelset *set, gamestate **saves,
290 int maxlev, int startlev) {
291 const int colwidth = 26, colgap = 8;
292 const int height = 21, llines = height-6;
293 int sx, sy, dx, dy, dx2;
294 int save = 0;
295 int level;
296 int levtop = 0;
297 int i, k;
298 int unseen;
299
300 getmaxyx(stdscr, sy, sx);
301
302 werase(stdscr);
303
304 if (maxlev > set->nlevels) {
305 /*
306 * User has completed game. Different defaults.
307 */
308 maxlev = set->nlevels;
309 startlev = 0;
310 }
311 level = startlev;
312 if (level == set->nlevels-1)
313 levtop = level - (llines-1);
314 else
315 levtop = level - (llines-2);
316 if (levtop < 0)
317 levtop = 0;
318
319 dx = (sx - 2*colwidth - colgap) / 2;
320 dy = (sy - height) / 2;
321 dx2 = dx + colwidth + colgap;
322
323 while (1) {
324 /*
325 * Display the ASCII art banner in the top right.
326 */
327 screen_prints(dx2, dy+0, T_ASCII_ART, " ");
328 screen_prints(dx2, dy+1, T_ASCII_ART, " ._ ");
329 screen_prints(dx2, dy+2, T_ASCII_ART, " |_ ._ o _ ._ _ _. ");
330 screen_prints(dx2, dy+3, T_ASCII_ART, " |_ | ||(_|| | |(_| ");
331 screen_prints(dx2, dy+4, T_ASCII_ART, " _| ");
332 screen_prints(dx2, dy+5, T_ASCII_ART, " ");
333
334 /*
335 * Display the saved position list.
336 */
337 for (i = 0; i < 10; i++) {
338 char buf[40];
339 saveslot_fmt(buf, i, saves[i]);
340 screen_prints(dx2, dy+7+i,
341 i == save ? T_LIST_SELECTED : T_LIST_ELEMENT, buf);
342 }
343
344 /*
345 * Display the level title list box.
346 */
347 wattrset(stdscr, attrs[T_LIST_BOX].attrs);
348 for (i = 1; i < colwidth+1; i++) {
349 screen_printc(dx+i, dy, T_LIST_BOX, '-');
350 screen_printc(dx+i, dy+height-5, T_LIST_BOX, '-');
351 }
352 screen_printc(dx, dy, T_LIST_BOX, '+');
353 screen_printc(dx, dy+height-5, T_LIST_BOX, '+');
354 screen_printc(dx+colwidth+1, dy, T_LIST_BOX, '+');
355 screen_printc(dx+colwidth+1, dy+height-5, T_LIST_BOX, '+');
356 for (i = 1; i < height-5; i++) {
357 char buf[50];
358 int attr = T_LIST_ELEMENT;
359 unseen = FALSE;
360 if (i == 1 && levtop > 0) {
361 sprintf(buf, "(more)");
362 attr = T_LIST_ADMIN;
363 } else if (i+levtop-1 == maxlev && maxlev < set->nlevels) {
364 sprintf(buf, "(%d remain%s unseen)",
365 set->nlevels - maxlev,
366 set->nlevels - maxlev == 1 ? "s" : "");
367 unseen = TRUE;
368 attr = T_LIST_ADMIN;
369 } else if (i == llines && i+levtop-1 < maxlev-1 && !unseen) {
370 sprintf(buf, "(more)");
371 attr = T_LIST_ADMIN;
372 } else {
373 if (i+levtop-1 < maxlev)
374 sprintf(buf, "%2d) %.40s", i+levtop,
375 set->levels[i+levtop-1]->title);
376 else
377 *buf = '\0';
378 if (i+levtop-1 == level)
379 attr = T_LIST_SELECTED;
380 }
381 if ((int)strlen(buf) < colwidth)
382 sprintf(buf+strlen(buf), "%*s", colwidth-strlen(buf), "");
383 buf[colwidth] = '\0';
384 screen_prints(dx+1, dy+i, attr, buf);
385 screen_printc(dx, dy+i, T_LIST_BOX, '|');
386 screen_printc(dx+colwidth+1, dy+i, T_LIST_BOX, '|');
387 }
388 screen_prints(dx, dy+height-3, T_INSTRUCTIONS, "Press Up/Down to choose a");
389 screen_prints(dx, dy+height-2, T_INSTRUCTIONS, "level, and RET to play from");
390 screen_prints(dx, dy+height-1, T_INSTRUCTIONS, "the start of that level.");
391 screen_prints(dx, dy+height, T_INSTRUCTIONS, "Press Q to exit Enigma.");
392 screen_prints(dx2+colwidth-26, dy+height-3, T_INSTRUCTIONS, "Press 1-9 or 0 to choose a");
393 screen_prints(dx2+colwidth-24, dy+height-2, T_INSTRUCTIONS, "saved position, and R to");
394 screen_prints(dx2+colwidth-26, dy+height-1, T_INSTRUCTIONS, "resume playing from there.");
395 screen_prints(dx2+colwidth-27, dy+height, T_INSTRUCTIONS, "Press Del to delete a save.");
396
397 move(0,0);
398 refresh();
399 k = getch();
400 if (k >= 'A' && k <= 'Z') k += 'a'-'A';
401 if (k >= '0' && k <= '9')
402 save = (k - '1' + 10) % 10;
403 if ((k == 'k' || k == KEY_UP) && level > 0) {
404 level--;
405 if (!(level > levtop || (level == 0 && levtop == 0)))
406 levtop = (level == 0 ? level : level-1);
407 }
408 if ((k == 'j' || k == KEY_DOWN) && level < maxlev-1) {
409 level++;
410 if (!(level < levtop+llines-1 || (level == levtop+llines-1 &&
411 level == set->nlevels-1))) {
412 if (level == set->nlevels-1)
413 levtop = level - (llines-1);
414 else
415 levtop = level - (llines-2);
416 }
417 }
418 if (k == 'q')
419 return -100;
420 if (k == '\r' || k == '\n')
421 return level+1;
422 if (k == 'r')
423 return -save;
424 if (k == '\010' || k == '\177' || k == KEY_BACKSPACE)
425 return -save-10; /* delete a save */
426 }
427 }
428
screen_saveslot_ask(char action,gamestate ** saves,int defslot)429 int screen_saveslot_ask(char action, gamestate **saves, int defslot) {
430 const int width = 28;
431 const int height = 14;
432 int sx, sy, dx, dy;
433 int i, k;
434 char buf[50];
435
436 getmaxyx(stdscr, sy, sx);
437 dx = (sx - width) / 2;
438 dy = (sy - height) / 2;
439
440 while (1) {
441 for (i = 1; i < width-1; i++) {
442 screen_printc(dx+i, dy, T_LIST_BOX, '-');
443 screen_printc(dx+i, dy+height-1, T_LIST_BOX, '-');
444 }
445 for (i = 1; i < height-1; i++) {
446 screen_printc(dx, dy+i, T_LIST_BOX, '|');
447 screen_printc(dx+width-1, dy+i, T_LIST_BOX, '|');
448 }
449 screen_printc(dx, dy, T_LIST_BOX, '+');
450 screen_printc(dx, dy+height-1, T_LIST_BOX, '+');
451 screen_printc(dx+width-1, dy, T_LIST_BOX, '+');
452 screen_printc(dx+width-1, dy+height-1, T_LIST_BOX, '+');
453 screen_prints(dx+1, dy+1, T_INSTRUCTIONS,
454 "Press 0-9 to pick a slot ");
455 screen_prints(dx+1, dy+height-2, T_INSTRUCTIONS,
456 (action == 's' ?
457 " Y to save over slot X " :
458 "Y to restore from slot X "));
459 screen_printc(dx+24, dy+height-2, T_INSTRUCTIONS, '0'+(defslot+1)%10);
460 for (i = 0; i < 10; i++) {
461 saveslot_fmt(buf, i, saves[i]);
462 screen_prints(dx+1, dy+i+2,
463 i == defslot ? T_LIST_SELECTED : T_LIST_ELEMENT,
464 buf);
465 }
466 move(0,0);
467 refresh();
468 k = getch();
469 if (k >= 'A' && k <= 'Q') {
470 k += 'a'-'A';
471 }
472 if (k >= '0' && k <= '9') {
473 defslot = (k+9-'0') % 10;
474 }
475 if (k == 'y' && (action == 's' || saves[defslot]))
476 return defslot;
477 if (k == 'n' || k == 'q')
478 return -1;
479 }
480 }
481
screen_ask_movefile(int saving)482 char *screen_ask_movefile(int saving) {
483 const int width = 40;
484 const int height = 4;
485 int sx, sy, dx, dy;
486 int i, k;
487 char buf[50];
488 int len;
489 char *p;
490
491 getmaxyx(stdscr, sy, sx);
492 dx = (sx - width) / 2;
493 dy = (sy - height) / 2;
494
495 len = 0;
496
497 while (1) {
498 for (i = 1; i < width-1; i++) {
499 screen_printc(dx+i, dy, T_LIST_BOX, '-');
500 screen_printc(dx+i, dy+height-1, T_LIST_BOX, '-');
501 screen_printc(dx+i, dy+1, T_INSTRUCTIONS, ' ');
502 screen_printc(dx+i, dy+2, T_INPUT, ' ');
503 }
504 for (i = 1; i < height-1; i++) {
505 screen_printc(dx, dy+i, T_LIST_BOX, '|');
506 screen_printc(dx+width-1, dy+i, T_LIST_BOX, '|');
507 }
508 screen_printc(dx, dy, T_LIST_BOX, '+');
509 screen_printc(dx, dy+height-1, T_LIST_BOX, '+');
510 screen_printc(dx+width-1, dy, T_LIST_BOX, '+');
511 screen_printc(dx+width-1, dy+height-1, T_LIST_BOX, '+');
512 if (saving) {
513 screen_prints(dx+1, dy+1, T_INSTRUCTIONS,
514 "Enter a move sequence file to save:");
515 } else {
516 screen_prints(dx+1, dy+1, T_INSTRUCTIONS,
517 "Enter a move sequence file to load:");
518 }
519 buf[len] = '\0';
520 screen_prints(dx+1, dy+2, T_INPUT, buf);
521 move(dy+2, dx+1+len);
522 refresh();
523 k = getch();
524 if (k == '\r' || k == '\n')
525 break;
526 else if (k == '\e')
527 return NULL; /* input abandoned */
528 else if (k == '\010' || k == '\177' || k == KEY_BACKSPACE)
529 len = (len>0 ? len-1 : 0);
530 else if (k == '\025')
531 len = 0;
532 else if (k >= '\040' && k <= '\176' && len < width-2)
533 buf[len++] = (char)k;
534 }
535 if (!len)
536 return NULL;
537 buf[len] = '\0';
538 p = smalloc(len+1);
539 strcpy(p, buf);
540 return p;
541 }
542
screen_error_box(char * msg)543 void screen_error_box(char *msg) {
544 int width = 2 + strlen(msg);
545 const int height = 3;
546 int sx, sy, dx, dy;
547 int i;
548 int len;
549
550 getmaxyx(stdscr, sy, sx);
551 dx = (sx - width) / 2;
552 dy = (sy - height) / 2;
553
554 len = 0;
555
556 for (i = 1; i < width-1; i++) {
557 screen_printc(dx+i, dy, T_LIST_BOX, '-');
558 screen_printc(dx+i, dy+height-1, T_LIST_BOX, '-');
559 }
560 for (i = 1; i < height-1; i++) {
561 screen_printc(dx, dy+i, T_LIST_BOX, '|');
562 screen_printc(dx+width-1, dy+i, T_LIST_BOX, '|');
563 }
564 screen_printc(dx, dy, T_LIST_BOX, '+');
565 screen_printc(dx, dy+height-1, T_LIST_BOX, '+');
566 screen_printc(dx+width-1, dy, T_LIST_BOX, '+');
567 screen_printc(dx+width-1, dy+height-1, T_LIST_BOX, '+');
568 screen_prints(dx+1, dy+1, T_INSTRUCTIONS, msg);
569 move(0,0);
570 refresh();
571 getch();
572 }
573
screen_completed_game(void)574 void screen_completed_game(void) {
575 int sx, sy;
576
577 getmaxyx(stdscr, sy, sx);
578
579 werase(stdscr);
580 screen_prints((sx-51)/2, sy/2-2, T_INSTRUCTIONS,
581 "Congratulations! You have completed this level set.");
582 screen_prints((sx-49)/2, sy/2, T_INSTRUCTIONS,
583 "Now why not become an Enigma developer, and write");
584 screen_prints((sx-48)/2, sy/2+1, T_INSTRUCTIONS,
585 "some levels of your own, or perhaps even help me");
586 screen_prints((sx-32)/2, sy/2+2, T_INSTRUCTIONS,
587 "write a better finishing screen?");
588 screen_prints((sx-15)/2, sy-1, T_INSTRUCTIONS,
589 "(Press any key)");
590 move(0,0);
591 refresh();
592 getch();
593 }
594