/*
**********************************************************************
**
**  MiniDD2.cpp
**
**  Author:  Bary W Pollack
**
**  A simple interactive "Miniature Dungeons & Dragons" game
**
**  This program plays Mini D&D on a rectangular board.  A single
**  cell containing "Gold" is randomly placed on the board.
**  The player's job is to find the Gold by exploring the board,
**  one cell at a time, moving North, South, East, West.
**
**  In this version of the game, the Human plays against the Machine.
**  The machine starts in the opposite corner, and moves randomly.
**
**  Modified:  05/02/99  -  created
**
**********************************************************************
*/

#include <iostream.h>
#include <stdlib.h>
#include <ctype.h>
#include <time.h>       // for seeding the random number generator

//  Random() and Randomize() have been "borrowed" from stdlib.h
//  since some C++ IDE's don't contain one or the other routine.
//
//      Random(num) returns a random int in the range:  0 - (num-1)
//      Randomize seeds the pseudo-random number generator

#define Random(num) (int)(((long)rand()*(num))/(RAND_MAX+1))

void Randomize (void) { srand ((unsigned) time (NULL)); }

/*
**********************************************************************
**  Global declarations: constants and types
**********************************************************************
*/

#ifndef WIN32                       // true for MSVC; false for Borland
enum bool { false, true };          // setup the Boolean type
#endif

const int BRD_HEIGHT = 4;           // height of the board
const int BRD_WIDTH  = 6;           // width of the board

enum CELL                           // contents of the board
{                                   //   (for display purposes)
    EMPTY       = '_',              // empty cell - unexplored
    HUM_PASSED  = 'X',              // human passed thru this cell
    MAC_PASSED  = 'Y',              // machine passed thru this cell
    HUM_HEAD    = '?',              // indicates human's position
    MAC_HEAD    = '&'               // indicates machine's position
};

typedef CELL BOARD[BRD_HEIGHT+1][BRD_WIDTH+1];

struct Coord
{
    int  y;                         // y coordinate 1..BRD_HEIGHT
    int  x;                         // x coordinate 1..BRD_WIDTH
};

const Coord humStartCoord = { 1, 1};        // starting coordinates for
const Coord macStartCoord = { BRD_HEIGHT, BRD_WIDTH };  // both players

/*
**********************************************************************
**  Class declarations for Player, Human and Machine
**********************************************************************
**  A Player is a "person" having a position on the board, and symbols
**  that represent his path and head.
**
**  GetMove requests and obtains the Player's next move.
**
**  EnterMove enters this move onto the board.
**
**  GetCoord is a public mechanism so that the game can determine
**  where each player's head currently is located on the board.
**
**  The Human and the Machine both have their own constructors (to
**  establish their existence), and GetMove (because the Human's
**  behavior is different from that of the Machine in this regard.
**********************************************************************
*/

class Player
{
  public:
    Player (const Coord aCoord, const CELL thePassedSym, const CELL theHeadSym);
    Coord GetMove (BOARD board);
    void EnterMove (BOARD board, const Coord aNewCoord);
    Coord GetCoord (void) const { return aCoord; }

  private:
    Coord   aCoord;
    CELL    thePassedSym;
    CELL    theHeadSym;
};

class Human : public Player
{
  public:
    Human (void);
    Coord GetMove (BOARD board);
};

class Machine : public Player
{
  public:
    Machine (void);
    Coord GetMove (BOARD board);
};

/*
**********************************************************************
**  Class declarations for Gold
**********************************************************************
**  Gold is a cell that is placed on the board in a fixed random 
**  location when the constructor is called.
**
**  IsAt is the Gold's way to tell you if it is located at (y,x).
**
**  Display is a routine used during development that allows you to
**  see the (private) Gold data.
**********************************************************************
*/

class Gold
{
  public:
    Gold (void);
    bool IsAt (const Coord aCoord) const;
    void Display (void) const;

  private:
    Coord   goldCoords;
};

/*
**********************************************************************
**  Class declarations for MiniDDGame
**********************************************************************
**  MiniDDGame is a game played on a rectangular board that contains
**  a hidden pot of Gold.  The player must find the Gold.
**
**  Play currently plays a single game.  Play is responsible for 
**  acquiring movement instructions from the user; updating the board,
**  tracking the player's path; etc.
**
**  DisplayState is used by Play to display the current board state.
**********************************************************************
*/

bool ValidMove (const BOARD board, const Coord aCoord);

class MiniDDGame
{
  public:
    MiniDDGame (void);
    void Play (void);
    void DisplayState (const char *sMsg) const;

  private:
    BOARD   board;
    Human   theHuman;
    Machine theMachine;
    Gold    theGold;
};

/*
**********************************************************************
**  Implementation of classes Player, Human, Machine
**********************************************************************
**  Constructors - establish coordinates, passed symbol and head symbol
**********************************************************************
*/

Player::Player (const Coord aCoord, const CELL thePassedSym, const CELL theHeadSym) :
    aCoord (aCoord), thePassedSym (thePassedSym), theHeadSym (theHeadSym)
{
}

