!7 static CirclesAndLines cal; static Thinker thinker; !include #1007377 // traits p-pretty { cal = new CirclesAndLines; makeGraph(); Canvas canvas = cal.show(900, 600); (thinker = nu(Thinker, +cal, +canvas, resetGraph := f makeGraph, think := f think)).init(); } svoid makeGraph { mergedNodes = 0; cal.clear(); Circle a = cal.circle_autoVis("PLAYER", "Chess Player", 0.25, 0.7); Circle b = cal.circle_autoVis("PLAYER", "Chess Player", 0.75, 0.7); cal.arrow(a, "VS", b); cal.circle_autoVis("MAGNUS CARLSEN", 0.25, 0.25); cal.circle_autoVis("HIKARU NAKAMURA", 0.75, 0.25); } sbool think() { // Find an isolated node and try to merge it with another node if (!anyIsolatedNodes()) false; Pair p = findTwoRandomNodes(); if (p != null && isIsolated(p.a) && nodesAttract(p.a, p.b)) mergeNodes(p.a, p.b); true; } sbool traitsAttract(S a, S b) { ret isPair(a, b, "player", "magnus carlsen") || isPair(a, b, "player", "hikaru nakamura"); } sbool traitsDiverge(S a, S b) { ret isPair(a, b, "magnus carlsen", "hikaru nakamura"); } sbool anyIsolatedNodes() { for (Circle c : cal.circles) if (isIsolated(c)) true; false; } sbool isIsolated(Circle c) { for (Line l : cal.lines) if (l.a == c || l.b == c) false; true; } static Pair findTwoRandomNodes() { ret selectRandomPair(cal.circles); } sbool nodesAttract(Circle a, Circle b) { if (nodesDiverge(a, b)) false; for (S x : a.traits()) for (S y : b.traits()) if (traitsAttract(x, y)) true; false; } sbool nodesDiverge(Circle a, Circle b) { for (S x : a.traits()) for (S y : b.traits()) if (traitsDiverge(x, y)) true; false; } sbool isPair(S a, S b, S x, S y) { ret eqic(a, x) && eqic(b, y) || eqic(a, y) && eqic(b, x); } static int mergedNodes; svoid mergeNodes(Circle a, Circle b) { assertTrue("Circle must be isolated to merge", isIsolated(a)); b.img = a.img; b.addTraits(a.traits()); cal.circles.remove(a); thinker.status("Merged " + n(mergedNodes += 2, "node") + "."); } sclass Thinker { double delay = 0.1; // seconds bool continueOnError = true; CirclesAndLines cal; Canvas canvas; JTextArea textArea; JButton btnThink; volatile bool thinking; S status, initialStatus = "Initial state."; int step; bool inited; O think; // func -> bool O resetGraph; // runnable Throwable error; void init { if (inited) ret; inited = true; addControls(); thread "Think" { Thinker.this.run(); } } void addControls { swing { addToWindowRight(canvas, withMargin(vstackWithSpacing( jMaxWidth(167, jMinHeight(180, jscroll(textArea = setFontSize(16, makeBold(wordWrapTypeWriterTextArea()))))), btnThink = fatButton(100, "Think!", r { thinkOrStop() }), fatButton(60, "Think again!", r { thinkAgain() }), fatButton(60, "Reset", r { reset() }) ))); increaseFrameWidth(canvas, 180); status(initialStatus); } } void startOver { lock(cal.lock, "StartOver", 2000); try { step = 0; status(initialStatus); callF(resetGraph); } finally { unlock(cal.lock, "StartOver"); } if (canvas != null) canvas.update(); } void thinkAgain { startOver(); thinking(true); } void reset { thinking(false); startOver(); } void run { repeat { sleepSeconds(delay); if (thinking) pcall { print("Thinking"); bool t; time { t = think(); } if (!t) { thinking(false); print("Done"); } else { print("Updating"); if (canvas != null) canvas.update(); } } } } void thinking(bool b) { thinking = b; status(); setText(btnThink, b ? "
Stop
thinking
" : "Think!"); } void status(S s) { status = s; status(); } void thinkOrStop { thinking(!thinking); } bool think() { if (think == null) false; lock(cal.lock, "Thinking", 5000); try { step++; status(); if (isFalse(callF(think))) { step--; status(); false; } true; } catch e { printStackTrace(e); error = getInnerException(e); ret continueOnError; } finally { unlock(cal.lock, "Done thinking"); } } JButton fatButton(int h, S text, O action) { JButton btn = jbutton(text, action); btn.setFont(sansSerifBold(16)); ret jMinSize(167, h, btn); } void status { setText(textArea, status + "\n\n" + (thinking ? "Thinking...\n\n" : "") + (step == 0 ? "No thoughts made yet." : "Thoughts made: " + step) + (error == null ? "" : "\n\nERROR: " + exceptionToStringShort(error))); } }