I'd really like t know what you were reading, that would give me some idea what you might already know.
And given by your behavior up to this point I'd wager everything I could show you would either be not impressing enough or too complex for you to understand in a timespan that you'd consider worthwhile. I mean, how much time have you spent with Common Lisp before dismissing it? Half an hour? Cannot be more than that if you haven't even found out about macros yet.
But
just for you, here's a demonstration of some stuff that is possible in Lisps. If you don't understand what's going on here I'd advise searching out some actual tutorials on Lisp, like, I don't know,
Practical Common Lisp or
this sample chapter from Land of Lisp or if you want to dive into the more unusual stuff straight away because properly understanding a language is for babies, you can read
On Lisp, which is all about macros. I'd also recommend buying "Let Over Lambda", but I assume making such an investment isn't really useful for someone with the patience of a cat.
Let's start with something trivial.
When looking something up in a collection oftentimes you get either back the object you were looking for, or a "NIL" value. This normally looks like this in code:
(let ((targeted-monster (find-occupant square)))
(if targeted-monster
(hurt targeted-monster)
(message "There's no target at this point!")))
It's not really that much, but after a while it gets tedious to do that all the time. So I'll write a macro to do it for me:
(defmacro [a]if (test then &optional else)
`(let ((it ,test))
(if it
,then
,else)))
Now I can write code as above like this:
([a]if (find-occupant square)
(hurt it)
(message "There's no target at this point!"))
That already saves a lot of typing.
But that's trivial. Barely worth talking about.
I don't know whether you know about the difference between eager and lazy evaluation. If you don't, go read up on it.
Most Lisps are eagerly evaluated, especially since Lisps are very decidedly not functional and lazy evaluation tends to behave weirdly when side effects are involved unless special care is taken to avoid these problems.
Still, lazy evaluation can be tremendously useful in parts of a program not relying on side effects. Although Common Lisp does not support lazy evaluation out of the box, you can implement it. There are many ways to do this and I'm going to show you the
easiest way of doing it, not the most useful one:
(defmacro freeze (&body body)
`(lambda ()
,@body))
(defun force (thunk)
(if (functionp thunk)
(funcall thunk)
thunk))
Those to alone aren't that useful, but more powerful macros can be written atop them to make them more useful:
(defmacro let~ ((&rest bindings)
&body body)
`(thunk
(let (,@(mapcar (lambda (binding)
`(,(first binding)
(force ,(second binding))))
bindings))
,@body)))
and so on. I've got a more involved library for the whole thing
here, which relies on
this. That library also implements algebraic data types and pattern matching on those algebraic data types.
Of course that's still small fries. writing some lazy evaluation library is only a few steps above writing a "Hello, world!", even if tons more useful.
I already know you think little of entity-component systems, but I find them to be incredibly useful. There already are some implementations for them on the net, but because they all relied on a central system loop to access them, which is too restrictive for my tastes and provides features I really don't need, so I rolled my own.
Entities themselves are simple objects, and the can be created like this;
(entity)
They aren't particularly interesting in themselves. The interesting parts are the components. To define a certain type of component, I can do one of the following:
To define a component with an already existing type:
(define-component-type strength (integer 0))
To define a component as a structure type:
(define-component-struct inventory ()
(content (empty-set)
:type set)
(capacity 0
:type (integer 0)))
Components can also depend on other components being present.
And then I can write functions which will only be run on entities in which certain components are present and let them do just nothing on other entities:
(define-entity-method distance ((e1 entity location1)
(e2 entity location2))
(max (d (x location1)
(x location2))
(d (y location 1)
(y location2))))
You want the implementation of these things?
Here's a paste. I don't want to make the corresponding repository public yet.
Here's some snippets of stuff not in this file:
(defmacro [a]if (test then &body else)
`(let ((it ,test))
(if it
,then
(progn
,@else))))
(defmacro [a]when (test &body then)
`([a]if ,test
(progn
,@then)))
(defmacro [av]if (test then &body else)
(let ((g!found? (gensym "found?")))
`(multiple-value-bind (it ,g!found?)
,test
(if ,g!found?
,then
(progn
,@else)))))
(defmacro [av]when (test &body then)
`([av]if ,test
(progn
,@then)))
(defun car-or-x (x)
(if (consp x)
(car x)
x))
(defun symb (&rest objs)
(intern (format nil "~{~A~}"
objs)))
(defun ensure-list (x)
(the list
(if (listp x)
x
(list x))))
(defun plist-rem (indicator/s property-list)
(declare (type (or cons symbol)
indicator/s)
(type list property-list))
(the list
(loop for (prop item)
on property-list by #'cddr
unless (if (consp indicator/s)
(member prop indicator/s
:test #'eq)
(eq prop indicator/s))
nconc
`(,prop ,item))))
(defun copy-hash-table (hash-table)
(declare (type hash-table hash-table))
(the hash-table
(let ((newhash (make-hash-table :test (hash-table-test hash-table)
:size (hash-table-size hash-table)
:rehash-size
(hash-table-rehash-size hash-table)
:rehash-threshold
(hash-table-rehash-threshold
hash-table))))
(declare (type hash-table newhash))
(maphash (lambda (key value)
(setf (gethash key newhash)
value))
hash-table)
newhash)))
None of this is in Common Lisp by default. Every programming paradigm you can come up with is implementable in Common Lisp itself in such a way that you can write it intermingled with the more typical Common Lisp. Damn, there are libraries implementing Prolog in Common Lisp – that is, you can write Common Lisp and Prolog in the same file without any external tools, without the need to write a Prolog interpreter or compiler. Just load the library and you're good to go.
Even if you're not going to use Common Lisp or any other Lisp for writing your roguelike, a Lisp is still your best bet for writing your own programming language. Once again, I point you at "Let Over Lambda" and "On Lisp".
In the end, unless you are willing to understand what programming actually is, it doesn't matter which programming language you are working in, you're just going to suck at it. And if you're going to write a programming language without any effort on your part to even understand what others have already been doing, your programming language is going to suck, because you'll make errors that others already made decades ago only because you have no idea why programming languages are the way they are.
Breaking stupid rules is a worthy endeavor, sure, but before that you must understand the rules you want to break.