Master Object-Oriented Programming by Creating a Card Game

What will we cover?

The best way to learn Object-Oriented Programming is by creating something object-oriented. In this tutorial we will create a simple card game.

Step 1: What is Object-Oriented Programming?

At is core, Object-Oriented Programming helps you with structuring your program to resemble reality.

That is, you declare objects with parameters and methods (functions) you need on them.

The best way is to learn it by creating an easy example you can relate to.

Consider the following.

This diagram represents three objects we want to model. The first is a Card, second a Deck, and finally a Hand.

There are many things to notice, but fist that Hand is actually a sub-class of Deck. What does that mean? Don’t worry, we’ll get there.

Step 2: Implement the Card class

What does it all mean?

Well, first of all, there are many ways to represent a class and the above is just one possible option. But the if we look at Card, we have two groups to look at. First, suit and rank. Second, __str__() and __lt__(other).

The suit and rank are instance variables, while __str__() and __lt__(other) are class methods.

Instance variables are variables only available to a specific object instance. Hence, different instances of the same class can have different values.

Class methods are methods you can call on an object instance.

The function __str__() is a special method, which will give the string representation of the object instance. This is how the object will be represented if printed.

The function __lt__(other) is also a special method, which returns whether the object and another object other is greater. Hence, it returns a truths statement.

One way to implement is as follows.

class Card:
    suits = ['\u2666', '\u2665', '\u2663', '\u2660']
    ranks = ["2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"]
    
    def __init__(self, suit, rank):
        self.suit = suit
        self.rank = rank
        
    def __str__(self):
        return f"{Card.ranks[self.rank]}{Card.suits[self.suit]}"
    
    def __lt__(self, other):
        if self.rank == other.rank:
            return self.suit < other.suit
        else:
            return self.rank < other.rank

Notice we also have class variables suits and ranks (with s). They are used to give a representation in the __str__() method.

Class variables are available and the same across all objects.

Also, notice the __init__(self, suit, rank), which is a method which is called at creation of the object, and it assigns variables to the instance variables (the ones with self)

Step 3: Implement the Deck class

A Deck should represent a pile of card.

Here we want it to create a new shuffled deck of cards when you create a new instance of the Deck object.

That can be accomplished as follows.

import random
class Deck:
    def __init__(self):
        self.deck = []
        for suit in range(4):
            for rank in range(13):
                self.deck.append(Card(suit, rank))
        self.shuffle()
        
    def __len__(self):
        return len(self.deck)
    
    def add_card(self, card):
        self.deck.append(card)
        
    def pop_card(self):
        return self.deck.pop()
    
    def shuffle(self):
        random.shuffle(self.deck)

Notice that __len__() method is also a special, and returns the length of the object. This is handy, if you want to use len(…) on an object instance of Deck.

The rest of the methods are simple and straightforward.

Step 4: Implement the Hand class

The hand class is a sub-class of Deck. How does that make sense?

Well, it will share the same instance variable and methods with some additional ones.

Think about it, a Hand is like a Deck of card, as it is a collection of cards.

How to implement that.

class Hand(Deck):
    def __init__(self, label):
        self.deck = []
        self.label = label
        self.win_count = 0
        
    def __str__(self):
        return self.label + ': ' + ' '.join([str(card) for card in self.deck])
    
    def get_label(self):
        return self.label
    
    def get_win_count(self):
        return self.win_count
    
    def round_winner(self):
        self.win_count = self.win_count + 1

Notice that we overwrite the __init__(…) method, as we do not want to create a full deck of cards. Here we start with empty hands.

Step 5: A simple game

  • Create a Deck of cards.
  • Create 4 players (P1, P2, P3, P4)
  • Divided all cards to 4 players.
  • Assume you are P1, print the hand of P1.
  • The game has 13 rounds:
    • Each player plays 1 card.
    • The player with highest card wins.
    • Update the score for the winning hand.
    • Print cards played in round and the winner (with winning card).
  • After the 13 rounds – print score for all players (P1, P2, P3, P4).

How to do that?

deck = Deck()
hands = []
for i in range(1, 5):
    hands.append(Hand(f'P{i}'))
    
while len(deck) > 0:
    for hand in hands:
        hand.add_card(deck.pop_card())
        
