CS21 Lab 10: Classes and Objects

Due Saturday, May 8, before midnight

Read through this entire lab writeup BEFORE you begin to write any code. Taking the time to thoroughly understand what you are trying to accomplish will allow you to more effectively implement the code.

checkpoint

At the end of your lab session, be sure to run handin21.

Your lab instructor will check your files, which should reflect that you have made non-trivial progress (in python or pseudocode) towards the solution. Note that if you have not made much progress towards your solution, we expect that you would have been actively seeking help during your lab over Slack.

10% of your lab grade is dependent on this checkpoint.

If there are circumstances which prevent you from making substantial progress on this lab, please contact your lab instructor as soon as possible.

Goals

  • gain experience writing classes

  • gain experience reading and using classes

1. Introduction

This week you will implement a card game called Triad. The game Triad is quite similar to a card game called Set. You will get practice writing your own classes and becoming more familiar with object-oriented programming.

We will provide you with two supporting classes that will create the display, "draw" the cards on the screen, highlight the cards when you select them, remove cards from the screen when they form an appropriate triad, deal more cards onto the screen when necessary, and end the game when the game is over.

Your assignment is to write three other classes that will work with the classes we provide you. When your three classes are implemented, you will have completed the lab and you will have a playable version of the game Triad.

First, we will explain the rules of the game. We will then describe the Card class and the Deck class that you will need to write. Finally, we will describe the Game class that you will write which implements the logic of the game. The primary responsibility for testing the code will be yours.

There are a lot of files in this lab. Don’t worry about all of them for now! If you follow the lab writeup, we’ll guide you through which ones to edit and when. The writeup may seem long, but there are only a few files to edit and run.

2. Triad Game Play

Triad is a solitaire card game played with a deck of 81 cards. The face of each card contains a simple symbol which may be repeated. Each card can be described in four ways: by the number of symbols which appear (1, 2, or 3), by the shape of those symbols ("triangle", "circle", or "square"), by the color of those symbols ("red", "green", or "blue"), and by the style of the border ("line", "open", or "pipe"). For example, consider the three cards presented below:

3 green triangles with a line border
1 red circle with an open border
2 blue squares with a pipe border

3 green triangles, line border

1 red circle, open border

2 blue squares, pipe border

The Triad deck contains all 34 = 81 unique combinations of these properties, one card for each. At the beginning of the game, the deck is shuffled and twelve cards are dealt onto the table (also called the "tableau").

2.1. Finding Triads

The player’s objective is to find triads of cards. A triad is formed of three cards such that, for each property, all cards are different or all cards are the same. The above three cards, for instance, form a triad because they all have a different number of symbols, a different shape, a different color, and a different shading. The following three cards are also a triad:

2 blue squares with a line border
2 green squares with a pipe border
2 red squares with an open border

2 blue squares, line border

2 green squares, pipe border

2 red squares, open border

The above cards form a triad because they all have different colors and all have different border stylings, but they all have the same shape (squares) and they all have two symbols each. These cards do not form a triad:

3 green triangles with a line border
2 green circles with a line border
1 green triangle with a line border

3 green triangles, line border

2 green circles, line border

1 green triangle, line border

These three cards are all different in their number of symbols. They are also all the same in their color (green) and styling (line). But two cards show triangles and one card shows circles; thus, they are neither all different nor all the same in their shape.

If you are still unsure with how the game works, you can try playing an online Set puzzle. The shapes and styles of the cards are different, but the idea of the game is the same.

2.2. Game play

After the initial twelve cards are dealt to the tableau, play proceeds with the player finding Triads of three cards. Each time the player finds a Triad of three cards, those three cards are removed from the tableau and discarded. (There is no discard pile: they are removed from the game forever.) Then, three new cards are dealt from the deck onto the tableau to replace them. Play continues until the deck is empty; at this point, the game is over when there are no more Triads to find.

It may occasionally happen that, although there are cards left in the deck, none of the cards on the tableau form a Triad. In this case, all of the cards on the tableau are discarded. (There is no discard pile: they are removed from the game forever.) Twelve new cards are dealt from deck and those new cards are placed on the tableau.

