2 * Netris -- A free networked version of T*tris
3 * Copyright (C) 1994-1996,1999 Mark H. Weaver <mhw@netris.org>
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.
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.
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.
19 * $Id: curses.c,v 1.33 1999/05/16 06:56:25 mhw Exp $
24 #include <sys/types.h>
36 #ifdef NCURSES_VERSION
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 };
45 static int boardYPos[MAX_SCREENS], boardXPos[MAX_SCREENS];
46 static int boardSize[MAX_SCREENS];
48 static int statusYPos, statusXPos;
49 static int messageYPos, messageXPos, messageHeight, messageWidth;
52 int PlayerDisp[MAX_SCREENS];
54 #define MSG_HEIGHT 64 //max history
55 char *message[MSG_HEIGHT];
56 char messages[MSG_HEIGHT][MSG_WIDTH];
58 static char *term_vi; /* String to make cursor invisible */
59 static char *term_ve; /* String to make cursor visible */
61 void InitScreens(void)
68 * Block signals while initializing curses. Otherwise a badly timed
69 * Ctrl-C during initialization might leave the terminal in a bad state.
71 BlockSignals(&oldMask, SIGINT, 0);
72 initscr(); //start curses
83 haveColor = Sets.color && has_colors();
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 },
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);
112 AtExit(CleanupScreens); //restore everything when done
113 RestoreSignals(NULL, &oldMask);
115 cbreak(); //no line buffering
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
124 memset(messages, 0, sizeof(messages)); //empty messages
127 for (i = 0; i<MSG_HEIGHT; i++)
128 message[i] = messages[i]; //set pointers
132 void CleanupScreens(void)
134 RemoveEventGen(&keyGen);
135 endwin(); //end curses
136 OutputTermStr(term_ve, 1);
139 void GetTermcapInfo(void)
141 char *term, *buf, *data;
145 if (!(term = getenv("TERM")))
147 if (tgetent(scratch, term) == 1) {
149 * Make the buffer HUGE, since tgetstr is unsafe.
150 * Allocate it on the heap too.
152 data = buf = malloc(bufSize);
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".
160 term_vi = (char *)tgetstr("vi", &data);
161 term_ve = (char *)tgetstr("ve", &data);
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");
167 /* Trim off the unused portion of buffer */
168 buf = realloc(buf, data - buf);
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.
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 };
184 for (i = 0; vts[i]; i++)
185 if (!strcmp(term, vts[i]))
187 term_vi = "\033[?25l";
188 term_ve = "\033[?25h";
192 if (!term_vi || !term_ve)
193 term_vi = term_ve = NULL;
196 void OutputTermStr(char *str, int flush)
200 if (flush) fflush(stdout);
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);
222 standend(); //normal text
225 void DrawBox(int x1, int y1, int x2, int y2)
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
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);
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);
251 char s[boardSize[scr]*Players[scr].boardWidth+1];
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);
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
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));
274 void InitFields(void)
275 { //calculate positions of all fields
282 getmaxyx(stdscr, y, x);
287 statusXPos = boardSize[me] * Players[me].boardWidth + 3;
289 ShowScore(me, Players[me].score);
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) {
297 messageHeight = y - 3;
298 messageXPos = statusXPos + 16;
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]);
311 for (scr = 1; scr <= maxPlayer; scr++)
312 spaceavail -= Players[scr].boardWidth+2;
314 for (scr = 1; scr < MAX_SCREENS; scr++) if (scr != me) {
317 boardXPos[prevscr] + 2 + boardSize[prevscr] * Players[prevscr].boardWidth;
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) {
326 spaceavail -= Players[scr].boardWidth;
327 } //not enough space, half width
330 if (x < boardXPos[scr] + 1 + boardSize[scr] * Players[scr].boardWidth)
331 PlayerDisp[scr] = 0; //field doesn't fit on screen
336 for (scr = 1; scr <= maxPlayer; scr++)
340 void CleanupScreen(int scr)
344 void DisplayMessage(char *p)
350 memcpy(s, p, sizeof(s)-1);
353 while (psearch = strchr(p, '\\')) {
356 c = atoi(++psearch)+1;
357 if (haveColor) wattrset(msgwin, A_REVERSE | COLOR_PAIR(c));
359 } //search for color escapes (\)
361 if (haveColor) wstandend(msgwin);
362 waddch(msgwin, '\n');
365 void Message(char *fmt, ...)
366 { //print game/bot message
372 if (!messageHeight) return;
374 vsnprintf(s, sizeof(s), fmt, 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
382 wmove(msgwin, messageHeight - 1, 0);
388 void Messagetype(char c, int x, char *s)
389 { //show single typed character
391 mvwaddch(msgwin, messageHeight-1, (x+1) % (messageWidth-1), ' ');
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] : ' ');
398 mvwaddch(msgwin, messageHeight - 1, x % (messageWidth-1), c);
399 mvwaddch(msgwin, messageHeight - 1, (x+1) % (messageWidth-1), '_');
404 void PlotBlock1(int y, int x, unsigned char type)
405 { //display block on screen
407 if (type == BT_none) addstr(" ");
408 else if (type == BT_shadow) addstr("::");
412 if (haveColor) attrset(COLOR_PAIR(type & 15));
413 else attrset(A_REVERSE);
416 switch (Sets.drawstyle) {
418 switch (type & 192) {
419 case 64: //right neighbour
428 } //horizontal stickiness
429 break; //ascii horizontally grouped
431 switch (type & 240) {
433 addstr("||"); break; //middle
434 case 64: case 80: case 96:
435 addstr("[="); break; //left end
438 case 128: case 144: case 160:
439 addstr("=]"); break; //right end
442 case 192: case 208: case 224:
445 addstr("[]"); break; //top/bottom/mid
447 break; //ascii semi-grouped
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;
466 break; //curses grouped
469 break; //ascii non-grouped
472 if (Sets.standout) standend();
474 } //display one brick
476 void PlotBlock1S(int y, int x, unsigned char type)
477 { //display block small
479 if (type == BT_none) addch(' ');
480 else if (type == BT_shadow) addch(':');
485 attrset(COLOR_PAIR(type & 15));
486 else attrset(A_REVERSE);
489 if ((type & 192) == 64)
491 else if ((type & 192) == 128)
496 if (Sets.standout) standend();
498 } //display one brick
500 void PlotBlock(int scr, int y, int x, unsigned char type)
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);
507 PlotBlock1S(boardYPos[scr] - y, boardXPos[scr] + x, type);
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);
515 void ShowScore(int scr, struct _Score score)
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;
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);
537 for (i = 7; i <= 10; i++)
538 mvaddstr(i, statusXPos, " ");
539 } //too early to display stats, remove old..
542 void FieldMessage(int playa, char *message)
543 { //put a message over playa's field
544 if (!PlayerDisp[playa]) return;
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;
556 mvprintw(boardYPos[playa] - Players[playa].boardVisible / 2,
557 boardXPos[playa], "%s", s);
562 y = Players[playa].boardVisible / 2;
563 for (x = 0; x <= Players[playa].boardWidth; x++)
564 PlotBlock(playa, y, x, GetBlock(playa, y, x));
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");
576 boardSize[playa] > 1 ? "N O T R E A D Y" : "NOT READY");
578 if (Game.started > 1)
579 FieldMessage(playa, NULL);
581 FieldMessage(playa, boardSize[playa] > 1 ? "R E A D Y" : "READY");
582 else if (!Players[playa].alive)
584 boardSize[playa] > 1 ? "G A M E O V E R" : "GAME OVER");
586 FieldMessage(playa, boardSize[playa] > 1 ? "E M P T Y" : "EMPTY");
592 mvprintw(statusYPos, statusXPos, "timer %7.0f ", CurTimeval() / 1e6);
595 void ScheduleFullRedraw(void)
598 } //ScheduleFullRedraw
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
608 static MyEventType KeyGenFunc(EventGenRec *gen, MyEvent *event)
610 if (MyRead(gen->fd, &event->u.key, 1))