feat(jobs): phase 3 - a job can be given up: jobs_give_up puts a written job back on offer from the next day (the day back is saved with it, so giving up is never a reroll), jobs_post_give_up takes a post down for the morning to replace; an auto board's posts are not taken and not given up; JOBS_F_GIVEN_UP tells the game to take down what it placed
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
e397e4eee9
commit
2c578c6f1c
8 changed files with 170 additions and 6 deletions
|
|
@ -64,7 +64,8 @@ export port JobsPay { reward: fn(int, int, int) -> void } # (scope, money, rep
|
||||||
| `jobs_morning()`, `jobs_roll(lo, hi)`, `jobs_post(b, s, kind, param, need, money, rep)`, `jobs_config_seed(seed)` | posting |
|
| `jobs_morning()`, `jobs_roll(lo, hi)`, `jobs_post(b, s, kind, param, need, money, rep)`, `jobs_config_seed(seed)` | posting |
|
||||||
| `jobs_slots(b)`, `jobs_posted(b)`, `jobs_post_kind / _param / _need / _have / _money / _rep / _state / _day(b, s)`, `jobs_posts_done(b)`, `jobs_total(b)` | the boards |
|
| `jobs_slots(b)`, `jobs_posted(b)`, `jobs_post_kind / _param / _need / _have / _money / _rep / _state / _day(b, s)`, `jobs_posts_done(b)`, `jobs_total(b)` | the boards |
|
||||||
| `jobs_post_hand_in(b, s)`, `jobs_post_set(b, s, ...)`, `jobs_set_total(b, n)` | a post's verbs |
|
| `jobs_post_hand_in(b, s)`, `jobs_post_set(b, s, ...)`, `jobs_set_total(b, n)` | a post's verbs |
|
||||||
| `jobs_facts() -> Queue<JobsFact>` | `{ what: JOBS_F_OFFERED / _PROGRESSED / _COMPLETED / _HANDED_IN, job, board, slot, kind, param, have, need, money, rep, scope }` |
|
| `jobs_give_up(i)`, `jobs_back_on(i)`, `jobs_post_give_up(b, s)` | letting one go: a written job is offered again from the next day (the day back is saved with it), a post comes down and the morning replaces it; an auto board's posts are never taken, so never given up |
|
||||||
|
| `jobs_facts() -> Queue<JobsFact>` | `{ what: JOBS_F_OFFERED / _PROGRESSED / _COMPLETED / _HANDED_IN / _GIVEN_UP, job, board, slot, kind, param, have, need, money, rep, scope }` |
|
||||||
| `jobs_system()`, `jobs_party_system()` | `"jobs"` (yours, the tick) and `"jobs.party"` (the party's), each saving its scope's jobs and boards by key |
|
| `jobs_system()`, `jobs_party_system()` | `"jobs"` (yours, the tick) and `"jobs.party"` (the party's), each saving its scope's jobs and boards by key |
|
||||||
|
|
||||||
## Tests
|
## Tests
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ export const JOBS_F_OFFERED: int = 0
|
||||||
export const JOBS_F_PROGRESSED: int = 1
|
export const JOBS_F_PROGRESSED: int = 1
|
||||||
export const JOBS_F_COMPLETED: int = 2
|
export const JOBS_F_COMPLETED: int = 2
|
||||||
export const JOBS_F_HANDED_IN: int = 3
|
export const JOBS_F_HANDED_IN: int = 3
|
||||||
|
export const JOBS_F_GIVEN_UP: int = 4 # taken and let go: what the game placed for it comes down
|
||||||
|
|
||||||
# `job` a written job, or -1 and `board` / `slot` a post; what it counted rides along, since a
|
# `job` a written job, or -1 and `board` / `slot` a post; what it counted rides along, since a
|
||||||
# post handed in is replaced before anyone reads the fact
|
# post handed in is replaced before anyone reads the fact
|
||||||
|
|
|
||||||
25
packages/ludic.jobs/giveup.ludic
Normal file
25
packages/ludic.jobs/giveup.ludic
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
# giveup.ludic - letting a job go. A written job goes back on offer, but not until tomorrow, so
|
||||||
|
# giving up is never a reroll; a post comes down and the morning puts up another in its place.
|
||||||
|
# Either way the fact says so, and the game takes down what it had placed for it.
|
||||||
|
export function jobs_give_up(jobs_st: mut JobsState, i: int) -> bool {
|
||||||
|
if not jobs_ok(i) or not jobs_under_way(jobs_st, i) { return false }
|
||||||
|
jobs__fact(jobs_st, JOBS_F_GIVEN_UP, i, -1, -1, jobs_st.jobs__hv[i], Jobs[i].need)
|
||||||
|
jobs_st.jobs__st[i] = JOBS_OFFERED
|
||||||
|
jobs_st.jobs__hv[i] = 0
|
||||||
|
jobs_st.jobs__back[i] = JobsWorld.day() + 1
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
# the day a written job given up may be taken again, 0 never given up
|
||||||
|
export function jobs_back_on(jobs_st: JobsState, i: int) -> int { return jobs_st.jobs__back[i] }
|
||||||
|
|
||||||
|
# a post taken and not yet paid; an auto board's posts are not taken, so they are not given up
|
||||||
|
export function jobs_post_give_up(jobs_st: mut JobsState, b: int, s: int) -> bool {
|
||||||
|
if not jobs_board_ok(b) or s < 0 or s >= JobBoards[b].slots or JobBoards[b].auto { return false }
|
||||||
|
let k = jobs_st.jobs__off[b] + s
|
||||||
|
let st = jobs_st.jobs__ps[k]
|
||||||
|
if jobs_st.jobs__pk[k] < 0 or (st != JOBS_ACTIVE and st != JOBS_READY) { return false }
|
||||||
|
jobs__fact(jobs_st, JOBS_F_GIVEN_UP, -1, b, s, jobs_st.jobs__ph[k], jobs_st.jobs__pn[k])
|
||||||
|
jobs__clear(jobs_st, k)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,7 @@ import "defs.ludic"
|
||||||
import "ports.ludic"
|
import "ports.ludic"
|
||||||
import "state.ludic"
|
import "state.ludic"
|
||||||
import "jobs.ludic"
|
import "jobs.ludic"
|
||||||
|
import "giveup.ludic"
|
||||||
import "count.ludic"
|
import "count.ludic"
|
||||||
import "lists.ludic"
|
import "lists.ludic"
|
||||||
import "boards.ludic"
|
import "boards.ludic"
|
||||||
|
|
|
||||||
|
|
@ -20,12 +20,12 @@ export function jobs_under_way(jobs_st: JobsState, i: int) -> bool {
|
||||||
# on offer: not taken, the scope's standing and the story far enough, and the game allowing it
|
# on offer: not taken, the scope's standing and the story far enough, and the game allowing it
|
||||||
export function jobs_offered(jobs_st: JobsState, i: int) -> bool {
|
export function jobs_offered(jobs_st: JobsState, i: int) -> bool {
|
||||||
let d = Jobs[i]
|
let d = Jobs[i]
|
||||||
if jobs_state(jobs_st, i) != JOBS_OFFERED { return false }
|
if jobs_state(jobs_st, i) != JOBS_OFFERED or JobsWorld.day() < jobs_st.jobs__back[i] { return false }
|
||||||
return JobsWorld.rep(d.scope) >= d.minrep and JobsWorld.stage() >= d.minstage and JobsWorld.open(i)
|
return JobsWorld.rep(d.scope) >= d.minrep and JobsWorld.stage() >= d.minstage and JobsWorld.open(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function jobs_take(jobs_st: mut JobsState, i: int) -> bool {
|
export function jobs_take(jobs_st: mut JobsState, i: int) -> bool {
|
||||||
if not jobs_ok(i) or jobs_state(jobs_st, i) != JOBS_OFFERED { return false }
|
if not jobs_ok(i) or jobs_state(jobs_st, i) != JOBS_OFFERED or JobsWorld.day() < jobs_st.jobs__back[i] { return false }
|
||||||
jobs_st.jobs__st[i] = JOBS_ACTIVE
|
jobs_st.jobs__st[i] = JOBS_ACTIVE
|
||||||
jobs_st.jobs__hv[i] = 0
|
jobs_st.jobs__hv[i] = 0
|
||||||
jobs__recheck_one(jobs_st, i)
|
jobs__recheck_one(jobs_st, i)
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ function jobs__reset_scope(jobs_st: mut JobsState, sc: int) -> void {
|
||||||
if Jobs[i].scope != sc { continue }
|
if Jobs[i].scope != sc { continue }
|
||||||
jobs_st.jobs__st[i] = JOBS_OFFERED
|
jobs_st.jobs__st[i] = JOBS_OFFERED
|
||||||
jobs_st.jobs__hv[i] = 0
|
jobs_st.jobs__hv[i] = 0
|
||||||
|
jobs_st.jobs__back[i] = 0
|
||||||
}
|
}
|
||||||
for b in 0 .. BOARD_COUNT {
|
for b in 0 .. BOARD_COUNT {
|
||||||
if JobBoards[b].scope != sc { continue }
|
if JobBoards[b].scope != sc { continue }
|
||||||
|
|
@ -19,8 +20,8 @@ function jobs__save_scope(jobs_st: JobsState, sc: int) -> Val {
|
||||||
let v = Value.object()
|
let v = Value.object()
|
||||||
let js = Value.object()
|
let js = Value.object()
|
||||||
for i in 0 .. JOB_COUNT {
|
for i in 0 .. JOB_COUNT {
|
||||||
if Jobs[i].scope != sc or jobs_st.jobs__st[i] == JOBS_OFFERED { continue }
|
if Jobs[i].scope != sc or (jobs_st.jobs__st[i] == JOBS_OFFERED and jobs_st.jobs__back[i] == 0) { continue }
|
||||||
sv_put_ints(js, Jobs[i].key, [jobs_st.jobs__st[i], jobs_st.jobs__hv[i]])
|
sv_put_ints(js, Jobs[i].key, [jobs_st.jobs__st[i], jobs_st.jobs__hv[i], jobs_st.jobs__back[i]])
|
||||||
}
|
}
|
||||||
Value.put(v, "jobs", js)
|
Value.put(v, "jobs", js)
|
||||||
let bs = Value.object()
|
let bs = Value.object()
|
||||||
|
|
@ -48,7 +49,9 @@ function jobs__load_scope(jobs_st: mut JobsState, sc: int, v: Val) -> void {
|
||||||
let js = Value.get(v, "jobs")
|
let js = Value.get(v, "jobs")
|
||||||
for i in 0 .. JOB_COUNT {
|
for i in 0 .. JOB_COUNT {
|
||||||
let p = sv_ints(js, Jobs[i].key)
|
let p = sv_ints(js, Jobs[i].key)
|
||||||
if Jobs[i].scope == sc and len(p) == 2 { jobs_set(jobs_st, i, p[0], p[1]) }
|
if Jobs[i].scope != sc or len(p) < 2 { continue }
|
||||||
|
jobs_set(jobs_st, i, p[0], p[1])
|
||||||
|
if len(p) > 2 { jobs_st.jobs__back[i] = p[2] }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if Value.has(v, "boards") == 0 { return }
|
if Value.has(v, "boards") == 0 { return }
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ export state JobsState {
|
||||||
jobs__st: []int = jobs__ints(JOB_COUNT, JOBS_OFFERED)
|
jobs__st: []int = jobs__ints(JOB_COUNT, JOBS_OFFERED)
|
||||||
jobs__hv: []int = jobs__ints(JOB_COUNT, 0)
|
jobs__hv: []int = jobs__ints(JOB_COUNT, 0)
|
||||||
jobs__seen: []bool = jobs__bools(JOB_COUNT) # an offer already said
|
jobs__seen: []bool = jobs__bools(JOB_COUNT) # an offer already said
|
||||||
|
jobs__back: []int = jobs__ints(JOB_COUNT, 0) # given up: the first day it may be taken again
|
||||||
jobs__primed: bool = false # the first tick after a reset learns the offers quietly
|
jobs__primed: bool = false # the first tick after a reset learns the offers quietly
|
||||||
jobs__done_n: []int = jobs__ints(JOBS_GROUPS, 0) # handed in, per group
|
jobs__done_n: []int = jobs__ints(JOBS_GROUPS, 0) # handed in, per group
|
||||||
jobs__off: []int = jobs__offsets()
|
jobs__off: []int = jobs__offsets()
|
||||||
|
|
|
||||||
132
packages/ludic.jobs/tests/giveup_test.ludic
Normal file
132
packages/ludic.jobs/tests/giveup_test.ludic
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
# giveup_test.ludic - letting a job go: a written job back on offer tomorrow, not today; a post taken
|
||||||
|
# down and replaced in the morning; a daily never taken; the day back saved with the job
|
||||||
|
import "ludic.jobs"
|
||||||
|
import "ludic.base"
|
||||||
|
program JobsGiveUpTest {
|
||||||
|
numbers float
|
||||||
|
const K_FISH: int = 0
|
||||||
|
const K_PHOTO: int = 1
|
||||||
|
const K_WOOD: int = 2
|
||||||
|
|
||||||
|
def Jobs catch { group: 0, kind: K_FISH, need: 3, money: 30, rep: 5 }
|
||||||
|
def Jobs deer { group: 0, kind: K_PHOTO, param: 4, need: 1, money: 20, rep: 2, minrep: 10 }
|
||||||
|
def Jobs ten { group: 0, kind: K_FISH, need: 10, money: 70, rep: 12, scope: JOBS_PARTY }
|
||||||
|
def Jobs still { group: 1, need: 0, money: 60, rep: 12, minstage: 2 }
|
||||||
|
def JobBoards daily { slots: 3, keep: 1, auto: true, refill: false }
|
||||||
|
def JobBoards ada { slots: 2, keep: 7, scope: JOBS_PARTY }
|
||||||
|
|
||||||
|
state JobsTestState {
|
||||||
|
rep: int = 0
|
||||||
|
stage: int = 0
|
||||||
|
day: int = 1
|
||||||
|
guest: bool = false
|
||||||
|
fish: int = 0 # the state's count of fish
|
||||||
|
paid: int = 0
|
||||||
|
paid_rep: int = 0
|
||||||
|
paid_scope: int = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
function world_rep(jobs_test_st: JobsTestState, sc: int) -> int { return jobs_test_st.rep }
|
||||||
|
function world_stage(jobs_test_st: JobsTestState) -> int { return jobs_test_st.stage }
|
||||||
|
function world_day(jobs_test_st: JobsTestState) -> int { return jobs_test_st.day }
|
||||||
|
function world_owns(jobs_test_st: JobsTestState, sc: int) -> bool { return sc == JOBS_SELF or not jobs_test_st.guest }
|
||||||
|
function world_evidence(jobs_test_st: JobsTestState, kind: int, param: int) -> int {
|
||||||
|
if kind == K_FISH { return jobs_test_st.fish }
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
function world_roll(jobs_st: mut JobsState, b: int, s: int) -> void {
|
||||||
|
let need = jobs_roll(jobs_st, 2, 4)
|
||||||
|
if b == BOARD_DAILY { jobs_post(jobs_st, b, s, K_WOOD, -1, need, 7, 2) } else { jobs_post(jobs_st, b, s, K_FISH, -1, need, 9 * need, 3) }
|
||||||
|
}
|
||||||
|
function pay(jobs_test_st: mut JobsTestState, sc: int, money: int, r: int) -> void {
|
||||||
|
jobs_test_st.paid += money
|
||||||
|
jobs_test_st.paid_rep += r
|
||||||
|
jobs_test_st.paid_scope = sc
|
||||||
|
}
|
||||||
|
|
||||||
|
bind JobsWorld { rep: fn world_rep, stage: fn world_stage, day: fn world_day, owns: fn world_owns, evidence: fn world_evidence, roll: fn world_roll }
|
||||||
|
bind JobsPay { reward: fn pay }
|
||||||
|
|
||||||
|
function fresh(jobs_st: mut JobsState, jobs_test_st: mut JobsTestState) -> void {
|
||||||
|
jobs_test_st.rep = 0
|
||||||
|
jobs_test_st.stage = 0
|
||||||
|
jobs_test_st.day = 1
|
||||||
|
jobs_test_st.guest = false
|
||||||
|
jobs_test_st.fish = 0
|
||||||
|
jobs_test_st.paid = 0
|
||||||
|
jobs_test_st.paid_rep = 0
|
||||||
|
jobs_reset(jobs_st)
|
||||||
|
jobs_party_reset(jobs_st)
|
||||||
|
}
|
||||||
|
|
||||||
|
function count_of(jobs_st: JobsState, what: int) -> int {
|
||||||
|
let fs = q_drain(jobs_facts(jobs_st))
|
||||||
|
var n = 0
|
||||||
|
for i in 0 .. len(fs) { if fs[i].what == what { n += 1 } }
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
test "a job given up is offered again tomorrow, not today" (jobs_st: mut JobsState, jobs_test_st: mut JobsTestState) {
|
||||||
|
fresh(jobs_st, jobs_test_st)
|
||||||
|
expect(jobs_take(jobs_st, JOB_CATCH))
|
||||||
|
jobs_count(jobs_st, K_FISH, 0, 2)
|
||||||
|
count_of(jobs_st, JOBS_F_GIVEN_UP)
|
||||||
|
expect(jobs_give_up(jobs_st, JOB_CATCH))
|
||||||
|
expect_eq(count_of(jobs_st, JOBS_F_GIVEN_UP), 1)
|
||||||
|
expect_eq(jobs_state(jobs_st, JOB_CATCH), JOBS_OFFERED)
|
||||||
|
expect_eq(jobs_have(jobs_st, JOB_CATCH), 0)
|
||||||
|
expect(not jobs_offered(jobs_st, JOB_CATCH))
|
||||||
|
expect(not jobs_take(jobs_st, JOB_CATCH))
|
||||||
|
expect(not jobs_give_up(jobs_st, JOB_CATCH))
|
||||||
|
jobs_test_st.day = 2
|
||||||
|
expect(jobs_offered(jobs_st, JOB_CATCH))
|
||||||
|
expect(jobs_take(jobs_st, JOB_CATCH))
|
||||||
|
expect_eq(jobs_test_st.paid, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
test "a ready job can be let go too, and nothing is paid" (jobs_st: mut JobsState, jobs_test_st: mut JobsTestState) {
|
||||||
|
fresh(jobs_st, jobs_test_st)
|
||||||
|
jobs_take(jobs_st, JOB_CATCH)
|
||||||
|
jobs_count(jobs_st, K_FISH, 0, 3)
|
||||||
|
expect_eq(jobs_state(jobs_st, JOB_CATCH), JOBS_READY)
|
||||||
|
expect(jobs_give_up(jobs_st, JOB_CATCH))
|
||||||
|
expect(not jobs_hand_in(jobs_st, JOB_CATCH))
|
||||||
|
expect_eq(jobs_test_st.paid, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
test "a post given up comes down, and the morning puts up another" (jobs_st: mut JobsState, jobs_test_st: mut JobsTestState) {
|
||||||
|
fresh(jobs_st, jobs_test_st)
|
||||||
|
jobs_morning(jobs_st)
|
||||||
|
count_of(jobs_st, JOBS_F_OFFERED)
|
||||||
|
expect(jobs_post_give_up(jobs_st, BOARD_ADA, 0))
|
||||||
|
expect_eq(count_of(jobs_st, JOBS_F_GIVEN_UP), 1)
|
||||||
|
expect_eq(jobs_post_kind(jobs_st, BOARD_ADA, 0), -1)
|
||||||
|
expect(not jobs_post_give_up(jobs_st, BOARD_ADA, 0))
|
||||||
|
expect(jobs_post_kind(jobs_st, BOARD_ADA, 1) >= 0)
|
||||||
|
jobs_test_st.day = 2
|
||||||
|
jobs_morning(jobs_st)
|
||||||
|
expect(jobs_post_kind(jobs_st, BOARD_ADA, 0) >= 0)
|
||||||
|
expect_eq(jobs_post_day(jobs_st, BOARD_ADA, 0), 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
test "a daily is not taken, so it is not given up" (jobs_st: mut JobsState, jobs_test_st: mut JobsTestState) {
|
||||||
|
fresh(jobs_st, jobs_test_st)
|
||||||
|
jobs_morning(jobs_st)
|
||||||
|
expect(not jobs_post_give_up(jobs_st, BOARD_DAILY, 0))
|
||||||
|
expect(jobs_post_kind(jobs_st, BOARD_DAILY, 0) >= 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
test "the day back is saved with the job" (jobs_st: mut JobsState, jobs_test_st: mut JobsTestState) {
|
||||||
|
fresh(jobs_st, jobs_test_st)
|
||||||
|
jobs_take(jobs_st, JOB_CATCH)
|
||||||
|
jobs_give_up(jobs_st, JOB_CATCH)
|
||||||
|
let body = jobs_save(jobs_st)
|
||||||
|
fresh(jobs_st, jobs_test_st)
|
||||||
|
expect(jobs_offered(jobs_st, JOB_CATCH))
|
||||||
|
jobs_load(jobs_st, body, 1)
|
||||||
|
expect_eq(jobs_back_on(jobs_st, JOB_CATCH), 2)
|
||||||
|
expect(not jobs_offered(jobs_st, JOB_CATCH))
|
||||||
|
jobs_test_st.day = 2
|
||||||
|
expect(jobs_offered(jobs_st, JOB_CATCH))
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue