Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/trunk/src/orxonox/objects/controllers/PongAI.cc @ 2968

Last change on this file since 2968 was 2885, checked in by landauf, 16 years ago

Extended PongAI:

  • Random prediction errors depend on the vertical ball-speed
  • Fixed a bug: Position correction was also affected by the reaction delay. Now it works instantly.
  • Added oscillation avoidance system (the already implemented hysteresis avoidance system failed at low framerates)

Additionally fixed auto-respawn in Pong Gametype.

  • Property svn:eol-style set to native
File size: 9.3 KB
RevLine 
[2839]1/*
2 *   ORXONOX - the hottest 3D action shooter ever to exist
3 *                    > www.orxonox.net <
4 *
5 *
6 *   License notice:
7 *
8 *   This program is free software; you can redistribute it and/or
9 *   modify it under the terms of the GNU General Public License
10 *   as published by the Free Software Foundation; either version 2
11 *   of the License, or (at your option) any later version.
12 *
13 *   This program is distributed in the hope that it will be useful,
14 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
15 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 *   GNU General Public License for more details.
17 *
18 *   You should have received a copy of the GNU General Public License
19 *   along with this program; if not, write to the Free Software
20 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
21 *
22 *   Author:
23 *      Fabian 'x3n' Landau
24 *   Co-authors:
25 *      ...
26 *
27 */
28
29#include "OrxonoxStableHeaders.h"
30#include "PongAI.h"
31
32#include "core/CoreIncludes.h"
[2857]33#include "core/ConfigValueIncludes.h"
[2839]34#include "objects/worldentities/ControllableEntity.h"
35#include "objects/worldentities/PongBall.h"
[2860]36#include "tools/Timer.h"
[2839]37
38namespace orxonox
39{
40    CreateUnloadableFactory(PongAI);
41
[2860]42    const static float MAX_REACTION_TIME = 0.4;
43
[2839]44    PongAI::PongAI(BaseObject* creator) : Controller(creator)
45    {
46        RegisterObject(PongAI);
47
48        this->ball_ = 0;
[2860]49        this->ballDirection_ = Vector2::ZERO;
50        this->ballEndPosition_ = 0;
[2839]51        this->randomOffset_ = 0;
52        this->relHysteresisOffset_ = 0.02;
[2857]53        this->strength_ = 0.5;
[2860]54        this->movement_ = 0;
[2885]55        this->oldMove_ = 0;
56        this->bOscillationAvoidanceActive_ = false;
[2857]57
58        this->setConfigValues();
[2839]59    }
60
[2860]61    PongAI::~PongAI()
62    {
63        for (std::list<std::pair<Timer<PongAI>*, char> >::iterator it = this->reactionTimers_.begin(); it != this->reactionTimers_.end(); ++it)
64            delete (*it).first;
65    }
66
[2857]67    void PongAI::setConfigValues()
68    {
69        SetConfigValue(strength_, 0.5).description("A value from 0 to 1 where 0 is weak and 1 is strong.");
70    }
71
[2839]72    void PongAI::tick(float dt)
73    {
74        if (!this->ball_ || !this->getControllableEntity())
75            return;
76
[2860]77        Vector3 mypos = this->getControllableEntity()->getPosition();
[2839]78        Vector3 ballpos = this->ball_->getPosition();
79        Vector3 ballvel = this->ball_->getVelocity();
80        float hysteresisOffset = this->relHysteresisOffset_ * this->ball_->getFieldDimension().y;
81
[2860]82        char move = 0;
[2885]83        bool delay = false;
[2860]84
[2839]85        // Check in which direction the ball is flying
86        if ((mypos.x > 0 && ballvel.x < 0) || (mypos.x < 0 && ballvel.x > 0))
87        {
[2872]88            // The ball is flying away
[2860]89            this->ballDirection_.x = -1;
90            this->ballDirection_.y = 0;
[2885]91            this->bOscillationAvoidanceActive_ = false;
[2839]92
[2872]93            // Move to the middle
[2839]94            if (mypos.z > hysteresisOffset)
[2860]95                move = 1;
[2839]96            else if (mypos.z < -hysteresisOffset)
[2860]97                move = -1;
[2839]98        }
99        else if (ballvel.x == 0)
100        {
[2872]101            // The ball is standing still
[2860]102            this->ballDirection_.x = 0;
103            this->ballDirection_.y = 0;
[2885]104            this->bOscillationAvoidanceActive_ = false;
[2839]105        }
106        else
107        {
[2872]108            // The ball is approaching
[2860]109            if (this->ballDirection_.x != 1)
110            {
[2872]111                // The ball just startet to approach, initialize all values
[2860]112                this->ballDirection_.x = 1;
113                this->ballDirection_.y = sgn(ballvel.z);
114                this->ballEndPosition_ = 0;
115                this->randomOffset_ = 0;
[2839]116
[2860]117                this->calculateRandomOffset();
118                this->calculateBallEndPosition();
[2885]119                delay = true;
120                this->bOscillationAvoidanceActive_ = false;
[2860]121            }
122
123            if (this->ballDirection_.y != sgn(ballvel.z))
124            {
[2872]125                // The ball just bounced from a bound, recalculate the predicted end position
[2860]126                this->ballDirection_.y = sgn(ballvel.z);
127
128                this->calculateBallEndPosition();
[2885]129                delay = true;
130                this->bOscillationAvoidanceActive_ = false;
[2860]131            }
132
[2872]133            // Move to the predicted end position with an additional offset (to hit the ball with the side of the bat)
[2885]134            if (!this->bOscillationAvoidanceActive_)
135            {
136                float desiredZValue = this->ballEndPosition_ + this->randomOffset_;
[2860]137
[2885]138                if (mypos.z > desiredZValue + hysteresisOffset * (this->randomOffset_ < 0))
139                    move = 1;
140                else if (mypos.z < desiredZValue - hysteresisOffset * (this->randomOffset_ > 0))
141                    move = -1;
142            }
143
144            if (move != 0 && this->oldMove_ != 0 && move != this->oldMove_ && !delay)
145            {
146                // We had to correct our position because we moved too far
147                // (and delay ist false, so we're not in the wrong place because of a new end-position prediction)
148                if (fabs(mypos.z - this->ballEndPosition_) < 0.5 * this->ball_->getBatLength() * this->ball_->getFieldDimension().y)
149                {
150                    // We're not at the right position, but we still hit the ball, so just stay there to avoid oscillation
151                    move = 0;
152                    this->bOscillationAvoidanceActive_ = true;
153                }
154            }
[2839]155        }
[2860]156
[2885]157        this->oldMove_ = move;
158        this->move(move, delay);
[2860]159        this->getControllableEntity()->moveFrontBack(this->movement_);
[2839]160    }
161
162    void PongAI::calculateRandomOffset()
163    {
[2857]164        // Calculate the exponent for the position-formula
165        float exp = pow(10, 1 - 2*this->strength_); // strength: 0   -> exp = 10
166                                                    // strength: 0.5 -> exp = 1
167                                                    // strength: 1   -> exp = 0.1
168
169        // Calculate the relative position where to hit the ball with the bat
170        float position = pow(rnd(), exp); // exp > 1 -> position is more likely a small number
171                                          // exp < 1 -> position is more likely a large number
172
173        // The position shouln't be larger than 0.5 (50% of the bat-length from the middle is the end)
[2860]174        position *= 0.48;
[2857]175
176        // Both sides are equally probable
[2872]177        position *= rndsgn();
[2857]178
179        // Calculate the offset in world units
180        this->randomOffset_ = position * this->ball_->getBatLength() * this->ball_->getFieldDimension().y;
[2839]181    }
[2860]182
183    void PongAI::calculateBallEndPosition()
184    {
185        Vector3 position = this->ball_->getPosition();
186        Vector3 velocity = this->ball_->getVelocity();
187        Vector2 dimension = this->ball_->getFieldDimension();
188
189        // calculate end-height: current height + slope * distance
190        this->ballEndPosition_ = position.z + velocity.z / velocity.x * (-position.x + dimension.x / 2 * sgn(velocity.x));
191
192        // Calculate bounces
193        for (float limit = 0.35; limit < this->strength_ || this->strength_ > 0.99; limit += 0.4)
194        {
[2885]195            // Calculate a random prediction error, based on the vertical speed of the ball and the strength of the AI
196            float randomError = rnd(-1, 1) * dimension.y * (velocity.z / velocity.x / PongBall::MAX_REL_Z_VELOCITY) * (1 - this->strength_);
197
198            // Bounce from the lower bound
[2860]199            if (this->ballEndPosition_ > dimension.y / 2)
200            {
[2872]201                // Mirror the predicted position at the upper bound and add some random error
[2885]202                this->ballEndPosition_ = dimension.y - this->ballEndPosition_ + randomError;
[2860]203                continue;
204            }
[2872]205            // Bounce from the upper bound
[2860]206            if (this->ballEndPosition_ < -dimension.y / 2)
207            {
[2872]208                // Mirror the predicted position at the lower bound and add some random error
[2885]209                this->ballEndPosition_ = -dimension.y - this->ballEndPosition_ + randomError;
[2860]210                continue;
211            }
[2872]212            // No bounce - break
[2860]213            break;
214        }
215    }
216
[2885]217    void PongAI::move(char direction, bool bUseDelay)
[2860]218    {
219        // The current direction is either what we're doing right now (movement_) or what is last in the queue
220        char currentDirection = this->movement_;
221        if (this->reactionTimers_.size() > 0)
222            currentDirection = this->reactionTimers_.back().second;
223
224        // Only add changes of direction
225        if (direction == currentDirection)
226            return;
227
[2885]228        if (bUseDelay)
[2860]229        {
[2885]230            // Calculate delay
[2860]231            float delay = MAX_REACTION_TIME * (1 - this->strength_);
232
233            // Add a new Timer
234            Timer<PongAI>* timer = new Timer<PongAI>(delay, false, this, createExecutor(createFunctor(&PongAI::delayedMove)));
235            this->reactionTimers_.push_back(std::pair<Timer<PongAI>*, char>(timer, direction));
236        }
237        else
238        {
[2885]239            this->movement_ = direction;
[2860]240        }
241    }
242
243    void PongAI::delayedMove()
244    {
[2872]245        // Get the new movement direction from the timer list
[2860]246        this->movement_ = this->reactionTimers_.front().second;
247
[2872]248        // Destroy the timer and remove it from the list
[2860]249        Timer<PongAI>* timer = this->reactionTimers_.front().first;
250        delete timer;
251
252        this->reactionTimers_.pop_front();
253    }
[2839]254}
Note: See TracBrowser for help on using the repository browser.