1 /* ----====----====----====----====----====----====----====----====----====----
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 ----====----====----====----====----====----====----====----====----====---- */
23 //#import <OpenGL/gl.h>
24 //#import <OpenGL/glu.h>
27 #import "GameController.h"
33 #import "ScoreBubble.h"
37 #import "OpenGLSprite.h"
39 @implementation GameView
41 @synthesize _animating;
43 - (id)initWithFrame:(NSRect)frame {
47 NSOpenGLPixelFormatAttribute attrs[] = {
48 NSOpenGLPFADepthSize, 1,
49 NSOpenGLPFAAccelerated,
51 NSOpenGLPixelFormat *pixFmt;
53 pixFmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs];
54 self = [super initWithFrame:frame pixelFormat:pixFmt];
56 m_glContextInitialized = NO;
61 ticsSinceLastMove = 0;
63 docTypeDictionary = [NSDictionary dictionary];
71 [backgroundColor release];
73 [gemImageArray release];
75 [gemImageArray release];
76 if (backgroundImagePathArray)
77 [backgroundImagePathArray release];
86 backgroundColor = [[NSColor purpleColor] retain];
88 [self loadImageArray];
90 tiffData = [[NSUserDefaults standardUserDefaults] dataForKey:@"backgroundTiffData"];
92 backgroundImage = [[NSImage alloc] initWithData:tiffData];
94 backgroundImage = [NSImage imageNamed:@"background"];
96 crosshairImage = [NSImage imageNamed:@"cross"];
97 movehintImage = [NSImage imageNamed:@"movehint"];
101 // Make the Open GL Sprites!
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)];
114 [gemSpriteArray release];
115 gemSpriteArray = [[NSMutableArray arrayWithCapacity:0] retain];
116 for (i = 0; i < 7; i++)
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)];
123 [gemSpriteArray addObject:sprite];
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]];
138 [self setLegend:[NSImage imageNamed:@"title"]];
144 // if custom backgrounds are to be used initialise the array of paths to images
146 if ([[NSUserDefaults standardUserDefaults] boolForKey:@"useCustomBackgrounds"])
148 NSString *customBackgroundFolderPath = [[[NSUserDefaults standardUserDefaults] stringForKey:@"customBackgroundFolderPath"] stringByResolvingSymlinksInPath];
150 //NSLog(@"customBackgroundFolderPath = ",customBackgroundFolderPath);
152 if (customBackgroundFolderPath)
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];
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)
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)])
182 [backgroundImagePathArray addObject:fullPath];
184 relativeFilePath=[picturesFolderEnum nextObject];
187 //NSLog(@"[backgroundImagePathArray count]= %d",[backgroundImagePathArray count]);
194 [self newBackground];
199 - (void) loadImageArray
201 BOOL useAlternateGraphics, useImportedGraphics;
202 useAlternateGraphics = [[NSUserDefaults standardUserDefaults]
203 boolForKey:@"useAlternateGraphics"];
204 useImportedGraphics = [[NSUserDefaults standardUserDefaults]
205 boolForKey:@"useImportedGraphics"];
207 [gemImageArray release];
208 gemImageArray = [[NSMutableArray arrayWithCapacity:0] retain];
209 if (!useAlternateGraphics)
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"]];
222 //NSData *tiffData = [[[NSUserDefaults standardUserDefaults]
223 // dataForKey:@"tiffData"] retain];
224 if (!useImportedGraphics)
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"]];
238 //NSLog(@"Loading custom graphics");
239 for (i = 0; i < 7; i++)
241 NSString *key = [NSString stringWithFormat:@"tiffGemImage%d", i];
242 NSData *tiffData = [[NSUserDefaults standardUserDefaults]
244 NSImage *gemImage = [[NSImage alloc] initWithData:tiffData];
245 [gemImageArray addObject:gemImage];
253 - (void) setMuted:(BOOL)value
258 - (void) setShowHint:(BOOL)value
263 - (void) setPaused:(BOOL)value
268 animationStatus = _animating;
272 _animating = animationStatus;
275 // ANIMATE called by the Timer
281 // MIKE WESSLER'S Scorebubbles
285 // needsUpdate added so setNeedsDisplay gets called once at most
287 BOOL needsUpdate = FALSE;
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];
295 [[game scoreBubbles] removeObjectAtIndex:b];
304 //NSLog(@"GameView.animate");
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];
313 [gameController animationEnded];
320 if (ticsSinceLastMove > 500)
326 [self setNeedsDisplay:YES];
330 - (void) setGame:(Game *) agame
338 - (NSArray *) imageArray
340 return gemImageArray;
342 - (NSArray *) spriteArray
344 return gemSpriteArray;
347 - (void) newBackground
349 if (([gameController useCustomBackgrounds])&&(backgroundImagePathArray)&&([backgroundImagePathArray count] > 0))
351 NSString *imagePath = [backgroundImagePathArray objectAtIndex:0];
352 [backgroundImagePathArray addObject:imagePath];
353 [backgroundImagePathArray removeObjectAtIndex:0];
354 //NSLog(@"Taking image from path: %@",imagePath);
356 backgroundImage = [[NSImage alloc] initWithContentsOfFile:imagePath];
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)];
367 NSData *tiffData = [[NSUserDefaults standardUserDefaults] dataForKey:@"backgroundTiffData"];
369 backgroundImage = [[NSImage alloc] initWithData:tiffData];
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)];
378 - (void) setLegend:(id)value
380 // NEED TO DRAW LEGEND INTO LEGENDIMAGE THEN REPLACE THE TEXTURE IN LEGENDSPRITE
382 if (!value) // is null
384 //NSLog(@"Legend cleared");
386 [self setNeedsDisplay:YES];
392 [legendImage lockFocus];
393 [[NSColor clearColor] set];
394 NSRectFill(NSMakeRect(0,0,384,384));
395 if ([value isKindOfClass:[NSAttributedString class]])
397 NSPoint legendPoint = NSMakePoint((384 - [value size].width)/2,(384 - [value size].height)/2);
398 [(NSAttributedString *)value drawAtPoint:legendPoint];
400 if ([value isKindOfClass:[NSImage class]])
402 NSPoint legendPoint = NSMakePoint((384 - [value size].width)/2,(384 - [value size].height)/2);
403 [(NSImage *)value compositeToPoint:legendPoint operation:NSCompositeSourceOver];
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;
414 [self setNeedsDisplay:YES];
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;
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 {
462 float size = 384.0/2.0; // screenSize/2;
463 float clearDepth = 1.0;
465 // try to fix image loading problem
471 if (!m_glContextInitialized)
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;
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)
499 [backgroundSprite blitToX:0.0
503 if ((game)&&(!paused))
505 for (i = 0; i < 8; i++)
506 for (j = 0; j < 8; j++)
507 [[game gemAt:i :j] drawSprite];
511 for (i=0; i<[[game scoreBubbles] count]; i++) {
512 ScoreBubble *sb= [[game scoreBubbles] objectAtIndex:i];
519 if ([gameController gameState] == GAMESTATE_AWAITINGSECONDCLICK)
521 [crosshairSprite blitToX:[gameController crossHair1Position].x
522 Y:[gameController crossHair1Position].y
527 [self showScores]; // draws the HighScores in the current focused view (Quartz)
528 //return; // now legendSprite contains the drawing...
532 if ((ticsSinceLastMove > 500)&&(showHint))
534 [movehintSprite blitToX:[game hintPoint].x
537 Alpha:(sin((ticsSinceLastMove-497.0)/4.0)+1.0)/2.0];
542 if (ticsSinceLastMove > 500)
543 [self setLegend:[NSImage imageNamed:@"title"]]; // show Logo
544 if ([legend isKindOfClass:[OpenGLSprite class]])
546 //NSLog(@"Blitting legend");
547 [legend blitToX:0.0 Y:0.0 Z:-0.75];
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);
572 legendPoint = NSMakePoint((384 - [hiScoreLegend size].width)/2,384 - [hiScoreLegend size].height*1.5 + scoreScroll);
574 [hiScoreLegend drawAtPoint:legendPoint];
576 for (i = 0; i< 10; i++)
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];
590 [attr setObject:[NSColor yellowColor] forKey:NSForegroundColorAttributeName];
592 [s1 drawAtPoint:p1 withAttributes:attr];
593 [s2 drawAtPoint:p2 withAttributes:attr];
595 [legendImage unlockFocus];
596 [legendSprite replaceTextureFromImage:legendImage
597 cropRectangle:NSMakeRect(0.0, 0.0, [legendImage size].width, [legendImage size].height)];
599 legend = legendSprite;
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.
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
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];
642 // check situation - is this a first or second mouseUp
643 if ([gameController gameState] == GAMESTATE_AWAITINGSECONDCLICK)
645 //NSLog(@"click at :%f,%f",center.x,center.y);
646 [gameController receiveClickAt:center.x:center.y];
648 else if ([gameController gameState] == GAMESTATE_AWAITINGFIRSTCLICK)
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!
656 [gameController receiveClickAt:dragStartPoint.x:dragStartPoint.y];
657 [gameController receiveClickAt:center.x:center.y];
661 [gameController receiveClickAt:center.x:center.y];