This is a continuation from Part 1, where I discuss the need to write a proper liquidate algorithm for Soda Dungeon. So let’s jump right in.

Problem Statement: Given a list of items of varying quantities and levels, liquidate (sell off) all but the maximum amount of each that can be equipped, while making sure those kept are also the most valuable. Do not remove any items that are currently equipped by a character, regardless of value.

You can check out the full code over on Pastebin.

There are a lot of extra details in the class itself, but I’m just going to focus on the main function here: calculateLiquidateValue(). It’s worth noting that the Liquidator class had to be designed to work in two parts. First, calculate the liquidate- then actually do it. This is because when the user clicks the ‘Liquidate’ option, we simply tell them how much they will receive, without actually enacting the liquidation until they give us the go-ahead.

Let’s look at a quick test case:

Item Level Quantity Equip Count Dagger 10 3 2 Dagger 9 1 0 Dagger 8 1 1 Dagger 7 3 1 Dagger 6 2 0

We’ll want to keep three of the level 10, one level 9, and one level 8. We’ll also keep one level 7 because it’s equipped, and none of the level 6.

First, we start by resetting and declaring some variables.

liquidateValue = 0;

liquidatedQuantities = new StringMap<Int>();



var numToKeepOfBaseId:Int;

var numKeptOfBaseId:Int;

var baseIdsCovered:Array<String> = [];

var curBaseIdGroup:Array<Item> = [];



var tempItem:Item;

var numKeptOfCurItem;

var numLiquidatedOfCurItem;

First we reset the liquidate value and quantity list, which belong to the class itself. These are used later to actually enact the liquidation. The rest of the variables are local to this function only. As we loop through and examine each item, we need to be aware of how many we are keeping, and also how many we are keeping for any item that shares that same base id. In the table above, each type of Dagger has its own unique id, but they all share the same base id.

for (item in itemListToLiquidate)

{

if (idIsInList(item.baseId, baseIdsCovered)) continue;

baseIdsCovered.push(item.baseId);



if (item.cannotBeSold) continue;

For each item in the list, we stop to see if we have already examined its base id. If so, continue to the next item. Otherwise, mark its base id as ‘examined.’ Lastly, make sure this item can even be sold at all. Unique legendaries like the Soda Stein and Widowmaker can’t be sold, so we skip them entirely.

if (item.type == Item.SPECIAL_ITEM) numToKeepOfBaseId = 10;

else numToKeepOfBaseId = 5;

numKeptOfBaseId = 0;

Next, decide how many of the current base id we can keep. Swords/Armor are limited to five, but we can keep ten of any special item since some classes can equip two of them. We then set ‘numKeptOfBaseId’ to 0, because, well, we haven’t kept any just yet.

clearArray(curBaseIdGroup);

for (i in 0...itemListToLiquidate.length){

if (itemListToLiquidate[i].baseId == item.baseId) {

curBaseIdGroup.push(itemListToLiquidate[i]);

}

}

curBaseIdGroup.sort(compareLevel);

Now that we know what base id we’re working with, we then iterate the full item list one more time to pull out all items that share that same base id. We then sort by each item’s level, descending, so we get a nice ordered list that looks pretty close to the table shown up top. Remember: this operation is going to take care of all items of a particular base id. This inner section will only run once per base id, due to us tracking which ones have already been covered.

for (i in 0...curBaseIdGroup.length)

{

numKeptOfCurItem = 0;

tempItem = curBaseIdGroup[i];



numKeptOfCurItem = tempItem.equipCount;

numKeptOfBaseId += tempItem.equipCount;

So now we examine each item in our sublist. We reset numKept to 0 for each new item. We then start with a very easy given: We’re at least going to keep as many as are currently equipped. It doesn’t matter what level the item is, or how many we have, we never want to get rid of something that is already equipped.

for (j in 0...tempItem.getAvailableQuantity())

{

if (numKeptOfBaseId < numToKeepOfBaseId)

{

numKeptOfBaseId ++;

numKeptOfCurItem ++;

}

else break;

}

Next is the slightly tricky part. We’ve kept as many as are equipped, but we still need to keep as many as we can until the limit for our base id has been satisfied. We’re safe to do this because our list is sorted by the highest level, descending. That means each item we examine is the highest or next highest level available to keep. We step through the item’s remaining inventory, one at a time, until we satisfy our conditions. The ‘getAvailableQuantity’ function simply returns the total quantity for an item, minus its equip count. So as we step, if we have still not kept enough of this base id, we keep it. If the item doesn’t have a quantity high enough to satisfy our demand, the loop will naturally exit as we reach the end of the item’s available quantity. The loop will continue to the next item: numKeptOfCurItem will reset, but numKeptOfBaseId will remain where we left off at.

The loop will continue to examine every item to make sure we are at least keeping the amount of it that we have equipped.

numLiquidatedOfCurItem = tempItem.quantity - numKeptOfCurItem;

liquidateValue += numLiquidatedOfCurItem * tempItem.getResaleValue();



liquidatedQuantities.set( tempItem.id, numLiquidatedOfCurItem);

The last step is to simply record our progress. Since we know how many we are keeping of an item, we also know how many we are getting rid of. So we multiply that by the item’s resale value, and keep a running total of how much the user will earn for their liquidate. We also make note of the current item’s id and how many we are getting rid of in a separate list. If the user confirms their liquidate, we revisit this list so we know what changes we actually can finalize with the item list located in the save file.

There are other small details to cover, such as how the enactLiquidation function carries out its duties. But if you’ve followed along with my clunky little algorithm so far, the enact part should be cake. Also, I just wanted to focus on the core of the liquidate process for this post. I don’t do very many programming write ups, so hopefully I explained myself succinctly while still making sense. Let me know!