wm: glendy

ref: aa0761be97fc5a0ba0fb80ca364c24d9dbd56c4d
dir: /engine.c/

View raw version
#include "port.h"

#include "util.h"
#include "engine.h"
#include "netclient.h"

/*
 * Sets up a new level and fills it
 */
void
initlevel(Game *game)
{
	int i, cnt, x, y;

	for(x = 0; x < SzX; x++)
		for(y = 0; y < SzY; y++)
			game->ogrid[x][y] = PIECE_PREV;

	switch(game->difficulty)
	{
		case DEasy:
			cnt = 10 + nrand(5);
			break;
		case DMed:
			cnt = 5 + nrand(5);
			break;
		case DHard:
			cnt = 1 + nrand(5);
			break;
		case DImp:
		default:
			cnt = 0;
			break;
	}
	game->ogrid[SzX/2][SzY/2] = PIECE_GLENDA;

setmap:
	for(i = 0 ; i < cnt ; i++)
	{
		/* ensure walls dont overlap with other walls or glenda */
		do
		{
			x = nrand(SzX);
			y = nrand(SzY);
		}while(game->ogrid[x][y] == PIECE_GLENDA || game->ogrid[x][y] == PIECE_WALL);
		game->ogrid[x][y] = PIECE_WALL;
	}
	/* ensure we don't make an unfair map */
	if(checkstate(game) == STATE_WON && findmin(game, Pt(SzX/2, SzY/2)) > 100)
		goto setmap;

	memcpy(game->grid, game->ogrid, sizeof(game->grid));
	game->state = STATE_START;
	game->turn = 0;
}

/*
 * Returns position
 */
Point
pointdir(int dir, Point p)
{
	int x = p.x;
	int y = p.y;
	
	switch(dir)
	{
		case NE: 
			return Pt(x+(y%2?1:0), y-1);
		case E:
			return Pt(x+1, y);
		case SE:
			return Pt(x+(y%2?1:0), y+1);
		case SW:
			return Pt(x+(y%2?0:-1), y+1);
		case W:
			return Pt(x-1, y);
		case NW:
			return Pt(x+(y%2?0:-1), y-1);
		default:
			sysfatal("andrey messed up big time: %d", dir);
	}
}

/*
 * Reverse of movedir, tells the direction for a dst from src
 */
int
finddir(Point src, Point dst)
{
	Point p;
	
	/* hacky */
	for(int i = NE ; i <= NW ; i++)
	{
		p = pointdir(i, src);
		if(p.x == dst.x && p.y == dst.y)
			return i;
	}
	return Err;
}

/*
 * Moves glenda into direction
 */
int
domove(Game *game, int dir)
{
	Point src, dst;
		
	src = findglenda(game);
	dst = pointdir(dir, src);

	if(game->grid[dst.x][dst.y] == PIECE_WALL)
		return PIECE_WALL;

	if(game->networked)
		return netmove(dir);
	
	game->grid[dst.x][dst.y] = PIECE_GLENDA;
	game->grid[src.x][src.y] = PIECE_PREV;

	game->turn++;
	
	/* we shouldn't guess, server should tell us */
	if(game->ptype[1] == Human && !game->networked)
		checkstate(game);
	return Ok;
}

/*
 * Puts a wall inside a point
 */
int
doput(Game *game, Point p)
{
	/* clients are expected to do their own error checking */
	if(p.x > SzX || p.x < 0 || p.y > SzY || p.y < 0)
		return Err;

	if(game->grid[p.x][p.y] == PIECE_WALL)
		return PIECE_WALL;

	if(game->grid[p.x][p.y] == PIECE_GLENDA)
		return PIECE_GLENDA;

	if(game->networked)
		return netput(p.x, p.y);
	
	/* take a copy for undo */
	memcpy(game->pgrid, game->grid, sizeof(game->grid));
	game->grid[p.x][p.y] = PIECE_WALL;


	/* assumes defenders start game first */
	if(game->state == STATE_START)
		game->state = STATE_PLAYING;

	game->turn++;
	/* reset the board scores */
	for(int x = 0; x < SzX; x++)
		for(int y = 0; y < SzY; y++)
			if(game->grid[x][y] != PIECE_WALL && game->grid[x][y] != PIECE_GLENDA)
				game->grid[x][y] = 100;

	/* we need it to check game state, even if not playing with computer */
	if(game->ptype[1] == Computer)
		nextglenda(game);
	else if(game->ptype[1] == Human && !game->networked)
		checkstate(game);

	return Ok;
}

