KGRKJGETMRETU895U-589TY5MIGM5JGB5SDFESFREWTGR54TY
Server : Apache/2.4.62
System : FreeBSD fbsdweb2.web.rcn.net 14.1-RELEASE FreeBSD 14.1-RELEASE releng/14.1-n267679-10e31f0946d8 GENERIC amd64
User : www ( 80)
PHP Version : 8.3.8
Disable Function : NONE
Directory :  /domains/gohover/cell/paprica/uploadtestfolder/ios-test/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : /domains/gohover/cell/paprica/uploadtestfolder/ios-test/ios-test-tiling-code-most-current.js

var rulesmenu_data = [ 
                 // Note that each entry except the last one must be followed by a comma.
            ["B3/S23", "Rule Menu"],
            ["PromptForRule","Enter Your Own Rule"],
            ["B3/S23", "--Lifelike rules--"],
            ["B1/S"],
            ["B2/S"],
            ["B3/S23", "B3/S23 (Life)"],
            ["B36/S23","B36/S23 (HighLife)"],
            ["B37/S23", "B37/S23 (DryLife)"],
            ["B3/S23", "--Generations rules--"], 
            ["B2/S/C3","B2/S/C3  (B's Brain)"],
            ["B2/S/C4","B2/S/C4 (PenroseP3 & AB)"],  // Sedate Brain, Valium Brain, Calm Brian
            ["B2/S345/C4", "B2/S345/C4 (StarWars)"],
            ["B23/S/C4","B23/S/C4 (PenroseP1)"],     // also:  ["B23/S459/C4", "B2/S459/C4 (AB Gun)"],
            ["B3/S23", "--Von Neumann rules--"], 
            ["B1/S1V"], 
            ["B3/S23", "--Bx2 rules--"], 
            ["B1x2/S23V"],
            ["B3/S23", "-Nontotalistic--"], 
            ["B2e/s"],
            ["B2a/S"],

            ["B3/S23", "--Misc Rules---"], 
            ["VN 3 State Fun", "VN_head Moore_tail"],

            ["VN 3 State Fun"],
            ["my TriGou"],   // beware using the name "Goucher" for other rules - consider what the parser is looking for.
            ["Goucher Gliders"]
                 // Note that the last entry must NOT be followed by a comma! 
                 ];  

var lifepatterns = { 
        "Test Blob": {rule: "all", 
                        patternstring: "#C 1 #S 1 #C 2 #S 1 #C 3 #S 1 #C 4 #S 1 #C 5 #S 1 #C 6 #S 1 #C 7 #S 1"
                       },
         "Test Blob2": {rule: "all", 
                        patternstring: "#C 8 #S 1 #C 9 #S 1 #C 10 #S 1 #C 11 #S 1 #C 12 #S 1 #C 13 #S 1 #C 14 #S 1"
                       }
                     };

 



function put_rules_in_menu() {
    document.getElementById("rulesmenu").options.length = 0;
    var len = rulesmenu_data.length;
    for (var i = 0; i<len; i++) {
        if (rulesmenu_data[i][1] != undefined) {
          // if there is an englishname option, use it
          addOption(document.getElementById("rulesmenu"),rulesmenu_data[i][1],rulesmenu_data[i][0] )
       }
      else { // there is no englishname option
           addOption(document.getElementById("rulesmenu"),rulesmenu_data[i][0],rulesmenu_data[i][0] )
       }
   } 
}

function put_patterns_in_menu() {
    for (var p in lifepatterns) {
         addOption(document.getElementById("patternmenu"),p,p )    
    }
}



function makebutton(b_id, b_style, b_value,  b_div, b_onclick ) {
 
    //Create an input type dynamically.
    var element = document.createElement("input");
 
    //Assign different attributes to the element.
    element.setAttribute("type", "button");
    element.setAttribute("id", b_id);
    element.setAttribute("style", b_style);
    element.setAttribute("value", b_value);

   // Is onclick an attribute?  If not, then don't use setAttribute
   // element.setAttribute("onclick", b_onclick); // this worked with //makebutton( "gobutton" , "width: 40px" , " Go " ,  "doTimer()")
   // but it might be wrong, might not work with all browsers(?), etc.

    document.getElementById(b_div).appendChild(element);
    document.getElementById(b_id).onclick = b_onclick
}




function addOption(selectbox,text,value ) {
	var optn = document.createElement("OPTION");
	optn.text = text;
	optn.value = value;
	selectbox.options.add(optn);
};

function pick_draw_action() {
   var themenu = document.getElementById("drawmenu")
   choice =  themenu.options[ themenu.selectedIndex].value
   switch(choice) {
       case "Randomize":   make_random_field(); break;
       case "Clear":       clear_field(); break;
   }
   themenu.selectedIndex = 0;
} 
    

   
function pick_pattern_action() {
   var themenu = document.getElementById("patternmenu")
   choice =  themenu.options[ themenu.selectedIndex].value
   load_pattern(choice)
   if (lifepatterns[choice].rule != "all"  )  {set_rule_from_outside(lifepatterns[choice].rule)} else {after_changing_rule()};
   themenu.selectedIndex = 0;
}

function change_speed_step() {
   var themenu = document.getElementById("speed_step_menu")
   speedstep = themenu.options[ themenu.selectedIndex].value
};


function choose_brush_action() {
   var themenu = document.getElementById("brushmenu")
   choice =  themenu.options[ themenu.selectedIndex].value
   switch (choice) {
       case "Draw Mode":
         mouse_status = "draw"
         themenu.selectedIndex = draw_mode_index;
         document.getElementById("brushradio").checked = true
         colored_pencil_choice = 1;
         break;


       case "--Draw Actions--":
         mouse_status = "draw"
         themenu.selectedIndex = draw_mode_index;
         document.getElementById("brushradio").checked = true
         colored_pencil_choice = 1;
         break;

       case "Select Mode":
         mouse_status = "select"
         themenu.selectedIndex = select_mode_index;
         document.getElementById("selectradio").checked = true
         break;

       case "--Select Actions--":
         mouse_status = "select"
         themenu.selectedIndex = select_mode_index;
         document.getElementById("selectradio").checked = true
         break;

       case "Random Fill": 
         make_random_field();
      //   themenu.selectedIndex = select_mode_index;
      //   document.getElementById("selectradio").checked = true

         if (mouse_status == "select") {
                 themenu.selectedIndex = select_mode_index;
                 document.getElementById("selectradio").checked = true
         }

         if (mouse_status == "draw") {
                 themenu.selectedIndex = draw_mode_index;
                 document.getElementById("brushradio").checked = true
         }

         break;

       case "Fill Selection":
         fill_selection(colored_pencil_choice);
         themenu.selectedIndex = select_mode_index;
         document.getElementById("selectradio").checked = true
         mouse_status = "select"  

         break;


       case "Clear Selection": 
         mouse_status = "select"  // delete this line if "Select Mode" must be selected first
         fill_selection(0);
         themenu.selectedIndex = select_mode_index;
         document.getElementById("selectradio").checked = true
         break;

       case "Clear Outside": 
         mouse_status = "select"  // delete this line if "Select Mode" must be selected first
         clear_outside();
         themenu.selectedIndex = select_mode_index;
         document.getElementById("selectradio").checked = true
         break;

       case "Clear All": 
         clear_field();


         if (mouse_status == "select") {
                 themenu.selectedIndex = select_mode_index;
                 document.getElementById("selectradio").checked = true
         }

         if (mouse_status == "draw") {
                 themenu.selectedIndex = draw_mode_index;
                 document.getElementById("brushradio").checked = true
         }

         break;

       case "Mesh Check": 
 //        mouse_status = "select"  // delete this line if "Select Mode" must be selected first
         mesh_check();
 //        themenu.selectedIndex = select_mode_index;
//         document.getElementById("selectradio").checked = true

         break;


       case "Mesh Info": 
//         mouse_status = "select"  // delete this line if "Select Mode" must be selected first
         mesh_info();
//         themenu.selectedIndex = select_mode_index;
//         document.getElementById("selectradio").checked = true

         break;

       default:   
         colored_pencil_choice = parseInt(choice)
         mouse_status = "draw"
         themenu.selectedIndex = draw_mode_index;
         document.getElementById("brushradio").checked = true

     }
                   
}

function choose_radio_draw() {
    document.getElementById("brushmenu").selectedIndex = draw_mode_index;
    mouse_status = "draw"
    colored_pencil_choice = 1;
}

function choose_radio_select() {
    document.getElementById("brushmenu").selectedIndex = select_mode_index;
    mouse_status = "select"
}

