1 /*
2 * IceBreaker
3 * Copyright (c) 2000-2002 Matthew Miller <mattdm@mattdm.org> and
4 *   Enrico Tassi <gareuselesinge@infinito.it>
5 *
6 * <http://www.mattdm.org/icebreaker/>
7 *
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License as published by the Free
10 * Software Foundation; either version 2 of the License, or (at your option)
11 * any later version.
12 *
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
16 * for more details.
17 *
18 * You should have received a copy of the GNU General Public License along
19 * with this program; if not, write to the Free Software Foundation, Inc., 59
20 * Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 *
22 */
23 
24 /* special thanks to Enrico for the grid coloring stuff for themes */
25 
26 #include <SDL.h>
27 #include <stdio.h>
28 
29 #include "icebreaker.h"
30 #include "cursor.h"
31 #include "penguin.h"
32 #include "line.h"
33 #include "grid.h"
34 #include "laundry.h"
35 #include "sound.h"
36 #include "globals.h"
37 #include "level.h"
38 #include "status.h"
39 #include "text.h"
40 #include "dialog.h"
41 #include "menu.h"
42 #include "options.h"
43 #include "themes.h"
44 #include "event.h"
45 #include "delay.h"
46 
47 // fix -- make this not need to be a global
48 Line line1;
49 Line line2;
50 
51 // fix -- good candidate for dynamic memory allocation
52 Penguin flock[MAXPENGUINS];
53 int penguincount=0;
54 
55 static int lives=0;
56 static int clear=0;
57 static long score=0;
58 
59 static void setuplevel(void);
60 
61 static void levelaction_startclick(int mousex, int mousey, LineType linetype);
62 static void levelaction_switchclick(LineType* linetypepointer);
63 
setuplevel()64 void setuplevel()
65 {
66 	int x,y;
67 
68 	setcursor(CURSORARROW);
69 	SDL_FillRect(screen,NULL,color.background);
70 
71 	for (x=0;x<WIDTH;x++)
72 		for (y=0;y<HEIGHT;y++)
73 		{
74 			if (x<BORDERLEFT || x>=BORDERRIGHT || y <BORDERTOP || y>=BORDERBOTTOM)
75 				grid[x][y]='X';
76 			else
77 				grid[x][y]=' ';
78 		}
79 
80 	drawgridblocks();
81 
82 	updateall();
83 
84 }
85 
86 
87 
playlevel(int level,long oldscore,ScoreSheet * levelscore)88 LevelExitType playlevel(int level, long oldscore, ScoreSheet * levelscore)
89 {
90 	LevelExitType returncode=ERROR;
91 	int i,mx,my;
92 
93 	SDL_Rect menubuttonrect;
94 	int menubuttonglow=false;
95 	int menubuttonpressed=false;
96 	int paused=false;
97 
98 	int linedoneflag=false;
99 	LineType linetype;
100 	float scoremod=1;
101 	float bonusmod=1;
102 
103 	int percentbonus=PERCENTBONUS; // right now, there's really no point in making this a variable but it doesn't hurt, and keeps things consistant
104 	int percentextrabonus=PERCENTEXTRABONUS-(level-1)/5; // this needs to decrease, or else the bonus disintegrates as the space required to actually trap penguins increases
105 
106 	int tick=0;
107 	int timepenalty=0;
108 	int timepenaltyinterval=0;
109 
110 	int domenuflag = false;
111 
112 	int movecursorleftflag = false;
113 	int movecursorrightflag = false;
114 	int movecursorupflag = false;
115 	int movecursordownflag = false;
116 	int keyboardcounter=0;
117 	int keyboarddelay=0;
118 
119 	Uint32 loopstartticks;
120 
121 	int done = false;
122 
123 	SDL_Event event;
124 
125 
126 	penguincount=level+1;
127 	lives=level+1;
128 	clear=0;
129 	score=oldscore;
130 
131 	switch(options.difficulty)
132 	{
133 		case NORMAL:
134 			scoremod=(level+1)/2.0;
135 			bonusmod=(level+1)/2.0;
136 			timepenaltyinterval=100;
137 		break;
138 		case EASY:
139 			scoremod=(level+2)/6.0;
140 			bonusmod=(level+1)/9.0;
141 			timepenaltyinterval=200;
142 		break;
143 		case HARD:
144 			scoremod=(level+1)/1.75;
145 			bonusmod=(level+1)/1.5;
146 			timepenaltyinterval=75;
147 		break;
148 		default:
149 			fprintf(stderr,"Unknown difficulty -- that can't happen!\n");
150 		break;
151 	}
152 
153 	levelscore->basescore=0;
154 	levelscore->clearbonus=0;
155 	levelscore->lifebonus=0;
156 
157 	setuplevel();
158 
159 	setcursor(CURSORVERTICAL); linetype=VERTICAL;
160 
161 /*	printf("===========================================================\n"
162 	       "Starting level %d.\n"
163 	       "Lives: %d\n",
164 	       level,lives);
165 */
166 
167 	menubuttonrect.x=WIDTH-(CHARWIDTH*2*4)-MARGINRIGHT-4;
168 	menubuttonrect.y=BOTTOMSTATUSY;
169 	menubuttonrect.w=CHARWIDTH*2*4+3;
170 	menubuttonrect.h=CHARHEIGHT*2+3;
171 	drawmenubutton(&menubuttonrect,false);
172 
173 	line1=createline(1);
174 	line2=createline(2);
175 
176 	for (i=0;i<penguincount;i++)
177 	{
178 		flock[i] = createpenguin();
179 	}
180 
181 	updatestatuslives(lives);
182 	updatestatuscleared(clear);
183 	updatestatusscore(score);
184 	updatehiscorebox();
185 
186 	clean();
187 
188 	do
189 	{
190 		loopstartticks = SDL_GetTicks();
191 
192 		// FIX -- this is way too messy. time to split it up into
193 		// neat little functions or something. Especially the menubutton stuff.
194 		while (pollevent(&event))
195 		{
196 			if (event.type == SDL_QUIT)
197 			{
198 				lives=0; // is this the right way?
199 				done = true;
200 				// fix -- should delete penguins, etc. here
201 				return QUIT;
202 			}
203 			else if (event.type == SDL_MOUSEMOTION)
204 			{
205 				//if (grid[event.motion.x][event.motion.y] == ' ' || grid[event.motion.x][event.motion.y] == '*')
206 				if (event.motion.x>BORDERLEFT && event.motion.x<BORDERRIGHT && event.motion.y>BORDERTOP && event.motion.y<BORDERBOTTOM)
207 				{
208 					switch (linetype)
209 					{
210 						case HORIZONTAL:
211 							setcursor(CURSORHORIZONTAL);
212 						break;
213 						case VERTICAL:
214 							setcursor(CURSORVERTICAL);
215 						break;
216 					}
217 				}
218 				else  // we're somewhere outside of the playing area
219 				{
220 					setcursor(CURSORARROW);
221 				}
222 
223 				if (event.motion.x>=menubuttonrect.x && event.motion.y>=menubuttonrect.y && event.motion.x<menubuttonrect.x+menubuttonrect.w && event.motion.y<menubuttonrect.y+menubuttonrect.h)
224 				{ // over the menu button
225 					if (!menubuttonglow)
226 					{
227 						menubuttonglow=true;
228 						drawmenubutton(&menubuttonrect,menubuttonglow);
229 					}
230 				}
231 				else
232 				{
233 					if (menubuttonglow && !menubuttonpressed)
234 					{
235 						menubuttonglow=false;
236 						drawmenubutton(&menubuttonrect,menubuttonglow);
237 					}
238 
239 				}
240 			}
241 			else if (event.type == SDL_MOUSEBUTTONDOWN)
242 			{
243 				if (event.button.button==1) //left
244 				{
245 					// in game area?
246 					if (event.button.x>BORDERLEFT && event.button.x<BORDERRIGHT && event.button.y>BORDERTOP && event.button.y<BORDERBOTTOM)
247 					{
248 						levelaction_startclick(event.button.x, event.button.y, linetype);
249 					}
250 					else if (event.button.x>=menubuttonrect.x && event.button.y>=menubuttonrect.y && event.button.x<menubuttonrect.x+menubuttonrect.w && event.button.y<menubuttonrect.y+menubuttonrect.h)
251 					{
252 						menubuttonpressed=true;
253 					}
254 				}
255   				else //middle or right
256   				{
257   					levelaction_switchclick(&linetype);
258 				}
259 			}
260 			else if (event.type == SDL_MOUSEBUTTONUP)
261 			{
262 				if (menubuttonpressed && event.button.x>=menubuttonrect.x && event.button.y>=menubuttonrect.y && event.button.x<menubuttonrect.x+menubuttonrect.w && event.button.y<menubuttonrect.y+menubuttonrect.h)
263 				{
264 					menubuttonglow=true;
265 					drawmenubutton(&menubuttonrect,menubuttonglow);
266 					domenuflag=true;
267 				}
268 				else if (menubuttonglow && menubuttonpressed)
269 				{
270 					menubuttonglow=false;
271 					drawmenubutton(&menubuttonrect,menubuttonglow);
272 				}
273 				menubuttonpressed=false;
274 			}
275 			else if (event.type == SDL_KEYDOWN)
276 			{
277 
278 				switch(translatekeyevent(&event))
279 				{
280 					case KEYMENU:
281 						domenuflag=true;
282 					break;
283 					case KEYPAUSE:
284 						// FIX -- this doesn't work.
285 						// paused=!paused;
286 					break;
287 					case KEYHELP: // fix -- kludgy code duplication
288 						setcursor(CURSORARROW);
289 						if (popuphelp()==POPUPQUITGAME)
290 						{
291 							lives=0; // is this the right way?
292 							done = true;
293 							return QUIT;
294 						}
295 						getmousestate(&mx,&my);
296 						if (mx>BORDERLEFT && mx<BORDERRIGHT && my>BORDERTOP && my<BORDERBOTTOM)
297 						{
298 							switch (linetype)
299 							{
300 								case HORIZONTAL:
301 									setcursor(CURSORHORIZONTAL);
302 								break;
303 								case VERTICAL:
304 									setcursor(CURSORVERTICAL);
305 								break;
306 							}
307 						}
308 					break;
309 					case KEYSTARTLINE:
310 						// in game area?
311 						getmousestate(&mx,&my);
312 						if (mx>BORDERLEFT && mx<BORDERRIGHT && my>BORDERTOP && my<BORDERBOTTOM)
313 							levelaction_startclick(mx, my, linetype);
314 						else if (menubuttonglow && mx>=menubuttonrect.x && my>=menubuttonrect.y && mx<menubuttonrect.x+menubuttonrect.w && my<menubuttonrect.y+menubuttonrect.h)
315 							domenuflag=true;
316 					break;
317 					case KEYSWITCHLINE:
318 	  					levelaction_switchclick(&linetype);
319 					break;
320 					case KEYMOVELEFT:
321 						movecursorleftflag = true;
322 					break;
323 					case KEYMOVERIGHT:
324 						movecursorrightflag = true;
325 					break;
326 					case KEYMOVEUP:
327 						movecursorupflag = true;
328 					break;
329 					case KEYMOVEDOWN:
330 						movecursordownflag = true;
331 					break;
332 					case KEYMOVELEFTUP:
333 						movecursorleftflag = true; movecursorupflag = true;
334 					break;
335 					case KEYMOVERIGHTUP:
336 						movecursorrightflag = true; movecursorupflag = true;
337 					break;
338 					case KEYMOVELEFTDOWN:
339 						movecursorleftflag = true; movecursordownflag = true;
340 					break;
341 					case KEYMOVERIGHTDOWN:
342 						movecursorrightflag = true; movecursordownflag = true;
343 					break;
344 					default:
345 					break;
346 				}
347 			}
348 			else if (event.type == SDL_KEYUP)
349 			{
350 				switch(translatekeyevent(&event))
351 				{
352 					case KEYMOVELEFT:
353 						movecursorleftflag = false;
354 					break;
355 					case KEYMOVERIGHT:
356 						movecursorrightflag = false;
357 					break;
358 					case KEYMOVEUP:
359 						movecursorupflag = false;
360 					break;
361 					case KEYMOVEDOWN:
362 						movecursordownflag = false;
363 					break;
364 					case KEYMOVELEFTUP:
365 						movecursorleftflag = false; movecursorupflag = false;
366 					break;
367 					case KEYMOVERIGHTUP:
368 						movecursorrightflag = false; movecursorupflag = false;
369 					break;
370 					case KEYMOVELEFTDOWN:
371 						movecursorleftflag = false; movecursordownflag = false;
372 					break;
373 					case KEYMOVERIGHTDOWN:
374 						movecursorrightflag = false; movecursordownflag = false;
375 					break;
376 					default:
377 					break;
378 				}
379 			}
380 			else if (event.type == SDL_ACTIVEEVENT && ((options.autopause == AUTOPAUSEON) || (event.active.state & SDL_APPACTIVE)))
381 			{
382 				if (event.active.gain==0)
383 				{ // iconified / lost focus
384 					paused=true;
385 				}
386 				else // event.active.gain==1
387 				{ // restored /got focus
388 					paused=false;
389 				}
390 			}
391 			// fix -- other events to handle?
392 		}
393 
394 		// move the mouse cursor in response to arrow keys
395 		if (!keyboarddelay)
396 		{
397 			if (movecursorleftflag || movecursorrightflag || movecursorupflag || movecursordownflag)
398 			{
399 				getmousestate(&mx,&my);
400 				mx=((mx/BLOCKWIDTH ) * BLOCKWIDTH ) + 4; // these numbers are kludgy -- really ought to change based on margin width
401 				my=((my/BLOCKHEIGHT) * BLOCKHEIGHT) + 3;
402 
403 				if (movecursorleftflag)
404 				{
405 					if (mx>BORDERLEFT)
406 						mx-=BLOCKWIDTH;
407 					else
408 						movecursorleftflag=false;
409 				}
410 				if (movecursorrightflag)
411 				{
412 					if (mx<BORDERRIGHT)
413 						mx+=BLOCKWIDTH;
414 					else
415 						movecursorrightflag=false;
416 				}
417 				if (movecursorupflag)
418 				{
419 					if (my>BORDERTOP)
420 						my-=BLOCKHEIGHT;
421 					else
422 						movecursorupflag=false;
423 				}
424 				if (movecursordownflag)
425 				{
426 					if (my<BORDERBOTTOM)
427 						my+=BLOCKHEIGHT;
428 					else
429 						movecursordownflag=false;
430 				}
431 				jumpmouse(mx,my);
432 				keyboarddelay=KEYBOARDARROWTHROTTLER;
433 				if (keyboardcounter>=KEYBOARDARROWACCELPOINT*3)
434 					keyboarddelay=KEYBOARDARROWTHROTTLER/3;
435 				else if (keyboardcounter>=KEYBOARDARROWACCELPOINT)
436 					keyboarddelay=KEYBOARDARROWTHROTTLER/2;
437 				keyboardcounter++;
438 
439 
440 			}
441 			else
442 			{
443 				keyboardcounter=0;
444 			}
445 		}
446 		else
447 		{
448 			keyboarddelay--;
449 		}
450 
451 
452 		// move split-lines
453 		if (line1.on)
454 		{
455 			// kludgy crap to implement speed = 1 1/2
456 			linedoneflag=moveline(&line1);
457 			if (!linedoneflag && line1.speedslower)
458 			{
459 				line1.speedslower=false;
460 				linedoneflag=moveline(&line1);
461 			}
462 			else
463 			{
464 				line1.speedslower=true;
465 			}
466 
467 			if (linedoneflag)
468 			{
469 				clear=(100-(countcleared()*100/(PLAYWIDTH*PLAYHEIGHT)));
470 				levelscore->basescore=(int)(clear*scoremod)-timepenalty;
471 				score=oldscore+levelscore->basescore;
472 				tick=0; // reset this to keep score from seeming to flicker around a lot. and to be nice. :)
473 
474 				updatestatuscleared(clear);
475 				updatestatusscore(score);
476 				//printf("Cleared: %d%%\n",clear);
477 			}
478 
479 			if (line1.dead)
480 			{
481 				playsound(SNDBREAK);
482 				killline(&line1);
483 				lives--;
484 				if (lives<0) lives=0;
485 				updatestatuslives(lives);
486 				//printf("Lives: %d\n",lives);
487 			}
488 		}
489 		if (line2.on)
490 		{
491 			linedoneflag=moveline(&line2);
492 			if (!linedoneflag && line2.speedslower)
493 			{
494 				line2.speedslower=false;
495 				linedoneflag=moveline(&line2);
496 			}
497 			else
498 			{
499 				line2.speedslower=true;
500 			}
501 
502 			if (linedoneflag)
503 			{
504 				clear=(100-(countcleared()*100/(PLAYWIDTH*PLAYHEIGHT)));
505 				levelscore->basescore=(int)(clear*scoremod)-timepenalty;
506 				score=oldscore+levelscore->basescore;
507 				tick=0; // reset this to keep score from seeming to flicker around a lot. and to be nice. :)
508 
509 				updatestatuscleared(clear);
510 				updatestatusscore(score);
511 				//printf("Cleared: %d%%\n",clear);
512 			}
513 
514 			if (line2.dead)
515 			{
516 				playsound(SNDBREAK);
517 				killline(&line2);
518 				lives--;
519 				if (lives<0) lives=0;
520 				updatestatuslives(lives);
521 				//printf("Lives: %d\n",lives);
522 
523 			}
524 		}
525 
526 
527 		// move (and get old background)
528 		for (i=0;i<penguincount;i++)
529 		{
530 
531 			soil(flock[i].geom); // mark the penguin's old position as dirty
532 			movepenguin(&flock[i]);
533 
534 			soil(flock[i].geom); // mark the penguin's new position as dirty too (it will be soon...)
535 			savebehindpenguin(&flock[i]);
536 		}
537 
538 
539 		// actually draw
540 		for (i=0;i<penguincount;i++)
541 		{
542 			drawpenguin(&flock[i]);
543 		}
544 
545 		if (domenuflag)
546 		{
547 			setcursor(CURSORARROW);
548 			switch (popuplevelmenu())
549 			{
550 				case POPUPQUITGAME:
551 					lives=0; // is this the right way?
552 					done = true;
553 					return QUIT;
554 				break;
555 				case POPUPNEWGAME:
556 					// hmmm... maybe this could be done better
557 					done = true;
558 					if (checkiflevelatstart())
559 						returncode=ZERO;
560 					else
561 						returncode=DEAD;
562 				break;
563 				default:
564 					// if we don't recognize the code, do nothing.
565 				break;
566 			}
567 
568 			domenuflag=false;
569 		}
570 
571 
572 		// update clock
573 		tick++;
574 		if (tick>timepenaltyinterval)
575 		{
576 			tick=0;
577 			if (levelscore->basescore>0)
578 			{
579 				timepenalty++;
580 				levelscore->basescore--;
581 				score=oldscore+levelscore->basescore;
582 				updatestatusscore(score);
583 			}
584 		}
585 
586 		// update screen
587 		clean();
588 
589 		if (paused)
590 		{
591 			SDL_WaitEvent(NULL);
592 		}
593 
594 
595 		// clear for next update
596 		for (i=0;i<penguincount;i++)
597 		{
598 			erasepenguin(&flock[i]);
599 		}
600 
601 		if (lives<=0) // game over
602 		{
603 			done = true;
604 			returncode=DEAD;
605 		}
606 		else if (!line1.on && !line2.on && clear>=PERCENTREQUIRED) // success!
607 		{
608 			done = true;
609 			levelscore->basescore=(int)(clear*scoremod)-timepenalty;
610 			returncode=PASS;
611 
612 
613 			levelscore->clearbonus=0;
614 			// bonuses for clearing lots
615 			if (clear>percentbonus)
616 				levelscore->clearbonus+=(int)((clear-percentbonus)*bonusmod);
617 			if (clear>percentextrabonus)
618 				levelscore->clearbonus+=(int)((clear-percentextrabonus)*(clear-percentextrabonus)*bonusmod);
619 
620 			// bonuses for leftover lives
621 			levelscore->lifebonus=(int)((lives-1)*3*bonusmod);
622 		}
623 
624 		//printboard();
625 
626 		delaytil(loopstartticks+MSECPERFRAME);
627 
628 
629 	} while (!done);
630 
631 	// make sure visible penguins are actually in the screen memory,
632 	// and then erase them so we don't leak surfaces.
633 	for (i=0;i<penguincount;i++)
634 	{
635 		drawpenguin(&flock[i]);
636 		deletepenguin(&flock[i]);
637 	}
638 
639 	clean();
640 	return returncode;
641 }
642 
643 
644 
645 // this function at least makes us able to keep the level variables local
646 // to this file instead of totally global...
redrawwholelevel()647 void redrawwholelevel()
648 {
649 	int i;
650 	SDL_FillRect(screen,NULL,color.background);
651 	drawgridblocks();
652 
653 	updatehiscorebox();
654 	updatestatusscore(score);
655 	updatestatuslives(lives);
656 	updatestatuscleared(clear);
657 
658 	// FIX -- changing theme makes line appear to grow
659 	// by one pixel. that's kinda gross -- we should correct that.
660 	if (line1.on) SDL_FillRect(screen,&(line1.geom),*(line1.colorpointer));
661 	if (line2.on) SDL_FillRect(screen,&(line2.geom),*(line2.colorpointer));
662 
663 	for (i=0;i<penguincount;i++)
664 	{
665 		resetpenguinimage(&flock[i]);
666 		savebehindpenguin(&flock[i]);
667 		drawpenguin(&flock[i]);
668 	}
669 
670 }
671 
checkiflevelatstart(void)672 int checkiflevelatstart(void)
673 {
674 	return (clear==0 && score==0);
675 }
676 
levelaction_startclick(int mousex,int mousey,LineType linetype)677 void levelaction_startclick(int mousex, int mousey, LineType linetype)
678 {
679 	int x,y,xdif,ydif;
680 
681 	x=(((mousex-BORDERLEFT)/BLOCKWIDTH ) * BLOCKWIDTH ) +BORDERLEFT;
682 	y=(((mousey-BORDERTOP)/BLOCKHEIGHT) * BLOCKHEIGHT) +BORDERTOP;
683 	xdif=mousex-x; ydif=mousey-y;
684 
685 	if (grid[x][y] == '*' || grid[mousex][mousey] == '*')
686 	{ // a little bit of grace if the player clicks directly on the penguin
687 		playsound(SNDOUCH);
688 	}
689 	else if (grid[x][y] == ' ')
690 	{
691 		switch (linetype)
692 		{
693 			case VERTICAL:
694 				if (!line1.on)
695 					(ydif<BLOCKHEIGHT/2) ? startline(&line1,UP,x,y) : startline(&line1,UP,x,y+BLOCKHEIGHT);
696 				if (!line2.on)
697 					(ydif<BLOCKHEIGHT/2) ? startline(&line2,DOWN,x,y) : startline(&line2,DOWN,x,y+BLOCKHEIGHT);
698 			break;
699 			case HORIZONTAL:
700 				if (!line1.on)
701 					(xdif<BLOCKWIDTH/2) ? startline(&line1,LEFT,x,y) : startline(&line1,LEFT,x+BLOCKWIDTH,y);
702 				if (!line2.on)
703 					(xdif<BLOCKWIDTH/2) ? startline(&line2,RIGHT,x,y) : startline(&line2,RIGHT,x+BLOCKWIDTH,y);
704 			break;
705 		}
706 	}
707 }
708 
levelaction_switchclick(LineType * linetypepointer)709 void levelaction_switchclick(LineType* linetypepointer)
710 {
711 	switch (*linetypepointer)
712 	{
713 		case VERTICAL:
714 			*linetypepointer=HORIZONTAL;
715 			setcursor(CURSORHORIZONTAL);
716 		break;
717 		case HORIZONTAL:
718 			*linetypepointer=VERTICAL;
719 			setcursor(CURSORVERTICAL);
720 		break;
721 	}
722 }
723