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 }