ChangeLog update
[tetrinet.git] / tty.c
blob6659756dff188c067c79909fe1a53b203a106ab5
1 /* Tetrinet for Linux, by Andrew Church <achurch@achurch.org>
2 * This program is public domain.
4 * Text terminal I/O routines.
5 */
7 #define _GNU_SOURCE /* strsignal() - FIXME!!! --pasky */
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"
22 /*************************************************************************/
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 : '+')
31 #define MY_HLINE2 (fancy ? (ACS_HLINE | A_BOLD) : '=')
32 #define MY_BOLD (fancy ? A_BOLD : 0)
34 /*************************************************************************/
35 /******************************* Input stuff *****************************/
36 /*************************************************************************/
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.
42 static int wait_for_input(int msec)
44 fd_set fds;
45 struct timeval tv;
46 int c;
47 static int escape = 0;
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");
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;
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 */
116 /*************************************************************************/
117 /****************************** Output stuff *****************************/
118 /*************************************************************************/
120 /* Size of the screen */
121 static int scrwidth, scrheight;
123 /* Is color available? */
124 static int has_color;
126 /*************************************************************************/
128 /* Text buffers: */
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;
137 static TextBuffer plinebuf, gmsgbuf, attdefbuf;
139 /*************************************************************************/
141 /* Window for typing in-game text, and its coordinates: */
143 static WINDOW *gmsg_inputwin;
144 static int gmsg_inputpos, gmsg_inputheight;
146 /*************************************************************************/
147 /*************************************************************************/
149 /* Clean up the screen on exit. */
151 static void screen_cleanup()
153 wmove(stdscr, scrheight-1, 0);
154 wrefresh(stdscr);
155 endwin();
156 printf("\n");
159 /*************************************************************************/
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.
165 static void (*old_tstp)(int sig);
167 static void sighandler(int sig)
169 if (sig != SIGTSTP) {
170 endwin();
171 fprintf(stderr, "%s\n", strsignal(sig));
172 exit(1);
174 endwin();
175 signal(SIGTSTP, old_tstp);
176 raise(SIGTSTP);
177 doupdate();
178 signal(SIGTSTP, sighandler);
181 /*************************************************************************/
182 /*************************************************************************/
184 #define MAXCOLORS 256
186 static int colors[MAXCOLORS][2] = { {-1,-1} };
188 /* Return a color attribute value. */
190 static long getcolor(int fg, int bg)
192 int i;
194 if (colors[0][0] < 0) {
195 start_color();
196 memset(colors, -1, sizeof(colors));
197 colors[0][0] = COLOR_WHITE;
198 colors[0][1] = COLOR_BLACK;
200 if (fg == COLOR_WHITE && bg == COLOR_BLACK)
201 return COLOR_PAIR(0);
202 for (i = 1; i < MAXCOLORS; i++) {
203 if (colors[i][0] == fg && colors[i][1] == bg)
204 return COLOR_PAIR(i);
206 for (i = 1; i < MAXCOLORS; i++) {
207 if (colors[i][0] < 0) {
208 if (init_pair(i, fg, bg) == ERR)
209 continue;
210 colors[i][0] = fg;
211 colors[i][1] = bg;
212 return COLOR_PAIR(i);
215 return -1;
218 /*************************************************************************/
219 /*************************************************************************/
221 /* Set up the screen stuff. */
223 static void screen_setup(void)
225 /* Avoid messy keyfield signals while we're setting up */
226 signal(SIGINT, SIG_IGN);
227 signal(SIGTSTP, SIG_IGN);
229 initscr();
230 cbreak();
231 noecho();
232 nodelay(stdscr, TRUE);
233 keypad(stdscr, TRUE);
234 leaveok(stdscr, TRUE);
235 if ((has_color = has_colors()))
236 start_color();
237 getmaxyx(stdscr, scrheight, scrwidth);
238 scrwidth--; /* Don't draw in last column--this can cause scroll */
240 /* Cancel all this when we exit. */
241 atexit(screen_cleanup);
243 /* Catch signals so we can exit cleanly. */
244 signal(SIGINT, sighandler);
245 signal(SIGTERM, sighandler);
246 signal(SIGHUP, sighandler);
247 signal(SIGUSR1, sighandler);
248 signal(SIGUSR2, sighandler);
249 signal(SIGALRM, sighandler);
250 signal(SIGTSTP, sighandler);
251 #ifdef SIGXCPU
252 signal(SIGXCPU, sighandler);
253 #endif
254 #ifdef SIGXFSZ
255 signal(SIGXFSZ, sighandler);
256 #endif
258 /* Broken pipes don't want to bother us at all. */
259 signal(SIGPIPE, SIG_IGN);
262 /*************************************************************************/
264 /* Redraw everything on the screen. */
266 static void screen_refresh(void)
268 if (gmsg_inputwin)
269 touchline(stdscr, gmsg_inputpos, gmsg_inputheight);
270 if (plinebuf.win)
271 touchline(stdscr, plinebuf.y, plinebuf.height);
272 if (gmsgbuf.win)
273 touchline(stdscr, gmsgbuf.y, gmsgbuf.height);
274 if (attdefbuf.win)
275 touchline(stdscr, attdefbuf.y, attdefbuf.height);
276 wnoutrefresh(stdscr);
277 doupdate();
280 /*************************************************************************/
282 /* Like screen_refresh(), but clear the screen first. */
284 static void screen_redraw(void)
286 clearok(stdscr, TRUE);
287 screen_refresh();
290 /*************************************************************************/
291 /************************* Text buffer routines **************************/
292 /*************************************************************************/
294 /* Put a line of text in a text buffer. */
296 static void outline(TextBuffer *buf, const char *s)
298 if (buf->line == buf->height) {
299 if (buf->win)
300 scroll(buf->win);
301 memmove(buf->text, buf->text+1, (buf->height-1) * sizeof(char *));
302 buf->line--;
304 if (buf->win)
305 mvwaddstr(buf->win, buf->line, 0, s);
306 if (s != buf->text[buf->line]) /* check for restoring display */
307 buf->text[buf->line] = strdup(s);
308 buf->line++;
311 static void draw_text(int bufnum, const char *s)
313 char str[1024]; /* hopefully scrwidth < 1024 */
314 const char *t;
315 int indent = 0;
316 int x = 0, y = 0;
317 TextBuffer *buf;
319 switch (bufnum) {
320 case BUFFER_PLINE: buf = &plinebuf; break;
321 case BUFFER_GMSG: buf = &gmsgbuf; break;
322 case BUFFER_ATTDEF: buf = &attdefbuf; break;
323 default: return;
325 if (!buf->text)
326 return;
327 if (buf->win) {
328 getyx(stdscr, y, x);
329 attrset(getcolor(COLOR_WHITE, COLOR_BLACK));
331 while (*s && isspace(*s))
332 s++;
333 while (strlen(s) > buf->width - indent) {
334 t = s + buf->width - indent;
335 while (t >= s && !isspace(*t))
336 t--;
337 while (t >= s && isspace(*t))
338 t--;
339 t++;
340 if (t < s)
341 t = s + buf->width - indent;
342 if (indent > 0)
343 sprintf(str, "%*s", indent, "");
344 strncpy(str+indent, s, t-s);
345 str[t-s+indent] = 0;
346 outline(buf, str);
347 indent = 2;
348 while (isspace(*t))
349 t++;
350 s = t;
352 if (indent > 0)
353 sprintf(str, "%*s", indent, "");
354 strcpy(str+indent, s);
355 outline(buf, str);
356 if (buf->win) {
357 move(y, x);
358 screen_refresh();
362 /*************************************************************************/
364 /* Clear the contents of a text buffer. */
366 static void clear_text(int bufnum)
368 TextBuffer *buf;
369 int i;
371 switch (bufnum) {
372 case BUFFER_PLINE: buf = &plinebuf; break;
373 case BUFFER_GMSG: buf = &gmsgbuf; break;
374 case BUFFER_ATTDEF: buf = &attdefbuf; break;
375 default: return;
377 if (buf->text) {
378 for (i = 0; i < buf->height; i++) {
379 if (buf->text[i]) {
380 free(buf->text[i]);
381 buf->text[i] = NULL;
384 buf->line = 0;
386 if (buf->win) {
387 werase(buf->win);
388 screen_refresh();
392 /*************************************************************************/
394 /* Restore the contents of the given text buffer. */
396 static void restore_text(TextBuffer *buf)
398 buf->line = 0;
399 while (buf->line < buf->height && buf->text[buf->line])
400 outline(buf, buf->text[buf->line]);
403 /*************************************************************************/
405 /* Open a window for the given text buffer. */
407 static void open_textwin(TextBuffer *buf)
409 if (buf->height <= 0 || buf->width <= 0) {
410 char str[256];
411 move(scrheight-1, 0);
412 snprintf(str, sizeof(str), "ERROR: bad textwin size (%d,%d)",
413 buf->width, buf->height);
414 addstr(str);
415 exit(1);
417 if (!buf->win) {
418 buf->win = subwin(stdscr, buf->height, buf->width, buf->y, buf->x);
419 scrollok(buf->win, TRUE);
421 if (!buf->text)
422 buf->text = calloc(buf->height, sizeof(char *));
423 else
424 restore_text(buf);
427 /*************************************************************************/
429 /* Close the window for the given text buffer, if it's open. */
431 static void close_textwin(TextBuffer *buf)
433 if (buf->win) {
434 delwin(buf->win);
435 buf->win = NULL;
439 /*************************************************************************/
440 /************************ Field drawing routines *************************/
441 /*************************************************************************/
443 /* Are we on a wide screen (>=92 columns)? */
444 static int wide_screen = 0;
446 /* Field display X/Y coordinates. */
447 static const int own_coord[2] = {1,0};
448 static int other_coord[5][2] = /* Recomputed based on screen width */
449 { {30,0}, {47,0}, {64,0}, {47,24}, {64,24} };
451 /* Position of the status window. */
452 static const int status_coord[2] = {29,25};
453 static const int next_coord[2] = {41,24};
454 static const int alt_status_coord[2] = {29,2};
455 static const int alt_next_coord[2] = {30,8};
457 /* Position of the attacks/defenses window. */
458 static const int attdef_coord[2] = {28,28};
459 static const int alt_attdef_coord[2] = {28,24};
461 /* Position of the text window. X coordinate is ignored. */
462 static const int field_text_coord[2] = {0,47};
464 /* Information for drawing blocks. Color attributes are added to blocks in
465 * the setup_fields() routine. */
466 static int tile_chars[15] =
467 { ' ','#','#','#','#','#','a','c','n','r','s','b','g','q','o' };
469 /* Are we redrawing the entire display? */
470 static int field_redraw = 0;
472 /*************************************************************************/
473 /*************************************************************************/
475 /* Set up the field display. */
477 static void draw_own_field(void);
478 static void draw_other_field(int player);
479 static void draw_status(void);
480 static void draw_specials(void);
481 static void draw_gmsg_input(const char *s, int pos);
483 static void setup_fields(void)
485 int i, j, x, y, base, delta, attdefbot;
486 char buf[32];
488 if (!(tile_chars[0] & A_ATTRIBUTES)) {
489 for (i = 1; i < 15; i++)
490 tile_chars[i] |= A_BOLD;
491 tile_chars[1] |= getcolor(COLOR_BLUE, COLOR_BLACK);
492 tile_chars[2] |= getcolor(COLOR_YELLOW, COLOR_BLACK);
493 tile_chars[3] |= getcolor(COLOR_GREEN, COLOR_BLACK);
494 tile_chars[4] |= getcolor(COLOR_MAGENTA, COLOR_BLACK);
495 tile_chars[5] |= getcolor(COLOR_RED, COLOR_BLACK);
498 field_redraw = 1;
499 leaveok(stdscr, TRUE);
500 close_textwin(&plinebuf);
501 clear();
502 attrset(getcolor(COLOR_WHITE,COLOR_BLACK));
504 if (scrwidth >= 92) {
505 wide_screen = 1;
506 base = 41;
507 } else {
508 base = 28;
510 delta = (scrwidth - base) / 3;
511 base += 2 + (delta - (FIELD_WIDTH+5)) / 2;
512 other_coord[0][0] = base;
513 other_coord[1][0] = base + delta;
514 other_coord[2][0] = base + delta*2;
515 other_coord[3][0] = base + delta;
516 other_coord[4][0] = base + delta*2;
518 attdefbot = field_text_coord[1] - 1;
519 if (scrheight - field_text_coord[1] > 3) {
520 move(field_text_coord[1], 0);
521 hline(MY_HLINE2, scrwidth);
522 attdefbot--;
523 if (scrheight - field_text_coord[1] > 5) {
524 move(scrheight-2, 0);
525 hline(MY_HLINE2, scrwidth);
526 attrset(MY_BOLD);
527 move(scrheight-1, 0);
528 addstr("F1=Show Fields F2=Partyline F3=Winlist");
529 move(scrheight-1, scrwidth-8);
530 addstr("F10=Quit");
531 attrset(A_NORMAL);
532 gmsgbuf.y = field_text_coord[1]+1;
533 gmsgbuf.height = scrheight - field_text_coord[1] - 3;
534 } else {
535 gmsgbuf.y = field_text_coord[1]+1;
536 gmsgbuf.height = scrheight - field_text_coord[1] - 1;
538 } else {
539 gmsgbuf.y = field_text_coord[1];
540 gmsgbuf.height = scrheight - field_text_coord[1];
542 gmsgbuf.x = field_text_coord[0];
543 gmsgbuf.width = scrwidth;
544 open_textwin(&gmsgbuf);
546 x = own_coord[0];
547 y = own_coord[1];
548 sprintf(buf, "%d", my_playernum);
549 mvaddstr(y, x-1, buf);
550 for (i = 2; i < FIELD_HEIGHT*2 && players[my_playernum-1][i-2]; i++)
551 mvaddch(y+i, x-1, players[my_playernum-1][i-2]);
552 if (teams[my_playernum-1] != '\0') {
553 mvaddstr(y, x+FIELD_WIDTH*2+2, "T");
554 for (i = 2; i < FIELD_HEIGHT*2 && teams[my_playernum-1][i-2]; i++)
555 mvaddch(y+i, x+FIELD_WIDTH*2+2, teams[my_playernum-1][i-2]);
557 move(y, x);
558 vline(MY_VLINE, FIELD_HEIGHT*2);
559 move(y, x+FIELD_WIDTH*2+1);
560 vline(MY_VLINE, FIELD_HEIGHT*2);
561 move(y+FIELD_HEIGHT*2, x);
562 addch(MY_LLCORNER);
563 hline(MY_HLINE, FIELD_WIDTH*2);
564 move(y+FIELD_HEIGHT*2, x+FIELD_WIDTH*2+1);
565 addch(MY_LRCORNER);
566 mvaddstr(y+FIELD_HEIGHT*2+2, x, "Specials:");
567 draw_own_field();
568 draw_specials();
570 for (j = 0; j < 5; j++) {
571 x = other_coord[j][0];
572 y = other_coord[j][1];
573 move(y, x);
574 vline(MY_VLINE, FIELD_HEIGHT);
575 move(y, x+FIELD_WIDTH+1);
576 vline(MY_VLINE, FIELD_HEIGHT);
577 move(y+FIELD_HEIGHT, x);
578 addch(MY_LLCORNER);
579 hline(MY_HLINE, FIELD_WIDTH);
580 move(y+FIELD_HEIGHT, x+FIELD_WIDTH+1);
581 addch(MY_LRCORNER);
582 if (j+1 >= my_playernum) {
583 sprintf(buf, "%d", j+2);
584 mvaddstr(y, x-1, buf);
585 if (players[j+1]) {
586 for (i = 0; i < FIELD_HEIGHT-2 && players[j+1][i]; i++)
587 mvaddch(y+i+2, x-1, players[j+1][i]);
588 if (teams[j+1] != '\0') {
589 mvaddstr(y, x+FIELD_WIDTH+2, "T");
590 for (i = 0; i < FIELD_HEIGHT-2 && teams[j+1][i]; i++)
591 mvaddch(y+i+2, x+FIELD_WIDTH+2, teams[j+1][i]);
594 draw_other_field(j+2);
595 } else {
596 sprintf(buf, "%d", j+1);
597 mvaddstr(y, x-1, buf);
598 if (players[j]) {
599 for (i = 0; i < FIELD_HEIGHT-2 && players[j][i]; i++)
600 mvaddch(y+i+2, x-1, players[j][i]);
601 if (teams[j] != '\0') {
602 mvaddstr(y, x+FIELD_WIDTH+2, "T");
603 for (i = 0; i < FIELD_HEIGHT-2 && teams[j][i]; i++)
604 mvaddch(y+i+2, x+FIELD_WIDTH+2, teams[j][i]);
607 draw_other_field(j+1);
611 if (wide_screen) {
612 x = alt_status_coord[0];
613 y = alt_status_coord[1];
614 mvaddstr(y, x, "Lines:");
615 mvaddstr(y+1, x, "Level:");
616 x = alt_next_coord[0];
617 y = alt_next_coord[1];
618 mvaddstr(y-2, x-1, "Next piece:");
619 move(y-1, x-1);
620 addch(MY_ULCORNER);
621 hline(MY_HLINE, 8);
622 mvaddch(y-1, x+8, MY_URCORNER);
623 move(y, x-1);
624 vline(MY_VLINE, 8);
625 move(y, x+8);
626 vline(MY_VLINE, 8);
627 move(y+8, x-1);
628 addch(MY_LLCORNER);
629 hline(MY_HLINE, 8);
630 mvaddch(y+8, x+8, MY_LRCORNER);
631 } else {
632 x = status_coord[0];
633 y = status_coord[1];
634 mvaddstr(y-1, x, "Next piece:");
635 mvaddstr(y, x, "Lines:");
636 mvaddstr(y+1, x, "Level:");
638 if (playing_game)
639 draw_status();
641 attdefbuf.x = wide_screen ? alt_attdef_coord[0] : attdef_coord[0];
642 attdefbuf.y = wide_screen ? alt_attdef_coord[1] : attdef_coord[1];
643 attdefbuf.width = (other_coord[3][0]-1) - attdefbuf.x;
644 attdefbuf.height = (attdefbot+1) - attdefbuf.y;
645 open_textwin(&attdefbuf);
647 if (gmsg_inputwin) {
648 delwin(gmsg_inputwin);
649 gmsg_inputwin = NULL;
650 draw_gmsg_input(NULL, -1);
653 (void)curs_set(0);
654 screen_refresh();
655 field_redraw = 0;
658 /*************************************************************************/
660 /* Display the player's own field. */
662 static void draw_own_field(void)
664 int x, y, x0, y0;
665 Field *f = &fields[my_playernum-1];
666 int shadow[4] = { -1, -1, -1, -1 };
668 if (dispmode != MODE_FIELDS)
669 return;
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;
677 for (j = 0; j < 4; j++) {
678 if (y+j < 0) {
679 shape += 4;
680 continue;
682 for (i = 0; i < 4; i++) {
683 if (*shape++)
684 shadow[i] = y + j;
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]];
695 if (playing_game && cast_shadow) {
696 PieceData *piece = &piecedata[current_piece][current_rotation];
697 int piece_x = current_x - piece->hot_x;
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;
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);
714 if (gmsg_inputwin) {
715 delwin(gmsg_inputwin);
716 gmsg_inputwin = NULL;
717 draw_gmsg_input(NULL, -1);
719 if (!field_redraw) {
720 (void)curs_set(0);
721 screen_refresh();
725 /*************************************************************************/
727 /* Display another player's field. */
729 static void draw_other_field(int player)
731 int x, y, x0, y0;
732 Field *f;
734 if (dispmode != MODE_FIELDS)
735 return;
736 f = &fields[player-1];
737 if (player > my_playernum)
738 player--;
739 player--;
740 x0 = other_coord[player][0]+1;
741 y0 = other_coord[player][1];
742 for (y = 0; y < 22; y++) {
743 move(y0+y, x0);
744 for (x = 0; x < 12; x++) {
745 addch(tile_chars[(int) (*f)[y][x]]);
748 if (gmsg_inputwin) {
749 delwin(gmsg_inputwin);
750 gmsg_inputwin = NULL;
751 draw_gmsg_input(NULL, -1);
753 if (!field_redraw) {
754 (void)curs_set(0);
755 screen_refresh();
759 /*************************************************************************/
761 /* Display the current game status (level, lines, next piece). */
763 static void draw_status(void)
765 int x, y, i, j;
766 char buf[32], shape[4][4];
768 x = wide_screen ? alt_status_coord[0] : status_coord[0];
769 y = wide_screen ? alt_status_coord[1] : status_coord[1];
770 sprintf(buf, "%d", lines>99999 ? 99999 : lines);
771 mvaddstr(y, x+7, buf);
772 sprintf(buf, "%d", levels[my_playernum]);
773 mvaddstr(y+1, x+7, buf);
774 x = wide_screen ? alt_next_coord[0] : next_coord[0];
775 y = wide_screen ? alt_next_coord[1] : next_coord[1];
776 if (get_shape(next_piece, 0, shape) == 0) {
777 for (j = 0; j < 4; j++) {
778 if (!wide_screen)
779 move(y+j, x);
780 for (i = 0; i < 4; i++) {
781 if (wide_screen) {
782 move(y+j*2, x+i*2);
783 addch(tile_chars[(int) shape[j][i]]);
784 addch(tile_chars[(int) shape[j][i]]);
785 move(y+j*2+1, x+i*2);
786 addch(tile_chars[(int) shape[j][i]]);
787 addch(tile_chars[(int) shape[j][i]]);
788 } else
789 addch(tile_chars[(int) shape[j][i]]);
795 /*************************************************************************/
797 /* Display the special inventory and description of the current special. */
799 static const char *descs[] = {
800 " ",
801 "Add Line ",
802 "Clear Line ",
803 "Nuke Field ",
804 "Clear Random Blocks ",
805 "Switch Fields ",
806 "Clear Special Blocks",
807 "Block Gravity ",
808 "Blockquake ",
809 "Block Bomb "
812 static void draw_specials(void)
814 int x, y, i;
816 if (dispmode != MODE_FIELDS)
817 return;
818 x = own_coord[0];
819 y = own_coord[1]+45;
820 mvaddstr(y, x, descs[specials[0]+1]);
821 move(y+1, x+10);
822 i = 0;
823 while (i < special_capacity && specials[i] >= 0 && x < attdef_coord[0]-1) {
824 addch(tile_chars[specials[i]+6]);
825 i++;
826 x++;
828 while (x < attdef_coord[0]-1) {
829 addch(tile_chars[0]);
830 x++;
832 if (!field_redraw) {
833 (void)curs_set(0);
834 screen_refresh();
838 /*************************************************************************/
840 /* Display an attack/defense message. */
842 static const char *msgs[][2] = {
843 { "cs1", "1 Line Added to All" },
844 { "cs2", "2 Lines Added to All" },
845 { "cs4", "4 Lines Added to All" },
846 { "a", "Add Line" },
847 { "c", "Clear Line" },
848 { "n", "Nuke Field" },
849 { "r", "Clear Random Blocks" },
850 { "s", "Switch Fields" },
851 { "b", "Clear Special Blocks" },
852 { "g", "Block Gravity" },
853 { "q", "Blockquake" },
854 { "o", "Block Bomb" },
855 { NULL }
858 static void draw_attdef(const char *type, int from, int to)
860 int i, width;
861 char buf[512];
863 width = other_coord[4][0] - attdef_coord[0] - 1;
864 for (i = 0; msgs[i][0]; i++) {
865 if (strcmp(type, msgs[i][0]) == 0)
866 break;
868 if (!msgs[i][0])
869 return;
870 strcpy(buf, msgs[i][1]);
871 if (to != 0)
872 sprintf(buf+strlen(buf), " on %s", players[to-1]);
873 if (from == 0)
874 sprintf(buf+strlen(buf), " by Server");
875 else
876 sprintf(buf+strlen(buf), " by %s", players[from-1]);
877 draw_text(BUFFER_ATTDEF, buf);
880 /*************************************************************************/
882 /* Display the in-game text window. */
884 static void draw_gmsg_input(const char *s, int pos)
886 static int start = 0; /* Start of displayed part of input line */
887 static const char *last_s;
888 static int last_pos;
890 if (s)
891 last_s = s;
892 else
893 s = last_s;
894 if (pos >= 0)
895 last_pos = pos;
896 else
897 pos = last_pos;
899 attrset(getcolor(COLOR_WHITE,COLOR_BLACK));
901 if (!gmsg_inputwin) {
902 gmsg_inputpos = scrheight/2 - 1;
903 gmsg_inputheight = 3;
904 gmsg_inputwin =
905 subwin(stdscr, gmsg_inputheight, scrwidth, gmsg_inputpos, 0);
906 werase(gmsg_inputwin);
907 leaveok(gmsg_inputwin, FALSE);
908 leaveok(stdscr, FALSE);
909 mvwaddstr(gmsg_inputwin, 1, 0, "Text>");
912 if (strlen(s) < scrwidth-7) {
913 start = 0;
914 mvwaddstr(gmsg_inputwin, 1, 6, s);
915 wmove(gmsg_inputwin, 1, 6+strlen(s));
916 move(gmsg_inputpos+1, 6+strlen(s));
917 wclrtoeol(gmsg_inputwin);
918 wmove(gmsg_inputwin, 1, 6+pos);
919 move(gmsg_inputpos+1, 6+pos);
920 } else {
921 if (pos < start+8) {
922 start = pos-8;
923 if (start < 0)
924 start = 0;
925 } else if (pos > start + scrwidth-15) {
926 start = pos - (scrwidth-15);
927 if (start > strlen(s) - (scrwidth-7))
928 start = strlen(s) - (scrwidth-7);
930 mvwaddnstr(gmsg_inputwin, 1, 6, s+start, scrwidth-6);
931 wmove(gmsg_inputwin, 1, 6 + (pos-start));
932 move(gmsg_inputpos+1, 6 + (pos-start));
934 (void)curs_set(1);
935 screen_refresh();
938 /*************************************************************************/
940 /* Clear the in-game text window. */
942 static void clear_gmsg_input(void)
944 if (gmsg_inputwin) {
945 delwin(gmsg_inputwin);
946 gmsg_inputwin = NULL;
947 leaveok(stdscr, TRUE);
948 touchline(stdscr, gmsg_inputpos, gmsg_inputheight);
949 setup_fields();
950 (void)curs_set(0);
951 screen_refresh();
955 /*************************************************************************/
956 /*************************** Partyline display ***************************/
957 /*************************************************************************/
959 static void setup_partyline(void)
961 close_textwin(&gmsgbuf);
962 close_textwin(&attdefbuf);
963 clear();
965 attrset(getcolor(COLOR_WHITE,COLOR_BLACK));
967 plinebuf.x = plinebuf.y = 0;
968 plinebuf.width = scrwidth;
969 plinebuf.height = scrheight-4;
970 open_textwin(&plinebuf);
972 move(scrheight-4, 0);
973 hline(MY_HLINE, scrwidth);
974 move(scrheight-3, 0);
975 addstr("> ");
977 move(scrheight-2, 0);
978 hline(MY_HLINE2, scrwidth);
979 attrset(MY_BOLD);
980 move(scrheight-1, 0);
981 addstr("F1=Show Fields F2=Partyline F3=Winlist");
982 move(scrheight-1, scrwidth-8);
983 addstr("F10=Quit");
984 attrset(A_NORMAL);
986 move(scrheight-3, 2);
987 leaveok(stdscr, FALSE);
988 (void)curs_set(1);
989 screen_refresh();
992 /*************************************************************************/
994 static void draw_partyline_input(const char *s, int pos)
996 static int start = 0; /* Start of displayed part of input line */
998 attrset(getcolor(COLOR_WHITE,COLOR_BLACK));
999 if (strlen(s) < scrwidth-3) {
1000 start = 0;
1001 mvaddstr(scrheight-3, 2, s);
1002 move(scrheight-3, 2+strlen(s));
1003 clrtoeol();
1004 move(scrheight-3, 2+pos);
1005 } else {
1006 if (pos < start+8) {
1007 start = pos-8;
1008 if (start < 0)
1009 start = 0;
1010 } else if (pos > start + scrwidth-11) {
1011 start = pos - (scrwidth-11);
1012 if (start > strlen(s) - (scrwidth-3))
1013 start = strlen(s) - (scrwidth-3);
1015 mvaddnstr(scrheight-3, 2, s+start, scrwidth-2);
1016 move(scrheight-3, 2 + (pos-start));
1018 screen_refresh();
1021 /*************************************************************************/
1022 /**************************** Winlist display ****************************/
1023 /*************************************************************************/
1025 static void setup_winlist(void)
1027 int i, x;
1028 char buf[32];
1030 leaveok(stdscr, TRUE);
1031 close_textwin(&plinebuf);
1032 clear();
1033 attrset(getcolor(COLOR_WHITE,COLOR_BLACK));
1035 for (i = 0; i < MAXWINLIST && *winlist[i].name; i++) {
1036 x = scrwidth/2 - strlen(winlist[i].name);
1037 if (x < 0)
1038 x = 0;
1039 if (winlist[i].team) {
1040 if (x < 4)
1041 x = 4;
1042 mvaddstr(i*2, x-4, "<T>");
1044 mvaddstr(i*2, x, winlist[i].name);
1045 snprintf(buf, sizeof(buf), "%4d", winlist[i].points);
1046 if (winlist[i].games) {
1047 int avg100 = winlist[i].points*100 / winlist[i].games;
1048 snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf),
1049 " %d.%02d",avg100/100, avg100%100);
1051 x += strlen(winlist[i].name) + 2;
1052 if (x > scrwidth - strlen(buf))
1053 x = scrwidth - strlen(buf);
1054 mvaddstr(i*2, x, buf);
1057 move(scrheight-2, 0);
1058 hline(MY_HLINE2, scrwidth);
1059 attrset(MY_BOLD);
1060 move(scrheight-1, 0);
1061 addstr("F1=Show Fields F2=Partyline F3=Winlist");
1062 move(scrheight-1, scrwidth-8);
1063 addstr("F10=Quit");
1064 attrset(A_NORMAL);
1066 (void)curs_set(0);
1067 screen_refresh();
1070 /*************************************************************************/
1071 /************************** Interface declaration ************************/
1072 /*************************************************************************/
1074 Interface tty_interface = {
1076 wait_for_input,
1078 screen_setup,
1079 screen_refresh,
1080 screen_redraw,
1082 draw_text,
1083 clear_text,
1085 setup_fields,
1086 draw_own_field,
1087 draw_other_field,
1088 draw_status,
1089 draw_specials,
1090 draw_attdef,
1091 draw_gmsg_input,
1092 clear_gmsg_input,
1094 setup_partyline,
1095 draw_partyline_input,
1097 setup_winlist
1100 /*************************************************************************/