Neo Mecha v0.4.0 Cleaning Art devlog

The goal for v0.4.0 is cleaning up some code and redoing some art

Remove Command Processors

The key to make sure development is remains fast is to make sure you have a good Architecture. While writing the various action classes for the new Smoke Action in v0.3.0 I decided that the Command Processor classes were a bit redundant. The only purpose they served was to separate the logic of applying a command from the actual command data. Which really wasn’t useful.

NeoMecha is separated into two main layers, Logic and UI. In the UI each button to take an action corresponds to an Action Generator. When you push a button the Action Generator generates Action Options which are then selectable on the Visual Gameboard. When you select an Action Option the Visual Gameboard passes an Action Choice, created by the Action Option, to the Rules Engine. The Rules Engine then processes the Action Choice through a series of Action Processor that change the Action Choice into a series of commands to be applied to both the Visual Gameboard and the Logical Gameboard. The commands are returned in the form of an Action Result, which is just a list of Action Groups, which are a list of commands that should be executed at the same time.

Later I plan to refactor the Action Choices so that they do not have object references and only have Ids. That will make it easy to pass them back and forth between a server and a client. I also need to find a way to organize reactions. Right now they just a bunch of messy If statements.

Middle mouse scroll

I never really liked how strategy games scroll the when you move your mouse to the edge of the screen. I put that into kind of scrolling into NeoMecha but it too slow. For my own sanity I added the ability to click and scroll with the middle mouse button. I will likely try to make edge scrolling better but for now middle mouse to scroll works much better.

Better High/Mid/Low Display

It is extremely difficult to to read the attack style that is used. To make things worse, during a replay the text just stacks on top of each other. In keeping with the Neo in NeoMecha I’m trying a Cyberpunk style. So, Gave the the attack style a purple background with a neon pink outline and changed the text color to hot pink.

Better Logo

After that I got very distracted by making a better logo. As I’m starting to post about NeoMecha I wanted to have a something more than a placeholder logo.

The placeholder logo

Confrim End Turn

One problem the game has, is people hitting “End Turn” before they use their reaction points. To try to make this better I added an confirmation dialog box. If you have unspent points when you end your turn, you will be asked if you’re sure you want to end your turn. Long term I’d like to change how reactions are set but that is too much work to do at the moment.

I also added some styling to the dialog box so it doesn’t look absolutely terrible.

Fix tile highlighting bug

Secondary tile highlighting wasn’t working so I took a look, and yeah, it just had the bad logic.

Logging and Testing

Time for some of the less glamorous side of coding, logging and testing. Previously their was a log file but nothing was actually being logged. I started by by adding logging scopes to each of the top level Rules Engine functions, then adding a log statement when something actually happened. Like an Action Choice was processed, an Action Command was processed, or check for end of game. This made a turn look like this

3-04-18T05:48:12.3698715-07:00 [Information]: (Intrepid.TBSRulesEngine.RulesEngine) [TakeAction]: Action Processor: SetDodgeReactionProcessor Processed Choice SetDodgeReactionChoice. 
2023-04-18T05:48:12.3699577-07:00 [Information]: (Intrepid.TBSRulesEngine.RulesEngine) [TakeAction]: Applying Action Result. 
2023-04-18T05:48:12.3699852-07:00 [Information]: (Intrepid.TBSRulesEngine.RulesEngine) [TakeAction]: Applying Action Group. 
2023-04-18T05:48:12.3700175-07:00 [Information]: (Intrepid.TBSRulesEngine.RulesEngine) [TakeAction]: Executing command SetDodgeReactionCommand.  
2023-04-18T05:48:12.3963735-07:00 [Information]: (Intrepid.TBSRulesEngine.RulesEngine) [TakeAction]: Checking for End of Game. 

I then added log statements to the GameBoard and MechUnit objects when they changed their state and ended up with a turn looking like this

