1/*
2 Copyright (C) 1998-2001 Andreas Zehender <az@azweb.de>
3
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation; either version 2 of the License, or
6 (at your option) any later version.
7
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License
14 along with this program; if not, write to the Free Software
15 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16*/
17
18#include "ai.h"
19
20#include <math.h>
21
22#include <QVector>
23
24#include "mathroutines.h"
25#include "options.h"
26#include "structs.h"
27
28int Ai::calcFrameIncrement[Options::EnumAiDifficulty::COUNT] = {15,8,6,2};
29int Ai::calcPositionNumber[Options::EnumAiDifficulty::COUNT] = {10,15,20,60};
30int Ai::calcShotDirections[Options::EnumAiDifficulty::COUNT] = {4,7,10,12};
31int Ai::calcCollisions[Options::EnumAiDifficulty::COUNT] = {30,15,10,10};
32int Ai::calcNextShot[Options::EnumAiDifficulty::COUNT] = {300,200,90,60};
33
34Ai::Ai(int pn,ShipSprite* s[2],QList<BulletSprite*>* b[2],
35 QList<MineSprite*>* m[2],SConfig *c)
36{
37 int i;
38
39 playerNumber=pn;
40 opponentNumber=(pn+1)%2;
41 cfg=c;
42
43 for(i=0;i<2;i++)
44 {
45 ship[i]=s[i];
46 bullets[i]=b[i];
47 mines[i]=m[i];
48 shipsNextPositions[i]=new QVector<AiSprite>
49 ((int)(calcPositionNumber[Options::aiDifficulty(playerNumber)]/cfg->gamespeed));
50 aiMines[i]=new QVector<AiSprite>(cfg->maxMines);
51 mineNumber[i]=0;
52 }
53}
54
55Ai::~Ai()
56{
57 int i;
58
59 for (i=0;i<2;i++)
60 {
61 delete shipsNextPositions[i];
62 delete aiMines[i];
63 }
64
65 qDeleteAll(myShots);
66 myShots.clear();
67
68 qDeleteAll(objectsHitByShip);
69 objectsHitByShip.clear();
70
71 qDeleteAll(minesHitByShot);
72 minesHitByShot.clear();
73}
74
75void Ai::newRound()
76{
77 accelerateFramesNumber=0;
78 rotateFramesNumber=0;
79 shoot=false;
80 score=1e10;
81
82 rotation=RNONE;
83 acc=false;
84 bullet=false;
85 mine=false;
86
87 borderTime=-1;
88 sunTime=-1;
89
90 calculateCollisions=(int)(calcCollisions[Options::aiDifficulty(playerNumber)]
91 /cfg->gamespeed);
92 waitShot=(int) rint( random.getDouble() *
93 calcNextShot[Options::aiDifficulty(playerNumber)]
94 /cfg->gamespeed);
95
96 qDeleteAll(myShots);
97 myShots.clear();
98 qDeleteAll(objectsHitByShip);
99 objectsHitByShip.clear();
100 qDeleteAll(minesHitByShot);
101 minesHitByShot.clear();
102}
103
104void Ai::think()
105{
106 setSpriteFieldSize();
107
108 qDeleteAll(myShots);
109 myShots.clear();
110 borderTime=-1;
111 sunTime=-1;
112 score--;
113 if(waitShot>0)
114 waitShot--;
115
116 calculateNextPositions();
117 if(Options::aiDifficulty(playerNumber)!=Options::EnumAiDifficulty::Trainee)
118 testForHits();
119 if(waitShot<=0)
120 {
121 tryShots();
122 shotScores();
123 }
124 chooseAction();
125
126
127 if(rotateFramesNumber<=0)
128 {
129 rotation=RNONE;
130 if(accelerateFramesNumber<=0)
131 {
132 acc=false;
133 if(shoot)
134 {
135 bullet=true;
136 shoot=false;
137 }
138 else
139 bullet=false;
140 score=1e10;
141 }
142 else
143 {
144 acc=true;
145 accelerateFramesNumber--;
146 }
147 }
148 else
149 rotateFramesNumber--;
150
151}
152
153
154AiSprite Ai::nextPosition(AiSprite sp,double mult)
155{
156 double abs_2,nx,ny,sq,eg;
157 if(!sp.sun)
158 {
159 abs_2=sp.x*sp.x+sp.y*sp.y;
160 if(abs_2<1)
161 abs_2=1;
162 sq=sqrt(abs_2);
163 nx=sp.x/sq;
164 ny=sp.y/sq;
165 eg=cfg->gravity*mult;
166 sp.dx-=eg*nx/abs_2;
167 sp.dy-=eg*ny/abs_2;
168
169 sp.x+=sp.dx*mult;
170 sp.y+=sp.dy*mult;
171
172 if(sp.x*sp.x+sp.y*sp.y<1600)
173 sp.sun=true;
174 else
175 {
176 //simple bounds actions
177 if(sp.x>sfwidth_2)
178 {
179 sp.x-=sfwidth;
180 sp.border=true;
181 }
182 else if(sp.x<-sfwidth_2)
183 {
184 sp.x+=sfwidth;
185 sp.border=true;
186 }
187 if(sp.y>sfheight_2)
188 {
189 sp.y-=sfheight;
190 sp.border=true;
191 }
192 else if(sp.y<-sfheight_2)
193 {
194 sp.y+=sfheight;
195 sp.border=true;
196 }
197 }
198 }
199
200 return sp;
201}
202
203void Ai::nextPositions(AiSprite sp,QVector<AiSprite> *a,int frames)
204{
205 int i,num;
206 double fmult=cfg->gamespeed*frames;
207
208 (*a)[0]=nextPosition(sp,cfg->gamespeed);
209 num=a->size();
210 for(i=1;i<num;i++)
211 (*a)[i]=nextPosition((*a)[i-1],fmult);
212}
213
214void Ai::calculateNextPositions()
215{
216 unsigned int i;
217 int j;
218
219 j=(int)(calcPositionNumber[Options::aiDifficulty(playerNumber)]/cfg->gamespeed);
220
221 if(shipsNextPositions[0]->size() != j)
222 for(i=0;i<2;i++)
223 shipsNextPositions[i]->resize(j);
224
225 for(i=0;i<2;i++)
226 nextPositions(ship[i]->toAiSprite(),shipsNextPositions[i],
227 calcFrameIncrement[Options::aiDifficulty(playerNumber)]);
228
229 if(cfg->maxMines > aiMines[0]->size())
230 for(i=0;i<2;i++)
231 aiMines[i]->resize(cfg->maxMines);
232
233 for(i=0;i<2;i++)
234 {
235 for (j=0; j<mines[i]->size(); j++)
236 {
237 (*(aiMines[i]))[j]=mines[i]->value(j)->toAiSprite();
238 }
239 mineNumber[i]=j;
240 }
241}
242
243void Ai::tryShots()
244{
245 AiSprite shot,me;
246 double rot,nr,nx,ny;
247 int i,f,frameIncrement,frameNum;
248 Hit hit;
249 Shot *goodShot;
250
251 me=ship[playerNumber]->toAiSprite();
252 rot=ship[playerNumber]->getRotation();
253
254 //Each 'frameIncrement' frames a shot is tried
255 frameIncrement=(int)((2*M_PI/calcShotDirections[Options::aiDifficulty(playerNumber)])
256 /cfg->rotationSpeed);
257 if(frameIncrement==0)
258 frameIncrement=1;
259 //Number of frames needed to rotate 180 degrees
260 frameNum=(int)(M_PI/(frameIncrement*cfg->rotationSpeed));
261
262 //if too much bullets are on the playfield, no shot is tried
263 if(bullets[playerNumber]->count() <
264 (cfg->maxBullets+ship[playerNumber]->getBulletPowerups()))
265 {
266 for(f=0;f<=frameNum;f++)
267 {
268 if(f!=0)
269 for(i=0;i<frameIncrement;i++)
270 me=nextPosition(me,cfg->gamespeed);
271 else
272 me=nextPosition(me,cfg->gamespeed);
273
274 if(!ship[playerNumber]->reloadsBullet(f*frameIncrement*cfg->gamespeed))
275 {
276 for(i=0;i<2;i++)
277 {
278 if((f==0)&&(i==1))
279 continue;
280 if(i==0)
281 nr=rot+frameIncrement*f*cfg->rotationSpeed;
282 else
283 nr=rot-frameIncrement*f*cfg->rotationSpeed;
284
285 nx=cos(nr);
286 ny=sin(nr);
287 shot.x=me.x+nx*SHOTDIST;
288 shot.y=me.y+ny*SHOTDIST;
289 shot.dx=me.dx+nx*cfg->shotSpeed;
290 shot.dy=me.dy+ny*cfg->shotSpeed;
291 shot.sun=false;
292 shot.border=false;
293
294 hit=firstObject(shot,f*frameIncrement,
295 calcFrameIncrement[Options::aiDifficulty(playerNumber)]);
296 if((hit.object!=HNOTHING) &&
297 !((hit.object==HSHIP)&&(hit.playerNumber==playerNumber)))
298 {
299 goodShot=new Shot;
300 goodShot->hit=hit;
301 goodShot->rotation=(i==0?RLEFT:RRIGHT);
302 goodShot->rotationFrames=f*frameIncrement;
303 goodShot->score=1e10;
304 myShots.append(goodShot);
305 }
306 }
307 }
308 }
309 }
310}
311
312Hit Ai::firstObject(AiSprite shot,int time,int frames)
313{
314 int optime,i,num,rtime,basetime,t,m;
315 double dist,distx,disty,shiplastdist=0;
316 bool shipdistgreater=true,hitfound=false;
317 Hit hit={HNOTHING,0,0,0,1e10};
318
319 basetime=time/frames;
320 if((time%frames)>0)
321 basetime++;
322 rtime=basetime*frames-time;
323 optime=shipsNextPositions[0]->size();
324
325 num=optime-basetime;
326 if(num>0)
327 {
328 for(t=0;(t<num)&&(!hitfound)&&(!shot.sun);t++)
329 {
330 if(t==0)
331 shot=nextPosition(shot,cfg->gamespeed*rtime);
332 else
333 shot=nextPosition(shot,cfg->gamespeed*frames);
334
335 //distance to other objects
336 for(i=0;i<2;i++)
337 {
338 distx=(*(shipsNextPositions[i]))[basetime].x-shot.x;
339 disty=(*(shipsNextPositions[i]))[basetime].y-shot.y;
340 dist=distx*distx+disty*disty;
341 //own ship
342 if(i==playerNumber)
343 {
344 if(dist<shiplastdist)
345 shipdistgreater=false;
346 if((!shipdistgreater)&&(dist<hit.distance))
347 {
348 hit.object=HSHIP;
349 hit.objectNumber=0;
350 hit.playerNumber=i;
351 hit.hitTime=basetime*frames;
352 hit.distance=dist;
353 }
354 shiplastdist=dist;
355 }
356 //other ship
357 else if(dist<hit.distance)
358 {
359 hit.object=HSHIP;
360 hit.objectNumber=0;
361 hit.playerNumber=i;
362 hit.hitTime=basetime*frames;
363 hit.distance=dist;
364 }
365
366 //mines
367 for(m=0;m<mineNumber[i];m++)
368 {
369 distx=(*(aiMines[i]))[m].x-shot.x;
370 disty=(*(aiMines[i]))[m].y-shot.y;
371 dist=distx*distx+disty*disty;
372
373 if(dist<hit.distance)
374 {
375 hit.object=HMINE;
376 hit.playerNumber=i;
377 hit.objectNumber=m;
378 hit.hitTime=basetime*frames;
379 hit.distance=dist;
380 }
381 }
382 }
383 if(hit.distance<100)
384 hitfound=true;
385 basetime++;
386 }
387 }
388
389 return hit;
390}
391
392void Ai::testForHits()
393{
394 AiSprite shot;
395 int i,j;
396 int m,p;
397 BulletSprite *bullet;
398 Hit *h;
399 Hit hit;
400 bool hitfound=false;
401 double distance,dx,dy;
402 int listsize; // used for caching QtList::size()
403
404 if(calculateCollisions>0)
405 {
406 calculateCollisions--;
407 i=0;
408 listsize = objectsHitByShip.size();
409 while(i<listsize)
410 {
411 h = objectsHitByShip[i];
412 if(h->hitTime>0)
413 {
414 h->hitTime--;
415 i++;
416 }
417 else
418 {
419 objectsHitByShip.removeAt(i);
420 delete h;
421 listsize--;
422 }
423 }
424 i=0;
425 listsize = minesHitByShot.size();
426 while(i<listsize)
427 {
428 h=minesHitByShot[i];
429 if(h->hitTime>0)
430 {
431 h->hitTime--;
432 i++;
433 }
434 else
435 {
436 minesHitByShot.removeAt(i);
437 delete h;
438 listsize--;
439 }
440 }
441 }
442 else
443 {
444 qDeleteAll(objectsHitByShip);
445 objectsHitByShip.clear();
446 qDeleteAll(minesHitByShot);
447 minesHitByShot.clear();
448 for(i=0;i<2;i++)
449 {
450 listsize = bullets[i]->size();
451 for (j=0; j<listsize; j++)
452 {
453 bullet=bullets[i]->value(j);
454 shot=bullet->toAiSprite();
455 hit=firstObject(shot,0,calcFrameIncrement[Options::aiDifficulty(playerNumber)]);
456 if(hit.object==HMINE)
457 {
458 h=new Hit(hit);
459 minesHitByShot.append(h);
460 }
461 if((hit.object==HSHIP)&&(hit.playerNumber==playerNumber))
462 {
463 h=new Hit(hit);
464 h->object=HSHOT;
465 objectsHitByShip.append(h);
466 }
467 }
468 }
469
470 hit.object=HNOTHING;
471 hit.distance=400;
472
473 for(i=0;(i<shipsNextPositions[0]->size()) &&
474 !(*shipsNextPositions[playerNumber])[i].sun;i++)
475 {
476 if((borderTime<0) && (*shipsNextPositions[playerNumber])[i].border)
477 borderTime=i*calcFrameIncrement[Options::aiDifficulty(playerNumber)];
478
479 dx=(*shipsNextPositions[playerNumber])[i].x;
480 dy=(*shipsNextPositions[playerNumber])[i].y;
481 distance=dx*dx+dy*dy;
482 if((distance<3025)&&(sunTime<0))
483 sunTime=i*calcFrameIncrement[Options::aiDifficulty(playerNumber)];
484
485 if(!hitfound)
486 for(p=0;p<2;p++)
487 for(m=0;m<mineNumber[p];m++)
488 {
489 dx=(*shipsNextPositions[playerNumber])[i].x-(*aiMines[p])[m].x;
490 dy=(*shipsNextPositions[playerNumber])[i].y-(*aiMines[p])[m].y;
491 distance=dx*dx+dy*dy;
492 if(hit.distance>distance)
493 {
494 hit.object=HMINE;
495 hit.playerNumber=p;
496 hit.objectNumber=m;
497 hit.hitTime=i*calcFrameIncrement[Options::aiDifficulty(playerNumber)];
498 hit.distance=distance;
499 if(distance<100)
500 hitfound=true;
501 }
502 }
503 }
504 if(hit.object!=HNOTHING)
505 {
506 h=new Hit(hit);
507 objectsHitByShip.append(h);
508 }
509 calculateCollisions=(int)(calcCollisions[Options::aiDifficulty(playerNumber)]/cfg->gamespeed);
510 }
511}
512
513void Ai::shotScores()
514{
515 Shot *s;
516 Hit *h,*mh;
517 bool found,foundmh;
518 double dist,dx,dy,fuel;
519 int i,j,k;
520 int listsize,listsize2; // used for caching QtList::size()
521
522
523 dx=(*shipsNextPositions[playerNumber])[0].x-(*shipsNextPositions[opponentNumber])[0].x;
524 dy=(*shipsNextPositions[playerNumber])[0].y-(*shipsNextPositions[opponentNumber])[0].y;
525 dist=dx*dx+dy*dy;
526
527 listsize = myShots.size();
528 for (j=0; j<listsize; j++)
529 {
530 s = myShots[j];
531 fuel=(100-(ship[playerNumber]->getEnergy()-cfg->shotEnergyNeed));
532 s->score=fuel*fuel/10 + s->hit.distance+s->hit.hitTime;
533 if(dist > (75*75))
534 s->score+=waitShot*8;
535 else
536 s->score+=waitShot*4;
537
538 if(s->hit.object==HMINE)
539 {
540 found=false;
541 for (i=0; i<objectsHitByShip.size() && !found; i++)
542 {
543 h=objectsHitByShip[i];
544 if((h->object==HMINE)&&(h->playerNumber==s->hit.playerNumber)
545 &&(h->objectNumber==s->hit.objectNumber))
546 //ship will hit a mine that will be hitten by the shot
547 {
548 found=true;
549 //ship hits earlier than shot
550 if(h->hitTime<s->hit.hitTime)
551 s->score+=1000;
552 else
553 {
554 foundmh=false;
555 listsize2 = minesHitByShot.size();
556 for (k=0; k<listsize2 && !foundmh; k++)
557 {
558 mh = minesHitByShot[k];
559 if((mh->playerNumber==s->hit.playerNumber)
560 &&(mh->objectNumber==s->hit.objectNumber))
561 //another shot will hit the mine
562 {
563 /* FIXME: check (and understand) this function
564 original version: "found" in for-clause, bot was never true
565 foundmh set to false before but never changed */
566 foundmh = true;
567 if(mh->hitTime<s->hit.hitTime)
568 s->score+=500;
569 else
570 s->score-=300;
571 }
572 }
573 }
574 }
575 }
576 if(!found)
577 s->score+=1000;
578 }
579 }
580}
581
582void Ai::chooseAction()
583{
584 double bestScore=1e10;
585 Shot *bestShot=NULL,*s;
586 AiSprite actualpos;
587 double posangle,movephi,phiright,phileft,torotate=0,velangle;
588 int framesleft,framesright;
589 bool rotateAndAccelerate=false;
590 Hit *nextHit=0;
591 int shotHitTime;
592 int i;
593
594 shotHitTime=1000000;
595 nextHit=0;
596/* for(h=objectsHitByShip.first();h;h=objectsHitByShip.next())
597 if(h->object==HSHOT)
598 if(h->hitTime<shotHitTime)
599 {
600 nextHit=h;
601 shotHitTime=h->hitTime;
602 }*/
603 if((borderTime>0) || (sunTime>0) || (nextHit))
604 {
605 actualpos=ship[playerNumber]->toAiSprite();
606 posangle=rectToAngle(actualpos.x,actualpos.y);
607
608 movephi=rectToAngle((*shipsNextPositions[playerNumber])[0].x,
609 (*shipsNextPositions[playerNumber])[0].y) - posangle;
610
611 phileft=movephi+cfg->rotationSpeed;
612 phiright=movephi-cfg->rotationSpeed;
613
614 if((borderTime>0)&& !((sunTime>0)&&(sunTime<borderTime)))
615 {
616 bestScore=borderTime/cfg->gamespeed;
617 if(score>bestScore)
618 {
619 velangle=rectToAngle(actualpos.dx,actualpos.dy);
620 if(fabs(difference(posangle+3*M_PI/4,velangle))
621 < fabs(difference(posangle-3*M_PI/4,velangle)))
622 torotate=posangle-3*M_PI/4-ship[playerNumber]->getRotation();
623 else
624 torotate=posangle+3*M_PI/4-ship[playerNumber]->getRotation();
625 rotateAndAccelerate=true;
626 score=bestScore;
627 accelerateFramesNumber=(int)(8/cfg->gamespeed);
628 }
629 }
630 else if(sunTime>0)
631 {
632 bestScore=sunTime/(cfg->gamespeed*10)
633 +(actualpos.x*actualpos.x+actualpos.y*actualpos.y)/5000;
634 if(score>bestScore)
635 {
636 velangle=rectToAngle(actualpos.dx,actualpos.dy);
637 if(fabs(difference(posangle+2*M_PI/5,velangle))
638 < fabs(difference(posangle-2*M_PI/5,velangle)))
639 torotate=posangle+2*M_PI/5-ship[playerNumber]->getRotation();
640 else
641 torotate=posangle-2*M_PI/5-ship[playerNumber]->getRotation();
642 rotateAndAccelerate=true;
643 score=bestScore;
644 accelerateFramesNumber=(int)(8/cfg->gamespeed);
645 }
646 }
647 else
648 {
649/* bestScore=abs(nextHit->hitTime-90)*4/cfg->gamespeed + nextHit->distance*2
650 + (100-(ship[playerNumber]->getEnergy()-cfg->shotEnergyNeed))*4;
651 if((score>bestScore)&&(bestScore<400))
652 {
653 velangle=rectToAngle(actualpos.dx,actualpos.dy);
654 if(fabs(difference(posangle+2*M_PI/5,velangle))
655 < fabs(difference(posangle-2*M_PI/5,velangle)))
656 torotate=posangle+2*M_PI/5-ship[playerNumber]->getRotation();
657 else
658 torotate=posangle-2*M_PI/5-ship[playerNumber]->getRotation();
659 rotateAndAccelerate=true;
660 score=bestScore;
661 accelerateFramesNumber=(int)(4/cfg->gamespeed);
662 }*/
663 }
664
665 if(rotateAndAccelerate)
666 {
667 if(phileft<0)
668 framesleft=1000;
669 else
670 {
671 while(torotate<0)
672 torotate+=2*M_PI;
673 while(torotate>=2*M_PI)
674 torotate-=2*M_PI;
675 framesleft=(int)(torotate/phileft+0.5);
676 }
677
678 if(phiright>0)
679 framesright=1000;
680 else
681 {
682 while(torotate>0)
683 torotate-=2*M_PI;
684 while(torotate<=-2*M_PI)
685 torotate+=2*M_PI;
686 framesright=(int)(torotate/phiright+0.5);
687 }
688
689 if(framesright<framesleft)
690 {
691 rotation=RRIGHT;
692 rotateFramesNumber=framesright;
693 }
694 else
695 {
696 rotation=RLEFT;
697 rotateFramesNumber=framesleft;
698 }
699 shoot=false;
700
701 }
702 }
703 else
704 {
705 int listsize; // used for caching QtList::size()
706 bestShot=0;
707 listsize = myShots.size();
708 for (i=0; i<listsize; i++)
709 {
710 s=myShots[i];
711 if(s->score<bestScore)
712 {
713 bestScore=s->score;
714 bestShot=s;
715 }
716 }
717 if(bestShot)
718 {
719 if((bestScore<score)&&(bestScore<400))
720 {
721 rotation=bestShot->rotation;
722 rotateFramesNumber=bestShot->rotationFrames;
723 accelerateFramesNumber=0;
724 shoot=true;
725 score=bestScore;
726 calculateCollisions = 0;
727 waitShot=(int) rint( random.getDouble() *
728 calcNextShot[Options::aiDifficulty(playerNumber)]
729 /cfg->gamespeed);
730 }
731 }
732 }
733}
734
735void Ai::setSpriteFieldSize()
736{
737 sfwidth=(double)(ship[playerNumber]->spriteFieldWidth());
738 sfheight=(double)(ship[playerNumber]->spriteFieldHeight());
739 sfwidth_2=sfwidth/2.0;
740 sfheight_2=sfheight/2.0;
741}
742
743