Python Tkinter Puzzle Game Source Code

How To Create Image Puzzle Game in Python Tkinter

How To Create Image Puzzle Game in Python Tkinter


In this Python Tutorial we will see How to Create a picture puzzle game using Python And Tkinter.
This project demonstrates how to build a complete image puzzle game where players can upload their own pictures and solve them as puzzles with customizable difficulty levels. 
The game features a modern dark theme, smooth piece-swapping animations, move counting, and a built-in timer.

The Game Features:
- Upload your own images: Players can upload any image (PNG, JPG, JPEG, GIF, or BMP) and instantly transform it into a playable puzzle. The game automatically resizes and crops images to fit perfectly within the puzzle grid.

Multiple difficulty levels: With grid sizes ranging from 4x4 (16 pieces) to 8x8 (64 pieces), players can choose their preferred challenge level.

Move counter and timer: The application tracks moves and elapsed time, adding a competitive element to the experience.

Smooth piece-swapping animations: The piece-swapping feature includes a modern animations with easing functions, making the gameplay feel cool.

- Winning detection and celebration: Displays final statistics in a congratulations window.

How the Game Works:
When you start the game, you'll see a window split into two parts:
- A sidebar with controls and game stats.
- The main puzzle grid area.

You can upload any image, and the game will automatically:
- Resize it to fit the game window.
- Split it into equal pieces based on your chosen difficulty.
- Shuffle the pieces for you to solve.



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




Project Source Code:

     - Opens file chooser and loads selected image to start puzzle.
     

def load_and_start_puzzle(self):
# Open file dialog to select an image
file_path = filedialog.askopenfilename(
filetypes=[("Image files", "*.png *.jpg *.jpeg *.gif *.bmp")])

# Check if user selected a file
if file_path:
# Load the selected image and start the game
self.original_image = Image.open(file_path)
self.reset_game() # Reset and start the game with the new image

    

     - Reset game state variables.

def reset_game(self):
self.moves = 0 # Reset move counter
self.elapsed_time = 0 # Reset timer
self.moves_label.config(text="Moves: 0") # Update moves display
self.timer_label.config(text="Time: 00:00") # Update timer display
self.stop_timer() # Stop any existing timer

# Clear previous game elements
self.canvas.delete("all") # Clear the canvas
self.label_list.clear() # Clear the list of puzzle pieces
self.correct_order.clear() # Clear the correct order list

# Resize the uploaded image to fit the puzzle area
resized_image = self.original_image.resize((self.image_size, self.image_size),
                        Image.LANCZOS)

# Slice the image into puzzle pieces
self.sliced_images = self.slice_image(resized_image)
self.correct_order = self.sliced_images.copy() # Store the correct order
random.shuffle(self.sliced_images) # Shuffle pieces to start the game

# Calculate the size of each puzzle piece
slice_width = self.image_size // self.grid_size
slice_height = self.image_size // self.grid_size

# Place sliced images on the canvas
for i, img in enumerate(self.sliced_images):
row = i // self.grid_size # Calculate row position
col = i % self.grid_size # Calculate column position
x = col * slice_width # Calculate x-coordinate
y = row * slice_height # Calculate y-coordinate

# Create the image on the canvas and store its reference
image_item = self.canvas.create_image(x, y, image=img, anchor="nw")
self.label_list.append(image_item) # Add to the list of pieces

# Bind click event to each puzzle piece
self.canvas.tag_bind(image_item, "<Button-1>",
lambda event, item=image_item: self.on_click(item))

# Start the game timer
self.start_timer()

    

     - Slice the image into a grid of smaller images.

def slice_image(self, image):
slices = [] # List to store sliced images
width, height = image.size # Get image dimensions

# Calculate dimensions of each slice
slice_width = width // self.grid_size
slice_height = height // self.grid_size

# Create slices by cropping the original image
for row in range(self.grid_size):
for col in range(self.grid_size):
# Calculate the coordinates for cropping
left = col * slice_width
upper = row * slice_height
right = left + slice_width
lower = upper + slice_height

# Crop the image and convert to PhotoImage for tkinter display
slice_img = image.crop((left, upper, right, lower))
photo_img = ImageTk.PhotoImage(slice_img)
slices.append(photo_img) # Add to slices list

return slices


    

     - Handles click events on puzzle pieces.

def on_click(self, item):
# Handle click events on puzzle pieces
if self.first_selected is None:
# First piece selected
self.first_selected = item # Store the selected piece
self.draw_selection_outline(item) # Draw outline around the selected piece
elif self.second_selected is None and item != self.first_selected:
# Second piece selected (and different from first)
self.second_selected = item # Store the second piece
self.canvas.delete("selection_outline") # Clear existing outlines
self.draw_selection_outline(item) # Draw outline around second piece

