CSC 1913 Spring 2026 Project 2 – Create your own Card Gane

Introduction to Algorithms, Data Structures, and Program Development

Deadline: Fri, Mar 27, 2026 5pm

Essential Information

Pacing and Planning

This is a 3-week long assignment. As such you should expect it to be longer, and more complicated, than past assignments you have undertaken. Similar to last project – there will be several moments in this project where you need to make some key design decisions. As before – plan time for understanding and designing code for this problem.

Deadline

The deadline for this assignment is Fri, Mar 27, 2026 5:00PM. Late work is not generally accepted on projects – there will be a brief grace period on gradescope to help mitigate technical issues – but beyond this grace period late work will not be accepted without an exception.

Files

This project uses a fairly typical object-oriented design. This means that you will have many required files, but each file will not necessarily be super-big.

  • Card.java
  • Deck.java
  • Hand.java
  • CardGameMatch.java
  • AI.java
  • SmallestCardAI.java
  • BiggestCardAI.java
  • Tournament.java
  • README.txt

Download testing files. Additional testing files may be provided over the course of the project, but ultimately you will be responsible for testing, and debugging, the code on your own.

Other important restrictions

Individual project – Unlike labs, where partner work is allowed, this project is an individual assignment.

You are not to use Gen AI to write code for this project.

This means that you are expected to solve this problem independently relying only on course resources (zybook, lecture, office hours) for assistance.

  • Inappropriate online resources, or collaboration at any level with another student will result in a grade of 0 on this assignment, even if appropriate attribution is given.
  • Inappropriate online resources, or collaboration at any level with another student without attribution will be treated as an incident of academic dishonesty.

To be very clear, you can ask other students questions only about this document itself (“What is Adriana asking for here?”).

Questions such as “how would you approach function X”, “How are you representing data in part 2?”, or even “I’m stuck on part B can you give me a pointer” are considered inappropriate collaboration even if no specific code is exchanged.

Coming up with general approaches for data representation, and finding active ways to become unstuck are all parts of the programming process, and therefore part of the work of this assignment that we are asking you to do independently.

Likewise, you are free to google search for any information you want about the java programming language and included java classes but it is not reasonable to look for information about the problem in this project (I.E. “how to program card games” would be an inappropriate google search)

Allowed java classes:

There are A LOT of java classes, which solve many interesting problems.

You are not allowed to use any of the pre-built java classes except those we’ve explicitly discussed in class such as Scanner, Random, and String.

If you are unsure if a java class would be allowed here you should ask. Using unapproved java classes can lead to failing this assignment.

Specific examples of BANNED java classes:

  • Any subclass of java.util.List (java’s list classes)
  • Any subclass of java.util.Map (java’s dictionary classes)
  • Any subclass of java.util.Set (java’s set classes)
  • java.util.Collections

Part of our goal here is practicing data storage using arrays. Every data storage task can be done by hand without these advanced data structures.

Introduction

This project has three main parts:

  • The classes to represent any card game (Card, Deck, Hand)
  • The classes that are specific to a card game of your creation (CardGameMatch.java + any other class you might need. For example, you might need a CardPile if your card game requires a card pile shared among all game players)
  • The classes that you are going to write to play the game automatically, and to test out different strategies for playing the game (AI.java, SmallestCardAI.java, BiggestCardAI.java, Tournament.java)

You are to create and specify a card game, and you will write your game specification, including but not limited to:

  • How it is played
  • How many cards each player gets in their hard
  • How players take turns
  • What the rules are for playing and winning the game

You are to include the game specification with all the above in addition to anything else about the game in the README.txt you are to submit to gradescope with your .java files.

Here’s an EXAMPLE of what your game specification should include (do not copy and paste these, your specifications should be DIFFERENT from these):

  • Each player has a hand of 5 cards.
  • The game starts by dealing a card from the deck onto a pile in the middle.
  • Players then take turns playing a card from their hand. The card they play must have the same suit, as the card in the middle, or the same or higher rank.
  • Play goes back and forth until one of the players is unable to play a card. At that point the other player gets one point.
  • The first player to 10 points wins.

As a software project, this will have two core parts:

  • Representing Card games (in general) and the card game (in particular). This part of the project will focus on standard java objects and arrays.
  • Representing different approaches to playing this game, and writing code to automatically compare strategies. This part of the project will focus on inheritance and polymorphism.

