Source Code and Ideas question

General discussion for players of Oolite.

Moderators: another_commander, winston

Astrobe
---- E L I T E ----
---- E L I T E ----
Posts: 601
Joined: Sun Jul 21, 2013 12:26 pm

Re: Source Code and Ideas question

Post by Astrobe »

I don't know anything about Objective-C, but it seems that you need to pass a custom selector in order to get the sort order you want. The one you use compares according to lexical order. I think you have to define a function or method that converts arguments into integers and return the comparison of those integers.

Another possible method is to pad the numeric values with leading zeros before sorting the array so that they have the same, fixed length (or maybe require that they are written that way in a comment). In this case, sorting by lexical order should work.

Storing things as strings when they actually are of some other type (here integer) is not very good practice in terms of programming. Besides this sorting problem, it will likely cause other problems, like the fact that if someone makes a typo (like "O512", a letter 'O' instead of the figure '0') it will likely screw up your program. But I guess there are other constrains forcing you to do it that way that I ignore.

User avatar
Diziet Sma
---- E L I T E ----
---- E L I T E ----
Posts: 6310
Joined: Mon Apr 06, 2009 12:20 pm
Location: Aboard the Pitviper S.E. "Blackwidow"

Re: Source Code and Ideas question

Post by Diziet Sma »

There are also a number of different sorting algorithms around, some of them rather famous in programming circles, designed for different requirements.. it will be worth your while to research them.. a google-search on "sorting algorithms" will get you started.
Most games have some sort of paddling-pool-and-water-wings beginning to ease you in: Oolite takes the rather more Darwinian approach of heaving you straight into the ocean, often with a brick or two in your pockets for luck. ~ Disembodied

User avatar
JensAyton
Grand Admiral Emeritus
Grand Admiral Emeritus
Posts: 6657
Joined: Sat Apr 02, 2005 2:43 pm
Location: Sweden
Contact:

Re: Source Code and Ideas question

Post by JensAyton »

Diziet Sma wrote:There are also a number of different sorting algorithms around, some of them rather famous in programming circles, designed for different requirements.. it will be worth your while to research them.. a google-search on "sorting algorithms" will get you started.
Please ignore this advice. :-) You really don’t want to be implementing your own sorting algorithm here.

sortedArrayUsingSelector:@selector(compare:) would be correct if you had an array of NSNumbers, but what you have is NSStrings.

Instead, add a method like this in NSStringOOExtensions.m:

Code: Select all

- (NSInteger)oo_compareNumerically:(NSString *)other
{
    return [self compare:other options:NSNumericSearch];
}
Then use sortedArrayUsingSelector:@selector(oo_compareNumerically:).

User avatar
Diziet Sma
---- E L I T E ----
---- E L I T E ----
Posts: 6310
Joined: Mon Apr 06, 2009 12:20 pm
Location: Aboard the Pitviper S.E. "Blackwidow"

Re: Source Code and Ideas question

Post by Diziet Sma »

Heh.. I stand corrected. :lol:

(I still maintain any coder should study sorting algorithms anyway.. it's a fundamental part of learning to program) :wink:
Most games have some sort of paddling-pool-and-water-wings beginning to ease you in: Oolite takes the rather more Darwinian approach of heaving you straight into the ocean, often with a brick or two in your pockets for luck. ~ Disembodied

JD
Deadly
Deadly
Posts: 154
Joined: Thu Nov 25, 2010 10:42 pm
Location: London, UK

Re: Source Code and Ideas question

Post by JD »

Just a minor point, but the figure of 126 has crept into the (non-hex) arrays for the competent rating - it should be 128.

User avatar
Pleb
---- E L I T E ----
---- E L I T E ----
Posts: 884
Joined: Sun Apr 29, 2012 2:23 pm
Location: United Kingdom

Re: Source Code and Ideas question

Post by Pleb »

JensAyton wrote:sortedArrayUsingSelector:@selector(compare:) would be correct if you had an array of NSNumbers, but what you have is NSStrings.

Instead, add a method like this in NSStringOOExtensions.m:

Code: Select all

- (NSInteger)oo_compareNumerically:(NSString *)other
{
    return [self compare:other options:NSNumericSearch];
}
Then use sortedArrayUsingSelector:@selector(oo_compareNumerically:).
I've tried implementing this but it still puts it in a weird order. However I didn't realise these were NSString values and not NSInteger values before, so I will do some more researching to see if this is possible another way. Thank you though! :)
JD wrote:Just a minor point, but the figure of 126 has crept into the (non-hex) arrays for the competent rating - it should be 128.
Thanks for pointing this out, not sure where 126 came from! I've fixed in my code now. :oops:

Another thing I've noticed was that the ☆ and ★ characters I have hardcoded do not display properly when reverting to the hardcoded ratings system... :(

User avatar
Pleb
---- E L I T E ----
---- E L I T E ----
Posts: 884
Joined: Sun Apr 29, 2012 2:23 pm
Location: United Kingdom

Re: Source Code and Ideas question

Post by Pleb »

Okay, building upon what JensAyton said about the values in the array being NSString, I've managed to cobble together a comparison method by modifying one used for putting numbers in strings in reverse order (I then reversed this so it goes the way I want it to):

Code: Select all

- (NSComparisonResult)oo_compareNumerically:(NSString *)str {
    NSNumber * num1 = [NSNumber numberWithInt:[self intValue]];
    NSNumber * num2 = [NSNumber numberWithInt:[str intValue]];

    return [num1 compare:num2];
}
This now puts the numbers in the right order. So this fixes the issue of OXP writers putting values in the oolite_rating_kills and oolite_legal_status_bounty arrays in non-numerical order. However if you swap the first two values on both the oolite_rating_kills and oolite_rating_names arrays, it will show the first rank as "Mostly Harmless" instead of "Harmless". So you'd still need the values in the oolite_rating_names and oolite_legal_status_names arrays to be in the right order. :?

Code: Select all

NSString *OODisplayRatingStringFromKillCount(unsigned kills)
{
	NSArray         *ratingNames = nil;
	NSArray         *ratingKills = nil;
	NSArray 		*sortedRatingKills = nil;
	unsigned		i;
	BOOL			newRatingsInvalid;

	ratingNames = [[UNIVERSE descriptions] oo_arrayForKey:@"oolite_rating_names"];
	ratingKills = [[UNIVERSE descriptions] oo_arrayForKey:@"oolite_rating_kills"];
	sortedRatingKills = [ratingKills sortedArrayUsingSelector:@selector(oo_compareNumerically:)];
	newRatingsInvalid = NO;

	if ([ratingNames count] != [sortedRatingKills count]) 
	{
		newRatingsInvalid = YES;
		OOLog(@"new.ratings.error", @"'oolite_rating_kills' and 'oolite_rating_names' array size in descriptions.plist not equal - reverting to original ratings system.");
	}

	if ([sortedRatingKills oo_unsignedIntAtIndex:0] != 0)
	{
		newRatingsInvalid = YES;
		OOLog(@"new.ratings.error", @"'oolite_rating_kills' array in descriptions.plist does not start at 0 - reverting to original ratings system.");
	}

	if (!newRatingsInvalid)
	{
		for (i = 0; i < [sortedRatingKills count]; ++i)
		{
			if (kills < [sortedRatingKills oo_unsignedIntAtIndex:i])  
			{
				if ([ratingNames oo_stringAtIndex:i-1] != nil)
				{
					return [ratingNames oo_stringAtIndex:i-1];
				}
				else
				{
					newRatingsInvalid = YES;
					OOLog(@"new.ratings.error", @"'nil' value returned when trying to display rating status - reverting to original ratings system.");
					break;
				}
			}
		}
	}

	if (newRatingsInvalid)
	{
		enum { kRatingCount = 9 };

		const unsigned      killThresholds[kRatingCount - 1] =
							{
								0x0008,
								0x0010,
								0x0020,
								0x0040,
								0x0080,
								0x0200,
								0x0A00,
								0x1900
							};

		ratingNames = [NSArray arrayWithObjects:@"Harmless",@"Mostly Harmless",@"Poor",@"Average",@"Above Average",@"Competent",@"Dangerous",@"☆ Deadly ☆",@"★★★ E L I T E ★★★",nil];
		for (i = 0; i < kRatingCount - 1; ++i)
		{
			if (kills < killThresholds[i])  return [ratingNames oo_stringAtIndex:i];
		}

		return [ratingNames oo_stringAtIndex:kRatingCount - 1];
	}

	return [ratingNames oo_stringAtIndex:[sortedRatingKills count] - 1];
}

Code: Select all

