Showing posts with label Java Swing. Show all posts

The Puzzle game in Java Swing



The goal of this little game is to form a picture. Buttons containing images are moved by clicking on them. Only buttons adjacent to the empty button can be moved.

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.awt.image.CropImageFilter;
import java.awt.image.FilteredImageSource;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;

class MyButton extends JButton {

private boolean isLastButton;

public MyButton() {

super();

initUI();
}

public MyButton(Image image) {

super(new ImageIcon(image));

initUI();
}

private void initUI() {

isLastButton = false;
BorderFactory.createLineBorder(Color.gray);

addMouseListener(new MouseAdapter() {

@Override
public void mouseEntered(MouseEvent e) {
setBorder(BorderFactory.createLineBorder(Color.yellow));
}

@Override
public void mouseExited(MouseEvent e) {
setBorder(BorderFactory.createLineBorder(Color.gray));
}
});
}

public void setLastButton() {

isLastButton = true;
}

public boolean isLastButton() {

return isLastButton;
}
}

public class PuzzleEx extends JFrame {

private JPanel panel;
private BufferedImage source;
private ArrayList<MyButton> buttons;

ArrayList<Point> solution = new ArrayList();

private Image image;
private MyButton lastButton;
private int width, height;
private final int DESIRED_WIDTH = 300;
private BufferedImage resized;

public PuzzleEx() {

initUI();
}

private void initUI() {

solution.add(new Point(0, 0));
solution.add(new Point(0, 1));
solution.add(new Point(0, 2));
solution.add(new Point(1, 0));
solution.add(new Point(1, 1));
solution.add(new Point(1, 2));
solution.add(new Point(2, 0));
solution.add(new Point(2, 1));
solution.add(new Point(2, 2));
solution.add(new Point(3, 0));
solution.add(new Point(3, 1));
solution.add(new Point(3, 2));

buttons = new ArrayList();

panel = new JPanel();
panel.setBorder(BorderFactory.createLineBorder(Color.gray));
panel.setLayout(new GridLayout(4, 3, 0, 0));

try {
source = loadImage();
int h = getNewHeight(source.getWidth(), source.getHeight());
resized = resizeImage(source, DESIRED_WIDTH, h,
BufferedImage.TYPE_INT_ARGB);

} catch (IOException ex) {
Logger.getLogger(PuzzleEx.class.getName()).log(
Level.SEVERE, null, ex);
}

width = resized.getWidth(null);
height = resized.getHeight(null);

add(panel, BorderLayout.CENTER);

for (int i = 0; i < 4; i++) {

for (int j = 0; j < 3; j++) {

image = createImage(new FilteredImageSource(resized.getSource(),
new CropImageFilter(j * width / 3, i * height / 4,
(width / 3), height / 4)));
MyButton button = new MyButton(image);
button.putClientProperty("position", new Point(i, j));

if (i == 3 && j == 2) {
lastButton = new MyButton();
lastButton.setBorderPainted(false);
lastButton.setContentAreaFilled(false);
lastButton.setLastButton();
lastButton.putClientProperty("position", new Point(i, j));
} else {
buttons.add(button);
}
}
}

Collections.shuffle(buttons);
buttons.add(lastButton);

for (int i = 0; i < 12; i++) {

MyButton btn = buttons.get(i);
panel.add(btn);
btn.setBorder(BorderFactory.createLineBorder(Color.gray));
btn.addActionListener(new ClickAction());
}

pack();
setTitle("Puzzle");
setResizable(false);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}

private int getNewHeight(int w, int h) {

double ratio = DESIRED_WIDTH / (double) w;
int newHeight = (int) (h * ratio);
return newHeight;
}

private BufferedImage loadImage() throws IOException {

BufferedImage bimg = ImageIO.read(new File("icesid.jpg"));

return bimg;
}

private BufferedImage resizeImage(BufferedImage originalImage, int width,
int height, int type) throws IOException {

BufferedImage resizedImage = new BufferedImage(width, height, type);
Graphics2D g = resizedImage.createGraphics();
g.drawImage(originalImage, 0, 0, width, height, null);
g.dispose();

return resizedImage;
}

private class ClickAction extends AbstractAction {

@Override
public void actionPerformed(ActionEvent e) {

checkButton(e);
checkSolution();
}

private void checkButton(ActionEvent e) {

int lidx = 0;
for (MyButton button : buttons) {
if (button.isLastButton()) {
lidx = buttons.indexOf(button);
}
}

JButton button = (JButton) e.getSource();
int bidx = buttons.indexOf(button);

if ((bidx - 1 == lidx) || (bidx + 1 == lidx)
|| (bidx - 3 == lidx) || (bidx + 3 == lidx)) {
Collections.swap(buttons, bidx, lidx);
updateButtons();
}
}

private void updateButtons() {

panel.removeAll();

for (JComponent btn : buttons) {

panel.add(btn);
}

panel.validate();
}
}

private void checkSolution() {

ArrayList<Point> current = new ArrayList();

for (JComponent btn : buttons) {
current.add((Point) btn.getClientProperty("position"));
}

if (compareList(solution, current)) {
JOptionPane.showMessageDialog(panel, "Finished",
"Congratulation", JOptionPane.INFORMATION_MESSAGE);
}
}

public static boolean compareList(List ls1, List ls2) {
return ls1.toString().contentEquals(ls2.toString());
}

public static void main(String[] args) {

EventQueue.invokeLater(new Runnable() {

@Override
public void run() {
PuzzleEx puzzle = new PuzzleEx();
puzzle.setVisible(true);
}
});
}
}

