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