Newer
Older
moobreeder / MooBreeder.java
/**
 * Moobreeder is a simple cow breeding game to illustrate the principles of
 * selective breeding to KS3 pupils.
 *
 * (c) Daniel Vedder 2018, Amano Christian School
 * Licensed under the terms of the MIT license.
 * https://git.synoikos.de/daniel/moobreeder
 */

/*
 * This is the main class with the GUI.
 */

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.filechooser.*;
import java.util.HashMap;
import java.io.File;

public class MooBreeder extends JFrame implements MouseListener
{
	private static final String VERSION = "1.0";
	private String goal;
	
	private JMenuBar menubar;
	private JMenu fileMenu, helpMenu;
	private JMenuItem select, save, load, reset, quit, help, about;
	private JPanel breedPanel;
	private JScrollPane herdScroller;
	private Box herdBox, motherBox, fatherBox, calf1Box, calf2Box;
	private JButton breedButton;
	private JFileChooser fileChooser;
	private HelpWindow helpWindow;

	public static void main(String[] args)
	{
		Herd.getInstance();
		MooBreeder mb = new MooBreeder();
	}
	
	public MooBreeder()
	{
		this.setTitle("MooBreeder");
		this.setSize(900, 450);
		this.setDefaultCloseOperation(EXIT_ON_CLOSE);
		goal = "dairy";
		helpWindow = new HelpWindow();
		fileChooser = new JFileChooser(System.getProperty("user.dir"));
		FileFilter datFilter = new FileNameExtensionFilter("MooBreeder (.dat) files", "dat");
		fileChooser.setFileFilter(datFilter);
		this.createMenu();
		this.createLayout();
		this.setVisible(true);
		chooseGoal();
	}

