407 lines
13 KiB
Text
407 lines
13 KiB
Text
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 connection_crawler(spots, s, visited):
|
|
n = 0
|
|
for i in spots[s][0]:
|
|
if i not in visited and spots[i][1]:
|
|
visited.add(i)
|
|
n += 1
|
|
n += connection_crawler(spots, i, visited)
|
|
return n
|
|
|
|
class Piece:
|
|
def __init__(self, p, q, team):
|
|
self.p = p
|
|
self.q = q
|
|
self.team = team
|
|
|
|
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 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] is None:
|
|
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] is None:
|
|
empty = True
|
|
else:
|
|
hive = True
|
|
if (hive and empty):
|
|
result.append(i)
|
|
return result
|
|
|
|
|
|
class Beetle(Piece):
|
|
def __init__(self, p, q, team, stack=""):
|
|
super().__init__(p, q, team)
|
|
self.stack = stack
|
|
|
|
def get_valid_jumps(self, spots, s):
|
|
if len(spots[s][1].stack) > 0:
|
|
return spots[s][0]
|
|
result = []
|
|
for i in spots[s][0]:
|
|
cs = copy.deepcopy(spots)
|
|
cs[s][1] = None
|
|
for j in spots[i][0]:
|
|
if j in spots.keys() and cs[j][1]:
|
|
result.append(i)
|
|
break
|
|
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 not in visited and spots[i][1] is None:
|
|
for j in spots[i][0]:
|
|
if empty and hive:
|
|
break
|
|
if j in spots[s][0]:
|
|
if spots[j][1] is None or j in visited:
|
|
empty = True
|
|
else:
|
|
hive = True
|
|
if (empty and hive):
|
|
result += self.crawl(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 q in spots.keys() and spots[q][1]:
|
|
while True:
|
|
q = (q[0]+i[0], q[1]+i[1])
|
|
if q in spots.keys() and spots[q][1] is None:
|
|
nei = [1 for n in spots[q][0] if n!=s and spots[n][1]]
|
|
if sum(nei)>0:
|
|
result.append(q)
|
|
break
|
|
if q not in spots.keys():
|
|
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 not in visited and spots[i][1] is None:
|
|
for j in spots[i][0]:
|
|
if empty and hive:
|
|
break
|
|
if j in spots[s][0]:
|
|
if spots[j][1] is None or j in visited:
|
|
empty = True
|
|
else:
|
|
hive = True
|
|
if empty and hive:
|
|
result += [i]
|
|
result += self.crawl(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
|
|
self.all_placed = list()
|
|
|
|
@property
|
|
def all_remaining(self):
|
|
return self.total_piece_count - sum(list(self.myPieces.values())+list(self.rivalPieces.values()))
|
|
|
|
@property
|
|
def remaining_pieces(self):
|
|
return sum(self.myPieces.values())
|
|
|
|
def connections_checker(self, spots, s):
|
|
if isinstance(spots[s][1], Beetle):
|
|
if len(spots[s][1].stack) > 0:
|
|
return True
|
|
for i in spots[s][0]:
|
|
if spots[i][1] is None:
|
|
continue
|
|
visited = set()
|
|
visited.add(s)
|
|
visited.add(i)
|
|
return len(self.all_placed)-2 == connection_crawler(spots, i, visited)
|
|
return False
|
|
|
|
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):
|
|
# mapper dict
|
|
piece_class_mapping = {
|
|
"Q": Bee,
|
|
"B": Beetle,
|
|
"S": Spider,
|
|
"G": Grasshopper,
|
|
"A": Ant,
|
|
}
|
|
|
|
spots = dict()
|
|
self.all_placed = list()
|
|
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])]
|
|
|
|
if a:
|
|
if len(a) > 1 and a[-1].lower == "b":
|
|
spots[(p,q)] = [surr, piece_class_mapping["B"](p, q, self.myColorIsUpper == a[-1].isupper(), a[:-1])]
|
|
else:
|
|
spots[(p, q)] = [surr, piece_class_mapping[a[-1].upper()](
|
|
p, q, self.myColorIsUpper == a[-1].isupper())]
|
|
else:
|
|
spots[(p,q)] = [surr, None]
|
|
|
|
if a and self.myColorIsUpper == a[-1].isupper():
|
|
figures_player.append((p, q))
|
|
if "Q" in list(map(lambda x: x.upper(),a)):
|
|
self.bee = Bee(p, q, True)
|
|
if a != "":
|
|
self.all_placed.append(piece_class_mapping[a[-1].upper()](
|
|
p, q, self.myColorIsUpper == a[-1].isupper()))
|
|
|
|
self.spots = spots
|
|
self.figures_player = figures_player
|
|
|
|
def get_movable_and_empties(self):
|
|
moveable = {}
|
|
empty_spots = []
|
|
for fig in self.figures_player:
|
|
# Empty spots
|
|
if self.remaining_pieces != 0:
|
|
for i in get_neighbors(fig[0], fig[1]):
|
|
if i in self.spots.keys():
|
|
if self.spots[i][1] is None:
|
|
enemy = False
|
|
for j in get_neighbors(i[0], i[1]):
|
|
if j in self.spots.keys():
|
|
if self.spots[j][1] and self.spots[j][1].team == False:
|
|
enemy = True
|
|
break
|
|
if not enemy:
|
|
empty_spots.append(i)
|
|
# actual move calc
|
|
if self.myMove > 3:
|
|
var = self.connections_checker(self.spots, fig)
|
|
if var:
|
|
animal = self.spots[fig][1]
|
|
arr = animal.get_valid_jumps(self.spots, fig)
|
|
if (len(arr) > 0):
|
|
moveable[fig] = arr
|
|
return moveable, empty_spots
|
|
|
|
def placer(self, empty):
|
|
animal = random.choice([a for a, v in self.myPieces.items() if v != 0])
|
|
spot = random.choice(empty)
|
|
return [animal, None, None, spot[0], spot[1]]
|
|
|
|
def mover(self, moveable):
|
|
animal = random.choice([a for a in moveable.keys()])
|
|
jump = random.choice(moveable[animal])
|
|
return self.spots[animal][1].get_piece_info(self.myColorIsUpper) + [jump[0], jump[1]]
|
|
|
|
def move(self):
|
|
if self.myMove > 80:
|
|
return []
|
|
|
|
self.translate_board()
|
|
|
|
free = [6]
|
|
if self.bee:
|
|
free = [1 for n in get_neighbors(
|
|
self.bee.p, self.bee.q) if n in self.spots.keys() and self.spots[n][1] == None]
|
|
if sum(free) <= 0:
|
|
return []
|
|
|
|
if self.myMove == 0:
|
|
animal = random.choice(list(self.myPieces.keys()))
|
|
if self.all_remaining == 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_and_empties()
|
|
if len(moveable) == 0 and len(empty) == 0: # movement impossible
|
|
return []
|
|
|
|
if self.bee == None and (self.myMove == 3 or random.choice([True, False])) and self.myMove<=3:
|
|
spot = random.choice(empty)
|
|
return ["Q" if self.myColorIsUpper else 'q', None, None, spot[0], spot[1]]
|
|
|
|
if self.myMove <= 3:
|
|
return self.placer(empty)
|
|
|
|
if self.bee:
|
|
if (random.choice([True, False]) or len(moveable) == 0) and self.remaining_pieces:
|
|
return self.placer(empty)
|
|
else:
|
|
return self.mover(moveable)
|
|
return moveable
|
|
|
|
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
|