整体游戏架构
游戏主循环(Game)
├─ 场景系统
│ ├─ 开始场景(BeginScene)
│ ├─ 游戏场景(GameScene)
│ └─ 结束场景(EndScene)
│
├─ 方块系统
│ ├─ 方块控制器(BlockWorker)
│ ├─ 方块形态数据(BlockInfo)
│ └─ 绘制单元(DrawObject)
│
├─ 地图系统(Map)
├─ 输入系统(InputCheak)
├─ 坐标系统(Position)
└─ 接口规范(IDraw, ISceneUpdate)
这个俄罗斯方块游戏采用经典的三层架构:
表现层:场景渲染、用户输入
逻辑层:游戏规则、方块行为、碰撞检测
数据层:方块形态数据、地图状态、坐标计算
各模块通过接口解耦,实现了良好的扩展性和可维护性。
using System;
using System.Collections.Generic;
using System.Text;
namespace 俄罗斯方块
{
interface ISceneUpdate
{
void Update();
}
}
ISceneUpdate.cs
作用:场景更新接口
核心功能:
定义场景更新接口
Update()
确保所有场景实现统一的更新逻辑
using System;
using System.Collections.Generic;
using System.Text;
namespace 俄罗斯方块
{
abstract class BeginOrEndBaseScene:ISceneUpdate
{
protected int selIndex = 0;
protected string strTilte;
protected string strOne;
public abstract void EnterJDoing();
public void Update()
{
//开始和结束场景的游戏逻辑
//选择当前的选项,然后监听键盘输入wsj
//后续在控制台输出的文本都会显示为白色,直至再次对ForegroundColor属性进行修改
Console.ForegroundColor = ConsoleColor.White;
//显示标题
Console.SetCursorPosition(Game.w / 2 - strTilte.Length, 5);
Console.Write(strTilte);
//显示下方的选项
Console.SetCursorPosition(Game.w / 2 - strOne.Length, 8);
Console.ForegroundColor = selIndex == 0 ? ConsoleColor.Red : ConsoleColor.White;
Console.Write(strOne);
Console.SetCursorPosition(Game.w / 2 - 4, 10);
Console.ForegroundColor = selIndex == 1 ? ConsoleColor.Red : ConsoleColor.White;
Console.Write("结束游戏");
//检测输入
switch (Console.ReadKey(true).Key)
{
case ConsoleKey.W:
--selIndex;
if (selIndex < 0)
selIndex = 0;
break;
case ConsoleKey.S:
++selIndex;
if (selIndex > 1)
selIndex = 1;
break;
case ConsoleKey.J:
EnterJDoing();
break;
}
}
}
}
BeginOrEndBaseScene.cs
作用:游戏开始/结束场景的抽象基类
核心功能:
处理场景选择逻辑(W/S键选择选项)
显示标题和选项文本
定义抽象方法
EnterJDoing()
供子类实现具体行为统一管理场景的输入处理和界面绘制
using System;
using System.Collections.Generic;
using System.Text;
namespace 俄罗斯方块
{
class BeginScene : BeginOrEndBaseScene
{
public BeginScene ()
{
strTilte = "俄罗斯方块";
strOne = "开始游戏";
}
public override void EnterJDoing()
{
if(selIndex ==0)
{
Game.ChangeScene(E_SceneType.Game);
}
else
{
Environment.Exit(0);
}
}
}
}
BeginScene.cs
作用:游戏开始场景
核心功能:
继承自
BeginOrEndBaseScene
设置开始界面文本("俄罗斯方块"、"开始游戏")
实现开始游戏和退出游戏的功能选择逻辑
using System;
using System.Collections.Generic;
using System.Text;
namespace 俄罗斯方块
{
class EndScene : BeginOrEndBaseScene
{
public EndScene()
{
strTilte = "游戏结束";
strOne = "回到开始界面";
}
public override void EnterJDoing()
{
if(selIndex ==0)
{
Game.ChangeScene(E_SceneType.Begin);
}
else
{
Environment.Exit(0);
}
}
}
}
EndScene.cs
作用:游戏结束场景
核心功能:
继承自
BeginOrEndBaseScene
设置结束界面文本("游戏结束"、"回到开始界面")
实现返回开始界面或退出游戏的功能
using System;
using System.Collections.Generic;
using System.Text;
namespace 俄罗斯方块
{
interface IDraw
{
void Draw();
}
}
IDraw.cs
作用:绘制接口
核心功能:
定义统一的绘制接口
Draw()
确保所有可绘制对象实现统一接口
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace 俄罗斯方块
{
enum E_SceneType
{
Begin,
Game,
End,
}
class Game
{
//要想得到控制台的数值,就要把Game类中的成员用const修饰为常量,方便其他类使用
public const int w = 50;
public const int h = 35;
public static ISceneUpdate nowScene;
public Game ()
{
//隐藏光标
Console.CursorVisible = false;
//设置游戏窗口大小
Console.SetWindowSize(w, h);
//设置缓冲区
Console.SetBufferSize(w, h);
ChangeScene(E_SceneType.Begin);
}
public void StartGame()
{
while (true)
{
if (nowScene !=null)
{
nowScene.Update();
}
}
}
public static void ChangeScene(E_SceneType type)
{
//切换场景之前先清理之前场景内容
Console.Clear();
switch (type)
{
case E_SceneType.Begin:
nowScene = new BeginScene();
break;
case E_SceneType.Game:
nowScene = new GameScene();
break;
case E_SceneType.End:
nowScene = new EndScene();
break;
}
}
}
}
Game.cs
作用:游戏主控制器
核心功能:
初始化游戏窗口和缓冲区
管理场景切换(开始→游戏→结束)
维护游戏主循环
提供全局常量(窗口宽高)
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace 俄罗斯方块
{
class GameScene : ISceneUpdate
{
Map map;
BlockWorker blockWorker;
//Thread inputThread;
//bool isRunning = true;
public GameScene ()
{
map = new Map(this);
blockWorker = new BlockWorker();
//inputThread = new Thread(CheakInputThread);
设置成后台线程,生命周期由主线程决定
//inputThread.IsBackground = true;
//inputThread.Start();
InputCheak.Instance.action += CheakInputThread;
}
private void CheakInputThread()
{
//while (true)
//{
if (Console.KeyAvailable)
{
//为了避免影响主线程,在输入 后加锁
lock(blockWorker)
{
switch (Console.ReadKey(true).Key)
{
case ConsoleKey.LeftArrow:
if (blockWorker.IsChange(E_ChangeDir.Left, map))
blockWorker.Change(E_ChangeDir.Left);
break;
case ConsoleKey.RightArrow:
if (blockWorker.IsChange(E_ChangeDir.Right, map))
blockWorker.Change(E_ChangeDir.Right);
break;
case ConsoleKey.A:
if (blockWorker.IsMoveRL(E_ChangeDir.Left, map))
blockWorker.MoveRL(E_ChangeDir.Left);
break;
case ConsoleKey.D:
if (blockWorker.IsMoveRL(E_ChangeDir.Right, map))
blockWorker.MoveRL(E_ChangeDir.Right);
break;
case ConsoleKey.S:
if (blockWorker.IsMove(map))
blockWorker.AutoMove();
break;
}
}
//}
}
}
public void StopThread()
{
//isRunning = false;
//inputThread = null;
InputCheak.Instance.action -= CheakInputThread;
}
public void Update()
{
lock (blockWorker)
{
map.Draw();
blockWorker.Draw();
if (blockWorker.IsMove(map))
blockWorker.AutoMove();
}
Thread.Sleep(200);
}
}
}
GameScene.cs
作用:游戏主场景
核心功能:
整合地图和方块控制器
实现输入监听线程(处理玩家操作)
管理游戏主循环的更新逻辑
协调方块移动和地图渲染
using System;
using System.Collections.Generic;
using System.Text;
namespace 俄罗斯方块
{
struct Position
{
public int x;
public int y;
public Position(int x,int y)
{
this.x = x;
this.y = y;
}
public static bool operator ==(Position pos1,Position pos2)
{
if (pos1.x == pos2.x && pos1.y == pos2.y)
return true;
return false;
}
public static bool operator !=(Position pos1, Position pos2)
{
if (pos1.x == pos2.x && pos1.y == pos2.y)
return false;
return true;
}
public static Position operator +(Position pos1, Position pos2)
{
Position pos = new Position(pos1.x + pos2.x, pos1.y + pos2.y);
return pos;
}
}
}
Position.cs
作用:坐标系统
核心功能:
定义二维坐标结构
重载坐标运算操作符(==, !=, +)
提供便捷的坐标计算功能
using System;
using System.Collections.Generic;
using System.Text;
namespace 俄罗斯方块
{
class Map:IDraw
{
public GameScene gameScene;
//固定墙壁
public List<DrawObject> wallList = new List<DrawObject>();
//动态墙壁
public List<DrawObject> dymaicWallList = new List<DrawObject>();
//声明一个数组来记录每一行的位置
public int[] recordInfo;
//方便 外部访问地图数值
public int w;
public int h;
public Map(GameScene scene)
{
this.gameScene = scene;
h = Game.h - 6;
w = 0;
for (int i = 0; i < Game.w; i +=2)
{
wallList.Add(new DrawObject(E_DrawType.Wall, i, Game.h - 6));
++w;
}
w -= 2;
for (int i = 0; i < Game.h - 6; i++)
{
wallList.Add(new DrawObject(E_DrawType.Wall, 0, i));
wallList.Add(new DrawObject(E_DrawType.Wall, Game.w - 2, i));
}
recordInfo = new int[h];
}
public void Draw()
{
for (int i = 0; i < wallList .Count; i++)
{
wallList[i].Draw();
}
//绘制动态墙壁
for (int i = 0; i < dymaicWallList .Count ; i++)
{
dymaicWallList[i].Draw();
}
}
public void ClearDraw()
{
for (int i = 0; i < dymaicWallList.Count; i++)
{
dymaicWallList[i].Clear();
}
}
//外部添加动态墙壁的函数
public void AddWalls(List<DrawObject>walls)
{
for (int i = 0; i < walls .Count ; i++)
{
//传递方块进来,将方块类型改为墙壁类型
walls[i].ChangeType(E_DrawType.Wall);
dymaicWallList.Add(walls[i]);
//当方块顶满了,需要结束游戏
if (walls [i].pos .y<0)
{
//关闭多线程
gameScene.StopThread();
Game.ChangeScene(E_SceneType.End);
return;
}
recordInfo[h - 1 - walls[i].pos.y] += 1;
}
//先把之前的动态小方块擦掉
ClearDraw();
//检测移除
CheakClear();
//再绘制动态小方块
Draw();
}
public void CheakClear()
{
List<DrawObject> delList = new List<DrawObject>();
for (int i = 0; i < recordInfo .Length ; i++)
{
if(recordInfo [i]==w)
{
//这一行的所有小方块移除
for (int j = 0; j < dymaicWallList .Count ; j++)
{
//当前通过动态方块的y计算他在哪一行如果行号和当前记录索引一致
//就证明应该删除
if(i==h-1-dymaicWallList [j].pos .y )
{
//移除这个方块,为了安全移除,添加一个记录列表
delList.Add(dymaicWallList[j]);
}
//让这行之上的所有小方块下移一格
else if(h-1-dymaicWallList [j].pos .y >i)
{
++dymaicWallList[j].pos.y;
}
}
//移除待删除的小方块
for (int j = 0; j < delList .Count ; j++)
{
dymaicWallList.Remove(delList[j]);
}
//3.记录小方块数量的数组从上到下迁移
for (int j = i; j < recordInfo .Length -1; j++)
{
recordInfo[j] = recordInfo[j + 1];
}
//置空最顶的计数
recordInfo[recordInfo.Length - 1] = 0;
//垮掉一行后,再从头检验是否跨层
CheakClear();
break;
}
}
}
}
}
Map.cs
作用:游戏地图管理器
核心功能:
管理静态墙壁和动态方块(已落地方块)
实现行消除检测和消除后的方块下落
处理方块落地后的地图更新
检测游戏结束条件(方块堆到顶部)
using System;
using System.Collections.Generic;
using System.Text;
namespace 俄罗斯方块
{
class BlockInfo
{
private List<Position[]> list;
public BlockInfo(E_DrawType type)
{
list = new List<Position[]>();
switch (type)
{
case E_DrawType.Cube:
list.Add(new Position[3]{new Position(2,0),new Position (0,1),new Position (2,1)});
break;
case E_DrawType.Line:
list.Add(new Position[3] { new Position(0, -1), new Position(0, 1), new Position(0, 2) });
list.Add(new Position[3] { new Position(-4,0), new Position(-2,0), new Position(2,0) });
list.Add(new Position[3] { new Position(0,-2), new Position(0,-1), new Position(0, 1) });
list.Add(new Position[3] { new Position(-2,0), new Position(2,0), new Position(4,0) });
break;
case E_DrawType.Tank:
list.Add(new Position[3] { new Position(-2,0), new Position(2,0), new Position(0,1) });
list.Add(new Position[3] { new Position(-2,0), new Position(0,-1), new Position(0,1) });
list.Add(new Position[3] { new Position(-2,0), new Position(0,-1), new Position(2,0) });
list.Add(new Position[3] { new Position(2,0), new Position(0, -1), new Position(2, 0) });
break;
case E_DrawType.Left_Ladder:
list.Add(new Position[3] { new Position(0,-1), new Position(2, 0), new Position(2,1) });
list.Add(new Position[3] { new Position(-2, 1), new Position(2,0), new Position(0, 1) });
list.Add(new Position[3] { new Position(-2,-1), new Position(-2,0), new Position(0,1) });
list.Add(new Position[3] { new Position(-2,0), new Position(0,-1), new Position(-2,-1) });
break;
case E_DrawType.Right_Ladder:
list.Add(new Position[3] { new Position(0, -1), new Position(-2, 0), new Position(-2, 1) });
list.Add(new Position[3] { new Position(-2, -1), new Position(0, -1), new Position(2,0) });
list.Add(new Position[3] { new Position(2, -1), new Position(2, 0), new Position(0, 1) });
list.Add(new Position[3] { new Position(0,1), new Position(2,1), new Position(-2, 0) });
break;
case E_DrawType.Legy_Long_Ladder:
list.Add(new Position[3] { new Position(-2,-1), new Position(0,-1), new Position(0,1) });
list.Add(new Position[3] { new Position(2,-1), new Position(-2,0), new Position(2,0) });
list.Add(new Position[3] { new Position(0, -1), new Position(2, 1), new Position(0, 1) });
list.Add(new Position[3] { new Position(2,0), new Position(-2, 0), new Position(-2, 1) });
break;
case E_DrawType.Right_Long_Ladder:
list.Add(new Position[3] { new Position(0, -1), new Position(0, 1), new Position(2, -1) });
list.Add(new Position[3] { new Position(2, 0), new Position(-2, 0), new Position(2, 1) });
list.Add(new Position[3] { new Position(0, -1), new Position(-2, 1), new Position(0, 1) });
list.Add(new Position[3] { new Position(2, -1), new Position(-2, 0), new Position(2, 0) });
break;
default:
break;
}
}
//索引器
public Position[] this[int index]
{
get
{
if (index < 0)
return list[0];
else if (index >= list.Count)
return list[list.Count-1];
else
return list[index];
}
}
//外部访问list长度属性
public int Count { get => list.Count; }
}
}
BlockInfo.cs
作用:方块形态数据管理
核心功能:
存储所有方块类型(7种)的各种旋转形态坐标
通过索引器提供不同旋转状态的坐标数据
管理方块形态的数量和访问逻辑
using System;
using System.Collections.Generic;
using System.Text;
namespace 俄罗斯方块
{
enum E_ChangeDir
{
Left,
Right,
}
class BlockWorker:IDraw
{
public List<DrawObject> blocks;
public Dictionary<E_DrawType, BlockInfo> blockInfoDic;
public BlockInfo nowBlcockInfo;
//记录当前形状
public int nowBlockIndex;
public BlockWorker()
{
//初始化方块信息
blockInfoDic = new Dictionary<E_DrawType, BlockInfo>()
{
{E_DrawType .Cube ,new BlockInfo(E_DrawType .Cube) },
{E_DrawType .Line ,new BlockInfo (E_DrawType.Line ) },
{E_DrawType .Tank ,new BlockInfo (E_DrawType.Tank ) },
{E_DrawType .Left_Ladder ,new BlockInfo (E_DrawType.Left_Ladder) },
{E_DrawType .Right_Ladder ,new BlockInfo (E_DrawType.Right_Ladder) },
{E_DrawType .Legy_Long_Ladder ,new BlockInfo (E_DrawType.Legy_Long_Ladder ) },
{E_DrawType .Right_Long_Ladder ,new BlockInfo (E_DrawType.Right_Long_Ladder ) }
};
//随机一个方块
RandomCreateBlock();
}
public void Draw()
{
for (int i = 0; i < blocks.Count ; i++)
{
blocks[i].Draw();
}
}
public void RandomCreateBlock()
{
//随机方块类型
Random r = new Random();
E_DrawType type = (E_DrawType)r.Next(1, 8);
//每次新建一个砖块,其实是创建4个小方形
blocks = new List<DrawObject>()
{
new DrawObject (type),
new DrawObject(type),
new DrawObject (type),
new DrawObject (type),
};
//初始化方块信息
//原点位置 我们随机 方块list中第0个位置就是原点的位置
blocks[0].pos = new Position(24, -5);
//其他三个方块的位置
//取出方块的形态信息来进行具体的随机
//应该把取出来的方块具体形态信息 存起来 之后用于变形
nowBlcockInfo = blockInfoDic[type];
//随机几种形态中的一种来设置方块的信息
nowBlockIndex = r.Next(0, nowBlcockInfo.Count);
//取出其中一种形态的坐标信息
Position[] pos = nowBlcockInfo[nowBlockIndex];
for (int i = 0; i < pos .Length ; i++)
{
//取出来的pos是相对原点方块的坐标 所以需要进行计算
blocks[i + 1].pos = blocks[0].pos + pos[i];
}
}
public void ClearDraw()
{
for (int i = 0; i < blocks .Count ; i++)
{
blocks[i].Clear();
}
}
public void Change(E_ChangeDir type)
{
ClearDraw();
switch (type)
{
case E_ChangeDir.Left:
--nowBlockIndex;
if (nowBlockIndex < 0)
nowBlockIndex = nowBlcockInfo.Count - 1;
break;
case E_ChangeDir.Right:
++nowBlockIndex;
if (nowBlockIndex >= nowBlcockInfo .Count)
nowBlockIndex = 0;
break;
}
Position[] pos = nowBlcockInfo[nowBlockIndex];
for (int i = 0; i < pos.Length; i++)
{
//取出来的pos是相对原点方块的坐标 所以需要进行计算
blocks[i + 1].pos = blocks[0].pos + pos[i];
}
Draw();
}
public bool IsChange(E_ChangeDir type,Map map)
{
//要想判断是否可以变形,首先要模拟变形
//判断是否超出墙壁
int tempIndex = nowBlockIndex;
Position tempPos;
Position[] nowPos = nowBlcockInfo[tempIndex];
for (int i = 0; i < nowPos .Length ; i++)
{
tempPos = blocks[0].pos + nowPos[i];
if (tempPos .x < 2 || tempPos.x >= Game .w-2 || tempPos.y >= map.h)
return false;
}
//判断是否和地图上的动态方块重合
for (int i = 0; i < nowPos .Length; i++)
{
tempPos = blocks[0].pos + nowPos[i];
for (int j = 0; j < map.dymaicWallList .Count ; j++)
{
if (tempPos == map.dymaicWallList[j].pos)
return false;
}
}
return true;
}
public void MoveRL(E_ChangeDir type)
{
ClearDraw();
//得到方块偏移量
Position pos = new Position(type == E_ChangeDir.Left ? -2 : 2, 0);
for (int i = 0; i < blocks.Count ; i++)
{
blocks[i].pos += pos;
}
Draw();
}
public bool IsMoveRL(E_ChangeDir type,Map map)
{
//判断和墙壁不能重合
Position movePos = new Position(type == E_ChangeDir.Left ? -2 : 2, 0);
Position pos;
for (int i = 0; i < blocks .Count ; i++)
{
pos = blocks[i].pos + movePos;
if (pos.x < 2 || pos.x >= Game.w - 2)
return false;
}
//判断和动态方块不能重合
for (int i = 0; i < blocks.Count ; i++)
{
pos = blocks[i].pos + movePos;
for (int j = 0; j <map.dymaicWallList .Count ; j++)
{
if (pos == map.dymaicWallList[j].pos)
return false;
}
}
return true;
}
//让方块自动往下移动
public void AutoMove()
{
ClearDraw();
Position downMove = new Position(0, 1);
for (int i = 0; i < blocks .Count ; i++)
{
blocks[i].pos += downMove;
}
Draw();
}
//让方块碰到边界时停下逻辑判断
public bool IsMove(Map map)
{
Position movePos = new Position(0, 1);
Position pos;
for (int i = 0; i < blocks .Count ; i++)
{
pos = blocks[i].pos + movePos;
if (pos.y >= map.h)
{
//停下来,给予地图动态方块
map.AddWalls(blocks);
//随机创建新的方块
RandomCreateBlock();
return false;
}
}
//不能让方块下落碰到动态方块
for (int i = 0; i < blocks .Count ; i++)
{
pos = blocks[i].pos + movePos;
for (int j = 0; j < map .dymaicWallList .Count ; j++)
{
if (pos == map.dymaicWallList[j].pos)
{
//停下来,给予地图动态方块
map.AddWalls(blocks);
//随机创建新的方块
RandomCreateBlock();
return false;
}
}
}
return true;
}
}
}
BlockWorker.cs
作用:方块行为控制中心
核心功能:
管理当前活动方块的状态
处理方块的创建、旋转、移动和下落
检测方块与地图的碰撞
实现方块变形和移动的物理规则
using System;
using System.Collections.Generic;
using System.Text;
namespace 俄罗斯方块
{
enum E_DrawType
{
Wall,
Cube,
Line,
Tank,
Left_Ladder,
Right_Ladder,
Legy_Long_Ladder,
Right_Long_Ladder,
}
class DrawObject : IDraw
{
public Position pos;
public E_DrawType type;
public DrawObject(E_DrawType type)
{
this.type = type;
}
public DrawObject(E_DrawType type,int x,int y):this(type)
{
this.pos = new Position(x, y);
}
public void Clear()
{
if (pos.y < 0)
return;
Console.SetCursorPosition(pos.x, pos.y);
Console.Write(" ");
}
public void Draw()
{
if (pos.y < 0)
return;
Console.SetCursorPosition(pos.x, pos.y);
switch (type)
{
case E_DrawType.Wall:
Console.ForegroundColor = ConsoleColor.Red;
break;
case E_DrawType.Cube:
Console.ForegroundColor = ConsoleColor.Blue;
break;
case E_DrawType.Line:
Console.ForegroundColor = ConsoleColor.Yellow;
break;
case E_DrawType.Tank:
Console.ForegroundColor = ConsoleColor.Cyan;
break;
case E_DrawType.Left_Ladder:
case E_DrawType.Right_Ladder:
Console.ForegroundColor = ConsoleColor.DarkCyan;
break;
case E_DrawType.Legy_Long_Ladder:
case E_DrawType.Right_Long_Ladder:
Console.ForegroundColor = ConsoleColor.Magenta;
break;
}
Console.Write("■");
}
public void ChangeType(E_DrawType type)
{
this.type = type;
}
}
}
DrawObject.cs
作用:游戏对象渲染单元
核心功能:
定义所有可绘制对象的类型(墙壁、各种方块)
管理对象的屏幕位置和绘制颜色
实现对象的绘制和清除方法
处理对象类型转换(如方块落地变为墙壁)
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace 俄罗斯方块
{
class InputCheak
{
public event Action action;
public Thread inputThread;
private static InputCheak instance = new InputCheak();
public static InputCheak Instance
{
get
{
return instance;
}
}
private InputCheak ()
{
inputThread = new Thread(CheakInput);
inputThread.IsBackground = true;
inputThread.Start();
}
private void CheakInput()
{
while (true)
{
action?.Invoke();
}
}
}
}
InputCheak.cs
作用:输入监听管理器
核心功能:
单例模式实现全局输入监听
使用独立线程持续检测键盘输入
通过事件机制将输入传递给游戏场景
using System;
namespace 俄罗斯方块
{
class Program
{
static void Main(string[] args)
{
Game game = new Game();
game.StartGame();
}
}
}
Program.cs
作用:程序入口
核心功能:
创建游戏实例
启动游戏主循环