Showing posts with label code. Show all posts
Showing posts with label code. Show all posts

15 August 2021

KoDP on the web: 50 Years of Text Games / Storylets with Casting

 Emily Short just posted

Aaron Reed’s 50 Years of Text Games has now covered King of Dragon Pass, which I strongly recommend reading: the game used techniques that we’re still very much exploring and discussing now, including elements we might now refer to as storylets with casting (that is, storylets that assign characters to particular roles). Well worth a look if you’re interested in the structure, code, or writing process of that game, or storylet games in general. 

First, Aaron did a very well researched look at King of Dragon Pass. If you’re at all interested in how we made the game (and how our effort at selling it was received), check it out.

Second, Emily’s categorization seems pretty accurate. Our scenes (a term we used because of their similarity to scenes in a play) would now be considered storylets, and they’re chock full of assignable roles. In many cases, that role is a clan (a neighbor, a rival, an ally, etc.). It can also be a character.

Often the role is a requirement for the scene to be run. The scene condition is in square brackets on the second line of this script, the actual assignment is on line 4.

scene: scene_228LetsPretend

scene228, left, [ProDragonewtOnRing AND dragonewt.knowledge <> 'unknown]

music: "CouldBeBad"

p = ProDragonewtCharacter

text: Two children have been pretending to be the god Orlanth and Aroka, the blue dragon. Somehow, their play has gone beyond pretend, and magic is gathering, almost as if they were on a heroquest.

saga: Somehow two children playing at Orlanth and Aroka started a full-blown ritual.

[ProDragonewt Priority] The children may learn draconic wisdom from this experience. [1]

[Trickster AND ProDragonewtOnRing] Good one, <p>! [1]


This is a fairly typical script. The scene is richer if there’s a named leader who can be involved, and in general, we favor choosing leaders who are on the clan ring. Players have typically had an opportunity to learn a bit about their personality (as well as have invested in them as a representative of the clan). The advisor is curious to know about the summoned dragon, and another character offers a rejoinder.

(This scene was created by Lysander Xen as part of our contest, so it’s technically not part of the version Aaron was describing. The illustration is by Jan Pospíšil.)

Note that KoDP’s spiritual successor, Six Ages, also makes use of storylets. A blog post goes into more detail. But pretty much it’s the technique we pioneered in 1997.

08 June 2013

Architecture Redux

I’ve mentioned the game’s architecture before, but understanding it helps answer some common questions, so I thought I’d draw a picture.


The game has three main divisions. In the original version, different developers were more or less responsible for each.

The user interface (the 50 or so screens and dialogs) were created for the Windows and Macintoshes of the day (that day being somewhere between 1997, when we started the project, and 1999, when it was released). We used mTropolis, a powerful multimedia development system that was discontinued over a year before the game came out.

The interactive scenes (and news) were coded in OSL (the Opal Scripting Language, or the Opal Scene Language).

The game engine, written in C++, executed the OSL code, ran the economic model (tracking cows and the effects of treasures on them), and was responsible for saving the game. It was cross-platform, running equally well on Mac OS and Windows.

When we created a new version for iOS, the basic game code and the scenes didn’t need radical change. Both were enhanced (for example, the game had more advice and supported 7 new treasures, and there were 28 new scenes) but existing code continued to work. By contrast, none of the user interface code could be used. Not that it would have mattered much, the small touch screen needed a new user interface anyway. This was a very substantial effort, and it ended up being partly duplicated for iPad and again for the 4 inch display.

There are numbers in the diagram because they suggest why the iOS version is distinct from anything else. Saved games assume a specific number of scenes and treasures. Adding more would mean substantial reworking of the C++ code. But this code had to work with mTropolis, so it’s tied to 1998 era systems. The hardware and compilers I used back then are long gone.

So that’s why the GOG Windows version can’t be updated.

Some have asked about a new Windows or Mac version, or an Android version. Since mTropolis no longer exists and UIKit is iOS-specific, any version for any other platform will require reworking the user interface (possibly with some redesign, definitely a brand new implementation). Think of it was rewriting a third of the game.

The first iOS release took about 20 calendar months. Since the C++ code didn’t need significant updating, the user interface could thus be said to be about half the code needed for that project.

Actually, there was some new C++ code, because we wanted to add Game Center achievements. Most of this would work on Mac OS X, but not on other platforms.

So unlike the original version, where it was trivial to build for both Mac and Windows, there’s a daunting amount of work to bring the game to another platform.

What we’ve done instead is reflected in those numbers: reworking the 50 screens for iPad (and soon the iPhone 5). And adding new treasures and new scenes in an update we hope to release this month.

13 April 2013

The Debug Dialog

When I was creating the iOS version of King of Dragon Pass, I included some of the same debugging utilities we had in the original game. Unfortunately, I couldn’t remember exactly what we had, and there was no good way to look, because the release version of the game didn’t have them. (And it’s real hard to search an mTropolis project.)

I knew there was a way to trigger a specific scene (critical for running them over and over to test all the possibilities). And a set of commands that do things like get rid of all your food (to test things like starvation advice) or get raided by Trolls.

But I knew there was something more, just not what it was.

Today I happened to run into this code
/*
Returns text about our clan helpful for debugging (shown in the \ dialogette)
*/
char* CPlayerClan::DebugInfo() const
{
gString = "farmerMorale: ";
UString::Append(gString, this->MoraleFarmers());
gString += ", warriorMorale: ";
UString::Append(gString, this->MoraleWarriors());
gString += ", thrallMorale: ";
UString::Append(gString, COSL::GetIntegerVariable(var_thrallMorale));
gString += ", mood: ";
UString::Append(gString, this->Mood());
gString += "\rvictory: ";
UString::Append(gString, COSL::GetIntegerVariable(var_victory));
gString += ", kingship: ";
UString::Append(gString, this->Kingship());
gString += "\r";
UString::Append(gString, fYearsAsType, "year", "years");
gString += " as clan type\r";

return (char *)gString.data();
} // DebugInfo

which was clearly used to display some internal status useful for testing.

This isn’t as important as it was in 1999, but we’re in the process of thoroughly testing a number of new scenes (currently 14, but several more are in progress). So I put this into the current debug dialog. It’s almost like seeing a long-forgotten friend.

02 September 2012

How Many Scenes?

A long-time player recently commented about how he had just gotten an interactive event he had never seen before. This is not as surprising as it sounds, even for a player who has had the game over a year, because many events are conditional, and the raw number is such that you won’t get each one every game. But I was curious about the exact odds.

Counting

The game calls interactive events “scenes,” which is biased towards the illustrated events. There are 1624 in total, but they aren’t all story events (scenes). They can be divided into:

code: A chunk of OSL script used to set state or conditionally trigger scenes. These have names like code_InitialTribalAgreements, fragment_BeSureToHaveElection, or code_R115MiddlingPenaltyOver. None of these have any player interaction. There are 464 of these.

news: Some sort of report, given by (or relating to) a single leader. These have no illustration. Most have no interaction, but some do give player choices. A notable subset of interactive news is heroic combat during a battle. Battle results is treated as a special case, and is shown with two illustrations. News scenes have names like news_TradeRouteEnded, news_R45aGrainHeFound, mission_EmissaryBanditAttack, or battle_HesGoneBerserk. There are 462 news scenes.

scenes: Interactive events are the core of the game. They always have an illustration, and at least one leader giving advice. They have names like scene_2Trader, scene_R194WeddingCelebration, or mission_ProposeAlliance. There are 614 of these.

quests: Heroquests are essentially a special type of interactive event, with no advice. There are 84 of these.

So there are 614 scenes defined in OSL, but it’s not really accurate to call all of those interactive events. That’s because when we designed an event, we sometimes wanted to show new advice in response to player choices, or change the background music to reflect a change in situation. This was implemented as switching to a new scene. So scene_R59TheChallenge might trigger scene_R59aChallengeResult, but that’s really just one event to the player.

Luckily, we were pretty consistent about naming scenes, and by looking for that pattern (R59a as opposed to R59), there are 70 scenes that are followups within an overall event.

That leaves 544 distinct interactive events. It’s worth noting that 28 of these are new in the iOS version (25 were in version 2.0, and one is new in the upcoming 2.1).

Calculating

But, what are the odds of not getting one of the 544? All scenes are not created equally (we kept the amount of branching in the game to a minimum, but some scenes directly depend on earlier choices), and many have specific preconditions. There’s no good way to figure that, so we’ll assume each does have an equal chance of showing up. If there are 5 random events each year, the odds of not getting a specific one each year is 99.1%. How long is a game? That can vary widely, but looking at two sagas of complete long games that are in our bug tracking system 48 to 58 years. For this quick calculation, let’s call it 53. So the odds of not getting a particular scene during a game are over 61%. Now we have to figure how many games. King of Dragon Pass is highly replayable, but even a hard-core player might not play more than 12 games in a year. The odds of not getting a random scene in a year of play are thus only around 0.3%.
A (thankfully) rare scene

It turns out the scene in question was not random, however. It related to the harvest, so it could only occur in Earth season. That means each game has over a 90% chance of not getting it, and thus he had a 31% chance of not seeing it in 12 games. Except that there was another condition on the scene, so the odds of getting it plunge even more.

(The odds would be slightly different for the original Windows version, but I’ll leave that as an exercise for the reader.)

Conclusion

So what does this analysis say? I think it verifies our design goal of replay, since you will not see many scenes your first game. (And this is only talking about events, not your responses to them.) And even if you have played a dozen games, you have a pretty good chance of getting something completely new if you play one more.

Also, our marketing copy of “nearly 500 interactive scenes” is conservative, and we should have done the math before.

22 May 2012

Skills, Expanded

I wrote before about the seven skills a leader is rated in. But we wanted to support OSL code such as

test Deception(expeditionLeader) vs Crankiness d6

In other words, like many roleplaying games, people have all sorts of abilities. But seven was already a fairly large number. So we came up with the idea of composite skills. Deception is the average of Bargaining and Leadership.

Including the composite skills, a leader can be tested in
Animals
Bargaining
Combat
Custom
Deception (Bargaining + Leadership)
Diplomacy (Bargaining + Custom)
Exploring (Bargaining + Combat)
Farming (Animals + Plants)
Hunting (Animals + Combat + Plants, special for Odayla worshippers)
Leadership
Magic
Plants
Poetry (Custom + Leadership)
Prophecy (Magic + Leadership)
Strategy (Combat + Leadership) 
Note that not every attempt to compose a poem is automatically use of the Poetry skill — it would depend on the context. But without some obvious reason to do things differently, that’s how we would have coded the scene.

29 September 2011

Feathered Scrolling

I think 2.0 really is an improvement over the original game in a number of ways. One aesthetic improvement depends on technology.

Our original multimedia engine, mTropolis, didn’t support alpha. A pixel on the screen came from only one asset. But iOS supports alpha blending, so we can have translucent effects.

iOS is also a multi-touch OS, designed for small screens. Scrollable areas don’t have always-visible scroll bars, so sometimes it can be hard to know that they can be scrolled. There are a number of approaches one can take to telegraph* this to the user. One is to fade the edges of the list. King of Dragon Pass does this.

The idea is that the list smoothly fades out when it can be scrolled. It may be a subtle cue, but it looks better than a hard crop. (And in my opinion, looks much better than drawing a box around the list, which we usually had to do in the original.) The list above is feathered on both top and bottom.

The implementation is a UIView that embeds a UIScrollView, and sets a layer mask. It’s the scroll view’s delegate, and changes the mask so there’s no feathering if the view can’t scroll in a particular direction. Here’s what one of the key methods looks like:


- (void)featherTop
{
if (fMaskStyle == kFeatherTop)
return;
fMaskStyle = kFeatherTop;

// Set up a mask to fade just top
CAGradientLayer* gradientMask = [CAGradientLayer layer];
gradientMask.bounds = self.layer.bounds;
gradientMask.position = CGPointMake([self bounds].size.width / 2, [self bounds].size.height / 2);
NSObject* transparent = (NSObject*) [[UIColor clearColor] CGColor];
NSObject* opaque = (NSObject*) [[UIColor blackColor] CGColor];
[gradientMask setColors: [NSArray arrayWithObjects: transparent, opaque, opaque, nil]];
[gradientMask setLocations: [NSArray arrayWithObjects:
[NSNumber numberWithFloat: 0.0],
[NSNumber numberWithFloat: kPickListFeather / self.bounds.size.height],
[NSNumber numberWithFloat: 1.0],
nil]];
self.layer.mask = gradientMask;
}


And, here’s what the effect looks like up close.

The iPhone screen is really small, and you need to pay attention to every pixel. Polish like this is really important. It may be subtle, but it just makes the app feel a little better.

* OK, I really wanted to use this word because its meaning is now so divorced from the original literal meaning.