首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >西蒙的对立面游戏

西蒙的对立面游戏
EN

Code Review用户
提问于 2014-12-05 22:39:25
回答 2查看 185关注 0票数 8

我今天为这里提到的社区挑战做了这个小游戏。游戏的理念是,有时当西蒙给你一种颜色,你应该选择相反的颜色(在对角的颜色)。游戏视觉上表明这是当它是相反的西蒙。我不完全确定游戏是否“有趣”,但它绝对是有趣的!

首先,这里是游戏模型:

SOGame.h

代码语言:javascript
运行
复制
#import <Foundation/Foundation.h>
#import "SOGameStates.h"
#import "SOSimonSays.h"

@interface SOGame : NSObject

@property GameState state;
@property NSMutableArray *sequence;

-(void) update;
-(void) evaluateMove:(SimonSays)playerChoice;
-(void) restart;

@end

SOGame.m

代码语言:javascript
运行
复制
#import "SOGame.h"
#import "SOSimon.h"

@implementation SOGame {
    int _selectionNumber;
}

-(id) init {
    self = [super init];
    if (self) {
        _state = GameStateStarted;
        _sequence = [[NSMutableArray alloc]init];
        _selectionNumber = 0;
    }
    return self;
}

#pragma mark - Game Rules
-(void) update {
    switch (self.state) {
        case GameStateStarted:
            [self createFirstSequence];
            break;
        case GameStateSuccess:
            [self createNewSequence];
            break;
        default:
            break;
    }
}
-(void) evaluateMove:(SimonSays)playerChoice {

    //if an invalid node is selected
    if (playerChoice == SimonSaysNumChoices) {
        return;
    }

    SOSimon *simon = [_sequence objectAtIndex:_selectionNumber];

    //different rules for opposite versus normal simons
    if (simon.opposite) {
        if (playerChoice == [self opposititePosition:simon.simonSays]) {
            [self doSuccessfulChoice];
        } else {
            _state = GameStateFailure;
        }
    } else {
        if (playerChoice == simon.simonSays) {
            [self doSuccessfulChoice];
        } else {
            _state = GameStateFailure;
        }
    }
}
-(void) doSuccessfulChoice {
    if (_selectionNumber < _sequence.count - 1) {
        _selectionNumber++;
    } else {
        _state = GameStateSuccess;
        _selectionNumber = 0;
    }
}
-(NSInteger) opposititePosition:(SimonSays)position {
    switch (position) {
        case SimonSaysTopLeft:
            return SimonSaysBottomRight;
        case SimonSaysTopRight:
            return SimonSaysBottomLeft;
        case SimonSaysBottomLeft:
            return SimonSaysTopRight;
        case SimonSaysBottomRight:
            return SimonSaysTopLeft;
        default:
            return 0;
    }
}
-(void) restart {
    _selectionNumber = 0;
    _sequence = nil;
    _state = GameStateStarted;
}

#pragma mark - Sequences
-(void) createFirstSequence {
    self.sequence = [self sequence:nil];
    self.state = GameStateSequenceReady;
}
-(void) createNewSequence {
    self.sequence = [self sequence:self.sequence];
    self.state = GameStateSequenceReady;
}
-(NSMutableArray *) sequence:(NSMutableArray *)oldSequence {
    NSMutableArray *newSequence = [[NSMutableArray alloc]init];
    if (oldSequence) {
        newSequence = [[NSMutableArray alloc]initWithArray:oldSequence];
    }
    [newSequence addObject:[[SOSimon alloc]init]];
    return newSequence;
}

@end

SOSimon.h

代码语言:javascript
运行
复制
#import <Foundation/Foundation.h>
#import "SOSimonSays.h"

@interface SOSimon : NSObject

@property SimonSays simonSays;
@property BOOL opposite;

@end

SOSimon.m

代码语言:javascript
运行
复制
#import "SOSimon.h"

static const int chanceForOpposite = 5;

@implementation SOSimon

-(instancetype) init {
    self = [super init];
    if (self) {
        _simonSays = arc4random_uniform(SimonSaysNumChoices);

        _opposite = NO;
        if (arc4random_uniform(100) < chanceForOpposite) {
            _opposite = YES;
        }
    }
    return self;
}

