Talk:Robocode/Game Physics

From RoboWiki
Jump to: navigation, search

Contents

Robot hitbox

I need to know percisely, which might involve rotating a rectangle, I don't know, if a point intersects my bot, given its X and Y location. Remeber that in Robocode, Y is reversed. While I'm at it, I might just ask: What your opinion is on whether I should do surfing or anti-gravity movement to dodge predicted bullets my robot simulates? --Awesomeness 21:52, 3 May 2009 (UTC)

The 'hitbox' of a robot is always a non-rotated square at the bot's location, so the former check is very very simple. As for your second question: It depends. Surfing is going to be stronger almost surely, however anti-gravity movement is considerably simpler. My suggestion would be to do anti-gravity for now, and at a later date try surfing once you feel comfortable with exactly how it works. --Rednaxela 22:01, 3 May 2009 (UTC)

Oh, is it a 40 pixel long sqare? that's what it looks like. --Awesomeness 22:43, 3 May 2009 (UTC)

No, it is 36px non-rotating square. » Nat | Talk » 23:42, 3 May 2009 (UTC)

Gun turn and fire execution order

When, in the execution order, are bullets fired, and when is the gun turned? Thanks! --Mageek 02:44, 28 May 2009 (UTC)

I believe that's part of #2 in the "Robocode Processing Loop" section. I can tell you for sure, though, that if you do setFire and setTurnGunRight in the same turn, the bullet will fire at the bearing of your gun before that gun turn happens. Both of them happen on the next tick, but the bullet is fired as if it were fired on the same tick as setFire (the location is from that tick, and it has advanced by its velocity once). Does that answer your question? --Voidious 02:53, 28 May 2009 (UTC)

Thank-you, that is exactly what I was trying to figure out. --Mageek 03:39, 28 May 2009 (UTC)

Accel/decel rules

(Conversation began on Talk:Darkcanuck/RRServer...)