Human::Human (void) : Player (humStartCoord, HUM_PASSED, HUM_HEAD)
{
}

Machine::Machine (void) : Player (macStartCoord, MAC_PASSED, MAC_HEAD)
{
}

/*
**********************************************************************
**  GetMoves - ask for Human's move; the Machine moves randomly.
**             Of course, a single more general routine could be
**             built, and then specialized for each player...
**             I just didn't bother...    Next update...   -grin-
**********************************************************************
*/

Coord Human::GetMove (BOARD board)
{
    Coord aNewCoord;
    bool  bMoveIsOK = false;

    do
    {
        aNewCoord = GetCoord ();
        char  cDirection;
        cout << "Direction to move (N,S,E,W): ";
        cin >> cDirection;

        switch (toupper (cDirection))
        {
            case '.':   // fall thru to 'Q'
            case 'Q':   aNewCoord.y = 0; bMoveIsOK = true;  break;
            case 'N':   --aNewCoord.y;                      break;
            case 'S':   ++aNewCoord.y;                      break;
            case 'E':   ++aNewCoord.x;                      break;
            case 'W':   --aNewCoord.x;                      break;
            default:    aNewCoord.y = 0;                    break;
        }
        
        if (! bMoveIsOK)
        {
            if (ValidMove (board, aNewCoord))
                bMoveIsOK = true;
            else
                cout << endl << "Invalid move" << endl << endl;
        }

    } while (! bMoveIsOK);

    return aNewCoord;
}


Coord Machine::GetMove (BOARD board)
{
    Coord aNewCoord;
    bool  bMoveIsOK = false;

    do
    {
        aNewCoord = GetCoord ();
        int nDirection = Random (4);

        switch (nDirection)
        {
            case 0: --aNewCoord.y;  break;
            case 1: ++aNewCoord.y;  break;
            case 2: ++aNewCoord.x;  break;
            case 3: --aNewCoord.x;  break;
        }
        
        if (ValidMove (board, aNewCoord))
            bMoveIsOK = true;

    } while (! bMoveIsOK);

    return aNewCoord;
}

/*
**********************************************************************
**  Implementation of class Gold
**********************************************************************
**  Constructor - randomly places the Gold on the board.
**                The Gold's position is HIDDEN from the game)
**********************************************************************
*/

Gold::Gold (void)
{
    goldCoords.y = Random (BRD_HEIGHT) + 1;
    goldCoords.x = Random (BRD_WIDTH)  + 1;
}

/*
**********************************************************************
**  IsAt - returns true if (y,x) is where the Gold is located
**********************************************************************
*/

bool Gold::IsAt (const Coord aCoord) const
{
    bool bIsAt = (aCoord.y == goldCoords.y && aCoord.x == goldCoords.x);

    return bIsAt;
}

/*
**********************************************************************
**  This debug routine was used during development so that we can see
**  where the Gold actually is located.
**********************************************************************
*/

void Gold::Display (void) const
{
    cout << "Gold: (" << goldCoords.y << "," << goldCoords.x << ")" << endl;
}

/*
**********************************************************************
**  Implementation of class MiniDDGame
**********************************************************************
**  The constructor sets up the game.  This includes initialization
**  of the entire board as well as other game-related data such as 
**  the player's initial location, etc.
**********************************************************************
*/

MiniDDGame::MiniDDGame (void)
{
    for (int j = 1; j <= BRD_HEIGHT; j++)
        for (int i = 1; i <= BRD_WIDTH; i++)
            board [j][i] = EMPTY;

    theHuman.EnterMove (board, humStartCoord);
    theMachine.EnterMove (board, macStartCoord);
}

/*
**********************************************************************
**  Play implements one "game:"  repeatedly asking for movement 
**  instructions, updating the board, tracking the player's path, 
**  redisplaying the board, etc., until the Gold is discovered.
**
**  Coordinate convention:  (y x), with y increasing downwards and
**                                      x increasing to the right
**
**                             x -->
**
**                         1 2 3 4 5 6 7
**                      1  X X . . . . .
**                  y   2  . X . . . & Y
**                  |   3  . X ? . . . Y
**                  V   4  . . . . . . Y
**                      5  . . . . . . Y
**
**  Note:  the 0-row and 0-column of the board (array) are "ignored"
**         so that we can use "natural" values for indices.
**
**********************************************************************
*/

void MiniDDGame::Play (void)
{
    for ( ; ; )
    {
        DisplayState ("The current situation:");

        Coord aCoord = theHuman.GetMove (board);
        if (aCoord.y == 0)
            break;
        theHuman.EnterMove (board, aCoord);
        if (theGold.IsAt (theHuman.GetCoord ()))
        {
            DisplayState ("YOU found the GOLD!");
            break;
        }

        aCoord = theMachine.GetMove (board);
        theMachine.EnterMove (board, aCoord);
        if (theGold.IsAt (theMachine.GetCoord ()))
        {
            DisplayState ("The MACHINE found the GOLD!");
            break;
        }
    }
}

