《C#零基础入门之百识百例》(四十)方法应用 -- 推箱子游戏 -- 源码分享

Posted 陈言必行

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《C#零基础入门之百识百例》(四十)方法应用 -- 推箱子游戏 -- 源码分享相关的知识,希望对你有一定的参考价值。

前言

本文属于C#零基础入门之百识百例系列文章。此系列文章旨在为学习C#语言的童鞋提供一套系统的学习路径。此系列文章都会通过【知识点】【练习题】的形式呈现。有任何问题,你都可以通过评论私信等方式找到我,我会一对一解答你的问题。


系列文章目录:
《C#零基础入门之百识百例》 目录文章传送门


效果展示


一,关卡设计

首先我们要确定一下数组值的含义和其对应的表示:

数值含义符号
0空格“ ” 空字符串
1墙体■ 实心正方形
2箱子□ 空心正方形
3小人人 汉字
4目标☆ 空心五角星
5完成★实心五角星

基本定义就是这样,详解在使用数组实例一文中: 《C#零基础入门之百识百例》(三十)数组应用 – 推箱子游戏 – 地图初始化,这里不在赘述。


二,地图初始化

主要逻辑:

  1. 定义两个二维数组表示关卡地图,map带有玩家(3),targeMap不带玩家(3)
  2. 遍历map将其值对应的地图含义转换为对应的图形,打印出来
