程序设计II上机实验10A:杀蚂蚁

杀蚂蚁

Time limit: 3000 ms

Memory limit: 256 MB

Standard I/O

Content

最近,佳佳迷上了一款好玩的小游戏:antbuster。

游戏规则非常简单:在一张地图上,左上角是蚂蚁窝,右下角是蛋糕,蚂蚁会源源不断地从窝里爬出来,试图把蛋糕搬回蚂蚁窝。而你的任务,就是用原始资金以及杀蚂蚁获得的奖金造防御塔,杀掉这些试图跟你抢蛋糕的蚂蚁~

下附一张游戏截图:

  • 为了拿到尽可能高的分数,佳佳设计了很多种造塔的方案,但在尝试了其中的一小部分后,佳佳发现,这个游戏实在是太费时间了。为了节省时间,佳佳决定写个程序,对于每一种方案,模拟游戏进程,根据效果来判断方案的优劣。

  • 根据自己在游戏中积累的一些经验,以及上网搜到的一些参数,佳佳猜了蚂蚁爬行的算法,并且假设游戏中的蚂蚁也是按这个规则选择路线:

    1. 每一秒钟开始的时候,蚂蚁都在平面中的某个整点上。如果蚂蚁没有扛着蛋糕,它会在该点留下2单位的信息素,否则它会留下5单位的信息素。然后蚂蚁会在正北、正南、正东、正西四个方向中选择一个爬过去。
    2. 选择方向的规则是:首先,爬完一个单位长度后到达的那个点上,不能有其他蚂蚁或是防御塔,并且那个点不能是蚂蚁上一秒所在的点(除非上一个时刻蚂蚁就被卡住,且这个时刻它仍无法动),当然,蚂蚁也不会爬出地图的边界(我们定义这些点为不可达点)。如果此时有多个选择,蚂蚁会选择信息素最多的那个点爬过去。
    3. 如果此时仍有多种选择,蚂蚁先面向正东,如果正东不是可选择的某个方向,它会顺时针转90°,再次判断,如果还不是,再转90°…直到找到可以去的方向。
    4. 如果将每只蚂蚁在洞口出现的时间作为它的活动时间的第1秒,那么每当这只蚂蚁的活动时间秒数为5的倍数的时候,它先按规则1~3确定一个方向,面对该方向后逆时针转90°,若它沿当前方向会走到一个不可达点,它会不停地每次逆时针转90°,直到它面对着一个可达的点,这样定下的方向才是蚂蚁最终要爬去的方向。
    5. 如果蚂蚁的四周都是不可达点,那么蚂蚁在这一秒内会选择停留在当前点。下一秒判断移动方向时,它上一秒所在点为其当前停留的点。
    6. 你可以认为蚂蚁在选定方向后,瞬间移动到它的目标点,这一秒钟剩下的时间里,它就停留在目标点。
    7. 蚂蚁按出生的顺序移动,出生得比较早的蚂蚁先移动。
  • 然后,是一些有关地图的信息:

    1. 每一秒,地图所有点上的信息素会损失1单位,如果那个点上有信息素的话。
    2. 地图上某些地方是炮台。炮台的坐标在输入中给出。
    3. 地图的长、宽在输入中给出,对于n * m的地图,它的左上角坐标为(0,0),右下角坐标为(n,m)。蚂蚁洞的位置为(0,0),蛋糕的位置为(n,m)。
    4. 你可以把蚂蚁看做一个直径为1单位的圆,圆心位于蚂蚁所在的整点。
    5. 游戏开始时,地图上没有蚂蚁,每个点上的信息素含量均为0。
  • 一些有关炮塔的信息:

    1. 炮塔被放置在地图上的整点处。
    2. 为了简单一些,我们认为这些炮塔都是激光塔。激光塔的射速是1秒/次,它的攻击伤害为d/次,攻击范围为r。你可以认为每秒蚂蚁移动完毕后,塔才开始攻击。并且,只有当代表蚂蚁的圆的圆心与塔的直线距离不超过r时,塔才算打得到那只蚂蚁。
    3. 如果一只蚂蚁扛着蛋糕,那么它会成为target,也就是说,任何打得到它的塔的炮口都会对准它。如果蛋糕好好地呆在原位,那么每个塔都会挑离它最近的蚂蚁进行攻击,如果有多只蚂蚁,它会选出生较早的一只。
    4. 激光塔有个比较奇怪的特性:它在选定了打击目标后,只要目标在其射程内,塔到目标蚂蚁圆心的连线上的所有蚂蚁(这里“被打到”的判定变成了表示激光的线段与表示蚂蚁的圆有公共点)都会被打到并损d格血,但激光不会穿透它的打击目标打到后面的蚂蚁。
    5. 尽管在真实游戏中,塔是可以升级的,但在这里我们认为塔的布局和等级就此定了下来,不再变动。
  • 再介绍一下蚂蚁窝:

    1. 如果地图上的蚂蚁不足6只,并且洞口没有蚂蚁,那么窝中每秒会爬出一只蚂蚁,直到地图上的蚂蚁数为6只。
    2. 刚出生的蚂蚁站在洞口。
    3. 每只蚂蚁有一个级别,级别决定了蚂蚁的血量,级别为k的蚂蚁的血量为int(4*1.1^k)(int(x)表示对x取下整)。每被塔打一次,蚂蚁的血减少d。注意,血量为0的蚂蚁仍能精力充沛地四处乱爬,只有一只蚂蚁的血被打成负数时,它才算挂了。
    4. 蚂蚁的级别是这样算的:前6只出生的蚂蚁是1级,第7~12只是2级,依此类推。
  • 最后给出关于蛋糕的介绍:

    1. 简单起见,你可以认为此时只剩最后一块蛋糕了。如果有蚂蚁走到蛋糕的位置,并且此时蛋糕没有被扛走,那么这只蚂蚁就扛上了蛋糕。蚂蚁被打死后蛋糕归位。
    2. 如果一只扛着蛋糕的蚂蚁走到蚂蚁窝的位置,我们就认为蚂蚁成功抢到了蛋糕,游戏结束。
    3. 蚂蚁扛上蛋糕时,血量会增加int(该蚂蚁出生时血量 / 2),但不会超过上限。
  • 整理一下1秒钟内发生的事件:

    • 1秒的最初,如果地图上蚂蚁数不足6,一只蚂蚁就会在洞口出生。
    • 接着,蚂蚁们在自己所在点留下一些信息素后,考虑移动。先出生的蚂蚁先移动。
    • 移动完毕后,如果有蚂蚁在蛋糕的位置上并且蛋糕没被拿走,它把蛋糕扛上,血量增加,并在这时被所有塔设成target。
    • 然后所有塔同时开始攻击。如果攻击结束后那只扛着蛋糕的蚂蚁挂了,蛋糕瞬间归位。
    • 攻击结束后,如果发现扛蛋糕的蚂蚁没死并在窝的位置,就认为蚂蚁抢到了蛋糕。游戏也在此时结束。
    • 最后,地图上所有点的信息素损失1单位。所有蚂蚁的年龄加1。
    • 漫长的1秒到此结束。

Input Description

输入的第一行是2个用空格隔开的整数,n、m,分别表示了地图的长和宽。

第二行是3个用空格隔开的整数,s、d、r,依次表示炮塔的个数、单次攻击伤害以及攻击范围。

接下来s行,每行是2个用空格隔开的整数x、y,描述了一个炮塔的位置。当然,蚂蚁窝的洞口以及蛋糕所在的位置上一定没有炮塔。

最后一行是一个正整数t,表示我们模拟游戏的前t秒钟。

Output Description

如果在第t秒或之前蚂蚁抢到了蛋糕,输出一行“Game over after x seconds”,其中x为游戏结束的时间,否则输出“The game is going on”。

如果游戏在t秒或之前结束,输出游戏结束时所有蚂蚁的信息,否则输出t秒后所有蚂蚁的信息。格式如下:

第一行是1个整数s,表示此时活着的蚂蚁的总数。

接下来s行,每行5个整数,依次表示一只蚂蚁的年龄(单位为秒)、等级、当前血量,以及在地图上的位置(a,b)。输出按蚂蚁的年龄递减排序。

Sample

INPUT

3 5
1 1 2
2 2
5

OUTPUT

The game is going on
5
5 1 3 1 4
4 1 3 0 4
3 1 3 0 3
2 1 3 0 2
1 1 4 0 1

Hint

样例说明:

