/******************************************************************************
 *
 * Copyright:
 *    (C) 2006 Embedded Artists AB
 *
 * File:
 *    memory.c
 *
 * Description:
 *    Implementation of memory game.
 *    Short game description:
 *    - 2 Players play against each other: player1 (P1, red color) and player2 (P2, blue color)
 *    - there are 21 pairs of cards (same image), randomly placed on the field
 *    - all cards are hidden (grey fields), each player has to find card pairs
 *    - player has to open 2 cards: select with up/down/left/right keys, then press Center key
 *    - if the 2 cards match, player gets a score point and may continue to play
 *    - if the 2 cards do not match, they are hidden again and the next player continues
 *    - after each 2 cards, one of the up/down/left/right keys has to be pressed to continue
 *    - status line: left:   score of player1
 *                   middle: shows, which player has to open cards
 *                   right:  score of player2
 *
 *****************************************************************************/

/******************************************************************************
 * 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 "memory.h"
#include "memory_bmp.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 BOARD_GRID_COLOR  ((tU8)0)    // black
#define BOARD_BKG_COLOR   ((tU8)0xff) // white
#define GAME_BKG_COLOR_OK ((tU8)0x7C) // bright green


/*****************************************************************************
 * Local variables
 ****************************************************************************/

static tU8 gameStatus;

/*****************************************************************************
 * External variables
 ****************************************************************************/

extern volatile tU32 ms;



  #define ROWS       7
  #define MAX_ROW (ROWS-1)
  #define COLUMNS    6
  #define MAX_COLUMN (COLUMNS-1)
  #define ROW_OFFSET 2
  #define COL_OFFSET 2
  #define ROW_SPACE  2
  #define COL_SPACE  2
  #define CARD_WIDTH  16
  #define CARD_HEIGHT 16

#define CARDSELECTED 0x01
#define CARDPRESENT  0x02
#define CARDHIDDEN   0x04

#define STATUSLINE_Y  114
#define COLOR_PLAYER1 0xE0
#define COLOR_PLAYER2 0x03
#define COLOR_HIDDENCARD 0x49

tU8 cYpos = 0;
tU8 cXpos = 0;
tU8 cLastYpos = 0;
tU8 cLastXpos = 0;
typedef struct
{
   tU8 *image;
   tU8 status;
} T_CARD;
T_CARD arCards[ROWS][COLUMNS];

tU8 cScorePlayer1 = 0;
tU8 cScorePlayer2 = 0;
tU8 cCurrentPlayer = 1;
tU8 arText[20];
tU8 cRemainingPairs = ROWS*COLUMNS/2;


/*****************************************************************************
 *
 * Description:
 *    Draw a frame around field (row, col) to mark as selected
 *
 ****************************************************************************/
void selectField(tU8 row, tU8 col)
{
   tU8 color;

   if(cCurrentPlayer == 1)
      color = COLOR_PLAYER1;
   else
      color = COLOR_PLAYER2;
   lcdRect(ROW_OFFSET + row*(CARD_WIDTH+ROW_SPACE)-2, COL_OFFSET + col*(CARD_HEIGHT+COL_SPACE)-2, CARD_WIDTH+4, 2, color); 
   lcdRect(ROW_OFFSET + row*(CARD_WIDTH+ROW_SPACE)-2, COL_OFFSET + col*(CARD_HEIGHT+COL_SPACE)+CARD_HEIGHT, CARD_WIDTH+4, 2, color); 
   lcdRect(ROW_OFFSET + row*(CARD_WIDTH+ROW_SPACE)-2, COL_OFFSET + col*(CARD_HEIGHT+COL_SPACE)-2, 2, CARD_HEIGHT+4, color); 
   lcdRect(ROW_OFFSET + row*(CARD_WIDTH+ROW_SPACE)+CARD_WIDTH, COL_OFFSET + col*(CARD_HEIGHT+COL_SPACE)-2, 2, CARD_HEIGHT+4, color); 
}


/*****************************************************************************
 *
 * Description:
 *    Clear the frame around field (row, col) to mark as unselected
 *
 ****************************************************************************/
