1 /* Tetrinet for Linux, by Andrew Church <achurch@achurch.org>
2  * This program is public domain.
3  *
4  * Text terminal I/O routines.
5  */
6 
7 #define _GNU_SOURCE /* strsignal() - FIXME!!! --pasky */
8 
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <unistd.h>
13 #include <ctype.h>
14 #include <curses.h>
15 #include <errno.h>
16 #include <signal.h>
17 #include <sys/time.h>
18 #include "tetrinet.h"
19 #include "tetris.h"
20 #include "io.h"
21 
22 /*************************************************************************/
23 
24 #define MY_HLINE	(fancy ? ACS_HLINE : '-')
25 #define MY_VLINE	(fancy ? ACS_VLINE : '|')
26 #define MY_ULCORNER	(fancy ? ACS_ULCORNER : '+')
27 #define MY_URCORNER	(fancy ? ACS_URCORNER : '+')
28 #define MY_LLCORNER	(fancy ? ACS_LLCORNER : '+')
29 #define MY_LRCORNER	(fancy ? ACS_LRCORNER : '+')
30 
31 #define MY_HLINE2	(fancy ? (ACS_HLINE | A_BOLD) : '=')
32 #define MY_BOLD		(fancy ? A_BOLD : 0)
33 
34 /*************************************************************************/
35 /******************************* Input stuff *****************************/
36 /*************************************************************************/
37 
38 /* Return either an ASCII code 0-255, a K_* value, or -1 if server input is
39  * waiting.  Return -2 if we run out of time with no input.
40  */
41 
wait_for_input(int msec)42 static int wait_for_input(int msec)
43 {
44     fd_set fds;
45     struct timeval tv;
46     int c;
47     static int escape = 0;
48 
49     FD_ZERO(&fds);
50     FD_SET(0, &fds);
51     FD_SET(server_sock, &fds);
52     tv.tv_sec = msec/1000;
53     tv.tv_usec = (msec*1000) % 1000000;
54     while (select(server_sock+1, &fds, NULL, NULL, msec<0 ? NULL : &tv) < 0) {
55 	if (errno != EINTR)
56 	    perror("Warning: select() failed");
57     }
58     if (FD_ISSET(0, &fds)) {
59 	c = getch();
60 	if (!escape && c == 27) {	/* Escape */
61 	    escape = 1;
62 	    c = wait_for_input(1000);
63 	    escape = 0;
64 	    if (c < 0)
65 		return 27;
66 	    else
67 		return c;
68 	}
69 	if (c == KEY_UP)
70 	    return K_UP;
71 	else if (c == KEY_DOWN)
72 	    return K_DOWN;
73 	else if (c == KEY_LEFT)
74 	    return K_LEFT;
75 	else if (c == KEY_RIGHT)
76 	    return K_RIGHT;
77 	else if (c == KEY_F(1) || c == ('1'|0x80) || (escape && c == '1'))
78 	    return K_F1;
79 	else if (c == KEY_F(2) || c == ('2'|0x80) || (escape && c == '2'))
80 	    return K_F2;
81 	else if (c == KEY_F(3) || c == ('3'|0x80) || (escape && c == '3'))
82 	    return K_F3;
83 	else if (c == KEY_F(4) || c == ('4'|0x80) || (escape && c == '4'))
84 	    return K_F4;
85 	else if (c == KEY_F(5) || c == ('5'|0x80) || (escape && c == '5'))
86 	    return K_F5;
87 	else if (c == KEY_F(6) || c == ('6'|0x80) || (escape && c == '6'))
88 	    return K_F6;
89 	else if (c == KEY_F(7) || c == ('7'|0x80) || (escape && c == '7'))
90 	    return K_F7;
91 	else if (c == KEY_F(8) || c == ('8'|0x80) || (escape && c == '8'))
92 	    return K_F8;
93 	else if (c == KEY_F(9) || c == ('9'|0x80) || (escape && c == '9'))
94 	    return K_F9;
95 	else if (c == KEY_F(10) || c == ('0'|0x80) || (escape && c == '0'))
96 	    return K_F10;
97 	else if (c == KEY_F(11))
98 	    return K_F11;
99 	else if (c == KEY_F(12))
100 	    return K_F12;
101 	else if (c == KEY_BACKSPACE)
102 	    return 8;
103 	else if (c >= 0x0100)
104 	    return K_INVALID;
105 	else if (c == 7)   /* ^G */
106 	    return 27;  /* Escape */
107 	else
108 	    return c;
109     } /* if (FD_ISSET(0, &fds)) */
110     else if (FD_ISSET(server_sock, &fds))
111 	return -1;
112     else
113 	return -2;	/* out of time */
114 }
115 
116 /*************************************************************************/
117 /****************************** Output stuff *****************************/
118 /*************************************************************************/
119 
120 /* Size of the screen */
121 static int scrwidth, scrheight;
122 
123 /* Is color available? */
124 static int has_color;
125 
126 /*************************************************************************/
127 
128 /* Text buffers: */
129 
130 typedef struct {
131     int x, y, width, height;
132     int line;
133     WINDOW *win;	/* NULL if not currently displayed */
134     char **text;
135 } TextBuffer;
136 
137 static TextBuffer plinebuf, gmsgbuf, attdefbuf;
138 
139 /*************************************************************************/
140 
141 /* Window for typing in-game text, and its coordinates: */
142 
143 static WINDOW *gmsg_inputwin;
144 static int gmsg_inputpos, gmsg_inputheight;
145 
146 /*************************************************************************/
147 /*************************************************************************/
148 
149 /* Clean up the screen on exit. */
150 
screen_cleanup()151 static void screen_cleanup()
152 {
153     wmove(stdscr, scrheight-1, 0);
154     wrefresh(stdscr);
155     endwin();
156     printf("\n");
157 }
158 
159 /*************************************************************************/
160 
161 /* Little signal handler that just does an exit(1) (thereby getting our
162  * cleanup routine called), except for TSTP, which does a clean suspend.
163  */
164 
165 static void (*old_tstp)(int sig);
166 
sighandler(int sig)167 static void sighandler(int sig)
168 {
169     if (sig != SIGTSTP) {
170 	endwin();
171 	if (sig != SIGINT)
172 	    fprintf(stderr, "%s\n", strsignal(sig));
173 	exit(1);
174     }
175     endwin();
176     signal(SIGTSTP, old_tstp);
177     raise(SIGTSTP);
178     doupdate();
179     signal(SIGTSTP, sighandler);
180 }
181 
182 /*************************************************************************/
183 /*************************************************************************/
184 
185 #define MAXCOLORS	256
186 
187 static int colors[MAXCOLORS][2] = { {-1,-1} };
188 
189 /* Return a color attribute value. */
190 
getcolor(int fg,int bg)191 static long getcolor(int fg, int bg)
192 {
193     int i;
194 
195     if (colors[0][0] < 0) {
196 	start_color();
197 	memset(colors, -1, sizeof(colors));
198 	colors[0][0] = COLOR_WHITE;
199 	colors[0][1] = COLOR_BLACK;
200     }
201     if (fg == COLOR_WHITE && bg == COLOR_BLACK)
202 	return COLOR_PAIR(0);
203     for (i = 1; i < MAXCOLORS; i++) {
204 	if (colors[i][0] == fg && colors[i][1] == bg)
205 	    return COLOR_PAIR(i);
206     }
207     for (i = 1; i < MAXCOLORS; i++) {
208 	if (colors[i][0] < 0) {
209 	    if (init_pair(i, fg, bg) == ERR)
210 		continue;
211 	    colors[i][0] = fg;
212 	    colors[i][1] = bg;
213 	    return COLOR_PAIR(i);
214 	}
215     }
216     return -1;
217 }
218 
219 /*************************************************************************/
220 /*************************************************************************/
221 
222 /* Set up the screen stuff. */
223 
screen_setup(void)224 static void screen_setup(void)
225 {
226     /* Avoid messy keyfield signals while we're setting up */
227     signal(SIGINT, SIG_IGN);
228     signal(SIGTSTP, SIG_IGN);
229 
230     initscr();
231     cbreak();
232     noecho();
233     nodelay(stdscr, TRUE);
234     keypad(stdscr, TRUE);
235     leaveok(stdscr, TRUE);
236     if ((has_color = has_colors()))
237 	start_color();
238     getmaxyx(stdscr, scrheight, scrwidth);
239     scrwidth--;  /* Don't draw in last column--this can cause scroll */
240 
241     /* Cancel all this when we exit. */
242     atexit(screen_cleanup);
243 
244     /* Catch signals so we can exit cleanly. */
245     signal(SIGINT, sighandler);
246     signal(SIGTERM, sighandler);
247     signal(SIGHUP, sighandler);
248     signal(SIGUSR1, sighandler);
249     signal(SIGUSR2, sighandler);
250     signal(SIGALRM, sighandler);
251     signal(SIGTSTP, sighandler);
252 #ifdef SIGXCPU
253     signal(SIGXCPU, sighandler);
254 #endif
255 #ifdef SIGXFSZ
256     signal(SIGXFSZ, sighandler);
257 #endif
258 
259     /* Broken pipes don't want to bother us at all. */
260     signal(SIGPIPE, SIG_IGN);
261 }
262 
263 /*************************************************************************/
264 
265 /* Redraw everything on the screen. */
266 
screen_refresh(void)267 static void screen_refresh(void)
268 {
269     if (gmsg_inputwin)
270 	touchline(stdscr, gmsg_inputpos, gmsg_inputheight);
271     if (plinebuf.win)
272 	touchline(stdscr, plinebuf.y, plinebuf.height);
273     if (gmsgbuf.win)
274 	touchline(stdscr, gmsgbuf.y, gmsgbuf.height);
275     if (attdefbuf.win)
276 	touchline(stdscr, attdefbuf.y, attdefbuf.height);
277     wnoutrefresh(stdscr);
278     doupdate();
279 }
280 
281 /*************************************************************************/
282 
283 /* Like screen_refresh(), but clear the screen first. */
284 
screen_redraw(void)285 static void screen_redraw(void)
286 {
287     clearok(stdscr, TRUE);
288     screen_refresh();
289 }
290 
291 /*************************************************************************/
292 /************************* Text buffer routines **************************/
293 /*************************************************************************/
294 
295 /* Put a line of text in a text buffer. */
296 
outline(TextBuffer * buf,const char * s)297 static void outline(TextBuffer *buf, const char *s)
298 {
299     if (buf->line == buf->height) {
300 	if (buf->win)
301 	    scroll(buf->win);
302 	memmove(buf->text, buf->text+1, (buf->height-1) * sizeof(char *));
303 	buf->line--;
304     }
305     if (buf->win)
306 	mvwaddstr(buf->win, buf->line, 0, s);
307     if (s != buf->text[buf->line])   /* check for restoring display */
308 	buf->text[buf->line] = strdup(s);
309     buf->line++;
310 }
311 
draw_text(int bufnum,const char * s)312 static void draw_text(int bufnum, const char *s)
313 {
314     char str[1024];	/* hopefully scrwidth < 1024 */
315     const char *t;
316     int indent = 0;
317     int x = 0, y = 0;
318     TextBuffer *buf;
319 
320     switch (bufnum) {
321 	case BUFFER_PLINE:  buf = &plinebuf;  break;
322 	case BUFFER_GMSG:   buf = &gmsgbuf;   break;
323 	case BUFFER_ATTDEF: buf = &attdefbuf; break;
324 	default: return;
325     }
326     if (!buf->text)
327 	return;
328     if (buf->win) {
329 	getyx(stdscr, y, x);
330 	attrset(getcolor(COLOR_WHITE, COLOR_BLACK));
331     }
332     while (*s && isspace(*s))
333 	s++;
334     while (strlen(s) > buf->width - indent) {
335 	t = s + buf->width - indent;
336 	while (t >= s && !isspace(*t))
337 	    t--;
338 	while (t >= s && isspace(*t))
339 	    t--;
340 	t++;
341 	if (t < s)
342 	    t = s + buf->width - indent;
343 	if (indent > 0)
344 	    sprintf(str, "%*s", indent, "");
345 	strncpy(str+indent, s, t-s);
346 	str[t-s+indent] = 0;
347 	outline(buf, str);
348 	indent = 2;
349 	while (isspace(*t))
350 	    t++;
351 	s = t;
352     }
353     if (indent > 0)
354 	sprintf(str, "%*s", indent, "");
355     strcpy(str+indent, s);
356     outline(buf, str);
357     if (buf->win) {
358 	move(y, x);
359 	screen_refresh();
360     }
361 }
362 
363 /*************************************************************************/
364 
365 /* Clear the contents of a text buffer. */
366 
clear_text(int bufnum)367 static void clear_text(int bufnum)
368 {
369     TextBuffer *buf;
370     int i;
371 
372     switch (bufnum) {
373 	case BUFFER_PLINE:  buf = &plinebuf;  break;
374 	case BUFFER_GMSG:   buf = &gmsgbuf;   break;
375 	case BUFFER_ATTDEF: buf = &attdefbuf; break;
376 	default: return;
377     }
378     if (buf->text) {
379 	for (i = 0; i < buf->height; i++) {
380 	    if (buf->text[i]) {
381 		free(buf->text[i]);
382 		buf->text[i] = NULL;
383 	    }
384 	}
385 	buf->line = 0;
386     }
387     if (buf->win) {
388 	werase(buf->win);
389 	screen_refresh();
390     }
391 }
392 
393 /*************************************************************************/
394 
395 /* Restore the contents of the given text buffer. */
396 
restore_text(TextBuffer * buf)397 static void restore_text(TextBuffer *buf)
398 {
399     buf->line = 0;
400     while (buf->line < buf->height && buf->text[buf->line])
401 	outline(buf, buf->text[buf->line]);
402 }
403 
404 /*************************************************************************/
405 
406 /* Open a window for the given text buffer. */
407 
open_textwin(TextBuffer * buf)408 static void open_textwin(TextBuffer *buf)
409 {
410     if (buf->height <= 0 || buf->width <= 0) {
411 	char str[256];
412 	move(scrheight-1, 0);
413 	snprintf(str, sizeof(str), "ERROR: bad textwin size (%d,%d)",
414 			buf->width, buf->height);
415 	addstr(str);
416 	exit(1);
417     }
418     if (!buf->win) {
419 	buf->win = subwin(stdscr, buf->height, buf->width, buf->y, buf->x);
420 	scrollok(buf->win, TRUE);
421     }
422     if (!buf->text)
423 	buf->text = calloc(buf->height, sizeof(char *));
424     else
425 	restore_text(buf);
426 }
427 
428 /*************************************************************************/
429 
430 /* Close the window for the given text buffer, if it's open. */
431 
close_textwin(TextBuffer * buf)432 static void close_textwin(TextBuffer *buf)
433 {
434     if (buf->win) {
435 	delwin(buf->win);
436 	buf->win = NULL;
437     }
438 }
439 
440 /*************************************************************************/
441 /************************ Field drawing routines *************************/
442 /*************************************************************************/
443 
444 /* Are we on a wide screen (>=92 columns)? */
445 static int wide_screen = 0;
446 
447 /* Field display X/Y coordinates. */
448 static const int own_coord[2] = {1,0};
449 static int other_coord[5][2] =	/* Recomputed based on screen width */
450     { {30,0}, {47,0}, {64,0}, {47,24}, {64,24} };
451 
452 /* Position of the status window. */
453 static const int status_coord[2]     = {29,25};
454 static const int next_coord[2]       = {41,24};
455 static const int alt_status_coord[2] = {29,2};
456 static const int alt_next_coord[2]   = {30,8};
457 
458 /* Position of the attacks/defenses window. */
459 static const int attdef_coord[2] = {28,28};
460 static const int alt_attdef_coord[2] = {28,24};
461 
462 /* Position of the text window.  X coordinate is ignored. */
463 static const int field_text_coord[2] = {0,47};
464 
465 /* Information for drawing blocks.  Color attributes are added to blocks in
466  * the setup_fields() routine. */
467 static int tile_chars[15] =
468     { ' ','#','#','#','#','#','a','c','n','r','s','b','g','q','o' };
469 
470 /* Are we redrawing the entire display? */
471 static int field_redraw = 0;
472 
473 /*************************************************************************/
474 /*************************************************************************/
475 
476 /* Set up the field display. */
477 
478 static void draw_own_field(void);
479 static void draw_other_field(int player);
480 static void draw_status(void);
481 static void draw_specials(void);
482 static void draw_gmsg_input(const char *s, int pos);
483 
setup_fields(void)484 static void setup_fields(void)
485 {
486     int i, j, x, y, base, delta, attdefbot;
487     char buf[32];
488 
489     if (!(tile_chars[0] & A_ATTRIBUTES)) {
490 	for (i = 1; i < 15; i++)
491 	    tile_chars[i] |= A_BOLD;
492 	tile_chars[1] |= getcolor(COLOR_BLUE, COLOR_BLACK);
493 	tile_chars[2] |= getcolor(COLOR_YELLOW, COLOR_BLACK);
494 	tile_chars[3] |= getcolor(COLOR_GREEN, COLOR_BLACK);
495 	tile_chars[4] |= getcolor(COLOR_MAGENTA, COLOR_BLACK);
496 	tile_chars[5] |= getcolor(COLOR_RED, COLOR_BLACK);
497     }
498 
499     field_redraw = 1;
500     leaveok(stdscr, TRUE);
501     close_textwin(&plinebuf);
502     clear();
503     attrset(getcolor(COLOR_WHITE,COLOR_BLACK));
504 
505     if (scrwidth >= 92) {
506 	wide_screen = 1;
507 	base = 41;
508     } else {
509 	base = 28;
510     }
511     delta = (scrwidth - base) / 3;
512     base += 2 + (delta - (FIELD_WIDTH+5)) / 2;
513     other_coord[0][0] = base;
514     other_coord[1][0] = base + delta;
515     other_coord[2][0] = base + delta*2;
516     other_coord[3][0] = base + delta;
517     other_coord[4][0] = base + delta*2;
518 
519     attdefbot = field_text_coord[1] - 1;
520     if (scrheight - field_text_coord[1] > 3) {
521 	move(field_text_coord[1], 0);
522 	hline(MY_HLINE2, scrwidth);
523 	attdefbot--;
524 	if (scrheight - field_text_coord[1] > 5) {
525 	    move(scrheight-2, 0);
526 	    hline(MY_HLINE2, scrwidth);
527 	    attrset(MY_BOLD);
528 	    move(scrheight-1, 0);
529 	    addstr("F1=Show Fields  F2=Partyline  F3=Winlist");
530 	    move(scrheight-1, scrwidth-8);
531 	    addstr("F10=Quit");
532 	    attrset(A_NORMAL);
533 	    gmsgbuf.y = field_text_coord[1]+1;
534 	    gmsgbuf.height = scrheight - field_text_coord[1] - 3;
535 	} else {
536 	    gmsgbuf.y = field_text_coord[1]+1;
537 	    gmsgbuf.height = scrheight - field_text_coord[1] - 1;
538 	}
539     } else {
540 	gmsgbuf.y = field_text_coord[1];
541 	gmsgbuf.height = scrheight - field_text_coord[1];
542     }
543     gmsgbuf.x = field_text_coord[0];
544     gmsgbuf.width = scrwidth;
545     open_textwin(&gmsgbuf);
546 
547     x = own_coord[0];
548     y = own_coord[1];
549     sprintf(buf, "%d", my_playernum);
550     mvaddstr(y, x-1, buf);
551     for (i = 2; i < FIELD_HEIGHT*2 && players[my_playernum-1][i-2]; i++)
552 	mvaddch(y+i, x-1, players[my_playernum-1][i-2]);
553     if (teams[my_playernum-1] != NULL) {
554 	mvaddstr(y, x+FIELD_WIDTH*2+2, "T");
555 	for (i = 2; i < FIELD_HEIGHT*2 && teams[my_playernum-1][i-2]; i++)
556 	    mvaddch(y+i, x+FIELD_WIDTH*2+2, teams[my_playernum-1][i-2]);
557     }
558     move(y, x);
559     vline(MY_VLINE, FIELD_HEIGHT*2);
560     move(y, x+FIELD_WIDTH*2+1);
561     vline(MY_VLINE, FIELD_HEIGHT*2);
562     move(y+FIELD_HEIGHT*2, x);
563     addch(MY_LLCORNER);
564     hline(MY_HLINE, FIELD_WIDTH*2);
565     move(y+FIELD_HEIGHT*2, x+FIELD_WIDTH*2+1);
566     addch(MY_LRCORNER);
567     mvaddstr(y+FIELD_HEIGHT*2+2, x, "Specials:");
568     draw_own_field();
569     draw_specials();
570 
571     for (j = 0; j < 5; j++) {
572 	x = other_coord[j][0];
573 	y = other_coord[j][1];
574 	move(y, x);
575 	vline(MY_VLINE, FIELD_HEIGHT);
576 	move(y, x+FIELD_WIDTH+1);
577 	vline(MY_VLINE, FIELD_HEIGHT);
578 	move(y+FIELD_HEIGHT, x);
579 	addch(MY_LLCORNER);
580 	hline(MY_HLINE, FIELD_WIDTH);
581 	move(y+FIELD_HEIGHT, x+FIELD_WIDTH+1);
582 	addch(MY_LRCORNER);
583 	if (j+1 >= my_playernum) {
584 	    sprintf(buf, "%d", j+2);
585 	    mvaddstr(y, x-1, buf);
586 	    if (players[j+1]) {
587 		for (i = 0; i < FIELD_HEIGHT-2 && players[j+1][i]; i++)
588 		    mvaddch(y+i+2, x-1, players[j+1][i]);
589 		if (teams[j+1] != NULL) {
590 		    mvaddstr(y, x+FIELD_WIDTH+2, "T");
591 		    for (i = 0; i < FIELD_HEIGHT-2 && teams[j+1][i]; i++)
592 			mvaddch(y+i+2, x+FIELD_WIDTH+2, teams[j+1][i]);
593 		}
594 	    }
595 	    draw_other_field(j+2);
596 	} else {
597 	    sprintf(buf, "%d", j+1);
598 	    mvaddstr(y, x-1, buf);
599 	    if (players[j]) {
600 		for (i = 0; i < FIELD_HEIGHT-2 && players[j][i]; i++)
601 		    mvaddch(y+i+2, x-1, players[j][i]);
602 		if (teams[j] != NULL) {
603 		    mvaddstr(y, x+FIELD_WIDTH+2, "T");
604 		    for (i = 0; i < FIELD_HEIGHT-2 && teams[j][i]; i++)
605 			mvaddch(y+i+2, x+FIELD_WIDTH+2, teams[j][i]);
606 		}
607 	    }
608 	    draw_other_field(j+1);
609 	}
610     }
611 
612     if (wide_screen) {
613 	x = alt_status_coord[0];
614 	y = alt_status_coord[1];
615 	mvaddstr(y, x, "Lines:");
616 	mvaddstr(y+1, x, "Level:");
617 	x = alt_next_coord[0];
618 	y = alt_next_coord[1];
619 	mvaddstr(y-2, x-1, "Next piece:");
620 	move(y-1, x-1);
621 	addch(MY_ULCORNER);
622 	hline(MY_HLINE, 8);
623 	mvaddch(y-1, x+8, MY_URCORNER);
624 	move(y, x-1);
625 	vline(MY_VLINE, 8);
626 	move(y, x+8);
627 	vline(MY_VLINE, 8);
628 	move(y+8, x-1);
629 	addch(MY_LLCORNER);
630 	hline(MY_HLINE, 8);
631 	mvaddch(y+8, x+8, MY_LRCORNER);
632     } else {
633 	x = status_coord[0];
634 	y = status_coord[1];
635 	mvaddstr(y-1, x, "Next piece:");
636 	mvaddstr(y, x, "Lines:");
637 	mvaddstr(y+1, x, "Level:");
638     }
639     if (playing_game)
640 	draw_status();
641 
642     attdefbuf.x = wide_screen ? alt_attdef_coord[0] : attdef_coord[0];
643     attdefbuf.y = wide_screen ? alt_attdef_coord[1] : attdef_coord[1];
644     attdefbuf.width = (other_coord[3][0]-1) - attdefbuf.x;
645     attdefbuf.height = (attdefbot+1) - attdefbuf.y;
646     open_textwin(&attdefbuf);
647 
648     if (gmsg_inputwin) {
649 	delwin(gmsg_inputwin);
650 	gmsg_inputwin = NULL;
651 	draw_gmsg_input(NULL, -1);
652     }
653 
654     screen_refresh();
655     field_redraw = 0;
656 }
657 
658 /*************************************************************************/
659 
660 /* Display the player's own field. */
661 
draw_own_field(void)662 static void draw_own_field(void)
663 {
664     int x, y, x0, y0;
665     Field *f = &fields[my_playernum-1];
666     int shadow[4] = { -1, -1, -1, -1 };
667 
668     if (dispmode != MODE_FIELDS)
669 	return;
670 
671     /* XXX: Code duplication with tetris.c:draw_piece(). --pasky */
672     if (playing_game && cast_shadow) {
673 	int y = current_y - piecedata[current_piece][current_rotation].hot_y;
674 	char *shape = (char *) piecedata[current_piece][current_rotation].shape;
675 	int i, j;
676 
677 	for (j = 0; j < 4; j++) {
678 	    if (y+j < 0) {
679 		shape += 4;
680 		continue;
681 	    }
682 	    for (i = 0; i < 4; i++) {
683 		if (*shape++)
684 		    shadow[i] = y + j;
685 	    }
686 	}
687     }
688 
689     x0 = own_coord[0]+1;
690     y0 = own_coord[1];
691     for (y = 0; y < 22; y++) {
692 	for (x = 0; x < 12; x++) {
693 	    int c = tile_chars[(int) (*f)[y][x]];
694 
695 	    if (playing_game && cast_shadow) {
696 		PieceData *piece = &piecedata[current_piece][current_rotation];
697 		int piece_x = current_x - piece->hot_x;
698 
699 		if (x >= piece_x && x <= piece_x + 3
700 			&& shadow[(x - piece_x)] >= 0
701 			&& shadow[(x - piece_x)] < y
702 			&& ((c & 0x7f) == ' ')) {
703 		    c = (c & (~0x7f)) | '.'
704 			| getcolor(COLOR_BLACK, COLOR_BLACK) | A_BOLD;
705 		}
706 	    }
707 
708 	    mvaddch(y0+y*2, x0+x*2, c);
709 	    addch(c);
710 	    mvaddch(y0+y*2+1, x0+x*2, c);
711 	    addch(c);
712 	}
713     }
714     if (gmsg_inputwin) {
715 	delwin(gmsg_inputwin);
716 	gmsg_inputwin = NULL;
717 	draw_gmsg_input(NULL, -1);
718     }
719     if (!field_redraw)
720 	screen_refresh();
721 }
722 
723 /*************************************************************************/
724 
725 /* Display another player's field. */
726 
draw_other_field(int player)727 static void draw_other_field(int player)
728 {
729     int x, y, x0, y0;
730     Field *f;
731 
732     if (dispmode != MODE_FIELDS)
733 	return;
734     f = &fields[player-1];
735     if (player > my_playernum)
736 	player--;
737     player--;
738     x0 = other_coord[player][0]+1;
739     y0 = other_coord[player][1];
740     for (y = 0; y < 22; y++) {
741 	move(y0+y, x0);
742 	for (x = 0; x < 12; x++) {
743 	    addch(tile_chars[(int) (*f)[y][x]]);
744 	}
745     }
746     if (gmsg_inputwin) {
747 	delwin(gmsg_inputwin);
748 	gmsg_inputwin = NULL;
749 	draw_gmsg_input(NULL, -1);
750     }
751     if (!field_redraw)
752 	screen_refresh();
753 }
754 
755 /*************************************************************************/
756 
757 /* Display the current game status (level, lines, next piece). */
758 
draw_status(void)759 static void draw_status(void)
760 {
761     int x, y, i, j;
762     char buf[32], shape[4][4];
763 
764     x = wide_screen ? alt_status_coord[0] : status_coord[0];
765     y = wide_screen ? alt_status_coord[1] : status_coord[1];
766     sprintf(buf, "%d", lines>99999 ? 99999 : lines);
767     mvaddstr(y, x+7, buf);
768     sprintf(buf, "%d", levels[my_playernum]);
769     mvaddstr(y+1, x+7, buf);
770     x = wide_screen ? alt_next_coord[0] : next_coord[0];
771     y = wide_screen ? alt_next_coord[1] : next_coord[1];
772     if (get_shape(next_piece, 0, shape) == 0) {
773 	for (j = 0; j < 4; j++) {
774 	    if (!wide_screen)
775 		move(y+j, x);
776 	    for (i = 0; i < 4; i++) {
777 		if (wide_screen) {
778 		    move(y+j*2, x+i*2);
779 		    addch(tile_chars[(int) shape[j][i]]);
780 		    addch(tile_chars[(int) shape[j][i]]);
781 		    move(y+j*2+1, x+i*2);
782 		    addch(tile_chars[(int) shape[j][i]]);
783 		    addch(tile_chars[(int) shape[j][i]]);
784 		} else
785 		    addch(tile_chars[(int) shape[j][i]]);
786 	    }
787 	}
788     }
789 }
790 
791 /*************************************************************************/
792 
793 /* Display the special inventory and description of the current special. */
794 
795 static const char *descs[] = {
796     "                    ",
797     "Add Line            ",
798     "Clear Line          ",
799     "Nuke Field          ",
800     "Clear Random Blocks ",
801     "Switch Fields       ",
802     "Clear Special Blocks",
803     "Block Gravity       ",
804     "Blockquake          ",
805     "Block Bomb          "
806 };
807 
draw_specials(void)808 static void draw_specials(void)
809 {
810     int x, y, i;
811 
812     if (dispmode != MODE_FIELDS)
813 	return;
814     x = own_coord[0];
815     y = own_coord[1]+45;
816     mvaddstr(y, x, descs[specials[0]+1]);
817     move(y+1, x+10);
818     i = 0;
819     while (i < special_capacity && specials[i] >= 0 && x < attdef_coord[0]-1) {
820 	addch(tile_chars[specials[i]+6]);
821 	i++;
822 	x++;
823     }
824     while (x < attdef_coord[0]-1) {
825 	addch(tile_chars[0]);
826 	x++;
827     }
828     if (!field_redraw)
829 	screen_refresh();
830 }
831 
832 /*************************************************************************/
833 
834 /* Display an attack/defense message. */
835 
836 static const char *msgs[][2] = {
837     { "cs1", "1 Line Added to All" },
838     { "cs2", "2 Lines Added to All" },
839     { "cs4", "4 Lines Added to All" },
840     { "a",   "Add Line" },
841     { "c",   "Clear Line" },
842     { "n",   "Nuke Field" },
843     { "r",   "Clear Random Blocks" },
844     { "s",   "Switch Fields" },
845     { "b",   "Clear Special Blocks" },
846     { "g",   "Block Gravity" },
847     { "q",   "Blockquake" },
848     { "o",   "Block Bomb" },
849     { NULL }
850 };
851 
draw_attdef(const char * type,int from,int to)852 static void draw_attdef(const char *type, int from, int to)
853 {
854     int i, width;
855     char buf[512];
856 
857     width = other_coord[4][0] - attdef_coord[0] - 1;
858     for (i = 0; msgs[i][0]; i++) {
859 	if (strcmp(type, msgs[i][0]) == 0)
860 	    break;
861     }
862     if (!msgs[i][0])
863 	return;
864     strcpy(buf, msgs[i][1]);
865     if (to != 0)
866 	sprintf(buf+strlen(buf), " on %s", players[to-1]);
867     if (from == 0)
868 	sprintf(buf+strlen(buf), " by Server");
869     else
870 	sprintf(buf+strlen(buf), " by %s", players[from-1]);
871     draw_text(BUFFER_ATTDEF, buf);
872 }
873 
874 /*************************************************************************/
875 
876 /* Display the in-game text window. */
877 
draw_gmsg_input(const char * s,int pos)878 static void draw_gmsg_input(const char *s, int pos)
879 {
880     static int start = 0;	/* Start of displayed part of input line */
881     static const char *last_s;
882     static int last_pos;
883 
884     if (s)
885 	last_s = s;
886     else
887 	s = last_s;
888     if (pos >= 0)
889 	last_pos = pos;
890     else
891 	pos = last_pos;
892 
893     attrset(getcolor(COLOR_WHITE,COLOR_BLACK));
894 
895     if (!gmsg_inputwin) {
896 	gmsg_inputpos = scrheight/2 - 1;
897 	gmsg_inputheight = 3;
898 	gmsg_inputwin =
899 		subwin(stdscr, gmsg_inputheight, scrwidth, gmsg_inputpos, 0);
900 	werase(gmsg_inputwin);
901 	leaveok(gmsg_inputwin, FALSE);
902 	leaveok(stdscr, FALSE);
903 	mvwaddstr(gmsg_inputwin, 1, 0, "Text>");
904     }
905 
906     if (strlen(s) < scrwidth-7) {
907 	start = 0;
908 	mvwaddstr(gmsg_inputwin, 1, 6, s);
909 	wmove(gmsg_inputwin, 1, 6+strlen(s));
910 	move(gmsg_inputpos+1, 6+strlen(s));
911 	wclrtoeol(gmsg_inputwin);
912 	wmove(gmsg_inputwin, 1, 6+pos);
913 	move(gmsg_inputpos+1, 6+pos);
914     } else {
915 	if (pos < start+8) {
916 	    start = pos-8;
917 	    if (start < 0)
918 		start = 0;
919 	} else if (pos > start + scrwidth-15) {
920 	    start = pos - (scrwidth-15);
921 	    if (start > strlen(s) - (scrwidth-7))
922 		start = strlen(s) - (scrwidth-7);
923 	}
924 	mvwaddnstr(gmsg_inputwin, 1, 6, s+start, scrwidth-6);
925 	wmove(gmsg_inputwin, 1, 6 + (pos-start));
926 	move(gmsg_inputpos+1, 6 + (pos-start));
927     }
928     screen_refresh();
929 }
930 
931 /*************************************************************************/
932 
933 /* Clear the in-game text window. */
934 
clear_gmsg_input(void)935 static void clear_gmsg_input(void)
936 {
937     if (gmsg_inputwin) {
938 	delwin(gmsg_inputwin);
939 	gmsg_inputwin = NULL;
940 	leaveok(stdscr, TRUE);
941 	touchline(stdscr, gmsg_inputpos, gmsg_inputheight);
942 	setup_fields();
943 	screen_refresh();
944     }
945 }
946 
947 /*************************************************************************/
948 /*************************** Partyline display ***************************/
949 /*************************************************************************/
950 
setup_partyline(void)951 static void setup_partyline(void)
952 {
953     close_textwin(&gmsgbuf);
954     close_textwin(&attdefbuf);
955     clear();
956 
957     attrset(getcolor(COLOR_WHITE,COLOR_BLACK));
958 
959     plinebuf.x = plinebuf.y = 0;
960     plinebuf.width = scrwidth;
961     plinebuf.height = scrheight-4;
962     open_textwin(&plinebuf);
963 
964     move(scrheight-4, 0);
965     hline(MY_HLINE, scrwidth);
966     move(scrheight-3, 0);
967     addstr("> ");
968 
969     move(scrheight-2, 0);
970     hline(MY_HLINE2, scrwidth);
971     attrset(MY_BOLD);
972     move(scrheight-1, 0);
973     addstr("F1=Show Fields  F2=Partyline  F3=Winlist");
974     move(scrheight-1, scrwidth-8);
975     addstr("F10=Quit");
976     attrset(A_NORMAL);
977 
978     move(scrheight-3, 2);
979     leaveok(stdscr, FALSE);
980     screen_refresh();
981 }
982 
983 /*************************************************************************/
984 
draw_partyline_input(const char * s,int pos)985 static void draw_partyline_input(const char *s, int pos)
986 {
987     static int start = 0;	/* Start of displayed part of input line */
988 
989     attrset(getcolor(COLOR_WHITE,COLOR_BLACK));
990     if (strlen(s) < scrwidth-3) {
991 	start = 0;
992 	mvaddstr(scrheight-3, 2, s);
993 	move(scrheight-3, 2+strlen(s));
994 	clrtoeol();
995 	move(scrheight-3, 2+pos);
996     } else {
997 	if (pos < start+8) {
998 	    start = pos-8;
999 	    if (start < 0)
1000 		start = 0;
1001 	} else if (pos > start + scrwidth-11) {
1002 	    start = pos - (scrwidth-11);
1003 	    if (start > strlen(s) - (scrwidth-3))
1004 		start = strlen(s) - (scrwidth-3);
1005 	}
1006 	mvaddnstr(scrheight-3, 2, s+start, scrwidth-2);
1007 	move(scrheight-3, 2 + (pos-start));
1008     }
1009     screen_refresh();
1010 }
1011 
1012 /*************************************************************************/
1013 /**************************** Winlist display ****************************/
1014 /*************************************************************************/
1015 
setup_winlist(void)1016 static void setup_winlist(void)
1017 {
1018     int i, x;
1019     char buf[32];
1020 
1021     leaveok(stdscr, TRUE);
1022     close_textwin(&plinebuf);
1023     clear();
1024     attrset(getcolor(COLOR_WHITE,COLOR_BLACK));
1025 
1026     for (i = 0; i < MAXWINLIST && *winlist[i].name; i++) {
1027 	x = scrwidth/2 - strlen(winlist[i].name);
1028 	if (x < 0)
1029 	    x = 0;
1030 	if (winlist[i].team) {
1031 	    if (x < 4)
1032 		x = 4;
1033 	    mvaddstr(i*2, x-4, "<T>");
1034 	}
1035 	mvaddstr(i*2, x, winlist[i].name);
1036 	snprintf(buf, sizeof(buf), "%4d", winlist[i].points);
1037 	if (winlist[i].games) {
1038 	    int avg100 = winlist[i].points*100 / winlist[i].games;
1039 	    snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf),
1040 			"   %d.%02d",avg100/100, avg100%100);
1041 	}
1042 	x += strlen(winlist[i].name) + 2;
1043 	if (x > scrwidth - strlen(buf))
1044 	    x = scrwidth - strlen(buf);
1045 	mvaddstr(i*2, x, buf);
1046     }
1047 
1048     move(scrheight-2, 0);
1049     hline(MY_HLINE2, scrwidth);
1050     attrset(MY_BOLD);
1051     move(scrheight-1, 0);
1052     addstr("F1=Show Fields  F2=Partyline  F3=Winlist");
1053     move(scrheight-1, scrwidth-8);
1054     addstr("F10=Quit");
1055     attrset(A_NORMAL);
1056 
1057     screen_refresh();
1058 }
1059 
1060 /*************************************************************************/
1061 /************************** Interface declaration ************************/
1062 /*************************************************************************/
1063 
1064 Interface tty_interface = {
1065 
1066     wait_for_input,
1067 
1068     screen_setup,
1069     screen_refresh,
1070     screen_redraw,
1071 
1072     draw_text,
1073     clear_text,
1074 
1075     setup_fields,
1076     draw_own_field,
1077     draw_other_field,
1078     draw_status,
1079     draw_specials,
1080     draw_attdef,
1081     draw_gmsg_input,
1082     clear_gmsg_input,
1083 
1084     setup_partyline,
1085     draw_partyline_input,
1086 
1087     setup_winlist
1088 };
1089 
1090 /*************************************************************************/
1091