如何通过有限的游戏状态检查来改进 Pong AI?

问题描述 投票:0回答:1

我目前正在为一个学校项目使用 JavaScript 开发 Pong 游戏。我的一条规则是人工智能每秒只能检查一次游戏状态。我正在寻找增强人工智能性能的方法。

目前,人工智能的运行原理很简单:

  • 如果球远离 AI,它会返回到棋盘的中心。
  • 如果球朝 AI 靠近,它会尝试接住球,然后返回中心。

当球慢时,这种策略效果很好,因为人工智能总是有足够的时间来评估情况并做出反应,使其能够接住每一个球。然而,当球以正常速度移动时,人工智能通常只能在球击中玩家球拍之前看到它。这使得人工智能没有时间做出反应,从而导致错失机会。因此,我正在探索解决这个问题的策略,可能是通过预测球最终的去向。

这是人工智能的代码:

import * as g from './global.js';
import * as game from './game.js';
import * as physics from './physics.js';

/*
 * The behaves according to the following rules:
 *
 * Since we can look at the state of the game once per second, it means
 * that we have a 'budget' of 60 ticks to use to make our actions.
 *
 * The budget is divided as follow :
 * | move towards the ball | wait for collision | move back towards the center | wait for the remainder of the budget |
 *
*/

export class AIManager {
    constructor() {
        this.last_looked = 0.0;
        this.game = new game.Game();

        this.collision_point = new physics.Vector(0, 0);

        this.inputs = [];
        this.nput_movement = g.INPUT_NEUTRAL;
        this.input_center = g.INPUT_NEUTRAL;

        this.tick_budget = 60; /* The number of ticks we have before the next time we update our view */
        this.tick_collision = 0; /* The number of ticks before the ball collide with the paddle */
        this.tick_movement = 0; /* The minimum number of ticks before the paddle reaches the collision point */
        this.tick_center = 0; /* The number of ticks need to go back to the center of the board */

        this.intersect_area = new physics.Rectangle(0, 2 * g.BOARD_WALL, g.BOARD_WIDTH, g.BALL_SIDE, 0, 0);
    }

    reset() {
        this.last_looked = performance.now();
        this.game = new game.Game();

        this.collision_point = new physics.Vector(0, 0);

        this.inputs = [];
        this.nput_movement = g.INPUT_NEUTRAL;
        this.input_center = g.INPUT_NEUTRAL;

        this.tick_budget = 60; /* The number of ticks we have before the next time we update our view */
        this.tick_collision = 0; /* The number of ticks before the ball collide with the paddle */
        this.tick_movement = 0; /* The minimum number of ticks before the paddle reaches the collision point */
        this.tick_center = 0; /* The number of ticks need to go back to the center of the board */

        this.intersect_area = new physics.Rectangle(0, 2 * g.BOARD_WALL, g.BOARD_WIDTH, g.BALL_SIDE, 0, 0);
    }

    refresh(game, dt) {

        const elapsed = performance.now() - this.last_looked;

        if (elapsed >= 1000) {

            this.reset();

            /* Load the game state into the AI's 'memory' */
            this.load_state(game);

            if (this.game.ball.velocity.y >= 0) {
                this.tick_movement = 0;
                this.tick_collision = 0;
                this.handle_reset(dt);
            } else {
                this.handle_collision(dt);
                this.handle_movement(dt);
                this.handle_reset(dt);
            }

            this.queue_moves();
        }

        return this.inputs.shift();
    }

    handle_collision(dt) {
        let tick = 0;
        let collision = null;
        let collision_happened = false;

        while (!collision_happened && tick < 120) {
            tick += 1;

            collision = physics.aabb_continuous_detection(this.game.ball, this.intersect_area, dt);
            if (collision.time > 0 && collision.time <= 1.0) {
                collision_happened = true;
            } else {
                this.game.ball.position.x += this.game.ball.velocity.x * dt;
                this.game.ball.position.y += this.game.ball.velocity.y * dt;
            }

            if (this.game.ball.position.x <= g.BOARD_WALL || this.game.ball.position.x + this.game.ball.size.x >= g.BOARD_WIDTH - g.BOARD_WALL) {
                /* Left and right walls */
                this.game.ball.position.x = this.game.ball.position.x <= g.BOARD_WALL ? g.BOARD_WALL : g.BOARD_WIDTH - this.game.ball.size.x - g.BOARD_WALL;
                this.game.ball.velocity.x *= -1;
            }
        }

        this.tick_collision = tick;
        this.collision_point.x = collision.point.x;
        this.collision_point.y = collision.point.y;
    }

    handle_movement(dt) {
        let tick = 0;
        const paddle = this.game.player2;

        const paddle_center = this.game.player2.position.x + (this.game.player2.size.x / 2);
        if (this.collision_point.x < paddle_center) {
            this.game.player2.velocity.x = -g.PADDLE_SPEED;
            this.nput_movement = g.INPUT_LEFT;
        } else {
            this.game.player2.velocity.x = g.PADDLE_SPEED;
            this.nput_movement = g.INPUT_RIGHT;
        }

        while (!(paddle.position.x < this.collision_point.x && paddle.position.x + paddle.size.x >= this.collision_point.x) && tick < 60) {
            tick += 1;
            if (paddle.position.x + paddle.velocity.x * dt > g.BOARD_CORRIDOR && paddle.position.x + paddle.size.x + paddle.velocity.x * dt < g.BOARD_WIDTH - g.BOARD_CORRIDOR) {
                paddle.position.x += paddle.velocity.x * dt;
            }
        }

        this.tick_movement = tick;
    }

