All Maps Are Not Created Equally

For this, the final blog post of the Capstone project, I’ve decided to utilize this space and time to gain a deeper understanding of maps and, more specifically, the boost container called flat_map. 

Recently in the Mahjong codebase I’ve been working with nested maps and accessing data stored in a nested map structure. Although maps were obviously a topic covered throughout my CS coursework, I honestly didn’t reach for them too often in my own projects and it took a fair amount of brainpower to unpack both what was in the nested map structure I was looking at and how to get the data from it that I needed.

Fast-forward through conversations with engineers, cppreference reading, and a supremely helpful video about maps in C++ from the Cherno and I succeeded in strengthening my understanding of maps, how to use them, and when to reach for them. But then, as often happens, my mentor/sensei asked me, “why do you think we’re using a boost container flat_map instead of just std::map?” to which, in the moment, I could only make assumptions and guesses around optimization, but nothing concrete. Back to the books for me!

First off, what the heck is a boost container flat_map? A flat_map is “an associative container that supports unique keys and provides fast retrieval of values of another type T based on the keys” (source). That sure sounds like a map… 🧐 Like std::map, flat_map also supports random access iterators.

Where I started to uncover some differences is in the following:

  • flat_map <Key, T> has a value_type of std::pair<Key, T> whereas std::map<Key,T> has a value_type of std::pair<const Key, T>
  • flat_map is implemented like an ordered vector, which means that insertion of new elements will invalidate previous iterators and references
  • Random insertion into a std::map is faster than random insertion into flat_map (source)
  • Random search between the two containers is comparable, though there is some debate that flat_map can be made faster and std::map cannot (source)

None of this necessarily answered my sensei’s question of why use a flat_map vs. a map. Thankfully though, a thorough and multiply cited above Stack Overflow post uncovered the answer to my query!

One very important, significant advantage of a boost container flat_map over std::map is the speed with which a flat_map can be iterated over. Although a std::map would work for the specific use case in our Mahjong game where we use a flat_map, the fact that our nested map structure needs to be iterated over in a couple of different places in the game to get the data we need, a flat_map is a much better choice because that iteration can be optimized.

Of course this is part of the job of a software engineer, however it’s still amazing to me that someone knew to or at least took the time to look into making that design decision. I am such a novice and constantly humbled by the intelligence and experience of my colleagues. It’s an incredible honor to work and learn from them, and I am eternally eager to continue growing in this field.

Critical Conversations

With graduation and the Capstone deadline fast approaching, I’ve been having a fair number of critical conversations reflecting on my Postbacc educational journey, career history, and what goals I have for my career path moving forward. 

Imposter syndrome has weighed heavy on me the past few weeks along with a sense of frustration and insecurity about my own C++ deficiencies, and too much of my energy has been aimed at calming anxiety about what I deliver by the end of term and if that’s “enough”. These feelings, quite frankly, are a nonsense-no-good-waste-of-time, and I know that, but they still well up and require some attention to quash.

On the flipside, this project has been an exceptional experience filled with tremendous personal growth as I tap back into my C++ knowledge and dig deeper into practical applications of topics explored in prior terms. This has allowed me to develop closer connections with other members of the engineering team and I feel a sense of pride for and ownership of the work I’ve contributed to the Mahjong codebase.

The rollercoaster ride of feelings and experiences I’ve been reflecting on place before me some important questions:

  • Do I really, truly want to be a software engineer?
  • Am I prepared to be a Brainium Software Engineer? Can I contribute meaningfully and maximally to our team in this role? 
  • Is there a role that suits my skills, professional experience, and passions more appropriately?
  • How will I be evaluated at Brainiun—both prior to any promotion to SWE and during my tenure working as a SWE?
  • Will the work of a Software Engineer bring me joy?

Most of my answers to these questions are in a state of flux currently. For the longest time, I’ve been stubbornly working towards this goal with the idea that after a few years of getting some software engineering experience I could work towards engineering management. This is still a sensible route, but does it have to look like this? Should my path look like this? Is there a better or different way for me to forge my own trail?

After recently conversing with Brainium’s People Operations Manager about my advancement goals, my greatest takeaway was to not put myself into a box. Just because I’ve had this idea of “Chelsea Marie Hicks – Software Engineer” in my mind, doesn’t mean reality has to exactly match that vision and perhaps I should take a wider look at the view to see what other possibilities are presented. I’ve some soul searching to do and more questions than answers (honestly, that never seems to change).

Buttons, You Really Push My Buttons

