一、斗地主规则介绍
1.1 基本规则
参加人数:3人
总牌数:54张,从2到A,四种花色("梅花", "红桃", "黑桃", "方形"),共52张,加黑白色的小王,彩色的大王。
分牌数:每人先分17张,最后确定地主拿3张
胜利规则:三人中有一人出完牌
1.2 出牌规则
单张:任意一张牌
对子:两张一样的牌
顺子:大于四张,连续的牌
三带一:三张一样的牌带任意一张
炸弹:四张一样,或双王
1.3 大小规则
单张:从3到10,J,Q,K,A,2,小王,大王依次增大
对子:同单张规则
三带一:三张牌满足单张规则
顺子:最小牌满足单张规则
炸弹:具有通吃属性,王炸无敌,四炸满足单张规则
二、代码实现规则
2.1 定义扑克牌
每张扑克牌应该具有自己的花色与值
/**
* 定义扑克牌
*/
class Card implements Comparable<Card> {
/**
* 花色(黑桃、红桃、梅花、方块)
*/
private String color;
/**
* 大小(2-10,J,Q,K,A,大王,小王)
*/
private String value;
public Card() {
}
public Card(String color, String value) {
this.color = color;
this.value = value;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Card) {
Card card = (Card) obj;
if (card.value.equalsIgnoreCase(this.value) && card.color.equalsIgnoreCase(this.color)) {
return true;
}
}
return false;
}
@Override
public int hashCode() {
return color.hashCode() + value.hashCode();
}
//定义大小规则
@Override
public int compareTo(Card card) {
return findIndex(this) - findIndex(card);
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("Card{");
sb.append("color='").append(color).append('\'');
sb.append(", value='").append(value).append('\'');
sb.append('}');
return sb.toString();
}
}
2.2 定义玩家
每个玩家具有自己的游戏名,在游戏中具有自己的上家与下家,持有牌与预出牌,并记录当前是否具有出牌资格
/**
* 定义玩家
*/
static class Player {
/**
* 用户名
*/
private String username;
/**
* 是否为第一个出牌
*/
private boolean first;
/**
* 持有牌
*/
LinkedList<Card> cards = new LinkedList<>();
/**
* 当前选择出牌
*/
LinkedList<Card> putCards = new LinkedList<>();
/**
* 上一位出牌人
*/
private Player before;
/**
* 下一位出牌人
*/
private Player next;
public Player() {
}
public Player(String username) {
this.username = username;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public void setCards(LinkedList<Card> cards) {
this.cards = cards;
}
public boolean isFirst() {
return first;
}
public void setFirst(boolean first) {
this.first = first;
}
public LinkedList<Card> getCards() {
return cards;
}
public LinkedList<Card> getPutCards() {
return putCards;
}
public void setPutCards(LinkedList<Card> putCards) {
this.putCards = putCards;
}
public Player getBefore() {
return before;
}
public void setBefore(Player before) {
this.before = before;
}
public Player getNext() {
return next;
}
public void setNext(Player next) {
this.next = next;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("Player{");
sb.append("username='").append(username).append('\'');
sb.append(", cards=").append(cards);
sb.append(", putCards=").append(putCards);
sb.append('}');
return sb.toString();
}
}
2.3 定义游戏规则
一场完整的斗地主,需要洗牌,分牌,确定地主,判断出牌是否大于上两家
/**
* 玩扑克牌
*
* @author sunyiran
* @date 2018-10-19
*/
public class PlayCards {
/**
* 扑克牌的值
*/
private static final String[] cardVlues = {"3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2", "小王",
"大王"};
/**
* 扑克牌的花色
*/
private static final String[] cardColors = {"梅花", "红桃", "黑桃", "方形"};
/**
* 一副扑克牌
*/
private ArrayList<Card> cardList = new ArrayList<>(54);
/**
* 参加玩家
*/
private Player[] players;
public PlayCards() {
}
public PlayCards(Player[] players) {
getCardArr();
this.players = players;
}
/**
* 获取一副扑克牌的算法
*/
private void getCardArr() {
//前52张牌具有花色
for (int i = 0; i < 52; i++) {
Card card = new Card();
card.setColor(cardColors[i % 4]);
card.setValue(cardVlues[i % 13]);
cardList.add(card);
}
cardList.add(new Card("黑白", "小王"));
cardList.add(new Card("彩色", "大王"));
}
/**
* 获取扑克牌大小位置
*
* @param card
* @return
*/
private int findIndex(Card card) {
for (int i = 0; i < cardVlues.length; i++) {
if (card.value.equalsIgnoreCase(cardVlues[i])) {
return i;
}
}
return -1;
}
/**
* 洗牌
*
* @param player 上轮赢家
*/
private List<Card> shuffle(Player player) {
//清除玩家手里的牌
Arrays.stream(players).forEach(x -> {
LinkedList<Card> cards = x.cards;
cards.clear();
});
//赢家取消优先级
if (player != null)
player.setFirst(false);
//采用随机策略洗牌
Random random = new Random();
for (int i = 0; i < cardList.size(); i++) {
Card temp = cardList.get(i);
//要交换的索引
int changeIndex = random.nextInt(54);
cardList.set(i, cardList.get(changeIndex));
cardList.set(changeIndex, temp);
}
return cardList;
}
/**
* 分牌
*/
private void distributional() {
//留下三张给地主
for (int i = 0; i < 51; i++) {
if (i < 17) {
players[0].getCards().add(cardList.get(i));
}
if (i >= 17 && i < 34) {
players[1].getCards().add(cardList.get(i));
}
if (i >= 34) {
players[2].getCards().add(cardList.get(i));
}
}
//随机确定地主
players[new Random().nextInt(3)].setFirst(true);
//地主拿三张
Arrays.stream(players).filter(Player::isFirst).forEach(x -> {
LinkedList<Card> cards = x.getCards();
cards.add(cardList.get(51));
cards.add(cardList.get(52));
cards.add(cardList.get(53));
System.out.println("当前地主: " + x.getUsername());
});
//按顺序排序
players[0].getCards().sort(Card::compareTo);
players[1].getCards().sort(Card::compareTo);
players[2].getCards().sort(Card::compareTo);
}
/**
* 判断出牌是否合法(根据出牌规则)
*
* @param player
* @return 个位代表出牌个数,个位相同时,值高低也代表大小级别(可以用Map优化)
*/
private int compliance(Player player) {
LinkedList<Card> cards = player.getPutCards();
LinkedList<Card> hasCards = player.getCards();
//可以不出牌
if (cards.size() == 0) {
return 0;
}
//每张牌必须是自己手里的牌
long count = cards.stream().filter(x -> !hasCards.contains(x)).count();
if (count > 0) {
return -1;
}
//如果是单张牌,只要在牌中即可
if (cards.size() == 1) {
return 1 + (findIndex(cards.get(0)) + 1) * 10;
}
//如果是两张,则为对子或王炸
if (cards.size() == 2 && cards.contains(new Card("彩色", "大王")) && cards.contains(new Card("黑白", "小王"))) {
return 20000002;
}
if (cards.size() == 2 && cards.get(0).value.equals(cards.get(1).value)) {
return 2 + (findIndex(cards.get(0)) + 1) * 100;
}
//如果是四张,则为三带一,或者炸弹
//判断是否为炸弹
boolean bomb = cards.get(0).value.equals(cards.get(1).value) && cards.get(1).value.equals(cards.get(2)
.value) && cards.get(2).value.equals(cards.get(3).value);
if (cards.size() == 4) {
if (bomb) {
return 4 + (findIndex(cards.get(0)) + 1) * 100000;
} else {
//偷懒,第一张牌不允许为单张
return 4 + (findIndex(cards.get(0)) + 1) * 1000;
}
}
//大于四张,则为连子
if (cards.size() > 4) {
for (int i = 1; i < cards.size(); i++) {
if (findIndex(cards.get(i)) != findIndex(cards.get(i - 1)) + 1) {
return -1;
}
}
return cards.size() + (findIndex(cards.get(0)) + 1) * 1000;
}
return -1;
}
/**
* 判断出牌是否合法(根据大小规则)
*
* @param player
* @return
*/
private boolean compareSize(Player player) {
//出牌一定比上两家大
//自己
int self = compliance(player);
String v1 = String.valueOf(self);
//上方第一家
int before1 = compliance(player.before);
String v2 = String.valueOf(self);
//上方第二家
int before2 = compliance(player.before.before);
String v3 = String.valueOf(self);
if (before1 == 0 && before2 == 0 && self > 0) {
return true;
}
if (self == 0) {
return true;
}
int amout1 = Integer.parseInt(v1.substring(v1.length() - 2));
int amout2 = Integer.parseInt(v2.substring(v2.length() - 2));
int amout3 = Integer.parseInt(v3.substring(v3.length() - 2));
if (self == 20000002) {
return true;
}
if (before1 > 0 && (amout1 == amout2 || self > 100003) && self > before1) {
return true;
}
if (before1 == 0 && before2 > 0 && (amout1 == amout3 || self > 100003) && self > before2) {
return true;
}
System.out.println("您的出牌没能大于上家");
return false;
}
}
2.4 定义游戏流程
玩家在控制台输入想要打出的牌,一行一张牌,输入end结束。每次会显示上两家出牌记录,以及自身当前持有牌组
/**
* 比赛流程
* @param players 参加的玩家
*/
public static void start(Player[] players) {
// //定义玩家
// Player p1 = new Player("一号");
// Player p2 = new Player("二号");
// Player p3 = new Player("三号");
//
// p1.setNext(p2);
// p1.setBefore(p3);
// p2.setNext(p3);
// p2.setBefore(p1);
// p3.setNext(p1);
// p3.setBefore(p2);
//开局
PlayCards play = new PlayCards(players);
//洗牌
play.shuffle(null);
//分牌
play.distributional();
Scanner sc = new Scanner(System.in);
boolean exit = play.players[0].getCards().size() > 0 && play.players[1].getCards().size() > 0 && play
.players[2].getCards().size() > 0;
while (exit) {
Player player = Arrays.stream(play.players).filter(Player::isFirst).findFirst().get();
//显示上两家出牌
System.out.println("上两家出牌为");
System.out.println(player.before.getPutCards());
System.out.println(player.before.before.getPutCards());
//当前玩家输出的一组牌
LinkedList<Card> putCards = player.getPutCards();
//从手里清除打出的牌
player.getCards().removeAll(putCards);
//清除上次出牌记录
putCards.clear();
System.out.println(player.getUsername() + "玩家请出牌");
System.out.println("您当前持有的卡牌:" + player.getCards());
//控制台输入输出的每张牌
String line;
while (!(line = sc.nextLine()).equalsIgnoreCase("end")) {
String[] split = line.split("[:|:]");
if (split.length != 2) {
System.out.println("请按规则输入");
continue;
}
Card card = play.new Card(split[0], split[1]);
putCards.add(card);
}
//如果当前玩家出牌符合规定,则下一位玩家出牌,否则重新出牌
if (play.compliance(player) > -1 && play.compareSize(player)) {
player.setFirst(false);
player.getNext().setFirst(true);
} else {
putCards.clear();
System.out.println("请选择正确的牌组重新出牌");
}
}
}
三、体验斗地主
更多规则测试,请下载源码,在本地运行
四、扩展方向
相对于完整的网络斗地主:
1、图形界面:可以采用Swing做简单开发,也可以基于安卓或IOS做App开发,当然Web界面也是不错的
2、网络图形:可以用socket通信实现多人在线游戏