NSString *OODisplayStringFromLegalStatus(int legalStatus)
{
	NSArray         *statusNames = nil;
	NSArray         *statusBounty = nil;
	NSArray         *sortedStatusBounty = nil;
	unsigned      	i;
	BOOL           	newStatusInvalid;

	statusNames = [[UNIVERSE descriptions] oo_arrayForKey:@"oolite_legal_status_names"];
	statusBounty = [[UNIVERSE descriptions] oo_arrayForKey:@"oolite_legal_status_bounty"];
	sortedStatusBounty = [statusBounty sortedArrayUsingSelector:@selector(oo_compareNumerically:)];
	newStatusInvalid = NO;

	if ([statusNames count] != [sortedStatusBounty count]) 
	{
		newStatusInvalid = YES;
		OOLog(@"new.legal.status.error", @"'oolite_legal_status_bounty' and 'oolite_legal_status_names' array size in descriptions.plist not equal - reverting to original legal status system.");
	}
	
	if ([sortedStatusBounty oo_unsignedIntAtIndex:0] != 0)
	{
		newStatusInvalid = YES;
		OOLog(@"new.legal.status.error", @"'oolite_legal_status_bounty' array in descriptions.plist does not start at 0 - reverting to original ratings system.");
	}
   
	if (!newStatusInvalid)
	{
		for (i = 0; i < [sortedStatusBounty count]; ++i)
		{
			if (legalStatus < [sortedStatusBounty oo_intAtIndex:i]) 
			{
				if([statusNames oo_stringAtIndex:i-1] != nil)
				{
					return [statusNames oo_stringAtIndex:i-1];
				}
				else
				{
					newStatusInvalid = YES;
					OOLog(@"new.legal.status.error", @"'nil' value returned when trying to display legal status - reverting to original legal status system.");
					break;
				}
			}
		}
	}

	if (newStatusInvalid)
	{
		enum { kStatusCount = 3 };

		const int         statusThresholds[kStatusCount - 1] =
							{
								1,
								51
							};

		statusNames = [NSArray arrayWithObjects:@"Clean",@"Offender",@"Fugitive",nil];
		for (i = 0; i != kStatusCount - 1; ++i)
		{
			if (legalStatus < statusThresholds[i])  return [statusNames oo_stringAtIndex:i];
		}
   
		return [statusNames oo_stringAtIndex:kStatusCount - 1];
	}
   
	return [statusNames oo_stringAtIndex:[sortedStatusBounty count] - 1];
}
Being able to sort the arrays means that, in theory, loading a dictionary should now be possible. However because it does not appear to be OXP-friendly I am not sure it's the right direction (although if it were possible it would look tidier)... :|

EDIT: So...things still left to fix:
  • Minus values need to make code revert to hardcoded ratings and legal status system.
  • Need to be able to sort string arrays (oolite_rating_names and oolite_legal_status_names) to match order of integer arrays (oolite_rating_kills and oolite_legal_status_bounty)

User avatar
cim
Quite Grand Sub-Admiral
Quite Grand Sub-Admiral
Posts: 4018
Joined: Fri Nov 11, 2011 6:19 pm

Re: Source Code and Ideas question

Post by cim »

Pleb wrote:[*]Need to be able to sort string arrays (oolite_rating_names and oolite_legal_status_names) to match order of integer arrays (oolite_rating_kills and oolite_legal_status_bounty)
The easiest approach is probably to create a temporary NSMutableDictionary within the function with the numeric value as the key and the string as the value. Then, sort the integer array, proceed as before, and get the appropriate string for the integer value from the dictionary

For your current code, what happens if you put:

Code: Select all

   (
      0,
      8,
      16,
      32,
      64,
      126,
      512,
      2560,
      6400
   );
in descriptions.plist (the lack of quotes is the key difference: those *are* now NSNumbers rather than NSStrings) A really inconsistent OXP writer could even do

Code: Select all

   (
      0,
      "8",
      16,
      32,
      "64",
      126,
      512,
      "2560",
      6400
   );

User avatar
JensAyton
Grand Admiral Emeritus
Grand Admiral Emeritus
Posts: 6657
Joined: Sat Apr 02, 2005 2:43 pm
Location: Sweden
Contact:

Re: Source Code and Ideas question

Post by JensAyton »