@end

SOSimonSays.h

代码语言:javascript
运行
复制
typedef NS_ENUM(NSInteger, SimonSays){
    SimonSaysTopLeft = 0,
    SimonSaysTopRight,
    SimonSaysBottomLeft,
    SimonSaysBottomRight,
    SimonSaysNumChoices
};

SOGameStates.h

代码语言:javascript
运行
复制
typedef NS_ENUM(NSInteger, GameState){
    GameStateStarted = 0,
    GameStateSequenceReady,
    GameStateSequencePlaying,
    GameStateWaitingForInput,
    GameStateSuccess,
    GameStateFailure,
    GameStateEnded
};

现在我们有了SKScene。这是完全工作在此刻,包括动画时,播放的序列,当玩家点击一个角落。最大的挑战是让动作按部就班地进行,而不是所有的动作同时进行。为此,我创建了一个名为SOQueuedAction的特殊类:

SOQueuedAction.h

代码语言:javascript
运行
复制
#import <Foundation/Foundation.h>
#import <SpriteKit/SpriteKit.h>
#import "SOSimon.h"

@interface SOQueuedAction : NSObject

-(id) initWithAction:(SKAction *)action node:(SKSpriteNode *)sprite simon:(SOSimon *)simon;

@property SKAction *action;
@property SKSpriteNode *sprite;
@property SOSimon *simon;

@end

SOQueuedAction.m

代码语言:javascript
运行
复制
#import "SOQueuedAction.h"

@implementation SOQueuedAction

-(id) initWithAction:(SKAction *)action node:(SKSpriteNode *)sprite simon:(SOSimon *)simon {
    self = [super init];
    if (self) {
        _action = action;
        _sprite = sprite;
        _simon = simon;
    }
    return self;
}

@end

这基本上只是存储要执行的动作和执行它的雪碧,然后游戏场景一次处理一个动作:

SOGameScene.m

代码语言:javascript
运行
复制
#import "SOGame.h"
#import "SOGameScene.h"
#import "SOQueuedAction.h"
#import "SOSimon.h"
#import "SOSimonSays.h"

NSString* const kNameTopLeft = @"topLeft";
NSString* const kNameTopRight = @"topRight";
NSString* const kNameBottomLeft = @"bottomLeft";
NSString* const kNameBottomRight = @"bottomRight";

@implementation SOGameScene {
    CGSize _initialScreenSize;

    SOGame *_game;
    SKNode *_gameBoard;
    SKNode *_overlayNode;

    SKLabelNode *_turnLabel;

    NSMutableArray *_actionQueue;
    BOOL _isActionPlaying;
}

