0

Im running a project, that makes an image of a bottle spin around. Just like the game "bottleneck". It actually works great, but its just way to often, that the image stops on the same position. I would like to make a new position every time at least 45 degrees from the last position. My code looks like this

for _ in 1...10 {

        UIView.animateWithDuration(2, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: [],
            animations: { [unowned self] in

                self.bottleImage.transform = CGAffineTransformMakeRotation(CGFloat(M_PI))


            }){ [unowned self] (finished: Bool) in
                self.bottleImage.hidden = false
        }

        UIView.animateWithDuration(8, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: [],
            animations: { [unowned self] in

                self.bottleImage.transform = CGAffineTransformMakeRotation(CGFloat(M_PI * 2))


            }){ [unowned self] (finished: Bool) in
                self.bottleImage.hidden = false
        }
    }

    //Random stop point

    let randomNumber: CGFloat = CGFloat(arc4random_uniform(360))

    UIView.animateWithDuration(2, delay: 0, usingSpringWithDamping: 5, initialSpringVelocity: 5, options: [],
        animations: { [unowned self] in

            self.bottleImage.transform = CGAffineTransformMakeRotation(CGFloat(randomNumber))

        }){ [unowned self] (finished: Bool) in
            self.bottleImage.hidden = false
    }

FINAL EDIT

var currentPosition: CGFloat = 0.0
/*
First spins...
*/


//Random stop point
let randomNumber: CGFloat = CGFloat(arc4random_uniform(270))

    currentPosition = CGFloat(Int(currentPosition + 45 + randomNumber) % 360)

    UIView.animateWithDuration(1, delay: 0, usingSpringWithDamping: 5, initialSpringVelocity: 2, options: [],
                               animations: { [unowned self] in

               self.bottleImage.transform = CGAffineTransformMakeRotation(self.currentPosition / 180 * CGFloat(M_PI))

           }){ [unowned self] (finished: Bool) in
               self.bottleImage.hidden = false
    }
jonask
  • 679
  • 2
  • 6
  • 21
  • Your approach would no longer be uniformly random, but random with constraints (on the range of the random number) based on current bottle position, but it sounds as if this is what you're after. To implement this, you could store the previous random number; generate a new on in range `[0, 270]` and rotate the bottle `45+newRandomNumber` from the given position. – dfrib Aug 06 '16 at 18:47
  • I actually thought about that, but I don't know how to get the last used number? Im getting a new random number every time i tap a button – jonask Aug 06 '16 at 18:50
  • You could e.g. store it as an instance (mutable) property of the class in which you've implemented your animation: e.g. initialize this `var lastRandomPosition` to `0` (in which case first spin will always end up in range `[0, 270]`: you could fix this as an initial exception), and update the value of it each time you generate a new random position (to `45+newRandomNumber`). – dfrib Aug 06 '16 at 18:51
  • Ok thanks. I'll try to work with it in playground. Im not sure how to do it yet – jonask Aug 06 '16 at 19:06
  • @JoshCaswell I don't entirely believe this is a duplicate of the one you marked it to: the OP here wants each random number to be generated within a span that is decided by the previous random number. Also, the accepted answer in the duplicate is not really pretty (and in the context of this question: not really applicable as position 333 would look the same as e.g. 331 or 329 w.r.t. spinning an image). – dfrib Aug 06 '16 at 19:07
  • I don't see much of a meaningful distinction between "generate a random number between 1-5 that's different than the last" and "generate a random quadrant that's different than the last". If there's a better answer (and I agree that the technique in the accepted answer isn't ideal), it can be posted there. – jscs Aug 06 '16 at 19:23
  • @JoshCaswell, This question is different enough than the one you linked that I would say it's not a duplicate. In this question the OP wants to create a new random number that's at least 90% different than the last one – Duncan C Aug 06 '16 at 19:48
  • @jonask Your animation is almost correctly coded, but you've forgotten to convert the random number in `[0, 360]` to radian range (`[0, 2pi]`). If you correct this, you wont see the repeated positioning of your rotating bottle. – dfrib Aug 06 '16 at 20:01
  • But how do i code the 2pi? M_2_PI? – jonask Aug 06 '16 at 20:23
  • @jonask You could e.g. use `self.TuborgFlasken.transform = CGAffineTransformMakeRotation(randomNumber/180*CGFloat(M_PI))`. – dfrib Aug 06 '16 at 20:40
  • Won't work. Sometimes it repeats the position – jonask Aug 06 '16 at 21:02
  • @jonask yes as I wrote in my previous comment that solution would be fully random without memory of the previous roll. I added an answer which shows an example of how to keep a 1-roll memory and, for each new roll, avoids the 90 degree area where the previous position were in center. Note however that this is _not_ how rolling a bottle works: in the general case, each roll is independent of the previous one (ignoring microcharacteristics of the bottle and the people spinning the bottle, naturally:). – dfrib Aug 06 '16 at 21:17

2 Answers2

2

It seems to me like each time, you want a new position that is at least 45 degrees different from the last position.

You need to save the previous angle, and then add a number that is either in the range 45 to 180 degrees or -45 to -180 degrees to the previous angle. (Angles in iOS are actually calculated in radians, but I'm using degrees because it's a little easier to understand.

So, use code something like this:

var angleChange = arc4random_uniform(180-45) + 45
if arc4random_uniform(2) == 0
   angleChange *= -1

Then convert the result to radians and add the change to your old angle value. Finally, limit the end angle to the range 0 to 2π (by adding 2π to the result and taking it modulo 2π (with the fmod() function.)

(The code above will probably need to be adjusted to use the correct data types.)

Duncan C
  • 128,072
  • 22
  • 173
  • 272
  • 2
    Re-reading the question, I believe the core "error" is not a bad random generator, but rather that the OP has missed to convert the random number rotation to radians, which means that a few integer radian positions will be chosen (in which case I guess this question should possibly be closed as "simple typographical error). – dfrib Aug 06 '16 at 19:57
  • I didn't even look at the OP's code, TBH. Going back, it looks like that is the problem, or at least part of the problem. The OP also doesn't seem to understand how to save the previous angle and add a change. I'm trying to provide some hints that will let him solve it for himself rather than providing code that he can cut and paste, which I think impedes learning. – Duncan C Aug 06 '16 at 20:00
  • Yeah I think question could be of use as randomly rotating views is of of interest to many new swifters, but possibly the question title should be updated :) – dfrib Aug 06 '16 at 20:02
  • Im working on it. Im struggling with saving the previous value. I can't just append it to an array and say __numberArray.last! + angleChange__ – jonask Aug 06 '16 at 20:17
  • You could, but there's no reason to. The only thing you need is the final value. Just use single Double instance variable. Also make sure you convert your degrees into radians. (`radians = degrees*π/180`) – Duncan C Aug 06 '16 at 21:03
1

I'll add a specific fix for your particular case:

/* instance variable in e.g. the UIViewController that holds your 
   TuborgFlasken view */
var currentPosition: CGFloat = 0.0

// ...

// some spin TuborgFlasken action
@IBAction func spinTuborgFlasken(sender: UIButton) {

    // for _ in 0...10 ... as in your example

    // Random stop point

    /* generate a random number in range [0, 270] and set next flask position
       as current position + 45 + this random number: this means the next
       position will be a random position outside of the 90 degree "quadrant" 
       in which current position is centered */
    let randomNumber: CGFloat = CGFloat(arc4random_uniform(270))
    currentPosition = CGFloat(Int(currentPosition + 45 + randomNumber) % 360)

    UIView.animateWithDuration(1, delay: 0, usingSpringWithDamping: 5, initialSpringVelocity: 2, options: [],
                               animations: { [unowned self] in

                                self.button.transform = CGAffineTransformMakeRotation(self.currentPosition/180*CGFloat(M_PI))
        /* we make sure to enter the new position in radians, not degrees */

    }){ /* ... */ }

}

// ...

Which would generate something along the lines of:

enter image description here


Note however that you probably want to just use a new random position each time: the reason for the repeated positions in your own code is due to entering the rotation for the random stop point in degrees rather than radians, which leads to a few discrete integer radian positions chosen randomly. This can be remedied simply by:

self.TuborgFlasken.transform = 
    CGAffineTransformMakeRotation(randomNumber/180*CGFloat(M_PI))
dfrib
  • 70,367
  • 12
  • 127
  • 192