package tests import "core:testing" import card "../src/card" import memory "../src/memory" import themes "../src/themes" // Runs the game logic forward in small steps, like frames @(private) advance :: proc(seconds: f32) { dt: f32 = 1.0 / 60 for t: f32 = 0; t < seconds; t += dt { memory.update(dt) } } // Finds the card index holding the other half of card i's pair @(private) partner_of :: proc(i: int) -> int { for c, j in memory.cards { if j != i && c.pair_id == memory.cards[i].pair_id { return j } } return -1 } // Finds a card index that does not pair with card i @(private) non_partner_of :: proc(i: int) -> int { for c, j in memory.cards { if j != i && c.pair_id != memory.cards[i].pair_id { return j } } return -1 } @(test) hiragana_table_is_complete_and_unique :: proc(t: ^testing.T) { table := memory.HIRAGANA testing.expect_value(t, len(table), 46) for a, i in table { for b, j in table { if i == j { continue } testing.expectf(t, a.kana != b.kana, "duplicate kana %s", a.kana) testing.expectf(t, a.romaji != b.romaji, "duplicate romaji %s", a.romaji) } } } @(test) deal_creates_kana_romaji_pairs :: proc(t: ^testing.T) { memory.deal(themes.JAPAN) testing.expect_value(t, memory.state, memory.State.WAITING) testing.expect_value(t, memory.moves, 0) testing.expect_value(t, memory.matches, 0) for pair in 0 ..< memory.PAIR_COUNT { kana_count, romaji_count := 0, 0 kana, romaji: cstring for c in memory.cards { if c.pair_id != pair { continue } testing.expect(t, !c.face_up && !c.matched) if c.is_kana { kana_count += 1 kana = c.text } else { romaji_count += 1 romaji = c.text } } testing.expect_value(t, kana_count, 1) testing.expect_value(t, romaji_count, 1) // The two halves must belong to the same table entry found := false for entry in memory.HIRAGANA { if entry.kana == kana && entry.romaji == romaji { found = true } } testing.expectf(t, found, "pair %d is %s/%s, which is not in the table", pair, kana, romaji) } } @(test) deal_lays_out_cards_on_screen_without_overlap :: proc(t: ^testing.T) { memory.deal(themes.JAPAN) for a, i in memory.cards { testing.expect(t, a.rect.x >= 0 && a.rect.x + a.rect.width <= themes.SCREEN_WIDTH) testing.expect(t, a.rect.y >= themes.GRID_TOP && a.rect.y + a.rect.height <= themes.SCREEN_HEIGHT) for b, j in memory.cards { if i == j { continue } overlap := a.rect.x < b.rect.x + b.rect.width && b.rect.x < a.rect.x + a.rect.width && a.rect.y < b.rect.y + b.rect.height && b.rect.y < a.rect.y + a.rect.height testing.expectf(t, !overlap, "cards %d and %d overlap", i, j) } } } @(test) matching_pair_stays_up :: proc(t: ^testing.T) { memory.deal(themes.JAPAN) a := 0 b := partner_of(a) testing.expect(t, memory.select_card(a)) testing.expect_value(t, memory.state, memory.State.FIRST) testing.expect(t, memory.select_card(b)) testing.expect_value(t, memory.state, memory.State.RESOLVING) testing.expect_value(t, memory.moves, 1) advance(2) testing.expect_value(t, memory.state, memory.State.WAITING) testing.expect_value(t, memory.matches, 1) testing.expect(t, memory.cards[a].matched && memory.cards[b].matched) testing.expect(t, memory.cards[a].face_up && memory.cards[b].face_up) } @(test) mismatched_pair_flips_back :: proc(t: ^testing.T) { memory.deal(themes.JAPAN) a := 0 b := non_partner_of(a) memory.select_card(a) memory.select_card(b) advance(3) testing.expect_value(t, memory.state, memory.State.WAITING) testing.expect_value(t, memory.matches, 0) testing.expect_value(t, memory.moves, 1) testing.expect(t, !memory.cards[a].face_up && !memory.cards[b].face_up) testing.expect_value(t, memory.cards[a].flip_t, 0) } @(test) invalid_picks_are_rejected :: proc(t: ^testing.T) { memory.deal(themes.JAPAN) a := 0 testing.expect(t, memory.select_card(a)) testing.expect(t, !memory.select_card(a)) // same card twice b := non_partner_of(a) memory.select_card(b) c := non_partner_of(b) for c == a { c += 1 } testing.expect(t, !memory.select_card(c)) // a third card while resolving testing.expect_value(t, memory.moves, 1) memory.deal(themes.JAPAN) p := partner_of(0) memory.select_card(0) memory.select_card(p) advance(2) testing.expect(t, !memory.select_card(0)) // already matched } @(test) finding_all_pairs_wins :: proc(t: ^testing.T) { memory.deal(themes.JAPAN) for i in 0 ..< memory.CARD_COUNT { if memory.cards[i].matched { continue } memory.select_card(i) memory.select_card(partner_of(i)) advance(2) } testing.expect_value(t, memory.state, memory.State.OVER) testing.expect_value(t, memory.matches, memory.PAIR_COUNT) testing.expect_value(t, memory.moves, memory.PAIR_COUNT) for &c in memory.cards { testing.expect(t, c.matched) } // The clock stops once the game is over elapsed := memory.elapsed advance(1) testing.expect_value(t, memory.elapsed, elapsed) } @(test) card_flip_animation_reaches_target :: proc(t: ^testing.T) { c := card.Card{} testing.expect(t, !card.is_animating(&c)) c.face_up = true testing.expect(t, card.is_animating(&c)) card.update(&c, 0.1) testing.expect(t, c.flip_t > 0 && c.flip_t < 1) for _ in 0 ..< 60 { card.update(&c, 1.0 / 60) } testing.expect_value(t, c.flip_t, 1) testing.expect(t, !card.is_animating(&c)) c.face_up = false for _ in 0 ..< 60 { card.update(&c, 1.0 / 60) } testing.expect_value(t, c.flip_t, 0) } @(test) card_pulse_counts_down :: proc(t: ^testing.T) { c := card.Card{} card.start_pulse(&c) testing.expect_value(t, c.pulse_t, card.PULSE_DURATION) card.update(&c, 1) testing.expect_value(t, c.pulse_t, 0) }