在个人电脑日益普及的今天,一些有趣的桌面游戏已经成为人们在使用计算机进行工作或学习之余休闲娱乐的首选,而俄罗斯方块游戏是人们最熟悉的小游戏知益,它趣味性极强,变化无穷,易上手等诸多特点得到了大众的认可,此外对运动的方块进行组合,可以训练玩家的反应能力。
此款游戏的总设计,需求分析和内部数据的具体处理和计算进行了详细的阐述。并对游戏的具体设计与实现进行了简要的说明。本系统基于C#语言下,应用了Visuai Studio 2022开发环境而发出的“俄罗斯方块”这款游戏。
1 功能概述
2 总体设计
2.1总体设计
2.2 功能设计(含界面设计、算法设计、代码)
3 测试
功能概述
我们开发的俄罗斯方块游戏,主要实现了以下几种功能:
1.可以灵活控制方块在图形框中运动。
2.游戏过程中方块可以自由旋转。
3.当某一行的方块排列满时,将自动将这一行方块消除,然后将上面所有方块向下移动,可以支持连续消行。
4.游戏前可以选择游戏的速度和游戏的等级,游戏速度既为方块下落速度,游戏等级为初始游戏时在基层随机生成一定行数的无规律方块,生成的行数由你来选择,每行至少产生5个以上的无规律方块,这样增加了游戏难度,对于游戏高手来说,无疑不是一个新的挑战。
5.游戏的得分支持积分
总体设计
熟悉C#运行环境,会简单的程序调试,熟悉C语言各种类型数据的输入和输出,以及各种函数的使用方法,掌握顺序结构程序设计。
按照经典的俄罗斯方块游戏设计方案、用C语言编写程序主要设计3个类。
设计游戏类(Game)
设计方块类(Block)
设计窗体类(From)
+背景音乐
基本功能实现
1、小方块
每个俄罗斯方块由四、五个小方块组成,定义小方块组合即俄罗斯方块类,实现俄罗斯方块的绘制,移动,擦除功能。
2、定时机制
建立计时器后,根据参数定义的时间步长触发定时处理时间,直到杀死计时器为止。通过计时器实现,每隔固定时间,检测方块状态以及方块下落,预览下一方块。当游戏结束或暂停时杀死计时器。
3、绘图实现
游戏开始时初始化界面,绘制俄罗斯方块的分数、等级、开始游戏、暂停游戏和结束游戏显示。
4、边界判断
通过键盘响应消息处理函数,响应键盘操作,判断俄罗斯方块是否与边界或者下方方块碰撞。
5、方块旋转功能
将小方块组合的位置基点作为旋转中心点,通过旋转各个小方块中心位置到小方块组合的位置基点的x与y边实现俄罗斯方块的旋转。
6、满行消除功能
将俄罗斯方块运动的主要界面看作一个二维数组,存储颜色。当某行满时,消除该行的方块,并且将该行上方的方块颜色向下移一行。
7、方块预览功能
将下一个方块画到游戏面板右侧的提示信息去中。
页面设计:
代码:
设计游戏类(Game)
internal class Game
{
public const int BlockImageWidth = 21;//方砖中每个小方格的大小
public const int BlockImageHeight = 21;
public const int PlayingFieldWidth = 10;//游戏面板大小
public const int PlayingFieldHeight = 20;
private int[,] pile; //存储在游戏面板中的所有方砖;
private Block currentBlock;//当前的俄罗斯方块
private Block nextBlock;//下一个的俄罗斯方块
public int score = 0, lines = 0;
public bool over = false;//游戏是否结束
public Game()//Game类构造函数
{
pile = new int[PlayingFieldWidth, PlayingFieldHeight];
ClearPile();//清空游戏面板中的所有方砖
CreateNewBlock();//产生新的俄罗斯方块
}
private void ClearPile()//清空游戏面板中的所有方砖
{
for (int i = 0; i < PlayingFieldWidth; i++)
{
for (int j = 0; j < PlayingFieldHeight; j++)
{
pile[i, j] = 0;
}
}
}
private void CreateNewBlock()//产生新的俄罗斯方块
{
if (this.nextBlock != null)
{
currentBlock = nextBlock;
}
else
{
currentBlock = new Block();
}
nextBlock = new Block();
}
public void DrawPile(Graphics g)//将pile存储的所有固定画到游戏面板中
{
Image brickImage = Image.FromFile(@"C:\Users\lenovo1\Desktop\俄罗斯方块\block2.gif");//方砖的图形
for (int i = 0; i < PlayingFieldWidth; i++)
{
for (int j = 0; j < PlayingFieldHeight; j++)
{
if (pile[i, j] == 1)
{
Rectangle rect = new Rectangle(i * BlockImageWidth, j * BlockImageHeight, BlockImageWidth, BlockImageHeight);//(j - 1)
g.DrawImage(brickImage, rect);
}
}
}
}
internal void DrawCurrentBlock(object graphics)
{
throw new NotImplementedException();
}
public void DrawCurrentBlock(Graphics g)//将当前方块画到游戏面板中
{
if (currentBlock != null)//检查当前块是否为空
{
currentBlock.Draw(g);
}
}
public void DrawNextBlock(Graphics drawingSurface)//将下一个方块画到游戏面板右侧的提示信息去中
{
if (nextBlock != null)
{
short currentLeft = nextBlock.Left;
short currentTop = nextBlock.Top;
nextBlock.Left = (short)((6 - nextBlock.Width) / 2);
nextBlock.Top = (short)((6 - nextBlock.Height) / 2);
nextBlock.Draw(drawingSurface);
nextBlock.Left = currentLeft;
nextBlock.Top = currentTop;
}
}
public bool DownCurrentBlock()//判断方块下落
{
bool hit = false;
currentBlock.Top++;
if ((currentBlock.Top + currentBlock.Height) > PlayingFieldHeight)
{
hit = true;//当前块触游戏面板底
}
else//检查是否接触到下一行其他已落方块
{
for (int i = 0; i < currentBlock.Width; i++)
{
for (int j = 0; j < currentBlock.Height; j++)
{
int fx, fy;
fx = currentBlock.Left + i;
fy = currentBlock.Top + j;
if ((currentBlock.shape[i, j] == 1) && (pile[fx, fy] == 1))//(fy + 1)
{
hit = true;
}
}
}
}
if (hit)//触到其他已落方块或游戏面板底
{
currentBlock.Top--;
MoveBlockToPile();//固定到游戏面板上
CreateNewBlock(); //产生新的俄罗斯方块
}
return hit;
}
private void MoveBlockToPile()//固定到游戏面板上
{
for (int i = 0; i < currentBlock.Width; i++)
{
for (int j = 0; j < currentBlock.Height; j++)
{
int fx, fy;
fx = currentBlock.Left + i;
fy = currentBlock.Top + j;
if (currentBlock.shape[i, j] == 1)
{
pile[fx, fy] = 1;//将当前块的信息存储到二维数组pile中
}
}
}
CheckForLines();
if (CheckForGameOver())//检查游戏是否结束
over = true;
}
private int CheckForLines()//检查是否满行并消去
{
int numLines = 0;
int[] completeLines = new int[PlayingFieldHeight];
for (int j = PlayingFieldHeight - 1; j > 0; j--)
{
bool fullLine = true;
for (int i = 0; i < PlayingFieldWidth; i++)//PlayingFieldHeight游戏面板宽度
{
if (pile[i, j] == 0)
{
fullLine = false;
break;
}
}
if (fullLine)
{
numLines++;
completeLines[numLines] = j;
}
}
if (numLines > 0)
{
for (int i = 1; i <= numLines; i++)
{
ClearLine((completeLines[i] + (i - 1)));
}
score += 5 * (numLines * (numLines + 1));
lines += numLines;
}
return numLines;
}
public void RotateCurrentBlock()//旋转方块
{
bool canRotate = true;
short newWidth = 0;
short newHeight = 0;
int[,] newShape;
newWidth = currentBlock.Height;
newHeight = currentBlock.Width;
newShape = new int[newWidth, newHeight];
int x, y;
//将某方块存储的形状信息shape二维数组进行转置
if (((currentBlock.Left + newWidth) <= Game.PlayingFieldWidth)
&& ((currentBlock.Top + newHeight) < Game.PlayingFieldHeight))
{
for (int i = 0; i < currentBlock.Width; i++)
{
for (int j = 0; j < currentBlock.Height; j++)
{
x = ((currentBlock.Height - 1) - j);
y = i;
newShape[x, y] = currentBlock.shape[i, j];
//将转置后的方块形状信息shape数组与游戏面板所对应的数组进行比较,判断是否有重叠的地方
if (newShape[x, y] == 1 && pile[x + currentBlock.Left, y + currentBlock.Top] == 1)
{
canRotate = false; return;//不能旋转 }
}
}
}
if (canRotate)
{
currentBlock.Width = newWidth;
currentBlock.Height = newHeight;
currentBlock.shape = newShape;
}
}
}
public void MoveCurrentBlockSide(bool left)//左右移动
{
bool canMove = true;
if (left)//左移动
{
if (currentBlock.Left > 0)
{
for (int i = 0; i < currentBlock.Width; i++)
{
for (int j = 0; j < currentBlock.Height; j++)
{
int fx, fy;
fx = currentBlock.Left + i;
fy = (currentBlock.Top + 1) + j;
if ((currentBlock.shape[i, j] == 1) && (pile[(fx - 1), fy] == 1))
{
canMove = false;//保证左右移动时和pile中存储的固定方块不重叠
}
}
}
if (canMove)
{
currentBlock.Left--;
}
}
}
else//右移动
{
if ((currentBlock.Left + currentBlock.Width) < PlayingFieldWidth)
{
for (int i = 0; i < currentBlock.Width; i++)
{
for (int j = 0; j < currentBlock.Height; j++)
{
int fx, fy;
fx = currentBlock.Left + i;
fy = (currentBlock.Top + 1) + j;
if ((currentBlock.shape[i, j] == 1) && (pile[(fx + 1), fy] == 1))
{
canMove = false;
}
}
}
if (canMove)
{
currentBlock.Left++;
}
}
}
}
private void ClearLine(int lineNumber)//消去满行
{
//将上方的行依次下移
for (int j = lineNumber; j > 0; j--)
{
for (int i = 0; i < PlayingFieldWidth; i++)
{
pile[i, j] = pile[i, (j - 1)];
}
}
//将首行清空
for (int i = 0; i < PlayingFieldWidth; i++)
{
pile[i, 0] = 0;
}
}
public bool CheckForGameOver()//检查游戏是否结束
{
if (currentBlock.Top == 0)
return true;
else
return false;
}
}
代码:
设计游戏类(Game)
internal class Game
{
public const int BlockImageWidth = 21;//方砖中每个小方格的大小
public const int BlockImageHeight = 21;
public const int PlayingFieldWidth = 10;//游戏面板大小
public const int PlayingFieldHeight = 20;
private int[,] pile; //存储在游戏面板中的所有方砖;
private Block currentBlock;//当前的俄罗斯方块
private Block nextBlock;//下一个的俄罗斯方块
public int score = 0, lines = 0;
public bool over = false;//游戏是否结束
public Game()//Game类构造函数
{
pile = new int[PlayingFieldWidth, PlayingFieldHeight];
ClearPile();//清空游戏面板中的所有方砖
CreateNewBlock();//产生新的俄罗斯方块
}
private void ClearPile()//清空游戏面板中的所有方砖
{
for (int i = 0; i < PlayingFieldWidth; i++)
{
for (int j = 0; j < PlayingFieldHeight; j++)
{
pile[i, j] = 0;
}
}
}
private void CreateNewBlock()//产生新的俄罗斯方块
{
if (this.nextBlock != null)
{
currentBlock = nextBlock;
}
else
{
currentBlock = new Block();
}
nextBlock = new Block();
}
public void DrawPile(Graphics g)//将pile存储的所有固定画到游戏面板中
{
Image brickImage = Image.FromFile(@"C:\Users\lenovo1\Desktop\俄罗斯方块\block2.gif");//方砖的图形
for (int i = 0; i < PlayingFieldWidth; i++)
{
for (int j = 0; j < PlayingFieldHeight; j++)
{
if (pile[i, j] == 1)
{
Rectangle rect = new Rectangle(i * BlockImageWidth, j * BlockImageHeight, BlockImageWidth, BlockImageHeight);//(j - 1)
g.DrawImage(brickImage, rect);
}
}
}
}
internal void DrawCurrentBlock(object graphics)
{
throw new NotImplementedException();
}
public void DrawCurrentBlock(Graphics g)//将当前方块画到游戏面板中
{
if (currentBlock != null)//检查当前块是否为空
{
currentBlock.Draw(g);
}
}
public void DrawNextBlock(Graphics drawingSurface)//将下一个方块画到游戏面板右侧的提示信息去中
{
if (nextBlock != null)
{
short currentLeft = nextBlock.Left;
short currentTop = nextBlock.Top;
nextBlock.Left = (short)((6 - nextBlock.Width) / 2);
nextBlock.Top = (short)((6 - nextBlock.Height) / 2);
nextBlock.Draw(drawingSurface);
nextBlock.Left = currentLeft;
nextBlock.Top = currentTop;
}
}
public bool DownCurrentBlock()//判断方块下落
{
bool hit = false;
currentBlock.Top++;
if ((currentBlock.Top + currentBlock.Height) > PlayingFieldHeight)
{
hit = true;//当前块触游戏面板底
}
else//检查是否接触到下一行其他已落方块
{
for (int i = 0; i < currentBlock.Width; i++)
{
for (int j = 0; j < currentBlock.Height; j++)
{
int fx, fy;
fx = currentBlock.Left + i;
fy = currentBlock.Top + j;
if ((currentBlock.shape[i, j] == 1) && (pile[fx, fy] == 1))//(fy + 1)
{
hit = true;
}
}
}
}
if (hit)//触到其他已落方块或游戏面板底
{
currentBlock.Top--;
MoveBlockToPile();//固定到游戏面板上
CreateNewBlock(); //产生新的俄罗斯方块
}
return hit;
}
private void MoveBlockToPile()//固定到游戏面板上
{
for (int i = 0; i < currentBlock.Width; i++)
{
for (int j = 0; j < currentBlock.Height; j++)
{
int fx, fy;
fx = currentBlock.Left + i;
fy = currentBlock.Top + j;
if (currentBlock.shape[i, j] == 1)
{
pile[fx, fy] = 1;//将当前块的信息存储到二维数组pile中
}
}
}
CheckForLines();
if (CheckForGameOver())//检查游戏是否结束
over = true;
}
private int CheckForLines()//检查是否满行并消去
{
int numLines = 0;
int[] completeLines = new int[PlayingFieldHeight];
for (int j = PlayingFieldHeight - 1; j > 0; j--)
{
bool fullLine = true;
for (int i = 0; i < PlayingFieldWidth; i++)//PlayingFieldHeight游戏面板宽度
{
if (pile[i, j] == 0)
{
fullLine = false;
break;
}
}
if (fullLine)
{
numLines++;
completeLines[numLines] = j;
}
}
if (numLines > 0)
{
for (int i = 1; i <= numLines; i++)
{
ClearLine((completeLines[i] + (i - 1)));
}
score += 5 * (numLines * (numLines + 1));
lines += numLines;
}
return numLines;
}
public void RotateCurrentBlock()//旋转方块
{
bool canRotate = true;
short newWidth = 0;
short newHeight = 0;
int[,] newShape;
newWidth = currentBlock.Height;
newHeight = currentBlock.Width;
newShape = new int[newWidth, newHeight];
int x, y;
//将某方块存储的形状信息shape二维数组进行转置
if (((currentBlock.Left + newWidth) <= Game.PlayingFieldWidth)
&& ((currentBlock.Top + newHeight) < Game.PlayingFieldHeight))
{
for (int i = 0; i < currentBlock.Width; i++)
{
for (int j = 0; j < currentBlock.Height; j++)
{
x = ((currentBlock.Height - 1) - j);
y = i;
newShape[x, y] = currentBlock.shape[i, j];
//将转置后的方块形状信息shape数组与游戏面板所对应的数组进行比较,判断是否有重叠的地方
if (newShape[x, y] == 1 && pile[x + currentBlock.Left, y + currentBlock.Top] == 1)
{
canRotate = false; return;//不能旋转 }
}
}
}
if (canRotate)
{
currentBlock.Width = newWidth;
currentBlock.Height = newHeight;
currentBlock.shape = newShape;
}
}
}
public void MoveCurrentBlockSide(bool left)//左右移动
{
bool canMove = true;
if (left)//左移动
{
if (currentBlock.Left > 0)
{
for (int i = 0; i < currentBlock.Width; i++)
{
for (int j = 0; j < currentBlock.Height; j++)
{
int fx, fy;
fx = currentBlock.Left + i;
fy = (currentBlock.Top + 1) + j;
if ((currentBlock.shape[i, j] == 1) && (pile[(fx - 1), fy] == 1))
{
canMove = false;//保证左右移动时和pile中存储的固定方块不重叠
}
}
}
if (canMove)
{
currentBlock.Left--;
}
}
}
else//右移动
{
if ((currentBlock.Left + currentBlock.Width) < PlayingFieldWidth)
{
for (int i = 0; i < currentBlock.Width; i++)
{
for (int j = 0; j < currentBlock.Height; j++)
{
int fx, fy;
fx = currentBlock.Left + i;
fy = (currentBlock.Top + 1) + j;
if ((currentBlock.shape[i, j] == 1) && (pile[(fx + 1), fy] == 1))
{
canMove = false;
}
}
}
if (canMove)
{
currentBlock.Left++;
}
}
}
}
private void ClearLine(int lineNumber)//消去满行
{
//将上方的行依次下移
for (int j = lineNumber; j > 0; j--)
{
for (int i = 0; i < PlayingFieldWidth; i++)
{
pile[i, j] = pile[i, (j - 1)];
}
}
//将首行清空
for (int i = 0; i < PlayingFieldWidth; i++)
{
pile[i, 0] = 0;
}
}
public bool CheckForGameOver()//检查游戏是否结束
{
if (currentBlock.Top == 0)
return true;
else
return false;
}
}
设计方块类(Block)
internal class Block
{
private short width;//方块的高度
private short height;//方块的宽度
private short top;//方块位置的纵坐标
private short left;//方块位置的横坐标
private int ID; //方块部件的ID
public int[,] shape; //存储方块部件的形状,0为空白,1为有砖块
public short Width
{
get
{
return width;
}
set
{
width = value;
}
}
public short Height
{
get
{
return height;
}
set
{
height = value;
}
}
public short Top
{
get
{
return top;
}
set
{
top = value;
}
}
public short Left
{
get
{
return left;
}
set
{
left = value;
}
}
public Block()
{
Random randomGenerator = new Random();
int randomBlock = randomGenerator.Next(1, 7);//产生1—4的数
this.ID = randomBlock;
switch (this.ID)
{
case 1: //横条形
this.Width = 4;
this.Height = 1;
this.Top = 0;
this.Left = 3;
shape = new int[this.Width, this.Height];
shape[0, 0] = 1; shape[1, 0] = 1;
shape[2, 0] = 1; shape[3, 0] = 1;
break;
case 2: //正方形
this.Width = 2;
this.Height = 2;
this.Top = 0;
this.Left = 4;
// Creates the new shape for this block.
shape = new int[this.Width, this.Height];
shape[0, 0] = 1; shape[0, 1] = 1;
shape[1, 0] = 1; shape[1, 1] = 1;
break;
case 3: //T形
this.Width = 3;
this.Height = 3;
this.Top = 0;
this.Left = 4;
// Creates the new shape for this block.
shape = new int[this.Width, this.Height];
shape[0, 0] = 1; shape[1, 0] = 1;
shape[2, 0] = 1; shape[1, 1] = 1;
shape[1, 2] = 1;
break;
case 4: //L形
this.Width = 2;
this.Height = 3;
this.Top = 0;
this.Left = 4;
// Creates the new shape for this block.
shape = new int[this.Width, this.Height];
shape[0, 0] = 1; shape[0, 1] = 1;
shape[0, 2] = 1; shape[1, 2] = 1;
break;
case 5: //新建
this.Width = 2;
this.Height = 3;
this.Top = 0;
this.Left = 4;
// Creates the new shape for this block.
shape = new int[this.Width, this.Height];
shape[0, 0] = 1; shape[0, 1] = 1;
shape[0, 2] = 1; shape[1, 1] = 1;
break;
case 6: //新建
this.Width = 2;
this.Height = 3;
this.Top = 0;
this.Left = 4;
// Creates the new shape for this block.
shape = new int[this.Width, this.Height];
shape[0, 2] = 1; shape[1, 0] = 1;
shape[1, 2] = 1; shape[1, 1] = 1;
break;
}
}
public void Draw(Graphics g)//随机生成方块类型的编号(this.ID),该构造函数中Switch/Case语句段根据类型编号this.ID生成相应方块的形状
{
Image brickImage = Image.FromFile(@"C:\Users\lenovo1\Desktop\俄罗斯方块\block2.gif");
for (int i = 0; i < this.Width; i++)
{
for (int j = 0; j < this.Height; j++)
{
if (this.shape[i, j] == 1)//黑色格子
{
//得到绘制这个格子的在游戏面板中的矩形区域
Rectangle rect = new Rectangle((this.Left + i) * Game.BlockImageWidth, (this.Top + j) * Game.BlockImageHeight, Game.BlockImageWidth, Game.BlockImageHeight);
g.DrawImage(brickImage, rect);
}
}
}
}
}
设计窗体类(From)
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
Game game = null;
private void button1_Click(object sender, EventArgs e)//开始游戏,启动定时器
{
game = new Game();
pictureBox1.Height = Game.BlockImageHeight * Game.PlayingFieldHeight + 3;
pictureBox1.Width = Game.BlockImageWidth * Game.PlayingFieldWidth + 3;
pictureBox1.Invalidate();//重画游戏面板区域
timer1.Enabled = true;
button1.Enabled = false;
}
private void button2_Click(object sender, EventArgs e)//暂停游戏
{
if (button2.Text == "暂停游戏")
{
timer1.Enabled = false; button2.Text = "继续游戏";
}
else
{
timer1.Enabled = true; button2.Text = "暂停游戏";
}
}
private void button3_Click(object sender, EventArgs e)//结束游戏
{
this.Close();
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)//刷新游戏面板屏幕,重画当前下落方块和pile存储的固定方块
{
//重画游戏面板
if (game != null)
{
game.DrawPile(e.Graphics);
game.DrawCurrentBlock(e.Graphics);
}
}
private void pictureBox2_Paint(object sender, PaintEventArgs e)//重画下一个方块
{
if (game != null) game.DrawNextBlock(e.Graphics);
}
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)//设置下落速度,调整时间
{
timer1.Interval = 550 - Convert.ToInt16(comboBox1.Text) * 50;
}
private void timer1_Tick(object sender, EventArgs e)
{
if (!game.DownCurrentBlock())
{
pictureBox1.Invalidate();//重画游戏面板区域
pictureBox2.Invalidate();//重画下一个方块
}
lblScore.Text = game.score.ToString();
if (game.over == true)
{
timer1.Enabled = false;
MessageBox.Show("游戏结束,", "提示");
button1.Enabled = true;
}
}
protected override bool ProcessCmdKey(ref Message msg, Keys e)
//重写ProcessCmdKey方法
{
if (button2.Text == "继续游戏") return true;//暂停时不响应键盘
if (e == Keys.Up || e == Keys.Down || e == Keys.Space ||
e == Keys.Left || e == Keys.Right)
{
MyKeyPress(this, new KeyPressEventArgs((char)e)); //然后在MyKeyPress方法中处理
}
return true;
}
private void MyKeyPress(object sender, KeyPressEventArgs e)
{
switch (e.KeyChar)
{
case (char)Keys.Up:
game.RotateCurrentBlock();
break;
case (char)Keys.Down:
if (!game.DownCurrentBlock())
pictureBox1.Invalidate();//重画游戏面板区域
break;
case (char)Keys.Right:
game.MoveCurrentBlockSide(false);
break;
case (char)Keys.Left:
game.MoveCurrentBlockSide(true);
break;
case (char)Keys.Space:
button2.PerformClick();
break;
}
pictureBox1.Invalidate();
pictureBox2.Invalidate();
}
}
背景音乐
SoundPlayer sp = new SoundPlayer(@"C:\Users\lenovo1\Desktop\音乐\清明上河图.wav");
sp.PlayLooping();
测试
使用情况正常且逻辑清晰
&用到的图片资源: