Struggling with "simple" chess related randomized code - python

I have as task to create a code that should randomly place two chess pieces on the board
The board looks like this: Chess board
What I should do is "place" two randomly selected chess pieces (one black, one white) on this board above and find out if these pieces fall in places where they would threat each other based on chess rules. I should repeat this 10 000 times and find out how often a threatening situation is met. Pieces should not fall in the same square.
There are two different scenarios:
3a. The chess pieces are rooks(towers) - move horisontally or vertically OR
3b. The chess pieces are queens - move horisontally, vertically and diagonally OR
My assumption is that the approach in 3a should be that if either the first digit or last digit are same for both chess pieces the situation causes a threat. I would use the random-function to assign random numbers for both pieces and run this 10K times. I'm not sure though how to build this code to find out if the chess pieces cause a threat to each other. How to use the chess board (11-88) to define the random numbers?
the approach for 3b is, on the other hand, a big question mark due to the fact that queens can also move diagonally.
I would really appreciate some code example to understand how this is tackled.
Thanks!
EDIT: Below is the code I generated myself using the skills so far acquired. I believe this gives me the answer of how often a queen threatens another queen BUT it does not take into account that the chess pieces can't be placed on the same square on the board. How could I implement it in my code, taken that the code is otherwise correct? I'd like to modify it as little as possible.
import random
rows=range(1,9) #generates the row number
columns=range(97,97+8) #generates the column number
hot=0 #times threat took place
icke_hot=0 #times threat did not take place
for spel in range(10000):
white_row=random.choice(rows) #Gets position in row
black_row=random.choice(rows)
white_column=random.choice(columns) #Gets position in column
black_column=random.choice(columns)
if white_column==black_column or white_row==black_row: #checks if rook can attack
hot=hot+1
elif white_column==black_column or white_column==black_column+1 or white_column==black_column-1:
hot=hot+1
#checks if queen can attack
elif white_row==black_row or white_row==black_row+1 or white_row==black_row-1:
hot=hot+1
#checks if queen can attack
else:
icke_hot=icke_hot+1
print "Threats took place in", hot/100.0,"% of cases"
print hot
print icke_hot