function makesite() {
    var element = document.createElement("div");
    element.setAttribute("id", "buttonsdiv")
    //element.setAttribute("style", "-webkit-user-select: none; -moz-user-select: none; -ms-user-select: none;")   // commented out for ios 
    // this also might work: -webkit-tap-highlight-color:transparent;
    document.getElementById("MainBody").appendChild(element);


    makebutton( "gobutton" , "width: 40px" , " Go " , "buttonsdiv",    doTimer)
    makebutton("stepbutton", "",             "Step", "buttonsdiv", stepCount)
   






// Radio Button Section
    element = document.createElement("input");
    element.setAttribute("type", "radio");
    element.setAttribute("name", "radiogroup1");
    //   element.setAttribute("style", "width: 50px; text-align:right");
    element.setAttribute("id", "brushradio");
    element.setAttribute("value", "pickeddraw");
    element.checked = true;
    // element.innerHTML = "Draw";
    element.onclick = choose_radio_draw
    document.getElementById("buttonsdiv").appendChild(element);

    element = document.createElement("label");
    element.setAttribute("for", "brushradio");
    element.innerHTML = "Draw";
    document.getElementById("buttonsdiv").appendChild(element);

    element = document.createElement("input");
    element.setAttribute("type", "radio");
    element.setAttribute("name", "radiogroup1");
    //  element.setAttribute("style", "width: 50px; text-align:right");
    element.setAttribute("id", "selectradio");
    element.setAttribute("value", "pickedselect");
   // element.innerHTML = "Select";
    element.onclick = choose_radio_select
     document.getElementById("buttonsdiv").appendChild(element);

    element = document.createElement("label");
    element.setAttribute("for", "selectradio");
    element.innerHTML = "Select";
    document.getElementById("buttonsdiv").appendChild(element);

 // End of radio button section

    

   // element = document.createElement("select");
   // document.getElementById("buttonsdiv").appendChild(element);
   // element.setAttribute("id", "drawmenu");
   // element.onchange=pick_draw_action
   // addOption(element, "Draw", "Draw")
   // addOption(element, "Spraypaint", "Spraypaint")
   // addOption(element, "(R)andomize", "Randomize")
   // addOption(element, "(C)lear", "Clear")


    element = document.createElement("select");
    document.getElementById("buttonsdiv").appendChild(element);
    element.setAttribute("id", "brushmenu");
    element.onchange=choose_brush_action
    addOption(element, "Mouse Menu", "1")
    addOption(element, "Draw Mode", "Draw Mode")
   // addOption(element, "--Draw Actions--", "--Draw Actions--")
    addOption(element, "--Color 1", "1")
    addOption(element, "--Color 2", "2")
    addOption(element, "--Color 3", "3")
    addOption(element, "--Color 4", "4")
    addOption(element, "------------", "--Draw Actions--")
    addOption(element, "Select Mode", "Select Mode")
   // addOption(element, "--Select Actions--", "--Select Actions--")
    addOption(element, "--Select", "Select Mode")
    addOption(element, "--Random Fill", "Random Fill")
    addOption(element, "--Fill Selection", "Fill Selection")
    addOption(element, "--Clear Selection", "Clear Selection")
    addOption(element, "--Clear All", "Clear All")
    addOption(element, "             ", "--Select Actions--")
    addOption(element, "--Clear Outside", "Clear Outside")
    addOption(element, "             ", "--Select Actions--")
    addOption(element, "Export Selection", "1")
    addOption(element, "Mesh Info", "Mesh Info")
    addOption(element, "Mesh ShowHoles", "Mesh Check")

    draw_mode_index = 1   // change this when the above menu changes
    select_mode_index = 7 // change this when the above menu changes

    element = document.createElement("select");
    document.getElementById("buttonsdiv").appendChild(element);
    element.setAttribute("id", "rulesmenu");
    element.onchange=change_rule
    put_rules_in_menu()

    element = document.createElement("select");
    document.getElementById("buttonsdiv").appendChild(element);
    element.setAttribute("id", "patternmenu");
    element.onchange=pick_pattern_action
    addOption(element, "Pattern Menu", "state 1 via mouse")

    makebutton("zoominbutton", "",           "Zoom In", "buttonsdiv",increase_magnification)
    makebutton("zommoutbutton", "",          "Zoom Out","buttonsdiv", decrease_magnification)

   // makebutton("exportmeshbutton", "",       "Export mesh to new tab","buttonsdiv", print_obj)

    element = document.createElement("select");
    document.getElementById("buttonsdiv").appendChild(element);
    element.setAttribute("id", "speed_step_menu");
    element.onchange=change_speed_step
    addOption(element, "1 Gen/Step", 1)
    addOption(element, "2 Gens/Step", 2)
    addOption(element, "3 Gens/Step", 3)
    addOption(element, "4 Gens/Step", 4)
    addOption(element, "5 Gens/Step", 5)
    addOption(element, "20 Gen/Step", 20)
    addOption(element, "21 Gen/Step", 21)
    addOption(element, "98 Gen/Step", 98)
    addOption(element, "99 Gen/Step", 99)


   makebutton("randombutton", "width: 40px" ,          "Test", "buttonsdiv",call_make_random_field_with_top); 
   makebutton("clearbutton", "",            "Clear", "buttonsdiv",clear_field)


    element = document.createElement("div");
    element.setAttribute("id", "canvasdiv")
    element.setAttribute("style", "position: relative; ")

    document.getElementById("MainBody").appendChild(element);



    element = document.createElement("canvas");

    element.setAttribute("id", "pjs")
  //  element.setAttribute("style", "border:1px solid #c3c3c3; position: absolute;  -webkit-user-select: none;  ")  // commented out for ios
  // element.setAttribute("style", "border:1px solid #c3c3c3; position: absolute;") // replacement for ios

    document.getElementById("canvasdiv").appendChild(element);
    ctx = document.getElementById('pjs').getContext('2d');
    ctx.canvas.width  = the_dimension   //  until these become variables:  if this value changes, change mousemove
    ctx.canvas.height = the_dimension

    element = document.createElement("canvas");
    element.setAttribute("id", "layer_Grid")
    element.setAttribute("style", "position: absolute; ")
    document.getElementById("canvasdiv").appendChild(element);
    ctxGrid = document.getElementById('layer_Grid').getContext('2d');
    ctxGrid.canvas.width  = the_dimension   //  until these become variables:  if this value changes, change mousemove
    ctxGrid.canvas.height = the_dimension


    element = document.createElement("canvas");
    element.setAttribute("id", "layer2")
    element.setAttribute("style", "position: absolute; ")
    document.getElementById("canvasdiv").appendChild(element);
    ctx2 = document.getElementById('layer2').getContext('2d');
    ctx2.canvas.width  = the_dimension   //  until these become variables:  if this value changes, change mousemove
    ctx2.canvas.height = the_dimension

 //  ctx2.fillStyle =  "rgba(0,0,0,0)"
//   ctx2.fillRect(0,0, the_dimension, the_dimension);

     greenish_select_color = "rgba(105, 135, 76, 0.4)" // "#69874C"  
   // ctx2.fillStyle = greenish_select_color;
  // ctx2.fillRect(200,200, 100,100);

/* commenting out for ios
    document.getElementById("canvasdiv").onmousemove = cnvs_mousemove;
    document.getElementById("canvasdiv").onmouseup = cnvs_mouseup;
    document.getElementById("canvasdiv").onmousedown = cnvs_mousedown;



 */

   //  document.getElementById("MainBody").onkeydown = interpret_keys; // commented out for ios
     var info_div_position = the_dimension + 60

    var element = document.createElement("div");
    element.setAttribute("id", "infodiv")
    element.setAttribute("style", "position: absolute; top:" + info_div_position +";width:980;font-size: 80%;background-color:#b0c4de")   // Width tester!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    document.getElementById("MainBody").appendChild(element);
    var element = document.createElement("textarea");
    element.setAttribute("id", "infodiv_textarea")
    document.getElementById("infodiv").appendChild(element);
    element.innerHTML = "Rule = B3/S23"

    

    

    

}


var ctx;

var maxLevel = 4;  
var sigdigits = 6

var timer_is_on=0; // this has nothing to do with actually timing anything - this is just "timer code" reuse.
var delay = 0;
var t; // for timer code
var generation = 0;

var changedcells = [];
var changecount = 0;
var initialize_flag = false

var the_dimension = 400 // change for ios

/*
will be useful

http://forum.processing.org/topic/filled-polygons-are-significantly-slowing-down-performance

http://go.yuri.at/some-notes-on-difficult-to-debug-processing-js-errors/

http://colorschemedesigner.com/#

VTU format
http://www.earthmodels.org/software/vtk-and-paraview/vtk-file-formats

Zip format
http://stackoverflow.com/questions/2095697/unzip-files-using-javascript

hit test -- point in polygon
wow:  http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html#Explanation
and also http://stackoverflow.com/questions/217578/point-in-polygon-aka-hit-test


http://www.lemoda.net/canvas/scribbler/



Dynamically add buttons etc, so that the javascript file can do the work instead of the html wrapper
http://viralpatel.net/blogs/dynamic-add-textbox-input-button-radio-element-html-javascript/

http://stackoverflow.com/questions/5490996/load-scripts-after-page-has-loaded
http://jsfiddle.net/Ea4kc/
http://stackoverflow.com/questions/8467822/html5-canvas-calculate-the-mouseposition-after-zooming-and-translating
http://stackoverflow.com/questions/9300590/html5-coordinates-of-a-scaled-canvas-element
http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas

http://www.w3schools.com/tags/canvas_scale.asp
http://www.w3schools.com/svg/svg_path.asp


http://processingjs.org/learning/
http://processingjs.org/articles/PomaxGuide.html  >=== entering data into your processing script via xml and such. could be generally useful for javascript

http://www.logarithmic.net/ghost.xhtml#
http://stackoverflow.com/questions/9458239/svg-paths-to-canvas-paths

http://geometricolor.wordpress.com/page/15/
http://texgraph.tuxfamily.org/pavages/aperiodiques.html  <--- awesome version of the tiling encyclopedia

*/




