{curl 3.0 applet} {applet manifest = "manifest.mcurl"} ||comes in handy later on; best defined at the beginning {let lowercase:CharClass = {CharClass "abcdefghijklmnopqrstuvwxyz"}} ||Represents a single gesture, either one-handed or two-handed. Uses standard Warlocks representation. {define-class public Gesture field private gesture:char field package children:{HashTable-of char, Gesture} = {new {HashTable-of char, Gesture}} field protected completed-spell:#String {constructor public implicit {default gesture:char} set self.gesture = gesture } {constructor public {ending-spell gesture:char, spell:String} set self.gesture = gesture {self.set-spell spell} } ||returns the name of the completed spell, if possible, along with whether the spell was completed {method public {spell-if-complete}:(#String, bool) let complete?:bool = false {if-non-null self.completed-spell then set complete? = true } {return self.completed-spell, complete?} } {method public {set-spell name:String} set self.completed-spell = name } {method public {add-child child:Gesture}:void {self.children.set child.value, child} } {method public {remove-child child:char}:bool {if {self.children.key-exists? child} then {self.children.remove child} {return true} else {return false} } } {method public {get-child gesture:char}:Gesture {return {self.children.get gesture}} } {method public {get-child-if-exists gesture:char}:(Gesture, bool) {return {self.children.get-if-exists gesture}} } {method public {has-child gesture:char}:bool {return {self.children.key-exists? gesture}} } {getter public {value}:char {return self.gesture} } {getter public {has-children?}:bool {return not self.children.empty?} } } ||Defines a full book of gesture-spells {define-class public SpellBook field public-get start-gesture:Gesture = 'B' field public-get name:String field public-get longest-spell:int = 0 ||Creates a SpellBook named source-name and reading from source-name.txt {factory public {named source-name:String}:SpellBook let source:TextInputStream = {read-open {url source-name&".txt"}} {return {SpellBook.from-stream source, source-name}} } ||Creates a SpellBook reading from file-name where the first line is used as a name. {factory public {from-file file-name:String}:SpellBook let source:TextInputStream = {read-open {url file-name}} let name = {{source.read-line}.to-String} {return {SpellBook.from-stream source, name}} } ||Constructs an empty spellbook: all spells must be added manually to this {constructor public {default name:String} set self.name = name } ||Constructor for use with special file-starting factories {constructor {from-stream source:TextInputStream, name:String} set self.name = name let line:#StringBuf let spell:#StringArray {until source.end-of-stream? do set line = {source.read-line buf = line} set spell = {line.split split-chars = ":"} {if spell.size > 1 then {self.add-spell spell[1], spell[0]} } } } {method public {add-spell name:String, gestures:String}:void let current-gesture:#Gesture = self.start-gesture let next-gesture:#Gesture let next-exists?:bool = false {for gesture key k in gestures do set (next-gesture, next-exists?) = {current-gesture.get-child-if-exists gesture} {if next-exists? then set current-gesture = next-gesture else {current-gesture.add-child gesture} set current-gesture = {current-gesture.get-child gesture} } {if k+1 == gestures.size then {current-gesture.set-spell name} {if k+1 > self.longest-spell then set self.longest-spell = k+1 } {return} } } } {method public {has-spell gestures:String}:bool let this-gesture:Gesture = self.start-gesture let another-gesture?:bool = false {for gesture in gestures do set (this-gesture, another-gesture?) = {self.start-gesture.get-child-if-exists gesture} {if not another-gesture? then {return false} } } {return true} } ||traverses the tree to the end of prefix and then starts calling self.all-spells-from {method public {predict-spells-from prefix:String}:StringArray let gesture:Gesture = self.start-gesture let next-gesture:#Gesture let another-gesture?:bool let spells:StringArray = {StringArray} {if prefix.size > 1 then {spells.concat {self.predict-spells-from {prefix.tail 1}}} } {for g in prefix do set (next-gesture, another-gesture?) = {gesture.get-child-if-exists g} {if another-gesture? then set gesture = next-gesture asa Gesture else {if {lowercase.member? g} then let uc:char = {{{String g}.to-upper-clone}.get 0} set (gesture, another-gesture?) = {gesture.get-child-if-exists uc} {if another-gesture? then {continue} } } {return spells} } } {for next-gesture in gesture.children do {spells.splice {self.all-spells-from prefix & next-gesture.value, next-gesture}, 0} } {return spells} } {method public {all-spells}:StringArray {return {self.all-spells-from "", self.start-gesture}} } ||Traverses the tree starting at a specific gesture and a history of existing gestures {method private {all-spells-from prefix:String, last-gesture:Gesture}:StringArray let spells:StringArray = {StringArray} let (spell:#String, has-spell?:bool) = {last-gesture.spell-if-complete} {if has-spell? then {spells.append {String prefix, ":", spell}} } {for gesture in last-gesture.children do {spells.concat {self.all-spells-from {String prefix, gesture.value}, gesture}} } {return spells} } } ||GUI convenience class... I was getting forward reference problems when doing the event handlers otherwise {define-class SpellBookGUI field spellbook:SpellBook field lh:TextField field rh:TextField field lh-spells:VBox = {VBox font-size = 8pt, "no spells yet"} field rh-spells:VBox = {VBox font-size = 8pt, "no spells yet"} field private layout:Table {constructor {default source-file:String} set self.spellbook = {SpellBook.from-file source-file} set self.lh = {TextField value = "B", width=3cm, {on ValueFinished do {self.update-predictions} } } set self.rh = {TextField value = "B", width=3cm, {on ValueFinished do {self.update-predictions} } } set self.layout = {Table {cell-prototype halign="center", colspan=2, {CommandButton label="Show Spell Book", {on Action do let book:VBox = {VBox} let spells:StringArray = {sbg.spellbook.all-spells} {for spell in spells do {book.add spell} } {popup-message book, title="Spells"} } } }, {row-prototype self.lh, self.rh}, {row-prototype self.lh-spells, self.rh-spells} } } {method private {update-predictions}:void let lh-gestures:String = {self.lh.value.to-upper-clone} let rh-gestures:String = {self.rh.value.to-upper-clone} {if lh-gestures.size > self.spellbook.longest-spell then set lh-gestures = {lh-gestures.tail lh-gestures.size - self.spellbook.longest-spell} } {if rh-gestures.size > self.spellbook.longest-spell then set rh-gestures = {rh-gestures.tail rh-gestures.size - self.spellbook.longest-spell} } let lh-spell-list:StringArray = {StringArray} let rh-spell-list:StringArray = {StringArray} {if self.lh.value.size == self.rh.value.size then let rsb:StringBuf = {StringBuf rh-gestures} let lsb:StringBuf = {StringBuf lh-gestures} {for key k in rsb do {if {rsb.get k} == {lsb.get k} then let lc:String = {{rsb.substr k, 1}.to-lower-clone} {rsb.set k, {lc.get 0}} {lsb.set k, {lc.get 0}} } } {lh-spell-list.concat {self.spellbook.predict-spells-from {lsb.to-String}}} {rh-spell-list.concat {self.spellbook.predict-spells-from {rsb.to-String}}} else {lh-spell-list.concat {self.spellbook.predict-spells-from lh-gestures}} {rh-spell-list.concat {self.spellbook.predict-spells-from rh-gestures}} } {self.lh-spells.clear} {self.rh-spells.clear} ||-- {output lh-gestures} {for each-spell in lh-spell-list do {self.lh-spells.add each-spell} ||-- {output each-spell} } ||-- {output rh-gestures} {for each-spell in rh-spell-list do {self.rh-spells.add each-spell} ||-- {output each-spell} } } {method public {display}:Graphic {return self.layout} } } {let sbg:SpellBookGUI = {SpellBookGUI "warlocks.txt"}} {sbg.display}