Author Topic: What would a programming language for roguelikes look like?  (Read 12464 times)

Skullcoder

  • Newcomer
  • Posts: 23
  • Karma: +0/-0
    • View Profile
    • Skullcode
    • Email
What would a programming language for roguelikes look like?
« on: April 01, 2016, 03:13:21 PM »
If one were crazy enough to implement a new programming language expressly for roguelikes, and possibly even a (virtual) chipset expressly for running said language upon, and potentially even a portable cross platform (virtual) operating system atop said chipset expressly to support the integrated development of roguelikes, what features and/or syntactical sugars might one expect to find therein?

Let's assume that the language's standard library would have the expected bog standard algorithms for pathfinding and procgen, such as A*, voroni, perlin & simplex noise, etc. as well as abstractions for recording and retrieving the game state and levels via the platform's available storage medium.  Let's also assume the standard library includes a tile based display interface capable of multiple layers of ASCII or graphical and potentially animated cells / sprites, something a bit more friendly than curses but in a similar vein, with definable "window" objects and scrolling regions within the display, etc.  And while we're at it let's assume there's a simple music and audio event system included.

I'm not asking what standard tools a roguelike programming language would want to have in a standard language library as that's quite apparent from existing frameworks, but rather I'm curious to discover what, if any, linguistic flavoring could prove more "expressive" for roguelike development than existing general purpose languages?  For example, in my experience roguelikes tend to be extremely stateful.  So, perhaps a roguelike language would have builtin Actor entities an in addition to the generic OOP features these would have "states" and "statuses" which can affect the actions the Actors perform.

Thus:
Code: [Select]
Actor enemy = new rand Enemy of [ floor.actors with[ Enemy ], currentDifficulty, floor.environmentType ];
...
enemy: if ( has Pain ) is Angry;
// Equivalent to:
// if ( enemy has Pain ) enemy is Angry;
...
enemy: if ( can See( target ) ) attack();
// Equivalent to:
// if ( enemy can See( enemy.target ) ) enemy.attack();

The above would create a new Actor of the Enemy type, randomly selected from those registered types matching the current set of "of" qualifiers: Actors of Enemy type available on the current floor, at the current difficulty setting and matching the current floor's environment status.  The union of qualifiers represents the pool from which the enemy's type would be randomly selected prior to instantiation.  The pseudorandom "rand" selector being deterministically seedable at the outset, of course.

Then the enemy.attack() function may perform a different set of logic depending on whether it entered the "Angry" state due to it having the Pain status, i.e., more than one set of "methods" could be registered for the Actor, and if the Enemy has an Angry state that defined a different .attack() method, it would be called rather than the Enemy's default attack() method.