We use an image of a Sid character from the Ice Age movie. We scale the image and cut it into 12 pieces. These pieces are used by JButton components. The last piece is not used; we have an empty button instead. You can download some reasonably large picture and use it in this game.

addMouseListener(new MouseAdapter() {

@Override
public void mouseEntered(MouseEvent e) {
setBorder(BorderFactory.createLineBorder(Color.yellow));
}

@Override
public void mouseExited(MouseEvent e) {
setBorder(BorderFactory.createLineBorder(Color.gray));

}
});

When we hover a mouse pointer over the button, its border changes to yellow colour.

public boolean isLastButton() {

return isLastButton;
}

There is one button that we call the last button. It is a button that does not have an image. Other buttons swap space with this one.

private final int DESIRED_WIDTH = 300;

The image that we use to form is scaled to have the desired width. With the getNewHeight() method we calculate the new height, keeping the image's ratio.

solution.add(new Point(0, 0));
solution.add(new Point(0, 1));
solution.add(new Point(0, 2));
solution.add(new Point(1, 0));
...

The solution array list stores the correct order of buttons which forms the image. Each button is identified by one Point.

panel.setLayout(new GridLayout(4, 3, 0, 0));

We use a GridLayout to store our components. The layout consists of 4 rows and 3 columns.

image = createImage(new FilteredImageSource(resized.getSource(),
new CropImageFilter(j * width / 3, i * height / 4,
(width / 3), height / 4)));

CropImageFilter is used to cut a rectangular shape from the already resized image source. It is meant to be used in conjunction with a FilteredImageSource object to produce cropped versions of existing images.

button.putClientProperty("position", new Point(i, j));

Buttons are identified by their position client property. It is a point containing the button's correct row and colum position in the picture. These properties are used to find out if we have the correct order of buttons in the window.

if (i == 3 && j == 2) {
lastButton = new MyButton();
lastButton.setBorderPainted(false);
lastButton.setContentAreaFilled(false);
lastButton.setLastButton();
lastButton.putClientProperty("position", new Point(i, j));
} else {
buttons.add(button);
}

The button with no image is called the last button; it is placed at the end of the grid in the bottom-right corner. It is the button that swaps its position with the adjacent button that is being clicked. We set its isLastButton flag with the setLastButton() method.

Collections.shuffle(buttons);
buttons.add(lastButton);

We randomly reorder the elements of the buttons list. The last button, i.e. the button with no image, is inserted at the end of the list. It is not supposed to be shuffled, it always goes at the end when we start the Puzzle game.

for (int i = 0; i < 12; i++) {

MyButton btn = buttons.get(i);
panel.add(btn);
btn.setBorder(BorderFactory.createLineBorder(Color.gray));
btn.addActionListener(new ClickAction());
}

All the components from the buttons list are placed on the panel. We create some gray border around the buttons and add a click action listener.

private int getNewHeight(int w, int h) {

double ratio = DESIRED_WIDTH / (double) w;
int newHeight = (int) (h * ratio);
return newHeight;
}

The getNewHeight() method calculates the height of the image based on the desired width. The image's ratio is kept. We scale the image using these values.

private BufferedImage loadImage() throws IOException {

BufferedImage bimg = ImageIO.read(new File("icesid.jpg"));

return bimg;
}

A JPG image is loaded from the disk. ImageIO's read() method returns a BufferedImage, which is Swing's important class for manipulating images.

private BufferedImage resizeImage(BufferedImage originalImage, int width,
int height, int type) throws IOException {

BufferedImage resizedImage = new BufferedImage(width, height, type);
Graphics2D g = resizedImage.createGraphics();
g.drawImage(originalImage, 0, 0, width, height, null);
g.dispose();

return resizedImage;
}