void unselectField(tU8 row, tU8 col)
{
   lcdRect(ROW_OFFSET + row*(CARD_WIDTH+ROW_SPACE)-2, COL_OFFSET + col*(CARD_HEIGHT+COL_SPACE)-2, CARD_WIDTH+4, 2, GAME_BKG_COLOR_OK); 
   lcdRect(ROW_OFFSET + row*(CARD_WIDTH+ROW_SPACE)-2, COL_OFFSET + col*(CARD_HEIGHT+COL_SPACE)+CARD_HEIGHT, CARD_WIDTH+4, 2, GAME_BKG_COLOR_OK); 
   lcdRect(ROW_OFFSET + row*(CARD_WIDTH+ROW_SPACE)-2, COL_OFFSET + col*(CARD_HEIGHT+COL_SPACE)-2, 2, CARD_HEIGHT+4, GAME_BKG_COLOR_OK); 
   lcdRect(ROW_OFFSET + row*(CARD_WIDTH+ROW_SPACE)+CARD_WIDTH, COL_OFFSET + col*(CARD_HEIGHT+COL_SPACE)-2, 2, CARD_HEIGHT+4, GAME_BKG_COLOR_OK); 
}


/*****************************************************************************
 *
 * Description:
 *    Draw an empty field at (row, col)
 *
 ****************************************************************************/
void showEmptyField(tU8 row, tU8 col)
{
   lcdRect(ROW_OFFSET + row*(CARD_WIDTH+ROW_SPACE), COL_OFFSET + col*(CARD_HEIGHT+COL_SPACE), CARD_WIDTH, CARD_HEIGHT, GAME_BKG_COLOR_OK); 
}


/*****************************************************************************
 *
 * Description:
 *    Draw a hidden field at (row, col)
 *
 ****************************************************************************/
void showHiddenField(tU8 row, tU8 col)
{
   lcdRect(ROW_OFFSET + row*(CARD_WIDTH+ROW_SPACE), COL_OFFSET + col*(CARD_HEIGHT+COL_SPACE), CARD_WIDTH, CARD_HEIGHT, COLOR_HIDDENCARD); 
}


/*****************************************************************************
 *
 * Description:
 *    Show card image at field (row, col)
 *
 ****************************************************************************/
void showImage(tU8 row, tU8 col, tU8 *image)
{
   lcdIcon(ROW_OFFSET + row*(CARD_WIDTH+ROW_SPACE), COL_OFFSET + col*(CARD_HEIGHT+COL_SPACE), CARD_WIDTH, CARD_HEIGHT, 0, 0, image);
}


/*****************************************************************************
 *
 * Description:
 *    Show which player may currently play
 *
 ****************************************************************************/
void showCurrentPlayer(void)
{
   lcdGotoxy(53, STATUSLINE_Y);
   if(cCurrentPlayer == 1)
   {
      lcdColor(COLOR_PLAYER1, 0xFF);
      lcdPuts("P1");
   }
   else
   {
      lcdColor(COLOR_PLAYER2, 0xFF);
      lcdPuts("P2");
   }
}


/*****************************************************************************
 *
 * Description:
 *    Hide the status which player may currently play
 *
 ****************************************************************************/
void hideCurrentPlayer(void)
{
   lcdGotoxy(53, STATUSLINE_Y);
   lcdColor(GAME_BKG_COLOR_OK, GAME_BKG_COLOR_OK);
   lcdPuts("  ");
}


/*****************************************************************************
 *
 * Description:
 *    Show each players score
 *
 ****************************************************************************/
void showScore(void)
{
   tU8 i;

   lcdGotoxy(1, STATUSLINE_Y);
   lcdColor(GAME_BKG_COLOR_OK, COLOR_PLAYER1);
   lcdPuts("P1:");
   i = 0;
   if(cScorePlayer1 > 9)
      arText[i++] = (cScorePlayer1 / 10) + '0';
   arText[i++] = (cScorePlayer1 % 10) + '0';
   arText[i++] = 0;
   lcdPuts(arText);

   lcdGotoxy(85, STATUSLINE_Y);
   lcdColor(GAME_BKG_COLOR_OK, COLOR_PLAYER2);
   lcdPuts("P2:");
   i = 0;
   if(cScorePlayer2 > 9)
      arText[i++] = (cScorePlayer2 / 10) + '0';
   arText[i++] = (cScorePlayer2 % 10) + '0';
   arText[i++] = 0;
   lcdPuts(arText);
}


/*****************************************************************************
 *
 * Description:
 *    Draw a card dependent on its status at field (row, col)
 *
 ****************************************************************************/
void redrawCard(tU8 row, tU8 col)
{
   tU8 cStatus;

   cStatus = arCards[row][col].status;
   if(cStatus & CARDSELECTED)
   {
      selectField(row, col);
   }
   else
   {
      unselectField(row, col);
   }

   if( (cStatus & CARDPRESENT) == 0 )
   {
      showEmptyField(row, col);
   }
   else
   {
      if(cStatus & CARDHIDDEN)
         showHiddenField(row, col);
      else
         showImage(row, col, arCards[row][col].image);
   }
}


