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 }