cim wrote:(the lack of quotes is the key difference: those *are* now NSNumbers rather than NSStrings
’Fraid not; there are no NSNumbers in OpenStep format plists.

User avatar
Pleb
---- E L I T E ----
---- E L I T E ----
Posts: 884
Joined: Sun Apr 29, 2012 2:23 pm
Location: United Kingdom

Re: Source Code and Ideas question

Post by Pleb »

JensAyton wrote:
cim wrote:(the lack of quotes is the key difference: those *are* now NSNumbers rather than NSStrings
’Fraid not; there are no NSNumbers in OpenStep format plists.
So if I'm following what you're saying correctly (I'm at work so I can't test this yet) the quotation marks wouldn't make a difference because it still reads the values as NSString and not NSNumbers? Thank you for the NSMutableDictionary idea cim, I will try this out later! :D

Zireael
---- E L I T E ----
---- E L I T E ----
Posts: 1396
Joined: Tue Nov 09, 2010 1:44 pm

Re: Source Code and Ideas question

Post by Zireael »

Following this very intently...

User avatar
cim
Quite Grand Sub-Admiral
Quite Grand Sub-Admiral
Posts: 4018
Joined: Fri Nov 11, 2011 6:19 pm

Re: Source Code and Ideas question

Post by cim »

JensAyton wrote:
cim wrote:(the lack of quotes is the key difference: those *are* now NSNumbers rather than NSStrings
’Fraid not; there are no NSNumbers in OpenStep format plists.
Okay, that's going to make this harder to test.

Something like this as an OXP's descriptions.plist is still possible, though.

Code: Select all

<dict>
<key>oolite_legal_status_bounty</key>
<array>
<integer>0</integer>
<integer>1</integer>
<integer>51</integer> <!-- <string>51</string> is also a possibility to be really awkward -->
</array>
</dict>

User avatar
Pleb
---- E L I T E ----
---- E L I T E ----
Posts: 884
Joined: Sun Apr 29, 2012 2:23 pm
Location: United Kingdom

Re: Source Code and Ideas question

Post by Pleb »

Okay, making some real progress here now. I have made modifications to the code now that will ensure that if someone has a descriptions.plist file looking either like this:

Code: Select all

{	
	"oolite_rating_kills" = 
	(
		"15",
		"8",
		0,
		"32",
		"64",
		"6400",
		"512",
		"2560",
		"128"
	);
   
	"oolite_rating_names" = 
	(
		"3Poor",
		"2Mostly Harmless",
		"1Harmless",
		"4Average",
		"5Above Average",
		"9★★★ E L I T E ★★★",
		"7Dangerous",
		"8☆ Deadly ☆",
		"6Competent"
	);
   
	"oolite_legal_status_bounty" =
	(
		"1",
		0,
		"51"
	);
   
	"oolite_legal_status_names" =
	(
		"2Offender",
		"1Clean",
		"3Fugitive"
	);
}
Or like this:

Code: Select all

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>oolite_rating_kills</key>
	<array>
		<integer>16</integer>
		<integer>8</integer>
		<string>0</string>
		<integer>32</integer>
		<integer>64</integer>
		<integer>6400</integer>
		<integer>512</integer>
		<integer>2560</integer>
		<integer>128</integer>
	</array>
	
	<key>oolite_rating_names</key>
	<array>
		<string>3Poor</string>
		<string>2Mostly Harmless</string>
		<string>1Harmless</string>
		<string>4Average</string>
		<string>5Above Average</string>
		<string>9★★★ E L I T E ★★★</string>
		<string>7Dangerous</string>
		<string>8☆ Deadly ☆</string>
		<string>6Competent</string>
	</array>
	
	<key>oolite_legal_status_bounty</key>
	<array>
		<integer>1</integer>
		<string>0</string>
		<integer>51</integer>
	</array>
	
	<key>oolite_legal_status_names</key>
	<array>
		<string>2Offender</string>
		<string>1Clean</string>
		<string>3Fugitive</string>
	</array>
</dict>
</plist>
Then it loads in the right order, and does not crash (as it would have done before without the new changes). Essentially I have put in safeguards that if the values in the array were actually NSNumbers and not NSStrings then it will convert the NSNumbers to NSStrings so that the code doesn't break. It should be noted that I've only put numbers at the start of the ratings and legal status text so I can tell if it's failed without having to look in the log (but there are obviously still log entries for when something does go wrong).

Code: Select all

NSString *OODisplayRatingStringFromKillCount(unsigned kills)
{
	NSArray         *ratingNames = nil;
	NSArray         *ratingKills = nil;
	NSMutableArray	*mutableRatingKills = [[NSMutableArray alloc] init];
	NSArray 		*sortedRatingKills = nil;
	NSMutableDictionary 		*ratingNamesDict = [[NSMutableDictionary alloc] init];
	NSString 		*killString;
	NSString 		*ratingString;
	unsigned		i;
	BOOL			newRatingsInvalid;

	ratingNames = [[UNIVERSE descriptions] oo_arrayForKey:@"oolite_rating_names"];
	ratingKills = [[UNIVERSE descriptions] oo_arrayForKey:@"oolite_rating_kills"];
	newRatingsInvalid = NO;
	
	for (i = 0; i < [ratingKills count]; ++i)
	{
		ratingString = [NSString stringWithFormat:@"%u", [ratingKills oo_intAtIndex:i]];
		[mutableRatingKills addObject:ratingString];
	}
	
	sortedRatingKills = [mutableRatingKills sortedArrayUsingSelector:@selector(oo_compareNumerically:)];
	
	for (i = 0; i < [mutableRatingKills count]; ++i)
	{
		killString = [NSString stringWithFormat:@"%u", [mutableRatingKills oo_unsignedIntAtIndex:i]];
		ratingString = [ratingNames oo_stringAtIndex:i];
		[ratingNamesDict setObject:ratingString forKey:killString];
	}

	if ([ratingNames count] != [sortedRatingKills count]) 
	{
		newRatingsInvalid = YES;
		OOLog(@"new.ratings.error", @"'oolite_rating_kills' and 'oolite_rating_names' array size in descriptions.plist not equal - reverting to original ratings system.");
	}

	if ([sortedRatingKills oo_unsignedIntAtIndex:0] != 0)
	{
		newRatingsInvalid = YES;
		OOLog(@"new.ratings.error", @"'oolite_rating_kills' array in descriptions.plist does not start at 0 - reverting to original ratings system.");
	}

	if (!newRatingsInvalid)
	{
		for (i = 0; i < [sortedRatingKills count]; ++i)
		{
			if (kills < [sortedRatingKills oo_unsignedIntAtIndex:i])  
			{
				killString = [NSString stringWithFormat:@"%u", [sortedRatingKills oo_unsignedIntAtIndex:i-1]];
				if ([ratingNamesDict objectForKey:killString] != nil)
				{
					return [ratingNamesDict objectForKey:killString];
				}
				else
				{
					newRatingsInvalid = YES;
					OOLog(@"new.ratings.error", @"'nil' value returned when trying to display rating status - reverting to original ratings system.");
					break;
				}
			}
		}
	}

	if (newRatingsInvalid)
	{
		enum { kRatingCount = 9 };

		const unsigned      killThresholds[kRatingCount - 1] =
							{
								0x0008,
								0x0010,
								0x0020,
								0x0040,
								0x0080,
								0x0200,
								0x0A00,
								0x1900
							};

		ratingNames = [NSArray arrayWithObjects:@"Harmless",@"Mostly Harmless",@"Poor",@"Average",@"Above Average",@"Competent",@"Dangerous",@"☆ Deadly ☆",@"★★★ E L I T E ★★★",nil];
		for (i = 0; i < kRatingCount - 1; ++i)
		{
			if (kills < killThresholds[i])  return [ratingNames oo_stringAtIndex:i];
		}

		return [ratingNames oo_stringAtIndex:kRatingCount - 1];
	}
	
	i = [sortedRatingKills count] - 1;
	killString = [NSString stringWithFormat:@"%u", [sortedRatingKills oo_unsignedIntAtIndex:i]];
	return [ratingNamesDict objectForKey:killString];
}

Code: Select all

NSString *OODisplayStringFromLegalStatus(int legalStatus)
{
	NSArray         *statusNames = nil;
	NSArray			*statusBounty = nil;
	NSMutableArray	*mutableStatusBounty = [[NSMutableArray alloc] init];
	NSArray         *sortedStatusBounty = nil;
	NSMutableDictionary 		*statusNamesDict = [[NSMutableDictionary alloc] init];
	NSString 		*bountyString;
	NSString 		*statusString;
	unsigned      	i;
	BOOL           	newStatusInvalid;

	statusNames = [[UNIVERSE descriptions] oo_arrayForKey:@"oolite_legal_status_names"];
	statusBounty = [[UNIVERSE descriptions] oo_arrayForKey:@"oolite_legal_status_bounty"];
	newStatusInvalid = NO;
	
	for (i = 0; i < [statusBounty count]; ++i)
	{
		bountyString = [NSString stringWithFormat:@"%u", [statusBounty oo_intAtIndex:i]];
		[mutableStatusBounty addObject:bountyString];
	}
	
	sortedStatusBounty = [mutableStatusBounty sortedArrayUsingSelector:@selector(oo_compareNumerically:)];
	
	for (i = 0; i < [mutableStatusBounty count]; ++i)
	{
		bountyString = [NSString stringWithFormat:@"%u", [mutableStatusBounty oo_unsignedIntAtIndex:i]];
		statusString = [statusNames oo_stringAtIndex:i];
		[statusNamesDict setObject:statusString forKey:bountyString];
	}

	if ([statusNames count] != [sortedStatusBounty count]) 
	{
		newStatusInvalid = YES;
		OOLog(@"new.legal.status.error", @"'oolite_legal_status_bounty' and 'oolite_legal_status_names' array size in descriptions.plist not equal - reverting to original legal status system.");
	}
	
	if ([sortedStatusBounty oo_unsignedIntAtIndex:0] != 0)
	{
		newStatusInvalid = YES;
		OOLog(@"new.legal.status.error", @"'oolite_legal_status_bounty' array in descriptions.plist does not start at 0 - reverting to original ratings system.");
	}
   
	if (!newStatusInvalid)
	{
		for (i = 0; i < [sortedStatusBounty count]; ++i)
		{
			if (legalStatus < [sortedStatusBounty oo_intAtIndex:i]) 
			{
				bountyString = [NSString stringWithFormat:@"%u", [sortedStatusBounty oo_unsignedIntAtIndex:i-1]];
				if ([statusNamesDict objectForKey:bountyString] != nil)
				{
					return [statusNamesDict objectForKey:bountyString];
				}
				else
				{
					newStatusInvalid = YES;
					OOLog(@"new.legal.status.error", @"'nil' value returned when trying to display legal status - reverting to original legal status system.");
					break;
				}
			}
		}
	}

	if (newStatusInvalid)
	{
		enum { kStatusCount = 3 };

		const int         statusThresholds[kStatusCount - 1] =
							{
								1,
								51
							};

		statusNames = [NSArray arrayWithObjects:@"Clean",@"Offender",@"Fugitive",nil];
		for (i = 0; i != kStatusCount - 1; ++i)
		{
			if (legalStatus < statusThresholds[i])  return [statusNames oo_stringAtIndex:i];
		}
   
		return [statusNames oo_stringAtIndex:kStatusCount - 1];
	}
   
	i = [sortedStatusBounty count] - 1;
	bountyString = [NSString stringWithFormat:@"%u", [sortedStatusBounty oo_unsignedIntAtIndex:i]];
	return [statusNamesDict objectForKey:bountyString];
}
So, only thing I haven't fixed here yet (based on tests provided) is negative values but it's getting late so this may have to wait until tomorrow. Also need to figure out a way to have the ☆ and ★ appear properly in the hardcoded bits, as at the moment they do not... But I wanted to share what I had so far! :)

User avatar
Pleb
---- E L I T E ----
---- E L I T E ----
Posts: 884
Joined: Sun Apr 29, 2012 2:23 pm
Location: United Kingdom

Re: Source Code and Ideas question

Post by Pleb »

Sorry for the double post, but I have fixed the negative value problem:

Code: Select all

NSString *OODisplayRatingStringFromKillCount(unsigned kills)
{
	NSArray         *ratingNames = nil;
	NSArray         *ratingKills = nil;
	NSMutableArray	*mutableRatingKills = [[NSMutableArray alloc] init];
	NSArray 		*sortedRatingKills = nil;
	NSMutableDictionary 		*ratingNamesDict = [[NSMutableDictionary alloc] init];
	NSString 		*killString;
	NSString 		*ratingString;
	unsigned		i;
	int				killInt;
	BOOL			newRatingsInvalid;

	ratingNames = [[UNIVERSE descriptions] oo_arrayForKey:@"oolite_rating_names"];
	ratingKills = [[UNIVERSE descriptions] oo_arrayForKey:@"oolite_rating_kills"];
	newRatingsInvalid = NO;
	killInt = 0;
	
	for (i = 0; i < [ratingKills count]; ++i)
	{
		ratingString = [NSString stringWithFormat:@"%u", [ratingKills oo_intAtIndex:i]];
		killInt = [ratingString integerValue];
		if (killInt < 0)
		{
			newRatingsInvalid = YES;
			OOLog(@"new.ratings.error", @"'oolite_rating_kills' array in descriptions.plist contains a negative value - reverting to original legal status system.");
			break;
		}
	}
	
	for (i = 0; i < [ratingKills count]; ++i)
	{
		ratingString = [NSString stringWithFormat:@"%u", [ratingKills oo_intAtIndex:i]];
		[mutableRatingKills addObject:ratingString];
	}
	
	sortedRatingKills = [mutableRatingKills sortedArrayUsingSelector:@selector(oo_compareNumerically:)];
	
	for (i = 0; i < [mutableRatingKills count]; ++i)
	{
		killString = [NSString stringWithFormat:@"%u", [mutableRatingKills oo_unsignedIntAtIndex:i]];
		ratingString = [ratingNames oo_stringAtIndex:i];
		[ratingNamesDict setObject:ratingString forKey:killString];
	}

	if ([ratingNames count] != [sortedRatingKills count] && !newRatingsInvalid) 
	{
		newRatingsInvalid = YES;
		OOLog(@"new.ratings.error", @"'oolite_rating_kills' and 'oolite_rating_names' array size in descriptions.plist not equal - reverting to original ratings system.");
	}

	if ([sortedRatingKills oo_unsignedIntAtIndex:0] != 0 && !newRatingsInvalid)
	{
		newRatingsInvalid = YES;
		OOLog(@"new.ratings.error", @"'oolite_rating_kills' array in descriptions.plist does not start at 0 - reverting to original ratings system.");
	}

	if (!newRatingsInvalid)
	{
		for (i = 0; i < [sortedRatingKills count]; ++i)
		{
			if (kills < [sortedRatingKills oo_unsignedIntAtIndex:i])  
			{
				killString = [NSString stringWithFormat:@"%u", [sortedRatingKills oo_unsignedIntAtIndex:i-1]];
				if ([ratingNamesDict objectForKey:killString] != nil)
				{
					return [ratingNamesDict objectForKey:killString];
				}
				else
				{
					newRatingsInvalid = YES;
					OOLog(@"new.ratings.error", @"'nil' value returned when trying to display rating status - reverting to original ratings system.");
					break;
				}
			}
		}
	}

	if (newRatingsInvalid)
	{
		enum { kRatingCount = 9 };

		const unsigned      killThresholds[kRatingCount - 1] =
							{
								0x0008,
								0x0010,
								0x0020,
								0x0040,
								0x0080,
								0x0200,
								0x0A00,
								0x1900
							};

		ratingNames = [NSArray arrayWithObjects:@"Harmless",@"Mostly Harmless",@"Poor",@"Average",@"Above Average",@"Competent",@"Dangerous",@"☆ Deadly ☆",@"★★★ E L I T E ★★★",nil];
		for (i = 0; i < kRatingCount - 1; ++i)
		{
			if (kills < killThresholds[i])  return [ratingNames oo_stringAtIndex:i];
		}

		return [ratingNames oo_stringAtIndex:kRatingCount - 1];
	}
	
	i = [sortedRatingKills count] - 1;
	killString = [NSString stringWithFormat:@"%u", [sortedRatingKills oo_unsignedIntAtIndex:i]];
	return [ratingNamesDict objectForKey:killString];
}

Code: Select all

NSString *OODisplayStringFromLegalStatus(int legalStatus)
{
	NSArray         *statusNames = nil;
	NSArray			*statusBounty = nil;
	NSMutableArray	*mutableStatusBounty = [[NSMutableArray alloc] init];
	NSArray         *sortedStatusBounty = nil;
	NSMutableDictionary 		*statusNamesDict = [[NSMutableDictionary alloc] init];
	NSString 		*bountyString;
	NSString 		*statusString;
	unsigned      	i;
	int				bountyInt;
	BOOL           	newStatusInvalid;

	statusNames = [[UNIVERSE descriptions] oo_arrayForKey:@"oolite_legal_status_names"];
	statusBounty = [[UNIVERSE descriptions] oo_arrayForKey:@"oolite_legal_status_bounty"];
	newStatusInvalid = NO;
	bountyInt = 0;
	
	for (i = 0; i < [statusBounty count]; ++i)
	{
		bountyString = [NSString stringWithFormat:@"%u", [statusBounty oo_intAtIndex:i]];
		bountyInt = [bountyString integerValue];
		if (bountyInt < 0)
		{
			newStatusInvalid = YES;
			OOLog(@"new.legal.status.error", @"'oolite_legal_status_bounty' array in descriptions.plist contains a negative value - reverting to original legal status system.");
			break;
		}
	}
	
	for (i = 0; i < [statusBounty count]; ++i)
	{
		bountyString = [NSString stringWithFormat:@"%u", [statusBounty oo_intAtIndex:i]];
		[mutableStatusBounty addObject:bountyString];
	}
	
	sortedStatusBounty = [mutableStatusBounty sortedArrayUsingSelector:@selector(oo_compareNumerically:)];
	
	for (i = 0; i < [mutableStatusBounty count]; ++i)
	{
		bountyString = [NSString stringWithFormat:@"%u", [mutableStatusBounty oo_unsignedIntAtIndex:i]];
		statusString = [statusNames oo_stringAtIndex:i];
		[statusNamesDict setObject:statusString forKey:bountyString];
	}

	if ([statusNames count] != [sortedStatusBounty count] && !newStatusInvalid) 
	{
		newStatusInvalid = YES;
		OOLog(@"new.legal.status.error", @"'oolite_legal_status_bounty' and 'oolite_legal_status_names' array size in descriptions.plist not equal - reverting to original legal status system.");
	}
	
	if ([sortedStatusBounty oo_unsignedIntAtIndex:0] != 0 && !newStatusInvalid)
	{
		newStatusInvalid = YES;
		OOLog(@"new.legal.status.error", @"'oolite_legal_status_bounty' array in descriptions.plist does not start at 0 - reverting to original ratings system.");
	}
   
	if (!newStatusInvalid)
	{
		for (i = 0; i < [sortedStatusBounty count]; ++i)
		{
			if (legalStatus < [sortedStatusBounty oo_intAtIndex:i]) 
			{
				bountyString = [NSString stringWithFormat:@"%u", [sortedStatusBounty oo_unsignedIntAtIndex:i-1]];
				if ([statusNamesDict objectForKey:bountyString] != nil)
				{
					return [statusNamesDict objectForKey:bountyString];
				}
				else
				{
					newStatusInvalid = YES;
					OOLog(@"new.legal.status.error", @"'nil' value returned when trying to display legal status - reverting to original legal status system.");
					break;
				}
			}
		}
	}

	if (newStatusInvalid)
	{
		enum { kStatusCount = 3 };

		const int         statusThresholds[kStatusCount - 1] =
							{
								1,
								51
							};

		statusNames = [NSArray arrayWithObjects:@"Clean",@"Offender",@"Fugitive",nil];
		for (i = 0; i != kStatusCount - 1; ++i)
		{
			if (legalStatus < statusThresholds[i])  return [statusNames oo_stringAtIndex:i];
		}
   
		return [statusNames oo_stringAtIndex:kStatusCount - 1];
	}
   
	i = [sortedStatusBounty count] - 1;
	bountyString = [NSString stringWithFormat:@"%u", [sortedStatusBounty oo_unsignedIntAtIndex:i]];
	return [statusNamesDict objectForKey:bountyString];
}
This now detects if there is a negative value. I've tested this using both layouts of plist files used in post above and this works.

Now if I could only fix the ☆ and ★ in the hardcoded bit... :roll:

User avatar
cim
Quite Grand Sub-Admiral
Quite Grand Sub-Admiral
Posts: 4018
Joined: Fri Nov 11, 2011 6:19 pm

Re: Source Code and Ideas question

Post by cim »

Looking good. Another tip, then: you have two increasingly long functions which are essentially the same apart from:
- choice of variable names
- two string constants to do the initial extraction from descriptions.plist
- two array constants for the fallback
You should therefore be able to replace them with one long function and two short ones.

The stars aren't showing up because the translation from Unicode to Oolite's non-standard 256-character encoding only happens when processing text from plists. A literal string is just treated as is. So you'll need to embed the characters in Oolite's charset - octal notation is probably the shortest way: you can find the stars' byte numbers by looking at the oolite-font texture or plist.

Post Reply