13
\$\begingroup\$

Cue Storyline: It is the early 21st century, and most things have become a thing of the past. You and your fellow code-golf-eteers, however, are on a quest to reenact the 1990s. As a part of this challenge, you must recreate a minimalistic chatroom.

The Goal: To create a chatroom with as few bytes as possible. The program(s) that you write shall function as a simple server, which serves a webpage, which allows users to post text onto the screen.

The Actual Goal: To actually host a working chatroom from your own computer. You don't have to do this, but it is a lot more fun this way.

Requirements:

  • Users should be able to give themselves a username that lasts for the session
  • Users should be able to repeatedly type and submit text, which will be displayed to other users
  • Each user should be able to see the text that is submitted by all users, along with the usernames of the submitters, and the information should be displayed in chronological order
  • The page should also display the number of people online and a list of their usernames
  • Your chatroom should be accessible to anyone on the internet who knows where to find it (such as knowing the IP address).
  • It should function in modern web browsers.

All else is up to you!

Submissions:

  • Should include the source code, or a link to the source code
  • Should include screenshots of the functional chatroom
  • Should include the total size, in bytes, of all of the programs/files that you have written that are required to make it work.

This challenge has been in the sandbox for a while now, so hopefully all of the kinks have been worked out.

\$\endgroup\$
  • 1
    \$\begingroup\$ would it be allowable if we forced the chatters to type each of their responses within nine seconds? \$\endgroup\$ – John Dvorak Aug 4 '13 at 18:12
  • \$\begingroup\$ I think that 9 seconds is probably too short. Something like 99 seconds could probably work, though. I never thought of putting time limits on the chatters. \$\endgroup\$ – PhiNotPi Aug 4 '13 at 18:48
  • \$\begingroup\$ define "online". Do we need to post back on before unload, 5s timeout will suffice, or are we even allowed to claim people never log off? \$\endgroup\$ – John Dvorak Aug 4 '13 at 18:54
  • \$\begingroup\$ are we allowed to allow HTML-injection and other stuff that could break the chatroom (as long as we don't hurt our own computer)? \$\endgroup\$ – John Dvorak Aug 4 '13 at 19:01
  • \$\begingroup\$ Actually, can you clarify "type each of their responses within nine seconds?" By "online," I would mean a list of people who are currently viewing the chatroom, such as having the chatroom open in a browser window. A timeout will be fine. \$\endgroup\$ – PhiNotPi Aug 4 '13 at 20:21
18
\$\begingroup\$

PHP + JQuery + HTML + CSS, 1535 bytes

This is a submission leaning more towards what the OP deemed as 'the actual goal'. That is, a fully functional chat server, which may be hosted on just about any web server anywhere.

Functionality includes:

  • Notifications when users enter or leave the chat room.
  • Notifications when users change their alias.
  • Real time polling for new messages, without generating excess server traffic, or server load.
  • Layout and usability strongly resembling existing chat clients, such as X-Chat.

To being a session, enter an alias in the appropriate box, and press Tab or Enter to submit. If the alias is already in use, you will be notified. Sending messages is also done via Enter.

For your convenience, an archive of all files may be found here: chat.zip (choose Download from the File menu). To install, unpack to a web directory on any server running PHP 5.4 or higher.

Caveats:

  • IE 8 or lower will spin into an infinite loop while polling, because for some reason unknown to humanity, all Ajax requests are cached by default. It also prevents you from sending the same message twice, and updating the user list properly. This could be fixed by adding cache:false to every Ajax request.
  • In all version of IE, messages will not be sent by pressing Enter, because the change event is not triggered (pressing Tab, however, does work). This could be fixed by adding an onkeypress handler, checking if the key was Enter, and then calling $(v).blur().focus().

In short, IE is not supported.


Client

Positioning of elements could be a bit more robust, but it should look alright with a minimum window size of about ~800x600.

chat.htm (190 bytes)

<script src=jquery.min.js></script>
<script src=c.js></script>
<link rel=stylesheet href=c.css>
<select id=u multiple></select><pre id=o></pre>
<input id=n onchange=u()><input id=v onchange=s()>

c.css (136 bytes)

i{color:#999}
#u{float:right;height:100%;width:200px;margin-left:10px}
#o{border:1px solid #999;height:93%;overflow-y:scroll}
#v{width:54%}

c.js (435 bytes)

var l
(function p(){
  $.ajax({url:'p.php',data:{n:$('#n').val()},success:function(d){
    $('#o').html(d).scrollTop(1e4);$('#u').load('n.php');
  },complete:p,timeout:2e4})
})()
function s(){
  $.get('s.php',{n:$(n).val(),v:$(v).val()})
  $(v).val('')
}
function u(){
  $.get('u.php',{l:i=l,n:l=$(n).val()}).fail(function(){
    alert("This name is already in use!")
    $(n).val(l=i)
  })
}
$(window).on('unload',function(){$.ajax({url:'l.php',data:{l:l},async:false})})

Server

I apologize for the server being broken up into so many tiny chunks. The alternative would be to use an adequate message protocol (via JSON encode/decode), or having a large if ... elseif ... according to which post variables are present. Making separate scripts, an just requesting from the one you need is a lot shorter, and perhaps simpler than both.

o.php (119 bytes) Opens as connection to the 'database'

<?$m=array_slice(unserialize(file_get_contents(m)),-300);
$u=unserialize(file_get_contents(u));$t=time();extract($_GET);

c.php (57 bytes) Commits changes to the 'database'

<?foreach([u,m]as$c)file_put_contents($c,serialize($$c));

p.php (151 bytes) Polls for new messages

<?for($t=time();@filemtime(m)<$t;usleep(1e3))clearstatcache();include('o.php');
foreach($m as$v)if($n&&$v[0]>=$u[$n])echo@date("[H:i]",$v[0])."$v[1]\n";

s.php (62 bytes) Send a message to the server

<?include('o.php');$m[]=[$t,"<b>$n</b>: $v"];include('c.php');

u.php (222 bytes) User registration or alias change

<?include('o.php');if(!trim($n)||$u[$n])exit(header('HTTP/1.1 418'));
$m[]=[$t,$u[$l]?
"<i><b>$l</b> is now known as <b>$n</b>.</i>":
"<i><b>$n</b> has entered the chat.</i>"];
$u[$n]=$u[$l]?:$t;unset($u[$l]);include('c.php');

n.php (65 bytes) Retrieves the list of user names

<?include('o.php');foreach($u as$k=>$v)echo"<option>$k</option>";

l.php (98 bytes) User has left (closed their browser)

<?include('o.php');$m[]=[$t,"<i><b>$l</b> has left the chat.</i>"];
unset($u[$l]);include('c.php');
\$\endgroup\$
  • \$\begingroup\$ I think you can do onchange=u without the parentheses. You won't get a consistent context, however, but you don't need it anyways. \$\endgroup\$ – John Dvorak Aug 9 '13 at 4:13
  • \$\begingroup\$ Can you make the tutorial a bit more detailed? I want to set this up on a Mac. \$\endgroup\$ – haykam Jul 27 '16 at 7:48
  • \$\begingroup\$ @Peanut I typed up some instructions: codepad.org/UKGwb4g2. I'm working blind, but this will probably work. \$\endgroup\$ – primo Jul 27 '16 at 9:13
13
\$\begingroup\$

Python, 230

This is fairly minimal, but it seems to be up to spec. Users are counted as "viewing the page" if they've chatted in the last 99 seconds.

import cherrypy as S,time
@S.quickstart
@S.expose
def _(n='',p='',l=["<form%sn value='%s'%sp%s'' type=submit>"],u={},t="><input name="):u[n]=time.time();l+=p and[n+':'+p];return'<br>'.join([k*(u[n]-99<u[k])for k in u]+l)%(t,n,t,t)

This uses one of my very favorite tricks in python: default values are just references to whatever you passed in. If it's a mutable object, it just comes along for the ride.

Another I don't get to use often -- currying!

Running the Server:

Run the chat script from python (for example, python chat.py) and then point your browser at http://localhost:8080 to see something like

screenshot

Python, 442

This one is actually nice to use. This is golf, so I consider this a less-satisfactory solution. Now, I abuse an iframe and a form with keyhandling... and a meta refresh to poll for new content.

import time,cherrypy as S
class C:
 c=S.expose(lambda s:"<form action=w target=t method=post><input name=n><input name=p onkeyup='if(event.keyCode==13){this.form.submit();this.value=\"\"}'><br><iframe name=t width=640>")
 @S.expose
 def w(s,n='',p='',l=[],u={}):u[n]=time.time();l+=p and[n+':'+p];return'<meta http-equiv=refresh content="1;url=w?n=%s">'%n+','.join(k for k in u if(u[n]-9<u[k])*k)+'<hr>'+'<br>'.join(l[::-1])
S.quickstart(C())

version 2

\$\endgroup\$
  • 2
    \$\begingroup\$ I doubt I can point my browser at http://localhost:8080/c and access your HTTP server \$\endgroup\$ – John Dvorak Aug 5 '13 at 20:05
  • 1
    \$\begingroup\$ @JanDvorak That's why I didn't make that a link. \$\endgroup\$ – boothby Aug 5 '13 at 22:21
  • 1
    \$\begingroup\$ For those who already have a service running on port 8080, you may prepend the following to use a different port: S.config.update({'server.socket_port':8090}) \$\endgroup\$ – primo Aug 6 '13 at 4:30
  • \$\begingroup\$ How difficult would it be to update the chat window when anybody sends a new message, and not just the user? (In its current form, in order to check if there are any new messages, you need submit a blank message before your window is updated.) \$\endgroup\$ – primo Aug 8 '13 at 8:45
  • 1
    \$\begingroup\$ @primo Right! That's how you check to see if people have said something. The problem said to reenact the 90's. And back then, expecting your users to accept the interface suggested by the simplest code was still cool. HP gave us RPN, and we liked it. Thinking like a 201*-er gets you 1280 characters of bloat. \$\endgroup\$ – boothby Aug 8 '13 at 23:03
5
\$\begingroup\$

Meteor: 575 characters

I had a lot of fun with this one! The application is live at http://cgchat.meteor.com/.

chat.html: 171 characters

<body>{{>b}}</body><template name="b">{{#if l}}Online: {{#each u}}{{n}}, {{/each}}<hr>{{#each m}}{{n}}: {{t}}<p>{{/each}}<hr><input>{{else}}Name: <input>{{/if}}</template>

lib/chat.js: 45 characters

c=Meteor.Collection;u=new c('u');m=new c('m')

client/client.js: 359 characters

j=$.now;s=Session;t=Template.b;t.events({'change input':function(){v=$('input').val();s.get('u')?(m.insert({n:s.get('u'),t:v}),u.update(u.findOne({n:s.get('u')})._id,{$set:{l:j()}})):(s.set('u',v),u.insert({n:v,l:j()}))}});t.l=function(){return !!s.get('u')};t.u=function(){return u.find({l:{$gt:(j()-20000)}}).fetch()};t.m=function(){return m.find().fetch()}
\$\endgroup\$
  • \$\begingroup\$ Link is now dead. \$\endgroup\$ – programmer5000 Apr 6 '17 at 12:02
5
\$\begingroup\$

Node/Meteor javascript + html + css + websocket: 1,105 bytes

Here's one using node.js/meteor. Obviously written in js, realtime, and using websockets. Uses meteor's default built-in packages.

It could be a lot smaller. Also it's persistent by way of the included mongo (not that that is a good thing).

A working screenshot:

enter image description here

To execute, install meteor.

Linux:

curl https://install.meteor.com | /bin/sh`

Windows: win.meteor.com

Clone my repo and execute meteor:

git clone http://github.com/bradgearon/meteor-chat
cd meteor-chat
meteor

point your browser to localhost:3000

chat.js: 703 bytes (client/server):

l='subscribe',d=[],n='n',i='i',b='b',c='click #',r='return ',u='u',y=0
f=Function,m=Meteor,o=m.Collection,p=new o(b),t=new o(u)
w=f('t.remove({i:d.pop()})'),g=f('_(d.length).times(w)')
m.isClient&&(h=Template.h,e=h.events={},m[l](b),m[l](u),s=Session,
w=f(r+'s.get(i)'),h.p=f(r+'p.find()'),h.t=f(r+'t.find()'),a=f('a','a','y=$("#3").val(),s.set(i,1)'),
e[c+'2']=f('p.insert({c:(y||"?")+": "+$("#l").val()})'),
e[c + '4'] = f('w()||m.call("x",$("#3").val(),t._connection._lastSessionId,a)')
)||(
m.startup(f('t.remove({}),p.remove({}),m.setInterval(g,100)')),j=f('h=this.id;h&&d.push(h)'),
m.methods({x:f('k','d','s=m.default_server.sessions[d].socket,s.on("close",j),t.insert({n:k,i:s.id})')}))

chat.css: 132 bytes

g{display:block;overflow-y:scroll;margin:10px;}
n{float:right;width:40%;min-height:100%;}
d{float:left;width:60%;min-height:100%;}

chat.html: 270 bytes

<body>
    {{> h}}
</body>
<template name="h">
<d>
<g>{{#each p}}{{c}}<br />{{/each}}</g>
<input id=l>{{this.k}}</input>
<input type=submit id=2 />
</d>
<n>
<g>{{#each t}}{{n}}<br />{{/each}}</g>
<input id=3 />
<input type=submit id=4 />
</n>
</template>
\$\endgroup\$
  • 1
    \$\begingroup\$ Welcome to codegolf! That file chat.html seems to only have 254 bytes. Note that browsers aren't terribly picky -- I don't bother to close tags, and you definitely don't need the slash at the end of tags (unless node requires it?). Also, kill more whitespace! I see a couple in the javascript, and way too much in the html. \$\endgroup\$ – boothby Aug 10 '13 at 15:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.