#lang typed-scheme
(require (planet dvanhorn/typed-student/advanced)
(planet dvanhorn/typed-student/world))
(provide (all-defined-out))
(define block-size 20) (define board-width 10) (define board-height 20)
(define-struct block ([x : Number] [y : Number] [color : Color]))
(define-type-alias Block block)
(define-struct tetra ([center : Posn] [blocks : BSet]))
(define-type-alias Tetra tetra)
(define-type-alias BSet (Listof Block))
(define-struct world ([tetra : Tetra] [blocks : BSet]))
(define-type-alias World world)
(require/typed htdp/world
[big-bang (Number Number Number World -> Boolean)]
[stop-when ((World -> Boolean) -> Boolean)]
[on-tick-event ((World -> World) -> Boolean)]
[on-redraw ((World -> Scene) -> Boolean)]
[on-key-event ((World KeyEvent -> World) -> Boolean)])
(: block=? (Block Block -> Boolean))
(define (block=? b1 b2)
(and (= (block-x b1) (block-x b2))
(= (block-y b1) (block-y b2))))
(: block-move (Number Number Block -> Block))
(define (block-move dx dy b)
(make-block (+ dx (block-x b))
(+ dy (block-y b))
(block-color b)))
(: block-rotate-ccw (Posn Block -> Block))
(define (block-rotate-ccw c b)
(make-block (+ (posn-x c)
(- (posn-y c)
(block-y b)))
(+ (posn-y c)
(- (block-x b)
(posn-x c)))
(block-color b)))
(: block-rotate-cw (Posn Block -> Block))
(define (block-rotate-cw c b)
(block-rotate-ccw c (block-rotate-ccw c (block-rotate-ccw c b))))
(: blocks-contains? (BSet Block -> Boolean))
(define (blocks-contains? bs b)
(ormap (lambda: ([c : Block]) (block=? b c)) bs))
(: blocks-subset? (BSet BSet -> Boolean))
(define (blocks-subset? bs1 bs2)
(andmap (lambda: ([b : Block]) (blocks-contains? bs2 b)) bs1))
(: blocks=? (BSet BSet -> Boolean))
(define (blocks=? bs1 bs2)
(and (blocks-subset? bs1 bs2)
(blocks-subset? bs2 bs1)))
(: blocks-intersect (BSet BSet -> BSet))
(define (blocks-intersect bs1 bs2)
(filter (lambda: ([b : Block]) (blocks-contains? bs2 b)) bs1))
(: blocks-union (BSet BSet -> BSet))
(define (blocks-union bs1 bs2)
(foldr (lambda: ([b : Block] [bs : BSet])
(cond [(blocks-contains? bs b) bs]
[else (cons b bs)]))
bs2
bs1))
(: blocks-count (BSet -> Number))
(define (blocks-count bs)
(length bs))
(: blocks-max-y (BSet -> Number))
(define (blocks-max-y bs)
(foldr (lambda: ([b : Block] [n : Number]) (max (block-y b) n)) 0 bs))
(: blocks-min-x (BSet -> Number))
(define (blocks-min-x bs)
(foldr (lambda: ([b : Block] [n : Number]) (min (block-x b) n)) board-width bs))
(: blocks-max-x (BSet -> Number))
(define (blocks-max-x bs)
(foldr (lambda: ([b : Block] [n : Number]) (max (block-x b) n)) 0 bs))
(: blocks-move (Number Number BSet -> BSet))
(define (blocks-move dx dy bs)
(map (lambda: ([b : Block]) (block-move dx dy b)) bs))
(: blocks-rotate-ccw (Posn BSet -> BSet))
(define (blocks-rotate-ccw c bs)
(map (lambda: ([b : Block]) (block-rotate-ccw c b)) bs))
(: blocks-rotate-cw (Posn BSet -> BSet))
(define (blocks-rotate-cw c bs)
(map (lambda: ([b : Block]) (block-rotate-cw c b)) bs))
(: blocks-change-color (BSet Color -> BSet))
(define (blocks-change-color bs c)
(map (lambda: ([b : Block]) (make-block (block-x b)
(block-y b)
c))
bs))
(: blocks-overflow? (BSet -> Boolean))
(define (blocks-overflow? bs)
(ormap (lambda: ([b : Block]) (<= (block-y b) 0)) bs))
(: blocks-row (BSet Number -> BSet))
(define (blocks-row bs i)
(filter (lambda: ([b : Block]) (= i (block-y b))) bs))
(: full-row? (BSet Number -> Boolean))
(define (full-row? bs i)
(= board-width (blocks-count (blocks-row bs i))))
(: eliminate-full-rows (BSet -> BSet))
(define (eliminate-full-rows bs)
(letrec: ((elim-row : (Number Number -> BSet)
(lambda (i offset)
(cond [(< i 0) empty]
[(full-row? bs i) (elim-row (sub1 i) (add1 offset))]
[else (blocks-union (elim-row (sub1 i) offset)
(blocks-move 0 offset (blocks-row bs i)))]))))
(elim-row board-height 0)))
(: tetra-move (Number Number Tetra -> Tetra))
(define (tetra-move dx dy t)
(make-tetra (make-posn (+ dx (posn-x (tetra-center t)))
(+ dy (posn-y (tetra-center t))))
(blocks-move dx dy (tetra-blocks t))))
(: tetra-rotate-ccw (Tetra -> Tetra))
(define (tetra-rotate-ccw tetra)
(make-tetra (tetra-center tetra)
(blocks-rotate-ccw (tetra-center tetra)
(tetra-blocks tetra))))
(: tetra-rotate-cw (Tetra -> Tetra))
(define (tetra-rotate-cw tetra)
(make-tetra (tetra-center tetra)
(blocks-rotate-cw (tetra-center tetra)
(tetra-blocks tetra))))
(: tetra-overlaps-blocks? (Tetra BSet -> Boolean))
(define (tetra-overlaps-blocks? t bs)
(not (empty? (blocks-intersect (tetra-blocks t) bs))))
(: tetra-change-color (Tetra Color -> Tetra))
(define (tetra-change-color t c)
(make-tetra (tetra-center t)
(blocks-change-color (tetra-blocks t) c)))
(: build-tetra-blocks (Color Number Number Number Number Number
Number Number Number Number Number -> Tetra))
(define (build-tetra-blocks color xc yc x1 y1 x2 y2 x3 y3 x4 y4)
(tetra-move 3 0
(make-tetra (make-posn xc yc)
(list (make-block x1 y1 color)
(make-block x2 y2 color)
(make-block x3 y3 color)
(make-block x4 y4 color)))))
(: tetras (Listof Tetra))
(define tetras
(list
(build-tetra-blocks 'green 1/2 -3/2 0 -1 0 -2 1 -1 1 -2)
(build-tetra-blocks 'blue 1 -1 0 -1 1 -1 2 -1 3 -1)
(build-tetra-blocks 'purple 1 -1 0 -1 1 -1 2 -1 2 -2)
(build-tetra-blocks 'cyan 1 -1 0 -1 1 -1 2 -1 0 -2)
(build-tetra-blocks 'orange 1 -1 0 -1 1 -1 2 -1 1 -2)
(build-tetra-blocks 'red 1 -1 0 -1 1 -1 1 -2 2 -2)
(build-tetra-blocks 'pink 1 -1 0 -2 1 -2 1 -1 2 -1)
))
(: touchdown (World -> World))
(define (touchdown w)
(make-world (list-pick-random tetras)
(eliminate-full-rows (blocks-union (tetra-blocks (world-tetra w))
(world-blocks w)))))
(: world-jump-down (World -> World))
(define (world-jump-down w)
(cond [(landed? w) w]
[else (world-jump-down (make-world (tetra-move 0 1 (world-tetra w))
(world-blocks w)))]))
(: landed-on-blocks? (World -> Boolean))
(define (landed-on-blocks? w)
(tetra-overlaps-blocks? (tetra-move 0 1 (world-tetra w))
(world-blocks w)))
(: landed-on-floor? (World -> Boolean))
(define (landed-on-floor? w)
(= (blocks-max-y (tetra-blocks (world-tetra w)))
(sub1 board-height)))
(: landed? (World -> Boolean))
(define (landed? w)
(or (landed-on-blocks? w)
(landed-on-floor? w)))
(: next-world (World -> World))
(define (next-world w)
(cond [(landed? w) (touchdown w)]
[else (make-world (tetra-move 0 1 (world-tetra w))
(world-blocks w))]))
(: try-new-tetra (World Tetra -> World))
(define (try-new-tetra w new-tetra)
(cond [(or (< (blocks-min-x (tetra-blocks new-tetra)) 0)
(>= (blocks-max-x (tetra-blocks new-tetra)) board-width)
(tetra-overlaps-blocks? new-tetra (world-blocks w)))
w]
[else (make-world new-tetra (world-blocks w))]))
(: world-move (Number Number World -> World))
(define (world-move dx dy w)
(try-new-tetra w (tetra-move dx dy (world-tetra w))))
(: world-rotate-ccw (World -> World))
(define (world-rotate-ccw w)
(try-new-tetra w (tetra-rotate-ccw (world-tetra w))))
(: world-rotate-cw (World -> World))
(define (world-rotate-cw w)
(try-new-tetra w (tetra-rotate-cw (world-tetra w))))
(: ghost-blocks (World -> BSet))
(define (ghost-blocks w)
(tetra-blocks (tetra-change-color (world-tetra (world-jump-down w))
'gray)))
(: world-key-move (World KeyEvent -> World))
(define (world-key-move w k)
(cond [(key=? k 'left)
(world-move -1 0 w)]
[(key=? k 'right)
(world-move 1 0 w)]
[(key=? k 'down)
(world-jump-down w)]
[(key=? k #\a)
(world-rotate-ccw w)]
[(key=? k #\s)
(world-rotate-cw w)]
[else w]))
(: list-pick-random (All (a) ((Listof a) -> a)))
(define (list-pick-random ls)
(list-ref ls (random (length ls))))
(: world->image (World -> Scene))
(define (world->image w)
(place-image (blocks->image (append (tetra-blocks (world-tetra w))
(append (ghost-blocks w)
(world-blocks w))))
0 0
(empty-scene (* board-width block-size)
(* board-height block-size))))
(: blocks->image (BSet -> Scene))
(define (blocks->image bs)
(foldr (lambda: ([b : Block] [img : Image])
(cond [(<= 0 (block-y b)) (place-block b img)]
[else img]))
(empty-scene (add1 (* board-width block-size))
(add1 (* board-height block-size)))
bs))
(: block->image (Block -> Image))
(define (block->image b)
(overlay
(rectangle (add1 block-size) (add1 block-size) 'solid (block-color b))
(rectangle (add1 block-size) (add1 block-size) 'outline 'black)))
(: place-block (Block Scene -> Scene))
(define (place-block b scene)
(place-image (block->image b)
(+ (* (block-x b) block-size) (/ block-size 2))
(+ (* (block-y b) block-size) (/ block-size 2))
scene))
(define world0
(make-world (list-pick-random tetras) empty))
(big-bang (* board-width block-size)
(* board-height block-size)
(/ 1.0 5)
world0)
(stop-when (lambda: ([w : World]) (blocks-overflow? (world-blocks w))))
(on-tick-event next-world)
(on-redraw world->image)
(on-key-event world-key-move)