Arjen Markus (21 february 2003) As a kind of dual technique to Literate programming this page contains a script that allows a user to create a story with embedded commands.
The whole thing is rather simple and actually silly. But try it to get a taste for the possibilities - I myself thought of reviewing chess plays (reusing Richard's page on chess), demonstrating geometrical constructions and so on.
For starters, type in a few commands:
@ puppet A green @ A walks 10 paces @ house 100 blue
Then try the story below (save it in a file with extension ".sto")
And above all: have fun and be creative!
This is a small example of a story: We introduce the characters: @ puppet A green @ puppet B red @ B at 123 and of course the houses in front of which the scene plays: @ house 100 gray75 @ house 140 gray50 @ house 180 lightblue @ house 220 lightblue Our first figure just happily strolls along. Our second figure is just standing @ A walks 40 paces
Note: The following commands are available: - house pos colour - puppet name colour - name turns - name stops - name at pos - name walks number paces
# story.tcl -- # Pilot project for programmed story telling: # You type the story, mix in some commands and the story # becomes animated with moving figures and the lot. # package require Tk # createStoryBoard -- # Create the main window with the story board # # Arguments: # None # Result: # None # Side effects: # Set up of story board window # proc createStoryBoard {} { global widget # # Menubar (simple) # frame .menubar -relief raised -borderwidth 1 pack .menubar -side top -fill x menubutton .menubar.file -text File -menu .menubar.file.menu menu .menubar.file.menu -tearoff false .menubar.file.menu add command -label "New" -command newStory .menubar.file.menu add command -label "Open ..." -command openStory .menubar.file.menu add separator .menubar.file.menu add command -label "Save" -command {saveStory 0} .menubar.file.menu add command -label "Save as ..." -command {saveStory 1} .menubar.file.menu add separator .menubar.file.menu add command -label Exit -command exit menubutton .menubar.help -text Help -menu .menubar.help.menu menu .menubar.help.menu -tearoff false .menubar.help.menu add command -label Overview -command showOverview pack .menubar.file .menubar.help -side left frame .f text .f.text -font "Courier 10" -width 40 -height 10 \ -yscrollcommand {.f.y set} \ -xscrollcommand {.f.x set} scrollbar .f.x -command {.f.text xview} -orient horizontal scrollbar .f.y -command {.f.text yview} grid .f.text .f.y -sticky ns grid .f.x x -sticky we set widget .f.text setupBindingsStoryBoard $widget frame .f2 button .f2.run -text Run -command runStory button .f2.pause -text Pause -command pauseStory grid .f2.run .f2.pause -sticky we pack .f .f2 -side top -anchor nw } # createSceneWindow -- # Create the scene window # # Arguments: # None # Result: # None # Side effects: # Set up of scene window # proc createSceneWindow {} { global cnv toplevel .scene wm title .scene "Scene" set cnv .scene.canvas canvas $cnv -background white -width 300 -height 200 pack $cnv -fill both #$cnv create rectangle 0 0 300 160 -fill blue -outline blue $cnv create oval 30 30 50 50 -fill yellow -outline yellow $cnv create line 0 160 300 160 } # showOverview -- # Show a message window # # Arguments: # None # Result: # None # Side effects: # Message window with help text # proc showOverview {} { global cnv toplevel .help wm title .help "Help" message .help.msg -text "Story telling:\n Type your text and use \"@ commands\" to create figures and let them move" pack .help.msg } # runStory -- # Run all the commands in a story # # Arguments: # lineno Line number to examine (optional) # Result: # None # Side effects: # Commands are run # proc runStory { {lineno 1} } { global cnv global widget if { [$widget compare $lineno.0 < end] } { set line [$widget get "$lineno.0" "$lineno.0 lineend"] # # Does it contain a @ (special story command)? if { [regexp {^ *@} $line] } { runStoryCommand [lrange $line 1 end] } after 250 [list runStory [incr lineno]] } } # setupBindingsStoryBoard -- # Set the special bindings for the story board # # Arguments: # widget Text widget to use # Result: # None # Side effects: # Bindings set up # proc setupBindingsStoryBoard { widget } { set front "-background gray -foreground red" set normal "-background white -foreground black" bind $widget <Key-Return> "+actUponKey %W %K" #bind $widget <Key-@> "+actUponKey %W %K" $widget tag configure command \ -background gray -foreground red #$widget tag bind command <Any-Enter> "$widget tag configure command $front" #$widget tag bind command <Any-Leave> "$widget tag configure command $normal" } # actUponKey -- # Take action when a special key is pressed # # Arguments: # widget Text widget in question # key Key pressed (keysym) # Result: # None # Side effects: # Bindings set up # proc actUponKey { widget key } { # # If the key was "Return", then examine the current line # if { $key == "Return" } { set line [$widget get "insert linestart" "insert lineend"] # # Does it contain a @ (special story command)? if { [regexp {^ *@} $line] } { set tags [$widget tag names insert] if { [lsearch $tags command] == -1 } { $widget tag add command "insert linestart" "insert lineend" } # # Run the command # runStoryCommand [lrange $line 1 end] } } } # runStoryCommand -- # Run the given story command # # Arguments: # args Any number of commands # Result: # None # Side effects: # The side effects from the commands # # Note: # We should probably examine the command before executing it! # (use a save interpreter?) # proc runStoryCommand { args } { eval [join $args] } # house -- # Create a house (static object in the background) # # Arguments: # pos Position of the house # colour Its colour (distinguishing feature) # Result: # None # Side effects: # Object on the canvas drawn # proc house { pos colour } { global object global cnv set pos1 [expr {$pos-25}] set pos2 [expr {$pos+25}] set height1 100 set height2 80 set id \ [$cnv create polygon $pos1 160 $pos1 $height1 $pos $height2 $pos2 \ $height1 $pos2 160 $pos1 160 -outline black -fill $colour] $cnv lower $id } # puppet -- # Create a puppet (figure in the story) by name and colour # # # Arguments: # name Name of the puppet (becomes a command) # colour Its colour (distinguishing feature) # Result: # None # Side effects: # Entries in a global array set, commands $name defined # proc puppet { name colour } { global object global cnv set object($name,name) $name set object($name,colour) $colour set object($name,state) "stand" set object($name,dir) 5 set object($name,pos) 0 # $cnv create rectangle 0 160 10 130 -fill $colour -outline black -tag $name $cnv create line -2 160 5 140 12 160 -fill black -tag $name -width 2 $cnv create line -6 140 5 125 16 140 -fill black -tag $name -width 2 $cnv create oval 0 125 10 145 -fill $colour -outline black -tag $name $cnv create oval 2 125 7 120 -fill $colour -outline black -tag $name interp alias {} $name {} PuppetAct $name } # PuppetAct -- # Specific actions for a puppet # # Arguments: # name Name of the puppet # action Action to take # args Any arguments # Result: # None # Side effects: # Any defined by the action # proc PuppetAct { name action args } { global object global cnv global stop switch -- $action { "stops" { set stop 1 } "at" { set newpos [lindex $args 0] set displace [expr {$newpos-$object($name,pos)}] $cnv move $name $displace 0 set object($name,pos) $newpos } "turns" { set object($name,dir) [expr {-$object($name,dir)}] } "walks" { set steps [lindex $args 0] if { $steps > 0 && $stop != 1 } { $cnv move $name $object($name,dir) 0 incr object($name,pos) $object($name,dir) after 100 [list PuppetAct $name $action [incr steps -1]] } if { $stop == 1 } { set stop 0 } } default { } } } # newStory -- # Remove all text and objects # # Arguments: # None # Result: # None # Side effects: # Clean up the memory # proc newStory {} { global cnv global object global widget $cnv delete all #$cnv create rectangle 0 0 300 160 -fill blue -outline blue $cnv create oval 30 30 50 50 -fill yellow -outline yellow $cnv create line 0 160 300 160 foreach {dummy name} [array get object "*,name"] { interp alias {} $name {} } $widget delete 1.0 end } # openStory -- # Read in an existing story # # Arguments: # None # Result: # None # Side effects: # Filled in text widget # # proc openStory {} { global cnv global object global widget set filename [tk_getOpenFile -filetypes { {{Story files} {.sto} } }] if { $filename != "" } { newStory set infile [open $filename "r"] while { [gets $infile line] >= 0 } { if { [regexp {^ *@} $line] } { set tag command } else { set tag "" } $widget insert end $line $tag $widget insert end "\n" } close $infile } } # main -- # Main code to get it all going # global stop set stop 0 createStoryBoard createSceneWindow
gold added pix