typed day22

pull/1/head
Ben Greenman 8 years ago
parent 70ecb35568
commit 452cb8f0fa

@ -0,0 +1,206 @@
#lang typed/racket
(define BASE-HP 50)
(define BASE-MANA 500)
(define MAGIC-MISSILE-DAMAGE 4)
(define DRAIN-DAMAGE 2)
(define SHIELD-ARMOR 7)
(define SHIELD-DURATION 6)
(define POISON-DAMAGE 3)
(define POISON-DURATION 6)
(define RECHARGE-MANA 101)
(define RECHARGE-DURATION 5)
(struct player (
[hp : Integer]
[attack : Natural]
[mana : Integer]
) #:transparent)
(define-type Player player)
(define-type Spell (U 'magic-missile 'drain 'shield 'poison 'recharge))
(define-type Active-Spells (Listof (Pairof Natural Spell)))
;(define-type Game-State (Values Player Player Active-Spells))
(define ALL-SPELLS : (Listof Spell)
'(magic-missile
drain
shield
poison
recharge))
(: unknown-spell (All (A) (-> Spell A)))
(define (unknown-spell spell)
(raise-user-error 'day22 "Unknown spell '~a'" spell))
(: spell-mana (-> Spell Natural))
(define (spell-mana s)
(case s
[(magic-missile)
53]
[(drain)
73]
[(shield)
113]
[(poison)
173]
[(recharge)
229]
[else
(unknown-spell s)]))
(: spell*-mana (-> (Listof Spell) Natural))
(define (spell*-mana spell*)
(for/sum : Natural ([spell (in-list spell*)])
(spell-mana spell)))
(: make-player (-> Player))
(define (make-player)
(player BASE-HP 0 BASE-MANA))
(: make-boss (-> Index * Player))
(define (make-boss . val*)
(player (car val*) (cadr val*) 0))
(: hp+ (-> Player Integer Player))
(define (hp+ p val)
(match-define (player hp attack mana) p)
(player (+ hp val) attack mana))
(: hp- (-> Player Integer Player))
(define (hp- p val)
(hp+ p (- val)))
(: mana+ (-> Player Integer Player))
(define (mana+ p val)
(match-define (player hp attack mana) p)
(player hp attack (+ mana val)))
(: mana- (-> Player Integer Player))
(define (mana- p val)
(mana+ p (- val)))
(: active? (-> Spell Active-Spells Boolean))
(define (active? spell active-spells)
(for/or : Boolean
([ctr+spell (in-list active-spells)])
(eq? spell (cdr ctr+spell))))
(: has-enough-mana? (-> Player Spell Boolean))
(define (has-enough-mana? player spell)
(<= (spell-mana spell) (player-mana player)))
(: boss-attack (-> Player Player Boolean Player))
(define (boss-attack player boss shield?)
(define boss-damage (max 1 (- (player-attack boss) (if shield? SHIELD-ARMOR 0))))
(hp- player boss-damage))
(: apply-effects (-> Player Player (Listof (Pairof Natural Spell)) (Values Player Player Active-Spells)))
(define (apply-effects player boss spells)
(for/fold ([p+ : Player player]
[b+ : Player boss]
[a+ : Active-Spells '()])
([ctr+spell (in-list spells)])
(match-define (cons ctr spell) ctr+spell)
(define a++ (if (= 1 ctr) a+ (cons (cons (assert (- ctr 1) index?) spell) a+)))
(case spell
[(poison)
(values p+ (hp- b+ POISON-DAMAGE) a++)]
[(recharge)
(values (mana+ p+ RECHARGE-MANA) b+ a++)]
[(shield)
(values p+ b+ a++)]
[else
(unknown-spell spell)])))
(: apply-spell (-> Spell Player Player Active-Spells (Values Player Player Active-Spells)))
(define (apply-spell spell player0 boss active-spells)
(define player (mana- player0 (spell-mana spell)))
(case spell
[(magic-missile)
(values player (hp- boss MAGIC-MISSILE-DAMAGE) active-spells)]
[(drain)
(values (hp+ player DRAIN-DAMAGE) (hp- boss DRAIN-DAMAGE) active-spells)]
[(shield)
(values player boss (cons (cons SHIELD-DURATION 'shield) active-spells))]
[(poison)
(values player boss (cons (cons POISON-DURATION 'poison) active-spells))]
[(recharge)
(values player boss (cons (cons RECHARGE-DURATION 'recharge) active-spells))]
[else
(unknown-spell spell)]))
(: win? (-> Player Player Boolean))
(define (win? player boss)
(dead? boss))
(: dead? (-> Player Boolean))
(define (dead? player)
(<= (player-hp player) 0))
(: lose? (-> Player Player Boolean))
(define lose?
(let ([MIN-MANA (apply min (map spell-mana ALL-SPELLS))])
(λ (player boss)
(or (dead? player)
(< (player-mana player) MIN-MANA)))))
(: win/least-mana : Player Player [#:hard-mode? Boolean] -> (Listof Spell))
(define (win/least-mana player boss #:hard-mode? [hard-mode? #f])
(or
(let maybe-win/least-mana : (U #f (Listof Spell))
([player : Player player]
[boss : Player boss]
[active-spells : Active-Spells '()]
[current-turn : Natural 0])
(cond
[(lose? player boss)
#f]
[(win? player boss)
'()]
[else
(define-values (p+ b+ a+) (apply-effects player boss active-spells))
(define next-turn (+ 1 current-turn))
(if (even? current-turn)
(let ([p+ (if hard-mode? (hp- p+ 1) p+)])
(minimize-mana
(for/list : (Listof (U #f (Listof Spell)))
([spell (in-list ALL-SPELLS)]
#:when (and (not (active? spell a+))
(has-enough-mana? p+ spell)))
(define-values (p++ b++ a++) (apply-spell spell p+ b+ a+))
(define future-spells (maybe-win/least-mana p++ b++ a++ next-turn))
(and future-spells (cons spell future-spells)))))
(maybe-win/least-mana (boss-attack p+ b+ (active? 'shield a+)) b+ a+ next-turn))]))
(raise-user-error 'day22 "Impossible for ~a to beat ~a. Sorry.\n" player boss)))
(: minimize-mana (-> (Listof (U #f (Listof Spell))) (U #f (Listof Spell))))
(define (minimize-mana spell**)
(for/fold : (U #f (Listof Spell))
([best : (U #f (Listof Spell)) #f])
([other : (U #f (Listof Spell)) (in-list spell**)])
(if (or (not best)
(and best other (< (spell*-mana other) (spell*-mana best))))
other
best)))
(: q1 (-> String Integer))
(define (q1 input-str)
(define player (make-player))
(define boss (apply make-boss (filter index? (map string->number (string-split input-str)))))
(spell*-mana (win/least-mana player boss)))
(: q2 (-> String Integer))
(define (q2 input-str)
(define player (make-player))
(define boss (apply make-boss (filter index? (map string->number (string-split input-str)))))
(spell*-mana (win/least-mana player boss #:hard-mode? #t)))
(module+ test
(require typed/rackunit)
(define input-str (file->string "../day22-input.txt"))
(check-equal? (q1 input-str) 1269)
(check-equal? (q2 input-str) 1309))