function roundNumber(rnum, rlength) { // Arguments: number to round, number of decimal places
   return Math.round(rnum*Math.pow(10,rlength))/Math.pow(10,rlength);
}



function cellvertex (index, x, y, partof) {
 this.index = index;
 this.x = x;
 this.y = y;
 this.partof = partof;
}

function cell(index, coords, fnn, neighbors, VNneighbors, state, placeholder_state, message, type) {
  this.index  = index;   // not needed if we have coords?
  this.coords = coords; // not needed if we have index?
  this.fnn = fnn; // an array of vertex indices
  this.neighbors = neighbors; 
  this.VNneighbors = VNneighbors;
  this.state = state;
  this.placeholder_state = placeholder_state;
  this.message = message;
  this.type  = type; // it would be nice to say rhomb or square - dimensions can be measured from coords if not from initial shape generation stage.
}
  
var settypecell =  new cell(0, [],[], [],[], 0,0,  "message", "shape")
var settypecellvertex = new cellvertex(0, 0, 0, [])

var vertexArray = [] 
var v_index = 1;
vertexArray[0] = settypecellvertex  // This shouldn't be used, but prevents errors if v_index goes to zero instead of starting/stopping at 1.

var allcells = [] 
var aci = 0;  // aci stands for All Cells Index

var x_to_verts = []
var y_to_verts = []


// this is for mesh info om=n
function mesh_info() {
  countarray = []
  var n = 0
  for (var i = aci; i--;) {
     n = allcells[i].neighbors.length
     var border = "no"
     for (var k = n; k--;) {  // go through the primary cell's neighbors -- if the neighbor is a border cell, the primary cell might secretly be one too, since border checks only work for neighbors which share an edge with the border, and the primary might share only a vertice with the border. 
        var a_neighbor = allcells[i].neighbors[k];
        if( a_neighbor.VNneighbors.length != a_neighbor.fnn.length) {border = "yes"}
      }
      


     if ((border == "no")  && (allcells[i].VNneighbors.length == allcells[i].fnn.length)) // This is the check from mesh_check below 
                                                                   // to see if the primary cell is surrounded by neighbors 
                                                                   // as opposed to being on the border or next to a hole.
        {
              countarray[n] = 1
         }
  }
  var answer = "Total number of cells = " + aci + ".  Cells which are not on the border will have one of the following number of neighboring cells: "
  for (var j = 0; j < countarray.length; j++) {
      if (countarray[j] == 1) { answer = answer + j + " "}
   }
   alert(answer)
}
      

function mesh_check() {
   for (var i = aci; i--;) {
      if (allcells[i].VNneighbors.length != allcells[i].fnn.length) {allcells[i].state = 1}
   }
   drawboard()
}


// find_angle would be useful for characterizing tiles with the same number of sides, such as thick rhombs from thin rhombs.
//  See also parse_OBJ_entries regarding distinguishing triangles from squares from pentagons, etc.
// How to find the angle, given  lines  from point p0 to point C and from point C to ponit P1.
// from http://stackoverflow.com/questions/1211212/how-to-calculate-an-angle-from-three-points

function find_angle(p0,p1,c) {
    var p0c = Math.sqrt(Math.pow(c.x-p0.x,2)+
                        Math.pow(c.y-p0.y,2)); // p0->c (b)   
    var p1c = Math.sqrt(Math.pow(c.x-p1.x,2)+
                        Math.pow(c.y-p1.y,2)); // p1->c (a)
    var p0p1 = Math.sqrt(Math.pow(p1.x-p0.x,2)+
                         Math.pow(p1.y-p0.y,2)); // p0->p1 (c)
    return Math.acos((p1c*p1c+p0c*p0c-p0p1*p0p1)/(2*p1c*p0c));
}

function color_by_neighbor_count(arr) {
  clear_field ()
   for (var i = aci; i--;) {
      var n = allcells[i].neighbors.length    
        for (var j = 0; j < arr.length; j++) {
           if (arr[j] == n) {allcells[i].state = j+1}
        }
    }
   drawboard()
}
// instead of inner for loop, you could use  // if (contains(n, arr))  {allcells[i].state = 1}


function get_nonbackground_cells() {
  var nonbackgroundcells = []
   for (var i = aci; i--;) {
      if (allcells[i].state != 0) {nonbackgroundcells.push(allcells[i])}    
   }
  return nonbackgroundcells
}

// redundant with above, except for how it treates states > 1
function whoisalive() {
     var result = [];
   for (var i = aci; i--;) {
      if (allcells[i].state == 1) {result.push(allcells[i])}

   }
  return result
}



function contains(value, arr) {
    var i = arr.length;
    while (i--) {
        if (arr[i] == value) return true;
    }
    return false;
}
      

/*
This function is currently unused because Moore neighbors are now pre-calculated
function Moore_neighbors_p (alphafnn, betafnn) {
   for (var i = alphafnn.length; i--;) {
      if (contains(alphafnn[i], betafnn)) {return true};
   } 
   return false;
}

*/


/*  this function is currently unused because VN neighbors are now pre-calculated
function VN_neighbors_p (alphafnn, betafnn) {
   var sharedvertices = 0;
   for (var i = alphafnn.length; i--;) {
      if (contains(alphafnn[i], betafnn)) {sharedvertices +=  1};
   } 
    return (sharedvertices > 1)   //if (sharedvertices >= 2) {return true} else {return false}
}
*/


      
function c_contains(value, arr) {
  // this is a version of contains where "value" is a cell index but "arr" is an array of cells (rather than an array of cell indices)
    var i = arr.length;
    while (i--) {
        if (arr[i].index == value) return true;
    }
    return false;
}



function initialize_neighbors() {  
   for (var i = aci; i--;) {
     allcells[i].neighbors = []
     allcells[i].VNneighbors = []
     for (var j = allcells[i].fnn.length; j--;) {
        for (var k = vertexArray[allcells[i].fnn[j]].partof.length; k--;) {
           var a_cell_index = vertexArray[allcells[i].fnn[j]].partof[k]
           if (a_cell_index != i) { 
               if (c_contains(a_cell_index, allcells[i].neighbors) == false) {    
                  allcells[i].neighbors.push(allcells[a_cell_index])
               }
               else if (c_contains(a_cell_index, allcells[i].VNneighbors) == false) { 
                   allcells[i].VNneighbors.push(allcells[a_cell_index])
              }

           }
        }
     }
   }
}

/*  Experimental



 */   
       


function one_alive_state_neighbor_count(c){
  var result = 0;
  for (var i =  c.neighbors.length; i--;) {
     if (c.neighbors[i].state == 1) {result += 1}   
  }
  return result
}

function state_neighbor_count(c, statenum){
  var result = 0;
  for (var i =  c.neighbors.length; i--;) {
     if (c.neighbors[i].state == statenum) {result += 1}
  }
  return result
}


function state_VNneighbor_count(c, statenum){
  var result = 0;
  for (var i =  c.VNneighbors.length; i--;) {
     if (c.VNneighbors[i].state == statenum) {result += 1}
  }
  return result
}

function state_Corner_neighbor_count(c, statenum){
  var result = 0;
  for (var i =  c.neighbors.length; i--;) {
     if (c.neighbors[i].state == statenum && !(contains(c.neighbors[i], c.VNneighbors))) {result += 1}
  }
  return result
}




// The following three functions are for 2a, 2e, and 2i   
//   Three different ways of defining when a cell has two neigbors which are adjacent to each other

function adjacent_two_VNneighbors(c) {
  var first = false;
  var second = false;
  for (var i =  c.neighbors.length; i--;) {
     if (c.neighbors[i].state == 1 && first == false)  {first = c.neighbors[i]}
     else if (c.neighbors[i].state == 1 && second == false)  {second = c.neighbors[i]}
  }
  return contains(first, second.VNneighbors)  
                                               // can also try Moore but not VN - which would be every other neighbor for some tilings
} 

function adjacent_two_Moore_neighbors(c) {
  var first = false;
  var second = false;
  for (var i =  c.neighbors.length; i--;) {
     if (c.neighbors[i].state == 1 && first == false)  {first = c.neighbors[i]}
     else if (c.neighbors[i].state == 1 && second == false)  {second = c.neighbors[i]}
  }
  return contains(first, second.neighbors)  
                                               // can also try Moore but not VN - which would be every other neighbor for some tilings
}    

function adjacent_two_Corner_neighbors(c) {
  var first = false;
  var second = false;
  for (var i =  c.neighbors.length; i--;) {
     if (c.neighbors[i].state == 1 && first == false)  {first = c.neighbors[i]}
     else if (c.neighbors[i].state == 1 && second == false)  {second = c.neighbors[i]}
  }
  return  (  !( contains(first, second.VNneighbors) ) &&  contains(first, second.neighbors)  )  
           // The two neighbors are NOT VN neighbors, but are still neighbers => They are corner neighbors
} 



// B013469/S02 - rule with nice oscillators


       // B013469/S02 - rule with nice oscillators



