From dad07bc637efdb430688e5013e018318615510cc Mon Sep 17 00:00:00 2001 From: En Yi Date: Sat, 4 May 2019 11:45:21 +0100 Subject: [PATCH] First commit --- .gitignore | 6 ++ cards.py | 240 +++++++++++++++++++++++++++++++++++++++++++++++ main.py | 44 +++++++++ players.py | 168 +++++++++++++++++++++++++++++++++ requirements.txt | 2 + view.py | 26 +++++ 6 files changed, 486 insertions(+) create mode 100644 .gitignore create mode 100644 cards.py create mode 100644 main.py create mode 100644 players.py create mode 100644 requirements.txt create mode 100644 view.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0f84d14 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +venv/ +data/ +test* +math_ext.py +.idea +__pycache__/ \ No newline at end of file diff --git a/cards.py b/cards.py new file mode 100644 index 0000000..252032f --- /dev/null +++ b/cards.py @@ -0,0 +1,240 @@ +""" +This module contains the Card class and the Deck class +Card contains the information of a playing card +Deck is used as a Card container +""" +import pygame +import view +import os +from enum import Enum + + +class DeckReveal(Enum): + SHOW_ALL = 1 + HIDE_ALL = 2 + ANY = 3 + + +class DeckSort(Enum): + ASCENDING = 1 + DESCENDING = 2 + NOSORT = 3 + + +class Card(pygame.sprite.Sprite): + + def __init__(self, x, y, width, height, value, hidden=False, image_data=None, parent=None): + super().__init__() + self.x = x + self.y = y + + self.width = width + self.height = height + + self.value = value + self.hidden = hidden + self.parent = parent + + if image_data: + self.image = image_data.convert() + self.image = pygame.transform.scale(self.image, (self.width, self.height)) + + # Display Value for Debug Purposes + myfont = pygame.font.SysFont("None", 16) + mytext = myfont.render(str(self.value), True, (0, 0, 0)) + mytext = mytext.convert_alpha() + self.image.blit(mytext, (0,0)) + + self._layer = 0 + + def get_pos(self): + return self.x, self.y + + +class Deck(): + + def __init__(self, x, y, length, width, spacing, deck_reveal=DeckReveal.SHOW_ALL, + sort_order=DeckSort.ASCENDING, vert_orientation=False, draw_from_last=False): + super().__init__() + self.x = x + self.y = y + + self.length = length + self.width = width + self.default_spacing = spacing + + self.deck_reveal = deck_reveal + self.vert_orientation = vert_orientation + self.draw_from_last = draw_from_last + self.sort_order = sort_order + + self.cards = [] + + if self.is_horizontal(): + self.background = pygame.Surface((self.length, self.width)) + self.background.fill((0, 255, 0)) + self.background = self.background.convert() + self.deck_surface = self.background.copy() + else: + self.background = pygame.Surface((self.width, self.length)) + self.background.fill((0, 255, 0)) + self.background = self.background.convert() + self.deck_surface = self.background.copy() + + self._layer = 1 + + def add_card(self, card, position=0): + card.parent = self + number_of_cards = len(self.cards) + + if number_of_cards == 0: + self.cards.append(card) + else: + if self.sort_order == DeckSort.NOSORT: + self.cards.insert(position, card) + else: + if self.sort_order == DeckSort.DESCENDING: + self.cards.reverse() + + if card.value < self.cards[0].value: + self.cards.insert(0, card) + elif card.value > self.cards[-1].value: + self.cards.append(card) + else: + lo = 0 + hi = number_of_cards + + while abs(lo-hi) != 1: + pos = (lo + hi) // 2 + if card.value > self.cards[pos].value: + lo = pos + else: + hi = pos + + self.cards.insert(hi, card) + + if self.sort_order == DeckSort.DESCENDING: + self.cards.reverse() + + self.set_card_positions() + + def set_card_positions(self): + number_of_cards = len(self.cards) + if self.is_horizontal(): + total_card_length = self.cards[0].width + self.default_spacing * (number_of_cards-1) + if total_card_length <= self.length: + start_point = (self.length - total_card_length)/2 + for (i, card) in enumerate(self.cards): + card.x = start_point + self.default_spacing * (i-1) + card.y = (self.width - self.cards[0].height)/ 2 + else: + adjusted_spacing = (self.length - self.cards[0].width)/(number_of_cards-1) + + start_point = 0 + for (i, card) in enumerate(self.cards): + card.x = start_point + adjusted_spacing * i + card.y = (self.width - self.cards[0].height)/ 2 + else: + total_card_length = self.cards[0].height + self.default_spacing * (number_of_cards-1) + if total_card_length <= self.length: + start_point = (self.length - total_card_length)/2 + for (i, card) in enumerate(self.cards): + card.y = start_point + self.default_spacing * (i-1) + card.x = (self.width - self.cards[0].width)/ 2 + else: + adjusted_spacing = (self.length - self.cards[0].height)/(number_of_cards-1) + + start_point = 0 + for (i, card) in enumerate(self.cards): + card.y = start_point + adjusted_spacing * i + card.x = (self.width - self.cards[0].width)/ 2 + + self.update_deck_display() + + def update_deck_display(self): + self.deck_surface.blit(self.background, (0, 0)) + if self.draw_from_last: + for card in reversed(self.cards): + self.deck_surface.blit(card.image, (card.x, card.y)) + else: + for card in self.cards: + self.deck_surface.blit(card.image, (card.x, card.y)) + + def remove_card(self): + pass + + def is_horizontal(self): + return not self.vert_orientation + + def get_pos(self): + return self.x, self.y + + def print_deck_values(self): + values = "" + for card in self.cards: + values = values + str(card.value) + ' ' + print(values) + + +class PlayerDeck(Deck): + def get_selected_card(self, pos): + # TODO: convert pos to card num, deselect if no card is clicked or out of range + + # TODO: check if card num is selected, set selected, otherwise use it (by removing) + pass + +DATA_FOLDER = "data" + +class test_screen(view.PygView): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + try: # try to load images from the harddisk + card_img = pygame.image.load(os.path.join(DATA_FOLDER, 'diamond.jpg')) + except: + raise Exception("Cannot load image") # print error message and exit program + self.test_card = Card(50, 0, 50, 75, 111, image_data=card_img) + self.test_deck = Deck(100, 100, 200, 100, 25) + self.test_deck.add_card(Card(50, 0, 50, 75, 412, image_data=card_img)) + self.test_deck.add_card(Card(50, 0, 50, 75, 315, image_data=card_img)) + self.test_deck.add_card(Card(50, 0, 50, 75, 210, image_data=card_img)) + self.test_deck.add_card(Card(50, 0, 50, 75, 103, image_data=card_img)) + self.test_deck.add_card(Card(50, 0, 50, 75, 405, image_data=card_img)) + self.test_deck.add_card(Card(50, 0, 50, 75, 112, image_data=card_img)) + self.test_deck.add_card(Card(50, 0, 50, 75, 301, image_data=card_img)) + self.test_deck.add_card(Card(50, 0, 50, 75, 206, image_data=card_img)) + self.test_deck.add_card(Card(50, 0, 50, 75, 206, image_data=card_img)) + self.test_deck.add_card(Card(50, 0, 50, 75, 206, image_data=card_img)) + + def draw_function(self): + self.screen.blit(self.test_card.image, self.test_card.get_pos()) + self.screen.blit(self.test_deck.deck_surface, self.test_deck.get_pos()) + + def run(self): + running = True + while running: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + running = False + elif event.type == pygame.KEYDOWN: + if event.key == pygame.K_ESCAPE: + running = False + if event.key == pygame.K_p: + print('add cards') + pass + + milliseconds = self.clock.tick(self.fps) + #self.playtime += milliseconds / 1000.0 + + self.draw_function() + + pygame.display.flip() + self.screen.blit(self.background, (0, 0)) + + pygame.quit() + + + +if __name__ == '__main__': + test_view = test_screen(640, 400, clear_colour=(0, 0, 0)) + test_view.run() diff --git a/main.py b/main.py new file mode 100644 index 0000000..c28ce83 --- /dev/null +++ b/main.py @@ -0,0 +1,44 @@ +import view +import pygame + + +class game_screen(view.PygView): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def load_assets(self): + pass + + def draw_function(self): + pass + + def run(self): + running = True + while running: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + running = False + elif event.type == pygame.KEYDOWN: + if event.key == pygame.K_ESCAPE: + running = False + if event.key == pygame.K_p: + print('add cards') + pass + + milliseconds = self.clock.tick(self.fps) + #self.playtime += milliseconds / 1000.0 + + self.draw_function() + + pygame.display.flip() + self.screen.blit(self.background, (0, 0)) + + pygame.quit() + + +if __name__ == '__main__': + + main_view = game_screen(640, 400, clear_colour=(255, 0, 0)) + + main_view.run() \ No newline at end of file diff --git a/players.py b/players.py new file mode 100644 index 0000000..29dc173 --- /dev/null +++ b/players.py @@ -0,0 +1,168 @@ +import pygame +import cards +import view +from signalslot import Signal + + +class Table: + """ + A Table is the place where all actions takes place. It is essentially a FSM, doing different + routines at each state. It needs to keep track of the score, roles, and the rules. It needs + to ask each player for decisions and respond to them accordingly. The table will also need + to inform any decision to the Main Screen so that it can update the screen to reflect that + change through the use of callbacks (Signal and Slot). + + FSM cycles + --- + Preloop - Prepare the cards once + - Initiate Players and connect them to the Table + 1. Shuffle and Deal out cards to Players. + 2a. Detect weak hands and ask for reshuffle. + 2b. Return to (1) if any reshuffle occurs, otherwise proceed. + 3. Bidding round. Randomly pick a starting player, in clockwise manner + ask for a bid until it is valid. + 3b. Proceed only if 3 consecutive skips are detected. + 3c. Ask the winner of the bid a card not in their hand. + 3d. Set up the player roles, trump suit, rounds to win for both side + 3e. Play the game. Start with bid winner if NO TRUMP, otherwise + Starting next to the bid winner. + 4a. With the first player, ask for any card, excluding trump suits if trump + is not broken + 4b. With subsequent players, ask for cards that follow the suit of the first player + , include trump suit if trump is broken. Ask for any card if the player cannot + follow suit. + 4c. Once all 4 players has made valid plays, announce results, update scoring. Announce + player roles if the partner card is played. Break trump if trump is played. + 4d. Repeat 4 until 13 rounds are made. Maybe add early win if confirmed one side wins + 5. Ask for a new game. Go back to 1 if true. + + All played cards go into a hidden discard pile. + + """ + update_table = Signal() + + def __init__(self, x, y, width, height, clear_colour): + self.x = x + self.y = y + self.width = width + self.height = height + + self.players = [] + self.table_status = {'played cards': [0,0,0,0], 'leading player': 0, 'trump suit': 1, + 'trump broken': False, 'round history': [], 'bid': 0} + + self.background = pygame.Surface((self.width, self.height)) + self.background.fill(clear_colour) + self.background = self.background.convert() + + self.discard_deck = [] # This is not a deck as it will never be drawn + + w_deck = (0.2*self.height, 0.2*self.width) + l_deck = (0.5*self.width, 0.5*self.height) + + playerx = (self.width//2 - l_deck[0], + 0, + self.width//2 - l_deck[0], + self.width - w_deck[0]) + playery = (self.height - w_deck[0], + self.height//2 - l_deck[1], + 0, + self.height//2 - l_deck[1]) + + for i in range(4): + if i == 1: + self.players.append(MainPlayer()) + else: + self.players.append(Player()) + + +class Player(cards.Deck): + """ + A player is essentiallg a Deck with decision making function or AI component if it is a bot + that returns a valid action for the Table/Board. + + The player has the knowledge of Table status in the form of a dictatary (as it is mutable, thus passed by ref) + so all validation is done by the player + + Possible decisions, each decision has to be enum maybe: + - Query the board status (i.e. current round, player status), AI most likely need a lot more + - Query the last round + - Attempt to play a card + - Play the validate move + + """ + def __init__(self, ai_component=None, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.AI = ai_component + self.table_status = None # This is found in Table and updated through Table + + def connect_to_table(self, table): + self.table_status = table + + def make_a_bid(self): + pass + + def make_a_play(self): + pass + + def view_last_round(self): + pass + + +class MainPlayer(cards.PlayerDeck): + def __init__(self, ai_component=None, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.AI = ai_component + self.table_status = None # This is found in Table and updated through Table + + def connect_to_table(self, table): + self.table_status = table + + def make_a_bid(self): + pass + + def make_a_play(self): + pass + + def view_last_round(self): + pass + + +class TestView(view.PygView): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def draw_function(self): + pass + + def run(self): + running = True + while running: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + running = False + elif event.type == pygame.KEYDOWN: + if event.key == pygame.K_ESCAPE: + running = False + if event.key == pygame.K_p: + print('add cards') + pass + + milliseconds = self.clock.tick(self.fps) + #self.playtime += milliseconds / 1000.0 + + self.draw_function() + + pygame.display.flip() + self.screen.blit(self.background, (0, 0)) + + pygame.quit() + + + +if __name__ == '__main__': + test_view = TestView(640, 400, clear_colour=(0, 0, 0)) + test_view.run() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..89da287 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +pygame +signalslot \ No newline at end of file diff --git a/view.py b/view.py new file mode 100644 index 0000000..9cd5658 --- /dev/null +++ b/view.py @@ -0,0 +1,26 @@ +import pygame + + +class PygView(object): + + def __init__(self, width=640, height=400, fps=60, clear_colour=(0, 0, 0)): + """Initialize pygame, window, background, font,... + """ + pygame.init() + pygame.display.set_caption("Press ESC to quit") + self.width = width + self.height = height + #self.height = width // 4 + self.screen = pygame.display.set_mode((self.width, self.height), pygame.DOUBLEBUF) + self.background = pygame.Surface(self.screen.get_size()) + self.background.fill(clear_colour) + self.background = self.background.convert() + self.clock = pygame.time.Clock() + self.fps = fps + #self.playtime = 0.0 + self.font = pygame.font.SysFont('mono', 20, bold=True) + + def run(self): + """The mainloop + """ + pass