PyCatan-AI / pycatan /core /player.py
EZTIME2025
organise the project
69373e6
from .building import Building
from .statuses import Statuses
from .card import ResCard, DevCard
import math
# The player class for
class Player:
def __init__ (self, game, num):
# the game the player belongs to
self.game = game
# the player number for this player
self.num = num
# the starting roads for this player
# used to determine the longest road
self.starting_roads = []
# the number of victory points
self.victory_points = 0
# the cards the player has
# each will be a number corresponding with the static variables CARD_<type>
self.cards = []
# the development cards this player has
self.dev_cards = []
# the number of knight cards the player has played
self.knight_cards = 0
# the longest road segment this player has
self.longest_road_length = 0
# builds a settlement belonging to this player
def build_settlement(self, point, is_starting=False):
if not is_starting:
# makes sure the player has the cards to build a settlements
cards_needed = [
ResCard.Wood,
ResCard.Brick,
ResCard.Sheep,
ResCard.Wheat
]
# checks the player has the cards
if not self.has_cards(cards_needed):
return Statuses.ERR_CARDS
# checks it is connected to a road owned by the player
connected_by_road = False
# gets the roads
roads = self.game.board.roads
for r in roads:
# checks if the road is connected
if r.point_one is point or r.point_two is point:
# checks this player owns the road
if r.owner == self.num:
connected_by_road = True
if not connected_by_road:
return Statuses.ERR_ISOLATED
# checks that a building does not already exist there
if point.building != None:
return Statuses.ERR_BLOCKED
# checks all other settlements are at least 2 away
# gets the connecting point's coords
points = point.connected_points
for p in points:
# checks if the point is occupied
if p.building != None:
return Statuses.ERR_BLOCKED
if not is_starting:
# removes the cards
self.remove_cards(cards_needed)
# adds the settlement
self.game.board.add_building(Building(
owner = self.num,
type = Building.BUILDING_SETTLEMENT,
point_one = point),
point = point)
# adds a victory point
self.victory_points += 1
return Statuses.ALL_GOOD
# checks if the player has all of the cards given in an array
def has_cards(self, cards):
# needs to duplicate the cards, and then delete them once found
# otherwise checking if the player has multiple of the same card
# will return true with only one card
# cards_dup stands for cards duplicate
cards_dup = self.cards[:]
for c in cards:
if cards_dup.count(c) == 0:
return False
else:
index = cards_dup.index(c)
del cards_dup[index]
return True
# adds some cards to a player's hand
def add_cards(self, cards):
for c in cards:
self.cards.append(c)
# removes cards from a player's hand
def remove_cards(self, cards):
# makes sure it has all the cards before deleting any
if not self.has_cards(cards):
return Statuses.ERR_CARDS
else:
# removes the cards
for c in cards:
index = self.cards.index(c)
del self.cards[index]
#adds a development card
def add_dev_card(self, dev_card):
self.dev_cards.append(dev_card)
# removes a dev card
def remove_dev_card(self, card):
# finds the card
for i in range(len(self.dev_cards)):
if self.dev_cards[i] == card:
# deletes the card
del self.dev_cards[i]
return Statuses.ALL_GOOD
# error if the player does not have the cards
return Statuses.ERR_CARDS
# checks a road location is valid
def road_location_is_valid(self, start, end):
# checks the two points are connected
connected = False
# gets the points connected to start
points = start.connected_points
for p in points:
if end == p:
connected = True
break
if not connected:
return Statuses.ERR_NOT_CON
connected_by_road = False
for road in self.game.board.roads:
# checks the road does not already exists with these points
if road.point_one == start or road.point_two == start:
if road.point_one == end or road.point_two == end:
return Statuses.ERR_BLOCKED
# check this player has a settlement on one of these points or a connecting road
is_connected = False
if start.building != None:
# checks if this player owns the settlement/city
if start.building.owner == self.num:
is_connected = True
# does the same for the other point
elif end.building != None:
if end.building.owner == self.num:
is_connected = True
# then checks if there is a road connecting them
roads = self.game.board.roads
points = [start, end]
for r in roads:
for p in points:
if r.point_one == p or r.point_two == p:
# checks that there is not another player's settlement here, so that it's not going through it
if p.building == None:
is_connected = True
# if theere is a settlement/city there, the road can be built if this player owns it
elif p.building.owner == self.num:
is_connected = True
if not is_connected:
return Statuses.ERR_ISOLATED
return Statuses.ALL_GOOD
# builds a road
def build_road(self, start, end, is_starting=False):
# checks the location is valid
location_status = self.road_location_is_valid(start=start, end=end)
if not location_status == Statuses.ALL_GOOD:
return location_status
# if the road is being created on the starting turn, the player does not needed
# to have the cards
if not is_starting:
# checks that it has the proper cards
cards_needed = [
ResCard.Wood,
ResCard.Brick
]
if not self.has_cards(cards_needed):
return Statuses.ERR_CARDS
# removes the cards
self.remove_cards(cards_needed)
# adds the road
road = Building(owner=self.num, type=Building.BUILDING_ROAD, point_one=start, point_two=end)
(self.game).board.add_road(road)
self.get_longest_road(new_road=road)
return Statuses.ALL_GOOD
# returns an array of all the harbors the player has access to
def get_connected_harbor_types(self):
# gets the settlements/cities belonging to this player
harbors = []
all_harbors = self.game.board.harbors
buildings = self.game.board.get_buildings()
for b in buildings:
# checks the building belongs to this player
if b.owner == self.num:
# checks if the building is connected to any harbors
for h in all_harbors:
print(h)
print(b.point)
if h.point_one is b.point or h.point_two is b.point:
print("A")
# adds the type
if harbors.count(h.type) == 0:
harbors.append(h.type)
return harbors
# gets the longest road segment this player has which includes the road given
# should be called whenever a new road is build
# since this player's longest road will only change if a new road is build
def get_longest_road(self, new_road):
# gets the roads that belong to this player
roads = self.get_roads()
del roads[roads.index(new_road)]
# checks for longest road
self.check_connected_roads(road=new_road, all_roads=roads, length=1)
# checks the roads for connected roads, and then checks those roads until there are no more
def check_connected_roads(self, road, all_roads, length):
# do both point one and two
points = [
road.point_one,
road.point_two
]
for p in points:
# gets the connected roads
connected = self.get_connected_roads(point=p, roads=all_roads)
# if there are no new connected roads
if len(connected) == 0:
# if this is the longest road so far
if length > self.longest_road_length:
# records the length
self.longest_road_length = length
# self.begin_celebration()
# if there are connected roads
else:
# check each of them for connections if they have not been used
for c in connected:
# checks it hasn't used this road before
if all_roads.count(c) > 0:
# copies all usable roads
c_roads = all_roads[:]
# removes this road from them
del c_roads[c_roads.index(c)]
# checks for connected roads to this road
self.check_connected_roads(c, c_roads, length + 1)
# returns which roads in the roads array are connected to the point
def get_connected_roads(self, point, roads):
con_roads = []
for r in roads:
if r.point_one == point or r.point_two == point:
con_roads.append(r)
return con_roads
# returns an array of all the roads belonging to this player
def get_roads(self):
# gets all the roads on the board
all_roads = (self.game).board.roads
# filters out roads that do not belong to this player
roads = []
for r in all_roads:
if r.owner == self.num:
roads.append(r)
return roads
# checks if the player has some development cards
def has_dev_cards(self, cards):
card_duplicate = self.dev_cards[:]
for c in cards:
if not card_duplicate.count(c) > 0:
return False
else:
del card_duplicate[card_duplicate.index(c)]
return True
# returns the number of VP
# if include_dev is False, it will not include points from developement cards
# because other players aren't able to see them
def get_VP(self, include_dev=False):
# gets the victory points from settlements and cities
points = self.victory_points
# adds VPs from longest road
if self.game.longest_road_owner == self.num:
points += 2
# adds VPs from largest army
if self.game.largest_army == self.num:
points += 2
# adds VPs from developement cards
if include_dev:
for d in self.dev_cards:
if d == DevCard.VictoryPoint:
points += 1
return points
# prints the cards given
@staticmethod
def print_cards(cards):
print("[")
for c in cards:
card_name = ""
if c == ResCard.Wood:
card_name = "Wood"
elif c == ResCard.Sheep:
card_name = "Sheep"
elif c == ResCard.Brick:
card_name = "Brick"
elif c == ResCard.Wheat:
card_name = "Wheat"
elif c == ResCard.Ore:
card_name = "Ore"
else:
print("INVALID CARD %s" % c)
continue
if cards.index(c) < len(cards) - 1:
card_name += ","
print(" %s" % card_name)
print("]")