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.
22 #include <sys/types.h>
34 #ifdef NCURSES_VERSION
38 window_t window[MAX_SCREENS];
40 static MyEventType KeyGenFunc(EventGenRec *gen, MyEvent *event);
41 static EventGenRec keyGen = {
42 NULL, 0, FT_read, STDIN_FILENO, KeyGenFunc, EM_key
45 static int statusYPos, statusXPos;
46 static int messageYPos, messageXPos, messageHeight, messageWidth;
50 #define MSG_HEIGHT 64 //max history
51 static char *message[MSG_HEIGHT];
52 static char messages[MSG_HEIGHT][MSG_WIDTH];
54 static char *term_vi; /* String to make cursor invisible */
55 static char *term_ve; /* String to make cursor visible */
57 void InitScreens(void)
64 * Block signals while initializing curses. Otherwise a badly timed
65 * Ctrl-C during initialization might leave the terminal in a bad state.
67 BlockSignals(&oldMask, SIGINT, 0);
68 initscr(); //start curses
79 haveColor = Sets.color && has_colors();
85 { BT_T, COLOR_WHITE },
87 { BT_O, COLOR_MAGENTA },
89 { BT_J, COLOR_YELLOW },
90 { BT_S, COLOR_GREEN },
97 if (can_change_color()) {
98 init_color (COLOR_YELLOW, 1000, 1000, 0);
99 } //I've never worked on a color-changable terminal, so no idea..
100 for (i = 0; myColorTable[i].type != BT_none; ++i)
101 init_pair(myColorTable[i].type, COLOR_BLACK,
102 myColorTable[i].color);
108 AtExit(CleanupScreens); //restore everything when done
109 RestoreSignals(NULL, &oldMask);
111 cbreak(); //no line buffering
113 // keypad(stdscr, TRUE); //get arrow/functionkeys 'n stuff
114 OutputTermStr(term_vi, 0);
115 AddEventGen(&keyGen); //key handler
116 signal(SIGWINCH, CatchWinCh); //handle window resize
117 // ioctl(STDIN_FILENO, KDSKBMODE, K_MEDIUMRAW);
118 standend(); //normal text
120 memset(messages, 0, sizeof(messages)); //empty messages
123 for (i = 0; i<MSG_HEIGHT; i++)
124 message[i] = messages[i]; //set pointers
128 void CleanupScreens(void)
130 RemoveEventGen(&keyGen);
131 endwin(); //end curses
132 OutputTermStr(term_ve, 1);
135 static void GetTermcapInfo(void)
137 char *term, *buf, *data;
141 if (!(term = getenv("TERM")))
143 if (tgetent(scratch, term) == 1) {
145 * Make the buffer HUGE, since tgetstr is unsafe.
146 * Allocate it on the heap too.
148 data = buf = malloc(bufSize);
151 * There is no standard include file for tgetstr, no prototype
152 * definitions. I like casting better than using my own prototypes
153 * because if I guess the prototype, I might be wrong, especially
154 * with regards to "const".
156 term_vi = (char *)tgetstr("vi", &data);
157 term_ve = (char *)tgetstr("ve", &data);
159 /* Okay, so I'm paranoid; I just don't like unsafe routines */
160 if (data > buf + bufSize)
161 fatal("tgetstr overflow, you must have a very sick termcap");
163 /* Trim off the unused portion of buffer */
164 buf = realloc(buf, data - buf);
168 * If that fails, use hardcoded vt220 codes.
169 * They don't seem to do anything bad on vt100's, so
170 * we'll try them just in case they work.
172 if (!term_vi || !term_ve) {
173 static char *vts[] = {
174 "vt100", "vt101", "vt102",
175 "vt200", "vt220", "vt300",
176 "vt320", "vt400", "vt420",
177 "screen", "xterm", NULL
181 for (i = 0; vts[i]; i++)
182 if (!strcmp(term, vts[i])) {
183 term_vi = "\033[?25l";
184 term_ve = "\033[?25h";
188 if (!term_vi || !term_ve)
189 term_vi = term_ve = NULL;
192 static void OutputTermStr(char *str, int flush)
196 if (flush) fflush(stdout);
200 static void DrawTitle(void)
210 getmaxyx(stdscr, rows, cols);
211 s = malloc(cols + 1);
212 sprintf(s, " " MSG_TITLE " %s", version_string);
213 const int titlelen = strlen(s);
214 memset(&s[titlelen], ' ', cols - titlelen); // pad
215 if (cols > titlelen + 1 + strlen(MSG_TITLESUB))
216 memcpy(&s[cols - 1 - strlen(MSG_TITLESUB)], MSG_TITLESUB, sizeof(MSG_TITLESUB) - 1);
217 memcpy(&s[cols], "\0", 1);
220 standend(); //normal text
223 static void window_border(int x1, int y1, int x2, int y2)
227 for (y = y1 + 1; y < y2; y++) {
228 mvaddch(y, x1, Sets.ascii ? '|' : ACS_VLINE); //left
229 mvaddch(y, x2, Sets.ascii ? '|' : ACS_VLINE); //right
230 } //draw vertical lines
232 addch(Sets.ascii ? '+' : ACS_ULCORNER);
233 for (x = x1 + 1; x < x2; x++)
234 addch(Sets.ascii ? '-' : ACS_HLINE);
235 addch(Sets.ascii ? '+' : ACS_URCORNER);
236 move(y2, x1); //bottom
237 addch(Sets.ascii ? '+' : ACS_LLCORNER);
238 for (x = x1 + 1; x < x2; x++)
239 addch(Sets.ascii ? '-' : ACS_HLINE);
240 addch(Sets.ascii ? '+' : ACS_LRCORNER);
243 void window_draw(int player)
244 { //draw field for player
245 if (!window[player].shown) return;
246 window_border(window[player].posx - 1, window[player].posy - Players[player].boardVisible,
247 window[player].posx + window[player].size * Players[player].boardWidth, window[player].posy + 1);
249 char s[window[player].size * Players[player].boardWidth + 1];
251 if (Players[player].host && Players[player].host[0])
252 snprintf(s, sizeof(s), " %s <%s> ",
253 Players[player].name, Players[player].host);
254 else snprintf(s, sizeof(s), " %s ", Players[player].name);
256 if (haveColor && Players[player].team > 0 && Players[player].team <= 7)
257 attrset(A_REVERSE | COLOR_PAIR(Players[player].team + 1));
258 mvaddstr(1, window[player].posx, s);
259 if (haveColor) standend();
260 } //display playername/host
264 for (y = 0; y <= Players[player].boardVisible; y++)
265 for (x = 0; x <= Players[player].boardWidth; x++)
266 block_draw_window(player, y, x, block_get(player, y, x));
269 window_msg_status(player);
272 void screen_setup(void)
273 { //calculate positions of all fields
280 getmaxyx(stdscr, y, x);
283 window[me].posy = 21;
284 window[me].shown = 1;
285 statusXPos = window[me].size * Players[me].boardWidth + 3;
287 status_draw(me, Players[me].score);
291 messageWidth = MIN(x - messageXPos - 2, MSG_WIDTH);
292 messageHeight = MIN(y - messageYPos - 1, MSG_HEIGHT);
293 if (messageHeight < 3) {
294 messageWidth = MIN(x - statusXPos - 18, 27);
295 messageHeight = y - 3;
296 messageXPos = statusXPos + 16;
298 } //messagebox doesn't fit below
299 window_border(messageXPos - 2, messageYPos - 1,
300 messageXPos + messageWidth + 1, messageYPos+messageHeight);
301 if (msgwin = subwin(stdscr, messageHeight, messageWidth,
302 messageYPos, messageXPos))
303 scrollok(msgwin, 1); //allow scrolling
304 wmove(msgwin, messageHeight - 2, 0);
305 for (i = messageHeight - 2; i >= 0; i--) //display message history
306 msg_draw(message[i]);
309 for (i = 1; i <= maxPlayer; i++)
310 spaceavail -= Players[i].boardWidth+2;
312 for (i = 1; i < MAX_SCREENS; i++) if (i != me) {
315 window[prev].posx + 2 + window[prev].size * Players[prev].boardWidth;
317 window[i].posx += 15; //scorebar
318 if (messageYPos < 24)
319 window[i].posx += messageWidth + 4; //messagebox
320 spaceavail -= window[i].posx - 3;
321 } //stuff before second player
322 if (spaceavail >= 0) {
324 spaceavail -= Players[i].boardWidth;
325 } //not enough space, half width
328 if (x < window[i].posx + 1 + window[i].size * Players[i].boardWidth)
329 window[i].shown = 0; //field doesn't fit on screen
334 for (i = 1; i <= maxPlayer; i++)
338 static void msg_draw(char *p)
344 memcpy(s, p, sizeof(s)-1);
347 while (psearch = strchr(p, '\\')) {
350 c = atoi(++psearch) + 1;
351 if (haveColor) wattrset(msgwin, A_REVERSE | COLOR_PAIR(c));
353 } //search for color escapes (\)
355 if (haveColor) wstandend(msgwin);
356 waddch(msgwin, '\n');
359 void msg_add(char *fmt, ...)
360 { //print game/bot message
366 if (!messageHeight) return;
368 vsnprintf(s, sizeof(s), fmt, args);
370 p = message[MSG_HEIGHT - 1]; //save last pointer
371 for (i = MSG_HEIGHT - 1; i > 0; i--)
372 message[i] = message[i - 1]; //scroll history
376 wmove(msgwin, messageHeight - 1, 0);
382 void msg_add_char(char c, int x, char *s)
383 { //show single typed character
385 mvwaddch(msgwin, messageHeight-1, (x+1) % (messageWidth-1), ' ');
388 if (c == 13 || c == 127) //enter/backspace
389 mvwaddch(msgwin, messageHeight - 1, (x+2) % (messageWidth-1),
390 x >= messageWidth-3 ? s[x - messageWidth + 3] : ' ');
392 mvwaddch(msgwin, messageHeight - 1, x % (messageWidth-1), c);
393 mvwaddch(msgwin, messageHeight - 1, (x+1) % (messageWidth-1), '_');
398 static void block_draw_2(int y, int x, unsigned char type)
399 { //display block on screen
401 if (type == BT_none) addstr(" ");
402 else if (type == BT_shadow) addstr("::");
406 if (haveColor) attrset(COLOR_PAIR(type & 15));
407 else attrset(A_REVERSE);
410 switch (Sets.drawstyle) {
412 switch (type & 192) {
413 case 64: //right neighbour
419 } //horizontal stickiness
420 break; //ascii horizontally grouped
422 switch (type & 240) {
424 addstr("||"); break; //middle
425 case 64: case 80: case 96:
426 addstr("[="); break; //left end
429 case 128: case 144: case 160:
430 addstr("=]"); break; //right end
433 case 192: case 208: case 224:
436 addstr("[]"); break; //top/bottom/mid
438 break; //ascii semi-grouped
440 switch (type & 240) {
441 case 16: addch(ACS_ULCORNER); addch(ACS_URCORNER); break;//top end
442 case 32: addch(ACS_LLCORNER); addch(ACS_LRCORNER); break;//bottom end
443 case 48: addch(ACS_VLINE); addch(ACS_VLINE); break; //vertical middle
444 case 64: addch('['); addch(ACS_HLINE); break; //left end
445 case 80: addch(ACS_ULCORNER); addch(ACS_TTEE); break; //top left corner
446 case 96: addch(ACS_LLCORNER); addch(ACS_BTEE); break; //bottom left corner
447 case 112: addch(ACS_LTEE); addch(ACS_PLUS); break; //vertical+right
448 case 128: addch(ACS_HLINE); addch(']'); break; //right end
449 case 144: addch(ACS_TTEE); addch(ACS_URCORNER); break; //top right corner
450 case 160: addch(ACS_BTEE); addch(ACS_LRCORNER); break; //bottom right corner
451 case 176: addch(ACS_PLUS); addch(ACS_RTEE); break; //vertical+left
452 case 192: addch(ACS_HLINE); addch(ACS_HLINE); break; //horizontal middle
453 case 208: addch(ACS_TTEE); addch(ACS_TTEE); break; //horizontal+down
454 case 224: addch(ACS_BTEE); addch(ACS_BTEE); break; //horizontal+up
455 default: addstr("[]"); break;
457 break; //curses grouped
460 break; //ascii non-grouped
463 if (Sets.standout) standend();
465 } //display one brick
468 static void block_draw_1(int y, int x, unsigned char type)
469 { //display block small
471 if (type == BT_none) addch(' ');
472 else if (type == BT_shadow) addch(':');
477 attrset(COLOR_PAIR(type & 15));
478 else attrset(A_REVERSE);
481 if ((type & 192) == 64)
483 else if ((type & 192) == 128)
488 if (Sets.standout) standend();
490 } //display one brick
493 void block_draw_window(int player, int y, int x, unsigned char type)
495 if (y >= 0 && y < Players[player].boardVisible
496 && x >= 0 && x < Players[player].boardWidth) {
497 if (window[player].size > 1)
498 block_draw_2(window[player].posy - y, window[player].posx + 2*x, type);
500 block_draw_1(window[player].posy - y, window[player].posx + x, type);
503 void block_draw_status(int y, int x, unsigned char type)
504 { //Draw block at specified position next to field
505 block_draw_2(20 - y, 2 * x, type);
508 void status_draw(int player, struct score_t score)
512 mvaddstr(13, statusXPos, MSG_NEXT " ");
513 mvaddstr(14, statusXPos + 5, " ");
514 shape_iterate(Players[player].nextShape, player, 8,
515 statusXPos/2 + (Players[player].nextShape/4 == 5 ? 3 : 4),
516 block_iter_set_status); //draw; BT_I one more to the left
517 mvprintw(3, statusXPos, MSG_LEVEL, score.level);
518 mvprintw(5, statusXPos, MSG_SCORE, score.score);
519 mvprintw(6, statusXPos, MSG_LINES, score.lines);
520 timer = CurTimeval() / 1e6;
522 mvprintw(9, statusXPos, MSG_PPM, score.pieces * 60 / timer);
523 if (score.lines > 0) {
524 mvprintw(7, statusXPos, MSG_YIELD, 100 * score.adds / score.lines);
525 mvprintw(10, statusXPos, MSG_APM, score.adds * 60 / timer);
530 for (i = 7; i <= 10; i++)
531 mvaddstr(i, statusXPos, " ");
532 } //too early to display stats, remove old..
535 void window_msg(int player, char *message)
536 { //put a message over player's field
537 if (!window[player].shown) return;
539 const int fieldsize = Players[player].boardWidth * window[player].size;
540 const int centered = (fieldsize - strlen(message)) / 2;
541 char s[fieldsize + 1];
543 memset(s, ' ', fieldsize);
544 memcpy(&s[centered], message, strlen(message));
551 mvprintw(window[player].posy - Players[player].boardVisible / 2,
552 window[player].posx, "%s", s);
557 y = Players[player].boardVisible / 2;
558 for (x = 0; x <= Players[player].boardWidth; x++)
559 block_draw_window(player, y, x, block_get(player, y, x));
563 void window_msg_wide(int player, char *message)
566 char *messagewide = malloc(strlen(message) * 2); // max += strlen - 1
567 const int fieldsize = Players[player].boardWidth * window[player].size;
569 const bool sep = strchr(message, ' ') != NULL;
570 // whitespace to pad at convenience
571 const bool pad = strlen(message) * 2 - sep <= fieldsize;
572 // (space to) put whitespace between all characters
573 bool odd = fieldsize & 1;
574 // odd number of characters (center off; try to change padding at sep)
575 if (!pad) odd ^= strlen(message) & 1;
576 // for odd strings, check for even fieldsize instead
578 if (pad || (sep && odd && strlen(message) < fieldsize)) {
579 // generate padded message in messagewide
580 for (i = 0; ; message++) {
581 messagewide[i++] = *message;
582 if (message[1] == 0) {
586 if (pad ? (*message != ' ' || odd) : (*message == ' ' && odd)) {
587 // add padding if wide; different padding at space if odd
588 messagewide[i++] = ' ';
592 message = messagewide;
594 window_msg(player, message);
597 void window_msg_status(int player)
598 { //put status (pause, readiness, game over) over player's field
599 if (Players[player].alive > 0)
600 if (Players[player].flags & SCF_paused)
601 if (Game.started > 1)
602 window_msg_wide(player, MSG_PLAYER_PAUSE);
604 window_msg_wide(player, MSG_PLAYER_JOIN);
606 if (Game.started > 1)
607 window_msg(player, NULL);
609 window_msg_wide(player, MSG_PLAYER_START);
610 else if (!Players[player].alive)
611 window_msg_wide(player, MSG_PLAYER_STOP);
613 window_msg_wide(player, MSG_PLAYER_PART);
617 void status_tick(void)
619 mvprintw(statusYPos, statusXPos, MSG_TIME, CurTimeval() / 1e6);
622 void ScheduleFullRedraw(void)
627 static void CatchWinCh(int sig)
628 { //handle window resize
629 endwin(); //exit curses
630 refresh(); //and reinit display (with different sizes)
631 screen_setup();//manually redraw everything
635 static MyEventType KeyGenFunc(EventGenRec *gen, MyEvent *event)
637 if (MyRead(gen->fd, &event->u.key, 1))