一.项目目标
利用Java swing技术能够实现玩家与电脑进行二十一点游戏。要求如下:

纸牌数:共52张纸牌,除去大小王两张纸牌。
花色:红桃、黑桃、方块、梅花。
纸牌的面值:A到10的纸牌面值按照1到10计算,J、Q、K的纸牌面值按照1进行计算。
玩法:电脑先抓牌,玩家后抓牌。计算自己的面值总数,比较面值数;如果面值数总数都大于或等于21,则平局;如果玩家和电脑的面值总数有一个大于21点,另一个不大于21点,则不大于21点的为赢家;如果玩家和电脑的面值总数都不大于21,则面值总数大的为赢家。
二.项目分析
算法 
(1)生成一副符号要求的纸牌,以纸牌的背面显示给玩家,即洗牌; 
(2)游戏开始,电脑先依次抓牌,抓牌的总面值数大于等于15点就停止抓牌。并以牌的背面显示给玩家; 
(3)玩家依次抓牌,玩家根据牌的总面值数确定是否再抓牌。以牌的正面显示给玩家; 
(4)玩家确定不再抓牌,即本次游戏结束,计算胜负关系,显示玩家和电脑的胜负关系。
牌的图片命名规则 
在游戏中的纸牌以图片的方式显示,因此对图片的命名与纸牌的面值和花色进行关联,规则如下: 
(1)“1”代表红桃,“2”代表黑桃,“3”代表方块,“4”代表梅花; 
(2)“1到10”代表面值“A到10”,11代表面值为J,12代表面值为Q,13代表面值为K; 
(3)图片的名称由花色、连字符“-”和面值三部分信息组成,如1-1.gih代表红桃A,4-11.gif代表梅花J
一张牌的设计 
根据牌的特性:设计一个Card类,含有两个属性type和value,分别表示牌的花色和面值。创建一个对象就等于生成一张牌。
一副牌的设计 
定义Card数组,长度为52,把52张牌放到数组中。要实现随机放置牌到数组中,首先生成一副有规律的牌,依次生成红桃A到红桃K、黑桃A到黑桃K、方块A到方块K、梅花A到梅花K,共52张牌,同时确定花色和面值,依次放入Card数组中。再循环52次,每次生成两个0~51之间的整数,依据利用这两个随机数交换Card数组中对应位置的内容,即随机打乱这副牌。
类的设计 
对本项目实现的功能设计了4个类:Card、CardManager、GameFrame和AboutFrame。 
Card类主要是对一张纸牌的设计;CardManager类主要是随机生成一副52张纸牌,并在容器中发牌;GameFrame类主要负责游戏界面和玩法等;AboutFrame类主要显示游戏规则和纸牌图片的版权信息。 
CardManager类中定义了Card数组,长度为52,用于存放纸牌。主要方法如下: 
(1)void initCards():按照一定规则初始化一副52张纸牌。 
(2)void randomCard():随机打乱这副纸牌。 
(3)void gameStart(JLabel game[], Container c):用于在容器中显示纸牌的图片,其中参数game[]是标签框控件数组,用于显示图片;参数c代表显示纸牌的容器,即Frame对象。 
GameManager类实现了ActionListener接口,主要含有记牌器、电脑点数、玩家点数、存放纸牌的标签控件数组、存储电脑纸牌的向量类对象、CardManager类对象等属性。主要方法如下: 
(1)GameFrame():窗体控件初始化。 
(2)void actionPerformed(ActionEvent e):主要处理命令按钮动作,即洗牌、开始游戏、玩家抓牌和显示结果的动作。
三.代码实现
Card.java:

package 二十一点游戏;

public class Card {
    private int value = 0;  //纸牌面值
    private int type = 0;  //纸牌花色
    public Card(int type, int value) {
        this.value = value;
        this.type = type;
    }
    public int getValue() {
        return value;
    }
    public void setValue(int value) {
        this.value = value;
    }
    public int getType() {
        return type;
    }
    public void setType(int type) {
        this.type = type;
    }

}

