class PlayerBox: """Box that holds all the usernames of the players in the game""" def __init__(self, x, y, max_num_players, chars_per_line, large_font, small_font, background_color): self.x = x self.y = y self.max_num_players = max_num_players self.chars_per_line = chars_per_line self.large_font = large_font self.small_font = small_font self.large_font_height = get_font_height(large_font) self.small_font_height = get_font_height(small_font) large_font_width = get_font_width(large_font) box_height = self.large_font_height * ( max_num_players + 1) + self.small_font_height * max_num_players * 3 box_width = large_font_width * chars_per_line self.box = NonCenteredRect(background_color, box_height, box_width, x, y) # note: title_text is a SelectableText but will never actually be selected self.title_text = SelectableText(large_font, "Players: ", WHITE, self.x, self.y) self.current_top = y + self.large_font_height # where the next text item should go self.players = [] def add_player(self, player_username, player_color): new_player = ClientOtherPlayer(player_username, player_color) new_player.set_text(self.large_font, self.small_font) # set up where the info regarding this player will be displayed # inside the player box # 1st, all the new text should be at the same x position as the box new_player.name_text.rect.x = self.x new_player.victory_point_text.rect.x = self.x new_player.dev_card_text.rect.x = self.x new_player.knights_played_text.rect.x = self.x # 2nd, heights have to be arranged, noting the different font sizes new_player.name_text.rect.y = self.current_top self.current_top += self.large_font_height new_player.victory_point_text.rect.y = self.current_top self.current_top += self.small_font_height new_player.dev_card_text.rect.y = self.current_top self.current_top += self.small_font_height new_player.knights_played_text.rect.y = self.current_top self.current_top += self.small_font_height self.players.append(new_player) def check_for_mouse(self, mouse_pos): for player in self.players: player.check_for_mouse(mouse_pos) def draw(self, screen): self.box.draw(screen) self.title_text.draw(screen) for player in self.players: player.draw(screen)
class TextRect(Rect): """A colored rectangle with centered text in it. That's it.""" color = common.LIGHT_BLUE text_color = common.BLACK def __init__(self, x, y, width, height, text, font): # setup rect super().__init__(TextRect.color, height, width, (x, y)) # setup text self.text = SelectableText(font, text, TextRect.text_color, x, y) self.text.rect.center = self.rect.center def draw(self, screen): super().draw(screen) self.text.draw(screen)
class ResourceRow: """A single line of colored text within a ResourceBlock (itself within the sidebar) representing a single resource""" def __init__(self, x, y, resource, color): self.resource = resource self.color = color self.num_resource = 0 # always start with zero of a resource self.text = SelectableText( large_font, self.resource + ': ' + str(self.num_resource), self.color, x, y) def check_for_mouse(self, mouse_pos): return self.text.check_for_mouse(mouse_pos) # change the value of this Row's resource to new def update(self, new): self.num_resource = new self.text.text = self.resource + ': ' + str(self.num_resource) # make sure to re-render text self.text.render() def draw(self, screen): self.text.draw(screen)
class ResourceCounter: """A colored number for each resource representing how much has been selected along with up / down arrows to change how much is selected""" arrow_size_modifier = 0.3 arrow_separation = 0.3 arrow_color = common.BLACK font_x_offset = 0.15 # centering the font intuitively looks off-center :\ font_y_offset = 0.1 min_value = 0 max_value = 15 arrow_color = common.BLACK def __init__(self, x, y, resource, font): self.resource = resource self.font = font # counter number initialization self.num = SelectableText(font, '0', resource.value, 0, 0) font_height = get_font_height(font) font_width = get_font_width(font) # store this center location long-term for re-centering self.num_center = (int(x + ResourceCounter.font_x_offset * font_width), int(y + ResourceCounter.font_y_offset * font_height)) self.num.rect.center = self.num_center # arrow initialization arrow_size = int(font_height * ResourceCounter.arrow_size_modifier) arrow_offset = int(font_height / 2 + font_height * ResourceCounter.arrow_separation) self.up_arrow = Triangle(x, y - arrow_offset, arrow_size, ResourceCounter.arrow_color) self.down_arrow = Triangle(x, y + arrow_offset, arrow_size, ResourceCounter.arrow_color, pointed_up=False) # collision rects for detecting mouse / clicks self.up_arrow_collision = pygame.rect.Rect(0, 0, arrow_size, arrow_size) self.up_arrow_collision.center = (x, y - arrow_offset) self.down_arrow_collision = pygame.rect.Rect(0, 0, arrow_size, arrow_size) self.down_arrow_collision.center = (x, y + arrow_offset) def check_for_mouse(self, mouse_pos, mouse_click): """"Checks if the mouse is close enough to either arrows to highlight them, and if there was a click updates the counter (assuming bounds not passed)""" if self.up_arrow_collision.collidepoint(mouse_pos): # mouse is near the up arrow self.up_arrow.color = common.WHITE if mouse_click: cur_val = int(self.num.text) new_val = cur_val + 1 # bounds checking if new_val <= ResourceCounter.max_value: if cur_val == 9: # need to re-initialize text obj for centering self.num = SelectableText(self.font, str(new_val), self.resource.value, 0, 0) self.num.rect.center = self.num_center else: self.num.text = str(new_val) self.num.deselect() # up arrow is being moused over, so down arrow shouldn't be selected self.down_arrow.color = ResourceCounter.arrow_color elif self.down_arrow_collision.collidepoint(mouse_pos): # mouse is near the down arrow self.down_arrow.color = common.WHITE if mouse_click: cur_val = int(self.num.text) new_val = cur_val - 1 if new_val >= ResourceCounter.min_value: if cur_val == 10: # need to re-initialize text obj for centering self.num = SelectableText(self.font, str(new_val), self.resource.value, 0, 0) self.num.rect.center = self.num_center else: self.num.text = str(cur_val - 1) self.num.deselect() # down arrow is being moused over, so up arrow shouldn't be selected self.up_arrow.color = ResourceCounter.arrow_color else: # mouse is away; deselect all self.up_arrow.color = ResourceCounter.arrow_color self.down_arrow.color = ResourceCounter.arrow_color def draw(self, screen): self.up_arrow.draw(screen) self.num.draw(screen) self.down_arrow.draw(screen)
class TBox(Rect): """Base class for all interactive, boxed prompts which require the user to select resource or some number of resources, including their own resources and those of other players""" color = common.BROWN width = 400 height = 300 font_type = 'graph-35.ttf' font_size = 18 prompt_box_height = 40 prompt_box_offset = height / 10 # response boxes consist of OK, CANCEL boxes response_box_height = 40 response_box_width = 90 response_box_y_offset = height * 9 / 10 ok_box_x_offset = width / 3 lone_ok_box_x_offset = width / 2 # if there is no cancel button cancel_box_x_offset = width * 2 / 3 error_text_color = common.BLACK error_text_y_offset = height * 7 / 10 def __init__(self, x, y, prompt, allow_cancel): super().__init__(TBox.color, TBox.height, TBox.width, (x, y)) self.x = x self.font = pygame.font.Font(TBox.font_type, TBox.font_size) # setup prompt prompt_width = get_font_width(self.font) * len(prompt) self.prompt = TextRect(x, self.rect.y + TBox.prompt_box_offset, prompt_width, TBox.prompt_box_height, prompt, self.font) # setup response buttons if allow_cancel: self.ok = TextRect(self.rect.left + TBox.ok_box_x_offset, self.rect.top + TBox.response_box_y_offset, TBox.response_box_width, TBox.response_box_height, 'OK', self.font) self.cancel = TextRect(self.rect.left + TBox.cancel_box_x_offset, self.rect.top + TBox.response_box_y_offset, TBox.response_box_width, TBox.response_box_height, 'CANCEL', self.font) else: self.ok = TextRect(self.rect.left + TBox.lone_ok_box_x_offset, self.rect.top + TBox.response_box_y_offset, TBox.response_box_width, TBox.response_box_height, 'OK', self.font) self.cancel = None # text for if the user does something bad and should be scolded self.error_text = None def check_for_mouse(self, mouse_pos, mouse_click): """Checks if the mouse is over any of the response buttons. Additionally checks for mouse click and will return what the user is trying to do""" if self.ok.text.check_for_mouse(mouse_pos): self.ok.text.select() if mouse_click: return TBoxState.OK elif self.cancel: if self.cancel.text.check_for_mouse(mouse_pos): self.cancel.text.select() if mouse_click: return TBoxState.CANCEL return TBoxState.CONTINUE def error(self, text): self.error_text = SelectableText(self.font, text, TBox.error_text_color, 0, 0) self.error_text.rect.center = (self.x, self.rect.y + TBox.error_text_y_offset) def draw(self, screen): super().draw(screen) self.prompt.draw(screen) self.ok.draw(screen) if self.cancel: self.cancel.draw(screen) if self.error_text: self.error_text.draw(screen)
class OtherPlayerView: """The players view of other players in the game""" def __init__(self, username, color, large_font, small_font, large_font_height, small_font_height, x, y): self.username = username self.color = color self.small_font = small_font self.small_font_height = small_font_height self.name_text = SelectableText(large_font, self.username + ':', self.color, x, y) self.x = x self.y = y + large_font_height self.victory_point_text = None self.dev_card_text = None self.knights_played_text = None self.update({'vp': 0, 'dev': 0, 'knights': 22}) # updates the text under player based on the dictionary passed def update(self, fields): y = self.y self.victory_point_text = SelectableText( self.small_font, 'Victory Points: ' + str(fields['vp']), self.color, self.x, y) y += self.small_font_height self.dev_card_text = SelectableText(self.small_font, 'Dev Cards: ' + str(fields['dev']), self.color, self.x, y) y += self.small_font_height self.knights_played_text = SelectableText( self.small_font, 'Knights Played: ' + str(fields['knights']), self.color, self.x, y) # deselect all text associated with this player def deselect(self): text_items = [ self.name_text, self.victory_point_text, self.dev_card_text, self.knights_played_text ] for item in text_items: item.deselect() # select all text associated with this player def select(self): text_items = [ self.name_text, self.victory_point_text, self.dev_card_text, self.knights_played_text ] for item in text_items: item.select() def check_for_mouse(self, mouse_pos): selected = False text_items = [ self.name_text, self.victory_point_text, self.dev_card_text, self.knights_played_text ] for item in text_items: if item.rect.collidepoint(mouse_pos): selected = True if selected: return self else: self.deselect() return None def draw(self, screen): self.name_text.draw(screen) self.victory_point_text.draw(screen) self.dev_card_text.draw(screen) self.knights_played_text.draw(screen)
class SideBar: """All the info to be displayed at the side of the screen""" x = 875 # left side of the bar y = 0 # top of the bar background = MAROON font_type = 'graph-35.ttf' large_font_size = 18 small_font_size = 15 normal_header_text_color = WHITE normal_text_color = BLACK chars_per_line = 15 def __init__(self, num_players): # font stuff self.large_font = pygame.font.Font(SideBar.font_type, SideBar.large_font_size) self.small_font = pygame.font.Font(SideBar.font_type, SideBar.small_font_size) self.large_font_height = get_font_height(self.large_font) self.small_font_height = get_font_height(self.small_font) # now begin adding Text items to the SideBar from the top to the bottom self.player_header = SelectableText(self.large_font, 'Players: ', SideBar.normal_header_text_color, SideBar.x, SideBar.y) self.cur_player_top = SideBar.y + self.large_font_height self.players = [] # players can't be added yet, so skip down and create everything else self.cur_top = self.cur_player_top + (self.large_font_height + 3 * self.small_font_height) \ * num_players # resources are the first thing after the players self.resources_header = SelectableText( self.large_font, 'Resources: ', SideBar.normal_header_text_color, SideBar.x, self.cur_top) self.cur_top += self.large_font_height self.resources = ResourceBlock(SideBar.x, self.cur_top) self.cur_top += 5 * self.large_font_height # 5 for the number of different resources # next come the development cards self.cards_header = SelectableText(self.large_font, 'Dev Cards: ', SideBar.normal_header_text_color, SideBar.x, self.cur_top) self.cur_top += self.large_font_height self.cards = self.create_cards() # then the actions a user can take when it is their turn self.action_header = SelectableText(self.large_font, 'Actions: ', SideBar.normal_header_text_color, SideBar.x, self.cur_top) self.cur_top += self.large_font_height self.actions = self.create_actions() # finally, create the box that will be in the background box_width = SideBar.chars_per_line * get_font_width(self.large_font) self.box = NonCenteredRect(SideBar.background, self.cur_top - SideBar.y, box_width, SideBar.x, SideBar.y) # create the 4 different development cards def create_cards(self): cards = [] cards.append( SelectableText(self.large_font, 'Knight: 0', SideBar.normal_text_color, SideBar.x, self.cur_top)) self.cur_top += self.large_font_height cards.append( SelectableText(self.large_font, 'Rd. Builder: 0', SideBar.normal_text_color, SideBar.x, self.cur_top)) self.cur_top += self.large_font_height cards.append( SelectableText(self.large_font, 'V. Point: 0', SideBar.normal_text_color, SideBar.x, self.cur_top)) self.cur_top += self.large_font_height cards.append( SelectableText(self.large_font, 'Monopoly: 0', SideBar.normal_text_color, SideBar.x, self.cur_top)) self.cur_top += self.large_font_height return cards # create the __ different actions a user can take on their turn def create_actions(self): actions = [] actions.append( SelectableText(self.large_font, 'Buy Road', SideBar.normal_text_color, SideBar.x, self.cur_top)) self.cur_top += self.large_font_height actions.append( SelectableText(self.large_font, 'Buy Sett.', SideBar.normal_text_color, SideBar.x, self.cur_top)) self.cur_top += self.large_font_height actions.append( SelectableText(self.large_font, 'Buy City', SideBar.normal_text_color, SideBar.x, self.cur_top)) self.cur_top += self.large_font_height actions.append( SelectableText(self.large_font, 'Buy Dev Card', SideBar.normal_text_color, SideBar.x, self.cur_top)) self.cur_top += self.large_font_height actions.append( SelectableText(self.large_font, 'Use Dev Card', SideBar.normal_text_color, SideBar.x, self.cur_top)) self.cur_top += self.large_font_height actions.append( SelectableText(self.large_font, 'Trade', SideBar.normal_text_color, SideBar.x, self.cur_top)) self.cur_top += self.large_font_height return actions # add a player and their associated info to the sidebar under the Players section def add_player(self, username, color): self.players.append( OtherPlayerView(username, color, self.large_font, self.small_font, self.large_font_height, self.small_font_height, SideBar.x, self.cur_player_top)) self.cur_player_top += self.large_font_height + 3 * self.small_font_height # select among players def select_player(self, mouse_pos): self.select_common(self.players, mouse_pos) # select among resources def select_resource(self, mouse_pos): self.select_common(self.resources.resources, mouse_pos) # select among development cards def select_card(self, mouse_pos): self.select_common(self.cards, mouse_pos) # select among player actions def select_action(self, mouse_pos): self.select_common(self.actions, mouse_pos) # utility for selection # if the mouse is between 2 text items, they will both try to be highlighted # this must be prevented by taking count of all items that think they are selected # and only actually going through with the selection for one of them def select_common(self, category, mouse_pos): selected = [] for item in category: selection = item.check_for_mouse(mouse_pos) if selection: selected.append(selection) if len(selected) < 2: # either 1 thing selected or none for item in selected: item.select() else: # 2 (possibly more, but I hope this is not possible) possible selections selected[0].select() for item in selected[1:]: item.deselect() def deselect(self, category): for item in category: item.deselect() def draw(self, screen): self.box.draw(screen) self.player_header.draw(screen) for player in self.players: player.draw(screen) self.resources_header.draw(screen) self.resources.draw(screen) self.cards_header.draw(screen) for card in self.cards: card.draw(screen) self.action_header.draw(screen) for action in self.actions: action.draw(screen)