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