I kid you not, I have more to say about buttons.

Previously I experienced success with creating a grid of buttons that would display on the screen, but they didn’t do anything yet. Ultimately, I wanted to have users be able to press a ghost tile button and for the tile to register being touched by turning on like a light switch going from quarter opacity to full opacity. 

I began by utilizing a member function that allows the opacity of buttons to be set, which seemed like a surefire way to get the opacity of the buttons to change on press, but when running the Board Editor on a device I saw no inkling of an opacity change. When running the debugger I was able to confirm that the opacity function was indeed being entered and called on the button pressed. Yet again, a mystery was afoot!

Turns out, instead of setting the opacity of the ghost tiles when they were created, I was setting the lower opacity in the function that renders the view, which on the device at first looks just fine, but is very problematic since the rendering function gets called any time there is a change to the board, or any time it is “made dirty”. So, when a tile button was pressed, the opacity changed to full opacity, a change to the board occurred, and the render function got called, changing the button back to ghost state. The remedy was simple, once figured out, and voila, buttons were able to be pressed and turned on.

A few other things would also need to happen when a ghost tile would be touched, such as the counter incrementing and that tile actually being added to the board. Although I really should’ve focused on adding the tile to the board and working on the model side of things, I kept tinkering with the view and added the increment counter (and decrement counter) functions to the handler for the buttons so that the counter would increase/decrease accordingly.

This led me to discover a bug, which truly was more of a result of poor planning on my part. The accuracy of the counter in its current state requires that the designer use the board editor in a very specific way. They start in add mode by default and can immediately tap on ghost tile buttons to get them to appear at full opacity and the counter to increment. To remove a tile and have the counter remain accurate, a designer must press the remove button and then click the tile they want removed. However, currently, a tile that is at full opacity will go back to quarter opacity when tapped, regardless of the current mode. 

Various discussions with engineers illuminated for me how and why the counter should be tied to the current/latest board in the boards vector instead of being tied to the number of touches on the screen. This makes a whole lot of sense when you think about it–who knows how many tiles there are at any given time? The current board! In practice though, it took me a while to absorb this and rather than letting the counter be something implemented later, I impatiently wanted it “to work” now. My way of doing it works in theory, but not well in practice.

For now, the buggy counter will live on and I’ll ignore it while I get to work on what should’ve come first, the model.

How to Add a Vector to Another Vector, or Things I Know But Struggle to Apply

This week I dabbled with creating the counter label and some functionality for the previously made add/remove buttons, but once again I spent the bulk of my time working with buttons. 

With much of the base UI complete, I needed to really start getting into the nitty gritty of getting tiles placed where the designer pressed on the screen, but I had one big question–how on earth do I do that? Posing my query to another engineer over coffee, they posited that one way I could accomplish this would be by turning the screen into a grid of buttons. My wheels immediately began spinning and I was keen on bringing this idea to fruition. 

Coding alongside my mentor, we started by creating a single button that used our tile texture so that the button on the screen looked like one of our blank Mahjong tiles. Having created together a single tile button that appeared on the screen at the specified point {{x,y}, z}, my mentor challenged me to extrapolate that into a function that would return a unique pointer of a TileButton and to then use that function to create a grid.

As per usual, bumps were encountered along the way, but eventually I had written a function named CreateButton that took in the rect of the board and a TileId, and returned a TileButton that could be pushed onto the Views vector. On the screen it looks a lil something like this:

Next I needed to create a grid of tile buttons, which would be a vector of unique pointers of TileButton. Filling the vector was pretty easy as I was able to push a new TileButton onto the button vector using the CreateButton() function I’d written, like this:

buttonVector.push_back(CreateButton(rect, tileId));

Getting the buttonVector into the views vector to display on the screen proved to be an embarrassing challenge for me. As I’d previously done with adding other elements to the views vector, I added a line in my GetActiveSubviews function:

views.push_back(&*buttonVector);  

The compiler was not pleased and gave me an error I didn’t understand to which I turned to Google and Stack Overflow for assistance. This got me nowhere. 

After a lot of Googling and searching through the codebase for a similar situation, I reached out to my project mentor via Slack to talk through the issue. I described that I was trying to add a vector<unique_ptr<T>> (where T is a subclass of View) to a vector<View*>. I was consistently getting hung up on how to “add” one vector to another, as though it had to be something particular with one vector being filled with unique pointers causing .push_back(vector) to not work and that I needed to use type casting or perhaps a different function for adding the vector. 

