1. solution exception:索引超出了数组界限。_单数组完成贪吃蛇游戏思路

贪吃蛇游戏可以说是EV3编程里一座的小山,每个人都可以尝试爬一爬2efa03d8f7b1a833c05a4e6f6c38c148.png。前期在中文乐高论坛以及丁神的公众号里见过贪吃蛇的教程(当时看得头昏脑胀,数据关系太多了),基本使用两个数组去完成的,他们的大致原理如下:

c129317df4e0b4ce14f971d8e3de1b68.png

基本的思路是用两个数组分别记录蛇头的移动轨迹(每移动一次,完成一组数据的添加),以上图的5乘5尺寸为例,假如图1作为初始状态(此时蛇已经吃掉了两个苹果),当控制蛇往下走的时候,蛇头移动到(1,3)位置,更新数组,蛇尾的(2,1)需要使用白色方块擦除,蛇尾的坐标对应的在数组上索引是多少呢?(这个游戏最难的地方就在这里)

因为坐标数组的写入是通过附加完成的,所以蛇头的最新坐标在数组的末尾,也就是最大索引(数组长度-1)的位置,往后数(吃掉的苹果数量+1)个方框就是需要擦除的蛇尾对应的索引——数组长度-2-吃掉的苹果数量

蛇头的坐标变化规律为:上移纵坐标减1,下移纵坐标加1,左移横坐标减1,右移横坐标加1,在5乘5方格中横坐标、纵坐标的范围都是0到4,超出这个范围视为撞墙,判定游戏失败。

使用两个数组进行编程的好处是容易理解,但是在判定是否吃到苹果,是否咬到自己,以及记录蛇头位置时需要对两个数组都进行运算和分析,计算量比较大。

下面我们来看看使用一个数组如何完成贪吃蛇游戏。2efa03d8f7b1a833c05a4e6f6c38c148.png

0f9f30e3429cb890dde259394eeb0173.png

同样以5乘5尺寸为例,把方格从左到右依次编号(通过编号的换算可以得到对应方框的坐标),可以发现行之间的编号不连续,连续的编号会导致蛇头移动到左右侧边缘时会自己进入下一行,难以判定是否撞墙。5乘5尺寸中没有6的倍数编号,如果蛇头的编号超出编号范围或者能够被6整除则可以判定蛇撞到边界。

蛇头在移动时,编号的变化规律为:上移编号减6,下移编号加6,左移编号减1,右移编号加1。

按照这种方式进行方格的编号就可以只用一个数组进行编程,这样的好处是出界的判定和数值的变化规律比较简单,判定是否吃到苹果,是否咬到自己,以及记录蛇头位置的记录工作量会减少了一半,只是编号和实际坐标的换算比较复杂。

下面我们看看如何用这种方法完成贪吃蛇游戏,下图为贪吃蛇的编程流程

5710cd6aafd51412c6bc8a8a86f0220d.png

蛇身和苹果的大小为8乘8像素的正方形,灰色区域为蛇的可活动区,一共12行,21列,对应的横纵坐标跨度为96和168(EV3屏幕的分辨率是178*128)。

970826c4006a182d5c0bf62055fe193b.png

程序的全貌如下,下面依次进行说明:

fa04257350419c538c8f2b297b510bb3.png

1、游戏界面生成

24d473d0fe193ebc1214f2106b93693f.png

显示蛇头运动区域方框、初始得分,苹果编号写入9并画出苹果的位置,初始移动速度为1(向右移动),变量overlap用来记录苹果是否和蛇身重叠,变量gameover用来记录游戏是否结束,变量inteval用来控制蛇头移动的时间间隔。初始蛇头出现在编号23位置,并初始化蛇头编号数组。

方框的分界线会占用一个像素,所以方框的长宽分别为168+2和96+2,编号1的方框左上角顶点的坐标为(3,3)。

2、自定义模块math、Apple和snake

他们都可以通过输入一个编号得到对应位置的苹果或者蛇头,具体内容如下:

c79c17283bac70d469ef4e0417835148.png

如果把(编号-1)除22取余的值记为a,(编号-1)除22向下取整的值记为b,那么编号对应的实际坐标为:

纵坐标=b*8+3 横坐标=a*8+3 d396b335d8368d7b0af62cfd70f1447c.png 1cbbbfc086d6525230972eae880f4e7a.png

自定义模块Apple中使用换算得到的坐标画了一个黑色填充的正方形,自定义模块snake中使用换算得到的坐标画了一个黑色填充的正方形和白色边框不填充的小正方形。

3、蛇头移动控制模块ctl

1fb63f52d75c25cb12bc5b6d1ec25c38.png

通过按键控制移动速度变量speed的值,按上写入-22,按下写入22,按左写入-1,按右写入1,不按时无操作。这个部分并行而不是放在循环内的好处是可以让操作更加灵敏。 4、自定义模块Time

dff76b7ecae4f0f67db14f2fa47db3d2.png

这个模块用来控制蛇头刷新的时间间隔,初始间隔为0.5秒,每吃掉一个苹果间隔减少0.001秒,如果数值小于等于0.2则控制间隔为0.2秒。 5、画出新蛇头的位置 根据蛇头和速度换算得到新蛇头的编号并显示蛇头。 6、自定义模块GG——判断是否咬到自己或超出边界

