View Javadoc

1   /*
2    * AI Soccer Project - network gaming environment for AI warriors.
3    * Copyright (C) 2001-2004  Marcin Werla, Pawel Widera
4    *
5    * This program is free software; you can redistribute it and/or
6    * modify it under the terms of the GNU General Public License
7    * as published by the Free Software Foundation; either version 2
8    * of the License, or (at your option) any later version.
9    *
10   * This program is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with this program; if not, you can find it here:
17   * http://www.gnu.org/licenses/gpl.html
18   */
19  
20  package aigames.soccer.application;
21  
22  import aigames.soccer.InvalidMoveException;
23  
24  import aigames.soccer.field.BasicGameField;
25  import aigames.soccer.field.GameField;
26  import aigames.soccer.field.constructor.Constructor;
27  import aigames.soccer.field.constructor.StandardConstructor;
28  
29  import org.apache.log4j.Logger;
30  
31  import org.dom4j.Document;
32  
33  import org.iso_relax.verifier.VerifierConfigurationException;
34  
35  import org.xml.sax.SAXException;
36  
37  import snifos.application.Application;
38  
39  import snifos.common.UserId;
40  
41  import snifos.exception.ConfigurationException;
42  import snifos.exception.InvalidUserException;
43  
44  import snifos.server.Server;
45  
46  import java.io.IOException;
47  
48  import java.util.ArrayList;
49  import java.util.Collection;
50  import java.util.Collections;
51  import java.util.Date;
52  import java.util.GregorianCalendar;
53  import java.util.HashMap;
54  import java.util.Iterator;
55  import java.util.List;
56  import java.util.Map;
57  import java.util.Properties;
58  
59  
60  /***
61   * @version $Id: SoccerApp.java,v 1.36 2004/05/08 21:55:26 mwerla Exp $
62   */
63  public class SoccerApp implements Application {
64      private static Logger logger = Logger.getLogger(SoccerApp.class);
65      private static final int MAX_NO_OF_BAD_MESSAGES = 3;
66      private static final int MAX_NO_OF_OBSERVERS = 2;
67      private static final int MESSAGE_TIMEOUT = 30000;
68      private static final int REQUIRED_NUMBER_OF_PLAYERS = 2;
69      private Server server;
70      private int width;
71      private int height;
72      private int goal;
73      private Validator validator;
74      private MessageSender sender;
75      private GameField field;
76      private String winnerName = "";
77      private int gameOverStatus;
78      private int turn;
79      private Collection messageBuffer = Collections.synchronizedCollection(new ArrayList());
80      private int actualNumberOfPlayers = 0;
81      private Collection allowedNames = new ArrayList();
82      private List registeredUsers = Collections.synchronizedList(new ArrayList());
83      private Map userInfos = new HashMap();
84      private boolean begin;
85      private int direction;
86      private int playerHelloCounter;
87      private Object turnSempahore = new Object();
88      private Object registerSempahore = new Object();
89      private LogMessageBuilder logMessageBuilder = new LogMessageBuilder();
90      private boolean unique;
91      private boolean canRegister = true;
92      private int noOfObservers = 0;
93  
94      /***
95       * @see snifos.application.Application#configure(Properties, boolean)
96       */
97      public void configure(final Properties configuration, boolean isUnique)
98          throws ConfigurationException {
99          unique = isUnique;
100         width = Integer.parseInt(configuration.getProperty("width"));
101         height = Integer.parseInt(configuration.getProperty("height"));
102         goal = Integer.parseInt(configuration.getProperty("goal"));
103 
104         Constructor fieldConstructor = new StandardConstructor(width, height,
105                 goal);
106         field = new BasicGameField(fieldConstructor);
107 
108         for (int i = 1; i <= REQUIRED_NUMBER_OF_PLAYERS; i++) {
109             String playerName = configuration.getProperty("player" + i);
110 
111             if (playerName != null) {
112                 allowedNames.add(playerName);
113             }
114         }
115 
116         begin = true;
117         direction = 1;
118 
119         try {
120             validator = new Validator(configuration.getProperty("xsd"));
121         } catch (VerifierConfigurationException e) {
122             throw new ConfigurationException(e);
123         } catch (SAXException e) {
124             throw new ConfigurationException(e);
125         } catch (IOException e) {
126             throw new ConfigurationException(e);
127         }
128     }
129 
130     /***
131      * @see snifos.application.Application#setServer(snifos.server.Server)
132      */
133     public void setServer(final Server serverToSet) {
134         this.server = serverToSet;
135         sender = new MessageSender(serverToSet);
136     }
137 
138     /***
139      * @see snifos.application.Application#unregisterUser(snifos.common.UserId)
140      */
141     public void unregisterUser(final UserId userId) throws InvalidUserException {
142         synchronized (registeredUsers) {
143             if (!registeredUsers.remove(userId)) {
144                 throw new InvalidUserException();
145             }
146 
147             UserInfo userInfo = (UserInfo) userInfos.remove(userId);
148 
149             if (userInfo.getType() == UserInfo.TYPE_OBSERVER) {
150                 noOfObservers--;
151             } else {
152                 playerHelloCounter--;
153                 actualNumberOfPlayers--;
154             }
155 
156             if (registeredUsers.size() == 0) {
157                 sender.clearUnconfirmedMessages();
158             }
159         }
160     }
161 
162     /***
163      * @see snifos.application.Application#receiveMessage(snifos.common.UserId, org.dom4j.Document)
164      */
165     public synchronized void receiveMessage(final UserId userId,
166         final Document messageDoc) throws InvalidUserException {
167         if (registeredUsers.contains(userId)) {
168             Message message = new Message(userId, messageDoc);
169 
170             if (message.validate(validator)) {
171                 if (message.getMessageType() != Message.CONFIRMATION) {
172                     sender.sendAccepted(message);
173                 }
174 
175                 synchronized (messageBuffer) {
176                     messageBuffer.add(message);
177                 }
178 
179                 synchronized (this) {
180                     notify();
181                 }
182             } else {
183                 sender.sendRefused(message, message.getLastValidationReason());
184                 logMessageBuilder.addError(((UserInfo) userInfos.get(userId)).getName(),
185                     message.getLastValidationReason());
186                 updateInvalidMessagesCount(userId);
187             }
188         } else {
189             throw new InvalidUserException();
190         }
191     }
192 
193     private void updateInvalidMessagesCount(final UserId userId) {
194         UserInfo info = (UserInfo) userInfos.get(userId);
195         int newNoOfBadMessages = info.getNoOfBadMessages() + 1;
196 
197         if (newNoOfBadMessages > MAX_NO_OF_BAD_MESSAGES) {
198             try {
199                 unregisterUser(userId);
200             } catch (InvalidUserException e) {
201                 //It has no meaning here - it's too late ;-)
202             }
203 
204             server.disconnectUser(userId);
205         } else {
206             info.setNoOfBadMessages(newNoOfBadMessages);
207         }
208     }
209 
210     /***
211      * @see java.lang.Runnable#run()
212      */
213     public void run() {
214         do {
215             try {
216                 synchronized (this) {
217                     synchronized (registerSempahore) {
218                         canRegister = true;
219                     }
220 
221                     wait(MESSAGE_TIMEOUT);
222                 }
223             } catch (InterruptedException e) {
224                 logger.error(e.getMessage(), e);
225             }
226 
227             sender.checkConfirmationTimeouts();
228 
229             boolean areNewMessages = false;
230 
231             do {
232                 areNewMessages = false;
233 
234                 Message message = null;
235 
236                 synchronized (messageBuffer) {
237                     Iterator iterator = messageBuffer.iterator();
238 
239                     if (iterator.hasNext()) {
240                         message = (Message) iterator.next();
241                         areNewMessages = true;
242                     }
243                 }
244 
245                 if (areNewMessages) {
246                     serviceMessage(message);
247                     messageBuffer.remove(message);
248                 }
249             } while (areNewMessages);
250 
251             checkGameOver();
252 
253             synchronized (registerSempahore) {
254                 canRegister = false;
255             }
256         } while ((registeredUsers.size() > 0) || unique);
257     }
258 
259     private void checkGameOver() {
260         if (!sender.areUnconfirmedMessages() && (gameOverStatus > 0)) {
261             if (gameOverStatus == 1) {
262                 sendGameOver(winnerName);
263                 gameOverStatus = 2;
264             } else if (gameOverStatus == 2) {
265                 disconnectAllUsers();
266                 gameOverStatus = 0;
267             }
268         }
269     }
270 
271     private void disconnectAllUsers() {
272         synchronized (registeredUsers) {
273             while (registeredUsers.size() > 0) {
274                 Iterator iter = registeredUsers.iterator();
275                 UserId id = (UserId) iter.next();
276 
277                 try {
278                     unregisterUser(id);
279                 } catch (InvalidUserException e) {
280                     // It doesn't matter
281                 }
282 
283                 server.disconnectUser(id);
284             }
285 
286             sender.clearUnconfirmedMessages();
287         }
288     }
289 
290     private void serviceMessage(Message message) {
291         UserInfo userInfo = (UserInfo) userInfos.get(message.getUserId());
292 
293         switch (message.getMessageType()) {
294         case Message.MOVE:
295             serviceMove(message);
296 
297             break;
298 
299         case Message.HELLO:
300             serviceHello(userInfo);
301 
302             break;
303 
304         case Message.CONFIRMATION:
305             sender.setMessageConfirmation(message);
306 
307             break;
308 
309         default:
310             break;
311         }
312 
313         if (message.getMessageType() != Message.CONFIRMATION) {
314             userInfo.setLastMessageDate(new GregorianCalendar());
315         }
316     }
317 
318     private UserId getOtherPlayer(UserId thisPlayer) {
319         synchronized (registeredUsers) {
320             for (Iterator iter = registeredUsers.iterator(); iter.hasNext();) {
321                 UserId id = (UserId) iter.next();
322                 UserInfo userInfo = (UserInfo) userInfos.get(id);
323 
324                 if ((userInfo.getType() == UserInfo.TYPE_PLAYER) &&
325                         (!userInfo.getUserId().equals(thisPlayer))) {
326                     return userInfo.getUserId();
327                 }
328             }
329         }
330 
331         return null;
332     }
333 
334     private void serviceMove(Message message) {
335         UserId winner = null;
336         Date moveDate = new Date();
337 
338         try {
339             field.makeMove(message.getMove(), getTurn());
340         } catch (InvalidMoveException e) {
341             winner = getOtherPlayer(message.getUserId());
342         }
343 
344         if ((winner == null) && (field.isGameOver())) {
345             int goal = field.isGoal();
346 
347             switch (goal) {
348             case -1:
349                 winner = getPlayer(2);
350 
351                 break;
352 
353             case 1:
354                 winner = getPlayer(1);
355 
356                 break;
357 
358             default:
359                 winner = message.getUserId();
360 
361                 break;
362             }
363         }
364 
365         if (winner != null) {
366             UserInfo winnerInfo = (UserInfo) userInfos.get(winner);
367             logger.info("Game over! And the winner is: " +
368                 winnerInfo.getName() + " " + winnerInfo.getUserId());
369             winnerName = winnerInfo.getName();
370             forwardMove(message, true, moveDate);
371             logMessageBuilder.addWinner(winnerName);
372             gameOverStatus = 1;
373         } else {
374             logMessageBuilder.addMove(message.getMove(), moveDate);
375             forwardMove(message, false, moveDate);
376         }
377     }
378 
379     private UserId getPlayer(int pos) {
380         synchronized (registeredUsers) {
381             int counter = 0;
382 
383             for (Iterator iter = registeredUsers.iterator(); iter.hasNext();) {
384                 UserId id = (UserId) iter.next();
385                 UserInfo userInfo = (UserInfo) userInfos.get(id);
386 
387                 if (userInfo.getType() == UserInfo.TYPE_PLAYER) {
388                     counter++;
389                 }
390 
391                 if (counter == pos) {
392                     return id;
393                 }
394             }
395         }
396 
397         return null;
398     }
399 
400     private void forwardMove(Message message, boolean observerOnly,
401         Date moveDate) {
402         synchronized (registeredUsers) {
403             for (Iterator iter = registeredUsers.iterator(); iter.hasNext();) {
404                 UserId id = (UserId) iter.next();
405                 UserInfo loopUserInfo = (UserInfo) userInfos.get(id);
406 
407                 switch (loopUserInfo.getType()) {
408                 case UserInfo.TYPE_PLAYER:
409 
410                     if (!((observerOnly) || (message.getUserId().equals(id)))) {
411                         sender.forwardIncoming(message.getMessage(), id);
412                     }
413 
414                     break;
415 
416                 case UserInfo.TYPE_OBSERVER:
417                     sender.sendLogMove(message.getMove(), id, moveDate);
418 
419                     break;
420 
421                 default:
422                     break;
423                 }
424             }
425         }
426     }
427 
428     private void sendGameOver(String winner) {
429         synchronized (registeredUsers) {
430             for (Iterator iter = registeredUsers.iterator(); iter.hasNext();) {
431                 UserId id = (UserId) iter.next();
432                 UserInfo loopUserInfo = (UserInfo) userInfos.get(id);
433 
434                 switch (loopUserInfo.getType()) {
435                 case UserInfo.TYPE_PLAYER:
436                     sender.sendGameOver(winner, id);
437 
438                     break;
439 
440                 case UserInfo.TYPE_OBSERVER:
441                     sender.sendLogGameOver(winner, id);
442 
443                     break;
444 
445                 default:
446                     break;
447                 }
448             }
449         }
450     }
451 
452     private int getTurn() {
453         synchronized (turnSempahore) {
454             int oldTurn = turn;
455             turn = ((turn + 1) % REQUIRED_NUMBER_OF_PLAYERS) + 1;
456 
457             return oldTurn;
458         }
459     }
460 
461     private void serviceHello(UserInfo userInfo) {
462         switch (userInfo.getType()) {
463         case UserInfo.TYPE_PLAYER:
464             playerHelloCounter++;
465 
466             if (playerHelloCounter == REQUIRED_NUMBER_OF_PLAYERS) {
467                 logMessageBuilder = new LogMessageBuilder();
468                 sendInitialGameInfo(true);
469                 logMessageBuilder.addMap(width, height, goal);
470                 logMessageBuilder.addBegin(((UserInfo) userInfos.get(getPlayer(
471                             1))).getName());
472                 sendInitialGameInfo(false);
473                 setTurn(1);
474                 gameOverStatus = 0;
475                 field.startGame();
476             }
477 
478             break;
479 
480         case UserInfo.TYPE_OBSERVER:
481 
482             if (playerHelloCounter == REQUIRED_NUMBER_OF_PLAYERS) {
483                 sender.sendWithConfirmation(userInfo.getUserId(),
484                     logMessageBuilder.getMessage());
485             }
486 
487             break;
488 
489         default:
490             break;
491         }
492     }
493 
494     private void setTurn(int i) {
495         synchronized (turnSempahore) {
496             turn = i;
497         }
498     }
499 
500     private void sendInitialGameInfo(boolean toPlayers) {
501         synchronized (registeredUsers) {
502             for (Iterator iter = registeredUsers.iterator(); iter.hasNext();) {
503                 UserInfo loopUserInfo = (UserInfo) userInfos.get(iter.next());
504 
505                 switch (loopUserInfo.getType()) {
506                 case UserInfo.TYPE_PLAYER:
507 
508                     if (toPlayers) {
509                         logMessageBuilder.addName(loopUserInfo.getName());
510                         sender.sendGameParameters(loopUserInfo.getUserId(),
511                             width, height, goal, direction, begin);
512                         direction = -1 * direction;
513                         begin = !begin;
514                     }
515 
516                     break;
517 
518                 case UserInfo.TYPE_OBSERVER:
519 
520                     if (!toPlayers) {
521                         sender.sendWithConfirmation(loopUserInfo.getUserId(),
522                             logMessageBuilder.getMessage());
523                     }
524 
525                     break;
526 
527                 default:
528                     break;
529                 }
530             }
531         }
532     }
533 
534     /***
535      * @see snifos.application.Application#registerUser(UserId, Document)
536      */
537     public synchronized void registerUser(UserId userId, Document message)
538         throws InvalidUserException {
539         UserInfo userInfo = new UserInfo(userId, message);
540 
541         synchronized (registerSempahore) {
542             if (canRegister) {
543                 if ((userInfo.getType() == UserInfo.TYPE_OBSERVER) &&
544                         (noOfObservers < MAX_NO_OF_OBSERVERS)) {
545                     noOfObservers++;
546                     registeredUsers.add(userId);
547                     userInfos.put(userId, userInfo);
548                 } else if ((userInfo.getType() == UserInfo.TYPE_PLAYER) &&
549                         (actualNumberOfPlayers < REQUIRED_NUMBER_OF_PLAYERS)) {
550                     if (((allowedNames.size() > 0) &&
551                             (allowedNames.contains(userInfo.getName()))) ||
552                             (allowedNames.size() == 0)) {
553                         registeredUsers.add(userId);
554                         userInfos.put(userId, userInfo);
555                         actualNumberOfPlayers++;
556                     } else {
557                         throw new InvalidUserException();
558                     }
559                 } else {
560                     throw new InvalidUserException();
561                 }
562             } else {
563                 throw new InvalidUserException();
564             }
565         }
566     }
567 }