手撸俄罗斯方块(一)——简单介绍
手撸俄罗斯方块
简单介绍
《俄罗斯方块》(俄语:Тетрис,英语:Tetris),是1980
年末期至1990
年代初期风靡全世界的电脑游戏,是落下型益智游戏的始祖,电子游戏领域的代表作之一,为苏联首个在美国发布的娱乐软件。此游戏最初由阿列克谢·帕基特诺夫
在苏联设计和编写,于1984年6月6日首次发布,当时他正在苏联科学院电算中心工作。此游戏的名称是由希腊语数字“四”的前缀“tetra-”
(因所有落下方块皆由四块组成)和帕基特诺夫最喜欢的运动网球(“tennis”
)拼接而成,华语地区则因游戏为俄罗斯人发明普遍称为“俄罗斯方块”。
数字“四”,显而易见,最初的图形都是由4个小方块组成的,这也是俄罗斯方块的一个特点。游戏的目标是通过控制不同形状的方块,使它们在一个矩形的游戏区域中排列成完整的一行或多行,然后这些完整的行就会消除,给玩家得分。
游戏规则
- 游戏开始时,游戏区域是一个空白的矩形,玩家通过控制方块的移动和旋转,使它们在游戏区域中排列成完整的一行或多行;
- 当一行或多行被完整填满时,这些行就会消除,给玩家得分;
- 当方块堆积到游戏区域的顶部时,游戏结束。
游戏特点
- 游戏简单易上手,但是难度逐渐增加;
- 游戏的速度会随着游戏的进行而逐渐加快;
- 游戏的随机性较大,玩家需要根据当前的情况,灵活地调整方块的位置和旋转。
形状
俄罗斯方块中的方块有7种不同的形状,分别是:
I
型:一字型;J
型:J型;L
型:L型。O
型:方块型;S
型:S型;T
型:T型;Z
型:Z型;
这些形状是由4个小方块组成的,每个小方块都可以旋转,但是旋转的中心不同。如下图:
各种四格骨牌由左至右,上至下的英文字母代号:I、J、L、O、S、T、Z
操作
玩家通过键盘控制方块的移动和旋转,具体操作如下:
←
:向左移动方块;→
:向右移动方块;↑
:旋转方块;↓
:加速下落。
得分
- 消除一行:得
100
分; - 消除两行:得
300
分; - 消除三行:得
700
分; - 消除四行:得
1500
分。
游戏结束
- 当方块堆积到游戏区域的顶部时,游戏结束;
- 游戏结束后,显示游戏结束的提示,并显示玩家的得分。
从开发角度看俄罗斯方块
坐标系
对于所有二维游戏来说,坐标系是一个非常重要的概念。在俄罗斯方块中,我们可以使用一个二维数组来表示游戏区域,数组的每一个元素表示一个方块,数组的索引表示方块的坐标。如下图:
图中区域表示正数的范围。但是实际情况下,我们可以使用一个二维数组来表示游戏区域。而且计算机在绘画过程中也是自上而下自左而右进行的,因此我们需要将坐标进行变换。如下图:
x
轴表示列,y
轴表示行。这样我们就可以通过一个二维数组来表示游戏区域。
对于执行的一个元素,我们可以通过二维数组快速定位到它。
const points = [[]]; // 二维数组
const x = 1; // 列
const y = 2; // 行
const point = points[y][x]; // 获取坐标为(1, 2)的元素
需要注意的是,数组的索引是从0
开始的,因此在实际过程中我们将y = 0看成第一行,x = 0看成第一列。
方块的表示
按照上述坐标体系的定义,我们可以表示任何一个方块,如当表示I
型方块时,我们可以定义一个数组来表示它的形状:
const IHorizonal = [[0, 1, 2, 3]
]
const IVertical = [[0],[1],[2],[3]
]
如下图:
这样我们就可以通过一个数组来表示一个方块的形状。通过类似的方法,我们可以表示其他的6个方块。但是,I方块有两种形态,我们到底使用哪一种作为初始形态呢?
这里面需要做一个约定,我们定义每个方块的初始化形态。定义如下:
I
型:
// 口口口口
J
型:
// 口
// 口口口
L
型:
// 口
// 口口口
O
型:
// 口口
// 口口
S
型:
// 口口
// 口口
T
型:
// 口
// 口口口
Z
型:
// 口口
// 口口
方块的移动
在游戏过程中,方块是可以移动的。分别可以向左移动、向右移动、向下移动。同时移动必须满足如下条件:
- 移动后的方块不能超出游戏区域;即不能超出游戏区域的左边界、右边界和底边界;翻译成程序语言为:
// 向左移动
if (x - 1 >= 0) {x -= 1;
}
// 向右移动
if (x + 1 < width) {x += 1;
}
// 向下移动
if (y + 1 < height) {y += 1;
}
- 移动后的方块不能与其他方块重叠;即不能与其他方块的坐标重叠;翻译成程序语言为:
// 向左移动
if (x - 1 >= 0 && !isOverlap(x - 1, y)) {x -= 1;
}
// 向右移动
if (x + 1 < width && !isOverlap(x + 1, y)) {x += 1;
}
// 向下移动
if (y + 1 < height && !isOverlap(x, y + 1)) {y += 1;
}
上面是描述某个点的情况,实际上判断方块情况,需要所有的点都满足条件。
方块的转换
在游戏过程中,方块是可以旋转的。如下图:
同样的,在旋转过程中,也必须满足如下条件:
-
旋转后的方块不能超出游戏区域;即不能超出游戏区域的左边界、右边界和底边界;
-
旋转后的方块不能与其他方块重叠;即不能与其他方块的坐标重叠。
使用程序语言表述如下:
// 旋转
const isValid = (newShape) => {for (let y = 0; y < newShape.length; y++) {for (let x = 0; x < newShape[y].length; x++) {if (newShape[y][x] && (x < 0 || x >= width || y >= height || isOverlap(x, y))) {return false;}}}return true;
}
const newShape = rotate(shape);
if (isValid(newShape)) {shape = newShape;
}
接下来问题来,如何实现rotate
函数呢?这里面就涉及到了方块的旋转。对于不同的方块,旋转的方式是不同的。
如I型方块,其旋转形态只有两种,分别是横向和纵向。
// I型方块
// 横向
// 口口口口
// 纵向
// 口
// 口
// 口
// 口
但是对于T型等其他方块,其旋转形态是四种的。
// T型方块
// 1
// 口
// 口口口
// 2
// 口
// 口口
// 口
// 3
// 口口口
// 口
// 4
// 口
// 口口
// 口
我们当然可以通过枚举的方式将所有的旋转形态都列出来,但是这样的方式是不可取的。因为这样的方式会使得代码变得复杂,而且不易维护。因此我们需要找到一种更好的方式来实现方块的旋转。
实际上,我们可以通过观察发现,方块的旋转是围绕一个中心点进行的。且旋转方向和角度是固定的。
因此我们可以得出如下结论:
-
方块的旋转是围绕一个中心点进行的;选择的中心点是不动的。
-
方块的旋转方向和角度是固定,我们可以选取逆时针旋转,相对我们定义的坐标系统旋转角度为 π / 2。 故根据旋转公式,新坐标定义如下:
const newX = Math.cos(Math.PI / 2) * (x - centerX) - Math.sin(Math.PI / 2) * (y - centerY) + centerX;const newY = Math.cos(Math.PI / 2) * (y - centerY) + Math.sin(Math.PI / 2) * (x - centerX) + centerY;
即
const newX = -y + centerY + centerX;const newY = x - centerX + centerY;
此处不太了解的可以去看看坐标旋转。
小结
上面我们讨论了俄罗斯方块的基本介绍,从开发的角度来看,我们讨论了坐标系、方块的表示、方块的移动和方块的旋转。那么我们可以通过如下两个实体在表示方块的基本情况。
- Point,坐标点
interface PointAttr {color?: string; // 颜色isReadyToClean?: boolean; // 是否准备消除[key: string]: any; // 其他属性
}class Point {private x: number;private y: number;private attr: PointAttr;constructor(x, y, attr?: PointAttr) {this.x = x;this.y = y;this.attr = attr || {};}
}
每个坐标点均包含了x、y坐标,以及其他属性,如颜色等;
- Block,方块
我们通过Block
来抽象方块,定义如下:
class Block {private points: Point[];private rotateIndex: number; /// 旋转因子constructor(points: Point[]) {this.points = points;}abstract getCenterIndex(): number; // 获取中心点abstract getRotateArray(); number[]; // 获取旋转数组
}
其他的方块均继承自Block,实现getCenterIndex
和getRotateArray
方法。
如IBlcok,其实现如下:
class IBlock extends Block {getCenterIndex() {return 1;}getRotateArray() {return [Math.PI / 2, // 第一次旋转相对于初始位置的角度-Math.PI / 2 // 第二次旋转相对于第一次旋转的角度]}
}
通过继承的关系,分别实现LBlock
、JBlock
、OBlock
、TBlock
、SBlock
、ZBlock
。
本章内容暂时就到这里,后续章节我们将继续讨论如何实现俄罗斯方块的游戏逻辑。
详细内容可以关注我的github账号: https://github.com/shushanfx/tetris
接下来我将从如下几个方面来阐述:
- 手撸俄罗斯方块——游戏设计
- 手撸俄罗斯方块——游戏核心模块设计
- 手撸俄罗斯方块——渲染与交互
- 手撸俄罗斯方块——游戏主题
相关文章:

手撸俄罗斯方块(一)——简单介绍
手撸俄罗斯方块 简单介绍 《俄罗斯方块》(俄语:Тетрис,英语:Tetris),是1980年末期至1990年代初期风靡全世界的电脑游戏,是落下型益智游戏的始祖,电子游戏领域的代表作之一&a…...
构建LangChain应用程序的示例代码:61、如何使用 LangChain 和 LangSmith 优化链
本示例介绍如何使用 LangChain 和 LangSmith 优化链。 设置 我们将为 LangSmith 设置环境变量,并加载相关数据 import osos.environ["LANGCHAIN_PROJECT"] "movie-qa" # 设置 LANGCHAIN_PROJECT 环境变量为 "movie-qa"import pan…...
Android系统通过属性设置来控制log输出的方案
Android系统通过属性设置来控制log输出的方案 背景 项目中经常需要在针对性的模块或者文件,分析问题的时候输出Log,但问题分析完成后,又由于性能问题,需要关闭这些log输出。当前大多数情况下是控制整个系统的log等级来实现&#…...
JavaDoc的最佳实践
文章目录 一、JavaDoc 使用说明1.1 什么是 JavaDoc1.2 文档注释结构1.3 常见的 Javadoc 标签 二、文档最佳实践2.1 注释原则2.2 实际案例 参考资料 一、JavaDoc 使用说明 1.1 什么是 JavaDoc JavaDoc 是一款能根据源代码中的文档注释来产生 HTML 格式的 API 文档的工具。 Jav…...

数字力量助西部职教全面提升——唯众品牌大数据、人工智能系列产品中标甘肃庆阳职院数字经济人才培养基地!
近日,唯众品牌凭借在大数据和人工智能领域深耕多年的技术积累和卓越产品,成功中标庆阳职业技术学院全国一体化算力网络国家枢纽节点数字经济人才培养基地项目,标志着唯众在助力西部职业教育与数字经济融合发展的新征程上迈出了坚实的一步。 …...
Swagger的原理及应用详解(四)
本系列文章简介: 在当今快速发展的软件开发领域,特别是随着微服务架构和前后端分离开发模式的普及,API(Application Programming Interface,应用程序编程接口)的设计与管理变得愈发重要。一个清晰、准确且易于理解的API文档不仅能够提升开发效率,还能促进前后端开发者之…...
Elasticsearch7.10集群搭建
Elasticsearch详细介绍: Elasticsearch 是一个分布式、RESTful 风格的搜索和分析引擎。它的核心基于 Apache Lucene,能够处理海量的数据,并支持实时的全文搜索。以下是关于 Elasticsearch 的详细介绍。 一、基本概念 索引(Index…...
SMU Summer 2024 Contest Round 3
A.Hcode OnlineJudge 先用欧拉筛把质数预处理出来,然后枚举左端点的质数,只需要询问右端点是不是质数并取差值的min就行了 #include<bits/stdc.h> #define endl \n #define mk make_pair #define int long long using namespace std; typedef lon…...
uniapp 封装瀑布流组件
思路: 1.coulumns:需要分成几列 2.如何分布数据 3.计算每列的宽度 4.图片进行高度自适应 <template><view :style"{ margin: boxM }"><view class"flex flex-justify-start bg-red" style"background-colo…...

pd虚拟机去虚拟化是什么意思?pd虚拟机去虚拟化教程 PD虚拟机优化设置
Parallels Desktop for Mac(PD虚拟机)去虚拟化是指在虚拟机(Virtual Machine,简称 VM)中禁用或减少虚拟化层的影响,使其表现更接近于物理机。这种操作通常用于提高虚拟机的性能或解决某些软件兼容性问题。具…...
低代码研发项目管理流程优化:提效与创新的双重驱动
随着信息技术的迅猛发展,软件项目的规模和复杂度日益增加,传统的软件开发方式已经难以满足快速迭代和高效交付的需求。在这一背景下,低代码平台应运而生,以其高效、灵活、易用的特点,迅速成为软件行业的新宠。然而&…...

32位版 C 库函数time 将在 2038 年溢出,那到时候,它该何去何从
简单地说,通常不必担心,在64位操作系统已经成为主流的今天这基本上不是问题(在写这篇回答的时候,我才发现我甚至找不到32位的机器来测试)刚好我有一些资料,是我根据网友给的问题精心整理了一份「32库函数的…...
C语言 printf函数缓冲机制
printf不立即打印到stdout的原因 printf函数使用了缓冲机制。当我们调用printf时,输出通常不会立即显示在屏幕上,而是先存储在一个缓冲区中。这是为了提高I/O操作的效率。 缓存数据输出的原理 stdio库维护了一个缓冲区。当缓冲区满了,或者在特定条件下,缓冲区的内容会被刷新…...

【Linux进阶】文件系统8——硬链接和符号连接:ln
在Linux下面的链接文件有两种, 一种是类似Windows的快捷方式功能的文件,可以让你快速地链接到目标文件(或目录);另一种则是通过文件系统的inode 链接来产生新文件名,而不是产生新文件,这种称为硬链接&…...
代码随想录算法训练营Day64|拓扑排序(卡码网117)、dijkstra朴素版
拓扑排序 117. 软件构建 (kamacoder.com) 拓扑排序简单的说是将一个有向图转为线性的排序。 它将图中的所有结点排序成一个线性序列,使得对于任何的边uv,结点u在序列中都出现在结点v之前,这样的序列满足图中所有的前驱-后继关系。 拓扑排…...

neo4j 图数据库:Cypher 查询语言、医学知识图谱
neo4j 图数据库:Cypher 查询语言、医学知识图谱 Cypher 查询语言创建数据查询数据查询并返回所有节点查询并返回所有带有特定标签的节点查询特定属性的节点及其所有关系和关系的另一端节点查询从名为“小明”的节点到名为“小红”的节点的路径 更新数据更新一个节点…...

数据结构基础--------【二叉树基础】
二叉树基础 二叉树是一种常见的数据结构,由节点组成,每个节点最多有两个子节点,左子节点和右子节点。二叉树可以用来表示许多实际问题,如计算机程序中的表达式、组织结构等。以下是一些二叉树的概念: 二叉树的深度&a…...

数据开源 | Magic Data大模型高质量十万轮对话数据集
能够自然的与人类进行聊天交谈,是现今的大语言模型 (LLM) 区别于传统语言模型的重要能力之一,近日OpenAI推出的GPT-4o给我们展示了这样的可能性。 对话于人类来说是与生俱来的,但构建具备对话能力的大模型是一项不小的挑战,收集高…...
webpack之ts打包
tsconfig.json配置 // 是否对js文件进行编译,默认false"allowJs": true,// 是否检查js代码是否符合语法规范,默认false(引入的外部文件有可能语法有问题)"checkJs": true, allowJs和checkJs基本是同时出现,因为有了allowJs 这个检查…...

MATLAB数据统计描述和分析
描述性统计就是搜集、整理、加工和分析统计数据, 使之系统化、条理化,以显示出数据资料的趋势、特征和数量关系。它是统计推断的基础,实用性较强,在数学建模的数据描述部分经常使用。 目录 1.频数表和直方图 2 .统计量 3.统计…...
DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径
目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...

遍历 Map 类型集合的方法汇总
1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...
STM32+rt-thread判断是否联网
一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...

第一篇:Agent2Agent (A2A) 协议——协作式人工智能的黎明
AI 领域的快速发展正在催生一个新时代,智能代理(agents)不再是孤立的个体,而是能够像一个数字团队一样协作。然而,当前 AI 生态系统的碎片化阻碍了这一愿景的实现,导致了“AI 巴别塔问题”——不同代理之间…...
Rust 异步编程
Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...

自然语言处理——Transformer
自然语言处理——Transformer 自注意力机制多头注意力机制Transformer 虽然循环神经网络可以对具有序列特性的数据非常有效,它能挖掘数据中的时序信息以及语义信息,但是它有一个很大的缺陷——很难并行化。 我们可以考虑用CNN来替代RNN,但是…...

成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战
在现代战争中,电磁频谱已成为继陆、海、空、天之后的 “第五维战场”,雷达作为电磁频谱领域的关键装备,其干扰与抗干扰能力的较量,直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器,凭借数字射…...

使用 SymPy 进行向量和矩阵的高级操作
在科学计算和工程领域,向量和矩阵操作是解决问题的核心技能之一。Python 的 SymPy 库提供了强大的符号计算功能,能够高效地处理向量和矩阵的各种操作。本文将深入探讨如何使用 SymPy 进行向量和矩阵的创建、合并以及维度拓展等操作,并通过具体…...
Java线上CPU飙高问题排查全指南
一、引言 在Java应用的线上运行环境中,CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时,通常会导致应用响应缓慢,甚至服务不可用,严重影响用户体验和业务运行。因此,掌握一套科学有效的CPU飙高问题排查方法&…...

佰力博科技与您探讨热释电测量的几种方法
热释电的测量主要涉及热释电系数的测定,这是表征热释电材料性能的重要参数。热释电系数的测量方法主要包括静态法、动态法和积分电荷法。其中,积分电荷法最为常用,其原理是通过测量在电容器上积累的热释电电荷,从而确定热释电系数…...