549008eaae7f087d9433599fad324f8d.png

撞到边界:蛇头编号在1到263之外或者能被22整除时,变量gameover写入是。 咬到自己:吃掉至少一个苹果时,把蛇头编号和蛇身编号比较(从倒数第二个索引开始比较,比较eat次),相同则变量gameover写入是。

7、记录蛇头位置

将蛇头当前的编号附加到数组中。

8、自定义模块New——判断是否吃掉苹果,并生成一个不跟蛇身重叠的苹果

72fada915e2f886e9fd9a7e5fa66ed3f.png

如果苹果编号和蛇头编号相同,发出声音,eat加1,更新分数,变量eaten写入真。

4307f7219b349894e9a41972834bb394.png

如果苹果被吃掉,则在蛇的活动区域内随机生成一个编号并存入变量apple中。

376e46be5c2bb0ab47232b3634eca99c.png

将新生成的苹果编号和蛇身编号(从蛇头开始比)数组依次进行比较,记录是否重叠。

c180dac7d5169a60b1a432d44569771c.png

比较的次数为eat数值的两倍,理论上是eat加2(eat加1是蛇的长度,加2是为了不让苹果出现在即将擦除的位置上)就可以,但是实测会有问题,采用两倍就没问题了,很奇怪! ( 求大佬告知 )。 如果比较完发现苹果和蛇身没有重叠,则变量overlap写入否,如果有重叠则重叠的次数归零,继续生成新的苹果编号,没有重叠则显示新的苹果位置,eaten写入否。 9、自定义模块Clear——先判断是否吃到苹果然后再进行蛇尾擦除 这个地方的顺序需要注意,如果是先擦除后判断苹果是否吃到会导致蛇吃到苹果的瞬间蛇身没有变长。

cdca47c03c21c3b73b330c4dd8de20e9.png

通过白色填充正方形完成蛇尾的擦除,蛇尾编号对应的索引为蛇身长度减2减吃掉的苹果数量。 10、游戏结束 如果变量gameover为真则结束循环,发出声音提示,等待确认键按下则结束程序。 游戏优化建议: 1、蛇身的外形可以优化,如果在蛇身上画个白色的x可以得到一条大花蛇; 2、可以使用文件读写模块进行最高分的记录和更新; 3、改变编号规律可以实现左右穿墙、上下穿墙效果; 4、游戏开始时可以进行游戏难度选择,不同难度对应不同的刷新速度; 附加: 1、穿墙效果视频: 2、不能穿墙效果视频(为了录到没有杂音的视频,生生玩到了600加): 对于苹果会出现在蛇身上的问题,可修改判定的程序为统计不重叠的次数,如果不重叠的数量为eat+2,则结束循环生成苹果。如果中途有重叠,重置变量n,重新生成新的苹果。

7509e5dc97744de45adc24a49c149175.png

如果觉得内容不错,可以关注一下公众号,顺便点下 在看 哦。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
class AbstractGreedyAndPrune(): def __init__(self, aoi: AoI, uavs_tours: dict, max_rounds: int, debug: bool = True): self.aoi = aoi self.max_rounds = max_rounds self.debug = debug self.graph = aoi.graph self.nnodes = self.aoi.n_targets self.uavs = list(uavs_tours.keys()) self.nuavs = len(self.uavs) self.uavs_tours = {i: uavs_tours[self.uavs[i]] for i in range(self.nuavs)} self.__check_depots() self.reachable_points = self.__reachable_points() def __pruning(self, mr_solution: MultiRoundSolution) -> MultiRoundSolution: return utility.pruning_multiroundsolution(mr_solution) def solution(self) -> MultiRoundSolution: mrs_builder = MultiRoundSolutionBuilder(self.aoi) for uav in self.uavs: mrs_builder.add_drone(uav) residual_ntours_to_assign = {i : self.max_rounds for i in range(self.nuavs)} tour_to_assign = self.max_rounds * self.nuavs visited_points = set() while not self.greedy_stop_condition(visited_points, tour_to_assign): itd_uav, ind_tour = self.local_optimal_choice(visited_points, residual_ntours_to_assign) residual_ntours_to_assign[itd_uav] -= 1 tour_to_assign -= 1 opt_tour = self.uavs_tours[itd_uav][ind_tour] visited_points |= set(opt_tour.targets_indexes) # update visited points mrs_builder.append_tour(self.uavs[itd_uav], opt_tour) return self.__pruning(mrs_builder.build()) class CumulativeGreedyCoverage(AbstractGreedyAndPrune): choice_dict = {} for ind_uav in range(self.nuavs): uav_residual_rounds = residual_ntours_to_assign[ind_uav] if uav_residual_rounds > 0: uav_tours = self.uavs_tours[ind_uav] for ind_tour in range(len(uav_tours)): tour = uav_tours[ind_tour] quality_tour = self.evaluate_tour(tour, uav_residual_rounds, visited_points) choice_dict[quality_tour] = (ind_uav, ind_tour) best_value = max(choice_dict, key=int) return choice_dict[best_value] def evaluate_tour(self, tour : Tour, round_count : int, visited_points : set): new_points = (set(tour.targets_indexes) - visited_points) return round_count * len(new_points) 如何改写上述程序,使其能返回所有已经探索过的目标点visited_points的数量,请用代码表示
最新发布
06-10

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值