import base as Base import copy import random import time import math from PIL import Image, ImageDraw from collections import deque # Player template for HIVE --- ALP semestral work # Vojta Vonasek, 2023 # PUT ALL YOUR IMPLEMENTATION INTO THIS FILE def inBoard(p, q, size): """ return True if (p,q) is valid coordinate """ return (q >= 0) and (q < size) and (p >= -(q//2)) and (p < (size - q//2)) def get_neighbors(p, q): directions = [(0, -1), (1, -1), (1, 0), (0, 1), (-1, 1), (-1, 0)] return [(p + dp, q + dq) for dp, dq in directions] def has_neighbors(p, q, board, pp, pq): # Check if the position (p, q) has at least one neighbor board = copy.deepcopy(board) board[pp][pq] = "" for dp, dq in get_neighbors(0, 0): neighbor_p, neighbor_q = p + dp, q + dq if inBoard(neighbor_p, neighbor_q, 13) and board[neighbor_p][neighbor_q] != "": return True return False def cn(spots, s, visited): n = 0 for i in spots[s][0]: if i not in visited and spots[i][1] == False: visited.add(i) n += len(spots[i][2]) n += cn(spots, i, visited) return n class Piece: def __init__(self, p, q, team): self.p = p self.q = q self.team = team @property def pos(self): return (self.p, self.q) def get_piece_info(self, myisupper): class_to_letter = { "Bee": "Q", "Beetle": "B", "Spider": "S", "Grasshopper": "G", "Ant": "A", } letter = class_to_letter[self.__class__.__name__] if not myisupper: letter = letter.lower() return [letter, self.p, self.q] def __repr__(self): return ",".join(map(str, self.get_piece_info(True)+[self.team])) def __str__(self): return self.__repr__() def validate_jumps(self, board): valid_jumps = self.get_valid_jumps(board) validated_jumps = [] for jump in valid_jumps: if self.hive_integrity_check(board): validated_jumps.append(jump) return validated_jumps def hive_integrity_check(self, board): board_copy = {p: {q: board[p][q] for q in board[p]} for p in board} # See if the hive falls apart without the piece (if so, then the move wasnt valid) # Remove the piece from its current position board_copy[self.p][self.q] = None # Get all remaining pieces on the board remaining_pieces = [ piece for row in board_copy.values() for piece in row.values() if piece ] # Start BFS from a random piece start_piece = remaining_pieces[0] visited = set() queue = deque([start_piece]) while queue: current_piece = queue.popleft() visited.add(current_piece) # Get all neighbors of the current piece for neighbor_p, neighbor_q in get_neighbors( current_piece.p, current_piece.q ): neighbor_piece = board_copy.get(neighbor_p, {}).get(neighbor_q) if neighbor_piece and neighbor_piece not in visited: queue.append(neighbor_piece) # Check if all pieces are connected return len(visited) == len(remaining_pieces) def get_valid_jumps() -> list: raise NotImplementedError class Bee(Piece): def get_valid_jumps(self, spots, s) -> list: result = [] for i in spots[s][0]: if spots[i][1]: hive = False empty = False for j in spots[i][0]: if hive and empty: break if j in spots[s][0]: if spots[j][1]: empty = True else: hive = True if (hive and empty): result.append(i) return result class Beetle(Piece): def get_valid_jumps(self, spots, s): if len(spots[s][2]) > 1: return spots[s][0] result = [] for i in spots[s][0]: if spots[i][1]: for j in spots[i][0]: if (j[0] != s[0] or j[1] != s[1]) and spots[j][1] == False: result.append(i) break else: result.append(i) return result class Spider(Piece): def get_valid_jumps(self, spots: dict, s: tuple): return self.crawl(spots, s, 0, set()) def crawl(self, spots, s, n, visited): n += 1 visited.add(s) if n == 4: return [s] result = [] for i in spots[s][0]: hive = False empty = False if i in visited and spots[i][1]: for j in spots[i][0]: if empty and hive: break if j in spots[s][0]: if spots[str(j)][1] or j in visited: empty = True else: hive = True if (empty and hive): result += self.spider(spots, i, n, visited.copy()) return result class Grasshopper(Piece): def get_valid_jumps(self, spots, s): result = [] for i in get_neighbors(0, 0): q = (s[0]+i[0], s[1]+i[1]) if not spots[q][1]: while True: q = (q[0]+i[0], q[1]+i[1]) if q in spots.keys() and spots[q][1]: result.append(q) break else: break return result class Ant(Piece): def get_valid_jumps(self, spots: dict, s: tuple): return self.crawl(spots, s, set()) def crawl(self, spots: dict, s: tuple, visited: set): visited.add(s) result = [] for i in spots[s][0]: hive = False empty = False if i in visited and spots[i][1]: for j in spots[i][0]: if empty and hive: break if j in spots[s][0]: if spots[str(j)][1] or visited.get(str(j)): empty = True else: hive = True if empty and hive: result += [i] result += self.ant(spots, i, visited) return result class Player(Base.Board): def __init__( self, playerName, myIsUpper, size, myPieces, rivalPieces ): # do not change this line Base.Board.__init__( self, myIsUpper, size, myPieces, rivalPieces ) # do not change this line self.playerName = playerName self.algorithmName = "just roll the dice, eh?" self.spots = dict() self.figures_player = list() self.bee = None self.total_piece_count = 18 @property def all_remaining(self): return self.total_piece_count - sum(self.myPieces.values()+self.rivalPieces.values()) @property def remaining_pieces(self): return sum(self.myPieces.values()) def connected(self, graph, cell): for i in graph[str(cell)][0]: if (graph[i][1] == False): break return self.remaining_pieces-1 == cn(graph, i, {str(cell): True, i: True}) + len(graph[i][2]) def getAllEmptyCells(self): result = [] for p in self.board: for q in self.board[p]: if self.isEmpty(p, q, self.board): result.append([p, q]) return result def getAllNonemptyCells(self): result = [] for p in self.board: for q in self.board[p]: if not self.isEmpty(p, q, self.board): result.append([p, q]) return result def translate_board(self, board): # mapper dict piece_class_mapping = { "Q": Bee, "B": Beetle, "S": Spider, "G": Grasshopper, "A": Ant, } spots = dict() figures_player = list() for p, r in self.board.items(): for q, a in r.items(): surr = [s for s in get_neighbors( p, q) if self.inBoard(s[0], s[1])] spots[(p, q)] = [surr, piece_class_mapping[a[-1].upper()]( p, q, self.myColorIsUpper == a[-1].isupper()) if a else None] if (a, self.myColorIsUpper == a.isupper()): figures_player.append((p, q)) self.spots = spots self.figures_player = figures_player def get_movable_pieces(self): # GET Movable,Empty moveable = {} empty_spots = [] for cell in self.figures_player: # Get all empty cells if (self.remaining_pieces != 0): for i in get_neighbors(0, 0): q = (cell[0]+i[0], cell[1]+i[1]) if q in self.spots.keys(): if self.spots[q][1]: enemy = False for j in get_neighbors(0, 0): qq = (q[0]+j[0], q[1]+j[1]) qqs = str(qq) if (self.spots.get(qqs) != None): if (self.spots[qqs][1] == False): if (self.spots[qqs][2] == False): enemy = True break if (not enemy): empty_spots.append(q) # Get all moves if self.myMove > 3: if self.connected(self.spots, cell): animal = self.spots[cell][2] arr = animal.get_valid_jumps(self.spots, cell) if (len(arr) > 0): moveable[cell] = arr return moveable, empty_spots def move(self): if self.myMove > 80: return [] self.translate_board() free = [1 for n in get_neighbors( self.bee.p, self.bee.q) if self.spots[n][1] == None] if sum(free) <= 0: return [] if (self.myMove == 0): animal = random.choice(list(self.myPieces.keys())) if (self.getCount() == 0): return [animal, None, None, 3, 6] else: choice = random.choice(get_neighbors(3, 6)) return [animal, None, None, choice[0], choice[1]] moveable, empty = self.get_movable_pieces() if len(moveable) == 0 and len(empty) == 0: # movement impossible return [] def updatePlayers(move, activePlayer, passivePlayer): """write move made by activePlayer player this method assumes that all moves are correct, no checking is made """ if len(move) == 0: return animal, p, q, newp, newq = move if p == None and q == None: # placing new animal activePlayer.myPieces[animal] -= 1 passivePlayer.rivalPieces = activePlayer.myPieces.copy() else: # just moving animal # delete its old position activePlayer.board[p][q] = activePlayer.board[p][q][:-1] passivePlayer.board[p][q] = passivePlayer.board[p][q][:-1] activePlayer.board[newp][newq] += animal passivePlayer.board[newp][newq] += animal if __name__ == "__main__": boardSize = 13 smallFigures = { "q": 1, "a": 2, "b": 2, "s": 2, "g": 2, } # key is animal, value is how many is available for placing bigFigures = { figure.upper(): smallFigures[figure] for figure in smallFigures } # same, but with upper case P1 = Player("player1", False, 13, smallFigures, bigFigures) P2 = Player("player2", True, 13, bigFigures, smallFigures) filename = "begin.png" P1.saveImage(filename) moveIdx = 0 while True: move = P1.move() print("P1 returned", move) updatePlayers(move, P1, P2) # update P1 and P2 according to the move filename = "moves/move-{:03d}-player1.png".format(moveIdx) P1.saveImage(filename) move = P2.move() print("P2 returned", move) updatePlayers(move, P2, P1) # update P2 and P1 according to the move filename = "moves/move-{:03d}-player2.png".format(moveIdx) P1.saveImage(filename) moveIdx += 1 P1.myMove = moveIdx P2.myMove = moveIdx if moveIdx > 50: print("End of the test game") break