/* File: MBCGameInfo.mm Contains: Managing information about the current game Copyright: © 2002-2005 Apple Computer, Inc. All rights reserved. IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc. ("Apple") in consideration of your agreement to the following terms, and your use, installation, modification or redistribution of this Apple software constitutes acceptance of these terms. If you do not agree with these terms, please do not use, install, modify or redistribute this Apple software. In consideration of your agreement to abide by the following terms, and subject to these terms, Apple grants you a personal, non-exclusive license, under Apple's copyrights in this original Apple software (the "Apple Software"), to use, reproduce, modify and redistribute the Apple Software, with or without modifications, in source and/or binary forms; provided that if you redistribute the Apple Software in its entirety and without modifications, you must retain this notice and the following text and disclaimers in all such redistributions of the Apple Software. Neither the name, trademarks, service marks or logos of Apple Computer, Inc. may be used to endorse or promote products derived from the Apple Software without specific prior written permission from Apple. Except as expressly stated in this notice, no other rights or licenses, express or implied, are granted by Apple herein, including but not limited to any patent rights that may be infringed by your derivative works or by other works in which the Apple Software may be incorporated. The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #import "MBCGameInfo.h" #import "MBCController.h" #import "MBCPlayer.h" #include <sys/types.h> #include <regex.h> #include <algorithm> using std::min; static NSTextTab * MakeTab(NSTextTabType type, float location) { return [[[NSTextTab alloc] initWithType:type location:location] autorelease]; } @implementation MBCGameInfo NSString * kMBCGameCity = @"MBCGameCity"; NSString * kMBCGameCountry = @"MBCGameCountry"; NSString * kMBCHumanFirst = @"MBCHumanFirst"; NSString * kMBCHumanLast = @"MBCHumanLast"; NSString * kMBCGameEvent = @"MBCGameEvent"; NSString * kMBCShowMoveInTitle = @"MBCShowMoveInTitle"; + (void) parseName:(NSString *)fullName intoFirst:(NSString **)firstName last:(NSString **)lastName { // // Get name as UTF8. If the name is longer than 99 bytes, the last // character might be bad if it's non-ASCII. What sane person would // have such a long name anyway? // char n[100]; NSData * d = [fullName dataUsingEncoding:NSUTF8StringEncoding]; [d getBytes:n length:99]; n[min(99u, [d length])] = 0; char * first = n+strspn(n, " \t"); // Beginning of first name char * last; char * nb1 = NULL; // Beginning of last word char * ne1 = NULL; // End of last word char * nb2 = NULL; // Beginning of last but one word char * ne2 = NULL; // End of last but two word char * ne3 = NULL; // End of last but three word nb1 = first; ne1 = nb1+strcspn(nb1, " \t"); for (char * n; (n = ne1+strspn(ne1, " \t")) && *n; ) { ne3 = ne2; nb2 = nb1; ne2 = ne1; nb1 = n; ne1 = nb1+strcspn(nb1, " \t"); } if (ne3 && *nb2 >= 'a' && *nb2 <= 'z') { // // Name has at least 3 words and last but one is // lowercase, as in Ludwig van Beethoven // last = nb2; *ne3 = 0; } else if (ne2) { // // Name has at least two words. If 3 or more, last but one is // uppercase as in John Wayne Miller // last = nb1; *ne2 = 0; } else { // Name is single word last = ne1; *ne1 = 0; } *firstName = [NSString stringWithUTF8String:first]; *lastName = [NSString stringWithUTF8String:last]; } + (void)initialize { // // Parse the user name into last and first name // NSString * humanFirst; NSString * humanLast; [MBCGameInfo parseName:NSFullUserName() intoFirst:&humanFirst last:&humanLast]; // // Get the city we might be in. This technique may not necessarily // work in future revisions of Mac OS X, I suppose. // // PGN wants IOC codes for countries, which we're too lazy to convert. // NSArray * cityInfo = (NSArray *)CFPreferencesCopyValue((CFStringRef) @"com.apple.TimeZonePref.Last_Selected_City", kCFPreferencesAnyApplication, kCFPreferencesAnyUser, kCFPreferencesCurrentHost); NSString * city = cityInfo ? [cityInfo objectAtIndex:7] : @"?"; NSString * country = cityInfo ? [cityInfo objectAtIndex:8] : @"?"; // // PGN wants ISO Latin 1, so we fall back to the non-Localized Name // for non-Latin places. // if (![city canBeConvertedToEncoding:NSISOLatin1StringEncoding]) city = [cityInfo objectAtIndex:5]; if (![country canBeConvertedToEncoding:NSISOLatin1StringEncoding]) country = [cityInfo objectAtIndex:6]; if (cityInfo) [cityInfo release]; NSString * event = [NSLocalizedString(@"casual_game", @"casual game") retain]; NSDictionary * defaults = [NSDictionary dictionaryWithObjectsAndKeys: humanFirst, kMBCHumanFirst, humanLast, kMBCHumanLast, city, kMBCGameCity, country, kMBCGameCountry, event, kMBCGameEvent, nil]; [[NSUserDefaults standardUserDefaults] registerDefaults: defaults]; } - (id) init { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateMoves:) name:MBCEndMoveNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(takeback:) name:MBCTakebackNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateMoves:) name:MBCIllegalMoveNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(gameEnd:) name:MBCGameEndNotification object:nil]; fOutcome = nil; fWhiteName = nil; fBlackName = nil; fSetInfo = false; return self; } - (void)awakeFromNib { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; [fShowMoveInTitle setIntValue:[defaults boolForKey:kMBCShowMoveInTitle]]; } - (void) updateMoves:(NSNotification *)notification { [fMoveList reloadData]; [fMoveList setNeedsDisplay:YES]; fTitleNeedsUpdate = true; int rows = [self numberOfRowsInTableView:fMoveList]; if (rows != fRows) { fRows = rows; [fMoveList selectRow:rows-1 byExtendingSelection:NO]; [fMoveList scrollRowToVisible:rows-1]; } } - (void) takeback:(NSNotification *)notification { [fOutcome release]; fOutcome = nil; fResult = [@"*" retain]; [self updateMoves:notification]; } - (void) gameEnd:(NSNotification *)notification { MBCMove * move = reinterpret_cast<MBCMove *>([notification object]); switch (move->fCommand) { case kCmdWhiteWins: fResult = @"1-0"; fOutcome = NSLocalizedString(@"white_win_msg", @"White wins"); break; case kCmdBlackWins: fResult = @"0-1"; fOutcome = NSLocalizedString(@"black_win_msg", @"Black wins"); break; case kCmdDraw: fResult = @"1/2-1/2"; fOutcome = NSLocalizedString(@"draw_msg", @"Draw"); break; default: return; } [fResult retain]; [fOutcome retain]; [self updateTitle:nil]; } - (NSDictionary *)getInfo { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; return [NSDictionary dictionaryWithObjectsAndKeys: fWhiteName, @"White", fBlackName, @"Black", fStartDate, @"StartDate", fStartTime, @"StartTime", fResult, @"Result", [defaults stringForKey:kMBCGameCity], @"City", [defaults stringForKey:kMBCGameCountry], @"Country", [defaults stringForKey:kMBCGameEvent], @"Event", nil]; } - (void)setInfo:(NSDictionary *)dict { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; NSString * human = [NSString stringWithFormat:@"%@ %@", [defaults stringForKey:kMBCHumanFirst], [defaults stringForKey:kMBCHumanLast]]; NSString * engine = @"Computer"; fSetInfo = true; [fWhiteName release]; [fBlackName release]; if (NSString * white = [dict objectForKey:@"White"]) fWhiteName = [white retain]; else if ([[dict objectForKey:@"WhiteType"] isEqual:kMBCHumanPlayer]) fWhiteName = [human retain]; else fWhiteName = [engine retain]; if (NSString * black = [dict objectForKey:@"Black"]) fBlackName = [black retain]; else if ([[dict objectForKey:@"BlackType"] isEqual:kMBCHumanPlayer]) fBlackName = [human retain]; else fBlackName = [engine retain]; fStartDate = [[dict objectForKey:@"StartDate"] retain]; fStartTime = [[dict objectForKey:@"StartTime"] retain]; fResult = [[dict objectForKey:@"Result"] retain]; if (NSString * city = [dict objectForKey:@"City"]) [defaults setObject:city forKey:kMBCGameCity]; if (NSString * country = [dict objectForKey:@"Country"]) [defaults setObject:country forKey:kMBCGameCountry]; if (NSString * event = [dict objectForKey:@"Event"]) [defaults setObject:event forKey:kMBCGameEvent]; [fOutcome release]; fOutcome = nil; if ([fResult isEqual:@"1-0"]) fOutcome = NSLocalizedString(@"white_win_msg", @"White wins"); if ([fResult isEqual:@"0-1"]) fOutcome = NSLocalizedString(@"black_win_msg", @"Black wins"); else if ([fResult isEqual:@"1/2-1/2"]) fOutcome = NSLocalizedString(@"draw_msg", @"Draw"); [fOutcome retain]; } - (void) startGame:(MBCVariant)variant playing:(MBCSide)sideToPlay { if (!fSetInfo) { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; NSString * human = [NSString stringWithFormat:@"%@ %@", [defaults stringForKey:kMBCHumanFirst], [defaults stringForKey:kMBCHumanLast]]; NSString * engine = @"Computer"; [fWhiteName release]; [fBlackName release]; switch (fHuman = fSideToPlay = sideToPlay) { case kWhiteSide: fWhiteName = [human retain]; fBlackName = [engine retain]; break; case kBlackSide: fWhiteName = [engine retain]; fBlackName = [human retain]; break; case kBothSides: fWhiteName = [human retain]; fBlackName = [human retain]; fHuman = kWhiteSide; break; case kNeitherSide: fWhiteName = [engine retain]; fBlackName = [engine retain]; break; } NSDate * now = [NSDate date]; fStartDate = [[now descriptionWithCalendarFormat:@"%Y.%m.%d" timeZone:nil locale:nil] retain]; fStartTime = [[now descriptionWithCalendarFormat:@"%H:%M:%S" timeZone:nil locale:nil] retain]; fResult = [@"*" retain]; [fOutcome release]; fOutcome = nil; fRows = 0; fBoard = [[MBCController controller] board]; } else fSetInfo = false; [self updateMoves:nil]; [self updateTitle:nil]; } - (IBAction) editInfo:(id)sender { NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults]; [fWhite setStringValue:fWhiteName]; [fBlack setStringValue:fBlackName]; [fCity setStringValue:[defaults stringForKey:kMBCGameCity]]; [fCountry setStringValue:[defaults stringForKey:kMBCGameCountry]]; [fEvent setStringValue:[defaults stringForKey:kMBCGameEvent]]; switch (fSideToPlay) { case kWhiteSide: [fWhite setEditable:YES]; [fBlack setSelectable:NO]; break; case kBlackSide: [fWhite setSelectable:NO]; [fBlack setEditable:YES]; break; case kBothSides: [fWhite setEditable:YES]; [fBlack setEditable:YES]; break; case kNeitherSide: [fWhite setSelectable:NO]; [fBlack setSelectable:NO]; break; } [NSApp beginSheet:fEditSheet modalForWindow:fInfoWindow modalDelegate:nil didEndSelector:nil contextInfo:nil]; [NSApp runModalForWindow:fEditSheet]; [NSApp endSheet:fEditSheet]; [fEditSheet orderOut:self]; [fInfoWindow makeKeyAndOrderFront:self]; } - (IBAction) cancelInfo:(id)sender { [NSApp stopModal]; } - (IBAction) updateInfo:(id)sender { NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults]; NSString * humanFirst; NSString * humanLast; fWhiteName = [[fWhite stringValue] retain]; fBlackName = [[fBlack stringValue] retain]; if (fHuman == kWhiteSide) { [MBCGameInfo parseName:fWhiteName intoFirst:&humanFirst last:&humanLast]; } else if (fHuman == kBlackSide) { [MBCGameInfo parseName:fBlackName intoFirst:&humanFirst last:&humanLast]; } [defaults setObject:humanFirst forKey:kMBCHumanFirst]; [defaults setObject:humanLast forKey:kMBCHumanLast]; [defaults setObject:[fCity stringValue] forKey:kMBCGameCity]; [defaults setObject:[fCountry stringValue] forKey:kMBCGameCountry]; [defaults setObject:[fEvent stringValue] forKey:kMBCGameEvent]; [self updateTitle:nil]; [NSApp stopModal]; } - (int)numberOfRowsInTableView:(NSTableView *)aTableView { return ([fBoard numMoves]+1) / 2; } - (id)tableView:(NSTableView *)v objectValueForTableColumn:(NSTableColumn *)col row:(int)row { // // Defer title update until table gets redrawn // if (fTitleNeedsUpdate) [self updateTitle:nil]; NSString * ident = [col identifier]; if ([ident isEqual:@"Move"]) return [NSString stringWithFormat:@"%d.", row+1]; NSString * move = [[fBoard move: row*2+[ident isEqual:@"Black"]] localizedText:YES]; NSMutableParagraphStyle * style = [[[NSParagraphStyle defaultParagraphStyle] mutableCopy] autorelease]; float tab = [col width] / 14.0f; [style setTabStops: [NSArray arrayWithObjects: [move characterAtIndex:1]=='\t' ? MakeTab(NSRightTabStopType, 1.0f) : MakeTab(NSRightTabStopType, 5.0f*tab), MakeTab(NSCenterTabStopType, 6.0f*tab), MakeTab(NSLeftTabStopType, 7.0f*tab), nil]]; return [[[NSAttributedString alloc] initWithString:move attributes: [NSDictionary dictionaryWithObject:style forKey:NSParagraphStyleAttributeName]] autorelease]; } - (IBAction) updateTitle:(id)sender { NSString * move; int numMoves = [fBoard numMoves]; if (sender) { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; [defaults setBool: [fShowMoveInTitle intValue] forKey:kMBCShowMoveInTitle]; } if (numMoves && [fShowMoveInTitle intValue]) move = [NSString stringWithFormat:@"%d. %@%@", (numMoves+1)/2, numMoves&1 ? @"":@"... ", [[fBoard lastMove] localizedText:NO]]; else if (!numMoves) move = NSLocalizedString(@"new_msg", @"New Game"); else if (numMoves & 1) move = NSLocalizedString(@"black_move_msg", @"Black to move"); else move = NSLocalizedString(@"white_move_msg", @"White to move"); NSString * title = [NSString stringWithFormat:@"%@ - %@ (%@)", fWhiteName, fBlackName, move]; if (fOutcome) title = [NSString stringWithFormat:@"%@ %@", title, fOutcome]; [fMainWindow setTitle:title]; unichar emdash = 0x2014; [fMatchup setStringValue: [NSString stringWithFormat:@"%@\n%@\n%@", fWhiteName, [NSString stringWithCharacters:&emdash length:1], fBlackName]]; fTitleNeedsUpdate = false; } - (NSSize)windowWillResize:(NSWindow *)sender toSize:(NSSize)proposedFrameSize { [fMoveList setNeedsDisplay:YES]; return proposedFrameSize; } - (NSString *)pgnHeader { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; NSString * wf; NSString * wl; NSString * bf; NSString * bl; [MBCGameInfo parseName:fWhiteName intoFirst:&wf last:&wl]; [MBCGameInfo parseName:fBlackName intoFirst:&bf last:&bl]; NSString * humanw = [NSString stringWithFormat:@"%@, %@", wl, wf]; NSString * humanb = [NSString stringWithFormat:@"%@, %@", bl, bf]; NSString * engine = [NSString stringWithFormat:@"Apple Chess %@", [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]]; NSString * white; NSString * black; switch (fSideToPlay) { case kWhiteSide: white = humanw; black = engine; break; case kBlackSide: white = engine; black = humanb; break; case kBothSides: white = humanw; black = humanb; break; case kNeitherSide: white = engine; black = engine; break; } // // PGN uses a standard format that is NOT localized // NSString * format = [NSString stringWithCString: "[Event \"%@\"]\n" "[Site \"%@, %@\"]\n" "[Date \"%@\"]\n" "[Round \"-\"]\n" "[White \"%@\"]\n" "[Black \"%@\"]\n" "[Result \"%@\"]\n" "[Time \"%@\"]\n"]; return [NSString stringWithFormat:format, [defaults stringForKey:kMBCGameEvent], [defaults stringForKey:kMBCGameCity], [defaults stringForKey:kMBCGameCountry], fStartDate, white, black, fResult, fStartTime]; } - (NSString *)pgnResult { return fResult; } @end // Local Variables: // mode:ObjC // End: