Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

I want to know how the AI calculates where the ball lands #8

Open
DavinciEvans opened this issue Dec 15, 2021 · 2 comments
Open

I want to know how the AI calculates where the ball lands #8

DavinciEvans opened this issue Dec 15, 2021 · 2 comments

Comments

@DavinciEvans
Copy link

It is nice to see the project you share! I am a Unity learner and trying to repeat your work in Unity. But the code of AI is a little hard. I have read the physics.js but I am a Javascript noob and not totally understand it. 馃槩

Could you use a simple text or some mathematical formulas to describe it? I will appreciate that!

(I am not a native English speaker, maybe you can understand my statement xD)

@gorisanson
Copy link
Owner

@DavinciEvans
Sorry for my late response.

The landing point of the ball is calculated by the function calculateExpectedLandingPointXFor:

/**
* FUN_004031b0
* Calculate x coordinate of expected landing point of the ball
* @param {Ball} ball
*/
function calculateExpectedLandingPointXFor(ball) {
const copyBall = {
x: ball.x,
y: ball.y,
xVelocity: ball.xVelocity,
yVelocity: ball.yVelocity,
};
let loopCounter = 0;
while (true) {
loopCounter++;
const futureCopyBallX = copyBall.xVelocity + copyBall.x;
if (futureCopyBallX < BALL_RADIUS || futureCopyBallX > GROUND_WIDTH) {
copyBall.xVelocity = -copyBall.xVelocity;
}
if (copyBall.y + copyBall.yVelocity < 0) {
copyBall.yVelocity = 1;
}
// If copy ball touches net
if (
Math.abs(copyBall.x - GROUND_HALF_WIDTH) < NET_PILLAR_HALF_WIDTH &&
copyBall.y > NET_PILLAR_TOP_TOP_Y_COORD
) {
// It maybe should be <= NET_PILLAR_TOP_BOTTOM_Y_COORD as in FUN_00402dc0, is it the original game author's mistake?
if (copyBall.y < NET_PILLAR_TOP_BOTTOM_Y_COORD) {
if (copyBall.yVelocity > 0) {
copyBall.yVelocity = -copyBall.yVelocity;
}
} else {
if (copyBall.x < GROUND_HALF_WIDTH) {
copyBall.xVelocity = -Math.abs(copyBall.xVelocity);
} else {
copyBall.xVelocity = Math.abs(copyBall.xVelocity);
}
}
}
copyBall.y = copyBall.y + copyBall.yVelocity;
// if copyBall would touch ground
if (
copyBall.y > BALL_TOUCHING_GROUND_Y_COORD ||
loopCounter >= INFINITE_LOOP_LIMIT
) {
break;
}
copyBall.x = copyBall.x + copyBall.xVelocity;
copyBall.yVelocity += 1;
}
ball.expectedLandingPointX = copyBall.x;
}

As you can see, the code in while (true) { ... } block (at line 746-786 of the code above) in the function calculateExpectedLandingPointXFor is similar to some part of the code in the function processCollisionBetweenBallAndWorldAndSetBallPosition, which is the following:

const futureBallX = ball.x + ball.xVelocity;
/*
If the center of ball would get out of left world bound or right world bound, bounce back.
In this if statement, when considering left-right symmetry,
"futureBallX > GROUND_WIDTH" should be changed to "futureBallX > (GROUND_WIDTH - BALL_RADIUS)",
or "futureBallX < BALL_RADIUS" should be changed to "futureBallX < 0".
Maybe the former one is more proper when seeing Pikachu player's x-direction boundary.
Is this a mistake of the author of the original game?
Or, was it set to this value to resolve inifite loop problem? (See comments on the constant INFINITE_LOOP_LIMIT.)
If apply (futureBallX > (GROUND_WIDTH - BALL_RADIUS)), and if the maximum number of loop is not limited,
it is observed that inifinite loop in the function expectedLandingPointXWhenPowerHit does not terminate.
*/
if (futureBallX < BALL_RADIUS || futureBallX > GROUND_WIDTH) {
ball.xVelocity = -ball.xVelocity;
}
let futureBallY = ball.y + ball.yVelocity;
// if the center of ball would get out of upper world bound
if (futureBallY < 0) {
ball.yVelocity = 1;
}
// If ball touches net
if (
Math.abs(ball.x - GROUND_HALF_WIDTH) < NET_PILLAR_HALF_WIDTH &&
ball.y > NET_PILLAR_TOP_TOP_Y_COORD
) {
if (ball.y <= NET_PILLAR_TOP_BOTTOM_Y_COORD) {
if (ball.yVelocity > 0) {
ball.yVelocity = -ball.yVelocity;
}
} else {
if (ball.x < GROUND_HALF_WIDTH) {
ball.xVelocity = -Math.abs(ball.xVelocity);
} else {
ball.xVelocity = Math.abs(ball.xVelocity);
}
}
}
futureBallY = ball.y + ball.yVelocity;
// if ball would touch ground
if (futureBallY > BALL_TOUCHING_GROUND_Y_COORD) {
// FUN_00408470 omitted
// the function omitted above receives 100 * (ball.x - 216),
// i.e. horizontal displacement from net maybe for stereo sound?
// code function (ballpointer + 0x28 + 0x10)? omitted
// the omitted two functions maybe do a part of sound playback role.
ball.sound.ballTouchesGround = true;
ball.yVelocity = -ball.yVelocity;
ball.punchEffectX = ball.x;
ball.y = BALL_TOUCHING_GROUND_Y_COORD;
ball.punchEffectRadius = BALL_RADIUS;
ball.punchEffectY = BALL_TOUCHING_GROUND_Y_COORD + BALL_RADIUS;
return true;
}
ball.y = futureBallY;
ball.x = ball.x + ball.xVelocity;
ball.yVelocity += 1;
return false;

