Saturday, October 13, 2012

Snakes and Ladders in Java Swing

Overview

We all know what 'Snakes and ladders' game is all about. In this artice, I am going write a Java Swing based game for two players. Before I begin, in case my rules for the game are different, I want explain this first. If you want to quickly jump over to the solution click here.

Rules of the game

The game has multiple players where players take turns to roll dice.
If on any player's turn the number on the dice is '1' or '6', the player's token moves forward to that many places (1 or 6) and also gets to throw one more time.
To start the game itself, a player needs to roll 1 or 6. This moves the token to the start position on the board or 1. The player again gets to throw.
If a player's token lands on the bottom of a ladder, the token moves to the top of the ladder (always greater than the current position).
Conversely, if the player's token lands on snake's mouth, then token moves to the tail of the snake (always less that the current position).
Whoever gets to the last number or greater wins. I know this logic is a bit off from the regular game where the player's token needs to land on the exact 'final' number in order to win or the token just moves the remaining position backwards.

Technical Requirements

We need a main GUI where the board is visible and allows users to see where their tokens are.
Players need to be created and their current token position maintained.
Users also need to be able to click on a "Roll Dice" button.
Every "roll" needs to randomly generate values from 1 through 6 only.
The tokens need to move to the correct position on the grid/board.
When a player's token reaches the final number in the board, a message should be printed indicating which player won and the game should then stop.
Users should be able to start the game by clicking the top menu on "File -> New game" .

Solution

