Prune State-Space: Don't identify blocks based on IDs, just use their positions
This commit is contained in:
@ -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);
|
||||||
|
57
src/main.js
57
src/main.js
@ -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;
|
||||||
});
|
});
|
||||||
|
@ -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];
|
||||||
|
@ -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) => {
|
||||||
|
@ -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];
|
||||||
|
@ -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) => {
|
||||||
|
Reference in New Issue
Block a user