diff --git a/funcs/games/fourInARow.py b/funcs/games/fourInARow.py index 3947681..cae9a17 100644 --- a/funcs/games/fourInARow.py +++ b/funcs/games/fourInARow.py @@ -2,6 +2,7 @@ import json import random import copy import math +import asyncio from . import fourInARowDraw from funcs import logThis, getName, getID @@ -13,7 +14,7 @@ AIScores = { "enemy two in a row": -35, "enemy three in a row": -200, "enemy win": -10000, - "win": 1000, + "win": 10000, "avoid losing": 100 } @@ -232,7 +233,7 @@ def isWon(board): return won, winDirection, winCoordinates # Plays as the AI -def fourInARowAI(channel): +async def fourInARowAI(channel): logThis("Figuring out best move") with open("resources/games/games.json", "r") as f: data = json.load(f) @@ -246,7 +247,7 @@ def fourInARowAI(channel): 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) + scores[column] = await 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() @@ -266,12 +267,10 @@ def AICalcPoints(board,player): 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): + if board[row][3] == player: + score += AIScores["middle"] rowArray = [int(i) for i in list(board[row])] for place in range(columnCount-3): window = rowArray[place:place+4] @@ -290,8 +289,6 @@ def AICalcPoints(board,player): 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) @@ -319,11 +316,12 @@ def evaluateWindow(window,player,otherPlayer): else: return 0 -def minimax(board, depth, player , originalPlayer, alpha, beta, maximizingPlayer): - terminal = ((isWon(board)[0] != 0) or (0 not in board[0])) +async def minimax(board, depth, player , originalPlayer, alpha, beta, maximizingPlayer): + #terminal = ((0 not in board[0]) or (isWon(board)[0] != 0)) + terminal = 0 not in board[0] + points = AICalcPoints(board,originalPlayer) # The depth is how many moves ahead the computer checks. This value is the difficulty. - if depth == 0 or terminal: - points = AICalcPoints(board,originalPlayer) + if depth == 0 or terminal or (points > 5000 or points < -6000): return points if maximizingPlayer: value = -math.inf @@ -331,12 +329,11 @@ def minimax(board, depth, player , originalPlayer, alpha, beta, maximizingPlayer testBoard = copy.deepcopy(board) testBoard = placeOnBoard(testBoard,player,column) if testBoard != None: - evaluation = minimax(testBoard,depth-1,player%2+1,originalPlayer,alpha,beta,False) + evaluation = await 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 + if beta <= alpha: break return value else: value = math.inf @@ -344,10 +341,9 @@ def minimax(board, depth, player , originalPlayer, alpha, beta, maximizingPlayer testBoard = copy.deepcopy(board) testBoard = placeOnBoard(testBoard,player,column) if testBoard != None: - evaluation = minimax(testBoard,depth-1,player%2+1,originalPlayer,alpha,beta,True) + evaluation = await 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 + if beta <= alpha: break return value diff --git a/funcs/games/gameLoops.py b/funcs/games/gameLoops.py index f1d2f38..4b3c143 100644 --- a/funcs/games/gameLoops.py +++ b/funcs/games/gameLoops.py @@ -45,6 +45,7 @@ async def runHex(channel,command,user): try: response, showImage, deleteImage, gameDone, gwendoTurn = hexAI(str(channel.id)) except: + response, showImage, deleteImage, gameDone, gwendoTurn = "An AI error occured",False,False,False,False logThis("AI error (error code 1520)") await channel.send(response) logThis(response,str(channel.id)) @@ -61,8 +62,9 @@ async def runHex(channel,command,user): with open("resources/games/hexGames.json", "r") as f: data = json.load(f) winner = data[str(channel.id)]["winner"] - if winner != 0: - addMoney(data[str(channel.id)]["players"][winner-1].lower(),50) + if winner != 0 and data[channel]["players"][0] != data[channel]["players"][1]: # player1 != player2 + winnings = data[str(channel.id)]["difficulty"]*10 + addMoney(data[str(channel.id)]["players"][winner-1].lower(),winnings) #deleteGame("hex games",str(channel.id)) with open("resources/games/hexGames.json", "r") as f: @@ -92,7 +94,7 @@ async def fiar(channel,command,user): if gameDone == False: if gwendoTurn: try: - response, showImage, deleteImage, gameDone, gwendoTurn = fourInARowAI(str(channel.id)) + response, showImage, deleteImage, gameDone, gwendoTurn = await fourInARowAI(str(channel.id)) except: logThis("AI error (error code 1420)") await channel.send(response) diff --git a/funcs/games/hex.py b/funcs/games/hex.py index 32259a3..205f1b4 100644 --- a/funcs/games/hex.py +++ b/funcs/games/hex.py @@ -6,15 +6,6 @@ import math from . import hexDraw from funcs import logThis, getName, getID -# This is totally copied from the four in a row game. Just modified - -AIScoresHex = { - "lol, dunno": 3, - "enemy win": -10000, - "win": 1000, - "avoid losing": 100 -} - BOARDWIDTH = 11 ALL_POSITIONS = [(i,j) for i in range(11) for j in range(11)] ALL_SET = set(ALL_POSITIONS) @@ -23,10 +14,12 @@ for position in ALL_POSITIONS: EMPTY_DIJKSTRA[position] = math.inf # an impossibly high number HEX_DIRECTIONS = [(0,1),(-1,1),(-1,0),(0,-1),(1,-1),(1,0)] - # Parses command def parseHex(command, channel, user): commands = command.lower().split() + with open("resources/games/hexGames.json", "r") as f: + data = json.load(f) + if command == "" or command == " ": return "I didn't get that. Use \"!hex start [opponent]\" to start a game.", False, False, False, False @@ -37,11 +30,12 @@ def parseHex(command, channel, user): logThis("Starting a hex game with hexStart(). "+str(user)+" challenged "+commands[1]) return hexStart(channel,user,commands[1]) # commands[1] is the opponent + # If using a command with no game, return error + elif channel not in data: + return "There's no game in this channel", False, False, False, False + # Stopping the game elif commands[0] == "stop": - with open("resources/games/hexGames.json", "r") as f: - data = json.load(f) - if user in data[channel]["players"]: return "Ending game.", False, False, True, False else: @@ -60,16 +54,28 @@ def parseHex(command, channel, user): # Surrender elif commands[0] == "surrender": - with open("resources/games/hexGames.json", "r") as f: - data = json.load(f) players = data[channel]["players"] if user in players: opponent = (players.index(user) + 1) % 2 - data[channel]["winner"] = opponent - return "{} surrendered. Ending game.".format(getName(user)), False, False, True, False + data[channel]["winner"] = opponent + 1 + return "{} surrendered. That means {} won! Adding 30 Gwendobucks to their account.".format(getName(user),getName(players[opponent])), False, False, True, False else: return "You can't surrender when you're not a player.", False, False, False, False + # Swap + elif commands[0] == "swap": + if len(data[channel]["gameHistory"]) == 1: # Only after the first move + data[channel]["players"] = data[channel]["players"][::-1] # Swaps their player-number + with open("resources/games/hexGames.json", "w") as f: + json.dump(data,f,indent=4) + # Swaps the color of the hexes on the board drawing: + hexDraw.drawSwap(channel) + player2 = data[channel]["players"][1] + gwendoTurn = (player2 == "Gwendolyn") + return "The color of both players were swapped. It is now {}'s turn".format(player2), True, True, False, gwendoTurn + else: + return "You can only swap as the second player after the very first move.", False, False, False, False + else: return "I didn't get that. Use \"!hex start [opponent]\" to start a game, \"!hex place [position]\" to place a piece, \"!hex undo\" to undo your last move or \"!hex stop\" to stop a current game.", False, False, False, False @@ -94,24 +100,21 @@ def hexStart(channel, user, opponent): return "That difficulty doesn't exist", False, False, False, False except: opponent = getID(opponent) - if opponent == user: - return "You can't play against yourself", False, False, False, False - elif opponent == None: + if opponent == None: return "I can't find that user", False, False, False, False else: # Opponent is another player - difficulty = 5 + difficulty = 3 diffText = "" - + # board is 11x11 board = [ [ 0 for i in range(BOARDWIDTH) ] for j in range(BOARDWIDTH) ] players = [user,opponent] random.shuffle(players) # random starting player - winningPieces = [[""],[""],[""]] # etc. - lastMove = (-1,-1) + gameHistory = [] data[channel] = {"board":board, "winner":0, - "players":players, "winningPieces":winningPieces, "turn":1, "difficulty":difficulty, "lastMove":lastMove} + "players":players, "turn":1, "difficulty":difficulty, "gameHistory":gameHistory} with open("resources/games/hexGames.json", "w") as f: json.dump(data,f,indent=4) @@ -119,7 +122,6 @@ def hexStart(channel, user, opponent): # draw the board hexDraw.drawBoard(channel) - gwendoTurn = True if players[0] == "Gwendolyn" else False showImage = True return "Started Hex game against "+getName(opponent)+ diffText+". It's "+getName(players[0])+"'s turn", showImage, False, False, gwendoTurn @@ -132,9 +134,14 @@ def placeHex(channel : str,position : str, user): data = json.load(f) if channel in data: - if user in data[channel]["players"]: + players = data[channel]["players"] + if user in players: turn = data[channel]["turn"] - player = data[channel]["players"].index(user)+1 + if players[0] == players[1]: + player = turn + else: + player = players.index(user)+1 + if player == turn: board = data[channel]["board"] @@ -149,32 +156,23 @@ def placeHex(channel : str,position : str, user): 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"]) + score, winner = evaluateBoard(data[channel]["board"]) - if won != 0: - gameWon = True - data[channel]["winner"] = won - data[channel]["winningPieces"] = winningPieces + if winner == 0: # Continue with the game. + gameWon = False + message = getName(data[channel]["players"][player-1])+" placed at "+position.upper()+". It's now "+getName(data[channel]["players"][turn-1])+"'s turn."# The score is "+str(score) - - message = data[channel]["players"][won-1]+" won!" - if data[channel]["players"][won-1] != "Gwendolyn": - winAmount = data[channel]["difficulty"]^2+5 + else: # Congratulations! + gameWon = True + data[channel]["winner"] = winner + message = getName(data[channel]["players"][player-1])+" placed at "+position.upper()+" and won!" + if data[channel]["players"][winner-1] != "Gwendolyn": + winAmount = data[channel]["difficulty"]*10 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) + + data[channel]["gameHistory"].append((int(position[1])-1, ord(position[0])-97)) # Is it now Gwendolyn's turn? gwendoTurn = False @@ -182,6 +180,10 @@ def placeHex(channel : str,position : str, user): logThis("It's Gwendolyn's turn") gwendoTurn = True + # Save the data + with open("resources/games/hexGames.json", "w") as f: + json.dump(data,f,indent=4) + # Update the board hexDraw.drawHexPlacement(channel,player,position) @@ -205,6 +207,7 @@ def placeHex(channel : str,position : str, user): def placeOnHexBoard(board,player,position): # Translates the position position = position.lower() + # Error handling try: column = ord(position[0]) - 97 # ord() translates from letter to number row = int(position[1:]) - 1 @@ -214,7 +217,6 @@ def placeOnHexBoard(board,player,position): except: logThis("Invalid position (error code 1531)") return "Error. The position should be a letter followed by a number, e.g. \"e2\"." - # Place at the position if board[row][column] == 0: board[row][column] = player @@ -229,19 +231,22 @@ def undoHex(channel, user): with open("resources/games/hexGames.json", "r") as f: data = json.load(f) if user in data[channel]["players"]: - if data[channel]["lastMove"] != (-1,-1): + if len(data[channel]["gameHistory"]): turn = data[channel]["turn"] # You can only undo after your turn, which is the opponent's turn. if user == data[channel]["players"][(turn % 2)]: # If it's not your turn logThis("Undoing {}'s last move".format(getName(user))) - lastMove = data[channel]["lastMove"] + lastMove = data[channel]["gameHistory"].pop() data[channel]["board"][lastMove[0]][lastMove[1]] = 0 data[channel]["turn"] = turn%2 + 1 - + # Save the data + with open("resources/games/hexGames.json", "w") as f: + json.dump(data,f,indent=4) + # Update the board hexDraw.drawHexPlacement(channel,0,"abcdefghijk"[lastMove[1]]+str(lastMove[0]+1)) # The zero makes the hex disappear - return "You undid", True, True, False, False + return "You undid your last move at {}".format(lastMove), True, True, False, False else: # Sassy return "Nice try. You can't undo your opponent's move. ", False, False, False, False @@ -258,61 +263,60 @@ def hexAI(channel): logThis("Figuring out best move") with open("resources/games/hexGames.json", "r") as f: data = json.load(f) - board = data[channel]["board"] - player = data[channel]["players"].index("Gwendolyn")+1 - #difficulty = data[channel]["difficulty"] - lastMove = data[channel]["lastMove"] + + if len(data[channel]["gameHistory"]): + lastMove = data[channel]["gameHistory"][-1] + else: + lastMove = (5,5) # 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] - for column in range(0,BOARDWIDTH): + movesCopy = moves.copy() + for move in movesCopy: + if board[move[0]][move[1]] != 0: + moves.remove(move) + chosenMove = random.choice(moves) + + """ + GwenColor = data[channel]["players"].index("Gwendolyn") + 1 # either 1 or 2 - red or blue + if len(data[channel]["gameHistory"]) == 0: + return placeHex(channel,"F6", "Gwendolyn") # If starting, start in the middle + board = data[channel]["board"] + difficulty = data[channel]["difficulty"] + possiblePlaces = [i for i,v in enumerate(sum(board,[])) if v == 0] + judgements = [float('nan')]*len(possiblePlaces) # All possible moves are yet to be judged + + current_score = evaluateBoard(board)[0] + for i in possiblePlaces: testBoard = copy.deepcopy(board) - # Testing a move - testBoard = placeOnHexBoard(testBoard,player,column) - # Evaluating that move - if testBoard != None: - 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])) + testBoard[i // BOARDWIDTH][i % BOARDWIDTH] = GwenColor + if evaluateBoard(testBoard)[0] != current_score: # only think about a move if it improves the score (it's impossible to get worse) + # Testing a move and evaluating it + judgements[i] = minimaxHex(testBoard,difficulty,-math.inf,math.inf,GwenColor==2) + logThis("Best score for place {} is {}".format((i // BOARDWIDTH,i % BOARDWIDTH),judgements[i])) - possibleScores = scores.copy() - - while (min(possibleScores) < (max(possibleScores)*0.9)): - possibleScores.remove(min(possibleScores)) - - highest_score = random.choice(possibleScores) - - indices = [i for i, x in enumerate(scores) if x == highest_score] + bestScore = max(judgements) if (GwenColor == 1) else min(judgements) # this line has an error + indices = [i for i, x in enumerate(judgements) if x == bestScore] # which moves got that score? + i = random.choice(indices) + chosenMove = (i // BOARDWIDTH , i % BOARDWIDTH) """ placement = "abcdefghijk"[chosenMove[1]]+str(chosenMove[0]+1) + logThis("ChosenMove is {} at {}".format(chosenMove,placement)) return placeHex(channel,placement, "Gwendolyn") def evaluateBoard(board): - score = {1:0, 2:0} + scores = {1:0, 2:0} winner = 0 # Here, I use Dijkstra's algorithm to evaluate the board, as proposed by this article: https://towardsdatascience.com/hex-creating-intelligent-adversaries-part-2-heuristics-dijkstras-algorithm-597e4dcacf93 for player in [1,2]: - logThis("Running Dijkstra for player "+str(player)) Distance = copy.deepcopy(EMPTY_DIJKSTRA) # Initialize the starting hexes. For the blue player, this is the leftmost column. For the red player, this is the tom row. for start in (ALL_POSITIONS[::11] if player == 2 else ALL_POSITIONS[:11]): # An empty hex adds a of distance of 1. A hex of own color add distance 0. Opposite color adds infinite distance. - Distance[start] = 1 if (board[v[0]][v[1]] == 0) else 0 if (board[v[0]][v[1]] == player) else math.inf + Distance[start] = 1 if (board[start[0]][start[1]] == 0) else 0 if (board[start[0]][start[1]] == player) else math.inf visited = set() # Also called sptSet, short for "shortest path tree Set" for _ in range(BOARDWIDTH**2): # We can at most check every 121 hexes # Find the next un-visited hex, that has the lowest distance @@ -326,57 +330,52 @@ def evaluateBoard(board): if v[0] in range(11) and v[1] in range(11) and v not in visited: new_dist = Distance[u] + (1 if (board[v[0]][v[1]] == 0) else 0 if (board[v[0]][v[1]] == player) else math.inf) Distance[v] = min(Distance[v], new_dist) - - # If at the goal, we've found the shortest distance - if v[player-1] == 10: # if the right coordinate of v is 10, it means we're at the goal - atGoal = True - break - if atGoal: - score[player] = Distance[v] # A player's score is the shortest distance to goal. Which equals the number of remaining moves they need to win if unblocked by the opponent. - break # After a hex has been visited, this is noted visited.add(u) - logThis("Distance from player {}'s start to {} is {}".format(player,u,Distance[u])) + #logThis("Distance from player {}'s start to {} is {}".format(player,u,Distance[u])) + if u[player-1] == 10: # if the right coordinate of v is 10, it means we're at the goal + scores[player] = Distance[u] # A player's score is the shortest distance to goal. Which equals the number of remaining moves they need to win if unblocked by the opponent. + break else: logThis("For some reason, no path to the goal was found. ") - if score[player] == 0: + if scores[player] == 0: winner = player break # We don't need to check the other player's score, if player1 won. - - return score, winner + return scores[2]-scores[1], winner - -def minimaxHex(board, depth, player , originalPlayer, alpha, beta, maximizingPlayer): +def minimaxHex(board, depth, alpha, beta, maximizingPlayer): # The depth is how many moves ahead the computer checks. This value is the difficulty. - if depth == 0 or 0 not in sum(board,[0]): - score = evaluateBoard(board) + if depth == 0 or 0 not in sum(board,[]): + score = evaluateBoard(board)[0] return score # if final depth is not reached, look another move ahead: - if maximizingPlayer: - value = -math.inf - for column in range(0,BOARDWIDTH): + if maximizingPlayer: # red player predicts next move + maxEval = -math.inf + possiblePlaces = [i for i,v in enumerate(sum(board,[])) if v == 0] + #logThis("Judging a red move at depth {}".format(depth)) + for i in possiblePlaces: testBoard = copy.deepcopy(board) - testBoard = placeOnHexBoard(testBoard,player,column) - if testBoard != None: - evaluation = minimaxHex(testBoard,depth-1,player%2+1,originalPlayer,alpha,beta,False) - if evaluation < -9000: evaluation += AIScoresHex["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,BOARDWIDTH): + testBoard[i // BOARDWIDTH][i % BOARDWIDTH] = 1 # because maximizingPlayer is Red which is number 1 + evaluation = minimaxHex(testBoard,depth-1,alpha,beta,False) + maxEval = max(maxEval, evaluation) + alpha = max(alpha, evaluation) + if beta <= alpha: + #logThis("Just pruned something!") + break + return maxEval + else: # blue player predicts next move + minEval = math.inf + possiblePlaces = [i for i,v in enumerate(sum(board,[])) if v == 0] + #logThis("Judging a blue move at depth {}".format(depth)) + for i in possiblePlaces: testBoard = copy.deepcopy(board) - testBoard = placeOnHexBoard(testBoard,player,column) - if testBoard != None: - evaluation = minimaxHex(testBoard,depth-1,player%2+1,originalPlayer,alpha,beta,True) - if evaluation < -9000: evaluation += AIScoresHex["avoid losing"] - value = min(value,evaluation) - beta = min(beta,evaluation) - if beta <= alpha: - break - return value + testBoard[i // BOARDWIDTH][i % BOARDWIDTH] = 2 # because minimizingPlayer is Blue which is number 2 + evaluation = minimaxHex(testBoard,depth-1,alpha,beta,True) + minEval = min(minEval, evaluation) + beta = min(beta, evaluation) + if beta <= alpha: + #logThis("Just pruned something!") + break + return minEval diff --git a/funcs/games/hexDraw.py b/funcs/games/hexDraw.py index a8d85ae..f457a01 100644 --- a/funcs/games/hexDraw.py +++ b/funcs/games/hexDraw.py @@ -22,7 +22,7 @@ HEXTHICKNESS = 6 # This is half the width of the background lining between every X_THICKNESS = HEXTHICKNESS * math.cos(math.pi/6) Y_THICKNESS = HEXTHICKNESS * math.sin(math.pi/6) BACKGROUND_COLOR = (230,230,230) -BETWEEN_COLOR = BACKGROUND_COLOR +BETWEEN_COLOR = (231,231,231) BLANK_COLOR = "lightgrey" PIECECOLOR = {1:(237,41,57),2:(0,165,255),0:BLANK_COLOR} # player1 is red, player2 is blue 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 @@ -41,16 +41,12 @@ SMOL_SIDELENGTH = SIDELENGTH * 0.6 def drawBoard(channel): logThis("Drawing empty Hex board") - - # Creates the empty image im = Image.new('RGB', size=(CANVAS_WIDTH, CANVAS_HEIGHT),color = BACKGROUND_COLOR) # 'd' is a shortcut to drawing on the image d = ImageDraw.Draw(im,"RGBA") - - - # The board is indexed with [row][column] + # Drawing all the hexagons for column in BOARDCOORDINATES: for startingPoint in column: x = startingPoint[0] @@ -98,22 +94,6 @@ def drawBoard(channel): d.text( (X_OFFSET + HEXAGONWIDTH*(i/2+11) + 30 , Y_OFFSET + 6 + i*HEXAGONHEIGHT) , str(i+1), font=fnt, fill=TEXTCOLOR) # Write player names and color - """ - with open("resources/games/hexGames.json", "r") as f: - data = json.load(f) - - board = data[channel]["board"] - # Getting player names - players = data[channel]["players"] - if players[0] == "Gwendolyn": - player1 = "Gwendolyn" - else: - player1 = getName(players[0]) - if players[1] == "Gwendolyn": - player2 = "Gwendolyn" - else: - player2 = getName(players[1]) -""" with open("resources/games/hexGames.json", "r") as f: data = json.load(f) for p in [1,2]: @@ -141,7 +121,7 @@ def drawBoard(channel): def drawHexPlacement(channel,player,position): FILEPATH = "resources/games/hexBoards/board"+channel+".png" - logThis("Drawing a newly placed hex. Filepath:"+FILEPATH) + logThis("Drawing a newly placed hex. Filepath: "+FILEPATH) # Translates position # We don't need to error-check, because the position is already checked in placeOnHexBoard() @@ -170,3 +150,40 @@ def drawHexPlacement(channel,player,position): im.save(FILEPATH) except: logThis("Error drawing new hex on board (error code 1541") + +def drawSwap(channel): + FILEPATH = "resources/games/hexBoards/board"+channel+".png" + with open("resources/games/hexGames.json", "r") as f: + data = json.load(f) + # Opens the image + try: + with Image.open(FILEPATH) as im: + d = ImageDraw.Draw(im,"RGBA") + + # Write player names and color + for p in [1,2]: + playername = getName(data[channel]["players"][p%2]) + + x = X_NAME[p] + x -= NAME_fnt.getsize(playername)[0] if p==2 else 0 # player2's name is right-aligned + y = Y_NAME[p] + + # Draw a half-size Hexagon to indicate the player's color + x -= NAMEHEXPADDING # To the left of both names + d.polygon([ + (x, y), + (x+SMOL_WIDTH/2, y-SMOL_SIDELENGTH/2), + (x+SMOL_WIDTH, y), + (x+SMOL_WIDTH, y+SMOL_SIDELENGTH), + (x+SMOL_WIDTH/2, y+SMOL_SIDELENGTH*3/2), + (x, y+SMOL_SIDELENGTH), + ],fill = PIECECOLOR[p % 2 + 1]) + + # Save + im.save(FILEPATH) + except: + logThis("Error drawing swap (error code 1542)") + + + + \ No newline at end of file diff --git a/funcs/miscFuncs.py b/funcs/miscFuncs.py index af8128f..2d730d5 100644 --- a/funcs/miscFuncs.py +++ b/funcs/miscFuncs.py @@ -323,7 +323,7 @@ def getName(userID): if userID in data: return data[userID]["user name"] else: - logThis("Couldn't find user") + logThis("Couldn't find user "+userID) return userID except: logThis("Error getting name") diff --git a/resources/errorCodes.txt b/resources/errorCodes.txt index cc76bb8..cbfec1c 100644 --- a/resources/errorCodes.txt +++ b/resources/errorCodes.txt @@ -114,6 +114,7 @@ 1532 - Cannot place on existing piece 1533 - Position out of bounds 1541 - Error loading board-image +1542 - Error swapping 16 - Monopoly 1600 - Unspecified error diff --git a/resources/games/hex-board.jpg b/resources/games/hex-board.jpg deleted file mode 100644 index 1274168..0000000 Binary files a/resources/games/hex-board.jpg and /dev/null differ