X-Git-Url: http://git.shiar.net/netris.git/blobdiff_plain/21add7c13bc1df386e45aad2939ee5ff2a152c2b..HEAD:/curses.c diff --git a/curses.c b/curses.c index f28a6a3..d24a110 100644 --- a/curses.c +++ b/curses.c @@ -15,51 +15,46 @@ * 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.33 1999/05/16 06:56:25 mhw Exp $ */ #include "netris.h" + #include #include #include #include #include +#include "client.h" +#include "curses.h" +#include "util.h" +#include "board.h" +#include "msg.h" + #ifdef NCURSES_VERSION # define HAVE_NCURSES #endif -#ifdef HAVE_NCURSES -static struct { - BlockType type; - short color; -} myColorTable[] = { - { BT_white, COLOR_WHITE }, - { BT_blue, COLOR_BLUE }, - { BT_magenta, COLOR_MAGENTA }, - { BT_cyan, COLOR_CYAN }, - { BT_yellow, COLOR_YELLOW }, - { BT_green, COLOR_GREEN }, - { BT_red, COLOR_RED }, - { BT_none, 0 } -}; -#endif +window_t window[MAX_SCREENS]; -ExtFunc void PlotBlock1(int scr, int y, int x, BlockType type); static MyEventType KeyGenFunc(EventGenRec *gen, MyEvent *event); +static EventGenRec keyGen = { + NULL, 0, FT_read, STDIN_FILENO, KeyGenFunc, EM_key +}; -static EventGenRec keyGen = - { NULL, 0, FT_read, STDIN_FILENO, KeyGenFunc, EM_key }; - -static int boardYPos[MAX_SCREENS], boardXPos[MAX_SCREENS]; static int statusYPos, statusXPos; +static int messageYPos, messageXPos, messageHeight, messageWidth; +WINDOW *msgwin; static int haveColor; -static char *term_vi; /* String to make cursor invisible */ -static char *term_ve; /* String to make cursor visible */ +#define MSG_HEIGHT 64 //max history +static char *message[MSG_HEIGHT]; +static char messages[MSG_HEIGHT][MSG_WIDTH]; + +static char *term_vi; /* String to make cursor invisible */ +static char *term_ve; /* String to make cursor visible */ -ExtFunc void InitScreens(void) +void InitScreens(void) { MySigSet oldMask; @@ -70,7 +65,7 @@ ExtFunc void InitScreens(void) * Ctrl-C during initialization might leave the terminal in a bad state. */ BlockSignals(&oldMask, SIGINT, 0); - initscr(); + initscr(); //start curses #ifdef CURSES_HACK { @@ -81,53 +76,67 @@ ExtFunc void InitScreens(void) #endif #ifdef HAVE_NCURSES - haveColor = Game.color && has_colors(); + 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); - } + myColorTable[i].color); + } //haveColor #else haveColor = 0; #endif - AtExit(CleanupScreens); + AtExit(CleanupScreens); //restore everything when done RestoreSignals(NULL, &oldMask); - cbreak(); + cbreak(); //no line buffering noecho(); +// keypad(stdscr, TRUE); //get arrow/functionkeys 'n stuff OutputTermStr(term_vi, 0); - AddEventGen(&keyGen); + AddEventGen(&keyGen); //key handler + signal(SIGWINCH, CatchWinCh); //handle window resize +// ioctl(STDIN_FILENO, KDSKBMODE, K_MEDIUMRAW); + standend(); //normal text - move(0, 1); - addstr("NETRIS "); - addstr(version_string); + memset(messages, 0, sizeof(messages)); //empty messages { - int rows, cols; - - getmaxyx(stdscr, rows, cols); - move(0, cols - 48); - addstr("(C)1994-1996,1999 Mark H. Weaver, (C)2002 Shiar"); -// addstr(" \"netris -h\" for more info"); + int i; + for (i = 0; i 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 +} - for (y = Players[scr].boardVisible - 1; y >= 0; --y) { - mvaddch(boardYPos[scr] - y, boardXPos[scr] - 1, - Game.ascii ? '|' : ACS_VLINE); //left - mvaddch(boardYPos[scr] - y, - boardXPos[scr] + 2 * Players[scr].boardWidth, - Game.ascii ? '|' : ACS_VLINE); //right - } - move(2, boardXPos[scr] - 1); //top - addch(Game.ascii ? '+' : ACS_ULCORNER); - for (x = Players[scr].boardWidth * 2 - 1; x >= 0; --x) - addch(Game.ascii ? '-' : ACS_HLINE); - addch(Game.ascii ? '+' : ACS_URCORNER); - move(boardYPos[scr] + 1, boardXPos[scr] - 1); //bottom - addch(Game.ascii ? '+' : ACS_LLCORNER); - for (x = Players[scr].boardWidth * 2 - 1; x >= 0; --x) - addch(Game.ascii ? '-' : ACS_HLINE); - addch(Game.ascii ? '+' : ACS_LRCORNER); - } //draw field grid +static void window_border(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); +} +void window_draw(int player) +{ //draw field for player + if (!window[player].shown) return; + window_border(window[player].posx - 1, window[player].posy - Players[player].boardVisible, + window[player].posx + window[player].size * Players[player].boardWidth, window[player].posy + 1); { - char userstr[300]; - - if (Players[scr].host && Players[scr].host[0]) - sprintf(userstr, "%s <%s>", Players[scr].name, Players[scr].host); - else sprintf(userstr, "%s", Players[scr].name); - userstr[20 - 7*((Players[scr].flags & SCF_usingRobot) != 0) - - 5*((Players[scr].flags & SCF_fairRobot) != 0)] = 0; - mvaddstr(1, boardXPos[scr], userstr); - - if (Players[scr].flags & SCF_usingRobot) { - addstr((Players[scr].flags & SCF_fairRobot) - ? "(fair robot)" : "(robot)"); - } + char s[window[player].size * Players[player].boardWidth + 1]; + + if (Players[player].host && Players[player].host[0]) + snprintf(s, sizeof(s), " %s <%s> ", + Players[player].name, Players[player].host); + else snprintf(s, sizeof(s), " %s ", Players[player].name); + s[sizeof(s)] = 0; + if (haveColor && Players[player].team > 0 && Players[player].team <= 7) + attrset(A_REVERSE | COLOR_PAIR(Players[player].team + 1)); + mvaddstr(1, window[player].posx, s); + if (haveColor) standend(); } //display playername/host - ShowPause(scr); -} //DrawScreen - -ExtFunc void InitFields() -{ //place fields for all players - int scr, prevscr; - - statusXPos = 2 * Players[me].boardWidth + 3; - boardXPos[me] = 1; - boardYPos[me] = 22; - prevscr = me; - for (scr = 1; scr <= totalPlayers + 1; scr++) if (scr != me) { - boardXPos[scr] = - boardXPos[prevscr] + 2 * Players[prevscr].boardWidth + 3; - if (prevscr == me) - boardXPos[scr] += 24; //scorebar - boardYPos[scr] = 22; - prevscr = scr; + { + int x, y; + for (y = 0; y <= Players[player].boardVisible; y++) + for (x = 0; x <= Players[player].boardWidth; x++) + block_draw_window(player, y, x, block_get(player, y, x)); + } //draw field + + window_msg_status(player); +} + +void screen_setup(void) +{ //calculate positions of all fields + int i, prev; + int y, x; + int spaceavail; + + clear(); + DrawTitle(); + getmaxyx(stdscr, y, x); + window[me].size = 2; + window[me].posx = 1; + window[me].posy = 21; + window[me].shown = 1; + statusXPos = window[me].size * Players[me].boardWidth + 3; + statusYPos = 21; + status_draw(me, Players[me].score); + + messageXPos = 2; + messageYPos = 24; + messageWidth = MIN(x - messageXPos - 2, MSG_WIDTH); + messageHeight = MIN(y - messageYPos - 1, MSG_HEIGHT); + if (messageHeight < 3) { + messageWidth = MIN(x - statusXPos - 18, 27); + messageHeight = y - 3; + messageXPos = statusXPos + 16; + messageYPos = 2; + } //messagebox doesn't fit below + window_border(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 (i = messageHeight - 2; i >= 0; i--) //display message history + msg_draw(message[i]); + + spaceavail = x; + for (i = 1; i <= maxPlayer; i++) + spaceavail -= Players[i].boardWidth+2; + prev = me; + for (i = 1; i < MAX_SCREENS; i++) if (i != me) { + window[i].posy = 21; + window[i].posx = + window[prev].posx + 2 + window[prev].size * Players[prev].boardWidth; + if (prev == me) { + window[i].posx += 15; //scorebar + if (messageYPos < 24) + window[i].posx += messageWidth + 4; //messagebox + spaceavail -= window[i].posx - 3; + } //stuff before second player + if (spaceavail >= 0) { + window[i].size = 2; + spaceavail -= Players[i].boardWidth; + } //not enough space, half width + else + window[i].size = 1; + if (x < window[i].posx + 1 + window[i].size * Players[i].boardWidth) + window[i].shown = 0; //field doesn't fit on screen + else + window[i].shown = 1; + prev = i; } -} //InitScreen + for (i = 1; i <= maxPlayer; i++) + window_draw(i); +} -ExtFunc void CleanupScreen(int scr) +static void msg_draw(char *p) { + 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 PlotBlock1(int scr, int y, int x, BlockType type) -{ - int colorIndex = abs(type); +void msg_add(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); + msg_draw(s); + wclrtoeol(msgwin); + wrefresh(msgwin); +} - move(y, x); +void msg_add_char(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); +} - if (type == BT_none) - addstr(" "); - else - { - if (Game.standout) - { +static void block_draw_2(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 (haveColor) - attrset(COLOR_PAIR(colorIndex)); - else + if (Sets.standout) { + attrset(haveColor ? COLOR_PAIR(type & 15) : A_REVERSE); + } #endif - standout(); + switch (Sets.drawstyle) { + case 2: // ascii horizontally grouped + switch (type & 0xC0) { + case 0x40: // right neighbour + addstr("[["); break; + case 0x80: // left + addstr("]]"); break; + default: // both/none + addstr("[]"); break; + } // horizontal stickiness + break; + case 3: // curses grouped + switch (type & 0xF0) { + case 0x10: addch(ACS_ULCORNER); addch(ACS_URCORNER); break; // top end + case 0x20: addch(ACS_LLCORNER); addch(ACS_LRCORNER); break; // bottom end + case 0x30: addch(ACS_VLINE); addch(ACS_VLINE); break; // vertical middle + case 0x40: addch('['); addch(ACS_HLINE); break; // left end + case 0x50: addch(ACS_ULCORNER); addch(ACS_TTEE); break; // top left corner + case 0x60: addch(ACS_LLCORNER); addch(ACS_BTEE); break; // bottom left corner + case 0x70: addch(ACS_LTEE); addch(ACS_PLUS); break; // vertical+right + case 0x80: addch(ACS_HLINE); addch(']'); break; // right end + case 0x90: addch(ACS_TTEE); addch(ACS_URCORNER); break; // top right corner + case 0xA0: addch(ACS_BTEE); addch(ACS_LRCORNER); break; // bottom right corner + case 0xB0: addch(ACS_PLUS); addch(ACS_RTEE); break; // vertical+left + case 0xC0: addch(ACS_HLINE); addch(ACS_HLINE); break; // horizontal middle + case 0xD0: addch(ACS_TTEE); addch(ACS_TTEE); break; // horizontal+down + case 0xE0: addch(ACS_BTEE); addch(ACS_BTEE); break; // horizontal+up + default: addstr("[]"); break; // all/none + } // neighbours + break; + default: // non-grouped + addstr("[]"); + break; } +#ifdef HAVE_NCURSES + if (Sets.standout) standend(); +#endif + } //display one brick +} - addstr(type ? "[]" : "$$"); - standend(); - } -} //PlotBlock1 +static void block_draw_1(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 { +#ifdef HAVE_NCURSES + if (Sets.standout) { + attrset(haveColor ? COLOR_PAIR(type & 15) : A_REVERSE); + } +#endif + if ((type & 0xC0) == 0x40) + addch('['); + else if ((type & 0xC0) == 0x80) + addch(']'); + else + addch('|'); +#ifdef HAVE_NCURSES + if (Sets.standout) standend(); +#endif + } //display one brick +} -ExtFunc void PlotBlock(int scr, int y, int x, BlockType type) +void block_draw_window(int player, int y, int x, unsigned char type) { - if (y >= 0 && y < Players[scr].boardVisible && - x >= 0 && x < Players[scr].boardWidth) - PlotBlock1(scr, boardYPos[scr] - y, boardXPos[scr] + 2 * x, type); -} //PlotBlock + if (y >= 0 && y < Players[player].boardVisible + && x >= 0 && x < Players[player].boardWidth) { + if (window[player].size > 1) + block_draw_2(window[player].posy - y, window[player].posx + 2*x, type); + else + block_draw_1(window[player].posy - y, window[player].posx + x, type); + } //on screen +} +void block_draw_status(int y, int x, unsigned char type) +{ //Draw block at specified position next to field + block_draw_2(20 - y, 2 * x, type); +} -ExtFunc void PlotUnderline(int scr, int x, int flag) -{ - move(boardYPos[scr] + 1, boardXPos[scr] + 2 * x); - if (Game.ascii) - addstr(flag ? "==" : "--"); - else { - addch(flag ? ACS_BTEE : ACS_HLINE); - addch(flag ? ACS_BTEE : ACS_HLINE); - } -} //PlotUnderline - -ExtFunc void ShowScore(int scr, struct _Score score) +void status_draw(int player, struct score_t score) { //show score stuff float timer; - move(6, statusXPos); addstr("Next: "); - move(7, statusXPos + 7); addstr(" "); - ShapeIterate(Players[scr].nextShape, scr, - ShapeToNetNum(Players[scr].nextShape) == 15 ? 13 : 14, - statusXPos / 2 + 5, 1, GlanceFunc, NULL); - move(statusYPos - 21 + 1, statusXPos); - printw("Score:%6d level: %2d", score.score, score.level); - move(statusYPos - 20 + 1, statusXPos); + mvaddstr(13, statusXPos, MSG_NEXT " "); + mvaddstr(14, statusXPos + 5, " "); + shape_iterate(Players[player].nextShape, player, 8, + statusXPos/2 + (Players[player].nextShape/4 == 5 ? 3 : 4), + block_iter_set_status); //draw; BT_I 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; - printw("Lines:%6d", score.lines); if (timer > 4) { - printw(" ppm:%5.1f", score.drops * 60 / timer); - move(statusYPos - 18, statusXPos); - if (score.lines > 0) - printw("yield: %3d%%", 100 * score.adds / score.lines); - else addstr(" "); - printw(" apm:%5.1f", score.adds * 60 / timer); - } -} //ShowScore + 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.. +} -ExtFunc void FieldMessage(int playa, char *message) -{ //put a message over playa's field - if (!Players[playa].spy) return; +void window_msg(int player, char *message) +{ //put a message over player's field + if (!window[player].shown) return; if (message) { - char s[MAX_BOARD_WIDTH+1]; - memset(s, ' ', MAX_BOARD_WIDTH); - memcpy(&s[Players[playa].boardWidth - strlen(message) / 2], - message, strlen(message)); - s[Players[playa].boardWidth * 2] = 0; - if (Game.standout) standout(); - mvprintw(boardYPos[playa] - Players[playa].boardVisible / 2, - boardXPos[playa], "%s", s); + const int fieldsize = Players[player].boardWidth * window[player].size; + const int centered = (fieldsize - strlen(message)) / 2; + char s[fieldsize + 1]; + + memset(s, ' ', fieldsize); + memcpy(&s[centered], message, strlen(message)); + s[fieldsize] = 0; +#ifdef HAVE_NCURSES + attrset(A_REVERSE); +#else + standout(); +#endif + mvprintw(window[player].posy - Players[player].boardVisible / 2, + window[player].posx, "%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)); + y = Players[player].boardVisible / 2; + for (x = 0; x <= Players[player].boardWidth; x++) + block_draw_window(player, y, x, block_get(player, y, x)); } //restore field -} //FieldMessage +} -ExtFunc void ShowPause(int playa) -{ //put paused over player's field - if (Players[playa].flags & SCF_paused) - FieldMessage(playa, "P A U S E D"); - else FieldMessage(playa, NULL); -} //ShowPause +void window_msg_wide(int player, char *message) +{ + int i; + char *messagewide = malloc(strlen(message) * 2); // max += strlen - 1 + const int fieldsize = Players[player].boardWidth * window[player].size; + + const bool sep = strchr(message, ' ') != NULL; + // whitespace to pad at convenience + const bool pad = strlen(message) * 2 - sep <= fieldsize; + // (space to) put whitespace between all characters + bool odd = fieldsize & 1; + // odd number of characters (center off; try to change padding at sep) + if (!pad) odd ^= strlen(message) & 1; + // for odd strings, check for even fieldsize instead + + if (pad || (sep && odd && strlen(message) < fieldsize)) { + // generate padded message in messagewide + for (i = 0; ; message++) { + messagewide[i++] = *message; + if (message[1] == 0) { + messagewide[i] = 0; + break; + } + if (pad ? (*message != ' ' || odd) : (*message == ' ' && odd)) { + // add padding if wide; different padding at space if odd + messagewide[i++] = ' '; + odd = 0; + } + } + message = messagewide; + } + window_msg(player, message); +} + +void window_msg_status(int player) +{ //put status (pause, readiness, game over) over player's field + if (Players[player].alive > 0) + if (Players[player].flags & SCF_paused) + if (Game.started > 1) + window_msg_wide(player, MSG_PLAYER_PAUSE); + else + window_msg_wide(player, MSG_PLAYER_JOIN); + else + if (Game.started > 1) + window_msg(player, NULL); + else + window_msg_wide(player, MSG_PLAYER_START); + else if (!Players[player].alive) + window_msg_wide(player, MSG_PLAYER_STOP); + else + window_msg_wide(player, MSG_PLAYER_PART); +} -ExtFunc void Message(char *s) -{ - static int line = 0; - -// move(statusYPos - 20 + line, statusXPos); - move(statusYPos + 2 + line, 1); - addstr(s); /* XXX Should truncate long lines */ - clrtoeol(); - line = (line + 1) % 10; -// move(statusYPos - 20 + line, statusXPos); - move(statusYPos + 2 + line, 1); - clrtoeol(); -} //Message - -ExtFunc void ShowTime(void) +void status_tick(void) { //display timer - move(statusYPos, statusXPos); - printw("Timer: %.0f ", CurTimeval() / 1e6); - move(boardYPos[0] + 1, boardXPos[0] + 2 * Players[0].boardWidth + 1); -// refresh(); -} //ShowTime + mvprintw(statusYPos, statusXPos, MSG_TIME, CurTimeval() / 1e6); +} -ExtFunc void ScheduleFullRedraw(void) +void ScheduleFullRedraw(void) { touchwin(stdscr); -} //ScheduleFullRedraw +} + +static void CatchWinCh(int sig) +{ //handle window resize + endwin(); //exit curses + refresh(); //and reinit display (with different sizes) + screen_setup();//manually redraw everything + refresh(); //refresh +} static MyEventType KeyGenFunc(EventGenRec *gen, MyEvent *event) { //read keypresses @@ -389,9 +617,5 @@ static MyEventType KeyGenFunc(EventGenRec *gen, MyEvent *event) return E_key; else return E_none; -} //KeyGenFunc +} -/* - * vi: ts=4 ai - * vim: noai si - */