Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

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

Last change on this file since 6372 was 5929, checked in by rgrieder, 15 years ago

Merged core5 branch back to the trunk.
Key features include clean level unloading and an extended XML event system.

Two important notes:
Delete your keybindings.ini files! * or you will still get parser errors when loading the key bindings.
Delete build_dir/lib/modules/libgamestates.module! * or orxonox won't start.
Best thing to do is to delete the build folder ;)

  • Property svn:eol-style set to native
File size: 12.6 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)
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            }
[5929]133           
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;
208           
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;
212           
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;
272               
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                }
283                   
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.