/* cursesdisplay.c Copyright (C) 2010-2019 Amf This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #ifdef CHROMA_CURSES_HEADER #include CHROMA_CURSES_HEADER #else #ifdef __WIN32__ #include #else #include #endif #endif #include "chroma.h" #include "menu.h" #include "level.h" #include "display.h" #include "colours.h" #include "actions.h" #include "util.h" #include "xmlparser.h" char options_colours[FILENAME_MAX] = COLOURS_DEFAULT; int options_curses_delay = 1; int options_curses_replay_delay = 1; int options_debug = 0; #ifdef XOR_COMPATIBILITY int options_xor_options = 0; int options_xor_mode = 1; int options_xor_display = 0; #endif #ifdef ENIGMA_COMPATIBILITY int options_enigma_options = 0; int options_enigma_mode = 1; #endif extern struct colours* pdisplaycolours; extern int *editor_piece_maps[]; extern char *action_name[]; extern char *action_shortname[]; /* Translation table for colours. This is necessary as some versions of curses interchange red and blue. */ short colourtrans[] = {COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_YELLOW, COLOR_BLUE, COLOR_MAGENTA, COLOR_CYAN, COLOR_WHITE}; int dp_attr[256], dp_col[256]; char dp_char[256]; int actions[KEY_MAX]; int display_size_x, display_size_y; int display_offset_x, display_offset_y; int display_focus_x, display_focus_y; int display_start_x, display_start_y; int display_end_x, display_end_y; int display_border = 7; void display_piece(struct level* plevel, int piece); int display_colourpairs = 0; short display_cpfore[64]; short display_cpback[64]; short colourpair_red; short colourpair_green; short colourpair_yellow; short colourpair_blue; short colourpair_cyan; short colourpair_magenta; short colourpair_cyan; short colourpair_white; short colourpair_menu; short colourpair_menugrey; char *display_keyname(int i); void display_addkeytomenu(struct menu* pmenu, int action, char *text); void display_keys(); void display_debug(); void display_initcolours(); void display_options_othergames(); short display_newcolourpair(short foreground, short background) { short i; for(i = 1; i <= display_colourpairs; i ++) { if(foreground == display_cpfore[i] && background == display_cpback[i]) return i; } display_colourpairs ++; display_cpfore[display_colourpairs] = foreground; display_cpback[display_colourpairs] = background; init_pair(display_colourpairs, foreground, background); return display_colourpairs; } void display_init() { setlocale(LC_CTYPE, ""); atexit(display_quit); initscr(); raw(); noecho(); keypad(stdscr, TRUE); curs_set(0); start_color(); getmaxyx(stdscr, display_size_y, display_size_x); colourpair_red = display_newcolourpair(COLOR_RED, COLOR_BLACK); colourpair_green = display_newcolourpair(COLOR_GREEN, COLOR_BLACK); colourpair_yellow = display_newcolourpair(COLOR_YELLOW, COLOR_BLACK); colourpair_blue = display_newcolourpair(COLOR_BLUE, COLOR_BLACK); colourpair_magenta = display_newcolourpair(COLOR_MAGENTA, COLOR_BLACK); colourpair_cyan = display_newcolourpair(COLOR_CYAN, COLOR_BLACK); colourpair_white = display_newcolourpair(COLOR_WHITE, COLOR_BLACK); colourpair_menu = display_newcolourpair(COLOR_CYAN, COLOR_BLUE); colourpair_menugrey = display_newcolourpair(COLOR_CYAN, COLOR_BLACK); display_options_load(); colours_init(); display_initcolours(); } void display_initcolours() { int i; short fg, bg; #ifdef PDCURSES short tg; #endif for(i = 0; i < PIECE_MAX; i ++) { fg = pdisplaycolours->foreground[i]; if(fg < 0 || fg > 7) fg = 7; bg = pdisplaycolours->background[i]; if(bg < 0 || bg > 7) bg = 0; #ifdef PDCURSES /* PDCurses doesn't handle reverse colours well; we swap them manually */ if(pdisplaycolours->reverse[i]) { tg = fg; fg = bg; bg = tg; } #endif dp_attr[i] = COLOR_PAIR(display_newcolourpair(colourtrans[fg], colourtrans[bg])); if(pdisplaycolours->bold[i]) dp_attr[i] |= A_BOLD; #ifndef PDCURSES if(pdisplaycolours->reverse[i]) dp_attr[i] |= A_REVERSE; #endif } } void display_quit() { clear(); refresh(); endwin(); } void display_hide() { clear(); refresh(); getch(); } void display_piece(struct level* plevel, int piece) { int p; if(piece < 0 || piece >= PIECE_MAX) return; /* Use the colours of PIECE_PLAYER_ONE for the active player, and the colours of PIECE_PLAYER_TWO for the inactive one. */ p = piece; if(p == PIECE_PLAYER_ONE || p == PIECE_PLAYER_TWO) { if(plevel->player != 2) { if(plevel->player != (p & 1)) p = PIECE_PLAYER_TWO; else p = PIECE_PLAYER_ONE; } else p = PIECE_PLAYER_ONE; } addch(pdisplaycolours->character[piece] | dp_attr[p]); } void display_moves(struct level* plevel, struct level* plevelreplay) { static int length = 0; int i; char buffer[256]; int moves, moves2; moves = 0; if(plevel->move_current != NULL) { /* If move_current->mover_first == NULL, we've actually undone all of the current move, and are just about to move back to the previous one; we treat it as the previous one for counting purposes */ if(plevel->move_current->mover_first != NULL) moves = plevel->move_current->count; else moves = plevel->move_current->count - 1; } moves2 = -1; if(plevelreplay != NULL) { moves2 = 0; if(plevelreplay->move_last != NULL) moves2 = plevelreplay->move_last->count; } /* Similarly, move_current->mover_first == NULL complicates things here */ else if(plevel->move_current != plevel->move_last || (plevel->move_current != NULL && plevel->move_current->mover_first == NULL)) { if(plevel->move_last != NULL) moves2 = plevel->move_last->count; } if(moves2 != -1) sprintf(buffer, "%s%d/%d", plevel->flags & LEVELFLAG_PAUSED ? gettext("paused ") : plevelreplay != NULL ? gettext("replay ") : "", moves, moves2); else sprintf(buffer, "%s%d", plevel->flags & LEVELFLAG_PAUSED ? gettext("paused ") : "", moves); if(plevel->flags & LEVELFLAG_FAILED) sprintf(buffer, gettext("failed")); attron(COLOR_PAIR(colourpair_cyan)); /* Blank previous display only if necessary */ if(utf8strlen(buffer) < length) { for(i = 0; i < length; i ++) mvprintw(display_size_y - 1, display_size_x - 2 - length + i, " "); } length = utf8strlen(buffer); mvprintw(display_size_y - 1, display_size_x - utf8strlen(buffer) - 2, "%s", buffer); attroff(COLOR_PAIR(colourpair_cyan)); move(display_size_y - 1, display_size_x - 1); display_piece(plevel, PIECE_PLAYER_ONE + plevel->player); refresh(); } void display_stars(struct level* plevel) { static int length = 0; char buffer[256]; int i; sprintf(buffer, "%d/%d", plevel->stars_caught, plevel->stars_total); if(plevel->stars_exploded != 0) sprintf(buffer, gettext("%d lost"), plevel->stars_exploded); if(plevel->flags & LEVELFLAG_SOLVED && !(plevel->flags & LEVELFLAG_FAILED)) sprintf(buffer, gettext("solved")); attron(COLOR_PAIR(colourpair_yellow)); /* Blank previous display only if necessary */ if(utf8strlen(buffer) < length) { for(i = 0; i < length; i ++) mvprintw(display_size_y - 1, i + 2, " "); } length = utf8strlen(buffer); mvprintw(display_size_y - 1, 2, "%s", buffer); attroff(COLOR_PAIR(colourpair_yellow)); move(display_size_y - 1, 0); display_piece(plevel, PIECE_STAR); } int display_focus(struct level* plevel) { int px, py; int ox, oy; #ifdef XOR_COMPATIBILITY int redraw; #endif getmaxyx(stdscr, display_size_y, display_size_x); #ifdef XOR_COMPATIBILITY if(plevel->mode == MODE_XOR && options_xor_display) { if(display_start_x != plevel->view_x[plevel->player] || display_start_y != plevel->view_y[plevel->player]) redraw = 1; else redraw = 0; display_start_x = plevel->view_x[plevel->player]; display_start_y = plevel->view_y[plevel->player]; display_end_x = display_start_x + 8; display_end_y = display_start_y + 8; return redraw; } #endif ox = display_start_x; oy = display_start_y; px = plevel->player_x[plevel->player]; py = plevel->player_y[plevel->player]; if(plevel->size_x < display_size_x) { display_start_x = 0; display_end_x = plevel->size_x; } else { if(px < display_start_x + display_border) display_start_x = px - display_border; if(px >= display_start_x + display_size_x - display_border) display_start_x = px - display_size_x + display_border; if(display_start_x < 0) display_start_x = 0; if(display_start_x + display_size_x > plevel->size_x) display_start_x = plevel->size_x - display_size_x; display_end_x = display_start_x + display_size_x; } if(plevel->size_y < display_size_y - 1) { display_start_y = 0; display_end_y = plevel->size_y; } else { if(py < display_start_y + display_border) display_start_y = py - display_border; if(py >= display_start_y + display_size_y - 1 - display_border) display_start_y = py - display_size_y + 1 + display_border; if(display_start_y < 0) display_start_y = 0; if(display_start_y + display_size_y - 1 > plevel->size_y) display_start_y = plevel->size_y - display_size_y + 1; display_end_y = display_start_y + display_size_y - 1; } if(ox != display_start_x || oy != display_start_y) return 1; else return 0; } void display_level(struct level* plevel) { int x, y; int p; clear(); getmaxyx(stdscr, display_size_y, display_size_x); if(display_start_x < 0) display_start_x = 0; if(display_start_y < 0) display_start_y = 0; if(display_end_x > plevel->size_x) display_end_x = plevel->size_x; if(display_end_y > plevel->size_y) display_end_y = plevel->size_y; display_offset_x = (display_size_x - (display_end_x - display_start_x))/2; display_offset_y = (display_size_y - (display_end_y - display_start_y))/2; for(y = display_start_y; y < display_end_y; y++) { move(y + display_offset_y - display_start_y, display_offset_x); for(x = display_start_x; x < display_end_x; x++) { p = level_piece(plevel, x, y); #ifdef XOR_COMPATIBILITY if(plevel->switched && (p == PIECE_SPACE || p == PIECE_WALL)) p = PIECE_DARKNESS; #endif display_piece(plevel, p); } } } void display_play(struct level* plevel, struct level* plevelreplay) { int key; int quit; struct mover* pmover; int redraw; int x, y; int p; int playermove; int delay; int fast; int pass; int c; short cp; char font_logo_colours[] = "1326454646644"; char buffer[256]; quit = 0; redraw = 1; fast = 0; while(!quit) { redraw += display_focus(plevel); if(redraw) { display_level(plevel); if(plevel->title != NULL) { y = display_size_y - 1; x = (display_size_x - utf8strlen(plevel->title) - (plevel->flags & LEVELFLAG_TESTING ? utf8strlen(gettext("testing: ")) : 0) ) / 2; if(x < 0) x = 0; move(y, x); if(plevel->flags & LEVELFLAG_TESTING) { attron(COLOR_PAIR(colourpair_cyan)); printw(gettext("testing: ")); attroff(COLOR_PAIR(colourpair_cyan)); } if((strncmp(gettext(plevel->title), "chroma", 6) == 0)) { strcpy(buffer, gettext(plevel->title)); for(x = 0; x < strlen(buffer); x ++) { cp = colourpair_white; if(x < strlen(font_logo_colours)) c = font_logo_colours[x] - '0'; else { printw(buffer + x); x = strlen(buffer); break; } switch(c) { case 1: cp = colourpair_red; break; case 2: cp = colourpair_green; break; case 3: cp = colourpair_yellow; break; case 4: cp = colourpair_blue; break; case 5: cp = colourpair_magenta; break; case 6: cp = colourpair_cyan; break; default: cp = colourpair_white; break; } addch((*(buffer + x)) | COLOR_PAIR(cp)); } } else printw("%s", plevel->title); } display_moves(plevel, plevelreplay); display_stars(plevel); refresh(); curs_set(0); redraw = 0; } /* If there are movers, plot and then evolve them */ if(plevel->mover_first != NULL && !(plevel->flags & LEVELFLAG_PAUSED)) { /* Plot movers in two passes - first spaces, then non-spaces. This is counter-intuitive, but makes undoing the player work. */ for(pass = 0; pass < 2; pass ++) { pmover = plevel->mover_first; while(pmover != NULL) { if((pass == 0 && pmover->piece != PIECE_SPACE) || (pass == 1 && pmover->piece == PIECE_SPACE)) { pmover = pmover->next; continue; } x = pmover->x; y = pmover->y;; if(x >= display_start_x && x < display_end_x && y>= display_start_y && y < display_end_y) { move(display_offset_y - display_start_y + y, display_offset_x - display_start_x + x); p = pmover->piece; #ifdef XOR_COMPATIBILITY if(plevel->switched && (p == PIECE_SPACE || p == PIECE_WALL)) p = PIECE_DARKNESS; #endif if(p != PIECE_GONE) display_piece(plevel, p); } pmover = pmover->next; } } /* Debug movers */ if(options_debug & DEBUG_ORDER) { /* Display the movers */ pmover = plevel->mover_first; y = 0; while(pmover != NULL && y < display_size_y - 1) { if(pmover->piece != PIECE_GONE) { move(y ++, 0); display_piece(plevel, pmover->piece); printw(" %2d,%2d ", pmover->x, pmover->y); } pmover = pmover->next; } while(y < display_size_y - 1) mvprintw(y++, 0, " "); /* Display the stack if our game engine uses it */ if(0 #ifdef XOR_COMPATIBILITY || (plevel->mode == MODE_XOR && options_xor_mode) #endif #ifdef ENIGMA_COMPATIBILITY || (plevel->mode == MODE_ENIGMA && options_enigma_mode) #endif ) { pmover = plevel->stack_first; y = 0; while(pmover != NULL && y < display_size_y - 1) { if(pmover->piece != PIECE_GONE) { move(y ++, display_size_x - 8); display_piece(plevel, pmover->piece); printw(" %2d,%2d ", pmover->x, pmover->y); } pmover = pmover->next; } while(y < display_size_y - 1) mvprintw(y++, display_size_x - 8, " "); } } refresh(); /* Evolve movers */ if(!(plevel->flags & LEVELFLAG_UNDO)) { if(level_evolve(plevel)) redraw += display_focus(plevel); level_storemovers(plevel); } else { if(level_undo(plevel)) plevel->flags |= LEVELFLAG_UNDO; else plevel->flags &= ~LEVELFLAG_UNDO; } } /* Determine which delay to use */ delay = options_curses_delay; if(plevelreplay != NULL) { if(plevel->mover_first == NULL && plevelreplay->move_current != NULL) delay = options_curses_replay_delay; if(fast) delay = 0; if(plevelreplay->flags & LEVELFLAG_UNDO) { if(plevel->move_current == NULL && plevel->mover_first == NULL) { if(options_curses_replay_delay != 0) delay = options_curses_replay_delay; else delay = 1; } } else { if(plevel->mover_first == NULL && plevelreplay->move_current == NULL) delay = -1; } } else { if(fast) delay = 0; if(plevel->mover_first == NULL) { delay = -1; fast = 0; } } if(delay > 0) halfdelay(delay); if(delay != 0) { key = getch(); if(key < 0 || key >= KEY_MAX) key = 0; if(key >= 'a' && key <='z') key -= 32; } else key = 0; if(delay > 0) cbreak(); playermove = MOVE_NONE; switch(actions[key]) { case ACTION_REDRAW: redraw = 1; break; case ACTION_HIDE: display_hide(); redraw = 1; break; case ACTION_QUIT: quit = 1; break; case ACTION_FAST: fast = 1 - fast; break; case ACTION_LEFT: if(plevelreplay != NULL) { plevelreplay->flags |= LEVELFLAG_UNDO; plevelreplay->flags &= ~LEVELFLAG_PAUSED; } else playermove = MOVE_LEFT; break; case ACTION_RIGHT: if(plevelreplay != NULL) { plevelreplay->flags &= ~LEVELFLAG_UNDO; plevelreplay->flags &= ~LEVELFLAG_PAUSED; } else playermove = MOVE_RIGHT; break; case ACTION_UP: if(plevelreplay != NULL) plevelreplay->flags |= LEVELFLAG_PAUSED; else playermove = MOVE_UP; break; case ACTION_DOWN: if(plevelreplay != NULL) plevelreplay->flags |= LEVELFLAG_PAUSED; else playermove = MOVE_DOWN; break; case ACTION_PAUSE: if(plevelreplay != NULL) { if(plevelreplay->flags & LEVELFLAG_PAUSED) plevelreplay->flags &= ~LEVELFLAG_PAUSED; else plevelreplay->flags |= LEVELFLAG_PAUSED; } else if(plevel->mover_first != NULL) { if(plevel->flags & LEVELFLAG_PAUSED) plevel->flags &= ~LEVELFLAG_PAUSED; else plevel->flags |= LEVELFLAG_PAUSED; plevel->flags |= LEVELFLAG_MOVES; } break; case ACTION_SWAP: if(plevelreplay == NULL) playermove = MOVE_SWAP; break; case ACTION_UNDO: if(plevelreplay == NULL) { if(plevel->mover_first == NULL && !(plevel->flags & LEVELFLAG_UNDO)) { if(level_undo(plevel)) plevel->flags |= LEVELFLAG_UNDO; else plevel->flags &= ~LEVELFLAG_UNDO; playermove = MOVE_NONE; } } break; case ACTION_REDO: playermove = MOVE_REDO; break; default: break; } /* Are we replaying the level? */ if(plevelreplay != NULL) { /* Is it time for another move? */ if(plevel->mover_first == NULL && !(plevelreplay->flags & LEVELFLAG_PAUSED)) { /* Moving backwards through replay */ if(plevelreplay->flags & LEVELFLAG_UNDO) { if(level_undo(plevel)) { plevel->flags |= LEVELFLAG_UNDO; if(plevelreplay->move_current != NULL) plevelreplay->move_current = plevelreplay->move_current->previous; else plevelreplay->move_current = plevelreplay->move_last; } else plevel->flags &= ~LEVELFLAG_UNDO; } /* Moving forwards through replay */ else { if(plevelreplay->move_current != NULL) { playermove = plevelreplay->move_current->direction; plevelreplay->move_current = plevelreplay->move_current->next; } } } } /* Can't move if we've failed or solved the level */ if(plevel->flags & (LEVELFLAG_FAILED | LEVELFLAG_SOLVED)) playermove = MOVE_NONE; /* If we can move, make the move */ if(playermove != MOVE_NONE && plevel->mover_first == NULL) level_move(plevel, playermove); /* Display things changed by the move */ if(plevel->flags & LEVELFLAG_MOVES) { display_moves(plevel, plevelreplay); plevel->flags ^= LEVELFLAG_MOVES; } if(plevel->flags & LEVELFLAG_STARS) { display_stars(plevel); plevel->flags ^= LEVELFLAG_STARS; } if(plevel->flags & LEVELFLAG_SWITCH) { redraw = 1; plevel->flags ^= LEVELFLAG_SWITCH; } #ifdef XOR_COMPATIBILITY if(plevel->flags & LEVELFLAG_MAP) { /* No sensible way to handle this in curses */ plevel->flags ^= LEVELFLAG_MAP; } #endif if(!(plevel->flags & LEVELFLAG_SOLVED) && plevel->flags & LEVELFLAG_EXIT) { plevel->flags |= LEVELFLAG_SOLVED; display_stars(plevel); } if(!(plevel->flags & LEVELFLAG_FAILED) && plevel->alive[0] == 0 && plevel->alive[1] ==0) { plevel->flags |= LEVELFLAG_FAILED; display_moves(plevel, plevelreplay); } } } void display_edit(struct level* plevel) { int key; int quit; static int editor_piece = PIECE_SPACE; int redraw, moved, pmoved; int i; int player; int piece_count; redraw = 1; moved = 1; pmoved = 1; /* Store player */ player = plevel->player; plevel->player = 2; piece_count = 0; while(editor_piece_maps[plevel->mode][piece_count] != PIECE_GONE) piece_count ++; if(editor_piece > piece_count) editor_piece = 0; quit = 0; while(!quit) { redraw += display_focus(plevel); if(redraw) { redraw = 0; display_level(plevel); for(i = 0; i < piece_count; i ++) { move(display_size_y - 1, 1 + i * 2); display_piece(plevel, editor_piece_maps[plevel->mode][i]); } move(display_size_y - 1, display_size_x - 4); printw("[ ]"); curs_set(1); pmoved = 1; } if(pmoved) { pmoved = 0; move(display_size_y - 1, editor_piece * 2); printw(">"); move(display_size_y - 1, 2 + editor_piece * 2); printw("<"); move(display_size_y - 1, display_size_x - 3); display_piece(plevel, editor_piece_maps[plevel->mode][editor_piece]); moved = 1; } if(moved) { moved = 0; move(display_offset_y - display_start_y + plevel->player_y[2], display_offset_x - display_start_x + plevel->player_x[2]); refresh(); } key = getch(); if(key < 0 || key >= KEY_MAX) key = 0; if(key >= 'a' && key <='z') key -= 32; switch(actions[key]) { case ACTION_REDRAW: redraw = 1; break; case ACTION_HIDE: display_hide(); redraw = 1; break; case ACTION_QUIT: quit = 1; break; case ACTION_LEFT: if(plevel->player_x[2] > 0) { plevel->player_x[2] --; moved = 1; } break; case ACTION_RIGHT: if(plevel->player_x[2] < plevel->size_x - 1) { plevel->player_x[2] ++; moved = 1; } break; case ACTION_UP: if(plevel->player_y[2] > 0) { plevel->player_y[2] --; moved = 1; } break; case ACTION_DOWN: if(plevel->player_y[2] < plevel->size_y -1) { plevel->player_y[2] ++; moved = 1; } break; case ACTION_SWAP: level_setpiece(plevel, plevel->player_x[2], plevel->player_y[2], editor_piece_maps[plevel->mode][editor_piece]); display_piece(plevel, editor_piece_maps[plevel->mode][editor_piece]); moved = 1; break; case ACTION_PIECE_LEFT: move(display_size_y - 1, editor_piece * 2); printw(" "); move(display_size_y - 1, 2 + editor_piece * 2); printw(" "); editor_piece --; if(editor_piece < 0) editor_piece = piece_count - 1; pmoved = 1; break; case ACTION_PIECE_RIGHT: move(display_size_y - 1, editor_piece * 2); printw(" "); move(display_size_y - 1, 2 + editor_piece * 2); printw(" "); editor_piece ++; if(editor_piece >= piece_count) editor_piece = 0; pmoved = 1; break; } } /* Restore real player */ plevel->player = player; } int display_type() { return DISPLAY_CURSES; } void display_options() { struct menu* pmenu; struct menu* pcoloursmenu; struct menuentry* pentrycolours; struct menuentry* pentryspeed; struct menuentry* pentryreplayspeed; char buffer[256]; int ok; int result; pmenu = menu_new(gettext("Display Options")); menuentry_new(pmenu, gettext("Return to previous menu"), 'Q', 0); menuentry_new(pmenu, "", 0, MENU_SPACE); menuentry_new(pmenu, gettext("Save Options"), 'S', 0); menuentry_new(pmenu, "", 0, MENU_SPACE); pentrycolours = menuentry_new(pmenu, gettext("Colour Scheme"), 'C', 0); menuentry_new(pmenu, "", 0, MENU_SPACE); pentryspeed = menuentry_new(pmenu, gettext("Move Speed"), 'M', MENU_SCROLLABLE); pentryreplayspeed = menuentry_new(pmenu, gettext("Replay Speed"), 'R', MENU_SCROLLABLE); menuentry_new(pmenu, "", 0, MENU_SPACE); menuentry_new(pmenu, gettext("Change Keys"), 'K', 0); /* XOR and Enigma options are only visible once an appropriate level has * been seen so as not to confuse those simply playing Chroma levels */ if(0 #ifdef XOR_COMPATIBILITY || options_xor_options #endif #ifdef ENIGMA_COMPATIBILITY || options_enigma_options #endif ) { menuentry_new(pmenu, "", 0, MENU_SPACE); menuentry_new(pmenu, gettext("Other Games Options"), 'X', 0); } if(options_debug & DEBUG_MENU) { menuentry_new(pmenu, "", 0, MENU_SPACE); menuentry_new(pmenu, gettext("Debug Options"), 'D', 0); } ok = 0; while(!ok) { if(pdisplaycolours == NULL) menuentry_extratext(pentrycolours, gettext("** NONE **"), NULL, NULL); else if(pdisplaycolours->title == NULL) menuentry_extratext(pentrycolours, gettext("[untitled colours]"), NULL, NULL); else if(pdisplaycolours->flags & COLOURS_TRANSLATE) menuentry_extratext(pentrycolours, gettext(pdisplaycolours->title), NULL, NULL); else menuentry_extratext(pentrycolours, pdisplaycolours->title, NULL, NULL); switch(options_curses_delay) { case -1: sprintf(buffer, gettext("after a key is pressed")); break; case 0: sprintf(buffer, gettext("instantaneous")); break; default: sprintf(buffer, gettext("%d00 milliseconds"), options_curses_delay); break; } menuentry_extratext(pentryspeed, buffer, NULL, NULL); switch(options_curses_replay_delay) { case -1: sprintf(buffer, gettext("after a key is pressed")); break; case 0: sprintf(buffer, gettext("instantaneous")); break; default: sprintf(buffer, gettext("%d00 milliseconds"), options_curses_replay_delay); break; } menuentry_extratext(pentryreplayspeed, buffer, NULL, NULL); result = menu_process(pmenu); if(result == MENU_QUIT) ok = 1; if(result == MENU_SELECT && pmenu->entry_selected != NULL) { switch(pmenu->entry_selected->key) { case 'Q': ok = 1; break; case 'C': pcoloursmenu = colours_menu(); if(menu_process(pcoloursmenu) == MENU_SELECT) { if(pcoloursmenu->entry_selected != NULL && pcoloursmenu->entry_selected->value != NULL) { strcpy(options_colours, pcoloursmenu->entry_selected->value); colours_init(); display_initcolours(); } } menu_delete(pcoloursmenu); break; case 'S': display_options_save(); ok = 1; break; case 'K': display_keys(); break; case 'X': display_options_othergames(); break; case 'D': display_debug(); break; } } if(result == MENU_SCROLLLEFT && pmenu->entry_selected != NULL) { switch(pmenu->entry_selected->key) { case 'M': options_curses_delay --; if(options_curses_delay < -1) options_curses_delay = 10; #ifdef PDCURSES if(options_curses_delay > 0) options_curses_delay = 0; #endif break; case 'R': options_curses_replay_delay --; if(options_curses_replay_delay < -1) options_curses_replay_delay = 10; #ifdef PDCURSES if(options_curses_replay_delay > 0) options_curses_replay_delay = 0; #endif break; } } if(result == MENU_SCROLLRIGHT && pmenu->entry_selected != NULL) { switch(pmenu->entry_selected->key) { case 'M': options_curses_delay ++; if(options_curses_delay > 10) options_curses_delay = -1; #ifdef PDCURSES if(options_curses_delay > 0) options_curses_delay = 0; #endif break; case 'R': options_curses_replay_delay ++; if(options_curses_replay_delay > 10) options_curses_replay_delay = -1; #ifdef PDCURSES if(options_curses_replay_delay > 0) options_curses_replay_delay = 0; #endif break; } } } menu_delete(pmenu); } int display_keyfixed(int i) { if(i == 0 || i == KEY_RESIZE || i == 27 || i == 'Q' || i == '\n' || i == '\r' || i == KEY_UP || i == KEY_DOWN || i == KEY_LEFT || i == KEY_RIGHT) return 1; return 0; } char *display_keyname(int i) { static char buffer[4]; if(i == '\t') return "TAB"; if(i == '\n') return "ENTER"; if(i == 27) return "ESCAPE"; if(i == 32) return "SPACE"; if(i == KEY_DC) return "DELETE"; if(i == KEY_IC) return "INSERT"; if(keyname(i) == NULL) return "UNKNOWN"; if(strcmp(keyname(i), "NO KEY NAME") == 0) { if(i >= 0 && i < 32) { sprintf(buffer, "^%c", i + '@'); return buffer; } if(i > 32 && i < 127) { sprintf(buffer, "%c", i); return buffer; } return "UNKNOWN"; } if(strncmp(keyname(i), "KEY_", 4) == 0) return (char *)(keyname(i) + 4); return (char *)keyname(i); } void display_addkeytomenu(struct menu* pmenu, int action, char *text) { struct menuentry *pentry; char buffer[256]; int i; sprintf(buffer, "%d", action); pentry = menuentry_newwithvalue(pmenu, text, 0, MENU_DOUBLE, buffer); strcpy(buffer, ""); for(i = 0; i < KEY_MAX; i ++) { if(actions[i] == action && i != KEY_RESIZE) { if(strlen(buffer) != 0) strcat(buffer,", "); strcat(buffer, "["); strcat(buffer, display_keyname(i)); strcat(buffer, "]"); } } if(strcmp(buffer, "") == 0) strcpy(buffer, "(none)"); menuentry_extratext(pentry, NULL, NULL, buffer); } void display_keys() { struct menu *pmenu; struct menu *psubmenu; struct menuentry *pentry; int action; int result; int redraw; int ok; int subok; char buffer[256]; int i; int key; ok = 0; while(!ok) { pmenu = menu_new(gettext("Keys")); menuentry_new(pmenu, gettext("Quit and return to previous menu"), 'Q', 0); menuentry_new(pmenu, "", 0, MENU_SPACE); display_addkeytomenu(pmenu, ACTION_LEFT, gettext(action_name[ACTION_LEFT])); display_addkeytomenu(pmenu, ACTION_RIGHT, gettext(action_name[ACTION_RIGHT])); display_addkeytomenu(pmenu, ACTION_UP, gettext(action_name[ACTION_UP])); display_addkeytomenu(pmenu, ACTION_DOWN, gettext(action_name[ACTION_DOWN])); display_addkeytomenu(pmenu, ACTION_SWAP, gettext(action_name[ACTION_SWAP])); display_addkeytomenu(pmenu, ACTION_UNDO, gettext(action_name[ACTION_UNDO])); display_addkeytomenu(pmenu, ACTION_REDO, gettext(action_name[ACTION_REDO])); display_addkeytomenu(pmenu, ACTION_FAST, gettext(action_name[ACTION_FAST])); display_addkeytomenu(pmenu, ACTION_PAUSE, gettext(action_name[ACTION_PAUSE])); display_addkeytomenu(pmenu, ACTION_QUIT, gettext(action_name[ACTION_QUIT])); display_addkeytomenu(pmenu, ACTION_REDRAW, gettext(action_name[ACTION_REDRAW])); display_addkeytomenu(pmenu, ACTION_HIDE, gettext(action_name[ACTION_HIDE])); display_addkeytomenu(pmenu, ACTION_PIECE_LEFT, gettext(action_name[ACTION_PIECE_LEFT])); display_addkeytomenu(pmenu, ACTION_PIECE_RIGHT, gettext(action_name[ACTION_PIECE_RIGHT])); menu_assignletters(pmenu); result = menu_process(pmenu); if(result == MENU_QUIT) ok = 1; if(result == MENU_SELECT) { if(pmenu->entry_selected->key == 'Q') ok = 1; else if(pmenu->entry_selected->value != NULL) { redraw = MENUREDRAW_ALL; subok = 0; while(!subok) { action = atoi(pmenu->entry_selected->value); sprintf(buffer, gettext("Set keys for '%s'"), gettext(action_name[action])); psubmenu = menu_new(buffer); menuentry_new(psubmenu, gettext("Quit and return to previous menu"), 'Q', 0); menuentry_new(psubmenu, "", 0, MENU_SPACE); for(i = 0; i < KEY_MAX; i ++) { if(actions[i] == action && i != KEY_RESIZE) { sprintf(buffer, "[%s]", display_keyname(i)); pentry = menuentry_new(psubmenu, buffer, 0, MENU_GREY); if(display_keyfixed(i)) menuentry_extratext(pentry, gettext("(fixed)"), NULL, NULL); } } menuentry_new(psubmenu, "", 0, MENU_SPACE); menuentry_new(psubmenu, gettext("Press a key to add or remove it from this list."), 0, MENU_NOTE | MENU_CENTRE); menu_display(psubmenu, redraw); redraw = MENUREDRAW_ENTRIES; menu_delete(psubmenu); key = getch(); if(key == KEY_RESIZE) { getmaxyx(stdscr, display_size_y, display_size_x); redraw = MENUREDRAW_ALL; } if(key >= 'a' && key <='z') key -=32; if(key == 27 || key == 'q' || key == 'Q' || key== '\n') subok = 1; else if(!display_keyfixed(key)) { if(actions[key] == action) actions[key] = ACTION_NONE; else actions[key] = action; } } } } menu_delete(pmenu); } } void display_debug() { struct menu* pmenu; struct menuentry* pentrymovers; struct menuentry* pentryhidden; int ok; int result; pmenu = menu_new(gettext("Debug Options")); menuentry_new(pmenu, gettext("Return to previous menu"), 'Q', 0); menuentry_new(pmenu, "", 0, MENU_SPACE); pentrymovers = menuentry_new(pmenu, gettext("Display order of movers"), 'O', MENU_SCROLLABLE); pentryhidden = menuentry_new(pmenu, gettext("Show hidden items"), 'H', MENU_SCROLLABLE); ok = 0; while(!ok) { menuentry_extratext(pentrymovers, options_debug & DEBUG_ORDER ? gettext("yes") : gettext("no"), NULL, NULL); menuentry_extratext(pentryhidden, options_debug & DEBUG_HIDDEN ? gettext("yes") : gettext("no"), NULL, NULL); result = menu_process(pmenu); if(result == MENU_QUIT) ok = 1; if((result == MENU_SELECT || result == MENU_SCROLLLEFT || result == MENU_SCROLLRIGHT) && pmenu->entry_selected != NULL) { switch(pmenu->entry_selected->key) { case 'Q': ok = 1; break; case 'O': options_debug ^= DEBUG_ORDER; break; case 'H': options_debug ^= DEBUG_HIDDEN; break; } pmenu->redraw = MENUREDRAW_CHANGED; pmenu->entry_selected->redraw = 1; } } menu_delete(pmenu); } void display_options_othergames() { struct menu* pmenu; #ifdef XOR_COMPATIBILITY struct menuentry* pentryxormode; struct menuentry* pentryxordisplay; #endif #ifdef ENIGMA_COMPATIBILITY struct menuentry* pentryenigmamode; #endif int ok; int result; pmenu = menu_new(gettext("Other Games Options")); menuentry_new(pmenu, gettext("Return to previous menu"), 'Q', 0); menuentry_new(pmenu, "", 0, MENU_SPACE); #ifdef XOR_COMPATIBILITY pentryxormode = menuentry_new(pmenu, gettext("XOR Engine"), 'X', options_xor_options ? 0 : MENU_INVISIBLE | MENU_GREY); pentryxordisplay = menuentry_new(pmenu, gettext("XOR Display"), 'D', options_xor_options ? 0 : MENU_INVISIBLE | MENU_GREY); if(options_xor_options) menuentry_new(pmenu, "", 0, MENU_SPACE); #endif #ifdef ENIGMA_COMPATIBILITY pentryenigmamode = menuentry_new(pmenu, gettext("Enigma Engine"), 'E', options_enigma_options ? 0 : MENU_INVISIBLE | MENU_GREY); #endif ok = 0; while(!ok) { #ifdef XOR_COMPATIBILITY menuentry_extratext(pentryxormode, options_xor_mode ? gettext("exact") : gettext("approximate"), NULL, NULL); menuentry_extratext(pentryxordisplay, options_xor_display ? gettext("partial") : gettext("full"), NULL, NULL); #endif #ifdef ENIGMA_COMPATIBILITY menuentry_extratext(pentryenigmamode, options_enigma_mode ? gettext("exact") : gettext("approximate"), NULL, NULL); #endif result = menu_process(pmenu); if(result == MENU_QUIT) ok = 1; if(result == MENU_SELECT && pmenu->entry_selected != NULL) { switch(pmenu->entry_selected->key) { case 'Q': ok = 1; break; #ifdef XOR_COMPATIBILITY case 'X': options_xor_mode = 1 - options_xor_mode; break; case 'D': options_xor_display = 1 - options_xor_display; break; #endif #ifdef ENIGMA_COMPATIBILITY case 'E': options_enigma_mode = 1 - options_enigma_mode; break; #endif } pmenu->redraw = MENUREDRAW_CHANGED; pmenu->entry_selected->redraw = 1; } } menu_delete(pmenu); } void display_options_save() { FILE *file; char filename[FILENAME_MAX]; int i; getfilename("curses.chroma", filename, 1, 0); file = fopen(filename, "w"); if(file == NULL) { warning("Unable to save options"); return; } fprintf(file, "\n" "\n" "\n"); fprintf(file, " \n", options_colours); if(options_curses_delay == -1) fprintf(file, " \n"); else fprintf(file, " \n", options_curses_delay * 100); if(options_curses_replay_delay == -1) fprintf(file, " \n"); else fprintf(file, " \n", options_curses_replay_delay * 100); #ifdef XOR_COMPATIBILITY if(options_xor_options) fprintf(file, " \n", options_xor_mode ? "exact" : "approximate", options_xor_display ? "partial" : "full"); #endif #ifdef ENIGMA_COMPATIBILITY if(options_enigma_options) fprintf(file, " \n", options_enigma_mode ? "exact" : "approximate"); #endif fprintf(file, " \n"); fprintf(file, " \n"); fprintf(file, " \n"); for(i = 0; i < KEY_MAX; i ++) { if(actions[i] != ACTION_NONE && i != KEY_RESIZE) fprintf(file, " \n", display_keyname(i), action_shortname[actions[i]]); } fprintf(file, " \n"); fprintf(file, "\n"); fclose(file); } void display_options_load() { struct parser* pparser; char filename[FILENAME_MAX]; int state; int i; int key, action; /* Sensible defaults */ #ifdef PDCURSES /* halfdelay() is broken in PDCurses */ options_curses_delay = 0; options_curses_replay_delay = 0; #else options_curses_delay = 1; options_curses_replay_delay = 1; #endif #ifdef XOR_COMPATIBILITY options_xor_options = 0; options_xor_mode = 1; options_xor_display = 0; #endif #ifdef ENIGMA_COMPATIBILITY options_enigma_options = 0; options_enigma_mode = 1; #endif options_debug = 0; getfilename("colours", filename, 0, 1); sprintf(options_colours, "%s/%s", filename, COLOURS_DEFAULT); getfilename("curses.chroma", filename, 0, 0); for(i = 0; i < KEY_MAX; i ++) { actions[i] = ACTION_NONE; } /* Fixed keys */ actions[KEY_RESIZE] = ACTION_REDRAW; actions[KEY_UP] = ACTION_UP; actions[KEY_DOWN] = ACTION_DOWN; actions[KEY_LEFT] = ACTION_LEFT; actions[KEY_RIGHT] = ACTION_RIGHT; actions['\r'] = ACTION_SWAP; actions['\n'] = ACTION_SWAP; actions['Q'] = ACTION_QUIT; actions[27] = ACTION_QUIT; /* Sensible default keys */ if(!isfile(filename)) { actions[12] = ACTION_REDRAW; actions[' '] = ACTION_SWAP; actions['F'] = ACTION_FAST; actions[KEY_BACKSPACE] = ACTION_UNDO; actions[KEY_DC] = ACTION_UNDO; actions['U'] = ACTION_UNDO; actions[KEY_IC] = ACTION_REDO; actions['Y'] = ACTION_REDO; actions['Z'] = ACTION_PIECE_LEFT; actions['X'] = ACTION_PIECE_RIGHT; actions[KEY_PPAGE] = ACTION_PIECE_LEFT;; actions[KEY_NPAGE] = ACTION_PIECE_RIGHT; actions['P'] = ACTION_PAUSE; return; } /* Parse XML file */ /* */ pparser = parser_new(filename); enum { OPTIONSPARSER_END, /* End of file */ OPTIONSPARSER_OUTSIDE, /* Outside of */ OPTIONSPARSER_CHROMA, /* Inside */ OPTIONSPARSER_KEYS /* Inside */ }; state = OPTIONSPARSER_OUTSIDE; key = 0; action = 0; while(state != OPTIONSPARSER_END) { switch(parser_parse(pparser)) { case PARSER_END: state = OPTIONSPARSER_END; break; case PARSER_ELEMENT_START: switch(state) { case OPTIONSPARSER_CHROMA: if(parser_match(pparser, 0, "keys")) state = OPTIONSPARSER_KEYS; break; case OPTIONSPARSER_KEYS: if(parser_match(pparser, 0, "key")) { key = 0; action = ACTION_NONE; } break; default: break; } break; case PARSER_ELEMENT_END: switch(state) { case OPTIONSPARSER_KEYS: if(parser_match(pparser, 0, "keys")) { state = OPTIONSPARSER_CHROMA; } if(parser_match(pparser, 0, "key")) { if(key != 0 && !display_keyfixed(key)) actions[key] = action; } break; default: break; } break; case PARSER_CONTENT: break; case PARSER_ATTRIBUTE: switch(state) { case OPTIONSPARSER_OUTSIDE: if(parser_match(pparser, 2, "chroma") && parser_match(pparser, 1, "type")) { if(parser_match(pparser, 0, "options")) state = OPTIONSPARSER_CHROMA; } break; case OPTIONSPARSER_CHROMA: if(parser_match(pparser, 2, "colour") && parser_match(pparser, 1, "scheme")) { strncpy(options_colours, parser_text(pparser, 0), FILENAME_MAX); } if(parser_match(pparser, 2, "move") && parser_match(pparser, 1, "speed")) { if(parser_match(pparser, 0, "key")) options_curses_delay = -1; else options_curses_delay = atoi(parser_text(pparser, 0)) / 100; #ifdef PDCURSES if(options_curses_delay > 0) options_curses_delay = 0; #endif } if(parser_match(pparser, 2, "replay") && parser_match(pparser, 1, "speed")) { if(parser_match(pparser, 0, "key")) options_curses_replay_delay = -1; else options_curses_replay_delay = atoi(parser_text(pparser, 0)) / 100; #ifdef PDCURSES if(options_curses_replay_delay > 0) options_curses_replay_delay = 0; #endif } #ifdef XOR_COMPATIBILITY if(parser_match(pparser, 2, "xor") && parser_match(pparser, 1, "mode")) { options_xor_options = 1; if(parser_match(pparser, 0, "approximate")) options_xor_mode = 0; if(parser_match(pparser, 0, "exact")) options_xor_mode = 1; } if(parser_match(pparser, 2, "xor") && parser_match(pparser, 1, "display")) { options_xor_options = 1; if(parser_match(pparser, 0, "full")) options_xor_display = 0; if(parser_match(pparser, 0, "partial")) options_xor_display = 1; } #endif #ifdef ENIGMA_COMPATIBILITY if(parser_match(pparser, 2, "enigma") && parser_match(pparser, 1, "mode")) { options_enigma_options = 1; if(parser_match(pparser, 0, "approximate")) options_enigma_mode = 0; if(parser_match(pparser, 0, "exact")) options_enigma_mode = 1; } #endif if(parser_match(pparser, 2, "debug") && parser_match(pparser, 1, "menu")) { if(parser_match(pparser, 0, "yes")) options_debug |= DEBUG_MENU; } if(parser_match(pparser, 2, "debug") && parser_match(pparser, 1, "order")) { if(parser_match(pparser, 0, "yes")) options_debug |= DEBUG_ORDER; } if(parser_match(pparser, 2, "debug") && parser_match(pparser, 1, "hidden")) { if(parser_match(pparser, 0, "yes")) options_debug |= DEBUG_HIDDEN; } break; case OPTIONSPARSER_KEYS: if(parser_match(pparser, 2, "key") && parser_match(pparser, 1, "name")) { for(i = 0; i < KEY_MAX; i ++) { if(parser_match(pparser, 0, display_keyname(i))) { key = i; i = KEY_MAX; } } } if(parser_match(pparser, 2, "key") && parser_match(pparser, 1, "action")) { for(i = ACTION_KEY_MIN; i < ACTION_KEY_MAX; i ++) { if(parser_match(pparser, 0, action_shortname[i])) { action = i; i = ACTION_KEY_MAX; } } } break; } break; case PARSER_ERROR: state = OPTIONSPARSER_END; break; } } parser_delete(pparser); }