OO is fundamentally about messaging not abstract data types. So if you start your design from trying to determine ADTs instead of trying to work out the kinds of message conversations that need to go on you end up with mess of types.
In the wizards and warriors we see a large amount of modelling of a domain with rules. But we, even at the end, still have little idea of what messaging is going to be going on, there is a hint about attacking werewolves. But we've already made a lot of assumptions about what our first class entites are. Often what can happen is we find, while we thought we modelled it ok and even sorted out a rule based system, it still seems difficult for our objects to have conversations.
maybe what would be better is some thing that can attack other things and wizards, weapons, and warriors are all simply modelled by some composable set of stat modifiers.
Sadly when confronted with a problem, the majority of programmers can get no further than writing down lots of nouns, perhaps dozens of them, with no idea how to combine the few actually required into an object-oriented design.
OO isn't fundamentally about messaging. That is incorrectly assuming that object-orientation began with Smalltalk, Alan Kay and Dan Ingalls. Smalltalk's "messaging with objects, all the way down" approach largely ended with its popular use and the widespread adoption of Java. Java's object model is entirely based on Simula (James Gosling Sept 2017).
Technically the latter is about object instances, the former about object/class (data/domain) modelling.
When I'm stuck on a software design problem, pick some random part of the program and see what happens if I make it first class.
In this case, Eric takes the game rules and turns them into objects. (Essentially the Command pattern[1], which is close to my heart[2].)
You can go overboard with this, of course, but I've found time and again if it seems like I can't get my code to hang together, it's usually because I'm missing a noun — a reification of some part of my problem that I can pass around and do stuff with.
[1]: https://en.wikipedia.org/wiki/Command_pattern
[2]: http://gameprogrammingpatterns.com/command.html
PS: Another solution to Eric's initial problem with warriors, wizards, swords, and staves is to be more precise about what capability Player has. If Warriors can only wield Swords and Wizards can only wield Staves, then it's not the case that Player's Weapon field can be set with any weapon.
So one option is to make Weapon an abstract getter in Player. Then add setters and fields in Wizard and Warrior for the specific types. If all you have is a Player, you can see what their wielding, but not change it. Then, to wield something, you need to know what kind of player you're dealing with first.
Of course, that doesn't scale very well to lots and lots of business rules as in later in the series. But it works if you have a relatively small number of constraints in your subclasses — you just push them up such that the superclass API only exposes the intersection of all of the subclasses' allowed operations.
Also, since Bob is too humble to say it - http://gameprogrammingpatterns.com is an amazing architecture resource for programmers in all disciplines, not just gamedevs.
He's got another book called http://craftinginterpreters.com/ that I've been eager to check out.
On the topic of state machines, I totally agree with the observation that games are a prime example of their usefulness. I've been working on and off on a relatively simple game that uses a Lua scripting backend to implement the game logic, and over time I've been refactoring this particular part many times, slowly converging to a solution resembling an event driven state machine implemented using reactive programming techniques. It's very interesting to see how even a very simple game already forces you to either apply concepts like state machines, events, etc, or end up with horrible crappy code that is hard to debug and not fun to work on.
What i would do is simply have Item (base for Weapon, Potion, etc) have a "IsUsableBy(Player p)" which returns true in the default implementation (which would be the case for the majority of items) and the objects that have special considerations check the player class themselves.
Similarly an Animal (or Entity or Organism or whatever) class would have a "OnDamage(Animal source, int damage)" that by default calls -say- "ApplyDamage(int damage)", but special animals - like the werewolves - may want to filter the result so that the damage is doubled when they are on holy ground or when the class of source is Paladin.
Also the IsUsableBy and OnDamage functions could check against Item objects in an inventory (e.g. Item could also provide a "FilterDamage" and "VetoUsageOf" so that an HolyCross can multiply all damage from Undead enemies by 0.25 but veto using any UnholyItem based class - truth be told here, you probably need a tag system for some of that stuff instead of relying on classes alone) and on an active Effect list (would also be able to do the same sort of vetoing and filtering and the Animal class could also provide a HasEffect method for use by the other classes to make decisions about - e.g. a PaperDoll might refuse to talk to you if you have the OnFireEffect :-P).
To me that sort of setup feels more natural and intuitive than the user-command-state-rule stuff mentioned in the article. You can't really generalize it in a way that applies to other things (e.g. a MagicSpell would need its own set of functions and such) but i believe that there is a thing as too much generalization.
(note that the above are when talking about the common Java/C++/C#/etc like OOP languages, in something like C i'd go with a more data and/or script driven approach...)
int a = 1;
int b = 1;
int c = a + b;
And we have the option of writing, alternatively: class Integer { ... }
class Operation { ... }
class Expression { ... }
Or something 'meta' like that. The upside is that it's flexible, powerful, expressive to turn code into data. The downside is that it's slower, and there's a whole extra system to maintain.However, in my experience as a game developer, past a certain large size, pretty much all systems seem to want to converge to Lippert's solution. You want to be able to put the entity data and the rules into tables, and not have to edit code to change the game rules. For small games, not worth it. But for large games, and also for game engines that aspire to be generic, it makes everything a lot easier.
Granted, the author may have taken it a little far, but the sentiment is right.
I also think trying to squeegee it into the language's type system is the wrong approach (for this specific problem, I mean - I'm not saying there aren't cases where doing it in the language is useful) purely because we're going to want the art team to add more monster types and player types, and to be able to change the rules for attacks and damage and soforth quickly on the fly to try out new game mechanics. We're going to want it all in data very soon, so it'd be better to just jump straight to a data-driven design.
Generally if you see mod tools for existing games (e.g. Skyrim) you'll see that the engine does have inherent support for the base classes (Weapon, Armor, etc) but it relies on data for the specific items, so what i describe above can be the engine (gameplay code) side of that.
Objects, particularly highly sophisticated objects, generally have minimal requirements that determine who can use them successfully. Requirements can be anything from strength to knowledge. At the same time people have skills (or don't have skills) and attributes which lets them use objects. Some people are more skilled than others.
Modelling this requires both the ability to ask an object "What are the minimal requirements needed for somebody to use you?" and the ability to ask a player "How skilled are you really at using this object?"
As a smell test, it doesn't make much sense to ask a sword "can I use you?" How would the sword know? But you can ask the sword, say, "how heavy are you?" It does make some sense to ask a person "can you use this sword?"
And yes, it's not clear at all that every object type corresponds to a first-class language type. You might have a Sword type... but in reality, unless the game is very sophisticated, all bladed weapons do pretty much the same thing. This is where it's useful to take a step back and focus not so much on nouns but also on verbs in our domain language. Swords, axes, ninja stars -- they are all cutting damage. Spears, knifes, polearms -- they are all stabbing damage.
Does it really? I mean, if we're talking about an actual person then sure, since a person can think about the problem and come up with a creative answer. But the object representing a person in a game? That would imply that the "person" object must know all about swords and the requirements for using each of them. How is that better than requiring the "sword" object to know about people and their capabilities?
Swords have attributes (e.g. weight). People have attributes (e.g. strength). Whether a particular person can wield a particular sword is not a question either a sword or a person can answer without introducing unnatural dependencies. It is a property of the environment in which the player and sword both exist, and IMHO is best modelled as some form of external "rule" object, or via a multiple-dispatch method (if your language supports that paradigm).
If anyone is curious and would like to see a practical demonstration of this data-oriented approach of modelling problems, I highly recommend this talk from Mark Bastian in Clojure/conj 2015: https://www.youtube.com/watch?v=Tb823aqgX_0
In it, he contrasts the data-oriented modelling approach with a traditional OOP approach for implementing a board game, and was able to come up with a complete implementation of the game with the former approach that was shorter than even the structural boilerplate for the OOP version.
Another related talk that I loved was Chris Granger's 2013 talk on Light Table: https://www.youtube.com/watch?v=V1Eu9vZaDYw
Where he walks through his process of building a game in ClojureScript using an Entity-Component-System architecture, which is very well suited to this data oriented modeling approach.
(Now it’s mostly Annotation Oriented Programming, especially with Spring. Which brings its own difficulties.)
Show us some code, how would you solve it with functions and data?
data Player =
Player (Maybe Weapon) Class
data Weapon =
Sword
| Staff
| Dagger
data Class =
Warrior
| Wizard
type Error = String
mkPlayer :: Maybe Weapon -> Class -> Either Error Player
mkPlayer (Just Sword) Warrior = Right (Player (Just Sword) Warrior)
mkPlayer (Just Dagger) Warrior = Right (Player (Just Dagger) Warrior)
mkPlayer Nothing Warrior = Right (Player Nothing Warrior)
mkPlayer (Just Staff) Warrior = Left "A Warrior cannot equip a Staff"
mkPlayer (Just Staff) Wizard = Right (Player (Just Staff) Wizard)
mkPlayer (Just Dagger) Wizard = Right (Player (Just Dagger) Wizard)
mkPlayer Nothing Wizard = Right (Player Nothing Wizard)
mkPlayer (Just Sword) Wizard = Left "A Wizard cannot equip a Sword"
A player is always a defined class(wizard or warrior), but they may not have a weapon equipped. This solution is a bit wordy, but comes with the benefit that if you ever add a new weapon/class, the compiler will scream at you if you haven't handled the case for it properly.You would only export the mkPlayer function in the library and you could potentially have much fancier error handling, such as building a data structure that contains an 'invalid' player anyways (e.g. `Left (Player (Just Sword) Wizard)`) so you can custom build an error message at the call site ("A $class cannot equip a $weapon") or even completely ignore the error if that is a potential usecase (such as building an armor/weapon preview tool, where you don't care whether they can use the weapon/armor).
Modifying it is pretty easy too. Say I wanted to allow for 2handed weapons, plus offhand weapons (shields, orbs, charms, etc.) I could encode that in a data type like:
data EquippedWeapon =
TwoHanded TwoHandWeapon
| OneHanded (Maybe OneHandWeapon) (Maybe Offhand)
| Unequipped
and swap it into the Player definition: data Player =
Player EquippedWeapon Class
And now I wouldn't be able to compile until I fixed the mkPlayer function and any other place that uses a Player and is dependent upon the weapon portion of the data structure.e.g. This function wouldn't need to change
areYouAWizardHarry :: Player -> Bool
areYouAWizardHarry (Player _ Wizard) = True
areYouAWizardHarry (Player _ _) = FalseI'm not a Haskell programmer, but I understood it. It looks like an ML language but with a lack of | and * for guards and tuples. I like your solution a lot.
The main features which allows you to code this solution in such a safe way are the Maybe and Either types. It's high time OO programmers - and OO programming languages - learn the lessons FP languages have taught us and include these constructs in the standard library. They're just so much cleaner than the usual alternatives (nullable types, checked exceptions) and there's no reason they can't be defined as small objects.
I've tried to learn rust a few times, but never with much tenacity. It's on my list because it seems to hit a good point wrt expressiveness and performance.
In oversimplified terms, Rust has objects but not classes. It skews more toward: - from a C dev's perspective: data-driven design - from a Haskell dev's perspective: typeclasses and ADTs
If so, is that a practicality issue, or an Haskell limitation?
But better yet, it certainly does have the big guns which you can pull out.
-- Just like before, we define `Class` and `Weapon`:
data Class = Warrior | Wizard
data Weapon = Sword | Staff | Dagger
-- The one really annoying thing is that
-- at the moment you have to use a little bit
-- of annoying boilerplate to define singletons
-- (not related to the OOP concept of singletons, by
-- the way), or use the `singletons` library. In the
-- future, with DependentHaskell, this won't be necessary:
data SWeapon (w :: Weapon) where
SSword :: SWeapon 'Sword
SStaff :: SWeapon 'Staff
SDagger :: SWeapon 'Dagger
-- Now we can define `Player`:
data Player (c :: Class) where
WizardPlayer :: AllowedToWield 'Wizard w ~ 'True => SWeapon w -> Player 'Wizard
WarriorPlayer :: AllowedToWield 'Warrior w ~ 'True => SWeapon w -> Player 'Warrior
This last part shouldn't be to difficult to understand, if you ignore the SWeapon boilerplate: Player is parameterized over the player's class, with different constructors for warriors and wizards. Each constructor has a parameter for the weapon the player is wielding, which is constrained by the type family (read: type-level function) named AllowedToWield.AllowedToWield isn't that complicated either, it's just a (type-level) function that takes a Class and a Weapon and returns a `Bool` using pattern matching:
type family AllowedToWield (c :: Class) (w :: Weapon) :: Bool where
AllowedToWield 'Wizard 'Sword = 'False
AllowedToWield 'Wizard 'Dagger = 'True
AllowedToWield 'Wizard 'Staff = 'True
AllowedToWield 'Warrior 'Sword = 'True
AllowedToWield 'Wizard 'Dagger = 'True
AllowedToWield 'Wizard 'Staff = 'False
And there it is. What do you gain from all this? Something which it is very had to get in certain other languages: compile-time type checking that there is no code that will allow a wizard to equip a sword, or a warrior to equip a staff.Once again, I want to make it clear that you absolutely don't need to do this, even in Haskell. You're absolutely allowed to write the simple code like in the parent post. But in my opinion, this is an extremely powerful and useful tool that Haskell lets you take much further than many other languages.
So long story short, the answer to your question is that it is indeed a "practicality issue", although I don't think that my code is that impracticable. It certainly is absolutely not a Haskell limitation: in fact if anything, Haskell makes it a bit too tempting to go in the other direction, and go way overboard with embedding this kind of thing in the type system.
mkPlayer :: Maybe Weapon -> Class -> Either Error Player
mkPlayer (Just Staff) Warrior = Left "A Warrior cannot equip a Staff"
mkPlayer (Just Sword) Wizard = Left "A Wizard cannot equip a Sword"
mkPlayer weapon klass = Right (Player weapon klass)Basically, I'm one of those people who might unironically write `class DefaultDog implements Dog extends AbstractAnimal`.
Oddly Eric never mentions ECS or SoA in the series, even though it solves the problem. (He only suggests in part 5 to use a Rule Engine, instead of inheritance)
protocol Weapon {}
class Staff: Weapon {}
class Sword: Weapon {}
protocol Player {
associatedtype T: Weapon
var weapon: T { get set }
}
class Wizard: Player {
var weapon = Staff()
}
class Warrior: Player {
var weapon = Sword()
} trait Weapon
class Sword extends Weapon
class Staff extends Weapon
class Dagger extends Weapon
trait Player {
type T <: Weapon
}
class Wizard extends Player {
type T = Dagger | Staff
var weapon: T = null
}
class Warrior extends Player {
type T = Dagger | Sword
var weapon: T = new Sword
} class Weapon: pass
class Sword(Weapon): pass
class Staff(Weapon): pass
class Dagger(Weapon): pass
class Player:
weapon: Weapon
class Wizard(Player):
weapon: Optional[Union[Dagger, Staff]] = None
class Warrior(Player):
weapon: Optional[Union[Dagger, Sword]] = Sword() class Wizard extends Player {
type T = Dagger | Staff
var weapon: T = null
}
The billion-dollar mistake in a new language? What a waste.class Chair: Furniture, Weapon, Firewood {}
warrior can also use a staff
both can use daggers
Eric Lippert is smart, I love his Stack Overflow answers.
The first post talks about the difficulty of getting compile time errors for Wizard wielding a Sword
The last post completely abandons the notion of statically checking anything about the Weapon assigned to a Player(beyond the most general case of is Player and is Weapon).
I don't actually have enough experience with Erlang/Elixir to know but I thought their function overloading (http://raganwald.com/2014/06/23/multiple-dispatch.html) still provides multiple dispatch with compile time checks?
The proposed Rules solution is more elegant to use(for the programmer, if not the person defining the Rules) but it does nothing(or at least very little) to help you check that the business logic is correctly encoded. Yes, you're likely going to avoid your program crashing, but that's just because you've given up on encoding the business logic entirely. At least where compile time checking is concerned.
Maybe I'm expecting too much of the type system and that's the actual lesson of the blog posts.
If given
1. Player can have a Weapon
2. Sword is a Weapon
3. Wizard is a Player
The from the statement "Wizard cannot have a Sword" follows a contradiction that "Player cannot have a Sword".
Does anyone know a good code example (preferably C# but anything similar would do) of a rules system like what he describes in the final part?
NRules is possibly what you're after, and maybe http://www.antlr.org/ to parse rules.
I am suspicious any time I see inheritance in my C# code base. It's rarely needed and causes a lot of pain when used extensively. I'm looking at you Credit Note and Invoice!
State machines are used extensively by HDL hardware design tools, and are easier to debug.
* A Sword is a type of weapon.
* A Fire Sword is a type of Sword.
* A Mace is a type of weapon.
* A Mace of Ice is a type of Mace.
* A Sword of Ice and Fire is a type of Sword.
A common problem I see in object-oriented design is:
A wizard is a kind of player.
A warrior is a kind of player.
A staff is a kind of weapon.
A sword is a kind of weapon.
A player has a weapon.
But before we get into the details, I just want to point out that I am not really talking about anything specific to the fantasy RPG genre here. Everything in this series applies equally well to Papers and Paychecks, but wizards and warriors are more fun to write about, so there you go.
OK, great, we have five bullet points so let’s write some classes without thinking about it! What could possibly go wrong?
https://ericlippert.com/2015/04/27/wizards-and-warriors-part...
https://ericlippert.com/2015/04/30/wizards-and-warriors-part...
https://ericlippert.com/2015/05/04/wizards-and-warriors-part...
https://ericlippert.com/2015/05/07/wizards-and-warriors-part...
https://ericlippert.com/2015/05/11/wizards-and-warriors-part...