Okay, so last night I came up with this idea for a CCG (Well, I'd been thinking about it for a while, but last night I finally hashed out enough of the rules that I felt making some sort of demo was in order), and I figured it might be a fun project to try and make a playable version of it on the computer as my prototype. It'd help a lot with playtesting and might (as a consequence) force me to make a structured database of the cards that I tentatively picture being in the first set.
However, when I sat down to start designing the game, I ran into a problem that I hadn't thought of. In any CCG, some cards can have extremely complex rules: they may have actions that trigger based on events in the game, actions that are activated by the user allocating resources, and even permanent actions that should take place for the entirety of the time that they're in play.
My first thought on how I'd make the game was to make a database of cards and their actions, but as the design went on it ended up being a gigantic and cumbersome web full of specialized niches for specific card examples I was imagining, but it would've been utterly unable to handle any generic card thrown at it.
Then I thought about Magic: The Gathering Online. Surely they ran into the same problem. What did they do? Searching the 'net has led nowhere thus far as to finding out how MTGO might have done it, and though their system is flawed in some aspects it's also impressively consistent and usable.
I have two guesses as to what they might've done, and neither might actually be it:
1) They have some kind of advanced parsing engine that parses the English text of each card and react to it as needed. (Though this would break down for cards with complex rules such as the Ink-Treader Nephilim or old cards that had inconsistent wording, so I'm not confident that this is what's happening)
2) They have some kind of scripting engine or plugin embedded into the game (perhaps on the server side), and each card has a script that programmatically details what's to be done in various situations (in response to game events, activated by user, etc...). This seems more likely than (1), but it also seems like this would be exceptionally cumbersome (having to explicitly program the features of every card). Also, how would they get the scripts incorporated in the game? Calling script in a compiled program is a subject that I'm unfamiliar with... I know it's done, but I don't know *how* it's done.
TL;DR
I'm looking to have a brainstorm session about the best way to go about enforcing rules on a card in a CCG. The ways I've thought of don't seem ideal, and I'm at a loss for other alternatives.
Posts
In The Sims, the characters themselves have a very limited AI set - they basically just have a series of needs - and yet they can interact in a massive number of ways with all sorts of objects - furniture - that can be dropped into the game. What's more, the game easily allows for more furniture to be designed and included post-release without having to rewrite all the game code, just small snippets of code for each new piece of furniture. The way the designers made the game this flexible was to load the furniture rather than the characters with all the AI information. So you have these characters that are basically about as complicated as a single-cell organism with needs at varying levels of importance and then you have all this furniture that advertises it's capabilities to the character. If the furniture has what the character needs most at that time and can satisfy it better than other nearby furniture the character is compelled towards it and then upon interacting with it, the furniture deals with animating the character and so forth.
Your problem is pretty different, but I think this context-sensitive approach is probably the neatest way to go. Less cumbersome having to write the main game code to second-guess every eventuality and will probably run a lot faster as well.
So the key would be to have a robust enough API so that the "card rule scripts" would be able to affect other cards in the game and the state of the game itself... is that how it works in the Sims? Like when you create furniture, do you have to write a script (that uses some specialized Sims API or something) to reveal its functionality?
I guess I could go look it up... this definitely seems like a point in the right direction though, thanks Szechuanosaurus! I'll report back with what I find.
EDIT:
http://www.gamedev.net/columns/events/coverage/feature.asp?feature_id=51
I found this link, which doesn't give the details about how scripting was done (on a technical level) in the Sims 2, but definitely verifies that the interactive objects use some sort of exceptionally robust script to represent each object.
This is making me think that perhaps instead of thinking in terms of a database with scripts just for the actions, perhaps I should be thinking about making each card a full Script object. Originally I was thinking that the database would hold common or non-complex information, such as resource costs to play cards, flavor text etc... but perhaps what I could do instead is centralize all the information for an individual card into one script file that had all of that.
Hmm. I'll need to think about it further... any more information that anyone finds/can contribute to the conversation would be awesome, thanks again!
I suppose if you have a card that modifies one or more cards or rules then you need to have that card tell the other cards or rules that they've been modified rather than having the cards and rules ask if they are being modified.
It's like doing hit detection in an action game. Sometimes it easier to tell an object when it's been hit rather than having it constantly check if anything has hit it.
I only really understand scripting from ActionScript in Flash, but what I'd probably think about doing is take an object oriented approach to actions. There'll be lots of common actions that just differ in the values fed into them, so all monster cards can use the same attack script kept in one place and the card just passes it's attack and defense values to the script that process them when it needs to attack. So yeah, the cards would hold all the raw values applicable to them but the scripts that process those values would be kept in a central place for easier editing. Which is kind of how it works when you play MtG for real, when you thin about it. You and your opponent know all the rules and how to handle encounters and values but you don't actually do any of that until a card is played and you can read the values off it.
Hmm... that's true! I hadn't thought about it that way (thinking about how you play MtG in real life in terms of scripts).
Aye... so if I can find a scripting language that perhaps supports inheritance or something like that, I can make some base "card" objects that perform all the common actions... and then have the scripts for individual cards just have what differentiates them.
You bring up a good point with a card needing to tell another card that it's modifying said target. I've never really used external scripting in any programming I've ever done before, so I need to research it more... but I imagine all of this could be possible. The common API could have some kind of querying capacity to retrieve one or more cards that match certain criteria (such as a user selecting it as a "target", or finding all the cards of a certain type that are controlled by the opponent, etc...), and then the cards themselves would have to have methods that would allow another card to modify them (or even associate the two together... like one card "owning" another, such as in Magic when you equip an artifact or place an enchantment on a specific card. Hmm....).
I'm going to need to look more into how scripting languages can be incorporated into a program for this kind of purpose. Does anyone have any good resources on this? I'll start looking on my own as well.
EDIT: Hmm... I see Microsoft is going to make this annoying to do in a CLR language. Apparently their native scripting solution (Vsa) was deprecated some years ago. I'll keep reading, maybe there's another solution... I'd definitely rather not try to hack my own solution here. The problem's become complex enough as it is.
EDIT 2: VSTA! Apparently Visual Studio Tools for Applications provides a scripting engine that has full .NET Framework support. This could be what I was looking for. Must... read... more. I found a little teaser video about it, looking for some kind of tutorial/reference/something more substantial as we speak.
It has a lot of neat tools for evaluating commands 'in the context of' certain objects. It also has some nifty on-the-fly re-classing and method definition things that can be pretty cool.
For example, you can make a 'card' class with all the usual functions. Then, if you equip something to it, you can have the equip command do an instance_eval that defines a public method. Your right-click context menu can simply list every public method available to the card, perhaps passing through a 'rules' object, that filters out things that are impossible.
So, for example, if I want to give something the ability to instant-speed poke for one: and then your context menu:
Hope that makes sense.
Also, since everything is an object in Ruby, and objects all have an object_id, you're able to specify easily 'that prodigal sorcerer, not this one' (that's probably what the find_in_gamestate mock-up method I've written will do, return the object such that self = object.object_id.
Yeah, sorry, way ahead of myself.
Come to think of it, aside from all the display bits, all this business logic wouldn't be too terribly difficult to write. If you can handle the display elements, maybe I could contribute!
Hmm, interesting! I'll definitely look into Ruby. I've been meaning to learn Ruby for some time now, but never had a side project in mind that'd get me to actually figure it out.
I like that you can programmatically refer to methods (as if they were objects themselves... are they in Ruby? Interesting idea!), that would allow for a very different way of looking at it than I had previously considered.
Is instance_eval a method provided by Ruby itself? What's it do? Hmm... perhaps I should start reading up on Ruby.
As far as the actual development itself goes, it could be a good collaborative project... though part of me wants to "maverick" this so I actually know what's going on (and hopefully apply the concepts/technologies I learn along the way to other projects later). It's been a long time since I've been excited about a non-work related project, and I want to see where it takes me.
The last time I got excited was when I made BabelFish for WoW, which was a simple mod but I felt good about it... and then when I joined up with the guys that made the MorseCode and LeetSpeak mods to make a single universal interface, they ran with it (adding many really neat features) and did a great job: but by the time it was done I had no idea what was going on in the program anymore. I think this time I want to, at least for this specific instance of the concept, see where I can bring it on my own. But...
Perhaps we could spin this off into a more generic project that those who are interested in could collaborate on? I think it'd be neat if we could end up making some sort of "all purpose" CCG rules processor (that let people script their own plugins for specific card games etc...). It'd be way beyond the scope of my original idea (making a demo for my own CCG), but it would be a really awesome open source project that everyone could get together to work on.
Aye, true... (and in traditional CCG fashion, my turns would go in phases as well... I can't imagine another way to do it sadly, though I tried. =( ) I'm envisioning a kind of "event based" system for that, if it is possible to do with scripts as the main objects.
Basically, when a card comes into play it would register itself to be notified (called back to) when certain events took place... for example...
Say you play an instant card that says "At the end of turn, destroy target permanent." When it resolves successfully, the instance of the card's script object would register itself with the game, requesting to observe the "end of turn" event.
When the end of turn came around, all observers of the event would be notified through their callback method, and they could use the game's API to do whatever in-game function was intended to be done (in this case, destroying a card that was targeted when the instant first resolved).
That's how I'm picturing it working, at least. In practice, I'm not sure how that'd work with the "scripting engine" approach involved, nor with the "Ruby" approach (as I'm inexperienced with both at the moment, but hopefully not for long ). I know that's what I'd do if they were just standard, native classes and objects in the language.
Actually, I could see text parsing being useful, if the OP's game use the Foo rule. The Foo rule basically means that if a card says "foo" somewhere in the card title, then it is a Foo card. That saves print card designers a lot of hassle, because if they make a card called "Shadow Ninja", but forget to give him the "Ninja" trait in the spot on the card where traits go, then you'll have players arguing over whether or not that card should be considered a "Ninja" card for the purposes of being targeted by cards that affect ninjas. In a computerized context, you could easily have a card send its card title text as part of any events it produces. Then you can do a simple regex on the title to tell whether or not the source of the event is a swamp, or whatever. Or for global events in the game (e.g. "Pirate Assault - All ninjas in play are destroyed"), you just regex the titles of all the cards in play to see whether they contain the string "ninja". If physical card designers have found that the Foo rule saves them a lot of hassle with obsessively specifying card traits, I bet it would work for the OP's computerized version as well.
If you use an event system, you don't have to iterate through every card at the start of every phase. If a card that performs an effect at the start of each upkeep phase, then you have your app produce an "upkeep phase is starting" event, and code the card to trigger its effect when it sees that event. You'd need two events per phase, I think, a start event and an end event, since it's not uncommon to see "At the start of..." or "At the end of...".
The Object#methods call returns an array of all methods available to the current object. Since all objects inherit from Object, they all have that method.
So, what you can do is, if you want to look at all the sorcery-speed tricks available through a card object, you can do:
Card.methods.select{|method| method =~ /Sorcery/}.display
(So, return the list of every method that includes "Sorcery", and then call the 'display' method on it, which presumably displays some sort of tooltip menu.)
That ugly select method, though, can be abstracted away through the method_missing method, which is also inherited from Object, but you can-re-define it to do whatever you want
and then we can just do an 'eval' on that commandstring, and we have our method... dynamically parsed!
So 'Select_By_Sorcery_And_Instant' and 'Select_By_Instant' are both valid method parsers.
Now granted, this isn't the speediest way to do things, but it simplifies things quite a bit, as all you need are a few 'glue' methods and classes, and you've got the objects talking directly to the display. You can even abstract that whole methods.select mess with:
Aye, I agree with you 100%. That seems like the best way to approach it to me. I think the notion of putting it in external scripts is messing with me though... I'm not sure exactly how the notion of external scripts work (still collecting resources in that regard), so I'm not sure at the moment what the limitations are of such a thing.
I guess in the worst case scenario, I could make each card set a DLL library filled with objects for the cards, and that'd ensure that I could do everything I'd normally be able to do in the language. Hmm... I guess I should read these resources before I jump to the conclusion that I can't do it. I'll keep reading them.
Interesting! It's very different than how I'm used to thinking about what you can do. I'll use this opportunity to try and actually teach myself Ruby.
Thanks for the info! It's definitely been helpful. I'll try to keep you all posted here.
I'm not convinced that's the BEST way to do it, though.
Aha! Thank you for that! I spent most of the night last night looking around to see if someone had attempted this in publicly-available source code before, and I came up empty in my search.
Though I agree, given the lengthy conversation everyone had today, that parsing it to determine rules might not be the *best* way, it'll likely give me a lot of ideas about how it can be done, which'll be terribly useful.
Oh, on an update... I've found some Ruby resources that I'll be reading (including what must be the most entertaining tutorial I've ever read in my life... it's all over the place, but it definitely doesn't have the problem of being dry).
I also found earlier today that my idea of using VSTA (Visual Studio Tools for Applications) for scripting and doing the program in C# or VB.NET wouldn't work in practice because for anyone to use the program, I need to pay some sort of retarded royalty fee for it. I guess it's not retarded... but it makes me not want to use it in principle. However I did find that you can interface with LUA and use LUA as a scripting language... which may not be relevant now that I'm reading about Ruby, but it brings up an interesting issue that I figured I'd mention here (to see what others would do about it)...
... in Magic: the Gathering online, sometimes an event will cause an action to trigger that requires user interaction. For instance, if you had an enchantment on the table that said "At end of turn, target Octopus is dealt 12 damage".
In MTGO, a prompt would appear for the user to select which Octopus (in this example) would receive the 12 damage. If they're using a script, however, that script would have to halt operation and wait for the users' UI (and thus the user) to make up its mind.
Maybe I haven't gotten enough sleep in the past 24 hours, but it seems like a strange situation to me. The UI prompt would need to be open-ended and event driven, but the prompt itself would come about due to some linear code that needs the result immediately in order to continue processing.
So if MTGO is using scripts for each of the cards' actions, does the API make the script "wait" for the UI prompt to finish by spitting it into a worker thread, or is there some kind of more complex event notification taking place (like the script keeps state information about where it was at in processing when it asks for UI interaction, passes control back to the UI through an API command, and watches for the UI event to fire saying it's done... at which time it reloads the state and position in the script that it was at before and keeps going)?
I'm feeling the worker thread idea, because I'm having a hard time wrapping my head around the concept of keeping the state of which line of code in a script it was on... that seems messy... maybe I need to sleep on it though, all this research today and last night is fumbling around in my head.
Anyways, the question might not be directly relevant anymore, but I think it'd be useful to think about in other scenarios anyways... and I still might want to go the C#/LUA route in the end, if for no other reason than it'd be neat to know how to incorporate scripting capability in .NET programs.
Heh, I had no idea what I was getting into when I sat down to start working on this yesterday afternoon. lol
I was thinking you would want to think about cards as plugins, which have handlers coded in them, but it looks like you need something more complex. But yeah, I'm thinking for the complexity you're asking for, you need event broadcasts and event listening. Cocoa (the Mac OS X library) has, from my experience, a really mature system for this, so you might find some interesting tutorials that will help you work this out in your head.
I read the documentation of the XML for the program Tomanta linked... it's a really fascinating approach! They basically enumerated in XML form all of the possible actions that could happen in the game... which I could see being a very good thing for consistency, but a pain in the butt when a large amount of new features need to be added... though perhaps, in practice, not as much as having to write a script for every card individually. The total definition of all possible actions in XML would allow for some more re-use, at the expense of constantly having to update that XML definition as the game evolved.
The trade-offs are interesting, and I could see some definite benefits in doing it either way. I also like some of the approaches they took in the XML file to enumerate certain actions (like tags for init, when it comes into play, etc...) It definitely gives some good ideas. I'm going to jump to the next step and look in the code tomorrow, after I get some sleep.
Thanks for all the interesting information here guys, and the great brainstorming session! I'll keep posting info here, and we can continue the brainstorm as desired.
EDIT:
Well I don't know specifically about using LUA to do it, but in any of the .NET languages I would just have the card effect cause a Dialog window to be shown. This causes operation in the main window to wait until the Dialog window has been resolved (just like an open/save dialog window). So the Enchantment fires and a dialog pops up asking the player to click on a card to target. When they attempt to click on a card it checks to make sure it's a valid target, and then either immediately fires the damage or maybe just selects the card and waits for them to press ok. I assume there should be some way from LUA to trigger a (probably preconstructed) Dialog like this.
Oh... yeah, that'd work! (smacks forehead) Researching all this stuff over the past couple of days has turned my head upside-down, I completely forgot that a modal dialog box would stop it.
Oh... but would they be able to interact with the main form to select a card if the dialog box was modal? I'm going to have to try it... I know you couldn't back in VB6 days, and honestly at the moment I can't remember if it will in C# or VB.NET. I'll check it out.
Something tells me that your idea will work a lot better than the not-so-pretty idea I had of using (and then pausing) a new thread to wait for the response. That notion didn't sit well with me.
Thus, you can have a stack object that is the only thing truly interacted with, and the 'legal target' type things can be checked before and after they are pushed onto the stack.
You only need the broadcast stuff for triggered abilities.
Speaking of triggered abilities: you could have a certain field for 'cares about' for each card object. So, my Moroii might care about things hitting the graveyard, while my Cloudstone Curio might care about 'creatures' that 'come into play'
So then, you can have a 'broadcast' object, that keeps track of all card that care about certain things, and then alerts those cards whenever their trigger is hit.
Just a thought.
AYe, I was thinking of a similar thing for the broadcasting, it'll definitely be a good plan to have it filter like that.
Apparantly, ruby has an each_object method! So once you've created your 'Card' class, you're able to iterate over all of them by doing:
ObjectSpace.each_object(Card){|this_card| (... do stuff)}
This makes cards like Wrath of God one-line programs...
ObjectSpace.each_object(Creature){|this_card| this_card.destroy unless this_card.indestructable?}
Yes, that's all well and good, ruby has that as well...
But what I posted, the ObjectScope, allows you to iterate over every object in the current scope.
Which is amazing.
But when would that be useful? I mean, theoretically the objects would all be part of one collection or another right? Is it just another way of doing the same thing, or does it have some further implication that I'm not considering at the moment?
Perhaps the "metadata access" (if I can call it that) features of Ruby allow for some interesting combinations to happen with that feature. I'll need to wrap my head around it though. I've not yet worked with a language that allowed you to iterate over the methods in an object (to use an example you noted earlier), so I might not yet realize the full implications with what you can do with a system that allows that kind of access.