set Gem._state as property
[exterlulz-kokogems.git] / src / GameView.m
blob21745637bf9a65d6f12fef6038683f2b49292c07
1 /* ----====----====----====----====----====----====----====----====----====----
2 GameView.m (jeweltoy)
4 JewelToy is a simple game played against the clock.
5 Copyright (C) 2001  Giles Williams
7 This program is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public License
9 as published by the Free Software Foundation; either version 2
10 of the License, or (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
20 ----====----====----====----====----====----====----====----====----====---- */
22 #include <math.h>
23 //#import <OpenGL/gl.h>
24 //#import <OpenGL/glu.h>
26 #import "GameView.h"
27 #import "GameController.h"
28 #import "Game.h"
29 #import "Gem.h"
31 // MW...
33 #import "ScoreBubble.h"
35 // OpenGLSprites
37 #import "OpenGLSprite.h"
39 @implementation GameView
41 @synthesize _animating;
43 - (id)initWithFrame:(NSRect)frame {
45     //NSData    *tiffData;
47     NSOpenGLPixelFormatAttribute attrs[] = {
48         NSOpenGLPFADepthSize, 1,
49         NSOpenGLPFAAccelerated,
50         0};
51     NSOpenGLPixelFormat *pixFmt;
53     pixFmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs];
54     self = [super initWithFrame:frame pixelFormat:pixFmt];
56     m_glContextInitialized = NO;
58     _animating = NO;
59     showHighScores = NO;
60     showHint = YES;
61     ticsSinceLastMove = 0;
62     
63     docTypeDictionary = [NSDictionary dictionary];
64     
65     return self;
68 - (void) dealloc
70     if (backgroundColor)
71         [backgroundColor release];
72     if (gemImageArray)
73         [gemImageArray release];
74     if (gemSpriteArray)
75         [gemImageArray release];
76     if (backgroundImagePathArray)
77         [backgroundImagePathArray release];
78     [super dealloc];
81 - (void) graphicSetUp
83     int         i;
84     NSData      *tiffData;
86     backgroundColor = [[NSColor purpleColor] retain];
88     [self loadImageArray];
90         tiffData = [[NSUserDefaults standardUserDefaults]       dataForKey:@"backgroundTiffData"];
91     if (tiffData)
92         backgroundImage = [[NSImage alloc] initWithData:tiffData];
93     else
94         backgroundImage = [NSImage imageNamed:@"background"];
96     crosshairImage = [NSImage imageNamed:@"cross"];
97     movehintImage = [NSImage imageNamed:@"movehint"];
99     //////
100     //
101     // Make the Open GL Sprites!
102     //
103     backgroundSprite = [[OpenGLSprite alloc] initWithImage:backgroundImage
104                                              cropRectangle:NSMakeRect(0.0, 0.0, [backgroundImage size].width, [backgroundImage size].height)
105                                                       size:NSMakeSize(384.0,384.0)];
107     crosshairSprite = [[OpenGLSprite alloc] initWithImage:crosshairImage
108                                             cropRectangle:NSMakeRect(0.0, 0.0, [crosshairImage size].width, [crosshairImage size].height)
109                                                      size:NSMakeSize(48.0,48.0)];
110     movehintSprite = [[OpenGLSprite alloc] initWithImage:movehintImage
111                                            cropRectangle:NSMakeRect(0.0, 0.0, [movehintImage size].width, [movehintImage size].height)
112                                                     size:NSMakeSize(48.0,48.0)];
113     if (gemSpriteArray)
114         [gemSpriteArray release];
115     gemSpriteArray = [[NSMutableArray arrayWithCapacity:0] retain];
116     for (i = 0; i < 7; i++)
117     {
118         NSImage *image = [gemImageArray objectAtIndex:i];
119         OpenGLSprite *sprite = [[OpenGLSprite alloc] initWithImage:image
120                                                      cropRectangle:NSMakeRect(0.0, 0.0, [image size].width, [image size].height)
121                                                               size:NSMakeSize(48.0,48.0)];
122         
123         [gemSpriteArray addObject:sprite];
124         [sprite release];
125     }
127     if (!legendImage)
128     {
129         legendImage = [[NSImage alloc] initWithSize:NSMakeSize(384,384)];
130         [legendImage lockFocus];
131         [[NSColor clearColor] set];
132         NSRectFill(NSMakeRect(0,0,384,384));
133         [legendImage unlockFocus];
134         legendSprite = [[OpenGLSprite alloc] initWithImage:legendImage
135                                             cropRectangle:NSMakeRect(0.0, 0.0, [legendImage size].width, [legendImage size].height)
136                                                     size:[legendImage size]];
137         
138         [self setLegend:[NSImage imageNamed:@"title"]];
139     }
140     //
141     //
142     //////
144     // if custom backgrounds are to be used initialise the array of paths to images
145     //
146     if ([[NSUserDefaults standardUserDefaults]  boolForKey:@"useCustomBackgrounds"])
147     {
148         NSString *customBackgroundFolderPath = [[[NSUserDefaults standardUserDefaults] stringForKey:@"customBackgroundFolderPath"] stringByResolvingSymlinksInPath];
150         //NSLog(@"customBackgroundFolderPath = ",customBackgroundFolderPath);
151         
152         if (customBackgroundFolderPath)
153         {
154             // borrowed code here
155             NSDirectoryEnumerator *picturesFolderEnum;
156             NSString *relativeFilePath,*fullPath;
157             // grab all picture formats NSImage knows about - we'll assume that if we can read them,
158             // we can set them to be the desktop picture
159             NSArray *imageFormats=[NSImage imageFileTypes];
161             if (backgroundImagePathArray)
162                 [backgroundImagePathArray autorelease];
163             backgroundImagePathArray = [[NSMutableArray arrayWithCapacity:0] retain];
164             // build the array
166             // borrowed code here
167             // now we need to go scan the folder chosen, enumerating through to find all picture files
168             picturesFolderEnum=[[NSFileManager defaultManager] enumeratorAtPath:customBackgroundFolderPath];
170             relativeFilePath=[picturesFolderEnum nextObject];
171             while (relativeFilePath)
172             {
173                 fullPath=[NSString stringWithFormat:@"%@/%@",customBackgroundFolderPath,relativeFilePath];
175                 // If the file's extension or type matches a format that NSImage understands,
176                 // then we're good to go, and we add a new menu item, using the display name
177                 // (which may have a hidden extension) for the menu item's title and passing
178                 // the full path to the picture to store with the menu item
179                 if ([imageFormats containsObject:[relativeFilePath pathExtension]] ||
180                     [imageFormats containsObject:NSHFSTypeOfFile(fullPath)])
181                 {
182                     [backgroundImagePathArray addObject:fullPath];
183                 }
184                 relativeFilePath=[picturesFolderEnum nextObject];
185             }
187             //NSLog(@"[backgroundImagePathArray count]= %d",[backgroundImagePathArray count]);
188             
189             //
190         }
192     }
194     [self newBackground];
195     
196     
199 - (void) loadImageArray
201     BOOL        useAlternateGraphics, useImportedGraphics;
202     useAlternateGraphics = [[NSUserDefaults standardUserDefaults]
203                                         boolForKey:@"useAlternateGraphics"];
204     useImportedGraphics = [[NSUserDefaults standardUserDefaults]
205                                         boolForKey:@"useImportedGraphics"];
206     if (gemImageArray)
207         [gemImageArray release];
208     gemImageArray = [[NSMutableArray arrayWithCapacity:0] retain];
209     if (!useAlternateGraphics)
210     {
211         //NSLog(@"Loading regular graphics");
212         [gemImageArray addObject:[NSImage imageNamed:@"1gem"]];
213         [gemImageArray addObject:[NSImage imageNamed:@"2gem"]];
214         [gemImageArray addObject:[NSImage imageNamed:@"3gem"]];
215         [gemImageArray addObject:[NSImage imageNamed:@"4gem"]];
216         [gemImageArray addObject:[NSImage imageNamed:@"5gem"]];
217         [gemImageArray addObject:[NSImage imageNamed:@"6gem"]];
218         [gemImageArray addObject:[NSImage imageNamed:@"7gem"]];
219     }
220     else
221     {
222         //NSData        *tiffData = [[[NSUserDefaults standardUserDefaults]
223         //                                dataForKey:@"tiffData"] retain];
224         if (!useImportedGraphics)
225         {
226             //NSLog(@"Loading alternate graphics");
227             [gemImageArray addObject:[NSImage imageNamed:@"1gemA"]];
228             [gemImageArray addObject:[NSImage imageNamed:@"2gemA"]];
229             [gemImageArray addObject:[NSImage imageNamed:@"3gemA"]];
230             [gemImageArray addObject:[NSImage imageNamed:@"4gemA"]];
231             [gemImageArray addObject:[NSImage imageNamed:@"5gemA"]];
232             [gemImageArray addObject:[NSImage imageNamed:@"6gemA"]];
233             [gemImageArray addObject:[NSImage imageNamed:@"7gemA"]];
234         }
235         else
236         {
237             int i = 0;
238             //NSLog(@"Loading custom graphics");
239             for (i = 0; i < 7; i++)
240             {
241                 NSString *key = [NSString stringWithFormat:@"tiffGemImage%d", i];
242                 NSData  *tiffData = [[NSUserDefaults standardUserDefaults]
243                                         dataForKey:key];
244                 NSImage *gemImage = [[NSImage alloc] initWithData:tiffData];
245                 [gemImageArray addObject:gemImage];
246                 [gemImage release];
247             }
248             
249         }
250     }
253 - (void) setMuted:(BOOL)value
255     muted = value;
258 - (void) setShowHint:(BOOL)value
260     showHint = value;
263 - (void) setPaused:(BOOL)value
265     paused = value;
266     if (paused)
267     {
268         animationStatus = _animating;
269         _animating = NO;
270     }
271     else
272         _animating = animationStatus;
275 // ANIMATE called by the Timer
276 - (void) animate
278     if (!paused)
279     {
280         //
281         // MIKE WESSLER'S Scorebubbles
282         //
283         int b;
284         //
285         // needsUpdate added so setNeedsDisplay gets called once at most
286         //
287         BOOL needsUpdate = FALSE;
288         //
289         // animate bubbles, if any
290         for (b=0; b<[[game scoreBubbles] count]; b++) {
291             ScoreBubble *sb= [[game scoreBubbles] objectAtIndex:b];
292             int more= [sb animate];
293             needsUpdate = TRUE;
294             if (!more) {
295                 [[game scoreBubbles] removeObjectAtIndex:b];
296                 b--;
297             }
298         }
299         //
300         ////
301         
302         if (_animating)
303         {
304             //NSLog(@"GameView.animate");
305             if (game)
306             {
307                 int i,j,c;      // animate each gem in the grid
308                 c = 0;          // animation accumulator
309                 for (i = 0; i < 8; i++)
310                     for (j = 0; j < 8; j++)
311                         if ([game gemAt:i:j])   c += [[game gemAt:i:j] animate];
312                 if (c == 0)
313                     [gameController animationEnded];
314             }
315             needsUpdate = TRUE;
316         }
317         else
318         {
319             ticsSinceLastMove++;
320             if (ticsSinceLastMove > 500)
321             {
322                 needsUpdate = TRUE;
323             }
324         }
325         if (needsUpdate)
326             [self setNeedsDisplay:YES];
327     }
330 - (void) setGame:(Game *) agame
332     game = agame;
334 - (Game *) game
336     return game;
338 - (NSArray *) imageArray
340     return gemImageArray;
342 - (NSArray *) spriteArray
344     return gemSpriteArray;
347 - (void) newBackground
349     if (([gameController useCustomBackgrounds])&&(backgroundImagePathArray)&&([backgroundImagePathArray count] > 0))
350     {
351         NSString *imagePath = [backgroundImagePathArray objectAtIndex:0];
352         [backgroundImagePathArray addObject:imagePath];
353         [backgroundImagePathArray removeObjectAtIndex:0];
354         //NSLog(@"Taking image from path: %@",imagePath);
355         
356         backgroundImage = [[NSImage alloc] initWithContentsOfFile:imagePath];
357         
358         //NSLog(@"Image size is %f x %f",[backgroundImage size].width, [backgroundImage size].height);
360         [backgroundSprite substituteTextureFromImage:backgroundImage];
361         //backgroundSprite = [[OpenGLSprite alloc] initWithImage:backgroundImage
362         //                                         cropRectangle:NSMakeRect(0.0, 0.0, [backgroundImage size].width, [backgroundImage size].height)
363         //                                                 size:NSMakeSize(384.0,384.0)];
364     }
365     else
366     {
367         NSData *tiffData = [[NSUserDefaults standardUserDefaults]       dataForKey:@"backgroundTiffData"];
368         if (tiffData)
369             backgroundImage = [[NSImage alloc] initWithData:tiffData];
370         else
371             backgroundImage = [NSImage imageNamed:@"background"];
372         backgroundSprite = [[OpenGLSprite alloc] initWithImage:backgroundImage
373                                                  cropRectangle:NSMakeRect(0.0, 0.0, [backgroundImage size].width, [backgroundImage size].height)
374                                                           size:NSMakeSize(384.0,384.0)];
375     }    
378 - (void) setLegend:(id)value
380     // NEED TO DRAW LEGEND INTO LEGENDIMAGE THEN REPLACE THE TEXTURE IN LEGENDSPRITE
382     if (!value)         // is null
383     {
384         //NSLog(@"Legend cleared");
385         legend = value;
386         [self setNeedsDisplay:YES];
387         return;
388     }
390     //
391     //
392     [legendImage lockFocus];
393     [[NSColor clearColor] set];
394     NSRectFill(NSMakeRect(0,0,384,384));
395     if ([value isKindOfClass:[NSAttributedString class]])
396     {
397         NSPoint legendPoint = NSMakePoint((384 - [value size].width)/2,(384 - [value size].height)/2);
398         [(NSAttributedString *)value drawAtPoint:legendPoint];
399     }
400     if ([value isKindOfClass:[NSImage class]])
401     {
402         NSPoint legendPoint = NSMakePoint((384 - [value size].width)/2,(384 - [value size].height)/2);
403         [(NSImage *)value compositeToPoint:legendPoint operation:NSCompositeSourceOver];
404     }
405     [legendImage unlockFocus];
406     [legendSprite replaceTextureFromImage:legendImage
407                             cropRectangle:NSMakeRect(0.0, 0.0, [legendImage size].width, [legendImage size].height)];
409     legend = legendSprite;
410     ticsSinceLastMove = 0;
411     showHighScores = NO;
412     _animating = NO;
414     [self setNeedsDisplay:YES];
415     
416     //
417     //
420 - (void) setHTMLLegend:(NSString *)value
422     NSData              *htmlData = [NSData dataWithBytes:[value UTF8String] length:[value length]];
423     [self setLegend:[[NSAttributedString alloc] initWithHTML:htmlData documentAttributes:NULL]];
426 - (void) setHiScoreLegend:(NSAttributedString *)value
428     hiScoreLegend = value;
431 - (void) setHTMLHiScoreLegend:(NSString *)value
433     NSData *htmlData = [NSData dataWithBytes:[value UTF8String] length:[value length]];
434     [self setHiScoreLegend:[[NSAttributedString alloc] initWithHTML:htmlData documentAttributes:NULL]];
437 - (void) setLastMoveDate
439     ticsSinceLastMove = 0;
442 - (void) showHighScores:(NSArray *)scores andNames:(NSArray *)names
444     hiScoreNumbers = scores;
445     hiScoreNames = names;
446     showHighScores = YES;
447     _animating = NO;
448     scoreScroll = 0;
449     [self setNeedsDisplay:YES];
453 // drawRect: should be overridden in subclassers of NSView to do necessary
454 // drawing in order to recreate the the look of the view. It will be called
455 // to draw the whole view or parts of it (pay attention the rect argument);
456 // it will also be called during printing if your app is set up to print.
458 - (void)drawRect:(NSRect)rect {
459     int i,j;
461     // Open GL
462     float       size = 384.0/2.0;       // screenSize/2;
463     float       clearDepth = 1.0;
465     // try to fix image loading problem
466     if (!legendImage)
467         [self graphicSetUp];
469     //////////
471     if (!m_glContextInitialized)
472     {
473         glShadeModel(GL_FLAT);
475         glMatrixMode(GL_PROJECTION);
476         glLoadIdentity();       // reset matrix
478         glFrustum(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0);     // set projection matrix
479         glMatrixMode(GL_MODELVIEW);
481         //glEnable(GL_DEPTH_TEST);              // depth buffer
483         glEnable(GL_BLEND);                     // alpha blending
484         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);              // alpha blending
485         m_glContextInitialized = YES;
486     }
488     glClearColor(0.3, 0.3, 0.3, 0.0);
489     glClearDepth(clearDepth);
490     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
492     glLoadIdentity();   // allows me to resize the window but keep position OK
494     glTranslatef(-1.0,-1.0,0.0);
495     glScalef(1/size,1/size,1.0);// scale to screen size and width
497     if (backgroundSprite)
498     {
499         [backgroundSprite blitToX:0.0
500                                 Y:0.0
501                                 Z:0.0];
502     }
503     if ((game)&&(!paused))
504     {
505         for (i = 0; i < 8; i++)
506             for (j = 0; j < 8; j++)
507                 [[game gemAt:i :j] drawSprite];
508         //
509         // MW - Scorebubbles
510         //
511         for (i=0; i<[[game scoreBubbles] count]; i++) {
512             ScoreBubble *sb= [[game scoreBubbles] objectAtIndex:i];
513             [sb drawSprite];
514         }
515         //
516         ////
517     }
519     if ([gameController gameState] == GAMESTATE_AWAITINGSECONDCLICK)
520     {
521         [crosshairSprite blitToX:[gameController crossHair1Position].x
522                                Y:[gameController crossHair1Position].y
523                                Z:-0.5];
524     }
525     if (showHighScores)
526     {
527         [self showScores];      // draws the HighScores in the current focused view (Quartz)
528         //return;               // now legendSprite contains the drawing...
529     }
530     if (!legend)
531     {
532         if ((ticsSinceLastMove > 500)&&(showHint))
533         {
534             [movehintSprite blitToX:[game hintPoint].x
535                                    Y:[game hintPoint].y
536                                    Z:-0.4
537                               Alpha:(sin((ticsSinceLastMove-497.0)/4.0)+1.0)/2.0];
538         }
539     }
540     else
541     {
542         if (ticsSinceLastMove > 500)
543             [self setLegend:[NSImage imageNamed:@"title"]];     // show Logo
544         if ([legend isKindOfClass:[OpenGLSprite class]])
545         {
546             //NSLog(@"Blitting legend");
547             [legend blitToX:0.0 Y:0.0 Z:-0.75];
548         }
549     }
550     glFlush();
551     
554 - (void) showScores
556     int i;
557     NSPoint legendPoint;
558     NSRect panelRect;
559     NSMutableDictionary *attr = [NSMutableDictionary dictionaryWithCapacity:0];
561     [attr setObject:[NSColor yellowColor] forKey:NSForegroundColorAttributeName];
563     [legendImage lockFocus];
564     [[NSColor clearColor] set];
565     NSRectFill(NSMakeRect(0,0,384,384));
567     [[NSColor colorWithCalibratedRed:0.0 green:0.0 blue:0.0 alpha:0.5] set];
568     panelRect = NSMakeRect(32, 16, 384-64, 384-32);
569     NSRectFill(panelRect);
570     
571     
572     legendPoint = NSMakePoint((384 - [hiScoreLegend size].width)/2,384 - [hiScoreLegend size].height*1.5 + scoreScroll);
574     [hiScoreLegend drawAtPoint:legendPoint];
575     
576     for (i = 0; i< 10; i++)
577     {
578         NSString *s1 = [NSString stringWithFormat:@"%d",[[hiScoreNumbers objectAtIndex:i] intValue]];
579         NSString *s2 = [hiScoreNames objectAtIndex:i];
580         NSPoint q1 = NSMakePoint( 192+20+1, 384 - 84 - i*30 + scoreScroll - 1); 
581         NSPoint q2 = NSMakePoint( 192-20-[s2 sizeWithAttributes:attr].width+1, 384 - 84 - i*30 + scoreScroll - 1); 
582         NSPoint p1 = NSMakePoint( 192+20, 384 - 84 - i*30 + scoreScroll); 
583         NSPoint p2 = NSMakePoint( 192-20-[s2 sizeWithAttributes:attr].width, 384 - 84 - i*30 + scoreScroll); 
585         [attr setObject:[NSColor blackColor] forKey:NSForegroundColorAttributeName];
587         [s1 drawAtPoint:q1 withAttributes:attr];
588         [s2 drawAtPoint:q2 withAttributes:attr];
589         
590         [attr setObject:[NSColor yellowColor] forKey:NSForegroundColorAttributeName];
592         [s1 drawAtPoint:p1 withAttributes:attr];
593         [s2 drawAtPoint:p2 withAttributes:attr];
594     }
595     [legendImage unlockFocus];
596     [legendSprite replaceTextureFromImage:legendImage
597                             cropRectangle:NSMakeRect(0.0, 0.0, [legendImage size].width, [legendImage size].height)];
599     legend = legendSprite;
600     
601     showHighScores = NO;
604 // Views which totally redraw their whole bounds without needing any of the
605 // views behind it should override isOpaque to return YES. This is a performance
606 // optimization hint for the display subsystem. This applies to DotView, whose
607 // drawRect: does fill the whole rect its given with a solid, opaque color.
609 - (BOOL)isOpaque {
610     return YES;
613 // Recommended way to handle events is to override NSResponder (superclass
614 // of NSView) methods in the NSView subclass. One such method is mouseUp:.
615 // These methods get the event as the argument. The event has the mouse
616 // location in window coordinates; use convertPoint:fromView: (with "nil"
617 // as the view argument) to convert this point to local view coordinates.
619 // Note that once we get the new center, we call setNeedsDisplay:YES to 
620 // mark that the view needs to be redisplayed (which is done automatically
621 // by the AppKit).
624 // I want to add a new behaviour here, the click-drag for a square
625 // I'm prolly going to have to fake this by sending gameController two clicks
626 // I might have to change the shape of the mouse cursor too!
628 - (void)mouseDown:(NSEvent *)event {
629     NSPoint eventLocation = [event locationInWindow];
630     NSPoint center = [self convertPoint:eventLocation fromView:nil];
631     dragStartPoint = center;
634 - (void)mouseDragged:(NSEvent *)event {
635     // do nothing for now
638 - (void)mouseUp:(NSEvent *)event {
639     NSPoint eventLocation = [event locationInWindow];
640     NSPoint center = [self convertPoint:eventLocation fromView:nil];
641     
642     // check situation - is this a first or second mouseUp
643     if ([gameController gameState] == GAMESTATE_AWAITINGSECONDCLICK)
644     {
645         //NSLog(@"click at :%f,%f",center.x,center.y);
646         [gameController receiveClickAt:center.x:center.y];
647     }
648     else if ([gameController gameState] == GAMESTATE_AWAITINGFIRSTCLICK)
649     {
650         int chx1 = floor(dragStartPoint.x / 48);
651         int chy1 = floor(dragStartPoint.y / 48);
652         int chx2 = floor(center.x / 48);
653         int chy2 = floor(center.y / 48);
654         if ((chx2 != chx1)^(chy2 != chy1))      // xor checks if a valid shove's occurred!
655         {
656             [gameController receiveClickAt:dragStartPoint.x:dragStartPoint.y];
657             [gameController receiveClickAt:center.x:center.y];
658         }
659         else
660         {
661             [gameController receiveClickAt:center.x:center.y];
662         }
663     }
664         
667 @end