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