import java.awt.*; //import all the necessary stuff import java.lang.*; import javax.swing.*; import javax.swing.event.*; import javax.swing.table.*; import java.awt.event.*; import java.util.*; import javax.imageio.*; import java.io.*; import java.awt.image.*; import java.security.*; import javax.crypto.*; import java.security.spec.*; import javax.crypto.spec.*; import java.net.*; import java.text.DecimalFormat; public class TriPeaks extends JFrame implements WindowListener { //it's a JFrame that listens to window events private CardPanel board; //the panel with the cards JLabel curGame, maxMin, curStr, sesWin, sesAvg, sesGame, plrGame, plrAvg, maxStr; //the labels for the stats public static final String scoresDir = "GevFpbef"; private final String dirName = scoresDir; //the folder with the score files (ROT13 of TriScores) private final String settingsFile = "TriSet"; private String uName; //name of the player private JPanel statsPanel; //the pnael with the stats private JCheckBoxMenuItem[] cheatItems = new JCheckBoxMenuItem[CardPanel.NCHEATS]; private boolean seenWarn = false; private JCheckBoxMenuItem statsCheck; public TriPeaks(String title) { //class constructor super(title); //call the JFrame contructor } public static void main(String[] args) { //entry point for the application TriPeaks TP = new TriPeaks("TriPeaks"); //create the frame TP.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); //don't do anything when user presses the X - custom close handling TP.createGUI(); //create the GUI TP.pack(); //give everything enough room TP.setIconImage(getIcon("Images" + File.separator + "TriPeaks.png")); TP.setResizable(false); //can't resize the window TP.setVisible(true); //show it. } public static Image getIcon(String path) { //returns an Image based on the path ImageIcon img = getImageIcon(path); //gets the image icon based on the path if (img != null) return img.getImage(); //if the image icon isn't null, get the image from it else return null; //otherwise return null } public static ImageIcon getImageIcon(String path) { //returns an ImageIcon based on the path (ImageIcon implements Icon, and Image doesn't) URL imgURL = TriPeaks.class.getResource(path); //get the URL if (imgURL != null) return new ImageIcon(imgURL); //if the URL isn't null, return the ImageIcon else return null; //otherwise return null } public void createGUI() { //creates the GUI with the given frame getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.PAGE_AXIS)); //align stuff on the Y-Axis setJMenuBar(createMenuBar()); //set the menubar for the frame board = new CardPanel(); //create the panel with the cards getContentPane().add(board); //add it to the frame statsPanel = new JPanel(); //create the stats panel statsPanel.setLayout(new BoxLayout(statsPanel, BoxLayout.LINE_AXIS)); //align stuff on the X-Axis getContentPane().add(statsPanel); //add it to the frame JPanel col1 = new JPanel(); //create the panel for the first column (of 3) col1.setLayout(new BoxLayout(col1, BoxLayout.PAGE_AXIS)); //align stuff on the Y-Axis col1.setBorder(BorderFactory.createEmptyBorder(5, 10, 5, 5)); //give it some room (5 px on each side, 10 on the left) statsPanel.add(col1); //add it to the stats panel statsPanel.add(Box.createHorizontalGlue()); //add horizontal "glue" - even out the space between the columns JPanel col2 = new JPanel(); //same thing for the second column col2.setLayout(new BoxLayout(col2, BoxLayout.PAGE_AXIS)); col2.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); //top, left, bottom, right statsPanel.add(col2); statsPanel.add(Box.createHorizontalGlue()); //more "glue" JPanel col3 = new JPanel(); //and the third col3.setLayout(new BoxLayout(col3, BoxLayout.PAGE_AXIS)); col3.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 10)); //10 on the right statsPanel.add(col3); curGame = new JLabel("Game Winnings: ?"); //create the label, with the default text curGame.setAlignmentX(Component.LEFT_ALIGNMENT); //it should be left-aligned within the panel col1.add(curGame); //add it to the first column //same thing for the rest of the labels maxMin = new JLabel("Most - Won: ?, Lost ?"); maxMin.setAlignmentX(Component.LEFT_ALIGNMENT); col1.add(maxMin); curStr = new JLabel("Current Streak: ?=?"); curStr.setAlignmentX(Component.LEFT_ALIGNMENT); col1.add(curStr); sesWin = new JLabel("Session Winnings: ?"); sesWin.setAlignmentX(Component.LEFT_ALIGNMENT); col2.add(sesWin); sesAvg = new JLabel("Session Average: ?"); sesAvg.setAlignmentX(Component.LEFT_ALIGNMENT); col2.add(sesAvg); sesGame = new JLabel("Session Games: ?"); sesGame.setAlignmentX(Component.LEFT_ALIGNMENT); col2.add(sesGame); plrGame = new JLabel("Player Games: ?"); plrGame.setAlignmentX(Component.LEFT_ALIGNMENT); col3.add(plrGame); plrAvg = new JLabel("Player Average: ?"); plrAvg.setAlignmentX(Component.LEFT_ALIGNMENT); col3.add(plrAvg); maxStr = new JLabel("Longest Streak: ?=?"); maxStr.setAlignmentX(Component.LEFT_ALIGNMENT); col3.add(maxStr); addWindowListener(this); //add a window-event listner to the frame } public JMenuBar createMenuBar() { //creates the menu bar JMenuBar menuBar = new JMenuBar(); //init the menu bar JMenu gameMenu = new JMenu("Game"); //game menu gameMenu.setMnemonic(KeyEvent.VK_G); //can be opened with Alt+G gameMenu.getAccessibleContext().setAccessibleDescription("Game Playing and Operation"); //the tool-tip text menuBar.add(gameMenu); //add the menu to the menu bar JMenuItem deal = new JMenuItem("Deal"); //redeal menu item deal.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0)); //accessed with F2 deal.addActionListener(new ActionListener() { //add an action listener to it public void actionPerformed(ActionEvent e) { board.redeal(); //call the redeal method of the board } }); gameMenu.add(deal); //add the menu item to the menu JMenuItem switchPlr = new JMenuItem("Switch Player..."); //switch players switchPlr.setMnemonic(KeyEvent.VK_P); //Alt+P switchPlr.getAccessibleContext().setAccessibleDescription("Change the current player"); //Tool-tip text switchPlr.addActionListener(new ActionListener() { //add an action listener public void actionPerformed(ActionEvent e) { int penalty = board.getPenalty(); //get the penalty for switching players if (penalty != 0) { //if there's some penalty int uI = JOptionPane.showConfirmDialog(TriPeaks.this, "Are you sure you want to switch players?\nSwitching now results in a penalty of $" + penalty + "!", "Confirm Player Switch", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); //show a confirmation dialog if (uI == JOptionPane.YES_OPTION) board.doPenalty(penalty); //if the user clicked Yes, perform the penalty else return; //Otherwise, the user clicked No, so don't do anything } String tempName = JOptionPane.showInputDialog(TriPeaks.this, "Player Name:", uName); //ask for the user's name if ((tempName != null) && (!tempName.equals(""))) { //if it's not null or empty writeScoreSets(); //write the current user's score board.reset(); uName = tempName; //change the user try { readScoreSets(); //read the new user's scores } catch (NewPlayerException eNP) { board.setDefaults(); } updateStats(); board.repaint(); } } }); gameMenu.add(switchPlr); //add the item to the menu JMenuItem highScores = new JMenuItem("High Scores"); highScores.setMnemonic(KeyEvent.VK_H); highScores.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_H, ActionEvent.CTRL_MASK)); highScores.getAccessibleContext().setAccessibleDescription("Show high score table"); highScores.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { final JDialog scoresDialog = new JDialog(TriPeaks.this, "High Scores", true); JPanel contentPanel = new JPanel(); contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.PAGE_AXIS)); JLabel title = new JLabel("High Score Table"); title.setFont(new Font("Serif", Font.BOLD, 20)); title.setAlignmentX(Component.CENTER_ALIGNMENT); title.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); contentPanel.add(title); HighScoreModel hsModel = new HighScoreModel(); writeScoreSets(); if (!hsModel.readAndSetData()) System.out.println("Error setting table values!"); JTable scoreTable = new JTable(hsModel) { public String getToolTipText(MouseEvent evt) { String tip = null; Point p = evt.getPoint(); if (rowAtPoint(p) == -1) { tip = super.getToolTipText(evt); return tip; } int r = convertRowIndexToModel(rowAtPoint(p)); int c = convertColumnIndexToModel(columnAtPoint(p)); HighScoreModel tm = (HighScoreModel) getModel(); DecimalFormat format = null; double num; if (getColumnClass(c) == Double.class) format = new DecimalFormat("$###,##0.00"); else if (getColumnClass(c) == Integer.class) format = new DecimalFormat("$###,###"); if (format == null) return super.getToolTipText(evt); switch (c) { case 1: int score = ((Integer) tm.getValueAt(r, 1)).intValue(); tip = (String) tm.getValueAt(r, 0) + " is " + ((score < 0) ? "losing $" + -1 * score: "winning $" + score) + "."; break; case 2: double avg = ((Double) tm.getValueAt(r, 2)).doubleValue(); tip = (String) tm.getValueAt(r, 0) + "'s average is " + format.format(avg) + " per game."; break; case 3: double max = (double) ((Integer) tm.getValueAt(r, 3)).intValue(); tip = (String) tm.getValueAt(r, 0) + " has won a maximum of " + format.format(max) + " in one game."; break; case 4: int min = ((Integer) tm.getValueAt(r, 4)).intValue(); tip = (String) tm.getValueAt(r, 0) + " has lost a maximum of $" + -1 * min + " in one game."; break; case 5: int maxStr = ((Integer) tm.getValueAt(r, 5)).intValue(); tip = (String) tm.getValueAt(r, 0) + "'s longest streak is " + maxStr + " cards in a row ($" + ((int) maxStr * (maxStr + 1) / 2) + ")."; break; case 6: int nGames = ((Integer) tm.getValueAt(r, 6)).intValue(); tip = (String) tm.getValueAt(r, 0) + " has played " + nGames + " " + ((nGames == 1) ? "game." : "games."); break; case 7: boolean cheater = ((Boolean) tm.getValueAt(r, 7)).booleanValue(); tip = (String) tm.getValueAt(r, 0) + ((cheater) ? " has cheated already." : " has never cheated yet."); break; default: tip = super.getToolTipText(evt); } return tip; } protected JTableHeader createDefaultTableHeader() { return new JTableHeader(columnModel) { public String getToolTipText(MouseEvent evt) { Point p = evt.getPoint(); int cF = columnModel.getColumnIndexAtX(p.x); int c = columnModel.getColumn(cF).getModelIndex(); switch (c) { case 0: return "The Player's Name."; case 1: return "The Player's current score."; case 2: return "The Player's per-game average."; case 3: return "The maximum the Player has won in one game."; case 4: return "The maximum the Player has lost in one game."; case 5: return "The Player's longest streak."; case 6: return "The number of games played by the Player."; case 7: return "Whether or not the Player has ever cheated."; default: return ""; } } }; } public TableCellRenderer getCellRenderer(int r, int c) { if ((c >= 1) && (c <= 4)) return new CurrencyRenderer(); return super.getCellRenderer(r, c); } }; scoreTable.setAutoCreateRowSorter(true); scoreTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); scoreTable.setPreferredScrollableViewportSize(new Dimension(500, 150)); scoreTable.setFillsViewportHeight(true); final TableRowSorter<HighScoreModel> sorter = new TableRowSorter<HighScoreModel>(hsModel); java.util.List<RowSorter.SortKey> keys = new ArrayList<RowSorter.SortKey>(); keys.add(new RowSorter.SortKey(1, SortOrder.DESCENDING)); sorter.setSortKeys(keys); setRowFilter(sorter, ""); scoreTable.setRowSorter(sorter); JScrollPane scoreScroll = new JScrollPane(scoreTable, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); scoreScroll.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); contentPanel.add(scoreScroll); JPanel searchPanel = new JPanel(new FlowLayout()); //searchPanel.setLayout(new BoxLayout(searchPanel, BoxLayout.LINE_AXIS)); searchPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), "Search (All Columns)")); contentPanel.add(searchPanel); final JTextField searchField = new JTextField(); searchField.setHorizontalAlignment(JTextField.LEFT); searchField.setColumns(20); searchField.setAlignmentX(Component.LEFT_ALIGNMENT); searchField.getDocument().addDocumentListener(new DocumentListener() { public void changedUpdate(DocumentEvent evt) { setRowFilter(sorter, searchField.getText()); } public void insertUpdate(DocumentEvent evt) { setRowFilter(sorter, searchField.getText()); } public void removeUpdate(DocumentEvent evt) { setRowFilter(sorter, searchField.getText()); } }); searchPanel.add(searchField); JButton clearSearch = new JButton("Clear"); clearSearch.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { searchField.setText(""); } }); clearSearch.setAlignmentX(Component.LEFT_ALIGNMENT); searchPanel.add(clearSearch); JButton closeButton = new JButton("Close"); closeButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { scoresDialog.setVisible(false); scoresDialog.dispose(); } }); closeButton.setAlignmentX(Component.CENTER_ALIGNMENT); contentPanel.add(closeButton); scoresDialog.setContentPane(contentPanel); scoresDialog.pack(); scoresDialog.setLocationRelativeTo(TriPeaks.this); scoresDialog.setVisible(true); } }); gameMenu.add(highScores); JMenuItem resetStats = new JMenuItem("Reset"); //reset all stats/scores resetStats.setMnemonic(KeyEvent.VK_R); //Alt+R resetStats.getAccessibleContext().setAccessibleDescription("Reset all stats and scores!"); //tooltip resetStats.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { int uI = JOptionPane.showConfirmDialog(TriPeaks.this, "Are you sure you want to reset your game?\nResetting results in a PERMANENT loss of score and stats!", "Confirm Game Reset", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); //show a confirmation dialog if (uI == JOptionPane.YES_OPTION) { //If the user clicked yes board.reset(); //reset the board setTitle("TriPeaks"); } } }); gameMenu.add(resetStats); //add the item to the menu gameMenu.addSeparator(); //add a separator to the menu JMenuItem exitGame = new JMenuItem("Exit"); //exit the game exitGame.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, ActionEvent.CTRL_MASK)); //accessed with Ctrl+Q getAccessibleContext().setAccessibleDescription("Exit the Game"); //tooltip exitGame.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { int penalty = board.getPenalty(); //get penalty for quitting if (penalty != 0) { //if there's a penalty, show the confirmation dialog int uI = JOptionPane.showConfirmDialog(TriPeaks.this, "Are you sure you want to exit?\nExiting now results in a penalty of $" + penalty + "!", "Confirm Exit", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); if (uI == JOptionPane.YES_OPTION) { //the user agrees to the penalty board.doPenalty(penalty); //perform the penalty } else return; } writeScoreSets(); //write the user's scores System.exit(0); //exit the program } }); gameMenu.add(exitGame); //add it to the menu JMenu optionMenu = new JMenu("Options"); //game options menu optionMenu.setMnemonic(KeyEvent.VK_O); //accessed with Alt+O optionMenu.getAccessibleContext().setAccessibleDescription("Game Options"); //set the tool-tip text menuBar.add(optionMenu); //add it to the menu bar JMenuItem cardStyle = new JMenuItem("Card Style"); //Change the image that appears on the front and back of the cards cardStyle.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, ActionEvent.ALT_MASK)); //Alt+C getAccessibleContext().setAccessibleDescription("Change the picture on the front and back of the cards"); //tooltip cardStyle.addActionListener(new ActionListener() { //add an action listener public void actionPerformed(ActionEvent e) { final JDialog styleDialog = new JDialog(TriPeaks.this, "Card Style"); //create a dialog box for the style final String oldFront = board.getCardFront(); final String oldBack = board.getCardBack(); final JTabbedPane stylesTabs = new JTabbedPane(); //create a tabbed pane ActionListener changeBack = new ActionListener() { //the action listener for the "back" buttons - one listener handles all public void actionPerformed(ActionEvent evt) { board.setCardBack(evt.getActionCommand()); //the action command is set when the button is created board.repaint(); //repaint - with the new style } }; File backsDir = new File("CardSets" + File.separator + "Backs"); //the folder with the back designs if ((!backsDir.exists()) || (!backsDir.isDirectory())) { //if the folder doesn't exist or isn't a folder JOptionPane.showMessageDialog(TriPeaks.this, "Invalid Structure for Card folders"); //give an error return; //stop the execution } File[] backFiles = backsDir.listFiles(); //get the list of files in the folder final ArrayList<JToggleButton> backButtons = new ArrayList<JToggleButton>(); //create and ArrayList of Toggle Buttons String fileName, picName; //fileName is the path of the image. picName is the image name (w/o extension) JToggleButton newBut; //a placeholder for the button ButtonGroup backGroup = new ButtonGroup(); //a button group for the toggle buttons (so only one can be selected) for (int q = 0; q < backFiles.length; q++) { //go through each file in the folder if (!backFiles[q].getName().endsWith(".png")) continue; //if the file isn't a .png, skip it fileName = backFiles[q].toString(); //the path to the image picName = backFiles[q].getName().substring(0, backFiles[q].getName().length() - 4); //the file name, w/o extension newBut = new JToggleButton(getImageIcon(fileName), false); //create a new toggle button for the image, no text, with the image, unselected if (picName.equals(board.getCardBack())) newBut.setSelected(true); //if that's the current back, select the button newBut.setActionCommand(picName); //set the buttons action command - used by the action listener to determine what to use for setting the image newBut.addActionListener(changeBack); //add the action listener to the button - created before newBut.getAccessibleContext().setAccessibleDescription(picName); //tool-tip is the image name backGroup.add(newBut); //add the button to the group, which handles selection backButtons.add(newBut); //add the button to the arrayList } ActionListener changeFront = new ActionListener() { //action listener for changing the front public void actionPerformed(ActionEvent evt) { board.setCardFront(evt.getActionCommand()); //use the action command to set the front design board.repaint(); //repaint with the new design } }; File frontsDir = new File("CardSets" + File.separator + "Fronts"); //the folder with the fronts if ((!frontsDir.exists()) || (!frontsDir.isDirectory())) { //if the folder doesn't exist or isn't a folder JOptionPane.showMessageDialog(TriPeaks.this, "Invalid Structure for Card folders"); //error message return; //stop the creation of the dialog } File[] frontsDirs = frontsDir.listFiles(); //get the list of files in the folder final ArrayList<JToggleButton> frontButtons = new ArrayList<JToggleButton>(); //re-instantiate the arraylist int randCard; //a placeholder for the random card value String previewName, dirName; //previewName is the path to the preview image, dirName is the name of the folder with the card styles ButtonGroup frontGroup = new ButtonGroup(); //a button group for the "front" buttons for (int q = 0; q < frontsDirs.length; q++) { //go through each file in the Fronts folder if (!frontsDirs[q].isDirectory()) continue; //if it's a file (not a directory), skip it dirName = frontsDirs[q].getName(); //the name of the folder randCard = randInt(52); //generate a random value to get the random card previewName = frontsDirs[q].toString() + File.separator + Card.suitAsString((int) (randCard / 13)) + ((randCard % 13) + 1) + ".png"; //get a random card from the folder newBut = new JToggleButton(getImageIcon(previewName), false); //create the button, with the random card as the image, no text, and not selected if (dirName.equals(board.getCardFront())) newBut.setSelected(true); //if the style is current, make the button selected newBut.setActionCommand(dirName); //set the action command as the folder name newBut.addActionListener(changeFront); //add the action listener to the button newBut.getAccessibleContext().setAccessibleDescription(dirName); //set the tooltip text to the folder name frontGroup.add(newBut); //add the button to the group frontButtons.add(newBut); //and to the arraylist } int[] backDims = genGrid(backButtons.size()); //generate the best dimensions for the grid layout JPanel backsPanel = new JPanel(new GridLayout(backDims[0], backDims[1])); //create a panel to hold the buttons, using grid layout with the best-dimensions for (Iterator<JToggleButton> it = backButtons.iterator(); it.hasNext(); ) backsPanel.add(it.next()); //go through the arrayList, and add each button to the panel int[] frontDims = genGrid(frontButtons.size()); //generate a new grid for these buttons JPanel frontsPanel = new JPanel(new GridLayout(frontDims[0], frontDims[1])); //create the panel with the best grid for (Iterator<JToggleButton> it = frontButtons.iterator(); it.hasNext(); ) frontsPanel.add(it.next()); //go through the buttons and add each to the panel int[] useDims = new int[2]; //the effective dimensions if (backDims[0] > frontDims[0]) useDims[0] = backDims[0]; //use the greater dimension (x) else useDims[0] = frontDims[0]; if (backDims[1] > frontDims[1]) useDims[1] = backDims[1]; //use the greater dimension (y) else useDims[1] = frontDims[1]; backsPanel.setPreferredSize(new Dimension(useDims[1] * (Card.WIDTH + 15), useDims[0] * (Card.HEIGHT + 15))); //set the panel sizes with the effective dimensions frontsPanel.setPreferredSize(new Dimension(useDims[1] * (Card.WIDTH + 15), useDims[0] * (Card.HEIGHT + 15))); stylesTabs.addTab("Backs", getImageIcon("Images" + File.separator + "Back.png"), backsPanel, "Card Backs"); //add a tab to the tabbed panel - Backs is the tab text. give it an icon, use the panel as the tab content and Card Backs for the tooltip stylesTabs.setMnemonicAt(0, KeyEvent.VK_B); //the tab can be accessed with Alt+B stylesTabs.addTab("Fronts", getImageIcon("Images" + File.separator + "Front.png"), frontsPanel, "Card Fronts"); //same thing - add a tab for the front styles stylesTabs.setMnemonicAt(1, KeyEvent.VK_F); //Alt+F JButton closeButton = new JButton("Close"); //button to close the dialog closeButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { styleDialog.setVisible(false); //hide the dialog styleDialog.dispose(); //let go of the resources for the dialog } }); JButton revertButton = new JButton("Revert"); //revert button revertButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { board.setCardBack(oldBack); //set the old values board.setCardFront(oldFront); board.repaint(); //repaint styleDialog.setVisible(false); //hide the dialog styleDialog.dispose(); //dispose of the dialog } }); JPanel buttonPanel = new JPanel(new FlowLayout()); //create a panel for the buttons buttonPanel.add(revertButton); buttonPanel.add(closeButton); //add the buttons to the panel JPanel contentPanel = new JPanel(new BorderLayout(5, 5)); //create a new panel to be the content panel contentPanel.add(stylesTabs, BorderLayout.CENTER); //add the tabbed pane to the panel contentPanel.add(buttonPanel, BorderLayout.PAGE_END); //add the panel with the close-button to the panel contentPanel.setOpaque(true); //paint all the pixels, don't skip any styleDialog.setContentPane(contentPanel); //the the content pane of the dialog box styleDialog.pack(); //pack the dialog styleDialog.setLocationRelativeTo(TriPeaks.this); //set the location relative to the frame (in its center) styleDialog.setVisible(true); //show the dialog } }); optionMenu.add(cardStyle); //add it to the menu JMenuItem boardColor = new JMenuItem("Board Background"); //change the boackground color of the board boardColor.setMnemonic(KeyEvent.VK_B); //Alt+B boardColor.getAccessibleContext().setAccessibleDescription("Change the Background Color of the board"); //tool-tip boardColor.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { Color newColor = JColorChooser.showDialog(TriPeaks.this, "Choose Background Color", board.getBackColor()); //show a color chooser, with the current color as the default if (newColor != null) board.setBackColor(newColor); //if the user didn't click Cancel, set the color board.repaint(); //repaint the baord. } }); optionMenu.add(boardColor); //add the item to the menu JMenuItem fontSelect = new JMenuItem("Text Font"); //change the font of the text on the board fontSelect.setMnemonic(KeyEvent.VK_F); //Alt+F fontSelect.getAccessibleContext().setAccessibleDescription("Change the font of the text on the board"); //tool-tip text fontSelect.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { final JDialog fontDialog = new JDialog(TriPeaks.this, "Choose Board Font", true); //create the dialog final Color oldColor = board.getFontColor(); //get the old color - in order to revert final Font oldFont = board.getTextFont(); //get the old color JPanel contentPanel = new JPanel(); //a panel to hold everything contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.PAGE_AXIS)); //align stuff on the y-axis JLabel title = new JLabel("Font Chooser"); //a title title.setFont(new Font("Serif", Font.BOLD, 20)); //make it big & bold title.setAlignmentX(Component.CENTER_ALIGNMENT); //center it title.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); //give it 5 pixels padding on each side contentPanel.add(title); //add it to the main panel JPanel selPanel = new JPanel(new FlowLayout()); //the selection panel contentPanel.add(selPanel); //add it to the main panel final JLabel preview = new JLabel("TriPeaks = Good Game"); //a preview label - very important. All values are "stored" in it because any change is reflected in the label preview.setFont(oldFont); //set the old font (current) preview.setOpaque(true); //make the label opaque preview.setForeground(oldColor); //set the color of the text preview.setBackground(board.getBackColor()); //set the background as the background color of the board preview.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); //give it 3 px. padding on each side preview.setAlignmentX(Component.CENTER_ALIGNMENT); //center-align it final String[] fonts = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames(); //get a list of available fonts int selIndex = 0; //initial selection index for (int q = 0; q < fonts.length; q++) { //go through available fonts if (oldFont.getFamily().equals(fonts[q])) selIndex = q; //find the old font's index } JList fontList = new JList(fonts); //a list for the fonts fontList.addListSelectionListener(new ListSelectionListener() { //add a list selection listener (when the selection changes) public void valueChanged(ListSelectionEvent evt) { if (evt.getValueIsAdjusting()) return; //if the user isn't done selecting, don't do anything int selected = evt.getLastIndex(); //get the new selection index int bold = (preview.getFont().isBold()) ? Font.BOLD : 0; //get the bold and italic status of the preview int ital = (preview.getFont().isItalic()) ? Font.ITALIC : 0; int size = preview.getFont().getSize(); //get the font size of the preview preview.setFont(new Font(fonts[selected], bold | ital, size)); //set the new font } }); fontList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); //only one font can be selected fontList.setSelectedIndex(selIndex); //set the initial selection index fontList.setLayoutOrientation(JList.VERTICAL); //give it a vertical orientation (all in one column) fontList.setVisibleRowCount(10); //10 items are visible JScrollPane fontScroll = new JScrollPane(fontList); //give the list scrollbars fontScroll.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), "Font")); //give the scroll pane an etched border with the title "Font" selPanel.add(fontScroll); //add it to the selection panel fontList.ensureIndexIsVisible(selIndex); //scroll so the initial selection is visible JPanel otrPanel = new JPanel(); //a panel for other stuff otrPanel.setLayout(new BoxLayout(otrPanel, BoxLayout.PAGE_AXIS)); //align stuff on the y-axis otrPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), "Other Options")); //give the panel a border (etched, with title) selPanel.add(otrPanel); //add it to the selection panel JLabel sizeLabel = new JLabel("Size:"); //a label for the size spinner sizeLabel.setAlignmentX(Component.LEFT_ALIGNMENT); //left-align it. otrPanel.add(sizeLabel); //add it to the panel SpinnerModel sizeSpinModel = new SpinnerNumberModel(oldFont.getSize(), 8, 18, 1); //create a spinner model - from 8 to 18 by 1's, starting at the current size. final JSpinner sizeSpin = new JSpinner(sizeSpinModel); //the spinner (final because it's accessed in a nested class sizeSpin.addChangeListener(new ChangeListener() { //add a listener for changes public void stateChanged(ChangeEvent evt) { SpinnerNumberModel model = (SpinnerNumberModel) sizeSpin.getModel(); //get the spinner's model String fontName = preview.getFont().getFamily(); //get the font, bold, and italic status from the preview int bold = (preview.getFont().isBold()) ? Font.BOLD : 0; int ital = (preview.getFont().isItalic()) ? Font.ITALIC : 0; int size = model.getNumber().intValue(); //get the new size from the spinner model preview.setFont(new Font(fontName, bold | ital, size)); //set the font on the preview } }); JFormattedTextField textField = ((JSpinner.DefaultEditor) sizeSpin.getEditor()).getTextField(); //get the text field part of the spinner textField.setColumns(4); //4 columns is more that adequate textField.setHorizontalAlignment(JTextField.LEFT); //left-align the number sizeSpin.setAlignmentX(Component.LEFT_ALIGNMENT); //left-align the spinner otrPanel.add(sizeSpin); //add it to the panel JCheckBox boldCheck = new JCheckBox("Bold", oldFont.isBold()); //a checkbox for the bold status, with the old status as the default boldCheck.addItemListener(new ItemListener() { //add a listener public void itemStateChanged(ItemEvent evt) { String fontName = preview.getFont().getFamily(); //get the stuff from the preview panel (except bold) int bold = (evt.getStateChange() == ItemEvent.SELECTED) ? Font.BOLD : 0; //set it to bold if the checkbox was checked int ital = (preview.getFont().isItalic()) ? Font.ITALIC : 0; int size = preview.getFont().getSize(); preview.setFont(new Font(fontName, bold | ital, size)); //set the new font } }); boldCheck.setAlignmentX(Component.LEFT_ALIGNMENT); //left-align the checkbox otrPanel.add(boldCheck); //add it to the panel JCheckBox italCheck = new JCheckBox("Italic", oldFont.isItalic()); //a checkbox for the italic status - same as above italCheck.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent evt) { String fontName = preview.getFont().getFamily(); int bold = (preview.getFont().isBold()) ? Font.BOLD : 0; int ital = (evt.getStateChange() == ItemEvent.SELECTED) ? Font.ITALIC : 0; int size = preview.getFont().getSize(); preview.setFont(new Font(fontName, bold | ital, size)); } }); italCheck.setAlignmentX(Component.LEFT_ALIGNMENT); otrPanel.add(italCheck); final JButton colorBut = new JButton("Font Color"); //a button to select the text color colorBut.addActionListener(new ActionListener() { //add an action listener public void actionPerformed(ActionEvent evt) { Color newColor = JColorChooser.showDialog(TriPeaks.this, "Choose Font Color", preview.getForeground()); //show a color chooser - default color is the current color if (newColor != null) { //if the user didn't click 'Cancel' colorBut.setForeground(newColor); //set the text color on the button preview.setForeground(newColor); //and on the preview label } } }); colorBut.setForeground(oldColor); //set the default text color colorBut.setBackground(board.getBackColor()); //set the background color of the button colorBut.setAlignmentX(Component.LEFT_ALIGNMENT); //left-align the button otrPanel.add(colorBut); //add it to the panel JPanel previewPanel = new JPanel(); //a panel for the preview label (so the label's background color works properly) previewPanel.setLayout(new BoxLayout(previewPanel, BoxLayout.PAGE_AXIS)); //align stuff on the y-axis previewPanel.add(preview); //add the label to it previewPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), "Preview")); //give it an etched, titled border. contentPanel.add(previewPanel); //add it to the main panel JPanel buttonPanel = new JPanel(new FlowLayout()); //a panel to hold the buttons JButton closeButton = new JButton("OK"); //OK button closeButton.getAccessibleContext().setAccessibleDescription("Apply the font and close"); //tool-tip text closeButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { board.setFontColor(preview.getForeground()); //set the font color board.setTextFont(preview.getFont()); //set the font board.repaint(); //repaint the board fontDialog.setVisible(false); //hide the dialog fontDialog.dispose(); //dispose of it } }); buttonPanel.add(closeButton); //add it to the panel JButton revertButton = new JButton("Cancel"); //revert button revertButton.getAccessibleContext().setAccessibleDescription("Revert to the previously used font"); //tool-tip revertButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { board.setFontColor(oldColor); //set the old values board.setTextFont(oldFont); board.repaint(); //repaint the board fontDialog.setVisible(false); //hide the dialog fontDialog.dispose(); //dispose of it } }); buttonPanel.add(revertButton); //add it to the panel JButton applyButton = new JButton("Apply"); //apply changes button applyButton.getAccessibleContext().setAccessibleDescription("Apply the new Font"); //tool-ip applyButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { board.setFontColor(preview.getForeground()); //set new values board.setTextFont(preview.getFont()); board.repaint(); //repaint the board } }); buttonPanel.add(applyButton); //add it to the panel contentPanel.add(buttonPanel); //add the button panel to the main panel fontDialog.setContentPane(contentPanel); //set the main panel for the dialog fontDialog.pack(); //pack the dialog fontDialog.setResizable(false); fontDialog.setLocationRelativeTo(TriPeaks.this); //center it relative to the frame fontDialog.setVisible(true); //show it. } }); optionMenu.add(fontSelect); //add the item to the menu. statsCheck = new JCheckBoxMenuItem("Show stats", true); //a checkbox to show/hide stats (show by default) statsCheck.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, ActionEvent.ALT_MASK)); statsCheck.getAccessibleContext().setAccessibleDescription("Show / Hide stats"); statsCheck.addItemListener(new ItemListener() { //add an Item-event listener - changes to the item public void itemStateChanged(ItemEvent e) { if (e.getStateChange() == ItemEvent.SELECTED) { //if it got selected statsPanel.setVisible(true); //show the stats panel updateStats(); //set the labels } else statsPanel.setVisible(false); //hide the stats panel pack(); //re-pack the frame } }); optionMenu.add(statsCheck); //add it to the menu JMenuItem resetDefs = new JMenuItem("Reset Defaults"); //Resets settings to their defaults resetDefs.getAccessibleContext().setAccessibleDescription("Reset the settings to their default values"); //set the tooltip text resetDefs.addActionListener(new ActionListener() { //add action listener public void actionPerformed(ActionEvent e) { int uI = JOptionPane.showConfirmDialog(TriPeaks.this, "Are you sure you want to reset ALL settings?", "Confirm Reset", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); //show a confirmation dialog if (uI == JOptionPane.YES_OPTION) { //if the user chose 'yes' board.setDefaults(); //set the defaults on the board board.repaint(); //repaint the board statsCheck.setSelected(true); //show the stats panel } } }); optionMenu.add(resetDefs); //add it to the menu JMenu cheatMenu = new JMenu("Cheats"); //a menu with cheats cheatMenu.addMenuListener(new MenuListener() { //add a menu listener to it public void menuSelected(MenuEvent e) { //when the menu was selected if (!board.hasCheated() && !seenWarn) JOptionPane.showMessageDialog(TriPeaks.this, "Using Cheats will SCAR your name!!!\nThe only way to un-scar is to RESET!!!\nProceed at your own risk!!!", "Cheat Warning!", JOptionPane.WARNING_MESSAGE); //if the user hasn't cheated yet, display a warning. seenWarn = true; } public void menuDeselected(MenuEvent e) { } //not interested in these, but necessary for implementation public void menuCanceled(MenuEvent e) { } }); menuBar.add(cheatMenu); //add it to the menu bar cheatItems[0] = new JCheckBoxMenuItem("Cards face up"); //cheat 1 - all cards appear face-up (doesn't actually make them face-up) cheatItems[0].addItemListener(new ItemListener() { //add item listener public void itemStateChanged(ItemEvent e) { if (e.getStateChange() == ItemEvent.SELECTED) board.setCheat(0, true); //if it was checked, enable the cheat else board.setCheat(0, false); //if it was unchecked, disable the cheat board.repaint(); //repaint the board setTitle("TriPeaks - Cheat Mode"); //set the cheating title bar } }); cheatMenu.add(cheatItems[0]); //same thing for the rest of the cheats cheatItems[1] = new JCheckBoxMenuItem("Click any card"); //cheat 2 - click any card that's face-up (regardless of value) cheatItems[1].addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { if (e.getStateChange() == ItemEvent.SELECTED) board.setCheat(1, true); else board.setCheat(1, false); board.repaint(); setTitle("TriPeaks - Cheat Mode"); } }); cheatMenu.add(cheatItems[1]); cheatItems[2] = new JCheckBoxMenuItem("No Penalty"); //cheat 3 - no penalty (score can never go down) cheatItems[2].addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { if (e.getStateChange() == ItemEvent.SELECTED) board.setCheat(2, true); else board.setCheat(2, false); board.repaint(); setTitle("TriPeaks - Cheat Mode"); } }); cheatMenu.add(cheatItems[2]); menuBar.add(Box.createHorizontalGlue()); //The next menu will be on the right JMenu helpMenu = new JMenu("Help"); //Help menu helpMenu.setMnemonic(KeyEvent.VK_H); //Accessed with Alt+H helpMenu.getAccessibleContext().setAccessibleDescription("Game Help and Information"); //tool-tip text menuBar.add(helpMenu); //add it to the menu bar JMenuItem gameHelp = new JMenuItem("Help", getImageIcon("Images" + File.separator + "help.png")); //basic explanation of gameplay gameHelp.getAccessibleContext().setAccessibleDescription("How to Play & Strategies"); gameHelp.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0)); gameHelp.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { final JDialog helpDialog = new JDialog(TriPeaks.this, "How to Play"); //create a new dialog box Font titleFont = new Font("SansSerif", Font.BOLD, 16); Font textFont = new Font("Serif", Font.PLAIN, 14); JLabel titleHelp = new JLabel("How to Play"); //the title text titleHelp.setFont(titleFont); //make it big and bold titleHelp.setHorizontalAlignment(JLabel.CENTER); //make it centered JTextArea textHelp = new JTextArea(); //create the area for the text textHelp.setText(" The goal of the game is to remove all the cards: you can remove any card that is adjacent in value. (e.g. If you have an Ace, you can remove a King or a Two). Suit doesn't matter.\n If there is no adjacent card, you can take a card from the deck, with a penalty of $5. For the first card you remove, you get $1; for the second $2; $3 for the third; and so on. However, when you take a card from the deck, the streak gets reset to 0.\n You get $15 for the first two peaks that you reach, and $30 for the last one (i.e. clearing the board). You can redeal before you clear the board AND still have some cards in the deck, but with a penalty of $5 for every card on the board. There is no penalty for redealing if your deck is empty or if you've cleared the board."); //set the text of the text area textHelp.setEditable(false); //the user can't change the help text textHelp.setFont(textFont); //set the font for the text textHelp.setLineWrap(true); //the text will wrap at the edges textHelp.setWrapStyleWord(true); //the text will only wrap whole words JScrollPane helpScroll = new JScrollPane(textHelp); //used to add scrollbars to the text area helpScroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); JPanel helpPanel = new JPanel(new BorderLayout(3, 3)); //create a panel to hold the scroll pane and title helpPanel.add(titleHelp, BorderLayout.PAGE_START); //add the title to the top helpPanel.add(helpScroll, BorderLayout.CENTER); //add the scroll pane to the center //same thing for the srategy and cheat text JLabel titleStrat = new JLabel("Game Strategies"); titleStrat.setFont(titleFont); titleStrat.setHorizontalAlignment(JLabel.CENTER); JTextArea textStrat = new JTextArea(); textStrat.setText(" The more cards you get in a row, the higher your score. However, there are times when you have to choose between cards. If those cards get you the same score, there are several strategies involved:\n 1) Pick the card that opens up more cards That will give you more to choose from on your next move. It might go with the card you just took.\n 2) If one on the choices is a peak, don't choose the peak. It doesn't open any cards.\n Other than choosing cards, try working out a streak in your head. If they're the same, go with the one that opens more cards."); textStrat.setEditable(false); textStrat.setFont(textFont); textStrat.setLineWrap(true); textStrat.setWrapStyleWord(true); JScrollPane stratScroll = new JScrollPane(textStrat); stratScroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); JPanel stratPanel = new JPanel(new BorderLayout(3, 3)); stratPanel.add(titleStrat, BorderLayout.PAGE_START); stratPanel.add(stratScroll, BorderLayout.CENTER); JLabel titleCheat = new JLabel("Game Cheats"); titleCheat.setFont(titleFont); titleCheat.setHorizontalAlignment(JLabel.CENTER); JTextArea textCheat = new JTextArea(); textCheat.setText("I HIGHLY DISCOURAGE CHEATING!!!\n\n There is a penalty for chating! Your account will be \"scarred\" - \"CHEATER\" will be displayed in the backgournd and \"Cheat Mode\" will appear in the titlebar once you enable any cheat. Even if you disable all cheats, your username will still be scarred. The only was to un-scar is to RESET! Here is what the cheats do:\n - All cards face up = all cards appear to be face-up, but act normally, as without the cheat.\n - Click any card = click any face-up card. Beware when using with previous cheat - cards only appear face-up\n - No Penalty = no penalty for anything. So your score never goes down."); textCheat.setEditable(false); textCheat.setFont(textFont); textCheat.setLineWrap(true); textCheat.setWrapStyleWord(true); JScrollPane cheatScroll = new JScrollPane(textCheat); cheatScroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); JPanel cheatPanel = new JPanel(new BorderLayout(3, 3)); cheatPanel.add(titleCheat, BorderLayout.PAGE_START); cheatPanel.add(cheatScroll, BorderLayout.CENTER); JTabbedPane helpTabs = new JTabbedPane(); //Initialize the tabbed pane helpTabs.addTab("How To Play", getImageIcon("Images" + File.separator + "help.png"), helpPanel, "How to Play"); //add the tab to the tabbed pane helpTabs.setMnemonicAt(0, KeyEvent.VK_P); //Alt+P helpTabs.addTab("Strategies", getImageIcon("Images" + File.separator + "Strategy.png"), stratPanel, "Game Strategies"); helpTabs.setMnemonicAt(1, KeyEvent.VK_S); //Alt+S helpTabs.addTab("Cheats", getImageIcon("Images" + File.separator + "cheat.png"), cheatPanel, "Game Cheats"); helpTabs.setMnemonicAt(2, KeyEvent.VK_C); //Alt+C helpScroll.getVerticalScrollBar().setValue(0); stratScroll.getVerticalScrollBar().setValue(0); cheatScroll.getVerticalScrollBar().setValue(0); JButton closeButton = new JButton("Close"); //button to close the dialog closeButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { helpDialog.setVisible(false); //hide the dialog helpDialog.dispose(); //dispose of the resources for the dialog } }); JPanel closePanel = new JPanel(); //a panel for the butotn closePanel.setLayout(new BoxLayout(closePanel, BoxLayout.LINE_AXIS)); //Align stuff on the X-Axis closePanel.add(Box.createHorizontalGlue()); //right-align the button closePanel.add(closeButton); //add the button to the panel closePanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 5, 5)); JPanel contentPanel = new JPanel(new BorderLayout(5, 5)); //create a panel to be the content panel, with a 5-pixel gap between elements contentPanel.add(helpTabs, BorderLayout.CENTER); //add the tabbed pane to the center contentPanel.add(closePanel, BorderLayout.PAGE_END); //add the panel with the close-button to the bottom helpDialog.setContentPane(contentPanel); //set the panel as the content pane helpDialog.setSize(new Dimension(400, 400)); //make the dialog 400 x 400 pixels helpDialog.setLocationRelativeTo(TriPeaks.this); //make it relative to the frame (in the center of the frame) helpDialog.setVisible(true); //show the dialog } }); helpMenu.add(gameHelp); //add the item to the menu helpMenu.addSeparator(); //add a separator to the menu JMenuItem about = new JMenuItem("About..."); //about the program/creator about.setMnemonic(KeyEvent.VK_A); about.getAccessibleContext().setAccessibleDescription("About the creator and program"); about.addActionListener(new ActionListener() { //add an action listener public void actionPerformed(ActionEvent e) { JOptionPane.showMessageDialog(TriPeaks.this, "TriPeaks Solitaire implementation by Valera Trubachev.\nWritten in Java using Kate in Linux.\n(C) 2008\nSpecial thanks to Christian d'Heureuse\nfor his Base64 encoder/decoder."); //kind of like some credits... } }); helpMenu.add(about); //add the item to the menu return menuBar; //return the finished menu bar } public void updateStats() { //sets the text of the stats labels if (!statsPanel.isVisible()) return; //if the stats panel isn't shown, don't do anything int[] stats = board.getAllStats(); //get the stats, which are stored in the board DecimalFormat intFmt = new DecimalFormat("$###,###"); DecimalFormat dblFmt = new DecimalFormat("$###,##0.00"); curGame.setText("Game Winnings: " + intFmt.format(stats[1])); //what was won/lost in the current game maxMin.setText("Most - Won: " + intFmt.format(stats[6]) + ", Lost: " + intFmt.format(stats[7])); //record win/loss during any game curStr.setText("Current Streak: " + stats[3] + " = " + intFmt.format((stats[3] * (stats[3] + 1) / 2))); //current streak sesWin.setText("Session Winnings: " + intFmt.format(stats[2])); //what was won/lost during the session (start program = new session) if (stats[5] != 0) { //if some games were played (so denominator doesn't equal 0) double avg = ((double) stats[2]) / ((double) stats[5]); //calulate the average sesAvg.setText("Session Average: " + dblFmt.format(avg)); //round the average } else sesAvg.setText("Session Average: $0.00"); //set it to 0 if no games were played sesGame.setText("Session Games: " + stats[5]); //how many games were played during the session plrGame.setText("Player Games: " + stats[4]); //how many games the player played altogether if (stats[4] != 0) { //if the player has played any games double avg = ((double) stats[0]) / ((double) stats[4]); //calculate the average plrAvg.setText("Player Average: " + dblFmt.format(avg)); //round the average } else plrAvg.setText("Player Average: $0.00"); //set it to 0 is no games were ever played maxStr.setText("Longest Streak: " + stats[8] + " = " + intFmt.format((stats[8] * (stats[8] + 1) / 2))); //longest streak ever by the player } private static void setRowFilter(TableRowSorter<HighScoreModel> sorter, String pattern) { RowFilter<HighScoreModel, Object> filter = null; try { filter = RowFilter.regexFilter(pattern); } catch (java.util.regex.PatternSyntaxException ePSE) { return; } sorter.setRowFilter(filter); } public static String capitalize(String in) { if (in.length() == 0) return ""; if (in.length() == 1) return in.toUpperCase(); return Character.toUpperCase(in.charAt(0)) + in.substring(1); } public static String dSign(int in) { //add a dollar sign to a number if (in < 0) return ("-$" + (-1) * in); //put the negative sign out in front if it's negative else return "$" + in; //otherwise just add the dollar sign } public int randInt(int max) { //returns a random integer - 0 <= anInt < max int anInt = (int) (max * Math.random()); //generate a the random value return anInt; //return it } public int[] genGrid(int num) { //generates an "optimal" grid based on the number of elements int[] dim = new int[2]; //the array for the dimensions for (int q = 1; q <= num; q++) { //go through each of the numbers to the given one if (q * q == num) { //if it's a perfect square dim[0] = dim[1] = q; //set both values as the given number's square root return dim; //return the dimensions } } for (int q = 1; q <= num; q++) { //go through the numbers again - check for something else int w; //a placeholder for (w = 1; w <= q + 2; w++) { //go from 1 to 2 more than the current number if (q * w >= num) { //if the grid will fit dim[0] = q; //set the first value dim[1] = w; //and the second return dim; //return the dimensions } } if ((q + 1) * (q + 2) >= num) { //if +1 and +2 will satisfy the number dim[0] = q + 1; //set the first value dim[1] = q + 2; //set the second value return dim; //return the dimensions } for ( ; w <= q + 4; w++) { //go to the 4 more than the current number (no initialization statement - go from the previous for left off) if (q * w >= num) { //if the grid will fit dim[0] = q; //set the first value dim[1] = w; //and the second return dim; //return the dimensions } } } return dim; //if something BAD happened, return 0 x 0 } public static String rot13(String in) { //calculates the ROT13 cipher of a string String low = in.toLowerCase(); //only lowercase characters are wanted StringBuffer out = new StringBuffer(); //a buffer for the output string final String letters = "abcdefghijklmnopqrstuvwxyz"; //all the letters of the alphabet int index, newIndex; //two index holders for (int q = 0; q < low.length(); q++) { //go through the letters in the input string index = letters.indexOf(low.charAt(q)); //find the current character's index in the alphabet string if (index == -1) continue; //if the letter wasn't found, skip it newIndex = (index + 13) % 26; //do the rotation by 13 out.append(letters.charAt(newIndex)); //append the ciphered characted } return out.toString(); //return the ciphered string } public static String backward(String in) { //reverse a string StringBuffer out = new StringBuffer(); //buffer for output for (int q = 1; q <= in.length(); q++) { //go through the characters out.append(in.charAt(in.length() - q)); //append that character from the end of the string } return out.toString(); //return the reversed string } public void readScoreSets() throws NewPlayerException { //reads the scores from the current user's file. String fileName = rot13(uName); //the filename is the ROT13 cipher of their name File file = new File(dirName + File.separator + fileName + ".txt"); //get the file String line = null; //placeholder for the line int[] stats = new int[CardPanel.NSTATS]; //the array for the stats boolean[] cheats = new boolean[CardPanel.NCHEATS]; //cheats array for the cheat menu items boolean hasCheated = false; //the cheat status int lNum = -1; //line number (incremented before setting value) Encryptor dec = new Encryptor(backward(fileName)); //set up the encryptor to decrypt the lines (the passphrase is the filename backwards) try { if (file == null) throw new NewPlayerException("New Player: " + uName); //if the file is null, don't do anything BufferedReader in = new BufferedReader(new FileReader(file)); //create a buffered reader for the file String deced; while ((line = in.readLine()) != null) { //read the lines one-by-one lNum++; //increment the line number if (lNum > (stats.length + cheats.length + 6)) break; //stop if there are more lines than needed deced = dec.decrypt(line); if ((lNum >= 0) && (lNum < stats.length)) stats[lNum] = Integer.parseInt(deced); //set the value based on the decrypted line, if the line belongs to the stats array else if ((lNum >= stats.length) && (lNum < (stats.length + cheats.length))) cheats[lNum - stats.length] = Boolean.parseBoolean(deced); //set the values based on the decrypted line, if the line belongs to the cheats array else if (lNum == stats.length + cheats.length) hasCheated = Boolean.parseBoolean(deced); else if (lNum == stats.length + cheats.length + 1) board.setCardFront(deced); else if (lNum == stats.length + cheats.length + 2) board.setCardBack(deced); else if (lNum == stats.length + cheats.length + 3) { int cm1, cm2; //two commas cm1 = deced.indexOf(','); //get the indexes of the two commas cm2 = deced.lastIndexOf(','); if ((cm1 == -1) || (cm2 == -1) || (cm1 == cm2)) continue; //if either comma isn't found, exit board.setBackColor(new Color(Integer.parseInt(deced.substring(0, cm1)), Integer.parseInt(deced.substring(cm1 + 1, cm2)), Integer.parseInt(deced.substring(cm2 + 1)))); //convert to integer and set the color } else if (lNum == stats.length + cheats.length + 4) { int dash, cm1, cm2; dash = deced.indexOf('-'); cm1 = deced.indexOf(','); cm2 = deced.lastIndexOf(','); if ((dash == -1) || (cm1 == -1) || (cm2 == -1) || (cm1 == cm2)) continue; int bold = (Boolean.parseBoolean(deced.substring(dash + 1, cm1))) ? Font.BOLD : 0; int ital = (Boolean.parseBoolean(deced.substring(cm1 + 1, cm2))) ? Font.ITALIC : 0; board.setTextFont(new Font(deced.substring(0, dash), bold | ital, Integer.parseInt(deced.substring(cm2 + 1)))); } else if (lNum == stats.length + cheats.length + 5) { int cm1, cm2; cm1 = deced.indexOf(','); cm2 = deced.lastIndexOf(','); if ((cm1 == -1) || (cm2 == -1) || (cm1 == cm2)) continue; board.setFontColor(new Color(Integer.parseInt(deced.substring(0, cm1)), Integer.parseInt(deced.substring(cm1 + 1, cm2)), Integer.parseInt(deced.substring(cm2 + 1)))); } else if (lNum == stats.length + cheats.length + 6) { if (Long.parseLong(deced) != file.lastModified()) { file.delete(); JOptionPane.showMessageDialog(this, "Score file has been modified since\nlast used by TriPeaks!\nThe file HAS BEEN DELETED!!!\nPlease don't cheat like that again!", "Cheating Error", JOptionPane.ERROR_MESSAGE); board.setDefaults(); board.reset(); return; } } } board.setStats(stats); //set the stats in the board board.setCheated(hasCheated); //set the cheat status setTitle(hasCheated ? "TriPeaks - Cheat Mode" : "TriPeaks"); //set the title based on the cheat status for (int q = 0; q < cheats.length; q++) { //go through the cheats cheatItems[q].setSelected(cheats[q]); //set the selected status of the menu items used for the cheats } updateStats(); //update the labels board.repaint(); //repaint the board in.close(); //close the file } catch (FileNotFoundException eFNF) { //file wasn't found (probalby because the user doesn't exist yet System.out.println("File not found (probably because the User hasn't played before): " + eFNF.getMessage()); } catch(IOException eIO) { //other IO error System.out.println("Error reading from file -OR- closing file"); } } public void writeScoreSets() { //writes the scores for the current player String fileName = rot13(uName); //filename is the ROT13 cipher of the username File setFile = new File(dirName + File.separator + fileName + ".txt"); //create the file Encryptor enc = new Encryptor(backward(fileName)); //set up the encryptor to encrpyt the lines try { if (setFile == null) return; //if the file doesn't exist, don't do anything BufferedWriter out = new BufferedWriter(new FileWriter(setFile)); //create a buffered writer for the file boolean[] cheats = board.getCheats(); Color boardColor = board.getBackColor(); Font textFont = board.getTextFont(); Color fontColor = board.getFontColor(); long dtMod = new Date().getTime(); out.write(enc.encrypt("" + board.getScore())); //player's overall score out.newLine(); //new line out.write(enc.encrypt("" + board.getHighScore())); //player's highes score out.newLine(); out.write(enc.encrypt("" + board.getLowScore())); //player's lowest score out.newLine(); out.write(enc.encrypt("" + board.getNumGames())); //number of games played by the user out.newLine(); out.write(enc.encrypt("" + board.getHighStreak())); //player's longest streak out.newLine(); out.write(enc.encrypt("" + cheats[0])); //first cheat out.newLine(); out.write(enc.encrypt("" + cheats[1])); //second cheat out.newLine(); out.write(enc.encrypt("" + cheats[2])); //third cheat out.newLine(); out.write(enc.encrypt("" + board.hasCheated())); //player's cheat status out.newLine(); out.write(enc.encrypt("" + board.getCardFront())); out.newLine(); out.write(enc.encrypt("" + board.getCardBack())); out.newLine(); out.write(enc.encrypt(boardColor.getRed() + "," + boardColor.getGreen() + "," + boardColor.getBlue())); out.newLine(); out.write(enc.encrypt(textFont.getFamily() + "-" + textFont.isBold() + "," + textFont.isItalic() + "," + textFont.getSize())); out.newLine(); out.write(enc.encrypt(fontColor.getRed() + "," + fontColor.getGreen() + "," + boardColor.getBlue())); out.newLine(); out.write(enc.encrypt("" + 1000 * ((long) dtMod / 1000))); out.close(); //close the file setFile.setLastModified(dtMod); } catch (FileNotFoundException eFNF) { //file wasn't found System.out.println("File not found: " + eFNF.getMessage()); } catch (IOException eIO) { //other IO exception System.out.println("Error writing to file -OR- closing file"); } } public void windowOpened(WindowEvent e) { //the window is opened InputStream is = TriPeaks.class.getResourceAsStream(settingsFile); //get the file as a stream String line = null; //placeholder for the line String defName = ""; int lNum = -1; try { if (is == null) throw new Exception("First Time Running"); BufferedReader in = new BufferedReader(new InputStreamReader(is)); //create a buffered reader for the file if ((line = in.readLine()) != null) { //read the line defName = line; } in.close(); //close the file } catch (FileNotFoundException eFNF) { //file wasn't found (probably first time running) System.out.println("File not found (probably because the User hasn't played before): " + eFNF.getMessage()); } catch (IOException eIO) { //other IO error System.out.println("Error reading from file -OR- closing file"); } catch (Exception eE) { System.out.println("First time run"); } uName = JOptionPane.showInputDialog(this, "Player Name:", defName); //ask for the player's name if ((uName == null) || (uName.equals(""))) System.exit(0); //if the name is empty or Cancel was pressed, exit try { readScoreSets(); //read the scores for the player } catch (NewPlayerException eNP) { board.setDefaults(); } } public void windowClosing(WindowEvent e) { //the X is clicked (not when the window disappears - that's windowClosed int penalty = board.getPenalty(); //get the penalty for quitting if (penalty != 0) { //if there is a penalty at all int uI = JOptionPane.showConfirmDialog(this, "Are you sure you want to quit?\nQuitting now results in a penalty of $" + penalty + "!", "Confirm Quit", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); //show a confirmation message if (uI == JOptionPane.YES_OPTION) { //if the user clicked Yes board.doPenalty(penalty); //perform the penalty } else return; //no was clicked - don't do anything } File setFile = new File(settingsFile); //create the file try { if (setFile == null) return; //if the file doesn't exist, don't do anything BufferedWriter out = new BufferedWriter(new FileWriter(setFile)); //create a buffered writer for the file out.write(uName); //write the default username out.close(); //close the file } catch (FileNotFoundException eFNF) { //file wasn't found System.out.println("File not found: " + eFNF.getMessage()); } catch (IOException eIO) { //other IO exception System.out.println("Error writing to file -OR- closing file"); } writeScoreSets(); //write the scores for the user System.exit(0); //exit } //the following methods aren't used, but necessary to implement KeyListener and WindowListener public void windowClosed(WindowEvent e) { } public void windowIconified(WindowEvent e) { } public void windowDeiconified(WindowEvent e) { } public void windowActivated(WindowEvent e) { } public void windowDeactivated(WindowEvent e) { } } //end class TriPeaks class CardPanel extends JPanel implements MouseListener { private Color backColor = (Color.GREEN).darker().darker(); //background color of the board private Color fontColor = Color.WHITE; private Font textFont = new Font("Serif", Font.BOLD, 14); public Card[] theCards = new Card[52]; //array with the cards public static final int NSTATS = 5; public static final int NCHEATS = 3; private boolean[] cheats = new boolean[NCHEATS]; private boolean hasCheatedYet = false; private int disIndex = 51; //index of the card in the discard pile private int score = 0; //player's overall score private int gameScore = 0; //current game score private int sesScore = 0; //session score private int streak = 0; //streak (number of cards, not the value) private int remCards = 0; //cards remaining in the deck private int cardsInPlay = 0; //cards left on the board (not removed into the discard pile) private int remPeaks = 3; //peaks remaining (0 is a clear board) private int numGames = 0; //number of player games private int sesGames = 0; //number of session games private int highScore = 0; //highest score private int lowScore = 0; //lowest score private int highStreak = 0; //longest strea private String status = ""; //status text (used later) private String frontFolder = "Default"; //folder in which the fronts of the cards are stored private String backStyle = "Default"; //style for the back of the cards public CardPanel() { //class constructor for (int q = 0; q < 52; q++) { //initialize all the cards theCards[q] = new Card(0, 0, false, false, 0, 0); //create a new Card object - random values - so it doesn't throw NullPointerException... theCards[q].setVisible(false); } setPreferredSize(new Dimension(Card.WIDTH * 10, Card.HEIGHT * 4)); //sets the size of the panel (10 cards by 4 cards) addMouseListener(this); //adds a mouse-listener to the board } public void paint(Graphics g) { //custom paint method super.paintComponent(g); //paints the JPanel g.setColor(backColor); //use the background color g.fillRect(0, 0, getSize().width, getSize().height); //draw the background if (hasCheatedYet) { //if the user has ever cheated g.setColor(new Color(fontColor.getRed(), fontColor.getGreen(), fontColor.getBlue(), 80)); //set the color - white, somewhat transparent g.setFont(new Font("SansSerif", Font.BOLD, 132)); //set the font - big and fat g.drawString("CHEATER", 0, getSize().height - 5); //print "CHEATER" on the bottom edge of the board } for (int q = 0; q < 52; q++) { //go through each card if (theCards[q] == null) continue; //if a card is null (i.e. program was just started, cards not initialized yet), skip it if (!theCards[q].isVisible()) continue; //if a card isn't visible, skip it BufferedImage img = null; //image to be created URL imgURL = null; //URL of the image if (!theCards[q].isFacingDown()) //if it's face-up imgURL = TriPeaks.class.getResource("CardSets" + File.separator + "Fronts" + File.separator + frontFolder + File.separator + theCards[q].suitAsString() + (theCards[q].getValue() + 1) + ".png"); //get the corresponding front of the card else {//otherwise it's face-down if (!cheats[0]) imgURL = TriPeaks.class.getResource("CardSets" + File.separator + "Backs" + File.separator + backStyle + ".png"); //get the image for the back of the card - if the first cheat isn't on else imgURL = TriPeaks.class.getResource("CardSets" + File.separator + "Fronts" + File.separator + frontFolder + File.separator + theCards[q].suitAsString() + (theCards[q].getValue() + 1) + ".png"); //get the corresponding front of the card if the cheat is on... } if (imgURL == null) continue; try { img = ImageIO.read(imgURL); //try to read the image } catch (IOException eIO) { System.out.println("Error reading card image"); //There's an error (probably because the card doesn't exist. } if (img == null) continue; int startX = theCards[q].getX() - ((int) Card.WIDTH / 2); //left edge of the laft int startY = theCards[q].getY() - ((int) Card.HEIGHT / 2); //top of the card int endX = startX + Card.WIDTH; //right int endY = startY + Card.HEIGHT; //bottom g.drawImage(img, startX, startY, endX, endY, 0, 0, img.getWidth(null), img.getHeight(null), null); //draws the image on the panel - resizing/scaling if necessary } String scoreStr = (score < 0) ? "Lost $" + (-1) * score : "Won $" + score; //The won/lost string String remStr = remCards + ((remCards == 1) ? " card" : " cards") + " remaining"; //display how many cards are remaining g.setColor(fontColor); //the text is white g.setFont(textFont); //set the font for the text g.drawString(scoreStr, 5, Card.HEIGHT * 3); //put the score on the panel g.drawString(remStr, 5, Card.HEIGHT * 3 + 25); //put the remaining cards on the panel g.drawString(status, 5, getSize().height - 10); //print the status message. status = ""; //reset the status message } public void redeal() { //redeals the cards int penalty = getPenalty(); //get the penalty for redealing if (penalty != 0) { //if there is a penalty int uI = JOptionPane.showConfirmDialog(this, "Are you sure you want to redeal?\nRedealing now results in a penalty of $" + penalty + "!", "Confirm Redeal", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); //show a confimation message if (uI == JOptionPane.YES_OPTION) doPenalty(penalty); //do the penalty if the user agreed else return; //the user doesn't like the penalty, don't rededal } int[] cards = randomize(); //randomize the cards for (int q = 0; q < 52; q++) { //initialize all the cards theCards[q] = new Card(); //create a new Card object theCards[q].setSuit((int) cards[q] / 13); //set the card's suit theCards[q].setValue(cards[q] % 13); //set its value theCards[q].setVisible(true); //all cards are visible, so far } for (int q = 0; q < 3; q++) { //first row theCards[q].setX(2 * Card.WIDTH + q * 3 * Card.WIDTH); //set the X-coord theCards[q].setY((int) Card.HEIGHT / 2); //set the Y-coord for the card theCards[q].flip(true); //make it face-down } for (int q = 0; q < 6; q++) { //second row theCards[q + 3].setX(3 * ((int) Card.WIDTH / 2) + q * Card.WIDTH + ((int) q / 2) * Card.WIDTH); //set the coords theCards[q + 3].setY(Card.HEIGHT); theCards[q + 3].flip(true); //face-down } for (int q = 0; q < 9; q++) { //third row theCards[q + 9].setX(Card.WIDTH + q * Card.WIDTH); //set the coords theCards[q + 9].setY(3 * ((int) Card.HEIGHT / 2)); theCards[q + 9].flip(true); //face-down } for (int q = 0; q < 10; q++) { //fourth row theCards[q + 18].setX(((int) Card.WIDTH / 2) + q * Card.WIDTH); //set the coords theCards[q + 18].setY(2 * Card.HEIGHT); theCards[q + 18].flip(false); //face-up } for (int q = 28; q < 51; q++) { //the deck theCards[q].setX(7 * ((int) Card.WIDTH / 2)); //same coords for all of them theCards[q].setY(13 * ((int) Card.HEIGHT / 4)); theCards[q].flip(true); //they're all face-down theCards[q].setVisible(false); //they're invisible } theCards[50].setVisible(true); //only the top one is visible (faster repaint) theCards[51].setX(13 * ((int) Card.WIDTH / 2)); //discard pile theCards[51].setY(13 * ((int) Card.HEIGHT / 4)); //set the coords theCards[51].flip(false); //face-up remCards = 23; //23 cards left in the deck cardsInPlay = 28; //all 28 cards are in play remPeaks = 3; //all three peaks are there streak = 0; //the streak is reset gameScore = 0; //the game score is reset disIndex = 51; //the discard pile index is back to 51 numGames++; //increment the number of games played sesGames++; //increment the number of session games repaint(); //repaint the board TriPeaks theFrame = (TriPeaks) SwingUtilities.windowForComponent(this); //get the frame that contains the board theFrame.updateStats(); //update the stats labels } public void reset() { //resets everything for (int q = 0; q < 52; q++) { //go through every card theCards[q].setVisible(false); //make all the cards invisible } disIndex = 51; //essentially the same thing as the default values for the fields score = 0; gameScore = 0; sesScore = 0; streak = 0; remCards = 0; cardsInPlay = 0; remPeaks = 3; numGames = 0; sesGames = 0; highScore = 0; lowScore = 0; highStreak = 0; status = ""; cheats = new boolean[cheats.length]; hasCheatedYet = false; repaint(); //repaint the board TriPeaks theFrame = (TriPeaks) SwingUtilities.windowForComponent(this); //get the frame theFrame.updateStats(); //update the stats labels } public int[] randomize() { //randomizes an array - we're working with a 52-element array, so it puts the numbers 0-51 in random order int[] retVal = new int[52]; //the array for the numbers boolean[] check = new boolean[52]; //the checking array - which numbers have been used int[] pass; //array to pick the random item from ArrayList<Integer> passList; //an ArrayList of Integers for the selection of possible values for (int q = 0; q < 52; q++) { //walk through the array, setting values for each of them passList = new ArrayList<Integer>(); //re-initialize the arraylist every time for (int r = 0; r < 52; r++) { //walk through the possible numbers if (!check[r]) passList.add(r); //if the number hasn't been used, add it to the ArrayList } pass = new int[passList.size()]; //create a new array to pass the elements into another method int w = 0; //index for (Iterator<Integer> it = passList.iterator(); it.hasNext(); ) pass[w++] = it.next().intValue(); //have an iterator get every element in the list and put it in the array retVal[q] = randItem(pass); //select a random item from the array and set the value check[retVal[q]] = true; //flag the value as used } return retVal; //return the randomized array } private int randItem(int[] list) { //selects a random item from an array if (list.length == 0) return -1; //if the array is empty, return a -1 (usually unfavorable) int dim = list.length; //upper bound of the random integer int randIndex = (int) (dim * Math.random()); //generate a random index for the element return list[randIndex]; //return the random value } public void mouseClicked(MouseEvent e) { //when the player clicks anywhere on the board int startX, startY, endX, endY; //placeholders for the bounds of the card for (int q = 51; q >= 0; q--) { //go through the cards in reverse order - the higher index-cards are on top if (theCards[q] == null) continue; //if the card hasn't been initialized, skip it if (!theCards[q].isVisible()) continue; //if the card is invisible, skip it if (((q < 28) || (q == 51)) && (theCards[q].isFacingDown())) continue; //if the card isn't part of the deck and is face-down, skip it if (q == disIndex) continue; //if the card is in the discard pile, skip it //all the skips make execution of the mouse-click faster startX = theCards[q].getX() - ((int) Card.WIDTH / 2); //left edge of the card startY = theCards[q].getY() - ((int) Card.HEIGHT / 2); //top edge of the card endX = theCards[q].getX() + ((int) Card.WIDTH / 2); //right edge of the card endY = theCards[q].getY() + ((int) Card.HEIGHT / 2); //bottom edge of the card if ((startX > e.getX()) || (endX < e.getX()) || (startY > e.getY()) || (endY < e.getY())) continue; //if the mouse was clicked outside the card, skip the rest boolean isAdjacent; //a value to check if the card is adjacent by value if (cheats[1]) { //if the second cheat is used, the value of the card won't be checked isAdjacent = true; //the card is adjacent automatically } else { //no cheat - check card isAdjacent = theCards[q].isAdjacentTo(theCards[disIndex]); //check if the card is adjacent by value } if ((q < 28) && isAdjacent) { //if the card isn't in the deck and is adjacent to the last discarded card theCards[q].setX(theCards[disIndex].getX()); //put the card in the discard pile theCards[q].setY(theCards[disIndex].getY()); //set the discard pile's card's coords theCards[disIndex].setVisible(false); //hide the previously discarded card - makes the repaint faster disIndex = q; //the card is now in the discard pile streak++; //increment the strea cardsInPlay--; //decrement the number of cards in play score += streak; //add the streak to the score gameScore += streak; //and to the current game's score sesScore += streak; //and to the session score if (streak > highStreak) highStreak = streak; //set the high streak if it's higher if (gameScore > highScore) highScore = gameScore; //set the high score if it's higher if (q < 3) { //if it was a peak remPeaks--; //there's one less peak score += 15; //add a 15-point bonus gameScore += 15; //and to the game score sesScore += 15; //and to the session score if (remPeaks == 0) { //if all the peaks are gone score += 15; //add another 15-point bonus (for a total of 30 bonus points) gameScore += 15; //and to the game score sesScore += 15; //and to the session score status = "You have Tri-Conquered! You get a bonus of $30"; //set the status message for (int w = 28; w < (remCards + 28); w++) { //the remaining deck theCards[w].setVisible(false); //hide the deck (so you can't take cards from the deck after you clear the board } } else status = "You have reached a peak! You get a bonus of $15"; //set the status message if (gameScore > highScore) highScore = gameScore; //set the high score if the score is higher break; //"consume" the mouse click - don't go through the rest of the cards } boolean noLeft, noRight; //check values for checking whether or not a card has a card to the left or right noLeft = noRight = false; //starts out as having both if ((q != 3) && (q != 9) && (q != 18) && (q != 5) && (q != 7) && (q != 12) && (q != 15)) { //if the card isn't a left end if (!theCards[q - 1].isVisible()) noLeft = true; //check if the left-adjacent card is visible } if ((q != 4) && (q != 6) && (q != 8) && (q != 17) && (q != 27) && (q != 11) && (q != 14)) { //if the card isn't a right end if (!theCards[q + 1].isVisible()) noRight = true; //check if the right-adjacent card is visible } //some of the cards in the third row are considered to be edge cards because not all pairs of adjacent cards in the third row uncover another card if ((!noLeft) && (!noRight)) break; //if both the left and right cards are present, don't do anything int offset = -1; //the "offset" is the difference in the indeces of the right card of the adjacent pair and the card that pair will uncover if ((q >= 18) && (q <= 27)) { //4th row offset = 10; } else if ((q >= 9) && (q <= 11)) { //first 3 of 3rd row offset = 7; } else if ((q >= 12) && (q <= 14)) { //second 3 of third row offset = 8; } else if ((q >= 15) && (q <= 17)) { //last 3 of third row offset = 9; } else if ((q >= 3) && (q <= 4)) { //first 2 of second row offset = 4; } else if ((q >= 5) && (q <= 6)) { //second 2 of second row offset = 5; } else if ((q >= 7) && (q <= 8)) { //last 2 of second row offset = 6; } //the first row isn't here because the peaks are special and were already taken care of above if (offset == -1) break; //if the offset didn't get set, don't do anything (offset should get set, but just in case) if (noLeft) theCards[q - offset].flip(); //if the left card is missing, use the current card as the right one if (noRight) theCards[q - offset + 1].flip(); //if the right card is missing, use the missing card as the right one } else if ((q >= 28) && (q < 51)) { //in the deck theCards[q].setX(theCards[disIndex].getX()); //move the card to the deck theCards[q].setY(theCards[disIndex].getY()); //set the deck's coordinates theCards[disIndex].setVisible(false); //hide the previously discarded card (for faster repaint) theCards[q].flip(); //flip the deck card if (q != 28) theCards[q - 1].setVisible(true); //show the next deck card if it's not the last deck card disIndex = q; //set the index of the dicard pile streak = 0; //reset the streak if (!cheats[2]) { //if the thrid cheat isn't on (no penalty cheat) score -= 5; //5-point penalty gameScore -= 5; //to the game score sesScore -= 5; //and the session score } if (gameScore < lowScore) lowScore = gameScore; //set the low score if score is lower remCards--; //decrement the number of cards in the deck } break; //"consume" the click - don't go through the rest of the cards } repaint(); //repaint the board TriPeaks theFrame = (TriPeaks) SwingUtilities.windowForComponent(this); //get the containing frame theFrame.updateStats(); //update the stats labels } public int getPenalty() { //return the penalty if (cheats[2]) return 0; //if the penalty cheat is on, there is no penalty if ((cardsInPlay != 0) && (remCards != 0)) return (cardsInPlay * 5); //if there are cards in the deck AND in play, the penalty is $5 for every card removed else return 0; //otherwise the penalty is 0 } public void doPenalty(int penalty) { //perform the penalty - penalty doesn't affect the low score score -= penalty; //subtract the penalty sesScore -= penalty; //from the session score gameScore -= penalty; //and from the game score } public String getCardFront() { //returns the current front style return frontFolder; } public String getCardBack() { //returns the current back style return backStyle; } public Color getBackColor() { //returns the background color return backColor; } public int getScore() { //returns the player's overall score return score; } public int getGameScore() { //returns the current game score return gameScore; } public int getStreak() { //returns the current sreak return streak; } public int getNumGames() { //returns the number of games played return numGames; } public int getHighScore() { //returns the high score return highScore; } public int getLowScore() { //returns the low score return lowScore; } public int getHighStreak() { //returns the longest streak return highStreak; } public int getSesScore() { //returns the session score return sesScore; } public int getSesGames() { //returns the number of session games return sesGames; } public Color getFontColor() { return fontColor; } public Font getTextFont() { return textFont; } public int[] getAllStats() { //returns all the stats in an array int[] retVal = {getScore(), getGameScore(), getSesScore(), getStreak(), getNumGames(), getSesGames(), getHighScore(), getLowScore(), getHighStreak()}; //the array of stats return retVal; } public boolean isCheating() { //check if the player is currently cheating for (int q = 0; q < cheats.length; q++) { //go through all the cheats if (cheats[q]) return true; //return true if any cheat is on } return false; //no cheat was found - return false } public boolean hasCheated() { //checks if player has ever cheated return hasCheatedYet; } public boolean[] getCheats() { //returns all the cheats return cheats; //return the cheats array } public void setStats(int[] stats) { //sets all the stats based on the array values score = stats[0]; //the programmer knows the order of the stats to be passed into this method: highScore = stats[1]; //overall score, high score, low score, number of games, and longest streak lowScore = stats[2]; numGames = stats[3]; highStreak = stats[4]; } public void setCardFront(String front) { //sets the front style frontFolder = front; } public void setCardBack(String back) { //sets the back style backStyle = back; } public void setBackColor(Color newColor) { //sets the background color backColor = newColor; } public void setCheat(int cheatNum, boolean newState) { //set a cheat with the given index if (cheatNum >= cheats.length) return; //if the index is out of bounds if (newState) hasCheatedYet = true; //if the cheat is turned on, set the "has cheated" flag cheats[cheatNum] = newState; //set the cheat } public void setCheats(boolean[] newCheats) { //set all the cheats in a given array for (int q = 0; q < cheats.length; q++) setCheat(q, newCheats[q]); //go through the array and set the cheats } public void setCheated(boolean hasCheatedYet) { //set the cheated status for the player. this.hasCheatedYet = hasCheatedYet; } public void setDefaults() { frontFolder = "Default"; backStyle = "Default"; backColor = (Color.GREEN).darker().darker(); fontColor = Color.WHITE; textFont = new Font("Serif", Font.BOLD, 14); } public void setFontColor(Color newColor) { fontColor = newColor; } public void setTextFont(Font newFont) { textFont = newFont; } //not used, but necessary to implement MouseListener public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { } public void mousePressed(MouseEvent e) { } public void mouseReleased(MouseEvent e) { } } //end class CardPanel class Card { //defines a card public static final int CLUBS = 0; //the 4 suits public static final int HEARTS = 1; public static final int DIAMONDS = 2; public static final int SPADES = 3; public static final int HEIGHT = 86; //the height and width of the card public static final int WIDTH = 64; private boolean isFaceDown; //is it facing down private boolean visible; //is it visible private int value; //value (0-12) - 0=Ace, 10=Jack, 11=Queen, 12=King private int suit; //suit of the card, as defined above private int xCoord; //coordinates of the card (center, not top-left) private int yCoord; public Card() { //must initialize manually, later on } public Card(int value, int suit, boolean isFaceDown, boolean visible, int x, int y) { //specify all the fields at once this.value = value; //set the value if ((suit == CLUBS) || (suit == HEARTS) || (suit == DIAMONDS) || (suit == SPADES)) //check if it's a valid suit this.suit = suit; //set the suit this.isFaceDown = isFaceDown; //set the face-down flag this.visible = visible; //set the visible flag xCoord = x; //set the coordinates yCoord = y; } //accessor methods for the class public int getValue() { return value; } public int getSuit() { return suit; } public boolean isFacingDown() { return isFaceDown; } public int getX() { return xCoord; } public int getY() { return yCoord; } public boolean isVisible() { return visible; } //mutator methods public void setValue(int newVal) { //sets the value of the card if ((newVal >= 0) && (newVal < 13)) //checks if it's a valid value value = newVal; //set the value } public void setSuit(int newSuit) { //sets the suit if ((newSuit == CLUBS) || (newSuit == HEARTS) || (newSuit == DIAMONDS) || (newSuit == SPADES)) suit = newSuit; } public void flip() { isFaceDown = !isFaceDown; } public void flip(boolean isFaceDown) { this.isFaceDown = isFaceDown; } public static String suitAsString(int aSuit) { //converts the suit to a string switch(aSuit) { case CLUBS: return "clubs"; case HEARTS: return "hearts"; case DIAMONDS: return "diamonds"; case SPADES: return "spades"; default: System.out.println("Invalid Suit!!!"); return "Invalid Suit"; } } public String suitAsString() { //returns the string representation of the current suit return suitAsString(suit); } public void setX(int newX) { xCoord = newX; } public void setY(int newY) { yCoord = newY; } public void setVisible(boolean newVis) { visible = newVis; } public String toString() { //converts the card to a string representation String val; switch(value) { case 12: val = "king"; break; case 11: val = "queen"; break; case 10: val = "jack"; break; default: val = value + ""; } String finVal = val + " of " + suitAsString() + ": " + ((isFaceDown) ? "facing down" : "facing up") + ", " + ((visible) ? "visible" : "invisible") + " :: (" + xCoord + ", " + yCoord + ")"; return finVal; } public boolean isAdjacentTo(Card that) { //checks if the value of the card is 1 off from the given card int tempThis = value; //this card's value int tempThat = that.getValue(); //the given card's value if (((tempThis + 1) % 13 == tempThat) || ((tempThat + 1) % 13 == tempThis)) return true; //check if it's one away else return false; } public boolean equals(Card that) { //checks if the cards are equivalent if ((that.getValue() == value) && (that.getSuit() == suit)) return true; else return false; } } //end class Card class Encryptor { //a class used to encrypt and decrypt stuff Cipher encipher; //two cipher objects - one for encrypting and one for decrypting Cipher decipher; byte[] salt = {(byte) 0xA9, (byte) 0x9B, (byte) 0xC8, (byte) 0x32, (byte) 0x56, (byte) 0x35, (byte) 0xE3, (byte) 0x03}; //salt for the encryption (more secure) int iterCt = 19; //number of iterations for encryption public Encryptor(String passPhrase) { //create the encryptor object try { //lots of things can go wrong KeySpec keySpec = new PBEKeySpec(passPhrase.toCharArray(), salt, iterCt); //create the PBE (Password-based ecryption) key specification SecretKey key = SecretKeyFactory.getInstance("PBEWithMD5AndDES").generateSecret(keySpec); //generate a secret encryption key (Password-Based Encryption with Message Digest 5 and Data Encryption Standard) encipher = Cipher.getInstance(key.getAlgorithm()); //create the Cipher objects decipher = Cipher.getInstance(key.getAlgorithm()); AlgorithmParameterSpec pSpec = new PBEParameterSpec(salt, iterCt); //create the encryption algorithm decipher.init(Cipher.DECRYPT_MODE, key, pSpec); //initialize the two Ciphers, with the same keya and algorithm, but different modes encipher.init(Cipher.ENCRYPT_MODE, key, pSpec); } catch (InvalidAlgorithmParameterException eIAP) { } //catch all the exceptions that can get thrown. catch (InvalidKeySpecException eIKS) { } catch (NoSuchPaddingException eNSP) { } catch (NoSuchAlgorithmException eNSA) { } catch (InvalidKeyException eIK) { } } public String encrypt(String in) { //encrypts a stirng try { //lots of things that can go wrong byte[] utf8 = in.getBytes("UTF8"); //Convert the string to UTF-8 bytecodes byte[] enBytes = encipher.doFinal(utf8); //have the Cipher encrypt the bytes String out = new String(Base64Coder.encode(enBytes)); //Create a string from the bytes using Base64 encoding return out; //return the encrypted string } catch (BadPaddingException eBP) { } //catch all the exceptions catch (IllegalBlockSizeException eIBS) { } catch (UnsupportedEncodingException eUE) { } catch (IOException eIO) { } return null; //return null if there was an exception } public String decrypt(String in) { //decrypts a string try { //lots of things that can go wrong byte[] deBytes = Base64Coder.decode(in); //get the encrypted bytes by decoding the Base64-encoded text byte[] utf8 = decipher.doFinal(deBytes); //use the Cipher to decrypt the bytes into UTF-8 bytecodes String out = new String (utf8, "UTF8"); //create a new string from those bytes return out; //return the decrypted string } catch (BadPaddingException eBP) { } //catch all the exceptions catch (IllegalBlockSizeException eIBS) { } catch (UnsupportedEncodingException eUE) { } catch (IOException eIO) { } return null; //return null if there was an exception } } //end Encryptor class /*Start Base64 encoding and decoding code. ***NOTE*** This is NOT my code. This code was written by Christian d'Heureuse to provide a more standard base64 coder that's fast and efficient. As such, I won't provide comments for that code. Java does NOT provide a Base64 encoder/decoder as part of the API.*/ class Base64Coder { private static char[] map1 = new char[64]; static { int i=0; for (char c='A'; c<='Z'; c++) map1[i++] = c; for (char c='a'; c<='z'; c++) map1[i++] = c; for (char c='0'; c<='9'; c++) map1[i++] = c; map1[i++] = '+'; map1[i++] = '/'; } private static byte[] map2 = new byte[128]; static { for (int i=0; i<map2.length; i++) map2[i] = -1; for (int i=0; i<64; i++) map2[map1[i]] = (byte)i; } public static String encodeString (String s) { return new String(encode(s.getBytes())); } public static char[] encode (byte[] in) { return encode(in,in.length); } public static char[] encode (byte[] in, int iLen) { int oDataLen = (iLen*4+2)/3; int oLen = ((iLen+2)/3)*4; char[] out = new char[oLen]; int ip = 0; int op = 0; while (ip < iLen) { int i0 = in[ip++] & 0xff; int i1 = ip < iLen ? in[ip++] & 0xff : 0; int i2 = ip < iLen ? in[ip++] & 0xff : 0; int o0 = i0 >>> 2; int o1 = ((i0 & 3) << 4) | (i1 >>> 4); int o2 = ((i1 & 0xf) << 2) | (i2 >>> 6); int o3 = i2 & 0x3F; out[op++] = map1[o0]; out[op++] = map1[o1]; out[op] = op < oDataLen ? map1[o2] : '='; op++; out[op] = op < oDataLen ? map1[o3] : '='; op++; } return out; } public static String decodeString (String s) { return new String(decode(s)); } public static byte[] decode (String s) { return decode(s.toCharArray()); } public static byte[] decode (char[] in) { int iLen = in.length; if (iLen%4 != 0) throw new IllegalArgumentException ("Length of Base64 encoded input string is not a multiple of 4."); while (iLen > 0 && in[iLen-1] == '=') iLen--; int oLen = (iLen*3) / 4; byte[] out = new byte[oLen]; int ip = 0; int op = 0; while (ip < iLen) { int i0 = in[ip++]; int i1 = in[ip++]; int i2 = ip < iLen ? in[ip++] : 'A'; int i3 = ip < iLen ? in[ip++] : 'A'; if (i0 > 127 || i1 > 127 || i2 > 127 || i3 > 127) throw new IllegalArgumentException ("Illegal character in Base64 encoded data."); int b0 = map2[i0]; int b1 = map2[i1]; int b2 = map2[i2]; int b3 = map2[i3]; if (b0 < 0 || b1 < 0 || b2 < 0 || b3 < 0) throw new IllegalArgumentException ("Illegal character in Base64 encoded data."); int o0 = ( b0 <<2) | (b1>>>4); int o1 = ((b1 & 0xf)<<4) | (b2>>>2); int o2 = ((b2 & 3)<<6) | b3; out[op++] = (byte)o0; if (op<oLen) out[op++] = (byte)o1; if (op<oLen) out[op++] = (byte)o2; } return out; } private Base64Coder() { } } //end Base64Coder class class NewPlayerException extends Exception { public NewPlayerException() { super(); } public NewPlayerException(String msg) { super(msg); } } class HighScoreModel extends AbstractTableModel { public static final String[] columnNames = {"Player Name", "Score", "Average", "Most Won", "Most Lost", "Longest Streak", "# of games", "Has Cheated"}; public static Object[][] defaultPlrs = new Object[10][columnNames.length]; static { defaultPlrs[0][0] = "The Game"; defaultPlrs[0][1] = new Integer(50000); defaultPlrs[0][2] = new Integer(150); defaultPlrs[0][3] = new Integer(-90); defaultPlrs[0][4] = new Integer(3500); defaultPlrs[0][5] = new Integer(17); defaultPlrs[0][6] = new Boolean(false); defaultPlrs[1][0] = "Bob"; defaultPlrs[1][1] = new Integer(26392); defaultPlrs[1][2] = new Integer(160); defaultPlrs[1][3] = new Integer(-70); defaultPlrs[1][4] = new Integer(2501); defaultPlrs[1][5] = new Integer(18); defaultPlrs[1][6] = new Boolean(false); defaultPlrs[2][0] = "Linus T."; defaultPlrs[2][1] = new Integer(10000); defaultPlrs[2][2] = new Integer(157); defaultPlrs[2][3] = new Integer(-77); defaultPlrs[2][4] = new Integer(721); defaultPlrs[2][5] = new Integer(15); defaultPlrs[2][6] = new Boolean(false); defaultPlrs[3][0] = "Who am I"; defaultPlrs[3][1] = new Integer(9876); defaultPlrs[3][2] = new Integer(200); defaultPlrs[3][3] = new Integer(-50); defaultPlrs[3][4] = new Integer(607); defaultPlrs[3][5] = new Integer(20); defaultPlrs[3][6] = new Boolean(false); defaultPlrs[4][0] = "Random"; defaultPlrs[4][1] = new Integer(7694); defaultPlrs[4][2] = new Integer(404); defaultPlrs[4][3] = new Integer(0); defaultPlrs[4][4] = new Integer(20); defaultPlrs[4][5] = new Integer(24); defaultPlrs[4][6] = new Boolean(true); defaultPlrs[5][0] = "The CardMan"; defaultPlrs[5][1] = new Integer(5000); defaultPlrs[5][2] = new Integer(137); defaultPlrs[5][3] = new Integer(-61); defaultPlrs[5][4] = new Integer(544); defaultPlrs[5][5] = new Integer(13); defaultPlrs[5][6] = new Boolean(false); defaultPlrs[6][0] = "The Sun"; defaultPlrs[6][1] = new Integer(3000); defaultPlrs[6][2] = new Integer(128); defaultPlrs[6][3] = new Integer(-40); defaultPlrs[6][4] = new Integer(321); defaultPlrs[6][5] = new Integer(16); defaultPlrs[6][6] = new Boolean(false); defaultPlrs[7][0] = "CPU"; defaultPlrs[7][1] = new Integer(1732); defaultPlrs[7][2] = new Integer(100); defaultPlrs[7][3] = new Integer(-79); defaultPlrs[7][4] = new Integer(109); defaultPlrs[7][5] = new Integer(12); defaultPlrs[7][6] = new Boolean(false); defaultPlrs[8][0] = "Your Creator"; defaultPlrs[8][1] = new Integer(1000); defaultPlrs[8][2] = new Integer(99); defaultPlrs[8][3] = new Integer(-96); defaultPlrs[8][4] = new Integer(80); defaultPlrs[8][5] = new Integer(9); defaultPlrs[8][6] = new Boolean(false); defaultPlrs[9][0] = "Bright One"; defaultPlrs[9][1] = new Integer(500); defaultPlrs[9][2] = new Integer(73); defaultPlrs[9][3] = new Integer(-109); defaultPlrs[9][4] = new Integer(25); defaultPlrs[9][5] = new Integer(10); defaultPlrs[9][6] = new Boolean(false); } private Object[][] data; public int getColumnCount() { return columnNames.length; } public int getRowCount() { return data.length; } public String getColumnName(int c) { return columnNames[c]; } public Object getValueAt(int r, int c) { return data[r][c]; } public Class getColumnClass(int c) { return getValueAt(0, c).getClass(); } public boolean readAndSetData() { File scoresDir = new File(TriPeaks.scoresDir); if (!scoresDir.isDirectory()) return false; File[] scoreFiles = scoresDir.listFiles(); BufferedReader in; ArrayList<ArrayList> scoreLists = new ArrayList<ArrayList>(); ArrayList<Object> plrScores; String fileName, deced, line, name; int dotIndex; Encryptor dec; for (int q = 0; q < scoreFiles.length; q++) { plrScores = new ArrayList<Object>(); fileName = scoreFiles[q].getName(); if (!fileName.endsWith(".txt")) continue; dotIndex = fileName.indexOf('.'); dec = new Encryptor(TriPeaks.backward(fileName.substring(0, dotIndex))); plrScores.add(TriPeaks.rot13(fileName.substring(0, dotIndex))); try { in = new BufferedReader(new FileReader(scoreFiles[q])); for (int w = 0; w < CardPanel.NSTATS; w++) { if ((line = in.readLine()) == null) break; deced = dec.decrypt(line); plrScores.add(new Integer(deced)); } for (int w = 0; w < CardPanel.NCHEATS; w++) { if ((line = in.readLine()) == null) break; } if ((line = in.readLine()) == null) continue; deced = dec.decrypt(line); plrScores.add(new Boolean(deced)); scoreLists.add(plrScores); } catch (FileNotFoundException eFNF) { //Should never happen b/c we are opening files listed in a folder... System.out.println(eFNF.getMessage()); } catch(IOException eIO) { System.out.println("Error reading from file -OR- closing file"); } } int remDefPlrs = 10 - scoreLists.size(); ArrayList<Object> tempList; for (int q = 0; q < remDefPlrs; q++) { tempList = new ArrayList<Object>(); for (int w = 0; w < getColumnCount(); w++) tempList.add(defaultPlrs[q][w]); scoreLists.add(tempList); } data = new Object[scoreLists.size()][getColumnCount()]; int q = 0; for (Iterator<ArrayList> it1 = scoreLists.iterator(); it1.hasNext(); q++) { ArrayList score = it1.next(); data[q][0] = TriPeaks.capitalize((String) score.get(0)); data[q][1] = score.get(1); if (((Integer) score.get(4)).intValue() != 0) data[q][2] = new Double((double) ((Integer) score.get(1)).intValue() / ((Integer) score.get(4)).intValue()); else data[q][2] = new Double(0.0); data[q][3] = score.get(2); data[q][4] = score.get(3); data[q][5] = score.get(5); data[q][6] = score.get(4); data[q][7] = score.get(6); } return true; } } class CurrencyRenderer extends DefaultTableCellRenderer { public CurrencyRenderer() { super(); } public void setValue(Object value) { if (value == null) setText(""); DecimalFormat format = null; double num = 0.0; if (value.getClass() == Integer.class) { format = new DecimalFormat("$###,###"); num = ((Integer) value).intValue(); } else if (value.getClass() == Double.class) { format = new DecimalFormat("$###,##0.00"); num = ((Double) value).doubleValue(); } else { setText(""); return; } if (format == null) { setText(""); return; } setText(format.format(num)); } }