Implementasi Tower of Hanoi pada Java
Tower of Hanoi
Tower of hanoi merupakan salah satu permainan asah otak yang terdiri dari tiga buah tiang dengan salah satu tiang awalnya memiliki beberapa buah piringan dengan diameter yang berbeda dan semakin ke bawah semakin besar, sehingga membentuk seperti suatu piramida. Cara memainkannya cukup sederhana, dimana kita harus memindahkan tumpukan piringan tersebut dari salah satu tiang ke tiang lainnya (umumnya dari tiang paling kiri ke tiang paling kanan) dengan aturan, sekali memindahkan hanya diperbolehkan satu buat piringan, dan piringan yang lebih besar harus berada di bawah piringan yang lebih kecil.
Implementasi Tower of Hanoi pada Java
Model
Model merupakan representasi dari data-data yang diperlukan dalam aplikasi. dalam implementasinya, model disini berperan sebagai storage atau tempat penyimpanan data-data yang diperlukan dalam aplikasi. Berikut ini merupakan implementasi model untuk aplikasi tower of hanoi
package assignment_08.models; | |
/** | |
* This class represents model of a single log | |
* | |
* @since June 8th 2021 | |
* @author Fajar Zuhri Hadiyanto | |
* @version 1.0 | |
* */ | |
public class LogModel { | |
/** field to contain index of the from rod */ | |
private final int fromRod; | |
/** field to contain index of the to rod */ | |
private final int toRod; | |
/** field to contain value of the disk moved */ | |
private final int diskVal; | |
/** | |
* This constructor will create new log by setting from rod, to rod, and value of the disk | |
* | |
* @param fromRod index of the from rod | |
* @param toRod index of the to rod | |
* @param diskVal value of the disk moved | |
* */ | |
public LogModel(int fromRod, int toRod, int diskVal) { | |
this.fromRod = fromRod; | |
this.toRod = toRod; | |
this.diskVal = diskVal; | |
} | |
/* ALL GETTER METHOD BELOW HERE */ | |
public int getFromRod() { | |
return this.fromRod; | |
} | |
public int getToRod() { | |
return this.toRod; | |
} | |
public int getDiskVal() { | |
return this.diskVal; | |
} | |
} |
package assignment_08.models; | |
import java.util.Stack; | |
/** | |
* This class represents model of the list of movement log | |
* | |
* @since June 8th 2021 | |
* @author Fajar Zuhri Hadiyanto | |
* @version 1.0 | |
* */ | |
public class LogsModel { | |
/** field to contain stack of logs */ | |
private final Stack<LogModel> logs; | |
/** | |
* This constructor will create new stack of logs | |
* */ | |
public LogsModel() { | |
logs = new Stack<>(); | |
} | |
// ALL GETTER METHOD BELOW HERE | |
public Stack<LogModel> getLogs() { | |
return this.logs; | |
} | |
} |
package assignment_08.models; | |
/** | |
* This class represents set of all necessary models in the application | |
* | |
* @since June 8th 2021 | |
* @author Fajar Zuhri Hadiyanto | |
* @version 1.0 | |
* */ | |
public class Model { | |
/** Logs Model */ | |
private final LogsModel logsModel; | |
/** Rods Model */ | |
private final RodsModel rodsModel; | |
/** State Model */ | |
private final StateModel stateModel; | |
/** | |
* This constructor will set all necessary model | |
* */ | |
public Model(LogsModel logsModel, RodsModel rodsModel, StateModel stateModel) { | |
this.logsModel = logsModel; | |
this.rodsModel = rodsModel; | |
this.stateModel = stateModel; | |
} | |
/* ALL GETTER METHOD BELOW HERE */ | |
public LogsModel getLogsModel() { | |
return this.logsModel; | |
} | |
public RodsModel getRodsModel() { | |
return this.rodsModel; | |
} | |
public StateModel getStateModel() { | |
return this.stateModel; | |
} | |
} |
package assignment_08.models; | |
import java.util.Stack; | |
/** | |
* This class represents model of the three rods of the game | |
* | |
* @since June 8th 2021 | |
* @author Fajar Zuhri Hadiyanto | |
* @version 1.0 | |
* */ | |
public class RodsModel { | |
/** field to contain all three rods data */ | |
private final RodModel[] towers = new RodModel[3]; | |
/** | |
* This class represents model of a single rod | |
* | |
* @since June 8th 2021 | |
* @author Fajar Zuhri Hadiyanto | |
* @version 1.0 | |
* */ | |
public class RodModel { | |
/** field to contain list of disk */ | |
private Stack<Integer> disks; | |
/** | |
* This constructor will create new list of disk with a given maximum disk value | |
* | |
* @param maximum maximum value of the disk in the list | |
* */ | |
public RodModel(int maximum) { | |
disks = new Stack<>(); | |
for (int i = maximum; i > 0; i--) { | |
disks.push(i); | |
} | |
} | |
/** | |
* This method is used to check if field disks is empty or not | |
* | |
* @return true if field disks is empty, else false | |
* */ | |
public boolean empty() { | |
return this.disks.empty(); | |
} | |
/** | |
* This method is used to get the top value of disk | |
* | |
* @return top value of the disks if its not empty, else null | |
* */ | |
public Integer getTopDisk() { | |
return !this.empty() ? this.disks.peek() : null; | |
} | |
/** | |
* This method is used to pop the top value of the disk of its not empty | |
* */ | |
public void pickTopDisk() { | |
if (!this.empty()) this.disks.pop(); | |
} | |
/** | |
* This method is used to put a new disk to the stack, if the value is valid | |
* | |
* @param value value of the new disk | |
* */ | |
public void putDisk(int value) { | |
if (this.disks.empty() || value < this.disks.peek()) this.disks.push(value); | |
} | |
} | |
/** | |
* This constructor will create new list of rods with the initial number of the disk for every rods | |
* | |
* @param startDisk number of the disk in the start rod | |
* @param auxDisk number of the disk in the aux rod | |
* @param destDisk number of the disk in the destination rod | |
* */ | |
public RodsModel(int startDisk, int auxDisk, int destDisk) { | |
this.reinitiate(startDisk, auxDisk, destDisk); | |
} | |
/** | |
* This constructor will create new list of rods with the initial number of the disk | |
* that will be placed in the start rod initially | |
* | |
* @param nDisk number of the disk | |
* */ | |
public RodsModel(int nDisk) { | |
this(nDisk, 0, 0); | |
} | |
/** | |
* This method is a utility method to create new list of rods with the initial number of the disk for every rods | |
* | |
* @param startDisk number of the disk in the start rod | |
* @param auxDisk number of the disk in the aux rod | |
* @param destDisk number of the disk in the destination rod | |
* */ | |
public void reinitiate(int startDisk, int auxDisk, int destDisk) { | |
this.towers[0] = new RodModel(startDisk); | |
this.towers[1] = new RodModel(auxDisk); | |
this.towers[2] = new RodModel(destDisk); | |
} | |
/** | |
* This method is a utility method to create new list of rods with the initial number of the disk | |
* that will be placed in the start rod initially | |
* | |
* @param nDisk number of the disk | |
* */ | |
public void reinitiate(int nDisk) { | |
this.reinitiate(nDisk, 0, 0); | |
} | |
/** | |
* This method is used to move top disk of the from rod to the to rod | |
* | |
* @param from index of the from rod | |
* @param to index of the to rod | |
* */ | |
public void moveDisk(int from, int to) { | |
this.towers[to].putDisk(this.towers[from].getTopDisk()); | |
this.towers[from].pickTopDisk(); | |
} | |
} |
package assignment_08.models; | |
/** | |
* This class represents model of the state in the application | |
* | |
* @since June 8th 2021 | |
* @author Fajar Zuhri Hadiyanto | |
* @version 1.0 | |
* */ | |
public class StateModel { | |
/** field to contain number of the disk in the game */ | |
private int nDisk; | |
/** field to contain total move done by the game */ | |
private int totalMoves; | |
/** | |
* This constructor will set new value for all state in the application | |
* | |
* @param nDisk number of the disk in the game | |
* @param totalMoves total move done by the game | |
* */ | |
public StateModel(int nDisk, int totalMoves) { | |
this.nDisk = nDisk; | |
this.totalMoves = totalMoves; | |
} | |
/** | |
* This method is used to increase total move done by the game | |
* */ | |
public void incrementTotalMoves() { | |
this.totalMoves++; | |
} | |
/** | |
* This method is used to decrease total move done by the game | |
* */ | |
public void decrementTotalMoves() { | |
this.totalMoves--; | |
} | |
/* ALL SETTER AND GETTER METHOD BELOW HERE */ | |
public int getnDisk() { | |
return this.nDisk; | |
} | |
public int getTotalMoves() { | |
return this.totalMoves; | |
} | |
public void setnDisk(int nDisk) { | |
this.nDisk = nDisk; | |
} | |
public void setTotalMoves(int totalMoves) { | |
this.totalMoves = totalMoves; | |
} | |
} |
View
View merupakan bagian dari aplikasi yang bertanggung jawab menampilkan data ke dalam UI yang dapat divisualisasikan melalui window aplikasi dan komponen-komponen di dalamnya. Berikut ini merupakan implementasi view untuk aplikasi tower of hanoi
package assignment_08.views.components; | |
import assignment_08.models.LogModel; | |
import javax.swing.*; | |
import javax.swing.border.Border; | |
import javax.swing.border.EmptyBorder; | |
import java.awt.*; | |
/** | |
* This class represents renderer tools for cell in a list of {@link LogModel} | |
* | |
* @since June 8th 2021 | |
* @author Fajar Zuhri Hadiyanto | |
* @version 1.0 | |
* */ | |
public class LogListCellRenderer extends JPanel implements ListCellRenderer<LogModel>{ | |
/** the border */ | |
private static final Border DEFAULT_NO_FOCUS_BORDER = new EmptyBorder(1, 1, 1, 1); | |
/** basic component to represent disk of the game by the color */ | |
private final JPanel disk; | |
/** label component to contain the information of the movement */ | |
private final JLabel label; | |
/** colors constant for disk component */ | |
private final Color[] COLORS = { | |
new Color(244, 67, 54), | |
new Color(255, 152, 0), | |
new Color(255, 235, 59), | |
new Color(139, 195, 74), | |
new Color(0, 150, 136), | |
new Color(3, 169, 244), | |
new Color(63, 81, 181), | |
new Color(233, 30, 99), | |
}; | |
/** | |
* This method is used to configure and render UI of each data in the list | |
* */ | |
public LogListCellRenderer() { | |
// CONFIGURATION | |
setOpaque(false); | |
setLayout(new FlowLayout(FlowLayout.LEFT)); | |
setBorder(DEFAULT_NO_FOCUS_BORDER); | |
// COMPONENT INITIALIZATION | |
this.disk = new JPanel(); | |
this.label = new JLabel(); | |
// COMPONENT RENDER | |
add(this.disk); | |
add(this.label); | |
} | |
/** | |
* This method is override behavior of {@link ListCellRenderer} interface. | |
* | |
* @param list | |
* @param value | |
* @param index | |
* @param isSelected | |
* @param cellHasFocus | |
* | |
* @return component to be rendered | |
* */ | |
@Override | |
public Component getListCellRendererComponent(JList<? extends LogModel> list, LogModel value, int index, boolean isSelected, boolean cellHasFocus) { | |
this.label.setText("From " + rod(value.getFromRod()) + " to " + rod(value.getToRod())); | |
this.disk.setBackground(COLORS[value.getDiskVal() - 1]); | |
return this; | |
} | |
/** | |
* This method is used to convert rod index to rod position (left, middle, right) | |
* | |
* @param index index of the rod | |
* @return corresponding string representation of rod position based on the index | |
* */ | |
private String rod(int index) { | |
switch (index) { | |
case 0: | |
return "left"; | |
case 1: | |
return "middle"; | |
case 2: | |
return "right"; | |
default: | |
return ""; | |
} | |
} | |
} |
package assignment_08.views.components; | |
import javax.swing.*; | |
import java.awt.*; | |
import java.util.Stack; | |
/** | |
* This class represents Rod components derive from general swing component | |
* | |
* @since June 8th 2021 | |
* @author Fajar Zuhri Hadiyanto | |
* @version 1.0 | |
* */ | |
public class Rod extends JComponent { | |
/** constant that define maximum disk for one rod */ | |
private final int MAXIMUM_DISK = 8; | |
/** constant that define thickness of every single disk */ | |
private final int DISK_THICKNESS = 20; | |
/** constant that define width of the smallest possible disk */ | |
private final int DISK_MINIMUM_WIDTH = 40; | |
/** constant that define Thickness of the rod */ | |
private final int ROD_THICKNESS = 20; | |
/** colors constant for disk component */ | |
private final Color[] colors = { | |
new Color(244, 67, 54), | |
new Color(255, 152, 0), | |
new Color(255, 235, 59), | |
new Color(139, 195, 74), | |
new Color(0, 150, 136), | |
new Color(3, 169, 244), | |
new Color(63, 81, 181), | |
new Color(233, 30, 99), | |
}; | |
/** list of disk data */ | |
private Stack<Integer> disks; | |
/** field to contain width of the largest possible disk */ | |
private int disk_max_width; | |
/** field to contain width gap for every adjacent disk */ | |
private int disk_gap_width; | |
/** | |
* This constructor will create new rod with the given list of disk data | |
* | |
* @param initialDisk initial list of the disk | |
* */ | |
public Rod(Stack<Integer> initialDisk) { | |
this.disks = initialDisk; | |
} | |
/** | |
* This constructor will create new rod with the given maximum disk value | |
* | |
* @param max maximum disk value | |
* */ | |
public Rod(int max) { | |
this.disks = new Stack<>(); | |
for (int i = max; i > 0; i--) { | |
this.disks.push(i); | |
} | |
} | |
/** | |
* This constructor will create new empty rod | |
* */ | |
public Rod() { | |
this(0); | |
} | |
/** | |
* This method overrides behavior of {@link JComponent} class | |
* | |
* @param g | |
* */ | |
@Override | |
public void paintComponent(Graphics g) { | |
// GET MAX WIDTH AND GAP WIDTH | |
this.disk_max_width = g.getClipBounds().width; | |
this.disk_gap_width = (this.disk_max_width - this.DISK_MINIMUM_WIDTH) / (this.MAXIMUM_DISK - 1); | |
// RENDER THE ROD | |
g.fillRect(0, g.getClipBounds().height - this.ROD_THICKNESS, g.getClipBounds().width, 20); | |
g.fillRect((g.getClipBounds().width - this.ROD_THICKNESS) / 2, 20, this.ROD_THICKNESS, g.getClipBounds().height); | |
// RENDER ALL THE DISKS | |
for (int i = 0; i < disks.size(); i++) { | |
g.setColor(this.colors[this.disks.get(i) - 1]); | |
int diskWidth = this.DISK_MINIMUM_WIDTH + ((this.disks.get(i) - 1) * this.disk_gap_width); | |
g.fillRect((g.getClipBounds().width - diskWidth) / 2, | |
g.getClipBounds().height - ((i+2) * this.DISK_THICKNESS), | |
diskWidth, | |
this.DISK_THICKNESS); | |
} | |
} | |
/** | |
* This method is used to get the top disk without removing it. | |
* If the rod is empty, return 0 instead | |
* | |
* @return peak value of the rod, or 0 if the rod is empty | |
* */ | |
public int getTopDisk() { | |
return this.disks.empty() ? 0 : this.disks.peek(); | |
} | |
/** | |
* This method is used to add new disk to the rod and rerender the UI | |
* | |
* @param value value of the disk | |
* @throws Exception if the disk is not empty and the input value is not valid (under 1 or greater than or equal to current peak) | |
* */ | |
public void addDisk(Integer value) throws Exception { | |
if (!this.disks.empty() && (value >= this.disks.peek() || value < 1)) { | |
throw new Exception("Invalid"); | |
} | |
this.disks.push(value); | |
repaint(); | |
} | |
/** | |
* This method is used to pick the peak value of the rod and rerender the UI | |
* | |
* @throws Exception when the rod is empty | |
* */ | |
public void removeDisk() throws Exception { | |
if (this.disks.empty()) { | |
throw new Exception("Invalid"); | |
} | |
this.disks.pop(); | |
repaint(); | |
} | |
/** | |
* This method is used to replace current rod with new list of disk and rerender the UI | |
* | |
* @param disks stack of the disk | |
* */ | |
public void updateDisk(Stack<Integer> disks) { | |
this.disks = disks; | |
repaint(); | |
} | |
} |
package assignment_08.views; | |
import assignment_08.models.LogModel; | |
import assignment_08.views.components.LogListCellRenderer; | |
import javax.swing.*; | |
import java.awt.*; | |
/** | |
* This class represents view of the log window of the application | |
* | |
* @since June 8th 2021 | |
* @author Fajar Zuhri Hadiyanto | |
* @version 1.0 | |
* */ | |
public class LogView { | |
/** main Frame that contain all components */ | |
private JFrame mainFrame; | |
/** scroll area that contain list of logs */ | |
private JScrollPane jScrollPane; | |
/** list component of all logs */ | |
private JList<LogModel> logs; | |
/** data model for the list */ | |
DefaultListModel<LogModel> list; | |
/** | |
* This constructor will initialize the window for the main app and render the UI | |
* */ | |
public LogView() { | |
initializeWindow(); | |
displayUI(); | |
} | |
/** | |
* This method is used to create and show new window with a certain configuration | |
* */ | |
private void initializeWindow() { | |
this.mainFrame = new JFrame("Log"); | |
this.mainFrame.setLayout(new GridLayout(1, 1, 0, 0)); | |
this.mainFrame.setSize(300, 300); | |
this.mainFrame.setResizable(false); | |
} | |
/** | |
* This method is used to render all UI components | |
* */ | |
private void displayUI() { | |
this.list = new DefaultListModel<>(); | |
this.logs = new JList<>(list); | |
this.logs.setCellRenderer(new LogListCellRenderer()); | |
this.jScrollPane = new JScrollPane(logs); | |
this.jScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); | |
this.jScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); | |
this.mainFrame.add(this.jScrollPane); | |
} | |
/* ALL GETTER METHOD BELOW HERE */ | |
public void displayFrame() { | |
this.mainFrame.setVisible(true); | |
} | |
public DefaultListModel<LogModel> getList() { | |
return list; | |
} | |
} |
package assignment_08.views; | |
import assignment_08.views.components.Rod; | |
import javax.swing.*; | |
import java.awt.*; | |
/** | |
* This class represents main view of the desktop app | |
* | |
* @since June 8th 2021 | |
* @author Fajar Zuhri Hadiyanto | |
* @version 1.0 | |
* */ | |
public class MainView { | |
/** main Frame that contain all components */ | |
private JFrame mainFrame; | |
/** top container pane that contain top bar menu */ | |
private JPanel top; | |
/** menu container that contains disk form menu */ | |
private JPanel diskFormMenu; | |
/** label component for disk form */ | |
private JLabel diskLabel; | |
/** spinner component for disk form */ | |
private JSpinner diskSpinner; | |
/** menu container that contains move state menu */ | |
private JPanel stateMenu; | |
/** label component for move state menu */ | |
private JLabel stateLabel; | |
/** label component for total move */ | |
private JLabel moveState; | |
/** menu container that contains all option for application */ | |
private JPanel optionFormMenu; | |
/** button component to do restart game */ | |
private JButton restartButton; | |
/** button component to check movement log */ | |
private JButton logButton; | |
/** button component to solve the game automatically */ | |
private JButton solveButton; | |
/** button component to solve the game step by step */ | |
private JButton stepButton; | |
/** button component to go to the previous step of the solution */ | |
private JButton prevButton; | |
/** button component to go to the next step of the solution */ | |
private JButton nextButton; | |
/** main container that contain all three rods */ | |
private JPanel towersContainer; | |
/** array of {@link Rod} component as representation of each rod in the tower of hanoi */ | |
private Rod[] towers; | |
/** | |
* This constructor will initialize the window for the main app and render the UI | |
* */ | |
public MainView() { | |
this.initializeWindow(); | |
this.displayUI(); | |
} | |
/** | |
* This method is used to create and show new window with a certain configuration | |
* */ | |
private void initializeWindow() { | |
this.mainFrame = new JFrame("Tower of Hanoi"); | |
this.mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); | |
this.mainFrame.getContentPane().setLayout(new BoxLayout(this.mainFrame.getContentPane(), BoxLayout.Y_AXIS)); | |
this.mainFrame.setSize(600, 450); | |
this.mainFrame.setResizable(false); | |
this.mainFrame.setVisible(true); | |
} | |
/** | |
* This method is used to render all UI components | |
* */ | |
private void displayUI() { | |
displayTop(); | |
displayTowerContainer(); | |
} | |
/** | |
* This method is used to render all top menu components | |
* */ | |
private void displayTop() { | |
this.top = new JPanel(); | |
this.top.setLayout(new GridLayout(1, 3, 10, 10)); | |
this.top.setPreferredSize(new Dimension(600, 150)); | |
displayDiskFormMenu(); | |
displayStateMenu(); | |
displayOptionFormMenu(); | |
this.mainFrame.add(this.top); | |
} | |
/** | |
* This method is used to render UI for disk form menu | |
* */ | |
private void displayDiskFormMenu() { | |
this.diskFormMenu = new JPanel(); | |
this.diskFormMenu.setLayout(new FlowLayout()); | |
this.diskLabel = new JLabel("disk"); | |
this.diskSpinner = new JSpinner(); | |
this.diskFormMenu.add(this.diskLabel); | |
this.diskFormMenu.add(this.diskSpinner); | |
this.top.add(this.diskFormMenu); | |
} | |
/** | |
* This method is used to render UI for movement status top menu | |
* */ | |
private void displayStateMenu() { | |
stateMenu = new JPanel(); | |
this.stateLabel = new JLabel("Total Move"); | |
this.moveState = new JLabel(); | |
stateMenu.add(this.stateLabel); | |
stateMenu.add(this.moveState); | |
this.top.add(this.stateMenu); | |
} | |
/** | |
* This method is used to render UI for option button top menu | |
* */ | |
private void displayOptionFormMenu() { | |
this.optionFormMenu = new JPanel(); | |
this.optionFormMenu.setLayout(new GridLayout(2, 3, 5, 5)); | |
this.restartButton = new JButton("Restart"); | |
this.restartButton.setMargin(new Insets(0, 0, 0, 0)); | |
this.logButton = new JButton("Log"); | |
this.logButton.setMargin(new Insets(0, 0, 0, 0)); | |
this.solveButton = new JButton("Solve"); | |
this.solveButton.setMargin(new Insets(0, 0, 0, 0)); | |
this.prevButton = new JButton("<"); | |
this.prevButton.setEnabled(false); | |
this.stepButton = new JButton("Step by Step"); | |
this.stepButton.setMargin(new Insets(0, 0, 0, 0)); | |
this.nextButton = new JButton(">"); | |
this.nextButton.setEnabled(false); | |
this.optionFormMenu.add(this.restartButton); | |
this.optionFormMenu.add(this.logButton); | |
this.optionFormMenu.add(this.solveButton); | |
this.optionFormMenu.add(this.prevButton); | |
this.optionFormMenu.add(this.stepButton); | |
this.optionFormMenu.add(this.nextButton); | |
this.top.add(this.optionFormMenu); | |
} | |
/** | |
* This method is used to render all game UI | |
* */ | |
private void displayTowerContainer() { | |
this.towersContainer = new JPanel(); | |
this.towersContainer.setLayout(new GridLayout(1, 3, 10, 10)); | |
this.towersContainer.setPreferredSize(new Dimension(600, 300)); | |
this.towers = new Rod[3]; | |
displayTower(0, new Rod(3)); | |
displayTower(1, new Rod(0)); | |
displayTower(2, new Rod(0)); | |
this.mainFrame.add(towersContainer); | |
} | |
/** | |
* This method is used to render a single Rod UI | |
* */ | |
private void displayTower(int index, Rod rod) { | |
this.towers[index] = rod; | |
this.towersContainer.add(this.towers[index]); | |
} | |
/* ALL GETTER METHOD BELOW HERE */ | |
public JSpinner getDiskSpinner() { | |
return diskSpinner; | |
} | |
public JLabel getMoveState() { | |
return moveState; | |
} | |
public JButton getRestartButton() { | |
return restartButton; | |
} | |
public JButton getLogButton() { | |
return logButton; | |
} | |
public JButton getSolveButton() { | |
return solveButton; | |
} | |
public JButton getStepButton() { | |
return stepButton; | |
} | |
public JButton getPrevButton() { | |
return prevButton; | |
} | |
public JButton getNextButton() { | |
return nextButton; | |
} | |
public Rod[] getTowers() { | |
return towers; | |
} | |
} |
package assignment_08.views; | |
/** | |
* This class represents set of all necessary views in the application | |
* | |
* @since June 8th 2021 | |
* @author Fajar Zuhri Hadiyanto | |
* @version 1.0 | |
* */ | |
public class View { | |
/** instance of log view */ | |
private final LogView logView; | |
/** instance of main view */ | |
private final MainView mainView; | |
/** | |
* This constructor is used to create all corresponding views | |
* */ | |
public View() { | |
this.logView = new LogView(); | |
this.mainView = new MainView(); | |
} | |
/* ALL GETTER METHOD BELOW HERE */ | |
public LogView getLogView() { | |
return this.logView; | |
} | |
public MainView getMainView() { | |
return this.mainView; | |
} | |
} |
Controller
Controller merupakan bagian dari aplikasi yang bertanggung jawab untuk menangani proses-proses yang terjadi pada aplikasi dan juga sebagai perantara antara model yang menyediakan data dengan view yang menampilkan data. Berikut ini merupakan implementasi controller untuk aplikasi tower of hanoi
package assignment_08.controllers; | |
import assignment_08.models.Model; | |
import assignment_08.views.View; | |
/** | |
* This class represents set of all necessary controllers in the application | |
* | |
* @since June 8th 2021 | |
* @author Fajar Zuhri Hadiyanto | |
* @version 1.0 | |
* */ | |
public class Controller { | |
/** instance of main controller */ | |
private final MainController mainController; | |
/** instance of tower of hanoi controller */ | |
private final TowerOfHanoiController towerOfHanoiController; | |
/** | |
* This constructor will create new main controller and tower of hanoi controller with a given model and view | |
* | |
* @param model model used by this controller | |
* @param view view used by this controller | |
* */ | |
public Controller(Model model, View view) { | |
this.mainController = new MainController(model, view); | |
this.towerOfHanoiController = new TowerOfHanoiController(model, view); | |
} | |
} |
package assignment_08.controllers; | |
import assignment_08.models.Model; | |
import assignment_08.views.View; | |
import javax.swing.*; | |
import java.util.Stack; | |
/** | |
* This class represents controller of model and view related to the main application | |
* | |
* @since June 8th 2021 | |
* @author Fajar Zuhri Hadiyanto | |
* @version 1.0 | |
* */ | |
public class MainController { | |
/** Model used by this controller */ | |
private final Model model; | |
/** View used by this controller */ | |
private final View view; | |
/** | |
* This constructor will create new controller with a given model and view | |
* | |
* @param model model used by this controller | |
* @param view view used by this controller | |
* */ | |
public MainController(Model model, View view) { | |
this.model = model; | |
this.view = view; | |
this.initView(); | |
this.initController(); | |
} | |
/** | |
* This method is used to initialize the view that need data from the model | |
* */ | |
public void initView() { | |
this.view.getMainView().getDiskSpinner().setModel(new SpinnerNumberModel(this.model.getStateModel().getnDisk(), 3, 8, 1)); | |
this.view.getMainView().getMoveState().setText(String.valueOf(this.model.getStateModel().getTotalMoves())); | |
} | |
/** | |
* This method is used to initialize the controller by setting event listener for every control | |
* related to the main app | |
* */ | |
public void initController() { | |
// EVENT LISTENER OF THE SPINNER | |
this.view.getMainView().getDiskSpinner().addChangeListener(e -> { | |
int num = (int) ((JSpinner)e.getSource()).getValue(); | |
this.model.getStateModel().setnDisk(num); | |
this.initGame(); | |
}); | |
// EVENT LISTENER OF THE RESTART BUTTON | |
this.view.getMainView().getRestartButton().addActionListener(e -> { | |
this.initGame(); | |
}); | |
// EVENT LISTENER OF THE LOG BUTTON | |
this.view.getMainView().getLogButton().addActionListener(e -> { | |
this.view.getLogView().displayFrame(); | |
}); | |
} | |
/** | |
* This method is used to initialize the game | |
* */ | |
private void initGame() { | |
try { | |
// REINITIALIZE THE ROD MODEL | |
this.model.getRodsModel().reinitiate(this.model.getStateModel().getnDisk()); | |
this.view.getMainView().getTowers()[0].updateDisk(new Stack<>()); | |
for (int i = this.model.getStateModel().getnDisk(); i > 0; i--) { | |
this.view.getMainView().getTowers()[0].addDisk(i); | |
} | |
this.view.getMainView().getTowers()[1].updateDisk(new Stack<>()); | |
this.view.getMainView().getTowers()[2].updateDisk(new Stack<>()); | |
// RESET THE MOVE STATE | |
this.model.getStateModel().setTotalMoves(0); | |
this.view.getMainView().getMoveState().setText(String.valueOf(0)); | |
// CLEAR THE LOGS | |
this.model.getLogsModel().getLogs().clear(); | |
this.view.getLogView().getList().clear(); | |
// ENABLE THE START GAME BUTTON | |
this.view.getMainView().getSolveButton().setEnabled(true); | |
this.view.getMainView().getStepButton().setEnabled(true); | |
// DISABLE THE NEXT AND PREVIOUS BUTTON | |
this.view.getMainView().getPrevButton().setEnabled(false); | |
this.view.getMainView().getNextButton().setEnabled(false); | |
// STOP THE RUNNING THREAD WITH NAME "toh" IF ITS EXIST | |
for (Thread t : Thread.getAllStackTraces().keySet()) { | |
if (t.getName().equals("toh")) { | |
t.stop(); | |
} | |
} | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} | |
} | |
} |
package assignment_08.controllers; | |
import assignment_08.models.LogModel; | |
import assignment_08.models.Model; | |
import assignment_08.views.View; | |
import java.util.ArrayList; | |
import java.util.ListIterator; | |
/** | |
* This class represents controller of model and view related to the main game | |
* | |
* @since June 8th 2021 | |
* @author Fajar Zuhri Hadiyanto | |
* @version 1.0 | |
* */ | |
public class TowerOfHanoiController implements Runnable { | |
/** Model used by this controller */ | |
private final Model model; | |
/** View used by this controller */ | |
private final View view; | |
/** field to contain integer iterator to do step by step solution */ | |
private ListIterator<Integer> moveIt; | |
/** | |
* This method override from {@link Runnable} interface | |
* */ | |
@Override | |
public void run() { | |
try { | |
this.solve(this.model.getStateModel().getnDisk(), 0, 2, 1); | |
} catch (Exception exception) { | |
exception.printStackTrace(); | |
} | |
} | |
/** | |
* This constructor will create new controller with a given model and view | |
* | |
* @param model model used by this controller | |
* @param view view used by this controller | |
* */ | |
public TowerOfHanoiController(Model model, View view) { | |
this.model = model; | |
this.view = view; | |
initController(); | |
} | |
/** | |
* This method is used to initialize the controller by setting event listener for every control | |
* related to the main game | |
* */ | |
public void initController() { | |
// EVENT LISTENER OF THE SOLVE BUTTON | |
this.view.getMainView().getSolveButton().addActionListener(e -> { | |
this.view.getMainView().getSolveButton().setEnabled(false); | |
this.view.getMainView().getStepButton().setEnabled(false); | |
this.getT().start(); | |
}); | |
// EVENT LISTENER OF THE STEP BY STEP BUTTON | |
this.view.getMainView().getStepButton().addActionListener(e -> { | |
this.view.getMainView().getSolveButton().setEnabled(false); | |
this.view.getMainView().getStepButton().setEnabled(false); | |
ArrayList<Integer> list = new ArrayList<>(); | |
for (int i = 1; i < Math.pow(2, this.model.getStateModel().getnDisk()); i++) { | |
list.add(i); | |
} | |
this.moveIt = list.listIterator(); | |
if (this.moveIt.hasNext()) this.view.getMainView().getNextButton().setEnabled(true); | |
}); | |
// EVENT LISTENER OF THE PREVIOUS BUTTON | |
this.view.getMainView().getPrevButton().addActionListener(e -> { | |
try { | |
this.stepSolution(false); | |
this.view.getMainView().getNextButton().setEnabled(true); | |
if (!this.moveIt.hasPrevious()) this.view.getMainView().getPrevButton().setEnabled(false); | |
} catch (Exception ignored) { | |
} | |
}); | |
// EVENT LISTENER OF THE NEXT BUTTON | |
this.view.getMainView().getNextButton().addActionListener(e -> { | |
try { | |
this.stepSolution(true); | |
this.view.getMainView().getPrevButton().setEnabled(true); | |
if (!this.moveIt.hasNext()) this.view.getMainView().getNextButton().setEnabled(false); | |
} catch (Exception ignored) { | |
} | |
}); | |
} | |
/** | |
* This method is used to do one step solution, either forward or backward | |
* | |
* @param forward flag parameter used to indicated if its forward or backward | |
* */ | |
private void stepSolution(boolean forward) throws Exception { | |
// GET CURRENT ITERATOR | |
int num = forward ? this.moveIt.next() % 3 : this.moveIt.previous() % 3; | |
// GET THE LEFT, MIDDLE, AND THE RIGHT INDEX AND ROD DATA | |
int leftDisk = this.view.getMainView().getTowers()[0].getTopDisk(), middleDisk, rightDisk, middleIndex, rightIndex; | |
if (this.model.getStateModel().getnDisk() % 2 == 1) { | |
middleIndex = 1; | |
rightIndex = 2; | |
} else { | |
middleIndex = 2; | |
rightIndex = 1; | |
} | |
middleDisk = this.view.getMainView().getTowers()[middleIndex].getTopDisk(); | |
rightDisk = this.view.getMainView().getTowers()[rightIndex].getTopDisk(); | |
// MOVEMENT CONSIDERATION | |
switch (num) { | |
case 1: | |
if (leftDisk == 0) this.moveDisk(rightIndex, 0, forward); | |
else if (rightDisk == 0) this.moveDisk(0, rightIndex, forward); | |
else if (leftDisk > rightDisk) this.moveDisk(rightIndex, 0, forward); | |
else this.moveDisk(0, rightIndex, forward); | |
break; | |
case 2: | |
if (leftDisk == 0) this.moveDisk(middleIndex,0, forward); | |
else if (middleDisk == 0) this.moveDisk(0, middleIndex, forward); | |
else if (leftDisk > middleDisk) this.moveDisk(middleIndex, 0, forward); | |
else this.moveDisk(0, middleIndex, forward); | |
break; | |
case 0: | |
if (rightDisk == 0) this.moveDisk(middleIndex,rightIndex, forward); | |
else if (middleDisk == 0) this.moveDisk(rightIndex, middleIndex, forward); | |
else if (rightDisk > middleDisk) this.moveDisk(middleIndex, rightIndex, forward); | |
else this.moveDisk(rightIndex, middleIndex, forward); | |
break; | |
default: | |
break; | |
} | |
} | |
/** | |
* This method is used to solve the game automatically | |
* | |
* @param num number of the disk | |
* @param from index of the from rod | |
* @param to index of the to rod | |
* @param aux index of the auc rod | |
* */ | |
private void solve(int num, int from, int to, int aux) throws Exception { | |
// BASE CASE, WHEN THE NUM IS 1 | |
if (num == 1) { | |
moveDisk(from, to, true); | |
Thread.sleep(500); | |
return; | |
} | |
solve(num - 1, from, aux, to); | |
moveDisk(from, to, true); | |
Thread.sleep(500); | |
solve(num - 1, aux, to, from); | |
} | |
/** | |
* This method is used to move a disk from one rod to another rod | |
* | |
* @param from index of the from rod | |
* @param to index of the to rod | |
* @param forward flag that indicate if the movement is forward or backward | |
* */ | |
private void moveDisk(int from, int to, boolean forward) throws Exception { | |
this.model.getRodsModel().moveDisk(from, to); | |
if (forward) { | |
this.model.getStateModel().incrementTotalMoves(); | |
this.model.getLogsModel().getLogs().add(new LogModel(from, to, this.view.getMainView().getTowers()[from].getTopDisk())); | |
this.view.getLogView().getList().addElement(new LogModel(from, to, this.view.getMainView().getTowers()[from].getTopDisk())); | |
} else { | |
this.model.getStateModel().decrementTotalMoves(); | |
this.model.getLogsModel().getLogs().pop(); | |
this.view.getLogView().getList().remove(this.view.getLogView().getList().getSize() - 1); | |
} | |
this.view.getMainView().getTowers()[to].addDisk(this.view.getMainView().getTowers()[from].getTopDisk()); | |
this.view.getMainView().getTowers()[from].removeDisk(); | |
this.view.getMainView().getMoveState().setText(String.valueOf(this.model.getStateModel().getTotalMoves())); | |
} | |
/** | |
* This method is used to create new Thread for game solving | |
* | |
* @return new Thread with name "toh" | |
* */ | |
public Thread getT() { | |
return new Thread(this, "toh"); | |
} | |
} |
Aplikasi
Untuk menggabungkan ketiga unsur MVC, maka dibuat satu class tambahan yang berperan sebagai class utama yang menjalankan aplikasi. Berikut ini merupakan source code untuk class utama.
package assignment_08; | |
import assignment_08.models.Model; | |
import assignment_08.controllers.Controller; | |
import assignment_08.models.LogsModel; | |
import assignment_08.models.RodsModel; | |
import assignment_08.models.StateModel; | |
import assignment_08.views.View; | |
/** | |
* This class represents main application | |
* | |
* @since June 8th 2021 | |
* @author Fajar Zuhri Hadiyanto | |
* @version 1.0 | |
* */ | |
public class MainApplication { | |
/** | |
* This method is used as main method | |
* | |
* @param args arguments to the console app while compiled and launched. | |
* */ | |
public static void main(String[] args) { | |
Model model = new Model( | |
new LogsModel(), | |
new RodsModel(3), | |
new StateModel(3, 0) | |
); | |
View view = new View(); | |
Controller controller = new Controller(model, view); | |
} | |
} |
Setelah class utama tersebut dijalankan, maka hasilnya akan seperti di video demo dibawah ini
Source code dari program di atas dapat diakses disini
Comments
Post a Comment