This repository has been archived on 2020-05-27. You can view files and clone it, but cannot push or open issues/pull-requests.
wumpus-agent/wumpus.c

1405 lines
36 KiB
C

/*
* Wum+ -- A wumpus clone with a self-solving intelligent agent.
* Andrew Coleman <mercury at penguincoder dot org>
* Copyleft 2007 -- Licensed under GNU GPLv2
* See http://gnu.org/licenses/old-licenses/gpl-2.0.html for more details.
*
* CSC-4240 Program 2.
*
* This program uses SQLite 3 so many of the database functions use a specific
* callback that is generally prefixed with the name of the calling function
* and suffixed with _callback. These are not used anywhere else and should
* not be used normally.
*
* This is an exceedingly complicated program that could easily be an end of
* semester project or a project to work on throughout the class. This is only
* the second program due. There were also no real usable Wumpus clones in C.
* This one had to be written from naught.
*
* How to compile:
* gcc -Os -Wall -lsqlite3 -lm -o wumplus wumpus.c
*
* How to use to play the game:
* ./wumplus
*
* How to use to make the agent play:
* ./wumplus --agent
*
*/
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>
#include <sqlite3.h>
#include <math.h>
/* Constants for map elements */
#define MAP_SIZE 14
#define MAP_MAXSTEPS 500
#define MAP_PLAYER '@'
#define MAP_EMPTY '.'
#define MAP_WALL '#'
#define MAP_PIT 'P'
#define MAP_WUMPUS 'W'
#define MAP_GOLD 'G'
#define MAP_SUPMUW 'S'
/* Constants for percepts */
#define PERCEPT_BUMP 1
#define PERCEPT_SMELL 2
#define PERCEPT_BREEZE 4
#define PERCEPT_MOO 8
#define PERCEPT_GLITTER 16
#define PERCEPT_DEAD 32
#define PERCEPT_WUMPUS 64
#define PERCEPT_SUPMUW 128
#define PERCEPT_PIT 256
#define PERCEPT_SAFE 512
#define PERCEPT_VISITED 1024
#define PERCEPT_DESTINATION 2048
/* constants for the direction of the move or arrow */
#define DIRECTION_NORTH 1
#define DIRECTION_EAST 2
#define DIRECTION_SOUTH 3
#define DIRECTION_WEST 4
/* constants for scoring */
#define SCORE_MOVE -1
#define SCORE_DEATH -1000
#define SCORE_SHOOT -10
#define SCORE_KILL 500
#define SCORE_GOLD 1000
#define SCORE_FOOD 100
#define SCORE_MIN -1000
/* queue elements for determining a path to a place, needs x,y in one bucket */
typedef struct COORD {
int x, y;
} coordinate;
/*
* struct for managing the whole game
* i wasn't going to make this global, but somehow passing a pointer to one
* of these structs around somehow causes the whole thing to get stupidly
* corrupted and cause segfaults...
*/
struct WUMPLUS {
/* general settings */
int x, y, arrows, percepts, score, steps_taken, dest_x, dest_y;
/* flags */
short int has_food, has_gold, supmuw_neighbors_wumpus, use_agent;
/* the map */
char map[MAP_SIZE][MAP_SIZE];
/* the knowledge base */
sqlite3 *db;
} game;
/* map initialization functions */
int random_map_coordinate();
void random_map_x_y(int *, int *);
void init_game();
/* interaction functions */
void process_percepts();
void unknown_action();
/* player inputs */
void process_player_command(char);
void user_input();
void agent_input();
/* game outputs */
void print_help();
void print_map();
void print_percepts();
void print_score();
/* game helpers */
int player_dead();
int has_won();
int has_lost();
char *delta_coordinates(int *, int *, int);
/* game actions */
void add_score(int);
void action_move(int);
void action_shoot(int);
void action_grab();
void action_quit();
/* agent stuff, yeah, there's a lot... */
void kb_init();
void kb_close();
static int kb_found_callback(void *, int, char **, char **);
int kb_found(int, int, int);
int visited(int, int);
int safe(int, int);
int wall(int, int);
int glitter(int, int);
int smell(int, int);
void kb_insert(int, int, int);
void kb_delete(int, int, int);
void check_corner(int, int, int, int);
void kb_inferrances(int, int);
void kb_tell();
void remove_destination();
int has_destination();
int at_destination();
int at_start();
static int huss_callback(void *, int, char **, char **);
int has_unvisited_safe_squares();
char relative_direction(int, int);
char shortest_path();
int wumpus_nearby(coordinate *);
char kb_ask_action();
char *word_from_percept(int);
static int kb_dump_callback(void *, int, char **, char **);
void kb_dump();
/* list / queue functions (SQL based!) */
void queue_make_empty(const char *);
static int queue_empty_callback(void *, int, char **, char **);
int queue_empty(const char *);
void queue_enqueue(const char *, coordinate *);
static int dequeue_callback(void *, int, char **, char **);
void queue_dequeue(const char *, coordinate *);
/*
* This is the main game loop. Checks for the command line argument and runs
* the input loop.
*/
int main(int argc, char **argv)
{
game.use_agent = 0;
/* check for agent usage */
if(argc == 2 && strcmp(argv[1], "--agent") == 0)
game.use_agent = 1;
printf("Wum+ By Andrew Coleman <mercury at penguincoder dot org>\n");
printf("Scoring:\n");
printf(" Move (%d), Death (%d), Shoot (%d)\n", SCORE_MOVE, SCORE_DEATH,
SCORE_SHOOT);
printf(" Food (%d), Gold (%d), Kill Wumpus(%d)\n", SCORE_FOOD, SCORE_GOLD,
SCORE_KILL);
printf("Available Percepts: [Bump,Smell,Breeze,Moo,Glitter,Dead]\n");
printf("Losing Conditions: Score < %d or Steps > %d or Dead\n",
SCORE_MIN, MAP_MAXSTEPS);
printf("Winning Conditions: Gold and Player in starting position (1,1).\n");
printf("Invocate program with --agent to run as F.O.L. agent\n");
/* initialize game */
srand(time(NULL));
init_game();
/* main game loop */
process_percepts();
do
{
printf("\n");
/* pretty map it if we are using */
if(game.use_agent)
print_map();
/* show the user */
print_percepts();
/* remove this now, otherwise it sticks */
if(game.percepts & PERCEPT_BUMP)
game.percepts ^= PERCEPT_BUMP;
/* show the score */
print_score();
/* get the requested action */
game.use_agent ? agent_input() : user_input();
/* figure out what's going on */
process_percepts();
} while(!has_won() && !has_lost());
/* fin */
action_quit();
return 0;
}
/* Returns a valid random coordinate for the map, not including a wall */
int random_map_coordinate()
{
return (rand() % (MAP_SIZE - 2)) + 1;
}
/* Randomly places the coordinate pair to an empty spot in the map */
void random_map_x_y(int *map_x, int *map_y)
{
int x = 0, y = 0;
do {
x = random_map_coordinate(); y = random_map_coordinate();
} while((x == 1 && y == 1) || game.map[x][y] != MAP_EMPTY);
*map_x = x; *map_y = y;
}
/* Intialize map with randomly placed obstackles */
void init_game()
{
int i, j, num_pits, num_walls, x, y;
/* flag for determining if the supmuw is next to the wumpus. */
game.supmuw_neighbors_wumpus = 0;
/* First create a Clean Slate */
for(j = 0; j < MAP_SIZE; j++)
for(i = 0; i < MAP_SIZE; i++)
game.map[i][j] = MAP_EMPTY;
/* Place player at (1,1) */
game.x = 1;
game.y = 1;
game.has_food = 0;
game.has_gold = 0;
game.arrows = 1;
game.percepts = 0;
game.score = 0;
game.steps_taken = 0;
game.dest_x = -1;
game.dest_y = -1;
/* Create walls around perimeter of map. one loop. figure it out. */
for(i = 0; i < MAP_SIZE; i++)
{
game.map[i][0] = MAP_WALL;
game.map[i][MAP_SIZE - 1] = MAP_WALL;
game.map[0][i] = MAP_WALL;
game.map[MAP_SIZE - 1][i] = MAP_WALL;
}
/* I maximize the number of pits to be 15% the size of the map */
num_pits = (rand() % (int)(MAP_SIZE * MAP_SIZE * .15)) + 1;
for(i = 0; i < num_pits; i++)
{
random_map_x_y(&x, &y);
game.map[x][y] = MAP_PIT;
}
/* set up the interior walls in random locations. max 10% of mapsize */
num_walls = (rand() % (int)(MAP_SIZE * MAP_SIZE * .10)) + 1;
for(i = 0; i < num_walls; i++)
{
random_map_x_y(&x, &y);
game.map[x][y] = MAP_WALL;
}
/* Create Wumpus at Random Location */
random_map_x_y(&x, &y);
game.map[x][y] = MAP_WUMPUS;
/* Randomly place a pot - o - gold */
random_map_x_y(&x, &y);
game.map[x][y] = MAP_GOLD;
/* Place the Supmuw (wumpus cousin) */
random_map_x_y(&x, &y);
game.map[x][y] = MAP_SUPMUW;
/* check to see if the supmuw neighbors the wumpus, used for percepts */
if(game.map[x][y + 1] == MAP_WUMPUS ||
game.map[x][y - 1] == MAP_WUMPUS ||
game.map[x + 1][y] == MAP_WUMPUS ||
game.map[x - 1][y] == MAP_WUMPUS)
{
game.supmuw_neighbors_wumpus = 1;
}
/* set up the database for the KB */
if(game.use_agent)
{
kb_init();
/* let the kb know about the outside walls. */
for(i = 0; i < MAP_SIZE; i++)
{
kb_insert(PERCEPT_BUMP, i, 0);
kb_insert(PERCEPT_BUMP, i, MAP_SIZE - 1);
kb_insert(PERCEPT_BUMP, 0, i);
kb_insert(PERCEPT_BUMP, MAP_SIZE - 1, i);
}
}
}
/*
* Processes the player position and determines if any percepts fire.
* This checks the map given the player's current position and sets all flags
* as appropriate. The only flag not set is the PERCEPT_BUMP flag which _must_
* be set by the action_move() function since the player cannot occupy the
* same square as the wall.
*/
void process_percepts()
{
int x = game.x, y = game.y, flags = 0;
char north = game.map[x][y - 1], south = game.map[x][y + 1];
char east = game.map[x + 1][y], west = game.map[x - 1][y];
/* the move function sets this percept */
int bumped = game.percepts & PERCEPT_BUMP;
if(bumped)
flags |= PERCEPT_BUMP;
/* see if the player is dead, first */
if(game.map[x][y] == MAP_PIT || game.map[x][y] == MAP_WUMPUS ||
(game.map[x][y] == MAP_SUPMUW && game.supmuw_neighbors_wumpus))
{
flags |= PERCEPT_DEAD;
add_score(SCORE_DEATH);
if(game.map[x][y] == MAP_PIT)
printf("You have fallen into a pit!\n");
else
printf("You have been consumed by the beast!\n");
}
if(north == MAP_WUMPUS ||
south == MAP_WUMPUS ||
east == MAP_WUMPUS ||
west == MAP_WUMPUS)
flags |= PERCEPT_SMELL;
if(north == MAP_PIT ||
south == MAP_PIT ||
east == MAP_PIT ||
west == MAP_PIT)
flags |= PERCEPT_BREEZE;
if(north == MAP_SUPMUW ||
south == MAP_SUPMUW ||
east == MAP_SUPMUW ||
west == MAP_SUPMUW)
flags |= PERCEPT_MOO;
if(game.map[x][y] == MAP_GOLD)
flags |= PERCEPT_GLITTER;
if(flags & PERCEPT_MOO && game.supmuw_neighbors_wumpus)
flags |= PERCEPT_SMELL;
game.percepts = flags;
if(game.use_agent)
kb_tell();
}
/* unknown action */
void unknown_action()
{
printf("Do what now? (Unknown action)\n");
}
/* does what the player wants */
void process_player_command(char choice)
{
switch(choice)
{
case '?':
print_help();
break;
case 'q':
action_quit();
break;
case 'n':
case 'k':
action_move(DIRECTION_NORTH);
break;
case 's':
case 'j':
action_move(DIRECTION_SOUTH);
break;
case 'e':
case 'l':
action_move(DIRECTION_EAST);
break;
case 'w':
case 'h':
action_move(DIRECTION_WEST);
break;
case 'N':
action_shoot(DIRECTION_NORTH);
break;
case 'S':
action_shoot(DIRECTION_SOUTH);
break;
case 'E':
action_shoot(DIRECTION_EAST);
break;
case 'W':
action_shoot(DIRECTION_WEST);
break;
case 'g':
action_grab();
break;
default:
unknown_action();
}
}
/* get user defined inputs */
void user_input()
{
char choice;
printf("Enter a Command (?): ");
scanf("%1s", &choice);
process_player_command(choice);
}
/*
* get agent (AI) desired action, asks the knowledge base and guesses for the
* best course of action.
*/
void agent_input()
{
char choice = kb_ask_action();
printf("agent_input: %c\n", choice);
process_player_command(choice);
}
/* prints help for a user */
void print_help()
{
printf("Usable commands:\n");
printf(" n,s,e,w Move in direction given (also VI keybindings)\n");
printf(" N,S,E,W Shoot in direction given\n");
printf(" g Grab gold\n");
printf(" q Quit\n");
}
/* display the current environment */
void print_map()
{
int i = 0, j = 0;
for(j = 0; j < MAP_SIZE; j++)
{
for(i = 0; i < MAP_SIZE; i++)
{
if(i == game.x && j == game.y)
printf("%c", MAP_PLAYER);
else
printf("%c", game.map[i][j]);
}
printf("\n");
}
printf("\n");
}
/* prints out what percepts the player feels */
void print_percepts()
{
char *nopercept = "None";
printf("Percepts: [");
printf("%s,", (game.percepts & PERCEPT_BUMP ? "Bump" : nopercept));
printf("%s,", (game.percepts & PERCEPT_SMELL ? "Smell" : nopercept));
printf("%s,", (game.percepts & PERCEPT_BREEZE ? "Breeze" : nopercept));
printf("%s,", (game.percepts & PERCEPT_MOO ? "Moo" : nopercept));
printf("%s,", (game.percepts & PERCEPT_GLITTER ? "Glitter" : nopercept));
printf("%s", (game.percepts & PERCEPT_DEAD ? "Dead" : nopercept));
printf("]\n");
}
/* prints out the player's score */
void print_score()
{
printf("Score: %5d\tSteps Taken: %3d/%d\n", game.score, game.steps_taken,
MAP_MAXSTEPS);
}
/* helper to tell if the player is dead */
int player_dead()
{
return (game.percepts & PERCEPT_DEAD);
}
/* you have won when you have the gold and are at the start square */
int has_won()
{
return (game.x == 1 && game.y == 1 && game.has_gold);
}
/*
* you lose when your score is less than SCORE_MIN or have taken more than
* MAP_MAXSTEPS or you have died.
*/
int has_lost()
{
return (game.score < SCORE_MIN || game.steps_taken > MAP_MAXSTEPS ||
player_dead());
}
/*
* figures out which direction the user wants to go and updates
* pointers to the new coordinates. returns a string for printing which
* direction was processed.
*/
char *delta_coordinates(int *x, int *y, int direction)
{
/* add the direction onto the coordinate */
switch(direction)
{
case DIRECTION_NORTH:
(*y)--;
return "North";
break;
case DIRECTION_SOUTH:
(*y)++;
return "South";
break;
case DIRECTION_EAST:
(*x)++;
return "East";
break;
case DIRECTION_WEST:
(*x)--;
return "West";
break;
}
return "Unknown";
}
/* adds score into the game */
void add_score(int delta)
{
game.score += delta;
}
/* moves the player around. also requires a direction. */
void action_move(int direction)
{
int x2 = game.x, y2 = game.y;
add_score(SCORE_MOVE);
game.steps_taken++;
printf("Moving %s ", delta_coordinates(&x2, &y2, direction));
printf("(%d, %d)\n", x2, y2);
/* this function will process bumps */
if(game.map[x2][y2] == MAP_WALL)
{
game.percepts |= PERCEPT_BUMP;
printf("You bumped into a wall!\n");
/* just go ahead and back out if you bump into something */
if(game.use_agent)
{
/* must tell the kb about this... */
kb_insert(PERCEPT_BUMP, x2, y2);
}
return;
}
/* see if you are in the same square as a supmuw */
if(game.map[x2][y2] == MAP_SUPMUW &&
!game.has_food && !game.supmuw_neighbors_wumpus)
{
game.has_food = 1;
printf("The supmuw has gifted food to you!\n");
add_score(SCORE_FOOD);
}
/* now go ahead and move the player */
game.x = x2; game.y = y2;
}
/* shoots arrows. requires a direction */
void action_shoot(int direction)
{
int x2 = game.x, y2 = game.y;
if(!game.arrows)
{
printf("You are out of arrows!\n");
return;
}
printf("Shooting %s\n", delta_coordinates(&x2, &y2, direction));
add_score(SCORE_SHOOT);
game.arrows--;
if(game.map[x2][y2] == MAP_WUMPUS || game.map[x2][y2] == MAP_SUPMUW)
{
add_score(SCORE_KILL);
printf("You hear a deafening scream as you slay the beast.\n");
game.map[x2][y2] = MAP_EMPTY;
/* regardless of who you kill, the supmuw does not neighbor wumpus */
game.supmuw_neighbors_wumpus = 0;
/* tell the agent that the thing was killed */
if(game.use_agent)
{
/* only one of these will be removed */
kb_delete(PERCEPT_WUMPUS, x2, y2);
kb_delete(PERCEPT_SUPMUW, x2, y2);
/* remove the smells, too */
kb_delete(PERCEPT_SMELL, x2 - 1, y2);
kb_delete(PERCEPT_SMELL, x2 + 1, y2);
kb_delete(PERCEPT_SMELL, x2, y2 - 1);
kb_delete(PERCEPT_SMELL, x2, y2 + 1);
}
}
}
/* grabs gold if possible */
void action_grab()
{
if(game.map[game.x][game.y] == MAP_GOLD)
{
add_score(SCORE_GOLD);
printf("You have found gold!\n");
game.map[game.x][game.y] = MAP_EMPTY;
game.has_gold = 1;
if(game.use_agent)
kb_delete(PERCEPT_GLITTER, game.x, game.y);
}
}
/* quits the game and returns to the OS */
void action_quit()
{
printf("\nFinal Analysis of gameplay\n");
/* show the final map */
print_map();
/* show the final set of percepts */
print_percepts();
/* one last chance to make fun of the player */
if(has_lost())
printf("Apparently you are not a winner. That would make you a loser.\n");
if(player_dead())
printf("You have died. Indiana Jones would be ashamed.\n");
if(has_won())
printf("You have won, the plantation is saved. Glory! Glory!\n");
/* final score */
print_score();
/* cleanup for agent */
if(game.use_agent)
{
/* dumps the contents of the knowledge base to stderr */
kb_dump();
kb_close();
}
exit(0);
}
/* initialize the knowledge base. builds an sqlite3 RAM db and build tables */
void kb_init()
{
char *err_msg;
int res = 0;
/* make db */
res = sqlite3_open(":memory:", &game.db);
if(res)
{
fprintf(stderr, "KB_INIT: %s\n",
sqlite3_errmsg(game.db));
sqlite3_close(game.db);
exit(1);
}
/* create the knowledge base table */
res = sqlite3_exec(game.db,
/* no primary key, you get locking errors if you do... */
"CREATE TABLE kb (sentence INT, x INT, y INT);",
NULL, 0, &err_msg);
if(res != SQLITE_OK)
{
fprintf(stderr, "KB_INIT: %s\n", err_msg);
sqlite3_free(err_msg);
exit(1);
}
/* create the queue table */
res = sqlite3_exec(game.db,
"CREATE TABLE queue (id INTEGER PRIMARY KEY, name VARCHAR, x INT, y INT);",
NULL, 0, &err_msg);
if(res != SQLITE_OK)
{
fprintf(stderr, "KB_INIT: %s\n", err_msg);
sqlite3_free(err_msg);
exit(1);
}
}
/* closes the database stuff */
void kb_close()
{
sqlite3_close(game.db);
}
/* private callback that just sees if a row has been found */
static int kb_found_callback(void *found, int argc, char **argv, char **cols)
{
if(argc > 0)
*((int *)found) = 1;
return 0;
}
/* finds a row in the kb */
int kb_found(int sentence, int x, int y)
{
int res = 0, found = 0;
char query[128], *err_msg;
sprintf(query,
"SELECT * FROM KB WHERE x = %d AND y = %d AND sentence = %d LIMIT 1;",
x, y, sentence);
res = sqlite3_exec(game.db, query, kb_found_callback, (void *)&found,
&err_msg);
if(res != SQLITE_OK)
{
fprintf(stderr, "KB_FOUND: %s\n", err_msg);
sqlite3_free(err_msg);
}
return found;
}
/* has the square been visited? */
int visited(int x, int y)
{
return kb_found(PERCEPT_VISITED, x, y);
}
/* is the square safe? */
int safe(int x, int y)
{
return kb_found(PERCEPT_SAFE, x, y);
}
/* is it a wall? */
int wall(int x, int y)
{
return kb_found(PERCEPT_BUMP, x, y);
}
/* does it glitter? */
int glitter(int x, int y)
{
return kb_found(PERCEPT_GLITTER, x, y);
}
/* does it smell? */
int smell(int x, int y)
{
return kb_found(PERCEPT_SMELL, x, y);
}
/*
* puts a percept or sentence into the kb. requires a percept and does not
* insert a row if one is already found of the same kind and position.
*/
void kb_insert(int sentence, int x, int y)
{
int res = 0;
char query[128], *err_msg;
/* don't insert another row of the same kind or for a wall */
if(kb_found(sentence, x, y))
return;
sprintf(query, "INSERT INTO KB (sentence, x, y) VALUES (%d, %d, %d);",
sentence, x, y);
res = sqlite3_exec(game.db, query, NULL, 0, &err_msg);
if(res != SQLITE_OK)
{
fprintf(stderr, "KB_INSERT: %s\n", err_msg);
sqlite3_free(err_msg);
}
}
/* removes a statement from the database */
void kb_delete(int sentence, int x, int y)
{
int res = 0;
char query[128], *err_msg;
if(!kb_found(sentence, x, y))
return;
sprintf(query, "DELETE FROM KB WHERE sentence = %d AND x = %d AND y = %d;",
sentence, x, y);
res = sqlite3_exec(game.db, query, NULL, 0, &err_msg);
if(res != SQLITE_OK)
{
fprintf(stderr, "KB_DELETE: %s\n", err_msg);
sqlite3_free(err_msg);
}
}
/*
* generalization for checking around a spot. this will insert something
* into the KB if found. it takes what it's looking for, what to insert when
* found and a delta for x and y from the current player position.
*
* This requires two pieces of information, one known to be safe and one known
* to have the percept. If you have more information, then something is not
* right. If you have less, not enough can be inferred.
*/
void check_corner(int percept, int known, int xd, int yd)
{
if(kb_found(percept, game.x + xd, game.y + yd) &&
!wall(game.x + xd, game.y + yd) &&
safe(game.x + xd, game.y) ^ safe(game.x, game.y + yd))
{
if(safe(game.x + xd, game.y))
kb_insert(known, game.x, game.y + yd);
if(safe(game.x, game.y + yd))
kb_insert(known, game.x + xd, game.y);
}
}
/*
* check for something at each corner around where the player sits. This only
* checks the diagonals around the player position. the general form to check
* the squares immediately accessible to the player require three pieces of
* information and only in the rarest of cases can you not determine this
* otherwise. A specific situation is like this:
*
* ..P.
* PPP.
*
* The agent does not seem to notice the middle P in the bottom row, but really
* the agent will not maneuver to that square anyways. Not a big deal, really.
*
* This function requires the 'maybe' percept and the percept to tell the KB
* that you 'found' the obstackle in question.
*/
void kb_inferrances(int percept, int known)
{
if(!kb_found(percept, game.x, game.y))
return;
/* check north west corner above player */
check_corner(percept, known, -1, -1);
/* check north east corner above player */
check_corner(percept, known, 1, -1);
/* check south west corner below player */
check_corner(percept, known, -1, 1);
/* check south east corner below player */
check_corner(percept, known, 1, 1);
}
/*
* tell the knowledge base about your percepts. this is a lot like processing
* the percepts, only now we are telling the kb about what we see using only
* the percepts.
*/
void kb_tell()
{
kb_insert(PERCEPT_VISITED, game.x, game.y);
if(!(game.percepts & PERCEPT_DEAD))
kb_insert(PERCEPT_SAFE, game.x, game.y);
if(game.percepts & PERCEPT_SMELL)
kb_insert(PERCEPT_SMELL, game.x, game.y);
if(game.percepts & PERCEPT_BREEZE)
kb_insert(PERCEPT_BREEZE, game.x, game.y);
if(game.percepts & PERCEPT_MOO)
kb_insert(PERCEPT_MOO, game.x, game.y);
if(game.percepts & PERCEPT_GLITTER)
kb_insert(PERCEPT_GLITTER, game.x, game.y);
if(!(game.percepts & PERCEPT_SMELL) &&
!(game.percepts & PERCEPT_BREEZE))
{
/*
* this is useful to expand the number of squares we can access after each
* move.
*/
kb_insert(PERCEPT_SAFE, game.x - 1, game.y);
kb_insert(PERCEPT_SAFE, game.x + 1, game.y);
kb_insert(PERCEPT_SAFE, game.x, game.y - 1);
kb_insert(PERCEPT_SAFE, game.x, game.y + 1);
}
/*
* now lets make some inferrances.
* this process is mostly the same, so just use the same function.
*/
kb_inferrances(PERCEPT_SMELL, PERCEPT_WUMPUS);
kb_inferrances(PERCEPT_BREEZE, PERCEPT_PIT);
kb_inferrances(PERCEPT_MOO, PERCEPT_SUPMUW);
}
/* removes the pre-set destination, if it exists */
void remove_destination()
{
kb_delete(PERCEPT_DESTINATION, 0, 0);
game.dest_x = -1; game.dest_y = -1;
}
/*
* sets a bee-line destination for the agent to navigate to this point.
* I use destinations to force the agent to explore more of the map. It is the
* sai-yu-sanji-kuyah (top priority) next to gold. You set a destination and the
* agent goes there. Because i always require a coordinate in the KB for each
* percept saved, i use (0, 0) as the coordinate in the database and keep the
* real coordinates in the WUMPLUS struct. This simplifies many things about
* the SQL code and prevents me from having to write another function just to
* pull the two coordinates out.
*/
void set_destination(int x, int y)
{
kb_insert(PERCEPT_DESTINATION, 0, 0);
game.dest_x = x;
game.dest_y = y;
}
/* does a destination exist? */
int has_destination()
{
if(kb_found(PERCEPT_DESTINATION, 0, 0))
return 1;
return 0;
}
/* is the agent is at the destination? */
int at_destination()
{
if(has_destination() && game.x == game.dest_x && game.y == game.dest_y)
return 1;
return 0;
}
/* is the agent is at the starting position? */
int at_start()
{
return game.x == 1 && game.y == 1;
}
/* callback to see if an unvisited safe square has been found */
static int huss_callback(void *found, int argc, char **argv, char **cols)
{
int x = atoi(argv[1]), y = atoi(argv[2]);
if(!*((int *)found) && !visited(x, y) && !wall(x, y))
{
*((int *)found) = 1;
set_destination(x, y);
}
return 0;
}
/* finds a random unvisited safe square and sets the destination thusly */
int has_unvisited_safe_squares()
{
int res = 0, found = 0;
char query[128], *err_msg;
sprintf(query, "SELECT * FROM kb WHERE sentence = %d ORDER BY random();",
PERCEPT_SAFE);
res = sqlite3_exec(game.db, query, huss_callback, &found, &err_msg);
if(res != SQLITE_OK)
{
fprintf(stderr, "HAS_UNVISITED_SAFE_SQUARES: %s\n", err_msg);
sqlite3_free(err_msg);
}
return found;
}
/* returns a direction to the requested square from the relative player pos. */
char relative_direction(int x, int y)
{
if(x == game.x)
return (game.y < y ? 's' : 'n');
if(y == game.y)
return (game.x < x ? 'e' : 'w');
return 'q';
}
/*
* determines if two squares are neighbors.
* returns a boolean if the delta between the two coordinates is one.
*/
int neighbors(int x1, int y1, int x2, int y2)
{
int xd = abs(x2 - x1), yd = abs(y2 - y1);
if(xd > 1 || yd > 1 || xd + yd > 1 || xd + xd == 0)
return 0;
return 1;
}
/*
* will return a char for the next step to get to the given square
*
* This function performs a breadth-first search to find the requested square.
* It also uses a modified A* approach to this. I use the idea of cost-to-next
* square-plus-heuristic to figure out where to go next. I use the breadth-first
* traversal and calculate the cost to the destination from that square. It
* adds one to the weights as it goes out. When you reach the player's square
* you only have to choose the smallest non-zero square to determine the best
* direction of travel.
*
* This is a lot of complication for a lot of simplification on the game side
* This is probably the hardest part of the whole program. 'Knowing' things
* about what you see is easy, inferring this is pretty easy. Getting there.
* That's complication.
*
* This algorithm is also completely capable of navigating 'walls' of obstackles
* in the game. Pits, walls, wumpuses, supmuws, whatever. It can get around it.
*
* ==General procedure==
* Unmark and set weights to 0 for all squares.
* Set weight to 1 for destination square.
* Enqueue the destination.
* While queue is not empty:
* Dequeue first item
* Skip if wall, not safe or marked
* Mark item
* Enqueue all four sides
* Set weights on all four sides to one plus current weight,
* if weight is zero or larger than desired weight
* Dump queue
* Zero all weights for walls or unsafe/unvisited squares
* Find smallest weight neighboring the player's position
* Go there.
*/
char shortest_path()
{
int i = 0, j = 0, marked[MAP_SIZE][MAP_SIZE], weights[MAP_SIZE][MAP_SIZE];
coordinate possibles[MAP_SIZE][MAP_SIZE], temp, min;
int new_weight = 0;
char *queue = "queue";
for(i = 0; i < MAP_SIZE; i++)
{
for(j = 0; j < MAP_SIZE; j++)
{
marked[i][j] = 0;
weights[i][j] = 0;
possibles[i][j].x = i;
possibles[i][j].y = j;
}
}
temp.x = -1;
temp.y = -1;
min.x = -1;
min.y = -1;
weights[game.dest_x][game.dest_y] = 1;
queue_enqueue(queue, &possibles[game.dest_x][game.dest_y]);
while(!queue_empty(queue))
{
queue_dequeue(queue, &temp);
if(wall(temp.x, temp.y) || !safe(temp.x, temp.y) || marked[temp.x][temp.y])
continue;
marked[temp.x][temp.y] = 1;
queue_enqueue(queue, &possibles[temp.x - 1][temp.y]);
queue_enqueue(queue, &possibles[temp.x + 1][temp.y]);
queue_enqueue(queue, &possibles[temp.x][temp.y - 1]);
queue_enqueue(queue, &possibles[temp.x][temp.y + 1]);
new_weight = weights[temp.x][temp.y] + 1;
if(weights[temp.x - 1][temp.y] == 0 ||
weights[temp.x - 1][temp.y] > new_weight)
{
weights[temp.x - 1][temp.y] = new_weight;
}
if(weights[temp.x + 1][temp.y] == 0 ||
weights[temp.x + 1][temp.y] > new_weight)
{
weights[temp.x + 1][temp.y] = new_weight;
}
if(weights[temp.x][temp.y - 1] == 0 ||
weights[temp.x][temp.y - 1] > new_weight)
{
weights[temp.x][temp.y - 1] = new_weight;
}
if(weights[temp.x][temp.y + 1] == 0 ||
weights[temp.x][temp.y + 1] > new_weight)
{
weights[temp.x][temp.y + 1] = new_weight;
}
}
queue_make_empty(queue);
for(i = 0; i < MAP_SIZE; i++)
for(j = 0; j < MAP_SIZE; j++)
if(wall(j, i) || (!safe(j, i) && !visited(j, i)))
weights[j][i] = 0;
new_weight = 0;
temp.x = game.x; temp.y = game.y;
if((weights[game.x - 1][game.y] < new_weight || new_weight == 0) &&
weights[game.x - 1][game.y])
{
new_weight = weights[game.x - 1][game.y];
temp.x = game.x - 1;
temp.y = game.y;
}
if((weights[game.x + 1][game.y] < new_weight || new_weight == 0) &&
weights[game.x + 1][game.y])
{
new_weight = weights[game.x + 1][game.y];
temp.x = game.x + 1;
temp.y = game.y;
}
if((weights[game.x][game.y - 1] < new_weight || new_weight == 0) &&
weights[game.x][game.y - 1])
{
new_weight = weights[game.x][game.y - 1];
temp.x = game.x;
temp.y = game.y - 1;
}
if((weights[game.x][game.y + 1] < new_weight || new_weight == 0) &&
weights[game.x][game.y + 1])
{
new_weight = weights[game.x][game.y + 1];
temp.x = game.x;
temp.y = game.y + 1;
}
return relative_direction(temp.x, temp.y);
}
/* determines if the (perceived) wumpus is in a nearby square */
int wumpus_nearby(coordinate *wumpus)
{
int found = 0;
if(kb_found(PERCEPT_WUMPUS, game.x - 1, game.y))
{
wumpus->x = game.x - 1;
wumpus->y = game.y;
found = 1;
}
if(kb_found(PERCEPT_WUMPUS, game.x + 1, game.y))
{
wumpus->x = game.x + 1;
wumpus->y = game.y;
found = 1;
}
if(kb_found(PERCEPT_WUMPUS, game.x, game.y - 1))
{
wumpus->x = game.x;
wumpus->y = game.y - 1;
found = 1;
}
if(kb_found(PERCEPT_WUMPUS, game.x, game.y + 1))
{
wumpus->x = game.x;
wumpus->y = game.y + 1;
found = 1;
}
return found;
}
/*
* gets the action from the knowledge base.
*
* priority of actions is as follows:
* 1. grab if glittering and set destination to start
* 2. kill that wumpus, if you know where he is and he is nearby.
* 3. go to the destination, if set
* 4. find an unvisited safe square and go there
* 5. go back to the beginning and give up.
*
* The supmuw will usually be de-food-ified through the normal course of travel
* so specific rules are not really necessary. Hunting the wumpus is viewed
* as a 'bonus' more than a goal, so it only happens if the agent discovers
* where the wumpus is located and then travels to a nearby square.
*/
char kb_ask_action()
{
coordinate wumpus;
wumpus.x = game.x; wumpus.y = game.y;
/* priority one: gold */
if(glitter(game.x, game.y))
{
/* if gold, stop, drop and proceed to exit */
remove_destination();
set_destination(1, 1);
return 'g';
}
/* check to see if the destination is deadly or a wall, if so, remove it */
if(has_destination() && (wall(game.dest_x, game.dest_y) ||
!safe(game.dest_x, game.dest_y)))
{
remove_destination();
}
/* kill the wumpus, if he is nearby */
if(smell(game.x, game.y) && game.arrows && wumpus_nearby(&wumpus))
{
return (char)((int)relative_direction(wumpus.x, wumpus.y) - 32);
}
/*
* if has destination, go there now. if we're not at the destination, then
* go ahead and find a new random location and go there if we have unvisited
* safe areas.
*/
if((has_destination() && !at_destination()) || has_unvisited_safe_squares())
{
return shortest_path();
}
/*
* should really hunt the wumpus and find the supmuw now, but we're gonna
* save that for later... since the objective is only to find the gold, this
* part is not strictly speaking necessary. Just go back and quit...
*/
if(!at_start())
{
set_destination(1, 1);
return shortest_path();
}
/*
* failure condition, just quit. can't find the gold and can't guarantee
* that the agent will survive any new squares.
*/
return 'q';
}
/* returns a word for a percept */
char *word_from_percept(int percept)
{
char *res = "Unknown";
switch(percept)
{
case(PERCEPT_BUMP):
res = "BUMP";
break;
case(PERCEPT_SMELL):
res = "SMELL";
break;
case(PERCEPT_BREEZE):
res = "BREEZE";
break;
case(PERCEPT_MOO):
res = "MOO";
break;
case(PERCEPT_GLITTER):
res = "GLITTER";
break;
case(PERCEPT_DEAD):
res = "DEAD";
break;
case(PERCEPT_WUMPUS):
res = "WUMPUS";
break;
case(PERCEPT_SUPMUW):
res = "SUPMUW";
break;
case(PERCEPT_PIT):
res = "PIT";
break;
case(PERCEPT_SAFE):
res = "SAFE";
break;
case(PERCEPT_VISITED):
res = "VISITED";
break;
case(PERCEPT_DESTINATION):
res = "DESTINATION";
break;
}
return res;
}
/* private callback for printing out each row of the knowledge base */
static int kb_dump_callback(void *x, int argc, char **argv, char **cols)
{
fprintf(stderr, "%4d: %7s: (%2d, %2d)\n", *((int *)x),
(argv[0] ? word_from_percept(atoi(argv[0])) : "NULL"),
atoi(argv[1]), atoi(argv[2]));
*((int *)x) = *((int *)x) + 1;
return 0;
}
/* dumps the kb's contents to stderr; sorts on value then on column then row */
void kb_dump()
{
int res = 0, counter = 1;
char *err_msg;
fprintf(stderr, "Knowledge Base Dump\n");
res = sqlite3_exec(game.db, "SELECT * FROM kb ORDER BY sentence, y, x;",
kb_dump_callback, (void *)&counter, &err_msg);
if(res != SQLITE_OK)
{
fprintf(stderr, "KB_DUMP: %s\n", err_msg);
sqlite3_free(err_msg);
}
}
/* empties a queue */
void queue_make_empty(const char *mylist)
{
int res = 0;
char query[128], *err_msg;
sprintf(query, "DELETE FROM queue WHERE name = '%s';", mylist);
res = sqlite3_exec(game.db, query, NULL, 0, &err_msg);
if(res != SQLITE_OK)
{
fprintf(stderr, "QUEUE_MAKE_EMPTY: %s\n", err_msg);
sqlite3_free(err_msg);
}
}
/* private callback to check if the number of items in a queue is empty */
static int queue_empty_callback(void *empty, int argc, char **argv, char **cols)
{
int result = atoi(argv[0]);
if(result > 0)
*((int *)empty) = 0;
return 0;
}
/* is the queue empty */
int queue_empty(const char *mylist)
{
int res = 0, empty = 1;
char query[128], *err_msg;
sprintf(query, "SELECT COUNT(*) FROM queue WHERE name = '%s';", mylist);
res = sqlite3_exec(game.db, query, queue_empty_callback, &empty, &err_msg);
if(res != SQLITE_OK)
{
fprintf(stderr, "QUEUE_EMPTY: %s\n", err_msg);
sqlite3_free(err_msg);
}
return empty;
}
/* adds a coordinate into the queue */
void queue_enqueue(const char *mylist, coordinate *data)
{
int res = 0;
char query[128], *err_msg;
sprintf(query, "INSERT INTO queue (name, x, y) VALUES ('%s', %d, %d);",
mylist, data->x, data->y);
res = sqlite3_exec(game.db, query, NULL, 0, &err_msg);
if(res != SQLITE_OK)
{
fprintf(stderr, "QUEUE_ENQUEUE: %s\n", err_msg);
sqlite3_free(err_msg);
}
}
/* private callback for dequeue to return the coordinate */
static int dequeue_callback(void *coord, int argc, char **argv, char **cols)
{
coordinate *mycoord = (coordinate *)coord;
mycoord->x = atoi(argv[2]);
mycoord->y = atoi(argv[3]);
return 0;
}
/* removes an item from the queue and returns the values into *result */
void queue_dequeue(const char *mylist, coordinate *result)
{
int res = 0;
char query[128], *err_msg;
sprintf(query,
"SELECT * FROM queue WHERE name = '%s' ORDER BY id ASC LIMIT 1;",
mylist);
res = sqlite3_exec(game.db, query, dequeue_callback, result, &err_msg);
if(res != SQLITE_OK)
{
fprintf(stderr, "QUEUE_DEQUEUE: %s\n", err_msg);
sqlite3_free(err_msg);
}
sprintf(query,
"DELETE FROM queue WHERE name = '%s' AND x = %d AND y = %d;",
mylist, result->x, result->y);
res = sqlite3_exec(game.db, query, NULL, 0, &err_msg);
if(res != SQLITE_OK)
{
fprintf(stderr, "QUEUE_DEQUEUE: %s\n", err_msg);
sqlite3_free(err_msg);
}
}