3
\$\begingroup\$

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.

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

This is a bit large to offer a detailed review, but there are some things which stand out.

Whitespace

The indentation seems rather variable, and there are several places with multiple blank lines which don't appear to be to structure the code. The whitespace should help to show the structure: use consistent indentation and use blank lines consistently too.

Pointless code

For example, from MainActivity:

   try {

       setContentView(R.layout.activity_main);
      MySurfaceView surfaceView;
      surfaceView = (MySurfaceView) findViewById((R.id.surfaceView));


   }
   catch (Exception e)
   {
       e.printStackTrace();
   }

(This also illustrates the previous points about whitespace).

What purpose does surfaceView serve?

Comments contradicting code

@Override
public boolean onDown(MotionEvent e) {

  // don't return false here or else none of the other
  // gestures will work

    return  false;
}

God class anti-pattern

MySurfaceView seems to do almost everything. I'm sure it could be refactored at least into one class for the game state and another class for the visualisation of the game state - the Model/View style. It's probably also worth pulling out one or two classes for two-dimensional Cartesian coordinates: maybe Size and Point. I haven't analysed in detail the way in which the coordinates are used, but it is quite noticeable that the fields of MySurfaceView include a number of decomposed 2D vectors.

DRY

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);
}

There are two ways to make this less redundant. The first is to move

    this.context = context;
    metrics = getResources().getDisplayMetrics();
    setWillNotDraw(false);
    gestureDetector = new GestureDetector(context, new GestureListener(this));

into init(context). The second, which is generally preferred, is to chain the constructors:

public MySurfaceView(Context context) {
    this(context, null);
}

public MySurfaceView(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
}

Note that this is exactly what the superclass does (lines 198ff).

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.