2
\$\begingroup\$

I implemented a card game using pygame. The game is a game of war and uses png files that I made in Microsoft Paint. I tried to keep the object classes as separated from the pygame module as possible. The idea of this is so I can use the CardClasses module with other graphical interfaces, and to reuse it on different games whose displays will be different.

When I go to rewrite this or something similar, the idea is to plan on using a Game class as opposed to the more function paradigm I wrote here. I learned that I can't upload the card image files here, so I will upload the code in hopes that someone will comb through the lines and give me some feedback. Looking at how to rewrite the docstrings, I feel as though they are weak and only give the idea of the functions or classes they represent.

CardClasses.py

"""Created: 3/30/2019

Objects to represent a playing Card, playing Deck, and Player
"""

from enum import Enum
from itertools import product
from random import shuffle

class ranks(Enum):
    TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING, ACE = range(2, 15)

class suits(Enum):
    CLUBS,DIAMONDS,HEARTS,SPADES = range(1, 5)

class Card(object):
    """Card object represents a standard playing card.

    The object attributes, suit and rank, are implemented as enums whose values determine the weight of the card
    """

    def __init__(self, suit, rank, in_deck = False, image = None):
        if rank in ranks and suit in suits:
            self.rank = rank
            self.suit = suit
        else:
            self.rank = None
            self.suit = None

        self.in_deck = in_deck
        self.image = image
        self.position_x, self.position_y = 0,0
        self.horizontal_demension = None
        self.vertical_demension = None

    def __str__(self):
        return str(self.rank.name) + " " + str(self.suit.name)

    def __eq__(self, other):
        return True if self.rank == other.rank and self.suit == other.suit else False

    def __gt__(self, other):
        """Tests suit precedence, if suits are equal then checks ranks precedence"""
        if self.suit == other.suit:
            if self.rank.value > other.rank.value:
                return True
        if self.suit.value > other.suit.value:
            return True

        return False

class Deck(object):
    """A deck is a collection of 52 Card objects

    Object attributes: cards, removed
    methods: draw(range = 1), deck_shuffle()
    """

    def __init__(self):
        self.cards = [Card(suit, rank, in_deck = True) for suit, rank in product(suits, ranks)]
        self.removed = []

    def __str__(self):
        return str([str(card) for card in self.cards])

    def draw(self, range = 1):
        """Draw card(s) by removing them from deck"""
        drawn_cards = self.cards[:range]
        for card in drawn_cards:
            card.in_deck = False
        del self.cards[:range]
        self.removed.append(drawn_cards)
        return drawn_cards

    def deck_shuffle(self):
        """Shuffles deck object in place"""
        shuffle(self.cards)

class Player(object):
    """Implementation of a player object

    Object attributes: name, hand, score, turn, card_selected
    methods: remove_from_hand(card)
    """

    def __init__(self, name, hand = None, score = 0, turn = False):
        self.name = name
        self.hand = hand
        self.score = score
        self.turn = turn
        self.selected_card = None

    def __str__(self):
        return str(self.name)

    def remove_from_hand(self, card):
        """Removes a card object from the players hand"""
        if card and card in self.hand:
            position = self.hand.index(card)
            del self.hand[position]
            return card
        return None

Game.py

"""3/31/2019

Implementation of game of war using pygame and CardClasses
"""
from CardClasses import *
import pygame
green = (0, 200, 50)

def show_hand(screen, player):
    """Displays all cards in hand of player on pygame display object"""
    x, y, space_between_cards = 5, 462, 5
    for card in player.hand:
        card.position_x, card.position_y = x, y
        screen.blit(card.image, (x, y))
        x += card.horizontal_demension + space_between_cards

def select_card(player, mouse_x, mouse_y):
    """Player selects a card to play"""
    if mouse_x:
        for card in player.hand:
            lower_x, upper_x = (card.position_x, card.position_x + card.horizontal_demension)
            lower_y, upper_y = (card.position_y, card.position_y + card.vertical_demension)

            if mouse_x > lower_x and mouse_x < upper_x:
                if mouse_y > lower_y and mouse_y < upper_y:
                    player.selected_card = card

def load_card_images(player):
    "Loads image, and demensions to card objects"
    for card in player.hand:
        card.image = pygame.image.load("Cards/" + str(card) + ".png")
        width, hieght = card.image.get_size()
        card.horizontal_demension = width
        card.vertical_demension = hieght

def play_selected_card(screen, player):
    """Display card that is selected on pygame display object"""
    x = player.selected_card.position_x = 220
    y = player.selected_card.position_y
    screen.blit(player.selected_card.image, (x,y))

def show_winner(screen, player1, player2, my_font):
    """Display text stating game winner at end of game"""
    screen.fill(green)
    winner = str(player1) if player1.score > player2.score else str(player2)
    textsurface = my_font.render("The winner is: " + winner, False, (0, 0, 0))
    screen.blit(textsurface, (100, 270))

def update_selected_card_position(player, new_y_position):
    """Change the Y position of selected card to move card to played position"""
    if player.selected_card:
        player.selected_card.position_y = new_y_position