Move Action
2023-04-18T05:48:23.8145502-07:00 [Information]: (Intrepid.TBSRulesEngine.RulesEngine) [TakeAction]: Action Processor: MoveProcessor Processed Choice MoveChoice. 
2023-04-18T05:48:23.8146153-07:00 [Information]: (Intrepid.TBSRulesEngine.RulesEngine) [TakeAction]: Applying Action Result. 
2023-04-18T05:48:23.8146370-07:00 [Information]: (Intrepid.TBSRulesEngine.RulesEngine) [TakeAction]: Applying Action Group. 
2023-04-18T05:48:23.8146564-07:00 [Information]: (Intrepid.TBSRulesEngine.RulesEngine) [TakeAction]: Executing command MoveCommand. 
2023-04-18T05:48:23.8146890-07:00 [Information]: (Intrepid.TBSRulesEngine.GameObjects.Gameboard) [TakeAction]: Placing game Piece: 32b0ac0d-048d-4715-b932-d8f0acbd4aa9 on Tile: Position { X = 3, Y = 8 }. 
2023-04-18T05:48:23.8147238-07:00 [Information]: (NeoMecha.Rules.GameObjects.MechUnit) [TakeAction]: MechUnit: 32b0ac0d-048d-4715-b932-d8f0acbd4aa9 Using 1 Action Points, 1 remaining. 
2023-04-18T05:48:23.8153571-07:00 [Information]: (Intrepid.TBSRulesEngine.RulesEngine) [TakeAction]: Checking for End of Game. 

Attack Action
2023-04-18T05:48:24.4860353-07:00 [Information]: (Intrepid.TBSRulesEngine.RulesEngine) [TakeAction]: Action Processor: MeleeAttackProcessor Processed Choice MeleeAttackChoice. 
2023-04-18T05:48:24.4860951-07:00 [Information]: (Intrepid.TBSRulesEngine.RulesEngine) [TakeAction]: Applying Action Result. 
2023-04-18T05:48:24.4861181-07:00 [Information]: (Intrepid.TBSRulesEngine.RulesEngine) [TakeAction]: Applying Action Group. 
2023-04-18T05:48:24.4861438-07:00 [Information]: (Intrepid.TBSRulesEngine.RulesEngine) [TakeAction]: Executing command DodgeToCommand. 
2023-04-18T05:48:24.4863373-07:00 [Information]: (Intrepid.TBSRulesEngine.GameObjects.Gameboard) [TakeAction]: Placing game Piece: 4ef3c6d5-914e-4a51-8598-efb0428721f6 on Tile: Position { X = 2, Y = 7 }. 
2023-04-18T05:48:24.4863760-07:00 [Information]: (Intrepid.TBSRulesEngine.RulesEngine) [TakeAction]: Executing command MeleeAttackCommand. 
2023-04-18T05:48:24.4866399-07:00 [Information]: (NeoMecha.Rules.GameObjects.MechUnit) [TakeAction]: MechUnit: 32b0ac0d-048d-4715-b932-d8f0acbd4aa9 Using 1 Action Points, 0 remaining. 
2023-04-18T05:48:24.4895473-07:00 [Information]: (Intrepid.TBSRulesEngine.RulesEngine) [TakeAction]: Checking for End of Game. 

Alright enough logging for now, lets move on to testing.

I created several builder classes that let me build & setup a Rules Engine test like this.
These builders turned out pretty well. will probably import them into the main game when I start importing real map data.

A basic test looks like.

And now I have a bit of test coverage.

Quick improvments

I put in a stop watch to see if the Rules Engine was having issues. It looks like it is preforming pretty good ATM.

2023-04-20T22:26:14.3502853-07:00 [Information]: (Intrepid.TBSRulesEngine.RulesEngine) [TakeAction]: Action Processor: MeleeAttackProcessor Processed Choice MeleeAttackChoice. 
2023-04-20T22:26:14.3503662-07:00 [Information]: (Intrepid.TBSRulesEngine.RulesEngine) [TakeAction]: Processed Action Choice in 0 ms . 
2023-04-20T22:26:14.3503873-07:00 [Information]: (Intrepid.TBSRulesEngine.RulesEngine) [TakeAction]: Applying Action Result. 
2023-04-20T22:26:14.3504076-07:00 [Information]: (Intrepid.TBSRulesEngine.RulesEngine) [TakeAction]: Applying Action Group. 
2023-04-20T22:26:14.3504265-07:00 [Information]: (Intrepid.TBSRulesEngine.RulesEngine) [TakeAction]: Executing command MeleeAttackCommand. 
2023-04-20T22:26:14.3504531-07:00 [Information]: (NeoMecha.Rules.GameObjects.MechUnit) [TakeAction]: MechUnit: 29590114-cd33-47ef-8fc5-696f0c3be3eb Using 0 Action Points, 2 remaining. 
2023-04-20T22:26:14.3504762-07:00 [Information]: (Intrepid.TBSRulesEngine.RulesEngine) [TakeAction]: Executing command TakeHitCommand. 
2023-04-20T22:26:14.3505093-07:00 [Information]: (NeoMecha.Rules.GameObjects.MechUnit) [TakeAction]: MechUnit: 29590114-cd33-47ef-8fc5-696f0c3be3eb Took 10 Damage, 90 HP remaining. 
2023-04-20T22:26:14.3505308-07:00 [Information]: (Intrepid.TBSRulesEngine.RulesEngine) [TakeAction]: Executing command TakeHitCommand. 
2023-04-20T22:26:14.3505521-07:00 [Information]: (NeoMecha.Rules.GameObjects.MechUnit) [TakeAction]: MechUnit: e830ec65-4279-4a6a-b4c7-486eb289874d Took 0 Damage, 100 HP remaining. 
2023-04-20T22:26:14.3505715-07:00 [Information]: (Intrepid.TBSRulesEngine.RulesEngine) [TakeAction]: Executing command TakeHitCommand. 
2023-04-20T22:26:14.3506749-07:00 [Information]: (Intrepid.TBSRulesEngine.RulesEngine) [TakeAction]: Executing command MeleeAttackCommand. 
2023-04-20T22:26:14.3507246-07:00 [Information]: (NeoMecha.Rules.GameObjects.MechUnit) [TakeAction]: MechUnit: e830ec65-4279-4a6a-b4c7-486eb289874d Using 1 Action Points, 0 remaining. 
2023-04-20T22:26:14.3507743-07:00 [Information]: (Intrepid.TBSRulesEngine.RulesEngine) [TakeAction]: Applied Commands in 0 ms. 