#pragma mark - Initialization
-(void) didMoveToView:(SKView *)view {
    _actionQueue = [[NSMutableArray alloc]init];

    _initialScreenSize = self.frame.size;
    self.backgroundColor = [SKColor whiteColor];

    _game = [[SOGame alloc]init];

    [self setUpGameBoard];

    [self setUpLabels];

    _overlayNode = [[SKNode alloc]init];
    [self addChild:_overlayNode];
}
-(void) setUpGameBoard {
    _gameBoard = [[SKNode alloc]init];
    [self addChild:_gameBoard];

    float widthDivisor = 3.05;
    float heightDivisor = 2.59;
    CGPoint topLeftPos = CGPointMake(_initialScreenSize.width/widthDivisor,
                                     _initialScreenSize.height - _initialScreenSize.height/heightDivisor);
    CGPoint topRightPos = CGPointMake(_initialScreenSize.width - _initialScreenSize.width/widthDivisor,
                                      _initialScreenSize.height - _initialScreenSize.height/heightDivisor);
    CGPoint bottomLeftPos = CGPointMake(_initialScreenSize.width/widthDivisor,
                                        _initialScreenSize.height/heightDivisor);
    CGPoint bottomRightPos = CGPointMake(_initialScreenSize.width - _initialScreenSize.width/widthDivisor,
                                         _initialScreenSize.height/heightDivisor);

    SKSpriteNode *pieceTopLeft = [self gamePieceForColor:[SKColor greenColor] position:SimonSaysTopLeft];
    pieceTopLeft.position = topLeftPos;
    [_gameBoard addChild:pieceTopLeft];

    SKSpriteNode *pieceTopRight = [self gamePieceForColor:[SKColor yellowColor] position:SimonSaysTopRight];
    pieceTopRight.position = topRightPos;
    [_gameBoard addChild:pieceTopRight];

    SKSpriteNode *pieceBottomLeft = [self gamePieceForColor:[SKColor redColor] position:SimonSaysBottomLeft];
    pieceBottomLeft.position = bottomLeftPos;
    [_gameBoard addChild:pieceBottomLeft];

    SKSpriteNode *pieceBottomRight = [self gamePieceForColor:[SKColor blueColor] position:SimonSaysBottomRight];
    pieceBottomRight.position = bottomRightPos;
    [_gameBoard addChild:pieceBottomRight];
}
-(NSString *) nameForPosition:(SimonSays)position {
    switch (position) {
        case SimonSaysTopLeft:
            return kNameTopLeft;
        case SimonSaysTopRight:
            return kNameTopRight;
        case SimonSaysBottomLeft:
            return kNameBottomLeft;
        case SimonSaysBottomRight:
            return kNameBottomRight;
        default:
            return @"Invalid Position";
    }
}
-(void) setUpLabels {
    SKLabelNode *titleLabelFirstLine = [self titleLabel:@"Simon's"];
    titleLabelFirstLine.position = CGPointMake(_initialScreenSize.width/2, _initialScreenSize.height - _initialScreenSize.height/10);
    [self addChild:titleLabelFirstLine];

    SKLabelNode *titleLabelSecondLine = [self titleLabel:@"Opposites"];
    titleLabelSecondLine.position = CGPointMake(_initialScreenSize.width/2, _initialScreenSize.height - _initialScreenSize.height/6);
    [self addChild:titleLabelSecondLine];

    SKLabelNode *turnWordLabel = [self defaultSizeLabel:@"Turn"];
    turnWordLabel.position = CGPointMake(_initialScreenSize.width/2, _initialScreenSize.height/6);
    [self addChild:turnWordLabel];

    _turnLabel = [self defaultSizeLabel:[NSString stringWithFormat:@"%lu", (unsigned long)_game.sequence.count]];
    _turnLabel.position = CGPointMake(_initialScreenSize.width/2, _initialScreenSize.height/10);
    [self addChild:_turnLabel];
}

#pragma mark - Game Loop
-(void)update:(CFTimeInterval)currentTime {

    [_game update];

    if (_game.state == GameStateSequenceReady) {
        [self createActionSequence];
    } else if (_game.state == GameStateSequencePlaying) {
        [self playActionSequence];
    } else if (_game.state == GameStateFailure) {
        SKLabelNode *message = [self titleLabel:@"Failed!"];
        message.position = CGPointMake(_initialScreenSize.width/2, _initialScreenSize.height/2);
        [_overlayNode addChild:message];

        SKSpriteNode *retryButton = [self retryButton];
        retryButton.position = CGPointMake(_initialScreenSize.width/2, _initialScreenSize.height/3);
        [_overlayNode addChild:retryButton];
    }

    _turnLabel.text = [NSString stringWithFormat:@"%lu", (unsigned long)_game.sequence.count];
}

#pragma mark - Touch Controls
-(void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    for (UITouch *touch in touches) {
        if(touch.tapCount == 1) {
            CGPoint positionInScene = [touch locationInNode:self];
            [self selectNodeForTouch:positionInScene];
        }
    }
}
-(void)selectNodeForTouch:(CGPoint)touchLocation {
    if (_game.state == GameStateWaitingForInput) {
        SKSpriteNode *touchedNode = (SKSpriteNode *)[self nodeAtPoint:touchLocation];
        [_game evaluateMove:[self simonSaysForName:touchedNode.name]];
        [self doQuickFadeAnimation:touchedNode];
    } else {
        SKSpriteNode *touchedNode = (SKSpriteNode *)[self nodeAtPoint:touchLocation];
        if ([touchedNode.name isEqualToString:@"retryButton"]) {
            [_game restart];
            [_overlayNode removeAllChildren];
        }
    }
}
-(SimonSays) simonSaysForName:(NSString *)name {
    if ([name isEqualToString:kNameTopLeft]) {
        return SimonSaysTopLeft;
    } else if ([name isEqualToString:kNameTopRight]) {
        return SimonSaysTopRight;
    } else if ([name isEqualToString:kNameBottomLeft]) {
        return SimonSaysBottomLeft;
    } else if ([name isEqualToString:kNameBottomRight]) {
        return SimonSaysBottomRight;
    }
    return SimonSaysNumChoices;
}

