Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

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

Last change on this file since 6260 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
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#include "PongAI.h"
30
31#include "core/CoreIncludes.h"
32#include "core/ConfigValueIncludes.h"
33#include "tools/Timer.h"
34#include "worldentities/ControllableEntity.h"
35#include "PongBall.h"
36
37namespace orxonox
38{
39    CreateUnloadableFactory(PongAI);
40
41    const static float MAX_REACTION_TIME = 0.4f;
42
43    PongAI::PongAI(BaseObject* creator) : Controller(creator)
44    {
45        RegisterObject(PongAI);
46
47        this->ball_ = 0;
48        this->ballDirection_ = Vector2::ZERO;
49        this->ballEndPosition_ = 0;
50        this->randomOffset_ = 0;
51        this->bChangedRandomOffset_ = false;
52        this->relHysteresisOffset_ = 0.02f;
53        this->strength_ = 0.5f;
54        this->movement_ = 0;
55        this->oldMove_ = 0;
56        this->bOscillationAvoidanceActive_ = false;
57
58        this->setConfigValues();
59    }
60
61    PongAI::~PongAI()
62    {
63        for (std::list<std::pair<Timer*, char> >::iterator it = this->reactionTimers_.begin(); it != this->reactionTimers_.end(); ++it)
64            (*it).first->destroy();
65    }
66
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
72    void PongAI::tick(float dt)
73    {
74        if (!this->ball_ || !this->getControllableEntity())
75            return;
76
77        Vector3 mypos = this->getControllableEntity()->getPosition();
78        Vector3 ballpos = this->ball_->getPosition();
79        Vector3 ballvel = this->ball_->getVelocity();
80        float hysteresisOffset = this->relHysteresisOffset_ * this->ball_->getFieldDimension().y;
81
82        char move = 0;
83        bool delay = false;
84
85        // Check in which direction the ball is flying
86        if ((mypos.x > 0 && ballvel.x < 0) || (mypos.x < 0 && ballvel.x > 0))
87        {
88            // The ball is flying away
89            this->ballDirection_.x = -1;
90            this->ballDirection_.y = 0;
91            this->bOscillationAvoidanceActive_ = false;
92
93            // Move to the middle
94            if (mypos.z > hysteresisOffset)
95                move = 1;
96            else if (mypos.z < -hysteresisOffset)
97                move = -1;
98        }
99        else if (ballvel.x == 0)
100        {
101            // The ball is standing still
102            this->ballDirection_.x = 0;
103            this->ballDirection_.y = 0;
104            this->bOscillationAvoidanceActive_ = false;
105        }
106        else
107        {
108            // The ball is approaching
109            if (this->ballDirection_.x != 1)
110            {
111                // The ball just startet to approach, initialize all values
112                this->ballDirection_.x = 1;
113                this->ballDirection_.y = sgn(ballvel.z);
114                this->ballEndPosition_ = 0;
115                this->randomOffset_ = 0;
116                this->bChangedRandomOffset_ = false;
117
118                this->calculateRandomOffset();
119                this->calculateBallEndPosition();
120                delay = true;
121                this->bOscillationAvoidanceActive_ = false;
122            }
123
124            if (this->ballDirection_.y != sgn(ballvel.z))
125            {
126                // The ball just bounced from a bound, recalculate the predicted end position
127                this->ballDirection_.y = sgn(ballvel.z);
128
129                this->calculateBallEndPosition();
130                delay = true;
131                this->bOscillationAvoidanceActive_ = false;
132            }
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            }
145
146            // Move to the predicted end position with an additional offset (to hit the ball with the side of the bat)
147            if (!this->bOscillationAvoidanceActive_)
148            {
149                float desiredZValue = this->ballEndPosition_ + this->randomOffset_;
150
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            }
168        }
169
170        this->oldMove_ = move;
171        this->move(move, delay);
172        this->getControllableEntity()->moveFrontBack(this->movement_);
173    }
174
175    void PongAI::calculateRandomOffset()
176    {
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)
187        position *= 0.48f;
188
189        // Both sides are equally probable
190        position *= rndsgn();
191
192        // Calculate the offset in world units
193        this->randomOffset_ = position * this->ball_->getBatLength() * this->ball_->getFieldDimension().y;
194    }
195
196    void PongAI::calculateBallEndPosition()
197    {
198        Vector3 position = this->ball_->getPosition();
199        Vector3 velocity = this->ball_->getVelocity();
200        Vector3 acceleration = this->ball_->getAcceleration();
201        Vector2 dimension = this->ball_->getFieldDimension();
202
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)
205        {
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            }
263
264            if (bouncetime < totaltime)
265            {
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;
286            }
287            else
288            {
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;
293            }
294        }
295    }
296
297    void PongAI::move(char direction, bool bUseDelay)
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
308        if (bUseDelay)
309        {
310            // Calculate delay
311            float delay = MAX_REACTION_TIME * (1 - this->strength_);
312
313            // Add a new Timer
314            Timer* timer = new Timer(delay, false, createExecutor(createFunctor(&PongAI::delayedMove, this)));
315            this->reactionTimers_.push_back(std::pair<Timer*, char>(timer, direction));
316        }
317        else
318        {
319            this->movement_ = direction;
320        }
321    }
322
323    void PongAI::delayedMove()
324    {
325        // Get the new movement direction from the timer list
326        this->movement_ = this->reactionTimers_.front().second;
327
328        // Destroy the timer and remove it from the list
329        Timer* timer = this->reactionTimers_.front().first;
330        timer->destroy();
331
332        this->reactionTimers_.pop_front();
333    }
334}
Note: See TracBrowser for help on using the repository browser.