Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/trunk/src/modules/pong/PongAI.cc @ 7940

Last change on this file since 7940 was 6417, checked in by rgrieder, 15 years ago

Merged presentation2 branch back to trunk.
Major new features:

  • Actual GUI with settings, etc.
  • Improved space ship steering (human interaction)
  • Rocket fire and more particle effects
  • Advanced sound framework
  • Property svn:eol-style set to native
File size: 12.5 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 "PongAI.h"
30
31#include "core/CoreIncludes.h"
[2857]32#include "core/ConfigValueIncludes.h"
[3196]33#include "tools/Timer.h"
[5735]34#include "worldentities/ControllableEntity.h"
[5725]35#include "PongBall.h"
[2839]36
37namespace orxonox
38{
39    CreateUnloadableFactory(PongAI);
40
[3196]41    const static float MAX_REACTION_TIME = 0.4f;
[2860]42
[2839]43    PongAI::PongAI(BaseObject* creator) : Controller(creator)
44    {
45        RegisterObject(PongAI);
46
47        this->ball_ = 0;
[2860]48        this->ballDirection_ = Vector2::ZERO;
49        this->ballEndPosition_ = 0;
[2839]50        this->randomOffset_ = 0;
[5929]51        this->bChangedRandomOffset_ = false;
[3196]52        this->relHysteresisOffset_ = 0.02f;
53        this->strength_ = 0.5f;
[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    {
[5929]63        for (std::list<std::pair<Timer*, char> >::iterator it = this->reactionTimers_.begin(); it != this->reactionTimers_.end(); ++it)
[6417]64            it->first->destroy();
[2860]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;
[5929]116                this->bChangedRandomOffset_ = false;
[2839]117
[2860]118                this->calculateRandomOffset();
119                this->calculateBallEndPosition();
[2885]120                delay = true;
121                this->bOscillationAvoidanceActive_ = false;
[2860]122            }
123
124            if (this->ballDirection_.y != sgn(ballvel.z))
125            {
[2872]126                // The ball just bounced from a bound, recalculate the predicted end position
[2860]127                this->ballDirection_.y = sgn(ballvel.z);
128
129                this->calculateBallEndPosition();
[2885]130                delay = true;
131                this->bOscillationAvoidanceActive_ = false;
[2860]132            }
[6417]133
[5929]134            // If the ball is close enough, calculate another random offset to accelerate the ball
135            if (!this->bChangedRandomOffset_)
136            {
137                float timetohit = (-this->ball_->getPosition().x + this->ball_->getFieldDimension().x / 2 * sgn(this->ball_->getVelocity().x)) / this->ball_->getVelocity().x;
138                if (timetohit < 0.05)
139                {
140                    this->bChangedRandomOffset_ = true;
141                    if (rnd() < this->strength_)
142                        this->calculateRandomOffset();
143                }
144            }
[2860]145
[2872]146            // Move to the predicted end position with an additional offset (to hit the ball with the side of the bat)
[2885]147            if (!this->bOscillationAvoidanceActive_)
148            {
149                float desiredZValue = this->ballEndPosition_ + this->randomOffset_;
[2860]150
[2885]151                if (mypos.z > desiredZValue + hysteresisOffset * (this->randomOffset_ < 0))
152                    move = 1;
153                else if (mypos.z < desiredZValue - hysteresisOffset * (this->randomOffset_ > 0))
154                    move = -1;
155            }
156
157            if (move != 0 && this->oldMove_ != 0 && move != this->oldMove_ && !delay)
158            {
159                // We had to correct our position because we moved too far
160                // (and delay ist false, so we're not in the wrong place because of a new end-position prediction)
161                if (fabs(mypos.z - this->ballEndPosition_) < 0.5 * this->ball_->getBatLength() * this->ball_->getFieldDimension().y)
162                {
163                    // We're not at the right position, but we still hit the ball, so just stay there to avoid oscillation
164                    move = 0;
165                    this->bOscillationAvoidanceActive_ = true;
166                }
167            }
[2839]168        }
[2860]169
[2885]170        this->oldMove_ = move;
171        this->move(move, delay);
[2860]172        this->getControllableEntity()->moveFrontBack(this->movement_);
[2839]173    }
174
175    void PongAI::calculateRandomOffset()
176    {
[2857]177        // Calculate the exponent for the position-formula
178        float exp = pow(10, 1 - 2*this->strength_); // strength: 0   -> exp = 10
179                                                    // strength: 0.5 -> exp = 1
180                                                    // strength: 1   -> exp = 0.1
181
182        // Calculate the relative position where to hit the ball with the bat
183        float position = pow(rnd(), exp); // exp > 1 -> position is more likely a small number
184                                          // exp < 1 -> position is more likely a large number
185
186        // The position shouln't be larger than 0.5 (50% of the bat-length from the middle is the end)
[3196]187        position *= 0.48f;
[2857]188
189        // Both sides are equally probable
[2872]190        position *= rndsgn();
[2857]191
192        // Calculate the offset in world units
193        this->randomOffset_ = position * this->ball_->getBatLength() * this->ball_->getFieldDimension().y;
[2839]194    }
[2860]195
196    void PongAI::calculateBallEndPosition()
197    {
198        Vector3 position = this->ball_->getPosition();
199        Vector3 velocity = this->ball_->getVelocity();
[5929]200        Vector3 acceleration = this->ball_->getAcceleration();
[2860]201        Vector2 dimension = this->ball_->getFieldDimension();
202
[5929]203        // Calculate bounces. The number of predicted bounces is limited by the AIs strength
204        for (float limit = -0.05f; limit < this->strength_ || this->strength_ > 0.99f; limit += 0.4f)
[2860]205        {
[5929]206            // calculate the time until the ball reaches the other side
207            float totaltime = (-position.x + dimension.x / 2 * sgn(velocity.x)) / velocity.x;
[6417]208
[5929]209            // calculate wall bounce position (four possible solutions of the equation: pos.z + vel.z*t + acc.z/2*t^2 = +/- dim.z/2)
210            float bouncetime = totaltime;
211            bool bUpperWall = false;
[6417]212
[5929]213            if (acceleration.z == 0)
214            {
215                if (velocity.z > 0)
216                {
217                    bUpperWall = true;
218                    bouncetime = (dimension.y/2 - position.z) / velocity.z;
219                }
220                else if (velocity.z < 0)
221                {
222                    bUpperWall = false;
223                    bouncetime = (-dimension.y/2 - position.z) / velocity.z;
224                }
225            }
226            else
227            {
228                // upper wall
229                float temp = velocity.z*velocity.z + 2*acceleration.z*(dimension.y/2 - position.z);
230                if (temp >= 0)
231                {
232                    float t1 = (sqrt(temp) - velocity.z) / acceleration.z;
233                    float t2 = (sqrt(temp) + velocity.z) / acceleration.z * (-1);
234                    if (t1 > 0 && t1 < bouncetime)
235                    {
236                        bouncetime = t1;
237                        bUpperWall = true;
238                    }
239                    if (t2 > 0 && t2 < bouncetime)
240                    {
241                        bouncetime = t2;
242                        bUpperWall = true;
243                    }
244                }
245                // lower wall
246                temp = velocity.z*velocity.z - 2*acceleration.z*(dimension.y/2 + position.z);
247                if (temp >= 0)
248                {
249                    float t1 = (sqrt(temp) - velocity.z) / acceleration.z;
250                    float t2 = (sqrt(temp) + velocity.z) / acceleration.z * (-1);
251                    if (t1 > 0 && t1 < bouncetime)
252                    {
253                        bouncetime = t1;
254                        bUpperWall = false;
255                    }
256                    if (t2 > 0 && t2 < bouncetime)
257                    {
258                        bouncetime = t2;
259                        bUpperWall = false;
260                    }
261                }
262            }
[2885]263
[5929]264            if (bouncetime < totaltime)
[2860]265            {
[5929]266                // Calculate a random prediction error, based on the vertical speed of the ball and the strength of the AI
267                float randomErrorX = rnd(-1, 1) * dimension.y * (velocity.z / velocity.x / PongBall::MAX_REL_Z_VELOCITY) * (1 - this->strength_);
268                float randomErrorZ = rnd(-1, 1) * dimension.y * (velocity.z / velocity.x / PongBall::MAX_REL_Z_VELOCITY) * (1 - this->strength_);
269
270                // ball bounces after <bouncetime> seconds, update the position and continue
271                velocity.z = velocity.z + acceleration.z * bouncetime;
[6417]272
[5929]273                if (bUpperWall)
274                {
275                    position.z = dimension.y / 2;
276                    velocity.z = -fabs(velocity.z) + fabs(randomErrorZ);
277                }
278                else
279                {
280                    position.z = -dimension.y / 2;
281                    velocity.z = fabs(velocity.z) - fabs(randomErrorZ);
282                }
[6417]283
[5929]284                position.x = position.x + velocity.x * bouncetime + randomErrorX;
285                this->ballEndPosition_ = position.z;
[2860]286            }
[5929]287            else
[2860]288            {
[5929]289                // ball doesn't bounce, calculate the end position and return
290                // calculate end-height: current height + slope * distance incl. acceleration
291                this->ballEndPosition_ = position.z + velocity.z * totaltime + acceleration.z / 2 * totaltime * totaltime;
292                return;
[2860]293            }
294        }
295    }
296
[2885]297    void PongAI::move(char direction, bool bUseDelay)
[2860]298    {
299        // The current direction is either what we're doing right now (movement_) or what is last in the queue
300        char currentDirection = this->movement_;
301        if (this->reactionTimers_.size() > 0)
302            currentDirection = this->reactionTimers_.back().second;
303
304        // Only add changes of direction
305        if (direction == currentDirection)
306            return;
307
[2885]308        if (bUseDelay)
[2860]309        {
[2885]310            // Calculate delay
[2860]311            float delay = MAX_REACTION_TIME * (1 - this->strength_);
312
313            // Add a new Timer
[5929]314            Timer* timer = new Timer(delay, false, createExecutor(createFunctor(&PongAI::delayedMove, this)));
315            this->reactionTimers_.push_back(std::pair<Timer*, char>(timer, direction));
[2860]316        }
317        else
318        {
[2885]319            this->movement_ = direction;
[2860]320        }
321    }
322
323    void PongAI::delayedMove()
324    {
[2872]325        // Get the new movement direction from the timer list
[2860]326        this->movement_ = this->reactionTimers_.front().second;
327
[2872]328        // Destroy the timer and remove it from the list
[5929]329        Timer* timer = this->reactionTimers_.front().first;
330        timer->destroy();
[2860]331
332        this->reactionTimers_.pop_front();
333    }
[2839]334}
Note: See TracBrowser for help on using the repository browser.