    handle_reset(dt) {
        const board_center = g.BOARD_WIDTH / 2;
        const paddle_center = this.game.player2.position.x + (this.game.player2.size.x / 2);
        const distance_from_center = board_center - paddle_center;
        const velocity = board_center <= paddle_center ? -g.PADDLE_SPEED : g.PADDLE_SPEED;
        const delta_move = velocity * dt;

        if (distance_from_center < 0) {
            this.input_center = g.INPUT_LEFT;
        } else if (distance_from_center > 0) {
            this.input_center = g.INPUT_RIGHT;
        }

        this.tick_center = Math.abs(Math.floor(distance_from_center / delta_move));
    }

    queue_moves() {
        for (let i = 0; i < this.tick_movement && this.tick_budget > 0; i++) {
            this.inputs.push(this.nput_movement);
            this.tick_budget -= 1;
        }
        for (let i = 0; i < this.tick_collision && this.tick_budget > 0; i++) {
            this.inputs.push(g.INPUT_NEUTRAL);
            this.tick_budget -= 1;
        }
        for (let i = 0; i < this.tick_center && this.tick_budget > 0; i++) {
            this.inputs.push(this.input_center);
            this.tick_budget -= 1;
        }
        for (let i = 0; this.tick_budget > 0; i++) {
            this.inputs.push(g.INPUT_NEUTRAL);
            this.tick_budget -= 1;
        }
    }

    load_state(game) {
        this.game.status = game.status;
        this.game.who_serves = game.who_serves;
        this.game.ball.position.x = game.ball.position.x;
        this.game.ball.position.y = game.ball.position.y;
        this.game.ball.velocity.x = game.ball.velocity.x;
        this.game.ball.velocity.y = game.ball.velocity.y;
        this.game.player1.position.x = game.player1.position.x;
        this.game.player2.position.x = game.player2.position.x;
    }
}

javascript artificial-intelligence pong
1个回答
0
投票

您可以预测球的位置,这样您的 AI 就可以快速做出反应,即使每秒只检查一次游戏状态...... 我不确定你的游戏基础设施以及所有内容是如何计算的,但这应该让你对我想要做什么有一个模糊的想法...... 这将导致更快的反应

预测球位置功能:

predictBallPositionAfterSecond() {
    // Clone the ball's position and velocity to not affect the actual game state
    let predictedPosition = {...this.game.ball.position};
    let predictedVelocity = {...this.game.ball.velocity};

    let timeRemaining = 1.0; // Predict for 1 second into the future
    const dt = 0.016; // Assuming a frame rate of 60 FPS, so each tick is approximately 1/60th of a second

    while (timeRemaining > 0) {
        // Calculate time to next collision with top or bottom wall
        let timeToCollision;
        if (predictedVelocity.y > 0) {
            // Moving downwards
            timeToCollision = (g.BOARD_HEIGHT - g.BOARD_WALL - predictedPosition.y - this.game.ball.size.y) / predictedVelocity.y;
        } else {
            // Moving upwards
            timeToCollision = (g.BOARD_WALL - predictedPosition.y) / predictedVelocity.y;
        }

        // If no collision within the next tick, move ball for the entire tick
        if (timeToCollision > dt || timeToCollision < 0) {
            predictedPosition.x += predictedVelocity.x * dt;
            predictedPosition.y += predictedVelocity.y * dt;
            timeRemaining -= dt;
        } else {
            // Move ball to the point of collision
            predictedPosition.x += predictedVelocity.x * timeToCollision;
            predictedPosition.y += predictedVelocity.y * timeToCollision;

            // Reflect the ball's velocity
            predictedVelocity.y *= -1;

            // Subtract the elapsed time to collision from the remaining time
            timeRemaining -= timeToCollision;
        }

        // Check for collision with left and right walls to reverse X direction if necessary
        // This part can be adjusted based on your game's rules about side wall bounces
        if (predictedPosition.x <= g.BOARD_WALL || predictedPosition.x + this.game.ball.size.x >= g.BOARD_WIDTH - g.BOARD_WALL) {
            predictedVelocity.x *= -1;
        }
    }

    return predictedPosition;
}

在代码中使用预测函数:

if (elapsed >= 1000) {
    this.reset();
    this.load_state(game);

    // Predict the ball's position
    const predictedPosition = this.predictBallPositionAfterSecond();

    // Now you can use predictedPosition to decide how the AI should move
}

请记住,为了进行精确的预测,您需要调整碰撞和弹跳逻辑以匹配您的游戏规则。基本上,您的预测越能模仿游戏的物理原理,就越准确。

© www.soinside.com 2019 - 2024. All rights reserved.