【问题标题】:How to display cards in a Card Game in Java and have them clickable for selection?如何在 Java 中的纸牌游戏中显示纸牌并让它们可点击以供选择?
【发布时间】:2018-02-13 03:31:01
【问题描述】:

我正在用 Java 编写纸牌游戏,一切顺利,但我遇到了关于用户界面的问题。有 4 名玩家(1 人 3 AI)和 54 张牌。我想让每个玩家的牌在屏幕上绘制成 4 个方向(上左下右),并且基本上只有一个 MouseListener 用于人类牌,以便在玩移动之前选择一张牌(让牌向上移动一点,这样你知道它已被选中),而其他玩家的卡片背面已绘制并且不需要任何侦听器。我在做绝对定位,卡片应该是这样显示的:Image

目前我正在扩展JComponent 并将绘图测试到它的paintComponent 方法中。由于卡片被放置并绘制在另一张卡片的顶部,因此我无法想象使用坐标工作,而且由于我有一个用于卡片的 Card 类,因此无法将 Card 实例和绘制的图像连接起来。一个想法是为每个人卡使用JLabel,添加一个ImageIcon 作为卡片表示,并为每个标签添加一个MouseListener,以便轻松确定鼠标单击。但问题是如何让他们在每个玩家移动时绘制和更新,因为它会自动为其他玩家卡在paintComponent 中绘制为图像。我确实尝试在 paintComponent 方法中创建 JLabels 并将其添加到组件中,但我已经看到完全不建议这样做。 你对我应该遵循什么想法有什么建议吗?

谢谢和问候

【问题讨论】:

  • 你能把你的部分代码贴在你需要帮助的地方吗?
  • "无法连接 Card 实例和绘制的图像" - 为什么不呢? Cards 代表模型,视图负责渲染。然后视图需要跟踪每张卡的位置,我可能会使用Rectangle 作为基本选项,通过Map 将它们映射在一起。然后您可以在JComponent 上使用MouseListener 并使用Rectangle#contains 测试鼠标事件是否发生在给定的卡片中

标签: java


【解决方案1】:

无法连接 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;

很明显,它为我们提供了下一张卡片的水平位置

【讨论】:

  • "can't have them connected" 我应该说我似乎无法将它们连接起来,因为我想不出办法,但你帮了我很多,这完全是什么我一直在寻找。虽然我似乎无法理解 invalidate() 方法中的部分,尤其是坐标。你能解释一下你为什么做这样的公式,我怎么能想出我自己的?提前致谢
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-06-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多