f28a6a36a2ec81cf6c846f87599e187a31713c05
[netris.git] / curses.c
1 /*
2  * Netris -- A free networked version of T*tris
3  * Copyright (C) 1994-1996,1999  Mark H. Weaver <mhw@netris.org>
4  * 
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  * 
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  * 
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18  *
19  * $Id: curses.c,v 1.33 1999/05/16 06:56:25 mhw Exp $
20  */
21
22 #include "netris.h"
23 #include <sys/types.h>
24 #include <unistd.h>
25 #include <curses.h>
26 #include <string.h>
27 #include <stdlib.h>
28
29 #ifdef NCURSES_VERSION
30 # define HAVE_NCURSES
31 #endif
32
33 #ifdef HAVE_NCURSES
34 static struct {
35         BlockType type;
36         short color;
37 } myColorTable[] = {
38         { BT_white,             COLOR_WHITE },
39         { BT_blue,              COLOR_BLUE },
40         { BT_magenta,   COLOR_MAGENTA },
41         { BT_cyan,              COLOR_CYAN },
42         { BT_yellow,    COLOR_YELLOW },
43         { BT_green,             COLOR_GREEN },
44         { BT_red,               COLOR_RED },
45         { BT_none,              0 }
46 };
47 #endif
48
49 ExtFunc void PlotBlock1(int scr, int y, int x, BlockType type);
50 static MyEventType KeyGenFunc(EventGenRec *gen, MyEvent *event);
51
52 static EventGenRec keyGen =
53                 { NULL, 0, FT_read, STDIN_FILENO, KeyGenFunc, EM_key };
54
55 static int boardYPos[MAX_SCREENS], boardXPos[MAX_SCREENS];
56 static int statusYPos, statusXPos;
57 static int haveColor;
58
59 static char *term_vi;   /* String to make cursor invisible */
60 static char *term_ve;   /* String to make cursor visible */
61
62 ExtFunc 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();
74
75 #ifdef CURSES_HACK
76         {
77                 extern char *CS;
78
79                 CS = 0;
80         }
81 #endif
82
83 #ifdef HAVE_NCURSES
84         haveColor = Game.color && has_colors();
85         if (haveColor) {
86                 int i = 0;
87
88                 start_color();
89                 for (i = 0; myColorTable[i].type != BT_none; ++i)
90                         init_pair(myColorTable[i].type, COLOR_BLACK,
91                                         myColorTable[i].color);
92         }
93 #else
94         haveColor = 0;
95 #endif
96
97         AtExit(CleanupScreens);
98         RestoreSignals(NULL, &oldMask);
99
100         cbreak();
101         noecho();
102         OutputTermStr(term_vi, 0);
103         AddEventGen(&keyGen);
104
105         move(0, 1);
106         addstr("NETRIS ");
107         addstr(version_string);
108         {
109                 int rows, cols;
110
111                 getmaxyx(stdscr, rows, cols);
112                 move(0, cols - 48);
113                 addstr("(C)1994-1996,1999 Mark H. Weaver, (C)2002 Shiar");
114 //              addstr("    \"netris -h\" for more info");
115         }
116         statusYPos = 22;
117         statusXPos = 0;
118 } //InitScreens
119
120 ExtFunc void CleanupScreens(void)
121 {
122         RemoveEventGen(&keyGen);
123         endwin();
124         OutputTermStr(term_ve, 1);
125 } //CleanupScreens
126
127 ExtFunc void GetTermcapInfo(void)
128 {
129         char *term, *buf, *data;
130         int bufSize = 10240;
131
132         if (!(term = getenv("TERM")))
133                 return;
134         if (tgetent(scratch, term) == 1) {
135                 /*
136                  * Make the buffer HUGE, since tgetstr is unsafe.
137                  * Allocate it on the heap too.
138                  */
139                 data = buf = malloc(bufSize);
140
141                 /*
142                  * There is no standard include file for tgetstr, no prototype
143                  * definitions.  I like casting better than using my own prototypes
144                  * because if I guess the prototype, I might be wrong, especially
145                  * with regards to "const".
146                  */
147                 term_vi = (char *)tgetstr("vi", &data);
148                 term_ve = (char *)tgetstr("ve", &data);
149
150                 /* Okay, so I'm paranoid; I just don't like unsafe routines */
151                 if (data > buf + bufSize)
152                         fatal("tgetstr overflow, you must have a very sick termcap");
153
154                 /* Trim off the unused portion of buffer */
155                 buf = realloc(buf, data - buf);
156         }
157
158         /*
159          * If that fails, use hardcoded vt220 codes.
160          * They don't seem to do anything bad on vt100's, so
161          * we'll try them just in case they work.
162          */
163         if (!term_vi || !term_ve) {
164                 static char *vts[] = {
165                                 "vt100", "vt101", "vt102",
166                                 "vt200", "vt220", "vt300",
167                                 "vt320", "vt400", "vt420",
168                                 "screen", "xterm", NULL };
169                 int i;
170
171                 for (i = 0; vts[i]; i++)
172                         if (!strcmp(term, vts[i]))
173                         {
174                                 term_vi = "\033[?25l";
175                                 term_ve = "\033[?25h";
176                                 break;
177                         }
178         }
179         if (!term_vi || !term_ve)
180                 term_vi = term_ve = NULL;
181 } //GetTermcapInfo
182
183 ExtFunc void OutputTermStr(char *str, int flush)
184 {
185         if (str) {
186                 fputs(str, stdout);
187                 if (flush) fflush(stdout);
188         }
189 } //OutputTermStr
190
191 ExtFunc void DrawField(int scr)
192 { //drow field for player scr
193         {
194                 int y, x;
195
196                 getmaxyx(stdscr, y, x);
197                 if (x < boardXPos[scr] + 2 * Players[scr].boardWidth + 1) {
198                         Players[scr].spy = 0;
199                         return;
200                 }
201
202                 for (y = Players[scr].boardVisible - 1; y >= 0; --y) {
203                         mvaddch(boardYPos[scr] - y, boardXPos[scr] - 1,
204                                 Game.ascii ? '|' : ACS_VLINE); //left
205                         mvaddch(boardYPos[scr] - y,
206                                 boardXPos[scr] + 2 * Players[scr].boardWidth,
207                                 Game.ascii ? '|' : ACS_VLINE); //right
208                 }
209                 move(2, boardXPos[scr] - 1); //top
210                 addch(Game.ascii ? '+' : ACS_ULCORNER);
211                 for (x = Players[scr].boardWidth * 2 - 1; x >= 0; --x)
212                         addch(Game.ascii ? '-' : ACS_HLINE);
213                 addch(Game.ascii ? '+' : ACS_URCORNER);
214                 move(boardYPos[scr] + 1, boardXPos[scr] - 1); //bottom
215                 addch(Game.ascii ? '+' : ACS_LLCORNER);
216                 for (x = Players[scr].boardWidth * 2 - 1; x >= 0; --x)
217                         addch(Game.ascii ? '-' : ACS_HLINE);
218                 addch(Game.ascii ? '+' : ACS_LRCORNER);
219         } //draw field grid
220
221         {
222                 char userstr[300];
223
224                 if (Players[scr].host && Players[scr].host[0])
225                          sprintf(userstr, "%s <%s>", Players[scr].name, Players[scr].host);
226                 else sprintf(userstr, "%s", Players[scr].name);
227                 userstr[20 - 7*((Players[scr].flags & SCF_usingRobot) != 0)
228                         - 5*((Players[scr].flags & SCF_fairRobot) != 0)] = 0;
229                 mvaddstr(1, boardXPos[scr], userstr);
230
231                 if (Players[scr].flags & SCF_usingRobot) {
232                         addstr((Players[scr].flags & SCF_fairRobot)
233                                 ? "(fair robot)" : "(robot)");
234                 }
235         } //display playername/host
236
237         ShowPause(scr);
238 } //DrawScreen
239
240 ExtFunc void InitFields()
241 { //place fields for all players
242         int scr, prevscr;
243
244         statusXPos = 2 * Players[me].boardWidth + 3;
245         boardXPos[me] = 1;
246         boardYPos[me] = 22;
247         prevscr = me;
248         for (scr = 1; scr <= totalPlayers + 1; scr++) if (scr != me) {
249                 boardXPos[scr] =
250                         boardXPos[prevscr] + 2 * Players[prevscr].boardWidth + 3;
251                 if (prevscr == me)
252                         boardXPos[scr] += 24; //scorebar
253                 boardYPos[scr] = 22;
254                 prevscr = scr;
255         }
256 } //InitScreen
257
258 ExtFunc void CleanupScreen(int scr)
259 {
260 }
261
262 ExtFunc void PlotBlock1(int scr, int y, int x, BlockType type)
263 {
264         int colorIndex = abs(type);
265
266         move(y, x);
267
268         if (type == BT_none)
269                 addstr("  ");
270         else
271         {
272                 if (Game.standout)
273                 {
274 #ifdef HAVE_NCURSES
275                         if (haveColor)
276                                 attrset(COLOR_PAIR(colorIndex));
277                         else
278 #endif
279                                 standout();
280                 }
281
282                 addstr(type ? "[]" : "$$");
283                 standend();
284         }
285 } //PlotBlock1
286
287 ExtFunc void PlotBlock(int scr, int y, int x, BlockType type)
288 {
289         if (y >= 0 && y < Players[scr].boardVisible &&
290                 x >= 0 && x < Players[scr].boardWidth)
291           PlotBlock1(scr, boardYPos[scr] - y, boardXPos[scr] + 2 * x, type);
292 } //PlotBlock
293
294 ExtFunc void PlotUnderline(int scr, int x, int flag)
295 {
296   move(boardYPos[scr] + 1, boardXPos[scr] + 2 * x);
297   if (Game.ascii)
298         addstr(flag ? "==" : "--");
299   else {
300         addch(flag ? ACS_BTEE : ACS_HLINE);
301         addch(flag ? ACS_BTEE : ACS_HLINE);
302   }
303 } //PlotUnderline
304
305 ExtFunc void ShowScore(int scr, struct _Score score)
306 { //show score stuff
307         float timer;
308
309         move(6, statusXPos); addstr("Next:          ");
310         move(7, statusXPos + 7);    addstr("        ");
311         ShapeIterate(Players[scr].nextShape, scr,
312           ShapeToNetNum(Players[scr].nextShape) == 15 ? 13 : 14,
313           statusXPos / 2 + 5, 1, GlanceFunc, NULL);
314         move(statusYPos - 21 + 1, statusXPos);
315         printw("Score:%6d  level: %2d", score.score, score.level);
316         move(statusYPos - 20 + 1, statusXPos);
317         timer = CurTimeval() / 1e6;
318         printw("Lines:%6d", score.lines);
319         if (timer > 4) {
320                 printw("  ppm:%5.1f", score.drops * 60 / timer);
321                 move(statusYPos - 18, statusXPos);
322                 if (score.lines > 0)
323                         printw("yield:  %3d%%", 100 * score.adds / score.lines);
324                 else addstr("            ");
325                 printw("  apm:%5.1f", score.adds * 60 / timer);
326         }
327 } //ShowScore
328
329 ExtFunc void FieldMessage(int playa, char *message)
330 { //put a message over playa's field
331         if (!Players[playa].spy) return;
332         if (message) {
333                 char s[MAX_BOARD_WIDTH+1];
334                 memset(s, ' ', MAX_BOARD_WIDTH);
335                 memcpy(&s[Players[playa].boardWidth - strlen(message) / 2],
336                         message, strlen(message));
337                 s[Players[playa].boardWidth * 2] = 0;
338                 if (Game.standout) standout();
339                 mvprintw(boardYPos[playa] - Players[playa].boardVisible / 2,
340                         boardXPos[playa], "%s", s);
341                 standend();
342         } //display
343         else {
344                 int x, y;
345                 y = Players[playa].boardVisible / 2;
346                 for (x = 0; x <= Players[playa].boardWidth; x++)
347                         PlotBlock(playa, y, x, GetBlock(playa, y, x));
348         } //restore field
349 } //FieldMessage
350
351 ExtFunc void ShowPause(int playa)
352 { //put paused over player's field
353         if (Players[playa].flags & SCF_paused)
354                 FieldMessage(playa, "P A U S E D");
355         else FieldMessage(playa, NULL);
356 } //ShowPause
357
358
359 ExtFunc void Message(char *s)
360 {
361         static int line = 0;
362
363 //      move(statusYPos - 20 + line, statusXPos);
364         move(statusYPos + 2 + line, 1);
365         addstr(s);      /* XXX Should truncate long lines */
366         clrtoeol();
367         line = (line + 1) % 10;
368 //      move(statusYPos - 20 + line, statusXPos);
369         move(statusYPos + 2 + line, 1);
370         clrtoeol();
371 } //Message
372
373 ExtFunc void ShowTime(void)
374 { //display timer
375         move(statusYPos, statusXPos);
376         printw("Timer: %.0f ", CurTimeval() / 1e6);
377         move(boardYPos[0] + 1, boardXPos[0] + 2 * Players[0].boardWidth + 1);
378 //      refresh();
379 } //ShowTime
380
381 ExtFunc void ScheduleFullRedraw(void)
382 {
383         touchwin(stdscr);
384 } //ScheduleFullRedraw
385
386 static MyEventType KeyGenFunc(EventGenRec *gen, MyEvent *event)
387 { //read keypresses
388         if (MyRead(gen->fd, &event->u.key, 1))
389                 return E_key;
390         else
391                 return E_none;
392 } //KeyGenFunc
393
394 /*
395  * vi: ts=4 ai
396  * vim: noai si
397  */