CardManager.java:

package 二十一点游戏;

import java.awt.Container;
import java.awt.Rectangle;

import javax.swing.ImageIcon;
import javax.swing.JLabel;

public class CardManager {
    public Card[] cards = new Card[52];
    //初始化一副52张纸牌
    public void initCards() {
        for (int i = 1; i <= 4; i++) {
            for (int j = 1; j <= 13; j++) {
                cards[(i - 1)*13 + j - 1] = new Card(i, j);
            }
        }
    }

    //随机打乱这52张纸牌
    public void randomCards() {
        Card temp = null;
        //随机生成牌号
        for (int i = 0; i < 52; i++) {
            int a = (int)(Math.random()*52);
            int b = (int)(Math.random()*52);
            temp = cards[a];
            cards[a] = cards[b];
            cards[b] = temp;
        }
    }


    //显示纸牌图片
    public void gameStart(JLabel game[], Container c) {
        //在容器中删除标签组件
        if (game[0] != null) {
            for (int i = 0; i < 52; i++) {
                c.remove(game[i]);
            }
            c.repaint();
        }
        //在容器中放置52个标签组件用于放置图片
        for (int i = 0; i < 52; i++) {
            game[i] = new JLabel();
            game[i].setBorder(javax.swing.BorderFactory.createEtchedBorder());
            game[i].setBounds(new Rectangle(100 + i*10, 10, 101, 150));
            c.add(game[i]);
        }
        //设置标签组件的图片为rear.jpg,即牌的背面
        for (int i = 0; i < 52; i++) {
            game[i].setIcon(new ImageIcon("images/rear.jpg"));
        }
    }
}

GameFrame.java:

package 二十一点游戏;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Vector;

import javax.swing.*;

