Hunt The Wumpus/Java: Difference between revisions

From Rosetta Code
Content added Content deleted
(→‎Code: small changes)
(→‎Code: fix bug with bat relocation)
Line 3: Line 3:
<br>
<br>
==Code==
==Code==
[[File:wumpus_java.png|300px|thumb|right]]
[[File:wumpus_java.png|200px|thumb|right]]
{{works with|Java|8}}
{{works with|Java|8}}
<lang java>import java.awt.*;
<lang java>import java.awt.*;
Line 9: Line 9:
import java.awt.geom.Path2D;
import java.awt.geom.Path2D;
import java.util.*;
import java.util.*;
import static java.util.Arrays.asList;
import java.util.List;
import java.util.List;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
import javax.swing.*;
import javax.swing.*;
import static java.util.stream.Collectors.*;
import static javax.swing.SwingUtilities.*;
import static javax.swing.SwingUtilities.*;


Line 30: Line 28:
static final Random rand = new Random();
static final Random rand = new Random();


final int caveSize = 45;
final int roomSize = 45;
final int playerSize = 16;
final int playerSize = 16;


Line 93: Line 91:


boolean insideRoom(int ex, int ey, int cx, int cy) {
boolean insideRoom(int ex, int ey, int cx, int cy) {
return ((ex > cx && ex < cx + caveSize)
return ((ex > cx && ex < cx + roomSize)
&& (ey > cy && ey < cy + caveSize));
&& (ey > cy && ey < cy + roomSize));
}
}
});
});
Line 155: Line 153:
} while (hazards.get(currRoom).contains(Hazard.Bat));
} while (hazards.get(currRoom).contains(Hazard.Bat));


// relocate the bat, but not to the player room
// relocate the bat, but not to the player room or a room with a bat
set.remove(Hazard.Bat);
set.remove(Hazard.Bat);
int newRoom;
int newRoom;
do {
do {
newRoom = rand.nextInt(rooms.length);
newRoom = rand.nextInt(rooms.length);
} while (newRoom == currRoom);
} while (newRoom == currRoom || hazards.get(newRoom).contains(Hazard.Bat));
hazards.get(newRoom).add(Hazard.Bat);
hazards.get(newRoom).add(Hazard.Bat);


Line 204: Line 202:


void drawPlayer(Graphics2D g) {
void drawPlayer(Graphics2D g) {
int x = rooms[currRoom][0] + (caveSize - playerSize) / 2;
int x = rooms[currRoom][0] + (roomSize - playerSize) / 2;
int y = rooms[currRoom][1] + (caveSize - playerSize) - 2;
int y = rooms[currRoom][1] + (roomSize - playerSize) - 2;


Path2D player = new Path2D.Double();
Path2D player = new Path2D.Double();
Line 240: Line 238:
for (int i = 0; i < links.length; i++) {
for (int i = 0; i < links.length; i++) {
for (int link : links[i]) {
for (int link : links[i]) {
int x1 = rooms[i][0] + caveSize / 2;
int x1 = rooms[i][0] + roomSize / 2;
int y1 = rooms[i][1] + caveSize / 2;
int y1 = rooms[i][1] + roomSize / 2;
int x2 = rooms[link][0] + caveSize / 2;
int x2 = rooms[link][0] + roomSize / 2;
int y2 = rooms[link][1] + caveSize / 2;
int y2 = rooms[link][1] + roomSize / 2;
g.drawLine(x1, y1, x2, y2);
g.drawLine(x1, y1, x2, y2);
}
}
Line 250: Line 248:
g.setColor(Color.orange);
g.setColor(Color.orange);
for (int[] r : rooms)
for (int[] r : rooms)
g.fillOval(r[0], r[1], caveSize, caveSize);
g.fillOval(r[0], r[1], roomSize, roomSize);


if (!gameOver) {
if (!gameOver) {
g.setColor(Color.magenta);
g.setColor(Color.magenta);
for (int link : links[currRoom])
for (int link : links[currRoom])
g.fillOval(rooms[link][0], rooms[link][1], caveSize, caveSize);
g.fillOval(rooms[link][0], rooms[link][1], roomSize, roomSize);
}
}


g.setColor(Color.darkGray);
g.setColor(Color.darkGray);
for (int[] r : rooms)
for (int[] r : rooms)
g.drawOval(r[0], r[1], caveSize, caveSize);
g.drawOval(r[0], r[1], roomSize, roomSize);
}
}



Revision as of 15:59, 19 April 2016

In this version, arrows only fire a distance of one room. Since bats relocate, we don't have to worry about the wumpus room being unreachable. There are also 3 bats instead of 2, and you get 3 arrows instead of 5.

Code

Works with: Java version 8

<lang java>import java.awt.*; import java.awt.event.*; import java.awt.geom.Path2D; import java.util.*; import java.util.List; import javax.swing.*; import static java.util.stream.Collectors.*; import static javax.swing.SwingUtilities.*;

public class Wumpus extends JPanel {

   enum Hazard {
       Wumpus("there's an awful smell"),
       Bat("you hear a rustling"),
       Pit("you feel a draft");
       Hazard(String warning) {
           this.warning = warning;
       }
       final String warning;
   }
   static final Random rand = new Random();
   final int roomSize = 45;
   final int playerSize = 16;
   boolean gameOver = true;
   int currRoom, numArrows, wumpusRoom;
   List<String> messages;
   Map<Integer, EnumSet<Hazard>> hazards;
   public Wumpus() {
       setPreferredSize(new Dimension(721, 687));
       setBackground(Color.white);
       setForeground(Color.lightGray);
       setFont(new Font("SansSerif", Font.PLAIN, 18));
       setFocusable(true);
       addMouseListener(new MouseAdapter() {
           boolean mouseReleased = true;
           @Override
           public void mousePressed(MouseEvent e) {
               /* mousePressed feels more responsive than mouseClicked
               but we must prevent multiple events going through */
               if (!mouseReleased)
                   return;
               mouseReleased = false;
               if (gameOver) {
                   startNewGame();
               } else {
                   int selectedRoom = -1;
                   int ex = e.getX();
                   int ey = e.getY();
                   for (int link : links[currRoom]) {
                       int cx = rooms[link][0];
                       int cy = rooms[link][1];
                       if (insideRoom(ex, ey, cx, cy)) {
                           selectedRoom = link;
                           break;
                       }
                   }
                   if (selectedRoom == -1)
                       return;
                   if (isLeftMouseButton(e)) {
                       currRoom = selectedRoom;
                       situation();
                   } else if (isRightMouseButton(e)) {
                       shoot(selectedRoom);
                   }
               }
               repaint();
           }
           @Override
           public void mouseReleased(MouseEvent e) {
               mouseReleased = true;
           }
           boolean insideRoom(int ex, int ey, int cx, int cy) {
               return ((ex > cx && ex < cx + roomSize)
                       && (ey > cy && ey < cy + roomSize));
           }
       });
   }
   void startNewGame() {
       numArrows = 3;
       currRoom = rand.nextInt(rooms.length);
       messages = new ArrayList<>();
       hazards = new HashMap<>();
       for (int i = 0; i < rooms.length; i++)
           hazards.put(i, EnumSet.noneOf(Hazard.class));
       // hazards can share rooms (unless they are identical)
       int[] ordinals = {0, 1, 1, 1, 2, 2};
       Hazard[] values = Hazard.values();
       for (int ord : ordinals) {
           int room;
           do {
               room = rand.nextInt(rooms.length);
           } while (tooClose(room) || hazards.get(room).contains(values[ord]));
           if (ord == 0)
               wumpusRoom = room;
           hazards.get(room).add(values[ord]);
       }
       gameOver = false;
   }
   // don't place hazards close to the starting room
   boolean tooClose(int room) {
       if (currRoom == room)
           return true;
       for (int link : links[currRoom])
           if (room == link)
               return true;
       return false;
   }
   void situation() {
       EnumSet<Hazard> set = hazards.get(currRoom);
       if (set.contains(Hazard.Wumpus)) {
           messages.add("you've been eaten by the Wumpus");
           gameOver = true;
       } else if (set.contains(Hazard.Pit)) {
           messages.add("you fell into a pit");
           gameOver = true;
       } else if (set.contains(Hazard.Bat)) {
           messages.add("a bat dropped you in a random room");
           // teleport, but avoid 2 teleports in a row
           do {
               currRoom = rand.nextInt(rooms.length);
           } while (hazards.get(currRoom).contains(Hazard.Bat));
           // relocate the bat, but not to the player room or a room with a bat
           set.remove(Hazard.Bat);
           int newRoom;
           do {
               newRoom = rand.nextInt(rooms.length);
           } while (newRoom == currRoom || hazards.get(newRoom).contains(Hazard.Bat));
           hazards.get(newRoom).add(Hazard.Bat);
           // re-evaluate
           situation();
       } else {
           // look around
           for (int link : links[currRoom]) {
               for (Hazard hazard : hazards.get(link))
                   messages.add(hazard.warning);
           }
       }
   }
   void shoot(int room) {
       if (hazards.get(room).contains(Hazard.Wumpus)) {
           messages.add("You win! You've killed the Wumpus!");
           gameOver = true;
       } else {
           numArrows--;
           if (numArrows == 0) {
               messages.add("You ran out of arrows.");
               gameOver = true;
           } else if (rand.nextInt(4) != 0) { // 75 %
               hazards.get(wumpusRoom).remove(Hazard.Wumpus);
               wumpusRoom = links[wumpusRoom][rand.nextInt(3)];
               if (wumpusRoom == currRoom) {
                   messages.add("You woke the Wumpus and he ate you");
                   gameOver = true;
               } else {
                   messages.add("You woke the Wumpus and he moved");
                   hazards.get(wumpusRoom).add(Hazard.Wumpus);
               }
           }
       }
   }
   void drawPlayer(Graphics2D g) {
       int x = rooms[currRoom][0] + (roomSize - playerSize) / 2;
       int y = rooms[currRoom][1] + (roomSize - playerSize) - 2;
       Path2D player = new Path2D.Double();
       player.moveTo(x, y);
       player.lineTo(x + playerSize, y);
       player.lineTo(x + playerSize / 2, y - playerSize);
       player.closePath();
       g.setColor(Color.white);
       g.fill(player);
       g.setStroke(new BasicStroke(1));
       g.setColor(Color.black);
       g.draw(player);
   }
   void drawStartScreen(Graphics2D g) {
       g.setColor(new Color(0xDDFFFFFF, true));
       g.fillRect(0, 0, getWidth(), getHeight() - 60);
       g.setColor(Color.darkGray);
       g.setFont(new Font("SansSerif", Font.BOLD, 48));
       g.drawString("hunt the wumpus", 160, 240);
       g.setFont(getFont());
       g.drawString("left-click to move, right-click to shoot", 210, 310);
       g.drawString("be aware that hazards may be in the same room", 175, 345);
       g.drawString("click to start", 310, 380);
   }
   void drawRooms(Graphics2D g) {
       g.setColor(Color.darkGray);
       g.setStroke(new BasicStroke(2));
       for (int i = 0; i < links.length; i++) {
           for (int link : links[i]) {
               int x1 = rooms[i][0] + roomSize / 2;
               int y1 = rooms[i][1] + roomSize / 2;
               int x2 = rooms[link][0] + roomSize / 2;
               int y2 = rooms[link][1] + roomSize / 2;
               g.drawLine(x1, y1, x2, y2);
           }
       }
       g.setColor(Color.orange);
       for (int[] r : rooms)
           g.fillOval(r[0], r[1], roomSize, roomSize);
       if (!gameOver) {
           g.setColor(Color.magenta);
           for (int link : links[currRoom])
               g.fillOval(rooms[link][0], rooms[link][1], roomSize, roomSize);
       }
       g.setColor(Color.darkGray);
       for (int[] r : rooms)
           g.drawOval(r[0], r[1], roomSize, roomSize);
   }
   void drawMessage(Graphics2D g) {
       if (!gameOver)
           g.drawString("arrows  " + numArrows, 610, 30);
       if (messages != null) {
           g.setColor(Color.black);
           // collapse identical messages
           messages = messages.stream().distinct().collect(toList());
           // concat at most three
           String msg = messages.stream().limit(3).collect(joining(" & "));
           g.drawString(msg, 20, getHeight() - 40);
           // if there's more, print underneath
           if (messages.size() > 3) {
               g.drawString("& " + messages.get(3), 20, getHeight() - 17);
           }
           messages.clear();
       }
   }
   @Override
   public void paintComponent(Graphics gg) {
       super.paintComponent(gg);
       Graphics2D g = (Graphics2D) gg;
       g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
               RenderingHints.VALUE_ANTIALIAS_ON);
       drawRooms(g);
       if (gameOver) {
           drawStartScreen(g);
       } else {
           drawPlayer(g);
       }
       drawMessage(g);
   }
   public static void main(String[] args) {
       invokeLater(() -> {
           JFrame f = new JFrame();
           f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
           f.setTitle("Hunt The Wumpus");
           f.setResizable(false);
           f.add(new Wumpus(), BorderLayout.CENTER);
           f.pack();
           f.setLocationRelativeTo(null);
           f.setVisible(true);
       });
   }
   int[][] rooms = {{334, 20}, {609, 220}, {499, 540}, {169, 540}, {62, 220},
   {169, 255}, {232, 168}, {334, 136}, {435, 168}, {499, 255}, {499, 361},
   {435, 447}, {334, 480}, {232, 447}, {169, 361}, {254, 336}, {285, 238},
   {387, 238}, {418, 336}, {334, 393}};
   int[][] links = {{4, 7, 1}, {0, 9, 2}, {1, 11, 3}, {4, 13, 2}, {0, 5, 3},
   {4, 6, 14}, {7, 16, 5}, {6, 0, 8}, {7, 17, 9}, {8, 1, 10}, {9, 18, 11},
   {10, 2, 12}, {13, 19, 11}, {14, 3, 12}, {5, 15, 13}, {14, 16, 19},
   {6, 17, 15}, {16, 8, 18}, {19, 10, 17}, {15, 12, 18}};

}</lang>