1 /*
  2     Copyright 2008-2023
  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";
 42 import Const from "./constants";
 43 import Mat from "../math/math";
 44 import GeometryElement from "./element";
 45 import Type from "../utils/type";
 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             penDown: 'penDown',
178             pd: 'penDown',
179             clearScreen: 'clearScreen',
180             cs: 'clearScreen',
181             clean: 'clean',
182             setPos: 'setPos',
183             home: 'home',
184             hideTurtle: 'hideTurtle',
185             ht: 'hideTurtle',
186             showTurtle: 'showTurtle',
187             st: 'showTurtle',
188             penSize: 'setPenSize',
189             penColor: 'setPenColor',
190             getPenColor: 'getPenColor',
191             getHighlightPenColor: 'getHighlightPenColor',
192             getPenSize: 'getPenSize',
193             pushTurtle: 'pushTurtle',
194             push: 'pushTurtle',
195             popTurtle: 'popTurtle',
196             pop: 'popTurtle',
197             lookTo: 'lookTo',
198             pos: 'pos',
199             moveTo: 'moveTo',
200             X: 'X',
201             Y: 'Y'
202         });
203 
204     return this;
205 };
206 
207 JXG.Turtle.prototype = new GeometryElement();
208 
209 JXG.extend(
210     JXG.Turtle.prototype,
211     /** @lends JXG.Turtle.prototype */ {
212         /**
213          * Initialize a new turtle or reinitialize a turtle after {@link JXG.Turtle#clearScreen}.
214          * @private
215          */
216         init: function (x, y, dir) {
217             var hiddenPointAttr = {
218                 fixed: true,
219                 name: "",
220                 visible: false,
221                 withLabel: false
222             };
223 
224             this.arrowLen =
225                 20 / Mat.hypot(this.board.unitX, this.board.unitY);
226 
227             this.pos = [x, y];
228             this.isPenDown = true;
229             this.dir = 90;
230             this.stack = [];
231             this.objects = [];
232             this.curve = this.board.create(
233                 "curve",
234                 [[this.pos[0]], [this.pos[1]]],
235                 this._attributes
236             );
237             this.objects.push(this.curve);
238 
239             this.turtle = this.board.create("point", this.pos, hiddenPointAttr);
240             this.objects.push(this.turtle);
241 
242             this.turtle2 = this.board.create(
243                 "point",
244                 [this.pos[0], this.pos[1] + this.arrowLen],
245                 hiddenPointAttr
246             );
247             this.objects.push(this.turtle2);
248 
249             this.visProp.arrow.lastArrow = true;
250             this.visProp.arrow.straightFirst = false;
251             this.visProp.arrow.straightLast = false;
252             this.arrow = this.board.create(
253                 "line",
254                 [this.turtle, this.turtle2],
255                 this.visProp.arrow
256             );
257             this.objects.push(this.arrow);
258 
259             this.subs = {
260                 arrow: this.arrow
261             };
262             this.inherits.push(this.arrow);
263 
264             this.right(90 - dir);
265             this.board.update();
266         },
267 
268         /**
269          * Move the turtle forward.
270          * @param {Number} len of forward move in user coordinates
271          * @returns {JXG.Turtle} pointer to the turtle object
272          */
273         forward: function (len) {
274             if (len === 0) {
275                 return this;
276             }
277 
278             var t,
279                 dx = len * Math.cos((this.dir * Math.PI) / 180),
280                 dy = len * Math.sin((this.dir * Math.PI) / 180);
281 
282             if (!this.turtleIsHidden) {
283                 t = this.board.create("transform", [dx, dy], { type: "translate" });
284 
285                 t.applyOnce(this.turtle);
286                 t.applyOnce(this.turtle2);
287             }
288 
289             if (this.isPenDown) {
290                 // IE workaround
291                 if (this.curve.dataX.length >= 8192) {
292                     this.curve = this.board.create(
293                         "curve",
294                         [[this.pos[0]], [this.pos[1]]],
295                         this._attributes
296                     );
297                     this.objects.push(this.curve);
298                 }
299             }
300 
301             this.pos[0] += dx;
302             this.pos[1] += dy;
303 
304             if (this.isPenDown) {
305                 this.curve.dataX.push(this.pos[0]);
306                 this.curve.dataY.push(this.pos[1]);
307             }
308 
309             this.board.update();
310             return this;
311         },
312 
313         /**
314          * Move the turtle backwards.
315          * @param {Number} len of backwards move in user coordinates
316          * @returns {JXG.Turtle} pointer to the turtle object
317          */
318         back: function (len) {
319             return this.forward(-len);
320         },
321 
322         /**
323          * Rotate the turtle direction to the right
324          * @param {Number} angle of the rotation in degrees
325          * @returns {JXG.Turtle} pointer to the turtle object
326          */
327         right: function (angle) {
328             this.dir -= angle;
329             this.dir %= 360;
330 
331             if (!this.turtleIsHidden) {
332                 var t = this.board.create(
333                     "transform",
334                     [(-angle * Math.PI) / 180, this.turtle],
335                     { type: "rotate" }
336                 );
337                 t.applyOnce(this.turtle2);
338             }
339 
340             this.board.update();
341             return this;
342         },
343 
344         /**
345          * Rotate the turtle direction to the right.
346          * @param {Number} angle of the rotation in degrees
347          * @returns {JXG.Turtle} pointer to the turtle object
348          */
349         left: function (angle) {
350             return this.right(-angle);
351         },
352 
353         /**
354          * Pen up, stops visible drawing
355          * @returns {JXG.Turtle} pointer to the turtle object
356          */
357         penUp: function () {
358             this.isPenDown = false;
359             return this;
360         },
361 
362         /**
363          * Pen down, continues visible drawing
364          * @returns {JXG.Turtle} pointer to the turtle object
365          */
366         penDown: function () {
367             this.isPenDown = true;
368             this.curve = this.board.create(
369                 "curve",
370                 [[this.pos[0]], [this.pos[1]]],
371                 this._attributes
372             );
373             this.objects.push(this.curve);
374 
375             return this;
376         },
377 
378         /**
379          * Removes the turtle curve from the board. The turtle stays in its position.
380          * @returns {JXG.Turtle} pointer to the turtle object
381          */
382         clean: function () {
383             var i, el;
384 
385             for (i = 0; i < this.objects.length; i++) {
386                 el = this.objects[i];
387                 if (el.type === Const.OBJECT_TYPE_CURVE) {
388                     this.board.removeObject(el);
389                     this.objects.splice(i, 1);
390                 }
391             }
392 
393             this.curve = this.board.create(
394                 "curve",
395                 [[this.pos[0]], [this.pos[1]]],
396                 this._attributes
397             );
398             this.objects.push(this.curve);
399             this.board.update();
400 
401             return this;
402         },
403 
404         /**
405          *  Removes the turtle completely and resets it to its initial position and direction.
406          * @returns {JXG.Turtle} pointer to the turtle object
407          */
408         clearScreen: function () {
409             var i,
410                 el,
411                 len = this.objects.length;
412 
413             for (i = 0; i < len; i++) {
414                 el = this.objects[i];
415                 this.board.removeObject(el);
416             }
417 
418             this.init(0, 0, 90);
419             return this;
420         },
421 
422         /**
423          *  Moves the turtle without drawing to a new position
424          * @param {Number} x new x- coordinate
425          * @param {Number} y new y- coordinate
426          * @returns {JXG.Turtle} pointer to the turtle object
427          */
428         setPos: function (x, y) {
429             var t;
430 
431             if (Type.isArray(x)) {
432                 this.pos = x;
433             } else {
434                 this.pos = [x, y];
435             }
436 
437             if (!this.turtleIsHidden) {
438                 this.turtle.setPositionDirectly(Const.COORDS_BY_USER, [x, y]);
439                 this.turtle2.setPositionDirectly(Const.COORDS_BY_USER, [x, y + this.arrowLen]);
440                 t = this.board.create(
441                     "transform",
442                     [(-(this.dir - 90) * Math.PI) / 180, this.turtle],
443                     { type: "rotate" }
444                 );
445                 t.applyOnce(this.turtle2);
446             }
447 
448             this.curve = this.board.create(
449                 "curve",
450                 [[this.pos[0]], [this.pos[1]]],
451                 this._attributes
452             );
453             this.objects.push(this.curve);
454             this.board.update();
455 
456             return this;
457         },
458 
459         /**
460          *  Sets the pen size. Equivalent to setAttribute({strokeWidth:size})
461          * but affects only the future turtle.
462          * @param {Number} size
463          * @returns {JXG.Turtle} pointer to the turtle object
464          */
465         setPenSize: function (size) {
466             //this.visProp.strokewidth = size;
467             this.curve = this.board.create(
468                 "curve",
469                 [[this.pos[0]], [this.pos[1]]],
470                 this.copyAttr("strokeWidth", size)
471             );
472             this.objects.push(this.curve);
473             return this;
474         },
475 
476         /**
477          *  Sets the pen color. Equivalent to setAttribute({strokeColor:color})
478          * but affects only the future turtle.
479          * @param {String} color
480          * @returns {JXG.Turtle} pointer to the turtle object
481          */
482         setPenColor: function (color) {
483             this.curve = this.board.create(
484                 "curve",
485                 [[this.pos[0]], [this.pos[1]]],
486                 this.copyAttr("strokeColor", color)
487             );
488             this.objects.push(this.curve);
489 
490             return this;
491         },
492 
493         /**
494          * Get attribute of the last turtle curve object.
495          *
496          * @param {String} key
497          * @returns attribute value
498          * @private
499          */
500         getPenAttribute: function(key) {
501             var pos, le = this.objects.length;
502             if (le === 4) {
503                 // No new turtle objects have been created
504                 pos = 0;
505             } else {
506                 pos = le - 1;
507             }
508             return Type.evaluate(this.objects[pos].visProp[key]);
509         },
510 
511         /**
512          * Get most recently set turtle size (in pixel).
513          * @returns Number Size of the last turtle segment in pixel.
514          */
515         getPenSize: function() {
516             return this.getPenAttribute('strokewidth');
517         },
518 
519         /**
520          * Get most recently set turtle color.
521          * @returns String RGB color value of the last turtle segment.
522          */
523         getPenColor: function() {
524             return this.getPenAttribute('strokecolor');
525         },
526 
527         /**
528          * Get most recently set turtle color.
529          * @returns String RGB highlight color value of the last turtle segment.
530          */
531          getHighlightPenColor: function() {
532             return this.getPenAttribute('highlightstrokecolor');
533         },
534 
535         /**
536          *  Sets the highlight pen color. Equivalent to setAttribute({highlightStrokeColor:color})
537          * but affects only the future turtle.
538          * @param {String} color
539          * @returns {JXG.Turtle} pointer to the turtle object
540          */
541         setHighlightPenColor: function (color) {
542             //this.visProp.highlightstrokecolor = colStr;
543             this.curve = this.board.create(
544                 "curve",
545                 [[this.pos[0]], [this.pos[1]]],
546                 this.copyAttr("highlightStrokeColor", color)
547             );
548             this.objects.push(this.curve);
549             return this;
550         },
551 
552         /**
553          * Sets properties of the turtle, see also {@link JXG.GeometryElement#setAttribute}.
554          * Sets the property for all curves of the turtle in the past and in the future.
555          * @param {Object} attributes key:value pairs
556          * @returns {JXG.Turtle} pointer to the turtle object
557          */
558         setAttribute: function (attributes) {
559             var i,
560                 el,
561                 tmp,
562                 len = this.objects.length;
563 
564             for (i = 0; i < len; i++) {
565                 el = this.objects[i];
566                 if (el.type === Const.OBJECT_TYPE_CURVE) {
567                     el.setAttribute(attributes);
568                 }
569             }
570 
571             // Set visProp of turtle
572             tmp = this.visProp.id;
573             this.visProp = Type.deepCopy(this.curve.visProp);
574             this.visProp.id = tmp;
575             this._attributes = Type.deepCopy(this.visProp);
576             delete this._attributes.id;
577 
578             return this;
579         },
580 
581         /**
582          * Set a future attribute of the turtle.
583          * @private
584          * @param {String} key
585          * @param {Number|String} val
586          * @returns {Object} pointer to the attributes object
587          */
588         copyAttr: function (key, val) {
589             this._attributes[key.toLowerCase()] = val;
590             return this._attributes;
591         },
592 
593         /**
594          * Sets the visibility of the turtle head to true,
595          * @returns {JXG.Turtle} pointer to the turtle object
596          */
597         showTurtle: function () {
598             this.turtleIsHidden = false;
599             this.arrow.setAttribute({ visible: true });
600             this.visProp.arrow.visible = false;
601             this.setPos(this.pos[0], this.pos[1]);
602             this.board.update();
603 
604             return this;
605         },
606 
607         /**
608          * Sets the visibility of the turtle head to false,
609          * @returns {JXG.Turtle} pointer to the turtle object
610          */
611         hideTurtle: function () {
612             this.turtleIsHidden = true;
613             this.arrow.setAttribute({ visible: false });
614             this.visProp.arrow.visible = false;
615             this.board.update();
616 
617             return this;
618         },
619 
620         /**
621          * Moves the turtle to position [0,0].
622          * @returns {JXG.Turtle} pointer to the turtle object
623          */
624         home: function () {
625             this.pos = [0, 0];
626             this.setPos(this.pos[0], this.pos[1]);
627 
628             return this;
629         },
630 
631         /**
632          *  Pushes the position of the turtle on the stack.
633          * @returns {JXG.Turtle} pointer to the turtle object
634          */
635         pushTurtle: function () {
636             this.stack.push([this.pos[0], this.pos[1], this.dir]);
637 
638             return this;
639         },
640 
641         /**
642          *  Gets the last position of the turtle on the stack, sets the turtle to this position and removes this
643          * position from the stack.
644          * @returns {JXG.Turtle} pointer to the turtle object
645          */
646         popTurtle: function () {
647             var status = this.stack.pop();
648             this.pos[0] = status[0];
649             this.pos[1] = status[1];
650             this.dir = status[2];
651             this.setPos(this.pos[0], this.pos[1]);
652 
653             return this;
654         },
655 
656         /**
657          * Rotates the turtle into a new direction.
658          * There are two possibilities:
659          * @param {Number|Array} target If a number is given, it is interpreted as the new direction to look to; If an array
660          * consisting of two Numbers is given targeted is used as a pair coordinates.
661          * @returns {JXG.Turtle} pointer to the turtle object
662          */
663         lookTo: function (target) {
664             var ax, ay, bx, by, beta;
665 
666             if (Type.isArray(target)) {
667                 ax = this.pos[0];
668                 ay = this.pos[1];
669                 bx = target[0];
670                 by = target[1];
671 
672                 // Rotate by the slope of the line [this.pos, target]
673                 beta = Math.atan2(by - ay, bx - ax);
674                 this.right(this.dir - (beta * 180) / Math.PI);
675             } else if (Type.isNumber(target)) {
676                 this.right(this.dir - target);
677             }
678             return this;
679         },
680 
681         /**
682          * Moves the turtle to a given coordinate pair.
683          * The direction is not changed.
684          * @param {Array} target Coordinates of the point where the turtle looks to.
685          * @returns {JXG.Turtle} pointer to the turtle object
686          */
687         moveTo: function (target) {
688             var dx, dy, t;
689 
690             if (Type.isArray(target)) {
691                 dx = target[0] - this.pos[0];
692                 dy = target[1] - this.pos[1];
693 
694                 if (!this.turtleIsHidden) {
695                     t = this.board.create("transform", [dx, dy], { type: "translate" });
696                     t.applyOnce(this.turtle);
697                     t.applyOnce(this.turtle2);
698                 }
699 
700                 if (this.isPenDown) {
701                     // IE workaround
702                     if (this.curve.dataX.length >= 8192) {
703                         this.curve = this.board.create(
704                             "curve",
705                             [[this.pos[0]], [this.pos[1]]],
706                             this._attributes
707                         );
708                         this.objects.push(this.curve);
709                     }
710                 }
711 
712                 this.pos[0] = target[0];
713                 this.pos[1] = target[1];
714 
715                 if (this.isPenDown) {
716                     this.curve.dataX.push(this.pos[0]);
717                     this.curve.dataY.push(this.pos[1]);
718                 }
719                 this.board.update();
720             }
721 
722             return this;
723         },
724 
725         /**
726          * Alias for {@link JXG.Turtle#forward}
727          */
728         fd: function (len) {
729             return this.forward(len);
730         },
731         /**
732          * Alias for {@link JXG.Turtle#back}
733          */
734         bk: function (len) {
735             return this.back(len);
736         },
737         /**
738          * Alias for {@link JXG.Turtle#left}
739          */
740         lt: function (angle) {
741             return this.left(angle);
742         },
743         /**
744          * Alias for {@link JXG.Turtle#right}
745          */
746         rt: function (angle) {
747             return this.right(angle);
748         },
749         /**
750          * Alias for {@link JXG.Turtle#penUp}
751          */
752         pu: function () {
753             return this.penUp();
754         },
755         /**
756          * Alias for {@link JXG.Turtle#penDown}
757          */
758         pd: function () {
759             return this.penDown();
760         },
761         /**
762          * Alias for {@link JXG.Turtle#hideTurtle}
763          */
764         ht: function () {
765             return this.hideTurtle();
766         },
767         /**
768          * Alias for {@link JXG.Turtle#showTurtle}
769          */
770         st: function () {
771             return this.showTurtle();
772         },
773         /**
774          * Alias for {@link JXG.Turtle#clearScreen}
775          */
776         cs: function () {
777             return this.clearScreen();
778         },
779         /**
780          * Alias for {@link JXG.Turtle#pushTurtle}
781          */
782         push: function () {
783             return this.pushTurtle();
784         },
785         /**
786          * Alias for {@link JXG.Turtle#popTurtle}
787          */
788         pop: function () {
789             return this.popTurtle();
790         },
791 
792         /**
793          * The "co"-coordinate of the turtle curve at position t is returned.
794          *
795          * @param {Number} t parameter
796          * @param {String} co. Either 'X' or 'Y'.
797          * @returns {Number} x-coordinate of the turtle position or x-coordinate of turtle at position t
798          */
799         evalAt: function (t, co) {
800             var i,
801                 j,
802                 el,
803                 tc,
804                 len = this.objects.length;
805 
806             for (i = 0, j = 0; i < len; i++) {
807                 el = this.objects[i];
808 
809                 if (el.elementClass === Const.OBJECT_CLASS_CURVE) {
810                     if (j <= t && t < j + el.numberPoints) {
811                         tc = t - j;
812                         return el[co](tc);
813                     }
814                     j += el.numberPoints;
815                 }
816             }
817 
818             return this[co]();
819         },
820 
821         /**
822          * if t is not supplied the x-coordinate of the turtle is returned. Otherwise
823          * the x-coordinate of the turtle curve at position t is returned.
824          * @param {Number} t parameter
825          * @returns {Number} x-coordinate of the turtle position or x-coordinate of turtle at position t
826          */
827         X: function (t) {
828             if (!Type.exists(t)) {
829                 return this.pos[0];
830             }
831 
832             return this.evalAt(t, "X");
833         },
834 
835         /**
836          * if t is not supplied the y-coordinate of the turtle is returned. Otherwise
837          * the y-coordinate of the turtle curve at position t is returned.
838          * @param {Number} t parameter
839          * @returns {Number} x-coordinate of the turtle position or x-coordinate of turtle at position t
840          */
841         Y: function (t) {
842             if (!Type.exists(t)) {
843                 return this.pos[1];
844             }
845             return this.evalAt(t, "Y");
846         },
847 
848         /**
849          * @returns {Number} z-coordinate of the turtle position
850          */
851         Z: function (t) {
852             return 1.0;
853         },
854 
855         /**
856          * Gives the lower bound of the parameter if the turtle is treated as parametric curve.
857          */
858         minX: function () {
859             return 0;
860         },
861 
862         /**
863          * Gives the upper bound of the parameter if the turtle is treated as parametric curve.
864          * May be overwritten in @see generateTerm.
865          */
866         maxX: function () {
867             var i,
868                 el,
869                 len = this.objects.length,
870                 np = 0;
871 
872             for (i = 0; i < len; i++) {
873                 el = this.objects[i];
874                 if (el.elementClass === Const.OBJECT_CLASS_CURVE) {
875                     np += this.objects[i].numberPoints;
876                 }
877             }
878             return np;
879         },
880 
881         /**
882          * Checks whether (x,y) is near the curve.
883          * @param {Number} x Coordinate in x direction, screen coordinates.
884          * @param {Number} y Coordinate in y direction, screen coordinates.
885          * @returns {Boolean} True if (x,y) is near the curve, False otherwise.
886          */
887         hasPoint: function (x, y) {
888             var i, el;
889 
890             // run through all curves of this turtle
891             for (i = 0; i < this.objects.length; i++) {
892                 el = this.objects[i];
893 
894                 if (el.type === Const.OBJECT_TYPE_CURVE) {
895                     if (el.hasPoint(x, y)) {
896                         // So what??? All other curves have to be notified now (for highlighting)
897                         return true;
898                         // This has to be done, yet.
899                     }
900                 }
901             }
902             return false;
903         }
904     }
905 );
906 
907 /**
908  * @class This element is used to provide a constructor for a turtle.
909  * @pseudo
910  * @description  Creates a new turtle
911  * @name Turtle
912  * @augments JXG.Turtle
913  * @constructor
914  * @type JXG.Turtle
915  *
916  * @param {JXG.Board} board The board the turtle is put on.
917  * @param {Array} parents
918  * @param {Object} attributes Object containing properties for the element such as stroke-color and visibility. See {@link JXG.GeometryElement#setAttribute}
919  * @returns {JXG.Turtle} Reference to the created turtle object.
920  */
921 JXG.createTurtle = function (board, parents, attributes) {
922     var attr;
923     parents = parents || [];
924 
925     attr = Type.copyAttributes(attributes, board.options, "turtle");
926     return new JXG.Turtle(board, parents, attr);
927 };
928 
929 JXG.registerElement("turtle", JXG.createTurtle);
930 
931 export default JXG.Turtle;
932 // export default {
933 //     Turtle: JXG.Turtle,
934 //     createTurtle: JXG.createTurtle
935 // };
936