print(hands[0])
for i in range(13):
    input()
    played_cards = []
    for hand in hands:
        played_cards.append(hand.pop_card())
    
    winner_card = max(played_cards)
    winner_hand = hands[played_cards.index(winner_card)]
    winner_hand.round_winner()
    
    print(f"R{i}: " + ' '.join([str(card) for card in played_cards]) + f' Winner: {winner_hand.get_label()} {str(winner_card)}')
    
for hand in hands:
    print(f"Score for {hand.get_label()}: {hand.get_win_count()}")

Amazing, right?

Want to learn more?

If this is something you like and you want to get started with Python, then this is part of a 8 hours FREE video course with full explanations, projects on each levels, and guided solutions.

The course is structured with the following resources to improve your learning experience.

  • 17 video lessons teaching you everything you need to know to get started with Python.
  • 34 Jupyter Notebooks with lesson code and projects.
  • A FREE 70+ pages eBook with all the learnings from the lessons.

See the full FREE course page here.

Master List and Dict Comprehension with Python

What will we cover?

What is List Comprehension in Python. We will demonstrate how to make List Comprehension and show how this also work with dictionaries, called, Dict Comprehension. Then show how Dict Comprehension can be used for frequency count.

Step 1: What is List Comprehension?

List Comprehension is a syntactic construct available in some programming languages for creating a list based on existing lists.

Wikipedia.org

It is easiest to demonstrate how to create it in Python (see more about lists and about foo-loops).

my_list = [i for i in range(10)]
print(my_list)

Will result in.

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Here you use the range(10) construct, which gives a sequence from 0 to 9 that can be used in the Comprehension construct.

A more direct example is given here.

my_new_list = [i*i for i in my_list]
print(my_new_list)

Which results in the following.

