用R写一个迷宫小游戏

本文介绍了如何利用R语言的图形API和深度优先搜索(DFS)算法创建一个迷宫游戏,涉及数据结构的设计、代码解析和键盘事件的处理。作者分享了从安装R环境到生成迷宫地图的完整过程,以及遇到的问题和解决方案。
摘要由CSDN通过智能技术生成

效果图

在这里插入图片描述

缘起

刚装了Ubuntu系统,发现里面有自带的扫雷等小游戏。最近又疯狂使用R,忽然有一个念头,R做游戏如何呢?是的,刚开始我想做的是扫雷,后面发现这里面存在一个很大的问题,故放弃了,具体原因我后面会讲。我便在网上查了一下,发现还真有人用R写 贪吃蛇游戏,便花费了一晚上也简单写了个游戏试试。

R的图形API

只有4个:

  • setGraphicsEventHandlers, 注册图形事件,包括键盘事件和鼠标事件(按下,释放,移动)。
  • getGraphicsEvent, 启动图形事件监听器,开始监听。
  • setGraphicsEventEnv, 设置图形设备和环境空间,默认为当前图形设备和当前环境空间。
  • getGraphicsEventEnv, 获取图形设备,默认为当前图形设备。

我只用到了第二个,这方面的知识可参考这篇 文章,很有用!

DFS函数生成迷宫

学过数据结构的小伙伴一定很熟悉 dfs(): 广度优先搜索算法
这里生成迷宫的思想是这样的,首先看如下图,黄色的地方代表 通路,灰色代表 围墙,最外侧的四面围墙是不可拆卸的,而相邻黄色格子间的通路是可以 打通的(对应代码里的connect()函数),然后利用 DFS()实现图的遍历,把一个个黄点看成独立的结点,进行搜索,生成连接路径,把 连接路径上的墙推倒(由灰色转为黄色)。是的,你可能意识到的,用DFS生成这样的迷宫会有如下两点:

  • 每两个初始的黄色结点必有通路,因为是遍历
  • 这样生成的迷宫岔路较少(相比其他生成方法)
    在这里插入图片描述

数据结构

前面也说到,视初始的黄色结点为 图的节点,我们用变量 block_map 进行保存,大小的变量为 size
最终生成图形黄色的部分实际上有两种:一是 初始结点 ,二是dfs()运行中 推倒的墙 (墙倒了形成了路)。我们的 block_map 只会记录初始结点,我们需要另外任命一个变量 maze_map 来记录究竟哪里是路哪里是墙,它的大小记为 size2,可以简单知道 size2 = 2 * size - 3

代码解析

输入参数为:size, cex_set(后面解释cex_set)
切记:0表示路,1表示墙

  • 初始化数据结构
size2 = 2*size -3
  block_map = matrix(0, size, size)
  maze_map = matrix(1, size2, size2)
  for(i in 1:(size-2)){
    for(j in 1:(size-2)){
      maze_map[2*i, 2*j]=0
    }
  }
  # 四面的围墙
  block_map[1, ] = 1
  block_map[size, ] = 1
  block_map[, 1] = 1
  block_map[, size] = 1
  • 使用move简便 “上下左右”的操作
  move=list(c(-1, 0), c(1, 0), c(0, -1), c(0, 1))

使用时应用 move[[i]][j] 这样的形式,list数据类型就是会麻烦点

  • dfs() 前的准备
    in_map() 用于判断操作是否越界
    connect() 用于拆墙
    neighbor_count() 用于计算该节点周围有几面墙
in_map <- function(x, y){
    return((1<=x) && (x<=size) && (1<=y) && (y<=size))
  }
