Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

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

Last change on this file since 8460 was 8108, checked in by dafrick, 14 years ago

Merging changes from tetris branch into trunk, since they are also useful, there.

  • Property svn:eol-style set to native
File size: 14.1 KB
Line 
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/**
30    @file PongAI.cc
31    @brief Implementation of the PongAI class.
32*/
33
34#include "PongAI.h"
35
36#include "core/CoreIncludes.h"
37#include "core/ConfigValueIncludes.h"
38#include "tools/Timer.h"
39
40#include "worldentities/ControllableEntity.h"
41
42#include "PongBall.h"
43
44namespace orxonox
45{
46    CreateUnloadableFactory(PongAI);
47
48    const static float MAX_REACTION_TIME = 0.4f;
49
50    /**
51    @brief
52        Constructor. Registers and initializes the object.
53    */
54    PongAI::PongAI(BaseObject* creator) : Controller(creator)
55    {
56        RegisterObject(PongAI);
57
58        this->ball_ = 0;
59        this->ballDirection_ = Vector2::ZERO;
60        this->ballEndPosition_ = 0;
61        this->randomOffset_ = 0;
62        this->bChangedRandomOffset_ = false;
63        this->relHysteresisOffset_ = 0.02f;
64        this->strength_ = 0.5f;
65        this->movement_ = 0;
66        this->oldMove_ = 0;
67        this->bOscillationAvoidanceActive_ = false;
68
69        this->setConfigValues();
70    }
71
72    /**
73    @brief
74        Destructor. Cleans up the list fo reaction timers.
75    */
76    PongAI::~PongAI()
77    {
78        for (std::list<std::pair<Timer*, char> >::iterator it = this->reactionTimers_.begin(); it != this->reactionTimers_.end(); ++it)
79            it->first->destroy();
80    }
81
82    /**
83    @brief
84        Sets config values.
85    */
86    void PongAI::setConfigValues()
87    {
88        // Sets the strength of the PongAi as a config value.
89        SetConfigValue(strength_, 0.5).description("A value from 0 to 1 where 0 is weak and 1 is strong.");
90    }
91
92    /**
93    @brief
94        Is called each tick.
95        Implements the behavior of the PongAI (i.e. its intelligence).
96    @param dt
97        The time that has elapsed since the last tick.
98    */
99    void PongAI::tick(float dt)
100    {
101        // If either the ball, or the controllable entity (i.e. the bat) don't exist (or aren't set).
102        if (this->ball_  == NULL || this->getControllableEntity() == NULL)
103            return;
104
105        Vector3 mypos = this->getControllableEntity()->getPosition();
106        Vector3 ballpos = this->ball_->getPosition();
107        Vector3 ballvel = this->ball_->getVelocity();
108        float hysteresisOffset = this->relHysteresisOffset_ * this->ball_->getFieldDimension().y;
109
110        char move = 0;
111        bool delay = false;
112
113        // Check in which direction the ball is flying
114        if ((mypos.x > 0 && ballvel.x < 0) || (mypos.x < 0 && ballvel.x > 0))
115        {
116            // The ball is flying away
117            this->ballDirection_.x = -1;
118            this->ballDirection_.y = 0;
119            this->bOscillationAvoidanceActive_ = false;
120
121            // Move to the middle
122            if (mypos.z > hysteresisOffset)
123                move = 1;
124            else if (mypos.z < -hysteresisOffset)
125                move = -1;
126        }
127        else if (ballvel.x == 0)
128        {
129            // The ball is standing still
130            this->ballDirection_.x = 0;
131            this->ballDirection_.y = 0;
132            this->bOscillationAvoidanceActive_ = false;
133        }
134        else
135        {
136            // The ball is approaching
137            if (this->ballDirection_.x != 1)
138            {
139                // The ball just started to approach, initialize all values
140                this->ballDirection_.x = 1;
141                this->ballDirection_.y = sgn(ballvel.z);
142                this->ballEndPosition_ = 0;
143                this->randomOffset_ = 0;
144                this->bChangedRandomOffset_ = false;
145
146                this->calculateRandomOffset();
147                this->calculateBallEndPosition();
148                delay = true;
149                this->bOscillationAvoidanceActive_ = false;
150            }
151
152            if (this->ballDirection_.y != sgn(ballvel.z))
153            {
154                // The ball just bounced from a bound, recalculate the predicted end position
155                this->ballDirection_.y = sgn(ballvel.z);
156
157                this->calculateBallEndPosition();
158                delay = true;
159                this->bOscillationAvoidanceActive_ = false;
160            }
161
162            // If the ball is close enough, calculate another random offset to accelerate the ball
163            if (!this->bChangedRandomOffset_)
164            {
165                float timetohit = (-this->ball_->getPosition().x + this->ball_->getFieldDimension().x / 2 * sgn(this->ball_->getVelocity().x)) / this->ball_->getVelocity().x;
166                if (timetohit < 0.05)
167                {
168                    this->bChangedRandomOffset_ = true;
169                    if (rnd() < this->strength_)
170                        this->calculateRandomOffset();
171                }
172            }
173
174            // Move to the predicted end position with an additional offset (to hit the ball with the side of the bat)
175            if (!this->bOscillationAvoidanceActive_)
176            {
177                float desiredZValue = this->ballEndPosition_ + this->randomOffset_;
178
179                if (mypos.z > desiredZValue + hysteresisOffset * (this->randomOffset_ < 0))
180                    move = 1;
181                else if (mypos.z < desiredZValue - hysteresisOffset * (this->randomOffset_ > 0))
182                    move = -1;
183            }
184
185            if (move != 0 && this->oldMove_ != 0 && move != this->oldMove_ && !delay)
186            {
187                // We had to correct our position because we moved too far
188                // (and delay is false, so we're not in the wrong place because of a new end-position prediction)
189                if (fabs(mypos.z - this->ballEndPosition_) < 0.5 * this->ball_->getBatLength() * this->ball_->getFieldDimension().y)
190                {
191                    // We're not at the right position, but we still hit the ball, so just stay there to avoid oscillation
192                    move = 0;
193                    this->bOscillationAvoidanceActive_ = true;
194                }
195            }
196        }
197
198        this->oldMove_ = move;
199        this->move(move, delay);
200        this->getControllableEntity()->moveFrontBack(this->movement_);
201    }
202
203    /**
204    @brief
205        Calculates the random offset, that accounts for random errors the AI makes in order to be beatable.
206        The higher the strength of the AI, the smaller the (expected value of the) error.
207        The result of this method is stored in this->randomOffset_.
208    */
209    void PongAI::calculateRandomOffset()
210    {
211        // Calculate the exponent for the position-formula
212        float exp = pow(10, 1 - 2*this->strength_); // strength: 0   -> exp = 10
213                                                    // strength: 0.5 -> exp = 1
214                                                    // strength: 1   -> exp = 0.1
215
216        // Calculate the relative position where to hit the ball with the bat
217        float position = pow(rnd(), exp); // exp > 1 -> position is more likely a small number
218                                          // exp < 1 -> position is more likely a large number
219
220        // The position shouldn't be larger than 0.5 (50% of the bat-length from the middle is the end)
221        position *= 0.48f;
222
223        // Both sides are equally probable
224        position *= rndsgn();
225
226        // Calculate the offset in world units
227        this->randomOffset_ = position * this->ball_->getBatLength() * this->ball_->getFieldDimension().y;
228    }
229
230    /**
231    @brief
232        Calculate the end position the ball will be in.
233        The result of this calculation is stored in this->ballEndPosition_.
234    */
235    void PongAI::calculateBallEndPosition()
236    {
237        Vector3 position = this->ball_->getPosition();
238        Vector3 velocity = this->ball_->getVelocity();
239        Vector3 acceleration = this->ball_->getAcceleration();
240        Vector2 dimension = this->ball_->getFieldDimension();
241
242        // Calculate bounces. The number of predicted bounces is limited by the AIs strength
243        for (float limit = -0.05f; limit < this->strength_ || this->strength_ > 0.99f; limit += 0.4f)
244        {
245            // calculate the time until the ball reaches the other side
246            float totaltime = (-position.x + dimension.x / 2 * sgn(velocity.x)) / velocity.x;
247
248            // calculate wall bounce position (four possible solutions of the equation: pos.z + vel.z*t + acc.z/2*t^2 = +/- dim.z/2)
249            float bouncetime = totaltime;
250            bool bUpperWall = false;
251
252            if (acceleration.z == 0)
253            {
254                if (velocity.z > 0)
255                {
256                    bUpperWall = true;
257                    bouncetime = (dimension.y/2 - position.z) / velocity.z;
258                }
259                else if (velocity.z < 0)
260                {
261                    bUpperWall = false;
262                    bouncetime = (-dimension.y/2 - position.z) / velocity.z;
263                }
264            }
265            else
266            {
267                // upper wall
268                float temp = velocity.z*velocity.z + 2*acceleration.z*(dimension.y/2 - position.z);
269                if (temp >= 0)
270                {
271                    float t1 = (sqrt(temp) - velocity.z) / acceleration.z;
272                    float t2 = (sqrt(temp) + velocity.z) / acceleration.z * (-1);
273                    if (t1 > 0 && t1 < bouncetime)
274                    {
275                        bouncetime = t1;
276                        bUpperWall = true;
277                    }
278                    if (t2 > 0 && t2 < bouncetime)
279                    {
280                        bouncetime = t2;
281                        bUpperWall = true;
282                    }
283                }
284                // lower wall
285                temp = velocity.z*velocity.z - 2*acceleration.z*(dimension.y/2 + position.z);
286                if (temp >= 0)
287                {
288                    float t1 = (sqrt(temp) - velocity.z) / acceleration.z;
289                    float t2 = (sqrt(temp) + velocity.z) / acceleration.z * (-1);
290                    if (t1 > 0 && t1 < bouncetime)
291                    {
292                        bouncetime = t1;
293                        bUpperWall = false;
294                    }
295                    if (t2 > 0 && t2 < bouncetime)
296                    {
297                        bouncetime = t2;
298                        bUpperWall = false;
299                    }
300                }
301            }
302
303            if (bouncetime < totaltime)
304            {
305                // Calculate a random prediction error, based on the vertical speed of the ball and the strength of the AI
306                float randomErrorX = rnd(-1, 1) * dimension.y * (velocity.z / velocity.x / PongBall::MAX_REL_Z_VELOCITY) * (1 - this->strength_);
307                float randomErrorZ = rnd(-1, 1) * dimension.y * (velocity.z / velocity.x / PongBall::MAX_REL_Z_VELOCITY) * (1 - this->strength_);
308
309                // ball bounces after <bouncetime> seconds, update the position and continue
310                velocity.z = velocity.z + acceleration.z * bouncetime;
311
312                if (bUpperWall)
313                {
314                    position.z = dimension.y / 2;
315                    velocity.z = -fabs(velocity.z) + fabs(randomErrorZ);
316                }
317                else
318                {
319                    position.z = -dimension.y / 2;
320                    velocity.z = fabs(velocity.z) - fabs(randomErrorZ);
321                }
322
323                position.x = position.x + velocity.x * bouncetime + randomErrorX;
324                this->ballEndPosition_ = position.z;
325            }
326            else
327            {
328                // ball doesn't bounce, calculate the end position and return
329                // calculate end-height: current height + slope * distance incl. acceleration
330                this->ballEndPosition_ = position.z + velocity.z * totaltime + acceleration.z / 2 * totaltime * totaltime;
331                return;
332            }
333        }
334    }
335
336    /**
337    @brief
338        Determine the movement the AI will undertake. (Either -1, 0 or 1)
339        The result of this calculation is stored in this->movement_;
340    @param direction
341        The current direction of movement.
342    @param bUseDelay
343        The time by which this move is delayed. (Again, to make the AI less efficient)
344    */
345    void PongAI::move(char direction, bool bUseDelay)
346    {
347        // The current direction is either what we're doing right now (movement_) or what is last in the queue
348        char currentDirection = this->movement_;
349        if (this->reactionTimers_.size() > 0)
350            currentDirection = this->reactionTimers_.back().second;
351
352        // Only add changes of direction
353        if (direction == currentDirection)
354            return;
355
356        if (bUseDelay)
357        {
358            // Calculate delay
359            float delay = MAX_REACTION_TIME * (1 - this->strength_);
360
361            // Add a new Timer
362            Timer* timer = new Timer(delay, false, createExecutor(createFunctor(&PongAI::delayedMove, this)));
363            this->reactionTimers_.push_back(std::pair<Timer*, char>(timer, direction));
364        }
365        else
366        {
367            this->movement_ = direction;
368        }
369    }
370
371    /**
372    @brief
373        Is called, when a delayed move takes effect.
374    */
375    void PongAI::delayedMove()
376    {
377        // Get the new movement direction from the timer list
378        this->movement_ = this->reactionTimers_.front().second;
379
380        // Destroy the timer and remove it from the list
381        Timer* timer = this->reactionTimers_.front().first;
382        timer->destroy();
383
384        this->reactionTimers_.pop_front();
385    }
386}
Note: See TracBrowser for help on using the repository browser.