# Start the swap animation after a short delay
self.root.after(100, self.swap_slices_with_animation)




     - Draw a highlight outline around the selected puzzle piece.

def draw_selection_outline(self, item):
coords = self.canvas.coords(item) # Get coordinates of the piece
if coords:
x, y = coords[0], coords[1] # Extract x and y coordinates
# Calculate width and height of the piece
width = self.image_size // self.grid_size
height = self.image_size // self.grid_size

# Create a rectangle outline around the selected piece
self.selection_outline = self.canvas.create_rectangle(
x, y, x + width, y + height,
outline="#f9c74f", width=3, tags="selection_outline")



     - Animates the swapping of two puzzle pieces.

def swap_slices_with_animation(self):
# Animate the swapping of two selected puzzle pieces
if self.first_selected and self.second_selected:
# Get current positions of both pieces
start_pos1 = self.canvas.coords(self.first_selected)
start_pos2 = self.canvas.coords(self.second_selected)

# Set target positions (swap them)
end_pos1 = start_pos2
end_pos2 = start_pos1

# Start the animation for both pieces
self.animate_swap(self.first_selected, start_pos1, end_pos1, 0)
self.animate_swap(self.second_selected, start_pos2, end_pos2, 0)

# Increment move counter and update display
self.moves += 1
self.moves_label.config(text=f"Moves: {self.moves}")

# Finalize the swap after the animation completes
self.root.after(500, self.finalize_swap)

    

     - Piece Swapping Animations.

def animate_swap(self, item, start_pos, end_pos, step):
# Perform a single step of the swap animation
if step <= 10: # Animation has 10 steps
# Calculate progress with easing function for smooth animation
progress = self.ease_in_out_quad(step / 10)

# Calculate new position
new_x = start_pos[0] + (end_pos[0] - start_pos[0]) * progress
new_y = start_pos[1] + (end_pos[1] - start_pos[1]) * progress

# Move the piece to the new position
self.canvas.coords(item, new_x, new_y)

# Schedule the next animation step
self.root.after(50, lambda:
                            self.animate_swap(item, start_pos, end_pos, step + 1))

    

     - Completes the swap operation after animation.

def finalize_swap(self):
# Complete the swap operation after animation
# Find indices of selected pieces in the label list
idx1 = self.label_list.index(self.first_selected)
idx2 = self.label_list.index(self.second_selected)

# Delete the selection outline
if hasattr(self, 'selection_outline'):
self.canvas.delete(self.selection_outline)

# Swap the images in the label list
self.label_list[idx1], self.label_list[idx2] = self.label_list[idx2],
                self.label_list[idx1]

# Reset selection variables
self.first_selected = None
self.second_selected = None

# Check if the puzzle is solved
self.check_if_solved()

    

     - Easing function for smooth animation.

@staticmethod
def ease_in_out_quad(t):
# Easing function for smooth animation
# Makes animation start and end slowly, with faster movement in the middle
return 2 * t * t if t < 0.5 else 1 - pow(-2 * t + 2, 2) / 2

    

     - Check if puzzle is solved.

def check_if_solved(self):
# Check if the puzzle is solved
# Get current order of images from canvas
current_order = [self.canvas.itemcget(item, 'image')
                            for item in self.label_list]
# Get correct order as strings for comparison
correct_order = [str(img) for img in self.correct_order]

# Compare current order with correct order
if current_order == correct_order:
self.stop_timer() # Stop the timer
# Show congratulations message after a short delay
self.root.after(100, self.show_congratulations)


     - Shows congratulation dialog when puzzle is solved.

def show_congratulations(self):
# Display a congratulations window when the puzzle is solved
congrats_window = tk.Toplevel(self.root) # Create new window
congrats_window.title("Congratulations!") # Set window title
congrats_window.geometry("400x350") # Set window size
congrats_window.configure(bg="#313244") # Set background color

# Add "Puzzle Solved!" title
tk.Label(congrats_window, text="Puzzle Solved!",
font=("Montserrat", 28, "bold"),
                        bg="#313244", fg="#cba6f7").pack(pady=(20, 10))

# Display the number of moves and time taken
tk.Label(congrats_window, text=f"Moves: {self.moves}",
font=("Montserrat", 20), bg="#313244", fg="#b4befe").pack(pady=5)

tk.Label(congrats_window, text=f"Time: {self.format_time(self.elapsed_time)}",
font=("Montserrat", 20), bg="#313244", fg="#b4befe").pack(pady=5)

# Display a congratulatory message
message = "Great job! You've successfully completed the puzzle."
tk.Label(congrats_window, text=message, font=("Montserrat", 16),
bg="#313244", fg="#a6e3a1", wraplength=350).pack(pady=(10, 20))