/// <summary>
/// 推箱子
/// 地图数值含义:0:空地,1:墙体,2:箱子,3:人,4:目标,5:完成
/// </summary>
class Sokoban
        
	static void Main(string[] args)
    
       // 关卡初始化
       InitMap();
       // 刷新地图
	   UpdateMap();

	   Console.ReadLine();
	

    // 本关卡地图
    static int[,] map;
    // 目标地图
    static int[,] targeMap;

    //玩家的初始坐标
    static int y = 4, x = 5;
    /// <summary>
    /// 关卡初始化 -- 关卡数组,玩家位置
    /// </summary>
    static void InitMap()
    
        y = 4;
        x = 5;

        map = new int[8, 9]
        
                0,0,0,1,1,1,0,0,0,
                0,0,0,1,4,1,0,0,0,
                0,0,0,1,0,1,1,1,1,
                0,1,1,1,2,0,2,4,1,
                0,1,4,0,2,3,1,1,1,
                0,1,1,1,1,2,1,0,0,
                0,0,0,0,1,4,1,0,0,
                0,0,0,0,1,1,1,0,0
        ;

        targeMap = new int[8, 9]
        
                0,0,0,1,1,1,0,0,0,
                0,0,0,1,4,1,0,0,0,
                0,0,0,1,0,1,1,1,1,
                0,1,1,1,2,0,2,4,1,
                0,1,4,0,2,0,1,1,1,
                0,1,1,1,1,2,1,0,0,
                0,0,0,0,1,4,1,0,0,
                0,0,0,0,1,1,1,0,0
        ;
    

	/// <summary>
    /// 刷新地图显示
    /// </summary>
    static void UpdateMap()
    
        // 清屏
        Console.Clear();
        Console.WriteLine("-------- 推箱子 --------");
        Console.WriteLine();

        // 遍历数组 -- 打印新地图
        for (int i = 0; i < map.GetLength(0); i++)
        
            for (int j = 0; j < map.GetLength(1); j++)
            
                if (map[i, j] == 0)
                
                    Console.Write(" ");
                
                if (map[i, j] == 1)
                
                    Console.Write("■");
                
                if (map[i, j] == 2)
                
                    Console.Write("□");
                
                if (map[i, j] == 3)
                
                    Console.Write("人![请添加图片描述](https://img-blog.csdnimg.cn/6a07bce3885340c6aa0b038e46858af9.gif)
");
                
                if (map[i, j] == 4)
                
                    Console.Write("☆");
                
                if (map[i, j] == 5)
                
                    Console.Write("★");
                
            
            Console.WriteLine();
        

        Console.WriteLine();
        Console.WriteLine("按 ↑←↓→ 控制玩家移动");
        Console.WriteLine("输入 G 重新开始");
    


三,玩家移动

实现逻辑:

  1. 用户通过 “↑←↓→“ 来控制玩家移动,所以需要在循环中,检测键盘输入,然后处理对应的键盘输入逻辑
  2. 需要注意玩家移动的方向,对数组进行的计算逻辑。比如向上移动,则是对y-1; 这里需要理解一下
  3. 进行数组越界校验,不满足的值不进行处理,避免程序报错。

模拟玩家移动逻辑:

/// <summary>
/// 移动函数
/// </summary>
/// <param name="direction">移动方向</param>
static void Move(ConsoleKey direction)

    // 本次玩家移动位置
    int ox = 0, oy = 0;

    // 根据方向进行偏移计算
    switch (direction)
    
        case ConsoleKey.UpArrow:
            oy--;
            break;
        case ConsoleKey.DownArrow:
            oy++;
            break;
        case ConsoleKey.LeftArrow:
            ox--;
            break;
        case ConsoleKey.RightArrow:
            ox++;
            break;
        default:
            return;
    

	// 玩家移动走 -- 还原地图
    map[y, x] = targeMap[y, x];
    y += oy;
    x += ox;
    // 做越界判断
    if (y < map.GetLength(0) && y > 0 && x < map.GetLength(1) && x > 0)
    
        map[y, x] = 3;
    

修改Main函数如下,即可运行输入控制玩家移动

static void Main(string[] args)

    // 关卡初始化
    InitMap();

    // 循环 -> 等待用户输入后执行一次
    while (true)
    
        // 刷新地图
        UpdateMap();
        // 接收用户输入操作
        ConsoleKeyInfo keyInfo = Console.ReadKey();
        // 主角移动
        Move(keyInfo.Key);
    
    
    Console.ReadLine();

移动效果:


四,玩家推箱子

玩家推箱子可以分解为两个步骤:**1. 玩家向前移动一步;2. 箱子向前移动一步。**而这里两个步骤又有各种条件限制,如前面是墙箱子不能动,箱子前面是墙不能动等等…

整理后的移动情况如下:(为方便描述将A1作为玩家移动的下一个目标点,A2作为下两个目标点)

  1. A1是空地目标 --> 可以移动
  2. A1是墙体 --> 不能移动
  3. A1是箱子到目标点的箱子 --> 可以移动 --> 此时需要看能不能推动箱子,即A2点情况
    - A2是空地目标点 --> 可以移动
    - A2是墙体箱子达到目标的箱子 --> 不能移动

注意:当可以移动时,还需要考虑当前玩家位置是否目标位置,若是目标位置还需要还原会目标,若不是则还原为空格即可。


移动代码修改如下:

/// <summary>
/// 移动函数
/// </summary>
/// <param name="direction">移动方向</param>
static void Move(ConsoleKey direction)

    // 玩家要移动的下一个位置的x,y
    int nx1 = x, ny1 = y;
    // 同方向的移动下两个位置的x,y
    int nx2 = x, ny2 = y;
    // 本次玩家移动位置
    int ox = 0, oy = 0;

    // 根据方向进行偏移计算
    switch (direction)
    
        case ConsoleKey.UpArrow:
            ny1 -= 1;
            ny2 -= 2;
            oy--;
            break;
        case ConsoleKey.DownArrow:
            ny1 += 1;
            ny2 += 2;
            oy++;
            break;
        case ConsoleKey.LeftArrow:
            nx1 -= 1;
            nx2 -= 2;
            ox--;
            break;
        case ConsoleKey.RightArrow:
            nx1 += 1;
            nx2 += 2;
            ox++;
            break;
        default:
            return;
    


    // 玩家的下一个坐标为空地,进行移动
    if (map[ny1, nx1] == 0 || map[ny1, nx1] == 4)
    
        map[ny1, nx1] = 3;
        map[y, x] = 0;
        // 玩家现在的坐标是目标点的坐标 -- 还原回去
        if (targeMap[y, x] == 4)
            map[y, x] = 4;
        else
            map[y, x] = 0;
        y += oy;
        x += ox;
    
    // 玩家的下一个坐标为墙 不做处理
    else if (map[ny1, nx1] == 1)
    
        return;
    
    // 玩家的下一个坐标为未到达目标的箱子或到达目标的箱子
    else if (map[ny1, nx1] == 2 || map[ny1, nx1] == 5)
               
        // 箱子的下一个目标为空地
        if (map[ny2, nx2] == 0)
        
            map[ny2, nx2] = 2;
            map[ny1, nx1] = 3;
            // 玩家现在的坐标是目标点的坐标 -- 还原回去
            if (targeMap[y, x] == 4)
                map[y, x] = 4;
            else
                map[y, x] = 0;
            y += oy;
            x += ox;
        
        // 箱子的下一个目标为目标点
        else if (map[ny2, nx2] == 4)
        
            map[ny2, nx2] = 5;
            map[ny1, nx1] = 3;
            // 玩家现在的坐标是目标点的坐标 -- 还原回去
            if (targeMap[y, x] == 4)
                map[y, x] = 4;
            else
                map[y, x] = 0;
            y += oy;
            x += ox;
        
        else
        
            // 箱子的下一个目标为墙或空箱子或到达目标的箱子 -- 不做处理
            //if (map[ny2, nx2] == 1 || map[ny2, nx2] == 2 || map[ny2, nx2] == 5)
        
    


五,游戏结束

判断游戏结束逻辑就比较简单了,当地图中没有箱子目标点时,即可认为是游戏成功。代码实现如下:

/// <summary>
/// 游戏结束 -- 地图中没有箱子和目标点
/// </summary>
/// <returns></returns>
static bool IsGameOver()

    for (int i = 0; i < map.GetLength(0); i++)
    
        for (int j = 0; j < map.GetLength(1); j++)
        
            if (map[i, j] == 2 || map[i, j] == 4)
            
                return false;
            
        
    
    return true;

游戏结束后,可处理根据用户操作进行重开或者进行下一关卡(本文未实现,其实逻辑不能,只是将地图设置为下一关卡即可)

修改Main函数如下:

static void Main(string[] args)

    // 关卡初始化
    InitMap();


    // 循环 -> 等待用户输入后执行一次
    while (true)
    
        // 刷新地图
        UpdateMap();

        // 游戏成功
        if (IsGameOver())
        
            Console.WriteLine("======== 恭喜过关 ========");
            // Console.WriteLine("按下任意键重玩...");
            // todo...下一关
            // Console.ReadKey();
            //break;
        

        // 接收用户输入操作
        ConsoleKeyInfo keyInfo = Console.ReadKey();

        // 重玩
        if (keyInfo.Key == ConsoleKey.G)
        
            // 关卡初始化
            InitMap();
            // 刷新地图
            UpdateMap();
        
        else
        
            // 主角移动
            Move(keyInfo.Key);
        
    

    Console.ReadLine();


六,源码分享

上面代码可以组成一个完成的推箱子游戏了,组合后的代码:

/// <summary>
/// 推箱子
/// </summary>
class Sokoban
       
    /// <summary>
    /// 本关卡地图
    /// 值释义 ->  0:空格; 墙:1■; 箱子:2□; 人:3人; 目标:4☆; 完成:5★
    /// </summary>
    static int[,] map;
    // 目标地图
    static int[,] targeMap;

    //玩家的初始坐标
    static int y = 4, x = 5;


    static void Main(string[] args)
    
        // 关卡初始化
        InitMap();


        // 循环 -> 等待用户输入后执行一次
        while (true)
        
            // 刷新地图
            UpdateMap();

            // 游戏成功
            if (IsGameOver())
            
                Console.WriteLine("======== 恭喜过关 ========");
                // Console.WriteLine("按下任意键重玩...");
                // todo...下一关
                // Console.ReadKey();

                break;
            

            // 接收用户输入操作
            ConsoleKeyInfo keyInfo = Console.ReadKey();

            // 重玩
            if (keyInfo.Key == ConsoleKey.G)
            
                《C#零基础入门之百识百例》(四十四)静态类 -- 温度单位转换

《C#零基础入门之百识百例》(四十六)类的索引 -- 数组索引器

《C#零基础入门之百识百例》(四十五)类的属性 -- 单例模式

《C#零基础入门之百识百例》(四十二)类的成员 -- 模拟计算器

《C#零基础入门之百识百例》(四十七)类的运算符 -- 学生成绩汇总

《C#零基础入门之百识百例》(四十一)类的定义和使用 -- 鸡兔同笼