Please help review my Card game tutorial,

robust ways to implement the basics of any card game

Page 1 of 1

7 Replies - 6505 Views - Last Post: 18 June 2009 - 02:15 PM Rate Topic: -----

#1 crazyjugglerdrummer  Icon User is offline

  • GAME OVER. NERD WINS.
  • member icon

Reputation: 124
  • View blog
  • Posts: 690
  • Joined: 07-January 09

Please help review my Card game tutorial,

Posted 10 June 2009 - 08:17 AM

So far we currently don't have the ability to update our code snippets or tutorials after their time signature has expired, so could everyone please help me review this tutorial before I submit it? I will gladly listen to absolutely any comments, about improving the code, teaching style, typos, etc.

Thanks Guys!!!!!!!!!!

[Start of tutorial]



When most of use began programming, we started looking for various project ideas to try. I've noticed card games come up a lot, so let's have a look at some ways to implement a card game. We will be focusing on the core of many card games, the cards and the deck.

The Card class is fairly simple, although it can be done a number of ways. I've chosen to use numeric variables for the internal data about the suit and rank, which will get converted to strings when they need to be outputed. This lets us easily compare the suits and ranks (see if they're equal or sequential), without trying to use strings to represent them internally. If we used strings, we'd have to use

 if (card1.rank == "three" && card2.rank == "four") 


with a whole bunch of other checks for different numbers, as opposed to

 if ( card1.rank + 1 == card2.rank) 


So, we end up with something like

package javacards;

public class Card
{
	private short rank, suit;

	Card(short suit, short rank)
	{
		this.rank=rank;
		this.suit=suit;
	}

	public @Override String toString()
	{
		String a,b;
		switch(rank)
		{
			case 1:
				a="Ace";
				break;
			case 11:
				a="Jack";
				break;
			case 12:
				a="Queen";
				break;
			case 13:
				a="King";
				break;
			default:
				a=  Integer.toString( rank );
				break;
		}
		switch (suit)
		{
			case 1:
				b="hearts";
				break;
			case 2:
				b="spades";
				break;
			case 3:
				b="diamonds";
				break;
			case 4:
				b="clubs";
				break;
			default:
				b="error in suit output method: Card.toString";
		}
		  return a + " of " + b;
		}

		public short getRank() {
			return rank;
		}

		public short getSuit() {
			return suit;
		}

	}



So we have read-only suit and rank variables, a simple constructor, and some methods for converting the numerical suit and rank values into Strings. In our suit switch, we have case values for the normal 1-4 values, and a default case that returns a string indicating what went wrong and where. If the suit isn't from 1 to 4, we have a problem. This is a good debugging technique, as the output shows exactly what to do to fix the problem, as opposed to just being blank. (You can also implement this in the rank switch; I just wanted to show you the idea) :)


The Card class really isn't that complicated, so most of our time will be spent on the Deck. Our Deck class will have to make and dole out Cards. To hold our Cards, we can use a number of different storage methods/structures. I will show examples using an array and a Vector. Each storage method will have a constructor to make the cards, a drawFromDeck() method that will return a random card, and a getTotalCards() method that will return the number of cards left in the Deck. The hard part is, once we return a card we have to make sure it isn't used again.


Using Array:
package javacards;

import java.util.Random;

public class Deck {
	private Card[] cards;
	short i;

	Deck()
	{
		i=51;
		cards = new Card[52];
		short x=0;
		for (short a=1; a<=4; a++)
		{
			for (short b=1; b<=13; b++)
			 {
			   cards[x] = new Card(a,b);
			   x++;
			 }
		}
	}

	public Card drawFromDeck()
	{
		Random generator = new Random();
		int index=0;

		do {
			index = generator.nextInt( 52 );
		} while (cards[index] == null);

		i--;
		Card temp = cards[index];
		cards[index]= null;
		return temp;
	}

	public int getTotalCards()
	{
		return i;
	}
} 
[code]

First we have a constructor which fills our deck will the cards of various suits and ranks. Every time we add a card at x we increment x to put us at a fresh spot.

drawFromDeck() returns a random card from our container. We need to get a random index and return that card. We have a post-test loop that finds an array index  (less than 52) that isn't null. When we find one, we return its card and set it to null. Our deck might need to give feedback on how many cards are left, (maybe to determine if there are enough for another round of black jack or if we need to add all the cards back), so we make a variable i that represents the number cards left in the deck. i starts at 51 and is decremented everytime we take a card out. So if i is -1, then we know we're out of cards.


Now we need to write a test for this program. We want to make sure the cards that get drawn really are drawn in a random order. So we have main:

[code]
package javacards;

public class Main {

	public static void main(String[] args)
	{
		Deck deck= new Deck();
		Card C;

		System.out.println( deck.getTotalCards() );

	   while (deck.getTotalCards()!=0 )
	   {
		   C = deck.drawFromDeck();
		   System.out.println( C.toString() );
	   }
	}



When we run this, we get a good look at how the cards would come off the deck. They seem random enough, so we're okay. However, this tutorial would be pretty short if I just ended it here, and many of you are probably looking at the drawFromDeck() method with disdain at its ineffeciency, so we press on......



This is really a pretty sloppy way of doing things, as we just keep trying to find an index in the array with a card in it. If the index doesn't have a card in it, we look in a new index, unti we finally get one. We really should only have to use one index, and we can do that if we make sure that we're only looking through places that actually have cards in them.

So to fix this, we use our variable i that represents the number of cards in the array that aren't used up yet. Now we need to make sure our selection comes only from that set of cards. Say the first card we take out is at index 4. We set that to null and decrement the number of cards. But index 4 is still null. We know we have 51 cards left, but we don't know where they are in the deck. There could be empty/null indexes all over the place, at 4, 7, 20, and all we can do is just keep our fingers crossed that we don't land on one of them. How about we organize our empty card spots a little better? Maybe we can switch the chosen card's empty spot with the last card in the deck? Let's put our i variable to work.


array example number 2:

package javacards;

import java.util.Random;

public class Deck {
	private Card[] cards;
	short i;

	Deck()
	{
		i=51;
		cards = new Card[52];
		short x=0;
		for (short a=1; a<=4; a++)
		{
			for (short b=1; b<=13; b++)
			 {
			   cards[x] = new Card(a,b);
			   x++;
			 }
		}
	}

	public Card drawFromDeck()
	{
		Random generator = new Random();
		int index=0; 

		index = generator.nextInt( i );

		Card temp = cards[index];
		cards[index]=cards[i];
		cards[i]=null;
		i--;
		return temp;
	}
} 



Our random index now is some number within the boundaries of the cards left, instead of always 52. If we have 40 cards left, index won't be 48. We get a random card, SWITCH IT'S NOW NULL LOCATION WITH THE LAST CARD IN THE DECK, and then return the random card. we picked the fourth card, and put the last card in its place. The null value we get when we take out the card at index 4 is now located at spot 51, the last spot in the deck, as opposed to some random spot in the middle of the deck. This ensures that the indexes up through i always have cards in them, and that the null values are always moved to the end of the array. When we get a random index between 0 and i, we can be 100% sure that there is a fresh card in that spot.

To test this method, we can use the exact same test code as we did for the first example, as all that's changed is the internal workings of the deck class. In fact, we will use that test code for all our Deck implementations.



We've implemented a lot of features with arrays that could've easily been done with vectors (adding a card to the end of the array, removing a card by setting its place to null, keeping track of the size of the array, etc. ). A Vector has most of the features we need built in already, so we don't have to write our own code to do them.

Vector example:

package javacards;

import java.util.Vector;
import java.util.Random;

public class Deck {
	private Vector cards;

	Deck()
	{
		cards= new Vector();
		for (short a=1; a<=4; a++)
		{
			for (short b=1; b<=13; b++)
			 {
			   cards.add( new Card(a,b) );
			 }
		}
	}

	public Card drawFromDeck()
	{
		Random generator = new Random();
		int index= generator.nextInt( cards.size() );
		Card temp = (Card) (cards.get(index));
		cards.remove(index);
		return temp;
	}
}



We can just add a card to the end of the vector with add(), as opposed to making our own variable to keep track of what index we're on. cards.size() automatically keeps track of how many cards are left in the array. When we return a card, we can actually remove it from the vector, thus decrementing the size, leaving us with a vector that only has cards left in it. (as opposed to just replacing its spot with the last card in the array, and setting the last spot to null)

Run the test code again to make sure everything still works.



One of the goals of OO programming is to emulate the real world. When we return a card from our deck, we remove it from the deck so it's not used again, just like a real dealer would. But when a dealer gives you a card, they just take it off the top of the deck, they don't fish through the deck to get a random card. They shuffle the deck, randomizing the cards before hand, so they can just pop cards off the top later and know that they're random. This leads us to another example:

Vector 2: shuffling the cards before we deal them

package javacards;

import java.util.Random;
import java.util.Vector;

public class Deck {
	private Vector cards;

	 Deck()
	{
		cards= new Vector();
		int index_1, index_2;
		Random generator = new Random();
		Card temp;

		for (short a=1; a<=4; a++)
		{
			for (short b=1; b<=13; b++)
			 {
			   cards.add( new Card(a,b) );
			 }
		}


		for (short i=0; i<100; i++)
		{
			index_1 = generator.nextInt( cards.size() - 1 );
			index_2 = generator.nextInt( cards.size() - 1 );

			temp = (Card) cards.get( index_1 );
			cards.set( index_1 , cards.get( index_2 ) );
			cards.set( index_2, temp );
		}
	}

	public Card drawFromDeck()
	{
		Card temp = (Card) cards.lastElement();
		cards.remove( cards.size()-1 );
		return temp;
	}

	public int getTotalCards()
	{
		return cards.size();
	}
} 



We put the cards in the Vector, then randomly take 100 pairs of cards and switch them, shuffling our deck. To draw from the deck, we just return the last element/card, and then remove that card from the deck. All the randomization is done before hand in the constructor, making our drawFromDeck method much simpler and less processor intensive. Piece of cake! :D

I hoped you've enjoyed and learned something from this tutorial!

Please post any and all questions, comments, concerns, or commentary!

:D :D :D :D :D :D :D :D :D :D :D :D :D :D :D :D :D :D :D :D :D :D :D :D :D

Is This A Good Question/Topic? 0
  • +

Replies To: Please help review my Card game tutorial,

#2 computerfox  Icon User is offline

  • straight vegetarian kid

Reputation: 50
  • View blog
  • Posts: 3,772
  • Joined: 29-January 09

Re: Please help review my Card game tutorial,

Posted 10 June 2009 - 08:21 AM

lots of information!! you might want to double check if there's any other information you can add in that you forgot. i also like how you added plenty of code snippets as visual add of what exactly is going on. overall looks pretty good, not great, but not bad.
Was This Post Helpful? 0
  • +
  • -

#3 baavgai  Icon User is offline

  • Dreaming Coder
  • member icon


Reputation: 6996
  • View blog
  • Posts: 14,635
  • Joined: 16-October 07

Re: Please help review my Card game tutorial,

Posted 10 June 2009 - 09:29 AM

Please don't teach folks Vectors. Can you even find those in Java docs written in the last five years?

Look at the generics. Using List<Card> cards would make more sense. Though it's unclear why you'd want to, if your card size is basically fixed.

Also, an enum for the suit and rank would also be helpful. You could just make them final, while you're at it. Why one earth are you not zero indexing them, in any case.

Good write up, though. I like seeing the incremental evolution of the code you're working on. I'm actually working on a tutorial ( different topic ) now where I'm trying to do the same thing. ( I'll try to get it up soon, so you can critique it. ;) )
Was This Post Helpful? 1
  • +
  • -

#4 pbl  Icon User is offline

  • There is nothing you can't do with a JTable
  • member icon

Reputation: 8378
  • View blog
  • Posts: 31,956
  • Joined: 06-March 08

Re: Please help review my Card game tutorial,

Posted 10 June 2009 - 05:33 PM

Your competing with mine ? :D :^:

if (card1.rank == "three" && card2.rank == "four") 


you should never use the == operator to compare String especially if your code is in another package
use the String.equals() method

If you want to make your stuff public no needs for the Array and just the Vector implementation just use the Vector one it is a lot better

99% of modern computers perform better with int than short (the bus is 32 bits so when you use a short the 32 bits are read/write and a mask with 0xFFFF is required) better to loose the habit of using short.

A String[] array of all your colour and card values would be a lot more efficient than your 2 switch statements
as your are the one controlling the variables no need to double check (belt, suspenders, brush, tuck tape)

This post has been edited by pbl: 10 June 2009 - 05:35 PM

Was This Post Helpful? 0
  • +
  • -

#5 crazyjugglerdrummer  Icon User is offline

  • GAME OVER. NERD WINS.
  • member icon

Reputation: 124
  • View blog
  • Posts: 690
  • Joined: 07-January 09

Re: Please help review my Card game tutorial,

Posted 14 June 2009 - 06:14 PM

Thanks so much guys! I've implemented your solutions, and the tutorial is much better for it. Here's the final version, I've made very few changes, all you really need to glance over is the code. If everything's okay I'll submit it. Thanks again for your help! :D

[start of tutorial]

When most of use began programming, we started looking for various project ideas to try. I've noticed card games come up a lot, so let's have a look at some ways to implement a card game. We will be focusing on the core of many card games, the cards and the deck.

The Card class is fairly simple, although it can be done a number of ways. I've chosen to use numeric variables for the internal data about the suit and rank, which will get converted to strings via predefined arrays when they need to be outputed. This lets us easily compare the suits and ranks (see if they're equal or sequential), without trying to use strings to represent them internally. If we used strings, we'd have to use

 if ( card1.rank.equals("three") && card2.rank.equals("four") ) 


with a whole bunch of other checks for different numbers, as opposed to

 if ( card1.rank + 1 == card2.rank) 


So, we end up with something like

package javacards;

public class Card
{
	private int rank, suit;

	private static String[] suits = { "hearts", "spades", "diamonds", "clubs" };
	private static String[] ranks  = { "Ace", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King" };


	Card(int suit, int rank)
	{
		this.rank=rank;
		this.suit=suit;
	}

	public @Override String toString()
	{
		  return ranks[rank] + " of " + suits[suit];
	}

	public int getRank() {
		 return rank;
	}

	public int getSuit() {
		return suit;
	}

}



So we have read-only suit and rank variables, a simple constructor, and a toString method. The class will be ultra fast as it knows which strings to output just by accessing indexes of static arrays. We don't even have to use String.ParseInt().

The Card class really isn't that complicated, so most of our time will be spent on the Deck. Our Deck class will have to make and dole out Cards. To hold our Cards, we can use a number of different storage methods/structures. I will show examples using an array and an ArrayList. Each storage method will have a constructor to make the cards, a drawFromDeck() method that will return a random card, and a getTotalCards() method that will return the number of cards left in the Deck. The hard part is, once we return a card we have to make sure it isn't used again.


Using Array:
package javacards;

import java.util.Random;

public class Deck {
	private Card[] cards;
	int i;

	Deck()
	{
		i=51;
		cards = new Card[52];
		int x=0;
		for (int a=1; a<=4; a++)
		{
			for (int b=1; b<=13; b++)
			 {
			   cards[x] = new Card(a,b);
			   x++;
			 }
		}
	}

	public Card drawFromDeck()
	{
		Random generator = new Random();
		int index=0;

		do {
			index = generator.nextInt( 52 );
		} while (cards[index] == null);

		i--;
		Card temp = cards[index];
		cards[index]= null;
		return temp;
	}

