1 /*
  2     Copyright 2008-2024
  3         Matthias Ehmann,
  4         Michael Gerhaeuser,
  5         Carsten Miller,
  6         Bianca Valentin,
  7         Alfred Wassermann,
  8         Peter Wilfahrt
  9 
 10     This file is part of JSXGraph.
 11 
 12     JSXGraph is free software dual licensed under the GNU LGPL or MIT License.
 13 
 14     You can redistribute it and/or modify it under the terms of the
 15 
 16       * GNU Lesser General Public License as published by
 17         the Free Software Foundation, either version 3 of the License, or
 18         (at your option) any later version
 19       OR
 20       * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT
 21 
 22     JSXGraph is distributed in the hope that it will be useful,
 23     but WITHOUT ANY WARRANTY; without even the implied warranty of
 24     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 25     GNU Lesser General Public License for more details.
 26 
 27     You should have received a copy of the GNU Lesser General Public License and
 28     the MIT License along with JSXGraph. If not, see <https://www.gnu.org/licenses/>
 29     and <https://opensource.org/licenses/MIT/>.
 30  */
 31 
 32 /*global JXG: true, define: true*/
 33 /*jslint nomen: true, plusplus: true*/
 34 
 35 /**
 36  * @fileoverview The JSXGraph object Turtle is defined. It acts like
 37  * "turtle graphics".
 38  * @author A.W.
 39  */
 40 
 41 import JXG from "../jxg.js";
 42 import Const from "./constants.js";
 43 import Mat from "../math/math.js";
 44 import GeometryElement from "./element.js";
 45 import Type from "../utils/type.js";
 46 
 47 /**
 48  * Constructs a new Turtle object.
 49  * @class This is the Turtle class.
 50  * It is derived from {@link JXG.GeometryElement}.
 51  * It stores all properties required
 52  * to move a turtle.
 53  * @constructor
 54  * @param {JXG.Board} board The board the new turtle is drawn on.
 55  * @param {Array} parents Start position and start direction of the turtle. Possible values are
 56  * [x, y, angle]
 57  * [[x, y], angle]
 58  * [x, y]
 59  * [[x, y]]
 60  * @param {Object} attributes Attributes to change the visual properties of the turtle object
 61  * All angles are in degrees.
 62  *
 63  * @example
 64  *
 65  * //creates a figure 8 animation
 66  * var board = JXG.JSXGraph.initBoard('jxgbox',{boundingbox: [-250, 250, 250, -250]});
 67  * var t = board.create('turtle',[0, 0], {strokeOpacity:0.5});
 68  * t.setPenSize(3);
 69  * t.right(90);
 70  * var alpha = 0;
 71  *
 72  * var run = function() {
 73  *  t.forward(2);
 74  *  if (Math.floor(alpha / 360) % 2 === 0) {
 75  *   t.left(1);        // turn left by 1 degree
 76  *  } else {
 77  *   t.right(1);       // turn right by 1 degree
 78  *  }
 79  *  alpha += 1;
 80  *
 81  *  if (alpha < 1440) {  // stop after two rounds
 82  *   setTimeout(run, 20);
 83  *  }
 84  * }
 85  *
 86  *run();
 87  *
 88  * </pre><div class="jxgbox" id="JXG14167b1c-2ad3-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
 89  * <script type="text/javascript">
 90  *     (function() {
 91  *         var brd = JXG.JSXGraph.initBoard('JXG14167b1c-2ad3-11e5-8dd9-901b0e1b8723',
 92  *             {boundingbox: [-250, 250, 250, -250], axis: true, showcopyright: false, shownavigation: false});
 93  *               var t = brd.create('turtle',[0, 0], {strokeOpacity:0.5});
 94  *               t.setPenSize(3);
 95  *               t.right(90);
 96  *               var alpha = 0;
 97  *
 98  *              var run = function() {
 99  *              t.forward(2);
100  *             if (Math.floor(alpha / 360) % 2 === 0) {
101  *                t.left(1);        // turn left by 1 degree
102  *              } else {
103  *                   t.right(1);       // turn right by 1 degree
104  *             }
105  *             alpha += 1;
106  *
107  *             if (alpha < 1440) {  // stop after two rounds
108  *                 setTimeout(run, 20);
109  *               }
110  *             }
111  *
112  *          run();
113  *
114  *     })();
115  *
116  * </script><pre>
117  */
118 JXG.Turtle = function (board, parents, attributes) {
119     var x, y, dir;
120 
121     this.constructor(board, attributes, Const.OBJECT_TYPE_TURTLE, Const.OBJECT_CLASS_OTHER);
122 
123     this.turtleIsHidden = false;
124     this.board = board;
125     this.visProp.curveType = "plot";
126 
127     // Save visProp in this._attributes.
128     // this._attributes is overwritten by setPenSize, setPenColor...
129     // Setting the color or size affects the turtle from the time of
130     // calling the method,
131     // whereas Turtle.setAttribute affects all turtle curves.
132     this._attributes = Type.copyAttributes(this.visProp, board.options, "turtle");
133     delete this._attributes.id;
134 
135     x = 0;
136     y = 0;
137     dir = 90;
138 
139     if (parents.length !== 0) {
140         // [x,y,dir]
141         if (parents.length === 3) {
142             // Only numbers are accepted at the moment
143             x = parents[0];
144             y = parents[1];
145             dir = parents[2];
146         } else if (parents.length === 2) {
147             // [[x,y],dir]
148             if (Type.isArray(parents[0])) {
149                 x = parents[0][0];
150                 y = parents[0][1];
151                 dir = parents[1];
152                 // [x,y]
153             } else {
154                 x = parents[0];
155                 y = parents[1];
156             }
157             // [[x,y]]
158         } else {
159             x = parents[0][0];
160             y = parents[0][1];
161         }
162     }
163 
164     this.init(x, y, dir);
165 
166         this.methodMap = Type.deepCopy(this.methodMap, {
167             forward: 'forward',
168             fd: 'forward',
169             back: 'back',
170             bk: 'back',
171             right: 'right',
172             rt: 'right',
173             left: 'left',
174             lt: 'left',
175             penUp: 'penUp',
176             pu: 'penUp',
177             up: 'penUp',
178             penDown: 'penDown',
179             pd: 'penDown',
180             down: 'penDown',
181             clearScreen: 'clearScreen',
182             cs: 'clearScreen',
183             clean: 'clean',
184             setPos: 'setPos',
185             home: 'home',
186             hideTurtle: 'hideTurtle',
187             ht: 'hideTurtle',
188             hide: 'hideTurtle',
189             showTurtle: 'showTurtle',
190             st: 'showTurtle',
191             show: 'showTurtle',
192             penSize: 'setPenSize',
193             setPenSize: 'setPenSize',
194             penColor: 'setPenColor',
195             setPenColor: 'setPenColor',
196             highlightPenColor: 'setHighlightPenColor',
197             setHighlightPenColor: 'setHighlightPenColor',
198             getPenColor: 'getPenColor',
199             Color: 'getPenColor',
200             getHighlightPenColor: 'getHighlightPenColor',
201             HighlightColor: 'getHighlightPenColor',
202             getPenSize: 'getPenSize',
203             Size: 'getPenSize',
204             pushTurtle: 'pushTurtle',
205             push: 'pushTurtle',
206             popTurtle: 'popTurtle',
207             pop: 'popTurtle',
208             lookTo: 'lookTo',
209             pos: 'pos',
210             Pos: 'pos',
211             moveTo: 'moveTo',
212             X: 'X',
213             Y: 'Y'
214         });
215 
216     return this;
217 };
218 
219 JXG.Turtle.prototype = new GeometryElement();
220 
221 JXG.extend(
222     JXG.Turtle.prototype,
223     /** @lends JXG.Turtle.prototype */ {
224         /**
225          * Initialize a new turtle or reinitialize a turtle after {@link JXG.Turtle#clearScreen}.
226          * @private
227          */
228         init: function (x, y, dir) {
229             var hiddenPointAttr = {
230                 fixed: true,
231                 name: "",
232                 visible: false,
233                 withLabel: false
234             };
235 
236             this.arrowLen =
237                 20 / Mat.hypot(this.board.unitX, this.board.unitY);
238 
239             this.pos = [x, y];
240             this.isPenDown = true;
241             this.dir = 90;
242             this.stack = [];
243             this.objects = [];
244             this.curve = this.board.create(
245                 "curve",
246                 [[this.pos[0]], [this.pos[1]]],
247                 this._attributes
248             );
249             this.objects.push(this.curve);
250 
251             this.turtle = this.board.create("point", this.pos, hiddenPointAttr);
252             this.objects.push(this.turtle);
253 
254             this.turtle2 = this.board.create(
255                 "point",
256                 [this.pos[0], this.pos[1] + this.arrowLen],
257                 hiddenPointAttr
258             );
259             this.objects.push(this.turtle2);
260 
261             this.visProp.arrow.lastArrow = true;
262             this.visProp.arrow.straightFirst = false;
263             this.visProp.arrow.straightLast = false;
264             this.arrow = this.board.create(
265                 "line",
266                 [this.turtle, this.turtle2],
267                 this.visProp.arrow
268             );
269             this.objects.push(this.arrow);
270 
271             this.subs = {
272                 arrow: this.arrow
273             };
274             this.inherits.push(this.arrow);
275 
276             this.right(90 - dir);
277             this.board.update();
278         },
279 
280         /**
281          * Move the turtle forward.
282          * @param {Number} len of forward move in user coordinates
283          * @returns {JXG.Turtle} pointer to the turtle object
284          */
285         forward: function (len) {
286             if (len === 0) {
287                 return this;
288             }
289 
290             var t,
291                 dx = len * Math.cos((this.dir * Math.PI) / 180),
292                 dy = len * Math.sin((this.dir * Math.PI) / 180);
293 
294             if (!this.turtleIsHidden) {
295                 t = this.board.create("transform", [dx, dy], { type: "translate" });
296 
297                 t.applyOnce(this.turtle);
298                 t.applyOnce(this.turtle2);
299             }
300 
301             if (this.isPenDown) {
302                 // IE workaround
303                 if (this.curve.dataX.length >= 8192) {
304                     this.curve = this.board.create(
305                         "curve",
306                         [[this.pos[0]], [this.pos[1]]],
307                         this._attributes
308                     );
309                     this.objects.push(this.curve);
310                 }
311             }
312 
313             this.pos[0] += dx;
314             this.pos[1] += dy;
315 
316             if (this.isPenDown) {
317                 this.curve.dataX.push(this.pos[0]);
318                 this.curve.dataY.push(this.pos[1]);
319             }
320 
321             this.board.update();
322             return this;
323         },
324 
325         /**
326          * Move the turtle backwards.
327          * @param {Number} len of backwards move in user coordinates
328          * @returns {JXG.Turtle} pointer to the turtle object
329          */
330         back: function (len) {
331             return this.forward(-len);
332         },
333 
334         /**
335          * Rotate the turtle direction to the right
336          * @param {Number} angle of the rotation in degrees
337          * @returns {JXG.Turtle} pointer to the turtle object
338          */
339         right: function (angle) {
340             this.dir -= angle;
341             this.dir %= 360;
342 
343             if (!this.turtleIsHidden) {
344                 var t = this.board.create(
345                     "transform",
346                     [(-angle * Math.PI) / 180, this.turtle],
347                     { type: "rotate" }
348                 );
349                 t.applyOnce(this.turtle2);
350             }
351 
352             this.board.update();
353             return this;
354         },
355 
356         /**
357          * Rotate the turtle direction to the right.
358          * @param {Number} angle of the rotation in degrees
359          * @returns {JXG.Turtle} pointer to the turtle object
360          */
361         left: function (angle) {
362             return this.right(-angle);
363         },
364 
365         /**
366          * Pen up, stops visible drawing
367          * @returns {JXG.Turtle} pointer to the turtle object
368          */
369         penUp: function () {
370             this.isPenDown = false;
371             return this;
372         },
373 
374         /**
375          * Pen down, continues visible drawing
376          * @returns {JXG.Turtle} pointer to the turtle object
377          */
378         penDown: function () {
379             this.isPenDown = true;
380             this.curve = this.board.create(
381                 "curve",
382                 [[this.pos[0]], [this.pos[1]]],
383                 this._attributes
384             );
385             this.objects.push(this.curve);
386 
387             return this;
388         },
389 
390         /**
391          * Removes the turtle curve from the board. The turtle stays in its position.
392          * @returns {JXG.Turtle} pointer to the turtle object
393          */
394         clean: function () {
395             var i, el;
396 
397             for (i = 0; i < this.objects.length; i++) {
398                 el = this.objects[i];
399                 if (el.type === Const.OBJECT_TYPE_CURVE) {
400                     this.board.removeObject(el);
401                     this.objects.splice(i, 1);
402                 }
403             }
404 
405             this.curve = this.board.create(
406                 "curve",
407                 [[this.pos[0]], [this.pos[1]]],
408                 this._attributes
409             );
410             this.objects.push(this.curve);
411             this.board.update();
412 
413             return this;
414         },
415 
416         /**
417          *  Removes the turtle completely and resets it to its initial position and direction.
418          * @returns {JXG.Turtle} pointer to the turtle object
419          */
420         clearScreen: function () {
421             var i,
422                 el,
423                 len = this.objects.length;
424 
425             for (i = 0; i < len; i++) {
426                 el = this.objects[i];
427                 this.board.removeObject(el);
428             }
429 
430             this.init(0, 0, 90);
431             return this;
432         },
433 
434         /**
435          *  Moves the turtle without drawing to a new position
436          * @param {Number} x new x- coordinate
437          * @param {Number} y new y- coordinate
438          * @returns {JXG.Turtle} pointer to the turtle object
439          */
440         setPos: function (x, y) {
441             var t;
442 
443             if (Type.isArray(x)) {
444                 this.pos = x;
445             } else {
446                 this.pos = [x, y];
447             }
448 
449             if (!this.turtleIsHidden) {
450                 this.turtle.setPositionDirectly(Const.COORDS_BY_USER, [x, y]);
451                 this.turtle2.setPositionDirectly(Const.COORDS_BY_USER, [x, y + this.arrowLen]);
452                 t = this.board.create(
453                     "transform",
454                     [(-(this.dir - 90) * Math.PI) / 180, this.turtle],
455                     { type: "rotate" }
456                 );
457                 t.applyOnce(this.turtle2);
458             }
459 
460             this.curve = this.board.create(
461                 "curve",
462                 [[this.pos[0]], [this.pos[1]]],
463                 this._attributes
464             );
465             this.objects.push(this.curve);
466             this.board.update();
467 
468             return this;
469         },
470 
471         /**
472          *  Sets the pen size. Equivalent to setAttribute({strokeWidth:size})
473          * but affects only the future turtle.
474          * @param {Number} size
475          * @returns {JXG.Turtle} pointer to the turtle object
476          */
477         setPenSize: function (size) {
478             //this.visProp.strokewidth = size;
479             this.curve = this.board.create(
480                 "curve",
481                 [[this.pos[0]], [this.pos[1]]],
482                 this.copyAttr("strokeWidth", size)
483             );
484             this.objects.push(this.curve);
485             return this;
486         },
487 
488         /**
489          *  Sets the pen color. Equivalent to setAttribute({strokeColor:color})
490          * but affects only the future turtle.
491          * @param {String} color
492          * @returns {JXG.Turtle} pointer to the turtle object
493          */
494         setPenColor: function (color) {
495             this.curve = this.board.create(
496                 "curve",
497                 [[this.pos[0]], [this.pos[1]]],
498                 this.copyAttr("strokeColor", color)
499             );
500             this.objects.push(this.curve);
501 
502             return this;
503         },
504 
505         /**
506          * Get attribute of the last turtle curve object.
507          *
508          * @param {String} key
509          * @returns attribute value
510          * @private
511          */
512         getPenAttribute: function(key) {
513             var pos, le = this.objects.length;
514             if (le === 4) {
515                 // No new turtle objects have been created
516                 pos = 0;
517             } else {
518                 pos = le - 1;
519             }
520             return Type.evaluate(this.objects[pos].visProp[key]);
521         },
522 
523         /**
524          * Get most recently set turtle size (in pixel).
525          * @returns Number Size of the last turtle segment in pixel.
526          */
527         getPenSize: function() {
528             return this.getPenAttribute('strokewidth');
529         },
530 
531         /**
532          * Get most recently set turtle color.
533          * @returns String RGB color value of the last turtle segment.
534          */
535         getPenColor: function() {
536             return this.getPenAttribute('strokecolor');
537         },
538 
539         /**
540          * Get most recently set turtle color.
541          * @returns String RGB highlight color value of the last turtle segment.
542          */
543          getHighlightPenColor: function() {
544             return this.getPenAttribute('highlightstrokecolor');
545         },
546 
547         /**
548          *  Sets the highlight pen color. Equivalent to setAttribute({highlightStrokeColor:color})
549          * but affects only the future turtle.
550          * @param {String} color
551          * @returns {JXG.Turtle} pointer to the turtle object
552          */
553         setHighlightPenColor: function (color) {
554             //this.visProp.highlightstrokecolor = colStr;
555             this.curve = this.board.create(
556                 "curve",
557                 [[this.pos[0]], [this.pos[1]]],
558                 this.copyAttr("highlightStrokeColor", color)
559             );
560             this.objects.push(this.curve);
561             return this;
562         },
563 
564         /**
565          * Sets properties of the turtle, see also {@link JXG.GeometryElement#setAttribute}.
566          * Sets the property for all curves of the turtle in the past and in the future.
567          * @param {Object} attributes key:value pairs
568          * @returns {JXG.Turtle} pointer to the turtle object
569          */
570         setAttribute: function (attributes) {
571             var i,
572                 el,
573                 tmp,
574                 len = this.objects.length;
575 
576             for (i = 0; i < len; i++) {
577                 el = this.objects[i];
578                 if (el.type === Const.OBJECT_TYPE_CURVE) {
579                     el.setAttribute(attributes);
580                 }
581             }
582 
583             // Set visProp of turtle
584             tmp = this.visProp.id;
585             this.visProp = Type.deepCopy(this.curve.visProp);
586             this.visProp.id = tmp;
587             this._attributes = Type.deepCopy(this.visProp);
588             delete this._attributes.id;
589 
590             return this;
591         },
592 
593         /**
594          * Set a future attribute of the turtle.
595          * @private
596          * @param {String} key
597          * @param {Number|String} val
598          * @returns {Object} pointer to the attributes object
599          */
600         copyAttr: function (key, val) {
601             this._attributes[key.toLowerCase()] = val;
602             return this._attributes;
603         },
604 
605         /**
606          * Sets the visibility of the turtle head to true,
607          * @returns {JXG.Turtle} pointer to the turtle object
608          */
609         showTurtle: function () {
610             this.turtleIsHidden = false;
611             this.arrow.setAttribute({ visible: true });
612             this.visProp.arrow.visible = false;
613             this.setPos(this.pos[0], this.pos[1]);
614             this.board.update();
615 
616             return this;
617         },
618 
619         /**
620          * Sets the visibility of the turtle head to false,
621          * @returns {JXG.Turtle} pointer to the turtle object
622          */
623         hideTurtle: function () {
624             this.turtleIsHidden = true;
625             this.arrow.setAttribute({ visible: false });
626             this.visProp.arrow.visible = false;
627             this.board.update();
628 
629             return this;
630         },
631 
632         /**
633          * Moves the turtle to position [0,0].
634          * @returns {JXG.Turtle} pointer to the turtle object
635          */
636         home: function () {
637             this.pos = [0, 0];
638             this.setPos(this.pos[0], this.pos[1]);
639 
640             return this;
641         },
642 
643         /**
644          *  Pushes the position of the turtle on the stack.
645          * @returns {JXG.Turtle} pointer to the turtle object
646          */
647         pushTurtle: function () {
648             this.stack.push([this.pos[0], this.pos[1], this.dir]);
649 
650             return this;
651         },
652 
653         /**
654          *  Gets the last position of the turtle on the stack, sets the turtle to this position and removes this
655          * position from the stack.
656          * @returns {JXG.Turtle} pointer to the turtle object
657          */
658         popTurtle: function () {
659             var status = this.stack.pop();
660             this.pos[0] = status[0];
661             this.pos[1] = status[1];
662             this.dir = status[2];
663             this.setPos(this.pos[0], this.pos[1]);
664 
665             return this;
666         },
667 
668         /**
669          * Rotates the turtle into a new direction.
670          * There are two possibilities:
671          * @param {Number|Array} target If a number is given, it is interpreted as the new direction to look to; If an array
672          * consisting of two Numbers is given targeted is used as a pair coordinates.
673          * @returns {JXG.Turtle} pointer to the turtle object
674          */
675         lookTo: function (target) {
676             var ax, ay, bx, by, beta;
677 
678             if (Type.isArray(target)) {
679                 ax = this.pos[0];
680                 ay = this.pos[1];
681                 bx = target[0];
682                 by = target[1];
683 
684                 // Rotate by the slope of the line [this.pos, target]
685                 beta = Math.atan2(by - ay, bx - ax);
686                 this.right(this.dir - (beta * 180) / Math.PI);
687             } else if (Type.isNumber(target)) {
688                 this.right(this.dir - target);
689             }
690             return this;
691         },
692 
693         /**
694          * Moves the turtle to a given coordinate pair.
695          * The direction is not changed.
696          * @param {Array} target Coordinates of the point where the turtle looks to.
697          * @returns {JXG.Turtle} pointer to the turtle object
698          */
699         moveTo: function (target) {
700             var dx, dy, t;
701 
702             if (Type.isArray(target)) {
703                 dx = target[0] - this.pos[0];
704                 dy = target[1] - this.pos[1];
705 
706                 if (!this.turtleIsHidden) {
707                     t = this.board.create("transform", [dx, dy], { type: "translate" });
708                     t.applyOnce(this.turtle);
709                     t.applyOnce(this.turtle2);
710                 }
711 
712                 if (this.isPenDown) {
713                     // IE workaround
714                     if (this.curve.dataX.length >= 8192) {
715                         this.curve = this.board.create(
716                             "curve",
717                             [[this.pos[0]], [this.pos[1]]],
718                             this._attributes
719                         );
720                         this.objects.push(this.curve);
721                     }
722                 }
723 
724                 this.pos[0] = target[0];
725                 this.pos[1] = target[1];
726 
727                 if (this.isPenDown) {
728                     this.curve.dataX.push(this.pos[0]);
729                     this.curve.dataY.push(this.pos[1]);
730                 }
731                 this.board.update();
732             }
733 
734             return this;
735         },
736 
737         /**
738          * Alias for {@link JXG.Turtle#forward}
739          */
740         fd: function (len) {
741             return this.forward(len);
742         },
743         /**
744          * Alias for {@link JXG.Turtle#back}
745          */
746         bk: function (len) {
747             return this.back(len);
748         },
749         /**
750          * Alias for {@link JXG.Turtle#left}
751          */
752         lt: function (angle) {
753             return this.left(angle);
754         },
755         /**
756          * Alias for {@link JXG.Turtle#right}
757          */
758         rt: function (angle) {
759             return this.right(angle);
760         },
761         /**
762          * Alias for {@link JXG.Turtle#penUp}
763          */
764         pu: function () {
765             return this.penUp();
766         },
767         /**
768          * Alias for {@link JXG.Turtle#penDown}
769          */
770         pd: function () {
771             return this.penDown();
772         },
773         /**
774          * Alias for {@link JXG.Turtle#hideTurtle}
775          */
776         ht: function () {
777             return this.hideTurtle();
778         },
779         /**
780          * Alias for {@link JXG.Turtle#showTurtle}
781          */
782         st: function () {
783             return this.showTurtle();
784         },
785         /**
786          * Alias for {@link JXG.Turtle#clearScreen}
787          */
788         cs: function () {
789             return this.clearScreen();
790         },
791         /**
792          * Alias for {@link JXG.Turtle#pushTurtle}
793          */
794         push: function () {
795             return this.pushTurtle();
796         },
797         /**
798          * Alias for {@link JXG.Turtle#popTurtle}
799          */
800         pop: function () {
801             return this.popTurtle();
802         },
803 
804         /**
805          * The "co"-coordinate of the turtle curve at position t is returned.
806          *
807          * @param {Number} t parameter
808          * @param {String} co. Either 'X' or 'Y'.
809          * @returns {Number} x-coordinate of the turtle position or x-coordinate of turtle at position t
810          */
811         evalAt: function (t, co) {
812             var i,
813                 j,
814                 el,
815                 tc,
816                 len = this.objects.length;
817 
818             for (i = 0, j = 0; i < len; i++) {
819                 el = this.objects[i];
820 
821                 if (el.elementClass === Const.OBJECT_CLASS_CURVE) {
822                     if (j <= t && t < j + el.numberPoints) {
823                         tc = t - j;
824                         return el[co](tc);
825                     }
826                     j += el.numberPoints;
827                 }
828             }
829 
830             return this[co]();
831         },
832 
833         /**
834          * if t is not supplied the x-coordinate of the turtle is returned. Otherwise
835          * the x-coordinate of the turtle curve at position t is returned.
836          * @param {Number} t parameter
837          * @returns {Number} x-coordinate of the turtle position or x-coordinate of turtle at position t
838          */
839         X: function (t) {
840             if (!Type.exists(t)) {
841                 return this.pos[0];
842             }
843 
844             return this.evalAt(t, "X");
845         },
846 
847         /**
848          * if t is not supplied the y-coordinate of the turtle is returned. Otherwise
849          * the y-coordinate of the turtle curve at position t is returned.
850          * @param {Number} t parameter
851          * @returns {Number} x-coordinate of the turtle position or x-coordinate of turtle at position t
852          */
853         Y: function (t) {
854             if (!Type.exists(t)) {
855                 return this.pos[1];
856             }
857             return this.evalAt(t, "Y");
858         },
859 
860         /**
861          * @returns {Number} z-coordinate of the turtle position
862          */
863         Z: function (t) {
864             return 1.0;
865         },
866 
867         /**
868          * Gives the lower bound of the parameter if the turtle is treated as parametric curve.
869          */
870         minX: function () {
871             return 0;
872         },
873 
874         /**
875          * Gives the upper bound of the parameter if the turtle is treated as parametric curve.
876          * May be overwritten in @see generateTerm.
877          */
878         maxX: function () {
879             var i,
880                 el,
881                 len = this.objects.length,
882                 np = 0;
883 
884             for (i = 0; i < len; i++) {
885                 el = this.objects[i];
886                 if (el.elementClass === Const.OBJECT_CLASS_CURVE) {
887                     np += this.objects[i].numberPoints;
888                 }
889             }
890             return np;
891         },
892 
893         /**
894          * Checks whether (x,y) is near the curve.
895          * @param {Number} x Coordinate in x direction, screen coordinates.
896          * @param {Number} y Coordinate in y direction, screen coordinates.
897          * @returns {Boolean} True if (x,y) is near the curve, False otherwise.
898          */
899         hasPoint: function (x, y) {
900             var i, el;
901 
902             // run through all curves of this turtle
903             for (i = 0; i < this.objects.length; i++) {
904                 el = this.objects[i];
905 
906                 if (el.type === Const.OBJECT_TYPE_CURVE) {
907                     if (el.hasPoint(x, y)) {
908                         // So what??? All other curves have to be notified now (for highlighting)
909                         return true;
910                         // This has to be done, yet.
911                     }
912                 }
913             }
914             return false;
915         }
916     }
917 );
918 
919 /**
920  * @class This element is used to provide a constructor for a turtle.
921  * @pseudo
922  * @description  Creates a new turtle
923  * @name Turtle
924  * @augments JXG.Turtle
925  * @constructor
926  * @type JXG.Turtle
927  *
928  * @param {JXG.Board} board The board the turtle is put on.
929  * @param {Array} parents
930  * @param {Object} attributes Object containing properties for the element such as stroke-color and visibility. See {@link JXG.GeometryElement#setAttribute}
931  * @returns {JXG.Turtle} Reference to the created turtle object.
932  */
933 JXG.createTurtle = function (board, parents, attributes) {
934     var attr;
935     parents = parents || [];
936 
937     attr = Type.copyAttributes(attributes, board.options, "turtle");
938     return new JXG.Turtle(board, parents, attr);
939 };
940 
941 JXG.registerElement("turtle", JXG.createTurtle);
942 
943 export default JXG.Turtle;
944 // export default {
945 //     Turtle: JXG.Turtle,
946 //     createTurtle: JXG.createTurtle
947 // };
948