public class GameFrame  extends JFrame implements ActionListener{
    JButton clear_btn = new JButton("洗牌");
    JButton compute_btn = new JButton("开始游戏");
    JButton game_btn = new JButton("玩家抓牌");
    JButton gameover_btn = new JButton("本轮结束");
    CardManager cm = new CardManager();
    JLabel game[] = new JLabel[52];  //放置52张扑克牌的标签
    int i = 0;  //定义抓牌数
    int computer_dot = 0;  //定义电脑点数
    int game_dot = 0;  //定义玩家点数
    Vector v = new Vector();  //存储电脑抓的纸牌
    JLabel jLabel1 = new JLabel("电脑显示牌区");
    JLabel jLabel2 = new JLabel("玩家显示牌区");
    public GameFrame() {
        getContentPane().setLayout(null);
        this.setTitle("二十一点游戏");
        this.setSize(800, 500);
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();  //获得屏幕的宽和高
        Dimension frameSize = this.getSize();  //获得当前窗体的宽和高
        //设置窗体居中
        if (frameSize.height > screenSize.height) {
            frameSize.height = screenSize.height;
        }
        if (frameSize.width > screenSize.width) {
            frameSize.width = screenSize.width;
        }
        this.setLocation((screenSize.width-frameSize.width)/2, (screenSize.height-frameSize.height)/2);
        clear_btn.setBounds(new Rectangle(78, 388, 73, 31));
        clear_btn.addActionListener(this);
        compute_btn.setBounds(new Rectangle(233, 388, 86, 31));
        compute_btn.setEnabled(false);
        compute_btn.addActionListener(this);
        game_btn.setBounds(new Rectangle(413, 389, 91, 32));
        game_btn.setEnabled(false);
        game_btn.addActionListener(this);
        gameover_btn.setBounds(new Rectangle(625, 390, 91, 32));
        gameover_btn.setEnabled(false);
        gameover_btn.addActionListener(this);
        JMenuBar menuBar = new JMenuBar();
        JMenu file = new JMenu("文件");
        JMenu help = new JMenu("帮助");
        JMenuItem fileExit = new JMenuItem("退出");
        JMenuItem helpAbout = new JMenuItem("关于");
        this.setJMenuBar(menuBar);
        menuBar.add(file);
        menuBar.add(help);
        file.add(fileExit);
        fileExit.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent arg0) {
                // TODO Auto-generated method stub
                System.exit(0);
            }
        });
        help.add(helpAbout);
        helpAbout.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent arg0) {
                // TODO Auto-generated method stub
                new AboutFrame();
            }
        });
        jLabel1.setBounds(new Rectangle(104, 343, 95, 38));
        jLabel2.setBounds(new Rectangle(499, 343, 92, 33));
        this.getContentPane().add(jLabel1);
        this.getContentPane().add(jLabel2);
        this.getContentPane().add(clear_btn);
        this.getContentPane().add(compute_btn);
        this.getContentPane().add(game_btn);
        this.getContentPane().add(gameover_btn);
        this.setVisible(true);
        this.setResizable(false);
    }
    @Override
    public void actionPerformed(ActionEvent arg0) {
        // TODO Auto-generated method stub
        //洗牌按钮
        if (arg0.getSource() == clear_btn) {
            //关闭和开启相应的按钮
            compute_btn.setEnabled(true);
            clear_btn.setEnabled(false);
            //对记牌器、电脑点数和玩家点数进行初始化
            i = 0;
            computer_dot = 0;
            game_dot = 0;
            //把标签框控件数组放入窗体窗格中
            cm.gameStart(game, this.getContentPane());
            //初始化纸牌
            cm.initCards();
            //随机打乱纸牌
            cm.randomCards();
        }

        //开始游戏按钮
        if (arg0.getSource() == compute_btn) {
            //关闭和开启相应的按钮
            compute_btn.setEnabled(false);
            game_btn.setEnabled(true);
            //电脑抓牌
            for (int k = 0; k < 20; k++) {
                game[i].setIcon(new ImageIcon("images/rear.jpg"));
                game[i].setBounds(new Rectangle(50 + i*20, 200, 101, 150));
                getContentPane().setComponentZOrder(game[i], 1);
                if (cm.cards[i].getValue() == 10) {
                    computer_dot = computer_dot + 1;
                }else {
                    computer_dot = computer_dot + cm.cards[i].getValue();
                }
                v.add(cm.cards[i]);
                getContentPane().repaint();
                i = i + 1;
                //如果面值总数大于15停止抓牌
                if (computer_dot >= 15) {
                    return;
                }
            }
        }

        //玩家抓牌按钮
        if (arg0.getSource() == game_btn) {
            //提示
            if (game_dot >= 10) {
                int a = JOptionPane.showConfirmDialog(null, "现在点数为:"+game_dot+"是否再抓牌", "提示", JOptionPane.YES_NO_OPTION);
                if (a == JOptionPane.NO_OPTION) {
                    game_btn.setEnabled(false);
                    gameover_btn.setEnabled(true);
                    return;
                }
            }
            //设置标签框力里显示抓到的纸牌
            game[i].setIcon(new ImageIcon("images/"+cm.cards[i].getType()+"-"+cm.cards[i].getValue()+".jpg"));
            game[i].setBounds(new Rectangle(350 + i*20, 200, 101, 150));
            this.getContentPane().setComponentZOrder(game[i], 1);
            //计算抓到的纸牌面值
            if (cm.cards[i].getValue() > 10) {
                game_dot = game_dot + 1;
            }else {
                game_dot = game_dot + cm.cards[i].getValue();
            }
            i = i + 1;
            //面值大于21停止抓牌,关闭和开启相应按钮
            if (game_dot > 21) {
                game_btn.setEnabled(false);
                gameover_btn.setEnabled(true);
                return;
            }
        }

        //本轮游戏结束按钮
        if (arg0.getSource() == gameover_btn) {
            for (int i = 0; i < v.size(); i++) {
                Card card = (Card)v.get(i);
                game[i].setIcon(new ImageIcon("images/"+card.getType()+"-"+card.getValue()+".jpg"));
                game[i].setBounds(new Rectangle(50 + i*20, 200, 101, 150));
                this.getContentPane().setComponentZOrder(game[i], 1);
            }
            //计算胜负
            String game_over = "";
            if (game_dot > 21 && computer_dot <= 21) {
                game_over = "电脑获胜";
            } else if (game_dot <= 21 && computer_dot > 21) {
                game_over = "玩家获胜";
            }else if (game_dot >= 21 & computer_dot >= 21) {
                game_over = "平局";
            }else if (game_dot > computer_dot) {
                game_over = "玩家获胜";
            }else if (game_dot < computer_dot) {
                game_over = "电脑获胜";
            }else if (game_dot == computer_dot) {
                game_over = "平局";
            }
            //以对话框的方式显示胜负
            String message = "游戏结果\n";
            message = message + "电脑点数:" + String.valueOf(computer_dot) + "\n";
            message = message + "玩家点数:" + String.valueOf(game_dot) + "\n";
            message = message + "游戏结果:" + game_over;
            JOptionPane.showMessageDialog(null, message, "本轮游戏结果", JOptionPane.INFORMATION_MESSAGE);
            //设置命令按钮可操作
            clear_btn.setEnabled(true);
            compute_btn.setEnabled(true);
            game_btn.setEnabled(true);
            gameover_btn.setEnabled(true);
        }
    }

    public static void main(String[] args) {
        GameFrame gameFrame = new GameFrame();
    }
}

