-
-
Notifications
You must be signed in to change notification settings - Fork 46.8k
minimax algorithm added #11678
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
minimax algorithm added #11678
Changes from all commits
0583a25
02e2eb8
c383eeb
e1a6701
27ff760
3580f82
e328c5e
3fe75e0
057380d
b1b3826
ca661e4
ad651d4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
|
||
# Minimax Algorithm | ||
|
||
A decision-making algorithm for two-player games to minimize the maximum possible loss (This is a simple, recursive, implementation of the MiniMax algorithm in Python) | ||
|
||
MiniMax is used in decision, game theory, statistics and philosophy. It can be implemented on a two player's game given full information of the states like the one offered by games like Tic Tac Toe. That means MM cannot be used in games that feature randomness like dices. The reason is that it has to be fully aware of all possible moves/states during gameplay before it makes its mind on the best move to play. | ||
|
||
The following implementation is made for the cube/sticks game: User sets an initial number of cubes available on a table. Both players (the user & the PC implementing MM) can pick up a number of cubes off the table in groups of 1, 2 or K. K is also set by the user. The player who picks up the last remaining cubes from the table on a single take wins the game. | ||
|
||
The MiniMax algorithm is being implemented for the PC player and it always assume that the opponent (user) is also playing optimum. MM is fully aware of the remaining cubes and its valid moves at all states. So technically it will recursively expand the whole game tree and given the fact that the amount of possible moves are three (1,2,K), all tree nodes will end up with 3 leaves, one for each option. | ||
|
||
Game over is the case where there are no available cubes on the table or in the case of a negative amount of cubes. The reason for the negative scenario is due to the fact that MM will expand the whole tree without checking if all three options are allowed during a state. In a better implementation we could take care of that scenario as we also did on the user side. No matter what, if MM’s move lead to negative cubes he will lose the game. | ||
|
||
Evaluation starts on the leaves of the tree. Both players alternate during game play so each layer of the tree marks the current player (MAX or MIN). That way the evaluation function can set a higher/positive value if player MAX wins and a lower/negative value if he loses (remember evaluation happens from the MiniMax’s perspective so he will be the MAX player). When all leaves get their evaluation and thanks to the recursive implementation of the algorithm, their values climb up on each layer till the root of the tree also gets evaluated. That way MAX player will try to lead the root to get the highest possible value, assuming that MIN player (user) will try its best to lead to the lowest value possible. When the root gets its value, MAX player (who will be the first one to play) knows what move would lead to victory or at least lead to a less painful loss. | ||
|
||
So the goal of MiniMax is to minimize the possible loss for a worst case scenario, from the algorithm's perspective. | ||
|
||
|
||
|
||
/// There is an example code implemented with deatailed explanation in the minimax.py file /// | ||
|
||
|
||
|
||
|
||
## Acknowledgements | ||
|
||
- [Original Author](https://github.com/savvasio) | ||
- [Wiki](https://en.wikipedia.org/wiki/Minimax) | ||
- [Video Explanation](https://www.youtube.com/watch?v=l-hh51ncgDI) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,183 @@ | ||
# ==================== 0. Evaluation & Utilities ================== | ||
|
||
# If the amount of cubes on the table is 0, the last player to pick up cubes off the table is the winner. | ||
# State evaluation is set on the MAX player's perspective (PC), so if he wins he gets eval +100. If he loses, his eval is set to -100. | ||
# In states with a negative amount of cubes availiable on the table, the last person played is the loser. | ||
# If the current state is not final, we don't care on the current evaluation so we simply initialise it to 0. | ||
|
||
|
||
def evaluate(state, player): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As there is no test file in this pull request nor any test function or class in the file Please provide return type hint for the function: Please provide type hint for the parameter: Please provide type hint for the parameter: |
||
if state == 0: | ||
if -player == MAX: | ||
return +100 | ||
else: | ||
return -100 | ||
elif state < 0: | ||
if -player == MAX: | ||
return -100 | ||
else: | ||
return +100 | ||
else: | ||
return 0 | ||
|
||
|
||
def gameOver(remainingCubes, player): | ||
Check failure on line 24 in maths/Game Theory/minimax/minimax.py
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As there is no test file in this pull request nor any test function or class in the file Variable and function names should follow the Please provide return type hint for the function: Variable and function names should follow the Please provide type hint for the parameter: Please provide type hint for the parameter: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As there is no test file in this pull request nor any test function or class in the file Variable and function names should follow the Please provide return type hint for the function: Variable and function names should follow the Please provide type hint for the parameter: Please provide type hint for the parameter: |
||
if remainingCubes == 0: | ||
if player == MAX: # If MAX's turn led to 0 cubes on the table | ||
print("=" * 20) | ||
print("Im sorry, you lost!") | ||
print("=" * 20) | ||
else: | ||
print("=" * 69) | ||
print("Hey congrats! You won MiniMax. Didnt see that coming!") | ||
print("=" * 69) | ||
return True | ||
|
||
|
||
# M input validation | ||
def validateM(message): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As there is no test file in this pull request nor any test function or class in the file Variable and function names should follow the Please provide return type hint for the function: Please provide type hint for the parameter: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As there is no test file in this pull request nor any test function or class in the file Variable and function names should follow the Please provide return type hint for the function: Please provide type hint for the parameter: |
||
while True: | ||
try: | ||
inp = input(message) | ||
if inp == "q" or inp == "Q": | ||
quit() # Exit tha game | ||
M = int(inp) | ||
except ValueError: | ||
print("Try again with an integer!") | ||
continue | ||
else: | ||
if M >= 4: # We can not accept less than 4 | ||
return M | ||
else: | ||
print("Please try again with an integer bigger than 3.") | ||
continue | ||
|
||
|
||
# K input validation | ||
def validateK(message): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As there is no test file in this pull request nor any test function or class in the file Variable and function names should follow the Please provide return type hint for the function: Please provide type hint for the parameter: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As there is no test file in this pull request nor any test function or class in the file Variable and function names should follow the Please provide return type hint for the function: Please provide type hint for the parameter: |
||
while True: | ||
try: | ||
inp = input(message) | ||
if inp == "q" or inp == "Q": | ||
quit() | ||
K = int(inp) | ||
except ValueError: | ||
print("Try again with an integer!") | ||
continue | ||
if (K > 2) and (K < M): # acceptable K limits are 2+1 & M-1 respectively. | ||
return K | ||
else: | ||
print(f"You need to insert an integer in the range of 3 to {M-1}!") | ||
|
||
|
||
# Game play input validation | ||
# Input is considered valid only if its one of the 3 availiable options and does not cause a negative amount of cubes on the table. | ||
def validateInput(message): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As there is no test file in this pull request nor any test function or class in the file Variable and function names should follow the Please provide return type hint for the function: Please provide type hint for the parameter: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As there is no test file in this pull request nor any test function or class in the file Variable and function names should follow the Please provide return type hint for the function: Please provide type hint for the parameter: |
||
while True: | ||
try: | ||
inp = input(message) | ||
if inp == "q" or inp == "Q": | ||
quit() | ||
inp = int(inp) # in the cause of not integer input it causes an error | ||
except ValueError: | ||
print(f"Try again with an integer!") | ||
continue | ||
if inp in choices: | ||
if M - inp >= 0: | ||
return inp # Accepted input | ||
else: | ||
print(f"There are no {inp} availiable cubes. Try to pick up less..") | ||
else: | ||
print(f"Wrong choice, try again. Availiable options are: 1 or 2 or {K}: ") | ||
|
||
|
||
def plural(choice): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As there is no test file in this pull request nor any test function or class in the file Please provide return type hint for the function: Please provide type hint for the parameter: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As there is no test file in this pull request nor any test function or class in the file Please provide return type hint for the function: Please provide type hint for the parameter: |
||
if choice == 1: | ||
return "cube" | ||
else: | ||
return "cubes" | ||
|
||
|
||
# ==================== 1. MiniMax for the optimal choice from MAX ================== | ||
# It recursively expands the whole tree and returns the list [score, move], | ||
# meaning the pair of best score tighten to the actual move that caused it. | ||
def MiniMax(state, player): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As there is no test file in this pull request nor any test function or class in the file Variable and function names should follow the Please provide return type hint for the function: Please provide type hint for the parameter: Please provide type hint for the parameter: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As there is no test file in this pull request nor any test function or class in the file Variable and function names should follow the Please provide return type hint for the function: Please provide type hint for the parameter: Please provide type hint for the parameter: |
||
if state <= 0: # Base case that will end recursion | ||
return [ | ||
evaluate(state, player), | ||
0, | ||
] # We really do not care on the move at this point | ||
|
||
availiableChoices = [] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Variable and function names should follow the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Variable and function names should follow the |
||
for i in range( | ||
len(choices) | ||
): # for every availiable choice/branch of the tree 1, 2 ή K | ||
score, move = MiniMax( | ||
state - choices[i], -player | ||
) # Again we dont care on the move here | ||
availiableChoices.append(score) | ||
|
||
if player == MAX: | ||
score = max(availiableChoices) | ||
move = [i for i, value in enumerate(availiableChoices) if value == score] | ||
# move list consists of all indexes where min or max shows up but we will | ||
# use only the 1st one. | ||
return [score, move[0]] | ||
else: | ||
score = min(availiableChoices) | ||
move = [i for i, value in enumerate(availiableChoices) if value == score] | ||
return [score, move[0]] | ||
|
||
|
||
# ====================== 2. MAIN EXECUTION ====================== | ||
print("+" * 126) | ||
print( | ||
"INSTUCTIONS: There are M availiable cubes on the table. Both players are allowed to remove 1, 2 or K cubes at the same time." | ||
) | ||
print( | ||
"You will set the M & K variables. Since tree prunning has not been implemented, its Minimax after all, we suggest you set M < 20 for the execution to be smooth." | ||
) | ||
print("Press q to exit the game.") | ||
print( | ||
"The player who removes the last cube off the table will be the winner. The first player is the PC. Good luck!" | ||
) | ||
print("+" * 126) | ||
|
||
MAX = +1 | ||
MIN = -1 | ||
M = validateM( | ||
"Please insert an initial number of cubes (M) availiable on the table: " | ||
) # M = state/depth/remainingCubes | ||
K = validateK( | ||
"Please insert an integer K, 2 < K < M, that will act as the 3rd option for the ammount of cubes both players can get off the table: " | ||
) | ||
choices = [1, 2, K] | ||
|
||
print( | ||
f"\nThe game begins with {M} cubes availiable on the table and each player can pick 1, 2 ή {K}:" | ||
) | ||
while M > 0: | ||
# ===== PC's turn ===== | ||
print("Please wait for the PC to make its mind..") | ||
score, move = MiniMax(M, MAX) | ||
M = M - choices[move] | ||
|
||
print( | ||
f"\nPc chose to remove {choices[move]} {plural(choices[move])} off the table. Remaining cubes are {M}." | ||
) | ||
if gameOver(M, MAX): | ||
break # Game over check | ||
|
||
# ===== Παίζει ο χρήστης ===== | ||
else: | ||
userChoice = validateInput( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Variable and function names should follow the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Variable and function names should follow the |
||
f"\nHow many cubes would you like to pick up (1, 2 ή {K}): " | ||
) | ||
# In valid the game goes on. In any other case it gets stacked on the validation function till a proper input is given. | ||
|
||
M = M - int(userChoice) | ||
print( | ||
f"\nYou chose to remove {userChoice} {plural(int(userChoice))} from the table. Remaining cubes are {M}." | ||
) | ||
if gameOver(M, MIN): | ||
break # Game over check. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As there is no test file in this pull request nor any test function or class in the file
maths/Game Theory/minimax/minimax.py
, please provide doctest for the functionevaluate
Please provide return type hint for the function:
evaluate
. If the function does not return a value, please provide the type hint as:def function() -> None:
Please provide type hint for the parameter:
state
Please provide type hint for the parameter:
player