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