/**
* Initializes the game timer that updates every second
*/
private void initializeTimer() {
timer = new Timer(1000, e -> {
elapsedTime++; // Increment elapsed time
int minutes = elapsedTime / 60; // Calculate minutes
int seconds = elapsedTime % 60; // Calculate seconds
timerLabel.setText(String.format("Time: %02d:%02d", minutes, seconds)); // Update timer display
});
}
/**
* Creates a panel to display game statistics (moves and time)
*/
private JPanel createStatsPanel() {
// Create panel with vertical box layout
JPanel statsPanel = new JPanel();
statsPanel.setLayout(new BoxLayout(statsPanel, BoxLayout.Y_AXIS));
statsPanel.setOpaque(false);
// Add semi-transparent border
statsPanel.setBorder(BorderFactory.createCompoundBorder(
new LineBorder(new Color(255, 255, 255, 50), 1, true),
new EmptyBorder(15, 15, 15, 15)));
// Add statistics elements
JLabel statsTitle = createLabel("Game Stats", 20, Font.BOLD);
statsPanel.add(statsTitle);
statsPanel.add(Box.createVerticalStrut(10));
statsPanel.add(movesLabel);
statsPanel.add(Box.createVerticalStrut(5));
statsPanel.add(timerLabel);
return statsPanel;
}
/**
* Creates a styled label with specified text, font size, and style
*/
private JLabel createLabel(String text, int fontSize, int fontStyle) {
JLabel label = new JLabel(text);
label.setFont(new Font("Arial", fontStyle, fontSize)); // Set font properties
label.setForeground(Color.WHITE); // Set text color
label.setAlignmentX(Component.CENTER_ALIGNMENT); // Center align
return label;
}
/**
* Creates a styled button with specified text and background color
*/
private JButton createButton(String text, Color color) {
// Create button with custom painting for rounded corners
JButton button = new JButton(text) {
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(getBackground());
g2d.fill(new RoundRectangle2D.Float(0,0, getWidth(), getHeight(), 15, 15));
g2d.dispose();
super.paintComponent(g);
}
};
// Style the button
button.setAlignmentX(Component.CENTER_ALIGNMENT);
button.setMaximumSize(new Dimension(240, 50));
button.setFont(new Font("Arial", Font.BOLD, 16));
button.setForeground(Color.WHITE);
button.setBackground(color);
button.setBorder(new EmptyBorder(10, 15, 10, 15));
button.setFocusPainted(false);
button.setContentAreaFilled(false);
// Add hover effect
button.addMouseListener(new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent evt) {
button.setBackground(color.darker()); // Darken on hover
}
@Override
public void mouseExited(MouseEvent evt) {
button.setBackground(color); // Restore original color
}
});
return button;
}
/**
* Applies custom styling to the difficulty combo box
*/
private void styleCombobox(JComboBox<String> combobox) {
combobox.setMaximumSize(new Dimension(240, 40));
combobox.setFont(new Font("Arial", Font.PLAIN, 16));
combobox.setBackground(new Color(236, 240, 241));
combobox.setForeground(new Color(44, 62, 80));
combobox.setBorder(BorderFactory.createCompoundBorder(
new LineBorder(new Color(189, 195, 199), 1),
new EmptyBorder(5, 10, 5, 10)
));
combobox.setAlignmentX(Component.CENTER_ALIGNMENT);
// Center align items in dropdown
combobox.setRenderer(new DefaultListCellRenderer() {
@Override
public Component getListCellRendererComponent(JList<?> list, Object value,
int index, boolean isSelected, boolean cellHasFocus) {
JLabel label = (JLabel)super.getListCellRendererComponent(
list, value, index, isSelected, cellHasFocus);
label.setHorizontalAlignment(JLabel.CENTER);
return label;
}
});
}
/**
* Opens file chooser and loads selected image to start puzzle
*/
private void loadAndStartPuzzle() {
JFileChooser fileChooser = new JFileChooser();
// Set filter to only show image files
fileChooser.setFileFilter(new javax.swing.filechooser.FileNameExtensionFilter(
"Image Files", ImageIO.getReaderFileSuffixes()));
int returnValue = fileChooser.showOpenDialog(null);
if(returnValue == JFileChooser.APPROVE_OPTION) {
File selectedFile = fileChooser.getSelectedFile();
try {
originalImage = ImageIO.read(selectedFile); // Load image
BufferedImage resizedImage = resizeImage(originalImage, IMAGE_SIZE, IMAGE_SIZE); // Resize
resetGame(resizedImage); // Start new game
} catch (IOException ex) {
Logger.getLogger(Java_Puzzle_Game.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
/**
* Resizes an image to specified dimensions while maintaining aspect ratio
*/
private BufferedImage resizeImage(BufferedImage image, int targetWidth, int targetHeight) {
// Create scaled instance of image
Image reszedImage = image.getScaledInstance(targetWidth, targetHeight, Image.SCALE_SMOOTH);
BufferedImage bufferedReszedImage = new BufferedImage(targetWidth, targetHeight,
BufferedImage.TYPE_INT_ARGB);
// Draw scaled image
Graphics2D g2d = (Graphics2D) bufferedReszedImage.createGraphics();
g2d.drawImage(reszedImage, 0, 0, null);
g2d.dispose();
return bufferedReszedImage;
}
/**
* Resets the game state and creates new puzzle pieces from the provided image
*/
private void resetGame(BufferedImage image) {
// Reset game statistics
timer.stop();
elapsedTime = 0;
timerLabel.setText("Timer: 00:00");
moves = 0;
movesLabel.setText("Moves: 0");
// Clear existing puzzle pieces
gridPanel.removeAll();
labelList.clear();
correctOrder.clear();
firstSelectedSlice = null;
secondSelectedSlice = null;
// Calculate dimensions for puzzle pieces
int sliceWidth = image.getWidth() / GRID_SIZE;
int sliceHeight = image.getHeight() / GRID_SIZE;
List<ImageIcon> imageSlices = new ArrayList<>();
// Create puzzle pieces
for(int y = 0; y < GRID_SIZE; y++) {
// Create puzzle pieces from subimages
for(int x = 0; x < GRID_SIZE; x++) {
// Extract piece from original image
BufferedImage slice = image.getSubimage(
x * sliceWidth,
y * sliceHeight,
sliceWidth,
sliceHeight
);
// Create icon from slice and add to lists
ImageIcon imageIcon = new ImageIcon(
slice.getScaledInstance(sliceWidth, sliceHeight, Image.SCALE_SMOOTH)
);
imageSlices.add(imageIcon);
correctOrder.add(imageIcon);
}
}
// Randomly shuffle the puzzle pieces
Collections.shuffle(imageSlices);
// Create and add labels for each puzzle piece
for(ImageIcon icon : imageSlices) {
// Create label for puzzle piece
JLabel sliceLabel = new JLabel(icon);
sliceLabel.setBorder(BorderFactory.createLineBorder(new Color(189, 195, 199), 2));
sliceLabel.setHorizontalAlignment(JLabel.CENTER);
sliceLabel.setVerticalAlignment(JLabel.CENTER);
// Add click listener to handle piece selection
sliceLabel.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
handleSliceClick(sliceLabel);
}
});
// Add to tracking lists and grid
labelList.add(sliceLabel);
gridPanel.add(sliceLabel);
}
// Refresh the display
gridPanel.revalidate();
gridPanel.repaint();
// Start the game timer
timer.start();
}
/**
* Updates the grid size based on selected difficulty level
*/
private void setGridSize(String difficulty) {
// Set grid dimensions based on selection
switch (difficulty) {
case "4x4" -> GRID_SIZE = 4;
case "5x5" -> GRID_SIZE = 5;
case "6x6" -> GRID_SIZE = 6;
case "7x7" -> GRID_SIZE = 7;
case "8x8" -> GRID_SIZE = 9;
}
// Update grid layout
gridPanel.setLayout(new GridLayout(GRID_SIZE, GRID_SIZE));
}
/**
* Handles click events on puzzle pieces
*/
private void handleSliceClick(JLabel clickedLabel) {
if(firstSelectedSlice == null) {
// First piece selection
firstSelectedSlice = clickedLabel;
firstSelectedSlice.setBorder(BorderFactory.createLineBorder(new Color(231,76,60), 2));
}
else if(secondSelectedSlice == null && clickedLabel != firstSelectedSlice) {
// Second piece selection
secondSelectedSlice = clickedLabel;
secondSelectedSlice.setBorder(BorderFactory.createLineBorder(new Color(231,76,60), 2));
// Start swap animation
swapSlices();
}
}
/**
* Animates the swapping of two puzzle pieces
*/
private void swapSlices() {
Timer animationTimer = new Timer(10, null);
final int animationDuration = 500; // Animation duration in milliseconds
final long startTime = System.currentTimeMillis();
// Store initial positions
Point firstLocation = firstSelectedSlice.getLocation();
Point secondLocation = secondSelectedSlice.getLocation();
// Animation update handler
animationTimer.addActionListener((e) -> {
// Calculate animation progress
long elapsed = System.currentTimeMillis() - startTime;
float progress = Math.min(1f, (float) elapsed / animationDuration);
float easedProgress = easeInOut(progress); // Apply easing for smooth movement
// Calculate new positions
int x1 = (int) (firstLocation.x + (secondLocation.x - firstLocation.x) * easedProgress);
int y1 = (int) (firstLocation.y + (secondLocation.y - firstLocation.y) * easedProgress);
int x2 = (int) (secondLocation.x + (firstLocation.x - secondLocation.x) * easedProgress);
int y2 = (int) (secondLocation.y + (firstLocation.y - secondLocation.y) * easedProgress);
// Update piece positions
firstSelectedSlice.setLocation(x1, y1);
secondSelectedSlice.setLocation(x2, y2);
// Check if animation is complete
if(progress >= 1f) {
animationTimer.stop();
finalizeSwap();
}
});
// Start animation
animationTimer.start();
}
/**
* Easing function for smooth animation
* Creates acceleration at start and deceleration at end
*/
private float easeInOut(float t) {
return t < 0.5f ? 2 * t * t : -1 + (4 - 2 * t) * t;
}
/**
* Completes the swap operation after animation
*/
private void finalizeSwap() {
// Swap the icons between pieces
Icon tempIcon = firstSelectedSlice.getIcon();
firstSelectedSlice.setIcon(secondSelectedSlice.getIcon());
secondSelectedSlice.setIcon(tempIcon);
// Reset pieces to their grid positions
firstSelectedSlice.setLocation(firstSelectedSlice.getParent().getComponent(
labelList.indexOf(firstSelectedSlice)).getLocation()
);
secondSelectedSlice.setLocation(secondSelectedSlice.getParent().getComponent(
labelList.indexOf(secondSelectedSlice)).getLocation()
);
// Reset borders to default
firstSelectedSlice.setBorder(BorderFactory.createLineBorder(new Color(189, 195, 199), 2));
secondSelectedSlice.setBorder(BorderFactory.createLineBorder(new Color(189, 195, 199), 2));
// Clear selection
firstSelectedSlice = null;
secondSelectedSlice = null;
// Update move counter
moves++;
movesLabel.setText("Moves: " + moves);
// Check if puzzle is solved
checkIfSolved();
}
/**
* Checks if the puzzle has been solved
*/
private void checkIfSolved() {
// Compare current arrangement with correct order
for(int i = 0; i < labelList.size(); i++) {
if(labelList.get(i).getIcon() != correctOrder.get(i)) {
return; // Puzzle not solved
}
}
// Puzzle is solved
timer.stop();
showCongratulationDialog();
}
/**
* Shows congratulation dialog when puzzle is solved
*/
private void showCongratulationDialog() {
// Create modal dialog
JDialog dialog = new JDialog(this, "Congratulation!", true);
dialog.setLayout(new BorderLayout());
dialog.setSize(300, 200);
dialog.setLocationRelativeTo(this);
// Create panel with gradient background
JPanel panel = new JPanel() {
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
// Create gradient background
int w = getWidth();
int h = getHeight();
Color color1 = new Color(41, 128, 185);
Color color2 = new Color(44, 62, 80);
GradientPaint gp = new GradientPaint(0, 0, color1, w, h, color2);
g2d.setPaint(gp);
g2d.fillRect(0, 0, w, h);
}
};
// Configure panel layout
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
panel.setBorder(new EmptyBorder(20, 20, 20, 20));
// Add congratulation message and stats
JLabel congratsLabel = createLabel("Puzzle Solved!", 24, Font.BOLD);
JLabel statsLabel = createLabel("Moves: " + moves + " | Time: " + formatTime(elapsedTime), 16, Font.PLAIN);
// Add close button
JButton closeButton = createButton("Close", new Color(231, 76, 60));
closeButton.addActionListener((ActionEvent e) -> dialog.dispose());
// Add components to panel
panel.add(congratsLabel);
panel.add(Box.createVerticalStrut(10));
panel.add(statsLabel);
panel.add(Box.createVerticalStrut(20));
panel.add(closeButton);
// Show dialog
dialog.add(panel);
dialog.setVisible(true);
}
/**
* Formats time in minutes:seconds format
*/
private String formatTime(int seconds) {
int minutes = seconds / 60;
int remainingSeconds = seconds % 60;
return String.format("%02d:%02d", minutes, remainingSeconds);
}
/**
* Shuffles the puzzle pieces on the grid
*/
private void shuffleGrid() {
// Reset game statistics
timer.stop();
elapsedTime = 0;
timerLabel.setText("Timer: 00:00");
moves = 0;
movesLabel.setText("Moves: 0");
// Create and shuffle new arrangement
List<ImageIcon> shuffledIcons = new ArrayList<>(correctOrder);
Collections.shuffle(shuffledIcons);
// Apply new arrangement to grid
for(int i = 0; i < labelList.size(); i++) {
labelList.get(i).setIcon(shuffledIcons.get(i));
}
// Start timer if game is active
if(originalImage != null) {
timer.start();
}
}