android 21点游戏代码,Android 21点

0x0000 写在前面

在写这个游戏之前,只是模糊地记得文曲星上的21点游戏规则

想当然地认为就是一个先后发牌然后开牌比大小的游戏

结果百度一下,发现玩法比我想的复杂不少

这个demo里删掉了分牌的功能,并且只支持单人游戏

转载请注明

作者和平北路

原文点击链接

0x0001 功能简介

庄家发牌

玩家发牌

玩家选择发牌/停牌/放弃

比较大小

0x0002 工程结构

94cd462b92d2

UML类图

Card是最基本的卡牌对象,包含花色(Suit)和大小(Rank)两个属性

Deck是去掉了大小王的一副牌

CardImage是对一张牌的封装,包含了卡牌的内容(Card)和与之绑定的一个视图对象(ImageView),用于在屏幕上进行旋转、移动等动作

BitmapUtils是一个工具类,用于从一张包含52张卡牌图案的图片上裁剪对应Card的图片

ScreenUtils是一个工具了,用于获取屏幕宽高(只适用于全屏)

Rotate3dAnimation是3D旋转动画,请自行百度Android官方源码

0x0003 源码分析

public class Card {

enum Suit {HEART, SPADE, DIAMOND, CLUB}

enum Rank {ACE, DEUCE, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING}

private Suit suit;

private Rank rank;

public Card(Suit suit, Rank rank) {

this.suit = suit;

this.rank = rank;

}

public Suit getSuit() {

return suit;

}

public Rank getRank() {

return rank;

}

@Override

public boolean equals(Object obj) {

if (this == obj) {

return true;

}

if (obj == null || obj.getClass() != getClass()) {

return false;

}

Card card = (Card) obj;

if (card.suit != suit || card.rank != rank) {

return false;

}

return true;

}

@Override

public int hashCode() {

int result = suit.ordinal();

result = 31 * result + rank.ordinal();

return result;

}

}

public class Deck {

Collection suits = Arrays.asList(Card.Suit.values());

Collection ranks = Arrays.asList(Card.Rank.values());

private List deck = new ArrayList<>();

public Deck() {

init();

}

private void init() {

for (Iterator i = suits.iterator(); i.hasNext(); /*do nothing*/) {

Card.Suit suit = i.next();

for (Iterator j = ranks.iterator(); j.hasNext(); /*do nothing*/) {

deck.add(new Card(suit, j.next()));

}

}

}

public List getDeck() {

return deck;

}

}

public class CardImage {

private static final long DURATION = 500L;

private static final float DEPTH_Z = 0f;

private int translationX;

private int translationY;

private Card card;

private ImageView image;

private AnimatorSet animatorSet;

private IAnimationCallback callback;

public CardImage(Card card, ImageView image) {

this.card = card;

this.image = image;

translationX = 0;

translationY = 0;

}

public void setCallback(IAnimationCallback callback) {

this.callback = callback;

}

public void translate(final int x, final int y) {

if (image == null) {

return;

}

if (image.getLeft() == 0 || image.getTop() == 0) {

final ViewTreeObserver observer = image.getViewTreeObserver();

observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {

@Override

public void onGlobalLayout() {

translate(x, y);

observer.removeOnGlobalLayoutListener(this);

}

});

return;

}

if (null != animatorSet) {

animatorSet.end();

}

image.clearAnimation();

ObjectAnimator animX = ObjectAnimator.ofFloat(image, "translationX", x - image.getLeft());

ObjectAnimator animY = ObjectAnimator.ofFloat(image, "translationY", y - image.getTop());

translationX = x - image.getLeft();

translationY = y - image.getTop();

animatorSet = new AnimatorSet();

animatorSet.playTogether(animX, animY);

animatorSet.setDuration(DURATION);

animatorSet.start();

animatorSet.addListener(new Animator.AnimatorListener() {

@Override

public void onAnimationStart(Animator animation) {

}

@Override

public void onAnimationEnd(Animator animation) {

if (null != callback)

callback.onTranslationEnd();

}

@Override

public void onAnimationCancel(Animator animation) {

}

@Override

public void onAnimationRepeat(Animator animation) {

}

});

}

