Thursday, 14 June 2012

VK_VERYLONGKEYNAME (on reflection in Clojure)

So, I'm still writing a game. As it happens with games, I rely on keypresses for the user to tell me what he wants to do. That made me fall back on Java's KeyListener/KeyAdapter facilities.

The basic idea is that I get callbacks with information about the pressed key as argument (WARNING: GROSS OVERSIMPLIFICATION.), among other things the keycode, which is an integer. Now, I'm supposed to dispatch it using constants defined in KeyListener/KeyAdapter/KeyEvent/probably some other places.

Problem?

It's a lot of typing in Clojure. Especially since each constant is called VK_(something). I had large maps looking like that:

{ KeyEvent/VK_UP [some function]
  KeyEvent/VK_DOWN [some function]
  KeyEvent/VK_RIGHT [some function]
... }

You get the idea. Since I have several keymaps, that gets annoying quickly. So I started to define aliases for some frequently used ones in a seperate "keys" namespace. Now I only had to write, for example, "keys/up" instead of "KeyAdapter/VK_UP". That's a lot of saved space (and some gained readability).

But that introduced something else that annoyed me: Some of the more rare keys were still written out fully, and it created an aesthetic discrepancy, which upset my minor OCD. Besides, having to redefine tons of variables by hand isn't particularly elegant in itself.

