我今天为这里提到的社区挑战做了这个小游戏。游戏的理念是,有时当西蒙给你一种颜色,你应该选择相反的颜色(在对角的颜色)。游戏视觉上表明这是当它是相反的西蒙。我不完全确定游戏是否“有趣”,但它绝对是有趣的!
首先,这里是游戏模型:
#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
#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
#import <Foundation/Foundation.h>
#import "SOSimonSays.h"
@interface SOSimon : NSObject
@property SimonSays simonSays;
@property BOOL opposite;
@end
#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
typedef NS_ENUM(NSInteger, SimonSays){
SimonSaysTopLeft = 0,
SimonSaysTopRight,
SimonSaysBottomLeft,
SimonSaysBottomRight,
SimonSaysNumChoices
};
typedef NS_ENUM(NSInteger, GameState){
GameStateStarted = 0,
GameStateSequenceReady,
GameStateSequencePlaying,
GameStateWaitingForInput,
GameStateSuccess,
GameStateFailure,
GameStateEnded
};
现在我们有了SKScene。这是完全工作在此刻,包括动画时,播放的序列,当玩家点击一个角落。最大的挑战是让动作按部就班地进行,而不是所有的动作同时进行。为此,我创建了一个名为SOQueuedAction
的特殊类:
#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
#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
这基本上只是存储要执行的动作和执行它的雪碧,然后游戏场景一次处理一个动作:
#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类中。
下面是游戏的截图:
发布于 2014-12-05 23:26:48
@property NSMutableArray *sequence;
所以,我只想看看我们课上的这个属性。目前,它是我们类中的一个可公开使用的属性。没有理由有人不能来写这段代码:
[soGameInstance.sequence removeAllObjects];
[soGameInstance.sequence addObject:[NSNull null]];
[soGameInstance evaluateMove:SimonSaysTopLeft];
现在你得到了一个异常,听起来像这样:
NSNull不响应选择器“getSimonSays”
我只浏览了剩下的代码(因为这个公开公开的可变数组让我很困扰),所以我不确定当前代码究竟是如何访问这个数组的,但它需要这样做吗?
我们确实需要一个内部数组来跟踪移动序列,这是事实。但是,在外部访问数组方面呢?
我们可以提供一个readonly
不变数组:
@property (readonly) NSArray *sequence;
它只会返回内部序列数组的副本:
- (NSArray *)sequence {
return [NSArray arrayWithArray:_sequence];
}
(我们在NSMutableArray *_sequence;
文件中声明了.m
)。
或者,如果我们只是提供一种获取特定序列号的方法,我可能会更喜欢:
- (SOSimon *)simonSaysAtIndex:(NSUInteger)index {
return (_sequence.count > index) ? _sequence[index] : nil;
}
还有一个小纸条:
-(NSMutableArray *) sequence:(NSMutableArray *)oldSequence {
NSMutableArray *newSequence = [[NSMutableArray alloc]init];
if (oldSequence) {
newSequence = [[NSMutableArray alloc]initWithArray:oldSequence];
}
[newSequence addObject:[[SOSimon alloc]init]];
return newSequence;
}
在这里,当oldSequence
为非零时,我们不必要地加倍分配数组。相反,我们应该这样做:
NSMutableArray *newSequence;
if (oldSequence) {
newSequence = [NSMutableArray arrayWithArray:oldSequence];
} else {
newSequence = [NSMutableArray array];
}
[newSequence addObject:[[SOSimon alloc] init]];
return newSequence;
不过,为什么我们不能直接这么做呢?
if (!oldSequence) {
oldSequence = [NSMutableArray array];
}
[oldSequence addObject:[[SOSimon alloc] init]];
return oldSequence;
不管是哪种方式,这个方法看起来都有点奇怪。
发布于 2014-12-06 16:32:03
SOGame.m类
验证逻辑可以改进:
// 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;
}
将SimonSaysNumChoices
放入您的枚举将混淆自动完成和警告系统的道路。为它创建一个单独的常量。
https://codereview.stackexchange.com/questions/71819
复制相似问题