screen.c revision 1.16 1 /* $NetBSD: screen.c,v 1.16 2001/11/02 18:27:00 christos 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 register struct tcsinfo *p;
187
188 for (p = tcstrings; p->tcaddr; p++)
189 *p->tcaddr = t_agetstr(info, p->tcname);
190 }
191 {
192 register 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 register cell *bp, *sp;
381 register regcell so, cur_so = 0;
382 register 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 register char *s;
493 int set;
494 {
495
496 if (set || CEstr == NULL) {
497 register 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