001         package com.croftsoft.apps.mars.model.seri;
002    
003         import java.awt.Color;
004         import java.awt.Shape;
005         import java.util.*;
006    
007         import com.croftsoft.core.lang.NullArgumentException;
008         import com.croftsoft.core.math.geom.Circle;
009         import com.croftsoft.core.math.geom.Point2DD;
010         import com.croftsoft.core.math.geom.PointXY;
011         import com.croftsoft.core.util.ArrayKeeper;
012    
013         import com.croftsoft.apps.mars.ai.TankOperator;
014         import com.croftsoft.apps.mars.model.AmmoDump;
015         import com.croftsoft.apps.mars.model.Damageable;
016         import com.croftsoft.apps.mars.model.Impassable;
017         import com.croftsoft.apps.mars.model.Model;
018         import com.croftsoft.apps.mars.model.Tank;
019         import com.croftsoft.apps.mars.model.TankAccessor;
020         import com.croftsoft.apps.mars.model.World;
021    
022         /*********************************************************************
023         * The default tank model implementation.
024         *
025         * @version
026         *   2003-04-29
027         * @since
028         *   2003-03-20
029         * @author
030         *   <a href="https://www.croftsoft.com/">David Wallace Croft</a>
031         *********************************************************************/
032    
033         public final class  SeriTank
034           extends SeriModel
035           implements Tank
036         //////////////////////////////////////////////////////////////////////
037         //////////////////////////////////////////////////////////////////////
038         {
039    
040         private static final long  serialVersionUID = 0L;
041    
042         //
043    
044         private static final double  Z = 1.0;
045    
046         /** radians per second */
047         private static final double  TURRET_ROTATION_SPEED
048           = 2.0 * Math.PI / 2.0;
049    
050         /** radians per second */
051         private static final double  BODY_ROTATION_SPEED
052           = 2.0 * Math.PI / 6.0;
053    
054         /** meters per second */
055         private static final double  TANK_SPEED = 30.0;
056    
057         /** meters per second */
058         private static final double  RADIUS = 25.0;
059    
060         private static final int     MAX_AMMO = 30;
061    
062         private static final double  DAMAGE_MAX = 2.0;
063    
064         //
065    
066         private static final int  INITIAL_AMMO = ( int ) DAMAGE_MAX + 1;
067    
068         //
069    
070         private final World     world;
071    
072         private final Circle    circle;
073    
074         private final Circle    testCircle;
075    
076         private final Color     color;
077    
078         private final Point2DD  targetPoint2DD;
079    
080         //
081    
082         private boolean       active;
083    
084         private int           ammo;
085    
086         private AmmoDump [ ]  ammoDumps;
087    
088         private double        bodyHeading;
089    
090         private double        damage;
091    
092         private PointXY       destination;
093    
094         private boolean       dryFiring;
095    
096         private boolean       firing;
097    
098         private boolean       sparking;
099    
100         private TankOperator  tankOperator;
101    
102         private double        turretHeading;
103    
104         private boolean       updated;
105    
106         //////////////////////////////////////////////////////////////////////
107         // constructor methods
108         //////////////////////////////////////////////////////////////////////
109    
110         public  SeriTank (
111           World         world,
112           double        centerX,
113           double        centerY,
114           Color         color )
115         //////////////////////////////////////////////////////////////////////
116         {
117           NullArgumentException.check ( this.world = world );
118    
119           NullArgumentException.check ( this.color = color );
120    
121           circle = new Circle ( 0.0, 0.0, RADIUS );
122    
123           testCircle = new Circle ( 0.0, 0.0, RADIUS );
124    
125           initialize ( centerX, centerY );
126    
127           ammoDumps = new AmmoDump [ 0 ];
128    
129           targetPoint2DD = new Point2DD ( );
130         }
131    
132         public void  initialize (
133           double  centerX,
134           double  centerY )
135         //////////////////////////////////////////////////////////////////////
136         {
137           ammo = INITIAL_AMMO;
138    
139           damage = 0.0;
140    
141           prepare ( );
142    
143           active  = true;
144    
145           updated = true;
146    
147           circle.setCenter ( centerX, centerY );
148         }
149    
150         //////////////////////////////////////////////////////////////////////
151         // interface Model methods
152         //////////////////////////////////////////////////////////////////////
153    
154         public boolean  isActive  ( ) { return active;  }
155    
156         public Shape    getShape  ( ) { return circle;  }
157    
158         public boolean  isUpdated ( ) { return updated; }
159    
160         public double   getZ      ( ) { return Z;       }
161    
162         public void  setCenter (
163           double  x,
164           double  y )
165         //////////////////////////////////////////////////////////////////////
166         {
167           circle.setCenter ( x, y );       
168         }
169    
170         public void  prepare ( )
171         //////////////////////////////////////////////////////////////////////
172         {
173           updated   = false;
174    
175           firing    = false;
176    
177           dryFiring = false;
178    
179           sparking  = false;
180         }
181    
182         public void  update ( double  timeDelta )
183         //////////////////////////////////////////////////////////////////////
184         {
185           if ( !active )
186           {
187             return;
188           }
189    
190           tankOperator.update ( timeDelta );
191    
192           updateAmmo ( );
193    
194           updatePosition ( timeDelta );
195    
196           updateTurretHeading ( timeDelta );
197         }
198    
199         //////////////////////////////////////////////////////////////////////
200         // interface Damageable method
201         //////////////////////////////////////////////////////////////////////
202    
203         public void  addDamage ( double  newDamage )
204         //////////////////////////////////////////////////////////////////////
205         {
206           if ( !active
207             || ( newDamage == 0.0 ) )
208           {
209             return;
210           }
211    
212           updated = true;
213    
214           sparking = true;
215    
216           damage += newDamage;
217    
218           if ( damage > DAMAGE_MAX )
219           {
220             active = false;
221           }
222         }
223    
224         //////////////////////////////////////////////////////////////////////
225         // interface TankAccessor methods
226         //////////////////////////////////////////////////////////////////////
227    
228         public int      getAmmo              ( ) { return ammo;          }
229    
230         public double   getBodyHeading       ( ) { return bodyHeading;   }
231    
232         public double   getBodyRotationSpeed ( ) {
233           return BODY_ROTATION_SPEED; }
234    
235         public Color    getColor             ( ) { return color;         }
236    
237         public double   getDamage            ( ) { return damage;        }
238    
239         public boolean  isDryFiring          ( ) { return dryFiring;     }
240    
241         public boolean  isFiring             ( ) { return firing;        }
242    
243         public double   getRadius            ( ) { return RADIUS;        }
244    
245         public boolean  isSparking           ( ) { return sparking;      }
246    
247         public TankOperator
248           getTankOperator ( ) { return tankOperator; }
249    
250         public double   getTankSpeed         ( ) { return TANK_SPEED;    }
251    
252         public double   getTurretHeading     ( ) { return turretHeading; }
253    
254         //////////////////////////////////////////////////////////////////////
255         // mutator methods
256         //////////////////////////////////////////////////////////////////////
257    
258         public void  setAmmo ( int  ammo )
259         //////////////////////////////////////////////////////////////////////
260         {
261           if ( ammo < 0 )
262           {
263             throw new IllegalArgumentException ( "ammo < 0:  " + ammo );
264           }
265    
266           this.ammo = ammo;
267         }
268    
269         public void  setTankOperator ( TankOperator  tankOperator )
270         //////////////////////////////////////////////////////////////////////
271         {
272           NullArgumentException.check ( this.tankOperator = tankOperator );
273         }
274    
275         //////////////////////////////////////////////////////////////////////
276         // interface TankConsole methods
277         //////////////////////////////////////////////////////////////////////
278    
279         public PointXY  getClosestAmmoDumpCenter ( )
280         //////////////////////////////////////////////////////////////////////
281         {
282           return world.getClosestAmmoDumpCenter ( circle.getCenter ( ) );
283         }
284    
285         public PointXY  getClosestEnemyTankCenter ( )
286         //////////////////////////////////////////////////////////////////////
287         {
288           return world.getClosestEnemyTankCenter (
289             circle.getCenter ( ), color );
290         }
291    
292         public boolean  isSpaceAvailable ( PointXY  center )
293         //////////////////////////////////////////////////////////////////////
294         {
295           testCircle.setCenter ( center );
296    
297           Iterator  iterator = world.getImpassables ( testCircle, this );
298    
299           while ( iterator.hasNext ( ) )
300           {
301             if ( ammo >= 1 )
302             {        
303               Impassable  impassable = ( Impassable ) iterator.next ( );
304    
305               if ( impassable instanceof TankAccessor )
306               {
307                 TankAccessor  tankAccessor = ( TankAccessor ) impassable;
308    
309                 if ( !tankAccessor.getColor ( ).equals ( color ) )
310                 {
311                   continue;
312                 }
313               }
314             }
315    
316             return false;
317           }
318    
319           return true;
320         }
321    
322         //////////////////////////////////////////////////////////////////////
323         //////////////////////////////////////////////////////////////////////
324    
325         public void  go ( PointXY  destination )
326         //////////////////////////////////////////////////////////////////////
327         {
328           this.destination = destination;
329         }
330    
331         public void  fire ( )
332         //////////////////////////////////////////////////////////////////////
333         {
334           if ( !active || firing || dryFiring )
335           {
336             return;
337           }
338             
339           updated = true;
340    
341           if ( ammo < 1 )
342           {
343             dryFiring = true;
344    
345             return;
346           }
347    
348           ammo--;
349    
350           firing = true;
351    
352           PointXY  center = circle.getCenter ( );
353    
354           double  bulletOriginX
355             = center.getX ( ) + ( RADIUS + 3.0 ) * Math.cos ( turretHeading );
356    
357           double  bulletOriginY
358             = center.getY ( ) + ( RADIUS + 3.0 ) * Math.sin ( turretHeading );
359    
360           world.fireBullet ( bulletOriginX, bulletOriginY, turretHeading );
361         }
362    
363         public void  rotateTurret ( PointXY  targetPointXY )
364         //////////////////////////////////////////////////////////////////////
365         {
366           targetPoint2DD.setXY ( targetPointXY );
367         }
368    
369         //////////////////////////////////////////////////////////////////////
370         // private update methods
371         //////////////////////////////////////////////////////////////////////
372    
373         private void  updateAmmo ( )
374         //////////////////////////////////////////////////////////////////////
375         {
376           if ( ammo >= MAX_AMMO )
377           {
378             return;
379           }
380    
381           int  ammoNeeded = MAX_AMMO - ammo;
382    
383           ammoDumps = world.getAmmoDumps ( circle.getCenter ( ), ammoDumps );
384    
385           for ( int  i = 0; i < ammoDumps.length; i++ )
386           {
387             AmmoDump  ammoDump = ammoDumps [ i ];
388    
389             if ( ammoDump == null )
390             {
391               break;
392             }
393    
394             double  dumpAmmo = ammoDump.getAmmo ( );
395    
396             if ( ammoNeeded <= dumpAmmo )
397             {
398               ammo = MAX_AMMO;
399    
400               ammoDump.setAmmo ( dumpAmmo - ammoNeeded );
401    
402               break;
403             }
404             else
405             {
406               ammo += ( int ) dumpAmmo;
407    
408               ammoDump.setAmmo ( dumpAmmo - ( int ) dumpAmmo );
409    
410               ammoNeeded = MAX_AMMO - ammo;
411             }
412           }
413         }
414    
415         private void  updatePosition ( double  timeDelta )
416         //////////////////////////////////////////////////////////////////////
417         {
418           if ( destination == null )
419           {
420             return;
421           }
422    
423           PointXY  center = circle.getCenter ( );
424    
425           double  centerX = center.getX ( );
426    
427           double  centerY = center.getY ( );
428    
429           double  deltaX = destination.getX ( ) - centerX;
430    
431           double  deltaY = destination.getY ( ) - centerY;
432    
433    /*
434           if ( ( Math.abs ( deltaX ) < 0.5 )
435             && ( Math.abs ( deltaY ) < 0.5 ) )
436           {
437             return;
438           }
439    */
440    
441           double  aimHeading = Math.atan2 ( deltaY, deltaX );
442    
443           if ( aimHeading < 0.0 )
444           {
445             aimHeading += 2.0 * Math.PI;
446           }
447    
448           double  newBodyHeading = rotateTowardHeading (
449             bodyHeading,
450             aimHeading,
451             timeDelta * BODY_ROTATION_SPEED );
452    
453           if ( newBodyHeading != bodyHeading )
454           {
455             updated = true;
456    
457             bodyHeading = newBodyHeading;
458           }
459    
460           if ( bodyHeading == aimHeading )
461           {
462             double  moveX = timeDelta * TANK_SPEED * Math.cos ( bodyHeading );
463    
464             double  moveY = timeDelta * TANK_SPEED * Math.sin ( bodyHeading );
465    
466             if ( Math.abs ( moveX ) > Math.abs ( deltaX ) )
467             {
468               moveX = deltaX;
469             }
470                 
471             if ( Math.abs ( moveY ) > Math.abs ( deltaY ) )
472             {
473               moveY = deltaY;
474             }
475    
476             double  newX = centerX + moveX;
477    
478             double  newY = centerY + moveY;
479    
480             circle.setCenter ( newX, newY );
481    
482             if ( world.isBlocked ( this ) )
483             {
484               circle.setCenter ( centerX, centerY );
485    
486               if ( world.isBlocked ( this ) )
487               {
488                 circle.setCenter ( newX, newY );
489    
490                 updated = true;
491               }
492             }
493             else
494             {
495               updated = true;
496             }
497           }
498         }
499    
500         private void  updateTurretHeading ( double  timeDelta )
501         //////////////////////////////////////////////////////////////////////
502         {
503           PointXY  center = circle.getCenter ( );
504    
505           double  centerX = center.getX ( );
506    
507           double  centerY = center.getY ( );
508    
509           double  desiredTurretHeading = Math.atan2 (
510             targetPoint2DD.y - centerY,
511             targetPoint2DD.x - centerX );
512    
513           if ( desiredTurretHeading < 0.0 )
514           {
515             desiredTurretHeading += 2.0 * Math.PI;
516           }
517    
518           double  newTurretHeading = rotateTowardHeading (
519             turretHeading,
520             desiredTurretHeading,
521             timeDelta * TURRET_ROTATION_SPEED );
522    
523           if ( newTurretHeading != turretHeading )
524           {
525             updated = true;
526    
527             turretHeading = newTurretHeading;
528           }
529         }
530    
531         private double  rotateTowardHeading (
532           double  currentHeading,
533           double  targetHeading,
534           double  rotationSpeed )
535         //////////////////////////////////////////////////////////////////////
536         {
537           double  newHeading;
538    
539           double  deltaHeading = targetHeading - currentHeading;
540    
541           if ( deltaHeading < -Math.PI )
542           {
543             newHeading = currentHeading + rotationSpeed;
544    
545             if ( newHeading >= 2.0 * Math.PI )
546             {
547               newHeading -= 2.0 * Math.PI;
548             }
549           }
550           else if ( deltaHeading < -rotationSpeed )
551           {
552             newHeading = currentHeading - rotationSpeed;
553           }
554           else if ( deltaHeading <= rotationSpeed )
555           {
556             newHeading = targetHeading;
557           }
558           else if ( deltaHeading < Math.PI )
559           {
560             newHeading = currentHeading + rotationSpeed;
561           }
562           else
563           {
564             newHeading = currentHeading - rotationSpeed;
565    
566             if ( newHeading < 0.0 )
567             {
568               newHeading += 2.0 * Math.PI;
569             }
570           }
571    
572           return newHeading;
573         }
574    
575         //////////////////////////////////////////////////////////////////////
576         //////////////////////////////////////////////////////////////////////
577         }