1. 首页 > 电脑教程 > 菜鸟用WPF实现俄罗斯方块游戏

菜鸟用WPF实现俄罗斯方块游戏

主要思想:

一、提供一个容器类(Container),用来作为方块活动的网格状区域。由于WPF自带Grid控件支持网格,因此就直接利用了。

1.Container类需要关联到Grid,活动区域;和waitingGrid等候区域(下一个出现的方块)

2.在Container类中实现消层的逻辑

二、提供一个方块基类(Box),7中方块全部从其派生。

1.每个方块包含4个Rectangle(小方格)

2.通过一个工厂类随机产生某个方块的实例

3.基类实现方块移动、变形、等逻辑、子类定义方块颜色、初始化方式,确定每个方格相对坐标。

4.在方块下降到底部触发OnBottom事件,Container接受此事件触发消行逻辑。Container类中OnGameover事件被界面层接受处理。

运行效果:

代码部分:

Box方块基类 abstract class Box{public Box(){for (int i = 0; i < 4; i++){rectangles.Add(new Rectangle());rectangles[i].Width = 24.0;rectangles[i].Height = 24.0;}dispatcherTimer = new DispatcherTimer(DispatcherPriority.Normal);dispatcherTimer.Tick += new EventHandler(Timer_Tick);dispatcherTimer.Interval = TimeSpan.FromMilliseconds(450 - Result.GetInstance().Level * 50);}protected Grid grid;///

/// 定义由四个方格组成的方块/// protected IList rectangles = new List(4);DispatcherTimer dispatcherTimer;/// /// 当方块到达底部时触发的事件句柄/// public event EventHandler OnBottom;/// /// 判断x行y列是否包含方格/// protected bool Existence(int x, int y){foreach (var r in grid.Children){if (r is Rectangle){if (this.rectangles.Contains(r as Rectangle)) return false;if (Convert.ToInt32((r as Rectangle).GetValue(Grid.ColumnProperty)) == x && Convert.ToInt32((r as Rectangle).GetValue(Grid.RowProperty)) == y){return true;}}}return false;}/// /// 当前方块是否与其他方块重叠/// public bool ISOverlapping(){foreach (var r in rectangles){int x = Convert.ToInt32((r as Rectangle).GetValue(Grid.ColumnProperty));int y = Convert.ToInt32((r as Rectangle).GetValue(Grid.RowProperty));if (Existence(x, y)) return true;}return false;}/// /// 判断由数值 x,y标示的行列值是否在Grid范围内/// protected bool InGrid(int x, int y){if (x >= 12 || y >= 24 || x < 0 || y < 0) return false;return true;}/// /// 定义活动方块自动下降/// public void AtuoDown(){dispatcherTimer.Start();}private void Timer_Tick(object sender, EventArgs e){if (!MoveDown()){dispatcherTimer.Stop();OnBottom(this, null);}}public abstract void Ready();public abstract void ShowWating(ref Grid WaingGrid);protected bool Move(int _x, int _y){if (IsPause) return false;for (int i = 0; i < 4; i++){int x = Convert.ToInt32(rectangles[i].GetValue(Grid.ColumnProperty)) + _x;int y = Convert.ToInt32(rectangles[i].GetValue(Grid.RowProperty)) + _y;if (Existence(x, y)) return false;if (!InGrid(x, y)) return false;}for (int i = 0; i < 4; i++){rectangles[i].SetValue(Grid.ColumnProperty, Convert.ToInt32(rectangles[i].GetValue(Grid.ColumnProperty)) + _x);rectangles[i].SetValue(Grid.RowProperty, Convert.ToInt32(rectangles[i].GetValue(Grid.RowProperty)) + _y);}return true;}public bool MoveUp(){return Move(0, -1);}public bool MoveDown(){return Move(0, 1);}public bool MoveLeft(){return Move(-1, 0);}public bool MoveRight(){return Move(1, 0);}/// /// 快速下降/// public bool FastDown(){if (IsPause) return false;bool mark = false;int j = 0;while (!mark){j++;for (int i = 0; i < 4; i++){int x = Convert.ToInt32(rectangles[i].GetValue(Grid.ColumnProperty));int y = Convert.ToInt32(rectangles[i].GetValue(Grid.RowProperty)) + j;if (Existence(x, y) || !InGrid(x, y)){j--;mark = true;}}}for (int i = 0; i < 4; i++){rectangles[i].SetValue(Grid.RowProperty, Convert.ToInt32(rectangles[i].GetValue(Grid.RowProperty)) + j);}//OnBottom(this, null);return mark;}/// /// 当前方块是否处于暂停状态/// private bool IsPause = false;public void Pause(){dispatcherTimer.IsEnabled = false;IsPause = true;}public void UnPause(){dispatcherTimer.IsEnabled = true;IsPause = false;}public void StopAction(){dispatcherTimer.Stop();}/// /// 当前变形状态/// protected Status ActivityStatus;/// /// 变形/// public bool ChangeShape(){if (IsPause) return false;IList P = new List(4);for (int i = 0; i < 4; i++) P.Add(new Position());P[0].x = Convert.ToInt32(rectangles[0].GetValue(Grid.ColumnProperty)) + ActivityStatus.NextRelativeposition[0].x;P[0].y = Convert.ToInt32(rectangles[0].GetValue(Grid.RowProperty)) + ActivityStatus.NextRelativeposition[0].y;if (ActivityStatus.NeedCheck[0]) if (Existence(P[0].x, P[0].y) || !InGrid(P[0].x, P[0].y)) return false;P[1].x = Convert.ToInt32(rectangles[1].GetValue(Grid.ColumnProperty)) + ActivityStatus.NextRelativeposition[1].x;P[1].y = Convert.ToInt32(rectangles[1].GetValue(Grid.RowProperty)) + ActivityStatus.NextRelativeposition[1].y;if (ActivityStatus.NeedCheck[1]) if (Existence(P[1].x, P[1].y) || !InGrid(P[1].x, P[1].y)) return false;P[2].x = Convert.ToInt32(rectangles[2].GetValue(Grid.ColumnProperty)) + ActivityStatus.NextRelativeposition[2].x;P[2].y = Convert.ToInt32(rectangles[2].GetValue(Grid.RowProperty)) + ActivityStatus.NextRelativeposition[2].y;if (ActivityStatus.NeedCheck[2]) if (Existence(P[2].x, P[2].y) || !InGrid(P[2].x, P[2].y)) return false;P[3].x = Convert.ToInt32(rectangles[3].GetValue(Grid.ColumnProperty)) + ActivityStatus.NextRelativeposition[3].x;P[3].y = Convert.ToInt32(rectangles[3].GetValue(Grid.RowProperty)) + ActivityStatus.NextRelativeposition[3].y;if (ActivityStatus.NeedCheck[3]) if (Existence(P[3].x, P[3].y) || !InGrid(P[3].x, P[3].y)) return false;for (int i = 0; i < 4; i++){rectangles[i].SetValue(Grid.ColumnProperty, P[i].x);rectangles[i].SetValue(Grid.RowProperty, P[i].y);}ActivityStatus = ActivityStatus.Next;return true;}}

“Z”形方块子类

class Box_Z : Box{public Box_Z(ref Grid grid){this.grid = grid;for (int i = 0; i < 4; i++) rectangles[i].Fill = new SolidColorBrush(Colors.DarkOrange);}private void ShowAt(Position P, ref Grid grid){rectangles[0].SetValue(Grid.ColumnProperty, P.x + 0);rectangles[0].SetValue(Grid.RowProperty, P.y + 0);rectangles[1].SetValue(Grid.ColumnProperty, P.x + 1);rectangles[1].SetValue(Grid.RowProperty, P.y + 0);rectangles[2].SetValue(Grid.ColumnProperty, P.x + 1);rectangles[2].SetValue(Grid.RowProperty, P.y + 1);rectangles[3].SetValue(Grid.ColumnProperty, P.x + 2);rectangles[3].SetValue(Grid.RowProperty, P.y + 1);for (int i = 0; i < 4; i++) grid.Children.Add(rectangles[i]);}public override void ShowWating(ref Grid WaingGrid){ShowAt(new Position(1, 1), ref WaingGrid);}public override void Ready(){ShowAt(new Position(4, 0), ref grid);ActivityStatus = new Status();ActivityStatus.NextRelativeposition.Add(new Position(1, 2));ActivityStatus.NextRelativeposition.Add(new Position(0, 1));ActivityStatus.NextRelativeposition.Add(new Position(1, 0));ActivityStatus.NextRelativeposition.Add(new Position(0, -1));ActivityStatus.NeedCheck.Add(true);ActivityStatus.NeedCheck.Add(false);ActivityStatus.NeedCheck.Add(false);ActivityStatus.NeedCheck.Add(true);ActivityStatus.Next = new Status();ActivityStatus.Next.NextRelativeposition.Add(new Position(-1, -2));ActivityStatus.Next.NextRelativeposition.Add(new Position(0, -1));ActivityStatus.Next.NextRelativeposition.Add(new Position(-1, 0));ActivityStatus.Next.NextRelativeposition.Add(new Position(0, 1));ActivityStatus.Next.NeedCheck.Add(true);ActivityStatus.Next.NeedCheck.Add(true);ActivityStatus.Next.NeedCheck.Add(false);ActivityStatus.Next.NeedCheck.Add(false);ActivityStatus.Next.Next = ActivityStatus;}}

由于每种方块的变形方式都不一样,Z型有4种状态,I型有2中状态,而O型只有一种状态,现在需要描述方块形状状态,需要定义循环链表数据类型。

定义一个坐标点,描述位置和相对位置 ///

/// 定义一个方格坐标点/// class Position{public Position(int x, int y){this.x = x;this.y = y;}public Position(){}public int x { get; set; }public int y { get; set; }}

///

/// 定义方块形状状态循环链表,标记变形状态/// class Status{/// /// 方格[四个方块]下一次变形将要去的相对位置/// public List NextRelativeposition = new List(4);/// /// 是否需要检查方块[每个方格]到这个位置的可行性/// public List NeedCheck = new List(4);/// /// 指向下一状态/// public Status Next;}

在方块子类中Ready方法即为每种方块设置状态链表。

由于方块的生成为随机方式,定义简单工厂模式生成方块如下:

代码 class BoxFactory{///

/// 随机方块工厂/// static public Box GetRandomBox(ref Grid grid){//return new Box_Z(ref grid);Random ran = new Random();int index = ran.Next(7);switch (index){case 0: return new Box_S(ref grid);case 1: return new Box_Z(ref grid);case 2: return new Box_J(ref grid);case 3: return new Box_L(ref grid);case 4: return new Box_I(ref grid);case 5: return new Box_O(ref grid);case 6: return new Box_T(ref grid);default: return null;}}}

到此为止,方块定义好了,也可以随机产生了,怎么让展示在Grid中?

1.方块子类ShowAt函数标示展示到指定Grid;

2.ShowWating表示展示在等候区域。

当方块展示到容器Grid中时,怎么自动下降呢?这里用到DispatcherTimer。

dispatcherTimer = new DispatcherTimer(DispatcherPriority.Normal);dispatcherTimer.Tick += new EventHandler(Timer_Tick);dispatcherTimer.Interval = TimeSpan.FromMilliseconds(450 - Result.GetInstance().Level * 50);///

/// 定义活动方块自动下降/// public void AtuoDown(){dispatcherTimer.Start();} private void Timer_Tick(object sender, EventArgs e) { if (!MoveDown()) { dispatcherTimer.Stop(); OnBottom(this, null); } }

当方块到达底部时,事件通知容器类执行消层函数:

在Box中:///

/// 当方块到达底部时触发的事件句柄/// public event EventHandler OnBottom;private void Timer_Tick(object sender, EventArgs e){if (!MoveDown()){dispatcherTimer.Stop();OnBottom(this, null);}}在Container中:ActivityBox.OnBottom += ActivityBox_OnBottom;/// /// 活动方块到达底部时触发/// static public void ActivityBox_OnBottom(object sender, EventArgs e){Result.GetInstance().CalculateScore(RemoveLine());NewBoxReadyToDown();}

RemoveLine消层函数 ///

/// 消层,并返回消除的层数/// static int RemoveLine(){if (grid == null) new Exception("缺少活动区域,必须为容器指定grid值。");int[] lineCount = new int[24];for (int i = 0; i < 24; i++) lineCount[i] = 0;int RemoveLineCount = 0;//计算每一行方块总数foreach (var r in grid.Children){if (r is Rectangle){int x = Convert.ToInt32((r as Rectangle).GetValue(Grid.RowProperty));lineCount[x]++;}}for (int i = 23; i >= 0; i--){if (lineCount[i] >= 12){//移除一行小方格for (int j = 0; j < grid.Children.Count; j++)// (var r in mygrid.Children){if (grid.Children[j] is Rectangle){if (Convert.ToInt32((grid.Children[j] as Rectangle).GetValue(Grid.RowProperty)) == i + RemoveLineCount){grid.Children.Remove((grid.Children[j] as Rectangle));j--;}}}//将上面的所有小方格下降一行foreach (var r in grid.Children){if (r is Rectangle){if (Convert.ToInt32((r as Rectangle).GetValue(Grid.RowProperty)) < i + RemoveLineCount){(r as Rectangle).SetValue(Grid.RowProperty, Convert.ToInt32((r as Rectangle).GetValue(Grid.RowProperty)) + 1);}}}//被移除行数加1RemoveLineCount++;}}return RemoveLineCount;}

OK,到此方块可以自动下降,可消层了,现在要统计消层得分和游戏级别(难度)。

定义一个新类Result

Result类 ///

/// 记录分数和级别/// class Result : INotifyPropertyChanged{Result(){Score = 0;Level = 1;}//单例模式private static Result instance;private static readonly object syncRoot = new object();public static Result GetInstance(){if (instance == null){lock (syncRoot){if (instance == null){instance = new Result();}}}return instance;}int score;int level;public int Score{get { return score; }set { score = value; Notify("Score"); }}public int Level{get { return level; }set { level = value; Notify("Level"); }}public void CalculateScore(int Lines){switch (Lines){case 1: Score += 5;break;case 2: Score += 15;break;case 3: Score += 30;break;case 4: Score += 50;break;default: Score += 0;break;}if (Score < 20) Level = 1;else if (Score < 100) Level = 2;else if (Score < 300) Level = 3;else if (Score < 500) Level = 4;else if (Score < 1000) Level = 5;else if (Score < 3000) Level = 6;else if (Score < 5000) Level = 7;else Level = 8;}void Notify(string propName){if (PropertyChanged != null){PropertyChanged(this, new PropertyChangedEventArgs(propName));}}public event PropertyChangedEventHandler PropertyChanged;}

在该类中用单例模式控制产生单一实例,并通过实现接口绑定到界面分数展示台。

界面XAML如下:

这样,代码中对Result赋值,直接影响界面控件展示数值的变化:

///

/// 活动方块到达底部时触发/// static public void ActivityBox_OnBottom(object sender, EventArgs e){Result.GetInstance().CalculateScore(RemoveLine());NewBoxReadyToDown();}

这里根据消行函数返回值此处对result实例进行修改,界面数值(分数、级别)也同步变化。

最后,界面添加功能按钮,实现Window_KeyDown事件 OnGameover事件,游戏完成。

声明:希维路由器教程网提供的内容,仅供网友学习交流,如有侵权请与我们联系删除,谢谢。ihuangque@qq.com
本文地址:https://www.ctrlcv.com.cn/diannao/169347607710788.html