3*5的地图,有1个单次伤害为1、攻击范围为2的激光炮塔,它的位置为(2,2),模拟游戏的前5秒。5秒内有5只蚂蚁出生,都是向东爬行,其中第1~4只在路过(0,2)点时被激光塔伤了1格血。在第5秒的时候,最早出生的蚂蚁按移动规则1~3本来该向东移动,但由于规则4的作用,它在发现向北和向西移动都会到达不可达点后,最终选择了向南移动。

数据说明:

100%的数据满足1 ≤ n,m ≤ 8,s ≤ 20,t ≤ 200,000

Code

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#define CANNON 1
#define ANT 2
//#define DEBUG 0
struct antdata{
    int age;
    int id;//蚂蚁的id,从1开始不重复,往上递增,为0表示不存在 
    int rank;//等级 
    int health;//血量 
    int x,y;
    int lastx,lasty;
    int cake;//0或1,是否有蛋糕 
}ant[6]={}; 
struct cannondata{
    int x,y;
};
int time=0;
int cake=-1;//记录蛋糕在哪只蚂蚁身上0-5 
int antid=0;//记录蚂蚁出生的顺序 
int antnum=0;//记录当前场上的蚂蚁数量 
int **map,**info;//记录地图上的蚂蚁/炮台,信息素 
int n,m;
int cnum,churt,cdistance;//炮台cannon数,炮台伤害,炮台攻击范围
struct cannondata *cannon;
int direction[4][2]={{0,1},{1,0},{0,-1},{-1,0}};//右下左上(东南西北,顺时针) 
int directionavailable[4]={};//记录可前进的方向 
void initialize(){
    int i,j;
    for(i=0;i<=n;i++) for(j=0;j<=m;j++) {info[i][j]=0; map[i][j]=0;}
}
void MakeAnt(){//不满6只则生成蚂蚁 
    int i;
    if(antnum<6 && map[0][0]==0){
        //for(i=0;i<6;i++) 
            i=antnum;
            //if(ant[i].id==0){
                antid++;
                ant[i].id=antid;
                ant[i].x=0;
                ant[i].y=0;
                ant[i].lastx=0;
                ant[i].lasty=0;
                ant[i].age=0;
                ant[i].rank=(antid-1)/6+1;
                ant[i].health=floor(4*pow(1.1,ant[i].rank));
                ant[i].cake=0;
            //}
            antnum++;
            map[0][0]=ANT+ant[i].id;
    }
}
void DeleteAnt(int i){//i为0-5
    int j;
    for(j=i+1;j<antnum;j++){//后面的前移 
        ant[j-1]=ant[j];
    }
    antnum--; 
    for(j=antnum;j<6;j++){//置零表示不存在 
        ant[j].id=0;

    }    
}
void GiveInfo(){//增加信息素 
    int i;
    for(i=0;i<6;i++) 
        if(ant[i].id!=0)
            if(ant[i].cake)
                info[ant[i].x][ant[i].y]+=5;
            else 
                info[ant[i].x][ant[i].y]+=2;
}
void LostInfo(){//失去信息素 
    int i,j;
    for(i=0;i<=n;i++) for(j=0;j<=m;j++) if(info[i][j]) info[i][j]--;
}
void PutAnt(int i,int x,int y){//放置蚂蚁并在地图上标记 
    ant[i].lastx=ant[i].x;
    ant[i].lasty=ant[i].y;
    map[ant[i].x][ant[i].y]=0;
#ifdef DEBUG 
    printf("AntID:%d from (%d,%d) to (%d,%d), it's time/Rank/Health is %d/%d/%d\n",ant[i].id,ant[i].x,ant[i].y,x,y,ant[i].age+1,ant[i].rank,ant[i].health);
#endif
    ant[i].x=x;
    ant[i].y=y;
    map[x][y]=ANT+ant[i].id;    
}
int SelectDirection(int i){//返回一个方向的序号,不能移动返回-1 
    int j,tx,ty,anum=0,a,amaxnum=0;
    memset(directionavailable,0,sizeof(directionavailable));
    for(j=0;j<4;j++){
        tx=ant[i].x+direction[j][0];
        ty=ant[i].y+direction[j][1];
        if(tx==ant[i].lastx && ty==ant[i].lasty) continue;
        if(tx<0 || ty<0 || tx>n || ty>m) continue;
        if(map[tx][ty]) continue;
        directionavailable[j]=1;//置为可达 
        a=j;
        anum++;//可达的数量加1 
    }
    if(anum==0) return -1; 
    if(anum==1) return a; 
    //此时有多个可达点 
    int max=-1;
    for(j=0;j<4;j++){
        if(directionavailable[j]){
            tx=ant[i].x+direction[j][0];
            ty=ant[i].y+direction[j][1];
            directionavailable[j]=info[tx][ty]+1;//将可达标志改为该方向前进一格的信息素+1,+1为了将信息素为0的与不可达点区分 
            if(directionavailable[j]>max) max=directionavailable[j];//更新最大值 
        }
    }
    for(j=0;j<4;j++){//该循环将信息素不是最大值的置零 (后来发现题意不是如此,不是最大值的仍是可达点,不该置零,这里做特殊标记-1) 
        if(directionavailable[j]){//是可达点
            if(directionavailable[j]<max){//但不是信息素最大的 
                directionavailable[j]=-1;
                //anum--;
            }
            else{//是最大值 
                a=j;// 记下最大值的下标,如果有多个,a将是最后一个最大值 
                amaxnum++;//最大值方向的数量 
            }
        }
    }
    if(amaxnum!=1){//有多个最大值     
        a=0;//东
        while(directionavailable[a]<=0){//该方向不是最大值的方向(-1),或不可达(0) 
             a++;//顺时针转90度 
        }
    }
    //在多个可达点时才考虑年龄为5的倍数的情况,因为只有1个方向可达时,逆时针多次后最终仍是该方向 
    if((ant[i].age+1)%5==0){
        do{
             a--;//逆时针转90度 
             if(a<0) a+=4;
        }while(directionavailable[a]==0);//该方向不可达 
    }
    return a;
} 
void Move(){
    int i,tx,ty,d;
    for(i=0;i<antnum;i++){
        //if(ant[i].id==0) break;//后面都是空的 
        d=SelectDirection(i); 
        if(d==-1) PutAnt(i,ant[i].x,ant[i].y);
        else {
            tx=ant[i].x+direction[d][0];
            ty=ant[i].y+direction[d][1];
            PutAnt(i,tx,ty);
        }
        if(ant[i].x==n && ant[i].y==m && cake==-1){//到达蛋糕位置 
            ant[i].cake=1; 
            cake=i;
            ant[i].health+=floor(floor(4*pow(1.1,ant[i].rank))/2);
            if(ant[i].health>(int)floor(4*pow(1.1,ant[i].rank)))
                ant[i].health=(int)floor(4*pow(1.1,ant[i].rank));
        }
    } 
}
int GetDistance2(int x1,int y1,int x2,int y2){//为了避免浮点数运算,这里计算距离的平方 
    return (x2-x1)*(x2-x1)+(y2-y1)*(y2-y1);
}
int VectorProduct2(int x1,int y1,int x2,int y2){//两向量矢积的平方 
    return (x1*y2-x2*y1)*(x1*y2-x2*y1);
}
int Mult(int x1,int y1,int x2,int y2){//两向量数量积 
    return x1*x2+y1*y2;
}
int IsInAttackArea(int x1,int y1,int x2,int y2,int x3,int y3){
    //判断以3点为圆心,半径为0.5的圆和1,2点连成的线段有无公共点(后来才注意蚂蚁的直径是1,不是半径...) 
    int tmp,tmp2;
    tmp2=(x2-x1)*(x2-x1)+(y2-y1)*(y2-y1);
    tmp=Mult(x3-x1,y3-y1,x2-x1,y2-y1);
    if(tmp<0 || tmp>tmp2){//printf("----------s1---------\n"); 
        return 0;
    }
    tmp=VectorProduct2(x3-x1,y3-y1,x2-x1,y2-y1);
    if(4*tmp>tmp2) {//printf("----------s2---------\n"); 
        return 0;
    }
    return 1;
}
void Attack(){//所有炮塔是一起攻击的,攻击结束后才判断蚂蚁生死 
    int i,j;
    for(j=0;j<cnum;j++){
        int imin=-1,target=-1;
        int d2min=9999999,d2;
        if(cake!=-1 && GetDistance2(ant[cake].x,ant[cake].y,cannon[j].x,cannon[j].y)<=cdistance*cdistance){
            target=cake;
        }
        else{
            for(i=0;i<antnum;i++){
                //if(ant[i].id==0) break;//后面都是空的 
                d2=GetDistance2(ant[i].x,ant[i].y,cannon[j].x,cannon[j].y);
                if(d2<d2min) {d2min=d2;imin=i;}
            }
            target=imin;
            if(GetDistance2(ant[target].x,ant[target].y,cannon[j].x,cannon[j].y)>cdistance*cdistance) continue;//最近的都打不到 

        }
        ant[target].health-=churt;
#ifdef DEBUG
        printf("Targer:%d,TargetAntId:%d\n",target,ant[target].id);
#endif
        for(i=0;i<antnum;i++){
            if(i==target) continue;
            if(IsInAttackArea(ant[target].x,ant[target].y,cannon[j].x,cannon[j].y,ant[i].x,ant[i].y)){
                ant[i].health-=churt;
#ifdef DEBUG
                printf("!!!!!!!!!!!!!!!!!ExtraTargetAntId:%d!!!!!!!!!!!!!!!!!!!\n",ant[i].id);
                //system("pause");
#endif
            } 
        } 
    }
}
void Judge1(){
    int i;
    for(i=0;i<antnum;i++){
        if(ant[i].health<0){
#ifdef DEBUG
            printf("AntID:%d died.\n",ant[i].id);
#endif
            map[ant[i].x][ant[i].y]=0;
            if(ant[i].cake) cake=-1;//蛋糕归位
            else if(cake!=-1) if(cake>i) cake--; 
            DeleteAnt(i);
            i--;
        }
    }
}
int Judge2(){
    if(cake!=-1) if(ant[cake].x==0 && ant[cake].y==0) return 1;
    return 0;
}
void AgeAdd(){
    int i;
    for(i=0;i<antnum;i++) ant[i].age++;
}
//-------------调试-----------