AboutFrame.java:

package 二十一点游戏;

import javax.swing.*;
import java.awt.*;

public class AboutFrame extends JFrame{
    JLabel jLabel1 = new JLabel("游戏规则");
    JLabel jLabel2 = new JLabel("声明");
    JTextArea textArea1 = new JTextArea();
    JTextArea textArea2 = new JTextArea();
    public AboutFrame() {
        getContentPane().setLayout(null);
        jLabel1.setBounds(new Rectangle(27, 0, 97, 36));
        jLabel2.setBounds(new Rectangle(26, 176, 80, 27));
        textArea1.setColumns(40);
        textArea1.setEditable(false);
        textArea1.setLineWrap(true);
        textArea1.setText("电脑先抓牌,玩家后抓牌。计算自己的面值总数,比较面值数,如果面值总数都大于或等于21,则平局;如果玩家和电脑的面值总数有一个大于21点,另一个不大于21点,则不大于21点的为赢家;如果玩家和电脑的面值总数都不大于21,则面值总数大的为赢家。");
        textArea1.setBounds(new Rectangle(25, 36, 392, 130));
        textArea1.setFont(new Font("楷体", Font.PLAIN, 16));
        textArea2.setEditable(false);
        textArea2.setLineWrap(true);
        textArea2.setBounds(new Rectangle(26, 207, 398, 63));
        textArea2.setText("该游戏中,纸牌的图片来自于WindowsXP的纸牌游戏,图片权属于原作者所有!");
        textArea2.setFont(new Font("楷体", Font.PLAIN, 16));
        this.getContentPane().add(jLabel1);
        this.getContentPane().add(jLabel2);
        this.getContentPane().add(textArea1);
        this.getContentPane().add(textArea2);
        this.setSize(450, 300);
        this.setTitle("关于");
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();  //获得屏幕的宽和高
        Dimension frameSize = this.getSize();  //获得当前窗体的宽和高
        //设置窗体居中
        if (frameSize.height > screenSize.height) {
            frameSize.height = screenSize.height;
        }
        if (frameSize.width > screenSize.width) {
            frameSize.width = screenSize.width;
        }
        this.setLocation((screenSize.width-frameSize.width)/2, (screenSize.height-frameSize.height)/2);
        this.setVisible(true);
    }
}