You can think of this as two “layers” to the code. First you’ll need a “layer” simply to allow java programs talking about / thinking about the card game. Then you will write MORE core using this first “layer” of code, which will automatically compare strategies for our game.

Learning Goals

This assignment is designed with a few learning goals in mind:

  • Implement several java objects of varying complexity.
  • Make a few object representation decisions based on a general description of an object
  • Practice using arrays for task-specific data structures (this will help prepare you for general-purpose data structures later!)
  • See a specific fully-worked-out example where polymorphism and inheritance allow much more general and generic code.
  • See how AI driven simulations can provide insight into competitive game playing.

Creating a Game Specification

Very detailed specifications of your game will be required. You are to write up the specifications in a README.txt file that you are to submit to gradescope with your .java files.

Your game is to be played with a standard deck of cards.

Include in your specification (and implementation):

  • How does the game start? How many cards does each player get in their hand? (the cards should be drawn from one deck of cards, the same deck of cards, unless you specify otherwise. But explain your decision.)
  • After a player plays a card, do they draw a replacement from the deck?

You might consider having a new deck be automatically created if the deck is ever empty – it is immediately re-shuffled (so the players can always draw another card).

Obviously, in real-life play when reshuffling the deck it will only have cards that are not in hands. However, this is quite hard to program well with the tools we have so-far. Therefore we will instead simulate the game as if each time the deck is empty we open and shuffle a new 52 card deck of cards.

The basic flow of your game should be done in rounds, with each round being worth one point.

How is each round structured?

  • How does a single round of your game start?
  • When do players draw new cards from the deck?
  • Is there a “card pile” that is shared by all players?
  • How do players take turns? What do they do? How do they play a card?
  • What cards can be played? What are the conditions for a card to be playable? Think about suits (suit is the symbol on the card: clubs, spades, diamonds, or hearts) and rank (rank is the number of the card: aces, twos, and threes are low, 10, jacks, queens, and kings are high, and so-forth.)
  • What happens if a player does not have a card that meets playability requirements in their hand?
  • At each round, when do players stop playing? Do players play until someone loses the round? How do they get points for each round?
  • Does the winner of a previous round gets to go first the next round (first round is normally given to “player 1” – chosen before the game begins)
  • Are rounds are played back-to-back?
  • What happens at the end of the round? Do players keep their hands between rounds? If there’s a card pile, is the card pile is discarded at the end of the round?

When is the game over? How is the winner of the game determined?

Example of how a game could be played (this is an example to guide you to create steps for your game, do not copy these):

  • Shuffle the deck of cards
  • Deal two 5-card hands two player 1 and player 2
  • Player 1 plays first in the first round
  • While no player has won 10 rounds, play one round:
    • Deal a card to start the card pile in the middle
    • Starting with player 1 in the first round, or whoever won the last round in all other rounds, players take turns picking who goes first.
    • if a player has no legal cards, that player loses the round, and the other player wins. The other player will have to go first next round.
    • Discard the card pile in the middle.

Whoever won 10 rounds is the winner of the game!

It will take you a lot of time to be detailed about how your game is played. You will ultimately need to program this logic.

Take a moment to make sure you understand every little detail of this – as there are a lot of important details that effect the nature of your game. For example, if you make player 1 go first every time the game becomes much less fair than if the player who won last round goes first.

Once you think you have the details, you might want to take a moment to think out how to divide this logic into useful functions. For example, you could have a function to handle one turn, or one round. As you do this, think about how you will track details like who goes first in which round, or whose turn is next. This thinking will come in useful later – so take notes now while the design of the game is fresh in your mind!

Creativity + Use of Gen AI

You are allowed (but you don’t have to) to use Gen AI (ChatGPT, Claude, Gemini, etc.) to help you create a game. If your game is exactly the same as another student’s, you will not get full points for this project. You are expected to create a new, never created before, game. You are also allowed to use your own experience – is there a game you played as a child, that your friends and family created? If so, you can base your project on that game, but make sure to specify that in your README.txt file. If you use Gen AI, also specify how you have done so in your README.txt file.

Prompt Ideas

