screen.c revision 1.2
1/*	$NetBSD: screen.c,v 1.2 1995/04/22 07:42:41 cgd 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 <sgtty.h>
46#include <sys/ioctl.h>
47
48#include <setjmp.h>
49#include <signal.h>
50#include <stdio.h>
51#include <stdlib.h>
52#include <string.h>
53#include <unistd.h>
54
55#ifndef sigmask
56#define sigmask(s) (1 << ((s) - 1))
57#endif
58
59#include "screen.h"
60#include "tetris.h"
61
62/*
63 * XXX - need a <termcap.h>
64 */
65int	tgetent __P((char *, const char *));
66int	tgetflag __P((const char *));
67int	tgetnum __P((const char *));
68int	tputs __P((const char *, int, int (*)(int)));
69
70static cell curscreen[B_SIZE];	/* 1 => standout (or otherwise marked) */
71static int curscore;
72static int isset;		/* true => terminal is in game mode */
73static struct sgttyb oldtt;
74static void (*tstp)();
75
76char	*tgetstr(), *tgoto();
77
78
79/*
80 * Capabilities from TERMCAP.
81 */
82char	PC, *BC, *UP;		/* tgoto requires globals: ugh! */
83short	ospeed;
84
85static char
86	*bcstr,			/* backspace char */
87	*CEstr,			/* clear to end of line */
88	*CLstr,			/* clear screen */
89	*CMstr,			/* cursor motion string */
90#ifdef unneeded
91	*CRstr,			/* "\r" equivalent */
92#endif
93	*HOstr,			/* cursor home */
94	*LLstr,			/* last line, first column */
95	*pcstr,			/* pad character */
96	*TEstr,			/* end cursor motion mode */
97	*TIstr;			/* begin cursor motion mode */
98char
99	*SEstr,			/* end standout mode */
100	*SOstr;			/* begin standout mode */
101static int
102	COnum,			/* co# value */
103	LInum,			/* li# value */
104	MSflag;			/* can move in standout mode */
105
106
107struct tcsinfo {	/* termcap string info; some abbrevs above */
108	char tcname[3];
109	char **tcaddr;
110} tcstrings[] = {
111	"bc", &bcstr,
112	"ce", &CEstr,
113	"cl", &CLstr,
114	"cm", &CMstr,
115#ifdef unneeded
116	"cr", &CRstr,
117#endif
118	"le", &BC,		/* move cursor left one space */
119	"pc", &pcstr,
120	"se", &SEstr,
121	"so", &SOstr,
122	"te", &TEstr,
123	"ti", &TIstr,
124	"up", &UP,		/* cursor up */
125	0
126};
127
128/* This is where we will actually stuff the information */
129
130static char combuf[1024], tbuf[1024];
131
132
133/*
134 * Routine used by tputs().
135 */
136int
137put(c)
138	int c;
139{
140
141	return (putchar(c));
142}
143
144/*
145 * putstr() is for unpadded strings (either as in termcap(5) or
146 * simply literal strings); putpad() is for padded strings with
147 * count=1.  (See screen.h for putpad().)
148 */
149#define	putstr(s)	(void)fputs(s, stdout)
150#define	moveto(r, c)	putpad(tgoto(CMstr, c, r))
151
152/*
153 * Set up from termcap.
154 */
155void
156scr_init()
157{
158	static int bsflag, xsflag, sgnum;
159#ifdef unneeded
160	static int ncflag;
161#endif
162	char *term, *fill;
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
174	}, tcnums[] = {
175		"co", &COnum,
176		"li", &LInum,
177		"sg", &sgnum,
178		0
179	};
180
181	if ((term = getenv("TERM")) == NULL)
182		stop("you must set the TERM environment variable");
183	if (tgetent(tbuf, term) <= 0)
184		stop("cannot find your termcap");
185	fill = combuf;
186	{
187		register struct tcsinfo *p;
188
189		for (p = tcstrings; p->tcaddr; p++)
190			*p->tcaddr = tgetstr(p->tcname, &fill);
191	}
192	{
193		register struct tcninfo *p;
194
195		for (p = tcflags; p->tcaddr; p++)
196			*p->tcaddr = tgetflag(p->tcname);
197		for (p = tcnums; p->tcaddr; p++)
198			*p->tcaddr = tgetnum(p->tcname);
199	}
200	if (bsflag)
201		BC = "\b";
202	else if (BC == NULL && bcstr != NULL)
203		BC = bcstr;
204	if (CLstr == NULL)
205		stop("cannot clear screen");
206	if (CMstr == NULL || UP == NULL || BC == NULL)
207		stop("cannot do random cursor positioning via tgoto()");
208	PC = pcstr ? *pcstr : 0;
209	if (sgnum >= 0 || xsflag)
210		SOstr = SEstr = NULL;
211#ifdef unneeded
212	if (ncflag)
213		CRstr = NULL;
214	else if (CRstr == NULL)
215		CRstr = "\r";
216#endif
217}
218
219/* this foolery is needed to modify tty state `atomically' */
220static jmp_buf scr_onstop;
221
222#define	sigunblock(mask) sigsetmask(sigblock(0) & ~(mask))
223
224static void
225stopset(sig)
226	int sig;
227{
228	(void) signal(sig, SIG_DFL);
229	(void) kill(getpid(), sig);
230	(void) sigunblock(sigmask(sig));
231	longjmp(scr_onstop, 1);
232}
233
234static void
235scr_stop()
236{
237	scr_end();
238	(void) kill(getpid(), SIGTSTP);
239	(void) sigunblock(sigmask(SIGTSTP));
240	scr_set();
241	scr_msg(key_msg, 1);
242}
243
244/*
245 * Set up screen mode.
246 */
247void
248scr_set()
249{
250	struct winsize ws;
251	struct sgttyb newtt;
252	volatile int omask;
253	void (*ttou)();
254
255	omask = sigblock(sigmask(SIGTSTP) | sigmask(SIGTTOU));
256	if ((tstp = signal(SIGTSTP, stopset)) == SIG_IGN)
257		(void) signal(SIGTSTP, SIG_IGN);
258	if ((ttou = signal(SIGTSTP, stopset)) == SIG_IGN)
259		(void) signal(SIGTSTP, SIG_IGN);
260	/*
261	 * At last, we are ready to modify the tty state.  If
262	 * we stop while at it, stopset() above will longjmp back
263	 * to the setjmp here and we will start over.
264	 */
265	(void) setjmp(scr_onstop);
266	(void) sigsetmask(omask);
267	Rows = 0, Cols = 0;
268	if (ioctl(0, TIOCGWINSZ, &ws) == 0) {
269		Rows = ws.ws_row;
270		Cols = ws.ws_col;
271	}
272	if (Rows == 0)
273		Rows = LInum;
274	if (Cols == 0)
275	Cols = COnum;
276	if (Rows < MINROWS || Cols < MINCOLS) {
277		(void) fprintf(stderr,
278		    "the screen is too small: must be at least %d x %d",
279		    MINROWS, MINCOLS);
280		stop("");	/* stop() supplies \n */
281	}
282	if (ioctl(0, TIOCGETP, &oldtt))
283		stop("ioctl(TIOCGETP) fails");
284	newtt = oldtt;
285	newtt.sg_flags = (newtt.sg_flags | CBREAK) & ~(CRMOD | ECHO);
286	if ((newtt.sg_flags & TBDELAY) == XTABS)
287		newtt.sg_flags &= ~TBDELAY;
288	if (ioctl(0, TIOCSETN, &newtt))
289		stop("ioctl(TIOCSETN) fails");
290	ospeed = newtt.sg_ospeed;
291	omask = sigblock(sigmask(SIGTSTP) | sigmask(SIGTTOU));
292
293	/*
294	 * We made it.  We are now in screen mode, modulo TIstr
295	 * (which we will fix immediately).
296	 */
297	if (TIstr)
298		putstr(TIstr);	/* termcap(5) says this is not padded */
299	if (tstp != SIG_IGN)
300		(void) signal(SIGTSTP, scr_stop);
301	(void) signal(SIGTTOU, ttou);
302
303	isset = 1;
304	(void) sigsetmask(omask);
305	scr_clear();
306}
307
308/*
309 * End screen mode.
310 */
311void
312scr_end()
313{
314	int omask = sigblock(sigmask(SIGTSTP) | sigmask(SIGTTOU));
315
316	/* move cursor to last line */
317	if (LLstr)
318		putstr(LLstr);	/* termcap(5) says this is not padded */
319	else
320		moveto(Rows - 1, 0);
321	/* exit screen mode */
322	if (TEstr)
323		putstr(TEstr);	/* termcap(5) says this is not padded */
324	(void) fflush(stdout);
325	(void) ioctl(0, TIOCSETN, &oldtt);
326	isset = 0;
327	/* restore signals */
328	(void) signal(SIGTSTP, tstp);
329	(void) sigsetmask(omask);
330}
331
332void
333stop(why)
334	char *why;
335{
336
337	if (isset)
338		scr_end();
339	(void) fprintf(stderr, "aborting: %s\n", why);
340	exit(1);
341}
342
343/*
344 * Clear the screen, forgetting the current contents in the process.
345 */
346void
347scr_clear()
348{
349
350	putpad(CLstr);
351	curscore = -1;
352	bzero((char *)curscreen, sizeof(curscreen));
353}
354
355#if vax && !__GNUC__
356typedef int regcell;	/* pcc is bad at `register char', etc */
357#else
358typedef cell regcell;
359#endif
360
361/*
362 * Update the screen.
363 */
364void
365scr_update()
366{
367	register cell *bp, *sp;
368	register regcell so, cur_so = 0;
369	register int i, ccol, j;
370	int omask = sigblock(sigmask(SIGTSTP));
371
372	/* always leave cursor after last displayed point */
373	curscreen[D_LAST * B_COLS - 1] = -1;
374
375	if (score != curscore) {
376		if (HOstr)
377			putpad(HOstr);
378		else
379			moveto(0, 0);
380		(void) printf("%d", score);
381		curscore = score;
382	}
383
384	bp = &board[D_FIRST * B_COLS];
385	sp = &curscreen[D_FIRST * B_COLS];
386	for (j = D_FIRST; j < D_LAST; j++) {
387		ccol = -1;
388		for (i = 0; i < B_COLS; bp++, sp++, i++) {
389			if (*sp == (so = *bp))
390				continue;
391			*sp = so;
392			if (i != ccol) {
393				if (cur_so && MSflag) {
394					putpad(SEstr);
395					cur_so = 0;
396				}
397				moveto(RTOD(j), CTOD(i));
398			}
399			if (SOstr) {
400				if (so != cur_so) {
401					putpad(so ? SOstr : SEstr);
402					cur_so = so;
403				}
404				putstr("  ");
405			} else
406				putstr(so ? "XX" : "  ");
407			ccol = i + 1;
408			/*
409			 * Look ahead a bit, to avoid extra motion if
410			 * we will be redrawing the cell after the next.
411			 * Motion probably takes four or more characters,
412			 * so we save even if we rewrite two cells
413			 * `unnecessarily'.  Skip it all, though, if
414			 * the next cell is a different color.
415			 */
416#define	STOP (B_COLS - 3)
417			if (i > STOP || sp[1] != bp[1] || so != bp[1])
418				continue;
419			if (sp[2] != bp[2])
420				sp[1] = -1;
421			else if (i < STOP && so == bp[2] && sp[3] != bp[3]) {
422				sp[2] = -1;
423				sp[1] = -1;
424			}
425		}
426	}
427	if (cur_so)
428		putpad(SEstr);
429	(void) fflush(stdout);
430	(void) sigsetmask(omask);
431}
432
433/*
434 * Write a message (set!=0), or clear the same message (set==0).
435 * (We need its length in case we have to overwrite with blanks.)
436 */
437void
438scr_msg(s, set)
439	register char *s;
440	int set;
441{
442
443	if (set || CEstr == NULL) {
444		register int l = strlen(s);
445
446		moveto(Rows - 2, ((Cols - l) >> 1) - 1);
447		if (set)
448			putstr(s);
449		else
450			while (--l >= 0)
451				(void) putchar(' ');
452	} else {
453		moveto(Rows - 2, 0);
454		putpad(CEstr);
455	}
456}
457