1 /* $NetBSD: screen.c,v 1.17 2002/06/02 22:17:38 wiz Exp $ */ 2 3 /*- 4 * Copyright (c) 1992, 1993 5 * The Regents of the University of California. All rights reserved. 6 * 7 * This code is derived from software contributed to Berkeley by 8 * Chris Torek and Darren F. Provine. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 3. All advertising materials mentioning features or use of this software 19 * must display the following acknowledgement: 20 * This product includes software developed by the University of 21 * California, Berkeley and its contributors. 22 * 4. Neither the name of the University nor the names of its contributors 23 * may be used to endorse or promote products derived from this software 24 * without specific prior written permission. 25 * 26 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 27 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 28 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 29 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 30 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 31 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 32 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 33 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 34 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 35 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 36 * SUCH DAMAGE. 37 * 38 * @(#)screen.c 8.1 (Berkeley) 5/31/93 39 */ 40 41 /* 42 * Tetris screen control. 43 */ 44 45 #include <sys/ioctl.h> 46 47 #include <setjmp.h> 48 #include <signal.h> 49 #include <stdio.h> 50 #include <stdlib.h> 51 #include <string.h> 52 #include <termcap.h> 53 #include <termios.h> 54 #include <unistd.h> 55 56 #ifndef sigmask 57 #define sigmask(s) (1 << ((s) - 1)) 58 #endif 59 60 #include "screen.h" 61 #include "tetris.h" 62 63 static cell curscreen[B_SIZE]; /* 1 => standout (or otherwise marked) */ 64 static int curscore; 65 static int isset; /* true => terminal is in game mode */ 66 static struct termios oldtt; 67 static void (*tstp) __P((int)); 68 69 static void scr_stop __P((int)); 70 static void stopset __P((int)) __attribute__((__noreturn__)); 71 72 73 /* 74 * Capabilities from TERMCAP. 75 */ 76 short ospeed; 77 78 static char 79 *bcstr, /* backspace char */ 80 *CEstr, /* clear to end of line */ 81 *CLstr, /* clear screen */ 82 *CMstr, /* cursor motion string */ 83 #ifdef unneeded 84 *CRstr, /* "\r" equivalent */ 85 #endif 86 *HOstr, /* cursor home */ 87 *LLstr, /* last line, first column */ 88 *pcstr, /* pad character */ 89 *TEstr, /* end cursor motion mode */ 90 *TIstr; /* begin cursor motion mode */ 91 char 92 *SEstr, /* end standout mode */ 93 *SOstr; /* begin standout mode */ 94 static int 95 COnum, /* co# value */ 96 LInum, /* li# value */ 97 MSflag; /* can move in standout mode */ 98 99 100 struct tcsinfo { /* termcap string info; some abbrevs above */ 101 char tcname[3]; 102 char **tcaddr; 103 } tcstrings[] = { 104 {"bc", &bcstr}, 105 {"ce", &CEstr}, 106 {"cl", &CLstr}, 107 {"cm", &CMstr}, 108 #ifdef unneeded 109 {"cr", &CRstr}, 110 #endif 111 {"le", &BC}, /* move cursor left one space */ 112 {"pc", &pcstr}, 113 {"se", &SEstr}, 114 {"so", &SOstr}, 115 {"te", &TEstr}, 116 {"ti", &TIstr}, 117 {"up", &UP}, /* cursor up */ 118 { {0}, NULL} 119 }; 120 121 /* This is where we will actually stuff the information */ 122 123 static struct tinfo *info; 124 125 /* 126 * Routine used by tputs(). 127 */ 128 int 129 put(c) 130 int c; 131 { 132 133 return (putchar(c)); 134 } 135 136 /* 137 * putstr() is for unpadded strings (either as in termcap(5) or 138 * simply literal strings); putpad() is for padded strings with 139 * count=1. (See screen.h for putpad().) 140 */ 141 #define putstr(s) (void)fputs(s, stdout) 142 143 void 144 moveto(int r, int c) 145 { 146 char buf[256]; 147 148 if (t_goto(info, CMstr, c, r, buf, 255) == 0) 149 putpad(buf); 150 } 151 152 /* 153 * Set up from termcap. 154 */ 155 void 156 scr_init() 157 { 158 static int bsflag, xsflag, sgnum; 159 #ifdef unneeded 160 static int ncflag; 161 #endif 162 char *term; 163 static struct tcninfo { /* termcap numeric and flag info */ 164 char tcname[3]; 165 int *tcaddr; 166 } tcflags[] = { 167 {"bs", &bsflag}, 168 {"ms", &MSflag}, 169 #ifdef unneeded 170 {"nc", &ncflag}, 171 #endif 172 {"xs", &xsflag}, 173 { {0}, NULL} 174 }, tcnums[] = { 175 {"co", &COnum}, 176 {"li", &LInum}, 177 {"sg", &sgnum}, 178 { {0}, NULL} 179 }; 180 181 if ((term = getenv("TERM")) == NULL) 182 stop("you must set the TERM environment variable"); 183 if (t_getent(&info, term) <= 0) 184 stop("cannot find your termcap"); 185 { 186 struct tcsinfo *p; 187 188 for (p = tcstrings; p->tcaddr; p++) 189 *p->tcaddr = t_agetstr(info, p->tcname); 190 } 191 { 192 struct tcninfo *p; 193 194 for (p = tcflags; p->tcaddr; p++) 195 *p->tcaddr = t_getflag(info, p->tcname); 196 for (p = tcnums; p->tcaddr; p++) 197 *p->tcaddr = t_getnum(info, p->tcname); 198 } 199 if (bsflag) 200 BC = "\b"; 201 else if (BC == NULL && bcstr != NULL) 202 BC = bcstr; 203 if (CLstr == NULL) 204 stop("cannot clear screen"); 205 if (CMstr == NULL || UP == NULL || BC == NULL) 206 stop("cannot do random cursor positioning via tgoto()"); 207 PC = pcstr ? *pcstr : 0; 208 if (sgnum >= 0 || xsflag) 209 SOstr = SEstr = NULL; 210 #ifdef unneeded 211 if (ncflag) 212 CRstr = NULL; 213 else if (CRstr == NULL) 214 CRstr = "\r"; 215 #endif 216 } 217 218 /* this foolery is needed to modify tty state `atomically' */ 219 static jmp_buf scr_onstop; 220 221 static void 222 stopset(sig) 223 int sig; 224 { 225 sigset_t sigset; 226 227 (void) signal(sig, SIG_DFL); 228 (void) kill(getpid(), sig); 229 sigemptyset(&sigset); 230 sigaddset(&sigset, sig); 231 (void) sigprocmask(SIG_UNBLOCK, &sigset, (sigset_t *)0); 232 longjmp(scr_onstop, 1); 233 } 234 235 static void 236 scr_stop(sig) 237 int sig; 238 { 239 sigset_t sigset; 240 241 scr_end(); 242 (void) kill(getpid(), sig); 243 sigemptyset(&sigset); 244 sigaddset(&sigset, sig); 245 (void) sigprocmask(SIG_UNBLOCK, &sigset, (sigset_t *)0); 246 scr_set(); 247 scr_msg(key_msg, 1); 248 } 249 250 /* 251 * Set up screen mode. 252 */ 253 void 254 scr_set() 255 { 256 struct winsize ws; 257 struct termios newtt; 258 sigset_t sigset, osigset; 259 void (*ttou) __P((int)); 260 261 sigemptyset(&sigset); 262 sigaddset(&sigset, SIGTSTP); 263 sigaddset(&sigset, SIGTTOU); 264 (void) sigprocmask(SIG_BLOCK, &sigset, &osigset); 265 if ((tstp = signal(SIGTSTP, stopset)) == SIG_IGN) 266 (void) signal(SIGTSTP, SIG_IGN); 267 if ((ttou = signal(SIGTTOU, stopset)) == SIG_IGN) 268 (void) signal(SIGTTOU, SIG_IGN); 269 /* 270 * At last, we are ready to modify the tty state. If 271 * we stop while at it, stopset() above will longjmp back 272 * to the setjmp here and we will start over. 273 */ 274 (void) setjmp(scr_onstop); 275 (void) sigprocmask(SIG_SETMASK, &osigset, (sigset_t *)0); 276 Rows = 0, Cols = 0; 277 if (ioctl(0, TIOCGWINSZ, &ws) == 0) { 278 Rows = ws.ws_row; 279 Cols = ws.ws_col; 280 } 281 if (Rows == 0) 282 Rows = LInum; 283 if (Cols == 0) 284 Cols = COnum; 285 if (Rows < MINROWS || Cols < MINCOLS) { 286 (void) fprintf(stderr, 287 "the screen is too small: must be at least %dx%d, ", 288 MINCOLS, MINROWS); 289 stop(""); /* stop() supplies \n */ 290 } 291 if (tcgetattr(0, &oldtt) < 0) 292 stop("tcgetattr() fails"); 293 newtt = oldtt; 294 newtt.c_lflag &= ~(ICANON|ECHO); 295 newtt.c_oflag &= ~OXTABS; 296 if (tcsetattr(0, TCSADRAIN, &newtt) < 0) 297 stop("tcsetattr() fails"); 298 ospeed = cfgetospeed(&newtt); 299 (void) sigprocmask(SIG_BLOCK, &sigset, &osigset); 300 301 /* 302 * We made it. We are now in screen mode, modulo TIstr 303 * (which we will fix immediately). 304 */ 305 if (TIstr) 306 putstr(TIstr); /* termcap(5) says this is not padded */ 307 if (tstp != SIG_IGN) 308 (void) signal(SIGTSTP, scr_stop); 309 if (ttou != SIG_IGN) 310 (void) signal(SIGTTOU, ttou); 311 312 isset = 1; 313 (void) sigprocmask(SIG_SETMASK, &osigset, (sigset_t *)0); 314 scr_clear(); 315 } 316 317 /* 318 * End screen mode. 319 */ 320 void 321 scr_end() 322 { 323 sigset_t sigset, osigset; 324 325 sigemptyset(&sigset); 326 sigaddset(&sigset, SIGTSTP); 327 sigaddset(&sigset, SIGTTOU); 328 (void) sigprocmask(SIG_BLOCK, &sigset, &osigset); 329 /* move cursor to last line */ 330 if (LLstr) 331 putstr(LLstr); /* termcap(5) says this is not padded */ 332 else 333 moveto(Rows - 1, 0); 334 /* exit screen mode */ 335 if (TEstr) 336 putstr(TEstr); /* termcap(5) says this is not padded */ 337 (void) fflush(stdout); 338 (void) tcsetattr(0, TCSADRAIN, &oldtt); 339 isset = 0; 340 /* restore signals */ 341 (void) signal(SIGTSTP, tstp); 342 (void) sigprocmask(SIG_SETMASK, &osigset, (sigset_t *)0); 343 } 344 345 void 346 stop(why) 347 const char *why; 348 { 349 350 if (isset) 351 scr_end(); 352 (void) fprintf(stderr, "aborting: %s\n", why); 353 exit(1); 354 } 355 356 /* 357 * Clear the screen, forgetting the current contents in the process. 358 */ 359 void 360 scr_clear() 361 { 362 363 putpad(CLstr); 364 curscore = -1; 365 memset((char *)curscreen, 0, sizeof(curscreen)); 366 } 367 368 #if vax && !__GNUC__ 369 typedef int regcell; /* pcc is bad at `register char', etc */ 370 #else 371 typedef cell regcell; 372 #endif 373 374 /* 375 * Update the screen. 376 */ 377 void 378 scr_update() 379 { 380 cell *bp, *sp; 381 regcell so, cur_so = 0; 382 int i, ccol, j; 383 sigset_t sigset, osigset; 384 static const struct shape *lastshape; 385 386 sigemptyset(&sigset); 387 sigaddset(&sigset, SIGTSTP); 388 (void) sigprocmask(SIG_BLOCK, &sigset, &osigset); 389 390 /* always leave cursor after last displayed point */ 391 curscreen[D_LAST * B_COLS - 1] = -1; 392 393 if (score != curscore) { 394 if (HOstr) 395 putpad(HOstr); 396 else 397 moveto(0, 0); 398 (void) printf("Score: %d", score); 399 curscore = score; 400 } 401 402 /* draw preview of nextpattern */ 403 if (showpreview && (nextshape != lastshape)) { 404 int i; 405 static int r=5, c=2; 406 int tr, tc, t; 407 408 lastshape = nextshape; 409 410 /* clean */ 411 putpad(SEstr); 412 moveto(r-1, c-1); putstr(" "); 413 moveto(r, c-1); putstr(" "); 414 moveto(r+1, c-1); putstr(" "); 415 moveto(r+2, c-1); putstr(" "); 416 417 moveto(r-3, c-2); 418 putstr("Next shape:"); 419 420 /* draw */ 421 putpad(SOstr); 422 moveto(r, 2*c); 423 putstr(" "); 424 for(i=0; i<3; i++) { 425 t = c + r*B_COLS; 426 t += nextshape->off[i]; 427 428 tr = t / B_COLS; 429 tc = t % B_COLS; 430 431 moveto(tr, 2*tc); 432 putstr(" "); 433 } 434 putpad(SEstr); 435 } 436 437 bp = &board[D_FIRST * B_COLS]; 438 sp = &curscreen[D_FIRST * B_COLS]; 439 for (j = D_FIRST; j < D_LAST; j++) { 440 ccol = -1; 441 for (i = 0; i < B_COLS; bp++, sp++, i++) { 442 if (*sp == (so = *bp)) 443 continue; 444 *sp = so; 445 if (i != ccol) { 446 if (cur_so && MSflag) { 447 putpad(SEstr); 448 cur_so = 0; 449 } 450 moveto(RTOD(j), CTOD(i)); 451 } 452 if (SOstr) { 453 if (so != cur_so) { 454 putpad(so ? SOstr : SEstr); 455 cur_so = so; 456 } 457 putstr(" "); 458 } else 459 putstr(so ? "XX" : " "); 460 ccol = i + 1; 461 /* 462 * Look ahead a bit, to avoid extra motion if 463 * we will be redrawing the cell after the next. 464 * Motion probably takes four or more characters, 465 * so we save even if we rewrite two cells 466 * `unnecessarily'. Skip it all, though, if 467 * the next cell is a different color. 468 */ 469 #define STOP (B_COLS - 3) 470 if (i > STOP || sp[1] != bp[1] || so != bp[1]) 471 continue; 472 if (sp[2] != bp[2]) 473 sp[1] = -1; 474 else if (i < STOP && so == bp[2] && sp[3] != bp[3]) { 475 sp[2] = -1; 476 sp[1] = -1; 477 } 478 } 479 } 480 if (cur_so) 481 putpad(SEstr); 482 (void) fflush(stdout); 483 (void) sigprocmask(SIG_SETMASK, &osigset, (sigset_t *)0); 484 } 485 486 /* 487 * Write a message (set!=0), or clear the same message (set==0). 488 * (We need its length in case we have to overwrite with blanks.) 489 */ 490 void 491 scr_msg(s, set) 492 char *s; 493 int set; 494 { 495 496 if (set || CEstr == NULL) { 497 int l = strlen(s); 498 499 moveto(Rows - 2, ((Cols - l) >> 1) - 1); 500 if (set) 501 putstr(s); 502 else 503 while (--l >= 0) 504 (void) putchar(' '); 505 } else { 506 moveto(Rows - 2, 0); 507 putpad(CEstr); 508 } 509 } 510