I've almost finished my first robot to enter to the melee rumble :), but I'm wondering: does the (melee)server use the old accel/decel rules (where you can go from -1 to 1) or the new (where you can't)? --Positive 23:22, 11 Jul 2009

The RoboRumble currently only allows clients using versions 1.5.4, 1.6.0, and 1.6.1.4, so it should only be the old rule (where you can). I actually didn't realize that was changed -- I really don't like that. =( Lots of legacy bots are programmed to believe you can. --Voidious 21:24, 11 July 2009 (UTC)

Woah, what a quick response! Thanks for the info. :) I don't really like the change either (I have a cool -1, +1 strategy and a nice move simulator), but alas. --Positive 23:36, 11 Jul 2009

Are you sure the rules have changed? I thought the only changes to movement in later versions of Robocode (still unsupported in the rumble, btw) were to fix the movement quirks that Simonton had found. I don't remember decelerating through 0 being one of them. --Darkcanuck 22:26, 11 July 2009 (UTC)

Here is the discussion at the bug tracker. Fnl explains that if for example your robot is at -1.0 velocity, it can only reach 0.75 velocity the next turn (as of version 1.7.1.2). --Positive 02:45, 12 Jul 2009

Errr.... 0.75 is not 'accurate'... It's what robocode as of 1.7.1.2 does, but it's just as incorrect as before the change... Fnl says it uses a formula of (Rules.DECELERATION * decelTime * decelTime + Rules.ACCELERATION * accelTime * accelTime), when the correct formula should be (Rules.DECELERATION * decelTime + Rules.ACCELERATION * accelTime). Change in velocity, is always equal to acceleration multiplied by time, not time squared... It looks like Fnl got his physics mixed up and looked at a formula to get distance from acceleration (d = a*t*t) instead of getting velocity change from acceleration (v = a*t) :-( --Rednaxela 01:47, 12 July 2009 (UTC)

Ooh, I see this now... Do some tests and file a bug for Fnl? --Darkcanuck 02:48, 12 July 2009 (UTC)
Robocode Developer Google Groups, I think. So Flemming can re-open that tracker instead of creating new one. I always wonder why it go from 1 to -0.75, not 0.5 since it have only half turns left, I always think that I don't have enough physics knowledge. » Nat | Talk » 02:59, 12 July 2009 (UTC)

Voidious, do you subscribe to Robocode Developer Google Groups? I think we have reached the conclusion that we worth this change, unless there are too many effects. After we changed it, Flemming and I did run a lot of test (well, mostly Flemming run, I'd run only one test). I've test Dookious with DrussGT, on both 1.6.1.4 and 1.7.1.x with new updateMovement(), and the difference is around 1-2%, which is acceptable in margin of error. I chose Dookious and DrussGT because I know that Dookious use FuturePosition library, which is the internal copy of old movement engine. And DrussGT use Skilgannon's special movement predictor which I believe should be the loosely implementation of the 'correct' movement engine.

Positive, I believe that if you create a good movement under 1.6.1.4, it will be good in 1.7.1.x version too. » Nat | Talk » 06:20, 12 July 2009 (UTC)

- - -

Hi guys, to you mind I join the discussion? I did the change in "update movement" for many reasons. The issue(s) were raised a long time ago on the old RoboWiki (the 3 caveats raised by Simonton - http://old.robowiki.net/cgi-bin/robowiki?GamePhysics/BotSpeed), and later discussed within the Robocode Developer Group (even Mathew Nelson joined the discussion). That is, if we would make the change or not. Especially with you guys in mind. The conclusion in the groups was to fix the bugs so Robocode would follow its own rules, and because lot's of robots out there counts on the rules as explained, not to the way Robocode was actually behaving. Not everybody replicates the internal code of Robocode to get the formulas correct.

The old closed bug tracker on SF created is available here: https://sourceforge.net/tracker/?func=detail&atid=419486&aid=2077512&group_id=37202

Another goal with the change was to make the internal code easier less messy and easier to understand. Regarding the impact of the change, the results are not very different (unless you can prove me wrong here). I ran lots of lots of test with RoboRumble (also Melee and TeamRumble) on my local machines (different single-code, quad-core, Linux, Windows, 32 and 64 bit etc.), and I couldn't tell the difference in rankings or score with or without the change. There was a difference, but it was in average less than 1 percent. Is it really that much of a problem? Really?

I would really love if people could try out the Betas on RoboRumble and put issues on SF as soon as they are discovered. Then the issues can be fixed or discussed before we do the real release. This way, we could avoid such issues as this one long time after the change was made and released.

Btw. I am very familiar with physics, even tough the physics in Robocode does not really follow the real physics laws. I might not have explained the details about the fix well enough.

Instead of shifting between old and new code (like you propose?), I should like you guys to tell me where the problem is in the code instead, since you are obviously the experts. The code is Open Source, so everybody is free to come with suggestions to what and where to correct things.

Here we go:

	private void updateMovement() {
		double distance = currentCommands.getDistanceRemaining();
 
		if (Double.isNaN(distance)) {
			distance = 0;
		}
 
		velocity = getNewVelocity(velocity, distance);
 
		double dx = velocity * sin(bodyHeading);
		double dy = velocity * cos(bodyHeading);
 
		x += dx;
		y += dy;
 
		if (dx != 0 || dy != 0) {
			updateBoundingBox();
		}
 
		if (distance != 0) {
			currentCommands.setDistanceRemaining(distance - velocity);
		}
	}
 
	/**
	 * Returns the new velocity based on the current velocity and distance to move.
	 *
	 * @param velocity the current velocity
	 * @param distance the distance to move
	 * @return the new velocity based on the current velocity and distance to move
	 */
	private double getNewVelocity(double velocity, double distance) {
		// If the distance is negative, then change it to be positive and change the sign of the input velocity and the result
		if (distance < 0) {
			return -getNewVelocity(-velocity, -distance);
		}
 
		double newVelocity;
 
		// Get the speed, which is always positive (because it is a scalar)
		final double speed = Math.abs(velocity); 
 
		// Check if we are decelerating, i.e. if the velocity is negative.
		// Note that if the speed is too high due to a new max. velocity, we must also decelerate.
		if (velocity < 0 || speed > currentCommands.getMaxVelocity()) {
			// If the velocity is negative, we are decelerating
			newVelocity = speed - Rules.DECELERATION;
 
			// Check if we are going from deceleration into acceleration
			if (newVelocity < 0) {
				// If we have decelerated to velocity = 0, then the remaining time must be used for acceleration
				double decelTime = speed / Rules.DECELERATION;
				double accelTime = (1 - decelTime);
 
				// New velocity (v) = d / t, where time = 1 (i.e. 1 turn). Hence, v = d / 1 => v = d
				// However, the new velocity must be limited by the max. velocity
				newVelocity = Math.min(currentCommands.getMaxVelocity(),
						Math.min(Rules.DECELERATION * decelTime * decelTime + Rules.ACCELERATION * accelTime * accelTime,
						distance));
 
				// Note: We change the sign here due to the sign check later when returning the result
				velocity *= -1;
			}
		} else {
			// Else, we are not decelerating, but might need to start doing so due to the remaining distance
 
			// Deceleration time (t) is calculated by: v = a * t => t = v / a
			final double decelTime = speed / Rules.DECELERATION;
 
			// Deceleration time (d) is calculated by: d = 1/2 a * t^2 + v0 * t + t
			// Adding the extra 't' (in the end) is special for Robocode, and v0 is the starting velocity = 0
			final double decelDist = 0.5 * Rules.DECELERATION * decelTime * decelTime + decelTime;
 
			// Check if we should start decelerating
			if (distance <= decelDist) {
				// If the distance < max. deceleration distance, we must decelerate so we hit a distance = 0
 
				// Calculate time left for deceleration to distance = 0
				double time = distance / (decelTime + 1); // 1 is added here due to the extra 't' for Robocode
 
				// New velocity (v) = a * t, i.e. deceleration * time, but not greater than the current speed
 
				if (time <= 1) {
					// When there is only one turn left (t <= 1), we set the speed to match the remaining distance
					newVelocity = Math.max(speed - Rules.DECELERATION, distance);
				} else {
					// New velocity (v) = a * t, i.e. deceleration * time
					newVelocity = time * Rules.DECELERATION;
 
					if (speed < newVelocity) {
						// If the speed is less that the new velocity we just calculated, then use the old speed instead
						newVelocity = speed;
					} else if (speed - newVelocity > Rules.DECELERATION) {
						// The deceleration must not exceed the max. deceleration.
						// Hence, we limit the velocity to the speed minus the max. deceleration.
						newVelocity = speed - Rules.DECELERATION;
					}
				}
			} else {
				// Else, we need to accelerate, but only to max. velocity
				newVelocity = Math.min(speed + Rules.ACCELERATION, currentCommands.getMaxVelocity());
			}
		}
 
		// Return the new velocity with the correct sign. We have been working with the speed, which is always positive
		return (velocity < 0) ? -newVelocity : newVelocity;
	}

What must be corrected in order to make everybody happy, following the "physic laws" and rules of Robocode? --Fnl 22:59, 12 July 2009 (UTC)

Fnl, it will take me some time to look over the code and discussions before I can give more constructive feedback. But some things I can comment on for now:

  • Fixing the issues Simonton brought up seems very reasonable to me. One is a bug that I doubt anyone depends on (the setMaxVelocity / decel). For the others, I'd guess DrussGT and SilverSurfer are the only bots that might simulate the quirks (being Go-To surfers). Some Wave Surfing Challenge tests with SS (I will run some) and feedback from Skilgannon could shed some light on potential impact.
  • I'm not clear on what has happened with this, but I believe changing the ability to go from velocity 1 to -1 could adversely impact a lot of bots. Even a bot as old as PrairieWolf (2002) has a movement mode that depends on it ("buzzing" between 1 and -1), and I'd guess nearly every modern wave surfer also simulates this aspect of the physics.
  • One percent is a lot -- see the "huge" 1% APS gap between DrussGT and Dookious =). That said, it takes hundreds or thousands of battles to get a result accurate enough to see that 1% difference.
  • I do not envy your position in these types of matters, and as always, I appreciate the effort you put into developing Robocode. Robocode's impact goes far beyond the "hardcore" Robocoders on this wiki, and I fully understand that you want to polish Robocode as best as you can for the benefit of everyone that uses it. Cheers,

--Voidious 00:01, 13 July 2009 (UTC)

1 percent is not a lot with this major change in physics, especially when it makes the game follows its own rule better. 1% is 'huge' in APS, I agree. But if you look at each battle closely, the results can swing up to 10% » Nat | Talk » 16:14, 13 July 2009 (UTC)
1% is 1% and it is a lot. You're right that individual battles have a large variance, that's why you need to run hundreds of battles to get a meaningful result. If you run enough battles to get a statistically accurate result, 1% is a lot. If you don't run enough battles, you can't draw any real conclusion from the results.
I'm not claiming there is a 1% difference in results. I'm only saying that if tests only narrowed it down to "1% or less", that's not really enough to satisfy my need for accuracy and consistency. No ill will here, I should get involved and run some tests if I care so much. And I do. And I will. =)
--Voidious 17:40, 13 July 2009 (UTC)
While I wat to argue that 1% is not a lot for this major physics change, I don't think that is an issue anymore. I think you already accept the changes now =) » Nat | Talk » 12:37, 14 July 2009 (UTC)

I've read the mailing list and bug report concerning the issue. Does the -1, +1 thing really need to be changed to fix the bugs? This is how I would code it, and I believe it should fix the bugs. I haven't actually compiled it into robocode, but the velocity routines themselves have been tested and are working. And excuse me, I'm a bad commenter :):

	private void updateMovement() {
		double distance = currentCommands.getDistanceRemaining();
 
		if (Double.isNaN(distance)) {
			distance = 0;
		}
 
		velocity = getNewVelocity(velocity, distance);
 
                // small tweak from original version
		if(velocity!=0)
		{
			x+=velocity * sin(bodyHeading);
			y+=velocity * cos(bodyHeading);
			updateBoundingBox();
		}
 
		if (distance != 0) {
			currentCommands.setDistanceRemaining(distance - velocity);
		}
	}
 
	static double getNewVelocity(double velocityArg, double distanceArg) 
	{	
		double velocity;
		double distance;
 
		// Make sure the remaining distance is always positive or zero
		// (we switch this back later)
		if(distanceArg<0)
		{
			velocity=-velocityArg;
			distance=-distanceArg;
		}
		else
		{
			velocity=velocityArg;
			distance=distanceArg;
		}
 
		// Get the next most positive velocity the robot can reach for next turn
		double mostPositiveReachableVelocity = VelocityToMostPositiveVelocity(velocity);
		// Get the next most negative velocity the robot can reach for next turn
		double mostNegativeReachableVelocity = VelocityToMostNegativeVelocity(velocity);
 
 
		double highestWantedVelocity = Math.min(currentCommands.getMaxVelocity(), maxSpeedToStopInDisp(distance));
 
		// The real next velocity is limited by what is actually reachable
		double nextVelocity = Math.min(mostPositiveReachableVelocity,Math.max(mostNegativeReachableVelocity,highestWantedVelocity ));
 
		// Switch return value back if needed
		if(distanceArg<0)
			nextVelocity = -nextVelocity;
 
		return nextVelocity;
	}
 
 
	static public double VelocityToMostPositiveVelocity(double velocity)
	{
		// Returns the most positive reachable velocity from the
		// specified velocity in one turn
		if(velocity>0)
		{
			double returnVelocity = velocity+Rules.ACCELERATION;
			if(returnVelocity>Rules.MAX_VELOCITY)
				return Rules.MAX_VELOCITY;
			else
				return returnVelocity;
		}
		else
		{
			double returnVelocity = velocity+Rules.DECELERATION;
			if(returnVelocity>Rules.ACCELERATION)
				return Rules.ACCELERATION;
			else
				return returnVelocity;
		}
	}
	static public double VelocityToMostNegativeVelocity(double velocity)
	{
		// Returns the most negative reachable velocity from the
		// specified velocity in one turn
		if(velocity<0)
		{
			double returnVelocity = velocity-Rules.ACCELERATION;
			if(returnVelocity<-Rules.MAX_VELOCITY)
				return -Rules.MAX_VELOCITY;
			else
				return returnVelocity;
		}
		else
		{
			double returnVelocity = velocity-Rules.DECELERATION;
			if(returnVelocity<-Rules.ACCELERATION)
				return -Rules.ACCELERATION;
			else
				return returnVelocity;
		}
	}
 
	static private final double[] dispStopArray = 
		{0.0000,1.0000,2.0000,2.5000,3.0000,
		3.5000,4.0000,4.3333,4.6666,5.0000,
		5.3333,5.6666,6.0000,6.2500,6.5000,
		6.7500,7.0000,7.2500,7.5000,7.7500};
	static public double maxSpeedToStopInDisp(double displacement)
	{
		// Returns the biggest velocity the robot could go to this turn,
		// still being able to stop without overshooting. (Or if 
		// remaining displacement is less than 2, returns that)
                // This routine could be improved to match up with robocode's
                // older velocity selecting rules.
		if(displacement>=0)
		{
			if(displacement>=20)
				return 8.0;
			else if(displacement<=2)
				return displacement;
			else
				return dispStopArray[(int)Math.floor(displacement)];
		}
		else
		{
			return 0;
		}
	}

I also appreciate your effort, Fnl. :) --Positive 01:48, 13 July 2009 (UTC)

Ok, I'm up to speed with the discussions and the code. I think, for how it was decided, this line:

    newVelocity = Math.min(currentCommands.getMaxVelocity(),
        Math.min(Rules.DECELERATION * decelTime * decelTime + Rules.ACCELERATION * accelTime * accelTime,
        distance));

...should be changed to:

    newVelocity = Math.min(currentCommands.getMaxVelocity(),
        Math.min(Rules.ACCELERATION * accelTime, distance));
  • The deceleration already takes you to zero, so we just need to use the remaining time for acceleration, right?
  • Don't need to square the accelTime (v = at).

So, as I understand it, your velocities should be, for example: -1, +0.5, -0.75, +0.625? "Legacy" issues aside, I like this solution a lot. It makes sense. I've been considering this issue a bit now, and here's how I'd lay out the impact to affected bots:

  • A bot like PrairieWolf that oscillates between 1 and -1 will now oscillate between roughly 0.7 and -0.7. I'm guessing this has very minimal impact on performance (though I am curious to test / confirm).
  • When Wave Surfers predict a change of direction, their predictions could be off. I believe most surfers are dealing with whole number velocities the great majority of the time. Changing direction from even velocities will not be impacted. Changing direction from odd velocities, their predictions could be off by as much as 4 pixels -- predicting a velocity 0.5 too high for 8 ticks (until they reach max velocity). Some might disagree, and of course I advocate ultimate precision in surfing, but I believe the impact here is negligible. (How many of us even do precise bot width? And I never found much to be gained there, anyway.)
  • I'm still not crazy about changing Robocode's physics, and I might've opposed it if put to a vote, but I think the impact is negligible and the new solution is a good one.

Also, I've started running those SilverSurfer WSC tests on 1.6.1.4 and 1.7.3 to compare. I'll have the results today or tomorrow.

--Voidious 15:31, 13 July 2009 (UTC)

The information in Robocode (mostly the floating point value) isn't digital, means that only 0.5 change isn't impact much. If you can dodge bullets well, the missed 2 ticks with the most of 2 pixels won't make the bullet hit you. If you can't dodge it, the slippage of 2 pixels may make you dodged it, but only by luck. » Nat | Talk » 16:14, 13 July 2009 (UTC)

<Edit conflict> Thanks for taking a look Fnl. I personally think that the current code is more complicated than necessary, and as such, harder to understand. Here's a few things that should be addressed:

  • The decelDist. Because robocode follows a discrete time base, it cannot be approximated with calculus. For example, say your speed is 3, your formula gives 3.75 as decelDist. However, 3 + 1 + 0 = 4, so your code would not want to decelerate in the right places. This would be the correct implementation:
       private static final double decelDistance(double velocity){
         double distance = 0;
         while(velocity > 0){
            distance += velocity;
            velocity = Math.max(0,velocity - Rules.DECELERATION);
         }
         return distance;
      }
  • One of the ideas of robocode is that your bot has a continuous acceleration/deceleration over a single tick. I see what you have done is, when a bot is decelerating over 0 velocity, it takes 1/2 of the tick to decelerate and then 1/2 of the tick is left to accelerate in the other direction (the split may depend on the amount of time taken to decelerate to 0 first). But many older bots rely on being able to decelerate over the entire tick, using the 2 deceleration value instead of the 1 acceleration value, so that they can 'vibrate' from 1 to -1 back to 1 etc. While I agree that it is not correct, these are the physics of robocode, not real life =) Because of this, you can essentially remove the majority of the inside of that 'if' block, just leaving velocity *= -1;.
  • I have re-written this method, and if people want I'll post it, but it seems that with a few quick fixes this one should be up to scratch =), so I don't really think that's necessary.

As an answer to Voidious, I haven't taken any of the 'quirks' into account while writing DrussGT. It is coded with the rules, as they are described on the Robocode/Game_Physics page, in mind. --Skilgannon 16:00, 13 July 2009 (UTC)

(edit conflict) In my tests, DrussGT usually performs worse. But just around 0.2% or somethings. I'm not sure since I ran only 3 35-rounds tests.
While I agree with you that this isn't real life, it still isn't correct under its own Rules. The major reason we have this fix is that we want Robocode to follows its own rule batter. Otherwise we should have the bullet velocity affected by robot velocity, the curved bullet, better bullet-bullet collision checking etc. » Nat | Talk » 16:14, 13 July 2009 (UTC)
Sorry, but 3 battles simply cannot produce a result within 0.2% accuracy. In my experience, you'd need 100 or more before the overall result stays within 0.2%. --Voidious 17:40, 13 July 2009 (UTC)

Hi guys. Thank you for the constructive feedback. I appreciate that. I see many of you have some really good pointer of what to change, and where to make the changes. Currently, I have my house full of guests and is just checking what is going on in this forum. Tomorrow evening, I will take a closer look at all the comments, and I agree that the new implementation for "update movevement" is buggy too. I intend to create some variants that you can download and test for yourselves. Afterwards, you can choose which one is the better one for the next version of Robocode. Compared to 1.6.x.x, the code has become much simpler than it was before. I am not sure that the code could be any simpler than it is now as we will brake something else (because something is missing) - even though I hope it is possible. So, tomorrow I will review all comment on this page and take action. =) --Fnl 17:43, 13 July 2009 (UTC)

