无法连接 Card 实例和绘制的图像
为什么不呢?基本上,Card 是一种数据状态。 Hand 只是 Cards 的集合。这些基本上建立了模型的概念。数据本身与显示它的视图是分开的。
这意味着,在大多数情况下,视图变得愚蠢。它只是获取模型并以某种方式呈现它,允许用户与之交互。
在您的情况下,您可以使用Rectangle 来表示物理卡的边界。单击时,您将遍历手并使用Rectangle#contains 测试鼠标是否在每个Rectangle(映射回Card)的范围内被单击
现在,以下示例“非常”基本。它只关注单个玩家的手牌,但它应该为您提供可以推动这个概念的想法。
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class SimpleCards {
public static void main(String[] args) {
new SimpleCards();
}
public SimpleCards() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
Deck.INSTANCE.shuffle();
List<Hand> players = new ArrayList<>(5);
for (int index = 0; index < 5; index++) {
players.add(new Hand());
}
for (int index = 0; index < 5; index++) {
for (Hand hand : players) {
hand.add(Deck.INSTANCE.pop());
}
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new GamePane(players));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class Hand {
private List<Card> cards;
public Hand() {
cards = new ArrayList<>(25);
}
public void add(Card card) {
cards.add(card);
}
public int size() {
return cards.size();
}
public Iterable<Card> cards() {
return cards;
}
public Iterable<Card> reveresed() {
List<Card> reversed = new ArrayList<>(cards);
Collections.reverse(reversed);
return reversed;
}
}
public static class Card {
private Suit suit;
private Face face;
public Card(Suit suit, Face face) {
this.suit = suit;
this.face = face;
}
public Suit getSuit() {
return suit;
}
public Face getFace() {
return face;
}
}
public enum Deck {
INSTANCE;
private List<Card> cards;
private List<Card> playDeck;
private Deck() {
cards = new ArrayList<>(52);
for (Suit suit : Suit.items) {
for (Face face : Face.items) {
cards.add(new Card(suit, face));
}
}
playDeck = new ArrayList<>(cards);
}
public void shuffle() {
playDeck.clear();
playDeck.addAll(cards);
Collections.shuffle(playDeck);
}
public Card pop() {
if (playDeck.isEmpty()) {
return null;
}
return playDeck.remove(0);
}
public void push(Card card) {
playDeck.add(card);
}
}
enum Face {
ONE("1"), TWO("2"), THREE("3"), FOUR("4"), FIVE("5"), SIX("6"), SEVEN("7"), EIGHT("8"), NINE("9"), JACK("J"), QUEEN("Q"), KING("K");
private String value;
private Face(String value) {
this.value = value;
}
public String getValue() {
return value;
}
private static Face[] items = new Face[]{
ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, JACK, QUEEN, KING
};
}
enum Suit {
CLUBS("♣"), DIAMONDS("♦"), HEARTS("♥"), SPADES("♠");
private String value;
private Suit(String value) {
this.value = value;
}
public String getValue() {
return value;
}
public static Suit[] items = new Suit[]{
CLUBS, DIAMONDS, HEARTS, SPADES
};
}
public class GamePane extends JPanel {
private List<Hand> players;
private Map<Card, Rectangle> mapCards;
private Card selected;
public GamePane(List<Hand> players) {
this.players = players;
mapCards = new HashMap<>(players.size() * 5);
addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (selected != null) {
Rectangle bounds = mapCards.get(selected);
bounds.y += 20;
repaint();
}
selected = null;
// This is done backwards, as the last card is on
// top. Of course you could render the cards
// in reverse order, but you get the idea
for (Card card : players.get(0).reveresed()) {
Rectangle bounds = mapCards.get(card);
if (bounds.contains(e.getPoint())) {
selected = card;
bounds.y -= 20;
repaint();
break;
}
}
}
});
}
@Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
@Override
public void invalidate() {
super.invalidate();
mapCards.clear();
Hand hand = players.get(0);
int cardHeight = (getHeight() - 20) / 3;
int cardWidth = (int) (cardHeight * 0.6);
int xDelta = cardWidth / 2;
int xPos = (int) ((getWidth() / 2) - (cardWidth * (hand.size() / 4.0)));
int yPos = (getHeight() - 20) - cardHeight;
for (Card card : hand.cards()) {
Rectangle bounds = new Rectangle(xPos, yPos, cardWidth, cardHeight);
mapCards.put(card, bounds);
xPos += xDelta;
}
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
Hand hand = players.get(0);
for (Card card : hand.cards) {
Rectangle bounds = mapCards.get(card);
System.out.println(bounds);
if (bounds != null) {
g2d.setColor(Color.WHITE);
g2d.fill(bounds);
g2d.setColor(Color.BLACK);
g2d.draw(bounds);
Graphics2D copy = (Graphics2D) g2d.create();
paintCard(copy, card, bounds);
copy.dispose();
}
}
g2d.dispose();
}
protected void paintCard(Graphics2D g2d, Card card, Rectangle bounds) {
g2d.translate(bounds.x + 5, bounds.y + 5);
g2d.setClip(0, 0, bounds.width - 5, bounds.height - 5);
String text = card.getFace().getValue() + card.getSuit().getValue();
FontMetrics fm = g2d.getFontMetrics();
g2d.drawString(text, 0, fm.getAscent());
}
}
}
虽然我似乎无法理解 invalidate() 方法中的部分,尤其是坐标。你能解释一下你为什么做这样的公式,我怎么能想出我自己的?提前致谢
从一张纸开始,画一个代表屏幕的矩形,然后按照你想要的方式绘制卡片 - 说真的,对于任何适当复杂的 UI,我总是从一张纸开始,你可以随意涂鸦想法,执行计算,执行演练,基本上,集中你的意图 - 并丢弃很多不起作用的东西。我什至知道会使用剪纸
invalidate 在组件的大小或位置发生变化时被调用。你也可以用ComponentListener,我只是走最简单的路线(注意,我也很懒,invalidate可以快速连续调用多次)
首先,我们需要知道一张卡片的高度,由此我们可以计算出宽度与高度的比率
int cardHeight = (getHeight() - 20) / 3;
int cardWidth = (int) (cardHeight * 0.6);
您可以花一些时间制定一种算法,确保卡片始终显示(即根据最小的组件高度/宽度计算卡片的高度/宽度,但我很简单) - 这意味着,如果组件的宽度足够小,卡片会溢出可用空间。
接下来,我们需要确定每张卡与前一张的偏移量...
int xDelta = cardWidth / 2;
在这种情况下,每张牌将与第二张牌重叠一半
接下来,我们需要计算第一张牌的起始位置……
int xPos = (int) ((getWidth() / 2) - (cardWidth * (hand.size() / 4.0)));
(getWidth() / 2) 计算组件的水平中心位置,这将确保卡片围绕组件的水平中心布置。
(cardWidth * (hand.size() / 4.0) 基本上计算了布置所有卡片所需的总宽度并将其分成两半,因为一半将出现在水平中心的左侧,另一半出现在右侧。好的,我意识到这可能没有意义,但请记住,每张卡片都与前一张卡片重叠宽度的一半,因此,我们不能除以 2,我们需要将其除以 4 (2 x 2)。
因此,cardWidth * hand.size() 将提供并排放置的所有卡片的总宽度。 (cardWidth * hand.size()) / 2 提供(显然)一半的数量,当从水平中心减去时,可以用作第一张卡片原点的基础。 ((cardWidth * hand.size()) / 2) / 2 将允许卡片重叠一半宽度。
最后,我们计算卡片的垂直原点...
int yPos = (getHeight() - 20) - cardHeight;
这很简单,一目了然。
在循环中,当我们计算每张卡片的位置时,我们将下一张卡片偏移xDelta
xPos += xDelta;
很明显,它为我们提供了下一张卡片的水平位置