chiark / gitweb /
New upstream version 1.18
[chroma-debian.git] / level.c
diff --git a/level.c b/level.c
new file mode 100644 (file)
index 0000000..d76afd0
--- /dev/null
+++ b/level.c
@@ -0,0 +1,1515 @@
+/*  
+    level.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 <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "chroma.h"
+#include "level.h"
+#include "util.h"
+
+extern char *piece_name[];
+
+char piecetochar(int piece)
+{
+    switch(piece)
+    {
+        case PIECE_SPACE:
+            return ' ';
+        case PIECE_WALL:
+            return '%';
+        case PIECE_PLAYER_ONE:
+            return '1';
+        case PIECE_PLAYER_TWO:
+            return '2';
+        case PIECE_DOTS:
+            return '.';
+        case PIECE_ARROW_RED_LEFT:
+            return 'a'; 
+        case PIECE_ARROW_RED_UP:
+            return 'b'; 
+        case PIECE_ARROW_RED_RIGHT:
+            return 'c'; 
+        case PIECE_ARROW_RED_DOWN:
+            return 'd'; 
+        case PIECE_BOMB_RED_LEFT:
+            return 'A'; 
+        case PIECE_BOMB_RED_UP:
+            return 'B'; 
+        case PIECE_BOMB_RED_RIGHT:
+            return 'C'; 
+        case PIECE_BOMB_RED_DOWN:
+            return 'D'; 
+        case PIECE_ARROW_GREEN_LEFT:
+            return 'e'; 
+        case PIECE_ARROW_GREEN_UP:
+            return 'f'; 
+        case PIECE_ARROW_GREEN_RIGHT:
+            return 'g'; 
+        case PIECE_ARROW_GREEN_DOWN:
+            return 'h'; 
+        case PIECE_BOMB_GREEN_LEFT:
+            return 'E'; 
+        case PIECE_BOMB_GREEN_UP:
+            return 'F'; 
+        case PIECE_BOMB_GREEN_RIGHT:
+            return 'G'; 
+        case PIECE_BOMB_GREEN_DOWN:
+            return 'H'; 
+        case PIECE_ARROW_BLUE_LEFT:
+            return 'i'; 
+        case PIECE_ARROW_BLUE_UP:
+            return 'j'; 
+        case PIECE_ARROW_BLUE_RIGHT:
+            return 'k'; 
+        case PIECE_ARROW_BLUE_DOWN:
+            return 'l'; 
+        case PIECE_BOMB_BLUE_LEFT:
+            return 'I'; 
+        case PIECE_BOMB_BLUE_UP:
+            return 'J'; 
+        case PIECE_BOMB_BLUE_RIGHT:
+            return 'K'; 
+        case PIECE_BOMB_BLUE_DOWN:
+            return 'L'; 
+        case PIECE_CIRCLE:
+            return 'o'; 
+        case PIECE_STAR:
+            return '*'; 
+        case PIECE_DOOR:
+            return '/'; 
+#ifdef ENIGMA_COMPATIBILITY
+        case PIECE_DOTS_DOUBLE:
+            return ':'; 
+        case PIECE_CIRCLE_DOUBLE:
+            return '8'; 
+#endif
+#ifdef XOR_COMPATIBILITY
+        case PIECE_DOTS_X:
+            return '-'; 
+        case PIECE_DOTS_Y:
+            return '|'; 
+        case PIECE_SWITCH:
+            return 'S'; 
+        case PIECE_TELEPORT:
+            return 'T'; 
+        case PIECE_MAP_TOP_LEFT:
+            return 'M'; 
+        case PIECE_MAP_TOP_RIGHT:
+            return 'm'; 
+        case PIECE_MAP_BOTTOM_LEFT:
+            return 'N'; 
+        case PIECE_MAP_BOTTOM_RIGHT:
+            return 'n'; 
+#endif
+        case PIECE_GONE:
+            return '!';
+        case PIECE_EXPLOSION_RED_LEFT:
+            return 'p';
+        case PIECE_EXPLOSION_RED_HORIZONTAL:
+            return 'q';
+        case PIECE_EXPLOSION_RED_RIGHT:
+            return 'r';
+        case PIECE_EXPLOSION_RED_TOP:
+            return 'P';
+        case PIECE_EXPLOSION_RED_VERTICAL:
+            return 'Q';
+        case PIECE_EXPLOSION_RED_BOTTOM:
+            return 'R';
+        case PIECE_EXPLOSION_GREEN_LEFT:
+            return 'u';
+        case PIECE_EXPLOSION_GREEN_HORIZONTAL:
+            return 'v';
+        case PIECE_EXPLOSION_GREEN_RIGHT:
+            return 'w';
+        case PIECE_EXPLOSION_GREEN_TOP:
+            return 'U';
+        case PIECE_EXPLOSION_GREEN_VERTICAL:
+            return 'V';
+        case PIECE_EXPLOSION_GREEN_BOTTOM:
+            return 'W';
+        case PIECE_EXPLOSION_BLUE_LEFT:
+            return 'x';
+        case PIECE_EXPLOSION_BLUE_HORIZONTAL:
+            return 'y';
+        case PIECE_EXPLOSION_BLUE_RIGHT:
+            return 'z';
+        case PIECE_EXPLOSION_BLUE_TOP:
+            return 'X';
+        case PIECE_EXPLOSION_BLUE_VERTICAL:
+            return 'Y';
+        case PIECE_EXPLOSION_BLUE_BOTTOM:
+            return 'Z';
+
+        default:
+            return '?';
+    }
+}
+
+int chartopiece(char c)
+{
+    switch(c)
+    {
+        case ' ':
+            return PIECE_SPACE; 
+        case '%':
+            return PIECE_WALL; 
+        case '1':
+            return PIECE_PLAYER_ONE; 
+        case '2':
+            return PIECE_PLAYER_TWO; 
+        case '.':
+            return PIECE_DOTS; 
+        case 'a':
+            return PIECE_ARROW_RED_LEFT; 
+        case 'b':
+            return PIECE_ARROW_RED_UP; 
+        case 'c':
+            return PIECE_ARROW_RED_RIGHT; 
+        case 'd':
+            return PIECE_ARROW_RED_DOWN; 
+        case 'A':
+            return PIECE_BOMB_RED_LEFT; 
+        case 'B':
+            return PIECE_BOMB_RED_UP; 
+        case 'C':
+            return PIECE_BOMB_RED_RIGHT; 
+        case 'D':
+            return PIECE_BOMB_RED_DOWN; 
+        case 'e':
+            return PIECE_ARROW_GREEN_LEFT; 
+        case 'f':
+            return PIECE_ARROW_GREEN_UP; 
+        case 'g':
+            return PIECE_ARROW_GREEN_RIGHT; 
+        case 'h':
+            return PIECE_ARROW_GREEN_DOWN; 
+        case 'E':
+            return PIECE_BOMB_GREEN_LEFT; 
+        case 'F':
+            return PIECE_BOMB_GREEN_UP; 
+        case 'G':
+            return PIECE_BOMB_GREEN_RIGHT; 
+        case 'H':
+            return PIECE_BOMB_GREEN_DOWN; 
+        case 'i':
+            return PIECE_ARROW_BLUE_LEFT; 
+        case 'j':
+            return PIECE_ARROW_BLUE_UP; 
+        case 'k':
+            return PIECE_ARROW_BLUE_RIGHT; 
+        case 'l':
+            return PIECE_ARROW_BLUE_DOWN; 
+        case 'I':
+            return PIECE_BOMB_BLUE_LEFT; 
+        case 'J':
+            return PIECE_BOMB_BLUE_UP; 
+        case 'K':
+            return PIECE_BOMB_BLUE_RIGHT; 
+        case 'L':
+            return PIECE_BOMB_BLUE_DOWN; 
+        case 'o':
+            return PIECE_CIRCLE; 
+        case '*':
+            return PIECE_STAR; 
+        case '/':
+            return PIECE_DOOR; 
+#ifdef ENIGMA_COMPATIBILITY
+        case '8':
+            return PIECE_CIRCLE_DOUBLE; 
+        case ':':
+            return PIECE_DOTS_DOUBLE; 
+#endif
+#ifdef XOR_COMPATIBILITY
+        case '-':
+            return PIECE_DOTS_X; 
+        case '|':
+            return PIECE_DOTS_Y; 
+        case 'S':
+            return PIECE_SWITCH; 
+        case 'T':
+            return PIECE_TELEPORT; 
+        case 'M':
+            return PIECE_MAP_TOP_LEFT; 
+        case 'm':
+            return PIECE_MAP_TOP_RIGHT; 
+        case 'N':
+            return PIECE_MAP_BOTTOM_LEFT; 
+        case 'n':
+            return PIECE_MAP_BOTTOM_RIGHT; 
+#endif
+        case '!':
+            return PIECE_GONE;
+        case 'p':
+            return PIECE_EXPLOSION_RED_LEFT;
+        case 'q':
+            return PIECE_EXPLOSION_RED_HORIZONTAL;
+        case 'r':
+            return PIECE_EXPLOSION_RED_RIGHT;
+        case 'P':
+            return PIECE_EXPLOSION_RED_TOP;
+        case 'Q':
+            return PIECE_EXPLOSION_RED_VERTICAL;
+        case 'R':
+            return PIECE_EXPLOSION_RED_BOTTOM;
+        case 'u':
+            return PIECE_EXPLOSION_GREEN_LEFT;
+        case 'v':
+            return PIECE_EXPLOSION_GREEN_HORIZONTAL;
+        case 'w':
+            return PIECE_EXPLOSION_GREEN_RIGHT;
+        case 'U':
+            return PIECE_EXPLOSION_GREEN_TOP;
+        case 'V':
+            return PIECE_EXPLOSION_GREEN_VERTICAL;
+        case 'W':
+            return PIECE_EXPLOSION_GREEN_BOTTOM;
+        case 'x':
+            return PIECE_EXPLOSION_BLUE_LEFT;
+        case 'y':
+            return PIECE_EXPLOSION_BLUE_HORIZONTAL;
+        case 'z':
+            return PIECE_EXPLOSION_BLUE_RIGHT;
+        case 'X':
+            return PIECE_EXPLOSION_BLUE_TOP;
+        case 'Y':
+            return PIECE_EXPLOSION_BLUE_VERTICAL;
+        case 'Z':
+            return PIECE_EXPLOSION_BLUE_BOTTOM;
+        default:
+            return PIECE_UNKNOWN;
+    }
+}
+
+char directiontochar(int direction)
+{
+    switch(direction)
+    {
+        case MOVE_LEFT:
+            return 'l';
+        case MOVE_UP:
+            return 'u';
+        case MOVE_RIGHT:
+            return 'r';
+        case MOVE_DOWN:
+            return 'd';
+        case MOVE_SWAP:
+            return 's';
+        case MOVE_SWAPPED:
+            return 'w';
+        case MOVE_NONE:
+            return 'n';
+        default:
+            return '?';
+    }
+}
+
+int chartodirection(char c)
+{
+    switch(tolower(c))
+    {
+        case 'l':
+            return MOVE_LEFT;
+        case 'u':
+            return MOVE_UP;
+        case 'r':
+            return MOVE_RIGHT;
+        case 'd':
+            return MOVE_DOWN;
+        case 's':
+            return MOVE_SWAP;
+        case 'w':
+            return MOVE_SWAPPED;
+        case 'n':
+            return MOVE_NONE;
+        default:
+            return MOVE_UNKNOWN;
+    }
+}
+
+struct level* level_new()
+{
+    struct level* plevel;
+    int i;
+
+    plevel = (struct level*)malloc(sizeof(struct level));
+
+    if(plevel == NULL)
+        fatal("Out of memory in level_new()");
+
+    plevel->size_x = 0;
+    plevel->size_y = 0;
+
+    plevel->player = 0;
+
+    plevel->data_pieces = NULL;
+    plevel->data_moving = NULL;
+    plevel->data_previous = NULL;
+    plevel->data_previousmoving = NULL;
+    plevel->data_detonator = NULL;
+    plevel->data_detonatormoving = NULL;
+    plevel->data_data = NULL;
+
+    plevel->move_first = NULL;
+    plevel->move_last = NULL;
+    plevel->move_current = NULL;
+
+    plevel->mover_first = NULL;
+    plevel->mover_last = NULL;
+
+    plevel->stack_first = NULL;
+    plevel->stack_last = NULL;
+
+    plevel->stars_caught = 0;
+    plevel->stars_exploded = 0;
+    plevel->stars_total = 0;
+
+    plevel->moves = 0;
+
+    plevel->flags = 0;
+
+#ifdef XOR_COMPATIBILITY
+    plevel->switched = 0;
+    plevel->mapped = 0;
+#endif
+
+    plevel->mode = MODE_CHROMA;
+
+    plevel->level = 0;
+
+    plevel->title = NULL;
+
+    for(i = 0; i < 2; i++)
+    {
+        plevel->alive[i] = 0;
+        plevel->teleport_x[0] = -1;
+        plevel->teleport_y[0] = -1;
+        plevel->view_teleport_x[0] = 0;
+        plevel->view_teleport_y[0] = 0;
+    }
+
+    for(i = 0; i < 3; i++)
+    {
+        plevel->player_x[i] = 0;
+        plevel->player_y[i] = 0;
+        plevel->view_x[i] = 0;
+        plevel->view_y[i] = 0;
+    }
+
+    return plevel;
+}
+
+struct level* level_create(int size_x, int size_y)
+{
+    struct level *pnew;
+    int x, y;
+
+    pnew = level_new();
+
+    pnew->size_x = size_x;
+    pnew->size_y = size_y;
+    
+    pnew->data_pieces = malloc(sizeof(char) * pnew->size_x * pnew->size_y);
+    pnew->data_moving = malloc(sizeof(char) * pnew->size_x * pnew->size_y);
+    pnew->data_previous = malloc(sizeof(char) * pnew->size_x * pnew->size_y);
+    pnew->data_previousmoving = malloc(sizeof(char) * pnew->size_x * pnew->size_y);  
+    pnew->data_detonator = malloc(sizeof(char) * pnew->size_x * pnew->size_y);
+    pnew->data_detonatormoving = malloc(sizeof(char) * pnew->size_x * pnew->size_y);
+    pnew->data_data = (unsigned int *)malloc(sizeof(unsigned int) * pnew->size_x * pnew->size_y);
+
+    if(pnew->data_pieces == NULL || pnew->data_moving == NULL ||
+        pnew->data_previous == NULL || pnew->data_previousmoving == NULL ||
+        pnew->data_detonator == NULL || pnew->data_detonatormoving == NULL ||
+        pnew->data_data == NULL )
+        fatal("Out of memory in level_create()");
+
+    for(y = 0; y < pnew->size_y; y++)
+    {
+        for(x = 0; x < pnew->size_x; x++)
+        { 
+            level_setpiece(pnew, x, y, PIECE_WALL);
+            level_setmoving(pnew, x, y, MOVE_NONE);
+            level_setprevious(pnew, x, y, PIECE_SPACE);
+            level_setpreviousmoving(pnew, x, y, MOVE_NONE);
+            level_setdetonator(pnew, x, y, PIECE_SPACE);
+            level_setdetonatormoving(pnew, x, y, MOVE_NONE);
+            level_setdata(pnew, x, y, 0);
+        }
+    }
+
+    for(y = 1; y < pnew->size_y - 1; y++)
+    {
+        for(x = 1; x < pnew->size_x - 1; x++)
+        { 
+            level_setpiece(pnew, x, y, PIECE_SPACE);
+        }
+    }
+
+    return pnew;
+}
+
+void level_delete(struct level* plevel)
+{
+    struct mover *pmover;
+    struct mover *ptmp;
+    struct move *pmove;
+    struct move *pmovetmp;
+
+    if(plevel == NULL)
+    return;
+    
+    if(plevel->title != NULL)
+        free(plevel->title);
+
+    if(plevel->data_pieces != NULL)
+        free(plevel->data_pieces);
+    if(plevel->data_moving != NULL)
+        free(plevel->data_moving);
+    if(plevel->data_previous != NULL)
+        free(plevel->data_previous);
+    if(plevel->data_previousmoving != NULL)
+        free(plevel->data_previousmoving);
+    if(plevel->data_detonator != NULL)
+        free(plevel->data_detonator);
+    if(plevel->data_detonatormoving != NULL)
+        free(plevel->data_detonatormoving);
+    if(plevel->data_data != NULL)
+        free(plevel->data_data);
+
+    pmover = plevel->mover_first;
+    while(pmover != NULL)
+    {
+        ptmp = pmover;
+        pmover = pmover->next;
+        free(ptmp);
+    }
+
+    pmover = plevel->stack_first;
+    while(pmover != NULL)
+    {
+        ptmp = pmover;
+        pmover = pmover->next;
+        free(ptmp);
+    }
+
+    pmove = plevel->move_first;
+    while(pmove != NULL)
+    {
+        pmover = pmove->mover_first;
+        while(pmover != NULL)
+        {
+            ptmp = pmover;
+            pmover = pmover->next;
+            free(ptmp);
+        }
+
+        pmovetmp = pmove;
+        pmove = pmove->next;
+        free(pmovetmp);
+    }
+
+    free(plevel);
+}
+
+char level_piece(struct level* plevel, int x, int y)
+{ 
+    if(plevel == NULL || plevel->data_pieces == NULL ||
+        x < 0 || x >= plevel->size_x || y < 0 || y >= plevel->size_y)
+    return PIECE_WALL;
+
+    return *(plevel->data_pieces + x + y * plevel->size_x);
+} 
+  
+void level_setpiece(struct level* plevel, int x, int y, char piece)
+{ 
+    if(plevel == NULL || plevel->data_pieces == NULL ||
+        x < 0 || x >= plevel->size_x || y < 0 || y >= plevel->size_y)
+    return;
+
+    *(plevel->data_pieces + x + y * plevel->size_x) = piece;
+}
+
+char level_moving(struct level* plevel, int x, int y)
+{
+    if(plevel == NULL || plevel->data_moving == NULL ||
+        x < 0 || x >= plevel->size_x || y < 0 || y >= plevel->size_y)
+    return MOVE_NONE;
+    
+    return *(plevel->data_moving + x + y * plevel->size_x);
+}
+
+void level_setmoving(struct level* plevel, int x, int y, char moving)
+{
+    if(plevel == NULL || plevel->data_moving == NULL ||
+        x < 0 || x >= plevel->size_x || y < 0 || y >= plevel->size_y)
+    return;
+
+    *(plevel->data_moving + x + y * plevel->size_x) = moving;
+}
+
+char level_previous(struct level* plevel, int x, int y)
+{
+    if(plevel == NULL || plevel->data_previous == NULL ||
+        x < 0 || x >= plevel->size_x || y < 0 || y >= plevel->size_y)
+        return PIECE_WALL;
+    
+    return *(plevel->data_previous + x + y * plevel->size_x);
+}
+
+void level_setprevious(struct level* plevel, int x, int y, char previous)
+{
+    if(plevel == NULL || plevel->data_previous == NULL ||
+        x < 0 || x >= plevel->size_x || y < 0 || y >= plevel->size_y)
+    return;
+
+    *(plevel->data_previous + x + y * plevel->size_x) = previous;
+}
+
+char level_previousmoving(struct level* plevel, int x, int y)
+{
+    if(plevel == NULL || plevel->data_previousmoving == NULL ||
+        x < 0 || x >= plevel->size_x || y < 0 || y >= plevel->size_y)
+    return MOVE_NONE;
+    
+    return *(plevel->data_previousmoving + x + y * plevel->size_x);
+}
+
+void level_setpreviousmoving(struct level* plevel, int x, int y, char previousmoving)
+{
+    if(plevel == NULL || plevel->data_previousmoving == NULL ||
+        x < 0 || x >= plevel->size_x || y < 0 || y >= plevel->size_y)
+    return;
+
+    *(plevel->data_previousmoving + x + y * plevel->size_x) = previousmoving;
+}
+
+char level_detonator(struct level* plevel, int x, int y)
+{
+    if(plevel == NULL || plevel->data_detonator == NULL ||
+        x < 0 || x >= plevel->size_x || y < 0 || y >= plevel->size_y)
+    return PIECE_SPACE;
+    
+    return *(plevel->data_detonator + x + y * plevel->size_x);
+}
+
+void level_setdetonator(struct level* plevel, int x, int y, char detonator)
+{
+    if(plevel == NULL || plevel->data_detonator == NULL ||
+        x < 0 || x >= plevel->size_x || y < 0 || y >= plevel->size_y)
+    return;
+
+    *(plevel->data_detonator + x + y * plevel->size_x) = detonator;
+}
+
+char level_detonatormoving(struct level* plevel, int x, int y)
+{
+    if(plevel == NULL || plevel->data_detonatormoving == NULL ||
+        x < 0 || x >= plevel->size_x || y < 0 || y >= plevel->size_y)
+    return MOVE_NONE;
+
+    return *(plevel->data_detonatormoving + x + y * plevel->size_x);
+}
+
+void level_setdetonatormoving(struct level* plevel, int x, int y, char moving)
+{
+    if(plevel == NULL || plevel->data_detonator == NULL ||
+        x < 0 || x >= plevel->size_x || y < 0 || y >= plevel->size_y)
+    return;
+
+    *(plevel->data_detonatormoving + x + y * plevel->size_x) = moving;
+}
+
+unsigned int level_data(struct level* plevel, int x, int y)
+{
+    if(plevel == NULL || plevel->data_pieces == NULL ||
+        x < 0 || x >= plevel->size_x || y < 0 || y >= plevel->size_y)
+    return 0;
+
+    return *(plevel->data_data + x + y * plevel->size_x);
+}
+
+void level_setdata(struct level* plevel, int x, int y, unsigned int data)
+{
+    if(plevel == NULL || plevel->data_pieces == NULL ||
+        x < 0 || x >= plevel->size_x || y < 0 || y >= plevel->size_y)
+    return;
+
+    *(plevel->data_data + x + y * plevel->size_x) = data;
+}
+
+struct level* level_copy(struct level* pold)
+{
+    struct level* pnew;
+    struct move* pmove;
+    struct mover* pmover;
+    int x, y;
+    int i;
+
+    pnew = level_new();
+
+    pnew->size_x = pold->size_x;
+    pnew->size_y = pold->size_y;
+
+    pnew->player = pold->player;
+
+    pnew->data_pieces = malloc(sizeof(char) * pnew->size_x * pnew->size_y);
+    pnew->data_moving = malloc(sizeof(char) * pnew->size_x * pnew->size_y);
+    pnew->data_previous = malloc(sizeof(char) * pnew->size_x * pnew->size_y);
+    pnew->data_previousmoving = malloc(sizeof(char) * pnew->size_x * pnew->size_y);
+    pnew->data_detonator = malloc(sizeof(char) * pnew->size_x * pnew->size_y);
+    pnew->data_detonatormoving = malloc(sizeof(char) * pnew->size_x * pnew->size_y);
+    pnew->data_data = (unsigned int *)malloc(sizeof(unsigned int) * pnew->size_x * pnew->size_y);
+
+    if(pnew->data_pieces == NULL || pnew->data_moving == NULL ||
+        pnew->data_previous == NULL || pnew->data_previousmoving == NULL ||
+        pnew->data_detonator == NULL || pnew->data_detonatormoving == NULL ||
+        pnew->data_data == NULL )
+        fatal("Out of memory in level_copy");
+
+    pnew->mover_first = NULL;
+    pnew->mover_last = NULL;
+
+    pnew->stack_first = NULL;
+    pnew->stack_last = NULL;
+
+    pnew->stars_caught = pold->stars_caught;
+    pnew->stars_exploded = pold->stars_exploded;
+    pnew->stars_total = pold->stars_total;
+
+    pnew->moves = pold->moves;
+  
+    pnew->flags = pold->flags;
+
+#ifdef XOR_COMPATIBILITY
+    pnew->switched = pold->switched;
+    pnew->mapped = pold->mapped;
+#endif
+
+    pnew->mode = pold->mode;
+
+    pnew->level = pold->level;
+
+    level_settitle(pnew, pold->title);
+
+    for(i = 0; i < 2; i ++)
+    {
+        pnew->alive[i] = pold->alive[i];
+        pnew->teleport_x[i] = pold->teleport_x[i];
+        pnew->teleport_y[i] = pold->teleport_y[i];
+        pnew->view_teleport_x[i] = pold->view_teleport_x[i];
+        pnew->view_teleport_y[i] = pold->view_teleport_y[i];
+        pnew->player_x[i] = pold->player_x[i];
+        pnew->player_y[i] = pold->player_y[i];
+        pnew->view_x[i] = pold->view_x[i];
+        pnew->view_y[i] = pold->view_y[i];
+    }
+
+    for(y = 0; y < pnew->size_y; y++)
+    {
+        for(x = 0; x < pnew->size_x; x++)
+        {
+            level_setpiece(pnew, x, y, level_piece(pold, x, y));
+            level_setmoving(pnew, x, y, MOVE_NONE);
+            level_setprevious(pnew, x, y, PIECE_SPACE);
+            level_setpreviousmoving(pnew, x, y, MOVE_NONE);
+            level_setdetonator(pnew, x, y, PIECE_SPACE);
+            level_setdetonatormoving(pnew, x, y, MOVE_NONE);
+            level_setdata(pnew, x, y, level_data(pold, x, y));
+        }
+    }
+
+    /* Copy moves and undo data */
+    pmove = pold->move_first;
+    while(pmove != NULL)
+    {
+        level_addmove(pnew, pmove->direction);
+
+        pnew->move_current = pnew->move_last;
+
+        pmover = pmove->mover_first;
+        while(pmover != NULL)
+        {
+            mover_newundo(pnew, pmover->x, pmover->y, pmover->direction, pmover->piece, pmover->piece_previous, MOVER_STORE | (pmover->fast ? MOVER_FAST : 0));
+            pmover = pmover->next;
+        }
+        pmove = pmove->next;
+    }
+
+    pnew->move_current = pnew->move_last;
+
+    return pnew;
+}
+
+struct level* level_load(char *filename, int partial)
+{
+    struct level* plevel;
+    FILE *level;
+    char buffer[4096];
+    int state;
+    int x, y, z;
+#ifdef XOR_COMPATIBILITY
+    int w;
+#endif
+    int i;
+    char c;
+    int piece, previous, direction;
+    int teleport;
+    int move;
+    int loop;
+
+    if(!isfile(filename))
+        return NULL;
+
+    level = fopen(filename, "r");
+    if(level == NULL)
+        return NULL;
+
+    plevel = level_new();
+
+    state = 0;
+    while(!feof(level))
+    {
+        file_readline(level, buffer, 4096);
+
+        /* Ignore comments and blank lines */
+        if(buffer[0] == '#' || buffer[0] == 0)
+        continue;
+
+        /* and everything before the "chroma level" line */
+        if(strncmp(buffer, "chroma level", 12) == 0)
+        state = 1;
+  
+        if(state == 1)
+        {
+            if(strncmp(buffer, "mode: ", 6) == 0)
+            {
+                plevel->mode = MODE_MAX;
+#ifdef XOR_COMPATIBILITY
+                if(strncmp(buffer, "mode: xor", 9) == 0)
+                    plevel->mode = MODE_XOR;
+#endif
+#ifdef ENIGMA_COMPATIBILITY
+                if(strncmp(buffer, "mode: enigma", 12) == 0)
+                    plevel->mode = MODE_ENIGMA;
+#endif
+                /* Unrecognised mode */
+                if(plevel->mode == MODE_MAX)
+                {
+                    level_delete(plevel);
+                    return NULL;
+                }
+            }
+        
+            /* Level data comes after level options */
+            if(strncmp(buffer, "data:", 5) == 0)
+            {
+                state = 2;
+                break;
+            }
+
+            /* Read level size */
+            if(strncmp(buffer, "size:", 5) == 0)
+            {
+                i = sscanf(buffer, "size: %d %d", &x, &y);
+                if(i == 2)
+                {
+                    plevel->size_x = x;
+                    plevel->size_y = y; 
+                }
+            }
+
+            /* Read star totals */
+            if(strncmp(buffer,"stars:",6)==0)
+            {
+                i = sscanf(buffer, "stars: %d %d %d", &x, &y, &z);
+                if(i == 3)
+                {
+                    plevel->stars_caught = x;
+                    plevel->stars_exploded = y;
+                    plevel->stars_total = z; 
+                }
+            }
+
+            /* Read move total */
+            if(strncmp(buffer,"moves:",6)==0)
+            {
+                i = sscanf(buffer, "moves: %d", &x);
+                if(i == 1)
+                    plevel->moves = x; 
+            }
+
+            /* Read player */
+            if(strncmp(buffer,"player:",7)==0)
+            {
+                i = sscanf(buffer, "player: %d", &x);
+                if(i == 1 && (x == 1 || x == 2))
+                    plevel->player = x - 1; 
+            }
+
+            /* Read level number */
+            if(strncmp(buffer,"level:",6)==0)
+            {
+                i = sscanf(buffer, "level: %d", &x);
+                if(i == 1)
+                plevel->level = x;
+            }
+
+            /* Read title */
+            if(strncmp(buffer,"title: ",7)==0)
+                level_settitle(plevel, buffer + 7);
+
+            if(strcmp(buffer, "solved: 1") == 0)
+                plevel->flags |= LEVELFLAG_SOLVED;
+
+            if(strcmp(buffer, "failed: 1") == 0)
+                plevel->flags |= LEVELFLAG_FAILED;
+
+#ifdef XOR_COMPATIBILITY
+            /* Read switched */
+            if(strncmp(buffer,"switched:",9)==0)
+            {
+                i = sscanf(buffer, "switched: %d", &x);
+                if(i == 1)
+                    plevel->switched = x; 
+            }
+            /* Read viewpoints */
+            if(strncmp(buffer,"view1: ",7)==0)
+            {
+                i = sscanf(buffer, "view1: %d %d", &x, &y);
+                if(i == 2)
+                {
+                    plevel->view_x[0] = x;
+                    plevel->view_y[0] = y;
+                }
+            }
+            if(strncmp(buffer,"view2: ",7)==0)
+            {
+                i = sscanf(buffer, "view2: %d %d", &x, &y);
+                if(i == 2)
+                {
+                    plevel->view_x[1] = x;
+                    plevel->view_y[1] = y;
+                }
+            }
+            if(strncmp(buffer,"viewteleport1: ",15)==0)
+            {
+                i = sscanf(buffer, "viewteleport1: %d %d (%d %d)", &x, &y, &z, &w);
+                if(i == 4)
+                {
+                    plevel->teleport_x[0] = z;
+                    plevel->teleport_y[0] = w;
+                    plevel->view_teleport_x[0] = x;
+                    plevel->view_teleport_y[0] = y;
+                    teleport ++;
+
+                }
+            }
+            if(strncmp(buffer,"viewteleport2: ",15)==0)
+            {
+                i = sscanf(buffer, "viewteleport2: %d %d (%d %d)", &x, &y, &z, &w);
+                if(i == 4)
+                {
+                    plevel->teleport_x[1] = z;
+                    plevel->teleport_y[1] = w;
+                    plevel->view_teleport_x[1] = x;
+                    plevel->view_teleport_y[1] = y;
+                    teleport ++;
+                }
+            }
+            if(strncmp(buffer, "mapped: ", 8) == 0)
+            {
+                if(strstr(buffer, "top_left") != NULL)
+                    plevel->mapped |= MAPPED_TOP_LEFT;
+                if(strstr(buffer, "top_right") != NULL)
+                    plevel->mapped |= MAPPED_TOP_RIGHT;
+                if(strstr(buffer, "bottom_left") != NULL)
+                    plevel->mapped |= MAPPED_BOTTOM_LEFT;
+                if(strstr(buffer, "bottom_right") != NULL)
+                    plevel->mapped |= MAPPED_BOTTOM_RIGHT;
+            }
+#endif
+        }
+    }
+
+    /* If the file ended before the level data started, it's no good */
+    if(state != 2)
+    {
+        fclose(level);
+        level_delete(plevel);
+        return NULL;
+    }
+
+    /* Don't load level or move data if we're indexing for a menu */
+    if(partial)
+    {
+        fclose(level);
+        return plevel;
+    }
+
+    plevel->data_pieces = malloc(sizeof(char) * plevel->size_x * plevel->size_y);
+    plevel->data_moving = malloc(sizeof(char) * plevel->size_x * plevel->size_y);
+    plevel->data_previous = malloc(sizeof(char) * plevel->size_x * plevel->size_y);
+    plevel->data_previousmoving = malloc(sizeof(char) * plevel->size_x * plevel->size_y);
+    plevel->data_detonator = malloc(sizeof(char) * plevel->size_x * plevel->size_y);
+    plevel->data_detonatormoving = malloc(sizeof(char) * plevel->size_x * plevel->size_y);
+    plevel->data_data = malloc(sizeof(int) * plevel->size_x * plevel->size_y);
+
+    if(plevel->data_pieces == NULL || plevel->data_moving == NULL ||
+            plevel->data_previous == NULL || plevel->data_previousmoving == NULL ||
+            plevel->data_detonator == NULL || plevel->data_detonatormoving == NULL ||
+            plevel->data_data == NULL )
+        fatal("Out of memory in level_load");
+
+    /* Seed random number generator based on level title */
+    x = 7;
+    if(plevel->title != NULL)
+    {
+        for(i = 0; i < strlen(plevel->title); i ++)
+        x = x ^ (plevel->title[i] << (i % 24));
+    }
+    srand(x);
+
+    for(y = 0; y < plevel->size_y; y++)
+    {
+        for(x = 0; x < plevel->size_x; x++)
+        {
+            level_setpiece(plevel, x, y, PIECE_WALL);
+            level_setmoving(plevel, x, y, MOVE_NONE);
+            level_setprevious(plevel, x, y, PIECE_SPACE);
+            level_setpreviousmoving(plevel, x, y, MOVE_NONE);
+            level_setdetonator(plevel, x, y, PIECE_SPACE);
+            level_setdetonatormoving(plevel, x, y, MOVE_NONE);
+            level_setdata(plevel, x, y, rand() % 0xffff);
+        }
+    }
+
+    /* Recalculate stars_total - use the saved value in partial loads only */
+    plevel->stars_total = plevel->stars_caught + plevel->stars_exploded;
+
+    teleport = 0;
+
+    x = 0; y = 0;
+    while(!feof(level) && y <= plevel->size_y)
+    {
+        c = fgetc(level);
+        if(feof(level))
+        break;
+        if(c == 10 || c == 13 || c == 0 || c == -1)
+        continue;
+
+        piece = chartopiece(c);
+      
+        switch(piece)
+        {
+            case PIECE_PLAYER_ONE:
+                plevel->player_x[0] = x;
+                plevel->player_y[0] = y;
+                plevel->alive[0] = 1;
+                break;
+
+            case PIECE_PLAYER_TWO:
+                plevel->player_x[1] = x;
+                plevel->player_y[1] = y;
+                plevel->alive[1] = 1;
+                break;
+
+            case PIECE_STAR:
+                plevel->stars_total ++;
+                break;
+
+#ifdef XOR_COMPATIBILITY
+            case PIECE_TELEPORT:
+                if(teleport < 2)
+                {
+                    plevel->teleport_x[teleport] = x;
+                    plevel->teleport_y[teleport] = y;
+                    teleport ++;
+                }
+                break;
+#endif
+            case PIECE_UNKNOWN:
+                piece = PIECE_SPACE;
+                break;
+
+            default:
+                break;
+        }
+
+        level_setpiece(plevel, x, y, piece);
+
+        x++;
+        if(x == plevel->size_x)
+        {
+            x = 0;
+            y ++;
+        }
+        if(y == plevel->size_y)
+            break;
+    }
+
+    /* Search for move data */
+    state = 0;
+    while(!feof(level))
+    {
+        file_readline(level, buffer, 4096);
+        if(strncmp(buffer,"movedata:",9)==0)
+        {
+            state = 1;
+            break;
+        }
+    }
+    /* and if we find it, read it */
+    if(state == 1)
+    {
+        i = 0;
+
+        while(i < plevel->moves && !feof(level))
+        {
+            move = chartodirection(fgetc(level));
+            if(move != MOVE_NONE && move != MOVE_UNKNOWN)
+            {
+                level_addmove(plevel, move);
+                i ++;
+            }
+        }
+    }
+
+    /* Search for undo data */
+    state = 0;
+    while(!feof(level))
+    {
+        file_readline(level, buffer, 4096);
+        if(strncmp(buffer,"undodata:",9)==0)
+        {
+            state = 1;
+            break;
+        }
+    }
+    /* and if we find it, read it */
+    if(state == 1)
+    {
+        plevel->move_current = plevel->move_first;
+
+        state = 0;
+
+        x = 0;
+        y = 0;
+        piece = PIECE_UNKNOWN;
+        previous = PIECE_UNKNOWN;
+        direction = MOVE_UNKNOWN;
+
+        while(plevel->move_current != NULL && !feof(level))
+        {
+            c = fgetc(level);
+
+            if(c == 13 || c == 10)
+            continue;
+
+            /* Undo data is read by means of a state machine. */
+            /* [x]:[y][direction][piece][previous piece][state of next move] */
+            loop = 1;
+            while(loop)
+            {
+                loop = 0;
+                switch(state)
+                {
+                    /* Read x */
+                    case 0:
+                        if(c >= '0' && c <='9')
+                        {
+                            x = x * 10 + (c - '0');
+                        }
+                        else
+                        {
+                            state = 1; loop = 1;
+                        }
+                        break;
+
+                    /* Read : */
+                    case 1:
+                        if(c == ':')
+                            state = 2;
+                        break;
+
+                    /* Read y */
+                    case 2:
+                        if(c >= '0' && c <='9')
+                        {
+                            y = y * 10 + (c - '0');
+                        }
+                        else
+                        {
+                            state = 3; loop = 1;
+                        }
+                        break;
+
+                    /* Read direction */
+                    case 3:
+                        if(chartodirection(c) != MOVE_UNKNOWN)
+                        {
+                            direction = chartodirection(c);
+                            state = 4;
+                        }
+                        break;
+
+                    /* Read piece */
+                    case 4:
+                        if(chartopiece(c) != PIECE_UNKNOWN)
+                        {
+                            piece = chartopiece(c);
+                            state = 5;
+                        }
+                        break;
+
+                    /* Read previous piece */
+                    case 5:
+                        if(chartopiece(c) != PIECE_UNKNOWN)
+                        {
+                            previous = chartopiece(c);
+                            state = 6;
+                        }
+                        break;
+
+                    /* Read state of next move */
+                    case 6:
+                        if(c == ',' || c == ';' || c == '.')
+                        {
+                            /* Add mover to current move */
+                            mover_newundo(plevel, x, y, direction, piece, previous, MOVER_STORE | (c == ',' ? MOVER_FAST : 0));
+                            /* Next move if this is the last mover for this one */
+                            if(c == '.')
+                                plevel->move_current = plevel->move_current->next;
+
+                            /* Reset state machine for next mover */
+                            state = 0;
+                            x = 0;
+                            y = 0;
+                            piece = PIECE_UNKNOWN;
+                            previous = PIECE_UNKNOWN;
+                            direction = MOVE_UNKNOWN;
+                        }
+                        break;
+
+                    default:
+                        break;
+            }
+        }
+    }
+
+    plevel->move_current = plevel->move_last;
+    }
+
+    fclose(level);
+    
+    return plevel;
+}
+
+int level_save(struct level* plevel, char *filename, int partial)
+{
+    FILE *level;
+    char c;
+    int x, y;
+    int i, j;
+    char buffer[256];
+    struct move* pmove;
+    struct mover* pmover;
+
+    level = fopen(filename, "w");
+    if(level == NULL)
+    return errno;
+
+    fprintf(level, "chroma level\n\n");
+
+#ifdef XOR_COMPATIBILITY
+    if(plevel->mode == MODE_XOR)
+        fprintf(level, "mode: xor\n\n");
+#endif
+#ifdef ENIGMA_COMPATIBILITY
+    if(plevel->mode == MODE_ENIGMA)
+        fprintf(level, "mode: enigma\n\n");
+#endif
+
+    if(plevel->title != NULL && strcmp(plevel->title, "") != 0)
+        fprintf(level, "title: %s\n", plevel->title);
+    if(plevel->level != 0)
+        fprintf(level, "level: %d\n", plevel->level);
+    fprintf(level, "size: %d %d\n", plevel->size_x, plevel->size_y);
+
+    if(!partial || plevel->flags & LEVELFLAG_SOLVED)
+    {
+        fprintf(level, "player: %d\n", plevel->player + 1);
+        fprintf(level, "moves: %d\n", plevel->moves);
+        fprintf(level, "stars: %d %d %d\n", plevel->stars_caught, plevel->stars_exploded, plevel->stars_total);
+        if(plevel->flags & LEVELFLAG_SOLVED)
+            fprintf(level, "solved: 1\n");
+        if(plevel->flags & LEVELFLAG_FAILED)
+            fprintf(level, "failed: 1\n");
+    }
+
+#ifdef XOR_COMPATIBILITY
+    if(plevel->mode == MODE_XOR)
+    {
+        if(plevel->switched)
+            fprintf(level, "switched: %d\n", plevel->switched);
+        fprintf(level, "view1: %d %d\n", plevel->view_x[0], plevel->view_y[0]);
+        fprintf(level, "view2: %d %d\n", plevel->view_x[1], plevel->view_y[1]);
+        if(plevel->teleport_x[0] != -1)
+        {
+            fprintf(level, "viewteleport1: %d %d (%d %d)\n", plevel->view_teleport_x[0], plevel->view_teleport_y[0], plevel->teleport_x[0], plevel->teleport_y[0]);
+            fprintf(level, "viewteleport2: %d %d (%d %d)\n", plevel->view_teleport_x[1], plevel->view_teleport_y[1], plevel->teleport_x[1], plevel->teleport_y[1]);
+        }
+        if(plevel->mapped)
+        {
+            fprintf(level, "mapped:");
+            if(plevel->mapped & MAPPED_TOP_LEFT)
+            fprintf(level, " top_left");
+            if(plevel->mapped & MAPPED_TOP_RIGHT)
+            fprintf(level, " top_right");
+            if(plevel->mapped & MAPPED_BOTTOM_LEFT)
+            fprintf(level, " bottom_left");
+            if(plevel->mapped & MAPPED_BOTTOM_RIGHT)
+            fprintf(level, " bottom_right");
+            fprintf(level, "\n");
+        }
+
+    }
+#endif
+
+    fprintf(level, "\ndata:\n");
+
+    for(y = 0; y < plevel->size_y; y ++)
+    {
+        for(x = 0; x < plevel->size_x; x ++)
+        {
+            fputc(piecetochar(level_piece(plevel, x, y)), level);
+        }
+        fputc('\n', level);
+    }
+    fputc('\n', level);
+
+    if(plevel->move_first != NULL && plevel->moves != 0)
+    {
+        fprintf(level, "movedata:\n");
+
+        i = 0;
+        pmove = plevel->move_first;
+        while(pmove != NULL && i < plevel->moves)
+        {
+            fputc(directiontochar(pmove->direction), level);
+            i ++;
+
+            if(i % 78 == 77 && pmove->next != NULL)
+            fputc('\n', level);
+
+            pmove = pmove->next;
+        }
+
+        fprintf(level, "\n\n");
+
+        if(!partial)
+        {
+        fprintf(level, "undodata:\n");
+
+            i = 0; j = 0;
+        pmove = plevel->move_first;
+        while(pmove != NULL && j < plevel->moves)
+        {
+            pmover = pmove->mover_first;
+            while(pmover != NULL)
+            {
+            c = ',';
+            if(pmover->fast == 0)
+                c = ';';
+            if(pmover->next == NULL)
+                c = '.';
+
+            sprintf(buffer, "%02d:%02d%c%c%c%c", pmover->x, pmover->y, directiontochar(pmover->direction), piecetochar(pmover->piece), piecetochar(pmover->piece_previous), c);
+            if(i + strlen(buffer) > 78)
+            {
+                fprintf(level, "\n");
+                i = 0;
+            }
+
+            fprintf(level, "%s", buffer);
+            i += strlen(buffer);
+            pmover = pmover->next;
+            }
+                j ++;
+            pmove = pmove->next;
+        }
+
+        fprintf(level, "\n\n");
+        }
+    }
+
+    fclose(level);
+
+    return 0;
+}
+
+void level_addmove(struct level* plevel, int move)
+{
+    struct move* pmove;
+    struct move* ptmp;
+    struct mover* pmover;
+    struct mover* pmovertmp;
+
+    /* If we are making a move after undoing some moves */
+    if(plevel->move_current != plevel->move_last)
+    {
+
+    /* Find the first undone move */
+    if(plevel->move_current != NULL)
+        pmove = plevel->move_current->next;
+    else
+        pmove = plevel->move_first;
+
+    /* Delete all moves that follow it */
+    while(pmove != NULL)
+    {
+        ptmp = pmove->next;
+
+        /* Delete movers associated with deleted move */
+        pmover = pmove->mover_first;
+        while(pmover != NULL)
+        {
+        pmovertmp = pmover->next;
+        free(pmover);
+        pmover = pmovertmp;
+        }
+
+        free(pmove);
+        pmove = ptmp;
+    }
+
+    /* Fix up this move so that it appears to be the last */
+    if(plevel->move_current != NULL)
+        plevel->move_current->next = NULL;
+    else
+        plevel->move_first = NULL;
+
+    plevel->move_last = plevel->move_current;
+    }
+
+    /* Create the new move */
+    pmove = (struct move*)malloc(sizeof(struct move));
+    if(pmove == NULL)
+        fatal("Out of memory in level_addmove()");
+
+    pmove->direction = move;
+    pmove->previous = plevel->move_last;
+    pmove->next = NULL;
+    pmove->mover_first = NULL;
+    pmove->mover_last = NULL;
+
+    if(plevel->move_first == NULL)
+    plevel->move_first = pmove;
+
+    if(plevel->move_last != NULL)
+    {
+    plevel->move_last->next = pmove;
+    pmove->count = plevel->move_last->count + 1;
+    }
+    else
+    pmove->count = 1;
+
+    plevel->move_last = pmove;
+    plevel->move_current = pmove;
+
+}
+
+void level_fix(struct level *plevel)
+{
+    int i, j;
+    int teleport;
+
+    for(i = 0; i < 2; i++)
+    {
+    plevel->alive[i] = 0;
+    plevel->teleport_x[i] = -1;
+    plevel->teleport_y[i] = -1;
+    }
+
+    plevel->stars_caught = 0;
+    plevel->stars_exploded = 0;
+    plevel->stars_total = 0;
+
+    teleport = 0;
+
+    for(j = 0; j < plevel->size_y; j ++)
+    {
+        for(i = 0; i < plevel->size_x; i ++)
+        {
+            switch(level_piece(plevel, i, j))
+            {
+            case PIECE_PLAYER_ONE:
+                plevel->alive[0] = 1;
+                plevel->player_x[0] = i;
+                plevel->player_y[0] = j;
+                break;
+
+            case PIECE_PLAYER_TWO:
+                plevel->alive[1] = 1;
+                plevel->player_x[1] = i;
+                plevel->player_y[1] = j;
+                break;
+
+            case PIECE_STAR:
+                plevel->stars_total ++;
+                break;
+
+#ifdef XOR_COMPATIBILITY
+            case PIECE_TELEPORT:
+                if(teleport < 2)
+                {
+                    plevel->teleport_x[teleport] = i;
+                    plevel->teleport_y[teleport] = j;
+                    teleport ++;
+                }
+                break;
+#endif
+            }
+        }
+    }
+}
+
+void level_settitle(struct level* plevel, char *title)
+{
+    if(plevel->title != NULL)
+        free(plevel->title);
+
+    if(title == NULL)
+    {
+        plevel->title = NULL;
+        return;
+    }
+
+    plevel->title = malloc(strlen(title) + 1);
+
+    if(plevel->title != NULL)
+        strcpy(plevel->title, title);
+}
+