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         }