ckdist(1): Sync with FreeBSD.
[dragonfly.git] / games / tetris / screen.c
blobbbcc9f1faad32e8ca3d79500d182f71ce5169b8e
1 /* $OpenBSD: screen.c,v 1.18 2017/04/16 18:04:02 tb Exp $ */
2 /* $NetBSD: screen.c,v 1.4 1995/04/29 01:11:36 mycroft Exp $ */
4 /*-
5 * Copyright (c) 1992, 1993
6 * The Regents of the University of California. All rights reserved.
8 * This code is derived from software contributed to Berkeley by
9 * Chris Torek and Darren F. Provine.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
35 * @(#)screen.c 8.1 (Berkeley) 5/31/93
39 * Tetris screen control.
42 #include <sys/ioctl.h>
44 #include <err.h>
45 #include <setjmp.h>
46 #include <signal.h>
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <string.h>
50 #include <term.h>
51 #include <unistd.h>
53 #include "screen.h"
54 #include "tetris.h"
56 static cell curscreen[B_SIZE]; /* 1 => standout (or otherwise marked) */
57 static int curscore;
58 static int isset; /* true => terminal is in game mode */
59 static struct termios oldtt;
60 static void (*tstp)(int);
62 static void scr_stop(int);
63 static void stopset(int);
66 * Capabilities from TERMCAP.
68 extern char PC, *BC, *UP; /* tgoto requires globals: ugh! */
70 static char *bcstr; /* backspace char */
71 static char *CEstr; /* clear to end of line */
72 static char *CLstr; /* clear screen */
73 static char *CMstr; /* cursor motion string */
74 #ifdef unneeded
75 static char *CRstr; /* "\r" equivalent */
76 #endif
77 static char *HOstr; /* cursor home */
78 static char *LLstr; /* last line, first column */
79 static char *pcstr; /* pad character */
80 static char *TEstr; /* end cursor motion mode */
81 static char *TIstr; /* begin cursor motion mode */
82 static char *VIstr; /* make cursor invisible */
83 static char *VEstr; /* make cursor appear normal */
84 char *SEstr; /* end standout mode */
85 char *SOstr; /* begin standout mode */
86 static int COnum; /* co# value */
87 static int LInum; /* li# value */
88 static int MSflag; /* can move in standout mode */
91 struct tcsinfo { /* termcap string info; some abbrevs above */
92 char tcname[3];
93 char **tcaddr;
94 } tcstrings[] = {
95 {"bc", &bcstr},
96 {"ce", &CEstr},
97 {"cl", &CLstr},
98 {"cm", &CMstr},
99 #ifdef unneeded
100 {"cr", &CRstr},
101 #endif
102 {"le", &BC}, /* move cursor left one space */
103 {"pc", &pcstr},
104 {"se", &SEstr},
105 {"so", &SOstr},
106 {"te", &TEstr},
107 {"ti", &TIstr},
108 {"vi", &VIstr},
109 {"ve", &VEstr},
110 {"up", &UP}, /* cursor up */
111 { {0}, NULL}
114 /* This is where we will actually stuff the information */
116 static char combuf[1024], tbuf[1024];
120 * Routine used by tputs().
123 put(int c)
125 return (putchar(c));
129 * putstr() is for unpadded strings (either as in termcap(5) or
130 * simply literal strings); putpad() is for padded strings with
131 * count=1. (See screen.h for putpad().)
133 #define putstr(s) fputs(s, stdout)
134 #define moveto(r, c) putpad(tgoto(CMstr, c, r))
137 * Set up from termcap.
139 void
140 scr_init(void)
142 static int bsflag, xsflag, sgnum;
143 #ifdef unneeded
144 static int ncflag;
145 #endif
146 char *term, *fill;
147 static struct tcninfo { /* termcap numeric and flag info */
148 char tcname[3];
149 int *tcaddr;
150 } tcflags[] = {
151 {"bs", &bsflag},
152 {"ms", &MSflag},
153 #ifdef unneeded
154 {"nc", &ncflag},
155 #endif
156 {"xs", &xsflag},
157 { {0}, NULL}
158 }, tcnums[] = {
159 {"co", &COnum},
160 {"li", &LInum},
161 {"sg", &sgnum},
162 { {0}, NULL}
165 if ((term = getenv("TERM")) == NULL)
166 stop("you must set the TERM environment variable");
167 if (tgetent(tbuf, term) <= 0)
168 stop("cannot find your termcap");
169 fill = combuf;
171 struct tcsinfo *p;
173 for (p = tcstrings; p->tcaddr; p++)
174 *p->tcaddr = tgetstr(p->tcname, &fill);
176 if (classic)
177 SOstr = SEstr = NULL;
179 struct tcninfo *p;
181 for (p = tcflags; p->tcaddr; p++)
182 *p->tcaddr = tgetflag(p->tcname);
183 for (p = tcnums; p->tcaddr; p++)
184 *p->tcaddr = tgetnum(p->tcname);
186 if (bsflag)
187 BC = __DECONST(char *, "\b");
188 else if (BC == NULL && bcstr != NULL)
189 BC = bcstr;
190 if (CLstr == NULL)
191 stop("cannot clear screen");
192 if (CMstr == NULL || UP == NULL || BC == NULL)
193 stop("cannot do random cursor positioning via tgoto()");
194 PC = pcstr ? *pcstr : 0;
195 if (sgnum > 0 || xsflag)
196 SOstr = SEstr = NULL;
197 #ifdef unneeded
198 if (ncflag)
199 CRstr = NULL;
200 else if (CRstr == NULL)
201 CRstr = "\r";
202 #endif
205 /* this foolery is needed to modify tty state `atomically' */
206 static jmp_buf scr_onstop;
208 static void
209 stopset(int sig)
211 sigset_t sigset;
213 signal(sig, SIG_DFL);
214 kill(getpid(), sig);
215 sigemptyset(&sigset);
216 sigaddset(&sigset, sig);
217 sigprocmask(SIG_UNBLOCK, &sigset, (sigset_t *)0);
218 longjmp(scr_onstop, 1);
221 static void
222 scr_stop(int sig)
224 sigset_t sigset;
226 scr_end();
227 kill(getpid(), sig);
228 sigemptyset(&sigset);
229 sigaddset(&sigset, sig);
230 sigprocmask(SIG_UNBLOCK, &sigset, (sigset_t *)0);
231 scr_set();
232 scr_msg(key_msg, 1);
236 * Set up screen mode.
238 void
239 scr_set(void)
241 struct winsize ws;
242 struct termios newtt;
243 sigset_t sigset, osigset;
244 void (*ttou)(int);
246 sigemptyset(&sigset);
247 sigaddset(&sigset, SIGTSTP);
248 sigaddset(&sigset, SIGTTOU);
249 sigprocmask(SIG_BLOCK, &sigset, &osigset);
250 if ((tstp = signal(SIGTSTP, stopset)) == SIG_IGN)
251 signal(SIGTSTP, SIG_IGN);
252 if ((ttou = signal(SIGTTOU, stopset)) == SIG_IGN)
253 signal(SIGTTOU, SIG_IGN);
255 * At last, we are ready to modify the tty state. If
256 * we stop while at it, stopset() above will longjmp back
257 * to the setjmp here and we will start over.
259 setjmp(scr_onstop);
260 sigprocmask(SIG_SETMASK, &osigset, (sigset_t *)0);
261 Rows = 0, Cols = 0;
262 if (ioctl(0, TIOCGWINSZ, &ws) == 0) {
263 Rows = ws.ws_row;
264 Cols = ws.ws_col;
266 if (Rows == 0)
267 Rows = LInum;
268 if (Cols == 0)
269 Cols = COnum;
270 if (Rows < MINROWS || Cols < MINCOLS) {
271 char smallscr[55];
273 snprintf(smallscr, sizeof(smallscr),
274 "the screen is too small (must be at least %dx%d)",
275 MINROWS, MINCOLS);
276 stop(smallscr);
278 if (tcgetattr(0, &oldtt) < 0)
279 stop("tcgetattr() fails");
280 newtt = oldtt;
281 newtt.c_lflag &= ~(ICANON|ECHO);
282 newtt.c_oflag &= ~OXTABS;
283 if (tcsetattr(0, TCSADRAIN, &newtt) < 0)
284 stop("tcsetattr() fails");
285 sigprocmask(SIG_BLOCK, &sigset, &osigset);
288 * We made it. We are now in screen mode, modulo TIstr
289 * (which we will fix immediately).
291 if (TIstr)
292 putstr(TIstr); /* termcap(5) says this is not padded */
293 if (VIstr)
294 putstr(VIstr); /* termcap(5) says this is not padded */
295 if (tstp != SIG_IGN)
296 signal(SIGTSTP, scr_stop);
297 if (ttou != SIG_IGN)
298 signal(SIGTTOU, ttou);
300 isset = 1;
301 sigprocmask(SIG_SETMASK, &osigset, (sigset_t *)0);
302 scr_clear();
306 * End screen mode.
308 void
309 scr_end(void)
311 sigset_t sigset, osigset;
313 sigemptyset(&sigset);
314 sigaddset(&sigset, SIGTSTP);
315 sigaddset(&sigset, SIGTTOU);
316 sigprocmask(SIG_BLOCK, &sigset, &osigset);
317 /* move cursor to last line */
318 if (LLstr)
319 putstr(LLstr); /* termcap(5) says this is not padded */
320 else
321 moveto(Rows - 1, 0);
322 /* exit screen mode */
323 if (TEstr)
324 putstr(TEstr); /* termcap(5) says this is not padded */
325 if (VEstr)
326 putstr(VEstr); /* termcap(5) says this is not padded */
327 fflush(stdout);
328 tcsetattr(0, TCSADRAIN, &oldtt);
329 isset = 0;
330 /* restore signals */
331 signal(SIGTSTP, tstp);
332 sigprocmask(SIG_SETMASK, &osigset, (sigset_t *)0);
335 void
336 stop(const char *why)
338 if (isset)
339 scr_end();
340 errx(1, "aborting: %s", why);
344 * Clear the screen, forgetting the current contents in the process.
346 void
347 scr_clear(void)
349 putpad(CLstr);
350 curscore = -1;
351 memset((char *)curscreen, 0, sizeof(curscreen));
354 typedef cell regcell;
357 * Update the screen.
359 void
360 scr_update(void)
362 cell *bp, *sp;
363 regcell so, cur_so = 0;
364 int i, ccol, j;
365 sigset_t sigset, osigset;
366 static const struct shape *lastshape;
368 sigemptyset(&sigset);
369 sigaddset(&sigset, SIGTSTP);
370 sigprocmask(SIG_BLOCK, &sigset, &osigset);
372 /* always leave cursor after last displayed point */
373 curscreen[D_LAST * B_COLS - 1] = -1;
375 if (score != curscore) {
376 if (HOstr)
377 putpad(HOstr);
378 else
379 moveto(0, 0);
380 printf("Score: %d", score);
381 curscore = score;
384 /* draw preview of next pattern */
385 if (showpreview && (nextshape != lastshape)) {
386 static int r=5, c=2;
387 int tr, tc, t;
389 lastshape = nextshape;
391 /* clean */
392 putpad(SEstr);
393 moveto(r-1, c-1); putstr(" ");
394 moveto(r, c-1); putstr(" ");
395 moveto(r+1, c-1); putstr(" ");
396 moveto(r+2, c-1); putstr(" ");
398 moveto(r-3, c-2);
399 putstr("Next shape:");
401 /* draw */
402 if (SOstr)
403 putpad(SOstr);
404 moveto(r, 2 * c);
405 putstr(SOstr ? " " : "[]");
406 for (i = 0; i < 3; i++) {
407 t = c + r * B_COLS;
408 t += nextshape->off[i];
410 tr = t / B_COLS;
411 tc = t % B_COLS;
413 moveto(tr, 2*tc);
414 putstr(SOstr ? " " : "[]");
416 putpad(SEstr);
419 bp = &board[D_FIRST * B_COLS];
420 sp = &curscreen[D_FIRST * B_COLS];
421 for (j = D_FIRST; j < D_LAST; j++) {
422 ccol = -1;
423 for (i = 0; i < B_COLS; bp++, sp++, i++) {
424 if (*sp == (so = *bp))
425 continue;
426 *sp = so;
427 if (i != ccol) {
428 if (cur_so && MSflag) {
429 putpad(SEstr);
430 cur_so = 0;
432 moveto(RTOD(j), CTOD(i));
434 if (SOstr) {
435 if (so != cur_so) {
436 putpad(so ? SOstr : SEstr);
437 cur_so = so;
439 putstr(" ");
440 } else
441 putstr(so ? "[]" : " ");
442 ccol = i + 1;
444 * Look ahead a bit, to avoid extra motion if
445 * we will be redrawing the cell after the next.
446 * Motion probably takes four or more characters,
447 * so we save even if we rewrite two cells
448 * `unnecessarily'. Skip it all, though, if
449 * the next cell is a different color.
451 #define STOP (B_COLS - 3)
452 if (i > STOP || sp[1] != bp[1] || so != bp[1])
453 continue;
454 if (sp[2] != bp[2])
455 sp[1] = -1;
456 else if (i < STOP && so == bp[2] && sp[3] != bp[3]) {
457 sp[2] = -1;
458 sp[1] = -1;
462 if (cur_so)
463 putpad(SEstr);
464 fflush(stdout);
465 sigprocmask(SIG_SETMASK, &osigset, (sigset_t *)0);
469 * Write a message (set!=0), or clear the same message (set==0).
470 * (We need its length in case we have to overwrite with blanks.)
472 void
473 scr_msg(char *s, int set)
475 if (set || CEstr == NULL) {
476 int l = strlen(s);
478 moveto(Rows - 2, ((Cols - l) >> 1) - 1);
479 if (set)
480 putstr(s);
481 else
482 while (--l >= 0)
483 putchar(' ');
484 } else {
485 moveto(Rows - 2, 0);
486 putpad(CEstr);