Here are some prompts for you (if you decide not to use Gen AI) or to inspire you if you are working with Gen AI:

  • “Suggest 10 card game mechanics that have never been combined before, but use a standard deck of cards”
  • “What are some unusual win conditions for a card game that don’t involve points or eliminating opponents?”
  • “Give me 5 card game mechanics inspired by [topic: cooking / space exploration / ecosystems / etc.] that would feel fresh and thematic.”
  • “What assumptions do most card games make that could be broken or subverted to create something innovative?”
  • “List common card game rules (with a standard deck of cards) that players take for granted, and suggest alternatives for each. The game should include a standard deck of cards, and a hand for each players that is drawn from that deck of cards”
  • “I have this card game idea: [description]. What makes it similar to existing games, and how could I make it more original?”
  • “What are potential problems or ambiguities in this card game: [description]?”
  • “Suggest three variations of this mechanic that would change the strategy significantly: [mechanic description].”
  • “Rewrite this rule so it is unambiguous and easy to understand for a first-time player: [rule description].”
  • “Identify any gaps or edge cases not covered by this: [rules’ description].”
  • “Given these rules [description], what strategies might a player exploit to win?”

Card Game Strategy

While there are theoretically infinite possible strategies, we have to come up with 3 simple strategies (minimum requirement). You are of course encouraged to explore more – see if you can find the BEST (or worst) possible strategy!

  • The basic AI will be a “baseline” – this means we don’t expect it to perform well, but we think it will be useful to compare against. * This basic AI will play a random valid card each turn. Strictly, we will ask you to program the AI to play the first valid card in their hand * Since this AI has no real “strategy” – any AI that does better than it can be said to have a good strategy, and any AI that does worse can be said to have bad strategy (worse than not trying!)
  • The second AI will always play the smallest-ranked valid card in their hand.
  • The third AI will always play the highest-ranked valid card in their hand.

Requirements

The design for this program is very typical of an Object-Oriented design. This is to say, it has many classes, most of which are small and serve one well defined purpose:

  • Card - represents one playing card
  • Deck - represents a deck of playing cards
  • Hand - represents a hand of playing cards
  • CardGameMatch - represents a match-up of two AIs at your Card Game
  • AI - a Java class. This serves both as a Random AI, and the parent class for all other AIs.
  • SmallestCardAI - an AI that plays the lowest-rank valid card in its hand.
  • BiggestCardAI - an AI that plays the highest-rank valid card in its hand.
  • Tournament - a driver class with a main method that reports the win-rate for every possible pair of AIs.

While this may sound like many classes to write, most of them are relatively simple on their own. By splitting the behavior up like this you can also focus on single classes at a time. You could, for example, try to write one (and only one) class a day and be done in a little over a week.

You are not to use Gen AI to write code for this project.

Card

The Card class represents a single playing card. There are MANY ways to represent a playing card in any modern programming language. We are going to represent these with two integers: rank (the number on the card) and suit. For rank we will use 1 to represent “Ace”, 2 to represent “Two”, and so-forth until we hit 11 (Jack) 12 (Queen) and 13 (King). For suit we will use 1 to represent Spades, 2 to represent Hearts, 3 to represent Clubs, and 4 to represent Diamonds.

Your card object should be immutable, meaning that once constructed there should be no way to change it. Your Card class can have any other variables or methods you want, but it must have all of the methods described below. These methods must have these exact names, and parameter types (although you can name the parameters differently)

public Card(int rank, int suit)

Constructor.

  • The first int should indicate the rank of the card (1 = Ace, 2 = Two, …, 11 = Jack, 12 = Queen, 13 = King)
  • The second int indicates the suit 1 = Spades, 2 = Hearts, 3 = Clubs, 4 = Diamonds.

This constructor should validate its inputs – if an invalid suit or rank is given it should print an error message. (See the tester code for the exact expected message) and then set private variables to be the Ace of Spades.

public int getRankNum()

This method should return the number representation of the cards rank.

public String getRankName()

This method should return the string naming the cards rank (I.E. “Ace”, “Four”, “Ten”, “Queen” etc.). All rank names should have the first letter capitalized (title case).

public String getSuitName()

This method should return the string naming the cards suit (“Spades”, “Hearts”, “Clubs”, or “Diamonds”)

public String toString()