connect <- function(x, y, xx, yy){
  maze_map[(x+xx)-2, (y+yy)-2] <<- 0
}
neighbor_count <- function(x, y){
  temp = 0
  for(i in 1:4){
    if(in_map(x + move[[i]][1], y + move[[i]][2])){
      if(block_map[x + move[[i]][1], y + move[[i]][2]] == 1){
        temp = temp + 1
      }
    }
  }
  return(temp)
 }
  • 实施 dfs()
    看不懂的话直接上网上搜,网上有些说得很清楚。DFS()算法展开来讲会很多,这里略过。但我提一个很容易犯的错误作用域,如果这里把 block_map[xx, yy] <<- 1 错写成 block_map[xx, yy] <- 1 和 block_map[xx, yy] = 1,代码都会失效,问题在哪呢?—— 问题在于在 函数体 内你对 block_map 所作的修改并 没法传出去 !!具体可见这篇 文章
 dfs <- function(x, y){
    print(c(x, y, neighbor_count(x, y)))
    if(neighbor_count(x, y) == 4){return}
    direction = c(FALSE, FALSE, FALSE, FALSE)
    while(neighbor_count(x, y) < 4){
      temp = -1
      while(temp == -1 || direction[temp] == TRUE ){
        temp = sample(1:4, 1)
      }
      xx=x+move[[temp]][1]
      yy=y+move[[temp]][2]
      if(in_map(xx, yy) && block_map[xx, yy] == 0){
        block_map[xx, yy] <<- 1
        connect(x, y, xx, yy)
        dfs(xx, yy)
        direction[temp]=TRUE
        if(neighbor_count(x, y) == 4){
          return
        }
      }
    }
    return
  }
  • 开始画图,这里的配色你可以自己后期改,配色的话可以参考这个
windows()
plot(0,0,xlim=c(0, 10),ylim=c(0, 10),type='n',xaxs="i", yaxs="i")
for(i in 1:10){
  points(i - 0.5, i - 0.5, col = i, pch = 15, cex = 2.5)
}

正式画图
now.x() 和 now.y() 记录现在的位置,dest.x() 和 dest.y() 记录终点位置

  windows()
  plot(0,0,xlim=c(0, size2),ylim=c(0, size2),type='n',xaxs="i", yaxs="i")
  for(i in 1:(size2 -1)){
    abline(h=i,col="gray60") # 水平线
    abline(v=i,col="gray60") 
  }
  abline(h=size2)
  abline(v=size2)
  for(i in 1:size2){
    for(j in 1:size2){
      if(maze_map[i, j]==1){
        points(i-0.5, j-0.5, col = 8, pch = 15, cex = cex_set)
      }
      else{
        points(i-0.5, j-0.5, col = 7, pch = 15, cex = cex_set)
      }
    }
  }
  now.x = 2
  now.y = 2
  dest.x = size2 - 1
  dest.y = size2 - 1
  points(now.x-0.5, now.y-0.5, col = 2, pch = 15, cex = cex_set)
  points(dest.x-0.5, dest.y-0.5, col = 6, pch = 15, cex = cex_set)
  • 写键盘事件
    判断是否越界 及 是否为路
    特别注意:now.y <<- now.y - 1
 if(K == "down"){
      if(now.y > 2 && maze_map[now.x, now.y-1] == 0){
        points(now.x-0.5, now.y-0.5, col = 7, pch = 15, cex = cex_set)
        now.y <<- now.y - 1
        points(now.x-0.5, now.y-0.5, col = 2, pch = 15, cex = cex_set)
      }
    }
  • 绑定键盘事件
getGraphicsEvent(onKeybd = keydown)
  • cex_set
    因为生成不同规模大小的地图,颜色填充的可能不好,要么没有填充完区域的大部份,要么超过区域到影响正常游戏操作,所以留了个cex_set设置点的大小,让使用者自己调节,实话实说,这是一处败笔,但我也懒得改了。
    (2022/7/28: 实际上可以用函数polygon,它可以很好地填充颜色,但我懒)

后话

为啥我没法做扫雷呢?因为获取到的鼠标位置和图像的位置对不上,这个问题我至今未解决。

附录:完整代码