/*****************************************************************************
 *
 * Description:
 *    Handle keypress UP, unselect current field, select next field
 *
 ****************************************************************************/
void keyUp(void)
{
   arCards[cXpos][cYpos].status &= (~CARDSELECTED);
   redrawCard(cXpos, cYpos);
   if(cYpos > 0)
      cYpos--;
   else
      cYpos = MAX_COLUMN;
   arCards[cXpos][cYpos].status |= CARDSELECTED;
   redrawCard(cXpos, cYpos);
}


/*****************************************************************************
 *
 * Description:
 *    Handle keypress DOWN, unselect current field, select next field
 *
 ****************************************************************************/
void keyDown(void)
{
   arCards[cXpos][cYpos].status &= (~CARDSELECTED);
   redrawCard(cXpos, cYpos);
   if(cYpos < MAX_COLUMN)
      cYpos++;
   else
      cYpos = 0;
   arCards[cXpos][cYpos].status |= CARDSELECTED;
   redrawCard(cXpos, cYpos);
}


/*****************************************************************************
 *
 * Description:
 *    Handle keypress RIGHT, unselect current field, select next field
 *
 ****************************************************************************/
void keyRight(void)
{
   arCards[cXpos][cYpos].status &= (~CARDSELECTED);
   redrawCard(cXpos, cYpos);
   if(cXpos < MAX_ROW)
      cXpos++;
   else
      cXpos = 0;
   arCards[cXpos][cYpos].status |= CARDSELECTED;
   redrawCard(cXpos, cYpos);
}


/*****************************************************************************
 *
 * Description:
 *    Handle keypress LEFT, unselect current field, select next field
 *
 ****************************************************************************/
void keyLeft(void)
{
   arCards[cXpos][cYpos].status &= (~CARDSELECTED);
   redrawCard(cXpos, cYpos);
   if(cXpos > 0)
      cXpos--;
   else
      cXpos = MAX_ROW;
   arCards[cXpos][cYpos].status |= CARDSELECTED;
   redrawCard(cXpos, cYpos);
}


/*****************************************************************************
 *
 * Description:
 *    Handle keypress CENTER, show selected card, check for matching cards
 *
 ****************************************************************************/
void keyCenter(void)
{
   tU8 anyKey;
   static tU8 cCardSelected = 0;

   if( (arCards[cXpos][cYpos].status & CARDPRESENT) == 0 )
      return;
   if(cCardSelected)
   {     // one card already shown
      if( (cXpos == cLastXpos) && (cYpos == cLastYpos) )
         return;
      arCards[cXpos][cYpos].status &= (~CARDHIDDEN);
      redrawCard(cXpos, cYpos);
      do // wait for key to continue (up/down/left/right)
      {
         osSleep(1);
         anyKey = checkKey();
      }
      while( (anyKey == KEY_NOTHING) || (anyKey == KEY_CENTER) );
      if(arCards[cXpos][cYpos].image == arCards[cLastXpos][cLastYpos].image)
      {   // cards match
         arCards[cLastXpos][cLastYpos].status &= (~CARDPRESENT);
         redrawCard(cLastXpos, cLastYpos);
         arCards[cXpos][cYpos].status &= (~CARDPRESENT);
         redrawCard(cXpos, cYpos);
         if(cCurrentPlayer == 1)
            cScorePlayer1++;
         else
            cScorePlayer2++;
         showScore();
         cRemainingPairs--;
         if(cRemainingPairs == 0)
         {
            gameStatus = GAME_OVER;
         }
      }
      else
      {   // cards do not match
         arCards[cLastXpos][cLastYpos].status |= CARDHIDDEN;
         redrawCard(cLastXpos, cLastYpos);
         arCards[cXpos][cYpos].status |= CARDHIDDEN;
         redrawCard(cXpos, cYpos);
         cCurrentPlayer++;
         if(cCurrentPlayer > 2)
            cCurrentPlayer = 1;
         showCurrentPlayer();
      }
      cCardSelected = 0;
      redrawCard(cXpos, cYpos);
   }
   else
   {     // first card to show
      arCards[cXpos][cYpos].status &= (~CARDHIDDEN);
      redrawCard(cXpos, cYpos);
      cCardSelected = 1;
   }
   cLastXpos = cXpos;
   cLastYpos = cYpos;
}


/*****************************************************************************
 *
 * Description:
 *    Draw game background and game board, initialize all variables
 *
 ****************************************************************************/