I really agree that the code is a lot more simple and adaptable than 1.6 and before. When I was adapting the internal Robocode's movement engine for my movement predictor, I can't adapt the <1.6 code. I get my head mess when I'm trying to understand it. However, I can adapt the current code really easy (in fact, I did copy almost whole getNewVelocity() method) » Nat | Talk » 12:37, 14 July 2009 (UTC)

I have examined all the comments here, and is trying to figure out how to modify the existing code for getNewVelocity(). I think you are on the right track Voidious, and hence I want to implement your modifications so we could all try them out. However, I have a hard time figuring out how the modified version of getNewVelocity() will look like. If a change the part for the newVelocity, it must be in the two places in the code, where it is assigned. You have only written how you want the first one. Voidious, could you give the full version of your version of getNewVelocity() as you think it should be? This way we all will be able to follow (and discuss) the changes/ideas, and I will be able to implement it straight away. :-) --Fnl 22:28, 14 July 2009 (UTC)

Sorry, I was only closely examining the decelerating through zero issue before. I agree with Skilgannon's assessment that decelDist doesn't seem right, though I'm not sure I'm understanding this fully. Skilgannon, you say that decelDist(3) should be 4, but shouldn't it be 1, since we can update the speed before we move? In either case, I don't understand why it would be 3.75, but I'm curious if there is some voodoo at work in this code that I'm not getting yet. I'll study some more and try to figure it out. =)

