From 9cd9873e207b5efbe021e5d413ef54da0ed5cc7a Mon Sep 17 00:00:00 2001 From: Serge Bazanski Date: Sat, 13 Apr 2019 17:09:11 +0200 Subject: [PATCH] code dump from kkc19 --- client.py | 23 ++ default.nix | 20 ++ example-round.js | 55 +++++ jeopardy.py | 468 ++++++++++++++++++++++++++++++++++++++++ static/favicon.png | Bin 0 -> 37330 bytes static/main.css | 189 ++++++++++++++++ static/main.js | 442 +++++++++++++++++++++++++++++++++++++ static/socket.io.min.js | 3 + templates/index.html | 21 ++ 9 files changed, 1221 insertions(+) create mode 100644 client.py create mode 100644 default.nix create mode 100644 example-round.js create mode 100644 jeopardy.py create mode 100644 static/favicon.png create mode 100644 static/main.css create mode 100644 static/main.js create mode 100644 static/socket.io.min.js create mode 100644 templates/index.html diff --git a/client.py b/client.py new file mode 100644 index 0000000..aecb3b5 --- /dev/null +++ b/client.py @@ -0,0 +1,23 @@ +import asyncio +import socketio + + +sio = socketio.AsyncClient() + +@sio.on('state') +def sio_state(state): + print(state) + +@sio.on('identified') +def sio_identified(msg): + if msg.get('identified') != True: + print('COULD NOT IDENTIFY') + return + print('Identified.') + +async def main(): + cl = await sio.connect('http://127.0.0.1:5000') + await sio.emit('admin', {'password': 'changeme', 'set_role': {'who': '716a784c', 'what': 'HOST'}}) + await asyncio.sleep(2) + +asyncio.run(main()) diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..6c7a762 --- /dev/null +++ b/default.nix @@ -0,0 +1,20 @@ +with import {}; + + +stdenv.mkDerivation rec { + name = "venv"; + env = buildEnv { name = name; paths = buildInputs; }; + buildInputs = [ + python37 + ] ++ (with python37Packages; [ + virtualenv + pip + flask + redis + flask-socketio + eventlet + ecdsa + python-jose + aiohttp + ]); +} diff --git a/example-round.js b/example-round.js new file mode 100644 index 0000000..cf0738a --- /dev/null +++ b/example-round.js @@ -0,0 +1,55 @@ +{ + "type": "simple", + "categories": [ + { + "name": "foo", + "questions": [ + {"clue": "foo1", "answer": "foo1!"}, + {"clue": "foo2", "answer": "foo2!"}, + {"clue": "foo3", "answer": "foo3!"}, + {"clue": "foo4", "answer": "foo4!"}, + {"clue": "foo5", "answer": "foo5!"} + ] + }, + { + "name": "bar", + "questions": [ + {"clue": "bar1", "answer": "bar1!"}, + {"clue": "bar2", "answer": "bar2!"}, + {"clue": "bar3", "answer": "bar3!"}, + {"clue": "bar4", "answer": "bar4!"}, + {"clue": "bar5", "answer": "bar5!"} + ] + }, + { + "name": "baz", + "questions": [ + {"clue": "baz1", "answer": "baz1!"}, + {"clue": "baz2", "answer": "baz2!"}, + {"clue": "baz3", "answer": "baz3!"}, + {"clue": "baz4", "answer": "baz4!"}, + {"clue": "baz5", "answer": "baz5!"} + ] + }, + { + "name": "barfoo", + "questions": [ + {"clue": "barfoo1", "answer": "barfoo1!"}, + {"clue": "barfoo2", "answer": "barfoo2!"}, + {"clue": "barfoo3", "answer": "barfoo3!"}, + {"clue": "barfoo4", "answer": "barfoo4!"}, + {"clue": "barfoo5", "answer": "barfoo5!"} + ] + }, + { + "name": "barbaz", + "questions": [ + {"clue": "barbaz1", "answer": "barbaz1!"}, + {"clue": "barbaz2", "answer": "barbaz2!"}, + {"clue": "barbaz3", "answer": "barbaz3!"}, + {"clue": "barbaz4", "answer": "barbaz4!"}, + {"clue": "barbaz5", "answer": "barbaz5!"} + ] + } + ] +} diff --git a/jeopardy.py b/jeopardy.py new file mode 100644 index 0000000..85dfba1 --- /dev/null +++ b/jeopardy.py @@ -0,0 +1,468 @@ +import secrets +import os +import logging +import json +import ecdsa +import binascii + +from jose.jwk import ECKey +from flask import Flask, render_template, g, session +from flask_socketio import SocketIO, emit + + +app = Flask(__name__) +app.config['SECRET_KEY'] = 'secret!' +app.config['ADMIN_PASSWORD'] = 'changeme' +socketio = SocketIO(app) +log = logging.getLogger() +log.setLevel(logging.DEBUG) +log.addHandler(logging.StreamHandler()) + +log.info("starting up...") + + +class Board: + def __init__(self, type): + self.type = type + self.categories = [] + + def add_category(self, c): + self.categories.append(c) + + def serialize(self): + res = {'categories': [c.serialize() for c in self.categories]} + return res + + def copy(self): + r = Board(self.type) + for c in self.categories: + r.add_category(c.copy()) + return r + + @classmethod + def unserialize(cls, j): + r = cls(j) + print('UNSERIALIZING ROUND', j) + + if 'categories' not in j: + log.error("No categories") + return + + for cid, c in enumerate(j['categories']): + if 'name' not in c: + log.error("No category name") + if 'questions' not in c or len(c['questions']) != 5: + log.error("Invalid question count") + + cat = Category(c['name']) + r.add_category(cat) + + questions = c['questions'] + for qid, q in enumerate(questions): + if 'clue' not in q: + log.error("No clue in question") + if 'answer' not in q: + log.error("No answer in question") + + value = (qid+1)*100 + ques = Question(value, q['clue'], q['answer'], c['name'], qid, cid) + cat.add_question(ques) + + return r + +class Category: + def __init__(self, name): + self.name = name + self.questions = [] + + def add_question(self, q): + self.questions.append(q) + + def serialize(self): + return {'name': self.name, 'questions': [q.serialize() for q in self.questions]} + + def copy(self): + c = Category(self.name) + for q in self.questions: + c.add_question(q.copy()) + return c + +class Question: + def __init__(self, value, clue, answer, category, qid, cid, answered=False): + self.value = value + self.clue = clue + self.answer = answer + self.category = category + self.answered = answered + self.qid = qid + self.cid = cid + + def serialize(self): + return { + 'value': self.value, + 'clue': self.clue, + 'answer': self.answer, + 'category': self.category, + 'answered': self.answered, + } + + def copy(self): + return Question(self.value, self.clue, self.answer, self.category, self.qid, self.cid, self.answered) + +class Compaction: + def __init__(self): + self.ct = 0 + self.board = None + self.roles = {} + self.identity = {} + self.contestants = {} + self.question = None + + @property + def buzzing(self): + for _, c in self.contestants.items(): + if c.buzzing: + return c.identity + return None + + def feed(self, entry): + log.info("Compacting {}".format(entry)) + if entry['event'] == 'new round': + self.board = entry['round'].copy() + log.info("LOG ENTRY, new round") + elif entry['event'] == 'role set': + who, what = entry['identity'], entry['what'] + self.roles[who] = what + if what == 'CONTESTANT' and who not in self.contestants: + self.contestants[who] = Contestant(who, who[:8]) + elif who in self.contestants: + del self.contestants[who] + log.info("LOG ENTRY, role set, {} is {}".format(who, what)) + elif entry['event'] == 'pretty set': + self.identity[entry['pretty']] = entry['identity'] + log.info("LOG ENTRY, pretty set, {} is {}".format(entry['pretty'], entry['identity'])) + elif entry['event'] == 'question': + cid = entry['cid'] + qid = entry['qid'] + log.info("LOG ENTRY, question activated, {}/{}".format(cid, qid)) + self.question = self.board.categories[cid].questions[qid].copy() + self.question.losers = set() + for _, c in self.contestants.items(): + c.buzzing = False + elif entry['event'] == 'buzz': + if not self.question: + self.ct += 1 + return + log.info("LOG ENTRY, buzz") + who = entry['identity'] + if not self.buzzing and who not in self.question.losers: + self.contestants[who].buzzing = True + elif entry['event'] == 'answered': + if not self.question: + self.ct += 1 + return + log.info("LOG ENTRY, answer") + ok = entry['ok'] + value = self.question.value + who = self.buzzing + if not who: + self.ct += 1 + return + self.contestants[who].buzzing = False + + if who in self.question.losers: + self.ct += 1 + return + if ok : + self.contestants[who].points += value + self.board.categories[self.question.cid].questions[self.question.qid].answered = True + self.question = None + self.ct += 1 + return + + self.contestants[who].points -= value + self.question.losers.add(who) + elif entry['event'] == 'give up': + if not self.question: + self.ct += 1 + return + self.board.categories[self.question.cid].questions[self.question.qid].answered = True + self.question = None + elif entry['event'] == 'set name': + who = entry['identity'] + name = entry['name'] + self.contestants[who].name = name + else: + log.error("Invalid log entry type {}".format(entry['event'])) + self.ct += 1 + + def serialize(self): + return { + 'board': self.board.serialize() if self.board else None, + 'roles': dict(self.roles), + 'identity': dict(self.identity), + 'contestants': [c.serialize() for (_, c) in self.contestants.items()], + 'question': self.question.serialize() if self.question else None, + 'losers': list(self.question.losers) if self.question else [], + } + + +class Contestant: + def __init__(self, identity, name): + self.identity = identity + self.name = name + self.points = 0 + self.buzzing = False + + def serialize(self): + return { + 'name': self.name, + 'points': self.points, + 'buzzing': self.buzzing, + 'identity': self.identity, + } + + +class Game: + def __init__(self): + self.state = 'IDLE' + self.log = [] + self._compaction = None + + def start_round(self, path): + with open(path) as f: + j = json.load(f) + r = Board.unserialize(j) + self.log.append({'event': 'new round', 'round': r}) + self.save() + + def activate_question(self, qid, cid): + if self.compacted.board.categories[cid].questions[qid].answered: + return + self.log.append({'event': 'question', 'qid': qid, 'cid': cid}) + self.save() + + def set_role(self, pretty, what): + self.log.append({'event': 'role set', 'identity': self.identity[pretty], 'what': what}) + self.save() + + def set_pretty(self, pretty, identity): + self.log.append({'event': 'pretty set', 'pretty': pretty, 'identity': identity}) + self.save() + + def answer(self, ok): + self.log.append({'event': 'answered', 'ok': ok}) + self.save() + + def give_up(self): + self.log.append({'event': 'give up'}) + self.save() + + def set_name(self, who, name): + self.log.append({'event': 'set name', 'identity': who, 'name': name}) + + @property + def roles(self): + return self.compacted.roles + + @property + def identity(self): + return self.compacted.identity + + @property + def compacted(self): + if self._compaction is None: + self._compaction = Compaction() + + if self._compaction.ct == len(self.log): + return self._compaction + + missing = self.log[self._compaction.ct:] + for m in missing: + self._compaction.feed(m) + + return self._compaction + + def save(self): + with open('jeopardy.dat.new', 'wb') as f: + for l in self.log: + if l['event'] == 'new round': + l = dict(l) + l['round'] = l['round'].serialize() + f.write(binascii.hexlify(json.dumps(l).encode())) + f.write(b'\n') + os.rename('jeopardy.dat.new', 'jeopardy.dat') + + def load(self): + if not os.path.exists('jeopardy.dat'): + log.info("New game...") + return + log.info("Loading game...") + with open('jeopardy.dat', 'r') as f: + for line in f: + line = line.strip() + entry = json.loads(binascii.unhexlify(line)) + print('RESTORING', entry) + if entry['event'] == 'new round': + entry['round'] = Board.unserialize(entry['round']) + self.log.append(entry) + + self._compaction = None + + def client_state(self, identity, admin): + role = self.roles.get(identity, 'SPECTATOR') + return { + 'role': role, + 'admin': admin, + 'state': self.state, + 'compacted': self.compacted_filter(self.compacted.serialize(), role, admin), + 'ct': self.compacted.ct, + } + + def compacted_filter(self, d, role, admin): + if admin: + return d + if role in ('SPECTATOR', 'CONTESTANT'): + del d['roles'] + del d['identity'] + if d['question']: + del d['question']['answer'] + # filter out answers + if d['board'] is not None: + for i, c in enumerate(d['board']['categories']): + for j, q in enumerate(c['questions']): + del d['board']['categories'][i]['questions'][j]['answer'] + + return d + + def buzz(self, identity): + if self.compacted.question is None: + return + for _, c in self.compacted.contestants.items(): + if c.buzzing: + return + self.log.append({'event': 'buzz', 'identity': identity}) + + +app.game = Game() +app.game.load() +#app.game.start_round('questions/final.js') +#app.game.save() + + +def emit_state(): + identity = session['identity'] + admin = session['admin'] + emit('state', app.game.client_state(identity, admin)) + +@app.route('/') +def view_index(): + return render_template("index.html") + + +@socketio.on('connect') +def sio_connect(): + log.info("Client connected") + session['identity'] = None + session['admin'] = False + session['nonce'] = secrets.token_hex(64) + emit_state() + emit('nonce', session['nonce']) + +@socketio.on('ping') +def sio_ping(): + emit_state() + +@socketio.on('proof') +def sio_proof(data): + log.info("Client attempting identity proof") + if data.get('nonce', '') != session['nonce']: + emit('identified', {'identified': False}) + return + + # ,___, + #jwk = data.get('jwk', {}) + #k = ECKey(jwk, 'ES384') + #proof = binascii.unhexlify(data.get('proof')) + #if not k.verify(session['nonce'], proof): + # emit('identified', {'identified': False}) + # return + + identity = data.get('identity') + session['identity'] = identity + app.game.set_pretty(identity[:8], identity) + emit('identified', {'identified': True}) + + emit_state() + +@socketio.on('admin') +def sio_admin(data): + log.info("Admin connecting...") + if data.get('password') != app.config['ADMIN_PASSWORD']: + emit('identified', {'identifier': True}) + return + + emit('identified', {'identified': True}) + session['admin'] = True + + if data.get('set_role') != None: + who = data['set_role'].get('who') + what = data['set_role'].get('what') + app.game.set_role(who, what) + + emit_state() + +@socketio.on('host-question') +def sio_host_question(data): + if app.game.roles[session['identity']] != 'HOST': + return + + cid = data.get('category') + qid = data.get('question') + if cid not in (0, 1, 2, 3, 4): + return + if qid not in (0, 1, 2, 3, 4): + return + app.game.activate_question(qid, cid) + +@socketio.on('host-set-role') +def sio_host_contestant(data): + if app.game.roles[session['identity']] != 'HOST': + return + if not data.get('who'): + log.info("Who?") + return + if data.get('what') not in ('SPECTATOR', 'CONTESTANT'): + log.info("What?") + return + app.game.set_role(data['who'], data['what']) + +@socketio.on('buzz') +def sio_buz(): + role = app.game.roles.get(session['identity']) + if role != 'CONTESTANT': + return + app.game.buzz(session['identity']) + +@socketio.on('answer') +def sio_answer(data): + if app.game.roles[session['identity']] != 'HOST': + return + + ok = data.get('ok', False) + app.game.answer(ok) + +@socketio.on('give up') +def sio_give_up(): + if app.game.roles[session['identity']] != 'HOST': + return + app.game.give_up() + +@socketio.on('set name') +def sio_set_name(msg): + if app.game.roles[session['identity']] != 'HOST': + return + app.game.set_name(msg['identity'], msg['name']) + +if __name__ == '__main__': + socketio.run(app, debug=True, host='0.0.0.0') diff --git a/static/favicon.png b/static/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..ee2af6c323c726b1208d8114f8d5f1f50b2aa0f8 GIT binary patch literal 37330 zcmb4q^;=cX7w$d*9Qx1=2c$%#OS(%CB$V!M1*GB7Eva<3NH-D(K|)DEIt8S=`||nT z=l%ir*?XSZ&-^emYu0*a)_P~{NcC3=xLA}}007`BDavX90O;`(1i;WAD}(o?R*x0B z*-Hgk;Nia~x4k6ku?5pbQP&j!IG+4hK%PYsA08Vq+>})1Fjk>hxM*0KGT4a6CJHw> z9XAMq^kZl5RVK15P*`bl$Pi0Zo5~#!-h89<=m3n zaM>4;j*G&Q5--`$Ar@3}0<~QxRMpiWZX({)S46@Kqi!stwykx-ffmmX8EnD2ZnfUe zb@F7A08_NzfrT>QkYf5+++K1>?{S2M?s&$2ySDrARoFs+?^4?Kn*~7|Z?!LKK|W4}8!u2m2c-`Fib4suD= zc|T5f+pu)^L*m(#wF54&bB3k6bP^zX4bkOz_&7`=ZNkMX9f3eFNhyOEzyx{e`qMuH zP9$zW8((ki@Lp^iynpQd#g5^_g3QwURr`+*LNJ%%?%DknKS>x4LlB$*n*5oo<$6}o zcYTBUX2JNjE}%&~*dEZSDtGf6Ynpf7_aBdakfP+Kr1ayvg_A=imC%Za3*i+HCH21D zsuzYQm*M`aNde8`NmZ3hlO@ZaYF;hYoJ?8pt)H9Bdka{m&7G$ zgLuaO)(j5HKcf2wCdZ+pZu3}%Gbkw%i-F4Mi$DobNjRKE@FU5NkNKk(-AO9ya4`@O zpToe^;5LuLU5q>=oKS;RaqOpb2L{T*Px*gBj9eQ^GXRm2FDt)ZfF^Z4oxA7}QP5H* zv`}?8qa^CWzb{Urwq3mFe`8zU>HR*HWM={a{{Z{vnXZu-%fvh|#WF-uQ0^w0O5<_T zj%MqB%k4dRb1jNQ@c<@52PTFzEyy?QvT#QF&@e?)fS}{HbB7&Jtr{1S8SIWHe);4(@EzjZ ze%2vEE84&T*ZZ$$rX87BZcc2F89BSw?HG%f$ezunTc-P*;N(1Y-suM8Jt%9_*L==h89$C-Ti`IF$V~;; z5J0>k`d|e7a6s(=`^Gsl|5+GG^WsVO0l9&UeybXji1;kFN!Uf@2{?7m+bn?%Y90qH zKR@UQzy+#C8!B)7)261{_BaTzPXpV3*J zFyt0dfZpOu+@yfaL~ky=hFPN02eThBPkI9Zt9rR?7D2vE^$bf9YAAXVP7A*ly)3Tk zWO>L6xGLG9XZX*kHtJ}-Hm$mqRVv$^RwrtbsH(z08$+2EVRBW03CPb#M#;!a_iI57 zXd)O6CFDEZA0PJn+v~`H>9SmkURbcv(Zhx6_DVSgRB0r&(6E%hNahPdn$b`SlW$%A zg8{|P^QunMlSQ9OzdM^JK4j0XKJ~9<^^@d6bqEo^yA{ht= z6{aExhNLP;893xcv%o4uL;UzbaZ3mQ#!w6-2hC<%C_Y}4`wl-J(|1NjR=88^MS8gZ zhVlK@NJZ85Gy4i_NC#{uTmQqK8lt3(MVE)Ns-EJj55(6X#W6}zY(D;P$VACX$tP$D zCtj!^C_(db9e0&;P3J2^f=vm;kETH9A6hfg}t79UFZJ)M zLfRp7Ae+=k2sm5Dw^`zDK`s1@rXC$x{(iytz0AD($^O99qQ^0|*Ru!Owd<2PLnHOs zvx1{KL_A@xbg%%9B4^I;$-REVv?`mIcZ6i|u(o*gp!j3BFiJS?OMUfZm8jKI#wg#A ziXlf8`3!YsK^O4B^PhH6KWTF_r>dE%qL_sX)Uwfp+8ZC{SD*c!!Wq>>_!6VSr97P; zT4ppKF4Q_6UI5mTo`fhOz$)kdaNOSH5jdZ|bzEaRrOm&aJNo9u`#`Hw>l{j`ln`9M zQ$mv?jE|J3G~?Aby`A{5L%=}kM+fzmPmZZggF&lZkZvxEd0*JKz4-w+=9=!up;$+U zSB<%!Y?tPI!e@uLs_@i@o=p^Cgs+E<-~yiL(!dSC=h#Qyz3Mpp^56wwhS&ki{q9J_ zQ8dDre7WR)d5YHOpG@afr1h6Hv8Dj;M)VMVpzYAUJ`EE<59cQYNv7^;pZz`XT_szp z@fly368))vrPdIZ<7D$$(b)X(KpkwBPtQDq)qOzlE`Mqb;wF3Y(Co_V1%2IpMGl^5 z5CH=)c`65$#_EO|NWsZ@yR=JnrL-=ciFi&P2Tc&wd)UBP1p0xPiP-cN3|i3$RJ?0y zlYcPaGgYM{zebL>_Xcm$iJsr6-Ig2Q3|xqTmXQ<~CwF$Z==Ol8>Ey-TuHXWA8JpnS z${Hrj4q!rdB9_Oa<@K9Zs z3rr1I#Hme&FUKLE00LN1(UimbTH2@JHxM8NsVGNq;PayQ;gW^wDEAh8Ozo+;|G5|uJ$(VovCGKSB=E6fLyuS{t zPc@esBRKw5QrTofedfg@j^2wah+V)YyVag)sb$OG&D0%TRLumjs@S&$)A&V-(9xvH zy`=*pF9jKY;3(DITquqFp`+f&7P~BaZ`65VeP0srP}AU5_2_LM4j=gI5SyRiPAxW` z#6`vKLfc_<84$=P8KKgyBKw8=z?CK=Dc=B7#)*E(G@;}I_bVR*lqC%|xe6X0)3&fT zK+o>+uxg4$o@DA;8yY(xP|vgXuE)(JvNqdHJR@(O^R<`bk`sLFB|7vH%k8ZdaT;4zEo$sw-kQD)DjM8n{>L5!(Hab(fKP|U-&Aisaq{TT5GScRng4~Z0J z+y?n<$#|LCm4Ec;>S*#monRCm0Z&2iK;KUoCTn(@)*j3KMf>=86^{6A?8U>jf_SUI z)!Sx_!{wAEzoEUVZ}%B=o;!OFl&F_;!(WuFm6WrAK*$pz`NR3*-sVUfOhkfN08?x8 zEgA|`EQBW-0??T~1Z=mgi*br}l)%+Bp72s6Yshe!p}QjS@y=QxS~)MMYE)nm48hda zS#A_r(no*)4r^`X%0US$5?Q1;b&%9^+oaR*!oZp;N=#*lB zD^d-`%XPg%R)c}m=yyXeQn^@_*GGW@#ddx1ZstMiBJsrWNz^?0Kn&^MH~JE1oo{RK zg6gAwsL65)iCDhpZ#lb9TJqkxoAUc2S}Jiq6PHst_3r@-gfyRW{?hb7hA)1Z9`1aP z3GcZ6nfo@$MFBHHMgHwHTJ;&MA#Q{NDb~jos8Kz57jH&iTl6vnh?1vD#27_;c#gYt zmlWX}!i}y+2^(uZb}RyceVS{7#3bZ|Sf!MuRo};JJ%f1*^A+C0F`uIK+o%^8+^BIf zbE*QHV{=A6+gme6XVaQFLcGQhly--JTFf7^fRmji&nX$fdlA6H=rzVv&yt3u4Q8Ij zDY}7VH7>bO$nUPVq`NZSE+r)r-+$?xJ4+X;O9MD@$k9Y(#lrY^=9jO-5l;yN_0o*m zLBb4h+Kx)o3+rm@5=4?4j`ZozxSHIe+%RRK-3Ou{h7wa1I}d~v3QhqFW$jn~XVz+d zw}X#?~S*{`$PN(~iuhI1~ ztVE{~g_3_k^IgRBcT-yXOcMAakRHJ8U#5ii2>&a{}U91nRm4J z^!>%c$GxD?GxuL0oN~K)t| z^z|CzYbLh`GR|qToYp&?GoOd4ocnW1pfd5yQR39HFK(&%u2752V;n{}s`q4EcrSTu zG&Cmcm2@FElaKyw|0WaPc3U>_r#RM#6dsuby1w4tv(%WXAEiQJ3`=1ea@0dKt+cn& z#e_QYZYKqyy*k1;<-5~NT!*w>a-2x9rjghHx*I?qpVh+Si#|N22frg z!YfrvHl5Al`RS<+mxD^#cmf_PZ!K&L5Bd|$GUW)k3SCA5BI%@Jm(5}$_W?&$^ zt9y#H^2xJtFsupd? z245+u=LM=LsO`vs0J~{g1J&$(!NFAORzB)Gax4fqjByM!Yo3Dk%UBQGaAd-gtef zt0D;6(e3~_Kz#R>9VKA{%iOzPx+Yh&cITGJ4>_EtJd8{_S_wR4J6!!1!A0z0pPAZR4Bx=66 zoa3LoJ&q&**9ZzlCWGh|FO!SStzfTB_+~=~1Q3Nr*MbqmG1))?IabK>itl60rE7E8 z)BVam8H1?}o4~{Z!t?ZHARf_j0U3%u42#DFhsXd**ihCLD&g%@yy9iEC5~5dxuFi1 zglZ6)C4-)zRV5vBd#TNzGUMQ2E%2hzu7PE%hC2fsV!1p?%15iP3Olkv?c$NDI>G*& zp&tVxh!P6q^B*XhUa2%1-7tCF${w!lEIyRey|c$(#?t*(A}|yjH-ATh-*$FR_wB31 zXwTGLrEv45#+>-O$-V0YiFoPnso>root)q5L;Ibk+o}-}EyF1;9orv+VU1tk9qxj@ z=+f|l8iMOUU40ngg`4KG7oC`B&8}`QpqI0KTu;oaBj{yHKsq02TRFZ<_qyfBzYnDr z^kx~>OSX)RXNnE}4A<*9x6(s?l2kXO*DC*o^YnB;g`MtAbe{ZDvfLa+BlXR8HTl+4SgB;WYW2mwB^OZVlK3>9q+hKC73PNdcGkaej|M^xJ;#@u{T?uPUFL zEQ#AtNt)?^96&ME4+xW zP)BtT4exL`KXV9V#Y&IJh-WRMpeO@TpxK;JA9*rdAe;)JMv%P>H{V9n&|r9-aUXFcl`M)Zd}4#gYGsl> z&?8eoCWgpe9Den*kqnL~-cKXS!yXF)VZMN(mE{&HWyU&4Np27}wV$^xSc|)#FJ=2& zqpCp5mU^od2J_b~Q+_9B9$A<7QuDLCw*vB{33vOjRwJ3HKoJ4pwMNUYh{WU_ZBqb=fZJ5smUgv@fneV!NB|v&7#Eo`<2gMoB6F@9tOG!;bby z4GS>bB&MgdKi-aeYKDqU| zEolEpY>a|`Qs&pf8-iUx47m`<_I+g`Ojyq$#<>6Ao@9&JalW`40 z-m`S*?QK=Z#l%j>Y2^i;;h|9jjr*X0x1bv@;)y_64EpcF@8l6zr|{*V@KYrMR`Y#{ z)-&K;%1Nh9Rc=M5xl25(0|>{#K?}m@rhBs{B^8BhQv-<>+k6-1`y@C^`%t`SgHg&3 z^yz7;w3IdBprn_{>+EMMU*#G_0%zX9+B5SVXQc#bRBMN99qr=U3I4r~H zRlN-gY|dn+VP9DwUv%p70b^iP?>KETirVo3T|N_*ky}?!&xiNAwB6Ub#&1Y7(34i5 ztPw%V!uog?2WnE3s57&>Fg_YcZ6)kcTJ*UZXpH%EN|tVMwDIVFhX=(w#;B{k6tb#z zJ$yQ!xAv)_3NNPCEqIpM`B^+KT)xQFIX79!Wpu+%xGB6MbSbAhYcW`)J=}#0?F;1^ zH55b9O8w_4ym_yijVJ=Eg1+~Oi3eHpzFtTcjf;`nd@K;76|W%$3$*px@4dJk6`{RP zdstt3IDYh&PomiAI?Q~){#OBay*K-JLHZBPohO1{+8WiwtA%X;jlTMWX$#Ov_hnWg zE<@l-cEZ}7 zf26U{^<9-@BjZVWMP3vV9)6PHZ(tR`iK@WSz|FAWE6ni#jh`_6Y^PKS8KP>ecD=zn z-lL`uoF_C8b-Q}14YjMtbH`(Pvw7k(9v}@%%@LDqkO-|{^L+c{5^!XUGLW&yJb--p zX(~`fZy{PT+{;`fO|?0I9yLJ9!y86ie6mSsip8}IzIFR}J>y!M^8(_r4;cJ`cgeWS z?=kT*0)eM~C-V>xp;BNP#o@WY_cG1HDWhO@;=K)vp2N{l%DQ7wJHb9T=j*VCz0T{R ziwZGhip#=%>rTMh=rCug>sVQn#Ms}3A+d|5E#ConCgpoVx-a#PNQMyB7DqHm4QZu> zwXS@UbUqQPZ#@$!q;;;EnE~GTHzn4od!HWJ5?@D6ep;4KrirxnxRyBNpvBMR&d(gO zQYE)^`n!p6c_*JvjmVIRB}CcCQ1-4}t2$)P%D!`eek%W9G5wkr=eZWi`E=JXWSJI% z3h$D2wu*0wk>s-E>eCHz-=l|iT_L}G;OaH0C9%ez)Q;WiA|C#6C?zR9fWuRUUncY7 z5k+FIw0|_MP1JJ2bA|)!R|Ug3y~F6}=&x|+_jdzfg)k|wEMDGxxX1;c!>~&mh%}i; z&hUij;CZUpBjcgau}asmBjSHt_3-mq<@h0q64?LlB48}I&w2hnK|pX}|JIC)GMcn? zmfgdl`PZqyA}We>#;^663vo=(XFYMhEZ{p@NU?*5QTNbuUt5B&;eeDqC`rlYIRc96 z>e8oT`qmg$Zr}VxRxGE0V`DTF6`9e-mZKAnc54WC zjfu@@tZ^R~kRP~IQqLTsxXjlmifwD52W3o5Z-1Z_Mc~P?x#A5sd58K>f*HAH(hgT05P#v+ zhwe5mPVDjKjm)ckeF6Wr{^{vnEJvs5MPyHI*}iG_Iz0i#KF$jSNYUQ@m;~xuc3nd#JlRQqH8}H#;o^lS7lJVwel|#m4#1hP!pK3lzv>8&^fa z%r3UCB|PxfSJevqxA|_bAR)8hF37HA7q}}Z{a{8;eHmathXxaagg+WuKk1SWj13tf z_O&umpx7-$KIeggLMCKtp86Q^pq=%2K6{uIF$(C$xZe~MKaYH*gcWzh4*b^kv+M=^ z-MELQ68xikbKdU`Y-SjWg!IOq91gi2CSWL9ka#1wnit)KI`9R0YUh7`*B)}(pEKmV zAbjdSi*eWRD~TnRs+`fRa)mks%wOJ_P3iDU7E-jR_z+@ zZD%S^nIU*!vO2+B7-`yFyXVb4(n&sruZ2{aArIx}BNvTYmml*f(D|n#-E*?-m#Bno z;f0<=!Nu#~xH3NC;Apx-sX=LR#T>(Th{5!wBwOa{QuC3%lS}>dZW7_+QZ31#9qGq- z{dl^=-^Cm>02G+ynB=&_#6siCDo-rX#YdLqU(ENyoD2{)B=+Q+X8k;rsvn}9+wRV$ z0+wYukB<5#fkTF`f_`Uy2Sf7z&SWDBUu%AxBf6;HE5yKkmQ+sbyQ{bAH`o>4anzTC zfd|8#y^zdRtL4{hGM{nG3?p)AniCW;IK&r5S>BSx@c&jAX6Lh$=oB<6<$A%W>LvZ4373N@BRC>u>)a?k&l z3m^iPYhi!AgzzkD$M2%wfaNuTKxqj7R8?UGM;C_CI;BIzOp^Ml@FttXAh zu1eHlR61&i3x}A18uwl;v0Nzv(l6tchU9p0E!f^r3iResT`~mIkhvrg+_EZ+vc=MT zi$B7bR^gv{+Oai^;q19YcX!<<-uMVA7rzUMtMt92S4Ss``=u&JmP?l{CGyx}-5vw| zSr;zt2Yz-@6dK&>_NRl8+Hzw_Y1!ANQwK{a@`gl$Jvwh4eiv1iR-|2YU|_{=MkF95 zrOHx&c}#Y_!TTeHYwQD)8BGfs;92_t(~!%a6nVU3MUa1q=h8=u+PkgJY*E0YwLDZ^ zth&)vxzuxu78R)Y%$OtTUbBO`!7ZF z$qj+ciup=%RnpM~4xR00Vxi9KRGb_hB3ub)9OEa%wo^I8E(Kt5K@;~!?{aLDY<=91 zqQ~>wW$(8o&vEDbl>pCO&PiP)nGN^l*gq@M3I4yi{xM61xkCGE;uO9+v*C-w!-f$q zLb&o3Ut_tcO_zS!Yj(#9)hIXGqKXxZKG6vZVil}o`C6{xSvziTg}-5k0J@bmHDb~* z(9K5iBQ8$VXc5gonXLYYXP@L9pG|0E;?Zgll&YIpzs}B)Awy@=L>e&bFh z;0gS=6y@`$82@6SHNO`NeTF&tt{JvNeiE+IO%)u!HN0S~5=forDP*?wNHpFZ2VCJV zUj56t?l8XVrv$P$g#EieBc9&dj_19JSV0arb$+ztJuu|C?QPh2<de9_C&$ER5jWSvVj@Fk%B1U{m#_Rl)Wl;meg6v^#iPZe;;pO(|0A5+HW%$Fn zO=j00JX1=zsQJSdg%&}E_oOz>2KpCI9JaIDli_ZW@GZ@o_)z&8-fcz6vFL?3yDACR zyuE%=_Ep)>w^YWD6vOBEG<0o#I5=5@+&QMErUsPsgWIgjXHX^Vu3aqIKTJ=*RQ-u( z_%aQ{5QTSd&hb^E*&oy5t`=|%dVR!CaVCNoFcof&J=7eY4E?p<4rE(j^qFf!1~mHE zo%|XRZMm2ihBgoK&kpu@++V)Uy)wkxA!f3qya6h6ep)EYx`1EP5l8N?dk(8m4C#Lp3OV z&QP!4TQW!e5kbuxrd&Q^RGpQy%))=~0 zt@TcvOq*@N6-jMPUnL{F{Kt6xrI4dw20;`nq?bZHmH5Qo#R(pT5=U6*QUAi`em$q36HtDo<0hDoo3Dh#48BtkHFiknMfo@- zRzK%bORUN1@xrgU6Ykk?HB#ps^PQW|MxF zUBgW=w`TTTb3nd5@J$8SS@+ z^pbnp@ND`Qg-$Ba1bdUjU}i5Uk|EJI^YJt&zK|~N(hDlug__A?tK4H(`Kw)w3AX=J zZMwZKk+_{88b4Xr))c#}bp=?QW(8LFcJI$+qd5M>In~io1{})B(a{b4Ro?y+fUhl! zoj>(sfx5Z_{jFxHI<6F;8eNl&eySS9WrY*8+PE7FmHLP7Z2>SzQX&(%uE?jy4tL2e zgW)eg8DU|~@xma5?A6mP*Zy;UW~wI+4o(boak+)ECcPGoktfz8CIWN8Vx@4=Cv&t? zR(5DqL2rZ?&Tk=fEhkmGQS%!ZsVjEaf>rP~1>Mg=9{;G18TxE^f+iQZ2;?Vp`DOuD za4%);`?1SU5koR8@1`~kg54SJ>cLc@xmsptKMpB5s0G{mi#S&+ZUsC4cKV%A`ETF& zulv~p&6n=D$j%w!0(HWia8w1dFO*{vzum~U+}z)8;r={9L#@2Erdp$D*#>f?u`pvGO{-h!Cilj_{REq`0^~duLG=@)@2M zRq2~2)e*hQ=!|>=ypV1rq<ZDI|n;Y_`+o`Lpg?~Jg(ldjeC;`r2B0nAY(0in+?VX;u$fU+&S`D1(}+VXUF3)UoI zJ@nKQTEs7XSTj>vzI<*U!Cp+(LZe5ScKHEJ_&oAzYtwbiQ>Np?qhM!Jk?JqtFlOF< zp5U+jkGoh*Bn49Frm#$4x5x()?2D|YC8sj+fV61?awXby@PK@^|~zx331j* zVg!KL*TdGO|Clm89FH`2Q=iO!4dml)XmN>s(|bEeji#oZZZ1?M1sY|0Wx}J2o@A-6 z5hz(*v|}-`weq#wS6qR=V)!hqV$>>m9m(-jj3g^q$4Uh(mgWXkBohUL7)52bxFlF>>b?cR69mOoohP_icnYl$!~_p&PyC_FOK@%_n6nMt&JD?!LSiIqarGj2De8F;u;#Kp zU!(l=snQMiuf+~}+28(|5=_ekb5}yR2I!;N8;>>ELS-0QZaC-RV`ORMZ5KVNb4&hv z72hNdPgzEq`2qH?KC~UWZAZ_HT#c>y+OK`ivA5cpzCFwc_<>-U&-g{kQP65O%(hL`R~@jw1}R*ns>(So;=_6l~dXSQu zo$+`1lz7}#a}Zy7!V*{ih3C#JmNFr<2+AYJTK|1*LLka4>^o6yERMIuW7Y-x!pcT~ zx3LPm`6gEIu43lum@NB#Ee*c{WOCFKUcF~`Q_c0oQF4a(!li0K^=<4=g`>N8Rr}4} zOE0uJ)nA7D{x;RSQ+fqzaoxIy+O9QTFY5et1!-u`jjrjQYtsrigj3sYCYg&~Bv`Af zJ$Gu`VU7`azd7h;hblya(dV+g+&#$Z?&@ku?aZ-=Purcwvby})O{?`t7{l%G!StFh zVg@BYb4f__bD54Av-^L&`$Wi_yRQ0RQ&Kpjr$*j35?=A7xW}l#sq+#k)XAB=|(U$eO$X}7RS;S%*-p9`2 zoxyD)rKPJ3()DGLgT#X$KGx;&u$Mg|d6ia$>1wSv>G3I=d zJF6Re#jB}hUA#wrqC!0SJ$JUsRyJ!secBe&8hic3mZM^X;m8TpEoPtc(VS&_ONiR9wvs~9;QM$Z-mi}l0mCsVj zkz*D?JAiuJ=)*6(4^Fc|GtUv9 zWLY*CJV_7Pjqi6{J?|WWr4LMC)UUsS?fcQrS#4ML2X|;`WeWXp)}+0CI@kNQQNP9$ zkCyOhLP8!T-weWqA_rl^zZ5#9bXI_W7sOL(_-_=k+{6frnZXCjupfs(<{h|QXb5O? z+K;-<2cg|A>eW4*f-Gw9!rYiJJ}hOs)UT4Kwn(;WnF}%K|Xhz5ZUiZyqw!*Yr*@oxVugadwc-ZZv1{cSUO5UFJF|M zZ-ac|({t>+jCSLoQNeqXeK4KnE?GEM@&~=K6R43Qj~&{AKMd!Z_s+(Wyo=^nLXYY$ zCePG#%Le1H@1fGJgg^3%k0)mGVPTGbkhHsRnoKPYNXPmmtWs+C5nx`}y()X{Fmy zJJVT@3JxZ|)JkpOvW*Jq9{cxrVHLxEmI?X1>HZEPR9qzpKyAw2s%aHgJ6bri>*whXkNfMKK27~nlGSk}rYLD?Z%egYgjK@?3L zVKIj$a3K1zy(QG|J>434loa3iwQ0ZiPs5A$h!;Sh z*ODffBW9RyU2G{p^u)&wK@4*t$ymeMsc$h#x1UNkBl<36>>pFydaGLOHNCDl6y0V| z_=Y~G{5j)jY_=0V<~s^-uHD5ZIpKr^DGK$@>I1-n~1KF#-)r*WTJ+yj%Ziwu0nI)XFFuP5m3Qt&d5z+e zegVEUd362(gey%(stt(`b40K9RzfQ%wu-LGi}waOCf<~eZ&eM4Ge~0XTggO`CtV5S z8gjp885IyzVv*zuW)&bkVxNlyrC@=B9y4i)s0z$kk8xp%hh6l%<|-T3+*w({_)bNF z)jkXI=V(ipDAmrplYmpQDmy^PeeP)LCFj1SSE?n|+Ju*Bp-0s($Bla;(u^>}<5z=Z z7p`{BA=x}l)_yWw8Y9pxG#>w+6|$ap^J($zQ5{wQ!hAHJUEm^SNSP6!Gx%!R&&VuD z11TmQpAEJmO^OAJLo>zeFs#LD2Xk3*3W*`bHIDhp@22`bqB~MV#Im2Sjbo`37LpXc zj#obCqqq|=H^lY`p}h>pgEI*63CaK^`;s;5>c^Cj?vk0TLh~~d>XF4~eon^%A+p3# zFJjZdVB02Gc~NYzo<`xyf7Tk>K`f z#r%HeLw~Yi{p3Hy^E)S_?3$bARbH1?ARyrUTMk~8Fc8n%k@3&huV4CS*}D?OmoHZG zlyQRlbG*L>_+WFRyYD`8^rDbHTn|J$BMb=?=Gs)~56`e3x0|yzN(&}e{1~BmMm>9i z@u&VYYPF%^XPzbAb6!?kM5wZMLI?sZ`p0GX`?YXuZ3)cupG(Irj)88M;`jX{=7vem zp1+L(5IukhE!D|Fzq9T*Z{Ve9W}BN9mXmHb0rS2VEiS=qexOS8I7o_!joTgX-`^{3 z2Bb6=ri+f=G<7%vRDf;s=|}BxQ5p#R`jpU4zso0j_H%gUxkU-;TgT0cCMJ;W`3D4m zC4Pz*_GTPOM^Px@}USdlW)ZMD|Klbp3y{kOCi zZc7nL(7V}{sW?so!`VJ!BDjX8Zc@-66G1ixK_Ek@Kj{2r%30^xIi>Igd3-%!Q-msp ze9%)zDyAiAA8^S1B_>xm1W$msLdC-q(O1o|8?w5*zOnHh@^4+ywO@+@^c_X?zOB8q z@7_`!dSbS;2$q_co)*BRY*V5yN^MTWMUOa=kA^5o zQ>V)z5P3yX#gDUxrqN3M5?!n>lw|ydr$CL+=?#DDIK%gm`}-d0`7ZTY^2%4$9s_(J z-8C1D^*|U0_Zo%=OLU_JH~FD&5-N?YWcEfspUtzmXa5VOC+NyO(7~JW3k!otF_Wx} zT1I3QX-qMTx55)5Oa@^EzSxtWOSiXp;6793guvpJ>P4JW+v@b*U6i=qdSg?Eg-1qo z*M7iymF4!S)v`RVOY(TV>Azwx)>f}jF)}H5U=dOSD#dL{i7A1J8>Wq zTq&L|jrkum`Vs0scxTp5v4c8$^IF}bPU6JI-rSxIbe_xDfO%>V(wl}|;<~9nzeSeq z^j&HQXTo7pC?XlmC2*I9Os|I%M)g0uI&ZteWwO36-G5;O zVV)KRD}OW6efE5)>*B(fdvn!)rBVBrhKnyFt|ZwOCiVJf4T&k$=!5aXru-V810RT- zj~HIcf{pa4uz^C@%lMGbFJjo^%iej%m{=?x0|Zd5IjiST3>LcycGEI&o1@2!S#4xR zMwQ&zgjdM&U(a1QJe^wniNLNyy6dUdR3HUIgTT&PJb^o$mb8-3Y$Tj$J)9%hRKy1>a673eIP#lQ?qpY^*`Cf_GygC@f z`g7CGS9;BHZ)__Wf@hPd%-)*Ilu-*3HmQg@%uEu!uP>N|25K58l&7|kp+UXZp7Dx8 zQ~8q*;jnP|VqQ=pL{X97^RMw8&&g@ zNKPg|be7ViGxSv)P;fs_N{hGDbF#P7F`5$n_-hizx7#ty;7_cT5P^M)kbnnZ;VQ_8PDf;x?|sn?m37U2cfpj0G${)|FM5)*f3LJdi%&aH#C z_Jf>CFcY;qz*}7z=7C4wlT4w>0Fay&OmYXp+zdef4ec7eRl~Oe|s= zP|Vljbs>jBd6Qi*q2!M)(kGu$VTD)ke6JZK8y24uDE9ZDITU=gI&__T)7HTTTF&Ea zQ{Bx@?YFXa2&Khtshz~s;i~AN5bRZ4s9d`76hX9R~?cw)zF z*aM*;Sha%ge)pzgrQOeP!$eYh?FdAue75R;I+>&R`pmc;aM{V~Q;QW;vEL+y>(8P` z@$Z-=VC1g6@uhFNF*9!BJ1jw~Gd#rNI1;zFsHFeXj6t`Y8SNAxb8h(csN|`VM(@U} z%_O<*x3(#2UnH2^yxnjf%ql_@x4ytoHK1~9vObrW80J_pRU#j^T&-UZS;H&kP3XSG z)pskTtPgrE59KL9CzjE_*r_ihZ-HDTSAk?4kW zk$Ub;W}uiw`qz02yciC3;tbYuh>$VM*zZpbJui=#aXP-;FjM-oS3A>gZgp@w+R380l1Ra#-^%)?@4r zKQE2aGoi`y{heIhNy%-%DZp14fyO$PRkG&oBV^otP1wL`>W8+m<7R$6@xJ2fCgEiS z!URzisQ6Tdp3yCverXXo%&S;L2&nt=nQBC21!Ck*87vw#Xh8#+ZwNq`VxN{z&cNAy zoEP6gfhaR3f@hKRp%~#b+vv7_@-5{7}31oTf{kIo7Yfgzihq@ z2vO;Xm0>Xc7?tcOdDAM#yK&gJQkFvoyY>V)j2Ik}`%2xF!T`l{N>+x6QZE@1!#o*t zvVaEmo?yLl%J0{Q+1^Vf z#AgqIQV#N-@NsuYVm!xGDJOq+-dTgtIjlP7ediL$*cWvs%2a&P z-9#4n_9hby^3K+^j>?0bVt4Pjq{5KWQAZddbs!bHX z-~>e9Pd%VzHdaohNRpnvW!zsS4glRakrx~|NM~h1IiGjw@_7?~=H1J>|NZ4M6y^5R zl|)6q{-uE+@ArYQB;a4XzfB=s{ooKVa_QsTvpXJ3Lzjq|XZvM4qfA+#2%DrZP7MdSf^#A1o5G3v_37PQ}Uza{TT#p9`x|0ze6a@Kj z>Kcw-N4VI^; zv6<1{6W*i=D#>#cN6(N$!i3X!&Hn*=K!m@q&JbYta9p37IwUTD({8{V01+qiMiGht z%U=5M3RD6!QdirhIsS`B{YIKArRY*@{rBKF`Pcdq(&@(ch7dBIFCu^g2tM}13l>6H zIAPmn>;1{e&5jk4>aWj)2@qtaQ@=mzYWM^7bM=MgkK04RV$Y)#gL z2bb;z#NwRXOi-AOXoM-$S)2yNDZSZ%fOT`!c8=Q3qRxTsp|V}i0)qHR*)YMw**WIr z9`bAg)6)fnM7)Ahg=A`E!dFH=>6Z-vt{LoN#GrnhA4CL!G-r!NHvqG-d`=x*xVZtT zi9hM`GJ_X*!77#fUSsWhs()|Bc`y8t3&B9+`ZqiOVj8jm8AleCodKXotf@S@ z^JHL#YM?Am84>d($;X^!h6?EU``r-Xo|UiT9|#ty}_m_ok(fKL?8S@ z2yqxFA$bQfogYdPW5l21V#5J~QUu6vN&>+?55e4$pRZN`I5xDR zq>HB*Z&6cI+jQP*3k2e87*R-)fVGRgla;I%2+_r{fS0Kw2;3`r3Gw(2r+WEJcHxnqC#m}0; z9o2mP9!Q2O&;mo|pTnNvK{( z1veX4mj=BO*UK{)q`66&UT<-lWmoE(cJ_X&XQp0HSE2zIz{Cs6tj4hT(#gLjKTQ|j zw;MxgQ+>QF*>@2kK;QVOe-0WljnXE}k;vj2%pN{iNLisy0w5`M3j|VXNNpflCssOw zK(tp6K?=b-*t&`U;4L@}WQ~PNF@4Cx4k(AmnsL_|scX`5*b9hr2qS7xU<3-S_a>!G z!k*O^iiCu06p*Uu8NX^-cCDHMsICIC32~ga@S%ox7BVEkl+2)20j&yD^)!VBWh{nb zh9ti<>THI@`QXy;l_;d@k$%mz^KSR&;I71hB`*U2v|7;E-;If)2M{=Z9r|5lV*?CE zQ%%+esDtiut)-oy_Y44DWQL*;i-KiHDkqlghmiCwajFuRSE?i{s`7(N&P!4* z+es-G$CgBej%`W46fMgVE!q@FfgnXgAOI2|2m&B60EYkw;m*NuE~qduw{NZWeS151MB|V95gZ1g>?~vO?Uw=QEPz?DY4{m) z9d0uDQakme2BgC4 zRCquzBnE?M1o7ZUf4k%a>=NUSoAzVggLKwq4rX-DWaPN;69!qvafM+xz%VS+_$sCI z6r$#ql!#|7)@?xV4A`K(1#f^K4J%E9ur$@3$q*7p?4Mz4+&tP zcXvKJ8`0YVW4DWLJisva7|4LEYuSZ)31fP1z)q#wz_trZKbvBCz;bmN)(+_V-Qtt? z_=AjdM4`813E;DUU^qT!B4%F)ntaPHv<%~>uoUMiIIe~(80`wZ2Lm-0;!3K~D+p~JKqH^_RL@X+dIo6*fRccu=iYXg zN2ftSwJ8=@Y*cL;2VN*J#`>~@lH-`85EtbHwBb(;`A8M}W!Ct0?VmXFDcM@&8!YJ4 z%8V*<9Wa4j7CJJeZIoqWLT@ zBJ0*-VN9GqASDW#_1SePATBj>-Hk38KSBl&c=_GHlkxnA)X-Gy$T3QZqLY`{0<{og zN37cI#!_M!uYkokFt7>Ux(1(f7Ax%Tu@U5rqsZ5==}3-A{%b5=4F5Ql?eNVocz zKKVB;x`z$3U;uFoEkw}JDWD{znQk&)u4{@bn|}weqMmm7^+c5}jE9BVZ3k*n?Hn3UFxopaxnHGXOG)l{jhpsn18_ z_*9?EI1T^=J%A3KR|iJq4lo`T5S1x}*@Z}8LX{@ z!T~%MU{}hKaV&AH9X*y3@BiTK=Ku(=1NhF_ztLrY2mPFIh*9~nUmxzg6EFnnJl&L% zl&e|*syiUju_)())2rDXcFX`?Tk8NK!iEIhTUNEFpQknium=e{|9$5jfuVI-cMTAR8G;t! zXC{Y*jVHOJ+C`p9|7+vV3^`~f8ZX5i)RPW74$bb4LSe9q02&}xFpevX!{CKOljF!t zj)DdNAupH$hl6SpPUEQRbSy^eUdzHP)YhQuY8Y`M>;mI^=?+bo;1-Bt{XWz^uPz>p?ACm=3-rpyc0Ynfm7x_#^u1Q=G+lEa?5cZ0g zVMiNBg$*Cw_hS_{YbjC~7k7_WfHIKIQ6g%B_Skiv}Ipq90x<4hR#F^pF* z@{rZ*gI!@Y9{4>0Sy#c$lzigr|5XG4{a-wOJ34@I!_bK!8%-lS>8sEN>kME!pko*H zt#0A8x6uh`l&w9ski8^z z|46*zXaAu|EXve5Dw8A-bxKk#bY6@G-$+Ad>4d_vleO0QZn%j+icCQ+`(q(k9{gaA zRa1wKstb1MI1qT{mV(HSLdR|Z#nS5o%8g2QjAl?5rJ} z1MmRgg*PwbBmdK{?ca_L;28jabn^^=0n4C~Jvc}1-;6hY<8SQt*F}s&czrnlNL_C)$81L!p+JQ`2#&+@Y8y2tl<11uz{lu}h)m07g4x0H7UC zUMui=@d148P@Mq=h*K#LCxDE14qhe;?1u)}fW$!9O?FA=J+kq@mC6W+sFHuGVe5Q{ zfMQ)tl{F>pJjv*wDi}&M_?L@M&0-ay6bO&2RFq@eG>Q{WXEjB6+Mp#@W0aZFuf|{~ zwLCM!s7H4bO}26ZgnY+?NL4@#%TewVUDtWw)3wHwZUYU)ry_a!e_a024RGkta-BRMK7g3A`VkPVoWK~NFh zq-bG9W2g4{DE6YjFtc{_SC!PjX2e7imrwi*fFUos*(evvQ&mntRQ8a8qZ65jPUVA0 zi|9qI>PgkBBr2Q->YsIapj@LUM-V|jin*2_X4?0=0Xv=zsNh5hvjb|G3b70ro$?W{ zFpf*NBxIK!V_=oR3C!G-sKPqP6SSuEf0Y4%{&?WFbO1y+bJ#nGUJ9(nIATNJwZ|;` ziNFx)y^&kLTtrBjfpM7u{Ld2pHc&Qs#~_*U%Vew1)rt&k(E7`AfEJ*(#t9${NQDI& zlH3j(rz?uHED)|_)ZT<<<`Y_aonCgG8 zu>9a{=>UG|pM2rqul~dz!S1QWMtEB^exio)7avrtjWv4(rw8G5afwM;LdXVU#y|>O zG-cfj)&I`S-(NV%_$TGWs7#EHmJI0;+GYvBHK-~jD#*H?$seNvG&NaEz)`f9q4EWi zS_(p*38>CM!U2$%6=juCefGfxk*odv@(8o1_J;A?5~xa&upJndr<(biZ$)cCiZ<3^uQYMz=Hlg4py+mx#QfAgjD`1KDz!ndshFa{3*^2b^iN}2w{ z@14^IIkHUste11bh0Z>j8J{tb)2vOdK2OMI{!E=6W%cfnyDC{5 zP9%3S6I)4nRuH!BARto@&Bl57Sob%&Ce)T$CKdft@=zFdnfg;PPF!Y%D(?tT2CIgJ z`=|svu(0eL0gIVv=eFNWf}k^u-q^!;iB+~T+9ig%{(l-L@VIUc;O=|wx*vi+mglZB zZ>T}4>21T%T3=~{4=S6Ep$x@`5HR{SBNt_W#|fx32-@_?PtcK#6jOmCge4fC<;1n= zDZ@#QAR}H`_xaK$|6WXPDq(4sycwdeC_{oBvW`R1JA&F7sNh6`j@TpqM_}zy`wAkA zVL0%k#B!oL0N2$4kdaS;<$rI4>Iodcx9(5`h0)Ls;ciTE11YJ#+)J~dKpLc0r)WfGJR86hu| zvUVXQd?Bi{Nv_bAC1?J8dt!h%rs|T>I*Wg3sqeeV`(A3s3mi}2a0Ql!@*bbM%^kq_ zybCZUZ(B`;0^`?0jUx@ElAN-2pE82bgp(mv-xeQW8QnzRbY}p0JCLZ(B&h9NH!0Jvl?P5TWR7NCxBmd(<*&VgkN%xcUpR>lz+B&pO&+MF zc&Gd4u?*;R-QAd?p_wwG1h2A(pxP-+W*L6QU)+EQc|udN7Q_}1>wXSvq2)$kD`h-M zEU^j>iNq%=CC}ceYCoV}1*Vqqwo?CQC?@4Z^PE6u1w==yeva>w4pD_+?Fx2WVYS+? z7=SOGKlsc^g@ep>d@d$~&t)|@&3tMKd=tOiBNLo-vN1wC^Sil`+J+;D&LB7fZ~VCv z*raAZDGq>qT^;7-)hcl2I2GXMe_z`=l}m)kwh6UiQ(@rRzw6c@skU_{Pq%ey{mhILfc9Yv`n@H5m|pA2ISX4tGaU&JFC)E z71NHCA13-P=iWHE)VQ^0+6CzeasqmJsakB(AZU19&@Db`X9FgAh^t zYG(!-L!fe+f^Lix5D~0a`>_KcwLH{kZ~F}3pZ~)r8Nx3^_**FfXpHz)QAyE%89kXt z(@sNQNHP=oX$`_FoBTdy17LQymy@*%Ny!q6&1aMJu1>m97dI@74 z^YyU90^HP|%ynv8Hm|V5=8hg6gYD>&z?TE=XU;8M)Ee#y6N(gN{34w185qja|7e&d zl>o^RSK@HcV>U<0NlGY0{EPE zq3g2%brI@0{dI^It-%R0oNgvPZ)F3m45H-(IOrc@f+kFEXf=>KO0~2St_=NND}#?W z6_v^O=&$cb`9xsV`YZn?E0^m10Mbr?THFdZ=KWj40d->2|Lg7mEL#TfAX+>1lUYDo zLlxx!q-E7I!@`r&iMC?TCZPP>FcR`|fW(5ts!YI_J(+GEZfqtXYN-i`O3aZ>$s|WW z(Fv#MPl2@A0Mt2ob28f4tJXkX7EBBVrMu>9!l@c0?Srm+`7i!DlFgoV>j1*>|Nd`Z zVlaOV%wM1KrE@_&uJY=m_Y-MN%nnLQp{8u#h-vtD`WL*-mNv zi}{b-~cQS>)JU-#HKA*Ot`6?ecJGo znsf>@1M+#k0nOkOhSBw{Dg_W_vcc@=%>q!UnS+%e%1HnLGZp4Q0<6VTRK$-%J)ME+ z$S+T}Q~Hc0hzfA8{P&^@pwoW$)Rd|d+#oL?H3rcBGkHWv-q<>T8V7i|Fl|2@FKcT0 zK=?U5cTX)RB8OPNjRcpf-V}=JJS3>;yrcmS2TUN$2Z&-cD!;!WEj2bo_Sv@?B45lSZBDD*wkk@*oW^885mm-Jk0(}6L(~mPr;m#NO!zL#5YS=@^N$-O zuzhP#4G#&6>ImGB%s7$j@9mz#CA=ibzFjLPgY%6MvmIe4qML3-Q-+ zzG2uERamx`Ocmm>8>Wr0g7dS z(57iUCBxT2PINfb$WubPNX48`4!FmfruHBt#GhgokYgTEbd=R>z;$#4dg(`$qFHhd6 zHR4nI*GHa;%!yqn&AKo`tVB>r#5A8oVmB(jl(jCBvQLumE*~>r9zrMubSFSK%8%oaLZnyi2vjyw zJ!go*KH6tDD$;tY)B1APUt{)y1Zy?_=RZS5jmwd!RU{O`of~)2ncDcv}ZBX~x3mApCGm@hiSgU$>Ey8|4%hK&q|hdirWdO^OU5 z>muYN6?(%hDG*b1F4<%0P3IyLh>~Yqwgees{jGEYa{QJr#RaXMWv*MKqc-I;`mLUS zxx%Zmg1OW`Jbu~o%ToXAY5Xw`=}wK~NeH+O!MaQU?)$jcBDf#G3tI;;bNtNz`K1xe zXPbGJHvBndZk+`XWu5IRn8p3U9LOtMc%(}asWSr7i||=DAa^)r&PWU;d!E{XRjCl1 zfCmjlpd`nStAzGY-T3^w8sj@Ab_s_D6Do+W9yuN$MMFZe_^(MWi1N|6kXfKnUSv*4;{Ygl0@OJ;T!10$>dWI@Auc8r zSzwsavACNEi~vScpcS&8)#orp@l_#rlyw+VpWoH{T?6&MZmYhr9Fh>|eXADg{g-uu zXy0a*7q$*ygTum}gUDx54%5W+SY~SAynYlz`GyKm)U~?yCuRJN=aB|Nn4#9BsIVzZ zc{3v^F#w$flsG^mQxHzL0Qi`B1QdY@v4s&y4!|-xW*8BF%bjor+_fma%7LFMbI@r& zdB873e{xQMCS?n+S*RDN#i+oG7=NDf8CuMQ0N~$44y%TJn>v6G|6fn6V286+R1wuG z+W-Lz3O>3c;66@ntnnAKd=u|tQ#5% zSi+&5fha{|3C7PZ`df9=Kl|*!uyX{l?U~K&Bd7SPL{pGD2!YY38ualcK*RXy+SSEf3xzmLLaMyHE%s5;erHaYkVXDZ)&CpwvtG6F=e!J}zn_{x zsZ`f=VM+M>$DVoN>tA|h+-3k9pMbn2z`u*M-%S)9>!Teh-oQpp7i)f1k2i;d!iQh3 z^_E+E<&wI_1$5O=y`H+ni8SL4(2FEb*M(CiAiTluM1iY_D6Rr#kaZY=|<@cii(X#Aej_v;*KR2mA|r+=-_KmOY{P4qtz z4&cnQ=g&j%D=ma4j?#z4Vi_aGvt*1Cxb^Zwdes#?<|b9FqUDga}1diK%+~7lJPfZ^yw%W zzbT16uTkVrFHZ?)Xbk`WAOJ~3K~xz(=T2af%3XkRU^=V?7q0vK)&U&i_|$zb?2{dz zAT;EAHd>IWhe>qPSSFl6wk=qu{HM3qsbUUJ^}YuDD5d|`c*bAU%tN;RtfRl^ z;V7xrq@}=Wa^sJ!+TXa@96)~(PJ)KzThjFnAW$|W%$OKQ6~%M|-3oM9nvt;Dsp?;; zTXORW2>FE>$wq~xFzj5ISQJ~3ftVyUHq~jXA`){Pej*7vg_OiiX}$-ujSylE^zfPB!S%nFF|i6V`3 zD+F8=Kvyk1RwaSc8cm@W&~p*87S-p3rudxB4LVWbb^MdX=pXb8WESYs=Sy)oDgc81 ze=I7DBb7mFf4~6fb*qn%)%+w^iTY*lmsCzbk35T}Vw3d`n2X1R$ghbJ}sPLZ}6DZ$4Qv_ndh1=TGB0LwjrqL$AF zkK%%mOs+mmGT#)qK)?hduArh?oM#UyfA+VSSZSB~?GCtrO&J7@Ha$0L>BY$EL&h=s zl*W(Rr?#AAUXe|t>3WDK@+~-Uk3C+(toI-42d~S55(fwlzUC!b?yp{Y?d#`O+YI24 z$CdqI1(63(;Q(qZcpb1pT zSVZx2bf$5xnR<~Njs{&zx(D)>kZ2g|x-U#XXHw+%j7mni!;a+IjQFbZN($!Xtlf7k zHEX*``Op97PhP@JcJwFZM7k@N&hy@#xjAaodama!9&HYSmQ(|Z9F+QES{#%0!~v*R z30ORgAaN~#))*-fK}h^pisT;IPLWKM7aLxklPc!v#M4=7VGqA(6u>`Xsl`sD{9X#< zPZ^D{cLL#c8ip7HGU99gwB6@QfS~2TD7Ua$K<#u{F75J2l<^}iJ>_Y>Y%P{d~P^ix%I57b#ijhr<32BI>t*I*eXAJSl=Hj?19kTz1w_$SK%1ujwm|9o`hMheO< zQ$So3M#^A3&6v=k@_^(7e7-~A^j>K2k3&|YLn%!^l}!C8APeUSMh0~Z(=m(~wag=N zm?}JjwE{zJmB%t-Poa_`?RrJlfqFCkRpv@dO^clAqsQaR09IQEa23aLIeubir~j$g zQ4Z-d3&^Ntm?>^3X{7%O)MCB*qFaVVR)a@ap)>*Y9AoX(ve=>71 z%iMXWc3pA+qEi1>CbhYY5|MVQF54ioAQMPK8HPjpFAZ)+)rP|cV|Rx&@X7ALhOLC` zwNsYo_mE*IKq38xsJ`j|(AB~U1ED%$Oe9q3^ zyNg~FD-Ob3fTkIshVdh15bHcm%Q?iC6&B0lQznBv1z|pVGiCthB%RF9|FbV2K;)vP1*EEOm3mQT zuBebA>QsPRi_B9}i%n3k?M{Mrnk6@%NuUbIl6)(0HBB@+`0$p$iH?$;rXot zIF3W)7qicwk&m!WfpH}fXXAIGD6mmZaF-pq3<$(#q9tXXTr3OK*L8kTwnR*>`Ur%h zssqplSW7pem!@et-BR-fVjUJBgN2ON^B8d6SC{nw+@zsbK5NR*H~nW(Q=+Y9FO%Qq zKbbd>!$?~PaKsDtSM>H?BGP2lx@zZorPN-&a!); z9PurgaZ`OxnejU;07=~5G2fWh9XlfP7ZFlHmpz0A^+PdAF-jW4mql-8PQ1Nw94tDyd#bPg)hVZ8+?IS{Eb7c!r+-r4Zz%TyfC-`P`04J^l5Ej7D z(`~@16x76YUE0FaMDMUmb=_7GbZU2h)9a}vo-qkq%UTZ9BYB6`Yba0B_zS)_K;Hpf z=f3a!O@AL(`OxO<3IjgYl>;&cn)B9k>kS&=&@&)L)eWT<`?Kj}l)E45)Vb zMNp&v+GxG-XE5-X1(Y*aMWHTKIu+5=$nJjxuptmx?+m-NJQv>IO~d)sM*6i4qcrS8 zA@$cpuxQu+{`l>i5db`y4nTy-m`f0jsdgIZSLtjl15hJB)uvlA>L#jPu>lu41sV&e zdiHbSe?r?p8pvfyLOd}GAV+lw`n@A9|Lvkbbv_I5B>**EiYUHCB0pM9e!icPl?M{q zkfQ~gq%SDFe-6#Ge4Oe0hVukcIcG|spRV~g^TUsC9l)`50RRvkWaJjX5GkqWP)7>5 zQwuMd^j41C2iaQ7Ui&<&y{%71!*=yutNYuCG#H zwgs9Rh(xIbXh#3|-cux4O&F)~pNVl9fp)rKrrt7mv{CKDfs5?2j;4c?%^cbWzud?d zP*MAr)bg8ofakX*z%kZg1gy{n_E{B`Gde5dvj8f#?4-^Blm#Y+8d7DlYE`u^7Cpqm z3KW~YKnPkyVu}<*;AO*N%MuMmq75$mvK*&kuAx-|l!B*fd|9TXuOs+cp!vaO6q~zv z{sb9AM*sj>5V}ZS(m*c{70u;Pj-vJX2GCP&_)Q2B%*-@Cw9NpH&HRM{tSC}z6;aiS zqrbAg3?Qjef8m1gBE(|9mTF@CAAE*jQhv`*?1h?_$~I&(0Vw&SYpkMLs>#}d{y`5z z5g^l!ixl6zrnu3fpH%EFP=HA#FNtE~q|jFj;{j4Sfsln*1`moekvdgg)6oq5kb42m z*-cWyWL5Ov_&8%n-a3F|!T+&^(0aZKh+Y~+UO4eZ*5C}V{-t2%K(tj za1|B9Zh%+@4Ew)f&#lM#$g9c#NGMWL#q5Gr8;RnK&O2GQI|n^iyOd_U9j*f=}}!e8UWk1Q4fP#U36M zq>0Vn&7=AH?Z*5!BuMGMF`nA}NDY^*12|>}Fe=n8|122*F~}0c*d)s(NaLxV*_N?QJcB+n7R(s`^muGqy-_pyTEXU?=cuLo&_m50&qQ=Z8;+Z5$^s5dZ1^YrCg zXp;=Mne%Dwr*1eED5xfXkB;&k5(wntXF`^yPs*?R=$}S}O={hRatNM0B?6=b>Rc)2 zn1b2;-l|2UP>R?^gEv6Zz;%e)&nXKB@t?3`(+knrPx7Cj3YS25!IwvM~s#JOIV)Mh`_50;Xs@ zzi~>yVNBRf3MxfpI0ocUxRX)7(75glbplu~*xLCnK&eK=0tvi=`pG*q@DDk(6Kv9s zDcbTZrfV0n>7tP&;ZnN#WuI*ZaGb|5G6Y~E zYNpRkD-+UCd((>>V`2+1>cnMo(A01)P&)SwqS3jiO~_`$64ib&mP>WkLYoA=!(6v5 zCl_8fqaJRWeL_K@x|iXSBorq&$#|em{34g3sA1^Xf{@XoiSq6n`B`Zyv|mM< z>FBUe41UWE6YdfDWc3nY6X^CP^+e z`ky{p2mZ^nE?*%vt{HyQB9Y8zDS9bJ8qq&~PqU&UwB|PDKq`D$ zmu_YUa5CF~A{Uwwt)%Q)=ktRuy)iH*Xx0V5T52+0QidNyJq#i{3>E!iQU)<4s!Jt( zVO5vYRm1;topX{Y;4vSUCMBLyla`_oE}5%=r2SfTZ!e778l$pPry{@4M?Fo0nA zly>98B%J5D*K9b=fxR-VE;14b`!(INf}u8}@o{v;N`TQnf1KdcO3Y1Zwlv-=C9qj) z@69^4Q5QY@TEMSnk-q79xX@Z#k%Q6jBt zRi+1RbjH#G18K8zS7iSB&(1?+vR_--J5!Lt6)M4Gv;ln!H}9jWPK{O$ZDP`!ze}V)=MDH;C(ps%_x`N|xF#h4_LC*kO#2Vh7nSks ze}2}xVgM3Oa^XPghp5`$G2jM-+K;Ga{UVx<-c{O7t^C(#9K~ZD*CcF}MNy$cz<#4o zZ8;jHy{OlqBLNEvA;p{LNbtj2m7h(IH?|JontW1nP<=j8Ionfhl`}w9R0-cI78_Fe9+`6j*01fh16~kcwnFF9aN*Gm2Or+*S>pGI9CY@?l z=?ub5r~d24f@R{5t037@KJl3lSwK9IiWuF3K;m8)m2>Q%CXJ4xK0iW;m#cZ~O#sIE zl;Ggf>i(?*xK_uNVZ_KT?bhgULGU3)vD1cWJ=Lxa>%XxB5PScr{ue1)Ktx4eK7$qs zWeI2{4&a&{%aLKlqNlFdK`B1SB62naK#1}r&y@jEk?$xQ%p>Vx zu_D15)!D)cP(egVqKHT~wWK-bj0q8xhy(a6fSrUFS(7p#=nXqgtH44o3+N|&;{7qy(;>7XSQ*s0WdkLVd9PKoYUMg(<2y{5X zkLI7f`O@28__crdTl-rFaLqgb0KrQ{_?}GnLnfqhq>YEuxo zDgmGrYD8T6&xH;EWoA-m1?@5M`k4h4B2cl4HC_QxLm*>n8g*mjT_T0|G+3D!jf53h zM_Ad_(|Vf8I8F~LXCPrZl5)o18IWCWP|Z-4j)yq5K8A z=ue~5TaeH`?IfSp^VCd^OvJyS&Uh|BUmA3jx*J!u%ruiV9F{FC%e1DgpVq&(Zvr>} z4nR1}Nj#~KfAM7|!3))t-PF#1xzmoko=$(KjGr;Fu_squq$ahY;739ZLj^vk$B&T> zEFDuBm2becGMk72z?A@_Kn4qp>}1$1+Pne2WWb7nl?9d-vSN1|{C&AO2yuKV{)SwN zb^X7o^P#FF8wdNTPyfPW2e*m?=uhg2iRGtAJgt<`*-LISTgo9jW*IX8K-IVyjx|B2K%h2Nj+CjtOmgNY!p|c_t$+hm zOhZzy2Iw-S$o*+%1VA=2e3{8@TS%g(mGo@Q&U5X>9^P&F`}D%1;1zZR-H8$FV=GWI29~7CZkrDe}fO z#WzSuf!AKXiwZ5iI145jrCl!F6{x@0q|J>>tqoBm5|F@4N0DUYD&`Ja4k9=_q}Q~O zh)yVVSRkIJJCsAhCINh=Ruzs76$!Wj$5v`o6DP_uy7hU>A%Mc!7lyhOIG+Ht&hqh3 zo_XNmk3GY;k^?X&^~7XghLxu@5ofDrZ86ceGhl7R06b?0O=3@(xHX&ttI5E@LRQwH{VVI7fo(W}c=N9> z$heQ}909k7ua5PSnYbjRDL7bif=~BgpMLO#UwQrPxtF&N;5r>Y^O-Y@Q69}?xG`~Y zt7u2Mx9-g^1O8C72unkl%mn7DOOy>hb(&NM;RzNXO(8gSHE%=_u3MU{A}>!6`c656 z*dFvzJhb^jt8etQ-66v^06Kzn1TkrVP!zS8<|LqvLAX}666nH9+VJ!OWMWN=D$rPg zuM_9-?B`#6@QoMG^R4Cp`jdQevOhc{rx)L%-uw;;K%mHU;aC)rqC#)%=1C#)QP5?J z=FMm1I)EiWH?alSx+J5&$-YuOsIz7Qa2b1Xn>HL+aa0?#hL_2_s z%ky$+^>AY2#r&pSx!$Mx|2m2j0u-GM#E(S|HNTV2Q{n_;Z07<>+HKxZlBC=( zTm+<|{@fhK=RW%7`(FRX+ZVPD;Cdex49gFK;B@LWN8l`|MQIX`%Zo;mv4NTeWUMM! z@(X)|tF^8soGxB7qmLK1zYK$N4~~EVXxZ&R_2sAeWaLz{S86_zK?F^3FiL+bY+#Ut z35*IDWM!-rfWhDdM6ry|IoNVkTbi>RSs9SYw_{qR2q?4w=iga@{67G`hFkPF(Io)0 zuwnVE2%LOWXB-NhMf)s-7Lh;-mM+PZe5kCKC)9r(Yc7;BP6F-b=*g=J)S#FVaMW8; z2?b79uG0*_l8K(Wv0hHbZ&Ng{8)wpBS1!8ZdY~X+3WEVU3-r#wvw3?zG$g9c>6Ws* zu`;z;fOV>`2dE@55NB#V<%^+0bCps#LyC57EhU&gmdn*gUORj4ty|RroahYT!w){s z^6i5s@z&*ErA6nRzzn&CLh(xi%$O=xOjZQ|rU0{alRONVVHs0g#zKo&G=d%E-G7VOUCZ!Q*5ml0KJ~0_GE)JT2(OF2o#(n4`ki(d! zF5t}Z_y5ID{>IC=g^&KkpW%oDvHU4om_I{%y~_Y>kdkhV^Cp89mTZv?#YCW+2TU}g-RfZYTmR~Rz5gxT;zxfooTOY>y~M9w`lsFRe8&ecEFgmgu|Sy8FbWkQUTA+& zsws6EDJ{dIkOc&Xz^+!cjxK`8ccqL{ck!x8!U~dF?}p;m8zB?hVmR?Trqd zUnP{D)#M%-0G-KK;wQk2>6LqBh}JuSkYxlOS5kHth)2m2jmU(OdhrJ6WI_r8R+<{; z)rp0u$nOOWHrcwocy1q`{Kt>})89VxCg17~z}yTc`7@tAvyzL${UG{HI=uj$UI2^E z5l|42PX_9mF&6@Xqs?Et=rR{X1lpEI8Zvw&UVEFI8@#R-<`REV0 z3GSQXxNEn+@Sohd`x3qT)PGMvM7D|Zwhwt#U(=ba#>*}q4%ZJS)V!b^*J{C9x{H*A zSGmL&7>8M3ylX5yXcu%Hp>rgL>G`K3zIr`k{Z!u48~wouzA}O@58%ry_w|bVckwMd z38=Vf7{!#*QX@TxI!Lka4@l$(^qY=;OgQ5~$aop0kSj}RT-L0Us&GR&!Jj6pDGC8r z5UKBg5*c?h9mw>6@jp*Eg9~q7{`?m{@$BDv{p`7M>i|yB@r^ex@dw}Yj&s=U-lTWz z{_z;4**k$C972HUk|Ho=rGcn!fXWQG7$zag_eIorF{wTNltxo5F`@&|eR<4q3lPu| zq3aAfWBkL-7XcwE2IJ@D2syCegAsOrfW16`FRj4)1LVN^PJl|&Lpk90^f!r&^A5q^ zy(jMg(q4dsuhCmfj3l#UGZ-}0NRgb^UsEY0Mx~?&;Q~Sz9RN|69C^dHzVYJuANlmp zKlU%067lKtlJGS=7&{ z6syw(M#(r;?VNNplLL^9g=jv{CvuU$GX`B}k~U$L@*_9?ks&J!jw$~D-XCEvuV60? z&iDsTJNg-5=wF)&%ExANi7cA%2ZUt&oihGTaUK-xOf@K}6_gs~59w&H#9-oQl~kO9 zC@dKRq7J6(VN3^*nVh)1cy9kkf8i&8{o}Zej{asl;h%os*>gYs-S7B|=Dl}(o9ncDXvFS8JGuC++P`;{`(BT^e|lyGLE#MEP1Y6T>j>lKKa~V zdFWT4-rqWao5cw{_RTjx^h0-lnNBbMBH)|=*)+aOL@-^^e9vac5o`Zy!c3-lwnzh3 zptz8h76+xd!kYnDr5X?sdPBYhNEfvr)cWFvdsYlyT8KA(UOJKvUOM2Q zf78YLg=1KaU-|s6KKqxStpKupRRctBmTh%J{TOLKX?#u zWgXJL8taL_IUp}=$Qc6l&R8ZT&~r;~RyctV01E@zF>!x`Oj&cwBp^x(yGbBk=>Vwc z(7;D~pFvR=`*p9$m{9+B8CVG5xd(pXvA^*6C(k^G+w{1p9l)dCJjWk+$Ehdi^x}Ky zuALu@^{A5l&bI#4qK~j-`vD^JNH>+mEUUUg&%MjE6VQq@I;}NC=*Xahfsrs($KZie zihso!zZ{I7qKo|ikaqwF7Ci7~(Z5j(P)DH7waDg;eAg_oaLT^R@5#z%K6yog7ifAjo@ z{?m6oj@|C}(doteBPO6pKzzL}#XLa70OnHV!tg1wdq@UAlJB#VC(WQy3a5a`HWa?^ zEYMkj83eTAIL#NM{ek~^rPX@U>*CRu2chW1_kHA%zy918UjE8* ze^}u*Ke}7xg*=mm?eSnmWzb}h+K zKm&sY|8;b&N;9b2ieZ)vEWNzefirfFGyR;_ZP~Hg8R&n)ANhUjj|}0;#pOSK_+!uf zkB@)y%o}W3wx)jzIsl&uy!OHOz4Ia3?e3vF7C#WsZptzM99mei>JVg(KmmZ741@E< zqQs}?YJCJSFZ}!H9S0W-K148mhsEv!{caD_8MjVr-9Qkp{LX(#_<7hPjNzIvB20&J z0u+XL7QO^n7*JT1 zjNcR~JzV#rj`Ygl)nb=hnCfA=&fQFx7=GMDQ(XFi2mZeO=KgapJ@e*|{Hy=v)4#Me z_1nk+0KlVX-@5dHyH5W{^N!uGu^d9ph&oBFg;Hx%ih7#R@AlX^ zwLrh3exbOq&eJfM`?pJ^7rN8pY|L?Kqwq|~HG2x~@e(Vo@ujzi^ zJwHUJ7heJka4wgG*qr4J2g4yX{PAy>edYC}UN(Ge`<f*0eV}`rGA&@++4v@avcUT=zZi z`Vsn$Q~y$W16ml!6*pjm(YhYKR}?;OKOk%WA$bYYcRuoWlP=n_Bld8s&u$KvdV%b8 zFgwlx1YSVLYQ%>t6T;@9Fjz*<_t(ShglwRJ2ZWL;h=^E<`UJ5I=#XnYW|M?^u&0CR zUCmFS-p_X z{bdZU_l|=hvgvApi(|zI);R$GiR|nxuzPAJ8^{Tq1I!G|f|1=8W0d6*)p_tFn>?f; zAz7tip+l7iVS=wSFpJJ*0lOW{!hrftP5Dtom1IX{0egNRXr}{qd(dtNEIL0RM43^C zC?8Y)F4g(zV)Y}%f*0R@{nAhT(%<{c7Xf+g!dv^>zW&w$Z0H33%kS^8L+`^ocmIfa z@0~veLw^{)hQ@Mqjv!OhQT%>#OmBNBSFadIcJ>xn?7Fl!$at|xl;z_{!Dam3yu#=? zObaHKUSjG&*N;PX*G8}D_;ufOn>*Z#$W|K`cZ$CW+%=1b?d>HgLM9MKW{(eK~G z>F)j5>3`UK*PVX}#(V&SpQ`iQb}pK7n^LK{DiM6jFIo__c!6PD z7oD3;px%IXTs9CL!NS-6r;Kw1yPeAdc015s5A5}z-5#{)fWGsq$emi;D@F94oNp zh!2D%oWRDL9DrIGj`CELm#l@cM#qL_J?QW(&~8#&Vu9&M9#>_ zFX8fV0fr3i^><>?y$`_qLAVFNJs^5N-MP3Ir+5A{Vp)h{5k|-m2J2XbL+0Zhf_QK- zstjk{lF-(3lY{78i9ibj+U;DV-|K+A4zfeh3D8~-I<;`Oy#;8|JLeHL5mVH^Lco0w z|GQ^C`|YGuy)9HSXPA%RC!uvt={SdiVSiTp)z397pXtz5h1Yh=eQBhW^ zzd`K71`^efCFGq8>Kw3Yc6*r93(y_Ci};H)9*o@{bgG9rz2ls~Ztt-Kg9~r&|MpX# z`^JL+J`3P}055$0SD)V2_FD&VeUJa)zqrSoT6Bi)SnT1hoqK7g`vV~SAqajyfO}!+ z6z#h7H4C`8B?F!_u;P;xH&OlrtVPMeHZhx9i_wSY7ydQ#l0Qr8f+%1B8!E(2-e3z^L9r7<8 zXMmcY$n*sa!R&T0ckIC2wF^4crzM~?q3!rUo__K18-Rls05fR%k2 z`2b71FIR>Y20KE)5HQHoi3o6MHS%kh4gdi6J^T6%t84$v@4egn&bv+QN zzw)J5wxHei*dE(sdu)&Gu|2lO_ShcVV|#3m?Xf+!$M)DB+hcodkL|HNw#W9^9@}Gk cT>Iny2gymlF1ZUAegFUf07*qoM6N<$g4 { return b.toString(16).padStart(2, "0"); }).join(""); + console.log("signed encoded", argh); + return argh; +}; + +const ClientState = Object.freeze({ + CONNECTING: Symbol("CONNECTING"), + SYNCING: Symbol("SYNCING"), + SYNCED: Symbol("SYNCED"), + BACKOFF: Symbol("BACKOFF"), +}); + +let Game = function() { + this.clientState = ClientState.CONNECTING; + this.role = 'SPECTATOR'; + this.error = null; + this.interval = null; + this.serverState = null; + this.identified = false; + this.ct = null; + this.rct = null; +}; + +Game.prototype.run = async function() { + let self = this; + + this.identity = new Identity(); + await this.identity.start(); + + console.log("Opening socket..."); + this.socket = io.connect('https://jeopardy.rhw.q3k.org'); + this.socket.on('connect', function() { self.socketConnected(); }); + this.socket.on('disconnect', function() { self.socketDisconnected(); }); + this.socket.on('state', function(msg) { self.socketState(msg); }); + this.socket.on('nonce', function(nonce) { self.socketNonce(nonce); }); + this.socket.on('identified', function(msg) { self.socketIdentified(msg); }); + + if (this.timeout == null) { + this.interval = setInterval(function() { self.tick(); }, 1000); + } + + this.lastStateUpdate = null; + this.serverState = null; +}; + +Game.prototype.mutate = function(newState) { + if (this.clientState != newState) { + console.log("Client %s -> %s", this.clientState, newState); + } + this.clientState = newState; + this.render(); +}; + +Game.prototype.fatal = function(msg) { + console.error("FATAL in %s: %s", this.clientState, msg); + this.error = msg; + this.mutate(ClientState.BACKOFF); + //setTimeout(() => this.backoffDone(), 3000); +}; + +Game.prototype.backoffDone = function() { + this.socket.close(); +} + +Game.prototype.socketConnected = function() { + console.log("Socket connected"); + if (this.clientState != ClientState.CONNECTING) { + this.fatal("Unexpected 'connect'"); + } + this.error = null; + this.mutate(ClientState.SYNCING); +}; + +Game.prototype.socketDisconnected = function() { + console.log("Socket disconnected"); + this.identified = false; + this.mutate(ClientState.CONNECTING); + this.socket.open(); + this.error = "Disconnected"; +}; + +Game.prototype.socketState = function(msg) { + this.serverState = msg.compacted; + this.ct = msg.ct; + this.role = msg.role; + this.lastStateUpdate = Date.now(); + this.mutate(ClientState.SYNCED); + + if (this.role == 'HOST') { + for (let [pretty, identity] of Object.entries(msg.compacted.identity)) { + let role = msg.compacted.roles[identity]; + if (role == undefined || role == null) { + continue; + } + } + } +}; + +Game.prototype.socketNonce = async function(nonce) { + console.log("Received nonce, sending identity proof."); + const proof = await this.identity.hexsign(nonce); + this.socket.emit('proof', {'identity': this.identity.identity, 'nonce': nonce, 'jwk': this.identity.jwk_pubkey, 'proof': proof}); +}; + +Game.prototype.socketIdentified = async function(msg) { + console.log("Identification result: ", msg); + if (msg.identified != true) { + this.fatal("Identification failed!") + return; + } + + this.identified = true; + this.render(); +}; + +Game.prototype.tick = function() { + let self = this; + this.socket.emit('ping') + + //this.render(); + + if (this.clientState == ClientState.CONNECTING) { + if (this.socket.disconnected) { + console.log("Reconnecting..."); + this.socket.open(); + } + } + + if (this.clientState != ClientState.SYNCED) { + return; + } +}; + +Game.prototype.render = function() { + if (this.rct == this.ct) { + return + } + this.rct = this.ct; + if (this.error != null) { + let err = document.querySelector("#error"); + err.style.display = "block"; + err.textContent = "Error: " + this.error; + } else { + let err = document.querySelector("#error"); + err.style.display = "none"; + } + + let status = document.querySelector("#status p"); + status.textContent = "State: " + this.clientState.description; + status.textContent += ", identity: " + this.identity.pretty; + status.textContent += ", role: " + this.role; + status.textContent += "."; + + this.renderGame(); +}; + +Game.prototype.renderGame = function() { + if (this.serverState == null) { + return; + } + + this.renderBoard(); + this.renderContestants(); + this.renderQuestion(); +}; + +Game.prototype.renderBoard = function() { + let host = (this.role == 'HOST'); + + let b = document.createElement("div"); + b.id = "board"; + + + if (this.serverState.board == null) { + document.querySelector("#board").replaceWith(b); + return; + } + + let desc = this.serverState.board; + + desc.categories.forEach((category, cid) => { + let c = document.createElement("div"); + c.className = "category"; + b.appendChild(c); + + let h = document.createElement("div"); + h.className = "header"; + if (host) { + h.className += " narrow"; + }; + h.textContent = category.name; + c.appendChild(h); + + category.questions.forEach((question, qid) => { + let p = document.createElement("div"); + p.className = "panel"; + if (question.answered) { + p.className += " solved"; + } + if (host) { + p.className += " narrow"; + }; + + if (this.role == "HOST") { + p.onclick = (() => { + this.socket.emit("host-question", {"category": cid, "question": qid}) + }); + } + + let t = document.createElement("div"); + t.className = "text"; + t.textContent = "" + question.value + " SOG"; + p.appendChild(t); + c.appendChild(p); + }); + }); + + if (host) { + let h = document.createElement("div"); + h.id = "host"; + h.textContent = "Host menu"; + b.appendChild(h); + + let u = document.createElement("ul"); + h.appendChild(u); + + for (let [pretty, identifier] of Object.entries(this.serverState.identity)) { + let role = this.serverState.roles[identifier]; + if (role == undefined) { + role = "SPECTATOR"; + } + let li = document.createElement("li"); + li.textContent = pretty + " " + role + " "; + u.appendChild(li); + let setRole = (who, what) => { + this.socket.emit('host-set-role', {'who': pretty, 'what': what}); + }; + if (role == "SPECTATOR") { + let a = document.createElement("a"); + a.textContent = "contestant"; + a.href = "#"; + a.onclick = () => { setRole(pretty, 'CONTESTANT'); }; + li.appendChild(a); + } + if (role == "CONTESTANT") { + let a = document.createElement("a"); + a.textContent = "spectator"; + a.href = "#"; + a.onclick = () => { setRole(pretty, 'SPECTATOR'); }; + li.appendChild(a); + + let b = document.createElement("a"); + b.textContent = "set name"; + b.href = "#"; + b.onclick = () => { this.socket.emit('set name', {'identity': identifier, 'name': prompt("name?")}); }; + li.appendChild(b); + } + } + } + + document.querySelector("#board").replaceWith(b); +} + +Game.prototype.renderContestants = function() { + let contestants = document.createElement("div"); + contestants.id = "contestants"; + this.serverState.contestants.forEach((c) => { + let name = c.name; + let cont = document.createElement("div"); + cont.className = "contestant"; + if (c.buzz) { + cont.className += " buzz"; + } + let h1 = document.createElement("h1"); + h1.textContent = name; + let h2 = document.createElement("h2"); + h2.textContent = c.points + " SOG"; + cont.appendChild(h1); + if (c.identity == this.identity.identity) { + let h3 = document.createElement("h3"); + h3.textContent = "(that's you!)"; + cont.appendChild(h3); + } + cont.appendChild(h2); + contestants.appendChild(cont); + }); + + document.querySelector("#contestants").replaceWith(contestants); +}; + +Game.prototype.renderQuestion = function() { + let question = document.createElement("div"); + question.id = "question"; + let q = this.serverState.question; + if (q == undefined || q == null) { + document.querySelector("#question").replaceWith(question); + return; + } + + let contestant = (this.role == "CONTESTANT"); + let host = (this.role == "HOST"); + let buzzed = false; + let buzzing = null; + this.serverState.contestants.forEach((c) => { + if (c.buzzing) { + buzzed = true; + buzzing = c; + } + }); + let loser = false; + this.serverState.losers.forEach((c) => { + if (c == this.identity.identity) { + loser = true; + }; + }); + + question.style = "display: flex;"; + let header = document.createElement("div"); + header.className = "header"; + header.textContent = q.category + " - " + q.value; + question.appendChild(header); + + let inner = document.createElement("div"); + inner.className = "inner"; + inner.innerHTML = q.clue; + question.appendChild(inner); + + if (!buzzed && !loser && contestant) { + let b = document.createElement("div"); + b.className = "buzz"; + b.textContent = "BUZZ!"; + b.onclick = () => { + this.socket.emit("buzz"); + }; + question.append(b); + } else if (buzzed && host) { + let choice = document.createElement("div"); + choice.className = "choice"; + choice.textContent = "buzzing: " + buzzing.name; + question.append(choice); + + let ack = document.createElement("div"); + ack.className = "ack"; + ack.textContent = "OK"; + ack.onclick = () => { + this.socket.emit("answer", {'ok': true}); + }; + + let nack = document.createElement("div"); + nack.className = "nack"; + nack.textContent = "WRONG"; + nack.onclick = () => { + this.socket.emit("answer", {'ok': false}); + }; + + + choice.appendChild(ack); + choice.appendChild(nack); + } else if (host) { + let choice = document.createElement("div"); + choice.className = "choice"; + choice.textContent = "losers: " + this.serverState.losers.map((e) => { return e.substr(0, 8); }).join(", ") + ", answer: " + this.serverState.question.answer; + question.append(choice); + + let giveup = document.createElement("div"); + giveup.className = "nack"; + giveup.textContent = "CANCEL QUESTION"; + giveup.onclick = () => { + this.socket.emit("give up"); + }; + choice.appendChild(giveup); + } else { + question.appendChild(document.createElement("div")); + }; + document.querySelector("#question").replaceWith(question); +}; + +window.onload = function() { + let nojs = document.querySelector("div.nojs"); + nojs.parentNode.removeChild(nojs); + + let run = async () => { + let g = new Game(); + await g.run(); + g.render(); + } + run().catch((e) => { + console.log(e); + console.log(e.stack); + console.log(e.message); + console.log(e.name); + }); +}; diff --git a/static/socket.io.min.js b/static/socket.io.min.js new file mode 100644 index 0000000..b622e1b --- /dev/null +++ b/static/socket.io.min.js @@ -0,0 +1,3 @@ +!function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var e;"undefined"!=typeof window?e=window:"undefined"!=typeof global?e=global:"undefined"!=typeof self&&(e=self),e.io=t()}}(function(){var t;return function e(t,n,r){function o(s,a){if(!n[s]){if(!t[s]){var c="function"==typeof require&&require;if(!a&&c)return c(s,!0);if(i)return i(s,!0);throw new Error("Cannot find module '"+s+"'")}var p=n[s]={exports:{}};t[s][0].call(p.exports,function(e){var n=t[s][1][e];return o(n?n:e)},p,p.exports,e,t,n,r)}return n[s].exports}for(var i="function"==typeof require&&require,s=0;s0&&!this.encoding){var t=this.packetBuffer.shift();this.packet(t)}},n.prototype.cleanup=function(){for(var t;t=this.subs.shift();)t.destroy();this.packetBuffer=[],this.encoding=!1,this.decoder.destroy()},n.prototype.close=n.prototype.disconnect=function(){this.skipReconnect=!0,this.backoff.reset(),this.readyState="closed",this.engine&&this.engine.close()},n.prototype.onclose=function(t){p("close"),this.cleanup(),this.backoff.reset(),this.readyState="closed",this.emit("close",t),this._reconnection&&!this.skipReconnect&&this.reconnect()},n.prototype.reconnect=function(){if(this.reconnecting||this.skipReconnect)return this;var t=this;if(this.backoff.attempts>=this._reconnectionAttempts)p("reconnect failed"),this.backoff.reset(),this.emitAll("reconnect_failed"),this.reconnecting=!1;else{var e=this.backoff.duration();p("will wait %dms before reconnect attempt",e),this.reconnecting=!0;var n=setTimeout(function(){t.skipReconnect||(p("attempting reconnect"),t.emitAll("reconnect_attempt",t.backoff.attempts),t.emitAll("reconnecting",t.backoff.attempts),t.skipReconnect||t.open(function(e){e?(p("reconnect attempt error"),t.reconnecting=!1,t.reconnect(),t.emitAll("reconnect_error",e.data)):(p("reconnect success"),t.onreconnect())}))},e);this.subs.push({destroy:function(){clearTimeout(n)}})}},n.prototype.onreconnect=function(){var t=this.backoff.attempts;this.reconnecting=!1,this.backoff.reset(),this.updateSocketIds(),this.emitAll("reconnect",t)}},{"./on":4,"./socket":5,"./url":6,backo2:7,"component-bind":8,"component-emitter":9,debug:10,"engine.io-client":11,indexof:42,"object-component":43,"socket.io-parser":46}],4:[function(t,e){function n(t,e,n){return t.on(e,n),{destroy:function(){t.removeListener(e,n)}}}e.exports=n},{}],5:[function(t,e,n){function r(t,e){this.io=t,this.nsp=e,this.json=this,this.ids=0,this.acks={},this.io.autoConnect&&this.open(),this.receiveBuffer=[],this.sendBuffer=[],this.connected=!1,this.disconnected=!0}var o=t("socket.io-parser"),i=t("component-emitter"),s=t("to-array"),a=t("./on"),c=t("component-bind"),p=t("debug")("socket.io-client:socket"),u=t("has-binary");e.exports=n=r;var f={connect:1,connect_error:1,connect_timeout:1,disconnect:1,error:1,reconnect:1,reconnect_attempt:1,reconnect_failed:1,reconnect_error:1,reconnecting:1},h=i.prototype.emit;i(r.prototype),r.prototype.subEvents=function(){if(!this.subs){var t=this.io;this.subs=[a(t,"open",c(this,"onopen")),a(t,"packet",c(this,"onpacket")),a(t,"close",c(this,"onclose"))]}},r.prototype.open=r.prototype.connect=function(){return this.connected?this:(this.subEvents(),this.io.open(),"open"==this.io.readyState&&this.onopen(),this)},r.prototype.send=function(){var t=s(arguments);return t.unshift("message"),this.emit.apply(this,t),this},r.prototype.emit=function(t){if(f.hasOwnProperty(t))return h.apply(this,arguments),this;var e=s(arguments),n=o.EVENT;u(e)&&(n=o.BINARY_EVENT);var r={type:n,data:e};return"function"==typeof e[e.length-1]&&(p("emitting packet with ack id %d",this.ids),this.acks[this.ids]=e.pop(),r.id=this.ids++),this.connected?this.packet(r):this.sendBuffer.push(r),this},r.prototype.packet=function(t){t.nsp=this.nsp,this.io.packet(t)},r.prototype.onopen=function(){p("transport is open - connecting"),"/"!=this.nsp&&this.packet({type:o.CONNECT})},r.prototype.onclose=function(t){p("close (%s)",t),this.connected=!1,this.disconnected=!0,delete this.id,this.emit("disconnect",t)},r.prototype.onpacket=function(t){if(t.nsp==this.nsp)switch(t.type){case o.CONNECT:this.onconnect();break;case o.EVENT:this.onevent(t);break;case o.BINARY_EVENT:this.onevent(t);break;case o.ACK:this.onack(t);break;case o.BINARY_ACK:this.onack(t);break;case o.DISCONNECT:this.ondisconnect();break;case o.ERROR:this.emit("error",t.data)}},r.prototype.onevent=function(t){var e=t.data||[];p("emitting event %j",e),null!=t.id&&(p("attaching ack callback to event"),e.push(this.ack(t.id))),this.connected?h.apply(this,e):this.receiveBuffer.push(e)},r.prototype.ack=function(t){var e=this,n=!1;return function(){if(!n){n=!0;var r=s(arguments);p("sending ack %j",r);var i=u(r)?o.BINARY_ACK:o.ACK;e.packet({type:i,id:t,data:r})}}},r.prototype.onack=function(t){p("calling ack %s with %j",t.id,t.data);var e=this.acks[t.id];e.apply(this,t.data),delete this.acks[t.id]},r.prototype.onconnect=function(){this.connected=!0,this.disconnected=!1,this.emit("connect"),this.emitBuffered()},r.prototype.emitBuffered=function(){var t;for(t=0;t0&&t.jitter<=1?t.jitter:0,this.attempts=0}e.exports=n,n.prototype.duration=function(){var t=this.ms*Math.pow(this.factor,this.attempts++);if(this.jitter){var e=Math.random(),n=Math.floor(e*this.jitter*t);t=0==(1&Math.floor(10*e))?t-n:t+n}return 0|Math.min(t,this.max)},n.prototype.reset=function(){this.attempts=0},n.prototype.setMin=function(t){this.ms=t},n.prototype.setMax=function(t){this.max=t},n.prototype.setJitter=function(t){this.jitter=t}},{}],8:[function(t,e){var n=[].slice;e.exports=function(t,e){if("string"==typeof e&&(e=t[e]),"function"!=typeof e)throw new Error("bind() requires a function");var r=n.call(arguments,2);return function(){return e.apply(t,r.concat(n.call(arguments)))}}},{}],9:[function(t,e){function n(t){return t?r(t):void 0}function r(t){for(var e in n.prototype)t[e]=n.prototype[e];return t}e.exports=n,n.prototype.on=n.prototype.addEventListener=function(t,e){return this._callbacks=this._callbacks||{},(this._callbacks[t]=this._callbacks[t]||[]).push(e),this},n.prototype.once=function(t,e){function n(){r.off(t,n),e.apply(this,arguments)}var r=this;return this._callbacks=this._callbacks||{},n.fn=e,this.on(t,n),this},n.prototype.off=n.prototype.removeListener=n.prototype.removeAllListeners=n.prototype.removeEventListener=function(t,e){if(this._callbacks=this._callbacks||{},0==arguments.length)return this._callbacks={},this;var n=this._callbacks[t];if(!n)return this;if(1==arguments.length)return delete this._callbacks[t],this;for(var r,o=0;or;++r)n[r].apply(this,e)}return this},n.prototype.listeners=function(t){return this._callbacks=this._callbacks||{},this._callbacks[t]||[]},n.prototype.hasListeners=function(t){return!!this.listeners(t).length}},{}],10:[function(t,e){function n(t){return n.enabled(t)?function(e){e=r(e);var o=new Date,i=o-(n[t]||o);n[t]=o,e=t+" "+e+" +"+n.humanize(i),window.console&&console.log&&Function.prototype.apply.call(console.log,console,arguments)}:function(){}}function r(t){return t instanceof Error?t.stack||t.message:t}e.exports=n,n.names=[],n.skips=[],n.enable=function(t){try{localStorage.debug=t}catch(e){}for(var r=(t||"").split(/[\s,]+/),o=r.length,i=0;o>i;i++)t=r[i].replace("*",".*?"),"-"===t[0]?n.skips.push(new RegExp("^"+t.substr(1)+"$")):n.names.push(new RegExp("^"+t+"$"))},n.disable=function(){n.enable("")},n.humanize=function(t){var e=1e3,n=6e4,r=60*n;return t>=r?(t/r).toFixed(1)+"h":t>=n?(t/n).toFixed(1)+"m":t>=e?(t/e|0)+"s":t+"ms"},n.enabled=function(t){for(var e=0,r=n.skips.length;r>e;e++)if(n.skips[e].test(t))return!1;for(var e=0,r=n.names.length;r>e;e++)if(n.names[e].test(t))return!0;return!1};try{window.localStorage&&n.enable(localStorage.debug)}catch(o){}},{}],11:[function(t,e){e.exports=t("./lib/")},{"./lib/":12}],12:[function(t,e){e.exports=t("./socket"),e.exports.parser=t("engine.io-parser")},{"./socket":13,"engine.io-parser":25}],13:[function(t,e){(function(n){function r(t,e){if(!(this instanceof r))return new r(t,e);if(e=e||{},t&&"object"==typeof t&&(e=t,t=null),t&&(t=u(t),e.host=t.host,e.secure="https"==t.protocol||"wss"==t.protocol,e.port=t.port,t.query&&(e.query=t.query)),this.secure=null!=e.secure?e.secure:n.location&&"https:"==location.protocol,e.host){var o=e.host.split(":");e.hostname=o.shift(),o.length?e.port=o.pop():e.port||(e.port=this.secure?"443":"80")}this.agent=e.agent||!1,this.hostname=e.hostname||(n.location?location.hostname:"localhost"),this.port=e.port||(n.location&&location.port?location.port:this.secure?443:80),this.query=e.query||{},"string"==typeof this.query&&(this.query=h.decode(this.query)),this.upgrade=!1!==e.upgrade,this.path=(e.path||"/engine.io").replace(/\/$/,"")+"/",this.forceJSONP=!!e.forceJSONP,this.jsonp=!1!==e.jsonp,this.forceBase64=!!e.forceBase64,this.enablesXDR=!!e.enablesXDR,this.timestampParam=e.timestampParam||"t",this.timestampRequests=e.timestampRequests,this.transports=e.transports||["polling","websocket"],this.readyState="",this.writeBuffer=[],this.callbackBuffer=[],this.policyPort=e.policyPort||843,this.rememberUpgrade=e.rememberUpgrade||!1,this.binaryType=null,this.onlyBinaryUpgrades=e.onlyBinaryUpgrades,this.pfx=e.pfx||null,this.key=e.key||null,this.passphrase=e.passphrase||null,this.cert=e.cert||null,this.ca=e.ca||null,this.ciphers=e.ciphers||null,this.rejectUnauthorized=e.rejectUnauthorized||null,this.open()}function o(t){var e={};for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n]);return e}var i=t("./transports"),s=t("component-emitter"),a=t("debug")("engine.io-client:socket"),c=t("indexof"),p=t("engine.io-parser"),u=t("parseuri"),f=t("parsejson"),h=t("parseqs");e.exports=r,r.priorWebsocketSuccess=!1,s(r.prototype),r.protocol=p.protocol,r.Socket=r,r.Transport=t("./transport"),r.transports=t("./transports"),r.parser=t("engine.io-parser"),r.prototype.createTransport=function(t){a('creating transport "%s"',t);var e=o(this.query);e.EIO=p.protocol,e.transport=t,this.id&&(e.sid=this.id);var n=new i[t]({agent:this.agent,hostname:this.hostname,port:this.port,secure:this.secure,path:this.path,query:e,forceJSONP:this.forceJSONP,jsonp:this.jsonp,forceBase64:this.forceBase64,enablesXDR:this.enablesXDR,timestampRequests:this.timestampRequests,timestampParam:this.timestampParam,policyPort:this.policyPort,socket:this,pfx:this.pfx,key:this.key,passphrase:this.passphrase,cert:this.cert,ca:this.ca,ciphers:this.ciphers,rejectUnauthorized:this.rejectUnauthorized});return n},r.prototype.open=function(){var t;if(this.rememberUpgrade&&r.priorWebsocketSuccess&&-1!=this.transports.indexOf("websocket"))t="websocket";else{if(0==this.transports.length){var e=this;return void setTimeout(function(){e.emit("error","No transports available")},0)}t=this.transports[0]}this.readyState="opening";var t;try{t=this.createTransport(t)}catch(n){return this.transports.shift(),void this.open()}t.open(),this.setTransport(t)},r.prototype.setTransport=function(t){a("setting transport %s",t.name);var e=this;this.transport&&(a("clearing existing transport %s",this.transport.name),this.transport.removeAllListeners()),this.transport=t,t.on("drain",function(){e.onDrain()}).on("packet",function(t){e.onPacket(t)}).on("error",function(t){e.onError(t)}).on("close",function(){e.onClose("transport close")})},r.prototype.probe=function(t){function e(){if(h.onlyBinaryUpgrades){var e=!this.supportsBinary&&h.transport.supportsBinary;f=f||e}f||(a('probe transport "%s" opened',t),u.send([{type:"ping",data:"probe"}]),u.once("packet",function(e){if(!f)if("pong"==e.type&&"probe"==e.data){if(a('probe transport "%s" pong',t),h.upgrading=!0,h.emit("upgrading",u),!u)return;r.priorWebsocketSuccess="websocket"==u.name,a('pausing current transport "%s"',h.transport.name),h.transport.pause(function(){f||"closed"!=h.readyState&&(a("changing transport and sending upgrade packet"),p(),h.setTransport(u),u.send([{type:"upgrade"}]),h.emit("upgrade",u),u=null,h.upgrading=!1,h.flush())})}else{a('probe transport "%s" failed',t);var n=new Error("probe error");n.transport=u.name,h.emit("upgradeError",n)}}))}function n(){f||(f=!0,p(),u.close(),u=null)}function o(e){var r=new Error("probe error: "+e);r.transport=u.name,n(),a('probe transport "%s" failed because of error: %s',t,e),h.emit("upgradeError",r)}function i(){o("transport closed")}function s(){o("socket closed")}function c(t){u&&t.name!=u.name&&(a('"%s" works - aborting "%s"',t.name,u.name),n())}function p(){u.removeListener("open",e),u.removeListener("error",o),u.removeListener("close",i),h.removeListener("close",s),h.removeListener("upgrading",c)}a('probing transport "%s"',t);var u=this.createTransport(t,{probe:1}),f=!1,h=this;r.priorWebsocketSuccess=!1,u.once("open",e),u.once("error",o),u.once("close",i),this.once("close",s),this.once("upgrading",c),u.open()},r.prototype.onOpen=function(){if(a("socket open"),this.readyState="open",r.priorWebsocketSuccess="websocket"==this.transport.name,this.emit("open"),this.flush(),"open"==this.readyState&&this.upgrade&&this.transport.pause){a("starting upgrade probes");for(var t=0,e=this.upgrades.length;e>t;t++)this.probe(this.upgrades[t])}},r.prototype.onPacket=function(t){if("opening"==this.readyState||"open"==this.readyState)switch(a('socket receive: type "%s", data "%s"',t.type,t.data),this.emit("packet",t),this.emit("heartbeat"),t.type){case"open":this.onHandshake(f(t.data));break;case"pong":this.setPing();break;case"error":var e=new Error("server error");e.code=t.data,this.emit("error",e);break;case"message":this.emit("data",t.data),this.emit("message",t.data)}else a('packet received with socket readyState "%s"',this.readyState)},r.prototype.onHandshake=function(t){this.emit("handshake",t),this.id=t.sid,this.transport.query.sid=t.sid,this.upgrades=this.filterUpgrades(t.upgrades),this.pingInterval=t.pingInterval,this.pingTimeout=t.pingTimeout,this.onOpen(),"closed"!=this.readyState&&(this.setPing(),this.removeListener("heartbeat",this.onHeartbeat),this.on("heartbeat",this.onHeartbeat))},r.prototype.onHeartbeat=function(t){clearTimeout(this.pingTimeoutTimer);var e=this;e.pingTimeoutTimer=setTimeout(function(){"closed"!=e.readyState&&e.onClose("ping timeout")},t||e.pingInterval+e.pingTimeout)},r.prototype.setPing=function(){var t=this;clearTimeout(t.pingIntervalTimer),t.pingIntervalTimer=setTimeout(function(){a("writing ping packet - expecting pong within %sms",t.pingTimeout),t.ping(),t.onHeartbeat(t.pingTimeout)},t.pingInterval)},r.prototype.ping=function(){this.sendPacket("ping")},r.prototype.onDrain=function(){for(var t=0;tn;n++)~c(this.transports,t[n])&&e.push(t[n]);return e}}).call(this,"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./transport":14,"./transports":15,"component-emitter":9,debug:22,"engine.io-parser":25,indexof:42,parsejson:34,parseqs:35,parseuri:36}],14:[function(t,e){function n(t){this.path=t.path,this.hostname=t.hostname,this.port=t.port,this.secure=t.secure,this.query=t.query,this.timestampParam=t.timestampParam,this.timestampRequests=t.timestampRequests,this.readyState="",this.agent=t.agent||!1,this.socket=t.socket,this.enablesXDR=t.enablesXDR,this.pfx=t.pfx,this.key=t.key,this.passphrase=t.passphrase,this.cert=t.cert,this.ca=t.ca,this.ciphers=t.ciphers,this.rejectUnauthorized=t.rejectUnauthorized}var r=t("engine.io-parser"),o=t("component-emitter");e.exports=n,o(n.prototype),n.timestamps=0,n.prototype.onError=function(t,e){var n=new Error(t);return n.type="TransportError",n.description=e,this.emit("error",n),this},n.prototype.open=function(){return("closed"==this.readyState||""==this.readyState)&&(this.readyState="opening",this.doOpen()),this},n.prototype.close=function(){return("opening"==this.readyState||"open"==this.readyState)&&(this.doClose(),this.onClose()),this},n.prototype.send=function(t){if("open"!=this.readyState)throw new Error("Transport not open");this.write(t)},n.prototype.onOpen=function(){this.readyState="open",this.writable=!0,this.emit("open")},n.prototype.onData=function(t){var e=r.decodePacket(t,this.socket.binaryType);this.onPacket(e)},n.prototype.onPacket=function(t){this.emit("packet",t)},n.prototype.onClose=function(){this.readyState="closed",this.emit("close")}},{"component-emitter":9,"engine.io-parser":25}],15:[function(t,e,n){(function(e){function r(t){var n,r=!1,a=!1,c=!1!==t.jsonp;if(e.location){var p="https:"==location.protocol,u=location.port;u||(u=p?443:80),r=t.hostname!=location.hostname||u!=t.port,a=t.secure!=p}if(t.xdomain=r,t.xscheme=a,n=new o(t),"open"in n&&!t.forceJSONP)return new i(t);if(!c)throw new Error("JSONP disabled");return new s(t)}var o=t("xmlhttprequest"),i=t("./polling-xhr"),s=t("./polling-jsonp"),a=t("./websocket");n.polling=r,n.websocket=a}).call(this,"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./polling-jsonp":16,"./polling-xhr":17,"./websocket":19,xmlhttprequest:20}],16:[function(t,e){(function(n){function r(){}function o(t){i.call(this,t),this.query=this.query||{},a||(n.___eio||(n.___eio=[]),a=n.___eio),this.index=a.length;var e=this;a.push(function(t){e.onData(t)}),this.query.j=this.index,n.document&&n.addEventListener&&n.addEventListener("beforeunload",function(){e.script&&(e.script.onerror=r)},!1)}var i=t("./polling"),s=t("component-inherit");e.exports=o;var a,c=/\n/g,p=/\\n/g;s(o,i),o.prototype.supportsBinary=!1,o.prototype.doClose=function(){this.script&&(this.script.parentNode.removeChild(this.script),this.script=null),this.form&&(this.form.parentNode.removeChild(this.form),this.form=null,this.iframe=null),i.prototype.doClose.call(this)},o.prototype.doPoll=function(){var t=this,e=document.createElement("script");this.script&&(this.script.parentNode.removeChild(this.script),this.script=null),e.async=!0,e.src=this.uri(),e.onerror=function(e){t.onError("jsonp poll error",e)};var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(e,n),this.script=e;var r="undefined"!=typeof navigator&&/gecko/i.test(navigator.userAgent);r&&setTimeout(function(){var t=document.createElement("iframe");document.body.appendChild(t),document.body.removeChild(t)},100)},o.prototype.doWrite=function(t,e){function n(){r(),e()}function r(){if(o.iframe)try{o.form.removeChild(o.iframe)}catch(t){o.onError("jsonp polling iframe removal error",t)}try{var e='