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