/******************************************************************************
 *
 * Copyright:
 *    (C) 2006 Embedded Artists AB
 *
 * File:
 *    sokoban.c
 *
 * Description:
 *    Implementation of Sokoban game.
 *
 *****************************************************************************/

/******************************************************************************
 * Includes
 *****************************************************************************/
#include "../pre_emptive_os/api/osapi.h"
#include "../pre_emptive_os/api/general.h"
#include <printf_P.h>
#include <ea_init.h>
#include <stdlib.h>
#include "lcd.h"
#include "key.h"
#include "select.h"

#include "configAppl.h"

#include "sokoban.h"
#include "sokoban_bmp.h"
#include "sokoban_levels.h"


/******************************************************************************
 * Typedefs and defines
 *****************************************************************************/

#define GAME_NOT_STARTED 0
#define GAME_RUNNING     1
#define GAME_OVER        2
#define GAME_END         3

#define SCREEN_WIDTH  ((tU8)130)
#define SCREEN_HEIGHT ((tU8)130)


#define ROW_OFFSET 3
#define COL_OFFSET 4
#define ROW_SPACE  0
#define COL_SPACE  0
#define FIELD_WIDTH  10
#define FIELD_HEIGHT 10

#define GAME_BKG_COLOR 0xD8
#define COLOR_STATUSLINE_BG  0x03
#define COLOR_STATUSLINE_FG  0xFF



// Posible values for each field:
// Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0
//                                          |       |       |- 0: free, 1: Box
//                                          |       |- 0: field, 1: wall (border)
//                                          |- 0: no target, 1: target field

#define F_FREE      0x00
#define F_BOX       0x01
#define F_BORDER    0x02
#define F_TARGET    0x04
#define F_POS       0x08

// Values for display routines:
enum { V_OLDCONTENT, V_POS, V_BOX, V_DELBOX_POS };

// Values for x-/y-size
#define XSIZE    12
#define YSIZE    11
#define XLEFT     0
#define XRIGHT   (XSIZE-1)
#define YUP       0
#define YDOWN    (YSIZE-1)

#define Y_STATUSLINE  116

enum { STARTMENU, LEVEL_COMPLETE };


tU8 field[YSIZE][XSIZE];     // array for field values
tU8 xpos, ypos;              // players actual x-/y-position
tU8  targetCounter;          // shows the number of remaining target fields
tU16 movesCounter;           // counts the number of moves

tU8 gameStatus;
tU8 cLevel = 0;
tU8 cMaxLevel;
tU8 cFirstStart;


void modifyField(unsigned char x, unsigned char y, unsigned char value);
void checkSuccess(void);


tMenu menu;

/*****************************************************************************
 *
 * Description:
 *    Draw cursor in sokoban menu
 *
 *
 ****************************************************************************/
static void drawMenuCursorSokoban(tU8 cursor)
{
  tU8 row;

  for(row=0; row<menu.noOfChoices; row++)
  {
    lcdGotoxy(menu.xPos+4,menu.yPos+17+(14*row));
    if(row == cursor)
      lcdColor(menu.bgColor+1,menu.selectedColor);
    else
      lcdColor(menu.bgColor,menu.choicesColor);

    lcdPuts(menu.pChoice[row]);
  }
}


/*****************************************************************************
 *
 * Description:
 *    Implements a menu
 *    
 ****************************************************************************/
void drawMenuSokoban(void)
{
  tU8 cursor;
  
  //draw boarder
  lcdRect(menu.xPos, menu.yPos, menu.xLen, menu.yLen, menu.borderColor);
  lcdRect(menu.xPos+1, menu.yPos+16, menu.xLen-2, menu.yLen-17, menu.bgColor);

  //write header text
  lcdGotoxy(menu.headerTextXpos,menu.yPos+1);
  lcdColor(menu.borderColor,menu.headerColor);
  lcdPuts(menu.pHeaderText);

  //write choices

  cursor = menu.initialChoice;

  drawMenuCursorSokoban(cursor);
}


tTimer tSokoTimer;
tU32 lTimer = 0;
tU8 cTimerRunning = 0;

