#!/usr/bin/env tclsh9.0 namespace import ::tcl::mathop::* proc input {} { global rows cols grid rr rc moves set rows 0 set cols 0 while {[gets stdin line] >= 0} { if {$line eq ""} break if {$cols == 0} {set cols [* 2 [string length $line]]} set row [list] foreach tile [split $line ""] { switch -- $tile { # {lappend row # #} O {lappend row < >} . {lappend row . .} @ {set rr $rows; set rc [llength $row]; lappend row . .} } } lappend grid $row incr rows } while {[gets stdin line] >= 0} { append moves $line } } proc show {} { global grid foreach row $grid { puts [join $row ""] } } proc move {} { global rows cols grid rr rc moves boxes foreach move [split $moves ""] { # show; puts "" # If the target spot is a wall, do nothing. # If it's open space, move there. # Otherwise, project out the path of tiles from there to the first wall. switch -- $move { ^ {set dr -1; set dc 0} v {set dr 1; set dc 0} < {set dr 0; set dc -1} > {set dr 0; set dc 1} default continue } set target [lindex $grid $rr+$dr $rc+$dc] if {$target eq "#"} continue if {$target eq "."} { incr rr $dr incr rc $dc continue } # If we're going horizontally, it's a lot like part 1. We just # need to figure out how many boxes we're pushing (if any), # then move the boxes (more work than before), then move the robot. if {$dr == 0} { set path "" set c $rc while 1 { incr c $dc set target [lindex $grid $rr $c] if {$target eq "#"} break append path $target } # If it's all <>'s, we can't move, so do nothing. # Otherwise, the location of the first . tells us how many boxes # we can push. set n [string first . $path] if {$n < 0} continue # Push n boxes. # Path projection is <><>. or ><><. depending on which way we're # moving. Either way, we can't just drop an O in the empty spot. # We have to move all the tiles one by one, farthest first. # puts "path=$path n=$n" for {set i [+ 1 $n]} {$i > 1} {incr i -1} { set j [- $i 1] lset grid $rr $rc+[* $i $dc] [lindex $grid $rr $rc+[* $j $dc]] } lset grid $rr $rc+$dc . # And finally, move to the newly emptied tile. incr rc $dc } else { # Moving vertically. Now it's harder. # Build a dict of all the boxes (< side) that must move. set boxes [dict create] mustmove [+ $rr $dr] $rc $dr # Only move if ALL of these boxes can move (i.e. none of them # are against a wall). set canmove 1 foreach box [dict keys $boxes] { lassign $box r c if {[lindex $grid [+ $r $dr] $c] eq "#" || [lindex $grid [+ $r $dr] [+ $c 1]] eq "#"} { set canmove 0 break } } if {! $canmove} continue # Move them all. Sort by row, direction depending on dr. set b [dict keys $boxes] if {$dr < 0} { set b [lsort -integer -increasing -index 0 $b] } else { set b [lsort -integer -decreasing -index 0 $b] } foreach box $b { lassign $box r c lset grid [+ $r $dr] $c < lset grid [+ $r $dr] [+ $c 1] > lset grid $r $c . lset grid $r [+ $c 1] . } # Finally, move the robot. incr rr $dr } } } proc mustmove {r c dr} { global grid boxes set here [lindex $grid $r $c] if {$here eq "."} return if {$here eq "#"} return if {$here eq "<"} { dict set boxes [list $r $c] 1 mustmove [+ $r $dr] $c $dr mustmove [+ $r $dr] [+ $c 1] $dr } else { dict set boxes [list $r [- $c 1]] 1 mustmove [+ $r $dr] [- $c 1] $dr mustmove [+ $r $dr] $c $dr } } proc count {} { global rows cols grid set total 0 for {set r 0} {$r < $rows} {incr r} { for {set c 0} {$c < $cols} {incr c} { if {[lindex $grid $r $c] eq "<"} { incr total [expr {100*$r + $c}] } } } puts $total } input # show # puts "rows=$rows cols=$cols rr=$rr rc=$rc moves=$moves" move # show count