screen.c revision 1.8 1 /* $NetBSD: screen.c,v 1.8 1999/01/03 02:00:18 hubertf 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));
71
72
73 /*
74 * Capabilities from TERMCAP.
75 */
76 char PC, *BC, *UP; /* tgoto requires globals: ugh! */
77 speed_t ospeed;
78
79 static 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 */
92 char
93 *SEstr, /* end standout mode */
94 *SOstr; /* begin standout mode */
95 static int
96 COnum, /* co# value */
97 LInum, /* li# value */
98 MSflag; /* can move in standout mode */
99
100
101 struct 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
124 static char combuf[1024], tbuf[1024];
125
126
127 /*
128 * Routine used by tputs().
129 */
130 void
131 put(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 */
149 void
150 scr_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' */
214 static jmp_buf scr_onstop;
215
216 static void
217 stopset(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
230 static void
231 scr_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 */
248 void
249 scr_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 */
315 void
316 scr_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
340 void
341 stop(why)
342 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 */
354 void
355 scr_clear()
356 {
357
358 putpad(CLstr);
359 curscore = -1;
360 memset((char *)curscreen, 0, sizeof(curscreen));
361 }
362
363 #if vax && !__GNUC__
364 typedef int regcell; /* pcc is bad at `register char', etc */
365 #else
366 typedef cell regcell;
367 #endif
368
369 /*
370 * Update the screen.
371 */
372 void
373 scr_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 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 #ifndef NO_PREVIEW
398 /* draw preview of nextpattern */
399 if (nextshape != lastshape) {
400 int i;
401 static int r=5, c=2;
402 int tr, tc, t;
403
404 lastshape = nextshape;
405
406 /* clean */
407 putpad(SEstr);
408 moveto(r-1, c-1); putstr(" ");
409 moveto(r, c-1); putstr(" ");
410 moveto(r+1, c-1); putstr(" ");
411 moveto(r+2, c-1); putstr(" ");
412
413 moveto(r-3, c-2);
414 putstr("Next shape:");
415
416 /* draw */
417 putpad(SOstr);
418 moveto(r, 2*c);
419 putstr(" ");
420 for(i=0; i<3; i++) {
421 t = c + r*B_COLS;
422 t += nextshape->off[i];
423
424 tr = t / B_COLS;
425 tc = t % B_COLS;
426
427 moveto(tr, 2*tc);
428 putstr(" ");
429 }
430 putpad(SEstr);
431 }
432 #endif
433
434 bp = &board[D_FIRST * B_COLS];
435 sp = &curscreen[D_FIRST * B_COLS];
436 for (j = D_FIRST; j < D_LAST; j++) {
437 ccol = -1;
438 for (i = 0; i < B_COLS; bp++, sp++, i++) {
439 if (*sp == (so = *bp))
440 continue;
441 *sp = so;
442 if (i != ccol) {
443 if (cur_so && MSflag) {
444 putpad(SEstr);
445 cur_so = 0;
446 }
447 moveto(RTOD(j), CTOD(i));
448 }
449 if (SOstr) {
450 if (so != cur_so) {
451 putpad(so ? SOstr : SEstr);
452 cur_so = so;
453 }
454 putstr(" ");
455 } else
456 putstr(so ? "XX" : " ");
457 ccol = i + 1;
458 /*
459 * Look ahead a bit, to avoid extra motion if
460 * we will be redrawing the cell after the next.
461 * Motion probably takes four or more characters,
462 * so we save even if we rewrite two cells
463 * `unnecessarily'. Skip it all, though, if
464 * the next cell is a different color.
465 */
466 #define STOP (B_COLS - 3)
467 if (i > STOP || sp[1] != bp[1] || so != bp[1])
468 continue;
469 if (sp[2] != bp[2])
470 sp[1] = -1;
471 else if (i < STOP && so == bp[2] && sp[3] != bp[3]) {
472 sp[2] = -1;
473 sp[1] = -1;
474 }
475 }
476 }
477 if (cur_so)
478 putpad(SEstr);
479 (void) fflush(stdout);
480 (void) sigprocmask(SIG_SETMASK, &osigset, (sigset_t *)0);
481 }
482
483 /*
484 * Write a message (set!=0), or clear the same message (set==0).
485 * (We need its length in case we have to overwrite with blanks.)
486 */
487 void
488 scr_msg(s, set)
489 register char *s;
490 int set;
491 {
492
493 if (set || CEstr == NULL) {
494 register int l = strlen(s);
495
496 moveto(Rows - 2, ((Cols - l) >> 1) - 1);
497 if (set)
498 putstr(s);
499 else
500 while (--l >= 0)
501 (void) putchar(' ');
502 } else {
503 moveto(Rows - 2, 0);
504 putpad(CEstr);
505 }
506 }
507