Category: Archive


… but nothing to announce yet. Taking a queue from animaleante, I’m moving to a model where I go from two distinct update steps (“do animations” and “update turns”) into one combined update step (“do animations if there are any, update turns if not”). This’ll require replacing the Actor list with a priority queue (to eventually support an Energy-based system where Actors are not locked to one-turn-per).

Jeff

Day 40: Miscellaneous cleanup

I’m still pondering the right way to incorporate real-time Animations in with a turn-based system.  While that’s percolating, I did a bit of miscellaneous cleanup:

  • When the player targets an empty cell for an AoE skill, display the Cell background and Adornment (if any) in the targeting dialog
  • Slight refactoring to Cell.Render to handle how AoE range is rendered over out-of range cells (thanks animaleante!)
  • Actors regen health each turn (‘n’ turns after performing the last action).  This was just adding some health directly each turn; I refactored that to use the Event (and eventually Buff) system.  This will allow things like Levels that disallow any healing (including regen).
  • I cleaned up the DecideNextAction/PerformNextAction stuff a bit in anticipation of figuring out the Turn/Animation issue.

Here’s what the AoE targeting UX looks like when the area of effect falls outside the range of the Skill:

I’ve got vacation coming up next week (sunny Cabo San Lucas, hallelujah! 🙂 ) –  I’m hoping to resolve the Turn/Animation issue this week so that I can focus on more fun aspects of the Dungeon :).

Here’s the code and exe:

Updated source: http://wanderlinggames.com/files/dungeon/dungeon-4-19-11.zip

Updated executable: http://wanderlinggames.com/files/dungeon/dungeonexe-4-19-11.zip

Some more improvements made to AoE targeting today:

  • When AoE targets a cell with a monster in it, CAB-select the monster
  • Support targeting valid empty cells for AoE skills

I’m still wondering what to do about Animation timing ><…

Winnable Mahjong layouts…

Wow, people really like Badges!  My mahjong game (3D Mahjong Solitaire) has been out there for a month or so with just a “free play” mode, and had received a bunch of very positive reviews.  Last week I added “Champion Mahjong” mode, in which you complete tables to acquire Badges (similar to Xbox LIVE achievements).  Unfortunately, the tables as they were defined were way too difficult – e.g. requiring 100 tries to pass in some instances.  The net result is that everyone got stuck on the few tables, and the negative reviews began pouring in (each one = pain).

I put a bunch of effort into generating “guaranteed winnable” layouts – and pretty much failed the first time.  The problem was that I wasn’t testing the results, and doing to much assuming.  So, I got creamed in the reviews again last night/today (ouch again :P).  The other challenge is that building guaranteed winnable mahjong tables is actually a pretty tricky problem; everything I could find on the internet talked about “just play the game backwards” – which it turns out is a load of tripe.  More on that in a moment…

So, I took a step back and created a “headless” version of the Mahjong code which could automatically play all of the tables a bunch of times, and allow me to do some statistical analysis on the win/loss ratios of them all.  Here’s what I saw with the currently posted “guaranteed winnable” logic after playing through every table 100 times each:

Putting that into English:  many of the tables were completely unwinnable!  OhmigodDOH moment for sure.  And this is after I posted a comment and an update saying that the problem had been fixed.  No wonder people were flaming the game in the comments; I was half-tempted to do so myself! ><…

So, back to the “guaranteed winnable” algorithm:  after playing and playing and playing around with the “play it backwards” approach, I increased the overall win/lose ratio by a whopping 3%.  Yeah, that would stop peoples’ frustration :P.   The problem is that the “guarantee winnability by playing the game backgrounds” approach doesn’t guarantee winnability.  Simplest example is if you had a layout with 4 horizontally arranged tiles, you could start by placing a pair of matching tiles in the 1st and 3rd positions; and you’d be left with the 2nd and 4th positions for other pair of tiles – resulting in an unwinnable board.

I fought with this left and right before taking a step back and realizing that there was a much better approach: play the game “forward” (like a human would), picking any two “free” tiles and ignoring their tiletype; then just remove those tiles from the board, set those tiles to matching types, and continue on your merry way until the table is empty.  At that point, you just put all of the tiles back on the table with their new matching styles.

When trying the “play it backwards” approach, the “GetAvailableTile” function grew to 100 lines of gobbledegook. But with a “play it forward” model, it’s dead-simple:

        static Tile GetAvailableTile(Tile dontmatch)
        {
            int offset = RandomMgr.Next(RemainingTiles.Count);
            for (int i = 0; i < RemainingTiles.Count; i++)
            {
                Tile tileToRemove = RemainingTiles[(offset + i) % RemainingTiles.Count];
                Debug.Assert(!tileToRemove.Removed);
                if (dontmatch == tileToRemove)
                    continue;

                if (tileToRemove.IsFree)
                    return tileToRemove;

            }
            return null;
        }

