吴昊品游戏核心算法 Round 4 —— 贪吃蛇模拟(简易版&&AI会在特别篇给出)(ZOJ 1056)

[简 介]贪吃蛇又名贪食蛇,是一款经典的小游戏。玩家使用方向键操控一条长长的蛇不断吞下豆子,同时蛇身随着吞下的豆子不断变长,当蛇头撞到蛇身或障壁时游戏 结束。贪吃蛇最初为人们所知的是诺基亚手机附带的一个小游戏,它伴随着诺基亚手机走向世界。现在的贪吃蛇出现了许多衍生版本,并被移植到各种平台上。

[历史] 1976年,Gremlin平台推出了一款经典街机游戏Blockade。游戏中,两名玩家分别控制一个角色在屏幕上移动,所经之处砌起围栏。角色只能向左、右方向90度转弯,游戏目标保证让对方先撞上屏幕或围栏。听起来有点复杂,其实就是下面这个样子:基本上就是两条每走一步都会长大的贪吃蛇比谁后完蛋,玩家要做的就是避免撞上障碍物和越来越长的身体。更多照片、视频可以看 GamesDBase 的介绍。Blockade 很受欢迎,类似的游戏先后出现在 Atari 2600、TRS-80、苹果 2 等早期游戏机、计算机上。但真正让这种游戏形式红遍全球的还是21年后随诺基亚手机走向世界的贪吃蛇游戏——Snake。

 

Source: ZOJ 1056(浙江大学Online Judge)

 

Problem——整个游戏棋盘是50*50大小的,左上角在(1,1),贪吃蛇由20个节点组成,头部位置在(25,30),水平延展到(25,11),可以有四个运动方向:东,西,南,北。题目就是给你一个运动序列,判断最终结果是下面3种情况的哪一种:1)正常。2)头撞到自己身体。3)出界。

 

Input——有很多实例,输入的每个问题实例有两行。第一行是一个整数(n<100),表示下一行中移动的步数。(n=0表示输入结束)下一行包含n个字符(向东,西,南或北),表示移动的序列,字母之间没有空格。

 

Output——对每个问题实例输出一行,输出格式应该是下面三种情况之一:

(1)   蠕虫在第m步撞到了自己。

(2)   蠕虫在第m步爬出了游戏界面。

(3)   蠕虫成功完成了m步移动。

其中的m需要用计算机判定,第1个移动是move 1.

 

什么是程序?程序=数据结构+算法,其中,数据结构应该说是骨架,而算法则是一个程序的灵魂所在,在这里,我将这个小程序拆成蠕虫的数据结构+蠕虫移动以及判定的模拟算法。

(1)蠕虫的数据结构:

蠕虫一系列移动的步数: int n;

蠕虫移动的方向: char data[MAXN];(这里的MAXN是一个常量,开一个比最大步数多5左右的就比较合适了)

蠕虫实际移动的步数:int step(这里就是Output中的m)

蠕虫的头部和尾部的坐标:(结构体)

Struct location

{

  Int x,y;

}head,nail;

游戏界面:(用二维数组来实现)

bool board[Row][Column];//蠕虫占据的位置设置为true,否则设置为false。这里用布尔逻辑来定义游戏界面是否占据位置,是一个很形象的做法!这是由于“棋盘”上的每一个“棋子”都是相同“属性”的,所以,我们可以这样做。

(2)模拟蠕虫在屏幕上的游动:

分三步进行:

第一步:初始化。

将蠕虫在界面中所占据的位置,记录到数组board中;蠕虫的头(25,30)尾(25,11)坐标。

第二步:移动头部。

根据给定的移动方向,确定头部移动的坐标,如果(A)头部移动到界面的外面或者(B)移动到其身体的内部,则结束模拟,输出相应的标志信息。

第三步:移动尾部。

注意:蠕虫尾部的移动和头部的移动,其规则是不一样的,比如说:游戏刚开始时,不管头部怎么移动,其尾部总是跟随身体向右平移(这个属于初始状态),直到蠕虫的尾部移到蠕虫头部刚开始时的移动一致,这一步乃是本问题编程的关键!

 

#include<stdio.h>

 #include<memory.h>//这是为了开启memset,进行初始化

 

 //定义贪吃蛇操作的上限

 #define MAXN 100 //假设最多有100个操作

 

 //定义整个棋盘[0,51],注意了,是闭区间!

 #define Row 51

 #define Column 51

 

 //看整个蛇的时候,由于每次移动只有头部和尾部在变化,所以,可以只关心首尾

 struct location

 {

   int x,y;      

 }head,nail;

 

 //在这一步,蛇会遇到“挫折”

 int step;

 

 //吴昊注释:这里的坐标和我们的坐标不同,x和y是倒转的

 int moves(int n,char data[])

 {

   bool board[Row][Column];

   //最初的时候,棋盘空无一物

   memset(board,false,sizeof(board));

   int i;

   for(i=11;i<=30;i++)

     board[25][i]=true;

   head.x=25;

   head.y=30;

   nail.x=25;

   nail.y=11;

   for(step=0;step<n;step++)

   {

     switch(data[step])

     {

       case 'N':

            head.x--;break;//break跳出,for循环自增

       case 'W':

            head.y--;break;

       case 'E':

            head.y++;break;

       case 'S':

            head.x++;break;                 

     }                    

     if(head.x>=Row||head.x<=0||head.y>=Column||head.y<=0)

       return 2;

     board[nail.x][nail.y]=false;

     //如果该位置已经被占据,就return 3,经过观察可以得到,唯有头结点

     //可能自己碰自己

     if(board[head.x][head.y]) return 3;

     else board[head.x][head.y]=true;

     //见分析,在小于19以前,尾端的移动都是一致的

     if(step<19) nail.y++;

     else

     {

       //超过19之后,可以说,后者比前者慢19拍

       switch(data[step-19])

       {

         case 'N':

            nail.x--;break;

         case 'W':

            nail.y--;break;

         case 'E':

            nail.y++;break;

         case 'S':

            nail.x++;break;                    

       }    

     }   

   }

   return 1;  

 }

 

 int main()

 {

   char data[MAXN]; //读入整个操作数据

   int n;

   while(scanf("%d",&n)==1&&n) //n为0时就退出

   {

     scanf("%s",data);

     switch(moves(n,data))

     {

       case 1:

            printf("The worm successfully made all %d moves.\n",n);

            break;

       //这里注意应该是(step+1)!

       case 2:

            printf("The worm ran off the board on move %d.\n",step+1);

            break;

       case 3:

            printf("The worm ran into itself on move %d.\n",step+1);

            break;                    

     }                           

   }

   return 0;   

 }

 

 

posted on 2013-02-27 20:47  吴昊系列  阅读(940)  评论(2编辑  收藏  举报

导航