# 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...


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:

This concatenates 3 different parameters,
which we can find in the docs:


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:


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:



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...


### 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:
Then, dump the correctly sized contents and hide the cursor:

user.socket.write(screenString + "\x1b[?25l");


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];

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) :^}