add bug report from alternate branch
[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
24 #include <sys/types.h>
25 #include <unistd.h>
26 #include <curses.h>
27 #include <string.h>
28 #include <stdlib.h>
29
30 #include "client.h"
31 #include "curses.h"
32 #include "util.h"
33 #include "board.h"
34 #include "msg.en.h"
35
36 #ifdef NCURSES_VERSION
37 # define HAVE_NCURSES
38 #endif
39
40 static MyEventType KeyGenFunc(EventGenRec *gen, MyEvent *event);
41 static EventGenRec keyGen =
42 //              { NULL, 0, FT_read, STDIN_FILENO, KeyGenFunc, EM_key };
43                 { NULL, 0, FT_read, STDIN_FILENO, KeyGenFunc, EM_key };
44
45 static int boardYPos[MAX_SCREENS], boardXPos[MAX_SCREENS];
46 static int boardSize[MAX_SCREENS];
47 //^^^struct
48 static int statusYPos, statusXPos;
49 static int messageYPos, messageXPos, messageHeight, messageWidth;
50 WINDOW *msgwin;
51 static int haveColor;
52 int PlayerDisp[MAX_SCREENS];
53
54 #define MSG_HEIGHT 64 //max history
55 char *message[MSG_HEIGHT];
56 char messages[MSG_HEIGHT][MSG_WIDTH];
57
58 static char *term_vi;   /* String to make cursor invisible */
59 static char *term_ve;   /* String to make cursor visible */
60
61 void InitScreens(void)
62 {
63         MySigSet oldMask;
64
65         GetTermcapInfo();
66
67         /*
68          * Block signals while initializing curses.  Otherwise a badly timed
69          * Ctrl-C during initialization might leave the terminal in a bad state.
70          */
71         BlockSignals(&oldMask, SIGINT, 0);
72         initscr();              //start curses
73
74 #ifdef CURSES_HACK
75         {
76                 extern char *CS;
77
78                 CS = 0;
79         }
80 #endif
81
82 #ifdef HAVE_NCURSES
83         haveColor = Sets.color && has_colors();
84         if (haveColor) {
85                 static struct {
86                         char type;
87                         short color;
88                 } myColorTable[] = {
89                         { BT_white,   COLOR_WHITE },
90                         { BT_blue,    COLOR_BLUE },
91                         { BT_magenta, COLOR_MAGENTA },
92                         { BT_cyan,    COLOR_CYAN },
93                         { BT_yellow,  COLOR_YELLOW },
94                         { BT_green,   COLOR_GREEN },
95                         { BT_red,     COLOR_RED },
96                         { BT_none,    0 }
97                 }; //myColorTable
98                 int i = 0;
99
100                 start_color();
101                 if (can_change_color()) {
102                         init_color (COLOR_YELLOW, 1000, 1000, 0);
103                 } //I've never worked on a color-changable terminal, so no idea..
104                 for (i = 0; myColorTable[i].type != BT_none; ++i)
105                         init_pair(myColorTable[i].type, COLOR_BLACK,
106                                 myColorTable[i].color);
107         } //haveColor
108 #else
109         haveColor = 0;
110 #endif
111
112         AtExit(CleanupScreens);        //restore everything when done
113         RestoreSignals(NULL, &oldMask);
114
115         cbreak();                      //no line buffering
116         noecho();
117 //      keypad(stdscr, TRUE);          //get arrow/functionkeys 'n stuff
118         OutputTermStr(term_vi, 0);
119         AddEventGen(&keyGen);          //key handler
120         signal(SIGWINCH, CatchWinCh);  //handle window resize
121 //  ioctl(STDIN_FILENO, KDSKBMODE, K_MEDIUMRAW);
122         standend();                    //normal text
123
124         memset(messages, 0, sizeof(messages)); //empty messages
125         {
126                 int i;
127                 for (i = 0; i<MSG_HEIGHT; i++)
128                         message[i] = messages[i];  //set pointers
129         }
130 }
131
132 void CleanupScreens(void)
133 {
134         RemoveEventGen(&keyGen);
135         endwin();                      //end curses
136         OutputTermStr(term_ve, 1);
137 }
138
139 void GetTermcapInfo(void)
140 {
141         char *term, *buf, *data;
142         int bufSize = 8192;
143         char scratch[1024];
144
145         if (!(term = getenv("TERM")))
146                 return;
147         if (tgetent(scratch, term) == 1) {
148                 /*
149                  * Make the buffer HUGE, since tgetstr is unsafe.
150                  * Allocate it on the heap too.
151                  */
152                 data = buf = malloc(bufSize);
153
154                 /*
155                  * There is no standard include file for tgetstr, no prototype
156                  * definitions.  I like casting better than using my own prototypes
157                  * because if I guess the prototype, I might be wrong, especially
158                  * with regards to "const".
159                  */
160                 term_vi = (char *)tgetstr("vi", &data);
161                 term_ve = (char *)tgetstr("ve", &data);
162
163                 /* Okay, so I'm paranoid; I just don't like unsafe routines */
164                 if (data > buf + bufSize)
165                         fatal("tgetstr overflow, you must have a very sick termcap");
166
167                 /* Trim off the unused portion of buffer */
168                 buf = realloc(buf, data - buf);
169         }
170
171         /*
172          * If that fails, use hardcoded vt220 codes.
173          * They don't seem to do anything bad on vt100's, so
174          * we'll try them just in case they work.
175          */
176         if (!term_vi || !term_ve) {
177                 static char *vts[] = {
178                         "vt100", "vt101", "vt102",
179                         "vt200", "vt220", "vt300",
180                         "vt320", "vt400", "vt420",
181                         "screen", "xterm", NULL };
182                 int i;
183
184                 for (i = 0; vts[i]; i++)
185                         if (!strcmp(term, vts[i]))
186                         {
187                                 term_vi = "\033[?25l";
188                                 term_ve = "\033[?25h";
189                                 break;
190                         }
191         }
192         if (!term_vi || !term_ve)
193                 term_vi = term_ve = NULL;
194 }
195
196 void OutputTermStr(char *str, int flush)
197 {
198         if (str) {
199                 fputs(str, stdout);
200                 if (flush) fflush(stdout);
201         }
202 }
203
204 void DrawTitle(void)
205 {
206         int rows, cols;
207         char s[255];
208
209 #ifdef HAVE_NCURSES
210         attrset(A_REVERSE);
211 #else
212         standout();
213 #endif
214         getmaxyx(stdscr, rows, cols);
215         sprintf(s, " NETRIS %s", version_string);
216         memset(&s[strlen(s)], ' ', 254 - strlen(s));
217         if (cols > 56 + strlen(version_string))
218                 memcpy(&s[cols - 48],
219                         "(C)1994-1996,1999 Mark H. Weaver, (C)2002 Shiar \0", 49);
220         else memcpy(&s[cols], "\0", 1);
221         mvaddstr(0, 0, s);
222         standend();     //normal text
223 }
224
225 void DrawBox(int x1, int y1, int x2, int y2)
226 { //draw grid
227         int y, x;
228
229         for (y = y1 + 1; y < y2; y++) {
230                 mvaddch(y, x1, Sets.ascii ? '|' : ACS_VLINE); //left
231                 mvaddch(y, x2, Sets.ascii ? '|' : ACS_VLINE); //right
232         } //draw vertical lines
233         move(y1, x1); //top
234         addch(Sets.ascii ? '+' : ACS_ULCORNER);
235         for (x = x1 + 1; x < x2; x++)
236                 addch(Sets.ascii ? '-' : ACS_HLINE);
237         addch(Sets.ascii ? '+' : ACS_URCORNER);
238         move(y2, x1); //bottom
239         addch(Sets.ascii ? '+' : ACS_LLCORNER);
240         for (x = x1 + 1; x < x2; x++)
241                 addch(Sets.ascii ? '-' : ACS_HLINE);
242         addch(Sets.ascii ? '+' : ACS_LRCORNER);
243 }
244
245 void DrawField(int scr)
246 { //draw field for player scr
247         if (!PlayerDisp[scr]) return; 
248         DrawBox(boardXPos[scr] - 1, boardYPos[scr] - Players[scr].boardVisible,
249                 boardXPos[scr] + boardSize[scr] * Players[scr].boardWidth, boardYPos[scr] + 1);
250         {
251                 char s[boardSize[scr]*Players[scr].boardWidth+1];
252
253                 if (Players[scr].host && Players[scr].host[0])
254                         snprintf(s, sizeof(s), " %s <%s> ",
255                                 Players[scr].name, Players[scr].host);
256                 else snprintf(s, sizeof(s), " %s ", Players[scr].name);
257                 s[sizeof(s)] = 0;
258                 if (haveColor && Players[scr].team > 0 && Players[scr].team <= 7)
259                         attrset(A_REVERSE | COLOR_PAIR(Players[scr].team + 1));
260                 mvaddstr(1, boardXPos[scr], s);
261                 if (haveColor) standend();
262         } //display playername/host
263
264         {
265                 int x, y;
266                 for (y = 0; y <= Players[scr].boardVisible; y++)
267                         for (x = 0; x <= Players[scr].boardWidth; x++)
268                                 PlotBlock(scr, y, x, GetBlock(scr, y, x));
269         } //draw field
270
271         ShowPause(scr);
272 }
273
274 void InitFields(void)
275 { //calculate positions of all fields
276         int scr, prevscr;
277         int y, x;
278         int spaceavail;
279
280         clear();
281         DrawTitle();
282         getmaxyx(stdscr, y, x);
283         boardSize[me] = 2;
284         boardXPos[me] = 1;
285         boardYPos[me] = 21;
286         PlayerDisp[me] = 1;
287         statusXPos = boardSize[me] * Players[me].boardWidth + 3;
288         statusYPos = 21;
289         ShowScore(me, Players[me].score);
290
291         messageXPos = 2;
292         messageYPos = 24;
293         if ((messageWidth = x - messageXPos - 2) > MSG_WIDTH) messageWidth = MSG_WIDTH;
294         if ((messageHeight = y - messageYPos - 1) > MSG_HEIGHT) messageHeight = MSG_HEIGHT;
295         if (messageHeight <= 0) {
296                 messageWidth = 27;
297                 messageHeight = y - 3;
298                 messageXPos = statusXPos + 16;
299                 messageYPos = 2;
300         } //messagebox doesn't fit below
301         DrawBox(messageXPos - 2, messageYPos - 1,
302                 messageXPos + messageWidth + 1, messageYPos+messageHeight);
303         if (msgwin = subwin(stdscr, messageHeight, messageWidth,
304                                 messageYPos, messageXPos))
305                 scrollok(msgwin, 1);  //allow scrolling
306         wmove(msgwin, messageHeight - 2, 0);
307         for (scr = messageHeight - 2; scr >= 0; scr--) //display message history
308                 DisplayMessage(message[scr]);
309
310         spaceavail = x;
311         for (scr = 1; scr <= maxPlayer; scr++)
312                 spaceavail -= Players[scr].boardWidth+2;
313         prevscr = me;
314         for (scr = 1; scr < MAX_SCREENS; scr++) if (scr != me) {
315                 boardYPos[scr] = 21;
316                 boardXPos[scr] =
317                         boardXPos[prevscr] + 2 + boardSize[prevscr] * Players[prevscr].boardWidth;
318                 if (prevscr == me) {
319                         boardXPos[scr] += 15; //scorebar
320                         if (messageYPos < 24)
321                                 boardXPos[scr] += messageWidth + 4; //messagebox
322                         spaceavail -= boardXPos[scr] - 3;
323                 } //stuff before second player
324                 if (spaceavail >= 0) {
325                         boardSize[scr] = 2;
326                         spaceavail -= Players[scr].boardWidth;
327                 } //not enough space, half width
328                 else
329                         boardSize[scr] = 1;
330                 if (x < boardXPos[scr] + 1 + boardSize[scr] * Players[scr].boardWidth)
331                         PlayerDisp[scr] = 0; //field doesn't fit on screen
332                 else
333                         PlayerDisp[scr] = 1;
334                 prevscr = scr;
335         }
336         for (scr = 1; scr <= maxPlayer; scr++)
337                 DrawField(scr);
338 }
339
340 void CleanupScreen(int scr)
341 {
342 }
343
344 void DisplayMessage(char *p)
345 {
346         char s[MSG_WIDTH];
347         char *psearch;
348         char c;
349
350         memcpy(s, p, sizeof(s)-1);
351         s[MSG_WIDTH-1] = 0;
352         p = s;
353         while (psearch = strchr(p, '\\')) {
354                 *psearch = '\0';
355                 waddstr(msgwin, p);
356                 c = atoi(++psearch)+1;
357                 if (haveColor) wattrset(msgwin, A_REVERSE | COLOR_PAIR(c));
358                 p = ++psearch;
359         } //search for color escapes (\)
360         waddstr(msgwin, p);
361         if (haveColor) wstandend(msgwin);
362         waddch(msgwin, '\n');
363 }
364
365 void Message(char *fmt, ...)
366 { //print game/bot message
367         va_list args;
368         char s[MSG_WIDTH];
369         char *p;
370         int i;
371
372         if (!messageHeight) return;
373         va_start(args, fmt);
374         vsnprintf(s, sizeof(s), fmt, args);
375         va_end(args);
376         p = message[MSG_HEIGHT - 1]; //save last pointer
377         for (i = MSG_HEIGHT - 1; i > 0; i--)
378                 message[i] = message[i - 1]; //scroll history
379         message[0] = p;
380         strcpy(p, s);
381
382         wmove(msgwin, messageHeight - 1, 0);
383         DisplayMessage(s);
384         wclrtoeol(msgwin);
385         wrefresh(msgwin);
386 }
387
388 void Messagetype(char c, int x, char *s)
389 { //show single typed character
390         if (c == 27) {
391                 mvwaddch(msgwin, messageHeight-1, (x+1) % (messageWidth-1), ' ');
392         } //escape
393         else {
394                 if (c == 13 || c==127) //enter/backspace
395                         mvwaddch(msgwin, messageHeight - 1, (x+2) % (messageWidth-1),
396                                 x>=messageWidth-3 ? s[x - messageWidth + 3] : ' ');
397                 else //any character
398                         mvwaddch(msgwin, messageHeight - 1, x % (messageWidth-1), c);
399                 mvwaddch(msgwin, messageHeight - 1, (x+1) % (messageWidth-1), '_');
400         } //typing mode
401         wrefresh(msgwin);
402 }
403
404 void PlotBlock1(int y, int x, unsigned char type)
405 { //display block on screen
406         move(y, x);
407         if (type == BT_none) addstr("  ");
408         else if (type == BT_shadow) addstr("::");
409         else {
410 #ifdef HAVE_NCURSES
411                 if (Sets.standout) {
412                         if (haveColor) attrset(COLOR_PAIR(type & 15));
413                         else attrset(A_REVERSE);
414                 }
415 #endif
416                 switch (Sets.drawstyle) {
417                         case 2:
418                                 switch (type & 192) {
419                                  case 64:  //right neighbour
420                                          addstr("[[");
421                                          break;
422                                  case 128: //left
423                                          addstr("]]");
424                                          break;
425                                  default:  //both/none
426                                          addstr("[]");
427                                          break;
428                                 } //horizontal stickiness
429                                 break; //ascii horizontally grouped
430                         case 3:
431                                 switch (type & 240) {
432                                  case 48:
433                                          addstr("||"); break; //middle
434                                  case 64: case 80: case 96:
435                                          addstr("[="); break; //left end
436                                  case 112:
437                                          addstr("|="); break;
438                                  case 128: case 144: case 160:
439                                          addstr("=]"); break; //right end
440                                  case 176:
441                                          addstr("=|"); break;
442                                  case 192: case 208: case 224:
443                                          addstr("=="); break;
444                                  default:
445                                          addstr("[]"); break; //top/bottom/mid
446                                 } //neighbours
447                                 break; //ascii semi-grouped
448                         case 7:
449                                 switch (type & 240) {
450                                  case 16:  addch(ACS_ULCORNER); addch(ACS_URCORNER); break;//top end
451                                  case 32:  addch(ACS_LLCORNER); addch(ACS_LRCORNER); break;//bottom end
452                                  case 48:  addch(ACS_VLINE); addch(ACS_VLINE); break;   //vertical middle
453                                  case 64:  addch(' '); addch(ACS_HLINE); break;         //left end
454                                  case 80:  addch(ACS_ULCORNER); addch(ACS_TTEE); break; //top left corner
455                                  case 96:  addch(ACS_LLCORNER); addch(ACS_BTEE); break; //bottom left corner
456                                  case 112: addch(ACS_LTEE); addch(ACS_PLUS); break;     //vertical+right
457                                  case 128: addch(ACS_HLINE); addch(' '); break;         //right end
458                                  case 144: addch(ACS_TTEE); addch(ACS_URCORNER); break; //top right corner
459                                  case 160: addch(ACS_BTEE); addch(ACS_LRCORNER); break; //bottom right corner
460                                  case 176: addch(ACS_PLUS); addch(ACS_RTEE); break;     //vertical+left
461                                  case 192: addch(ACS_HLINE); addch(ACS_HLINE); break;   //horizontal middle
462                                  case 208: addch(ACS_TTEE); addch(ACS_TTEE); break;     //horizontal+down
463                                  case 224: addch(ACS_BTEE); addch(ACS_BTEE); break;     //horizontal+up
464                                  default:  addstr("[]"); break;
465                                 } //neighbours
466                                 break; //curses grouped
467                         default:
468                                 addstr("[]");
469                                 break; //ascii non-grouped
470                 } //draw block
471 #ifdef HAVE_NCURSES
472                 if (Sets.standout) standend();
473 #endif
474         } //display one brick
475 }
476 void PlotBlock1S(int y, int x, unsigned char type)
477 { //display block small
478         move(y, x);
479         if (type == BT_none) addch(' ');
480         else if (type == BT_shadow) addch(':');
481         else {
482                 if (Sets.standout) {
483 #ifdef HAVE_NCURSES
484                         if (haveColor)
485                                 attrset(COLOR_PAIR(type & 15));
486                         else attrset(A_REVERSE);
487 #endif
488                 }
489                 if ((type & 192) == 64)
490                         addch('[');
491                 else if ((type & 192) == 128)
492                         addch(']');
493                 else
494                         addch('|');
495 #ifdef HAVE_NCURSES
496                 if (Sets.standout) standend();
497 #endif
498         } //display one brick
499 }
500 void PlotBlock(int scr, int y, int x, unsigned char type)
501 {
502         if (y >= 0 && y < Players[scr].boardVisible &&
503                 x >= 0 && x < Players[scr].boardWidth) {
504                 if (boardSize[scr] > 1)
505                         PlotBlock1(boardYPos[scr] - y, boardXPos[scr] + 2*x, type);
506                 else
507                         PlotBlock1S(boardYPos[scr] - y, boardXPos[scr] + x, type);
508         } //on screen
509 }
510 void PlotBlockXY(int y, int x, unsigned char type)
511 { //Draw block at specified position on screen (not on field)
512         PlotBlock1(20 - y, 2 * x, type);
513 }
514
515 void ShowScore(int scr, struct _Score score)
516 { //show score stuff
517         float timer;
518
519         mvaddstr(13, statusXPos, MSG_NEXT " ");
520         mvaddstr(14, statusXPos + 5,  "        ");
521         ShapeIterate(Players[scr].nextShape, scr,
522                 8, statusXPos/2 + (Players[scr].nextShape/4 == 5 ? 3 : 4),
523                 GlanceFunc); //draw; stick one more to the left
524         mvprintw(3, statusXPos, MSG_LEVEL, score.level);
525         mvprintw(5, statusXPos, MSG_SCORE, score.score);
526         mvprintw(6, statusXPos, MSG_LINES, score.lines);
527         timer = CurTimeval() / 1e6;
528         if (timer > 4) {
529                 mvprintw(9, statusXPos, MSG_PPM, score.pieces * 60 / timer);
530                 if (score.lines > 0) {
531                         mvprintw(7, statusXPos, MSG_YIELD, 100 * score.adds / score.lines);
532                         mvprintw(10, statusXPos, MSG_APM, score.adds * 60 / timer);
533                 }
534         } //display [ap]pm
535         else {
536                 int i;
537                 for (i = 7; i <= 10; i++)
538                         mvaddstr(i, statusXPos, "             ");
539         } //too early to display stats, remove old..
540 }
541
542 void FieldMessage(int playa, char *message)
543 { //put a message over playa's field
544         if (!PlayerDisp[playa]) return;
545         if (message) {
546                 char s[MAX_BOARD_WIDTH+1];
547                 memset(s, ' ', MAX_BOARD_WIDTH);
548                 memcpy(&s[(boardSize[playa] * Players[playa].boardWidth / 2) - (strlen(message) / 2)],
549                         message, strlen(message));
550                 s[boardSize[playa] * Players[playa].boardWidth] = 0;
551 #ifdef HAVE_NCURSES
552                 attrset(A_REVERSE);
553 #else
554                 standout();
555 #endif
556                 mvprintw(boardYPos[playa] - Players[playa].boardVisible / 2,
557                         boardXPos[playa], "%s", s);
558                 standend();
559         } //display
560         else {
561                 int x, y;
562                 y = Players[playa].boardVisible / 2;
563                 for (x = 0; x <= Players[playa].boardWidth; x++)
564                         PlotBlock(playa, y, x, GetBlock(playa, y, x));
565         } //restore field
566 }
567
568 void ShowPause(int playa)
569 { //put paused over player's field
570         if (Players[playa].alive > 0)
571                 if (Players[playa].flags & SCF_paused)
572                         if (Game.started > 1)
573                                 FieldMessage(playa, boardSize[playa] > 1 ? "P A U S E D" : "PAUSED");
574                         else
575                                 FieldMessage(playa,
576                                         boardSize[playa] > 1 ? "N O T  R E A D Y" : "NOT  READY");
577                 else
578                         if (Game.started > 1)
579                                 FieldMessage(playa, NULL);
580                         else
581                                 FieldMessage(playa, boardSize[playa] > 1 ? "R E A D Y" : "READY");
582         else if (!Players[playa].alive)
583                 FieldMessage(playa,
584                         boardSize[playa] > 1 ? "G A M E  O V E R" : "GAME  OVER");
585         else
586                 FieldMessage(playa, boardSize[playa] > 1 ? "E M P T Y" : "EMPTY");
587 }
588
589
590 void ShowTime(void)
591 { //display timer
592         mvprintw(statusYPos, statusXPos, "timer %7.0f ", CurTimeval() / 1e6);
593 }
594
595 void ScheduleFullRedraw(void)
596 {
597         touchwin(stdscr);
598 }
599
600 void CatchWinCh(int sig)
601 { //handle window resize
602         endwin();    //exit curses
603         refresh();    //and reinit display (with different sizes)
604         InitFields();  //manually redraw everything
605         refresh();    //refresh
606 }
607
608 static MyEventType KeyGenFunc(EventGenRec *gen, MyEvent *event)
609 { //read keypresses
610         if (MyRead(gen->fd, &event->u.key, 1))
611                 return E_key;
612         else
613                 return E_none;
614 }
615
616 /*
617  * vi: ts=4 ai
618  * vim: noai si
619  */