Skip to content

Commit

Permalink
implement basic wasm integration
Browse files Browse the repository at this point in the history
  • Loading branch information
alexander-zibert committed Oct 29, 2023
1 parent 63d284f commit 4f89199
Show file tree
Hide file tree
Showing 10 changed files with 326 additions and 123 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/a.out
/.vscode
/test.out
/test.out
/build
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ all:
clang++ -std=c++2b -O2 -Wall main.cpp
doctest:
clang++ -std=c++2b -O2 -Wall test/*.cpp -o test.out
wasm:
em++ wasm.cpp -O2 -s WASM=1 -o build/wasmWrapper.js
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,19 @@ This is a solver for the board game:

## Building

### CLI Build

Just run `make` in the root directory. Then you can run the binary `./a.out`.

### WASM Build

The WASM build requires a working [emscripten toolchain](https://emscripten.org/docs/getting_started/downloads.html#installation-instructions-using-the-emsdk-recommended).

Afterwards, you can run `make wasm` to build.

Next, you need to serve these files. You can run `python -m http.server 9000` in the root directory to serve the files with a simple python http server.
Then, the frontend is accessible under `http://localhost:9000/frontend/`. As of now there only is some output in the JS console.

## Tests

Run `make doctest` in the root directory. Then you can run the binary `./test.out`.
Expand Down
16 changes: 15 additions & 1 deletion cards.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -183,4 +183,18 @@ const auto c48 = card_t{blue_less_than_yellow, blue_equal_to_yellow,
yellow_greater_than_purple};

// card XX
const auto v = verifier_t{"", [](const code_t &code) { return true; }};
const auto v = verifier_t{"", [](const code_t &code) { return true; }};

const auto all_cards = []() {
auto result = std::vector<card_t>{49};
result[2] = c2;
result[12] = c12;
result[16] = c16;
result[23] = c23;
result[24] = c24;
result[33] = c33;
result[35] = c35;
result[45] = c45;
result[48] = c48;
return result;
}();
12 changes: 12 additions & 0 deletions frontend/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en-us">
<head>
<meta charset="utf-8" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Turing Machine</title>
</head>
<body>
<script type="text/javascript" src="main.js"></script>
<script async type="text/javascript" src="../build/wasmWrapper.js"></script>
</body>
</html>
98 changes: 98 additions & 0 deletions frontend/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
function main(Module) {
const { memory, solve_wasm } = Module.asm;
console.log(memory.buffer);
const numCards = 6;
const numQueries = 0;
const inputValues = [numCards, 2, 12, 24, 33, 35, 45, numQueries];
let offset = 0;
const input = new Uint8Array(memory.buffer, offset, inputValues.length);
input.set(inputValues);
offset += input.byteLength;

const output = new Uint8Array(memory.buffer, offset, 1000);
offset += output.byteLength;

const start = new Date();
solve_wasm(input.byteOffset, output.byteOffset);
console.log("Solving took", new Date() - start, "ms");
offset = 0;
const numCodes = output[offset];
offset += 1;
const codes = [];
for (let i = 0; i < numCodes; i += 1) {
codes.push(
String(output[offset]) + output[offset + 1] + output[offset + 2]
);
offset += 3;
}
console.log(codes);

const possibleVerifiers = [];
for (let i = 0; i < numCards; i += 1) {
const numVerifiers = output[offset];
offset += 1;
possibleVerifiers.push([]);
for (let j = 0; j < numVerifiers; j += 1) {
const verifier = output[offset];
offset += 1;
possibleVerifiers[i].push(verifier);
}
}
console.log(possibleVerifiers);

const possibleLetters = [];
for (let i = 0; i < numCards; i += 1) {
const numLetters = output[offset];
offset += 1;
possibleLetters.push([]);
for (let j = 0; j < numLetters; j += 1) {
const letter = output[offset];
offset += 1;
possibleLetters[i].push(letter);
}
}
console.log(possibleLetters);
}

window.Module = {
preRun: [],
postRun: [main],
print: (function () {
return function (text) {
console.log(text);
};
})(),
setStatus: (text) => {
if (!Module.setStatus.last)
Module.setStatus.last = { time: Date.now(), text: "" };
if (text === Module.setStatus.last.text) return;
var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/);
var now = Date.now();
if (m && now - Module.setStatus.last.time < 30) return; // if this is a progress update, skip it if too soon
Module.setStatus.last.time = now;
Module.setStatus.last.text = text;
console.log(text);
},
totalDependencies: 0,
monitorRunDependencies: (left) => {
this.totalDependencies = Math.max(this.totalDependencies, left);
Module.setStatus(
left
? "Preparing... (" +
(this.totalDependencies - left) +
"/" +
this.totalDependencies +
")"
: "All downloads complete."
);
},
};
Module.setStatus("Downloading...");
window.onerror = (event) => {
// TODO: do not warn on ok events like simulating an infinite loop or exitStatus
Module.setStatus("Exception thrown, see JavaScript console");
spinnerElement.style.display = "none";
Module.setStatus = (text) => {
if (text) console.error("[post-exception status] " + text);
};
};
119 changes: 0 additions & 119 deletions main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,125 +9,6 @@
#include "solver.hpp"
#include "verifier.hpp"

struct query_t {
code_t code;
char verifierIdx;
bool result;
};

const auto initialize_verifier_sets = [](const std::vector<card_t> &game,
bool nightmareMode) {
auto matchingVerifiers = std::vector<std::vector<char>>{game.size()};
auto nightmareSet = std::vector<char>{};
for (size_t i = 0; i < game.size(); i += 1) {
nightmareSet.push_back('A' + i);
}
for (size_t i = 0; i < game.size(); i += 1) {
if (nightmareMode) {
matchingVerifiers[i] = nightmareSet;
} else {
matchingVerifiers[i].push_back('A' + i);
}
}
return matchingVerifiers;
};

struct result_t {
std::set<std::string> possibleCodes;
std::vector<std::set<uint8_t>> possibleVerifiers;
std::vector<std::set<char>> possibleMatches;
};

const auto solve = [](const std::vector<card_t> &game,
const std::vector<query_t> &queries) {
// Step 1: Calculate all possible verifier combinations of the game that lead
// to a unique solution with no redundant verifiers
auto possibleCombinations = std::vector<std::vector<verifier_t>>{};
auto consumer = [&possibleCombinations](
const std::vector<verifier_t> &verifierCombination) {
if (has_single_solution(verifierCombination) &&
!has_redundant_information(verifierCombination)) {
possibleCombinations.push_back(verifierCombination);
}
};
cartesianProduct(game, consumer);

// Step 2: Use the queries to match verifier cards with machine slots ('A',
// 'B', 'C', ...).

auto solverResult = result_t{};
solverResult.possibleMatches.resize(game.size());
solverResult.possibleVerifiers.resize(game.size());
for (size_t i = 0; i < possibleCombinations.size(); i += 1) {
const auto &possibleCombination = possibleCombinations[i];
auto possibleMatchings = initialize_verifier_sets(game, true);
// remove all matchings which give a different result
for (const auto &query : queries) {
for (size_t cardIdx = 0; cardIdx < possibleCombination.size();
cardIdx += 1) {
const auto &verifier = possibleCombination[cardIdx];
auto &possibleMatching = possibleMatchings[cardIdx];
if (verifier.isValid(query.code) != query.result) {
possibleMatching.erase(std::remove(possibleMatching.begin(),
possibleMatching.end(),
query.verifierIdx),
possibleMatching.end());
}
}
}
// go through all remaining combinations
auto possibleMatchingCombinations = std::vector<std::vector<char>>{};
cartesianProduct(possibleMatchings, [&possibleMatchingCombinations](
const std::vector<char> &comb) {
// TODO: the performance can be improved if the the branch is not further
// followed if a duplicate is encountered, however then we cannot just
// reuse cartesianProduct
auto uniqueSet = std::vector<bool>{};
uniqueSet.resize(comb.size(), false);

auto count = 0;
for (const auto el : comb) {
if (!uniqueSet[el - 'A']) {
count += 1;
}
uniqueSet[el - 'A'] = true;
}
if (count != comb.size()) {
return;
}
possibleMatchingCombinations.push_back(comb);
});
if (possibleMatchingCombinations.size() == 0) {
continue;
}

// build result
const auto solution = get_solution(possibleCombination);
const auto solutionIdx = find_solution_idx(solution);
const auto code = human_codes[solutionIdx];
solverResult.possibleCodes.insert(code);

for (const auto &possibleMatchingCombination :
possibleMatchingCombinations) {
for (size_t i = 0; i < possibleMatchingCombination.size(); i += 1) {
solverResult.possibleMatches[i].insert(possibleMatchingCombination[i]);
}
}

for (size_t cardIdx = 0; cardIdx < game.size(); cardIdx += 1) {
const auto &card = game[cardIdx];
for (size_t verifierIdx = 0; verifierIdx < card.size();
verifierIdx += 1) {
const auto &verifier = card[verifierIdx];
if (verifier.name == possibleCombination[cardIdx].name) {
solverResult.possibleVerifiers[cardIdx].insert(verifierIdx);
}
}
}
}
return solverResult;
};

int main() {
const auto game = std::vector<card_t>{c2, c12, c24, c33, c35, c45};
auto result = solve(game, {{{1, 2, 3}, 'A', true},
Expand Down
Loading

0 comments on commit 4f89199

Please sign in to comment.