Hex so beautiful. And works!!!

This commit is contained in:
jona605a
2020-08-07 23:31:10 +02:00
parent b45aac1d1c
commit e6ce610366
3 changed files with 143 additions and 82 deletions

View File

@ -56,7 +56,7 @@ async def runHex(channel,command,user):
f.write(str(oldImage.id)) f.write(str(oldImage.id))
if gameDone: if gameDone:
with open("resources/games/games.json", "r") as f: with open("resources/games/hexGames.json", "r") as f:
data = json.load(f) data = json.load(f)
try: try:
@ -66,11 +66,16 @@ async def runHex(channel,command,user):
except: except:
logThis("The old image was already deleted") logThis("The old image was already deleted")
winner = data["hex games"][str(channel.id)]["winner"] winner = data[str(channel.id)]["winner"]
if winner != 0: if winner != 0:
addMoney(data["hex games"][str(channel.id)]["players"][winner-1].lower(),20) addMoney(data[str(channel.id)]["players"][winner-1].lower(),20)
deleteGame("hex games",str(channel.id)) #deleteGame("hex games",str(channel.id))
with open("resources/games/hexGames.json", "r") as f:
data = json.load(f)
del data[str(channel.id)]
with open("resources/games/hexGames.json", "w") as f:
json.dump(data,f,indent=4)

View File

@ -15,7 +15,7 @@ AIScoresHex = {
"avoid losing": 100 "avoid losing": 100
} }
boardWidth = 11 BOARDWIDTH = 11
# Parses command # Parses command
@ -44,9 +44,9 @@ def parseHex(command, channel, user):
# Placing a piece # Placing a piece
elif commands[0] == "place": elif commands[0] == "place":
try: try:
return placeHex(channel,int(commands[1]),commands[2]) return placeHex(channel,commands[1], user)
except: except:
return "I didn't get that. To place a piece use \"!hex place [player number] [position]\". A valid position is e.g. \"e2\".", False, False, False, False return "I didn't get that. To place a piece use \"!hex place [position]\". A valid position is e.g. \"E2\".", False, False, False, False
else: else:
return "I didn't get that. Use \"!hex start [opponent]\" to start a game or \"!hex stop\" to stop a current game.", False, False, False, False return "I didn't get that. Use \"!hex start [opponent]\" to start a game or \"!hex stop\" to stop a current game.", False, False, False, False
@ -81,13 +81,14 @@ def hexStart(channel, user, opponent):
diffText = "" diffText = ""
# board is 11x11 # board is 11x11
board = [ [ 0 for i in range(boardWidth) ] for j in range(boardWidth) ] board = [ [ 0 for i in range(BOARDWIDTH) ] for j in range(BOARDWIDTH) ]
players = [user,opponent] players = [user,opponent]
random.shuffle(players) # random starting player random.shuffle(players) # random starting player
winningPieces = [[""],[""],[""]] # etc. winningPieces = [[""],[""],[""]] # etc.
lastMove = (5,5)
data[channel] = {"board": board,"winner":0, data[channel] = {"board":board, "winner":0,
"players":players, "winningPieces":winningPieces,"turn":0,"difficulty":difficulty} "players":players, "winningPieces":winningPieces, "turn":1, "difficulty":difficulty, "lastMove":lastMove}
with open("resources/games/hexGames.json", "w") as f: with open("resources/games/hexGames.json", "w") as f:
json.dump(data,f,indent=4) json.dump(data,f,indent=4)
@ -103,62 +104,76 @@ def hexStart(channel, user, opponent):
return "There's already a hex game going on in this channel", False, False, False, False return "There's already a hex game going on in this channel", False, False, False, False
# Places a piece at the given location and checks things afterwards # Places a piece at the given location and checks things afterwards
def placeHex(channel : str,player : int,position : str): def placeHex(channel : str,position : str, user):
with open("resources/games/hexGames.json", "r") as f: with open("resources/games/hexGames.json", "r") as f:
data = json.load(f) data = json.load(f)
if channel in data: if channel in data:
board = data[channel]["board"] if user in data[channel]["players"]:
turn = data[channel]["turn"]
player = data[channel]["players"].index(user)+1
if player == turn:
board = data[channel]["board"]
logThis("Placing a piece on the board with placeHex()") logThis("Placing a piece on the board with placeHex()")
# Places on board # Places on board
board = placeOnHexBoard(board,player,position) board = placeOnHexBoard(board,player,position)
if isinstance(board, list): if isinstance(board, list):
# If the move is valid: # If the move is valid:
data[channel]["board"] = board data[channel]["board"] = board
turn = (data[channel]["turn"]+1)%2 turn = 1 if turn == 2 else 2
data[channel]["turn"] = turn data[channel]["turn"] = turn
with open("resources/games/hexGames.json", "w") as f:
json.dump(data,f,indent=4)
"""
# Checking for a win
logThis("Checking for win")
won, winningPieces = isHexWon(data[channel]["board"])
if won != 0:
gameWon = True
data[channel]["winner"] = won
data[channel]["winningPieces"] = winningPieces
message = data[channel]["players"][won-1]+" won!" """
if data[channel]["players"][won-1] != "Gwendolyn": with open("resources/games/hexGames.json", "w") as f:
winAmount = data[channel]["difficulty"]^2+5 json.dump(data,f,indent=4)
message += " Adding "+str(winAmount)+" GwendoBucks to their account."
else:"""
gameWon = False
message = getName(data[channel]["players"][player-1])+" placed at "+position+". It's now "+getName(data[channel]["players"][turn])+"'s turn."
with open("resources/games/hexGames.json", "w") as f:
json.dump(data,f,indent=4)
# Is it Gwendolyn's turn? # Checking for a win
gwendoTurn = False logThis("Checking for win")
if data[channel]["players"][turn] == "Gwendolyn": won, winningPieces = isHexWon(data[channel]["board"])
logThis("It's Gwendolyn's turn")
gwendoTurn = True
# Update the board if won != 0:
hexDraw.drawHexPlacement(channel,player,position) gameWon = True
data[channel]["winner"] = won
data[channel]["winningPieces"] = winningPieces
return message, True, True, gameWon, gwendoTurn
message = data[channel]["players"][won-1]+" won!"
if data[channel]["players"][won-1] != "Gwendolyn":
winAmount = data[channel]["difficulty"]^2+5
message += " Adding "+str(winAmount)+" GwendoBucks to their account."
else:"""
gameWon = False
message = getName(data[channel]["players"][player-1])+" placed at "+position.upper()+". It's now "+getName(data[channel]["players"][turn-1])+"'s turn."
data[channel]["lastMove"] = (int(position[1])-1, ord(position[0])-97)
with open("resources/games/hexGames.json", "w") as f:
json.dump(data,f,indent=4)
# Is it now Gwendolyn's turn?
gwendoTurn = False
if data[channel]["players"][turn-1] == "Gwendolyn":
logThis("It's Gwendolyn's turn")
gwendoTurn = True
# Update the board
hexDraw.drawHexPlacement(channel,player,position)
return message, True, True, gameWon, gwendoTurn
else:
# Invalid move. "board" is the error message
message = board
return message, False, False, False, False
else:
# Move out of turn
message = "It isn't your turn, it is "+getName(data[channel]["players"][turn-1])+"'s turn."
return message, False, False, False, False
else: else:
# Invalid move. "board" is the error message message = "You can't place when you're not in the game. The game's players are: "+getName(data[channel]["players"][0])+" and "+getName(data[channel]["players"][1])+"."
message = board return message, False, False, False, False
return message, True, True, False, False
else: else:
return "There's no game in this channel", False, False, False, False return "There's no game in this channel", False, False, False, False
@ -168,9 +183,9 @@ def placeOnHexBoard(board,player,position):
# Translates the position # Translates the position
position = position.lower() position = position.lower()
try: try:
column = ord(position[0])-97 # ord() translates from letter to number column = ord(position[0]) - 97 # ord() translates from letter to number
row = int(position[1])-1 row = int(position[1:]) - 1
if column not in range(boardWidth) or row not in range(boardWidth): if column not in range(BOARDWIDTH) or row not in range(BOARDWIDTH):
logThis("Position out of bounds (error code 1533)") logThis("Position out of bounds (error code 1533)")
return "Error. That position is out of bounds." return "Error. That position is out of bounds."
except: except:
@ -202,31 +217,50 @@ def hexAI(channel):
board = data[channel]["board"] board = data[channel]["board"]
player = data[channel]["players"].index("Gwendolyn")+1 player = data[channel]["players"].index("Gwendolyn")+1
difficulty = data[channel]["difficulty"] #difficulty = data[channel]["difficulty"]
lastMove = data[channel]["lastMove"]
# These moves are the last move +- 2.
moves = [[(lastMove[0]+j-2,lastMove[1]+i-2) for i in range(5) if lastMove[1]+i-2 in range(11)] for j in range(5) if lastMove[0]+j-2 in range(11)]
moves = sum(moves,[])
chosenMove = None
safety = 0
while chosenMove == None:
safety += 1
if safety > 1000:
break
candidate = random.choice(moves)
if board[candidate[0]][candidate[1]] == 0:
chosenMove = candidate
logThis("Last move was "+str(lastMove))
logThis("Chosen move is "+str(chosenMove))
"""
scores = [-math.inf,-math.inf,-math.inf,-math.inf,-math.inf,-math.inf,-math.inf] scores = [-math.inf,-math.inf,-math.inf,-math.inf,-math.inf,-math.inf,-math.inf]
for column in range(0,boardWidth): for column in range(0,BOARDWIDTH):
testBoard = copy.deepcopy(board) testBoard = copy.deepcopy(board)
# Testing a move
testBoard = placeOnHexBoard(testBoard,player,column) testBoard = placeOnHexBoard(testBoard,player,column)
# Evaluating that move
if testBoard != None: if testBoard != None:
scores[column] = minimaxHex(testBoard,difficulty,player%2+1,player,-math.inf,math.inf,False) scores[column] = minimaxHex(testBoard,difficulty,player%2+1,player,-math.inf,math.inf,False)
logThis("Best score for column "+str(column)+" is "+str(scores[column])) logThis("Best score for column "+str(column)+" is "+str(scores[column]))
possibleScores = scores.copy() possibleScores = scores.copy()
while (min(possibleScores) <= (max(possibleScores) - max(possibleScores)/10)) and len(possibleScores) != 1: while (min(possibleScores) < (max(possibleScores)*0.9)):
possibleScores.remove(min(possibleScores)) possibleScores.remove(min(possibleScores))
highest_score = random.choice(possibleScores) highest_score = random.choice(possibleScores)
indices = [i for i, x in enumerate(scores) if x == highest_score] indices = [i for i, x in enumerate(scores) if x == highest_score]
placement = random.choice(indices) """
return placeHex(channel,player,placement) placement = "abcdefghijk"[chosenMove[1]]+str(chosenMove[0]+1)
return placeHex(channel,placement, "Gwendolyn")
# Calculates points for a board # Calculates points for a board
def AICalcHexPoints(board,player): def AICalcHexPoints(board,player):
score = 0 score = 0
otherPlayer = player%2+1 #otherPlayer = player%2+1
## Checks if anyone has won ## Checks if anyone has won
@ -259,7 +293,7 @@ def minimaxHex(board, depth, player , originalPlayer, alpha, beta, maximizingPla
return points return points
if maximizingPlayer: if maximizingPlayer:
value = -math.inf value = -math.inf
for column in range(0,boardWidth): for column in range(0,BOARDWIDTH):
testBoard = copy.deepcopy(board) testBoard = copy.deepcopy(board)
testBoard = placeOnHexBoard(testBoard,player,column) testBoard = placeOnHexBoard(testBoard,player,column)
if testBoard != None: if testBoard != None:
@ -272,7 +306,7 @@ def minimaxHex(board, depth, player , originalPlayer, alpha, beta, maximizingPla
return value return value
else: else:
value = math.inf value = math.inf
for column in range(0,boardWidth): for column in range(0,BOARDWIDTH):
testBoard = copy.deepcopy(board) testBoard = copy.deepcopy(board)
testBoard = placeOnHexBoard(testBoard,player,column) testBoard = placeOnHexBoard(testBoard,player,column)
if testBoard != None: if testBoard != None:

View File

@ -23,12 +23,14 @@ X_THICKNESS = HEXTHICKNESS * math.cos(math.pi/6)
Y_THICKNESS = HEXTHICKNESS * math.sin(math.pi/6) Y_THICKNESS = HEXTHICKNESS * math.sin(math.pi/6)
BACKGROUND_COLOR = (230,230,230) BACKGROUND_COLOR = (230,230,230)
BETWEEN_COLOR = BACKGROUND_COLOR BETWEEN_COLOR = BACKGROUND_COLOR
BLANK_COLOR = "lightgrey" BLANK_COLOR = "lightgrey" # maybe lighter?
BOARDCOORDINATES = [ [(X_OFFSET + HEXAGONWIDTH*(column + row/2),Y_OFFSET + HEXAGONHEIGHT*row) for column in range(11)] for row in range(11)] # These are the coordinates for the upperleft corner of every hex BOARDCOORDINATES = [ [(X_OFFSET + HEXAGONWIDTH*(column + row/2),Y_OFFSET + HEXAGONHEIGHT*row) for column in range(11)] for row in range(11)] # These are the coordinates for the upperleft corner of every hex
COLHEXTHICKNESS = 4 COLHEXTHICKNESS = 4
COLX_THICKNESS = COLHEXTHICKNESS * math.cos(math.pi/6) COLX_THICKNESS = COLHEXTHICKNESS * math.cos(math.pi/6)
COLY_THICKNESS = COLHEXTHICKNESS * math.sin(math.pi/6) COLY_THICKNESS = COLHEXTHICKNESS * math.sin(math.pi/6)
X_NAME = {1:175, 2:CANVAS_WIDTH-100}
Y_NAME = {1:CANVAS_HEIGHT-150, 2:150}
NAMEHEXPADDING = 75
def drawBoard(channel): def drawBoard(channel):
logThis("Drawing empty Hex board") logThis("Drawing empty Hex board")
@ -106,6 +108,26 @@ def drawBoard(channel):
else: else:
player2 = getName(players[1]) player2 = getName(players[1])
""" """
with open("resources/games/hexGames.json", "r") as f:
data = json.load(f)
for p in [1,2]:
playername = getName(data[channel]["players"][p-1])
# Draw name
x = X_NAME[p]
x -= fnt.getsize(playername)[0] if p==2 else 0 # player2's name is right-aligned
y = Y_NAME[p]
d.text((x,y),playername, font=fnt, fill = TEXTCOLOR)
# Draw a half-size Hexagon to indicate the player's color
x -= NAMEHEXPADDING # To the left of both names
d.polygon([
(x, y),
(x+HEXAGONWIDTH/4, y-SIDELENGTH/4),
(x+HEXAGONWIDTH/2, y),
(x+HEXAGONWIDTH/2, y+SIDELENGTH/2),
(x+HEXAGONWIDTH/4, y+SIDELENGTH*3/4),
(x, y+SIDELENGTH/2),
],fill = PIECECOLOR[p])
im.save("resources/games/hexBoards/board"+channel+".png") im.save("resources/games/hexBoards/board"+channel+".png")
@ -119,7 +141,7 @@ def drawHexPlacement(channel,player,position):
# We don't need to error-check, because the position is already checked in placeOnHexBoard() # We don't need to error-check, because the position is already checked in placeOnHexBoard()
position = position.lower() position = position.lower()
column = ord(position[0])-97 # ord() translates from letter to number column = ord(position[0])-97 # ord() translates from letter to number
row = int(position[1])-1 row = int(position[1:])-1
# Find the coordinates for the filled hex drawing # Find the coordinates for the filled hex drawing
hexCoords = [ hexCoords = [
@ -136,7 +158,7 @@ def drawHexPlacement(channel,player,position):
with Image.open(FILEPATH) as im: with Image.open(FILEPATH) as im:
d = ImageDraw.Draw(im,"RGBA") d = ImageDraw.Draw(im,"RGBA")
# Draws the hex piece # Draws the hex piece
d.polygon(hexCoords,fill = PIECECOLOR[player]) d.polygon(hexCoords,fill = PIECECOLOR[player], outline = BETWEEN_COLOR)
# Save # Save
im.save(FILEPATH) im.save(FILEPATH)
@ -144,12 +166,12 @@ def drawHexPlacement(channel,player,position):
logThis("Error drawing new hex on board (error code 1541") logThis("Error drawing new hex on board (error code 1541")
if __name__ == '__main__': if __name__ == '__main__':
drawBoard("HexTest2") drawBoard("resources/games/hexBoards/boardTest.png")
drawHexPlacement("HexTest2",1,"f7") drawHexPlacement("resources/games/hexBoards/boardTest.png",1,"f7")
drawHexPlacement("HexTest2",2,"f8") drawHexPlacement("resources/games/hexBoards/boardTest.png",2,"f8")
drawHexPlacement("HexTest2",1,"h6") drawHexPlacement("resources/games/hexBoards/boardTest.png",1,"h6")
drawHexPlacement("HexTest2",2,"e8") drawHexPlacement("resources/games/hexBoards/boardTest.png",2,"e8")
drawHexPlacement("HexTest2",1,"e7") drawHexPlacement("resources/games/hexBoards/boardTest.png",1,"e7")
drawHexPlacement("HexTest2",2,"c9") drawHexPlacement("resources/games/hexBoards/boardTest.png",2,"c9")
drawHexPlacement("HexTest2",1,"g8") drawHexPlacement("resources/games/hexBoards/boardTest.png",1,"g8")
drawHexPlacement("HexTest2",2,"h4") drawHexPlacement("resources/games/hexBoards/boardTest.png",2,"h4")