Swift 实现俄罗斯方块详细思路解析(附完整项目)
一:写在开发前
俄罗斯方块,是一款我们小时候都玩过的小游戏,我自己也是看着书上的思路,学着用 Swift 来写这个小游戏,在写这个游戏的过程中,除了一些位置的计算,数据模型和理解 Swift 语言之外,最好知道UIKIt框架中的 Quartz2D 这个知识点。是我在简书上面找的,是关于 Quartz2D 这个知识点的,看它我觉得也就够学习。经过这两天的整理,充分觉得在写这些之前,一定要理清楚思路,你可能会花很多时间在它上面,你要知道了,怎么写就变的反而简单了。
二:具体开发思路及主要代码
我在博客的最下面附上了完整的代码,大家可以在Git上下载到它,你要也使用Git,就顺便给我个小星星吧 O(∩_∩)O哈哈~。。
1》游戏界面的布局设计
这个里面的Label 和 Button 就不多费口舌了,这不是我们的重点,看看这个效果我们也就一笔带过了吧!重点是我们使用的上面说的利用 Quartz2D 这个知识画出来表格。它单看就是一个 N * M 的表格,在它里面就要运行我们的俄罗斯小方块,在下面的代码里面也会详细的说明它的制作。

下面是我们绘制上面网格视图的方法,下面所有代码方法里面的有些参数是定义成全局变量的,大家可以下载完整版的代码去看看。在代码中也加了许多的注释,相信都能看的明白的。
// MARK: 绘制俄罗斯方库网格的方法
func creatcells(rows:Int,cols:Int,cellwidth:Int,cellHeight:Int) -> Void { // 开始创建路径
CGContextBeginPath(CTX)
// 绘制横向网格对应的路径
for i in 0...TETRIS_Row { CGContextMoveToPoint(CTX, 0, CGFloat(i * CELL_Size))
CGContextAddLineToPoint(CTX, CGFloat(TETRIS_Cols * CELL_Size), CGFloat(i * CELL_Size)) }
// 绘制纵向的网格对应路径
for i in 0...TETRIS_Cols { CGContextMoveToPoint(CTX, CGFloat(i * CELL_Size),0)
CGContextAddLineToPoint(CTX, CGFloat(i * CELL_Size), CGFloat(TETRIS_Row * CELL_Size)) }
// 关闭
CGContextClosePath(CTX) // 设置笔触颜色
CGContextSetStrokeColorWithColor(CTX, UIColor(red: 0.9 , green: 0.9 , blue: 0.9,alpha: 1).CGColor)
// 设置效线条粗细
CGContextSetLineWidth(CTX, CGFloat(STROKE_Width))
// 绘制线条
CGContextStrokePath(CTX) }
2》小游戏的数据模型
1: 游戏的游戏界面是一个 N * M 的网格,每一张网格显示一张图片,但对于我们来说,我门就得用一个二维数组来定义,纪录每一块的行和列!来保存游戏的状态。我们在最开始把每一个小块的游状态都初始化为 0 ,看下面代码。
// 定义用于纪录方块游戏状态的二维数组
var tetris_status = [[Int]]() // MARK初始化游戏状态
func initTetrisStatus() -> Void { let tmpRow = Array.init(count: TETRIS_Cols, repeatedValue: NO_Block)
tetris_status = Array.init(count: TETRIS_Row, repeatedValue: tmpRow) }
2: 游戏的过程中有一只处于“下落”状态的四个方块,这四个方块我们也会是要纪录,才可以做它的旋转、向左、向右等等的处理。我们就用一个数组包含着四个方块,那具体到这四个方块呢?我们就用一个结构体去体现你这四个方块它的 X、Y值和颜色。
struct Block {
    var X:Int
    var Y:Int
    var Color:Int
    var description:String {
        return "Block[X=\(X),Y=\(Y),Color=\(Color)]"
    }
}
3:在俄罗斯方块这个游戏中,你也肯定得知道有哪些方块的组合可以下落,这也是一个数据源!你也得定义好,在每次要下落的时候你就随机取出这个而数据源里面的数据,让它随机的出现下落。这些工作也就是你要在初始化上面要纪录的四个正在下落的方块数组的时候做的事了,下面是这些个组合的数据源。
// 几种可能的组合方块
self.blockArr = [ // 第一种可能出现的组合 Z
[
Block(X:TETRIS_Cols/2 - 1,Y:0,Color:1),
Block(X:TETRIS_Cols/2,Y:0,Color:1),
Block(X:TETRIS_Cols/2,Y:1,Color:1),
Block(X:TETRIS_Cols/2 + 1,Y:1,Color:1) ],
// 第二种可能出现的组合 反Z
[
Block(X:TETRIS_Cols/2 + 1,Y:0,Color:2),
Block(X:TETRIS_Cols/2,Y:0,Color:2),
Block(X:TETRIS_Cols/2,Y:1,Color:2),
Block(X:TETRIS_Cols/2 - 1,Y:1,Color:2) ],
// 第三种可能出现的组合 田
[
Block(X:TETRIS_Cols/2 - 1,Y:0,Color:3),
Block(X:TETRIS_Cols/2,Y:0,Color:3),
Block(X:TETRIS_Cols/2 - 1,Y:1,Color:3),
Block(X:TETRIS_Cols/2 ,Y:1,Color:3) ],
// 第四种可能出现的组合 L
[
Block(X:TETRIS_Cols/2 - 1,Y:0,Color:4),
Block(X:TETRIS_Cols/2 - 1,Y:1,Color:4),
Block(X:TETRIS_Cols/2 - 1,Y:2,Color:4),
Block(X:TETRIS_Cols/2 ,Y:2,Color:4) ],
// 第五种可能出现的组合 J
[
Block(X:TETRIS_Cols/2,Y:0,Color:5),
Block(X:TETRIS_Cols/2,Y:1,Color:5),
Block(X:TETRIS_Cols/2,Y:2,Color:5),
Block(X:TETRIS_Cols/2 - 1,Y:2,Color:5) ],
// 第六种可能出现的组合 ——
[
Block(X:TETRIS_Cols/2,Y:0,Color:6),
Block(X:TETRIS_Cols/2,Y:1,Color:6),
Block(X:TETRIS_Cols/2,Y:2,Color:6),
Block(X:TETRIS_Cols/2,Y:3,Color:6) ],
// 第七种可能出现的组合 土缺一
[
Block(X:TETRIS_Cols/2,Y:0,Color:7),
Block(X:TETRIS_Cols/2-1,Y:1,Color:7),
Block(X:TETRIS_Cols/2,Y:1,Color:7),
Block(X:TETRIS_Cols/2 + 1,Y:1,Color:7) ],
]
随机取出下落
// 定义纪录 “正在下掉的四个方块” 位置
var currentFall = [Block]()
func initBlock() -> Void { // 生成一个在 0 - blockArr.count 之间的随机数
let rand = Int(arc4random()) % blockArr.count
// 随机取出 blockArr 数组中的某个元素为正在下掉的方块组合
currentFall = blockArr[rand] }
3》 游戏逻辑处理
1:下落
前面我们提到过有用数组纪录正在下落的四个方块的状态,我们梳理一下“下落”状态的逻辑关系。如果在下落的状态,你只需要把这四个正在下落的方块的 Y 值加 1 即可! 但是得注意什么情况下它不能再下落了。。
(1):如果方块组合中任意一个方块已经到达了最底下就不能再下落了。
(2) :如果方库组合中任意一个方块的下面有了方块就不能再下落了。
下落的实现思路就是,如果有方块可以下落,那么就把方块组合原来所在位置的颜色清楚,然后把组合中的每一个方块的 Y 属性加1 ,最后把当前方块的所在位置加上相应的颜色,下面是思路实现的代码。
// MARK:控制方块组合向下移动
func movedown () -> Void { // 定义能否向下掉落的 标签
var canDown = true // 遍历每一块方块,判断它是否能向下掉落
for i in 0..<currentFall.count { // 第一种情况,如果位置到行数最底下了,不能再下落
if currentFall[i].Y >= TETRIS_Row - 1 { canDown = false
break
}
// 第二种情况,如果他的下面有了方块,不能再下落
if tetris_status[currentFall[i].Y + 1][currentFall[i].X] != NO_Block { canDown = false
break
}
}
// 如果能向下掉落
if canDown { self.drawBlock()// for i in 0..<currentFall.count { let cur = currentFall[i]
// 设置填充颜色
CGContextSetFillColorWithColor(CTX, UIColor.whiteColor().CGColor) CGContextFillRect(CTX, CGRectMake(CGFloat(cur.X * CELL_Size + STROKE_Width), CGFloat(cur.Y * CELL_Size + STROKE_Width), CGFloat(CELL_Size - STROKE_Width * 2),CGFloat(CELL_Size - STROKE_Width * 2))) }
// 遍历每一个方块。控制每一个方块的 有坐标都 加 1
for i in 0..<currentFall.count { currentFall[i].Y += 1 }
// 将下移后的每一个方块的背景涂色称该方块的颜色
for i in 0..<currentFall.count { let cur = currentFall[i]
// print(cur.X , cur.Y)
CGContextSetFillColorWithColor(CTX, colors[cur.Color])
CGContextFillRect(CTX, CGRectMake(CGFloat(cur.X * CELL_Size + STROKE_Width), CGFloat(cur.Y * CELL_Size + STROKE_Width), CGFloat(CELL_Size - STROKE_Width * 2),CGFloat(CELL_Size - STROKE_Width * 2))) }
}
// 不能向下掉落
else
{
// 遍历每个方块,把每个方块的值纪录到
for i in 0..<currentFall.count { let cur = currentFall[i]
// 小于2表示已经到最上面,游戏要结束了
if cur.Y < 2 { // 计时器失效
curTimer?.invalidate()
// 提示游戏结束
self.delegate.UpdateGameState() } // 把每个方块当前所在的位置赋值为当前方块的颜色值
tetris_status[cur.Y][cur.X] = cur .Color }
// 判断是否有可消除的行
lineFull()
// 开始一组新的方块
initBlock()
} // 获取缓存区的图片
image = UIGraphicsGetImageFromCurrentImageContext()
// 通知重绘
self.setNeedsDisplay()
}
里面的代理更新UI(及分数和速度)我们就不多说了,说说 drawBlock() 这个方法,它是来绘制了我们在所有的方块,相当于把我们的互数据模型给全都可视化;
//MARK: 绘制俄罗斯方块的状态
func drawBlock() -> Void { for i in 0..<TETRIS_Row { for j in 0..<TETRIS_Cols { if tetris_status[i][j] != NO_Block { // 设置填充颜色
CGContextSetFillColorWithColor(CTX, colors[tetris_status[i][j]])
CGContextFillRect(CTX, CGRectMake(CGFloat(j * CELL_Size + STROKE_Width),CGFloat(i * CELL_Size + STROKE_Width) , CGFloat(CELL_Size - STROKE_Width * 2), CGFloat(CELL_Size - STROKE_Width * 2))) }
else
{ // 设置填充颜色
CGContextSetFillColorWithColor(CTX, UIColor.whiteColor().CGColor)
CGContextFillRect(CTX, CGRectMake(CGFloat(j * CELL_Size + STROKE_Width),CGFloat(i * CELL_Size + STROKE_Width) , CGFloat(CELL_Size - STROKE_Width * 2), CGFloat(CELL_Size - STROKE_Width * 2))) }
}
}
}
2:判断这行是否已满
上面是让它下落了,里面有调用判断一行是否已满,其实这里的逻辑就是遍历每一行每一个方块,给你的每一行都加一个状态,这里是 true ,判断你该行的每一个方块的状态是不是初始化时候的 0 ,要是,那说明是缺方块的,这行没有满,跳出。。要是都不是,那就说明这行都满了。。就可以进行消除这行的后续操作了。增加积分,消除相应的行等,下面是它的代码。
// MARK: 判断是否有一行已满
func lineFull() -> Void{
// 遍历每一行
for i in 0..<TETRIS_Row { var flag = true
// 遍历每一行的每一个单元
for j in 0..<TETRIS_Cols { if tetris_status[i][j] == NO_Block { flag = false
break
}
}
// 如果当前行已经全部有了方块
if flag { // 当前积分增加 100
curScore += 100
// 代理更新当前积分
self.delegate.UpdateScore(curScore) if curScore >= curSpeed * curSpeed * 500{ curSpeed += 1
// 代理更新当前速度
self.delegate.UpdateSpeed(curSpeed)
curTimer?.invalidate()
curTimer = NSTimer.scheduledTimerWithTimeInterval(BASE_Speed/Double(curSpeed), target: self, selector: #selector(self.movedown), userInfo: nil, repeats: true)
} }
// 把所有的整体下移一行
for var j = i; j < 0 ; j -= 1 { for k in 0..<TETRIS_Cols { tetris_status[j][k] = tetris_status[j-1][k] } }
// 播放消除的音乐
// if !disBackGroundMusicPlayer.play() {
//
// disBackGroundMusicPlayer.play()
// }
}
}
3.左移处理
它的处理方式和上面的下落的逻辑是一样的,也就是两点,到了最左边和左边有了两类型的情况,代码如下。
//MARK: 定义左边移动的方法
func moveLeft () -> Void { // 定义左边移动的标签
var canLeft = true
for i in 0..<currentFall.count { if currentFall[i].X <= 0 { canLeft = false
break
}
// 左变位置的前边一块
if tetris_status[currentFall[i].Y][currentFall[i].X - 1] != NO_Block { canLeft = false
break }
}
// 如果可以左移
if canLeft { self.drawBlock()
// 将左移前的的每一个方块背景涂成白底
for i in 0..<currentFall.count { let cur = currentFall[i]
CGContextSetFillColorWithColor(CTX, UIColor.whiteColor()
.CGColor)
CGContextFillRect(CTX, CGRectMake(CGFloat(cur.X * CELL_Size + STROKE_Width), CGFloat(cur.Y * CELL_Size + STROKE_Width), CGFloat(CELL_Size - STROKE_Width * 2), CGFloat(CELL_Size - STROKE_Width * 2))) } // 左移正字啊下掉的方块
for i in 0..<currentFall.count { currentFall[i].X -= 1 } // 将左移后的的每一个方块背景涂成对应的颜色
for i in 0..<currentFall.count { let cur = currentFall[i]
CGContextSetFillColorWithColor(CTX,colors[cur.Color])
CGContextFillRect(CTX, CGRectMake(CGFloat(cur.X * CELL_Size + STROKE_Width), CGFloat(cur.Y * CELL_Size + STROKE_Width), CGFloat(CELL_Size - STROKE_Width * 2), CGFloat(CELL_Size - STROKE_Width * 2))) }
// 获取缓冲区的图片
image = UIGraphicsGetImageFromCurrentImageContext() // 通知重新绘制
self.setNeedsDisplay() }
}
4.右移处理
右边移动的处理情况几乎就和左边的完全相同了,见代码
// MARK: 定义右边移动的方法
func moveRight () -> Void { // 能否右移动的标签
var canRight = true
for i in 0..<currentFall.count { // 如果已经到最右边就不能再移动
if currentFall[i].X >= TETRIS_Cols - 1 { canRight = false
break
}
// 如果右边有方块,就不能再移动
if tetris_status[currentFall[i].Y][currentFall[i].X + 1] != NO_Block { canRight = false
break
}
}
// 如果能右边移动
if canRight { self.drawBlock()
// 将香油移动的每个方块涂白色
for i in 0..<currentFall.count { let cur = currentFall[i]
CGContextSetFillColorWithColor(CTX, UIColor.whiteColor().CGColor)
CGContextFillRect(CTX, CGRectMake(CGFloat(cur.X * CELL_Size + STROKE_Width), CGFloat(cur.Y * CELL_Size + STROKE_Width), CGFloat(CELL_Size - STROKE_Width * 2), CGFloat(CELL_Size - STROKE_Width * 2))) }
}
// 右边移动正在下落的所有的方块
for i in 0..<currentFall.count { currentFall[i].X += 1 }
// 有以后将每个方块的颜色背景图成各自方块对应的颜色
for i in 0..<currentFall.count { let cur = currentFall[i]
// 设置填充颜色
CGContextSetFillColorWithColor(CTX, colors[cur.Color])
// 绘制矩形
CGContextFillRect(CTX, CGRectMake(CGFloat(cur.X * CELL_Size + STROKE_Width), CGFloat(cur.Y * CELL_Size + STROKE_Width), CGFloat(CELL_Size - STROKE_Width * 2), CGFloat(CELL_Size - STROKE_Width * 2))) image = UIGraphicsGetImageFromCurrentImageContext()
// 通知重新绘制
self.setNeedsDisplay() }
}
5.旋转处理
旋转处理,就得用点数学知识了,你画一个坐标轴,试着把一个点顺时针或者逆时针旋转九十度,你再写出旋转后的坐标。其实清楚了这点也就OK了,我们是按逆时针旋转处理的,四个方块,就按照第三个作为它的旋转轴心。
// MARK: 定义旋转的方法
func rotate () -> Void { // 定义是否能旋转的标签
var canRotate = true
for i in 0..<currentFall.count
{ let preX = currentFall[i].X
let preY = currentFall[i].Y
// 始终以第三块作为旋转的中心
// 当 i == 2的时候,说明是旋转的中心
if i != 2
{ // 计算方块旋转后的X,Y坐标
let afterRotateX = currentFall[2].X + preY - currentFall[2].Y
let afterRotateY = currentFall[2].Y + currentFall[2].X - preX // 如果旋转后的x,y坐标越界,或者旋转后的位置已有别的方块,表示不能旋转
if afterRotateX < 0 || afterRotateX > TETRIS_Cols - 1 || afterRotateY < 0 || afterRotateY > TETRIS_Row - 1 || tetris_status[afterRotateY][afterRotateX] != NO_Block
{ canRotate = false
break }
}
} // 如果能旋转
if canRotate
{ self.drawBlock() for i in 0..<currentFall.count
{ let cur = currentFall[i]
// 设置填充颜色
CGContextSetFillColorWithColor(CTX, UIColor.whiteColor().CGColor)
// 绘制矩形
CGContextFillRect(CTX, CGRectMake(CGFloat(cur.X * CELL_Size + STROKE_Width), CGFloat(cur.Y * CELL_Size + STROKE_Width), CGFloat(CELL_Size - STROKE_Width * 2), CGFloat(CELL_Size - STROKE_Width * 2))) } for i in 0..<currentFall.count
{ let preX = currentFall[i].X
let preY = currentFall[i].Y // 始终第三个作为旋转中心
if i != 2
{
currentFall[i].X = currentFall[2].X + preY - currentFall[2].Y
currentFall[i].Y = currentFall[2].Y + currentFall[2].X - preX
}
} for i in 0..<currentFall.count
{ let cur = currentFall[i]
CGContextSetFillColorWithColor(CTX, colors[cur.Color])
// 绘制矩形
CGContextFillRect(CTX, CGRectMake(CGFloat(cur.X * CELL_Size + STROKE_Width), CGFloat(cur.Y * CELL_Size + STROKE_Width), CGFloat(CELL_Size - STROKE_Width * 2), CGFloat(CELL_Size - STROKE_Width * 2))) } // 获取缓存区的图片
image = UIGraphicsGetImageFromCurrentImageContext()
// 通知重新绘制
self.setNeedsDisplay() }
}
三:启动游戏
做完了上面的工作,你就可以启动你的游戏了,你的做的工作就有下面这些;
重置游戏积分,将积分设置为 0
重置下落的速度,也将它设置为0
初始化俄罗斯方块的状态,将它们的值全都初始化为 0
生成一组在下落的方块组
启动计时器,控制下落的方块
// MARK:开始游戏
func startGame()
{ self.curSpeed = 1
self.delegate.UpdateSpeed(self.curSpeed) self.curScore = 0
self.delegate.UpdateScore(self.curScore) // 初始化游戏状态
self.initTetrisStatus() // 初始化四个正在下落的方块
self.initBlock() // 定时器控制下落
curTimer = NSTimer.scheduledTimerWithTimeInterval(BASE_Speed/Double(curSpeed), target: self, selector: #selector(self.movedown), userInfo: nil, repeats: true) }
PS:一张游戏运行图片

四:写在开发后
差不多到这里也就结束了,但里面有一个BUG,有些时候会发生一个数组的越界导致的崩溃,这个问题有时间在好好看一下,自己写的里面可能还有我不知道的问题,也没做大量的测试,感兴趣的朋友可以自己好好完善一下,比如试试暂停,重新开始这些功能的。。反正肯定还有写的不好的地方,有问题大家可以发消息随时交流!!

写完了,说点无聊的,说说自己
Swift 实现俄罗斯方块详细思路解析(附完整项目)的更多相关文章
- Java架构师方案—多数据源开发详解及原理(二)(附完整项目代码)
		
1. mybatis下数据源开发工作 2. 数据源与DAO的关系原理模型 3. 为什么要配置SqlSessionTemplate类的bean 4. 多数据源应用测试 1. mybatis下数据源开发工 ...
 - Java eclipse下 Ant build.xml实例详解 附完整项目源码
		
在有eclipse集成环境下ant其实不是很重要,但有些项目需要用到,另外通过eclipse来学习和理解ant是个很好的途径,所以写他demo总结下要点,希望能够帮到大家. 一.本人测试环境eclip ...
 - 【算法】禁忌搜索算法(Tabu Search,TS)超详细通俗解析附C++代码实例
		
01 什么是禁忌搜索算法? 1.1 先从爬山算法说起 爬山算法从当前的节点开始,和周围的邻居节点的值进行比较. 如果当前节点是最大的,那么返回当前节点,作为最大值 (既山峰最高点):反之就用最高的邻居 ...
 - 使用Express连接mysql详细教程(附项目的完整代码我放在结尾了)
		
使用Express连接mysql详细教程(附项目的完整代码我放在结尾了) 要使用Express连接本地数据库 我们首先需要安装好Express的依赖 我们使用这个框架呢首先要有一点ajax的基础 如果 ...
 - 图片文档倾斜矫正算法 附完整c代码
		
2年前在学习图像算法的时候看到一个文档倾斜矫正的算法. 也就是说能将一些文档图像进行旋转矫正, 当然这个算法一般用于一些文档扫描软件做后处理 或者用于ocr 文字识别做前处理. 相关的关键词: 抗倾斜 ...
 - java连接mysql数据库详细步骤解析
		
java连接mysql数据库详细步骤解析 第一步:下载一个JDBC驱动包,例如我用的是:mysql-connector-java-5.1.17-bin.jar 第二步:导入下载的J ...
 - Swift完整项目打包Framework,嵌入OC项目使用
		
场景说明: -之前做的App,使用Swift框架语言,混合编程,内涵少部分OC代码. -需要App整体功能打包成静态库,完整移植到另一个App使用,该App使用OC. -所以涉及到一个语言互转的处理, ...
 - Jbpm4.4+hibernate3.5.4+spring3.0.4+struts2.1.8整合例子(附完整的请假流程例子,jbpm基础,常见问题解决)
		
Jbpm4.4+hibernate3.5.4+spring3.0.4+struts2.1.8 整合例子(附完整的请假流程例子). 1.jbpm4.4 测试环境搭建 2.Jbpm4.4+hibernat ...
 - WebRTC 音频采样算法 附完整C++示例代码
		
之前有大概介绍了音频采样相关的思路,详情见<简洁明了的插值音频重采样算法例子 (附完整C代码)>. 音频方面的开源项目很多很多. 最知名的莫过于谷歌开源的WebRTC, 其中的音频模块就包 ...
 
随机推荐
- CORBA技术及实例
			
CORBA技术及实例 CORBA是一种规范,它定义了分布式对象如何实现互操作.在WorldWideWeb盛行之前,非凡是java编程语言风靡之前,C++开发者基本将CORBA作为其高端分布式对象的解决 ...
 - 严重: Servlet.service() for servlet jsp threw exception     java.lang.IllegalStateException: getOutputStream() has already been called for this response
			
严重: Servlet.service() for servlet jsp threw exception java.lang.IllegalStateException: getOutputS ...
 - MySQL 启动、关闭、选择数据库等命令
			
一.MySQL服务的启动和停止 1.net 命令来启动或停止mysql服务 net stop mysql(mysql是指你真正装的服务,如果装的是 mysql5,必须写成 net stop mysql ...
 - FZU 2112 Tickets
			
这个问题可以转变一下,先要知道有几个连通块,连通块之间肯定需要添加一条边, 还需要知道每个连通块内部需要添加几条边,这个问题等价于求一张图至少需要几笔画成,这个问题的答案是度为奇数的点的个数/2 #i ...
 - 【转】HashMap实现原理分析
			
1. HashMap的数据结构 数据结构中有数组和链表来实现对数据的存储,但这两者基本上是两个极端. 数组 数组存储区间是连续的,占用内存严重,故空间复杂的很大.但数组的二分查找时间复杂度小,为O(1 ...
 - 【转】安卓必备Java基础
			
[转]http://www.zhihu.com/question/19937886(里面提到的知识点的链接) 1. Java 语言基础 谈到Java 语言基础学习的书籍,大家肯定会推荐Bruce Ec ...
 - C++ CRTP singleton
			
C++ CRTP 是个很有意思的东西,因为解释原理的文章很多,但是讲怎么用的就不是很多了. 今天就稍微写下CRTP(奇异递归模板模式)的一个有趣的用法:Singleton(单例模式) 单例有很多中写法 ...
 - UVa 11631 - Dark roads
			
题目大意:政府为了减小开支决定关闭一些路灯,同时保证照亮的路能连接所有路口. 又是一个MST问题,Kruskal算法,不过数据规模比较大,又Submission Error了...扔这吧... #in ...
 - java求两个集合的差集
			
public static void main(String[] args) {Set set = new HashSet();Set set1 = new HashSet();set.add(&qu ...
 - c#和java中的方法覆盖——virtual、override、new
			
多态和覆盖 多态是面向对象编程中最为重要的概念之一,而覆盖又是体现多态最重要的方面.对于像c#和java这样的面向对象编程的语言来说,实现了在编译时只检查接口是否具备,而不需关心最终的实现,即最终的实 ...