ref: dd155881cf69b09d8e33ad2f4d6ef5a96eaa097c
dir: /engine.c/
#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] = 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] = 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] == Glenda || game->ogrid[x][y] == Wall);
game->ogrid[x][y] = Wall;
}
/* ensure we don't make an unfair map */
if(checkstate(game) == Won && findmin(game, Pt(SzX/2, SzY/2)) > 100)
goto setmap;
memcpy(game->grid, game->ogrid, sizeof(game->grid));
game->state = Start;
game->turn = 0;
}
/*
* Returns position
*/
Point
movedir(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
pointdir(Point src, Point dst)
{
Point p;
/* hacky */
for(int i = NE ; i <= NW ; i++)
{
p = movedir(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 = movedir(dir, src);
if(game->grid[dst.x][dst.y] == Wall)
return Wall;
if(game->networked)
return netmove(dir);
game->grid[dst.x][dst.y] = Glenda;
game->grid[src.x][src.y] = 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] == Wall)
return Wall;
if(game->grid[p.x][p.y] == Glenda)
return 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] = Wall;
/* assumes defenders start game first */
if(game->state == Start)
game->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] != Wall && game->grid[x][y] != 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] != Wall && game->grid[x][y] != 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 = movedir(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 = movedir(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 = 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 = 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 = Won;
if(p.x == 0 || p.x == SzX-1 || p.y == 0 || p.y == SzY-1)
game->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 = Start;
}
/*
* Undos last move
*/
void
undo(Game *game)
{
int g[SzX][SzY];
if(game->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 == Playing || game->state == Start;
}