BHE - Tetris in 375 lines. This was the first game engine I came up with so I'm sure it can be shortened quite a bit more. No real frills here. It's almost bug free: there are a few times when a piece is able to overlap an existing one.
I originally added this as an easter egg into a program at work which is why the code footprint is small as well as the game itself, =). Well, it started off under 100 lines like the other easter eggs ... oh well
If the blocks are too small, change the blocksize (in pixels) variable near the top. The whole table will be adjusted.
Controls:
uniquename 2013aug01
Surprisingly good for such a small amount of code. It deserves an image:
namespace eval tetris { variable grid variable run variable step 0 variable level 0 variable score 0 variable lines 0 variable piece variable blocksize 10 variable pieces array set pieces { 0 {lightblue 4 0 2 2 {1 1 1 1}} 1 {yellow 4 0 4 3 {0 1 0 0 1 0 0 1 0 0 1 0}} 2 {purple 4 0 3 3 {0 1 0 1 1 0 1 0 0}} 3 {green 4 0 3 3 {0 1 0 0 1 1 0 0 1}} 4 {red 4 0 3 3 {0 1 0 1 1 1 0 0 0}} 5 {blue 4 0 3 3 {0 0 1 1 1 1 0 0 0}} 6 {orange 4 0 3 3 {1 0 0 1 1 1 0 0 0}} } proc build {} { variable blocksize wm title . "TinyTetris" # canvas should be 15x20 blocks # the game table is 10x20 # the right 5 blocks are for the nextpiece canvas .c -width [expr {$blocksize*15 + 2}] -height [expr {$blocksize*20 + 2}] .c create rectangle 0 0 [expr {$blocksize*10 + 2}] [expr {$blocksize*20 + 2}] -fill black -outline red label .l1 -text "POINTS" -font {courier 8} label .l2 -anchor n -font {courier 8} -textvariable tetris::score label .l3 -text "LEVEL" -font {courier 8} label .l4 -anchor n -font {courier 8} -textvariable tetris::level label .l5 -text "LINES" -font {courier 8} label .l6 -anchor n -font {courier 8} -textvariable tetris::lines grid .l1 .c -sticky w grid .l2 ^ -sticky nw grid .l3 ^ -sticky w grid .l4 ^ -sticky nw grid .l5 ^ -sticky w grid .l6 ^ -sticky nw grid rowconfigure . 1 -weight 1 grid rowconfigure . 3 -weight 1 grid rowconfigure . 5 -weight 1 set f [frame .debug] label $f.d1 -text "step : " label $f.d2 -textvariable tetris::step label $f.d3 -text "score: " label $f.d4 -textvariable tetris::score label $f.d5 -text "piece: " label $f.d6 -textvariable tetris::piece # etc button $f.next -text "next" -command tetris::nextStep grid $f.d1 $f.d2 grid $f.d3 $f.d4 grid $f.d5 $f.d6 grid $f.next #pack $f -side top bind . <Up> "tetris::rotateCW" bind . <Left> "tetris::move left" bind . <Right> "tetris::move right" bind . <space> "tetris::drop" bind . <Down> "tetris::nextStep" bind . <F2> "tetris::restart" restart } proc restart {} { clearGrid variable run 1 after 1000 tetris::newPiece } proc endGame {} { variable run 0 } proc clearGrid {} { variable lines 0 variable score 0 variable step 0 variable level 0 variable run 1 variable nextpiece variable grid foreach i {0 1 2 3 4 5 6 7 8 9} { for {set j 0} {$j < 20} {incr j} { set grid($i,$j) "" } } .c delete block set nextpiece [randomPiece] cycle } proc cycle {} { variable level variable run if {!$run} { return } nextStep set speed [expr {100 - 8*$level}] if {$speed < 20} { set speed 20 } after $speed tetris::cycle } proc newPiece {} { variable piece variable nextpiece variable step 0 variable blocksize set piece $nextpiece set nextpiece [randomPiece] if {[checkPiece] == 0} { endGame return } drawPiece .c delete nextpiece foreach {color X Y h w piecegrid} $nextpiece {} for {set j 0} {$j < $h} {incr j} { for {set i 0} {$i < $w} {incr i} { if {[lindex $piecegrid [expr {$i + $j*$w}]] != 0} { set x [expr {12*$blocksize + $i*$blocksize}] set y [expr {3*$blocksize + $j*$blocksize}] set ID [.c create rectangle $x $y [expr {$x+$blocksize}] [expr {$y+$blocksize}] \ -fill $color -outline gray25 -tags {block nextpiece}] lset nextpiece 5 [expr {$i + $j*$w}] $ID } } } } proc randomPiece {} { variable pieces return $pieces([expr {int(rand()*7)}]) } proc checkPiece {} { variable piece variable grid foreach {color X Y h w piecegrid} $piece {} for {set j 0} {$j < $h} {incr j} { for {set i 0} {$i < $w} {incr i} { if {[lindex $piecegrid [expr {$i + $j*$w}]] == 0} { continue } set x [expr {$X+$i}] set y [expr {$Y+$j}] if {$x < 0} { return 0 } if {$x > 9} { return 0 } if {$grid($x,$y) != ""} { return 0 } } } return 1 } proc drawPiece {} { variable grid variable piece variable step variable blocksize .c delete piece foreach {color X Y h w piecegrid} $piece {} for {set j 0} {$j < $h} {incr j} { for {set i 0} {$i < $w} {incr i} { if {[lindex $piecegrid [expr {$i + $j*$w}]] != 0} { set x [expr {($X+$i)*$blocksize + 1}] set y [expr {($Y+$j)*$blocksize}] if {[expr {$step%$blocksize}] > 0} { incr y [expr {$step%$blocksize - $blocksize}] } set ID [.c create rectangle $x $y [expr {$x+$blocksize}] [expr {$y+$blocksize}] \ -fill $color -outline gray25 -tags {block piece}] lset piece 5 [expr {$i + $j*$w}] $ID } } } } proc rotateCCW {} { variable grid variable piece if {$piece == ""} { return } set oldpiece $piece foreach {color X Y h w piecegrid} $piece {} set newgrid $piecegrid set s [expr {$h-1}] for {set j 0} {$j < $h} {incr j} { for {set i 0} {$i < $w} {incr i} { set id [lindex $piecegrid [expr {$i + $j*$w}]] set p $j set q [expr {$s-$i}] lset newgrid [expr {$p + $q*$h}] $id } } set piece [list $color $X $Y $w $h $newgrid] if {[checkPiece]} { drawPiece } else { set piece $oldpiece } } proc rotateCW {} { variable grid variable piece if {$piece == ""} { return } set oldpiece $piece foreach {color X Y h w piecegrid} $piece {} set newgrid $piecegrid set s [expr {$h-1}] for {set j 0} {$j < $h} {incr j} { for {set i 0} {$i < $w} {incr i} { set id [lindex $piecegrid [expr {$i + $j*$w}]] set p [expr {$s-$j}] set q $i lset newgrid [expr {$p + $q*$h}] $id } } set piece [list $color $X $Y $w $h $newgrid] if {[checkPiece]} { drawPiece } else { set piece $oldpiece } } proc drop {} { variable piece while {$piece != ""} { nextStep } } proc move {dir} { variable piece variable blocksize if {$piece == ""} { return } foreach {color X Y h w piecegrid} $piece {} switch $dir { left { set dx -1 } right { set dx 1 } } lset piece 1 [expr {$X + $dx}] if {[checkPiece] == 0} { lset piece 1 $X return } .c move piece [expr {$dx*$blocksize}] 0 } proc nextStep {} { variable step variable piece variable grid variable blocksize if {$piece == ""} { return } foreach {color X Y h w piecegrid} $piece {} incr step .c move piece 0 1 set offset [expr {$step%$blocksize}] if {$offset == 1} { lset piece 2 [incr Y] } if {$offset != 0} { return } # foreach block in piece # if Y+1 is a block, call finish # else incr Y for {set i 0} {$i < $w} {incr i} { for {set j [expr {$h-1}]} {$j >= 0} {incr j -1} { if {[lindex $piecegrid [expr {$i + $j*$w}]] != 0} { set x [expr {$X+$i}] set y [expr {$Y+$j+1}] if {$y == 20 || $grid($x,$y) != ""} { finishPiece return } break } } } } proc finishPiece {} { variable grid variable piece variable level variable score variable lines foreach {color X Y h w piecegrid} $piece {} for {set j 0} {$j < $h} {incr j} { for {set i 0} {$i < $w} {incr i} { set id [lindex $piecegrid [expr {$i + $j*$w}]] if {$id != 0} { set grid([expr {$X+$i}],[expr {$Y+$j}]) $id } } } .c itemconfig piece -tags {block} set nlines 0 for {set y $Y} {$y < [expr {$Y+$h}]} {incr y} { incr nlines [checkLine $y] } incr score [expr {($level+1)*[lindex {0 40 100 300 1200} $nlines]}] incr lines $nlines set level [expr {$lines/10}] set piece {} after 500 "tetris::newPiece" } proc checkLine {y} { variable grid variable blocksize if {$y == 20} { return 0 } set ids {} for {set x 0} {$x < 10} {incr x} { lappend ids $grid($x,$y) if {$grid($x,$y) == ""} { return 0 } } foreach id $ids { .c delete $id } while {$y > 0} { for {set x 0} {$x < 10} {incr x} { set id $grid($x,[expr {$y-1}]) set grid($x,$y) $id .c move $id 0 $blocksize } incr y -1 } for {set x 0} {$x < 10} {incr x} { set grid($x,0) "" } return 1 } } tetris::build