The player wins and the game ends when there are no cards left in the deck. There is no loss condition; the only way to lose this game is by quitting.

3. Card class

The first class you are going to write will be the Card class. A Card object will represent a single Card in the game of Triad. You must define your class in a file called card.py.

Here are the instance variables for a Card class:

  • shape: a string describing the shape on the card ("square", "circle" or "triangle")

  • color: a string describing the color on the card ("red", "blue", or "green")

  • count: an integer describing how many shapes there are on the card (1, 2, or 3)

  • style: a description of the card border ("line", "open", or "pipe")

Here are the methods you need to write for the Card class:

__init__(self, shape, color, count, style)

constructor for Card objects

__str__(self)

returns a string representation of the Card

get_shape(self)

getter for the shape on the card (str)

get_color(self)

getter for the color on the card (str)

get_count(self)

getter for the count on the card (int)

get_style(self)

getter for the style on the card (str)

Here is a sample run of the Card class to demonstrate how it should work:

$ python3
Python 3.9.4 (default, Apr  4 2021, 17:42:23)
[Clang 12.0.0 (clang-1200.0.32.29)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from card import Card
>>> c1 = Card("square", "green", 3, "line")
>>> print(c1)
square,green,3,line
>>> c1.get_shape()
'square'
>>> c1.get_color()
'green'
>>> c1.get_count()
3
>>> c1.get_style()
'line'
>>>
At the bottom of the card.py file we provided, there is some test code. You can run python3 card.py, though some of the tests will fail until you’ve written all of the methods.

When you have finished with the Card class and after you have thoroughly tested it, you can move on to the Deck class. For this lab, you can assume that the Card constructor is only ever called with legal values for the shape, color, style and count.

4. Deck class

The second class you are going to write will be the Deck class, which you will save in a file called deck.py. A Deck object will represent deck of Triad cards. When you create a Deck of cards, you should create all 81 cards.

Your Deck class will only have one instance variable:

  • cards: a list of Card objects - you should use a nested for loop to create the cards, appending the cards to this list. IMPORTANT: Your Deck class must use the name cards for the name of this instance variable.

Here are the methods you need to write for the Card class:

__init__(self)

constructor for Deck objects. After calling the constructor, your deck should contain a shuffled list of 81 cards.

deal(self)

removes the first card of the cards list and returns it

cards_left(self)

returns an integer equal to the number of Cards left in the cards list

Here is a sample run of the Deck class to demonstrate how it should work (though because the deck is randomly shuffled, you will get different cards printed out than in this example.)

$ python3
Python 3.9.4 (default, Apr  4 2021, 17:42:23)
[Clang 12.0.0 (clang-1200.0.32.29)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from deck import Deck
>>> deck = Deck()
>>> print(deck.cards_left())
81
>>> card = deck.deal()
>>> print(card)
square,green,1,pipe
>>> card = deck.deal()
>>> print(card)
circle,green,1,line
>>> print(deck.cards_left())
79
>>> print(deck.deal())
square,red,3,pipe
>>> print(deck.deal())
circle,blue,2,pipe
>>> print(deck.deal())
triangle,blue,1,pipe
>>> print(deck.deal())
square,blue,3,line
>>> print(deck.deal())
triangle,red,3,pipe
>>> print(deck.cards_left())
74
>>>
Like the Card class, at the bottom of the deck.py file we provided, there is some test code. You can run python3 deck.py, though some of the tests will fail until you’ve written all of the methods.

It is critical that you do not proceed to the next section unless you are confident that your Deck and Card classes work without any errors. If anything is wrong with your Deck and/or Card classes, debugging the next class will be very difficult.

5. Game

The final class you will write is the Game class, saved in the file game.py. The Game class will manage the current state of the game and implement methods that allow the game to be played.

We have provided you a lot of code that handles displaying the cards and getting input from the user, so you will not be using all of the instance variables or writing all of the methods in this class.

The Game class will have four instance variables, only two of which you need to use:

  • deck: a Deck of cards to play the game with

  • tableau: a list of 12 cards currently on the table

There are many methods in the Game class, but you only need to write these. (Details on each are provided below.)

game_over(self)

This method returns True if the deck of cards is empty; False otherwise.

test_triad(self, i, j, k)

Given three integers, i, j, and k, check to see if the tableau cards at these three positions form a Triad. Returns True if they form a Triad; False otherwise.

find_all_triads(self)

Returns a list of all Triads found on the tableau. Each element of the list will be a list of three integers.

exists_triad(self)

Returns True if there is at least one Triad on the board.

We have provided stubs for all of these functions and you can run python3 game.py now, before you implement any code. When you run the game, you will see the cards displayed on the screen by their indices in the tableau, and you can try selecting cards. You won’t be able to find Triads (because you didn’t write test_triad yet) and you won’t be able to verify that there are any triads at all (because you didn’t write exists_triad yet). But you can pretend to play the game!

After you write each method below, re-run python3 game.py.

5.1. game_over

The game_over(self) method is the most straightforward method you’ll write in the Game class. The game is over when there are no cards left in the deck. When this method gets called, you need to return True if there are no more cards in the deck; otherwise you need to return False.

After writing the game_over method, run python3 game.py. You can test to see if your method is working by typing Q to quit the game. When the user types Q, code that we’ve written for you makes the deck empty. So, if your game_over method is working properly, the game should end.

5.2. test_triad

The test_triad(self, i, j, k) method returns True if the three Cards found at the indexes i, j, and k in the tableau list form a triad; False otherwise. For example, if i is 3, j is 7, and k is 0, this method will return True if self.tableau[3], self.tableau[7], and self.tableau[0] form a Triad. If those cards do not form a Triad, this method will return False. Remember that the three cards form a Triad if:

  • either every shape is the same or every shape is different, and

  • either every color is the same or every color is different, and

  • either every count is the same or every count is different, and

  • either every style is the same or every style is different.

Each element of the instance variable self.tableau is a Card, so you can use the getter methods for Card objects to access the shape, color, count and style of each card.

You may find that breaking this up into a series of if statements will be much easier than writing one giant if statement.

After writing the test_triad method, run python3 game.py. You can test to see if your method is working by finding Triads. It’s hard to find them if you’ve never played this game before!

5.3. find_all_triads

This function tests all possible sets of three cards on the tableau to see if they form a Triad. You’ll have to test every combination of three cards in on the tableau. You should make use of nested loops to check all of the triples of cards. For each combination of three cards, call the test_triad method you just wrote. If the combination of three cards forms a Triad, append that combination to a list of results. This function returns the list of all of the combinations. For example, if cards 0, 2, 9 form a Triad, cards 1, 2, 4 form a Triad and cards 3, 4, 5 form a Triad, find_all_triads would return the list [[0, 2, 9], [1, 2, 4], [3, 4, 5]].

Click here if you want a hint on how to check for all Triads.

After writing the find_all_triads method, run python3 game.py. You can test to see if your method is working by running the secret Cheat Mode! At the prompt where you are asked to type in an index or Q to quit, you can type C! If you type C, the game will print out all of the Triads you found in your find_all_triads method.

Are you sure your find_all_triads method is working? Click here to see how you can verify that you are getting the right results.

5.4. exists_triad

This function returns True if there is at least one Triad on the tableau; False otherwise. You can call your find_all_triads method. If the length of the list returned by find_all_triads has at least one element, return True. If the list is empty, return False.

After writing your exists_triad function, run python3 game.py. If the user thinks that there are no Triads, and there really aren’t any, the tableau will be cleared and 12 new cards will be placed on the tableau. This is challenging to test since there is often a Triad. But, you can make extensive use of Cheat Mode now!

You can refer back here to see how you can set up the board such that there aren’t any Triads, allowing you to test this method more easily.

6. Playing Triad

If everything was implemented correctly, you will have a working version of Triad that you can play — and impress your friends with.

For an upgraded user interface, replace the line from boardText import Board with the line from board import Board and then try playing again!

Answer the Questionnaire

Please edit the Questions-10.txt file in your cs21/labs/10 directory and answer the questions in that file.

Once you’re done with that, run handin21 again.

Turning in your labs…​.

Remember to run handin21 to turn in your lab files! You may run handin21 as many times as you want. Each time it will turn in any new work. We recommend running handin21 after you complete each program or after you complete significant work on any one program.