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