#pragma mark - Animations
-(void) createActionSequence {

    for (SOSimon *simon in _game.sequence) {

        SKSpriteNode *nodeToAnimate = nil;
        switch (simon.simonSays) {
            case SimonSaysTopLeft:
                nodeToAnimate = (SKSpriteNode *)[_gameBoard childNodeWithName:kNameTopLeft];
                break;
            case SimonSaysTopRight:
                nodeToAnimate = (SKSpriteNode *)[_gameBoard childNodeWithName:kNameTopRight];
                break;
            case SimonSaysBottomLeft:
                nodeToAnimate = (SKSpriteNode *)[_gameBoard childNodeWithName:kNameBottomLeft];
                break;
            case SimonSaysBottomRight:
                nodeToAnimate = (SKSpriteNode *)[_gameBoard childNodeWithName:kNameBottomRight];
                break;
            default:
                break;
        }

        if (nodeToAnimate) {
            SKAction *fadeOutAndIn = [SKAction sequence:@[[SKAction colorizeWithColor:[SKColor whiteColor]
                                                                 colorBlendFactor:0.5 duration:1],
                                                          [SKAction colorizeWithColor:nodeToAnimate.color
                                                                 colorBlendFactor:0.5 duration:1]
                                                          ]];
            SOQueuedAction *actionForQueue = [[SOQueuedAction alloc]initWithAction:fadeOutAndIn
                                                                          node:nodeToAnimate
                                                                         simon:simon];
            [_actionQueue addObject:actionForQueue];
        }
    }
    _game.state = GameStateSequencePlaying;
}
-(void) playActionSequence {
    if (!_isActionPlaying) {
        if (_actionQueue.count > 0) {
            _isActionPlaying = YES;

            SOQueuedAction *action = [_actionQueue firstObject];

            if (action.simon.opposite) {
                [action.sprite addChild:[[SKSpriteNode alloc]initWithColor:[SKColor blackColor] size:CGSizeMake(action.sprite.size.width*0.85, action.sprite.size.height*0.85)]];
            }

            [action.sprite runAction:action.action completion:^{
                [action.sprite removeAllChildren];
                _isActionPlaying = NO;
                if (_actionQueue.count == 0) {
                    _game.state = GameStateWaitingForInput;
                    [self playActionSequence];
                }
            }];

            [_actionQueue removeObjectAtIndex:0];
        }
    }
}
-(void) doQuickFadeAnimation:(SKSpriteNode *)sprite {
    SKColor *originalColor = sprite.color;
    [sprite runAction:[SKAction colorizeWithColor:[SKColor whiteColor]
                             colorBlendFactor:0.5 duration:0.2] completion:^ {
        [sprite runAction:[SKAction colorizeWithColor:originalColor
                                 colorBlendFactor:0.5 duration:0.2]];
    }];
}

#pragma mark - UI Elements
-(SKLabelNode *) titleLabel:(NSString *)text {
    SKLabelNode *titleLabel = [[SKLabelNode alloc]initWithFontNamed:@"Arial"];
    titleLabel.text = text;
    titleLabel.fontSize = 30;
    titleLabel.fontColor = [SKColor blackColor];
    return titleLabel;
}
-(SKLabelNode *) defaultSizeLabel:(NSString *)text {
    SKLabelNode *defaultSizeLabel = [[SKLabelNode alloc]initWithFontNamed:@"Arial"];
    defaultSizeLabel.text = text;
    defaultSizeLabel.fontColor = [SKColor blackColor];
    return defaultSizeLabel;
}
-(SKSpriteNode *) gamePieceForColor:(SKColor *)color position:(SimonSays)position {
    SKSpriteNode *gamePiece = [[SKSpriteNode alloc]initWithColor:color size:CGSizeMake(_initialScreenSize.width/3, _initialScreenSize.width/3)];
    gamePiece.name = [self nameForPosition:position];
    return gamePiece;
}
-(SKSpriteNode *) retryButton {
    SKSpriteNode *retryButton = [[SKSpriteNode alloc]initWithColor:[SKColor purpleColor] size:CGSizeMake(_initialScreenSize.width/2, _initialScreenSize.height/5)];
    retryButton.name = @"retryButton";

    SKLabelNode *retryLabel = [self defaultSizeLabel:@"Retry?"];
    retryLabel.name = @"retryButton";
    [retryButton addChild:retryLabel];

    return retryButton;
}

