commit 02ba7c97f94a7f595763eab306fe906c671440d2 Author: Andrew Ekstedt Date: Mon Dec 2 01:22:03 2024 +0000 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3d8d9bb --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/cookie diff --git a/cron.sh b/cron.sh new file mode 100755 index 0000000..3c3585d --- /dev/null +++ b/cron.sh @@ -0,0 +1,79 @@ +#!/bin/bash +set -euo pipefail + +# install me in your crontab! +# 0 5 1-25 12 * $HOME/adventofcode2023/cron.sh auto + +year=2023 +root=~/adventofcode${year} + +getinput() { + curl -fsS -b "$root/cookie" -O "https://adventofcode.com/$year/day/$1/input" +} + +getsamples() { + curl -fsS "https://adventofcode.com/$year/day/$1" | + awk '/
/ {sub("
", ""); CODE=1; IDX+=1} /<\/code>/ {CODE=0} CODE {print > sprintf("sample%d.in", IDX)}'
+}
+
+if [[ "$*" = "auto" ]]; then
+    # get the current day in UTC with and without zero padding.
+    # UTC is a few hours ahead of US Eastern time, so
+    # it should match the day of the current puzzle even
+    # if the clocks on tilde.town and AoC aren't synced exactly
+    day_unpadded=$(TZ=UTC date '+%-d')
+    day_padded=$(TZ=UTC date '+%0d')
+    dir=$root/day$day_padded
+elif (( $# == 1 || $# == 2 )); then
+    day_unpadded=$(printf "%d" "$1")
+    day_padded=$(printf "%02d" "$1")
+    dir=${2:-}
+else
+    echo >&2 "usage: $0 day [out_dir]"
+    echo >&2 "       $0 auto"
+    exit 0
+fi
+
+if [[ -z "$dir" ]]; then
+    dir=$root/day$day_padded
+fi
+
+if test -e "$dir"; then
+    echo >&2 "'$dir' already exists! refusing to continue"
+    exit 1
+fi
+
+if ! test -e "$root/cookie"; then
+    echo >&2 "warning: cookiefile '$root/cookie' not found. consider creating it"
+fi
+
+cwd=${PWD:-$(pwd)}
+mkdir "$dir"
+cd "$dir" || exit 1
+
+error=0
+
+if ! getinput "$day_unpadded"; then
+    echo >&2 "error: failed to get input for day $day_unpadded"
+    error=1
+fi
+
+if ! getsamples "$day_unpadded"; then
+    echo >&2 "error: failed to get sample inputs for day $day_unpadded"
+    error=1
+fi
+
+if [[ $error -ne 0 ]]; then
+    # we might not have managed to grab any files
+    # at all. try to clean up after ourselves by removing
+    # the directory (if it's empty)
+    if cd "$cwd" && rmdir --ignore-fail-on-non-empty "$dir" >&2; then
+        if ! test -d "$dir"; then
+            echo "cleaning up '$dir'"
+        else
+            echo "not cleaning up '$dir'"
+        fi
+    fi
+fi
+
+exit $error
diff --git a/getinput.sh b/getinput.sh
new file mode 100755
index 0000000..4dbd21a
--- /dev/null
+++ b/getinput.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+set -eu
+curl -b ../cookie -O "https://adventofcode.com/2024/day/$1/input"
diff --git a/getsample.sh b/getsample.sh
new file mode 100755
index 0000000..acb77f0
--- /dev/null
+++ b/getsample.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+set -eu -o pipefail
+curl -fsS https://adventofcode.com/2024/day/"$1" | awk '/
/ {sub("
", ""); CODE=1; IDX+=1} /<\/code>/ {CODE=0} CODE {print > sprintf("sample%d.in", IDX)}'
diff --git a/prelude.tcl b/prelude.tcl
new file mode 100644
index 0000000..869fd6b
--- /dev/null
+++ b/prelude.tcl
@@ -0,0 +1,112 @@
+package require Tcl 8.6
+#package require textutil ;# from tcllib
+namespace import tcl::mathfunc::min
+namespace import tcl::mathfunc::max
+
+#puts "prelude [info script]"
+
+proc {#} args {}
+
+# short aliases for common things
+
+proc llen {lst} { return [llength $lst] }
+proc slen {str} { return [string length $str] }
+proc trim args { return [uplevel [concat string trim $args]] }
+proc replace {str args} { return [string map $args $str] }
+
+# sum and product
+
+proc ladd {list} {
+    set t 0
+    foreach x $list { incr t $x }
+    return $t
+}
+
+proc lmul {list} {
+    set p 1
+    foreach x $list { set p [expr {$p * $x}] }
+    return $p
+}
+
+# split s on a substring
+proc splitstr {s sep} {
+    # replace $sep with ascii char 30, aka "record separator"
+    return [split [replace $s $sep "\x1E"] "\x1E"]
+}
+
+proc must_regexp args {
+    if {! [uplevel [concat regexp $args]]} {
+        error "regexp failed"
+    }
+}
+
+proc regsplit {re str {varname {}}} {
+    set result [textutil::split::splitx $str $re]
+    if {$varname ne ""} {
+        upvar $varname dest
+        set dest $result
+    }
+    return $result
+}
+
+# transpose a list of strings
+#
+#     % transpose {abc 123}
+#     a1 b2 c3
+#     % transpose a1 b2 c3
+#     abc 123
+#
+proc transpose strings {
+    set out {}
+    set C [slen [lindex $strings 0]]
+    for {set i 0} {$i < $C} {incr i} {
+        set column {}
+        foreach row $strings {
+            lappend column [string index $row $i]
+        }
+        lappend out [join $column ""]
+    }
+    return $out
+}
+
+# transpose a list of lists
+#
+#     % ltranspose {{a b c} {1 2 3}}
+#     {a 1} {b 2} {c 3}
+#     % ltranspose {{a 1} {b 2} {c 3}}
+#     {a b c} {1 2 3}
+#
+proc ltranspose lists {
+    set out {}
+    set C [llen [lindex $lists 0]]
+    for {set i 0} {$i < $C} {incr i} {
+        set column {}
+        foreach row $lists {
+            lappend column [lindex $row $i]
+        }
+        lappend out $column
+    }
+    return $out
+}
+
+# extracts one or more indexes from a strided list
+# e.g.
+#       % set l {0 1 2   a b c   x y z}
+#       % lextract $l 3 0
+#       0 a x
+#       % lextract $l 3 {1 2}
+#       1 2 b c y z
+#
+# equivalent to [lmap {a b c} $lst {list $b $c}] except that
+# you don't have to name the list elements
+proc lextract {lst stride index} {
+    set i 0
+    set out {}
+    if {$stride <= 0} { error "stride must be positive: $stride" }
+    for {set i 0} {$i < [llength $lst]} {incr i $stride} {
+        foreach j $index {
+            lappend out [lindex $lst $i+$j]
+        }
+    }
+    return $out
+}