主要思想:
一、提供一個容器類(Container),用來作為方塊活動的網(wǎng)格狀區(qū)域。由于WPF自帶Grid控件支持網(wǎng)格,因此就直接利用了。
1.Container類需要關聯(lián)到Grid,活動區(qū)域;和waitingGrid等候區(qū)域(下一個出現(xiàn)的方塊)
2.在Container類中實現(xiàn)消層的邏輯
二、提供一個方塊基類(Box),7中方塊全部從其派生。
1.每個方塊包含4個Rectangle(小方格)
2.通過一個工廠類隨機產(chǎn)生某個方塊的實例
3.基類實現(xiàn)方塊移動、變形、等邏輯、子類定義方塊顏色、初始化方式,確定每個方格相對坐標。
4.在方塊下降到底部觸發(fā)OnBottom事件,Container接受此事件觸發(fā)消行邏輯。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;
/// <summary>
/// 定義由四個方格組成的方塊
/// </summary>
protected IList<Rectangle> rectangles = new List<Rectangle>(4);
DispatcherTimer dispatcherTimer;
/// <summary>
/// 當方塊到達底部時觸發(fā)的事件句柄
/// </summary>
public event EventHandler OnBottom;
/// <summary>
/// 判斷x行y列是否包含方格
/// </summary>
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;
}
/// <summary>
/// 當前方塊是否與其他方塊重疊
/// </summary>
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;
}
/// <summary>
/// 判斷由數(shù)值 x,y標示的行列值是否在Grid范圍內
/// </summary>
protected bool InGrid(int x, int y)
{
if (x >= 12 || y >= 24 || x < 0 || y < 0) return false;
return true;
}
/// <summary>
/// 定義活動方塊自動下降
/// </summary>
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);
}
/// <summary>
/// 快速下降
/// </summary>
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;
}
/// <summary>
/// 當前方塊是否處于暫停狀態(tài)
/// </summary>
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();
}
/// <summary>
/// 當前變形狀態(tài)
/// </summary>
protected Status ActivityStatus;
/// <summary>
/// 變形
/// </summary>
public bool ChangeShape()
{
if (IsPause) return false;
IList<Position> P = new List<Position>(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種狀態(tài),I型有2中狀態(tài),而O型只有一種狀態(tài),現(xiàn)在需要描述方塊形狀狀態(tài),需要定義循環(huán)鏈表數(shù)據(jù)類型。
定義一個坐標點,描述位置和相對位置 /// <summary>
/// 定義一個方格坐標點
/// </summary>
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; }
}
/// <summary>
/// 定義方塊形狀狀態(tài)循環(huán)鏈表,標記變形狀態(tài)
/// </summary>
class Status
{
/// <summary>
/// 方格[四個方塊]下一次變形將要去的相對位置
/// </summary>
public List<Position> NextRelativeposition = new List<Position>(4);
/// <summary>
/// 是否需要檢查方塊[每個方格]到這個位置的可行性
/// </summary>
public List<bool> NeedCheck = new List<bool>(4);
/// <summary>
/// 指向下一狀態(tài)
/// </summary>
public Status Next;
}
在方塊子類中Ready方法即為每種方塊設置狀態(tài)鏈表。
由于方塊的生成為隨機方式,定義簡單工廠模式生成方塊如下:
代碼 class BoxFactory
{
/// <summary>
/// 隨機方塊工廠
/// </summary>
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;
}
}
}
到此為止,方塊定義好了,也可以隨機產(chǎn)生了,怎么讓展示在Grid中?
1.方塊子類ShowAt函數(shù)標示展示到指定Grid;
2.ShowWating表示展示在等候區(qū)域。
當方塊展示到容器Grid中時,怎么自動下降呢?這里用到DispatcherTimer。
dispatcherTimer = new DispatcherTimer(DispatcherPriority.Normal);
dispatcherTimer.Tick += new EventHandler(Timer_Tick);
dispatcherTimer.Interval = TimeSpan.FromMilliseconds(450 - Result.GetInstance().Level * 50);
/// <summary>
/// 定義活動方塊自動下降
/// </summary>
public void AtuoDown()
{
dispatcherTimer.Start();
} private void Timer_Tick(object sender, EventArgs e)
{
if (!MoveDown())
{
dispatcherTimer.Stop();
OnBottom(this, null);
}
}
當方塊到達底部時,事件通知容器類執(zhí)行消層函數(shù):
在Box中:
/// <summary>
/// 當方塊到達底部時觸發(fā)的事件句柄
/// </summary>
public event EventHandler OnBottom;
private void Timer_Tick(object sender, EventArgs e)
{
if (!MoveDown())
{
dispatcherTimer.Stop();
OnBottom(this, null);
}
}
在Container中:
ActivityBox.OnBottom += ActivityBox_OnBottom;
/// <summary>
/// 活動方塊到達底部時觸發(fā)
/// </summary>
static public void ActivityBox_OnBottom(object sender, EventArgs e)
{
Result.GetInstance().CalculateScore(RemoveLine());
NewBoxReadyToDown();
}
RemoveLine消層函數(shù) /// <summary>
/// 消層,并返回消除的層數(shù)
/// </summary>
static int RemoveLine()
{
if (grid == null) new Exception("缺少活動區(qū)域,必須為容器指定grid值。");
int[] lineCount = new int[24];
for (int i = 0; i < 24; i++) lineCount[i] = 0;
int RemoveLineCount = 0;
//計算每一行方塊總數(shù)
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);
}
}
}
//被移除行數(shù)加1
RemoveLineCount++;
}
}
return RemoveLineCount;
}
OK,到此方塊可以自動下降,可消層了,現(xiàn)在要統(tǒng)計消層得分和游戲級別(難度)。
定義一個新類Result
Result類 /// <summary>
/// 記錄分數(shù)和級別
/// </summary>
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;
}
在該類中用單例模式控制產(chǎn)生單一實例,并通過實現(xiàn)接口綁定到界面分數(shù)展示臺。
界面XAML如下:
<Grid Grid.Column="1" Grid.Row="1" Height="65" HorizontalAlignment="Left" Margin="5,6,0,0" Name="grid4" VerticalAlignment="Top" Width="100">
<Label Content="積分:" Height="28" Name="label1" Margin="0,5,0,32" HorizontalAlignment="Left" Width="38" />
<Label Content="{Binding Path=Score}" Height="28" Name="label2" Margin="35,5,0,32" />
<Label Content="級別:" Height="28" Name="label3" Margin="0,34,0,3" HorizontalAlignment="Left" Width="38" />
<Label Content="{Binding Path=Level}" Height="28" Name="label4" Margin="0,0,0,4" HorizontalAlignment="Right" VerticalAlignment="Bottom" Width="65" />
</Grid>
這樣,代碼中對Result賦值,直接影響界面控件展示數(shù)值的變化:
/// <summary>
/// 活動方塊到達底部時觸發(fā)
/// </summary>
static public void ActivityBox_OnBottom(object sender, EventArgs e)
{
Result.GetInstance().CalculateScore(RemoveLine());
NewBoxReadyToDown();
}
這里根據(jù)消行函數(shù)返回值此處對result實例進行修改,界面數(shù)值(分數(shù)、級別)也同步變化。
最后,界面添加功能按鈕,實現(xiàn)Window_KeyDown事件 OnGameover事件,游戲完成。