用C#编写俄罗斯方块游戏-含代码
效果展示
简介:
俄罗斯方块是很多人儿时玩过的游戏,作者本人也不例外,那是我玩的是手持式液晶屏游戏机,其趣味性极强,成为了儿时不可或缺的美好记忆。本文展示如何通过C#设计这样一款桌面游戏,同时也展示了如何规范的编写代码,对于C#初学者或迫切需要规范编码的人群非常友好。
项目结构
代码将游戏分成两部分:
1、Tetris.Core是一个动态链接库(DLL),他主要实现游戏的数据结构、逻辑以及算法,其实现与图形界面完全无关,所以可以方便的移植到别的UI实现方式,如WPF、Web,甚至可以移植到Web,而不需要修改Tetris.Core中的代码。
2、TetrisWF是实现展示游戏的UI模块,砖块的绘制和用户的交互动作都由他来实现,由于游戏功能已经在Core中实现,所以此模块的代码相对较少。
游戏核心类Game的实现代码
using System;
using System.Drawing;
using System.Timers;
namespace Tetris.Core
{
/// <summary>
/// 定义游戏对象
/// </summary>
public class Game
{
private Timer tmrEngine = null;//游戏执行时钟或引擎
#region 基础数据结构
/// <summary>
/// 获取游戏当前的分数,从0开始
/// </summary>
public int Score { get; private set; }
/// <summary>
/// 获取游戏当前等级, 从1开始
/// </summary>
public int Level { get; private set; }
/// <summary>
/// 墙体对象
/// </summary>
public Wall Wall { get; }
/// <summary>
/// 获取当前砖块对象
/// </summary>
public Block Block { get; private set; }
/// <summary>
/// 获取当前砖块的中心点位置
/// </summary>
public Point CurrentBlockPosition { get; private set; } = Point.Empty;
/// <summary>
/// 获取下一个砖块对象
/// </summary>
public Block NextBlock { get; private set; }
#endregion
/// <summary>
/// 设置或获取游戏的绘制宿主
/// </summary>
public IDrawing DrawingHost { get; set; } = null;
/// <summary>
/// 获取一个值,该值指示游戏是否已经结束
/// </summary>
public bool IsGameOver { get; private set; } = false;
/// <summary>
/// 获取砖块仓库对象
/// </summary>
private BlockStore BlockStore { get; } = null;
/// <summary>
/// 游戏结束时触发
/// </summary>
public event EventHandler GameOver = null;
public Game(int wallWidth, int wallHeight, BlockStore blockStore)
{
this.Block = null;
this.BlockStore = blockStore;
this.NextBlock = blockStore.CreateNextBlock();
this.Wall = new Wall(wallWidth, wallHeight, Color.Black);
}
/// <summary>
/// 开始游戏
/// </summary>
public void Start()
{
if (IsGameOver)
{
Wall.Reset();
IsGameOver = false;
}
if (tmrEngine == null)
{
tmrEngine = new Timer();
tmrEngine.Interval = 500;
tmrEngine.Elapsed += TmrEngine_Elapsed;
tmrEngine.AutoReset = true;
Reset();
}
tmrEngine.Start();
}
/// <summary>
/// 获取一个值,该值指示游戏是否在运行
/// </summary>
public bool IsStarted
{
get
{
if (tmrEngine == null)
return false;
return tmrEngine.Enabled;
}
}
/// <summary>
/// 暂停正在进行的游戏
/// </summary>
public void Stop()
{
if (tmrEngine != null)
{
tmrEngine.Stop();
}
}
/// <summary>
/// 复位游戏数据
/// </summary>
public void Reset()
{
if (tmrEngine != null)
tmrEngine.Interval = 500;
Score = 0;
Level = 1;
}
private void TmrEngine_Elapsed(object sender, ElapsedEventArgs e)
{
if (this.Block == null)
{
if (!PlaceBlockToTop(this.NextBlock))
{
//当放不下新砖块时,游戏便可以结束了
IsGameOver = true;
Stop();
Redraw();
GameOver?.Invoke(this, EventArgs.Empty);
return;
}
else
this.CreateNextBlock();
}
else
{
if (!MoveToBottom())
{
Merge();
int count = CleanUpFullLines();
UpdateScoreAndLevel(count);
}
}
Redraw();
}
private void UpdateScoreAndLevel(int count)
{
Score += count * 10;
if (Score >= 100)
{
Score = 0;
Level++;
tmrEngine.Interval = tmrEngine.Interval * 0.8;
}
}
/// <summary>
/// 重绘游戏对象,他用来通知宿主程序绘制游戏本身
/// 它可以是WinForm,也可以是WPF和其它
/// </summary>
public void Redraw()
{
if (DrawingHost != null)
DrawingHost.Draw(this);
}
/// <summary>
/// 创建下一个砖块,预备砖块
/// </summary>
private void CreateNextBlock()
{
this.NextBlock = BlockStore.CreateNextBlock();
Redraw();
}
/// <summary>
/// 放置一个新的砖块
/// </summary>
/// <param name="block"></param>
/// <returns></returns>
private bool PlaceBlockToTop(Block block)
{
int left = (this.Wall.Width - Block.Width) / 2;
if (!Measure(new Point(left, 0), block))
return false;
this.Block = block.Copy();
this.CurrentBlockPosition = new Point(left, 0);
Redraw();
return true;
}
#region 移动和旋转砖块
/// <summary>
/// 将当前砖块向下移动一格
/// </summary>
/// <returns></returns>
public bool MoveToBottom()
{
return Move(0, 1);
}
/// <summary>
/// 快速丢下砖块
/// </summary>
public void FastDown()
{
while (MoveToBottom()) ;
}
/// <summary>
/// 将当前砖块向左移动一格
/// </summary>
/// <returns></returns>
public bool MoveToLeft()
{
return Move(-1, 0);
}
/// <summary>
/// 将当前砖块向右移动一格
/// </summary>
/// <returns></returns>
public bool MoveToRight()
{
return Move(1, 0);
}
/// <summary>
/// 旋转当前砖块
/// </summary>
/// <param name="direction">指定旋转方向</param>
/// <returns>返回值确认是否旋转成功</returns>
public bool Rotate(RotationDirection direction)
{
if (this.Block == null)
return false;
Block newBlock = this.Block.Copy();
bool cw = direction == RotationDirection.CW;
for (int x = 0; x < Block.Width; x++)//X
{
for (int y = 0; y < Block.Height; y++)//Y
{
newBlock[x, y] = cw ? this.Block[y, 2 - x] : this.Block[2 - y, x];
}
}
//确认旋转后的砖块有效部分是否超出了墙体
if (!Measure(this.CurrentBlockPosition, newBlock))
return false;
this.Block = newBlock;
Redraw();
return true;
}
/// <summary>
/// 将当前砖块移动指定的偏移量
/// </summary>
/// <param name="xOffset"></param>
/// <param name="yOffset"></param>
/// <returns></returns>
private bool Move(int xOffset, int yOffset)
{
if (this.Block == null)
return false;
int left = CurrentBlockPosition.X + xOffset;
int top = CurrentBlockPosition.Y + yOffset;
if (!Measure(new Point(left, top), this.Block))
return false;
this.CurrentBlockPosition = new Point(left, top);
Redraw();
return true;
}
#endregion
#region 砖块与墙体合并/清除满行
/// <summary>
/// 将当前砖块融合到墙体
/// </summary>
private void Merge()
{
int left = CurrentBlockPosition.X;
int top = CurrentBlockPosition.Y;
Block block = this.Block;
for (int x = left; x < left + Block.Width; x++)
{
for (int y = top; y < top + Block.Height; y++)
{
if (block.Data[x - left, y - top])
{
Wall[x, y] = true;
Wall.Colors[x, y] = block.Color;
}
}
}
this.Block = null;
Redraw();
}
/// <summary>
/// 清理满行,并返回清理的行数
/// </summary>
/// <returns></returns>
private int CleanUpFullLines()
{
int count = 0;
for (int y = Wall.Height - 1; y >= 0; y--)
{
if (IsFullLine(y))
{
CleanUpLine(y);
y++;
count++;
}
}
return count;
}
/// <summary>
/// 清理指定行
/// </summary>
/// <param name="lineNo"></param>
private void CleanUpLine(int lineNo)
{
for (int x = 0; x < Wall.Width; x++)
Wall[x, lineNo] = false;
for (int y = lineNo; y >= 0; y--)
{
for (int x = 0; x < Wall.Width; x++)
{
if (y == 0)
{
Wall[x, y] = false;
Wall.Colors[x, y] = Wall.BackColor;
}
else
{
Wall[x, y] = Wall[x, y - 1];
Wall.Colors[x, y] = Wall.Colors[x, y - 1];
}
}
}
}
/// <summary>
/// 判断是否为满行
/// </summary>
/// <param name="lineNo"></param>
/// <returns></returns>
private bool IsFullLine(int lineNo)
{
bool isFullLine = true;
for (int x = 0; x < Wall.Width; x++)
{
if (!Wall[x, lineNo])
{
isFullLine = false;
break;
}
}
return isFullLine;
}
#endregion
/// <summary>
/// 测试当前位置是否可以容纳当前砖块(碰撞测试)
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
private bool Measure(Point position, Block block)
{
int left = position.X;
int top = position.Y;
for (int x = left; x < left + Block.Width; x++)
{
for (int y = top; y < top + Block.Height; y++)
{
//越界判断,当出现越界时,则表示当前砖块的有效部分已经超出墙体
bool indexIsOk = (x >= 0 && x < this.Wall.Width &&
y >= 0 && y < this.Wall.Height);
if (block.Data[x - left, y - top]
&& (!indexIsOk || this.Wall.Data[x, y]))
return false;
}
}
return true;
}
}
}
Game 类主要实现游戏的算法,生产新的砖块、左右移动等逻辑动作,碰撞检测、砖块消除以及消除后的积分动作都由他来实现。
以上供参考,谢谢。