Project restructure

This commit is contained in:
Rory Healy 2024-06-13 14:25:56 +10:00
parent 3e2e69f1c3
commit 392e60e54b
Signed by: roryhealy
GPG key ID: 0A3CBDE9C2AE672F
64 changed files with 595 additions and 54 deletions

4
.gitignore vendored
View file

@ -1 +1,3 @@
.vscode/ sokoban
*.o
solution.txt

92
Makefile Normal file → Executable file
View file

@ -1,51 +1,41 @@
## BUILDDIR=$(CURDIR)/build
## EPITECH PROJECT, 2017 NAME=sokoban
## Makefile CFLAGS+=-I./include/
## File description: CC=gcc -Wall -Wextra -Werror -pedantic -g
## Makefile
## OBJ= build/main.o \
build/helper.o \
CC = gcc -Wall -Wextra -O3 -g build/key_check.o \
#CC = gcc -Wall -Wextra -g build/loose_check.o \
build/find_player.o \
build/map_check.o \
RM = rm -f build/map_reading.o \
build/movement.o \
NAME = sokoban build/play.o \
build/win_check.o \
SRC = src/main.c \ build/zone_check.o \
src/helper.c \ build/my_putchar.o \
src/key_check.c \ build/my_putstr.o \
src/loose_check.c \ build/utils.o \
src/find_player.c \ build/priority_queue.o \
src/map_check.c \ build/hashtable.o \
src/map_reading.c \ build/ai.o
src/movement.c \
src/play.c \ $(NAME): $(OBJ) | $(BUILDDIR)
src/win_check.c \ $(CC) -o $(NAME) $^ -lncurses
src/zone_check.c \
lib/my_putchar.c \ build/%.o: src/%.c
lib/my_putstr.c \ $(CC) -c $< -o $@
src/ai/utils.o \
src/ai/priority_queue.o \ build/%.o: src/ai/%.c
src/ai/hashtable.o \ $(CC) -c $< -o $@
src/ai/ai.o \
build/%.o: lib/%.c
CFLAGS += -I./include/ $(CC) -c $< -o $@
OBJ = $(SRC:.c=.o) $(BUILDDIR):
mkdir -p $(BUILDDIR)
all: $(NAME)
clean:
$(NAME): $(OBJ) @rm -rf build
$(CC) -o $(NAME) $(OBJ) -lncurses @rm -f $(NAME)
clean:
$(RM) $(OBJ)
fclean: clean
$(RM) $(NAME)
re: fclean all
.PHONY: all clean fclean re

1
README.md Normal file
View file

@ -0,0 +1 @@
## COMP20003 Assignment 3 - Sokoban Solver using Dijkstra's algortihm

250
docs/SPECIFICATION.md Normal file
View file