int GetHealthById(int id){
    int i;
    for(i=0;i<antnum;i++) if(ant[i].id==id) return ant[i].health;
    return -1;
} 
void PFSTATE(){
    int i,j;

    for(i=0;i<=n;i++){
        for(j=0;j<=m;j++){
            printf("[%3d]",info[i][j]);
            if(map[i][j]==CANNON) printf(" CANNON ");
            else if(map[i][j]>ANT) printf("%4d(%2d)",map[i][j]-ANT,GetHealthById(map[i][j]-ANT));
            else printf("|------|");
        }
        printf("\n");
    }
    printf("Cake:%d,CakeAntId:%d,ant[cake].cake:%d\n",cake,ant[cake].id,ant[cake].cake);
    printf("=====================time: %d=====================\n",time);
} 

//---------------------------
int Clock(){//返回1表示结束 
    time++; 
    MakeAnt(); 
    GiveInfo(); 
    Move(); 
    Attack();
    Judge1(); 
    if(Judge2()) return 1;
    AgeAdd();
    LostInfo();
#ifdef DEBUG
    PFSTATE();
#endif
    return 0;
}
int main(){
    int i,tx,ty,runtime,isend=0;
    scanf("%d%d",&n,&m);
    map=(int **)malloc(sizeof(int *)*(n+1));
    info=(int **)malloc(sizeof(int *)*(n+1));
    for(i=0;i<=n;i++){
        map[i]=(int *)malloc(sizeof(int)*(m+1));
        info[i]=(int *)malloc(sizeof(int *)*(m+1));
    }
    initialize();
    scanf("%d%d%d",&cnum,&churt,&cdistance);
    cannon=(struct cannondata *)malloc(cnum*sizeof(struct cannondata));
    for(i=0;i<cnum;i++){
        scanf("%d%d",&tx,&ty);
        map[tx][ty]=CANNON;
        cannon[i].x=tx;
        cannon[i].y=ty;
    }
    scanf("%d",&runtime);
    for(i=0;i<runtime;i++){
        if(Clock()){
            printf("Game over after %d seconds\n",time);
            isend=1;
            break;
        }
    }
    if(!isend) printf("The game is going on\n");
    printf("%d\n",antnum);
    for(i=0;i<antnum;i++){
        printf("%d %d %d %d %d\n",ant[i].age,ant[i].rank,ant[i].health,ant[i].x,ant[i].y);
    }
    return 0;     
}
文章目录
  1. 杀蚂蚁
    1. Time limit: 3000 ms
    2. Memory limit: 256 MB
    3. Standard I/O
  • Content
  • Input Description
  • Output Description
  • Sample
    1. INPUT
    2. OUTPUT
  • Hint
    1. 样例说明:
    2. 数据说明:
  • Code
  • |