zoukankan      html  css  js  c++  java
  • IOS的MVC

    1 翻牌游戏

    1.1 问题

    根据苹果MVC设计模式的思想原则实现一个简单的翻牌游戏,功能如下:

    1)界面上随机摆放12张背面朝上的纸牌,界面效果如图-1所示:

    图- 1

    2)点击纸牌可以使纸牌翻页,翻牌后进行数字和花色的匹配,如果数字一样得4分,花色一样得1分;

    3)在界面的左下角有一个记录得分的标签,界面如图-2所示:

    图- 2

    1.2 方案

    首先使用Xcode创建一个带有xib的项目,在xib界面拖放12个UIButton对象和一个计分的UILabel对象,因为纸牌可以点击,所以使用UIButton控件作为纸牌对象,并且在检查器中设置好纸牌的背景图片。

    其次根据苹果IOS开发的MVC原则,将整个案例的类分为三个群组,即Model层——用来保存游戏的纸牌数据和分数计算,View层——保存游戏的界面(xib文件)以及Controller层——控制程序的流程,协调View层和Model层。

    然后创建TRCard类(纸牌类)用来管理纸牌的数据,TRDeck类(牌桌类)管理发牌的规则,TRCardGame类(游戏类)管理整个游戏的逻辑,这三个类都属于Model层,分别给这三个类增加属性和方法,实现各自管理的数据和逻辑。

    最后在TRCardGameController类里面实现Model层和View层的通信逻辑。

    1.3 步骤

    实现此案例需要按照如下步骤进行。

    步骤一:搭建游戏界面

    首先在xib界面拖放12个UIButton对象和一个计分的UILabel对象,因为纸牌可以点击这是使用UIButton对象作为纸牌。

    其次给纸牌设置背景图片,选中纸牌的背景图片拖放到Xcode导航栏的View群组里面,会弹出如图-3所示对话框,在Copy items if needed选项前的复选框打钩,表明将图片拷贝到项目中:

    图-3

    点击Finish按钮之后,可以在导航栏的View群组里面看见添加进来的两张图片,名字分别为cardback.png和cardfront.png,分别是纸牌正面图片和背面图片,如图-4所示:

    图-4

    然后在右边栏的第四个检查器里面设置纸牌的背景图,初始界面将按钮的背景图设置为纸牌的背面图片cardback.png,如图-5所示:

    图-5

    步骤二:创建TRCard类、TRDeck类和TRCardGame类

    创建Model层的三个类,TRCard,TRDeck和TRGame,这三个类全都继承至NSObject,如图-6所示:

    图-6

    其次在TRCard类里面声明NSString类型的用来表示纸牌内容的属性content,而每张牌由级别和花色组成,因此再声明两个属性rank和suit,分别用来表示级别和花色,在这里级别使用NSUInteger类型,花色使用特殊的字符表示,因此是NSString类型,代码如下所示:

    1. //这张牌的内容, 如:"♣A"
    2. @property (nonatomic, strong, readonly)NSString *content;
    3. //纸牌花色♠♥♣♦
    4. @property (nonatomic, strong)NSString *suit;
    5. //纸牌级别
    6. @property (nonatomic) NSUInteger rank;

    然后再声明两个BOOL类型的属性用来表示纸牌的两个状态,chosen表示是否被选中状态,matched表示是否被匹配状态,代码如下所示:

    1. @property (nonatomic, getter=isChosen) BOOL chosen;
    2. @property (nonatomic, getter=isMatched) BOOL matched;

    在这里定义属性使用了getter关键字,这是一种开发习惯,将BOOL类型属性的getter方法进行重新命名,表达更清楚明确,在这里自动生成的两个属性的getter方法名为isChosen和isMatched。

    实际的开发中为了提高代码的效率,根据程序的需要通常会重写属性的setter和getter方法,属性suit只能接受♠♥♣♦这四种花色,赋值其他字符是非法的,为了保护suit属性,可以在suit的setter方法里面增加以下限制代码,如下所示:

    1. + (NSArray *)validSuits
    2. {
    3. return @[@"♠", @"♥", @"♣", @"♦"];
    4. }
    5. - (void)setSuit:(NSString *)suit
    6. {
    7. //判断传入的参数是否合法
    8. if([[TRCardvalidSuits] containsObject:suit]){
    9. _suit = suit;
    10. }
    11. }

    以上代码中validSuits方法可以在TRCard.h文件中公开方便外部使用,否则外部可能不知道哪些是合法字符。同样的为了保护suit属性在getter方法里面也可以增加一些限制的代码,如下所示:

    1. -(NSString *)suit
    2. {
    3. //判断_suit实例变量是否为空,如果为空则返回?,保证_suit不会为空
    4. return _suit ? _suit : @"?";
    5. }

    此时需要注意,重写完setter和getter方法之后系统不会再自动生成实例变量_suit,因此需要使用@synthesize关键字来生成实例变量_suit,代码如下所示:

    1. @synthesize suit = _suit;

    同样为了保护rank属性,也可以将rank的setter方法和getter方法重写,代码如下所示:

    1. - (void)setRank:(NSUInteger)rank
    2. {
    3. //纸牌一共有13个级别
    4. if(rank <= 13){
    5. _rank = rank;
    6. }
    7. }

    纸牌的内容是由花色和级别组成,因此重写content的getter方法即可,每次调用getter方法时即可获取到纸牌的显示内容,代码如下所示:

    1. + (NSArray *)randStrins
    2. {
    3. //纸牌13个级别对应的字符串,由小到大放入数组
    4. return @[@"?", @"A", @"2", @"3", @"4", @"5", @"6", @"7", @"8", @"9", @"10", @"J", @"Q", @"K"];
    5. }
    6. - (NSString *)content
    7. {
    8. NSString *rankString = [TRCardrandStrins][self.rank];
    9. return [self.suitstringByAppendingString:rankString];
    10. }

    最后在TRCard类里面增加一个公开的方法,用于获取纸牌的最大级别,方便其他对象使用,代码如下所示:

    1. //返回级别的最大值
    2. + (NSUInteger)maxRank
    3. {
    4. return [[TRCardrandStrins] count] - 1;
    5. }

    步骤三:TRDeck类添加属性和方法

    TRDeck类主要是管理发牌的规则,既然是发牌,就需要拥有一个纸牌数组的属性,因此声明一个NSMutableArray类型的私有属性cards,使用懒汉模式(延迟加载)给实例变量_cards进行初始化,代码如下所示:

    1. //声明属性
    2. @property (nonatomic, strong) NSMutableArray *cards;
    3. //重写setter方法初始化_card实例变量
    4. - (NSMutableArray *)cards
    5. {
    6. if(!_cards)_cards = [[NSMutableArrayalloc]init];
    7. return _cards;
    8. }

    然后在TRDeck的初始化方法里面按照纸牌的规则给_card数组添加纸牌对象,一共52个纸牌对象,代码如下所示:

    1. - (instancetype)init
    2. {
    3. self = [super init];
    4. if (self) {
    5. for (NSString *suit in [TRCardvalidSuits]) {
    6. for(NSUInteger rank = 1; rank<=[TRCardmaxRank]; rank++){
    7.                 //创建纸牌对象
    8. TRCard *card = [[TRCardalloc]init];
    9. card.suit = suit;
    10. card.rank = rank;
    11. //将纸牌对象添加的纸牌数组中
    12. [self.cardsaddObject:card];
    13. }
    14. }
    15. }
    16. return self;
    17. }

    最后实现随机发牌的方法randomCard,此方法需要在TRDeck.h文件中公开,代码如下所示:

    1. - (TRCard *)randomCard
    2. {
    3. //使用随机函数,随机出一个数组下标,
    4. unsignedint index = arc4random() % self.cards.count;
    5. //根据随机出的数组下标从数组中获取到纸牌对象
    6. TRCard *card = self.cards[index];
    7. //从纸牌数值将刚才获取的纸牌对象移除
    8. [self.cardsremoveObjectAtIndex:index];
    9. return card;
    10. }

    步骤四:TRCardGame类添加属性和方法

    TRCardGame类是用来管理整个游戏的逻辑,分析本案例主要需要实现纸牌匹配和计算分数的逻辑,因此首先声明一个NSInteger的属性score,此时需要注意分数只能由TRCardGame类进行计算和修改,外部对该属性只能读取不能修改,因此在TRCardGame.h文件中声明一个只读的score属性,在TRCardGame.m文件中声明一个可读写的score属性,代码如下所示:

    1. //TRCardGame.h文件中
    2. @property (nonatomic, readonly) NSInteger score;
    3. //TRCardGame.m文件中
    4. @property (nonatomic, readwrite) NSInteger score;

    其次TRCardGame需要随机产生12张纸牌,所以需要声明一个NSMutableArray类型的属性cards,同样在setter方法中进行实例变量_cards初始化,代码如下所示:

    1. //声明属性
    2. @property (nonatomic, strong) NSMutableArray *cards;
    3. //重写setter方法初始化_card实例变量
    4. - (NSMutableArray *)cards
    5. {
    6. if(!_cards)_cards = [[NSMutableArrayalloc]init];
    7. return _cards;
    8. }

    然后自定义一个TRCardGame类的初始化方法initWithCardCount:usingDeck:,此方法用来初始化TRCardGame对象,此方法中使用一个TRDeck对象随机出12张纸牌,此时需要注意初始化方法通常都是需要公开在.h文件中进行声明,代码如下所示:

    1. - (instancetype)initWithCardCount:(NSUInteger)count usingDeck:(TRDeck *)deck
    2. {
    3. self = [super init];
    4. if (self) {
    5. for(inti=0; i<count; i++){
    6. TRCard *card = [deck randomCard];
    7. [self.cardsaddObject:card];
    8. }
    9. }
    10. return self;
    11. }

    最后实现纸牌的匹配逻辑,定义一个动态方法chooseCardAtIndex:,当用户选中某张牌是调用此方法,其中TRCard对象的匹配判断应有TRCard类提供,同样此方法需要在.h文件中公开,代码如下所示:

    1. TRCardGame类中的代码:
    2. //根据下标返回指定扑克牌
    3. - (TRCard *)cardAtIndex:(NSUInteger)index
    4. {
    5. return index<self.cards.count ? self.cards[index] : nil;
    6. }
    7. //用户选中了一张牌
    8. - (void)chooseCardAtIndex:(NSUInteger)index
    9. {
    10. //进行匹配
    11. TRCard *card = [self cardAtIndex:index];
    12. if(![card isMatched]){
    13. if([card isChosen]){
    14. card.chosen = NO;//再翻回去
    15. }else{//没有匹配,也没有在正面
    16. //匹配其他翻过来的牌
    17. for (TRCard *otherCard in self.cards) {
    18. if([otherCardisChosen] && ![otherCardisMatched]){
    19. int score = [card match:otherCard];
    20. if(score){//匹配成功
    21. self.score += score;
    22. card.matched = YES;
    23. otherCard.matched = YES;
    24. }else{//匹配失败
    25. otherCard.chosen = NO;
    26. }
    27. }
    28. }
    29. //把牌翻过来
    30. card.chosen = YES;
    31. }
    32. }
    33. }
    34. TRCard类中的代码:
    35. - (int)match:(TRCard *)otherCard
    36. {
    37. int score = 0;
    38. if(self.rank == otherCard.rank) score = 4;
    39. else if(self.suit == otherCard.suit) score = 1;
    40. return score;
    41. }

    步骤五:通过TRCardGameController实现View和Model层的通信

    第一步已经搭建好了界面,此时需要将xib中的对象关联到TRCardGameController中,首先关联计分UILabel对象,以拉线的方式关联成TRCardGameController的私有属性scoreLabel,代码如下所示:

    1. @property (weak, nonatomic) IBOutletUILabel *scoreLabel;

    其次关联12个纸牌对象,选中一张纸牌按住control键,往TRCardGameController.m文件的类扩展中拖,在弹出的对话框Connection选项中选择Outlet Collection,如图-7所示:

    图-7

    释放鼠标会自动生成一个NSArray类型的属性cardButtons,该数组里面的元素指向xib中的一个对象,选中代码前的是新圆圈,依次关联xib上的其他11张纸牌,这样_cardButtons数组里面的元素指向12个xib创建的纸牌对象,如图-8所示:

    图-8

    然后将12个纸牌对象以拉线的方式关联同一个IBAction方法touchCardButton:,每当选择纸牌时需要进行纸牌匹配的逻辑判断计算得分,这件事情需要TRCardGame对象去实现,因此TRCardGameController类需要拥有一个TRCardGame类的私有属性以及一个TRDeck类的私有属性,声明属性并且进行初始化,代码如下所示:

    1. @property (nonatomic, strong) TRDeck *deck;
    2. @property (nonatomic, strong) TRCardGame *game;
    3. - (TRDeck *)deck
    4. {
    5. if(!_deck)_deck = [[TRDeckalloc]init];
    6. return _deck;
    7. }
    8. - (TRCardGame *)game
    9. {
    10. if(!_game)_game = [[TRCardGamealloc]initWithCardCount:self.cardButtons.countusingDeck:self.deck];
    11. return _game;
    12. }

    然后在touchCardButton:方法里通过TRCardGame对进行纸牌的匹配逻辑计算,代码如下所示:

    1. //点击按钮计算纸牌的匹配逻辑
    2. - (IBAction)touchCardButton:(UIButton *)sender
    3. {
    4. NSUIntegerchooseCardIndex = [self.cardButtonsindexOfObject:sender];
    5. [self.gamechooseCardAtIndex:chooseCardIndex];
    6. }

    最后通过TRCardGame对象得出的匹配的结果更新界面,代码如下所示:

    1. //更新整个界面
    2. - (void)updateUI
    3. {
    4. for (UIButton *cardButton in self.cardButtons) {
    5. NSUInteger index = [self.cardButtonsindexOfObject:cardButton];
    6. TRCard *card = [self.gamecardAtIndex:index];
    7. [cardButtonsetTitle:[self titleForCard:card] forState:UIControlStateNormal];
    8. [cardButtonsetBackgroundImage:[self imageForCard:card] forState:UIControlStateNormal];
    9. cardButton.enabled = ![card isMatched];
    10. self.scoreLabel.text = [NSStringstringWithFormat:@"Score:%d", self.game.score];
    11. }
    12. }
    13. //根据纸牌选中的状态更改纸牌背景图片
    14. - (UIImage *)imageForCard:(TRCard *)card
    15. {
    16. if([card isChosen]){
    17. return [UIImageimageNamed:@"cardfront.png"];
    18. }else{
    19. return [UIImageimageNamed:@"cardback.png"];
    20. }
    21. }
    22. //根据纸牌选中的状态更改纸牌的title
    23. - (NSString *)titleForCard:(TRCard *)card
    24. {
    25. return [card isChosen] ? card.content : @"";
    26. }
    27. //点击按钮更新界面
    28. - (IBAction)touchCardButton:(UIButton *)sender
    29. {
    30.     NSUIntegerchooseCardIndex = [self.cardButtonsindexOfObject:sender];
    31. [self.gamechooseCardAtIndex:chooseCardIndex];
    32. [selfupdateUI];
    33. }

    1.4 完整代码

    本案例中,TRCardGameViewController.m文件中的完整代码如下所示:

     
    1. #import "TRCardGameViewController.h"
    2. #import "TRCardGame.h"
    3. @interfaceTRCardGameViewController ()
    4. @property (nonatomic, strong) TRDeck *deck;
    5. @property (nonatomic, strong) TRCardGame *game;
    6. @property (strong, nonatomic) IBOutletCollection(UIButton) NSArray *cardButtons;
    7. @property (weak, nonatomic) IBOutletUILabel *scoreLabel;
    8. @end
    9. @implementationTRCardGameViewController
    10. - (TRDeck *)deck
    11. {
    12. if(!_deck)_deck = [[TRDeckalloc]init];
    13. return _deck;
    14. }
    15. - (TRCardGame *)game
    16. {
    17. if(!_game)_game = [[TRCardGamealloc]initWithCardCount:self.cardButtons.countusingDeck:self.deck];
    18. return _game;
    19. }
    20. - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle*)nibBundleOrNil
    21. {
    22. self = [super initWithNibName:nibNameOrNilbundle:nibBundleOrNil];
    23. if (self) {
    24. // Custom initialization
    25. }
    26. return self;
    27. }
    28. - (IBAction)touchCardButton:(UIButton *)sender
    29. {
    30. NSUIntegerchooseCardIndex = [self.cardButtonsindexOfObject:sender];
    31. [self.gamechooseCardAtIndex:chooseCardIndex];
    32. [selfupdateUI];
    33. }
    34. - (void)updateUI
    35. {
    36. for (UIButton *cardButton in self.cardButtons) {
    37. NSUInteger index = [self.cardButtonsindexOfObject:cardButton];
    38. TRCard *card = [self.gamecardAtIndex:index];
    39. [cardButtonsetTitle:[self titleForCard:card] forState:UIControlStateNormal];
    40. [cardButtonsetBackgroundImage:[self imageForCard:card] forState:UIControlStateNormal];
    41. cardButton.enabled = ![card isMatched];
    42. self.scoreLabel.text = [NSStringstringWithFormat:@"Score:%d", self.game.score];
    43. }
    44. }
    45. - (UIImage *)imageForCard:(TRCard *)card
    46. {
    47. if([card isChosen]){
    48. return [UIImageimageNamed:@"cardfront.png"];
    49. }else{
    50. return [UIImageimageNamed:@"cardback.png"];
    51. }
    52. }
    53. - (NSString *)titleForCard:(TRCard *)card
    54. {
    55. return [card isChosen] ? card.content : @"";
    56. }
    57. @end
     

    本案例中,TRCard.h文件中的完整代码如下所示:

     
    1. #import<Foundation/Foundation.h>
    2. //扑克牌类
    3. @interfaceTRCard : NSObject
    4. @property (nonatomic, getter=isChosen) BOOL chosen;//被选中
    5. @property (nonatomic, getter=isMatched) BOOL matched;//被匹配
    6. @property (nonatomic, strong, readonly)NSString *content;//这张牌的内容, 如:"♣️A"
    7. @property (nonatomic, strong)NSString *suit;//花色♠️♥️♣️♦️
    8. @property (nonatomic) NSUInteger rank;//级别
    9. //和另外的一个扑克牌匹配,返回得分
    10. - (int)match:(TRCard *)otherCard;
    11. //返回合法的花色
    12. + (NSArray *)validSuits;
    13. //返回级别的最大值
    14. + (NSUInteger)maxRank;
    15. @end
     

    本案例中,TRCard.m文件中的完整代码如下所示:

     
    1. #import "TRCard.h"
    2. @implementationTRCard
    3. @synthesize suit = _suit;
    4. - (int)match:(TRCard *)otherCard
    5. {
    6. int score = 0;
    7. if(self.rank == otherCard.rank) score = 4;
    8. else if(self.suit == otherCard.suit) score = 1;
    9. return score;
    10. }
    11.     
    12. + (NSArray *)validSuits
    13. {
    14. return @[@"♠️", @"♥️", @"♣️", @"♦️"];
    15. }
    16. - (void)setSuit:(NSString *)suit
    17. {
    18. if([[TRCardvalidSuits] containsObject:suit]){
    19. _suit = suit;
    20. }
    21. }
    22. -(NSString *)suit
    23. {
    24. return _suit ? _suit : @"?";
    25. }
    26.     
    27. - (void)setRank:(NSUInteger)rank
    28. {
    29. if(rank <= 13){
    30. _rank = rank;
    31. }
    32. }
    33. + (NSArray *)randStrins
    34. {
    35. return @[@"?", @"A", @"2", @"3", @"4", @"5", @"6", @"7", @"8", @"9", @"10", @"J", @"Q", @"K"];
    36. }
    37. - (NSString *)content
    38. {
    39. NSString *rankString = [TRCardrandStrins][self.rank];
    40. return [self.suitstringByAppendingString:rankString];
    41. }
    42. //返回级别的最大值
    43. + (NSUInteger)maxRank
    44. {
    45. return [[TRCardrandStrins] count] - 1;
    46. }
    47. @end
     

    本案例中,TRDeck.h文件中的完整代码如下所示:

     
    1. #import<Foundation/Foundation.h>
    2. #import "TRCard.h"
    3. //牌桌类
    4. @interfaceTRDeck : NSObject
    5. - (TRCard *)randomCard;
    6. @end
     

    本案例中,TRDeck.m文件中的完整代码如下所示:

     
    1. #import "TRDeck.h"
    2. @interfaceTRDeck ()
    3. @property (nonatomic, strong) NSMutableArray *cards;
    4. @end
    5. @implementationTRDeck
    6. - (NSMutableArray *)cards
    7. {
    8. if(!_cards)_cards = [[NSMutableArrayalloc]init];
    9. return _cards;
    10. }
    11. - (instancetype)init
    12. {
    13. self = [super init];
    14. if (self) {
    15. for (NSString *suit in [TRCardvalidSuits]) {
    16. for(NSUInteger rank = 1; rank<=[TRCardmaxRank]; rank++){
    17. //创建纸牌对象
    18. TRCard *card = [[TRCardalloc]init];
    19. card.suit = suit;
    20. card.rank = rank;
    21. //将纸牌对象添加的纸牌数组中
    22. [self.cardsaddObject:card];
    23. }
    24. }
    25. }
    26. return self;
    27. }
    28. //随机发牌
    29. - (TRCard *)randomCard
    30. {
    31. unsignedint index = arc4random() % self.cards.count;
    32. TRCard *card = self.cards[index];
    33. [self.cardsremoveObjectAtIndex:index];
    34. return card;
    35. }
    36. @end
     

    本案例中,TRCardGame.h文件中的完整代码如下所示:

     
    1. #import<Foundation/Foundation.h>
    2. #import "TRCard.h"
    3. #import "TRDeck.h"
    4. //游戏类
    5. @interfaceTRCardGame : NSObject
    6. - (instancetype)initWithCardCount:(NSUInteger)count usingDeck:(TRDeck *)deck;
    7. //用户选中了一张牌
    8. - (void)chooseCardAtIndex:(NSUInteger)index;
    9. //根据下标返回指定扑克牌
    10. - (TRCard *)cardAtIndex:(NSUInteger)index;
    11. @property (nonatomic, readonly) NSInteger score;//分数
    12. @end
     

    本案例中,TRCardGame.m文件中的完整代码如下所示:

     
    1. #import "TRCardGame.h"
    2. @interfaceTRCardGame ()
    3. @property (nonatomic, strong)NSMutableArray *cards;
    4. @property (nonatomic, readwrite) NSInteger score;//分数
    5. @end
    6. @implementationTRCardGame
    7. - (NSMutableArray *)cards
    8. {
    9. if (!_cards) {
    10. _cards = [[NSMutableArrayalloc]init];
    11. }
    12. return _cards;
    13. }
    14. - (instancetype)initWithCardCount:(NSUInteger)count usingDeck:(TRDeck *)deck
    15. {
    16. self = [super init];
    17. if (self) {
    18. for(inti=0; i<count; i++){
    19. TRCard *card = [deck randomCard];
    20. [self.cardsaddObject:card];
    21. }
    22. }
    23. return self;
    24. }
    25. //用户选中了一张牌
    26. - (void)chooseCardAtIndex:(NSUInteger)index
    27. {
    28. //进行匹配
    29. TRCard *card = [self cardAtIndex:index];
    30. if(![card isMatched]){
    31. if([card isChosen]){
    32. card.chosen = NO;//再翻回去
    33. }else{//没有匹配,也没有在正面
    34. //匹配其他翻过来的牌
    35. for (TRCard *otherCard in self.cards) {
    36. if([otherCardisChosen] && ![otherCardisMatched]){
    37. int score = [card match:otherCard];
    38. if(score){//匹配成功
    39. self.score += score;
    40. card.matched = YES;
    41. otherCard.matched = YES;
    42. }else{//匹配失败
    43. otherCard.chosen = NO;
    44. }
    45. }
    46. }
    47. //把牌翻过来
    48. card.chosen = YES;
    49. }
    50. }
    51. }
    52. //根据下标返回指定扑克牌
    53. - (TRCard *)cardAtIndex:(NSUInteger)index
    54. {
    55. return index<self.cards.count ? self.cards[index] : nil;
    56. }
    57. @end
  • 相关阅读:
    Ajax实现在textbox中输入内容,动态从数据库中模糊查询显示到下拉框中
    JavaScript 多级联动浮动(下拉)菜单 (第二版)
    JavaScript在IE浏览器和Firefox浏览器中的差异总结
    IE和FF对CSS兼容问题
    XHTML的特征(规范)
    总结引入CSS样式方式中的link和import的区别
    CSS知识精化集全,每天更新一点点,自己总结。
    今天遇见了setTimeout()函数
    jquery的发展由来和深入理解(一)
    左边导航条动态增加或缩短高度以及放大缩小问题的解决方法
  • 原文地址:https://www.cnblogs.com/52190112cn/p/5049305.html
Copyright © 2011-2022 走看看