import java.applet.Applet; import java.awt.*; import java.awt.event.*; import java.util.*; public class PacmanApplet extends Applet implements Runnable, MouseListener, MouseMotionListener, ActionListener, AdjustmentListener { GameObject Pacman, Ghost, Dot; private Thread thread; private boolean isRunning = false; Dimension boardSize; Image dotPic = null; Random rand; Panel pacBoard; Checkbox [] chbBehavior; Panel controlPanel = new Panel (); Label statusLabel = new Label ("Applet is loading, please wait..."); Button startButton = new Button ("Reset"); final int repaintTime = 25; final int DUMB = 0, SMARTER = 1, AVOIDING = 2; boolean debugging = false; Scrollbar hsbPacSpeed, hsbGhostSpeed; public void init () { rand = new Random (); setLayout (new BorderLayout(2, 2)); setupControlPanel (); add ("South", controlPanel); //int h = Integer.parseInt (getParameter ("height")); //int w = Integer.parseInt (getParameter ("width")); pacBoard = new Panel (); add("Center", pacBoard); //Make graphics Image dotPic = createImage (20, 20); Graphics dotGraph = dotPic.getGraphics (); dotGraph.fillRect (0, 0, 20, 20); dotGraph.drawLine (5, 5, 10, 10); Pacman = new GameObject (getImage (getCodeBase (), "pacman.jpg"), pacBoard, 2.5); Ghost = new GameObject (getImage (getCodeBase (), "ghost.jpg"), pacBoard, 2.0); Dot = new GameObject (getImage (getCodeBase (), "dot.jpg"), pacBoard); hsbPacSpeed.setValue (5); hsbGhostSpeed.setValue (4); thread = null; pacBoard.addMouseListener (this); pacBoard.addMouseMotionListener (this); } public void setupControlPanel () { controlPanel.setLayout(new BorderLayout (2, 2)); //center panel contains status label and speed scrollbars Panel midPanel = new Panel (); controlPanel.add ("Center", midPanel); midPanel.setLayout(new BorderLayout (2, 2)); statusLabel.setAlignment (Label.CENTER); /* Font f = getFont ().deriveFont (Font.BOLD); statusLabel.setFont (f); */ midPanel.add ("North", statusLabel); Panel barsPanel = new Panel (); midPanel.add ("Center", barsPanel); Label l1 = new Label ("Pacman's Speed"), l2 = new Label ("Ghost's Speed"); l1.setAlignment (Label.RIGHT); l2.setAlignment (Label.RIGHT); barsPanel.setLayout(new GridLayout(2, 2, 4, 4)); hsbPacSpeed = new Scrollbar (Scrollbar.HORIZONTAL, 0, 1, 0, 20); hsbGhostSpeed = new Scrollbar (Scrollbar.HORIZONTAL, 0, 1, 0, 20); hsbPacSpeed.addAdjustmentListener (this); hsbGhostSpeed.addAdjustmentListener (this); barsPanel.add (l1); barsPanel.add (hsbPacSpeed); barsPanel.add (l2); barsPanel.add (hsbGhostSpeed); Panel behaviorPanel = new Panel (); controlPanel.add ("West", behaviorPanel); Panel movementPanel = new Panel (); behaviorPanel.add ("West", movementPanel); movementPanel.setLayout (new BorderLayout ()); movementPanel.add ("West", new Label ("Movement type:")); Panel checkboxPanel = new Panel (); movementPanel.add ("Center", checkboxPanel); checkboxPanel.setLayout(new GridLayout(3, 1)); CheckboxGroup cbg = new CheckboxGroup (); chbBehavior = new Checkbox [3]; chbBehavior [0] = new Checkbox ("Diagonals and lines", cbg, false); chbBehavior [1] = new Checkbox ("Straight lines", cbg, false); chbBehavior [2] = new Checkbox ("Avoid enemy", cbg, true); for (int i = 0; i < 3; i ++) { checkboxPanel.add (chbBehavior [i]); } //right side of control panel contains the "reset" button controlPanel.add ("East", startButton); startButton.addActionListener (this); } public void initObjs () { // debugMsg ("size is " + boardSize.width + ", " + boardSize.height); Pacman.setRandomly (); Ghost.setRandomly (); Dot.setRandomly (); } public void begin () { initObjs (); iLive = true; statusMsg ("Running..."); } public void start () { boardSize = pacBoard.getSize (); begin (); if (thread == null) { thread = new Thread (this); } isRunning = true; thread.start (); } boolean iLive = true; public void run () { while (isRunning) { if (iLive) action (); try { thread.sleep (20); } catch (InterruptedException e) {} } } public void stop () { if ((thread != null) && thread.isAlive ()) { //thread.stop (); isRunning = false; thread = null; } } public void paint (Graphics g) { setForeground (Color.black); g.fillRect (0, 0, boardSize.width, boardSize.height); Dot.draw (g); Pacman.draw (g); Ghost.draw (g); } Image Buffer = null; Graphics bufGraphics = null; public final void update (Graphics g) { if (Buffer == null) { Buffer = createImage (boardSize.width, boardSize.height); bufGraphics = Buffer.getGraphics (); } paint (bufGraphics); Graphics boardGraphics = pacBoard.getGraphics (); boardGraphics.drawImage (Buffer, 0, 0, null); //paint (g); } public void statusMsg (String msg) { if (!debugging) { statusLabel.setText ("Status: " + msg); } } public void debugMsg (String msg) { debugging = true; statusLabel.setText ("Debug: " + msg); } boolean mouseDown = false; public void action () { double dist; if (chbBehavior [2].getState ()) dist = Ghost.chaseWhileAvoiding (Pacman, Dot); else if (chbBehavior [1].getState ()) dist = Ghost.chase (Pacman); else dist = Ghost.chase2 (Pacman); if (dist < 25.0) { iLive = false; statusMsg ("Pacman is dead!!!"); } else { } if (!mouseDown) { if (chbBehavior [2].getState ()) dist = Pacman.chaseWhileAvoiding (Dot, Ghost); else if (chbBehavior [1].getState ()) dist = Pacman.chase (Dot); else dist = Pacman.chase2 (Dot); if (dist < 2.0) { Dot.setRandomly (); } } repaint (); } ////////////////////////////////////////// // ActionListener actions ////////////////////////////////////////// public void actionPerformed (ActionEvent e) { begin (); } ////////////////////////////////////////// // AdjustmentListener actions ////////////////////////////////////////// public void adjustmentValueChanged(AdjustmentEvent e) { Pacman.setSpeed (hsbPacSpeed.getValue () / 2.0); Ghost.setSpeed (hsbGhostSpeed.getValue () / 2.0); } ////////////////////////////////////////// // MouseListener actions ////////////////////////////////////////// public void mouseEntered (MouseEvent e) { } public void mouseExited (MouseEvent e) { } public void mousePressed (MouseEvent e) { mouseDown = true; } public void mouseReleased (MouseEvent e) { mouseDown = false; } public void mouseClicked (MouseEvent e) { //Pacman.setLocation (e.getX (), e.getY ()); } ////////////////////////////////////////// // MouseEventListener actions ////////////////////////////////////////// public void mouseMoved (MouseEvent e) { if (mouseDown) { //Pacman.setLocation (e.getX (), e.getY ()); //repaint (); } } public void mouseDragged (MouseEvent e) { Pacman.setLocation (e.getX (), e.getY ()); //repaint (); } private class GameObject { protected Vector2d pos; protected Image pic; protected double speed; protected Dimension size; protected boolean sizeSet; protected Component location; public void init (Image p, Component l, double s) { pic = p; location = l; speed = s; pos = new Vector2d (); sizeSet = false; while (pic.getWidth (l) == -1) ; //have to sit and wait for image to load size = new Dimension (pic.getWidth (l), pic.getHeight (l)); //debugMsg ("size is " + size.width + " " + size.height); } public GameObject (Image p, Component l, double s) { init (p, l, s); } public GameObject (Image p, Component l) { init (p, l, 0.); } public void draw (Graphics g) { g.drawImage (pic, (int)pos.x - size.width/2, (int)pos.y - size.height/2, location); } public void setSpeed (double s) { speed = s; } public void setLocation (int x, int y) { setLocation ((double)x, (double)y); } public void setLocation (double x, double y) { pos.x = x; pos.y = y; repaint (); // repaint (repaintTime); } public void setRandomly () { setLocation (rand.nextDouble () * (boardSize.width-20.0) +10.0, rand.nextDouble () * (boardSize.height-20.0)+10.0); } public void move (double amountX, double amountY) { int oldX = (int)pos.x, oldY = (int)pos.y, maxX, minX, maxY, minY; pos.x += amountX; pos.y += amountY; repaint (repaintTime); //repaint (oldX, oldY, (int)amountX, (int)amountY); //debugMsg ("Redrawing at " + oldX + " " + oldY + // " " + (int)amountX + " " + (int)amountY); // minX = amountX < 0 ? pos.x : oldX; //maxX = amountX > 0 ? pos.x : oldX; // minY = amountY < 0 ? pos.y : oldY; //maxY = amountY > 0 ? pos.y : oldY; //repaint ((int)minX, (int)minY, (int)Math.abs (amountX), (int)Math.abs (amountY)); //repaint (); } public void move (Vector2d moving) { move (moving.x, moving.y); } //getDistance function: //Returns the distance between this and another GameObject. //To do this, subtract position of this from position of //other, then get the length of the resulting vector protected double getDistance (GameObject other) { return other.pos.sub (pos).getLength (); } //isBetween function: //This function will determine whether the object is in between //the first and second objects that are past as parameters. //I will define "between" to mean that the angle between //first-this-second is between 90 and -90 degrees. private boolean isBetween (GameObject first, GameObject second) { double vx1 = first.pos.x-pos.x; double vy1 = first.pos.y-pos.y; double vx2 = second.pos.x-pos.x; double vy2 = second.pos.y-pos.y; double dotProduct = vx1*vx2 + vy1*vy2; //double l1 = Math.sqrt (vx1*vx1 + vy1*vy1); //double l2 = Math.sqrt (vx2*vx2 + vy2*vy2); //double cosAngle = dotProduct / (l1*l2); //debugMsg ("cosAngle is " + cosAngle); //debugMsg ("l1 " + l1 + ", l2 " + l2); return (dotProduct < 0.0); } public double chase (GameObject victim) { double newX = pos.x, newY = pos.y; double totalDist = getDistance (victim); if (totalDist < speed) { setLocation (victim.pos.x, victim.pos.y); } else { double xDist = (victim.pos.x-pos.x) / totalDist, yDist = (victim.pos.y-pos.y) / totalDist; move (speed*xDist, speed*yDist); } return totalDist; } public double chase2 (GameObject victim) { double newX = pos.x, newY = pos.y; if (pos.x < victim.pos.x) { newX += speed; if (newX > victim.pos.x) newX = victim.pos.x; } else if (pos.x > victim.pos.x) if (pos.x > victim.pos.x) { newX -= speed; if (newX < victim.pos.x) newX = victim.pos.x; } if (pos.y < victim.pos.y) { newY += speed; if (newY > victim.pos.y) newY = victim.pos.y; } else if (pos.y > victim.pos.y) { newY -= speed; if (newY < victim.pos.y) newY = victim.pos.y; } setLocation (newX, newY); return (Math.abs (newX - victim.pos.x) + Math.abs(newY - victim.pos.y)); } public double chaseWhileAvoiding (GameObject victim, GameObject pursuer) { final int WarningZone = 80; final double DangerZone = 27.1; double purDist = getDistance (pursuer); double fearFactor; if (purDist < DangerZone) fearFactor = 1.0; else fearFactor = 1.0 / (Math.pow (purDist-DangerZone, .4)); Vector2d attack = victim.pos.sub (pos); double dist = attack.getLength (); // attack.x /= dist; //attack.y /= dist; if (dist < speed) { setLocation (victim.pos.x, victim.pos.y); } else { //statusMsg ("Fear factor is " + fearFactor); attack.normalize (); Vector2d escape = pos.sub (pursuer.pos); escape.normalize (); Vector2d movement = escape.mult (fearFactor).add (attack.mult (1.0-fearFactor)); movement.normalize (); movement = keepInBounds (movement); movement = movement.mult (speed); move (movement); } return getDistance (victim); } public Vector2d keepInBounds (Vector2d movement) { double xMin = size.width / 2., xMax = boardSize.width - xMin, yMin = size.height / 2., yMax = boardSize.height - yMin; double newX = pos.x+movement.x, newY = pos.y+movement.y; Vector2d awayFromXMin = new Vector2d (), awayFromXMax = new Vector2d (), awayFromYMin = new Vector2d (), awayFromYMax = new Vector2d (); double acceptableDist = 2.; double factor; if (pos.x-xMin < acceptableDist) { awayFromXMin.x = 1 - ((pos.x-xMin) / (acceptableDist)); awayFromXMin.x *= awayFromXMin.x; if (awayFromXMin.x > 1.0) awayFromXMin.x = 1.0; movement = movement.mult (1.-awayFromXMin.x); movement = movement.add (awayFromXMin); movement.normalize (); } if (xMax-pos.x < acceptableDist) { awayFromXMax.x = 1 - ((xMax-pos.x) / (acceptableDist)); awayFromXMax.x *= awayFromXMax.x; if (awayFromXMax.x > 1.0) awayFromXMax.x = 1.0; movement = movement.mult (1-awayFromXMax.x); movement = movement.sub (awayFromXMax); movement.normalize (); } if (pos.y-yMin < acceptableDist) { awayFromYMin.y = 1 - ((pos.y-yMin) / (acceptableDist)); awayFromYMin.y *= awayFromYMin.y; if (awayFromYMin.y > 1.0) awayFromYMin.y = 1.0; movement = movement.mult (1-awayFromYMin.y); movement = movement.add (awayFromYMin); movement.normalize (); } if (yMax-pos.y < acceptableDist) { awayFromYMax.y = 1 - ((yMax-pos.y) / (acceptableDist)); awayFromYMax.y *= awayFromYMax.y; if (awayFromYMax.y > 1.0) awayFromYMax.y = 1.0; movement = movement.mult (1-awayFromYMax.y); movement = movement.sub (awayFromYMax); movement.normalize (); } return movement; } public double chaseWhileAvoiding2 (GameObject victim, GameObject pursuer) { final int WarningZone = 80; final int DangerZone = 40; double purDist = getDistance (pursuer); if (purDist > WarningZone || !pursuer.isBetween (victim, this)) { statusMsg ("totally cool"); //it is safe to pursue food, since pursuer is far away... return chase (victim); } else if (purDist > DangerZone) { statusMsg ("a little nervous"); //the monster is close, but not TOO close... we want to sort //of sidle around it while still heading in the general //direction of the food. double xDir = speed*(pos.y-pursuer.pos.y) / purDist; double yDir = speed*(pursuer.pos.x-pos.x) / purDist; double newx = pos.x+xDir, newy = pos.y+yDir; double vicDist = getDistance (victim); if (Math.abs ((newx-victim.pos.x)*(newx-victim.pos.x) + (newy-victim.pos.y)*(newy-victim.pos.y)) < vicDist*vicDist) { move (xDir, yDir); } else { move (-xDir, -yDir); } } else { statusMsg ("really scared!"); //The monster is very close! Move directly away from it as //fast as you can!! double xDir = speed*(pos.x-pursuer.pos.x) / purDist; double yDir = speed*(pos.y-pursuer.pos.y) / purDist; move (xDir, yDir); } return getDistance (victim); } }; };