一开始看题目我就看了很久,面对下面这张图解我完全看不懂,不知道这个游戏到底怎么进行,后来在win10自带的游戏里找到了这个游戏才知道是要做什么。(做一个项目之前必须得完完全全理解这个项目具体的要求是什么,不能摸棱两可)
(点击solitaire进去后选择经典单人游戏即可感受游戏)
(以上的四个牌堆对应我项目中的deck、waste、foundation、tableau)
1.有关牌堆的设计
(1)枚举类Suit和Colour我们需要纸牌的四个花色和十三个大小,所以初始化了两个枚举类Suit和Colour,分别存放花色和大小,以便卡牌的初始化。
(2)Card类我们需要卡牌类Card,然后初始化每一张卡牌,Card类中有卡片图像、数值、花色是否正面朝上的属性,以及各个属性对应的get和set方法。
(3)Pile由于牌堆后进先出的,所以我使用了 stack来模拟了堆栈进行操作。Pile类有两个属性,一个是Pile堆在面板上的位置,一个是stack存储该牌堆的所有card。实现的方法有,获取最顶部卡牌,往牌堆顶添加一张卡牌,push一组卡牌,pop一组卡牌,删除顶端卡牌,判断牌堆是否为空等等方法。
(4)Deck卡库继承自Pile。初始化时就把所有卡片先放入Deck中,添加paintComponent方法(画出deck堆,若无牌则显示空白框,有牌则显示有牌的背面)。
(5)waste丢弃堆继承自Pile。添加paintComponent方法(画出waste堆,若无牌则显示空白框,有牌则只显示最上面一张牌的正面,其他牌被覆盖)。(6)Foundation正序堆堆花色堆继承自Pile,是收集已经有序的牌的地方。添加paintComponent方法(画出deck堆,若没有牌则显示花色底牌,有牌则只显示最上面一张牌的正面,其他牌被覆盖)、moveFromWaste方法(从丢弃堆移动到正序堆)、moveTo方法(从正序堆移动到倒序堆)、accepts方法(该卡片是否能放到正序堆)、intToSuit方法(花色和花色相应的数字是否匹配)
(7)Tableau倒序堆桌面堆是玩牌的主要区域,需要根据游戏的规则堆牌堆进行拖动,达到排序的效果,最终让牌全部放到花色堆。桌面堆继承自Pile,并且添加paintComponent方法(画出tableau堆,一沓牌只有最上面的牌显示为正面,其他的显示为背面,并且堆叠放置)、moveFromWaste方法(从丢弃堆移动到倒序堆)、accepts方法(该卡片是否能放到倒序堆)、两个moveTo方法(分别是移动到正序堆和其他倒序堆)、getClickedCard方法(获取选中的是倒序堆的哪些牌)
2. 有关界面的设计
(1)定义GameMoveListener类继承自MouseInputAdapter,其中重写mousePressed、mouseReleased方法,鼠标下压如果选中的是卡库、丢弃堆、或倒序堆的牌如果能放到对应的正序堆则直接放上去。若不能,则根据鼠标释放时的位置判断是否允许该次移动,最后重画各个变化了的组件。
(2)定义GamePanel,在其上添加监听器、各个牌堆类并放置好位置。
cards包用来装卡片的图像图片
(命名格式非常重要!!!!)
//back001为卡片背面图片,bottom01为空白卡片轮廓图片,放在空白遗弃堆位置。fpBase为四个花色的背景图片,放在空白正序堆位置。
实现思路:
Pile类为牌堆的基类
其他四个牌堆继承Pile类,
Color、suit类为枚举类,存放颜色、花色信息
GameMoveListener实现移动卡片的监听器
GamePanel为创建游戏面板,添加入所有组件
PhotoMoving为一个移动卡片拖拽的实现,尝试了加在项目里面结果没成功(于是项目现在的卡片移动过程看不到,只能怪看到移动成功后的卡片位置变换)
上代码!!!
package solitaire;
public enum Suit {
Spades, Hearts, Clubs, Diamonds }//enum枚举类型这
//段代码实际上调用了Enum(String name, int ordinal):new Enum("MON",0);
package solitaire;
public enum Colour {
Red, Black, Neither }
package solitaire;
import java.awt.Image;
import javax.swing.ImageIcon;
public class Card {
public static String cardBackFilename = "back001", cardOutlineFilename = "bottom01",
fpBaseFilename = "fpBase0";
public static String directory = "cards", extension = ".gif";
private Image im;//每一张卡片的正面图片
private int value;//卡片数值
private String suit;//花色
private boolean faceUp; private Colour colour;
public Card(int value, Suit suit) {//通过switch选择花色
this.value = value;
switch(suit) {
case Clubs:
this.suit = "c";
colour = Colour.Black;
break;
case Diamonds:
this.suit = "d";
colour = Colour.Red;
break;
case Spades:
this.suit = "s";
colour = Colour.Black;
break;
case Hearts:
this.suit = "h";
colour = Colour.Red;
break;
}
faceUp = false;
try {
ImageIcon ii = new
ImageIcon(getClass().getResource("/"+directory + cardFile(suit, value)));
im = ii.getImage();
}catch(Exception e) {
System.err.println("Error: " + e.getMessage()+"lalala");
}
}
private String cardFile(Suit s, int val) {//返回卡片对应图片的文件名
char ch;
if (val < 1 || val > 13)
throw new IllegalArgumentException("Bad Card Number");
if(s == Suit.Clubs) {
ch = 'c';
}else if(s == Suit.Hearts) { ch = 'h'; }else if(s == Suit.Spades) { ch = 's'; }else if(s == Suit.Diamonds) { ch = 'd'; } else throw new IllegalArgumentException("Bad Card Suit"); if(val < 10) return "/0" + val + ch + extension; else return "/" + val + ch + extension; } public Image getCardImage() { return im; }
public boolean isFaceUp() {//是否为正面
return faceUp; }
public Colour getColour() {
return colour; }
@Override
public String toString() {
return value + " of " + suit ; }
public static Image getFoundationBase(int suit) {//获取牌的花色信息,,空牌
ImageIcon ii = new ImageIcon( Card.class.getResource("/"+directory + "/" + fpBaseFilename + suit + extension));
Image image = ii.getImage();
return image; }
public static Image getCardOutline() {//获取空白的卡牌图像
ImageIcon ii = new ImageIcon(
Card.class.getResource("/"+directory + "/" + cardOutlineFilename + extension));
Image image = ii.getImage();
return image; }
public static Image getCardBack() {//返回盖着的牌的图像
ImageIcon ii = new ImageIcon(Card.class.getResource("/"+directory + "/" + cardBackFilename + extension));
Image image = ii.getImage();
return image; }
public int getValue() {
return value; }
public String getSuit() {
return suit; }
public void showFace() {
faceUp = true; } }
package solitaire;
import java.util.EmptyStackException;
import java.util.Stack;
import javax.swing.JPanel;
public abstract class Pile extends JPanel {//牌堆基类
//牌堆类
protected int x, y;//牌堆放置的位置
protected Stack<Card> cards;//用堆栈来存储牌堆里的卡片
public Pile(int x, int y) {
super.setLocation(x, y);//设置面板的位置
cards = new Stack<>(); }
public Card topCard() {//用栈来返回牌堆顶的牌
if(!this.cards.isEmpty()) {
return this.cards.peek();
}
return null;
}
public Card pop() {//用堆的pop实现取走最上面的牌
try {
return cards.pop();
}
catch(EmptyStackException ese) {
return null;
}
}
public void push(Card someCard) {//用push实现放牌到牌堆的最上面
this.cards.push(someCard);
}
public boolean isEmpty() {//判断牌堆是否为空
return this.cards.isEmpty();
}
}
package solitaire;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.util.Collections;
public class Deck extends Pile {//卡库卡堆
public Deck(int x, int y) {
super(x, y);
super.setSize(72, 96);
for(Suit suit : Suit.values()) {//把所有花色1-13的卡片放进卡库堆栈中
for(int j = 1; j <= 13; ++j) {
push(new Card(j, suit));
}
}
Collections.shuffle(cards);//打乱deck牌堆的所有牌
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setStroke(new BasicStroke(5));//设置画笔宽度为5的画笔
g2d.setColor(Color.WHITE);
g2d.drawRect(0, 0, 72, this.getHeight());//设置举行的左上角位置和宽高
if (!isEmpty()) {//如果卡堆不空则显示卡的背面图片
g.drawImage(Card.getCardBack(), 0, 0, 72,
this.getHeight(),this);
}
}
}
package solitaire;
import java.awt.Graphics;
public class Foundation extends Pile{//四个正序卡堆,从小到大
private int suit;
public Foundation(int x, int y, int i) {
super(x, y);
super.setSize(72, 96);
this.suit = i;
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if(this.isEmpty()) {
g.drawImage(Card.getFoundationBase(suit), 0, 0,
this.getWidth(), this.getHeight(), this);
}
else {
g.drawImage(this.topCard().getCardImage(),
0, 0, this.getWidth(), this.getHeight(), this);
}
}
public boolean moveFromWaste(Waste source, Card card) {//如果以移动成功返回true,失败返回false
if(accepts(card)) {
this.push(source.pop());//从waste的最上面放到正序卡堆的最上面
source = null;
return true;
}
return false;
}
public void moveTo(Tableau destination, Card card) {//放到倒叙卡堆
if(destination.accepts(card)) {
destination.push(this.pop());
}
}
public boolean accepts(Card card) {//能放入的条件,当前卡片数值比已有卡片大1,且卡片花色相同
if(!this.isEmpty()) {
return this.topCard().getValue() == card.getValue() - 1 &&
this.topCard().getSuit().equals(card.getSuit());
}
return card.getValue() == 1 && intToSuit(card.getSuit()); // Ace
}
private boolean intToSuit(String pSuit) {//花色和花色对应的数字是否匹配
if (pSuit.equals("c")) {
return this.suit == 3;
}
else if (pSuit.equals("s")) {
return this.suit == 1;
}
else if (pSuit.equals("h")) {
return this.suit == 2; }
else if (pSuit.equals("d")) {
return this.suit == 4;
}
return false; }
}
package solitaire;
import java.awt.Color;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.util.ArrayDeque;
import java.util.Deque;
public class Tableau extends Pile {
public Tableau(int x, int y, int initSize) {
super(x, y);
super.setSize(72, 450);
super.setOpaque(false);//如果设置为true的话,原样显示组件中的每个像素,也就是正常显示,当设置为false时,组件并未不会显示其中的某些像素,允许控件下面的像素显现出来。即透明
for (int i = 0; i < initSize; ++i) {
push(GamePanel.getDeck().pop());//7个牌堆依次是1-7张牌,创建tableau对象时就从deck卡库中依次取出牌放到tableau中
}
if (initSize > 0) {//如果牌堆的牌数大于0则将最上面的牌翻开 topCard().showFace();
}
}//paintComponent()是swing的一个方法,相当于图形版的main(),
//是会自执行的。如果一个class中有构造函数,则执行顺序是先执行构造函数,再执行这个。
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.WHITE);
g2d.drawLine(0, 0, this.getWidth(), 0);
g2d.drawLine(0, 0, 0, 96);
g2d.drawLine(this.getWidth() - 1, 0, this.getWidth() - 1, 96);
g2d.setPaint(new GradientPaint(36, 0, new Color(255, 255, 255, 160), 36, 60, new Color(0, 0, 0, 0)));
g2d.fillRect(0, 0, this.getWidth(), this.getHeight());
int cardYPos = 0;
if (this.isEmpty()) {
} else {
for (Card c : this.cards) {
if (c.isFaceUp()) {
g.drawImage(c.getCardImage(), 0, cardYPos, 72, 96, this); cardYPos += 20;
} else {
g.drawImage(Card.getCardBack(), 0, cardYPos, 72, 96, this);
cardYPos += 20;//一张接一张往下放
}
}
}
}
public void moveFromWaste(Waste source, Card card) {//从waste牌堆移动过来
if (this.accepts(card)) {
this.push(source.pop());
}
source = null;
}
public boolean accepts(Card card) {//能否放入这个牌堆,1:当前牌比最顶端的牌数值小1,2:花色为黑红相间
if (!this.isEmpty()) {
return this.topCard().getValue() == card.getValue() + 1 && !this.topCard().getColour().equals(card.getColour());
}
return card.getValue() == 13;//如果当前牌堆是空的,则牌堆只能接收k
}
public boolean moveTo(Foundation destination, Card card) {//移动到foundation
if (destination.accepts(card)) {//若可以移动,则进行移动,并返回true,否则为false
destination.push(this.pop());
if (!this.isEmpty()) {
this.topCard().showFace();
}
return true;
}
return false;
}
public void moveTo(Tableau destination, Card card) {//移动到tableau
if (!this.isEmpty() || card.getValue() == 13) {//牌堆不空,或者移动的这张牌是k
if (destination.accepts(card)) {//移动终点可以放这张牌的话
Deque<Card> toBeMovedCards = new ArrayDeque<>();//既可以当栈也可以当队列用
while(!this.isEmpty()) {//牌堆不为空
Card tmp = this.pop();
toBeMovedCards.push(tmp);//把牌堆最顶端的牌放到移动队列中
if(tmp.equals(card)) {//找出到和card相同的卡片为止
break;
}
}
while(!toBeMovedCards.isEmpty())
{//把移动队列中的卡片放到tableau牌堆中
destination.push(toBeMovedCards.pop());
}
}
}
if(!this.isEmpty()) {//如果不空的话把最上面的牌翻开
this.topCard().showFace();
}
}
public Card getClickedCard(int y) {//获取tableau卡堆中的牌
int index = y / 20;//每张牌下相距20
if (index < this.cards.toArray().length) {//toarray获取栈里的所有元素
Card returnMe = (Card) cards.toArray()[index];
if (returnMe.isFaceUp()) {
return returnMe;
}
}
return (Card) cards.toArray()[cards.toArray().length - 1];
}
}
package solitaire;
import java.awt.Graphics;
public class Waste extends Pile{//丢弃牌牌堆
public Waste(int x, int y) {
super(x, y);
super.setSize(72, 96); }
@Override
protected void paintComponent(Graphics g) { super.paintComponent(g);
if(this.isEmpty()) {//如果牌堆为空则显示空白框
g.drawImage(Card.getCardOutline(), 0, 0, 72, this.getHeight(), this); }
else {
g.drawImage(this.topCard().getCardImage(), 0, 0, 72, this.getHeight(), this);
}//画出牌堆最上面的卡片
}
}
package solitaire;
import java.awt.Component;
import java.awt.Point;
import java.awt.event.MouseEvent;
import javax.swing.SwingUtilities;
import javax.swing.event.MouseInputAdapter;
public class GameMoveListener extends MouseInputAdapter {
private Deck deck = GamePanel.getDeck();
private Waste waste = null;
private Tableau selectedTaubleau = null;
private Foundation selectedFoundation = null;
private Card selectedCard = null;
@Override
public void mousePressed(MouseEvent e) {
Component pressedComponent =
e.getComponent().getComponentAt(e.getPoint());//获取鼠标下压位置的组件
if(pressedComponent instanceof Foundation) {//instanceof运算符是用来在运行时指出对象是否是特定类的一个实例
selectedFoundation = (Foundation) pressedComponent;//如果鼠标点击的是正序卡堆,则其他卡堆置为null,取出正序卡堆顶端的牌
selectedTaubleau = null;
waste = null;
selectedCard = selectedFoundation.topCard();
}else if(pressedComponent instanceof Tableau) {
selectedTaubleau = (Tableau) pressedComponent;
waste = null;
selectedCard = selectedTaubleau.getClickedCard(e.getY() - 150);//根据具体位置计算出来的
for(Foundation foundation : GamePanel.getFoundationPiles()) {//正序卡堆数目,单次点击的若是倒叙卡堆的牌则如果可以直接放入合适的正序卡堆中
if(selectedTaubleau.moveTo(foundation, selectedCard)) {
selectedTaubleau = null;
break;
}
}
}else if(pressedComponent instanceof Deck) {//如果鼠标点击的是卡库
selectedTaubleau = null;
if(!deck.isEmpty()) {//且卡库不为空,则将卡库最上面的牌放到遗弃堆最上面
Waste waste = GamePanel.getWastePile();
waste.push(deck.pop());//把卡库最上面的牌放到遗弃堆上
waste.topCard().showFace();//将遗弃堆的最上面牌翻面
}
}else if(pressedComponent instanceof Waste) {//如果鼠标点击的是遗弃堆
selectedTaubleau = null;
waste = GamePanel.getWastePile();
selectedCard = waste.topCard();
if(selectedCard != null) {
for(Foundation foundation : GamePanel.getFoundationPiles()) {//自动放入正序牌堆合适的牌堆中
if(foundation.moveFromWaste(waste,
selectedCard)); } } }
e.getComponent().repaint();//刷新鼠标点击选中的组件的画面
}
@Override
public void mouseReleased(MouseEvent e) {//鼠标释放监听器
if(selectedCard != null) {//鼠标选中的卡片不为空
Component releasedComponent =
e.getComponent().getComponentAt(e.getPoint());//获取释放鼠标的位置上的组件
if(releasedComponent instanceof Tableau) {//如果选中的是倒叙堆
if(waste != null) {//且鼠标按下时选中的是遗弃堆那个位置,
Tableau destination = (Tableau) releasedComponent;//取出鼠标释放时在的倒叙堆
if(!waste.isEmpty()) {//如果此时遗弃堆不为空
destination.moveFromWaste(waste, selectedCard);//把牌从遗弃堆移动到倒叙堆上
}
waste.repaint();//刷新遗弃堆
}else if(selectedTaubleau != null) {//如果鼠标释放时选中的是倒叙堆的那个位置
Tableau source = selectedTaubleau;
Tableau destination = (Tableau) releasedComponent;
source.moveTo(destination, selectedCard);//从鼠标下压时选中的倒叙堆移动到鼠标释放选中的倒叙堆
source.repaint();
}else if(selectedFoundation != null) { //如果选中的是正序堆
Foundation source = selectedFoundation;
Tableau destination = (Tableau) releasedComponent;//必须释放鼠标选中的是倒叙堆才行
source.moveTo(destination, selectedCard);//把卡片从正序堆移动到倒叙堆
source.repaint();//重画正序堆和倒叙堆
destination.repaint();
}
}
}
e.getComponent().repaint();//释放位置的组件重画,所有鼠标选中的组件置为null
selectedCard = null;
selectedFoundation = null;
selectedTaubleau = null;
waste = null; }
}
package solitaire;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import javax.swing.JPanel;
public class GamePanel extends JPanel { //以下属性全部为一个游戏面板共享且同步改变,所以设置为静态的
protected static int XShift = 80;
public static Point DECK_POSITION = new Point(500, 20);
public static Point TABLEAU_POSITION = new Point(20, 150);
private static int TABLEAU_OFFSET = 80;
private static Deck deck;
private static Waste waste;
private static Foundation[] foundationPiles;
private static Tableau[] tableau;
public GamePanel() {
super.setLayout(null);
initializePiles();
GameMoveListener l = new GameMoveListener();
addMouseListener(l);
addMouseMotionListener(l);
}
private void initializePiles() {//初始化堆,四个堆:1、正序堆2、倒叙堆3、卡库4、遗弃堆
deck = new Deck(DECK_POSITION.x, DECK_POSITION.y);
add(deck);
waste = new Waste(DECK_POSITION.x - XShift, DECK_POSITION.y);
add(waste);
foundationPiles = new Foundation[4];
for(int i = 0; i < foundationPiles.length; ++i) {
foundationPiles[i] = new Foundation(20 + XShift * i, 20, i + 1);//设置放在面板的位置
add(foundationPiles[i]);
}
tableau = new Tableau[7];
for(int tableauIndex = 1; tableauIndex <= tableau.length; ++tableauIndex) {
tableau[tableauIndex - 1] = new Tableau(TABLEAU_POSITION.x + TABLEAU_OFFSET * (tableauIndex - 1), TABLEAU_POSITION.y, tableauIndex + 1);
add(tableau[tableauIndex - 1]);
} }
public static Foundation[] getFoundationPiles() { return foundationPiles; }
public static Waste getWastePile() {
return waste; }
public static Deck getDeck() {
return deck; }
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.green);
g.fillRect(0, 0, this.getWidth(), this.getHeight());//画矩形
}
}
package solitaire;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
public class PhotoMoving {//实现拖动图片的例子,
JLabel Pic; //图片,用于拖动
JFrame frame;
JPanel panel;
public PhotoMoving() {
frame=new JFrame("图片的拖动");
Pic = new JLabel(new
ImageIcon(getClass().getResource("/cards/13s.gif")));
panel=new JPanel();
panel.setBackground(Color.white);
panel.add(Pic); //事件
MyMouseInputAdapter listener=new MyMouseInputAdapter(); //鼠标事件处理
Pic.addMouseListener(listener); //增加标签的鼠标事件处理
Pic.addMouseMotionListener(listener);
frame.add(panel);
frame.setSize(800,800);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); }
class MyMouseInputAdapter extends MouseInputAdapter{
Point point=new Point(0,0); //坐标点
public void mousePressed(MouseEvent e) {
point=SwingUtilities.convertPoint(Pic,e.getPoint(),Pic.getParent()); //得到当前坐标点 }
public void mouseDragged(MouseEvent e) {
Point newPoint=SwingUtilities.convertPoint(Pic,e.getPoint(),Pic.getParent()); //转换坐标系统
Pic.setLocation(Pic.getX()+(newPoint.x-point.x),Pic.getY()+(newPoint.y-point.y)); //设置标签图片的新位置
point=newPoint; //更改坐标点
}
}
public static void main(String[] args){
new PhotoMoving();
}
}
package solitaire;
import java.awt.Dimension;
import javax.swing.JFrame;
public class Solitaire extends JFrame{
static protected GamePanel gamePanel;
public static final int PANEL_WIDTH = 640, PANEL_HEIGHT = 500; public Solitaire() { setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//点击退出后在程序内退出
gamePanel = new GamePanel();
gamePanel.setPreferredSize(new Dimension(PANEL_WIDTH, PANEL_HEIGHT));//设置大小
add(gamePanel);
pack();//适配部件的大小 }
public static void main(String[] args) {
new Solitaire().setVisible(true);
}
}
1.没能实现卡片拖拽时位置的动态变化,即现在看不到卡片的移动,只能看到移动成功前后的闪现。(有尝试过,但是在设置坐标或者什么乱七八糟的地方弄错了没成功,swing的使用还是挺困难的。。)
2.没有设置游戏难度,现在的卡片只是随机打乱,不能保证一定能通关。