sokoban.scm 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477
  1. (define MAX-MENU-HEIGHT 4)
  2. (define MAX-MENU-WIDTH 36)
  3. (define (new-menu callback undo-callback item-list)
  4. (make-menu
  5. (list->vector item-list)
  6. 0
  7. 0
  8. callback
  9. undo-callback))
  10. (define (menu-select! menu)
  11. (define menu-item (vector-ref (menu-items menu) (menu-cursor menu)))
  12. (when (menu-callback menu)
  13. ((menu-callback menu) (cdr menu-item))))
  14. (define (menu-close! menu)
  15. (when (menu-undo-callback menu)
  16. ((menu-undo-callback menu))))
  17. (define (menu-length menu)
  18. (vector-length (menu-items menu)))
  19. (define (menu-bottom-item menu)
  20. (+ MAX-MENU-HEIGHT (menu-top-item menu) -1))
  21. (define (menu-bottom-item-set! menu new-val)
  22. (menu-top-item-set! menu (- new-val MAX-MENU-HEIGHT -1)))
  23. (define (menu-scroll! menu direction)
  24. (unless (memq direction '(up down))
  25. (error "menu-scroll!" "Direction not recognized" direction))
  26. (let ((new-cursor-val (+ (if (eq? direction 'up) -1 1)
  27. (menu-cursor menu))))
  28. (unless (or (< new-cursor-val 0)
  29. (>= new-cursor-val (menu-length menu)))
  30. (menu-cursor-set! menu new-cursor-val)
  31. (cond
  32. ((< new-cursor-val (menu-top-item menu))
  33. (menu-top-item-set! menu new-cursor-val))
  34. ((> new-cursor-val (menu-bottom-item menu))
  35. (menu-bottom-item-set! menu new-cursor-val))))))
  36. (define (render-menu menu)
  37. (let loop ((result '())
  38. (i (menu-top-item menu)))
  39. (if (and (< i (menu-length menu))
  40. (<= i (menu-bottom-item menu)))
  41. (loop (cons (string-append (if (= i (menu-cursor menu))
  42. "> "
  43. " ")
  44. (string-take
  45. (string-pad-right (car (vector-ref (menu-items menu) i))
  46. MAX-MENU-WIDTH)
  47. MAX-MENU-WIDTH))
  48. result)
  49. (+ i 1))
  50. (string-join (reverse result) "\n"))))
  51. (define (kill-game! global-state)
  52. ((global-event-handler global-state) 'kill))
  53. (define (play-victory-sound! global-state)
  54. ((global-event-handler global-state) 'victory-sound))
  55. (define (play-level-music! global-state)
  56. ((global-event-handler global-state) 'level-music))
  57. (define (open-level! global-state game-selected level-number)
  58. (play-level-music! global-state)
  59. (global-level-set! global-state (game-file-ref game-selected level-number))
  60. (global-menu-set! global-state #f))
  61. (define (restart-level! global-state)
  62. (define g (level-game-file (global-level global-state)))
  63. (define n (level-number (global-level global-state)))
  64. (open-level! global-state g n))
  65. (define (level-up! global-state)
  66. (define g (level-game-file (global-level global-state)))
  67. (define n (level-number (global-level global-state)))
  68. (open-level! global-state g (+ n 1)))
  69. (define (open-pause-menu! global-state)
  70. (define parent-menu (global-menu global-state))
  71. (define (on-enter command)
  72. (case command
  73. ((restart)
  74. (restart-level! global-state))
  75. (else
  76. (open-main-menu! global-state))))
  77. (define (on-exit)
  78. (global-menu-set! global-state parent-menu))
  79. (global-menu-set! global-state
  80. (new-menu on-enter on-exit '(("Restart level" . restart)
  81. ("Exit to main menu" . main)))))
  82. (define (open-victory-menu! global-state)
  83. (define (on-enter command)
  84. (level-up! global-state))
  85. (global-menu-set! global-state
  86. (new-menu on-enter #f '(("Next level" . next)))))
  87. (define (open-main-menu! global-state)
  88. (define (on-enter command)
  89. (case command
  90. ((new)
  91. (open-pick-game-menu! global-state))
  92. (else
  93. (kill-game! global-state))))
  94. (global-level-set! global-state #f)
  95. (global-menu-set! global-state
  96. (new-menu on-enter #f '(("Pick game" . new)
  97. ("Exit program" . exit)))))
  98. (define (open-pick-game-menu! global-state)
  99. (define parent-menu (global-menu global-state))
  100. (define (on-enter command)
  101. (open-pick-level-menu! global-state command this-menu))
  102. (define (on-exit)
  103. (global-menu-set! global-state parent-menu))
  104. (define this-menu (new-menu on-enter on-exit xsb-level-menu-list))
  105. (global-menu-set! global-state this-menu))
  106. (define (open-pick-level-menu! global-state game-selection parent-menu)
  107. (define (make-menu-list-from-progress p)
  108. (define a (assoc game-selection p))
  109. (if a
  110. (let loop ((i 1)
  111. (out '()))
  112. (if (<= i (+ 1 (cdr a)))
  113. (loop (+ i 1)
  114. (cons (cons (string-append "Level " (number->string i))
  115. i)
  116. out))
  117. (reverse out)))
  118. '(("Level 1" . 1))))
  119. (define (on-enter command)
  120. (let ((levels (file-id->game-file game-selection)))
  121. (open-level! global-state levels (- command 1))))
  122. (define (on-exit)
  123. (global-menu-set! global-state parent-menu))
  124. (define menu-list (make-menu-list-from-progress (global-progress global-state)))
  125. (global-menu-set! global-state (new-menu on-enter on-exit menu-list)))
  126. (define (increment-progress! global-state)
  127. (define file-id (level-game-file (global-level global-state)))
  128. (define level (level-number (global-level global-state)))
  129. (define (assoc-set l k v)
  130. (let loop ((in l)
  131. (out '()))
  132. (if (null? in)
  133. (cons (cons k v) l)
  134. (if (equal? (car (car in)) k)
  135. (append (reverse out)
  136. (list (cons k v))
  137. (cdr in))
  138. (loop (cdr in)
  139. (cons (car in) out))))))
  140. (define p (assoc file-id (global-progress global-state)))
  141. (when (or (not p)
  142. (> level (cdr p)))
  143. (let ((new-progress (assoc-set (global-progress global-state)
  144. file-id
  145. level)))
  146. (global-progress-set! global-state new-progress))))
  147. (define (move-player-and-check-complete! global-state direction)
  148. (define current-level-state (global-level global-state))
  149. (move-player! current-level-state direction)
  150. (when (level-complete? current-level-state)
  151. (play-victory-sound! global-state)
  152. (increment-progress! global-state)
  153. (open-victory-menu! global-state)))
  154. (define (try-undo-last-move! global-state)
  155. (define current-level-state (global-level global-state))
  156. (when (level-last-move current-level-state)
  157. (undo-move-player! current-level-state)))
  158. (define (string->level-data s)
  159. (vector-map string->vector (list->vector (string-split s "\n"))))
  160. (define (level->string l)
  161. (string-join (vector->list (vector-map vector->string (level-data l))) "\n"))
  162. (define (char->tile-type c)
  163. (cond
  164. ((char=? c #\space) 'floor)
  165. ((char=? c #\.) 'goal-square)
  166. ((char=? c #\*) 'box-on-goal-square)
  167. ((char=? c #\$) 'box)
  168. ((char=? c #\+) 'player-on-goal-square)
  169. ((char=? c #\@) 'player)
  170. ((char=? c #\#) 'wall)
  171. (else (error "tile char not recognized" c))))
  172. (define (level-last-move level)
  173. (define result (level-move-history level))
  174. (if (null? result) #f (car result)))
  175. (define (render-level-status level)
  176. (define game-name (game-name-lookup (game-file-id (level-game-file level))))
  177. (define level-no (number->string (level-number level)))
  178. (define move-ct (number->string (length (level-move-history level))))
  179. (string-append
  180. (string-pad-left game-name 16)
  181. "-"
  182. (string-pad-left level-no 3 #\0)
  183. "\n"
  184. (string-pad-left move-ct 4 #\0)))
  185. (define (level-ref l i j)
  186. (define data (level-data l))
  187. (make-tile
  188. i
  189. j
  190. (char->tile-type
  191. (if (and (>= i 0)
  192. (< i (vector-length data)))
  193. (let ((v (vector-ref data i)))
  194. (if (and (>= j 0)
  195. (< j (vector-length v)))
  196. (vector-ref v j)
  197. #\space))
  198. #\space))))
  199. (define (level-set! l i j tile-type)
  200. (define v (cdr (assq tile-type '((floor . #\space)
  201. (goal-square . #\.)
  202. (box-on-goal-square . #\*)
  203. (box . #\$)
  204. (player-on-goal-square . #\+)
  205. (player . #\@)
  206. (wall . #\#)))))
  207. (vector-set! (vector-ref (level-data l) i) j v))
  208. (define (level-add-history! l history-item)
  209. (level-move-history-set! l (cons history-item (level-move-history l))))
  210. (define (level-remove-history! l)
  211. (level-move-history-set! l (cdr (level-move-history l))))
  212. (define (tile-player? t)
  213. (let ((tt (tile-type t)))
  214. (or (eq? tt 'player)
  215. (eq? tt 'player-on-goal-square))))
  216. (define (find-next-tile level tile direction)
  217. (define i (tile-row tile))
  218. (define j (tile-col tile))
  219. (cond
  220. ((eq? direction 'left)
  221. (level-ref level i (- j 1)))
  222. ((eq? direction 'right)
  223. (level-ref level i (+ j 1)))
  224. ((eq? direction 'up)
  225. (level-ref level (- i 1) j))
  226. ((eq? direction 'down)
  227. (level-ref level (+ i 1) j))
  228. (else
  229. (error "find-next-tile" "Direction not recognized" direction))))
  230. (define (player-position l)
  231. (define rows (level-rows l))
  232. (define cols (level-cols l))
  233. (let loop ((i 0)
  234. (j 0))
  235. (when (>= i rows)
  236. (error "player-position" "Player position not found"))
  237. (if (< j cols)
  238. (if (tile-player? (level-ref l i j))
  239. (list i j)
  240. (loop i (+ j 1)))
  241. (loop (+ i 1) 0))))
  242. (define (level-complete? l)
  243. (define rows (level-rows l))
  244. (define cols (level-cols l))
  245. (let loop ((i 0)
  246. (j 0))
  247. (or (>= i rows)
  248. (if (< j cols)
  249. (and (not (memq (tile-type (level-ref l i j))
  250. '(goal-square player-on-goal-square)))
  251. (loop i (+ j 1)))
  252. (loop (+ i 1) 0)))))
  253. (define (find-player l)
  254. (define results (player-position l))
  255. (define i (list-ref results 0))
  256. (define j (list-ref results 1))
  257. (level-ref l i j))
  258. (define (tile->list-repr t)
  259. (list (tile-row t) (tile-col t) (tile-type t)))
  260. (define (opposite-direction sym)
  261. (cond
  262. ((eq? sym 'up) 'down)
  263. ((eq? sym 'down) 'up)
  264. ((eq? sym 'left) 'right)
  265. (else 'left)))
  266. (define (undo-move-player! level)
  267. (define player (find-player level))
  268. (define last-move (level-last-move level))
  269. (define previous-direction
  270. (cond
  271. ((memv last-move '(#\u #\U)) 'up)
  272. ((memv last-move '(#\d #\D)) 'down)
  273. ((memv last-move '(#\l #\L)) 'left)
  274. ((memv last-move '(#\r #\R)) 'right)))
  275. (define direction (opposite-direction previous-direction))
  276. (define did-push? (memv last-move '(#\U #\D #\L #\R)))
  277. (define next (find-next-tile level player direction))
  278. (define previous (find-next-tile level player previous-direction))
  279. (define player-type (tile-type player))
  280. (define next-type (tile-type next))
  281. (define previous-type (tile-type previous))
  282. (level-remove-history! level)
  283. (cond
  284. (did-push?
  285. (level-set! level (tile-row previous)
  286. (tile-col previous)
  287. (if (eq? previous-type 'box)
  288. 'floor
  289. 'goal-square))
  290. (level-set! level (tile-row player)
  291. (tile-col player)
  292. (if (eq? player-type 'player)
  293. 'box
  294. 'box-on-goal-square))
  295. (level-set! level (tile-row next)
  296. (tile-col next)
  297. (if (eq? next-type 'floor)
  298. 'player
  299. 'player-on-goal-square)))
  300. (else
  301. (level-set! level (tile-row next)
  302. (tile-col next)
  303. (if (eq? next-type 'floor)
  304. 'player
  305. 'player-on-goal-square))
  306. (level-set! level (tile-row player)
  307. (tile-col player)
  308. (if (eq? player-type 'player)
  309. 'floor
  310. 'goal-square)))))
  311. (define (move-player! level direction)
  312. (define player (find-player level))
  313. (define next (find-next-tile level player direction))
  314. (define next-next (find-next-tile level next direction))
  315. (let ((player-type (tile-type player))
  316. (next-type (tile-type next))
  317. (next-next-type (tile-type next-next)))
  318. (cond
  319. ((memq next-type '(floor goal-square))
  320. (level-add-history! level
  321. (cond
  322. ((eq? direction 'up) #\u)
  323. ((eq? direction 'down) #\d)
  324. ((eq? direction 'left) #\l)
  325. ((eq? direction 'right) #\r)))
  326. (level-set! level (tile-row next)
  327. (tile-col next)
  328. (if (eq? next-type 'floor)
  329. 'player
  330. 'player-on-goal-square))
  331. (level-set! level (tile-row player)
  332. (tile-col player)
  333. (if (eq? player-type 'player)
  334. 'floor
  335. 'goal-square)))
  336. ((and (memq next-type '(box box-on-goal-square))
  337. (memq next-next-type '(floor goal-square)))
  338. (level-add-history! level
  339. (cond
  340. ((eq? direction 'up) #\U)
  341. ((eq? direction 'down) #\D)
  342. ((eq? direction 'left) #\L)
  343. ((eq? direction 'right) #\R)))
  344. (level-set! level (tile-row next-next)
  345. (tile-col next-next)
  346. (if (eq? next-next-type 'floor)
  347. 'box
  348. 'box-on-goal-square))
  349. (level-set! level (tile-row next)
  350. (tile-col next)
  351. (if (eq? next-type 'box)
  352. 'player
  353. 'player-on-goal-square))
  354. (level-set! level (tile-row player)
  355. (tile-col player)
  356. (if (eq? player-type 'player)
  357. 'floor
  358. 'goal-square))))))
  359. (define (read-level-list-data port)
  360. (let loop ((in-tiles? #f)
  361. (in-comment? #f)
  362. (current-level '())
  363. (level-list '()))
  364. (define c (read-char port))
  365. (cond
  366. ((eof-object? c)
  367. ;; eof
  368. (reverse (map (lambda (l)
  369. (string->level-data
  370. (string-join
  371. (reverse
  372. (map list->string (map reverse l)))
  373. "\n")))
  374. level-list)))
  375. ((char=? c #\newline)
  376. ;; new line
  377. (if in-tiles?
  378. (loop #f
  379. #f
  380. (cons '() current-level)
  381. level-list)
  382. (loop #f
  383. #f
  384. '()
  385. (if (null? current-level)
  386. level-list
  387. (cons current-level level-list)))))
  388. ((memv c '(#\space #\. #\* #\$ #\+ #\@ #\#))
  389. ;; tile
  390. (if in-comment?
  391. (loop in-tiles?
  392. #t
  393. current-level
  394. level-list)
  395. (loop #t
  396. #f
  397. (if (null? current-level)
  398. (list (list c))
  399. (cons (cons c (car current-level))
  400. (cdr current-level)))
  401. level-list)))
  402. ((char=? c #\return)
  403. ;; ignore returns
  404. (loop in-tiles?
  405. in-comment?
  406. current-level
  407. level-list))
  408. (else
  409. ;; comment
  410. (loop in-tiles?
  411. #t
  412. current-level
  413. level-list)))))
  414. (define (game-file-ref game-selected level-number)
  415. (define data (list-ref (game-file-level-list game-selected) level-number))
  416. (define rows (vector-length data))
  417. (define cols (apply max (map vector-length (vector->list data))))
  418. (make-level
  419. rows
  420. cols
  421. (vector-map vector-copy data)
  422. '()
  423. game-selected
  424. level-number))
  425. (define (game-file-length game-selected)
  426. (length (game-file-level-list game-selected)))
  427. (define (file-id->game-file file-id)
  428. (define f (fake-open-input-file file-id))
  429. (let ((result (read-level-list-data f)))
  430. (close-input-port f)
  431. (make-game-file result file-id)))