[C#] 简单的俄罗斯方块实现

这篇具有很好参考价值的文章主要介绍了[C#] 简单的俄罗斯方块实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一个控制台俄罗斯方块游戏的简单实现. 已在 github.com/SlimeNull/Tetris 开源.
[C#] 简单的俄罗斯方块实现,.NET,控制台,灌水,c#,开发语言,玩游戏


思路

很简单, 一个二维数组存储当前游戏的方块地图, 用 bool 即可, true 表示当前块被填充, false 表示没有.

然后, 抽一个 “形状” 类, 形状表示当前玩家正在操作的一个形状, 例如方块, 直线, T 形什么的. 一个形状又有不同的样式, 也就是玩家可以切换的样式. 每一个样式都是原来样式旋转之后的结果. 为了方便, 可以直接使用硬编码的方式存储所有样式中方块的相对坐标.

一个形状有一个自己的坐标, 并且它包含很多方块. 在绘制的时候, 获取它每一个方块的坐标, 转换为地图内的绝对坐标, 然后使用 StringBuilder 拼接字符串, 即可.


资料

俄罗斯方块中总共有这七种方块

[C#] 简单的俄罗斯方块实现,.NET,控制台,灌水,c#,开发语言,玩游戏


类型定义

一个简单的二维坐标

/// <summary>
/// 表示一个坐标
/// </summary>
/// <param name="X"></param>
/// <param name="Y"></param>
record struct Coordinate(int X, int Y)
{
    /// <summary>
    /// 根据基坐标和相对坐标, 获取一个绝对坐标
    /// </summary>
    /// <param name="baseCoord"></param>
    /// <param name="relativeCoord"></param>
    /// <returns></returns>
    public static Coordinate GetAbstract(Coordinate baseCoord, Coordinate relativeCoord)
    {
        return new Coordinate(baseCoord.X + relativeCoord.X, baseCoord.Y + relativeCoord.Y);
    }
}

形状的一个样式, 单纯使用坐标数组存储即可.

record struct ShapeStyle(Coordinate[] Coordinates);

形状

/// <summary>
/// 形状基类
/// </summary>
abstract class Shape
{
    /// <summary>
    /// 名称
    /// </summary>
    public abstract string Name { get; }

    /// <summary>
    /// 形状的位置
    /// </summary>
    public Coordinate Position { get; set; }

    /// <summary>
    /// 形状所有的样式
    /// </summary>
    protected abstract ShapeStyle[] ShapeStyles { get; }

    /// <summary>
    /// 当前使用的样式索引
    /// </summary>
    private int _currentStyleIndex = 0;

    /// <summary>
    /// 从坐标构建一个新形状
    /// </summary>
    /// <param name="position"></param>
    public Shape(Coordinate position)
    {
        Position = position;
    }

    /// <summary>
    /// 获取当前形状的当前所有方块 (相对坐标)
    /// </summary>
    /// <returns></returns>
    public IEnumerable<Coordinate> GetBlocks()
    {
        return ShapeStyles[_currentStyleIndex].Coordinates;
    }

    /// <summary>
    /// 获取当前形状下一个样式的所有方块 (相对坐标)
    /// </summary>
    /// <returns></returns>
    public IEnumerable<Coordinate> GetNextStyleBlocks()
    {
        return ShapeStyles[(_currentStyleIndex + 1) % ShapeStyles.Length].Coordinates;
    }

    /// <summary>
    /// 改变样式
    /// </summary>
    public void ChangeStyle()
    {
        _currentStyleIndex = (_currentStyleIndex + 1) % ShapeStyles.Length;
    }
}

一个 T 形状的实现

class ShapeT : Shape
{
    public ShapeT(Coordinate position) : base(position)
    {
    }

    public override string Name => "T";

    protected override ShapeStyle[] ShapeStyles { get; } = new ShapeStyle[]
    {
        new ShapeStyle(
            new Coordinate[]
            {
                new Coordinate(-1, 0),
                new Coordinate(0, 0),
                new Coordinate(1, 0),
                new Coordinate(0, 1),
            }),
        new ShapeStyle(
            new Coordinate[]
            {
                new Coordinate(-1, 0),
                new Coordinate(0, -1),
                new Coordinate(0, 0),
                new Coordinate(0, 1),
            }),
        new ShapeStyle(
            new Coordinate[]
            {
                new Coordinate(-1, 0),
                new Coordinate(0, 0),
                new Coordinate(1, 0),
                new Coordinate(0, -1),
            }),
        new ShapeStyle(
            new Coordinate[]
            {
                new Coordinate(1, 0),
                new Coordinate(0, -1),
                new Coordinate(0, 0),
                new Coordinate(0, 1),
            }),
    };
}

主逻辑

上面的定义已经写好了, 接下来就是写游戏主逻辑.

主逻辑包含每一回合自动向下移动形状, 如果无法继续向下移动, 则把当前的形状存储到地图中. 并进行一次扫描, 将所有的整行全部消除.

抽一个 TetrisGame 的类用来表示俄罗斯方块游戏, 下面是这个类的基本定义.

class TetrisGame
{
    /// <summary>
    /// x, y
    /// </summary>
    private readonly bool[,] map;

    private readonly Random random = new Random();


    public TetrisGame(int width, int height)
    {
        map = new bool[width, height];

        Width = width;
        Height = height;
    }

    public Shape? CurrentShape { get; set; }

    public int Width { get; }
    public int Height { get; }
}

判断当前形状是否可以进行移动的方法

/// <summary>
/// 判断是否可以移动 (移动后是否会与已有方块重合, 或者超出边界)
/// </summary>
/// <param name="xOffset"></param>
/// <param name="yOffset"></param>
/// <returns></returns>
private bool CanMove(int xOffset, int yOffset)
{
    // 如果当前没形状, 返回 false
    if (CurrentShape == null)
        return false;

    foreach (var block in CurrentShape.GetBlocks())
    {
        Coordinate coord = Coordinate.GetAbstract(CurrentShape.Position, block);

        coord.X += xOffset;
        coord.Y += yOffset;

        // 如果移动后方块坐标超出界限, 不能移动
        if (coord.X < 0 || coord.X >= Width ||
            coord.Y < 0 || coord.Y >= Height)
            return false;

        // 如果移动后方块会与地图现有方块重合, 则不能移动
        if (map[coord.X, coord.Y])
            return false;
    }

    return true;
}

判断当前形状是否能够切换到下一个样式的方法

/// <summary>
/// 判断是否可以改变形状 (改变形状后是否会和已有方块重合, 或者超出边界)
/// </summary>
/// <returns></returns>
private bool CanChangeShape()
{
    // 如果当前没形状, 当然不能切换样式
    if (CurrentShape == null)
        return false;

    // 获取下一个样式的所有方块
    foreach (var block in CurrentShape.GetNextStyleBlocks())
    {
        Coordinate coord = Coordinate.GetAbstract(CurrentShape.Position, block);

        // 如果超出界限, 不能切换
        if (coord.X < 0 || coord.X >= Width ||
            coord.Y < 0 || coord.Y >= Height)
            return false;

        // 如果与现有方块重合, 不能切换
        if (map[coord.X, coord.Y])
            return false;
    }

    return true;
}

把当前形状存储到地图中

/// <summary>
/// 将当前形状存储到地图中
/// </summary>
private void StorageShapeToMap()
{
    // 没形状, 存寂寞
    if (CurrentShape == null)
        return;

    // 所有方块遍历一下
    foreach (var block in CurrentShape.GetBlocks())
    {
        // 转为绝对坐标
        Coordinate coord = Coordinate.GetAbstract(CurrentShape.Position, block);

        // 超出界限则跳过
        if (coord.X < 0 || coord.X >= Width ||
            coord.Y < 0 || coord.Y >= Height)
            continue;

        // 存地图里
        map[coord.X, coord.Y] = true;
    }

    // 当前形状设为 null
    CurrentShape = null;
}

生成一个新形状

/// <summary>
/// 生成一个新形状
/// </summary>
/// <exception cref="InvalidOperationException"></exception>
private void GenerateShape()
{
    int shapeCount = 7;
    int randint = random.Next(shapeCount);

    Coordinate initCoord = new Coordinate(Width / 2, 0);

    Shape newShape = randint switch
    {
        0 => new ShapeI(initCoord),
        1 => new ShapeJ(initCoord),
        2 => new ShapeL(initCoord),
        3 => new ShapeO(initCoord),
        4 => new ShapeS(initCoord),
        5 => new ShapeT(initCoord),
        6 => new ShapeZ(initCoord),

        _ => throw new InvalidOperationException()
    };

    CurrentShape = newShape;
}

扫描地图, 消除所有整行

/// <summary>
/// 扫描, 消除掉可消除的行
/// </summary>
private void Scan()
{
    for (int y = 0;  y < Height; y++)
    {
        // 设置当前行是整行
        bool ok = true;

        // 循环当前行的所有方块, 如果方块为 false, ok 就会被设为 false
        for (int x = 0; x < Width; x++)
            ok &= map[x, y];

        // 如果当前行确实是整行
        if (ok)
        {
            // 所有行全部往下移动
            for (int _y = y; _y > 0; _y--)
                for (int x = 0; x < Width; x++)
                    map[x, _y] = map[x, _y - 1];
            
            // 最顶行全设为空
            for (int x = 0; x < Width; x++)
                map[x, 0] = false;
        }
    }
}

封装一些用户操作使用的方法

/// <summary>
/// 根据指定偏移, 进行移动
/// </summary>
/// <param name="xOffset"></param>
/// <param name="yOffset"></param>
public void Move(int xOffset, int yOffse
{
    lock (this)
    {
        if (CurrentShape == null)
            return;

        if (CanMove(xOffset, yOffset))
        {
            var newCoord = CurrentShape.
            newCoord.X += xOffset;
            newCoord.Y += yOffset;

            CurrentShape.Position = newC
        }
    }
}

/// <summary>
/// 向左移动
/// </summary>
public void MoveLeft()
{
    Move(-1, 0);
}

/// <summary>
/// 向右移动
/// </summary>
public void MoveRight()
{
    Move(1, 0);
}

/// <summary>
/// 向下移动
/// </summary>
public void MoveDown()
{
    Move(0, 1);
}

/// <summary>
/// 改变形状样式
/// </summary>
public void ChangeShapeStyle()
{
    lock (this)
    {
        if (CurrentShape == null)
            return;

        if (CanChangeShape())
            CurrentShape.ChangeStyle();
    }
}

/// <summary>
/// 降落到底部
/// </summary>
public void Fall()
{
    lock (this)
    {
        while (CanMove(0, 1))
        {
            Move(0, 1);
        }
    }
}

游戏每一轮的主逻辑

/// <summary>
/// 下一个回合
/// </summary>
public void NextTurn()
{
    lock (this)
    {
        // 如果当前没有存在的形状, 则生成一个新的, 并返回
        if (CurrentShape == null)
        {
            GenerateShape();
            return;
        }

        // 如果可以向下移动
        if (CanMove(0, 1))
        {
            // 直接改变当前形状的坐标
            var newCoord = CurrentShape.Position;
            newCoord.Y += 1;

            CurrentShape.Position = newCoord;
        }
        else
        {
            // 将当前的形状保存到地图中
            StorageShapeToMap();
        }

        // 扫描, 判断某些行可以被消除
        Scan();
    }
}

将地图渲染到控制台文章来源地址https://www.toymoban.com/news/detail-637172.html

public void Render()
{
    StringBuilder sb = new StringBuilder();

    bool[,] mapCpy = new bool[Width, Height];
    Array.Copy(map, mapCpy, mapCpy.Length);

    if (CurrentShape != null)
    {
        foreach (var block in CurrentShape.GetBlocks())
        {
            Coordinate coord = Coordinate.GetAbstract(CurrentShape.Position, block);
            if (coord.X < 0 || coord.X >= Width ||
                coord.Y < 0 || coord.Y >= Height)
                continue;

            mapCpy[coord.X, coord.Y] = true;
        }
    }

    sb.AppendLine("┌" + new string('─', Width * 2) + "┐");
    for (int y = 0; y < Height; y++)
    {
        sb.Append("|");

        for (int x = 0; x < Width; x++)
        {
            sb.Append(mapCpy[x, y] ? "##" : "  ");
        }

        sb.Append("|");

        sb.AppendLine();
    }

    sb.AppendLine("└" + new string('─', Width * 2) + "┘");

    lock (this)
    {
        Console.SetCursorPosition(0, 0);
        Console.Write(sb.ToString());
    }
}

到了这里,关于[C#] 简单的俄罗斯方块实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • python实现俄罗斯方块【含代码和讲解】

    Python实现俄罗斯方块:打造经典游戏的代码实现教程 俄罗斯方块是世界上最受欢迎的电子游戏之一,源自俄罗斯。这是一个简单却富有挑战和乐趣的游戏,让玩家必须思考如何将各种形状的积木放入底部的平面上,以便完整地填满一行或多行,每填满一行就会消失并获得分数

    2024年02月11日
    浏览(53)
  • Java实现俄罗斯方块小游戏。(附完整源代码)

    大家好,我是百思不得小赵。 创作时间:2022 年 5 月 12 日 博客主页: 🔍点此进入博客主页 —— 新时代的农民工 🙊 —— 换一种思维逻辑去看待这个世界 👀 今天是加入CSDN的第1167天。觉得有帮助麻烦👏点赞、🍀评论、❤️收藏 目录 一、游戏背景 二、功能实现 三、效果

    2024年02月03日
    浏览(72)
  • 基于Python的PyGame的俄罗斯方块游戏设计与实现

    近年来,随着游戏产业的突飞猛进,游戏玩家的技术也是与日俱增,当你看见游戏高手完美的表演时,你是否想过我也能达到那种水平,本程序用Python语言编写俄罗斯方块,左侧显示正在运行的游戏,右边显示下一个出现的形状、等级和积分等。游戏运行时随着等级的提高而

    2024年02月04日
    浏览(57)
  • 基于Python+Pygame实现一个俄罗斯方块小游戏【完整代码】

    俄罗斯方块,一款起源于上世纪80年代的经典电子游戏,凭借简单的规则和独特的魅力,一跃成为全球家喻户晓的经典。你知道其实只需要一些基础的编程知识,就可以自己实现它吗?今天,我们将使用Python的Pygame库,一步步带你构建属于自己的俄罗斯方块小游戏! 游戏初始

    2024年02月04日
    浏览(50)
  • python毕设分享 俄罗斯方块小游戏设计与实现 (源码)

    🔥 Hi,各位同学好呀,这里是L学长! 🥇今天向大家分享一个今年(2022)最新完成的毕业设计项目作品 python小游戏毕设 俄罗斯方块小游戏设计与实现 (源码) 🥇 学长根据实现的难度和等级对项目进行评分(最低0分,满分5分) 难度系数:3分 工作量:3分 创新点:4分 项目获取:

    2024年02月03日
    浏览(61)
  • 打造经典游戏:HTML5与CSS3实现俄罗斯方块

    🌟 前言 欢迎来到我的技术小宇宙!🌌 这里不仅是我记录技术点滴的后花园,也是我分享学习心得和项目经验的乐园。📚 无论你是技术小白还是资深大牛,这里总有一些内容能触动你的好奇心。🔍 🤖 洛可可白 :个人主页 🔥 个人专栏 :✅前端技术 ✅后端技术 🏠 个人

    2024年03月11日
    浏览(60)
  • 免费分享一套Python俄罗斯方块源码 PyQt5俄罗斯方块源码,太好玩了~

    大家好,我是java1234_小锋老师,看到一个不错的Python俄罗斯方块源码 PyQt5俄罗斯方块源码,分享下哈。 【免费】Python俄罗斯方块源码 PyQt5俄罗斯方块源码 Python小游戏源码_哔哩哔哩_bilibili 【免费】Python俄罗斯方块源码 PyQt5俄罗斯方块源码 Python小游戏源码项目来自互联网,免

    2024年01月25日
    浏览(47)
  • 编写一个俄罗斯方块

    编写俄罗斯方块 思路。 1、创建容器数组,方块, 2、下落,左右移动,旋转,判断结束,消除。  定义一个20行10列的数组表示游戏区。初始这个数组里用0填充,1表示有一个方块,2表示该方块固定了, 然后随机出一个方块,操作左右转,触底变2后,再随机下一个方块,循

    2024年02月12日
    浏览(53)
  • 【用unity实现100个游戏之10】复刻经典俄罗斯方块游戏(附项目源码)

    【视频】:https://www.bilibili.com/video/BV1Fr4y1x7mx 注意 :本文为学习笔记记录,推荐支持原作者,去看原视频自己手敲代码理解更加深入

    2024年02月04日
    浏览(81)
  • pygame俄罗斯方块游戏

    1.安装python 2.引入游戏库pygame 3.引入随机数 俄罗斯方块初始形状 这里使用一个二维数组 用来标记俄罗斯相对应的方块形状 代码如下: 游戏移动方向是否可能判断 这里为了不让他出现穿墙,跨过方块下落 都做对应的碰撞判断 具体代码如下: 俄罗斯方块旋转变形代码实现 俄

    2024年02月08日
    浏览(45)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包