But as you surely know, vector doesn’t have a function that allows you to insert another vector into it. To add a vector to another vector, you need to implement a for loop that moves each item in the vector to the other vector (which, if vector did have a function to add a vector to itself, that’s how it would have to be implemented).

This “Aha!” moment left me with a lot of mixed feelings. I was pleased to be moving forward with pushing my vector of buttons onto the views vector by adding each button to the views vector, relieved that my code compiled, and satisfied that the grid of buttons had been created. But I was also feeling a bit of shame that I didn’t clue into this elementary concept sooner and without so much hand holding. I know that a vector is essentially just a list and that if you want to put each item from one list into another you are essentially looping through to do the job, but for some reason this knowledge was just out of reach at the time of application. 

This likely won’t be the last time some coding knowledge that I’ve previously learned doesn’t come to the forefront of my mind when it needs to, and I’ll continue to work on being kind to myself when it occurs; in the end, the goal is still to learn and grow, and this week I certainly learned something important about vectors. 

The latest iteration of the Board Editor with a grid of “ghost” tiles for designers to select and add to their board design:

Designing a User Interface

Having implemented an access point for board designers to enter into the Board Editor from the main Mahjong app, the next phase for this project involved conjuring up what we want the user interface to look like and thinking about how we will interact with the Board Editor. 

Upon entering the Board Editor for the first time, I felt the designer should see a blank backdrop, which can be accomplished by essentially just removing the GameView to have the MainView displayed with the BoardEditorView laid on top/pushed into the Views vector. 

Besides the tile free background, there needs to be a way to specify that touches to the backdrop are for adding more tiles or, alternatively, that selecting a specific tile that you no longer want in that position allows the tile to be removed. Two pill buttons at the top of the backdrop could accomplish this, one for triggering an add mode and one for triggering a remove mode, and an indication of which mode is currently selected. Brainium often uses a specific blue color in toggle switches and in the Options Menus of our games to show a specific option is selected, so it makes sense to use this as an indicator that the particular mode is selected.

Finally, one of the important features of a valid board design is that the board includes an even number of tiles. While making a board design, it would be very easy to lose track of the number of tiles that have been placed and thus it makes a lot of sense to include a counter that increments/decrements as tiles are added/removed from the board. Our Mahjong games include an optional Timer that can be enabled and appears at the top of the game screen. This position is a suitable spot for a Counter to appear with a label “Counter” and the current count of tiles displayed below.

Bringing this altogether, the initial UI design for the Board Editor looks something like this:

Initial prototype of Board Editor UI

The other crucial UI component of the Board Editor is the Toolbar, which is part of the MainView. The Toolbar as it is designed for the game includes access to the Options Menu, Themes Menu and Game Menu, as well as the ability to call upon hints to solving a puzzle with the Hint button and the ability to go back a step in one’s progression of moves in the game with the Undo button. Much of the Toolbar functionality could still be used in the Board Editor for the purpose of changing the Backdrop in Themes or exiting the Board Editor through the Game Menu > New Game, however Hint and Undo would not need to be utilized within the Board Editor, so either they could be removed when in the Board Editor or replaced with some other functionality of importance.

Luckily for me, I needed to find a logical place to put two buttons for exporting and saving board designs, and two sensible spaces for buttons just became available. 

All told, here is the very basic prototype of the UI for the Board Editor:

Toolbar with Hint/Undo replaced with Save/Export buttons

Looks pretty good, right? Now I just need to figure out how to program these UI elements in C++. More on that later.

Appreciating the View

Much of my project brainpower this week was spent examining the existing view hierarchy for Brainium Mahjong and thinking about where the Board Editor will fit in and what its own view hierarchy should look like.

At a very basic level, the Mahjong view hierarchy looks something like this:

Starting from the root node and working my way down the tree, I pretty quickly determined that the BoardEditorView should sit at the same functional level as the GameView and should be inserted into the overall view hierarchy at this point. The resulting view hierarchy would now look something like this:

With this decided, the next step was to figure out where in the existing application would be the best entry point for accessing the Board Editor. In certain project builds there are menus for debugging that are accessed in different ways, but that logic didn’t seem appropriate for the Board Editor. In my mind I saw the access point for the Board Editor being a button that would then transport the designer to a clean backdrop display with a simple GUI for adding and removing tiles to create a new board design. Tinkering within the Mahjong app and inspecting the Toolbar, the ideal access point became clear–adding another button to the bottom of the Game menu. 

Current Game menu in Mahjong without the Board Editor

