I am developing a card game in Android. This is my first time I am working in Android,. The game contains following features:
- It will be a 6 player game.
- There are two decks on the table, one with hidden Cards(Dealt Deck), and other with shown cards(Discarded Deck).
- There are multiple rounds in each set, and multiple set in each match.
- In each round a new card will be added to user deck
- User can either remove a single card or multiple cards if they belong to a particular category like "Three of a kind".
- User then have to pick a single card either from Discarded Deck or from Dealt Deck.
- Each card has it's own value, in the end of the match user with lowest no. of scores will win the match.
For this I have created the following classes:
MainActivity
class: Launcher class of the game
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
try {
setContentView(R.layout.activity_main);
MySurfaceView surfaceView;
surfaceView = (MySurfaceView) findViewById((R.id.surfaceView));
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
MySurfaceView
class: Most of important class, this class will handle all the operations of the game, showing cards on UI, handling of touch event, swapping of touch cards etc.
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback {
private static final String TAG = MySurfaceView.class.getSimpleName(); // To get name of class in Logging
private Context context;
private DisplayMetrics metrics;
private MySurfaceViewThread thread;
private int Screen_Width;
private int Screen_Height;
private float density;
private int Card_Width;
private int Card_Height;
private int Screen_Center_X;
private int Screen_Center_Y;
private int Screen_Bottom_Middle_X;
private int Screen_Bottom_Middle_Y;
private Deck MainPlayer;
private Deck DeatlDeck;
private Deck DiscardedDeck;
private Bitmap BlueBackCard;
private int DealtDeck_CurrentX;
private int DealtDeck_CurrentY;
private int DiscardedDeck_CurrentX;
private int DiscardedDeck_CurrentY;
private boolean isLongTouched=false;
final Handler longpressedhandler= new Handler();
private Card touchedcard=null;
private int cardindex=-1;
private Card replacedcard=null;
private GestureDetector gestureDetector;
private long startclicktime;
private final int MIN_CLICK_DURATION=1000;
private ArrayList<Card> tempLongtouchList= new ArrayList<>();
private ArrayList<Integer> tempListindex= new ArrayList<>();
public MySurfaceView(Context context) {
super(context);
this.context = context;
metrics = getResources().getDisplayMetrics();
setWillNotDraw(false);
gestureDetector = new GestureDetector(context, new GestureListener(this));
init(context);
}
public MySurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
metrics = getResources().getDisplayMetrics();
setWillNotDraw(false);
gestureDetector = new GestureDetector(context, new GestureListener(this));
init(context);
}
public MySurfaceView(Context context, AttributeSet attrs, int defstyles) {
super(context, attrs, defstyles);
this.context = context;
metrics = getResources().getDisplayMetrics();
setWillNotDraw(false);
gestureDetector = new GestureDetector(context, new GestureListener(this));
init(context);
}
private void init(Context context) {
this.context = context;
getHolder().addCallback(this);
thread = new MySurfaceViewThread(getHolder(), this);
setFocusable(true);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
Log.d(TAG, "Inside Surface Created method");
initializevariable();
AllocatedCardList();
thread.setRunning(true);
thread.start();
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
thread.setRunning(false);
while (retry) {
try {
thread.join();
retry = false;
} catch (InterruptedException e) {
}
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
private void initializevariable() {
Screen_Width = getWidth();
Screen_Height = getHeight();
density = metrics.density;
Card_Width = (int) (125.0F * density);
Card_Height = (int) (93.0F * density);
Screen_Center_X = Screen_Width / 2;
Screen_Center_Y = Screen_Height / 2;
Screen_Bottom_Middle_X = Screen_Center_X - Card_Width;
Screen_Bottom_Middle_Y = Screen_Height - Card_Height;
BlueBackCard = DecodeSampleBitmapFromResource(getResources(), Card.GetBlueBackCardImageId(context), Card_Width, Card_Height);
MainPlayer = new Deck();
DeatlDeck = new Deck();
DiscardedDeck = new Deck();
DealtDeck_CurrentX = Screen_Center_X - Card_Width;
DealtDeck_CurrentY = Screen_Center_Y - Card_Height / 2;
DiscardedDeck_CurrentX= Screen_Center_X+Card_Width;
DiscardedDeck_CurrentY= Screen_Center_Y- Card_Height/2;
}
private void AllocatedCardList() {
Log.d(TAG, "inside AllocatedCardList method");
for (Suit suit : Suit.values()) {
for (Rank rank : Rank.values()) {
DeatlDeck.add(new Card(rank, suit, false, DealtDeck_CurrentX, DealtDeck_CurrentY , BlueBackCard));
}
}
DealCards();
}
private void DealCards() {
Log.d(TAG, "Inside Deal Card method");
MainPlayer.add(DeatlDeck.Deal(true));
MainPlayer.add(DeatlDeck.Deal(true));
// MainPlayer.add(DeatlDeck.Deal(true));
}
// @SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(TAG,"Inside Touch Event");
float lasttouched_X, lasttouched_Y;
Card localcard;
int index=-1;
Log.d(TAG,"Inside OnTouch event");
gestureDetector.onTouchEvent(event);
return true;
}
/**
* Method to swap Main Player card with
* either Dealt Deck or with Discarded
* Deck, this method will check whether
* user has touched Main Player deck,
* Dealt Deck or Discarded Deck, if
* user has touched Main Deck using
* long touch it will add those touch
* cards to temporary deck, till
* user does not touch Discarde/
* Dealt Deck
* @param e: to determine which type of event has performed.
*/
public void swapSingleTouchCard(MotionEvent e) {
float lasttouched_X, lasttouched_Y;
Card localcard;
int index = -1;
lasttouched_X = e.getX();
lasttouched_Y = e.getY();
// Code for long touch and single touch swap
if(((lasttouched_X >= DiscardedDeck_CurrentX && lasttouched_X < (DiscardedDeck_CurrentX + DiscardedDeck.getCard().getImage().getWidth())))==false &&isLongTouched) // Main Player Deck, card is touched
{
addTouchedCardToLongTouched(e);
}
else if (isLongTouched==false && touchedcard==null)
{
index = cardTouched((int) lasttouched_X, (int) lasttouched_Y);
if (index > -1) {
touchedcard = MainPlayer.getCard(index);
cardindex = index;
}
}
else if(lasttouched_X >= DiscardedDeck_CurrentX && lasttouched_X < (DiscardedDeck_CurrentX + DiscardedDeck.getCard().getImage().getWidth())) //if touched card is Discard deck
{
if(touchedcard!=null) // to replace with single card
{
replacedcard = DiscardedDeck.Deal(true);
Card swapcard = MainPlayer.swapCard(replacedcard, cardindex);
swapcard.setCurrent_X(DiscardedDeck_CurrentX);
swapcard.setCurrent_Y(DiscardedDeck_CurrentY);
DiscardedDeck.add(swapcard);
touchedcard = null;
cardindex = -1;
}
else if(isLongTouched)
{
int i=tempListindex.size()-1;
Card Discarddeckcard;
Discarddeckcard=DiscardedDeck.Deal(true);
while(i>=0)
{
MainPlayer.removeCard(tempListindex.get(i));
Card removecard= tempLongtouchList.remove(i);
removecard.setCurrent_X(DiscardedDeck_CurrentX);
removecard.setCurrent_Y(DiscardedDeck_CurrentY);
DiscardedDeck.add(removecard);
i--;
}
MainPlayer.add(Discarddeckcard);
isLongTouched=false;
}
}
}
private int cardTouched(int lasttouched_x, int lasttouched_y) {
int index=0;
Card localcard=null;
while (index<MainPlayer.Count())
{
localcard=MainPlayer.getCard(index);
if(lasttouched_x>= localcard.getCurrent_X() && lasttouched_x<(localcard.getCurrent_X()+localcard.getImage().getWidth())) //&& (lasttouched_y>=localcard.getCurrent_Y() &&lasttouched_y <(localcard.getCurrent_Y()+localcard.getImage().getWidth())))
{
return index;
}
index++;
}
return -1;
}
public void addTouchedCardToLongTouched(MotionEvent event)
{
float lasttouched_X, lasttouched_Y;
int index=-1;
lasttouched_X=event.getX();
lasttouched_Y=event.getY();
index=cardTouched((int)lasttouched_X,(int)lasttouched_Y);
isLongTouched=true;
if(index>-1)
{
tempLongtouchList.add(MainPlayer.getCard(index));
tempListindex.add(index);
}
}
public void render(Canvas canvas)
{
canvas.drawColor(Color.TRANSPARENT,PorterDuff.Mode.CLEAR);
drawDealtDeck(canvas);
if(DiscardedDeck.Count()==0) { //To add card in discarded deck only first time
setDiscardedDeck();
}
drawDiscardedDeck(canvas);
setMainPlayer();
DrawMainPlayerDeck(canvas);
}
private void drawDealtDeck (Canvas canvas){
Card localcard = DeatlDeck.getCard();
canvas.drawBitmap(localcard.getImage(), localcard.getCurrent_X(), localcard.getCurrent_Y(), null);
}
private void drawDiscardedDeck(Canvas canvas) {
Log.d(TAG,"Inside Draw Discarded deck");
Card localcard= DiscardedDeck.getCard();
canvas.drawBitmap(localcard.getImage(),localcard.getCurrent_X(),localcard.getCurrent_Y(),null);
}
private void setDiscardedDeck() {
Log.d(TAG,"Inside set Discarded Deck");
Card localcard;
Bitmap localimage;
localcard=DeatlDeck.Deal(true);
localimage= DecodeSampleBitmapFromResource(getResources(),localcard.GetImageId(context),Card_Width,Card_Height);
localcard.setImage(localimage);
localcard.setCurrent_X(DiscardedDeck_CurrentX);
localcard.setCurrent_Y(DiscardedDeck_CurrentY);
DiscardedDeck.add(localcard);
}
private void setMainPlayer ()
{
Log.d(TAG, "Inside Set Main Player Method");
Card localcard = null;
Bitmap localimage = null;
int currentiteration = 0;
int Down_Card_Gap = 0;
int Down_Card_Gap_positive = 0;
int Down_Card_Gap_negative = 0;
while (currentiteration < MainPlayer.Count()) {
localcard = MainPlayer.getCard(currentiteration);
localimage = DecodeSampleBitmapFromResource(getResources(), localcard.GetImageId(context), Card_Width, Card_Height);
localcard.setImage(localimage);
localcard.setCurrent_Y(Screen_Height - localcard.getImage().getHeight());
MainPlayer.setCurrentCard(localcard, currentiteration);
currentiteration++;
if (Down_Card_Gap >= 0) {
Down_Card_Gap_positive = Down_Card_Gap;
localcard.setCurrent_X(Screen_Center_X + Down_Card_Gap_positive);
Down_Card_Gap += 75;
} else {
Down_Card_Gap_negative = Down_Card_Gap;
localcard.setCurrent_X(Screen_Center_X + Down_Card_Gap_negative);
}
Down_Card_Gap *= -1;
}
}
private void DrawMainPlayerDeck (Canvas canvas)
{
Log.d(TAG, " Inside Draw Main Player Deck");
Card localcard;
int currentiteration = 0;
while (currentiteration < MainPlayer.Count()) {
localcard = MainPlayer.getCard(currentiteration);
canvas.drawBitmap(localcard.getImage(), localcard.getCurrent_X(), localcard.getCurrent_X(), null);
currentiteration++;
}
}
private Bitmap DecodeSampleBitmapFromResource (Resources res,int resId,
int reqWidth, int reqHeight){
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
private int calculateInSampleSize (BitmapFactory.Options options,int reqWidth, int reqHeight)
{
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
int heightratio= (int)Math.round((double)height/reqHeight);
int widthratio= (int)Math.round((double)width/reqWidth);
inSampleSize= heightratio < widthratio ? widthratio : heightratio;
}
return inSampleSize;
}
}
/**
* Class to implements Touch events
*/
class GestureListener extends GestureDetector.SimpleOnGestureListener{
private static final String TAG = GestureListener.class.getSimpleName(); //
To get name of class in Logging
MySurfaceView mySurfaceView;
public GestureListener(MySurfaceView paramMySurfaceView)
{
mySurfaceView=paramMySurfaceView;
}
@Override
public void onLongPress(MotionEvent e) {
Log.d(TAG,"Inside Long Pressed event");
mySurfaceView.addTouchedCardToLongTouched(e);
}
@Override
public boolean onDown(MotionEvent e) {
// don't return false here or else none of the other
// gestures will work
return false;
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
mySurfaceView.swapSingleTouchCard(e);
return false;
}
@Override
public boolean onDoubleTap(MotionEvent e) {
Log.d(TAG,"Inside On Double Tap event");
return false;
}
}
MySurfaceViewThread
: Thread class to start the game
public class MySurfaceViewThread extends Thread {
private MySurfaceView mySurfaceView;
private SurfaceHolder mySurfaceHolder;
boolean running;
public MySurfaceViewThread(SurfaceHolder paramSurfaceHolder, MySurfaceView paramSurfaceView)
{
mySurfaceHolder=paramSurfaceHolder;
mySurfaceView=paramSurfaceView;
}
public void setRunning(boolean run){
running=run;
}
@SuppressLint("WrongCall")
@Override
public void run() {
Canvas c;
while(running)
{
c=null;
try{
c= mySurfaceHolder.lockCanvas();
mySurfaceView.render(c);
}
catch(Exception e)
{
Log.e("Thread Class run method","exception",e);
}
finally {
if (c!=null)
{
mySurfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
}
Other classes:
Card
public class Card {
private int current_X;
private int current_Y;
private boolean showcardface;
private Bitmap Image;
private Rank CardValue;
private Suit suit;
Card(Rank value, Suit suit,boolean showcardface, int current_X,int current_Y, Bitmap image)
{
this.CardValue=value;
this.suit=suit;
this.showcardface=showcardface;
this.current_X=current_X;
this.current_Y=current_Y;
this.Image= image;
}
public int GetImageId(Context currentcontext)
{
int cardimageid;
int imagenumber;
String imagename;
if(showcardface==false)
{
imagename="blueback";
}
else
{
imagename= suit.getName()+ CardValue.getImageName();
}
cardimageid= currentcontext.getResources().getIdentifier(imagename,"drawable",currentcontext.getPackageName());
return cardimageid;
}
public static int GetBlueBackCardImageId(Context currentcontext){
return currentcontext.getResources().getIdentifier("blueback","drawable",currentcontext.getPackageName());
}
public Bitmap getImage() {
return Image;
}
public int getCurrent_X() {
return current_X;
}
public int getCurrent_Y() {
return current_Y;
}
public boolean getShowCardFaced()
{
return showcardface;
}
public void setShowcardface(boolean showcardface) {
this.showcardface = showcardface;
}
public void setImage(Bitmap image)
{
this.Image=image;
}
public void setCurrent_X(int current_X) {
this.current_X = current_X;
}
public void setCurrent_Y(int current_Y) {
this.current_Y = current_Y;
}
}
Deck
class
public class Deck {
private ArrayList<Card> deck= new ArrayList<>() ;
public Deck()
{
}
/**
* Method to add card in the deck
* @param card
*/
public void add(Card card) {
deck.add(card);
}
/**
* Method to get the top element of the deck
* @return The Top element of the deck
*/
public Card getCard()
{
return deck.get(deck.size()-1);
}
/**
*
* @param index
* @return Element at index value
*/
public Card getCard(int index)
{
return deck.get(index);
}
/**
* Method to get the size of the deck
* @return Deck size
*/
public int Count()
{
return deck.size();
}
/**
* Method to remove a single card from deck
* @return Removed card from the deck.
*/
public Card Deal()
{
int cardcount= Count();
Card dealcards= deck.get(cardcount-1);
deck.remove(cardcount-1);
dealcards.setShowcardface(true);
return dealcards;
}
/**
* Method to remove a single card from deck
* @return Removed card from the deck.
*/
public Card Deal(boolean showcardface)
{
int cardcount= Count();
Card dealcards= deck.get(cardcount-1);
deck.remove(cardcount-1);
dealcards.setShowcardface(showcardface);
return dealcards;
}
public void setCurrentCardImage(Bitmap image, int index)
{
getCard(index).setImage(image);
}
public void setCurrentCard(Card card, int index)
{
deck.set(index,card);
}
/**
* Method to swap between touch cards of the two decks
* @param replacedcard Card to be added {@index} of the deck
* @param index
* @return Remove the card from @param Index
*/
public Card swapCard(Card replacedcard, int index) {
Card removedcard= deck.remove(index);
deck.add(index,replacedcard);
return removedcard;
}
public void removeCard(int index)
{
Card removecard=deck.remove(index);
}
}
Rank
enum
public enum Rank {
Ace(1,"ace","ace"),
Two(2,"two","2"),
Three(3,"three","3"),
Four(4,"four","4"),
Five(5,"five","5"),
Six(6,"six","6"),
Seven(7,"seven","7"),
Eight(8,"eight","8"),
Nine(9,"nine","9"),
Ten(10,"ten","10"),
Jack(11,"jack","jack"),
Queen(12,"queen","queen"),
King(13,"king","king");
private final int rank;
private final String name;
private final String imageName;
Rank(int rank,String name, String imageName){
this.rank=rank;
this.name=name;
this.imageName=imageName;
}
public String getImageName()
{return imageName;
}
}
Suit enum
public enum Suit {
Clubs(1,"clubs"),
Spades(2,"spades"),
Hearts(3,"hearts"),
Diamond(4,"diamonds");
private final int value;
private final String name;
Suit(int value,String name)
{
this.value=value;
this.name=name;
}
public String getName()
{
return name;
}
}
Currently, I have completed it for main player only, and availability to swap only with a discarded deck. Since it is my first time, before going any forward I really like to know whether I am following right approach. All suggestions are welcome, especially regarding any approach that I could simplify. If you need help regarding any part of program, let me know I will explain it.