screen.c revision 1.11
1/* $NetBSD: screen.c,v 1.11 1999/09/08 21:18:00 jsm 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 63static cell curscreen[B_SIZE]; /* 1 => standout (or otherwise marked) */ 64static int curscore; 65static int isset; /* true => terminal is in game mode */ 66static struct termios oldtt; 67static void (*tstp) __P((int)); 68 69static void scr_stop __P((int)); 70static void stopset __P((int)); 71 72 73/* 74 * Capabilities from TERMCAP. 75 */ 76char PC, *BC, *UP; /* tgoto requires globals: ugh! */ 77short ospeed; 78 79static char 80 *bcstr, /* backspace char */ 81 *CEstr, /* clear to end of line */ 82 *CLstr, /* clear screen */ 83 *CMstr, /* cursor motion string */ 84#ifdef unneeded 85 *CRstr, /* "\r" equivalent */ 86#endif 87 *HOstr, /* cursor home */ 88 *LLstr, /* last line, first column */ 89 *pcstr, /* pad character */ 90 *TEstr, /* end cursor motion mode */ 91 *TIstr; /* begin cursor motion mode */ 92char 93 *SEstr, /* end standout mode */ 94 *SOstr; /* begin standout mode */ 95static int 96 COnum, /* co# value */ 97 LInum, /* li# value */ 98 MSflag; /* can move in standout mode */ 99 100 101struct tcsinfo { /* termcap string info; some abbrevs above */ 102 char tcname[3]; 103 char **tcaddr; 104} tcstrings[] = { 105 {"bc", &bcstr}, 106 {"ce", &CEstr}, 107 {"cl", &CLstr}, 108 {"cm", &CMstr}, 109#ifdef unneeded 110 {"cr", &CRstr}, 111#endif 112 {"le", &BC}, /* move cursor left one space */ 113 {"pc", &pcstr}, 114 {"se", &SEstr}, 115 {"so", &SOstr}, 116 {"te", &TEstr}, 117 {"ti", &TIstr}, 118 {"up", &UP}, /* cursor up */ 119 { {0}, NULL} 120}; 121 122/* This is where we will actually stuff the information */ 123 124static char combuf[1024], tbuf[1024]; 125 126 127/* 128 * Routine used by tputs(). 129 */ 130void 131put(c) 132 int c; 133{ 134 135 (void) putchar(c); 136} 137 138/* 139 * putstr() is for unpadded strings (either as in termcap(5) or 140 * simply literal strings); putpad() is for padded strings with 141 * count=1. (See screen.h for putpad().) 142 */ 143#define putstr(s) (void)fputs(s, stdout) 144#define moveto(r, c) putpad(tgoto(CMstr, c, r)) 145 146/* 147 * Set up from termcap. 148 */ 149void 150scr_init() 151{ 152 static int bsflag, xsflag, sgnum; 153#ifdef unneeded 154 static int ncflag; 155#endif 156 char *term, *fill; 157 static struct tcninfo { /* termcap numeric and flag info */ 158 char tcname[3]; 159 int *tcaddr; 160 } tcflags[] = { 161 {"bs", &bsflag}, 162 {"ms", &MSflag}, 163#ifdef unneeded 164 {"nc", &ncflag}, 165#endif 166 {"xs", &xsflag}, 167 { {0}, NULL} 168 }, tcnums[] = { 169 {"co", &COnum}, 170 {"li", &LInum}, 171 {"sg", &sgnum}, 172 { {0}, NULL} 173 }; 174 175 if ((term = getenv("TERM")) == NULL) 176 stop("you must set the TERM environment variable"); 177 if (tgetent(tbuf, term) <= 0) 178 stop("cannot find your termcap"); 179 fill = combuf; 180 { 181 register struct tcsinfo *p; 182 183 for (p = tcstrings; p->tcaddr; p++) 184 *p->tcaddr = tgetstr(p->tcname, &fill); 185 } 186 { 187 register struct tcninfo *p; 188 189 for (p = tcflags; p->tcaddr; p++) 190 *p->tcaddr = tgetflag(p->tcname); 191 for (p = tcnums; p->tcaddr; p++) 192 *p->tcaddr = tgetnum(p->tcname); 193 } 194 if (bsflag) 195 BC = "\b"; 196 else if (BC == NULL && bcstr != NULL) 197 BC = bcstr; 198 if (CLstr == NULL) 199 stop("cannot clear screen"); 200 if (CMstr == NULL || UP == NULL || BC == NULL) 201 stop("cannot do random cursor positioning via tgoto()"); 202 PC = pcstr ? *pcstr : 0; 203 if (sgnum >= 0 || xsflag) 204 SOstr = SEstr = NULL; 205#ifdef unneeded 206 if (ncflag) 207 CRstr = NULL; 208 else if (CRstr == NULL) 209 CRstr = "\r"; 210#endif 211} 212 213/* this foolery is needed to modify tty state `atomically' */ 214static jmp_buf scr_onstop; 215 216static void 217stopset(sig) 218 int sig; 219{ 220 sigset_t sigset; 221 222 (void) signal(sig, SIG_DFL); 223 (void) kill(getpid(), sig); 224 sigemptyset(&sigset); 225 sigaddset(&sigset, sig); 226 (void) sigprocmask(SIG_UNBLOCK, &sigset, (sigset_t *)0); 227 longjmp(scr_onstop, 1); 228} 229 230static void 231scr_stop(sig) 232 int sig; 233{ 234 sigset_t sigset; 235 236 scr_end(); 237 (void) kill(getpid(), sig); 238 sigemptyset(&sigset); 239 sigaddset(&sigset, sig); 240 (void) sigprocmask(SIG_UNBLOCK, &sigset, (sigset_t *)0); 241 scr_set(); 242 scr_msg(key_msg, 1); 243} 244 245/* 246 * Set up screen mode. 247 */ 248void 249scr_set() 250{ 251 struct winsize ws; 252 struct termios newtt; 253 sigset_t sigset, osigset; 254 void (*ttou) __P((int)); 255 256 sigemptyset(&sigset); 257 sigaddset(&sigset, SIGTSTP); 258 sigaddset(&sigset, SIGTTOU); 259 (void) sigprocmask(SIG_BLOCK, &sigset, &osigset); 260 if ((tstp = signal(SIGTSTP, stopset)) == SIG_IGN) 261 (void) signal(SIGTSTP, SIG_IGN); 262 if ((ttou = signal(SIGTTOU, stopset)) == SIG_IGN) 263 (void) signal(SIGTTOU, SIG_IGN); 264 /* 265 * At last, we are ready to modify the tty state. If 266 * we stop while at it, stopset() above will longjmp back 267 * to the setjmp here and we will start over. 268 */ 269 (void) setjmp(scr_onstop); 270 (void) sigprocmask(SIG_SETMASK, &osigset, (sigset_t *)0); 271 Rows = 0, Cols = 0; 272 if (ioctl(0, TIOCGWINSZ, &ws) == 0) { 273 Rows = ws.ws_row; 274 Cols = ws.ws_col; 275 } 276 if (Rows == 0) 277 Rows = LInum; 278 if (Cols == 0) 279 Cols = COnum; 280 if (Rows < MINROWS || Cols < MINCOLS) { 281 (void) fprintf(stderr, 282 "the screen is too small: must be at least %dx%d, ", 283 MINCOLS, MINROWS); 284 stop(""); /* stop() supplies \n */ 285 } 286 if (tcgetattr(0, &oldtt) < 0) 287 stop("tcgetattr() fails"); 288 newtt = oldtt; 289 newtt.c_lflag &= ~(ICANON|ECHO); 290 newtt.c_oflag &= ~OXTABS; 291 if (tcsetattr(0, TCSADRAIN, &newtt) < 0) 292 stop("tcsetattr() fails"); 293 ospeed = cfgetospeed(&newtt); 294 (void) sigprocmask(SIG_BLOCK, &sigset, &osigset); 295 296 /* 297 * We made it. We are now in screen mode, modulo TIstr 298 * (which we will fix immediately). 299 */ 300 if (TIstr) 301 putstr(TIstr); /* termcap(5) says this is not padded */ 302 if (tstp != SIG_IGN) 303 (void) signal(SIGTSTP, scr_stop); 304 if (ttou != SIG_IGN) 305 (void) signal(SIGTTOU, ttou); 306 307 isset = 1; 308 (void) sigprocmask(SIG_SETMASK, &osigset, (sigset_t *)0); 309 scr_clear(); 310} 311 312/* 313 * End screen mode. 314 */ 315void 316scr_end() 317{ 318 sigset_t sigset, osigset; 319 320 sigemptyset(&sigset); 321 sigaddset(&sigset, SIGTSTP); 322 sigaddset(&sigset, SIGTTOU); 323 (void) sigprocmask(SIG_BLOCK, &sigset, &osigset); 324 /* move cursor to last line */ 325 if (LLstr) 326 putstr(LLstr); /* termcap(5) says this is not padded */ 327 else 328 moveto(Rows - 1, 0); 329 /* exit screen mode */ 330 if (TEstr) 331 putstr(TEstr); /* termcap(5) says this is not padded */ 332 (void) fflush(stdout); 333 (void) tcsetattr(0, TCSADRAIN, &oldtt); 334 isset = 0; 335 /* restore signals */ 336 (void) signal(SIGTSTP, tstp); 337 (void) sigprocmask(SIG_SETMASK, &osigset, (sigset_t *)0); 338} 339 340void 341stop(why) 342 const char *why; 343{ 344 345 if (isset) 346 scr_end(); 347 (void) fprintf(stderr, "aborting: %s\n", why); 348 exit(1); 349} 350 351/* 352 * Clear the screen, forgetting the current contents in the process. 353 */ 354void 355scr_clear() 356{ 357 358 putpad(CLstr); 359 curscore = -1; 360 memset((char *)curscreen, 0, sizeof(curscreen)); 361} 362 363#if vax && !__GNUC__ 364typedef int regcell; /* pcc is bad at `register char', etc */ 365#else 366typedef cell regcell; 367#endif 368 369/* 370 * Update the screen. 371 */ 372void 373scr_update() 374{ 375 register cell *bp, *sp; 376 register regcell so, cur_so = 0; 377 register int i, ccol, j; 378 sigset_t sigset, osigset; 379 static const struct shape *lastshape; 380 381 sigemptyset(&sigset); 382 sigaddset(&sigset, SIGTSTP); 383 (void) sigprocmask(SIG_BLOCK, &sigset, &osigset); 384 385 /* always leave cursor after last displayed point */ 386 curscreen[D_LAST * B_COLS - 1] = -1; 387 388 if (score != curscore) { 389 if (HOstr) 390 putpad(HOstr); 391 else 392 moveto(0, 0); 393 (void) printf("Score: %d", score); 394 curscore = score; 395 } 396 397 /* draw preview of nextpattern */ 398 if (showpreview && (nextshape != lastshape)) { 399 int i; 400 static int r=5, c=2; 401 int tr, tc, t; 402 403 lastshape = nextshape; 404 405 /* clean */ 406 putpad(SEstr); 407 moveto(r-1, c-1); putstr(" "); 408 moveto(r, c-1); putstr(" "); 409 moveto(r+1, c-1); putstr(" "); 410 moveto(r+2, c-1); putstr(" "); 411 412 moveto(r-3, c-2); 413 putstr("Next shape:"); 414 415 /* draw */ 416 putpad(SOstr); 417 moveto(r, 2*c); 418 putstr(" "); 419 for(i=0; i<3; i++) { 420 t = c + r*B_COLS; 421 t += nextshape->off[i]; 422 423 tr = t / B_COLS; 424 tc = t % B_COLS; 425 426 moveto(tr, 2*tc); 427 putstr(" "); 428 } 429 putpad(SEstr); 430 } 431 432 bp = &board[D_FIRST * B_COLS]; 433 sp = &curscreen[D_FIRST * B_COLS]; 434 for (j = D_FIRST; j < D_LAST; j++) { 435 ccol = -1; 436 for (i = 0; i < B_COLS; bp++, sp++, i++) { 437 if (*sp == (so = *bp)) 438 continue; 439 *sp = so; 440 if (i != ccol) { 441 if (cur_so && MSflag) { 442 putpad(SEstr); 443 cur_so = 0; 444 } 445 moveto(RTOD(j), CTOD(i)); 446 } 447 if (SOstr) { 448 if (so != cur_so) { 449 putpad(so ? SOstr : SEstr); 450 cur_so = so; 451 } 452 putstr(" "); 453 } else 454 putstr(so ? "XX" : " "); 455 ccol = i + 1; 456 /* 457 * Look ahead a bit, to avoid extra motion if 458 * we will be redrawing the cell after the next. 459 * Motion probably takes four or more characters, 460 * so we save even if we rewrite two cells 461 * `unnecessarily'. Skip it all, though, if 462 * the next cell is a different color. 463 */ 464#define STOP (B_COLS - 3) 465 if (i > STOP || sp[1] != bp[1] || so != bp[1]) 466 continue; 467 if (sp[2] != bp[2]) 468 sp[1] = -1; 469 else if (i < STOP && so == bp[2] && sp[3] != bp[3]) { 470 sp[2] = -1; 471 sp[1] = -1; 472 } 473 } 474 } 475 if (cur_so) 476 putpad(SEstr); 477 (void) fflush(stdout); 478 (void) sigprocmask(SIG_SETMASK, &osigset, (sigset_t *)0); 479} 480 481/* 482 * Write a message (set!=0), or clear the same message (set==0). 483 * (We need its length in case we have to overwrite with blanks.) 484 */ 485void 486scr_msg(s, set) 487 register char *s; 488 int set; 489{ 490 491 if (set || CEstr == NULL) { 492 register int l = strlen(s); 493 494 moveto(Rows - 2, ((Cols - l) >> 1) - 1); 495 if (set) 496 putstr(s); 497 else 498 while (--l >= 0) 499 (void) putchar(' '); 500 } else { 501 moveto(Rows - 2, 0); 502 putpad(CEstr); 503 } 504} 505