(Disclaimer: I am aware of clojure.contrib/import-static. But I don't rely on that library enough to warrant including it in my project just for this.)

Sure enough, there is a better way.
The first nugget of information I needed was... how to get a list of all fields in a Java class?

Of course, Stack Overflow helped me there. It's

(clojure.reflect/reflect java.awt.event.KeyEvent)

It spews out a LOT of things like this:

{:bases #{java.awt.event.InputEvent},
 :flags #{:public},
 :members
 #{{:name VK_F17,
    :type int,
    :declaring-class java.awt.event.KeyEvent,
    :flags #{:static :public :final}}
   {:name VK_DEAD_DOUBLEACUTE,
    :type int,
    :declaring-class java.awt.event.KeyEvent,
    :flags #{:static :public :final}}
...}

This is the abridged pretty-print output of the stuff. We know all the VK_ fields are keycodes. Everything else falls in place itself, basically. Most of what I need I covered in a previous post already.

We want to:
1. From the list of members, filter out everything that is not a keycode.
2. Define the aliases over it.

Simple enough.

(->> (clojure.reflect/reflect java.awt.event.KeyEvent)
     (:members) (map :name) (filter #(.startsWith (str %) "VK_")))

This yields us a list of symbols (which I am grateful for, as it simplifies the next step) of all fields that contain keycodes. Now, as in my earlier post, we doseq over the list and define the aliases via eval in the loop. I also wanted the VK_ dropped and the entire name downcased, but that's just my whim.

(doseq [sym (->> (clojure.reflect/reflect java.awt.event.KeyEvent)
            (:members) (map :name) (filter #(.startsWith (str %) "VK_")))]
  (eval `(def ~(symbol (.toLowerCase (drop 3 (str sym))))
           (. java.awt.event.KeyEvent ~sym))))

That's it. Now, there are vars like "a", "up" and so on that hold the corresponding keycodes. I put the entire thing into a new namespace (game.keys) that I require like that:

(ns game.some-module
  (:require [game.keys :as keys])

Another nonproblem solved! :D

Wednesday, 13 June 2012

A Simple Solution; Part Two

A Short Solution, Part 2:

In the previous part, I laid out the initial draft of the solution to the problem described there, as well as, in the end, pointed out a few problems with said solution.
In addition, you might have noticed that the definition of generate-node was superfluous; in fact, I factored it out. The (cons chunk...) was added directly inside generate-tree.

Anyways. First we shall deal with dealing with duplicate entries in the chunk list and there not being a value in the root node.

(defn to-tree
  "Generates a solution tree for the given word and chunk list."
  [word chunks]
  (cons word (generate-tree word (keys (group-by identity chunks)))))
 
This function will be the one called in the end - it's not done yet, but both the mentioned problems are solved. The value of the root node is now the word being assembled, and
the chunk list is filtered of duplicate entries (I am not sure whether there is some other, more efficient method, this was what I thought of off the top of my head when writing).

Now, for the harder part. Filtering invalid entries out of the tree. Any branch that ends without an entry is invalid - but guessing which ones are invalid before everything is
generated would be impossible (I think?), or at least extremely hard to implement. So I went for the simpler, more functional approach: Filter out invalid entries after everything
is generated. Since the user will never see the unfiltered step inbetween, we can mark invalid branches in some way instead of writing a function that assembles a branch in order
to check it for validity.

So, first we need to modify our tree generation function:

(defn generate-tree
  [word chunks]
  (if-let [nexts (seq (possible-nexts word chunks))]
    (map #(cons % (generate-tree (drop (count %) word) chunks)) nexts)
    (when (seq word) '((:invalid)))))

(This also contains the correction with generate-node.)

Now, when there are no possible nexts, and there still remains some part of the word to be assembled (note again how (seq ...) is used as emptiness check), we add a node with the
value of :invalid. So now we have all invalid branches marked.

Then, the filtering...
First, we need a validity predicate, let's call it "valid?" (how imaginative).
First, let's consider which branches are technically invalid.
Given:

"valid?" ["va" "li" "id" "l" "d" "d?"]

We have:

va \ - l - id - :invalid
   \ - li \ - d - :invalid
            - d?

From the viewpoint of the "va" node, the "l" branch is invalid, and the "li" branch is valid. That is because all the children of the "l" branch are invalid, but one child of the
"li" branch is valid. Seeing that, we can conclude that nodes are invalid if:

1) It has at least one child and all children are invalid
  or
2) The own value is :invalid

Let's code it:

(defn valid?
  "Returns whether a node in the tree is valid."
  [[value & children]]
  (cond (= value :invalid) false
        (not children)     true
        :else (reduce #(or %1 %2) false (map valid? children))))

Fairly simple, is it not? The check is, obviously, recursive. I suppose that if we could traverse the tree from the lowermost nodes instead from the root, the algorithm would be faster
(we could simply hack off invalid branches without having to check the entire depth if a branch is invalid), but our tree nodes are not aware of their parents, nor do we have a list of
branch tips.

So, given this check, we can now filter.

(defn filter-valid
  "Filters invalid branches from the tree."
  [[value & children]]
  (cons value (map filter-valid (filter valid? children))))

Again pretty much one single line.
Now, add this to to-tree...

(defn to-tree
  "Generates a solution tree for the given word and chunk list."
  [word chunks]
  (filter-valid (cons word (generate-tree word (keys (group-by identity chunks))))))

DONE. Simply call to-tree with the word and the chunks and it spews out the final result. You can run it through clojure.pprint/pprint to make it readable, but that only aesthetics. ;P

Final program:

(defn possible-nexts
  "Given a word and a list of chunks, returns a list containing the chunks that are contained at the beginning of the word."
  [word chunks]
  (filter #(= (take (count %) word) (seq %)) chunks))
(defn generate-tree
  [word chunks]
  (if-let [nexts (seq (possible-nexts word chunks))]
    (map #(cons % (generate-tree (drop (count %) word) chunks)) nexts)
    (when (seq word) '((:invalid)))))

(defn valid?
  "Returns whether a node in the tree is valid."
  [[value & children]]
  (cond (= value :invalid) false
        (not children)     true
        :else (reduce #(or %1 %2) false (map valid? children))))

(defn filter-valid
  "Filters invalid branches from the tree."
  [[value & children]]
  (cons value (map filter-valid (filter valid? children))))

(defn to-tree
  "Generates a solution tree for the given word and chunk list."
  [word chunks]
  (filter-valid (cons word (generate-tree word (keys (group-by identity chunks))))))

So, that's it. FIVE function ranging from one to three lines of executed code each.

Wednesday, 2 May 2012

The Last Remnant

Alright, so far this blog was solely a about programming. Time for a change of pace.

The Last Remnant. Yes. I'm having such a hard-on for this game right now.
It's an jRPG, by Square Enix (the Final Fantasy guys). Like any respectable jRPG it is full of Guide, Dang It! moments, but WHO CARES. Seriously.

This is probably one of the most epic games I have played so far, despite having menu-based combat. Really. The game starts off really fast, having you battle overwhelming numbers of enemies with only three or so squads at your disposal. The epicness is hard to describe, really. And all of this underlined by the AWESOME soundtrack. It is... powerful, yet humble enough as not to overshadow what is happening. It's tremendous. Here, listen for yourself. Or this.

Also, I've enabled the japanese voiceover. It's just funny to hear japanesemen attempt to say "Yes, My Lord!" - it sounds roughly like "YES MA ROD" :D Or there is this large pigman guy thingie named "Blocter", I chuckle everytime I hear "BUROKTA" yelled ingame.

The game was criticised for it's extremelly stupid and childish main character, which incidentally happens to be, y'know, a stupid child. Nevermind the fact that he lived most of his life on a secluded island without parents or whatnot. And, from what little japanese I understand (which is virtually none), I gather that he simply gained a little obnoxiousness in the translation. For example, in the original voiceover, I've heard him thanking "Dave" several times, which is absent in the translation. Really, he has some disregard for authority (not weird in his position), this is commented on in the game (later on only by exasparated sighs and shrugs from David, who happens to be a marquis).

I don't claim I like him, but neither is he the annoying useless character everyone says.

Anyways. This game.
THIS GAME.

Epic. More music.

The battles are really intense for some reason. Unlike other menu-based jRPG's, you don't give exact commands to your troops. Each "Union", consisting of one to five characters, is given a general order by you at the beginning of the turn. Examples include the regular "Attack", "Attack with Combat Arts!", "Keep your HP up!", and others. The regular attack changes its name depending on the situation, which is yet another detail that makes everything more epic. You don't "Attack" bosses, you "Charge" them. If you fight against something significantly stronger, you tell your troops: "Don't be afraid to die!" There are other situational commands: If one union is getting battered, another can be given the command "Save them no matter what!".

There's also a morale bar. It increases when you pull of maneuvers like back attacks, interceptions and so on, or defeat enemy unions, decreases when the enemy does any of that. It influences how well your units fight.

Sometimes, a unit may suddenly disregard your command. This sounds wrong, but actually is really cool - your union is getting hammered apart suddenly, and one of the spellcaster decides on his own to heal the group. Especially during boss fights - the without doubt most intense parts of the game - this adds a lot to the experience.

While picking your commands, all fighting unions sparr with each other, the battlefield never rests - no damage is dealt or anything, but it looks cool.

 Really, all this builds up to an extremely immersive and FUN experience.

Buuuuut, getting down from my unrestrained fanboyism for a moment...

This game has flaws. Lots of things are confusing and make no sense unless you read an in-depth guide (up to and including the levelling system). You might find out a lot through experimentation... but that would take thrice the time, I guess. Sometimes, you will be crushed by relatively weak, or equal enemies through no fault of your own (it's rare.) Advancing the story may make things unavailable, a problem for completionists. Sometimes you will run into superpowered enemies who will, again, crush you within seconds - with no warning whatsoever. And maybe at least a dozen other pet peeves.

Despite all this, this game...

Here, have another battle theme.

Despite all this, this game is PURE. Awesome.

Wednesday, 25 April 2012

A short solution.

My friend and roommate (and possibly future brother-in-law, heh.), who studies IT, was presented with the following problem/homework:

Given a string and a list of other strings, build a tree of all possible ways of assembling the given string from the strings in the list.

For example, given "tree" and ["tr" "e" "t" "re"], possible branches are ("tr" "e" "e"), ("t" "re" "e"), and so on. Each item from the list can be reused, so "tree" and ["tr" "e"] produces one valid result: ("tr" "e" "e"). A very simple final solution looks like this: (("tr" ("e" ("e")) ("ee"))) (for "tree" and ["tr" "ee" "e"]).

He had to do it in Java. Since I thought the problem was interesting, I decided to implement  a solution in Clojure.

Working in a language that builds trees out of nested lists, my choice of datatype was obvious: Nested lists (you probably didn't see that one coming). The first element of each list is the value of the node, whereas the rest of the elements are its children.

So, I though about what I needed.
First of all, a function that filtered the list of strings (lets call them "chunks" here), leaving only the chunks that are included at the start of the word. For example, given the same parameters as above, the function would return ("tr" "t"). Fairly simple to write:

(defn possible-nexts ;; Yes, slightly awkward name.
  "Given a word and a list of chunks, returns a list containing the chunks that are contained at the beginning of the word."
  [word chunks]
  (filter #(= (take (count %) word) (seq %)) chunks))

It's a fairly simple function, and I believe I don't need to explain how it works (and actually, it's largely irrelevant, as long as it produces the output we need).

Now, since we want to generate a tree, the generating function will obviously be recursive. The general idea for the algorithm is this:

1) Given a word and chunk list, obtain a list of possible nexts.
2) For each possible next chunk, recursively call this with the chunk cut off from the beginning of the word (so with "tree" and "tr" the call would get "ee") and the whole chunks list
3) If there are no possible nexts, terminate.

Simple enough.

(defn generate-tree
  "Generates a tree of all possible ways \"word\" can be assembled from the \"chunks\"."
  [word chunks]
  (when-let [nexts (seq (possible-nexts word chunks))]
    (map #(generate-tree (drop (count %) word) chunks) nexts))))

This was my original version. Again, a very simple function (though I managed to screw up, see below). when-let is fairly simple and does what it says on the tin, whereas seq coerces a collection into a seq, or nil if the collection is empty. This is great for at least two reasons:
1) You get uniform behaviour for all collection types.
2) Free emptiness-check!
Doing emptiness checks via seq is apparently idiomatic in Clojure. I found it rather confusing in the beginning, but now I like it.

Anyways, I have to admit I failed to store the value in each node. But that was quickly rectified with some minor changes...

(defn possible-nexts ;; Yes, slightly awkward name.
  "Given a word and a list of chunks, returns a list containing the chunks that are contained at the beginning of the word."
  [word chunks]
  (filter #(= (take (count %) word) (seq %)) chunks))

(declare generate-node)
(defn generate-tree
  "Generates a tree of all possible ways \"word\" can be assembled from the \"chunks\"."
  [word chunks]
  (when-let [nexts (seq (possible-nexts word chunks))]
    (map #(generate-node (drop (count %) word) chunks %) nexts))))

(defn generate-node
  "Generates a tree node."
  [word chunks chunk]
  (cons chunk (generate-tree word chunks)))

That's pretty much it. Less than ten lines total. Or so I (actually, we) thought. There turned out to be a few actual problems, and one aesthetic one.
For one, I didn't have a root node, per se, just a list of subtrees (one for each possible first chunk). I decided the root node shall hold the original word that is being assembled. This is easily solved; call (generate-node word chunks word) instead of (generate-tree word chunks). In light of the other problems, though, I decided to write one last function:

(defn to-tree
  "Nothing much, yet. But this shall be the entry point for the \"program\" shortly."
  [word chunks]
  (generate-node word chunks word))

The other problems are more serious: With certain combinations of chunks, there would be incomplete branches. For example, given "lol" and ["lo" "l"], one branch would be simply... ("l"). We don't want this garbage.
Other than that, if there were duplicate chunks, parts of the tree would be duplicated as well. So, we might want to filter the chunks list.

But I will solve both of those in the next posts, this is getting a bit long. You might have noticed that the "program" mostly consists of one-liners; my original, slightly less verbose (no function descriptions) was only 9 lines (2 whitespace, 3 function "header" and 4 lines of function body). The next post won't add more than ten lines (not counting function descriptions, again), either.

It also performs rather well, it was clearly faster than my friends Java version (though I suspect an adequately experienced Java programmer may write one faster than my nineliner. As might an adequately experienced Clojure programmer, I guess...).

Functional languages amaze me over and over again with how little I need to do to achieve what I want.

Saturday, 21 April 2012

Installing Clojure: Linux, Emacs, Swank - Step by Step

(This was posted in 2012. Don't count on it being up to date.)

I had to format my computer recently, so now I also have to go through the entire setup process again.

When I did it the first time, it was an epic pain in the arse, but this time I got through with barely a problem, so I decided I might want to share my fairly simple method. Also, because it annoys me when I find handwritten guides and am too agitated to check the post date for outdatedness, I am writing this post on the 21 April 2012. And minor explanations why I do certain things, because that's another thing I don't like about handwritten guides: Magic. ;P


I'm using Emacs + leiningen under Linux Mint to serve all my needs (any linux distro will do, I suppose. I'm not an expert on distros, or Linux in general, quite the opposite). Leiningen is a truly wonderful tool that is surprisingly comfortable to use, provided the command line does not scare you, of course. Anyways, leiningen provides much useful functionality for handling projects and is fairly configurable. It also, and that is the most important thing, I think, it handles dependencies for your projects. Emacs is of course the editor we'll use.

So, without further ado:

1) Get all the things you need (via "sudo apt-get install [name]"). After a fresh install of Mint 12, that was:
  a) emacs23
  b) leiningen
Any other dependencies should be handled by apt-get.

2) Install the swank plugin for leiningen.

> lein plugin install swank-clojure 1.4.2

A very useful thing. Really, very useful. Very short version: Get reasonably feature-loaded REPL within Emacs with minimal hassle. Long version: Slime. Slime is a REPL for lisps (it literally stands for "Superior Lisp Interaction Mode for Emacs"), which consists of two parts, the server and the client. This easily allows Emacs to interact with it; the client is integrated into Emacs, with it not caring where the server really is. lein-swank provides the server part, customised for Clojure.

3) Launch emacs once. I usually use emacs -nw, so it runs in the console, but some people might prefer the more interface-y version. This spawns the .emacs.d/ folder in your home directory.

4) Get clojure-mode (and paredit).

First, create and enter ~/.emacs.d/site-lisp   **
Paredit is a minor mode for Emacs that makes working with LISPs sane, and also infinitely more pleasurable. It has a few quirks you need to get used to, but saying it is "recommended" when working with a LISP (like Clojure) is like saying that breathing is "recommended" when living.
Obtain it via
> wget http://mumble.net/~campbell/emacs/paredit.el

** Small plugins for Emacs, like paredit, I usually put into a common site-lisp directory, whereas larger ones, that span multiple files, go into their own directories. You don't have to do it like that, of course, you''ll just have to adjust the load paths (will come up soon) accordingly. It's not hard.

Now, Clojure Mode. I am sad to say my git-fu failed me, and I downloaded it from github via the ZIP button and unzipped it into ~/.emacs.d/clojure-mode/. Oh well, one day I shall be capable of everything from within the command line!
Anyways, Clojure mode. Major mode for Emacs. It provides syntax highlighting, intendation, and other stuff for Clojure.

5) Configure the crap out of Emacs.

This is pretty much the final step. Go to your home directory, and open the file called .emacs - this file will be executed when Emacs is being started up and can be used to configure it.
We'll be using a few commands in Emacs Lisp, a language integrated into Emacs, here.


  a) I usually disable the menu bars and stuff. If you want, too, put:

(menu-bar-mode nil)

This is of course entirely optional, but those one or two lines of extra space are more valuable to me than those pesky menu bars. :P


  b) Add site-lisp to the load path.

(add-to-list 'load-path "~/.emacs.d/site-lisp")

That way, Emacs knows where to look when you want to load a file. Actually, there are a multitude of directories on the load path already (to check which ones, type "load-path" into the file and press C-x C-e with the curser directly after what you just typed. Just remember to remove that line afterwards :3), and you theoretically could use those, but they are shared, and it's always a good idea to keep personal configurations bound to your account - thus we use the appropriate directories in your home folder.


  c) Load paredit

(require 'paredit)

Fairly self-explanatory - we load the paredit.el file. This does not activate it yet, but it supplies the function paredit-mode, which can be called to enable (and disable) paredit. We will use it in the next steps. Emacs knows where to find paredit.el thanks to our adding the site-lisp directory to the load path, of course.


  d)  Load and configure clojure-mode
(add-to-list 'load-path "~/.emacs.d/clojure-mode")
(require 'clojure-mode)

Two fairly self-explanatory steps, again. We basically do the same as with paredit. Again, this does not yet activate clojure-mode, it merely loads the file.

(add-hook 'clojure-mode-hook
          (lambda ()
            (paredit-mode 1)))

 This is the by far trickiest part, but by no means complicated. add-hook takes two arguments, the hook (I'm not sure what exactly they are, but they are usually [or always?] called [mode-name]-hook), and a function. Then, whenever the mode is enabled, that function is called. Due to this snippet of code, whenever clojure-mode is enabled, paredit goes along with it.

Almost done.

clojure-mode gets enabled automatically when you open a .clj file, though you can manually enable it via M-x clojure-mode any time.

 Once you have clojure-mode loaded, you can use M-x clojure-jack-in to open a slime session/REPL (after typing that, wait for a few seconds - booting it up takes some times).

Actually, you could add clojure-jack-in  to the function that is hooked onto clojure-mode, so that the REPL is automatically launched, though since clojure-mode is started everytime you open a new .clj file, it tries connecting each time you do so (and succeeds, but I'm not sure if there are any side effects - better to just do it manually, or do some slightly more advanced hacking that checks whether there is a clojure repl buffer and doesn't start a new one if there is).

And a final thing about clojure-jack-in: It is intimatly bound with leiningen. It will only work within leiningen projects, which isn't much of limitation actually. Simply learn to use leiningen - it is worth it.

Aaaaand now really a final thing:

When using swank-clojure 1.4.2, I got an error message about Emacs not being able to start a swank server - it STILL WORKS for some reason. If it doesn't, or the message annoys you, use swank-clojure 1.3.3.

I hope I helped.

Saturday, 31 March 2012

Final thing on destructuring.

I haven't been writing in some time, that is because I haven't been doing much coding lately. Now I've started again, and immediately found something interesting.

So, I managed to nuke my environment (Emacs/Slime/Swank) by trying to install a Common Lisp thingie next to it, so I had to setup everything again and... it turned out my previous setup was unnecessarily complicated and hacky. Oh well.

Anyways, I've been expanding a few macros for fun to see how exactly they work (->, to be exact), and expanded let by accident.

So... one quick call to (doc let) later...

01(defmacro let
02  "binding => binding-form init-expr
03
04  Evaluates the exprs in a lexical context in which the symbols in
05  the binding-forms are bound to their respective init-exprs or parts
06  therein."
07  {:added "1.0", :special-form true, :forms '[(let [bindings*] exprs*)]}
08  [bindings & body]
09  (assert-args let
10     (vector? bindings) "a vector for its binding"
11     (even? (count bindings)) "an even number of forms in binding vector")
12  `(let* ~(destructure bindings) ~@body))


Actually, this is taken from clojuredocs.org. Neat site. But I found it out over (doc let).
It turns out let is NOT a special form, let* is. And then...

(destructure bindings)

Yes. Destructuring is actually implemented in the language. clojure.core/destructure, to be exact. You can even pass quoted argument vectors to that function and see the output.

The source is ugly (and long) as hell, but still, it's fascinating this is implemented in the language as well.



















Saturday, 18 February 2012

Generating functions

Macros in LISPs are fascinating, and from what I gather, they are fairly well implemented in Clojure. As I'm sure I've mentioned before, I'm writing a game in it, and recently I wrote a piece of code I find very interesting.

In that game, I have units, and those units have attributes like health, attack power and so on. I wanted to write a bunch of functions to get, set, modify and reset those values. Thats four functions times seven attributes... 28 functions. Naturally, it would be crazy to spell out ALL of those accessors by hand, especially since they are almost identical to each other in many cases.

What I did is made of three parts, basically.

First, I defined a set of attributes, which will be used later.

Second, I wrote generic accessors, accepting a keyword naming the attribute to work on as first argument, and whatever else they needed as the remaining arguments. I called them set-, get-, change- and reset-.

Last, and this is the most fun part, I wrote a little tidbit of top-level code:

(doseq [stat stats, f ["get-", "set-", "change-", "reset-"]]
  (eval `(def ~(symbol (str f (->> stat str rest (apply str))))
           (partial ~(symbol f) ~stat))))

While I didn't use defmacro explicitly, I use the macro mechanics here. I just wrote it inline. What happens here isn't very difficult to understand once you grasp the basics of macros, but I will step through it nonetheless.

The first line can basically translated as "for every possible combination of stats and those string, do the following".

eval executes the code it is given. Executing LISP code literally means just running it through eval. Also, it is a special form. But nevermind that right now.

The backtick ` is a very pleasant thing introduced in Clojure to combat common macro problems which aren't important right now, but generally it does the same as an apostrophe ', it turns the expression it precedes into a literal (when writing macros in Clojure, use backticks, never apostrophes - ALWAYS). So while (+ 2 5) evals to 7, '(+ 2 5) evals to... well, '(+ 2 5). The other one, the wave ~ (or however it is called), kind of cancels the quoting. As an example, '(+ ~(+ 1 1) 5) evaluates to '(+ 2 5).

Now it becomes fairly obvious what is happening here. We plug the attribute and function names into a generic def.

As an example, for :attack and "get-"...

`(def ~(symbol (str "get-" (->> :attack str rest (apply str))))
   (partial ~(symbol "get-") :attack))))

Which then turns into...

`(def get-attack
   (partial get- :attack))

And that is given to eval to finally execute it.

The abominable first line happens due to my choice of using keywords - running a keyword through str returns it together WITH the colon, and I remove it with the str rest (apply str) part. The second line is fairly straightforward. This is why it was important that the name of the attribute be the first argument.

Anyways, now, whenever the file is executed (be it by loading or by... well, running it), 28 functions are generated. I can easily add more attribute accessors by adding entries to the list of attributes, I can easily change the implementations of the functions themselves without having to wade through tons of code and correcting them in several places at once (which is a really error-prone procedure). Also, should I never need to, I can add new types of accessors with a minimum of hassle. And finally - I had fun writing accessors. When was the last time that ever happened to any of you?

I'm am fairly confident I am entitled to say I did an awesome job there.
Now I should go play Guitar Hero "Fury Of The Storm" on hard before my self-esteem gets too high.

PS: I slightly modifed the generic def later. The first line turned into
`(def (->> stat str rest (apply str) (str f) symbol)
It doesn't change anything, but looks more consistent, in my opinion.

PPS: The base functions (get-, set-...) are private. Just saying.