maze <- function(size, cex_set = 2.5){
  # 1是墙体,0是通路
  size2 = 2*size -3
  block_map = matrix(0, size, size)
  maze_map = matrix(1, size2, size2)
  for(i in 1:(size-2)){
    for(j in 1:(size-2)){
      maze_map[2*i, 2*j]=0
    }
  }
  block_map[1, ] = 1
  block_map[size, ] = 1
  block_map[, 1] = 1
  block_map[, size] = 1
  
  move=list(c(-1, 0), c(1, 0), c(0, -1), c(0, 1))
  
  in_map <- function(x, y){
    return((1<=x) && (x<=size) && (1<=y) && (y<=size))
  }
  connect <- function(x, y, xx, yy){
    maze_map[(x+xx)-2, (y+yy)-2] <<- 0
  }
  neighbor_count <- function(x, y){
    temp = 0
    for(i in 1:4){
      if(in_map(x + move[[i]][1], y + move[[i]][2])){
        if(block_map[x + move[[i]][1], y + move[[i]][2]] == 1){
          temp = temp + 1
        }
      }
    }
    return(temp)
  }
  dfs <- function(x, y){
    print(c(x, y, neighbor_count(x, y)))
    if(neighbor_count(x, y) == 4){return}
    direction = c(FALSE, FALSE, FALSE, FALSE)
    while(neighbor_count(x, y) < 4){
      temp = -1
      
      while(temp == -1 || direction[temp] == TRUE ){
        temp = sample(1:4, 1)
      }
      
      xx=x+move[[temp]][1]
      yy=y+move[[temp]][2]
      
      if(in_map(xx, yy) && block_map[xx, yy] == 0){
        block_map[xx, yy] <<- 1
        connect(x, y, xx, yy)
        dfs(xx, yy)
        
        direction[temp]=TRUE
        if(neighbor_count(x, y) == 4){
          return
        }
      }
    }
    return
  }
  # 设起点,并开始生成地图矩阵
  block_map[2, 2] = 1
  dfs(2, 2)
  
  # 生成地图
  windows()
  plot(0,0,xlim=c(0, size2),ylim=c(0, size2),type='n',xaxs="i", yaxs="i")
  for(i in 1:(size2 -1)){
    abline(h=i,col="gray60") # 水平线
    abline(v=i,col="gray60") 
  }
  abline(h=size2)
  abline(v=size2)
  for(i in 1:size2){
    for(j in 1:size2){
      if(maze_map[i, j]==1){
        points(i-0.5, j-0.5, col = 8, pch = 15, cex = cex_set)
      }
      else{
        points(i-0.5, j-0.5, col = 7, pch = 15, cex = cex_set)
      }
    }
  }
  
  now.x = 2
  now.y = 2
  dest.x = size2 - 1
  dest.y = size2 - 1
  points(now.x-0.5, now.y-0.5, col = 2, pch = 15, cex = cex_set)
  points(dest.x-0.5, dest.y-0.5, col = 6, pch = 15, cex = cex_set)
  
  keydown<-function(K){
    K = tolower(K)
    print(K)
    
    if(K == "down"){
      if(now.y > 2 && maze_map[now.x, now.y-1] == 0){
        points(now.x-0.5, now.y-0.5, col = 7, pch = 15, cex = cex_set)
        now.y <<- now.y - 1
        points(now.x-0.5, now.y-0.5, col = 2, pch = 15, cex = cex_set)
      }
    }
    
    if(K == "up"){
      if(now.y < size2 - 1 && maze_map[now.x, now.y+1] == 0){
        points(now.x-0.5, now.y-0.5, col = 7, pch = 15, cex = cex_set)
        now.y <<- now.y + 1
        points(now.x-0.5, now.y-0.5, col = 2, pch = 15, cex = cex_set)
      }
    }
    
    if(K == "left"){
      if(now.x > 2 && maze_map[now.x - 1, now.y] == 0){
        points(now.x-0.5, now.y-0.5, col = 7, pch = 15, cex = cex_set)
        now.x <<- now.x - 1
        points(now.x-0.5, now.y-0.5, col = 2, pch = 15, cex = cex_set)
      }
    }
    
    if(K == "right"){
      if(now.x < size2 - 1 && maze_map[now.x + 1, now.y] == 0){
        points(now.x-0.5, now.y-0.5, col = 7, pch = 15, cex = cex_set)
        now.x <<- now.x + 1
        points(now.x-0.5, now.y-0.5, col = 2, pch = 15, cex = cex_set)
      }
    }
   
    if(now.x == dest.x && now.y == dest.y){
      text(2, 2, label="You Win", cex = 3)
      getGraphicsEvent(onKeybd = NULL)
    } 
  }
  
  getGraphicsEvent(onKeybd = keydown)
  
}



maze(16)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值