And Skilgannon, no harm in posting your rewritten version, I wouldn't mind seeing it. I'll also check out Positive's version. And I'll have some 1.6.1.4 vs 1.7.1.3 WSC2K6 scores ready to examine tomorrow. Only about 180 more seasons to run. =)

--Voidious 00:23, 15 July 2009 (UTC)

  • My understanding (and subsequently, DrussGT's movement simulator, which uses a decelDistance based method of deciding when to decelerate) is if we go with the velocity of 3 this tick, how far will we travel in total if we decide it's necessary to decelerate next tick. Basically, do we have freedom with to do as we please with our velocity this tick, and still be able to decelerate sufficiently in the future. If the answer is "no", then we need to decelerate or maintain velocity this tick. My statements about 4 and 3.75 assumed the DECELERATION is 2. Fnl gets 3.75 from this little bit of code:
final double decelTime = speed / Rules.DECELERATION;
final double decelDist = 0.5 * Rules.DECELERATION * decelTime * decelTime + decelTime;
ie. 3/2 = 1.5 --> 0.5*2*1.5*1.5 + 1.5 = 3.75 whereas it should be 3 + (3-2) = 4. I'll be very interested to see what the scores look like =) --Skilgannon 08:59, 15 July 2009 (UTC)

Wow, this problem really is not trivial. Here's what I came up with, I think it works and is pretty minimal:

    private void updateMovement() {
        double distance = currentCommands.getDistanceRemaining();
 
        if (Double.isNaN(distance)) {
            distance = 0;
        }
 
        velocity = getNewVelocity(velocity, distance);
 
        double dx = velocity * sin(bodyHeading);
        double dy = velocity * cos(bodyHeading);
 
        x += dx;
        y += dy;
 
        if (dx != 0 || dy != 0) {
            updateBoundingBox();
        }
 
        if (distance != 0) {
            currentCommands.setDistanceRemaining(distance - velocity);
        }
    }
 
    /**
     * Returns the new velocity based on the current velocity and distance to move.
     *
     * @param velocity the current velocity
     * @param distance the distance to move
     * @return the new velocity based on the current velocity and distance to move
     */
    private double getNewVelocity(double velocity, double distance) {
        // If the distance is negative, then change it to be positive and change the sign of the input velocity and the result
        if (distance < 0) {
            return -getNewVelocity(-velocity, -distance);
        }
 
        double newVelocity;
 
        // Get the speed, which is always positive (because it is a scalar)
        final double speed = Math.abs(velocity); 
 
        if (velocity < 0) {
            // Check if we are decelerating, i.e. if the velocity is negative.
            newVelocity = speed - Rules.DECELERATION;
 
            // Check if we are going from deceleration into acceleration
            if (newVelocity < 0) {
                // If we have decelerated to velocity = 0, then the remaining time must be used for acceleration
                double decelTime = speed / Rules.DECELERATION;
                double accelTime = (1 - decelTime);
 
                // New velocity (v) = d / t, where time = 1 (i.e. 1 turn). Hence, v = d / 1 => v = d
                // However, the new velocity must be limited by the max. velocity
                newVelocity = Math.min(Rules.ACCELERATION * accelTime, distance));
 
                // Note: We change the sign here due to the sign check later when returning the result
                velocity *= -1;
            }
        } else {
            // Deceleration distance (d) is calculated iteratively due to Robocode's
            // discrete time system.
            final double decelDist = decelDistance(speed);
 
            // Deceleration ticks is the number of ticks it will take to get to
            // zero velocity. 
            final long decelTime = Math.round( // VOIDIOUS: for rounding errors? maybe unnecessary
                Math.ceil((speed - Rules.DECELERATION) / Rules.DECELERATION));
 
            // The maximum distance coverable with an equivalent decelTime
            final double decelTimeMaxDist = ((decelTime + 1) / 2.0) * decelTime // sum of 1..decelTime
                * Rules.DECELERATION;
 
            if (distance <= Rules.DECELERATION) {
                // If we can cover remaining distance and then completely stop,
                // set speed = distance
                newVelocity = Math.max(speed - Rules.DECELERATION, distance);
            } else if (distance <= decelTimeMaxDist) {
                // If we can cover distance in decelTime, split any extra
                // distance (between decelDist and distance) over decelTime
                // ticks
                newVelocity = speed - Rules.DECELERATION + 
                    ((distance - decelDist) / decelTime);
            } else {
                // If we need more than decelTime ticks, try to spread the
                // extra distance over (decelTime + 1) ticks. This will just max 
                // the acceleration if it needs to (ie, if we need more ticks).
                // VOIDIOUS: I think this part would break if Rules.ACCELERATION
                //           were set above Rules.DECELERATION; we might need an 
                //           extra case or something. Doh. =(
                newVelocity = Math.min(speed + Rules.ACCELERATION,
                    (decelTime * Rules.DECELERATION) +
                        ((distance - decelTimeMaxDist) / (decelTime + 1)));
            }
        }
 
        // VOIDIOUS: I think it makes more sense to do this here; no need to decelerate maximally
        //           if you don't need to to accomodate a new setMaxVelocity.
        newVelocity = Math.max(speed - Rules.DECELERATION, 
            Math.min(newVelocity, currentCommands.getMaxVelocity()));
 
        // Return the new velocity with the correct sign. We have been working with the speed, which is always positive
        return (velocity < 0) ? -newVelocity : newVelocity;
    }
 
    private static final double decelDistance(double speed){
        double distance = 0;
        while(speed > 0){
            speed = Math.max(0,speed - Rules.DECELERATION);
            distance += speed;
        }
        return distance;
    }

Velocity is updated before the bot moves, right? =) This is a serious brain teaser! Wanted to post what I had before bed, but I'm still not satisfied with the part that would break with different accel/decel values. --Voidious 05:06, 15 July 2009 (UTC)

(maybe we should move this lengthy discussion to another page?) I've been going through the new velocity formula and have similar concerns as Skilgannon. Just running some test cases using my VelocityTest class and have found cases where the bot overshoots the requested distance (e.g. try starting velocity 0, distance 6 -- the bot ramps up to velocity 3 and will thus overshoot badly). I'll put together some tests and then maybe make a fixed version of the routine.

Regarding deceleration through 0, I have to sympathize with Fnl et al. There's nothing in the rules that says a bot can decelerate through 0 and gain some free acceleration. Deceleration takes you towards 0, acceleration takes you away from it. It's a quirk -- often called a "bug" -- that was perhaps first publicized here (near bottom) and has been exploited since. Its not a huge quirk, the advantage is minimal so fixing it should also be minimal but only tests can really confirm this.

--Darkcanuck 05:09, 15 July 2009 (UTC)

Hey Fellas. Great you're doing more research into this! I think the -1, +1 thing is not a major issue, but if the incorrect movement handling bugs can be fixed in a way that the -1, +1 thing stays valid, is there any harm? I think the version I posted is simpler to understand, and it should work effectively. The only thing that you might not agree with is the working of the maxSpeedToStopInDisp function, but that should at least centralize the problem. :) --Positive 05:53, 15 July 2009 (UTC)

I'm sorry I haven't examined yours more. There are two reasons, really:
  • It does look like you're solving the problem efficiently (and I assume correctly), but I think it depends on fixing one or all of Rules.DECELERATION, Rules.ACCELERATION, and maximum velocity=8. I think we want a solution independent of those values.
  • I find this quite an enjoyable brain teaser, and just wanted to figure out an answer myself. =)
--Voidious 13:26, 15 July 2009 (UTC)
I did test your solution and it passes all the same tests that Voidious' version 2 does. But as he pointed out, the hardcoded values are problematic. --Darkcanuck 17:48, 15 July 2009 (UTC)

The reason I don't like breaking the +-1 thing is because it makes the precise prediction significantly more complicated, and what's more it assumes that the acceleration has more than one value between 2 ticks, which to me seems to break the whole concept of a discrete time interval. For interests sake, here's the one I wrote:

    private void updateMovement() {
         double distance = currentCommands.getDistanceRemaining();
 
         if (Double.isNaN(distance)) {
            distance = 0;
         }
 
         velocity = getNewVelocity(velocity, distance);
 
         double dx = velocity * sin(bodyHeading);
         double dy = velocity * cos(bodyHeading);
 
         x += dx;
         y += dy;
 
         if (velocity != 0 ) {
            updateBoundingBox();
         }
 
         if (distance != 0) {
            currentCommands.setDistanceRemaining(distance - velocity);
         }
      }
 
   /**
    * Returns the new velocity based on the current velocity and distance to move.
    *
    * @param velocity the current velocity
    * @param distance the distance to move
    * @return the new velocity based on the current velocity and distance to move
    */
       private double getNewVelocity(double velocity, double distance) {
 
         if (distance < 0) 
            return -getNewVelocity(-velocity, -distance);
      	// If the distance is negative, then change it to be positive
      	// and change the sign of the input velocity and the result
 
         double maxVel = currentCommands.getMaxVelocity();
         if (velocity < 0)
            return Math.min(velocity + Rules.DECELERATION, maxVel);
         //we want to go in the opposite direction, so decelerate   
 
         else{ 
         //we are going in the direction we want, test what happens if we accelerate
 
            double accel = Math.min(Rules.ACCELERATION, maxVel - velocity);
            //speed up, but not more than max velocity. decel if maxVel is less than velocity.
 
            accel = Math.max(accel, -Rules.DECELERATION);
             //prevent excess deceleration due to bot lowering the maxVel while velocity is high
 
            if (distance > decelDistance(velocity + accel))
               return velocity + accel;
            else if(accel != 0 && distance > decelDistance(velocity))
               return velocity;
            else{
               if(distance < Rules.DECELERATION 
               //we'll be able to cover remaining distance in 1 tick and then decel to stop
 
               && velocity - distance <= Rules.DECELERATION 
               //and our velocity is low enough for us to get to that required velocity
               )
                  return Math.min(distance, maxVel);
             //choose the velocity to cover all remaining distance        
 
               return Math.max(-maxVel, velocity -  Rules.DECELERATION);
            //velocity > 0 and we are close enough, so decelerate. 
            }
 
         }
 
      }
    /**
    * Returns the linear distance it would take to decelerate from a given positive velocity
    *
    * @param velocity the positive velocity from which to test
    * @return the linear distance required to decelerate to a standstill
    */
       private static final double decelDistance(double velocity){
         double distance = 0;
         while(velocity > 0){
            distance += velocity;
            velocity = Math.max(0,velocity - Rules.DECELERATION);
         }
         return distance;
      }

I'm fairly sure this fixes the 0,6 test Darkcanuck is doing. Voidious, one of the problems with your decelDistance() implementation is that your decel distance assumes you decelerate before adding the distance, whereas mine checks if we can still accelerate this tick and be able to decelerate sufficiently in the future. Also, mine considers the case of continuing at the current velocity, which may be a better option, as in the 0,6 test. --Skilgannon 08:27, 15 July 2009 (UTC)

I agree that the new formulas do break the simplicity of the discrete time period. One simple solution would be simply to apply deceleration until 0 velocity is reached -- the extra time for acceleration would be lost. That would both follow the rules and keep the accel/decel values simple. But Fnl's solution does compromise by giving back the extra time for some acceleration, which should help bots that rely on this quirk. --Darkcanuck 16:29, 15 July 2009 (UTC)

(I agree about moving this to a different or its own page. Anyone can feel free, or I'll do it in a bit.) Ah, I'm just imagining a different significance to decelDist: I imagine it as the minimum distance we would cover if we slam on the brakes. The problem with the (0, 6) case (more specifically the (2, 3) case) in mine is similar to the problem I theorize about when Rules.accel is greater than Rules.decel: I assume that if you can't cover distance in decelTime ticks, you can split the extra distance over (decelTime + 1) ticks, which of course is 1 tick and you go to speed 3. Maybe I should force decelTime to a min of 1, since if it's 0 and distance <= Rules.decel, we hit the first case and don't use it, otherwise we hit the 3rd case and need 2 ticks.

Mine would also remain at constant velocity if needed, or even speed up a bit, despite lack of a special case. I'll have to investigate that 0 to -2 acceleration... hopefully that's a simple fix.

I forgot that you'd already be familiar with this kinda stuff, since you are the Lord of Go-To. =) But I still see a problem with yours in that you restrict your decisions to max accel, constant, or max decel. Take the v=6.1, d=15 case: you want to do it in 4 ticks, and mine will (I think =)) go 6.75, 4.75, 2.75, 0.75. I think yours would go 6.1, 4.1, then be presented with (4.1, 4.8) and fail to do it in 4 ticks.

I swear there must be a known Dynamic Programming problem that reduces to this. Maybe my effort would be better spent finding it and copying the established solution. =) To be clear, we are trying to do this in the optimal number of ticks, right?

--Voidious 13:26, 15 July 2009 (UTC)

  • Yep, what we are attempting to do is minimize number of ticks until velocity and distance are 0. The implementation I have in DrussGT is a highly simplified version of what I posted, and probably runs about 10 times faster =) --Skilgannon 7:57, 16 July 2009 (UTC)

I updated my method and posted it to User:Voidious/Optimal Velocity. I think this fixes both the (2, 3) case and the (0, 2) case, though I feel like the (2, 3) case should go 2/1 instead of 1.5/1.5. Can we agree that Rules.DECELERATION will never be less than Rules.ACCELERATION? Or just accept that caveat in our solutions? I believe changing that would seriously complicate things. --Voidious 14:28, 15 July 2009 (UTC)

Ok, I really need to stop thinking about this and get some work done =), but I found some topics maybe worth checking out:

--Voidious 14:43, 15 July 2009 (UTC)

I understand your point Voidious. But I think in any case, the whole concept is complex in such a way, it's good to break it up into different functions (like Darkcanuck has already done partially). How about you guys make the following functions for your versions:

double getMaxVelocity(double distance)
{
 // returns the largest possible velocity for the robot to be set to,
 // that, if next turn it had to start decelerating, it would still be
 // able to stop without overshooting
}

Basically, that would refine the problem. Because in that function you don't have to care about the current velocity. :) After this function returns, you only need to check if the returned velocity "wantedVelocity" is actually reachable from the current velocity by the accelerating/deceleration laws of robocode. And if not try to get closest to it (that won't cause any problems, because any speed returned lower than the max velocity won't overshoot either) :)

So basically, the other function needed is:

double getClosestReachableVelocityToVelocity(double currentVelocity,double wantedVelocity)
{
 // with this function you can basically assume setAhead(Infinity) or setAhead(-Infinity)
 // was called, and you determine the next velocity based on the max velocity
 // set by the robot. For example, if the current velocity is 0 and the max velocity
 // set was 4.0, it would return 1.0. If the current velocity was 8.0, it would return 6.0.
}

Finally, the getNewVelocities final version would be (independant of the workings of the other functions):

double getNewVelocity(double velocity, double distance) 
{
 if(distance<0)
  return -getNewVelocity(-velocity,-distance);
  double highestVelocity = getMaxVelocity(distance); // highest velocity without overshooting
  double wantedVelocity = Math.min(highestVelocity,currentCommands.getMaxVelocity());
      // the actually wanted velocity by the robot is the highest possible,
      // limited by what the robot set by the setMaxVelocity command
  return getClosestReachableVelocityToVelocity(velocity, wantedVelocity);
      // return whatever is closest to that velocity
}