/*
 * Returns glenda's position
 */
Point
findglenda(Game *game)
{
	for(int x = 0; x < SzX; x++)
		for(int y = 0; y < SzY; y++)
			if(game->grid[x][y] == 1000)
				return Pt(x, y);
	
	return Pt(-1, -1);
}

/*
 * the following two routines constitute the "game AI"
 * they score the field based on the number of moves
 * required to reach the edge from a particular point
 * scores > 100 are "dead spots" (this assumes the field 
 * is not larger than ~100*2
 * 
 * routines need to run at least twice to ensure a field is properly
 * scored: there are errors that creep up due to the nature of 
 * traversing the board
 */
int 
score1(Game *game, Point p)
{
	int min = 999;

	if(p.x == 0 || p.x == SzX-1 || p.y == 0 || p.y == SzY-1)
		return 1; 		/* we can always escape from the edges */

	if(findmin(game, p) < min)
		min = findmin(game, p);

	if(min >= 998)
		return 998;
	return 1+min;
}

/*
 * calculates scores of whole graph
 * O(n³), but why?
 */
void
calc(Game *game)
{
	/* assumes SzX = SzY */
	for(int i = 0; i < SzX; i++)
		for(int x = i; x < SzX-i; x++)
			for(int y = i; y < SzY-i; y++)
				if(game->grid[x][y] != PIECE_WALL && game->grid[x][y] != PIECE_GLENDA)
					game->grid[x][y] = score1(game, Pt(x, y));
}

/*
 * finds the node with least score around the p
 */
int
findmin(Game *game, Point p)
{
	Point tmp;
	int next, min = 998;

	for(int dir = NE; dir <= NW; dir++)
	{
		tmp = pointdir(dir, p);
		next = game->grid[tmp.x][tmp.y];
		if(next < min)
			min = next;
	}
	return min;
}

/*
 * Makes a move for glenda
 * only used in single player
 */
void
nextglenda(Game *game)
{
	int min = 999, next, dir, nextdir = 0, count = 0;
	Point tmp, p = findglenda(game);

	if(game->networked)
		return;

	calc(game);
	calc(game);
	calc(game);

	for(dir = NE; dir <= NW; dir++)
	{
		tmp = pointdir(dir, p);
		next = game->grid[tmp.x][tmp.y];
		if(next < min)
		{
			min = next;
			nextdir = dir;
			++count;
		}
		else if(next == min)
			nextdir = (nrand(++count) == 0) ? dir : nextdir;
	}
	if(min > 100)
		game->state = STATE_WON;
	else
		domove(game, nextdir);

	p = findglenda(game);
	if(p.x == 0 || p.x == SzX-1 || p.y == 0 || p.y == SzY-1)
		game->state = STATE_LOST;
}

/*
 * See if game is over
 */
int
checkstate(Game *game)
{
	Point p = findglenda(game);

	calc(game);
	calc(game);
	calc(game);

	if(findmin(game, p) > 100)
		game->state = STATE_WON;
	if(p.x == 0 || p.x == SzX-1 || p.y == 0 || p.y == SzY-1)
		game->state = STATE_LOST;

	return game->state;
}

/*
 * Resets the game position
 */
void
restart(Game *game)
{
	game->turn = 0;
	memcpy(game->grid, game->ogrid, sizeof(game->grid));
	game->state = STATE_START;
}

/*
 * Undos last move
 */
void
undo(Game *game)
{
	int g[SzX][SzY];

	if(game->state == STATE_START || game->turn < 2)
		return;

	/* revert turn counter, can be abused by one side */
	game->turn -= 2;

	/* swap grids */
	memcpy(g, game->grid, sizeof g);
	memcpy(game->grid, game->pgrid, sizeof g);
	memcpy(game->pgrid, g, sizeof g);
}

int
isplaying(Game *game)
{
	return game->state == STATE_PLAYING || game->state == STATE_START;
}