xref: /csrg-svn/games/tetris/screen.c (revision 60854)
157273Sbostic /*-
2*60854Sbostic  * Copyright (c) 1992, 1993
3*60854Sbostic  *	The Regents of the University of California.  All rights reserved.
457273Sbostic  *
557288Sbostic  * This code is derived from software contributed to Berkeley by
657288Sbostic  * Chris Torek and Darren F. Provine.
757288Sbostic  *
857273Sbostic  * %sccs.include.redist.c%
957273Sbostic  *
10*60854Sbostic  *	@(#)screen.c	8.1 (Berkeley) 05/31/93
1157273Sbostic  */
1257273Sbostic 
1357273Sbostic /*
1457273Sbostic  * Tetris screen control.
1557273Sbostic  */
1657273Sbostic 
1757273Sbostic #include <sgtty.h>
1857273Sbostic #include <sys/ioctl.h>
1957273Sbostic 
2057273Sbostic #include <setjmp.h>
2157273Sbostic #include <signal.h>
2257273Sbostic #include <stdio.h>
2357273Sbostic #include <stdlib.h>
2457273Sbostic #include <string.h>
2557273Sbostic #include <unistd.h>
2657273Sbostic 
2757273Sbostic #ifndef sigmask
2857273Sbostic #define sigmask(s) (1 << ((s) - 1))
2957273Sbostic #endif
3057273Sbostic 
3157273Sbostic #include "screen.h"
3257273Sbostic #include "tetris.h"
3357273Sbostic 
3457273Sbostic /*
3557273Sbostic  * XXX - need a <termcap.h>
3657273Sbostic  */
3757273Sbostic int	tgetent __P((char *, const char *));
3857273Sbostic int	tgetflag __P((const char *));
3957273Sbostic int	tgetnum __P((const char *));
4057273Sbostic int	tputs __P((const char *, int, int (*)(int)));
4157273Sbostic 
4257273Sbostic static cell curscreen[B_SIZE];	/* 1 => standout (or otherwise marked) */
4357273Sbostic static int curscore;
4457273Sbostic static int isset;		/* true => terminal is in game mode */
4557273Sbostic static struct sgttyb oldtt;
4657273Sbostic static void (*tstp)();
4757273Sbostic 
4857273Sbostic char	*tgetstr(), *tgoto();
4957273Sbostic 
5057273Sbostic 
5157273Sbostic /*
5257273Sbostic  * Capabilities from TERMCAP.
5357273Sbostic  */
5457273Sbostic char	PC, *BC, *UP;		/* tgoto requires globals: ugh! */
5557273Sbostic short	ospeed;
5657273Sbostic 
5757273Sbostic static char
5857273Sbostic 	*bcstr,			/* backspace char */
5957273Sbostic 	*CEstr,			/* clear to end of line */
6057273Sbostic 	*CLstr,			/* clear screen */
6157273Sbostic 	*CMstr,			/* cursor motion string */
6257273Sbostic #ifdef unneeded
6357273Sbostic 	*CRstr,			/* "\r" equivalent */
6457273Sbostic #endif
6557273Sbostic 	*HOstr,			/* cursor home */
6657273Sbostic 	*LLstr,			/* last line, first column */
6757273Sbostic 	*pcstr,			/* pad character */
6857273Sbostic 	*TEstr,			/* end cursor motion mode */
6957273Sbostic 	*TIstr;			/* begin cursor motion mode */
7057273Sbostic char
7157273Sbostic 	*SEstr,			/* end standout mode */
7257273Sbostic 	*SOstr;			/* begin standout mode */
7357273Sbostic static int
7457273Sbostic 	COnum,			/* co# value */
7557273Sbostic 	LInum,			/* li# value */
7657273Sbostic 	MSflag;			/* can move in standout mode */
7757273Sbostic 
7857273Sbostic 
7957273Sbostic struct tcsinfo {	/* termcap string info; some abbrevs above */
8057273Sbostic 	char tcname[3];
8157273Sbostic 	char **tcaddr;
8257273Sbostic } tcstrings[] = {
8357273Sbostic 	"bc", &bcstr,
8457273Sbostic 	"ce", &CEstr,
8557273Sbostic 	"cl", &CLstr,
8657273Sbostic 	"cm", &CMstr,
8757273Sbostic #ifdef unneeded
8857273Sbostic 	"cr", &CRstr,
8957273Sbostic #endif
9057273Sbostic 	"le", &BC,		/* move cursor left one space */
9157273Sbostic 	"pc", &pcstr,
9257273Sbostic 	"se", &SEstr,
9357273Sbostic 	"so", &SOstr,
9457273Sbostic 	"te", &TEstr,
9557273Sbostic 	"ti", &TIstr,
9657273Sbostic 	"up", &UP,		/* cursor up */
9757273Sbostic 	0
9857273Sbostic };
9957273Sbostic 
10057273Sbostic /* This is where we will actually stuff the information */
10157273Sbostic 
10257273Sbostic static char combuf[1024], tbuf[1024];
10357273Sbostic 
10457273Sbostic 
10557273Sbostic /*
10657273Sbostic  * Routine used by tputs().
10757273Sbostic  */
10857273Sbostic int
put(c)10957273Sbostic put(c)
11057273Sbostic 	int c;
11157273Sbostic {
11257273Sbostic 
11357273Sbostic 	return (putchar(c));
11457273Sbostic }
11557273Sbostic 
11657273Sbostic /*
11757273Sbostic  * putstr() is for unpadded strings (either as in termcap(5) or
11857273Sbostic  * simply literal strings); putpad() is for padded strings with
11957273Sbostic  * count=1.  (See screen.h for putpad().)
12057273Sbostic  */
12157273Sbostic #define	putstr(s)	(void)fputs(s, stdout)
12257273Sbostic #define	moveto(r, c)	putpad(tgoto(CMstr, c, r))
12357273Sbostic 
12457273Sbostic /*
12557273Sbostic  * Set up from termcap.
12657273Sbostic  */
12757273Sbostic void
scr_init()12857273Sbostic scr_init()
12957273Sbostic {
13057273Sbostic 	static int bsflag, xsflag, sgnum;
13157273Sbostic #ifdef unneeded
13257273Sbostic 	static int ncflag;
13357273Sbostic #endif
13457273Sbostic 	char *term, *fill;
13557273Sbostic 	static struct tcninfo {	/* termcap numeric and flag info */
13657273Sbostic 		char tcname[3];
13757273Sbostic 		int *tcaddr;
13857273Sbostic 	} tcflags[] = {
13957273Sbostic 		"bs", &bsflag,
14057273Sbostic 		"ms", &MSflag,
14157273Sbostic #ifdef unneeded
14257273Sbostic 		"nc", &ncflag,
14357273Sbostic #endif
14457273Sbostic 		"xs", &xsflag,
14557273Sbostic 		0
14657273Sbostic 	}, tcnums[] = {
14757273Sbostic 		"co", &COnum,
14857273Sbostic 		"li", &LInum,
14957273Sbostic 		"sg", &sgnum,
15057273Sbostic 		0
15157273Sbostic 	};
15257273Sbostic 
15357273Sbostic 	if ((term = getenv("TERM")) == NULL)
15457273Sbostic 		stop("you must set the TERM environment variable");
15557273Sbostic 	if (tgetent(tbuf, term) <= 0)
15657273Sbostic 		stop("cannot find your termcap");
15757273Sbostic 	fill = combuf;
15857273Sbostic 	{
15957273Sbostic 		register struct tcsinfo *p;
16057273Sbostic 
16157273Sbostic 		for (p = tcstrings; p->tcaddr; p++)
16257273Sbostic 			*p->tcaddr = tgetstr(p->tcname, &fill);
16357273Sbostic 	}
16457273Sbostic 	{
16557273Sbostic 		register struct tcninfo *p;
16657273Sbostic 
16757273Sbostic 		for (p = tcflags; p->tcaddr; p++)
16857273Sbostic 			*p->tcaddr = tgetflag(p->tcname);
16957273Sbostic 		for (p = tcnums; p->tcaddr; p++)
17057273Sbostic 			*p->tcaddr = tgetnum(p->tcname);
17157273Sbostic 	}
17257273Sbostic 	if (bsflag)
17357273Sbostic 		BC = "\b";
17457273Sbostic 	else if (BC == NULL && bcstr != NULL)
17557273Sbostic 		BC = bcstr;
17657273Sbostic 	if (CLstr == NULL)
17757273Sbostic 		stop("cannot clear screen");
17857273Sbostic 	if (CMstr == NULL || UP == NULL || BC == NULL)
17957273Sbostic 		stop("cannot do random cursor positioning via tgoto()");
18057273Sbostic 	PC = pcstr ? *pcstr : 0;
18157273Sbostic 	if (sgnum >= 0 || xsflag)
18257273Sbostic 		SOstr = SEstr = NULL;
18357273Sbostic #ifdef unneeded
18457273Sbostic 	if (ncflag)
18557273Sbostic 		CRstr = NULL;
18657273Sbostic 	else if (CRstr == NULL)
18757273Sbostic 		CRstr = "\r";
18857273Sbostic #endif
18957273Sbostic }
19057273Sbostic 
19157273Sbostic /* this foolery is needed to modify tty state `atomically' */
19257273Sbostic static jmp_buf scr_onstop;
19357273Sbostic 
19457273Sbostic #define	sigunblock(mask) sigsetmask(sigblock(0) & ~(mask))
19557273Sbostic 
19657273Sbostic static void
stopset(sig)19757273Sbostic stopset(sig)
19857273Sbostic 	int sig;
19957273Sbostic {
20057273Sbostic 	(void) signal(sig, SIG_DFL);
20157273Sbostic 	(void) kill(getpid(), sig);
20257273Sbostic 	(void) sigunblock(sigmask(sig));
20357273Sbostic 	longjmp(scr_onstop, 1);
20457273Sbostic }
20557273Sbostic 
20657273Sbostic static void
scr_stop()20757273Sbostic scr_stop()
20857273Sbostic {
20957273Sbostic 	scr_end();
21057273Sbostic 	(void) kill(getpid(), SIGTSTP);
21157273Sbostic 	(void) sigunblock(sigmask(SIGTSTP));
21257273Sbostic 	scr_set();
21357273Sbostic 	scr_msg(key_msg, 1);
21457273Sbostic }
21557273Sbostic 
21657273Sbostic /*
21757273Sbostic  * Set up screen mode.
21857273Sbostic  */
21957273Sbostic void
scr_set()22057273Sbostic scr_set()
22157273Sbostic {
22257273Sbostic 	struct winsize ws;
22357273Sbostic 	struct sgttyb newtt;
22457273Sbostic 	volatile int omask;
22557273Sbostic 	void (*ttou)();
22657273Sbostic 
22757273Sbostic 	omask = sigblock(sigmask(SIGTSTP) | sigmask(SIGTTOU));
22857273Sbostic 	if ((tstp = signal(SIGTSTP, stopset)) == SIG_IGN)
22957273Sbostic 		(void) signal(SIGTSTP, SIG_IGN);
23057273Sbostic 	if ((ttou = signal(SIGTSTP, stopset)) == SIG_IGN)
23157273Sbostic 		(void) signal(SIGTSTP, SIG_IGN);
23257273Sbostic 	/*
23357273Sbostic 	 * At last, we are ready to modify the tty state.  If
23457273Sbostic 	 * we stop while at it, stopset() above will longjmp back
23557273Sbostic 	 * to the setjmp here and we will start over.
23657273Sbostic 	 */
23757273Sbostic 	(void) setjmp(scr_onstop);
23857273Sbostic 	(void) sigsetmask(omask);
23957273Sbostic 	Rows = 0, Cols = 0;
24057273Sbostic 	if (ioctl(0, TIOCGWINSZ, &ws) == 0) {
24157273Sbostic 		Rows = ws.ws_row;
24257273Sbostic 		Cols = ws.ws_col;
24357273Sbostic 	}
24457273Sbostic 	if (Rows == 0)
24557273Sbostic 		Rows = LInum;
24657273Sbostic 	if (Cols == 0)
24757273Sbostic 	Cols = COnum;
24857273Sbostic 	if (Rows < MINROWS || Cols < MINCOLS) {
24957273Sbostic 		(void) fprintf(stderr,
25057273Sbostic 		    "the screen is too small: must be at least %d x %d",
25157273Sbostic 		    MINROWS, MINCOLS);
25257273Sbostic 		stop("");	/* stop() supplies \n */
25357273Sbostic 	}
25457273Sbostic 	if (ioctl(0, TIOCGETP, &oldtt))
25557273Sbostic 		stop("ioctl(TIOCGETP) fails");
25657273Sbostic 	newtt = oldtt;
25757273Sbostic 	newtt.sg_flags = (newtt.sg_flags | CBREAK) & ~(CRMOD | ECHO);
25857273Sbostic 	if ((newtt.sg_flags & TBDELAY) == XTABS)
25957273Sbostic 		newtt.sg_flags &= ~TBDELAY;
26057273Sbostic 	if (ioctl(0, TIOCSETN, &newtt))
26157273Sbostic 		stop("ioctl(TIOCSETN) fails");
26257273Sbostic 	ospeed = newtt.sg_ospeed;
26357273Sbostic 	omask = sigblock(sigmask(SIGTSTP) | sigmask(SIGTTOU));
26457273Sbostic 
26557273Sbostic 	/*
26657273Sbostic 	 * We made it.  We are now in screen mode, modulo TIstr
26757273Sbostic 	 * (which we will fix immediately).
26857273Sbostic 	 */
26957273Sbostic 	if (TIstr)
27057273Sbostic 		putstr(TIstr);	/* termcap(5) says this is not padded */
27157273Sbostic 	if (tstp != SIG_IGN)
27257273Sbostic 		(void) signal(SIGTSTP, scr_stop);
27357273Sbostic 	(void) signal(SIGTTOU, ttou);
27457273Sbostic 
27557273Sbostic 	isset = 1;
27657273Sbostic 	(void) sigsetmask(omask);
27757273Sbostic 	scr_clear();
27857273Sbostic }
27957273Sbostic 
28057273Sbostic /*
28157273Sbostic  * End screen mode.
28257273Sbostic  */
28357273Sbostic void
scr_end()28457273Sbostic scr_end()
28557273Sbostic {
28657273Sbostic 	int omask = sigblock(sigmask(SIGTSTP) | sigmask(SIGTTOU));
28757273Sbostic 
28857273Sbostic 	/* move cursor to last line */
28957273Sbostic 	if (LLstr)
29057273Sbostic 		putstr(LLstr);	/* termcap(5) says this is not padded */
29157273Sbostic 	else
29257273Sbostic 		moveto(Rows - 1, 0);
29357273Sbostic 	/* exit screen mode */
29457273Sbostic 	if (TEstr)
29557273Sbostic 		putstr(TEstr);	/* termcap(5) says this is not padded */
29657273Sbostic 	(void) fflush(stdout);
29757273Sbostic 	(void) ioctl(0, TIOCSETN, &oldtt);
29857273Sbostic 	isset = 0;
29957273Sbostic 	/* restore signals */
30057273Sbostic 	(void) signal(SIGTSTP, tstp);
30157273Sbostic 	(void) sigsetmask(omask);
30257273Sbostic }
30357273Sbostic 
30457273Sbostic void
stop(why)30557273Sbostic stop(why)
30657273Sbostic 	char *why;
30757273Sbostic {
30857273Sbostic 
30957273Sbostic 	if (isset)
31057273Sbostic 		scr_end();
31157273Sbostic 	(void) fprintf(stderr, "aborting: %s\n", why);
31257273Sbostic 	exit(1);
31357273Sbostic }
31457273Sbostic 
31557273Sbostic /*
31657273Sbostic  * Clear the screen, forgetting the current contents in the process.
31757273Sbostic  */
31857273Sbostic void
scr_clear()31957273Sbostic scr_clear()
32057273Sbostic {
32157273Sbostic 
32257273Sbostic 	putpad(CLstr);
32357273Sbostic 	curscore = -1;
32457273Sbostic 	bzero((char *)curscreen, sizeof(curscreen));
32557273Sbostic }
32657273Sbostic 
32757273Sbostic #if vax && !__GNUC__
32857273Sbostic typedef int regcell;	/* pcc is bad at `register char', etc */
32957273Sbostic #else
33057273Sbostic typedef cell regcell;
33157273Sbostic #endif
33257273Sbostic 
33357273Sbostic /*
33457273Sbostic  * Update the screen.
33557273Sbostic  */
33657273Sbostic void
scr_update()33757273Sbostic scr_update()
33857273Sbostic {
33957273Sbostic 	register cell *bp, *sp;
34057273Sbostic 	register regcell so, cur_so = 0;
34157273Sbostic 	register int i, ccol, j;
34257273Sbostic 	int omask = sigblock(sigmask(SIGTSTP));
34357273Sbostic 
34457273Sbostic 	/* always leave cursor after last displayed point */
34557273Sbostic 	curscreen[D_LAST * B_COLS - 1] = -1;
34657273Sbostic 
34757273Sbostic 	if (score != curscore) {
34857273Sbostic 		if (HOstr)
34957273Sbostic 			putpad(HOstr);
35057273Sbostic 		else
35157273Sbostic 			moveto(0, 0);
35257273Sbostic 		(void) printf("%d", score);
35357273Sbostic 		curscore = score;
35457273Sbostic 	}
35557273Sbostic 
35657273Sbostic 	bp = &board[D_FIRST * B_COLS];
35757273Sbostic 	sp = &curscreen[D_FIRST * B_COLS];
35857273Sbostic 	for (j = D_FIRST; j < D_LAST; j++) {
35957273Sbostic 		ccol = -1;
36057273Sbostic 		for (i = 0; i < B_COLS; bp++, sp++, i++) {
36157273Sbostic 			if (*sp == (so = *bp))
36257273Sbostic 				continue;
36357273Sbostic 			*sp = so;
36457273Sbostic 			if (i != ccol) {
36557273Sbostic 				if (cur_so && MSflag) {
36657273Sbostic 					putpad(SEstr);
36757273Sbostic 					cur_so = 0;
36857273Sbostic 				}
36957273Sbostic 				moveto(RTOD(j), CTOD(i));
37057273Sbostic 			}
37157273Sbostic 			if (SOstr) {
37257273Sbostic 				if (so != cur_so) {
37357273Sbostic 					putpad(so ? SOstr : SEstr);
37457273Sbostic 					cur_so = so;
37557273Sbostic 				}
37657273Sbostic 				putstr("  ");
37757273Sbostic 			} else
37857273Sbostic 				putstr(so ? "XX" : "  ");
37957273Sbostic 			ccol = i + 1;
38057273Sbostic 			/*
38157273Sbostic 			 * Look ahead a bit, to avoid extra motion if
38257273Sbostic 			 * we will be redrawing the cell after the next.
38357273Sbostic 			 * Motion probably takes four or more characters,
38457273Sbostic 			 * so we save even if we rewrite two cells
38557273Sbostic 			 * `unnecessarily'.  Skip it all, though, if
38657273Sbostic 			 * the next cell is a different color.
38757273Sbostic 			 */
38857273Sbostic #define	STOP (B_COLS - 3)
38957273Sbostic 			if (i > STOP || sp[1] != bp[1] || so != bp[1])
39057273Sbostic 				continue;
39157273Sbostic 			if (sp[2] != bp[2])
39257273Sbostic 				sp[1] = -1;
39357273Sbostic 			else if (i < STOP && so == bp[2] && sp[3] != bp[3]) {
39457273Sbostic 				sp[2] = -1;
39557273Sbostic 				sp[1] = -1;
39657273Sbostic 			}
39757273Sbostic 		}
39857273Sbostic 	}
39957273Sbostic 	if (cur_so)
40057273Sbostic 		putpad(SEstr);
40157273Sbostic 	(void) fflush(stdout);
40257273Sbostic 	(void) sigsetmask(omask);
40357273Sbostic }
40457273Sbostic 
40557273Sbostic /*
40657273Sbostic  * Write a message (set!=0), or clear the same message (set==0).
40757273Sbostic  * (We need its length in case we have to overwrite with blanks.)
40857273Sbostic  */
40957273Sbostic void
scr_msg(s,set)41057273Sbostic scr_msg(s, set)
41157273Sbostic 	register char *s;
41257273Sbostic 	int set;
41357273Sbostic {
41457273Sbostic 
41557273Sbostic 	if (set || CEstr == NULL) {
41657273Sbostic 		register int l = strlen(s);
41757273Sbostic 
41857273Sbostic 		moveto(Rows - 2, ((Cols - l) >> 1) - 1);
41957273Sbostic 		if (set)
42057273Sbostic 			putstr(s);
42157273Sbostic 		else
42257273Sbostic 			while (--l >= 0)
42357273Sbostic 				(void) putchar(' ');
42457273Sbostic 	} else {
42557273Sbostic 		moveto(Rows - 2, 0);
42657273Sbostic 		putpad(CEstr);
42757273Sbostic 	}
42857273Sbostic }
429