--Positive 16:50, 15 July 2009 (UTC)

I didn't break anything up, just plugged the getNewVelocity functions into a simple (well, the latest version is no longer so simple) testing framework. The best solution is the one that gets the job done in the least code, while still remaining understandable. =) --Darkcanuck 17:48, 15 July 2009 (UTC)
I really like how you frame the problem here. I think I have a solution to getMaxVelocity at User:Voidious/Optimal Velocity. This seems like a much cooler and cleaner way to deal with this. --Voidious 18:14, 15 July 2009 (UTC)
Okay Darkcanuck, I understand. I just finished creating an implementation of the getClosestReachableVelocityToVelocity here: Positive/Optimal Velocity. I'll try to combine Voidious's getMaxVelocity routine with it and the code above, and post the results. --Positive 19:11, 15 July 2009 (UTC)

It seems like it's working. I posted the results here Positive/Optimal Velocity. Great job Voidious with your getMaxVelocity function! --Positive 19:37, 15 July 2009 (UTC)

You guys are truely amazing. Good job! I knew the "update movement" was non-trivial and could be complex when I tried to simplify things and get rid of the "bugs". But now it seems even much more complex. The rules are simple, but the code behind it complex?! I will put take all the code from the Positive/Optimal Velocity page and put into Robocode. Then I will make a new Alpha version, which everybody could try out to see if this is what we want in the next version of Robococe (1.7.1.4). And yes Positive, your version of updateMovement is better. --Fnl 21:41, 15 July 2009 (UTC)

Yes, great team-work. And.. it's great to feel appreciated. :) Fnl, please include the temporary fix in the getMaxVelocity that was just added (to speed up robots that use setAhead(huge number)). --Positive 21:50, 15 July 2009 (UTC)
Cool! =) --Voidious 22:25, 15 July 2009 (UTC)

Okay, I will make sure the include the temporary fix. If we find a better solution later, I will of course update the code as well.

Notice: If you are not a member of the Robocode Developers Discussion Group then please do not hesitate with joining this group soon. The way you/we won't miss important aspects in our discussions about changes (and new features) in Robocode. --Fnl 22:07, 15 July 2009 (UTC)

Ok, so I ran some tests. With both Diamond and SilverSurfer, in both 1.6.1.4 and 1.7.1.3, I ran 100 seasons of 500-round Wave Surfing Challenge 2K6. We've banged on the 1.7.1.3 updateMovement code quite a bit now, and clearly it has changes that could affect both True Surfing (decel through 0) and Go-To (velocity selection). However, the results are extremely close (and surprisingly, 5 out of 6 of the scores are higher in 1.7.1.3.)

Bot Bot A Bot B Bot C Total
SilverSurfer 2.53.33 99.84/99.85 91.15/91.04 85.77/85.81 92.25/92.23
Diamond 1.197 99.92/99.93 98.46/98.48 96.94/96.98 98.44/98.47

I'll be sure to run some more and different tests with the new Alpha, as well.

--Voidious 22:25, 15 July 2009 (UTC)

Here is the new Alpha-1: http://robocode.sf.net/files/robocode-1.7.1.4-Alpha-1-setup.jar --Fnl 22:39, 15 July 2009 (UTC)

Hey Fnl, that last change Positive made at Positive/Optimal Velocity is an important one. Without it, setMaxVelocity actually doesn't work at all in this Alpha-1. (Tested with Jen, who starts in a stop and go mode, but just moves full speed in the alpha.) Math.min(highestVelocity,8.0) should have currentCommands.getMaxVelocity() instead of 8.0. --Voidious 00:04, 16 July 2009 (UTC)

I just tested it too, and had the same results. --Positive 00:08, 16 July 2009 (UTC)

I like this =) Although there still seems to me to be some clean solution that we've all missed, which doesn't involve different cases. Although it may be the tick-based environment that prevents that. However, Murphy be blamed, now that it's too late, I've finally come up with a decelDistance() function that doesn't have a loop:

       private static final double decelDistance(double speed){
         double t = speed / Rules.DECELERATION;
         double floor_t = Math.floor(t);
         return 0.5*floor_t*(floor_t+1)*Rules.DECELERATION + (speed%Rules.DECELERATION)*Math.ceil(t);//used by Skilgannon's solution
       //return 0.5*floor_t*(floor_t-1)*Rules.DECELERATION + (speed%Rules.DECELERATION)*Math.ceil(t);//used by Voidious's solution
      }

--Skilgannon 07:57, 16 July 2009 (UTC)

Fnl, to save you from looking all over the place, I think the final evolution of the code is at:

As of now, Darkcanuck has tested the former but not the latter. You can take a look yourself and decide which you want to try in the next Alpha.

--Voidious 16:00, 16 July 2009 (UTC)

Results for both can be found here: Talk:Darkcanuck/VelocityTest. I have yet to break either version, but I'll try... --Darkcanuck 16:04, 16 July 2009 (UTC)

The section itself is 48KB now, longer than most pages =) Did we reach the conclusion on old decel-through-zero or new decel-through-zero rules yet? » Nat | Talk » 16:51, 16 July 2009 (UTC)

Well, that decision really is Fnl's. I don't think we debated it until there was a consensus among the rest of us. Personally, I believe that the effect on current bots will be immeasurably small. I will run some extensive tests to compare the versions, and hopefully the results will support this. (I've got some more of the 1.6.1.4 test runs going already.) --Voidious 17:54, 16 July 2009 (UTC)

Oh, I think he'll choose the new rules. The older conclusion is to makes Robocode follow its own rules better. » Nat | Talk » 18:09, 16 July 2009 (UTC)


Sorry for the bad Alpha-1. The compiler did not see the update (?) I made on the source for the temporary fix. Nevermind, I have made two new alphas to try out:

Actually, I am not sure which version I prefer after this long discussion, but it seems to me that the new decel-through-zero rules is more correct. Actually, it was Mathew Nelson who mentioned that it ought to be fixed too, so that was my motivation for adding it. But I will let it up to you guys to decide.

Btw. Nice job with making the source code smaller with the two Hijack versions.

--Fnl 22:54, 16 July 2009 (UTC)

Thanks, Fnl. I'm going to run quite a few tests with these two and 1.6.1.4 and post results. So far, Diamond 1.191 vs Barracuda over 500 battles (35 rounds) is 99.51 in 1.6.1.4 and 99.52 in 1.7.1.4-Alpha 3 -- not a bad start. =)

I'm planning to test with Diamond, Phoenix, DrussGT, and PrairieWolf (vs a few other bots). The first 3 because they are likely among the most precise bots out there, Diamond over Dookious because it's the same precision and surfing but twice as fast, and PrairieWolf because I know he has a +1/-1 movement mode. Maybe I should add Wintermute? If anyone has suggestions on who else I should test with (and maybe why), let me know. I will definitely do some tests with non-surfers, but surfers do seem the most precise and thus the most likely to be affected.

--Voidious 01:19, 17 July 2009 (UTC)

FYI: From tomorrow and one week forward (week 30) I will be on a summer vacation in a rented summer cottage together with my family. If I am lucky, I will be able to access the Internet from there. But the connection might be slow. I will check out these pages while I am off, but I cannot promise that I will be able to compile everything into a new version, and I need to fix some other issues before I can release a 1.7.1.4 Beta. Until then, have fun with the alphas here. If other issues needs to be addressed as well, then please create a bug report on SourceForge so we can keep track of all issues. :-) --Fnl 21:56, 17 July 2009 (UTC)

Sanguijuela v0.8 is a micro rambot using the Accel/decel trick. Please, let me know if I'm wrong, but as far as I understand, this trick will be inevitable useless in the future at the RoboRumble, right? If this is correct I will remove the trick and upload a new version (with 50 less of codesize :P ) and we will see the impact. Thanks! --jab 16:02, 13 August 2009 (UTC)

