mirror of
				https://github.com/zenorogue/hyperrogue.git
				synced 2025-10-26 03:17:39 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			462 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			462 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include "../hyper.h"
 | |
| 
 | |
| // an implementation of WFC-like constraint satisfaction problem using polytime dynamic programming
 | |
| // see: https://arxiv.org/abs/2002.09534
 | |
| 
 | |
| // usage: hyper -canvas 0 -dynamic-wfc [constraint] [cutoff] [radius]
 | |
| 
 | |
| // where constraint is one of:
 | |
| // 0: every cell has 2 yellow neighbors
 | |
| // 1: every cell's neighborhood has two consistent regions
 | |
| // 2: every cell's neighborhood has four consistent regions
 | |
| // 3: landscape: every cell has 1 consistent region of next type and 1 region of prev type
 | |
| 
 | |
| // cutoff is cut off from the disk
 | |
| // radius is the radius of the disk to generate
 | |
| 
 | |
| namespace hr {
 | |
| 
 | |
| namespace dynamic_wfc {
 | |
| 
 | |
| bool animated = false;
 | |
| 
 | |
| vector<cell*> generate_pd_list(celllister& cl) {
 | |
|   cell *croot = cl.lst.back();
 | |
|   
 | |
|   cellwalker cw(croot, 0);
 | |
|   while(cl.listed(cw.peek())) cw++;
 | |
|   while(!cl.listed(cw.peek())) cw++;
 | |
|   
 | |
|   vector<cell*> currpath = {};
 | |
|   cellwalker cw1 = cw;
 | |
| 
 | |
|   vector<cell*> result;
 | |
|   
 | |
|   // int steps = 0;
 | |
|   
 | |
|   auto push = [&] (cell *c) {
 | |
|     // println(hlog, "push ", c, " at ", isize(currpath));
 | |
|     currpath.push_back(c);
 | |
|     result.push_back(c);
 | |
|     };
 | |
| 
 | |
|   auto pop = [&] () {
 | |
|     // println(hlog, "pop ", currpath.back(), " at ", isize(currpath)-1);
 | |
|     currpath.pop_back();
 | |
|     };
 | |
|   
 | |
|   auto replace = [&] (int pos, cell *c) {
 | |
|     // println(hlog, "replace ", currpath[pos], " to ", c, " at ", pos, "/", isize(currpath));
 | |
|     currpath[pos] = c;
 | |
|     result.push_back(c);
 | |
|     };
 | |
|   
 | |
|   push(croot);
 | |
|   
 | |
|   while(true) {
 | |
|     cw += wstep;
 | |
|     cw++;
 | |
|     while(!cl.listed(cw.peek())) cw++;
 | |
|     cell *cn = cw.at;
 | |
|     // cn->item = itGold;
 | |
|     // println(hlog, "at ", cn, " [", steps++, "]");
 | |
|     int cd = celldistance(cn, croot) + 1;
 | |
|     if(cd > isize(currpath)) 
 | |
|       push(cn);
 | |
|     else {
 | |
|       if(cd < isize(currpath))
 | |
|         pop();
 | |
|       
 | |
|       if(currpath.back() != cn) {
 | |
|         int moves = 1;
 | |
|     
 | |
|         while(celldistance(currpath[cd-1-moves], cn) > moves) {
 | |
|           moves++;
 | |
|           continue;
 | |
|           }
 | |
|         
 | |
|         while(moves > 0) {
 | |
|           moves--;        
 | |
|           forCellEx(cx, currpath[cd-2-moves])
 | |
|             if(celldistance(cx, cn) == moves)
 | |
|               replace(cd-1-moves, cx);
 | |
|           }
 | |
|         }
 | |
|       }
 | |
| 
 | |
|     /*
 | |
|     cell *last = nullptr;
 | |
|     
 | |
|     for(cell *c: currpath) {
 | |
|       if(last) print(hlog, " ", celldistance(c, last), " ");
 | |
|       last = c;
 | |
|       print(hlog, c);
 | |
|       }
 | |
|     println(hlog); */
 | |
|     
 | |
|     for(int i=1; i<isize(currpath); i++)
 | |
|       if(celldistance(currpath[i-1], currpath[i]) != 1)
 | |
|         println(hlog, "PD error");
 | |
|     
 | |
|     if(cw == cw1) break;
 | |
|     }
 | |
|   
 | |
|   return result;
 | |
|   }
 | |
| 
 | |
| struct ttrans {
 | |
|   int news;
 | |
|   int olds;
 | |
|   float proportion;
 | |
|   char id;
 | |
|   };
 | |
| 
 | |
| vector<ttrans> trans;
 | |
| 
 | |
| vector<cell*> ctf;
 | |
| 
 | |
| vector<cell*> global_list;
 | |
| 
 | |
| int wfctype, wfcrad, cutoff;
 | |
| 
 | |
| void wfc_clear() {
 | |
|   trans.clear();
 | |
|   ctf.clear();
 | |
|   global_list.clear();
 | |
|   }
 | |
| 
 | |
| void wfc_build() {  
 | |
|   int code_at = trans.back().news;
 | |
|   int cpos = isize(ctf);
 | |
|   for(int i=isize(trans)-1; i>=0; i--) {
 | |
|     auto& tri = trans[i];
 | |
|     if(tri.news == code_at) {
 | |
|       // println(hlog, tie(tri.news, tri.olds, tri.proportion, tri.where, tri.id));
 | |
|       if(hrandf() < tri.proportion) {
 | |
|         cell *c = ctf[--cpos];
 | |
|         int id = tri.id;
 | |
|         
 | |
|         switch(wfctype) {
 | |
|           case 0:          
 | |
|             if(id == 0) c->wall = waNone, c->landparam = 0xFF4040;
 | |
|             else c->wall = waNone, c->landparam = 0xFFFF40;
 | |
|             break;
 | |
|           case 1:
 | |
|             if(id == 0) c->wall = waNone, c->landparam = 0x4040FF;
 | |
|             else c->wall = waNone, c->landparam = 0x40FF40;
 | |
|             break;
 | |
|           case 2:
 | |
|             if(id == 0) c->wall = waNone, c->landparam = 0x8080FF;
 | |
|             else c->wall = waNone, c->landparam = 0x202020;
 | |
|             break;
 | |
|           case 3:
 | |
|             if(id == 0) c->wall = waNone, c->landparam = 0xC08080;
 | |
|             else if(id == 1) c->wall = waNone, c->landparam = 0x80C080;
 | |
|             else c->wall = waNone, c->landparam = 0x8080C0;
 | |
|             break;
 | |
|           }
 | |
| 
 | |
|         // println(hlog, code_at, " -> ", tri.olds, " [", tri.where, "=", tri.id, "]");
 | |
|         code_at = tri.olds;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
| void wfc() {
 | |
|   int rad = wfcrad;
 | |
|   vector< vector< vector<cell*> > > new_neighborhoods;
 | |
|   map<cell*, int> lorder;
 | |
|   map<cell*, int> multiplicity;
 | |
|   
 | |
|   set<cell*> consider;
 | |
|   
 | |
|   celllister cl(cwt.at, rad, 999999, nullptr);
 | |
|   for(cell *c: cl.lst) setdist(c, 7, nullptr);
 | |
|   auto l = generate_pd_list(cl);
 | |
|   int ls = isize(l);
 | |
|   for(int i=0; i<ls; i++) lorder[l[i]] = i;
 | |
|   println(hlog, "lorder[0] = ", l[0]);
 | |
| 
 | |
|   for(int i=0; i<ls; i++) {
 | |
|     // int dctr = celldistance(l[i], cwt.at);
 | |
|     int dfirst = celldistance(l[i], l[0]);
 | |
|     // int dtotal = celldistance(l[0], cwt.at);
 | |
|     //if(dtotal == dfirst + dctr && dfirst < cutoff) ;
 | |
|     //else
 | |
|     if(dfirst >= cutoff)
 | |
|       consider.insert(l[i]); 
 | |
|     }
 | |
|   
 | |
|   new_neighborhoods.resize(ls);
 | |
| 
 | |
|   for(cell *c: cl.lst) {
 | |
|     bool ok = true;
 | |
|     forCellEx(c1, c) if(!consider.count(c1)) ok = false;
 | |
|     if(!ok) continue;
 | |
|     vector<cell*> nn = {c};
 | |
|     forCellEx(c1, c) nn.push_back(c1);
 | |
|     for(auto cx: nn) multiplicity[cx]++;
 | |
|     int max_order = -1;
 | |
|     for(auto cx: nn) max_order = max(max_order, lorder[cx]);
 | |
|     
 | |
|     new_neighborhoods[max_order].push_back(nn);
 | |
|     }
 | |
|   
 | |
|   int typecount = 2;
 | |
|   if(wfctype == 3) typecount++;
 | |
|   vector<cell*> inpath;
 | |
|   
 | |
|   typedef unsigned long long code_t;
 | |
|   const int cpo = 2;
 | |
|   const code_t mask = (1<<cpo)-1;
 | |
|   
 | |
|   struct freqdata {
 | |
|     code_t code;
 | |
|     int id;
 | |
|     double prob;
 | |
|     };
 | |
|   
 | |
|   vector<freqdata> freq = { freqdata{0, 0, 1} };
 | |
|   vector<freqdata> nfreq;
 | |
|   
 | |
|   trans.clear();
 | |
|   
 | |
|   int nextcode = 1;
 | |
|   
 | |
|   int all = 2 << (cpo * 8);
 | |
|   
 | |
|   vector<double> mul(all, 0);
 | |
|   
 | |
|   for(int a=0; a<all; a++) {
 | |
|     vector<int> v;
 | |
|     int a0 = a;
 | |
|     while(a0) { v.push_back(a0 & mask); a0 >>= cpo; }
 | |
|     if(isize(v) < 2) continue;
 | |
|     v.pop_back();
 | |
|     int cur = v.back();
 | |
|     v.pop_back();
 | |
|     int changes = 0;
 | |
|     for(int i=1; i<isize(v); i++) if(v[i] == 1 && v[i-1] == 0) changes++;
 | |
|     if(v[0] == 1 && v.back() == 0) changes++;
 | |
|     int nes = 0;
 | |
|     for(int vv: v) nes += vv;
 | |
|     
 | |
|     switch(wfctype) {
 | |
|       case 0:
 | |
|         mul[a] = nes == 2;
 | |
|         break;
 | |
|       case 1:
 | |
|         mul[a] = changes == 1;
 | |
|         break;
 | |
|       case 2:
 | |
|         mul[a] = changes == 2;
 | |
|         break;
 | |
|       case 3: {
 | |
|         int ca=0, cb=0, cc=0, cd=0, ce=0;
 | |
|         int cur1 = (cur+1) % 3;
 | |
|         int cur2 = (cur+2) % 3;
 | |
|         int same = 0;
 | |
|         int in1 = 0;
 | |
|         int in2 = 0;
 | |
|         for(int i=0; i<isize(v); i++) {
 | |
|           auto va = v[i], vb = v[(i+1)%isize(v)];
 | |
|           if(va == cur && vb == cur1) ca++;
 | |
|           if(va == cur && vb == cur2) cb++;
 | |
|           if(va == cur1 && vb == cur) cc++;
 | |
|           if(va == cur2 && vb == cur) cd++;
 | |
|           if(va == cur1 && vb == cur2) ce++;
 | |
|           if(va == cur2 && vb == cur1) ce++;
 | |
|           if(va == cur) same++;
 | |
|           if(va == cur1) in1++;
 | |
|           if(va == cur2) in2++;
 | |
|           }
 | |
|         mul[a] = ca==1 && cb==1 && cc==1 && cd==1 && ce==0; // && in1 >= 2; // && in2 >= 2;
 | |
|         if(mul[a]) println(hlog, v, cur, " => ", mul[a]);
 | |
|         break;
 | |
|         }
 | |
|       }
 | |
|     
 | |
|     // println(hlog, a, ": ", v, " -> ", mul[a]);
 | |
|     }
 | |
|   
 | |
|   /*
 | |
|   mul[0] = 0;
 | |
|   for(int a=0; a<8; a++)
 | |
|     mul[1<<a] = 1;
 | |
|   */
 | |
| 
 | |
|   long long tfreq = 0;
 | |
| 
 | |
|   if(animated)
 | |
|   for(int j=0; j<ls; j++)
 | |
|       for(auto& nns: new_neighborhoods[j]) for(auto c: nns) c->landparam = 0x202020;
 | |
|   
 | |
|   for(int i=0; i<ls; i++) if(consider.count(l[i])) {
 | |
|     tfreq += isize(freq);
 | |
|     string s = lalign(0, i, "/", ls, ": freqs = ", isize(freq), " inpath ", isize(inpath));
 | |
|     println(hlog, s);
 | |
| 
 | |
|     if(animated) {
 | |
|       for(int j=0; j<i; j++)
 | |
|         for(auto& nns: new_neighborhoods[j]) for(auto c: nns) c->landparam = multiplicity[c] ? 0xFFFF00 : 0x00FF00;
 | |
|       for(auto& nns: new_neighborhoods[i]) for(auto c: nns) c->landparam = 0xFFFFFF;
 | |
| 
 | |
|       history::progress(s);
 | |
|       }
 | |
| 
 | |
|     inpath.push_back(l[i]);
 | |
|   
 | |
|     int sh = cpo * (isize(inpath)-1);
 | |
| 
 | |
|     for(auto& p: freq) if(p.prob) for(char c=0; c<typecount; c++) {
 | |
|       nfreq.emplace_back(p);
 | |
|       nfreq.back().code |= (code_t(c) << sh);
 | |
|       }
 | |
|     freq = std::move(nfreq);
 | |
|     nfreq = {};
 | |
|     // println(hlog, "inpath = ", inpath);
 | |
|     // println(hlog, "freqs = ", isize(freq));
 | |
|     if(isize(freq) > 500000000) {
 | |
|       println(hlog, "freq overflow");
 | |
|       exit(3);
 | |
|       }
 | |
|     fflush(stdout);
 | |
|     
 | |
|     global_list = cl.lst;
 | |
| 
 | |
|     for(auto& nns: new_neighborhoods[i]) {
 | |
|       // println(hlog, "acknowledge ", nns);
 | |
|       fflush(stdout);
 | |
|             
 | |
|       vector<int> sids;
 | |
|       for(auto cz: nns) {
 | |
|         for(int id=0; id<isize(inpath); id++) if(inpath[id] == cz) sids.push_back(id);
 | |
|         }
 | |
|       
 | |
|       for(auto& p: freq) {
 | |
|         unsigned code = 1;
 | |
|         for(int v: sids) code = (code << cpo) | ((p.code >> (cpo*v)) & mask);
 | |
|         p.prob *= mul[code];
 | |
|         }
 | |
| 
 | |
|       for(auto cz: nns) {
 | |
|         multiplicity[cz]--;
 | |
|         if(multiplicity[cz] == 0) {
 | |
|           ctf.push_back(cz);
 | |
|           // println(hlog, "remove ", cz, " (#", lorder[cz], ") from ", inpath);
 | |
|           fflush(stdout);
 | |
|           int is = isize(inpath)-1;
 | |
|           int id = 0;
 | |
|           while(id <= is && inpath[id] != cz) id++;
 | |
|           if(id > is) { 
 | |
|             println(hlog, "error");
 | |
|             exit(4);
 | |
|             }
 | |
|           inpath[id] = inpath[is];
 | |
|           inpath.resize(is);
 | |
|           for(auto& p: freq) if(p.prob) {
 | |
|             nfreq.push_back(p);
 | |
|             int tid = (p.code >> (cpo*id)) & mask;
 | |
|             code_t& s = nfreq.back().code;
 | |
|             s &=~ (mask << (cpo*id));
 | |
|             if(id < is) s |= ((s >> (cpo*is)) & mask) << (cpo*id);
 | |
|             s &=~ (mask << (cpo*is));
 | |
|             // s.resize(is);
 | |
|             // if(id < is) s[id] = p.code[is];
 | |
|             nfreq.back().id = (nfreq.back().id << cpo) | tid;
 | |
|             }
 | |
|           
 | |
|           freq.clear();
 | |
|           sort(nfreq.begin(), nfreq.end(), [] (const freqdata& a, const freqdata& b) { return a.code < b.code; });
 | |
|           for(int i=0; i<isize(nfreq); i++) {
 | |
|             auto &next = nfreq[i];
 | |
|             if(i == 0 || nfreq[i].code != nfreq[i-1].code) {
 | |
|               freq.push_back(nfreq[i]);
 | |
|               freq.back().id = nextcode++;
 | |
|               freq.back().prob = 0;
 | |
|               }
 | |
|             
 | |
|             auto& group = freq.back();
 | |
|             
 | |
|             group.prob += next.prob;
 | |
| 
 | |
|             ttrans nt;
 | |
|             nt.news = group.id;
 | |
|             nt.olds = next.id >> cpo;
 | |
|             nt.proportion = next.prob / group.prob;
 | |
|             nt.id = next.id & mask;
 | |
|             trans.push_back(nt);
 | |
|             }
 | |
|           
 | |
|           nfreq.clear();            
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   
 | |
|   if(isize(freq)) println(hlog, "last freq = ", freq[0].prob);
 | |
|   println(hlog, "freq size = ", isize(freq));
 | |
|   println(hlog, "tfreq = ", format("%lld", tfreq));
 | |
|   println(hlog, "trans size = ", isize(trans));
 | |
|   println(hlog, "next code = ", nextcode);
 | |
|   clearMessages();
 | |
|   
 | |
|   wfc_build();
 | |
|   }
 | |
| 
 | |
| bool wfc_handleKey(int sym, int uni) {  
 | |
|   if(sym == '5') {
 | |
|     wfc_build();
 | |
|     return true;
 | |
|     }
 | |
|   return false;
 | |
|   }
 | |
| 
 | |
| void hwrite(hstream& f, const ttrans& t) {
 | |
|   hwrite(f, t.news);
 | |
|   hwrite(f, t.olds);
 | |
|   hwrite(f, t.proportion);
 | |
|   hwrite(f, t.id);
 | |
|   }
 | |
| 
 | |
| void save(string s) {
 | |
|   fhstream f(s, "wb");
 | |
|   vector<int> indices;
 | |
|   for(auto cf: ctf)
 | |
|     for(int i=0; i<isize(global_list); i++)
 | |
|       if(global_list[i] == cf) indices.push_back(i);
 | |
|   
 | |
|   hwrite(f, wfctype);
 | |
|   hwrite(f, cutoff);
 | |
|   hwrite(f, wfcrad);
 | |
|   hwrite(f, indices);
 | |
|   hwrite(f, trans);
 | |
|   }
 | |
| 
 | |
| auto wfc_hook = 
 | |
|   addHook(hooks_args, 100, [] {
 | |
|     using namespace arg;
 | |
| 
 | |
|     if(0) ;
 | |
|     else if(argis("-dynamic-wfc")) {
 | |
|       start_game();
 | |
|       shift(); wfctype = argi();
 | |
|       shift(); cutoff = argi();
 | |
|       shift(); wfcrad = argi();
 | |
|       wfc();
 | |
|       }
 | |
|     else if(argis("-dynamic-wfc-save")) {
 | |
|       shift(); save(args());
 | |
|       }
 | |
|     else if(argis("-wfc-regen-key")) {
 | |
|       rogueviz::rv_hook(hooks_handleKey, 100, wfc_handleKey);
 | |
|       }
 | |
|       
 | |
|     else return 1;
 | |
|     return 0;
 | |
|     });
 | |
| 
 | |
| }
 | |
| }
 | 
