I am currently working on a game where there is a parent-child relationship in a quite small array where;
A `Group` (parent)
has many `Child` objects
where 1 `Child` object can be owned by only 1 `Player`
and a `Player` can only ever own 1 `Child` per group
In the game, a `Child` object is called `Locomotive` (the game is about selling Locomotives) and a given `Player` can own a single locomotive in a given group.
I needed a way to grab all the child objects in a given array that met a series of conditions; in short, I’m after a list of child objects that I can `legally` purchase.
By `legally` I mean from the following rules (in no particular order)
- I want to remove all children where I do not own anything
- I want to remove all children which I already own a locomotive
- I want to remove all children where I don’t have enough money to buy it
- I want to remove all children by inspecting the group to only show those children where the sum total of orders > 0
- I want to remove all children where something has been bought already
Phew, quite a lot of conditions. At first I was not sure how to solve so many conditions.
My first solution was to use a NSPredicate… with a block statement and simply loop through everything.
NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
EngYard_LocomotiveGroup *group = evaluatedObject;
for (EngYard_Locomotive *child in group.locomotives) {
if ( ([child.purchased boolValue] == NO) && (child.owner != player) && (group.purchasePrice < player.cash) && (group.initialOrders>0 || [group.totalOrders integerValue]>0 || group.unlocked==YES) )
{
return YES;
break;
}
return NO;
break;
}
return NO;
}];
This seemed quite a lot of code. Surely there is an easier way to do this?
SUBQUERY to the rescue!
Having played around with the SUBQUERY for a while, I finally got a single-line solution;
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SUBQUERY(locomotives, $rv, $rv.purchased = %@).@count > 0 AND (purchasePrice < %d) AND (initialOrders>0 OR orders.@sum.integerValue>0 OR unlocked==YES)", @NO, player.cash];
I double checked both instances in my logger; and they both return my expected results.
Now on to more complex matters — how to make the AI make a decision to buy.