char is unsigned by default on powerpc - more char fixup to come, this is a serious...
[tetrinet.git] / tty.c
blob90c64ef579ddb13e230c42fc31d7998db3b6c8b5
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 /* Cancel all this when we exit. */
240 atexit(screen_cleanup);
242 /* Catch signals so we can exit cleanly. */
243 signal(SIGINT, sighandler);
244 signal(SIGTERM, sighandler);
245 signal(SIGHUP, sighandler);
246 signal(SIGUSR1, sighandler);
247 signal(SIGUSR2, sighandler);
248 signal(SIGALRM, sighandler);
249 signal(SIGTSTP, sighandler);
250 #ifdef SIGXCPU
251 signal(SIGXCPU, sighandler);
252 #endif
253 #ifdef SIGXFSZ
254 signal(SIGXFSZ, sighandler);
255 #endif
257 /* Broken pipes don't want to bother us at all. */
258 signal(SIGPIPE, SIG_IGN);
261 /*************************************************************************/
263 /* Redraw everything on the screen. */
265 static void screen_refresh(void)
267 if (gmsg_inputwin)
268 touchline(stdscr, gmsg_inputpos, gmsg_inputheight);
269 if (plinebuf.win)
270 touchline(stdscr, plinebuf.y, plinebuf.height);
271 if (gmsgbuf.win)
272 touchline(stdscr, gmsgbuf.y, gmsgbuf.height);
273 if (attdefbuf.win)
274 touchline(stdscr, attdefbuf.y, attdefbuf.height);
275 wnoutrefresh(stdscr);
276 doupdate();
279 /*************************************************************************/
281 /* Like screen_refresh(), but clear the screen first. */
283 static void screen_redraw(void)
285 clearok(stdscr, TRUE);
286 screen_refresh();
289 /*************************************************************************/
290 /************************* Text buffer routines **************************/
291 /*************************************************************************/
293 /* Put a line of text in a text buffer. */
295 static void outline(TextBuffer *buf, const unsigned char *s)
297 if (buf->line == buf->height) {
298 if (buf->win)
299 scroll(buf->win);
300 memmove(buf->text, buf->text+1, (buf->height-1) * sizeof(char *));
301 buf->line--;
303 if (buf->win) {
304 int i, x = 0, l = strlen(s);
306 for (i = 0; i < l; i++) {
307 unsigned char c = s[i] - 1;
309 if (c < TATTR_MAX) {
310 static const int cmap[8] = {
311 COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_YELLOW,
312 COLOR_BLUE, COLOR_MAGENTA, COLOR_CYAN, COLOR_WHITE,
315 switch (c) {
316 case TATTR_RESET:
317 wattrset(buf->win, A_NORMAL);
318 break;
319 case TATTR_BOLD:
320 wattron(buf->win, A_BOLD);
321 break;
322 case TATTR_ITALIC:
323 wattron(buf->win, A_STANDOUT);
324 break;
325 case TATTR_UNDERLINE:
326 wattron(buf->win, A_UNDERLINE);
327 break;
328 default:
329 assert(c < TATTR_CMAX);
330 wattron(buf->win, getcolor(c == 0 ? COLOR_WHITE : cmap[c % 8], COLOR_BLACK)
331 | (A_BOLD * (c / 8)));
332 break;
335 } else {
336 mvwaddch(buf->win, buf->line, x++, c + 1);
340 wattrset(buf->win, A_NORMAL);
342 if (s != (const unsigned char *) buf->text[buf->line]) /* check for restoring display */
343 buf->text[buf->line] = strdup(s);
344 buf->line++;
347 static void draw_text(int bufnum, const char *s)
349 char str[1024]; /* hopefully scrwidth < 1024 */
350 const char *t;
351 int indent = 0;
352 int x = 0, y = 0;
353 TextBuffer *buf;
355 switch (bufnum) {
356 case BUFFER_PLINE: buf = &plinebuf; break;
357 case BUFFER_GMSG: buf = &gmsgbuf; break;
358 case BUFFER_ATTDEF: buf = &attdefbuf; break;
359 default: return;
361 if (!buf->text)
362 return;
363 if (buf->win) {
364 getyx(stdscr, y, x);
365 attrset(getcolor(COLOR_WHITE, COLOR_BLACK));
367 while (*s && isspace(*s))
368 s++;
369 while (strlen(s) > buf->width - indent) {
370 t = s + buf->width - indent;
371 while (t >= s && !isspace(*t))
372 t--;
373 while (t >= s && isspace(*t))
374 t--;
375 t++;
376 if (t < s)
377 t = s + buf->width - indent;
378 if (indent > 0)
379 sprintf(str, "%*s", indent, "");
380 strncpy(str+indent, s, t-s);
381 str[t-s+indent] = 0;
382 outline(buf, str);
383 indent = 2;
384 while (isspace(*t))
385 t++;
386 s = t;
388 if (indent > 0)
389 sprintf(str, "%*s", indent, "");
390 strcpy(str+indent, s);
391 outline(buf, str);
392 if (buf->win) {
393 move(y, x);
394 screen_refresh();
398 /*************************************************************************/
400 /* Clear the contents of a text buffer. */
402 static void clear_text(int bufnum)
404 TextBuffer *buf;
405 int i;
407 switch (bufnum) {
408 case BUFFER_PLINE: buf = &plinebuf; break;
409 case BUFFER_GMSG: buf = &gmsgbuf; break;
410 case BUFFER_ATTDEF: buf = &attdefbuf; break;
411 default: return;
413 if (buf->text) {
414 for (i = 0; i < buf->height; i++) {
415 if (buf->text[i]) {
416 free(buf->text[i]);
417 buf->text[i] = NULL;
420 buf->line = 0;
422 if (buf->win) {
423 werase(buf->win);
424 screen_refresh();
428 /*************************************************************************/
430 /* Restore the contents of the given text buffer. */
432 static void restore_text(TextBuffer *buf)
434 buf->line = 0;
435 while (buf->line < buf->height && buf->text[buf->line])
436 outline(buf, buf->text[buf->line]);
439 /*************************************************************************/
441 /* Open a window for the given text buffer. */
443 static void open_textwin(TextBuffer *buf)
445 if (buf->height <= 0 || buf->width <= 0) {
446 char str[256];
447 move(scrheight-1, 0);
448 snprintf(str, sizeof(str), "ERROR: bad textwin size (%d,%d)",
449 buf->width, buf->height);
450 addstr(str);
451 exit(1);
453 if (!buf->win) {
454 buf->win = subwin(stdscr, buf->height, buf->width, buf->y, buf->x);
455 scrollok(buf->win, TRUE);
457 if (!buf->text)
458 buf->text = calloc(buf->height, sizeof(char *));
459 else
460 restore_text(buf);
463 /*************************************************************************/
465 /* Close the window for the given text buffer, if it's open. */
467 static void close_textwin(TextBuffer *buf)
469 if (buf->win) {
470 delwin(buf->win);
471 buf->win = NULL;
475 /*************************************************************************/
476 /************************ Field drawing routines *************************/
477 /*************************************************************************/
479 /* Are we on a wide screen (>=92 columns)? */
480 static int wide_screen = 0;
482 /* Field display X/Y coordinates. */
483 static const int own_coord[2] = {1,0};
484 static int other_coord[5][2] = /* Recomputed based on screen width */
485 { {30,0}, {47,0}, {64,0}, {47,24}, {64,24} };
487 /* Position of the status window. */
488 static const int status_coord[2] = {29,25};
489 static const int next_coord[2] = {41,24};
490 static const int alt_status_coord[2] = {29,2};
491 static const int alt_next_coord[2] = {30,8};
493 /* Position of the attacks/defenses window. */
494 static const int attdef_coord[2] = {28,28};
495 static const int alt_attdef_coord[2] = {28,24};
497 /* Position of the text window. X coordinate is ignored. */
498 static const int field_text_coord[2] = {0,47};
500 /* Information for drawing blocks. Color attributes are added to blocks in
501 * the setup_fields() routine. */
502 static int tile_chars[15] =
503 { ' ','#','#','#','#','#','a','c','n','r','s','b','g','q','o' };
505 /* Are we redrawing the entire display? */
506 static int field_redraw = 0;
508 /*************************************************************************/
509 /*************************************************************************/
511 /* Set up the field display. */
513 static void draw_own_field(void);
514 static void draw_other_field(int player);
515 static void draw_status(void);
516 static void draw_specials(void);
517 static void draw_gmsg_input(const char *s, int pos);
519 static void setup_fields(void)
521 int i, j, x, y, base, delta, attdefbot;
522 char buf[32];
524 if (!(tile_chars[0] & A_ATTRIBUTES)) {
525 for (i = 1; i < 15; i++)
526 tile_chars[i] |= A_BOLD;
527 tile_chars[1] |= getcolor(COLOR_BLUE, COLOR_BLACK);
528 tile_chars[2] |= getcolor(COLOR_YELLOW, COLOR_BLACK);
529 tile_chars[3] |= getcolor(COLOR_GREEN, COLOR_BLACK);
530 tile_chars[4] |= getcolor(COLOR_MAGENTA, COLOR_BLACK);
531 tile_chars[5] |= getcolor(COLOR_RED, COLOR_BLACK);
534 field_redraw = 1;
535 leaveok(stdscr, TRUE);
536 close_textwin(&plinebuf);
537 clear();
538 attrset(getcolor(COLOR_WHITE,COLOR_BLACK));
540 if (scrwidth >= 92) {
541 wide_screen = 1;
542 base = 41;
543 } else {
544 base = 28;
546 delta = (scrwidth - base) / 3;
547 base += 2 + (delta - (FIELD_WIDTH+5)) / 2;
548 other_coord[0][0] = base;
549 other_coord[1][0] = base + delta;
550 other_coord[2][0] = base + delta*2;
551 other_coord[3][0] = base + delta;
552 other_coord[4][0] = base + delta*2;
554 attdefbot = field_text_coord[1] - 1;
555 if (scrheight - field_text_coord[1] > 3) {
556 move(field_text_coord[1], 0);
557 hline(MY_HLINE2, scrwidth);
558 attdefbot--;
559 if (scrheight - field_text_coord[1] > 5) {
560 move(scrheight-2, 0);
561 hline(MY_HLINE2, scrwidth);
562 attrset(MY_BOLD);
563 move(scrheight-1, 0);
564 addstr("F1=Show Fields F2=Partyline F3=Winlist");
565 move(scrheight-1, scrwidth-8);
566 addstr("F10=Quit");
567 attrset(A_NORMAL);
568 gmsgbuf.y = field_text_coord[1]+1;
569 gmsgbuf.height = scrheight - field_text_coord[1] - 3;
570 } else {
571 gmsgbuf.y = field_text_coord[1]+1;
572 gmsgbuf.height = scrheight - field_text_coord[1] - 1;
574 } else {
575 gmsgbuf.y = field_text_coord[1];
576 gmsgbuf.height = scrheight - field_text_coord[1];
578 gmsgbuf.x = field_text_coord[0];
579 gmsgbuf.width = scrwidth;
580 open_textwin(&gmsgbuf);
582 x = own_coord[0];
583 y = own_coord[1];
584 sprintf(buf, "%d", my_playernum);
585 mvaddstr(y, x-1, buf);
586 for (i = 2; i < FIELD_HEIGHT*2 && players[my_playernum-1][i-2]; i++)
587 mvaddch(y+i, x-1, players[my_playernum-1][i-2]);
588 if (teams[my_playernum-1] != '\0') {
589 mvaddstr(y, x+FIELD_WIDTH*2+2, "T");
590 for (i = 2; i < FIELD_HEIGHT*2 && teams[my_playernum-1][i-2]; i++)
591 mvaddch(y+i, x+FIELD_WIDTH*2+2, teams[my_playernum-1][i-2]);
593 move(y, x);
594 vline(MY_VLINE, FIELD_HEIGHT*2);
595 move(y, x+FIELD_WIDTH*2+1);
596 vline(MY_VLINE, FIELD_HEIGHT*2);
597 move(y+FIELD_HEIGHT*2, x);
598 addch(MY_LLCORNER);
599 hline(MY_HLINE, FIELD_WIDTH*2);
600 move(y+FIELD_HEIGHT*2, x+FIELD_WIDTH*2+1);
601 addch(MY_LRCORNER);
602 mvaddstr(y+FIELD_HEIGHT*2+2, x, "Specials:");
603 draw_own_field();
604 draw_specials();
606 for (j = 0; j < 5; j++) {
607 x = other_coord[j][0];
608 y = other_coord[j][1];
609 move(y, x);
610 vline(MY_VLINE, FIELD_HEIGHT);
611 move(y, x+FIELD_WIDTH+1);
612 vline(MY_VLINE, FIELD_HEIGHT);
613 move(y+FIELD_HEIGHT, x);
614 addch(MY_LLCORNER);
615 hline(MY_HLINE, FIELD_WIDTH);
616 move(y+FIELD_HEIGHT, x+FIELD_WIDTH+1);
617 addch(MY_LRCORNER);
618 if (j+1 >= my_playernum) {
619 sprintf(buf, "%d", j+2);
620 mvaddstr(y, x-1, buf);
621 if (players[j+1]) {
622 for (i = 0; i < FIELD_HEIGHT-2 && players[j+1][i]; i++)
623 mvaddch(y+i+2, x-1, players[j+1][i]);
624 if (teams[j+1] != '\0') {
625 mvaddstr(y, x+FIELD_WIDTH+2, "T");
626 for (i = 0; i < FIELD_HEIGHT-2 && teams[j+1][i]; i++)
627 mvaddch(y+i+2, x+FIELD_WIDTH+2, teams[j+1][i]);
630 draw_other_field(j+2);
631 } else {
632 sprintf(buf, "%d", j+1);
633 mvaddstr(y, x-1, buf);
634 if (players[j]) {
635 for (i = 0; i < FIELD_HEIGHT-2 && players[j][i]; i++)
636 mvaddch(y+i+2, x-1, players[j][i]);
637 if (teams[j] != '\0') {
638 mvaddstr(y, x+FIELD_WIDTH+2, "T");
639 for (i = 0; i < FIELD_HEIGHT-2 && teams[j][i]; i++)
640 mvaddch(y+i+2, x+FIELD_WIDTH+2, teams[j][i]);
643 draw_other_field(j+1);
647 if (wide_screen) {
648 x = alt_status_coord[0];
649 y = alt_status_coord[1];
650 mvaddstr(y, x, "Lines:");
651 mvaddstr(y+1, x, "Level:");
652 x = alt_next_coord[0];
653 y = alt_next_coord[1];
654 mvaddstr(y-2, x-1, "Next piece:");
655 move(y-1, x-1);
656 addch(MY_ULCORNER);
657 hline(MY_HLINE, 8);
658 mvaddch(y-1, x+8, MY_URCORNER);
659 move(y, x-1);
660 vline(MY_VLINE, 8);
661 move(y, x+8);
662 vline(MY_VLINE, 8);
663 move(y+8, x-1);
664 addch(MY_LLCORNER);
665 hline(MY_HLINE, 8);
666 mvaddch(y+8, x+8, MY_LRCORNER);
667 } else {
668 x = status_coord[0];
669 y = status_coord[1];
670 mvaddstr(y-1, x, "Next piece:");
671 mvaddstr(y, x, "Lines:");
672 mvaddstr(y+1, x, "Level:");
674 if (playing_game)
675 draw_status();
677 attdefbuf.x = wide_screen ? alt_attdef_coord[0] : attdef_coord[0];
678 attdefbuf.y = wide_screen ? alt_attdef_coord[1] : attdef_coord[1];
679 attdefbuf.width = (other_coord[3][0]-1) - attdefbuf.x;
680 attdefbuf.height = (attdefbot+1) - attdefbuf.y;
681 open_textwin(&attdefbuf);
683 if (gmsg_inputwin) {
684 delwin(gmsg_inputwin);
685 gmsg_inputwin = NULL;
686 draw_gmsg_input(NULL, -1);
689 (void)curs_set(0);
690 screen_refresh();
691 field_redraw = 0;
694 /*************************************************************************/
696 /* Display the player's own field. */
698 static void draw_own_field(void)
700 int x, y, x0, y0;
701 Field *f = &fields[my_playernum-1];
702 int shadow[4] = { -1, -1, -1, -1 };
704 if (dispmode != MODE_FIELDS)
705 return;
707 /* XXX: Code duplication with tetris.c:draw_piece(). --pasky */
708 if (playing_game && cast_shadow) {
709 int y = current_y - piecedata[current_piece][current_rotation].hot_y;
710 char *shape = (char *) piecedata[current_piece][current_rotation].shape;
711 int i, j;
713 for (j = 0; j < 4; j++) {
714 if (y+j < 0) {
715 shape += 4;
716 continue;
718 for (i = 0; i < 4; i++) {
719 if (*shape++)
720 shadow[i] = y + j;
725 x0 = own_coord[0]+1;
726 y0 = own_coord[1];
727 for (y = 0; y < 22; y++) {
728 for (x = 0; x < 12; x++) {
729 int c = tile_chars[(int) (*f)[y][x]];
731 if (playing_game && cast_shadow) {
732 PieceData *piece = &piecedata[current_piece][current_rotation];
733 int piece_x = current_x - piece->hot_x;
735 if (x >= piece_x && x <= piece_x + 3
736 && shadow[(x - piece_x)] >= 0
737 && shadow[(x - piece_x)] < y
738 && ((c & 0x7f) == ' ')) {
739 c = (c & (~0x7f)) | '.'
740 | getcolor(COLOR_BLACK, COLOR_BLACK) | A_BOLD;
744 mvaddch(y0+y*2, x0+x*2, c);
745 addch(c);
746 mvaddch(y0+y*2+1, x0+x*2, c);
747 addch(c);
750 if (gmsg_inputwin) {
751 delwin(gmsg_inputwin);
752 gmsg_inputwin = NULL;
753 draw_gmsg_input(NULL, -1);
755 if (!field_redraw) {
756 (void)curs_set(0);
757 screen_refresh();
761 /*************************************************************************/
763 /* Display another player's field. */
765 static void draw_other_field(int player)
767 int x, y, x0, y0;
768 Field *f;
770 if (dispmode != MODE_FIELDS)
771 return;
772 f = &fields[player-1];
773 if (player > my_playernum)
774 player--;
775 player--;
776 x0 = other_coord[player][0]+1;
777 y0 = other_coord[player][1];
778 for (y = 0; y < 22; y++) {
779 move(y0+y, x0);
780 for (x = 0; x < 12; x++) {
781 addch(tile_chars[(int) (*f)[y][x]]);
784 if (gmsg_inputwin) {
785 delwin(gmsg_inputwin);
786 gmsg_inputwin = NULL;
787 draw_gmsg_input(NULL, -1);
789 if (!field_redraw) {
790 (void)curs_set(0);
791 screen_refresh();
795 /*************************************************************************/
797 /* Display the current game status (level, lines, next piece). */
799 static void draw_status(void)
801 int x, y, i, j;
802 char buf[32], shape[4][4];
804 x = wide_screen ? alt_status_coord[0] : status_coord[0];
805 y = wide_screen ? alt_status_coord[1] : status_coord[1];
806 sprintf(buf, "%d", lines>99999 ? 99999 : lines);
807 mvaddstr(y, x+7, buf);
808 sprintf(buf, "%d", levels[my_playernum]);
809 mvaddstr(y+1, x+7, buf);
810 x = wide_screen ? alt_next_coord[0] : next_coord[0];
811 y = wide_screen ? alt_next_coord[1] : next_coord[1];
812 if (get_shape(next_piece, 0, shape) == 0) {
813 for (j = 0; j < 4; j++) {
814 if (!wide_screen)
815 move(y+j, x);
816 for (i = 0; i < 4; i++) {
817 if (wide_screen) {
818 move(y+j*2, x+i*2);
819 addch(tile_chars[(int) shape[j][i]]);
820 addch(tile_chars[(int) shape[j][i]]);
821 move(y+j*2+1, x+i*2);
822 addch(tile_chars[(int) shape[j][i]]);
823 addch(tile_chars[(int) shape[j][i]]);
824 } else
825 addch(tile_chars[(int) shape[j][i]]);
831 /*************************************************************************/
833 /* Display the special inventory and description of the current special. */
835 static const char *descs[] = {
836 " ",
837 "Add Line ",
838 "Clear Line ",
839 "Nuke Field ",
840 "Clear Random Blocks ",
841 "Switch Fields ",
842 "Clear Special Blocks",
843 "Block Gravity ",
844 "Blockquake ",
845 "Block Bomb "
848 static void draw_specials(void)
850 int x, y, i;
852 if (dispmode != MODE_FIELDS)
853 return;
854 x = own_coord[0];
855 y = own_coord[1]+45;
856 mvaddstr(y, x, descs[specials[0]+1]);
857 move(y+1, x+10);
858 i = 0;
859 while (i < special_capacity && specials[i] >= 0 && x < attdef_coord[0]-1) {
860 addch(tile_chars[specials[i]+6]);
861 i++;
862 x++;
864 while (x < attdef_coord[0]-1) {
865 addch(tile_chars[0]);
866 x++;
868 if (!field_redraw) {
869 (void)curs_set(0);
870 screen_refresh();
874 /*************************************************************************/
876 /* Display an attack/defense message. */
878 static const char *msgs[][2] = {
879 { "cs1", "1 Line Added to All" },
880 { "cs2", "2 Lines Added to All" },
881 { "cs4", "4 Lines Added to All" },
882 { "a", "Add Line" },
883 { "c", "Clear Line" },
884 { "n", "Nuke Field" },
885 { "r", "Clear Random Blocks" },
886 { "s", "Switch Fields" },
887 { "b", "Clear Special Blocks" },
888 { "g", "Block Gravity" },
889 { "q", "Blockquake" },
890 { "o", "Block Bomb" },
891 { NULL }
894 static void draw_attdef(const char *type, int from, int to)
896 int i, width;
897 char buf[512];
899 width = other_coord[4][0] - attdef_coord[0] - 1;
900 for (i = 0; msgs[i][0]; i++) {
901 if (strcmp(type, msgs[i][0]) == 0)
902 break;
904 if (!msgs[i][0])
905 return;
906 strcpy(buf, msgs[i][1]);
907 if (to != 0)
908 sprintf(buf+strlen(buf), " on %s", players[to-1]);
909 if (from == 0)
910 sprintf(buf+strlen(buf), " by Server");
911 else
912 sprintf(buf+strlen(buf), " by %s", players[from-1]);
913 draw_text(BUFFER_ATTDEF, buf);
916 /*************************************************************************/
918 /* Display the in-game text window. */
920 static void draw_gmsg_input(const char *s, int pos)
922 static int start = 0; /* Start of displayed part of input line */
923 static const char *last_s;
924 static int last_pos;
926 if (s)
927 last_s = s;
928 else
929 s = last_s;
930 if (pos >= 0)
931 last_pos = pos;
932 else
933 pos = last_pos;
935 attrset(getcolor(COLOR_WHITE,COLOR_BLACK));
937 if (!gmsg_inputwin) {
938 gmsg_inputpos = scrheight/2 - 1;
939 gmsg_inputheight = 3;
940 gmsg_inputwin =
941 subwin(stdscr, gmsg_inputheight, scrwidth, gmsg_inputpos, 0);
942 werase(gmsg_inputwin);
943 leaveok(gmsg_inputwin, FALSE);
944 leaveok(stdscr, FALSE);
945 mvwaddstr(gmsg_inputwin, 1, 0, "Text>");
948 if (strlen(s) < scrwidth-7) {
949 start = 0;
950 mvwaddstr(gmsg_inputwin, 1, 6, s);
951 wmove(gmsg_inputwin, 1, 6+strlen(s));
952 move(gmsg_inputpos+1, 6+strlen(s));
953 wclrtoeol(gmsg_inputwin);
954 wmove(gmsg_inputwin, 1, 6+pos);
955 move(gmsg_inputpos+1, 6+pos);
956 } else {
957 if (pos < start+8) {
958 start = pos-8;
959 if (start < 0)
960 start = 0;
961 } else if (pos > start + scrwidth-15) {
962 start = pos - (scrwidth-15);
963 if (start > strlen(s) - (scrwidth-7))
964 start = strlen(s) - (scrwidth-7);
966 mvwaddnstr(gmsg_inputwin, 1, 6, s+start, scrwidth-6);
967 wmove(gmsg_inputwin, 1, 6 + (pos-start));
968 move(gmsg_inputpos+1, 6 + (pos-start));
970 (void)curs_set(1);
971 screen_refresh();
974 /*************************************************************************/
976 /* Clear the in-game text window. */
978 static void clear_gmsg_input(void)
980 if (gmsg_inputwin) {
981 delwin(gmsg_inputwin);
982 gmsg_inputwin = NULL;
983 leaveok(stdscr, TRUE);
984 touchline(stdscr, gmsg_inputpos, gmsg_inputheight);
985 setup_fields();
986 (void)curs_set(0);
987 screen_refresh();
991 /*************************************************************************/
992 /*************************** Partyline display ***************************/
993 /*************************************************************************/
995 static void setup_partyline(void)
997 close_textwin(&gmsgbuf);
998 close_textwin(&attdefbuf);
999 clear();
1001 attrset(getcolor(COLOR_WHITE,COLOR_BLACK));
1003 plinebuf.x = plinebuf.y = 0;
1004 plinebuf.width = scrwidth;
1005 plinebuf.height = scrheight-4;
1006 open_textwin(&plinebuf);
1008 move(scrheight-4, 0);
1009 hline(MY_HLINE, scrwidth);
1010 move(scrheight-3, 0);
1011 addstr("> ");
1013 move(scrheight-2, 0);
1014 hline(MY_HLINE2, scrwidth);
1015 attrset(MY_BOLD);
1016 move(scrheight-1, 0);
1017 addstr("F1=Show Fields F2=Partyline F3=Winlist");
1018 move(scrheight-1, scrwidth-8);
1019 addstr("F10=Quit");
1020 attrset(A_NORMAL);
1022 move(scrheight-3, 2);
1023 leaveok(stdscr, FALSE);
1024 (void)curs_set(1);
1025 screen_refresh();
1028 /*************************************************************************/
1030 static void draw_partyline_input(const char *s, int pos)
1032 static int start = 0; /* Start of displayed part of input line */
1034 attrset(getcolor(COLOR_WHITE,COLOR_BLACK));
1035 if (strlen(s) < scrwidth-3) {
1036 start = 0;
1037 mvaddstr(scrheight-3, 2, s);
1038 move(scrheight-3, 2+strlen(s));
1039 clrtoeol();
1040 move(scrheight-3, 2+pos);
1041 } else {
1042 if (pos < start+8) {
1043 start = pos-8;
1044 if (start < 0)
1045 start = 0;
1046 } else if (pos > start + scrwidth-11) {
1047 start = pos - (scrwidth-11);
1048 if (start > strlen(s) - (scrwidth-3))
1049 start = strlen(s) - (scrwidth-3);
1051 mvaddnstr(scrheight-3, 2, s+start, scrwidth-2);
1052 move(scrheight-3, 2 + (pos-start));
1054 screen_refresh();
1057 /*************************************************************************/
1058 /**************************** Winlist display ****************************/
1059 /*************************************************************************/
1061 static void setup_winlist(void)
1063 int i, x;
1064 char buf[32];
1066 leaveok(stdscr, TRUE);
1067 close_textwin(&plinebuf);
1068 clear();
1069 attrset(getcolor(COLOR_WHITE,COLOR_BLACK));
1071 for (i = 0; i < MAXWINLIST && *winlist[i].name; i++) {
1072 x = scrwidth/2 - strlen(winlist[i].name);
1073 if (x < 0)
1074 x = 0;
1075 if (winlist[i].team) {
1076 if (x < 4)
1077 x = 4;
1078 mvaddstr(i*2, x-4, "<T>");
1080 mvaddstr(i*2, x, winlist[i].name);
1081 snprintf(buf, sizeof(buf), "%4d", winlist[i].points);
1082 if (winlist[i].games) {
1083 int avg100 = winlist[i].points*100 / winlist[i].games;
1084 snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf),
1085 " %d.%02d",avg100/100, avg100%100);
1087 x += strlen(winlist[i].name) + 2;
1088 if (x > scrwidth - strlen(buf))
1089 x = scrwidth - strlen(buf);
1090 mvaddstr(i*2, x, buf);
1093 move(scrheight-2, 0);
1094 hline(MY_HLINE2, scrwidth);
1095 attrset(MY_BOLD);
1096 move(scrheight-1, 0);
1097 addstr("F1=Show Fields F2=Partyline F3=Winlist");
1098 move(scrheight-1, scrwidth-8);
1099 addstr("F10=Quit");
1100 attrset(A_NORMAL);
1102 (void)curs_set(0);
1103 screen_refresh();
1106 /*************************************************************************/
1107 /************************** Interface declaration ************************/
1108 /*************************************************************************/
1110 Interface tty_interface = {
1112 wait_for_input,
1114 screen_setup,
1115 screen_refresh,
1116 screen_redraw,
1118 draw_text,
1119 clear_text,
1121 setup_fields,
1122 draw_own_field,
1123 draw_other_field,
1124 draw_status,
1125 draw_specials,
1126 draw_attdef,
1127 draw_gmsg_input,
1128 clear_gmsg_input,
1130 setup_partyline,
1131 draw_partyline_input,
1133 setup_winlist
1136 /*************************************************************************/