def evaluate(player1, player2):
    """determines who won round and updates their score"""
    round_winner = None
    if player1.selected_card and player2.selected_card:
        pygame.time.delay(1000)
        round_winner = player1 if player1.selected_card > player2.selected_card else player2
        round_winner.score += 1
        player1.selected_card, player2.selected_card = None, None
    return round_winner

def show_player_scores(screen, player1, player2):
    """Left corner is player 1 score, right corner is player 2 score"""
    font_size = 12
    my_font = pygame.font.SysFont('Times New Roman', font_size)
    textsurface1 = my_font.render("Player 1 score: " + str(player1.score), False, (0, 0, 0))
    textsurface2 = my_font.render("Player 2 score: " + str(player2.score), False, (0, 0, 0))
    screen.blit(textsurface1, (0,0))
    screen.blit(textsurface2, (470,0))

def flip_turns(player1, player2):
    """Negates Turn attributes of player1 and player2"""
    player1.turn = not player1.turn
    player2.turn = not player2.turn

def turn(player, mouse_x, mouse_y, new_y_position):
    """Player will select card using mouse_x, and mouse_y, card will be removed from hand and played"""
    select_card(player, mouse_x, mouse_y)
    player.remove_from_hand(player.selected_card)
    update_selected_card_position(player, new_y_position)

def winner_goes_first(winner, loser):
    """Sets the winner to the starter of the next round"""
    winner.turn = True
    loser.turn = False

def main():
    """GAME of war, each player is given a hand of 10 cards, on each turn a player will select a card to play,
    players cards will be compared and the player with the greater in value card will be assigned a point for round victory.
    When all cards in hand have been played game ends and winner is displayed

    """

    sc_width, sc_height = 555, 555
    selected_card_y_pos_player_1 = 330
    selected_card_y_pos_player_2 = 230
    font_size = 30
    delay_time_ms = 1000
    number_of_cards = 10
    turn_count = 1

    deck = Deck()
    deck.deck_shuffle()
    player1 = Player(input("Player 1 name: "), hand = deck.draw(number_of_cards), turn = True)
    player2 = Player(input("Player 2 name: "), hand = deck.draw(number_of_cards))

    pygame.init()
    screen = pygame.display.set_mode((sc_width, sc_height))
    load_card_images(player1)
    load_card_images(player2)

    pygame.font.init()
    my_font = pygame.font.SysFont('Times New Roman', font_size)

    """Main Game Loop"""
    game_is_running = True
    while game_is_running:
        screen.fill(green)

        mouse_x, mouse_y = None, None
        events = pygame.event.get()
        for event in events:
            if event.type == pygame.QUIT:
                game_is_running = False
                quit()
            if event.type == pygame.MOUSEBUTTONUP:
                mouse_x, mouse_y = pygame.mouse.get_pos()

        if player1.turn:
            show_hand(screen, player1)
            turn(player1, mouse_x, mouse_y, selected_card_y_pos_player_1)
            if player1.selected_card:
                flip_turns(player1, player2)
        else:
            show_hand(screen, player2)
            turn(player2, mouse_x, mouse_y, selected_card_y_pos_player_2)
            if player2.selected_card:
                flip_turns(player1, player2)

        if player1.selected_card:
            play_selected_card(screen, player1)
        if player2.selected_card:
            play_selected_card(screen, player2)

        show_player_scores(screen, player1, player2)
        pygame.display.update()

        winner = evaluate(player1,player2)
        if winner:
            if winner == player1:
                winner_goes_first(player1, player2)
            else:
                winner_goes_first(player2, player1)

        if not player1.hand and not player2.hand:
            show_winner(screen, player1, player2, my_font)
            pygame.display.update()
            pygame.time.delay(delay_time_ms)
            game_is_running = False

if __name__ == '__main__':
    main()
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

DRY

The mouse_x variable is repeated in this expression:

if mouse_x > lower_x and mouse_x < upper_x:

It can be simplified as:

if lower_x < mouse_x < upper_x:

Class

object is not needed:

class Deck(object):

and is often omitted:

class Card():

The flip_turns function could have been implemented as a method in the Player class since the logic is duplicated in flip_turns.

Spelling

The variable named hieght would be better as height.

Layout

Long lines like this in the docstring:

players cards will be compared and the player with the greater in value card will be assigned a point for round victory.

should be split into multiple lines for better readability:

players cards will be compared and the player with the
greater in value card will be assigned a point for round victory.

Naming

The PEP 8 style guide recommends upper case for constant names. For example:

green = (0, 200, 50)

would be:

GREEN = (0, 200, 50)

The same goes for these:

sc_width, sc_height = 555, 555
selected_card_y_pos_player_1 = 330
selected_card_y_pos_player_2 = 230
font_size = 30
delay_time_ms = 1000
number_of_cards = 10

The variable named range in the Deck class is the same name as a Python built-in function. This can be confusing. To eliminate the confusion, rename the variable as something like card_range. The first clue is that "range" has special coloring (syntax highlighting) in the question, as it does when I copy the code into my editor.

Unused

This variable is set:

turn_count = 1

but it is never used otherwise. It should be deleted.

\$\endgroup\$

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.