# Collaborative ASCII Drawing With Telnet *by [@bwasti](https://twitter.com/bwasti)* **** If the server isn't swamped, you can try it out (hold shift to erase, arrow keys to move): ``` telnet bram.town ``` If you're on a newer mac, you may need to `brew install telnet`. It doesn't come by default these days... ![](https://i.imgur.com/QfIJWob.gif) The full code listing can be found [here](https://github.com/bwasti/bram.town/blob/main/server.ts). I run it with `bun server.ts`. **** ### User Input For details, check out the xterm docs: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html We can tell xterm terminals to start sending mouse information by sending connected users an ANSI sequence like this: ``` socket.write("\x1b[?1003;1006;1015h"); ``` This concatenates 3 different parameters, which we can find in the docs: ![](https://i.imgur.com/K1qmfPb.png) I found the `EXT_MODE` parameters to be particularly important, as they can cause the *client* to crash if not set correctly (when using a macOS version at least). This was a nightmare to debug, because the errors looked like this: ![](https://i.imgur.com/k5MH6vO.png) and they only happened... sometimes! When moving the cursor beyond ~90 or so characters to the right, the client would crash but not disconnect. But this wasn't consistent. And that was because I was playing with a really cool project for inspiration: **mapscii.me!** ![](https://i.imgur.com/RbjbedX.gif) and every time I used mapscii, my terminal was correctly set to the extended mode. Because terminals are state based and can be manipulated by the characters they print. Neat. I guess we should clean up when people leave... ``` user.socket.write("\x1b[?1003;1006;1015l"); ``` ### Rendering Output But just getting user input isn't enough, we also need to "render" the output. This happens to be a lot easier, and ChatGPT wrote that code for me: First, move the cursor to the top left corner: ``` user.socket.write("\x1b[H"); ``` Then, dump the correctly sized contents and hide the cursor: ``` user.socket.write(screenString + "\x1b[?25l"); ``` Done. Although I didn't explain "correctly sized contents" at all. This turns out to be another tricky mess of hard to understand control code sequences. Luckily for me, ChatGPT actually *did* understand this stuff, so I didn't have to write/debug that part of the code. Getting the user window size is done with telnet's NAWS option. NAWS stands for "Negotiate About Window Size." The server requests NAWS by outputting a series of telnet protocol bytes: ``` socket.write(Buffer.from([IAC, DO, NAWS])); ``` and the client will respond if it can handle such a request as well as the data associated with it. I assume it can and just read the data: ``` if (data.length >= 5) { user.width = (data[0] << 8) + data[1]; user.height = (data[2] << 8) + data[3]; //scheduleRender(user); } ``` You can read more about this stuff here: http://www.pcmicro.com/netfoss/telnet.html **** ### Features Once I got this working I added some random features, but I'm hoping to add more. Drawing and panning around a global canvas with multiple users is kind of a litmus test for these things, so I just did that. You can move around with WASD or arrow keys. You can click and drag to draw pixels. These can be "layered" by repeated drawing to get a typical "ASCII art" effect. If you hold shift while dragging around, you erase pixels. Your cursor is represented by a little circle and everyone can see it. --- if you'd like to follow me [@bwasti](https://twitter.com/bwasti) :^}