public void front() {

if (image == null) {

return;

}

if (null != animatorSet) {

animatorSet.end();

}

image.clearAnimation();

final int x = image.getLayoutParams().width / 2 + translationX;

final int y = image.getLayoutParams().height / 2 + translationY;

Animation firstHalf = rotate(0, 90, x, y);

firstHalf.setAnimationListener(new Animation.AnimationListener() {

@Override

public void onAnimationStart(Animation animation) {

}

@Override

public void onAnimationEnd(Animation animation) {

image.setImageBitmap(BitmapUtils.getCardImage(image.getContext(), card));

Animation secondHalf = rotate(270, 360, x, y);

secondHalf.setAnimationListener(new Animation.AnimationListener() {

@Override

public void onAnimationStart(Animation animation) {

}

@Override

public void onAnimationEnd(Animation animation) {

if (null != callback)

callback.onFrontEnd();

}

@Override

public void onAnimationRepeat(Animation animation) {

}

});

}

@Override

public void onAnimationRepeat(Animation animation) {

}

});

}

private Animation rotate(float startDegree, float endDegree, float centerX, float centerY) {

Rotate3dAnimation anim = new Rotate3dAnimation(

startDegree, endDegree, centerX, centerY, DEPTH_Z, false);

anim.setDuration(DURATION);

anim.setFillAfter(true);

image.startAnimation(anim);

return anim;

}

public Card getCard() {

return card;

}

public ImageView getImage() {

return image;

}

public interface IAnimationCallback {

void onFrontEnd();

void onTranslationEnd();

}

}

public class BitmapUtils {

public static Bitmap cards;

public static Bitmap getCardImage(Context context, Card card) {

if (null == card)

return null;

if (null == cards)

cards = BitmapFactory.decodeResource(context.getResources(), R.drawable.cards);

int rows = Card.Suit.values().length;

int cols = Card.Rank.values().length;

int width = cards.getWidth() / cols;

int height = cards.getHeight() / rows;

int x = width * card.getRank().ordinal();

int y = height * card.getSuit().ordinal();

return Bitmap.createBitmap(cards, x, y, width, height);

}

}

public class ScreenUtils {

private static int sScreenWidth;

private static int sScreenHeight;

public static void init(Context context) {

Resources resources = context.getResources();

DisplayMetrics dm = resources.getDisplayMetrics();

sScreenWidth = dm.widthPixels;

sScreenHeight = dm.heightPixels;

}

public static int getScreenWidth(Context context) {

if (sScreenWidth <= 0) {

init(context);

}

return sScreenWidth;

}

public static int getScreenHeight(Context context) {

if (sScreenHeight <= 0) {

init(context);

}

return sScreenHeight;

}

}

public class MainActivity extends Activity {

private static final int MSG_PLAYER_DEAL = 0x00;

private static final int MSG_PLAYER_HIT = 0x01;

private static final int MSG_PLAYER_STAND = 0x02;

private static final int MSG_PLAYER_FOLD = 0x03;

private static final int MSG_BANKER_DEAL = 0x04;

private static final int MSG_BANKER_DEAL_HIDE = 0x05;

private static final int MSG_BANKER_FRONT_HIDE = 0x06;

private static final int MSG_RESET = 0x07;

private static final int TEN = 10;

private static final int BLACK_JACK = 21;

private static final int CARD_WIDTH = 225;

private static final int CARD_HEIGHT = 315;

private static final int CARD_MARGIN = 10;

private static final int CARD_MARGIN_TOP = 20;

private static final int CARD_MARGIN_BOTTOM = 500;

private int screenWidth;

private int screenHeight;

private Random random;

private ViewGroup parent;

private View hit;

private View stand;

private View fold;

private List cards;

private List playerCards;

private List bankerCards;

private Handler msgHandle = new Handler() {

@Override

public void handleMessage(Message msg) {

int what = msg.what;

switch (what) {

case MSG_PLAYER_DEAL:

dealPlayer();

break;

case MSG_BANKER_DEAL:

dealBanker();

break;

case MSG_BANKER_DEAL_HIDE:

dealBankerHide();

break;

case MSG_BANKER_FRONT_HIDE:

frontBankerHide();

break;

case MSG_PLAYER_HIT:

hit();

break;

case MSG_PLAYER_STAND:

stand();

break;

case MSG_PLAYER_FOLD:

fold();

break;

case MSG_RESET:

reset();

break;

default:

break;

}

}

};

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

requestWindowFeature(Window.FEATURE_NO_TITLE);

getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,

WindowManager.LayoutParams.FLAG_FULLSCREEN);

ScreenUtils.init(getApplicationContext());

setContentView(R.layout.activity_main);

screenWidth = ScreenUtils.getScreenWidth(this);