The attack() function(s) would not be called if the status of sighting its current target is missing.  Furthermore, the state transitions would have a default "on" method that is called when entering such a state:
Code: [Select]
Dragon:Enemy{
  ... 
  on: Pain{ is Bleeding; }
  on: Death{ floor.addAt( self.position, new rand Drop of self:[ drops, inventory, floor.level ] );
  ...
}

Also note the "with [object]" shorthand of "[object]:", which selects the given Object/Actor/Entity as the default scope for the duration of the following statement / block.

A form of compile time static type analysis could generate warnings if unique / non-eventful, and possibly misspelled statuses/states are applied to Actors.

This is a hypothetical syntax, of course; I'm only mostly crazy enough to implement such a domain specific language, and can neither confirm nor deny whether several iterations of cross platform virtual machines already exist and are hungry for construction of a language other than Assembly or C... Mostly, I'm interested in what features / syntax others would envision an RL lang of having.

The obvious response that few if any would bother tinkering with a new language just to utilize some newfangled portable rogulike operating system is a given; Thus, please ignore the difficulty and insanity required to achieve such a goal and instead approach the question as an outlandish thought experiment:
What if...
« Last Edit: April 01, 2016, 04:55:01 PM by Skullcoder »

Tzan

  • Rogueliker
  • ***
  • Posts: 193
  • Karma: +0/-0
    • View Profile

tuturto

  • Rogueliker
  • ***
  • Posts: 259
  • Karma: +0/-0
    • View Profile
    • pyherc
Re: What would a programming language for roguelikes look like?
« Reply #2 on: April 01, 2016, 09:10:45 PM »
I'm extremely biased on the subject, as I have been tinkering with something like this for a bit. Also, language features is one of those topics that can generate very heated discussions, so be warned :) This is just my opinion on the subject and isn't any more correct or more wrong than any other opinion on the topic.

I wouldn't choose OOP as main paradigm, but rather split data and functions that operate on that data apart. It's easy to build a hierarchies and topologies of different type of data, but as soon as behaviour enters the picture it starts to get trickier. Having functions that operate on the data separate also helps in organizing the code and reduces amount of 'doers', 'managers' and 'utilityclassxes'. It also helps to answer the question "if I want to read a scroll, who has the method? character, scroll, some manager?". Multiple dispatch would be really nice, with :before, :after and :around semantics like in CLOS, so one can check if preconditions of a function (like attack for example) are fulfilled, before calling the function. Compile time type checking with inferred typing would be my preference, but my preference on that tends to fluctuate a bit. I like syntax of lisp quite a bit, so I think I would take that as a base.

Having ability to model the basic constructs like state machines is very good idea. Syntax I chose for my current project is somewhat like:

Code: [Select]
(defstatemachine <statemachine name> [<interface>]

  (--init-- [<init parameters>] <init code>)

  (<state> <initial-state>
                (on-activate <activation code>)
                (active <code that is run while state is active>)
                (transitions [<check> <new state>]
                                   [<another check> <another state>]))
  <more states>)

This creates a state machine, which can then be instantiated and called like it were a function with parameters defined in <interface>. <init parameters> and <init code> are for initialization of the finite state machine. Each state is then defined in a block that starts with name of the state (on being marked as initial state, which is the state the state machine starts in). on-activate block is executed when state activates, active is run when finite state machine is given signal to process and transitions define when and how the machine switches to different state.

Ability to do random stuff, without having to resort to random numbers and comparing them to some range is nice. So instead of:

Code: [Select]
(if (> (.next random 1 100) 90) (something-bad-happens player-character))

I want to write:

Code: [Select]
(rarely (something-bad-happens player-character))

Another option I would like to have is relational or logic programming. Instead of describing an algorithm, I would describe problem domain and relations of things within it and let the computer to solve the problem. For example, if a AI character would like to figure out what kind of items it needs to bypass a trap and if they already have them, it could be done with a program like (this one doesn't take account possible skills or perks of the character, only their inventory):

Code: [Select]
(defn item-for-safe-passage [inventory trap]
  (run 10 [q]
    (fresh [item trigger safe-method]
      (triggerᵒ trap trigger)
      (safe-movement-methodᵒ trigger safe-method)
      (memberᵒ item inventory)
      (effectᵒ item safe-method)
      (nameᵒ item q))))

Code would output at most names of 10 items that let character to bypass the trap. Neat thing about this approach is that the goals can work all directions. For example in:

Code: [Select]
      (safe-movement-methodᵒ trigger safe-method)

If triggering method is know, this will produce safe method of crossing the trap. If safe-method is know, this will tell what kinds of triggers can be avoided with that. If both are known, the code will check if the trap is sprung or not. Since this language can be wrapped into a function, it is transparently callable from regular code. Having AI and game logic share the same routines helps to make sure that if rules of the game are updated, AI is updated too.

Since designing language is hard and designer will never get it right for every user, I would like to have ability to modify and extend the language. It could be done in macro system similar to what lisp has, or it could work with some other system. But since ability to do symbolic programming is something I really like, lisp macros would be my preferred method of extending the language.

I think something like that would be my start.
Everyone you will ever meet knows something you don't.
 - Bill Nye

sokol815

  • Rogueliker
  • ***
  • Posts: 85
  • Karma: +0/-0
  • Web Developer by Day, still Web Developer by night
    • View Profile
    • Email
Re: What would a programming language for roguelikes look like?
« Reply #3 on: April 02, 2016, 03:53:49 AM »
I find this topic fun. Perhaps if you ever end up building a custom chip for your new roguelike language, you could include the option to turn permadeath on, which would instantly fry the chip when you lose. Then you have to go solder another one in!

tuturto

  • Rogueliker
  • ***
  • Posts: 259
  • Karma: +0/-0
    • View Profile
    • pyherc
Re: What would a programming language for roguelikes look like?
« Reply #4 on: April 02, 2016, 04:37:21 AM »
I find this topic fun. Perhaps if you ever end up building a custom chip for your new roguelike language, you could include the option to turn permadeath on, which would instantly fry the chip when you lose. Then you have to go solder another one in!

 ;D That would be hilarious.

After sleeping a bit, I think I have another perspective to this. Maybe we should look from point of view of primitive, means of combination and means of abstraction too? They're the basic parts of pretty much every programming language and the essence that really tells them apart.

Primitives: the usual suspects like numbers, text, lists and such are obvious. But since we're talking specificially roguelike language, should there be higher concepts here too? Like character/creature, item, coordinates, action, ai-routine and such? If they're very detailed, building different kinds of roguelikes might be tricky, but if they're too vague they're maybe not so useful? Code is one primitive too I would say. Things like lazy and infinite datastructures and asynchronous operations would be nice to have built into language.

Means of combination: Code gets put together sequentially. Primitives can be placed inside lists and arrays. Actions can be chained together to create more complicated actions (or is this last one actually about abstraction or both even?) Pieces of AI-routines can be combined in a meaningful way with state machines and other AI-routines to create more complex/sophisticated AI-routines.

Means of abstraction: Code can be wrapped into functions, so we can give explicit names to higher level concepts. Functions can be used as parameters and return values (think of a* where you can parametrize how routine works by passing in part of the routine).
Everyone you will ever meet knows something you don't.
 - Bill Nye

Skullcoder

  • Newcomer
  • Posts: 23
  • Karma: +0/-0
    • View Profile
    • Skullcode
    • Email
Re: What would a programming language for roguelikes look like?
« Reply #5 on: April 13, 2016, 10:22:45 AM »
Having functions that operate on the data separate also helps in organizing the code and reduces amount of 'doers', 'managers' and 'utilityclassxes'. It also helps to answer the question "if I want to read a scroll, who has the method? character, scroll, some manager?".

I agree with and enjoy the procedural and functional paradigms.  However, I think that "OOP" suffers from its own flexibility.  The name "method" is poorly chosen, IMO, as is the term "function".
In fact, I also object to "object" as it's objectively been selected only for the purpose of requiring an object lesson in ambiguity rather than objectivity. QED.
I hold a similar stance on the objectively subjective terms "definition" and "declaration", but I digress.

Despite my admittedly unpopular views I do recognize the value of Agent Aware Programming.  An agent is like an "object", except is should be clear that an Agent can act, while objects are only acted upon.  Thus in your example, the ambiguity is easily resolved.  The scroll is an object.  It has only properties and does not perform actions of its own.  Thus it is the Player (or some other Agent) which should activate its .read() action while passing the Agent which scroll to read.   Spells act upon the world so they are Agents invoked by Players or other Magic capable Agents.  Which Spell to invoke when reading a Scroll is a property of the scroll entity.
Code: [Select]
Player.read( scroll ){ scroll.spell.invoke() }
...
IdentifySpell.read( scroll ){ scroll.identified = 1 }

In other words, OOP shouldn't be "objects all the way down".

One of the things many OOP implementations fail at is they neglect to provide their objects with a facility to determine what container object they may be a property of.  For instance, an object can access its properties.  Properties should be able to determine the object to which they are attached.  This is far more useful for the same reason that a doubly linked list is more useful than a singly linked list.  In theoretical terms, the "object.property" relationship is broken in nearly all OOP implementations' symbolic dimensions, as the relationship is only accessible from the scope of one half of the pair.  This is the cause of many unnecessary headaches in today's OOP languages.

For instance.  If the Spell is invoked, how does it know to delete the scroll to which it is attached or if it is attached to a scroll at all?  Most OOP systems would force you to pass spell.invoke() an optional scroll reference and if it's a non nil value, then the spell would destroy the scroll object passed in -- Or, perhaps you fall into the trap of adding actions to POD via giving scrolls a state transition, perhaps through data hiding: scroll.setRead( bool ){ ...; destroy( self ) }, which causes the the scroll to be an Agent with actions of its own to perform (or object with methods), thus causing the "Classness" to proliferate onto what could otherwise be merely a data structure.

Perhaps I should explain first that a "symbolic dimension" is a language construct imagined as a dimension of the n-dimensional language-space.  So, when you create a construct, such as the ability to give a symbolic name to an offset in memory (create a variable) you are mapping the data dimension to a symbolic dimension.  The same symbolic dimension that allows you to name global variables allows you to name properties of structs.  Each property of the struct is a named offset in memory, relative to the struct's starting address.  Therefore the variable naming symbolic dimension is universally applied in these examples.  In a "complete" language you would be able to apply any symbolic dimension (any language construct) to any language primitive.  Furthermore you should be able to traverse in either direction along an unbroken dimension, in order to reverse the mapping.  C (and most languages) fail at this because there's no way for me to say: Give me the name of the property at memory offset 0x100, or Give me the Nth property name of structs having type T.  Likewise the symbolic mapping between Object.property is broken.

Instead, consider an unbroken symbolic dimension of object.property mapping:
Code: [Select]
// If a spell is the property of a scroll, the scroll is destroyed upon invoking the spell.
Spell.invoke(){ if ( typeof self.owner == Scroll ) destroy ( self.owner ); self.applyMagic(); }

This codifies the relationship of "ownership" which RAII implicitly creates, but I digress.

Though I've been using c-like pseudo code syntax a RL need not be so.  However, since Java, JS, C/C++, and a host of other languages use similar syntax, an RL lang might benefit from the principal of least surprise by adopting some commonly familiar syntactical idioms.  On the other hand, in keeping with the roguelike spirit a RL lang might utilize as many strange symbols as possible, as RL players (and thus devs) tend to be able to cope with such things more easily than others...  I've always found it odd there weren't more roguelikes implemented in Perl (oh look, another C-like syntax [or more correctly called Algo-like syntaxes]).

And yet, on the gripping hand, a Roguelike language that allows new symbols to be added to the Lexer's tokens in order to extend the language, could also have an "import Roguish" which would randomize the language's syntax using the source's file name as the seed.  Then the programmer would get to play a Rogue-like identification game where compiler errors act as your identify spell, and you slowly get to learn what symbols to use in the source file in order to program -- Need to rename the file?  Sorry, that causes confusion which lasts until the source is reprogrammed to use the new symbols and may result in project permadeath.  Note that removing "import Roguish" would have the same effect.

I'm not saying that an OOP or Agent Aware paradigm is required in a RL language, simply that such things are useful and probably should be possible to use as the optimal RL language would probably support multiple programming paradigms.  This is one reason why I implement virtual machines before languages, so that one need not cram every paradigm under the sun into one language syntax.  Multiple languages can compile to one common bytecode, but I'm getting ahead of myself.

I like syntax of lisp quite a bit, so I think I would take that as a base.
I like the reflection and dynamism of Lisps, but think the syntax could be improved : )))))))
Using the Sexp for everything is powerful because it simplifies the implementation of self modifying code, or self aware code (macro facilities).  IMO, Lisp is a nice language to write programs that write programs in...  However, any new language can gain the features of and/or surpass Lisp (esp. in terms of usability) so long as: the compiler is self hosting, the macro language is the same syntax as the programming language, and the syntax tree being compiled is accessible from within macros.  One could take things a bit further and have the macro language be aware of the tokenizer too.

