/**
* EECS 285 Project 3 - Wheel of Fortune main GUI.
*
* See https://eecs285.github.io/p3-wheel/ for the specification.
*
* Project UID 7a0233619c14f47d99d949048c3b9c3319411051
*
* @author Andrew M. Morgan
* @author Amir Kamil
*/
package eecs285.proj3.benxzh; // replace with your uniqname
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Color;
import java.awt.GridLayout;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import javax.swing.SwingConstants;
import java.io.File;
import java.io.FileFilter;
import java.lang.reflect.Array;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.HashMap;
import java.util.Locale;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JTextField;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.BoxLayout;
import javax.swing.JLabel;
import javax.swing.BorderFactory;
import javax.swing.border.TitledBorder;
public class WheelOfFortuneFrame extends JFrame {
/** Uniqname used for package and file paths. */
public static final String UNIQNAME = "benxzh";
// Replace the string above with your uniqname.
/** Number of wheel spaces in the game. */
public static final int NUM_WHEEL_SPACES = 24;
/** Path to images folder. */
public static final String IMAGES_PATH =
"eecs285/proj3/" + UNIQNAME + "/images";
/** File extension for images. */
public static final String IMAGE_EXTENSION = "jpg";
// Number of players
private int players;
// Names of players
private ArrayList<String> playerNames = new ArrayList<String>();
// Scores of players
private ArrayList<Integer> playerScore = new ArrayList<Integer>();
//amount of money for each player
// Solution phrase
private String phrase;
// Characters that have been guessed
private HashSet<String> charactersGuessed = new HashSet<String>();
// Characters that haven't been guessed from the phrase
private HashSet<String> charactersRemaining = new HashSet<String>();
// Indice of the player whose turn it currently is
private int playerTurn = 0;
// bools to turn buttons on and off
private boolean vowelEnable = false;
private boolean consEnable = false;
// num of vowels, consonants guessed
private int vowelsGuessed = 0;
private int consGuessed = 0;
// array of wheelspace objects
private WheelSpace[] wheelSpaces;
// random generator seed
private Random generator;
// current image for the wheel
private ImageIcon wheelImage;
// index of current spin
private int spin = 0;
// price of a vowel
private static final int VOWELPRICE = 250;
/**
* Loades wheel-space images from the images/ directory.
*
* Looks for files that follow the naming pattern
* <spaceNumber>_<value>.jpg. Ignores all other files in the
* directory. Assumes that there are exactly NUM_WHEEL_SPACES
* images, numbered from 1 to NUM_WHEEL_SPACES.
*
* @return array of WheelSpace objects representing the images
*/
static WheelSpace[] loadImages() {
File[] fileList;
File myDir = null;
// Allocate array for number of spaces, which is set to a constant
WheelSpace[] wheelSpaces = new WheelSpace[NUM_WHEEL_SPACES];
// Get a File object for the directory containing the images
try {
myDir = new File(WheelOfFortuneFrame.class.getClassLoader()
.getResource(IMAGES_PATH).toURI());
} catch (URISyntaxException uriExcep) {
System.out.println("Caught a URI syntax exception");
System.exit(4); // Just bail for simplicity in this project
}
// Loop from 1 to the number of spaces expected, so we can look
// for files named <spaceNumber>_<value>.jpg. Note: Space numbers
// in image filenames are 1-based, NOT 0-based.
for (int i = 1; i <= NUM_WHEEL_SPACES; i++) {
// Get a listing of files named appropriately for an image for
// wheel space #i. There should only be one, and this will be
// checked below.
fileList = myDir.listFiles(new WheelSpaceImageFilter(i));
if (fileList.length == 1) {
// Index starts at 0, space numbers start at 1: hence the - 1
wheelSpaces[i - 1] =
new WheelSpace(WheelSpaceImageFilter.getSpaceValue(fileList[0]),
new ImageIcon(fileList[0].toString()));
} else {
System.out.println("ERROR: Invalid number of images for space: " + i);
System.out.println(" Expected 1, but found " + fileList.length);
}
}
return wheelSpaces;
}
//need javadoc comments
private static class WheelSpace {
private int dollar;
private ImageIcon img;
WheelSpace(int dollar, ImageIcon img) {
this.dollar = dollar;
this.img = img;
}
}
// Helper nested class to filter images used for wheel spaces, based
// on specifically expected filename format.
private static class WheelSpaceImageFilter implements FileFilter {
/** Prefix of the requested filename. */
private String prefix; // The prefix of the filename we're looking
// for - what comes before the first underscore
/**
* Constructs a filter with the given prefix.
*
* @param inPref integer corresponding to the prefix
*/
WheelSpaceImageFilter(int inPref) {
// Sets the prefix member to string version of space number
prefix = new Integer(inPref).toString();
}
/**
* Tests whether the file provided should be accepted by our file
* filter. In the FileFilter interface.
*/
@Override
public boolean accept(File imageFile) {
boolean isAccepted = false;
// Accepted if matched "<prefix>_<...>.jpg" where
// IMAGE_EXTENSION is assumed to be "jpg" for this example
if (imageFile.getName().startsWith(prefix + "_") &&
imageFile.getName().endsWith("." + IMAGE_EXTENSION)) {
isAccepted = true;
}
return isAccepted;
}
/**
* Parses a wheel space image's filename to determine the dollar
* value associated with it.
*
* @param imageFile the wheel space image
* @return the dollar value associated with the image
*/
public static int getSpaceValue(File imageFile) {
String name = imageFile.getName();
int startIndex = 0;
int endIndex = 0;
for (int i = 0; i < name.length(); i++) {
if (name.charAt(i) == '_') {
startIndex = i + 1;
} else if (name.charAt(i) == '.') {
endIndex = i;
break;
}
}
String value = name.substring(startIndex, endIndex);
if (value.equals("loseATurn")) {
return -1;
} else if (value.equals("bankrupt")) {
return 0;
} else {
return Integer.parseInt(value);
}
}
}
/**
* Adds a listener for the countButton that progresses to name input
*
* @param countButton the button on the count panel
* @param countField the text field on the count panel
* @return doesn't return anything
*/
private JButton addCountListener(JButton countButton, JTextField countField) {
countButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String count = countField.getText();
//if input is not a number of is less than 1, display error
if (!isNumeric(count) || Integer.parseInt(count) < 1) {
JOptionPane.showMessageDialog(countButton,
"Input must be a positive integer",
"Input Error",
JOptionPane.ERROR_MESSAGE);
} else {
//otherwise, proceed to take in names
setVisible(false);
//parse input string into a usable int
players = Integer.parseInt(count);
for (int i = 0; i < players; i++) {
Object[] possibilities = {"OK"};
//query player names and initialize scores
String s = (String)JOptionPane.showInputDialog(null, "Enter name of
player #" + i,
"Player Name Input", JOptionPane.INFORMATION_MESSAGE, null,
null,"");
playerNames.add(i, s);
playerScore.add(i, 0);
//reload
pack();
revalidate();
repaint();
}
//create panel to get puzzle input
JPanel getInput = new JPanel();
setTitle("Puzzle Input");
getInput.setLayout(new BoxLayout(getInput, BoxLayout.Y_AXIS));
getContentPane().setSize(1500, 150);
JLabel inputLabel = new JLabel("Ask a non-player to enter a puzzle");
inputLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
getInput.add(inputLabel);
JTextField inputField = new JTextField(35);
inputField.setAlignmentX(Component.CENTER_ALIGNMENT);
getInput.add(inputField);
JButton inputButton = new JButton("OK");
inputButton.setAlignmentX(Component.CENTER_ALIGNMENT);
//take input when ok button is clicked
inputButton.addActionListener(new ActionListener() {
@Override
//parse input
public void actionPerformed(ActionEvent e) {
phrase = inputField.getText().toUpperCase();
for (int i = 0; i < phrase.length(); i++) {
charactersRemaining.add(phrase.substring(i, i + 1));
}
//create main game window
getContentPane().removeAll();
setPreferredSize(new Dimension(600, 400));
setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));
add(getScorePanel());
add(getWheelPanel(true));
add(getLetterPanel());
add(getPhrasePanel());
pack();
revalidate();
repaint();
setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
setVisible(true);
}
});
//adding input panel
getInput.add(inputButton);
getContentPane().removeAll();
getContentPane().add(getInput);
pack();
revalidate();
repaint();
setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
setVisible(true);
return;
}
}
});
return countButton;
}
/**
* Create and start a game of Wheel of Fortune.
*
* @param generator the random-number generator to use
*/
public WheelOfFortuneFrame(Random generator) {
//initializing size and close op
setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
setSize(1500, 150);
//create panel to get number of players
JPanel getPlayerCount = new JPanel();
setTitle("Number of Players Input");
getPlayerCount.setLayout(new BoxLayout(getPlayerCount, BoxLayout.Y_AXIS));
getContentPane().setSize(1500, 150);
JLabel countLabel = new JLabel("Enter Number of Players (must be at least 1)",
0);
countLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
getPlayerCount.add(countLabel);
JTextField countField = new JTextField(35);
countField.setAlignmentX(Component.CENTER_ALIGNMENT);
getPlayerCount.add(countField);
JButton countButton = new JButton("OK");
countButton.setAlignmentX(Component.CENTER_ALIGNMENT);
getPlayerCount.add(countButton);
//action listener to check if input is valid
countButton = addCountListener(countButton, countField);
//adding count panel
getContentPane().removeAll();
getContentPane().add(getPlayerCount);
pack();
revalidate();
repaint();
//random seed
this.generator = generator;
//loads images and converts to wheelSpace objects
wheelSpaces = loadImages();
//initalize wheelImage to LOSE A TURN
wheelImage = wheelSpaces[0].img;
}
/**
* returns JPanel of scoreboard with player money
* marks player box as red if they are supposed to buy vowel, spin wheel, or
solve puzzle
*
* @return JPanel that represents scoreboard (players and how much money that
have)
*/
private JPanel getScorePanel() {
JPanel scorePanel = new JPanel(new GridLayout(1, players));
//creates panel for each player
for (int i = 0; i < players; i++) {
JPanel playerPanel = new JPanel(new BorderLayout());
playerPanel.add(new JLabel(Integer.toString(playerScore.get(i)),
SwingConstants.CENTER),
BorderLayout.CENTER);
TitledBorder playerTitle =
BorderFactory.createTitledBorder(playerNames.get(i));
//player whose turn it is has red border
if (i == playerTurn) {
playerTitle.setBorder(BorderFactory.createLineBorder(Color.RED));
}
playerTitle.setTitleJustification(TitledBorder.LEFT);
playerPanel.setBorder(playerTitle);
scorePanel.add(playerPanel);
}
return scorePanel;
}
/**
* sets private member variable wheelImage to img of new wheel spin
*
* @param img image of new wheel spin
*/
public void updateWheelImage(ImageIcon img) {
wheelImage = img;
}
/**
* Adds action listener for when the buy vowel button is pressed.
* PLayer loses $250 (or vowel price) and can guess a vowel
* If guess is correct, phrase will reveal those vowels
* Otherwise, no letter is revealed
*
* @param buyVowel JButton to which we will add the action listener to
* @return same button but with added action listener
*/
private JButton addBuyVowelListener(JButton buyVowel) {
buyVowel.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//take away $250 (or vowel price) from user
playerScore.set(playerTurn, playerScore.get(playerTurn) - VOWELPRICE);
//enable vowel buttons to be able to be pressed
vowelEnable = true;
vowelsGuessed++;
getContentPane().removeAll();
getContentPane().add(getScorePanel());
getContentPane().add(getWheelPanel(false));
getContentPane().add(getLetterPanel());
getContentPane().add(getPhrasePanel());
pack();
revalidate();
repaint();
}
});
return buyVowel;
}
/**
* Adds action listener for when the spin wheel button is pressed.
* Spins wheel and wheel image is updated to show spin
* Grants user a guess or deducts money or skips user according to the spin
*
* @param spinWheel JButton to which we will add the action listener to
* @return same button but with added action listener
*/
private JButton addSpinWheelListener(JButton spinWheel) {
spinWheel.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//spins wheel
spin = generator.nextInt(NUM_WHEEL_SPACES);
updateWheelImage(wheelSpaces[spin].img);
//enable is used to decide whether the buttons in wheelPanel should be
enabled
boolean enable;
if (wheelSpaces[spin].dollar == 0) {
//if 0 (bankrupt), user loses all money and is skipped
playerScore.set(playerTurn, 0);
playerTurn = (playerTurn + 1) % players;
enable = true;
consEnable = false;
} else if (wheelSpaces[spin].dollar == -1) {
//if -1 (bankrupt), user is skipped
playerTurn = (playerTurn + 1) % players;
enable = true;
consEnable = false;
} else {
//otherwise, user can guess (will guess later on)
enable = false;
consEnable = true;
consGuessed++;
}
getContentPane().removeAll();
getContentPane().add(getScorePanel());
getContentPane().add(getWheelPanel(enable));
getContentPane().add(getLetterPanel());
getContentPane().add(getPhrasePanel());
pack();
revalidate();
repaint();
}
});
return spinWheel;
}
/**
* Adds action listener for when the solve puzzle button is pressed.
* Prompts user to guess the phrase and responds according
* User either wins money or loses turn and game continues
*
* @param solvePuzzle JButton to which we will add the action listener to
* @return same button but with added action listener
*/
private JButton addSolvePuzzleListener(JButton solvePuzzle) {
solvePuzzle.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String s = (String)JOptionPane.showInputDialog(null, "Enter complete puzzle
exactly " +
"as displayed","Solve Puzzle", JOptionPane.PLAIN_MESSAGE, null,
null,"");
s = s.toUpperCase();
if (s.equals(phrase)) {
//phrase is guessed correctly
JOptionPane.showMessageDialog(null, playerNames.get(playerTurn) + " wins
$" +
playerScore.get(playerTurn), "Game Over", JOptionPane.PLAIN_MESSAGE);
dispose();
} else {
//phrase is guessed incorrectly
JOptionPane.showMessageDialog(null, "Guess by " +
playerNames.get(playerTurn)
+ " incorrect!","Wrong Answer", JOptionPane.ERROR_MESSAGE);
playerTurn = (playerTurn + 1) % players;
getContentPane().removeAll();
getContentPane().add(getScorePanel());
getContentPane().add(getWheelPanel(true));
getContentPane().add(getLetterPanel());
getContentPane().add(getPhrasePanel());
pack();
revalidate();
repaint();
}
}
});
return solvePuzzle;
}
/**
* sets up the panel with buy vowel, spin wheel, solve puzzle buttons and wheel
image
* Buy vowel button allows user to buy a vowel to guess
* Spin wheel allows user to spin wheel and guess consonant and gain points (when
possible)
* also updates the wheel image to the spin
* Solve puzzle allows user to guess the phrase
*
* @param enable determines whether the buy vowel, spin wheel, and solve puzzle
buttons should
* be enabled
* @return panel with buy vowel, spin wheel, solve puzzle buttons and wheel image
*/
private JPanel getWheelPanel(boolean enable) {
JPanel wheelPanel = new JPanel(new FlowLayout());
JPanel wheelButtons = new JPanel(new GridLayout(3,1));
JButton buyVowel = new JButton("Buy a Vowel");
JButton spinWheel = new JButton("Spin the Wheel");
JButton solvePuzzle = new JButton("Solve the Puzzle");
//enabled only when specified and letters of each respective category have not
been all guessed
buyVowel.setEnabled(enable && vowelsGuessed != 5);
spinWheel.setEnabled(enable && consGuessed != 21);
solvePuzzle.setEnabled(enable);
buyVowel = addBuyVowelListener(buyVowel);
//deactivate buy vowel option if all vowels have been guessed or not enough
money to purchase
// vowels
if (playerScore.get(playerTurn) < VOWELPRICE || vowelsGuessed == 5) {
buyVowel.setEnabled(false);
}
spinWheel = addSpinWheelListener(spinWheel);
//deactive spin wheel option if all consonants have been guessed
if (consGuessed == 21) {
spinWheel.setEnabled(false);
}
solvePuzzle = addSolvePuzzleListener(solvePuzzle);
wheelButtons.add(buyVowel);
wheelButtons.add(spinWheel);
wheelButtons.add(solvePuzzle);
wheelPanel.add(wheelButtons);
wheelPanel.add(new JLabel(wheelImage));
return wheelPanel;
}
/**
* set up vowel panel's dimensions, layout, border color, title, title
justification
*
* @return vowel panel without buttons
*/
private JPanel getVowelPanel() {
JPanel vowelPanel = new JPanel(new GridLayout(3,2));
vowelPanel.setPreferredSize(new Dimension(140, 150));
TitledBorder vowelTitle = BorderFactory.createTitledBorder("Vowels");
vowelTitle.setTitleJustification(TitledBorder.LEFT);
vowelPanel.setBorder(vowelTitle);
return vowelPanel;
}
/**
* set up consonant panel's dimensions, layout, border color, title, title
justification
*
* @return consonant panel without buttons
*/
private JPanel getConsPanel() {
JPanel consonantsPanel = new JPanel(new GridLayout(3,7));
consonantsPanel.setPreferredSize(new Dimension(460, 150));
TitledBorder consonantsTitle = BorderFactory.createTitledBorder("Consonants");
consonantsTitle.setTitleJustification(TitledBorder.LEFT);
consonantsPanel.setBorder(consonantsTitle);
return consonantsPanel;
}
/**
* Adds action listener for when the letter button is pressed.
* If guessed correct consonant, distribute points.
* If guessed incorrect, game moves on to next player.
*
* @param button JButton to which we will add the action listener to
* @param character letter that the button represents
* @return same button but with added action listener
*/
private JButton addLetterListener(JButton button, String character) {
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
charactersGuessed.add(character);
//count number of times a guessed letter appear in phrase
int times = 0;
for (int i = 0; i < phrase.length(); i++) {
if (String.valueOf(phrase.charAt(i)).equals(character)) {
times++;
}
}
//can't earn points for vowels
if (!character.equals("A") && !character.equals("E") && !
character.equals("I")
&& !character.equals("O") && !character.equals("U")) {
//distribute points to players
playerScore.set(playerTurn,
playerScore.get(playerTurn) + times * wheelSpaces[spin].dollar);
}
int previousSize = charactersRemaining.size();
charactersRemaining.remove(character);
//if none of characters from phrase is guessed, move on to next player
if (charactersRemaining.size() == previousSize) {
playerTurn = (playerTurn + 1) % players;
}
//disable all cons and vowel buttons
consEnable = false;
vowelEnable = false;
//rebuild the JFrame
getContentPane().removeAll();
getContentPane().add(getScorePanel());
getContentPane().add(getWheelPanel(true));
getContentPane().add(getLetterPanel());
getContentPane().add(getPhrasePanel());
pack();
revalidate();
repaint();
}
});
return button;
}
private ArrayList<JPanel> addButtons(ArrayList<JPanel> panels) {
for (int i = 0; i < 26; i++) {
String character = String.valueOf((char) (((int) 'A') + i));
JButton button = new JButton(character);
button = addLetterListener(button, character);
if (character.equals("A") || character.equals("E") || character.equals("I")
||
character.equals("O") || character.equals("U")) {
if (!vowelEnable) {
button.setEnabled(false);
}
panels.get(0).add(button);
} else {
if (!consEnable) {
button.setEnabled(false);
}
panels.get(1).add(button);
}
if (charactersGuessed.contains(character)) {
//if letter is guessed, disable the button
button.setEnabled(false);
}
}
return panels;
}
/**
* sets up the panel with the letter buttons to guess
*
* @return panel with vowel and consonant buttons
*/
private JPanel getLetterPanel() {
JPanel letterPanel = new JPanel();
letterPanel.setLayout(new BoxLayout(letterPanel, BoxLayout.X_AXIS));
letterPanel.setSize(600, 150);
JPanel vowelPanel = getVowelPanel();
JPanel consonantsPanel = getConsPanel();
ArrayList<JPanel> panels = new ArrayList<JPanel>();
panels.add(vowelPanel);
panels.add(consonantsPanel);
panels = addButtons(panels);
letterPanel.add(panels.get(0));
letterPanel.add(panels.get(1));
return letterPanel;
}
/**
* sets up the panel that displays the phrase
*
* @return panel that displays the phrase
*/
private JPanel getPhrasePanel() {
JPanel phrasePanel = new JPanel(new FlowLayout());
String hiddenPhrase = "";
for (int i = 0; i < phrase.length(); i++) {
String str = phrase.substring(i, i + 1);
if (charactersGuessed.contains(str)) {
// if letter is guessed, reveal that letter
hiddenPhrase += str + " ";
} else if (!(phrase.charAt(i) >= 'A' && phrase.charAt(i) <= 'Z')) {
// if it is puncuation, number, etc. -- not a letter -- then reveal that
symbol
hiddenPhrase += str + " ";
} else {
// otherwise, hide the letter
hiddenPhrase += "- ";
}
}
phrasePanel.add(new JLabel(hiddenPhrase));
return phrasePanel;
}
private boolean isNumeric(String strNum) {
if (strNum == null) {
return false;
}
try {
} catch (NumberFormatException nfe) {
return false;
}
return true;
}