function ca_rule3(c) {
   switch(rulefamily) {

      case 1:     // Life-like
         n = one_alive_state_neighbor_count(c);
         if (c.state) { return survivalrule[n] } else { return birthrule[n] };     // define cstate and then say if (cstate == 0){ return birthrule[n] } else if (cstate == 1) {  return survivalrule[n] }
         break;

      case 2:   // Generations
         n = one_alive_state_neighbor_count(c);
         cstate = c.state;
         if (cstate == 0){ return birthrule[n] };
         if (cstate == 1) { 
            if ( survivalrule[n] == 1) {return 1} else {return 2} 
         };
         if (cstate < (n_states - 1)) {return (cstate + 1)} else {return 0};
         break;

      case 3:  // VN_lifelike and VN_Generations combined 
         n = state_VNneighbor_count(c, 1);
         cstate = c.state;
         if (cstate == 0){ return birthrule[n] };
         if (cstate == 1) { 
            if ( survivalrule[n] == 1) {return 1} 
            else {if (n_states == 2) {return 0} 
                  else {return 2} };
         };
         if (cstate < (n_states - 1)) {return (cstate + 1)} else {return 0};
         break;

      case 4:  // Bx2 && VN 
         n = state_VNneighbor_count(c, 1);
         cstate = c.state;
         if (cstate == 0){ return birthrule[n] * 2 };  // return state 2 for intermediate state.
         if (cstate == 2){ return birthrule[n] };      // return state 1 for promoted to alive state
         return survivalrule[n]                      // if cstate == 1 (or anything else), treat as an alive state
         break;

      case 5:  // Bx2 && Moore 
         n = state_neighbor_count(c, 1);
         cstate = c.state;
         if (cstate == 0){ return birthrule[n] * 2 };  // return state 2 for intermediate state.
         if (cstate == 2){ return birthrule[n] };      // return state 1 for promoted to alive state
         return survivalrule[n]                      // if cstate == 1 (or anything else), treat as an alive state
         break;         
         
     
      case 6:   // Goucher Glider Rule  -- code translated from Ready 
         n1 = state_neighbor_count(c, 1)
         n2 = state_neighbor_count(c, 2)
         n3 = state_neighbor_count(c, 3)
         cstate = c.state;
         if (cstate == 0)   {
            if(n1>0 && n2>0)   {return 3}
            else if(n1>0 && n3>=2)  {return 1}
            else  {return  0}
         }
         else if(cstate==1)  { // head 
            if(n3>0) { return 2}
            else {  return 1}
         }
         else if(cstate==2)  {  return 3}  // tail
         else { return 0}
         break;
      case 7:  //   VN 3 State Fun
         VN_n1 = state_VNneighbor_count(c, 1)
         n2 = state_neighbor_count(c, 2)  // Not VN 
         cstate = c.state;
         if (cstate == 0) {
            if (VN_n1 == 1 && n2 == 0) {return 1} else {return 0}
          }
         if (cstate == 1) {return 2}
         if (cstate == 2) {return 0}
         else {return 0}                                         
         break;
      case 8:   //  my Tri-Goucher Glider Rule 
         n1 = state_neighbor_count(c, 1)
         n2 = state_neighbor_count(c, 2)
         n3 = state_neighbor_count(c, 3)
         VN_n3 = state_VNneighbor_count(c, 3)
         cstate = c.state;
         if (cstate == 0)   {
            if(n1>0 && n2>0)   {return 3}
            else if (n1>0 && n3>=2 && VN_n3==0) {return 1}  // original: else if(n1>0 && n3>=2)  {return 1} 
            else  {return  0}
         }
         else if(cstate==1)  { // head 
            if(n3>0) { return 2}
            else {  return 1}
         }
         else if(cstate==2)  {  return 3}  // tail
         else { return 0}
         break;
      case 9:
         n = state_neighbor_count(c, 1);
         cstate = c.state;
         if (n == 2) { if (cstate == 0 && Bnontotal_flag == "2a" && adjacent_two_VNneighbors(c)) {return 1} 
                       if (cstate == 0 && Bnontotal_flag == "2-a" && adjacent_two_VNneighbors(c) == false) {return 1} 
                       if (cstate == 1 && Snontotal_flag == "2a" && adjacent_two_VNneighbors(c)) {return 1} 
                       if (cstate == 1 && Snontotal_flag == "2-a" && adjacent_two_VNneighbors(c) == false) {return 1} 
                       return 0
         }
        
         else {
              if (cstate == 0){ return birthrule[n] };
              if (cstate == 1) { 
                 if ( survivalrule[n] == 1) {return 1} 
                 else {if (n_states == 2) {return 0} 
                       else {return 2} };
               };

              if (cstate < (n_states - 1)) {return (cstate + 1)} else {return 0};
         }
         break;

      case 10:  // Corner_lifelike and Corner_Generations combined  - this is like VN but for corners instead.
         n = state_Corner_neighbor_count(c, 1);
         cstate = c.state;
         if (cstate == 0){ return birthrule[n] };
         if (cstate == 1) { 
            if ( survivalrule[n] == 1) {return 1} 
            else {if (n_states == 2) {return 0} 
                  else {return 2} };
         };
         if (cstate < (n_states - 1)) {return (cstate + 1)} else {return 0};
         break;
      case 11:  // Bx2 && Corner
         n = state_Corner_neighbor_count(c, 1);
         cstate = c.state;
         if (cstate == 0){ return birthrule[n] * 2 };  // return state 2 for intermediate state.
         if (cstate == 2){ return birthrule[n] };      // return state 1 for promoted to alive state
         return survivalrule[n]                      // if cstate == 1 (or anything else), treat as an alive state
         break;
      case 12:
         n = state_neighbor_count(c, 1);
         cstate = c.state;
         if (n == 2) { if (cstate == 0 && Bnontotal_flag == "2e" && adjacent_two_Moore_neighbors(c)) {return 1} 
                       if (cstate == 0 && Bnontotal_flag == "2-e" && adjacent_two_Moore_neighbors(c) == false) {return 1} 
                       if (cstate == 1 && Snontotal_flag == "2e" && adjacent_two_Moore_neighbors(c)) {return 1} 
                       if (cstate == 1 && Snontotal_flag == "2-e" && adjacent_two_Moore_neighbors(c) == false) {return 1} 
                       return 0
         }
        
         else {
              if (cstate == 0){ return birthrule[n] };
              if (cstate == 1) { 
                 if ( survivalrule[n] == 1) {return 1} 
                 else {if (n_states == 2) {return 0} 
                       else {return 2} };
               };

              if (cstate < (n_states - 1)) {return (cstate + 1)} else {return 0};
         }
         break;

     

      default: return 0
   }
}
   

