This seems to do something useful. Now I want to break it apart into little chunks that can be run from timeouts, to avoid long-script warnings. Profiling might be nice too!
authorBenjamin Smedberg <benjamin@smedbergs.us>
Fri, 12 Dec 2008 15:03:00 -0500
changeset 5 b45eafc86660
parent 4 8be31ac9ef27
child 6 a87e98928885
push id1
push userbsmedberg@mozilla.com
push date2008-12-12 21:25 +0000
This seems to do something useful. Now I want to break it apart into little chunks that can be run from timeouts, to avoid long-script warnings. Profiling might be nice too!
jswordle.js
wordle.html
--- a/jswordle.js
+++ b/jswordle.js
@@ -1,11 +1,12 @@
 const kWidth = 800;
-const kHeight = 400;
+const kHeight = 800;
 const kParanoid = false;
+const kVerticalRatio = 0.7;
 
 const commonWords = [
   'the',
   'of',
   'to',
   'and',
   'a',
   'in',
@@ -113,19 +114,28 @@ function setStatus(msg)
 
 function getOwnProperties(o)
 {
   for (let p in o)
     if (o.hasOwnProperty(p))
       yield [p, o[p]];
 }
 
+function range(start, end)
+{
+  for (; start < end; ++start)
+    yield start;
+}
+
+/**
+ * A "random" integer between two points, weighted to the center of the range.
+ */ 
 function randomInt(min, max)
 {
-  return Math.floor(Math.random() * (max - min)) + min;
+  return Math.floor([Math.random() for (i in range(0, 4))].reduce(function (i, j) i + j, 0) / 4 * (max - min)) + min;
 }
 
 function getWordList()
 {
   let wordmap = {};
 
   for (let word in getWords()) {
     if (wordmap.hasOwnProperty(word)) {
@@ -190,17 +200,31 @@ function getWords()
 function hitTest(fullimg, wordimg, x, y)
 {
   let wdata = wordimg.data;
   let fdata = fullimg.data;
 
   if (x < 0 || y < 0 ||
       x + wordimg.width > fullimg.width ||
       y + wordimg.height > fullimg.height)
-    throw new Error("Bad args to hitTest");
+    throw new Error("Bad args to hitTest: " + [x, y, wordimg.width, wordimg.height] + Error().stack);
+
+  if (fullimg.minx === undefined)
+    throw Error("Nothing placed on fullimg yet?");
+
+  if (x > fullimg.maxx ||
+      y > fullimg.maxy ||
+      x + wordimg.width < fullimg.minx ||
+      y + wordimg.height < fullimg.miny) {
+    /*
+    setStatus("outside of bounds: " + [x, y, wordimg.width, wordimg.height] + ", " +
+	      [fullimg.minx, fullimg.miny, fullimg.maxx, fullimg.maxy]);
+     */
+    return false;
+  }
 
   for (let wy = wordimg.height - 1; wy >= 0; --wy) {
     let widx = wy * wordimg.width * 4;
     let widxend = widx + (wordimg.width * 4);
 
     let fidx = ((y + wy) * fullimg.width + x) * 4;
     for (; widx < widxend; widx += 4, fidx += 4) {
 
@@ -217,176 +241,193 @@ function hitTest(fullimg, wordimg, x, y)
 
       if (wv && fv)
 	return true;
     }
   }
   return false;
 }
 
-function slideUntilHit(fullimg, wordimg, x, y, dx, dy, backoff)
+function slide(fullimg, wordimg, x1, y1, x2, y2)
 {
-  if (kParanoid) {
-    if (hitTest(fullimg, wordimg, x, y)) {
-      throw new Error("slideUntilHit hitting at start!");
-    }
+  let hitting = hitTest(fullimg, wordimg, x1, y1);
+
+  setStatus("started slide hitting: " + hitting);
+
+  let steps = Math.max(Math.abs(x2 - x1), Math.abs(y2 - y1));
+
+  for (let step = 0; step < steps; ++step) {
+    let ix = Math.floor(x1 + (x2 - x1) * step / steps);
+    let iy = Math.floor(y1 + (y2 - y1) * step / steps);
+    if (hitTest(fullimg, wordimg, ix, iy) != hitting)
+      return [ix, iy];
   }
 
-  let ix, iy;
-  for (ix = x, iy = y;
-       true;
-       ix += dx, iy += dy) {
-    if (hitTest(fullimg, wordimg, ix, iy))
-      break;
-  }
-
-  // TODO: don't back off past the original starting point!
-  ix += backoff * -dx;
-  iy += backoff * -dy;
-
-  return [ix, iy];
+  return [null, null];
 }
 
 function merge(fullimg, wordimg, x, y)
 {
   let wdata = wordimg.data;
   let fdata = fullimg.data;
 
   if (x < 0 || y < 0 ||
       x + wordimg.width > fullimg.width ||
       y + wordimg.height > fullimg.height)
-    throw new Error("Bad args to hitTest");
+    throw new Error("Bad args to merge");
 
   for (let wy = wordimg.height - 1; wy >= 0; --wy) {
     let widx = wy * wordimg.width * 4;
     let widxend = widx + (wordimg.width * 4);
 
     let fidx = ((y + wy) * fullimg.width + x) * 4;
     for (; widx < widxend; widx += 4, fidx += 4) {
       if (wdata[widx + 3]) {
 	// if the word bit has opacity
 	fdata[fidx] = wdata[widx];
 	fdata[fidx + 1] = wdata[widx + 1];
 	fdata[fidx + 2] = wdata[widx + 2];
 	fdata[fidx + 3] = wdata[widx + 3];
       }
     }
   }
+
+  if (fullimg.minx === undefined) {
+    fullimg.minx = x;
+    fullimg.miny = y;
+    fullimg.maxx = x + wordimg.width;
+    fullimg.maxy = y + wordimg.height;
+  }
+  else {
+    fullimg.minx = Math.min(fullimg.minx, x);
+    fullimg.miny = Math.min(fullimg.miny, y);
+    fullimg.maxx = Math.max(fullimg.maxx, x + wordimg.width);
+    fullimg.maxy = Math.max(fullimg.maxy, y + wordimg.height);
+  }
 }
 
 function drawWord(cx, word, size, vertical)
 {
-  cx.font = size + 'px Baskerville';
+  cx.font = size + 'px Papyrus';
   let width = cx.measureText(word).width;
 
-  let geth, getw;
+  let geth, getw, _go;
 
   if (vertical) {
     geth = width + size * 2;
     getw = size * 3;
 
-    if (geth > kHeight)
-      throw new Error("Word is just too long, vertically");
-
-    cx.clearRect(0, 0, getw, geth);
+    cx.textAlign = 'right';
 
-    cx.textAlign = 'right';
-    cx.save();
-    cx.translate(size, size);
-    cx.rotate(270 * Math.PI / 180);
-    cx.fillText(word, 0, 0);
-    cx.restore();
-
+    _go = function() {
+      cx.save();
+      cx.translate(size, size);
+      cx.rotate(270 * Math.PI / 180);
+      cx.fillText(word, 0, 0);
+      cx.restore();
+    }
   }
   else {
     geth = size * 3;
     getw = width + size * 2;
 
-    cx.clearRect(0, 0, getw, geth);
+    cx.textAlign = 'left';
 
-    cx.textAlign = 'left';
-    cx.fillText(word, size, size);
+    _go = function() {
+      cx.fillText(word, size, size);
+    }
   }
 
-  return cx.getImageData(0, 0, getw, geth);
+  cx.clearRect(0, 0, getw, geth);
+  cx.shadowBlur = 0;
+  _go();
+  let noshadow = cx.getImageData(0, 0, getw, geth);
+
+  cx.clearRect(0, 0, getw, geth);
+  cx.shadowBlur = size / 4;
+  _go();
+  let shadow = cx.getImageData(0, 0, getw, geth);
+
+  return [noshadow, shadow];
 }
 
 function simpletest(cx, imgdata)
 {
   let fontsize = 80;
 
-  let wdata = drawWord(cx, 'Happiness', 80, false);
+  let [wdata, wshadow] = drawWord(cx, 'Happiness', 80, false);
   merge(imgdata, wdata, 10, 10);
 
-  wdata = drawWord(cx, 'Sillyness', 60, true);
-  let [x,y] = slideUntilHit(imgdata, wdata, 600, 20, -1, 0, 20);
+  [wdata, wshadow] = drawWord(cx, 'Sillyness', 60, true);
+  let [x,y] = slide(imgdata, wshadow, 600, 20, 0, 20);
   merge(imgdata, wdata, x, y);
 }
 
 function draw()
 {
   statusel.textContent = '';
 
   let words = getWordList();
 
   let cx = $('cc').getContext('2d');
   cx.clearRect(0, 0, kWidth, kHeight);
 
   cx.textBaseline = 'top';
-  cx.fillStyle = "rgb(200, 0, 0, 0.8)";
-
-  let first = true;
-  let word, size;
+  cx.shadowColor = "rgba(0, 0, 0, 0.3)";
 
   let imgdata = cx.getImageData(0, 0, kWidth, kHeight);
 
   /* comment out testing code */
 
-  simpletest(cx, imgdata);
-
-  cx.putImageData(imgdata, 0, 0);
-  return;
+  // simpletest(cx, imgdata);
 
-  let i = 0;
-
-  /* Place the first five words randomly */
-  for (; i < 5; ++i) {
+  let wdata, wshadow, word, size, vertical, x, y;
+  /*
+   * place the first word in a position randomly
+   */ 
+  [word, size] = words[0];
+  vertical = Math.random() < kVerticalRatio * size / 88;
+  [wdata, wshadow] = drawWord(cx, word, size, vertical);
+  x = randomInt(0, kWidth - wdata.width);
+  y = randomInt(0, kHeight - wdata.height);
+  merge(imgdata, wdata, x, y);
+  
+  for (let i = 1; i < 10; ++i) {
     let [word, size] = words[i];
 
-    cx.font = size + 'px Baskerville';
-    let width = cx.measureText(word).width;
+    vertical = Math.random() < kVerticalRatio * size / 88;
+    [wdata, wshadow] = drawWord(cx, word, size, vertical);
 
-    let x, y, vertical;
+    [x, y] = placeWord(imgdata, wshadow);
+    if (x != null)
+      merge(imgdata, wdata, x, y);
+    else
+      setStatus('unable to place word: ' + word);
+  }
 
-    while (true) {
-      vertical = Math.random() < 0.4;
+  cx.putImageData(imgdata, 0, 0);
+}
 
-      let hitw, hith;
-      if (vertical) {
-	hitw = size;
-	hith = width;
-      }
-      else {
-	hitw = width;
-	hith = size;
-      }	
- 
-      x = randomInt(0, kWidth - hitw);
-      y = randomInt(0, kHeight - hith);
+function placeWord(imgdata, wdata)
+{
+  let x = randomInt(0, kWidth - wdata.width);
+  let y = randomInt(0, kHeight - wdata.height);
+  for (let t = 0; t < 20; ++t) {
+    let x2 = randomInt(0, kWidth - wdata.width);
+    let y2 = randomInt(0, kWidth - wdata.height);
 
-      if (!hitTest(x, y, hitw, hith, imgdata))
-	break;
-    }
-    drawWord(cx, word, x, y, vertical, size, width, imgdata);
+    [x, y] = slide(imgdata, wdata, x, y, x2, y2);
+    if (x != null)
+      return [x, y];
 
-    if (kParanoid) {
-      let testdata = cx.getImageData(0, 0, kWidth, kHeight);
-      compareArrays(imgdata.data, testdata.data);
-    }
+    x = x2;
+    y = y2;
   }
+
+  return [null, null];
 }
 
 /*
 
   for each ([word, size] in words) {
     cx.font = size + 'px Utopia';
     let twidth = cx.measureText(word).width;
 
--- a/wordle.html
+++ b/wordle.html
@@ -1,19 +1,26 @@
 <head>
   <title>Trying to replicate wordle.net</title>
+  <style type="text/css">
+    td {
+      vertical-align: top;
+    }
+  </style>
 </head>
 <body>
   <div>
   <input type="button" value="Get Words" onclick="draw()">
   </div>
 
-  <div>
-  <canvas width="800" height="400" id="cc" style="border: 1px solid black;"></canvas>
-  </div>
+  <table>
+   <tr>
+    <td>
+     <canvas width="800" height="800" id="cc" style="border: 1px solid black;"></canvas>
+    <td>
+     <pre id="status"></pre>
+  </table>
 
   <iframe src="about:blank" id="i" style="display: none"></iframe>
 
-  <pre id="status"></pre>
-
   <script type="application/javascript;version=1.8" src="jswordle.js">
   </script>
 </body>