diff --git a/agents/greed.rhai b/agents/greed.rhai new file mode 100644 index 0000000..99e33ec --- /dev/null +++ b/agents/greed.rhai @@ -0,0 +1,169 @@ + + +// Return a random valid action on the given board. +// Used as a last resort. +fn random_action(board) { + let symb = rand_symb(); + let pos = rand_int(0, 10); + let action = Action(symb, pos); + + while !board.can_play(action) { + let symb = rand_symb(); + let pos = rand_int(0, 10); + action = Action(symb, pos); + } + + return action +} + + +/// Returns an array of (idx, f32) for each empty slot in the board. +/// - idx is the index of this slot +/// - f32 is the "influence of" this slot +fn compute_influence(board) { + // Fill all empty slots with fives and compute starting value + let filled = board; + for i in filled.free_spots_idx() { + filled[i] = 5; + } + + // Compute the value of the filled board + let base = filled.evaluate(); + + // Exit early if the board is invalid. + // This is usually caused by zero-division. + if (base == ()) { + return []; + } + + // Increase each slot's value by 1 + // and record the effect on the expression's total value. + // + // `influence` is an array of (slot_idx, value) + let influence = []; + for i in 0..board.size() { + let slot = board[i]; + + // Ignore slots that are not empty + if slot != "" { + continue + } + + // Don't assign directly to `filled`, + // we want to keep it full of fives. + // Assigning to `b` make a copy of the board. + let b = filled; + b[i] = 6; + + influence.push([i, b.evaluate() - base]); + } + + + // Sort by increasing absolute score + influence.sort(|a, b| { + let a_abs = a[1].abs(); + let b_abs = b[1].abs(); + + // Returns... + // 1 if positive (a_abs > b_abs), + // -1 if negative, + // 0 if equal + return sign(a_abs - b_abs); + }); + + return influence; +} + +fn place_number(board, minimize) { + let numbers = [0,1,2,3,4,5,6,7,8,9]; + let available_numbers = numbers.retain(|x| board.contains(x)); + + let influence = compute_influence(board); + + // Stupid edge cases, fall back to random + if influence.len() == 0 || available_numbers.len() == 0 { + return random_action(board); + } + + + // Get the most influential position + let pos = influence[-1][0]; + let val = influence[-1][1]; + + // Pick the number we should use, + // This is always either the largest + // or the smallest number available to us. + let symbol = 0; + if minimize { + if val > 0 { + symbol = available_numbers[0]; + } else { + symbol = available_numbers[-1]; + } + } else { + if val > 0 { + symbol = available_numbers[-1]; + } else { + symbol = available_numbers[0]; + } + } + + return Action(symbol, pos); +} + +fn place_op(board, minimize) { + let ops = ["+", "-", "*", "/"]; + let available_ops = ops.retain(|x| board.contains(x)); + + // Place operations first, + // they matter much more than numbers + let give_up = 10; + if !available_ops.is_empty() { + let aa = available_ops.rand_shuffle(); + let pos = rand_int(0, 10); + let action = Action(aa[0], pos); + + while !board.can_play(action) { + let pos = rand_int(0, 10); + action = Action(aa[0], pos); + + // In case there are no valid operator moves + give_up -= 1; + if give_up == 0 { break } + } + + return action + } + + // Could not place an operation + return (); +} + + +// Main step function (shared between min and max) +fn greed_step(board, minimize) { + + let action = place_op(board, minimize); + if action == () { + action = place_number(board, minimize); + } + + if board.can_play(action) { + return action; + } + + // Prevent invalid moves, random fallback + return random_action(board); +} + + + +// Minimizer step +fn step_min(board) { + greed_step(board, true) +} + +// Maximizer step +fn step_max(board) { + greed_step(board, false) +} diff --git a/agents/random.rhai b/agents/random.rhai new file mode 100644 index 0000000..8dc56a6 --- /dev/null +++ b/agents/random.rhai @@ -0,0 +1,21 @@ +fn random_action(board) { + let symb = rand_symb(); + let pos = rand_int(0, 10); + let action = Action(symb, pos); + + while !board.can_play(action) { + let symb = rand_symb(); + let pos = rand_int(0, 10); + action = Action(symb, pos); + } + + return action +} + +fn step_min(board) { + random_action(board) +} + +fn step_max(board) { + random_action(board) +} \ No newline at end of file