screenHeight = ScreenUtils.getScreenHeight(this);

random = new Random();

cards = new Deck().getDeck();

playerCards = new ArrayList<>();

bankerCards = new ArrayList<>();

parent = (ViewGroup) findViewById(R.id.activity_main);

hit = findViewById(R.id.hit);

hit.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

hit();

}

});

stand = findViewById(R.id.stand);

stand.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

stand();

}

});

fold = findViewById(R.id.fold);

fold.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

fold();

}

});

deal();

}

private void deal() {

disableButtons();

msgHandle.sendMessageDelayed(Message.obtain(msgHandle, MSG_PLAYER_DEAL), 500);

msgHandle.sendMessageDelayed(Message.obtain(msgHandle, MSG_BANKER_DEAL), 2000);

msgHandle.sendMessageDelayed(Message.obtain(msgHandle, MSG_PLAYER_DEAL), 3500);

msgHandle.sendMessageDelayed(Message.obtain(msgHandle, MSG_BANKER_DEAL_HIDE), 5000);

}

private void dealPlayer() {

disableButtons();

CardImage player = new CardImage(

cards.remove(random.nextInt(cards.size())),

createCardImage());

player.setCallback(new CardImage.IAnimationCallback() {

@Override

public void onFrontEnd() {

updatePlayerCardsLocation();

}

@Override

public void onTranslationEnd() {

if (playerCards.size() >= 2) {

enableButtons();

}

if (playerCards.size() > 2) {

check();

}

}

});

player.front();

playerCards.add(player);

}

private void dealBanker() {

disableButtons();

CardImage banker = new CardImage(

cards.remove(random.nextInt(cards.size())),

createCardImage());

banker.setCallback(new CardImage.IAnimationCallback() {

@Override

public void onFrontEnd() {

updateBankerCardsLocation();

}

@Override

public void onTranslationEnd() {

if (bankerCards.size() > 2) {

disableButtons();

if (!check()) {

if (count(bankerCards) > count(playerCards)) {

lose();

} else {

msgHandle.sendMessageDelayed(Message.obtain(msgHandle, MSG_BANKER_DEAL), 2000);

}

}

}

}

});

banker.front();

bankerCards.add(banker);

}

private void dealBankerHide() {

disableButtons();

CardImage banker = new CardImage(

cards.remove(random.nextInt(cards.size())),

createCardImage());

banker.setCallback(new CardImage.IAnimationCallback() {

@Override

public void onFrontEnd() {

}

@Override

public void onTranslationEnd() {

check();

}

});

bankerCards.add(banker);

updateBankerCardsLocation();

}

private void frontBankerHide() {

disableButtons();

CardImage banker = bankerCards.get(1);

banker.front();

}

private ImageView createCardImage() {

ImageView card = new ImageView(this);

card.setImageResource(R.drawable.card_back);

card.setScaleType(ImageView.ScaleType.CENTER_CROP);

RelativeLayout.LayoutParams layoutParams =

new RelativeLayout.LayoutParams(CARD_WIDTH, CARD_HEIGHT);

layoutParams.addRule(RelativeLayout.CENTER_VERTICAL);

layoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);

layoutParams.rightMargin = 40;

parent.addView(card, layoutParams);

return card;

}

private int[] getPlayerCardLocation(int num) {

int centerX = screenWidth / 2;

int size = playerCards.size();

int x = centerX

- ((size - 1) * CARD_MARGIN + size * CARD_WIDTH) / 2

+ num * (CARD_MARGIN + CARD_WIDTH);

int y = screenHeight - CARD_MARGIN_BOTTOM;

return new int[]{x, y};

}

private void updatePlayerCardsLocation() {

int size = playerCards.size();

for (int i = 0; i < size; i++) {

int[] location = getPlayerCardLocation(i);

CardImage cardImage = playerCards.get(i);

cardImage.translate(location[0], location[1]);

}

}

private int[] getBankerCardLocation(int num) {

int centerX = screenWidth / 2;

int size = bankerCards.size();

int x = centerX

- ((size - 1) * CARD_MARGIN + size * CARD_WIDTH) / 2

+ num * (CARD_MARGIN + CARD_WIDTH);

int y = CARD_MARGIN_TOP;

return new int[]{x, y};

}

private void updateBankerCardsLocation() {

int size = bankerCards.size();

for (int i = 0; i < size; i++) {

int[] location = getBankerCardLocation(i);

CardImage cardImage = bankerCards.get(i);

cardImage.translate(location[0], location[1]);

}

}

