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