/***** All content © 2010 DigiPen (USA) Corporation, all rights reserved. *****/
/*! \file Amazons.c
** \author Nathan Williams
** \par email: nwilliam\@digipen.edu
** \date 04/11/2010
** \brief Implementation of Amazons.
**
** References:
** - http://ticc.uvt.nl/icga/games/amazons/
** - http://www.csun.edu/~lorentz/amazon.htm
** - http://en.wikipedia.org/wiki/Game_of_the_Amazons
** - http://swiss2.whosting.ch/jenslieb/amazong/amazong.html
**
** Amazons can be played on boards of arbitrary size, however, it is usually
** played on a 10x10 board. The two players, white and black, are each given
** given four amazons in predefined locations.
** - White starting amazon locations: a4, d1, g1, j4
** - Black starting amazon locations: a7, d10, g10, j7
** - White moves first. Each move contains two mandatory parts: The player's
** amazon moves like a chess queen, any number of squares in a horizontal,
** vertical, or diagonal line. Once it gets to its final position, the
** amazon throws an arrow that also moves like a chess queen. The arrow
** permanently blocks the board square it lands in.
** - Both the amazon and the arrow it throws must move along a line that is
** not obstructed by another amazon or an arrow.
** - The last player able to complete a move wins the game (ie, no draws).
**
** Standard starting board (W = white amazon, B = black amazon)
** 10 - - - B - - B - - -
** 9 - - - - - - - - - -
** 8 - - - - - - - - - -
** 7 B - - - - - - - - B
** 6 - - - - - - - - - -
** 5 - - - - - - - - - -
** 4 W - - - - - - - - W
** 3 - - - - - - - - - -
** 2 - - - - - - - - - -
** 1 - - - W - - W - - -
** a b c d e f g h i j
**
**
** Plugin Specifics:
** Move Notation: <start_loc>-<dest_loc>,<arrow_loc>
** - This plugin uses the PGN like format, which is the same as used on
** Wikipedia, Gamazons, and Amazong (to name a few).
** - For ease of interaction, the positions can be in any case and
** there can be varying amounts of whitespace.
** - A few example moves:
** a4-d4,g7 (White: move queen from A4 to D4, throw arrow to G7)
** j7-g4,j7 (Black: move queen from J7 to G4, throw arrow to J7)
** d4-d9,g9 (White: move queen from D4 to D9, throw arrow to G9
** d10-f8,f2 (Black: move queen from D10 to F8, throw arrow to F2)
** Board Representation:
** - A simple array of n*n characters, indexed so top left of board is
** zero. That is, a10 = 0 and j1 = 99.
** - Empty cell is integer 0, white location is 'W', black location is 'B',
** white arrow is 'w', and black arrow is 'b'.
**
** Possible To Do's:
** - Game option for setting board size
** - Game option for setting first player
** - Game option for loading a board (ex: with blocked spaces)
**
*******************************************************************************/
#include <string.h> /* strlen */
#include <stdlib.h> /* calloc, free */
#include <math.h> /* abs */
#include "PluginCommon.h"
#include "PluginInterface.h"
#define BOARD_COLS 10
#define BOARD_ROWS 10
#define WHITE 1
#define BLACK 2
struct Game
{
int mNextPlayer;
void* mPlayers[2];
enum PLUGIN_GAME_STATUS mStatus;
char mBoard[BOARD_COLS*BOARD_ROWS];
};
/* Does no validation. Assumes 10x10 board. Returns -1 on invalid. */
static int GetIndex(char col, int row)
{
/* make them zero based */
col = col - 'A';
--row;
if(col < 0 || col >= BOARD_COLS || row < 0 || row >= BOARD_ROWS)
return -1;
return (row*BOARD_COLS + col);
}
static int D4Distance(char c1, int r1, char c2, int r2)
{
c1 -= 'A'; --r1;
c2 -= 'A'; --r2;
return abs(c1 - c2) + abs(r2 - r1);
}
static int MoveIsValid(const char* board, char c1, int r1, char c2, int r2)
{
int colInc, rowInc, index;
/* Basic rule checks */
if( (c1 != c2) && (r1 != r2) && (D4Distance(c1, r1, c2, r2) % 2 != 0) )
return 1;
colInc = (c2 != c1) ? (c2 > c1 ? 1 : -1) : 0;
rowInc = (r2 != r1) ? (r2 > r1 ? 1 : -1) : 0;
do
{
c1 += colInc;
r1 += rowInc;
index = GetIndex(c1, r1);
if(board[index] != 0)
return 1;
}
while(c1 != c2 || r1 != r2);
return 0;
}
static char GetPlayerSymbol(int player)
{
if(player == WHITE) return 'W';
if(player == BLACK) return 'B';
return -1;
}
static char GetArrowSymbol(int player)
{
if(player == WHITE) return 'w';
if(player == BLACK) return 'b';
return -1;
}
static int CanMoveFromPos(const char* board, char col, int row)
{
int i;
int canMove = 0;
int mC[8], mR[8];
/* left, right, up, down, left/up, left/down, right/up, right/down */
mC[0]=col - 1; mC[1]=col + 1; mC[2]=col; mC[3]=col; mC[4]=col - 1; mC[5]=col - 1; mC[6]=col + 1; mC[7]=col + 1;
mR[0]=row; mR[1]=row; mR[2]=row + 1; mR[3]=row - 1; mR[4]=row + 1; mR[5]=row - 1; mR[6]=row + 1; mR[7]=row - 1;
for(i = 0; (i < 8) && (canMove == 0); ++i)
{
if(mC[i] >= 'A' && mC[i] < ('A'+BOARD_COLS) && mR[i] >= 1 && mR[i] < BOARD_ROWS)
canMove = (board[ GetIndex(mC[i], mR[i]) ] == 0);
}
return canMove;
}
int Init(void)
{
/* Nothing to do */
return 0;
}
void Shutdown(void)
{
}
const char* Name(void)
{
return "Amazons";
}
const char* Author(void)
{
return "nlw";
}
const char* Type(void)
{
return "Amazons";
}
const char* Version(void)
{
return "0.1";
}
const char* APIVersion(void)
{
return "0.1";
}
int NumberOfPlayers(void)
{
return 2;
}
void* CreateGame(void)
{
struct Game* newGame = calloc(1, sizeof(struct Game));
if(newGame)
newGame->mStatus = PLUGIN_GAME_STATUS_CREATED;
return newGame;
}
int SetGameOption(void* igame, const char* option)
{
/* No options for this game */
(void)igame;
(void)option;
return 1;
}
int AddPlayer(void* igame, void* player)
{
struct Game* game = igame;
if(game->mPlayers[0] == NULL)
game->mPlayers[0] = player;
else if(game->mPlayers[1] == NULL)
game->mPlayers[1] = player;
else
return 1;
return 0;
}
int RemovePlayer(void* igame, void* player)
{
struct Game* game = igame;
if(game->mPlayers[0] == player)
game->mPlayers[0] = NULL;
else if(game->mPlayers[1] == player)
game->mPlayers[1] = NULL;
else
return 1;
if(game->mStatus == PLUGIN_GAME_STATUS_PLAYING)
game->mStatus = PLUGIN_GAME_STATUS_PAUSED;
return 0;
}
int PlayerCount(void* igame)
{
struct Game* game = igame;
return (game->mPlayers[0] != 0) + (game->mPlayers[1] != 0);
}
void* GetPlayer(void* igame, int playerNumber)
{
struct Game* game = igame;
if(playerNumber == WHITE || playerNumber == BLACK)
return game->mPlayers[playerNumber-1];
return NULL;
}
int GetPlayerNumber(void* igame, void* player)
{
struct Game* game = igame;
if(game->mPlayers[0] == player)
return 1;
if(game->mPlayers[1] == player)
return 2;
return 0;
}
int StartGame(void* igame)
{
struct Game* game = igame;
if(game->mPlayers[0] == NULL || game->mPlayers[1] == NULL)
return 1;
/* Init board */
memset(game->mBoard, 0, BOARD_COLS*BOARD_ROWS);
/* Starting positions */
game->mBoard[ GetIndex('A', 4) ] = 'W';
game->mBoard[ GetIndex('D', 1) ] = 'W';
game->mBoard[ GetIndex('G', 1) ] = 'W';
game->mBoard[ GetIndex('J', 4) ] = 'W';
game->mBoard[ GetIndex('A', 7) ] = 'B';
game->mBoard[ GetIndex('D',10) ] = 'B';
game->mBoard[ GetIndex('G',10) ] = 'B';
game->mBoard[ GetIndex('J', 7) ] = 'B';
game->mNextPlayer = WHITE;
game->mStatus = PLUGIN_GAME_STATUS_PLAYING;
return 0;
}
void* GetNextPlayer(void* igame)
{
struct Game* game = igame;
return GetPlayer(igame, game->mNextPlayer);
}
int ValidateMove(void* igame, void* player, const char* move)
{
struct Game* game = igame;
char* board = game->mBoard;
int index = 0;
char m[6] = {0};
const char *it = move;
int arrowSymbol = GetArrowSymbol(game->mNextPlayer);
int playerSymbol = GetPlayerSymbol(game->mNextPlayer);
int startIndex, destIndex, arrowIndex, x, y;
if(move == NULL || player != GetNextPlayer(igame) || game->mStatus != PLUGIN_GAME_STATUS_PLAYING)
return -1;
while(*it && index < 6)
{
if(*it == ' ' || *it == '-' || *it == ',')
{
++it; /* skip */
}
else if(*it >= '0' && *it <= '9')
{
int mul = 1;
while(*it && *it >= '0' && *it <= '9')
{
m[index] *= mul;
m[index] += (*it - '0');
mul *= 10;
++it;
}
++index;
}
else if(*it >= 'A' && *it <= 'Z')
{
m[index++] = *it++;
}
else if(*it >= 'a' && *it <= 'z')
{
m[index++] = *it++ - 32; /* toupper */
}
else
{
/* Invalid character */
return -1;
}
}
if(index != 6)
return -1; /* partial move */
startIndex = GetIndex(m[0], m[1]);
destIndex = GetIndex(m[2], m[3]);
arrowIndex = GetIndex(m[4], m[5]);
if(startIndex < 0 || destIndex < 0 || arrowIndex < 0)
return -1; /* invalid board positions in move */
/* Move syntactically valid, now check against board */
/* Moving from owned pos */
if(board[startIndex] != GetPlayerSymbol(game->mNextPlayer))
return -1;
/* Basic bad movies that don't fit in checks below) */
if((startIndex == destIndex) || (destIndex == arrowIndex))
return -1;
/* Check amazon movement */
if(MoveIsValid(board, m[0], m[1], m[2], m[3]))
return 1;
/* Check arrow movement */
board[startIndex] = 0; /* arrow shouldn't be blocked by where we started */
if((arrowIndex != startIndex) && MoveIsValid(board, m[2], m[3], m[4], m[5]))
{
board[startIndex] = playerSymbol;
return 1;
}
/* Valid move. Update board info */
board[startIndex] = 0;
board[destIndex] = playerSymbol;
board[arrowIndex] = arrowSymbol;
/* Check game over (new player cannot move) */
game->mNextPlayer = (game->mNextPlayer == WHITE) ? BLACK : WHITE;
playerSymbol = GetPlayerSymbol(game->mNextPlayer);
index = 0; /* bool, move left */
for(x = 0; (x < BOARD_COLS) && (index == 0); ++x)
{
char col = 'A' + x;
for(y = 0; (y < BOARD_ROWS) && (index == 0); ++y)
{
if(board[ GetIndex(col, 1 + y) ] == playerSymbol)
index |= CanMoveFromPos(board, col, y + 1);
}
}
/* current player has no moves, last player won */
if(index == 0)
{
game->mNextPlayer = (game->mNextPlayer == WHITE) ? BLACK : WHITE;
game->mStatus = PLUGIN_GAME_STATUS_OVER_WINNER;
}
return 0;
}
int GetGameStatus(void* igame)
{
struct Game* game = igame;
return game->mStatus;
}
void DestroyGame(void* igame)
{
free(igame);
}