mirror of
https://github.com/zenorogue/hyperrogue.git
synced 2024-12-30 03:50:34 +00:00
595 lines
16 KiB
C++
595 lines
16 KiB
C++
#include "../hyper.h"
|
|
#include <fstream>
|
|
#include <semaphore.h>
|
|
|
|
namespace hr {
|
|
|
|
namespace rulegen {
|
|
bool set_general(const string& s);
|
|
}
|
|
|
|
struct symbol {
|
|
unsigned char sid;
|
|
unsigned char eid;
|
|
bool operator < (const symbol s1) const { return (int16_t&) (*this) < (int16_t&) s1; }
|
|
bool operator == (const symbol s1) const { return (int16_t&) (*this) == (int16_t&) s1; }
|
|
bool operator != (const symbol s1) const { return (int16_t&) (*this) != (int16_t&) s1; }
|
|
};
|
|
|
|
using seq = vector<symbol>;
|
|
|
|
void block(symbol& s) {
|
|
s.sid ^= 128;
|
|
}
|
|
|
|
void unblock(symbol& s) {
|
|
s.sid ^= 128;
|
|
}
|
|
|
|
bool is_blocked(symbol& s) {
|
|
return s.sid & 128;
|
|
}
|
|
|
|
void print(hstream& hs, symbol s) {
|
|
print(hs, "[", int(s.sid), ":", int(s.eid), "]");
|
|
}
|
|
|
|
bool reversed;
|
|
|
|
bool lexless(const seq& a, const seq& b) {
|
|
int al = isize(a);
|
|
int bl = isize(b);
|
|
if(al != bl) return al < bl;
|
|
if(reversed) {
|
|
for(int i=al-1; i>=0; i--) if(a[i].eid < 120 && a[i] != b[i]) return a[i] < b[i];
|
|
return a.back() < b.back();
|
|
}
|
|
else {
|
|
return a < b;
|
|
}
|
|
}
|
|
|
|
struct lexless_comparator {
|
|
bool operator()(const seq& a, const seq& b) const { return lexless(a, b); }
|
|
};
|
|
|
|
map<seq, seq, lexless_comparator> rules;
|
|
|
|
seq sub(const seq& s, int a, int l) {
|
|
seq result;
|
|
for(int i=0; i<l; i++) result.push_back(s[a+i]);
|
|
return result;
|
|
}
|
|
|
|
seq cat(const seq& a, const seq& b) {
|
|
seq result;
|
|
for(auto e: a) result.push_back(e);
|
|
for(auto e: b) result.push_back(e);
|
|
return result;
|
|
}
|
|
|
|
void catto(seq& a, const seq& b) {
|
|
a.reserve(a.size() + b.size());
|
|
for(auto e: b) a.push_back(e);
|
|
}
|
|
|
|
void catto_sub(seq& a, const seq& b, int pos, int len) {
|
|
a.reserve(a.size() + len);
|
|
for(int i=0; i<len; i++) a.push_back(b[pos+i]);
|
|
}
|
|
|
|
int max_rule_length;
|
|
|
|
set<pair<seq, seq>> rules_checked;
|
|
|
|
queue<pair<seq, seq>> rulequeue;
|
|
|
|
void enqueue_rule(seq l, seq r) {
|
|
rulequeue.emplace(l, r);
|
|
}
|
|
|
|
cellwalker trace(cellwalker cw, const seq& sq, bool debug) {
|
|
for(auto sym: sq) {
|
|
if(shvid(cw.at) != sym.sid) println(hlog, "error: wrong type");
|
|
if(debug) println(hlog, "at: ", cw);
|
|
cw += sym.eid;
|
|
cw += wstep;
|
|
cw -= cw.spin % arb::current.shapes[shvid(cw.at)].cycle_length;
|
|
}
|
|
return cw;
|
|
}
|
|
|
|
cell *get_sample(int sid) {
|
|
celllister cl(currentmap->gamestart(), 20, 100000, nullptr);
|
|
for(cell *c: cl.lst) if(shvid(c) == sid) return c;
|
|
println(hlog, "sample not found of ", sid);
|
|
exit(1);
|
|
}
|
|
|
|
string verify_rule(const pair<seq, seq>& rule, bool debug = false) {
|
|
cell *cstart = get_sample(rule.first[0].sid);
|
|
cellwalker cw(cstart);
|
|
cellwalker cw1 = trace(cw, rule.first, debug);
|
|
cellwalker cw2 = trace(cw, rule.second, debug);
|
|
return cw1 == cw2 ? "OK" : lalign(0, "FAIL ", cw1, " VS ", cw2);
|
|
}
|
|
|
|
int add_rule(seq l, seq r) {
|
|
indenter ind(2);
|
|
if(l == r) return 0;
|
|
if(lexless(l, r)) swap(l, r);
|
|
|
|
again:
|
|
|
|
if(!rules.count(l)) {
|
|
// println(hlog, "rule added: ", l, " -> ", r, " verify: ", verify_rule({l, r}));
|
|
rules[l] = r;
|
|
return 1;
|
|
}
|
|
else if(rules[l] == r) return 0;
|
|
else if(lexless(r, rules[l])) {
|
|
l = rules[l];
|
|
goto again;
|
|
}
|
|
else {
|
|
// println(hlog, "rule simplified: ", l, " -> ", r);
|
|
auto x = rules[l];
|
|
x = r;
|
|
l = x;
|
|
goto again;
|
|
}
|
|
}
|
|
|
|
symbol gen_symbol(int sid, int eid) {
|
|
symbol sym;
|
|
sym.sid = sid;
|
|
sym.eid = eid;
|
|
return sym;
|
|
}
|
|
|
|
bool include_rotations = false;
|
|
|
|
void prepare_rules() {
|
|
auto& ac = arb::current;
|
|
int N = isize(ac.shapes);
|
|
|
|
/* move and back and move */
|
|
if(!include_rotations) for(int i=0; i<N; i++) {
|
|
auto& sh = ac.shapes[i];
|
|
int K = sh.size();
|
|
for(int k=0; k<K; k++)
|
|
for(int l=0; l<K; l++) {
|
|
seq sq, sq1;
|
|
sq.push_back(gen_symbol(i, k));
|
|
auto con = sh.connections[k % sh.cycle_length];
|
|
auto& sh1 = ac.shapes[con.sid];
|
|
con.eid %= sh1.cycle_length;
|
|
sq.push_back(gen_symbol(con.sid, con.eid));
|
|
sq.push_back(gen_symbol(i, l));
|
|
int k1 = k / sh.cycle_length * sh.cycle_length;
|
|
// if(k1)
|
|
sq1.push_back(gen_symbol(i, (k1 + l) % K));
|
|
enqueue_rule(sq, sq1);
|
|
}
|
|
}
|
|
|
|
/* move and back -> nothing or rotate */
|
|
if(include_rotations) for(int i=0; i<N; i++) {
|
|
auto& sh = ac.shapes[i];
|
|
int K = sh.size();
|
|
for(int k=0; k<K; k++) {
|
|
seq sq, sq1;
|
|
sq.push_back(gen_symbol(i, k));
|
|
auto con = sh.connections[k % sh.cycle_length];
|
|
auto& sh1 = ac.shapes[con.sid];
|
|
con.eid %= sh1.cycle_length;
|
|
sq.push_back(gen_symbol(con.sid, con.eid));
|
|
int k1 = k / sh.cycle_length * sh.cycle_length;
|
|
if(k1) sq1.push_back(gen_symbol(i, 120 + k1));
|
|
if(k1) sq1.push_back(gen_symbol(255, 255));
|
|
if(lexless(sq, sq1)) swap(sq, sq1);
|
|
enqueue_rule(sq, sq1);
|
|
}
|
|
}
|
|
|
|
/* rotate + move -> move */
|
|
if(include_rotations) for(int i=0; i<N; i++) {
|
|
auto& sh = ac.shapes[i];
|
|
int K = sh.size();
|
|
int C = sh.cycle_length;
|
|
for(int k=C; k<K; k+=C)
|
|
for(int l=0; l<K; l++) {
|
|
seq sq, sq1;
|
|
sq.push_back(gen_symbol(i, 120+k));
|
|
sq.push_back(gen_symbol(255, 255));
|
|
sq.push_back(gen_symbol(i, l));
|
|
sq1.push_back(gen_symbol(i, (k+l) % K));
|
|
if(lexless(sq, sq1)) swap(sq, sq1);
|
|
enqueue_rule(sq, sq1);
|
|
}
|
|
}
|
|
|
|
/* cycle a vertex */
|
|
for(int i=0; i<N; i++) {
|
|
auto& sh = ac.shapes[i];
|
|
for(int j=0; j<sh.cycle_length; j++) {
|
|
int val = sh.vertex_valence[j]; // (j+1) % sh.cycle_length];
|
|
int ai = i, aj = j;
|
|
seq sq;
|
|
// cellwalker cw(get_sample(i), j);
|
|
// auto cw0 = cw;
|
|
for(int v=0; v<val; v++) {
|
|
// println(hlog, "at: ", cw);
|
|
/*
|
|
cw++;
|
|
cw += wstep;
|
|
*/
|
|
auto& ash = ac.shapes[ai];
|
|
aj += 1;
|
|
aj %= ash.size();
|
|
sq.push_back(gen_symbol(ai, aj));
|
|
auto co = ash.connections[aj % ash.cycle_length];
|
|
ai = co.sid;
|
|
aj = co.eid;
|
|
aj %= ac.shapes[ai].cycle_length;
|
|
}
|
|
// println(hlog, "finish at: ", cw, " from: ", cw0);
|
|
aj %= sh.cycle_length;
|
|
if(i != ai || j != aj /* || cw != cw0 */) { println(hlog, "bad cycling!"); return; }
|
|
enqueue_rule(sq, {});
|
|
}
|
|
}
|
|
|
|
for(auto r: rules) println(hlog, r);
|
|
}
|
|
|
|
void final_rules() {
|
|
auto& ac = arb::current;
|
|
int N = isize(ac.shapes);
|
|
for(int i=0; i<N; i++) {
|
|
auto& sh = ac.shapes[i];
|
|
int K = sh.size();
|
|
int C = sh.cycle_length;
|
|
for(int k=0; k<K; k++)
|
|
for(int l=0; l<K; l+=C) {
|
|
seq sq, sq1;
|
|
sq.push_back(gen_symbol(i, k));
|
|
auto con = sh.connections[k % sh.cycle_length];
|
|
auto& sh1 = ac.shapes[con.sid];
|
|
con.eid %= sh1.cycle_length;
|
|
sq.push_back(gen_symbol(con.sid, con.eid));
|
|
sq.push_back(gen_symbol(i, 120 + l));
|
|
int k1 = k / sh.cycle_length * sh.cycle_length;
|
|
// if(k1)
|
|
sq1.push_back(gen_symbol(i, 120 + (k1 + l) % K));
|
|
enqueue_rule(sq, sq1);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* the substring of s starting at pos equals needle */
|
|
|
|
bool sub_at(const seq& s, int pos, const seq& needle) {
|
|
for(int i=0; i<isize(needle); i++)
|
|
if(s[i+pos] != needle[i])
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
/* the suffix of s1 of length len, and the prefix of s2 of length len, agree */
|
|
bool suf_pref_agree(const seq& s1, const seq& s2, int len) {
|
|
for(int i=0; i<len; i++)
|
|
if(s1[isize(s1)-len+i] != s2[i])
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
bool is_reducible(const seq& s, const pair<seq, seq>& r, seq& result) {
|
|
int rl = isize(r.first);
|
|
int sl = isize(s);
|
|
for(int i=0; i<=sl-rl; i++)
|
|
if(sub_at(s, i, r.first)) {
|
|
result = sub(s, 0, i);
|
|
catto(result, r.second);
|
|
catto_sub(result, s, i+rl, sl-rl-i);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void find_critical(const pair<seq,seq>& p, const pair<seq,seq>& q) {
|
|
int pl = isize(p.first);
|
|
int ql = isize(q.first);
|
|
for(int i=1; i<pl && i < ql; i++)
|
|
if(suf_pref_agree(p.first, q.first, i)) {
|
|
seq nleft = sub(p.first, 0, pl-i);
|
|
catto(nleft, q.second);
|
|
seq nright = p.second;
|
|
catto_sub(nright, q.first, i, ql-i);
|
|
enqueue_rule(nleft, nright);
|
|
}
|
|
}
|
|
|
|
void handle_rule(const pair<seq, seq>& nr) {
|
|
auto& lh = nr.first;
|
|
auto& rh = nr.second;
|
|
|
|
for(auto& r: rules) {
|
|
seq res;
|
|
if(is_reducible(lh, r, res)) {
|
|
enqueue_rule(res, rh);
|
|
return;
|
|
}
|
|
if(is_reducible(rh, r, res)) {
|
|
enqueue_rule(lh, res);
|
|
return;
|
|
}
|
|
}
|
|
|
|
vector<seq> to_erase;
|
|
|
|
for(auto& r: rules) {
|
|
seq res;
|
|
if(is_reducible(r.first, nr, res)) {
|
|
to_erase.push_back(r.first);
|
|
enqueue_rule(res, r.second);
|
|
}
|
|
if(is_reducible(rh, r, res)) {
|
|
to_erase.push_back(r.first);
|
|
enqueue_rule(r.first, res);
|
|
}
|
|
}
|
|
|
|
for(auto s: to_erase) rules.erase(s);
|
|
|
|
for(auto& r: rules) {
|
|
find_critical(r, nr);
|
|
find_critical(nr, r);
|
|
}
|
|
|
|
rules[nr.first] = nr.second;
|
|
}
|
|
|
|
int kb_result;
|
|
double total_time;
|
|
|
|
int main_loop(int timelimit = 5, int lenlimit = 300) {
|
|
|
|
clock_t start = clock();
|
|
while(!rulequeue.empty()) {
|
|
if(clock() > start + timelimit * CLOCKS_PER_SEC) return 1;
|
|
auto p = rulequeue.front();
|
|
rulequeue.pop();
|
|
if(p.first == p.second) continue;
|
|
if(lexless(p.first, p.second)) swap(p.first, p.second);
|
|
if(rules_checked.count(p)) continue;
|
|
rules_checked.insert(p);
|
|
int len = p.first.size() + p.second.size();
|
|
if(len > max_rule_length) max_rule_length = len;
|
|
if(len > lenlimit) return 2;
|
|
handle_rule(p);
|
|
}
|
|
|
|
println(hlog, "finished in ", (clock() - start) * 1. / CLOCKS_PER_SEC);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void test_knuth_bendix() {
|
|
rules.clear();
|
|
rulequeue = {};
|
|
rules_checked.clear();
|
|
max_rule_length = 0;
|
|
start_game();
|
|
|
|
prepare_rules();
|
|
|
|
clock_t total_start = clock();
|
|
println(hlog, "total_start = ", total_start * 1. / CLOCKS_PER_SEC);
|
|
|
|
kb_result = main_loop(10);
|
|
|
|
println(hlog, "after first phase = ", clock() * 1. / CLOCKS_PER_SEC);
|
|
|
|
if(kb_result == 0) {
|
|
|
|
println(hlog, "intermediate rules:");
|
|
for(auto s: rules) println(hlog, s.first, " => ", s.second);
|
|
|
|
final_rules();
|
|
kb_result = main_loop(50, 999999);
|
|
if(kb_result) kb_result += 2;
|
|
}
|
|
|
|
println(hlog, "after second phase = ", clock() * 1. / CLOCKS_PER_SEC);
|
|
|
|
total_time = (clock() - total_start) * 1. / CLOCKS_PER_SEC;
|
|
}
|
|
|
|
bool is_end_reducible(const seq& s1) {
|
|
for(auto r: rules)
|
|
if(isize(s1) >= isize(r.first) && sub(s1, isize(s1)-isize(r.first), isize(r.first)) == r.first)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
int count_tree_states() {
|
|
set<seq> all_prefixes;
|
|
for(auto r: rules) {
|
|
auto left = r.first;
|
|
for(int i=0; i<isize(left); i++) {
|
|
seq prefix = sub(left, 0, i);
|
|
seq s = prefix;
|
|
s.emplace_back(gen_symbol(left[i].eid, 120));
|
|
if(!is_end_reducible(s))
|
|
all_prefixes.emplace(prefix);
|
|
}
|
|
}
|
|
all_prefixes.erase(seq{});
|
|
for(auto x: all_prefixes)
|
|
println(hlog, x);
|
|
return isize(all_prefixes) + isize(arb::current.shapes);
|
|
}
|
|
|
|
bool forked = false;
|
|
int max_children = 7;
|
|
|
|
void test_all(string setname) {
|
|
disable_floorshapes = true;
|
|
vector<string> filenames;
|
|
std::ifstream is("devmods/rulegen-tests/"+setname+".lst");
|
|
string s;
|
|
while(getline(is, s)) {
|
|
while(s != "" && s[0] == ' ') s = s.substr(1);
|
|
if(s != "" && s[0] != '#') filenames.push_back(s);
|
|
}
|
|
|
|
println(hlog, "CSV;kbres;Tp;kbrules;tree;maxlen;file");
|
|
|
|
int children = 0;
|
|
|
|
sem_t sem;
|
|
if(forked) sem_init(&sem, true, 1);
|
|
fflush(stdout);
|
|
|
|
for(const string& s: filenames) {
|
|
|
|
if(forked) {
|
|
int pid;
|
|
if(children >= max_children) {
|
|
wait(&pid); children--;
|
|
}
|
|
if((pid = fork())) children++;
|
|
else goto doit;
|
|
continue;
|
|
}
|
|
|
|
doit:
|
|
|
|
if(rulegen::set_general(s)) {
|
|
|
|
if(!arb::in()) try {
|
|
arb::convert::convert();
|
|
arb::convert::activate();
|
|
}
|
|
catch(hr_exception& e) {
|
|
println(hlog, "CSV; failed to convert ", s);
|
|
return;
|
|
}
|
|
// println(hlog, "will call test_knuth_bendix");
|
|
test_knuth_bendix();
|
|
// println(hlog, "after test_knuth_bendix");
|
|
if(forked) sem_wait(&sem);
|
|
println(hlog, "CSV;",kb_result,";", total_time, ";", isize(rules), ";", kb_result == 0 ? count_tree_states() : 0, ";", max_rule_length, ";", s);
|
|
if(forked) sem_post(&sem);
|
|
fflush(stdout);
|
|
}
|
|
if(forked) exit(0);
|
|
}
|
|
|
|
while(children) { int pid; wait(&pid); children--; }
|
|
if(forked) sem_destroy(&sem);
|
|
}
|
|
|
|
void kb_merge() {
|
|
vector<string> filenames;
|
|
std::ifstream is("devmods/rulegen-tests/all.lst");
|
|
string s;
|
|
while(getline(is, s)) {
|
|
while(s != "" && s[0] == ' ') s = s.substr(1);
|
|
if(s != "" && s[0] != '#') filenames.push_back(s);
|
|
}
|
|
|
|
map<string, string> cas;
|
|
if(std::ifstream is = std::ifstream("devmods/rulegen-tests/kbmerge.txt")) {;
|
|
while(getline(is, s)) {
|
|
int i = 0, j = 0;
|
|
for(char c: s) { i++; if(c == ';') j = i; }
|
|
string fname = s.substr(j);
|
|
if(cas.count(fname) && cas[fname].substr(0,5) != s.substr(0,5)) {
|
|
// println(hlog, "repetition:\n", cas[fname], "\n", s);
|
|
s = min(s, cas[fname]);
|
|
}
|
|
cas[fname] = s;
|
|
// println(hlog, "assigned to ", fname, ": ", s);
|
|
}
|
|
}
|
|
|
|
println(hlog, "CSV;kbres;Tp;kbrules;tree;maxlen;file");
|
|
|
|
for(auto ss: filenames)
|
|
if(!cas.count(ss))
|
|
println(hlog, "MISSING: ", ss);
|
|
else
|
|
println(hlog, cas[ss]);
|
|
}
|
|
|
|
map<cell*, seq> first_seq_last;
|
|
map<cell*, seq> first_seq;
|
|
map<cell*, int> qty;
|
|
|
|
void view_kb_tree(const shiftmatrix& V, cellwalker cw, seq s) {
|
|
qty[cw.at]++;
|
|
if(cw.at == lmouseover) println(hlog, s, " ; ", cw.spin);
|
|
for(int i=0; i<cw.at->type; i++) {
|
|
seq s1 = s;
|
|
s1.emplace_back(gen_symbol(shvid(cw.at), i));
|
|
if(is_end_reducible(s1)) continue;
|
|
auto cw1 = cw;
|
|
cw1 += i;
|
|
int is = cw1.spin;
|
|
cw1 += wstep;
|
|
if(!gmatrix.count(cw1.at)) continue;
|
|
transmatrix A = currentmap->adj(cw.at, is);
|
|
int eid = shvid(cw1.at);
|
|
int clen = arb::current.shapes[eid].cycle_length;
|
|
cw1 -= cw1.spin % clen;
|
|
// queueline(V * get_corner_position(cw.at, cw.spin, 6), V * A * get_corner_position(cw1.at, cw1.spin, 6), 0xC00000FF);
|
|
|
|
seq s2 = s1;
|
|
s2.emplace_back(gen_symbol(shvid(cw1.at), 120));
|
|
|
|
if(!is_end_reducible(s2)) { // first_seq_last[cw1.at] == s1) {
|
|
if(cw1.at == lmouseover) println(hlog, "is not end_reducible: ", s2);
|
|
vid.linewidth *= 3;
|
|
queueline(V * C0, V * mid(tC0(A), mid(C0, tC0(A))), 0xFFC0C0FF, 3);
|
|
vid.linewidth /= 3;
|
|
}
|
|
|
|
if(!first_seq.count(cw1.at))
|
|
first_seq[cw1.at] = s1;
|
|
else
|
|
first_seq[cw1.at] = min(s1, first_seq[cw1.at]);
|
|
|
|
view_kb_tree(V*A, cw1, s1);
|
|
}
|
|
}
|
|
|
|
void kb_marker() {
|
|
qty.clear();
|
|
first_seq_last = std::move(first_seq);
|
|
first_seq.clear();
|
|
view_kb_tree(gmatrix[cwt.at], cellwalker(cwt.at, 0), {});
|
|
for(auto p: qty)
|
|
queuestr(gmatrix[p.first], .5, its(p.second), 0xFFFFFFFF, 1);
|
|
}
|
|
|
|
void add_kb_view() {
|
|
addHook(hooks_markers, 100, kb_marker);
|
|
}
|
|
|
|
int u = arg::add3("-kb", [] {
|
|
test_knuth_bendix();
|
|
println(hlog, "result: ", kb_result, " rules: ", isize(rules));
|
|
println(hlog, "final rules:");
|
|
for(auto s: rules) println(hlog, s.first, " => ", s.second);
|
|
if(kb_result == 0) println(hlog, "tree states: ", count_tree_states());
|
|
})
|
|
+ arg::add3("-kb-test-all", [] { arg::shift(); test_all(arg::args()); })
|
|
+ arg::add3("-kb-forked", [] { arg::shift(); max_children = arg::argi(); forked = max_children; })
|
|
+ arg::add3("-kb-rev", [] { arg::shift(); reversed = arg::argi(); })
|
|
+ arg::add3("-kb-view", add_kb_view)
|
|
+ arg::add3("-kb-merge", kb_merge);
|
|
|
|
}
|