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