|
|
Posted on 2009-01-08 08:55 webabcd 阅读(13335) 评论(84) 编辑 收藏
[源码下载]
游戏人生Silverlight(1) - 七彩俄罗斯方块[Silverlight 2.0(c#)] 作者: webabcd介绍 使用 Silverlight 2.0(c#) 开发一个七彩俄罗斯方块 玩法 ↑ - 变形;← - 向左移动;→ - 向右移动;↓ - 向下移动 在线DEMO
思路 1、每个形状都是由4个“块”组成的,也就是“块”是俄罗斯方块的最小单位,首先要有一个“块”的用户控件。要求可以设置“块”的位置和颜色 2、经典俄罗斯方块一共7种形状,把每种形状所需要的功能抽象出来写一个抽象类,7个具体形状分别继承这个抽象类,并重写其抽象属性和抽象方法 3、核心控制部分:在容器内铺满隐藏的“块”,上/下/左/右/控制形状的变形和移动,通过控制容器内“块”的颜色来响应变化,当形状下一步移动或变形的位置处已经有颜色时则禁止移动或变形,当形状下一步移动或变形的位置在底边有颜色或处于容器的底部则判断消行并生成新的形状 关键代码 1、形状抽象类 PieceBase.cs
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace YYTetris.Piece
  {
public abstract class PieceBase
 {
public PieceBase()
 {
InitPiece();
}

// 形状的矩阵
 public int[,] Matrix { get; set; }

// 形状的索引
private int _index = 0;

// 形状的最大索引
 public int MaxIndex { get; set; }

 /**//// <summary>
/// 初始化形状,需要设置 Matrix 和 MaxIndex
/// </summary>
public abstract void InitPiece();

 /**//// <summary>
/// 变形
/// </summary>
/// <returns>变形后的矩阵</returns>
public abstract int[,] GetRotate();

 /**//// <summary>
/// 形状的颜色
/// </summary>
 public abstract Color Color { get; }

 /**//// <summary>
/// 获取下一个形状的索引。如果超过最大索引则返回最初索引
/// </summary>
/// <returns></returns>
public int GetNextIndex()
 {
int nextIndex = _index >= MaxIndex ? 0 : _index + 1;

return nextIndex;
}

 /**//// <summary>
/// 变形。设置 Matrix 为变形后的矩阵
/// </summary>
public void Rotate()
 {
Matrix = GetRotate();

_index = GetNextIndex();
}
}
}
 2、继承PieceBase类,以“L”为例。每种形状均为一个4×4矩阵,1代表有“块”,0代表空 L.cs
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace YYTetris.Piece
  {
public class L : PieceBase
 {
public override void InitPiece()
 {
Matrix = new int[,]
 {
 {0,1,0,0},
 {0,1,0,0},
 {0,1,1,0},
 {0,0,0,0}
};

MaxIndex = 3;
}

public override int[,] GetRotate()
 {
switch (GetNextIndex())
 {
case 0:
return new int[,]
 {
 {0,1,0,0},
 {0,1,0,0},
 {0,1,1,0},
 {0,0,0,0}
};
case 1:
return new int[,]
 {
 {0,0,0,0},
 {1,1,1,0},
 {1,0,0,0},
 {0,0,0,0}
};
case 2:
return new int[,]
 {
 {1,1,0,0},
 {0,1,0,0},
 {0,1,0,0},
 {0,0,0,0}
};
case 3:
return new int[,]
 {
 {0,0,1,0},
 {1,1,1,0},
 {0,0,0,0},
 {0,0,0,0}
};
default:
return Matrix;
}
}

public override Color Color
 {
 get { return Helper.GetColor("#339933"); }
}
}
}
 3、核心控制类 UIControl.cs
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

using YYTetris.Piece;
using System.Windows.Threading;
using System.Collections.Generic;
using System.ComponentModel;

namespace YYTetris
  {
public class UIControl : INotifyPropertyChanged
 {
 /**//// <summary>
/// 俄罗斯方块容器
/// </summary>
 public Block[,] Container { get; set; }

 /**//// <summary>
/// 下一个形状的容器(4×4)
/// </summary>
 public Block[,] NextContainer { get; set; }

 /**//// <summary>
/// 游戏状态(Ready, Play, Pause, Over)
/// </summary>
 public GameStatus GameStatus { get; set; }

private int _rows = 20; // 行数(Y 方向)
private int _columns = 10; // 列数(X 方向)
private int _positionX = 3; // 形状所属的 4×4 容器的 X 坐标
private int _positionY = 0; // 形状所属的 4×4 容器的 Y 坐标

private List<PieceBase> _pieces; // 形状集合

private PieceBase _currentPiece; // 当前形状
private PieceBase _nextPiece; // 下一个形状

private int _initSpeed = 400; // 初始速率(毫秒)
private int _levelSpeed = 50; // 每增加一个级别所需增加的速率(毫秒)

private DispatcherTimer _timer;

 /**//// <summary>
/// 构造函数
/// </summary>
public UIControl()
 {
// 初始化形状集合,共七种形状
 _pieces = new List<PieceBase>() { new I(), new L(), new L2(), new N(), new N2(), new O(), new T() };

// 初始化方块容器(用 Block 对象填满整个容器)
Container = new Block[_rows, _columns];
for (int i = 0; i < _rows; i++)
 {
for (int j = 0; j < _columns; j++)
 {
var block = new Block();
block.Top = i * block.rectangle.ActualHeight;
block.Left = j * block.rectangle.ActualWidth;
block.Color = null;

Container[i, j] = block;
}
}

// 初始化下一个形状的容器(用 Block 对象将其填满)
NextContainer = new Block[4, 4];
for (int i = 0; i < 4; i++)
 {
for (int j = 0; j < 4; j++)
 {
var block = new Block();
block.Top = i * block.rectangle.ActualHeight;
block.Left = j * block.rectangle.ActualWidth;
block.Color = null;

NextContainer[i, j] = block;
}
}

// 创建一个新的形状
CreatePiece();
// 呈现当前创建出的形状
AddPiece(0, 0);

// Timer 用于定时向下移动形状
_timer = new DispatcherTimer();
_timer.Interval = TimeSpan.FromMilliseconds(_initSpeed);
_timer.Tick += new EventHandler(_timer_Tick);

GameStatus = GameStatus.Ready;
}

 /**//// <summary>
/// 开始游戏(启动计时器)
/// </summary>
public void Play()
 {
GameStatus = GameStatus.Play;
_timer.Start();
}

 /**//// <summary>
/// 暂停游戏(停止计时器)
/// </summary>
public void Pause()
 {
GameStatus = GameStatus.Pause;
_timer.Stop();
}

 /**//// <summary>
/// 创建一个新的形状
/// </summary>
private void CreatePiece()
 {
// 逻辑移到 下坠后 的逻辑内
for (int x = 0; x < _columns; x++)
 {
if (Container[0, x].Color != null)
 {
OnGameOver(null);
break;
}
}

// 计算 当前形状 和 下一个形状
Random random = new Random();
_currentPiece = _nextPiece == null ? _pieces[random.Next(0, 7)] : _nextPiece;
_nextPiece = _pieces[random.Next(0, 7)];

// 形状所属的 4×4 容器的 X 坐标和 Y 坐标
_positionX = 3;
_positionY = 0;

// 设置“下一个形状的容器”的 UI
SetNextContainerUI();
}

private void _timer_Tick(object sender, EventArgs e)
 {
MoveToDown();
}

 /**//// <summary>
/// 向左移动
/// </summary>
public void MoveToLeft()
 {
if (GameStatus != GameStatus.Play) return;

if (!IsBoundary(_currentPiece.Matrix, -1, 0))
 {
RemovePiece();
AddPiece(-1, 0);
}
}

 /**//// <summary>
/// 向右移动
/// </summary>
public void MoveToRight()
 {
if (GameStatus != GameStatus.Play) return;

if (!IsBoundary(_currentPiece.Matrix, 1, 0))
 {
RemovePiece();
AddPiece(1, 0);
}
}

 /**//// <summary>
/// 向下移动
/// </summary>
public void MoveToDown()
 {
if (GameStatus != GameStatus.Play) return;

if (!IsBoundary(_currentPiece.Matrix, 0, 1))
 {
RemovePiece();
AddPiece(0, 1);
}
else
 {
// 如果触及底边了,则消除可消的行并且创建新的形状
RemoveRow();
CreatePiece();

// 每落下一个形状加 1 分
Score++;
}
}

 /**//// <summary>
/// 变形
/// </summary>
public void Rotate()
 {
if (GameStatus != GameStatus.Play) return;

if (!IsBoundary(_currentPiece.GetRotate(), 0, 0))
 {
RemovePiece();
_currentPiece.Rotate();
AddPiece(0, 0);
}
}

 /**//// <summary>
/// 清除俄罗斯方块容器
/// </summary>
public void Clear()
 {
for (int x = 0; x < _columns; x++)
 {
for (int y = 0; y < _rows; y++)
 {
Container[y, x].Color = null;
}
}
}

 /**//// <summary>
/// 边界判断(是否超过边界)
/// </summary>
/// <param name="matrix">当前操作的形状的4×4矩阵</param>
/// <param name="offsetX">矩阵 X 方向的偏移量</param>
/// <param name="offsetY">矩阵 Y 方向的偏移量</param>
/// <returns></returns>
private bool IsBoundary(int[,] matrix, int offsetX, int offsetY)
 {
RemovePiece();

for (int i = 0; i < 4; i++)
 {
for (int j = 0; j < 4; j++)
 {
if (matrix[i, j] == 1)
 {
if (j + _positionX + offsetX > _columns - 1 // 超过列的右边界
|| i + _positionY + offsetY > _rows - 1 // 超过行的下边界
|| j + _positionX + offsetX < 0 // 超过列的左边界
|| Container[i + _positionY + offsetY, j + _positionX + offsetX].Color != null) // matrix 所需偏移的地方已经有 Block 占着了
 {
AddPiece(0, 0);
return true;
}
}
}
}

AddPiece(0, 0);
return false;
}

 /**//// <summary>
/// 设置“下一个形状的容器”的 UI
/// </summary>
private void SetNextContainerUI()
 {
// 清空
foreach (Block block in NextContainer)
 {
block.Color = null;
}

// 根据 _nextPiece 的矩阵设置相对应的 Block 对象的呈现
for (int x = 0; x < 4; x++)
 {
for (int y = 0; y < 4; y++)
 {
if (_nextPiece.Matrix[x, y] == 1)
 {
NextContainer[x, y].Color = _nextPiece.Color;
}
}
}
}

 /**//// <summary>
/// 移除 _currentPiece 在界面上的呈现
/// </summary>
private void RemovePiece()
 {
for (int i = 0; i < 4; i++)
 {
for (int j = 0; j < 4; j++)
 {
if (_currentPiece.Matrix[i, j] == 1)
 {
Container[i + _positionY, j + _positionX].Color = null;
}
}
}
}

 /**//// <summary>
/// 增加 _currentPiece 在界面上的呈现
/// </summary>
/// <param name="offsetX">X 方向上的偏移量</param>
/// <param name="offsetY">Y 方向上的偏移量</param>
private void AddPiece(int offsetX, int offsetY)
 {
for (int i = 0; i < 4; i++)
 {
for (int j = 0; j < 4; j++)
 {
if (_currentPiece.Matrix[i, j] == 1)
 {
Container[i + _positionY + offsetY, j + _positionX + offsetX].Color = _currentPiece.Color;
}
}
}

_positionX += offsetX;
_positionY += offsetY;
}

 /**//// <summary>
/// 根据游戏规则,如果某行出现连续的直线则将其删除,该线以上的部分依次向下移动
/// </summary>
private void RemoveRow()
 {
// 删除的行数
int removeRowCount = 0;

// 行的遍历(Y 方向)
for (int y = 0; y < _rows; y++)
 {
// 该行是否是一条连续的直线
bool isLine = true;

// 列的遍历(X 方向)
for (int x = 0; x < _columns; x++)
 {
if (Container[y, x].Color == null)
 {
// 出现断行,则继续遍历下一行
isLine = false;
break;
}
}

// 该行是一条连续的直线则将其删除,并将该行以上的部分依次向下移动
if (isLine)
 {
removeRowCount++;

// 删除该行
for (int x = 0; x < _columns; x++)
 {
Container[y, x].Color = null;
}

// 将被删除行的以上行依次向下移动
for (int i = y; i > 0; i--)
 {
for (int x = 0; x < _columns; x++)
 {
Container[i, x].Color = Container[i - 1, x].Color;
}
}
}
}

// 加分,计算方法: 2 的 removeRowCount 次幂 乘以 10
if (removeRowCount > 0)
Score += 10 * (int)Math.Pow(2, removeRowCount);

// 更新总的已消行数
RemoveRowCount += removeRowCount;

// 根据已消行数计算级别,依据丁学的建议,计算方法: 已消行数/5 的平方根 取整
Level = (int)Math.Sqrt(RemoveRowCount / 5);

// 根据级别计算速率,计算方法: 初始速率 减 (每多一个级别所需增加的速率 乘以 当前级别)
_timer.Interval = TimeSpan.FromMilliseconds(_initSpeed - _levelSpeed * Level > _levelSpeed ? _initSpeed - _levelSpeed * Level : _levelSpeed);
}

private int _score = 0;
 /**//// <summary>
/// 得分
/// </summary>
public int Score
 {
 get { return _score; }
set
 {
_score = value;
if (PropertyChanged != null)
 {
PropertyChanged(this, new PropertyChangedEventArgs("Score"));
}
}
}

private int _removeRowCount = 0;
 /**//// <summary>
/// 总共被消除的行数
/// </summary>
public int RemoveRowCount
 {
 get { return _removeRowCount; }
set
 {
_removeRowCount = value;
if (PropertyChanged != null)
 {
PropertyChanged(this, new PropertyChangedEventArgs("RemoveRowCount"));
}
}
}

private int _level = 0;
 /**//// <summary>
/// 级别(游戏难度)
/// </summary>
public int Level
 {
 get { return _level; }
set
 {
_level = value;
if (PropertyChanged != null)
 {
PropertyChanged(this, new PropertyChangedEventArgs("Level"));
}
}
}

public event PropertyChangedEventHandler PropertyChanged;

 /**//// <summary>
/// 游戏结束的事件委托
/// </summary>
public event EventHandler GameOver;

 /**//// <summary>
/// 游戏结束后所调用的方法,并触发游戏结束事件
/// </summary>
/// <param name="e"></param>
private void OnGameOver(EventArgs e)
 {
GameStatus = GameStatus.Over;
_timer.Interval = TimeSpan.FromMilliseconds(_initSpeed);
_timer.Stop();

EventHandler handler = GameOver;
if (handler != null)
handler(this, e);
}
}
}
 OK [源码下载]
Feedback
第一次玩:
级别5,行数104,得分2513,Game Over
一个小建议:使用已消行数计算级别而不是根据分数计算,这样的话可以鼓励游戏者尽量一次消多行,这是一种游戏激励机制,呵呵
@x-man
:)
嗯,写点东西好巩固一下学过的东西
楼主太无私了。。。不光公开源码,还把实现思想都说了出来。
@丁学
:)
你强,我玩了几百分就玩不下去了
其实级别也是间接地根据已消行数计算的,一次消的行数越多,得分就几何增长
10 * (int)Math.Pow(2, removeRowCount)
@HedgeHog
:)
过奖了。。。过奖了。。。
第二次玩:
级别5,行数95,得分2747,Game Over
------------
因为下落的对象不是从最上面逐渐出现的,而且直接整个下落对象出现在上面,导致当级别稍微高一点的时候,一旦超过了一半儿的烂行,将很难坚持下来
--引用--------------------------------------------------
webabcd: @丁学
其实级别也是间接地根据已消行数计算的,一次消的行数越多,得分就几何增长
10 * (int)Math.Pow(2, removeRowCount)
--------------------------------------------------------
我的意思是说,固定行数时升级,比如15、30、50、80、120、200、300,达到这些行数时就升级
因为速度到达一定程度之后将很难再继续玩下去,,因此每次玩其实最后都是差不多的分数,但是采用固定行数升级的情况下,因为在玩的过程中可以通过一次消多行来获得更高的分数,所以可玩性相对高一些
我也第一次玩:
级别5,行数134,得分3113,Game over
@丁学
:)
。。。厉害,兄弟QA出身?
嗯,计分的那个明白了,确实像你说的那样计分最合理,中午没事时改了它
@Clark Zheng
:)
@笑忘书
:)
像丁学说的一样,最后的得分都差不多
@webabcd
偶是coder不是QA,只是游戏玩多了,哈哈
@丁学
我可是游戏白痴啊,从小玩游戏就是不用秘籍肯定通不了关,咳。。。
@Andy Huang
:)
多谢支持
@lalala
:)
过奖
@webabcd
你是做游戏的,我是玩游戏的,大家所站的位置不同,哈哈
俄罗斯方块 玩儿过多种多样的,只还没有见到过Silverlight制造出来的。。
玩儿过多种多样的方块,只还没有见过Silverlight制造出来的,
有测试地址否?
@GuoYong.Che
@SuperWulei
:)
大家多交流,互相学习
@百虎歌
就是本页开头的那个在线DEMO,装了插件就可以玩了
@丁学
:)
按你的建议改好了,呵呵
// 根据已消行数计算级别,依据丁学的建议,计算方法: 已消行数/5 的平方根 取整
Level = (int)Math.Sqrt(RemoveRowCount / 5);
我来回来了,我的小游戏网站是哈哈游戏网:www.212616.com flash游戏很多
// 根据已消行数计算级别,依据丁学的建议,计算方法: 已消行数/5 的平方根 取整
Level = (int)Math.Sqrt(RemoveRowCount / 5);
看来我下的是修改过的版本。。按照楼主的方法做了一遍。。这算了让我好头疼。。崇拜楼主。。
@webabcd
级别5,行数130,分数3372
总也过不了速度5 ~~~~~~
@wenew
:)
我的理想:把所有flash游戏都用sl做一遍
不可能啊
@Yoshow
:)
过奖,大家多交流,共同进步
@·风信子·
@renjf
:)
呵呵,加油
@lalala
:)
呵呵,算法应该还算是简单吧
第一次写游戏,没经验啊
@丁学
:)
反正是比我强
@丁学
:)
我那个算是幻想了
嗯。。。目标是争取一个月写一个小游戏
@jol
@玉开
@Jeffrey Zhao
:)
游戏处女作,希望以后能越来越好
呼~~~~~~~~~~
终于搞定第5个级别了:
级别6,行数182,分数4403
为了在追行数时安全一些,分数有点低,嘿嘿
楼主,你好,首先你的代码写的已经非常漂亮了,我很久以前也写过方块游戏,很多地方都不如你的好。
由于我也写过,加之对面向对象的理解,想对你提一些建议,这些建议并不是说你的代码不够好,只不过如果考虑了,可能会更可扩展和维护。
1)有没有考虑过少于四个或多于四个方块的扩展,如果是那样,你的继承PieceBase类中的那些和代码中循环判断中频繁出现的4就需要再考虑。我曾经玩过复杂的方块游戏,在大多数是4个方块的过程中,时不时会来一个、两个或三个方块的不规则方块(此需求如何实现,我目前也没有太好的办法)。
2)在每个方块类中的
public override Color Color
{
get { return Helper.GetColor("#339933"); }
}
具体的颜色属于UI部分,不应该设计在方块类中,应该可以配置
3)积分算法不应该出现在UIControl.cs中,前面丁学提到的积分算法和你原来的算法其实只是两种不同的需求,应该利用策略模式来实现,即开闭原则,这样如果有新的积分算法,就不用再去修改UIControl了。
4)类似 private void RemoveRow()这样的长方法,尽量不要出现,因为它承担了太多的责任(删除行,更新剩下行,积分,其它功能)。可以分解为多个方法更加合适。
其实你的代码已经很优秀,我实在是在鸡蛋里挑骨头,而且说说容易,要实现我说的要求其实很难,有些我自己也没有想好。只是建议,共同探讨。
@丁学
:)
厉害
@BlackWhite
@oec2003
:)
继续努力中。。。
@伍迷
:)
1、现在这个程序,只要是在4×4的范围内的形状都是支持的,也就是最少1块,最多16块,继承PieceBase写好初始化和变形的逻辑就好
2、嗯,这个应该加个setter,不过在具体的形状类中设置一个默认值,我觉得也是合理的
3、4、没错,完全同意,虽然也就是几百行的代码,要是从oo的角度来看确实是还要好多需要重构的地方
多谢你提的宝贵意见,以后开发时会多注意的(其实一直也很注意这些,只不过确实有些犯懒)
再次感谢
4级120行3490分
做为曾经的神匠提点建议:
1,移动和转方向有键冲突,习惯边移边转(托马斯前旋)
2,方块触底没有僵值,L形的块不能在底边“塞”到T形的缺口下面
3,没有一键直落,要按很多次“下”,我称为"垃圾操作"(非特指,每个游戏都有)
4,我习惯双手操作,左手管方向ASWD,左手直落和转向,单手操作中指会死
5,。。。。换个图片?
ps:不要追求数量,我会选择多次玩一年开发的一个游戏,而只玩一次只用一个月开发的游戏。
@风海迷沙
:)
1、2、在我的测试中,没发现着两个问题
3、4、5、嗯,想做好东西,有很多细节需要考虑
其实我写这个东西就是想巩固一下对sl的所学,所以想多写点东西
@小手冰冰凉
:)
。。。其实理顺了还是很简单的,就几百行代码
@未注册
下载完直接打开.sln就可以了,但是在这之前,你得先装上SL的开发包
Helper.GetColor("#9347923")
这个类是自己定义的吗 ?
我怎么没找到.
我怎么引用不到..
@worldman
是的,篇幅有限,只能贴出关键部分的代码
看全部代码的话请下载源码
磊哥. 能否将Helper.GetColor的源码发送到我的邮箱.
很想知道如何实现
@worldman
:)
整个源代码都有的,下载后查看
YYTetris/Helper.cs文件就是你说的那段代码
楼主,我刚学习Silverlight,为什么我下载后,打开文件提示"This project type is not supported by this installation",这个错误?
public Color? color { get { if (RectBlock.Visibility == Visibility.Collapsed) return null; else return GradationColor.Color; } } Color后面的问号是干什么用的??一直都没有想明白。。。。
@Jane Wang 借由泛型而实现的可空类型,使得值类型也可以赋null “?”是简写,其等价于 Nullable<T>
想请教个问题,如何实现点一个矩形的时候出现八个控制点,控制矩形的大小,类似dreamweaver设计状态下拉一个div的大小那种效果??
@小高好孩子 逻辑上你已经描述的很清楚了 就这样矩形获得焦点,动态生成控制点,在控制点上监听鼠标拖动事件,在此事件中动态改变矩形的大小及控制点的位置 没有别的简单的办法,老老实实写吧
|