var birthrule =     [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];   
var survivalrule =  [0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; 
birth1 = "B3"
survive1 = "S23"
var n_states = 2
var rulefamily = 1

var   Bnontotal_flag = "totalistic" 
var   Snontotal_flag = "totalistic" 

function parse_rule_string(inputrulestring) {
   birthrule =     [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];   
           // for weighted life, this doesn't work - may need more than 36 spaces
   survivalrule =  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; 


   Bnontotal_flag = "totalistic" 
   Snontotal_flag = "totalistic" 

  if (inputrulestring.match("Goucher")) {
   birth1 = "Goucher Gliders"
   survive1 = ""
   rulefamily = 6;
  } 
  else if (inputrulestring.match("VN 3 State Fun")) {
   birth1 = "VN 3 State Fun"
   survive1 = ""
   rulefamily = 7;
  } 
  else if (inputrulestring.match("my TriGou")) { 
   birth1 = "my TriGou"
   survive1 = ""
   rulefamily = 8;
  } 
  else {
 
  inputrulestring = inputrulestring.toUpperCase();
  inputrulestring = inputrulestring.replace("_", "/");

  inputrulestringparts = inputrulestring.split("/");
  	if (inputrulestringparts[0].match("B") != null) {
	   birth1 = inputrulestringparts[0];
           survive1 = inputrulestringparts[1];
          decay1 = inputrulestringparts[2];
 	  }
	 else {
           birth1 = inputrulestringparts[1];
           survive1 = inputrulestringparts[0];
    	   decay1 = inputrulestringparts[2];
 	  };
      
       
       if (decay1 == undefined) {n_states = 2} else {n_states = parseInt(decay1.replace("C", " "))};

       if (n_states ==  2) 
            {rulefamily = 1}  // lifelike  for now, non-totalistic later
       else {rulefamily = 2};   // Generations for now, Bx2 later.

       if (inputrulestring.match("X2") ) { 
           birth1 = birth1.replace("X2","");  // it is important to remove the "X2" so that the 2 isn't parsed as the birth rule B2.
// but put it back at the end for the menu entry, the save pattern entry, etc
           if (inputrulestring.match("V")) {rulefamily = 4} // Bx2 with Von Neuman neighobrs
           else if (inputrulestring.match("CORN") || inputrulestring.match("CNR") ) {rulefamily = 11}  // Bx2 with Corner neighbors
           else   {rulefamily = 5}   // Bx2 with Moore neighbors
           n_states = 3;   // not necessary but might be useful in the future
       }
       else if  ( inputrulestring.match("V")) {rulefamily = 3}
       else if  ( inputrulestring.match("CORN") || inputrulestring.match("CNR"))  {rulefamily = 10}
       else if  ( inputrulestring.match("2A") || inputrulestring.match("2-A") ) {rulefamily = 9}  // this precludes 2a and 2-a rules and von Neuman neighborhoods at the same time.
       else if  ( inputrulestring.match("2E") || inputrulestring.match("2-E") ) {rulefamily = 12}
         


        birth1 = birth1.split(",");
        survive1 = survive1.split(",");

        if (birth1[0].match("2A"))  {Bnontotal_flag = "2a"}
        else if (birth1[0].match("2-A"))  {Bnontotal_flag = "2-a"}

        if (survive1[0].match("2A"))  {Snontotal_flag = "2a"}
        else if (survive1[0].match("2-A"))  {Snontotal_flag = "2-a"}

        if (birth1[0].match("2E"))  {Bnontotal_flag = "2e"}
        else if (birth1[0].match("2-E"))  {Bnontotal_flag = "2-e"}

        if (survive1[0].match("2E"))  {Snontotal_flag = "2e"}
        else if (survive1[0].match("2-E"))  {Snontotal_flag = "2-e"}





	for (var i = 0; i<birth1[0].length; i++) {
           var x = parseInt( birth1[0][i]);
  	   if (x >= 0 && x <= 9) {birthrule[x] = 1};
	}
        
       // Then parse the rest as B,10,19,20,24
      	for (var i = 1; i<birth1.length; i++) {
           var x = parseInt( birth1[i]);
  	   if (!isNaN(x) ){birthrule[x] = 1};
	}
        



	for (var i = 0; i<survive1[0].length; i++) {
	   var x = parseInt( survive1[0][i]);
	   if (x >= 0 && x <= 9) {survivalrule[x] = 1};
	}

      	for (var i = 1; i<survive1.length; i++) {
           var x = parseInt( survive1[i]);
  	   if (!isNaN(x) ){survivalrule[x] = 1};
	}
         }  // end non-goucher section
 if (rulefamily == 4 || rulefamily == 4) {birth1 = birth1 + "x2"}
 return inputrulestring

}

function add_rule (rulestring) {
    // check if the rule is actually already on the list of rules
    var newrule_flag = 1;
    len = rulesmenu_data.length
    for (var i = 0; i<len; i++) {                                        
                                                                         
       if (rulestring == rulesmenu_data[i][0]) {                        // if the rulestring matches a known rule
           document.getElementById("rulesmenu").selectedIndex = i      // set the rulemenu to the old rule.
           newrule_flag = 0
           break;
          }
     }
    if (newrule_flag) {
                   var previousmenu = document.getElementById("rulesmenu").selectedIndex;
                   rulesmenu_data.splice(2,0,[rulestring]);  // add new rule to list of rules
                   put_rules_in_menu();
                   if (previousmenu > 0) {document.getElementById("rulesmenu").selectedIndex = previousmenu + 1};
     }
}

function after_changing_rule() {
  do_CA_rule_on_all_cells()
  generation += 1;

  //  clearboard()
  drawboard()
}
   

function change_rule () {
   var therulesmenu = document.getElementById("rulesmenu")
   choice =  therulesmenu.options[ therulesmenu.selectedIndex].value
   switch(choice) {
       case "PromptForRule": 
                   add_rule( parse_rule_string( prompt("Enter a Lifelike or Generations rule using  the Bxxx/Sxxx or Bxxx/Sxxx/Cx formats:")));
                   break;
       default: parse_rule_string(choice)
   }

  after_changing_rule()
}

function set_rule_from_outside (rulestring) {
  add_rule(parse_rule_string (rulestring))
  after_changing_rule()
}
    
   // # instead of default patterns, url-based rules and patterns
  // # use partof to calc (or pre-store) vn neighbors
  // #  Larger than life too?

   
   



function updatecells3() {
   for (var j=NewChangeCount; j--;){
      cel = newchangedcells[j];
      cel.state = cel.placeholder_state;
      changedcells[j] = cel;
    }
}

var newchangedcells = [];  // quick global experiment
var NewChangeCount = 0;  // quick global experiment


function refresh () {
 var cel = settypecell;
 var neighborcell = settypecell;
 var neighborlist = [];
 
 NewChangeCount = 0; 

 for (var i=changecount; i--;) {
   cel = changedcells[i];

   // first handle neighbors
   neighborlist = cel.neighbors;
   for (var j=neighborlist.length; j--;) {
      neighborcell = neighborlist[j];
      if (neighborcell.message != generation) {
         neighborcell.message = generation;
       
          neighborcell.placeholder_state = ca_rule3(neighborcell);
 
          if (neighborcell.placeholder_state != neighborcell.state) {
              newchangedcells[NewChangeCount] = neighborcell;
               NewChangeCount += 1;
            }
          }
   }
 // now almost repeat the code to handle cell itself
  if (cel.message != generation) {
       cel.message = generation;
       cel.placeholder_state = ca_rule3(cel);
       if (cel.placeholder_state != cel.state  ) { 
            newchangedcells[NewChangeCount] = cel;
            NewChangeCount += 1;
          } 
     }
  }
 
        updatecells3()
        changecount = NewChangeCount;
};




function do_CA_rule_on_all_cells() {
//  This is the slow way  -- see other functions for a faster way using a change list!!!
//   The slow way is useful just after the CA rule changes
   var cel;
   NewChangeCount = 0; 

   for (var i = aci; i--;) {
      cel = allcells[i];
      cel.message = generation;
      cel.placeholder_state = ca_rule3(cel);
      if (cel.placeholder_state != cel.state  ) { 
            newchangedcells[NewChangeCount] = cel;
            NewChangeCount += 1;
       }
   }
   updatecells3()
   changecount = NewChangeCount;
   
}



   
function drawboard() {
   for (var i = aci; i--;) {
        colorcell(allcells[i])
   }
}

function drawgrid() {
   for (var i = aci; i--;) {
       strokecell(allcells[i])
   }
}




function draw_changed () {
   if (drawEdgesFlag) {  // this flag might be replaced with the re-definition of colorcell where the flag is set.
      for (var i=changecount; i--;){
          colorcell( changedcells[i])
      }
   } 
   else if (drawShapesFlag) {
      for (var i=changecount; i--;){
          NoStrokecolorcell( changedcells[i])
      }
   } 
   else {
      for (var i=changecount; i--;){
          Rectcolorcell( changedcells[i])
      }
   }

}



function cnvs_mousemove(event) {
   if (mouse_status == "select") {
     if (select_clicks == 1) {
          xmouse_unscaled =  Math.round((event.clientX + window.pageXOffset - 10)  )
          ymouse_unscaled =  Math.round((event.clientY+ window.pageYOffset - 28)  )

           ctx2.clearRect(Math.min(select_1st_x,select_2nd_x),Math.min(select_1st_y,select_2nd_y),
                       Math.abs( select_2nd_x - select_1st_x), Math.abs(select_2nd_y - select_1st_y));

          ctx2.fillStyle = greenish_select_color;  
          ctx2.fillRect(Math.min(select_1st_x,xmouse_unscaled),Math.min(select_1st_y,ymouse_unscaled),
                        Math.abs(xmouse_unscaled - select_1st_x), Math.abs(ymouse_unscaled - select_1st_y));
          select_2nd_x =  xmouse_unscaled;
          select_2nd_y = ymouse_unscaled;
         //  Since these select_2nd values are provisional (before the second select click),
         //   there must be a provision to ensure they don't become numbers that go outside the boundaries of the canvas
          select_2nd_x =  Math.min(Math.max(select_2nd_x,0),the_dimension);
          select_2nd_y =  Math.min(Math.max(select_2nd_y,0),the_dimension);
       }
   }
   else if ( draw_status  == "on") {
      xmouse = (event.clientX + window.pageXOffset - 10)/currentscale  //  - current_translate_x/currentscale ;
      ymouse = (event.clientY+ window.pageYOffset - 28)/currentscale   //  - current_translate_y/currentscale;
      draw_at_mouse_here(xmouse, ymouse, "moving")
      event.preventDefault();  // this line is intended to ensure that mouseup eventually fires
   }
   
 }
 
function cnvs_mouseup(event) {
         draw_status  = "off"
        lastdrawnhere = -1

    if (mouse_status == "select" && select_clicks == 1) {
          xmouse_unscaled =  Math.round( (event.clientX + window.pageXOffset - 10) ) 
          ymouse_unscaled =  Math.round( (event.clientY+ window.pageYOffset - 28) ) 

          select_clicks = 2;
          select_2nd_x = xmouse_unscaled;
          select_2nd_y = ymouse_unscaled;
          ctx2.fillStyle=greenish_select_color;  
          ctx2.fillRect(Math.min(select_1st_x,select_2nd_x),Math.min(select_1st_y,select_2nd_y),Math.abs( select_2nd_x - select_1st_x), Math.abs(select_2nd_y - select_1st_y));       
      }

}


function calc_cell_size (cel) {
   var xmin = Math.abs( cel.coords[0])
   var xmax = Math.abs( cel.coords[0])
   var ymin = Math.abs( cel.coords[1])
   var ymax = Math.abs( cel.coords[1])
   var coordslen = cel.coords.length
   for (var j = 0; j<coordslen; j = j + 2) {
      if (Math.abs( cel.coords[j] < xmin)) {xmin = Math.abs( cel.coords[j])}
      if (Math.abs( cel.coords[j] > xmax)) {xmax = Math.abs( cel.coords[j])}
      if (Math.abs( cel.coords[j+1] < ymin)) {ymin = Math.abs( cel.coords[j+1])}
      if (Math.abs( cel.coords[j+1] > ymax)) {ymax = Math.abs( cel.coords[j+1])}
    }
   return ((xmax - xmin) + (ymax - ymin))/2
}
    
function calc_avg_cell_size (startindex, endindex) {
    var avg_cell_size = calc_cell_size(allcells[startindex]);
    for (var i = startindex + 1; i <= endindex; i++) {
        avg_cell_size = avg_cell_size + calc_cell_size (allcells[i]);
    }
    return avg_cell_size/(endindex - startindex + 1)
}
         


function calc_cell_center (cel) {
     var xtotal = 0
     var ytotal = 0
     var coordslen = cel.coords.length
     for (var j = 0; j<coordslen; j = j + 2) {
           xtotal = xtotal  + Math.abs( cel.coords[j])
           ytotal = ytotal +  Math.abs( cel.coords[j + 1])
     }
     var avgXtotal = xtotal/(coordslen/2)
     var avgYtotal = ytotal/(coordslen/2)
     return [avgXtotal, avgYtotal]
}

function cell_x_distance (cel1, cel2) {
 return  Math.abs( calc_cell_center(cel1)[0] - calc_cell_center(cel2)[0] )
}

function cell_y_distance (cel1, cel2) {
 return  Math.abs( calc_cell_center(cel1)[1] - calc_cell_center(cel2)[1] )
}
  
//*************************
//  pnpoly
// Credit:  http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html#Explanation
function pnpoly( nvert, vertx, verty, testx,  testy)
{
  var i,j,c =0;
  //var j = 0;
  //var c = 0;
  for (i = 0, j = nvert-1; i < nvert; j = i++) {
    if ( ((verty[i]>testy) != (verty[j]>testy)) &&
	 (testx < (vertx[j]-vertx[i]) * (testy-verty[i]) / (verty[j]-verty[i]) + vertx[i]) )
       c = !c;
  }
  return c;
}


function call_pnpoly (x, y, celll) {
  var vertx = [];
  var verty = [];
  var clen = celll.coords.length;
  for (i = 0; i < clen; i = i + 2) {
     vertx.push(celll.coords[i])
     verty.push(celll.coords[i+1])
  }
  var fnnlen = celll.fnn.length;
  return pnpoly(fnnlen, vertx, verty, x, y)
}

function cnvs_mousedown(event) {
   if (mouse_status == "draw") {
          xmouse = (event.clientX + window.pageXOffset - 10)/currentscale  //  - current_translate_x/currentscale ;
          ymouse = (event.clientY+ window.pageYOffset - 28)/currentscale   //  - current_translate_y/currentscale;

         draw_at_mouse_here(xmouse,ymouse, "clicking")
         draw_status = "on";
  } 
  else {  // select

      xmouse_unscaled = Math.round( (event.clientX + window.pageXOffset - 10) ) //  - current_translate_x/currentscale ;
      ymouse_unscaled = Math.round((event.clientY+ window.pageYOffset - 28)  ) //  - current_translate_y/currentscale;

     if (select_clicks == 0) {
          select_clicks = 1;
          select_1st_x = xmouse_unscaled;
          select_1st_y = ymouse_unscaled;
      }
      else if (select_clicks == 1) {
          select_clicks = 2;
          select_2nd_x = xmouse_unscaled;
          select_2nd_y = ymouse_unscaled;
          ctx2.fillStyle=greenish_select_color;  
          ctx2.fillRect(Math.min(select_1st_x,select_2nd_x),Math.min(select_1st_y,select_2nd_y),Math.abs( select_2nd_x - select_1st_x), Math.abs(select_2nd_y - select_1st_y));

      }
      else {
          select_clicks = 0
          //ctx2.fillStyle=black;  
          ctx2.clearRect(Math.min(select_1st_x,select_2nd_x),Math.min(select_1st_y,select_2nd_y),Math.abs( select_2nd_x - select_1st_x), Math.abs(select_2nd_y - select_1st_y));
      }
   }
        
       
    event.preventDefault();  // this line is intended to ensure that mouseup eventually fires

}


var  mouse_status =  "draw";
var  draw_status  = "off"
var  colored_pencil = 1
var  colored_pencil_choice = 1

var  lastdrawnhere;

// Selecting cells via the mouse
var select_clicks = 0;
var select_1st_x = 0;
var select_1st_y = 0;
var select_2nd_x = 0;
var select_2nd_y = 0;


function draw_at_mouse_here(xmouse,ymouse, clickingstatus) {
  for (var i = aci; i--;) {
    if (Math.abs(allcells[i].coords[0]  - xmouse) < shapesize*3) {
     if (call_pnpoly( xmouse, ymouse, allcells[i])) {  // could rule out many candidates before calling  pnpoly
       if (lastdrawnhere != i) {
 
         if (clickingstatus == "clicking") {
         
            if ( allcells[i].state != 0) {
                colored_pencil = 0;
                allcells[i].state = 0;
             } 
             else {
                 colored_pencil = colored_pencil_choice;
                 allcells[i].state = colored_pencil_choice;
              }
          }
          else {  // the mouse is moving, not clicking, so colored_pencil can't change status
              allcells[i].state = colored_pencil
           }
         colorcell(allcells[i])
         changedcells[changecount] = allcells[i];
         changecount += 1;
         lastdrawnhere = i
         break;  
       }
      }
   }
  }
}





var speedstep = 1;

function timedCount ()  { 
   for (var ij=speedstep; ij--;) {   

     refresh()

     draw_changed()
    

     generation += 1;
  }
   //drawboard()    // pick which algo !!!!!!!!!!!!!!!!!!

};


function doTimer() {
  if (!timer_is_on) {
    document.getElementById("gobutton").value = "Stop";
   // document.getElementById("randombutton").value = "Stop";
    timer_is_on=1;
    t=setInterval(timedCount,delay);
  }
  else { 
    stopCount();
  } 
};

function stopCount() {
  clearInterval(t);
  timer_is_on=0;
  document.getElementById("gobutton").value = "Go";
 // document.getElementById("randombutton").value = "Test";
  //drawboard();   // cleans up the board
  draw_changed()

};

function interpret_keys(ev) {
    ev.preventDefault();  //  <-- this disables the normal scrolling effect that the arrow keys usually have

   switch(ev.keyCode) {
      case 13: // Enter Key
          doTimer();
          break;
      case 32: // space Key
          stepCount();
          break;
      case 221: // right bracket
          increase_magnification();
          break;
      case 219:  // left bracket
          decrease_magnification();
          break;
   }
}




function stepCount() {

   /*
     refresh()
     draw_changed()
     //drawboard()    // pick which algo !!!!!!!!!!!!!!!!!!
     generation += 1;
   */
    timedCount();

    stopCount();

}
     




//   background purple:  #9966FF
//  background  light blue that shows black lines:  #4A77BA as opposed to a simple blue like #00F

/* fillStyles for displaying decay in Generations rule family:

{ctx.fillStyle = "rgba(255,0,0," +  .3  + ")"}  // 0.5/st
               // alternatives for decay:  {ctx.fillStyle = '#F00'}   //"rgb(" + 255/(st*2) + "," + 255/(st*2) +",0)" }  //{fill(255/st)}

*/
                 // 0           1       2       3           4 
var colorlookup = ['#4A77BA', '#FF0', '#F00', '#00FF00', '#CC33FF', '#00FFFF', '#FFFFFF', '#000000', '#000000', '#000000', '#000000', '#000000', '#000000', '#000000', '#000000', '#000000']

//  ctx.fillStyles should be pre-calculated so that ctx.fillStyle = colorlookup[x.state]




//  var st =  x.state;
//  if (st == 1)   {ctx.fillStyle = '#FF0'}
//  else if (st > 1)    {ctx.fillStyle = "rgb(" + 300/st + "," + 300/st + "," + 300/st + ")"} // {ctx.fillStyle = "rgb(" + 255/(st-1) + ",0,0)"}  //
//  else  {ctx.fillStyle = '#4A77BA'}; 

/*
  Render Options:
  default - prettiest
  noEdges - faster
  fillRect - fastest?
  putImage - fastest?

  Pretty & slowest
  Plain & faster
  Blocky & fastest
*/


  

function Rectcolorcell (x) {
 var c = x.coords;
 var st = x.state;
// if (st) {
   ctx.fillStyle = colorlookup[x.state]
//  ctx.fillRect(Math.floor(c[0]),Math.floor(c[1]), scaledshapesize, scaledshapesize)
   ctx.fillRect(Math.floor(c[0]),Math.floor(c[1]), 2, 2)

// }  
 //else {
 //  ctx.clearRect(c[0],c[1], scaledshapesize, scaledshapesize)
// }  
 
}


function NoStrokecolorcell(x) {   // candidate for deletion -- see function strokecell(x)
 var c = x.coords;
 ctx.fillStyle = colorlookup[x.state]

 ctx.beginPath();   
 ctx.moveTo(c[0], c[1]);
 for (var i=c.length; i-=2;) {
     ctx.lineTo(c[i], c[i+1]);
 }
 ctx.closePath();  
 ctx.fill();
// ctx.stroke();

}


function colorcell(x) {
 var c = x.coords;
 ctx.fillStyle = colorlookup[x.state]

 ctx.beginPath();   
 ctx.moveTo(c[0], c[1]);
 for (var i=c.length; i-=2;) {
     ctx.lineTo(c[i], c[i+1]);
 }
 ctx.closePath();  
 ctx.fill();
// ctx.stroke();

}



/* The for loop in this older version is more robust -- handles an f with nothing after it in the obj data
function colorcell(x) {
 var c = x.coords;
 var clen = c.length;

  ctx.fillStyle = colorlookup[x.state]

  ctx.beginPath();  // Start tracing the cell's shape
  ctx.moveTo(c[0], c[1]);
  for (var i = 2; i < clen-1; i+=2) {
     ctx.lineTo(c[i], c[i+1]);
  }
 ctx.closePath();  
 ctx.fill();
// ctx.stroke();
}
*/

function strokecell(x) {
  var c = x.coords;

       //ctx.fillStyle = colorlookup[x.state]
  ctxGrid.beginPath();   
  ctxGrid.moveTo(c[0], c[1]);
  for (var i=c.length; i-=2;) {
     ctxGrid.lineTo(c[i], c[i+1]);
  }
  ctxGrid.closePath();  
      // ctx.fill();
  ctxGrid.stroke();

}







/*
 var clen = c.length - 1;

  for (i=clen; i-=2;) {
          ctx.lineTo(c[i], c[i+1]);
  }
*/

/*
// An experimetn with PutImageData
var yellow1x1array = [255, 255, 0, 255];
var blue1x1array =   [0,255,255,255];
var  ypxid1 = cxt.createImageData(1,1);
var  ypxdd1 =  ypxid1.data;
var  ypxid2 = cxt.createImageData(2,2);
var  ypxdd2 =  ypxid2.data;
var  ypxid3 = cxt.createImageData(3,3);
var  ypxdd3 =  ypxid3.data;
var  ypxid4 = cxt.createImageData(4,4);
var  ypxdd4 =  ypxid4.data;
var  ypxid4 = cxt.createImageData(4,4);
var  ypxdd4 =  ypxid4.data;

var  bpxid1 = cxt.createImageData(1,1);
var  bpxdd1 =  bpxid1.data;
var  bpxid2 = cxt.createImageData(2,2);
var  bpxdd2 =  bpxid2.data;
var  bpxid3 = cxt.createImageData(3,3);
var  bpxdd3 =  bpxid3.data;
var  bpxid4 = cxt.createImageData(4,4);
var  bpxdd4 =  bpxid4.data;

for (var j = 0;  j<4; j++){ypxdd1[j] = yellow1x1array[j]};
for (var i = 0;  i<4; i++){for (var j=0; j<4; j++) {ypxdd2[(i*4)+ j ] = yellow1x1array[j]}};    
for (var i = 0;  i<9; i++){for (var j=0; j<4; j++) {ypxdd3[(i*4)+ j ] = yellow1x1array[j]}};
for (var i = 0;  i<12; i++){for (var j=0; j<4; j++) {if (i%4 != 0) {ypxdd4[(i*4)+ j ] = yellow1x1array[j]}}};

for (var j = 0;  j<4; j++){bpxdd1[j] = blue1x1array[j]};
for (var i = 0;  i<4; i++){for (var j=0; j<4; j++) {bpxdd2[(i*4)+ j ] = blue1x1array[j]}};    
for (var i = 0;  i<9; i++){for (var j=0; j<4; j++) {bpxdd3[(i*4)+ j ] = blue1x1array[j]}};
for (var i = 0;  i<12; i++){for (var j=0; j<4; j++) {if (i%4 != 0) {bpxdd4[(i*4)+ j ] = blue1x1array[j]}}};

*/



function clearboard () {
   ctx.save();
   ctx.setTransform(1, 0, 0, 1, 0, 0);  // Will always clear the right space
   ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
   ctx.restore(); 

   ctxGrid.save();
   ctxGrid.setTransform(1, 0, 0, 1, 0, 0);  // Will always clear the right space
   ctxGrid.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
   ctxGrid.restore(); 


}



function clear_field () {
   for (var i = aci; i--;) {
      allcells[i].state = 0;
      changedcells[changecount] = allcells[i];
      changecount += 1;

   }
 drawboard() 
}



function getrandomnumber(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min
}

function call_make_random_field_with_top() {
 // if (timer_is_on) {
 //   stopCount()
 // } 
 // else { 

     make_random_field("top")

 //    doTimer();
 // }
}

function make_random_field(loc) {
 
 if (loc == "top" || (select_1st_x -select_2nd_x == 0 && select_1st_y - select_2nd_y == 0))  {   //  if (select_clicks == 0) {

   for (var i = aci ; i--;) {
      if ((getrandomnumber(0,3) == 1) && ( allcells[i].coords[1] < (mesh_y_size /3))) { allcells[i].state = 1} else { allcells[i].state = 0}
      changedcells[changecount] = allcells[i];
      changecount += 1;
    }
  } 
  else {
   var c_select_1st_x = Math.min(select_1st_x, select_2nd_x)/currentscale
   var c_select_2nd_x = Math.max(select_1st_x, select_2nd_x)/currentscale
   var c_select_1st_y = Math.min(select_1st_y, select_2nd_y)/currentscale
   var c_select_2nd_y = Math.max(select_1st_y, select_2nd_y)/currentscale
   for (var i = aci; i--;) {
     if (  allcells[i].coords[0] > c_select_1st_x  && allcells[i].coords[0] < c_select_2nd_x  &&  
           allcells[i].coords[1] > c_select_1st_y  && allcells[i].coords[1] < c_select_2nd_y
        ) {
           if (getrandomnumber(0,3) == 1) { allcells[i].state = 1} else { allcells[i].state = 0}
            changedcells[changecount] = allcells[i];
            changecount += 1;
          }
   }
 }
  drawboard() 
}


// This function doubles as  " function clear_selection "  by using zero as a parameter
function fill_selection(st) {
 if (select_clicks != 0) {
   var c_select_1st_x = Math.min(select_1st_x, select_2nd_x)/currentscale
   var c_select_2nd_x = Math.max(select_1st_x, select_2nd_x)/currentscale
   var c_select_1st_y = Math.min(select_1st_y, select_2nd_y)/currentscale
   var c_select_2nd_y = Math.max(select_1st_y, select_2nd_y)/currentscale
  for (var i = aci; i--;) {
     if (  allcells[i].coords[0] > c_select_1st_x  && allcells[i].coords[0] < c_select_2nd_x  &&  
           allcells[i].coords[1] > c_select_1st_y  && allcells[i].coords[1] < c_select_2nd_y
        ) {
            allcells[i].state = st;
            changedcells[changecount] = allcells[i];
            changecount += 1;
          }
   }
  drawboard() 
 }
}

function clear_outside() {
 if (select_clicks != 0) {
   var c_select_1st_x = Math.min(select_1st_x, select_2nd_x)/currentscale
   var c_select_2nd_x = Math.max(select_1st_x, select_2nd_x)/currentscale
   var c_select_1st_y = Math.min(select_1st_y, select_2nd_y)/currentscale
   var c_select_2nd_y = Math.max(select_1st_y, select_2nd_y)/currentscale
  for (var i = aci; i--;) {
     if (  allcells[i].coords[0] < c_select_1st_x  || allcells[i].coords[0] > c_select_2nd_x  ||  
           allcells[i].coords[1] < c_select_1st_y  || allcells[i].coords[1] > c_select_2nd_y
        ) {
            allcells[i].state = 0;
            changedcells[changecount] = allcells[i];
            changecount += 1;
          }
   }
  drawboard() 
 }
}


    


function print_obj() {
  windowref = window.open();
  var third = 0;
  windowref.document.writeln("<pre>")

  for (var i = 1; i<v_index; i++) {
      windowref.document.writeln("v " + vertexArray[i].x.toFixed(sigdigits) + " " + vertexArray[i].y.toFixed(sigdigits) + " " + third.toFixed(1));
  } 
  for (var i = 0; i<aci; i++) {
      windowref.document.write("f");
      var flen = allcells[i].fnn.length;
      for (var j = 0; j<flen; j++) {
          windowref.document.write(" " + allcells[i].fnn[j])
      }
      windowref.document.writeln();
  }
  windowref.document.writeln();
/*
  for (var i = 0; i<aci; i++) {
      windowref.document.write("#C " + i + " #N ")
      for (var j = 0; j<allcells[i].neighbors.length; j++) {
         windowref.document.write(allcells[i].neighbors[j].index)
         windowref.document.write(" ")
      }
      windowref.document.writeln();
   }
*/

   windowref.document.writeln(" ")
   windowref.document.writeln(" ")
   windowref.document.writeln("#R "+ birth1 + "_" + survive1 + "_C" + n_states)
   windowref.document.writeln(" ")
   windowref.document.writeln(" ")


  for (var i = 0; i<aci; i++) {
    if (allcells[i].state != 0) {
      windowref.document.writeln("#C " + i + " #S " + allcells[i].state)
     }
   }
 
  windowref.document.writeln("</pre>")
}
 
 
function print_pattern () {
  windowref = window.open();
  var third = 0;
  windowref.document.writeln("<pre>")
  windowref.document.writeln("#N "+ " Enter_name_here")
  windowref.document.write("#R "+ birth1 + "/" + survive1)
  if (rulefamily == 2) {windowref.document.write("/C" + n_states)};
  windowref.document.writeln("");
  //windowref.document.writeln(" ")
  //windowref.document.writeln(" ")
  for (var i = 0; i<aci; i++) {
    if (allcells[i].state != 0) {
      windowref.document.writeln("#C " + i + " #S " + allcells[i].state)
     }
   }
  windowref.document.writeln("</pre>")
}



     
          
      


function load_pattern(pattern_name) {
   var p = lifepatterns[pattern_name].patternstring;
   p = p.replace(" ", "");
   p = p.split("#C");
   var len = p.length;
   var entry;
   for (var i = 1; i<len; i++) {
      entry = p[i].split("#S");
      allcells[parseInt(entry[0])].state = parseInt(entry[1]);
      changedcells[changecount] = allcells[parseInt(entry[0])];
      changecount += 1;
   }
}      
   


function parse_pattern_entries(pattern_entries) {
   pattern_entries = pattern_entries.replace(/(\r\n|\n|\r)/gm,"-----newline-----");
   pattern_entries = pattern_entries.split("-----newline-----");
   pattern_entries[pattern_entries.length] = "";  // ????
    var newname = "";
    var newrule = "all";
    var newpatternstring = "";
   var len = pattern_entries.length
   for (var i = 0; i < len; i++) {
      entry = pattern_entries[i].split(" ");
      if (entry[0] == "#N") {
          newname = entry[1]
          continue;
       }
       if (entry[0] == "#R") {
          newrule = entry[1]
          continue;
       }
       if (entry[0] == "#C") {
          newpatternstring = newpatternstring.concat(" " + pattern_entries[i] + " ");
          continue;
       }
       if (newname != "") {
         lifepatterns[newname] = {rule: newrule, patternstring: newpatternstring};
       }
      newname = "";
      newrule = "all";
      newpatternstring = "";
    }
}

  

   
function parse_OBJ_entries (OBJinput) {
     OBJinput = OBJinput.replace(/(\r\n|\n|\r)/gm,"-----newline-----");
     OBJinput = OBJinput.split("-----newline-----");
     var arr = OBJinput;
     var len = OBJinput.length
     var facearr;
     var coordarr;
     var shapetype = "shape"

     for (var i = 0; i < len; i++) {
       entry = arr[i].split(" ");
 
       if (entry[0] == "v") {
        vertexArray[v_index] =  new cellvertex(v_index, parseFloat(entry[1]), parseFloat(entry[2]), []);    
        v_index = v_index + 1;


       
       }
       else if (entry[0] == "f") {
           facearr = [];
           coordarr = [];
           var jlen = entry.length;
           if (jlen > 2) {
              for (var j = 1; j<jlen; j++) {
                 facearr[j - 1] = parseInt(entry[j]);
                 coordarr[(j-1)*2] = vertexArray[entry[j]].x
                 coordarr[(j-1)*2+1] = vertexArray[entry[j]].y
                 vertexArray[entry[j]].partof.push(aci);
              }
           
               // if (jlen - 1 == 4) {shapetype = "triangle"} // jlen - 1 == 4 for quads, jlen -1 == 6 for shields and other hexagons, etc.
              allcells[aci] = new cell(aci, coordarr, facearr, [], [],  0, 0, "message", shapetype);

              aci = aci + 1;
           }
       }
     }   
}

/*
This is just for reference - delete this section after load and save are written
  windowref.document.writeln("#R "+ birth1 + "_" + survive1 + "_C" + n_states)
   windowref.document.writeln(" ")
   windowref.document.writeln(" ")


  for (var i = 0; i<aci; i++) {
    if (allcells[i].state != 0) {
      windowref.document.writeln("#C " + i + " #S " + allcells[i].state)
     }
   }

*/


var scalefactor = 1.5; 

function increase_magnification() {
   clearboard();
   ctx.scale(scalefactor, scalefactor);
   ctxGrid.scale(scalefactor, scalefactor);
   currentscale = currentscale*scalefactor;
   scaledshapesize = Math.max(Math.floor(shapesize*currentscale), 1);
   //   drawShapesFlag = scaledshapesize > shapesthreshhold
   //   drawEdgesFlag = scaledshapesize > edgesthreshold

 
   drawboard();
   drawgrid()
}

function decrease_magnification() {
   clearboard();
   ctx.scale(1/scalefactor, 1/scalefactor);
   ctxGrid.scale(1/scalefactor, 1/scalefactor);

   currentscale = currentscale/scalefactor;
   scaledshapesize = Math.max(Math.floor(shapesize*currentscale), 1);
    //  drawShapesFlag = scaledshapesize > shapesthreshhold
    //  drawEdgesFlag = scaledshapesize > edgesthreshold

   drawboard();
   drawgrid()
 }

// var current_translate_x; // global
// var current_translate_y  // global

function center_mesh () {
 
   var xmin = vertexArray[1].x  // rename before making this variable global
   var xmax = vertexArray[1].x

   var ymin =  vertexArray[1].y
   var ymax =  vertexArray[1].y


   for (var i = 1; i < v_index; i++) {
       if (vertexArray[i].x < xmin) { xmin = vertexArray[i].x}
       if (vertexArray[i].y< ymin) { ymin = vertexArray[i].y}
       if (vertexArray[i].x > xmax) { xmax = vertexArray[i].x}
       if (vertexArray[i].y> ymax) { ymax = vertexArray[i].y}
   }

  
  // current_translate_x = -xmin;
  // current_translate_y = -ymin;
  


      // this is one approach:  ctx.translate(current_translate_x , current_translate_y ) 
     // but it makes zooming harder.
   // instead of translating, lets change the vertices 


   for (var i = 0; i < v_index; i++) {
       vertexArray[i].x += -xmin 
       vertexArray[i].y += -ymin
   }
   for (var i = 0; i < aci; i++) {
     var clen = allcells[i].coords.length
     for (var j = 0; j < clen; j = j + 2) {
        allcells[i].coords[j] += -xmin
        allcells[i].coords[j+1] += -ymin
     }
   }
  mesh_x_size = Math.abs(xmax - xmin)
  mesh_y_size = Math.abs(ymax - ymin)


}



var currentscale;
var shapesize; 

var scaledshapesize;
var drawShapesFlag = false;  // ios
var drawEdgesFlag = false;    // ios
// var shapesthreshhold = 1
// var edgesthreshold = 2


function initialize() {
  if (initialize_flag == false) {
       makesite()


     // *********Lines*******
     // See https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Canvas_tutorial/Applying_styles_and_colors

       ctx.lineJoin='round';  //  ctx.lineJoin='bevel';
       ctx.lineCap='square';
       ctx.strokeStyle =  "#000000"; // "#FFFFFF"  //"#000000";
       ctxGrid.lineJoin='round';  //  ctx.lineJoin='bevel';
       ctxGrid.lineCap='square';
       ctxGrid.strokeStyle =  "#000000"; // "#FFFFFF"  //"#000000";
  
  

      if (document.getElementById("OBJdata") != null) {
            parse_OBJ_entries (document.getElementById("OBJdata").innerHTML);    
            document.getElementById("MainBody").removeChild(document.getElementById("OBJdata"))  // not necessary
      }

      initialize_neighbors()   

        center_mesh();

    //  currentscale =  20/( cell_x_distance(allcells[0], allcells[1]) + cell_y_distance(allcells[0], allcells[1]) ) ;
    //  another way to do it:   var shapesize =   ( cell_x_distance(allcells[0], allcells[1]) + cell_y_distance(allcells[0], allcells[1]) ) / 1.6 ; 

      
      shapesize = calc_avg_cell_size(1, 10)
      currentscale =   980 /  mesh_y_size
      scaledshapesize =  Math.max(Math.floor(shapesize*currentscale), 1);
    //  drawShapesFlag = scaledshapesize > shapesthreshhold
    //  drawEdgesFlag = scaledshapesize > edgesthreshold



       ctx.lineWidth =  shapesize/12  
       ctxGrid.lineWidth =  shapesize/12  

       ctx.scale(currentscale,currentscale); 

       ctxGrid.scale(currentscale,currentscale); 

       if (document.getElementById("Pattern_div") != null) {
                parse_pattern_entries(document.getElementById("Pattern_div").innerHTML)
                document.getElementById("MainBody").removeChild(document.getElementById("Pattern_div"))  // not necessary
       }
       put_patterns_in_menu()

      initialize_flag = true
      generation = 0
      make_random_field("top")  
      drawboard();
     //  drawgrid()   // commenting out for ios

  } 
}


// Idea:  scale up or down the actual vertex values in centermesh, just in case scale() is causing trouble.


/*
http://diveintohtml5.info/canvas.html#paths
http://stackoverflow.com/questions/7017998/html-5-canvas-avoid-fill-behaviour-on-overlap
https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Canvas_tutorial/Applying_styles_and_colors

clear   save pattern   load pattern   brushcolor brush    timesteps    export mesh, export mesh+pattern, export pattern only


GGG:
x = 36, y = 9, rule = B3/S23
23b2o$23b2o$10bo15b2o6b2o$8bobo15b3o5b2o$2o4b2o18b2o$2o4b2o15b2o$6b2o
15b2o$8bobo$10bo!

*/









Anon7 - 2021