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