Compare commits
7 Commits
13d0d82391
...
809fea634c
Author | SHA1 | Date | |
---|---|---|---|
809fea634c | |||
0a374b852d | |||
e69d8eda4f | |||
934387dfff | |||
26b5a015d2 | |||
3a15dae884 | |||
f0d0dd0051 |
@ -13,7 +13,6 @@
|
|||||||
E1EB2FBA247A33F200AA6A24 /* GameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1EB2FB9247A33F200AA6A24 /* GameViewController.swift */; };
|
E1EB2FBA247A33F200AA6A24 /* GameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1EB2FB9247A33F200AA6A24 /* GameViewController.swift */; };
|
||||||
E1EB2FBD247A33F200AA6A24 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E1EB2FBB247A33F200AA6A24 /* Main.storyboard */; };
|
E1EB2FBD247A33F200AA6A24 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E1EB2FBB247A33F200AA6A24 /* Main.storyboard */; };
|
||||||
E1EB2FBF247A340600AA6A24 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E1EB2FBE247A340600AA6A24 /* Assets.xcassets */; };
|
E1EB2FBF247A340600AA6A24 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E1EB2FBE247A340600AA6A24 /* Assets.xcassets */; };
|
||||||
E1EB2FC2247A340600AA6A24 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E1EB2FC0247A340600AA6A24 /* LaunchScreen.storyboard */; };
|
|
||||||
E1EB2FCD247A340700AA6A24 /* PrivyetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1EB2FCC247A340700AA6A24 /* PrivyetTests.swift */; };
|
E1EB2FCD247A340700AA6A24 /* PrivyetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1EB2FCC247A340700AA6A24 /* PrivyetTests.swift */; };
|
||||||
E1EB2FD8247A340700AA6A24 /* PrivyetUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1EB2FD7247A340700AA6A24 /* PrivyetUITests.swift */; };
|
E1EB2FD8247A340700AA6A24 /* PrivyetUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1EB2FD7247A340700AA6A24 /* PrivyetUITests.swift */; };
|
||||||
E1EB2FE6247A37EB00AA6A24 /* Sounds in Resources */ = {isa = PBXBuildFile; fileRef = E1EB2FE5247A37EB00AA6A24 /* Sounds */; };
|
E1EB2FE6247A37EB00AA6A24 /* Sounds in Resources */ = {isa = PBXBuildFile; fileRef = E1EB2FE5247A37EB00AA6A24 /* Sounds */; };
|
||||||
@ -29,6 +28,7 @@
|
|||||||
E1EB2FFB247A5E4200AA6A24 /* ZShape.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1EB2FFA247A5E4200AA6A24 /* ZShape.swift */; };
|
E1EB2FFB247A5E4200AA6A24 /* ZShape.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1EB2FFA247A5E4200AA6A24 /* ZShape.swift */; };
|
||||||
E1EB2FFD247AF6ED00AA6A24 /* Privyet.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1EB2FFC247AF6ED00AA6A24 /* Privyet.swift */; };
|
E1EB2FFD247AF6ED00AA6A24 /* Privyet.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1EB2FFC247AF6ED00AA6A24 /* Privyet.swift */; };
|
||||||
E1EB2FFE247B2FFA00AA6A24 /* Sprites.atlas in Resources */ = {isa = PBXBuildFile; fileRef = E1EB2FE7247A380100AA6A24 /* Sprites.atlas */; };
|
E1EB2FFE247B2FFA00AA6A24 /* Sprites.atlas in Resources */ = {isa = PBXBuildFile; fileRef = E1EB2FE7247A380100AA6A24 /* Sprites.atlas */; };
|
||||||
|
E1EB3001247E480300AA6A24 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E1EB2FFF247E480300AA6A24 /* LaunchScreen.storyboard */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@ -56,7 +56,6 @@
|
|||||||
E1EB2FB9247A33F200AA6A24 /* GameViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameViewController.swift; sourceTree = "<group>"; };
|
E1EB2FB9247A33F200AA6A24 /* GameViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameViewController.swift; sourceTree = "<group>"; };
|
||||||
E1EB2FBC247A33F200AA6A24 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
E1EB2FBC247A33F200AA6A24 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||||
E1EB2FBE247A340600AA6A24 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
E1EB2FBE247A340600AA6A24 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
E1EB2FC1247A340600AA6A24 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
|
||||||
E1EB2FC3247A340600AA6A24 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
E1EB2FC3247A340600AA6A24 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
E1EB2FC8247A340700AA6A24 /* PrivyetTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PrivyetTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
E1EB2FC8247A340700AA6A24 /* PrivyetTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PrivyetTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
E1EB2FCC247A340700AA6A24 /* PrivyetTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivyetTests.swift; sourceTree = "<group>"; };
|
E1EB2FCC247A340700AA6A24 /* PrivyetTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivyetTests.swift; sourceTree = "<group>"; };
|
||||||
@ -77,6 +76,7 @@
|
|||||||
E1EB2FF8247A5C8200AA6A24 /* SShape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SShape.swift; sourceTree = "<group>"; };
|
E1EB2FF8247A5C8200AA6A24 /* SShape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SShape.swift; sourceTree = "<group>"; };
|
||||||
E1EB2FFA247A5E4200AA6A24 /* ZShape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZShape.swift; sourceTree = "<group>"; };
|
E1EB2FFA247A5E4200AA6A24 /* ZShape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZShape.swift; sourceTree = "<group>"; };
|
||||||
E1EB2FFC247AF6ED00AA6A24 /* Privyet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Privyet.swift; sourceTree = "<group>"; };
|
E1EB2FFC247AF6ED00AA6A24 /* Privyet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Privyet.swift; sourceTree = "<group>"; };
|
||||||
|
E1EB3000247E480300AA6A24 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@ -127,6 +127,7 @@
|
|||||||
E1EB2FB0247A33F200AA6A24 /* Privyet */ = {
|
E1EB2FB0247A33F200AA6A24 /* Privyet */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
E1EB2FFF247E480300AA6A24 /* LaunchScreen.storyboard */,
|
||||||
E1EB2FB1247A33F200AA6A24 /* AppDelegate.swift */,
|
E1EB2FB1247A33F200AA6A24 /* AppDelegate.swift */,
|
||||||
E1EB2FB5247A33F200AA6A24 /* Actions.sks */,
|
E1EB2FB5247A33F200AA6A24 /* Actions.sks */,
|
||||||
E1EB2FB7247A33F200AA6A24 /* GameScene.swift */,
|
E1EB2FB7247A33F200AA6A24 /* GameScene.swift */,
|
||||||
@ -135,7 +136,6 @@
|
|||||||
E1EB2FE5247A37EB00AA6A24 /* Sounds */,
|
E1EB2FE5247A37EB00AA6A24 /* Sounds */,
|
||||||
E1EB2FBB247A33F200AA6A24 /* Main.storyboard */,
|
E1EB2FBB247A33F200AA6A24 /* Main.storyboard */,
|
||||||
E1EB2FBE247A340600AA6A24 /* Assets.xcassets */,
|
E1EB2FBE247A340600AA6A24 /* Assets.xcassets */,
|
||||||
E1EB2FC0247A340600AA6A24 /* LaunchScreen.storyboard */,
|
|
||||||
E1EB2FC3247A340600AA6A24 /* Info.plist */,
|
E1EB2FC3247A340600AA6A24 /* Info.plist */,
|
||||||
E1EB2FE8247A3CB900AA6A24 /* Array2D.swift */,
|
E1EB2FE8247A3CB900AA6A24 /* Array2D.swift */,
|
||||||
E1EB2FEA247A408800AA6A24 /* Block.swift */,
|
E1EB2FEA247A408800AA6A24 /* Block.swift */,
|
||||||
@ -277,8 +277,8 @@
|
|||||||
E1EB2FFE247B2FFA00AA6A24 /* Sprites.atlas in Resources */,
|
E1EB2FFE247B2FFA00AA6A24 /* Sprites.atlas in Resources */,
|
||||||
E1EB2FE6247A37EB00AA6A24 /* Sounds in Resources */,
|
E1EB2FE6247A37EB00AA6A24 /* Sounds in Resources */,
|
||||||
E1EB2FBD247A33F200AA6A24 /* Main.storyboard in Resources */,
|
E1EB2FBD247A33F200AA6A24 /* Main.storyboard in Resources */,
|
||||||
|
E1EB3001247E480300AA6A24 /* LaunchScreen.storyboard in Resources */,
|
||||||
E1EB2FBF247A340600AA6A24 /* Assets.xcassets in Resources */,
|
E1EB2FBF247A340600AA6A24 /* Assets.xcassets in Resources */,
|
||||||
E1EB2FC2247A340600AA6A24 /* LaunchScreen.storyboard in Resources */,
|
|
||||||
E1EB2FB6247A33F200AA6A24 /* Actions.sks in Resources */,
|
E1EB2FB6247A33F200AA6A24 /* Actions.sks in Resources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
@ -361,10 +361,10 @@
|
|||||||
name = Main.storyboard;
|
name = Main.storyboard;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
E1EB2FC0247A340600AA6A24 /* LaunchScreen.storyboard */ = {
|
E1EB2FFF247E480300AA6A24 /* LaunchScreen.storyboard */ = {
|
||||||
isa = PBXVariantGroup;
|
isa = PBXVariantGroup;
|
||||||
children = (
|
children = (
|
||||||
E1EB2FC1247A340600AA6A24 /* Base */,
|
E1EB3000247E480300AA6A24 /* Base */,
|
||||||
);
|
);
|
||||||
name = LaunchScreen.storyboard;
|
name = LaunchScreen.storyboard;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -11,34 +11,37 @@
|
|||||||
"size" : "20x20"
|
"size" : "20x20"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename" : "icon-29pt.png",
|
"filename" : "privyet-icon-58sq.png",
|
||||||
"idiom" : "iphone",
|
"idiom" : "iphone",
|
||||||
"scale" : "2x",
|
"scale" : "2x",
|
||||||
"size" : "29x29"
|
"size" : "29x29"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"filename" : "privyet-icon-87sq.png",
|
||||||
"idiom" : "iphone",
|
"idiom" : "iphone",
|
||||||
"scale" : "3x",
|
"scale" : "3x",
|
||||||
"size" : "29x29"
|
"size" : "29x29"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename" : "icon-40pt.png",
|
"filename" : "privyet-icon-80sq.png",
|
||||||
"idiom" : "iphone",
|
"idiom" : "iphone",
|
||||||
"scale" : "2x",
|
"scale" : "2x",
|
||||||
"size" : "40x40"
|
"size" : "40x40"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"filename" : "privyet-icon-120sq-1.png",
|
||||||
"idiom" : "iphone",
|
"idiom" : "iphone",
|
||||||
"scale" : "3x",
|
"scale" : "3x",
|
||||||
"size" : "40x40"
|
"size" : "40x40"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename" : "icon-60pt.png",
|
"filename" : "privyet-icon-120sq.png",
|
||||||
"idiom" : "iphone",
|
"idiom" : "iphone",
|
||||||
"scale" : "2x",
|
"scale" : "2x",
|
||||||
"size" : "60x60"
|
"size" : "60x60"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"filename" : "privyet-icon-180sq.png",
|
||||||
"idiom" : "iphone",
|
"idiom" : "iphone",
|
||||||
"scale" : "3x",
|
"scale" : "3x",
|
||||||
"size" : "60x60"
|
"size" : "60x60"
|
||||||
@ -89,6 +92,7 @@
|
|||||||
"size" : "83.5x83.5"
|
"size" : "83.5x83.5"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"filename" : "privyet-1024sq.png",
|
||||||
"idiom" : "ios-marketing",
|
"idiom" : "ios-marketing",
|
||||||
"scale" : "1x",
|
"scale" : "1x",
|
||||||
"size" : "1024x1024"
|
"size" : "1024x1024"
|
||||||
|
Before Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 5.0 KiB |
Before Width: | Height: | Size: 7.9 KiB |
BIN
Privyet/Assets.xcassets/AppIcon.appiconset/privyet-1024sq.png
Normal file
After Width: | Height: | Size: 178 KiB |
After Width: | Height: | Size: 7.6 KiB |
After Width: | Height: | Size: 7.6 KiB |
After Width: | Height: | Size: 14 KiB |
BIN
Privyet/Assets.xcassets/AppIcon.appiconset/privyet-icon-58sq.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
Privyet/Assets.xcassets/AppIcon.appiconset/privyet-icon-80sq.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
Privyet/Assets.xcassets/AppIcon.appiconset/privyet-icon-87sq.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
200
Privyet/Assets.xcassets/LaunchImage.launchimage/Contents.json
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"extent" : "full-screen",
|
||||||
|
"filename" : "privyet-launch-2688.png",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"minimum-system-version" : "12.0",
|
||||||
|
"orientation" : "portrait",
|
||||||
|
"scale" : "3x",
|
||||||
|
"subtype" : "2688h"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"extent" : "full-screen",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"minimum-system-version" : "12.0",
|
||||||
|
"orientation" : "landscape",
|
||||||
|
"scale" : "3x",
|
||||||
|
"subtype" : "2688h"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"extent" : "full-screen",
|
||||||
|
"filename" : "privyet-launch-1792.png",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"minimum-system-version" : "12.0",
|
||||||
|
"orientation" : "portrait",
|
||||||
|
"scale" : "2x",
|
||||||
|
"subtype" : "1792h"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"extent" : "full-screen",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"minimum-system-version" : "12.0",
|
||||||
|
"orientation" : "landscape",
|
||||||
|
"scale" : "2x",
|
||||||
|
"subtype" : "1792h"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"extent" : "full-screen",
|
||||||
|
"filename" : "privyet-launch-2436.png",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"minimum-system-version" : "11.0",
|
||||||
|
"orientation" : "portrait",
|
||||||
|
"scale" : "3x",
|
||||||
|
"subtype" : "2436h"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"extent" : "full-screen",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"minimum-system-version" : "11.0",
|
||||||
|
"orientation" : "landscape",
|
||||||
|
"scale" : "3x",
|
||||||
|
"subtype" : "2436h"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"extent" : "full-screen",
|
||||||
|
"filename" : "privyet-launch-2208.png",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"minimum-system-version" : "8.0",
|
||||||
|
"orientation" : "portrait",
|
||||||
|
"scale" : "3x",
|
||||||
|
"subtype" : "736h"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"extent" : "full-screen",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"minimum-system-version" : "8.0",
|
||||||
|
"orientation" : "landscape",
|
||||||
|
"scale" : "3x",
|
||||||
|
"subtype" : "736h"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"extent" : "full-screen",
|
||||||
|
"filename" : "privyet-launch-1334.png",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"minimum-system-version" : "8.0",
|
||||||
|
"orientation" : "portrait",
|
||||||
|
"scale" : "2x",
|
||||||
|
"subtype" : "667h"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"extent" : "full-screen",
|
||||||
|
"filename" : "privyet-launch-960.png",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"minimum-system-version" : "7.0",
|
||||||
|
"orientation" : "portrait",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"extent" : "full-screen",
|
||||||
|
"filename" : "privyet-launch-1136.png",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"minimum-system-version" : "7.0",
|
||||||
|
"orientation" : "portrait",
|
||||||
|
"scale" : "2x",
|
||||||
|
"subtype" : "retina4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"extent" : "full-screen",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"minimum-system-version" : "7.0",
|
||||||
|
"orientation" : "portrait",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"extent" : "full-screen",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"minimum-system-version" : "7.0",
|
||||||
|
"orientation" : "landscape",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"extent" : "full-screen",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"minimum-system-version" : "7.0",
|
||||||
|
"orientation" : "portrait",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"extent" : "full-screen",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"minimum-system-version" : "7.0",
|
||||||
|
"orientation" : "landscape",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"extent" : "full-screen",
|
||||||
|
"filename" : "privyet-launch-480.png",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"orientation" : "portrait",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"extent" : "full-screen",
|
||||||
|
"filename" : "privyet-launch-961.png",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"orientation" : "portrait",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"extent" : "full-screen",
|
||||||
|
"filename" : "privyet-launch-1137.png",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"orientation" : "portrait",
|
||||||
|
"scale" : "2x",
|
||||||
|
"subtype" : "retina4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"extent" : "to-status-bar",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"orientation" : "portrait",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"extent" : "full-screen",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"orientation" : "portrait",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"extent" : "to-status-bar",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"orientation" : "landscape",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"extent" : "full-screen",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"orientation" : "landscape",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"extent" : "to-status-bar",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"orientation" : "portrait",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"extent" : "full-screen",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"orientation" : "portrait",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"extent" : "to-status-bar",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"orientation" : "landscape",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"extent" : "full-screen",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"orientation" : "landscape",
|
||||||
|
"scale" : "2x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 199 KiB |
After Width: | Height: | Size: 199 KiB |
After Width: | Height: | Size: 262 KiB |
After Width: | Height: | Size: 304 KiB |
After Width: | Height: | Size: 492 KiB |
After Width: | Height: | Size: 510 KiB |
After Width: | Height: | Size: 497 KiB |
After Width: | Height: | Size: 71 KiB |
After Width: | Height: | Size: 198 KiB |
After Width: | Height: | Size: 198 KiB |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 7.7 KiB |
22
Privyet/Assets.xcassets/panelback.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "panelback.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "panelback@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Privyet/Assets.xcassets/panelback.imageset/panelback.png
vendored
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
Privyet/Assets.xcassets/panelback.imageset/panelback@2x.png
vendored
Normal file
After Width: | Height: | Size: 2.5 KiB |
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16097" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16097" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
<device id="retina6_5" orientation="portrait" appearance="light"/>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
|
||||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
@ -14,13 +14,14 @@
|
|||||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<color key="backgroundColor" systemColor="systemOrangeColor" red="1" green="0.58431372550000005" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="backgroundColor" red="0.0" green="0.16078431372549018" blue="0.83921568627450982" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<color key="tintColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
|
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
|
||||||
</view>
|
</view>
|
||||||
</viewController>
|
</viewController>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="53" y="375"/>
|
<point key="canvasLocation" x="52.173913043478265" y="375"/>
|
||||||
</scene>
|
</scene>
|
||||||
</scenes>
|
</scenes>
|
||||||
</document>
|
</document>
|
||||||
|
@ -11,58 +11,62 @@
|
|||||||
<scene sceneID="tXr-a1-R10">
|
<scene sceneID="tXr-a1-R10">
|
||||||
<objects>
|
<objects>
|
||||||
<viewController modalPresentationStyle="fullScreen" id="BV1-FR-VrT" customClass="GameViewController" customModule="Privyet" customModuleProvider="target" sceneMemberID="viewController">
|
<viewController modalPresentationStyle="fullScreen" id="BV1-FR-VrT" customClass="GameViewController" customModule="Privyet" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
<view key="view" multipleTouchEnabled="YES" contentMode="scaleToFill" id="3se-qz-xqx" customClass="SKView">
|
<view key="view" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="3se-qz-xqx" customClass="SKView">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="rZI-CB-OQn">
|
|
||||||
<rect key="frame" x="264" y="250" width="84" height="100"/>
|
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
|
|
||||||
<subviews>
|
|
||||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="whitebg" translatesAutoresizingMaskIntoConstraints="NO" id="2kD-q4-jVs">
|
|
||||||
<rect key="frame" x="0.0" y="0.0" width="84" height="100"/>
|
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
|
||||||
</imageView>
|
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="SCORE" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="InR-1s-zXh">
|
|
||||||
<rect key="frame" x="7" y="20" width="70" height="21"/>
|
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
|
||||||
<fontDescription key="fontDescription" name="Avenir-Black" family="Avenir" pointSize="15"/>
|
|
||||||
<color key="textColor" red="0.13333333333333333" green="0.6470588235294118" blue="0.82745098039215681" alpha="1" colorSpace="calibratedRGB"/>
|
|
||||||
<nil key="highlightedColor"/>
|
|
||||||
</label>
|
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="999" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Zja-c9-zBQ">
|
|
||||||
<rect key="frame" x="0.0" y="45" width="84" height="39"/>
|
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
|
||||||
<fontDescription key="fontDescription" name="Avenir-Light" family="Avenir" pointSize="35"/>
|
|
||||||
<color key="textColor" red="0.1333333333" green="0.64705882349999999" blue="0.82745098039999998" alpha="1" colorSpace="calibratedRGB"/>
|
|
||||||
<nil key="highlightedColor"/>
|
|
||||||
</label>
|
|
||||||
</subviews>
|
|
||||||
</view>
|
|
||||||
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="5g3-e3-yLO">
|
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="5g3-e3-yLO">
|
||||||
<rect key="frame" x="264" y="417" width="84" height="100"/>
|
<rect key="frame" x="310" y="538" width="84" height="100"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="whitebg" translatesAutoresizingMaskIntoConstraints="NO" id="nRI-Ry-bjG">
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="panelback" translatesAutoresizingMaskIntoConstraints="NO" id="nRI-Ry-bjG">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="84" height="100"/>
|
<rect key="frame" x="0.0" y="0.0" width="84" height="100"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
</imageView>
|
</imageView>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="LEVEL" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Boc-bL-hFe">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="LEVEL" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Boc-bL-hFe">
|
||||||
<rect key="frame" x="7" y="20" width="70" height="21"/>
|
<rect key="frame" x="7" y="51" width="70" height="21"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
|
||||||
<fontDescription key="fontDescription" name="Avenir-Black" family="Avenir" pointSize="15"/>
|
<fontDescription key="fontDescription" name="Avenir-Black" family="Avenir" pointSize="15"/>
|
||||||
<color key="textColor" red="0.54509803921568623" green="0.45490196078431372" blue="0.76078431372549016" alpha="1" colorSpace="calibratedRGB"/>
|
<color key="textColor" red="1" green="1" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="999" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="G8x-d8-J4Q">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="999" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="G8x-d8-J4Q">
|
||||||
<rect key="frame" x="0.0" y="45" width="84" height="39"/>
|
<rect key="frame" x="0.0" y="70" width="84" height="22"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
|
||||||
<fontDescription key="fontDescription" name="Avenir-Light" family="Avenir" pointSize="35"/>
|
<fontDescription key="fontDescription" name="Avenir-Black" family="Avenir" pointSize="20"/>
|
||||||
<color key="textColor" red="0.54509803921568623" green="0.45490196078431372" blue="0.76078431372549016" alpha="1" colorSpace="calibratedRGB"/>
|
<color key="textColor" red="1" green="1" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SCORE" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="InR-1s-zXh">
|
||||||
|
<rect key="frame" x="7" y="4" width="70" height="21"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
|
||||||
|
<fontDescription key="fontDescription" name="Avenir-Black" family="Avenir" pointSize="15"/>
|
||||||
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="999" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Zja-c9-zBQ">
|
||||||
|
<rect key="frame" x="0.0" y="14" width="84" height="39"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
|
||||||
|
<fontDescription key="fontDescription" name="Avenir-Black" family="Avenir" pointSize="20"/>
|
||||||
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
</subviews>
|
</subviews>
|
||||||
</view>
|
</view>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="NEXT" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="kjl-Kl-IpX">
|
||||||
|
<rect key="frame" x="257" y="44" width="40" height="21"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
|
<fontDescription key="fontDescription" name="Avenir-Heavy" family="Avenir" pointSize="15"/>
|
||||||
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="HOLD" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="BOe-2c-WvE">
|
||||||
|
<rect key="frame" x="257" y="204" width="50" height="21"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
|
<fontDescription key="fontDescription" name="Avenir-Heavy" family="Avenir" pointSize="15"/>
|
||||||
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
</subviews>
|
</subviews>
|
||||||
<color key="backgroundColor" systemColor="systemPurpleColor" red="0.68627450980000004" green="0.32156862749999998" blue="0.87058823529999996" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="backgroundColor" systemColor="systemPurpleColor" red="0.68627450980000004" green="0.32156862749999998" blue="0.87058823529999996" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<gestureRecognizers/>
|
<gestureRecognizers/>
|
||||||
@ -99,10 +103,10 @@
|
|||||||
</connections>
|
</connections>
|
||||||
</swipeGestureRecognizer>
|
</swipeGestureRecognizer>
|
||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="-64" y="86"/>
|
<point key="canvasLocation" x="-65.217391304347828" y="85.714285714285708"/>
|
||||||
</scene>
|
</scene>
|
||||||
</scenes>
|
</scenes>
|
||||||
<resources>
|
<resources>
|
||||||
<image name="whitebg" width="84" height="100"/>
|
<image name="panelback" width="84" height="100"/>
|
||||||
</resources>
|
</resources>
|
||||||
</document>
|
</document>
|
||||||
|
@ -9,10 +9,9 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import SpriteKit
|
import SpriteKit
|
||||||
|
|
||||||
let NumberOfColors: UInt32 = 6
|
// Colors that may be applied to blocks (whcih controls the sprite image they use)
|
||||||
|
enum BlockColor: Int, CustomStringConvertible, CaseIterable {
|
||||||
enum BlockColor: Int, CustomStringConvertible {
|
case Blue = 0, Orange, Purple, Red, Teal, Yellow, Green
|
||||||
case Blue = 0, Orange, Purple, Red, Teal, Yellow
|
|
||||||
|
|
||||||
var spriteName: String {
|
var spriteName: String {
|
||||||
switch self {
|
switch self {
|
||||||
@ -28,18 +27,56 @@ enum BlockColor: Int, CustomStringConvertible {
|
|||||||
return "teal"
|
return "teal"
|
||||||
case .Yellow:
|
case .Yellow:
|
||||||
return "yellow"
|
return "yellow"
|
||||||
|
case .Green:
|
||||||
|
return "green"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var description: String {
|
var description: String {
|
||||||
return self.spriteName
|
return self.spriteName
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rectangles in block coordinates
|
||||||
|
class BlockRect {
|
||||||
|
var left: Int
|
||||||
|
var top: Int
|
||||||
|
var right: Int
|
||||||
|
var bottom: Int
|
||||||
|
|
||||||
static func random() -> BlockColor {
|
init(left: Int, top: Int, right: Int, bottom: Int) {
|
||||||
return BlockColor(rawValue:Int(arc4random_uniform(NumberOfColors)))!
|
self.left = left
|
||||||
|
self.top = top
|
||||||
|
self.right = right
|
||||||
|
self.bottom = bottom
|
||||||
|
}
|
||||||
|
|
||||||
|
convenience init(x: Int, y: Int) {
|
||||||
|
self.init(left: x, top: y, right: x, bottom: y)
|
||||||
|
}
|
||||||
|
|
||||||
|
var width: Int {
|
||||||
|
return right - left + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
var height: Int {
|
||||||
|
return bottom - top + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
static func +(lhs: BlockRect, rhs: BlockRect) -> BlockRect {
|
||||||
|
return BlockRect(left: min(lhs.left, rhs.left), top: min(lhs.top, rhs.top), right: max(lhs.right, rhs.right), bottom: max(lhs.bottom, rhs.bottom))
|
||||||
|
}
|
||||||
|
|
||||||
|
static func +=(lhs: BlockRect, rhs: BlockRect) -> BlockRect {
|
||||||
|
lhs.left = min(lhs.left, rhs.left)
|
||||||
|
lhs.top = min(lhs.top, rhs.top)
|
||||||
|
lhs.right = max(lhs.right, rhs.right)
|
||||||
|
lhs.bottom = max(lhs.bottom, rhs.bottom)
|
||||||
|
return lhs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Represents a single block in the game.
|
||||||
class Block: Hashable, CustomStringConvertible {
|
class Block: Hashable, CustomStringConvertible {
|
||||||
// Constants
|
// Constants
|
||||||
let color: BlockColor
|
let color: BlockColor
|
||||||
|
@ -22,6 +22,9 @@ class GameScene: SKScene {
|
|||||||
var tickLengthMillis = TickLengthLevelOne
|
var tickLengthMillis = TickLengthLevelOne
|
||||||
var lastTick: NSDate?
|
var lastTick: NSDate?
|
||||||
|
|
||||||
|
var scaleFactor: CGFloat!
|
||||||
|
var holdControlRect: CGRect!
|
||||||
|
|
||||||
var textureCache = Dictionary<String, SKTexture>()
|
var textureCache = Dictionary<String, SKTexture>()
|
||||||
var textureAtlas: SKTextureAtlas?
|
var textureAtlas: SKTextureAtlas?
|
||||||
|
|
||||||
@ -31,38 +34,54 @@ class GameScene: SKScene {
|
|||||||
|
|
||||||
override init(size: CGSize) {
|
override init(size: CGSize) {
|
||||||
super.init(size: size)
|
super.init(size: size)
|
||||||
print("Init scene with size \(size)")
|
//print("Init scene with size \(size)")
|
||||||
|
|
||||||
textureAtlas = SKTextureAtlas(named: "Sprites")
|
textureAtlas = SKTextureAtlas(named: "Sprites")
|
||||||
|
|
||||||
anchorPoint = CGPoint(x: 0, y: 1.0)
|
anchorPoint = CGPoint(x: 0, y: 1.0)
|
||||||
|
|
||||||
let background = SKSpriteNode(imageNamed: "background")
|
let background = SKSpriteNode(imageNamed: "background")
|
||||||
print("Background is sized \(background.size)")
|
//print("Background is sized \(background.size)")
|
||||||
|
|
||||||
|
// Scale computations
|
||||||
|
scaleFactor = CGFloat(min(size.width / background.size.width, size.height / background.size.height))
|
||||||
|
|
||||||
background.position = CGPoint(x: 0, y: 0)
|
background.position = CGPoint(x: 0, y: 0)
|
||||||
background.anchorPoint = CGPoint(x: 0, y: 1.0)
|
background.anchorPoint = CGPoint(x: 0, y: 1.0)
|
||||||
|
background.setScale(scaleFactor)
|
||||||
addChild(background)
|
addChild(background)
|
||||||
|
|
||||||
|
gameLayer.setScale(scaleFactor)
|
||||||
addChild(gameLayer)
|
addChild(gameLayer)
|
||||||
|
|
||||||
|
// Load and add the game board and shape layer
|
||||||
let gameBoardTexture = SKTexture(imageNamed: "gameboard")
|
let gameBoardTexture = SKTexture(imageNamed: "gameboard")
|
||||||
let gameBoard = SKSpriteNode(texture: gameBoardTexture, size: CGSize(width: BlockSize * CGFloat(NumColumns), height: BlockSize * CGFloat(NumRows)))
|
let gameBoard = SKSpriteNode(texture: gameBoardTexture, size: CGSize(width: BlockSize * CGFloat(NumColumns), height: BlockSize * CGFloat(NumRows)))
|
||||||
gameBoard.anchorPoint = CGPoint(x: 0, y: 1.0)
|
gameBoard.anchorPoint = CGPoint(x: 0, y: 1.0)
|
||||||
gameBoard.position = LayerPosition
|
gameBoard.position = LayerPosition
|
||||||
|
|
||||||
shapeLayer.position = LayerPosition
|
shapeLayer.position = LayerPosition
|
||||||
shapeLayer.addChild(gameBoard)
|
shapeLayer.addChild(gameBoard)
|
||||||
gameLayer.addChild(shapeLayer)
|
gameLayer.addChild(shapeLayer)
|
||||||
|
|
||||||
|
// Calculate area of "hold" piece for hit-testing
|
||||||
|
let holdBBox = Shape.boundingBox(baseColumn: HoldColumn, baseRow: HoldRow)
|
||||||
|
let holdWidth = CGFloat(holdBBox.width) * BlockSize
|
||||||
|
let holdHeight = CGFloat(holdBBox.height) * BlockSize
|
||||||
|
let holdX = LayerPosition.x + (CGFloat(HoldColumn) * BlockSize)
|
||||||
|
let holdY = LayerPosition.y + (CGFloat(HoldRow) * BlockSize)
|
||||||
|
holdControlRect = CGRect(x: holdX * scaleFactor, y: holdY * scaleFactor, width: holdWidth * scaleFactor, height: holdHeight * scaleFactor)
|
||||||
|
|
||||||
|
// Set the theme music to play infinitely.
|
||||||
run(SKAction.repeatForever(SKAction.playSoundFileNamed("Sounds/theme.mp3", waitForCompletion: true)))
|
run(SKAction.repeatForever(SKAction.playSoundFileNamed("Sounds/theme.mp3", waitForCompletion: true)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convenience function to play a sound file.
|
||||||
func playSound(sound: String) {
|
func playSound(sound: String) {
|
||||||
run(SKAction.playSoundFileNamed(sound, waitForCompletion: false))
|
run(SKAction.playSoundFileNamed(sound, waitForCompletion: false))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Called once per frame by the framework, before it's rendered.
|
||||||
override func update(_ currentTime: TimeInterval) {
|
override func update(_ currentTime: TimeInterval) {
|
||||||
// Called before each frame is rendered
|
|
||||||
guard let lastTick = lastTick else {
|
guard let lastTick = lastTick else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -71,23 +90,32 @@ class GameScene: SKScene {
|
|||||||
self.lastTick = NSDate()
|
self.lastTick = NSDate()
|
||||||
tick?()
|
tick?()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Starts the game tick going.
|
||||||
func startTicking() {
|
func startTicking() {
|
||||||
lastTick = NSDate()
|
lastTick = NSDate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stops the game tick.
|
||||||
func stopTicking() {
|
func stopTicking() {
|
||||||
lastTick = nil
|
lastTick = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert bucket row/column coordinates to display coordinates.
|
||||||
func pointForColumn(column: Int, row: Int) -> CGPoint {
|
func pointForColumn(column: Int, row: Int) -> CGPoint {
|
||||||
let x = LayerPosition.x + (CGFloat(column) * BlockSize) + (BlockSize / 2)
|
let x = LayerPosition.x + (CGFloat(column) * BlockSize) + (BlockSize / 2)
|
||||||
let y = LayerPosition.y - ((CGFloat(row) * BlockSize) + (BlockSize / 2))
|
let y = LayerPosition.y - ((CGFloat(row) * BlockSize) + (BlockSize / 2))
|
||||||
return CGPoint(x: x, y: y)
|
return CGPoint(x: x, y: y)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func rectForBucket() -> CGRect {
|
||||||
|
let width = CGFloat(NumColumns) * BlockSize
|
||||||
|
let height = CGFloat(NumRows) * BlockSize
|
||||||
|
return CGRect(x: (LayerPosition.x + (BlockSize / 2)) * scaleFactor, y: (LayerPosition.y + (BlockSize / 2)) * scaleFactor, width: width * scaleFactor, height: height * scaleFactor)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the "preview" shape to the current scene. This also "rezzes in" a shape's block sprites.
|
||||||
func addPreviewShapeToScene(shape: Shape, completion: @escaping () -> ()) {
|
func addPreviewShapeToScene(shape: Shape, completion: @escaping () -> ()) {
|
||||||
for block in shape.blocks {
|
for block in shape.blocks {
|
||||||
var texture = textureCache[block.spriteName]
|
var texture = textureCache[block.spriteName]
|
||||||
@ -115,6 +143,7 @@ class GameScene: SKScene {
|
|||||||
run(SKAction.sequence([waitAction, completeAction]))
|
run(SKAction.sequence([waitAction, completeAction]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Move the "preview" shape into position so it becomes the new "falling" shape.
|
||||||
func movePreviewShape(shape: Shape, completion: @escaping () -> ()) {
|
func movePreviewShape(shape: Shape, completion: @escaping () -> ()) {
|
||||||
for block in shape.blocks {
|
for block in shape.blocks {
|
||||||
let sprite = block.sprite!
|
let sprite = block.sprite!
|
||||||
@ -128,6 +157,7 @@ class GameScene: SKScene {
|
|||||||
run(SKAction.sequence([waitAction, completeAction]))
|
run(SKAction.sequence([waitAction, completeAction]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Redraw the falling shape by shifting the position of its component blocks.
|
||||||
func redrawShape(shape: Shape, completion: @escaping () -> ()) {
|
func redrawShape(shape: Shape, completion: @escaping () -> ()) {
|
||||||
for block in shape.blocks {
|
for block in shape.blocks {
|
||||||
let sprite = block.sprite!
|
let sprite = block.sprite!
|
||||||
@ -141,9 +171,21 @@ class GameScene: SKScene {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func animateCollapsingLines(linesToRemove: Array<Array<Block>>, fallenBlocks: Array<Array<Block>>, completion: @escaping () -> ()) {
|
// Animate the removed lines "exploding" and the remaining blocks dropping into place.
|
||||||
|
func animateCollapsingLines(linesToRemove: Array<Array<Block>>, fallenBlocks: Array<Array<Block>>, fadingShapes: Array<Shape>, completion: @escaping () -> ()) {
|
||||||
var longestDuration: TimeInterval = 0
|
var longestDuration: TimeInterval = 0
|
||||||
|
|
||||||
|
// Animate the shapes fading out
|
||||||
|
for fadeShape in fadingShapes {
|
||||||
|
for block in fadeShape.blocks {
|
||||||
|
let sprite = block.sprite!
|
||||||
|
let fadeOutAction: SKAction = SKAction.fadeOut(withDuration: TimeInterval(0.1))
|
||||||
|
fadeOutAction.timingMode = .easeIn
|
||||||
|
sprite.run(SKAction.sequence([fadeOutAction, SKAction.removeFromParent()]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Animate the falling blocks dropping into place
|
||||||
for (columnIdx, column) in fallenBlocks.enumerated() {
|
for (columnIdx, column) in fallenBlocks.enumerated() {
|
||||||
for (blockIdx, block) in column.enumerated() {
|
for (blockIdx, block) in column.enumerated() {
|
||||||
let newPosition = pointForColumn(column: block.column, row: block.row)
|
let newPosition = pointForColumn(column: block.column, row: block.row)
|
||||||
@ -158,15 +200,16 @@ class GameScene: SKScene {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Animate the "removed" blocks "exploding"
|
||||||
for rowToRemove in linesToRemove {
|
for rowToRemove in linesToRemove {
|
||||||
for block in rowToRemove {
|
for block in rowToRemove {
|
||||||
let randomRadius = CGFloat(UInt(arc4random_uniform(400) + 100))
|
let randomRadius = CGFloat(Float.random(in: 100.0 ... 500.0))
|
||||||
let goLeft = arc4random_uniform(100) % 2 == 0
|
let goLeft = Bool.random()
|
||||||
|
|
||||||
var point = pointForColumn(column: block.column, row: block.row)
|
var point = pointForColumn(column: block.column, row: block.row)
|
||||||
point = CGPoint(x: point.x + (goLeft ? -randomRadius : randomRadius), y: point.y)
|
point = CGPoint(x: point.x + (goLeft ? -randomRadius : randomRadius), y: point.y)
|
||||||
|
|
||||||
let randomDuration = TimeInterval(arc4random_uniform(2)) + 0.5
|
let randomDuration = TimeInterval(Float.random(in: 0.5 ... 2.5))
|
||||||
|
|
||||||
var startAngle = CGFloat(Double.pi)
|
var startAngle = CGFloat(Double.pi)
|
||||||
var endAngle = startAngle * 2
|
var endAngle = startAngle * 2
|
||||||
@ -188,4 +231,28 @@ class GameScene: SKScene {
|
|||||||
}
|
}
|
||||||
run(SKAction.sequence([SKAction.wait(forDuration: longestDuration), SKAction.run(completion)]))
|
run(SKAction.sequence([SKAction.wait(forDuration: longestDuration), SKAction.run(completion)]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Animate the multiple-line clearance banner.
|
||||||
|
func animateClearanceBanner(bannerName: String?) {
|
||||||
|
guard let name = bannerName else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var texture = textureCache[name]
|
||||||
|
if texture == nil {
|
||||||
|
texture = textureAtlas?.textureNamed(name)
|
||||||
|
textureCache[name] = texture
|
||||||
|
}
|
||||||
|
let sprite = SKSpriteNode(texture: texture)
|
||||||
|
|
||||||
|
sprite.position = CGPoint(x: LayerPosition.x + (CGFloat(NumColumns / 2) * BlockSize), y: LayerPosition.y - (CGFloat(NumRows / 2) * BlockSize))
|
||||||
|
sprite.zPosition = 20
|
||||||
|
sprite.alpha = 0
|
||||||
|
shapeLayer.addChild(sprite)
|
||||||
|
|
||||||
|
// Animate a quick fade-in followed by a slow fade out
|
||||||
|
let fadeInAction: SKAction = SKAction.fadeAlpha(to: 0.9, duration: 0.05)
|
||||||
|
let fadeOutAction: SKAction = SKAction.fadeOut(withDuration: 1.5)
|
||||||
|
fadeOutAction.timingMode = .easeIn
|
||||||
|
sprite.run(SKAction.sequence([fadeInAction, fadeOutAction, SKAction.removeFromParent()]))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,9 +10,12 @@ import UIKit
|
|||||||
import SpriteKit
|
import SpriteKit
|
||||||
import GameplayKit
|
import GameplayKit
|
||||||
|
|
||||||
|
let ClearanceAnimation = [nil, "double", "triple", "privyet"]
|
||||||
|
|
||||||
class GameViewController: UIViewController, PrivyetDelegate, UIGestureRecognizerDelegate {
|
class GameViewController: UIViewController, PrivyetDelegate, UIGestureRecognizerDelegate {
|
||||||
var scene: GameScene!
|
var scene: GameScene!
|
||||||
var privyet: Privyet!
|
var privyet: Privyet!
|
||||||
|
var bucketRect: CGRect!
|
||||||
var panPointReference: CGPoint?
|
var panPointReference: CGPoint?
|
||||||
|
|
||||||
@IBOutlet weak var scoreLabel: UILabel!
|
@IBOutlet weak var scoreLabel: UILabel!
|
||||||
@ -29,6 +32,10 @@ class GameViewController: UIViewController, PrivyetDelegate, UIGestureRecognizer
|
|||||||
scene = GameScene(size: skView.bounds.size)
|
scene = GameScene(size: skView.bounds.size)
|
||||||
scene.scaleMode = .aspectFill
|
scene.scaleMode = .aspectFill
|
||||||
|
|
||||||
|
bucketRect = scene.rectForBucket()
|
||||||
|
//print("Computed bucket rectangle = \(bucketRect!)")
|
||||||
|
//print("Computed hold rectangle = \(scene.holdControlRect!)")
|
||||||
|
|
||||||
scene.tick = didTick
|
scene.tick = didTick
|
||||||
|
|
||||||
privyet = Privyet()
|
privyet = Privyet()
|
||||||
@ -43,10 +50,17 @@ class GameViewController: UIViewController, PrivyetDelegate, UIGestureRecognizer
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Called when the user taps the screen
|
||||||
@IBAction func didTap(_ sender: UITapGestureRecognizer) {
|
@IBAction func didTap(_ sender: UITapGestureRecognizer) {
|
||||||
privyet.rotateShape()
|
let tapLoc = sender.location(in: view)
|
||||||
|
if bucketRect.contains(tapLoc) {
|
||||||
|
privyet.rotateShape()
|
||||||
|
} else if scene.holdControlRect.contains(tapLoc) {
|
||||||
|
privyet.holdShape()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Called when the user slides their finger across the screen
|
||||||
@IBAction func didPan(_ sender: UIPanGestureRecognizer) {
|
@IBAction func didPan(_ sender: UIPanGestureRecognizer) {
|
||||||
let currentPoint = sender.translation(in: self.view)
|
let currentPoint = sender.translation(in: self.view)
|
||||||
if let originalPoint = panPointReference {
|
if let originalPoint = panPointReference {
|
||||||
@ -64,14 +78,17 @@ class GameViewController: UIViewController, PrivyetDelegate, UIGestureRecognizer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Called when the user swipes the screen in a downward direction
|
||||||
@IBAction func didSwipe(_ sender: UISwipeGestureRecognizer) {
|
@IBAction func didSwipe(_ sender: UISwipeGestureRecognizer) {
|
||||||
privyet.dropShape()
|
privyet.dropShape()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Used to allow multiple gesture recognizers to be used simultaneously
|
||||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Used to prioritize one gesture recognizer over another
|
||||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||||
if gestureRecognizer is UISwipeGestureRecognizer {
|
if gestureRecognizer is UISwipeGestureRecognizer {
|
||||||
if otherGestureRecognizer is UIPanGestureRecognizer {
|
if otherGestureRecognizer is UIPanGestureRecognizer {
|
||||||
@ -85,10 +102,12 @@ class GameViewController: UIViewController, PrivyetDelegate, UIGestureRecognizer
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Called once per game tick (interval over which blocks drop)
|
||||||
func didTick() {
|
func didTick() {
|
||||||
privyet.letShapeFall()
|
privyet.letShapeFall()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Advances the "next' and "falling" shapes and places them on the screen
|
||||||
func nextShape() {
|
func nextShape() {
|
||||||
let newShapes = privyet.newShape()
|
let newShapes = privyet.newShape()
|
||||||
guard let fallingShape = newShapes.fallingShape else {
|
guard let fallingShape = newShapes.fallingShape else {
|
||||||
@ -101,6 +120,7 @@ class GameViewController: UIViewController, PrivyetDelegate, UIGestureRecognizer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Called when the game starts
|
||||||
func gameDidBegin(privyet: Privyet) {
|
func gameDidBegin(privyet: Privyet) {
|
||||||
levelLabel.text = "\(privyet.level)"
|
levelLabel.text = "\(privyet.level)"
|
||||||
scoreLabel.text = "\(privyet.score)"
|
scoreLabel.text = "\(privyet.score)"
|
||||||
@ -116,15 +136,17 @@ class GameViewController: UIViewController, PrivyetDelegate, UIGestureRecognizer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Called when the game ends
|
||||||
func gameDidEnd(privyet: Privyet) {
|
func gameDidEnd(privyet: Privyet) {
|
||||||
view.isUserInteractionEnabled = false
|
view.isUserInteractionEnabled = false
|
||||||
scene.stopTicking()
|
scene.stopTicking()
|
||||||
scene.playSound(sound: "Sounds/gameover.mp3")
|
scene.playSound(sound: "Sounds/gameover.mp3")
|
||||||
scene.animateCollapsingLines(linesToRemove: privyet.removeAllBlocks(), fallenBlocks: privyet.removeAllBlocks()) {
|
scene.animateCollapsingLines(linesToRemove: privyet.removeAllBlocks(), fallenBlocks: privyet.removeAllBlocks(), fadingShapes: privyet.removeAllShapes()) {
|
||||||
privyet.beginGame()
|
privyet.beginGame()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Called when the game advances in level (gets faster)
|
||||||
func gameDidLevelUp(privyet: Privyet) {
|
func gameDidLevelUp(privyet: Privyet) {
|
||||||
levelLabel.text = "\(privyet.level)"
|
levelLabel.text = "\(privyet.level)"
|
||||||
if scene.tickLengthMillis >= 100 {
|
if scene.tickLengthMillis >= 100 {
|
||||||
@ -135,6 +157,7 @@ class GameViewController: UIViewController, PrivyetDelegate, UIGestureRecognizer
|
|||||||
scene.playSound(sound: "Sounds/levelup.mp3")
|
scene.playSound(sound: "Sounds/levelup.mp3")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Called when a shape is "dropped"
|
||||||
func gameShapeDidDrop(privyet: Privyet) {
|
func gameShapeDidDrop(privyet: Privyet) {
|
||||||
scene.stopTicking()
|
scene.stopTicking()
|
||||||
scene.redrawShape(shape: privyet.fallingShape!) {
|
scene.redrawShape(shape: privyet.fallingShape!) {
|
||||||
@ -143,21 +166,55 @@ class GameViewController: UIViewController, PrivyetDelegate, UIGestureRecognizer
|
|||||||
scene.playSound(sound: "Sounds/drop.mp3")
|
scene.playSound(sound: "Sounds/drop.mp3")
|
||||||
}
|
}
|
||||||
|
|
||||||
func gameShapeDidLand(privyet: Privyet) {
|
// Called when the current shape is put "on hold"
|
||||||
|
func gameShapePutOnHold(privyet: Privyet, firstHold: Bool) {
|
||||||
|
guard let held = privyet.heldShape else {
|
||||||
|
return
|
||||||
|
}
|
||||||
scene.stopTicking()
|
scene.stopTicking()
|
||||||
self.view.isUserInteractionEnabled = false
|
view.isUserInteractionEnabled = false
|
||||||
|
scene.redrawShape(shape: held) {
|
||||||
|
if (firstHold) {
|
||||||
|
self.nextShape() // act like the previous shape settled
|
||||||
|
} else {
|
||||||
|
self.scene.redrawShape(shape: privyet.fallingShape!) {
|
||||||
|
self.scene.startTicking()
|
||||||
|
self.view.isUserInteractionEnabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scene.playSound(sound: "Sounds/zap.mp3")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal: called when a shape lands, used to keep from showing the "special" banner more than once
|
||||||
|
func internalDidLand(privyet: Privyet, showBanner: Bool) {
|
||||||
let removedLines = privyet.removeCompletedLines()
|
let removedLines = privyet.removeCompletedLines()
|
||||||
if removedLines.linesRemoved.count > 0 {
|
let linesCount = removedLines.linesRemoved.count
|
||||||
|
if linesCount > 0 {
|
||||||
self.scoreLabel.text = "\(privyet.score)"
|
self.scoreLabel.text = "\(privyet.score)"
|
||||||
scene.animateCollapsingLines(linesToRemove: removedLines.linesRemoved, fallenBlocks: removedLines.fallenBlocks) {
|
if (showBanner) {
|
||||||
self.gameShapeDidLand(privyet: privyet)
|
scene.animateClearanceBanner(bannerName: ClearanceAnimation[linesCount - 1])
|
||||||
|
}
|
||||||
|
scene.animateCollapsingLines(linesToRemove: removedLines.linesRemoved, fallenBlocks: removedLines.fallenBlocks, fadingShapes: []) {
|
||||||
|
self.internalDidLand(privyet: privyet, showBanner: (linesCount <= 1) && showBanner)
|
||||||
}
|
}
|
||||||
self.scene.playSound(sound: "Sounds/bomb.mp3")
|
self.scene.playSound(sound: "Sounds/bomb.mp3")
|
||||||
|
if showBanner && linesCount == 4 {
|
||||||
|
self.scene.playSound(sound: "Sounds/privyet.mp3")
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
nextShape()
|
nextShape()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Called when a shape "lands" on top of existing blocks or at the bottom of the bucket
|
||||||
|
func gameShapeDidLand(privyet: Privyet) {
|
||||||
|
scene.stopTicking()
|
||||||
|
self.view.isUserInteractionEnabled = false
|
||||||
|
internalDidLand(privyet: privyet, showBanner: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called when a shape moves on the screen
|
||||||
func gameShapeDidMove(privyet: Privyet) {
|
func gameShapeDidMove(privyet: Privyet) {
|
||||||
scene.redrawShape(shape: privyet.fallingShape!) { }
|
scene.redrawShape(shape: privyet.fallingShape!) { }
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,10 @@ class JShape: Shape {
|
|||||||
*/
|
*/
|
||||||
// Rotates around block #1
|
// Rotates around block #1
|
||||||
|
|
||||||
|
override var color: BlockColor? {
|
||||||
|
return .Blue
|
||||||
|
}
|
||||||
|
|
||||||
override var blockRowColumnPositions: [Orientation : Array<(columnDiff: Int, rowDiff: Int)>] {
|
override var blockRowColumnPositions: [Orientation : Array<(columnDiff: Int, rowDiff: Int)>] {
|
||||||
return [
|
return [
|
||||||
Orientation.Zero: [(1, 0), (1, 1), (1, 2), (0, 2)],
|
Orientation.Zero: [(1, 0), (1, 1), (1, 2), (0, 2)],
|
||||||
|
@ -32,6 +32,10 @@ class LShape: Shape {
|
|||||||
*/
|
*/
|
||||||
// Pivots about block #1
|
// Pivots about block #1
|
||||||
|
|
||||||
|
override var color: BlockColor? {
|
||||||
|
return .Green
|
||||||
|
}
|
||||||
|
|
||||||
override var blockRowColumnPositions: [Orientation : Array<(columnDiff: Int, rowDiff: Int)>] {
|
override var blockRowColumnPositions: [Orientation : Array<(columnDiff: Int, rowDiff: Int)>] {
|
||||||
return [
|
return [
|
||||||
Orientation.Zero: [(0, 0), (0, 1), (0, 2), (1, 2)],
|
Orientation.Zero: [(0, 0), (0, 1), (0, 2), (1, 2)],
|
||||||
|
@ -22,6 +22,10 @@ class LineShape: Shape {
|
|||||||
*/
|
*/
|
||||||
// Hinges about the second block
|
// Hinges about the second block
|
||||||
|
|
||||||
|
override var color: BlockColor? {
|
||||||
|
return .Teal
|
||||||
|
}
|
||||||
|
|
||||||
override var blockRowColumnPositions: [Orientation : Array<(columnDiff: Int, rowDiff: Int)>] {
|
override var blockRowColumnPositions: [Orientation : Array<(columnDiff: Int, rowDiff: Int)>] {
|
||||||
return [
|
return [
|
||||||
Orientation.Zero: [(0, 0), (0, 1), (0, 2), (0, 3)],
|
Orientation.Zero: [(0, 0), (0, 1), (0, 2), (0, 3)],
|
||||||
|
@ -15,10 +15,14 @@ let StartingColumn = 4
|
|||||||
let StartingRow = 0
|
let StartingRow = 0
|
||||||
|
|
||||||
let PreviewColumn = 12
|
let PreviewColumn = 12
|
||||||
let PreviewRow = 1
|
let PreviewRow = 3
|
||||||
|
|
||||||
let PointsPerLine = 10
|
let HoldColumn = 12
|
||||||
let LevelThreshold = 500
|
let HoldRow = 10
|
||||||
|
|
||||||
|
// number of points for single (1 line), double (2 lines), triple (3 lines), Privyet (4 lines)
|
||||||
|
let PointsPerLine = [1, 2, 5, 10]
|
||||||
|
let LevelThreshold = 50
|
||||||
|
|
||||||
protocol PrivyetDelegate {
|
protocol PrivyetDelegate {
|
||||||
// Invoked when the current round of Privyet ends
|
// Invoked when the current round of Privyet ends
|
||||||
@ -36,14 +40,18 @@ protocol PrivyetDelegate {
|
|||||||
// invoked when the falling shape has changed its location after being dropped
|
// invoked when the falling shape has changed its location after being dropped
|
||||||
func gameShapeDidDrop(privyet: Privyet)
|
func gameShapeDidDrop(privyet: Privyet)
|
||||||
|
|
||||||
|
// invoked when the current shape is put on hold
|
||||||
|
func gameShapePutOnHold(privyet: Privyet, firstHold: Bool)
|
||||||
|
|
||||||
// invoked when the game has reached a new level
|
// invoked when the game has reached a new level
|
||||||
func gameDidLevelUp(privyet: Privyet)
|
func gameDidLevelUp(privyet: Privyet)
|
||||||
}
|
}
|
||||||
|
|
||||||
class Privyet {
|
class Privyet {
|
||||||
var blockArray: Array2D<Block>
|
var bucket: Array2D<Block>
|
||||||
var nextShape: Shape?
|
var nextShape: Shape?
|
||||||
var fallingShape: Shape?
|
var fallingShape: Shape?
|
||||||
|
var heldShape: Shape?
|
||||||
var delegate: PrivyetDelegate?
|
var delegate: PrivyetDelegate?
|
||||||
|
|
||||||
var score = 0
|
var score = 0
|
||||||
@ -52,9 +60,11 @@ class Privyet {
|
|||||||
init() {
|
init() {
|
||||||
fallingShape = nil
|
fallingShape = nil
|
||||||
nextShape = nil
|
nextShape = nil
|
||||||
blockArray = Array2D<Block>(columns: NumColumns, rows: NumRows)
|
heldShape = nil
|
||||||
|
bucket = Array2D<Block>(columns: NumColumns, rows: NumRows)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Starts the game by initializing game data
|
||||||
func beginGame() {
|
func beginGame() {
|
||||||
if (nextShape == nil) {
|
if (nextShape == nil) {
|
||||||
nextShape = Shape.random(startingColumn: PreviewColumn, startingRow: PreviewRow)
|
nextShape = Shape.random(startingColumn: PreviewColumn, startingRow: PreviewRow)
|
||||||
@ -62,6 +72,7 @@ class Privyet {
|
|||||||
delegate?.gameDidBegin(privyet: self)
|
delegate?.gameDidBegin(privyet: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Advances the current falling shape and the "next" shape. Returns both these shapes as a tuple.
|
||||||
func newShape() -> (fallingShape: Shape?, nextShape: Shape?) {
|
func newShape() -> (fallingShape: Shape?, nextShape: Shape?) {
|
||||||
fallingShape = nextShape
|
fallingShape = nextShape
|
||||||
nextShape = Shape.random(startingColumn: PreviewColumn, startingRow: PreviewRow)
|
nextShape = Shape.random(startingColumn: PreviewColumn, startingRow: PreviewRow)
|
||||||
@ -77,6 +88,8 @@ class Privyet {
|
|||||||
return (fallingShape, nextShape)
|
return (fallingShape, nextShape)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns true if the falling shape is in an "illegal" placement (outside bucket bounds or "colliding"
|
||||||
|
// with existing blocks in the bucket).
|
||||||
func detectIllegalPlacement() -> Bool {
|
func detectIllegalPlacement() -> Bool {
|
||||||
guard let shape = fallingShape else {
|
guard let shape = fallingShape else {
|
||||||
return false
|
return false
|
||||||
@ -84,82 +97,93 @@ class Privyet {
|
|||||||
for block in shape.blocks {
|
for block in shape.blocks {
|
||||||
if block.column < 0 || block.column >= NumColumns || block.row < 0 || block.row >= NumRows {
|
if block.column < 0 || block.column >= NumColumns || block.row < 0 || block.row >= NumRows {
|
||||||
return true
|
return true
|
||||||
} else if blockArray[block.column, block.row] != nil {
|
} else if bucket[block.column, block.row] != nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// "Settle" the falling shape by transferring its blocks to the bucket.
|
||||||
func settleShape() {
|
func settleShape() {
|
||||||
guard let shape = fallingShape else {
|
guard let shape = fallingShape else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for block in shape.blocks {
|
for block in shape.blocks {
|
||||||
blockArray[block.column, block.row] = block
|
bucket[block.column, block.row] = block
|
||||||
}
|
}
|
||||||
fallingShape = nil
|
fallingShape = nil
|
||||||
delegate?.gameShapeDidLand(privyet: self)
|
delegate?.gameShapeDidLand(privyet: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns true if the falling block is "landing" (at the bottom of the bucket or directly above existing blocks)
|
||||||
func detectTouch() -> Bool {
|
func detectTouch() -> Bool {
|
||||||
guard let shape = fallingShape else {
|
guard let shape = fallingShape else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for bottomBlock in shape.bottomBlocks {
|
for bottomBlock in shape.bottomBlocks {
|
||||||
if bottomBlock.row == NumRows - 1 || blockArray[bottomBlock.column, bottomBlock.row + 1] != nil {
|
if bottomBlock.row == NumRows - 1 || bucket[bottomBlock.column, bottomBlock.row + 1] != nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ends the current game
|
||||||
func endGame() {
|
func endGame() {
|
||||||
score = 0
|
score = 0
|
||||||
level = 1
|
level = 1
|
||||||
delegate?.gameDidEnd(privyet: self)
|
delegate?.gameDidEnd(privyet: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Detect and remove completed lines. Returns a tuple of two arrays of arrays:
|
||||||
|
// linesRemoved - The blocks that will be removed as a result of completed lines.
|
||||||
|
// fallenBlocks - The blocks that will drop to a lower line in the bucket when the above blocks are removed.
|
||||||
|
// If no lines are to be removed, returns a pair of empty lists.
|
||||||
func removeCompletedLines() -> (linesRemoved: Array<Array<Block>>, fallenBlocks: Array<Array<Block>>) {
|
func removeCompletedLines() -> (linesRemoved: Array<Array<Block>>, fallenBlocks: Array<Array<Block>>) {
|
||||||
var removedLines = Array<Array<Block>>()
|
var removedLines = Array<Array<Block>>()
|
||||||
for row in (1..<NumRows).reversed() {
|
for row in (1..<NumRows).reversed() {
|
||||||
var rowOfBlocks = Array<Block>()
|
var rowOfBlocks = Array<Block>()
|
||||||
for column in 0..<NumColumns {
|
for column in 0..<NumColumns {
|
||||||
guard let block = blockArray[column, row] else {
|
guard let block = bucket[column, row] else {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
rowOfBlocks.append(block)
|
rowOfBlocks.append(block)
|
||||||
}
|
}
|
||||||
if rowOfBlocks.count == NumColumns {
|
if rowOfBlocks.count == NumColumns {
|
||||||
|
// filled line detected, remove it
|
||||||
removedLines.append(rowOfBlocks)
|
removedLines.append(rowOfBlocks)
|
||||||
for block in rowOfBlocks {
|
for block in rowOfBlocks {
|
||||||
blockArray[block.column, block.row] = nil
|
bucket[block.column, block.row] = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if removedLines.count == 0 {
|
if removedLines.count == 0 {
|
||||||
return ([], [])
|
return ([], []) // no lines filled
|
||||||
}
|
}
|
||||||
let pointsEarned = removedLines.count * PointsPerLine * level
|
|
||||||
|
// Advance score and game level as appropriate
|
||||||
|
let pointsEarned = PointsPerLine[removedLines.count - 1] * max(level / 2, 1)
|
||||||
score += pointsEarned
|
score += pointsEarned
|
||||||
if score >= level * LevelThreshold {
|
if score >= level * LevelThreshold {
|
||||||
level += 1
|
level += 1
|
||||||
delegate?.gameDidLevelUp(privyet: self)
|
delegate?.gameDidLevelUp(privyet: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
var fallenBlocks = Array<Array<Block>>()
|
var fallenBlocks = Array<Array<Block>>()
|
||||||
for column in 0..<NumColumns {
|
for column in 0..<NumColumns {
|
||||||
var fallenBlocksArray = Array<Block>()
|
var fallenBlocksArray = Array<Block>()
|
||||||
for row in (1..<removedLines[0][0].row).reversed() {
|
for row in (1..<removedLines[0][0].row).reversed() {
|
||||||
guard let block = blockArray[column, row] else {
|
guard let block = bucket[column, row] else {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var newRow = row
|
var newRow = row
|
||||||
while (newRow < NumRows - 1 && blockArray[column, newRow + 1] == nil) {
|
while (newRow < NumRows - 1 && bucket[column, newRow + 1] == nil) {
|
||||||
newRow += 1
|
newRow += 1
|
||||||
}
|
}
|
||||||
block.row = newRow
|
block.row = newRow
|
||||||
blockArray[column, row] = nil
|
bucket[column, row] = nil
|
||||||
blockArray[column, newRow] = block
|
bucket[column, newRow] = block
|
||||||
fallenBlocksArray.append(block)
|
fallenBlocksArray.append(block)
|
||||||
}
|
}
|
||||||
if fallenBlocksArray.count > 0 {
|
if fallenBlocksArray.count > 0 {
|
||||||
@ -169,22 +193,39 @@ class Privyet {
|
|||||||
return (removedLines, fallenBlocks)
|
return (removedLines, fallenBlocks)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Removes all blocks from the bucket (at end of game). Returns all the removed blocks.
|
||||||
func removeAllBlocks() -> Array<Array<Block>> {
|
func removeAllBlocks() -> Array<Array<Block>> {
|
||||||
var allBlocks = Array<Array<Block>>()
|
var allBlocks = Array<Array<Block>>()
|
||||||
for row in 0..<NumRows {
|
for row in 0..<NumRows {
|
||||||
var rowOfBlocks = Array<Block>()
|
var rowOfBlocks = Array<Block>()
|
||||||
for column in 0..<NumColumns {
|
for column in 0..<NumColumns {
|
||||||
guard let block = blockArray[column, row] else {
|
guard let block = bucket[column, row] else {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
rowOfBlocks.append(block)
|
rowOfBlocks.append(block)
|
||||||
blockArray[column, row] = nil
|
bucket[column, row] = nil
|
||||||
}
|
}
|
||||||
allBlocks.append(rowOfBlocks)
|
allBlocks.append(rowOfBlocks)
|
||||||
}
|
}
|
||||||
return allBlocks
|
return allBlocks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Removes all stored shapes from their slots (at end of game). Returns all the removed shapes.
|
||||||
|
func removeAllShapes() -> Array<Shape> {
|
||||||
|
var allShapes = Array<Shape>()
|
||||||
|
for shape in [fallingShape, nextShape, heldShape] {
|
||||||
|
guard let s = shape else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
allShapes.append(s)
|
||||||
|
}
|
||||||
|
fallingShape = nil
|
||||||
|
nextShape = nil
|
||||||
|
heldShape = nil
|
||||||
|
return allShapes
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drop the falling shape down as far as it will go.
|
||||||
func dropShape() {
|
func dropShape() {
|
||||||
guard let shape = fallingShape else {
|
guard let shape = fallingShape else {
|
||||||
return
|
return
|
||||||
@ -196,6 +237,7 @@ class Privyet {
|
|||||||
delegate?.gameShapeDidDrop(privyet: self)
|
delegate?.gameShapeDidDrop(privyet: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Let the falling shape fall by one row.
|
||||||
func letShapeFall() {
|
func letShapeFall() {
|
||||||
guard let shape = fallingShape else {
|
guard let shape = fallingShape else {
|
||||||
return
|
return
|
||||||
@ -204,18 +246,21 @@ class Privyet {
|
|||||||
if detectIllegalPlacement() {
|
if detectIllegalPlacement() {
|
||||||
shape.raiseShapeByOneRow()
|
shape.raiseShapeByOneRow()
|
||||||
if detectIllegalPlacement() {
|
if detectIllegalPlacement() {
|
||||||
endGame()
|
endGame() // shape was in illegal place to start, this ends the game
|
||||||
} else {
|
} else {
|
||||||
settleShape()
|
settleShape() // shape can land
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
delegate?.gameShapeDidMove(privyet: self)
|
delegate?.gameShapeDidMove(privyet: self)
|
||||||
|
/* AGRB 5/29/2020 - removed to allow "finessing" moves
|
||||||
if detectTouch() {
|
if detectTouch() {
|
||||||
settleShape()
|
settleShape()
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rotate the falling shape clockwise by 90 degrees.
|
||||||
func rotateShape() {
|
func rotateShape() {
|
||||||
guard let shape = fallingShape else {
|
guard let shape = fallingShape else {
|
||||||
return
|
return
|
||||||
@ -228,6 +273,7 @@ class Privyet {
|
|||||||
delegate?.gameShapeDidMove(privyet: self)
|
delegate?.gameShapeDidMove(privyet: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Move the falling shape left by one column.
|
||||||
func moveShapeLeft() {
|
func moveShapeLeft() {
|
||||||
guard let shape = fallingShape else {
|
guard let shape = fallingShape else {
|
||||||
return
|
return
|
||||||
@ -240,6 +286,7 @@ class Privyet {
|
|||||||
delegate?.gameShapeDidMove(privyet: self)
|
delegate?.gameShapeDidMove(privyet: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Move the falling shape right by one column.
|
||||||
func moveShapeRight() {
|
func moveShapeRight() {
|
||||||
guard let shape = fallingShape else {
|
guard let shape = fallingShape else {
|
||||||
return
|
return
|
||||||
@ -252,4 +299,21 @@ class Privyet {
|
|||||||
delegate?.gameShapeDidMove(privyet: self)
|
delegate?.gameShapeDidMove(privyet: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Places current shape "on hold."
|
||||||
|
func holdShape() {
|
||||||
|
guard let shape = fallingShape else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let held = heldShape else {
|
||||||
|
shape.moveTo(column: HoldColumn, row: HoldRow)
|
||||||
|
heldShape = shape
|
||||||
|
fallingShape = nil
|
||||||
|
delegate?.gameShapePutOnHold(privyet: self, firstHold: true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
shape.exchangePositions(other: held)
|
||||||
|
fallingShape = held
|
||||||
|
heldShape = shape
|
||||||
|
delegate?.gameShapePutOnHold(privyet: self, firstHold: false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,10 @@ class SShape: Shape {
|
|||||||
* marks the row/column indicator for the shape
|
* marks the row/column indicator for the shape
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
override var color: BlockColor? {
|
||||||
|
return .Purple
|
||||||
|
}
|
||||||
|
|
||||||
override var blockRowColumnPositions: [Orientation : Array<(columnDiff: Int, rowDiff: Int)>] {
|
override var blockRowColumnPositions: [Orientation : Array<(columnDiff: Int, rowDiff: Int)>] {
|
||||||
return [
|
return [
|
||||||
Orientation.Zero: [(0, 0), (0, 1), (1, 1), (1, 2)],
|
Orientation.Zero: [(0, 0), (0, 1), (1, 1), (1, 2)],
|
||||||
|
@ -9,9 +9,8 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import SpriteKit
|
import SpriteKit
|
||||||
|
|
||||||
let NumOrientations: UInt32 = 4
|
// Represents the orientation of a shape in increments of 90 degrees
|
||||||
|
enum Orientation: Int, CustomStringConvertible, CaseIterable {
|
||||||
enum Orientation: Int, CustomStringConvertible {
|
|
||||||
case Zero = 0, Ninety, OneEighty, TwoSeventy
|
case Zero = 0, Ninety, OneEighty, TwoSeventy
|
||||||
|
|
||||||
var description: String {
|
var description: String {
|
||||||
@ -28,9 +27,10 @@ enum Orientation: Int, CustomStringConvertible {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static func random() -> Orientation {
|
static func random() -> Orientation {
|
||||||
return Orientation(rawValue:Int(arc4random_uniform(NumOrientations)))!
|
return Orientation.allCases.shuffled().first!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns the orientation resulting from a rotation in either direction.
|
||||||
static func rotate(orientation: Orientation, clockwise: Bool) -> Orientation {
|
static func rotate(orientation: Orientation, clockwise: Bool) -> Orientation {
|
||||||
var rotated = orientation.rawValue + (clockwise ? 1 : -1)
|
var rotated = orientation.rawValue + (clockwise ? 1 : -1)
|
||||||
if rotated > Orientation.TwoSeventy.rawValue {
|
if rotated > Orientation.TwoSeventy.rawValue {
|
||||||
@ -43,7 +43,7 @@ enum Orientation: Int, CustomStringConvertible {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The number of total shape varieties
|
// The number of total shape varieties
|
||||||
let NumShapeTypes: UInt32 = 7
|
let NumShapeTypes = 7
|
||||||
|
|
||||||
// Shape indexes
|
// Shape indexes
|
||||||
let FirstBlockIdx: Int = 0
|
let FirstBlockIdx: Int = 0
|
||||||
@ -51,10 +51,9 @@ let SecondBlockIdx: Int = 1
|
|||||||
let ThirdBlockIdx: Int = 2
|
let ThirdBlockIdx: Int = 2
|
||||||
let FourthBlockIdx: Int = 3
|
let FourthBlockIdx: Int = 3
|
||||||
|
|
||||||
|
// Base class representing a shape consisting of four blocks. Derived classes represent the different
|
||||||
|
// tetramino shapes.
|
||||||
class Shape: Hashable, CustomStringConvertible {
|
class Shape: Hashable, CustomStringConvertible {
|
||||||
// The color of the shape
|
|
||||||
let color: BlockColor
|
|
||||||
|
|
||||||
// The blocks comprising the shape
|
// The blocks comprising the shape
|
||||||
var blocks = Array<Block>()
|
var blocks = Array<Block>()
|
||||||
// The current orientation of the shape
|
// The current orientation of the shape
|
||||||
@ -65,15 +64,24 @@ class Shape: Hashable, CustomStringConvertible {
|
|||||||
// Required overrides
|
// Required overrides
|
||||||
|
|
||||||
// Subclasses must override this property
|
// Subclasses must override this property
|
||||||
|
// The color of the shape
|
||||||
|
var color: BlockColor? {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subclasses must override this property
|
||||||
|
// Returns the relative positions of the shape blocks based on the shape orientation
|
||||||
var blockRowColumnPositions: [Orientation: Array<(columnDiff: Int, rowDiff: Int)>] {
|
var blockRowColumnPositions: [Orientation: Array<(columnDiff: Int, rowDiff: Int)>] {
|
||||||
return [:]
|
return [:]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subclasses must override this property
|
// Subclasses must override this property
|
||||||
|
// Returns the blocks in a shape that are "on the bottom" based on the shape orientation
|
||||||
var bottomBlocksForOrientations: [Orientation: Array<Block>] {
|
var bottomBlocksForOrientations: [Orientation: Array<Block>] {
|
||||||
return [:]
|
return [:]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns the blocks in this shape that are currently "on the bottom"
|
||||||
var bottomBlocks: Array<Block> {
|
var bottomBlocks: Array<Block> {
|
||||||
guard let bottomBlocks = bottomBlocksForOrientations[orientation] else {
|
guard let bottomBlocks = bottomBlocksForOrientations[orientation] else {
|
||||||
return []
|
return []
|
||||||
@ -95,11 +103,13 @@ class Shape: Hashable, CustomStringConvertible {
|
|||||||
|
|
||||||
// CustomStringConvertible
|
// CustomStringConvertible
|
||||||
var description: String {
|
var description: String {
|
||||||
|
guard let color = color else {
|
||||||
|
return "<invalid color> block facing \(orientation): \(blocks[FirstBlockIdx]), \(blocks[SecondBlockIdx]), \(blocks[ThirdBlockIdx]), \(blocks[FourthBlockIdx])"
|
||||||
|
}
|
||||||
return "\(color) block facing \(orientation): \(blocks[FirstBlockIdx]), \(blocks[SecondBlockIdx]), \(blocks[ThirdBlockIdx]), \(blocks[FourthBlockIdx])"
|
return "\(color) block facing \(orientation): \(blocks[FirstBlockIdx]), \(blocks[SecondBlockIdx]), \(blocks[ThirdBlockIdx]), \(blocks[FourthBlockIdx])"
|
||||||
}
|
}
|
||||||
|
|
||||||
init(column: Int, row: Int, color: BlockColor, orientation: Orientation) {
|
init(column: Int, row: Int, orientation: Orientation) {
|
||||||
self.color = color
|
|
||||||
self.column = column
|
self.column = column
|
||||||
self.row = row
|
self.row = row
|
||||||
self.orientation = orientation
|
self.orientation = orientation
|
||||||
@ -107,19 +117,36 @@ class Shape: Hashable, CustomStringConvertible {
|
|||||||
}
|
}
|
||||||
|
|
||||||
convenience init(column: Int, row: Int) {
|
convenience init(column: Int, row: Int) {
|
||||||
self.init(column: column, row: row, color: BlockColor.random(), orientation: Orientation.random())
|
self.init(column: column, row: row, orientation: Orientation.random())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize the blocks of the shape based on the current position, color, and orientation.
|
||||||
final func initializeBlocks() {
|
final func initializeBlocks() {
|
||||||
guard let blockRowColumnTranslations = blockRowColumnPositions[orientation] else {
|
guard let blockRowColumnTranslations = blockRowColumnPositions[orientation] else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
blocks = blockRowColumnTranslations.map { (diff) -> Block in
|
blocks = blockRowColumnTranslations.map { (diff) -> Block in
|
||||||
return Block(column: column + diff.columnDiff, row: row + diff.rowDiff, color: color)
|
return Block(column: column + diff.columnDiff, row: row + diff.rowDiff, color: color!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final func getBoundingBoxForOrientation(baseColumn: Int, baseRow: Int, orientation: Orientation) -> BlockRect {
|
||||||
|
guard let offsets = blockRowColumnPositions[orientation] else {
|
||||||
|
return BlockRect(x: baseColumn, y: baseRow)
|
||||||
|
}
|
||||||
|
|
||||||
|
let blockRects = offsets.map { (offset) -> BlockRect in
|
||||||
|
return BlockRect(x: baseColumn + offset.columnDiff, y: baseRow + offset.rowDiff)
|
||||||
|
}
|
||||||
|
return blockRects[0] + blockRects[1] + blockRects[2] + blockRects[3]
|
||||||
|
}
|
||||||
|
|
||||||
|
final func getBoundingBox(baseColumn: Int, baseRow: Int) -> BlockRect {
|
||||||
|
return getBoundingBoxForOrientation(baseColumn: baseColumn, baseRow: baseRow, orientation: .Zero) + getBoundingBoxForOrientation(baseColumn: baseColumn, baseRow: baseRow, orientation: .Ninety) + getBoundingBoxForOrientation(baseColumn: baseColumn, baseRow: baseRow, orientation: .OneEighty) + getBoundingBoxForOrientation(baseColumn: baseColumn, baseRow: baseRow, orientation: .TwoSeventy)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rotate the blocks in this shape to a new orientation.
|
||||||
final func rotateBlocks(orientation: Orientation) {
|
final func rotateBlocks(orientation: Orientation) {
|
||||||
guard let blockRowColumnTranslation: Array<(columnDiff: Int, rowDiff: Int)> = blockRowColumnPositions[orientation] else {
|
guard let blockRowColumnTranslation: Array<(columnDiff: Int, rowDiff: Int)> = blockRowColumnPositions[orientation] else {
|
||||||
return
|
return
|
||||||
@ -130,34 +157,41 @@ class Shape: Hashable, CustomStringConvertible {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rotate the blocks in this shape clockwise.
|
||||||
final func rotateClockwise() {
|
final func rotateClockwise() {
|
||||||
let newOrientation = Orientation.rotate(orientation: orientation, clockwise: true)
|
let newOrientation = Orientation.rotate(orientation: orientation, clockwise: true)
|
||||||
rotateBlocks(orientation: newOrientation)
|
rotateBlocks(orientation: newOrientation)
|
||||||
orientation = newOrientation
|
orientation = newOrientation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rotate the blocks in this shape counterclockwise.
|
||||||
final func rotateCounterClockwise() {
|
final func rotateCounterClockwise() {
|
||||||
let newOrientation = Orientation.rotate(orientation: orientation, clockwise: false)
|
let newOrientation = Orientation.rotate(orientation: orientation, clockwise: false)
|
||||||
rotateBlocks(orientation: newOrientation)
|
rotateBlocks(orientation: newOrientation)
|
||||||
orientation = newOrientation
|
orientation = newOrientation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Updates block positions in this shape to one row down.
|
||||||
final func lowerShapeByOneRow() {
|
final func lowerShapeByOneRow() {
|
||||||
shiftBy(columns: 0, rows: 1)
|
shiftBy(columns: 0, rows: 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Updates block positions in this shape to one row up.
|
||||||
final func raiseShapeByOneRow() {
|
final func raiseShapeByOneRow() {
|
||||||
shiftBy(columns: 0, rows: -1)
|
shiftBy(columns: 0, rows: -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Updates block positions in this shape to one colukmn to the right.
|
||||||
final func shiftRightByOneColumn() {
|
final func shiftRightByOneColumn() {
|
||||||
shiftBy(columns: 1, rows: 0)
|
shiftBy(columns: 1, rows: 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Updates block positions in this shape to one column to the left.
|
||||||
final func shiftLeftByOneColumn() {
|
final func shiftLeftByOneColumn() {
|
||||||
shiftBy(columns: -1, rows: 0)
|
shiftBy(columns: -1, rows: 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Shifts plock positions in this shape in a relative fashion.
|
||||||
final func shiftBy(columns: Int, rows: Int) {
|
final func shiftBy(columns: Int, rows: Int) {
|
||||||
self.column += columns
|
self.column += columns
|
||||||
self.row += rows
|
self.row += rows
|
||||||
@ -167,14 +201,27 @@ class Shape: Hashable, CustomStringConvertible {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Moves the shape to an absolute position.
|
||||||
final func moveTo(column: Int, row: Int) {
|
final func moveTo(column: Int, row: Int) {
|
||||||
self.column = column
|
self.column = column
|
||||||
self.row = row
|
self.row = row
|
||||||
rotateBlocks(orientation: orientation)
|
rotateBlocks(orientation: orientation)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final func exchangePositions(other: Shape) {
|
||||||
|
var tmp = other.column
|
||||||
|
other.column = self.column
|
||||||
|
self.column = tmp
|
||||||
|
tmp = other.row
|
||||||
|
other.row = self.row
|
||||||
|
self.row = tmp
|
||||||
|
other.rotateBlocks(orientation: other.orientation)
|
||||||
|
rotateBlocks(orientation: orientation)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generates a random shape.
|
||||||
final class func random(startingColumn: Int, startingRow: Int) -> Shape {
|
final class func random(startingColumn: Int, startingRow: Int) -> Shape {
|
||||||
switch Int(arc4random_uniform(NumShapeTypes)) {
|
switch Int.random(in: 0 ..< NumShapeTypes) {
|
||||||
case 0:
|
case 0:
|
||||||
return SquareShape(column: startingColumn, row: startingRow)
|
return SquareShape(column: startingColumn, row: startingRow)
|
||||||
case 1:
|
case 1:
|
||||||
@ -191,4 +238,14 @@ class Shape: Hashable, CustomStringConvertible {
|
|||||||
return ZShape(column: startingColumn, row: startingRow)
|
return ZShape(column: startingColumn, row: startingRow)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final class func boundingBox(baseColumn: Int, baseRow: Int) -> BlockRect {
|
||||||
|
return SquareShape(column: 0, row: 0).getBoundingBox(baseColumn: baseColumn, baseRow: baseRow) +
|
||||||
|
TShape(column: 0, row: 0).getBoundingBox(baseColumn: baseColumn, baseRow: baseRow) +
|
||||||
|
LineShape(column: 0, row: 0).getBoundingBox(baseColumn: baseColumn, baseRow: baseRow) +
|
||||||
|
LShape(column: 0, row: 0).getBoundingBox(baseColumn: baseColumn, baseRow: baseRow) +
|
||||||
|
JShape(column: 0, row: 0).getBoundingBox(baseColumn: baseColumn, baseRow: baseRow) +
|
||||||
|
SShape(column: 0, row: 0).getBoundingBox(baseColumn: baseColumn, baseRow: baseRow) +
|
||||||
|
ZShape(column: 0, row: 0).getBoundingBox(baseColumn: baseColumn, baseRow: baseRow)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
BIN
Privyet/Sounds/privyet.mp3
Executable file
BIN
Privyet/Sounds/zap.mp3
Executable file
Before Width: | Height: | Size: 231 B After Width: | Height: | Size: 6.5 KiB |
Before Width: | Height: | Size: 383 B After Width: | Height: | Size: 7.3 KiB |
BIN
Privyet/Sprites.atlas/double.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
Privyet/Sprites.atlas/double@2x.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
Privyet/Sprites.atlas/green.png
Normal file
After Width: | Height: | Size: 7.0 KiB |
BIN
Privyet/Sprites.atlas/green@2x.png
Normal file
After Width: | Height: | Size: 7.9 KiB |
Before Width: | Height: | Size: 225 B After Width: | Height: | Size: 6.4 KiB |
Before Width: | Height: | Size: 381 B After Width: | Height: | Size: 7.5 KiB |
BIN
Privyet/Sprites.atlas/privyet.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
Privyet/Sprites.atlas/privyet@2x.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 226 B After Width: | Height: | Size: 6.4 KiB |
Before Width: | Height: | Size: 383 B After Width: | Height: | Size: 7.2 KiB |
Before Width: | Height: | Size: 226 B After Width: | Height: | Size: 6.7 KiB |
Before Width: | Height: | Size: 380 B After Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 222 B After Width: | Height: | Size: 6.8 KiB |
Before Width: | Height: | Size: 370 B After Width: | Height: | Size: 7.7 KiB |
BIN
Privyet/Sprites.atlas/triple.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
Privyet/Sprites.atlas/triple@2x.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 225 B After Width: | Height: | Size: 6.6 KiB |
Before Width: | Height: | Size: 378 B After Width: | Height: | Size: 7.5 KiB |
@ -16,6 +16,10 @@ class SquareShape: Shape {
|
|||||||
*/
|
*/
|
||||||
// The square shape wll not rotate
|
// The square shape wll not rotate
|
||||||
|
|
||||||
|
override var color: BlockColor? {
|
||||||
|
return .Red
|
||||||
|
}
|
||||||
|
|
||||||
override var blockRowColumnPositions: [Orientation : Array<(columnDiff: Int, rowDiff: Int)>] {
|
override var blockRowColumnPositions: [Orientation : Array<(columnDiff: Int, rowDiff: Int)>] {
|
||||||
return [
|
return [
|
||||||
Orientation.Zero: [(0, 0), (1, 0), (0, 1), (1, 1)],
|
Orientation.Zero: [(0, 0), (1, 0), (0, 1), (1, 1)],
|
||||||
|
@ -31,6 +31,10 @@ class TShape: Shape {
|
|||||||
* marks the row/column indicator for the shape
|
* marks the row/column indicator for the shape
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
override var color: BlockColor? {
|
||||||
|
return .Yellow
|
||||||
|
}
|
||||||
|
|
||||||
override var blockRowColumnPositions: [Orientation : Array<(columnDiff: Int, rowDiff: Int)>] {
|
override var blockRowColumnPositions: [Orientation : Array<(columnDiff: Int, rowDiff: Int)>] {
|
||||||
return [
|
return [
|
||||||
Orientation.Zero: [(1, 0), (0, 1), (1, 1), (2, 1)],
|
Orientation.Zero: [(1, 0), (0, 1), (1, 1), (2, 1)],
|
||||||
|
@ -21,6 +21,10 @@ class ZShape: Shape {
|
|||||||
* marks the row/column indicator for the shape
|
* marks the row/column indicator for the shape
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
override var color: BlockColor? {
|
||||||
|
return .Orange
|
||||||
|
}
|
||||||
|
|
||||||
override var blockRowColumnPositions: [Orientation : Array<(columnDiff: Int, rowDiff: Int)>] {
|
override var blockRowColumnPositions: [Orientation : Array<(columnDiff: Int, rowDiff: Int)>] {
|
||||||
return [
|
return [
|
||||||
Orientation.Zero: [(1, 0), (1, 1), (0, 1), (0, 2)],
|
Orientation.Zero: [(1, 0), (1, 1), (0, 1), (0, 2)],
|
||||||
|
11
README.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# Privyet: A Familar Puzzle Game for iOS
|
||||||
|
|
||||||
|
Thanks to:
|
||||||
|
|
||||||
|
* Bloc.io for the tutorial containing the original "Swiftris" code on which this is based
|
||||||
|
* GIMP for graphics editing
|
||||||
|
* Audacity on Windows for recording a sound clip
|
||||||
|
* ZapSplat.com for supplying a sound effect
|
||||||
|
* VMware Carbon Black for having a Hackathon where I could put this together
|
||||||
|
|
||||||
|
- Amy G. Bowersox, May 29, 2020
|