Well, it depends on the result of the vote. You can vote against removing it, to tie the vote. --Positive 18:44, 13 August 2009 (UTC)

Vote? Ok, it could be against my interests but I vote in favor of removing it. :D I read this discussion by chance while I was checking my bots ranking after more than a year and a half so I see the problem: There are many bots out there using the trick and thinking that others are using it. Many bots are not going to be updated... But it is a trick, we took advantage of a robocode bug. I read once about the Teleport bug at the old wiki, it is not similar somehow? but the accel/decel bug seems to be accepted for a long time. Solutions? I think that there are two solutions: Removing the bug (I didn't check if it is already resolved in new versions) and reset the roborumble server and only allows newer versions as roborumble clients, or making this Accel/decel phenomenon part of the official Robocode/Game Physics. That could be another solution. What do you think? --jab 21:58, 13 August 2009 (UTC)

Hey jab, welcome back. =) Always good to have more input on this. No, it's not resolved yet, and there is some testing here and more discussion on that page's talk page. It is certainly a grey area and I can see both sides. If I thought the impact on scores would be great, I would be strongly against allowing this change, "bug" or not, but really I don't think that will be the case. You could still "vibrate", just not between 1 and -1 (would end up around -0.7/0.7), and precise prediction would only end up off by a few pixels, at most. --Voidious 22:26, 13 August 2009 (UTC)

It could be as easy as putting it in the official rules. "Vibrating" could be allowed, but it should appear at least in the game physics page. With this condition I could change my vote for keeping the old rules and the code that could be the reason why Sanguijuela is currently the best rambot in the rumble ;-)

if (changingDirection && Math.abs(getVelocity()) >= 2) {
	//Acceleration Trick
	setMaxVelocity(0.00001);
} else

I think that this option of making it official should be firmly accepted or rejected before trying to find a correct implementation of preventing it. --jab 08:59, 14 August 2009 (UTC)

Yeah, that's the weirdest thing in old rules. It may make new Robocoders do 'Don't decel to 0, decel to 0.0000001'. That will make a tie, if you want to change it. » Nat | Talk » 12:02, 14 August 2009 (UTC)

Processing loop

Is this telling me that my onScanRobotEvent happens after I've executed my behavour (in the while loop)?? if I store Event data, then use that data to move ect.. That it is 1 tick old? - thx for clarity. -Jlm0924 19:15, 22 January 2011 (UTC)

Not really... I tend to imagine the "time being updated" as the last thing in the loop, but it's the 3rd thing in this list. And it's a loop, so either way is technically correct. But anyway, the last things to happen on a tick are onScannedRobot, graphics repaint, then your while loop. If you paint with just onPaint (not the new 1.7.x direct painting stuff) and update graphics stuff in the main loop, those graphics will lag 1 tick - I think that's the only real thing to worry about. --Voidious 19:25, 22 January 2011 (UTC)

That last bit has always bugged me a lot. See, my large bots are coded so that I process a list of all events in the main loop, not with event handlers. In order to do this, and work with onPaint nicely, I had to code it so that a *whole* iteration of the main loop would instead run at the start of onPaint, but only on ticks that onPaint was called. Also, are you sure that 1.7.x "direct" painting stuff is immune to that? I had always assume that painted 1 tick delayed. --Rednaxela 19:36, 22 January 2011 (UTC)
Yeah, I hear ya. Stuff that I paint in my main loop is painted 1 tick ahead, so it looks mostly right. No, I'm not sure about the direct painting, I've never used it. But I thought that stuff just let you repaint directly. Maybe I'm wrong? --Voidious 19:39, 22 January 2011 (UTC)
The direct painting stuff is never old. Robocode update battleview after ALL processing is finished. Just the the old onPaint() interface (leftover from RobocodeSG) integrated into Robocode event system, so it couldn't be call after the main loop. Mt solution to this problem is to create new custom event with high priority than onPaint() and process my main loop there ;) --Nat Pavasant 03:08, 23 January 2011 (UTC)
I should rename the bot I'm workig on to : " TickTwisted " as I'm sure it is... God knows what my graphics are lined up with.. :P I have added a new value "dataAge" to the EnemyClass, which needs to be updated in an Event then .... Thanks for the Info guys -Jlm0924 20:05, 22 January 2011 (UTC)

You know, this is all stil very confusing. Perhaps I will do some debugging to try and understand it better, and then write a separate page on the subject. You would think, as long as I have been doing Robocode, I would understand it, but I still don't. Things that are unclear to me include:

  • What is the difference between step 2 (All robots execute their code until they take action and then paused) and step 7 (All robots are resumed to take new action.). How is this not the same thing?
  • What's the deal with onPaint() and direct painting? Did I miss something in the Robocode UI? Should I not be using onPaint()?
  • Why is it I should use my info from two ticks ago as the data an opponent used to fire on me? If I walk through the steps, starting from when the opponent scans me (the last scan before they fire), it would seem to go like this: 6 (opp scans me), 7, 8, 1, 2 (opp sets fire using scan), 3 (time increment), 4 (opp bullet fires), 5 (I move), 6 (I scan opp), 7, 8 (I log energy drop and create opp bullet wave). Following that order, time along with my position only advanced one tick. So I should use my data from 1 tick ago. How is this wrong?

-- Skotty 20:42, 19 September 2011 (UTC)

I agree this could be clearer. Attempts at answers:

  • I don't know what step 7 means. Step 2 is the stuff in your while (true) loop in run(). Basically, everything else happens during the execute() call. In my mind, step 2 is the last step on a given tick.
  • Well, I use onPaint. The issue is that the battle field is repainted before step 2. So you might paint stuff from your main loop and whatever you paint will be 1-tick-old data by the time it's painted. You can instead use Robot.getGraphics to get the Graphics2D object and just skip using onPaint - I think it will be painted immediately in that case.
  • When the opponent fires in step 2/4, he can't be using the data from the most recent step 6 to aim, because his gun won't turn again until step 5. He can setTurnGun and setFire on the same tick, but the bullet will be fired at the previous gun heading, then the gun will turn.
  • I do still encourage you to do some debugging to make sure it's all clear in your brain. =) I certainly have.

--Voidious 21:05, 19 September 2011 (UTC)

  • Well... I would say 2 and 7 are the same actually.
  • They do the exact same thing, there's nothing different in the Robocode UI. The "direct paining" was more recently added than onPaint. The catch to remember, is that onPaint is only called when painting is enabled for the robot, whereas "direct painting" code will always run and thus always eat up CPU. For that reason, I strongly prefer onPaint.
  • You use your info from two ticks ago, because bullets fire before the gun turns. If the enemy fires on "2 (opp sets fire using scan)" in your list, the the last time they had an opportunity to turn their gun was a full turn earlier. What matters is using the information from the tick when they aimed, not the tick when fired, and they are separate ticks due to setFire taking priority over turning the gun.

(Voidious got to the submit button before me, but posting my response anyway :P) --Rednaxela 21:15, 19 September 2011 (UTC)

