CodeGuru Home VC++ / MFC / C++ .NET / C# Visual Basic VB Forums Developer.com
Results 1 to 15 of 15
  1. #1
    Join Date
    May 2017
    Posts
    182

    program and track a car on a road

    I trying to work on a challenging topic which I would certainly need help and advice . I will upload an image followed by the explanation in order to make things much easier to understand .

    So I have uploaded the image. What I'm trying to figure out is to program cars to begin from the starting point in the right corner of the image and take either road 1 in black or road 2 in violet and go all the way round and come back to the starting point where everything began. lets say I have 5 cars in first place and the cars should surf and stop in specific location which I represented in stars before coming back to the starting point I mentioned in the beginning . I should be able at any time to track and locate all cars surfing on either road 1 or road 2 . what I think I should use is coordinates (x,y) with toString to tell me the location of cars on the roads. And I think also use of inheritance is important to pass object between cars and make it more flexible . What is a bit tricky here is how to use coordinates and be able to make the cars stop by itself at specific places which are represented with stars. hope you can give me your tips , advice . thank you for your time and support.
    Attached Images Attached Images  
    Last edited by david16; November 30th, 2017 at 04:35 PM.

  2. #2
    Join Date
    May 2017
    Posts
    182

    Re: program and track a car on a road

    I'm new in java but I saw this topic and was interested in solving it . Any advice on how to start this task ?
    Last edited by david16; December 2nd, 2017 at 06:33 AM. Reason: additional information mentioned

  3. #3
    Join Date
    Feb 2017
    Posts
    677

    Re: program and track a car on a road

    Quote Originally Posted by david16 View Post
    Any advice on how to start this task ?
    It's always a good idea to start simple. Why not begin with just one car racing along a straight line with one stop in the middle.

  4. #4
    Join Date
    May 2017
    Posts
    182

    Re: program and track a car on a road

    Actually your right if its working for one we can simply add the number of cars then and all roads . what I'm trying to do ( to be more specific then my first description ) first the most important thing is that a car should start where I mentioned (starting point) it should have the ability to stop at any star on its way ( I should tell the car on which star to stop for example ) and then drive back to the starting point when I tell it to. Now lets say a car is on a straight line like you said to make it more simple what I did is I made a class named Car which has private members : road , position ( which I should keep tracking at all time) , and max_speed ( I want for example to be able to fix max speed of all cars by 50 km/h for all time ) did the default and non-default constructors, setters getters and toString and then in the driver I created a new Car car1 ? Any way on how to start the first task I mentioned ?
    Last edited by david16; December 3rd, 2017 at 01:02 PM. Reason: spelling mistake

  5. #5
    Join Date
    Feb 2017
    Posts
    677

    Re: program and track a car on a road

    Quote Originally Posted by david16 View Post
    Any way on how to start the first task I mentioned ?
    It depends on what kind of program you're writing. If it proceeds with time there's usually a main loop that does something like this (in principle):

    Repeat forever: {
    1. Wait for some time (a tick).
    2. Inform concerned parties (say cars) that a tick has occurred.
    3. Update the consequences of 2 (react to the car's new positions and display the consequences).
    4. Check for user action (commands to start things, stop things, modify things or exit the program).
    }

    I suggest you use a GUI framework such as Swing or the new JavaFX. It very much works like the loop I described.

    I also usually recommend a game engine such as JMonkeyEngine but that may be overkill for a learning project like yours.

    http://jmonkeyengine.org/
    Last edited by wolle; December 4th, 2017 at 09:43 AM.

  6. #6
    Join Date
    May 2017
    Posts
    182

    Re: program and track a car on a road

    Yes I guess the program should keep on running till I decided to stop . By the way , I'm using TEXTPAD or notepad++ to implement this program . The main problem I'm facing is how to translate what I said in post #4 into a code . What I'm going to do is add maybe something such as do while in driver to keep on going infinitely what do you think or is there something better ?
    I mean something like that :

    Code:
    public class Forever {
        public static void main(String[] args) throws InterruptedException {
            while (true) { Thread.sleep(1000); }
        }
    }
    Lets say I have a car with an engine controlled by my program how to send information to the car to start and go to a certain position or a stars ? is there a way to translate this logic to start and stop into a code that's the problem I'm facing at the moment.
    Last edited by david16; December 4th, 2017 at 10:51 AM.

  7. #7
    Join Date
    Feb 2017
    Posts
    677

    Re: program and track a car on a road

    Quote Originally Posted by david16 View Post
    Lets say I have a car with an engine controlled by my program how to send information to the car to start and go to a certain position or a stars ? is there a way to translate this logic to start and stop into a code that's the problem I'm facing at the moment.
    It's not hard in principle but it depends on what kind of program you are writing. What for example do you mean by "go to a certain position"?

    It could mean you call a set position method in a car object and the move would be instant in one gigant leap. But it could also mean the car is supposed to move in small smooth increments like it's driving from one position to another, implying you're planning to show it visually.

    Even though you're using a simple text editor you can still make use of Swing or JavaFX (or even AWT) which I think you should. It will help you deal with user events and graphic output. Otherwise you will be forced to do lots of complicated low-level coding to accomplish the same. As I mentioned, these GUI frameworks essentially implement the basic loop I suggested in #5 and you fill in the application specifics.

    So start by determining which kind of application you want to write, then decide on an appropriate application structure including choice of GUI, and only then start thinking about the rest. I have a feeling you're doing it backwards.
    Last edited by wolle; December 4th, 2017 at 12:57 PM.

  8. #8
    Join Date
    May 2017
    Posts
    182

    Re: program and track a car on a road

    Yeah the program should actually meet the real life condition .
    It could mean you call a set position method in a car object and the move would be instant in one gigant leap. But it could also mean the car is supposed to move in small smooth increments like it's driving from one position to another, implying you're planning to show it visually.
    Actually I supposed that the drawing I did in post #1 is a real surface where I ask car to drive slowly at a fixed speed from a starting point to a position on the image which I represented with stars stop there then go back to the starting point . Each car has 2 things to do with 1 parameter :
    1-drive to the position I asked for and stop .
    2- Then when I ask it to start again it will drive to the starting point again non stop
    3- speed is fixed all the time for all cars ( lets say to 50 km/h )

    that is the first task I should do . So like you said it should move in a small smooth increments . now translating it to coding that's where I'm facing difficulties see what I mean ?
    Last edited by david16; December 4th, 2017 at 02:01 PM.

  9. #9
    Join Date
    Feb 2017
    Posts
    677

    Re: program and track a car on a road

    Quote Originally Posted by david16 View Post
    So like you said it should move in a small smooth increments . now translating it to coding that's where I'm facing difficulties see what I mean ?
    I've planned to get reacquainted with Java after a fairly long pause so I've downloaded the latest Java and Eclipse now.

    I'll write a small "game" first thing tomorrow and post here. Not exactly your application but I'm sure you will be able to use it as a starting point. And then you'll have some real code to ask more specific questions about.

    I will latch on to the Swing/AWT event loop to accomplish the loop I mentioned in #5. It's not the only way but it's the easiest way to get something up and running.

  10. #10
    Join Date
    May 2017
    Posts
    182

    Re: program and track a car on a road

    Oh I'm glad to hear from you again . Thanks I'll be waiting for it then

  11. #11
    Join Date
    Feb 2017
    Posts
    677

    Re: program and track a car on a road

    Quote Originally Posted by david16 View Post
    Oh I'm glad to hear from you again . Thanks I'll be waiting for it then
    My program is coming along quite nicely.

    I quit Java at about when Java 7 was introduced so it's been some time (since around 2011). Apart from being somewhat rusty in Java I can only program in the evenings so it takes a little bit longer than expected.

    I'm doing it in Swing which was the thing to use around the time I left Java. I probably should use JavaFX so I guess my next project will be to port this program to that GUI to really get up to date with Java.

    Then there's the functional thing. My program is OO, period. I know Java has become more functional and I'll see what I can do about that but that's for a next project.

  12. #12
    Join Date
    May 2017
    Posts
    182

    Re: program and track a car on a road

    I see. No problem take your time . I'll be here when you'll be back . thanks a lot
    Last edited by david16; December 10th, 2017 at 05:08 AM.

  13. #13
    Join Date
    Feb 2017
    Posts
    677

    Re: program and track a car on a road

    I've written a Java program now. The purpose was to get reacquainted with Java again after a long period of C++ programming. It means my Java style probably is a somewhat dated. The program is a non-trivial game of predator-prey type called Circle Chase. It's keyboard controlled and the commands are described at the beginning of the source file. I've used Eclipse version Mars as IDE with the latest Java version 8.

    The whole program is on one file for simplicity. The most specific code comes first and then the code becomes more and more general towards the end where the Java entry point main() can be found in class BoardGame. I'm using Swing as GUI so BoardGame is a JFrame. BoardGame sets up class PlayGround which is a JPanel where the game is drawn. The logical part of the game is implemented in class ChaseGame. The rest of the program defines the participating game agents and a collision handling system. For the graphics and the physics I strongly recommend a game engine such as jMonkeyEngine rather than rolling it oneself as I did this time.

    The program is designed according to object orientation (OO) which can be somewhat convoluted if you're not familiar with it. The core principle is that objects are made to act as autonomously as possible based on supplied information rather than being controlled in detail from the outside. This leads to a high degree of decoupling which makes the code easy to modify and extend and that's the ultimate goal of OO.

    This project has been a nostalgic trip for me but I don't regret for a second I switched to C++. Java still is weak on the desktop, especially for scientific computing, and has been so from the beginning. There were some early attempts to improve the situation, R.I.P Java Grande, R.I.P Java3D, but they all fizzled out. I had high hopes when Oracle took over from Sun but I soon realized this wasn't a priority for them either so I finally gave up. Still Java is an excellent language for many purposes and well worth learning. It's also the most popular language according to the TIOBE index.

    The Circle Chase source code is too long for one post so I post it in two parts.
    Last edited by wolle; January 5th, 2018 at 02:39 AM.

  14. #14
    Join Date
    Feb 2017
    Posts
    677

    Re: program and track a car on a road

    Circle Chase source part 1:

    Code:
    import java.awt.*;
    import java.awt.event.*;
    import java.awt.geom.*;
    import java.util.*;
    import java.util.concurrent.*;
    import javax.swing.*;
    
    
    /**********************************
    *
    * Circle Chase is a predator-prey type game
    * 
    * The predator is the blue triangle. The red circles are the prey.
    * 
    * The predator can be moved and if it hits a prey that prey loses color but if
    * it is hit again the color comes back. When all prey have lost color the predator
    * wins. But the predator must be careful. It can be trapped inside the middle 
    * square and the game is lost.
    *
    * All commands are on the keyboard.
    * 
    * Predator
    * --------
    * Arrow up:		move faster
    * Arrow down: 	move slower
    * Arrow right:	turn clockwise
    * Arrow left:	turn counter-clockwise
    * Control-key:	stop
    * Shift-key:	attack at top speed
    * 
    * Prey
    * ----
    * Page up:		all move faster
    * Page down:	all move slower
    * 
    * 
    ***********************************/
    
    
    class DTuple { 	//	tuple used for positions and vectors
    	private final double x;
    	private final double y;
    
    	public DTuple(double x, double y) {
    		this.x = x; this.y = y;
    	}
    	public DTuple(Point2D p) {
    		this(p.getX(), p.getY());
    	}
    	public DTuple() {
    		this(0.0,0.0);
    	}
    
    	public double x() {return x;};
    	public double y() {return y;};
    	
    	public static DTuple add(DTuple p, DTuple q) {
    		return new DTuple(p.x() + q.x(), p.y() + q.y());
    	}
    	public static DTuple sub(DTuple p, DTuple q) {
    		return new DTuple(p.x() - q.x(), p.y() - q.y());
    	}
    	public static DTuple neg(DTuple p) {
    		return new DTuple(-p.x(), -p.y());
    	}
    	public static DTuple scale(DTuple p, double f) {
    		return new DTuple(f*p.x(), f*p.y());
    	}
    	public static double lenSqr(DTuple p) { // squared length
    		return p.x()*p.x() + p.y()*p.y();
    	}
    	public static double len(DTuple p) { // the "Euclidean" length
    		return Math.sqrt(lenSqr(p));
    	}
    	public static double angle(DTuple p) { // the "polar" angle (-PI to PI) 
    		return Math.atan2(p.y(), p.x());
    	}
    	public static DTuple norm(DTuple p) { // normalize 
    		return scale(p, 1.0/len(p));
    	}
    	public static boolean zero(DTuple p) { // is zero!
    		return lenSqr(p) < 0.000001;
    	}
    	
    	public static class Utils {
    		
    			// true if a line segment (p1,p2) intersects with another segment (p3,p4)
    		public static boolean SSIntersect(DTuple p1, DTuple p2, DTuple p3, DTuple p4) {
    			return Line2D.linesIntersect(p1.x(),p1.y(),p2.x(),p2.y(),p3.x(),p3.y(),p4.x(),p4.y());
    		}
    
    			// closest distance between a point (p) and a line segment (p1,p2)
    		public static double PSDistance(DTuple p, DTuple p1, DTuple p2) {
    			return Line2D.ptSegDist(p1.x(),p1.y(),p2.x(),p2.y(),p.x(),p.y());
    		}
    
    			// rotate a polygon around an anchor point an angle described by a direction vector
    		public static DTuple[] rotate(DTuple[] polygon, DTuple direction, DTuple anchor) {
    			AffineTransform rotate = AffineTransform.getRotateInstance(
    										direction.x(),direction.y(),anchor.x(),anchor.y());
    			DTuple[] result = new DTuple[polygon.length]; 
    			for (int i=0; i<result.length; i++) {
    				Point2D point = new Point2D.Double(polygon[i].x(), polygon[i].y());
    				result[i] = new DTuple(rotate.transform(point, null));
    			}
    			return result;
    		}
    		
    		 	// angle between p1 and p2 (-PI to PI)
    			//https://stackoverflow.com/questions/2150050/finding-signed-angle-between-vectors
    		public static double angle(DTuple p1, DTuple p2) {
    			return Math.atan2(p1.x()*p2.y() - p1.y()*p2.x(), p1.x()*p2.x() + p1.y()*p2.y());
    		}
    
    		
    			// rotate a vector (v) an angle (a)
    		static DTuple rotate(DTuple v, double a) {
    			return new DTuple(AffineTransform.getRotateInstance(a).transform(
    					new Point2D.Double(v.x(), v.y()), null));
    		}
    
    		// reverse DTuple array
    		static DTuple[] reverse(DTuple[] straight) {
    			final int N = straight.length;
    			DTuple[] reverse = new DTuple[N];
    			for (int i=0; i<N; i++) reverse[(N-1)-i] = straight[i];
    			return reverse;
    		}
    		
    	}
    } // DTuple
    
    
    
    /**********************************
     *
     * The bouncing system handles collisions
     * 
     * Bouncing elements are circles and polygons. Bouncing acts like reflecting 
     * light in a mirror. (mass and velocities are not considered) 
     * 
     *  The Visitor design pattern is used to avoid down-casting.
     *  
     ***********************************/
    
    interface IBounceResult { // result of a possible collision
    	boolean collision(); // true if a collision took place
    	DTuple bounce(DTuple vector); // get reflecting bounce vector
    }
    
    interface IBounceElement { // Element as in the visitor pattern
    	void accept(IBounceVisitor visitor);
    }
    
    interface IBounceVisitor { // Visitor as in the Visitor pattern
    	void visit(BounceCircle circle);
    	void visit(BouncePolygon polygon);
    	IBounceResult result();
    }
    
    interface ICollidable {	// participates in collisions
    	enum Property {
    		ASDEF, // as defined
    		INVERSE // Inverse bouncing  
    	}
    	IBounceElement shape(Property p); // returns bounceable shape, possible modified
    	
    	enum Directive { // application dependent directives
    		NONE, 
    		MORPH, 	// change appearance - prey switches between color and no color on collision
    		TRAP	// let in but not out - predator gets trapped inside a shape
    	}
    	boolean encounters(ICollidable c, Directive a); // checks for collision and applies directive where applicable
    }
    
    class BounceUtils { // algorithms for collision between bounceable shapes
    
    	// All collision detection functions return null if there was no collision. 
    	// Otherwise a tangent vector is returned off which the shape bounces
    	// (like a ray of light reflected by a mirror). The direction of the tangent 
    	// indicates whether the reflection of the first shape took place inside or 
    	// outside the second
    	
    	
    		// Circle - circle ?
    	public static DTuple collision(BounceCircle c1, BounceCircle c2) {
    		DTuple tangent = null;
    		DTuple normal = DTuple.sub(c2.center(), c1.center());
    		double distance = DTuple.len(normal); 
    		if (distance <= c1.radius() + c2.radius()) {
    			tangent = DTuple.Utils.rotate(normal, Math.PI/2.0);
    		}
    		return tangent;
    	}
    	
    		// Polygon - polygon ?
    		// All vertices of one polygon is checked for intersection with all vertices
    		// of another. Either one or two vertices of the first polygon intersects with
    		// one polygon of the second.
    	public static DTuple collision(BouncePolygon p1, BouncePolygon p2) { 
    		final DTuple[] v1 = p1.vertices();
    		final DTuple[] v2 = p2.vertices();
    		DTuple tangent = null;
    		for (int j=1; j<v2.length; j++) { // all vertices of the second polygon
    			for (int i=1; i<v1.length; i++) { // all vertices of the first polygon
    				if (DTuple.Utils.SSIntersect(v1[i-1],v1[i],v2[j-1],v2[j])) {
    					if (tangent==null) {
    						tangent = DTuple.sub(v1[i],v1[i-1]); // a one vertice intersection so far
    					} else {
    						tangent = DTuple.sub(v2[j-1],v2[j]); // a two vertice intersection
    						break;
    					} 
    				}
    			}
    			if (tangent!=null) break;
    		}
    		return tangent;
    	}
    
    		// Polygon - circle ?
    		// Either one or two vertices of the polygon intersect with the circle.
    		// Intersection takes place when the distance between the circle centre and a polygon 
    		// vertice is shorter than the circle radius. The circle perimeter is considered to 
    		// have a "collision active" thickness and a collision closer to the circle centre is ignored.
    		// This is a way to allow the circle to be bounceable both on the inside and the outside.
    		// In a one vector intersection the vector is the collision tangent. In a two vector 
    		// intersection a perpendicular vector is calculated between the circle centre and the 
    		// common point of the two intersecting vertices. This vector is then rotated 90 degrees
    		// to become the bouncing tangent.
    	public static DTuple collision(BouncePolygon p, BounceCircle c) {
    		final DTuple[] v = p.vertices();
    		DTuple tangent = null;
    		int q = -1;
    		for (int i=0,j=v.length-2; i<v.length; j=i++) { // all vertices of the polygon
    			if (q>=0 && q!=i-1) break;
    			double distance = DTuple.Utils.PSDistance(c.center(), v[j], v[i]); // distance is inside collision 
    			if (distance <= c.radius() && distance >= c.radius()*0.90) { // active circle perimeter thickness
    				if (q<0) { // a one vertice intersection so far 
    					tangent = DTuple.sub(v[i],v[j]);
    					q=i;
    				} else { // a two vertice intersection
    					double angle = DTuple.Utils.angle(tangent, DTuple.sub(v[i],v[j]));
    					//angle = -1.0;
    					DTuple perpendic = DTuple.sub((angle>=0.0) ? v[i] : v[j], c.center());
    					tangent = DTuple.Utils.rotate(perpendic, ((angle>=0.0) ? -Math.PI : Math.PI)/2.0);
    				}
    			}
    		}
    		return tangent;
    	}
    
    		// 	Circle - polygon ?  (reverse of polygon-circle)
    	public static DTuple collision(BounceCircle c, BouncePolygon p) { 
    		DTuple tangent = collision(p,c);
    		return (tangent==null) ? null : DTuple.neg(tangent);
    	}
    	
    	
    	
    	// Calculates reflected vector after collision with a tangent vector.
    	// If the angle between vector and tangent is negative the vector is outgoing so 
    	// although there was a collision no bounce should take place.
    	// The reflection is the same as light reflected in a mirror.
    	// To allow elastic collision (like say between billiard balls) mass and velocity
    	// must be included:
    	// http://www.vobarian.com/collisions/2dcollisions2.pdf
    	public static DTuple bounce(DTuple vector, DTuple tangent) {
    		if (tangent != null) {
    			double angle = DTuple.Utils.angle(vector, tangent); 
    			if (angle > 0.0) vector = DTuple.Utils.rotate(vector, 2.0*angle); 
    		}
    		return vector; // reflected vector
    	}
    		
    } // BlockUtils
    
    
    class BounceCircle implements IBounceElement { // defines a bounceable circular shape
    	private DTuple center;
    	private double radius;
    	
    	public BounceCircle(DTuple center, double radius) {
    		this.center = center;
    		this.radius = radius;
    	}
    	public BounceCircle() {
    		this(new DTuple(), 0.0);
    	}
    
    	public DTuple center() {
    		return center;
    	}
    	public double radius() {
    		return radius;
    	}
    	
    	@Override
    	public void accept(IBounceVisitor visitor) {
    		visitor.visit(this);
    	}
    }
    
    class BouncePolygon implements IBounceElement { // defines a bounceable polygon shape
    	private final DTuple[] polygon;
    	
    	public BouncePolygon(DTuple[] vertices, boolean reverse) {
    		this.polygon = (reverse) ? DTuple.Utils.reverse(vertices) : vertices;
    	}
    	public BouncePolygon(DTuple[] vertices) {
    		this(vertices, false);
    	}
    	public BouncePolygon() {
    		this(new DTuple[0]);
    	}
    	
    	public DTuple[] vertices() {
    		return polygon;
    	}
    	
    	@Override
    	public void accept(IBounceVisitor visitor) {
    		visitor.visit(this);
    	}
    }
    
    
    
    class BounceResult implements IBounceResult { // reports collision result
    	private final DTuple tangent;
    
    	BounceResult(DTuple t) {
    		tangent = t;
    	}
    	BounceResult() {
    		this(null);
    	}
    	
    	@Override
    	public boolean collision() {
    		return tangent != null;
    	}
    	@Override
    	public DTuple bounce(DTuple vector) {
    		return BounceUtils.bounce(vector, tangent);
    	}
    }
    
    	// 	polygon is checked for collision with some other shape
    class BouncePolygonVisitor implements IBounceVisitor {
    	private final BouncePolygon thisPolygon;
    	private IBounceResult result = new BounceResult();
    	
    	public BouncePolygonVisitor(BouncePolygon polygon) {
    		thisPolygon = polygon;
    	}
    	public BouncePolygonVisitor() {
    		this(new BouncePolygon());
    	}
    
    	@Override
    	public IBounceResult result() {
    		return result;
    	}
    	@Override
    	public void visit(BounceCircle thatCircle) {
    		result = new BounceResult(BounceUtils.collision(thisPolygon, thatCircle));
    	}
    	@Override
    	public void visit(BouncePolygon thatPolygon) {
    		result = new BounceResult(BounceUtils.collision(thisPolygon, thatPolygon));
    	}
    }
    
    	// 	circle is checked for collision with some other shape
    class BounceCircleVisitor implements IBounceVisitor {
    	
    	private final BounceCircle thisCircle;
    	private IBounceResult result = new BounceResult();
    	
    	public BounceCircleVisitor(BounceCircle circle) {
    		thisCircle = circle;
    	}
    	public BounceCircleVisitor() {
    		this(new BounceCircle());
    	}
    	
    	@Override
    	public IBounceResult result() {
    		return result;
    	}
    	@Override
    	public void visit(BounceCircle thatCircle) {
    		result = new BounceResult(BounceUtils.collision(thisCircle, thatCircle));
    	}
    	@Override
    	public void visit(BouncePolygon thatPolygon) {
    		result = new BounceResult(BounceUtils.collision(thisCircle, thatPolygon));
    	}
    }
    Last edited by wolle; January 1st, 2018 at 02:32 AM.

  15. #15
    Join Date
    Feb 2017
    Posts
    677

    Re: program and track a car on a road

    Circle Chase source part 2:

    Code:
    /***********************************
     * 
     * The game agent system defines the (active and passive) participants of the game 
     * (which are predator, prey, perimeter, trap and obstacle).
     * 
     ***********************************/
    
    interface IGameAgent extends ICollidable { // takes part in the game
    	void paint(Graphics2D g2);  // paints itself
    	void tick(int steptime, double tickstep); 	// acts on timer ticks
    	void command(int keycode); // acts on commands (keyboard keys)
    }
    
    class GameRectangle implements IGameAgent { // generic rectangular passive agent
    	protected final Color color;;
    	protected final boolean reverse;
    	protected DTuple[] polygon;
    	
    	protected GameRectangle (DTuple upperLeft, DTuple lowerRight, Color color, boolean reverse) {
    		this.color = color;
    		this.reverse = reverse;
    		this.polygon = rectangle(upperLeft, lowerRight);
    	}
    
    	@Override
    	public void paint(Graphics2D g2) {
    		if (polygon==null) return;
            GeneralPath polypath = new GeneralPath(Path2D.WIND_EVEN_ODD, polygon.length-1);
            polypath.moveTo(polygon[0].x(), polygon[0].y());
            for (int i=1; i<polygon.length; i++) polypath.lineTo(polygon[i].x(), polygon[i].y());
            g2.setColor(this.color);
            g2.draw(polypath);
    	}
    
    	@Override
    	public IBounceElement shape(ICollidable.Property property) {
    		boolean b = (property==ICollidable.Property.INVERSE) ? !reverse : reverse;
    		return new BouncePolygon(this.polygon, b);
    	}
    	
    	@Override
    	public boolean encounters(ICollidable c, Directive a) {
    		return false;
    	}
    
    	@Override
    	public void tick(int steptime, double steplength) {}
    
    	@Override
    	public void command(int keycode) {}
    
    		// clockwise rectangle (bounces on the outside)
    	private DTuple[] rectangle(DTuple upperLeft, DTuple lowerRight) { 
    		return new DTuple[] {
    	        new DTuple(upperLeft.x(), upperLeft.y()),
    	        new DTuple(lowerRight.x(), upperLeft.y()),
    	        new DTuple(lowerRight.x(), lowerRight.y()),
    	        new DTuple(upperLeft.x(), lowerRight.y()),
    	        new DTuple(upperLeft.x(), upperLeft.y())
    		};
    	}
    	
    } // Perimeter
    
    class Perimeter extends GameRectangle {   // The game board perimeter
    	public Perimeter(DTuple upperLeft, DTuple lowerRight) {
    		super(upperLeft, lowerRight, Color.GRAY, true);
    	}
    }
    
    class Trap extends GameRectangle {   // A square that may trap the predator but bounces prey
    	public Trap(DTuple upperLeft, DTuple lowerRight) {
    		super(upperLeft, lowerRight, Color.GRAY, false);
    	}
    }
    
    class Obstacle implements IGameAgent { // a circle that bounces both predator and prey
    	private final Color color = Color.GRAY;
    	private final double radius;
    	private final DTuple position;
    	private final BounceCircle bounceShape;	
    
    	public Obstacle(DTuple position, double radius) {
    		this.position = position;
    		this.radius = radius;
    		bounceShape = new BounceCircle(position, radius);
    	}
    		
    	@Override
    	public void paint(Graphics2D g2) {
    		g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    		g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
    		Ellipse2D.Double circle = 
    			new Ellipse2D.Double(position.x()-radius, position.y()-radius, 2.0*radius, 2.0*radius);
    		g2.setColor(this.color);
    		g2.draw(circle);
    	}
    
    	@Override
    	public IBounceElement shape(Property p) {
    		return bounceShape;
    	}
    	
    	@Override
    	public boolean encounters(ICollidable c, Directive a) {
    		return false;
    	}
    
    	@Override
    	public void tick(int steptime, double tickstep) {}
    
    	
    	@Override
    	public void command(int keycode) {}
    	
    } // Obstacle
    
    
    
    
    
    	// The predator is a blue triangle the user can move around
    class Predator implements IGameAgent {
    
    	private final double SPEEDLIMIT = 250;
    	private double radius;
    	private DTuple position;
    	private DTuple direction;
    	private DTuple velocity = new DTuple();
    	private DTuple[] predatorShape;
    	private BouncePolygon bounceShape;
    	
    	public Predator(DTuple position, DTuple direction, double radius) {
    		this.position = position;
    		this.direction = direction;
    		this.radius = radius;
    		predatorShape = predatorShape(position, direction, radius);
    		bounceShape = new BouncePolygon(predatorShape);
    	}
    
    	@Override
    	public void tick(int steptime, double steplength) {
    		if (!DTuple.zero(velocity)) {
    			double angle = DTuple.Utils.angle(direction, velocity);
    			
    			double len = DTuple.len(velocity);
    			final double DA = (len>25) ? steplength : steplength*0.25; // turn slower at slower velocities
    			if (Math.abs(angle) > Math.PI*DA) { // rotates back the predator's direction to its velocity
    				direction = DTuple.Utils.rotate(direction, angle*DA);
    			} else direction = velocity;			
    			
    			position = DTuple.add(position, DTuple.scale(velocity, steplength));
    			predatorShape = predatorShape(position, direction, radius);
    			bounceShape = new BouncePolygon(predatorShape);
    		}
    	}
    
    	@Override
    	public void paint(Graphics2D g2) {
    		final DTuple[] shape = predatorShape; 
            if (shape.length < 4) return;
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
            GeneralPath polygon = new GeneralPath(Path2D.WIND_EVEN_ODD, shape.length-1);
            polygon.moveTo(shape[0].x(), shape[0].y());
            for (int i=1; i<shape.length; i++) polygon.lineTo(shape[i].x(), shape[i].y());
            GradientPaint colorGradient = new GradientPaint(
           		(int)shape[1].x(), (int)shape[1].y(), Color.BLUE,
           		(int)((shape[2].x()+shape[3].x())*0.5), (int)((shape[2].y()+shape[3].y())*0.5), Color.WHITE);
            g2.setPaint(colorGradient);
            g2.fill(polygon);    
            g2.draw(polygon); 
      	}
    
    	@Override
    	public void command(int keycode) {
    		if (DTuple.zero(velocity)) velocity = DTuple.norm(direction);
        	double fi = DTuple.angle(velocity);
        	double len = DTuple.len(velocity);
    	    switch(keycode) { 
    	        case KeyEvent.VK_UP: // faster
    	        	len *= 1.25;
    	        	if (len > SPEEDLIMIT) len = SPEEDLIMIT; 
    	        	velocity = new DTuple(len*Math.cos(fi), len*Math.sin(fi));
    	            break;
    	        case KeyEvent.VK_DOWN: // slower
    	        	len *= 0.8;
    	        	if (len < 1.0) len = 0.0; 
    	        	velocity = new DTuple(len*Math.cos(fi), len*Math.sin(fi));
    	            break;
    	        case KeyEvent.VK_LEFT: // rotate left
    				velocity = DTuple.Utils.rotate(velocity, -Math.PI*0.1);
    	            break;
    	        case KeyEvent.VK_RIGHT: // rotate right
    				velocity = DTuple.Utils.rotate(velocity, Math.PI*0.1);
    	            break;
    	        case KeyEvent.VK_CONTROL: // stop
    	        	velocity = new DTuple();
    	        	break;
    	        case KeyEvent.VK_SHIFT: // full speed
    	        	velocity = new DTuple(SPEEDLIMIT*Math.cos(fi), SPEEDLIMIT*Math.sin(fi));
    	        	break;
    	     }
    	}
    
    	@Override
    	public IBounceElement shape(ICollidable.Property s) {
    		return bounceShape;
    	}
    
    	@Override
    	public boolean encounters(ICollidable collidable,  Directive directive) {
    		IBounceVisitor visitor = new BouncePolygonVisitor(bounceShape);
    		ICollidable.Property property = ICollidable.Property.ASDEF;
    		if (directive == ICollidable.Directive.TRAP) property = ICollidable.Property.INVERSE; 
    		IBounceElement element = collidable.shape(property);
    		element.accept(visitor); // double dispatch according to the Visitor pattern
    		IBounceResult result = visitor.result();
    		if (result.collision()) {
    			velocity = result.bounce(velocity);
    		}
    		return result.collision();
    	}
    
    		// A triangle inscribed in a circle with specified radius and position
    		// The triangle (pointing along the positive x-axis) is rotated to align
    		// with the specified direction vector
    	private DTuple[] predatorShape(DTuple position, DTuple direction, double radius) {
    		DTuple[] shape = new DTuple[] { // straight shape
    		        new DTuple(position.x()-radius, position.y()-radius),
    		        new DTuple(position.x()+radius, position.y()),
    		        new DTuple(position.x()-radius, position.y()+radius),
    		        new DTuple(position.x()-radius, position.y()-radius)
    		};		
    		return DTuple.Utils.rotate(shape, direction, position); // rotated shape
    	}
    
    } // Predator
    
    	// 	Prey are red circles that move autonomously with the same speed determined by the user
    class Prey implements IGameAgent {
    
    	private final double SPEEDLIMIT = 200.0;
    	private final double STARTSPEED = 10.0;
    	private double radius;
    	private DTuple position;
    	private DTuple velocity;
    	private boolean color = true;
    	private int morphcount = 0;
    	private BounceCircle blockCircle = new BounceCircle();	
    		
    	public Prey(DTuple position, double radius, DTuple direction) {
    		this.position = position;
    		this.radius = radius;
    		double fi = DTuple.angle(direction);
        	velocity = DTuple.scale(new DTuple(Math.cos(fi),Math.sin(fi)), STARTSPEED);
    	}
    	
    	@Override
    	public void tick(int steptime, double steplength) {
    		if (morphcount<0) {
    			morphcount = (int) 1000.0/steptime; // max one morph per second
    		} else if (morphcount>0) morphcount--;
    		
    		position = DTuple.add(position, DTuple.scale(velocity, steplength));
    		blockCircle = new BounceCircle(position, radius);
    	}
    
    	@Override
    	public void paint(Graphics2D g2) {
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
            Ellipse2D.Double circle = 
            	new Ellipse2D.Double(position.x()-radius, position.y()-radius, 2.0*radius, 2.0*radius);
     		if (color) { 
    	        RadialGradientPaint colorGradient = 
    	        	new RadialGradientPaint(new Point2D.Float((int)position.x(),(int)position.y()),
    	        	(float)radius, new float[]{0.0f, 1.0f},	new Color[]{Color.ORANGE, Color.RED});
    	        g2.setPaint(colorGradient);  
    	        g2.fill(circle);
            } else g2.setColor(Color.RED);
            g2.draw(circle);
     	}
    
    	@Override
    	public IBounceElement shape(ICollidable.Property s) {
    		return blockCircle;
    	}
    
    	@Override
    	public boolean encounters(ICollidable collidable,  Directive action) {
    		IBounceVisitor visitor = new BounceCircleVisitor(blockCircle);
    		IBounceElement element = collidable.shape(ICollidable.Property.ASDEF);
    		element.accept(visitor);  // double dispatch according to the Visitor pattern
    		IBounceResult result = visitor.result();
    		if (result.collision()) {
    			velocity = result.bounce(velocity);
    			if (action==Directive.MORPH && morphcount==0) {
    				color = !color;
    				morphcount = -1; // start counter
    			}
    		}
    		return result.collision();
    	}
    
    	@Override
    	public void command(int keycode) {
        	double fi = DTuple.angle(velocity);
        	double len = DTuple.len(velocity);
    	    switch(keycode) { 
    	        case KeyEvent.VK_PAGE_UP:
    	        	len *= 1.25;
    	        	if (len > SPEEDLIMIT) len = SPEEDLIMIT; 
    	        	velocity = new DTuple(len*Math.cos(fi), len*Math.sin(fi));
    	            break;
    	        case KeyEvent.VK_PAGE_DOWN:
    	        	len *= 0.8;
    	        	if (len < 1.0) len = 1.0; 
    	        	velocity = new DTuple(len*Math.cos(fi), len*Math.sin(fi));
    	            break;
    	    }
    	}
    	
    } // Pray
    
    
    /*
     **********************************
     *
     * The game logic sets up and handles the game agents
     *
     ***********************************/
    
    
    interface IGameLogic {
    	void init(Dimension dimension);
    	void tick(int steptime, double steplength);
    	void repaint(Graphics g);
    	void keyPressed(KeyEvent e);
    }
    
    class ChaseGame implements IGameLogic { 
    
    	// 	list of all agents, order matters for printing (later agents overprint already printed)
    	private final ArrayList<IGameAgent> ALLAGENTS = new ArrayList<IGameAgent>();	
    		
    	@Override
    	public void init(Dimension dimension) { // initialize agents
    
    		final double W = dimension.getWidth();
    		final double H = dimension.getHeight();
    		final double R = W/H; // width/height ratio
    			
    		{ // perimeter
    			DTuple margin = new DTuple(5.0, 5.0*R);  // margin outside perimeter
    			ALLAGENTS.add(new Perimeter(margin, DTuple.sub(new DTuple(W,H), margin)));
    		}
    		{ // trap
    			DTuple middle = new DTuple(W*0.5, H*0.5);
    			DTuple halfside = new DTuple(W*0.1, H*0.1*R);		
    			ALLAGENTS.add(new Trap(DTuple.sub(middle,halfside), DTuple.add(middle,halfside)));
    		}
    		{ // obstacles
    			ALLAGENTS.add(new Obstacle(new DTuple(W*0.2, H*0.25), H*0.1));
    			ALLAGENTS.add(new Obstacle(new DTuple(W*0.8, H*0.75), H*0.1));
    		}
    		{ // prey
    			final DTuple centre = new DTuple(W*0.75, H*0.25);
    			final double radius = H*0.02;
    			final int NUM = 5;
    			DTuple offset = new DTuple(4.0*radius, 0.0);
    			for (int i=0; i<NUM; i++) {
    				ALLAGENTS.add(new Prey(DTuple.add(centre, offset), radius, offset));
    				offset = DTuple.Utils.rotate(offset, 2.0*Math.PI/NUM);
    			}
    		}
    		{ // predator
    			ALLAGENTS.add(new Predator(new DTuple(W*0.25, H*0.75), new DTuple(1.0,-1.0), H*0.02));
    		}
    	}
    	
    	@Override
    	public void tick(int steptime, double steplength) {
    			// move actors
    		for (IGameAgent actor : ALLAGENTS) actor.tick(steptime, steplength);
    	
    		// 	drive encounters		
    		for (IGameAgent actor1 : ALLAGENTS) {
    			for (IGameAgent actor2 : ALLAGENTS) {
    				if (actor1 != actor2) {
    					ICollidable.Directive directive = ICollidable.Directive.NONE;
    					if (actor1 instanceof Predator && actor2 instanceof Trap) {
    						directive = ICollidable.Directive.TRAP; // predator gets trapped
    					} else if (actor1 instanceof Prey && actor2 instanceof Predator) {
    						directive = ICollidable.Directive.MORPH; // pray changes appearance
    					}
    					actor1.encounters(actor2, directive);
    				}
    			}
    		}
    	}
    
    	@Override
    	public void repaint(Graphics g) {
    		for (IGameAgent agent : ALLAGENTS) agent.paint((Graphics2D)g);
    	}
    
    	@Override
    	public void keyPressed(KeyEvent e) {
    		for (IGameAgent agent : ALLAGENTS) agent.command(e.getKeyCode());
    	}
    
    } // ChaseGame
    
    /***********************************
     * 
     * The playground is the physical place of the game (a JPanel)
     * 
     * It informs the game logic of timer ticks, keyboard events, repaint requests, etcetera.
     * 
     */
    
    @SuppressWarnings("serial")
    class PlayGround extends JPanel implements Runnable { // the game board
    
    	private final int STEPTIME = 1; // step time - time between ticks (milliseconds)
    	private final double STEPLENGTH = STEPTIME*0.01; // step length - basic unit of length
    	private final IGameLogic gameLogic;
    	
    	public PlayGround(IGameLogic gameLogic) { // constructor
    		this.gameLogic = gameLogic;
    		addKeyListener(new KeyListener());
    	        setFocusable(true);
    		setBackground(Color.BLACK);
    	}
    
    	public void init() {
    		gameLogic.init(getBounds().getSize()); // first initialize
    		Executors.newSingleThreadScheduledExecutor(). // then start tick generator
            	scheduleAtFixedRate(this, 100, STEPTIME, TimeUnit.MILLISECONDS); // 100 = initial delay
    	}
    
    	@Override
        public void paintComponent(Graphics g) { // on repaint request
        	super.paintComponent(g);
            gameLogic.repaint(g);
            Toolkit.getDefaultToolkit().sync();
        }
    
        @Override
    	public void run() { // The run() method of Runnable is called every tick
    		gameLogic.tick(STEPTIME, STEPLENGTH); // inform game logic of tick event
    		repaint(); // app invocation of paintComponent() so game is redrawn after tick
    	}
    
        private class KeyListener extends KeyAdapter { // on keyboard action
            @Override
            public void keyPressed(KeyEvent e) {
            	gameLogic.keyPressed(e);
            }
        }
        
        
    } // PlayGround
    
    /***********************************
     *
     * The main program (a JFrame)
     *  
     *
     ************************************/
    
    @SuppressWarnings("serial")
    public class BoardGame extends JFrame { // app main
    
    	private final PlayGround playGround = new PlayGround(new ChaseGame());
    	
    	private BoardGame() {
                    add(playGround); // add playground JPanel to app
    		setTitle("Circle Chase V0.1"); // app name
                    Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
    		setSize((int)(screen.getWidth()*0.5),(int)(screen.getHeight()*0.75)); // determines playground size
    		setLocationRelativeTo(null); // place playground at screen middle
    		setResizable(false); // no resize
    		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // terminate when app window is closed
    		setVisible(true); // show app on screen
    	}
    
    	private void init() { // app initialization
    		playGround.init(); // setup playground
    	}
    
    	public static void main(String[] args) { // app entry point
    		SwingUtilities.invokeLater(new Runnable() {
    			@Override
    			public void run() {
    				new BoardGame().init(); // run BoardGame constructor and initalize app
    			}
    	   });
    	}
    } // BoardGame
    Last edited by wolle; January 2nd, 2018 at 04:01 AM.

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  





Click Here to Expand Forum to Full Width

Featured