美文网首页iOS零碎知识iOS学习资料整理+学习方法公开课
斯坦福大学iOS开发公开课总结(三) :纸牌配对游戏Demo

斯坦福大学iOS开发公开课总结(三) :纸牌配对游戏Demo

作者: J_Knight_ | 来源:发表于2016-06-26 17:41 被阅读1629次

    本节课知识点内容不多,主要是延续了上一节课翻单张纸牌的游戏(详情请见:斯坦福大学iOS开发公开课总结(二) :翻纸牌Demo),将一张纸牌扩展到一个多张纸牌并进行配对和打分的小游戏。

    本节课的内容虽然简单,但是十分重要,讲师强调了MVC的设计原则并实际运用到了代码中,本文就Demo的具体代码来讲解本节课提到的知识点。

    设计需求


    • 显示多张纸牌,点击任意一张牌可以翻牌。
    • 两张牌都显示正面后可以进行配对:
      • 花色匹配得1分;数字匹配得4分,匹配后,两张牌切换为不可点击状态(置灰)。
      • 都不匹配扣2分。
      • 每次翻牌都减一分。
    • 每次翻牌都要更新分数。

    效果图:


    左:初始界面 ;右:游戏中界面

    重要代码段与知识点


    模型类:CardMatchingGame

    1. 在公共接口设置只读属性

    
    //CardMatchingGame.h
    @property (nonatomic, readonly) NSInteger score;
    
    //CardMatchingGame.m
    @property (nonatomic, readwrite) NSInteger score;
    
    

    在.h文件中将分数属性设置为只读,并在.m文件中设定该属性为读写,以便在内部计算。

    原因:不希望其他类更改此属性,只能获取该属性。通俗一点地说:“你们就拿我给你算好的分数就好了,你们是不能更改它的!”

    2. 指定初始化器:Designated initializer

    
    - (instancetype)initWithCardCount:(NSUInteger)count usingDeck:(Deck *)deck
    {
        self = [super init];
        
        if (self) {
            
            for (int i = 0; i < count; i++) {
                
                Card *card = [deck drawRandomCard];
                
                if (card) {
                   
                    [self.cards addObject:card];
                    
                }else{
                    
                    self = nil;
                    break;
                }
            }
        }
        return self;
    }
    
    

    有些时候,我们需要在类实例化的时候就要求对象持有某些数据,这就需要设计指定初始化器,因为原始的初始化方法-(instancetype)init方法是无法让实例对象持有非零数据的(初始化后,基本数据类型属性=0;对象属性=nil)。

    在这段代码里,该模型类通过数量count和一堆纸牌deck中拿到了自己持有的数组self.cards

    举个🌰 :想要从一个有52张牌的堆里抽取了12张牌来作为自己的一堆纸牌的话,就要设置Deck为具有52张牌的数组;而设置count为12即可。

    3. 设定常量

    #define MISMATCH_PENALTY 2 //简单的替换,不具有数据类型
    
    static const int MISMATCH_PENALTY = 2; //非简单替换,具有数据类型
    

    控制器类:CardMathcingGameViewController


    1. 接收来自View的点击事件并更新UI

    
    /**
     *  接收用户的点击事件
     *
     *  @param sender 点击的按钮对象
     */
    - (IBAction)touchCardButton:(UIButton *)sender {
    
        //1. 找到界面中所点击的按钮index
        NSInteger cardIndex = [self.cardButtons indexOfObject:sender];
        
        //2. 找到模型中相同index的纸牌数据,并判断是否匹配,计算分数
        [self.game chooseCardAtIndex: cardIndex];
        
        //3. 更新UI
        [self updateUI];
    }
    
    /**
     *  更新UI
     */
    - (void)updateUI
    {
        //1. 更新view上所有牌面
        for (UIButton *cardButton in self.cardButtons) {
            
            //1.1 找到界面中的一张纸牌(按照枚举的顺序)
            NSInteger cardIndex = [self.cardButtons indexOfObject:cardButton];
            
            //1.2 找到模型中对应的纸牌数据
            Card *card  = [self.game cardAtIndex:cardIndex];
            
            //1.3 根据数据更新纸牌的UI和可点击性
            [cardButton setTitle:[self titleForCard:card] forState:UIControlStateNormal];
            [cardButton setBackgroundImage: [self backgroundImageForCard:card] forState:UIControlStateNormal];
             cardButton.enabled = !card.isMatched;        
        }
        
        //2. 更新分数
        self.scoreLabel.text = [NSString stringWithFormat:@"Score: %ld", (long)self.game.score];
    }
    

    在这里,我们可以很容易看到MVC的工作流程:

    1. 在View里发生了点击事件并通知给了Controller。
    2. Controller告诉Model发生了点击。
    3. Model根据点击事件更新自己,然后将更新后的自己告诉Controller。
    4. Controller根据更新后的模型去更新UI。

    这里笔者有一张自己画的图,略逗逼,掩面贴出,独乐乐不如众乐乐~

    MVC流程图.png

    零散知识点


    1. 在数组里传入其包含的对象返回其所在序号

    NSInteger cardIndex = [self.cardButtons indexOfObject:cardButton];
    

    2. 在数组中找到第一个元素

    //应该使用:
    PlayingCard *otherCard = [otherCards firstObject];
    
    //不应该使用:
    PlayingCard *otherCard = otherCards[0];
    
    //不应该使用:
    PlayingCard *otherCard = [otherCards objectAtIndex:0];
    

    应该使用第一种情况。
    因为如果数组为空,那么如果使用第一种情况会返回nil,而向nil发送消息是不会造成崩溃的。
    但是如果使用后两种方法,一旦数组为空,就会立刻造成程序崩溃!而且同样适用与取数组的最后一个元素的情况。
    在数组中找到最后一个元素:

    PlayingCard *otherCard = [otherCards lastObject];
    

    3. 在数组中是否包含某元素

    if ([ [PlayingCard ValidSuits] containsObject:suit]) {
            _suit = suit;
    }
    

    containsObject:是NSArray的方法,返回布尔值,用来判断是否包含某个元素。

    4. JPG格式图片的读取

    - (UIImage *)backgroundImageForCard: (Card *)card
    {
        //默认是png,如果是jpg需要加上.jpg的后缀
        return [UIImage imageNamed:card.isChosen? @"cardFront.jpg":@"CardBack.png"];
    }
    

    如果使用imageNamed:方法,仅传入jpg格式的文件名是无法显示出图片的,应该讲后缀.jpg拼接后传入才可以哦~而相同情况下,若要显示png格式的图片的话是不需要另外加后缀的。

    最后的话:


    如果哪位小伙伴想拿到此Demo的代码请不要客气,在评论里留言即可。
    而且十分欢迎给笔者的代码和文笔抛出宝贵的意见和建议~

    本文为笔者原创,如需转载,请事先与笔者交涉~

    2016.7.12日更新:


    笔者已经把目前为止整理的所有Demo(第二课到第十课)放入到了我的GitHub仓库里。分为英文注释版和中文注释版(英文注释要少一点,嘿嘿)想要的小伙伴可以果断下载~ 如果有不知道怎么下载的小伙伴请联系我~

    本文已在版权印备案,如需转载请访问版权印。48422928

    获取授权

    相关文章

      网友评论

      • 樊二哈:翻纸牌的那个游戏,我想加一个重新开始游戏的按钮,就是当游戏结束或者游戏进行不下去的时候重新开始游戏应该加在哪啊
        J_Knight_:@樊呵呵 因为我没加啊 教程里没这个需求吧我记得
        樊二哈:@J_Knight 我找不到恢复初始状态的代码啊,
        J_Knight_:@樊呵呵 上下左右四个角落随便哪里都可以的 这个我没想到过 恢复初始状态就行
      • 樊二哈:求完整demo
        J_Knight_:@樊呵呵 ?怎么会 很教程的一样的啊 你看看序号
        樊二哈:@J_Knight 但是我找不到完成的游戏运行demo,我都打开了,不是游戏啊
        J_Knight_:@樊呵呵 文章里Github里面有 所有系列的都有
      • dobbinyang: 505327695@qq.com 谢谢 好文章
      • 6988cf2af3a0:第三节课我觉得Paul的建模思路比较诡异。就是那个-(void)chooseCardAtIndex:(NSUInteger)index 方法的的业务逻辑流程。个人感觉Paul的做法很不好。
        首先是概念的问题:matching card应该是在chosenCards中完成的。而不应该是在遍历一遍全局牌堆。
        再次效率也低下:每次都遍历全局,如果不是12牌,而是100牌呢?
        最后就是不能一般化解决N-cards matching。
        自负的大撸sir:@J_Knight 选牌的逻辑相当乱...
        J_Knight_:@justindigix 是的 搜索所有牌这个我也挺不赞同的。而且选牌 这个逻辑也比较混乱。不过课程准备可能以语法为主吧 设计上可能欠考虑
      • 安小贤:跪求大神发一份demo啊,拜托了,真心感谢 1491024705@qq.com :pray: :pray:
        J_Knight_:@安小贤 地址:https://github.com/Shijie0111/Stanford_iOS_Lecture_DemoBundle
        J_Knight_:@安小贤 请去我的github账号里找,目前跟新到地14课的Demo了
      • 代码:求demo15890171571@163.com
        代码:@代码 嗯,好的,谢谢
        J_Knight_:@代码 请到我的GitHub下载 可以下载到全部的~
      • 江阴帮:demo来一份呗,346127275@qq.com,多谢
        J_Knight_:@ailulee 请到我的GitHub下载 可以下载到全部的哦~
      • Hello_kid:yimao009@qq.com。。求demo
        学习
        J_Knight_:@yimao009 请到我的GitHub下载 可以下载到全部的~
      • ZYCoderr:1569207102@qq.com。 感谢 :kissing_heart:
        J_Knight_:@ZYCoderr 不客气~
      • 5627dc3aab30:861071918@qq.com 。求demo学习 ,不胜感谢
        5627dc3aab30:@Hero_SJ 谢谢
        J_Knight_:@mall浓 已发
      • seky:1021574475@qq.com 谢谢啦 :blush:
        J_Knight_:@seky 不客气
        J_Knight_:@seky 已发~
      • 0cfed2412e8e:334230215@qq.com谢谢
        J_Knight_:@dobbinyang 在文章结尾的guthub链接里有呀
        dobbinyang:505327695@qq.com 谢谢 好文章
        J_Knight_:@Miu_C 已发
      • nickName0:跪求分享。407650611@qq.com Thank You!!!
        J_Knight_:@ZF00_Fly0 已发~
      • 咖啡bu加糖:1042657187@qq.com 谢谢
        J_Knight_:@咖啡bu加糖 已发~
      • 槑头脑:q872458917@163.com 感谢:pray:
        J_Knight_:@七州小槑 已发~
      • Mines李Hysteria:399827263@qq.com也给我来一份,谢谢
        J_Knight_:@Mines李Hysteria 已发~
      • 陈弟CD:求demo,万分感谢。 :pray: 625554343@qq.com
        陈弟CD:@Hero_SJ 多谢
        J_Knight_:@47乖_摸摸头 已发~
      • 萧城x:老司机带带我
        J_Knight_:@低调做事 已发~
        萧城x:asd3306642@qq.com 求种子
        J_Knight_:@低调做事 可以随时问我问题,可以关注我的博客更新~ 当然还有更多大牛的呢
      • 苜蓿鬼仙:求发一份Demo吧,302926124@qq.com
        J_Knight_:@苜蓿鬼仙 已发~
      • anyurchao:求发个Demo 995684946@qq.com 非常感谢
        J_Knight_:@anyurchao 已发~
      • 小可蛮:非常感谢,291741280@qq.com
        小可蛮:@Hero_SJ 多谢
        J_Knight_:@小可蛮 已发
      • 古流风:谢了,gujianchao335@163.com
        古流风:谢了
        J_Knight_:@走刀口的人 已发~
      • 放牛的星星:感谢分享,新手上路,也发个Demo给我吧!谢谢!grnwich @163.com
        J_Knight_:@波虎波 不客气啊 多交流学习哈
        257f312dccb8:@Hero_SJ 谢谢!
        J_Knight_:@放牛的星星 已发~
      • wg689:596201463@qq.com 谢谢
        J_Knight_:@haojingxue_iOS 已发~
      • 257f312dccb8: 感谢 分享! 麻烦您将demo也分享让俺学习一下呗! wb911025@163.com 万分感谢!
        J_Knight_:@波虎波 已发~
      • 小马哥亿天洋:感谢分享,Demo发下吧,谢谢! sukerm@163.com
        小马哥亿天洋:@Hero_SJ 感谢:pray:
        J_Knight_:@小马哥亿天洋 已发
        J_Knight_:@小马哥亿天洋 好的 待俺吃完午饭回来给你发

      本文标题:斯坦福大学iOS开发公开课总结(三) :纸牌配对游戏Demo

      本文链接:https://www.haomeiwen.com/subject/udaadttx.html