Your Card should override the default toString method to one that prints a human readable name for the card. Once written this will help you when using print to debug since it provides a human readable description of the card. Examples of the type of output we are looking for can be found in the test files.

public boolean equals(Object obj)

You card class should override the default equals method.

A Card should only be equal to other instances of the Card class, and then only other cards that have the same rank and suit.

NOTE This is not a typo – the parameter should be Object, not Card here. We will discuss this function, why it is the way it is, and how to program it, in lecture.

Deck

The Deck class represents a deck of cards. It must use an array of type cards (length 52) to represent the cards and their current order. You will also need at least one additional variable to track which cards have been dealt, and which have not.

NOTE/WARNING I’m giving you extra design freedom for how to make the private internal variable management of this class work. You are required to store cards in an array, but beyond that we are not telling you how to track which cards are used and which are not.

As such, you should expect some many of the function descriptions are written abstractly representing the external view of the object, not its internal view. For example, the draw method will say it “removes a card from the deck” – since you are storing your deck in an array you likely will not actually remove the card from the array, but instead would update variables so that this card cannot be drawn again.

You should seek an efficient way to do this, and think carefully about the \(O(1)\) efficiency requirements below. As a hint – one integer, used well, can solve this problem.

We are going to implement Deck so that if you draw from an empty deck it automatically reshuffles. In the real world reshuffling a deck of cards would only replace cards not otherwise in use. To make life simpler, however, we will reshuffle a new deck of 52 cards. You can think of this like opening a new deck of cards, rather than trying to collect all the discarded cards. While this may lead to certain cards being duplicated in our game, it shouldn’t substantially effect the quality of our simulation, so it should be fine.

Your Deck can have any other variables or methods you want, but it must have all of the methods described below. These methods must have these exact names, and parameter types (although you can name the parameters differently).

public Deck()

Constructor - creates a new deck.

  • Should make an array containing 52 different Cards.
  • You must use one or more loops: you will receive no points if you just write 52 assignment statements. (writing 52 assignments statements will be considered hardcoding)
  • The order of Cards within the array does not matter.
  • The last line of your constructor should call your shuffle method so that all decks are shuffled by default.

public void shuffle()

Shuffle the deck of Cards that is represented by the array you made in the constructor.

  • The easiest way is the Durstenfeld-Fisher-Yates1 algorithm, named after its inventors.
  • The algorithm exchanges randomly chosen pairs of array elements, and works in \(O(n)\) time for an array of size n.

You must use the following pseudocode for this algorithm.

Do the following steps for the integer values of \(i\) starting from the length of the array minus one, and ending with 1.

  • Let \(j\) be a random integer between 0 and \(i\), inclusive.
  • Exchange the array elements at indices \(i\) and \(j\).

To generate random numbers, please use Java’s Random object, and in particular the Random class method public int nextInt(int bound) which generates a number between 0 and \(bound\) (not including \(bound\))).

public Card draw()

Draw and return the next card.

  • Whatever card is drawn should not be drawn again until the deck is shuffled again. This should decrease the number of cards remaining.

NOTE: you do not want to pick a card at random here – the Card array should be in a random order, so you should come up with a way to return the “next” card that has not been drawn.

This method must work in \(O(1)\) time (unless it needs to reshuffle).

If the deck is empty it should shuffle the cards and then deal the new top of the deck.

public int cardsRemaining()

Returns the number of cards remaining before the next reshuffle.

This should return 52 after constructor or a call to shuffle, and decrease with calls to draw down to 0.

public boolean isEmpty()

Returns whether or not the deck is empty. If this returns true, the next call to draw will trigger a reshuffle.

Hand

The Hand class represents a hand full of cards. Careful here, a hand draws from a deck, but each hand does not have their own deck.

To make this class easier to use, we will make the Hand class automatically draw cards from a deck as cards are removed from the hand.

While we will only use one hand size for this specific program, the Hand class should be designed to work with any sized hand. Designing it this way will let this class be reused in the future.

The Hand class must represent the hand of cards using an array of cards. You will need other private variables than just the array – we will give no hint for this.

public Hand(Deck deck, int size)

Constructor.

This should create an array to store cards of the given size, and then draw it full of cards using the supplied deck.

public int getSize()

Get the size of the hand.

public Card get(int i)

