implement Cardlib; 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; Attributes, Range, Object, Clique, Member, rand: import spree; include "objstore.m"; objstore: Objstore; include "cardlib.m"; MAXPLAYERS: con 4; Layobject: adt { lay: ref Object; name: string; packopts: int; pick { Obj => obj: ref Object; # nil if it's a frame Frame => facing: int; # only valid if for frames } }; clique: ref Clique; cmembers: array of ref Cmember; cpids := array[8] of list of ref Cmember; # XXX first string is unnecessary as it's held in the Layobject anyway? layouts := array[17] of list of (string, ref Layout, ref Layobject); maxlayid := 1; cmemberid := 1; archiveobjs: array of list of (string, ref Object); defaultrank := array[13] of {12, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; defaultsuitrank := array[] of {CLUBS => 0, DIAMONDS => 1, HEARTS => 2, SPADES => 3}; table := array[] of { 0 => array[] of { (-1, dTOP|EXPAND, dBOTTOM, dTOP), }, 1 => array [] of { (0, dBOTTOM|FILLX, dBOTTOM, dTOP), (-1, dTOP|EXPAND, dBOTTOM, dTOP), }, 2 => array[] of { (0, dBOTTOM|FILLX, dBOTTOM, dTOP), (1, dTOP|FILLX, dTOP, dBOTTOM), (-1, dTOP|EXPAND, dBOTTOM, dTOP) }, 3 => array[] of { (2, dRIGHT|FILLY, dRIGHT, dLEFT), (0, dBOTTOM|FILLX, dBOTTOM, dTOP), (1, dTOP|FILLX, dTOP, dBOTTOM), (-1, dRIGHT|EXPAND, dBOTTOM, dTOP) }, 4 => array[] of { (1, dLEFT|FILLY, dLEFT, dRIGHT), (3, dRIGHT|FILLY, dRIGHT, dLEFT), (0, dBOTTOM|FILLX, dBOTTOM, dTOP), (2, dTOP|FILLX, dTOP, dBOTTOM), (-1, dRIGHT|EXPAND, dBOTTOM, dTOP) }, }; init(mod: Spree, g: ref Clique) { sys = load Sys Sys->PATH; sets = load Sets Sets->PATH; if (sets == nil) panic(sys->sprint("cannot load %s: %r", Sets->PATH)); objstore = load Objstore Objstore->PATH; if (objstore == nil) panic(sys->sprint("cannot load %s: %r", Objstore->PATH)); objstore->init(mod, g); clique = g; spree = mod; } archive(): ref Object { for (i := 0; i < len cmembers; i++) { cp := cmembers[i]; setarchivename(cp.obj, "member" + string i); setarchivename(cp.layout.lay, "layout" + string i); sel := cp.sel; if (sel.stack != nil) setarchivename(sel.stack, "sel" + string i); } for (i = 0; i < len layouts; i++) { for (ll := layouts[i]; ll != nil; ll = tl ll) { (name, lay, layobj) := hd ll; if (name != nil) layobj.lay.setattr("layname", name, None); pick l := layobj { Frame => l.lay.setattr("facing", sides[l.facing], None); Obj => setarchivename(l.obj, "layid" + l.obj.getattr("layid")); } } } # XXX should archive layouts that aren't particular to a member. archiveobj := clique.newobject(nil, None, "archive"); setarchivename(archiveobj, "archive"); archiveobj.setattr("maxlayid", string maxlayid, None); archiveobj.setattr("cmemberid", string cmemberid, None); return archiveobj; } setarchivename(o: ref Object, name: string) { objstore->setname(o, name); } getarchiveobj(name: string): ref Object { return objstore->get(name); } archivearray(a: array of ref Object, name: string) { for (i := 0; i < len a; i++) objstore->setname(a[i], name + string i); } getarchivearray(name: string): array of ref Object { l: list of ref Object; for (i := 0; ; i++) { o := objstore->get(name + string i); if (o == nil) break; l = o :: l; } a := array[i] of ref Object; for (; l != nil; l = tl l) a[--i] = hd l; return a; } unarchive(): ref Object { objstore->unarchive(); archiveobj := getarchiveobj("archive"); cpl: list of ref Cmember; for (i := 0; (o := getarchiveobj("member" + string i)) != nil; i++) { cp := ref Cmember( i, int o.getattr("id"), clique.membernamed(o.getattr("name")), o, ref Layout(getarchiveobj("layout" + string i)), ref Selection(getarchiveobj("sel" + string i), -1, 1, (0, 0), nil) ); cp.sel.ownerid = cp.id; sel := cp.sel; if (sel.stack != nil && (selstr := sel.stack.getattr("sel")) != nil) { (n, val) := sys->tokenize(selstr, " "); if (tl val != nil && hd tl val == "-") (sel.r.start, sel.r.end) = (int hd val, int hd tl tl val); else { idxl: list of int; sel.isrange = 0; for (; val != nil; val = tl val) idxl = int hd val :: idxl; sel.idxl = idxl; } } lay := cp.layout.lay; # there should be exactly one child, of type "layframe" if (len lay.children != 1 || lay.children[0].objtype != "layframe") panic("invalid layout"); x := strhash(nil, len layouts); layouts[x] = (nil, cp.layout, obj2layobj(lay.children[0])) :: layouts[x]; unarchivelayoutobj(cp.layout, lay.children[0]); cpl = cp :: cpl; } cmembers = array[len cpl] of ref Cmember; for (; cpl != nil; cpl = tl cpl) { cp := hd cpl; cmembers[cp.ord] = cp; idx := cp.id % len cpids; cpids[idx] = cp :: cpids[idx]; } maxlayid = int archiveobj.getattr("maxlayid"); cmemberid = int archiveobj.getattr("cmemberid"); return archiveobj; } unarchivelayoutobj(layout: ref Layout, o: ref Object) { for (i := 0; i < len o.children; i++) { child := o.children[i]; layobj := obj2layobj(child); if (layobj.name != nil) { x := strhash(layobj.name, len layouts); layouts[x] = (layobj.name, layout, layobj) :: layouts[x]; } if (tagof(layobj) == tagof(Layobject.Frame)) unarchivelayoutobj(layout, child); } } obj2layobj(o: ref Object): ref Layobject { case o.objtype { "layframe" => return ref Layobject.Frame( o, o.getattr("layname"), s2packopts(o.getattr("opts")), searchopt(sides, o.getattr("facing")) ); "layobj" => return ref Layobject.Obj( o, o.getattr("layname"), s2packopts(o.getattr("opts")), getarchiveobj("layid" + o.getattr("layid")) ); * => panic("invalid layobject found, of type '" + o.objtype + "'"); return nil; } } Cmember.join(member: ref Member, ord: int): ref Cmember { cmembers = (array[len cmembers + 1] of ref Cmember)[0:] = cmembers; if (ord == -1) ord = len cmembers - 1; else { cmembers[ord + 1:] = cmembers[ord:len cmembers - 1]; for (i := ord + 1; i < len cmembers; i++) cmembers[i].ord = i; } cp := cmembers[ord] = ref Cmember(ord, cmemberid++, member, nil, nil, nil); cp.obj = clique.newobject(nil, All, "member"); cp.obj.setattr("id", string cp.id, All); cp.obj.setattr("name", member.name, All); cp.obj.setattr("you", string cp.id, None.add(member.id)); cp.obj.setattr("cliquetitle", clique.fname, All); cp.layout = newlayout(cp.obj, None.add(member.id)); cp.sel = ref Selection(nil, cp.id, 1, (0, 0), nil); idx := cp.id % len cpids; cpids[idx] = cp :: cpids[idx]; return cp; } Cmember.find(p: ref Member): ref Cmember { id := p.id; for (i := 0; i < len cmembers; i++) if (cmembers[i].p.id == id) return cmembers[i]; return nil; } Cmember.index(ord: int): ref Cmember { if (ord < 0 || ord >= len cmembers) return nil; return cmembers[ord]; } Cmember.next(cp: self ref Cmember, fwd: int): ref Cmember { if (!fwd) return cp.prev(1); x := cp.ord + 1; if (x >= len cmembers) x = 0; return cmembers[x]; } Cmember.prev(cp: self ref Cmember, fwd: int): ref Cmember { if (!fwd) return cp.next(1); x := cp.ord - 1; if (x < 0) x = len cmembers - 1; return cmembers[x]; } Cmember.leave(cp: self ref Cmember) { ord := cp.ord; cmembers[ord] = nil; cmembers[ord:] = cmembers[ord + 1:]; cmembers[len cmembers - 1] = nil; cmembers = cmembers[0:len cmembers - 1]; for (i := ord; i < len cmembers; i++) cmembers[i].ord = i; cp.obj.delete(); dellayout(cp.layout); cp.layout = nil; idx := cp.id % len cpids; l: list of ref Cmember; ll := cpids[idx]; for (; ll != nil; ll = tl ll) if (hd ll != cp) l = hd ll :: l; cpids[idx] = l; cp.ord = -1; } Cmember.findid(id: int): ref Cmember { for (l := cpids[id % len cpids]; l != nil; l = tl l) if ((hd l).id == id) return hd l; return nil; } newstack(parent: ref Object, owner: ref Member, spec: Stackspec): ref Object { vis := All; if (spec.conceal) { vis = None; if (owner != nil) vis = vis.add(owner.id); } o := clique.newobject(parent, vis, "stack"); o.setattr("maxcards", string spec.maxcards, All); o.setattr("style", spec.style, All); # XXX provide some means for this to contain the member's name? o.setattr("title", spec.title, All); return o; } makecard(deck: ref Object, c: Card, rear: string): ref Object { card := clique.newobject(deck, None, "card"); card.setattr("face", string c.face, All); vis := None; if(c.face) vis = All; card.setattr("number", string (c.number * 4 + c.suit), vis); if (rear != nil) card.setattr("rear", rear, All); return card; } makecards(deck: ref Object, r: Range, rear: string) { for (i := r.start; i < r.end; i++) for(suit := 0; suit < 4; suit++) makecard(deck, (suit, i, 0), rear); } # deal n cards to each member, if possible. # deal in chunks for efficiency. # if accuracy is required (e.g. dealing from an unshuffled # deck containing known cards) then this'll have to change. deal(deck: ref Object, n: int, stacks: array of ref Object, first: int) { ncards := len deck.children; ord := 0; permember := n; leftover := 0; if (n * len stacks > ncards) { # if trying to deal more cards than we've got, # deal all that we've got, distributing the remainder fairly. permember = ncards / len stacks; leftover = ncards % len stacks; } for (i := 0; i < len stacks; i++) { n = permember; if (leftover > 0) { n++; leftover--; } priv := stacks[(first + i) % len stacks]; deck.transfer((ncards - n, ncards), priv, len priv.children); priv.setattr("n", string (int priv.getattr("n") + n), All); # make cards visible to member for (j := len priv.children - n; j < len priv.children; j++) setface(priv.children[j], 1); ncards -= n; } } setface(card: ref Object, face: int) { # XXX check parent stack style and if it's a pile, # only expose a face up card at the top. card.setattr("face", string face, All); if (face) card.setattrvisibility("number", All); else card.setattrvisibility("number", None); } nmembers(): int { return len cmembers; } getcard(card: ref Object): Card { n := int card.getattr("number"); (suit, num) := (n % 4, n / 4); return Card(suit, num, int card.getattr("face")); } getcards(stack: ref Object): array of Card { a := array[len stack.children] of Card; for (i := 0; i < len a; i++) a[i] = getcard(stack.children[i]); return a; } discard(stk, pile: ref Object, facedown: int) { n := len stk.children; if (facedown) for (i := 0; i < n; i++) setface(stk.children[i], 0); stk.transfer((0, n), pile, len pile.children); } # shuffle children into a random order. first we make all the children # invisible (which will cause them to be deleted in the clients) then # shuffle to our heart's content, and make visible again... shuffle(o: ref Object) { ovis := o.visibility; o.setvisibility(None); a := o.children; n := len a; for (i := 0; i < n; i++) { j := i + rand(n - i); (a[i], a[j]) = (a[j], a[i]); } o.setvisibility(ovis); } sort(o: ref Object, rank, suitrank: array of int) { if (rank == nil) rank = defaultrank; if (suitrank == nil) suitrank = defaultsuitrank; ovis := o.visibility; o.setvisibility(None); cardmergesort(o.children, array[len o.children] of ref Object, rank, suitrank); o.setvisibility(ovis); } cardcmp(a, b: ref Object, rank, suitrank: array of int): int { c1 := getcard(a); c2 := getcard(b); if (suitrank[c1.suit] != suitrank[c2.suit]) return suitrank[c1.suit] - suitrank[c2.suit]; return rank[c1.number] - rank[c2.number]; } cardmergesort(a, b: array of ref Object, rank, suitrank: array of int) { r := len a; if (r > 1) { m := (r-1)/2 + 1; cardmergesort(a[0:m], b[0:m], rank, suitrank); cardmergesort(a[m:], b[m:], rank, suitrank); b[0:] = a; for ((i, j, k) := (0, m, 0); i < m && j < r; k++) { if (cardcmp(b[i], b[j], rank, suitrank) > 0) a[k] = b[j++]; else a[k] = b[i++]; } if (i < m) a[k:] = b[i:m]; else if (j < r) a[k:] = b[j:r]; } } # reverse and flip all cards in stack. flip(stack: ref Object) { ovis := stack.visibility; stack.setvisibility(None); a := stack.children; (n, m) := (len a, len a / 2); for (i := 0; i < m; i++) { j := n - i - 1; (a[i], a[j]) = (a[j], a[i]); } for (i = 0; i < n; i++) setface(a[i], !int a[i].getattr("face")); stack.setvisibility(ovis); } selection(stack: ref Object): ref Selection { if ((owner := stack.getattr("owner")) != nil && (cp := Cmember.findid(int owner)) != nil) return cp.sel; return nil; } Selection.set(sel: self ref Selection, stack: ref Object) { if (stack == sel.stack) return; if (stack != nil) { oldowner := stack.getattr("owner"); if (oldowner != nil) { oldcp := Cmember.findid(int oldowner); if (oldcp != nil) oldcp.sel.set(nil); } } if (sel.stack != nil) sel.stack.setattr("owner", nil, All); sel.stack = stack; sel.isrange = 1; sel.r = (0, 0); sel.idxl = nil; setsel(sel); } Selection.setexcl(sel: self ref Selection, stack: ref Object): int { if (stack != nil && (oldowner := stack.getattr("owner")) != nil) if ((cp := Cmember.findid(int oldowner)) != nil && !cp.sel.isempty()) return 0; sel.set(stack); return 1; } Selection.owner(sel: self ref Selection): ref Cmember { return Cmember.findid(sel.ownerid); } Selection.setrange(sel: self ref Selection, r: Range) { if (!sel.isrange) { sel.idxl = nil; sel.isrange = 1; } sel.r = r; setsel(sel); } Selection.addindex(sel: self ref Selection, i: int) { if (sel.isrange) { sel.r = (0, 0); sel.isrange = 0; } ll: list of int; for (l := sel.idxl; l != nil; l = tl l) { if (hd l >= i) break; ll = hd l :: ll; } if (l != nil && hd l == i) return; l = i :: l; for (; ll != nil; ll = tl ll) l = hd ll :: l; sel.idxl = l; setsel(sel); } Selection.delindex(sel: self ref Selection, i: int) { if (sel.isrange) { sys->print("cardlib: delindex from range-type selection\n"); return; } ll: list of int; for (l := sel.idxl; l != nil; l = tl l) { if (hd l == i) { l = tl l; break; } ll = hd l :: ll; } for (; ll != nil; ll = tl ll) l = hd ll :: l; sel.idxl = l; setsel(sel); } Selection.isempty(sel: self ref Selection): int { if (sel.stack == nil) return 1; if (sel.isrange) return sel.r.start == sel.r.end; return sel.idxl == nil; } Selection.isset(sel: self ref Selection, index: int): int { if (sel.isrange) return index >= sel.r.start && index < sel.r.end; for (l := sel.idxl; l != nil; l = tl l) if (hd l == index) return 1; return 0; } Selection.transfer(sel: self ref Selection, dst: ref Object, index: int) { if (sel.isempty()) return; src := sel.stack; if (sel.isrange) { r := sel.r; sel.set(nil); src.transfer(r, dst, index); } else { if (sel.stack == dst) { sys->print("cardlib: cannot move multisel to same stack\n"); return; } xl := l := sel.idxl; sel.set(nil); rl: list of Range; for (; l != nil; l = tl l) { r := Range(hd l, hd l); last := l; # concatenate adjacent items, for efficiency. for (l = tl l; l != nil; (last, l) = (l, tl l)) { if (hd l != r.end + 1) break; r.end = hd l; } rl = (r.start, r.end + 1) :: rl; l = last; } # do ranges in reverse, so that later ranges # aren't affected by earlier ones. if (index == -1) index = len dst.children; for (; rl != nil; rl = tl rl) src.transfer(hd rl, dst, index); } } setsel(sel: ref Selection) { if (sel.stack == nil) return; s := ""; if (sel.isrange) { if (sel.r.end > sel.r.start) s = string sel.r.start + " - " + string sel.r.end; } else { if (sel.idxl != nil) { s = string hd sel.idxl; for (l := tl sel.idxl; l != nil; l = tl l) s += " " + string hd l; } } if (s != nil) sel.stack.setattr("owner", string sel.owner().id, All); else sel.stack.setattr("owner", nil, All); vis := None.add(sel.owner().p.id); sel.stack.setattr("sel", s, vis); sel.stack.setattrvisibility("sel", vis); } newlayout(parent: ref Object, vis: Set): ref Layout { l := ref Layout(clique.newobject(parent, vis, "layout")); x := strhash(nil, len layouts); layobj := ref Layobject.Frame(nil, "", dTOP|EXPAND|FILLX|FILLY, dTOP); layobj.lay = clique.newobject(l.lay, All, "layframe"); layobj.lay.setattr("opts", packopts2s(layobj.packopts), All); layouts[x] = (nil, l, layobj) :: layouts[x]; # sys->print("[%d] => ('%s', %ux, %ux) (new layout)\n", x, "", l, layobj); return l; } addlayframe(name, parent: string, layout: ref Layout, packopts: int, facing: int) { # sys->print("addlayframe('%s', %ux, name: %s\n", parent, layout, name); addlay(parent, layout, ref Layobject.Frame(nil, name, packopts, facing)); } addlayobj(name, parent: string, layout: ref Layout, packopts: int, obj: ref Object) { # sys->print("addlayobj('%s', %ux, name: %s, obj %d\n", parent, layout, name, obj.id); addlay(parent, layout, ref Layobject.Obj(nil, name, packopts, obj)); } addlay(parent: string, layout: ref Layout, layobj: ref Layobject) { a := layouts; name := layobj.name; x := strhash(name, len a); added := 0; for (nl := a[strhash(parent, len a)]; nl != nil; nl = tl nl) { (s, lay, parentlay) := hd nl; if (s == parent && (layout == nil || layout == lay)) { pick p := parentlay { Obj => sys->fprint(sys->fildes(2), "cardlib: cannot add layout to non-frame: %d\n", p.obj.id); Frame => nlayobj := copylayobj(layobj); nlayobj.packopts = packoptsfacing(nlayobj.packopts, p.facing); o: ref Object; pick lo := nlayobj { Obj => o = clique.newobject(p.lay, All, "layobj"); id := lo.obj.getattr("layid"); if (id == nil) { id = string maxlayid++; lo.obj.setattr("layid", id, All); } o.setattr("layid", id, All); Frame => o = clique.newobject(p.lay, All, "layframe"); lo.facing = (lo.facing + p.facing) % 4; } o.setattr("opts", packopts2s(nlayobj.packopts), All); nlayobj.lay = o; if (name != nil) a[x] = (name, lay, nlayobj) :: a[x]; added++; } } } if (added == 0) sys->print("no parent found, adding '%s', parent '%s', layout %ux\n", layobj.name, parent, layout); # sys->print("%d new entries\n", added); } maketable(parent: string) { # make a table for all current members. plcount := len cmembers; packopts := table[plcount]; for (i := 0; i < plcount; i++) { layout := cmembers[i].layout; for (j := 0; j < len packopts; j++) { (ord, outer, inner, facing) := packopts[j]; name := "public"; if (ord != -1) name = "p" + string ((ord + i) % plcount); addlayframe("@" + name, parent, layout, outer, dTOP); addlayframe(name, "@" + name, layout, inner, facing); } } } dellay(name: string, layout: ref Layout) { a := layouts; x := strhash(name, len a); rl: list of (string, ref Layout, ref Layobject); for (nl := a[x]; nl != nil; nl = tl nl) { (s, lay, layobj) := hd nl; if (s != name || (layout != nil && layout != lay)) rl = hd nl :: rl; } a[x] = rl; } dellayout(layout: ref Layout) { for (i := 0; i < len layouts; i++) { ll: list of (string, ref Layout, ref Layobject); for (nl := layouts[i]; nl != nil; nl = tl nl) { (s, lay, layobj) := hd nl; if (lay != layout) ll = hd nl :: ll; } layouts[i] = ll; } } copylayobj(obj: ref Layobject): ref Layobject { pick o := obj { Frame => return ref *o; Obj => return ref *o; } return nil; } packoptsfacing(opts, facing: int): int { if (facing == dTOP) return opts; nopts := 0; # 4 directions nopts |= (facing + (opts & dMASK)) % 4; # 2 orientations nopts |= ((facing + ((opts & oMASK) >> oSHIFT)) % 4) << oSHIFT; # 8 anchorpoints (+ centre) a := (opts & aMASK); if (a != aCENTRE) a = ((((a >> aSHIFT) - 1 + facing * 2) % 8) + 1) << aSHIFT; nopts |= a; # two fill options if (facing % 2) { if (opts & FILLX) nopts |= FILLY; if (opts & FILLY) nopts |= FILLX; } else nopts |= (opts & (FILLX | FILLY)); nopts |= (opts & EXPAND); return nopts; } # these arrays are dependent on the ordering of # the relevant constants defined in cardlib.m sides := array[] of {"top", "left", "bottom", "right"}; anchors := array[] of {"centre", "n", "nw", "w", "sw", "s", "se", "e", "ne"}; orientations := array[] of {"right", "up", "left", "down"}; fills := array[] of {"none", "x", "y", "both"}; packopts2s(opts: int): string { s := orientations[(opts & oMASK) >> oSHIFT] + " -side " + sides[opts & dMASK]; if ((opts & aMASK) != aCENTRE) s += " -anchor " + anchors[(opts & aMASK) >> aSHIFT]; if (opts & EXPAND) s += " -expand 1"; if (opts & (FILLX | FILLY)) s += " -fill " + fills[(opts & FILLMASK) >> FILLSHIFT]; return s; } searchopt(a: array of string, s: string): int { for (i := 0; i < len a; i++) if (a[i] == s) return i; panic("unknown pack option '" + s + "'"); return 0; } s2packopts(s: string): int { (nil, toks) := sys->tokenize(s, " "); if (toks == nil) panic("invalid packopts: " + s); p := searchopt(orientations, hd toks) << oSHIFT; for (toks = tl toks; toks != nil; toks = tl tl toks) { if (tl toks == nil) panic("invalid packopts: " + s); arg := hd tl toks; case hd toks { "-anchor" => p |= searchopt(anchors, arg) << aSHIFT; "-fill" => p |= searchopt(fills, arg) << FILLSHIFT; "-side" => p |= searchopt(sides, arg) << dSHIFT; "-expand" => if (int hd tl toks) p |= EXPAND; * => panic("unknown pack option: " + hd toks); } } return p; } panic(e: string) { sys->fprint(sys->fildes(2), "cardlib panic: %s\n", e); raise "panic"; } assert(b: int, err: string) { if (b == 0) raise "parse:" + err; } # from Aho Hopcroft Ullman strhash(s: string, n: int): int { h := 0; m := len s; for(i := 0; i