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 -------------------------------------------------*/