diff options
Diffstat (limited to 'examples/tetra')
-rw-r--r-- | examples/tetra/tetra.scm | 695 |
1 files changed, 508 insertions, 187 deletions
diff --git a/examples/tetra/tetra.scm b/examples/tetra/tetra.scm index 2093c2f..5bb2ac5 100644 --- a/examples/tetra/tetra.scm +++ b/examples/tetra/tetra.scm @@ -20,36 +20,76 @@ ;; ;;; Code: -(use-modules (chickadee math matrix) +(use-modules (chickadee math easings) + (chickadee math matrix) (chickadee math rect) (chickadee math vector) (chickadee render color) + (chickadee render font) + (chickadee render particles) (chickadee render texture) (chickadee render sprite) (chickadee scripting) (ice-9 match) (oop goops) (srfi srfi-1) + (srfi srfi-43) (starling asset) (starling kernel) (starling node) (starling node-2d) (starling scene)) + +;;; +;;; Constants +;;; + +(define window-width 600) +(define window-height 800) +(define board-width 10) +(define board-height 20) +(define tile-width 32) +(define tile-height 32) + + +;;; +;;; Assets +;;; + (define (load-block-atlas file-name) (let ((texture (load-image file-name))) - (split-texture texture 32 32))) + (split-texture texture tile-width tile-height))) (define-asset atlas (load-block-atlas "images/blocks.png")) +(define-asset star (load-image "images/star.png")) +(define-asset click-font (load-font "fonts/click.xml")) -(define shapes - '((i (0 0) (0 1) (0 2) (0 3)) - (o (0 0) (0 1) (1 0) (1 1)) - (t (0 0) (0 1) (0 2) (1 1)) - (j (1 0) (1 1) (1 2) (0 0)) - (l (0 0) (0 1) (0 2) (1 0)) - (s (1 0) (1 1) (0 2) (0 1)) - (z (0 0) (0 1) (1 2) (1 1)))) + +;;; +;;; Convenience Procedures/Macros +;;; + +(define (transparent color) + "Make a fully transparent version of COLOR." + (make-color (color-r color) + (color-g color) + (color-b color) + 0.0)) + +(define (centered width) + "Return the X coordinate needed to center an object occupying WIDTH +pixels in the right-hand side UI area." + ;; We're not centering it with the full screen, just the portion of + ;; the screen available to the right of the game board. + (let ((board-width-pixels (+ (* board-width tile-width) (* 16.0 2.0)))) + (+ board-width-pixels + (/ (- window-width board-width-pixels width) 2.0)))) + +(define (centered-text text) + "Return the X coordinate needed to center the string TEXT in the UI +area next to the board." + (centered (* (font-line-width (asset-ref click-font) text) 2.0))) (define-syntax for-each-permutation (syntax-rules () @@ -83,47 +123,102 @@ ((_ ((var start end) rest ...) body ...) (map-permutation ((var start end 1) rest ...) body ...)))) -(define rects - (map-permutation ((x 0 4) (y 0 4)) - `((,x ,y) . ,(make-rect (* x 32.0) (* y 32.0) 32.0 32.0)))) + +;;; +;;; Tiles +;;; +;; A single block on a tetris board. (define-class <tile> () (type #:accessor type #:init-form #f) - (position #:accessor position #:init-keyword #:position)) - -(define (type->atlas-index type) - (match type - ('yellow 0) - ('blue 1) - ('green 2) - ('purple 3) - ('red 4) - ('gray 5) - (_ #f))) + (area #:getter area #:init-keyword #:area)) (define (for-each-coord proc) - (for-each-permutation ((x 0 10) (y 0 20)) (proc x y))) - -(define (make-tiles) - (let ((tiles (make-array #f 10 20))) - (for-each-coord - (lambda (x y) - (array-set! tiles - (make <tile> - #:position (vec2 (* x 32.0) - (* y 32.0))) - x y))) - tiles)) + (for-each-permutation ((x 0 board-width) (y 0 board-height)) (proc x y))) + + +;;; +;;; Pieces +;;; +;; Moveable tetromino. (define-class <piece> (<node-2d>) (x #:accessor x #:init-keyword #:x #:init-form 0) (y #:accessor y #:init-keyword #:y #:init-form 0) (type #:accessor type #:init-keyword #:type) - (shape #:accessor shape #:init-keyword #:shape) - (batch #:accessor batch #:init-keyword #:batch)) + (shape #:accessor shape #:init-keyword #:shape)) + +(define shapes + '((i (0 1) (1 1) (2 1) (3 1)) + (j (0 0) (1 0) (2 0) (0 1)) + (l (0 0) (1 0) (2 0) (2 1)) + (o (0 0) (0 1) (1 0) (1 1)) + (s (0 0) (1 0) (1 1) (2 1)) + (t (0 0) (1 0) (2 0) (1 1)) + (z (0 1) (1 1) (1 0) (2 0)))) + +(define colors + '((i . yellow) + (j . blue) + (l . red) + (o . purple) + (s . green) + (t . gray) + (z . red))) + +(define (type->atlas-index type) + (match type + ('yellow 0) + ('blue 0) + ('green 0) + ('purple 0) + ('red 0) + ('gray 0) + (_ #f))) + +;; A convenient cache of all the possible sprite locations with a +;; tetrnomino to avoid recalculating them all the time. +(define rects + (map-permutation ((x 0 4) (y 0 4)) + `((,x ,y) . ,(make-rect (* x tile-width) + (* y tile-height) + tile-width + tile-height)))) + +(define-method (piece-width (piece <piece>)) + (let loop ((shape (shape piece)) + (minx #f) + (maxx #f)) + (match shape + (() (+ (- maxx minx) 1)) + (((x _) . rest) + (loop rest + (if minx (min minx x) x) + (if maxx (max maxx x) x)))))) + +(define-method (piece-height (piece <piece>)) + (let loop ((shape (shape piece)) + (miny #f) + (maxy #f)) + (match shape + (() (+ (- maxy miny) 1)) + (((_ y) . rest) + (loop rest + (if miny (min miny y) y) + (if maxy (max maxy y) y)))))) + +(define-method (piece-y-offset (piece <piece>)) + (fold (lambda (p memo) + (match p + ((_ y) + (if memo + (min y memo) + y)))) + #f + (shape piece))) (define-method (max-x (piece <piece>)) - (- 9 + (- board-width 1 (fold (lambda (p memo) (match p ((x _) @@ -131,80 +226,132 @@ 0 (shape piece)))) -(define-method (piece-above-board? (piece <piece>)) - (let ((py (y piece))) - (any (match-lambda - ((_ y) - (>= (+ y py) 20))) - (shape piece)))) - -(define-method (move-piece (piece <piece>) new-x new-y) +(define-method (move-piece (piece <piece>) new-x new-y init?) (let ((old-x (x piece)) - (old-y (y piece))) + (old-y (y piece)) + (px (* new-x tile-width)) + (py (* new-y tile-height))) (set! (x piece) new-x) (set! (y piece) new-y) - (with-agenda (agenda piece) - (script - (move-to piece (* old-x 32.0) (* old-y 32.0)) - (move-to piece (* new-x 32.0) (* new-y 32.0) 8))))) + (if init? + (teleport piece px py) + (run-script piece + (move-to piece px py 8))))) -(define-method (rotate-piece (piece <piece>)) - (let* ((max-y (fold (lambda (pos memo) - (match pos - ((x y) - (max y memo)))) - 0 - (shape piece))) - (new-shape (map (match-lambda - ((x y) - (list (- max-y y) x))) - (shape piece))) - (px (x piece)) - (py (y piece))) - (when (every (match-lambda - ((x y) - (let ((sx (+ x px)) - (sy (+ y py))) - (and (>= sx 0) - (< sx 10) - (>= sy 0))))) - new-shape) - (set! (shape piece) new-shape)))) +(define-method (move-piece (piece <piece>) new-x new-y) + (move-piece piece new-x new-y #f)) (define-method (render (piece <piece>) alpha) - (let ((batch (batch piece))) - (for-each (match-lambda - (pos - (sprite-batch-add* batch - (assoc-ref rects pos) - (world-matrix piece) - #:texture-region - (texture-atlas-ref (asset-ref atlas) - (type->atlas-index - (type piece)))))) - (shape piece)))) + (for-each (match-lambda + (pos + (draw-sprite* (texture-atlas-ref (asset-ref atlas) + (type->atlas-index + (type piece))) + (assoc-ref rects pos) + (world-matrix piece)))) + (shape piece))) + +(define (make-random-piece) + (let ((type (list-ref '(i o t j l s z) (random 7)))) + (make <piece> + #:type (assq-ref colors type) + #:shape (assq-ref shapes type) + #:rank 999))) + + +;;; +;;; Rows +;;; -(define (make-random-piece batch) - (let* ((shape (assq-ref shapes (list-ref '(i o t j l s z) (random 7)))) - (piece (make <piece> - #:type (list-ref '(yellow blue red purple green gray) - (random 6)) - #:shape shape - #:batch batch - #:x 4 - #:y 20))) - (move-to piece (* 4.0 32.0) (* 20.0 32.0)) - piece)) +;; A horizontal line of tiles on the tetris board. +(define-class <row> (<node-2d>) + (y #:accessor y #:init-keyword #:y) + (tiles #:accessor tiles #:init-keyword #:tiles)) +(define-method (on-boot (row <row>)) + (teleport row 0.0 (* (y row) tile-height))) + +(define-method (render (row <row>) alpha) + (let ((tiles (tiles row)) + (batch (batch (parent row))) + (atlas (asset-ref atlas))) + (let loop ((x 0)) + (when (< x board-width) + (let* ((tile (vector-ref tiles x)) + (i (type->atlas-index (type tile)))) + (when i + (sprite-batch-add* batch + (area tile) + (world-matrix row) + #:texture-region (texture-atlas-ref atlas i)))) + (loop (+ x 1)))))) + +(define-method (on-clear (row <row>) particles) + (run-script row + (scale-to row 0.0 0.0 10) + (detach row)) + ;; Emit some particles! woooooo + (let* ((pos (position row)) + (x (vec2-y pos)) + (y (vec2-y pos))) + (vector-for-each (lambda (i tile) + (let* ((area (make-rect (* i tile-width) + y + tile-width + tile-height)) + (emitter (make-particle-emitter area 2 10))) + (add-particle-emitter particles emitter))) + (tiles row)))) + +(define (make-row y) + (define (make-tile x) + (make <tile> + #:area (make-rect (* x tile-width) + 0.0 + tile-width + tile-height))) + (let ((row (make-vector board-width))) + (vector-for-each (lambda (x e) + (vector-set! row x (make-tile x))) + row) + (make <row> #:y y #:tiles row))) + + +;;; +;;; Boards +;;; + +;; A collection of rows forming the complete tetris game board. (define-class <board> (<node-2d>) (batch #:accessor batch #:init-keyword #:batch) - (tiles #:getter tiles #:init-thunk make-tiles)) + (rows #:accessor rows #:init-form (make-vector board-height #f))) + +(define-method (on-boot (board <board>)) + (set! (batch board) (make-sprite-batch #f)) + (attach-to board + (make <filled-rect> + #:name 'background + #:region (make-rect 0.0 0.0 320.0 640.0) + #:color tango-aluminium-6) + (make <sprite-batch> + #:name 'batch + #:batch (batch board)) + (make <particles> + #:name 'particles + #:particles (make-particles 2000 + #:texture (asset-ref star) + #:start-color tango-butter + #:end-color (transparent tango-butter) + #:lifetime 10)))) + +(define-method (get-tile (board <board>) x y) + (vector-ref (tiles (vector-ref (rows board) y)) x)) (define-method (board-ref (board <board>) x y) - (type (array-ref (tiles board) x y))) + (type (get-tile board x y))) (define-method (board-set! (board <board>) x y new-type) - (set! (type (array-ref (tiles board) x y)) new-type)) + (set! (type (get-tile board x y)) new-type)) (define-method (add-to-board (board <board>) (piece <piece>)) (let ((type (type piece)) @@ -214,112 +361,273 @@ ((sx sy) (let ((bx (+ x sx)) (by (+ y sy))) - (when (< by 20) + (when (< by board-height) (board-set! board bx by type))))) (shape piece)))) +(define-method (overlaps-board? (piece <piece>) (board <board>)) + (let ((px (x piece)) + (py (y piece))) + (any (match-lambda + ((x y) + (board-ref board (+ x px) (+ y py)))) + (shape piece)))) + +(define-method (out-of-bounds? (piece <piece>)) + (let ((px (x piece)) + (py (y piece))) + (any (match-lambda + ((x y) + (let ((sx (+ x px)) + (sy (+ y py))) + (or (< sx 0) + (>= sx board-width) + (< sy 0) + (>= sy board-height))))) + (shape piece)))) + +(define-method (rotate-piece (piece <piece>) (board <board>)) + (let* ((max-y (fold (lambda (pos memo) + (match pos + ((x y) + (max y memo)))) + 0 + (shape piece))) + (old-shape (shape piece)) + (new-shape (map (match-lambda + ((x y) + (list (- max-y y) x))) + old-shape))) + (set! (shape piece) new-shape) + (when (or (out-of-bounds? piece) + (overlaps-board? piece board)) + (set! (shape piece) old-shape)))) + (define-method (filled-rows (board <board>)) (define (row-full? y) (let loop ((x 0)) (cond - ((= x 10) + ((= x board-width) #t) ((board-ref board x y) (loop (+ x 1))) (else #f)))) - (let loop ((y 0)) - (if (= y 20) + (let loop ((y 19)) + (if (< y 0) '() (if (row-full? y) - (cons y (loop (+ y 1))) - (loop (+ y 1)))))) + (cons y (loop (- y 1))) + (loop (- y 1)))))) (define-method (remove-filled-rows (board <board>)) - (let loop ((rows (filled-rows board))) - (match rows - (() #t) - ((y . rest) - (let y-loop ((y (+ y 1))) - (when (< y 20) - (let x-loop ((x 0)) - (when (< x 10) - (board-set! board x (- y 1) (board-ref board x y)) - (x-loop (+ x 1)))) - (y-loop (+ y 1)))) - (loop rest))))) - -(define-method (board-clear! (board <board>)) - (for-each-coord - (lambda (x y) - (set! (type (array-ref (tiles board) x y)) #f)))) + (let* ((rows (rows board)) + (rows-to-remove (filled-rows board)) + (anim-duration 10)) + (let loop ((dead-rows rows-to-remove) + (count 0)) + (match dead-rows + (() (length rows-to-remove)) + ((dead-y . rest) + ;; Remove the cleared row. + (let ((dead-row (vector-ref rows dead-y)) + (particles (particles (& board particles)))) + (on-clear dead-row particles)) + ;; Move everything above the cleared row down. + (let y-loop ((old-y (+ dead-y 1))) + (when (< old-y board-height) + (let ((row (vector-ref rows old-y)) + (new-y (- old-y 1))) + (vector-set! rows new-y row) + (set! (y row) new-y) + ;; Smoothly animate the drop down. + (run-script row + (unless (zero? count) + (sleep (* count anim-duration))) + (move-to row 0.0 (* new-y tile-height) anim-duration + (if (zero? count) smoothstep linear)))) + (y-loop (+ old-y 1)))) + ;; Add a new blank row to the top. + (let ((new-row (make-row (- board-height 1)))) + (attach-to board new-row) + (vector-set! rows (- board-height 1) new-row)) + (loop rest (+ count 1))))))) + +(define-method (clear-board (board <board>)) + (let ((rows (rows board))) + (when rows + (let loop ((y 0)) + (when (< y board-height) + (let ((old-row (vector-ref rows y))) + (and old-row (detach old-row))) + (let ((new-row (make-row y))) + (vector-set! rows y new-row) + (attach-to board new-row)) + (loop (+ y 1))))))) (define-method (touching-next-row? (piece <piece>) (board <board>)) (any (match-lambda ((sx sy) (let ((bx (+ (x piece) sx)) (by (- (+ (y piece) sy) 1))) - (and (< by 20) - (board-ref board bx by))))) + (or (= by -1) + (and (< by board-height) + (board-ref board bx by)))))) (shape piece))) -(define-method (render (board <board>) alpha) - (let ((tiles (tiles board)) - (batch (batch board)) - (atlas (asset-ref atlas))) - (for-each-coord - (lambda (x y) - (let* ((tile (array-ref tiles x y)) - (i (type->atlas-index (type tile)))) - (when i - (sprite-batch-add! batch (position tile) - #:texture-region (texture-atlas-ref atlas i)))))))) +(define-method (render-tree (board <board>) alpha) + (set-sprite-batch-texture! (batch board) + (texture-atlas-texture (asset-ref atlas))) + (next-method)) + + +;;; +;;; Tetra +;;; + +;; The main game scene. (define-class <tetra> (<scene-2d>) (state #:accessor state #:init-form 'play) - (batch #:getter batch #:init-form (make-sprite-batch #f)) - (board #:accessor board #:init-form (make <board>)) + (board #:accessor board) (piece #:accessor piece #:init-form #f) + (next-piece #:accessor next-piece #:init-form #f) (timer #:accessor timer #:init-form 0) - (down-interval #:accessor down-interval #:init-form 30)) + (down-interval #:accessor down-interval #:init-form 30) + (score #:accessor score #:init-form -1)) + +(define-method (game-over (tetra <tetra>)) + (set! (state tetra) 'game-over) + (let* ((message "GAME OVER") + (instructions "press ENTER to play again") + (font (asset-ref click-font)) + (message-width (* (font-line-width font message) 2.0)) + (instructions-width (* (font-line-width font instructions) 2.0)) + (line-height (* (font-line-height font) 2.0)) + (padding 16.0) + (border 2.0) + (container-width (+ (max message-width instructions-width) (* padding 2.0))) + (container-height (+ (* line-height 2) (* padding 3.0))) + (container (make <node-2d> + #:name 'game-over-container + #:rank 9999 + #:position (vec2 (- (+ (/ (* board-width tile-width) 2.0) 16.0) + (/ container-width 2.0)) + (/ (- window-height container-height) 2.0))))) + (attach-to container + (make <filled-rect> + #:name 'background + #:region (make-rect 0.0 0.0 container-width container-height) + #:color tango-aluminium-3) + (make <filled-rect> + #:name 'background + #:region (make-rect border + border + (- container-width (* border 2.0)) + (- container-height (* border 2.0))) + #:color tango-aluminium-6) + (make <label> + #:name 'message + #:text message + #:font click-font + #:scale (vec2 2.0 2.0) + #:position (vec2 (/ (- container-width message-width) 2.0) + (+ line-height (* padding 2.0)))) + (make <label> + #:name 'instructions + #:text instructions + #:font click-font + #:scale (vec2 2.0 2.0) + #:position (vec2 (/ (- container-width instructions-width) 2.0) + padding))) + (attach-to tetra container))) + +(define-method (add-next-piece (tetra <tetra>)) + (let* ((new-piece (make-random-piece)) + (w (* (piece-width new-piece) tile-width)) + (h (* (piece-height new-piece) tile-height)) + (y-offset (+ (/ (- (* tile-height 3.0) h) 2.0) + (* (piece-y-offset new-piece) tile-height))) + (x (centered w)) + (y (- 630.0 16.0 h y-offset))) + (teleport new-piece x y) + (set! (next-piece tetra) new-piece) + (attach-to tetra new-piece))) (define-method (add-new-piece (tetra <tetra>)) - (let ((new-piece (make-random-piece (batch tetra)))) - (when (piece tetra) - (detach (piece tetra))) - (set! (piece tetra) new-piece) - (set! (timer tetra) 0) - (attach-to tetra new-piece))) + (let ((new-piece (next-piece tetra))) + (detach new-piece) + (attach-to (& tetra board-container) new-piece) + (move-piece new-piece 4 18 #t) + (if (overlaps-board? new-piece (board tetra)) + (begin + (detach new-piece) + (game-over tetra)) + (begin + (when (piece tetra) + (detach (piece tetra))) + (set! (piece tetra) new-piece) + (set! (timer tetra) 0) + (add-next-piece tetra))))) (define-method (on-boot (tetra <tetra>)) - (set! (batch (board tetra)) (batch tetra)) + (set! (board tetra) (make <board>)) (attach-to tetra (make <filled-rect> - #:name 'background - #:region (make-rect 0.0 0.0 320.0 640.0) - #:color tango-light-sky-blue) - (board tetra) - (make <sprite-batch> - #:batch (batch tetra) - #:rank 9999)) - (add-new-piece tetra)) + #:region (make-rect 0.0 0.0 window-width window-height) + #:color tango-aluminium-5) + (let ((text "NEXT")) + (make <label> + #:name 'next-label + #:text text + #:font click-font + #:position (vec2 (centered-text text) 630.0) + #:scale (vec2 2.0 2.0))) + (make <filled-rect> + #:name 'next-background + #:color tango-aluminium-6 + #:region (let* ((w (* tile-width 5.0)) + (h (* tile-height 3.0)) + (x (centered w)) + (y (- 630.0 h 16.0))) + (make-rect x y w h))) + (let ((text "HI CHRIS")) + (make <label> + #:name 'score-label + #:text text + #:font click-font + #:position (vec2 (centered-text text) 420.0) + #:scale (vec2 2.0 2.0))) + (make <label> + #:name 'score-counter + #:font click-font + #:scale (vec2 2.0 2.0)) + (make <node-2d> + #:name 'board-container + #:position (vec2 16.0 + (/ (- window-height + (* board-height tile-height)) + 2.0)))) + (attach-to (& tetra board-container) + (board tetra)) + (reset-game tetra)) (define-method (move-piece (tetra <tetra>) dx dy) (let* ((p (piece tetra)) (b (board tetra)) (new-x (min (max (+ (x p) dx) 0) (max-x p))) - (new-y (max (+ (y p) dy) 0))) + (new-y (+ (y p) dy))) (define (touch?) (any (match-lambda ((sx sy) (let ((bx (+ new-x sx)) (by (+ new-y sy))) - (and (>= bx 0) - (< bx 10) - (>= by 0) - (< by 20) - (board-ref b bx by))))) + (or (< bx 0) + (>= bx board-width) + (< by 0) + (>= by board-height) + (board-ref b bx by))))) (shape p))) (when (and p (not (touch?))) (move-piece p new-x new-y)))) @@ -327,16 +635,32 @@ (define-method (rotate-piece (tetra <tetra>)) (let ((p (piece tetra))) (when p - (rotate-piece p)))) + (rotate-piece p (board tetra))))) + +(define-method (change-score (tetra <tetra>) new-score) + (unless (= new-score (score tetra)) + (let ((score-text (number->string new-score)) + (label (& tetra score-counter))) + (set! (score tetra) new-score) + (set! (text label) score-text) + (teleport label (centered-text score-text) 380.0)))) (define-method (reset-game (tetra <tetra>)) (set! (state tetra) 'play) (set! (timer tetra) 0) - (board-clear! (board tetra)) + (when (piece tetra) + (detach (piece tetra))) + (when (next-piece tetra) + (detach (next-piece tetra))) + (set! (piece tetra) #f) + (set! (next-piece tetra) #f) + (clear-board (board tetra)) + (add-next-piece tetra) (add-new-piece tetra) - (let ((game-over-text (& tetra game-over-text))) - (when game-over-text - (detach game-over-text)))) + (change-score tetra 0) + (let ((game-over-container (& tetra game-over-container))) + (when game-over-container + (detach game-over-container)))) (define-method (on-key-press (tetra <tetra>) key scancode modifiers repeat?) (match (state tetra) @@ -360,32 +684,29 @@ (when (= (timer tetra) (down-interval tetra)) (let ((p (piece tetra)) (b (board tetra))) - (when (or (zero? (y p)) - (touching-next-row? p b)) - (if (piece-above-board? p) - (begin - (set! (state tetra) 'game-over) - (attach-to tetra - (make <label> - #:name 'game-over-text - #:text "*** GAME OVER ***\n\n\nPress ENTER to play again" - #:position (vec2 340.0 620.0)))) - (begin - (add-to-board b p) - (remove-filled-rows b) - (add-new-piece tetra)))) - (move-piece tetra 0 -1) + (if (touching-next-row? p b) + (begin + (add-to-board b p) + (change-score tetra (+ (score tetra) + ;; Calculate score based on how + ;; many rows were cleared by + ;; placing the tetromino. + (match (remove-filled-rows b) + (0 0) + (1 40) + (2 100) + (3 300) + (4 1200)))) + (add-new-piece tetra)) + (move-piece tetra 0 -1)) (set! (timer tetra) 0))))) -(define-method (render-tree (tetra <tetra>) alpha) - (set-sprite-batch-texture! (batch tetra) - (texture-atlas-texture (asset-ref atlas))) - (next-method)) - +;; Seed the random number generator. +(set! *random-state* (random-state-from-platform)) (boot-kernel (make <kernel> #:window-config (make <window-config> - #:title "tetris clone" - #:width 600 - #:height 800)) + #:title "tetra" + #:width window-width + #:height window-height)) (lambda () (make <tetra>))) |