Squidi.net Sites:     Blog   |   Webcomics   |   Three Hundred Mechanics   |   The Blind Mapmaker   |   iPhone Apps   |   Free Pixel Project
 

  Additional Blueprint Considerations

I've been thinking about my Blueprints system recently and had a few half thoughts that I need to work through. So this musing is less about teaching something and more about organizing my thoughts. Expect it to be rambling and perhaps a little crazy. One thing it won't be is lupus. It's never lupus.

 

  Internal States?

When I implemented my Blueprints system in Objective-C, I ran into a few logical errors when it came time to master the blueprints, as well as a late design change that didn't work very well, involving making Blueprints into expressions so that that could be used as atomic elements. What it basically boils down to is that I did not properly plan out how blueprints would maintain an internal state, such as variables and even properties.

Essentially, when mastering a blueprint, I basically just copied it and then did all the work on a copy. This was not an optimal approach, as I needed to add in functionality that determined whether a Blueprint was a master or not. Ultimately, the idea was that blueprints could be mastered through other blueprints, like (masterBlueprint LargeDragon) returning something that could be used interchangeably with the more read only LargeDragon blueprint. A master is essentially just a blueprint which masters to itself. Obviously, this is confusing and doesn't work all that well in practice.

What I'm considering is that a blueprint master would be a separate, distinct object that could pose as a simple blueprint in a pinch. A master would just have the mastered properties without any of the internal state information such as private properties, functions, of variables. One could obtain the non-master blueprint through request if, for example, you wanted to call some functions from it.

The problem is that when it comes to an internal state, this requires basically a middle step between blueprint and master. If you think of a blueprint as an object class and the master as immutable final object, there would need to be a mutable object in the middle that would maintain the internal state needed to go from one to the other. While this would be possible, this intermediary step would not be accessible to expressions. You wouldn't be able to change this object from within the code, such as setting variables or calling functions that would change them.

So, my mind wanders to the possibility that maybe an internal state is not strictly necessary. Things get really messy when you factor that in, or even the possibility that one property can reference another like propA = propB + 4 (what if the two properties reference each other? The mastering process would get stuck in a loop). The original intent for blueprints is simply to provide a mechanism for defining procedural inputs. I think you can make properties completely independent and still retain nearly all functionality and virtue of this system.

But I'm not totally sure. All the examples I can think of work fine, but these are potentially very simple problems. But maybe it is okay for blueprints to only solve simple problems. Maybe aim for a general purpose, simple system that works most of the time rather than essentially creating a full object orientated programming language that simply adds an extra step at the end.

 

  Generator-called Functions?

One of the things I had considered for blueprints in the past was the ability for generators to call functions on the blueprint in order to perform actions. In general, a blueprint can provide either a single object or a set of objects. When you provide the single object, the blueprint has total control over how it is selected. If you provide a set to a generator, the generator would be in control over how objects were selected from it.

Imagine that you have a generator that lays out the furniture in a room. A blueprint could easily provide a set of furniture objects that the generator could place in the room, but would not be able to say that bunk beds are 40% more likely to be chosen than four-poster beds. My idea is that the generator could, instead of making the selection itself, call the function "selectBed" on the blueprint master. This would return a bed appropriate to the particular blueprint.

There are many reasons why this would be a good thing, but primarily the reason is that blueprints are supposed to represent a particular genre of output, and outsourcing such things to blueprints rather than making assumptions within the generator makes blueprints just a little more capable and a little more powerful.