private boolean check() {

boolean isBankerBlackJack = isBlackJack(bankerCards);

boolean isPlayerBlackJack = isBlackJack(playerCards);

if (isBusted(playerCards) || (isBankerBlackJack && !isPlayerBlackJack)) {

lose();

return true;

} else if (isBusted(bankerCards) || (!isBankerBlackJack && isPlayerBlackJack)) {

win();

return true;

} else if (isBankerBlackJack && isPlayerBlackJack) {

draw();

return true;

}

return false;

}

private boolean isBlackJack(List handCards) {

return count(handCards) == BLACK_JACK;

}

private boolean isBusted(List handCards) {

return count(handCards) > BLACK_JACK;

}

private int count(List handCards) {

int total = 0;

if (null == handCards) {

return total;

}

for (CardImage cardImage : handCards) {

if (cardImage == null || cardImage.getCard() == null) {

continue;

}

Card card = cardImage.getCard();

if (card.getRank() == Card.Rank.ACE) {

if (total + 11 > BLACK_JACK) {

total += 1;

} else {

total += 11;

}

} else if (card.getRank().ordinal() >= 9) {

total += TEN;

} else {

total += (card.getRank().ordinal() + 1);

}

}

return total;

}

private void reset() {

cards = new Deck().getDeck();

for (CardImage cardImage : playerCards) {

if (cardImage != null && cardImage.getImage() != null) {

parent.removeView(cardImage.getImage());

}

}

for (CardImage cardImage : bankerCards) {

if (cardImage != null && cardImage.getImage() != null) {

parent.removeView(cardImage.getImage());

}

}

playerCards.clear();

bankerCards.clear();

for (int i = 0; i < 8; i++) {

msgHandle.removeMessages(i);

}

deal();

}

private void enableButtons() {

hit.setClickable(true);

stand.setClickable(true);

fold.setClickable(true);

}

private void disableButtons() {

hit.setClickable(false);

stand.setClickable(false);

fold.setClickable(false);

}

private void hit() {

disableButtons();

msgHandle.sendMessage(Message.obtain(msgHandle, MSG_PLAYER_DEAL));

}

private void stand() {

disableButtons();

if (count(bankerCards) > count(playerCards)) {

lose();

return;

}

msgHandle.sendMessage(Message.obtain(msgHandle, MSG_BANKER_DEAL));

}

private void fold() {

disableButtons();

frontBankerHide();

lose();

}

private void win() {

disableButtons();

Toast.makeText(MainActivity.this, "you win!", Toast.LENGTH_SHORT).show();

msgHandle.sendMessage(Message.obtain(msgHandle, MSG_BANKER_FRONT_HIDE));

msgHandle.sendMessageDelayed(Message.obtain(msgHandle, MSG_RESET), 4000);

}

private void lose() {

disableButtons();

Toast.makeText(MainActivity.this, "you lose!", Toast.LENGTH_SHORT).show();

msgHandle.sendMessage(Message.obtain(msgHandle, MSG_BANKER_FRONT_HIDE));

msgHandle.sendMessageDelayed(Message.obtain(msgHandle, MSG_RESET), 4000);

}

private void draw() {

disableButtons();

Toast.makeText(MainActivity.this, "game draws!", Toast.LENGTH_SHORT).show();

msgHandle.sendMessage(Message.obtain(msgHandle, MSG_BANKER_FRONT_HIDE));

msgHandle.sendMessageDelayed(Message.obtain(msgHandle, MSG_RESET), 4000);

}

}

其实源码没有什么技术难点:

所有的动作都以Message方式传递给Handler处理,Handler分发事件调用各个方法

每发一张牌,都是在屏幕上new了一个ImageView,并对这个ImageView进行动画操作

自认为写的比较挫的是对连续动画实现的不好,现在的硬编码low爆了。一个AnimationListener嵌套另一个AnimationListener,而且还需要添加Callback监听旋转和移动动画完成后的下一个操作,更好的实现方式是单起Thread,利用sleep或者wait/notify来实现动画的衔接

Animator的使用没有想的简单,说是会改变View属性,但连续使用“translation”操作,View对象的边界其实没有改变,后续的传值需要考虑之前的translation赋值,或者在每次动画之后调用View.layout方法更新一遍边界

0x0004 写码感想

像Handler、Animator这些东西自以为源码看了几遍应该手到擒来的,在使用的时候还是会发现各种效果实现和自己想的不一样

行胜于言

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值