Project archive / 11
Command-line Game
Retro command-line stag game
Welcome to the realm of the Retro Command-Line Stag Game, which combines nostalgia with interactive gameplay. Delve into the random rooms, honing your strategic skills to navigate obstacles and unlock achievements in this captivating 80s journey.
- Archive
- 11
- Category
- Command-line Game
- Result
- Data-driven command-line game
System / highlights
What I delivered.
- 01
Designed a retro command-line interface with configurable maps, entities, and actions.
- 02
Added server-client multiplayer, health, scoring, and progression systems.
- 03
Implemented fuzzy command matching and data-driven XML action parsing.
Archive / full note
Project notes.
Originally published May 8, 2022
A configurable multiplayer command-line adventure built with Java.
Introduction
Stag game is a command-line game implemented in Java, utilizing parser and game-object oriented programming principles. It can be initialized with a custom map, item and action which can be design entirely by the user. Also, it has a built in health system and a score system which can be used to keep track of the player's progress.
Multiplayer
The game can be played in multiplayer mode. The game can be played in multiplayer mode by running the Server class and the Client class. The Server class will run the game and the Client class will connect to the server and play the game.
Action Parsing
TO implement custom action, a xml file need to be created. example of a xml file is shown below.
<?xml version="1.0" encoding="UTF-8"?> <actions> <action> <triggers> <keyword>open</keyword> <keyword>unlock</keyword> </triggers> <subjects> <entity>trapdoor</entity> <entity>key</entity> </subjects> <consumed> <entity>key</entity> </consumed> <produced> <entity>cellar</entity> </produced> <narration>You unlock the trapdoor and see steps leading down into a cellar</narration> </action> <action> <triggers> <keyword>chop</keyword> <keyword>cut</keyword> <keyword>cutdown</keyword> </triggers> <subjects> <entity>tree</entity> <entity>axe</entity> </subjects> <consumed> <entity>tree</entity> </consumed> <produced> <entity>log</entity> </produced> <narration>You cut down the tree with the axe</narration> </action> <action> <triggers> <keyword>drink</keyword> </triggers> <subjects> <entity>potion</entity> </subjects> <consumed> <entity>potion</entity> </consumed> <produced> <entity>health</entity> </produced> <narration>You drink the potion and your health improves</narration> </action> <action> <triggers> <keyword>fight</keyword> <keyword>hit</keyword> <keyword>attack</keyword> </triggers> <subjects> <entity>elf</entity> </subjects> <consumed> <entity>health</entity> </consumed> <produced> </produced> <narration>You attack the elf, but he fights back and you lose some health</narration> </action> </actions>
then, the xml file need to be parsed and the action need to be added to the game. This can be done by using the DocumentBuilder class.
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); Document document = builder.parse("basic-actions.xml");
Command parsing and fuzzy matching
The user interface is retro: the player types commands into a text field and the game responds. The game parses the command and tries to match it with the action in the game. If the command is not recognized, the game will try to find a fuzzy match for the command. The fuzzy matching is done by using the Levenshtein distance algorithm.
## input example open trapdoor with key leave house and go north chop tree with axe drink potion fight elf
// Levenshtein distance algorithm. public static int levenshteinDistance(String s1, String s2) { int[][] dp = new int[s1.length() + 1][s2.length() + 1]; for (int i = 0; i <= s1.length(); i++) { for (int j = 0; j <= s2.length(); j++) { if (i == 0) { dp[i][j] = j; } else if (j == 0) { dp[i][j] = i; } else { dp[i][j] = min(dp[i - 1][j - 1] + costOfSubstitution(s1.charAt(i - 1), s2.charAt(j - 1)), dp[i - 1][j] + 1, dp[i][j - 1] + 1); } } } return dp[s1.length()][s2.length()]; } public static int costOfSubstitution(char a, char b) { return a == b ? 0 : 1; } public static int min(int... numbers) { return Arrays.stream(numbers) .min().orElse(Integer.MAX_VALUE); }
Map/Item Parsing
The map and item can be parsed from a .dot file, which is a graph description language. The .dot file can be created using the Graphviz software. The .dot file can be parsed using the Graphviz library.

digraph layout { /* ortho splines just makes the arrows into straight lines (rather than curvy ones !) */ splines = ortho; node [shape = "rect"]; subgraph locations { subgraph cluster001 { node [shape = "none"]; cabin [description = "An empty room"]; subgraph artefacts { node [shape = "diamond"]; potion [description = "Magic potion"]; } subgraph furniture { node [shape = "hexagon"]; trapdoor [description = "Wooden trapdoor"]; } } subgraph cluster002 { node [shape = "none"]; forest [description = "A dark forest"]; subgraph artefacts { node [shape = "diamond"]; key [description = "Brass key"]; } } subgraph cluster003 { node [shape = "none"]; cellar [description = "A dusty cellar"] subgraph characters { node [shape = "ellipse"]; elf [description = "Angry Elf"]; } } subgraph cluster999 { node [shape = "none"]; storeroom [description = "Storage for any entities not placed in the game"] subgraph characters { node [shape = "ellipse"]; } subgraph artefacts { node [shape = "diamond"]; log [description = "A heavy wooden log"]; } subgraph furniture { node [shape = "hexagon"]; } } } subgraph paths { cabin -> forest; forest -> cabin; cellar -> cabin; } }
Index / methods
Technology & methods.
- 01Java
- 02Command Parsing
- 03XML
- 04Graphviz
- 05Multiplayer
- 06Game Logic