/*****************************************************************************
 *
 * Description:
 *    This function is called every 1s by an OS API function via interrupt.
 *    Keep processing time a s small as possible!
 *
 ****************************************************************************/
void sokobanTimer(void)
{
   if(cTimerRunning)
   {
      if(lTimer < 5999)   // max. 99 minutes, 59 seconds
         lTimer++;
   }
}


/*****************************************************************************
 *
 * Description:
 *    Start timer for Sokoban playtime
 *
 *
 ****************************************************************************/
void startTimer(void)
{
   cTimerRunning = 1;
}


/*****************************************************************************
 *
 * Description:
 *    Stop timer for sokoban playtime
 *
 *
 ****************************************************************************/
void stopTimer(void)
{
   cTimerRunning = 0;
}


/*****************************************************************************
 *
 * Description:
 *    Draw an empty field at (col, row)
 *
 ****************************************************************************/
void showFree(tU8 col, tU8 row)
{
   lcdIcon(COL_OFFSET + col*(FIELD_WIDTH+COL_SPACE), ROW_OFFSET + row*(FIELD_HEIGHT+ROW_SPACE), FIELD_WIDTH, FIELD_HEIGHT, 0, 0, bmpFree); 
}


/*****************************************************************************
 *
 * Description:
 *    Draw player in field at (col, row)
 *
 ****************************************************************************/
void showPlayer(tU8 col, tU8 row)
{
   lcdIcon(COL_OFFSET + col*(FIELD_WIDTH+COL_SPACE), ROW_OFFSET + row*(FIELD_HEIGHT+ROW_SPACE), FIELD_WIDTH, FIELD_HEIGHT, 0, 0, bmpPlayer); 
}


/*****************************************************************************
 *
 * Description:
 *    Draw a box at (col, row)
 *
 ****************************************************************************/
void showBox(tU8 col, tU8 row)
{
   lcdIcon(COL_OFFSET + col*(FIELD_WIDTH+COL_SPACE), ROW_OFFSET + row*(FIELD_HEIGHT+ROW_SPACE), FIELD_WIDTH, FIELD_HEIGHT, 0, 0, bmpBox); 
}


/*****************************************************************************
 *
 * Description:
 *    Draw a border field at (col, row)
 *
 ****************************************************************************/
void showBorder(tS8 col, tS8 row)
{
   lcdIcon(COL_OFFSET + col*(FIELD_WIDTH+COL_SPACE), ROW_OFFSET + row*(FIELD_HEIGHT+ROW_SPACE), FIELD_WIDTH, FIELD_HEIGHT, 0, 0, bmpBorder); 
}


/*****************************************************************************
 *
 * Description:
 *    Draw a free target field at (col, row)
 *
 ****************************************************************************/
void showTargetFree(tU8 col, tU8 row)
{
   lcdIcon(COL_OFFSET + col*(FIELD_WIDTH+COL_SPACE), ROW_OFFSET + row*(FIELD_HEIGHT+ROW_SPACE), FIELD_WIDTH, FIELD_HEIGHT, 0, 0, bmpTargetFree); 
}


/*****************************************************************************
 *
 * Description:
 *    Draw player in a target field at (col, row)
 *
 ****************************************************************************/
void showTargetPlayer(tU8 col, tU8 row)
{
   lcdIcon(COL_OFFSET + col*(FIELD_WIDTH+COL_SPACE), ROW_OFFSET + row*(FIELD_HEIGHT+ROW_SPACE), FIELD_WIDTH, FIELD_HEIGHT, 0, 0, bmpTargetPlayer); 
}


/*****************************************************************************
 *
 * Description:
 *    Draw box in a target field at (col, row)
 *
 ****************************************************************************/
void showTargetBox(tU8 col, tU8 row)
{
   lcdIcon(COL_OFFSET + col*(FIELD_WIDTH+COL_SPACE), ROW_OFFSET + row*(FIELD_HEIGHT+ROW_SPACE), FIELD_WIDTH, FIELD_HEIGHT, 0, 0, bmpTargetBox); 
}


/*****************************************************************************
 *
 * Description:
 *    Initialize all fields with the default values for the current level
 *
 ****************************************************************************/
void initFields(tU8 level)
{
  unsigned char x, y;

  targetCounter = 0;
  xpos = 255;

  for(y=0; y < YSIZE; y++)
    {
     for(x=0; x < XSIZE; x++)
       {
        switch(defaultField[(tU16)level*YSIZE*XSIZE + y*XSIZE + x])
        {
           case ' ':
              field[y][x] = F_FREE;
           break;
           case '*':
              field[y][x] = F_BORDER;
           break;
           case 't':
              field[y][x] = F_TARGET | F_FREE;
           break;
           case 'b':
              field[y][x] = F_BOX;
           break;
           case 'B':
              field[y][x] = F_BOX | F_TARGET;
           break;
           case 'p':
              field[y][x] = F_POS | F_FREE;
           break;
           case 'P':
              field[y][x] = F_POS | F_TARGET;
           break;

        }

        if(field[y][x] == (F_FREE | F_TARGET))
           targetCounter++;
        if(field[y][x] & F_POS)
          {
           xpos = x;
           ypos = y;
           field[y][x] &= (~F_POS);
          }
       }
    }
  if(xpos == 255)
    {
     // no player position found!
    }
  modifyField(xpos, ypos, V_POS);
  movesCounter = 0;
  checkSuccess();
}


/*****************************************************************************
 *
 * Description:
 *    Converts a 16-Bit value to ASCII BCD-value in array text
 *
 ****************************************************************************/
void convertBcd(tU16 value, tU8 *text)
{
   tU8 i;
   tU16 temp;

   i = 0;
   temp = 10000;
   do
   {
      text[i++] = (value / temp) + '0';
      value = value % temp;
      temp = temp / 10;
   }
   while(temp != 0);
   if(i == 0)
      text[i++] = '0';
   text[i++] = 0;
}



tU8 arText[20];

/*****************************************************************************
 *
 * Description:
 *    Show current level in statusline
 *
 ****************************************************************************/
void showLevel(void)
{
   lcdGotoxy(0,Y_STATUSLINE);
   lcdColor(COLOR_STATUSLINE_BG, COLOR_STATUSLINE_FG);
   lcdPuts("L");
   convertBcd(cLevel, arText);
   lcdGotoxy(10,Y_STATUSLINE);
   lcdPuts(&arText[3]);
}


/*****************************************************************************
 *
 * Description:
 *    Show current playtime in statusline
 *
 ****************************************************************************/
void showTime(tU32 t)
{
  tU8 cMinutes, cSeconds;
  tU8 i;

  lcdGotoxy(32,Y_STATUSLINE);
  cMinutes = t / 60;
  cSeconds = t % 60;
  i = 0;
  arText[i++] = (cMinutes / 10) + '0';
  arText[i++] = (cMinutes % 10) + '0';
  arText[i++] = ':';
  arText[i++] = (cSeconds / 10) + '0';
  arText[i++] = (cSeconds % 10) + '0';
  arText[i++] = 0;
  lcdColor(COLOR_STATUSLINE_BG, COLOR_STATUSLINE_FG);
  lcdPuts("T");
  lcdGotoxy(42,Y_STATUSLINE);
  lcdPuts(arText);
}


/*****************************************************************************
 *
 * Description:
 *    Show number of moves in statusline
 *
 ****************************************************************************/
void updateMovesCounter(void)
{
   lcdGotoxy(88,Y_STATUSLINE);
   lcdColor(COLOR_STATUSLINE_BG, COLOR_STATUSLINE_FG);
   lcdPuts("M");
   convertBcd(movesCounter, arText);
   lcdGotoxy(98,Y_STATUSLINE);
   lcdPuts(&arText[2]);
}


/*****************************************************************************
 *
 * Description:
 *    Increment the counter for the moves
 *
 ****************************************************************************/
void incMovesCounter(void)
{
   if(movesCounter < 999)
      movesCounter++;
}


/*****************************************************************************
 *
 * Description:
 *    Show menu after a level has been completed
 *
 ****************************************************************************/
