# Lesson21.tcl
#
# NeHe's Line Tutorial
#
# This Code Was Created By Jeff Molofee 2000
# If You've Found This Code Useful, Please Let Me Know.
# Visit My Site At nehe.gamedev.net
#
# Modified for Tcl3D by Paul Obermeier 2006/03/14
# See www.tcl3d.org for the Tcl3D extension.

package require Img       ; # Img extension for loading BMP files
package require tcl3d     ; # Tcl3D extension for OpenGL graphics

# Optional: Snack extension for playing WAV sound files
set retVal [catch {package require sound} soundVersion] 
set gDemo(haveSnack) [expr !$retVal]

# Determine the directory of this script.
set gDemo(scriptDir) [file dirname [info script]]

# Window size.
set gDemo(winWidth)  640
set gDemo(winHeight) 480

# Display mode.
set gDemo(fullScreen) 0

# Initialize administrative arrays.
for { set loop1 0 } { $loop1 < 11 } { incr loop1 } {
    for { set loop2 0 } { $loop2 < 11 } { incr loop2 } {
        set vline($loop1,$loop2) 0
        set hline($loop1,$loop2) 0
    }
}

for { set loop1 0 } { $loop1 < 9 } { incr loop1 } {
    set enemy($loop1,x)    0
    set enemy($loop1,y)    0
    set enemy($loop1,fx)   0
    set enemy($loop1,fy)   0
    set enemy($loop1,spin) 0.0
}

set hourglass(x)    0
set hourglass(y)    0
set hourglass(fx)   0
set hourglass(fy)   0
set hourglass(spin) 0.0

set player(x)    0
set player(y)    0
set player(fx)   0
set player(fy)   0
set player(spin) 0.0

set keys(LEFT)  0
set keys(RIGHT) 0
set keys(UP)    0
set keys(DOWN)  0
set keys(SPACE) 0

set gDemo(filled)   0                          ; # Done Filling In The Grid?
set gDemo(gameover) 0                          ; # Is The Game Over?
set gDemo(anti)     1                          ; # Antialiasing?
set gDemo(active)   1                          ; # Window Active Flag Set To TRUE By Default

set gDemo(delay)  0                            ; # Enemy Delay
set gDemo(adjust) 3                            ; # Speed Adjustment For Really Slow Video Cards
set gDemo(lives)  5                            ; # Player Lives
set gDemo(level)  1                            ; # Internal Game Level
set gDemo(level2) $gDemo(level)                ; # Displayed Game Level
set gDemo(stage)  1                            ; # Game Stage

set gDemo(steps) { 1 2 4 5 10 20 }             ; # Stepping Values For Slow Video Adjustment

set gDemo(texture) [tcl3dVector GLuint 5]      ; # Font Texture Storage Space

# Show errors occuring in the Togl callbacks.
proc bgerror { msg } {
    tk_messageBox -icon error -type ok -message "Error: $msg\n\n$::errorInfo"
    ExitProg
}

proc SetFullScreenMode { win } {
    set sh [winfo screenheight $win]
    set sw [winfo screenwidth  $win]

    wm minsize $win $sw $sh
    wm maxsize $win $sw $sh
    set fmtStr [format "%dx%d+0+0" $sw $sh]
    wm geometry $win $fmtStr
    wm overrideredirect $win 1
    focus -force $win
}

proc SetWindowMode { win w h } {
    set sh [winfo screenheight $win]
    set sw [winfo screenwidth  $win]

    wm minsize $win 10 10
    wm maxsize $win $sw $sh
    set fmtStr [format "%dx%d+0+25" $w $h]
    wm geometry $win $fmtStr
    wm overrideredirect $win 0
    focus -force $win
}

# Toggle between windowing and fullscreen mode.
proc ToggleWindowMode {} {
    global gWidList

    if { $::gDemo(fullScreen) } {
        SetFullScreenMode .
        set ::gDemo(fullScreen) false
        set ::slowdown 2.0
    } else {
        SetWindowMode . $::gDemo(winWidth) $::gDemo(winHeight)
        set ::gDemo(fullScreen) true
        set ::slowdown 1.0
    }
}

proc LoadGLTextures {} {
    # Load texture images.
    set imgList { "Font.bmp" "Image.bmp" }

    glGenTextures [llength $imgList] $::gDemo(texture)          ; # Create N Textures

    set imgInd 0
    foreach imgName $imgList {
        set texName [file join $::gDemo(scriptDir) "Data" $imgName]
        set retVal [catch {set phImg [image create photo -file $texName]} err1]
        if { $retVal != 0 } {
            error "Error reading image $texName ($err1)"
        } else {
            set w [image width  $phImg]
            set h [image height $phImg]
            set n [tcl3dPhotoChans $phImg]
            set TextureImage [tcl3dVectorFromPhoto $phImg]
            image delete $phImg
        }
        if { $n == 3 } {
            set type $::GL_RGB
        } else {
           set type $::GL_RGBA
        }

        glBindTexture GL_TEXTURE_2D [$::gDemo(texture) get $imgInd]
        glTexImage2D GL_TEXTURE_2D 0 $n $w $h 0 $type GL_UNSIGNED_BYTE $TextureImage
        glTexParameteri GL_TEXTURE_2D GL_TEXTURE_MIN_FILTER $::GL_LINEAR
        glTexParameteri GL_TEXTURE_2D GL_TEXTURE_MAG_FILTER $::GL_LINEAR

        $TextureImage delete       ; # Free The Texture Image Memory
        incr imgInd
    }
}

proc IntRand {} {
    return [expr {int (rand() * 32767.0)}]
}

proc PlaySound { wavFile blockingFlag } {
    global gDemo

    if { ! $gDemo(haveSnack) } {
        after 500
        return
    }
    if { $wavFile eq "" } {
        # OPA TODO Kill Sound
        return
    }
    set fullName [file join $::gDemo(scriptDir) "Data" $wavFile]
    set fullName [tcl3dGetExtFile $fullName]
    set snd [snack::sound -load $fullName]
    $snd play -blocking $blockingFlag
}

# Initialize Our Timer (Get It Ready)
proc TimerInit {} {
    set ::timer [tcl3dNewSwatch]
    tcl3dResetSwatch $::timer
    tcl3dStartSwatch $::timer
}

# Get Time In Milliseconds
proc TimerGetTime {} {
    return [expr {1000.0 * [tcl3dLookupSwatch $::timer]}]
}

# Reset Player And Enemies
proc ResetObjects {} {
    set ::player(x)  0          ; # Reset Player X Position To Far Left Of The Screen
    set ::player(y)  0          ; # Reset Player Y Position To The Top Of The Screen
    set ::player(fx) 0          ; # Set Fine X Position To Match
    set ::player(fy) 0          ; # Set Fine Y Position To Match

    set num [expr $::gDemo(stage) * $::gDemo(level)]
    for { set loop1 0 } { $loop1 < $num } { incr loop1 } {
        set ::enemy($loop1,x)  [expr 5+[IntRand]%6]             ; # Select A Random X Position
        set ::enemy($loop1,y)  [expr [IntRand]%11]              ; # Select A Random Y Position
        set ::enemy($loop1,fx) [expr $::enemy($loop1,x)*60]     ; # Set Fine X To Match
        set ::enemy($loop1,fy) [expr $::enemy($loop1,y)*40]     ; # Set Fine Y To Match
    }
}

# Build Our Font Display List
proc BuildFont {} {
    set ::base [glGenLists 256]                                      ; # Creating 256 Display Lists
    glBindTexture GL_TEXTURE_2D [$::gDemo(texture) get 0]            ; # Select Our Font Texture
    for { set loop1 0 } { $loop1 < 256 } { incr loop1 } {
        set cx [expr double($loop1%16)/16.0]                         ; # X Position Of Current Character
        set cy [expr double($loop1/16)/16.0]                         ; # Y Position Of Current Character

        glNewList [expr $::base+$loop1] GL_COMPILE                   ; # Start Building A List
            glBegin GL_QUADS                                         ; # Use A Quad For Each Character
                glTexCoord2f $cx [expr 1.0-$cy-0.0625]               ; # Texture Coord (Bottom Left)
                glVertex2d 0 16                                      ; # Vertex Coord (Bottom Left)
                glTexCoord2f [expr $cx+0.0625] [expr 1.0-$cy-0.0625] ; # Texture Coord (Bottom Right)
                glVertex2i 16 16                                     ; # Vertex Coord (Bottom Right)
                glTexCoord2f [expr $cx+0.0625] [expr 1.0-$cy]        ; # Texture Coord (Top Right)
                glVertex2i 16 0                                      ; # Vertex Coord (Top Right)
                glTexCoord2f $cx [expr 1.0-$cy]                      ; # Texture Coord (Top Left)
                glVertex2i 0 0                                       ; # Vertex Coord (Top Left)
            glEnd                                                    ; # Done Building Our Quad (Character)
            glTranslated 15 0 0                                      ; # Move To The Right Of The Character
        glEndList                                                    ; # Done Building The Display List
    }                                                                ; # Loop Until All 256 Are Built
}

# Delete The Font From Memory
proc KillFont {} {
    glDeleteLists $::base 256                                        ; # Delete All 256 Display Lists
}

# Where The Printing Happens
proc glPrint { x y cset fmt args } {

    set text [format $fmt $args]
    if { $cset > 1 } {
        # Did User Choose An Invalid Character Set?
        set cset 1                                              ; # If So, Select Set 1 (Italic)
    }
    glEnable GL_TEXTURE_2D                                      ; # Enable Texture Mapping
    glLoadIdentity                                              ; # Reset The Modelview Matrix
    glTranslated $x $y 0                                        ; # Position The Text (0,0 - Bottom Left)
    glListBase [expr {$::base+(128*$cset)}]                     ; # Choose The Font Set (0 or 1)

    if { $cset == 0 } {
        # If Set 0 Is Being Used Enlarge Font
        glScalef 1.5 2.0 1.0                                    ; # Enlarge Font Width And Height
    }

    set len [string length $text]
    set sa [tcl3dVectorFromString GLubyte $text]
    $sa addvec -32  0 $len                                      ; # Subtract 32 (space)
    glCallLists $len GL_UNSIGNED_BYTE $sa                       ; # Write The Text To The Screen
    $sa delete
    glDisable GL_TEXTURE_2D                                     ; # Disable Texture Mapping
}

# Resize And Initialize The GL Window
proc ReshapeCallback { toglwin { w -1 } { h -1 } } {
    set w [$toglwin width]
    set h [$toglwin height]

    glViewport 0 0 $w $h                ; # Reset The Current Viewport
    glMatrixMode GL_PROJECTION          ; # Select The Projection Matrix
    glLoadIdentity                      ; # Reset The Projection Matrix

    glOrtho 0.0 $w $h 0.0 -1.0 1.0      ; # Create Ortho 640x480 View (0,0 At Top Left)

    glMatrixMode GL_MODELVIEW           ; # Select The Modelview Matrix
    glLoadIdentity                      ; # Reset The Modelview Matrix
    set ::gDemo(winWidth)  $w
    set ::gDemo(winHeight) $h
}

# All Setup For OpenGL Goes Here
proc CreateCallback { toglwin } {
    LoadGLTextures                                  ; # Jump To Texture Loading Routine
    BuildFont                                       ; # Build The Font
    glShadeModel GL_SMOOTH                          ; # Enable Smooth Shading
    glClearColor 0.0 0.0 0.0 0.5                    ; # Black Background
    glClearDepth 1.0                                ; # Depth Buffer Setup
    glHint GL_LINE_SMOOTH_HINT GL_NICEST            ; # Set Line Antialiasing
    glEnable GL_BLEND                               ; # Enable Blending
    glBlendFunc GL_SRC_ALPHA GL_ONE_MINUS_SRC_ALPHA ; # Type Of Blending To Use
}

proc HandleLogic {} {
    set step [lindex $::gDemo(steps) $::gDemo(adjust)]
    set num [expr {$::gDemo(stage) * $::gDemo(level)}]
    if { ! $::gDemo(gameover) && $::gDemo(active) } {
        # If Game Isn't Over And Programs Active Move Objects
        for { set loop1 0 } { $loop1 < $num } { incr loop1 } {
            if { ($::enemy($loop1,x) < $::player(x)) && \
                 ($::enemy($loop1,fy) == [expr {$::enemy($loop1,y)*40}]) } {
                incr ::enemy($loop1,x)                          ; # Move The Enemy Right
            }

            if { ($::enemy($loop1,x) > $::player(x)) && \
                 ($::enemy($loop1,fy) == [expr {$::enemy($loop1,y)*40}]) } {
                incr ::enemy($loop1,x) -1                       ; # Move The Enemy Left
            }

            if { ($::enemy($loop1,y) < $::player(y)) && \
                 ($::enemy($loop1,fx) == [expr {$::enemy($loop1,x)*60}]) } {
                incr ::enemy($loop1,y)                          ; # Move The Enemy Down
            }

            if { ($::enemy($loop1,y) > $::player(y)) && \
                 ($::enemy($loop1,fx) == [expr {$::enemy($loop1,x)*60}]) } {
                incr ::enemy($loop1,y) -1                       ; # Move The Enemy Up
            }

            if { ($::gDemo(delay) > [expr {3-$::gDemo(level)}]) && ($::hourglass(fx) != 2) } {
                # If Our Delay Is Done And Player Doesn't Have Hourglass
                set ::gDemo(delay) 0                            ; # Reset The Delay Counter Back To Zero
                for { set loop2 0 } { $loop2 < $num } { incr loop2 } {
                    if { $::enemy($loop2,fx) < [expr {$::enemy($loop2,x)*60}] } {
                        # Is Fine Position On X Axis Lower Than Intended Position?
                        # If So, Increase Fine Position On X Axis
                        set ::enemy($loop2,fx) [expr {$::enemy($loop2,fx) + $step}]
                        # Spin Enemy Clockwise
                        set ::enemy($loop2,spin) [expr {$::enemy($loop2,spin) + $step}]
                    }
                    if { $::enemy($loop2,fx) > [expr {$::enemy($loop2,x)*60}] } {
                        # Is Fine Position On X Axis Higher Than Intended Position?
                        # If So, Decrease Fine Position On X Axis
                        set ::enemy($loop2,fx) [expr {$::enemy($loop2,fx) - $step}]
                        # Spin Enemy Counter Clockwise
                        set ::enemy($loop2,spin) [expr {$::enemy($loop2,spin) - $step}]
                    }
                    if { $::enemy($loop2,fy) < [expr {$::enemy($loop2,y)*40}] } {
                        # Is Fine Position On Y Axis Lower Than Intended Position?
                        # If So, Increase Fine Position On Y Axis
                        set ::enemy($loop2,fy) [expr {$::enemy($loop2,fy) + $step}]
                        # Spin Enemy Clockwise
                        set ::enemy($loop2,spin) [expr {$::enemy($loop2,spin) + $step}]
                    }
                    if { $::enemy($loop2,fy) > [expr {$::enemy($loop2,y)*40}] } {
                        # Is Fine Position On Y Axis Higher Than Intended Position?
                        # If So, Decrease Fine Position On Y Axis
                        set ::enemy($loop2,fy) [expr {$::enemy($loop2,fy) - $step}]
                        # Spin Enemy Counter Clockwise
                        set ::enemy($loop2,spin) [expr {$::enemy($loop2,spin) - $step}]
                    }
                }
            }

            # Are Any Of The Enemies On Top Of The Player?
            if { ($::enemy($loop1,fx) == $::player(fx)) && \
                 ($::enemy($loop1,fy) == $::player(fy)) } {
                incr ::gDemo(lives) -1                  ; # If So, Player Loses A Life

                if { $::gDemo(lives) == 0 } {
                    # Are We Out Of Lives?
                    set ::gDemo(gameover) 1             ; # If So, gameover Becomes TRUE
                }

                ResetObjects                            ; # Reset Player / Enemy Positions
                PlaySound "Die.wav" true                ; # Play The Death Sound
            }
        }

        if { $::keys(RIGHT) && ($::player(x) < 10) && \
            ($::player(fx) == [expr {$::player(x)*60}]) && ($::player(fy) == [expr {$::player(y)*40}]) } {
            set ::hline($::player(x),$::player(y)) 1        ; # Mark The Current Horizontal Border As Filled
            incr ::player(x)                                ; # Move The Player Right
        }
        if { $::keys(LEFT) && ($::player(x) > 0) && \
            ($::player(fx) == [expr {$::player(x)*60}]) && ($::player(fy) == [expr {$::player(y)*40}]) } {
            incr ::player(x) -1                             ; # Move The Player Left
            set ::hline($::player(x),$::player(y)) 1        ; # Mark The Current Horizontal Border As Filled
        }
        if { $::keys(DOWN) && ($::player(y) < 10) && \
            ($::player(fx) == [expr {$::player(x)*60}]) && ($::player(fy) == [expr {$::player(y)*40}]) } {
            set ::vline($::player(x),$::player(y)) 1        ; # Mark The Current Vertical Border As Filled
            incr ::player(y)                                ; # Move The Player Down
        }
        if { $::keys(UP) && ($::player(y) > 0) && \
            ($::player(fx) == [expr {$::player(x)*60}]) && ($::player(fy) == [expr {$::player(y)*40}]) } {
            incr ::player(y) -1                             ; # Move The Player Up
            set ::vline($::player(x),$::player(y)) 1        ; # Mark The Current Verticle Border As Filled
        }

        # Is Fine Position On X Axis Lower Than Intended Position?
        if { $::player(fx) < [expr {$::player(x)*60}] } {
            # If So, Increase The Fine X Position
            set ::player(fx) [expr {$::player(fx) + $step}]
        }
        # Is Fine Position On X Axis Greater Than Intended Position?
        if { $::player(fx) > [expr {$::player(x)*60}] } {
            # If So, Decrease The Fine X Position
            set ::player(fx) [expr {$::player(fx) - $step}]
        }
        # Is Fine Position On Y Axis Lower Than Intended Position?
        if { $::player(fy) < [expr {$::player(y)*40}] } {
            # If So, Increase The Fine Y Position
            set ::player(fy) [expr {$::player(fy) + $step}]
        }
        # Is Fine Position On Y Axis Greater Than Intended Position?
        if { $::player(fy) > [expr {$::player(y)*40}] } {
            # If So, Decrease The Fine Y Position
            set ::player(fy) [expr {$::player(fy) - $step}]
        }
    } else {
        if { $::keys(SPACE) } {
            # If Spacebar Is Being Pressed
            set ::gDemo(gameover) 0                            ; # gameover Becomes FALSE
            set ::gDemo(filled)   1                            ; # filled Becomes TRUE
            set ::gDemo(level)    1                            ; # Starting Level Is Set Back To One
            set ::gDemo(level2)   1                            ; # Displayed Level Is Also Set To One
            set ::gDemo(stage)    0                            ; # Game Stage Is Set To Zero
            set ::gDemo(lives)    5                            ; # Lives Is Set To Five
        }
    }

    if { $::gDemo(filled) } {
        # Is The Grid Filled In?
        PlaySound "Complete.wav" true                          ; # If So, Play The Level Complete Sound
        incr ::gDemo(stage)                                    ; # Increase The Stage
        if { $::gDemo(stage) > 3 } {
            set ::gDemo(stage) 1                               ; # If So, Set The Stage To One
            incr ::gDemo(level)                                ; # Increase The Level
            incr ::gDemo(level2)                               ; # Increase The Displayed Level
            if { $::gDemo(level) > 3 } {
                set ::gDemo(level) 3                           ; # If So, Set The Level To 3
                incr ::gDemo(lives)                            ; # Give The Player A Free Life
                if { $::gDemo(lives) > 5 } {
                    # Does The Player Have More Than 5 Lives?
                    set ::gDemo(lives) 5                       ; # If So, Set Lives To Five
                }
            } 
        }

        ResetObjects                                    ; # Reset Player / Enemy Positions

        for { set loop1 0 } { $loop1 < 11 } { incr loop1 } {
            # Loop Through The Grid X Coordinates
            for { set loop2 0 } { $loop2 < 11 } { incr loop2 } {
                # Loop Through The Grid Y Coordinates
                if { $loop1 < 10 } {
                    set ::hline($loop1,$loop2) 0        ; # Set The Current Horizontal Value To FALSE
                }
                if { $loop2 < 10 } {
                    set ::vline($loop1,$loop2) 0        ; # Set The Current Vertical Value To FALSE
                }
            }
        }
    }

    # If The Player Hits The Hourglass While It's Being Displayed On The Screen
    if { ($::player(fx) == [expr {$::hourglass(x)*60}]) && \
         ($::player(fy) == [expr {$::hourglass(y)*40}]) && ($::hourglass(fx) == 1) } {
        # Play Freeze Enemy Sound
        PlaySound "Freeze.wav" false                    ; # OPA TODO LOOP
        set ::hourglass(fx) 2                           ; # Set The hourglass fx Variable To Two
        set ::hourglass(fy) 0                           ; # Set The hourglass fy Variable To Zero
    }

    # Spin The Player Clockwise
    set ::player(spin) [expr {$::player(spin) + 0.5 * $step}]
    if { $::player(spin) > 360.0 } {
        set ::player(spin) [expr {$::player(spin) - 360}]
    }

    # Spin The Hourglass Counter Clockwise
    set ::hourglass(spin) [expr {$::hourglass(spin) - 0.25 * $step}]
    if { $::hourglass(spin) < 0.0 } {
        set ::hourglass(spin) [expr {$::hourglass(spin) + 360}]
    }

    set ::hourglass(fy) [expr {$::hourglass(fy) + $step}]
    if { ($::hourglass(fx) == 0) && ($::hourglass(fy) > [expr {6000/$::gDemo(level)}]) } {
        PlaySound "Hourglass.wav" false
        set ::hourglass(x) [expr [IntRand]%10+1]    ; # Give The Hourglass A Random X Value
        set ::hourglass(y) [expr [IntRand]%11]      ; # Give The Hourglass A Random Y Value
        set ::hourglass(fx) 1                       ; # Set hourglass fx Variable To One (Hourglass Stage)
        set ::hourglass(fy) 0                       ; # Set hourglass fy Variable To Zero (Counter)
    }

    if { ($::hourglass(fx) == 1) && ($::hourglass(fy) > [expr {6000/$::gDemo(level)}]) } {
        set ::hourglass(fx) 0                       ; # If So, Set fx To Zero (Hourglass Will Vanish)
        set ::hourglass(fy) 0                       ; # Set fy to Zero (Counter Is Reset)
    }

    if { ($::hourglass(fx) == 2) && ($::hourglass(fy) > [expr {500+(500*$::gDemo(level))}]) } {
        PlaySound "" false                          ; # If So, Kill The Freeze Sound
        set ::hourglass(fx) 0                       ; # Set hourglass fx Variable To Zero
        set ::hourglass(fy) 0                       ; # Set hourglass fy Variable To Zero
    }

    incr ::gDemo(delay)                                    ; # Increase The Enemy Delay Counter
}

# Here's Where We Do All The Drawing
proc DisplayCallback { toglwin } {
    set start [TimerGetTime]                        ; # Grab Timer Value Before We Draw
    set count 0
    while {[TimerGetTime] < [expr {$start + double([lindex $::gDemo(steps) $::gDemo(adjust)])*2.0}] } {
        incr count                                  ; # Waste Cycles On Fast Systems
    }

    # Clear Screen And Depth Buffer
    glClear [expr $::GL_COLOR_BUFFER_BIT | $::GL_DEPTH_BUFFER_BIT]

    # Viewport command is not really needed, but has been inserted for
    # Mac OSX. Presentation framework (Tk) does not send a reshape event,
    # when switching from one demo to another.
    glViewport 0 0 [$toglwin width] [$toglwin height]

    glBindTexture GL_TEXTURE_2D [$::gDemo(texture) get 0]     ; # Select Our Font Texture
    glColor3f 1.0 0.5 1.0                              ; # Set Color To Purple
    glPrint 207 24 0 "GRID CRAZY"                      ; # Write GRID CRAZY On The Screen
    glColor3f 1.0 1.0 0.0                              ; # Set Color To Yellow
    glPrint 20 20 1 "Level:%2i" $::gDemo(level2)       ; # Write Actual Level Stats
    glPrint 20 40 1 "Stage:%2i" $::gDemo(stage)        ; # Write Stage Stats

    if { $::gDemo(gameover) } {
        # Pick A Random Color
        glColor3ub [expr [IntRand]%255] [expr [IntRand]%255] [expr [IntRand]%255]       
        glPrint 472 20 1 "GAME OVER"                  ; # Write GAME OVER To The Screen
        glPrint 456 40 1 "PRESS S"                    ; # Write PRESS S To The Screen
    }

    for { set loop1 0 } { $loop1 < [expr {$::gDemo(lives)-1}] } { incr loop1 } {
        glLoadIdentity                                            ; # Reset The View
        glTranslatef [expr {490+($loop1*40.0)}] 40.0 0.0          ; # Move To The Right Of Our Title Text
        glRotatef [expr {-1.0 * $::player(spin)}] 0.0 0.0 1.0     ; # Rotate Counter Clockwise
        glColor3f 0.0 1.0 0.0                                     ; # Set Player Color To Light Green
        glBegin GL_LINES                                          ; # Start Drawing Our Player Using Lines
            glVertex2d -5 -5                                      ; # Top Left Of Player
            glVertex2d  5  5                                      ; # Bottom Right Of Player
            glVertex2d  5 -5                                      ; # Top Right Of Player
            glVertex2d -5  5                                      ; # Bottom Left Of Player
        glEnd                                                     ; # Done Drawing The Player
        glRotatef [expr {-1.0 * $::player(spin)*0.5}] 0.0 0.0 1.0 ; # Rotate Counter Clockwise
        glColor3f 0.0 0.75 0.0                                    ; # Set Player Color To Dark Green
        glBegin GL_LINES                                          ; # Start Drawing Our Player Using Lines
            glVertex2d -7  0                                      ; # Left Center Of Player
            glVertex2d  7  0                                      ; # Right Center Of Player
            glVertex2d  0 -7                                      ; # Top Center Of Player
            glVertex2d  0  7                                      ; # Bottom Center Of Player
        glEnd                                                     ; # Done Drawing The Player
    }

    set ::gDemo(filled) 1                                         ; # Set Filled To True Before Testing
    glLineWidth  2.0                                              ; # Set Line Width For Cells To 2.0
    glDisable GL_LINE_SMOOTH                                      ; # Disable Antialiasing
    glLoadIdentity                                                ; # Reset The Current Modelview Matrix
    for { set loop1 0 } { $loop1 < 11 } { incr loop1 } {
        for { set loop2 0 } { $loop2 < 11 } { incr loop2 } {
            set l1 [expr {$loop1*60}]
            set l2 [expr {$loop2*40}]
            # Loop From Top To Bottom
            if { $::hline($loop1,$loop2) } {
                # Has The Horizontal Line Been Traced
                glColor3f 1.0 1.0 1.0                             ; # If So, Set Line Color To White
            } else {
                glColor3f 0.0 0.5 1.0                             ; # Set Line Color To Blue
            }

            if { $loop1 < 10 } {
                # Dont Draw To Far Right
                if { ! $::hline($loop1,$loop2) } {
                    # If A Horizontal Line Isn't Filled
                    set ::gDemo(filled) 0                         ; # filled Becomes False
                }
                glBegin GL_LINES                                  ; # Start Drawing Horizontal Cell Borders
                    glVertex2d [expr {20+$l1}] [expr {70+$l2}]    ; # Left Side Of Horizontal Line
                    glVertex2d [expr {80+$l1}] [expr {70+$l2}]    ; # Right Side Of Horizontal Line
                glEnd                                             ; # Done Drawing Horizontal Cell Borders
            }

            if { $::vline($loop1,$loop2) } {
                # Has The Horizontal Line Been Traced
                glColor3f 1.0 1.0 1.0                             ; # If So, Set Line Color To White
            } else {
                glColor3f 0.0 0.5 1.0                             ; # Set Line Color To Blue
            }
            if { $loop2 < 10 } {
                # Dont Draw To Far Down
                if { ! $::vline($loop1,$loop2) } {
                    # If A Verticle Line Isn't Filled
                    set ::gDemo(filled) 0                         ; # filled Becomes False
                }
                glBegin GL_LINES                                  ; # Start Drawing Verticle Cell Borders
                    glVertex2d [expr {20+$l1}] [expr { 70+$l2}]   ; # Left Side Of Horizontal Line
                    glVertex2d [expr {20+$l1}] [expr {110+$l2}]   ; # Right Side Of Horizontal Line
                glEnd                                             ; # Done Drawing Verticle Cell Borders
            }

            glEnable GL_TEXTURE_2D                                ; # Enable Texture Mapping
            glColor3f 1.0 1.0 1.0                                 ; # Bright White Color
            glBindTexture GL_TEXTURE_2D [$::gDemo(texture) get 1] ; # Select The Tile Image
            if { ($loop1<10) && ($loop2<10) } {
                # If In Bounds, Fill In Traced Boxes
                # Are All Sides Of The Box Traced?
                if { $::hline($loop1,$loop2) && $::hline($loop1,[expr {$loop2+1}]) && \
                     $::vline($loop1,$loop2) && $::vline([expr {$loop1+1}],$loop2) } {
                    set l1_10 [expr {double($loop1/10.0)}]
                    set l2_10 [expr {double($loop2/10.0)}]
                    glBegin GL_QUADS                                      ; # Draw A Textured Quad
                        glTexCoord2f [expr {$l1_10+0.1}] [expr {1.0-$l2_10}]
                        glVertex2d [expr {20+$l1+59}] [expr {70+$l2+1}]   ; # Top Right
                        glTexCoord2f $l1_10 [expr {1.0-$l2_10}]
                        glVertex2d [expr {20+$l1+1}] [expr {70+$l2+1}]    ; # Top Left
                        glTexCoord2f $l1_10 [expr {1.0-($l2_10+0.1)}]
                        glVertex2d [expr {20+$l1+1}] [expr {70+$l2+39}]   ; # Bottom Left
                        glTexCoord2f [expr {$l1_10+0.1}] [expr {1.0-($l2_10+0.1)}]
                        glVertex2d [expr {20+$l1+59}] [expr {70+$l2+39}]  ; # Bottom Right
                    glEnd                                                 ; # Done Texturing The Box
                }
            }
            glDisable GL_TEXTURE_2D                                     ; # Disable Texture Mapping
        }
    }
    glLineWidth 1.0                                                     ; # Set The Line Width To 1.0

    if { $::gDemo(anti) } {
        # Is Anti TRUE?
        glEnable GL_LINE_SMOOTH                                         ; # If So, Enable Antialiasing
    }

    if { $::hourglass(fx) == 1 } {
        # If fx=1 Draw The Hourglass
        glLoadIdentity                                          ; # Reset The Modelview Matrix
        # Move To The Fine Hourglass Position
        glTranslatef [expr {20.0+($::hourglass(x)*60)}] [expr {70.0+($::hourglass(y)*40)}] 0.0
        glRotatef $::hourglass(spin) 0.0 0.0 1.0                ; # Rotate Clockwise
        glColor3ub [expr [IntRand]%255] [expr [IntRand]%255] [expr [IntRand]%255]
        glBegin GL_LINES                                        ; # Start Drawing Our Hourglass Using Lines
            glVertex2d -5 -5                                    ; # Top Left Of Hourglass
            glVertex2d  5  5                                    ; # Bottom Right Of Hourglass
            glVertex2d  5 -5                                    ; # Top Right Of Hourglass
            glVertex2d -5  5                                    ; # Bottom Left Of Hourglass
            glVertex2d -5  5                                    ; # Bottom Left Of Hourglass
            glVertex2d  5  5                                    ; # Bottom Right Of Hourglass
            glVertex2d -5 -5                                    ; # Top Left Of Hourglass
            glVertex2d  5 -5                                    ; # Top Right Of Hourglass
        glEnd                                                   ; # Done Drawing The Hourglass
    }

    glLoadIdentity                                              ; # Reset The Modelview Matrix
    # Move To The Fine Player Position
    glTranslatef [expr {$::player(fx)+20.0}] [expr {$::player(fy)+70.0}] 0.0
    glRotatef $::player(spin) 0.0 0.0 1.0                       ; # Rotate Clockwise
    glColor3f 0.0 1.0 0.0                                       ; # Set Player Color To Light Green
    glBegin GL_LINES                                            ; # Start Drawing Our Player Using Lines
        glVertex2d -5 -5                                        ; # Top Left Of Player
        glVertex2d  5  5                                        ; # Bottom Right Of Player
        glVertex2d  5 -5                                        ; # Top Right Of Player
        glVertex2d -5  5                                        ; # Bottom Left Of Player
    glEnd                                                       ; # Done Drawing The Player
    glRotatef [expr {$::player(spin)*0.5}] 0.0 0.0 1.0          ; # Rotate Clockwise
    glColor3f 0.0 0.75 0.0                                      ; # Set Player Color To Dark Green
    glBegin GL_LINES                                            ; # Start Drawing Our Player Using Lines
        glVertex2d -7  0                                        ; # Left Center Of Player
        glVertex2d  7  0                                        ; # Right Center Of Player
        glVertex2d  0 -7                                        ; # Top Center Of Player
        glVertex2d  0  7                                        ; # Bottom Center Of Player
    glEnd                                                       ; # Done Drawing The Player

    set num [expr {$::gDemo(stage) * $::gDemo(level)}]
    for { set loop1 0 } { $loop1 < $num } { incr loop1 } {
        glLoadIdentity                                          ; # Reset The Modelview Matrix
        glTranslatef [expr {$::enemy($loop1,fx)+20.0}] [expr {$::enemy($loop1,fy)+70.0}] 0.0
        glColor3f 1.0 0.5 0.5                                   ; # Make Enemy Body Pink
        glBegin GL_LINES                                        ; # Start Drawing Enemy
            glVertex2d  0 -7                                    ; # Top Point Of Body
            glVertex2d -7  0                                    ; # Left Point Of Body
            glVertex2d -7  0                                    ; # Left Point Of Body
            glVertex2d  0  7                                    ; # Bottom Point Of Body
            glVertex2d  0  7                                    ; # Bottom Point Of Body
            glVertex2d  7  0                                    ; # Right Point Of Body
            glVertex2d  7  0                                    ; # Right Point Of Body
            glVertex2d  0 -7                                    ; # Top Point Of Body
        glEnd                                                   ; # Done Drawing Enemy Body
        glRotatef $::enemy($loop1,spin) 0.0 0.0 1.0             ; # Rotate The Enemy Blade
        glColor3f 1.0 0.0 0.0                                   ; # Make Enemy Blade Red
        glBegin GL_LINES                                        ; # Start Drawing Enemy Blade
            glVertex2d -7 -7                                    ; # Top Left Of Enemy
            glVertex2d  7  7                                    ; # Bottom Right Of Enemy
            glVertex2d -7  7                                    ; # Bottom Left Of Enemy
            glVertex2d  7 -7                                    ; # Top Right Of Enemy
        glEnd                                                   ; # Done Drawing Enemy Blade
    }
    $toglwin swapbuffers
    HandleLogic
}

proc Cleanup {} {
    unset ::vline
    unset ::hline
    unset ::enemy
    unset ::hourglass
    unset ::player
    unset ::keys
}

# Put all exit related code here.
proc ExitProg {} {
    KillFont
    exit
}

proc ToggleAnti {} {
    set ::gDemo(anti) [expr 1 - $::gDemo(anti)]
}

proc SetKeys { type onOff } {
    set ::keys($type) $onOff
}

proc Animate {} {
    .fr.toglwin postredisplay
    set ::animateId [tcl3dAfterIdle Animate]
}

proc StartAnimation {} {
    if { ! [info exists ::animateId] } {
        Animate
    }
}

proc StopAnimation {} {
    if { [info exists ::animateId] } {
        after cancel $::animateId 
        unset ::animateId
    }
}

# Create the OpenGL window and some Tk helper widgets.
proc CreateWindow {} {
    frame .fr
    pack .fr -expand 1 -fill both
    # Create Our OpenGL Window
    togl .fr.toglwin -width $::gDemo(winWidth) -height $::gDemo(winHeight) \
                     -swapinterval 1 \
                     -double true -depth true \
                     -createcommand CreateCallback \
                     -reshapecommand ReshapeCallback \
                     -displaycommand DisplayCallback 
    grid .fr.toglwin -row 0 -column 0 -sticky news
    grid rowconfigure .fr 0 -weight 1
    grid columnconfigure .fr 0 -weight 1
    wm title . "Tcl3D demo: NeHe's Line Tutorial (Lesson 21)"

    # Watch For ESC Key And Quit Messages
    wm protocol . WM_DELETE_WINDOW "ExitProg"
    bind . <Key-Escape>       "ExitProg"
    bind . <Key-F1>           "ToggleWindowMode"
    bind . <KeyPress-Left>    "SetKeys LEFT 1"
    bind . <KeyRelease-Left>  "SetKeys LEFT 0"
    bind . <KeyPress-Right>   "SetKeys RIGHT 1"
    bind . <KeyRelease-Right> "SetKeys RIGHT 0"
    bind . <KeyPress-Up>      "SetKeys UP 1"
    bind . <KeyRelease-Up>    "SetKeys UP 0"
    bind . <KeyPress-Down>    "SetKeys DOWN 1"
    bind . <KeyRelease-Down>  "SetKeys DOWN 0"
    bind . <KeyPress-s>       "SetKeys SPACE 1"
    bind . <KeyRelease-s>     "SetKeys SPACE 0"
    bind . <KeyPress-a>       "ToggleAnti"

    bind .fr.toglwin <1> "StartAnimation"
    bind .fr.toglwin <2> "StopAnimation"
    bind .fr.toglwin <3> "StopAnimation"
    bind .fr.toglwin <Control-Button-1> "StopAnimation"
}

CreateWindow
ResetObjects
TimerInit
if { [file tail [info script]] eq [file tail $::argv0] } {
    # If started directly from tclsh or wish, then start animation.
    update
    StartAnimation
}