What does it mean? I guess maybe you can see it now:)
Right! The calculateExpectedLandingPointXFor function first makes a copy of the ball and it just loops through the processing of collisions between the copied ball and the world, and if it detects the copied ball is touching ground, it read the x coordinate of the copied ball. This x coordinate is the point where the ball lands on the ground!

There is also a function expectedLandingPointXWhenPowerHit which is used for calculate the landing point the ball when the ball is power hit:

/**
* FUN_00402870
* This function is called by {@link decideWhetherInputPowerHit},
* and calculates the expected x coordinate of the landing point of the ball
* when power hit
* @param {PikaUserInput["xDirection"]} userInputXDirection
* @param {PikaUserInput["yDirection"]} userInputYDirection
* @param {Ball} ball
* @return {number} x coord of expected landing point when power hit the ball
*/
function expectedLandingPointXWhenPowerHit(
userInputXDirection,
userInputYDirection,
ball
) {
const copyBall = {
x: ball.x,
y: ball.y,
xVelocity: ball.xVelocity,
yVelocity: ball.yVelocity,
};
if (copyBall.x < GROUND_HALF_WIDTH) {
copyBall.xVelocity = (Math.abs(userInputXDirection) + 1) * 10;
} else {
copyBall.xVelocity = -(Math.abs(userInputXDirection) + 1) * 10;
}
copyBall.yVelocity = Math.abs(copyBall.yVelocity) * userInputYDirection * 2;
let loopCounter = 0;
while (true) {
loopCounter++;
const futureCopyBallX = copyBall.x + copyBall.xVelocity;
if (futureCopyBallX < BALL_RADIUS || futureCopyBallX > GROUND_WIDTH) {
copyBall.xVelocity = -copyBall.xVelocity;
}
if (copyBall.y + copyBall.yVelocity < 0) {
copyBall.yVelocity = 1;
}
if (
Math.abs(copyBall.x - GROUND_HALF_WIDTH) < NET_PILLAR_HALF_WIDTH &&
copyBall.y > NET_PILLAR_TOP_TOP_Y_COORD
) {
/*
The code below maybe is intended to make computer do mistakes.
The player controlled by computer occasionally power hit ball that is bounced back by the net pillar,
since code below do not anticipate the bounce back.
*/
if (copyBall.yVelocity > 0) {
copyBall.yVelocity = -copyBall.yVelocity;
}
/*
An alternative code for making the computer not do those mistakes is as below.
if (copyBall.y <= NET_PILLAR_TOP_BOTTOM_Y_COORD) {
if (copyBall.yVelocity > 0) {
copyBall.yVelocity = -copyBall.yVelocity;
}
} else {
if (copyBall.x < GROUND_HALF_WIDTH) {
copyBall.xVelocity = -Math.abs(copyBall.xVelocity);
} else {
copyBall.xVelocity = Math.abs(copyBall.xVelocity);
}
}
*/
}
copyBall.y = copyBall.y + copyBall.yVelocity;
if (
copyBall.y > BALL_TOUCHING_GROUND_Y_COORD ||
loopCounter >= INFINITE_LOOP_LIMIT
) {
return copyBall.x;
}
copyBall.x = copyBall.x + copyBall.xVelocity;
copyBall.yVelocity += 1;
}
}

I think you can now analyze this function by yourself!

If you have any more question or need some help, please let me know:)

@gorisanson gorisanson reopened this Dec 18, 2021
@DavinciEvans
Copy link
Author

Sorry for my neglect and the very very very very late response. ;)

I just noticed this open issue and saw your reply. Although I have already solved this problem using reinforcement learning and behavioural trees, thank you very much for your serious reply, which still inspires me and gives me a lot of inspiration. And it is indeed a good approach, I need to do a lot of practice to beat this AI.

I have a few more questions: although I can predict the landing point, how can I tell when to jump and when to use the power hit?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants