Игры на Scheme(Lisp) в среде DrRacket

Habrahabr

В данной статье используется среда разработки DrRacket. Для начала рассмотрим связь конечного автомата и игрового процесса. Объект управления в игре можно представить в виде конечного автомата. Рассмотрим программу, моделирующую светофор. Этот пример был описан в предыдущей статье. Переходом в другое устойчивое состояние является переключение сигнала светофора. Диаграмму состояний можно изобразить в следующем виде. image Для того, чтобы создать светофор, нарисуем окружность в центре пустой сцены.

#lang racket
(require picturing-programs)
(define (DOT s)  (circle 100 "solid" s))

s — это переменная, отвечающая за цвет. Переход в другое состояние можно представить следующей конструкцией
(define (traffic-light-next s)
  (cond
    [(string=? "red" s) "green"]
    [(string=? "green" s) "yellow"]
    [(string=? "yellow" s) "red"]))

Для того, чтобы промоделировать переключение сигнала, используем функцию big-bang.
(big-bang "red"
          [on-tick traffic-light-next 1]
          [to-draw DOT])   
    
Теперь светофор переходит в следующее устойчивое состояние 1 раз в секунду. Управляющие воздействие в играх также могут оказывать платформы, препятствия, враги и т.д. Например, в некоторых играх по ходу движения объект может «перескакивать» на следующий канвас, т.е. приблизившись к границе канваса, объект исчезает и появляется на противоположной границе. Условие перехода («перескакивания») определяется сравнением координат объекта и края канваса.
 [(> (+ x DELTA) WIDTH)  0]

Если условие не выполнено, остаёмся на том же экране.
(cond
      [(> (+ x dx) WIDTH)  0]
       [else (+ x dx)]        )

Движение определяется приращением DELTA к координате x Напишем программу целиком
#lang racket
(require 2htdp/image)
(require 2htdp/universe)
(define WIDTH 100)
(define DELTA 1)
(define BALL (circle 5 "solid" "red"))
(define MT   (empty-scene WIDTH 10))
 (define (main x0)
  (big-bang x0
    [to-draw render]
    [on-tick bounce]))
(define (bounce x)
      (cond
      [(> (+ x DELTA) WIDTH)  0]
       [else (+ x DELTA)]  ))
 (define (render x)
  (place-image BALL   x 5 MT))
(main 50)

На этой странице представлены примеры программ, использующих big-bang. Запустить программы можно в режиме «Начинающий студент», добавив необходимые пакеты. Далее напишем программу, в которой управление объектом осуществляется клавишами «left» и «right». Здесь нам понадобится функция для обработкой клавиш.
#lang racket
(require 2htdp/image)
(require 2htdp/universe)
(define BACKGROUND (empty-scene 100 100))
(define DOT (circle 10 "solid" "red"))
(define (place-dot-at x)
  (place-image DOT x 50 BACKGROUND))
(define (change-func p k) ; обработка клавиш
  (cond
    [(string=? "left" k)
     (- p 5)]
    [(string=? "right" k)
     (+ p 5)]
    [else p]))
( big-bang 50
[to-draw place-dot-at] 
[on-key change-func]   )

Если же нам также необходимо обрабатывать нажатия клавиш «up», «down», то координаты объекта следует хранить в структуре вида
(define-struct posn (x y))
(define INIT-WORLD (make-posn 100 100))

Напишем программу, в которой объект может перемещаться по горизонтали и вертикали.
#lang racket
(require picturing-programs) 
(define WIDTH 200)
(define HEIGHT 200)
(define BACKGROUND     (empty-scene WIDTH HEIGHT))
(define obj1 (circle 10 "solid" "red"))
(define-struct posn (x y))            ;объявляем структуру
(define INIT-WORLD (make-posn 100 100) )
(define (change-current-world-key current-world a-key-event) ;обработка "событий" клавиатуры 
  (cond
    [(key=? a-key-event "up")
     (make-posn (posn-x current-world) (- (posn-y current-world) 5))]
    [(key=? a-key-event "down")
    (make-posn (posn-x current-world) (+ (posn-y current-world) 5))]
    [(key=? a-key-event "left")
     (make-posn (-(posn-x current-world)5) (posn-y current-world) )]
    [(key=? a-key-event "right")
    (make-posn (+(posn-x current-world)5) (posn-y current-world) )]
    [else current-world]))
(define (redraw current-world)
  (place-image obj1
               (posn-x current-world)
               (posn-y current-world)
               BACKGROUND))
(big-bang INIT-WORLD
(on-key change-current-world-key)
(on-draw redraw) )

Да, но обычно в играх присутствует несколько объектов. Напишем программу, в которой присутствует два объекта. Используем две структуры: world и posn.
#lang racket
(require picturing-programs) 
(define WIDTH 300)
(define HEIGHT 300)
(define BACKGROUND     (empty-scene WIDTH HEIGHT))
(define obj1 (circle 10 "solid" "red"))
(define obj2 (circle 10 "solid" "green"))
(define-struct posn (x y))
(define-struct world [obj1 obj2])
; Инициализация параметров в структуре world
(define INIT-WORLD (make-world (make-posn 100 100) (make-posn 200 100)))
(define (draw-game world)
   (place-image 
      obj1
      (posn-x (world-obj1 world))
      (posn-y (world-obj1 world))
      (place-image 
         obj2
         (posn-x (world-obj2 world))
         (posn-y (world-obj2 world))
         BACKGROUND)))
(big-bang   INIT-WORLD
  [to-draw draw-game])

Теперь для того, чтобы управлять одним из объектов, добавим функцию обработки клавиш.
#lang racket
(require picturing-programs) 
(define WIDTH 300)
(define HEIGHT 300)
(define BACKGROUND (empty-scene WIDTH HEIGHT))
(define obj1 (circle 10 "solid" "red"))
(define obj2 (circle 10 "solid" "green"))
(define-struct posn (x y))
(define-struct world [obj1 obj2])
(define INIT-WORLD (make-world (make-posn 50 150) (make-posn 250 150)))
(define (change-current-world-key current-world a-key-event)
  (make-world (change-obj1 (world-obj1 current-world) a-key-event)
              (world-obj2 current-world)))
(define (change-obj1 a-posn a-key-event)
  (cond
    [(key=? a-key-event "up")
     (make-posn (posn-x a-posn) (- (posn-y a-posn) 5))]
    [(key=? a-key-event "down")
     (make-posn (posn-x a-posn) (+ (posn-y a-posn) 5))]
    [(key=? a-key-event "left")
     (make-posn (-(posn-x a-posn) 5) (posn-y a-posn) )]
    [(key=? a-key-event "right")
     (make-posn (+(posn-x a-posn)5) (posn-y a-posn) )]
    [else a-posn]))
(define (draw-game world)
   (place-image 
      obj1
      (posn-x (world-obj1 world))
      (posn-y (world-obj1 world))
     (place-image 
        obj2
        (posn-x (world-obj2 world))
        (posn-y (world-obj2 world))
         BACKGROUND)))
(big-bang   INIT-WORLD
(on-key change-current-world-key)
  [to-draw draw-game] )

Более подробно о создании игр можно прочитать в книге How to Design Worlds.