Create a new Swing Application and add a FrameView. I called it SnakesAndLadderView. The snippet below shows the initizalization of the Swing application. Notice the class variables Player: Player1 and Player: Player2. I will come to this later. This takes care of our first requirement.
package snakesandladder;
import java.awt.Color;
import java.awt.Graphics;
import org.jdesktop.application.Action;
import org.jdesktop.application.ResourceMap;
import org.jdesktop.application.SingleFrameApplication;
import org.jdesktop.application.FrameView;
import org.jdesktop.application.TaskMonitor;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;
import javax.swing.Timer;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JDialog;
import javax.swing.JFrame;
import snakesandladder.domain.Player;
/**
* The application's main frame.
*/
public class SnakesAndLadderView extends FrameView {
private Player currentPlayer;
Player player1 = new Player("red", "Player1");
Player player2 = new Player("yellow", "Player2");
boolean canRollAgain;
private javax.swing.JButton btnRoll;
private javax.swing.JLabel lblDiceValue;
private javax.swing.JLabel lblMain;
private javax.swing.JLabel lblPlayer1;
private javax.swing.JLabel lblPlayer2;
private javax.swing.JPanel mainPanel;
private javax.swing.JMenuBar menuBar;
private javax.swing.JMenuItem newGameMenuItem;
private javax.swing.JPanel pnlGame;
private javax.swing.JLabel statusAnimationLabel;
private javax.swing.JLabel statusMessageLabel;
private javax.swing.JPanel statusPanel;
private javax.swing.JTextField txtStatus;
// End of variables declaration
private final Timer messageTimer;
private final Timer busyIconTimer;
private final Icon idleIcon;
private final Icon[] busyIcons = new Icon[15];
private int busyIconIndex = 0;
private JDialog aboutBox;
/**
* @return the currentPlayer
*/
public Player getCurrentPlayer() {
return currentPlayer;
}
/**
* @param currentPlayer the currentPlayer to set
*/
public void setCurrentPlayer(Player currentPlayer) {
this.currentPlayer = currentPlayer;
}
public SnakesAndLadderView(SingleFrameApplication app) {
super(app);
initComponents();
this.pnlGame.setVisible(false);
this.btnRoll.setVisible(false);
this.txtStatus.setVisible(false);
this.lblPlayer1.setVisible(false);
this.lblPlayer2.setVisible(false);

// status bar initialization - message timeout, idle icon and busy animation, etc
ResourceMap resourceMap = getResourceMap();
int messageTimeout = resourceMap.getInteger("StatusBar.messageTimeout");
messageTimer = new Timer(messageTimeout, new ActionListener() {
public void actionPerformed(ActionEvent e) {
statusMessageLabel.setText("");
}
});
messageTimer.setRepeats(false);
int busyAnimationRate = resourceMap.getInteger("StatusBar.busyAnimationRate");
for (int i = 0; i < busyIcons.length; i++) {
busyIcons[i] = resourceMap.getIcon("StatusBar.busyIcons[" + i + "]");
}
busyIconTimer = new Timer(busyAnimationRate, new ActionListener() {
public void actionPerformed(ActionEvent e) {
busyIconIndex = (busyIconIndex + 1) % busyIcons.length;
statusAnimationLabel.setIcon(busyIcons[busyIconIndex]);
}
});
idleIcon = resourceMap.getIcon("StatusBar.idleIcon");
statusAnimationLabel.setIcon(idleIcon);
// progressBar.setVisible(false);
// connecting action tasks to status bar via TaskMonitor
TaskMonitor taskMonitor = new TaskMonitor(getApplication().getContext());
taskMonitor.addPropertyChangeListener(new java.beans.PropertyChangeListener() {
public void propertyChange(java.beans.PropertyChangeEvent evt) {
String propertyName = evt.getPropertyName();
if ("started".equals(propertyName)) {
if (!busyIconTimer.isRunning()) {
statusAnimationLabel.setIcon(busyIcons[0]);
busyIconIndex = 0;
busyIconTimer.start();
}
// progressBar.setVisible(true);
// progressBar.setIndeterminate(true);
} else if ("done".equals(propertyName)) {
busyIconTimer.stop();
statusAnimationLabel.setIcon(idleIcon);
//progressBar.setVisible(false);
// progressBar.setValue(0);
} else if ("message".equals(propertyName)) {
String text = (String) (evt.getNewValue());
statusMessageLabel.setText((text == null) ? "" : text);
messageTimer.restart();
} else if ("progress".equals(propertyName)) {
int value = (Integer) (evt.getNewValue());
// progressBar.setVisible(true);
// progressBar.setIndeterminate(false);
// progressBar.setValue(value);
}
}
});

}
@Action
public void showAboutBox() {
if (aboutBox == null) {
JFrame mainFrame = SnakesAndLadderApp.getApplication().getMainFrame();
aboutBox = new SnakesAndLadderAboutBox(mainFrame);
aboutBox.setLocationRelativeTo(mainFrame);
}
SnakesAndLadderApp.getApplication().show(aboutBox);
}
/** This method is called from within the constructor to
* initialize the form.
* WARNING: Do NOT modify this code. The content of this method is
* always regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">
private void initComponents() {
mainPanel = new javax.swing.JPanel();
pnlGame = new javax.swing.JPanel();
lblMain = new javax.swing.JLabel();
menuBar = new javax.swing.JMenuBar();
javax.swing.JMenu fileMenu = new javax.swing.JMenu();
newGameMenuItem = new javax.swing.JMenuItem();
javax.swing.JMenuItem exitMenuItem = new javax.swing.JMenuItem();
javax.swing.JMenu helpMenu = new javax.swing.JMenu();
javax.swing.JMenuItem aboutMenuItem = new javax.swing.JMenuItem();
statusPanel = new javax.swing.JPanel();
javax.swing.JSeparator statusPanelSeparator = new javax.swing.JSeparator();
statusMessageLabel = new javax.swing.JLabel();
statusAnimationLabel = new javax.swing.JLabel();
txtStatus = new javax.swing.JTextField();
btnRoll = new javax.swing.JButton();
lblPlayer1 = new javax.swing.JLabel();
lblPlayer2 = new javax.swing.JLabel();
lblDiceValue = new javax.swing.JLabel();
mainPanel.setMaximumSize(new java.awt.Dimension(583, 560));
mainPanel.setMinimumSize(new java.awt.Dimension(583, 560));
mainPanel.setName("mainPanel"); // NOI18N
pnlGame.setMaximumSize(new java.awt.Dimension(583, 560));
pnlGame.setName("pnlGame"); // NOI18N
org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(snakesandladder.SnakesAndLadderApp.class).getContext().getResourceMap(SnakesAndLadderView.class);
lblMain.setBackground(resourceMap.getColor("lblMain.background")); // NOI18N
lblMain.setIcon(resourceMap.getIcon("lblMain.icon")); // NOI18N
lblMain.setText(resourceMap.getString("lblMain.text")); // NOI18N
lblMain.setName("lblMain"); // NOI18N
javax.swing.GroupLayout pnlGameLayout = new javax.swing.GroupLayout(pnlGame);
pnlGame.setLayout(pnlGameLayout);
pnlGameLayout.setHorizontalGroup(
pnlGameLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(pnlGameLayout.createSequentialGroup()
.addGap(27, 27, 27)
.addComponent(lblMain, javax.swing.GroupLayout.PREFERRED_SIZE, 506, javax.swing.GroupLayout.PREFERRED_SIZE)
.addContainerGap(30, Short.MAX_VALUE))
);
pnlGameLayout.setVerticalGroup(
pnlGameLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(pnlGameLayout.createSequentialGroup()
.addComponent(lblMain, javax.swing.GroupLayout.DEFAULT_SIZE, 538, Short.MAX_VALUE)
.addContainerGap())
);
javax.swing.GroupLayout mainPanelLayout = new javax.swing.GroupLayout(mainPanel);
mainPanel.setLayout(mainPanelLayout);
mainPanelLayout.setHorizontalGroup(
mainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(mainPanelLayout.createSequentialGroup()
.addContainerGap()
.addComponent(pnlGame, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
mainPanelLayout.setVerticalGroup(
mainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(mainPanelLayout.createSequentialGroup()
.addComponent(pnlGame, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addContainerGap())
);
menuBar.setDoubleBuffered(true);
menuBar.setMaximumSize(new java.awt.Dimension(62, 21));
menuBar.setMinimumSize(new java.awt.Dimension(62, 21));
menuBar.setName("menuBar"); // NOI18N
fileMenu.setText(resourceMap.getString("fileMenu.text")); // NOI18N
fileMenu.setName("fileMenu"); // NOI18N
javax.swing.ActionMap actionMap = org.jdesktop.application.Application.getInstance(snakesandladder.SnakesAndLadderApp.class).getContext().getActionMap(SnakesAndLadderView.class, this);
newGameMenuItem.setAction(actionMap.get("startGame")); // NOI18N
newGameMenuItem.setText(resourceMap.getString("newGameMenuItem.text")); // NOI18N
newGameMenuItem.setName("newGameMenuItem"); // NOI18N
fileMenu.add(newGameMenuItem);
exitMenuItem.setAction(actionMap.get("quit")); // NOI18N
exitMenuItem.setName("exitMenuItem"); // NOI18N
fileMenu.add(exitMenuItem);
menuBar.add(fileMenu);
helpMenu.setText(resourceMap.getString("helpMenu.text")); // NOI18N
helpMenu.setName("helpMenu"); // NOI18N
aboutMenuItem.setAction(actionMap.get("showAboutBox")); // NOI18N
aboutMenuItem.setName("aboutMenuItem"); // NOI18N
helpMenu.add(aboutMenuItem);
menuBar.add(helpMenu);
statusPanel.setMaximumSize(new java.awt.Dimension(583, 82));
statusPanel.setMinimumSize(new java.awt.Dimension(583, 82));
statusPanel.setName("statusPanel"); // NOI18N
statusPanelSeparator.setName("statusPanelSeparator"); // NOI18N
statusMessageLabel.setName("statusMessageLabel"); // NOI18N
statusAnimationLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
statusAnimationLabel.setName("statusAnimationLabel"); // NOI18N
txtStatus.setText(resourceMap.getString("txtStatus.text")); // NOI18N
txtStatus.setName("txtStatus"); // NOI18N
btnRoll.setText(resourceMap.getString("btnRoll.text")); // NOI18N
btnRoll.setName("btnRoll"); // NOI18N
btnRoll.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
btnRollActionPerformed(evt);
}
});
lblPlayer1.setIcon(resourceMap.getIcon("lblPlayer1.icon")); // NOI18N
lblPlayer1.setText(resourceMap.getString("lblPlayer1.text")); // NOI18N
lblPlayer1.setName("lblPlayer1"); // NOI18N
lblPlayer2.setIcon(resourceMap.getIcon("lblPlayer2.icon")); // NOI18N
lblPlayer2.setText(resourceMap.getString("lblPlayer2.text")); // NOI18N
lblPlayer2.setName("lblPlayer2"); // NOI18N
lblDiceValue.setText(resourceMap.getString("lblDiceValue.text")); // NOI18N
lblDiceValue.setName("lblDiceValue"); // NOI18N
javax.swing.GroupLayout statusPanelLayout = new javax.swing.GroupLayout(statusPanel);
statusPanel.setLayout(statusPanelLayout);
statusPanelLayout.setHorizontalGroup(
statusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(statusPanelLayout.createSequentialGroup()
.addContainerGap()
.addGroup(statusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(statusMessageLabel)
.addGroup(statusPanelLayout.createSequentialGroup()
.addComponent(txtStatus, javax.swing.GroupLayout.PREFERRED_SIZE, 178, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(18, 18, 18)
.addComponent(lblDiceValue, javax.swing.GroupLayout.PREFERRED_SIZE, 157, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(18, 18, 18)
.addGroup(statusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(lblPlayer1)
.addComponent(lblPlayer2))
.addGroup(statusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(statusPanelLayout.createSequentialGroup()
.addGap(122, 122, 122)
.addGroup(statusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(statusPanelSeparator, javax.swing.GroupLayout.DEFAULT_SIZE, 24, Short.MAX_VALUE)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, statusPanelLayout.createSequentialGroup()
.addComponent(statusAnimationLabel)
.addContainerGap())))
.addGroup(statusPanelLayout.createSequentialGroup()
.addGap(18, 18, 18)
.addComponent(btnRoll, javax.swing.GroupLayout.PREFERRED_SIZE, 73, javax.swing.GroupLayout.PREFERRED_SIZE)
.addContainerGap())))))
);
statusPanelLayout.setVerticalGroup(
statusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(statusPanelLayout.createSequentialGroup()
.addGroup(statusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(statusPanelLayout.createSequentialGroup()
.addComponent(statusPanelSeparator, javax.swing.GroupLayout.PREFERRED_SIZE, 2, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addGroup(statusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(lblPlayer1)
.addComponent(btnRoll)))
.addGroup(statusPanelLayout.createSequentialGroup()
.addGap(16, 16, 16)
.addGroup(statusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(txtStatus, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(lblDiceValue))))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(statusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(statusPanelLayout.createSequentialGroup()
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 18, Short.MAX_VALUE)
.addGroup(statusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(statusMessageLabel)
.addComponent(statusAnimationLabel))
.addGap(22, 22, 22))
.addGroup(statusPanelLayout.createSequentialGroup()
.addComponent(lblPlayer2)
.addContainerGap())))
);
setComponent(mainPanel);
setMenuBar(menuBar);
setStatusBar(statusPanel);
}

Now to take care of rest of the logic. Notice that I have used x and y co-ordinates to move the player's tokens. "changeActualPosition()" passes an int value and the co-ordinates based on the board's geometry is returned. I simply created two buffered images of ovals with colors 'yellow' and 'red' with size 30px by 30 px. The "rollAgain()" method generates a random double between 1 and 6 and is converted into int. We really want to lose the precision in this case. When the player wins, the "canRollAgain" class level boolean is set to false and the status message displays "Player x" wins the games.
private void btnRollActionPerformed(java.awt.event.ActionEvent evt) {
// TODO add your handling code here:

double l = Math.ceil(Math.random() * 6);
boolean status = getCurrentPlayer().isStatus();
this.lblDiceValue.setText("dice rolles out number.." + (int) l);
if (l == 1 || l == 6) {
canRollAgain = true;
}
//when status is false
if (!status) {
//Not 1 or 6
if ((l < 6) && (l > 1)) {
if (currentPlayer.getName().equals("Player1")) {
this.txtStatus.setText("Player 2's turn to roll the dice!");
this.setCurrentPlayer(player2);
} else {
this.txtStatus.setText("Player 1's turn to roll the dice!");
this.setCurrentPlayer(player1);
}
} else {
if (currentPlayer.getName().equals("Player1")) {
this.player1.setStatus(true);
this.txtStatus.setText("Player " + this.currentPlayer.getName() + "'s turn to roll the dice again!");
} else {
this.player2.setStatus(true);
this.txtStatus.setText("Player " + this.currentPlayer.getName() + "'s turn to roll the dice again!");
}
canRollAgain = false;
}
//when status is true
} else {
if (canRollAgain){
this.move(l);
this.txtStatus.setText("Player " + this.currentPlayer.getName() + "'s turn to roll the dice again!");
canRollAgain = false;
}else{
this.move(l);
if (currentPlayer.getName().equals("Player1")) {
this.txtStatus.setText("Player 2's turn to roll the dice!");
this.setCurrentPlayer(player2);
} else {
this.txtStatus.setText("Player 1's turn to roll the dice!");
this.setCurrentPlayer(player1);
}
}
}
}
@Action
public void startGame() {
txtStatus.setEditable(false);
this.pnlGame.setVisible(true);
this.btnRoll.setVisible(true);
this.txtStatus.setVisible(true);
this.lblPlayer1.setVisible(true);
this.lblPlayer2.setVisible(true);
this.setCurrentPlayer(player1);
this.txtStatus.setText("Player 1's turn to roll the dice");
}
// Variables declaration - do not modify
private javax.swing.JButton btnRoll;
private javax.swing.JLabel lblDiceValue;
private javax.swing.JLabel lblMain;
private javax.swing.JLabel lblPlayer1;
private javax.swing.JLabel lblPlayer2;
private javax.swing.JPanel mainPanel;
private javax.swing.JMenuBar menuBar;
private javax.swing.JMenuItem newGameMenuItem;
private javax.swing.JPanel pnlGame;
private javax.swing.JLabel statusAnimationLabel;
private javax.swing.JLabel statusMessageLabel;
private javax.swing.JPanel statusPanel;
private javax.swing.JTextField txtStatus;
// End of variables declaration
private final Timer messageTimer;
private final Timer busyIconTimer;
private final Icon idleIcon;
private final Icon[] busyIcons = new Icon[15];
private int busyIconIndex = 0;
private JDialog aboutBox;
/**
* @return the currentPlayer
*/
public Player getCurrentPlayer() {
return currentPlayer;
}
/**
* @param currentPlayer the currentPlayer to set
*/
public void setCurrentPlayer(Player currentPlayer) {
this.currentPlayer = currentPlayer;
}
private void move(double l) {
currentPlayer.setPosition(currentPlayer.getPosition() + (int) l);
changeActualPostion();
if (currentPlayer.getPosition()>=64){
this.txtStatus.setText(this.currentPlayer.getName() + " wins!!!!");
this.btnRoll.setEnabled(false);
}
try {
BufferedImage image = ImageIO.read(new File("lib/board.gif"));
Graphics g = image.getGraphics();
int transparency = 90;
Color color =new Color(255, 0, 0, 255 * transparency / 100);
int x = getX(player1.getPosition());
int y = getY(player1.getPosition());
g.setColor(color);
g.fillOval(x, y, 30, 30);

int x2 = getX(player2.getPosition());
int y2 = getY(player2.getPosition());
color = new Color(255, 255, 0, 255 * transparency / 100);
g.setColor(color);
g.fillOval(x2, y2, 30, 30);
ImageIcon icon = new ImageIcon(image);
icon.getImage().flush();
lblMain.setIcon(icon);
//ImageIO.write(image, "jpg", new File("d:\\temp\\output.bmp"));

} catch (Exception e) {
System.out.println(e);
}
if (currentPlayer.getName().equals("Player1")) {
this.player1.setPosition(currentPlayer.getPosition());
} else {
this.player2.setPosition(currentPlayer.getPosition());
}

}

private void rollAgain() {
double l = Math.ceil(Math.random() * 6);
this.lblDiceValue.setText("Dice rolles out number......." + (int) l);
move(l);
}
private void changeActualPostion() {
switch (currentPlayer.getPosition()) {
case 3:
currentPlayer.setPosition(18);
break;
case 19:
currentPlayer.setPosition(5);
break;
case 24:
currentPlayer.setPosition(39);
break;
case 27:
currentPlayer.setPosition(8);
break;
case 29:
currentPlayer.setPosition(53);
break;
case 62:
currentPlayer.setPosition(32);
break;
case 58:
currentPlayer.setPosition(41);
break;
case 48:
currentPlayer.setPosition(63);
break;
}
}
private int getX(int pos) {
int x = 0;
switch (pos) {
case 1:
x = 20;
break;
case 2:
x = 82;
break;
case 3:
x = 82 + 62;
break;
case 4:
x = 82 + 62 + 62;
break;
case 5:
x = 82 + 62 + 62 + 62;
break;
case 6:
x = 82 + 62 + 62 + 62 + 62;
break;
case 7:
x = 82 + 62 + 62 + 62 + 62 + 62;
break;
case 8:
x = 82 + 62 + 62 + 62 + 62 + 62 + 62;
break;
case 9:
x = 82 + 62 + 62 + 62 + 62 + 62 + 62;
break;
case 10:
x = 82 + 62 + 62 + 62 + 62 + 62;
break;
case 11:
x = 82 + 62 + 62 + 62 + 62;
break;
case 12:
x = 82 + 62 + 62 + 62;
break;
case 13:
x = 82 + 62 + 62;
break;
case 14:
x = 82 + 62;
break;
case 15:
x = 82;
break;
case 16:
x = 20;
break;
case 17:
x = 20;
break;
case 18:
x = 82;
break;
case 19:
x = 82 + 62;
break;
case 20:
x = 82 + 62 + 62;
break;
case 21:
x = 82 + 62 + 62 + 62;
break;
case 22:
x = 82 + 62 + 62 + 62 + 62;
break;
case 23:
x = 82 + 62 + 62 + 62 + 62 + 62;
break;
case 24:
x = 82 + 62 + 62 + 62 + 62 + 62 + 62;
break;
case 25:
x = 82 + 62 + 62 + 62 + 62 + 62 + 62;
break;
case 26:
x = 82 + 62 + 62 + 62 + 62 + 62;
break;
case 27:
x = 82 + 62 + 62 + 62 + 62;
break;
case 28:
x = 82 + 62 + 62 + 62;
break;
case 29:
x = 82 + 62 + 62;
break;
case 30:
x = 82 + 62;
break;
case 31:
x = 82;
break;
case 32:
x = 20;
break;
case 33:
x = 20;
break;
case 34:
x = 82;
break;
case 35:
x = 82 + 62;
break;
case 36:
x = 82 + 62 + 62;
break;
case 37:
x = 82 + 62 + 62 + 62;
break;
case 38:
x = 82 + 62 + 62 + 62 + 62;
break;
case 39:
x = 82 + 62 + 62 + 62 + 62 + 62;
break;
case 40:
x = 82 + 62 + 62 + 62 + 62 + 62 + 62;
break;
case 41:
x = 82 + 62 + 62 + 62 + 62 + 62 + 62;
break;
case 42:
x = 82 + 62 + 62 + 62 + 62 + 62;
break;
case 43:
x = 82 + 62 + 62 + 62 + 62;
break;
case 44:
x = 82 + 62 + 62 + 62;
break;
case 45:
x = 82 + 62 + 62;
break;
case 46:
x = 82 + 62;
break;
case 47:
x = 82;
break;
case 48:
x = 20;
break;
case 49:
x = 20;
break;
case 50:
x = 82;
break;
case 51:
x = 82 + 62;
break;
case 52:
x = 82 + 62 + 62;
break;
case 53:
x = 82 + 62 + 62 + 62;
break;
case 54:
x = 82 + 62 + 62 + 62 + 62;
break;
case 55:
x = 82 + 62 + 62 + 62 + 62 + 62;
break;
case 56:
x = 82 + 62 + 62 + 62 + 62 + 62 + 62;
break;
case 57:
x = 82 + 62 + 62 + 62 + 62 + 62 + 62;
break;
case 58:
x = 82 + 62 + 62 + 62 + 62 + 62;
break;
case 59:
x = 82 + 62 + 62 + 62 + 62;
break;
case 60:
x = 82 + 62 + 62 + 62;
break;
case 61:
x = 82 + 62 + 62;
break;
case 62:
x = 82 + 62;
break;
case 63:
x = 82;
break;
case 64:
x = 20;
break;
}
return x;
}
private int getY(int pos) {
int y = 0;
if (pos > 0 && pos < 9) {
y = 60 + 62 + 62 + 62 + 62 + 62 + 62 + 62;
}
if (pos > 8 && pos < 17) {
y = 60 + 62 + 62 + 62 + 62 + 62 + 62;
}
if (pos > 16 && pos < 25) {
y = 60 + 62 + 62 + 62 + 62 + 62;
}
if (pos > 24 && pos < 33) {
y = 60 + 62 + 62 + 62 + 62;
}
if (pos > 32 && pos < 41) {
y = 60 + 62 + 62 + 62;
}
if (pos > 40 && pos < 49) {
y = 60 + 62 + 62;
}
if (pos > 48 && pos < 57) {
y = 60 + 62;
}
if (pos > 56) {
y = 60;
}
return y;
}

Finally the missing piece is the Player domain itself. We need to maintain the status, position, name and icon. Status is basically "turn". Position is the token's position. Icon is basically what color - "yellow" or "red".
package snakesandladder.domain;
/**
*
* @author tesnep
*/
public class Player {
private boolean status;
private String icon;
private String name;
private int position;
public Player(String icon, String name){
setPosition(0);
setStatus(false);
setIcon(icon);
setName(name);
}
/**
* @return the status
*/
public boolean isStatus() {
return status;
}
/**
* @param status the status to set
*/
public void setStatus(boolean status) {
this.status = status;
}
/**
* @return the icon
*/
public String getIcon() {
return icon;
}
/**
* @param icon the icon to set
*/
public void setIcon(String icon) {
this.icon = icon;
}
/**
* @return the name
*/
public String getName() {
return name;
}
/**
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}
/**
* @return the position
*/
public int getPosition() {
return position;
}
/**
* @param position the position to set
*/
public void setPosition(int position) {
this.position = position;
}
}

When you run, you may need to resize the GUI. Please also note that I did not show all the IDE generated GUI code as that would make it too long to read.

FYI: http://forum.codecall.net/topic/71330-java-swing-game-snakes-ladders/#axzz294rcg3MR

This is the same contribution I made to CodeCall.NET

Tuesday, July 3, 2012

WPF Access the child control of a user control

This was a question asked here. This is a situation when we've created a custom user control and when we add it to a parent user control, we can no longer access the child user control's UI components.  I'd answered it there, but wanted to add this here as it would be helpful.

Define properties in the child's class for each of those controls. You will be able to access them from the Parent User Control, assuming you have added the Child User Control within the Parent User Control.

Parent User Control.. SingalData is the child User Contol
<my:C1TabItem Header="Signal"> <local:SignalData Width="1036"></local:SignalData> </my:C1TabItem> 

In the Child User Contorl class, if you have a component named tabProductList you add a property -
public C1.WPF.C1TabControl TabProductList { get { return this.tabProductList; } } 

And finally, from your parent class you can reference it as -
C1TabItem tbItem = (C1TabItem)c1TabControl1.SelectedItem;
SignalData sigInp = (SignalData)tbItem.Content;
if (sigInp.TabProductList.SelectedIndex == 0)
{
....
}

Saturday, June 23, 2012

Does Java FileLock work in Linux?

Before I write anything I want to mention that the OS is RHEL 4 and the java version is 1.4. Also, the application does not run under any application server - it is standalone console application with main method and there is no multithreading within the code.

The code uses FileLock's The zero-argument tryLock() . The logic is - the first instance of the application acquires lock on a file that either exists or tries to create if it does not and holds until execution finishes and releases it with release(). Other instances try to get the lock and if

Here's the code:

import java.nio.channels.*;

...
public static synchronized boolean isLockObtainedFromFile() throws IOException {
//try to get the lock
try {
while ((MailingThread.LCK = MailingThread.getLockFromFile()) == null) {
Logging.myLogger.info("A previous instance is already running....");
Thread.currentThread().sleep(300000l);
}
Logging.myLogger.info("This is the first instance of this program...");
return true;
} catch (Exception ex) {
Logging.myLogger.error("Locking file not found");
if (MailingThread.LCK != null) {
MailingThread.LCK.release();
}
return false;
}
}



...
public static synchronized FileLock getLockFromFile() {
try {
new DBConstants();
System.out.println(DBConstants.LOCKING_FILE + " this is the locking file");
File file = new File(DBConstants.LOCKING_FILE);
if (!file.exists()) {
file.createNewFile();
}
return new FileOutputStream(DBConstants.LOCKING_FILE).getChannel().
tryLock();
} catch (Exception ex) {
System.out.println("Lock File not found");
System.exit(0);

return null;
}
}
...


So far this looks good. On Windows, this works perfectly, and presumably it did on our Linux machine until we noticed what was in the log file..
2012-06-21 12:07:28,769;main;LOCK RELEASED.
2012-06-21 12:07:28,769;main;******The e-campaign engine executed in total time of - 1344 seconds with total size of - 1015 emails*****
2012-06-21 12:10:05,616;main;This is the first instance of this program...
2012-06-21 12:10:06,686;main;A previous instance is already running....
2012-06-21 12:10:06,914;main;A previous instance is already running....

While the above looks fine - An instance released lock and then an instance that was waiting executes while others that were waiting continues to wait, look below:
2012-06-21 11:55:03,552;main;Send the content to
2012-06-21 11:55:03,552;main;Email has been sent to:
2012-06-21 11:55:04,337;main;This is the first instance of this program...
2012-06-21 11:55:04,750;main;A previous instance is already running....
2012-06-21 11:55:04,773;main;A previous instance is already running....

....

2012-06-21 12:00:04,837;main;Send the content to
2012-06-21 12:00:04,837;main;Email has been sent to:
2012-06-21 12:00:04,837;main;The reply email in send email
2012-06-21 12:00:04,902;main;This is the first instance of this program...
2012-06-21 12:00:04,970;main;Send the content to

Notice the new instances start running when the running instance has not even finished and released the lock. This is a very random behavior and one that is clearly not desired!

Clearly, the lock file is not being used for anything but locking. We're not modifying the content within.

My verdit: Java FileLock does not work in Linux!

 
 

Saturday, June 2, 2012

Setting up FTP in Windows Server 2008

To set up the FTP site, follow these steps:

  1. In User Manager, create a user account for each of your users.

    Note These users need to have the local log on permission. By default, new users belong to the built-in Users group, which has local log on permission.

  2. Using the Windows NT Explorer, on a partition formatted with NTFS, give the FTP site's root folder the following security access types:

    • Administrators: Full Control

    • Everyone: Read

    • System: Read



  3. Create a subfolder for each user. (These subfolders will inherit the root folder's security settings). Make the following security changes:

    • Remove the Everyone group.

    • Change the System account's folder's access to Full Control (instead of Read).

    • Add the user who will use that folder, and give that user Full Control access.



  4. Configure the home page for the user. To do this, follow these steps:




  1. In Control Panel, open Administrative Tools.

  2. Double-click Computer Management.

  3. Expand Local Users and Groups, click Users, right-click the user name, and then click Properties.

  4. Click the Profile tab.

  5. Make sure that Local path is selected under Home Folder, and then type the appropriate path in the Local path box. For example, type the path as d:\Ftp\FolderName.

    Note The user name and the folder name must match. For example, if the user name is JoeUser, the FolderName should also be JoeUser. If the folder name is different, the user will see the same view that is available to Anonymous users. This view is the complete parent folder structure. The user is still denied access to folders to which they are not specifically granted NTFS permissions. However, the parent folder view may be undesirable

  6. Also Check to see FTP service is running.

Tuesday, April 3, 2012

EntityFramework 4 - Code First v Model First (pros and cons)

I have worked on MVC 3 projects using both Code First and Model First. From my personal experience here were the pros and cons I could think of:

Code First Pros | Model First Cons

  • For Model First approach, DB generated names are not friendly especially if the DBA/Organization follows rules such as OAT0012 for table(entity) names and FSN_XXX for column names. Although, you can manually edit this, it will all go away during a refresh/update. But with Code First Approach you can have User(Developer)-friendly names like Person or Account as table(entity) names and AccountID as field names and not worry about that changing.

  • Create relationships as you need them, like the way you want them to behave (bi-directional/uni-directional)

  • Generated code is not always easy to understand, especially when the database is not 3N-normalized. With this, you can add your own relationships to maintain relationships. Now I have also seen Id field mapping to another entity as well as that entity as an object within the same entity. Duh!

  • When working on a team Model First can sometimes be a pain. This is true in initial development when changes are frequent to the database. You could say that this is true for Code First too. To some degree, Yes. But if the DBA's model does not suit your Controller's need and you need to hack into one or two entities, you would have to consider keeping local changes (branching out your repo) and updating the team's changes and applying your changes again.


Code First Cons | Model First Pros

  • You are up and running as soon as the database is ready and you can immediately start working on your service methods.

  • If you have a complex database the relationships that are automatically created for you in a matter of seconds. This is probably why people choose to go with this when they do it.

  • You do not need to read an entire book to get started with EF if you choose this option. Probably another reason to go with this.


Overall, the deciding factors, if I was choosing would be, is there going to be a lot of changes to the database? If so, I would stick to CodeFirst. It also suits Agile way of working. Creating only what you need to get the unit of work done and making changes as you go along. Also, if the database is not normalized, generated code would not be very helpful. But with a complex database and that which is almost ready, I would, for sure, prefer Model First.

Wednesday, January 4, 2012

JUnit 4.10 - Writing your first JUnit 4 test in a TDD Approach

In this article I will show you how to write your first JUnit 4 test.I will use Test Driven Development Approach (TDD from here onwards). This means I will first write an empty method to test. Then I will write the Test Class which will test this method. This of course means that the test will fail. Then once we know what we want from the test, we then refactor our actual method. This is exactly how it works in a TDD approach. We write the test first, then it fails, then we discover what needs to be refactored and we make the change in the class to test.

I will not use any IDE. A simple text editor like Notepad++ will suffice. This is in Windows environment.

  1. Download JUnit 4.10 source from here.

  2. Extract to C:\JUnit4.10.

  3. Add C:\JUnit4.10\junit-4.10.jar to CLASSPATH. Also make sure your JAVA_HOME variable is configured properly. Add your JAVA_HOME folder path to CLASSPATH as well since we will be compiling classes from command prompt.

  4. Now you are ready to write your first class.Since we are going to follow Test Driven Development, we will simply create a stub of the method we want to test.
    So go ahead and create a simple class called Calculator.java

    public class Calculator{

        public int add(int a, int b){
            return null;
        }

    }


    You notice that the method add, which we will test, returns null. This is a very simple test and you can figure out that it should return a + b. But this being TDD approach, we will not write this method. We actually want this method to fail. Now we are ready to write our Test Class.

  5. Go ahead and write the Test Class. We will call it CalculatorTest.java

    import static org.junit.Assert.*;
    import org.junit.Test;

    public class CalculatorTest{

        @Test
        public void testAdd()
        {
            Calculator calc = new Calculator(); //Setup object to Test
            int result = calc.add(2,8); //Call the method to Test
            assertEquals(10, result, 0); //Verify        
        }

    }


    The first thing to note is that I have annotated the method testAdd() with @Test. This is significant. The test suite will specifically look for methods with @Test in order to test it. Also note that test methods are void and the convention is to write testXX() where 'XX' is the name of the method to test.

    In any test method, you will first instantiate (by creating a new object in our case or use DI) the object to test. You would then set all the defaults of the object. In our case, there isn't any. This is the setup portion. You setup the object to test. Next you will call the method to test which may or may not return something. In our case it does. If it does not return, you can simply verify that the method has been called and that there is no exception. Finally, you assert or verify that the outcome is what you expect.

  6. Compile both classes from the command prompt using javac Calculator.java and javac CalculatorTest.java. Both should compile.

  7. Run the test from the command prompt again using java org.junit.runner.JUnitCore CalculatorTest


  8. You will get a Test Failed Error
    .

  9. Now you are ready to refactor!
    You know that your Calculator class needs to return the sum of the two passed-in integers by looking at the test. So you make that change.

    public class Calculator{

        public int add(int a, int b){
            return a+b;
        }

    }



  10. Repeat 6 and 7. You will now get a Test Passed message.


 

This is a very simple test. But think of a scenario when you have to test a very complex method. Even better think of a scenario where your method is tied up with resources(values) from your configuration file that you may not be able to access(acquire) in your local development environment. But you still need to write the method correctly before you can check-in the code. In such a scenario, you will mock out the object in the test class. This will ensure that your method is fully tested and what values of the mocked object you need from the configuration. At the very least, you will know how the method should actually work because tests should be informative.

I will be writing more on testing DAOs and Service layers (mocking) in the future. As usual, I appreciate your comments.