PageRenderTime 115ms CodeModel.GetById 43ms app.highlight 37ms RepoModel.GetById 26ms app.codeStats 1ms

/bamboojoint.js

https://code.google.com/p/bamboojoint/
JavaScript | 449 lines | 337 code | 64 blank | 48 comment | 75 complexity | 9e086abd4bdb58ee3cff05b5ae06e7b2 MD5 | raw file
  1"use strict";
  2
  3// BambooJoint.Js
  4// HTML5 Canvas Goban Renderer
  5//
  6// Copyright (c) 2011 Stack Exchange, Inc.
  7// Permission is hereby granted, free of charge, to any person obtaining a copy
  8// of this software and associated documentation files (the "Software"), to deal
  9// in the Software without restriction, including without limitation the rights
 10// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 11// copies of the Software, and to permit persons to whom the Software is furnished
 12// to do so, subject to the following conditions:
 13// 
 14// The above copyright notice and this permission notice shall be included in
 15// all copies or substantial portions of the Software.
 16// 
 17// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 18// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 19// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 20// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 21// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 22// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 23// SOFTWARE.
 24//
 25// -----------------------------------------------------------------------------
 26//
 27// Contains lyfe.js, Copyright (c) 2011 Benjamin Dumke-von der Ehe
 28// Also licensed under the MIT license
 29// https://bitbucket.org/balpha/lyfe
 30//
 31// -----------------------------------------------------------------------------
 32//
 33// Usage:
 34//
 35// var result = BambooJoint.render(source);
 36//
 37// If source is not legal board markup, or if the browser doesn't support the HTML5 canvas, returns null.
 38// Otherwise, returns an object with a property result.canvas, which is a <canvas> DOM element,
 39// properties result.width and result.height that give the dimensions of the canvas in pixels,
 40// and optionally -- if given in the source -- a property result.caption.
 41//
 42// -----------------------------------------------------------------------------
 43
 44window.BambooJoint = (function () {
 45    
 46    if (!document.createElement("canvas").getContext)
 47        return { render: function () { return null; }};
 48    
 49    function parse(text) {
 50        text = text.replace(/\r/g, "\n");
 51        var lines = Generator(text.split(/\n+/)).filter(function (l) { return /\S/.test(l); }).evaluated(),
 52            result = { board: [] },
 53            board = result.board;
 54        
 55        if (lines.any(function (line) { return !/^\$\$/.test(line); })) {
 56            return null;
 57        }
 58        
 59        var firstLine = lines.first(),
 60            coordinates;
 61            
 62        if (!firstLine)
 63            return null;
 64
 65        result.moveDelta = 0;
 66        
 67        // check the first line for options and/or caption
 68        // note that we're not actually replacing (and not storing the result), and that the function gets called at most once
 69        firstLine.replace(/^\$\$(([BW]?)(c?)(\d*)(?:m(\d+))?)(?:\s+(.*)|)$/, function (whole, options, color, coord, size, firstMove, caption) {
 70            
 71            caption = (caption || "").replace(/\s+$/, "");
 72            if (!options.length) { // no options -- is this a regular board markup line, or is there a caption?
 73                
 74                if (!caption.length) // no caption at all -- we're outta here
 75                    return;
 76                
 77                if (!/[^\sOW@QPXB#YZCSTM\d?a-z,*+|_-]/.test(caption)) { // all characters are legal markup
 78                    if (!/\w{2} \w{2}/.test(caption)) // doesn't seem to be words
 79                        return;
 80                }
 81            }
 82            
 83            result.whiteFirst = color === "W";
 84            coordinates = coord === "c";
 85            
 86            var sizeInt = parseInt(size, 10);
 87            if (isFinite(sizeInt))
 88                result.boardSize = sizeInt;
 89                
 90            var firstMoveInt = parseInt(firstMove, 10);
 91            if (isFinite(firstMoveInt))
 92                result.moveDelta = firstMoveInt - 1;
 93                
 94            result.caption = caption;
 95            
 96            lines = lines.skip(1);
 97        });
 98        
 99        var lastRow;
100        
101        lines.forEach(function (line) {
102            if (/^\$\$\s(?:[|+-]\s*){2,}$/.test(line)) { // currently, only full horizontal edges are considered
103                if (lastRow)
104                    lastRow.bottom = true;
105                if (!board.length || board[board.length - 1].length) {
106                    lastRow = [];
107                    board.push(lastRow);
108                }
109                board[board.length - 1].top = true;
110                return;
111            }
112            if (!board.length || board[board.length - 1].length) {
113                lastRow = [];
114                board.push(lastRow);
115            }
116            var pieces = Generator(function () {
117                var l = line.length, c;
118                for (var i = 0; i < l; i++) {
119                    c = line.charAt(i);
120                    if (!/[$\s]/.test(c))
121                        this.yield(c);
122                }
123            });
124                    
125            var lastField,
126                nextIsLeft = false;
127            
128            pieces.forEach(function (piece) {
129                if (/[|+-]/.test(piece)) {
130                    if (lastField)
131                        lastField.right = true;
132                    nextIsLeft = true;
133                    return;
134                }
135                var field = { piece: piece };
136                if (nextIsLeft)
137                    field.left = true;
138                nextIsLeft = false;
139                lastRow.push(field);
140                lastField = field;
141            });
142           
143        });
144        
145        // last row is empty
146        if (board.length && ! board[board.length - 1].length)
147            board.pop();
148        
149        var width = 0, height = 0;
150        if (!board.length) {
151            //console.log("empty")
152            return null;
153        } else {
154            var diff = false,
155                
156            width = Generator(board).map(function (r) { return r.length; }).reduce(function (a, b) {
157                diff = diff || (a !== b);
158                return Math.max(a, b);
159            })
160            height = board.length;
161            if (diff) {
162                return null; // the rows don't have equal widths
163            }
164        }
165        
166        if (coordinates) {
167            var leftEdge = board[0][0].left,
168                rightEdge = board[0][width - 1].right,
169                topEdge = board[0].top,
170                bottomEdge = board[height - 1].bottom;
171                
172            var boardSize = result.boardSize;
173            if (!boardSize) {
174                boardSize = 19;
175                if (leftEdge && rightEdge)
176                    boardSize = width;
177                else if (topEdge && bottomEdge)
178                    boardSize = height;
179            }
180            if (leftEdge)
181                result.leftCoordinate = 0;
182            else if (rightEdge)
183                result.leftCoordinate = boardSize - width;
184            else
185                coordinates = false;
186                
187            if (topEdge)
188                result.topCoordinate = boardSize - 1;
189            else if (bottomEdge)
190                result.topCoordinate = height - 1;
191            else
192                coordinates = false;
193                
194            if (coordinates)
195                result.coordinates = true;
196        }
197        
198        result.width = width;
199        result.height = height;
200        return result;
201    }
202
203    var stoneImages;
204    
205    function createStoneImages() {
206        return {
207            white: createStoneImage("white"),
208            black: createStoneImage("black"),
209            both: createStoneImage("both")
210        };
211    }
212
213    function createStoneImage(color) {
214        var stone, actualColor, angle1, angle2;
215        if (color === "both") {
216            stone = createStoneImage("black");
217            actualColor = "white";
218            angle1 = 0.5 * Math.PI;
219            angle2 = 1.5 * Math.PI;
220        } else {
221            stone = document.createElement("canvas");
222            stone.width = 29;
223            stone.height = 29;
224            actualColor = color;
225            angle1 = 0;
226            angle2 = 2 * Math.PI;
227        }
228        var ctx = stone.getContext("2d");
229        
230        ctx.save();
231        ctx.fillStyle = actualColor;
232        if (color !== "both") {
233            ctx.shadowOffsetX = 1;
234            ctx.shadowOffsetY = 1;
235            ctx.shadowBlur = 3;
236            ctx.shadowColor = "rgba(0,0,0,.7)";
237        }
238       
239        ctx.beginPath();
240        ctx.arc(14.5, 14.5, 10, angle1, angle2, false);
241        ctx.fill();
242        ctx.restore();
243
244        // we're doing this in two steps because of a bug in the android browser
245        // http://code.google.com/p/android/issues/detail?id=21813
246        ctx.save();
247        var gradient = ctx.createRadialGradient(14.5, 14.5, 10, 7.5, 7.5, 2);
248        var c1 = actualColor === "white" ? "#e0e0e0" : "black";
249        var c2 = actualColor === "black" ? "#404040" : "white";
250        gradient.addColorStop(0, c1);
251        gradient.addColorStop(.25, c1);
252        gradient.addColorStop(1, c2);
253        ctx.fillStyle = gradient;
254        
255        ctx.beginPath();
256        ctx.arc(14.5, 14.5, 10, angle1, angle2, false);
257        ctx.fill();
258        ctx.restore();
259        return stone;
260    }
261    
262    function renderParsed(parsed) {
263        
264        stoneImages = stoneImages || createStoneImages();
265        
266        var pixWidth = (parsed.width + 1) * 22,
267            pixHeight = (parsed.height + 1) * 22,
268            bgColor = '#d3823b';
269        
270        if (parsed.coordinates) {
271            pixWidth += 6;
272            pixHeight += 6;
273        }
274        
275        var canvas = document.createElement("canvas");
276        canvas.width = pixWidth;
277        canvas.height = pixHeight;
278
279        var ctx = canvas.getContext("2d");
280        ctx.fillStyle = bgColor;
281        ctx.fillRect(0, 0, pixWidth, pixHeight);
282        
283        function stone(x, y, color) {
284            ctx.drawImage(stoneImages[color], x - 14.5, y - 14.5)
285        }
286        
287        function putlines(x, y, top, right, bottom, left) {
288            ctx.beginPath();
289            ctx.moveTo(x - (left ? 0 : 11), y);
290            ctx.lineTo(x + (right ? 0 : 11), y);
291            ctx.moveTo(x, y - (top ? 0 : 11));
292            ctx.lineTo(x, y + (bottom ? 0 : 11));
293            ctx.stroke();
294        }
295        
296        function mark_circle(x, y) {
297            ctx.save();
298            ctx.lineWidth = 2;
299            ctx.strokeStyle = "red";
300            ctx.beginPath();
301            ctx.arc(x, y, 5, 0, 2 * Math.PI, false);
302            ctx.stroke();
303            ctx.restore();
304        }
305        function mark_square(x, y) {
306            ctx.save();
307            ctx.fillStyle = "red";
308            ctx.fillRect(x - 5, y - 5, 10, 10);
309            ctx.restore();
310        }
311        function mark_triangle(x, y) {
312            ctx.save();
313            ctx.fillStyle = "red";
314            ctx.beginPath();
315            ctx.moveTo(x, y - 6);
316            ctx.lineTo(x + 6, y + 4);
317            ctx.lineTo(x - 6, y + 4);
318            ctx.closePath();
319            ctx.fill();
320            ctx.restore();
321        }
322        function mark_x(x, y) {
323            ctx.save();
324            ctx.strokeStyle = "red";
325            ctx.lineWidth = 2;
326            ctx.beginPath();
327            ctx.moveTo(x - 5, y - 5);
328            ctx.lineTo(x + 5, y + 5);
329            ctx.moveTo(x + 5, y - 5);
330            ctx.lineTo(x - 5, y + 5);
331            ctx.stroke();
332            ctx.restore();
333        }
334        
335        var edge = parsed.coordinates ? 28.5 : 22.5;
336        
337        Generator(parsed.board).forEach(function (line, row) {
338            
339            Generator(line).forEach(function (field, col) {
340                var x = col * 22 + edge,
341                    y = row * 22 + edge,
342                    piece = field.piece;
343                    
344                if (piece !== "_")
345                    putlines(x, y, line.top, field.right, line.bottom, field.left);
346                ctx.save();
347                
348                if (/[OW@QP]/.test(piece))
349                    stone(x, y, "white");
350                else if (/[XB#YZ]/.test(piece))
351                    stone(x, y, "black");
352                    
353                if (/[BWC]/.test(piece))
354                    mark_circle(x, y);
355                else if (/[#@S]/.test(piece))
356                    mark_square(x, y);
357                else if (/[YQT]/.test(piece))
358                    mark_triangle(x, y);
359                else if (/[ZPM]/.test(piece))
360                    mark_x(x, y);
361                else if (piece === "*")
362                    stone(x, y, "both");
363                else if (/^\d$/.test(piece)) {
364                    var val = parseInt(piece, 10);
365                    if (val === 0)
366                        val = 10;
367                    var isBlack = (val % 2 === 1) ^ parsed.whiteFirst;
368                    stone(x, y, isBlack ? "black" : "white");
369                    ctx.font = "12px sans-serif";
370                    ctx.textAlign = "center";
371                    ctx.fillStyle = isBlack ? "white" : "black";
372                    ctx.fillText(val + parsed.moveDelta, x, y + 4);
373                } else if (piece === "?") {
374                    ctx.fillStyle = "rgba(255,255,255,0.5)";
375                    
376                    // using integer coordinates here to avoid seeing very thin lines between adjacent shaded points
377                    ctx.fillRect(x - 11.5, y - 11.5, 22, 22);
378                } else if (/^[a-z]$/.test(piece)) {
379                    ctx.font = "bold 15px sans-serif";
380                    ctx.textAlign = "center";
381                    ctx.fillStyle = "black";
382                    ctx.lineWidth = 6;
383                    ctx.strokeStyle = bgColor;
384                    ctx.strokeText(piece, x, y + 5);
385                    ctx.fillText(piece, x, y + 5);
386                    ctx.lineWidth = 1;
387                } else if (piece === ",") {
388                    ctx.fillStyle = "black";
389                    ctx.beginPath();
390                    ctx.arc(x, y, 2.5, 0, 2 * Math.PI, false);
391                    ctx.fill();
392                }
393                ctx.restore();
394            });
395        });
396        
397        if (parsed.coordinates) {
398            ctx.save();
399            ctx.fillStyle = "#6b421e";
400            ctx.font = "10px sans-serif";
401
402            for (var row = 0; row < parsed.height; row++) {
403                ctx.textAlign = "right";
404                ctx.fillText(parsed.topCoordinate - row + 1, 16, row * 22 + 31.5);
405            }
406            for (var col = 0; col < parsed.width; col++) {
407                ctx.textAlign = "center";
408                
409                var letter = parsed.leftCoordinate + col + 1;
410                if (letter >= 9) // skip "I"
411                    letter++;
412                
413                ctx.fillText(String.fromCharCode(64 + letter), col * 22 + 28.5, 12);
414            }
415            ctx.restore();
416        }
417        
418        return {
419            canvas: canvas,
420            width: pixWidth,
421            height: pixHeight
422        };
423    }
424    
425    return {
426        render: function (source) {
427            var parsed = parse(source);
428            
429            if (!parsed)
430                return null;
431                
432            var result = renderParsed(parsed);
433                
434            if (parsed.caption && parsed.caption.length)
435                result.caption = parsed.caption;
436            
437            return result;
438        }
439    }
440})();
441
442// lyfe.js
443(function(){var k;k=Array.prototype.indexOf?function(a,b){return a.indexOf(b)}:function(a,b){for(var c=a.length,d=0;d<c;d++)if(d in a&&a[d]===b)return d;return-1};var h={},e=function(a){if(!(this instanceof e))return new e(a);this.forEach=typeof a==="function"?i(a):a.constructor===Array?p(a):q(a)},l=function(){throw h;},j=function(a){this.message=a;this.name="IterationError"};j.prototype=Error.prototype;var i=function(a){return function(b,c){var d=!1,f=0,m={yield:function(a){if(d)throw new j("yield after end of iteration");
444a=b.call(c,a,f,l);f++;return a},yieldMany:function(a){(a instanceof e?a:new e(a)).forEach(function(a){m.yield(a)})},stop:l};try{a.call(m)}catch(g){if(g!==h)throw g;}finally{d=!0}}},p=function(a){return i(function(){for(var b=a.length,c=0;c<b;c++)c in a&&this.yield(a[c])})},q=function(a){return i(function(){for(var b in a)a.hasOwnProperty(b)&&this.yield([b,a[b]])})};e.prototype={toArray:function(){var a=[];this.forEach(function(b){a.push(b)});return a},filter:function(a,b){var c=this;return new e(function(){var d=
445this;c.forEach(function(c){a.call(b,c)&&d.yield(c)})})},take:function(a){var b=this;return new e(function(){var c=this;b.forEach(function(b,f){f>=a&&c.stop();c.yield(b)})})},skip:function(a){var b=this;return new e(function(){var c=this;b.forEach(function(b,f){f>=a&&c.yield(b)})})},map:function(a,b){var c=this;return new e(function(){var d=this;c.forEach(function(c){d.yield(a.call(b,c))})})},zipWithArray:function(a,b){typeof b==="undefined"&&(b=function(a,b){return[a,b]});var c=this;return new e(function(){var d=
446a.length,f=this;c.forEach(function(c,e){e>=d&&f.stop();f.yield(b(c,a[e]))})})},reduce:function(a,b){var c,d;arguments.length<2?c=!0:(c=!1,d=b);this.forEach(function(b){c?(d=b,c=!1):d=a(d,b)});return d},and:function(a){var b=this;return new e(function(){this.yieldMany(b);this.yieldMany(a)})},takeWhile:function(a){var b=this;return new e(function(){var c=this;b.forEach(function(b){a(b)?c.yield(b):c.stop()})})},skipWhile:function(a){var b=this;return new e(function(){var c=this,d=!0;b.forEach(function(b){(d=
447d&&a(b))||c.yield(b)})})},all:function(a){var b=!0;this.forEach(function(c,d,e){if(!(a?a(c):c))b=!1,e()});return b},any:function(a){var b=!1;this.forEach(function(c,d,e){if(a?a(c):c)b=!0,e()});return b},first:function(){var a;this.forEach(function(b,c,d){a=b;d()});return a},groupBy:function(a){var b=this;return new e(function(){var c=[],d=[];b.forEach(function(b){var e=a(b),g=k(c,e);g===-1?(c.push(e),d.push([b])):d[g].push(b)});this.yieldMany((new e(c)).zipWithArray(d,function(a,b){var c=new e(b);
448c.key=a;return c}))})},evaluated:function(){return new e(this.toArray())},except:function(a){return this.filter(function(b){return b!==a})},sortBy:function(a){var b=this;return new e(function(){var c=b.toArray(),d=n(0,c.length).toArray(),f=this;d.sort(function(b,e){var d=a(c[b]),f=a(c[e]);if(typeof d===typeof f){if(d===f)return b<e?-1:1;if(d<f)return-1;if(d>f)return 1}throw new TypeError("cannot compare "+d+" and "+f);});(new e(d)).forEach(function(a){f.yield(c[a])})})}};var o=function(a,b){var c=
449a;typeof b==="undefined"&&(b=1);return new e(function(){for(;;)this.yield(c),c+=b})},n=function(a,b){return o(a,1).take(b)};window.Generator=e;e.BreakIteration=h;e.Count=o;e.Range=n;e.IterationError=j})();