Swift实战之2048小游戏
上周在图书馆借了一本Swift语言实战入门,入个门玩一玩^_^正好这本书的后面有一个2048小游戏的实例,笔者跟着实战了一把。
差不多一周的时间,到今天,游戏的基本功能已基本实现,细节我已不打算继续完善,就这么整理一下过程中一些值得记录的点吧。
用的Swift版本是2.0,原书中的Swift版本会低一些,所以实践起来有些地方语法并不一样。
一、开始页面
在程序的第一张页面(Main.storyboard)上,只放了一个“开始游戏”按钮,点击按钮,弹出一个提示对话框,确认后,进入游戏页面。
@IBAction func startGame(sender: UIButton) {
let alerController = UIAlertController(title: "开始", message: "游戏就要开始,你准备好了吗?", preferredStyle: UIAlertControllerStyle.Alert)
alerController.addAction(UIAlertAction(title: "Ready Go!", style: UIAlertActionStyle.Default, handler: {
action in
self.presentViewController(MainTabViewController(), animated: true, completion: nil)
}))
self.presentViewController(alerController, animated: true, completion: nil) }
二、游戏页面
用一个TabViewController来实现,控制游戏页面(MainViewController)、设置页面(SettingViewController)和主题页面(KKColorListViewController)等三个页面的切换。
import UIKit class MainTabViewController: UITabBarController,KKColorListViewControllerDelegate {
var viewMain = MainViewController()
var viewColor = KKColorListViewController(schemeType:KKColorsSchemeType.Crayola)
override func viewDidLoad() {
super.viewDidLoad() // Do any additional setup after loading the view. viewMain.title = ""
let user=UserModel.sharedInstance().user
if let red = user?.red{
let uicolor=UIColor(red: red, green: (user?.green)!, blue: (user?.blue)!, alpha: (user?.alpha)!)
viewMain.view.backgroundColor=uicolor
} let viewSetting = SettingViewController()
viewSetting.title = "设置" viewColor.title="颜色"
viewColor.headerTitle="选择背景色"
viewColor.delegate=self let main = UINavigationController(rootViewController: viewMain)
let setting = UINavigationController(rootViewController: viewSetting)
let color = UINavigationController(rootViewController: viewColor) self.viewControllers = [main, setting,color]
self.selectedIndex =
} override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
} func colorListController(controller: KKColorListViewController!, didSelectColor color: KKColor!) {
viewMain.view.backgroundColor = color.uiColor() UserModel.sharedInstance().saveColor(color.uiColor())
self.selectedIndex=
} func colorListPickerDidComplete(controller: KKColorListViewController!) {
self.selectedIndex=
} }
(一)主题页面
其中,主题页面直接使用GitHub上的一个开源项目KKColorListViewController,选中颜色后,改变游戏页面的背景色。
这个项目可以从GitHub直接下载,但这个项目是用Objective-C写的,所以添加到Swift项目中后,需要新建一个Bridge头文件,这个头文件需要保存在项目文件夹的根目录下,而不是项目文件夹里面的源码文件夹(否则,可能需要自己配置头文件的目录)
#ifndef Bridging_Header_h
#define Bridging_Header_h
#import "KKColorListPicker.h" #endif /* Bridging_Header_h */
另外,添加到项目后,编译时还会有一些文件会报错,需要修改一些细节才能正常使用。
(1)KKColorsSchemeType.h中需添加
#import <Foundation/Foundation.h>
(2)KKColorListViewController中(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath方法,将最后一句注释掉。否则每次选完颜色,程序就会关闭当前的MainTabViewController而回到开始游戏页面。
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
KKColor *color = self.colors[indexPath.row];
if (self.delegate) {
[self.delegate colorListController:self didSelectColor:color];
}
// [self dismissViewControllerAnimated:YES completion:nil];
}
(二)游戏页面
1、ScoreView
游戏页面,上方有两个自定义的UIView,用于动态显示游戏分数。
import UIKit enum ScoreType{
case Common
case Best
} protocol ScoreViewProtocol{
func changeScore(value s:Int)
} class ScoreView: UIView, ScoreViewProtocol { var label:UILabel!
let defaultFrame = CGRectMake(, , , )
var stype:String! var score:Int = {
didSet{
label.text = "\(stype):\(score)"
}
} init(stype: ScoreType){
super.init(frame: defaultFrame)
self.stype = (stype == ScoreType.Common ? "分数":"最高分") backgroundColor = UIColor.orangeColor()
label = UILabel(frame: defaultFrame)
label.textAlignment = NSTextAlignment.Center
label.font = UIFont(name: "微软雅黑", size: )
label.textColor = UIColor.whiteColor() self.addSubview(label) //布局约束
//必须将该属性值设置为false,否则自己设置的约束和AutoresizingMask生成的约束有冲突,运行时会产生异常
self.translatesAutoresizingMaskIntoConstraints = false
//宽度约束
self.widthAnchor.constraintEqualToConstant().active=true
//高度约束
self.heightAnchor.constraintEqualToConstant().active=true
} required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
} func changeScore(value s: Int) {
self.score = s
} }
上面的ScoreView首先由一个ScoreType来选择显示“分数”还是“最高分”,然后有一个changeScore的方法,可以改变Score属性值,改变该值得时候同时改变Label显示的数字。
值得一提的是,在该UIView中,添加了布局约束,方便我们把该UIView添加到页面时控制它的布局。在iOS9.0中,多了一个NSLayoutAnchor类,用它来完成布局约束,比原来低版本用的NSLayoutConstraint要更方便一些。
2、按钮
游戏页面下方是两个按钮,重置清空本次游戏的数字,生成则产生一个数字,这两个按钮主要用于调试。
按钮是在一个ViewFactory的工厂类中生产的,同样生产时,添加了一些布局约束。
class func createButton(title:String,action:Selector,sender:UIViewController) -> UIButton{
let button = UIButton(frame: CGRectZero)
button.backgroundColor=UIColor.orangeColor()
button.setTitle(title, forState: .Normal)
button.titleLabel!.textColor=UIColor.whiteColor()
button.titleLabel!.font=UIFont.systemFontOfSize() //布局约束
button.translatesAutoresizingMaskIntoConstraints = false
button.widthAnchor.constraintEqualToConstant().active=true
button.heightAnchor.constraintEqualToConstant().active=true button.addTarget(sender, action: action, forControlEvents: UIControlEvents.TouchUpInside)
return button
}
3、游戏区域(游戏地图)
一个5X5的矩阵,首先在所有位置上放置灰色的方块UIView。
var backgrounds:Array<UIView>! //所有方块的背景
func setupGameMap(){
let margins = self.view.layoutMarginsGuide for row in ..<self.dimension {
for col in ..<self.dimension {
//放置灰色的方块在对应的矩阵位置上
let background = UIView(frame: CGRectMake(, , self.width, self.width))
background.backgroundColor = UIColor.darkGrayColor()
background.translatesAutoresizingMaskIntoConstraints = false
background.widthAnchor.constraintEqualToConstant(self.width).active = true
background.heightAnchor.constraintEqualToConstant(self.width).active = true
self.view.addSubview(background)
self.backgrounds.append(background) //布局约束
background.translatesAutoresizingMaskIntoConstraints=false
background.widthAnchor.constraintEqualToConstant(self.width).active=true
background.heightAnchor.constraintEqualToConstant(self.width).active=true //用代码进行布局约束
var centerXConstant:CGFloat
var centerYConstant:CGFloat
if self.dimension% == {
centerXConstant = (self.padding+self.width) * CGFloat(col-self.dimension/)
centerYConstant = (self.padding+self.width) * CGFloat(row-self.dimension/)
}else{
centerXConstant = (self.padding+self.width) * CGFloat(Double(col-self.dimension/)+0.5)
centerYConstant = (self.padding+self.width) * CGFloat(Double(row-self.dimension/)+0.5)
} background.centerXAnchor.constraintEqualToAnchor(margins.centerXAnchor, constant: centerXConstant).active=true
background.centerYAnchor.constraintEqualToAnchor(margins.centerYAnchor, constant: centerYConstant).active=true }
}
}
然后,添加数字时,再在相应位置上放置数字方块TileView。
var tiles = [NSIndexPath:TileView]() //存储当前的有数字的方块 //插入一个数字方块
func insertTile(pos:(Int,Int),value:Int){
let (row,col)=pos let x= + CGFloat(col) * (self.width+self.padding)
let y= + CGFloat(row) * (self.width+self.padding) let tile=TileView(pos:CGPointMake(x,y),width:self.width,value:value) self.view.addSubview(tile)
self.view.bringSubviewToFront(tile) let index = NSIndexPath(forRow: row, inSection: col) tiles[index] = tile //布局约束
var centerXConstant:CGFloat
var centerYConstant:CGFloat
if self.dimension% == {
centerXConstant = (self.padding+self.width) * CGFloat(col-self.dimension/)
centerYConstant = (self.padding+self.width) * CGFloat(row-self.dimension/)
}else{
centerXConstant = (self.padding+self.width) * CGFloat(Double(col-self.dimension/)+0.5)
centerYConstant = (self.padding+self.width) * CGFloat(Double(row-self.dimension/)+0.5)
}
let margins=self.view.layoutMarginsGuide
tile.centerXAnchor.constraintEqualToAnchor(margins.centerXAnchor, constant: centerXConstant).active=true
tile.centerYAnchor.constraintEqualToAnchor(margins.centerYAnchor, constant: centerYConstant).active=true
}
上面用了一个数字tiles来保存插入的TileView,以便清除数字时,可以把对应的TileView从界面中移除。
//移除一个数字方块
func clearTile(row:Int,col:Int){ let index=NSIndexPath(forRow: row, inSection: col)
let tile=tiles[index]!
tile.removeFromSuperview()
tiles.removeValueForKey(index) }
TileView的实现代码如下:
import Foundation
import UIKit class TileView : UIView {
let colorMap=[
:UIColor.redColor(),
:UIColor.orangeColor(),
:UIColor.lightTextColor(),
:UIColor.greenColor(),
:UIColor.brownColor(),
:UIColor.blackColor(),
:UIColor.purpleColor(),
:UIColor.lightGrayColor(),
:UIColor.cyanColor(),
:UIColor.magentaColor(),
:UIColor.blackColor()
] var value:Int{
didSet{
backgroundColor=colorMap[value]
numberLabel.text="\(value)"
}
} var numberLabel:UILabel! init(pos:CGPoint,width:CGFloat,value:Int){
self.value=value numberLabel=UILabel(frame: CGRectMake(, , width, width))
numberLabel.textColor=UIColor.whiteColor()
numberLabel.textAlignment=NSTextAlignment.Center
numberLabel.minimumScaleFactor=0.5
numberLabel.font=UIFont(name: "微软雅黑", size: )
numberLabel.text="\(value)" super.init(frame: CGRectMake(pos.x, pos.y,width , width))
addSubview(numberLabel)
backgroundColor=colorMap[value] //代码约束
self.translatesAutoresizingMaskIntoConstraints = false
self.widthAnchor.constraintEqualToConstant(width).active=true
self.heightAnchor.constraintEqualToConstant(width).active=true } required init?(coder aDecoder: NSCoder) {
self.value =
super.init(coder: aDecoder)
} }
4、游戏模型
(1)游戏数据存储
采用一个自定义的矩阵数据结构,存储游戏数据。
//自定义矩阵,对应2048的游戏面板
struct Matrix {
let rows:Int,columns:Int
var grid:[Int]
init(rows:Int,columns:Int){
self.rows=rows
self.columns=columns
grid = Array<Int>(count: rows * columns, repeatedValue: )
} func indexIsValidForRow(row:Int,column:Int)->Bool{
return (row >= ) && (row < rows) && (column >= ) && (column < columns)
} subscript(row:Int,column:Int)->Int{
get{
assert(indexIsValidForRow(row, column: column),"超出范围")
return grid[(row * columns)+column]
}
set{
assert(indexIsValidForRow(row, column: column),"超出范围")
grid[(row * columns)+column]=newValue
}
} // 相等函数,判断两个Matrix是否相等
func isEqualTo(matrix:Matrix)->Bool{
if rows != matrix.rows{
return false
}
if columns != matrix.columns{
return false
}
for i in ..<rows{
for j in ..<columns{
if self[i,j] != matrix[i,j]{
return false
}
}
}
return true
}
}
游戏模型中,包含tiles和维度两个属性,用维度来对tiles这个Matrix进行初始化,同时,提供一些方法。
class GameModel{
var dimension:Int = {
didSet{
self.tiles = Matrix(rows: self.dimension, columns: self.dimension)
}
}
var tiles:Matrix
init(dimension:Int){
self.dimension=dimension
self.tiles = Matrix(rows: self.dimension, columns: self.dimension)
} func emptyPosition()->[Int]{
var emptytiles=Array<Int>()
for row in ..<self.dimension{
for col in ..<self.dimension{
let val=tiles[row,col]
if val== {
emptytiles.append(tiles[row,col])
}
} }
return emptytiles
} func isFull()->Bool{
if emptyPosition().count== {
return true
}
return false
}
// 打印矩阵,调试时使用
func printTiles(){
for i in ..<self.dimension{
print(tiles.grid[(i*self.dimension)...((i+)*self.dimension-)])
}
} // 设置某个位置的值
func setPosition(row:Int,col:Int,value:Int)->Bool{
assert(row>= && row<dimension)
assert(col>= && col<dimension) print("值更新之前:")
printTiles() tiles[row,col]=value print("值更新之后:tiles[\(row),\(col)]=\(tiles[row,col])")
printTiles()
return true
}
// 清除数据
func clearAll(){
tiles=Matrix(rows: self.dimension, columns: self.dimension)
} }
(2)游戏数据变更
产生一个数字:
//生成新的数字,生成按钮的响应方法
func genNumber(){
//// 随机产生数字2和4,几率为1:4
// let randv=Int(arc4random_uniform(5))
// var seed:Int = 2
// if randv==1 {
// seed = 4
// } let col=Int(arc4random_uniform(UInt32(dimension)))
let row=Int(arc4random_uniform(UInt32(dimension))) if gameModel.isFull(){
print("位置已经满了")
return
} if gameModel.tiles[row, col]> {
genNumber()
return
} let seed= //原书的程序,按照一定的几率比来生成2或4。此处改成一直生成2.
gameModel.setPosition(row, col: col, value: seed)
insertTile((row,col), value: seed) // 生成数字后,判断一下游戏是否结束
checkGameOver()
}
每次生成一个数字后,判断一下游戏是否结束。如果矩阵的所有位置都有数字,并且相邻位置的数字都不相同,则本轮游戏结束,弹出一个提示框,重新开始游戏。
//检查游戏是否结束
func checkGameOver(){
if self.gameModel.isFull(){
for row in ..<self.dimension{
for col in ..<self.dimension{
let val = gameModel.tiles[row,col]
let left:Int? = (col-)>= ? gameModel.tiles[row,col-] : nil
let right:Int? = (col+)<self.dimension ? gameModel.tiles[row,col+] : nil
let up:Int? = (row-)>= ? gameModel.tiles[row-,col] : nil
let down:Int? = (row+)<self.dimension ? gameModel.tiles[row+,col] : nil
if (val==left) || (val==right) || (val==up) || (val==down){
return
} }
} let alerController = UIAlertController(title: "游戏结束", message: "本轮游戏结束,重新开始吧!", preferredStyle: UIAlertControllerStyle.Alert)
alerController.addAction(UIAlertAction(title: "Ready Go!", style: UIAlertActionStyle.Default, handler: {
action in
self.resetGameMap()
}))
self.presentViewController(alerController, animated: true, completion: nil)
return
}
}
5、游戏效果实现
首先,是添加手势识别器,相当于对手势处理方法进行注册。
//添加滑动的手势识别处理
func setupSwipeGestures(){
let upSwipe=UISwipeGestureRecognizer(target: self, action: Selector("swipeUp"))
upSwipe.numberOfTouchesRequired=
upSwipe.direction=UISwipeGestureRecognizerDirection.Up
self.view.addGestureRecognizer(upSwipe) let downSwipe=UISwipeGestureRecognizer(target: self, action: Selector("swipeDown"))
downSwipe.numberOfTouchesRequired=
downSwipe.direction=UISwipeGestureRecognizerDirection.Down
self.view.addGestureRecognizer(downSwipe) let leftSwipe=UISwipeGestureRecognizer(target: self, action: Selector("swipeLeft"))
leftSwipe.numberOfTouchesRequired=
leftSwipe.direction=UISwipeGestureRecognizerDirection.Left
self.view.addGestureRecognizer(leftSwipe) let rightSwipe=UISwipeGestureRecognizer(target: self, action: Selector("swipeRight"))
rightSwipe.numberOfTouchesRequired=
rightSwipe.direction=UISwipeGestureRecognizerDirection.Right
self.view.addGestureRecognizer(rightSwipe) }
然后,就是具体的处理方法,上下左右四个方向的滑动,会引起对应的方向上的数字重排和合并。原书中的处理是先将数字重排,然后进行合并。这里采用自己的逻辑方法,用递归的方式,对于值为0的位置,向后检索不为0的数字放入其中,并考虑是否要和相邻位置进行合并。
var tileVals:Matrix //合并数字时所用的游戏数据复本 //向上滑动
func swipeUp(){
tileVals = gameModel.tiles func merge(row row:Int,col:Int){
if row == self.dimension-{
return
} for i in (row+)..<self.dimension {
let valNew = tileVals[i,col]
if valNew> {
let val = tileVals[row, col] if val == {
tileVals[row, col] = valNew
tileVals[i, col] =
merge(row: row, col: col)
}else if val == valNew{
tileVals[row, col] = valNew<<
tileVals[i, col] =
if row== {
merge(row: row, col: col)
}else{
merge(row: row-, col: col)
}
// 若产生合并,则加分
changeScore(valNew)
}else{
merge(row: row+, col: col)
} break
}
}
} for col in ..<self.dimension{
merge(row: , col: col)
} // 如果合并后的结果与原来相同,则不做任何操作
if tileVals.isEqualTo(gameModel.tiles){
return
}
// 显示合并后的结果,并产生新的数字
refresh()
genNumber()
}
如果过程中产生合并,则进行加分。
//加分
func changeScore(baseNum:Int){
self.scoreView.score += baseNum * }
处理时,用了一个临时的Matrix变量tileVals来作处理,以便与未处理的数据进行比较,决定是否要进行后续的操作。如果处理后的数据与处理前不一致,则需要刷新页面中的游戏数据。
//刷新页面
func refresh(){
for i in ..<self.dimension{
for j in ..<self.dimension{
let val = gameModel.tiles[i,j]
let valNew = tileVals[i,j]
if valNew != val{
gameModel.setPosition(i, col: j, value: valNew)
if valNew>{
if val>{
clearTile(i, col: j)
}
insertTile((i,j) , value: valNew) }else{
clearTile(i, col: j)
}
} }
}
}
其他三个方向上的处理方法类似:
//向下滑动 func swipeDown(){
tileVals = gameModel.tiles func merge(row row:Int,col:Int){
if row == {
return
} for var i=row-;i>=;i-- {
let valNew=tileVals[i,col]
if valNew> {
let val=tileVals[row,col] if val == {
tileVals[row, col] = valNew
tileVals[i, col] =
merge(row: row, col: col)
}else if val == valNew{
tileVals[row, col] = valNew<<
tileVals[i, col] = if row==self.dimension- {
merge(row: row, col: col)
}else{
merge(row: row+, col: col)
}
// 若产生合并,则加分
changeScore(valNew)
}else{
merge(row: row-, col: col)
} break
}
}
} for col in ..<self.dimension{
merge(row: self.dimension-, col: col)
}
// 如果合并后的结果与原来相同,则不做任何操作
if tileVals.isEqualTo(gameModel.tiles){
return
} refresh()
genNumber()
} //向左滑动
func swipeLeft(){
tileVals = gameModel.tiles
func merge(row row:Int,col:Int){
if col == self.dimension-{
return
} for i in (col+)..<self.dimension {
let valNew=tileVals[row,i]
if valNew> {
let val=tileVals[row,col] if val == {
tileVals[row, col] = valNew
tileVals[row, i] =
merge(row: row, col: col)
}else if val == valNew{
tileVals[row, col] = valNew<<
tileVals[row, i] =
if col== {
merge(row: row, col: col)
}else{
merge(row: row, col: col-)
}
// 若产生合并,则加分
changeScore(valNew)
}else{
merge(row: row, col: col+)
} break
}
}
} for row in ..<self.dimension{
merge(row: row, col: )
} // 如果合并后的结果与原来相同,则不做任何操作
if tileVals.isEqualTo(gameModel.tiles){
return
}
refresh()
genNumber()
} //向右滑动
func swipeRight(){
tileVals = gameModel.tiles
func merge(row row:Int,col:Int){
if col == {
return
} for var i=col-;i>=;i-- {
let valNew=tileVals[row, i]
if valNew> {
let val=tileVals[row, col] if val == {
tileVals[row, col] = valNew
tileVals[row, i] =
merge(row: row, col: col)
}else if val == valNew{
tileVals[row, col] = valNew<<
tileVals[row, i] =
if col==self.dimension- {
merge(row: row, col: col)
}else{
merge(row: row, col: col+)
}
// 若产生合并,则加分
changeScore(valNew)
}else{
merge(row: row, col: col-)
} break
}
}
} for row in ..<self.dimension{
merge(row: row, col: self.dimension-)
} // 如果合并后的结果与原来相同,则不做任何操作
if tileVals.isEqualTo(gameModel.tiles){
return
}
refresh()
genNumber() }
(三)设置页面
设置页面可以设置游戏矩阵的维度。上面的阈值,游戏中数字超过该阈值可以视作通关,不过这个功能并没有实现,因为到后面对这个功能无甚兴趣~\(≧▽≦)/~。
上面的几个控件都是在ViewFactory生产的,大多是一些常用控件,UISegmentedControl我倒是第一次用到,功能上有点像RadioButton,只是外观不一样。
ViewFactory的代码如下:
import UIKit class ViewFactory {
class func getDefaultFrame() -> CGRect {
let defaultFrame = CGRectMake(, , , )
return defaultFrame
} class func createControl(type:String, title:[String],action:Selector,sender:AnyObject)->UIView{
switch(type){
case "label": return ViewFactory.createLabel(title[])
case "button": return ViewFactory.createButton(title[], action: action, sender: sender as! UIViewController)
case "text":return ViewFactory.createTextField(title[], action: action, sender: sender as! UITextFieldDelegate)
case "segment":return ViewFactory.createSegment(title, action: action, sender: sender as! UIViewController)
default: return ViewFactory.createLabel(title[])
}
} class func createLabel(title:String) -> UILabel{
let label = UILabel()
label.textColor = UIColor.blackColor()
label.backgroundColor = UIColor.whiteColor()
label.text = title
label.frame = CGRectZero
label.font = UIFont(name: "HelveticaNeue-Bold", size: )
label.translatesAutoresizingMaskIntoConstraints = false return label
} class func createButton(title:String,action:Selector,sender:UIViewController) -> UIButton{
let button = UIButton(frame: CGRectZero)
button.backgroundColor=UIColor.orangeColor()
button.setTitle(title, forState: .Normal)
button.titleLabel!.textColor=UIColor.whiteColor()
button.titleLabel!.font=UIFont.systemFontOfSize() //布局约束
button.translatesAutoresizingMaskIntoConstraints = false
button.widthAnchor.constraintEqualToConstant().active=true
button.heightAnchor.constraintEqualToConstant().active=true button.addTarget(sender, action: action, forControlEvents: UIControlEvents.TouchUpInside)
return button
} class func createTextField(value:String,action:Selector,sender:UITextFieldDelegate) -> UITextField{
let textField=UITextField(frame: ViewFactory.getDefaultFrame())
textField.backgroundColor=UIColor.clearColor()
textField.textColor=UIColor.blackColor()
textField.text=value
textField.borderStyle=UITextBorderStyle.RoundedRect
textField.adjustsFontSizeToFitWidth=true
textField.delegate=sender textField.translatesAutoresizingMaskIntoConstraints = false
return textField
} class func createSegment(items:[String],action:Selector,sender:UIViewController)->UISegmentedControl {
let segment=UISegmentedControl(items: items)
segment.frame=ViewFactory.getDefaultFrame()
segment.momentary=false
segment.addTarget(sender, action: action, forControlEvents: UIControlEvents.ValueChanged) segment.translatesAutoresizingMaskIntoConstraints = false
return segment }
}
SettingViewController的实现:
import UIKit class SettingViewController: UIViewController,UITextFieldDelegate {
init(){
super.init(nibName: nil, bundle: nil)
} required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
} var main:MainViewController?
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor=UIColor.whiteColor() let views = (self.parentViewController?.parentViewController as! MainTabViewController).viewControllers
main = ((views?[] as? UINavigationController)?.childViewControllers)?[] as? MainViewController
setupControls()
// Do any additional setup after loading the view.
} override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
} var segDimension:UISegmentedControl?
func setupControls(){
let segVals = [,,]
let textNum = ViewFactory.createTextField("", action: Selector("numChanged:"), sender: self)
textNum.returnKeyType=UIReturnKeyType.Done self.view.addSubview(textNum) let labelNum = ViewFactory.createLabel("阈值:")
self.view.addSubview(labelNum) segDimension = ViewFactory.createSegment(["3X3","4X4","5X5"], action: "dimensionChanged:", sender: self)
self.view.addSubview(segDimension!)
segDimension!.selectedSegmentIndex=segVals.indexOf((main?.dimension)!)! let labelDm=ViewFactory.createLabel("维度:")
self.view.addSubview(labelDm) //布局约束
let margins=self.view.layoutMarginsGuide
labelNum.widthAnchor.constraintEqualToConstant().active=true
labelNum.heightAnchor.constraintEqualToConstant().active=true
labelNum.leadingAnchor.constraintEqualToAnchor(margins.leadingAnchor, constant: ).active=true
labelNum.topAnchor.constraintEqualToAnchor(margins.topAnchor, constant: ).active=true textNum.widthAnchor.constraintEqualToConstant().active=true
textNum.heightAnchor.constraintEqualToConstant().active=true
textNum.leadingAnchor.constraintEqualToAnchor(labelNum.trailingAnchor).active=true
textNum.topAnchor.constraintEqualToAnchor(labelNum.topAnchor).active=true labelDm.widthAnchor.constraintEqualToConstant().active=true
labelDm.heightAnchor.constraintEqualToConstant().active=true
labelDm.leadingAnchor.constraintEqualToAnchor(labelNum.leadingAnchor).active=true
labelDm.topAnchor.constraintEqualToAnchor(textNum.topAnchor, constant: ).active=true segDimension?.widthAnchor.constraintEqualToConstant().active=true
segDimension?.heightAnchor.constraintEqualToConstant().active=true
segDimension?.leadingAnchor.constraintEqualToAnchor(labelDm.trailingAnchor).active=true
segDimension?.topAnchor.constraintEqualToAnchor(labelDm.topAnchor).active=true } //
func numChanged(textField:UITextField)->Bool{
textField.resignFirstResponder()
//改变游戏页面
if (textField.text != "\(main?.maxnumber)"){
let num = Int(textField.text!)
main?.maxnumber = num! }
//存储到本地数据库
UserModel.sharedInstance().saveMaxnum((main?.maxnumber)!)
return true
} func dimensionChanged(sender:SettingViewController){
let segVals = [,,]
//改变游戏页面
main?.dimension = segVals[segDimension!.selectedSegmentIndex]
main?.resetGameMap()
//存储到本地数据库
UserModel.sharedInstance().saveDimesion((main?.dimension)!)
} }
设置页面不仅要对游戏页面的刷新,同时也对本地数据库的游戏参数进行变更。
1、游戏界面刷新
//游戏面板重绘
func resetGameMap(){
UserModel.sharedInstance().saveBestScore(self.scoreView.score)
for view in self.view.subviews{
view.removeFromSuperview()
}
backgrounds.removeAll()
tiles.removeAll()
gameModel.clearAll()
gameModel.dimension=self.dimension
setupScoreLables()
setupGameMap()
setupButton()
genNumber() }
刷新界面时,先保存当前分数,然后再对界面进行重绘。
2、游戏参数保存
原书使用SQLite作为本地数据库,笔者个人觉得SQLite的使用挺麻烦的(偶是个没有用过SQLite的菜鸟O(∩_∩)O~),无意间找到了一个相对使用更为方便的数据库Realm。
官网地址:https://realm.io,在首页上就可以下载Objective-C、Swift或Android等三个版本的Realm。首页下方有Realm的介绍,标题就是这么一句:
Realm is a replacement for SQLite & Core Data
Realm的中文资料不是很多,但因为其使用方便,现有资料也已经足够。这里有一篇关于Realm的教程:Realm数据库基础教程,具体配置可以参考,不过由于版本差异可能会有些不一样的地方。
笔者下载的版本是realm-swift-0.95.2,下载后自动解压。
(1)在iOS/swift-2.0中找到Realm.framework和RealmSwift.framework,把它们拖到项目中,一定要确保勾选了 Copy Items if needed 选项。
(2)将链接库配置如下:
下面是基于Realm的数据模型。UserMode使用单例模式保证数据库的唯一性,并提供方法来对Realm数据库中的user的属性值进行改写。
import Foundation
import UIKit
import RealmSwift
import Realm // 继承Object的用户数据模型
// 属性包括用户id、游戏维度、通过数值、背景颜色参数、不同维度对应的最高分数
class UserObject:Object{
// 所有属性必须明确数据类型并且初始化
dynamic var userid:String = ""
dynamic var dimension:Int =
dynamic var maxnum:Int = dynamic var red:CGFloat = 0.0
dynamic var green:CGFloat = 0.0
dynamic var blue:CGFloat = 0.0
dynamic var alpha:CGFloat = 0.0 dynamic var bestScoreForD3:Int =
dynamic var bestScoreForD4:Int =
dynamic var bestScoreForD5:Int = // 自定义的初始化方法
init(userid:String,dimension:Int,maxnum:Int,backgroundColor:UIColor){ let cicolor = CIColor(color:backgroundColor)
red = cicolor.red
green = cicolor.green
blue = cicolor.blue
alpha = cicolor.alpha self.dimension = dimension
self.maxnum = maxnum
self.userid = userid super.init()
} required init() {
super.init()
} override init(realm: RLMRealm, schema: RLMObjectSchema) {
super.init(realm: realm, schema: schema)
} // 将userid作为primaryKey,这个功能不是必要的,因为userid都是同一个。
// 只是看了Realm的文档后,试着添加进来而已
override static func primaryKey() -> String?{
return "userid"
}
} class UserModel
{
// 产生userid
class func get_uuid()->String{
let userid = NSUserDefaults.standardUserDefaults().stringForKey("swift2048user")
if userid == nil {
let uuid_ref=CFUUIDCreate(nil)
let uuid_string_ref=CFUUIDCreateString(nil, uuid_ref)
let uuid:String = uuid_string_ref as String
NSUserDefaults.standardUserDefaults().setObject(uuid, forKey: "swift2048user")
return uuid
}
return userid!
} var realm:Realm!
let userid:String
var user:UserObject?
required init(){
//------ Realm 配置 --------------
// 这里涉及到Realm的Migrations。
// 因为我对UserObject的架构进行更改过,必须配置,才能使数据库中的旧的UserObject更新。
var config = Realm.Configuration() // schemaVersion,这可以看做UserObject的版本
// 原始值为0,这是第二次更改,所以要赋值为2
config.schemaVersion =
config.migrationBlock = {
migration,oldSchemaVersion in
if oldSchemaVersion < { }
}
Realm.Configuration.defaultConfiguration = config
//------ Realm 配置 Over -------------- realm = try! Realm()
userid = UserModel.get_uuid()
let objects = realm.objects(UserObject).filter("userid == '\(userid)'") //检索对象
//如果用户不存在
if objects.count == {
realm.beginWrite()
//根据默认的数据创建游戏
user = UserObject(userid: userid, dimension: , maxnum: , backgroundColor: UIColor.darkGrayColor())
realm.add(user!)
realm.commitWrite()
}else{
user = objects.first
}
} // 单例模式
struct Static {
static var instance:UserModel? = nil
static var token: dispatch_once_t =
}
class func sharedInstance()->UserModel {
dispatch_once(&Static.token){
print("UserModel - Dispatch once")
Static.instance = self.init()
}
return Static.instance!
} // 返回属性值,调试可用
func getUserdata()->[String:String]{
let dic:[String:String] = ["maxnum":"\(self.user?.maxnum)",
"dimension":"\(self.user?.dimension)",
"red":"\(self.user?.red)",
"green":"\(self.user?.green)",
"blue":"\(self.user?.blue)",
"alpha":"\(self.user?.alpha)"]
return dic } // 更改属性值
func saveDimesion(dimension:Int){
realm.write({
self.user?.dimension = dimension
}) } func saveMaxnum(maxnum:Int){
realm.write({
self.user?.maxnum = maxnum
})
} func saveColor(backgroundColor:UIColor){
realm.write({
let cicolor = CIColor(color: backgroundColor)
self.user?.red = cicolor.red
self.user?.green = cicolor.green
self.user?.blue = cicolor.alpha
})
} func saveBestScore(score:Int){
realm.write({
if (self.user?.dimension==) && (score>self.user?.bestScoreForD3){
self.user?.bestScoreForD3 = score
return
} if (self.user?.dimension==) && (score>self.user?.bestScoreForD4){
self.user?.bestScoreForD4 = score
return
} if (self.user?.dimension==) && (score>self.user?.bestScoreForD5){
self.user?.bestScoreForD5 = score
return
}
})
}
}
至此,整个项目的完成过程介绍完毕。
关于游戏成品:功能不算丰富,细节不算完美,个别地方只是在原书的基础上做了更改,如有兴趣,可以自行完善。
最后,附上MainViewController的完整代码。
import UIKit
import RealmSwift class MainViewController: UIViewController { var dimension:Int = //矩阵的维度,即游戏面板的大小
var maxnumber:Int = //游戏通关阈值,该功能没有实现
var gameModel:GameModel //游戏数据模型 var width:CGFloat = //方块的宽度,用于布局
var padding:CGFloat = //方块之间的间隔宽度,用于布局 var backgrounds:Array<UIView>! //所有方块的背景 var tiles = [NSIndexPath:TileView]() //存储当前的有数字的方块
var tileVals:Matrix //合并数字时所用的游戏数据复本 init(){
self.dimension = (UserModel.sharedInstance().user?.dimension)! //从数据库中取出维度
self.backgrounds = Array<UIView> ()
self.gameModel = GameModel(dimension: self.dimension)
self.tileVals = Matrix(rows: self.dimension, columns: self.dimension) print("dimension:\(self.dimension)")
super.init(nibName: nil, bundle: nil)
} required init?(coder aDecoder: NSCoder) {
self.gameModel = GameModel(dimension: dimension)
self.tileVals = Matrix(rows: dimension, columns: dimension)
super.init(coder: aDecoder)
} override func viewDidLoad() {
super.viewDidLoad() // Do any additional setup after loading the view.
self.view.backgroundColor = UIColor.whiteColor()
setupScoreLables()
setupGameMap()
setupButton()
setupSwipeGestures() genNumber() } override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
} //---------------------------初始化游戏页面-------------------------
func setupGameMap(){
let margins = self.view.layoutMarginsGuide for row in ..<self.dimension {
for col in ..<self.dimension {
//放置灰色的方块在对应的矩阵位置上
let background = UIView(frame: CGRectMake(, , self.width, self.width))
background.backgroundColor = UIColor.darkGrayColor()
background.translatesAutoresizingMaskIntoConstraints = false
background.widthAnchor.constraintEqualToConstant(self.width).active = true
background.heightAnchor.constraintEqualToConstant(self.width).active = true
self.view.addSubview(background)
self.backgrounds.append(background) //布局约束
background.translatesAutoresizingMaskIntoConstraints=false
background.widthAnchor.constraintEqualToConstant(self.width).active=true
background.heightAnchor.constraintEqualToConstant(self.width).active=true //用代码进行布局约束
var centerXConstant:CGFloat
var centerYConstant:CGFloat
if self.dimension% == {
centerXConstant = (self.padding+self.width) * CGFloat(col-self.dimension/)
centerYConstant = (self.padding+self.width) * CGFloat(row-self.dimension/)
}else{
centerXConstant = (self.padding+self.width) * CGFloat(Double(col-self.dimension/)+0.5)
centerYConstant = (self.padding+self.width) * CGFloat(Double(row-self.dimension/)+0.5)
} background.centerXAnchor.constraintEqualToAnchor(margins.centerXAnchor, constant: centerXConstant).active=true
background.centerYAnchor.constraintEqualToAnchor(margins.centerYAnchor, constant: centerYConstant).active=true }
}
} let scoreView = ScoreView(stype:ScoreType.Common)
let bestScoreView = ScoreView(stype: ScoreType.Best)
func setupScoreLables(){ scoreView.changeScore(value: )
self.view.addSubview(scoreView) let val=(self.dimension==) ? (UserModel.sharedInstance().user?.bestScoreForD3)! : ((self.dimension==) ? (UserModel.sharedInstance().user?.bestScoreForD4)! : (UserModel.sharedInstance().user?.bestScoreForD5)!)
bestScoreView.changeScore(value: val)
self.view.addSubview(bestScoreView) //布局约束
let margins = self.view.layoutMarginsGuide
scoreView.trailingAnchor.constraintEqualToAnchor(margins.centerXAnchor, constant: -).active=true
scoreView.topAnchor.constraintEqualToAnchor(margins.topAnchor, constant: ).active=true
//
bestScoreView.leadingAnchor.constraintEqualToAnchor(margins.centerXAnchor, constant: ).active=true
bestScoreView.topAnchor.constraintEqualToAnchor(scoreView.topAnchor).active=true } func setupButton(){
let clrBtn=ViewFactory.createButton("重置", action: Selector("clearNumber"), sender: self)
self.view.addSubview(clrBtn) let genBtn=ViewFactory.createButton("生成", action: Selector("genNumber"), sender: self)
self.view.addSubview(genBtn) //布局约束
let margins = self.view.layoutMarginsGuide
//
clrBtn.trailingAnchor.constraintEqualToAnchor(margins.centerXAnchor, constant: -).active = true
clrBtn.bottomAnchor.constraintEqualToAnchor(margins.bottomAnchor, constant:-).active=true
//
genBtn.leadingAnchor.constraintEqualToAnchor(margins.centerXAnchor, constant: ).active = true
genBtn.bottomAnchor.constraintEqualToAnchor(margins.bottomAnchor, constant:-).active=true
//
} //---------------------------游戏逻辑实现------------------------- //游戏面板重绘
func resetGameMap(){
UserModel.sharedInstance().saveBestScore(self.scoreView.score)
for view in self.view.subviews{
view.removeFromSuperview()
}
backgrounds.removeAll()
tiles.removeAll()
gameModel.clearAll()
gameModel.dimension=self.dimension
setupScoreLables()
setupGameMap()
setupButton()
genNumber() } // 清除所有数字,重置按钮的响应方法
func clearNumber(){
gameModel.clearAll()
for (_,tile) in tiles{
tile.removeFromSuperview()
}
tiles.removeAll() } //生成新的数字,生成按钮的响应方法
func genNumber(){
//// 随机产生数字2和4,几率为1:4
// let randv=Int(arc4random_uniform(5))
// var seed:Int = 2
// if randv==1 {
// seed = 4
// } let col=Int(arc4random_uniform(UInt32(dimension)))
let row=Int(arc4random_uniform(UInt32(dimension))) if gameModel.isFull(){
print("位置已经满了")
return
} if gameModel.tiles[row, col]> {
genNumber()
return
} let seed= //原书的程序,按照一定的几率比来生成2或4。此处改成一直生成2.
gameModel.setPosition(row, col: col, value: seed)
insertTile((row,col), value: seed) // 生成数字后,判断一下游戏是否结束
checkGameOver()
} //插入一个数字方块
func insertTile(pos:(Int,Int),value:Int){
let (row,col)=pos let x= + CGFloat(col) * (self.width+self.padding)
let y= + CGFloat(row) * (self.width+self.padding) let tile=TileView(pos:CGPointMake(x,y),width:self.width,value:value) self.view.addSubview(tile)
self.view.bringSubviewToFront(tile) let index = NSIndexPath(forRow: row, inSection: col) tiles[index] = tile //布局约束
var centerXConstant:CGFloat
var centerYConstant:CGFloat
if self.dimension% == {
centerXConstant = (self.padding+self.width) * CGFloat(col-self.dimension/)
centerYConstant = (self.padding+self.width) * CGFloat(row-self.dimension/)
}else{
centerXConstant = (self.padding+self.width) * CGFloat(Double(col-self.dimension/)+0.5)
centerYConstant = (self.padding+self.width) * CGFloat(Double(row-self.dimension/)+0.5)
}
let margins=self.view.layoutMarginsGuide
tile.centerXAnchor.constraintEqualToAnchor(margins.centerXAnchor, constant: centerXConstant).active=true
tile.centerYAnchor.constraintEqualToAnchor(margins.centerYAnchor, constant: centerYConstant).active=true
//原书中的动画效果
// tile.layer.setAffineTransform(CGAffineTransformMakeScale(0.1, 0.1)) // UIView.animateWithDuration(0.5, delay: 0.1, options: UIViewAnimationOptions.TransitionNone, animations: { () -> Void in
// tile.layer.setAffineTransform(CGAffineTransformMakeRotation(90))
// }, completion: { (Bool) -> Void in
// UIView.animateWithDuration(0.5, animations: { () -> Void in
// tile.layer.setAffineTransform(CGAffineTransformIdentity)
// })
// }) // tile.alpha=0
// UIView.animateWithDuration(0.5, delay: 0.01, options: UIViewAnimationOptions.CurveEaseInOut, animations: { () -> Void in
// }) { (Bool) -> Void in
// UIView.animateWithDuration(1, animations: { () -> Void in
// tile.alpha = 1
// })
// } // UIView.beginAnimations("animation", context: nil)
// UIView.setAnimationDuration(2)
// UIView.setAnimationCurve(UIViewAnimationCurve.EaseInOut)
// UIView.setAnimationTransition(UIViewAnimationTransition.FlipFromLeft, forView: self.view, cache: false)
// UIView.commitAnimations()
} //移除一个数字方块
func clearTile(row:Int,col:Int){ let index=NSIndexPath(forRow: row, inSection: col)
let tile=tiles[index]!
tile.removeFromSuperview()
tiles.removeValueForKey(index) } //添加滑动的手势识别处理
func setupSwipeGestures(){
let upSwipe=UISwipeGestureRecognizer(target: self, action: Selector("swipeUp"))
upSwipe.numberOfTouchesRequired=
upSwipe.direction=UISwipeGestureRecognizerDirection.Up
self.view.addGestureRecognizer(upSwipe) let downSwipe=UISwipeGestureRecognizer(target: self, action: Selector("swipeDown"))
downSwipe.numberOfTouchesRequired=
downSwipe.direction=UISwipeGestureRecognizerDirection.Down
self.view.addGestureRecognizer(downSwipe) let leftSwipe=UISwipeGestureRecognizer(target: self, action: Selector("swipeLeft"))
leftSwipe.numberOfTouchesRequired=
leftSwipe.direction=UISwipeGestureRecognizerDirection.Left
self.view.addGestureRecognizer(leftSwipe) let rightSwipe=UISwipeGestureRecognizer(target: self, action: Selector("swipeRight"))
rightSwipe.numberOfTouchesRequired=
rightSwipe.direction=UISwipeGestureRecognizerDirection.Right
self.view.addGestureRecognizer(rightSwipe) } //向上滑动
func swipeUp(){
tileVals = gameModel.tiles func merge(row row:Int,col:Int){
if row == self.dimension-{
return
} for i in (row+)..<self.dimension {
let valNew = tileVals[i,col]
if valNew> {
let val = tileVals[row, col] if val == {
tileVals[row, col] = valNew
tileVals[i, col] =
merge(row: row, col: col)
}else if val == valNew{
tileVals[row, col] = valNew<<
tileVals[i, col] =
if row== {
merge(row: row, col: col)
}else{
merge(row: row-, col: col)
}
// 若产生合并,则加分
changeScore(valNew)
}else{
merge(row: row+, col: col)
} break
}
}
} for col in ..<self.dimension{
merge(row: , col: col)
} // 如果合并后的结果与原来相同,则不做任何操作
if tileVals.isEqualTo(gameModel.tiles){
return
}
// 显示合并后的结果,并产生新的数字
refresh()
genNumber()
} //向下滑动 func swipeDown(){
tileVals = gameModel.tiles func merge(row row:Int,col:Int){
if row == {
return
} for var i=row-;i>=;i-- {
let valNew=tileVals[i,col]
if valNew> {
let val=tileVals[row,col] if val == {
tileVals[row, col] = valNew
tileVals[i, col] =
merge(row: row, col: col)
}else if val == valNew{
tileVals[row, col] = valNew<<
tileVals[i, col] = if row==self.dimension- {
merge(row: row, col: col)
}else{
merge(row: row+, col: col)
}
// 若产生合并,则加分
changeScore(valNew)
}else{
merge(row: row-, col: col)
} break
}
}
} for col in ..<self.dimension{
merge(row: self.dimension-, col: col)
}
// 如果合并后的结果与原来相同,则不做任何操作
if tileVals.isEqualTo(gameModel.tiles){
return
} refresh()
genNumber()
} //向左滑动
func swipeLeft(){
tileVals = gameModel.tiles
func merge(row row:Int,col:Int){
if col == self.dimension-{
return
} for i in (col+)..<self.dimension {
let valNew=tileVals[row,i]
if valNew> {
let val=tileVals[row,col] if val == {
tileVals[row, col] = valNew
tileVals[row, i] =
merge(row: row, col: col)
}else if val == valNew{
tileVals[row, col] = valNew<<
tileVals[row, i] =
if col== {
merge(row: row, col: col)
}else{
merge(row: row, col: col-)
}
// 若产生合并,则加分
changeScore(valNew)
}else{
merge(row: row, col: col+)
} break
}
}
} for row in ..<self.dimension{
merge(row: row, col: )
} // 如果合并后的结果与原来相同,则不做任何操作
if tileVals.isEqualTo(gameModel.tiles){
return
}
refresh()
genNumber()
} //向右滑动
func swipeRight(){
tileVals = gameModel.tiles
func merge(row row:Int,col:Int){
if col == {
return
} for var i=col-;i>=;i-- {
let valNew=tileVals[row, i]
if valNew> {
let val=tileVals[row, col] if val == {
tileVals[row, col] = valNew
tileVals[row, i] =
merge(row: row, col: col)
}else if val == valNew{
tileVals[row, col] = valNew<<
tileVals[row, i] =
if col==self.dimension- {
merge(row: row, col: col)
}else{
merge(row: row, col: col+)
}
// 若产生合并,则加分
changeScore(valNew)
}else{
merge(row: row, col: col-)
} break
}
}
} for row in ..<self.dimension{
merge(row: row, col: self.dimension-)
} // 如果合并后的结果与原来相同,则不做任何操作
if tileVals.isEqualTo(gameModel.tiles){
return
}
refresh()
genNumber() } //刷新页面
func refresh(){
for i in ..<self.dimension{
for j in ..<self.dimension{
let val = gameModel.tiles[i,j]
let valNew = tileVals[i,j]
if valNew != val{
gameModel.setPosition(i, col: j, value: valNew)
if valNew>{
if val>{
clearTile(i, col: j)
}
insertTile((i,j) , value: valNew) }else{
clearTile(i, col: j)
}
} }
}
} //加分
func changeScore(baseNum:Int){
self.scoreView.score += baseNum * } //检查游戏是否结束
func checkGameOver(){
if self.gameModel.isFull(){
for row in ..<self.dimension{
for col in ..<self.dimension{
let val = gameModel.tiles[row,col]
let left:Int? = (col-)>= ? gameModel.tiles[row,col-] : nil
let right:Int? = (col+)<self.dimension ? gameModel.tiles[row,col+] : nil
let up:Int? = (row-)>= ? gameModel.tiles[row-,col] : nil
let down:Int? = (row+)<self.dimension ? gameModel.tiles[row+,col] : nil
if (val==left) || (val==right) || (val==up) || (val==down){
return
} }
} let alerController = UIAlertController(title: "游戏结束", message: "本轮游戏结束,重新开始吧!", preferredStyle: UIAlertControllerStyle.Alert)
alerController.addAction(UIAlertAction(title: "Ready Go!", style: UIAlertActionStyle.Default, handler: {
action in
self.resetGameMap()
}))
self.presentViewController(alerController, animated: true, completion: nil)
return
}
} }
Swift实战之2048小游戏的更多相关文章
- jQuery实践-网页版2048小游戏
▓▓▓▓▓▓ 大致介绍 看了一个实现网页版2048小游戏的视频,觉得能做出自己以前喜欢玩的小游戏很有意思便自己动手试了试,真正的验证了这句话-不要以为你以为的就是你以为的,看视频时觉得看懂了,会写了, ...
- C# 开发2048小游戏
这应该是几个月前,闲的手痒,敲了一上午代码搞出来的,随之就把它丢弃了,当时让别人玩过,提过几条更改建议,但是时至今日,我也没有进行过优化和更改(本人只会作案,不会收场,嘎嘎),下面的建议要给代码爱好的 ...
- 如何在CentOS上安装一个2048小游戏
如何在centos上安装一个2048小游戏 最近在学习CentOS系统,就琢磨着玩点什么,然后我看到有人在玩2048小游戏,所有我就在想,为啥不装一个2048小游戏搞一下嘞,于是乎,我就开始工作啦 由 ...
- js、jQuery实现2048小游戏
2048小游戏 一.游戏简介: 2048是一款休闲益智类的数字叠加小游戏 二. 游戏玩法: 在4*4的16宫格中,您可以选择上.下.左.右四个方向进行操作,数字会按方向移动,相邻的两个数字相同就会合 ...
- 用js实现2048小游戏
用js实现2048小游戏 笔记仓库:https://github.com/nnngu/LearningNotes 1.游戏简介 2048是一款休闲益智类的数字叠加小游戏.(文末给出源代码和演示地址) ...
- 2048小游戏代码解析 C语言版
2048小游戏,也算是风靡一时的益智游戏.其背后实现的逻辑比较简单,代码量不算多,而且趣味性强,适合作为有语言基础的童鞋来加强编程训练.本篇分析2048小游戏的C语言实现代码. 前言 游戏截图: 游 ...
- Docker从0开始之部署一套2048小游戏
本文记录一下在docker部署一套2048小游戏的过程,在娱乐中熟悉docker的应用部署.docker 安装不在本文讲述之中,参考我的其它博客. 1.获取image镜像. 方法一:daocloud. ...
- 使用JS实现2048小游戏
JS实现2048小游戏源码 效果图: 代码如下,复制即可使用: (适用浏览器:360.FireFox.Chrome.Opera.傲游.搜狗.世界之窗. 不支持Safari.IE8及以下浏览器.) &l ...
- 2048小游戏4X4C语言
*/ #include<stdio.h> #include<stdlib.h> #include<conio.h> #include<time.h> v ...
随机推荐
- 数据结构作业——order(二叉树遍历)
order Description 给出一棵二叉树的中序遍历和每个节点的父节点,求这棵二叉树的先序和后 序遍历. Input 输入第一行为一个正整数 n 表示二叉树的节点数目, 节点编号从 1 到 n ...
- CF 468A 24 Game
题目链接: 传送门 24 Game time limit per test:1 second memory limit per test:256 megabytes Description L ...
- 【Alpha版本】冲刺-Day8
队伍:606notconnected 会议时间:11月16日 会议总结 张斯巍(433) 今天安排:回收站界面设计 完成度:90% 明天计划:关注界面设计 遇到的问题:无 感想:有时候自己设计的队友说 ...
- webservice理解
什么是webservice? 1.基于web的一种服务,webservice分为服务器端server和客户端client. server端会会提供一些资源供客户端的应用来访问(获取所需要的数据) 2. ...
- su su- sudo的区别
linux su命令参数及用法详解(linux切换用户命令) su的作用是变更为其它使用者的身份,超级用户除外,需要键入该使用者的密码 linux su 命令 建议大家切换用户的时候 使用 su ...
- ubuntu server设置时区和更新时间
ubuntu server设置时区和更新时间 今天测试时,发现时间不对,查了一下时区: data -R 结果时区是:+0000 我需要的是东八区,这儿显示不是,所以需要设置一个时区 一.运行 ...
- JavaScript学习笔记——BOM_window子对象_History、Location、Screnn对象
javascript-History.Location.Screnn对象实例讲解 一.history对象 包含浏览器访问过的url 1.属性 length 返回浏览器历史记录的数量 alert(his ...
- VC----Class Style类风格和窗口风格
CS_BYTEALIGNCLIENT:以字节边界来对齐窗口客户区,这个风格会影响 窗口 的宽度和水平位置.实际上没有看到效果. CS_BYTEALIGNWINDOW:以字节边界来对齐窗口,这个风格会影 ...
- CPU绑定操作
使用virsh vcpuinfp命令查看虚拟机VCPU和物理CPU的对应关系 [root@svn ~]# virsh vcpuinfo 16 VCPU: 0 CPU: 3 状态: running CP ...
- 第五章:创建DbContext
一.DbContext介绍 DbContext是Entity Framework(EF)操作数据库的上下文网关(接口).我们对数据库的所有操作都是通过DbContext完成的.下面我们将讨论在code ...