Skip to content

Commit

Permalink
initial commit with full tutorial code
Browse files Browse the repository at this point in the history
  • Loading branch information
podiki committed Feb 20, 2017
1 parent 0729a13 commit bf6aa95
Show file tree
Hide file tree
Showing 18 changed files with 8,116 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Backup files
*~
Binary file added arial10x10.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added menu_background.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 19 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Complete roguelike tutorial with libtcod and cl-tcod

## Introduction

This is a port of the Python code from the ["Complete Roguelike Tutorial, using python+libtcod"](http://www.roguebasin.com/index.php?title=Complete_Roguelike_Tutorial,_using_python%2Blibtcod) into Common Lisp using [cl-tcod](https://bitbucket.org/eeeickythump/cl-tcod). It uses [libtcod](https://bitbucket.org/libtcod/libtcod) (a.k.a. "The Doryen Library") through the Common Foreign Function Interface, [CFFI](https://common-lisp.net/project/cffi/). Commentary on writing this tutorial in lisp will be found on my [blog](https://9bladed.com).

The code is basically a straight "translation" from Python to Common Lisp, and is not necessarily "lispy" in it's current form. This was done on purpose to make it easy to refer to the original tutorial, and to reduce the chance of bugs in making sure this works as a proof of concept of a roguelike in lisp.

Everything works exactly the same in terms of gameplay and presentation, as near as I can tell. The only exception is that currently cannot restore savegames where a monster was confused (via a "scroll of confusion"). This is due to the use of a recursive anonymous function (lambda). There are probably several ways around this, but for the purposes of this tutorial translation this is a minor issue and will be left as is for now.

## Technical Details

cl-tcod is in the process of being updated to fix some bugs with the latest version of libtcod. Details here and on the blog posts will discuss the changes necessary if an updated version of cl-tcod is not on the official repository or mirrored on GitHub.

This code has only been tested with the current versions of libtcod (1.6.2), SBCL (1.3.12), and SDL (2.0.5) on x86_64 Arch Linux.

## Status

All of the code works (other than the saving caveat mentioned above), but is missing some of the original Python comments and lisp specific comments. The code is not necessarily pretty, well organized, or in a final state. Updates will be made as the blog posts go up for each part. It is possible in the future to produce a "lisp first" approach to the tutorial, but that is not planned yet.
Binary file added terminal16x16_gs_ro.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
45 changes: 45 additions & 0 deletions tutorial-part01.lisp
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
;;;; tutorial-part01.lisp
;;;; "Graphics"
;;;; February 2017
;;;;
;;;; Commentary to be posted on 9bladed.com
;;;;
;;;; Original python+libtcod tutorial, part 1:
;;;; http://www.roguebasin.com/index.php?title=Complete_Roguelike_Tutorial,_using_python%2Blibtcod,_part_1

(ql:quickload :tcod)

(defparameter *screen-width* 80)
(defparameter *screen-height* 50)
(defparameter *limit-fps* 20)

(tcod:console-set-custom-font "arial10x10.png" '(:FONT-TYPE-GREYSCALE :FONT-LAYOUT-TCOD))

(tcod:console-init-root *screen-width* *screen-height* :title "lisp/libtcod tutorial" :fullscreen? nil)

(tcod:sys-set-fps *limit-fps*)

(defvar *playerx* (/ *screen-width* 2))
(defvar *playery* (/ *screen-height* 2))

(defun handle-keys ()
(let ((events (tcod:sys-get-events)))
(loop for event in events
when (eql (car event) :event-key-press)
do (cond ((and (eql (tcod:key-vk (cdr event)) :ENTER) (tcod:key-lalt (cdr event)))
(tcod:console-set-fullscreen (not (tcod:console-is-fullscreen?))))
((eql (tcod:key-c (cdr event)) #\f)
(tcod:console-set-fullscreen (not (tcod:console-is-fullscreen?))))
((eql (tcod:key-vk (cdr event)) :UP) (decf *playery*))
((eql (tcod:key-vk (cdr event)) :DOWN) (incf *playery*))
((eql (tcod:key-vk (cdr event)) :LEFT) (decf *playerx*))
((eql (tcod:key-vk (cdr event)) :RIGHT) (incf *playerx*))
((eql (tcod:key-vk (cdr event)) :ESCAPE) (return-from handle-keys :exit))))))

(do ()
((tcod:console-is-window-closed?))
(tcod:console-set-default-foreground tcod:*root* (tcod:color :white))
(tcod:console-put-char tcod:*root* *playerx* *playery* (char-code #\@) :NONE)
(tcod:console-flush)
(tcod:console-put-char tcod:*root* *playerx* *playery* (char-code #\SPACE) :NONE)
(handle-keys))
151 changes: 151 additions & 0 deletions tutorial-part02.lisp
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
;;;; tutorial-part02.lisp
;;;; "The object and the map"
;;;; February 2017
;;;;
;;;; Commentary to be posted on 9bladed.com
;;;;
;;;; Original python+libtcod tutorial, part 2:
;;;; http://www.roguebasin.com/index.php?title=Complete_Roguelike_Tutorial,_using_python%2Blibtcod,_part_2

(ql:quickload :tcod)

(defparameter *screen-width* 80)
(defparameter *screen-height* 50)
(defparameter *limit-fps* 20)

(defparameter *map-width* 80)
(defparameter *map-height* 45)

(tcod:make-color :dark-wall 0 0 100)
(tcod:make-color :dark-ground 50 50 150)

(tcod:console-set-custom-font "arial10x10.png" '(:FONT-TYPE-GREYSCALE :FONT-LAYOUT-TCOD))

(tcod:console-init-root *screen-width* *screen-height*
:title "lisp/libtcod tutorial" :fullscreen? nil :renderer :renderer-sdl)
(defparameter *con* (tcod:console-new *screen-width* *screen-height*))

(tcod:sys-set-fps *limit-fps*)

(defgeneric move (obj dx dy)
(:documentation "move by the given amount"))

(defgeneric draw (obj)
(:documentation "set the color and then draw the character that represents this object at its position"))

(defgeneric clear (obj)
(:documentation "erase the character that represents this object"))

(defclass tile ()
((blocked
:initarg :blocked
:initform (error "Must specify blocked")
:accessor blocked)
(block-sight
:initarg :block-sight
:initform nil
:accessor block-sight))
(:documentation "a tile of the map and its properties"))

(defmethod initialize-instance :after ((tt tile) &key)
"by default, if a tile is blocked, it also blocks sight"
(if (not (block-sight tt))
(setf (block-sight tt) (blocked tt))))

;; (defmethod draw ((tt tile) &key x y)
;; (if (block-sight tt)
;; (tcod:console-set-default-background *con* x y :dark-wall :set)
;; (tcod:console-set-default-background *con* x y :dark-ground :set)))

(defvar *map*)
(defun make-map ()
(setf *map* (make-array (list *map-height* *map-width*)))
(dotimes (i *map-height*)
(dotimes (j *map-width*)
(setf (aref *map* i j) (make-instance 'tile :blocked nil))))

(setf (blocked (aref *map* 22 30)) t)
(setf (block-sight (aref *map* 22 30)) t)
(setf (blocked (aref *map* 22 50)) t)
(setf (block-sight (aref *map* 22 50)) t))

(defclass object ()
((x
:initarg :x
:initform (error "Must have a x value")
:accessor x)
(y
:initarg :y
:initform (error "Must have a y value")
:accessor y)
(cha
:initarg :cha
:initform (error "Must have a character")
:accessor cha)
(color
:initarg :color
:initform (error "Must have a color")
:accessor color))
(:documentation "this is a generic object: the player, a monster, an item, the stairs...
it's always represented by a character on the screen."))

(defmethod move ((obj object) dx dy)
(if (not (blocked (aref *map* (+ (y obj) dy) (+ (x obj) dx))))
(progn
(incf (x obj) dx)
(incf (y obj) dy))))

(defmethod draw ((obj object))
(tcod:console-set-default-foreground *con* (tcod:color (color obj)))
(tcod:console-put-char *con* (x obj) (y obj) (char-code (cha obj)) :NONE))

(defmethod clear ((obj object))
(tcod:console-put-char *con* (x obj) (y obj) (char-code #\SPACE) :NONE))

(defun handle-keys ()
(let ((events (tcod:sys-get-events)))
(loop for event in events
when (eql (car event) :event-key-press)
do (pprint (cdr event))
(cond ((or (eql (tcod:key-vk (cdr event)) :ENTER) (tcod:key-lctrl (cdr event)))
(print (tcod:key-lctrl (cdr event)))
(tcod:console-set-fullscreen (not (tcod:console-is-fullscreen?))))
((eql (tcod:key-c (cdr event)) #\f)
(pprint (tcod:key-c (cdr event))))
((eql (tcod:key-vk (cdr event)) :UP) (move *player* 0 -1))
((eql (tcod:key-vk (cdr event)) :DOWN) (move *player* 0 1))
((eql (tcod:key-vk (cdr event)) :LEFT) (move *player* -1 0))
((eql (tcod:key-vk (cdr event)) :RIGHT) (move *player* 1 0))
((eql (tcod:key-vk (cdr event)) :ESCAPE) (return-from handle-keys :exit))))))

(defparameter *player* (make-instance 'object
:x (/ *screen-width* 2) :y (/ *screen-height* 2)
:cha #\@ :color :white))
(defparameter *npc* (make-instance 'object
:x (- (/ *screen-width* 2) 5) :y (/ *screen-height* 2)
:cha #\@ :color :yellow))
(defparameter *objects* (list *player* *npc*))

;; generate map (at this point it's not drawn to the screen)
(make-map)

(defun render-all ()
;; go through all the tiles, and set their background color
(dotimes (i *map-height*)
(dotimes (j *map-width*)
(if (block-sight (aref *map* i j))
(tcod:console-set-char-background *con* j i (tcod:color :dark-wall) :set)
(tcod:console-set-char-background *con* j i (tcod:color :dark-ground) :set))))

;; draw all objects in the list
(mapcar #'draw *objects*)

;; blit the contents of con to the root console
(tcod:console-blit *con* 0 0 *screen-width* *screen-height* tcod:*root* 0 0 1.0 1.0))

(do ()
((tcod:console-is-window-closed?))
(render-all)
(tcod:console-flush)
(mapcar #'clear *objects*)
(handle-keys))
Loading

0 comments on commit bf6aa95

Please sign in to comment.