1/*
2 * LapisPuzzleView.m
3
4 * Copyright 2004-2011 The Free Software Foundation
5 *
6 * Copyright (C) 2004 Banlu Kemiyatorn.
7 * July 19, 2004
8 * Written by Banlu Kemiyatorn <object at gmail dot com>
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Library General Public
11 * License as published by the Free Software Foundation; either
12 * version 2 of the License, or (at your option) any later version.
13
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17 * Library General Public License for more details.
18
19 * You should have received a copy of the GNU Library General Public
20 * License along with this library; if not, write to the Free
21 * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA.
22 */
23
24#import "LapisPuzzleView.h"
25#import "LPController.h"
26#include <math.h>
27
28static float _grid_height;
29
30#define MATCH_COLOR(a,b) ((a == b))
31
32@implementation LPUnit
33
34- (id) description
35{
36	return [NSString stringWithFormat:@"<%@: %p %d>",[self className], self, _color];
37}
38
39+ (void) setGridHeight:(float)points
40{
41	_grid_height = points;
42}
43
44- (id) initWithOwner:(id <LPUnitOwner>)owner
45			   color:(LPUnitColorType)color
46{
47	alpha = 1.0;
48	__owner = owner;
49	_color = color;
50	_isBlowing = NO;
51	return self;
52}
53
54- (void) setOwner:(id <LPUnitOwner>)owner
55{
56	__owner = owner;
57}
58
59- (float) alpha
60{
61	return alpha;
62}
63
64- (void) setAlpha:(float)a
65{
66  if (a < 0)
67    a = 0;
68  alpha = a;
69}
70
71- (LPUnitColorType) unitColor
72{
73	return _color;
74}
75
76- (void) setUnitColor:(LPUnitColorType)color
77{
78	_color = color;
79}
80
81- (int) rows
82{
83	return 0;
84}
85
86- (int) columns
87{
88	return 0;
89}
90
91- (BOOL) hasPartAtX:(int)x
92				  Y:(int)y
93{
94	return NO;
95}
96
97- (void) fallToBottom
98{
99	while ([self moveInDir:LP_MOVE_DOWN]);
100}
101
102- (BOOL) isBlowing
103{
104	return _isBlowing;
105}
106
107- (void) softBlow
108{
109	_isBlowing = YES;
110}
111
112- (void) blow
113{
114	int unit_x, unit_y, unit_rows, unit_columns;
115	int i;
116	id m;
117
118	if (_isBlowing)
119	{
120		return;
121	}
122
123	_isBlowing = YES;
124
125	unit_x = [self X];
126	unit_y = [self Y];
127	unit_rows = [self rows];
128	unit_columns = [self columns];
129
130	for (i = 0; i < unit_columns; i++)
131	{
132		m = [__owner getUnitAtX:unit_x + i
133			     Y:unit_y - 1];
134
135		if (m && (MATCH_COLOR([m unitColor], _color) || [m isMemberOfClass:[LPStoneUnit class]]))
136		{
137			[m blow];
138		}
139
140		m = [__owner getUnitAtX:unit_x + i
141			     Y:unit_y + unit_rows];
142		if (m && (MATCH_COLOR([m unitColor], _color) || [m isMemberOfClass:[LPStoneUnit class]]))
143		{
144			[m blow];
145		}
146
147	}
148
149	for (i = 0; i < unit_rows; i++)
150	{
151		m = [__owner getUnitAtX:unit_x - 1
152			     Y:unit_y + i];
153		if (m && (MATCH_COLOR([m unitColor], _color) || [m isMemberOfClass:[LPStoneUnit class]]))
154		{
155			[m blow];
156		}
157
158		m = [__owner getUnitAtX:unit_x + unit_columns
159			     Y:unit_y + i];
160		if (m && (MATCH_COLOR([m unitColor], _color) || [m isMemberOfClass:[LPStoneUnit class]]))
161		{
162			[m blow];
163		}
164
165	}
166}
167
168/** subclass responsibility **/
169
170- (BOOL) moveInDir:(LPDirType)dir
171{
172  return NO;
173}
174
175- (BOOL) rMoveX:(int)rx Y:(int)ry
176{
177  return NO;
178}
179
180- (void) changePhase
181{
182}
183
184- (int) X
185{
186  return 0;
187}
188
189- (int) Y
190{
191  return 0;
192}
193
194- (float) phase
195{
196  return 0.0;
197}
198
199- (void) explode
200{
201}
202
203- (void) draw
204{
205}
206
207- (void) round
208{
209}
210
211- (void) setX:(unsigned int)x Y:(unsigned int)y
212{
213}
214
215- (BOOL) canMoveInDir:(LPDirType)dir
216{
217  return NO;
218}
219
220- (BOOL) canRMoveX:(int)rx Y:(int)ry
221{
222  return NO;
223}
224
225@end
226
227@implementation LPStoneUnit;
228- (id) initWithOwner:(id <LPUnitOwner>)owner
229			   color:(LPUnitColorType)color
230				   X:(int)x
231				   Y:(int)y
232{
233	_count = 5;
234
235	return [super initWithOwner:owner
236						  color:color
237							  X:x
238							  Y:y];
239}
240
241- (int) count
242{
243	return _count;
244}
245
246- (void) countDown
247{
248	_count--;
249}
250
251
252- (void) draw
253{
254	int i;
255	NSColor *tcolor;
256	NSMutableAttributedString *str;
257	NSSize strSize;
258	NSSize gz = [__owner gridSize];
259	float border = gz.width/6;
260
261	[super draw];
262
263	PSsetrgbcolor(0.7,0.7,0.7);
264	PSsetalpha(alpha);
265	PSrectfill(_x * gz.width , _y *gz.height, _columns * gz.width, _rows * gz.height);
266	PSsetrgbcolor(1,1,1);
267
268	PSsetalpha(alpha/3);
269	PSsetlinewidth(0);
270	PSmoveto(_x * gz.width, _y * gz.height);
271	PSlineto(_x * gz.width + gz.width/4, _y * gz.height + gz.width/4);
272	PSlineto(_x * gz.width + gz.width/4, (_y+_rows) * gz.height - gz.width/4);
273	PSlineto(_x * gz.width, (_y+_rows) * gz.height);
274	PSfill();
275
276	PSsetalpha(alpha);
277	PSsetlinewidth(0);
278	PSmoveto(_x * gz.width, (_y+_rows) * gz.height);
279	PSlineto(_x * gz.width + gz.width/4, (_y+_rows) * gz.height - gz.width/4);
280	PSlineto((_x+_columns) * gz.width - gz.width/4, (_y+_rows) * gz.height - gz.width/4);
281	PSlineto((_x+_columns) * gz.width, (_y+_rows) * gz.height);
282	PSfill();
283
284	for (i = 0; i <_rows; i+=2)
285	{
286		PSgsave();
287			PSsetlinecap(1);
288			PSsetalpha(alpha/10);
289			PSsetlinewidth(_columns * border * 4);
290			PSrectclip(_x * gz.width + gz.width/4, _y *gz.height + gz.height/4, _columns * gz.width - gz.width/2, _rows * gz.height - gz.height/2);
291
292			PSmoveto(_x * gz.width, (_y + i) * gz.height);
293			PSlineto((_x + _columns) * gz.width, (_y + i + _columns) * gz.height);
294			PSstroke();
295		PSgrestore();
296	}
297
298	for (i = 0; i <_rows; i++)
299	{
300		PSgsave();
301			PSsetlinecap(1);
302			PSsetlinewidth(_columns * border * 2);
303			PSrectclip(_x * gz.width + gz.width/4, _y *gz.height + gz.height/4, _columns * gz.width - gz.width/2, _rows * gz.height - gz.height/2);
304
305			PSsetalpha(alpha/4);
306			PSmoveto(_x * gz.width, (_y + i*4) * gz.height + (i + _rows) * 10);
307			PSlineto((_x + _columns) * gz.width, (_y + i*4 + _columns) * gz.height + (i + _rows) * 10);
308			PSstroke();
309		PSgrestore();
310	}
311
312	PSsetrgbcolor(0.3,0.3,0.3);
313
314	PSsetalpha(alpha/2);
315	PSsetlinewidth(0);
316	PSmoveto(_x * gz.width, _y * gz.height);
317	PSlineto(_x * gz.width + gz.width/4, _y * gz.height + gz.width/4);
318	PSlineto((_x+_columns) * gz.width - gz.width/4, _y * gz.height + gz.width/4);
319	PSlineto((_x+_columns) * gz.width, _y * gz.height);
320	PSfill();
321
322	PSsetalpha(alpha/3);
323	PSsetlinewidth(0);
324	PSmoveto((_x+_columns) * gz.width, _y * gz.height);
325	PSlineto((_x+_columns) * gz.width - gz.width/4, _y * gz.height + gz.width/4);
326	PSlineto((_x+_columns) * gz.width - gz.width/4, (_y+_rows) * gz.height - gz.width/4);
327	PSlineto((_x+_columns) * gz.width, (_y+_rows) * gz.height);
328	PSfill();
329
330
331	/****/
332
333	PSsetrgbcolor(0,0,0);
334	PSsetalpha(0.8);
335	PSrectfill(_x * gz.width + gz.width/8, _y *gz.height + gz.height/8, gz.width - gz.width/4, gz.height - gz.height/4);
336
337	switch (_color)
338	{
339		case LP_COLOR_YELLOW:
340			tcolor = [NSColor yellowColor];
341			break;
342		case LP_COLOR_GREEN:
343			tcolor = [NSColor greenColor];
344			break;
345		case LP_COLOR_RED:
346			tcolor = [NSColor redColor];
347			break;
348		case LP_COLOR_BLUE:
349			tcolor = [NSColor blueColor];
350			break;
351		default:
352			tcolor = [NSColor grayColor];
353	}
354
355	str = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%d",_count]];
356	[str addAttribute:NSForegroundColorAttributeName
357				value:tcolor
358				range:NSMakeRange(0,1)];
359	[str addAttribute:NSFontAttributeName
360				value:[NSFont boldSystemFontOfSize:gz.height/1.5]
361				range:NSMakeRange(0,1)];
362	strSize = [str size];
363
364	PSsetalpha(alpha);
365	[str drawAtPoint:NSMakePoint(_x * gz.width + gz.width/2 - strSize.width/2, _y * gz.height + gz.height/2 - strSize.height/2)];
366
367}
368
369- (void) blow
370{
371	_isBlowing = YES;
372}
373@end
374
375@implementation LPSparkerUnit
376- (void) spark
377{
378	int unit_x, unit_y, unit_rows, unit_columns;
379	int i;
380	id m;
381
382	unit_x = [self X];
383	unit_y = [self Y];
384	unit_rows = [self rows];
385	unit_columns = [self columns];
386
387	for (i = 0; i < unit_columns; i++)
388	{
389		m = [__owner getUnitAtX:unit_x + i
390							  Y:unit_y - 1];
391
392		if (m && MATCH_COLOR([m unitColor],_color) && ![m isMemberOfClass:[LPStoneUnit class]])
393		{
394			[self blow];
395		}
396
397		m = [__owner getUnitAtX:unit_x + i
398							  Y:unit_y + unit_rows];
399		if (m && MATCH_COLOR([m unitColor],_color) && ![m isMemberOfClass:[LPStoneUnit class]])
400		{
401			[self blow];
402		}
403
404	}
405
406	for (i = 0; i < unit_rows; i++)
407	{
408		m = [__owner getUnitAtX:unit_x - 1
409							  Y:unit_y + i];
410		if (m && MATCH_COLOR([m unitColor], _color) && ![m isMemberOfClass:[LPStoneUnit class]])
411		{
412			[self blow];
413		}
414
415		m = [__owner getUnitAtX:unit_x + unit_columns
416							  Y:unit_y + i];
417		if (m && MATCH_COLOR([m unitColor], _color) && ![m isMemberOfClass:[LPStoneUnit class]])
418		{
419			[self blow];
420		}
421
422	}
423}
424
425- (void) draw
426{
427	NSSize gz = [__owner gridSize];
428	float border = gz.width/7;
429
430	switch (_color)
431	{
432		case LP_COLOR_BLUE:
433			PSsetrgbcolor(0,0,0.5);
434			break;
435		case LP_COLOR_RED:
436			PSsetrgbcolor(0.5,0,0);
437			break;
438		case LP_COLOR_GREEN:
439			PSsetrgbcolor(0,0.5,0);
440			break;
441		case LP_COLOR_YELLOW:
442			PSsetrgbcolor(0.5,0.5,0);
443			break;
444		default:
445			PSsetrgbcolor(0.5,0.5,0.5);
446			break;
447	}
448	PSmoveto(_x * gz.width + gz.width/2, _y * gz.height);
449	PSlineto(_x * gz.width + border, _y * gz.height + border);
450	PSlineto(_x * gz.width, _y * gz.height + gz.height/2);
451	PSlineto(_x * gz.width + border, (_y+1) * gz.height - border);
452	PSlineto(_x * gz.width + gz.width/2, (_y+1) * gz.height);
453	PSlineto((_x+1) * gz.width - border, (_y+1) * gz.height - border);
454	PSlineto((_x+1) * gz.width, _y * gz.height + gz.height/2);
455	PSlineto((_x+1) * gz.width - border, _y * gz.height + border);
456
457	PSclosepath();
458	PSsetalpha(alpha);
459	PSfill();
460
461	switch (_color)
462	{
463		case LP_COLOR_BLUE:
464			PSsetrgbcolor(0,0,1.0-z);
465			break;
466		case LP_COLOR_RED:
467			PSsetrgbcolor(1.0-z,0,0);
468			break;
469		case LP_COLOR_GREEN:
470			PSsetrgbcolor(0,1.0-z,0);
471			break;
472		case LP_COLOR_YELLOW:
473			PSsetrgbcolor(1.0-z,1.0-z,0);
474			break;
475		default:
476			PSsetrgbcolor(1.0-z,1.0-z,1.0-z);
477			break;
478	}
479
480	PSmoveto(_x * gz.width + gz.width/2, _y * gz.height);
481	PSrlineto(gz.width/2, gz.height/2);
482	PSrlineto(-gz.width/2, gz.height/2);
483	PSrlineto(-gz.width/2, -gz.height/2);
484	PSclosepath();
485	PSsetalpha(alpha);
486	PSfill();
487
488	switch (_color)
489	{
490		case LP_COLOR_BLUE:
491			PSsetrgbcolor(0,0,0.5+z);
492			break;
493		case LP_COLOR_RED:
494			PSsetrgbcolor(0.5+z,0,0);
495			break;
496		case LP_COLOR_GREEN:
497			PSsetrgbcolor(0,0.5+z,0);
498			break;
499		case LP_COLOR_YELLOW:
500			PSsetrgbcolor(0.5+z,0.5+z,0);
501			break;
502		default:
503			PSsetrgbcolor(0.5+z,0.5+z,0.5+z);
504			break;
505	}
506
507	PSsetalpha(alpha/1.5);
508	PSrectfill(_x * gz.width + border , _y *gz.height + border, _columns * gz.width - border*2, _rows * gz.height - border*2);
509
510
511
512	switch (_color)
513	{
514		case LP_COLOR_BLUE:
515			PSsetrgbcolor(0,0,1.0-z);
516			break;
517		case LP_COLOR_RED:
518			PSsetrgbcolor(1.0-z,0,0);
519			break;
520		case LP_COLOR_GREEN:
521			PSsetrgbcolor(0,1.0-z,0);
522			break;
523		case LP_COLOR_YELLOW:
524			PSsetrgbcolor(1.0-z,1.0-z,0);
525			break;
526		default:
527			PSsetrgbcolor(1.0-z,1.0-z,1.0-z);
528			break;
529	}
530
531
532	PSsetalpha(alpha/2);
533	PSmoveto(_x * gz.width + gz.width/2, _y * gz.height);
534	PSlineto(_x * gz.width + border, _y * gz.height + border);
535	PSlineto(_x * gz.width, _y * gz.height + gz.height/2);
536	PSlineto(_x * gz.width + border, (_y+1) * gz.height - border);
537	PSlineto(_x * gz.width + gz.width/2, (_y+1) * gz.height);
538	PSlineto((_x+1) * gz.width - border, (_y+1) * gz.height - border);
539	PSlineto((_x+1) * gz.width, _y * gz.height + gz.height/2);
540	PSlineto((_x+1) * gz.width - border, _y * gz.height + border);
541
542	PSlineto(_x * gz.width + gz.width/2, _y * gz.height);
543	PSlineto((_x+1) * gz.width, _y * gz.height + gz.height/2);
544	PSlineto(_x * gz.width + gz.width/2, (_y+1) * gz.height);
545	PSlineto(_x * gz.width, _y * gz.height + gz.height/2);
546
547	PSclosepath();
548	PSfill();
549}
550
551@end
552
553@implementation LPJewelUnit
554- (id) initWithOwner:(id <LPUnitOwner>)owner
555			   color:(LPUnitColorType)color
556				   X:(int)x
557				   Y:(int)y
558{
559	[super initWithOwner:owner
560				   color:color];
561
562	_x = x;
563	_y = y;
564	_rows = 1;
565	_columns = 1;
566	z = random()%5;
567	z/= 10;
568	[self changePhase];
569	return self;
570}
571
572- (float) phase
573{
574	return z;
575}
576
577- (void) changePhase
578{
579	z += 0.1;
580	if (z > 0.5)
581	{
582		z = 0;
583	}
584}
585
586- (int) rows
587{
588	return _rows;
589}
590
591- (int) columns
592{
593	return _columns;
594}
595
596- (int) X
597{
598	return _x;
599}
600
601- (int) Y
602{
603	return _y;
604}
605
606- (void) draw
607{
608	NSSize gz = [__owner gridSize];
609	float border = gz.width/6;
610
611	switch (_color)
612	{
613		case LP_COLOR_BLUE:
614			PSsetrgbcolor(0,0,0.7);
615			break;
616		case LP_COLOR_RED:
617			PSsetrgbcolor(0.7,0,0);
618			break;
619		case LP_COLOR_GREEN:
620			PSsetrgbcolor(0,0.7,0);
621			break;
622		case LP_COLOR_YELLOW:
623			PSsetrgbcolor(0.7,0.7,0);
624			break;
625		default:
626			PSsetrgbcolor(0.7,0.7,0.7);
627			break;
628	}
629	PSsetalpha(alpha);
630	PSrectfill(_x * gz.width , _y *gz.height, _columns * gz.width, _rows * gz.height);
631	switch (_color)
632	{
633		case LP_COLOR_BLUE:
634			PSsetrgbcolor(0,0,0.6);
635			break;
636		case LP_COLOR_RED:
637			PSsetrgbcolor(0.6,0,0);
638			break;
639		case LP_COLOR_GREEN:
640			PSsetrgbcolor(0,0.6,0);
641			break;
642		case LP_COLOR_YELLOW:
643			PSsetrgbcolor(0.6,0.6,0);
644			break;
645		default:
646			PSsetrgbcolor(0.6,0.6,0.6);
647			break;
648	}
649	PSsetalpha(alpha);
650	//PSrectfill(_x * gz.width + border, _y *gz.height + border, _columns * gz.width - 2*border, _rows * gz.height - 2*border);
651
652	switch (_color)
653	{
654		case LP_COLOR_BLUE:
655			PSsetrgbcolor(0,0.5,1);
656			break;
657		case LP_COLOR_RED:
658			PSsetrgbcolor(1,0.2,0.2);
659			break;
660		case LP_COLOR_GREEN:
661			PSsetrgbcolor(0.5,1,0);
662			break;
663		case LP_COLOR_YELLOW:
664			PSsetrgbcolor(1,1,0.0);
665			break;
666		default:
667			PSsetrgbcolor(1,1,1);
668			break;
669	}
670
671	PSsetalpha(alpha/3);
672	PSsetlinewidth(0);
673	PSmoveto(_x * gz.width, _y * gz.height);
674	PSlineto(_x * gz.width + gz.width/4, _y * gz.height + gz.width/4);
675	PSlineto(_x * gz.width + gz.width/4, (_y+_rows) * gz.height - gz.width/4);
676	PSlineto(_x * gz.width, (_y+_rows) * gz.height);
677	PSfill();
678
679	PSsetalpha(alpha);
680	PSsetlinewidth(0);
681	PSmoveto(_x * gz.width, (_y+_rows) * gz.height);
682	PSlineto(_x * gz.width + gz.width/4, (_y+_rows) * gz.height - gz.width/4);
683	PSlineto((_x+_columns) * gz.width - gz.width/4, (_y+_rows) * gz.height - gz.width/4);
684	PSlineto((_x+_columns) * gz.width, (_y+_rows) * gz.height);
685	PSfill();
686
687	if (_rows > 1)
688	{
689		int i;
690		for (i = 0; i <_rows; i+=2)
691		{
692			PSgsave();
693			PSsetlinecap(1);
694			PSsetalpha(alpha/10);
695			PSsetlinewidth(_columns * border * 4);
696			PSrectclip(_x * gz.width + gz.width/4, _y *gz.height + gz.height/4, _columns * gz.width - gz.width/2, _rows * gz.height - gz.height/2);
697
698			PSmoveto(_x * gz.width, (_y + i) * gz.height);
699			PSlineto((_x + _columns) * gz.width, (_y + i + _columns) * gz.height);
700			PSstroke();
701			PSgrestore();
702		}
703
704		for (i = 0; i <_rows; i++)
705		{
706			PSgsave();
707			PSsetlinecap(1);
708			PSsetlinewidth(_columns * border * 2);
709			PSrectclip(_x * gz.width + gz.width/4, _y *gz.height + gz.height/4, _columns * gz.width - gz.width/2, _rows * gz.height - gz.height/2);
710
711			PSsetalpha(alpha/4);
712			PSmoveto(_x * gz.width, (_y + i*4) * gz.height + (i + _rows) * 10);
713			PSlineto((_x + _columns) * gz.width, (_y + i*4 + _columns) * gz.height + (i + _rows) * 10);
714			PSstroke();
715			PSgrestore();
716		}
717	}
718
719	switch (_color)
720	{
721		case LP_COLOR_BLUE:
722			PSsetrgbcolor(0,0,0.3);
723			break;
724		case LP_COLOR_RED:
725			PSsetrgbcolor(0.3,0,0);
726			break;
727		case LP_COLOR_GREEN:
728			PSsetrgbcolor(0,0.3,0);
729			break;
730		case LP_COLOR_YELLOW:
731			PSsetrgbcolor(0.3,0.3,0);
732			break;
733		default:
734			PSsetrgbcolor(0.3,0.3,0.3);
735			break;
736	}
737
738
739	PSsetalpha(alpha/2);
740	PSsetlinewidth(0);
741	PSmoveto(_x * gz.width, _y * gz.height);
742	PSlineto(_x * gz.width + gz.width/4, _y * gz.height + gz.width/4);
743	PSlineto((_x+_columns) * gz.width - gz.width/4, _y * gz.height + gz.width/4);
744	PSlineto((_x+_columns) * gz.width, _y * gz.height);
745	PSfill();
746
747	PSsetalpha(alpha/3);
748	PSsetlinewidth(0);
749	PSmoveto((_x+_columns) * gz.width, _y * gz.height);
750	PSlineto((_x+_columns) * gz.width - gz.width/4, _y * gz.height + gz.width/4);
751	PSlineto((_x+_columns) * gz.width - gz.width/4, (_y+_rows) * gz.height - gz.width/4);
752	PSlineto((_x+_columns) * gz.width, (_y+_rows) * gz.height);
753	PSfill();
754
755}
756
757- (BOOL) canRMoveX:(int)rx
758				 Y:(int)ry
759{
760	id en;
761	LPUnit* unit;
762
763	int cx,cy,i,j;
764
765	cx = _x + rx;
766	cy = _y + ry;
767
768	if (cx < 0 || cx > 5 || cy < 0)
769	{
770		return NO;
771	}
772
773	en = [[__owner allUnits] objectEnumerator];
774	while ((unit = [en nextObject]))
775	{
776		if (unit == self)
777		{
778			continue;
779		}
780		for (j = 0; j < _rows; j++)
781		{
782			for (i = 0; i < _columns; i++)
783			{
784				if ([unit hasPartAtX:cx+i
785								   Y:cy+j])
786				{
787					return NO;
788				}
789			}
790		}
791	}
792
793	return YES;
794}
795
796- (BOOL) canMoveInDir:(LPDirType)dir
797{
798  int cx,cy;
799
800  switch(dir)
801    {
802    case LP_MOVE_LEFT:
803      if (_x == 0)
804	{
805	  return NO;
806	}
807      cx = -1;
808      cy = 0;
809      break;
810    case LP_MOVE_DOWN:
811      if (_y == 0)
812	{
813	  return NO;
814	}
815      cx = 0;
816      cy = -1;
817      break;
818    case LP_MOVE_RIGHT:
819      if (_x == 5)
820	{
821	  return NO;
822	}
823      cx = 1;
824      cy = 0;
825      break;
826    case LP_MOVE_UP:
827      /* no check for upper border */
828      cx = 0;
829      cy = 1;
830      break;
831    default:
832      NSAssert(0, @"Unreachable");
833      return NO;
834      break;
835    }
836
837  return [self canRMoveX:cx Y:cy];
838}
839
840- (BOOL) rMoveX:(int)rx
841			  Y:(int)ry
842{
843	if (![self canRMoveX:rx
844					   Y:ry])
845	{
846		return NO;
847	}
848	_x += rx;
849	_y += ry;
850
851	return YES;
852}
853
854- (BOOL) moveInDir:(LPDirType)dir
855{
856  if (![self canMoveInDir:dir])
857    {
858      return NO;
859    }
860  switch(dir)
861    {
862    case LP_MOVE_DOWN:
863      _y--;
864      break;
865    case LP_MOVE_LEFT:
866      _x--;
867      break;
868    case LP_MOVE_UP:
869      _y++;
870      break;
871    case LP_MOVE_RIGHT:
872      _x++;
873      break;
874    default:
875      NSAssert(0, @"Unreachable");
876      break;
877
878    }
879  return YES;
880}
881
882- (BOOL) hasPartAtX:(int)x
883				  Y:(int)y
884{
885	if (x >= _x && y >= _y && x < (_x + _columns) && y < (_y + _rows))
886	{
887		return YES;
888	}
889	return NO;
890}
891
892- (void) addRows:(int)r
893{
894	_rows += r;
895}
896
897- (void) addColumns:(int)c
898{
899	_columns += c;
900}
901@end
902
903@implementation LPGroupUnit
904
905- (id) initWithOwner:(id <LPUnitOwner>)owner
906			   atoms:(NSArray *)unitList
907{
908	__owner = owner;
909	ASSIGN(_units, unitList);
910	_laydir = LP_MOVE_DOWN;
911	return self;
912}
913
914- (NSSize) gridSize
915{
916	return [__owner gridSize];
917}
918
919- (NSArray *) allUnits
920{
921	NSMutableArray *array = [NSMutableArray arrayWithArray:[__owner allUnits]];
922	[array removeObject:self];
923	return array;
924}
925
926- (NSArray *) atoms
927{
928	return _units;
929}
930
931- (id) getUnitAtX:(int)x
932				Y:(int)y
933{
934	exit(0);
935	// NYI
936}
937
938- (void) rotateCCW
939{
940  id move = [_units objectAtIndex:0];
941  id base = [_units objectAtIndex:1];
942
943  switch(_laydir)
944    {
945    case LP_MOVE_DOWN:
946      if ([move rMoveX:-1
947		     Y:-1])
948	{
949	  _laydir = LP_MOVE_LEFT;
950	}
951      else if ([base canRMoveX:1 Y:0])
952	{
953	  [base rMoveX:1 Y:0];
954	  [move rMoveX:0 Y:-1];
955	  _laydir = LP_MOVE_LEFT;
956	}
957      break;
958    case LP_MOVE_LEFT:
959      if ([move rMoveX:1
960		     Y:-1])
961	{
962	  _laydir = LP_MOVE_UP;
963	}
964      break;
965    case LP_MOVE_UP:
966      if ([move rMoveX:1
967		     Y:1])
968	{
969	  _laydir = LP_MOVE_RIGHT;
970	}
971      else if ([base canRMoveX:-1 Y:0])
972	{
973	  [base rMoveX:-1 Y:0];
974	  [move rMoveX:0 Y:1];
975	  _laydir = LP_MOVE_RIGHT;
976	}
977      break;
978    case LP_MOVE_RIGHT:
979      if ([move rMoveX:-1
980		     Y:1])
981	{
982	  _laydir = LP_MOVE_DOWN;
983	}
984      break;
985    default:
986      NSAssert(0, @"Unreachable");
987      break;
988    }
989
990}
991
992- (void) rotateCW
993{
994  id move = [_units objectAtIndex:0];
995  id base = [_units objectAtIndex:1];
996
997  switch(_laydir)
998    {
999    case LP_MOVE_DOWN:
1000      if ([move rMoveX:1 // should physically block rotation?
1001		     Y:-1])
1002	{
1003	  _laydir = LP_MOVE_RIGHT;
1004	}
1005      else if ([base canRMoveX:-1 Y:0])
1006	{
1007	  [base rMoveX:-1 Y:0];
1008	  [move rMoveX:0 Y:-1];
1009	  _laydir = LP_MOVE_RIGHT;
1010	}
1011      break;
1012    case LP_MOVE_RIGHT:
1013      if ([move rMoveX:-1
1014		     Y:-1])
1015	{
1016	  _laydir = LP_MOVE_UP;
1017	}
1018      break;
1019    case LP_MOVE_UP:
1020      if ([move rMoveX:-1
1021		     Y:1])
1022	{
1023	  _laydir = LP_MOVE_LEFT;
1024	}
1025      else if ([base canRMoveX:1 Y:0])
1026	{
1027	  [base rMoveX:1 Y:0];
1028	  [move rMoveX:0 Y:1];
1029	  _laydir = LP_MOVE_LEFT;
1030	}
1031      break;
1032    case LP_MOVE_LEFT:
1033      if ([move rMoveX:1
1034		     Y:1])
1035	{
1036	  _laydir = LP_MOVE_DOWN;
1037	}
1038      break;
1039    default:
1040      NSAssert(0, @"Unreachable");
1041      break;
1042    }
1043
1044}
1045
1046- (void) changePhase
1047{
1048	id en;
1049	LPUnit* unit;
1050
1051	en = [_units objectEnumerator];
1052	while ((unit = [en nextObject]))
1053	{
1054		[unit changePhase];
1055	}
1056}
1057
1058- (void) draw
1059{
1060	id en;
1061	LPUnit* unit;
1062
1063	en = [_units objectEnumerator];
1064	while ((unit = [en nextObject]))
1065	{
1066		[unit draw];
1067	}
1068}
1069
1070- (int) X
1071{
1072	id en;
1073	LPUnit* unit;
1074	float mX;
1075	mX = 5;
1076
1077	en = [_units objectEnumerator];
1078	while ((unit = [en nextObject]))
1079	{
1080		if ([unit X] < mX)
1081		{
1082			mX = [unit X];
1083		}
1084	}
1085	return mX;
1086}
1087
1088- (int) Y
1089{
1090  return 0;
1091}
1092
1093- (void) dealloc
1094{
1095	RELEASE(_units);
1096	[super dealloc];
1097}
1098
1099- (BOOL) hasPartAtX:(int)x
1100				  Y:(int)y
1101{
1102	id en;
1103	LPUnit* unit;
1104
1105	en = [_units objectEnumerator];
1106	while ((unit = [en nextObject]))
1107	{
1108		if ([unit hasPartAtX:x
1109						   Y:y])
1110		{
1111			return YES;
1112		}
1113	}
1114	return NO;
1115}
1116
1117- (BOOL) moveInDir:(LPDirType)dir
1118{
1119	id en;
1120	LPUnit* unit;
1121
1122	en = [_units objectEnumerator];
1123	while ((unit = [en nextObject]))
1124	{
1125		if([unit canMoveInDir:dir] == NO)
1126		{
1127			return NO;
1128		}
1129	}
1130
1131	en = [_units objectEnumerator];
1132	while ((unit = [en nextObject]))
1133	{
1134		[unit moveInDir:dir];
1135	}
1136	return YES;
1137}
1138
1139- (BOOL) canMoveInDir:(LPDirType)dir
1140{
1141	id en;
1142	LPUnit* unit;
1143
1144	en = [_units objectEnumerator];
1145	while ((unit = [en nextObject]))
1146	{
1147		if([unit canMoveInDir:dir] == NO)
1148		{
1149			return NO;
1150		}
1151	}
1152	return YES;
1153}
1154
1155- (BOOL) canRMoveX:(int)rx
1156				 Y:(int)ry
1157{
1158	id en;
1159	LPUnit* unit;
1160
1161	en = [_units objectEnumerator];
1162	while ((unit = [en nextObject]))
1163	{
1164		if([unit canRMoveX:rx Y:ry] == NO)
1165		{
1166			return NO;
1167		}
1168	}
1169	return YES;
1170}
1171
1172- (BOOL) rMoveX:(int)rx
1173			  Y:(int)ry
1174{
1175	id en;
1176	LPUnit* unit;
1177
1178	en = [_units objectEnumerator];
1179	while ((unit = [en nextObject]))
1180	{
1181		if([unit canRMoveX:rx Y:ry] == NO)
1182		{
1183			return NO;
1184		}
1185	}
1186
1187	en = [_units objectEnumerator];
1188	while ((unit = [en nextObject]))
1189	{
1190		[unit rMoveX:rx Y:ry];
1191	}
1192	return YES;
1193}
1194
1195@end
1196
1197@implementation LapisPuzzleView
1198
1199static LPUnitColorType _random_unit_color()
1200{
1201	return random()%LP_COLOR_ALL;
1202}
1203
1204static LPUnit * _random_unit(id owner, int x, int y, BOOL diamond)
1205{
1206	LPUnit* unit;
1207	if (random()%20 < 6)
1208	{
1209		if (random()%10 == 1 && diamond)
1210		{
1211			unit = [[LPSparkerUnit alloc] initWithOwner:owner
1212												  color:LP_COLOR_ALL
1213													  X:x
1214													  Y:y];
1215		}
1216		else
1217		{
1218
1219			unit = [[LPSparkerUnit alloc] initWithOwner:owner
1220												  color:_random_unit_color()
1221													  X:x
1222													  Y:y];
1223		}
1224	}
1225	else
1226	{
1227		unit = [[LPJewelUnit alloc] initWithOwner:owner
1228											color:_random_unit_color()
1229												X:x
1230												Y:y];
1231	}
1232	return AUTORELEASE(unit);
1233}
1234
1235
1236- (NSSize) gridSize
1237{
1238	return NSMakeSize(
1239		NSWidth(_bounds)/(_numberOfColumns * _stepsInUnit),
1240		NSHeight(_bounds)/(((float)_numberOfRows + 0.3) * _stepsInUnit)
1241		);
1242}
1243
1244- (void) awakeFromNib
1245{
1246	chain = 0;
1247	trip = 0;
1248	chaintrip = 0;
1249	_gameOver = NO;
1250	_numberOfRows = 13;
1251	_numberOfColumns = 6;
1252
1253	_stepsInUnit = 1;
1254	_stepHeight = NSHeight(_frame)/((float)_numberOfRows + 0.3);
1255	_stepWidth = NSWidth(_frame)/_numberOfColumns;
1256
1257	_units = [[NSMutableArray alloc] init];
1258
1259	_blowing = [[NSMutableSet alloc] init];
1260
1261}
1262
1263- (void) setFrame:(NSRect)r
1264{
1265	[super setFrame:r];
1266}
1267
1268- (void) setBackgroundImage:(NSImage *)image
1269{
1270	ASSIGN(_background, image);
1271}
1272
1273- (void) gameOver
1274{
1275	_gameOver = YES;
1276}
1277
1278- (void) round
1279{
1280	id en;
1281	LPUnit* unit;
1282
1283	if (chain)
1284	{
1285		[self fallEmDown];
1286		[self packCell];
1287		[self blowIt];
1288		if (chain == 0)
1289		{
1290			[self runStone];
1291		}
1292		[self setNeedsDisplay:YES];
1293		return;
1294	}
1295
1296	_lockControl = NO;
1297
1298	if (__currentUnit == nil)
1299	{
1300		if (!_gameOver)
1301		{
1302			[__owner lapisPuzzleView:self
1303			 didFinishUnitWithResult:LP_RESULT_REQUEST];
1304		}
1305	}
1306
1307	if(![__currentUnit moveInDir:LP_MOVE_DOWN])
1308	{
1309		if (__currentUnit)
1310		{
1311			/* replace timer stone with jewel */
1312			NSMutableArray *ar;
1313
1314			_lockControl = YES;
1315			ar = [NSMutableArray array];
1316			en = [_units objectEnumerator];
1317			while ((unit = [en nextObject]))
1318			{
1319				if ([unit isMemberOfClass:[LPStoneUnit class]])
1320				{
1321					[(LPStoneUnit *)unit countDown];
1322					if ([(LPStoneUnit *)unit count] == 0)
1323					{
1324						[ar addObject:unit];
1325					}
1326				}
1327			}
1328
1329			en = [ar objectEnumerator];
1330			while ((unit = [en nextObject]))
1331			{
1332				id new;
1333				new = [[LPJewelUnit alloc] initWithOwner:self
1334												   color:[unit unitColor]
1335													   X:[unit X]
1336													   Y:[unit Y]];
1337				[_units removeObject:unit];
1338				[_units addObject:new];
1339				[new release];
1340			}
1341
1342
1343			en = [[__currentUnit atoms] objectEnumerator];
1344			while ((unit = [en nextObject]))
1345			{
1346				[_units addObject:unit];
1347				[unit setOwner:self];
1348				[unit fallToBottom];
1349			}
1350
1351
1352			[_units removeObject:__currentUnit];
1353			__currentUnit = nil;
1354		}
1355
1356		[self packCell];
1357		[self blowIt];
1358		if (chain == 0)
1359		{
1360			[self runStone];
1361		}
1362
1363
1364	}
1365
1366	[self setNeedsDisplay:YES];
1367}
1368
1369- (void) runStone
1370{
1371	LPUnit* unit;
1372	/* run stone */
1373	int yy,xx;
1374
1375	if (stone > 0)
1376	{
1377		[(LPController *)__owner player:self processStone:stone];
1378	}
1379
1380	yy = 13, xx = 0;
1381	while (stone > 0)
1382	{
1383		unit = nil;
1384		while (unit == nil)
1385		{
1386			while (unit == nil)
1387			{
1388				if ([self getUnitAtX:xx Y:yy] == nil)
1389				{
1390					unit = [[LPStoneUnit alloc] initWithOwner:self
1391								    color:_random_unit_color()
1392								    X:xx
1393								    Y:yy];
1394					[_units addObject:unit];
1395					[unit fallToBottom];
1396					RELEASE(unit);
1397				}
1398				xx++;
1399				if (xx > 5)
1400				{
1401					xx = 0;
1402					break;
1403				}
1404			}
1405			yy++;
1406		}
1407		stone--;
1408	}
1409}
1410
1411- (id) getUnitAtX:(int)x
1412				Y:(int)y
1413{
1414	id en;
1415	LPUnit* unit;
1416
1417	en = [_units objectEnumerator];
1418	while ((unit = [en nextObject]))
1419	{
1420		if ([unit hasPartAtX:x
1421						   Y:y])
1422		{
1423			return unit;
1424		}
1425	}
1426
1427	return nil;
1428}
1429
1430- (void) fallEmDown
1431{
1432	id en;
1433	LPUnit* unit;
1434	BOOL moving;
1435
1436	/* fall em down */
1437	do
1438	{
1439		moving = NO;
1440		en = [_units objectEnumerator];
1441		while ((unit = [en nextObject]))
1442		{
1443			if ([unit moveInDir:LP_MOVE_DOWN])
1444			{
1445				moving = YES;
1446			}
1447		}
1448	} while (moving);
1449
1450}
1451
1452- (void) packCell
1453{
1454	id en;
1455	LPJewelUnit* unit;
1456	int i,j;
1457	BOOL merge;
1458
1459
1460	id m1,m2,m3;
1461	LPUnitColorType color;
1462
1463	if (_gameOver)
1464	{
1465		return;
1466	}
1467
1468	/* pack bigger cell */
1469	for (i = 0; i < 12; i++)
1470	{
1471		for (j = 0; j < 5; j++)
1472		{
1473			unit = [self getUnitAtX:j
1474								  Y:i];
1475			if (unit && [unit rows] == 1 && [unit isMemberOfClass:[LPJewelUnit class]])
1476			{
1477				color = [unit unitColor];
1478				if ((m1 = [self getUnitAtX:j+1 Y:i]) &&
1479					[m1 unitColor] == color &&
1480					[m1 rows] == 1 &&
1481					[m1 isMemberOfClass:[LPJewelUnit class]] &&
1482					(m2 = [self getUnitAtX:j Y:i+1]) &&
1483					[m2 unitColor] == color &&
1484					[m2 rows] == 1 &&
1485					[m2 isMemberOfClass:[LPJewelUnit class]] &&
1486					(m3 = [self getUnitAtX:j+1 Y:i+1]) &&
1487					[m3 unitColor] == color &&
1488					[m3 rows] == 1 &&
1489					[m3 isMemberOfClass:[LPJewelUnit class]]
1490					)
1491				{
1492					[unit addRows:1];
1493					[unit addColumns:1];
1494					[_units removeObject:m1];
1495					[_units removeObject:m2];
1496					[_units removeObject:m3];
1497				}
1498			}
1499		}
1500	}
1501
1502	do
1503	{
1504		merge = NO;
1505		en = [_units objectEnumerator];
1506		while ((unit = [en nextObject]))
1507		{
1508			int unit_x, unit_y, unit_rows, unit_columns;
1509			LPUnitColorType color;
1510
1511			unit_rows = [unit rows];
1512
1513			if (unit_rows >= 2)
1514			{
1515				NSMutableArray *ar = [NSMutableArray array];
1516
1517				color = [unit unitColor];
1518				unit_x = [unit X];
1519				unit_y = [unit Y];
1520				unit_columns = [unit columns];
1521
1522
1523				m1 = [self getUnitAtX:unit_x + unit_columns
1524									Y:unit_y];
1525
1526				/* check horizontal axis */
1527				if (m1 && [m1 isMemberOfClass:[LPJewelUnit class]] && [m1 Y] == unit_y && [m1 rows] == unit_rows && [m1 unitColor] == color)
1528				{
1529					merge = YES;
1530					[unit addColumns:[m1 columns]];
1531					[_units removeObject:m1];
1532					break;
1533				}
1534
1535				m1 = [self getUnitAtX:unit_x
1536									Y:unit_y + unit_rows];
1537
1538				/* check vertical axis */
1539				if (m1 && [m1 isMemberOfClass:[LPJewelUnit class]] && [m1 X] == unit_x && [m1 columns] == unit_columns && [m1 unitColor] == color)
1540				{
1541					merge = YES;
1542					[unit addRows:[m1 rows]];
1543					[_units removeObject:m1];
1544					break;
1545				}
1546
1547
1548				/* check right side */
1549				for (i = 0; i < unit_rows; i++)
1550				{
1551					m1 = [self getUnitAtX:unit_x + unit_columns
1552										Y:unit_y + i];
1553					if (m1 == nil || ![m1 isMemberOfClass:[LPJewelUnit class]] || [m1 rows] > 1 || [m1 unitColor]!=color)
1554					{
1555						break;
1556					}
1557					[ar addObject:m1];
1558				}
1559				if ([ar count] == unit_rows)
1560				{
1561					id en2, unit2;
1562
1563					merge = YES;
1564					en2 = [ar objectEnumerator];
1565					while ((unit2 = [en2 nextObject]))
1566					{
1567						[_units removeObject:unit2];
1568					}
1569					[unit addColumns:1];
1570					break;
1571				}
1572
1573				/* check left side */
1574				ar = [NSMutableArray array];
1575				for (i = 0; i < unit_rows; i++)
1576				{
1577					m1 = [self getUnitAtX:unit_x - 1
1578										Y:unit_y + i];
1579					if (m1 == nil || ![m1 isMemberOfClass:[LPJewelUnit class]] || [m1 rows] > 1 || [m1 unitColor]!=color)
1580					{
1581						break;
1582					}
1583					[ar addObject:m1];
1584				}
1585				if ([ar count] == unit_rows)
1586				{
1587					id en2, unit2;
1588
1589					merge = YES;
1590					en2 = [ar objectEnumerator];
1591					while ((unit2 = [en2 nextObject]))
1592					{
1593						[_units removeObject:unit2];
1594					}
1595					[unit addColumns:1];
1596					[unit moveInDir:LP_MOVE_LEFT];
1597					break;
1598				}
1599
1600				/* check top side */
1601				ar = [NSMutableArray array];
1602				for (i = 0; i < unit_columns; i++)
1603				{
1604					m1 = [self getUnitAtX:unit_x + i
1605										Y:unit_y + unit_rows];
1606					if (m1 == nil || ![m1 isMemberOfClass:[LPJewelUnit class]] || [m1 rows] > 1 || [m1 unitColor]!=color)
1607					{
1608						break;
1609					}
1610					[ar addObject:m1];
1611				}
1612				if ([ar count] == unit_columns)
1613				{
1614					id en2, unit2;
1615
1616					merge = YES;
1617					en2 = [ar objectEnumerator];
1618					while ((unit2 = [en2 nextObject]))
1619					{
1620						[_units removeObject:unit2];
1621					}
1622					[unit addRows:1];
1623					break;
1624				}
1625
1626				/* check bottom side */
1627				ar = [NSMutableArray array];
1628				for (i = 0; i < unit_columns; i++)
1629				{
1630					m1 = [self getUnitAtX:unit_x + i
1631										Y:unit_y - 1];
1632					if (m1 == nil || ![m1 isMemberOfClass:[LPJewelUnit class]] || [m1 rows] > 1 || [m1 unitColor]!=color)
1633					{
1634						break;
1635					}
1636					[ar addObject:m1];
1637				}
1638				if ([ar count] == unit_columns)
1639				{
1640					id en2, unit2;
1641
1642					merge = YES;
1643					en2 = [ar objectEnumerator];
1644					while ((unit2 = [en2 nextObject]))
1645					{
1646						[_units removeObject:unit2];
1647					}
1648					[unit addRows:1];
1649					[unit moveInDir:LP_MOVE_DOWN];
1650					break;
1651				}
1652
1653			}
1654		}
1655	} while(merge);
1656}
1657
1658- (void) blowIt
1659{
1660	id en;
1661	LPUnit *unit;
1662	int i;
1663
1664	LPUnit *all = nil;
1665
1666	if (_gameOver)
1667	{
1668		return;
1669	}
1670
1671	en = [_units objectEnumerator];
1672	while ((unit = [en nextObject]))
1673	{
1674		if ([unit isMemberOfClass:[LPSparkerUnit class]])
1675		{
1676			if ([unit unitColor] == LP_COLOR_ALL)
1677			{
1678				[unit blow];
1679				all = [self getUnitAtX:[unit X]
1680									 Y:[unit Y] - 1];
1681			}
1682			else [(LPSparkerUnit *)unit spark];
1683		}
1684	}
1685
1686	if (all)
1687	{
1688		en = [_units objectEnumerator];
1689		while ((unit = [en nextObject]))
1690		{
1691			if ([unit unitColor] == [all unitColor])
1692			{
1693				[unit softBlow];
1694			}
1695		}
1696	}
1697
1698	i = 0;
1699	en = [_units objectEnumerator];
1700	while ((unit = [en nextObject]))
1701	{
1702		if ([unit isBlowing])
1703		{
1704			[_blowing addObject:unit];
1705			i++;
1706		}
1707	}
1708
1709	if (i)
1710	{
1711//		NSLog(@"blow blocks %d",i);
1712	}
1713	stone -= (i/4) * chain + chain>1?chain:0;
1714
1715	en = [_blowing objectEnumerator];
1716	while ((unit = [en nextObject]))
1717	{
1718		if ([_units containsObject:unit])
1719		{
1720			if ([unit rows] > 1)
1721			{
1722				stone -= [unit rows] * 2 * [unit columns] * (chain + 1);
1723			}
1724			[_units removeObject:unit];
1725		}
1726	}
1727
1728	if (i)
1729	{
1730		chain ++;
1731		if (chain >= 2)
1732		{
1733			stone -= chain;
1734		}
1735		chaintrip = chain * 2;
1736		if (chaintrip > 8)
1737		{
1738			chaintrip = 8;
1739		}
1740		if (chain > maxchain)
1741		{
1742			maxchain = chain;
1743		}
1744	}
1745	else
1746	{
1747		chain = 0;
1748
1749		if (stone < 0)
1750		{
1751			[__owner player:self addStoneToOp:-stone];
1752			stone = 0;
1753		}
1754	}
1755
1756#if 0
1757	if (stone < 0)
1758	{
1759//		NSLog(@"%@ sends %d stones",self,-stone);
1760		[__owner player:self addStoneToOp:-stone];
1761		stone = 0;
1762	}
1763#endif
1764}
1765
1766- (void) restart
1767{
1768	id en;
1769	LPUnit * unit;
1770	[self gameOver];
1771	if (__currentUnit)
1772	{
1773		[_units removeObject:__currentUnit];
1774		__currentUnit = nil;
1775	}
1776	en = [_units objectEnumerator];
1777	while ((unit = [en nextObject]))
1778	{
1779		[unit blow];
1780	}
1781
1782	en = [_units objectEnumerator];
1783	while ((unit = [en nextObject]))
1784	{
1785		if ([unit isBlowing])
1786		{
1787			[_blowing addObject:unit];
1788		}
1789	}
1790
1791	en = [_blowing objectEnumerator];
1792	while ((unit = [en nextObject]))
1793	{
1794		[_units removeObject:unit];
1795	}
1796
1797	_gameOver = NO;
1798	stone = 0;
1799	trip = 0;
1800	chaintrip = 0;
1801}
1802
1803- (void) refresh
1804{
1805	id en;
1806	LPUnit *unit;
1807
1808	NSMutableArray *ar;
1809
1810	ar = [NSMutableArray array];
1811
1812	if (trip)
1813	{
1814		trip--;
1815	}
1816	if (chaintrip)
1817	{
1818		chaintrip--;
1819		if (chaintrip == 0)
1820		{
1821			maxchain = 0;
1822		}
1823	}
1824
1825	en = [_units objectEnumerator];
1826	while ((unit = [en nextObject]))
1827	{
1828		if (!_gameOver)
1829		{
1830			[unit changePhase];
1831		}
1832	}
1833
1834	if (_gameOver)
1835	{
1836		int cc,xx,yy;
1837		LPUnit* unit;
1838		cc=0;
1839
1840		for (yy = 13; yy >= 0 && cc < 8; yy--)
1841		for (xx = 5; xx >= 0; xx--)
1842		{
1843			unit = [self getUnitAtX:xx
1844								  Y:yy];
1845
1846			if (unit && [unit unitColor] != LP_COLOR_ALL)
1847			{
1848				[unit setUnitColor:LP_COLOR_ALL];
1849				cc++;
1850			}
1851		}
1852	}
1853	else
1854	{
1855		en = [_blowing objectEnumerator];
1856		while ((unit = [en nextObject]))
1857		{
1858			[unit setAlpha:[unit alpha]-0.2];
1859			if ([unit alpha] < 0.2)
1860			{
1861				[ar addObject:unit];
1862			}
1863		}
1864		en = [ar objectEnumerator];
1865		while ((unit = [en nextObject]))
1866		{
1867			[_blowing removeObject:unit];
1868		}
1869	}
1870
1871	[self setNeedsDisplay:YES];
1872}
1873
1874- (void) addStone:(int)num
1875{
1876	stone += num;
1877	if (stone > 0)
1878	{
1879		trip = 8;
1880	}
1881}
1882
1883- (void) addUnit:(id)newUnit
1884{
1885	[_units addObject:newUnit];
1886
1887	__currentUnit = newUnit;
1888
1889	if (![__currentUnit canMoveInDir:LP_MOVE_DOWN])
1890	{
1891		[_units removeObject:__currentUnit];
1892		__currentUnit = nil;
1893		[self gameOver];
1894		[__owner lapisPuzzleView:self
1895		 didFinishUnitWithResult:LP_RESULT_GAMEOVER];
1896
1897	}
1898	[self setNeedsDisplay:YES];
1899}
1900
1901- (void) addJewelUnit
1902{
1903	/*
1904	id newUnit = [LPGroupUnit alloc];
1905	[newUnit initWithOwner:self
1906					 atoms:[NSArray arrayWithObjects:
1907						   		_random_unit(newUnit,3,14),
1908								_random_unit(newUnit,3,13),nil]];
1909
1910	[self addUnit:AUTORELEASE(newUnit)];
1911	*/
1912}
1913
1914- (void) drawRect:(NSRect)r
1915{
1916	int i;
1917	id en;
1918	LPUnit *unit;
1919
1920	/*
1921	[_background compositeToPoint:NSZeroPoint
1922						operation:NSCompositeCopy];
1923						*/
1924
1925	PSsetrgbcolor(0,0,0);
1926	PSsetalpha(1.0);  // 0.7
1927	PSrectfill(0,0,NSWidth(_bounds),NSHeight(_bounds));
1928
1929	PSsetrgbcolor(1,1,1);
1930	PSsetalpha(0.1);
1931	PSmoveto(0,0);
1932	for (i = 0; i < _numberOfRows; i++)
1933	{
1934		PSrlineto(NSWidth(_bounds), 0);
1935		PSrmoveto(-NSWidth(_bounds), _stepHeight);
1936	}
1937	PSstroke();
1938
1939	PSmoveto(0,0);
1940	for (i = 0; i < _numberOfColumns; i++)
1941	{
1942		PSrlineto(0,NSHeight(_bounds));
1943		PSrmoveto(_stepWidth,-NSHeight(_bounds));
1944	}
1945	PSstroke();
1946
1947	en = [_blowing objectEnumerator];
1948	while ((unit = [en nextObject]))
1949	{
1950		float p = [(LPJewelUnit *)unit phase] * 50;
1951		p = p - 12;
1952		PSgsave();
1953			PStranslate((1 - [unit alpha]) * p, (1 - [unit alpha]) * -p);
1954			[unit draw];
1955		PSgrestore();
1956	}
1957
1958
1959	{
1960		int cc,xx,yy;
1961		LPUnit* unit;
1962		NSMutableSet *set = [NSMutableSet setWithCapacity:70];
1963		cc=0;
1964
1965		for (yy = 13; yy >= 0 && cc < 8; yy--)
1966		for (xx = 5; xx >= 0; xx--)
1967		{
1968			unit = [self getUnitAtX:xx
1969								  Y:yy];
1970			if (unit != nil)
1971			{
1972				[set addObject:unit];
1973			}
1974		}
1975		[set makeObjectsPerform:@selector(draw)];
1976	}
1977
1978	PSgsave();
1979		PSinitclip();
1980
1981		PSsetrgbcolor(0,0,0);
1982		PSsetalpha(0.3);
1983		PSrectfill(0, _stepHeight * 12, _stepWidth * 3, _stepHeight);
1984		PSrectfill(_stepWidth * 4, _stepHeight * 12, _stepWidth * 2, _stepHeight);
1985
1986		PSsetalpha(0.5);
1987		PSsetrgbcolor(0.7,0.7,0.7);
1988		PSsetlinewidth(4);
1989		PSrectstroke(0, _stepHeight * 12, _stepWidth * 3, _stepHeight);
1990		PSrectstroke(_stepWidth * 4, _stepHeight * 12, _stepWidth * 2, _stepHeight);
1991		PSmoveto(0,NSHeight(_bounds));
1992		PSlineto(0,0);
1993		PSlineto(NSWidth(_bounds),0);
1994		PSlineto(NSWidth(_bounds),NSHeight(_bounds));
1995		PSstroke();
1996
1997		/****/
1998
1999		PSsetalpha(1);
2000		PSsetrgbcolor(0,0,0);
2001		PSsetlinewidth(2);
2002		PSrectstroke(0, _stepHeight * 12, _stepWidth * 3, _stepHeight);
2003		PSrectstroke(_stepWidth * 4, _stepHeight * 12, _stepWidth * 2, _stepHeight);
2004		PSmoveto(0,NSHeight(_bounds));
2005		PSlineto(0,0);
2006		PSlineto(NSWidth(_bounds),0);
2007		PSlineto(NSWidth(_bounds),NSHeight(_bounds));
2008		PSstroke();
2009
2010
2011		PStranslate(-1,1);
2012		PSsetrgbcolor(0.8,0.8,0.8);
2013		PSsetlinewidth(2);
2014		PSrectstroke(0, _stepHeight * 12, _stepWidth * 3, _stepHeight);
2015		PSrectstroke(_stepWidth * 4, _stepHeight * 12, _stepWidth * 2, _stepHeight);
2016		PSmoveto(0,NSHeight(_bounds));
2017		PSlineto(0,0);
2018		PSlineto(NSWidth(_bounds),0);
2019		PSlineto(NSWidth(_bounds),NSHeight(_bounds));
2020		PSstroke();
2021	PSgrestore();
2022
2023	if (trip || stone > 0)
2024	{
2025		int istone = stone > 0?stone:0;
2026		NSMutableAttributedString *str;
2027		NSString *s;
2028		NSSize strSize;
2029		NSSize gz = [self gridSize];
2030		s = [NSString stringWithFormat:@"%d",istone];
2031		str = [[NSMutableAttributedString alloc] initWithString:s];
2032		[str addAttribute:NSForegroundColorAttributeName
2033					value:[NSColor redColor]
2034					range:NSMakeRange(0,[s length])];
2035		[str addAttribute:NSFontAttributeName
2036					value:[NSFont boldSystemFontOfSize:gz.height*1.2]
2037					range:NSMakeRange(0,[s length])];
2038		strSize = [str size];
2039
2040		[str drawAtPoint:NSMakePoint(5 * gz.width - strSize.width/2, 12 * gz.height + gz.height/2 - strSize.height/2)];
2041		RELEASE(str);
2042	}
2043
2044	if (chaintrip%2 == 1 && maxchain > 1)
2045	{
2046		NSMutableAttributedString *str;
2047		NSString *s;
2048		NSSize strSize;
2049		NSSize gz = [self gridSize];
2050		s = [NSString stringWithFormat:@"%d CHAINS!",maxchain];
2051		str = [[NSMutableAttributedString alloc] initWithString:s];
2052		[str addAttribute:NSForegroundColorAttributeName
2053					value:[NSColor yellowColor]
2054					range:NSMakeRange(0,[s length])];
2055		[str addAttribute:NSFontAttributeName
2056					value:[NSFont boldSystemFontOfSize:gz.height * (maxchain>4?4:maxchain)/4]
2057					range:NSMakeRange(0,[s length])];
2058		strSize = [str size];
2059
2060		[str drawAtPoint:NSMakePoint((6 * gz.width / 2) - strSize.width/2, (13 * gz.height / 2) - strSize.height/2)];
2061		RELEASE(str);
2062	}
2063
2064}
2065
2066- (NSArray *) allUnits
2067{
2068	return _units;
2069}
2070
2071/*** key ***/
2072- (BOOL) acceptsFirstResponder
2073{
2074	  return YES;
2075}
2076
2077-(BOOL) performKeyEquivalent: (NSEvent *)event
2078{
2079	return NO;
2080}
2081
2082- (id) currentUnit
2083{
2084	return __currentUnit;
2085}
2086
2087-(BOOL) processDir:(LPDirType)dir
2088{
2089  if (_lockControl)
2090    {
2091      return NO;
2092    }
2093  if (__currentUnit == nil)
2094    {
2095      return NO;
2096    }
2097  switch(dir)
2098    {
2099    case LP_MOVE_DOWN:
2100      return [__currentUnit moveInDir:LP_MOVE_DOWN];
2101      break;
2102    case LP_MOVE_LEFT:
2103      return [__currentUnit moveInDir:LP_MOVE_LEFT];
2104      break;
2105    case LP_MOVE_RIGHT:
2106      return [__currentUnit moveInDir:LP_MOVE_RIGHT];
2107      break;
2108    case LP_MOVE_FALL:
2109      _lockControl = YES;
2110      [__currentUnit fallToBottom];
2111      break;
2112    case LP_MOVE_CW:
2113      [__currentUnit rotateCW];
2114      break;
2115    case LP_MOVE_CCW:
2116      [__currentUnit rotateCCW];
2117      break;
2118    default:
2119      NSAssert(0, @"Unreachable");
2120      break;
2121
2122    }
2123
2124  [self setNeedsDisplay:YES];
2125  return YES;
2126}
2127
2128-(void) keyDown: (NSEvent *)event
2129{
2130//NSLog(@"%d %@",[event keyCode], [event characters]);
2131
2132	switch ([[event characters] characterAtIndex:0])
2133	{
2134		case 'a':
2135			_useAI = !_useAI;
2136			break;
2137		case ',':
2138			[self processDir:LP_MOVE_CW];
2139			break;
2140		case '.':
2141			[self processDir:LP_MOVE_CCW];
2142			break;
2143		case ' ':
2144			[self processDir:LP_MOVE_FALL];
2145			break;
2146		case NSUpArrowFunctionKey:
2147			[self processDir:LP_MOVE_CCW];
2148			break;
2149		case NSLeftArrowFunctionKey:
2150			[self processDir:LP_MOVE_LEFT];
2151			break;
2152		case NSRightArrowFunctionKey:
2153			[self processDir:LP_MOVE_RIGHT];
2154			break;
2155		case NSDownArrowFunctionKey:
2156			[self processDir:LP_MOVE_DOWN];
2157			break;
2158		case 'd':
2159			[__owner op:self processDir:LP_MOVE_CW];
2160			break;
2161		case 'f':
2162			[__owner op:self processDir:LP_MOVE_CCW];
2163			break;
2164		case 'x':
2165	   		[__owner op:self processDir:LP_MOVE_LEFT];
2166			break;
2167		case 'v':
2168	   		[__owner op:self processDir:LP_MOVE_RIGHT];
2169			break;
2170		case 'c':
2171			[__owner op:self processDir:LP_MOVE_DOWN];
2172			break;
2173		default:
2174			break;
2175	}
2176	[self setNeedsDisplay:YES];
2177}
2178
2179- (void) toggleAI
2180{
2181	_useAI = !_useAI;
2182}
2183
2184- (BOOL) useAI
2185{
2186	return _useAI;
2187}
2188
2189@end
2190
2191@implementation LapisNextView
2192- (void) awakeFromNib
2193{
2194	chain = 0;
2195	trip = 0;
2196	chaintrip = 0;
2197	_gameOver = NO;
2198	_numberOfRows = 2;
2199	_numberOfColumns = 1;
2200
2201	_stepsInUnit = 1;
2202	_stepHeight = NSHeight(_frame)/((float)_numberOfRows);
2203	_stepWidth = NSWidth(_frame)/_numberOfColumns;
2204
2205	_units = [[NSMutableArray alloc] init];
2206
2207	_blowing = [[NSMutableSet alloc] init];
2208
2209}
2210
2211- (NSSize) gridSize
2212{
2213	return NSMakeSize(
2214		NSWidth(_bounds)/(_numberOfColumns * _stepsInUnit),
2215		NSHeight(_bounds)/(((float)_numberOfRows) * _stepsInUnit)
2216		);
2217}
2218
2219- (void) addJewelUnit
2220{
2221	id newUnit = [LPGroupUnit alloc];
2222	newUnit = [newUnit initWithOwner:self
2223					 atoms:[NSArray arrayWithObjects:
2224						   		_random_unit(newUnit,0,10,YES),
2225								_random_unit(newUnit,0,9,NO),nil]];
2226
2227	[self addUnit:AUTORELEASE(newUnit)];
2228}
2229
2230- (void) removeUnit:(id)unit
2231{
2232	[_units removeObject:unit];
2233}
2234
2235-(void) keyDown: (NSEvent *)event
2236{
2237}
2238
2239@end
2240