tU8 menuLevelComplete(tU8 param)
{
  tU8 arLevel[] = "Play level<  >\0";
  tU8 level;
  tU8 cursor;
  tU8 anyKey;

  menu.xPos = 5;
  menu.yPos = 39;
  menu.xLen = 6+(14*8);
  menu.yLen = 3*14+7;
  menu.noOfChoices = 2;
  menu.initialChoice = 0;
  if(param == STARTMENU)
     menu.pHeaderText = "   Sokoban    ";
  else
     menu.pHeaderText = "Level complete";
  menu.headerColor = 0xFF;
  menu.headerTextXpos = 8;
  menu.pChoice[0] =  arLevel;
  menu.pChoice[1] =  "End game      ";
  menu.bgColor       = 0;
  menu.borderColor   = 0xE3;
  menu.choicesColor  = 0xfd;
  menu.selectedColor = 0xe0;

  checkKey();  // dummy call
  if(param == STARTMENU)
     level = 0;
  else if(cLevel < cMaxLevel)
     level = cLevel+1;
  else
     level = 0;
  arLevel[11] = (level / 10) + '0';
  arLevel[12] = (level % 10) + '0';
  cursor = menu.initialChoice;
  drawMenuSokoban();
  do
  {
       anyKey = checkKey();
   
       if (anyKey != KEY_NOTHING)
       {
   
         //select specific function
   
         if (anyKey == KEY_CENTER)
         {
   
         }
         else if (anyKey == KEY_UP)
         {
           if (cursor > 0)
             cursor--;
           else
             cursor = menu.noOfChoices - 1;
   
           drawMenuCursorSokoban(cursor);
         }
         else if (anyKey == KEY_DOWN)
         {
           if (cursor < menu.noOfChoices - 1)
             cursor++;
           else
             cursor = 0;
   
           drawMenuCursorSokoban(cursor);
         }
         else if (anyKey == KEY_LEFT)
         {
           if(cursor == 0)
           {
              if(level > 0)
                 level--;
              else
                 level = cMaxLevel;
              arLevel[11] = (level / 10) + '0';
              arLevel[12] = (level % 10) + '0';
              drawMenuCursorSokoban(cursor);
           }
         }
         else if (anyKey == KEY_RIGHT)
         {
           if(cursor == 0)
           {
              if(level < cMaxLevel)
                 level++;
              else
                 level = 0;
              arLevel[11] = (level / 10) + '0';
              arLevel[12] = (level % 10) + '0';
              drawMenuCursorSokoban(cursor);
           }
         }
   
       }
       else
         osSleep(1);

  }
  while(anyKey != KEY_CENTER);

  switch(cursor)
  {
    case 0: //goto level xx
      cLevel = level;
      gameStatus = GAME_NOT_STARTED;
      break;
    case 1: //End game
      gameStatus = GAME_OVER;
      break;
  }
  return(cursor);
}


/*****************************************************************************
 *
 * Description:
 *    Check if all boxes have been placed on target fields
 *
 ****************************************************************************/
void checkSuccess(void)     // check if all boxes are placed onto target fields
{
  updateMovesCounter();

  if(targetCounter == 0)
  {
     stopTimer();
     menuLevelComplete(LEVEL_COMPLETE);
  }
}


/*****************************************************************************
 *
 * Description:
 *    Illegal move operation, no move in the selected direction possible
 *
 ****************************************************************************/
void illegalMove(void)      // illegal move operation (field blocked)
{
}


/*****************************************************************************
 *
 * Description:
 *    Show field(x,y) with the current value
 *
 ****************************************************************************/
void displayField(unsigned char x, unsigned char y, unsigned char value)
{
   switch(value)
    {
     case F_FREE:
       showFree(x, y);
       break;

     case (F_FREE | F_POS):
       showPlayer(x, y);
       break;

     case (F_FREE | F_TARGET):
       showTargetFree(x, y);
       break;

     case (F_FREE | F_TARGET | F_POS):
       showTargetPlayer(x, y);
       break;

     case F_BORDER:
       showBorder(x, y);
       break;

     case F_BOX:
       showBox(x, y);
       break;

     case (F_BOX | F_TARGET):
       showTargetBox(x, y);
       break;
    }

}


