Related
The dungeon game is described as:
The demons had captured the princess (P) and imprisoned her
in the bottom-right corner of a dungeon. T
he dungeon consists of M x N rooms laid out in a 2D grid.
Our valiant knight (K) was initially positioned in the top-left room
and must fight his way through the dungeon to rescue the princess.
The knight has an initial health point represented by a positive integer.
If at any point his health point drops to 0 or below, he dies immediately.
Some of the rooms are guarded by demons,
so the knight loses health (negative integers) upon entering these rooms;
other rooms are either empty (0's) or contain magic orbs that increase the knight's health (positive integers).
In order to reach the princess as quickly as possible,
the knight decides to move only rightward or downward in each step.
Write a function to determine the knight's minimum initial health
so that he is able to rescue the princess.
For example, given the dungeon below, the initial health of
the knight must be at least 7 if he follows the optimal path RIGHT-> RIGHT -> DOWN -> DOWN.
Notes:
The knight's health has no upper bound.
Any room can contain threats or power-ups, even the first room the knight enters
and the bottom-right room where the princess is imprisoned.
Example:
dungeon = [[-2, -3, 4],
[-6, -15, 0],
[10, 25, -6]]
Answer: 8
The code solution is:
def dungeonGame(dungeon):
dp = [float("inf") for _ in dungeon[0]]
dp[-1] = 1
for i in reversed(range(len(dungeon))):
dp[-1] = max(dp[-1] - dungeon[i][-1], 1)
for j in reversed(range(len(dungeon[i]) - 1)):
min_HP_on_exit = min(dp[j], dp[j + 1])
dp[j] = max(min_HP_on_exit - dungeon[i][j], 1)
return dp[0]
Can somebody explain how the solution above is working? Why is the dp only len 3 with the provided example? Is it because there are only 3 steps required, excluding start and finish rooms? Why is it getting the minimum on the adjacent dp's and then the maximum? Also how come it seems that the last column is not being taken into consideration since dungeon[i][j], where j only goes up to 1 (taking the given example matrix). I know the solution is written well, just trying to understand how its taking all the path into consideration.
This algorithm works its way back from the bottom right, going left and then up, finding the optimal score for each step along the way. I recommend you execute the algorithm with pen and paper, writing down the current values of i, j and dp along the way. That should really clear things up.
(Start): No i and no j yet, dp = [inf inf 1]
You'll need at least 1 HP after reaching the bottom right in order to win.
(After entering the first loop): i=2, dp = [inf inf 7].
You need 7 health to survive the -6 of the bottom right square itself.
(After entering the inner loop): i=2, j=1, dp = [inf 1 7]
If you're in the bottom center square, the bare minimum 1 health is enough to survive that square's +25, and reach the adjacent square that requires at least 7. And so on.
This is the crucial line that chooses between going right (stored in the next element of the intermediate results, dp[j + 1]) or down, dp[j].
min_HP_on_exit = min(dp[j], dp[j + 1])
There are only three elements to the intermediate results because with the movement rules (only move right and down) and a dungeon with a diagonal of 3, there are only at most 3 places where you could be after any number of moves.
Every time the solver moves up a line, the last column is taken care of as a special case here:
dp[-1] = max(dp[-1] - dungeon[i][-1], 1)
Why? Well, it's different from the other columns in that you can't move right, only down.
I made the original battleship and now I'm looking to upgrade my AI from random guessing to guessing statistically probably locations. I'm having trouble finding algorithms online, so my question is what kinds of algorithms already exist for this application? And how would I implement one?
Ships: 5, 4, 3, 3, 2
Field: 10X10
Board:
OCEAN = "O"
FIRE = "X"
HIT = "*"
SIZE = 10
SEA = [] # Blank Board
for x in range(SIZE):
SEA.append([OCEAN] * SIZE)
If you'd like to see the rest of the code, I posted it here: (https://github.com/Dbz/Battleship/blob/master/BattleShip.py); I didn't want to clutter the question with a lot of irrelevant code.
The ultimate naive solution wold be to go through every possible placement of ships (legal given what information is known) and counting the number of times each square is full.
obviously, in a relatively empty board this will not work as there are too many permutations, but a good start might be:
for each square on board: go through all ships and count in how many different ways it fits in that square, i.e. for each square of the ships length check if it fits horizontally and vertically.
an improvement might be to also check for each possible ship placement if the rest of the ships can be placed legally whilst covering all known 'hits' (places known to contain a ship).
to improve performance, if only one ship can be placed in a given spot, you no longer need to test it on other spots. also, when there are many 'hits', it might be quicker to first cover all known 'hits' and for each possible cover go through the rest.
edit: you might want to look into DFS.
Edit: Elaboration on OP's (#Dbz) suggestion in the comments:
hold a set of dismissed placements ('dissmissed') of ships (can be represented as string, say "4V5x3" for the placement of length 4 ship in 5x3, 5x4, 5x5, 5x6), after a guess you add all the placements the guess dismisses, then for each square hold a set of placements that intersect with it ('placements[x,y]') then the probability would be:
34-|intersection(placements[x,y], dissmissed)|/(3400-|dismissed|)
To add to the dismissed list:
if guess at (X,Y) is a miss add placements[x,y]
if guess at (X,Y) is a hit:
add neighboring placements (assuming that ships cannot be placed adjacently), i.e. add:
<(2,3a,3b,4,5)>H<X+1>x<Y>, <(2,3a,3b,4,5)>V<X>x<Y+1>
<(2,3a,3b,4,5)>H<X-(2,3,3,4,5)>x<Y>, <(2,3a,3b,4,5)>V<X>x<Y-(2,3,3,4,5)>
2H<X+-1>x<Y+(-2 to 1)>, 3aH<X+-1>x<Y+(-3 to 1)> ...
2V<X+(-2 to 1)>x<Y+-1>, 3aV<X+(-3 to 1)>x<Y+-1> ...
if |intersection(placements[x,y], dissmissed)|==33, i.e. only one placement possible add ship (see later)
check if any of the previews hits has only one possible placement left, if so, add the ship
check to see if any of the ships have only possible placement, if so, add the ship
adding a ship:
add all other placements of that ship to dismissed
for each (x,y) of the ships placement add placements[x,y] with out the actual placement
for each (x,y) of the ships placement mark as hit guess (if not already known) run stage 2
for each (x,y) neighboring the ships placement mark as miss guess (if not already known) run stage 1
run stage 3 and 4.
i might have over complicated this, there might be some redundant actions, but you get the point.
Nice question, and I like your idea for statistical approach.
I think I would have tried a machine learning approach for this problem as follows:
First model your problem as a classification problem.
The classification problem is: Given a square (x,y) - you want to tell the likelihood of having a ship in this square. Let this likelihood be p.
Next, you need to develop some 'features'. You can take the surrounding of (x,y) [as you might have partial knowledge on it] as your features.
For example, the features of the middle of the following mini-board (+ indicates the square you want to determine if there is a ship or not in):
OO*
O+*
?O?
can be something like:
f1 = (0,0) = false
f2 = (0,1) = false
f3 = (0,2) = true
f4 = (1,0) = false
**note skipping (1,1)
f5 = (1,2) = true
f6 = (2,0) = unknown
f7 = (2,1) = false
f8 = (2,2) = unknown
I'd implement features relative to the point of origin (in this case - (1,1)) and not as absolute location on board (so the square up to (3,3) will also be f2).
Now, create a training set. The training set is a 'labeled' set of features - based on some real boards. You can create it manually (create a lot of boards), automatically by a random generator of placements, or by some other data you can gather.
Feed the training set to a learning algorithm. The algorithm should be able to handle 'unknowns' and be able to give probability of "true" and not only a boolean answer. I think a variation of Naive Bayes can fit well here.
After you have got a classifier - exploit it with your AI.
When it's your turn, choose to fire upon a square which has the maximal value of p. At first, the shots will be kinda random - but with more shots you fire, you will have more information on the board, and the AI will exploit it for better predictions.
Note that I gave features based on a square of size 1. You can of course choose any k and find features on this bigger square - it will give you more features, but each might be less informative. There is no rule of thumb which will be better - and it should be tested.
Main question is, how are you going to find statistically probable locations. Are they already known or you want to figure them out?
Either case, I'd just make the grid weighed. In your case, the initial weight for each slot would be 1.0/(SIZE^2). The sum of weights must be equal to 1.
You can then adjust weights based on the statistics gathered from N last played games.
Now, when your AI makes a choice, it chooses a coordinate to hit based on weighed probabilities. The quick and simple way to do that would be:
Generate a random number R in range [0..1]
Start from slot (0, 0) adding the weights, i.e. S = W(0, 0) + W(0, 1) + .... where W(n, m) is the weight of the corresponding slot. Once S >= R, you've got the coordinate to hit.
This can be optimised by pre-calculating cumulative weights for each row, have fun :)
Find out which ships are still alive:
alive = [2,2,3,4] # length of alive ships
Find out spots where you have not shot, for example with a numpy.where()
Loop over spots where you can shoot.
Check the sides of the given position. Go left and right, how many spaces? Go up and down, how many spaces? If you can fit a boat in that many spaces, you can fit any smaller boat, so this loop I'd do it from the largest ship downwards, and I'd add to the counts in this position as many +1 as ships smaller than the one that fits.
Once you have done all of this, the position with more points should be the most probable to attack and hit something.
Of course, it can get as complicated as you want. You can also ask yourself, instead of which is my next hit, which combinations of hits will give me the victory in less number of hits or any other combination/parametrization of the problem. Good luck!
I'm trying to solve a problem related to graphs in Python. Since its a comeptitive programming problem, I'm not using any other 3rd party packages.
The problem presents a graph in the form of a 5 X 5 square grid.
A bot is assumed to be at a user supplied position on the grid. The grid is indexed at (0,0) on the top left and (4,4) on the bottom right. Each cell in the grid is represented by any of the following 3 characters. ‘b’ (ascii value 98) indicates the bot’s current position, ‘d’ (ascii value 100) indicates a dirty cell and ‘-‘ (ascii value 45) indicates a clean cell in the grid.
For example below is a sample grid where the bot is at 0 0:
b---d
-d--d
--dd-
--d--
----d
The goal is to clean all the cells in the grid, in minimum number of steps.
A step is defined as a task, where either
i) The bot changes it position
ii) The bot changes the state of the cell (from d to -)
Assume that initially the position marked as b need not be cleaned. The bot is allowed to move UP, DOWN, LEFT and RIGHT.
My approach
I've read a couple of tutorials on graphs,and decided to model the graph as an adjacency matrix of 25 X 25 with 0 representing no paths, and 1 representing paths in the matrix (since we can move only in 4 directions). Next, I decided to apply Floyd Warshell's all pairs shortest path algorithm to it, and then sum up the values of the paths.
But I have a feeling that it won't work.
I'm in a delimma that the problem is either one of the following:
i) A Minimal Spanning Tree (which I'm unable to do, as I'm not able to model and store the grid as a graph).
ii) A* Search (Again a wild guess, but the same problem here, I'm not able to model the grid as a graph properly).
I'd be thankful if you could suggest a good approach at problems like these. Also, some hint and psuedocode about various forms of graph based problems (or links to those) would be helpful. Thank
I think you're asking two questions here.
1. How do I represent this problem as a graph in Python?
As the robot moves around, he'll be moving from one dirty square to another, sometimes passing through some clean spaces along the way. Your job is to figure out the order in which to visit the dirty squares.
# Code is untested and may contain typos. :-)
# A list of the (x, y) coordinates of all of the dirty squares.
dirty_squares = [(0, 4), (1, 1), etc.]
n = len(dirty_squares)
# Everywhere after here, refer to dirty squares by their index
# into dirty_squares.
def compute_distance(i, j):
return (abs(dirty_squares[i][0] - dirty_squares[j][0])
+ abs(dirty_squares[i][1] - dirty_squares[j][1]))
# distances[i][j] is the cost to move from dirty square i to
# dirty square j.
distances = []
for i in range(n):
distances.append([compute_distance(i, j) for j in range(n)])
# The x, y coordinates of where the robot starts.
start_node = (0, 0)
# first_move_distances[i] is the cost to move from the robot's
# start location to dirty square i.
first_move_distances = [
abs(start_node[0] - dirty_squares[i][0])
+ abs(start_node[1] - dirty_squares[i][1]))
for i in range(n)]
# order is a list of the dirty squares.
def cost(order):
if not order:
return 0 # Cleaning 0 dirty squares is free.
return (first_move_distances[order[0]]
+ sum(distances[order[i]][order[i+1]]
for i in range(len(order)-1)))
Your goal is to find a way to reorder list(range(n)) that minimizes the cost.
2. How do I find the minimum number of moves to solve this problem?
As others have pointed out, the generalized form of this problem is intractable (NP-Hard). You have two pieces of information that help constrain the problem to make it tractable:
The graph is a grid.
There are at most 24 dirty squares.
I like your instinct to use A* here. It's often good for solving find-the-minimum-number-of-moves problems. However, A* requires a fair amount of code. I think you'd be better of going with a Branch-and-Bound approach (sometimes called Branch-and-Prune), which should be almost as efficient but is much easier to implement.
The idea is to start enumerating all possible solutions using a depth-first-search, like so:
# Each list represents a sequence of dirty nodes.
[]
[1]
[1, 2]
[1, 2, 3]
[1, 3]
[1, 3, 2]
[2]
[2, 1]
[2, 1, 3]
Every time you're about to recurse into a branch, check to see if that branch is more expensive than the cheapest solution found so far. If so, you can skip the whole branch.
If that's not efficient enough, add a function to calculate a lower bound on the remaining cost. Then if cost([2]) + lower_bound(set([1, 3])) is more expensive than the cheapest solution found so far, you can skip the whole branch. The tighter lower_bound() is, the more branches you can skip.
Let's say V={v|v=b or v=d}, and get a full connected graph G(V,E). You could calculate the cost of each edge in E with a time complexity of O(n^2). Afterwards the problem becomes exactly the same as: Start at a specified vertex, and find a shortest path of G which covers V.
We call this Traveling Salesman Problem(TSP) since 1832.
The problem can certainly be stored as a graph. The cost between nodes (dirty cells) is their Manhattan distance. Ignore the cost of cleaning cells, because that total cost will be the same no matter what path taken.
This problem looks to me like the Minimum Rectilinear Steiner Tree problem. Unfortunately, the problem is NP hard, so you'll need to come up with an approximation (a Minimum Spanning Tree based on Manhattan distance), if I am correct.
I enjoy playing Ticket to Ride, so I decided to play around with implementing parts of the game logic in Python as a side programming project. The game board is essentially a weighted multigraph, so replicating the basic structure of the game with NetworkX was a cinch.
The one part I'm having trouble with is analyzing whether a particular path through the board is possible given an inventory of train cards the player possesses. I think it's more of a math problem than a programming problem per se, and I can probably piece together a brute force method for figuring things out, but thought there must be a more efficient way.
For those who don't know the game: at any given time, each player has a number of train cards in one of eight colors, plus a special "locomotive" category that serves as a wild card. These colors correspond to the color of train lines on the game board (shown here) except for the gray lines, where you can use any color, as long as all cars in the segment are the same color. (There are edge cases involving tunnels and ferries, but we'll leave those aside for now.)
With the code as it stands now, I can find all paths between two given cities and get back how many train cards of each color are needed to take that particular path, unless the paths involve gray segments. I do the non-gray segments first since they're more straightforward -- either you have enough red/green/blue cards for each red/green/blue segment in the path or you don't. With gray, because you can pick any color to use for each segment, it gets a bit more involved.
For paths with just one gray segment, it's still easy -- either you have enough cards of any one color to fill it in or not. With multiple gray segments, however, one can run into situations where the color chosen for the first segment makes completing the second or third segment impossible.
As an example, suppose a player's card inventory is 4 red, 2 green, 3 blue, and we're trying to figure out if he can get from Paris to Vienna. Looking at the board, it's pretty easy to see that the only possible route for this card combination involves going Paris --(3 gray)--> Zurich --(2 green)--> Venice --(2 gray)--> Zagrad --(2 gray)--> Vienna. My algorithm for figuring this out starts with the green segment, and allocates the two green cards there. Then it needs to decide how to use the remaining 4 red and 3 blue cards to cover the gray segments of lengths 3, 2, and 2.
The answer, of course, is to use the 3 blue cards between Paris and Zurich, and 2 red cards each for Venice to Zagrad and Zagrad to Vienna. But how does one write a generalized algorithm that solves this problem for less obvious cases involving more colors and more segments?
My code for this right now looks like this:
def can_afford(path, cards):
grays = list()
for segment in path:
if segment.color == 'Gray':
grays.append(segment)
else:
if cards.get(segment.color, 0) >= segment.weight:
cards[segment.color] -= segment.weight
else:
return False
for gray in grays:
# Halp!
pass
return True
("weight" is the length of the segment in train cars.)
I feel like there's a really trivial solution lurking in here that I just can't put my finger on. Any ideas?
As Daniel Brückner says, the problem of finding a way to assign colors of cards to gray segments corresponds to the bin packing problem, with the sets of colored cards corresponding to the bins, and the gray segments corresponding to the objects to be packed.
Now, the bin packing problem is NP-hard, but that's not a disaster in this case, because the problem can be solved in pseudo-polynomial time (that is, in time that's polynomial in the size of the bins) using dynamic programming, and that should be good enough for your application, where the size of the bins is limited by the number of cards in the game. Here's an example implementation of bin packing, using the #functools.lru_cache decorator to memoize it:
from functools import lru_cache
#lru_cache(maxsize=None)
def packing(bins, objects):
"""Return a packing of objects into bins, or None if impossible. Both
arguments are tuples of numbers, and the packing is returned in
the form of a list giving the bin number for each object.
>>> packing((4,5,6), (6,5,4))
[2, 1, 0]
>>> packing((4,5,6), (1,1,2,4,5))
[0, 0, 0, 1, 2]
"""
if not objects:
return []
o = objects[0]
rest = objects[1:]
for i, b in enumerate(bins):
if o <= b:
p = packing(bins[:i] + (b - o,) + bins[i+1:], rest)
if p is not None:
return [i] + p
return None
And this can be used to determine if a path can be followed in Ticket to Ride:
def can_afford(path, cards):
"""Return True if path can be followed using cards, False if not.
cards might be updated, so pass a copy if you don't want that to
happen.
"""
grays = []
for segment in path:
c, w = segment.color, segment.weight
if c == 'Gray':
grays.append(w)
elif cards.get(c, 0) >= w:
cards[c] -= w
else:
return False
return packing(tuple(cards.values()), tuple(grays)) is not None
Note that if you made cards a collection.Counter, then you could just write cards[c] instead of cards.get(c, 0).
This problem bears some similarities with problems like bin packing, subset sum and other similar problems. The mentioned and many related problems are NP-complete and therefore it may turn out that there is no (known) efficient algorithm for this problem but I can not prove this at the moment - it is just an intuition. I will think about it some more and then update this answer.
Another way of approaching this is to build a search tree as follows:
Each node is labelled with a city name, a set of cards, and a number of trains. This corresponds to the starting city of a particular route and the cards and train pieces you have available.
Each child of a node corresponds to a city you can reach from the parent, along with the cards and train pieces that remain in your hand after completing the route from the parent to the node.
For example, the root of the tree could correspond to Montreal with 4 blue, 1 white, and 1 wild card, and 45 train pieces. The children of the root would be:
Toronto, (1 blue, 1 white, 1 wild), 42 # Use 3 blue cards
Toronto, (2 blue, 1 white), 42 # Use 2 blue and a wild card
New York, ( 1 blue, 1 white, 1 wild), 42 # Use 3 blue cards
New York, ( 2 blue, 1 white ), 43 # Use 2 blue and a wild card
Boston, ( 3 blue, 1 white ), 43 # Use 1 blue and a wild card
Boston, ( 2 blue, 1 white, 1 wild ), 43 # Use 2 blue cards
Boston, ( 4 blue ), 43 # Use 1 white and a wild card
Now, you just need to perform a depth-first search in this tree to see if you can reach the destination city. The edges you add to the search tree are limited by the cards in your hand (i.e., you can't go directly from Montreal to Sault St. Marie because you don't have a total of 6 black/wild cards in your hand) and the number of trains remaining (you couldn't go from Calgary to Seattle if you only had 3 cards remaining).
I have a collection of 1*1 polygons, each of which is defined by its boundary (a set of four points) and I use the function area() below in my code example to create these. I wish to combine such adjacent squares into a single polygon, with this polygon also defined in terms of its boundary points.
I wish to do this in a brute force fashion where I begin by adding two adjacent 1*1 squares to create a larger polygon using the function join() below and continue in this fashion in order to grow the polygon. So the first argument of join is the polygon so far and the second argument is an adjecent 1*1 square which I wish to add to the polygon. The return value is the boundary of the new polygon, current joined with the new 1*1.
Here's what I've come with so far:
def join(current, new):
""" current is the polygon, new the 1*1 square being added to it"""
return get_non_touching_part_of_boundary(current, new) + get_non_touching_part_of_boundary(new, current)
def get_non_touching_part_of_boundary(this, other):
for i,point in enumerate(this):
if point not in other and this[i-1] in other:
break # start of non touching boundary from a clockwise perspective
non_touching_part_of_boundary = []
for point in this[i:] + this[:i]:
if not point in other:
non_touching_part_of_boundary.append(point)
return non_touching_part_of_boundary
def area(point):
""" boundary defined in a clockwise fashion """
return [point,(point[0],point[1]+1),(point[0]+1,point[1]+1),(point[0]+1,point[1])]
a = area((0,1)) # a assigned a 1*1 polygon
a = join(a, area((0,2))) # a assigned a 2*1 polygon
a = join(a, area((1,2)))
a = join(a, area((2,2)))
a = join(a, area((2,1)))
a = join(a, area((2,0)))
print(a)
This gives me the following polygon shape (with the numbers representing the order in which which its composing squares are added):
234
1 5
6
The printed output of the code above gives:
[(2, 2), (1, 2), (1, 1), (0, 1), (0, 3), (3, 3), (3, 0), (2, 0)]
this is the minimum number of points required to defined the boundary of the polygon.
But if I add one more square to this shape via a = join(a, area((1,0))) and thereby creating a hole, my algorithm falls to pieces:
234
1 5
76
Here's another polygon that my algorithm can't handle:
123
64
5
Can anyone help me? I'd like the holes in the polygon to be listed in a separate list.
Thank!
I think that your algorithm is hard to fix. Consider that for example adding a single square to a polygon could create several holes:
xxx
x x
xxx xxx
x y x
xxx xxx
x x
xxx
imagine for example that all the x are the "current polygon" and you then andd y...
In general a closed area is defined by a collection of closed loops and you can use just a single list only with a much more complex approach of creating zero-area bridges between the loops. A simple approach to what seems to me you are looking for is radically different:
loop on every square and for each one:
if the square is in and the square to the left is out (or vice versa) then you know you need a wall on the left
if the square is in and the square above is out (or vice versa) then you know that you need a wall above
collect all the edges in a dictionary where for every coordinate pair you keep a list of all walls starting or arriving at that edge
once the scan is finished you can rebuild the resulting loops by starting with any wall and keeping following the chain until you get back to the initial point... in case that when you arrive at a point there are multiple choiches about which wall to use for continue the walk then pick any of them.
if you collected the data properly and assuming you can add a border of one "out" cell around your data then it's guaranteed that you will get as result a list of zero or more closed loops because each point will list an even number of walls.
Those loops (when considering an odd-even filling rule) will be defining your initial area. Note that you may get self-intersecting loops... if you want to avoid that the algorithm is slightly more complex.
This approach is also much faster than processing boundaries one at a time and doing all those merge operations and the result will be general (including non-connected areas and holes).
EDIT
The following image is the result of a complete implementation of this algorithm including a right-turn logic during cycle collection to avoid self-intersecting cycles. Different colors have been assigned to output polygons and corners have been cut to make the turns evident.