	private void createMenu()
	{
		menubar = new JMenuBar();
		fileMenu = new JMenu("File");
		helpMenu = new JMenu("Help");
		select = new JMenuItem("Select a goal");
		save = new JMenuItem("Save");
		load = new JMenuItem("Load");
		reset = new JMenuItem("Reset");
		quit = new JMenuItem("Quit");
		help = new JMenuItem("Help");
		about = new JMenuItem("About");
		select.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				chooseGoal();
			}
		});
		save.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				int saveConfirm = fileChooser.showSaveDialog(null);
				if (saveConfirm == JFileChooser.APPROVE_OPTION) {
					File saveFile = fileChooser.getSelectedFile();
					if (!saveFile.getName().endsWith(".dat"))
						saveFile = new File(saveFile.getAbsolutePath()+".dat");
					Herd.saveHerd(saveFile.getAbsolutePath());
					JOptionPane.showMessageDialog(null, "Current herd saved to "+saveFile.getName(),
												  "Game saved", JOptionPane.INFORMATION_MESSAGE);
				}
			}
		});
		save.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, ActionEvent.ALT_MASK));
		load.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				int loadConfirm = fileChooser.showOpenDialog(null);
				if (loadConfirm == JFileChooser.APPROVE_OPTION) {
					File loadFile = fileChooser.getSelectedFile();
					if (!loadFile.exists() && !loadFile.getName().endsWith(".dat"))
						loadFile = new File(loadFile.getAbsolutePath()+".dat");
					int userConfirm = JOptionPane.showConfirmDialog(null, "Load herd from "+loadFile.getName()+"?",
																	"Confirm", JOptionPane.OK_CANCEL_OPTION,
																	JOptionPane.QUESTION_MESSAGE);
					if (userConfirm == JOptionPane.OK_OPTION && loadFile.exists()) {
						Herd.loadHerd(loadFile.getAbsolutePath());
						updateGUI();
					}
				}
			}
		});
		load.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, ActionEvent.ALT_MASK));
		reset.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				int restart = JOptionPane.showConfirmDialog(null, "Reset the game?", "Reset?",
															JOptionPane.OK_CANCEL_OPTION,
															JOptionPane.QUESTION_MESSAGE);
				if (restart == JOptionPane.OK_OPTION) resetGame();
			}
		});
		reset.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, ActionEvent.ALT_MASK));
		help.addActionListener(new ActionListener() {
          public void actionPerformed(ActionEvent e) {
              helpWindow.setVisible(true);
          }
        });
		help.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_H, ActionEvent.ALT_MASK));
		quit.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				int confirm = JOptionPane.showConfirmDialog(null, "Quit MooBreeder?", "Quit?",
															JOptionPane.YES_NO_OPTION,
															JOptionPane.WARNING_MESSAGE);
				if(confirm == JOptionPane.YES_OPTION) System.exit(0);
			}
		});
		quit.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, ActionEvent.ALT_MASK));
		about.addActionListener(new ActionListener() {
		   public void actionPerformed(ActionEvent e) {
				JOptionPane.showMessageDialog(null, "MooBreeder "+VERSION+
											  "\n- an educational breeding game -"+
											  "\n\n(c) 2018 Daniel Vedder"+
											  ", Amano Christian School"+
											  "\nhttps://git.synoikos.de/daniel/moobreeder",
											  "About", JOptionPane.INFORMATION_MESSAGE);
			}
		});
		fileMenu.add(select);
		fileMenu.add(save);
		fileMenu.add(new JSeparator());
		fileMenu.add(load);
		fileMenu.add(reset);
		fileMenu.add(new JSeparator());
		fileMenu.add(quit);
		helpMenu.add(help);
		helpMenu.add(about);
		menubar.add(fileMenu);
		menubar.add(helpMenu);
		this.setJMenuBar(menubar);
	}

	private void createLayout()
	{
		this.setLayout(new BoxLayout(this.getContentPane(), BoxLayout.X_AXIS));
		//Create the left half of the window with the herd box
		JPanel herdBorderPanel = new JPanel(new BorderLayout());
		herdBox = new Box(BoxLayout.Y_AXIS);
		herdScroller = new JScrollPane(herdBox, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
									   ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
		herdScroller.setWheelScrollingEnabled(true);
		herdBorderPanel.add(herdScroller, BorderLayout.CENTER);
		//Take care of the layout
		herdBorderPanel.add(Box.createVerticalStrut(25), BorderLayout.NORTH);
		herdBorderPanel.add(Box.createVerticalStrut(25), BorderLayout.SOUTH);
		herdBorderPanel.add(Box.createHorizontalStrut(25), BorderLayout.EAST);
		herdBorderPanel.add(Box.createHorizontalStrut(25), BorderLayout.WEST);
		this.add(herdBorderPanel);
		//Create the right half of the window with the breeding interface
		JPanel breedBorderPanel = new JPanel(new BorderLayout());
		Box breedBox = new Box(BoxLayout.Y_AXIS);
		breedPanel = new JPanel(new GridLayout(2,2));
		fatherBox = new Box(BoxLayout.Y_AXIS);
		motherBox = new Box(BoxLayout.Y_AXIS);
		calf1Box = new Box(BoxLayout.Y_AXIS);
		calf2Box = new Box(BoxLayout.Y_AXIS);
		breedPanel.add(fatherBox);
		breedPanel.add(motherBox);
		breedPanel.add(calf1Box);
		breedPanel.add(calf2Box);
		breedBox.add(breedPanel);
		//Add the breed button
		breedButton = new JButton("Breed");
		breedButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				breedAction();
			}
		});
		breedBox.add(Box.createVerticalStrut(10));
		breedBox.add(breedButton);
		//Take care of the layout
		breedBorderPanel.add(breedBox, BorderLayout.CENTER);
		breedBorderPanel.add(Box.createVerticalStrut(25), BorderLayout.NORTH);
		breedBorderPanel.add(Box.createHorizontalStrut(25), BorderLayout.EAST);
		breedBorderPanel.add(Box.createVerticalStrut(25), BorderLayout.SOUTH);
		this.add(breedBorderPanel);
		updateGUI();
	}

	private void updateGUI()
	{
		updateHerdBox();
		updateParentBox();
		updateOffspringBox();
		this.validate();
		this.repaint();
	}

	private void updateHerdBox()
	{
		//TODO It should be possible to choose just bulls or cows
		herdBox.removeAll();
		for (int c = 0; c < Herd.getInstance().size(); c++) {
			Cow cow = Herd.getInstance().getNthCow(c);
			if (Herd.getInstance().isParent(cow.getID())) continue;
			Box cowBox = getCowBox(cow);
			herdBox.add(cowBox);
			herdBox.add(Box.createVerticalStrut(1));
		}		herdScroller.validate();
		herdBox.validate();
		herdScroller.validate();
	}

	private void updateParentBox()
	{
		fatherBox.removeAll();
		motherBox.removeAll();
		//Label boxes
		JLabel dl = new JLabel("Father:");
		JLabel ml = new JLabel("Mother:");
		fatherBox.add(dl);
		motherBox.add(ml);
		//Include information boxes
		Cow[] parents = Herd.getInstance().getParents();
		Box db = getCowBox(parents[0]);
		fatherBox.add(Box.createVerticalStrut(10));
		fatherBox.add(db);
		Box mb = getCowBox(parents[1]);
		motherBox.add(Box.createVerticalStrut(10));
		motherBox.add(mb);
		fatherBox.validate();
		motherBox.validate();
	}


	private void updateOffspringBox()
	{
		calf1Box.removeAll();
		calf2Box.removeAll();
		JLabel l1 = new JLabel("Calf 1:");
		JLabel l2 = new JLabel("Calf 2:");
		calf1Box.add(l1);
		calf2Box.add(l2);
		//Include information boxes
		Cow[] calves = Herd.getInstance().getCalves();
		Box c1b = getCowBox(calves[0]);
		calf1Box.add(Box.createVerticalStrut(10));
		calf1Box.add(c1b);
		Box c2b = getCowBox(calves[1]);
		calf2Box.add(Box.createVerticalStrut(10));
		calf2Box.add(c2b);
		calf1Box.validate();
		calf2Box.validate();
	}

	private Box getCowBox(Cow c)
	{
		Box cowBox = new Box(BoxLayout.Y_AXIS);
		cowBox.setBorder(new BevelBorder(BevelBorder.RAISED));
		cowBox.setMaximumSize(new Dimension(300, 110));
		cowBox.setMinimumSize(new Dimension(300, 110));
		cowBox.add(Box.createVerticalStrut(7));
		//If there is no cow here, display an empty panel
		if (c == null) {
			cowBox.add(new JLabel(" <empty> ", JLabel.CENTER));
			cowBox.add(Box.createVerticalStrut(9));
			return cowBox;
		}
		//Genealogy labels
		cowBox.setName(String.valueOf(c.getID()));
		JLabel name = new JLabel(c.getName());
		name.setFont(new Font(Font.SERIF, Font.BOLD, 14));
		JLabel genealogy = new JLabel("Generation "+c.getGeneration()+
									  ",  Father: "+c.getParents()[0]+
									  ",  Mother: "+c.getParents()[1]);
		cowBox.add(name);
		cowBox.add(Box.createVerticalStrut(5));
		cowBox.add(genealogy);
		cowBox.add(Box.createVerticalStrut(3));
		//Trait and rating labels
		HashMap<String, Integer> t = c.getTraits();
		JLabel tl1 = new JLabel("Milk: "+t.get("milk")+",  Meat: "+t.get("meat"));
		JLabel tl2 = new JLabel("Strength: "+t.get("strength")+",  Aggression: "+
								t.get("aggression"));
		cowBox.add(tl1);
		cowBox.add(tl2);
		cowBox.add(Box.createVerticalStrut(5));
		JLabel rl = new JLabel(c.rateCow(goal));
		rl.setFont(new Font(Font.SANS_SERIF, Font.ITALIC, 12));
		cowBox.add(rl);
		//Final stuff
		cowBox.add(Box.createVerticalStrut(5));
		cowBox.addMouseListener(this);
		return cowBox;
	}

	private void breedAction()
	{
		String possible = Herd.getInstance().checkBreeding();
		if (possible == "OK") {
			Herd.getInstance().breed();
			updateGUI();
			testWin();
		}
		else {
			JOptionPane.showMessageDialog(null, possible, "Breeding failed",
										  JOptionPane.WARNING_MESSAGE);
		}
	}

	private void testWin()
	{
		Cow[] calves = Herd.getInstance().getCalves();
		if ((calves[0] != null && calves[0].rateCow(goal).indexOf("excellent") != -1) ||
			(calves[1] != null && calves[1].rateCow(goal).indexOf("excellent") != -1))
			JOptionPane.showMessageDialog(null, "You have bred an excellent cow for "+
										  goal+" in "+Cow.getMaxGen()+" generations.",
										  "Congratulations!", JOptionPane.INFORMATION_MESSAGE);
	}

	private void chooseGoal()
	{
		String[] possible = {"dairy", "beef", "plowing"};
		Object newGoal = JOptionPane.showInputDialog(null, "What do you want to breed for?",
													 "Select a goal", JOptionPane.QUESTION_MESSAGE,
													 null, possible, possible[0]);
		if (newGoal != null) goal = (String) newGoal;
		updateGUI();
	}

	private void resetGame()
	{
		Herd.getInstance().resetHerd();
		updateGUI();
		chooseGoal();
	}

	/*
	 * MouseListener methods
	 */

	@Override
	public void mouseClicked(MouseEvent me)
	{
		int selectionID = Integer.parseInt(me.getComponent().getName());
		//Respond to the mouse click with an action or a context menu
		if (me.getButton() == MouseEvent.BUTTON3) {
			mouseSideAction(selectionID, me);
		}
		else if (me.getButton() == MouseEvent.BUTTON1 &&
				 me.getClickCount() == 2) {
			mouseMainAction(selectionID);
		}
	}

	/*
	 * The action launched by a double left click or the first context menu:
	 * set/remove a parent or add a calf to the herd.
	 */
	private void mouseMainAction(int selection)
	{
		if (Herd.getInstance().isParent(selection))
			Herd.getInstance().removeParent(selection);
		else if (Herd.getInstance().isCalf(selection))
			Herd.getInstance().keepCalf(selection);
		else Herd.getInstance().setParent(selection);
		updateGUI();
	}

	/*
	 * The context menu launched by a right click. Offers the main mouse action
	 * (see above), or the option to discard/delete a calf or adult animal.
	 */
	private void mouseSideAction(int selection, MouseEvent me)
	{
		//Initialise menu
		JPopupMenu contextMenu = new JPopupMenu();
		JMenuItem item1 = new JMenuItem();
		JMenuItem item2 = new JMenuItem();
		//Change item labels and actions depending on context
		item1.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				mouseMainAction(selection); //always the same
			}
		});
		if (Herd.getInstance().isParent(selection)) {
			item1.setText("Remove as parent");
		}
		else if (Herd.getInstance().isCalf(selection)) {
			item1.setText("Add calf to herd");
			item2.setText("Discard calf");
			item2.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					Herd.getInstance().discardCalf(selection);
					updateGUI();
				}
			});
		}
		else {
			item1.setText("Select for breeding");
			item2.setText("Remove from herd");
			item2.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					String question = "Remove "+Herd.getInstance().
						getCow(selection).getName()+" from herd?";
					int confirm = JOptionPane.showConfirmDialog(null, question, "Remove animal?",
																JOptionPane.YES_NO_OPTION,
																JOptionPane.QUESTION_MESSAGE);
					if(confirm == JOptionPane.YES_OPTION) {
						Herd.getInstance().removeCow(selection);
						updateGUI();
					}
				}
			});
		}
		//Add menu items and show menu
		contextMenu.add(item1);
		if (item2.getText() != "") contextMenu.add(item2);
		contextMenu.show(me.getComponent(), me.getX(), me.getY());
	}

	@Override
	public void mouseEntered(MouseEvent me) {}

	@Override
	public void mouseExited(MouseEvent me) {}

	@Override
	public void mousePressed(MouseEvent me) {
		Box tile = (Box) me.getComponent();
		tile.setBorder(new BevelBorder(BevelBorder.LOWERED));
	}

	@Override
	public void mouseReleased(MouseEvent me) {
		Box tile = (Box) me.getComponent();
		tile.setBorder(new BevelBorder(BevelBorder.RAISED));
	}
	
}