Get the card at the given index in this hand.

  • If the index is 0 it would be the first, card, 1 should give the second card, etc.
  • If an index is given that is out of bounds this method should print an error (see the tester code for the exact string) and then return the first card.

public boolean remove(Card card)

This method should remove a given card from the hand.

  • If the card is found in the hand it should be removed, and a replacement card should be drawn from the deck (specifically the instance of Deck that was passed as a parameter to the constructor). In this case the method should return true.
  • If the card is not found in the hand it should return false.

You should design this method to work if the input card is null (you can, however, safely assume that the cards in the hand are not null)

AI class

As a reminder, the AI class has two major goals in our design. First, it will play the role of “parent class” to our other two AIs. This will let us use polymorphism in the Game code and easily work with any AI subclass. Secondly, this class will also serve as a baseline “no strategy” AI strategy by choosing cards essentially at random.

The AI class should have two methods:

public Card getPlay(Hand hand, ..)

Depending on your game specification, this method takes one or more parameters, the player’s hand should be one of them.

An AI (here we’re talking about any-subclass) should pick a card from the hand and return it to mark it as the card the AI intends to play.

  • The AI can return null to indicate that they have no card that can be played on this card pile.
  • The AI is not responsible for removing the card from the hand – this will be managed by another class – just choosing which card to play.
  • The AI should not return a card that is not in the input hand.

For this specific class (the AI parent class, not its subtypes) – this function should pick the first card in the hand that is valid processing from left-to-right. Since we don’t sort the cards in the hand – this is effectively random.

Other AI subclasses will work in other ways.

public String toString()

This method should return the name of the AI. For the AI class itself, that’s “Random Card AI”

SmallestCardAI

The SmallestCardAI class should be a subclass of AI and should override both methods:

public Card getPlay(Hand hand, CardPile cardPile)

This AI class should return the smallest-rank valid card in the hand. (Ties can be broken arbitrarily - we do not require any specific policy)

public String toString()

This method should return the name of the AI. For the SmallestCardAI that’s “Smallest Card AI”

BiggestCardAI

The BiggestCardAI class should be a subclass of AI and should override both methods:

public Card getPlay(Hand hand, CardPile cardPile)

This AI class should return the biggest-rank valid card in the hand. (Ties can be broken arbitrarily - we do not require any specific policy)

public String toString()

This method should return the name of the AI. For the BiggestCardAI that’s “Biggest Card AI”

CardGameMatch

The CardGameMatch class should contain the majority of the code involving playing a game between two strategies that are implemented as instances of the AI class.

This class sits at the intersection of the two major “parts” of this project: playing a card game, and comparing AIs. As such it has two core functions: 1) play a single game – reporting who won, 2) play MANY games, reporting how often player1 won as a double.

Clearly these two functions are closely related (the “play many times” function just needs to call the “play once” function in a loop and count…)

Note, the only instance variables for this class should be two AI objects. While there are other attributes such as a deck of cards, hands, or win/loss rates that you will be tempted to make instance variables, all of these are needed only for one method, and can therefore be local to that method. Making these variables function-local (instead of instance-variables) will save you a lot of debugging and headache.

While we only mandate the following three methods to be implemented, we recommend that you make several other private methods. The process of playing a single game can be complicated enough that decomposing it into methods will be useful.

public CardGameMatch(AI ai1, AI ai2)

Constructor.

This takes the two AIs that this CardGameMatch class is intended to compare.

public boolean playGame()

Play a single game.

This should play the game as described by your specifications up until one of the AIs has won 10 rounds. The return value should be true if ai1 wins, and false if ai2 wins.

As a brief reminder: The basic flow of your game should be done in rounds, with each round being worth one point.

Before you start building this function I recommend having your full-detail description of your card game rules. Even quite small changes to the correct function of the game can lead to quite large differences in ultimate outcomes to this project.

Some details on this method:

  • This method should start by constructing a new deck, and hand objects
  • This method should make sure cards are removed from the AIs hands once they are chosen for play. We do this here instead of in the AI class so that we can’t write a cheating AI.
  • Remember, the AI can chose to play null if no card is valid – make sure your code can handle “null” here.
  • AI 1 should play first in the first round. In all other rounds, your game rules should determined which AI/player goes first in the next round.

public double winRate(int nTrials)

