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