1

我是编程新手,通过学习一些在线课程一直在学习 Swift。在其中一门课程中,我们构建了一个基本的琐事游戏,我一直在尝试通过自己编写代码来改进它(最好的学习方式!)。

最近我遇到了所谓的 Fisher-Yates 洗牌,经过多次试验和错误(在堆栈溢出社区的帮助下),我能够使用 Swift Gameplaykit 中的 GKRandomSource 来洗牌我的琐事问题,以便他们被随机询问. 这是对我使用的原始 arc4random 代码的改进,因为随机播放从整个问题池中删除了已经提出的问题,从而确保它们不会重复(至少在 iOS9 中)。

这在会话中运行良好,但是一旦用户退出应用程序并重新启动它,随机播放将从头开始。所以我正在寻找一种方法让应用程序“记住”会话之间已经提出的问题。我的研究使我产生了播种的想法,我一直试图让它与我的 GKRandomSource 代码一起使用,但我显然遗漏了一些东西。

任何建议等都会受到欢迎 - 特别是因为我不完全确定这种“播种”方法将实现我的最终目标,即不重复在应用程序的前几节中已经提出的问题。

以下是我认为是我修改后的代码的相关部分。

所有问题和可能的答案选择都存储在 .json 文件中,如下所示:

{
        "id" : "1",
        "question": "Earth is a:",
             "answers": [
            "Planet",
            "Meteor",
            "Star",
            "Asteroid"
          ],
          "difficulty": "1"
      }

我使用以下代码加载 .json 文件:

func loadAllQuestionsAndAnswers()
{
    let path = NSBundle.mainBundle().pathForResource("content", ofType: "json")
    let jsonData : NSData = NSData(contentsOfFile: path!)!
    allEntries = (try! NSJSONSerialization.JSONObjectWithData(jsonData, options: NSJSONReadingOptions.MutableContainers)) as! NSArray
    //println(allEntries)

}

下面是我最近尝试实现所有问题的洗牌并在以后的会话中复制它的代码):

var allEntries : NSArray!
var shuffledQuestions: [AnyObject]!
var nextQuestion = -1


    var mySeededQuestions : [AnyObject]
    loadAllQuestionsAndAnswers()

    if #available(iOS 9.0, *) {
            let lcg = GKLinearCongruentialRandomSource(seed: mySeededQuestions)
            let shuffledQuestions = lcg.arrayByShufflingObjectsInArray(allEntries)
            nextQuestion++
            loadQuestion(nextQuestion)

            // Fallback on earlier versions

        }else{

            let randomNumber = Int(arc4random_uniform(UInt32(allEntries.count)))
            loadQuestionPreiOS9(randomNumber)

        }

我至少知道上面的代码有问题,但我很茫然。我也在想,也许我在储存种子方面错过了一步?

为了完整起见,我使用一个标签来显示问题,并使用四个图像来显示可能的答案,使用以下代码:

func loadQuestion(index : Int)
{
    let entry : NSDictionary = shuffledQuestions[index] as! NSDictionary
    let question : NSString = entry.objectForKey("question") as! NSString
    let arr : NSMutableArray = entry.objectForKey("answers") as! NSMutableArray

    //println(question)
    //println(arr)

    labelQuestion.text = question as String

    let indices : [Int] = [0,1,2,3]
    //let newSequence = shuffle(indices)
    let newSequence = indices.shuffle()
    var i : Int = 0
    for(i = 0; i < newSequence.count; i++)
    {
        let index = newSequence[i]
        if(index == 0)
        {
            // we need to store the correct answer index
            currentCorrectAnswerIndex =  i

        }

        let answer = arr.objectAtIndex(index) as! NSString
        switch(i)
        {
        case 0:
            buttonA.setTitle(answer as String, forState: UIControlState.Normal)
            break;

        case 1:
            buttonB.setTitle(answer as String, forState: UIControlState.Normal)
            break;

        case 2:
            buttonC.setTitle(answer as String, forState: UIControlState.Normal)
            break;

        case 3:
            buttonD.setTitle(answer as String, forState: UIControlState.Normal)
            break;

        default:
            break;
        }



    }
    buttonNext.hidden = true
    // we will need to reset the buttons to reenable them
    ResetAnswerButtons()

}


func loadQuestionPreiOS9(index : Int)
{
    let entry : NSDictionary = allEntries.objectAtIndex(index) as! NSDictionary
    let question : NSString = entry.objectForKey("question") as! NSString
    let arr : NSMutableArray = entry.objectForKey("answers") as! NSMutableArray

    //println(question)
    //println(arr)

    labelQuestion.text = question as String

    let indices : [Int] = [0,1,2,3]
    //let newSequence = shuffle(indices)
    let newSequence = indices.shuffle()
    var i : Int = 0
    for(i = 0; i < newSequence.count; i++)
    {
        let index = newSequence[i]
        if(index == 0)
        {
            // we need to store the correct answer index
            currentCorrectAnswerIndex =  i

        }

        let answer = arr.objectAtIndex(index) as! NSString
        switch(i)
        {
        case 0:
            buttonA.setTitle(answer as String, forState: UIControlState.Normal)
            break;

        case 1:
            buttonB.setTitle(answer as String, forState: UIControlState.Normal)
            break;

        case 2:
            buttonC.setTitle(answer as String, forState: UIControlState.Normal)
            break;

        case 3:
            buttonD.setTitle(answer as String, forState: UIControlState.Normal)
            break;

        default:
            break;
        }



    }
    buttonNext.hidden = true
    // we will need to reset the buttons to reenable them
    ResetAnswerButtons()

}

