The object of art is to give life shape
- William Shakespeare
上一章节我们介绍了这个游戏最基本的组成元素,block,那么接下来我们就开始更为清晰和形象地了解如果做出来俄罗斯方块的shape吧。是的,就是这样的形状:
首先我们来新建一个类,名字叫做Shape;到这里新建一个类的步骤应该很熟练了吧。 我们来修改下面的代码
在代码的第一部分,我们先建立了一个枚举类型,这个enumeration用来辅助我们定义我们的shape的4个方向,无论我们的形状处于什么角度,我们都将把它修正成4个方向: 0,90,180和270,形象点,就是这样的:
在这部分代码里面,还有两个函数,一个是随机生成函数(random),一个是旋转函数(rotate)。
random函数我们之前有接触过,而且代码看起来也很清楚,就不多说了。而rotate函数其实也不难,传入两个参数,一个是我们上面建立好的枚举类型
Orientation,一个是bool型的变量,表示是顺时针还是逆时针旋转,如果是顺时针,枚举类型就+1, 如果是逆时针就-1
当然,这样很容一出现4和-1的情况,如果是270°(3)再顺时针转一圈就+1变成了4,我们要手动把它修复成0度;同理-1的情况修复成270°,然后我们把这个经过旋转后的角度返回出来。
好了,定义好了角度,我们就来写shape类了,准备好了么?
提醒:可能在你输入了下面的代码以后会有部分错误,不要着急,我们会在后面修补它们
在执行printable的 computed property里面,图片里面没有显示完全,这里重新打一遍吧:
var description:String { return "\(color) block facing \(orientation): \(blocks[FirstBlockIdx]), \(blocks[SecondBlockIdx]), \(blocks[ThirdBlockIdx]), \(blocks[FourthBlockIdx])" }
shape类其实是一个父类,我们游戏中用到的所有shapes都将继承自这个类,所以NumShapeTypes被定义为7,因为我们一共有7种形状。而对于每一个shape,我们都可以用4个block来组建成,所以我们把4个block分别加上index。
#1和#2
介绍了新的swift 特性,你一定会很感兴趣。这里我们定义了两个 computed properties,然后把它们的返回值设置为空,这就好比是C++里面的纯虚函数,必须在它的子类中进行重新定义。我们一会将会看到它。
在#1中,blockRowColumnPositions 定义了一个computed 的字典,字典是被一对方括弧【】定义的:字典中的内容都是成对出现的,一个是key(关键字),对应的是value(值)。
关于更多swift dictionary 的细节可以点击这里
字典我们知道的,可以当我第一次看到Array<(columnDiff:Int, rowDiff: Int)>
的时候,还是特别不能理解这又是个啥?
这个在swift里面其实还是一个传统的数组,我们从Array上面也能理解,只不过里面是一个叫做tuple的类型。 tuple其实就是为了简化开发者的工作量,让我们可以直接定义一个返回multiple variable的结构。
总体说来,这个blockRowColumnPositions字典,里面定义的是一个shape的4个方向(这就是为什么key是orientation)时,block的位置(一个shape是由多个block组成的,所以是一个数组;而位置需要坐标来定义,所以需要tuple)。
如果大家在这里还是不太明白,不用着急,一会看到子类的定义后,再回来看这个定义就能够明白了。
#3 我们定义了一个完整的computed property,我们需要返回处于底部的blocks,你可以想象下你的shape落到底层,或者和别的shape堆叠起来时的样子,这也是为什么我们需要这样的定义。
#4 这里我们用到了 reduce<S : Sequence, U>(sequence:
方式 去hash我们的整个blocks数组,和之前一样,我们用$0表示第一个参数,$1,表示第二个参数,用他们的亦或值来唯一的定位他们
S, initial: U, combine: (U, S.GeneratorType.Element) -> U) -> U
#5 这里我们遇到了swift的一个新的关键字 : convenience。 其实就相当于构造函数的重载 , 之前的那个init在swift里面叫做 designated init,也就是必须要有的,而为什么要叫 convenience 就如它的字面意思一样,是一个便利用户的init,在这里面必须调用之前的
designated init,否则会出错。其实就是在convenience init里面做了一些定制化的操作,例如在我们的程序里面,构造了一个随机颜色,随机方向的shape。
接下来,让我们来修复里面的一些bug吧:
#1 我们定义了一个final func意味着这个函数不能被子类重写,而且这个initializeBlocks只能被shape类及它的子类所调用。
#2
上去的if语句其实相当于这样:
我们注意到里面还有个符号
..< 其实就是 i >=0 && i< blockRowColumnTranslations.count , 而如果是 ...
就表示 0 <= i <= count了。
好了,我们的shape父类就定义好了,让我们来定义它的子类吧:
我们首先来定义一个方块的shape吧:
和新建shape类的步骤一样,我们新建一个名为 SquareShape的类
我们可以在注释掉的内容里面看懂,其实一个方块的shape,就是4个block堆积起来的,仿佛你画一个草稿,4个方块,以此是0,1,2,3 ,我们只需要补充一下在父类里面的两个“纯虚函数”
blockRowColumnPositions 和bottomBlocksForOrientations
因为方块无论你怎样旋转,它看起来都是不变的,所以对于4个方向,我们的0,1,2,3号block其实不需要变换位置,坐标都是固定的。
接下来就是TShape,LineShape,SShape,ZShape,LShape和JShape总共7种shape了,建议大家可以新建一个文件夹,然后把这些文件都放进去,这样看起来整洁一点:
这部分的代码确实比较繁琐,如果大家可以试着自己写一写,当然,也可以直接用我们提供给大家的
TShape.swift
class TShape: Shape{ /* orientation 0 * |0| |1||2||3| orientation 90 * |1| |2||0| |3| orientation 180 * or * |3||2||1| |1||2||3| |0| |0| orientation 270 * |3| or * |1| |0||2| |0||2| |1| |3| * marks the row/column indicator for the shape */ override var blockRowColumnPositions: [Orientation: Array<(columnDiff:Int, rowDiff: Int)>]{ return [ Orientation.Zero : [(1,0),(0,1),(1,1),(2,1)], Orientation.Ninety : [(2,1),(1,0),(1,1),(1,2)], Orientation.OneEighty : [(1,2),(2,1),(1,1),(0,1)], Orientation.TwoSeventy : [(0,1),(1,2),(1,1),(1,0)] ] } override var bottomBlocksForOrientations: [Orientation: Array<Block>]{ return [ Orientation.Zero : [blocks[SecondBlockIdx],blocks[ThirdBlockIdx],blocks[FourthBlockIdx]], Orientation.Ninety : [blocks[FirstBlockIdx],blocks[FourthBlockIdx]], Orientation.OneEighty : [blocks[FirstBlockIdx],blocks[SecondBlockIdx],blocks[FourthBlockIdx]], Orientation.TwoSeventy : [blocks[FirstBlockIdx],blocks[SecondBlockIdx]] ] } }
这里我把它的程序中 180和270°的位置换一下,我的是完全按照顺时针的顺序转下去时每个block的位置计算的,而原始教材里面的代码会出现的一个情况是,旋转的时候会出现类似跳帧的情况,你会看到左上角的方块直接到右下角了,大家可以按照两种不同的位置试一下。
LineShape.swift
class LineShape:Shape { /* Orientations 0 and 180: | 0?| | 1 | | 2 | | 3 | Orientations 90 and 270: | 0 | 1?| 2 | 3 | ? marks the row/column indicator for the shape */ // Hinges about the second block override var blockRowColumnPositions: [Orientation: Array<(columnDiff: Int, rowDiff: Int)>] { return [ Orientation.Zero: [(0, 0), (0, 1), (0, 2), (0, 3)], Orientation.Ninety: [(-1,0), (0, 0), (1, 0), (2, 0)], Orientation.OneEighty: [(0, 0), (0, 1), (0, 2), (0, 3)], Orientation.TwoSeventy: [(-1,0), (0, 0), (1, 0), (2, 0)] ] } override var bottomBlocksForOrientations: [Orientation: Array<Block>] { return [ Orientation.Zero: [blocks[FourthBlockIdx]], Orientation.Ninety: blocks, Orientation.OneEighty: [blocks[FourthBlockIdx]], Orientation.TwoSeventy: blocks ] } }
LShape.swift
class LShape:Shape { /* Orientation 0 | 0?| | 1 | | 2 | 3 | Orientation 90 ? | 2 | 1 | 0 | | 3 | Orientation 180 | 3 | 2?| | 1 | | 0 | Orientation 270 ? | 3 | | 0 | 1 | 2 | ? marks the row/column indicator for the shape Pivots about `1` */ override var blockRowColumnPositions: [Orientation: Array<(columnDiff: Int, rowDiff: Int)>] { return [ Orientation.Zero: [ (0, 0), (0, 1), (0, 2), (1, 2)], Orientation.Ninety: [ (1, 1), (0, 1), (-1,1), (-1, 2)], Orientation.OneEighty: [ (0, 2), (0, 1), (0, 0), (-1,0)], Orientation.TwoSeventy: [ (-1,1), (0, 1), (1, 1), (1,0)] ] } override var bottomBlocksForOrientations: [Orientation: Array<Block>] { return [ Orientation.Zero: [blocks[ThirdBlockIdx], blocks[FourthBlockIdx]], Orientation.Ninety: [blocks[FirstBlockIdx], blocks[SecondBlockIdx], blocks[FourthBlockIdx]], Orientation.OneEighty: [blocks[FirstBlockIdx], blocks[FourthBlockIdx]], Orientation.TwoSeventy: [blocks[FirstBlockIdx], blocks[SecondBlockIdx], blocks[ThirdBlockIdx]] ] } }
JShape.swift
class JShape:Shape { /* Orientation 0 ? | 0 | | 1 | | 3 | 2 | Orientation 90 | 3?| | 2 | 1 | 0 | Orientation 180 | 2?| 3 | | 1 | | 0 | Orientation 270 | 0?| 1 | 2 | | 3 | ? marks the row/column indicator for the shape Pivots about `1` */ override var blockRowColumnPositions: [Orientation: Array<(columnDiff: Int, rowDiff: Int)>] { return [ Orientation.Zero: [(1, 0), (1, 1), (1, 2), (0, 2)], Orientation.Ninety: [(2, 1), (1, 1), (0, 1), (0, 0)], Orientation.OneEighty: [(0, 2), (0, 1), (0, 0), (1, 0)], Orientation.TwoSeventy: [(0, 0), (1, 0), (2, 0), (2, 1)] ] } override var bottomBlocksForOrientations: [Orientation: Array<Block>] { return [ Orientation.Zero: [blocks[ThirdBlockIdx], blocks[FourthBlockIdx]], Orientation.Ninety: [blocks[FirstBlockIdx], blocks[SecondBlockIdx], blocks[ThirdBlockIdx]], Orientation.OneEighty: [blocks[FirstBlockIdx], blocks[FourthBlockIdx]], Orientation.TwoSeventy: [blocks[FirstBlockIdx], blocks[SecondBlockIdx], blocks[FourthBlockIdx]] ] } }
SShape.swift
class SShape:Shape { /* Orientation 0 | 0?| | 1 | 2 | | 3 | Orientation 90 ? | 1 | 0 | | 3 | 2 | Orientation 180 | 0?| | 1 | 2 | | 3 | Orientation 270 ? | 1 | 0 | | 3 | 2 | ? marks the row/column indicator for the shape */ override var blockRowColumnPositions: [Orientation: Array<(columnDiff: Int, rowDiff: Int)>] { return [ Orientation.Zero: [(0, 0), (0, 1), (1, 1), (1, 2)], Orientation.Ninety: [(2, 0), (1, 0), (1, 1), (0, 1)], Orientation.OneEighty: [(0, 0), (0, 1), (1, 1), (1, 2)], Orientation.TwoSeventy: [(2, 0), (1, 0), (1, 1), (0, 1)] ] } override var bottomBlocksForOrientations: [Orientation: Array<Block>] { return [ Orientation.Zero: [blocks[SecondBlockIdx], blocks[FourthBlockIdx]], Orientation.Ninety: [blocks[FirstBlockIdx], blocks[ThirdBlockIdx], blocks[FourthBlockIdx]], Orientation.OneEighty: [blocks[SecondBlockIdx], blocks[FourthBlockIdx]], Orientation.TwoSeventy: [blocks[FirstBlockIdx], blocks[ThirdBlockIdx], blocks[FourthBlockIdx]] ] } }
ZShape.swift
class ZShape:Shape { /* Orientation 0 ? | 0 | | 2 | 1 | | 3 | Orientation 90 | 0 | 1?| | 2 | 3 | Orientation 180 ? | 0 | | 2 | 1 | | 3 | Orientation 270 | 0 | 1?| | 2 | 3 | ? marks the row/column indicator for the shape */ override var blockRowColumnPositions: [Orientation: Array<(columnDiff: Int, rowDiff: Int)>] { return [ Orientation.Zero: [(1, 0), (1, 1), (0, 1), (0, 2)], Orientation.Ninety: [(-1,0), (0, 0), (0, 1), (1, 1)], Orientation.OneEighty: [(1, 0), (1, 1), (0, 1), (0, 2)], Orientation.TwoSeventy: [(-1,0), (0, 0), (0, 1), (1, 1)] ] } override var bottomBlocksForOrientations: [Orientation: Array<Block>] { return [ Orientation.Zero: [blocks[SecondBlockIdx], blocks[FourthBlockIdx]], Orientation.Ninety: [blocks[FirstBlockIdx], blocks[ThirdBlockIdx], blocks[FourthBlockIdx]], Orientation.OneEighty: [blocks[SecondBlockIdx], blocks[FourthBlockIdx]], Orientation.TwoSeventy: [blocks[FirstBlockIdx], blocks[ThirdBlockIdx], blocks[FourthBlockIdx]] ] } }