@@ -90,8 +90,13 @@ impl Game {
9090 let mut previous_choices = mem:: take ( & mut self . choices ) ;
9191 let mut next_choices = self . draw_next_choices ( ) ;
9292
93- next_choices. iter_mut ( ) . for_each ( |ctx| ctx. last_seen = Some ( 0 ) ) ;
94- previous_choices. iter_mut ( ) . for_each ( |ctx| ctx. last_seen = Some ( 1 ) ) ;
93+ next_choices. sort_by_key ( Self :: weight) ;
94+ next_choices
95+ . iter_mut ( )
96+ . for_each ( |ctx| ctx. last_seen = Some ( 0 ) ) ;
97+ previous_choices
98+ . iter_mut ( )
99+ . for_each ( |ctx| ctx. last_seen = Some ( 1 ) ) ;
95100 // Everyone left in `pack` or `benched` waited one more round for this
96101 // draw; bump their clock before `benched` graduates into `pack` below.
97102 self . pack
@@ -122,7 +127,10 @@ impl Game {
122127 let shortfall = MULTIPLE_CHOICE_SIZE - self . pack . len ( ) ;
123128 self . benched . sort_by_key ( |ctx| Reverse ( Self :: weight ( ctx) ) ) ;
124129 let backfill = self . benched . drain ( 0 ..shortfall. min ( self . benched . len ( ) ) ) ;
125- mem:: take ( & mut self . pack ) . into_iter ( ) . chain ( backfill) . collect ( )
130+ mem:: take ( & mut self . pack )
131+ . into_iter ( )
132+ . chain ( backfill)
133+ . collect ( )
126134 }
127135 }
128136
@@ -198,3 +206,91 @@ impl BirdContext {
198206 self . consecutively_identified >= LEARN_THRESHOLD
199207 }
200208}
209+
210+ #[ cfg( test) ]
211+ mod tests {
212+ use std:: collections:: HashMap ;
213+
214+ use super :: * ;
215+
216+ fn fake_pack ( n : u64 ) -> Vec < Bird > {
217+ ( 0 ..n)
218+ . map ( |id| Bird {
219+ id,
220+ common_name : format ! ( "bird-{id}" ) ,
221+ scientific_name : format ! ( "sci-{id}" ) ,
222+ image : String :: new ( ) ,
223+ sounds : vec ! [ ] ,
224+ } )
225+ . collect ( )
226+ }
227+
228+ #[ test]
229+ fn incorrect_answer_resets_streak_but_keeps_mistaken_count ( ) {
230+ let mut game = Game :: init ( fake_pack ( 10 ) , false ) ;
231+
232+ game. record_choice ( true ) ;
233+ game. record_choice ( true ) ;
234+ assert_eq ! ( game. correct_choice( ) . consecutively_identified, 2 ) ;
235+
236+ game. record_choice ( false ) ;
237+ assert_eq ! ( game. correct_choice( ) . consecutively_identified, 0 ) ;
238+ assert_eq ! ( game. correct_choice( ) . mistaken, 1 ) ;
239+ // `identified` only counts correct answers, so it's untouched by the miss.
240+ assert_eq ! ( game. correct_choice( ) . identified, 2 ) ;
241+ }
242+
243+ #[ test]
244+ fn bird_is_learned_after_learn_threshold_consecutive_corrects ( ) {
245+ let mut game = Game :: init ( fake_pack ( 10 ) , false ) ;
246+
247+ for _ in 0 ..LEARN_THRESHOLD {
248+ assert ! ( !game. correct_choice( ) . learned( ) ) ;
249+ game. record_choice ( true ) ;
250+ }
251+
252+ assert ! ( game. correct_choice( ) . learned( ) ) ;
253+ }
254+
255+ #[ test]
256+ fn is_complete_only_once_every_bird_is_learned ( ) {
257+ let mut game = Game :: init ( fake_pack ( 8 ) , false ) ;
258+ assert ! ( !game. is_complete( ) ) ;
259+
260+ // Each round only advances whichever bird is `correct_choice()`, so
261+ // keep answering correctly and advancing rounds until every bird has
262+ // racked up LEARN_THRESHOLD consecutive corrects.
263+ let mut rounds = 0 ;
264+ while !game. is_complete ( ) {
265+ game. record_choice ( true ) ;
266+ game. set_next_challenge ( ) ;
267+ rounds += 1 ;
268+ assert ! ( rounds < 1000 , "did not converge -- possible infinite loop" ) ;
269+ }
270+
271+ let ( learned, total) = game. progress ( ) ;
272+ assert_eq ! ( learned, total) ;
273+ }
274+
275+ #[ test]
276+ fn a_bird_never_reappears_within_one_round_of_being_shown ( ) {
277+ let mut game = Game :: init ( fake_pack ( 10 ) , true ) ;
278+ let mut last_shown_round: HashMap < u64 , i32 > = HashMap :: new ( ) ;
279+
280+ for round in 0 ..200 {
281+ for ctx in game. choices ( ) {
282+ if let Some ( & prev) = last_shown_round. get ( & ctx. bird . id ) {
283+ assert ! (
284+ round - prev >= 2 ,
285+ "bird {} reappeared after only {} round(s)" ,
286+ ctx. bird. id,
287+ round - prev
288+ ) ;
289+ }
290+ last_shown_round. insert ( ctx. bird . id , round) ;
291+ }
292+ game. record_choice ( true ) ;
293+ game. set_next_challenge ( ) ;
294+ }
295+ }
296+ }
0 commit comments