The original image is resized by creating a new BufferedImage with new dimensions. We paint from the original image into this new buffered image.

private void checkButton(ActionEvent e) {

int lidx = 0;
for (MyButton button : buttons) {
if (button.isLastButton()) {
lidx = buttons.indexOf(button);
}
}

JButton button = (JButton) e.getSource();
int bidx = buttons.indexOf(button);

if ((bidx - 1 == lidx) || (bidx + 1 == lidx)
|| (bidx - 3 == lidx) || (bidx + 3 == lidx)) {
Collections.swap(buttons, bidx, lidx);
updateButtons();
}
}

Buttons are stored in an array list. This list is then mapped to the grid of the panel. We get the indexes of the last button and the clicked button. They are swapped using the Collections.swap() if they are adjacent.

private void updateButtons() {

panel.removeAll();

for (JComponent btn : buttons) {

panel.add(btn);
}

panel.validate();
}

The updateButtons() method maps the list to the panel's grid. First, all components are removed with the removeAll() method. A for loop is used to go trough the buttons list to add the reordered buttons back to the panel's layout manager. Finally, the validate() method implements the new layout.

private void checkSolution() {

ArrayList<Point> current = new ArrayList();

for (JComponent btn : buttons) {
current.add((Point) btn.getClientProperty("position"));
}

if (compareList(solution, current)) {
JOptionPane.showMessageDialog(panel, "Finished",
"Congratulation", JOptionPane.INFORMATION_MESSAGE);
}
}

Solution checking is done by comparing the list of points of the correctly ordered buttons with the current list containg the order of buttons from the window. A message dialog is shown in case the solution is reached.
Continue Reading »

Simple Text Editor


Today, early in the morning I made my first notepad clone... with less options and solutions !
So today I am going to show you ! How to make a less of a notepad in java !

Packages we will need
import java.awt.BorderLayout;
import java.awt.Font;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.FileReader;
import java.io.FileWriter;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JToolBar;
import javax.swing.text.DefaultEditorKit;

