不久前,我在这个问题上发布了一个关于员工AI和工作排队的策略游戏。我已经发布了关于作业队列的其他问题,现在我想发布这个修改过的问题,特别是关于游戏中工人的运动类。我认为这将有助于进行一次良好的代码评审。
其基本思想是,每次转弯时,都会触发更新方法,该方法根据员工的当前状态和不同楼层上的可用作业来计算目标层。DwarfMovement类计算要移动到的地板,移动类处理基本运动本身。敌人、动物和侏儒运动阶级都继承了运动阶级。
我花了很多时间清理这段代码,但我确信还有一些东西我可以改进。我知道这是很多代码,但请随时评论小事情。这些类型的答案也很有帮助。
#import <Foundation/Foundation.h>
@interface DTMovement : NSObject <NSCoding>
-(id) initWithWorldSize:(CGSize)worldSize;
#pragma mark - Position Properties
@property CGPoint currentPosition;
@property CGPoint destinationPosition;
@property int currentFloor;
@property int destinationFloor;
#pragma mark - Update
-(void) doMovement;
#pragma mark - Advanced Movement
-(void) doIdleMovement;
-(void) doVerticalMovement;
-(void) doFloorMovement;
-(void) pickRandomDestinationOnCurrentFloor;
#pragma mark - Simple Movement
-(void) moveUp;
-(void) moveDown;
-(void) moveLeft;
-(void) moveRight;
#pragma mark - Postion Calculations
-(int) currentFloorByPosition;
-(int) closestFloor:(NSMutableArray *)possibleFloors;
-(void) calculateFloorExitPositionByFloor;
-(void) calculateDestinationPositionByFloor;
-(BOOL) isAtVerticalDestinationPosition;
-(BOOL) isAtFloorDestinationPosition;
-(BOOL) shouldMoveUp;
-(BOOL) shouldMoveRight;
#pragma mark - Tower Change
-(void) transitionToNewTower;
@end#import "DTMovement.h"
#import "DTTowerFloor.h"
static int const kMinVerticalStep = 18;
static int const kMinHorizontalStep = 18;
@implementation DTMovement {
CGSize _worldSize;
}
-(id) initWithWorldSize:(CGSize)worldSize {
self = [super init];
if (self) {
_worldSize = worldSize;
}
return self;
}
#pragma mark - Update
-(void) doMovement {
//overridden by subclasses
}
#pragma mark - Vertical Movement
-(void) doVerticalMovement {
//the destination floor could change so this needs to update each tick
[self calculateDestinationPositionByFloor];
//once close enough to the destination, jump to the destination
if ([self isAtVerticalDestinationPosition]) {
[self arriveAtDestinationFloor];
} else {
if ([self shouldMoveUp]) {
[self moveUp];
} else {
[self moveDown];
}
}
}
-(BOOL) isAtVerticalDestinationPosition {
int distanceFromDestination = self.currentPosition.y - self.destinationPosition.y;
int minimumDistance = kMinVerticalStep;
return (!(distanceFromDestination > minimumDistance || distanceFromDestination < -minimumDistance));
}
-(BOOL) shouldMoveUp {
int distanceFromDestination = self.currentPosition.y - self.destinationPosition.y;
return distanceFromDestination < 0;
}
-(void) arriveAtDestinationFloor {
//overridden by subclasses
}
#pragma mark - Horizontal Movement
-(void) doFloorMovement {
//overridden by subclasses
}
-(BOOL) isAtFloorDestinationPosition {
int distanceFromDestination = self.currentPosition.x - self.destinationPosition.x;
int minimumDistance = kMinHorizontalStep;
return (!(distanceFromDestination > minimumDistance || distanceFromDestination < -minimumDistance));
}
-(BOOL) shouldMoveRight {
int distanceFromDestination = self.currentPosition.x - self.destinationPosition.x;
return distanceFromDestination < 0;
}
#pragma mark - Idle Movement
-(void) doIdleMovement {
//move randomly left and right, prevent moving past the walls of the floor
CGPoint tempPosition;
int moveLeftOrRight = arc4random_uniform(2);
if (moveLeftOrRight == 0) {
tempPosition = CGPointMake(self.currentPosition.x + kMinHorizontalStep, self.currentPosition.y);
} else if (moveLeftOrRight == 1) {
tempPosition = CGPointMake(self.currentPosition.x - kMinHorizontalStep, self.currentPosition.y);
}
//bounds checking
if (tempPosition.x < - _worldSize.width/2) {
tempPosition.x = -_worldSize.width/2 + kMinHorizontalStep;
}
if (tempPosition.x > _worldSize.width/2){
tempPosition.x = _worldSize.width/2 - kMinHorizontalStep;
}
self.currentPosition = tempPosition;
}
#pragma mark - Simple Movement
-(void) moveUp {
self.currentPosition = CGPointMake(0, self.currentPosition.y + _worldSize.height / kMinVerticalStep);
}
-(void) moveDown {
self.currentPosition = CGPointMake(0, self.currentPosition.y - _worldSize.height / kMinVerticalStep);
}
-(void) moveLeft {
self.currentPosition = CGPointMake(self.currentPosition.x - _worldSize.width / kMinHorizontalStep, self.currentPosition.y);
}
-(void) moveRight {
self.currentPosition = CGPointMake(self.currentPosition.x + _worldSize.width / kMinHorizontalStep, self.currentPosition.y);
}
#pragma mark - Position Calculations
-(int) closestFloor:(NSMutableArray *)possibleFloors {
int currentFloor = [self currentFloorByPosition];
int destinationFloor = 0;
int bestCount = 1000; //to guarantee it gets assigned on the first try
int currentCount = 0;
for (DTTowerFloor *floor in possibleFloors) {
//find out what floor has the lowest count of distance
if (currentFloor > floor.floorNumber) {
for (int i = currentFloor; i > floor.floorNumber; i--) {
currentCount++;
}
} else {
for (int i = currentFloor; i < floor.floorNumber; i++) {
currentCount++;
}
}
//set the destination to the floor with lowest count
if (currentCount < bestCount) {
destinationFloor = floor.floorNumber;
bestCount = currentCount;
}
currentCount = 0;
}
return destinationFloor;
}
-(int) currentFloorByPosition {
int floorSize = _worldSize.height/6;
return self.currentPosition.y / floorSize;
}
-(void) calculateDestinationPositionByFloor {
self.destinationPosition = CGPointMake(0, self.destinationFloor * _worldSize.height/6 - _worldSize.height/6/3);
}
-(void) calculateFloorExitPositionByFloor {
int currentFloor = [self currentFloorByPosition];
int floorY = currentFloor * _worldSize.height/6 - _worldSize.height/6/3;
self.destinationPosition = CGPointMake(0, floorY);
}
-(void) pickRandomDestinationOnCurrentFloor {
int randomNumber = arc4random_uniform(4) + 1;
int randomXPosition = (_worldSize.width/3)/randomNumber;
self.destinationPosition = CGPointMake(randomXPosition, self.currentPosition.y);
}
#pragma mark - Tower Change
-(void) transitionToNewTower {
//overridden by subclasses
}
#pragma mark - NSCoding methods
-(id) initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self) {
_currentPosition = [aDecoder decodeCGPointForKey:@"currentPosition"];
_destinationPosition = [aDecoder decodeCGPointForKey:@"destinationPosition"];
_currentFloor = [aDecoder decodeIntForKey:@"currentFloor"];
_destinationFloor = [aDecoder decodeIntForKey:@"destinationFloor"];
_worldSize = [aDecoder decodeCGSizeForKey:@"worldSize"];
}
return self;
}
-(void) encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeCGPoint:self.currentPosition forKey:@"currentPosition"];
[aCoder encodeCGPoint:self.destinationPosition forKey:@"destinationPosition"];
[aCoder encodeInt:self.currentFloor forKey:@"currentFloor"];
[aCoder encodeInt:self.destinationFloor forKey:@"destinationFloor"];
[aCoder encodeCGSize:_worldSize forKey:@"worldSize"];
}
@end#import <Foundation/Foundation.h>
#import "DTMovement.h"
#import "DTDwarfMovementState.h"
@interface DTDwarfMovement : DTMovement <NSCoding>
-(id) initWithWorldSizeForCharacter:(CGSize)worldSize;
@property DwarfMovementState movementState;
@property BOOL isTiredOrHungry;
-(void) acceptFloorList:(NSMutableArray *)floorList;
-(void) acceptFilteredFloorListForWork:(NSMutableArray *)filteredFloorList;
@end这是工人的大部分人工智能。
#import "DTDwarfMovement.h"
#import "DTTowerFloor.h"
/*
Each turn if the dwarf is in a correct state, position is updated in the doMovement method
Lists of valid floors are loaded in before the destination is computed
There are different computations for special cases such as hunger, idle, playing
Movement is computed in the super class, and it is restricted to up down left and right
Typically when at a destination, state changes
Some states are advanced by the Tower or TowerFloor
*/
@implementation DTDwarfMovement {
NSMutableArray *_floorList;
NSMutableArray *_floorListForWork;
}
-(id) initWithWorldSizeForCharacter:(CGSize)worldSize {
self = [super initWithWorldSize:worldSize];
if (self) {
_floorList = [[NSMutableArray alloc]init];
_floorListForWork = [[NSMutableArray alloc]init];
}
return self;
}
#pragma mark - Floor List Handling
-(void) acceptFloorList:(NSMutableArray *)floorList {
_floorList = floorList;
}
-(void) acceptFilteredFloorListForWork:(NSMutableArray *)filteredFloorList {
_floorListForWork = filteredFloorList;
}
#pragma mark - Checking For Jobs
-(BOOL) checkFloorsForJobs {
for (DTTowerFloor *floor in _floorListForWork) {
if ([floor areJobSlotsAvailable]) {
return YES;
}
}
return NO;
}
#pragma mark - Update Movement
-(void) doMovement {
switch (self.movementState) {
//in the idle state, if not hungry or tired, try to choose a job, if no job found, idle
case DwarfMovementStateIdle:
if (!self.isTiredOrHungry) {
[self tryToChooseAJobWhileIdle];
}
break;
//once at the floor, if no jobs available, idle
case DwarfMovementStateAtDestinationFloor:
if (![self checkFloorsForJobs]) {
self.movementState = DwarfMovementStateIdle;
}
break;
//this is the state when a dwarf first finds a job
case DwarfMovementStateNeedsMoving:
[self calculateFloorExitPositionByFloor];
self.movementState = DwarfMovementStateToFloorExit;
break;
//basic movement between floors and to positions on floors
case DwarfMovementStateAtFloorExit:
self.movementState = DwarfMovementStateToFloor;
break;
case DwarfMovementStateToFloor:
[self doVerticalMovement];
break;
case DwarfMovementStateToJobPosition:
[self doFloorMovement];
break;
case DwarfMovementStateToFloorExit:
[self doFloorMovement];
break;
case DwarfMovementStateToRandomPositionForEating:
[self doFloorMovement];
break;
case DwarfMovementStateToRandomPositionForResting:
[self doFloorMovement];
break;
//just randomly moves around, mostly up and down as the destination floor constantly changes
case DwarfMovementStatePlaying:
[self handlePlayingMovement];
break;
//deal with specific movement states
case DwarfMovementStateToStockpile:
[self pickDestinationFloorForStockpile];
[self doMovementForMovementState:DwarfMovementStateToStockpile];
break;
case DwarfMovementStateToSlaughterhouse:
[self pickDestinationFloorForSlaughterhouse];
[self doMovementForMovementState:DwarfMovementStateToSlaughterhouse];
break;
case DwarfMovementStateToPasture:
[self pickDestinationFloorForPasture];
[self doMovementForMovementState:DwarfMovementStateToPasture];
break;
case DwarfMovementStateHunger:
[self pickDestinationFloorForEating];
[self doMovementForMovementState:DwarfMovementStateHunger];
break;
case DwarfMovementStateResting:
[self pickDestinationFloorForResting];
[self doMovementForMovementState:DwarfMovementStateResting];
break;
case DwarfMovementStateFleeingFromEnemy:
{
int currentFloor = [self currentFloorByPosition];
self.destinationFloor = currentFloor + 1; //makes the dwarf flee one floor up
[self doMovementForMovementState:DwarfMovementStateFleeingFromEnemy];
break;
}
//special case for setting up moving to a new tower
case DwarfMovementStateToNewTower:
[self handleMovingToNewTower];
break;
default:
break;
}
}
-(void) tryToChooseAJobWhileIdle {
[self pickDestinationFloorForWork];
[self calculateDestinationPositionByFloor];
if (self.currentFloor != self.destinationFloor) {
self.movementState = DwarfMovementStateNeedsMoving;
} else {
if ([self checkFloorsForJobs]) {
self.movementState = DwarfMovementStateNeedsMoving;
} else {
[self doIdleMovement];
}
}
}
-(void) handlePlayingMovement {
[self pickDestinationFloorForPlaying];
[self calculateDestinationPositionByFloor];
if (self.currentFloor != self.destinationFloor) {
[self doVerticalMovement];
} else {
[self doIdleMovement];
}
}
-(void) handleMovingToNewTower {
[self pickDestinationFloorForGateway];
[self calculateDestinationPositionByFloor];
if (![self isAtFloorDestinationPosition]) {
[self doFloorMovement];
} else if (![self isAtVerticalDestinationPosition]) {
[self doVerticalMovement];
} else {
self.movementState = DwarfMovementStateReadyToMoveToNewTower;
}
}
-(void) doMovementForMovementState:(DwarfMovementState)movementState {
//the destination floor has already been chosen
[self calculateDestinationPositionByFloor];
if ([self doBasicMovementToFloor]) {
switch (movementState) {
case DwarfMovementStateToStockpile:
self.movementState = DwarfMovementStateAtStockpile;
break;
case DwarfMovementStateToSlaughterhouse:
self.movementState = DwarfMovementStateAtSlaughterhouse;
break;
case DwarfMovementStateToPasture:
self.movementState = DwarfMovementStateAtPasture;
break;
case DwarfMovementStateHunger:
self.movementState = DwarfMovementStateToRandomPositionForEating;
[self pickRandomDestinationOnCurrentFloor];
break;
case DwarfMovementStateResting:
self.movementState = DwarfMovementStateToRandomPositionForResting;
[self pickRandomDestinationOnCurrentFloor];
break;
case DwarfMovementStateFleeingFromEnemy:
//do nothing yet
break;
default:
break;
}
}
}
-(BOOL) doBasicMovementToFloor {
if (![self isAtFloorDestinationPosition]) {
[self doFloorMovement];
} else if (![self isAtVerticalDestinationPosition]) {
[self doVerticalMovement];
} else {
[self arriveAtDestinationFloor];
return YES;
}
return NO;
}
#pragma mark - Overrides
-(void) arriveAtDestinationFloor {
//these have to be set this way to make sure movement remains aligned
self.currentPosition = self.destinationPosition;
self.currentFloor = self.destinationFloor;
self.movementState = DwarfMovementStateAtDestinationFloor;
}
-(void) doFloorMovement {
//once close enough to the destination, jump to the destination
//all of these special cases specifically route the dwarf based on their movement state
//once the state switches the appropriate logic will run on the next tick
if ([self isAtFloorDestinationPosition]) {
if (self.movementState == DwarfMovementStateToFloorExit) {
self.currentPosition = self.destinationPosition;
self.movementState = DwarfMovementStateAtFloorExit;
}
if (self.movementState == DwarfMovementStateToJobPosition) {
self.currentPosition = self.destinationPosition;
self.movementState = DwarfMovementStateInPositionForWork;
}
if (self.movementState == DwarfMovementStateToRandomPositionForEating) {
self.currentPosition = self.destinationPosition;
self.movementState = DwarfMovementStateInPositionForEating;
}
if (self.movementState == DwarfMovementStateToRandomPositionForResting) {
self.currentPosition = self.destinationPosition;
self.movementState = DwarfMovementStateInPositionForResting;
}
} else {
if ([self shouldMoveRight]) {
[self moveRight];
} else {
[self moveLeft];
}
}
}
#pragma mark - Pick Destination Floors
-(void) pickDestinationFloorForWork {
NSMutableArray *possibleFloorsArray = [[NSMutableArray alloc]init];
for (DTTowerFloor *floor in _floorListForWork) {
if ([floor areJobSlotsAvailable]) {
[possibleFloorsArray addObject:floor];
}
}
if (possibleFloorsArray.count > 0) {
self.destinationFloor = [self closestFloor:possibleFloorsArray];
}
}
-(void) pickDestinationFloorForEating {
NSMutableArray *possibleFloorsArray = [[NSMutableArray alloc]init];
for (DTTowerFloor *floor in _floorList) {
if (floor.room.roomType == Foodhall) {
[possibleFloorsArray addObject:floor];
}
if (floor.room.roomType == GatewayExit) {
[possibleFloorsArray addObject:floor];
}
}
if (possibleFloorsArray.count > 0) {
self.destinationFloor = [self closestFloor:possibleFloorsArray];
} else {
self.destinationFloor = 0;
}
}
-(void) pickDestinationFloorForResting {
NSMutableArray *possibleFloorsArray = [[NSMutableArray alloc]init];
for (DTTowerFloor *floor in _floorList ) {
if (floor.room.roomType == Dormitory) {
[possibleFloorsArray addObject:floor];
}
if (floor.room.roomType == GatewayExit) {
[possibleFloorsArray addObject:floor];
}
}
if (possibleFloorsArray.count > 0) {
self.destinationFloor = [self closestFloor:possibleFloorsArray];
} else {
self.destinationFloor = 0;
}
}
-(void) pickDestinationFloorForGateway {
NSMutableArray *possibleFloorsArray = [[NSMutableArray alloc]init];
for (DTTowerFloor *floor in _floorList) {
if (floor.room.roomType == Gateway) {
[possibleFloorsArray addObject:floor];
}
}
if (possibleFloorsArray.count > 0) {
self.destinationFloor = [self closestFloor:possibleFloorsArray];
} else {
self.destinationFloor = 0;
}
}
-(void) pickDestinationFloorForStockpile {
NSMutableArray *possibleFloorsArray = [[NSMutableArray alloc]init];
for (DTTowerFloor *floor in _floorList) {
BOOL foundAStockpile = NO;
if (floor.room.roomType == RoomTypeStockpile) {
foundAStockpile = YES;
[possibleFloorsArray addObject:floor];
}
if (!foundAStockpile) {
if (floor.room.roomType == GatewayExit) {
[possibleFloorsArray addObject:floor];
}
}
}
if (possibleFloorsArray.count > 0) {
self.destinationFloor = [self closestFloor:possibleFloorsArray];
} else {
self.destinationFloor = 0;
}
}
-(void) pickDestinationFloorForSlaughterhouse {
NSMutableArray *possibleFloorsArray = [[NSMutableArray alloc]init];
for (DTTowerFloor *floor in _floorList) {
BOOL foundASlaughterhouse = NO;
if (floor.room.roomType == Slaughterhouse) {
foundASlaughterhouse = YES;
[possibleFloorsArray addObject:floor];
}
if (!foundASlaughterhouse) {
if (floor.room.roomType == GatewayExit) {
[possibleFloorsArray addObject:floor];
}
}
}
if (possibleFloorsArray.count > 0) {
self.destinationFloor = [self closestFloor:possibleFloorsArray];
} else {
self.destinationFloor = 0;
}
}
-(void) pickDestinationFloorForPasture {
NSMutableArray *possibleFloorsArray = [[NSMutableArray alloc]init];
for (DTTowerFloor *floor in _floorList) {
BOOL foundAPasture = NO;
if (floor.room.roomType == RoomTypePasture) {
foundAPasture = YES;
[possibleFloorsArray addObject:floor];
}
if (!foundAPasture) {
if (floor.room.roomType == GatewayExit) {
[possibleFloorsArray addObject:floor];
}
}
}
if (possibleFloorsArray.count > 0) {
self.destinationFloor = [self closestFloor:possibleFloorsArray];
} else {
self.destinationFloor = 0;
}
}
-(void) pickDestinationFloorForPlaying {
if (_floorList.count > 0) {
int randomNumber = arc4random_uniform((int)_floorList.count);
DTTowerFloor *floor = [_floorList objectAtIndex:randomNumber];
self.destinationFloor = floor.floorNumber;
} else {
BOOL gatewayExitPresent = NO;
for (DTTowerFloor *floor in _floorList) {
if (floor.room.roomType == GatewayExit) {
gatewayExitPresent = YES;
self.destinationFloor = floor.floorNumber;
}
}
if (!gatewayExitPresent) {
self.destinationFloor = 0;
}
}
}
#pragma mark - Tower Transition
-(void) transitionToNewTower {
[self calculateDestinationPositionByFloor];
[self arriveAtDestinationFloor];
}
#pragma mark - NSCoding methods
-(id) initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
_movementState = [aDecoder decodeIntegerForKey:@"dwarfMovementState"];
_isTiredOrHungry = [aDecoder decodeBoolForKey:@"isTiredOrHungry"];
_floorList = [[NSMutableArray alloc]init];
_floorListForWork = [[NSMutableArray alloc]init];
}
return self;
}
-(void) encodeWithCoder:(NSCoder *)aCoder {
[super encodeWithCoder:aCoder];
[aCoder encodeInteger:self.movementState forKey:@"dwarfMovementState"];
[aCoder encodeBool:self.isTiredOrHungry forKey:@"isTiredOrHungry"];
}
@end发布于 2014-07-22 11:42:12
-(void) tryToChooseAJobWhileIdle你不需要说“尝试”。我认为这个方法名可以称为"chooseJobWhileIdle“,甚至可能是"lookForJob”、"findJob",或者可能是"idleJobSearch“。目前的方法名称是冗长的,是的,我们喜欢目标-C中的名称。我们想要的是描述性的,自我记录的,但不要太过。
方法名称应该向开发人员传达方法所做的重要事情。对于开发人员来说,这种方法可能找不到工作并不特别重要。如果是的话,开发商怎么会知道侏儒找不到工作呢?如果我们想让开发人员知道这个方法可能找不到工作,我们可以通过返回一个表示成功/失败的BOOL并让他根据这些知识进行操作来让他知道。
-(void) pickDestinationFloorForEating您有一些这样的方法,所有这些方法的逻辑都是相同的。逻辑应该合并成一个单一的方法。我将这些方法作为方便的方法保存在这里,但是实际的逻辑应该存在于一个方法中。这可能意味着重构实际的逻辑,但最终,当您返回到添加更多这类方法时,情况会更好。
if (currentFloor > floor.floorNumber) {
for (int i = currentFloor; i > floor.floorNumber; i--) {
currentCount++;
}
} else {
for (int i = currentFloor; i < floor.floorNumber; i++) {
currentCount++;
}
}并不是循环中的循环总是可以避免的,但是它们应该是一个可以停止的标志,并确保它们不能被避免。循环是获得一小块代码以多次执行的有效方法.因此,循环中的循环是一种让循环(每次执行它的身体几次)执行几次的方法。您可以花费大量时间执行嵌套循环。
在这里这是不必要的。所有这些代码都被以下代码替换:
currentCount += abs(currentFloor - floor.floorNumber);您可以删除每次通过外部循环检查if条件所需的时间、检查for条件所需的时间,以及每次通过内环增加/减少两个变量所需的时间。您还完全删除了循环迭代变量。
老实说,在这种方法中,我认为这是唯一修改currentCount的地方,所以我们可以在每个循环的末尾将它重置为0,然后简单地这样做:
currentCount = abs(currentFloor - floor.floorNumber);这可能比+=略高一些,并且可以节省每次迭代将currentCount重置为0的时间。
https://codereview.stackexchange.com/questions/57645
复制相似问题