Personally I prefer compiled languages with strong typing because it catches a crap load of errors at compile time.  I can't tell you the number of times I've had to resort to hunting through diffs line by line for hours to chase down a bug in a large library coded in dynamically typed languages.  IMO, dynamic typing is great for small projects, but its usefulness is quickly outpaced by the pitfalls introduced.  Case and point: Most successful dynamic languages eventually adopt a "use strict" facility to enable stronger type checking.  Strong typing need not restrict run time dynamism in a significant way (though in most strongly typed languages have little to no dynamism).  Duck typing with compile time type analysis can yield much of the freedom that purely dynamic languages have, while introducing far less headaches.  "Adapter typing" is another feature strongly typed languages can use whereby methods with the same parameters but differing names can be mapped from one type onto the other to add more dynamism.

The key feature missing in most compiled languages, I think, is they lack a runtime scripting language that is the same syntax as the compiled language.  IMO, if you find yourself "embedding" a scripting language then the core language has failed you as it lacks valuable features you need (namely, ability to compile code at runtime).  "I heard you like languages so I put another language in your language so you can program while you program", ugh, no, that's just shameful.   IMO, the optimal compilable roguelike language would include an embedded scripting language which is of the same syntax as the compiled "host" language.  Thus, enabling the compiled code and scripted code to transparently call from one into the other with no "wrappers" or "binding layer" required.

