Java Memory Game Source Code

How To Create Memory Game in Java Swing Using NetBeans

How To Create Memory Game in Java Swing Using NetBeans


In this Java Tutorial we will see How to Create a memory card game using Java Swing with Netbeans.

The Game Features:
- Multiple Difficulty Levels: 4x4, 6x6, and 8x8 grid sizes
Card Matching: Automatic pair detection.
Move counter and timer: Track your progress and improve your memory skills.

What We Are Gonna Use In This Project:

- Java Programming Language.
- NetBeans Editor.




if you want the source code click on the download button below




Project Source Code:


    /**
     * Initializes the game timer that updates every second
     */
private void initializeTimer() {
       
        timer = new Timer();
        timer.scheduleAtFixedRate(new TimerTask() {
            
            @Override
            public void run() {
                elapsedTime++;
                int minutes = elapsedTime / 60;
                int seconds = elapsedTime % 60;
                SwingUtilities.invokeLater(() -> 
                    timerLabel.setText(String.format("Time: %02d:%02d", minutes, seconds)));
            }
            
        }, 0, 1000);
       
   }


    /**
     * Method to initialize the game
     */
    private void initializeGame() {
        
        // Reset game state
        timer.cancel();
        timer.purge();
        elapsedTime = 0;
        timerLabel.setText("Time: 00:00");
        moves = 0;
        movesLabel.setText("Moves: 0");
        
        // Clear the grid and flipped cards
        gridPanel.removeAll();
        flippedCards.clear();
        
        // Create new cards
        cards = new Card[gridSize * gridSize];
        List<String> iconList = new ArrayList<>(
                Arrays.asList(ICONS).subList(0, (gridSize * gridSize) / 2)
        );
        iconList.addAll(iconList); // Duplicate icons for pairs
        Collections.shuffle(iconList);
        
        // Initialize cards and add them to the grid
        IntStream.range(0, cards.length).forEach(i -> {
            cards[i] = new Card(iconList.get(i), COLORS[i % COLORS.length]);
            gridPanel.add(cards[i]);
        });
        
        // Refresh the grid and restart the timer
        gridPanel.revalidate();
        gridPanel.repaint();
        initializeTimer();
        SwingUtilities.invokeLater(this::updateCardSizes);
        
    }
    
    

    /**
     * Creates a panel to display game statistics (moves and time)
     */
    private JPanel createStatsPanel() {
        
        // Create a panel for game statistics
        JPanel statsPanel = new JPanel();
        statsPanel.setLayout(new BoxLayout(statsPanel, BoxLayout.Y_AXIS));
        statsPanel.setOpaque(false);
        statsPanel.setBorder(BorderFactory.createCompoundBorder(
            new LineBorder(new Color(255, 255, 255, 50), 1, true),
            new EmptyBorder(15, 15, 15, 15)
        ));
        
        // Add title and stats labels to the panel
        JLabel statsTitle = createLabel("Game Stats", 20, Font.BOLD);
        statsPanel.add(statsTitle);
        statsPanel.add(Box.createVerticalStrut(10));
        movesLabel = createLabel("Moves: 0", 18, Font.PLAIN);
        timerLabel = createLabel("Time: 00:00", 18, Font.PLAIN);
        
        statsPanel.add(movesLabel);
        statsPanel.add(Box.createVerticalStrut(5));
        statsPanel.add(timerLabel);
        statsPanel.add(Box.createVerticalStrut(5));
        
        return statsPanel;
            
    }


    

    /**
     * Method called when all cards are matched and the game is won
     */
    private void gameWon() {
        
            // Stop the game timer
            timer.cancel();
            
            // Show the congratulations dialog
            SwingUtilities.invokeLater(() -> {
                showCongratulationsDialog();
            });
        
    }
    

    /**
     * Method to display the congratulations dialog when the game is won
     */
    private void showCongratulationsDialog() {
        
         // Create a modal dialog
        JDialog dialog = new JDialog(this, "Congratulations!", true);
        dialog.setLayout(new BorderLayout());
        dialog.setSize(300, 200);
        dialog.setLocationRelativeTo(this);
        
         // Create a custom panel with gradient background
         JPanel panel = new JPanel() {
             
              @Override
              protected void paintComponent(Graphics g) {
                  
                  super.paintComponent(g);
                  Graphics2D g2d = (Graphics2D) g;
                  g2d.setRenderingHint(
                          RenderingHints.KEY_RENDERING, 
                          RenderingHints.VALUE_RENDER_QUALITY
                  );
                  
                  int w = getWidth(), h = getHeight();
                  Color color1 = new Color(41, 128, 185);
                  Color color2 = new Color(0, 0, 0);
                  
                  GradientPaint gp = new GradientPaint(0, 0, color1, w, h, color2);
                  g2d.setPaint(gp);
                  g2d.fillRect(0, 0, w, h);

              };
             
         };
         
         panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
         panel.setBorder(new EmptyBorder(20, 20, 20, 20));
         
         // Create and add labels for congratulations and stats
        JLabel congratsLabel = createLabel("Puzzle Solved!", 24, Font.BOLD);
        JLabel statsLabel = createLabel("Moves: " + moves + " | Time: " + 
                formatTime(elapsedTime), 16, Font.PLAIN);
        
         // Create a close button
        JButton closeButton = createButton("Close", new Color(231, 76, 60));
        closeButton.addActionListener(e -> dialog.dispose());
        
         // Add components to the panel
        panel.add(congratsLabel);
        panel.add(Box.createVerticalStrut(10));
        panel.add(statsLabel);
        panel.add(Box.createVerticalStrut(20));
        panel.add(closeButton);
        
         // Add the panel to the dialog and display it
        dialog.add(panel, BorderLayout.CENTER);
        dialog.setVisible(true);

    }
    
    

    /**
     * Method to check for a match between flipped cards
     */
    private void checkMatch() {
        
         // Increment the number of moves and update the moves label
         moves++;
         movesLabel.setText("Moves: " + moves);
         
         // Get the two flipped cards
         Card card1 = flippedCards.get(0);
         Card card2 = flippedCards.get(1);
         
         // Check if the icons of the two cards match
        if (card1.icon.equals(card2.icon)) {
            
             // Cards match
            // Mark both cards as matched
            card1.isMatched = true;
            card2.isMatched = true;
            
            // Start match animation for both cards
            card1.matchAnimation();
            card2.matchAnimation();
            
             // Clear the list of flipped cards
             flippedCards.clear();
             
            // Check if all cards are matched, if so, end the game
            if (Arrays.stream(cards).allMatch(card -> card.isMatched)) {
                 gameWon();
            }
            
        } else{
            
              // Cards don't match
            // Schedule a timer to flip the cards back after a 1-second delay
            Timer flipBackTimer = new Timer();
            flipBackTimer.schedule(new TimerTask() {
                @Override
                public void run() {
                
                    // Reset both cards to their original state
                    card1.reset();
                    card2.reset();
                    // Clear the list of flipped cards
                    flippedCards.clear();
                    
                }
                
            }, 1000); // 1000 milliseconds = 1 second
            
        }
        
    }
    


    /**
     * Updates the grid size based on selected difficulty level
     */
   private void setGridSize(String difficulty) {
       
         gridSize = switch (difficulty) {
            case "4x4" -> 4;
            case "6x6" -> 6;
            case "8x8" -> 8;
            default -> 4;       
         };
        
         gridPanel.setLayout(new GridLayout(gridSize, gridSize, 0, 0));
   }
   
    

    /**
     * Method to format the elapsed time in MM:SS format
     */
    private String formatTime(int seconds) {
        int minutes = seconds / 60;
        int remainingSeconds = seconds % 60;
        return String.format("%02d:%02d", minutes, remainingSeconds); 
    }
    


   // ****  Inner class representing a card in the memory game
    private class Card extends JPanel {
        
            // Properties of the card
            private String icon;          // The icon (or symbol) displayed on the card when flipped
            private Color color;          // The background color of the card when flipped
            private boolean isFlipped = false;  // Indicates if the card is currently flipped
            private boolean isMatched = false;  // Indicates if the card has been matched
            private float rotation = 0;   // Current rotation angle for flip animation
            private float scale = 1.0f;   // Current scale factor for bounce and match animations
            private java.util.Timer animationTimer; // Timer for controlling animations
            // List to store particle effects
            private List<Particle> particles = new ArrayList<>();
            
            
            // Constructor for the Card class
            public Card(String icon, Color color) {
                
                  this.icon = icon;
                  this.color = color;
                  
                  // Set background color based on dark mode setting
                  setBackground(isDarkMode ? new Color(44, 62, 80) : BUTTON_PRIMARY);
                  // Set border color based on dark mode setting
                  setBorder(BorderFactory.createLineBorder(
                          isDarkMode ? MAIN_COLOR : BUTTON_SECONDARY, 
                          2, true)
                  );
                  
                  
                  // Add mouse listener for card flipping
                  addMouseListener(new MouseAdapter() {
                        @Override
                        public void mouseClicked(MouseEvent e) {
       // Only flip the card if it's not already flipped, not matched, and less than 2 cards are flipped
                            if (!isFlipped && !isMatched && flippedCards.size() < 2) {
                                flip();
                            }
                        }
                    });

            }
            
           // Method to reset the card state
           public void reset() {
                    
                  if (animationTimer != null) {
                        animationTimer.cancel();  // Stop any ongoing animations
                  }
                  
                  isFlipped = false;
                  isMatched = false;
                  rotation = 0;
                  scale = 1.0f;
                  
                  // Reset background color based on dark mode setting
                  setBackground(isDarkMode ? new Color(44, 62, 80) : BUTTON_PRIMARY);
                  repaint();  // Redraw the card
               
           }
           
          // Method to flip the card
          public void flip() {
              
             isFlipped = true;
             flippedCards.add(this);  // Add this card to the list of flipped cards
             
            if (animationTimer != null) {
                animationTimer.cancel();  // Stop any ongoing animations
            }
            animationTimer = new Timer();
            
             // Start flip animation
            animationTimer.scheduleAtFixedRate(new TimerTask() {
                
                  @Override
                  public void run() {
                      
                      rotation += 10;  // Increase rotation by 10 degrees each frame
                      if (rotation >= 360) {
                          rotation = 360;
                          animationTimer.cancel();  // Stop animation when full rotation is complete
                          if (flippedCards.size() == 2) {
                                checkMatch();  // Check for a match if two cards are flipped
                            }
                      }
                      
                      repaint();  // Redraw the card
                      
                  }
                
            }, 0, 16);  // Run every 16ms
              
          }
            
        
          // Method for match animation
         public void matchAnimation() {
             
             if (animationTimer != null) {
                    animationTimer.cancel();  // Stop any ongoing animations
             }
             
             animationTimer = new Timer();
             final float[] scales = {1.2f, 0.9f, 1.1f, 1.0f};  // Scale factors for match effect
             final int[] durations = {150, 100, 100, 100};  // Durations for each scale change
             
             // Schedule each scale change
             for (int i = 0; i < scales.length; i++) {
                 
                   final int index = i;
                   
                   animationTimer.schedule(new TimerTask() {
                       @Override
                       public void run() {
                       
                            scale = scales[index];
                            repaint();  // Redraw the card
                            if (index == scales.length - 1) {
                                startParticleEffect();  // Start particle effect after last scale change
                            }
                           
                       }

                   }, Arrays.stream(durations).limit(i + 1).sum());  // Schedule based on cumulative duration
                 
             }
             
         }
          
          
         // Method to start particle effect animation
         private void startParticleEffect() {
             
            synchronized (particles) {
                
                particles.clear();  // Clear any existing particles
                
                for (int i = 0; i < 500; i++) {
                    particles.add(new Particle(getWidth() / 2, getHeight() / 2));  // Create new particles
                }
                
            }
            
             Timer particleTimer = new Timer();
             
             // Animate particles
             particleTimer.scheduleAtFixedRate(new TimerTask() {
                 
                 int frames = 0;
                 
                @Override
                public void run() {
                
                    synchronized (particles) {
                        particles.forEach(Particle::update);  // Update each particle's position
                    }
                    
                    SwingUtilities.invokeLater(() -> repaint());  // Redraw the card
                    
                    frames++;
                    
                    if (frames > 60) {  // Stop animation after 60 frames (1 second at 60 FPS)
                        particleTimer.cancel();
                        synchronized (particles) {
                            particles.clear();  // Remove all particles
                        }
                        SwingUtilities.invokeLater(() -> repaint());  // Final redraw
                    }
                    
                }
                 
             }, 0, 16);  // Run every 16ms
            
             
         }
          
          
         // Override paintComponent to draw the card  
         @Override
         protected void paintComponent(Graphics g) {
             
               // Call the superclass method to ensure proper painting
              super.paintComponent(g);
              
               // Cast Graphics to Graphics2D for more advanced rendering options
               Graphics2D g2d = (Graphics2D) g;
               
               // Enable antialiasing for smoother rendering of shapes and text
               g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
                       RenderingHints.VALUE_ANTIALIAS_ON
               );
               
                // Get the current width and height of the component
                int width = getWidth();
                int height = getHeight();
                
                // Save the current transformation for later restoration
                AffineTransform oldTransform = g2d.getTransform();
                
                // Apply rotation and scale transformations
                // First, move the origin to the center of the component
                g2d.translate(width / 2, height / 2);
                
                // Apply rotation (convert degrees to radians)
                g2d.rotate(Math.toRadians(rotation));
                // Apply scaling
                g2d.scale(scale, scale);
                // Move the origin back to the top-left corner
                g2d.translate(-width / 2, -height / 2);
                
                if (rotation < 90) {
                    
                     // Unflipped state (back of the card)
                    // Fill the card with the background color
                    g2d.setColor(getBackground());
                    g2d.fillRoundRect(0, 0, width, height, 15, 15);
                    
                    // Draw the "?" symbol
                    g2d.setColor(isDarkMode ? Color.LIGHT_GRAY : Color.WHITE);
                    g2d.setFont(new Font("Arial", Font.BOLD, 48));
                    g2d.drawString("?", width / 2 - 15, height / 2 + 15);
                    
                } else{
                    
                     // Flipped state (front of the card)
                    // Fill the card with its color
                    g2d.setColor(color);
                    g2d.fillRoundRect(0, 0, width, height, 15, 15);
                    
                     // Draw the icon (symbol) on the card
                    g2d.setColor(Color.WHITE);
                    g2d.setFont(new Font("Arial", Font.BOLD, 48));
                    FontMetrics fm = g2d.getFontMetrics();
                    
                    // Calculate position to center the icon
                    int x = (width - fm.stringWidth(icon)) / 2;
                    int y = ((height - fm.getHeight()) / 2) + fm.getAscent();
                    g2d.drawString(icon, x, y);
                    
                }
                
                 // Restore the original transformation
                g2d.setTransform(oldTransform);
                
                 // Draw particles (for animation effects)
                 // Create a copy of the particles list to avoid concurrent modification
                 List<Particle> particlesCopy;
                  synchronized (particles) {
                        particlesCopy = new ArrayList<>(particles);
                  }
                  
                  // Draw each particle
                  particlesCopy.forEach(p -> {
                        if (p != null) {
                            p.draw(g2d);
                        }
                    });

         }
          
          
    }