The class
public class NotedPad extends JFrame {

Base variables.
        private static final long serialVersionUID = 1L;

private JTextArea area = new JTextArea();
private JFileChooser dialog = new JFileChooser(System.getProperty("user.dir"));
private String currentFile = "Untitled";
private boolean changed = false;

Our JTextArea will be the area where you can write documents and etc...
Also if you are unfamiliar with JFileChooser, then try to read more about the swing lib in Javas API page. Also by changed it will represent our flag of when things in our area changes.

Constructor Part #1
        public NotedPad() {
area.setFont(new Font("Monospaced", Font.PLAIN, 12));
JScrollPane scroll = new JScrollPane(area, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
add(scroll, BorderLayout.CENTER);

JMenuBar mBar = new JMenuBar();
setJMenuBar(mBar);
JMenu mFile = new JMenu("File");
JMenu mEdit = new JMenu("Edit");
mBar.add(mFile);
mBar.add(mEdit);
So here we are our area and the toolbar we will use... with JScrollPane we will be able to view a bit more of our text and area...Most likely as anything else :>
Constructor Part #2
                mFile.add(New);
mFile.add(Open);
mFile.add(Save);
mFile.add(Quit);
mFile.add(SaveAs);
mFile.addSeparator();

for (int i=0; i<4; i++) {
mFile.getItem(i).setIcon(null);

mEdit.add(Cut);
mEdit.add(Copy);
mEdit.add(Past);

mEdit.getItem(0).setText("Cut");
mEdit.getItem(1).setText("Copy");
mEdit.getItem(2).setText("Past");
Now by adding methods and arguments we haven't implemented from the beginning will of course complain in an active IDE...However fear not we will come to the point what New/Open/Save/Quit/SaveAs are...
While adding we also position them in a correct order with our for loop and set every icon to null.
Constructor Part #3
                        JToolBar tool = new JToolBar();
add(tool, BorderLayout.NORTH);
tool.add(New);
tool.add(Open);
tool.add(Save);
tool.addSeparator();

JButton btnCut = tool.add(Cut), btnCopy = tool.add(Copy), btnPast = tool.add(Past);
btnCut.setText(null); btnCut.setIcon(new ImageIcon(this.getClass().getResource("cut.gif")));
btnCopy.setText(null); btnCopy.setIcon(new ImageIcon(this.getClass().getResource("copy.gif")));
btnPast.setText(null); btnPast.setIcon(new ImageIcon(this.getClass().getResource("paste.gif")));

Save.setEnabled(false);
SaveAs.setEnabled(false);

setDefaultCloseOperation(EXIT_ON_CLOSE);
pack();
area.addKeyListener(k1);
setTitle(currentFile);
setSize(400, 400);
setLocationRelativeTo(this);
setVisible(true);
}
}
We are currently adding our functions and giving some proper icons for our toolbar items ! Note we are adding in icons and giving null text to each item.
Our Save and SaveAs functions will represent the standard every time we launch the application...hence it's false...

Listener
        private KeyListener k1 = new KeyAdapter() {
public void keyPressed(KeyEvent evt) {
changed = true;
Save.setEnabled(true);
SaveAs.setEnabled(true);
}
};
Now here we have our listener which will listen when any key is pressed thus making the changed statement true and allowing us to save or saveas.Quite good right?

Action #0
        Action New = new AbstractAction("New") {
private static final long serialVersionUID = 1L;

@Override
public void actionPerformed(ActionEvent evt) {
area.setText(null);
}
};
Action #1
        Action Open = new AbstractAction("Open", new ImageIcon(this.getClass().getResource("open.gif"))) {
private static final long serialVersionUID = 1L;

@Override
public void actionPerformed(ActionEvent evt) {
saveOld();
if (dialog.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) {
readInFile(dialog.getSelectedFile().getAbsolutePath());
}
SaveAs.setEnabled(true);
}
};
By pressing on our ItemIcon we will trigger an Action(Abstract one). Also by triggering the Icon action we will be able to preserve the moment of our text. Resulting us to be able choose where to save and use it later

Action #2
       Action Save = new AbstractAction("Save", new ImageIcon(this.getClass().getResource("save.gif"))) {
private static final long serialVersionUID = 1L;

@Override
public void actionPerformed(ActionEvent evt) {
if (!currentFile.equals("Untitled"))
saveFile(currentFile);
else
saveFileAs();
}
};
So here is our save function. We will be able to save our current file. So by playing along I thought like this. If the currentFile doesn't equal to Untitled then just save it as it's called... else save as thus allowing the sure to give it a title 

Action #3
        Action SaveAs = new AbstractAction("Save As...") {
private static final long serialVersionUID = 1L;

@Override
public void actionPerformed(ActionEvent evt) {
saveFileAs();
}
};
Nothing much with this action. We are just allowing the user to saveAs...thus giving it a title...

Action #4
        Action Quit = new AbstractAction("Quit") {
private static final long serialVersionUID = 1L;

@Override
public void actionPerformed(ActionEvent evt) {
saveOld();
System.exit(0); // for close this frame
}
};
A quit function is needed also we are saving the old one. Which means... we will see later on 

Action Maping
        ActionMap am = area.getActionMap();
Action Cut = am.get(DefaultEditorKit.cutAction);
Action Copy = am.get(DefaultEditorKit.copyAction);
Action Past = am.get(DefaultEditorKit.pasteAction);
By allowing us, to give absolute correct power we are using the DefaultEditorKit in the Swing package...(IO too..)

Methods #1
        private void saveFileAs() {
if (dialog.showSaveDialog(null) == JFileChooser.APPROVE_OPTION)
saveFile(dialog.getSelectedFile().getAbsolutePath());
}
So now we have defined what saveFileAs will result to. However what will saveFile do, is something I will return to.

Methods #2
        private void saveOld() {
if (changed) {
if (JOptionPane.showConfirmDialog(this, "Would you like to save " + currentFile + " ?", "Save", JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION)
saveFile(currentFile);
}
}
By using a saveOld giving us an option to save the changed state thus naming it old doesn't seem wrong, right~?

Method #3
       private void readInFile(String fileName) {
try {
FileReader r = new FileReader(fileName);
area.read(r, null);
r.close();
currentFile = fileName;
setTitle(currentFile);
changed = false;
} catch (Exception e) {
Toolkit.getDefaultToolkit().beep();
JOptionPane.showMessageDialog(this, "Editor can't find the file called " + fileName);
}
}
As we are allowed to save, how do we open a file? Easy making a filereader method... The beep sound isn't required however I feel that making it kinda "cooler" why not give it an extra touch?

Method #4
        private void saveFile(String fileName) {
try {
FileWriter w = new FileWriter(fileName);
area.write(w);
w.close();
currentFile = fileName;
setTitle(currentFile);
changed = false;
Save.setEnabled(false);
} catch (Exception e) {}
}
Our savefile method. Allowing the user to define a name for the title as well to his/her document. 

The main
        public static void main(String[] args) {
new NotedPad();
}
}
Creating a new TextEditor everytime we launch !
Hope you enjoyed to follow this tutorial as I created it for you guys !
Cheers !

Download Icon

You see this tutorial with my video:

Part 1:




Part 2:




Part 3:



Full Video:

Continue Reading »