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