1 /*- 2 * Copyright (c) 1992, 1993 3 * The Regents of the University of California. All rights reserved. 4 * 5 * This code is derived from software contributed to Berkeley by 6 * Chris Torek and Darren F. Provine. 7 * 8 * %sccs.include.redist.c% 9 * 10 * @(#)screen.c 8.1 (Berkeley) 05/31/93 11 */ 12 13 /* 14 * Tetris screen control. 15 */ 16 17 #include <sgtty.h> 18 #include <sys/ioctl.h> 19 20 #include <setjmp.h> 21 #include <signal.h> 22 #include <stdio.h> 23 #include <stdlib.h> 24 #include <string.h> 25 #include <unistd.h> 26 27 #ifndef sigmask 28 #define sigmask(s) (1 << ((s) - 1)) 29 #endif 30 31 #include "screen.h" 32 #include "tetris.h" 33 34 /* 35 * XXX - need a <termcap.h> 36 */ 37 int tgetent __P((char *, const char *)); 38 int tgetflag __P((const char *)); 39 int tgetnum __P((const char *)); 40 int tputs __P((const char *, int, int (*)(int))); 41 42 static cell curscreen[B_SIZE]; /* 1 => standout (or otherwise marked) */ 43 static int curscore; 44 static int isset; /* true => terminal is in game mode */ 45 static struct sgttyb oldtt; 46 static void (*tstp)(); 47 48 char *tgetstr(), *tgoto(); 49 50 51 /* 52 * Capabilities from TERMCAP. 53 */ 54 char PC, *BC, *UP; /* tgoto requires globals: ugh! */ 55 short ospeed; 56 57 static char 58 *bcstr, /* backspace char */ 59 *CEstr, /* clear to end of line */ 60 *CLstr, /* clear screen */ 61 *CMstr, /* cursor motion string */ 62 #ifdef unneeded 63 *CRstr, /* "\r" equivalent */ 64 #endif 65 *HOstr, /* cursor home */ 66 *LLstr, /* last line, first column */ 67 *pcstr, /* pad character */ 68 *TEstr, /* end cursor motion mode */ 69 *TIstr; /* begin cursor motion mode */ 70 char 71 *SEstr, /* end standout mode */ 72 *SOstr; /* begin standout mode */ 73 static int 74 COnum, /* co# value */ 75 LInum, /* li# value */ 76 MSflag; /* can move in standout mode */ 77 78 79 struct tcsinfo { /* termcap string info; some abbrevs above */ 80 char tcname[3]; 81 char **tcaddr; 82 } tcstrings[] = { 83 "bc", &bcstr, 84 "ce", &CEstr, 85 "cl", &CLstr, 86 "cm", &CMstr, 87 #ifdef unneeded 88 "cr", &CRstr, 89 #endif 90 "le", &BC, /* move cursor left one space */ 91 "pc", &pcstr, 92 "se", &SEstr, 93 "so", &SOstr, 94 "te", &TEstr, 95 "ti", &TIstr, 96 "up", &UP, /* cursor up */ 97 0 98 }; 99 100 /* This is where we will actually stuff the information */ 101 102 static char combuf[1024], tbuf[1024]; 103 104 105 /* 106 * Routine used by tputs(). 107 */ 108 int 109 put(c) 110 int c; 111 { 112 113 return (putchar(c)); 114 } 115 116 /* 117 * putstr() is for unpadded strings (either as in termcap(5) or 118 * simply literal strings); putpad() is for padded strings with 119 * count=1. (See screen.h for putpad().) 120 */ 121 #define putstr(s) (void)fputs(s, stdout) 122 #define moveto(r, c) putpad(tgoto(CMstr, c, r)) 123 124 /* 125 * Set up from termcap. 126 */ 127 void 128 scr_init() 129 { 130 static int bsflag, xsflag, sgnum; 131 #ifdef unneeded 132 static int ncflag; 133 #endif 134 char *term, *fill; 135 static struct tcninfo { /* termcap numeric and flag info */ 136 char tcname[3]; 137 int *tcaddr; 138 } tcflags[] = { 139 "bs", &bsflag, 140 "ms", &MSflag, 141 #ifdef unneeded 142 "nc", &ncflag, 143 #endif 144 "xs", &xsflag, 145 0 146 }, tcnums[] = { 147 "co", &COnum, 148 "li", &LInum, 149 "sg", &sgnum, 150 0 151 }; 152 153 if ((term = getenv("TERM")) == NULL) 154 stop("you must set the TERM environment variable"); 155 if (tgetent(tbuf, term) <= 0) 156 stop("cannot find your termcap"); 157 fill = combuf; 158 { 159 register struct tcsinfo *p; 160 161 for (p = tcstrings; p->tcaddr; p++) 162 *p->tcaddr = tgetstr(p->tcname, &fill); 163 } 164 { 165 register struct tcninfo *p; 166 167 for (p = tcflags; p->tcaddr; p++) 168 *p->tcaddr = tgetflag(p->tcname); 169 for (p = tcnums; p->tcaddr; p++) 170 *p->tcaddr = tgetnum(p->tcname); 171 } 172 if (bsflag) 173 BC = "\b"; 174 else if (BC == NULL && bcstr != NULL) 175 BC = bcstr; 176 if (CLstr == NULL) 177 stop("cannot clear screen"); 178 if (CMstr == NULL || UP == NULL || BC == NULL) 179 stop("cannot do random cursor positioning via tgoto()"); 180 PC = pcstr ? *pcstr : 0; 181 if (sgnum >= 0 || xsflag) 182 SOstr = SEstr = NULL; 183 #ifdef unneeded 184 if (ncflag) 185 CRstr = NULL; 186 else if (CRstr == NULL) 187 CRstr = "\r"; 188 #endif 189 } 190 191 /* this foolery is needed to modify tty state `atomically' */ 192 static jmp_buf scr_onstop; 193 194 #define sigunblock(mask) sigsetmask(sigblock(0) & ~(mask)) 195 196 static void 197 stopset(sig) 198 int sig; 199 { 200 (void) signal(sig, SIG_DFL); 201 (void) kill(getpid(), sig); 202 (void) sigunblock(sigmask(sig)); 203 longjmp(scr_onstop, 1); 204 } 205 206 static void 207 scr_stop() 208 { 209 scr_end(); 210 (void) kill(getpid(), SIGTSTP); 211 (void) sigunblock(sigmask(SIGTSTP)); 212 scr_set(); 213 scr_msg(key_msg, 1); 214 } 215 216 /* 217 * Set up screen mode. 218 */ 219 void 220 scr_set() 221 { 222 struct winsize ws; 223 struct sgttyb newtt; 224 volatile int omask; 225 void (*ttou)(); 226 227 omask = sigblock(sigmask(SIGTSTP) | sigmask(SIGTTOU)); 228 if ((tstp = signal(SIGTSTP, stopset)) == SIG_IGN) 229 (void) signal(SIGTSTP, SIG_IGN); 230 if ((ttou = signal(SIGTSTP, stopset)) == SIG_IGN) 231 (void) signal(SIGTSTP, SIG_IGN); 232 /* 233 * At last, we are ready to modify the tty state. If 234 * we stop while at it, stopset() above will longjmp back 235 * to the setjmp here and we will start over. 236 */ 237 (void) setjmp(scr_onstop); 238 (void) sigsetmask(omask); 239 Rows = 0, Cols = 0; 240 if (ioctl(0, TIOCGWINSZ, &ws) == 0) { 241 Rows = ws.ws_row; 242 Cols = ws.ws_col; 243 } 244 if (Rows == 0) 245 Rows = LInum; 246 if (Cols == 0) 247 Cols = COnum; 248 if (Rows < MINROWS || Cols < MINCOLS) { 249 (void) fprintf(stderr, 250 "the screen is too small: must be at least %d x %d", 251 MINROWS, MINCOLS); 252 stop(""); /* stop() supplies \n */ 253 } 254 if (ioctl(0, TIOCGETP, &oldtt)) 255 stop("ioctl(TIOCGETP) fails"); 256 newtt = oldtt; 257 newtt.sg_flags = (newtt.sg_flags | CBREAK) & ~(CRMOD | ECHO); 258 if ((newtt.sg_flags & TBDELAY) == XTABS) 259 newtt.sg_flags &= ~TBDELAY; 260 if (ioctl(0, TIOCSETN, &newtt)) 261 stop("ioctl(TIOCSETN) fails"); 262 ospeed = newtt.sg_ospeed; 263 omask = sigblock(sigmask(SIGTSTP) | sigmask(SIGTTOU)); 264 265 /* 266 * We made it. We are now in screen mode, modulo TIstr 267 * (which we will fix immediately). 268 */ 269 if (TIstr) 270 putstr(TIstr); /* termcap(5) says this is not padded */ 271 if (tstp != SIG_IGN) 272 (void) signal(SIGTSTP, scr_stop); 273 (void) signal(SIGTTOU, ttou); 274 275 isset = 1; 276 (void) sigsetmask(omask); 277 scr_clear(); 278 } 279 280 /* 281 * End screen mode. 282 */ 283 void 284 scr_end() 285 { 286 int omask = sigblock(sigmask(SIGTSTP) | sigmask(SIGTTOU)); 287 288 /* move cursor to last line */ 289 if (LLstr) 290 putstr(LLstr); /* termcap(5) says this is not padded */ 291 else 292 moveto(Rows - 1, 0); 293 /* exit screen mode */ 294 if (TEstr) 295 putstr(TEstr); /* termcap(5) says this is not padded */ 296 (void) fflush(stdout); 297 (void) ioctl(0, TIOCSETN, &oldtt); 298 isset = 0; 299 /* restore signals */ 300 (void) signal(SIGTSTP, tstp); 301 (void) sigsetmask(omask); 302 } 303 304 void 305 stop(why) 306 char *why; 307 { 308 309 if (isset) 310 scr_end(); 311 (void) fprintf(stderr, "aborting: %s\n", why); 312 exit(1); 313 } 314 315 /* 316 * Clear the screen, forgetting the current contents in the process. 317 */ 318 void 319 scr_clear() 320 { 321 322 putpad(CLstr); 323 curscore = -1; 324 bzero((char *)curscreen, sizeof(curscreen)); 325 } 326 327 #if vax && !__GNUC__ 328 typedef int regcell; /* pcc is bad at `register char', etc */ 329 #else 330 typedef cell regcell; 331 #endif 332 333 /* 334 * Update the screen. 335 */ 336 void 337 scr_update() 338 { 339 register cell *bp, *sp; 340 register regcell so, cur_so = 0; 341 register int i, ccol, j; 342 int omask = sigblock(sigmask(SIGTSTP)); 343 344 /* always leave cursor after last displayed point */ 345 curscreen[D_LAST * B_COLS - 1] = -1; 346 347 if (score != curscore) { 348 if (HOstr) 349 putpad(HOstr); 350 else 351 moveto(0, 0); 352 (void) printf("%d", score); 353 curscore = score; 354 } 355 356 bp = &board[D_FIRST * B_COLS]; 357 sp = &curscreen[D_FIRST * B_COLS]; 358 for (j = D_FIRST; j < D_LAST; j++) { 359 ccol = -1; 360 for (i = 0; i < B_COLS; bp++, sp++, i++) { 361 if (*sp == (so = *bp)) 362 continue; 363 *sp = so; 364 if (i != ccol) { 365 if (cur_so && MSflag) { 366 putpad(SEstr); 367 cur_so = 0; 368 } 369 moveto(RTOD(j), CTOD(i)); 370 } 371 if (SOstr) { 372 if (so != cur_so) { 373 putpad(so ? SOstr : SEstr); 374 cur_so = so; 375 } 376 putstr(" "); 377 } else 378 putstr(so ? "XX" : " "); 379 ccol = i + 1; 380 /* 381 * Look ahead a bit, to avoid extra motion if 382 * we will be redrawing the cell after the next. 383 * Motion probably takes four or more characters, 384 * so we save even if we rewrite two cells 385 * `unnecessarily'. Skip it all, though, if 386 * the next cell is a different color. 387 */ 388 #define STOP (B_COLS - 3) 389 if (i > STOP || sp[1] != bp[1] || so != bp[1]) 390 continue; 391 if (sp[2] != bp[2]) 392 sp[1] = -1; 393 else if (i < STOP && so == bp[2] && sp[3] != bp[3]) { 394 sp[2] = -1; 395 sp[1] = -1; 396 } 397 } 398 } 399 if (cur_so) 400 putpad(SEstr); 401 (void) fflush(stdout); 402 (void) sigsetmask(omask); 403 } 404 405 /* 406 * Write a message (set!=0), or clear the same message (set==0). 407 * (We need its length in case we have to overwrite with blanks.) 408 */ 409 void 410 scr_msg(s, set) 411 register char *s; 412 int set; 413 { 414 415 if (set || CEstr == NULL) { 416 register int l = strlen(s); 417 418 moveto(Rows - 2, ((Cols - l) >> 1) - 1); 419 if (set) 420 putstr(s); 421 else 422 while (--l >= 0) 423 (void) putchar(' '); 424 } else { 425 moveto(Rows - 2, 0); 426 putpad(CEstr); 427 } 428 } 429