unofficial version 0.7.1: ui improvements
[netris.git] / curses.c
1 /*
2  * Netris -- A free networked version of T*tris
3  * Copyright (C) 1994-1996,1999  Mark H. Weaver <mhw@netris.org>
4  * 
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  * 
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  * 
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18  *
19  * $Id: curses.c,v 1.33 1999/05/16 06:56:25 mhw Exp $
20  */
21
22 #include "netris.h"
23 #include <sys/types.h>
24 #include <unistd.h>
25 #include <curses.h>
26 #include <string.h>
27 #include <stdlib.h>
28
29 #ifdef NCURSES_VERSION
30 # define HAVE_NCURSES
31 #endif
32
33 ExtFunc void PlotBlock1(int scr, int y, int x, BlockType type);
34
35 static MyEventType KeyGenFunc(EventGenRec *gen, MyEvent *event);
36 static EventGenRec keyGen =
37                 { NULL, 0, FT_read, STDIN_FILENO, KeyGenFunc, EM_key };
38
39 static int boardYPos[MAX_SCREENS], boardXPos[MAX_SCREENS];
40 static int statusYPos, statusXPos;
41 static int messageYPos, messageXPos, messageHeight, messageWidth;
42 static int haveColor;
43
44 static char *term_vi;   /* String to make cursor invisible */
45 static char *term_ve;   /* String to make cursor visible */
46
47 ExtFunc void InitScreens(void)
48 {
49         MySigSet oldMask;
50
51         GetTermcapInfo();
52
53         /*
54          * Block signals while initializing curses.  Otherwise a badly timed
55          * Ctrl-C during initialization might leave the terminal in a bad state.
56          */
57         BlockSignals(&oldMask, SIGINT, 0);
58         initscr();              //start curses
59
60 #ifdef CURSES_HACK
61         {
62                 extern char *CS;
63
64                 CS = 0;
65         }
66 #endif
67
68 #ifdef HAVE_NCURSES
69         haveColor = Game.color && has_colors();
70         if (haveColor) {
71                 static struct {
72                         BlockType type;
73                         short color;
74                 } myColorTable[] = {
75                         { BT_white,             COLOR_WHITE },
76                         { BT_blue,              COLOR_BLUE },
77                         { BT_magenta,   COLOR_MAGENTA },
78                         { BT_cyan,              COLOR_CYAN },
79                         { BT_yellow,    COLOR_YELLOW },
80                         { BT_green,             COLOR_GREEN },
81                         { BT_red,               COLOR_RED },
82                         { BT_none,              0 }
83                 }; //myColorTable
84                 int i = 0;
85
86                 start_color();
87                 if (can_change_color()) {
88                         init_color (COLOR_YELLOW, 1000, 1000, 0);
89                 } //I've never worked on a color-changable terminal, so no idea..
90                 for (i = 0; myColorTable[i].type != BT_none; ++i)
91                         init_pair(myColorTable[i].type, COLOR_BLACK,
92                                         myColorTable[i].color);
93         } //haveColor
94 #else
95         haveColor = 0;
96 #endif
97
98         AtExit(CleanupScreens);         //restore everything when done
99         RestoreSignals(NULL, &oldMask);
100
101         cbreak();                                       //no line buffering
102         noecho();
103 //      keypad(stdscr, TRUE);           //get arrow/functionkeys 'n stuff
104         OutputTermStr(term_vi, 0);
105         AddEventGen(&keyGen);           //key handler
106         standend();                                     //normal text
107 } //InitScreens
108
109 ExtFunc void CleanupScreens(void)
110 {
111         RemoveEventGen(&keyGen);
112         endwin();                                       //end curses
113         OutputTermStr(term_ve, 1);
114 } //CleanupScreens
115
116 ExtFunc void GetTermcapInfo(void)
117 {
118         char *term, *buf, *data;
119         int bufSize = 10240;
120
121         if (!(term = getenv("TERM")))
122                 return;
123         if (tgetent(scratch, term) == 1) {
124                 /*
125                  * Make the buffer HUGE, since tgetstr is unsafe.
126                  * Allocate it on the heap too.
127                  */
128                 data = buf = malloc(bufSize);
129
130                 /*
131                  * There is no standard include file for tgetstr, no prototype
132                  * definitions.  I like casting better than using my own prototypes
133                  * because if I guess the prototype, I might be wrong, especially
134                  * with regards to "const".
135                  */
136                 term_vi = (char *)tgetstr("vi", &data);
137                 term_ve = (char *)tgetstr("ve", &data);
138
139                 /* Okay, so I'm paranoid; I just don't like unsafe routines */
140                 if (data > buf + bufSize)
141                         fatal("tgetstr overflow, you must have a very sick termcap");
142
143                 /* Trim off the unused portion of buffer */
144                 buf = realloc(buf, data - buf);
145         }
146
147         /*
148          * If that fails, use hardcoded vt220 codes.
149          * They don't seem to do anything bad on vt100's, so
150          * we'll try them just in case they work.
151          */
152         if (!term_vi || !term_ve) {
153                 static char *vts[] = {
154                                 "vt100", "vt101", "vt102",
155                                 "vt200", "vt220", "vt300",
156                                 "vt320", "vt400", "vt420",
157                                 "screen", "xterm", NULL };
158                 int i;
159
160                 for (i = 0; vts[i]; i++)
161                         if (!strcmp(term, vts[i]))
162                         {
163                                 term_vi = "\033[?25l";
164                                 term_ve = "\033[?25h";
165                                 break;
166                         }
167         }
168         if (!term_vi || !term_ve)
169                 term_vi = term_ve = NULL;
170 } //GetTermcapInfo
171
172 ExtFunc void OutputTermStr(char *str, int flush)
173 {
174         if (str) {
175                 fputs(str, stdout);
176                 if (flush) fflush(stdout);
177         }
178 } //OutputTermStr
179
180 ExtFunc void DrawTitle(void)
181 {
182         int rows, cols;
183         char s[255];
184
185 #ifdef HAVE_NCURSES
186         attrset(A_REVERSE);
187 #else
188         standout();
189 #endif
190         getmaxyx(stdscr, rows, cols);
191         sprintf(s, " NETRIS %s", version_string);
192         memset(&s[strlen(s)], ' ', 254 - strlen(s));
193         if (cols > 56 + strlen(version_string))
194                 memcpy(&s[cols - 48],
195                         "(C)1994-1996,1999 Mark H. Weaver, (C)2002 Shiar \0", 49);
196         else memcpy(&s[cols], "\0", 1);
197         mvaddstr(0, 0, s);
198         standend();     //normal text
199 } //DrawTitle
200
201 ExtFunc void DrawBox(int x1, int y1, int x2, int y2)
202 { //draw grid
203         int y, x;
204
205         for (y = y1 + 1; y < y2; y++) {
206                 mvaddch(y, x1, Game.ascii ? '|' : ACS_VLINE); //left
207                 mvaddch(y, x2, Game.ascii ? '|' : ACS_VLINE); //right
208         }
209         move(y1, x1); //top
210         addch(Game.ascii ? '+' : ACS_ULCORNER);
211         for (x = x1 + 1; x < x2; x++)
212                 addch(Game.ascii ? '-' : ACS_HLINE);
213         addch(Game.ascii ? '+' : ACS_URCORNER);
214         move(y2, x1); //bottom
215         addch(Game.ascii ? '+' : ACS_LLCORNER);
216         for (x = x1 + 1; x < x2; x++)
217                 addch(Game.ascii ? '-' : ACS_HLINE);
218         addch(Game.ascii ? '+' : ACS_LRCORNER);
219 } //DrawBox
220
221 ExtFunc void DrawField(int scr)
222 { //draw field for player scr
223         if (!Players[scr].spy) return; 
224         DrawBox(boardXPos[scr] - 1, boardYPos[scr] - Players[scr].boardVisible,
225                 boardXPos[scr] + 2 * Players[scr].boardWidth, boardYPos[scr] + 1);
226         {
227                 char s[2*Players[scr].boardWidth];
228
229                 memset(s, ' ', sizeof(s));
230                 if (Players[scr].host && Players[scr].host[0])
231                         snprintf(s, sizeof(s), "%s <%s>",
232                                 Players[scr].name, Players[scr].host);
233                 else snprintf(s, sizeof(s), "%s", Players[scr].name);
234                 s[strlen(s)] = ' ';
235                 s[sizeof(s) - 7*((Players[scr].flags & SCF_usingRobot) != 0)
236                         - 5*((Players[scr].flags & SCF_fairRobot) != 0)] = 0;
237                 mvaddstr(1, boardXPos[scr], s);
238
239                 if (Players[scr].flags & SCF_usingRobot) {
240                         addstr((Players[scr].flags & SCF_fairRobot)
241                                 ? "(fair robot)" : "(robot)");
242                 } //add robot indicator
243         } //display playername/host
244
245         // draw blocks (which is usually just clear field)
246
247         ShowPause(scr);
248 } //DrawField
249
250 ExtFunc void InitFields(void)
251 { //calculate positions of all fields
252         int scr, prevscr;
253         int y, x;
254
255         getmaxyx(stdscr, y, x);
256         boardXPos[me] = 1;
257         boardYPos[me] = 22;
258         statusXPos = 2 * Players[me].boardWidth + 3;
259         statusYPos = 22;
260         messageXPos = 2;
261         messageYPos = 25;
262         messageWidth = x - messageXPos - 2;
263         if ((messageHeight = y - messageYPos - 1) < 0) messageHeight = 0;
264         else DrawBox(messageXPos - 2, messageYPos - 1,
265                 messageXPos + messageWidth + 1, messageYPos + messageHeight);
266         prevscr = me;
267         for (scr = 1; scr < MAX_SCREENS; scr++) if (scr != me) {
268                 boardXPos[scr] =
269                         boardXPos[prevscr] + 2 * Players[prevscr].boardWidth + 3;
270                 if (prevscr == me)
271                         boardXPos[scr] += 14; //scorebar
272                 boardYPos[scr] = 22;
273                 if (x < boardXPos[scr] + 2 * Players[scr].boardWidth + 1)
274                         Players[scr].spy = 0; //field doesn't fit on screen
275                 prevscr = scr;
276         }
277         for (scr = 1; scr <= Game.maxplayers; scr++)
278                 DrawField(scr);
279 } //InitFields
280
281 ExtFunc void CleanupScreen(int scr)
282 {
283 }
284
285 ExtFunc void Messagef(char *fmt, ...)
286 { //print game/bot message
287         static int line = 0;
288         va_list args;
289         char s[255];
290         char *p, *psearch;
291         int i;
292
293         if (!messageHeight) return;
294         va_start(args, fmt);
295         move(messageYPos + line, messageXPos);
296 //      vwprintw(stdscr, fmt, args); //doesn't seem to be a vprintw
297         vsprintf(s, fmt, args);
298         p = s;
299         while (psearch = strchr(s, '\\')) {
300                 *psearch = '\0';
301                 addstr(p);
302                 if (haveColor)
303                         attrset(A_REVERSE | COLOR_PAIR(atoi(psearch + 1) + 1));
304                 p = psearch + 2;
305         } //search for color escapes (\)
306         addstr(p);
307         if (messageHeight > 1) {
308                 char s[messageWidth + 1];
309                 line = (line + 1) % messageHeight;
310                 memset(s, ' ', messageWidth);
311                 s[messageWidth] = 0;
312                 mvaddstr(messageYPos + line, messageXPos, s);
313         } //multiple lines
314         if (haveColor) standend();
315         va_end(args);
316 } //Message
317
318 ExtFunc void PlotBlock1(int scr, int y, int x, BlockType type)
319 {
320         int colorIndex = abs(type);
321
322         move(y, x);
323         if (type == BT_none) addstr("  ");
324         else if (type == BT_shadow) addstr("::");
325         else {
326                 if (Game.standout) {
327 #ifdef HAVE_NCURSES
328                         if (haveColor) attrset(COLOR_PAIR(colorIndex));
329                         else attrset(A_REVERSE);
330 #endif
331                 }
332                 addstr(type ? "[]" : "$$");
333 #ifdef HAVE_NCURSES
334                 if (Game.standout) standend();
335 #endif
336         } //display one brick
337 } //PlotBlock1
338 ExtFunc void PlotBlock(int scr, int y, int x, BlockType type)
339 {
340         if (y >= 0 && y < Players[scr].boardVisible &&
341                 x >= 0 && x < Players[scr].boardWidth)
342           PlotBlock1(scr, boardYPos[scr] - y, boardXPos[scr] + 2 * x, type);
343 } //PlotBlock
344
345 ExtFunc void PlotShadowBlock1(int scr, int y, int x, BlockType type)
346 {
347         move(y, x);
348         if (type == BT_none) addstr("  ");
349         else addstr("::");
350 } //PlotShadowBlock1
351 ExtFunc void PlotShadowBlock(int scr, int y, int x, BlockType type)
352 {
353         if (y >= 0 && y < Players[scr].boardVisible &&
354                 x >= 0 && x < Players[scr].boardWidth)
355           PlotShadowBlock1(scr, boardYPos[scr] - y, boardXPos[scr] + 2 * x, type);
356 } //PlotShadowBlock
357
358 ExtFunc void PlotBlockS1(int scr, int y, int x, BlockType type)
359 { //DOESN"T WORK YET...
360         move(y, x);
361         if (type == BT_none) addstr(" ");
362         else {
363                 addstr(type ? "O" : "$");
364                 standend();
365         } //display one brick
366 } //PlotBlock1
367 ExtFunc void PlotBlockS(int scr, int y, int x, BlockType type)
368 {
369         if (y >= 0 && y < Players[scr].boardVisible &&
370                 x >= 0 && x < Players[scr].boardWidth)
371           PlotBlockS1(scr, boardYPos[scr] - y, boardXPos[scr] + x, type);
372 } //PlotBlock
373
374 ExtFunc void PlotUnderline(int scr, int x, int flag)
375 { //display piece of bottom fieldgrid
376   move(boardYPos[scr] + 1, boardXPos[scr] + 2 * x);
377   if (Game.ascii)
378         addstr(flag ? "==" : "--");
379   else {
380         addch(flag ? ACS_BTEE : ACS_HLINE);
381         addch(flag ? ACS_BTEE : ACS_HLINE);
382   } //ncurses graphics
383 } //PlotUnderline
384
385 ExtFunc void ShowScore(int scr, struct _Score score)
386 { //show score stuff
387         float timer;
388
389         mvaddstr(13, statusXPos, "next         ");
390         mvaddstr(14, statusXPos + 5,  "        ");
391         ShapeIterate(Players[scr].nextShape, scr,
392           ShapeToNetNum(Players[scr].nextShape) == 15 ? 6 : 7,
393           statusXPos / 2 + 4, 1, GlanceFunc, NULL);
394         mvprintw(3, statusXPos, "level   %5d", score.level);
395         mvprintw(5, statusXPos, "score%8d", score.score);
396         mvprintw(6, statusXPos, "lines%8d", score.lines);
397         timer = CurTimeval() / 1e6;
398         if (timer > 4) {
399                 mvprintw(9, statusXPos, "ppm %9.1f", score.drops * 60 / timer);
400                 if (score.lines > 0) {
401                         mvprintw(7, statusXPos,
402                                 "yield    %3d%%", 100 * score.adds / score.lines);
403                         mvprintw(10, statusXPos, "apm %9.1f", score.adds * 60 / timer);
404                 }
405         }
406 } //ShowScore
407
408 ExtFunc void FieldMessage(int playa, char *message)
409 { //put a message over playa's field
410         if (!Players[playa].spy) return;
411         if (message) {
412                 char s[MAX_BOARD_WIDTH+1];
413                 memset(s, ' ', MAX_BOARD_WIDTH);
414                 memcpy(&s[Players[playa].boardWidth - strlen(message) / 2],
415                         message, strlen(message));
416                 s[Players[playa].boardWidth * 2] = 0;
417 #ifdef HAVE_NCURSES
418                 attrset(A_REVERSE);
419 #else
420                 standout();
421 #endif
422                 mvprintw(boardYPos[playa] - Players[playa].boardVisible / 2,
423                         boardXPos[playa], "%s", s);
424                 standend();
425         } //display
426         else {
427                 int x, y;
428                 y = Players[playa].boardVisible / 2;
429                 for (x = 0; x <= Players[playa].boardWidth; x++)
430                         PlotBlock(playa, y, x, GetBlock(playa, y, x));
431         } //restore field
432 } //FieldMessage
433
434 ExtFunc void ShowPause(int playa)
435 { //put paused over player's field
436         if (Players[playa].alive)
437                 if (Players[playa].flags & SCF_paused)
438                         FieldMessage(playa, Game.started > 1
439                                 ? "P A U S E D" : "N O T  R E A D Y");
440                 else FieldMessage(playa, Game.started > 1 ? NULL : "R E A D Y");
441         else FieldMessage(playa, playa > maxPlayer
442                         ? "E M P T Y" : "G A M E  O V E R");
443 } //ShowPause
444
445
446 ExtFunc void ShowTime(void)
447 { //display timer
448         mvprintw(statusYPos, statusXPos, "timer %7.0f ", CurTimeval() / 1e6);
449 //      move(boardYPos[0] + 1, boardXPos[0] + 2 * Players[0].boardWidth + 1);
450 //      refresh();
451 } //ShowTime
452
453 ExtFunc void ScheduleFullRedraw(void)
454 {
455         touchwin(stdscr);
456 } //ScheduleFullRedraw
457
458 static MyEventType KeyGenFunc(EventGenRec *gen, MyEvent *event)
459 { //read keypresses
460         if (MyRead(gen->fd, &event->u.key, 1))
461                 return E_key;
462         else
463                 return E_none;
464 } //KeyGenFunc
465
466 /*
467  * vi: ts=4 ai
468  * vim: noai si
469  */