@end

我认为我的代码是相当干净的整体,但我总是开放给任何类型的批评。如果有任何重大问题,它们可能在GameScene类中。

下面是游戏的截图:

EN

回答 2

Code Review用户

回答已采纳

发布于 2014-12-05 23:26:48

代码语言:javascript
运行
复制
@property NSMutableArray *sequence;

所以,我只想看看我们课上的这个属性。目前,它是我们类中的一个可公开使用的属性。没有理由有人不能来写这段代码:

代码语言:javascript
运行
复制
[soGameInstance.sequence removeAllObjects];
[soGameInstance.sequence addObject:[NSNull null]];
[soGameInstance evaluateMove:SimonSaysTopLeft];

现在你得到了一个异常,听起来像这样:

NSNull不响应选择器“getSimonSays”

我只浏览了剩下的代码(因为这个公开公开的可变数组让我很困扰),所以我不确定当前代码究竟是如何访问这个数组的,但它需要这样做吗?

我们确实需要一个内部数组来跟踪移动序列,这是事实。但是,在外部访问数组方面呢?

我们可以提供一个readonly不变数组:

代码语言:javascript
运行
复制
@property (readonly) NSArray *sequence;

它只会返回内部序列数组的副本:

代码语言:javascript
运行
复制
- (NSArray *)sequence {
     return [NSArray arrayWithArray:_sequence];
}

(我们在NSMutableArray *_sequence;文件中声明了.m )。

或者,如果我们只是提供一种获取特定序列号的方法,我可能会更喜欢:

代码语言:javascript
运行
复制
- (SOSimon *)simonSaysAtIndex:(NSUInteger)index {
    return (_sequence.count > index) ? _sequence[index] : nil;
}

还有一个小纸条:

代码语言:javascript
运行
复制
-(NSMutableArray *) sequence:(NSMutableArray *)oldSequence {
    NSMutableArray *newSequence = [[NSMutableArray alloc]init];
    if (oldSequence) {
        newSequence = [[NSMutableArray alloc]initWithArray:oldSequence];
    }
    [newSequence addObject:[[SOSimon alloc]init]];
    return newSequence;
}

在这里,当oldSequence为非零时,我们不必要地加倍分配数组。相反,我们应该这样做:

代码语言:javascript
运行
复制
NSMutableArray *newSequence;
if (oldSequence) {
    newSequence = [NSMutableArray arrayWithArray:oldSequence];
} else {
    newSequence = [NSMutableArray array];
}
[newSequence addObject:[[SOSimon alloc] init]];
return newSequence;

不过,为什么我们不能直接这么做呢?

代码语言:javascript
运行
复制
if (!oldSequence) {
    oldSequence = [NSMutableArray array];
}
[oldSequence addObject:[[SOSimon alloc] init]];
return oldSequence;

不管是哪种方式,这个方法看起来都有点奇怪。

票数 4
EN

Code Review用户

发布于 2014-12-06 16:32:03

SOGame.m类

验证逻辑可以改进:

代码语言:javascript
运行
复制
// Use shorter syntax to access array elements
SOSimon *simon = _sequence[_selectionNumber];

// Remove duplicated code
NSUInteger correctChoice = simon.opposite ? [self opposititePosition:simon.simonSays] : simon.simonSays;

if (playerChoice == correctChoice) {
    [self doSuccessfulChoice];
} else {
    _state = GameStateFailure;
}

SOSimonSays.h

SimonSaysNumChoices放入您的枚举将混淆自动完成和警告系统的道路。为它创建一个单独的常量。

票数 2
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/71819

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档