diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6769724 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.vscode/* +game/flask_session/* +Chatbox/flask_session/* \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..99eac10 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.pythonPath": "C:\\Users\\tranq\\AppData\\Local\\Programs\\Python\\Python39\\python.exe" +} \ No newline at end of file diff --git a/Chatbox/__pycache__/bot.cpython-39.pyc b/Chatbox/__pycache__/bot.cpython-39.pyc new file mode 100644 index 0000000..fb70ffb Binary files /dev/null and b/Chatbox/__pycache__/bot.cpython-39.pyc differ diff --git a/Chatbox/__pycache__/sessionManager.cpython-39.pyc b/Chatbox/__pycache__/sessionManager.cpython-39.pyc new file mode 100644 index 0000000..937e005 Binary files /dev/null and b/Chatbox/__pycache__/sessionManager.cpython-39.pyc differ diff --git a/Chatbox/bot.py b/Chatbox/bot.py new file mode 100644 index 0000000..12ba9b1 --- /dev/null +++ b/Chatbox/bot.py @@ -0,0 +1,14 @@ +class botmessage(object): + def __init__(self, sessId): + self.sessID = sessId + self.botOutput = sessId + + def botmes(self, userInput): + if userInput == "Hello": + #mess = botmessage(sessionID) + botOutput = "Hi " + self.sessID + return botOutput + else: + #messtwo = botmessage(sessionID) + botOutput = "Bye " + self.sessID + return botOutput \ No newline at end of file diff --git a/Chatbox/chatbox_flask.py b/Chatbox/chatbox_flask.py new file mode 100644 index 0000000..b9a955b --- /dev/null +++ b/Chatbox/chatbox_flask.py @@ -0,0 +1,183 @@ +import uuid +from flask import Flask, render_template, jsonify, request, session, redirect, url_for +from flask_session import Session + +import os + +app = Flask(__name__) +app.secret_key = os.urandom(24) +app.config["SESSION_TYPE"] = "filesystem" +Session(app) + +# separate route to deal with tutorial +@app.route("/tutorial", methods=["GET", "POST"]) +def tutorial(): + # tutorial action followed by a hint message + actionList = [{"name":"pointAt", "args":"aspirin"}, {"name":"pointAt", "args":"monday noon"}] + hintList = ["first click on an aspirin pill to grab", "now click on the highlighted position on a calendar to drop the pill"] + + if request.method == "POST": + pos = int(request.form["position"]) # get current step in list of tutorial actions + response = request.form["response"] + proceed = False + + # only proceeds if user remove from container or add to grid + # TODO: make it more dynamic + if "remove_from_container aspirin" in response or "add_to_grid" in response: + proceed = True + + # check if the current action is the last or not + if pos < len(actionList): + output = {"action": actionList[pos], "hint": hintList[pos], "proceed": proceed} + else: + output = None + return jsonify(output) + +# route for communicating back and forth between UI and server +@app.route("/message", methods=["GET", "POST"]) +def message(): + if request.method == "POST": + print(request.form["botm"]) + message = request.form["botm"] + + # data to send to UI (may be combined into 1 variable) + action = {} + hint = "" + start = "" + state = "speaking" + time_mapping = ["none", "morning", "noon", "afternoon", "evening"] + day_mapping = {"mon":"Monday", "tue":"Tuesday", "wed":"Wednesday", "thu":"Thursday", "fri":"Friday", +"sat":"Saturday", "sun":"Sunday"} + + # functions to check for invalid user action (may be moved to a separate route) + # check if number of 1 type medication is too large for a time in a day + def numCheck(): + print(session.get("calendar_count")) + for position, medCount in session.get("calendar_count").items(): + for med, count in medCount.items(): + if count > 2: + return (med, position, count) + return None + + # check if ibuprofen and aspirin are in the same position (used together) + def interactionCheck(): + for position, medCount in session.get("calendar_count").items(): + if "ibuprofen" in medCount and "aspirin" in medCount and medCount["ibuprofen"] > 0 and medCount["aspirin"] > 0: + return position + return None + + # manually respond to user action/message + if "hello" in message: + start = "hello" + elif "bye" in message or "goodbye" in message: + state = "sleeping" + start = "see ya later!" + elif "thank you" in message: + start = "you are welcome!" + elif "no" in message and session.get("toUser")[-1]["state"] == "questioning": # may include problem/question type? + state = "speaking" + hint = "here is an image to help" + action = {"name":"showImage", "args":"static/pill_interaction.png"} + elif "what" in message and session.get("toUser")[-1]["state"] == "questioning": + state = "questioning" + hint = "Do you remember the interaction for the blue pill?" + elif "add_to_grid" in message: + messageList = message[1:-1].split(" ") + med = messageList[1] + position = messageList[2][:3].lower() + str(time_mapping.index(messageList[-1])) + + # increase number of pill in calendar position + if position not in session.get("calendar_count"): + session.get("calendar_count")[position] = {med: 1} + else: + if med in session.get("calendar_count")[position]: + session.get("calendar_count")[position][med] += 1 + else: + session.get("calendar_count")[position][med] = 1 + + # check if ibuprofen is used in the morning + if med == "ibuprofen" and messageList[-1] == "morning": + hint = "ibuprofen should not be used in the morning" + action = {"name":"pointAt", "args": messageList[2] + " " + messageList[-1]} + else: + # check if there are no position with more than 2 pills of any type + overdose = numCheck() + if overdose != None: + day = day_mapping[overdose[1][:3]] + time = time_mapping[int(overdose[1][-1])] + hint = "too many " + overdose[0] + " on " + day + " " + time + action = {"name":"pointAt", "args":day + " " + time} + # check for interaction conflict is there is any + else: + incompatible = interactionCheck() + print(incompatible) + if incompatible != None: + day = day_mapping[incompatible[:3]] + time = time_mapping[int(incompatible[-1])] + state = "questioning" + hint = "huhhhhhh????" + + elif "remove_from_grid" in message: + messageList = message[1:-1].split(" ") + med = messageList[1] + position = messageList[2][:3].lower() + str(time_mapping.index(messageList[-1])) + # decrease number of pills of a type in a calendar positon + session.get("calendar_count")[position][med] -= 1 + if (session.get("calendar_count")[position][med] < 0): + session.get("calendar_count")[position][med] = 0 + + output = {"start": start, "action": action, "hint": hint, "state":state, "userInput":message} + # keep track of past conversation between user and agent + session.get("fromUser").append(message) + session.get("toUser").append(output) + return jsonify(output) + +#################### PREVIOUS PROJECT CODE ####################### +#Session Manager +# @app.route('/processOne', methods=['GET', 'POST']) +# def processOne(): +# # Session Manager +# # @app.route('/process', methods=['GET', 'POST']) +# # def process(): +# userm = request.form['botm'] +# botm = sessionManage(userm, session['user']) +# state = botm['state'] +# hint = botm['hint'] + +# return jsonify(botm) + +# @app.route('/processTwo', methods=['GET', 'POST']) +# def processTwo(): +# print ("process two being called") +# message = newSessionTwo(session['user']) +# output = {'start': message} +# print (message) +# return jsonify(output) +############################################################# + +# initializes the game at the beginning +@app.route('/startgame', methods=['GET', 'POST']) +def startgame(): + # message = newSession(session['user']) + session["calendar_count"] = {} + session["fromUser"] = [] + session["toUser"] = [] + + # configurations for med sorting game + if "events" not in session: + session["events"] = [{"name": "exercise", "day": "mon", "time":"1"}, {"name":"appointment", "day":"thu", "time":"2"}, + {"name":"work", "day":"sat","time":"3"}] + + if "medications" not in session: + session["medications"] = [{"name":"ibuprofen", "color":"red", "number":"11"}, {"name":"aspirin", "color":"blue", "number":"15"}, + {"name":"albuterol", "color":"green", "number":"7"}] + + output = {"events": session.get("events"), "medications": session.get("medications"), "hint":"Game started! Do you want to see the tutorial?"} + return jsonify(output) + +@app.route('/', methods=['GET']) +def chatbox(): + return render_template("index.html") + +if __name__ == "__main__": + app.run(debug=True) diff --git a/Chatbox/sessionManager.py b/Chatbox/sessionManager.py new file mode 100644 index 0000000..0b1c6f2 --- /dev/null +++ b/Chatbox/sessionManager.py @@ -0,0 +1,38 @@ +import sys, os +cur = os.path.dirname(os.path.realpath(__file__)) + "/" +sys.path.insert(0, cur + "../") + +# from Chatbox.bot import botmessage +# import planners.pyhop.pyhop +# import game.game +# from model.maze import operators +# from model.maze import methods +# import csv + +# sessionDictionary = {} + +# def newSession(sessionID): +# ph = planners.pyhop.pyhop.Pyhop('hoppity') +# #if (trial == 0): +# session = game.game.GenericPyhopGame('model/lightbulb/game.config', ph) +# sessionDictionary[sessionID] = session +# output = session.start_game(sessionID) + + +# return output + +# def newSessionTwo(sessionID): +# ph = planners.pyhop.pyhop.Pyhop('hoppity') +# session = game.game.GenericPyhopGame('model/maze/game.config', ph) +# sessionDictionary[sessionID] = session +# output = session.start_game(sessionID) + +# return output + +# def sessionManage(userInput, sessionID): +# if sessionID in sessionDictionary: +# session = sessionDictionary[sessionID] +# output = session.handle_user_input(userInput) +# else: +# output = "No game initialized" +# return output diff --git a/Chatbox/static/avatar.js b/Chatbox/static/avatar.js new file mode 100644 index 0000000..ad8ae2b --- /dev/null +++ b/Chatbox/static/avatar.js @@ -0,0 +1,38 @@ +function animateAvatar(state) { + switch(state) { + // add new questioning state in which some question marks appear over avatar's head + case "questioning": + $("#eye1, #eye2").removeClass('eye-down eye-sleep').addClass('eye'); + $(".ball").css("animation", "bounce 2s ease-out 3"); + $(".shadow").css("animation", "shadow-bounce 2s ease-out 3"); + $("#questionMark").css("visibility", "visible").css("animation", "wobble 1s 3 both"); + break; + case "speaking": + $("#eye1, #eye2").removeClass('eye-down eye-sleep').addClass('eye'); + $(".ball").css("animation", "bounce 2s ease-out 2"); + $(".shadow").css("animation", "shadow-bounce 2s ease-out 2"); + $("#questionMark").css("visibility", "hidden"); + break; + case "thinking": + $("#eye1, #eye2").removeClass('eye eye-sleep').addClass('eye-down'); + $(".ball").css("animation", "lil-bounce 2s linear infinite"); + $(".shadow").css("animation", "shadow-lil-bounce 2s linear infinite"); + $("#questionMark").css("visibility", "hidden"); + break; + case "listening": + $("#eye1, #eye2").removeClass('eye-down eye-sleep').addClass('eye'); + $(".ball").css("animation", "breathe 3s linear infinite"); + $(".shadow").css("animation", "none"); + $("#questionMark").css("visibility", "hidden"); + break; + case "sleeping": + $("#eye1, #eye2").removeClass('eye eye-down').addClass('eye-sleep'); + $(".ball").css("animation", "to-roll 1s, roll 5s linear infinite"); + $(".shadow").css("animation", "shadow-roll 5s linear infinite"); + $("#questionMark").css("visibility", "hidden"); + break; + default: + // TODO output to debug log + console.log("unknown state: " + state); + } + } \ No newline at end of file diff --git a/Chatbox/static/avatarSpace.css b/Chatbox/static/avatarSpace.css new file mode 100644 index 0000000..066208e --- /dev/null +++ b/Chatbox/static/avatarSpace.css @@ -0,0 +1,250 @@ +.ball { + display: block; + background: radial-gradient(circle at 195px 105px, #330099, #000); + border-radius: 50%; + height: 300px; + width: 300px; + margin: 0 auto; + position: relative; + top: 175px; + transition: transform 1s; + } + .ball:before { + content: ""; + position: absolute; + background: radial-gradient(circle at 50% 120%, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.4) 70%); + border-radius: 50%; + bottom: 3.5%; + left: 4.5%; + opacity: 0.4; + height: 100%; + width: 95%; + filter: blur(5px); + z-index: 2; + } + .ball:after { + content: ""; + width: 100%; + height: 100%; + position: absolute; + top: -1%; + left: 15%; + border-radius: 50%; + background: radial-gradient(circle at 20% 20%, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.8) 14%, rgba(255, 255, 255, 0) 24%); + transform: translateX(80px) translateY(-90px) skewX(-20deg); + filter: blur(10px); + } + + .shadow { + position: relative; + width: 260px; + height: 16px; + background: #999; + opacity: .5; + border-radius: 100%; + margin: 0 auto; + top: 170px; + z-index: -1; + } + + #eye1 { + left: -45px; + top: -20px; + transform: skewX(-5deg) skewY(-2deg); + } + + #eye2 { + left: 75px; + top: -20px; + transform: skewX(5deg) skewY(2deg); + } + + #questionMark { + position: relative; + color: red; + font-size: 70px; + bottom: 80px; + left: 275px; + visibility: hidden; + z-index: 1; + } + + .wobble { + visibility: visible; + animation: wobble 1s 1s 3; + } + + .eye { + width: 30%; + height: 30%; + margin: 30%; + border-radius: 50%; + background: radial-gradient(circle at 50% 50%, #208ab4 0%, #6fbfff 30%, #4381b2 100%); + position: absolute; + } + .eye:before { + content: ""; + display: block; + position: absolute; + width: 37.5%; + height: 37.5%; + border-radius: 50%; + top: 31.25%; + left: 31.25%; + background: black; + } + .eye:after { + content: ""; + display: block; + position: absolute; + width: 31.25%; + height: 31.25%; + border-radius: 50%; + top: 18.75%; + left: 48.75%; + background: rgba(255, 255, 255, 0.2); + } + + .eye-down { + width: 30%; + height: 30%; + margin: 30%; + border-radius: 50%; + background: radial-gradient(circle at 50% 70%, #208ab4 0%, #6fbfff 30%, #330099 100%); + position: absolute; + } + .eye-down:before { + content: ""; + display: block; + position: absolute; + width: 37.5%; + height: 37.5%; + border-radius: 50%; + top: 41.25%; + left: 31.25%; + background: black; + animation: look-down 100s ease-out infinite; + } + .eye-down:after { + content: ""; + display: block; + position: absolute; + width: 31.25%; + height: 31.25%; + border-radius: 50%; + top: 33.75%; + left: 48.75%; + background: rgba(255, 255, 255, 0.2); + } + + .eye-sleep { + width: 30%; + height: 30%; + margin: 30%; + border-radius: 50%; + background: radial-gradient(circle at 50% 70%, #4400aa 0%, #3700aa 30%, #330099 100%); + position: absolute; + transition: background 2s; + } + .eye-sleep:before { + content: ""; + display: block; + position: absolute; + width: 87.5%; + height: 37.5%; + border: solid 3px; + border-color: transparent transparent black transparent; + border-radius: 0 0 50% 50%; + top: 11.25%; + left: 0; + } + + /***** ANIMATIONS *****/ + /*** Speaking ***/ + @keyframes bounce { + 0% { transform: scale(1); } + 40% { transform: translateY(-20px) scaleY(1.05) scaleX(0.95); } + 80% { transform: scaleY(0.95) scaleX(1.05); } + 100% { transform: scale(1); } + } + @keyframes shadow-bounce { + 0% { transform: scale(1); } + 40% { transform: margin: 0% 0% 0% 0%; opacity: 0.1; transform: scaleX(0.95) translateX(-5px);} + 80% { transform: margin: 0% 20% 0% 20%; opacity: 0.5; transform: scaleX(1.05);} + 100% { transform: scale(1); } + } + + /*** Thinking ***/ + @keyframes lil-bounce { + 0% { transform: scale(1); } + 40% { transform: translateY(-5px) scaleY(1.05) scaleX(0.95); } + 80% { transform: scaleY(0.95) scaleX(1.05); } + 100% { transform: scale(1); } + } + @keyframes shadow-lil-bounce { + 0% { transform: scale(1); } + 40% { transform: margin: 0% 10% 0% 10%; opacity: 0.4; transform: scaleX(0.98) translateX(-2px);} + 80% { transform: margin: 0% 20% 0% 20%; opacity: 0.5; transform: scaleX(1.05);} + 100% { transform: scale(1); } + } + /* eyes */ + @keyframes look-down { + 5% { transform: translateY(35%) translateX(-5%); } + 10% { transform: translateY(35%) translateX(-10%); } + 15% { transform: translateY(35%) translateX(-5%); } + 25% { transform: translateY(35%) translateX(-10%) ; } + 50% { transform: translateY(35%) translateX(0%); } + 65% { transform: translateY(35%) translateX(-5%); } + 75% { transform: translateY(35%) translateX(0%) ; } + 100% { transform: translateY(35%) translateX(-5%); } + } + + /*** Sleeping ***/ + @keyframes roll { + 0% { transform: rotate(-5deg) scale(1); background: radial-gradient(circle at 130px 70px, #320088, #000); } + 50% { transform: rotate(-15deg) scale(0.9) translateY(12px); background: radial-gradient(circle at 130px 70px, #320084, #000); } + 100% { transform: rotate(-5deg) scale(1); background: radial-gradient(circle at 130px 70px, #320088, #000); } + } + @keyframes shadow-roll { + 0% { transform: scale(0.95); } + 50% { transform: translateX(-5px) scale(0.9);} + 100% { transform: scale(0.95); } + } + + /*** Sleeping ***/ + @keyframes to-roll { + 0% { transform: scale(1); } + 100% { transform: rotate(-5deg) scale(1); + background: radial-gradient(circle at 130px 70px, #330088, #000); } + } + + /*** Listening ***/ + @keyframes breathe { + 0% { transform: scale(1); } + 75% { transform: scale(1.03) translateY(-2px) ; } + 100% { transform: scale(1); } + } + + /*** Questioning ***/ + @keyframes wobble { + 0%, + 100% { + transform: translateX(0%); + /* transform-origin: 50% 50%; */ + } + 15% { + transform: translateX(-30px) rotate(-6deg); + } + 30% { + transform: translateX(15px) rotate(6deg); + } + 45% { + transform: translateX(-15px) rotate(-3.6deg); + } + 60% { + transform: translateX(9px) rotate(2.4deg); + } + 75% { + transform: translateX(-6px) rotate(-1.2deg); + } + } \ No newline at end of file diff --git a/Chatbox/static/chat.css b/Chatbox/static/chat.css new file mode 100644 index 0000000..8cb59b7 --- /dev/null +++ b/Chatbox/static/chat.css @@ -0,0 +1,83 @@ +#title { + font-size: 25px; + padding: 10px; + background-color: #4E2A84; + color: white; + border-radius: 20px 20px 0 0; + } + + #topSpace { + border: 2px solid gray; + border-width: 0 2px; + height: 650px; + } + + #userContainer { + min-height: 60px; + border: 1px solid gray; + border-width: 1px 2px; + padding: 1% 5%; + } + + .newuserDiv { + border-radius: 15px 15px 2px 15px; + border: 2px solid skyblue; + padding: 5px 10px; + float: right; + max-width: 70%; + margin: 5px 15px 5px auto; + background-color: white; + } + + .newbotDiv { + border-radius: 15px 15px 15px 2px; + border: 2px solid lightgrey; + padding: 5px 10px; + float: left; + max-width: 70%; + margin: 5px auto 5px 15px; + white-space: pre-line; + background-color: white; + } + + #chatSpace { + overflow-y: scroll; + padding: 15px; + background-color: #ece9f3; + max-height: 100%; + font-size: x-large; + } + + #user, #user::-webkit-input-placeholder { + font-size: x-large; + } + + #submitBtn { + font-size: x-large; + } + + #debug { + padding: 15px; + } + + /* TODO put this in an init.css and use bootstrap */ + .toggleDebug { + width: 0; + height: 0; + border-left: 10px solid transparent; + border-right: 10px solid transparent; + border-top: 10px solid #999; + border-bottom: none; + padding: 0px; + margin: 5px; + } + + /* TODO put this in an init.css and use bootstrap */ + .debugOn { + width: 0; + height: 0; + border-left: 10px solid transparent; + border-right: 10px solid transparent; + border-bottom: 10px solid #999; + border-top: none; + } \ No newline at end of file diff --git a/Chatbox/static/chatbox.js b/Chatbox/static/chatbox.js new file mode 100644 index 0000000..6688efa --- /dev/null +++ b/Chatbox/static/chatbox.js @@ -0,0 +1,116 @@ +function message(botm) { + // if there's user input, add to chatbox + var userInput = $('#user').val(); + if (userInput) { + addUserInput(userInput); + + // Run tutorial if user ask for tutorial + // TODO: move tutorial check to backend + if (userInput.includes("how to play")) { + tutorial(); + } + + // reset message box + $("#user").parent().parent()[0].reset(); + } + + // Output user action to chatbox + // else if (botm["userInput"]) { + // addUserInput(botm["userInput"]) + // } + + // create a row div + var botRow = document.createElement("div"); + $(botRow).addClass("row"); + + // create a newbotDiv div + var newBotDiv = document.createElement("div"); + $(newBotDiv).addClass("newbotDiv"); + + // set text to bot output + $(newBotDiv).append(botm["start"]); + + if (botm["action"]) { + doAction(botm["action"]); + } + + if (botm['state']){ + // $(newBotDiv).append(botm["state"] + "\n"); + animateAvatar(botm["state"]); + } + // if the agent has messages to the user, add to the chatSpace + // represent sayText action + $(newBotDiv).append(botm["hint"]); + $(newBotDiv).append(botm["end"]); + + // add newbotDiv as child of row and add row to child of chatSpace (only if there is text inside) + if ($(newBotDiv).text() != "") { + $(botRow).append($(newBotDiv)); + $("#chatSpace").append($(botRow)); + $("#chatSpace").get(0).scrollIntoView({ behavior: 'smooth' }); + } + + // TODO add debugging log + // TODO add avatar status +} + +function addUserInput(userInput) { + // create a row div + var userRow = document.createElement("div"); + $(userRow).addClass("row"); + + // create a newuserDiv div and set text to user input + var newUserDiv = document.createElement("div"); + $(newUserDiv).addClass("newuserDiv") + .html(userInput) + // add newuserDiv as child of row, and add row as child of chatSpace + $(userRow).append($(newUserDiv)); + $("#chatSpace").append($(userRow)); +} + +function updateScroll() { + $("#chatSpace").scrollTop($("#chatSpace")[0].scrollHeight); +} + +$(document).ready(function () { + + // first thing to do when page fully loads: process configurations received from backend + $.post($SCRIPT_ROOT + "/startgame", { }, + function (data) { + console.log("start game") + medInit(data); + message(data); + updateScroll(); + }); + + // TODO move this to an init file + animateAvatar("sleeping"); + + // TODO also put this in an init file + $("#btnSleeping").click(function() {animateAvatar('sleeping')}); + $("#btnThinking").click(function() {animateAvatar('thinking')}); + $("#btnListening").click(function() {animateAvatar('listening')}); + $("#btnSpeaking").click(function() {animateAvatar('speaking')}); + $("#btnQuestioning").click(function() {animateAvatar('questioning')}); + + // TODO also put this in an init file + $(".toggleDebug").click(function() { + $("#debug").toggle("slow", function(){}); + $(".toggleDebug").toggleClass("debugOn"); + }); + + // this used to be a
+ $("#submitBtn").on('click', function (event) { + $.post($SCRIPT_ROOT + "/message", { + botm: $('#user').val() + }, function (data) { + message(data); + updateScroll(); + }); + // if using , this has something to do with not refreshing + return false; + // Joseph's code. Maybe this is the same as the above return false? + // event.preventDefault(); + }); + +}); diff --git a/Chatbox/static/interactionSpace.css b/Chatbox/static/interactionSpace.css new file mode 100644 index 0000000..5a7f180 --- /dev/null +++ b/Chatbox/static/interactionSpace.css @@ -0,0 +1,6 @@ +#interactionSpace { + border-radius: 0 0 20px 20px; + border: 2px solid gray; + border-top: none; + height: 960px; + } \ No newline at end of file diff --git a/Chatbox/static/medication.css b/Chatbox/static/medication.css new file mode 100644 index 0000000..5f5ceb0 --- /dev/null +++ b/Chatbox/static/medication.css @@ -0,0 +1,141 @@ +#grid-container { + display: grid; + grid-template-columns: 0.5fr 2fr 2fr 2fr 2fr 2fr 2fr 2fr; + padding: 10px; + max-height: 50vh; + margin-bottom: 50px; + } + +#grid-container > div { + background-color: white; + border: 0.5px solid skyblue; + text-align: center; + font-size: 25px; + height: 90%; + padding-bottom: 10px; + + /* change when imported */ + box-sizing: unset; +} + +#grid-container > div > span { + /* font-size: 30px; */ + display: block; + width: 100%; +} + +#grid-container > div > div { + margin-top: 2px; + margin-left: 2px; + display: inline-block; + vertical-align: top; + /* float: left; */ + width: 30px; + height: 10px; + } + +.time > p{ + font-size: 15px; +} + +.med-container { + float: left; + display: grid; + grid-template-columns: auto auto auto ; + grid-template-rows: auto auto auto auto; + width: 200px; + height: 250px; + text-align: center; + align-items: center; + justify-items: center; + /* color: red; */ + margin-right: 5%; + padding: 10px; + padding-top: 10px; + border-radius: 25px; +} + +.time{ + width: 100%; + border: 2px; + font-size: 10px; +} + +.med { + /* float: left; */ + height: 20px; + width: 50px; + border-radius: 25%/50%; + border: 1px solid; +} + +#info { + float: left; + margin-top: 10px; + margin-left: 5%; + margin-right: 5%; + font-size: 30px; + text-align: center; + } + +#med { + display: inline-block; +} + +@keyframes highlight { + 0% { + background-color: #ffff99; + } + + 100% { + background-color: #ffff99; + } +} + +.highlight { + animation: highlight 10s; +} + +@keyframes blink { + 0% { + opacity: 1; + } + + 50% { + opacity: 0; + } + + 100% { + opacity: 1; + } +} + +.blink { + animation: blink 0.8s 5; +} + +#overlay { + position: fixed; + width: 100%; + height: 100%; + display: block; + background-color: rgba(0, 0, 0, 0.5); + z-index: 999; + top: 0; + left: 0; +} + +#popup { + display: none; + position: absolute; + margin: 0 auto; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 99999; +} + +#close { + float: right; + font-size: xx-large; +} \ No newline at end of file diff --git a/Chatbox/static/medication.js b/Chatbox/static/medication.js new file mode 100644 index 0000000..ee94974 --- /dev/null +++ b/Chatbox/static/medication.js @@ -0,0 +1,316 @@ +var my_hand = {"count": 0, "color": "none", "name":"none"}; + +// map medication color to medication name and vice versa +var color_mapping = {}; +var medication_mapping = {}; +const time_mapping = ["none", "morning", "noon", "afternoon", "evening"]; +const day_mapping = {"mon":"Monday", "tue":"Tuesday", "wed":"Wednesday", "thu":"Thursday", "fri":"Friday", +"sat":"Saturday", "sun":"Sunday"}; +const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; + +// variable for tutorial part +var tutorialPhase = false; +var tutorialStep = 0; + +// init game configurations +function medInit(serverInput) { + var events = serverInput["events"]; + var medications = serverInput["medications"]; + + generate_layout(); + events.forEach(populate_events); + + medications.forEach(function(medication) { + var name = medication["name"]; + var color = medication["color"]; + var number = parseInt(medication["number"]); + make_container(name, color, number); + }); +} + +// generate calendar +function generate_layout() { + var title = document.createElement("h1"); + title.textContent = "Medication Sorting"; + title.style = "text-align: center"; + $("#interactionSpace").append(title); + + var gridContainer = document.createElement("div"); + gridContainer.id = "grid-container"; + $("#interactionSpace").append(gridContainer); + + for (let i = 0; i < time_mapping.length; i++) { + var timeDiv = document.createElement("div"); + $(timeDiv).addClass("time"); + if (time_mapping[i] != "none") { + var timeName = document.createElement("p"); + $(timeName).text(time_mapping[i]); + $(timeDiv).append(timeName); + } + $(gridContainer).append(timeDiv); + + for (let j = 0; j < days.length; j++) { + var dayDiv = document.createElement("div"); + if (time_mapping[i] == "none") { + $(dayDiv).text(days[j]); + } else { + dayDiv.id = days[j].toLowerCase() + i; + $(dayDiv).attr("onclick", "calendar_click(this.id);"); + + } + $(gridContainer).append(dayDiv); + } + } + + var br = document.createElement("br"); + $("#interactionSpace").append(br); + + var medAreaDiv = document.createElement("div"); + medAreaDiv.id = "med-area"; + $("#interactionSpace").append(medAreaDiv); + + var infoDiv = document.createElement("div"); + infoDiv.id = "info"; + $(medAreaDiv).append(infoDiv); + + var hand = document.createElement("p"); + $(hand).text("Hand"); + $(infoDiv).append(hand); + + var medDiv = document.createElement("div"); + medDiv.id = "med"; + $(infoDiv).append(medDiv); +} + +// put events on calendar +function populate_events(event) { + var span = document.createElement("span"); + $(span).html(event.name); + $("#"+event.day + event.time).append(span); +} + +// create medication container +function make_container(name, color, number) { + medication_mapping[name] = color; + color_mapping[color] = {"name":name, "number":number}; + + var container = document.createElement("div"); + container.id = color + "_container"; + $(container).addClass("med-container"); + $(container).attr("onclick", "container_click(this.id)"); + $(container).attr("style", "border: 2px solid " + color); + $("#med-area").append(container); + + // span for medication name + var span = document.createElement("span"); + $(container).append(span); + $(span).html(name); + + // for medication information/instruction + var info = document.createElement("i"); + info.id = color + "_instruction"; + info.style.color = color; + $(info).addClass("fa"); + $(info).addClass("fa-info-circle"); + $(info).attr("onclick", "instruction(this.id);"); + $(container).append(info); + + // generate pills + for (var i = 0; i < number; i++) { + var med = document.createElement("div"); + med.id = "" + color + (i+1); + $(med).addClass("med"); + $(med).attr("onclick", "med_click(this.id); event.stopPropagation();"); + med.style.backgroundColor = color; + $(container).append(med); + } +} + +// process tutorial actions from backend +function tutorial() { + tutorialPhase = true; + $.post("/tutorial", {"position": tutorialStep, "response": ""}, function(data) { + if (data) { + message(data); + updateScroll(); + } else { + message({"hint":"Congratulations! You have finished the tutorial"}) + updateScroll(); + tutorialPhase = false; + tutorialStep = 0; + } + }) +} + +// send action to backend and process data sent back from backend +function send_action(action) { + // send action if user is doing tutorial + if (tutorialPhase) { + $.post("/tutorial", { + "position": tutorialStep, + "response": '(' + action + ')' + }, function(data) { + if (data) { + if (data["proceed"]) { + tutorialStep += 1; + tutorial(); + } + } + updateScroll(); + }) + // send normal action + } else { + $.post("/message", { + "botm": '(' + action + ')' + }, function(data) { + doAction(data["action"]); + message(data); + updateScroll(); + + }) + } +} + +// triggers when click on a position in calendar +function calendar_click(pos_id) { + var hand_val = my_hand.count; + var hand_color = my_hand.color; + + if (hand_val == 1) { + // move pill from hand to position in calendar + $("#"+pos_id).append($("#med").children().first()) + + my_hand.count = 0; + send_action('add_to_grid ' + color_mapping[hand_color].name + ' ' + day_mapping[pos_id.substring(0,3)] + ' ' + time_mapping[pos_id.slice(-1)]); + } + else { + // Include previous action for backend to process (commented because backend cannot process 2 actions in sequence right now) + // send_action("add_to_grid " + color_mapping[hand_color].name + " " + day_mapping[pos_id.substring(0,3)] + " " + time_mapping[pos_id.slice(-1)]); + send_action("empty hand"); + } + +} + +// triggers when click on information button in container +function instruction(instruction_id){ + var color = instruction_id.split("_")[0]; + send_action("request-instructions " + color_mapping[color].name); +} + +// triggers when click on a pill/medication +function med_click(med_id) { + var hand_val = my_hand.count; + var color = med_id.match(/[A-Za-z]+/g); + var med = $("#"+med_id); + console.log("clicked: " + med_id); + + if (hand_val == 1) { + // includes action user trying to do + if (med.parent().parent().attr("id") == "med-area") { + send_action("remove_from_container " + color_mapping[color].name); + } else if (med.parent().parent().attr("id") == "grid-container") { + var calId = med.parent().attr("id"); + send_action("remove_from_grid " + color_mapping[color].name + " " + day_mapping[calId.substring(0,3)] + " " + time_mapping[calId.slice(-1)]); + } + send_action("hand is full"); + } + else { + // pill is inside container + if (med.parent().parent().attr("id") == "med-area") { + color_mapping[color].number -= 1; + send_action("remove_from_container " + color_mapping[color].name); + } + // pill is inside calendar + else if (med.parent().parent().attr("id") == "grid-container") { + var calId = med.parent().attr("id"); + send_action("remove_from_grid " + color_mapping[color].name + " " + day_mapping[calId.substring(0,3)] + " " + time_mapping[calId.slice(-1)]); + } + + // move pill to hand + med.appendTo("#med"); + my_hand.count = 1; + my_hand.color = color; + } +} + +// trigger when click inside a container +function container_click(container_id) { + var hand_val = my_hand.count; + var containerColor = container_id.split("_")[0]; + if (color_mapping[containerColor].number == 0) { + send_action("remove_from_container " + color_mapping[containerColor].name); + send_action(color_mapping[containerColor].name + " container empty"); + } else if (hand_val == 1) { + var med = $("#med").children().first(); + var medColor = med.attr("id").match(/[A-Za-z]+/g); + if (medColor == containerColor) { + // move pill from hand to container + $("#"+containerColor + "_container").append(med); + my_hand.count = 0; + color_mapping[containerColor].number += 1; + } + } +} + +// execute action from backend +function doAction(action) { + if (action.name == "pointAt") { + pointAt(action.args); + } else if (action.name == "showImage") { + showImgPopup(action.args); + } +} + +// show image as popup +function showImgPopup(imgSrc) { + var imgPopup = document.createElement("div"); + imgPopup.id = "popup"; + + var overlay = document.createElement("div"); + overlay.id = "overlay"; + $(overlay).append(imgPopup); + $("body").append(overlay); + + var closeBtn = document.createElement("button"); + closeBtn.id = "close"; + $(closeBtn).text("X"); + $(imgPopup).append(closeBtn); + + // var imgSrc = "static/pill_interaction.png"; + var img = document.createElement("img"); + img.src = imgSrc; + $(imgPopup).append(img); + + $(imgPopup).hide().fadeIn(100); + + $(closeBtn).click(function (e) { + e.preventDefault(); + e.stopPropagation(); + $(imgPopup).fadeOut(100) + $(overlay).fadeOut(100); + }); +} + +// point at an object in interface +// TODO: maybe keep pointing until there is response from user +function pointAt(args) { + var list = args.split(' '); + if (list.length == 2) { + var day = list[0].toLowerCase().slice(0, 3); + var time = time_mapping.indexOf(list[1]); + // $("#"+day+time).get(0).scrollIntoView({ behavior: 'smooth' }); + $("#"+day+time).addClass("highlight"); + + setTimeout(function() { + $("#"+day+time).removeClass('highlight'); + }, 5000); + } else if (list.length == 1) { + var color = medication_mapping[args]; + console.log("point at " + $("#"+color+"_container").children().last().attr("id")) + // $("#"+color+"_container").get(0).scrollIntoView({ behavior: 'smooth' }); + $("#"+color+"_container").children().last().addClass("blink"); + setTimeout(function() { + $("#"+color+"_container").children().last().removeClass('blink'); + }, 3000); + } +} \ No newline at end of file diff --git a/Chatbox/static/pill_interaction.png b/Chatbox/static/pill_interaction.png new file mode 100644 index 0000000..d83682b Binary files /dev/null and b/Chatbox/static/pill_interaction.png differ diff --git a/Chatbox/templates/index.html b/Chatbox/templates/index.html new file mode 100644 index 0000000..1e7a1f5 --- /dev/null +++ b/Chatbox/templates/index.html @@ -0,0 +1,71 @@ + + + + + + + + + + + + Chat.bot + + + +
+
chat.bot
+
+
+ +
+ + + ??? +
+
+
+
+
+
+
+ +
+ +
+ +
+
+ +
+
+
+
+ +
+
+
+ + + + + + + + + + + + + + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..b71955e --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# Medication_Sorting_Game +Web UI and backend for medication sorting game + +## Layout +- Top half + + Left: avatar space, a space for the avatar of the virtual agent to express different states or actions + + Right: chatbox, where messages between user and agent appear +- Bottom half: interactive space for any tasks or game (currently medication sorting game but could be replaced with any javascript-generated web application) + +## List of agent's state displayed in avatar space (could improve noticeability in the future) +- Sleeping: head tilted, eyes closed, continuously breathing +- Speaking: eyes open, high bounce twice +- Thinking: continuously low bouncing, eyes open +- Listening: eyes open, continuously breathing +- Questioning: eyes open, question marks on top of head, high bounce twice + +## List of current agent's actions in the UI +- Talk to the user throught the chat box +- Give user a short step-by-step tutorial (currently hardcoded) +- Ask user questions (currently hardcoded) +- Point at an object inside UI (could improve noticeability in the future) +- Show user an image as a popup +- Warn the user if there is any problems through a combination of actions and messages (currently the problem-checking is hardcoded, includes checking for interaction conflict, overdosage, and incorrect time of dosage) + +### How to run +1. Download Flask and flask_session using pip ``` pip install Flask flask_session``` or pip3 ``` pip3 install Flask flask_session``` +2. Go to Chatbox folder and run this command in command line ``` python3 chatbox_flask.py ``` +3. Go to http://127.0.0.1:5000/ on your favorite browser to see the UI working diff --git a/bluepill.jpg b/bluepill.jpg deleted file mode 100644 index 5ae8cd2..0000000 Binary files a/bluepill.jpg and /dev/null differ diff --git a/bottle.jpg b/bottle.jpg deleted file mode 100644 index a385a23..0000000 Binary files a/bottle.jpg and /dev/null differ diff --git a/data.txt b/data.txt deleted file mode 100644 index 37cb093..0000000 --- a/data.txt +++ /dev/null @@ -1 +0,0 @@ -{"Mon1r": [], "Mon2r": [], "Mon3r": [], "Mon4r": [], "Mon1b": [], "Mon2b": [], "Mon3b": [], "Mon4b": [], "Tues1r": [], "Tues2r": [], "Tues3r": [], "Tues4r": [], "Tues1b": [], "Tues2b": [], "Tues3b": [], "Tues4b": [], "Wed1r": [], "Wed2r": [], "Wed3r": [], "Wed4r": [], "Wed1b": [], "Wed2b": [], "Wed3b": [], "Wed4b": [], "Thurs1r": [], "Thurs2r": [], "Thurs3r": [], "Thurs4r": [], "Thurs1b": [], "Thurs2b": [], "Thurs3b": [], "Thurs4b": [], "Fri1r": [], "Fri2r": [], "Fri3r": [], "Fri4r": [], "Fri1b": [], "Fri2b": [], "Fri3b": [], "Fri4b": [], "Sat1r": [], "Sat2r": [], "Sat3r": [], "Sat4r": [], "Sat1b": [], "Sat2b": [], "Sat3b": [], "Sat4b": [], "Sun1r": [], "Sun2r": [], "Sun3r": [], "Sun4r": [], "Sun1b": [], "Sun2b": [], "Sun3b": [], "Sun4b": []} \ No newline at end of file diff --git a/days.txt b/days.txt deleted file mode 100644 index 6c2c88e..0000000 --- a/days.txt +++ /dev/null @@ -1 +0,0 @@ -{"Mon1r": 0, "Mon2r": 0, "Mon3r": 0, "Mon4r": 0, "Mon1b": 0, "Mon2b": 0, "Mon3b": 0, "Mon4b": 0, "Tues1r": 0, "Tues2r": 0, "Tues3r": 0, "Tues4r": 0, "Tues1b": 0, "Tues2b": 0, "Tues3b": 0, "Tues4b": 0, "Wed1r": 0, "Wed2r": 0, "Wed3r": 0, "Wed4r": 0, "Wed1b": 0, "Wed2b": 0, "Wed3b": 0, "Wed4b": 0, "Thurs1r": 0, "Thurs2r": 0, "Thurs3r": 0, "Thurs4r": 0, "Thurs1b": 0, "Thurs2b": 0, "Thurs3b": 0, "Thurs4b": 0, "Fri1r": 0, "Fri2r": 0, "Fri3r": 0, "Fri4r": 0, "Fri1b": 0, "Fri2b": 0, "Fri3b": 0, "Fri4b": 0, "Sat1r": 0, "Sat2r": 0, "Sat3r": 0, "Sat4r": 0, "Sat1b": 0, "Sat2b": 0, "Sat3b": 0, "Sat4b": 0, "Sun1r": 0, "Sun2r": 0, "Sun3r": 0, "Sun4r": 0, "Sun1b": 0, "Sun2b": 0, "Sun3b": 0, "Sun4b": 0} \ No newline at end of file diff --git a/index.html b/index.html deleted file mode 100644 index 6912dee..0000000 --- a/index.html +++ /dev/null @@ -1,346 +0,0 @@ - - - - - - - - - -

Medication Sorting

- -
- -
-
Sun
-
Mon
-
Tues
-
Wed
-
Thurs
-
Fri
-
Sat
- - -
-

10AM-12PM

-
-
- Morning Therapy - -
-
-
- -
-
- -
-
-
-
- -
-
- -
-
-
-
- -
-
- -
-
-
-
- -
-
- -
-
-
-
- -
-
- -
-
-
-
- -
-
- -
-
- - -
-

12PM-2PM

-
-
-
- -
-
- -
-
-
-
- -
-
- -
-
-
-
- -
-
- -
-
-
-
- -
-
- -
-
-
-
- -
-
- -
-
-
-
- -
-
- -
-
-
-
- -
-
- -
-
- - -
-

2PM - 4PM

-
-
-
- -
-
- -
-
-
-
- -
-
- -
-
-
-
- -
-
- -
-
-
-
- -
-
- -
-
-
-
- -
-
- -
-
-
-
- -
-
- -
-
-
-
- -
-
- -
-
- - -
-

4PM - 6PM

-
-
-
- -
-
- -
-
-
-
- -
-
- -
-
-
-
- -
-
- -
-
-
- - Exercise -
-
-
- -
-
- -
-
-
-
- -
-
- -
-
-
-
- -
-
- -
-
- - - - - - - - - -
-
- - -
- - - - - - - - - - - - - - -
-
- - - - - - - - - - - - -
- -
- 0 -
- - - - - - - - diff --git a/medication.css b/medication.css deleted file mode 100644 index a13f8f3..0000000 --- a/medication.css +++ /dev/null @@ -1,152 +0,0 @@ -.everything{ - display: grid; - grid-template-columns: 2fr 2fr; - grid-template-areas: "red blue"; -} -.grid-container { - display: grid; - grid-template-columns: 0.5fr 2fr 2fr 2fr 2fr 2fr 2fr 2fr; - padding: 10px; - max-height: 50vh; - margin-bottom: 50px; - } - -.grid-container > div { - background-color: white; - border: 0.5px solid skyblue; - text-align: center; - font-size: 30px; - height: 90%; - - padding-bottom: 10px; - - } - - .grid-container > div > div{ - background-color: white; - border: 0.5px solid skyblue; - text-align: center; - font-size: 30px; - height: 100%; - padding-top: 10px; - } - -.time > p{ - font-size: 15px; -} - -.container { - width: 100%; - height: 50px; - margin: auto; - padding: 10px; -} - -.time{ - width: 100%; - border: 2px; - font-size: 10px; -} - -.red { - width: 30%; - height: 100px; - float: left; - background: red; - margin-left: 20%; - font-size: 50px; - text-align: center; - -} -.blue { - width: 30%; - height: 100px; - float: left; - background: blue; - margin-right: 20%; - font-size: 50px; - text-align: center; -} -.left { - width: 50%; - height: 20%; - float: left; - text-align: center; - color: red; -} -.right { - margin-left: 50%; - height: 20%; - text-align: center; - color: blue; -} - -.red_medicine{ - display: grid; - grid-template-columns: auto auto auto ; - grid-template-rows: auto auto auto auto; - grid-gap: 15px; - float: left; - width: 200px; - height: 250px; - text-align: center; - color: red; - margin-left: 16%; - padding: 10px; - padding-top: 20px; - border-radius: 25px; - border: 2px solid red; -} - - -.red_medicine > div{ - background-color: red; - border: 1px solid black; - text-align: center; - font-size: 15px; - height: 30px; -} - -.red_medicine > #cap { - background-color: white; - height: 50px; - color: black; - font-size: 30px; -} - -.blue_medicine{ - float: right; - display: grid; - grid-template-columns: auto auto auto; - grid-template-rows: auto auto auto auto; - width: 200px; - height: 250px; - text-align: center; - color: blue; - margin-right: 20%; - padding: 10px; - padding-top: 20px; - border-radius: 25px; - border: 2px solid blue; -} - -.blue_medicine > div{ - background-color: blue; - border: 1px solid black; - text-align: center; - font-size: 15px; - height: 30px; -} - -.blue_medicine > #cap { - background-color: white; - height: 50px; - color: black; - font-size: 30px; -} - -#hand { - margin-top: 100px; - margin-left: 50%; - font-size: 30px; - } diff --git a/medication.js b/medication.js deleted file mode 100644 index 78defaf..0000000 --- a/medication.js +++ /dev/null @@ -1,151 +0,0 @@ -var my_hand = {"count": 0, "type": "none"}; -var medicine_count = {"red": 10, "blue": 10}; -var calendar_count = {"Mon1r": 0, "Mon2r": 0, "Mon3r": 0, "Mon4r": 0, "Mon1b": 0, "Mon2b": 0, "Mon3b": 0, "Mon4b": 0, "Tues1r": 0, "Tues2r": 0, "Tues3r": 0, "Tues4r": 0, "Tues1b": 0, "Tues2b": 0, "Tues3b": 0, "Tues4b": 0, "Wed1r": 0, "Wed2r": 0, "Wed3r": 0, "Wed4r": 0, "Wed1b": 0, "Wed2b": 0, "Wed3b": 0, "Wed4b": 0, "Thurs1r": 0, "Thurs2r": 0, "Thurs3r": 0, "Thurs4r": 0, "Thurs1b": 0, "Thurs2b": 0, "Thurs3b": 0, "Thurs4b": 0, "Fri1r": 0, "Fri2r": 0, "Fri3r": 0, "Fri4r": 0, "Fri1b": 0, "Fri2b": 0, "Fri3b": 0, "Fri4b": 0, "Sat1r": 0, "Sat2r": 0, "Sat3r": 0, "Sat4r": 0, "Sat1b": 0, "Sat2b": 0, "Sat3b": 0, "Sat4b": 0, "Sun1r": 0, "Sun2r": 0, "Sun3r": 0, "Sun4r": 0, "Sun1b": 0, "Sun2b": 0, "Sun3b": 0, "Sun4b": 0} - - -function calender_click(clicked_id) - { - var calendar_val = Number(document.getElementById(clicked_id).innerHTML); - var calendar_type = clicked_id.substr(-1); - var hand_val = my_hand["count"]; - var hand_type = my_hand["type"]; - - if (hand_val == 1){ - if (hand_type == calendar_type && calendar_type == 'r'){ - var id = clicked_id + calendar_count[clicked_id ] - calendar_count[clicked_id ] += 1 - var img = document.createElement("img"); - img.src = "redpill.jpg"; - img.id = id; - img.setAttribute("height" , 30); - document.getElementById(clicked_id).appendChild(img); - document.getElementById("hand").innerHTML = Number(0); - - my_hand["count"] = 0; - my_hand["type"] = "none" - } - else if (hand_type == "b" && hand_type == calendar_type){ - var id = clicked_id + calendar_count[clicked_id ] - calendar_count[clicked_id ] += 1 - var img = document.createElement("img"); - img.src = "bluepill.jpg"; - img.id = id; - img.setAttribute("height" , 30); - document.getElementById(clicked_id).appendChild(img); - - document.getElementById("hand").innerHTML = Number(0); - my_hand["count"] = 0; - my_hand["type"] = "none" - } - else{ - alert("You are butting the pill in a wrong container!!"); - } - - } - else if(hand_val == 0){ - if (calendar_count[clicked_id] > 0){ - var id = clicked_id + String(calendar_count[clicked_id] - 1); - document.getElementById(id).remove(); - calendar_count[clicked_id] -= 1; - document.getElementById("hand").innerHTML = hand_val + 1; - my_hand["count"] = 1; - my_hand["type"] = calendar_type; - } - else{ - alert("The hand is empty!"); - } - } - - } - -function red_instruction(){ - alert("Red pill instructions!"); -} - -function blue_instruction(){ - alert("Blue pill instructions!"); -} - -function blue_medicine_click(){ - var hand_val = my_hand["count"]; - var hand_type = my_hand["type"]; - - if (hand_val == 1 && hand_type == "r"){ - alert("You can't put a red pill inside a blue bottle!"); - } - else if (hand_val == 1 && hand_type == "b"){ - medicine_count["blue"] += 1; - var id = "blue" + String(medicine_count["blue"]) - var img = document.createElement("img"); - img.src = "bluepill.jpg"; - img.id = id; - img.setAttribute("height", "42"); - // img.setAttribute("width", "42"); - - document.getElementById("blue_bottle").appendChild(img); - // document.getElementById(id).style.backgroundColor = "blue"; - document.getElementById("hand").innerHTML = hand_val - 1; - my_hand["count"] = 0; - my_hand["type"] = "none" - } - else if (hand_val == 0){ - if (medicine_count["blue"] > 0) { - var id = "blue" + String(medicine_count["blue"]) - document.getElementById(id).remove(); - document.getElementById("hand").innerHTML = hand_val + 1; - medicine_count["blue"] -= 1; - my_hand["count"] = 1 - my_hand["type"] = "b" - } - else{ - alert("Red medicine bottle is empty"); - } - } - else{ - alert("Wrong operation!"); - } -} - -function red_medicine_click(){ - var hand_val = my_hand["count"]; - var hand_type = my_hand["type"]; - document.body.style.cursor = "url('redpill.jpg')"; - - - if (hand_val == 1 && hand_type == "b"){ - alert("You can't put a blue pill inside a red bottle!"); - } - else if (hand_val == 1 && hand_type == "r"){ - medicine_count["red"] += 1; - var id = "red" + String(medicine_count["red"]) - var img = document.createElement("img"); - img.src = "redpill.jpg"; - img.id = id; - img.setAttribute("height" , 42); - - document.getElementById("red_bottle").appendChild(img); - document.getElementById("hand").innerHTML = hand_val - 1; - my_hand["count"] = 0; - my_hand["type"] = "none" - } - else if (hand_val == 0){ - if (medicine_count["red"] > 0) { - var id = "red" + String(medicine_count["red"]) - document.getElementById(id).remove(); - document.getElementById("hand").innerHTML = hand_val + 1; - medicine_count["red"] -= 1; - my_hand["count"] = 1 - my_hand["type"] = "r" - } - else{ - alert("Red medicine bottle is empty"); - } - } - else{ - alert("Wrong operation!"); - } - -} - -var json = {"day" : { "Sun": { "1": { "red" : 0, "blue" : 0 } , "2": { "red" : 0, "blue" : 0 } , "3": { "red" : 0, "blue" : 0 } , "4": { "red" : 0, "blue" : 0 } } , "Mon": { "1": { "red" : 0, "blue" : 0 } , "2": { "red" : 0, "blue" : 0 } , "3": { "red" : 0, "blue" : 0 } , "4": { "red" : 0, "blue" : 0 } } , "Tues": { "1": { "red" : 0, "blue" : 0 } , "2": { "red" : 0, "blue" : 0 } , "3": { "red" : 0, "blue" : 0 } , "4": { "red" : 0, "blue" : 0 } } , "Wed": { "1": { "red" : 0, "blue" : 0 } , "2": { "red" : 0, "blue" : 0 } , "3": { "red" : 0, "blue" : 0 } , "4": { "red" : 0, "blue" : 0 } } , "Thurs": { "1": { "red" : 0, "blue" : 0 } , "2": { "red" : 0, "blue" : 0 } , "3": { "red" : 0, "blue" : 0 } , "4": { "red" : 0, "blue" : 0 } } , "Fri": { "1": { "red" : 0, "blue" : 0 } , "2": { "red" : 0, "blue" : 0 } , "3": { "red" : 0, "blue" : 0 } , "4": { "red" : 0, "blue" : 0 } } , "Sat": { "1": { "red" : 0, "blue" : 0 } , "2": { "red" : 0, "blue" : 0 } , "3": { "red" : 0, "blue" : 0 } , "4": { "red" : 0, "blue" : 0 } } } } -// console.log(json["day"]["Sun"]["1"]["red"]); \ No newline at end of file diff --git a/medication.json b/medication.json deleted file mode 100644 index 65fbe93..0000000 --- a/medication.json +++ /dev/null @@ -1,191 +0,0 @@ -{ - "Sun": - { - "1": - { - "red" : 0, - "blue" : 0 - } - , - "2": - { - "red" : 0, - "blue" : 0 - } - , - "3": - { - "red" : 0, - "blue" : 0 - } - , - "4": - { - "red" : 0, - "blue" : 0 - } - } - , - "Mon": - { - "1": - { - "red" : 0, - "blue" : 0 - } - , - "2": - { - "red" : 0, - "blue" : 0 - } - , - "3": - { - "red" : 0, - "blue" : 0 - } - , - "4": - { - "red" : 0, - "blue" : 0 - } - } - , - "Tues": - { - "1": - { - "red" : 0, - "blue" : 0 - } - , - "2": - { - "red" : 0, - "blue" : 0 - } - , - "3": - { - "red" : 0, - "blue" : 0 - } - , - "4": - { - "red" : 0, - "blue" : 0 - } - } - , - "Wed": - { - "1": - { - "red" : 0, - "blue" : 0 - } - , - "2": - { - "red" : 0, - "blue" : 0 - } - , - "3": - { - "red" : 0, - "blue" : 0 - } - , - "4": - { - "red" : 0, - "blue" : 0 - } - } - , - "Thurs": - { - "1": - { - "red" : 0, - "blue" : 0 - } - , - "2": - { - "red" : 0, - "blue" : 0 - } - , - "3": - { - "red" : 0, - "blue" : 0 - } - , - "4": - { - "red" : 0, - "blue" : 0 - } - } - , - "Fri": - { - "1": - { - "red" : 0, - "blue" : 0 - } - , - "2": - { - "red" : 0, - "blue" : 0 - } - , - "3": - { - "red" : 0, - "blue" : 0 - } - , - "4": - { - "red" : 0, - "blue" : 0 - } - } - , - "Sat": - { - "1": - { - "red" : 0, - "blue" : 0 - } - , - "2": - { - "red" : 0, - "blue" : 0 - } - , - "3": - { - "red" : 0, - "blue" : 0 - } - , - "4": - { - "red" : 0, - "blue" : 0 - } - } - } - diff --git a/node_modules/sax/LICENSE b/node_modules/sax/LICENSE deleted file mode 100644 index ccffa08..0000000 --- a/node_modules/sax/LICENSE +++ /dev/null @@ -1,41 +0,0 @@ -The ISC License - -Copyright (c) Isaac Z. Schlueter and Contributors - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR -IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -==== - -`String.fromCodePoint` by Mathias Bynens used according to terms of MIT -License, as follows: - - Copyright Mathias Bynens - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be - included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/sax/README.md b/node_modules/sax/README.md deleted file mode 100644 index afcd3f3..0000000 --- a/node_modules/sax/README.md +++ /dev/null @@ -1,225 +0,0 @@ -# sax js - -A sax-style parser for XML and HTML. - -Designed with [node](http://nodejs.org/) in mind, but should work fine in -the browser or other CommonJS implementations. - -## What This Is - -* A very simple tool to parse through an XML string. -* A stepping stone to a streaming HTML parser. -* A handy way to deal with RSS and other mostly-ok-but-kinda-broken XML - docs. - -## What This Is (probably) Not - -* An HTML Parser - That's a fine goal, but this isn't it. It's just - XML. -* A DOM Builder - You can use it to build an object model out of XML, - but it doesn't do that out of the box. -* XSLT - No DOM = no querying. -* 100% Compliant with (some other SAX implementation) - Most SAX - implementations are in Java and do a lot more than this does. -* An XML Validator - It does a little validation when in strict mode, but - not much. -* A Schema-Aware XSD Thing - Schemas are an exercise in fetishistic - masochism. -* A DTD-aware Thing - Fetching DTDs is a much bigger job. - -## Regarding `Hello, world!').close(); - -// stream usage -// takes the same options as the parser -var saxStream = require("sax").createStream(strict, options) -saxStream.on("error", function (e) { - // unhandled errors will throw, since this is a proper node - // event emitter. - console.error("error!", e) - // clear the error - this._parser.error = null - this._parser.resume() -}) -saxStream.on("opentag", function (node) { - // same object as above -}) -// pipe is supported, and it's readable/writable -// same chunks coming in also go out. -fs.createReadStream("file.xml") - .pipe(saxStream) - .pipe(fs.createWriteStream("file-copy.xml")) -``` - - -## Arguments - -Pass the following arguments to the parser function. All are optional. - -`strict` - Boolean. Whether or not to be a jerk. Default: `false`. - -`opt` - Object bag of settings regarding string formatting. All default to `false`. - -Settings supported: - -* `trim` - Boolean. Whether or not to trim text and comment nodes. -* `normalize` - Boolean. If true, then turn any whitespace into a single - space. -* `lowercase` - Boolean. If true, then lowercase tag names and attribute names - in loose mode, rather than uppercasing them. -* `xmlns` - Boolean. If true, then namespaces are supported. -* `position` - Boolean. If false, then don't track line/col/position. -* `strictEntities` - Boolean. If true, only parse [predefined XML - entities](http://www.w3.org/TR/REC-xml/#sec-predefined-ent) - (`&`, `'`, `>`, `<`, and `"`) - -## Methods - -`write` - Write bytes onto the stream. You don't have to do this all at -once. You can keep writing as much as you want. - -`close` - Close the stream. Once closed, no more data may be written until -it is done processing the buffer, which is signaled by the `end` event. - -`resume` - To gracefully handle errors, assign a listener to the `error` -event. Then, when the error is taken care of, you can call `resume` to -continue parsing. Otherwise, the parser will not continue while in an error -state. - -## Members - -At all times, the parser object will have the following members: - -`line`, `column`, `position` - Indications of the position in the XML -document where the parser currently is looking. - -`startTagPosition` - Indicates the position where the current tag starts. - -`closed` - Boolean indicating whether or not the parser can be written to. -If it's `true`, then wait for the `ready` event to write again. - -`strict` - Boolean indicating whether or not the parser is a jerk. - -`opt` - Any options passed into the constructor. - -`tag` - The current tag being dealt with. - -And a bunch of other stuff that you probably shouldn't touch. - -## Events - -All events emit with a single argument. To listen to an event, assign a -function to `on`. Functions get executed in the this-context of -the parser object. The list of supported events are also in the exported -`EVENTS` array. - -When using the stream interface, assign handlers using the EventEmitter -`on` function in the normal fashion. - -`error` - Indication that something bad happened. The error will be hanging -out on `parser.error`, and must be deleted before parsing can continue. By -listening to this event, you can keep an eye on that kind of stuff. Note: -this happens *much* more in strict mode. Argument: instance of `Error`. - -`text` - Text node. Argument: string of text. - -`doctype` - The ``. Argument: -object with `name` and `body` members. Attributes are not parsed, as -processing instructions have implementation dependent semantics. - -`sgmldeclaration` - Random SGML declarations. Stuff like `` -would trigger this kind of event. This is a weird thing to support, so it -might go away at some point. SAX isn't intended to be used to parse SGML, -after all. - -`opentagstart` - Emitted immediately when the tag name is available, -but before any attributes are encountered. Argument: object with a -`name` field and an empty `attributes` set. Note that this is the -same object that will later be emitted in the `opentag` event. - -`opentag` - An opening tag. Argument: object with `name` and `attributes`. -In non-strict mode, tag names are uppercased, unless the `lowercase` -option is set. If the `xmlns` option is set, then it will contain -namespace binding information on the `ns` member, and will have a -`local`, `prefix`, and `uri` member. - -`closetag` - A closing tag. In loose mode, tags are auto-closed if their -parent closes. In strict mode, well-formedness is enforced. Note that -self-closing tags will have `closeTag` emitted immediately after `openTag`. -Argument: tag name. - -`attribute` - An attribute node. Argument: object with `name` and `value`. -In non-strict mode, attribute names are uppercased, unless the `lowercase` -option is set. If the `xmlns` option is set, it will also contains namespace -information. - -`comment` - A comment node. Argument: the string of the comment. - -`opencdata` - The opening tag of a ``) of a `` tags trigger a `"script"` -event, and their contents are not checked for special xml characters. -If you pass `noscript: true`, then this behavior is suppressed. - -## Reporting Problems - -It's best to write a failing test if you find an issue. I will always -accept pull requests with failing tests if they demonstrate intended -behavior, but it is very hard to figure out what issue you're describing -without a test. Writing a test is also the best way for you yourself -to figure out if you really understand the issue you think you have with -sax-js. diff --git a/node_modules/sax/lib/sax.js b/node_modules/sax/lib/sax.js deleted file mode 100644 index 795d607..0000000 --- a/node_modules/sax/lib/sax.js +++ /dev/null @@ -1,1565 +0,0 @@ -;(function (sax) { // wrapper for non-node envs - sax.parser = function (strict, opt) { return new SAXParser(strict, opt) } - sax.SAXParser = SAXParser - sax.SAXStream = SAXStream - sax.createStream = createStream - - // When we pass the MAX_BUFFER_LENGTH position, start checking for buffer overruns. - // When we check, schedule the next check for MAX_BUFFER_LENGTH - (max(buffer lengths)), - // since that's the earliest that a buffer overrun could occur. This way, checks are - // as rare as required, but as often as necessary to ensure never crossing this bound. - // Furthermore, buffers are only tested at most once per write(), so passing a very - // large string into write() might have undesirable effects, but this is manageable by - // the caller, so it is assumed to be safe. Thus, a call to write() may, in the extreme - // edge case, result in creating at most one complete copy of the string passed in. - // Set to Infinity to have unlimited buffers. - sax.MAX_BUFFER_LENGTH = 64 * 1024 - - var buffers = [ - 'comment', 'sgmlDecl', 'textNode', 'tagName', 'doctype', - 'procInstName', 'procInstBody', 'entity', 'attribName', - 'attribValue', 'cdata', 'script' - ] - - sax.EVENTS = [ - 'text', - 'processinginstruction', - 'sgmldeclaration', - 'doctype', - 'comment', - 'opentagstart', - 'attribute', - 'opentag', - 'closetag', - 'opencdata', - 'cdata', - 'closecdata', - 'error', - 'end', - 'ready', - 'script', - 'opennamespace', - 'closenamespace' - ] - - function SAXParser (strict, opt) { - if (!(this instanceof SAXParser)) { - return new SAXParser(strict, opt) - } - - var parser = this - clearBuffers(parser) - parser.q = parser.c = '' - parser.bufferCheckPosition = sax.MAX_BUFFER_LENGTH - parser.opt = opt || {} - parser.opt.lowercase = parser.opt.lowercase || parser.opt.lowercasetags - parser.looseCase = parser.opt.lowercase ? 'toLowerCase' : 'toUpperCase' - parser.tags = [] - parser.closed = parser.closedRoot = parser.sawRoot = false - parser.tag = parser.error = null - parser.strict = !!strict - parser.noscript = !!(strict || parser.opt.noscript) - parser.state = S.BEGIN - parser.strictEntities = parser.opt.strictEntities - parser.ENTITIES = parser.strictEntities ? Object.create(sax.XML_ENTITIES) : Object.create(sax.ENTITIES) - parser.attribList = [] - - // namespaces form a prototype chain. - // it always points at the current tag, - // which protos to its parent tag. - if (parser.opt.xmlns) { - parser.ns = Object.create(rootNS) - } - - // mostly just for error reporting - parser.trackPosition = parser.opt.position !== false - if (parser.trackPosition) { - parser.position = parser.line = parser.column = 0 - } - emit(parser, 'onready') - } - - if (!Object.create) { - Object.create = function (o) { - function F () {} - F.prototype = o - var newf = new F() - return newf - } - } - - if (!Object.keys) { - Object.keys = function (o) { - var a = [] - for (var i in o) if (o.hasOwnProperty(i)) a.push(i) - return a - } - } - - function checkBufferLength (parser) { - var maxAllowed = Math.max(sax.MAX_BUFFER_LENGTH, 10) - var maxActual = 0 - for (var i = 0, l = buffers.length; i < l; i++) { - var len = parser[buffers[i]].length - if (len > maxAllowed) { - // Text/cdata nodes can get big, and since they're buffered, - // we can get here under normal conditions. - // Avoid issues by emitting the text node now, - // so at least it won't get any bigger. - switch (buffers[i]) { - case 'textNode': - closeText(parser) - break - - case 'cdata': - emitNode(parser, 'oncdata', parser.cdata) - parser.cdata = '' - break - - case 'script': - emitNode(parser, 'onscript', parser.script) - parser.script = '' - break - - default: - error(parser, 'Max buffer length exceeded: ' + buffers[i]) - } - } - maxActual = Math.max(maxActual, len) - } - // schedule the next check for the earliest possible buffer overrun. - var m = sax.MAX_BUFFER_LENGTH - maxActual - parser.bufferCheckPosition = m + parser.position - } - - function clearBuffers (parser) { - for (var i = 0, l = buffers.length; i < l; i++) { - parser[buffers[i]] = '' - } - } - - function flushBuffers (parser) { - closeText(parser) - if (parser.cdata !== '') { - emitNode(parser, 'oncdata', parser.cdata) - parser.cdata = '' - } - if (parser.script !== '') { - emitNode(parser, 'onscript', parser.script) - parser.script = '' - } - } - - SAXParser.prototype = { - end: function () { end(this) }, - write: write, - resume: function () { this.error = null; return this }, - close: function () { return this.write(null) }, - flush: function () { flushBuffers(this) } - } - - var Stream - try { - Stream = require('stream').Stream - } catch (ex) { - Stream = function () {} - } - - var streamWraps = sax.EVENTS.filter(function (ev) { - return ev !== 'error' && ev !== 'end' - }) - - function createStream (strict, opt) { - return new SAXStream(strict, opt) - } - - function SAXStream (strict, opt) { - if (!(this instanceof SAXStream)) { - return new SAXStream(strict, opt) - } - - Stream.apply(this) - - this._parser = new SAXParser(strict, opt) - this.writable = true - this.readable = true - - var me = this - - this._parser.onend = function () { - me.emit('end') - } - - this._parser.onerror = function (er) { - me.emit('error', er) - - // if didn't throw, then means error was handled. - // go ahead and clear error, so we can write again. - me._parser.error = null - } - - this._decoder = null - - streamWraps.forEach(function (ev) { - Object.defineProperty(me, 'on' + ev, { - get: function () { - return me._parser['on' + ev] - }, - set: function (h) { - if (!h) { - me.removeAllListeners(ev) - me._parser['on' + ev] = h - return h - } - me.on(ev, h) - }, - enumerable: true, - configurable: false - }) - }) - } - - SAXStream.prototype = Object.create(Stream.prototype, { - constructor: { - value: SAXStream - } - }) - - SAXStream.prototype.write = function (data) { - if (typeof Buffer === 'function' && - typeof Buffer.isBuffer === 'function' && - Buffer.isBuffer(data)) { - if (!this._decoder) { - var SD = require('string_decoder').StringDecoder - this._decoder = new SD('utf8') - } - data = this._decoder.write(data) - } - - this._parser.write(data.toString()) - this.emit('data', data) - return true - } - - SAXStream.prototype.end = function (chunk) { - if (chunk && chunk.length) { - this.write(chunk) - } - this._parser.end() - return true - } - - SAXStream.prototype.on = function (ev, handler) { - var me = this - if (!me._parser['on' + ev] && streamWraps.indexOf(ev) !== -1) { - me._parser['on' + ev] = function () { - var args = arguments.length === 1 ? [arguments[0]] : Array.apply(null, arguments) - args.splice(0, 0, ev) - me.emit.apply(me, args) - } - } - - return Stream.prototype.on.call(me, ev, handler) - } - - // this really needs to be replaced with character classes. - // XML allows all manner of ridiculous numbers and digits. - var CDATA = '[CDATA[' - var DOCTYPE = 'DOCTYPE' - var XML_NAMESPACE = 'http://www.w3.org/XML/1998/namespace' - var XMLNS_NAMESPACE = 'http://www.w3.org/2000/xmlns/' - var rootNS = { xml: XML_NAMESPACE, xmlns: XMLNS_NAMESPACE } - - // http://www.w3.org/TR/REC-xml/#NT-NameStartChar - // This implementation works on strings, a single character at a time - // as such, it cannot ever support astral-plane characters (10000-EFFFF) - // without a significant breaking change to either this parser, or the - // JavaScript language. Implementation of an emoji-capable xml parser - // is left as an exercise for the reader. - var nameStart = /[:_A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD]/ - - var nameBody = /[:_A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\u00B7\u0300-\u036F\u203F-\u2040.\d-]/ - - var entityStart = /[#:_A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD]/ - var entityBody = /[#:_A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\u00B7\u0300-\u036F\u203F-\u2040.\d-]/ - - function isWhitespace (c) { - return c === ' ' || c === '\n' || c === '\r' || c === '\t' - } - - function isQuote (c) { - return c === '"' || c === '\'' - } - - function isAttribEnd (c) { - return c === '>' || isWhitespace(c) - } - - function isMatch (regex, c) { - return regex.test(c) - } - - function notMatch (regex, c) { - return !isMatch(regex, c) - } - - var S = 0 - sax.STATE = { - BEGIN: S++, // leading byte order mark or whitespace - BEGIN_WHITESPACE: S++, // leading whitespace - TEXT: S++, // general stuff - TEXT_ENTITY: S++, // & and such. - OPEN_WAKA: S++, // < - SGML_DECL: S++, // - SCRIPT: S++, //