Difficulty added when starting a game of fourinarow, on a scale of 1-5. The Hex functions are added, but right now they are mostly copies of the fourinarow functions, and do not work
340 lines
13 KiB
Python
340 lines
13 KiB
Python
import json
|
|
import random
|
|
import copy
|
|
import math
|
|
|
|
from . import fourInARowDraw
|
|
from funcs import logThis
|
|
|
|
AIScores = {
|
|
"middle": 3,
|
|
"two in a row": 10,
|
|
"three in a row": 50,
|
|
"enemy two in a row": -35,
|
|
"enemy three in a row": -200,
|
|
"enemy win": -10000,
|
|
"win": 1000,
|
|
"avoid losing": 100
|
|
}
|
|
|
|
rowCount = 6
|
|
columnCount = 7
|
|
easy = True
|
|
|
|
# Starts the game
|
|
def fourInARowStart(channel, user, opponent):
|
|
with open("resources/games/games.json", "r") as f:
|
|
data = json.load(f)
|
|
|
|
if user.lower() != opponent.lower():
|
|
if channel not in data["4 in a row games"]:
|
|
|
|
if opponent in ["1","2","3","4","5"]:
|
|
difficulty = int(opponent)
|
|
opponent = "Gwendolyn"
|
|
elif opponent in ["0","6","7","8","9","10","69","100","420"]:
|
|
return "That difficulty doesn't exist", False, False, False, False
|
|
else:
|
|
# Opponent is another player
|
|
difficulty = "NA"
|
|
|
|
board = [ [ 0 for i in range(columnCount) ] for j in range(rowCount) ]
|
|
players = [user,opponent]
|
|
random.shuffle(players)
|
|
|
|
data["4 in a row games"][channel] = {"board": board,"winner":0,"win direction":"",
|
|
"win coordinates":[0,0],"players":players,"turn":0,"difficulty":difficulty}
|
|
|
|
with open("resources/games/games.json", "w") as f:
|
|
json.dump(data,f,indent=4)
|
|
|
|
fourInARowDraw.drawImage(channel)
|
|
|
|
gwendoTurn = False
|
|
|
|
if players[0] == "Gwendolyn":
|
|
gwendoTurn = True
|
|
|
|
return "Started game against "+opponent+". It's "+players[0]+"'s turn", True, False, False, gwendoTurn
|
|
else:
|
|
return "There's already a 4 in a row game going on in this channel", False, False, False, False
|
|
else:
|
|
return "You can't play against yourself", False, False, False, False
|
|
|
|
# Places a piece at the lowest available point in a specific column
|
|
def placePiece(channel : str,player : int,column : int):
|
|
with open("resources/games/games.json", "r") as f:
|
|
data = json.load(f)
|
|
|
|
if channel in data["4 in a row games"]:
|
|
board = data["4 in a row games"][channel]["board"]
|
|
|
|
board = placeOnBoard(board,player,column)
|
|
|
|
|
|
if board != None:
|
|
data["4 in a row games"][channel]["board"] = board
|
|
turn = (data["4 in a row games"][channel]["turn"]+1)%2
|
|
data["4 in a row games"][channel]["turn"] = turn
|
|
|
|
with open("resources/games/games.json", "w") as f:
|
|
json.dump(data,f,indent=4)
|
|
|
|
logThis("Checking for win")
|
|
won, winDirection, winCoordinates = isWon(data["4 in a row games"][channel]["board"])
|
|
|
|
if won != 0:
|
|
gameWon = True
|
|
data["4 in a row games"][channel]["winner"] = won
|
|
data["4 in a row games"][channel]["win direction"] = winDirection
|
|
data["4 in a row games"][channel]["win coordinates"] = winCoordinates
|
|
|
|
message = data["4 in a row games"][channel]["players"][won-1]+" won."
|
|
if data["4 in a row games"][channel]["players"][won-1] != "Gwendolyn":
|
|
message += " Adding 20 GwendoBucks to their account."
|
|
elif 0 not in board[0]:
|
|
gameWon = True
|
|
message = "It's a draw!"
|
|
else:
|
|
gameWon = False
|
|
message = data["4 in a row games"][channel]["players"][player-1]+" placed a piece in column "+str(column+1)+". It's now "+data["4 in a row games"][channel]["players"][turn]+"'s turn."
|
|
|
|
with open("resources/games/games.json", "w") as f:
|
|
json.dump(data,f,indent=4)
|
|
|
|
gwendoTurn = False
|
|
|
|
if data["4 in a row games"][channel]["players"][turn] == "Gwendolyn":
|
|
logThis("It's Gwendolyn's turn")
|
|
gwendoTurn = True
|
|
|
|
fourInARowDraw.drawImage(channel)
|
|
return message, True, True, gameWon, gwendoTurn
|
|
else:
|
|
return "There isn't any room in that column", True, True, False, False
|
|
else:
|
|
return "There's no game in this channel", False, False, False, False
|
|
|
|
# Returns a board where a piece has been placed in the column
|
|
def placeOnBoard(board,player,column):
|
|
placementx, placementy = -1, column
|
|
|
|
for x in range(len(board)):
|
|
if board[x][column] == 0:
|
|
placementx = x
|
|
|
|
board[placementx][placementy] = player
|
|
|
|
if placementx == -1:
|
|
return None
|
|
else:
|
|
return board
|
|
|
|
# Parses command
|
|
def parseFourInARow(command, channel, user):
|
|
commands = command.split()
|
|
if command == "" or command == " ":
|
|
return "I didn't get that. Use \"!fourinarow start [opponent]\" to start a game. To play against the computer, use difficulty 1 through 5 as the [opponent].", False, False, False, False
|
|
elif commands[0] == "start":
|
|
# Starting a game
|
|
return fourInARowStart(channel,user,commands[1]) # commands[1] is the opponent
|
|
|
|
# Stopping the game
|
|
elif commands[0] == "stop":
|
|
with open("resources/games/games.json", "r") as f:
|
|
data = json.load(f)
|
|
|
|
if user in data["4 in a row games"][channel]["players"]:
|
|
return "Ending game.", False, False, True, False
|
|
else:
|
|
return "You can't end a game where you're not a player.", False, False, False, False
|
|
|
|
# Placing manually
|
|
elif commands[0] == "place":
|
|
try:
|
|
return placePiece(channel,int(commands[1]),int(commands[2])-1)
|
|
except:
|
|
return "I didn't get that. To place a piece use \"!fourinarow place [player number] [column]\" or press the corresponding message-reaction beneath the board.", False, False, False, False
|
|
else:
|
|
return "I didn't get that. Use \"!fourinarow start [opponent]\" to start a game. To play against the computer, use difficulty 1 through 5 as the [opponent].", False, False, False, False
|
|
|
|
# Checks if someone has won the game and returns the winner
|
|
def isWon(board):
|
|
won = 0
|
|
winDirection = ""
|
|
winCoordinates = [0,0]
|
|
|
|
for row in range(len(board)):
|
|
for place in range(len(board[row])):
|
|
if won == 0:
|
|
piecePlayer = board[row][place]
|
|
if piecePlayer != 0:
|
|
# Checks horizontal
|
|
if place <= columnCount-4:
|
|
pieces = [board[row][place+1],board[row][place+2],board[row][place+3]]
|
|
else:
|
|
pieces = [0]
|
|
|
|
if all(x == piecePlayer for x in pieces):
|
|
won = piecePlayer
|
|
winDirection = "h"
|
|
winCoordinates = [row,place]
|
|
|
|
# Checks vertical
|
|
if row <= rowCount-4:
|
|
pieces = [board[row+1][place],board[row+2][place],board[row+3][place]]
|
|
else:
|
|
pieces = [0]
|
|
|
|
if all(x == piecePlayer for x in pieces):
|
|
won = piecePlayer
|
|
winDirection = "v"
|
|
winCoordinates = [row,place]
|
|
|
|
# Checks right diagonal
|
|
if row <= rowCount-4 and place <= columnCount-4:
|
|
pieces = [board[row+1][place+1],board[row+2][place+2],board[row+3][place+3]]
|
|
else:
|
|
pieces = [0]
|
|
|
|
if all(x == piecePlayer for x in pieces):
|
|
won = piecePlayer
|
|
winDirection = "r"
|
|
winCoordinates = [row,place]
|
|
|
|
# Checks left diagonal
|
|
if row <= rowCount-4 and place >= 3:
|
|
pieces = [board[row+1][place-1],board[row+2][place-2],board[row+3][place-3]]
|
|
else:
|
|
pieces = [0]
|
|
|
|
if all(x == piecePlayer for x in pieces):
|
|
won = piecePlayer
|
|
winDirection = "l"
|
|
winCoordinates = [row,place]
|
|
|
|
|
|
return won, winDirection, winCoordinates
|
|
|
|
# Plays as the AI
|
|
def fourInARowAI(channel):
|
|
logThis("Figuring out best move")
|
|
with open("resources/games/games.json", "r") as f:
|
|
data = json.load(f)
|
|
|
|
board = data["4 in a row games"][channel]["board"]
|
|
player = data["4 in a row games"][channel]["players"].index("Gwendolyn")+1
|
|
difficulty = data["4 in a row games"][channel]["difficulty"]
|
|
|
|
scores = [-math.inf,-math.inf,-math.inf,-math.inf,-math.inf,-math.inf,-math.inf]
|
|
for column in range(0,columnCount):
|
|
testBoard = copy.deepcopy(board)
|
|
testBoard = placeOnBoard(testBoard,player,column)
|
|
if testBoard != None:
|
|
scores[column] = minimax(testBoard,difficulty,player%2+1,player,-math.inf,math.inf,False)
|
|
logThis("Best score for column "+str(column)+" is "+str(scores[column]))
|
|
|
|
possibleScores = scores.copy()
|
|
|
|
while (min(possibleScores) <= (max(possibleScores) - max(possibleScores)/10)) and len(possibleScores) != 1:
|
|
possibleScores.remove(min(possibleScores))
|
|
|
|
highest_score = random.choice(possibleScores)
|
|
|
|
indices = [i for i, x in enumerate(scores) if x == highest_score]
|
|
placement = random.choice(indices)
|
|
return placePiece(channel,player,placement)
|
|
|
|
# Calculates points for a board
|
|
def AICalcPoints(board,player):
|
|
score = 0
|
|
otherPlayer = player%2+1
|
|
|
|
# Adds points for middle placement
|
|
for row in range(len(board)):
|
|
if board[row][3] == player:
|
|
score += AIScores["middle"]
|
|
|
|
# Checks horizontal
|
|
for row in range(rowCount):
|
|
rowArray = [int(i) for i in list(board[row])]
|
|
for place in range(columnCount-3):
|
|
window = rowArray[place:place+4]
|
|
score += evaluateWindow(window,player,otherPlayer)
|
|
|
|
# Checks Vertical
|
|
for column in range(columnCount):
|
|
columnArray = [int(i[column]) for i in list(board)]
|
|
for place in range(rowCount-3):
|
|
window = columnArray[place:place+4]
|
|
score += evaluateWindow(window,player,otherPlayer)
|
|
|
|
# Checks right diagonal
|
|
for row in range(rowCount-3):
|
|
for place in range(columnCount-3):
|
|
window = [board[row][place],board[row+1][place+1],board[row+2][place+2],board[row+3][place+3]]
|
|
score += evaluateWindow(window,player,otherPlayer)
|
|
|
|
# Checks left diagonal
|
|
for row in range(rowCount-3):
|
|
for place in range(3,columnCount):
|
|
window = [board[row][place],board[row+1][place-1],board[row+2][place-2],board[row+3][place-3]]
|
|
score += evaluateWindow(window,player,otherPlayer)
|
|
|
|
|
|
## Checks if anyone has won
|
|
#won = isWon(board)[0]
|
|
|
|
## Add points if AI wins
|
|
#if won == player:
|
|
# score += AIScores["win"]
|
|
|
|
return score
|
|
|
|
|
|
def evaluateWindow(window,player,otherPlayer):
|
|
if window.count(player) == 4:
|
|
return AIScores["win"]
|
|
elif window.count(player) == 3 and window.count(0) == 1:
|
|
return AIScores["three in a row"]
|
|
elif window.count(player) == 2 and window.count(0) == 2:
|
|
return AIScores["two in a row"]
|
|
elif window.count(otherPlayer) == 4:
|
|
return AIScores["enemy win"]
|
|
else:
|
|
return 0
|
|
|
|
def minimax(board, depth, player , originalPlayer, alpha, beta, maximizingPlayer):
|
|
terminal = ((isWon(board)[0] != 0) or (0 not in board[0]))
|
|
# The depth is how many moves ahead the computer checks. This value is the difficulty.
|
|
if depth == 0 or terminal:
|
|
points = AICalcPoints(board,originalPlayer)
|
|
return points
|
|
if maximizingPlayer:
|
|
value = -math.inf
|
|
for column in range(0,columnCount):
|
|
testBoard = copy.deepcopy(board)
|
|
testBoard = placeOnBoard(testBoard,player,column)
|
|
if testBoard != None:
|
|
evaluation = minimax(testBoard,depth-1,player%2+1,originalPlayer,alpha,beta,False)
|
|
if evaluation < -9000: evaluation += AIScores["avoid losing"]
|
|
value = max(value,evaluation)
|
|
alpha = max(alpha,evaluation)
|
|
if beta <= alpha:
|
|
break
|
|
return value
|
|
else:
|
|
value = math.inf
|
|
for column in range(0,columnCount):
|
|
testBoard = copy.deepcopy(board)
|
|
testBoard = placeOnBoard(testBoard,player,column)
|
|
if testBoard != None:
|
|
evaluation = minimax(testBoard,depth-1,player%2+1,originalPlayer,alpha,beta,True)
|
|
if evaluation < -9000: evaluation += AIScores["avoid losing"]
|
|
value = min(value,evaluation)
|
|
beta = min(beta,evaluation)
|
|
if beta <= alpha:
|
|
break
|
|
return value
|
|
|