chiark / gitweb /
infra: Clean up project setup
[xor] / ui-curses.c
1 /* -*-c-*-
2  *
3  * $Id$
4  *
5  * Curses user interface
6  *
7  * (c) 2003 Straylight/Edgeware
8  */
9
10 /*----- Licensing notice --------------------------------------------------* 
11  *
12  * This file is part of XOR.
13  *
14  * XOR is free software; you can redistribute it and/or modify
15  * it under the terms of the GNU General Public License as published by
16  * the Free Software Foundation; either version 2 of the License, or
17  * (at your option) any later version.
18  * 
19  * XOR is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU General Public License for more details.
23  * 
24  * You should have received a copy of the GNU General Public License
25  * along with XOR; if not, write to the Free Software Foundation,
26  * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
27  */
28
29 /*----- Header files ------------------------------------------------------*/
30
31 #include "xor.h"
32
33 #if defined(HAVE_NCURSES_H)
34 #  include <ncurses.h>
35 #elif defined(HAVE_NCURSES_NCURSES_H)
36 #  include <ncurses/ncurses.h>
37 #else
38 #  include <curses.h>
39 #endif
40
41 #ifdef __CYGWIN__
42 #  include <windows.h>
43 #  undef usleep
44 #  define usleep(n) Sleep(n / 1000);
45 #endif
46
47 /*----- Data structures ---------------------------------------------------*/
48
49 struct ui_state {
50   int vx, vy;
51   level *l;
52 };
53
54 /*---- Display setup ------------------------------------------------------*/
55
56 #define VIEWX 5
57 #define VIEWY 3
58 #define VIEWWD 8
59 #define VIEWHT 8
60 #define CELLHT 2
61 #define CELLWD 3
62
63 #define STATUSX 40
64 #define STATUSY 10
65 #define STATUSWD 36
66 #define STATUSHT 2
67
68 #define MESSAGEY 16
69
70 static chtype *dispmap[CHAR_MAX], mdispmap[CHAR_MAX];
71
72 static struct { int c; chtype m; chtype d[CELLHT * CELLWD]; } disptab[] = {
73   { C_EMPTY,    ' ',    { ' ', ' ', ' ',   ' ', ' ', ' ' } },
74   { C_PLAYER,   '$',    { '|', '~', '|',   '\\', '_', '/' } },
75   { C_SPARE,    '%',    { '|', 'X', '|',   '\\', '_', '/' } },
76   { C_EXIT,     '=',    { '|', '=', '|',   '|', '_', '|' } },
77   { C_WALL,     '#',    { '#', '#', '#',   '#', '#', '#' } },
78   { C_MASK,     '+',    { 'o', ' ', 'o',   ' ', '>', ' ' } },
79   { C_NWMAP,    'M',    { '|', '~', '|',   '|', '_', '|' } },
80   { C_NEMAP,    'M',    { '|', '~', '|',   '|', '_', '|' } },
81   { C_SWMAP,    'M',    { '|', '~', '|',   '|', '_', '|' } },
82   { C_SEMAP,    'M',    { '|', '~', '|',   '|', '_', '|' } },
83   { C_DOT,      ':',    { ':', ':', ':',   ':', ':', ':' } },
84   { C_WAVE,     '~',    { '~', '~', '~',   '~', '~', '~' } },
85   { C_CHICKEN,  '<',    { '<', 'o', ')',   ' ', '(', ')' } },
86   { C_FISH,     'V',    { ' ', 'V', ' ',   ' ', 'V', ' ' } },
87   { C_HBOMB,    'U',    { '_', 'T', '_',   '|', '_', '|' } },
88   { C_VBOMB,    'C',    { ' ', ',', '*',   '(', ')', ' ' } },
89   { C_DOLLY,    '@',    { '\\', '@', '/',  '/', '=', '\\' } },
90   { C_SWITCH,   '~',    { 'o', ' ', 'o',   ' ', '*', ' ' } },
91   { C_BMUS,     'O',    { '/', '~', '\\',  '\\', '_', '/' } },
92   { 0,          0,      { 0 } }
93 };
94
95 /*----- Main code ---------------------------------------------------------*/
96
97 static void rect(int x, int y, int w, int h)
98 {
99   int i;
100
101   mvaddch(y - 1, x - 1, ACS_ULCORNER);
102   for (i = 0; i < w; i++) addch(ACS_HLINE);
103   addch(ACS_URCORNER);
104   for (i = 0; i < h; i++) {
105     mvaddch(y + i, x - 1, ACS_VLINE);
106     mvaddch(y + i, x + w, ACS_VLINE);
107   }
108   mvaddch(y + h, x - 1, ACS_LLCORNER);
109   for (i = 0; i < w; i++) addch(ACS_HLINE);
110   addch(ACS_LRCORNER);
111 }
112
113 static void hbar(int x, int y, int w)
114 {
115   int i;
116
117   move(y, x - 1);
118   addch(ACS_LTEE);
119   for (i = 0; i < w; i++) addch(ACS_HLINE);
120   addch(ACS_RTEE);
121 }
122
123 static void vbar(int x, int y, int h)
124 {
125   int i;
126
127   mvaddch(y - 1, x, ACS_TTEE);
128   for (i = 0; i < h; i++) mvaddch(y + i, x, ACS_VLINE);
129   mvaddch(y + h, x, ACS_BTEE);
130 }
131
132 static void draw(void)
133 {
134   erase();
135   rect(VIEWX, VIEWY, VIEWWD * CELLWD, VIEWHT * CELLHT);
136   rect(STATUSX, STATUSY - 2, STATUSWD, STATUSHT + 2);
137   hbar(STATUSX, STATUSY - 1, STATUSWD);
138   vbar(STATUSX + STATUSWD - 5, STATUSY, STATUSHT);
139 }
140
141 void ui_init(void)
142 {
143   int i;
144
145   /* --- Kick curses --- */
146
147   initscr();
148   if (LINES < 25 || COLS < 80) {
149     endwin();
150     fprintf(stderr, "terminal too small\n");
151     exit(1);
152   }
153   cbreak();
154   noecho();
155   nonl();
156   intrflush(stdscr, FALSE);
157   keypad(stdscr, TRUE);
158   draw();
159
160   /* --- Initialize the display map --- */
161
162   for (i = 0; disptab[i].c; i++) {
163     dispmap[disptab[i].c] = disptab[i].d;
164     mdispmap[disptab[i].c] = disptab[i].m;
165   }
166   for (i = 0; i < CELLWD * CELLHT; i++)
167     dispmap[C_WALL][i] = ' ' | A_REVERSE;
168   mdispmap[C_WALL] = ' ' | A_REVERSE;
169 }
170
171 static void redraw(ui_state *u)
172 {
173   const level *l = u->l;
174   int x, y, i, j, c;
175   
176   for (y = 0; y < VIEWHT; y++) {
177     for (j = 0; j < CELLHT; j++) {
178       move(VIEWY + y * CELLHT + j, VIEWX);
179       for (x = 0; x < VIEWWD; x++) {
180         c = CELL(l, u->vx + x, u->vy + y) & CF_CELLMASK;
181         if ((l->f & LF_DARK) && (cellmap[c]->f & CF_HIDE)) c = C_EMPTY;
182         for (i = 0; i < CELLWD; i++) {
183           addch(dispmap[c][i + j * CELLWD]);
184         }
185       }
186     }
187   }
188
189   mvaddstr(STATUSY - 2, STATUSX + (STATUSWD - strlen(u->l->name))/2,
190            u->l->name);
191
192   mvprintw(STATUSY, STATUSX, "Masks: %2d/%2d", l->m, l->mtot);
193   mvprintw(STATUSY + 1, STATUSX, "Moves: %4d/%4d", l->v, l->vtot);
194   move(STATUSY, STATUSX + STATUSWD - 4);
195   if (l->f & LF_NWMAP) { addch(' ' | A_REVERSE); addch(' ' | A_REVERSE); }
196   else { addch(' '); addch(' '); }
197   if (l->f & LF_NEMAP) { addch(' ' | A_REVERSE); addch(' ' | A_REVERSE); }
198   else { addch(' '); addch(' '); }
199   move(STATUSY + 1, STATUSX + STATUSWD - 4);
200   if (l->f & LF_SWMAP) { addch(' ' | A_REVERSE); addch(' ' | A_REVERSE); }
201   else { addch(' '); addch(' '); }
202   if (l->f & LF_SEMAP) { addch(' ' | A_REVERSE); addch(' ' | A_REVERSE); }
203   else { addch(' '); addch(' '); }
204 }
205
206 void ui_update(ui_state *u)
207 {
208   redraw(u);
209   refresh();
210 }
211
212 void ui_explode(struct ui_state *u, const point *pt, int n)
213 {
214   int i, j, x, y;
215   static const chtype frame[][CELLWD * CELLHT] = {
216     { '\\', '|', '/',  '/', '|', '\\' },
217     { '*', '*', '*',   '*', '*', '*' },
218     { ':', ':', ':',   ':', ':', ':' },
219     { '.', '.', '.',   ':', ':', ':' },
220     { ' ', ' ', ' ',   ':', ':', ':' },
221     { ' ', ' ', ' ',   '.', '.', '.' }
222   };
223
224   redraw(u);
225   refresh();
226   for (i = 0; i < sizeof(frame)/sizeof(frame[0]); i++) {
227     for (j = 0; j < n; j++) {
228       if (pt[j].x < u->vx || pt[j].x >= u->vx + VIEWWD ||
229           pt[j].y < u->vy || pt[j].y >= u->vy + VIEWHT)
230         continue;
231       for (y = 0; y < CELLHT; y++) {
232         move(VIEWY + (pt[j].y - u->vy) * CELLHT + y,
233              VIEWX + (pt[j].x - u->vx) * CELLWD);
234         for (x = 0; x < CELLWD; x++)
235           addch(frame[i][x + CELLWD * y]);
236       }
237     }
238     refresh();
239     usleep(10000);
240   }
241 }
242
243 void ui_track(ui_state *u, int x, int y)
244 {
245   if (u->vx == -1) {
246     u->vx = x - VIEWWD/2;
247     u->vy = y - VIEWHT/2;
248     if (u->vx < 0) u->vx = 0;
249     else if (u->vx > u->l->w - VIEWWD) u->vx = u->l->w - VIEWWD;
250     if (u->vy < 0) u->vy = 0;
251     else if (u->vy > u->l->h - VIEWHT) u->vy = u->l->h - VIEWHT;
252   } else {
253     if (u->vx + 1 > x) u->vx = x - 1;
254     else if (u->vx + VIEWWD - 2 < x) u->vx = x - VIEWWD + 2;
255     if (u->vy + 1 > y) u->vy = y - 1;
256     else if (u->vy + VIEWHT - 2 < y) u->vy = y - VIEWHT + 2;
257   }
258 }
259
260 ui_state *ui_start(level *l, int x, int y)
261 {
262   ui_state *u = CREATE(ui_state);
263
264   u->l = l;
265   u->vx = u->vy = -1;
266   ui_track(u, x, y);
267   return (u);
268 }
269
270 void ui_switch(ui_state *u)
271 {
272   redraw(u);
273 }
274
275 void ui_frame(struct ui_state *u)
276 {
277   redraw(u);
278   refresh();
279   usleep(20000);
280 }
281
282 void ui_message(struct ui_state *u, const char *p)
283 {
284   size_t n = strlen(p);
285   rect((COLS - n)/2, MESSAGEY, n, 1);
286   mvaddstr(MESSAGEY, (COLS - n)/2, p);
287   refresh();
288   usleep(500000);
289   draw();
290   redraw(u);
291 }
292
293 static void showmap(ui_state *u)
294 {
295   const level *l = u->l;
296   int wd, ht, x, y;
297   int vs = 0, hs = 0, maxv, maxh;
298   int i, j;
299   int bit;
300   int c;
301
302   erase();
303   wd = l->w; if (wd + 6 > COLS) wd = COLS - 6;
304   ht = l->h; if (ht + 6 > LINES) ht = LINES - 6;
305   x = (COLS - wd)/2; y = (LINES - ht)/2;
306   rect(x, y, wd, ht);
307   maxh = l->w - wd; maxv = l->h - ht;
308
309   for (;;) {
310     for (j = 0; j < ht; j++) {
311       move(y + j, x);
312       for (i = 0; i < wd; i++) {
313         bit = LF_NWMAP;
314         if (hs + i >= l->w/2) bit <<= 1;
315         if (vs + j >= l->h/2) bit <<= 2;
316         if (!(l->f & bit))
317           c = C_EMPTY;
318         else {
319           c = CELL(l, hs + i, vs + j) & CF_CELLMASK;
320           if (!(cellmap[c]->f & CF_MAP)) c = C_EMPTY;
321         }
322         addch(mdispmap[c]);
323       }
324     }
325     switch (getch()) {
326       case 'q': case 'Q': case 'm': case 'M': case 0x1b: goto done;
327       case KEY_UP:    case 'k': case 'K': vs--; break;
328       case KEY_DOWN:  case 'j': case 'J': vs++; break;
329       case KEY_LEFT:  case 'h': case 'H': hs--; break;
330       case KEY_RIGHT: case 'l': case 'L': hs++; break;
331     }
332     if (hs < 0) hs = 0; else if (hs > maxh) hs = maxh;
333     if (vs < 0) vs = 0; else if (vs > maxv) vs = maxv;
334   }
335 done:
336   draw();
337   redraw(u);
338   return;
339 }
340
341 void ui_go(struct game_state *g, ui_state *u)
342 {
343   refresh();
344   switch (getch()) {
345     case KEY_UP:    case 'k': case 'K': game_move(g,  0, -1); break;
346     case KEY_DOWN:  case 'j': case 'J': game_move(g,  0, +1); break;
347     case KEY_LEFT:  case 'h': case 'H': game_move(g, -1,  0); break;
348     case KEY_RIGHT: case 'l': case 'L': game_move(g, +1,  0); break;
349     case 'u': case 'U': undo(g); break;
350     case 'r': case 'R': redo(g); break;
351     case ' ': case '\r': game_switch(g); break;
352     case 'm': case 'M': showmap(u); break;
353     case 0x1b: case 'q': case 'Q': game_quit(g); break;
354   }
355 }
356
357 void ui_destroy(ui_state *u)
358 {
359   DESTROY(u);
360 }
361
362 void ui_exit(void)
363 {
364   endwin();
365 }
366
367 /*----- That's all, folks -------------------------------------------------*/