Add justfile, ols/odinfmt config, tests and Gitea workflows; drop odin.json
- justfile with build/run/check/test/release/package/fmt/clean recipes - ols.json and odinfmt.json for neovim (ols) - Tests for memory game rules and card animation; memory.deal and memory.select_card split out so logic is testable without a window - Gitea workflows: build+test on main/develop, package and upload to the generic package registry on version branches - Game changes to the executable's directory so packaged builds find assets Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
148570a4e6
commit
9ee2eabe9a
12 files changed
+428
-23
No files matched your search
@@ -0,0 +1,198 @@
|
||||
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)
|
||||
}
|
||||
Reference in new issue
Block a user