/*
**********************************************************************
**  Enters the current move; updates myCoords
**********************************************************************
*/

void Player::EnterMove (BOARD board, const Coord aNewCoord)
{
    board [aCoord.y][aCoord.x] = thePassedSym;
    aCoord = aNewCoord;
    board [aCoord.y][aCoord.x] = theHeadSym;
}

/*
**********************************************************************
**  Display the state of the game as a 2-D tableau
**********************************************************************
*/

void MiniDDGame::DisplayState (const char *sMsg) const
{
    int  j, i;

    cout << endl << endl << sMsg << endl << endl << ' ';

    for (i = 1; i <= BRD_WIDTH; i++)
        cout << ' ' << i;
    cout << endl;

    for (j = 1; j <= BRD_HEIGHT; j++)
    {
        cout << j << ' ' << char (board [j][1]);
        for (i = 2; i <= BRD_WIDTH; i++)
            cout << '|' << char (board [j][i]);
        cout << endl;
    }

    //theGold.Display ();           //FOO - for development only...
    cout << endl << endl;
}

/*
**********************************************************************
**  ValidMove is true if the move is legal; it is false otherwise
**********************************************************************
*/

bool ValidMove (const BOARD board, const Coord aCoord)
{
    CELL myLocation = board [aCoord.y][aCoord.x];

    bool bValidMove = (1 <= aCoord.y && aCoord.y <= BRD_HEIGHT)
                   && (1 <= aCoord.x && aCoord.x <= BRD_WIDTH)
                   && (myLocation == EMPTY      ||
                       myLocation == HUM_PASSED ||
                       myLocation == MAC_PASSED ||
                       myLocation == HUM_HEAD   ||
                       myLocation == MAC_HEAD);

    return bValidMove;
}

/*
**********************************************************************
**  The main routine plays multiple games of Gold.
**********************************************************************
*/

int main (void)
{
    Randomize ();

    cout << "* * M I N I - D U N G E O N S - A N D - D R A G O N S * *" << endl;

    char ch;
    do              // play one game
    {
        MiniDDGame game;
        game.Play ();

        do          // ask user about playing again;
        {           // again, this could be a separate routine...
            cout << "Game Over!" << endl << endl
                 << "Would you like to play again (y/n)? ";
            cin >> ch;
            ch = char (tolower (ch));
        } while (ch != 'y' && ch != 'n');

    } while (ch == 'y');

    cout << endl << "Thanks for playing..." << endl << endl;

    return 0;
}

/*
**********************************************************************
**  The output...
**********************************************************************

* * M I N I - D U N G E O N S - A N D - D R A G O N S * *

The current situation:

  1 2 3 4 5 6
1 ?|_|_|_|_|_
2 _|_|_|_|_|_
3 _|_|_|_|_|_
4 _|_|_|_|_|&

Direction to move (N,S,E,W): s

The current situation:

  1 2 3 4 5 6
1 X|_|_|_|_|_
2 ?|_|_|_|_|_
3 _|_|_|_|_|&
4 _|_|_|_|_|Y

Direction to move (N,S,E,W): s

The current situation:

  1 2 3 4 5 6
1 X|_|_|_|_|_
2 X|_|_|_|_|_
3 ?|_|_|_|_|Y
4 _|_|_|_|_|&

Direction to move (N,S,E,W): s

The current situation:

  1 2 3 4 5 6
1 X|_|_|_|_|_
2 X|_|_|_|_|_
3 X|_|_|_|_|Y
4 ?|_|_|_|&|Y

Direction to move (N,S,E,W): e

The current situation:

  1 2 3 4 5 6
1 X|_|_|_|_|_
2 X|_|_|_|_|_
3 X|_|_|_|_|Y
4 X|?|_|&|Y|Y

Direction to move (N,S,E,W): n

The current situation:

  1 2 3 4 5 6
1 X|_|_|_|_|_
2 X|_|_|_|_|_
3 X|?|_|_|_|Y
4 X|X|&|Y|Y|Y

Direction to move (N,S,E,W): n

The current situation:

  1 2 3 4 5 6
1 X|_|_|_|_|_
2 X|?|_|_|_|_
3 X|X|_|_|_|Y
4 X|&|Y|Y|Y|Y

Direction to move (N,S,E,W): n

The current situation:

  1 2 3 4 5 6
1 X|?|_|_|_|_
2 X|X|_|_|_|_
3 X|X|_|_|_|Y
4 &|Y|Y|Y|Y|Y

Direction to move (N,S,E,W): e

The current situation:

  1 2 3 4 5 6
1 X|X|?|_|_|_
2 X|X|_|_|_|_
3 &|X|_|_|_|Y
4 Y|Y|Y|Y|Y|Y

Direction to move (N,S,E,W): s

YOU found the GOLD!

  1 2 3 4 5 6
1 X|X|X|_|_|_
2 X|X|?|_|_|_
3 &|X|_|_|_|Y
4 Y|Y|Y|Y|Y|Y

Game Over!

Would you like to play again (y/n)? n

Thanks for playing...

******/