I also refactored the GetTilesICanMoveTo function to not use recursion and instead use a while loop. I did this because I want to increase the movement range but the recursive version can have performance issues. The recursive version ran in ~ 9900 ticks just under a millisecond, while the while loop runs in ~ 300 ticks for a 4 tile range. I going to try increasing the range to 20.


        public void FindTilesICanMoveTo(
            ITile curTile,
            int range,
            ref List<ITile> foundTiles)
            if (range <= 0)

            // Get up tile
            var upPos = Position.Create(curTile.X, curTile.Y - 1);
            if (HasTile(upPos))
                if (AddToFoundTilesForMoveTo(GetTile(upPos), ref foundTiles))
                    FindTilesICanMoveTo(GetTile(upPos), range - GetTile(upPos).MovementCost, ref foundTiles);

            // Get Right tile
            var rightPos = Position.Create(curTile.X + 1, curTile.Y);
            if (HasTile(rightPos))
                if (AddToFoundTilesForMoveTo(GetTile(rightPos), ref foundTiles))
                    FindTilesICanMoveTo(GetTile(rightPos), range - GetTile(rightPos).MovementCost, ref foundTiles);

            // Get Down tile
            var downPos = Position.Create(curTile.X, curTile.Y + 1);
            if (HasTile(downPos))
                if (AddToFoundTilesForMoveTo(GetTile(downPos), ref foundTiles))
                    FindTilesICanMoveTo(GetTile(downPos), range - GetTile(downPos).MovementCost, ref foundTiles);

            // Get Left tile
            var leftPos = Position.Create(curTile.X - 1, curTile.Y);
            if (HasTile(leftPos))
                if (AddToFoundTilesForMoveTo(GetTile(leftPos), ref foundTiles))
                    FindTilesICanMoveTo(GetTile(leftPos), range - GetTile(leftPos).MovementCost, ref foundTiles);


public void FindTilesICanMoveTo(
            ITile startTile,
            int range,
            ref List<ITile> foundTiles)
            if (range <= 0)

            List<ITile> nextFrontier = new List<ITile>();

            List<ITile> frontier = new List<ITile>() { startTile };

            while (range > 0)
                foreach (var tile in frontier)
                    IEnumerable<ITile> neighbors = GetNeighbors(tile);

                    foreach (var neighbor in neighbors)
                        // If we dont' have it add it to the nextFrontier
                        if (!nextFrontier.Contains(neighbor) 
                            && !foundTiles.Contains(neighbor)
                            && !neighbor.IsOcupied
                            && neighbor.IsTraversable)

                frontier = nextFrontier.ToList();

UI Work

Buttons Redesign

As the fisrt step of redesigning the controls the buttons need to be replaced.

Console Redesign

This is what the current console looks like.

This is what the console looks like after replacing the buttons and other graphics without changing the overall layout.

This is what the console looks like after a redesign of the layout.

And just so sound is completely neglected I added a sfx to the button.

I think that will be good for v0.4.0. It’s time to start scoping out v0.5.0