Line data Source code
1 0 : /*
2 :
3 : OORoleSet.m
4 :
5 :
6 : Copyright (C) 2007-2013 Jens Ayton
7 :
8 : Permission is hereby granted, free of charge, to any person obtaining a copy
9 : of this software and associated documentation files (the "Software"), to deal
10 : in the Software without restriction, including without limitation the rights
11 : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 : copies of the Software, and to permit persons to whom the Software is
13 : furnished to do so, subject to the following conditions:
14 :
15 : The above copyright notice and this permission notice shall be included in all
16 : copies or substantial portions of the Software.
17 :
18 : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 : SOFTWARE.
25 :
26 : */
27 :
28 : #import "OORoleSet.h"
29 :
30 : #import "OOStringParsing.h"
31 : #import "OOCollectionExtractors.h"
32 : #import "OOLogging.h"
33 :
34 :
35 : @interface OORoleSet (OOPrivate)
36 :
37 0 : - (id)initWithRolesAndProbabilities:(NSDictionary *)dict;
38 :
39 : @end
40 :
41 :
42 : @implementation OORoleSet
43 :
44 : + (instancetype) roleSetWithString:(NSString *)roleString
45 : {
46 : return [[[self alloc] initWithRoleString:roleString] autorelease];
47 : }
48 :
49 :
50 : + (instancetype) roleSetWithRole:(NSString *)role probability:(float)probability
51 : {
52 : return [[[self alloc] initWithRole:role probability:probability] autorelease];
53 : }
54 :
55 : - (id)initWithRoleString:(NSString *)roleString
56 : {
57 : NSDictionary *dict = nil;
58 :
59 : dict = OOParseRolesFromString(roleString);
60 : return [self initWithRolesAndProbabilities:dict];
61 : }
62 :
63 :
64 : - (id)initWithRole:(NSString *)role probability:(float)probability
65 : {
66 : NSDictionary *dict = nil;
67 :
68 : if (role != nil && 0 <= probability)
69 : {
70 : dict = [NSDictionary dictionaryWithObject:[NSNumber numberWithFloat:probability] forKey:role];
71 : }
72 : return [self initWithRolesAndProbabilities:dict];
73 : }
74 :
75 :
76 0 : - (void)dealloc
77 : {
78 : [_roleString autorelease];
79 : [_rolesAndProbabilities autorelease];
80 : [_roles autorelease];
81 :
82 : [super dealloc];
83 : }
84 :
85 :
86 0 : - (NSString *)description
87 : {
88 : return [NSString stringWithFormat:@"<%@ %p>{%@}", [self class], self, [self roleString]];
89 : }
90 :
91 :
92 0 : - (BOOL)isEqual:(id)other
93 : {
94 : if ([other isKindOfClass:[OORoleSet class]])
95 : {
96 : return [_rolesAndProbabilities isEqual:[other rolesAndProbabilities]];
97 : }
98 : else return NO;
99 : }
100 :
101 :
102 0 : - (NSUInteger)hash
103 : {
104 : return [_rolesAndProbabilities hash];
105 : }
106 :
107 :
108 0 : - (id)copyWithZone:(NSZone *)zone
109 : {
110 : // Note: since object is immutable, a copy is no different from the original.
111 : return [self retain];
112 : }
113 :
114 :
115 : - (NSString *)roleString
116 : {
117 : NSArray *roles = nil;
118 : NSEnumerator *roleEnum = nil;
119 : NSString *role = nil;
120 : float probability;
121 : NSMutableString *result = nil;
122 : BOOL first = YES;
123 :
124 : if (_roleString == nil)
125 : {
126 : // Construct role string. We always do this so that it's in a normalized form.
127 : result = [NSMutableString string];
128 : roles = [self sortedRoles];
129 : for (roleEnum = [roles objectEnumerator]; (role = [roleEnum nextObject]); )
130 : {
131 : if (!first) [result appendString:@" "];
132 : else first = NO;
133 :
134 : [result appendString:role];
135 :
136 : probability = [self probabilityForRole:role];
137 : if (probability != 1.0f)
138 : {
139 : [result appendFormat:@"(%g)", probability];
140 : }
141 : }
142 :
143 : _roleString = [result copy];
144 : }
145 :
146 : return _roleString;
147 : }
148 :
149 :
150 : - (BOOL)hasRole:(NSString *)role
151 : {
152 : return role != nil && [_rolesAndProbabilities objectForKey:role] != nil;
153 : }
154 :
155 :
156 : - (float)probabilityForRole:(NSString *)role
157 : {
158 : return [_rolesAndProbabilities oo_floatForKey:role defaultValue:0.0f];
159 : }
160 :
161 :
162 : - (BOOL)intersectsSet:(id)set
163 : {
164 : if ([set isKindOfClass:[OORoleSet class]]) set = [set roles];
165 : else if (![set isKindOfClass:[NSSet class]]) return NO;
166 :
167 : return [[self roles] intersectsSet:set];
168 : }
169 :
170 :
171 : - (NSSet *)roles
172 : {
173 : if (_roles == nil)
174 : {
175 : _roles = [[NSSet alloc] initWithArray:[_rolesAndProbabilities allKeys]];
176 : }
177 : return _roles;
178 : }
179 :
180 :
181 : - (NSArray *)sortedRoles
182 : {
183 : return [[_rolesAndProbabilities allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
184 : }
185 :
186 :
187 : - (NSDictionary *)rolesAndProbabilities
188 : {
189 : return _rolesAndProbabilities;
190 : }
191 :
192 :
193 : - (NSString *)anyRole
194 : {
195 : NSEnumerator *roleEnum = nil;
196 : NSString *role = nil;
197 : float prob, selected;
198 :
199 : selected = randf() * _totalProb;
200 : prob = 0.0f;
201 :
202 : if ([_rolesAndProbabilities count] == 0) return nil;
203 :
204 : for (roleEnum = [_rolesAndProbabilities keyEnumerator]; (role = [roleEnum nextObject]); )
205 : {
206 : prob += [_rolesAndProbabilities oo_floatForKey:role];
207 : if (selected <= prob) break;
208 : }
209 : if (role == nil)
210 : {
211 : role = [[self roles] anyObject];
212 : OOLog(@"roleSet.anyRole.failed", @"Could not get a weighted-random role from role set %@, returning unweighted selection %@. TotalProb: %g, selected: %g, prob at end: %f", self, role, _totalProb, selected, prob);
213 : }
214 : return role;
215 : }
216 :
217 :
218 : - (id)roleSetWithAddedRoleIfNotSet:(NSString *)role probability:(float)probability
219 : {
220 : NSMutableDictionary *dict = nil;
221 :
222 : if (role == nil || probability < 0 || ([self hasRole:role] && [self probabilityForRole:role] == probability))
223 : {
224 : return [[self copy] autorelease];
225 : }
226 :
227 : dict = [[_rolesAndProbabilities mutableCopy] autorelease];
228 : [dict setObject:[NSNumber numberWithFloat:probability] forKey:role];
229 : return [[[[self class] alloc] initWithRolesAndProbabilities:dict] autorelease];
230 : }
231 :
232 :
233 : - (id)roleSetWithAddedRole:(NSString *)role probability:(float)probability
234 : {
235 : NSMutableDictionary *dict = nil;
236 :
237 : if (role == nil || probability < 0 || [self hasRole:role])
238 : {
239 : return [[self copy] autorelease];
240 : }
241 :
242 : dict = [[_rolesAndProbabilities mutableCopy] autorelease];
243 : [dict setObject:[NSNumber numberWithFloat:probability] forKey:role];
244 : return [[[[self class] alloc] initWithRolesAndProbabilities:dict] autorelease];
245 : }
246 :
247 :
248 : - (id)roleSetWithRemovedRole:(NSString *)role
249 : {
250 : NSMutableDictionary *dict = nil;
251 :
252 : if (![self hasRole:role]) return [[self copy] autorelease];
253 :
254 : dict = [[_rolesAndProbabilities mutableCopy] autorelease];
255 : [dict removeObjectForKey:role];
256 : return [[[[self class] alloc] initWithRolesAndProbabilities:dict] autorelease];
257 : }
258 :
259 : @end
260 :
261 :
262 : @implementation OORoleSet (OOPrivate)
263 :
264 : - (id)initWithRolesAndProbabilities:(NSDictionary *)dict
265 : {
266 : NSEnumerator *roleEnum = nil;
267 : NSString *role = nil;
268 : float prob;
269 :
270 : if (dict == nil)
271 : {
272 : [self release];
273 : return nil;
274 : }
275 :
276 : self = [super init];
277 : if (self == nil) return nil;
278 :
279 : // Note: _roles and _roleString are derived on the fly as needed.
280 : // MKW 20090815 - if we are re-initialising this OORoleSet object, we need
281 : // to ensure that _roles and _roleString are cleared.
282 : // Why would we be re-initing? That's never valid. -- Ahruman 2010-02-06
283 : assert(_roles == nil && _roleString == nil);
284 :
285 : NSMutableDictionary *tDict = [[dict mutableCopy] autorelease];
286 : float thargProb = [dict oo_floatForKey:@"thargon" defaultValue:0.0f];
287 :
288 : if ( thargProb > 0.0f && [dict objectForKey:@"EQ_THARGON"] == nil)
289 : {
290 : [tDict setObject:[NSNumber numberWithFloat:thargProb] forKey:@"EQ_THARGON"];
291 : [tDict removeObjectForKey:@"thargon"];
292 : }
293 :
294 : _rolesAndProbabilities = [tDict copy];
295 :
296 : for (roleEnum = [dict keyEnumerator]; (role = [roleEnum nextObject]); )
297 : {
298 : prob = [dict oo_floatForKey:role defaultValue:-1];
299 : if (prob < 0)
300 : {
301 : OOLog(@"roleSet.badValue", @"Attempt to create a role set with negative or non-numerical probability for role %@.", role);
302 : [self release];
303 : return nil;
304 : }
305 :
306 : _totalProb += prob;
307 : }
308 :
309 : return self;
310 : }
311 :
312 : @end
313 :
314 :
315 0 : NSDictionary *OOParseRolesFromString(NSString *string)
316 : {
317 : NSMutableDictionary *result = nil;
318 : NSArray *tokens = nil;
319 : NSUInteger i, count;
320 : NSString *role = nil;
321 : float probability;
322 : NSScanner *scanner = nil;
323 :
324 : // Split string at spaces, sanity checks, set-up.
325 : if (string == nil) return nil;
326 :
327 : tokens = ScanTokensFromString(string);
328 : count = [tokens count];
329 : if (count == 0) return nil;
330 :
331 : result = [NSMutableDictionary dictionaryWithCapacity:count];
332 :
333 : // Scan tokens, looking for probabilities.
334 : for (i = 0; i != count; ++i)
335 : {
336 : role = [tokens objectAtIndex:i];
337 :
338 : probability = 1.0f;
339 : if ([role rangeOfString:@"("].location != NSNotFound)
340 : {
341 : scanner = [[NSScanner alloc] initWithString:role];
342 : [scanner scanUpToString:@"(" intoString:&role];
343 : [scanner scanString:@"(" intoString:NULL];
344 : if (![scanner scanFloat:&probability]) probability = 1.0f;
345 : // Ignore rest of string
346 :
347 : [scanner release];
348 : }
349 :
350 : // shipKey roles start with [ so other roles can't
351 : if (0 <= probability && ![role hasPrefix:@"["])
352 : {
353 : [result setObject:[NSNumber numberWithFloat:probability] forKey:role];
354 : }
355 : }
356 :
357 : if ([result count] == 0) result = nil;
358 : return result;
359 : }
|