diff options
Diffstat (limited to 'kspaceduel/ai.cpp')
-rw-r--r-- | kspaceduel/ai.cpp | 680 |
1 files changed, 680 insertions, 0 deletions
diff --git a/kspaceduel/ai.cpp b/kspaceduel/ai.cpp new file mode 100644 index 00000000..5a7bec20 --- /dev/null +++ b/kspaceduel/ai.cpp @@ -0,0 +1,680 @@ +#include <math.h> + +#include "ai.h" +#include "mathroutines.h" +#include "options.h" + +int Ai::calcFrameIncrement[Options::EnumAiDifficulty::COUNT] = {15,8,6,2}; +int Ai::calcPositionNumber[Options::EnumAiDifficulty::COUNT] = {10,15,20,60}; +int Ai::calcShotDirections[Options::EnumAiDifficulty::COUNT] = {4,7,10,12}; +int Ai::calcCollisions[Options::EnumAiDifficulty::COUNT] = {30,15,10,10}; +int Ai::calcNextShot[Options::EnumAiDifficulty::COUNT] = {300,200,90,60}; + +Ai::Ai(int pn,ShipSprite* s[2],QPtrList<BulletSprite>* b[2], + QPtrList<MineSprite>* m[2],SConfig *c) +{ + int i; + + playerNumber=pn; + opponentNumber=(pn+1)%2; + cfg=c; + + for(i=0;i<2;i++) + { + ship[i]=s[i]; + bullets[i]=b[i]; + mines[i]=m[i]; + shipsNextPositions[i]=new QMemArray<AiSprite> + ((int)(calcPositionNumber[Options::aiDifficulty(playerNumber)]/cfg->gamespeed)); + aiMines[i]=new QMemArray<AiSprite>(cfg->maxMines); + mineNumber[i]=0; + } + myShots.setAutoDelete(true); + objectsHitByShip.setAutoDelete(true); + minesHitByShot.setAutoDelete(true); +} + +void Ai::newRound() +{ + accelerateFramesNumber=0; + rotateFramesNumber=0; + shoot=false; + score=1e10; + + rotation=RNONE; + acc=false; + bullet=false; + mine=false; + + borderTime=-1; + sunTime=-1; + + calculateCollisions=(int)(calcCollisions[Options::aiDifficulty(playerNumber)] + /cfg->gamespeed); + waitShot=(int) rint( random.getDouble() * + calcNextShot[Options::aiDifficulty(playerNumber)] + /cfg->gamespeed); + + myShots.clear(); + objectsHitByShip.clear(); + minesHitByShot.clear(); +} + +void Ai::think() +{ + setSpriteFieldSize(); + + myShots.clear(); + borderTime=-1; + sunTime=-1; + score--; + if(waitShot>0) + waitShot--; + + calculateNextPositions(); + if(Options::aiDifficulty(playerNumber)!=Options::EnumAiDifficulty::Trainee) + testForHits(); + if(waitShot<=0) + { + tryShots(); + shotScores(); + } + chooseAction(); + + + if(rotateFramesNumber<=0) + { + rotation=RNONE; + if(accelerateFramesNumber<=0) + { + acc=false; + if(shoot) + { + bullet=true; + shoot=false; + } + else + bullet=false; + score=1e10; + } + else + { + acc=true; + accelerateFramesNumber--; + } + } + else + rotateFramesNumber--; + +} + + +AiSprite Ai::nextPosition(AiSprite sp,double mult) +{ + double abs_2,nx,ny,sq,eg; + if(!sp.sun) + { + abs_2=sp.x*sp.x+sp.y*sp.y; + if(abs_2<1) + abs_2=1; + sq=sqrt(abs_2); + nx=sp.x/sq; + ny=sp.y/sq; + eg=cfg->gravity*mult; + sp.dx-=eg*nx/abs_2; + sp.dy-=eg*ny/abs_2; + + sp.x+=sp.dx*mult; + sp.y+=sp.dy*mult; + + if(sp.x*sp.x+sp.y*sp.y<1600) + sp.sun=true; + else + { + //simple bounds actions + if(sp.x>sfwidth_2) + { + sp.x-=sfwidth; + sp.border=true; + } + else if(sp.x<-sfwidth_2) + { + sp.x+=sfwidth; + sp.border=true; + } + if(sp.y>sfheight_2) + { + sp.y-=sfheight; + sp.border=true; + } + else if(sp.y<-sfheight_2) + { + sp.y+=sfheight; + sp.border=true; + } + } + } + + return sp; +} + +void Ai::nextPositions(AiSprite sp,QMemArray<AiSprite> *a,int frames) +{ + int i,num; + double fmult=cfg->gamespeed*frames; + + (*a)[0]=nextPosition(sp,cfg->gamespeed); + num=a->size(); + for(i=1;i<num;i++) + (*a)[i]=nextPosition((*a)[i-1],fmult); +} + +void Ai::calculateNextPositions() +{ + unsigned int i,j; + MineSprite *ms; + + j=(int)(calcPositionNumber[Options::aiDifficulty(playerNumber)]/cfg->gamespeed); + + if(shipsNextPositions[0]->size() != j) + for(i=0;i<2;i++) + shipsNextPositions[i]->resize(j); + + for(i=0;i<2;i++) + nextPositions(ship[i]->toAiSprite(),shipsNextPositions[i], + calcFrameIncrement[Options::aiDifficulty(playerNumber)]); + + if(cfg->maxMines > aiMines[0]->size()) + for(i=0;i<2;i++) + aiMines[i]->resize(cfg->maxMines); + + for(i=0;i<2;i++) + { + j=0; + ms=mines[i]->first(); + while(ms) + { + (*(aiMines[i]))[j]=ms->toAiSprite(); + ms=mines[i]->next(); + j++; + } + mineNumber[i]=j; + } +} + +void Ai::tryShots() +{ + AiSprite shot,me; + double rot,nr,nx,ny; + int i,f,frameIncrement,frameNum; + Hit hit; + Shot *goodShot; + + me=ship[playerNumber]->toAiSprite(); + rot=ship[playerNumber]->getRotation(); + + //Each 'frameIncrement' frames a shot is tried + frameIncrement=(int)((2*M_PI/calcShotDirections[Options::aiDifficulty(playerNumber)]) + /cfg->rotationSpeed); + if(frameIncrement==0) + frameIncrement=1; + //Number of frames needed to rotate 180 degrees + frameNum=(int)(M_PI/(frameIncrement*cfg->rotationSpeed)); + + //if too much bullets are on the playfield, no shot is tried + if(bullets[playerNumber]->count() < + (cfg->maxBullets+ship[playerNumber]->getBulletPowerups())) + { + for(f=0;f<=frameNum;f++) + { + if(f!=0) + for(i=0;i<frameIncrement;i++) + me=nextPosition(me,cfg->gamespeed); + else + me=nextPosition(me,cfg->gamespeed); + + if(!ship[playerNumber]->reloadsBullet(f*frameIncrement*cfg->gamespeed)) + { + for(i=0;i<2;i++) + { + if((f==0)&&(i==1)) + continue; + if(i==0) + nr=rot+frameIncrement*f*cfg->rotationSpeed; + else + nr=rot-frameIncrement*f*cfg->rotationSpeed; + + nx=cos(nr); + ny=sin(nr); + shot.x=me.x+nx*SHOTDIST; + shot.y=me.y+ny*SHOTDIST; + shot.dx=me.dx+nx*cfg->shotSpeed; + shot.dy=me.dy+ny*cfg->shotSpeed; + shot.sun=false; + shot.border=false; + + hit=firstObject(shot,f*frameIncrement, + calcFrameIncrement[Options::aiDifficulty(playerNumber)]); + if((hit.object!=HNOTHING) && + !((hit.object==HSHIP)&&(hit.playerNumber==playerNumber))) + { + goodShot=new Shot; + goodShot->hit=hit; + goodShot->rotation=(i==0?RLEFT:RRIGHT); + goodShot->rotationFrames=f*frameIncrement; + goodShot->score=1e10; + myShots.append(goodShot); + } + } + } + } + } +} + +Hit Ai::firstObject(AiSprite shot,int time,int frames) +{ + int optime,i,num,rtime,basetime,t,m; + double dist,distx,disty,shiplastdist=0; + bool shipdistgreater=true,hitfound=false; + Hit hit={HNOTHING,0,0,0,1e10}; + + basetime=time/frames; + if((time%frames)>0) + basetime++; + rtime=basetime*frames-time; + optime=shipsNextPositions[0]->size(); + + num=optime-basetime; + + if(num>0) + { + for(t=0;(t<num)&&(!hitfound)&&(!shot.sun);t++) + { + if(t==0) + shot=nextPosition(shot,cfg->gamespeed*rtime); + else + shot=nextPosition(shot,cfg->gamespeed*frames); + + //distance to other objects + for(i=0;i<2;i++) + { + distx=(*(shipsNextPositions[i]))[basetime].x-shot.x; + disty=(*(shipsNextPositions[i]))[basetime].y-shot.y; + dist=distx*distx+disty*disty; + //own ship + if(i==playerNumber) + { + if(dist<shiplastdist) + shipdistgreater=false; + if((!shipdistgreater)&&(dist<hit.distance)) + { + hit.object=HSHIP; + hit.objectNumber=0; + hit.playerNumber=i; + hit.hitTime=basetime*frames; + hit.distance=dist; + } + shiplastdist=dist; + } + //other ship + else if(dist<hit.distance) + { + hit.object=HSHIP; + hit.objectNumber=0; + hit.playerNumber=i; + hit.hitTime=basetime*frames; + hit.distance=dist; + } + + //mines + for(m=0;m<mineNumber[i];m++) + { + distx=(*(aiMines[i]))[m].x-shot.x; + disty=(*(aiMines[i]))[m].y-shot.y; + dist=distx*distx+disty*disty; + + if(dist<hit.distance) + { + hit.object=HMINE; + hit.playerNumber=i; + hit.objectNumber=m; + hit.hitTime=basetime*frames; + hit.distance=dist; + } + } + } + if(hit.distance<100) + hitfound=true; + basetime++; + } + } + + return hit; +} + +void Ai::testForHits() +{ + AiSprite shot; + unsigned int i; + int m,p; + BulletSprite *bullet; + Hit *h; + Hit hit; + bool hitfound=false; + double distance,dx,dy; + + if(calculateCollisions>0) + { + calculateCollisions--; + h=objectsHitByShip.first(); + while(h) + { + if(h->hitTime>0) + { + h->hitTime--; + h=objectsHitByShip.next(); + } + else + { + objectsHitByShip.remove(); + h=objectsHitByShip.current(); + } + } + h=minesHitByShot.first(); + while(h) + { + if(h->hitTime>0) + { + h->hitTime--; + h=minesHitByShot.next(); + } + else + { + minesHitByShot.remove(); + h=minesHitByShot.current(); + } + } + } + else + { + objectsHitByShip.clear(); + minesHitByShot.clear(); + for(i=0;i<2;i++) + { + for(bullet=bullets[i]->first();bullet;bullet=bullets[i]->next()) + { + shot=bullet->toAiSprite(); + hit=firstObject(shot,0,calcFrameIncrement[Options::aiDifficulty(playerNumber)]); + if(hit.object==HMINE) + { + h=new Hit(hit); + minesHitByShot.append(h); + } + if((hit.object==HSHIP)&&(hit.playerNumber==playerNumber)) + { + h=new Hit(hit); + h->object=HSHOT; + objectsHitByShip.append(h); + } + } + } + + hit.object=HNOTHING; + hit.distance=400; + + for(i=0;(i<shipsNextPositions[0]->size()) && + !(*shipsNextPositions[playerNumber])[i].sun;i++) + { + if((borderTime<0) && (*shipsNextPositions[playerNumber])[i].border) + borderTime=i*calcFrameIncrement[Options::aiDifficulty(playerNumber)]; + + dx=(*shipsNextPositions[playerNumber])[i].x; + dy=(*shipsNextPositions[playerNumber])[i].y; + distance=dx*dx+dy*dy; + if((distance<3025)&&(sunTime<0)) + sunTime=i*calcFrameIncrement[Options::aiDifficulty(playerNumber)]; + + if(!hitfound) + for(p=0;p<2;p++) + for(m=0;m<mineNumber[p];m++) + { + dx=(*shipsNextPositions[playerNumber])[i].x-(*aiMines[p])[m].x; + dy=(*shipsNextPositions[playerNumber])[i].y-(*aiMines[p])[m].y; + distance=dx*dx+dy*dy; + if(hit.distance>distance) + { + hit.object=HMINE; + hit.playerNumber=p; + hit.objectNumber=m; + hit.hitTime=i*calcFrameIncrement[Options::aiDifficulty(playerNumber)]; + hit.distance=distance; + if(distance<100) + hitfound=true; + } + } + } + if(hit.object!=HNOTHING) + { + h=new Hit(hit); + objectsHitByShip.append(h); + } + calculateCollisions=(int)(calcCollisions[Options::aiDifficulty(playerNumber)]/cfg->gamespeed); + } +} + +void Ai::shotScores() +{ + Shot *s; + Hit *h,*mh; + bool found,foundmh; + double dist,dx,dy,fuel; + + + dx=(*shipsNextPositions[playerNumber])[0].x-(*shipsNextPositions[opponentNumber])[0].x; + dy=(*shipsNextPositions[playerNumber])[0].y-(*shipsNextPositions[opponentNumber])[0].y; + dist=dx*dx+dy*dy; + + for(s=myShots.first();s;s=myShots.next()) + { + fuel=(100-(ship[playerNumber]->getEnergy()-cfg->shotEnergyNeed)); + s->score=fuel*fuel/10 + s->hit.distance+s->hit.hitTime; + if(dist > (75*75)) + s->score+=waitShot*8; + else + s->score+=waitShot*4; + + if(s->hit.object==HMINE) + { + found=false; + for(h=objectsHitByShip.first();h && !found;h=objectsHitByShip.next()) + { + if((h->object==HMINE)&&(h->playerNumber==s->hit.playerNumber) + &&(h->objectNumber==s->hit.objectNumber)) + //ship will hit a mine that will be hitten by the shot + { + found=true; + //ship hits earlier then shot + if(h->hitTime<s->hit.hitTime) + s->score+=1000; + else + { + foundmh=false; + for(mh=minesHitByShot.first();mh && !foundmh;mh=minesHitByShot.next()) + { + if((mh->playerNumber==s->hit.playerNumber) + &&(mh->objectNumber==s->hit.objectNumber)) + //another shot will hit the mine + { + if(mh->hitTime<s->hit.hitTime) + s->score+=500; + else + s->score-=300; + } + } + } + } + } + if(!found) + s->score+=1000; + } + } +} + +void Ai::chooseAction() +{ + double bestScore=1e10; + Shot *bestShot=NULL,*s; + AiSprite actualpos; + double posangle,movephi,phiright,phileft,torotate=0,velangle; + int framesleft,framesright; + bool rotateAndAccelerate=false; + Hit *nextHit=0; + int shotHitTime; + + + shotHitTime=1000000; + nextHit=0; +/* for(h=objectsHitByShip.first();h;h=objectsHitByShip.next()) + if(h->object==HSHOT) + if(h->hitTime<shotHitTime) + { + nextHit=h; + shotHitTime=h->hitTime; + }*/ + + if((borderTime>0) || (sunTime>0) || (nextHit)) + { + actualpos=ship[playerNumber]->toAiSprite(); + posangle=rectToAngle(actualpos.x,actualpos.y); + + movephi=rectToAngle((*shipsNextPositions[playerNumber])[0].x, + (*shipsNextPositions[playerNumber])[0].y) - posangle; + + phileft=movephi+cfg->rotationSpeed; + phiright=movephi-cfg->rotationSpeed; + + if((borderTime>0)&& !((sunTime>0)&&(sunTime<borderTime))) + { + bestScore=borderTime/cfg->gamespeed; + if(score>bestScore) + { + velangle=rectToAngle(actualpos.dx,actualpos.dy); + if(fabs(difference(posangle+3*M_PI/4,velangle)) + < fabs(difference(posangle-3*M_PI/4,velangle))) + torotate=posangle-3*M_PI/4-ship[playerNumber]->getRotation(); + else + torotate=posangle+3*M_PI/4-ship[playerNumber]->getRotation(); + rotateAndAccelerate=true; + score=bestScore; + accelerateFramesNumber=(int)(8/cfg->gamespeed); + } + } + else if(sunTime>0) + { + bestScore=sunTime/(cfg->gamespeed*10) + +(actualpos.x*actualpos.x+actualpos.y*actualpos.y)/5000; + if(score>bestScore) + { + velangle=rectToAngle(actualpos.dx,actualpos.dy); + if(fabs(difference(posangle+2*M_PI/5,velangle)) + < fabs(difference(posangle-2*M_PI/5,velangle))) + torotate=posangle+2*M_PI/5-ship[playerNumber]->getRotation(); + else + torotate=posangle-2*M_PI/5-ship[playerNumber]->getRotation(); + rotateAndAccelerate=true; + score=bestScore; + accelerateFramesNumber=(int)(8/cfg->gamespeed); + } + } + else + { +/* bestScore=abs(nextHit->hitTime-90)*4/cfg->gamespeed + nextHit->distance*2 + + (100-(ship[playerNumber]->getEnergy()-cfg->shotEnergyNeed))*4; + if((score>bestScore)&&(bestScore<400)) + { + velangle=rectToAngle(actualpos.dx,actualpos.dy); + if(fabs(difference(posangle+2*M_PI/5,velangle)) + < fabs(difference(posangle-2*M_PI/5,velangle))) + torotate=posangle+2*M_PI/5-ship[playerNumber]->getRotation(); + else + torotate=posangle-2*M_PI/5-ship[playerNumber]->getRotation(); + rotateAndAccelerate=true; + score=bestScore; + accelerateFramesNumber=(int)(4/cfg->gamespeed); + }*/ + } + + if(rotateAndAccelerate) + { + if(phileft<0) + framesleft=1000; + else + { + while(torotate<0) + torotate+=2*M_PI; + while(torotate>=2*M_PI) + torotate-=2*M_PI; + framesleft=(int)(torotate/phileft+0.5); + } + + if(phiright>0) + framesright=1000; + else + { + while(torotate>0) + torotate-=2*M_PI; + while(torotate<=-2*M_PI) + torotate+=2*M_PI; + framesright=(int)(torotate/phiright+0.5); + } + + if(framesright<framesleft) + { + rotation=RRIGHT; + rotateFramesNumber=framesright; + } + else + { + rotation=RLEFT; + rotateFramesNumber=framesleft; + } + shoot=false; + + } + } + else + { + + bestShot=0; + for(s=myShots.first();s;s=myShots.next()) + if(s->score<bestScore) + { + bestScore=s->score; + bestShot=s; + } + if(bestShot) + { + if((bestScore<score)&&(bestScore<400)) + { + rotation=bestShot->rotation; + rotateFramesNumber=bestShot->rotationFrames; + accelerateFramesNumber=0; + shoot=true; + score=bestScore; + calculateCollisions = 0; + waitShot=(int) rint( random.getDouble() * + calcNextShot[Options::aiDifficulty(playerNumber)] + /cfg->gamespeed); + } + } + } +} + +void Ai::setSpriteFieldSize() +{ + sfwidth=(double)(ship[playerNumber]->spriteFieldWidth()); + sfheight=(double)(ship[playerNumber]->spriteFieldHeight()); + sfwidth_2=sfwidth/2.0; + sfheight_2=sfheight/2.0; +} + |