思路
要求设计思路是类似手持拼图游戏,拼图需求要求有一块为空白版,作为移动方块的预留位置用,通过选择图片后在起初对所有图像方块随机打乱顺序时,发现随机打乱顺序,没办法拼图完成,拼图移动是空白快最临近的上下左右四个图像块的移动,在打乱顺序的时候,也要按照这个算法逻辑实现,才能拼图完成;
另外逻辑实现上,用tag来记录图片,用accessibilityValue 来记录图片的实际位置标记;
用三个数组来实现顺序打乱、正序校验、拼图位置的校验等,起初对三个数组进行相同的初始化值;
实现
变量及相关初始化
///次序,用来排序 @property (nonatomic,strong) NSMutableArray * orderArray; ///次序,用来乱序打乱拼图 @property (nonatomic,strong) NSMutableArray * disorderArray; ///次序,用来拼图移动位置记录 @property (nonatomic,strong) NSMutableArray * puzzleArray; ///图片原图 @property (nonatomic,strong) UIImage * puzzleImage; ///行、列数【难度】 @property (nonatomic,assign) NSInteger rows; ///方块图间距 @property (nonatomic,assign) CGFloat itemSpace; ///四周边距 @property (nonatomic,assign) CGFloat marginSpace; ///是否允许拼图 @property (nonatomic,assign) BOOL allowJoint; ///拖动拼图 @property (nonatomic,strong) UIImageView * panImageView; ///拖动拼图Frame @property (nonatomic,assign) CGRect panImageFrame;
- (instancetype)initWithFrame:(CGRect)frame rows:(NSInteger)rows puzzleImage:(UIImage *)puzzleImage{ self = [super initWithFrame:frame]; if (self) { _rows = rows; _puzzleImage = puzzleImage; [self setupPP]; } return self; } - (void)setupPP{ self.userInteractionEnabled = NO; _allowJoint = YES; _orderArray = [NSMutableArray array]; _disorderArray = [NSMutableArray array]; _puzzleArray = [NSMutableArray array]; // _rows = 6; _itemSpace = floor(_rows*xkScale/(_rows/2)); _marginSpace = floor(_rows*xkScale); self.backgroundColor = [UIColor whiteSmoke]; [self setupOrderArray:(_rows * _rows)]; ///如果图片的大小大于当前宽度,就压缩 if (_puzzleImage.size.width > CGRectGetWidth(self.frame)) { UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 1); [_puzzleImage drawInRect:CGRectMake(0,0,self.bounds.size.width,self.bounds.size.height)]; UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); _puzzleImage = newImage; } CGFloat pWidth = (CGRectGetWidth(self.frame) - _itemSpace*(_rows-1) - _marginSpace*2)/_rows; CGFloat pHeight = (CGRectGetHeight(self.frame) - _itemSpace*(_rows-1) - _marginSpace*2)/_rows; for (int i = 0; i < _rows; i ++) { for (int j = 0; j < _rows; j ++) { NSInteger order = _rows * i + j; NSLog(@"order = %ld",order); /* NSInteger indexes_x = 0; NSInteger indexes_y = 0; if (order < (_rows *_rows) - 1) { NSInteger location = [_disorderArray[order] integerValue]; indexes_y = location/_rows;///第几行 indexes_x = location%_rows;///第几个 } else{ indexes_y = _rows - 1; indexes_x = _rows - 1; } CGFloat x_img = _marginSpace + (indexes_x)*(pWidth + _itemSpace); CGFloat y_img = _marginSpace + (indexes_y)*(pHeight + _itemSpace); */ CGFloat x = _marginSpace + (j)*(pWidth + _itemSpace); CGFloat y = _marginSpace + (i)*(pHeight + _itemSpace); UIImageView *imgView = [self puzzleImageWithFrame:CGRectMake(x, y, pWidth, pHeight)]; //将UIImage转化成CGImage CGImageRef imageRef = CGImageCreateWithImageInRect(_puzzleImage.CGImage, CGRectMake(x, y, pWidth, pHeight)); //将CGImage转化成UIImage UIImage *imageNew = [UIImage imageWithCGImage:imageRef]; imgView.image = imageNew; ///用来标记view imgView.tag = order + 1; ///用来记录view位置 imgView.accessibilityValue = [NSString stringWithFormat:@"%ld",order + 1]; [self addSubview:imgView]; if (imgView.tag == (_rows * _rows)) { imgView.image = [UIImage imageNamed:@"pp_chunk"]; imgView.backgroundColor = [UIColor whiteSmoke]; } } } dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 1.5 * NSEC_PER_SEC); dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ [self startDisorganizePuzzleImage]; }); }
- (void)setupOrderArray:(NSInteger)count{ for (int i = 1; i<=count; i ++) { [_orderArray addObject:[NSString stringWithFormat:@"%d",i]]; [_disorderArray addObject:[NSString stringWithFormat:@"%d",i]]; [_puzzleArray addObject:[NSString stringWithFormat:@"%d",i]]; } }
打乱拼图顺序
打乱拼图顺序的算法和规则,可以根据打乱的程度或者次数,通过递归添加结束条件
- (void)setupDisorganizePuzzleImageNumber:(NSInteger)number{ if (number <= 0) { self.userInteractionEnabled = YES; return; } self.userInteractionEnabled = NO; ///获取空白格 UIImageView *emImg = [self viewWithTag:(_rows * _rows)]; ///获取空白格的位置 NSInteger emLocation = [emImg.accessibilityValue integerValue]; ///通过空白格位置,获取四周可以移动的格子的位置与tag NSMutableArray *arrayLoc = [NSMutableArray array]; NSInteger upLocation = emLocation - _rows; NSInteger downLocation = emLocation + _rows; NSInteger leftLocation = emLocation - 1; NSInteger righjtLocation = emLocation + 1; if (upLocation > 0) {///上 [arrayLoc addObject:@(upLocation)]; } if (downLocation <= (_rows*_rows)) {///下 [arrayLoc addObject:@(downLocation)]; } if (leftLocation%_rows != 0 && leftLocation <= (_rows*_rows)) {///左 [arrayLoc addObject:@(leftLocation)]; } if (righjtLocation%_rows != 1 && righjtLocation <= (_rows*_rows)) {///右 [arrayLoc addObject:@(righjtLocation)]; } ///随机获取一个转移目标 NSInteger random = arc4random() % arrayLoc.count; NSInteger targetLocation = [arrayLoc[random] integerValue]; NSInteger targetIndex = [_disorderArray indexOfObject:[NSString stringWithFormat:@"%ld",targetLocation]]; ///获取目标试图 UIImageView *targetImg = [self viewWithTag:targetIndex + 1]; if (targetImg) { CGRect targetRect = CGRectMake(CGRectGetMinX(targetImg.frame), CGRectGetMinY(targetImg.frame), CGRectGetWidth(targetImg.frame), CGRectGetHeight(targetImg.frame)); CGRect emRect = CGRectMake(CGRectGetMinX(emImg.frame), CGRectGetMinY(emImg.frame), CGRectGetWidth(emImg.frame), CGRectGetHeight(emImg.frame)); [UIView animateWithDuration:0.01 animations:^{ emImg.frame = targetRect; targetImg.frame = emRect; } completion:^(BOOL finished) { ///处理交换【打乱次序】 NSInteger emIndex = [_disorderArray indexOfObject:emImg.accessibilityValue]; [_disorderArray exchangeObjectAtIndex:(targetIndex) withObjectAtIndex:(emIndex)]; ///切换保存顺序【拼图】 NSInteger accesTarget = [targetImg.accessibilityValue integerValue] - 1; NSInteger accesEm = [emImg.accessibilityValue integerValue] - 1; [_puzzleArray exchangeObjectAtIndex:(accesTarget) withObjectAtIndex:(accesEm)]; targetImg.accessibilityValue = [NSString stringWithFormat:@"%ld",emLocation]; emImg.accessibilityValue = [NSString stringWithFormat:@"%ld",targetLocation]; [self setupDisorganizePuzzleImageNumber:number - 1]; }]; } }
拼图点击手势(空白格不允许)
///拼图点击事件 - (void)puzzleImageTapClick:(UITapGestureRecognizer *)tap{ NSInteger tapTag = tap.view.tag; UIImageView *tapImg = [self viewWithTag:tapTag]; [self puzzleImageTapGestureHandler:tapImg]; } ///点击手势操作 - (void)puzzleImageTapGestureHandler:(UIImageView *)puzzleImage{ if (!_allowJoint) { return; } NSInteger emTag = (_rows * _rows); NSInteger tapTag = puzzleImage.tag; if (emTag == tapTag) { return; } UIImageView *emImg = [self viewWithTag:emTag]; UIImageView *tapImg = puzzleImage; CGFloat emMinX = floor(CGRectGetMinX(emImg.frame)); CGFloat emMaxX = floor(CGRectGetMaxX(emImg.frame)); CGFloat emMinY = floor(CGRectGetMinY(emImg.frame)); CGFloat emMaxY = floor(CGRectGetMaxY(emImg.frame)); CGFloat tapMinX = floor(CGRectGetMinX(tapImg.frame)); CGFloat tapMaxX = floor(CGRectGetMaxX(tapImg.frame)); CGFloat tapMinY = floor(CGRectGetMinY(tapImg.frame)); CGFloat tapMaxY = floor(CGRectGetMaxY(tapImg.frame)); BOOL isExchange = NO; if ((tapMinX == emMinX) && fabs((tapMaxY + _itemSpace) - emMinY) < 5*xkScale){ isExchange = YES; } else if ((tapMinX == emMinX) && fabs((emMaxY + _itemSpace) - tapMinY) < 5*xkScale){ isExchange = YES; } else if ((tapMinY == emMinY) && fabs((tapMaxX + _itemSpace) - emMinX) < 5*xkScale){ isExchange = YES; } else if ((tapMinY == emMinY) && fabs((emMaxX + _itemSpace) - tapMinX) < 5*xkScale){ isExchange = YES; } else{ isExchange = NO; } CGRect tapRect = CGRectMake(CGRectGetMinX(tapImg.frame), CGRectGetMinY(tapImg.frame), CGRectGetWidth(tapImg.frame), CGRectGetHeight(tapImg.frame)); CGRect emRect = CGRectMake(CGRectGetMinX(emImg.frame), CGRectGetMinY(emImg.frame), CGRectGetWidth(emImg.frame), CGRectGetHeight(emImg.frame)); if (isExchange) { NSLog(@"允许交换"); [UIView animateWithDuration:0.3 animations:^{ _allowJoint = NO; emImg.frame = tapRect; tapImg.frame = emRect; } completion:^(BOOL finished) { NSInteger accesTap = [tapImg.accessibilityValue integerValue]; NSInteger accesEm = [emImg.accessibilityValue integerValue]; ///因为accessibilityValue与tag一样,索引需要减1 [_puzzleArray exchangeObjectAtIndex:(accesTap - 1) withObjectAtIndex:(accesEm - 1)]; tapImg.accessibilityValue = [NSString stringWithFormat:@"%ld",accesEm]; emImg.accessibilityValue = [NSString stringWithFormat:@"%ld",accesTap]; _allowJoint = YES; if ([self isPuzzleImageFinish]) { NSLog(@"拼图完成"); [self puzzleImageFinishHandler]; } else{ NSLog(@"继续加油"); } }]; } else{ NSLog(@"不允许交换"); } }
拼图拖动手势(空白格不允许)
/// 拼图拖动 - (void)puzzleImagePanGesture:(UIPanGestureRecognizer *)pan{ _panImageView = (UIImageView *)pan.view; [self bringSubviewToFront:pan.view]; if (_panImageView.tag == (_rows * _rows)) {///空白格 } else{ if (pan.state == UIGestureRecognizerStateBegan) { _panImageFrame = pan.view.frame; _panImageView = (UIImageView *)pan.view; } else if (pan.state == UIGestureRecognizerStateChanged){ //获取偏移量 CGPoint transP = [pan translationInView:pan.view]; // 移动图片控件 CGRect tapRect = CGRectMake(CGRectGetMinX(pan.view.frame) + transP.x, CGRectGetMinY(pan.view.frame) + transP.y, CGRectGetWidth(pan.view.frame), CGRectGetHeight(pan.view.frame)); pan.view.frame = tapRect; // 复位,表示相对上一次位置复位重置 [pan setTranslation:CGPointZero inView:pan.view]; } else if (pan.state == UIGestureRecognizerStateEnded){ if (!_allowJoint) { [UIView animateWithDuration:0.1 animations:^{ _allowJoint = NO; _panImageView.frame = _panImageFrame; } completion:^(BOOL finished) { _allowJoint = YES; }]; return; } NSInteger emTag = (_rows * _rows); UIImageView *emImg = [self viewWithTag:emTag]; CGPoint point1 = _panImageView.center; CGPoint point2 = emImg.center; CGFloat distance = sqrt(pow((point1.x - point2.x), 2) + pow((point1.y - point2.y), 2)); if (distance <= CGRectGetHeight(_panImageFrame)/2) {///中心点相差小于20的,允许判断是否交换位置 [self puzzleImagePanGestureHandler:_panImageView defaultFrame:_panImageFrame]; } else{///放回原来位置 [UIView animateWithDuration:0.1 animations:^{ _allowJoint = NO; _panImageView.frame = _panImageFrame; } completion:^(BOOL finished) { _allowJoint = YES; }]; } } else{ } } } ///拖动手势操作 - (void)puzzleImagePanGestureHandler:(UIImageView *)puzzleImage defaultFrame:(CGRect)defaultFrame{ NSInteger emTag = (_rows * _rows); NSInteger tapTag = puzzleImage.tag; if (emTag == tapTag) { return; } UIImageView *emImg = [self viewWithTag:emTag]; UIImageView *tapImg = puzzleImage; CGFloat emMinX = floor(CGRectGetMinX(emImg.frame)); CGFloat emMaxX = floor(CGRectGetMaxX(emImg.frame)); CGFloat emMinY = floor(CGRectGetMinY(emImg.frame)); CGFloat emMaxY = floor(CGRectGetMaxY(emImg.frame)); CGFloat tapMinX = floor(CGRectGetMinX(defaultFrame)); CGFloat tapMaxX = floor(CGRectGetMaxX(defaultFrame)); CGFloat tapMinY = floor(CGRectGetMinY(defaultFrame)); CGFloat tapMaxY = floor(CGRectGetMaxY(defaultFrame)); BOOL isExchange = NO; if ((tapMinX == emMinX) && fabs((tapMaxY + _itemSpace) - emMinY) < 5*xkScale){ isExchange = YES; } else if ((tapMinX == emMinX) && fabs((emMaxY + _itemSpace) - tapMinY) < 5*xkScale){ isExchange = YES; } else if ((tapMinY == emMinY) && fabs((tapMaxX + _itemSpace) - emMinX) < 5*xkScale){ isExchange = YES; } else if ((tapMinY == emMinY) && fabs((emMaxX + _itemSpace) - tapMinX) < 5*xkScale){ isExchange = YES; } else{ isExchange = NO; } CGRect emRect = CGRectMake(CGRectGetMinX(emImg.frame), CGRectGetMinY(emImg.frame), CGRectGetWidth(emImg.frame), CGRectGetHeight(emImg.frame)); if (isExchange) { NSLog(@"允许交换"); [UIView animateWithDuration:0.3 animations:^{ _allowJoint = NO; emImg.frame = defaultFrame; tapImg.frame = emRect; } completion:^(BOOL finished) { NSInteger accesTap = [tapImg.accessibilityValue integerValue]; NSInteger accesEm = [emImg.accessibilityValue integerValue]; [_puzzleArray exchangeObjectAtIndex:(accesTap - 1) withObjectAtIndex:(accesEm - 1)]; tapImg.accessibilityValue = [NSString stringWithFormat:@"%ld",accesEm]; emImg.accessibilityValue = [NSString stringWithFormat:@"%ld",accesTap]; _allowJoint = YES; if ([self isPuzzleImageFinish]) { NSLog(@"拼图完成"); [self puzzleImageFinishHandler]; } else{ NSLog(@"继续加油"); } }]; } else{ NSLog(@"不允许交换"); ///原图归位 [UIView animateWithDuration:0.3 animations:^{ _allowJoint = NO; tapImg.frame = defaultFrame; } completion:^(BOOL finished) { _allowJoint = YES; }]; } }
判断拼图是否完成
- (BOOL)isPuzzleImageFinish{ NSString *order = [_orderArray componentsJoinedByString:@""]; NSString *after = [_puzzleArray componentsJoinedByString:@""]; return [order isEqualToString:after]; }
效果