[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

Step 2: List Comprehension with if statements

Let’s get straight to it (see more about if-statements).

my_list = [i for i in range(10) if i % 2 == 0]
print(my_list)

This will result in.

[0, 2, 4, 6, 8]

Also, you can make List Comprehension with if-else-statements.

my_list = [i if i % 2 else -i for i in range(10)]
print(my_list)

This results in.

[0, 1, -2, 3, -4, 5, -6, 7, -8, 9]

Step 3: Dict Comprehension

Let’s dive straight into it (see more about dict).

my_list = [i for i in range(10)]
my_dict = {i: i*i for i in my_list}
print(my_dict)

Which results in.

{0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81}

Notice, that the lists do not need to contain integers or floats. The constructs also works with any other values.

Step 4: Frequency Count using Dict Comprehension

This is amazing.

text = 'aabbccccc'
freq = {c: text.count(c) for c in text}
print(freq)

Which results in.

{'a': 2, 'b': 2, 'c': 5}

Notice, that here we will recount for each instance of the letters. To avoid that you can do as follows.

text = 'aabbccccc'
freq = {c: text.count(c) for c in set(text)}
print(freq)

Which results in the same output. The difference is that you only count for each letter once.

Step 5: Want more?

I am happy you asked.

If this is something you like and you want to get started with Python, then this is part of a 8 hours FREE video course with full explanations, projects on each levels, and guided solutions.

The course is structured with the following resources to improve your learning experience.

  • 17 video lessons teaching you everything you need to know to get started with Python.
  • 34 Jupyter Notebooks with lesson code and projects.
  • A FREE 70+ pages eBook with all the learnings from the lessons.

See the full FREE course page here.

Master Recursion with Python with Examples | Fibonacci numbers | Tower of Hanoi

What will we cover?

We will learn what recursion is and why it can help simplify your code.

Step 1: What is recursion?

In computer science, recursion is a method of solving a problem where the solution depends on solutions to smaller instances of the same problem.

It is often used in computer science to take one complex problem and break it down to smaller sub-problems, which eventually will have a simple base case.

How could a real life problem be?

Imagine you are standing in a long line and you do not see the beginning of it. You want to figure out how many people are in front of you.

How can you solve this?

Ask the person in front of you how many people are in front. It the person does not know, then this person should ask the person in front as well. When you get the answer, return the answer added one (as that person will count one).

Imagine this goes on until you reach the first person in the line. Then this person will answer 0 (there are no-one in front).

Then the next person will answer 1. The next 2 and so forth.

When the answer reaches you, you get the answer of how many are in front of you.

What to learn from this?

Well, the problem of how many are in front of you is complex and you cannot solve it yourself. Then you break it down to a smaller problem, and send that problem to the next one. When you get the answer you update it with your part.

This is done all they way down to the base case when it reaches the first person.

This is the essence of recursion.

Step 2: The first recursion problem you solve: Fibonacci numbers

Given the Fibonacci number defined as

  • F_0 = 0
  • F_1 = 1
  • F_n = F_(n-1) + F_(n-2)

The sequence begins as follows

0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, …

Create a list of first n Fibonacci numbers.

First take a moment and try to think how you would solve this? Seems a bit complex.

Then look at this recursive solution.

def fib(n):
    if n <= 1:
        return n
    return fib(n - 1) + fib(n - 2)

It is important to notice the base case (n <= 1). Why? Because without the base case it would never terminate.

Then notice how it makes calls to smaller instances of the same function – these are the recursive calls.

Now that is beautiful.

Step 3: Tower of Hanoi

The Tower of Hanoi is an amazing mathematical problem to solve.

Tower of Hanoi Explained

Before we set the rules, let’s see how our universe looks like.

  • All the disks have different sizes.
  • The goal is to move all the disks from on tower (rod) to another one with the following 3 rules.
    1. You can only move one disk at the time.
    2. You can only take the top disk and place on top of another tower (rod).
    3. You cannot place a bigger disk on top of a smaller disk.
  • The first two rules combined means that you can only take one top disk and move it.
  • The third rule says, that we cannot move disk 2 on top of disk 1.
  • Game: How do you get from here.
  • To here – following the 3 rules.

How to Solve Tower of Hanoi Recursively

  • Assume you can solve the smaller problem of 2 disks.
  • Then we can move 2 disk at the same time
  • Then we can move disk 3 on place.
  • And we can move the subproblem of 2 disk on place.

The Implemented Solution of Tower of Hanoi in Python

We need to represent the towers. This can be done by using a list of list (see more about lists).

towers = [[3, 2, 1], [], []]

Then a way to move plates.

def move(towers, from_tower, dest_tower):
    disk = towers[from_tower].pop()
    towers[dest_tower].append(disk)
    return towers

As a helper function we want to print it on the way (see more about for-loops).

def print_towers(towers):
    for i in range(3, 0, -1):
        for tower in towers:
            if len(tower) >= i:
                print(tower[i - 1], end='  ')
            else:
                print('|', end='  ')
        print()
    print('-------')

Then print_towers(towers) would print

1  |  |  
2  |  |  
3  |  |  
-------

Finally, the algorithm we want to implement is as follows.

  • Step 1: Represent the towers as [[3, 2, 1], [], []]
  • Step 2: Create a move function, which takes the towers and can move a disk from one tower to another.
    • HINT: Use .pop() and .append(.)
  • Step 3: Make a helper function to print the towers
    • HINT: Assume that we have 3 towers and 3 disks
  • Step 4: The recursive function
    • solve_tower_of_hanoi(towers, n, start_tower, dest_tower, aux_tower)
    • n is the number of disks we move, starting with 3, then we call recursive down with 2, 1, and 0.
    • The base case is n = 0, just return in that case
    • Move subproblem of n – 1 disks from start_tower to aux_tower.
    • Move disk n to dest_tower. (you can print the tower here if you like)
    • Move subproblem of n – 1 disk from aux_tower to dest_tower.
def solve_tower_of_hanoi(towers, n, start_tower, dest_tower, aux_tower):
    if n == 0:
        return
    # Move subproblem of n - 1 disks from start_tower to aux_tower.
    solve_tower_of_hanoi(towers, n - 1, start_tower, aux_tower, dest_tower)
    
    # Move disk n to dest_tower. (you can print the tower here if you like)
    move(towers, start_tower, dest_tower)
    print_towers(towers)
    
    # Move subproblem of n - 1 disk from aux_tower to dest_tower.
    solve_tower_of_hanoi(towers, n - 1, aux_tower, dest_tower, start_tower)

Try it out.

towers = [[3, 2, 1], [], []]
print_towers(towers)
solve_tower_of_hanoi(towers, 3, 0, 2, 1)

Want more?

I am happy you asked.

If this is something you like and you want to get started with Python, then this is part of a 8 hours FREE video course with full explanations, projects on each levels, and guided solutions.

The course is structured with the following resources to improve your learning experience.

  • 17 video lessons teaching you everything you need to know to get started with Python.
  • 34 Jupyter Notebooks with lesson code and projects.
  • A FREE 70+ pages eBook with all the learnings from the lessons.

See the full FREE course page here.