(3/18)重學Standford_iOS7開發_Objective-C_課程筆記,iphone4升級ios7
第三課:
本節課主要是遊戲實現的demo,因此我將把課程中簡單的幾個編程技巧提取出來,重點介紹如何自己實現作業中的要求。
紙牌遊戲實現:
①遊戲的進行是模型的一部分(理解什麼是模型:Model = What your application is (but not how it is displayed) )UI無關。
②編程技巧:建立一個新類時首先考慮公用部分(API),即別人如何使用這個類,再考慮其細節實現,以此來驅動整個設計。
③關於屬性中使用NSInteger,NSUInteger,int,unsigned int只存在風格問題,只需要保持風格統一。
關於 NSInteger 與 NSUInteger 的官方定義:
1 #if __LP64__ || (TARGET_OS_EMBEDDED && !TARGET_OS_IPHONE) || TARGET_OS_WIN32 || NS_BUILD_32_LIKE_642 typedef long NSInteger;3 typedef unsigned long NSUInteger;4 #else5 typedef int NSInteger;6 typedef unsigned int NSUInteger;7 #endif
④關於屬性唯讀問題:公有API中可以設定屬性為唯讀,實現代碼中可以再次聲明屬性為可讀寫。屬性聲明時除非指定,否則預設為讀寫屬性。
例如:
1 @interface example : NSObject2 3 @property (nonatomic,readonly) Objectype number;4 5 @end
1 @interface example() 2 3 @property (nonatomic,readwrite) Objectype number; 4 5 @end 6 7 8 @implementation NUNetWork 9 10 @end
⑤IBOutletConllection:多輸出口,必須為strong,因為NSArray儲存輸出口,UI由視圖指標指向,而NSArray必須指定為strong才能保持在堆中。
如:@property (strong, nonatomic) IBOutletCollection(UIButton) NSArray *cardButtons;
IBOutletCollection(UIButton)類似於IBOutlet或IBAction,編譯器會忽略。
NSArray中的UI順序未知(不可視為拖入的順序)
⑥作業
a.在模擬器上運行課程當前最後版本的Matchismo
b.添加一個button實現重新開始遊戲功能(包括UI重設,牌堆重設,分數重設等)
c.使用UISwitch或者UISegmentedControl 實現兩個card匹配與三個card匹配遊戲模式之間的轉換。(注意三個card匹配成功或失敗的得分要比兩個card更顯著,在三card匹配中兩個紙牌不會產生匹配結果)(可以考慮n張牌匹配)
d.遊戲開始後,c中控制遊戲的功能失效直到遊戲結束或重新開始。
e.加入lable提示遊戲進行狀態(如:“Matched J♥ J♠ for 4 points.” 或“6♦ J♣ don’t match! 2 point penalty!” 或“8♦”表示只選擇一張牌還沒有進行匹配等)(不要違反MVC規則)
f.將紙牌數量由12擴充到30(40*60)
g.不要改變之前的代碼,可以增加公有或私人API
作業解答:
最終實現效果如所示
1、加入如下輸出口:
1 @property (weak, nonatomic) IBOutlet UIButton *restartButton;2 3 @property (weak, nonatomic) IBOutlet UISegmentedControl *gameModelSelectSegmented;4 5 @property (weak, nonatomic) IBOutlet UILabel *gameModelLable;6 7 @property (weak, nonatomic) IBOutlet UITextField *matchModelTextFiled;8 9 @property (weak, nonatomic) IBOutlet UILabel *gameStateLable;
2、重新開始遊戲功能實現
考慮到重新啟動遊戲後還未確定遊戲模式,因此必須重寫不包含game屬性的初始化(game的setter會自動初始化game)
1 - (IBAction)touchRestartButton 2 3 { 4 //恢複預設值 5 self.gameModelSelectSegmented.enabled = YES; 6 self.matchModelTextFiled.enabled = YES; 7 self.matchModelTextFiled.enabled = YES; 8 self.gameModelSelectSegmented.selectedSegmentIndex = 0; 9 self.selfDefiningModel = 2;10 self.gameModelLable.text = [NSString stringWithFormat:@"game model:%d",_selfDefiningModel];11 self.matchModelTextFiled.text = nil;12 13 self.game = nil;14 [self updateUIWithNotCreateGame];15 }16 17 - (void) updateUIWithNotCreateGame18 {19 for (UIButton *cardButton in self.cardButtons)20 {21 [cardButton setTitle:@"" forState:UIControlStateNormal];22 [cardButton setBackgroundImage:[UIImage imageNamed:@"cardBack"] forState:UIControlStateNormal];23 cardButton.enabled = YES;24 }25 26 self.gameStateLable.text = @"State";27 self.scoreLable.text = @"Score:0";28 }
3、多種遊戲模式及遊戲狀態輸出的實現
//Viewcontroller加入使用者自訂遊戲模式
@property (nonatomic) NSUInteger selfDefiningModel;
選取器初始化(此處放在了viewDidLoad方法,有點超範圍,其中選取器的target-action即為MVC中的view同controller通訊的一種方法,selfDefiningModel為使用者可自行設定的遊戲模式,初始值為2)
還可以使用UISwitch,本文為了實現多紙牌匹配因此採用了UISegmentedControll,感興趣的朋友可以試試UISwitch實現。
1 [self.gameModelSelectSegmented addTarget:self action:@selector(segmentAction:) forControlEvents:UIControlEventValueChanged];//target-action2 3 _selfDefiningModel = 2;//default model
選取器回應程式法(action)
1 - (void) segmentAction:(UISegmentedControl *)Seg 2 { 3 if (self.gameModelSelectSegmented.selectedSegmentIndex == 2)//此時為自訂輸入 4 { 5 [self assertSelfDefiningModel:self.matchModelTextFiled.text];//檢測使用者輸入 6 } 7 else 8 { 9 self.selfDefiningModel = self.gameModelSelectSegmented.selectedSegmentIndex + 2;//SegmengtdControll選取器選項欄從0開始計數10 }11 12 self.gameModelLable.text = [NSString stringWithFormat:@"game model:%d",self.selfDefiningModel];13 }
自訂輸入檢測,設定為數字鍵台,只檢測輸入範圍(由於使用者輸入的任意性,需要考慮到各種可能的輸入情況,本文為了簡便,採用了數字鍵台,因此只需檢測數字輸入的合理性即可)
1 - (void) assertSelfDefiningModel:(NSString *)text 2 { 3 if ([self.matchModelTextFiled.text integerValue] < 2) 4 { 5 [[[UIAlertView alloc] initWithTitle:@"Wrong" message:@"game model at least 2" delegate:nil cancelButtonTitle:@"certain" otherButtonTitles:nil, nil] show];//匹配模式至少為2 6 self.matchModelTextFiled.text = @""; 7 self.gameModelSelectSegmented.selectedSegmentIndex = 0; 8 } 9 else if ([self.matchModelTextFiled.text integerValue] > 30)10 {11 [[[UIAlertView alloc] initWithTitle:@"Wrong" message:@"beyond card max number" delegate:nil cancelButtonTitle:@"certain" otherButtonTitles:nil, nil] show];//匹配紙牌模式不能超過介面最大紙牌數量12 self.matchModelTextFiled.text = @"";13 self.gameModelSelectSegmented.selectedSegmentIndex = 0;14 }15 else16 {17 self.selfDefiningModel = [self.matchModelTextFiled.text integerValue];//自訂輸入正確時儲存輸入結果18 }19 }
UITextFiled委託(即MVC中View與Controller的另一種通訊方式,略超範圍,不理解沒有關係,暫不做要求),目的實現點擊鍵盤的return按鈕收合鍵盤
1 - (BOOL) textFieldShouldReturn:(UITextField *)textField2 {3 [textField resignFirstResponder];4 return YES;5 }
4、Model部分的實現
PlayingCard重寫match:方法
重寫前(只實現了兩張牌的匹配):
1 - (int)match:(NSArray *)otherCards 2 { 3 int score = 0; 4 5 // two card match 6 if ([otherCards count] == 1) 7 { 8 PlayingCard *otherCard = [otherCards firstObject]; 9 if ([self.suit isEqualToString:otherCard.suit])10 {11 score = 1;12 }13 else if (self.rank == otherCard.rank)14 {15 score = 4;16 }17 } 18 return score;19 }
重寫後(適用於多紙牌匹配): 此處我簡單的實現了如下規則:任意兩張牌含有相同的屬性即認為此牌與數組中的牌匹配,讀者可以嘗試更複雜的規則,如所有的牌數字相等或者花色相等才能視為匹配等)
1 - (int)match:(NSArray *)otherCards 2 { 3 int score = 0; 4 5 // n card match ( at least two card have same element,we think they are matched ) 6 for (PlayingCard *otherCard in otherCards) 7 { 8 if ([self.suit isEqualToString:otherCard.suit]) 9 {10 score += 1;11 }12 else if (self.rank == otherCard.rank)13 {14 score += 4;15 }16 }17 18 return score;19 }
game公有API加入遊戲模式及狀態屬性
@property (nonatomic) NSUInteger gameModel;// >=2
@property (nonatomic,readonly) NSString *gameState;
實現檔案更改許可權
@property (nonatomic,readwrite) NSString *gameState;
重寫選擇紙牌方法邏輯:
此次作業最難的部分,整個遊戲的邏輯核心。首先保持兩張紙牌匹配的架構不變,引入新的陣列變數otherCards儲存需要匹配的紙牌,先用for迴圈遍曆檢測紙牌狀態,若符合要求則加入otherCards,遍曆完畢再檢測數組大小是否達到遊戲模式要求(注意n張牌匹配數組大小應為n-1,想想為什嗎?:)),若不符合則返回,若符合則開始進行匹配。數組大小小於遊戲模式要求時,此處應注意紙牌狀態的儲存位置與兩張牌匹配時不同,下方代碼注釋有詳細說明。匹配時的計分邏輯:在原有基礎上乘以遊戲模式數量,當匹配規則越難時匹配成功得分越高。並在匹配過程中儲存遊戲狀態到gameState。
1 - (void) chooseCardAtIndex:(NSUInteger)index 2 { 3 Card *card = [self cardAtIndex:index]; 4 if (!card.isMacthed) 5 { 6 if (card.isChosen) 7 { 8 card.chosen = NO; 9 }10 else11 {12 // match against other chosen cards13 14 NSMutableArray *otherCards = [NSMutableArray arrayWithCapacity:self.gameModel];15 for (Card *otherCard in self.cards)16 {17 if (otherCard.isChosen && !otherCard.isMacthed)18 {19 [otherCards addObject:otherCard];20 }21 }22 23 //不能放於for迴圈之前,否則會將本次被選擇的牌加入cards,不能放於下面的if之後,否則當if成立時返回,沒有將本次翻牌的cost記錄,且不能翻牌24 self.score -= COST_TO_CHOOSE * self.gameModel;25 card.chosen = YES;26 27 if ([otherCards count] < self.gameModel - 1)28 {29 self.gameState = [NSString stringWithFormat:@"State:%@",card.contents];30 return;31 }32 else33 {34 int matchScore = [card match:otherCards];35 36 if (matchScore)37 {38 NSMutableString *state = [NSMutableString stringWithFormat:@"State:%@ matched",card.contents];39 self.score += matchScore * MATCH_BOUNDS * self.gameModel;40 for (Card *otherCard in otherCards)41 {42 [state appendFormat:@" %@",otherCard.contents];43 otherCard.matched = YES;44 }45 [state appendFormat:@". Get %d score!",matchScore * MATCH_BOUNDS * self.gameModel];46 card.matched = YES;47 48 self.gameState = state;49 }50 else51 {52 NSMutableString *state = [NSMutableString stringWithFormat:@"State:%@ with",card.contents];53 self.score -= MISMATCH_PENALTY * self.gameModel;54 for (Card *otherCard in otherCards)55 {56 [state appendFormat:@" %@",otherCard.contents];57 otherCard.chosen = NO;58 }59 [state appendFormat:@" not matched! %d point penalty!",MISMATCH_PENALTY * self.gameModel];60 61 self.gameState = state;62 }63 }64 }65 }66 }
作業總結:雖然完成了作業的要求,但還有許多地方需要改進。如輸入情況檢測的30閾值為特定的數字,可以考慮改為[cardButtons count],遊戲的計分邏輯不夠完善,在遊戲匹配模式較大的情況下(貌似到5張牌匹配就出現了:p)容易出現極端的得分情況,大家可以下載原始碼玩玩試試。遊戲狀態的Lable字數過多時的顯示問題。restart按鈕的邏輯有沒有更好的實現方法,本文為了防止restart中的updateUI方法在game置為nil後馬上重新初始化nil(此時selfDefiningModel為初始值2)特意重寫了一個更新UI的方法,有沒有更輕便的方法(如更符合MVC一點)?這些問題都是拋磚引玉,我並沒有細想,希望大家積極思考交流,如有不對歡迎指正:)
作業源碼地址:https://github.com/NSLogMeng/Stanford_iOS7_Study_Machismo/commit/39819086ac1c0b7a38679b75307b1cf0a687a190(今後同一個項目每次作業我會把當次完成的commit連結發給大家)
------------------------------------------------我是後來補充的-----------------------------------------
剛剛在真機上玩發現個嚴重的問題。。。。數字鍵台沒有return鍵啊,數字鍵台沒有return鍵啊,數字鍵台沒有return鍵啊!!!模擬器直接在電腦的鍵盤上輸入的啊,根本沒有意識到_(:3 」∠)_
怒改之,添加了view響應事件,隱藏鍵盤,不過這部分源碼就不貼了,只是簡單地加了個方法,此部分已經嚴重超綱了。不過感興趣大家可以看看,github已經更新了。
最新版本:https://github.com/NSLogMeng/Stanford_iOS7_Study_Machismo/commit/117717b54bd39ffad9ea3e16bbf173ecb9c348c7
-----------------------------------------------------end-----------------------------------------------
課程視頻地址:網易公開課:http://open.163.com/movie/2014/1/H/U/M9H7S9F1H_M9H7VNFHU.html
或者iTunes U搜尋standford課程