summaryrefslogtreecommitdiff
path: root/appl/spree/engines/lobby.b
diff options
context:
space:
mode:
Diffstat (limited to 'appl/spree/engines/lobby.b')
-rw-r--r--appl/spree/engines/lobby.b389
1 files changed, 389 insertions, 0 deletions
diff --git a/appl/spree/engines/lobby.b b/appl/spree/engines/lobby.b
new file mode 100644
index 00000000..119922e2
--- /dev/null
+++ b/appl/spree/engines/lobby.b
@@ -0,0 +1,389 @@
+implement Engine;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sets.m";
+ sets: Sets;
+ Set, set, A, B, All, None: import sets;
+include "../spree.m";
+ spree: Spree;
+ archives: Archives;
+ Archive: import Archives;
+ Attributes, Range, Object, Clique, Member, rand: import spree;
+include "readdir.m";
+ readdir: Readdir;
+
+# what the lobby provides:
+# a list of cliques it's started
+# name of clique
+# current members
+# list of members inside the lobby.
+# name
+# invites
+# how does a gather engine know who's been invited?
+# as the lobby's the only place with the knowledge of who's around to invite.
+# could allow lobby to communicate with the cliques it's started...
+# but clique also needs to communicate with the lobby
+# (e.g. to say clique has started, no more invites necessary or allowed)
+#
+# list of available engines
+# title
+# clienttype(s?)
+#
+# understands commands:
+# chat message
+# invite
+# new name params
+#
+# question: how do we know about archives?
+# answer: maybe we don't... could have another module
+# that does, or maybe an option to gather ("gather unarchive"?)
+#
+# the one that's started the clique is always invited.
+# start clique.
+# clique says to parent "invite x, y and z" (perhaps they were in the archive)
+# how should we deal with recursive invocation?
+# could queue up requests to other clique engines,
+# and deliver them after the current request has been processed.
+# no return available (one way channel) but maybe that's good,
+# as if sometime in the future engines do run in parallel, we will
+# need to avoid deadlock.
+# Clique.notify(clique: self ref Clique, cliqueid: int, note: string);
+# when a request has been completed, we run notify requests
+# for all the cliques that have been notified, and repeat
+# until no more. (could keep a count to check for infinite loop).
+# don't allow communication between unrelated cliques.
+
+clique: ref Clique;
+
+members: ref Object;
+sessions: ref Object;
+available: ref Object;
+archiveobj: ref Object;
+
+ARCHIVEDIR: con "./archive";
+
+init(srvmod: Spree, g: ref Clique, nil: list of string): string
+{
+ sys = load Sys Sys->PATH;
+ clique = g;
+ spree = srvmod;
+ sets = load Sets Sets->PATH;
+ if (sets == nil) {
+ sys->print("lobby: cannot load %s: %r\n", Sets->PATH);
+ return "bad module";
+ }
+ readdir = load Readdir Readdir->PATH;
+ if (readdir == nil) {
+ sys->print("lobby: cannot load %s: %r\n", Readdir->PATH);
+ return "bad module";
+ }
+ archives = load Archives Archives->PATH;
+ if (archives == nil) {
+ sys->print("lobby: cannot load %s: %r\n", Archives->PATH);
+ return "bad module";
+ }
+ archives->init(srvmod);
+ members = clique.newobject(nil, All, "members");
+ sessions = clique.newobject(nil, All, "sessions");
+ available = clique.newobject(nil, All, "available");
+ o := clique.newobject(available, All, "sessiontype");
+ o.setattr("name", "freecell", All);
+ o.setattr("title", "Freecell", All);
+ o.setattr("clienttype", "cards", All);
+ o.setattr("start", "gather 1 freecell", All);
+
+ o = clique.newobject(available, All, "sessiontype");
+ o.setattr("title", "Lobby", All);
+ o.setattr("name", "lobby", All);
+ o.setattr("clienttype", "lobby", All);
+ o.setattr("start", "lobby", All);
+
+ o = clique.newobject(available, All, "sessiontype");
+ o.setattr("title", "Spit", All);
+ o.setattr("name", "spit", All);
+ o.setattr("clienttype", "cards", All);
+ o.setattr("start", "gather 2 spit", All);
+
+ o = clique.newobject(available, All, "sessiontype");
+ o.setattr("title", "Canfield", All);
+ o.setattr("name", "canfield", All);
+ o.setattr("clienttype", "cards", All);
+ o.setattr("start", "gather 1 canfield", All);
+
+ o = clique.newobject(available, All, "sessiontype");
+ o.setattr("title", "Afghan", All);
+ o.setattr("name", "afghan", All);
+ o.setattr("clienttype", "cards", All);
+ o.setattr("start", "gather 1 afghan", All);
+
+ o = clique.newobject(available, All, "sessiontype");
+ o.setattr("title", "Spider", All);
+ o.setattr("name", "spider", All);
+ o.setattr("clienttype", "cards", All);
+ o.setattr("start", "gather 1 spider", All);
+
+ o = clique.newobject(available, All, "sessiontype");
+ o.setattr("title", "Racing Demon", All);
+ o.setattr("name", "racingdemon", All);
+ o.setattr("clienttype", "cards", All);
+ o.setattr("start", "gather 3 racingdemon", All);
+
+ o = clique.newobject(available, All, "sessiontype");
+ o.setattr("title", "Othello", All);
+ o.setattr("name", "othello", All);
+ o.setattr("clienttype", "othello", All);
+ o.setattr("start", "gather 2 othello", All);
+
+ o = clique.newobject(available, All, "sessiontype");
+ o.setattr("title", "Whist", All);
+ o.setattr("name", "whist", All);
+ o.setattr("clienttype", "whist", All);
+ o.setattr("start", "gather 4 whist", All);
+
+ getarchives();
+
+ clique.start();
+
+ return nil;
+}
+
+join(p: ref Member, cmd: string, nil: int): string
+{
+ sys->print("%s joins '%s'\n", p.name, cmd);
+ clique.notify(clique.parentid, "join " + p.name);
+ s := None.add(p.id);
+ clique.action("clienttype lobby", nil, nil, s);
+ clique.breakmsg(s);
+ clique.action("name " + p.name, nil, nil, s);
+ o := clique.newobject(members, All, "member");
+ o.setattr("name", p.name, All);
+ return nil;
+}
+
+leave(p: ref Member): int
+{
+ clique.notify(clique.parentid, "leave " + p.name);
+ deletename(members, p.name, "member");
+ sys->print("%s leaves\n", p.name);
+ return 1;
+}
+
+readfile(nil: int, nil: big, nil: int): array of byte
+{
+ return nil;
+}
+
+command(p: ref Member, cmd: string): string
+{
+ sys->print("%s: '%s'\n", p.name, cmd);
+ (n, toks) := sys->tokenize(cmd, " \n");
+ if (n == 0)
+ return "bad command";
+ case hd toks {
+ "kick" =>
+ getarchives();
+ return nil;
+ "chat" =>
+ clique.action("chat " + p.name + " " + concat(tl toks), nil, nil, All);
+ return nil;
+ "start" =>
+ # start engine [params]
+ if (n >= 2) {
+ (gid, fname, err) := clique.new(
+ ref Archive(tl toks, nil, nil, nil),
+ p.name);
+ if (gid == -1)
+ return err;
+ s := addname(sessions, string gid, "session");
+ s.setattr("title", concat(tl toks), All);
+ s.setattr("filename", fname, All);
+ s.setattr("cliqueid", string gid, None);
+ s.setattr("owner", p.name, All);
+ return nil;
+ }
+ return "bad start params";
+ "invite" or
+ "uninvite"=>
+ # invite sessionid name
+ if (n == 3) {
+ (what, sessionid, name) := (hd toks, int hd tl toks, hd tl tl toks);
+ if ((s := p.obj(sessionid)) == nil)
+ return "bad object id";
+ if (s.objtype != "session")
+ return "bad session type " + s.objtype;
+ if (s.getattr("owner") != p.name)
+ return "permission denied";
+ clique.notify(int s.getattr("cliqueid"), what + " " + name);
+ if (hd toks == "invite")
+ addname(s, name, "invite");
+ else
+ deletename(s, name, "invite");
+ return nil;
+ }
+ return "bad invite params";
+ "unarchive" =>
+ # unarchive object
+ if (n == 2) {
+ o := p.obj(int hd tl toks);
+ if (o == nil || o.objtype != "archive")
+ return "bad archive object";
+ # archive object contains:
+ # name name of clique
+ # members members of the clique
+ # file filename of archive
+
+ aname := o.getattr("file");
+ (archive, err) := archives->read(aname);
+ if (archive == nil)
+ return sys->sprint("cannot load archive: %s", err);
+ for (i := 0; i < len archive.members; i++)
+ if (p.name == archive.members[i])
+ break;
+ if (i == len archive.members)
+ return "you did not participate in that session";
+ (gid, fname, err2) := clique.new(archive, p.name);
+ if (gid == -1)
+ return err2;
+ s := addname(sessions, string gid, "session");
+ s.setattr("title", concat(archive.argv), All);
+ s.setattr("filename", fname, All);
+ s.setattr("cliqueid", string gid, None);
+ s.setattr("owner", p.name, All);
+
+ o.delete();
+ (ok, d) := sys->stat(aname);
+ if (ok != -1) {
+ d.name += ".old";
+ sys->wstat(aname, d);
+ }
+ # XXX delete old archive file?
+ return nil;
+ }
+ return "bad unarchive params";
+ * =>
+ return "bad command";
+ }
+}
+
+notify(srcid: int, note: string)
+{
+ sys->print("lobby: note from %d: %s\n", srcid, note);
+ s := findname(sessions, string srcid);
+ if (s == nil) {
+ sys->print("cannot find srcid %d\n", srcid);
+ return;
+ }
+ if (note == nil) {
+ s.delete();
+ return;
+ }
+ if (srcid == clique.parentid)
+ return;
+ (n, toks) := sys->tokenize(note, " ");
+ case hd toks {
+ "join" =>
+ p := addname(s, hd tl toks, "member");
+ "leave" =>
+ deletename(s, hd tl toks, "member");
+ "invite" =>
+ addname(s, hd tl toks, "invite");
+ "uninvite" =>
+ deletename(s, hd tl toks, "invite");
+ "title" =>
+ s.setattr("title", concat(tl toks), All);
+ "archived" =>
+ # archived filename
+ arch := clique.newobject(archiveobj, All, "archive");
+ arch.setattr("name", s.getattr("title"), All);
+ pnames := "";
+ for (i := 0; i < len s.children; i++)
+ if (s.children[i].objtype == "member")
+ pnames += " " + s.children[i].getattr("name");
+ if (pnames != nil)
+ pnames = pnames[1:];
+ arch.setattr("members", pnames, All);
+ arch.setattr("file", hd tl toks, None);
+ * =>
+ sys->print("unknown note from %d: %s\n", srcid, note);
+ }
+}
+
+addname(o: ref Object, name: string, otype: string): ref Object
+{
+ x := clique.newobject(o, All, otype);
+ x.setattr("name", name, All);
+ return x;
+}
+
+findname(o: ref Object, name: string): ref Object
+{
+ c := o.children;
+ for (i := 0; i < len c; i++)
+ if (c[i].getattr("name") == name)
+ return c[i];
+ return nil;
+}
+
+deletename(o: ref Object, name: string, objtype: string)
+{
+ c := o.children;
+ for (i := 0; i < len c; i++)
+ if (c[i].objtype == objtype && c[i].getattr("name") == name) {
+ o.deletechildren((i, i+1));
+ break;
+ }
+}
+
+getarchives()
+{
+ if (archiveobj == nil)
+ archiveobj = clique.newobject(nil, All, "archives");
+ else
+ archiveobj.deletechildren((0, len archiveobj.children));
+ for (names := spree->archivenames(); names != nil; names = tl names) {
+ fname := hd names;
+ (a, err) := archives->readheader(fname);
+ if (a == nil) {
+ sys->print("lobby: cannot read archive header on %s: %s\n", fname, err);
+ continue;
+ }
+ title := "";
+ for (inf := a.info; inf != nil; inf = tl inf) {
+ if ((hd inf).t0 == "title") {
+ title = (hd inf).t1;
+ break;
+ }
+ }
+ if (title == nil)
+ title = concat(a.argv);
+ arch := clique.newobject(archiveobj, All, "archive");
+ arch.setattr("name", title, All);
+ arch.setattr("members", concatarray(a.members), All);
+ arch.setattr("file", fname, None);
+ j := 0;
+ for (info := a.info; info != nil; info = tl info)
+ arch.setattr("info" + string j++, (hd info).t0 + " " + (hd info).t1, All);
+ }
+}
+
+concat(l: list of string): string
+{
+ if (l == nil)
+ return nil;
+ s := hd l;
+ for (l = tl l; l != nil; l = tl l)
+ s += " " + hd l;
+ return s;
+}
+
+concatarray(a: array of string): string
+{
+ if (len a == 0)
+ return nil;
+ s := a[0];
+ for (i := 1; i < len a; i++)
+ s += " " + a[i];
+ return s;
+}