/* * Wum+ -- A wumpus clone with a self-solving intelligent agent. * Andrew Coleman * 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 #include #include #include #include #include /* 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 \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); } }