#!/bin/sh #| -*- scheme -*- exec csi -R r7rs -ss "$0" "$@" game --- 2023 lisp game jam (C) Case Duckworth Distributed under the terms of the LATCRIFPL, v1.0. See COPYING for details. |# (import (scheme base) (scheme time) (scheme write) (chicken io) (chicken port) (chicken random) (yolk common) (yolk xterm) (yolk erase) (yolk cursor) (srfi 1) (srfi 18) (matchable) (stty)) (define (log . xs) (with-output-to-port (current-error-port) (lambda () (for-each (lambda (x) (display x) (display " ") x) xs) (newline)))) (define (main args) (parameterize ((world (make-world 80 25))) (dynamic-wind game-setup game-loop game-cleanup)) #t) (define (game-setup) ;; Prepare terminal (stty '(raw (not echo))) (set-buffering-mode! (current-input-port) #:none 1) (set-buffering-mode! (current-output-port) #:none 1) (draw alt-buffer-enable erase-screen (cursor-save) invisible-cursor) ;; Set up world (world-init (world)) (world-draw (world)) ;; Start snake (snake-init (snake))) (define (game-cleanup) ;; Restore terminal (stty '(cooked echo)) (draw cursor-home (cursor-restore) alt-buffer-disable visible-cursor)) (define (game-loop) (let loop ((c (readch))) (if (eq? c 'done) #t (let ((done? #f)) (match c ((or #\q #\) (set! done? #t)) ;; Escape characters (#\escape (match (readch) (#\[ (match (readch) (#\A (up!)) (#\B (down!)) (#\C (right!)) (#\D (left!)) (else))) (else))) (else)) (snake-step (snake) (world)) (world-draw (world)) (loop (if done? 'done (readch))))))) (define (readch) (let ((c (integer->char (read-byte)))) c)) ;;; Drawing stuff (define (draw . instructions) (for-each (lambda (i) (cond ((string? i) (display i)) ((list? i) (apply draw i)) (else (error "Don't know how to draw" i)))) instructions)) ;;; World (define-record-type (%make-world width height map) world? (width world-width world-width-set!) (height world-height world-height-set!) (map world-map world-map-set!)) (define (in-bounds? world x y) (or (< x (world-width world)) (> x 0) (< y (world-height world)) (> y 0))) (define (coords->index world x y) (if (in-bounds? world x y) (+ x (* (world-width world) y)) (error "Out of bounds" (list (world-width world) (world-height world)) (list x y)))) (define (world-get world x y) (vector-ref (world-map world) (coords->index world x y))) (define (world-set! world x y obj) (vector-set! (world-map world) (coords->index world x y) obj)) (define (make-world width height) (%make-world width height (make-vector (* width height) #f))) (define world (make-parameter #f)) (define (for-world proc world) (do ((y 1 (+ y 1))) ((= y (world-height world)) #t) (do ((x 1 (+ x 1))) ((= x (world-width world)) #t) (proc world x y)))) (define (world-draw world) (draw cursor-home) (for-world (lambda (w x y) (cond ((thing? (world-get w x y)) (draw (cursor-move x y) (thing-look (world-get w x y)))) (else (draw (cursor-move x y) " ")))) world)) (define (world-init world) (for-world (lambda (w x y) (cond ((or (= x 1) (= y 1) (= x (- (world-width world) 1)) (= y (- (world-height world) 1))) (thing-place! w (wall x y))) (else))) world) (thing-place! world (me)) (thing-place! world (snake-head (snake))) (for-each (lambda (t) (thing-place! world t)) (snake-tail (snake)))) ;;; Things (define-record-type (make-thing name look x y z attrs) thing? (name thing-name) (look thing-look thing-look-set!) (x thing-x thing-x-set!) (y thing-y thing-y-set!) (z thing-z thing-z-set!) (attrs thing-attrs thing-attrs-set!)) (define (thing-attr-get thing attr) (let ((x (assoc attr (thing-attrs thing)))) (if x (cdr x) #f))) (define (thing-attr-set! thing attr val) (let ((x (assoc attr (thing-attrs thing)))) (if x (set-cdr! x val) (thing-attrs-set! thing (cons (cons attr val) (thing-attrs thing)))))) (define (thing-place! world thing) (world-set! world (thing-x thing) (thing-y thing) thing)) (define (thing-move! world thing x y) (world-set! world (thing-x thing) (thing-y thing) #f) (thing-x-set! thing x) (thing-y-set! thing y) (world-set! world x y thing)) (define (thing-move-relative! world thing dx dy) (let* ((new-x (+ (thing-x thing) dx)) (new-y (+ (thing-y thing) dy)) (other (world-get world new-x new-y))) (cond ((and (thing? other) (> (thing-z other) (thing-z thing)))) (else (cond ((< new-x 1) (set! new-x 1)) ((> new-x (- (world-width world) 1)) (set! new-x (- (world-width world) 1)))) (cond ((< new-y 1) (set! new-y 1)) ((> new-y (- (world-height world) 1)) (set! new-y (- (world-height world) 1)))) (thing-move! world thing new-x new-y))))) (define (thing-up! thing) (thing-move-relative! (world) thing 0 -1)) (define (thing-down! thing) (thing-move-relative! (world) thing 0 1)) (define (thing-right! thing) (thing-move-relative! (world) thing 1 0)) (define (thing-left! thing) (thing-move-relative! (world) thing -1 0)) (define (wall x y) (make-thing 'wall "#" x y 100 '())) (define me (make-parameter (make-thing 'me "@" 40 10 2 '()))) (define (up!) (thing-up! (me))) (define (down!) (thing-down! (me))) (define (left!) (thing-left! (me))) (define (right!) (thing-right! (me))) ;;; random (define random-int (case-lambda (() (pseudo-random-integer 100)) ((max) (pseudo-random-integer max)) ((min max) (let ((max (if (> max min) max min)) (min (if (< min max) min max))) (+ min (pseudo-random-integer (- max min))))))) (define (random-x world) (random-int 1 (- (world-width world) 1))) (define (random-y world) (random-int 1 (- (world-height world) 1))) ;;; snake (define-record-type (make-snake length head tail) snake? (length snake-length snake-length-set!) (head snake-head snake-head-set!) (tail snake-tail snake-tail-set!)) (define snake (make-parameter (make-snake 5 (make-thing 'snake-head "<" 60 20 2 '((dir . (-1 0)))) (list (make-thing 'snake-tail "-" 61 20 2 '()) #;(make-thing 'snake-tail "-" 62 20 2 '()) #;(make-thing 'snake-tail "-" 63 20 2 '()) #;(make-thing 'snake-tail "-" 64 20 2 '()) #;(make-thing 'snake-tail "-" 65 20 2 '()))))) (define (snake-init snake) #f) (define (snake-x snake) (thing-x (snake-head snake))) (define (snake-y snake) (thing-y (snake-head snake))) (define (snake-z snake) (thing-z (snake-head snake))) (define (snake-direction snake) (thing-attr-get (snake-head snake) 'dir)) (define (take* xs n) ;;; Like `take' from SRFI 1, but doesn't choke on too-short lists. (unless (and (integer? n) (> n 0)) (error "Must take non-negative integer" n)) (let loop ((xs xs) (n n) (acc '())) (if (or (null? xs) (zero? n)) (reverse acc) (loop (cdr xs) (- n 1) (cons (car xs) acc))))) (define (snake-step snake world) (let ((new-tail (make-thing 'snake-tail (if (zero? (car (snake-direction snake))) "|" "-") (snake-x snake) (snake-y snake) (snake-z snake) '()))) (apply thing-move-relative! world (snake-head snake) (snake-direction snake)) (for-each (lambda (t) (world-set! world (thing-x t) (thing-y t) #f)) (snake-tail snake)) (snake-tail-set! snake (take* (cons new-tail (snake-tail snake)) (snake-length snake))) (for-each (lambda (t) (thing-place! world t)) (snake-tail snake))))