最后,在用户回答问题后,我使用以下代码向用户显示“下一步”按钮:

@IBAction func PressedButtonNext(sender: UIButton) {
    print("button Next pressed")

    if #available(iOS 9.0, *) {
    nextQuestion++
    loadQuestion(nextQuestion)
    }else{
        let randomNumber = Int(arc4random_uniform(UInt32(allEntries.count)))
        loadQuestionPreiOS9(randomNumber)
    }

我知道我的编码可能非常冗长且不必要,但在最新的改进之前,它一直运行良好,我实际上了解其中的大部分内容(我想!)

4

2 回答 2

3

这里实际上有两个问题:您在问什么以及您似乎想要什么。由于不同的原因,它们都值得回答,所以...

如何播种 GK(Whatever)RandomSource

(所有GKRandomSource子类都有种子,即使超类GKRandomSource本身没有……那是因为每个类都有自己的种子数据类型。但用法是一样的。)

由于类型不匹配,您发布的代码的关键位甚至无法编译:seed/的init(seed:)GKLinearCongruentialRandomSource是整数,而不是对象数组。该值的文档说明了它的用途(强调添加)以及如何使用它:

使用相同种子数据初始化的任何两个随机源将生成相同的随机数序列。要复制现有GKLinearCongruentialRandomSource实例的行为,请读取该实例的seed属性,然后通过将结果数据传递给initWithSeed:初始化程序来创建一个新实例。

所以,如果你想复制一个随机数序列:

  1. 使用普通初始化程序创建一个随机源。

    let source = GKLinearCongruentialRandomSource()
    
  2. 节省该来源的seed价值。

    let seed = source.seed // -> some UInt64 value
    // write seed to user defaults, a file, a web service, whatever.
    
  3. 使用该随机源做任何事情。

  4. 稍后,当您再次启动并想要相同的序列时,读取种子值并使用种子创建一个随机源。

    let seed = // read in seed value from wherever you saved it
    let source = GKLinearCongruentialRandomSource(seed: seed)
    

但是,这仍然无法为您提供您实际寻找的内容:如果source在步骤 1 中生成了序列1, 6, 3, 9, 2, 7source则从步骤 4 中也将生成序列1, 6, 3, 9, 2, 7- 种子不会记录您在序列中“离开”的位置。或者,由于您将它用于数组洗牌,它会产生与第一次洗牌相同的数组洗牌顺序,但它不记得您此后对洗牌数组做了什么。

如何在多个应用程序启动中使用随机排序

如果您想打乱一个数组,按顺序遍历它,然后在您的应用程序的稍后运行中继续遍历您离开的地方的同一个打乱数组,您需要围绕该要求进行设计。

  1. 首次启动时随机播放。

  2. 记录有关产生的订单的一些信息。(例如,将 shuffle 中的索引映射到原始数据中的索引。)

  3. 在经过洗牌的阵列时,记录您通过它的距离。

  4. 在以后的应用程序运行中,使用订购记录和进度记录来决定你在哪里。

这是一个粗略的传球。(请注意,我没有触及您的数据模型——这是一个程序设计问题,SO 不是编码服务。您需要考虑如何充实此设计以匹配您的模型及其用例。)

struct Defaults {
    static let lastQuestionIndex = "lastQuestionIndex"
    static let questionOrder = "questionOrder"
}
let questions: [Question] // array of model objects, always in fixed order

func nextQuestion() -> Question {
    let defaults = NSUserDefaults.standardUserDefaults()
    if let lastIndex = defaults.integerForKey(Defaults.lastQuestionIndex) {
        // we've run before, load the ordering
        guard let shuffledOrder = defaults.arrayForKey(Defaults.questionOrder) as? [Int]
            else { fatalError("save questionOrder with lastQuestionIndex") }

        // advance the saved index so the next call to this function 
        // will get the next question
        if lastIndex + 1 < count {
            defaults.setInteger(lastIndex + 1, forKey: Defaults.lastQuestionIndex)
        } else {
            // ran out of shuffled questions, forget the order so we
            // can reshuffle on the next call
            defaults.removeObjectForKey(Defaults.questionOrder)
            defaults.removeObjectForKey(Defaults.lastQuestionIndex)
        }

        // map lastQuestionIndex from sequential to shuffled
        // and return the corresponding answer
        let shuffledIndex = shuffledOrder[lastIndex]
        return questions[shuffledIndex]

    } else {
        // first run, shuffle the question ordering (not the actual questions)
        let source = GKRandomSource()
        let sequentialOrder = Array(0..<questions.count)
        let shuffledOrder = source.arrayByShufflingObjectsInArray(sequentialOrder)

        // save the ordering, and the fact that we're asking the first question
        defaults.setObject(shuffledOrder, forKey: Defaults.questionOrder)
        defaults.setInteger(0, forKey: Defaults.lastQuestionIndex)

        // return the first question in the shuffled ordering
        let shuffledIndex = shuffledOrder[0]
        return questions[shuffledIndex]
     }
}

这可能有点伪代码(所以你可能需要担心转换数组以使用NSUserDefaults等),但作为一个通用设计,它应该足以让你思考。

于 2016-02-03T20:27:46.730 回答
0

您还可以使用以下内容来删除一定数量的值,因此如果您保持滚动计数在下一次开始时下降这么多,那么简单:

arc4.dropValues(rollCount)
于 2019-12-09T02:11:27.947 回答