import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.Image;
import java.awt.image.MemoryImageSource;
/**
* This class provides support for the *Turtle* (the object displayed which
* is the source of all graphics activity). It provides the actual image of
* the turtle as well as support for all TurtleTalk commands and operators
* which manipulate/query the turtle's state.
*
* @author Guy Haas
*/
public class Turtle extends Component
{
//
// constants
//
public static final int NORTH = 0;
public static final int EAST = 90;
public static final int SOUTH = 180;
public static final int WEST = 270;
public static final int BLACK = 0;
public static final int BLUE = 1;
public static final int GREEN = 2;
public static final int CYAN = 3;
public static final int RED = 4;
public static final int MAGENTA = 5;
public static final int YELLOW = 6;
public static final int WHITE = 7;
public static final int BROWN = 8;
public static final int TAN = 9;
public static final int FOREST = 10;
public static final int AQUA = 11;
public static final int SALMON = 12;
public static final int VIOLET = 13;
public static final int ORANGE = 14;
public static final int GRAY = 15;
public static final int TURTLE = 0;
public static final int ARROW = 1;
public static final int BALL = 2;
public static final int BOX = 3;
public static final int CROSS = 4;
public static final int TRIANGLE = 5;
private static final Color COLORS[] = new Color[16];
static
{
COLORS[ BLACK ] = Color.black;
COLORS[ BLUE ] = Color.blue;
COLORS[ GREEN ] = Color.green;
COLORS[ CYAN ] = Color.cyan;
COLORS[ RED ] = Color.red;
COLORS[ MAGENTA ] = Color.magenta;
COLORS[ YELLOW ] = Color.yellow;
COLORS[ WHITE ] = Color.white;
COLORS[ BROWN ] = new Color(155, 96, 59);
COLORS[ TAN ] = new Color(197, 136, 18);
COLORS[ FOREST ] = new Color(100, 162, 64);
COLORS[ AQUA ] = new Color(120, 187, 187);
COLORS[ SALMON ] = new Color(255, 149, 119);
COLORS[ VIOLET ] = new Color(144, 113, 208);
COLORS[ ORANGE ] = Color.orange;
COLORS[ GRAY ] = Color.lightGray;
};
private static final int INITIAL_PEN_SIZE = 2;
private static final int INITIAL_FONT_SIZE = 14;
private static final int INITIAL_FONT_STYLE = Font.PLAIN;
private static final String INITIAL_FONT_NAME = "Courier";
private static final Font INITIAL_FONT;
static
{
INITIAL_FONT = new Font( INITIAL_FONT_NAME,
INITIAL_FONT_STYLE,
INITIAL_FONT_SIZE
);
}
private static final Color INITIAL_FOREGROUND = Color.black;
// variables with class-wide scope
//
private boolean penDown;
private boolean showTurtle;
private Color curColor;
private float curHeading; // radians in conventional/AWT manner
private Font curFont = INITIAL_FONT;
private Image turtleImage;
private int curPenSize;
private MemoryImageSource turtleImageProducer;
private TGCanvas tgc; // where this turtle draws
private TGPoint curPoint; // current X,Y location of the turtle
private TurtlePixels curTurtlePixels; // current turtle's image
//
// Turtle Constructors
//
public Turtle( TGCanvas tgc )
{
this.tgc = tgc;
curFont = INITIAL_FONT;
curColor = INITIAL_FOREGROUND;
curPenSize = INITIAL_PEN_SIZE;
curPoint = new TGPoint( 0, 0 );
curHeading = (float) (Math.PI / 2.0);
curTurtlePixels = new TurtleTurtle( curColor, curHeading );
penDown = true;
tgc.addTurtle( this );
showTurtle = true;
} // end Turtle()
protected void finalize() throws Throwable
{
super.finalize();
if ( showTurtle )
tgc.removeTurtle( this );
showTurtle = false;
} // end finalize()
private double getRadiansTwds(TGPoint frmPt, TGPoint toPt)
{
double retVal = 0.0;
double frmX = (double) frmPt.xFloatValue();
double frmY = (double) frmPt.yFloatValue();
double toX = (double) toPt.xFloatValue();
double toY = (double) toPt.yFloatValue();
if ( frmX != toX || frmY != toY )
{
if ( frmX == toX )
retVal = (frmY < toY) ? Math.PI/2.0 : Math.PI + Math.PI/2.0;
else
{
double slope = (toY - frmY) / (toX - frmX);
retVal = Math.atan(slope);
if ( retVal == -0.0 )
retVal = 0.0;
if ( frmX > toX )
retVal = retVal + Math.PI;
if ( retVal < 0.0 )
retVal += Math.PI*2;
}
}
return retVal;
} // end getRadiansTwds()
private int rgbToPencolor( int rgbValue )
{
rgbValue &= 0xFFFFFF;
for (int i=0; i < COLORS.length; i++)
if ( (COLORS[i].getRGB() & 0xFFFFFF) == rgbValue )
return( i );
return rgbValue;
} // end rgbToPencolor()
// -----------------------------------------------------------------
// Methods available outside the Turtle class, sorted alphabetically
// -----------------------------------------------------------------
/**
* Move the turtle backwards along its current heading. If the
* pen is currently in the DOWN position, a line is drawn.
*
* Long name for bk(). Both spellings need to provided for
* compatibility.
*
* @param steps Number of pixels (in this implementation) to take.
* @see #bk
*/
public void back( double steps ) { bk( steps ); }
public void back( float steps ) { bk( (double) steps ); }
public void back( int steps ) { bk( (double) steps ); }
/**
* Move the turtle backwards along its current heading. If the
* pen is currently in the DOWN position, a line is drawn.
*
* Abbreviation for back(). Both spellings need to
* provided for compatibility.
*
* @param steps Number of pixels (in this implementation) to take.
* @see #back
*/
public void bk( float steps ) { bk( (double) steps ); }
public void bk( int steps ) { bk( (double) steps ); }
public void bk( double steps )
{
if ( penDown )
curPoint = tgc.drawLine( curPoint,
-steps,
(double) curHeading,
curPenSize,
curColor);
else
curPoint = curPoint.otherEndPoint((double) curHeading, -steps);
if ( penDown || showTurtle )
tgc.repaint();
} // end bk()
/**
* Return the color the Turtle is sitting on
*
* @see #pencolor
* @see #setpc
* @see #setpencolor
*/
public int colorunder()
{
return rgbToPencolor( tgc.colorunder(curPoint) );
} // end colorunder()
/**
* Fill a bounded area in the graphics image.
*
* The current pixel, and any of its neighbors that are the
* same color as it (and any of their neighbors that are the
* same color as it, etc...) are changed to the current color.
*/
public void fill()
{
tgc.fill( curPoint, curColor );
tgc.repaint();
} // end fill()
/**
* Move the turtle forward along its current heading. If the
* pen is currently in the DOWN position, a line is drawn.
*
* Abbreviation for forward(). Both spellings need to
* provided for compatibility.
*
* @param steps Distance in TurtleSpace to move.
* @see #forward
*/
public void fd( float steps ) { fd( (double) steps ); }
public void fd( int steps ) { fd( (double) steps ); }
public void fd( double steps )
{
if ( penDown )
curPoint = tgc.drawLine( curPoint,
steps,
(double) curHeading,
curPenSize,
curColor );
else
curPoint = curPoint.otherEndPoint((double) curHeading, steps);
if ( penDown || showTurtle )
tgc.repaint();
} // end fd()
/**
* Move the turtle forward along its current heading. If the
* pen is currently in the DOWN position, a line is drawn.
*
* Long name for fd(). Both spellings need to provided for
* compatibility.
*
* @param steps Number of pixels (in this implementation) to take.
* @see #fd
*/
public void forward( double steps ) { fd( steps ); }
public void forward( float steps ) { fd( (double) steps ); }
public void forward( int steps ) { fd( (double) steps ); }
/**
* Return the turtle's Image
*/
public Image getImage()
{
while ( turtleImage == null )
{
int[] turtlePixels = curTurtlePixels.getPixels();
int turtleSideSize = curTurtlePixels.getSideSize();
// create turtleImage to match the array of pixels
// AWT Graphics only supports painting of Image objects, no kind
// of BitBlt for arrays of pixel values (?who know's why?)
if ( turtleImageProducer == null )
turtleImageProducer = new MemoryImageSource( turtleSideSize,
turtleSideSize,
turtlePixels,
0,
turtleSideSize
);
turtleImage = createImage( turtleImageProducer );
}
return turtleImage;
} // end getImage()
/**
* Return the size of a side of the turtle's Image
*
* @see #getImage
*/
public int getImageSideSize()
{ return curTurtlePixels.getSideSize(); }
/**
* Return the width of the pen the turtle is currently
* writing with
*
* @see #setpensize
*/
public int pensize()
{ return( curPenSize ); }
/**
* Return the Turtle's heading
*
* @see #seth
* @see #setheading
*/
// Logo's TurtleSpace doesn't match the mathematical
// convention of measuring angles counter-clockwise from
// the positive X axis. Logo defines NORTH (+Y axis) as
// 0 degrees and degrees increase in the clockwise
// direction, so East is 90 degrees, South is 180 degrees
// and West is 270 degrees. The module-wide variable
// curHeading contains the mathematically-correct
// heading in radians, not Logo's point of view. So, we
// must convert curHeading to TurtleSpace's scheme.
//
public int heading()
{
int degrees = (int) Math.rint( curHeading / (Math.PI/180.0) );
int turtleSpaceDegrees = 360 - degrees;
turtleSpaceDegrees += 90;
turtleSpaceDegrees %= 360;
return( turtleSpaceDegrees );
}
/**
* Move the turtle to the center of the display. If the
* pen is currently in the DOWN position, a line is drawn.
*
* Home is equivilent to setxy( 0, 0 )
*
* @see #setxy
*/
public void home()
{
setxy( 0, 0 );
//seth( 0 );
} // end home()
/**
* Hide the turtle; make it invisible.
*
* Abbreviation for hideturtle(). Both spellings need to
* provided for compatibility.
*
* @see #hideturtle
* @see #showturtle
* @see #st
*/
public void ht()
{
if ( showTurtle )
{
tgc.removeTurtle( this );
tgc.repaint();
showTurtle = false;
}
} // end ht()
/**
* Hide the turtle; make it invisible.
*
* Long name for ht(). Both spellings need to provided for
* compatibility.
*
* @see #ht
* @see #showturtle
* @see #st
*/
public void hideturtle() { ht(); }
/**
* Return the current status of the pen.
*
* Return true if the turtle's pen is down or
* false if it in the up position.
*
* @see #pendown
* @see #penup
* @see #pd
* @see #pu
*/
public boolean ispendown() { return penDown; }
/**
* Paints a String of characters on the display.
* The text is painted in the current pen's color,
* starting at the current position of the turtle.
*
* The text is always painted in the standard
* horizontal manner, i.e., the heading of the
* turtle is ignored.
*
* @param text Characters to be painted on the display.
*/
public void label( String text )
{
if ( text != null )
{
tgc.label( text, curPoint, curFont, curColor );
tgc.repaint();
}
} // end label()
/**
* Rotate the turtle counterclockwise by the specified
* angle, measured in degrees.
*
* @param degrees Angle to change turtle's heading by.
* @see #lt
*/
public void left( float degrees ) { lt( degrees ); }
public void left( int degrees ) { lt( (float) degrees ); }
/**
* Rotate the turtle counterclockwise by the specified
* angle, measured in degrees.
*
* Abbreviation for left(). Both spellings need
* to provided for compatibility.
*
* @param degrees Angle to change turtle's heading by.
* @see #left
*/
// Logo degrees increase in the clockwise direction.
// Since this does not match the mathematical convention
// of measuring angles counterclockwise, we must convert
// the parameter.
public void lt( float degrees ) { lt( (double) degrees ); }
public void lt( int degrees ) { lt( (double) degrees ); }
public void lt( double degrees )
{
float radians = (float) (degrees * (Math.PI/180.0));
curHeading += radians;
if ( curHeading > Math.PI * 2.0 )
curHeading -= Math.PI * 2.0;
if ( showTurtle )
if ( curTurtlePixels.setTurtleHeading(curHeading) )
{
turtleImage = null;
tgc.repaint();
}
} // end lt()
/**
* Put the turtle's pen in the down position.
*
* When the turtle moves, it will leave a trace from its
* current position to its destination (its new position).
* @see #ispendown
* @see #pendown
* @see #pu
* @see #penup
*/
public void pd()
{
penDown = true;
}
/**
* Return the color the pen is currently writing in
*
* @see #setpc
* @see #setpencolor
*/
public int pencolor()
{
int rgbVal = curColor.getRGB();
return rgbToPencolor( rgbVal );
} // end pencolor()
/**
* Put the turtle's pen in the down position.
*
* When the turtle moves, it will leave a trace from its
* current position to its destination (its new position).
* @see #ispendown
* @see #pd
* @see #pu
* @see #penup
*/
public void pendown() { pd(); }
/**
* Put the turtle's pen in the up position.
*
* When the turtle moves, it will leave no trace.
* @see #ispendown
* @see #pd
* @see #pendown
* @see #penup
*/
public void pu()
{
penDown = false;
}
/**
* Put the turtle's pen in the up position.
*
* When the turtle moves, it will leave no trace.
* @see #ispendown
* @see #pd
* @see #pendown
* @see #pu
*/
public void penup() { pu(); }
public static Color rgbToColor( int rgbValue )
{
rgbValue &= 0xFFFFFF;
if ( rgbValue < COLORS.length )
return( COLORS[rgbValue] );
return new Color( rgbValue );
} // end rgbToColor()
/**
* Rotate the turtle clockwise by the specified angle,
* measured in degrees.
*
* @param degrees Angle to change turtle heading.
* @see #rt
*/
public void right( double degrees ) { rt( degrees ); }
public void right( float degrees ) { rt( (double) degrees ); }
public void right( int degrees ) { rt( (double) degrees ); }
/**
* Rotate the turtle clockwise by the specified angle,
* measured in degrees.
*
* Abbreviation for right(). Both spellings need
* to provided for compatibility.
*
* @param degrees Angle to change turtle's heading by.
* @see #right
*/
// Logo degrees increase in the clockwise direction.
// Since this does not match the mathematical convention
// of measuring angles counterclockwise, we must convert
// the parameter.
public void rt( float degrees ) { rt( (double) degrees ); }
public void rt( int degrees ) { rt( (double) degrees ); }
public void rt( double degrees )
{
float radians = (float) (degrees * (Math.PI/180.0));
curHeading -= radians;
if ( curHeading < 0.0F )
curHeading += Math.PI * 2.0;
if ( showTurtle )
if ( curTurtlePixels.setTurtleHeading(curHeading) )
{
turtleImage = null;
tgc.repaint();
}
} // end rt()
/**
* Set the size of the text displayed in the graphics area.
* @see #label
*/
public void setlabelheight( int size )
{
if ( curFont.getSize() != size )
curFont = new Font(INITIAL_FONT_NAME, INITIAL_FONT_STYLE, size);
} // end setlabelheight()
/**
* Turns the turtle to the specified absolute heading.
* The heading is specified in degrees (units of 1/360th
* of a circle) with 0 being North (+Y axis), increasing
* clockwise. So, East is 90 degrees, South is 180 degrees,
* and West is 270 degrees.
*
* Abbreviation for setheading(). Both spellings
* need to provided for compatibility.
*
* @param degrees number of 1/360ths increments clockwise
* from the positive Y axis.
* @see #setheading
*/
// Logo defines North (+Y axis) as 0 degrees and degrees
// increase in the clockwise direction, so East is 90
// degrees, South is 180 degrees and West is 270 degrees.
// Since this does not match the mathematical convention
// of measuring angles counterclockwise from the positive
// X axis, we must convert the parameter. The module-wide
// variable (curHeading) contains the mathematically-
// correct heading, not Logo's point of view.
//
public void seth( int turtleSpaceDegrees )
{
int degrees = (360 - (turtleSpaceDegrees % 360));
degrees += 90;
degrees %= 360;
float newHeading = (float) (degrees * (Math.PI/180.0));
curHeading = newHeading;
if ( showTurtle )
if ( curTurtlePixels.setTurtleHeading(curHeading) )
{
turtleImage = null;
tgc.repaint();
}
} // end seth()
/**
* Turns the turtle to the specified absolute heading.
* The heading is specified in degrees (units of 1/360th
* of a circle) with 0 being North (+Y axis), increasing
* clockwise. So, East is 90 degrees, South is 180 degrees,
* and West is 270 degrees.
* @param degrees number of 1/360ths increments clockwise
* from the positive Y axis.
* @see #seth
*/
public void setheading(int degrees) { seth(degrees); }
/**
* Sets the color of the turtle's pen to the supplied number.
* @param colorNum numbers 0-15 are:
*
* Number Color Number Color
* ------ ------- ------ -------
* 0 black 8 brown
* 1 blue 9 tan
* 2 green 10 forest
* 3 cyan 11 aqua
* 4 red 12 salmon
* 5 magenta 13 violet
* 6 yellow 14 orange
* 7 white 15 grey
*
* Color numbers greater than 15 will be assumed to be RGB
* values with the red component in bits 16-23, the green
* component in bits 8-15, and the blue component in bits
* 0-7. The actual color used in rendering will depend on
* finding the best match given the color space available
* for a given display.
* @see #getpencolor
* @see #setpencolor
*/
public void setpc( int colorNum )
{
Color color;
if ( colorNum >= 0 && colorNum <= 15 )
color = COLORS[colorNum];
else
{
colorNum &= 0xFFFFFF;
color = new Color( colorNum );
}
if ( curColor.getRGB() != color.getRGB() )
{
curColor = color;
if ( showTurtle )
if ( curTurtlePixels.setTurtleColor(color) )
{
turtleImage = null;
tgc.repaint();
}
}
} // end setpc()
/**
* Sets the color of the turtle's pen to the supplied number.
* @param colorNum numbers 0-15 are:
*
* Number Color Number Color
* ------ ------- ------ -------
* 0 black 8 brown
* 1 blue 9 tan
* 2 green 10 forest
* 3 cyan 11 aqua
* 4 red 12 salmon
* 5 magenta 13 violet
* 6 yellow 14 orange
* 7 white 15 grey
*
* Color numbers greater than 15 will be assumed to be RGB
* values with the red component in bits 16-23, the green
* component in bits 8-15, and the blue component in bits
* 0-7. The actual color used in rendering will depend on
* finding the best match given the color space available
* for a given display.
* @see #getpencolor
* @see #setpc
*/
public void setpencolor(int colorNum) { setpc(colorNum); }
/**
* Sets the width of the turtle's pen to the supplied number.
* @param width small positive number; 1 (or less) results
* in a single pixel line.
*/
public void setpensize( int width )
{
if ( width == curPenSize )
return;
if ( width < 1 )
curPenSize = 1;
else
curPenSize = width;
} // end setpensize()
/**
* Sets the shape of a turtle - its pixel image.
* @param shapeNum small positive number; 0 for default
* turtle; see constants (e.g., BALL, BOX, etc...) for
* other turtle image shapes...
* @param params an optional int array containing sizing
* information hints, e.g. radius of a ball, ...
*/
public void setshape( int shapeNum, int[] params )
{
TurtlePixels newTurtlePixels = null;
int width = 0;
if ( params != null )
width = params[0];
int height = width;
if ( (params != null) && (params.length >= 2))
height = params[1];
switch ( shapeNum )
{
case TURTLE:
newTurtlePixels = new TurtleTurtle( curColor, curHeading );
break;
case ARROW:
if ( params != null )
newTurtlePixels = new ArrowTurtle( width, height, curColor, curHeading );
else
newTurtlePixels = new ArrowTurtle( curColor, curHeading );
break;
case BALL:
if ( params != null )
newTurtlePixels = new BallTurtle( width, curColor, curHeading );
else
newTurtlePixels = new BallTurtle( curColor, curHeading );
break;
case BOX:
if ( params != null )
newTurtlePixels = new BoxTurtle( width, height, curColor, curHeading );
else
newTurtlePixels = new BoxTurtle( curColor, curHeading );
break;
case CROSS:
if ( params != null )
newTurtlePixels = new CrossTurtle( width, height, curColor, curHeading );
else
newTurtlePixels = new CrossTurtle( curColor, curHeading );
break;
case TRIANGLE:
newTurtlePixels = new TriangleTurtle( curColor, curHeading );
break;
}
if ( newTurtlePixels != null )
{
curTurtlePixels = newTurtlePixels;
turtleImage = null;
turtleImageProducer = null;
tgc.repaint();
}
} // end setshape()
/**
* Move the turtle to an absolute display position.
*
* Move the turtle horizontally to a new location
* specified as an X coordinate argument.
* @param newX the X-coordinate of destination.
* @see #home
* @see #setxy
* @see #sety
*/
public void setx( int newX )
{ setx((float) newX); }
public void setx( float newX )
{
TGPoint p2 = new TGPoint( (float) newX, curPoint.yFloatValue() );
double heading = 0;
if ( newX < curPoint.xFloatValue() )
heading += Math.PI;
if ( penDown )
tgc.drawLine( curPoint, p2, heading, curPenSize, curColor );
curPoint = p2;
if ( penDown || showTurtle )
tgc.repaint();
} // end setx()
/**
* Move the turtle to an absolute display position.
*
* Move the turtle to the x and y coordinates provided
* as arguments.
* @param newX the X-coordinate of destination.
* @param newY the Y-coordinate of destination.
* @see #home
* @see #setx
* @see #sety
*/
public void setxy( int newX, int newY )
{ setxy( new TGPoint(newX, newY) ); }
public void setxy( float newX, float newY )
{ setxy( new TGPoint(newX, newY) ); }
/**
* Move the turtle to an absolute display position.
*
* Move the turtle to the x and y coordinates provided in the
* TGPoint parameter.
* @param newPt a TGPoint objext containing the X-coordinate
* and Y-coordinate of destination.
* @see #home
* @see #setx
* @see #sety
*/
public void setxy( TGPoint newPt )
{
if ( penDown )
{
double heading = getRadiansTwds( curPoint, newPt );
tgc.drawLine( curPoint, newPt, heading, curPenSize, curColor );
}
curPoint = newPt;
if ( penDown || showTurtle )
tgc.repaint();
} // end setxy()
/**
* Move the turtle to an absolute display position.
*
* Move the turtle vertically to a new location
* specified as an Y coordinate argument.
* @param newY the Y-coordinate of destination.
* @see #home
* @see #setx
* @see #setxy
*/
public void sety( int newY )
{ sety((float) newY); }
public void sety( float newY )
{
TGPoint p2 = new TGPoint( curPoint.xFloatValue(), newY );
double heading = Math.PI/2.0;
if ( newY < curPoint.yFloatValue() )
heading += Math.PI;
if ( penDown )
tgc.drawLine( curPoint, p2, heading, curPenSize, curColor );
curPoint = p2;
if ( penDown || showTurtle )
tgc.repaint();
} // end sety()
/**
* Show the turtle; make it visible.
*
* Long name for st(). Both spellings need to provided for
* compatibility.
*
* @see #hideturtle
* @see #ht
* @see #st
*/
public void showturtle() { st(); }
/**
* Show the turtle; make it visible.
*
* Abbreviation for showturtle(). Both spellings need to
* provided for compatibility.
*
* @see #hideturtle
* @see #ht
* @see #showturtle
*/
public void st()
{
if ( ! showTurtle )
{
if ( curTurtlePixels.setTurtleColor(curColor) )
turtleImage = null;
if ( curTurtlePixels.setTurtleHeading(curHeading) )
turtleImage = null;
tgc.addTurtle( this );
tgc.repaint();
showTurtle = true;
}
} // end st()
/**
* Return the Turtle's X-coordinate
* @see #setxy
* @see #setx
* @see #sety
* @see #ycor
*/
public float xcor()
{ return curPoint.xFloatValue(); }
/**
* Return the Turtle's Y-coordinate
* @see #setxy
* @see #setx
* @see #sety
* @see #xcor
*/
public float ycor()
{ return curPoint.yFloatValue(); }
} // end class Turtle