// Inner class representing a particle for visual effects
    private class Particle {

         // Properties of the particle
        private double x, y;      // Current position of the particle
        private double vx, vy;    // Velocity of the particle in x and y directions
        private double size;      // Size of the particle
        private Color color;      // Color of the particle
        private double alpha = 1.0; // Transparency of the particle (1.0 = fully opaque)
        
         // Constructor for the Particle class
        public Particle(int x, int y) {
            
             // Set the initial position of the particle
            this.x = x;
            this.y = y;
            
             // Calculate a random angle and speed for the particle's movement
            double angle = Math.random() * 2 * Math.PI;  // Random angle in radians
            double speed = 1 + Math.random() * 3;        // Random speed between 1 and 4
            
            // Calculate the x and y components of the velocity based on angle and speed
            this.vx = Math.cos(angle) * speed;
            this.vy = Math.sin(angle) * speed;
            
             // Set a random size for the particle between 2 and 6
             this.size = 2 + Math.random() * 4;
             
             // Assign a random color to the particle
            this.color = new Color(
                (float) Math.random(),  // Random red component
                (float) Math.random(),  // Random green component
                (float) Math.random()   // Random blue component
            );
            
        }
        
        
        // Method to update particle position and properties
         public void update() {
             
               // Update the particle's position based on its velocity
                x += vx;
                y += vy;
                
                // Apply a gravity effect by increasing the downward velocity
                vy += 0.1; // Gravity effect
                
                // Shrink the particle over time
                size *= 0.97; // Reduce size by 3% each update
                
                 // Fade out the particle over time
                 alpha *= 0.97; // Reduce alpha by 3% each update
             
         }
         
         // Method to draw the particle
         public void draw(Graphics2D g2d) {
             
                // Set the transparency (alpha) for drawing the particle
                g2d.setComposite(AlphaComposite.getInstance(
                        AlphaComposite.SRC_OVER, (float) alpha)
                );
                
                 // Set the color of the particle
                 g2d.setColor(color);
                 
                // Draw the particle as a filled circle (ellipse)
                // The position is adjusted to center the particle on its x,y coordinates
                g2d.fill(new Ellipse2D.Double(x - size / 2, y - size / 2, size, size));

         }

    }
    



The Final Result:


Java Memory Game Source Code

Java Memory Game Dark Mode

Java Memory Game

Java Swing Memory Game

Java Swing Memory Game Source Code

Java Swing Memory Cards Game Source Code

Memory Cards Game In Java Netbeans

Memory Cards Game In Java Swing

Memory Game In Java Swing




if you want the full source code click on the download button below




disclaimer: you will get the source code with the database script and to make it work in your machine is your responsibility and to debug any error/exception is your responsibility this project is for the students who want to see an example and read the code not to get and run.





Share this

Related Posts

Latest
Previous
Next Post »