@ -0,0 +1,250 @@
# Assignment Specification
Below is the assignment specification, in full, slightly edited for context and appearence.
## Purpose
The purpose of this assignment is for you to:
- Increase your proficiency in C programming, your dexterity with dynamic memory allocation and your understanding of data structures, through programming a search algorithm over Graphs.
- Gain experience with applications of graphs and graph algorithms to solving combinatorial games, one form of artificial intelligence.
## Sokoban
![](./images/image01.png)
In this assignment, youll be expected to build an AI algorithm to solve Sokoban. The game invented in 1980 (program released in 1982) is one of the classics among arcade puzzle games. You can play the game compiling the code given to you using the keyboard, or using this [web implementation](https://www.sokobanonline.com/) with tutorials ([alternative](https://sokoban.info/) without tutorials).
The code in this assignment was adapted from the open-source terminal version using [ncurses](https://invisible-island.net/ncurses/) made available by [CorrentinB](https://github.com/CorentinB/sokoban).
### Game Rules
![](./images/game-rules.gif)
As explained in the [Wikipedia](https://en.wikipedia.org/wiki/Sokoban) entry, the game is played on a board of squares, where each square is a floor or a wall. Some floor squares contain boxes, and some floor squares are marked as storage locations.
The player is confined to the board and may **move horizontally or vertically** onto empty squares (never through walls or boxes). The player can **move a box** by walking up to it and **pushing** it to the square beyond. <u>Boxes cannot be pulled</u>, and they <u>cannot be pushed to squares with walls or other boxes</u>.
The **number of boxes equals the number of storage locations**.
The puzzle is solved when <u>**all boxes are placed at storage locations**</u>.
### For the curious reader - the science of Sokoban
Here is some interesting material for those that want to take a deeper dive on Sokoban Solvers.
- [Official benchmarks and results](http://sokobano.de/wiki/index.php?title=Solver_Statistics) and best Sokoban solvers from 1980-2021. The best solver, [Festival](https://festival-solver.site/), was introduced at a conference last year, in 2020. A collaboration by a Google employee and a great professor [Jonathan Schaeffer](http://webdocs.cs.ualberta.ca/~jonathan/) who made outstanding contributions to AI and games.
- [Relevant publications](http://sokoban.dk/science/), A great starting point in that list is to read Andreas Junghanns Ph.D. dissertation about Sokoban. 1999. Pushing the Limits: New Developments in Single-Agent Search. *Ph.D. Dissertation, University of Alberta, 1999.*
- Everything about Sokoban: [WIKI](http://sokobano.de/wiki/index.php?title=Main_Page)
- The juice of the great [computational ideas](http://sokobano.de/wiki/index.php?title=Solver) & [insights](http://sokobano.de/wiki/index.php?title=Sokoban_solver_%22scribbles%22_by_Florent_Diedler_about_the_Sokolution_solver#Heuristic_score) behind the best Sokoban solvers.
## The Algorithm
A configuration of the Sokoban game is specified by the location of walls, boxes, storage areas and player. A configuration is called a *state*. The Sokoban Graph $G=\langle V, E \rangle$ is implicitly defined. The vertex set $V$ is defined as all the possible configurations (states), and the edges $E$ connecting two vertexes are defined by the legal movements **(right, left, up, down).** All edges have a weight of 1.
Your task is to find the path traversing the Sokoban Graph from the initial state (vertex) leading to a state (vertex) where all the boxes are located on a storage area. The best path is the shortest path. A path is a sequence of movements. You are going to use Dijkstra to find the shortest path first, along with some game-specific optimizations to speed up your algorithm.
When the AI solver is called (Algorithm 1), it should explore all possible paths (sequence of move actions) following a Dijktsra strategy, until a path solving the game is found. Note that we use transposition tables to avoid duplicate states in the search. If a state was already expanded (popped from the priority queue), we will not include it again in the priority queue (line 23 in Algorithm 1). We will also ignore states where the player doesn't move as a result of moving towards an adjacent wall, or a box which cannot move (line 18). We will finally avoid states where boxes are located in a corner (line 18, see file `utils.h`). The algorithm should return the **best solution found**. This path will then be executed by the game engine if the option `play_solution` was used as an argument.
![](./images/image02.png)
You might have multiple paths leading to a solution. **Your algorithm should consider the possible actions in the following order**: left, right, up or down.
<u>Make sure you manage the memory well.</u> When you finish running the algorithm, you have to free all the nodes from the memory, otherwise you will have memory leaks. You will notice that the algorithm can run out of memory fairly fast after expanding millions nodes.
The `applyAction` creates a **new node**, that
- points to the <u>parent</u>,
- updates the <u>state</u> with the action chosen,
- updates the <u>depth</u> of the node,
- updates the <u>priority</u> (used by the priority queue) of the node to be the negative node's depth d (if the node is the dth step of the path, then its priority is -d). This ensures the expansion of the shortest paths first, as the priority queue provided is a max heap,
- updates the <u>action</u> used to create the node.
Check the file `utils.h, hash_table.h, priority_queue.h` where you'll find many of the functions in the algorithm already implemented. Other useful functions are located directly in the file `ai.c` , which is the only file you need to edit to write your algorithm inside the function `findSolution` . Look for the comment `FILL IN THE GRAPH ALGORITHM` . All the files are in the folder `src/ai`.
## Deliverables
You are expected to hand in the source code for your solver, written in C. Obviously, your source code is expected to compile and execute flawlessly using the following makefile command: `make` generating an executable called `sokoban`. Remember to compile using the optimization flag `gcc -O3` for doing your experiments, it will run twice as quickly as compiling with the debugging flag `gcc -g` (see `Makefile`, and change the `CC` variable accordingly). For the submission, please **submit your makefile with `gcc -g` option**, as our scripts need this flag for testing. Your program must not be compiled under any flags that prevents it from working under gdb or valgrind.
Your implementation should work well over the first 3 layouts, but it will not be expected to find a solution to any layout, as it may exceed the available RAM in your computer before finding a solution. Feel free to explore maps given in the folder `maps_suites.` They are taken from the official benchmarks of sokoban. All you have to do is to copy and paste a single map into a new file, and then call it with your solver.
### Deadlock Detection (optimizations)
The simplest way to improve the code is by improving the [deadlock](http://sokobano.de/wiki/index.php?title=Deadlocks) detection algorithm. Implement at least 1 deadlock detection method in this link.
Take a look at these 2 links for further optimization [ideas](http://sokobano.de/wiki/index.php?title=Solver) & [insights](http://sokobano.de/wiki/index.php?title=Sokoban_solver_%22scribbles%22_by_Florent_Diedler_about_the_Sokolution_solver#Heuristic_score).
If you do any optimizations & change of Data Structures used & deadlock detection improvement, please make sure to explain concisely **what** it is that you implemented, **why** you chose that optimization, **how** it affects the performance (show number of expanded nodes before and after the optimization for a set of test maps), and **where** the code of the optimizations is located. This explanation should be included in the file located at the root of the basecode: `Report.md`. Please make sure that your solver still returns the optimal solution.
## Rubric
Assignment marks will be divided into three different components.
1. Solver (12)
2. Code Style (2)
3. Optimizations (1)
Please note that you should be ready to answer any question we might have on the details of your assignment solution by e-mail, or even attending a brief interview with me, in order to clarify any doubts we might have.
## Maps & Solution formats
### Puzzle file format
We adapted the sokoban file reader to support the following specification, but we don't support comments specified by `%`.
| **Symbol** | **Represents** |
| ----------- | --------------- |
| # | Wall |
| ` ` (space) | Empty space |
| . | Goal |
| @ | Sokoban |
| $ | Box |
| + | Sokoban on Goal |
| * | Box on Goal |
| % | Comment |
### Solution file format
Solutions returned by the solver follow this format. You can load your map and solution into the [JS Visualiser](#js-visualiser).
| **Symbol** | **Represents** |
| ---------- | -------------- |
| l | Move left |
| r | Move right |
| u | Move up |
| d | Move down |
| L | Push left |
| R | Push right |
| D | Push down |
| U | Push up |
## JS Visualiser
For the [visualiser](https://www.cs.rochester.edu/u/kautz/sokoban/Sokoban.html) to work, puzzles must be rectangular. If your map is not rectangular, just filled it in with walls instead of empty spaces.
![](./images/image03.png)
## The Code Base
You are given a base code. You can compile the code and play with the keyboard (arrows). You are going to have to program your solver in the file `ai.c`. Look at the file `main.c` (main function) to know which function is called to call the AI algorithm.
You are given the structure of a node, the state, a max-heap (priority queue) and a hashtable implementation to check for duplicate states efficiently (line 23 in the Algorithm 1). Look into the `utils.*` files to know about the functions you can call to apply an action to update a game state. All relevant files are located in the folder `src/ai`.
In your final submission, you are free to change any file, but make sure the command line options remain the same.
### Input
You can play the game with the keyboard by executing
```bash
./sokoban map_file.txt
```
where `map_file.txt` is a file containing the sokoban problem to solve.
In order to execute your AI solver use the following command:
```bash
./sokoban -s map_file.txt play_solution
```
The `-s` flag calls your algorithm. `play_solution` is optional, and if typed in as an argument, the program will play the solution found by your algorithm once it finishes. All the options can be found if you use option `-h`:
```bash
./sokoban -h
USAGE
./sokoban <-s> map <play_solution>
DESCRIPTION
Arguments within <> are optional
-s calls the AI solver
play_solution animates the solution found by the AI solver
```
For example:
```bash
./sokoban -s test/maps/test_map2 play_solution
```
Will run the 2nd map expanding and will play the solution found.
### Output
Your solver will print into an `solution.txt` file the following information:
1. Solution
2. Number of expanded nodes.
3. Number of generated nodes.
4. Number of duplicated nodes.
5. Solutions length
6. Number of nodes expanded per second.
7. Total search time, in seconds.
For example, the output of your solver `./sokoban -s test/maps/test_map2` could be:
```bash
SOLUTION:
rrrrrrdrdLLLLLLLLullluRRRRRRRururRRRRRRRRRR
STATS:
Expanded nodes: 978745
Generated nodes: 3914976
Duplicated nodes: 2288345
Solution Length: 43
Expanded/seconds: 244506
Time (seconds): 4.002942
```
Expanded/Second is computed by dividing the total number of expanded nodes by the time it took to solve the game. A node is expanded if it was popped out from the priority queue, and a node is generated if it was created using the `applyAction` function. This code is already provided.
## Programming Style
[This](./programming-style.c) is a style guide which assignments are evaluated against. For this subject, the 80 character limit is a guideline rather than a rule - if your code exceeds this limit, you should consider whether your code would be more readable if you instead rearranged it.
Some automatic evaluations of your code style may be performed where they are reliable. As determining whether these style-related issues are occurring sometimes involves non-trivial (and sometimes even undecidable) calculations, a simpler and more error-prone (but highly successful) solution is used. You may need to add a comment to identify these cases, so check any failing test outputs for instructions on how to resolve incorrectly flagged issues.
## Plagiarism
This is an individual assignment. The work must be your own.
While you may discuss your program development, coding problems and experimentation with your classmates, you must not share files, as this is considered plagiarism.
**If you refer to published work in the discussion of your experiments, be sure to include a citation to the publication or the web link.**
“Borrowing” of someone elses code without acknowledgment is plagiarism. Plagiarism is considered a serious offense at the University of Melbourne. You should read the University code on [Academic integrity](https://academicintegrity.unimelb.edu.au/) and details on [plagiarism](https://academicintegrity.unimelb.edu.au/#plagiarism-and-collusion). Make sure you are not plagiarizing, intentionally or unintentionally.
You are also advised that there will be a C programming component (on paper, not on a computer) in the final examination. Students who do not program their own assignments will be at a disadvantage for this part of the examination.
## Late Policy
The late penalty is 10% of the available marks for that project for each day (or part thereof) overdue. Requests for extensions on medical grounds will need to be supported by a medical certificate. Any request received less than 48 hours before the assessment date (or after the date!) will generally not be accepted except in the most extreme circumstances. In general, extensions will not be granted if the interruption covers less than 10% of the project duration. Remember that departmental servers are often heavily loaded near project deadlines, and unexpected outages can occur; these will not be considered as grounds for an extension.
Students who experience difficulties due to personal circumstances are encouraged to make use of the appropriate University student support services, and to contact the lecturer, at the earliest opportunity.
**Finally, we are here to help!** There is information about getting help in this subject on the LMS. Frequently asked questions about the project will be answered on Ed.
## Additional Support
Your tutors will be available to help with your assignment during the scheduled workshop times. Questions related to the assignment may be posted on the Ed discussion forum, using the folder tag Assignments for new posts. You should feel free to answer other students questions if you are confident of your skills.
A tutor will check the discussion forum regularly, and answer some questions, but be aware that for some questions you will just need to use your judgment and document your thinking. For example, a question like, “How much data should I use for the experiments?”, will not be answered; you must try out different data and see what makes sense.
If you have questions about your code specifically which you feel would reveal too much of the assignment, feel free to post a private question on the discussion forum.
**Have fun!**

BIN
docs/images/game-rules.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
docs/images/image01.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
docs/images/image02.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

BIN
docs/images/image03.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

298
docs/programming-style.c Normal file
View file

@ -0,0 +1,298 @@
/** ***********************
* C Programming Style for Engineering Computation
* Created by Aidan Nagorcka-Smith (aidann@student.unimelb.edu.au) 13/03/2011
* Definitions and includes
* Definitions are in UPPER_CASE
* Includes go before definitions
* Space between includes, definitions and the main function.
* Use definitions for any constants in your program, do not just write them
* in.
*
* Tabs may be set to 4-spaces or 8-spaces, depending on your editor. The code
* Below is ``gnu'' style. If your editor has ``bsd'' it will follow the 8-space
* style. Both are very standard.
*/
/**
* GOOD:
*/
#include <stdio.h>
#include <stdlib.h>
#define MAX_STRING_SIZE 1000
#define DEBUG 0
int main(int argc, char **argv) {
...
/**
* BAD:
*/
/* Definitions and includes are mixed up */
#include <stdlib.h>
#define MAX_STING_SIZE 1000
/* Definitions are given names like variables */
#define debug 0
#include <stdio.h>
/* No spacing between includes, definitions and main function*/
int main(int argc, char **argv) {
...
/** *****************************
* Variables
* Give them useful lower_case names or camelCase. Either is fine,
* as long as you are consistent and apply always the same style.
* Initialise them to something that makes sense.
*/
/**
* GOOD: lower_case
*/
int main(int argc, char **argv) {
int i = 0;
int num_fifties = 0;
int num_twenties = 0;
int num_tens = 0;
...
/**
* GOOD: camelCase
*/
int main(int argc, char **argv) {
int i = 0;
int numFifties = 0;
int numTwenties = 0;
int numTens = 0;
...
/**
* BAD:
*/
int main(int argc, char **argv) {
/* Variable not initialised - causes a bug because we didn't remember to
* set it before the loop */
int i;
/* Variable in all caps - we'll get confused between this and constants
*/
int NUM_FIFTIES = 0;
/* Overly abbreviated variable names make things hard. */
int nt = 0
while (i < 10) {
...
i++;
}
...
/** ********************
* Spacing:
* Space intelligently, vertically to group blocks of code that are doing a
* specific operation, or to separate variable declarations from other code.
* One tab of indentation within either a function or a loop.
* Spaces after commas.
* Space between ) and {.
* No space between the ** and the argv in the definition of the main
* function.
* When declaring a pointer variable or argument, you may place the asterisk
* adjacent to either the type or to the variable name.
* Lines at most 80 characters long.
* Closing brace goes on its own line
*/
/**
* GOOD:
*/
int main(int argc, char **argv) {
int i = 0;
for(i = 100; i >= 0; i--) {
if (i > 0) {
printf("%d bottles of beer, take one down and pass it around,"
" %d bottles of beer.\n", i, i - 1);
} else {
printf("%d bottles of beer, take one down and pass it around."
" We're empty.\n", i);
}
}
return 0;
}
/**
* BAD:
*/
/* No space after commas
* Space between the ** and argv in the main function definition
* No space between the ) and { at the start of a function */
int main(int argc,char ** argv){
int i = 0;
/* No space between variable declarations and the rest of the function.
* No spaces around the boolean operators */
for(i=100;i>=0;i--) {
/* No indentation */
if (i > 0) {
/* Line too long */
printf("%d bottles of beer, take one down and pass it around, %d
bottles of beer.\n", i, i - 1);
} else {
/* Spacing for no good reason. */
printf("%d bottles of beer, take one down and pass it around."
" We're empty.\n", i);
}
}
/* Closing brace not on its own line */
return 0;}
/** ****************
* Braces:
* Opening braces go on the same line as the loop or function name
* Closing braces go on their own line
* Closing braces go at the same indentation level as the thing they are
* closing
*/
/**
* GOOD:
*/
int main(int argc, char **argv) {
...
for(...) {
...
}
return 0;
}
/**
* BAD:
*/
int main(int argc, char **argv) {
...
/* Opening brace on a different line to the for loop open */
for(...)
{
...
/* Closing brace at a different indentation to the thing it's
closing
*/
}
/* Closing brace not on its own line. */
return 0;}
/** **************
* Commenting:
* Each program should have a comment explaining what it does and who created
* it.
* Also comment how to run the program, including optional command line
* parameters.
* Any interesting code should have a comment to explain itself.
* We should not comment obvious things - write code that documents itself
*/
/**
* GOOD:
*/
/* change.c
*
* Created by Aidan Nagorcka-Smith (aidann@student.unimelb.edu.au)
13/03/2011
*
* Print the number of each coin that would be needed to make up some
change
* that is input by the user
*
* To run the program type:
* ./coins --num_coins 5 --shape_coins trapezoid --output blabla.txt
*
* To see all the input parameters, type:
* ./coins --help
* Options::
* --help Show help message
* --num_coins arg Input number of coins
* --shape_coins arg Input coins shape
* --bound arg (=1) Max bound on xxx, default value 1
* --output arg Output solution file
*
*/
int main(int argc, char **argv) {
int input_change = 0;
printf("Please input the value of the change (0-99 cents
inclusive):\n");
scanf("%d", &input_change);
printf("\n");
// Valid change values are 0-99 inclusive.
if(input_change < 0 || input_change > 99) {
printf("Input not in the range 0-99.\n")
}
...
/**
* BAD:
*/
/* No explanation of what the program is doing */
int main(int argc, char **argv) {
/* Commenting obvious things */
/* Create a int variable called input_change to store the input from
the
* user. */
int input_change;
...
/** ****************
* Code structure:
* Fail fast - input checks should happen first, then do the computation.
* Structure the code so that all error handling happens in an easy to read
* location
*/
/**
* GOOD:
*/
if (input_is_bad) {
printf("Error: Input was not valid. Exiting.\n");
exit(EXIT_FAILURE);
}
/* Do computations here */
...
/**
* BAD:
*/
if (input_is_good) {
/* lots of computation here, pushing the else part off the screen.
*/
...
} else {
fprintf(stderr, "Error: Input was not valid. Exiting.\n");
exit(EXIT_FAILURE);
}

Binary file not shown.

Binary file not shown.

BIN
sokoban

Binary file not shown.

View file

@ -302,8 +302,8 @@ void find_solution(sokoban_t *init_data, bool show_solution) {
} }
printf("STATS: \n"); printf("STATS: \n");
printf("\tExpanded nodes: %'d\n\tGenerated nodes: %'d\n\t" \ printf("\tExpanded nodes: %d\n\tGenerated nodes: %d\n\t" \
"Duplicated nodes: %'d\n", expanded_nodes, generated_nodes, \ "Duplicated nodes: %d\n", expanded_nodes, generated_nodes, \
duplicated_nodes); duplicated_nodes);
printf("\tSolution Length: %d\n", solution_size); printf("\tSolution Length: %d\n", solution_size);
printf("\tExpanded/seconds: %d\n", (int)(expanded_nodes/cpu_time_used)); printf("\tExpanded/seconds: %d\n", (int)(expanded_nodes/cpu_time_used));

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.