static void
setupGame (void)
{
  int row,col;
  tU8 cnt = 0;
  tU8 cImages;
  tU8 *pImage = arImages;
  tU8 i;
  tU8 cValue;

  //initialize random generator and reset score
  srand(ms);

  for(col = 0; col < COLUMNS; col++)
  {
     for(row = 0; row < ROWS; row++)
     {
        arCards[row][col].status = CARDPRESENT | CARDHIDDEN;
        arCards[row][col].image = 0;
     }
  }
  row = 0;
  col = 0;
  cnt = 0;
  for(cImages = 0; cImages < COLUMNS*ROWS; cImages++)
  {
     cValue = rand() % 100;
     cValue++;

     for(i = 0; i < cValue; i++)
     {
        do
        {
           if(row < MAX_ROW)
              row++;
           else
           {
              row = 0;
              if(col < MAX_COLUMN)
                 col++;
              else
                 col = 0;
           }
        }
        while(arCards[row][col].image != 0);
     }

     arCards[row][col].image = pImage;
     cnt++;
     if(cnt == 2)
     {
        cnt = 0;
        pImage += (CARD_WIDTH*CARD_HEIGHT);
     }
  }
  
  lcdClrscr();
  for(col = 0; col < COLUMNS; col++)
  {
     for(row = 0; row < ROWS; row++)
     {
        redrawCard(row, col); 
     }
  }
  cScorePlayer1 = 0;
  cScorePlayer2 = 0;
  cCurrentPlayer = 1;
  cRemainingPairs = ROWS*COLUMNS/2;
  cXpos = 0;
  cYpos = 0;
  arCards[cXpos][cYpos].status |= CARDSELECTED;
  redrawCard(cXpos, cYpos);

  showScore();
  showCurrentPlayer();
}



/*****************************************************************************
 *
 * Description:
 *    Load a new board from storage
 *
 ****************************************************************************/
static void
showPreStartupScreen(void)
{
  // clear screen
  lcdColor(GAME_BKG_COLOR_OK,BOARD_GRID_COLOR);
  lcdClrscr();
  lcdRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, GAME_BKG_COLOR_OK); 

#define CHAR_HEIGHT 14
#define CHAR_WIDTH   8  
#define CENTER_X(numchars) ((SCREEN_WIDTH - numchars*CHAR_WIDTH)/2)

  // print message
  lcdGotoxy(CENTER_X(12), SCREEN_HEIGHT/2 - CHAR_HEIGHT-2);
  lcdPuts("Press button");  
  lcdGotoxy(CENTER_X(8), SCREEN_HEIGHT/2 + 2);
  lcdPuts("to start");  

}


/*****************************************************************************
 *
 * Description:
 *    Implements memory game
 *    
 ****************************************************************************/
void playMemory(void)
{
  tU8 anyKey;

  gameStatus = GAME_NOT_STARTED;

/*
  showPreStartupScreen();
  
  do
  {
     osSleep(1);
     anyKey = checkKey();
  }
  while(anyKey == KEY_NOTHING);

  setupGame();
  gameStatus = GAME_RUNNING;
*/

  while(gameStatus != GAME_END)
  {
    if(gameStatus == GAME_NOT_STARTED)
    {
        showPreStartupScreen();
  
        do
        {
           osSleep(1);
           anyKey = checkKey();
        }
        while(anyKey == KEY_NOTHING);

        setupGame();
        gameStatus = GAME_RUNNING;
    }
    
    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);
        }
        break;
      
      case GAME_OVER:
      {
        tMenu menu;
        arCards[cXpos][cYpos].status &= (~CARDSELECTED);
        redrawCard(cXpos, cYpos);
        hideCurrentPlayer();
        menu.xPos = 20;
        menu.yPos = 40;
        menu.xLen = 6+(10*8);
        menu.yLen = 4*14;
        menu.noOfChoices = 2;
        menu.initialChoice = 0;
        if(cScorePlayer1 > cScorePlayer2)
        {
           menu.pHeaderText = "Winner: P1";
           menu.headerColor = COLOR_PLAYER1;
        }
        else
        {
           menu.pHeaderText = "Winner: P2";
           menu.headerColor = COLOR_PLAYER2;
        }
        menu.headerTextXpos = 22;
        menu.pChoice[0] = "New game";
        menu.pChoice[1] = "End game";
        menu.bgColor       = 0;
        menu.borderColor   = 0x6d;
        menu.choicesColor  = 0xfd;
        menu.selectedColor = 0xe0;
        
        switch(drawMenu(menu))
        {
          case 0: //Restart game
            gameStatus = GAME_NOT_STARTED;
            break;

          case 1: 
             gameStatus = GAME_END;
             break;                       //End game

          default: 
             break;
        }
      }
        break;
        
      default:
        gameStatus = GAME_END;
        break;
    }
  }
}

