#!/usr/bin/env racket #lang racket ;;; Пример использования библиотеки 2htdp/universe для создания игры. ;;; Ракета управляется клавишами «вверх» (ускорение), «влево», «вправо» (поворот). ;;; ;;; М. А. Крышень / Mikhail Kryshen , 2018, 2019. ;;; ;;; Автор передает данную программу в общественное достояние, см. ;;; https://creativecommons.org/publicdomain/zero/1.0/ (require 2htdp/universe 2htdp/image) ;; Размеры игрового пространства. (define width 500) (define height 500) ;; Координаты цели и требуемое расстояние до нее. (define target-x 100) (define target-y 100) (define target-distance 10) ;; Модуль ускорения при нажатии на кнопку "вперед". (define key-accel 2.0) ;; Модуль угловой скорости при нажатии на кнопки поворота. (define key-ω (/ pi 50)) ;; Множитель для гашения скорости. (define damp 0.9) ;; Состояние "мира". (struct world (x y ; координаты vx vy ; вектор скорости accel ; модуль ускорения α ; угол поворота относительно Ox ω ; угловая скорость particles) ; список частиц #:transparent) ;; Исходное состояние (ракета в центре в состоянии покоя). (define world0 (world (/ width 2) (/ height 2) 0 0 0 0 0 '())) ;; Частица. (struct particle (x y ; координаты vx vy ; скорость ttl) ; время жизни #:transparent) ;; Обработка нажатий и отпусканий кнопок. ;; press? — истина, если клавиша нажата, ложь — отпущена. ;; w — состояние игрового мира, ;; k — клавиша. (define ((control press?) w k) (let ((new-a (if press? key-accel 0.0)) (new-ω (if press? key-ω 0.0))) (cond ((key=? k "up") (struct-copy world w (accel new-a))) ((key=? k "left") (struct-copy world w (ω (- new-ω)))) ((key=? k "right") (struct-copy world w (ω new-ω))) (else w)))) ;; Создает одну частицу. (define (emit-particle w) ;; Захват нужных полей структуры world. (match-define (struct* world ((x x) (y y) (vx vx) (vy vy) (α α))) w) ;; Вылетает в направлении противоположном направлению ракеты со ;; случайной скоростью и случайным отклонением. (let* ((pα (+ α (* 0.6 (- (random) 0.5)))) ; угол направления (pv (+ 5 (* (random) 10))) ; значение скорости (pvx (- (* (cos pα) pv))) ; вектор скорости (pvy (- (* (sin pα) pv)))) ;; Координаты и скорость были вычислены относительно ракеты, ;; получаем их относительно игрового пространства. (particle (+ x pvx) (+ y pvy) (+ pvx vx) (+ pvy vy) ;; Случайное время жизни. (random 3 12)))) ;; Возвращает список новых частиц. (define (emit-particles w) (build-list 15 (λ _ (emit-particle w)))) ;; Обновляет состояние частицы p. (define (update-particle p) (match-define (particle x y vx vy ttl) p) (particle (+ x vx) (+ y vy) vx vy (sub1 ttl))) ;; Обновляет состояния частиц в списке particles и удаляет частицы с ;; истекшим временем жизни. (define (update-particles particles) (map update-particle (filter (λ (p) (positive? (particle-ttl p))) particles))) ;; Возвращает новое состояние мира по прошествии единицы времени. (define (tick w) (match-define (world x y vx vy accel α ω particles) w) (let* (;; Поворот (применяем угловую скорость). (α (+ α ω)) ;; Вектор ускорения. (ax (* (cos α) accel)) (ay (* (sin α) accel)) ;; Вектор скорости. (vx (+ vx ax)) (vy (+ vy ay)) ;; Перемещаем на значение скорости и переносим на ;; противоположную сторону при выходе за границы игрового ;; пространства. (x (modulo (round (+ x vx)) width)) (y (modulo (round (+ y vy)) height)) ;; Обновляем частицы. (particles (update-particles particles)) ;; Добавляем новые частицы, если двигатель работает. (particles (if (zero? accel) particles (append (emit-particles w) particles)))) (world x y (* vx damp) (* vy damp) accel α ω particles))) ;; Изображение ракеты. Помещаем треугольник на прозрачный круг, ;; чтобы зафиксировать центр вращения. (define rocket-pic (let* ((the-rocket (isosceles-triangle 50 30 'solid 'black)) (w (image-width the-rocket)) (h (image-height the-rocket)) (d (max w h))) (place-image/align the-rocket (- d (/ w 2)) (- d (* 2/3 h)) 'left 'top (circle d 'solid (color 255 255 255 0))))) ;; Изображение цели. (define target-pic (circle 30 'outline 'red)) ;; Изображение частицы. (define particle-pic (circle 2 'solid 'red)) ;; Добавляет частицу p к картинке scene. (define (place-particle p scene) (place-image particle-pic (particle-x p) (particle-y p) scene)) (define (draw w) (let* ((s (rectangle width height 'solid 'white)) (s (place-image target-pic target-x target-y s)) (s (foldl place-particle s (world-particles w))) (s (place-image (rotate (radians->degrees (- (* pi 3/2) (world-α w))) rocket-pic) (world-x w) (world-y w) s))) s)) (define (done? w) (< (+ (sqr (- target-x (world-x w))) (sqr (- target-y (world-y w)))) (sqr target-distance))) (define (last-picture w) (text "Done!" 24 'black)) (define (start) (big-bang world0 (on-tick tick) (on-key (control #t)) (on-release (control #f)) (to-draw draw) (stop-when done? last-picture))) ;; Запускать игру, если файл запустили как скрипт или программу. (module+ main (start)) ;; Команда для запуска: ;; $ racket game.rkt ;; ;; Или сделать исполняемым и запустить как скрипт: ;; $ chmod +x game.rkt ;; $ ./game.rkt ;; (такой способ запуска обеспечивается первой строчкой с #!) ;; ;; Или собрать в бинарный исполняемый файл: ;; $ raco exe game.rkt ;; $ ./game ;; ;; Или загрузить в REPL и выполнить (start). ;; Чтобы продолжать работать в REPL пока игра запущена, можно ;; использовать (thread start).