Updated 2018-04-05 06:36:44 by foo

Description  edit

See trains2.tcl for the second version with a passenger and a freight train, or trains3.tcl for large scenery with some animations


Richard Suchenwirth - This weekend fun project varies the theme of Model railroading with Tcl and takes a windshield perspective (TclTrain has the engineer's point of view). Imagine you're standing at a railroad crossing, red lights are flashing... and then the train runs by - an armour yellow F7A, boxcars, gondola, trailer on flat car.. and finally, the caboose. That's what the following piece shows on a Tk canvas. You can control train speed with left (faster), middle (emergency stop), and right (slower, or back) mouse buttons.

In order to cope with the higher data complexity, some more structure and a rr namespace were introduced. The API, so to speak, is simple:
 rr::init   $canvas ;# creates and packs a canvas, if not existing
 rr::create $type $number [$otherdata] ;# make a vehicle (loco or car)
 rr::train  $number $consist ;# vehicles of which a train is made up
 rr::run    $trainnumber ;# guess what that does ;-)

See the demo at end for concrete examples.

Changes  edit

PYK 2012-10-09: eliminated update

[BigL] When running this program I got the following error message
 missing close-bracket
 missing close-bracket
     while executing
 "wm title . [.c canvasx 582],["
     (command bound to event)

2018-04-05: just an error introduced by PYK when adding braces to an expr. Fixed.

Code  edit

package require Tk

namespace eval rr {
    variable data
    set data(curx) 700
    set data(y) 190
    proc init w {
        variable data
        set data(c) $w
        set data(speed) 6
        if ![winfo exists $w] {
            canvas $w -width 700 -height 220 -bg lightblue
            pack $w
        }
        $w delete all
        foreach i [after info] {after cancel $i}
        bind . <Shift-1> [list source [info script]]
        bind . <1> {incr rr::data(speed) 1}
        bind . <2> {set rr::data(speed) 0}
        bind . <3> {incr rr::data(speed) -1}
        bind .c <Motion> {wm title . [.c canvasx %x],[expr {[.c canvasy %y]-190}]}
        $w create poly 0 220 0 77 42 67 99 130 155 63 199 102 255 83 312 126\
             380 116 433 105 501 75 600 104 700 100 700 220   -fill green3 -tag bg
        $w create rect 0 191 7000 200 -fill brown -outline brown ;# ballast
        $w create poly 0 220 100 130 200 220 -fill gray50 ;# road
        $w create poly 97 220 100 130 103 220 -outline yellow -fill gray50
        $w create line 0 190 7000 190 -fill gray -width 3 ;# rail
        crossing 210 215
    }
    proc define {name def} {
        variable data
        set data($name) $def
    }
    proc create {type id args} {
        variable data
        set c $data(c)
        set tag $type:$id
        foreach i [split $data($type) \n] {
            set cmd [lindex $i 0]
            switch $cmd {
            bogie {
                set x [lindex $i 1]
                set diameter 21
                $c create oval $x -$diameter [expr {$x+$diameter}] 0\
                    -fill black -outline white -tag $tag
                set x1 [expr {$x+[lindex $i 2]}]
                $c create oval $x1 -$diameter [expr {$x1+$diameter}] 0\
                    -fill black -outline white -tag $tag
                $c create rect [expr {$x-5}] [expr {-$diameter/2-5}]\
                     [expr {$x1+$diameter+5}] [expr {-$diameter/2+5}] -fill gray20 -tag $tag
            }
            f7abody {
                set t [list f7abody $tag]
                $c create rect 10 -25 430 -22 -fill black -tag $tag
                $c create poly \
                17 -9 30 -85 35 -88 58 -90 60 -92 67 -106 70 -108 73 -110 \
                    425 -110 425 -15 410 -15 400 -25 295 -25 290 -15 165 -15 \
                    160 -25 45 -25 35 -9 -fill gold -tag $t
                $c create rect 30 -81 53 -69 -fill black -tag $t 
                $c create text 31 -81 -text $id -anchor nw -fill white -tag $t
                $c create poly 67 -102 72 -101 76 -97 70 -87 62 -92 \
                    -fill white -outline black -tag $t
                $c create poly 71 -81 80 -94 94 -94 94 -81 -fill white \
                    -outline black -tag $t
                $c create rect 98 -97 114 -52 -outline gold3 -tag $t
                $c create rect 101 -94 111 -81 -fill white -tag $t ;# cab door window
                $c create rect 118 -97 420 -80 -outline gold3 \
                     -tag $t ;# cooler grill
                for {set i 121} {$i<420} {incr i 3} {
                    $c create line $i -97 $i -80 -fill gold3 -tag $t
                }
                $c create rect 140 -110 424 -100 -fill gray75 \
                    -outline gray75 -tag $t;# roof
                $c create line 100 -113 110 -113 -arrow both \
                    -arrowshape {-5 -5 3} -width 2 -tag $t ;# horns
                $c create rect 103 -115 107 -110 -fill black -tag $t
                $c create oval 150 -77 165 -62 -fill gray50 -tag $t
                $c create oval 300 -77 315 -62 -fill gray50 -tag $t
                $c create text 145 -56 -text "U N I O N    P A C I F I C" -fill red \
                -font {Helvetica 13 bold} -anchor nw -tag $t
                $c create text 55 -56 -text $id -fill red -font {Helvetica 13 bold}\
                     -anchor nw -tag $t
                $c create line 55 -37 423 -37 -fill red -width 3 -tag $t
            }
            boxcarbody {
                $c create rect 0 -25 380 -22 -fill black -tag $tag
                $c create rect 10 -26 370 -110 -fill [lindex $args 1] -tag $tag
                set rgrey grey[expr {round(rand()*40+50)}]
                $c create rect 10 -105 370 -110 -fill $rgrey -tag $tag
                $c create rect 160 -100 220 -30 -tag $tag
                $c create text 100 -70 -text [lindex $args 0] -fill white -tag $tag
                $c create text 100 -50 -text $id -fill white -tag $tag
            }
            caboosebody {
                $c create rect 0 -25 300 -22 -fill black -tag $tag
                $c create poly 35 -25 35 -110 120 -110 120 -140 190 -140\
                     190 -110 270 -110 270 -25\
                     -fill [lindex $args 1] -tag $tag
                $c create line 10 -10 10 -100 -tag $tag
                $c create line 290 -10 290 -100 -tag $tag
                set rgrey grey[expr {round(rand()*40+10)}]
                $c create rect 10 -100 120 -110 -fill $rgrey -tag $tag
                $c create rect 118 -135 192 -140 -fill $rgrey -tag $tag
                $c create rect 190 -100 290 -110 -fill $rgrey -tag $tag
                $c create rect 210 -105 215 -140 -fill black -tag $tag
                window $tag 130 -130 18 15 2 15
                window $tag  50 -80 19 17 2 15
                window $tag 200 -80 19 17 2 15
                $c create text 150 -90 -text [lindex $args 0] -fill white -tag $tag
                $c create text 150 -50 -text $id -fill white -tag $tag
                $c create arc 40 -30 85 -85 -style arc -start 180 \
                    -extent 90 -outline yellow -width 1 -tag $tag
                $c create arc 220 -30 265 -85 -style arc -start 270 \
                    -extent 90 -outline yellow -width 1 -tag $tag
            }
            flatcarbody {
                $c create rect 0 -25 380 -22 -fill black -tag $tag
                $c create rect 10 -26 370 -35 -fill [lindex $args 1] -tag $tag
                $c create text 80 -29 -text [lindex $args 0] -fill white -tag $tag
                $c create text 220 -29 -text $id -fill white -tag $tag
            }
            gondolabody {
                $c create rect 0 -25 380 -22 -fill black -tag $tag
                $c create rect 10 -26 370 -90 -fill [lindex $args 1] -tag $tag
                $c create text 100 -70 -text [lindex $args 0] -fill white -tag $tag
                $c create text 100 -50 -text $id -fill white -tag $tag
            }
            trailer {
                set color [lindex $i 1]
                $c create rect 40 -110 340 -50 -fill $color -tag $tag
                $c create text 190 -80 -text "ROADWAY" \
                    -font {Helvetica 40} -fill green4 -tag $tag
                $c create line 80 -50 80 -35 -width 3 -tag $tag
                $c create oval 240 -50 260 -30 -fill gray50 -tag $tag 
                $c create oval 280 -50 300 -30 -fill gray50 -tag $tag
                $c create oval 245 -45 255 -35 -fill $color -tag $tag 
                $c create oval 285 -45 295 -35 -fill $color -tag $tag
            }
            "" continue
            default {error "bad definition word $cmd:\n$i"}
            }
        }
    }
    proc train {name rstock} {
        variable data
        set c $data(c)
        set newx 0
        foreach i $rstock {
                $c move $i $data(curx) $data(y)
                set data(curx) [lindex [$c bbox $i] 2]
                $c addtag $name withtag $i
        }
    }
    proc crossing {x y} {
        variable data
        set c $data(c)
        $c create line [expr {$x-10}] [expr {$y-40}] [expr {$x+15}] [expr {$y-40}]\
            -width 3 -tag fg
        $c create rect $x $y [expr {$x+5}] [expr {$y-70}] -fill orange -tag fg
        $c create line [expr {$x-15}] [expr {$y-80}] [expr {$x+20}] [expr {$y-60}]\
             -width 5 -fill white -tag fg
        $c create line [expr {$x-15}] [expr {$y-60}] [expr {$x+20}] [expr {$y-80}]\
             -width 5 -fill white -tag fg
        $c create oval [expr {$x-8}] [expr {$y-45}] [expr {$x-18}] [expr {$y-35}]\
            -fill white -tag fg
        $c create oval [expr {$x-10}] [expr {$y-43}] [expr {$x-16}] [expr {$y-37}]\
            -fill black -tag {fg blink0}
        $c create oval [expr {$x+15}] [expr {$y-45}] [expr {$x+25}] [expr {$y-35}]\
            -fill white -tag fg
        $c create oval [expr {$x+17}] [expr {$y-43}] [expr {$x+23}] [expr {$y-37}]\
            -fill black -tag {fg blink1}
        set data(blink) 1
        flashCrossing 0
    }
    proc flashCrossing {which} {
        variable data
        set c $data(c)
        if $data(blink) {$c itemconfig blink$which -fill red}
        set which [expr {1-$which}]
        $c itemconfig blink$which -fill black
        after 250 [list rr::flashCrossing $which]
    }
    proc window {t x y w h {n 1} {space 10}} {
        variable data
        set c $data(c)
        for {set i 0} {$i<$n} {incr i} {
            $c create rect $x $y [expr {$x+$w}] [expr {$y+$h}] -fill black -tag $t
            $c create rect [expr {$x+3}] [expr {$y+3}] [expr {$x+$w}] [expr {$y+$h}]\
                 -fill white -tag $t
            set x [expr {$x+$w+$space}]
        }
    }
    proc run {train} {
        variable data
        set c $data(c)
        $c move $train -1 0
        after 0 [list after idle [list [namespace current]::run2 $train]]
    }

    proc run2 {train} {
        variable data
        set c $data(c)
        if {[lindex [$c bbox $train] 2] < 0} {
            $c move $train 5000 0
            set data(blink) 0
        } elseif {[lindex [$c bbox $train] 0] < 1500} {
            set data(blink) 1
        }
        after [expr {10-$data(speed)}] [list after idle [list [namespace current]::run $train]]
        $c raise fg
    }

    define F7A {
        bogie 55 60
        bogie 305 60
        f7abody
    }
    define boxcar {
        bogie 40 40
        bogie 280 40
        boxcarbody
    }
    define gondola {
        bogie 40 40
        bogie 280 40
        gondolabody
    }
    define flatcar {
        bogie 40 40
        bogie 280 40
        trailer gray85
        flatcarbody
    }
    define caboose {
        bogie 40 40
        bogie 190 40
        caboosebody
    }

}

namespace eval rr {
    # Usage examples, and demo:
    init .c
    create F7A I50I
    create boxcar 42135 ATSF brown
    create boxcar 42199 C&NW salmon3
    create gondola 745219 N.Y.C. salmon4
    create caboose 18832 "U N I O N   P A C I F I C" red
    create flatcar 88402 "BOSTON & MAINE" black
    train T1 {F7A:I50I boxcar:42135 gondola:745219 boxcar:42199 flatcar:88402 caboose:18832}

    run T1
}