X-Git-Url: http://git.shiar.net/netris.git/blobdiff_plain/c11ae0d113cc5f60bfd1bed29b47211013f8adef..cc18d725f485013b9be8bb16f3a93e5eef12d5c1:/curses.c diff --git a/curses.c b/curses.c index 2d93900..7b5a26d 100644 --- a/curses.c +++ b/curses.c @@ -1,6 +1,6 @@ /* - * Netris -- A free networked version of Tetris - * Copyright (C) 1994,1995 Mark Weaver + * Netris -- A free networked version of T*tris + * Copyright (C) 1994-1996,1999 Mark H. Weaver * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -15,200 +15,591 @@ * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - * - * $Id: curses.c,v 1.29 1995/07/11 08:53:21 mhw Exp $ */ #include "netris.h" + #include #include #include #include #include -static void PlotBlock1(int scr, int y, int x, BlockType type); -static MyEventType KeyGenFunc(EventGenRec *gen, MyEvent *event); +#include "client.h" +#include "curses.h" +#include "util.h" +#include "board.h" +#include "msg.h" -static EventGenRec keyGen = - { NULL, 0, FT_read, STDIN_FILENO, KeyGenFunc, EM_key }; +#ifdef NCURSES_VERSION +# define HAVE_NCURSES +#endif + +static MyEventType KeyGenFunc(EventGenRec *gen, MyEvent *event); +static EventGenRec keyGen = { + NULL, 0, FT_read, STDIN_FILENO, KeyGenFunc, EM_key +}; static int boardYPos[MAX_SCREENS], boardXPos[MAX_SCREENS]; +static int boardSize[MAX_SCREENS]; +//^^^struct static int statusYPos, statusXPos; +static int messageYPos, messageXPos, messageHeight, messageWidth; +WINDOW *msgwin; +static int haveColor; +int PlayerDisp[MAX_SCREENS]; + +#define MSG_HEIGHT 64 //max history +char *message[MSG_HEIGHT]; +char messages[MSG_HEIGHT][MSG_WIDTH]; -ExtFunc void InitScreens(void) +static char *term_vi; /* String to make cursor invisible */ +static char *term_ve; /* String to make cursor visible */ + +void InitScreens(void) { MySigSet oldMask; + GetTermcapInfo(); + + /* + * Block signals while initializing curses. Otherwise a badly timed + * Ctrl-C during initialization might leave the terminal in a bad state. + */ BlockSignals(&oldMask, SIGINT, 0); - initscr(); - AtExit(CleanupScreens); + initscr(); //start curses + +#ifdef CURSES_HACK + { + extern char *CS; + + CS = 0; + } +#endif + +#ifdef HAVE_NCURSES + haveColor = Sets.color && has_colors(); + if (haveColor) { + static struct { + char type; + short color; + } myColorTable[] = { + { BT_T, COLOR_WHITE }, + { BT_I, COLOR_BLUE }, + { BT_O, COLOR_MAGENTA }, + { BT_L, COLOR_CYAN }, + { BT_J, COLOR_YELLOW }, + { BT_S, COLOR_GREEN }, + { BT_Z, COLOR_RED }, + { BT_none, 0 } + }; //myColorTable + int i = 0; + + start_color(); + if (can_change_color()) { + init_color (COLOR_YELLOW, 1000, 1000, 0); + } //I've never worked on a color-changable terminal, so no idea.. + for (i = 0; myColorTable[i].type != BT_none; ++i) + init_pair(myColorTable[i].type, COLOR_BLACK, + myColorTable[i].color); + } //haveColor +#else + haveColor = 0; +#endif + + AtExit(CleanupScreens); //restore everything when done RestoreSignals(NULL, &oldMask); - cbreak(); + + cbreak(); //no line buffering noecho(); - AddEventGen(&keyGen); - move(0, 0); - addstr("Netris "); - addstr(version_string); - addstr(" (C) 1994,1995 Mark Weaver " - "\"netris -h\" for more info"); - statusYPos = 22; - statusXPos = 0; +// keypad(stdscr, TRUE); //get arrow/functionkeys 'n stuff + OutputTermStr(term_vi, 0); + AddEventGen(&keyGen); //key handler + signal(SIGWINCH, CatchWinCh); //handle window resize +// ioctl(STDIN_FILENO, KDSKBMODE, K_MEDIUMRAW); + standend(); //normal text + + memset(messages, 0, sizeof(messages)); //empty messages + { + int i; + for (i = 0; i= 0; --y) { - move(boardYPos[scr] - y, boardXPos[scr] - 1); - addch('|'); - move(boardYPos[scr] - y, boardXPos[scr] + 2 * boardWidth[scr]); - addch('|'); + if (!(term = getenv("TERM"))) + return; + if (tgetent(scratch, term) == 1) { + /* + * Make the buffer HUGE, since tgetstr is unsafe. + * Allocate it on the heap too. + */ + data = buf = malloc(bufSize); + + /* + * There is no standard include file for tgetstr, no prototype + * definitions. I like casting better than using my own prototypes + * because if I guess the prototype, I might be wrong, especially + * with regards to "const". + */ + term_vi = (char *)tgetstr("vi", &data); + term_ve = (char *)tgetstr("ve", &data); + + /* Okay, so I'm paranoid; I just don't like unsafe routines */ + if (data > buf + bufSize) + fatal("tgetstr overflow, you must have a very sick termcap"); + + /* Trim off the unused portion of buffer */ + buf = realloc(buf, data - buf); } - for (y = boardVisible[scr]; y >= -1; y -= boardVisible[scr] + 1) { - move(boardYPos[scr] - y, boardXPos[scr] - 1); - addch('+'); - for (x = boardWidth[scr] - 1; x >= 0; --x) - addstr("--"); - addch('+'); + + /* + * If that fails, use hardcoded vt220 codes. + * They don't seem to do anything bad on vt100's, so + * we'll try them just in case they work. + */ + if (!term_vi || !term_ve) { + static char *vts[] = { + "vt100", "vt101", "vt102", + "vt200", "vt220", "vt300", + "vt320", "vt400", "vt420", + "screen", "xterm", NULL + }; + int i; + + for (i = 0; vts[i]; i++) + if (!strcmp(term, vts[i])) { + term_vi = "\033[?25l"; + term_ve = "\033[?25h"; + break; + } } + if (!term_vi || !term_ve) + term_vi = term_ve = NULL; } -ExtFunc void CleanupScreen(int scr) +void OutputTermStr(char *str, int flush) { + if (str) { + fputs(str, stdout); + if (flush) fflush(stdout); + } } -static void PlotBlock1(int scr, int y, int x, BlockType type) +void DrawTitle(void) { - move(boardYPos[scr] - y, boardXPos[scr] + 2 * x); - switch (type) { - case BT_none: - addstr(" "); - break; - case -BT_piece1: - if (standoutEnable) - standout(); - addstr("$$"); - if (standoutEnable) - standend(); - break; - case BT_piece1: - if (standoutEnable) - standout(); - addstr("[]"); - if (standoutEnable) - standend(); - break; - default: - assert(0); - } + int rows, cols; + char *s; + +#ifdef HAVE_NCURSES + attrset(A_REVERSE); +#else + standout(); +#endif + getmaxyx(stdscr, rows, cols); + s = malloc(cols + 1); + sprintf(s, " " MSG_TITLE " %s", version_string); + const int titlelen = strlen(s); + memset(&s[titlelen], ' ', cols - strlen(MSG_TITLE)); // pad + if (cols > titlelen + 1 + strlen(MSG_TITLESUB)) + memcpy(&s[cols - 1 - strlen(MSG_TITLESUB)], MSG_TITLESUB, sizeof(MSG_TITLESUB) - 1); + memcpy(&s[cols], "\0", 1); + mvaddstr(0, 0, s); + free(s); + standend(); //normal text } -ExtFunc void PlotBlock(int scr, int y, int x, BlockType type) -{ - if (y >= 0 && y < boardVisible[scr] && x >= 0 && x < boardWidth[scr]) - PlotBlock1(scr, y, x, type); +void DrawBox(int x1, int y1, int x2, int y2) +{ //draw grid + int y, x; + + for (y = y1 + 1; y < y2; y++) { + mvaddch(y, x1, Sets.ascii ? '|' : ACS_VLINE); //left + mvaddch(y, x2, Sets.ascii ? '|' : ACS_VLINE); //right + } //draw vertical lines + move(y1, x1); //top + addch(Sets.ascii ? '+' : ACS_ULCORNER); + for (x = x1 + 1; x < x2; x++) + addch(Sets.ascii ? '-' : ACS_HLINE); + addch(Sets.ascii ? '+' : ACS_URCORNER); + move(y2, x1); //bottom + addch(Sets.ascii ? '+' : ACS_LLCORNER); + for (x = x1 + 1; x < x2; x++) + addch(Sets.ascii ? '-' : ACS_HLINE); + addch(Sets.ascii ? '+' : ACS_LRCORNER); } -ExtFunc void PlotUnderline(int scr, int x, int flag) -{ - move(boardYPos[scr] + 1, boardXPos[scr] + 2 * x); - addstr(flag ? "==" : "--"); +void DrawField(int scr) +{ //draw field for player scr + if (!PlayerDisp[scr]) return; + DrawBox(boardXPos[scr] - 1, boardYPos[scr] - Players[scr].boardVisible, + boardXPos[scr] + boardSize[scr] * Players[scr].boardWidth, boardYPos[scr] + 1); + { + char s[boardSize[scr]*Players[scr].boardWidth+1]; + + if (Players[scr].host && Players[scr].host[0]) + snprintf(s, sizeof(s), " %s <%s> ", + Players[scr].name, Players[scr].host); + else snprintf(s, sizeof(s), " %s ", Players[scr].name); + s[sizeof(s)] = 0; + if (haveColor && Players[scr].team > 0 && Players[scr].team <= 7) + attrset(A_REVERSE | COLOR_PAIR(Players[scr].team + 1)); + mvaddstr(1, boardXPos[scr], s); + if (haveColor) standend(); + } //display playername/host + + { + int x, y; + for (y = 0; y <= Players[scr].boardVisible; y++) + for (x = 0; x <= Players[scr].boardWidth; x++) + PlotBlock(scr, y, x, GetBlock(scr, y, x)); + } //draw field + + ShowPause(scr); } -ExtFunc void ShowDisplayInfo(void) -{ - move(statusYPos - 9, statusXPos); - printw("Seed = %d", initSeed); - clrtoeol(); - move(statusYPos - 8, statusXPos); - printw("Speed = %dms", speed / 1000); - clrtoeol(); - if (robotEnable) { - move(statusYPos - 6, statusXPos); - if (fairRobot) - addstr("Controlled by a fair robot"); +void InitFields(void) +{ //calculate positions of all fields + int scr, prevscr; + int y, x; + int spaceavail; + + clear(); + DrawTitle(); + getmaxyx(stdscr, y, x); + boardSize[me] = 2; + boardXPos[me] = 1; + boardYPos[me] = 21; + PlayerDisp[me] = 1; + statusXPos = boardSize[me] * Players[me].boardWidth + 3; + statusYPos = 21; + ShowScore(me, Players[me].score); + + messageXPos = 2; + messageYPos = 24; + messageWidth = MIN(x - messageXPos - 2, MSG_WIDTH); + messageHeight = MIN(y - messageYPos - 1, MSG_HEIGHT); + if (messageHeight <= 0) { + messageWidth = 27; + messageHeight = y - 3; + messageXPos = statusXPos + 16; + messageYPos = 2; + } //messagebox doesn't fit below + DrawBox(messageXPos - 2, messageYPos - 1, + messageXPos + messageWidth + 1, messageYPos+messageHeight); + if (msgwin = subwin(stdscr, messageHeight, messageWidth, + messageYPos, messageXPos)) + scrollok(msgwin, 1); //allow scrolling + wmove(msgwin, messageHeight - 2, 0); + for (scr = messageHeight - 2; scr >= 0; scr--) //display message history + DisplayMessage(message[scr]); + + spaceavail = x; + for (scr = 1; scr <= maxPlayer; scr++) + spaceavail -= Players[scr].boardWidth+2; + prevscr = me; + for (scr = 1; scr < MAX_SCREENS; scr++) if (scr != me) { + boardYPos[scr] = 21; + boardXPos[scr] = + boardXPos[prevscr] + 2 + boardSize[prevscr] * Players[prevscr].boardWidth; + if (prevscr == me) { + boardXPos[scr] += 15; //scorebar + if (messageYPos < 24) + boardXPos[scr] += messageWidth + 4; //messagebox + spaceavail -= boardXPos[scr] - 3; + } //stuff before second player + if (spaceavail >= 0) { + boardSize[scr] = 2; + spaceavail -= Players[scr].boardWidth; + } //not enough space, half width else - addstr("Controlled by a robot"); - clrtoeol(); - } - if (opponentFlags & SCF_usingRobot) { - move(statusYPos - 5, statusXPos); - if (opponentFlags & SCF_fairRobot) - addstr("The opponent is a fair robot"); + boardSize[scr] = 1; + if (x < boardXPos[scr] + 1 + boardSize[scr] * Players[scr].boardWidth) + PlayerDisp[scr] = 0; //field doesn't fit on screen else - addstr("The opponent is a robot"); - clrtoeol(); + PlayerDisp[scr] = 1; + prevscr = scr; } + for (scr = 1; scr <= maxPlayer; scr++) + DrawField(scr); } -ExtFunc void UpdateOpponentDisplay(void) +void DisplayMessage(char *p) { - move(1, 0); - printw("Playing %s@%s", opponentName, opponentHost); - clrtoeol(); + char s[MSG_WIDTH]; + char *psearch; + char c; + + memcpy(s, p, sizeof(s)-1); + s[MSG_WIDTH-1] = 0; + p = s; + while (psearch = strchr(p, '\\')) { + *psearch = '\0'; + waddstr(msgwin, p); + c = atoi(++psearch) + 1; + if (haveColor) wattrset(msgwin, A_REVERSE | COLOR_PAIR(c)); + p = ++psearch; + } //search for color escapes (\) + waddstr(msgwin, p); + if (haveColor) wstandend(msgwin); + waddch(msgwin, '\n'); } -ExtFunc void ShowPause(int pausedByMe, int pausedByThem) +void Message(char *fmt, ...) +{ //print game/bot message + va_list args; + char s[MSG_WIDTH]; + char *p; + int i; + + if (!messageHeight) return; + va_start(args, fmt); + vsnprintf(s, sizeof(s), fmt, args); + va_end(args); + p = message[MSG_HEIGHT - 1]; //save last pointer + for (i = MSG_HEIGHT - 1; i > 0; i--) + message[i] = message[i - 1]; //scroll history + message[0] = p; + strcpy(p, s); + + wmove(msgwin, messageHeight - 1, 0); + DisplayMessage(s); + wclrtoeol(msgwin); + wrefresh(msgwin); +} + +void Messagetype(char c, int x, char *s) +{ //show single typed character + if (c == 27) { + mvwaddch(msgwin, messageHeight-1, (x+1) % (messageWidth-1), ' '); + } //escape + else { + if (c == 13 || c == 127) //enter/backspace + mvwaddch(msgwin, messageHeight - 1, (x+2) % (messageWidth-1), + x >= messageWidth-3 ? s[x - messageWidth + 3] : ' '); + else //any character + mvwaddch(msgwin, messageHeight - 1, x % (messageWidth-1), c); + mvwaddch(msgwin, messageHeight - 1, (x+1) % (messageWidth-1), '_'); + } //typing mode + wrefresh(msgwin); +} + +void PlotBlock1(int y, int x, unsigned char type) +{ //display block on screen + move(y, x); + if (type == BT_none) addstr(" "); + else if (type == BT_shadow) addstr("::"); + else { +#ifdef HAVE_NCURSES + if (Sets.standout) { + if (haveColor) attrset(COLOR_PAIR(type & 15)); + else attrset(A_REVERSE); + } +#endif + switch (Sets.drawstyle) { + case 2: + switch (type & 192) { + case 64: //right neighbour + addstr("[["); break; + case 128: //left + addstr("]]"); break; + default: //both/none + addstr("[]"); break; + } //horizontal stickiness + break; //ascii horizontally grouped + case 3: + switch (type & 240) { + case 48: + addstr("||"); break; //middle + case 64: case 80: case 96: + addstr("[="); break; //left end + case 112: + addstr("|="); break; + case 128: case 144: case 160: + addstr("=]"); break; //right end + case 176: + addstr("=|"); break; + case 192: case 208: case 224: + addstr("=="); break; + default: + addstr("[]"); break; //top/bottom/mid + } //neighbours + break; //ascii semi-grouped + case 7: + switch (type & 240) { + case 16: addch(ACS_ULCORNER); addch(ACS_URCORNER); break;//top end + case 32: addch(ACS_LLCORNER); addch(ACS_LRCORNER); break;//bottom end + case 48: addch(ACS_VLINE); addch(ACS_VLINE); break; //vertical middle + case 64: addch('['); addch(ACS_HLINE); break; //left end + case 80: addch(ACS_ULCORNER); addch(ACS_TTEE); break; //top left corner + case 96: addch(ACS_LLCORNER); addch(ACS_BTEE); break; //bottom left corner + case 112: addch(ACS_LTEE); addch(ACS_PLUS); break; //vertical+right + case 128: addch(ACS_HLINE); addch(']'); break; //right end + case 144: addch(ACS_TTEE); addch(ACS_URCORNER); break; //top right corner + case 160: addch(ACS_BTEE); addch(ACS_LRCORNER); break; //bottom right corner + case 176: addch(ACS_PLUS); addch(ACS_RTEE); break; //vertical+left + case 192: addch(ACS_HLINE); addch(ACS_HLINE); break; //horizontal middle + case 208: addch(ACS_TTEE); addch(ACS_TTEE); break; //horizontal+down + case 224: addch(ACS_BTEE); addch(ACS_BTEE); break; //horizontal+up + default: addstr("[]"); break; + } //neighbours + break; //curses grouped + default: + addstr("[]"); + break; //ascii non-grouped + } //draw block +#ifdef HAVE_NCURSES + if (Sets.standout) standend(); +#endif + } //display one brick +} +void PlotBlock1S(int y, int x, unsigned char type) +{ //display block small + move(y, x); + if (type == BT_none) addch(' '); + else if (type == BT_shadow) addch(':'); + else { + if (Sets.standout) { +#ifdef HAVE_NCURSES + if (haveColor) + attrset(COLOR_PAIR(type & 15)); + else attrset(A_REVERSE); +#endif + } + if ((type & 192) == 64) + addch('['); + else if ((type & 192) == 128) + addch(']'); + else + addch('|'); +#ifdef HAVE_NCURSES + if (Sets.standout) standend(); +#endif + } //display one brick +} +void PlotBlock(int scr, int y, int x, unsigned char type) { - move(statusYPos - 3, statusXPos); - if (pausedByThem) - addstr("Game paused by opponent"); - else - clrtoeol(); - move(statusYPos - 2, statusXPos); - if (pausedByMe) - addstr("Game paused by you"); + if (y >= 0 && y < Players[scr].boardVisible + && x >= 0 && x < Players[scr].boardWidth) { + if (boardSize[scr] > 1) + PlotBlock1(boardYPos[scr] - y, boardXPos[scr] + 2*x, type); + else + PlotBlock1S(boardYPos[scr] - y, boardXPos[scr] + x, type); + } //on screen +} +void PlotBlockXY(int y, int x, unsigned char type) +{ //Draw block at specified position on screen (not on field) + PlotBlock1(20 - y, 2 * x, type); +} + +void ShowScore(int scr, struct _Score score) +{ //show score stuff + float timer; + + mvaddstr(13, statusXPos, MSG_NEXT " "); + mvaddstr(14, statusXPos + 5, " "); + ShapeIterate(Players[scr].nextShape, scr, 8, + statusXPos/2 + (Players[scr].nextShape/4 == 5 ? 3 : 4), + GlanceFunc); //draw; stick one more to the left + mvprintw(3, statusXPos, MSG_LEVEL, score.level); + mvprintw(5, statusXPos, MSG_SCORE, score.score); + mvprintw(6, statusXPos, MSG_LINES, score.lines); + timer = CurTimeval() / 1e6; + if (timer > 4) { + mvprintw(9, statusXPos, MSG_PPM, score.pieces * 60 / timer); + if (score.lines > 0) { + mvprintw(7, statusXPos, MSG_YIELD, 100 * score.adds / score.lines); + mvprintw(10, statusXPos, MSG_APM, score.adds * 60 / timer); + } + } //display [ap]pm + else { + int i; + for (i = 7; i <= 10; i++) + mvaddstr(i, statusXPos, " "); + } //too early to display stats, remove old.. +} + +void FieldMessage(int playa, char *message) +{ //put a message over playa's field + if (!PlayerDisp[playa]) return; + if (message) { + char s[MAX_BOARD_WIDTH+1]; + memset(s, ' ', MAX_BOARD_WIDTH); + memcpy(&s[(boardSize[playa] * Players[playa].boardWidth / 2) - (strlen(message) / 2)], + message, strlen(message)); + s[boardSize[playa] * Players[playa].boardWidth] = 0; +#ifdef HAVE_NCURSES + attrset(A_REVERSE); +#else + standout(); +#endif + mvprintw(boardYPos[playa] - Players[playa].boardVisible / 2, + boardXPos[playa], "%s", s); + standend(); + } //display + else { + int x, y; + y = Players[playa].boardVisible / 2; + for (x = 0; x <= Players[playa].boardWidth; x++) + PlotBlock(playa, y, x, GetBlock(playa, y, x)); + } //restore field +} + +void ShowPause(int playa) +{ //put paused over player's field + if (Players[playa].alive > 0) + if (Players[playa].flags & SCF_paused) + if (Game.started > 1) + FieldMessage(playa, boardSize[playa] > 1 ? "P A U S E D" : "PAUSED"); + else + FieldMessage(playa, + boardSize[playa] > 1 ? "N O T R E A D Y" : "NOT READY"); + else + if (Game.started > 1) + FieldMessage(playa, NULL); + else + FieldMessage(playa, boardSize[playa] > 1 ? "R E A D Y" : "READY"); + else if (!Players[playa].alive) + FieldMessage(playa, + boardSize[playa] > 1 ? "G A M E O V E R" : "GAME OVER"); else - clrtoeol(); + FieldMessage(playa, boardSize[playa] > 1 ? "E M P T Y" : "EMPTY"); } -ExtFunc void Message(char *s) -{ - static int line = 0; - move(statusYPos - 20 + line, statusXPos); - addstr(s); /* XXX Should truncate long lines */ - clrtoeol(); - line = (line + 1) % 10; - move(statusYPos - 20 + line, statusXPos); - clrtoeol(); +void ShowTime(void) +{ //display timer + mvprintw(statusYPos, statusXPos, "timer %7.0f ", CurTimeval() / 1e6); } -ExtFunc void RefreshScreen(void) +void ScheduleFullRedraw(void) { - static char timeStr[2][32]; - time_t theTime; + touchwin(stdscr); +} - time(&theTime); - strftime(timeStr[0], 30, "%I:%M %p", localtime(&theTime)); - /* Just in case the local curses library sucks */ - if (strcmp(timeStr[0], timeStr[1])) - { - move(statusYPos, statusXPos); - addstr(timeStr[0]); - strcpy(timeStr[1], timeStr[0]); - } - move(boardYPos[0] + 1, boardXPos[0] + 2 * boardWidth[0] + 1); - refresh(); +void CatchWinCh(int sig) +{ //handle window resize + endwin(); //exit curses + refresh(); //and reinit display (with different sizes) + InitFields(); //manually redraw everything + refresh(); //refresh } static MyEventType KeyGenFunc(EventGenRec *gen, MyEvent *event) -{ +{ //read keypresses if (MyRead(gen->fd, &event->u.key, 1)) return E_key; else