This method should have the AIs play each other nTrials times, and report the percent of times AI 1 beat AI 2 as a double.

  • The return value should be between 0 and 1 inclusive, where 1 means “AI 1 always won”, and 0 mean “AI 2 always won”

Since the winner of this game is partially determined by chance (the best AI can’t win if it only gets bad cards) the game may need to be repeated thousands of times to get a precise estimate of the chance of one AI beating another.

But, if you repeat thousands of times, you should get relatively reliable numbers.

Tournament

This class stores the actual runable program for this project, meaning it only needs to have a main method.

  • The main method should print the win rate of every pair of AIs.

The output of my program is given below:

Random Card AI vs. Random Card AI winRate: 0.499
Random Card AI vs. Smallest Card AI winRate: 0.002
Random Card AI vs. Biggest Card AI winRate: 0.842
Smallest Card AI vs. Random Card AI winRate: 0.998
Smallest Card AI vs. Smallest Card AI winRate: 0.499
Smallest Card AI vs. Biggest Card AI winRate: 0.999
Biggest Card AI vs. Random Card AI winRate: 0.156
Biggest Card AI vs. Smallest Card AI winRate: 0.0
Biggest Card AI vs. Biggest Card AI winRate: 0.491
  • Your code does not need to output this exactly (in fact, I would be surprised if it did, as estimated winRate is somewhat random) but it should contain all this information. However, in general, two of the same AI instances should be a winRate close to 0.5.
  • Make sure you clearly label which winRate goes with which pair of AIs, and test AIs in both AI 1 and AI 2 position (as the game may be effected by who goes first in the first round!)

When calling the winRate method to compute win rates, I recommend using nTrails around 1000 or higher – less than that can be heavily subject to random chance. That said, while testing if your code works, you are free to test with smaller nTrials for speed.

Testing and incremental development

This project is quite typical of an object-oriented program design: There are A LOT of things to program, many of which are individually quite small. The idea here is to spread the complexity of the program over more code – that way no one function is individually super-complicated. This leads to longer development time, but often faster testing and debugging, since each function and class can be debugged individually.

To make this process faster, we’ve provided testing code for several of the simpler classes (those with limited random behavior) Since some of these tests rely on the behavior of other classes (you can’t test Deck without a working Card class, for example) there is an intended order to these tests. The intended order of completing these tests is as follows:

  • CardTest.java
  • DeckTest.java
  • HandTest.java
  • AITest.java

Note, these tests only cover some of the simpler classes. Once you pass all these test you will still need to manually evaluate your CardGameMatch class for correctness. The easiest way to do this is to add print statements so that you can “watch” the games get played, and confirm that the rules are being followed.

To help you plan your progress on this assignment, here’s a rough development schedule:

  • Week 1 goals: read the instructions, start writing your game specification, write the Card, Deck, and Hand classes.
  • Week 2, write the AI class, and CardGameMatch.
  • Week 3, Finish and debug CardGameMatch., write the other two AIs, test and debug carefully before submission.

Submission

For this project you should submit the following files (minimum requirements, you will probably have more files):

  • AI.java
  • BiggestCardAI.java
  • Card.java
  • Deck.java
  • Hand.java
  • SmallestCardAI.java
  • Tournament.java
  • CardGameMatch.java
  • README.txt

These files must be submitted on gradescope before the deadline (found at the beginning of this document).

There is no plan to accept late work on this assignment without an extension.

Grading

The autograder will be based on the test code, and will be worth 14 points.

This autograder is not even remotely comprehensive.

Much of the code in this project is not well suited to testing, and making it more testable would also involve telling you how to structure the code (which is something you need to be deciding for yourself!)

Make sure you are manually reviewing your code and the assignment instructions so you don’t lose the 82 points based on direct code review.

The remaining points will probably be split roughly as follows:

  • 10 points for code style
  • 6 points for the Card class
  • 6 points for the Deck class
  • 6 points for the Hand class
  • 6 points for the AI class,
  • 6 points for the SmallestCardAI
  • 6 points for the BiggestCardAI
  • 18 points for CardGameMatch
  • 2 points for the Tournament class
  • 20 points for README.txt

Footnotes

  1. For more information on this algorithm I recommend the Wikipedia page it is both informative, and currently free of actual Java source code (looking up source code for this algorithm would be cheating, so be careful what you search)↩︎