Comments on OP Code
Column numbering is problematic
columns=range(97,97+8) #generates the column number 97, 98, ...
You could use
columns = [chr(i) for i in range(97,97+8)
The following generation of row/columns does not insure that white & black are not assigned the same square.
white_row=random.choice(rows) #Gets position in row
black_row=random.choice(rows)
white_column=random.choice(columns) #Gets position in column
black_column=random.choice(columns)
It's better to define a position as a row & column pair then
choose randomly two of the 64 positions using either random.choices or random.sample
This code doesn't check for all positions along diagonal for queen attack.
elif white_column==black_column or white_column==black_column+1 or white_column==black_column-1:
hot=hot+1
#checks if queen can attack
elif white_row==black_row or white_row==black_row+1 or white_row==black_row-1:
hot=hot+1
#checks if queen can attack
Revised Code
import random as random
def all_positions():
" Generates list of all piece positions (i.e. 64 positions) "
rows = '12345678'
columns = 'abcdefgh'
return [r+c for r in rows for c in columns] # i.e. ['1a', '1b', ...]
def random_positions():
" Two random positions (not equal) "
# Use random sample without replacement
return random.sample(all_positions(), 2)
def position_to_numeric(position):
" Convert row, column string to numeric tuple i.e. '3a -> 3, 1 "
return int(position[0]), 'abcdefgh'.index(position[1]) + 1
def can_rook_attack(position1, position2):
" Do rooks at position1 and position2 attack each other "
return position1[0] == position2[0] or position1[1] == position2[1]
def can_queen_attack(position1, position2):
" if queens at position 1 & 2 can attack each other "
# Check as rooks they would attack
if can_rook_attack(position1, position2):
return True
# Get positions as numeric
r1, c1 = position_to_numeric(position1)
r2, c2 = position_to_numeric(position2)
# If queen and the opponent are
# If queen can attack diagonally
return abs(r1 - r2) == abs(c1 - c2)
def simulate_rooks(number_trials):
" Simulate of how often rook can attack "
cnt = 0
for _ in range(number_trials):
position1, position2 = random_positions()
if can_rook_attack(position1, position2):
cnt += 1
return f'Rook attacked {cnt} out of {number_trials} random placements'
def simulate_queens(number_trials):
" Simulate of how often queens can attack "
cnt = 0
for _ in range(number_trials):
position1, position2 = random_positions()
if can_queen_attack(position1, position2):
cnt += 1
return f'Queen attacked {cnt} out of {number_trials} random placements'
Test
simulations = 10000
print(simulate_rooks(simulations))
print(simulate_queens(simulations))
Output
Rook attacked 2257 out of 10000 random placements
Queen attacked 3634 out of 10000 random placements
Explanation
Key Issues
What I should do is "place" two randomly selected chess pieces (one
black, one white) on this board above and find out if these pieces
fall in places where they would threat each other based on chess
rules. I should repeat this 10 000 times and find out how often a
threatening situation is met. Pieces should not fall in the same
square.
This is done in simulate_rooks and simulate_queens with the following code
for _ in range(number_trials):
position1, position2 = random_positions()
Where random_positions uses random.sample (without replacement) to select two positions out of all possible positions
My assumption is that the approach in 3a should be that if either the first digit or last digit are same for both chess pieces the situation
This is done with the code in can_rook_attack
return position1[0] == position2[0] or position1[1] == position2[1]
the approach for 3b is, on the other hand, a big question mark due to the fact that queens can also move diagonally.
This is solved using:
return abs(r1 - r2) == abs(c1 - c2)
This uses the constraint that to be along the diagonal the slope has to be +/-1 which leads to the above expression.

Related

Program about ask to write teams and points, save them and show the result (Python)

I have tried to make a program that works like this:
I request to write matches of a paddle league in this format:
{TeamNameA} {TeamWonSetsA} {TeamNameB} {TeamWonSetsB}
All of that is written on the same line, but each match is written on a different line. In each match, the team with the most sets wins. Every time a team wins a match, they add 3 points, and if they lose, 0.
I must save the names of all the teams that I write because a team can appear in different matches. In addition, as a team can appear in several matches, I also have to save and update their score (if they have won 1 match, they will have 3 points , but if they later wins another match, it will increase to 6 points).
I have to enter matches all the time until I enter the word END, and at that time it will stop asking me for matches and will show the results with the winner (which will be the team with the most points), and then it will show a ranking of team scores.
Since it is somewhat confusing, I give you an example: I am going to enter 3 matches and then the word END, and then it will show the results:
Gachon 1 PitGuss 4
//As the "PitGuss" team has more sets, they win the match and get 3 points
Picasos 2 Redbull 3
//The "Redbull" team wins, for which they get 3 points
PitGuss 4 Shirts 3
//The PitGuss team wins again, so they now have 6 points
END
//When entering this, the program stops asking me for matches and displays the results
Winning team: "PitGuss"
League Results:
PitGuss- 6 points
Redbull - 3 points
Gachon - 0 points
//The ranking is of the 3 best teams, so it isn't necessary to put all the teams with 0 points
And basically that would be the program, put several matches until writing END, the teams earn points by winning matches, and then show the result with a ranking of 3 teams maximum.
This is what I have of code:
while team1 != END:
team1, points1, team2, points2 = input().split()
if points1 > points2
team1punct = 3
else:
team2punct = 3
else:
print("Winning team:")
print("League Results:")
Basically what I have is very little, because no matter how much I search in Google and on this page, I don't understand how the "while" works in Python for constantly asking me for matches until I put END, to save the teams and points maybe I could use a bidimensional array, but I don't know exactly how to fill the array when entering the data (teams and points). And well, in the part where I print the results I haven't written any team either because to begin with I don't know how to select the winning team if I don't know how to add points when they win matches.
I know I'm asking for a lot of help, but I've been searching and learning about Python and I can't make this program, so if someone could help me I'd be incredibly grateful.
This may not be too helpful but I hope it helps you understand the while loop further;
while True: #Will re-iterate until closed by choice
teamANDscore = [] #Resets teams and scores stored
for x in range(2): #Gets 2 teams and their scores respectively
teamANDscore.append([input("Team " + str(x+1) + " Name: "), input("Team " + str(x+1) + " Sets Won: ")])
if teamANDscore[0][1] > teamANDscore[1][1]: #If first team wins
print(teamANDscore[0][0] + " won and gained 3 points!")
elif teamANDscore[0][1] < teamANDscore[1][1]: #If second team wins
print(teamANDscore[0][0] + " won and gained 3 points!")
else:
print("It was a draw!")
#To stop re-iterating you must use break
break
That would show one way of checking which team has won a match, hope it helps.

Optimize permutations search loop (can't use itertools) that is extremely slow. Any suggestions?

This is a game where you have 12 cards and you pick you until you choose 3 from the same group. I am attempting to find the probability of choosing each group. The script that I have created works, but it is extremely slow. My coworker created a similar script in R without the functions and his script takes 1/100th the time that mine takes. I am just trying to figure out why. Any ideas would be greatly appreciated.
from collections import Counter
import pandas as pd
from datetime import datetime
weight = pd.read_excel('V01Weights.xlsx')
Weight looks like the following:
Symb Weight
Grand 170000
Grand 170000
Grand 105
Major 170000
Major 170000
Major 215
Minor 150000
Minor 150000
Minor 12000
Bonus 105000
Bonus 105000
Bonus 105000
Max Picks represents the total number of different "cards". Total Picks represents the max number of user choices. This is because after 8 choices, you are guaranteed to have 2 of each type so on the 9th pick, you are guaranteed to have 3 matching.
TotalPicks = 9
MaxPicks = 12
This should have been named PickedProbabilities.
Picks = {0:0,1:0,2:0,3:0}
This is my simple version of the timeit class because I don't like the timeit class
def Time_It(function):
start =datetime.now()
x = function()
finish = datetime.now()
TotalTime = finish - start
Minutes = int(TotalTime.seconds/60)
Seconds = TotalTime.seconds % 60
print('It took ' + str(Minutes) + ' minutes and ' + str(Seconds) + ' seconds')
return(x)
Given x(my picks in order) I find the probability. These picks are done without replacement
def Get_Prob(x,weight):
prob = 1
weights = weight.iloc[:,1]
for index in x:
num = weights[index]
denom = sum(weights)
prob *= num/denom
weights.drop(index, inplace = True)
# print(weights)
return(prob)
This is used to determine if there are duplicates in my loop because that is not allowed
def Is_Allowed(x):
return(len(x) == len(set(x)))
This determines if a win is present in all of the cards present thus far.
def Is_Win(x):
global Picks
WinTypes = [[0,1,2],[3,4,5],[6,7,8],[9,10,11]]
IsWin = False
for index,item in enumerate(WinTypes):
# print(index)
if set(item).issubset(set(x)):
IsWin = True
Picks[index] += Get_Prob(x,weight)
# print(Picks[index])
print(sum(Picks.values()))
break
return(IsWin)
This is my main function that cycles through all of the cards. I attempted to do this using recursion but I eventually gave up. I can't use itertools to create all of the permutations because for example [0,1,2,3,4] will be created by itertools but this is not possible because once you get 3 matching, the game ends.
def Cycle():
for a in range(MaxPicks):
x = [a]
for b in range(MaxPicks):
x = [a,b]
if Is_Allowed(x):
for c in range(MaxPicks):
x = [a,b,c]
if Is_Allowed(x):
if Is_Win(x):
# print(x)
continue
for d in range(MaxPicks):
x = [a,b,c,d]
if Is_Allowed(x):
if Is_Win(x):
# print(x)
continue
for e in range(MaxPicks):
x = [a,b,c,d,e]
if Is_Allowed(x):
if Is_Win(x):
continue
for f in range(MaxPicks):
x = [a,b,c,d,e,f]
if Is_Allowed(x):
if Is_Win(x):
continue
for g in range(MaxPicks):
x = [a,b,c,d,e,f,g]
if Is_Allowed(x):
if Is_Win(x):
continue
for h in range(MaxPicks):
x = [a,b,c,d,e,f,g,h]
if Is_Allowed(x):
if Is_Win(x):
continue
for i in range(MaxPicks):
if Is_Allowed(x):
if Is_Win(x):
continue
Calls the main function
x = Time_It(Cycle)
print(x)
writes the probabilities to a text file
with open('result.txt','w') as file:
# file.write(pickle.dumps(x))
for item in x:
file.write(str(item) + ',' + str(x[item]) + '\n')
My coworker created a similar script in R without the functions and his script takes 1/100th the time that mine takes.
Two easy optimizations:
1) In-line the function calls like Is_Allowed() because Python have a lot of function call overhead (such as creating a new stackframe and argument tuples).
2) Run the code in using pypy which is really good at optimizing functions like this one.
Ok, this time I hope I got your problem right:)
There are two insights (I guess you have them, just for the sake of the completeness) needed in order to speed up your program algorithmically:
The probabilities for the sequence (card_1, card_2) and (card_2, card_1) are not equal, so we cannot use the results from the urn problem, and it looks like we need to try out all permutations.
However, given a set of cards we picked so far, we don't really need the information in which sequence they where picked - it is all the same for the future course of the game. So it is enough to use dynamic programming and calculate the probabilities for every subset to be traversed during the game (thus we need to check 2^N instead of N! states).
For a set of picked cards set the probability to pick a card i in the next turn is:
norm:=sum Wi for i in set
P(i|set)=Wi/norm if i not in set else 0.0
The recursion for calculating P(set) - the probability that a set of picked card occured during the game is:
set_without_i:=set/{i}
P(set)=sum P(set_without_i)*P(i|set_without_i) for i in set
However this should be done only for set_without_i for which the game not ended yet, i.e. no group has 3 cards picked.
This can be done by means of recursion+memoization or, as my version does, by using bottom-up dynamic programming. It also uses binary representation of integers for representations of sets and (most important part!) returns the result almost instantly [('Grand', 0.0014104762718021384), ('Major', 0.0028878988709489244), ('Minor', 0.15321793072867956), ('Bonus', 0.84248369412856905)]:
#calculates probability to end the game with 3 cards of a type
N=12
#set representation int->list
def decode_set(encoded):
decoded=[False]*N
for i in xrange(N):
if encoded&(1<<i):
decoded[i]=True
return decoded
weights = [170000, 170000, 105, 170000, 170000, 215, 150000, 150000, 12000, 105000, 105000, 105000]
def get_probs(decoded_set):
denom=float(sum((w for w,is_taken in zip(weights, decoded_set) if not is_taken)))
return [w/denom if not is_taken else 0.0 for w,is_taken in zip(weights, decoded_set)]
def end_group(encoded_set):
for i in xrange(4):
whole_group = 7<<(3*i) #7=..000111, 56=00111000 and so on
if (encoded_set & whole_group)==whole_group:
return i
return None
#MAIN: dynamic program:
MAX=(1<<N)#max possible set is 1<<N-1
probs=[0.0]*MAX
#we always start with the empty set:
probs[0]=1.0
#building bottom-up
for current_set in xrange(MAX):
if end_group(current_set) is None: #game not ended yet!
decoded_set=decode_set(current_set)
trans_probs=get_probs(decoded_set)
for i, is_set in enumerate(decoded_set):
if not is_set:
new_set=current_set | (1<<i)
probs[new_set]+=probs[current_set]*trans_probs[i]
#filtering wins:
group_probs=[0.0]*4
for current_set in xrange(MAX):
group_won=end_group(current_set)
if group_won is not None:
group_probs[group_won]+=probs[current_set]
print zip(["Grand", "Major", "Minor", "Bonus"], group_probs)
Some explanation of the "tricks" used in code:
A pretty standard trick is to use integer's binary representation to encode a set. Let's say we have objects [a,b,c], so we could represent the set {b,c} as 110, which would mean a (first in the list corresponds to 0- the lowest digit) - not in the set, b(1) in the set, c(1) in the set. However, 110 read as integer it is 6.
The current_set - for loop simulates the game and best understood while playing. Let's play with two cards [a,b] with weights [2,1].
We start the game with an empty set, 0 as integer, so the probability vector (given set, its binary representation and as integer mapped onto probability):
probs=[{}=00=0->1.0, 01={a}=1->0.0, {b}=10=2->0.0, {a,b}=11=3->0.0]
We process the current_set=0, there are two possibilities 66% to take card a and 33% to take cardb, so the probabilities become after the processing:
probs=[{}=00=0->1.0, 01={a}=1->0.66, {b}=10=2->0.33, {a,b}=11=3->0.0]
Now we process the current_set=1={a} the only possibility is to take b so we will end with set {a,b}. So we need to update its (3={a,b}) probability via our formula and we get:
probs=[{}=00=0->1.0, 01={a}=1->0.66, {b}=10=2->0.33, {a,b}=11=3->0.66]
In the next step we process 2, and given set {b} the only possibility is to pick card a, so probability of set {a,b} needs to be updated again
probs=[{}=00=0->1.0, 01={a}=1->0.66, {b}=10=2->0.33, {a,b}=11=3->1.0]
We can get to {a,b} on two different paths - this could be seen in our algorithm. The probability to go through set {a,b} at some point in our game is obviously 1.0.
Another important thing: all paths that leads to {a,b} are taken care of before we process this set (it would be the next step).
Edit: I misunderstood the original problem, the here presented solution is for the following problem:
Given 4 groups with 3 different cards with a different score for every card, we pick up cards as long as we don't have picked 3 cards from the same group. What is the expected score(sum of scores of picked cards) in the end of the game.
I leave the solution as it is, because it was such a joy to work it out after so many probability-theory-less years and I just cannot delete it:)
See my other answer for handling of the original problem
There are two possibilities to improve the performance: making the code faster (and before starting this, one should profile in order to know which part of the program should be optimized, otherwise the time is spent optimizing things that don't count) or improving the algorithm. I propose to do the second.
Ok, this problem seems to be more complex as at the first site. Let's start with some observations.
All you need to know is the expected number of the picked cards at the end of the game:
If Pi is the probability that the card i is picked somewhere during the game, then we are looking for the expected value of the score E(Score)=P1*W1+P2*W2+...Pn*Wn. However, if we look at the cards of a group, we can state that because of the symmetry the probabilities for the cards of this group are the same, e.g. P1=P2=P3=:Pgrand in your case. Thus our expectation can be calculated:
E(Score)=3*Pgrand*(W1+W2+W3)/3+...+3*Pbonus*(W10+W11+W12)/3
We call averageWgrand:=(W1+W2+W3)/3 and note that E(#grand)=3*Pgrand - the expected number of picked grand card at the end of the game. With this our formula becomes:
E(Score)=E(#grand)*averageWgrand+...+E(#bonus)*averageWbonus
In your example we can go even further: The number of cards in every group is equal, so because of the symmetry we can claim: E(#grand)=E(#minor)=E(#major)=E(#grand)=:(E#group). For the sake of simplicity, in the following we consider only this special case (but the outlined solution could be extended also to the general case). This lead to the following simplification:
E(Score)=4*E(#group)(averageWgrand+...+averageWbonus)/4
We call averageW:=(averageWgrand+...+averageWbonus)/4 and note that E(#cards)=4*E(#grand) is the expected number of picked card at the end of the game.
Thus, E(Score)=E(#cards)*averageW, so our task is reduced to calculating the expected value of the number of cards at the end of the game:
E(#cards)=P(1)*1+P(2)*2+...P(n)*n
where P(i) denotes the probability, that the game ends with exact i cards. The probabilities P(1),P(2) and P(k), k>9 are easy to see - they are 0.
Calculation of the probability of ending the game with i picked cards -P(i):
Let's play a slightly different game: we pick exactly i cards and win if and only if:
There is exactly one group with 3 cards picked. We call this group full_group.
The last picked (i-th) card was from the full_group.
It is easy to see, that the probability to win this game P(win) is exactly the probability we are looking for - P(i). Once again we can use the symmetry, because all groups are equal (P(win, full=grand) means the probability that we what and that the full_group=grand):
P(win)=P(win, grand)+P(win, minor)+P(win, major)+P(win, bonus)
=4*P(win, grand)
P(win, grand) is the probability that:
after picking i-1 cards the number of picked grand cards is 2, i.e. `#grand=2' and
after picking i-1 cards, for every group the number of picked cards is less than 3 and
we pick a grand-card in the last round. Given the first two constraints hold, this (conditional) probability is 1/(n-i+1) (there are n-i+1 cards left and only one of them is "right").
From the urn problem we know the probability for
P(#grand=u, #minor=x, #major=y, #bonus=z) = binom(3,u)*binom(3,x)*binom(3,y)*binom(3,z)/binom(12, u+x+y+z)
with binom(n,k)=n!/k!/(n-k)!. Thus P(win, grand) can be calculated as:
P(win, grand) = 1/(n-i+1)*sum P(#grand=2, #minor=x, #major=y, #bonus=z)
where x<=2, y<=2, z<=2 and 2+x+y+z=i-1
And now the code:
import math
def binom(n,k):
return math.factorial(n)//math.factorial(k)//math.factorial(n-k)
#expected number of cards:
n=12 #there are 12 cards
probs=[0]*n
for minor in xrange(3):
for major in xrange(3):
for bonus in xrange(3):
i = 3 + minor +major +bonus
P_urn = binom(3,2)*binom(3,minor)*binom(3,major)*binom(3,bonus)/float(binom(n, n-i+1))
P_right_last_card = 1.0/(n-i+1)
probs[i]+=4*P_urn*P_right_last_card #factor 4 from symmetry
print "Expected number of cards:", sum((prob*card_cnt for card_cnt, prob in enumerate(probs)))
As result I get 6.94285714286 as the expected number of cards in the end of the game. And very fast - almost instantly. Not sure whether it is right though...
Conclusion:
Obviously, if you like to handle a more general case (more groups, number cards in a group different) you have to extend the code (recursion, memoization of binom) and the theory.
But the most crucial part: with this approach you (almost) don't care in which order the cards were picked - and thus the number of states you have to inspect is down by factor of (k-1)! where k is the maximal possible number of cards in the end of the game. In your example k=9 and thus the approach is faster by factor 40000 (I don't even consider the speed-up from the exploited symmetry, because it might not be possible in general case).

Dungeon Game Solution explanation

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.

Lights Out in Python- How to Get Adjacent Tiles in a one-dimensional array?

My next python project is Lights Out using python and pygame. I need some help with beginning. I have a system that creates an empty board, then creates 25 index positions, and sets them all to false except a few, like so:
board = []
for x in range(0, 25):
board.append(False)
for x in range(0, random.randint(3,8)):
board[x] = True
random.shuffle(board)
Then the program can use the list to read the state for rectangles in a 5x5 grid. But how do I find adjacent tiles?
Lights out consists of a grid of tiles that can be on or off, similar to many cellular animations. A board is typically 5x5, and a certain number of tiles start off as "on". When a player clicks a tile, the state of the tile flips, as well as all the orthogonally adjacent tiles.
To get adjacent tiles, should I make a dictionary with numbers 0 through 24 that correspond to tiles they're next to? There must be a simpler way. Research turned up nothing concerning 1D arrays, only 2D arrays. Modeling this.
Having a board of size MXM we can write this short algorithm:
def getIndexOfNeighoringTiles(i):
neighbors = []
if (i % M != 0):
neighbors.append(i-1)
if (i % M != (M-1)):
neighbors.append(i+1)
if (i / M != 0):
neighbors.append(i-M)
if (i / M != (M-1)):
neighbors.append(i+M)
return neighbors
To explain a bit, we have 4 edge cases where we want to exclude certain indexes.

connect 4 minimax algorithm: one for loop

I'm trying to write the minimax algorithm in python with one for loop (yes I know wikipedia says the min and max players are often treated separately), and I'm using the variable turn to keep track of whether the min or max player is currently exploring options. I think, however, that at present the code wrongly evaluates for X when it is the O player's turn and O when it is the X player's turn.
Here's the source (p12) : http://web.cs.wpi.edu/~rich/courses/imgd4000-d10/lectures/E-MiniMax.pdf
Things you might be wondering about:
b is a list of lists; 0 denotes an available space
evaluate is used both for checking for a victory (by default) as well as for scoring the board for a particular player (we look for places where the value of a cell on the board ).
makeMove returns the row of the column the piece is placed in (used for subsequent removal)
Any help would be very much appreciated. Please let me know if anything is unclear.
def minMax(b, turn, depth=0):
player, piece = None, None
best, move = None, -1
if turn % 2 == 0 : # even player is max player
player, piece = 'max', 'X'
best, move = -1000, -1
else :
player, piece = 'min', 'O'
best, move = 1000, -1
if boardFull(b) or depth == MAX_DEPTH:
return evaluate(b, False, piece)
for col in range(N_COLS):
if possibleMove(b, col) :
row = makeMove(b, col, piece)
turn += 1 # now the other player's turn
score = minMax(b, turn, depth+1)
if player == 'max':
if score > best:
best, move = score, col
else:
if score < best:
best, move = score, col
reset(b, row, col)
return move
#seaotternerd. Yes I was wondering about that. But I'm not sure that is the problem. Here is one printout. As you can see, X has been dropped in the fourth column by AI but is evaluating from the min player's perspective (it counts 2 O units in the far right column).
Here's what the evaluate function determines, depending on piece:
if piece == 'O':
return best * -25
return best * 25
You are incrementing turn every time that you find a possible move and not undoing it. As a result, when control returns to a given minMax call, turn is 1 greater than it was before. Then, the next time your program finds a possible move, it increments turn again. This will cause the next call to minMax to select the wrong player as the current one. Overall, I believe this will result in the board getting evaluated for the wrong player approximately half the time. You can fix this by adding 1 to turn in the recursive call to minMax(), rather than by changing the value stored in the variables:
row = makeMove(b, col, piece)
score = minMax(b, turn+1, depth+1)
EDIT: Digging deeper into your code, I'm finding a number of additional problems:
MAX_DEPTH is set to 1. This will not allow the ai to see its own next move, instead forcing it to make decisions solely based on getting in the way of the other player.
minMax() returns the score if it has reached MAX_DEPTH or a win condition, but otherwise it returns a move. This breaks propagation of the score back up the recursion tree.
This is not critical, but it's something to keep in mind: your board evaluation function only takes into account how long a given player's longest string is, ignoring how the other player is doing and any other factors that may make one placement better than another. This mostly just means that your AI won't be very "smart."
EDIT 2: A big part of the problem with the way that you're keeping track of min and max is in your evaluation function. You check to see if each piece has won. You are then basing the score of that board off of who the current player is, but the point of having a min player and a max player is that you don't need to know who the current player is to evaluate the board. If max has won, the score is infinity. If min has won, the score is -infinity.
def evaluate(b, piece):
if evaluate_aux(b, True, 'X'):
return 100000
if evaluate_aux(b, True, 'O'):
return -100000
return evaluate_aux(b, False, piece)
In general, I think there is a lot that you could do to make your code cleaner and easier to read, which would make it a lot easier to detect errors. For instance, if you are saying that "X" is always max and "Y" is always min, then you don't need to bother keeping track of both player and piece. Additionally, having evaluate_aux sometimes return a boolean and sometimes return an int is confusing. You could just have it count the number of each piece in a row, for instance, with contiguous "X"s counting positive and contiguous "O"s counting negative and sum the scores; an evaluation function isn't supposed to be from one player's perspective or the other. Obviously you would still need to have a check for win conditions in there. This would also address point 3.
It's possible that there are more problems, but like I said, this code is not particularly easy to wade through. If you fix the things that I've already found and clean it up, I can take another look.

Categories