README.md
Sokoban Environment - NYU CS-GY6613
Prerequisites
Requires python3 to run
Install li
aries
$ pip install -r requirements.txt
Run the Game
Solve as a human
$ python3 game.py --play $ python3 game.py --agent Human
Solve with an agent
$ python3 game.py --agent [AGENT-NAME-HERE]
$ python3 game.py --agent BFS #run game with BFS agent
$ python3 game.py --agent AStar --no_render #run game with AStar agent without rendering
Parameters
--play - run the game as a human playe
--no_render - run the AI solver without showing the game screen
--agent [NAME] - the type of agent to use [Human, DoNothing, Random, BFS, DFS, AStar, HillClimber, Genetic, MCTS]
--level [#] - which level to test XXXXXXXXXXor 'random' for a randomly selected level that an agent can solve in at most 2000 iterations.
These levels can be found in the 'assets/gen_levels/' folder (default=0)
--iterations [#] - how many iterations to allow the agent to search for (default=3000)
--solve_speed [#] - how fast (in ms) to show each step of the solution being executed on the game screen
Code Functions
These are the only functions you need to concern yourselves with to complete the assignments.WARNING: DO NOT MODIFY THESE
FUNCTIONS!
Sokoban_py
state.clone() - creates a full copy of the cu
ent state (for use in initializing Nodes or for feedforward simulation of states without
modifying the original) Use with HillClimber to test sequences
state.checkWin() - checks if the game has been won in this state (return type: bool)
state.update(x,y) - updates the state with the given direction in the form x,y where x is the change in x axis position and y is the
change in y axis position. Used to feed-forward a state. Use with HillClimber Agent to test sequences.
Agent_py
Agent() - base class for the Agents
RandomAgent() - agent that returns list of 20 random directions
DoNothingAgent() - agent that makes no movement for 20 steps
BFSAgent() - agent that solves the level using Breadth First Search
DFSAgent() - agent that solves the level using Depth First Search
AStarAgent() - agent that solves the level using A* Search
HillClimberAgent() - agent that solves the level using HillClimber Search algorithm
GeneticAgent() - agent that solves the level using Genetic Search algorithm
MCTSAgent() - agent that solves the level using Monte Carlo Tree Search algorithm
Helper_py
Other functions
getHeuristic(state) - returns the remaining heuristic cost for the cu
ent state - a.k.a. distance to win condition (return type:
int). Use with HillClimber Agent to compare states at the end of sequence simulations
directions - list of all possible directions (x,y) the agent/player can take Use with HillClimber Agent to mutate sequences
Node Class
__init__(state, parent, action) - where state is the cu
ent layout of the game map, parent is the Node object preceding the
state, and action is the dictionary XY direction used to reach the state (return type: Node object)
checkWin() - returns if the game is in a win state where all of the goals are covered by crates (return type: bool)
getActions() - returns the sequence of actions taken from the initial node to the cu
ent node (return type: str list)
getHeuristic() - returns the remaining heuristic cost for the cu
ent state - a.k.a. distance to win condition (return type: int)
getHash() - returns a unique hash for the cu
ent game state consisting of the positions of the player, goals, and crates made
of a string of integers - for use of keeping track of visited states and comparing Nodes (return type: str)
getChildren() - retrieves the next consecutive Nodes of the cu
ent state by expanding all possible directional actions (return
type: Node list)
getCost() - returns the depth of the node in the search tree (return type: int)
MCTSNode Class (extension of Node() for use with the MCTSAgent only)
__init__(state, parent, action, maxDist) - modified to include variable to keep track of number of times visited (self.n), variable
to keep track of score (self.q), and variable to keep make score value larger as solution gets nearer (self.maxDist)
getChildren(visited) - returns the node's children if already made - otherwise creates new children based on whether states
have been visited yet and saves them for use later (self.children)
calcEvalScore(state) - calculates the evaluation score for a state compared to the node by examining the heurstic value
compared to the starting heuristic value (larger = better = higher score) - for use with the rollout and general MCTS algorithm
functions
FAQs
What is iterations and max_iterations ?
iterations keeps track of how many nodes you can expand upon in the tree, and max_iterations is the number of nodes
you are allowed to search. In the case of evolutionary search, this is the number of times the solution is allowed to evolve. This
variable is implemented to prevent infinite loops from occuring in case your algorithm has an e
or. Each level in the Sokoban
framework should be able to be solved in 3000 iterations (the default number) or less for every AI solver.
Can I use other li
aries?
No. All the functions you need are already included in the code template. Do not import any internal or external li
aries for any
eason.
I get infinite loops / My agent is running in circles. / My agent keeps going back to the same place.
Make sure you are using node_a.getHash() == node_b.getHash() to check the equivalency of 2 nodes and NOT node_a ==
node_
Is it ok if my agent's solution is less than 50 steps but returned a solution length of 50 anyways?
Yes, as long as it reaches the win state of the level.
My Hillclimbe
MCTS Agent doesn't win the level, is this ok?
That's fine, as long as the final solution gets somewhat close to the win state. Because of the stochastic nature of Hillclimbe
and MCTS, around 70%-80% of levels should be solved.
screenshot XXXXXXXXXXat XXXXXXXXXXikkgusyk.png
screenshot XXXXXXXXXXat XXXXXXXXXXf2px315y.png
sokoban-y1ejkzaf.py
from PIL import Image
import os
#Cu
ent Sokoban State
class State:
#Empty Sokoban Level
def __init__(self):
XXXXXXXXXXself.solid=[]
XXXXXXXXXXself.targets=[]
XXXXXXXXXXself.crates=[]
XXXXXXXXXXself.player=None
#Initialize a Sokoban level from lines
def stringInitialize(self, lines):
XXXXXXXXXXself.solid=[]
XXXXXXXXXXself.targets=[]
XXXXXXXXXXself.crates=[]
XXXXXXXXXXself.player=None
# clean the input
XXXXXXXXXXfor i in range(len(lines)):
XXXXXXXXXXlines[i]=lines[i].replace("\n","")
XXXXXXXXXXfor i in range(len(lines)):
XXXXXXXXXXif len(lines[i].strip()) != 0:
eak
XXXXXXXXXXelse:
XXXXXXXXXXdel lines[i]
XXXXXXXXXXi-=1
XXXXXXXXXXfor i in range(len(lines)-1,0,-1):
XXXXXXXXXXif len(lines[i].strip()) != 0:
eak
XXXXXXXXXXelse:
XXXXXXXXXXdel lines[i]
XXXXXXXXXXi+=1
#get size of the map
XXXXXXXXXXself.width=0
XXXXXXXXXXself.height=len(lines)
XXXXXXXXXXfor l in lines:
XXXXXXXXXXif len(l) > self.width:
XXXXXXXXXXself.width = len(l)
#set the level
XXXXXXXXXXfor y in range(self.height):
XXXXXXXXXXl = lines[y]
XXXXXXXXXXself.solid.append([])
XXXXXXXXXXfor x in range(self.width):
XXXXXXXXXXif x > len(l)-1:
XXXXXXXXXXself.solid[y].append(False)
XXXXXXXXXXcontinue
XXXXXXXXXXc=l[x]
XXXXXXXXXXif c == "#":
XXXXXXXXXXself.solid[y].append(True)
XXXXXXXXXXelse:
XXXXXXXXXXself.solid[y].append(False)
XXXXXXXXXXif c == "@" or c=="+":
XXXXXXXXXXself.player={"x":x, "y":y}
XXXXXXXXXXif c=="$" or c=="*":
XXXXXXXXXXself.crates.append({"x":x, "y":y})
XXXXXXXXXXif c=="." or c=="+" or c=="*":
XXXXXXXXXXself.targets.append({"x":x, "y":y})
# Make a clone of the cu
ent state
def clone(self):
XXXXXXXXXXclone=State()
XXXXXXXXXXclone.width = self.width
XXXXXXXXXXclone.height = self.height
# since the solid is not changing then copy by value
XXXXXXXXXXclone.solid = self.solid
XXXXXXXXXXif hasattr(self, 'deadlocks'):
XXXXXXXXXXclone.deadlocks =