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