/*****************************************************************************
 *
 * Description:
 *    Show all fields
 *
 ****************************************************************************/
void displayAllFields(void)
{                            // display all fields
  unsigned char x, y, value;


  for(y=0; y < YSIZE; y++)
  {
     for(x=0; x < XSIZE; x++)
     {
        value = field[y][x];
        if( (x == xpos) && (y == ypos) )
           value |= F_POS;
        displayField(x, y, value);
     }
  }

}


/*****************************************************************************
 *
 * Description:
 *    Change field(x,y) to the new value
 *
 ****************************************************************************/
void modifyField(unsigned char x, unsigned char y, unsigned char value)
{
   switch(value)
    {
     case V_POS:
       incMovesCounter();
       displayField(x, y, field[y][x] | F_POS);
       break;

     case V_OLDCONTENT:
       displayField(x, y, field[y][x]);
       break;

     case V_BOX:
       field[y][x] |= F_BOX;
       if(field[y][x] & F_TARGET)
         targetCounter--;
       displayField(x, y, field[y][x]);
       break;

     case V_DELBOX_POS:
       incMovesCounter();
       field[y][x] &= (~F_BOX);
       if(field[y][x] & F_TARGET)
         targetCounter++;
       displayField(x, y, field[y][x] | F_POS);
       break;
    }
}


/*****************************************************************************
 *
 * Description:
 *    handle key LEFT
 *
 ****************************************************************************/
void keyLeft(void)
{
if(xpos > XLEFT)
  {
   switch(field[ypos][xpos-1])
     {
      case F_FREE:
      case (F_FREE | F_TARGET):
        modifyField(xpos, ypos, V_OLDCONTENT);
        xpos--;
        modifyField(xpos, ypos, V_POS);
        break;

      case F_BORDER:
        illegalMove();
        break;

      case F_BOX:
      case (F_BOX | F_TARGET):
        if((xpos - 1) > XLEFT)
          {
           switch(field[ypos][xpos-2])
             {
              case F_FREE:
              case (F_FREE | F_TARGET):
                modifyField(xpos, ypos, V_OLDCONTENT);
                xpos--;
                modifyField(xpos, ypos, V_DELBOX_POS);
                modifyField(xpos-1, ypos, V_BOX);
                break;

              default:
                illegalMove();
                break;
             }
          }
        else
          illegalMove();
        break;
     }
  }
else
  illegalMove();

checkSuccess();

}


/*****************************************************************************
 *
 * Description:
 *    handle key RIGHT
 *
 ****************************************************************************/
void keyRight(void)
{
if(xpos < XRIGHT)
  {
   switch(field[ypos][xpos+1])
     {
      case F_FREE:
      case (F_FREE | F_TARGET):
        modifyField(xpos, ypos, V_OLDCONTENT);
        xpos++;
        modifyField(xpos, ypos, V_POS);
        break;

      case F_BORDER:
        illegalMove();
        break;

      case F_BOX:
      case (F_BOX | F_TARGET):
        if((xpos + 1) < XRIGHT)
          {
           switch(field[ypos][xpos+2])
             {
              case F_FREE:
              case (F_FREE | F_TARGET):
                modifyField(xpos, ypos, V_OLDCONTENT);
                xpos++;
                modifyField(xpos, ypos, V_DELBOX_POS);
                modifyField(xpos+1, ypos, V_BOX);
                break;

              default:
                illegalMove();
                break;
             }
          }
        else
          illegalMove();
        break;
     }
  }
else
  illegalMove();

checkSuccess();

}


/*****************************************************************************
 *
 * Description:
 *    handle key UP
 *
 ****************************************************************************/
void keyUp(void)
{
if(ypos > YUP)
  {
   switch(field[ypos-1][xpos])
     {
      case F_FREE:
      case (F_FREE | F_TARGET):
        modifyField(xpos, ypos, V_OLDCONTENT);
        ypos--;
        modifyField(xpos, ypos, V_POS);
        break;

      case F_BORDER:
        illegalMove();
        break;

      case F_BOX:
      case (F_BOX | F_TARGET):
        if((ypos - 1) > YUP)
          {
           switch(field[ypos-2][xpos])
             {
              case F_FREE:
              case (F_FREE | F_TARGET):
                modifyField(xpos, ypos, V_OLDCONTENT);
                ypos--;
                modifyField(xpos, ypos, V_DELBOX_POS);
                modifyField(xpos, ypos-1, V_BOX);
                break;

              default:
                illegalMove();
                break;
             }
          }
        else
          illegalMove();
        break;
     }
  }
else
  illegalMove();

checkSuccess();

}


/*****************************************************************************
 *
 * Description:
 *    handle key DOWN
 *
 ****************************************************************************/
void keyDown(void)
{
if(ypos < YDOWN)
  {
   switch(field[ypos+1][xpos])
     {
      case F_FREE:
      case (F_FREE | F_TARGET):
        modifyField(xpos, ypos, V_OLDCONTENT);
        ypos++;
        modifyField(xpos, ypos, V_POS);
        break;

      case F_BORDER:
        illegalMove();
        break;

      case F_BOX:
      case (F_BOX | F_TARGET):
        if((ypos + 1) < YDOWN)
          {
           switch(field[ypos+2][xpos])
             {
              case F_FREE:
              case (F_FREE | F_TARGET):
                modifyField(xpos, ypos, V_OLDCONTENT);
                ypos++;
                modifyField(xpos, ypos, V_DELBOX_POS);
                modifyField(xpos, ypos+1, V_BOX);
                break;

              default:
                illegalMove();
                break;
             }
          }
        else
          illegalMove();
        break;
     }
  }
else
  illegalMove();

checkSuccess();

}


/*****************************************************************************
 *
 * Description:
 *    handle key CENTER: show menu
 *
 ****************************************************************************/
void keyCenter(void)
{
  tU8 arLevel[] = "Play level<  >\0";
  tU8 level;
  tU8 cursor;
  tU8 anyKey;

  stopTimer();

  menu.xPos = 5;
  menu.yPos = 28;
  menu.xLen = 6+(14*8);
  menu.yLen = 5*14+7;
  menu.noOfChoices = 4;
  menu.initialChoice = 0;
  menu.pHeaderText = "Sokoban paused";
  menu.headerColor = 0xFF;
  menu.headerTextXpos = 8;
  menu.pChoice[0] =  "Continue level";
  menu.pChoice[1] =  "Restart level ";
  menu.pChoice[2] =  arLevel;
  menu.pChoice[3] =  "End game      ";
  menu.bgColor       = 0;
  menu.borderColor   = 0xe3;
  menu.choicesColor  = 0xfd;
  menu.selectedColor = 0xe0;

  checkKey();  // dummy call
  if(cLevel < cMaxLevel)
     level = cLevel+1;
  else
     level = 0;
  arLevel[11] = (level / 10) + '0';
  arLevel[12] = (level % 10) + '0';
  cursor = menu.initialChoice;
  drawMenuSokoban();
  do
  {
       anyKey = checkKey();
   
       if (anyKey != KEY_NOTHING)
       {
   
         //select specific function
   
         if (anyKey == KEY_CENTER)
         {
   
         }
         else if (anyKey == KEY_UP)
         {
           if (cursor > 0)
             cursor--;
           else
             cursor = menu.noOfChoices - 1;
   
           drawMenuCursorSokoban(cursor);
         }
         else if (anyKey == KEY_DOWN)
         {
           if (cursor < menu.noOfChoices - 1)
             cursor++;
           else
             cursor = 0;
   
           drawMenuCursorSokoban(cursor);
         }
         else if (anyKey == KEY_LEFT)
         {
           if(cursor == 2)
           {
              if(level > 0)
                 level--;
              else
                 level = cMaxLevel;
              arLevel[11] = (level / 10) + '0';
              arLevel[12] = (level % 10) + '0';
              drawMenuCursorSokoban(cursor);
           }
         }
         else if (anyKey == KEY_RIGHT)
         {
           if(cursor == 2)
           {
              if(level < cMaxLevel)
                 level++;
              else
                 level = 0;
              arLevel[11] = (level / 10) + '0';
              arLevel[12] = (level % 10) + '0';
              drawMenuCursorSokoban(cursor);
           }
         }
   
       }
       else
         osSleep(1);

  }
  while(anyKey != KEY_CENTER);

  switch(cursor)
  {
    case 0: //continue game
      displayAllFields();
      startTimer();
      break;
    case 1: //Restart level
      gameStatus = GAME_NOT_STARTED;
      break;
    case 2: //goto level xx
      cLevel = level;
      gameStatus = GAME_NOT_STARTED;
      break;
    case 3: //End game
      gameStatus = GAME_OVER;
      break;
  }

}


