1/* 2 Project: GShisen 3 4 Copyright (C) 2003-2009 The GNUstep Application Project 5 6 Author: Enrico Sersale, Riccardo Mottola 7 8 Board View 9 10 This application is free software; you can redistribute it and/or 11 modify it under the terms of the GNU General Public 12 License as published by the Free Software Foundation; either 13 version 2 of the License, or (at your option) any later version. 14 15 This application is distributed in the hope that it will be useful, 16 but WITHOUT ANY WARRANTY; without even the implied warranty of 17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 Library General Public License for more details. 19 20 You should have received a copy of the GNU General Public 21 License along with this library; if not, write to the Free 22 Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 23 */ 24 25 26#import "gshisen.h" 27#import "board.h" 28 29int imin(int a, int b) { 30 if(a < b) 31 return a; 32 else 33 return b; 34} 35 36int imax(int a, int b) { 37 if(a > b) 38 return a; 39 else 40 return b; 41} 42 43static NSComparisonResult randomizeTiles(GSTile *t1, GSTile *t2, id self) 44{ 45 return [[t1 rndpos] compare: [t2 rndpos]]; 46} 47 48static NSComparisonResult sortScores(NSDictionary *d1, NSDictionary *d2, id self) 49{ 50 int min1, min2, sec1, sec2; 51 52 min1 = [[d1 objectForKey: @"minutes"] intValue]; 53 sec1 = [[d1 objectForKey: @"seconds"] intValue]; 54 sec1 += min1 * 60; 55 min2 = [[d2 objectForKey: @"minutes"] intValue]; 56 sec2 = [[d2 objectForKey: @"seconds"] intValue]; 57 sec2 += min2 * 60; 58 59 return [[NSNumber numberWithInt: sec1] compare: [NSNumber numberWithInt: sec2]]; 60} 61 62@implementation GSBoard 63 64- (id)initWithFrame:(NSRect)frameRect 65{ 66 NSArray *tempArray; 67 self = [super initWithFrame:frameRect]; 68 if(self) { 69 seconds = 0; 70 minutes = 0; 71 tiles = nil; 72 timeField = nil; 73 tmr = nil; 74 gameState = GAME_STATE_PAUSED; 75 iconsNamesRefs = [[NSArray alloc] initWithObjects: 76 @"1-1", @"1-2", @"1-3", @"1-4", @"2-1", @"2-2", @"2-3", @"2-4", 77 @"3-1", @"3-2", @"3-3", @"3-4", @"4-1", @"4-2", @"4-3", @"4-4", 78 @"5-1", @"5-2", @"5-3", @"5-4", @"6-1", @"6-2", @"6-3", @"6-4", 79 @"7-1", @"7-2", @"7-3", @"7-4", @"8-1", @"8-2", @"8-3", @"8-4", 80 @"9-1", @"9-2", @"9-3", @"9-4", nil]; 81 82 defaults = [NSUserDefaults standardUserDefaults]; 83 numScoresToKeep = [defaults integerForKey:@"scoresToKeep"]; 84 if (numScoresToKeep == 0) { 85 numScoresToKeep = 20; 86 } 87 tempArray = (NSMutableArray *)[[defaults arrayForKey:@"scores"] retain]; 88 if(!tempArray) { 89 scores = [[NSMutableArray arrayWithCapacity:1] retain]; 90 [defaults setObject:scores forKey:@"scores"]; 91 } 92 else { 93 scores = [[NSMutableArray arrayWithCapacity:1] retain]; 94 [scores setArray:tempArray]; 95 [defaults setObject:scores forKey:@"scores"]; 96 } 97 [defaults synchronize]; 98 99 hadEndOfGame = NO; 100 undoArray = nil; 101 102 [self newGame]; 103 } 104 return self; 105} 106 107- (void)dealloc 108{ 109 [tiles release]; 110 [iconsNamesRefs release]; 111 [timeField release]; 112 [scores release]; 113 [undoArray release]; 114 [super dealloc]; 115} 116 117- (void)undo 118{ 119 GSTilePair *pairToUndo; 120 if( [undoArray count] > 0 ) 121 { 122 pairToUndo = [undoArray lastObject]; 123 [pairToUndo activateTiles]; 124 [undoArray removeObject:pairToUndo]; 125 } 126 else 127 { 128 NSBeep(); 129 } 130} 131 132- (void)newGame 133{ 134 NSMutableArray *tmptiles; 135 GSTile *tile; 136 NSString *ref; 137 NSTimeInterval timeInterval; 138 int i, j, p; 139 BOOL bordt; 140 int borderPositions[56] = 141 {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 142 20, 39, 143 40, 59, 144 60, 79, 145 80, 99, 146 100, 119, 147 120, 139, 148 140, 159, 149 160, 179, 150 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 151 194, 195, 196, 197, 198, 199}; 152 153 timeInterval = [NSDate timeIntervalSinceReferenceDate]; 154 srand((int)timeInterval); 155 156 if(undoArray != nil) 157 { 158 [undoArray removeAllObjects]; 159 [undoArray release]; 160 undoArray = nil; 161 } 162 undoArray = [[NSMutableArray alloc] initWithCapacity: 72]; 163 164 tmptiles = [NSMutableArray arrayWithCapacity: 144]; 165 for(i = 0; i < [iconsNamesRefs count]; i++) { 166 for(j = 0; j < 4; j++) { 167 ref = [iconsNamesRefs objectAtIndex: i]; 168 tile = [[GSTile alloc] initOnBoard: self 169 iconRef: ref group: i rndpos: rand() isBorderTile:NO]; 170 [tmptiles addObject: tile]; 171 [tile release]; 172 } 173 } 174 tmptiles = (NSMutableArray *)[tmptiles sortedArrayUsingFunction:(int (*)(id, id, void*))randomizeTiles context:self]; 175 176 if(tmr && !hadEndOfGame) { 177 if([tmr isValid]) 178 [tmr invalidate]; 179 } 180 if(timeField) { 181 [timeField removeFromSuperview]; 182 [timeField release]; 183 } 184 185 if(tiles) { 186 for(i = 0; i < [tiles count]; i++) 187 [[tiles objectAtIndex: i] removeFromSuperview]; 188 [tiles release]; 189 tiles = nil; 190 } 191 tiles = [[NSMutableArray alloc] initWithCapacity: 200]; 192 193 p = 0; 194 for(i = 0; i < 200; i++) { 195 bordt = NO; 196 for(j = 0; j < 56; j++) { 197 if(i == borderPositions[j]) { 198 tile = [[GSTile alloc] initOnBoard: self 199 iconRef: nil group: -1 rndpos: -1 isBorderTile: YES]; 200 [tiles addObject: tile]; 201 [tile release]; 202 bordt = YES; 203 } 204 } 205 if(!bordt) { 206 [tiles addObject: [tmptiles objectAtIndex: p]]; 207 p++; 208 } 209 } 210 211 for(i = 0; i < [tiles count]; i++) 212 [self addSubview: [tiles objectAtIndex:i]]; 213 214 firstTile = nil; 215 secondTile = nil; 216 217 timeField = [[NSTextField alloc] initWithFrame:NSMakeRect(10, 5, 60, 15)]; 218 [timeField setFont: [NSFont systemFontOfSize: 10]]; 219 [timeField setAlignment:NSCenterTextAlignment]; 220 [timeField setBezeled:NO]; 221 [timeField setEditable:NO]; 222 [timeField setSelectable:NO]; 223 [timeField setStringValue:@"00:00"]; 224 [self addSubview: timeField]; 225 226 [self resizeWithOldSuperviewSize: [self frame].size]; 227 228 seconds = 0; 229 minutes = 0; 230 231 tmr = [NSTimer scheduledTimerWithTimeInterval:1 target:self 232 selector:@selector(timestep:) userInfo:nil repeats:YES]; 233 hadEndOfGame = NO; 234 gameState = GAME_STATE_RUNNING; 235} 236 237- (void)timestep:(NSTimer *)t 238{ 239 NSString *timeStr; 240 241 if(gameState == GAME_STATE_RUNNING) 242 { 243 seconds++; 244 if(seconds == 60) { 245 seconds = 0; 246 minutes++; 247 } 248 } 249 timeStr = [NSString stringWithFormat:@"%02i:%02i", minutes, seconds]; 250 [timeField setStringValue: timeStr]; 251 [timeField setNeedsDisplay: YES]; 252} 253 254- (int)prepareTilesToRemove:(GSTile *)clickedTile 255{ 256 if(!firstTile) { 257 firstTile = clickedTile; 258 return 1; 259 } 260 secondTile = clickedTile; 261 262 if([firstTile group] == [secondTile group]) 263 { 264 if([self findPathBetweenTiles]) 265 return 2; 266 else 267 { 268 [firstTile unselect]; 269 firstTile = clickedTile; 270 secondTile = nil; 271 return 1; 272 } 273 } 274 else 275 { 276 [firstTile unselect]; 277 firstTile = clickedTile; 278 secondTile = nil; 279 return 1; 280 } 281 282 return 0; 283} 284 285- (BOOL)findPathBetweenTiles 286{ 287 int x1 = [firstTile px]; 288 int y1 = [firstTile py]; 289 int x2 = [secondTile px]; 290 int y2 = [secondTile py]; 291 int dx[4] = {1, 0, -1, 0}; 292 int dy[4] = {0, 1, 0, -1}; 293 int newx, newy, i; 294 295 if([self findSimplePathFromX1:x1 y1:y1 toX2:x2 y2:y2]) 296 return YES; 297 298 for(i = 0; i < 4; i++) { 299 newx = x1 + dx[i]; 300 newy = y1 + dy[i]; 301 302 while(![[self tileAtxPosition:newx yPosition:newy] isActive] 303 && newx >= 0 && newx < 20 && newy >= 0 && newy < 10) { 304 305 if([self findSimplePathFromX1:newx y1:newy toX2:x2 y2:y2]) 306 return YES; 307 308 newx += dx[i]; 309 newy += dy[i]; 310 } 311 } 312 313 return NO; 314} 315 316- (BOOL)findSimplePathFromX1:(int)x1 y1:(int)y1 toX2:(int)x2 y2:(int)y2 317{ 318 GSTile *tile; 319 BOOL r = NO; 320 321 if([self canMakeLineFromX1:x1 y1:y1 toX2:x2 y2:y2]) { 322 r = YES; 323 } else { 324 if(!(x1 == x2 || y1 == y2)) { 325 tile = [self tileAtxPosition:x2 yPosition:y1]; 326 if(![tile isActive] 327 && [self canMakeLineFromX1:x1 y1:y1 toX2:x2 y2:y1] 328 && [self canMakeLineFromX1:x2 y1:y1 toX2:x2 y2:y2]) { 329 r = YES; 330 } else { 331 tile = [self tileAtxPosition:x1 yPosition:y2]; 332 if(![tile isActive] 333 && [self canMakeLineFromX1:x1 y1:y1 toX2:x1 y2:y2] 334 && [self canMakeLineFromX1:x1 y1:y2 toX2:x2 y2:y2]) { 335 r = YES; 336 } 337 } 338 } 339 } 340 341 return r; 342} 343 344- (BOOL)canMakeLineFromX1:(int)x1 y1:(int)y1 toX2:(int)x2 y2:(int)y2 345{ 346 NSArray *lineOfTiles; 347 GSTile *tile; 348 int i; 349 350 if(x1 == x2) { 351 lineOfTiles = [self tilesAtXPosition: x1]; 352 for(i = imin(y1, y2)+1; i < imax(y1, y2); i++) { 353 tile = [lineOfTiles objectAtIndex: i]; 354 if([tile isActive]) 355 return NO; 356 } 357 return YES; 358 } 359 360 if(y1 == y2) { 361 lineOfTiles = [self tilesAtYPosition: y1]; 362 for(i = imin(x1, x2)+1; i < imax(x1, x2); i++) { 363 tile = [lineOfTiles objectAtIndex: i]; 364 if([tile isActive]) 365 return NO; 366 } 367 return YES; 368 } 369 370 return NO; 371} 372 373- (void)removeCurrentTiles 374{ 375 GSTilePair *removedPair; 376 377 [firstTile deactivate]; 378 [secondTile deactivate]; 379 380 removedPair = [[GSTilePair alloc] initWithTile:firstTile andTile:secondTile]; 381 382 [undoArray addObject:removedPair]; 383 384 [self verifyEndOfGame]; 385 [self unSetCurrentTiles]; 386} 387 388- (void)unSetCurrentTiles 389{ 390 firstTile = nil; 391 secondTile = nil; 392} 393 394- (void)verifyEndOfGame 395{ 396 int i; 397 BOOL found = NO; 398 399 for(i = 0; i < [tiles count]; i++) { 400 firstTile = [tiles objectAtIndex: i]; 401 if([firstTile isActive]) { 402 found = YES; 403 firstTile = nil; 404 break; 405 } 406 } 407 408 if(!found) { 409 [self endOfGame]; 410 return; 411 } 412} 413 414- (void)getHint 415{ 416 GSTile *tile; 417 int i, j, result; 418 BOOL found = NO; 419 420 for(i = 0; i < [tiles count]; i++) { 421 tile = [tiles objectAtIndex: i]; 422 if([tile isActive] && [tile isSelect]) 423 [tile unselect]; 424 } 425 426 for(i = 0; i < [tiles count]; i++) { 427 if(found) 428 break; 429 for(j = 0; j < [tiles count]; j++) { 430 if(i != j) { 431 firstTile = [tiles objectAtIndex: i]; 432 secondTile = [tiles objectAtIndex: j]; 433 if([firstTile isActive] && [secondTile isActive]) { 434 if([firstTile group] == [secondTile group]) { 435 if([self findPathBetweenTiles]) { 436 found = YES; 437 break; 438 } 439 } 440 } 441 } 442 } 443 } 444 if(found) { 445 [firstTile hightlight]; 446 [secondTile hightlight]; 447 [[NSRunLoop currentRunLoop] runUntilDate: 448 [NSDate dateWithTimeIntervalSinceNow: 2]]; 449 tile = secondTile; 450 [firstTile unselect]; 451 [tile unselect]; 452 } else { 453 result = NSRunAlertPanel(nil, @"No more moves possible!", @"New Game", @"Quit", @"Continue"); 454 if(result == NSAlertDefaultReturn) 455 [self newGame]; 456 else if (result == NSAlertAlternateReturn) 457 [NSApp terminate:self]; 458 } 459} 460 461- (void)pause 462{ 463 if(gameState == GAME_STATE_PAUSED) { 464 gameState = GAME_STATE_RUNNING; 465 } else if(gameState == GAME_STATE_RUNNING) { 466 gameState = GAME_STATE_PAUSED; 467 } 468 [self setNeedsDisplay:YES]; 469} 470 471- (void)endOfGame 472{ 473 NSString *username; 474 NSMutableDictionary *gameData; 475 NSString *entry; 476 477 hadEndOfGame = YES; 478 479 if([tmr isValid]) 480 [tmr invalidate]; 481 482 gameState = GAME_STATE_PAUSED; 483 484 username = [[GShisen sharedshisen] getUserName]; 485 486 gameData = [NSMutableDictionary dictionaryWithCapacity: 3]; 487 [gameData setObject: username forKey: @"username"]; 488 entry = [NSString stringWithFormat: @"%i", minutes]; 489 [gameData setObject: entry forKey: @"minutes"]; 490 entry = [NSString stringWithFormat: @"%02i", seconds]; 491 [gameData setObject: entry forKey: @"seconds"]; 492 493 [scores addObject: gameData]; 494 [scores sortUsingFunction:(int (*)(id, id, void*))sortScores context:self]; 495 if ([scores count] > numScoresToKeep) { 496 NSRange scoresToZap = NSMakeRange(numScoresToKeep, 497 [scores count] - numScoresToKeep); 498 [scores removeObjectsInRange:scoresToZap]; 499 } 500 [defaults setObject: scores forKey: @"scores"]; 501 [defaults synchronize]; 502 503 [[GShisen sharedshisen] showHallOfFame:self]; 504 505 seconds = 0; 506 minutes = 0; 507 508 [self setNeedsDisplay:YES]; 509} 510 511- (NSMutableArray *)scores 512{ 513 return scores; 514} 515 516- (NSArray *)tilesAtXPosition:(int)xpos 517{ 518 NSMutableArray *tls; 519 GSTile *tile; 520 int i; 521 522 tls = [NSMutableArray arrayWithCapacity: 1]; 523 for(i = 0; i < [tiles count]; i++) { 524 tile = [tiles objectAtIndex: i]; 525 if([tile px] == xpos) 526 [tls addObject: tile]; 527 } 528 return tls; 529} 530 531- (NSArray *)tilesAtYPosition:(int)ypos 532{ 533 NSMutableArray *tls; 534 GSTile *tile; 535 int i; 536 537 tls = [NSMutableArray arrayWithCapacity: 1]; 538 for(i = 0; i < [tiles count]; i++) { 539 tile = [tiles objectAtIndex: i]; 540 if([tile py] == ypos) 541 [tls addObject: tile]; 542 } 543 return tls; 544} 545 546- (GSTile *)tileAtxPosition:(int)xpos yPosition:(int)ypos 547{ 548 GSTile *tile; 549 int i; 550 551 for(i = 0; i < [tiles count]; i++) { 552 tile = [tiles objectAtIndex: i]; 553 if(([tile px] == xpos) && ([tile py] == ypos)) 554 return tile; 555 } 556 return nil; 557} 558 559- (void)resizeWithOldSuperviewSize:(NSSize)oldFrameSize 560{ 561 GSTile *tile; 562 int i, hcount, vcount, hpos, vpos; 563 564 vpos = [self frame].size.height -10; 565 hpos = -30; 566 hcount = 0; 567 vcount = 0; 568 for(i = 0; i < [tiles count]; i++) { 569 tile = [tiles objectAtIndex: i]; 570 [tile setPositionOnBoard: hcount posy: vcount]; 571 [tile setFrame: NSMakeRect(hpos, vpos, 40, 56)]; 572 [self setNeedsDisplayInRect: [tile frame]]; 573 hpos += 40; 574 hcount++; 575 if(hcount == 20) { 576 hcount = 0; 577 vcount++; 578 hpos = -30; 579 vpos -= 56; 580 } 581 } 582} 583 584- (BOOL)performKeyEquivalent:(NSEvent *)theEvent 585{ 586 NSString *commchar = [theEvent charactersIgnoringModifiers]; 587 588 if([commchar isEqualToString: @"n"]) { 589 [self newGame]; 590 return YES; 591 } 592 if([commchar isEqualToString: @"g"]) { 593 [self getHint]; 594 return YES; 595 } 596 if([commchar isEqualToString: @"z"]) { 597 [self undo]; 598 return YES; 599 } 600 601 return NO; 602} 603 604- (void)drawRect:(NSRect)rect 605{ 606 id font = [NSFont boldSystemFontOfSize:48]; 607 //id font = [NSFont boldSystemFontOfSize:24]; 608 NSString *pauseString = @"Paused"; 609 NSString *gameOverString = @"Game Over"; 610 611 // arrays for dictionaries 612 NSArray *keyArray = [NSArray arrayWithObjects:NSFontAttributeName, 613 NSForegroundColorAttributeName, nil]; 614 NSArray *valueArray1 = [NSArray arrayWithObjects:font, 615 [NSColor colorWithCalibratedRed: 0.09 green: 0.3 blue: 0 alpha: 1], nil]; 616 NSArray *valueArray2 = [NSArray arrayWithObjects:font, 617 [NSColor colorWithCalibratedRed: 0.9 green: 0.9 blue: 1 alpha: 1], nil]; 618 619 // attribute dictionaries 620 NSDictionary *fontDict1 = [NSDictionary dictionaryWithObjects:valueArray1 forKeys:keyArray]; 621 NSDictionary *fontDict2 = [NSDictionary dictionaryWithObjects:valueArray2 forKeys:keyArray]; 622 623 // drawing locations 624 NSPoint drawLocation = { 260, 256 }; 625 NSPoint drawLocation2 = { 256, 260 }; 626 627 [[NSColor colorWithCalibratedRed: 0.1 green: 0.47 blue: 0 alpha: 1] set]; 628 NSRectFill(rect); 629 if(gameState == GAME_STATE_PAUSED && !hadEndOfGame) { 630 [pauseString drawAtPoint:drawLocation withAttributes:fontDict1]; 631 [pauseString drawAtPoint:drawLocation2 withAttributes:fontDict2]; 632 } 633 else if(hadEndOfGame) { 634 [gameOverString drawAtPoint:drawLocation withAttributes:fontDict1]; 635 [gameOverString drawAtPoint:drawLocation2 withAttributes:fontDict2]; 636 } 637} 638 639- (int)gameState 640{ 641 return gameState; 642} 643 644@end 645 646 647 648 649