	public int getTotalCards()
	{
		return i;
	}
} 
[code]

First we have a constructor which fills our deck will the cards of various suits and ranks. Every time we add a card at x we increment x to put us at a fresh spot.

drawFromDeck() returns a random card from our container. We need to get a random index and return that card. We have a post-test loop that finds an array index  (less than 52) that isn't null. When we find one, we return its card and set it to null. Our deck might need to give feedback on how many cards are left, (maybe to determine if there are enough for another round of black jack or if we need to add all the cards back), so we make a variable i that represents the number cards left in the deck. i starts at 51 and is decremented everytime we take a card out. So if i is -1, then we know we're out of cards.


Now we need to write a test for this program. We want to make sure the cards that get drawn really are drawn in a random order. So we have main:

[code]
package javacards;

public class Main {

	public static void main(String[] args)
	{
		Deck deck = new Deck();
		Card C;

		System.out.println( deck.getTotalCards() );

	   while (deck.getTotalCards()!= 0 )
	   {
		   C = deck.drawFromDeck();
		   System.out.println( C.toString() );
	   }
	}



When we run this, we get a good look at how the cards would come off the deck. They seem random enough, so we're okay. However, this tutorial would be pretty int if I just ended it here, and many of you are probably looking at the drawFromDeck() method with disdain at its ineffeciency, so we press on......



This is really a pretty sloppy way of doing things, as we just keep trying to find an index in the array with a card in it. If the index doesn't have a card in it, we look in a new index, unti we finally get one. We really should only have to use one index, and we can do that if we make sure that we're only looking through places that actually have cards in them.

So to fix this, we use our variable i that represents the number of cards in the array that aren't used up yet. Now we need to make sure our selection comes only from that set of cards. Say the first card we take out is at index 4. We set that to null and decrement the number of cards. But index 4 is still null. We know we have 51 cards left, but we don't know where they are in the deck. There could be empty/null indexes all over the place, at 4, 7, 20, and all we can do is just keep our fingers crossed that we don't land on one of them. How about we organize our empty card spots a little better? Maybe we can switch the chosen card's empty spot with the last card in the deck? Let's put our i variable to work.


array example number 2:

package javacards;

import java.util.Random;

public class Deck {
	private Card[] cards;
	int i;

	Deck()
	{
		i=51;
		cards = new Card[52];
		int x=0;
		for (int a=1; a<=4; a++)
		{
			for (int b=1; b<=13; b++)
			 {
			   cards[x] = new Card(a,b);
			   x++;
			 }
		}
	}

	public Card drawFromDeck()
	{
		Random generator = new Random();
		int index=0; 

		index = generator.nextInt( i );

		Card temp = cards[index];
		cards[index]=cards[i];
		cards[i]=null;
		i--;
		return temp;
	}
} 



Our random index now is some number within the boundaries of the cards left, instead of always 52. If we have 40 cards left, index won't be 48. We get a random card, SWITCH IT'S NOW NULL LOCATION WITH THE LAST CARD IN THE DECK, and then return the random card. we picked the fourth card, and put the last card in its place. The null value we get when we take out the card at index 4 is now located at spot 51, the last spot in the deck, as opposed to some random spot in the middle of the deck. This ensures that the indexes up through i always have cards in them, and that the null values are always moved to the end of the array. When we get a random index between 0 and i, we can be 100% sure that there is a fresh card in that spot.

To test this method, we can use the exact same test code as we did for the first example, as all that's changed is the internal workings of the deck class. In fact, we will use that test code for all our Deck implementations.



We've implemented a lot of features with arrays that could've easily been done with generic, like an ArrayList (adding a card to the end of the array, removing a card by setting its place to null, keeping track of the size of the array, etc. ). An ArrayList has most of the features we need built in already, so we don't have to write our own code to do them.

ArrayList example:

package javacards;

import java.util.Random;
import java.util.ArrayList;

public class Deck {
	private ArrayList<Card> cards;

	 Deck()
	{
		cards = new ArrayList<Card>();
		for (int a=1; a<=4; a++)
		{
			for (int b=1; b<=13; b++)
			 {
			   cards.add( new Card(a,b) );
			 }
		}
	}

	public Card drawFromDeck()
	{
		Random generator = new Random();
		int index= generator.nextInt( cards.size() );
		return cards.remove(index);
	}

	 public int getTotalCards()
	{
		return cards.size();
	}
}



We can just add a card to the end of the ArrayList with add(), as opposed to making our own variable to keep track of what index we're on. cards.size() automatically keeps track of how many cards are left in the array. When we return a card, we can actually remove it from the ArrayList, thus decrementing the size, leaving us with a collection that only has cards left in it. (as opposed to just replacing its spot with the last card in the array, and setting the last spot to null)

Run the test code again to make sure everything still works.



One of the goals of OO programming is to emulate the real world. When we return a card from our deck, we remove it from the deck so it's not used again, just like a real dealer would. But when a dealer gives you a card, they just take it off the top of the deck, they don't fish through the deck to get a random card. They shuffle the deck, randomizing the cards before hand, so they can just pop cards off the top later and know that they're random. This leads us to another example:

ArrayList 2: shuffling the cards before we deal them

package javacards;

import java.util.Random;
import java.util.ArrayList;

public class Deck {
	private ArrayList<Card> cards;

	 Deck()
	{
		cards = new ArrayList<Card>();
		int index_1, index_2;
		Random generator = new Random();
		Card temp;

		for (int a=1; a<=4; a++)
		{
			for (int b=1; b<=13; b++)
			 {
			   cards.add( new Card(a,b) );
			 }
		}


		for (int i=0; i<100; i++)
		{
			index_1 = generator.nextInt( cards.size() - 1 );
			index_2 = generator.nextInt( cards.size() - 1 );

			temp = (Card) cards.get( index_2 );
			cards.set( index_2 , cards.get( index_1 ) );
			cards.set( index_1, temp );
		}
	}

	public Card drawFromDeck()
	{	   
		return cards.remove( 0 );
	}

	public int getTotalCards()
	{
		return cards.size();
	}
}



We put the cards in the ArrayList, then randomly take 100 pairs of cards and switch them, shuffling our deck. To draw from the deck, we just return the last element/card, and then remove that card from the deck. All the randomization is done before hand in the constructor, making our drawFromDeck method much simpler and less processor intensive. Piece of cake! :D

I hoped you've enjoyed and learned something from this tutorial!

Please post any and all questions, comments, concerns, or commentary!

:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D:D
Was This Post Helpful? 0
  • +
  • -

#6 pbl  Icon User is offline

  • There is nothing you can't do with a JTable
  • member icon

Reputation: 8378
  • View blog
  • Posts: 31,956
  • Joined: 06-March 08

Re: Please help review my Card game tutorial,

Posted 14 June 2009 - 07:19 PM

Still think your

"If this post was usefull stuff..." is kind of agressive by its size but this is your problem :D
Was This Post Helpful? 0
  • +
  • -

#7 crazyjugglerdrummer  Icon User is offline

  • GAME OVER. NERD WINS.
  • member icon

Reputation: 124
  • View blog
  • Posts: 690
  • Joined: 07-January 09

Re: Please help review my Card game tutorial,

Posted 18 June 2009 - 02:08 PM

@pbl yeah, I can probably make it a size smaller. I don't mean to be agressive, just to tell users how to "say thanks" (although I will not even try to deny the reason for it was to get a few more thanks) :D

Thanks for all your help guys! The tutorial is live here and it is much better for all of your contributions!
Was This Post Helpful? 0
  • +
  • -

#8 Fuzzyness  Icon User is offline

  • Comp Sci Student
  • member icon

Reputation: 669
  • View blog
  • Posts: 2,438
  • Joined: 06-March 09

Re: Please help review my Card game tutorial,

Posted 18 June 2009 - 02:15 PM

Just a thought, in your next tutorial you make if you make anotehr one, add comments in the code. If it is a tutorial chances are these concepts are unkown/fairly new to some of the programmers so just seeing the code may not be working too well; so add comment lines saying this is the array to hold the cards ect. It will kind of break down the code a little more. Goodjob on the tutorial though :)
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1