!759 // by Stefan Reich and Omar Khan static abstract class Status { abstract S answer(S s); } // Actually it's not really a status because it moves to the next status immediately (so we could get rid of this class) static class InGame extends Status { QuestionNode node; *() {} *(QuestionNode *node) {} S answer(S s) { if (node.isAnswer()) { status(new IsIt(node)); ret "Would your object happen to be " + node.data + "?"; } else { status(new DoesIt(node)); ret node.data; // the question } } } static class IsIt extends Status { QuestionNode node; *() {} *(QuestionNode *node) {} S answer(S s) { if (isYes(s)) { status(null); ret "I win!"; } else if (isNo(s)) { status(new WhatIsIt(node)); ret "I lose. What is your object?"; } else ret "Yes or no, sir?"; } } static class WhatIsIt extends Status { QuestionNode node; *() {} *(QuestionNode *node) {} S answer(S s) { status(new GiveQuestion(node, s)); ret "Please type a question that is answered with \"yes\" for " + quote(s) + " and with \"no\" for " + quote(node.data) + "."; } } static class GiveQuestion extends Status { QuestionNode node; S objectName; *() {} *(QuestionNode *node, S *objectName) {} S answer(S s) { // original node goes to "no" QuestionNode no = new QuestionNode(node.data); no.yes = node.yes; no.no = node.no; node.data = s; // question entered by user node.yes = new QuestionNode(objectName); node.no = no; // save save("root"); status(new PlayAgain); ret "Thanks, I learned this :-)) Play again?"; } } static S play() { status(new InGame(root)); ret "Alrighty! Type 'exit' at any time to cancel the game. Think of an object! Now tell me: " + status.answer(""); } static class PlayAgain extends Status { S answer(S s) { if (isYes(s)) { ret play(); } else { status(null); ret "OK, goodbye :) To play again, say \"play 20 questions\"."; } } } static class DoesIt extends Status { QuestionNode node; *() {} *(QuestionNode *node) {} S answer(S s) { if (isYes(s)) { status(new InGame(node.yes)); ret status.answer(""); } else if (isNo(s)) { status(new InGame(node.no)); ret status.answer(""); } else ret "Yes or no, sir?"; } } static QuestionNode root = new QuestionNode("computer"); static Status status; static S dialogID; p { load("root"); load("status"); load("dialogID"); } answer { if (match("20 questions", s) || match("play 20 questions", s)) { dialogID = getDialogID(); save("dialogID"); ret play(); } else if (eq(getDialogID(), dialogID) && status != null) { if "exit" { status(null); ret "OK, game cancelled."; } ret status.answer(s); } // inspect game structures if "20 questions dump" ret structure(root); if "20 questions find *" ret structure(findNode(m.unq(0))); if "20 questions fix question * *" { if (!onSlack()) ret "Sorry... go on Slack for this"; QuestionNode node = findNode(m.unq(0)); if (node == null) ret "Node not found, sorry"; fixlog(s); node.data = m.unq(1); save("root"); status(null); ret "OK, fixed!"; } // question, branch to keep (yes/no) if "20 questions collapse question * *" { if (!onSlack()) ret "Sorry... go on Slack for this"; QuestionNode node = findNode(m.unq(0)); if (node == null) ret "Node not found, sorry"; S field; // the one to keep if (isYes(m.unq(1))) field = "yes"; else if (isNo(m.unq(1))) field = "no"; else ret "Yes or no? " + quote(m.unq(1)); fixlog(s); QuestionNode sub = cast get(node, field); node.data = sub.data; node.yes = sub.yes; node.no = sub.no; save("root"); status(null); ret "OK, collapsed!"; } } static void fixlog(S s) { logStructure("fixlog", s); logStructure("fixlog", root); } static void status(Status s) { status = s; save("status"); } // QuestionNodes have a String data field as well as a reference to two other QuestionNodes. static class QuestionNode{ public String data; public QuestionNode yes; public QuestionNode no; //Constructs a new QuestionNode with no references so it is a leaf node. *(String *data) {} *() {} //Checks it the QuestionNode is a leaf node. public boolean isAnswer(){ return yes == null && no == null; } } static QuestionNode findNode(S q) { for (O node : scanTree(root, litlist("yes", "no"))) if (match(q, getString(node, "data"))) ret (QuestionNode) node; null; }