So now when I run the analysis tool, I get an average win/loss ratio of 45% which is MUCH better:

Wonderful!  Now a “dumb” computer can solve almost all of the puzzles in just a few tries…

Next up, intuitively you’d want the tables to get progressively harder.  Now that I’ve got the analysis tool, I can just sort by the Win/Loss ratio in Excel:

And now I know what order to place the tables in so that it gets harder as you go.  Furthermore; when I add a new table, I can simply run the tool against it and tweak the table so that its difficulty is at an appropriate level.

Finally, you’ll notice that the last few levels are still too difficult; indeed, Table 21 was impossible for the “dumb” computer to solve!  So now I have the knowledge and tools with which to go back into the layout editor, tweak those layouts until they’re closer to a ~.15 win/loss ratio (what I’d consider appropriate for the hardest levels), and then the whole string of tables should be doable, the earlier badges will be easier and the later badges should be harder to get (as they should be), and everyone goes back to spending time enjoying the game instead of posting frustrated comments :-).

So: morale of the story: Don’t assume; statistical analysis rocks.  When 7YRL gets to the “level balancing” stage, I’ll be doing a heck of a lot more of this at that point!

(Oh, and finally: you might ask why I’m spending all this time writing this post when I haven’t actually submitted the update yet.  It’s because MS’s submission process – which in almost all ways rocks – sucks when you’ve got an update in the queue that you want to override.  I need to wait for that update to go through, and then can submit this fix.  So, more waiting – and more frustrated players in the meantime… ><…)

UPDATE (4/17 @ 12:49PM PST)

I’ve just completed the work described above.  This required reordering the tables and making some tweaks to the tables with too-low win/loss ratios.  After all of that was done, here’s what the new stats look like:

Things to note:

  • All Win/loss ratios are at .15 or above.  My assumption is that a good human player can probably double win the rate of a “dumb” computer, so the harder levels will take them about 3 tries to pass.  That’s about right, and leaves me some leeway to add harder levels without getting too hard.
  • The table difficulties smoothly decrease, meaning that the earlier levels are easier and the harder levels are harder (as you’d expect).
  • There’s three exceptions to the smoothly-decreasing: Tables 8-10 get really easy.  Although I could have left them up at the front, I opted to move them  in a little ways because the table layouts are pretty sparse and not a good introduction to the gameplay.  Over time I can look at increasing the difficulty of those levels so that they fit in better difficulty-wise.  But the user won’t really be able to tell either way…

So, now the waiting begins for MS to publish the update that they’re currently testing, and then I can submit :P…

To those of you coming here from my comment in the Wp7 Marketplace review for my game – thanks for your patience, and please up-rate your rating once the new checkin goes in! 😉

Cheers,

Jeff

Quick update today on the Dungeon; I fixed the issue where firing the same Animation would cause it to not remove the emitter from the system (the result being that the particles wouldn’t stop emitting even after the Animation ended).  In order to reduce memory allocations, I don’t put instance-specific variables in the derived keyframe classes.  Or rather, I tried not to 🙂 – I was allocating the particle emitter in the AnimationKeyFrame_Particle.Start function and deallocating it in AnimationKeyFrame_Particle.End.  So two calls to Start would result in two allocations and only one deallocation.  This was fixed by making the abstract AnimationKeyFrame.Start return a derived AnimationKeyFrameInstanceData object, which is then passed to Render and End.  Most keyframe classes ignore this, but the particle one uses it to store the emitter (and system) and delete them in End.

Using that, the AnimationKeyFrame_Particle class is updated as follows:

 public override KeyFrame_InstanceData Start(GameObject performer, GameObject target)
        {
            // Create the per-instance data object to store our particle emitter nad system.
            AnimationKeyFrame_Particle_InstanceData data = new AnimationKeyFrame_Particle_InstanceData();

            // Create the particle system for this keyframe if not already created.
            data.ParticleSystem = new ParticleSystem(this, 10);
            ParticleMgr.Systems.Add(data.ParticleSystem);

            // Spin up a new particle emitter for this instance
            data.ParticleEmitter = new ParticleEmitter(data.ParticleSystem, 10, GameState_InDungeon.MapCellToScreenCoords(performer.Location));
            ParticleMgr.Emitters.Add(data.ParticleEmitter);

            return data;
        }

        public override void End(KeyFrame_InstanceData data, GameObject performer, GameObject target)
        {
            AnimationKeyFrame_Particle_InstanceData myData = data as AnimationKeyFrame_Particle_InstanceData;

            // Tear down our particle emitter
            myData.ParticleSystem.RemoveWhenDone();
            ParticleMgr.RemoveEmitter(myData.ParticleEmitter);
        }

… and the end result is a correctly working Animation system (whew).

Here’s a pic of AoE in action:

No code drop today though; still some bugs in the AoE targeting system.  More impactfully, it has raised some questions about turn timing and animations that I need to think through (specifically: user uses a skill like ice storm (in the picture above) – do the monsters move at the same time as the skill animation fires? after? before? automatically? etc…

Day 37: AoE selection

Not much time to work on the Dungeon today, but was able to make some progress on AoE target (cell) selection for AoE skills. This is requiring some nontrivial (but not unexpected) tweaking to specifying Skill targets – before I was passing a GameObject, but now an AoE  Skill can impact multiple targets.  This has also caused a bit of refactoring in event Animations, causing me to move the animation into the Action definitions (in addition to the higher level Events being able to specify animations), and adding support for both success and failure animations.

Here’s what the definition for an AoE “Ice Storm” skill looks like:

    <Skill Id="Priest3" Level="1" Name="Ice Storm" Tile="Skill_IceStorm" Description="Blast a group of targets with ice">
      <Event Type="UseSkill" SelfSuccessAnimation="Rainbow">
        <DoDamage Target="Cell" Amount="4+1d2" Range="3" AoERange="2"
                  OnSuccessText="A bolt of ice hits the {target} for {amount}"
                  TargetSuccessAnimation="IceBolt" TargetFailureAnimation="IceBoltFail" />
      </Event>
    </Skill>

Note that an AoE skill has both Range (how far away the Skill can be applied) and AoERange (the range of impact of the Skilll when used).

Here’s what the AoE selection currently looks like.  It highlights the AoE range of impact in yellow:

No code as there remain many issues with the Animation refactoring and target refactoring…

Up another 5 in the list of all WP7 marketplace apps to #60.  #29 in the list of all free games, which means it finally appears “above the fold” in the Zune top apps list on my 24″ monitor :).

Yes, it’s more popular than not only YouTube, but people apparently like the game more than they like Sexy Babes.  FREE ones, even!

I’m apparently going to need to spend a little more time on it; the latest review says “Been playing mahjong for years and I am completely stuck on the second puzzle for a badge, been literally playing it ALL day and just can’t seem to get past it, is this thing set to expert???”.  On the one hand; awesome that he’s playing it all day ;-).  On the other hand, not awesome that it’s so hard (I’ve noticed the same thing).  I’m going to look into updating the table generation code to place tiles in a guaranteed winnable fashion (placing the tiles by basically playing it backwards).  It’ll also give me an excuse to drop a “my other games” button onto the main menu ;-).

So, that’ll likely take the bulk of my “free” time this weekend, and Dungeon won’t get as much love…

I’ve worked out the last few kinks in single-mob target selection, although I’ve turned up a bug in the Animations that I need to track down (specifically: if you fire off the same animation twice in quick succession, it fails to remove the emitter from the system; some ref-counting issue somewhere…).

In order to minimize the amount of thumb movement while playing, the ContextualActionBar and SkillsBar work together:

  • Performing a Skill on a target mob selects that mob into the ContextualActionBar
  • Performing a skill from the SkillBar now sets the secondary skill in the ContextualActionBar to the same skill
  • Selecting a Skill from the SkillBar when there’s a target in the ContextualActionBar automatically selects that target for offensive skills (i.e. doesn’t open the targeting dialog

Here’s what it looks like after clicking on the skill in the skillbar and selecting the target:

Here’s the code and exe:

Updated source: http://wanderlinggames.com/files/dungeon/dungeon-4-15-11.zip

Updated executable: http://wanderlinggames.com/files/dungeon/dungeonexe-4-15-11.zip

It’s up to #65 (from #80 three days ago) out of all 13,000 apps in the WP7 marketplace!  It’s up to #40 (from #45) out of all games,  and #31 (from #36) out of all free games…

Jeff

Bunch of minor tweaks to the Target selection dialog/process; it’s working for Single-mob targets and most of the code is there for AoE effects, but I have a little ways to go before it’s done.  Here’s what it looks like now:

Of note:

  • I’ve removed the “previous” button – I don’t think it’ll get used.  This leaves the dialog unbalanced; something to fix later (perhaps with an “Info” button that brings up an Info dialog for the selected mob)
  • I’m highlighting out-of-range and invalid selection (e.g. wall) cells in Red.
  • I’ve made a number of tweaks to the selection logic; e.g. tracking the last selected mob, using and updating the ContextualActionBar’s selected mob, etc.

Still todo:

  • I’ve reached the point where I need to be able to inject values into event text messages.  For instance, the text for the Bolt of Light skill will be something akin to “A bolt of light hits {target} for {amount}.”  I need to be able to dynamically replace those {} values; I could pull that out of the Action.Info field, but that gets interesting for Events that have multiple Actions.  Requires more thought…
  • I’ve somehow managed to break the data-driven Animations :).  I’ll get that fixed tonight and do a code drop then.