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
put(c)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
scr_init()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
stopset(sig)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
scr_stop()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
scr_set()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
scr_end()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
stop(why)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
scr_clear()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
scr_update()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
scr_msg(s,set)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