I am very new to Nim, and am trying to port the libtcod tutorial written in Python - here: Complete Roguelike Tutorial, using python+libtcod - to Nim.
For the most part, it is smooth sailing, but now we are going to use composition and inheritance, and this is what I've got:
type
Fighter = ref object of RootObj
max_hp, hp, defense, power : int
owner : ref RootObj
AI = ref object of RootObj
owner : ref RootObj
# Generic object represented by a character on the screen
# A Thing can be: player, monster, item, stairs, ...
Thing = ref object of RootObj
x, y : int
color : TColor
symbol : char
name : string
blocks : bool
fighter : Fighter
ai : AI
BasicMonster = ref object of AI
Then, the Thing constructor:
proc newThing(x : int, y : int, symbol : char, name : string, color : TColor, blocks : bool, fighter : Fighter = nil, ai : AI = nil) : Thing =
result = new Thing
result.x = x
result.y = y
result.symbol = symbol
result.name = name
result.color = color
result.blocks = blocks
result.fighter = fighter
if fighter != nil:
fighter.owner = result
result.ai = ai
if ai != nil:
ai.owner = result
And an example of a method using it:
method take_turn(self : BasicMonster) =
var monster = Thing(self.owner)
if map_is_in_fov(fov_map, monster.x, monster.y):
if monster.distance_to(player) >= 2:
monster.move_towards(player.x, player.y)
elif player.fighter.hp > 0:
echo("The attack of the ", monster.name, " bounces off your shiny metal armor!")
While it seems to work, it does look a bit messy - am I on the right track?
The whole idea behind the AI component is that it can be of different types - BasicMonster is just one of them, using a dead simple terminator chase approach.
Disclaimer:
I am not going to have an opinion about whether or not the design is OK or not, because the goal of this exercise is to duplicate the Python code.
The particular piece of code is at this page: Complete Roguelike Tutorial, using python+libtcod, part 6 code
Any good docs on concepts in Nim ?
I failed to find it in the tutorial - perhaps I should browse the manual as well ? ;)
[Edit] I guess I can't find them because they are unstable, right?
In that case, I think I'll keep them in mind and carry on :) [/Edit]
Generally Nim's docs = [..] concise and legible.
Exactly. And one of the main reasons why I have decided to prefer Nim :)
I guess I can't find them because they are unstable, right?
They're in the language manual, right after type classes, as they used to be known as "user defined type classes". Beware that some parts that have been in the documentation for months now are not even in the compiler. While I'd love to have that feature available, I believe it's false advertising to present it in the manual as though it were.
Unless you feel like being a trail blazer, I'd avoid certain exciting features of Nim that are not really polished yet, like concepts, and be careful of others (eg method combined with generics).
Thanks!
I searched for 'concept' but it failed me - I even looked at the section you linked to, but managed to overlook the (prominent) section on concepts ;p
Yes, even though I am a programmer - and thus prone to overthinking - I guess I'd get the basics down before moving on to unchartered territory.
So, what I basically want to know is if what I wrote is valid and sound Nim code.
It seems to work, though.
So, what I basically want to know is if what I wrote is valid and sound Nim code.
It is valid code. However:
You don't actually use inheritance in your code for Fighter and Thing, so of RootObj and method (instead of proc) may introduce unnecessary overhead, unless you plan on using inheritance later. If you do want to use inheritance, then you may want to mark the root objects of your own inheritance hierarchy as {.inheritable.} instead of inheriting from RootObj. Inheriting from RootObj means that you can assign anything to variables of type RootRef. This can be useful (e.g. for debugging purposes), but also means that you can possibly get types mixed up.
Concepts (in their current form) are somewhat orthogonal to inheritance, as they're (currently) for compile-time polymorphism (via generics) instead of runtime polymorphism (which is what inheritance is for). Once vtref support is available, you can then also use them for runtime polymorphism (see the manual for details).
It seems to be valid Nim code.
That's what I wanted to know, thanks!
It's also a design right from the 90ies, slow, impossible to parallelize, hard to serialize.
I know. I wouldn't have come up with the design if I wrote it myself, because it is a straight port of the Python tutorial code.
Yes, I have been in C++ for long enough to have seen some really horrible OOP code, especially from 90's and the early 2000's :D
If you do want to use inheritance, then you may want to mark the root objects of your own inheritance hierarchy as {.inheritable.} instead of inheriting from RootObj. Inheriting from RootObj means that you can assign anything to variables of type RootRef. This can be useful (e.g. for debugging purposes), but also means that you can possibly get types mixed up.
That is interesting!
I wish there were more tutorials / manual material that dealt with objects - and explains the various pragmas (are they called pragmas?) - that is really useful information, thanks! :)
Later on, when the entire tutorial has been ported, and I know Nim more, I will definitely be revising the architecture, that's for sure.