-
Notifications
You must be signed in to change notification settings - Fork 47
Squidbridges
SquidPony edited this page Apr 30, 2012
·
4 revisions
The following is the code from the three classes that make up a Hashiwokakero (Bridges) game using the SquidLib library.
This code was created in a short period of time primarily to test out the basics of mouse interaction with the library and implementing a mouse driven puzzle game. Any bugs reports/comments/suggestions are welcome.
For more information on the puzzle (as well as rules of play) go to the wikipedia page. An online version can be found here.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import javax.swing.JFrame;
import squidpony.squidcolor.SColor;
import squidpony.squidgrid.gui.SGTextPanel;
import squidpony.squidgrid.util.Direction;
/**
* A frame that starts up a game of SquidBridges.
*
* @author Eben Howard - http://squidpony.com
*/
public class SquidBridges extends JFrame implements MouseListener, MouseMotionListener {
private SGTextPanel display;
Hashiwokakero board = new Hashiwokakero(40, 20);
/**
* Creates new form SquidBridges
*/
public SquidBridges() {
initComponents();
display = new SGTextPanel();
display.initialize(board.width, board.height, Font.getFont("Serif"));
//set up and show the frame
setTitle("SquidBridges");
add(display);
setLocationRelativeTo(null);
drawGuessedBoard();
int w = display.getPreferredSize().width + this.getInsets().left + this.getInsets().right;
int h = display.getPreferredSize().height + this.getInsets().top + this.getInsets().bottom;
setPreferredSize(new Dimension(w, h));
pack();
setVisible(true);
attachListeners();
}
private void drawGuessedBoard() {
Color back, front;
char c;
for (int x = 0; x < board.width; x++) {
for (int y = 0; y < board.height; y++) {
back = SColor.BEIGE;
if (board.isLocked(x, y)) {
front = SColor.AZUL;
} else {
front = SColor.BLACK;
}
switch (board.getGuessedTile(x, y)) {
case EMPTY:
c = ' ';
break;
case NODE:
c = String.valueOf(board.guessedQuantity[x][y]).charAt(0);
break;
case VERTICAL_LINE:
switch (board.guessedQuantity[x][y]) {
case 0:
c = ' ';
break;
case 1:
c = '|';
break;
case 2:
c = 'H';
break;
default:
c = 'X';//error
}
break;
case HORIZONTAL_LINE:
switch (board.guessedQuantity[x][y]) {
case 0:
c = ' ';
break;
case 1:
c = '-';
break;
case 2:
c = '=';
break;
default:
c = 'X';//error
}
break;
default:
c = 'X';//error
}
display.placeCharacter(x, y, c, front, back);
}
}
display.refresh();
repaint();
}
private void attachListeners() {
display.addMouseListener(this);
display.addMouseMotionListener(this);
}
//
//variables related to listening to the mouse
Point startMouse = new Point(0, 0);
Point endMouse = new Point(0, 0);
boolean wasDragged = false;
@Override
public void mouseClicked(MouseEvent me) {
int x = me.getX() / display.getCellDimension().width;
int y = me.getY() / display.getCellDimension().height;
if (board.isNode(x, y)) {
board.flipNodeLock(x, y);
drawGuessedBoard();
}
}
@Override
public void mousePressed(MouseEvent me) {
if (me.getButton() == MouseEvent.BUTTON1) {
startMouse = me.getPoint();
}
}
@Override
public void mouseReleased(MouseEvent me) {
if (wasDragged) {
wasDragged = false;
endMouse = me.getPoint();
Point nodePicked = new Point(startMouse.x / display.getCellDimension().width, startMouse.y / display.getCellDimension().height);
Direction direction;
if (board.isNode(nodePicked.x, nodePicked.y)) {
if (Math.abs(startMouse.x - endMouse.x) > Math.abs(startMouse.y - endMouse.y)) {//mostly horizontal
if (startMouse.x < endMouse.x) {//went right
direction = Direction.RIGHT;
} else {//went left
direction = Direction.LEFT;
}
} else {//mostly vertical
if (startMouse.y < endMouse.y) {//went down
direction = Direction.DOWN;
} else {//went up
direction = Direction.UP;
}
}
board.guessBridge(nodePicked, direction);
drawGuessedBoard();
repaint();
}
}
}
@Override
public void mouseEntered(MouseEvent me) {
//nothing special happens
}
@Override
public void mouseExited(MouseEvent me) {
wasDragged = false;//cancel drag event
}
@Override
public void mouseDragged(MouseEvent me) {
wasDragged = true;
}
@Override
public void mouseMoved(MouseEvent me) {
//nothing special happens
}
private void initComponents() {
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
getContentPane().setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING).addGap(0, 400, Short.MAX_VALUE));
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING).addGap(0, 300, Short.MAX_VALUE));
pack();
}
public static void main(String args[]) {
/*
* Set the Nimbus look and feel
*/
try {
for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
if ("Nimbus".equals(info.getName())) {
javax.swing.UIManager.setLookAndFeel(info.getClassName());
break;
}
}
} catch (ClassNotFoundException ex) {
java.util.logging.Logger.getLogger(SquidBridges.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
} catch (InstantiationException ex) {
java.util.logging.Logger.getLogger(SquidBridges.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
} catch (IllegalAccessException ex) {
java.util.logging.Logger.getLogger(SquidBridges.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
} catch (javax.swing.UnsupportedLookAndFeelException ex) {
java.util.logging.Logger.getLogger(SquidBridges.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
}
/*
* Create and display the form
*/
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new SquidBridges().setVisible(true);
}
});
}
}
import java.awt.Point;
import java.util.ArrayList;
import java.util.Random;
import squidpony.squidgrid.util.Direction;
/**
* Generates and contains information for a single instance of a Hashiwokakero
* (also known as Bridges) puzzle.
*
* @author Eben Howard - http://squidpony.com
*/
public class Hashiwokakero {
HashiTile board[][];
HashiTile guessedBoard[][];
int boardQuantity[][];
int guessedQuantity[][];
private boolean[][] locked;
int width, height, totalNodes;
Random rng = new Random();
ArrayList<Point> nodes = new ArrayList<Point>();//holds a quick reference to created nodes
static final double stopChance = 0.20;
static final int singleLineChance = 70;//out of 100
public Hashiwokakero(int width, int height) {
this.width = width;
this.height = height;
board = new HashiTile[width][height];
boardQuantity = new int[width][height];
guessedBoard = new HashiTile[width][height];
guessedQuantity = new int[width][height];
locked = new boolean[width][height];
totalNodes = 0;
buildBoard();
}
private void buildBoard() {
//initialize board
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
board[x][y] = HashiTile.EMPTY;
boardQuantity[x][y] = 0;
guessedBoard[x][y] = HashiTile.EMPTY;
guessedQuantity[x][y] = 0;
}
}
//place a random node
int x = rng.nextInt(width - 4) + 2;
int y = rng.nextInt(height - 4) + 2;
board[x][y] = HashiTile.NODE;//mark it as a starting node
nodes.add(new Point(x, y));
totalNodes++;
drawRandomLines();
//copy nodes into guessed board
for (x = 0; x < width; x++) {
for (y = 0; y < height; y++) {
if (board[x][y] == HashiTile.NODE) {
guessedBoard[x][y] = HashiTile.NODE;
guessedQuantity[x][y] = boardQuantity[x][y];
}
}
}
}
private void drawRandomLines() {
do {
Point node = nodes.get(rng.nextInt(nodes.size()));
//determine if node has any open edges (has to have two open spaces)
int usedEdges = 0;
if (node.x <= 1 || board[node.x - 1][node.y] != HashiTile.EMPTY || board[node.x - 2][node.y] != HashiTile.EMPTY) {
usedEdges++;
}
if (node.y <= 1 || board[node.x][node.y - 1] != HashiTile.EMPTY || board[node.x][node.y - 2] != HashiTile.EMPTY) {
usedEdges++;
}
if (node.x >= width - 2 || board[node.x + 1][node.y] != HashiTile.EMPTY || board[node.x + 2][node.y] != HashiTile.EMPTY) {
usedEdges++;
}
if (node.y >= height - 2 || board[node.x][node.y + 1] != HashiTile.EMPTY || board[node.x][node.y + 2] != HashiTile.EMPTY) {
usedEdges++;
}
if (usedEdges >= 4 || (nodes.size() > 15 && rng.nextDouble() < 0.5)) {//only attempt to add if there's possible room
nodes.remove(node);
} else {
//determine thickness of bridge
int thickness;
if (rng.nextInt(100) < singleLineChance) {
thickness = 1;
} else {
thickness = 2;
}
//determine random direction
Direction direction = Direction.cardinals[rng.nextInt(Direction.cardinals.length)];
if (node.y + direction.deltaY > 1 && node.x + direction.deltaX > 1
&& node.y + direction.deltaY < height - 2 && node.x + direction.deltaY < height - 2
&& board[node.x + direction.deltaX][node.y + direction.deltaY] == HashiTile.EMPTY) {
boardQuantity[node.x + direction.deltaX][node.y + direction.deltaY] += thickness;
placeLine(node.x + direction.deltaX, node.y + direction.deltaY, direction, thickness, true);
}
}
} while (!nodes.isEmpty());//keep adding lines as long as there are any possible nodes
}
private void placeLine(int x, int y, Direction direction, int thickness, boolean forced) {
if (x + direction.deltaX >= 0 && x + direction.deltaX < width
&& y + direction.deltaY >= 0 && y + direction.deltaY < height
&& board[x + direction.deltaX][y + direction.deltaY] == HashiTile.EMPTY
&& (forced || rng.nextDouble() > stopChance)) {//place a line
boardQuantity[x][y] = thickness;
if (direction == Direction.DOWN || direction == Direction.UP) {
board[x][y] = HashiTile.VERTICAL_LINE;
} else {
board[x][y] = HashiTile.HORIZONTAL_LINE;
}
placeLine(x + direction.deltaX, y + direction.deltaY, direction, thickness, false);
} else {//place a node
board[x][y] = HashiTile.NODE;
boardQuantity[x][y] = thickness;
nodes.add(new Point(x, y));
totalNodes++;
}
}
@Override
public String toString() {
StringBuilder out = new StringBuilder();
char c;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
switch (board[x][y]) {
case EMPTY:
c = ' ';
break;
case NODE:
c = String.valueOf(boardQuantity[x][y]).charAt(0);
break;
case VERTICAL_LINE:
switch (boardQuantity[x][y]) {
case 0:
c = ' ';
break;
case 1:
c = '|';
break;
case 2:
c = 'H';
break;
default:
c = 'X';//error
}
break;
case HORIZONTAL_LINE:
switch (boardQuantity[x][y]) {
case 0:
c = ' ';
break;
case 1:
c = '-';
break;
case 2:
c = '=';
break;
default:
c = 'X';//error
}
break;
default:
c = 'X';//error
}
out.append(c);
}
out.append("\n");
}
return out.toString();
}
/**
* Returns the type of tile that is guessed to be at the given location.
*
* @param deltaX
* @param deltaY
* @return
*/
public HashiTile getGuessedTile(int x, int y) {
return guessedBoard[x][y];
}
/**
* Returns the type of tile that is part of the solution at the given
* location.
*
* @param deltaX
* @param deltaY
* @return
*/
public HashiTile getTile(int x, int y) {
return board[x][y];
}
public boolean isNode(int x, int y) {
return board[x][y] == HashiTile.NODE;
}
/**
* Returns true if the given location is currently locked.
*
* @param deltaX
* @param deltaY
* @return
*/
public boolean isLocked(int x, int y) {
return locked[x][y];
}
/**
* Sets the node and all bridges attached to it to the passed in boolean
* value.
*
* @param startX The node's deltaX coordinate
* @param startY The node's deltaY coordinate
* @param lock The target locked state
*/
private void setNodeLock(int startX, int startY, boolean lock) {
if (guessedBoard[startX][startY] == HashiTile.NODE) {//only do this lock if start is a node
locked[startX][startY] = !locked[startX][startY];
for (Direction dir : Direction.values()) {//check all directions
int x = startX;
int y = startY;
while (x + dir.deltaX >= 0 && x + dir.deltaX < width && y + dir.deltaY >= 0 && y + dir.deltaY < height
&& (((dir == Direction.UP || dir == Direction.DOWN) && guessedBoard[x + dir.deltaX][y + dir.deltaY] == HashiTile.VERTICAL_LINE)
|| ((dir == Direction.LEFT || dir == Direction.RIGHT) && guessedBoard[x + dir.deltaX][y + dir.deltaY] == HashiTile.HORIZONTAL_LINE))) {
x += dir.deltaX;
y += dir.deltaY;
locked[x][y] = lock;
}
}
}
}
/**
* Switches the locked state of the node and all attached bridges. The
* attached bridges will be placed into the same state as the provided node,
* regardless of their state prior.
*
* If the passed in location is not a node, no changes are made.
*
* @param deltaX
* @param deltaY
*/
public void flipNodeLock(int x, int y) {
if (guessedBoard[x][y] == HashiTile.NODE) {
setNodeLock(x, y, !locked[x][y]);
}
}
/**
* Attempts to add a guessed bridge from the given node that travels in the
* given direction. Adding a bridge is a mod 3 action, so the "third" add
* will reset to no bridge.
*
* Will return true if the operation made a change, and false if the
* operation was for some reason illegal.
*
* @param start
* @param direction
* @return
*/
public boolean guessBridge(Point start, Direction direction) {
if(start.x + direction.deltaX < 0 || start.x+direction.deltaX >= width || start.y + direction.deltaY <0 || start.y+direction.deltaY >= height){
return false;//can't go off the edge of the map
}
if (isNode(start.x, start.y)) {
//check for open path to next node in given direction
if (guessedBoard[start.x + direction.deltaX][start.y + direction.deltaY] == HashiTile.NODE) {
return false;//no space for a bridge between those two nodes
}
boolean foundNode = false;
searchLoop:
for (int i = 1; start.x + direction.deltaX * i >= 0 && start.x + direction.deltaX * i < width
&& start.y + direction.deltaY * i >= 0 && start.y + direction.deltaY * i < height; i++) {
//see if a node was hit
if (guessedBoard[start.x + direction.deltaX * i][start.y + direction.deltaY * i] == HashiTile.NODE) {
foundNode = true;
break searchLoop;
}
//check for easy case, empty
if (guessedBoard[start.x + direction.deltaX * i][start.y + direction.deltaY * i] != HashiTile.EMPTY) {
//not empty, check for conflicting line
if (direction == Direction.DOWN || direction == Direction.UP) {
if (guessedBoard[start.x + direction.deltaX * i][start.y + direction.deltaY * i] != HashiTile.VERTICAL_LINE) {
return false;
}
} else {
if (guessedBoard[start.x + direction.deltaX * i][start.y + direction.deltaY * i] != HashiTile.HORIZONTAL_LINE) {
return false;
}
}
}
}
if (!foundNode) {
return false;//hit the edge of the board with no node found
}
//add a bridge (mod 3)
int newQuantity = (guessedQuantity[start.x + direction.deltaX][start.y + direction.deltaY] + 1) % 3;
for (int i = 1; guessedBoard[start.x + direction.deltaX * i][start.y + direction.deltaY * i] != HashiTile.NODE; i++) {
guessedQuantity[start.x + direction.deltaX * i][start.y + direction.deltaY * i] = newQuantity;
if (newQuantity > 0) {
if (direction == Direction.UP || direction == Direction.DOWN) {
guessedBoard[start.x + direction.deltaX * i][start.y + direction.deltaY * i] = HashiTile.VERTICAL_LINE;
} else {
guessedBoard[start.x + direction.deltaX * i][start.y + direction.deltaY * i] = HashiTile.HORIZONTAL_LINE;
}
} else {
guessedBoard[start.x + direction.deltaX * i][start.y + direction.deltaY * i] = HashiTile.EMPTY;
}
}
return true;//operation finished successfully
} else {
return false;//can't start from something that's not a node
}
}
}
/**
* Enum that represents the possible types of tiles in the standard grid of a
* Hashiwokakero game.
*
* @author Eben Howard - http://squidpony.com
*/
public enum HashiTile {
EMPTY, NODE, VERTICAL_LINE, HORIZONTAL_LINE;
@Override
public String toString() {
return this.name().toLowerCase().substring(0, 1);
}
}