/*****************************************************************************
 *
 * Description:
 *    Draw game background and game board, initialize all variables
 *
 ****************************************************************************/
static tU8
setupGame (void)
{
  tS8 i;
  tU8 status = GAME_RUNNING;

  lcdColor(GAME_BKG_COLOR,0x00);
  lcdClrscr();

  if(cFirstStart)
  {
     cFirstStart = 0;
     if( menuLevelComplete(STARTMENU) == 1)
        status = GAME_OVER;
  }

  if(status == GAME_RUNNING)
  {
     for(i = -1; i < XSIZE+1; i++)
        showBorder(i, -1);
     for(i = -1; i < YSIZE+1; i++)
        showBorder(-1, i);
     for(i = -1; i < YSIZE+1; i++)
        showBorder(XSIZE, i);
     for(i = -1; i < XSIZE+1; i++)
        showBorder(i, YSIZE);
   
     lcdColor(COLOR_STATUSLINE_BG, COLOR_STATUSLINE_FG);
     lcdRect(-1, Y_STATUSLINE, 130, 130 - Y_STATUSLINE, COLOR_STATUSLINE_BG);
     initFields(cLevel);
     displayAllFields();
     showLevel();
     showTime(0);
     lTimer = 0;
     startTimer();
  }

  return(status);
}


/*****************************************************************************
 *
 * Description:
 *    Implements Sokoban game
 *    
 ****************************************************************************/
void playSokoban(void)
{
  tU8 anyKey;
  tU8 errorcode;
  static tU32 lLastTimer = 0;
  static tU8 cFirstRun = 1;


  if(cFirstRun)
  {
     cFirstRun = 0;
     osInitTimers(&errorcode);
  }
  osCreateTimer(&tSokoTimer, sokobanTimer, TRUE, 100);

  gameStatus = GAME_NOT_STARTED;
  cLevel = 0;
  cMaxLevel = (tU16)sizeof(defaultField) / (XSIZE*YSIZE) - 1;
  cFirstStart = 1;
  lLastTimer = 0;

  while(gameStatus != GAME_END)
  {
    if(gameStatus == GAME_NOT_STARTED)
    {
        gameStatus = setupGame();
    }
    
    osSleep(1);

    anyKey = checkKey();
    switch(gameStatus)
    {
      case GAME_RUNNING:
        if (anyKey != KEY_NOTHING)
        {
          if (anyKey == KEY_UP)
          {
            keyUp();
          }
          else if (anyKey == KEY_DOWN)
          {
            keyDown();
          }
          else if (anyKey == KEY_RIGHT)
          {
            keyRight();
          }
          else if (anyKey == KEY_LEFT)
          {
            keyLeft();
          }
          else if (anyKey == KEY_CENTER)
          {
            keyCenter();
          }

        }
        else
        {
          osSleep(1);
        }

        if(lTimer != lLastTimer)
        {
           lLastTimer = lTimer;
           showTime(lLastTimer);
        }
        break;
      
      case GAME_OVER:
          gameStatus = GAME_END;
          break;                       //End game
       
      default:
        gameStatus = GAME_END;
        break;
    }
  }

  osDeleteTimer(&tSokoTimer, &errorcode);

}