Quick little additional note: Personally, I get around the "1-tick-old data" issue Voidious mentions, my having my robot framework call the iteration of it's own main loop at the start onPaint. It's a little annoying though. Personally, I might prefer "direct painting" if only Robocode had an API to allow robots to see if painting is on or off (without the hack of seeing if onPaint is getting called when actually using "direct paining"). --Rednaxela 21:19, 19 September 2011 (UTC)
Okay, so then the presumed behavior of the average opponent robot (ignoring step 7) would be: 6 (opp scans me), 8, 1, 2 (opp turns gun to aim using scan), 3 (time increment), 4, 5 (I move), 6, 8, 1, 2 (opp sets bullet to fire), 3 (time increment), 4 (opp bullet fires), 5 (I move), 6 (I scan opp), 7, 8 (I log energy drop and create opp bullet wave, which should have 1 tick ago for his data and 2 ticks ago for my data). Did I get that right? That makes sense; not sure why I got confused on it. I kind of already knew, as my XanderGun actually predicts the opponent 1 tick into the future to account for having to aim before firing (though it's a very crude prediction). I need to go back and carefully review all that code. I know I am only pulling the scan from 1 tick ago right now for myself when opponent fires on me. I wonder if changing it to 2 ticks will improve my rumble score any. -- Skotty 21:33, 19 September 2011 (UTC)
Extra little note -- I know you all have talked about this before, but I'm one of those hard headed types that doesn't believe anything until I can understand and prove it myself. It really drives my family crazy sometimes. :-) Thanks for the help, it has finally sunk in now. -- Skotty 21:49, 19 September 2011 (UTC)
I know what you mean. Took me awhile to understand the physics, now days I am discovering undocumented behavior. :) — Chase-san 22:50, 19 September 2011 (UTC)
Well, initial tests suggest that using my scan from time-2 ticks instead of time-1 will actually lower my rumble score by a noticeable margin. Maybe more accurate data makes me more predictable? Note that I don't roll my data at all. In the past, any rolling of data at all would lower my score. But maybe with more accurate data, I need to start rolling it now. With the change in place, scores against some of the easier opponents went up a little, but scores against some of the harder opponents like Gaff and Chalk dropped like a rock (still too early to tell as only about 2 seasons run at the moment, but avg score against Gaff dropped from 53 to 44, and score against Chalk dropped from 50 to 43). Or maybe there is some other bug skunking up the results. -- Skotty 01:06, 20 September 2011 (UTC)
In my opinion, you should focus on getting everything working right first, then focus on improving your score. — Chase-san 01:09, 20 September 2011 (UTC)
As far as I know, pretty much everything IS working right. But this issue is outstanding. Getting it working right is exactly what I'm trying to do by working on this issue. But when getting something right lowers score (as most of the time, the opposite happens), I think it is good to question why. -- Skotty 01:23, 20 September 2011 (UTC)
Well, to throw out some theories, your time - 2 information may not actually be from time - 2.I usually just use a linked list, the most recent I add to the top(front/start) before I check waves, so that it is at position zero, placing 1 at 1 turn ago and 2 at 2 turns ago. I keep the list small. Another possible reason is that your movement simulation starts early or late, which could cause similar behavior if the timings for the waves are off. etc — Chase-san 01:33, 20 September 2011 (UTC)
Hmm, a couple things. First, I'd be shocked if you really lost overall score from this change, so if you do, I'd look for some other bugs related to this change (exposed by it or offsetting the previous bug somehow). Second, while I'd expect some measurable score increase in the rumble, I would also expect the impact on any given matchup to be very small. I'd guess there's almost no chance you could lose 7-9% against those bots if you ran enough battles to measure it accurately (100+ at least). I've seen some pairings swing from 35 to 60+, so 2 battles is not nearly enough to really say anything useful. On a separate note, I'd definitely play more with rolling or decaying your data! --Voidious 01:37, 20 September 2011 (UTC)
↑↑↑↑↑ Voidious explains it better then I do. — Chase-san 01:56, 20 September 2011 (UTC)
After several more seasons, it was starting to look more like a wash (no performance change). I'm keeping the change (using time-2 for self) since it is more correct. I even ran it against my own test opponent I created just to verify that something wasn't wacky with XanderCat, and that the data times were in sync (had the test opponent print out what data it used to aim, compared it to what XanderCat was using for opponent wave). Ideally I would like to run more seasons for my various tests, but my computer isn't exactly top of the line, so I usually keep it to just 10 seasons against a test bed of 23 robots. -- Skotty 06:05, 20 September 2011 (UTC)

Contents

Thread titleRepliesLast modified
robocode.Bullet instances not being synchronized900:02, 12 February 2013

robocode.Bullet instances not being synchronized

I've been trying to track down a weird bug that makes DrussGT occasionally not be able to identify one of its bullets in an onBulletHitBullet situation. The bullet I get in the onBulletHitBullet handler gives a getX() and getY() to a point which is one tick behind the one I have stored from fire time. This only happens rarely, once every ~100 or so bullets, but it's enough to make DrussGT crash with some new optimizations I've added, and enough to prevent the wave shadows from being cleared in certain circumstances.

I'm also not sure which one is giving the incorrect results, the setFireBullet Bullet or the BulletHitBulletEvent Bullet; if it's the former, it means that every so often my bullet shadows are incorrect, which would also explain why I get hit by a bullet which is inside of a bullet shadow several times a battle against Diamond.

So, any ideas of how on earth this might be happening, or the best way to mitigate it? Would somebody please check if this happens to them as well? I'm using 1.7.3.2 as a dev environment.

Skilgannon19:26, 10 February 2013

If you just match the bullet within a distance of 50 and also match the bullet power to 1 decimal, there should be no room for error, right? At least in matching to the right wave. I think I've always done it that way, probably going back to the point where my wave management was buggy enough that I just compensated for it.

In fact I thought the *HitBullet methods used to return points not even along the line the bullet traveled, though again that could have been buggy wave management. But I thought it was something Fnl fixed at some point.

That is pretty funky and I'd def like us to track it down and fix it. I'll run some tests when I get a chance.

Voidious23:27, 10 February 2013
 

onHitByBullet events return bullet coordinates after it hits you, and onBulletHitBullet events return bullet coordinates before it hits the other bullet. If you reuse the same code for both events, you have to adjust time accordingly. I stumbled on this while tracking a wave detection bug in Combat a while ago.

MN23:53, 10 February 2013

But surely both Bullet objects should show the same location even after a hit? ie, the one from setFireBullet and the one from BulletHitBulletEvent? They both represent the same simulated object. They should stay in step with each other.

Skilgannon05:44, 11 February 2013

They both represent states of the same simulated object. Maybe the Bullet instance returned from setFireBullet is updated every tick while the instance returned by BulletHitBulletEvent isn't, but only debugging the Robocode engine to be sure.

MN00:02, 12 February 2013
 

I agree this is a silly bug and it sucks that it's causing you problems, but I'm kind of surprised you are using these Bullet objects this way at all. I just check if setFireBullet returns null and if it's not I mark the appropriate wave. It never even crossed my mind to store the Bullet object or use any information from it. :-)

Voidious17:53, 11 February 2013
 

I'm actually using it as a pass-through between my gun and movement so that I can mark the enemy waves with the correct shadows. I could always make a class that has bullet power, firetime and initial fire location, but that's essentially what the Bullet object already is so I thought I'd rather just use the built-in one =) A bit of my codesize-restricted mindset weighing in, perhaps.

Skilgannon18:10, 11 February 2013

Ah, that makes more sense. Indeed I do duplicate that in my own class for the gun/movement linking of bullet shadows.

(Not sure if you're joking about your focus on code size... =) jk)

Voidious19:25, 11 February 2013
 

At one time, I was using the bullet objects for matching things up. I stopped doing that after the bug in 1.7.3.0 where the bullet objects in the events could no longer be compared against the one from the setFire method. Now I use my own bullet objects, kind of like what Skilgannon mentioned as a possibility.

I thought this was fixed in Robocode 1.7.3.2....maybe it was, but a lot of us are still running 1.7.3.0 (in part because 1.7.3.2 is not available on the web, and newer versions are not currently supported by the Rumble). Furthermore, I thought this was the bug that broke Krabby2, but I see it's scores are not improved with 1.7.3.2 (if you look at it's history, you will see much better scores for clients from version 1.6.1.4).

Skotty18:31, 11 February 2013
 

At one point when I was just starting out kept the bullet object, but things didn't seem to ever match up at the time (I probably just didn't know what I was doing). So I ditched it.

So yeah, I just fudge it now too. If it is within x of a wave, and that wave is the closest, then that wave is the wave the bullet is from.

But for longest time I had this weird bug in my detection which I finally found out was an enemy firing on the same turn it died (thus I could not detect its firing). So I just kept the heat wave, which solved the problem most the time.

Chase20:36, 11 February 2013
 
 
Personal tools