001 package com.croftsoft.apps.mars.net; 002 003 import java.awt.*; 004 import java.io.*; 005 import java.util.*; 006 007 import com.croftsoft.core.animation.clock.HiResClock; 008 import com.croftsoft.core.animation.clock.Timekeeper; 009 import com.croftsoft.core.io.SerializableLib; 010 import com.croftsoft.core.lang.NullArgumentException; 011 import com.croftsoft.core.util.queue.ListQueue; 012 import com.croftsoft.core.util.queue.Queue; 013 014 import com.croftsoft.apps.mars.ai.DirectedTankOperator; 015 import com.croftsoft.apps.mars.ai.TankOperator; 016 import com.croftsoft.apps.mars.model.*; 017 import com.croftsoft.apps.mars.model.seri.SeriAmmoDump; 018 import com.croftsoft.apps.mars.model.seri.SeriTank; 019 import com.croftsoft.apps.mars.model.seri.SeriWorld; 020 021 /********************************************************************* 022 * The game state and behavior. 023 * 024 * @version 025 * 2003-09-10 026 * @since 027 * 2003-04-07 028 * @author 029 * <a href="https://www.croftsoft.com/">David Wallace Croft</a> 030 *********************************************************************/ 031 032 public final class NetGame 033 ////////////////////////////////////////////////////////////////////// 034 ////////////////////////////////////////////////////////////////////// 035 { 036 037 private final SeriWorld seriWorld; 038 039 private final Random actionsRandom; 040 041 private final Random contentRandom; 042 043 private final Timekeeper timekeeper; 044 045 private final Queue newPlayerNameQueue; 046 047 private final Map nameToPlayerMap; 048 049 // frequently used GameInitAccessor values 050 051 private final double timeDeltaMax; 052 053 private final long playerTimeout; 054 055 private final int attemptsMax; 056 057 private final int worldWidth; 058 059 private final int worldHeight; 060 061 private final double obstacleRadiusMax; 062 063 private final double obstacleRadiusMin; 064 065 // 066 067 private GameData gameData; 068 069 private SeriWorld copySeriWorld; 070 071 ////////////////////////////////////////////////////////////////////// 072 ////////////////////////////////////////////////////////////////////// 073 074 public static void main ( String [ ] args ) 075 ////////////////////////////////////////////////////////////////////// 076 { 077 System.out.println ( test ( args ) ); 078 } 079 080 public static boolean test ( String [ ] args ) 081 ////////////////////////////////////////////////////////////////////// 082 { 083 try 084 { 085 new NetGame ( ).update ( ); 086 } 087 catch ( Exception ex ) 088 { 089 ex.printStackTrace ( ); 090 091 return false; 092 } 093 094 return true; 095 } 096 097 public static NetGame load ( 098 GameInitAccessor gameInitAccessor, 099 String primaryFilename, 100 String backupFilename ) 101 throws ClassNotFoundException, IOException 102 ////////////////////////////////////////////////////////////////////// 103 { 104 SeriWorld seriWorld = ( SeriWorld ) 105 SerializableLib.load ( primaryFilename, backupFilename ); 106 107 return new NetGame ( gameInitAccessor, seriWorld ); 108 } 109 110 ////////////////////////////////////////////////////////////////////// 111 // public constructor methods 112 ////////////////////////////////////////////////////////////////////// 113 114 public NetGame ( GameInitAccessor gameInitAccessor ) 115 ////////////////////////////////////////////////////////////////////// 116 { 117 this ( gameInitAccessor, ( SeriWorld ) null ); 118 } 119 120 public NetGame ( ) 121 ////////////////////////////////////////////////////////////////////// 122 { 123 this ( ( GameInitAccessor ) null ); 124 } 125 126 ////////////////////////////////////////////////////////////////////// 127 ////////////////////////////////////////////////////////////////////// 128 129 public GameData getGameData ( ) 130 ////////////////////////////////////////////////////////////////////// 131 { 132 return gameData; 133 } 134 135 public Player getPlayer ( String playerName ) 136 ////////////////////////////////////////////////////////////////////// 137 { 138 Player player = ( Player ) nameToPlayerMap.get ( playerName ); 139 140 if ( player == null ) 141 { 142 newPlayerNameQueue.replace ( playerName ); 143 } 144 145 return player; 146 } 147 148 public void save ( 149 String primaryFilename, 150 String backupFilename ) 151 throws IOException 152 ////////////////////////////////////////////////////////////////////// 153 { 154 SerializableLib.save ( seriWorld, primaryFilename, backupFilename ); 155 } 156 157 public void update ( ) 158 ////////////////////////////////////////////////////////////////////// 159 { 160 seriWorld.prepare ( ); 161 162 timekeeper.update ( ); 163 164 double timeDelta = timekeeper.getTimeDelta ( ); 165 166 if ( timeDelta > timeDeltaMax ) 167 { 168 timeDelta = timeDeltaMax; 169 } 170 171 seriWorld.update ( timeDelta ); 172 173 // restore and relocate destroyed obstacles 174 175 Obstacle [ ] obstacles = seriWorld.getObstacles ( ); 176 177 for ( int i = 0; i < obstacles.length; i++ ) 178 { 179 Obstacle obstacle = obstacles [ i ]; 180 181 if ( !obstacle.isActive ( ) ) 182 { 183 resetObstacle ( obstacle ); 184 } 185 } 186 187 // create list of owned tanks and disconnected players 188 189 java.util.List tankList = new ArrayList ( ); 190 191 java.util.List removeList = new ArrayList ( ); 192 193 Iterator iterator = nameToPlayerMap.values ( ).iterator ( ); 194 195 long currentTimeMillis = System.currentTimeMillis ( ); 196 197 while ( iterator.hasNext ( ) ) 198 { 199 Player player = ( Player ) iterator.next ( ); 200 201 tankList.add ( player.getSeriTank ( ) ); 202 203 long lastRequestTime = player.getLastRequestTime ( ); 204 205 if ( currentTimeMillis >= lastRequestTime + playerTimeout ) 206 { 207 removeList.add ( player ); 208 } 209 } 210 211 // remove disconnected players and their tanks 212 213 iterator = removeList.iterator ( ); 214 215 while ( iterator.hasNext ( ) ) 216 { 217 Player player = ( Player ) iterator.next ( ); 218 219 seriWorld.remove ( player.getSeriTank ( ) ); 220 221 nameToPlayerMap.remove ( player.getName ( ) ); 222 } 223 224 // remove unowned tanks and reactivate dead player tanks 225 226 Tank [ ] tanks = seriWorld.getTanks ( ); 227 228 for ( int i = 0; i < tanks.length; i++ ) 229 { 230 Tank tank = tanks [ i ]; 231 232 if ( !tankList.contains ( tank ) ) 233 { 234 seriWorld.remove ( tank ); 235 } 236 else if ( !tank.isActive ( ) ) 237 { 238 for ( int j = 0; j < attemptsMax; j++ ) 239 { 240 tank.initialize ( 241 worldWidth * actionsRandom.nextDouble ( ), 242 worldHeight * actionsRandom.nextDouble ( ) ); 243 244 if ( !seriWorld.isBlocked ( tank ) ) 245 { 246 break; 247 } 248 } 249 } 250 } 251 252 // create new players and their tanks 253 254 while ( true ) 255 { 256 String newPlayerName = ( String ) newPlayerNameQueue.poll ( ); 257 258 if ( newPlayerName == null ) 259 { 260 break; 261 } 262 263 Player player = getPlayer ( newPlayerName ); 264 265 if ( player != null ) 266 { 267 continue; 268 } 269 270 Tank playerTank = seriWorld.createTank ( 271 0.0, 0.0, 272 new Color ( 273 actionsRandom.nextInt ( 256 ), 274 actionsRandom.nextInt ( 256 ), 275 actionsRandom.nextInt ( 256 ) ) ); 276 277 for ( int j = 0; j < attemptsMax; j++ ) 278 { 279 playerTank.initialize ( 280 worldWidth * actionsRandom.nextDouble ( ), 281 worldHeight * actionsRandom.nextDouble ( ) ); 282 283 if ( !seriWorld.isBlocked ( playerTank ) ) 284 { 285 break; 286 } 287 } 288 289 playerTank.setTankOperator ( 290 new DirectedTankOperator ( playerTank ) ); 291 292 player = new Player ( newPlayerName, ( SeriTank ) playerTank ); 293 294 nameToPlayerMap.put ( newPlayerName, player ); 295 } 296 297 // update snapshots 298 299 try 300 { 301 copySeriWorld = ( SeriWorld ) SerializableLib.copy ( seriWorld ); 302 } 303 catch ( IOException ex ) 304 { 305 // This normally will never happen. 306 307 throw ( RuntimeException ) 308 new RuntimeException ( ).initCause ( ex ); 309 } 310 311 gameData = new GameData ( copySeriWorld, null ); 312 313 iterator = nameToPlayerMap.values ( ).iterator ( ); 314 315 while ( iterator.hasNext ( ) ) 316 { 317 Player player = ( Player ) iterator.next ( ); 318 319 try 320 { 321 SeriTank copySeriTank = ( SeriTank ) 322 SerializableLib.copy ( player.getSeriTank ( ) ); 323 324 player.setGameData ( 325 new GameData ( copySeriWorld, copySeriTank ) ); 326 } 327 catch ( IOException ex ) 328 { 329 // This normally will never happen. 330 331 throw ( RuntimeException ) 332 new RuntimeException ( ).initCause ( ex ); 333 } 334 } 335 } 336 337 ////////////////////////////////////////////////////////////////////// 338 // private methods 339 ////////////////////////////////////////////////////////////////////// 340 341 private NetGame ( 342 GameInitAccessor gameInitAccessor, 343 SeriWorld seriWorld ) 344 ////////////////////////////////////////////////////////////////////// 345 { 346 if ( gameInitAccessor == null ) 347 { 348 gameInitAccessor = GameInit.createDefaultGameInit ( ); 349 } 350 351 long randomSeed = gameInitAccessor.getRandomSeed ( ); 352 353 actionsRandom = new Random ( randomSeed ); 354 355 contentRandom = new Random ( randomSeed ); 356 357 // frequently used GameInitAccessor values 358 359 timeDeltaMax = gameInitAccessor.getTimeDeltaMax ( ); 360 361 playerTimeout = gameInitAccessor.getPlayerTimeout ( ); 362 363 attemptsMax = gameInitAccessor.getAttemptsMax ( ); 364 365 worldWidth = gameInitAccessor.getWorldWidth ( ); 366 367 worldHeight = gameInitAccessor.getWorldHeight ( ); 368 369 obstacleRadiusMax = gameInitAccessor.getObstacleRadiusMax ( ); 370 371 obstacleRadiusMin = gameInitAccessor.getObstacleRadiusMin ( ); 372 373 if ( seriWorld == null ) 374 { 375 seriWorld = new SeriWorld ( 376 actionsRandom, 377 new SeriAmmoDump.Shared ( 378 gameInitAccessor.getAmmoDumpGrowth ( ), 379 gameInitAccessor.getAmmoDumpMax ( ), 380 gameInitAccessor.getAmmoDumpExplosion ( ), 381 gameInitAccessor.getAmmoDumpZ ( ) ) ); 382 383 this.seriWorld = seriWorld; 384 385 for ( int i = 0; i < gameInitAccessor.getAmmoDumps ( ); i++ ) 386 { 387 AmmoDump ammoDump = seriWorld.createAmmoDump ( 388 worldWidth * contentRandom.nextDouble ( ), 389 worldHeight * contentRandom.nextDouble ( ) ); 390 391 for ( int j = 0; j < attemptsMax; j++ ) 392 { 393 if ( !seriWorld.isBlocked ( ammoDump ) ) 394 { 395 break; 396 } 397 398 ammoDump.setCenter ( 399 worldWidth * contentRandom.nextDouble ( ), 400 worldHeight * contentRandom.nextDouble ( ) ); 401 } 402 } 403 404 Rectangle driftBounds 405 = new Rectangle ( 0, 0, worldWidth, worldHeight ); 406 407 for ( int i = 0; i < gameInitAccessor.getObstacles ( ); i++ ) 408 { 409 Obstacle obstacle = seriWorld.createObstacle ( 410 0.0, 411 0.0, 412 0.0, 413 obstacleRadiusMin, 414 driftBounds ); 415 416 resetObstacle ( obstacle ); 417 } 418 } 419 else 420 { 421 this.seriWorld = seriWorld; 422 } 423 424 timekeeper = new Timekeeper ( 425 new HiResClock ( ), gameInitAccessor.getTimeFactorDefault ( ) ); 426 427 newPlayerNameQueue = new ListQueue ( ); 428 429 nameToPlayerMap = new HashMap ( ); 430 431 try 432 { 433 copySeriWorld = ( SeriWorld ) SerializableLib.copy ( seriWorld ); 434 } 435 catch ( IOException ex ) 436 { 437 // This normally will never happen. 438 439 throw ( RuntimeException ) 440 new RuntimeException ( ).initCause ( ex ); 441 } 442 443 gameData = new GameData ( copySeriWorld, null ); 444 } 445 446 private void resetObstacle ( Obstacle obstacle ) 447 ////////////////////////////////////////////////////////////////////// 448 { 449 obstacle.setActive ( true ); 450 451 for ( int j = 0; j < attemptsMax; j++ ) 452 { 453 obstacle.setCenter ( 454 worldWidth * contentRandom.nextDouble ( ), 455 worldHeight * contentRandom.nextDouble ( ) ); 456 457 if ( !seriWorld.isBlocked ( obstacle ) ) 458 { 459 break; 460 } 461 } 462 463 double radius = ( obstacleRadiusMax - obstacleRadiusMin ) 464 * contentRandom.nextDouble ( ) + obstacleRadiusMin; 465 466 obstacle.setRadius ( radius ); 467 } 468 469 ////////////////////////////////////////////////////////////////////// 470 ////////////////////////////////////////////////////////////////////// 471 }