終於有時間跟新了,兩週時間復(yu)習(xi)了5門考試累覺不愛。。。。。。html
--------------------------------------------------------------------------我是正文分割線---------------------------------------------------------------------------------------------git
第六課github
一、控制器多態性數組
這裏控制器多態性是指在控制器中使用繼承,經過繼承構造通用視圖控制器(ViewController),在具體的MVC中繼承通用視圖控制器實現具體功能(一般是父類中的抽象方法)安全
注意:objective-C並無abstract關鍵字,所以一般要經過註釋的方式說明,抽象方法須要在.h文件中聲明,通常實現文件中的抽象方法沒有具體功能,只返回nil。app
storyboard的多態控制器類須要在屬性檢查器中更改指定的類。dom
說明:經過繼承,父類中的鏈接也會被完整的繼承下來,如輸出口(Outlet)等。ide
demo地址:https://github.com/NSLogMeng/Stanford_iOS7_Study/commit/238167d6080df94e0383aceccab6d16fea290af2ui
二、多MVCatom
理解:多MVC基本組合方式,子MVC做爲父MVC的視圖(View)呈現。
(1)UINavigationController(以日曆應用爲例)
1) Navigation Bar
a.title 當前MVC的標題,與內容有關
b.navigationItem.rightBarButtonItems(ViewController屬性) 功能按鈕(注意與UIButton區別,UIBarButton的數組,便可以嵌入多個BarButton)
c.Back Button (通常由UINavigationController自動設定,默認爲上一MVC的title,長度較長時顯示爲back,亦可自行設置)
當點擊當前MVC的back時,當前MVC會被釋放,返回前一個MVC
2)toolbarItems (ViewController屬性)
底部出現(an NSArray of UIBarButtonItems )
3)rootViewController
指向一個MVC的controller,做爲根MVC
//UINavigationController經常使用方法 - (instancetype)initWithRootViewController:(UIViewController *)rootViewController; // 設置根視圖控制器 - (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated; //壓入新視圖 - (UIViewController *)popViewControllerAnimated:(BOOL)animated;//彈出當前視圖 //navigationBar @property(nonatomic,getter=isNavigationBarHidden) BOOL navigationBarHidden; - (void)setNavigationBarHidden:(BOOL)hidden animated:(BOOL)animated; //toolbar @property(nonatomic,getter=isToolbarHidden) BOOL toolbarHidden NS_AVAILABLE_IOS(3_0); - (void)setToolbarHidden:(BOOL)hidden animated:(BOOL)animated NS_AVAILABLE_IOS(3_0);
(2)segue(很是重要,MVC間切換的基礎)
push:一種以push,pop方式在UINavigationController上進行MVC切換的方式
identifier:用來表示push,方便在代碼中使用
1 //須要跳轉時的Action 2 - (IBAction)rentEquipment 3 { 4 if (self.snowTraversingTalent == Skiing) { 5 [self performSegueWithIdentifier:@「AskAboutSkis」 sender:self]; 6 } else { 7 [self performSegueWithIdentifier:@「AskAboutSnowboard」 sender:self]; 8 } 9 } 10 11 //爲各segue作跳轉前的準備 12 - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 13 if ([segue.identifier isEqualToString:@「DoSomething」]) { 14 if ([segue.destinationViewController isKindOfClass:[DoSomethingVC class]]) { 15 DoSomethingVC *doVC = (DoSomethingVC *)segue.destinationViewController; 16 doVC.neededInfo = ...; } 17 } 18 } 19 20 //segue跳轉判斷 21 - (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender { 22  if ([segue.identifier isEqualToString:@「DoAParticularThing」]) { 23 return [self canDoAParticularThing] ? YES : NO; 24 } 25 }
注意:segue跳轉的準備工做中並未設置好輸出口,所以不能直接賦值給新MVC的輸出口,MVC中須要設置API來接收數據。
(3)demo : Attributor Stats
Use a UINavigationController to show 「statistics」 on colors and outlining in Attributor.
demo地址:https://github.com/NSLogMeng/Stanford_iOS7_Study/commit/fba52faaefd72cb1d4e0ef0c9029092fd5749f63
(4)UITabBarController(以時鐘應用爲例)
//設置選項個數,通常不超過5個,超過5個時,第五個及其餘將集合在最後一欄 @property (nonatomic, strong) NSArray *viewControllers;
三、做業
要求:a.在Matchismo 的基礎上添加一個新的MVC做爲Set紙牌匹配遊戲。
b.新的Set紙牌遊戲與原紙牌遊戲相似(3張牌匹配模式)
c.原遊戲的功能保留,能夠適當刪減或更改UI(原要求是取消UISegmentedControll)
d.使用UITabBarController將兩個遊戲分開
e.Set紙牌的數目可能會與以前不一樣,選擇合適的UI尺寸
f.使用▲ ● ■ 以及NSAttributedString 來表示Set紙牌的三個屬性(形狀,顏色,陰影)
g.每一個遊戲必須展現當前的得分情況,並容許用戶重置
h.使用NSAttributedString來記錄並展現紙牌匹配情況
i.使用UINavigationController 添加新的MVC來詳細展現紙牌匹配狀況的歷史
做業解析:
最終實現效果以下圖:
實現過程:
針對上次做業的結果對紙牌遊戲再次進行抽象化並添加Set紙牌遊戲
抽象化結果:https://github.com/NSLogMeng/Stanford_iOS7_Study/commit/58afa7577cb339328819fccb5422aef27fd843ff
a.添加Set及Model的抽象化
SetCard
SetCard的匹配詳見做業要求中的規則,此處斷定匹配成功則score爲1,不然爲0(僅做爲斷定標記,也能夠根據本身喜愛自定義分數規則)
1 #import "Card.h" 2 3 @interface SetCard : Card 4 5 @property (strong,nonatomic) NSString *suit;// ▲ ● ■ 6 @property (strong,nonatomic) NSString *color;// red green purple 7 @property (nonatomic) BOOL shading; 8 9 + (NSArray *)validSuits; 10 + (NSArray *)validColors; 11 + (NSUInteger)shadingNumber; 12 13 @end
1 #import "SetCard.h" 2 3 @interface SetCard() 4 5 @end 6 7 @implementation SetCard 8 9 - (NSString *)contents 10 { 11 return nil; 12 } 13 14 - (int)match:(NSArray *)otherCards 15 { 16 int score = 0; 17 18 if ([otherCards count] == 2) 19 { 20 SetCard *firstCard = [otherCards firstObject]; 21 SetCard *secondCard = [otherCards lastObject]; 22 if ([self.suit isEqualToString:firstCard.suit] && [firstCard.suit isEqualToString:secondCard.suit]) 23 { 24 score += 1; 25 } 26 else if (![self.suit isEqualToString:firstCard.suit] && ![self.suit isEqualToString:secondCard.suit] && ![firstCard.suit isEqualToString:secondCard.suit]) 27 { 28 score += 1; 29 } 30 else 31 { 32 score -= 1; 33 } 34 35 if ([self.color isEqualToString:firstCard.color] && [firstCard.color isEqualToString:secondCard.color])//全相等 36 { 37 score += 1; 38 } 39 else if (![self.color isEqualToString:firstCard.color] && ![self.color isEqualToString:secondCard.color] && ![firstCard.color isEqualToString:secondCard.color])//全不相等 40 { 41 score += 1; 42 } 43 else 44 { 45 score -= 1; 46 } 47 48 if ((self.shading == firstCard.shading) && (firstCard.shading == secondCard.shading)) 49 { 50 score += 1; 51 } 52 else 53 { 54 score -= 1; 55 } 56 57 } 58 59 if (score == 3) 60 { 61 score = 1; 62 } 63 else 64 { 65 score = 0; 66 } 67 68 return score; 69 } 70 71 + (NSArray *)validSuits 72 { 73 return @[@"▲",@"▲▲",@"▲▲▲",@"●",@"●●",@"●●●",@"■",@"■ ■",@"■ ■ ■"]; 74 } 75 76 + (NSArray *)validColors 77 { 78 return @[@"red",@"green",@"purple"]; 79 } 80 81 82 + (NSUInteger)shadingNumber 83 { 84 return 2; 85 }
SetCardDeck
與PlayingCardDeck相似
1 #import "Deck.h" 2 3 @interface SetCardDeck : Deck 4 5 @end
1 #import "SetCardDeck.h" 2 #import "SetCard.h" 3 4 @implementation SetCardDeck 5 6 - (instancetype) init 7 { 8 self = [super init]; 9 if (self) 10 { 11 for (NSString *suit in [SetCard validSuits]) 12 { 13 for (NSString *color in [SetCard validColors]) 14 { 15 for (NSUInteger i = 0; i < [SetCard shadingNumber]; i++) 16 { 17 SetCard *card = [[SetCard alloc] init]; 18 card.suit = suit; 19 card.color = color; 20 card.shading = (i<1) ? true : false; 21 22 [self addCard:card]; 23 } 24 } 25 } 26 } 27 return self; 28 } 29 30 @end
CardMatchingGame的抽象化
上次做業中咱們的CardMatchingGame只針對PlayingCard的匹配規則及方法在SetCard中任然相似或適用,所以咱們抽象化CardMatchingGame,使其成爲PlayingCardMatchingGame與SetCardMatchingGame的超類。
首先咱們根據做業要求咱們添加gameStateHistory屬性來保存遊戲進行的歷史數據,因爲可能會出現多紙牌匹配,所以添加validOfOtherCards屬性來獲取其他紙牌的內容。由於gameState屬性最好使用只讀屬性,所以咱們在超類中去除此屬性而在子類中實現
1 #import <Foundation/Foundation.h> 2 #import "Deck.h" 3 #import "Card.h" 4 5 static const int MISMATCH_PENALTY = 2; 6 static const int MATCH_BOUNDS = 4; 7 static const int COST_TO_CHOOSE = 1; 8 9 @interface CardMatchingGame : NSObject 10 11 // designated initializer 12 - (instancetype) initWithCardCount:(NSUInteger)count 13 usingDeck:(Deck *)deck; 14 - (void) chooseCardAtIndex:(NSUInteger)index; 15 - (Card *) cardAtIndex:(NSUInteger)index; 16 17 - (NSString *)validOfOtherCards:(NSArray *)otherCards; 18 19 @property (nonatomic,readonly) NSString *validOfOtherCards; 20 @property (nonatomic,readonly) NSInteger score; 21 @property (nonatomic) NSUInteger gameModel;// >=2 22 @property (nonatomic,strong) NSMutableArray *gameStateHistory; 23 24 @end
1 #import "CardMatchingGame.h" 2 3 @interface CardMatchingGame() 4 5 @property (nonatomic,readwrite) NSString *validOfOtherCards; 6 @property (nonatomic,readwrite) NSInteger score; 7 @property (nonatomic,strong) NSMutableArray *cards;//of playingcard 8 9 @end 10 11 @implementation CardMatchingGame 12 13 - (NSMutableArray *)cards 14 { 15 if (!_cards) _cards = [[NSMutableArray alloc] init]; 16 return _cards; 17 } 18 19 - (NSMutableArray *)gameStateHistory 20 { 21 if (!_gameStateHistory) 22 { 23 _gameStateHistory = [[NSMutableArray alloc] init]; 24 } 25 return _gameStateHistory; 26 } 27 28 - (instancetype) initWithCardCount:(NSUInteger)count usingDeck:(Deck *)deck 29 { 30 self = [super init]; 31 if (self) 32 { 33 for (int i = 0; i < count; i++) 34 { 35 Card *card = [deck drawRandomCard]; 36 if (card) 37 { 38 [self.cards addObject:card]; 39 } 40 else 41 { 42 self = nil; 43 break; 44 } 45 } 46 } 47 48 return self; 49 } 50 51 - (Card *) cardAtIndex:(NSUInteger)index 52 { 53 return (index < [self.cards count]) ? self.cards[index] : nil; 54 } 55 56 - (void) chooseCardAtIndex:(NSUInteger)index 57 { 58 Card *card = [self cardAtIndex:index]; 59 if (!card.isMacthed) 60 { 61 if (card.isChosen) 62 { 63 card.chosen = NO; 64 } 65 else 66 { 67 // match against other chosen cards 68 NSMutableArray *otherCards = [NSMutableArray arrayWithCapacity:self.gameModel]; 69 70 for (Card *otherCard in self.cards) 71 { 72 if (otherCard.isChosen && !otherCard.isMacthed) 73 { 74 [otherCards addObject:otherCard]; 75 } 76 } 77 78 //不能放於for循環以前,不然會將本次被選擇的牌加入cards,不能放於下面的if以後,不然當if成立時返回,沒有將本次翻牌的cost記錄,且不能翻牌 79 self.score -= COST_TO_CHOOSE * self.gameModel; 80 card.chosen = YES; 81 82 if ([otherCards count] < self.gameModel - 1) 83 { 84 return; 85 } 86 else 87 { 88 self.validOfOtherCards = [self validOfOtherCards:otherCards]; 89 int matchScore = [card match:otherCards]; 90 91 if (matchScore) 92 { 93 self.score += matchScore * MATCH_BOUNDS * self.gameModel; 94 for (Card *otherCard in otherCards) 95 { 96 otherCard.matched = YES; 97 } 98 card.matched = YES; 99 } 100 else 101 { 102 self.score -= MISMATCH_PENALTY * self.gameModel; 103 for (Card *otherCard in otherCards) 104 { 105 otherCard.chosen = NO; 106 } 107 } 108 } 109 } 110 } 111 } 112 113 - (NSString *)validOfOtherCards:(NSArray *)otherCards 114 { 115 NSMutableString *string = [[NSMutableString alloc] init]; 116 for (Card *card in otherCards) 117 { 118 [string appendFormat:@"%@ ",card.contents]; 119 } 120 return string; 121 } 122 123 @end
子類化的PlayingCardMatchingGame
實現類gameState的記錄,並添加方法使其在紙牌匹配時記錄狀態及狀態歷史
1 #import "CardMatchingGame.h" 2 3 @interface PlayingCardMatchingGame : CardMatchingGame 4 5 @property (nonatomic,readonly) NSString *gameState; 6 7 @end
1 #import "PlayingCardMatchingGame.h" 2 3 @interface PlayingCardMatchingGame() 4 5 @property (nonatomic,readwrite) NSString *gameState; 6 7 @end 8 9 @implementation PlayingCardMatchingGame 10 11 - (void)chooseCardAtIndex:(NSUInteger)index 12 { 13 int forwardScore = self.score; 14 [super chooseCardAtIndex:index]; 15 [self updateGameState:forwardScore withIndexOfCard:index]; 16 } 17 18 - (void)updateGameState:(int)forwardScore withIndexOfCard:(int)index 19 { 20 if (self.score == forwardScore - self.gameModel * COST_TO_CHOOSE) 21 { 22 _gameState = [self cardAtIndex:index].contents; 23 } 24 else if (self.score < (forwardScore - self.gameModel * COST_TO_CHOOSE)) 25 { 26 _gameState = [NSString stringWithFormat:@"%@ with %@ not matched. %d point penalty!",[self cardAtIndex:index].contents,self.validOfOtherCards,forwardScore - self.score]; 27 [self.gameStateHistory addObject:_gameState]; 28 } 29 else if (self.score > (forwardScore - self.gameModel * COST_TO_CHOOSE)) 30 { 31 _gameState = [NSString stringWithFormat:@"%@ matched %@ . get %d score!",[self cardAtIndex:index].contents,self.validOfOtherCards,self.score - forwardScore]; 32 [self.gameStateHistory addObject:_gameState]; 33 } 34 } 35 36 @end
子類化的SetCardMatchingGame
與PlayingCardMatchingGame相似,注意在SetCard中並無content的數據,所以要重寫validOfOtherCard:方法,以保證正確獲取SetCard的內容
1 #import "CardMatchingGame.h" 2 3 @interface SetCardMatchingGame : CardMatchingGame 4 5 @property (nonatomic,readonly) NSString *gameState; 6 7 @end
1 #import "SetCardMatchingGame.h" 2 #import "SetCard.h" 3 4 @interface SetCardMatchingGame() 5 6 @property (nonatomic,readwrite) NSString *gameState; 7 8 @end 9 10 @implementation SetCardMatchingGame 11 12 - (void)chooseCardAtIndex:(NSUInteger)index 13 { 14 int forwardScore = self.score; 15 [super chooseCardAtIndex:index]; 16 [self updateGameState:forwardScore withIndexOfCard:index]; 17 } 18 19 - (NSString *)validOfOtherCards:(NSArray *)otherCards 20 { 21 NSMutableString *string = [[NSMutableString alloc] init]; 22 for (SetCard *card in otherCards) 23 { 24 [string appendString:card.suit]; 25 } 26 return string; 27 } 28 29 - (void)updateGameState:(int)forwardScore withIndexOfCard:(int)index 30 { 31 if (self.score == forwardScore - (self.gameModel * COST_TO_CHOOSE)) 32 { 33 _gameState = ((SetCard *)[self cardAtIndex:index]).suit; 34 } 35 else if (self.score < (forwardScore - (self.gameModel * COST_TO_CHOOSE))) 36 { 37 _gameState = [NSString stringWithFormat:@"%@ with %@ not matched. %d point penalty!",((SetCard *)[self cardAtIndex:index]).suit,self.validOfOtherCards,forwardScore - self.score]; 38 [self.gameStateHistory addObject:_gameState]; 39 } 40 else if (self.score > (forwardScore - (self.gameModel * COST_TO_CHOOSE))) 41 { 42 _gameState = [NSString stringWithFormat:@"%@ matched %@ . get %d score!",((SetCard *)[self cardAtIndex:index]).suit,self.validOfOtherCards,self.score - forwardScore]; 43 [self.gameStateHistory addObject:_gameState]; 44 } 45 } 46 47 @end
b.UI的更新
如圖:
因爲SetCard的遊戲規則,SetCard並不會被翻回到背面,所以要更改紙牌的牌面圖片
c.Controller的抽象化及子類化實現
上次做業中針對ViewController有不少只與PlayingCardMatchingGame有關的內容,這次將這些內容轉移到PlayingCardGameViewController中,對ViewController進行抽象化,而且也在子類SetCardGameViewController中實現SetCard匹配遊戲
ViewController
因爲許多有關UI的操做須要在子類中實現,所以將他們放到.h文件中,同時titleForCard:與backgroundImageForCard:方法也許要在SetCard子類中重寫(想一想爲何?),所以也放入.h文件中
1 #import <UIKit/UIKit.h> 2 #import "Deck.h" 3 4 @class CardMatchingGame; 5 6 @interface ViewController : UIViewController 7 8 //protected 9 //for subclasses 10 - (Deck *)createDeck; // abstract 11 12 @property (strong,nonatomic) CardMatchingGame *game; 13 @property (strong, nonatomic) IBOutletCollection(UIButton) NSArray *cardButtons; 14 @property (weak, nonatomic) IBOutlet UILabel *scoreLable; 15 16 - (NSUInteger)cardButtonsNumber; 17 - (void) updateUI; 18 - (NSString *)titleForCard:(Card *)card; 19 - (UIImage *)backgroundImageForCard:(Card *)card; 20 21 @end
1 #import "ViewController.h" 2 #import "CardMatchingGame.h" 3 4 @interface ViewController () 5 6 @end 7 8 @implementation ViewController 9 10 - (NSUInteger)cardButtonsNumber 11 { 12 return [_cardButtons count]; 13 } 14 15 - (Deck *)createDeck // abstract 16 { 17 return nil; 18 } 19 20 - (IBAction)touchCardButton:(UIButton *)sender 21 { 22 NSUInteger cardIndex = [self.cardButtons indexOfObject:sender]; 23 [self.game chooseCardAtIndex:cardIndex]; 24 [self updateUI]; 25 } 26 27 - (void) updateUI 28 { 29 for (UIButton *cardButton in self.cardButtons) 30 { 31 NSUInteger cardIndex = [self.cardButtons indexOfObject:cardButton]; 32 Card *card = [self.game cardAtIndex:cardIndex]; 33 [cardButton setTitle:[self titleForCard:card] forState:UIControlStateNormal]; 34 [cardButton setBackgroundImage:[self backgroundImageForCard:card] forState:UIControlStateNormal]; 35 cardButton.enabled = !card.isMacthed; 36 } 37 self.scoreLable.text = [NSString stringWithFormat:@"Score:%ld",(long)self.game.score]; 38 } 39 40 - (NSString *)titleForCard:(Card *)card 41 { 42 return card.isChosen ? card.contents : nil; 43 } 44 45 - (UIImage *)backgroundImageForCard:(Card *)card 46 { 47 return [UIImage imageNamed:card.isChosen ? @"cardFront" : @"cardBack"]; 48 } 49 50 @end
PlayingCardGameController
與上次做業基本相同,只不過變爲從更抽象的超類中繼承爲子類(一些只適用於PlayingCardGame的UI與方法在本類實現),而且添加了History功能
1 #import "ViewController.h" 2 3 @interface PlayingCardGameViewController : ViewController 4 5 @end
1 #import "PlayingCardGameViewController.h" 2 #import "PlayingCardDeck.h" 3 #import "PlayingCardMatchingGame.h" 4 #import "HistoryViewController.h" 5 6 @interface PlayingCardGameViewController () 7 8 @property (weak, nonatomic) IBOutlet UISegmentedControl *gameModelSelectSegmented; 9 @property (weak, nonatomic) IBOutlet UILabel *gameModelLable; 10 @property (weak, nonatomic) IBOutlet UITextField *matchModelTextFiled; 11 @property (weak, nonatomic) IBOutlet UILabel *gameStateLable; 12 @property (weak, nonatomic) IBOutlet UIButton *restartButton; 13 @property (nonatomic) NSUInteger selfDefiningModel; 14 15 @end 16 17 @implementation PlayingCardGameViewController 18 19 #define CORNER_FONT_STANDARD_HIGHT 180.0 20 #define CORNER_RADIUS 12.0 21 22 //讓界面變得更好看的魔法 23 - (CGFloat)cornerScaleFactor:(CGFloat)height {return height / CORNER_FONT_STANDARD_HIGHT;} 24 - (CGFloat)cornerRadius:(CGFloat)height {return CORNER_RADIUS * [self cornerScaleFactor:height];} 25 26 - (void)viewDidLoad 27 { 28 //又是個讓界面變得更好看的魔法 29 self.restartButton.layer.cornerRadius = [self cornerRadius:self.restartButton.bounds.size.height]; 30 self.gameModelSelectSegmented.layer.cornerRadius = [self cornerRadius:self.gameModelSelectSegmented.bounds.size.height * 3]; 31 32 [self.gameModelSelectSegmented addTarget:self action:@selector(segmentAction:) forControlEvents:UIControlEventValueChanged];//target-action 33 34 _selfDefiningModel = 2;//default model 35 } 36 37 - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender 38 { 39 if ([segue.identifier isEqualToString:@"show PlayingCardGameHistory"]) 40 { 41 if ([segue.destinationViewController isKindOfClass:[HistoryViewController class]]) 42 { 43 HistoryViewController *HVC = (HistoryViewController *)segue.destinationViewController; 44 HVC.history = self.game.gameStateHistory; 45 } 46 } 47 } 48 49 50 - (void) segmentAction:(UISegmentedControl *)Seg 51 { 52 if (self.gameModelSelectSegmented.selectedSegmentIndex == 2) 53 { 54 [self assertSelfDefiningModel:self.matchModelTextFiled.text]; 55 } 56 else 57 { 58 self.selfDefiningModel = self.gameModelSelectSegmented.selectedSegmentIndex + 2; 59 } 60 self.gameModelLable.text = [NSString stringWithFormat:@"game model:%lu",(unsigned long)self.selfDefiningModel]; 61 } 62 63 - (void) assertSelfDefiningModel:(NSString *)text 64 { 65 if ([self.matchModelTextFiled.text integerValue] < 2) 66 { 67 [[[UIAlertView alloc] initWithTitle:@"Wrong" message:@"game model at least 2" delegate:nil cancelButtonTitle:@"certain" otherButtonTitles:nil, nil] show]; 68 self.matchModelTextFiled.text = @""; 69 self.gameModelSelectSegmented.selectedSegmentIndex = 0; 70 } 71 else if ([self.matchModelTextFiled.text integerValue] > [self cardButtonsNumber]) 72 { 73 [[[UIAlertView alloc] initWithTitle:@"Wrong" message:@"beyond card max number" delegate:nil cancelButtonTitle:@"certain" otherButtonTitles:nil, nil] show]; 74 self.matchModelTextFiled.text = @""; 75 self.gameModelSelectSegmented.selectedSegmentIndex = 0; 76 } 77 else 78 { 79 self.selfDefiningModel = [self.matchModelTextFiled.text integerValue]; 80 } 81 } 82 83 - (Deck *)createDeck 84 { 85 return [[PlayingCardDeck alloc] init]; 86 } 87 88 - (IBAction)touchRestartButton 89 { 90 //恢復默認值 91 self.gameModelSelectSegmented.enabled = YES; 92 self.matchModelTextFiled.enabled = YES; 93 self.matchModelTextFiled.enabled = YES; 94 self.gameModelSelectSegmented.selectedSegmentIndex = 0; 95 self.selfDefiningModel = 2; 96 self.gameModelLable.text = [NSString stringWithFormat:@"game model:%d",_selfDefiningModel]; 97 self.matchModelTextFiled.text = nil; 98 99 self.game = nil; 100 [self updateUIWithNotCreateGame]; 101 } 102 103 - (IBAction)touchCardButton:(UIButton *)sender 104 { 105 //遊戲開始後禁用模式選擇功能 106 self.gameModelSelectSegmented.enabled = NO; 107 self.matchModelTextFiled.enabled = NO; 108 self.matchModelTextFiled.enabled = NO; 109 110 NSUInteger cardIndex = [self.cardButtons indexOfObject:sender]; 111 if(!self.game) 112 { 113 self.game = [[PlayingCardMatchingGame alloc] initWithCardCount:[self.cardButtons count] 114 usingDeck:[self createDeck]]; 115 self.game.gameModel = self.selfDefiningModel; 116 } 117 [self.game chooseCardAtIndex:cardIndex]; 118 [self updateUI]; 119 } 120 121 - (void) updateUIWithNotCreateGame 122 { 123 for (UIButton *cardButton in self.cardButtons) 124 { 125 [cardButton setTitle:@"" forState:UIControlStateNormal]; 126 [cardButton setBackgroundImage:[UIImage imageNamed:@"cardBack"] forState:UIControlStateNormal]; 127 cardButton.enabled = YES; 128 } 129 self.gameStateLable.text = @"State"; 130 self.scoreLable.text = @"Score:0"; 131 } 132 133 - (void)updateUI 134 { 135 [super updateUI]; 136 self.gameStateLable.text = ((PlayingCardMatchingGame *)self.game).gameState; 137 } 138 139 - (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 140 { 141 [self.view endEditing:YES]; 142 } 143 144 @end
SetCardGameViewController
本類實現了SetCard匹配遊戲,由於是UI層,所以使用了NSAttributeString來表示SetCard的內容,一樣也實現了History功能
注意:因爲SetCard牌面始終朝上,所以要在ViewDidLoad中初始化game
1 #import "ViewController.h" 2 3 @interface SetCardGameViewController : ViewController 4 5 @end
1 #import "SetCardGameViewController.h" 2 #import "SetCardMatchingGame.h" 3 #import "SetCardDeck.h" 4 #import "SetCard.h" 5 #import "HistoryViewController.h" 6 7 @interface SetCardGameViewController() 8 9 @property (weak, nonatomic) IBOutlet UILabel *gameStateLable; 10 @property (weak, nonatomic) IBOutlet UIButton *restartButton; 11 @property (nonatomic) NSUInteger selfDefiningModel; 12 @property (nonatomic,strong) NSDictionary *colorOfCard; 13 14 @end 15 16 @implementation SetCardGameViewController 17 18 #define CORNER_FONT_STANDARD_HIGHT 180.0 19 #define CORNER_RADIUS 12.0 20 21 //讓界面變得更好看的魔法 22 - (CGFloat)cornerScaleFactor:(CGFloat)height {return height / CORNER_FONT_STANDARD_HIGHT;} 23 - (CGFloat)cornerRadius:(CGFloat)height {return CORNER_RADIUS * [self cornerScaleFactor:height];} 24 25 - (void)viewDidLoad 26 { 27 //又是個讓界面變得更好看的魔法 28 self.restartButton.layer.cornerRadius = [self cornerRadius:self.restartButton.bounds.size.height]; 29 30 self.colorOfCard = @{@"red" : [UIColor redColor], @"green" : [UIColor greenColor], @"purple" : [UIColor purpleColor]}; 31 32 _selfDefiningModel = 3;//default model 33 34 if(!self.game) 35 { 36 self.game = [[SetCardMatchingGame alloc] initWithCardCount:[self.cardButtons count] 37 usingDeck:[self createDeck]]; 38 self.game.gameModel = self.selfDefiningModel; 39 } 40 [self updateUIWithNotCreateGame]; 41 } 42 43 - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender 44 { 45 if ([segue.identifier isEqualToString:@"show SetCardGameHistory"]) 46 { 47 if ([segue.destinationViewController isKindOfClass:[HistoryViewController class]]) 48 { 49 HistoryViewController *HVC = (HistoryViewController *)segue.destinationViewController; 50 HVC.history = self.game.gameStateHistory; 51 } 52 } 53 } 54 55 56 - (Deck *)createDeck 57 { 58 return [[SetCardDeck alloc] init]; 59 } 60 - (IBAction)touchCardButtons:(UIButton *)sender 61 { 62 //遊戲開始後禁用模式選擇功能 63 NSUInteger cardIndex = [self.cardButtons indexOfObject:sender]; 64 if(!self.game) 65 { 66 self.game = [[SetCardMatchingGame alloc] initWithCardCount:[self.cardButtons count] 67 usingDeck:[self createDeck]]; 68 self.game.gameModel = self.selfDefiningModel; 69 } 70 [self.game chooseCardAtIndex:cardIndex]; 71 [self updateUI]; 72 } 73 74 - (IBAction)touchRestartButton:(UIButton *)sender 75 { 76 //恢復默認值 77 self.selfDefiningModel = 3; 78 self.game = nil; 79 if(!self.game) 80 { 81 self.game = [[SetCardMatchingGame alloc] initWithCardCount:[self.cardButtons count] 82 usingDeck:[self createDeck]]; 83 self.game.gameModel = self.selfDefiningModel; 84 } 85 [self updateUIWithNotCreateGame]; 86 } 87 88 - (void) updateUIWithNotCreateGame 89 { 90 for (UIButton *cardButton in self.cardButtons) 91 { 92 NSUInteger cardIndex = [self.cardButtons indexOfObject:cardButton]; 93 Card *card = [self.game cardAtIndex:cardIndex]; 94 [cardButton setAttributedTitle:[self attributeTitleForCard:card] forState:UIControlStateNormal]; 95 [cardButton setBackgroundImage:[UIImage imageNamed:@"cardFront"] forState:UIControlStateNormal]; 96 cardButton.enabled = YES; 97 } 98 self.gameStateLable.text = @"State"; 99 self.scoreLable.text = @"Score:0"; 100 } 101 102 - (void)updateUI 103 { 104 for (UIButton *cardButton in self.cardButtons) 105 { 106 NSUInteger cardIndex = [self.cardButtons indexOfObject:cardButton]; 107 Card *card = [self.game cardAtIndex:cardIndex]; 108 [cardButton setAttributedTitle:[self attributeTitleForCard:card] forState:UIControlStateNormal]; 109 [cardButton setBackgroundImage:[self backgroundImageForCard:card] forState:UIControlStateNormal]; 110 cardButton.enabled = !card.isMacthed; 111 } 112 self.scoreLable.text = [NSString stringWithFormat:@"Score:%ld",(long)self.game.score]; 113 self.gameStateLable.text = ((SetCardMatchingGame *)self.game).gameState; 114 } 115 116 - (NSAttributedString *)attributeTitleForCard:(Card *)card 117 { 118 NSShadow *shadow1 = [[NSShadow alloc] init]; 119 shadow1.shadowColor = [UIColor grayColor]; 120 shadow1.shadowOffset = CGSizeMake(2.0f, 2.0f); 121 122 NSShadow *shadow2 = [[NSShadow alloc] init]; 123 shadow2.shadowColor = [UIColor whiteColor]; 124 shadow2.shadowOffset = CGSizeMake(0.0F,0.0f); 125 return [[NSAttributedString alloc] initWithString:((SetCard *)card).suit 126 attributes:@{NSForegroundColorAttributeName : [self.colorOfCard valueForKey:((SetCard *)card).color], 127 NSShadowAttributeName : ((SetCard *)card).shading ? shadow1 : shadow2, 128 NSFontAttributeName : [UIFont boldSystemFontOfSize:10.0f]}]; 129 } 130 131 - (UIImage *)backgroundImageForCard:(Card *)card 132 { 133 return card.chosen ? [UIImage imageNamed:@"setCardback"] : [UIImage imageNamed:@"cardFront"]; 134 } 135 136 @end
d.history功能的實現
HistoryViewController
屬性history用於接收父MVC傳來的數據
1 #import <UIKit/UIKit.h> 2 3 @interface HistoryViewController : UIViewController 4 @property (strong, nonatomic) NSArray *history; 5 @end
1 #import "HistoryViewController.h" 2 3 @interface HistoryViewController () 4 @property (weak, nonatomic) IBOutlet UITextView *HistoryTextView; 5 @end 6 7 @implementation HistoryViewController 8 9 - (void)viewDidLoad { 10 [super viewDidLoad]; 11 // Do any additional setup after loading the view. 12 for (NSString *state in self.history) 13 { 14 self.HistoryTextView.text = [self.HistoryTextView.text stringByAppendingFormat:@"%@\n",state]; 15 } 16 } 17 @end
總結:本次做業提出的問題比解決的問題要多不少,做者只是極其簡單的實現了做業的大體要求,仍然有許多問題沒有解決,如Model的抽象化問題中原有的readonly屬性在超類中可能變爲了readwrite,是否會產生安全性問題,有沒有更好的方法;Controller的抽象化中將許多本來隱藏在.m文件中的輸出口放至超類的.h文件中,是否會形成子類對輸出口的濫用,有沒有更好的解決方案;仍然沒有解決State顯示內容超出屏幕空間的問題(太懶了=_=);Setcard的state顯示可使用NSAttributeString(感興趣能夠試試);一樣SetCard中history的顯示也可使用NSAttributeString,此時可能要改變一些傳值的內容(感興趣去玩耍一下吧);關於SetCard的遊戲規則其實還不太完善,好比初始化後可能無適合匹配狀況的判斷(百度百科的遊戲規則中是要再次添加三張牌,我在屏幕中儘量多添加了幾張紙牌來避免這種狀況),以及遊戲結束狀況的判斷,本次做業至關於只實現了setcard選擇後的匹配計分規則(任重而道遠。。。)。到此就是我大概能想到的值得改進的地方(感受到提出的問題比解決的問題多了吧=_=),一次做業的內容不只能複習到前面所學的內容,仍是對我的學到知識融匯貫通的一次檢驗,各位加油,有任何問題歡迎討論:)
做業源碼地址:https://github.com/NSLogMeng/Stanford_iOS7_Study/commit/0bb1103ccb4293caa9dc3b12200ba9fb12a51170
課程視頻地址:網易公開課:http://open.163.com/movie/2014/1/L/H/M9H7S9F1H_M9H801GLH.html
或者iTunes U搜索standford課程