code wiki / (root) / nx_actor_role_game_logic.nx

nx_actor_role_game_logic.nx source

↩ module page · 171 lines · 6116 B

1// nx_actor_role_game_logic.nx -- game-logic actor role. 2// 3// Wraps [[nx_tictactoe]] (other-agent / sibling-arc game primitive) as 4// a cooperative actor. Each step plays ONE ply: the configured AI 5// picks a move for the current side, applies it, evaluates outcome, 6// fans out a GAME_ACTION message. Actor COMPLETED when game ends 7// (WIN / DRAW). 8// 9// V1 ships tic-tac-toe; the same adapter shape will wrap nx_checkers 10// + future game primitives in subsequent versions. Lighter transitive 11// import set than the image grader -- composes cleanly with the LLM + 12// audio adapters in a quad-companion smoke per 13// [[feedback-nxc2-module-const-ceiling]] guidance. 14 15import "nx_syscalls.nx" 16import "nx_tier.nx" 17import "nx_prng.nx" 18import "nx_tictactoe.nx" 19import "nx_actor.nx" 20import "nx_message.nx" 21 22// ===== Sealed enum: NxGameLogicVerdict ============================ 23 24const NX_GL_V_STEPPED: nx_int = 0 25const NX_GL_V_COMPLETED: nx_int = 1 26const NX_GL_V_FAILED_PICK: nx_int = 2 27const NX_GL_V_FAILED_APPLY: nx_int = 3 28const NX_GL_V_INVALID: nx_int = 4 29const NX_GL_V_NULL: nx_int = 5 30const NX_GL_V_N: nx_int = 6 31 32// ===== Struct: NxGameLogicCtx ===================================== 33 34struct NxGameLogicCtx { 35 state: *i64, // tictactoe board state 36 difficulty_x: nx_int, // NX_TTT_AI_EASY/MEDIUM/PERFECT 37 difficulty_o: nx_int, 38 prng_state: *i64, 39 n_plies_emitted: nx_int, 40 last_move_idx: nx_int, 41 last_side: nx_int, // who moved last (X or O) 42 last_outcome: nx_int, // ONGOING/WIN/DRAW after last apply 43 last_verdict: nx_int, 44} 45 46const NX_GL_CTX_BYTES: nx_int = 72 // 9 fields * 8 47 48// ===== Validators ================================================= 49 50func nx_gl_v_is_valid(v: nx_int) -> nx_int { 51 if v < 0 { return 0 } 52 if v >= NX_GL_V_N { return 0 } 53 return 1 54} 55 56func nx_gl_difficulty_is_valid(d: nx_int) -> nx_int { 57 if d == NX_TTT_AI_EASY { return 1 } 58 if d == NX_TTT_AI_MEDIUM { return 1 } 59 if d == NX_TTT_AI_PERFECT { return 1 } 60 return 0 61} 62 63// ===== Constructor ================================================ 64 65func nx_gl_actor_new(first_mover: nx_int, 66 difficulty_x: nx_int, 67 difficulty_o: nx_int, 68 prng_seed: i64) -> *NxGameLogicCtx { 69 if first_mover != NX_TTT_X { 70 if first_mover != NX_TTT_O { return 0 as *NxGameLogicCtx } 71 } 72 if nx_gl_difficulty_is_valid(difficulty_x) == 0 { return 0 as *NxGameLogicCtx } 73 if nx_gl_difficulty_is_valid(difficulty_o) == 0 { return 0 as *NxGameLogicCtx } 74 let raw: *u8 = sys_mmap(NX_GL_CTX_BYTES) 75 let ctx: *NxGameLogicCtx = raw as *NxGameLogicCtx 76 ctx.state = nx_ttt_new(first_mover) 77 ctx.difficulty_x = difficulty_x 78 ctx.difficulty_o = difficulty_o 79 ctx.prng_state = sys_mmap(16) as *i64 80 nx_prng_init(ctx.prng_state, prng_seed) 81 ctx.n_plies_emitted = 0 82 ctx.last_move_idx = -1 83 ctx.last_side = 0 84 ctx.last_outcome = NX_TTT_ONGOING 85 ctx.last_verdict = NX_GL_V_STEPPED 86 return ctx 87} 88 89// ===== Pick + apply + emit (one ply) ============================== 90 91func nx_gl_actor_step(ctx: *NxGameLogicCtx, 92 sched: *NxActorScheduler, 93 bus: *NxMessageBus, 94 actor_id: nx_int, 95 now_us: nx_size) -> nx_int { 96 if (ctx as i64) == 0 { return NX_GL_V_NULL } 97 // If game already terminal, idempotent COMPLETED. 98 if ctx.last_outcome != NX_TTT_ONGOING { 99 if (sched as i64) != 0 { 100 let a_done: *NxActor = nx_ac_find(sched, actor_id) 101 if (a_done as i64) != 0 { 102 if a_done.state != NX_AC_STATE_COMPLETED { 103 nx_ac_complete(sched, actor_id) 104 } 105 } 106 } 107 return NX_GL_V_COMPLETED 108 } 109 let side: nx_int = nx_ttt_turn(ctx.state) 110 var difficulty: nx_int = ctx.difficulty_x 111 if side == NX_TTT_O { difficulty = ctx.difficulty_o } 112 let mv: nx_int = nx_ttt_pick(ctx.state, side, difficulty, ctx.prng_state) 113 if mv < 0 { return NX_GL_V_FAILED_PICK } 114 if mv >= 9 { return NX_GL_V_FAILED_PICK } 115 if nx_ttt_is_legal(ctx.state, mv) == 0 { return NX_GL_V_FAILED_PICK } 116 let applied: nx_int = nx_ttt_try_apply(ctx.state, mv) 117 if applied == 0 { return NX_GL_V_FAILED_APPLY } 118 nx_ttt_evaluate(ctx.state) 119 ctx.last_move_idx = mv 120 ctx.last_side = side 121 ctx.last_outcome = nx_ttt_outcome(ctx.state) 122 ctx.n_plies_emitted = ctx.n_plies_emitted + 1 123 124 // EMIT fanout 125 if (bus as i64) != 0 { 126 // payload: pack (side<<4 | move_idx) so listener can decode both 127 let payload: nx_size = ((side * 16) + mv) as nx_size 128 nx_ms_send_fanout(bus, actor_id, NX_MS_KIND_GAME_ACTION, payload, 8, now_us) 129 } 130 131 if (sched as i64) != 0 { nx_ac_step(sched, actor_id, 1000, now_us) } 132 133 if ctx.last_outcome != NX_TTT_ONGOING { 134 if (sched as i64) != 0 { nx_ac_complete(sched, actor_id) } 135 return NX_GL_V_COMPLETED 136 } 137 return NX_GL_V_STEPPED 138} 139 140// ===== Accessors ================================================== 141 142func nx_gl_actor_outcome(ctx: *NxGameLogicCtx) -> nx_int { 143 if (ctx as i64) == 0 { return NX_TTT_ONGOING } 144 return ctx.last_outcome 145} 146 147func nx_gl_actor_winner(ctx: *NxGameLogicCtx) -> nx_int { 148 if (ctx as i64) == 0 { return 0 } 149 return nx_ttt_winner(ctx.state) 150} 151 152func nx_gl_actor_plies(ctx: *NxGameLogicCtx) -> nx_int { 153 if (ctx as i64) == 0 { return 0 } 154 return ctx.n_plies_emitted 155} 156 157func nx_gl_actor_last_move(ctx: *NxGameLogicCtx) -> nx_int { 158 if (ctx as i64) == 0 { return -1 } 159 return ctx.last_move_idx 160} 161 162func nx_gl_actor_last_side(ctx: *NxGameLogicCtx) -> nx_int { 163 if (ctx as i64) == 0 { return 0 } 164 return ctx.last_side 165} 166 167func nx_gl_actor_is_done(ctx: *NxGameLogicCtx) -> nx_int { 168 if (ctx as i64) == 0 { return 0 } 169 if ctx.last_outcome != NX_TTT_ONGOING { return 1 } 170 return 0 171}