chiark / gitweb /
New upstream version 1.18
[chroma-debian.git] / cursesdisplay.c
diff --git a/cursesdisplay.c b/cursesdisplay.c
new file mode 100644 (file)
index 0000000..1c694ad
--- /dev/null
@@ -0,0 +1,1872 @@
+/*  
+    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 <stdlib.h>
+#include <string.h>
+#include <libintl.h>
+#include <locale.h>
+
+#ifdef CHROMA_CURSES_HEADER
+#include CHROMA_CURSES_HEADER
+#else
+#ifdef __WIN32__
+#include <curses.h>
+#else
+#include <ncurses.h>
+#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, "<!-- Chroma curses options \n"
+                  "     This file is automatically generated. -->\n"
+                  "\n"
+                  "<chroma type=\"options\">\n");
+
+    fprintf(file, "    <colour scheme=\"%s\" />\n", options_colours);
+
+    if(options_curses_delay == -1)
+        fprintf(file, "    <move speed=\"key\" />\n");
+    else
+        fprintf(file, "    <move speed=\"%d\" />\n", options_curses_delay * 100);
+
+    if(options_curses_replay_delay == -1)
+        fprintf(file, "    <replay speed=\"key\" />\n");
+    else
+        fprintf(file, "    <replay speed=\"%d\" />\n", options_curses_replay_delay * 100);
+
+#ifdef XOR_COMPATIBILITY
+    if(options_xor_options)
+        fprintf(file, "    <xor mode=\"%s\" display=\"%s\" />\n", options_xor_mode ? "exact" : "approximate", options_xor_display ? "partial" : "full");
+#endif
+#ifdef ENIGMA_COMPATIBILITY
+    if(options_enigma_options)
+        fprintf(file, "    <enigma mode=\"%s\" />\n", options_enigma_mode ? "exact" : "approximate");
+#endif
+
+    fprintf(file, "    <!-- Set <debug menu=\"yes\" /> to change debug options within Chroma -->\n");
+    fprintf(file, "    <debug ");
+    fprintf(file, "menu=\"%s\" ", options_debug & DEBUG_MENU ? "yes" : "no");
+    fprintf(file, "order=\"%s\" ", options_debug & DEBUG_ORDER ? "yes" : "no");
+    fprintf(file, "hidden=\"%s\" ", options_debug & DEBUG_HIDDEN ? "yes" : "no");
+    fprintf(file,"/>\n");
+
+    fprintf(file, "    <keys>\n");
+
+    for(i = 0; i < KEY_MAX; i ++)
+    {
+        if(actions[i] != ACTION_NONE && i != KEY_RESIZE)
+            fprintf(file, "        <key name=\"%s\" action=\"%s\" />\n", display_keyname(i), action_shortname[actions[i]]);
+    }
+
+    fprintf(file, "    </keys>\n");
+
+    fprintf(file, "</chroma>\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 */
+    /*
+       <chroma type="options">
+           <colour scheme="filename" />
+           <move speed="speed" />
+           <replay speed="speed" />
+           <xor mode="mode" />
+           <debug menu="yes/no" movers="yes/no" />
+           <keys>
+               <key name="name" action="action" />
+           </keys>
+       </chroma>
+    */
+
+    pparser = parser_new(filename);
+
+    enum {
+        OPTIONSPARSER_END,       /* End of file */
+        OPTIONSPARSER_OUTSIDE,   /* Outside of <chroma> */
+        OPTIONSPARSER_CHROMA,    /* Inside <chroma> */
+        OPTIONSPARSER_KEYS       /* Inside <keys> */
+    };
+
+    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);
+}