The problem, of course, is that if blueprints have no internal state, these functions can not rely on things like persistent variables or sets of objects. You could not, for example, keep a list of objects in the blueprint and then select and remove objects every time the function is called. All data must be passed to the function (it can't even access the finalized properties!).

But that might be okay since the blueprint expression system is modeled after Lisp, a functional language. Functional languages are designed to not have internal states and for all data to be passed and returned. This creates a cleaner language with less bugs since the scope of every function is, by definition, no greater than the immediate function.

So for functions designed to be called by generators, we can just pass the data as needed and return the changed data. For instance, "selectBed" could take a set of furniture as a parameter and then return both the selected bed (or even a set of selected bed) and then the potentially modified set of furniture. So:

(bed, furnitureSet) = selectBed(furnitureSet)
(Obviously, the generators wouldn't be written in the lisp-like language and would have to manually check the result to see if it is a list of values or whatever and change its internal furnitureSet manually, but I don't think that's going to be more than a few lines of code).

 

  Validating Generator Inputs

I realized that you could use the expression system to validate the input to generators. Basically, you define a generator in much the same way you define a blueprint (meaning it can also be referenced by blueprints). Then you create a validator for every potential input which basically just checks to see if the value is in the correct range.
@generator DungeonFloorLayoutGenerator
    @input(number) numRooms = (and ($val > 2) ($val < 10)) @input(set) furnitureSet = (not (emptySet? $val))
@end
In this example, the DungeonFloorLayoutGenerator is expecting a blueprint master with a number property named "numRooms" that is between 3 and 9, as well as a nonempty set of furniture. Before passing a blueprint master to a generator, you can simply validate it to make sure that it will produce acceptable values. Additionally, you can allow for optional inputs and provide default values in case it isn't there.

Ultimately, I'm not sure how absolutely necessary validating input is. I guess it doesn't hurt to have extra error checking and the way expressions are done, this is really not particularly difficult to add. Plus, once generators are referenced from within the blueprint system, additional functionality might come to mind, like being able to query generators for values, random generator selection, or even generator inheritance.

Truth be told, I haven't really thought much about how generators would be implemented within the blueprint system. The more I think about it, though, the more I believe that it should probably be an object within the blueprint runtime.

 

  Generators and Random Seeds

Every blueprint should have two required properties. The first defines what generator it will be passed to and the second should be the random seed.

For the generator thing, that's not too complicated to understand. Mastering a blueprint is necessarily aimed towards a specific generator (or hierarchy of generators). You aren't going to randomly create properties and just hope for the best. Making it an expression means that the blueprint can select which generator it uses, even at runtime. For instance, you can create a set of all generators that are descended from DungeonFloorLayoutGenerator and then select one at random or specify one (BigRoomsAndLongHallwaysDungeonFloorLayoutGenerator).

The random seed is something that is implied. Basically, I want blueprints to be able to create a class of inputs, even if that class of inputs is a single value. Putting seeds as an expression allows us to more tightly control how objects are created. We can pick one of a set of values or simply use a random value at the time of mastering. While this control is nice, need to be careful to keep the process deterministic. Mastering a top level blueprint with the same seed should produce the same output, even if that output is a bunch of other blueprints with random seeds. So random seeds can't be based on ticks from start up - they need to be based on the previous seed.

 

  Pass Down Variables

While I'm leaning against the idea of internal states, there is at least one area where I think it could be useful: passing information to a blueprint to control how it is mastered.

Let's say you have a variable called $difficulty which is a number between 1 and 100, where 1 represents the beginning and easiest part of a game and 100 represents the game at full difficulty. You could use the same blueprint to create every level, just incrementing $difficulty as you go, to create a specific progression through the game. Other examples include selecting which quest item a particular quest will yield, or controlling the overall theme of a level by passing down a particular set of objects that can be used in it. In some cases, you are just going to want blueprints that can changed based on input.

Rather than using an internal state, I think a more appropriate course of action would be to simple keep these variables external and pass them in during the mastering process. These variables could be global (such as $difficulty) or they could be specific to a blueprint, created specifically for that unique mastering process (set of furniture). These variables should be read-only and should only be referenced.

Again, I'm not entirely sure here. Maybe an internal state would be useful. I don't have a particularly good system to control blueprint inputs except other blueprints, which means that you'd be creating blueprints to create inputs to create blueprints to create inputs to generators. Perhaps the best solution is simply to not bother with passed down variables and just create unique blueprints (EasyDifficultyLevel, MediumDifficultyLevel, HardDifficultyLevel) and pick form them.

Something to think about.

 

  Sets or Lists?

Finally, the language I've chosen is based on Lisp, and therefor, the main data structure for everything, from structures to expressions to evaluation, is a list. However, the main data structure used to do all this fancy procedural stuff is a set. List and sets are very similar and you can switch between them at will in most cases. But lists are ordered and may contain multiple versions of a unique value. Constantly switching between lists and sets could lose this information. A sort of structural rounding error.

In the blueprint system I implemented, I had a special format used to create automatic keyword sets. Basically, you'd have brackets (instead of parentheses), the key word type, and a list of keywords that were anded together:

[type: small !fat]
Would return a list of all the blueprint with the type-keywords small and not fat. The set is created automatically, but to be used in the blueprint expression system, it had to be converted to a list. If you wanted to do set operations, these automatic sets would be converted to lists, then back to sets for the operation, then back to lists.
(intersectSet [type: small] (unionSet [hair: red] [hair:biege]))
This would start by creating automatic sets for red hair and beige hair, turning them both into list expressions. Then the unionSet function would attempt to convert them both to sets to find their union. Then convert that result to a list, only to convert it back to set in order to find the intersection with the set-turned-list-turned-set for small type.

Obviously, this is doing a lot of conversion that isn't required or even desired. I'm wondering if maybe I can't make sets into a first class citizen and remove it from the list aspect altogether - a sort of separate but equal hierarchy of data structures and functions. It's still useful to be able to use lists and sets interchangeably, so maybe the solution is simple to keep it as a set as long as possible before converting it to a list. Maybe keep everything in brackets such that it would only be converted to a list at the very end.

[intersect [type:small] [union [hair:red] [heir:biege]]]
The result would be a list, but created through a bunch of set operations that never touch lists. If you wanted to convert a list back into a set, you'd have to do it explicitly:
[intersect [b, c] [set (a, b, c)]]
I don't know. Could work. I'm just looking for a more set-centric way to manage the language. While lists are the primary data structure of the language, sets are the primary data structure of what the language is doing, so I think making sets a first class citizen is almost a requirement. Just not entirely sure how I'm going to do it yet.

 

 





Copyright 2009 Sean Howard. All rights reserved.