Welcome to Card Clash:Color Chaos
Introduction
Welcome to the Card Game Assignment!This assignment is designed to help you learn the fundamentals of algorithms and data structures by implementing a card game.Below are the details of the game,its rules,and your tasks.Good luck and have fun!
Game Overview
The card game involves multiple players and a deck of cards.The objective is to be the first player to get rid of all your cards.Each card has a colour and a value,and there are special black cards with unique abilities.
Rules of the game
The deck
In one standard deck you'll find the 112 cards shown below,built from four blocks of 26 cards: one block for each green,red,blue,and yellow colours.For each colour,the 26 cards are:
● 20 number cards (2 of each number from 0 up to 9)
● 2 skip cards.
● 2 reverse cards.
● 2 draw 2 cards.
In addition to this,there are 8 special cards:
● 4 BLACK draw 4 cards.
● 4 BLACK CRAZY cards.
Meaning of the Special Cards
You'll find cards that don't have basic numbers on-these special cards can help you to win the game,so use them wisely!
Reverse card
This card reverses the direction of play,for example from clockwise to anticlockwise.
Skip card
This card simply skips the next player's turn.
Draw 2 card
A draw 2 card forces the next player to pick up 2 cards and makes them skip their turn.Since the aim of the game is to get rid of all of your cards,playing a+2 card can be a clever strategy to prevent your opponent from winning.
BLACK CRAZY card
The Black Crazy card has all 4 card colours on it and allows a player to pick which colour they want the game to move on with.In our version of the game,we will be choosing a colour using the given RandomGen class using the following code:
CardColor(RandomGen.randint(0,3)) where RandomGen is an instance of the RandomGen class and CardColor is an enum class representing the colours of the cards.
BLACK Draw 4
Similar to the standard Black Crazy card,playing this card means that the colour should be changed randomly using the given RandomGen class using the following code: CardColor(RandomGen.randint(0,3)). The difference,however,with this card is that the next player must draw 4 cards from the draw pile and miss their turn.
Playing the game
● Initially all cards are put in a Draw Pile face down.Next to the pile,a space should be designated for a Discard Pile.Next,each player take turns to draw a card until everyone has 7 cards.Then,cards are drawn from the Draw Pile to the Discard Pile facing up until there is a number card,whose colour and label is set to be the current colour and the current label of the game.All set,game begins!
● At the beginning,players take turns in the order they were given in the input.The first player in the input array goes first,followed by the next player in increasing index order.
● In each round,a player can put a card to the Discard Pile facing up to play a card. A player can only play a card if that card is of the same colour as the current colour of the game,or has the same label (number/reverse/skip)as the current label of the game. Alternatively,a Black Crazy card,or a Black Draw Four card can always be played.
● If a player can play ANY card in their hand,they must play it.If they cannot,they must draw a card from the Draw Pile.If that card can be played,they must play it.Otherwise, they keep the card and the turn moves to the next player.
● After a card is played,the current colour and current label are updated to match the played card.If a special card is played,its effect is applied immediately,unless the game ends at that moment.
● If the Draw Pile is empty,the cards in the Discard Pile are shuffled and added to new Draw Pile.
● A player wins by playing all their cards.Once a player runs out of cards,the game ends immediately and that player is declared the winner.
Task 1-Implement the Card and Player Classes
Complexity analysis reminder
Although DECK_SIZE and NUM_CARDS_AT_INIT are set to constants in config.py, we may change these numbers during testing.You should consider these number to be variable and calculate the complexity accordingly.
File card.py has two enum classes:CardColor and CardLabel. You must now modify it to implement the Card class with the following attributes:
· color -an enum value representing the color of the card.
· label -an enum value representing the label of the card.
You must also modify file player.py to implement the Player class with the following attributes:
· name -a string representing the name of the player.
· hand -a collection of Card objects representing the cards in the player's hand.
The Player class also has the following methods:
· add_card(self,card:Card) -a method that takes a Card object as an argument and adds it to the hand.The method should return None.
● play_card(self,current_color:CardColor,current_label: CardLabel) -a method that takes the current colour and the current label of the game and removes a playable card from the hand:
o If no cards are playable,return None.
o If there are multiple playable cards,the card with the least colour(according to the enum values)is played.
o If there are multiple playable cards with the least colour,the card with the least card label(according to the enum values)is played.
o If there are multiple playable cards with the least colour and the least card label, an arbitrary card among them can be played.
· is_empty(self) -a method that returns whether the player's hand is empty.
· cards_in_hand(self) -a method that returns the number of cards left in the player's hand.
NOTE-For both of these classes,please add additional helper methods if you think they are necessary(including a __str__ method to help with debugging) .
Task 2-The GameBoard Class
Complexity analysis reminder
Although DECK_SIZE and NUM_CARDS_AT_INIT are set to constants in config.py, we may change these numbers during testing.You should consider these number to be variable and calculate the complexity accordingly.
You have been given some code for the GameBoard class in the game_board.py .You must now modify it to implement the class with the following attributes:
· init (self,cards:ArrayList[Card]) -Initialises a GameBoard object with a list of Card objects( cards )with the following attributes:
o draw_pile: A collection of Card objects from which players draw during the game.Initially, draw_pile should contain all Card objects from cards in the same order.The first card in cards should be the first card drawn using draw_card() below.
o discard_pile: A collection of Card objects representing cards that have already been played.Initially, discard_pile should be empty.
· discard_card(self,card:Card -Takes a Card object and adds it to discard_pile .
· reshuffle(self) -Moves all cards from discard_pile to draw_pile after shuffling them using RandomGen.random_shuffle. Ensure that the first card in the list after random_shuffle should be the first card drawn in the call of draw_card method. You can assume that the draw_pile is empty when reshuffle(self) is called.(See Task 4 for more details)
· draw_card(self) -Returns a card from draw_pile according to the order if it is not empty.If draw_pile is empty,it reshuffles discard_pile into draw_pile before drawing a card.
Complexity analysis reminder
You can assume that the complexity of RandomGen.random_shuffle(collections) is 0(NlogN) where N is the number of elements in collections.
Once you complete this task,you should be able to pass all the tests starting with 2.*or python run_tests.py 2
Task 3-The Game Class
Complexity analysis reminder
Although DECK_SIZE and NUM_CARDS_AT_INIT are set to constants in config.py,we may change these numbers during testing.You should consider these number to be variable and calculate the complexity accordingly.
You have been given some code in file game.py. You must now modify it to implement the Game class with the following attributes:
· players -a collection of Player objects representing the players in the game.The game should commence in the order of their indices in players.
· current_player -a Player object representing the player whose turn it is to play.
· current_color -an enum value representing the colour of the top card of the discard_pile .
· current_label -an enum value representing the label of the top card of the discard_pile .
· game_board-a GameBoard object representing the game board with draw_pile and discard_pile .
Note that when initialising the Game object,all attributes should be empty.
To help you program the game logic,you are required to implement the following methods:
· init (self) - The constructor of the class.This method takes no arguments but should be used to set up the instance variables
· initialise_game(self,players:ArrayList) -This method performs the following tasks:
o Use the list of Player objects being passed to this method to populate the players attribute of the Game object.
o Call the method generate_cards to get a list of shuffled cards,and put these cards to game_board .
o Each player draw one card in the order of the players from game_board. Continue to draw cards until each player has Config.NUM_CARDS_AT_INIT cards in their hand.
o A card is drawn and discarded on the game_board. If the top card is not a number card,repeat drawing a new one until the top card is a number card.
o Update the current_color and current_label attributes appropriately to make them consistent with the top card.The current_player attribute remains unchanged,i.e.,None (indicating the game has not yet started).
· next_player(self) -a method that returns the next player in the game,which means:
o After initialisation and before game starts,the next player should be the first one in the argument players for initialise_game.
o During the game,the next player should be the player in the next round.
· reverse_players(self) -a method that reverse the order of Player objects in the players attribute.The method should return None.
· skip_next_player(self) -a method that skips the next player's turn.The method should return None .
· play_draw_two(self) -a method that forces the next player to draw two cards and skips the next player's turn.The method should return None.
· play_black(self,card:Card) -a method that takes a Card object and changes the game's current_color instance variable to a randomly chosen colour.To choose the colour,we use the following code: CardColor(RandomGen.randint(0,3)) where RandomGen is an instance of the RandomGen class and CardColor is an enum class representing the colours of the cards.If its a Black Draw 4 card,this method makes the next player draw 4 cards from the draw_pile and skips the next player's turn.The method should return None .Note that:
o Redundant calls to RandomGen.randint(0,3) may result in a loss of marks.
o You can assume that the card being passed is always a Black card.
o Try to reuse predefined methods where possible to achieve this.
· draw_card(self,player:Player,playing:bool) -a method that takes a Player object as an argument and draws a Card object from the draw_pile. If the card can be played and the playing argument is True, the card should be returned. Otherwise,the card should be added to the player's hand and the method should return None .This method should be called multiple times if the played card is a Draw two or Draw four.
NOTE-You can assume that there are at least 2 players.You can also assume that the players will not have duplicate names.
Once you complete this task,you should be able to pass all the tests starting with 3.*or python run_tests.py 3
Task 4-Playing the Game
Complexity analysis reminder
Although DECK_SIZE and NUM_CARDS_AT_INIT are set to constants in config.py, we may change these numbers during testing.You should consider these number to be variable and calculate the complexity accordingly.
Now you can implement the game logic.You are required to implement the following methods
· play_game(self) -This method simulates the Card Clash game and returns the winning player.The returned player must be the same Player object passed in initialise_game; do not create new Player objects.The method should implement the following:
o The game starts and players take turns in the order of the players argument for initialise_game method.The game ends immediately when a player plays the last card in their hand.That player is the winner of the game.
o At each turn,a player attempt to play a card in their hand.A card can be played if it matches either the current colour or the current label of the game.Alternatively, a Black card can be played on any card.
o If a player cannot play any card from their hand,they must draw one card from the draw pile.If the drawn card can be played,it must be played immediately; otherwise,it remains in the player's hand and the turn passes to the next player.
o If the draw pile is empty whenever a player attempts to draw a card,reshuffle the discard pile (using the reshufflemethod of the GameBoard class)into the draw pile before drawing.Ensure that the discard pile is only reshuffled into the draw pile when necessary (i.e.,when the draw pile is empty).Re-shuffling an already shufled array may lead to unpredictable outcomes,causing discrepancies with expected test results.
o A played special card has the following effect if the game is not ended:
■ Draw Two Card-when a player plays a Draw Two card,the next player must draw two cards from the draw pile and is not allowed to play them immediately.
■ Draw Four Card-similarly,playing a Draw Four card forces the next player to draw four cards and skip their turn.
■ Reverse Card-when a reverse card is played,the order of players should be reversed.
■ Black Card:when a player plays a Black card(Black Crazy or Black Draw 4),they choose the new colour for the game.In our setting of the game, please use CardColor(RandomGen.randint(0,3)) to select a colour.
o Remember to update current_colour and current_label to match the latest played card,except when a Black card is played and current_colour is updated by random.
o You may utilise the previously defined methods to manage the game logic.
Once you complete this task,you should be able to pass all the tests starting with 4.*or python run_tests.py 4
NOTE -In addition to the tests,you have been given two log files (test4_1.md and test4_2.md) that detail the two scenarios given in the two test cases for this task.If you set the seed and the players correctly,your game should play out identically to the given game.If there are variations,that means your game has NOT been set up correctly.Please try to troubleshoot by running your code line-by-line in that case.