Scripting languages tend to become prohibitive to use for performance intensive computation when they're not designed to also be compilable.  With a compilable scripting language: A complex script running too slow interpreted at runtime?  Have it compiled at compile time rather than runtime, and it goes faster with no change to the code.  Want to quickly prototype a feature without recompiling a bunch?  Just implement it as a script, then have it be compiled later when you're done rapid prototyping.  This would also greatly assist in adding "mod support".

Think about it: A macro language is simply a scripting language that runs at compile time.  If you could link with the compiler's "macro facility" code in your program, presto you have an embedded run-time scripting language.  All the better if all three (compile time, macro, and runtime code) share the same syntax as the compiled language -- Unlike, say, C/C++ or Assembler preprocessor macros.  IMO, it's as easy as reusing the compiler's code: Make the compiler part of the standard library's API, and if it's used then the compiler's code gets included.  Having a design goal of (optional) compilation tends to improve performance over languages meant to be interpreted like Lisp and JS which suffer loss of language features to compilation constraints (I wish Lisp Machines were still a thing).  At least Lisp doesn't actively fight against being compiled (like JS, hence ASM.js & WebAssembly (sacrifice JS features to the compiler gods!))).  Additionally, having the goal of linking with the compiler (and potentially an interpretor) in programs helps keep the language lean and fast (design naturally tends towards less bloat then).

