OSDN Git Service

抜けたthisを補充
[jindolf/Jindolf.git] / src / main / java / jp / sfjp / jindolf / data / Village.java
1 /*
2  * Village
3  *
4  * License : The MIT License
5  * Copyright(c) 2008 olyutorskii
6  */
7
8 package jp.sfjp.jindolf.data;
9
10 import java.awt.image.BufferedImage;
11 import java.io.IOException;
12 import java.text.MessageFormat;
13 import java.util.Collections;
14 import java.util.Comparator;
15 import java.util.HashMap;
16 import java.util.LinkedList;
17 import java.util.List;
18 import java.util.Map;
19 import java.util.logging.Level;
20 import java.util.logging.Logger;
21 import jp.sfjp.jindolf.net.HtmlSequence;
22 import jp.sfjp.jindolf.net.ServerAccess;
23 import jp.sfjp.jindolf.util.GUIUtils;
24 import jp.sourceforge.jindolf.corelib.LandDef;
25 import jp.sourceforge.jindolf.corelib.LandState;
26 import jp.sourceforge.jindolf.corelib.PeriodType;
27 import jp.sourceforge.jindolf.corelib.VillageState;
28 import jp.sourceforge.jindolf.parser.DecodedContent;
29 import jp.sourceforge.jindolf.parser.HtmlAdapter;
30 import jp.sourceforge.jindolf.parser.HtmlParseException;
31 import jp.sourceforge.jindolf.parser.HtmlParser;
32 import jp.sourceforge.jindolf.parser.PageType;
33 import jp.sourceforge.jindolf.parser.SeqRange;
34
35 /**
36  * いわゆる「村」。
37  */
38 public class Village implements Comparable<Village> {
39
40     private static final int GID_MIN = 3;
41
42     private static final Comparator<Village> VILLAGE_COMPARATOR =
43             new VillageComparator();
44
45     private static final HtmlParser PARSER = new HtmlParser();
46     private static final VillageHeadHandler HANDLER =
47             new VillageHeadHandler();
48
49     private static final Logger LOGGER = Logger.getAnonymousLogger();
50
51     static{
52         PARSER.setBasicHandler   (HANDLER);
53         PARSER.setSysEventHandler(HANDLER);
54         PARSER.setTalkHandler    (HANDLER);
55     }
56
57
58     private final Land parentLand;
59     private final String villageID;
60     private final int villageIDNum;
61     private final String villageName;
62
63     private final boolean isValid;
64
65     private int limitMonth;
66     private int limitDay;
67     private int limitHour;
68     private int limitMinute;
69
70     private VillageState state = VillageState.UNKNOWN;
71
72     private final LinkedList<Period> periodList = new LinkedList<>();
73     private final List<Period> unmodList =
74             Collections.unmodifiableList(this.periodList);
75
76     private final Map<String, Avatar> avatarMap =
77             new HashMap<>();
78
79     private final Map<Avatar, BufferedImage> faceImageMap =
80             new HashMap<>();
81     private final Map<Avatar, BufferedImage> bodyImageMap =
82             new HashMap<>();
83     private final Map<Avatar, BufferedImage> faceMonoImageMap =
84             new HashMap<>();
85     private final Map<Avatar, BufferedImage> bodyMonoImageMap =
86             new HashMap<>();
87
88
89     /**
90      * Villageを生成する。
91      * @param parentLand Villageの所属する国
92      * @param villageID 村のID
93      * @param villageName 村の名前
94      */
95     public Village(Land parentLand, String villageID, String villageName) {
96         this.parentLand    = parentLand;
97         this.villageID   = villageID.intern();
98         this.villageIDNum = Integer.parseInt(this.villageID);
99         this.villageName = villageName.intern();
100
101         this.isValid = this.parentLand.getLandDef()
102                        .isValidVillageId(this.villageIDNum);
103
104         return;
105     }
106
107
108     /**
109      * 村同士を比較するためのComparatorを返す。
110      * @return Comparatorインスタンス
111      */
112     public static Comparator<Village> comparator(){
113         return VILLAGE_COMPARATOR;
114     }
115
116     /**
117      * 人狼BBSサーバからPeriod一覧情報が含まれたHTMLを取得し、
118      * Periodリストを更新する。
119      * @param village 村
120      * @throws java.io.IOException ネットワーク入出力の異常
121      */
122     public static synchronized void updateVillage(Village village)
123             throws IOException{
124         Land land = village.getParentLand();
125         LandDef landDef = land.getLandDef();
126         LandState landState = landDef.getLandState();
127         ServerAccess server = land.getServerAccess();
128
129         HtmlSequence html;
130         if(landState == LandState.ACTIVE){
131             html = server.getHTMLBoneHead(village);
132         }else{
133             html = server.getHTMLVillage(village);
134         }
135
136         DecodedContent content = html.getContent();
137         HANDLER.setVillage(village);
138         try{
139             PARSER.parseAutomatic(content);
140         }catch(HtmlParseException e){
141             LOGGER.log(Level.WARNING, "村の状態が不明", e);
142         }
143
144         return;
145     }
146
147     /**
148      * 所属する国を返す。
149      * @return 村の所属する国(Land)
150      */
151     public Land getParentLand(){
152         return this.parentLand;
153     }
154
155     /**
156      * 村のID文字列を返す。
157      * @return 村ID
158      */
159     public String getVillageID(){
160         return this.villageID;
161     }
162
163     /**
164      * 村のID数値を返す。
165      * @return 村ID
166      */
167     public int getVillageIDNum(){
168         return this.villageIDNum;
169     }
170
171     /**
172      * 村の名前を返す。
173      * @return 村の名前
174      */
175     public String getVillageName(){
176         StringBuilder name = new StringBuilder();
177
178         LandDef landDef = this.parentLand.getLandDef();
179         String prefix = landDef.getLandPrefix();
180         name.append(prefix);
181
182         StringBuilder id = new StringBuilder(this.villageID);
183         if(landDef.getLandId().equals("wolfg")){
184             while(id.length() < GID_MIN){
185                 id.insert(0, '0');
186             }
187         }
188         name.append(id);
189
190         String result = name.toString();
191         return result;
192     }
193
194     /**
195      * 村の長い名前を返す。
196      * @return 村の長い名前
197      */
198     public String getVillageFullName(){
199         return this.villageName;
200     }
201
202     /**
203      * 村の状態を返す。
204      * @return 村の状態
205      */
206     public VillageState getState(){
207         return this.state;
208     }
209
210     /**
211      * 村の状態を設定する。
212      * @param state 村の状態
213      */
214     public void setState(VillageState state){
215         this.state = state;
216         return;
217     }
218
219     /**
220      * プロローグを返す。
221      * @return プロローグ
222      */
223     public Period getPrologue(){
224         for(Period period : this.periodList){
225             if(period.isPrologue()) return period;
226         }
227         return null;
228     }
229
230     /**
231      * エピローグを返す。
232      * @return エピローグ
233      */
234     public Period getEpilogue(){
235         for(Period period : this.periodList){
236             if(period.isEpilogue()) return period;
237         }
238         return null;
239     }
240
241     /**
242      * 指定された日付の進行日を返す。
243      * @param day 日付
244      * @return Period
245      */
246     public Period getProgress(int day){
247         for(Period period : this.periodList){
248             if(   period.isProgress()
249                && period.getDay() == day ) return period;
250         }
251         return null;
252     }
253
254     /**
255      * PROGRESS状態のPeriodの総数を返す。
256      * @return PROGRESS状態のPeriod総数
257      */
258     public int getProgressDays(){
259         int result = 0;
260         for(Period period : this.periodList){
261             if(period.isProgress()) result++;
262         }
263         return result;
264     }
265
266     /**
267      * 指定されたPeriodインデックスのPeriodを返す。
268      * プロローグやエピローグへのアクセスも可能。
269      * @param day Periodインデックス
270      * @return Period
271      */
272     public Period getPeriod(int day){
273         return this.periodList.get(day);
274     }
275
276     /**
277      * 指定されたアンカーの対象のPeriodを返す。
278      * @param anchor アンカー
279      * @return Period
280      */
281     public Period getPeriod(Anchor anchor){
282         Period anchorPeriod;
283
284         if(anchor.isEpilogueDay()){
285             anchorPeriod = getEpilogue();
286             return anchorPeriod;
287         }
288
289         int anchorDay = anchor.getDay();
290         anchorPeriod = getPeriod(anchorDay);
291
292         return anchorPeriod;
293     }
294
295     /**
296      * Period総数を返す。
297      * @return Period総数
298      */
299     public int getPeriodSize(){
300         return this.periodList.size();
301     }
302
303     /**
304      * Periodへのリストを返す。
305      * @return Periodのリスト。
306      */
307     public List<Period> getPeriodList(){
308         return this.unmodList;
309     }
310
311     /**
312      * 指定した名前で村に登録されているAvatarを返す。
313      * @param fullName Avatarの名前
314      * @return Avatar
315      */
316     public Avatar getAvatar(String fullName){
317         // TODO CharSequenceにできない?
318         Avatar avatar;
319
320         avatar = Avatar.getPredefinedAvatar(fullName);
321         if( avatar != null ){
322             preloadAvatarFace(avatar);
323             return avatar;
324         }
325
326         avatar = this.avatarMap.get(fullName);
327         if( avatar != null ){
328             preloadAvatarFace(avatar);
329             return avatar;
330         }
331
332         return null;
333     }
334
335     /**
336      * Avatarの顔画像を事前にロードする。
337      * @param avatar Avatar
338      */
339     private void preloadAvatarFace(Avatar avatar){
340         if(this.faceImageMap.get(avatar) != null) return;
341
342         Land land = getParentLand();
343         LandDef landDef = land.getLandDef();
344
345         String template = landDef.getFaceURITemplate();
346         int serialNo = avatar.getIdNum();
347         String uri = MessageFormat.format(template, serialNo);
348
349         BufferedImage image = land.downloadImage(uri);
350         if(image == null) image = GUIUtils.getNoImage();
351
352         this.faceImageMap.put(avatar, image);
353
354         return;
355     }
356
357     /**
358      * Avatarを村に登録する。
359      * @param avatar Avatar
360      */
361     // 未知のAvatar出現時の処理が不完全
362     public void addAvatar(Avatar avatar){
363         if(avatar == null) return;
364         String fullName = avatar.getFullName();
365         this.avatarMap.put(fullName, avatar);
366
367         preloadAvatarFace(avatar);
368
369         return;
370     }
371
372     /**
373      * 村に登録されたAvatarの顔イメージを返す。
374      * @param avatar Avatar
375      * @return 顔イメージ
376      */
377     // TODO 失敗したらプロローグを強制読み込みして再トライしたい
378     public BufferedImage getAvatarFaceImage(Avatar avatar){
379         return this.faceImageMap.get(avatar);
380     }
381
382     /**
383      * 村に登録されたAvatarの全身像イメージを返す。
384      * @param avatar Avatar
385      * @return 全身イメージ
386      */
387     public BufferedImage getAvatarBodyImage(Avatar avatar){
388         BufferedImage result;
389         result = this.bodyImageMap.get(avatar);
390         if(result != null) return result;
391
392         Land land = getParentLand();
393         LandDef landDef = land.getLandDef();
394
395         String template = landDef.getBodyURITemplate();
396         int serialNo = avatar.getIdNum();
397         String uri = MessageFormat.format(template, serialNo);
398
399         result = land.downloadImage(uri);
400         if(result == null) result = GUIUtils.getNoImage();
401
402         this.bodyImageMap.put(avatar, result);
403
404         return result;
405     }
406
407     /**
408      * 村に登録されたAvatarのモノクロ顔イメージを返す。
409      * @param avatar Avatar
410      * @return 顔イメージ
411      */
412     public BufferedImage getAvatarFaceMonoImage(Avatar avatar){
413         BufferedImage result;
414         result = this.faceMonoImageMap.get(avatar);
415         if(result == null){
416             result = getAvatarFaceImage(avatar);
417             result = GUIUtils.createMonoImage(result);
418             this.faceMonoImageMap.put(avatar, result);
419         }
420         return result;
421     }
422
423     /**
424      * 村に登録されたAvatarの全身像イメージを返す。
425      * @param avatar Avatar
426      * @return 全身イメージ
427      */
428     public BufferedImage getAvatarBodyMonoImage(Avatar avatar){
429         BufferedImage result;
430         result = this.bodyMonoImageMap.get(avatar);
431         if(result == null){
432             result = getAvatarBodyImage(avatar);
433             result = GUIUtils.createMonoImage(result);
434             this.bodyMonoImageMap.put(avatar, result);
435         }
436         return result;
437     }
438
439     /**
440      * 国に登録された墓イメージを返す。
441      * @return 墓イメージ
442      */
443     public BufferedImage getGraveImage(){
444         BufferedImage result = getParentLand().getGraveIconImage();
445         return result;
446     }
447
448     /**
449      * 国に登録された墓イメージ(大)を返す。
450      * @return 墓イメージ(大)
451      */
452     public BufferedImage getGraveBodyImage(){
453         BufferedImage result = getParentLand().getGraveBodyImage();
454         return result;
455     }
456
457     /**
458      * 村にアクセスするためのCGIクエリーを返す。
459      * @return CGIクエリー
460      */
461     public CharSequence getCGIQuery(){
462         StringBuilder result = new StringBuilder();
463         result.append("?vid=").append(getVillageID());
464         return result;
465     }
466
467     /**
468      * 次回更新月を返す。
469      * @return 更新月(1-12)
470      */
471     public int getLimitMonth(){
472         return this.limitMonth;
473     }
474
475     /**
476      * 次回更新日を返す。
477      * @return 更新日(1-31)
478      */
479     public int getLimitDay(){
480         return this.limitDay;
481     }
482
483     /**
484      * 次回更新時を返す。
485      * @return 更新時(0-23)
486      */
487     public int getLimitHour(){
488         return this.limitHour;
489     }
490
491     /**
492      * 次回更新分を返す。
493      * @return 更新分(0-59)
494      */
495     public int getLimitMinute(){
496         return this.limitMinute;
497     }
498
499     /**
500      * 有効な村か否か判定する。
501      * @return 無効な村ならfalse
502      */
503     public boolean isValid(){
504         return this.isValid;
505     }
506
507     /**
508      * Periodリストの指定したインデックスにPeriodを上書きする。
509      * リストのサイズと同じインデックスを指定する事が許される。
510      * その場合の動作はList.addと同じ。
511      * @param index Periodリストのインデックス。
512      * @param period 上書きするPeriod
513      * @throws java.lang.IndexOutOfBoundsException インデックスの指定がおかしい
514      */
515     private void setPeriod(int index, Period period)
516             throws IndexOutOfBoundsException{
517         int listSize = this.periodList.size();
518         if(index == listSize){
519             this.periodList.add(period);
520         }else if(index < listSize){
521             this.periodList.set(index, period);
522         }else{
523             throw new IndexOutOfBoundsException();
524         }
525         return;
526     }
527
528     /**
529      * アンカーに一致する会話(Talk)のリストを取得する。
530      * @param anchor アンカー
531      * @return Talkのリスト
532      * @throws java.io.IOException おそらくネットワークエラー
533      */
534     public List<Talk> getTalkListFromAnchor(Anchor anchor)
535             throws IOException{
536         List<Talk> result = new LinkedList<>();
537
538         /* G国アンカー対応 */
539         if(anchor.hasTalkNo()){
540             // 事前に全Periodがロードされているのが前提
541             for(Period period : this.periodList){
542                 Talk talk = period.getNumberedTalk(anchor.getTalkNo());
543                 if(talk == null) continue;
544                 result.add(talk);
545             }
546             return result;
547         }
548
549         Period anchorPeriod = getPeriod(anchor);
550         if(anchorPeriod == null) return result;
551
552         Period.parsePeriod(anchorPeriod, false);
553
554         for(Topic topic : anchorPeriod.getTopicList()){
555             if( ! (topic instanceof Talk) ) continue;
556             Talk talk = (Talk) topic;
557             if(talk.getHour()   != anchor.getHour()  ) continue;
558             if(talk.getMinute() != anchor.getMinute()) continue;
559             result.add(talk);
560         }
561         return result;
562     }
563
564     /**
565      * 全Periodの発言データをアンロードする。
566      */
567     public void unloadPeriods(){
568         for(Period period : this.periodList){
569             period.unload();
570         }
571         return;
572     }
573
574     /**
575      * {@inheritDoc}
576      * 二つの村を順序付ける。
577      * @param village {@inheritDoc}
578      * @return {@inheritDoc}
579      */
580     @Override
581     public int compareTo(Village village){
582         int cmpResult = VILLAGE_COMPARATOR.compare(this, village);
583         return cmpResult;
584     }
585
586     /**
587      * {@inheritDoc}
588      * 二つの村が等しいか調べる。
589      * @param obj {@inheritDoc}
590      * @return {@inheritDoc}
591      */
592     @Override
593     public boolean equals(Object obj){
594         if(obj == null) return false;
595         if( ! (obj instanceof Village) ) return false;
596         Village village = (Village) obj;
597
598         if( getParentLand() != village.getParentLand() ) return false;
599
600         int cmpResult = compareTo(village);
601         if(cmpResult == 0) return true;
602         return false;
603     }
604
605     /**
606      * {@inheritDoc}
607      * @return {@inheritDoc}
608      */
609     @Override
610     public int hashCode(){
611         int homeHash = getParentLand().hashCode();
612         int vidHash = getVillageID().hashCode();
613         int result = homeHash ^ vidHash;
614         return result;
615     }
616
617     /**
618      * {@inheritDoc}
619      * 村の文字列表現を返す。
620      * 村の名前と等しい。
621      * @return 村の名前
622      */
623     @Override
624     public String toString(){
625         return getVillageFullName();
626     }
627
628
629     /**
630      * Period一覧取得用ハンドラ。
631      */
632     private static class VillageHeadHandler extends HtmlAdapter{
633
634         private Village village = null;
635
636         private boolean hasPrologue;
637         private boolean hasProgress;
638         private boolean hasEpilogue;
639         private boolean hasDone;
640         private int maxProgress;
641
642         /**
643          * コンストラクタ。
644          */
645         public VillageHeadHandler(){
646             super();
647             return;
648         }
649
650         /**
651          * 更新対象の村を設定する。
652          * @param village 村
653          */
654         public void setVillage(Village village){
655             this.village = village;
656             return;
657         }
658
659         /**
660          * リセットを行う。
661          */
662         public void reset(){
663             this.hasPrologue = false;
664             this.hasProgress = false;
665             this.hasEpilogue = false;
666             this.hasDone = false;
667             this.maxProgress = 0;
668             return;
669         }
670
671         /**
672          * パース結果から村の状態を算出する。
673          * @return 村の状態
674          */
675         public VillageState getVillageState(){
676             if(this.hasDone){
677                 return VillageState.GAMEOVER;
678             }else if(this.hasEpilogue){
679                 return VillageState.EPILOGUE;
680             }else if(this.hasProgress){
681                 return VillageState.PROGRESS;
682             }else if(this.hasPrologue){
683                 return VillageState.PROLOGUE;
684             }
685
686             return VillageState.UNKNOWN;
687         }
688
689         /**
690          * {@inheritDoc}
691          * @param content {@inheritDoc}
692          * @throws HtmlParseException {@inheritDoc}
693          */
694         @Override
695         public void startParse(DecodedContent content)
696                 throws HtmlParseException{
697             reset();
698             return;
699         }
700
701         /**
702          * {@inheritDoc}
703          * 自動判定の結果が日ページでなければ例外を投げる。
704          * @param type {@inheritDoc}
705          * @throws HtmlParseException {@inheritDoc} 意図しないページが来た。
706          */
707         @Override
708         public void pageType(PageType type) throws HtmlParseException{
709             if(type != PageType.PERIOD_PAGE){
710                 throw new HtmlParseException(
711                         "日ページが必要です。");
712             }
713             return;
714         }
715
716         /**
717          * {@inheritDoc}
718          * @param month {@inheritDoc}
719          * @param day {@inheritDoc}
720          * @param hour {@inheritDoc}
721          * @param minute {@inheritDoc}
722          * @throws HtmlParseException {@inheritDoc}
723          */
724         @Override
725         public void commitTime(int month, int day,
726                                 int hour, int minute)
727                 throws HtmlParseException{
728             this.village.limitMonth  = month;
729             this.village.limitDay    = day;
730             this.village.limitHour   = hour;
731             this.village.limitMinute = minute;
732
733             return;
734         }
735
736         /**
737          * {@inheritDoc}
738          * @param content {@inheritDoc}
739          * @param anchorRange {@inheritDoc}
740          * @param periodType {@inheritDoc}
741          * @param day {@inheritDoc}
742          * @throws HtmlParseException {@inheritDoc}
743          */
744         @Override
745         public void periodLink(DecodedContent content,
746                                 SeqRange anchorRange,
747                                 PeriodType periodType,
748                                 int day)
749                 throws HtmlParseException{
750             if(periodType == null){
751                 this.hasDone = true;
752                 return;
753             }
754
755             switch(periodType){
756             case PROLOGUE:
757                 this.hasPrologue = true;
758                 break;
759             case PROGRESS:
760                 this.hasProgress = true;
761                 this.maxProgress = day;
762                 break;
763             case EPILOGUE:
764                 this.hasEpilogue = true;
765                 break;
766             default:
767                 assert false;
768                 break;
769             }
770
771             return;
772         }
773
774         /**
775          * {@inheritDoc}
776          * @throws HtmlParseException {@inheritDoc}
777          */
778         @Override
779         public void endParse() throws HtmlParseException{
780             Land land = this.village.getParentLand();
781             LandDef landDef = land.getLandDef();
782             LandState landState = landDef.getLandState();
783
784             VillageState villageState = getVillageState();
785             if(villageState == VillageState.UNKNOWN){
786                 this.village.setState(villageState);
787                 this.village.periodList.clear();
788                 LOGGER.warning("村の状況を読み取れません");
789                 return;
790             }
791
792             if(landState == LandState.ACTIVE){
793                 this.village.setState(villageState);
794             }else{
795                 this.village.setState(VillageState.GAMEOVER);
796             }
797
798             modifyPeriodList();
799
800             return;
801         }
802
803         /**
804          * 抽出したリンク情報に伴いPeriodリストを更新する。
805          * まだPeriodデータのロードは行われない。
806          * ゲーム進行中の村で更新時刻をまたいで更新が行われた場合、
807          * 既存のPeriodリストが伸張する場合がある。
808          */
809         private void modifyPeriodList(){
810             Period lastPeriod = null;
811
812             if(this.hasPrologue){
813                 Period prologue = this.village.getPrologue();
814                 if(prologue == null){
815                     lastPeriod = new Period(this.village,
816                                             PeriodType.PROLOGUE, 0);
817                     this.village.setPeriod(0, lastPeriod);
818                 }else{
819                     lastPeriod = prologue;
820                 }
821             }
822
823             if(this.hasProgress){
824                 for(int day = 1; day <= this.maxProgress; day++){
825                     Period progress = this.village.getProgress(day);
826                     if(progress == null){
827                         lastPeriod = new Period(this.village,
828                                                 PeriodType.PROGRESS, day);
829                         this.village.setPeriod(day, lastPeriod);
830                     }else{
831                         lastPeriod = progress;
832                     }
833                 }
834             }
835
836             if(this.hasEpilogue){
837                 Period epilogue = this.village.getEpilogue();
838                 if(epilogue == null){
839                     lastPeriod = new Period(this.village,
840                                             PeriodType.EPILOGUE,
841                                             this.maxProgress +1);
842                     this.village.setPeriod(this.maxProgress +1, lastPeriod);
843                 }else{
844                     lastPeriod = epilogue;
845                 }
846             }
847
848             assert this.village.getPeriodSize() > 0;
849             assert lastPeriod != null;
850
851             // 念のためチョップ。
852             // リロードで村が縮むわけないじゃん。みんな大げさだなあ
853             while(this.village.periodList.getLast() != lastPeriod){
854                 this.village.periodList.removeLast();
855             }
856
857             if(this.village.getState() != VillageState.GAMEOVER){
858                 lastPeriod.setHot(true);
859             }
860
861             return;
862         }
863
864     }
865
866
867     /**
868      * 村同士を比較するためのComparator。
869      */
870     private static class VillageComparator implements Comparator<Village> {
871
872         /**
873          * コンストラクタ。
874          */
875         public VillageComparator(){
876             super();
877             return;
878         }
879
880         /**
881          * {@inheritDoc}
882          * @param v1 {@inheritDoc}
883          * @param v2 {@inheritDoc}
884          * @return {@inheritDoc}
885          */
886         @Override
887         public int compare(Village v1, Village v2){
888             int v1Num;
889             if(v1 == null) v1Num = Integer.MIN_VALUE;
890             else           v1Num = v1.getVillageIDNum();
891
892             int v2Num;
893             if(v2 == null) v2Num = Integer.MIN_VALUE;
894             else           v2Num = v2.getVillageIDNum();
895
896             return v1Num - v2Num;
897         }
898
899     }
900
901 }