-
-
Notifications
You must be signed in to change notification settings - Fork 48.7k
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 11 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,218 @@ | ||
from random import choice | ||
Check failure on line 1 in maths/Game Theory/AlphaBetaPruning/alphabetapruning.py
|
||
from math import inf | ||
|
||
board = [[0, 0, 0], [0, 0, 0], [0, 0, 0]] | ||
|
||
|
||
def Gameboard(board): | ||
chars = {1: "X", -1: "O", 0: " "} | ||
for x in board: | ||
for y in x: | ||
ch = chars[y] | ||
print(f"| {ch} |", end="") | ||
print("\n" + "---------------") | ||
print("===============") | ||
|
||
|
||
def Clearboard(board): | ||
|
||
for x, row in enumerate(board): | ||
for y, col in enumerate(row): | ||
board[x][y] = 0 | ||
|
||
|
||
def winningPlayer(board, player): | ||
|
||
conditions = [ | ||
[board[0][0], board[0][1], board[0][2]], | ||
[board[1][0], board[1][1], board[1][2]], | ||
[board[2][0], board[2][1], board[2][2]], | ||
[board[0][0], board[1][0], board[2][0]], | ||
[board[0][1], board[1][1], board[2][1]], | ||
[board[0][2], board[1][2], board[2][2]], | ||
[board[0][0], board[1][1], board[2][2]], | ||
[board[0][2], board[1][1], board[2][0]], | ||
] | ||
|
||
if [player, player, player] in conditions: | ||
return True | ||
|
||
return False | ||
|
||
|
||
def gameWon(board): | ||
|
||
return winningPlayer(board, 1) or winningPlayer(board, -1) | ||
|
||
|
||
def printResult(board): | ||
|
||
if winningPlayer(board, 1): | ||
print("X has won! " + "\n") | ||
|
||
elif winningPlayer(board, -1): | ||
print("O's have won! " + "\n") | ||
|
||
else: | ||
print("Draw" + "\n") | ||
|
||
|
||
def blanks(board): | ||
|
||
blank = [] | ||
for x, row in enumerate(board): | ||
for y, col in enumerate(row): | ||
if board[x][y] == 0: | ||
blank.append([x, y]) | ||
|
||
return blank | ||
|
||
|
||
def boardFull(board): | ||
|
||
if len(blanks(board)) == 0: | ||
return True | ||
return False | ||
|
||
|
||
def setMove(board, x, y, player): | ||
|
||
board[x][y] = player | ||
|
||
|
||
def playerMove(board): | ||
|
||
e = True | ||
moves = { | ||
1: [0, 0], | ||
2: [0, 1], | ||
3: [0, 2], | ||
4: [1, 0], | ||
5: [1, 1], | ||
6: [1, 2], | ||
7: [2, 0], | ||
8: [2, 1], | ||
9: [2, 2], | ||
} | ||
while e: | ||
try: | ||
move = int(input("Enter a number between 1-9: ")) | ||
if move < 1 or move > 9: | ||
print("Invalid Move! Try again!") | ||
elif not (moves[move] in blanks(board)): | ||
print("Invalid Move! Try again!") | ||
else: | ||
setMove(board, moves[move][0], moves[move][1], 1) | ||
Gameboard(board) | ||
e = False | ||
except (KeyError, ValueError): | ||
print("Enter a number!") | ||
|
||
|
||
def getScore(board): | ||
|
||
if winningPlayer(board, 1): | ||
return 10 | ||
|
||
elif winningPlayer(board, -1): | ||
return -10 | ||
|
||
else: | ||
return 0 | ||
|
||
|
||
def abminimax(board, depth, alpha, beta, player): | ||
|
||
row = -1 | ||
col = -1 | ||
if depth == 0 or gameWon(board): | ||
return [row, col, getScore(board)] | ||
|
||
else: | ||
for cell in blanks(board): | ||
setMove(board, cell[0], cell[1], player) | ||
score = abminimax(board, depth - 1, alpha, beta, -player) | ||
if player == 1: | ||
# X is always the max player | ||
if score[2] > alpha: | ||
alpha = score[2] | ||
row = cell[0] | ||
col = cell[1] | ||
|
||
else: | ||
if score[2] < beta: | ||
beta = score[2] | ||
row = cell[0] | ||
col = cell[1] | ||
|
||
setMove(board, cell[0], cell[1], 0) | ||
|
||
if alpha >= beta: | ||
break | ||
|
||
if player == 1: | ||
return [row, col, alpha] | ||
|
||
else: | ||
return [row, col, beta] | ||
|
||
|
||
def o_comp(board): | ||
|
||
if len(blanks(board)) == 9: | ||
x = choice([0, 1, 2]) | ||
y = choice([0, 1, 2]) | ||
setMove(board, x, y, -1) | ||
Gameboard(board) | ||
|
||
else: | ||
result = abminimax(board, len(blanks(board)), -inf, inf, -1) | ||
setMove(board, result[0], result[1], -1) | ||
Gameboard(board) | ||
|
||
|
||
def x_comp(board): | ||
|
||
if len(blanks(board)) == 9: | ||
x = choice([0, 1, 2]) | ||
y = choice([0, 1, 2]) | ||
setMove(board, x, y, 1) | ||
Gameboard(board) | ||
|
||
else: | ||
result = abminimax(board, len(blanks(board)), -inf, inf, 1) | ||
setMove(board, result[0], result[1], 1) | ||
Gameboard(board) | ||
|
||
|
||
def makeMove(board, player, mode): | ||
|
||
if mode == 1: | ||
if player == 1: | ||
playerMove(board) | ||
|
||
else: | ||
o_comp(board) | ||
else: | ||
if player == 1: | ||
o_comp(board) | ||
else: | ||
x_comp(board) | ||
|
||
|
||
def pvc(): | ||
|
||
while True: | ||
try: | ||
order = int(input("Enter to play 1st or 2nd: ")) | ||
if not (order == 1 or order == 2): | ||
print("Please pick 1 or 2") | ||
else: | ||
break | ||
except (KeyError, ValueError): | ||
print("Enter a number") | ||
|
||
Clearboard(board) | ||
if order == 2: | ||
currentPlayer = -1 | ||
|
||
else: | ||
currentPlayer = 1 | ||
|
||
|
||
while not (boardFull(board) or gameWon(board)): | ||
makeMove(board, currentPlayer, 1) | ||
currentPlayer *= -1 | ||
|
||
printResult(board) | ||
|
||
|
||
# Driver Code | ||
print("=================================================") | ||
print("TIC-TAC-TOE using MINIMAX with ALPHA-BETA Pruning") | ||
print("=================================================") | ||
pvc() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
# Alpha-Beta Pruning | ||
|
||
An optimization technique for the minimax algorithm that reduces the number of nodes evaluated by eliminating branches that won't affect the final decision (basically an upgrade of minimax algorithm) | ||
|
||
As we have seen in the minimax search algorithm that the number of game states it has to examine are exponential in depth of the tree. Since we cannot eliminate the exponent, but we can cut it to half. Hence there is a technique by which without checking each node of the game tree we can compute the correct minimax decision, and this technique is called pruning. This involves two threshold parameter Alpha and beta for future expansion, so it is called alpha-beta pruning. It is also called as Alpha-Beta Algorithm. Alpha-beta pruning can be applied at any depth of a tree, and sometimes it not only prunes the tree leaves but also entire sub-tree. The two-parameter can be defined as: | ||
|
||
1. Alpha: The best (highest-value) choice we have found so far at any point along the path of Maximizer. The initial value of alpha is -∞. | ||
2. Beta: The best (lowest-value) choice we have found so far at any point along the path of Minimizer. The initial value of beta is +∞. The Alpha-beta pruning to a standard minimax algorithm returns the same move as the standard algorithm does, but it removes all the nodes which are not really affecting the final decision but making algorithm slow. Hence by pruning these nodes, it makes the algorithm fast. | ||
## Acknowledgements | ||
|
||
- [Original Author](https://github.com/anmolchandelCO180309) | ||
- [Wiki](https://en.wikipedia.org/wiki/Alpha%E2%80%93beta_pruning) | ||
|
||
#### /// The alphabetapruning.py file has a Tic-Tac-Toe game implemented with a good explanation /// |
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) | ||
|
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/AlphaBetaPruning/alphabetapruning.py
, please provide doctest for the functionGameboard
Variable and function names should follow the
snake_case
naming convention. Please update the following name accordingly:Gameboard
Please provide return type hint for the function:
Gameboard
. If the function does not return a value, please provide the type hint as:def function() -> None:
Please provide type hint for the parameter:
board