While there are many Lisp implementations readily available to extend, they're typically written in C.  And if we're going to do that, might as well just port a C compiler to the VM and be done (then you could compile your favorite Lisp on the C compiler for the VM and then code in Lisp on the VM).  An explicit VM meant to run only Lisp might be a worthwhile project, but the last time I tried designing a real CPU like that in VHDL (which you could fab or output to a FPGA), the chip grew so large that I couldn't afford a FPGA with enough gates on it to actually run it (maybe because I tried to implement a Lisp with lots of features rather than a minimal Lisp).

Of course a RL lang could ditch the VM underpinnings and just extend an extensible language like Haskel or Lisp, but I'm wondering more along the lines of what a from scratch RLLang syntax would be.

Of note, I think the @ needs to play a prominent role in a RLLang's syntax.  Perhaps it could be the "loop" symbol?

say "hello world" @( 1 ); // infinite loop while true of hello world.
[1 2 3 4 5] @ say( "count " #); // map over an array; outputs hello 1 hello 2 ... hello 5, # being a symbol for the value.

That or @ could be a valid identifier character, so that your player var could just be: @

In one of my VM's Assemblers @ is used to denote indirection (like dereferencing a pointer).  Here's a manual implementation of a switch / case block (jump table).

Code: [Select]
// Switch: /r0
up 2 /r0
add main_jt0 /r0
set main_jt0_max /r1
// jump to default case if value (reg 0) is beyond the jump table (reg 1)
jae /r0 /r1 main_jt0_default
set @/r0 /r0 // sets the value at reg 0 into reg 0.
jump /r0

// Jump table #0
:main_jt0
#inline main_jt0_0 main_jt0_1 main_jt0_2 //... snip
:main_jt0_max

// JT0: Case 0: syscall EXIT( status )
:main_jt0_0
// Set the exit status to user specified value, then die.
pop /r1
set /r1 @sys_exitStatus
pop /r0
err E_DEATH
...

Having ability to model the basic constructs like state machines is very good idea.
I tend to agree.  Support for state machines at the language level eliminates the headache of huge switch statements within functions, or forgetting to call state transition functions when change.

Another option I would like to have is relational or logic programming. Instead of describing an algorithm, I would describe problem domain and relations of things within it and let the computer to solve the problem.

That's pretty nifty.

I've done something like this before (at work) using neural networks to map and emerge a solution for abstract problem spaces, but I'm having a hard time thinking of how one would implement such things at the language level (rather than programming them).

An AI with weighted decision trees could do the trick.  Give the player the ability to access a sufficiently advanced AI agent -- in other words, let the player call upon the AI code that NPCs / enemies use to defuse traps, etc.  However, this might be best left up to the programmer rather than the language, since some programmers may prefer that traps be explicitly disarmed rather than have a "move cautiously" trap solver.  Perhaps I've also simply misunderstood your intent.

I find this topic fun. Perhaps if you ever end up building a custom chip for your new roguelike language, you could include the option to turn permadeath on, which would instantly fry the chip when you lose. Then you have to go solder another one in!

Coincidentally one of my virtual CPUs can enter an evil state of permanent death.  This VM assembly code documents the feature, it's status #13:

Code: [Select]
// Constantly recognized evils.  Encountered when misfortune has befallen.
#const E_BREAK 0 // Debugger demands a break from execution.
#const E_PERM 1 // Permission level not high enough.
#const E_INVALID 2 // Operator is lame or otherwise severely unwell.
#const E_EXEOFLOW 3 // Execution has overflown the stack.
#const E_EXEUFLOW 4 // Execution attempted below the stack.
#const E_NOMEM 5 // RAM's inventory is full.
#const E_ACCESS 6 // Thwarted an improper access attempt.
#const E_ALIGN 7 // Incompatible alignment for RAM.
#const E_UNKNOWN 8 // All attempts to identify this mysterious evil failed.
#const E_ENV 9 // Evil lurks in the environment beyond the skull's safety.
#const E_HALTED 10 // Can't run, but not dead yet; Just waiting for a spell.
#const E_PAROFLOW 11 // Parameters have overflown their stack.
#const E_PORTAL 12 // Engram unable to traverse the skull's portal.
#const E_DEATH 13 // Brain has permanently died, and can not be resurrected.
#const E_NOSUPPORT 14 // Feature known but abandoned (hopefully temporarily).

While I can force a hard reboot of the "hardware", I haven't discovered a way to fry a chip in VERILOG / VHDL (hardware definition language).  I guess i could include some NVRAM on chip, then set that to "permadeath", and check the non volatile storage to see if that's been set before executing any instructions...  I do accidentally fry a motherboard sometimes working with custom PCI boards and/or badly wired custom peripheral devices.  For this reason it's recommended that in-development devices be attached only to a separate controller board not on a port integrated with the mainboard since it's far cheaper to replace a fried expansion card.  So, it would be trivial to make an off-chip facility for the feature, however impractical that may be.  One could supply an optional serial or parallel device which responds to permadeath messages by suicidally dumping a bank of capacitors onto the system's IO bus, frying your computer when your game is over.

After sleeping a bit, I think I have another perspective to this. Maybe we should look from point of view of primitive, means of combination and means of abstraction too? They're the basic parts of pretty much every programming language and the essence that really tells them apart.

Primitives: the usual suspects like numbers, text, lists and such are obvious. But since we're talking specificially roguelike language, should there be higher concepts here too? Like character/creature, item, coordinates, action, ai-routine and such? If they're very detailed, building different kinds of roguelikes might be tricky, but if they're too vague they're maybe not so useful? Code is one primitive too I would say. Things like lazy and infinite datastructures and asynchronous operations would be nice to have built into language.
[snip]
Yeah, that's the stuff!  One way to implement a "code primitive" would be to make them special literals of the type "script" which can be executed by a run-time interpreter.  The difference between a String and a Script would be that a Script has been lexicographically analyzed, tokenized, and potentially parsed into a syntax tree (or bytecode) and is ready for interpretation.  Scripts that are dynamically linked could be fetched and processed at initialization time.  The RL lang would need a standard function (or constructor) that created a Script entity from a plain string.  This would allow compiling and running of arbitrary text strings at runtime.  One could then implement an optional "console" feature simply by executing user input (like an eval() function).  However, due to the nature of roguelikes (and games in general) it would be useful to have a way of specifying the scope available to a piece of runtime code, as well as which API functions can be accessed.

I've often wished I had the ability to write small scripts to search inventory or perform complex repetitive actions...  My terminal emulator has sufficed for most games, but an in-game console would potentially enable far greater control and script-ability.  With the right data in scope players could even code up an AI / bot to play for them via such "user script" features.

The question of some primitives would be, what characteristics / properties should roguelike primitives have.  E.g., a "generic RL item" has: brief text, detail description, possibly text for when its not identified, a symbol (or tile#) to display when it's visible on the map, etc. If it can be equipped, to what slot, what "action" uses the item, etc.

Of course a base "RL Item" could be part of the RL lang's standard library.  Coders would be free to implement their own Item, but we could provide a basic item and inventory implementation to extend if they so desired. The trick of good language design is not to provide everything everyone could ever need, but to provide some useful things to some people, and allow those with specialized needs to roll their own.
« Last Edit: April 13, 2016, 10:42:20 AM by Skullcoder »