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 ticksSinceLastMove = 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 (NSUInteger b = 0; b < [[game scoreBubbles] count]; b++) {
291 ScoreBubble *sb = [[game scoreBubbles] objectAtIndex:b];
292 int more = [sb animate];
295 [[game scoreBubbles] removeObjectAtIndex:b];
302 //NSLog(@"GameView.animate");
305 int i,j,c; // animate each gem in the grid
306 c = 0; // animation accumulator
307 for (i = 0; i < 8; i++)
308 for (j = 0; j < 8; j++)
309 if ([game gemAt:i:j]) c += [[game gemAt:i:j] animate];
311 [gameController animationEnded];
317 ticksSinceLastMove++;
318 if (ticksSinceLastMove > 500)
324 [self setNeedsDisplay:YES];
328 - (void) setGame:(Game *) agame
336 - (NSArray *) imageArray
338 return gemImageArray;
340 - (NSArray *) spriteArray
342 return gemSpriteArray;
345 - (void) newBackground
347 if (([gameController useCustomBackgrounds])&&(backgroundImagePathArray)&&([backgroundImagePathArray count] > 0))
349 NSString *imagePath = [backgroundImagePathArray objectAtIndex:0];
350 [backgroundImagePathArray addObject:imagePath];
351 [backgroundImagePathArray removeObjectAtIndex:0];
352 //NSLog(@"Taking image from path: %@",imagePath);
354 backgroundImage = [[NSImage alloc] initWithContentsOfFile:imagePath];
356 //NSLog(@"Image size is %f x %f",[backgroundImage size].width, [backgroundImage size].height);
358 [backgroundSprite substituteTextureFromImage:backgroundImage];
359 //backgroundSprite = [[OpenGLSprite alloc] initWithImage:backgroundImage
360 // cropRectangle:NSMakeRect(0.0, 0.0, [backgroundImage size].width, [backgroundImage size].height)
361 // size:NSMakeSize(384.0,384.0)];
365 NSData *tiffData = [[NSUserDefaults standardUserDefaults] dataForKey:@"backgroundTiffData"];
367 backgroundImage = [[NSImage alloc] initWithData:tiffData];
369 backgroundImage = [NSImage imageNamed:@"background"];
370 backgroundSprite = [[OpenGLSprite alloc] initWithImage:backgroundImage
371 cropRectangle:NSMakeRect(0.0, 0.0, [backgroundImage size].width, [backgroundImage size].height)
372 size:NSMakeSize(384.0,384.0)];
376 - (void) setLegend:(id)value
378 // NEED TO DRAW LEGEND INTO LEGENDIMAGE THEN REPLACE THE TEXTURE IN LEGENDSPRITE
380 if (!value) // is null
382 //NSLog(@"Legend cleared");
384 [self setNeedsDisplay:YES];
390 [legendImage lockFocus];
391 [[NSColor clearColor] set];
392 NSRectFill(NSMakeRect(0,0,384,384));
393 if ([value isKindOfClass:[NSAttributedString class]])
395 NSPoint legendPoint = NSMakePoint((384 - [value size].width)/2,(384 - [value size].height)/2);
396 [(NSAttributedString *)value drawAtPoint:legendPoint];
398 if ([value isKindOfClass:[NSImage class]])
400 NSPoint legendPoint = NSMakePoint((384 - [value size].width)/2,(384 - [value size].height)/2);
401 [(NSImage *)value compositeToPoint:legendPoint operation:NSCompositeSourceOver];
403 [legendImage unlockFocus];
404 [legendSprite replaceTextureFromImage:legendImage
405 cropRectangle:NSMakeRect(0.0, 0.0, [legendImage size].width, [legendImage size].height)];
407 legend = legendSprite;
408 ticksSinceLastMove = 0;
412 [self setNeedsDisplay:YES];
418 - (void) setHTMLLegend:(NSString *)value
420 NSData *htmlData = [NSData dataWithBytes:[value UTF8String] length:[value length]];
421 [self setLegend:[[NSAttributedString alloc] initWithHTML:htmlData documentAttributes:NULL]];
424 - (void) setHiScoreLegend:(NSAttributedString *)value
426 hiScoreLegend = value;
429 - (void) setHTMLHiScoreLegend:(NSString *)value
431 NSData *htmlData = [NSData dataWithBytes:[value UTF8String] length:[value length]];
432 [self setHiScoreLegend:[[NSAttributedString alloc] initWithHTML:htmlData documentAttributes:NULL]];
435 - (void) setLastMoveDate
437 ticksSinceLastMove = 0;
440 - (void) showHighScores:(NSArray *)scores andNames:(NSArray *)names
442 hiScoreNumbers = scores;
443 hiScoreNames = names;
444 showHighScores = YES;
447 [self setNeedsDisplay:YES];
450 // drawRect: should be overridden in subclassers of NSView to do necessary
451 // drawing in order to recreate the the look of the view. It will be called
452 // to draw the whole view or parts of it (pay attention the rect argument);
453 // it will also be called during printing if your app is set up to print.
455 - (void)drawRect:(NSRect)rect {
459 float size = 384.0/2.0; // screenSize/2;
460 float clearDepth = 1.0;
462 // try to fix image loading problem
468 if (!m_glContextInitialized)
470 glShadeModel(GL_FLAT);
472 glMatrixMode(GL_PROJECTION);
473 glLoadIdentity(); // reset matrix
475 glFrustum(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0); // set projection matrix
476 glMatrixMode(GL_MODELVIEW);
478 //glEnable(GL_DEPTH_TEST); // depth buffer
480 glEnable(GL_BLEND); // alpha blending
481 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // alpha blending
482 m_glContextInitialized = YES;
485 glClearColor(0.3, 0.3, 0.3, 0.0);
486 glClearDepth(clearDepth);
487 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
489 glLoadIdentity(); // allows me to resize the window but keep position OK
491 glTranslatef(-1.0,-1.0,0.0);
492 glScalef(1/size,1/size,1.0);// scale to screen size and width
494 if (backgroundSprite)
496 [backgroundSprite blitToX:0.0
500 if ((game)&&(!paused))
502 for (i = 0; i < 8; i++)
503 for (j = 0; j < 8; j++)
504 [[game gemAt:i :j] drawSprite];
508 for (i=0; i<[[game scoreBubbles] count]; i++) {
509 ScoreBubble *sb= [[game scoreBubbles] objectAtIndex:i];
516 if ([gameController gameState] == GAMESTATE_AWAITINGSECONDCLICK)
518 [crosshairSprite blitToX:[gameController crossHair1Position].x
519 Y:[gameController crossHair1Position].y
524 [self showScores]; // draws the HighScores in the current focused view (Quartz)
525 //return; // now legendSprite contains the drawing...
529 if ((ticksSinceLastMove > 500)&&(showHint))
531 [movehintSprite blitToX:[game hintPoint].x
534 Alpha:(sin((ticksSinceLastMove-497.0)/4.0)+1.0)/2.0];
539 if (ticksSinceLastMove > 500)
540 [self setLegend:[NSImage imageNamed:@"title"]]; // show Logo
541 if ([legend isKindOfClass:[OpenGLSprite class]])
543 //NSLog(@"Blitting legend");
544 [legend blitToX:0.0 Y:0.0 Z:-0.75];
556 NSMutableDictionary *attr = [NSMutableDictionary dictionaryWithCapacity:0];
558 [attr setObject:[NSColor yellowColor] forKey:NSForegroundColorAttributeName];
560 [legendImage lockFocus];
561 [[NSColor clearColor] set];
562 NSRectFill(NSMakeRect(0,0,384,384));
564 [[NSColor colorWithCalibratedRed:0.0 green:0.0 blue:0.0 alpha:0.5] set];
565 panelRect = NSMakeRect(32, 16, 384-64, 384-32);
566 NSRectFill(panelRect);
569 legendPoint = NSMakePoint((384 - [hiScoreLegend size].width)/2,384 - [hiScoreLegend size].height*1.5 + scoreScroll);
571 [hiScoreLegend drawAtPoint:legendPoint];
573 for (i = 0; i< 10; i++)
575 NSString *s1 = [NSString stringWithFormat:@"%d",[[hiScoreNumbers objectAtIndex:i] intValue]];
576 NSString *s2 = [hiScoreNames objectAtIndex:i];
577 NSPoint q1 = NSMakePoint( 192+20+1, 384 - 84 - i*30 + scoreScroll - 1);
578 NSPoint q2 = NSMakePoint( 192-20-[s2 sizeWithAttributes:attr].width+1, 384 - 84 - i*30 + scoreScroll - 1);
579 NSPoint p1 = NSMakePoint( 192+20, 384 - 84 - i*30 + scoreScroll);
580 NSPoint p2 = NSMakePoint( 192-20-[s2 sizeWithAttributes:attr].width, 384 - 84 - i*30 + scoreScroll);
582 [attr setObject:[NSColor blackColor] forKey:NSForegroundColorAttributeName];
584 [s1 drawAtPoint:q1 withAttributes:attr];
585 [s2 drawAtPoint:q2 withAttributes:attr];
587 [attr setObject:[NSColor yellowColor] forKey:NSForegroundColorAttributeName];
589 [s1 drawAtPoint:p1 withAttributes:attr];
590 [s2 drawAtPoint:p2 withAttributes:attr];
592 [legendImage unlockFocus];
593 [legendSprite replaceTextureFromImage:legendImage
594 cropRectangle:NSMakeRect(0.0, 0.0, [legendImage size].width, [legendImage size].height)];
596 legend = legendSprite;
605 /* I want to add a new behaviour here, the click-drag for a square
606 * I'm prolly going to have to fake this by sending gameController two clicks
607 * I might have to change the shape of the mouse cursor too!
609 - (void)mouseDown:(NSEvent *)event {
610 NSPoint eventLocation = [event locationInWindow];
611 NSPoint center = [self convertPoint:eventLocation fromView:nil];
612 dragStartPoint = center;
615 - (void)mouseDragged:(NSEvent *)event {
616 // do nothing for now
619 - (void)mouseUp:(NSEvent *)event
621 NSPoint eventLocation = [event locationInWindow];
622 NSPoint center = [self convertPoint:eventLocation fromView:nil];
624 // check situation - is this a first or second mouseUp
625 if ([gameController gameState] == GAMESTATE_AWAITINGSECONDCLICK)
627 //NSLog(@"click at :%f,%f",center.x,center.y);
628 [gameController receiveClickAt:center.x:center.y];
630 else if ([gameController gameState] == GAMESTATE_AWAITINGFIRSTCLICK)
632 int chx1 = floor(dragStartPoint.x / 48);
633 int chy1 = floor(dragStartPoint.y / 48);
634 int chx2 = floor(center.x / 48);
635 int chy2 = floor(center.y / 48);
637 // ???: WTF, you don't really xor booleans...
638 if ((chx2 != chx1)^(chy2 != chy1)) // xor checks if a valid shove's occurred!
640 [gameController receiveClickAt:dragStartPoint.x:dragStartPoint.y];
641 [gameController receiveClickAt:center.x:center.y];
644 [gameController receiveClickAt:center.x:center.y];