Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/core5/src/modules/pong/PongAI.cc @ 5874

Last change on this file since 5874 was 5831, checked in by landauf, 15 years ago

Realized Timer doesn't have to be a template, hence merged TimerBase, Timer and StaticTimer.
Removed the object pointer from Timer for memberfunctions, use createFunctor(f, object) instead.

  • Property svn:eol-style set to native
File size: 9.2 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->relHysteresisOffset_ = 0.02f;
52        this->strength_ = 0.5f;
53        this->movement_ = 0;
54        this->oldMove_ = 0;
55        this->bOscillationAvoidanceActive_ = false;
56
57        this->setConfigValues();
58    }
59
60    PongAI::~PongAI()
61    {
62        for (std::list<std::pair<Timer*, char> >::iterator it = this->reactionTimers_.begin(); it != this->reactionTimers_.end(); ++it)
63            (*it).first->destroy();
64    }
65
66    void PongAI::setConfigValues()
67    {
68        SetConfigValue(strength_, 0.5).description("A value from 0 to 1 where 0 is weak and 1 is strong.");
69    }
70
71    void PongAI::tick(float dt)
72    {
73        if (!this->ball_ || !this->getControllableEntity())
74            return;
75
76        Vector3 mypos = this->getControllableEntity()->getPosition();
77        Vector3 ballpos = this->ball_->getPosition();
78        Vector3 ballvel = this->ball_->getVelocity();
79        float hysteresisOffset = this->relHysteresisOffset_ * this->ball_->getFieldDimension().y;
80
81        char move = 0;
82        bool delay = false;
83
84        // Check in which direction the ball is flying
85        if ((mypos.x > 0 && ballvel.x < 0) || (mypos.x < 0 && ballvel.x > 0))
86        {
87            // The ball is flying away
88            this->ballDirection_.x = -1;
89            this->ballDirection_.y = 0;
90            this->bOscillationAvoidanceActive_ = false;
91
92            // Move to the middle
93            if (mypos.z > hysteresisOffset)
94                move = 1;
95            else if (mypos.z < -hysteresisOffset)
96                move = -1;
97        }
98        else if (ballvel.x == 0)
99        {
100            // The ball is standing still
101            this->ballDirection_.x = 0;
102            this->ballDirection_.y = 0;
103            this->bOscillationAvoidanceActive_ = false;
104        }
105        else
106        {
107            // The ball is approaching
108            if (this->ballDirection_.x != 1)
109            {
110                // The ball just startet to approach, initialize all values
111                this->ballDirection_.x = 1;
112                this->ballDirection_.y = sgn(ballvel.z);
113                this->ballEndPosition_ = 0;
114                this->randomOffset_ = 0;
115
116                this->calculateRandomOffset();
117                this->calculateBallEndPosition();
118                delay = true;
119                this->bOscillationAvoidanceActive_ = false;
120            }
121
122            if (this->ballDirection_.y != sgn(ballvel.z))
123            {
124                // The ball just bounced from a bound, recalculate the predicted end position
125                this->ballDirection_.y = sgn(ballvel.z);
126
127                this->calculateBallEndPosition();
128                delay = true;
129                this->bOscillationAvoidanceActive_ = false;
130            }
131
132            // Move to the predicted end position with an additional offset (to hit the ball with the side of the bat)
133            if (!this->bOscillationAvoidanceActive_)
134            {
135                float desiredZValue = this->ballEndPosition_ + this->randomOffset_;
136
137                if (mypos.z > desiredZValue + hysteresisOffset * (this->randomOffset_ < 0))
138                    move = 1;
139                else if (mypos.z < desiredZValue - hysteresisOffset * (this->randomOffset_ > 0))
140                    move = -1;
141            }
142
143            if (move != 0 && this->oldMove_ != 0 && move != this->oldMove_ && !delay)
144            {
145                // We had to correct our position because we moved too far
146                // (and delay ist false, so we're not in the wrong place because of a new end-position prediction)
147                if (fabs(mypos.z - this->ballEndPosition_) < 0.5 * this->ball_->getBatLength() * this->ball_->getFieldDimension().y)
148                {
149                    // We're not at the right position, but we still hit the ball, so just stay there to avoid oscillation
150                    move = 0;
151                    this->bOscillationAvoidanceActive_ = true;
152                }
153            }
154        }
155
156        this->oldMove_ = move;
157        this->move(move, delay);
158        this->getControllableEntity()->moveFrontBack(this->movement_);
159    }
160
161    void PongAI::calculateRandomOffset()
162    {
163        // Calculate the exponent for the position-formula
164        float exp = pow(10, 1 - 2*this->strength_); // strength: 0   -> exp = 10
165                                                    // strength: 0.5 -> exp = 1
166                                                    // strength: 1   -> exp = 0.1
167
168        // Calculate the relative position where to hit the ball with the bat
169        float position = pow(rnd(), exp); // exp > 1 -> position is more likely a small number
170                                          // exp < 1 -> position is more likely a large number
171
172        // The position shouln't be larger than 0.5 (50% of the bat-length from the middle is the end)
173        position *= 0.48f;
174
175        // Both sides are equally probable
176        position *= rndsgn();
177
178        // Calculate the offset in world units
179        this->randomOffset_ = position * this->ball_->getBatLength() * this->ball_->getFieldDimension().y;
180    }
181
182    void PongAI::calculateBallEndPosition()
183    {
184        Vector3 position = this->ball_->getPosition();
185        Vector3 velocity = this->ball_->getVelocity();
186        Vector2 dimension = this->ball_->getFieldDimension();
187
188        // calculate end-height: current height + slope * distance
189        this->ballEndPosition_ = position.z + velocity.z / velocity.x * (-position.x + dimension.x / 2 * sgn(velocity.x));
190
191        // Calculate bounces
192        for (float limit = 0.35f; limit < this->strength_ || this->strength_ > 0.99f; limit += 0.4f)
193        {
194            // Calculate a random prediction error, based on the vertical speed of the ball and the strength of the AI
195            float randomError = rnd(-1, 1) * dimension.y * (velocity.z / velocity.x / PongBall::MAX_REL_Z_VELOCITY) * (1 - this->strength_);
196
197            // Bounce from the lower bound
198            if (this->ballEndPosition_ > dimension.y / 2)
199            {
200                // Mirror the predicted position at the upper bound and add some random error
201                this->ballEndPosition_ = dimension.y - this->ballEndPosition_ + randomError;
202                continue;
203            }
204            // Bounce from the upper bound
205            if (this->ballEndPosition_ < -dimension.y / 2)
206            {
207                // Mirror the predicted position at the lower bound and add some random error
208                this->ballEndPosition_ = -dimension.y - this->ballEndPosition_ + randomError;
209                continue;
210            }
211            // No bounce - break
212            break;
213        }
214    }
215
216    void PongAI::move(char direction, bool bUseDelay)
217    {
218        // The current direction is either what we're doing right now (movement_) or what is last in the queue
219        char currentDirection = this->movement_;
220        if (this->reactionTimers_.size() > 0)
221            currentDirection = this->reactionTimers_.back().second;
222
223        // Only add changes of direction
224        if (direction == currentDirection)
225            return;
226
227        if (bUseDelay)
228        {
229            // Calculate delay
230            float delay = MAX_REACTION_TIME * (1 - this->strength_);
231
232            // Add a new Timer
233            Timer* timer = new Timer(delay, false, createExecutor(createFunctor(&PongAI::delayedMove, this)));
234            this->reactionTimers_.push_back(std::pair<Timer*, char>(timer, direction));
235        }
236        else
237        {
238            this->movement_ = direction;
239        }
240    }
241
242    void PongAI::delayedMove()
243    {
244        // Get the new movement direction from the timer list
245        this->movement_ = this->reactionTimers_.front().second;
246
247        // Destroy the timer and remove it from the list
248        Timer* timer = this->reactionTimers_.front().first;
249        timer->destroy();
250
251        this->reactionTimers_.pop_front();
252    }
253}
Note: See TracBrowser for help on using the repository browser.