# Create a close button for the congratulations window
close_button = tk.Button(
congrats_window, text="Close", font=("Montserrat", 16, "bold"),
bg="#f9c74f", fg="#1e1e2e", activebackground="#f3a712",
activeforeground="#1e1e2e", bd=0, command=congrats_window.destroy,
width=20, height=2)

close_button.pack(pady=10)

    

     - Shuffle the pieces.

def shuffle_grid(self):
# Shuffle the puzzle pieces
if hasattr(self, 'sliced_images'): # Check if puzzle is loaded
random.shuffle(self.sliced_images) # Shuffle the pieces

# Update the images on the canvas
for i, img in enumerate(self.sliced_images):
self.canvas.itemconfig(self.label_list[i], image=img)

# Reset game state
self.moves = 0 # Reset moves counter
self.elapsed_time = 0 # Reset timer
self.moves_label.config(text="Moves: 0") # Update moves display
self.timer_label.config(text="Time: 00:00") # Update timer display
self.stop_timer() # Stop the timer before starting again
self.start_timer() # Start the timer again

    

     - Change the game difficulty

def change_difficulty(self, *args):
# Change the grid size based on selected difficulty
self.grid_size = int(self.difficulty_var.get()[0]) # Get first character as number
if hasattr(self, 'original_image'): # Check if an image is loaded
self.reset_game() # Reset the game with the new grid size
    

The Final Result:

Python Tkinter Puzzle Game Source Code

Python Tkinter Puzzle Game

Python Puzzle Game

Puzzle Game Using Python Tkinter With Source Code

Puzzle Game Using Python Tkinter

Image Puzzle Game Using Python Tkinter With Source Code

Image Puzzle Game In Python Tkinter




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




disclaimer: you will get the source code 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.





Python Tkinter Donut Chart

How to Create a Donut Chart In Python Tkinter

How to Create a Donut Chart In Python Tkinter


In this Python tutorial we will create a donut chart using the Tkinter library for the graphical user interface. 
The chart represents data slices with different colors and includes a legend to describe each slice.

What We Are Gonna Use In This Project:

- Python Programming Language.
- Tkinter for GUI.
- VS Code Editor.




Project Source Code:


import tkinter as tk
from tkinter import Canvas


class DonutChart(tk.Tk):
def __init__(self):
# Initialize the Tkinter application
super().__init__()
self.title("Donut Chart")
self.geometry("550x400")

# Create and pack the DonutChartPanel
self.donut_chart_panel = DonutChartPanel(self)
self.donut_chart_panel.pack(fill=tk.BOTH, expand=True)

# Start the Tkinter main loop
self.mainloop()


class DonutChartPanel(Canvas):
def __init__(self, master=None):
# Initialize the DonutChartPanel as a Canvas with a light background color
super().__init__(master, bg="#f0f0f0")
# color palette for the donut slices
self.slice_colors = ["#65AEDD", "#FFB74D", "#66BB6A", "gray"]
# Data for the donut slices
self.data = [40, 30, 20, 10]
# Draw the donut chart
self.draw_donut_chart()

def draw_donut_chart(self):
# Get the width and height of the canvas
width = self.winfo_reqwidth()
height = self.winfo_reqheight()
# Calculate the diameter of the donut chart
diameter = min(width, height) - 20
outer_radius = diameter / 2
inner_radius = outer_radius * 0.5
x = (width - diameter) / 2
y = (height - diameter) / 2
start_angle = 0

# Draw each slice of the donut chart
for i, value in enumerate(self.data):
arc_angle = int(value / 100 * 360)
# Create an arc for the donut slice
self.create_arc(x, y, x + diameter, y + diameter, start = start_angle,
            extent=arc_angle, fill=self.slice_colors[i], outline="black")
start_angle += arc_angle


# Draw the inner circle to create the donut effect
self.create_oval(x + outer_radius - inner_radius, y +
                         outer_radius - inner_radius,
x + outer_radius + inner_radius, y +
                         outer_radius + inner_radius,
fill="#f0f0f0", outline="black")

# Draw legend for each slice
legend_x = width - 110
legend_y = 20

for i, value in enumerate(self.data):
# Draw a rectangle with the slice color in the legend
self.create_rectangle(legend_x + 100, legend_y, legend_x + 120,
            legend_y + 20, fill=self.slice_colors[i])

# Display the legend text with slice number and percentage
self.create_text(legend_x + 130, legend_y + 10,
            text=f"Slice{i+1}:{value}%", anchor=tk.W, fill="#333")

legend_y += 30



if __name__ == "__main__":
DonutChart()


The Final Result:

Python Tkinter Donut Chart












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, 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.