Having successfully built the project in Xcode, it was time to tinker with the MainView to add a Board Editor button to the Game Menu. I began by skimming the MainView.cpp file for how it implemented the Achievements button and began adding code to create the Board Editor button. 

This might be time to mention one important aspect of the Board Editor project. Since this tool lives within the actual Mahjong project and will soon be merged with the main branch, anything related to the Board Editor implementation needs to be written within #if BOARD_EDITOR…#endif to ensure that the Board Editor can only be accessed from a specific Board Editor build or when the “BoardEditor” build configuration is selected in Xcode. Although this can be laborious and seems to dirty the code a bit in cases of numerous #if..#endif guards, the majority of the implementation will be in separate board editor files with a single #if..#endif at the top and bottom of the file.

With some mimicry of existing code and frequent visitation to the Xcode error messages when trying to build, eventually I got a working button added to the Game Menu that when clicked, for the time being, saves the game state and slides the Game Menu down.

Board Editor button in the Game Menu of BoardEditor builds

Next up I’ll be coding the BoardEditorView to open to the selected user Backdrop with no tiles displayed, a tile counter at the top of the screen, and two buttons for adding and removing tiles.

Worth celebrating:

  • Made a new branch “board_editor” and initial commits to the project
  • Successfully created the Board_Editor build configuration and got the project building in Xcode!
  • Began fleshing out the view hierarchy for the Board Editor (read this post)
  • Selected logical UI entry point within the Mahjong project for the Board Editor to be accessed (Toolbar -> Game Menu -> Board Editor)
  • Added BoardEditorView to the view hierarchy
  • Added the Board Editor as an app on App Center for builds to be accessed by internal teams

Where to begin?

This project has been a long time coming, not only within the actual CS program at OSU as the bookend to an educational milestone, but also at work. I proposed the idea of completing my capstone project through Brainium back in the summer of 2021 and the wheels moved swiftly, from bringing this up to my engineering manager as a neat and mutually beneficial way for me to shift into the software engineering role to having the specs of an oft-sidelined project fully fleshed out and a mentor designated. Everything was set on the company side by early last fall and from there I simply had to wait, until now…

Being perhaps overly eager and not the most patient individual, I immediately jumped into Asana and spit out tickets, translating the specs of a MVP into task cards and identifying a laundry list of code components I could do away with once I created a clone of the project repository to make my board editor from. This was my first mistake.

Prior to “Potential Code Components to Disable” I had a card for “Code to Remove”

As I continued combing through the existing code for Mahjong, I continued to find large swaths of code that would simply not be needed in the Mahjong Board Editor I was to build. 

  • In-App Purchase functionality? Let’s get rid of it! 
  • Localization strings? We won’t need those for making Mahjong boards! 
  • Daily Puzzle logic? Once again, cut it out! 

My list grew with all of the pieces that I could eliminate from my cloned project, seemingly making improvements by reducing code and only holding onto what was really necessary for my project. I had it all planned out on my Asana board and included in my initial project setup the subtasks “Create new repository” and “Eliminate unnecessary code (see card)”.

Excited by this early, albeit minimal progress, in my weekly one-on-one with my engineering manager I shared my Asana board and asked to discuss making a new repository on Bitbucket. This is when I realized I definitely hadn’t thought everything through.

Although creating a new repository may have been nice, my manager pointed out that in the long run this would be problematic as changes made to Mahjong would not be applied to the Mahjong Board Editor. Hindsight is 20/20 and this should have been obvious to me, but in my enthusiasm to hit the ground running I hadn’t considered this key detail. Our conversation illuminated a better route—keep all the guts of Mahjong and instead create a new target. Much in the way our apps have a paid and unpaid target, we could create a new target for the Mahjong Board Editor that is in the same project. 

This seemed like the right way to go and I began talking with other engineers about making a new target, the consensus was that it would be tricky, but doable. Logan, a Principal Engineer at Brainium and my capstone mentor, agreed that this would be one route, but he had another idea for how to proceed with the setup and legacy of the Mahjong Board Editor.

Rather than creating a new target, in Xcode we could create a new custom build scheme. Our projects already include a few different schemes like DEBUG and QA, which incorporates a QA menu and various functionalities like advancing to the end of a game, and adding another scheme would not only be easier to do, but also allow for all of the benefits we want with having the Board Editor project exist in the same repo!

And so we’re off to the races with tomorrow marking the start of the Mahjong Board Editor Project and getting to shift some of those task cards from “To Do” to “In Progress”. Wish me luck.