From 6bc296631b7cc6988112489030ad7a8c18648e88 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Sat, 26 Sep 2020 16:48:57 -0400 Subject: Commit this old stuff that was hanging around. --- examples/tetra/tetra.scm | 695 ++++++++++++++++++++++++++++++++++------------- guix.scm | 8 +- starling/node-2d.scm | 2 +- 3 files changed, 513 insertions(+), 192 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 () (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 - #: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 () (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 )) + (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 )) + (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 )) + (fold (lambda (p memo) + (match p + ((_ y) + (if memo + (min y memo) + y)))) + #f + (shape piece))) (define-method (max-x (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 )) - (let ((py (y piece))) - (any (match-lambda - ((_ y) - (>= (+ y py) 20))) - (shape piece)))) - -(define-method (move-piece (piece ) new-x new-y) +(define-method (move-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 )) - (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 ) new-x new-y) + (move-piece piece new-x new-y #f)) (define-method (render (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 + #: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 - #: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 () + (y #:accessor y #:init-keyword #:y) + (tiles #:accessor tiles #:init-keyword #:tiles)) +(define-method (on-boot (row )) + (teleport row 0.0 (* (y row) tile-height))) + +(define-method (render (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 ) 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 + #: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 #:y y #:tiles row))) + + +;;; +;;; Boards +;;; + +;; A collection of rows forming the complete tetris game board. (define-class () (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 )) + (set! (batch board) (make-sprite-batch #f)) + (attach-to board + (make + #:name 'background + #:region (make-rect 0.0 0.0 320.0 640.0) + #:color tango-aluminium-6) + (make + #:name 'batch + #:batch (batch board)) + (make + #: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 ) x y) + (vector-ref (tiles (vector-ref (rows board) y)) x)) (define-method (board-ref (board ) x y) - (type (array-ref (tiles board) x y))) + (type (get-tile board x y))) (define-method (board-set! (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 ) (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 ) (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 )) + (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 ) (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 )) (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 )) - (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 )) - (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 )) + (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 ) (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 ) 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 ) alpha) + (set-sprite-batch-texture! (batch board) + (texture-atlas-texture (asset-ref atlas))) + (next-method)) + + +;;; +;;; Tetra +;;; + +;; The main game scene. (define-class () (state #:accessor state #:init-form 'play) - (batch #:getter batch #:init-form (make-sprite-batch #f)) - (board #:accessor board #:init-form (make )) + (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 )) + (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 + #: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 + #:name 'background + #:region (make-rect 0.0 0.0 container-width container-height) + #:color tango-aluminium-3) + (make + #:name 'background + #:region (make-rect border + border + (- container-width (* border 2.0)) + (- container-height (* border 2.0))) + #:color tango-aluminium-6) + (make