Prune State-Space: Don't identify blocks based on IDs, just use their positions

This commit is contained in:
2025-08-30 16:58:48 +02:00
parent 478fa10afd
commit 847d61ecca
6 changed files with 78 additions and 231 deletions

View File

@ -23,8 +23,10 @@ export const generate_graph = (data, node_click_handler) => {
node_click_handler(node, graph); node_click_handler(node, graph);
}); });
// graph.forceEngine("ngraph");
// graph.numDimensions([3]);
graph.d3Force("link").distance(35); graph.d3Force("link").distance(35);
// graph.warmupTicks([100]); graph.warmupTicks([10]);
// graph.cooldownTicks([0]); // graph.cooldownTicks([0]);
reset_graph_view(graph); reset_graph_view(graph);

View File

@ -26,28 +26,44 @@ let graph = null;
// Helpers // Helpers
// //
export const key_of = (arr) => JSON.stringify(arr.board); export const prepare = (state) => {
let _state = structuredClone(state);
for (let i = 0; i < state.board.length; i++) {
// Remove IDs
_state.board[i].splice(0, 1);
}
// Sort, so we can compare keys
_state.board.sort();
return _state;
};
// Disable "prepare" to compare IDs aswell (explodes the state space though)
export const key_of = (state) => JSON.stringify(prepare(state).board);
const clear_states = () => { const clear_states = () => {
states = { states = {
_map: new Map(), _map: new Map(),
add(arr) { add(state) {
const k = key_of(arr); const key = key_of(state);
if (!this._map.has(k)) this._map.set(k, arr); if (!this._map.has(key)) {
this._map.set(key, state);
}
return this; return this;
}, },
has(arr) { has(state) {
return this._map.has(key_of(arr)); return this._map.has(key_of(state));
}, },
get(k) { get(key) {
return this._map.get(k); return this._map.get(key);
}, },
delete(arr) { delete(state) {
return this._map.delete(key_of(arr)); return this._map.delete(key_of(state));
}, },
values() { values() {
@ -135,19 +151,20 @@ document.getElementById("model_canvas").addEventListener("click", (event) => {
}); });
const move = (direction) => { const move = (direction) => {
if (selected_element === null) { if (selected_element === null || graph === null) {
return; return;
} }
const [state, element] = model.move( const [state, element] = model.move(
states, states,
graph, data,
current_state, current_state,
selected_element, selected_element,
DIRECTIONS[direction], DIRECTIONS[direction],
); );
if (state !== null) { if (state !== null) {
graph.graphData(data);
current_state = state; current_state = state;
selected_element = element; selected_element = element;
clear_visualization(); clear_visualization();
@ -190,26 +207,22 @@ document.addEventListener("keyup", (event) => {
// //
document.getElementById("select_model_button").addEventListener("click", () => { document.getElementById("select_model_button").addEventListener("click", () => {
clear_graph();
clear_visualization();
current_model = (current_model + 1) % models.length; current_model = (current_model + 1) % models.length;
model = models[current_model]; model = models[current_model];
current_initial_state = 0; current_initial_state = 0;
initial_state = model.initial_states[current_initial_state]; initial_state = model.initial_states[current_initial_state];
selected_element = null;
current_state = initial_state; current_state = initial_state;
selected_element = null;
clear_states(); clear_states();
states.add(initial_state); states.add(initial_state);
graph = null; clear_graph();
graph = generate_graph(data, node_click_view_state); clear_visualization();
model.visualize(initial_state); model.visualize(initial_state);
document.getElementById("model_name").innerHTML = model.name; document.getElementById("model_name").innerHTML = model.name;
document.getElementById("state_name").innerHTML = initial_state.name; document.getElementById("state_name").innerHTML = initial_state.name;
}); });
document.getElementById("select_state_button").addEventListener("click", () => { document.getElementById("select_state_button").addEventListener("click", () => {
clear_graph();
clear_visualization();
current_initial_state = current_initial_state =
(current_initial_state + 1) % model.initial_states.length; (current_initial_state + 1) % model.initial_states.length;
initial_state = model.initial_states[current_initial_state]; initial_state = model.initial_states[current_initial_state];
@ -217,8 +230,10 @@ document.getElementById("select_state_button").addEventListener("click", () => {
selected_element = null; selected_element = null;
clear_states(); clear_states();
states.add(initial_state); states.add(initial_state);
graph = null; clear_graph();
graph = generate_graph(data, node_click_view_state); clear_visualization();
// graph = null;
// graph = generate_graph(data, node_click_view_state);
model.visualize(initial_state); model.visualize(initial_state);
document.getElementById("state_name").innerHTML = initial_state.name; document.getElementById("state_name").innerHTML = initial_state.name;
}); });

View File

@ -9,10 +9,7 @@ import { cross_three } from "./initial_states/cross_three.js";
import { import {
get_moves, get_moves,
block_is_movable, block_is_movable,
index_of_state,
remove_block,
move_block, move_block,
insert_block,
move_state_block, move_state_block,
arrays_are_equal, arrays_are_equal,
} from "./state.js"; } from "./state.js";
@ -112,53 +109,27 @@ const move = (states, data, state, block, direction) => {
return [null, null]; return [null, null];
} }
// let new_state = remove_block(state, block);
// let new_block = move_block(block, direction);
// insert_block(new_state, block);
let new_state = structuredClone(state); let new_state = structuredClone(state);
delete new_state.name; delete new_state.name;
let new_block = move_block(block, direction); let new_block = move_block(block, direction);
move_state_block(new_state, block, direction); move_state_block(new_state, block, direction);
let new_link = null; const new_link = {
let new_node = null;
if (states.has(new_state)) {
// We already had this state, just generate a link
new_link = {
source: key_of(state), // We're coming from this state... source: key_of(state), // We're coming from this state...
target: key_of(new_state), // ...and ended up here, at a previous state. target: key_of(new_state), // ...and ended up here, at a previous state.
}; };
} else {
let new_node = null;
if (!states.has(new_state)) {
states.add(new_state); states.add(new_state);
new_node = { new_node = {
id: key_of(new_state), id: key_of(new_state),
}; };
new_link = {
source: key_of(state), // We're coming from this state...
target: key_of(new_state), // ...and ended up here, at a new state.
};
} }
// TODO: Faster without this? data.links.push(new_link);
const has_link = (data, link) => {
// for (let l of data.links) {
// if (l.source.id === link.source && l.target.id === link.target) {
// return true;
// }
// if (l.source.id === link.target && l.target.id === link.source) {
// return true;
// }
// }
return false;
};
if (new_node !== null) { if (new_node !== null) {
data.nodes.push(new_node); data.nodes.push(new_node);
data.links.push(new_link);
} else if (!has_link(data, new_link)) {
data.links.push(new_link);
} }
return [new_state, new_block]; return [new_state, new_block];

View File

@ -24,80 +24,20 @@ export const arrays_are_equal = (array, other_array) => {
return true; return true;
}; };
export const state_contains_block = (state, block) => {
return (
state.board.length >= block[0] &&
arrays_are_equal(state.board[block[0]], block)
);
// for (let i = 0; i < state.board.length / 4; i++) {
// const other_block = state.board.slice(i * 4, (i + 1) * 4);
//
// if (arrays_are_equal(block, other_block)) {
// return true;
// }
// }
//
// return false;
};
export const states_are_equal = (state, other_state) => { export const states_are_equal = (state, other_state) => {
return key_of(state) === key_of(other_state); let _state = structuredClone(state);
let _other_state = structuredClone(other_state);
if (state.board.length != other_state.board.length) {
return false;
}
// Disable to compare IDs aswell (explodes the state space though)
for (let i = 0; i < state.board.length; i++) { for (let i = 0; i < state.board.length; i++) {
if (!arrays_are_equal(state.board[i], other_state.board[i])) { // Remove IDs
return false; _state.board[i].splice(0, 1);
} _other_state.board[i].splice(0, 1);
} }
_state.board.sort();
_other_state.board.sort();
return true; return key_of(_state) === key_of(_other_state);
// for (let i = 0; i < state.board.length / 4; i++) {
// const block = state.board.slice(i * 4, (i + 1) * 4);
//
// if (!state_contains_block(other_state, block)) {
// return false;
// }
// }
//
// return true;
};
export const index_of_state = (states, state) => {
for (let i = 0; i < states.length; i++) {
if (states_are_equal(states[i], state)) {
return i;
}
}
return null;
};
export const remove_block = (state, block) => {
let new_state = structuredClone(state);
delete new_state.name;
new_state.board.splice(block[0], 1);
return new_state;
// for (let i = 0; i < state.board.length / 4; i++) {
// const other_block = state.board.slice(i * 4, (i + 1) * 4);
//
// if (arrays_are_equal(block, other_block)) {
// new_state.board.splice(i * 4, 4);
// }
// }
//
// return new_state;
};
export const insert_block = (state, block) => {
state.board.splice(block[0], 0, block);
}; };
export const move_block = (block, direction) => { export const move_block = (block, direction) => {

View File

@ -7,10 +7,7 @@ import { klotski } from "./initial_states/klotski.js";
import { import {
get_moves, get_moves,
block_is_movable, block_is_movable,
index_of_state,
remove_block,
move_block, move_block,
insert_block,
move_state_block, move_state_block,
} from "./state.js"; } from "./state.js";
import { key_of } from "../../main.js"; import { key_of } from "../../main.js";
@ -46,6 +43,11 @@ const generate = (states, initial_state, graph, previous_move) => {
last_print = states_after; last_print = states_after;
} }
// Arbitrary upper limit in case of errors
if (states_after > 100000) {
return;
}
if (states_after > states_before) { if (states_after > states_before) {
stack.push([new_state, m]); stack.push([new_state, m]);
} }
@ -80,10 +82,6 @@ const move = (states, data, state, block, direction) => {
return [null, null]; return [null, null];
} }
// let new_state = remove_block(state, block);
// let new_block = move_block(block, direction);
// insert_block(new_state, block);
let new_state; let new_state;
try { try {
new_state = structuredClone(state); new_state = structuredClone(state);
@ -95,44 +93,22 @@ const move = (states, data, state, block, direction) => {
let new_block = move_block(block, direction); let new_block = move_block(block, direction);
move_state_block(new_state, block, direction); move_state_block(new_state, block, direction);
let new_link = null; const new_link = {
let new_node = null;
if (states.has(new_state)) {
// We already had this state, just generate a link
new_link = {
source: key_of(state), // We're coming from this state... source: key_of(state), // We're coming from this state...
target: key_of(new_state), // ...and ended up here, at a previous state. target: key_of(new_state), // ...and ended up here, at a previous state.
}; };
} else {
let new_node = null;
if (!states.has(new_state)) {
states.add(new_state); states.add(new_state);
new_node = { new_node = {
id: key_of(new_state), id: key_of(new_state),
}; };
new_link = {
source: key_of(state), // We're coming from this state...
target: key_of(new_state), // ...and ended up here, at a new state.
};
} }
// TODO: Faster without this? data.links.push(new_link);
const has_link = (data, link) => {
// for (let l of data.links) {
// if (l.source.id === link.source && l.target.id === link.target) {
// return true;
// }
// if (l.source.id === link.target && l.target.id === link.source) {
// return true;
// }
// }
return false;
};
if (new_node !== null) { if (new_node !== null) {
data.nodes.push(new_node); data.nodes.push(new_node);
data.links.push(new_link);
} else if (!has_link(data, new_link)) {
data.links.push(new_link);
} }
return [new_state, new_block]; return [new_state, new_block];

View File

@ -22,80 +22,23 @@ export const arrays_are_equal = (array, other_array) => {
return true; return true;
}; };
export const state_contains_block = (state, block) => {
return (
state.board.length >= block[0] &&
arrays_are_equal(state.board[block[0]], block)
);
// for (let i = 0; i < state.board.length / 4; i++) {
// const other_block = state.board.slice(i * 4, (i + 1) * 4);
//
// if (arrays_are_equal(block, other_block)) {
// return true;
// }
// }
//
// return false;
};
export const states_are_equal = (state, other_state) => { export const states_are_equal = (state, other_state) => {
return key_of(state) === key_of(other_state); let _state = structuredClone(state);
let _other_state = structuredClone(other_state);
if (state.board.length != other_state.board.length) {
return false;
}
// Disable to compare IDs aswell (explodes the state space though)
for (let i = 0; i < state.board.length; i++) { for (let i = 0; i < state.board.length; i++) {
if (!arrays_are_equal(state.board[i], other_state.board[i])) { // Remove IDs
return false; _state.board[i].splice(0, 1);
} _other_state.board[i].splice(0, 1);
} }
return true; // TODO: Doesn't work
// Sort, so we can compare keys
_state.board.sort();
_other_state.board.sort();
// for (let i = 0; i < state.board.length / 4; i++) { return key_of(_state) === key_of(_other_state);
// const block = state.board.slice(i * 4, (i + 1) * 4);
//
// if (!state_contains_block(other_state, block)) {
// return false;
// }
// }
//
// return true;
};
export const index_of_state = (states, state) => {
for (let i = 0; i < states.length; i++) {
if (states_are_equal(states[i], state)) {
return i;
}
}
return null;
};
export const remove_block = (state, block) => {
let new_state = structuredClone(state);
delete new_state.name;
new_state.board.splice(block[0], 1);
return new_state;
// for (let i = 0; i < state.board.length / 4; i++) {
// const other_block = state.board.slice(i * 4, (i + 1) * 4);
//
// if (arrays_are_equal(block, other_block)) {
// new_state.board.splice(i * 4, 4);
// }
// }
//
// return new_state;
};
export const insert_block = (state, block) => {
state.board.splice(block[0], 0, block);
}; };
export const move_block = (block, direction) => { export const move_block = (block, direction) => {