Как "затравить", используя GKRandomSource из Gameplaykit Swift, чтобы запомнить случайность между сессиями
Я новичок в программировании и изучаю Swift, пройдя ряд онлайн-курсов. На одном из курсов мы создали простую игру викторины, и я постепенно пытался улучшить ее, создав собственную кодировку (лучший способ научиться!).
Недавно я наткнулся на то, что называется случайным образом Фишера-Йейтса, и после долгих проб и ошибок (и с помощью сообщества по переполнению стека) смог использовать GKRandomSource из Gameplaykit Swift, чтобы перетасовать мои пустяковые вопросы так, чтобы их задавали случайным образом., Это было улучшение по сравнению с исходным кодом 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)
}
Я знаю, что мое кодирование, вероятно, довольно многословно и не нужно, но вплоть до этого последнего улучшения оно работало нормально, и я на самом деле понимаю большую его часть (я думаю!)
1 ответ
Здесь действительно два вопроса: о чем ты спрашиваешь и чего ты хочешь. Им обоим стоит ответить по разным причинам, так что...
Как посеять GK(что угодно) RandomSource
(Все GKRandomSource
у подклассов есть семена, хотя суперкласс GKRandomSource
сам по себе не... это потому, что каждый класс имеет свой собственный тип данных для начальных чисел. Но использование то же самое.)
Критические части кода, который вы разместили, даже не компилируются из-за несоответствия типов: seed
/init(seed:)
значение для GKLinearCongruentialRandomSource
является целым числом, а не массивом объектов. В документации по этому значению разъясняется, для чего оно (выделение выделено) и как его использовать:
Любые два случайных источника, инициализированные одинаковыми начальными данными, будут генерировать одинаковую последовательность случайных чисел. Чтобы повторить поведение существующего
GKLinearCongruentialRandomSource
экземпляр, читать этот экземплярseed
свойство, а затем создать новый экземпляр, передав результирующие данныеinitWithSeed:
инициализатор.
Итак, если вы хотите повторить последовательность случайных чисел:
Создайте случайный источник с простым инициализатором.
let source = GKLinearCongruentialRandomSource()
Сохранить от этого источника
seed
значение.let seed = source.seed // -> some UInt64 value // write seed to user defaults, a file, a web service, whatever.
Используйте этот случайный источник для чего угодно.
Позже, когда вы снова запустите и захотите ту же самую последовательность, прочитайте значение семени и создайте случайный источник, используя семя.
let seed = // read in seed value from wherever you saved it let source = GKLinearCongruentialRandomSource(seed: seed)
Это все еще не дает вам то, что вы на самом деле ищете, хотя: если source
на шаге 1 производится последовательность 1, 6, 3, 9, 2, 7
, source
из шага 4 также получим последовательность 1, 6, 3, 9, 2, 7
- семя не записывает, где вы "остановились" в последовательности. Или, поскольку вы используете его для перестановки массива, он будет производить такое же упорядоченное расположение массива, что и первый перестановка, но он не помнит, что вы сделали с перестановленным массивом в дальнейшем.
Как использовать случайный порядок при нескольких запусках приложений
Если вы хотите перетасовать массив, пройтись по нему по порядку, а затем при последующем запуске приложения продолжить проход по тому же перетасованному массиву, с которого вы остановились, вам необходимо разработать это требование.
Перемешать при первом запуске.
Запишите что-нибудь о произведенном заказе. (Скажем, отображение индексов в случайном порядке и индексов в исходных данных.)
Проходя через перемешанный массив, запишите, как далеко вы прошли через него.
При последующих запусках приложения используйте запись заказа и запись прогресса, чтобы решить, где вы находитесь.
Вот грубый проход в этом. (Обратите внимание, что я не касаюсь вашей модели данных - это вопрос разработки программы, и 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
и т. д.), но в общем случае этого должно быть достаточно, чтобы дать вам пищу для размышлений.
Вы также можете использовать следующее, чтобы сбросить определенное количество значений, поэтому, если вы ведете счетчик рулонов, сбросить это количество при следующем запуске так же просто, как:
arc4.dropValues(rollCount)