Contents

1. Introduction
2. The main loop
3. Locations
4. Objects
5. Inventory
6. Passages
7. Distance
8. North, east, south, west
9. Code generation
10. More attributes
11. Conditions
12. Open and close
13. The parser
14. Multiple nouns
15. Light and dark
16. Savegame
17. Test automation
18. Abbreviations
19. Conversations
20. Combat
21. Multi-player
22. Client-server
23. WebSockets
24. Database
25. Speech

How to program a text adventure in C

by Ruud Helderman <r.helderman@hccnet.nl>

Licensed under MIT License

23. WebSockets

Telnet uses Berkeley sockets to implement two-way communication between client and server. Modern web browsers have something similar: WebSockets.

In the previous chapter, we turned our text adventure into an online multi-player experience. To play the game, you needed a Telnet client. A Telnet client can still be found on most computers, but most computer users have never used it, and are not even aware it is there. But that is probably best for the safety of all. Securing a Telnet connection takes effort and skill; users are likely to give up, or worse, mess up.

Today, web browsers are the ubiquitous internet clients. Browsing the world wide web, we take for granted the security measures offered by modern web browsers. Wherever you go, encrypted connections and digital certificates help protect your digital identity.

So let’s see if we can use a web browser instead of a Telnet client. To make a web browser talk to our game server, they both need to use WebSockets. We need to do two things:

  1. Build a WebSocket client.
  2. Turn our TCP server into a WebSocket server.

The first part is easy. A WebSocket client is basically a web page; it is the player’s user interface. JavaScript is what makes the communication happen. MDN has some excellent articles about WebSockets. This one describes how to implement a WebSocket client in JavaScript:
https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_client_applications

Based on that knowledge, I made the following HTML file. When opened in a web browser, it will show a very basic web page. There is an input field for the player to enter commands. The rest of the page is used to print the responses coming back from the game server.

www/index.html
  1. <!DOCTYPE html>
  2. <meta charset="utf-8">
  3. <title>Little Cave Adventure</title>
  4. <style>
  5. div { min-height: 1em; }
  6. div.in { border: thin solid silver; padding: 3px; }
  7. .in { margin: 1em 0; width: 100%; box-sizing: border-box; }
  8. #output:empty:before { content: 'Waiting for connection...'; }
  9. </style>
  10. <h2>Little Cave Adventure</h2>
  11. <div id="output"></div>
  12. <form name="lilcave" action="#"><input name="in" class="in" autofocus></form>
  13. <script>
  14. let prompt = false;
  15. const url = window.location.href.replace('http', 'ws');
  16. const ws = new WebSocket(url);
  17. ws.onopen = function() { console.info('Socket opened', event); };
  18. ws.onclose = function() { console.info('Socket closed', event); };
  19. ws.onerror = function() { console.error('Socket error', event); };
  20. ws.onmessage = function() { event.data.text().then(print); };
  21. lilcave.onsubmit = function() {
  22. const input = this.elements.in.value;
  23. this.reset();
  24. event.preventDefault();
  25. ws.send(new TextEncoder().encode(input + '\r'));
  26. }
  27. function print(text) {
  28. for (let line of text.replace(/.*\x01/, '').trimEnd().split('\n')) {
  29. if (line == '-->') {
  30. prompt = true;
  31. }
  32. else {
  33. const div = document.createElement('div');
  34. const re = /\r\s*\r/;
  35. if (re.test(line)) {
  36. line = line.replace(re, '');
  37. }
  38. else if (prompt) {
  39. div.className = 'in';
  40. }
  41. div.appendChild(document.createTextNode(line));
  42. document.getElementById('output').appendChild(div);
  43. window.scrollTo({top: document.body.scrollHeight, behavior: 'smooth'});
  44. prompt = false;
  45. }
  46. }
  47. }
  48. </script>

Explanation:

The next step is the server. MDN has this article that describes in full detail how to implement the WebSocket protocol around the Berkeley sockets we already implemented in the previous chapter.
https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers

It is doable, but still, it feels like a lot of work, and what for? Surely, I cannot be first person on earth that wants to make a translation between WebSocket traffic and standard Berkeley socket traffic. I went looking for something off-the-shelf.

One possibility is a C library. I tried Mongoose and it worked very well. Still, I did not like the many changes I had to make to my existing TCP server. In this article series, each chapter should be a small step forward. So I went looking for something less intrusive.

I settled for websockify. It is a straightforward command line tool. On a Linux server, there is a good chance you can install (and update) it straight from a repository. For example, here is how to install it on Ubuntu:

sudo apt install websockify

There are two possible ways to use websockify. The first one is to let websockify and lilcave run side by side.

./lilcave <<<quit & websockify --web www 18812 localhost:18811

This has the advantage that the same game is playable both from Telnet clients (port 18811) and from web clients (port 18812). If you do not care about Telnet support, then it is also possible to let websockify act as a wrapper around lilcave:

websockify --web ./www 18811 -- ../lilcave <<<quit

Open your web browser and navigate to http://localhost:18811/. Enter some commands to see if it works.

The final step is to expose your game to the outside world.

How about self-hosting the game? Traditionally, you would configure port forwarding on your internet router. This has the disadvantage that every player needs to know your IP address. It is the digital equivalent of inviting the whole world to your home address. In today’s world, it probably better to hide behind a proxy server. A very nice option is a Cloudflare tunnel.

Do be careful, this game is written in C, and C is notorious for its lack of memory safety. A small bug in the program may be enough to allow a smart player to hack your entire computer via a carefully crafted command.

Authentication is still missing in this game.


⭳   Download source code 🌀   Run on Repl.it

Next chapter: 24. Database