diff --git a/.classpath b/.classpath
new file mode 100644
index 0000000000000000000000000000000000000000..fb5011632c0ab8d6649a148c6fb5845a1b34c747
--- /dev/null
+++ b/.classpath
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..21a3013037921d96997bce6ed6f590be35cf4cbb
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+bin
+*.class
\ No newline at end of file
diff --git a/.project b/.project
new file mode 100644
index 0000000000000000000000000000000000000000..9772bd2069c0a5d67244ec1877b0098bb423bb72
--- /dev/null
+++ b/.project
@@ -0,0 +1,28 @@
+
+
+ functional zork
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+
+ org.eclipse.jdt.core.javanature
+
+
+
+ 1667407459743
+
+ 30
+
+ org.eclipse.core.resources.regexFilterMatcher
+ node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__
+
+
+
+
diff --git a/README.md b/README.md
index 7a2575efcbca3d94bc617821a28f1298694c8048..62d4de2e4b3194c8b0a6b9a091cd1dc421832f62 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,35 @@
-# fpzork
+# 1024 程序员开源挑战赛项目 —— fpzork
-用于练习 Java 函数式编程的一个命令行小游戏
\ No newline at end of file
+---
+
+
+
+## 1. 项目概述
+
+`fpzork` 是一个用于练习 `Java` 函数式编程的一个命令行小游戏,是 `functional programming: zork` 的简称。核心代码改编自 2015 年 `Packt` 出版设出版的《Learning Java Functional Programming》开源示例项目。
+
+
+
+## 2. 运行方式
+
+按普通 `Java` 项目导入您熟悉的 `IDE` 后(如 `Eclipse`),无需下载其它第三方依赖,即可直接运行项目。运行方式至少有以下三种方式:(以 `Eclipse` 为例)
+
+1. 选中项目后,右键菜单 Run As :arrow_right: Java Application Alt+Shift+X,J;
+2. 打开 `FunctionalZork.java`,运行 `main` 方法;
+3. 导出项目为可执行 `jar` 文件(如 `zork.jar`)后,连同 `data.txt` 文件一同复制到某文件夹下(如桌面 `demo` 文件夹);通过命令行进入 `demo`,运行命令 `java -jar zork.jar`。
+
+无论以哪种方式运行成功后,都会看到像如下所示的命令行界面:(这里以 `Windows Terminal` 为例)
+
+
+
+
+
+## 3 游戏操作命令
+
+- 捡起物品:`pickup [ [ ]]`:拾起 `Item1`、`Item2` 和 `Item3`。`pickup` 也可写为 `Pickup`;
+- 放下物品:`drop [ [ ]]`:放下 `Item1`、`Item2` 和 `Item3`。`drop` 也可写为 `Drop`;
+- 前往某个方向:`go `:前往 `Direction1`。`go` 也可换作 `walk` 或 `Walk`;
+- 查看当前位置:`look`;
+- 查看可以前往的方向:`dir | direction`;
+- 查看拥有的物品:`inv | inventory`;
+- 结束游戏:`quit`。
\ No newline at end of file
diff --git a/data.txt b/data.txt
new file mode 100644
index 0000000000000000000000000000000000000000..99ef962c184e11f2c127fb3a2540af3dcd7b4329
--- /dev/null
+++ b/data.txt
@@ -0,0 +1,50 @@
+Location
+House
+You are standing outside a house sitting in the middle of a meadow. There is a path leading north to the woods and a path leading east to a lake.
+Direction
+North
+Woods
+Direction
+East
+Emerald Blue Lake
+Item
+Axe
+A shape wooden handle axe
+Item
+Stone
+A small round rock
+Location
+Woods
+You are standing in the middle of a dense woods. There is a path leading south to a meadow and a path leading east to some small hills.
+Direction
+South
+House
+Direction
+East
+Small Hills
+Location
+Small Hills
+You are standing on the top of a small hill. To the west you see a path leading to a vast forest. To the south is a path leading to an emerald blue lake.
+Direction
+West
+Woods
+Direction
+South
+Emerald Blue Lake
+Item
+Key
+A small bronze key
+Location
+Emerald Blue Lake
+You are standing on the shores of the lake you are impressed by it deep emerald blue color. There is a path leading north to some small hills and a path leading west to a meadow.
+Direction
+West
+House
+Direction
+North
+Small Hills
+NPC
+Orge
+A fierce ugly orge lurks a few meters away. He looks like he is about to attack.
+StartingLocation
+House
\ No newline at end of file
diff --git a/img/running-game.png b/img/running-game.png
new file mode 100644
index 0000000000000000000000000000000000000000..1ca6e5944259bf6bb3f53f04cf56614faadce935
Binary files /dev/null and b/img/running-game.png differ
diff --git a/src/fpzork/Character.java b/src/fpzork/Character.java
new file mode 100644
index 0000000000000000000000000000000000000000..2d3c82c03e955e4118d0473156b79f5bbfe68715
--- /dev/null
+++ b/src/fpzork/Character.java
@@ -0,0 +1,105 @@
+package fpzork;
+
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+public class Character {
+
+ private final List items = new ArrayList<>();
+ private Location location;
+
+ public Character(Location currentLocation) {
+ this.location = currentLocation;
+ }
+
+ public boolean pickup(Command command) {
+ List arguments = command.getArguments();
+ arguments.stream()
+ .filter(itemName -> {
+ if (this.location.getItems().contains(itemName)) {
+ return true;
+ } else {
+ System.out.println("Cannot pickup " + itemName);
+ return false;
+ }
+ })
+ .forEach(itemName -> {
+ items.add(itemName);
+ this.location.getItems().remove(itemName);
+ System.out.println("Picking up " + itemName);
+ });
+ return true;
+ }
+
+ public boolean drop(Command command) {
+ List arguments = command.getArguments();
+ if (arguments.isEmpty()) {
+ System.out.println("Drop what?");
+ return false;
+ } else {
+ boolean droppedItem = false;
+
+ // Functional solution - does not modify droppedItem
+// arguments.stream()
+// .map(itemName -> {
+// if (items.remove(itemName)) {
+// this.location.addItem(itemName);
+// return "Dropping " + itemName;
+// } else {
+// return "Cannot drop " + itemName;
+// }
+// })
+// .forEach(System.out::println);
+
+ for (String itemName : arguments) {
+ droppedItem = items.remove(itemName);
+ if (droppedItem) {
+ this.location.addItem(itemName);
+ System.out.println("Dropping " + itemName);
+ droppedItem = true;
+ } else {
+ System.out.println("Cannot drop " + itemName);
+ }
+ }
+ return droppedItem;
+ }
+ }
+
+ public boolean walk(Command command) {
+ List directions = command.getArguments();
+ if (directions.isEmpty()) {
+ System.out.println("Go where?");
+ return false;
+ } else {
+ directions.forEach((direction) -> {
+ Optional locationName = GameElements.currentLocation.getLocation(direction);
+ System.out.print(locationName
+ .map(name -> {
+ Location newLocation = GameElements.locations.get(name);
+ this.location = newLocation;
+ GameElements.currentLocation = newLocation;
+ GameElements.displayView(GameElements.currentLocation);
+ return "";
+ })
+ .orElse("However, you can't go " + direction + "\n"));
+ });
+ return true;
+ }
+ }
+
+ public boolean inventory(Command command) {
+ //List arguments = command.getArguments();
+ if (items.isEmpty()) {
+ System.out.println("You are holding nothing");
+ } else {
+ System.out.print("You are holding:");
+ this.items.forEach((item) -> {
+ System.out.print(" " + item);
+ });
+ System.out.println();
+ }
+ return true;
+ }
+}
diff --git a/src/fpzork/Command.java b/src/fpzork/Command.java
new file mode 100644
index 0000000000000000000000000000000000000000..e59e6b7d04d595e772677ddd4bb91ce55fff4bd7
--- /dev/null
+++ b/src/fpzork/Command.java
@@ -0,0 +1,31 @@
+package fpzork;
+
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Command {
+ private String command;
+ private final List arguments = new ArrayList<>();
+
+ public void setCommand(String command) {
+ this.command = command;
+ }
+
+ public String getCommand() {
+ return command;
+ }
+
+ public void addArgument(String argument) {
+ this.arguments.add(argument);
+ }
+
+ public List getArguments() {
+ return arguments;
+ }
+
+ public void clear() {
+ this.command = "";
+ this.arguments.clear();
+ }
+}
diff --git a/src/fpzork/Direction.java b/src/fpzork/Direction.java
new file mode 100644
index 0000000000000000000000000000000000000000..fdcbd2eeb0f58bb441ac0b304aad2b0e115728de
--- /dev/null
+++ b/src/fpzork/Direction.java
@@ -0,0 +1,34 @@
+package fpzork;
+
+
+public class Direction {
+ private String direction;
+ private String location;
+
+ public Direction() {
+ this.direction = "";
+ this.location = "";
+ }
+
+ public Direction(String direction, String location) {
+ this.direction = direction;
+ this.location = location;
+ }
+ public String getDirection() {
+ return direction;
+ }
+
+ public Direction direction(String direction) {
+ this.direction = direction;
+ return this;
+ }
+
+ public String getLocation() {
+ return location;
+ }
+
+ public Direction location(String location) {
+ this.location = location;
+ return this;
+ }
+}
diff --git a/src/fpzork/FunctionalCommands.java b/src/fpzork/FunctionalCommands.java
new file mode 100644
index 0000000000000000000000000000000000000000..22a0e55696176a07ce06cae308f6cfa4fb386fb3
--- /dev/null
+++ b/src/fpzork/FunctionalCommands.java
@@ -0,0 +1,19 @@
+package fpzork;
+
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Supplier;
+
+public class FunctionalCommands {
+ private final List> commands = new ArrayList<>();
+
+ public void addCommand(Supplier command) {
+ commands.add(command);
+ }
+
+ public void executeCommand() {
+ commands.forEach(Supplier::get);
+ commands.clear();
+ }
+}
diff --git a/src/fpzork/FunctionalZork.java b/src/fpzork/FunctionalZork.java
new file mode 100644
index 0000000000000000000000000000000000000000..05b1b6c88732ce5a7f7873498cecc900b1a08628
--- /dev/null
+++ b/src/fpzork/FunctionalZork.java
@@ -0,0 +1,241 @@
+package fpzork;
+
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Scanner;
+import java.util.function.Supplier;
+import java.util.regex.Pattern;
+import java.util.stream.Stream;
+
+import static fpzork.GameElements.commands;
+
+public class FunctionalZork {
+
+ private final Scanner scanner;
+ private Character character = null;
+ private final FunctionalCommands fc;
+ private final Command command = new Command();
+
+ private Supplier dropCommand = () -> character.drop(command);
+ private Supplier pickupCommand = () -> character.pickup(command);
+ private Supplier walkCommand = () -> character.walk(command);
+ private Supplier inventoryCommand = () -> character.inventory(command);
+ private Supplier lookCommand = () -> {
+ GameElements.displayView(GameElements.currentLocation);
+ return true;
+ };
+
+ private Supplier directionsCommand = () -> {
+ GameElements.currentLocation.displayPaths();
+ return true;
+ };
+ private final Supplier quitCommand = () -> {
+ System.out.println("Thank you for playing!");
+ return true;
+ };
+
+ public void initializeCommands() {
+ commands.put("drop", dropCommand);
+ commands.put("Drop", dropCommand);
+ commands.put("pickup", pickupCommand);
+ commands.put("Pickup", pickupCommand);
+ commands.put("walk", walkCommand);
+ commands.put("go", walkCommand);
+ commands.put("inventory", inventoryCommand);
+ commands.put("inv", inventoryCommand);
+ commands.put("look", lookCommand);
+ commands.put("directions", directionsCommand);
+ commands.put("dir", directionsCommand);
+ commands.put("quit", quitCommand);
+ }
+
+ public static void main(String[] args) {
+ String command = "";
+ Stream commandStream;
+ FunctionalZork game = new FunctionalZork();
+ while (!"Quit".equalsIgnoreCase(command)) {
+ System.out.print(">> ");
+ commandStream = game.getCommandStream();
+ game.parseCommandStream(commandStream);
+ command = game.executeCommand();
+ }
+ }
+
+ public FunctionalZork() {
+ scanner = new Scanner(System.in);
+ fc = new FunctionalCommands();
+ initializeGame();
+ character = new Character(GameElements.currentLocation);
+ }
+
+ public void initializeGame() {
+ System.out.println("Welcome to Functional Zork!\n");
+ File file = new File("data.txt");
+ try (FileInputStream fis = new FileInputStream(file);
+ BufferedReader br = new BufferedReader(new InputStreamReader(fis))) {
+ String line = br.readLine();
+ while ("Location".equalsIgnoreCase(line)) {
+ Location location = new Location()
+ .name(br.readLine())
+ .description(br.readLine());
+ line = br.readLine();
+ while ("Direction".equalsIgnoreCase(line)) {
+ // Add direction
+ location.addDirection(new Direction()
+ .direction(br.readLine())
+ .location(br.readLine())
+ );
+ line = br.readLine();
+ }
+ while ("Item".equalsIgnoreCase(line)) {
+ // Add items
+ Item item = new Item()
+ .name(br.readLine())
+ .description(br.readLine());
+ location.addItem(item.getName());
+ GameElements.items.put(item.getName(), item);
+ line = br.readLine();
+ }
+ while ("NPC".equalsIgnoreCase(line)) {
+ // Add NPC
+ // Imperative solution
+ NPC npc;
+// npc = new NPC();
+// npc.setName(br.readLine());
+// npc.setDescription(br.readLine());
+ // Functional solution
+ npc = new NPC()
+ .name(br.readLine())
+ .description(br.readLine());
+ location.addNPC(npc.getName());
+ GameElements.npcs.put(npc.getName(), npc);
+ line = br.readLine();
+ }
+ GameElements.locations.put(location.getName(), location);
+ }
+ if ("StartingLocation".equalsIgnoreCase(line)) {
+ GameElements.currentLocation = GameElements.locations.get(br.readLine());
+ GameElements.displayView(GameElements.currentLocation);
+ } else {
+ System.out.println("Missing Starting Location");
+ }
+ initializeCommands();
+ // Test imperative partial solution
+// executeCommandImperative("drop axe and rope");
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ }
+ }
+
+ public String additionalProcessing(String token) {
+ if (token.equalsIgnoreCase("drop")) {
+ // Additional processing
+ }
+ return token;
+ }
+
+ public List processCommand(String commandLine) {
+ // Imperative solution
+ // Stop words
+ List toRemove = Arrays.asList("a", "an", "the", "and");
+
+ List tokens = new ArrayList<>();
+ for (String token : commandLine.split("\\s+")) {
+ if (!toRemove.contains(token)) {
+// tokens.add(token);
+ tokens.add(token.toLowerCase());
+
+ // First approach
+ String tmp = token.toLowerCase();
+ tmp = additionalProcessing(tmp);
+ tokens.add(tmp);
+
+ // Second approach
+ tokens.add(additionalProcessing(token.toLowerCase()));
+ }
+ }
+ return tokens;
+ }
+
+ public Stream getCommandStream() {
+ String commandLine = scanner.nextLine();
+// processCommand(commandLine);
+
+ // Stop words
+ List toRemove = Arrays.asList("a", "an", "the", "and");
+ Stream commandStream = Pattern
+ .compile("\\s+")
+ .splitAsStream(commandLine)
+ .map(s -> additionalProcessing(s))
+ //.map(s -> s.toLowerCase())
+ .filter(s -> !toRemove.contains(s));
+
+ return commandStream;
+ }
+
+ public void parseCommandStream(Stream tokens) {
+ command.clear();
+ tokens
+ .map(token -> {
+ if (commands.containsKey(token)) {
+ // Make sure this is done only once
+ command.setCommand(token);
+ } else {
+ command.addArgument(token);
+ }
+ return token;
+ })
+ .allMatch(token -> !"quit".equalsIgnoreCase(token));
+ }
+
+ public void executeDropCommand(String command, String arguments[]) {
+ // Execute command
+ System.out.print("Command: " + command);
+ for (String arg : arguments) {
+ System.out.print(" " + arg);
+ }
+ System.out.println();
+ }
+
+ public void executeCommandImperative(String commandLine) {
+ // Imperative solution variation
+ String tokens[] = commandLine.split("\\s+");
+ String arguments[] = new String[tokens.length-1];
+ int index = 0;
+ String cmd = tokens[0];
+ if (cmd.equalsIgnoreCase("drop")) {
+ // Setup drop command
+ while (index+1 < tokens.length) {
+ arguments[index] = tokens[index+1];
+ index++;
+ }
+ executeDropCommand(cmd, arguments);
+ } else if (cmd.equalsIgnoreCase("pickup")) {
+ // Setup drop command
+ } else if (cmd.equalsIgnoreCase("go")) {
+ // Setup drop command
+ } else {
+ // Bad command
+ }
+ }
+
+ public String executeCommand() {
+ Supplier nextCommand = commands.get(command.getCommand());
+ if (nextCommand != null) {
+ fc.addCommand(nextCommand);
+ fc.executeCommand();
+ return command.getCommand();
+ } else {
+ System.out.println("Bad commmand");
+ return "";
+ }
+
+ }
+}
diff --git a/src/fpzork/GameElements.java b/src/fpzork/GameElements.java
new file mode 100644
index 0000000000000000000000000000000000000000..4b6d73e920c453f768aa3ff76764a541fedbc616
--- /dev/null
+++ b/src/fpzork/GameElements.java
@@ -0,0 +1,38 @@
+package fpzork;
+
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Supplier;
+
+public class GameElements {
+
+ public static Map locations = new HashMap<>();
+ public static Map items = new HashMap<>();
+ public static Map npcs = new HashMap<>();
+ public static final Map> commands = new HashMap<>();
+ public static Location currentLocation;
+
+ public static void displayLocation(Location location) {
+ System.out.println("-------------------");
+ System.out.println("Name: " + location.getName());
+ System.out.println("Description: " + location.getDescription());
+ System.out.println("Items -------- ");
+
+ location.getItems().forEach(name -> System.out.println(GameElements.items.get(name)));
+
+ location.getNPCs().forEach(name -> System.out.println(GameElements.npcs.get(name)));
+ System.out.println();
+ }
+
+ public static void displayView(Location location) {
+ System.out.println(location.getDescription());
+ GameElements.currentLocation.displayItems();
+ GameElements.currentLocation.displayNPCs();
+ }
+
+ public static void displayDirections(Location location) {
+ GameElements.currentLocation.displayPaths();
+ }
+
+}
diff --git a/src/fpzork/Item.java b/src/fpzork/Item.java
new file mode 100644
index 0000000000000000000000000000000000000000..9b30c47a32b66960311e1220a5fd3ba6e872955f
--- /dev/null
+++ b/src/fpzork/Item.java
@@ -0,0 +1,30 @@
+package fpzork;
+
+
+public class Item {
+ private String name;
+ private String description;
+
+ public String getName() {
+ return name;
+ }
+
+ public Item name(String name) {
+ this.name = name;
+ return this;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public Item description(String description) {
+ this.description = description;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return "Name: " + this.name + " Description: " + this.description;
+ }
+}
diff --git a/src/fpzork/Location.java b/src/fpzork/Location.java
new file mode 100644
index 0000000000000000000000000000000000000000..34d2ba2ef7d52e4993e0c2e6fd3f93f25e791299
--- /dev/null
+++ b/src/fpzork/Location.java
@@ -0,0 +1,92 @@
+package fpzork;
+
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+public class Location {
+
+ private String name;
+ private String description;
+ private final List items = new ArrayList<>();
+ private final List npcs = new ArrayList<>();
+ private final Map directions = new HashMap<>();
+
+ public String getName() {
+ return name;
+ }
+
+ public Location name(String name) {
+ this.name = name;
+ return this;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public Location description(String description) {
+ this.description = description;
+ return this;
+ }
+
+ public List getItems() {
+ return this.items;
+ }
+
+ public void addItem(String item) {
+ this.items.add(item);
+ }
+
+ public List getNPCs() {
+ return npcs;
+ }
+
+ public void addNPC(String npc) {
+ this.npcs.add(npc);
+ }
+
+ public Optional getLocation(String direction) {
+ if (this.directions.containsKey(direction)) {
+ return Optional.of(this.directions.get(direction).getLocation());
+ } else {
+ return Optional.empty();
+ }
+ }
+
+ public void addDirection(Direction direction) {
+ directions.put(direction.getDirection(), direction);
+ }
+
+ public void displayItems() {
+ if (items.isEmpty()) {
+ // Do nothing
+ } else {
+ System.out.print("On the ground you see:");
+ items.stream()
+ .forEach(item -> {
+ System.out.print(" " + item);
+ });
+ System.out.println();
+ }
+ }
+
+ public void displayNPCs() {
+ if (npcs.isEmpty()) {
+ // Do nothing
+ } else {
+ npcs.forEach(System.out::println);
+ }
+ }
+
+ public void displayPaths() {
+ directions.forEach((way, direction) -> {
+ System.out.print("If you go " + way);
+ System.out.println(" you can get to " + direction.getLocation());
+ });
+ }
+
+}
diff --git a/src/fpzork/NPC.java b/src/fpzork/NPC.java
new file mode 100644
index 0000000000000000000000000000000000000000..9619ac7ce9dd100754ad9b38ca068769c89fd211
--- /dev/null
+++ b/src/fpzork/NPC.java
@@ -0,0 +1,38 @@
+package fpzork;
+
+
+public class NPC {
+ private String name;
+ private String description;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public NPC name(String name) {
+ this.name = name;
+ return this;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public NPC description(String description) {
+ this.description = description;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return "Name: " + name + " Description: " + description;
+ }
+}