ãã¹ãŠã®ããã°ã©ããŒãé¢çœãã²ãŒã ãäœããããšæã£ãç¬éããããŸããã å€ãã®ããã°ã©ããŒã¯ãããã®å€¢ãå®çŸããããã«ã¯æåããŸãããããã¯åœŒãã«é¢ãããã®ã§ã¯ãããŸããã ããã¯ãã²ãŒã ããã¬ã€ããã®ã奜ããªäººãç¥èãçµéšããªããŠãäžåºŠã²ãŒã ãäœæããããšããŠãäžççãªå声ïŒããã³å€§ããªå©çïŒãéæããå€ç¬ãªããŒããŒã®äŸã«è§Šçºããããã圌ã¯äœè£ããªãã°ã«ã€ã°ãã¹ããã€ãšç«¶ããŸãã
ããªãã§ãã ãã...
å°ããªçŽ¹ä»
ããã«äºçŽããŸããç§ãã¡ã®ç®æšã¯ãéã皌ãããšã§ã¯ãããŸãã-ãã®ãããã¯ã«é¢ããå€ãã®èšäºãHabréã«ãããŸãã ãããã倢ã®ã²ãŒã ãäœããŸãã
倢ã®ã²ãŒã ã«ã€ããŠã®åæ
çãªäœè«
ç¬èº«ã®éçºè
ãå°ããªã¹ã¿ãžãªãããã®èšèãäœåèããããšããããŸãã ã©ãã«ããŠããåå¿è
ã®ã€ã°ããããã¯åœŒãã®å€¢ãšãå®ç§ãªããžã§ã³ããæ¥ãã§äžçã«å
¬éãã圌ãã®è±éçãªåªåãä»äºã®ããã»ã¹ãé¿ããããªãçµæžçå°é£ãåºç瀟ãšäžè¬çãªããã¬ãŒã€ãŒ-æ©æµã®ãªãç¬-ã€ã -ã°ã©ããšã³ã€ã³ãšãã¹ãŠãç¡æã§æ¯æããã²ãŒã ãããªãã§ãã ãã-æµ·è³-ãããŠãç§ãã¡ã¯-圌ãã®ãããã§å€±ãããå©ç-ããã ãã§ãã
人ã ãã ãŸãããŠã¯ãããŸããã ããªãã¯å€¢ã®ã²ãŒã ãäœã£ãŠããã®ã§ã¯ãªãããã売ããã²ãŒã ãäœã£ãŠããŸã-ãããã¯2ã€ã®ç°ãªããã®ã§ãã ãã¬ã€ã€ãŒïŒç¹ã«æŽç·Žããããã¬ã€ã€ãŒïŒã¯ããªãã®å€¢ãæ°ã«ããŸããã å©çãå¿ èŠãªå Žåããã¬ã³ããç 究ããçŸåšäººæ°ã®ãããã®ã確èªãããŠããŒã¯ãªããšãããä»ã®äººãããåªãããããçããããšãè¡ããèšäºãèªã¿ïŒå€ããããŸãïŒãåºç瀟ãšã³ãã¥ãã±ãŒã·ã§ã³ãåããŸã-äžè¬ã«ãããªãã§ã¯ãªããšã³ããŠãŒã¶ãŒã®å€¢ãå®çŸããŸãã
ãŸã éããŠããªãã®ã«å€¢ã®ã²ãŒã ãå®çŸãããå Žåã¯ãäºåã«å©çãæŸæ£ããŠãã ããã 倢ã売ãã®ã§ã¯ãªããç¡æã§å ±æããŸãããã 人ã ã«ããªãã®å€¢ãäžãã圌ãã«ããããããããããªãã®å€¢ãäœã䟡å€ããããªããããªãã¯ãéã§ã¯ãªããŠããæãšèªèãåãåããŸãã ããã¯æã«ã¯ã¯ããã«äŸ¡å€ããããŸãã
人ã ãã ãŸãããŠã¯ãããŸããã ããªãã¯å€¢ã®ã²ãŒã ãäœã£ãŠããã®ã§ã¯ãªãããã売ããã²ãŒã ãäœã£ãŠããŸã-ãããã¯2ã€ã®ç°ãªããã®ã§ãã ãã¬ã€ã€ãŒïŒç¹ã«æŽç·Žããããã¬ã€ã€ãŒïŒã¯ããªãã®å€¢ãæ°ã«ããŸããã å©çãå¿ èŠãªå Žåããã¬ã³ããç 究ããçŸåšäººæ°ã®ãããã®ã確èªãããŠããŒã¯ãªããšãããä»ã®äººãããåªãããããçããããšãè¡ããèšäºãèªã¿ïŒå€ããããŸãïŒãåºç瀟ãšã³ãã¥ãã±ãŒã·ã§ã³ãåããŸã-äžè¬ã«ãããªãã§ã¯ãªããšã³ããŠãŒã¶ãŒã®å€¢ãå®çŸããŸãã
ãŸã éããŠããªãã®ã«å€¢ã®ã²ãŒã ãå®çŸãããå Žåã¯ãäºåã«å©çãæŸæ£ããŠãã ããã 倢ã売ãã®ã§ã¯ãªããç¡æã§å ±æããŸãããã 人ã ã«ããªãã®å€¢ãäžãã圌ãã«ããããããããããªãã®å€¢ãäœã䟡å€ããããªããããªãã¯ãéã§ã¯ãªããŠããæãšèªèãåãåããŸãã ããã¯æã«ã¯ã¯ããã«äŸ¡å€ããããŸãã
å€ãã®äººã¯ãã²ãŒã ã¯æéãšåŽåã®ç¡é§ã§ãããçé¢ç®ãªäººã¯ãã®ãããã¯ã«ã€ããŠãŸã£ãã話ãã¹ãã§ã¯ãªããšèããŠããŸãã ããããããã«éãŸã£ã人ã ã¯æ·±å»ã§ã¯ãªãã®ã§ãç§ãã¡ã¯éšåçã«åæããŸã-ããªãããããããã¬ã€ããå Žåãã²ãŒã ã¯æ¬åœã«å€ãã®æéãããããŸãã ãã ããã²ãŒã ã®éçºã«ã¯äœåãæéãããããŸãããå€ãã®ã¡ãªããããããŸãã ããšãã°ãã²ãŒã 以å€ã®ã¢ããªã±ãŒã·ã§ã³ã®éçºã«ã¯èŠãããªãååãã¢ãããŒããã¢ã«ãŽãªãºã ã«æ £ããããšãã§ããŸãã ãŸãã¯ãããŒã«ïŒããšãã°ãããã°ã©ãã³ã°èšèªïŒãææããã¹ãã«ãæ·±ããç°åžžã§åºæ¿çãªããšãè¡ããŸãã èªåã§ã²ãŒã éçºïŒæåããªããŠãïŒã¯åžžã«ç¹å¥ã§æ¯é¡ã®ãªãäœéšã§ãããšä»ãå ããŸãïŒãããŠå€ãã®äººãåæããŸãïŒããããææãšæã§æãåºããŸãã
ç§ãã¡ã¯ãææ°ã®ã²ãŒã ãšã³ãžã³ããã¬ãŒã ã¯ãŒã¯ãã©ã€ãã©ãªã䜿çšããŸãããã²ãŒã ãã¬ã€ã®æ¬è³ªãèŠãŠãå éšããæããŸãã æè»ãªéçºæ¹æ³è«ã¯æŸæ£ããŸãïŒ1人ã®äœæ¥ãæŽçããå¿ èŠããããããã¿ã¹ã¯ã¯ç°¡çŽ åãããŸãïŒã ãµãŠã³ãã®ãã¶ã€ããŒãã¢ãŒãã£ã¹ããäœæ²å®¶ãã¹ãã·ã£ãªã¹ããæ¢ãããã«æéãšãšãã«ã®ãŒãè²»ããããšã¯ãããŸãã-ç§ãã¡ã¯ã§ããéãã®ããšããã¹ãŠããŸãå®æãããã¬ãŒã äžã®ã°ã©ãã£ãã¯ïŒã æçµçã«ã¯ãããŒã«ãå®éã«ç 究ããŠé©åãªããŒã«ãéžæããããšããããŸããã䜿çšæ¹æ³ãããã£ãŠãããã®ã§å®è¡ããŸãã ããšãã°ãJavaã®å ŽåãåŸã§å¿ èŠã«å¿ããŠAndroidïŒãŸãã¯ã³ãŒããŒã¡ãŒã«ãŒïŒã«è»¢éããŸãã
ããã!!! ãã©ãŒïŒ æªå€¢ïŒ ã©ãããŠãããªãã³ã»ã³ã¹ã«æéãè²»ããããšãã§ããŸããïŒ ããããåºãŠããã£ãšé¢çœããã®ãèªãã§ãããŸãããïŒã
ããã¯ãªãã§ããïŒ ã€ãŸããè»èŒªãåçºæããŸããïŒ æ¢è£œã®ã²ãŒã ãšã³ãžã³ã䜿çšããŠã¿ãŸãããïŒ çãã¯ç°¡åã§ãã圌ã«ã€ããŠã¯äœãç¥ããŸããããä»ã¯ã²ãŒã ã欲ããã§ãã å¹³åçãªããã°ã©ããŒã®èãæ¹ãæ³åããŠãã ããããç§ã¯ã²ãŒã ãäœãããïŒ èãççºããã³ããããã
ããã§ã¯ãç¶ããŸãããã
ç§èªèº«ã®èŠãçµéšã®è©³çŽ°ã«ã¯è§ŠããŸããããã²ãŒã éçºã«ãããããã°ã©ããŒã®äž»ãªåé¡ã®1ã€ã¯ã°ã©ãã£ãã¯ã¹ã ãšèšããŸãã ããã°ã©ããŒã¯éåžžãæç»ã®æ¹æ³ãç¥ããïŒäŸå€ã¯ãããŸãïŒãã¢ãŒãã£ã¹ãã¯éåžžââãããã°ã©ãã³ã°ã®æ¹æ³ãç¥ããŸããïŒäŸå€ã¯ãããŸãïŒã ãããŠãã°ã©ãã£ãã¯ããªããã°ãããªãã¯èªããªããã°ãªããŸããããŸããªã²ãŒã ã¯ãã€ãã¹ãããŸãã ã©ãããïŒ
ãªãã·ã§ã³ããããŸãïŒ
1.ã·ã³ãã«ãªã°ã©ãã£ã«ã«ãšãã£ã¿ã§ãã¹ãŠãèªåã§æã
2.ãã¹ãŠãèªåã§ãã¯ãã«ã«æã
3.æãæ¹ãããããªãå
åŒã«å°ããïŒããããå°ãè¯ãããïŒ
4. 3Dã¢ããªã³ã°çšã®ããã°ã©ã ãããŠã³ããŒãããŠãããããã¢ã»ãããååŸããŸã
5.絶æçã«ãé ã®æ¯ãåŒãè£ã
6.èªåã§ãã¹ãŠãæ¬äŒŒã°ã©ãã£ãã¯ã¹ïŒASCIIïŒã§æç»ããŸã
åŸè ã«ã€ããŠè©³ããèŠãŠãããŸãããïŒäžéšã¯ä»ã®äººã»ã©æ°ã®ãããããã«èŠããªãããã§ãïŒã çµéšã®æµ ãã²ãŒããŒã®å€ãã¯ãã¯ãŒã«ã§ã¢ãã³ãªã°ã©ãã£ãã¯ã®ãªãã²ãŒã ã¯ãã¬ã€ã€ãŒã®å¿ãã€ããããšãã§ããªããšèããŠããŸããã²ãŒã ã®ååã§ããã²ãŒã ã«å€ããããšããã§ããŸããã ADOM ã NetHack ã Dwarf Fortressãªã©ã®åäœã®éçºè ã¯ããã®ãããªè°è«ã«æé»çã«å察ããŠããŸãã å€èŠ³ã¯åžžã«æ±ºå®çãªèŠå ã§ã¯ãããŸãããASCIIã䜿çšãããšãããã€ãã®èå³æ·±ãå©ç¹ãåŸãããŸãã
- éçºã®éçšã§ãããã°ã©ããŒã¯ã²ãŒã ãã¬ã€ãã²ãŒã ã®ä»çµã¿ãããããã³ã³ããŒãã³ããªã©ã«çŠç¹ãåœãŠãäºçŽ°ãªããšã«æ°ãæ£ããããšãªã;
- ã°ã©ãã£ãã¯ã³ã³ããŒãã³ãã®éçºã«ã¯ããŸãæéãããããŸãããåäœãããããã¿ã€ãïŒã€ãŸããç解ã§ãããã¬ã€ããããŒãžã§ã³ã§ãããç¶ãã䟡å€ã¯ãããŸãïŒã¯ãã¯ããã«æ©ãæºåã§ããŸãã
- ãã¬ãŒã ã¯ãŒã¯ãšã°ã©ãã£ãã¯ãšã³ãžã³ãåŠã¶å¿ èŠã¯ãããŸããã
- ã²ãŒã ãéçºãã5幎éã§ã°ã©ãã£ãã¯ã¹ãé³è åããããšã¯ãããŸããã
- çéå ¥ãã®åŽåè ã¯ãã°ã©ãã£ã«ã«ç°å¢ãæããªããã©ãããã©ãŒã äžã§ã補åãè©äŸ¡ã§ããŸãã
- ãã¹ãŠãæ£ããè¡ãããŠããã°ãã¯ãŒã«ãªã°ã©ãã£ãã¯ãåŸã§åºå®ã§ããŸãã
äžèšã®é·ã玹ä»ã¯ãåå¿è ã®ã€ã°ãããããææãåèŠãå æããå¿é ããããããã§ããã®ãããªããšãããããšããã®ãå©ããããšãæå³ããŸããã æºåã¯ããïŒ ããã§ã¯å§ããŸãããã
æåã®ã¹ãããã ã¢ã€ãã¢
ã©ããã£ãŠïŒ ãŸã ããããªãïŒ
ã³ã³ãã¥ãŒã¿ãŒã®é»æºãåããé£ã¹ã«è¡ããæ£æ©ããéåããŸãã ãŸãã¯ææªã®å Žåãç ããŸãã ã²ãŒã ãæãä»ããšããããšã¯ãçªãæŽãããšã§ã¯ãããŸãã-ããã»ã¹ã®æŽå¯ã¯åŸãããŸããã éåžžãã²ãŒã ã®ã¢ã€ãã¢ã¯ããŸã£ããèããªããšãã«çªç¶ãäºæããã«çãŸããŸãã ãããçªç¶èµ·ãã£ãå Žåã¯ãéçãããéãã€ãã¿ãã¢ã€ãã¢ãæ¶ãããŸã§æžãçããŠãã ããã ã¯ãªãšã€ãã£ãããã»ã¹ã¯ãã¹ãŠãã®æ¹æ³ã§å®è£ ãããŸãã
ãããŠãä»ã®äººã®ã²ãŒã ãã³ããŒã§ããŸãã ãŸããã³ããŒã ãã¡ãããããªããã©ãã»ã©é ãããããé ã ãŸã§èšã£ãŠäžæ¬ã«æŠãã®ã§ã¯ãªããããªãã®è£œåã§ä»ã®äººã®éçºã䜿ã£ãŠãã ããã å€ãã®å Žåãã²ãŒããŒã¯ãããæã£ãŠããã®ã§ããããããªãã®å€¢ããå ·äœçã«ã©ã®ãããæ®ã£ãŠããã®ã§ããããïŒåœŒãã¯ããã€ãã®è¿·æãªããšãé€ããŠã²ãŒã ã®ãã¹ãŠã奜ãã§ããããããç°ãªã£ãŠè¡ãããå Žå...誰ãç¥ã£ãŠããŸã誰ãã®è¯ãã¢ã€ãã¢ãæãæµ®ãã¹ãã®ã¯ããªãã®å€¢ã§ãããã
ããããç§ãã¡ã¯ç°¡åãªæ¹æ³ã§é²ã¿ãŸã-ç§ãã¡ã¯ãã§ã«ã¢ã€ãã¢ãæã£ãŠãããšä»®å®ããç§ãã¡ã¯é·ãéããã«ã€ããŠèããŠããŸããã§ããã æåã®å£®å€§ãªãããžã§ã¯ããšããŠãObsidian- Pathfinder Adventuresã®åªããã²ãŒã ã®ã¯ããŒã³ãäœæããŸãã
ãããã¯äžäœäœã ïŒ ããŒãã«ã¯ãããŸããïŒã
圌ããèšãããã«ã ããŒã«ã³ã€ãã¹ïŒ ç§ãã¡ã¯ãã§ã«åèŠãæŸæ£ããŠããããã«èŠããã®ã§ãç§ãã¡ã¯å€§èã«ã¢ã€ãã¢ãæŽç·Žãå§ããŸãã åœç¶ãã²ãŒã ã1察1ã§ã¯ããŒã³ããããšã¯ãããŸããããåºæ¬çãªã¡ã«ããºã ã¯åçšããŸãã ããã«ãã¿ãŒã³ããŒã¹ã®å調åããŒãã²ãŒã ã®å®è£ ã«ã¯æ¬¡ã®å©ç¹ããããŸãã
- ããã¯æ®µéçã§ã-ããã«ãããã¿ã€ããŒãåæãæé©åãFPSããã®ä»ã®éå±ãªããšãå¿é ããå¿ èŠããªããªããŸãã
- ååçã§ããã€ãŸãããã¬ã€ã€ãŒå士ã¯ç«¶äºããŸãããã決å®è«çãªã«ãŒã«ã«åŸã£ãŠç¹å®ã®ãç°å¢ããšå¯ŸæŠããŸããããã«ãããã²ãŒã éçºã®æãé£ãã段éã®1ã€ã§ããAI ïŒ AI ïŒãããã°ã©ã ããå¿ èŠããªããªããŸãã
- ããã¯æå³ããããŸã-ããŒãã«ãããã¯äžè¬ã«æ°ãŸãããªäººã ã§ããã圌ãã¯äœãå·ã€ããŸããïŒåœŒãã«ææ ®æ·±ãã¡ã«ããºã ãšé¢çœãã²ãŒã ãã¬ã€ãäžããŸã-ããªãã¯1ã€ã®çŸããåçã«åºãããŸããïŒããã¯ããã€ãã®èº«è¿ãªãã®ã«äœããäžããŸããïŒïŒ
- å€ãã®eã¹ããŒããã³ã¯åæããŸããããå人çã«ã¯ã²ãŒã ã¯é¢çœãç©èªãèªãã¹ãã§ã-æ¬ã®ããã«ãç¹å¥ãªèžè¡çæ段ã䜿çšããã ãã§ãã
- 圌女ã¯é¢çœãã誰ãã楜ãããããã§ã¯ãããŸããã説æããã¢ãããŒãã¯ãããªããäœäººããããšãããã®åŸã®å€¢ã«é©çšã§ããŸãã
ã«ãŒã«ã«æ
£ããŠããªã人ã®ããã«ãç°¡åãªçŽ¹ä»ïŒ
Pathfinder Adventuresã¯ãããŒãããŒã«ãã¬ã€ã³ã°ã²ãŒã ïŒãŸãã¯ããŒã«ãã¬ã€ã³ã°ã·ã¹ãã å
šäœïŒPathfinderã«åºã¥ããŠäœæãããããŒãã«ãŒãã²ãŒã ã®ããžã¿ã«ããŒãžã§ã³ã§ãã ãã¬ã€ã€ãŒïŒ1ã6人ïŒã¯èªåã§ãã£ã©ã¯ã¿ãŒãéžæãã圌ãšäžç·ã«ããã€ãã®ã·ããªãªã«åãããŠåéºã«åºãŸãã åãã£ã©ã¯ã¿ãŒã¯ãããŸããŸãªã¿ã€ãã®ã«ãŒãïŒæŠåšãé§ãåªæãå³æ¹ãã¢ã€ãã ãªã©ïŒãèªç±ã«äœ¿çšã§ããŸããåã·ããªãªã§ã圌ã¯ãã®å©ããåããŠãæªå
-ç¹å¥ãªç¹æ§ãæã€ç¹å¥ãªã«ãŒããèŠã€ããŠé
·ã眰ããªããã°ãªããŸããã
åã·ããªãªã¯ããã¬ã€ã€ãŒã蚪åããŠæ¢çŽ¢ããå¿ èŠãããå ŽæãŸãã¯å Žæã®æ°ïŒãããã®æ°ã¯ãã¬ã€ã€ãŒã®æ°ã«ãã£ãŠç°ãªããŸãïŒãæäŸããŸãã åå Žæã«ã¯è£åãã«ãªã£ãŠããã«ãŒãã®ããããå«ãŸããŠããããã£ã©ã¯ã¿ãŒã¯é çªã«æ¢çŽ¢ããŸããã€ãŸãããããã«ãŒããéããé¢é£ããã«ãŒã«ã«åŸã£ãŠãããå æããããšããŸãã ãã¬ã€ã€ãŒã®ãããã«è£å ããç¡å®³ãªã«ãŒãã«å ããŠããããã®ãããã«ã¯éªæªãªæµãé害ç©ãå«ãŸããŠããŸããããã«åé²ããã«ã¯ãããããåãå¿ èŠããããŸãã Scoundrelã«ãŒãããããã®1ã€ã«ãããŸããããã¬ã€ã€ãŒã¯ã©ã®ã«ãŒãããç¥ããŸãã-ãããèŠã€ããå¿ èŠããããŸãã
ã«ãŒããæã¡è² ããïŒããã³æ°ããã«ãŒããç²åŸããïŒããã«ããã£ã©ã¯ã¿ãŒã¯ããµã€ãºã察å¿ããç¹æ§ã®å€ïŒd4ããd12ïŒã«ãã£ãŠæ±ºå®ããããã€ã¹ãæããŠãç¹æ§ïŒRPGã®åŒ·ããåšçšããç¥æµãªã©ã®æšæºïŒã®ãã¹ãã«åæ Œããå¿ èŠããããŸãã«ãŒã«ãšãã£ã©ã¯ã¿ãŒéçºã®ã¬ãã«ïŒããããŠæããã®é©åãªã«ãŒãã®å¹æãé«ããããã«ãã¬ã€ããŸãã åå©ãããšãåºäŒã£ãã«ãŒãã¯ã²ãŒã ããåãé€ããããïŒæµã®å ŽåïŒããã¬ã€ã€ãŒã®ææãè£å ãïŒã¢ã€ãã ã®å ŽåïŒã移åã¯å¥ã®ãã¬ã€ã€ãŒã«ç§»åããŸãã è² ãããšããã£ã©ã¯ã¿ãŒã¯ãã°ãã°ãã¡ãŒãžãåããæããã«ãŒããæšãŠãããŸãã èå³æ·±ãã¡ã«ããºã ã¯ããããã®ã«ãŒãã®æ°ã«ãã£ãŠãã£ã©ã¯ã¿ãŒã®äœåã決ãŸããšããããšã§ãããã¬ã€ã€ãŒããããããã«ãŒããåŒãå¿ èŠããããšããã«ããã£ã©ã¯ã¿ãŒãããã«ããªããã°ããã£ã©ã¯ã¿ãŒã¯æ»ã«ãŸãã
ç®æšã¯ããã±ãŒã·ã§ã³ããããéãæããŠãScoundrelãèŠã€ããŠåããéåŽãžã®éã以åãããã¯ããŠããããšã§ãïŒããã«ã€ããŠã¯ãã«ãŒã«ãèªãããšã§ããã«åŠã¶ããšãã§ããŸãïŒã ãã ããããããã°ããè¡ãå¿ èŠãããããããã²ãŒã ã®äž»ãªé£ç¹ã§ãã æã®æ°ã¯å³å¯ã«å¶éãããŠãããå©çšå¯èœãªãã¹ãŠã®ã«ãŒãã®åçŽãªåæã§ã¯ç®æšã«éããŸããã ããŸããŸãªããªãã¯ãšã¹ããŒããªãã¯ããã¯ãé©çšããå¿ èŠãããããã§ãã
ã·ããªãªãæºãããããšããã£ã©ã¯ã¿ãŒã¯æé·ããŠçºéãããã£ã©ã¯ã¿ãŒã®ç¹æ§ãåäžããæ°ããæçšãªã¹ãã«ãç²åŸããŸãã ãããã®ç®¡çãã²ãŒã ã®éåžžã«éèŠãªèŠçŽ ã§ããã·ããªãªã®çµæïŒç¹ã«åŸã®æ®µéïŒã¯ãéåžžãé©åãªã«ãŒãã«äŸåããŸãïŒãããŠã幞éã«å€§ããäŸåããŸããããµã€ã³ãã䜿ã£ãã²ãŒã ã«ã¯äœãå¿ èŠã§ããïŒïŒã
åã·ããªãªã¯ããã¬ã€ã€ãŒã蚪åããŠæ¢çŽ¢ããå¿ èŠãããå ŽæãŸãã¯å Žæã®æ°ïŒãããã®æ°ã¯ãã¬ã€ã€ãŒã®æ°ã«ãã£ãŠç°ãªããŸãïŒãæäŸããŸãã åå Žæã«ã¯è£åãã«ãªã£ãŠããã«ãŒãã®ããããå«ãŸããŠããããã£ã©ã¯ã¿ãŒã¯é çªã«æ¢çŽ¢ããŸããã€ãŸãããããã«ãŒããéããé¢é£ããã«ãŒã«ã«åŸã£ãŠãããå æããããšããŸãã ãã¬ã€ã€ãŒã®ãããã«è£å ããç¡å®³ãªã«ãŒãã«å ããŠããããã®ãããã«ã¯éªæªãªæµãé害ç©ãå«ãŸããŠããŸããããã«åé²ããã«ã¯ãããããåãå¿ èŠããããŸãã Scoundrelã«ãŒãããããã®1ã€ã«ãããŸããããã¬ã€ã€ãŒã¯ã©ã®ã«ãŒãããç¥ããŸãã-ãããèŠã€ããå¿ èŠããããŸãã
ã«ãŒããæã¡è² ããïŒããã³æ°ããã«ãŒããç²åŸããïŒããã«ããã£ã©ã¯ã¿ãŒã¯ããµã€ãºã察å¿ããç¹æ§ã®å€ïŒd4ããd12ïŒã«ãã£ãŠæ±ºå®ããããã€ã¹ãæããŠãç¹æ§ïŒRPGã®åŒ·ããåšçšããç¥æµãªã©ã®æšæºïŒã®ãã¹ãã«åæ Œããå¿ èŠããããŸãã«ãŒã«ãšãã£ã©ã¯ã¿ãŒéçºã®ã¬ãã«ïŒããããŠæããã®é©åãªã«ãŒãã®å¹æãé«ããããã«ãã¬ã€ããŸãã åå©ãããšãåºäŒã£ãã«ãŒãã¯ã²ãŒã ããåãé€ããããïŒæµã®å ŽåïŒããã¬ã€ã€ãŒã®ææãè£å ãïŒã¢ã€ãã ã®å ŽåïŒã移åã¯å¥ã®ãã¬ã€ã€ãŒã«ç§»åããŸãã è² ãããšããã£ã©ã¯ã¿ãŒã¯ãã°ãã°ãã¡ãŒãžãåããæããã«ãŒããæšãŠãããŸãã èå³æ·±ãã¡ã«ããºã ã¯ããããã®ã«ãŒãã®æ°ã«ãã£ãŠãã£ã©ã¯ã¿ãŒã®äœåã決ãŸããšããããšã§ãããã¬ã€ã€ãŒããããããã«ãŒããåŒãå¿ èŠããããšããã«ããã£ã©ã¯ã¿ãŒãããã«ããªããã°ããã£ã©ã¯ã¿ãŒã¯æ»ã«ãŸãã
ç®æšã¯ããã±ãŒã·ã§ã³ããããéãæããŠãScoundrelãèŠã€ããŠåããéåŽãžã®éã以åãããã¯ããŠããããšã§ãïŒããã«ã€ããŠã¯ãã«ãŒã«ãèªãããšã§ããã«åŠã¶ããšãã§ããŸãïŒã ãã ããããããã°ããè¡ãå¿ èŠãããããããã²ãŒã ã®äž»ãªé£ç¹ã§ãã æã®æ°ã¯å³å¯ã«å¶éãããŠãããå©çšå¯èœãªãã¹ãŠã®ã«ãŒãã®åçŽãªåæã§ã¯ç®æšã«éããŸããã ããŸããŸãªããªãã¯ãšã¹ããŒããªãã¯ããã¯ãé©çšããå¿ èŠãããããã§ãã
ã·ããªãªãæºãããããšããã£ã©ã¯ã¿ãŒã¯æé·ããŠçºéãããã£ã©ã¯ã¿ãŒã®ç¹æ§ãåäžããæ°ããæçšãªã¹ãã«ãç²åŸããŸãã ãããã®ç®¡çãã²ãŒã ã®éåžžã«éèŠãªèŠçŽ ã§ããã·ããªãªã®çµæïŒç¹ã«åŸã®æ®µéïŒã¯ãéåžžãé©åãªã«ãŒãã«äŸåããŸãïŒãããŠã幞éã«å€§ããäŸåããŸããããµã€ã³ãã䜿ã£ãã²ãŒã ã«ã¯äœãå¿ èŠã§ããïŒïŒã
äžè¬çã«ãã²ãŒã ã¯èå³æ·±ãã䟡å€ãããã泚ç®ã«å€ããŸãããããŠãç§ãã¡ã«ãšã£ãŠéèŠãªããšã¯ãã¯ããŒã³ãå®è£ ããããšãé¢çœãããããã«éåžžã«è€éã§ãïŒãé£ããããšã¯ãé£ããããšããæå³ã§ã¯ãããŸããïŒã
ãã®ã±ãŒã¹ã§ã¯ã1ã€ã®ã°ããŒãã«ãªæŠå¿µå€æŽãè¡ããŸã-ã«ãŒããæŸæ£ããŸãã ããããç§ãã¡ã¯ãŸã£ããæåŠããŸãããããµã€ãºãšè²ãç°ãªãç«æ¹äœã«ã«ãŒãã眮ãæããŸãïŒæè¡çã«ã¯ãæ£ããå è§åœ¢ä»¥å€ã®åœ¢ç¶ãããããããç«æ¹äœãã䜿çšããããšã¯ãŸã£ããæ£ãããããŸããããã骚ããšåŒã¶ã®ã¯çããã§ãïŒããã¯äžå¿«ã§ãããã¢ã¡ãªã«ã®ãã€ãžãŒã䜿çšããããšã¯å³ã®ãããã®å åãªã®ã§ããã®ãŸãŸã«ããŠãããŸãããïŒã ä»ããããã®ä»£ããã«ããã¬ã€ã€ãŒã¯ããã°ãæã£ãŠããŸãã ãŸããå Žæã«ã¯ããã°ããããããããç 究ããã»ã¹ã®ãã¬ã€ã€ãŒãä»»æã®ãã¥ãŒããåŒãåºããŸãã ãã¥ãŒãã®è²ã«ãã£ãŠããã¥ãŒãã®ã¿ã€ãã決ãŸããããã«å¿ããŠããã¹ãã«åæ Œããããã®ã«ãŒã«ã決ãŸããŸãã ãã®çµæããã£ã©ã¯ã¿ãŒã®å人çãªç¹æ§ïŒåŒ·ããåšçšããªã©ïŒã¯ãªããªããŸãããæ°ããèå³æ·±ãã¡ã«ããºã ã衚瀺ãããŸãïŒè©³çŽ°ã¯åŸã»ã©ïŒã
éã¶ã®ã¯æ¥œããã§ããïŒ ç§ã«ã¯èŠåœãã€ãããå®éã®ãããã¿ã€ããå®æãããŸã§èª°ããããç解ã§ããŸããã ããããã²ãŒã ã楜ããã®ã§ã¯ãªããéçºã楜ãã¿ãŸããïŒ ãããã£ãŠãæåã®çãã¯ãªãã¯ãã§ãã
ã¹ããã2 èšèš
ã¢ã€ãã¢ãæã€ããšã¯ãç©èªã®3åã®1ã«ãããŸããã ä»ããã®èããçºå±ãããããšãéèŠã§ãã ã€ãŸããå ¬åãæ£æ©ããããã¹ããŒã ãã¹ã济ã³ããããã®ã§ã¯ãªããããŒãã«ã«åº§ã£ãŠãã³ã§çŽãåãïŒãŸãã¯ãæ°ã«å ¥ãã®ããã¹ããšãã£ã¿ãŒãéãïŒããã¶ã€ã³ããã¥ã¡ã³ããæ éã«äœæããã²ãŒã ã¡ã«ãã¯ã¹ã®ããããåŽé¢ãå ¥å¿µã«äœæããŸãã ããã«ã¯æéãããããŸãã®ã§ãäžæ°ã«æç« ãå®æãããããšãæåŸ ããªãã§ãã ããã ãããŠããã¹ãŠãäžåºŠã«ãã¹ãŠæ€èšããããšããæãã§ããŸãã-å®è£ ããã«ã€ããŠãå€ãã®å€æŽãšå€æŽãè¡ãå¿ èŠãããããšãããããŸãïŒãããŠãæã«ã¯ã°ããŒãã«ã«äœããããçŽããŸãïŒããéçºããã»ã¹ãå§ãŸãåã«äœããã®åºç€ãååšããªããã°ãªããŸããã
ãããŠã壮倧ãªã¢ã€ãã¢ã®æåã®æ³¢ã«å¯ŸåŠããåŸã«åããŠãé ãåããããã¥ã¡ã³ãã®æ§é ã決å®ãããããã³ã³ãã³ãã§æŽç¶ãšæºããå§ããŸãïŒäžèŠãªç¹°ãè¿ããç¹ã«ççŸãé¿ããããã«ããã§ã«æžãããå 容ã§æ¯ç§ããã§ãã¯ããŸãïŒã 次第ã«ã次ã®ããã«æå³ã®ããç°¡æœãªãã®ãåŸãããŸãã
èšèšã説æãããšãã¯ãç¹ã«äžäººã§äœæ¥ããå Žåã¯ãèããè¡šçŸããããèšèªãéžæããŠãã ããã ãããžã§ã¯ãã«ãµãŒãããŒãã£ã®éçºè ãåå ãããå¿ èŠãããå Žåã¯ãããªãã®é ã®äžã§èµ·ãã£ãŠãããã¹ãŠã®åµé çãªãã³ã»ã³ã¹ã圌ããç解ããŠããããšã確èªããŠãã ããã
ç¶è¡ããã«ã¯ãåŒçšãããããã¥ã¡ã³ããå°ãªããšãæãã«èªãããšã匷ããå§ãããŸããå°æ¥çã«ã¯ããã®è§£éã«ã€ããŠè©³ãã説æããããšãªããããã«æ瀺ãããŠããçšèªãšæŠå¿µãåç §ããããã§ãã
ãèè ãå£ã«åãã£ãŠèªæ®ºããŠãã ããã æåãå€ãããŸããã
ã¹ããã3 ã¢ããªã³ã°
ã€ãŸãããã¹ãŠåããã¶ã€ã³ã§ããã詳现ãªãã®ã§ãã
å€ãã®äººãIDEãéããŠã³ãŒãã£ã³ã°ãå§ããããšæã£ãŠããŸãããããå°ãå¿è匷ãåŸ ã£ãŠãã ããã ã¢ã€ãã¢ãé ãå§åãããšãããŒããŒãã«è§Šããã ãã§æã空ã®é«ãã®è·é¢ã«çªé²ããããã«æããŸã-ã¹ããŒãã§ã³ãŒããŒã沞隰ããåã«ãã¢ããªã±ãŒã·ã§ã³ã®äœæ¥ããŒãžã§ã³ã¯ãŽãç®±ã«è¡ãæºåãã§ããŠããŸã... åãããšãäœåºŠãæžãçŽããªãããã«ïŒç¹ã«ã3æéã®éçºåŸã«ã¬ã€ã¢ãŠããæ©èœãããæ°ãã«éå§ããå¿ èŠãããããšã確èªããªãããã«ïŒãæåã«ã¢ããªã±ãŒã·ã§ã³ã®äž»èŠæ§é ãæ€èšïŒããã³ææžåïŒããããšããå§ãããŸãã
éçºè ãšããŠããªããžã§ã¯ãæåããã°ã©ãã³ã°ïŒOOPïŒã«ç²ŸéããŠããã®ã§ããããžã§ã¯ãã§ãã®ååã䜿çšããŸãã ããããOOPã®å Žåãéå±ãªUMLãã€ã¢ã°ã©ã ã®æããéçºãéå§ããããšã»ã©æåŸ ãããŠããŸããã ïŒ UMLãäœãªã®ãããããŸãããïŒç§ãå¿ãããã«ãªããŸããããåãã§æãåºããŸã-ç§ãå€åãªããã°ã©ããŒã§ããããšã瀺ãããã§ãã
ãŠãŒã¹ã±ãŒã¹å³ããå§ããŸãããã ãŠãŒã¶ãŒïŒãã¬ãŒã€ãŒïŒãå°æ¥ã®ã·ã¹ãã ãšããåãããæ¹æ³ã説æããŸãã
ããããš...ããã¯äœã®ããšã§ããïŒã
åè«ã§ããåè«ã§ã...ãããŠããããããç§ã¯ããã«ã€ããŠåè«ãèšãã®ããããŸã-ããã¯æ·±å»ãªåé¡ã§ãïŒçµå±ã倢ã§ãïŒã ãŠãŒã¹ã±ãŒã¹ã®å³ã§ã¯ãã·ã¹ãã ããŠãŒã¶ãŒã«æäŸããå¯èœæ§ã衚瀺ããå¿ èŠããããŸãã 詳现ã«ã ãããããã®ç¹å®ã®ã¿ã€ãã®ãã€ã¢ã°ã©ã ãç§ã«ãšã£ãŠææªã§ããããšãå€æããã®ã¯æŽå²çã«èµ·ãããŸãã-æããã«ååãªå¿èããããŸããã ãããŠãããªãã¯ãã®ããã«ç§ãèŠãå¿ èŠã¯ãããŸãã-ç§ãã¡ã¯å€§åŠã§åæ¥èšŒæžãä¿è·ããŠããŸããããç§ãã¡ã¯ä»äºã®ããã»ã¹ã楜ããã§ããŸãã ãã®ããã»ã¹ã§ã¯ããŠãŒã¹ã±ãŒã¹ã¯ããã»ã©éèŠã§ã¯ãããŸããã ã¢ããªã±ãŒã·ã§ã³ãç¬ç«ããã¢ãžã¥ãŒã«ã«æ£ããåå²ããããšãã€ãŸããããžã¥ã¢ã«ã€ã³ã¿ãŒãã§ã€ã¹ã®æ©èœãã²ãŒã ã¡ã«ãã¯ã¹ã«åœ±é¿ãäžããªãããã«ã²ãŒã ãå®è£ ããå¿ èŠã«å¿ããŠã°ã©ãã£ãã¯ã³ã³ããŒãã³ããç°¡åã«å€æŽã§ããããã«ããããšãã¯ããã«éèŠã§ãã
ãã®ç¹ã¯ã次ã®ã³ã³ããŒãã³ãå³ã§è©³ãã説æã§ããŸãã
ããã§ãã¢ããªã±ãŒã·ã§ã³ã®äžéšã§ããç¹å®ã®ãµãã·ã¹ãã ããã§ã«ç¹å®ããŸãããåŸã§ç€ºãããã«ããããã¯ãã¹ãŠäºãã«ç¬ç«ããŠéçºãããŸãã
ãŸããåã段éã§ãã²ãŒã ã®ã¡ã€ã³ãµã€ã¯ã«ãã©ã®ããã«èŠããããæšå®ããŸãïŒãŸãã¯ãæãèå³æ·±ãéšåã¯ãã¹ã¯ãªããå ã®ãã£ã©ã¯ã¿ãŒãå®è£ ããéšåã§ãïŒã ããã«ã¯ãã¢ã¯ãã£ããã£å³ãé©ããŠããŸãã
ãããŠæåŸã«ãå ¥åºåã·ã¹ãã ãä»ãããšã³ããŠãŒã¶ãŒãšã²ãŒã ãšã³ãžã³ãšã®çžäºäœçšã®ã·ãŒã±ã³ã¹ãäžè¬çãªçšèªã§ç€ºããšããã§ãããã
å€ã¯é·ããå€æãã®ãã£ãšåã§ãã ããŒãã«ã«åº§ã£ãåŸãä»ã®20åã®å³ãèœã¡çããŠæããŸããå°æ¥çã«ã¯ãããããååšããããšã§ãéžæããéãæ©ã¿ãèªå°å¿ãé«ããéšå±ã®ã€ã³ããªã¢ãæŽæ°ããè²ãããå£çŽã§è²ãããå£çŽãåãããç°¡åã«ããªãã®ããžã§ã³ãäŒããããšãã§ããŸãããã«ããªãã®æ°ããã¹ã¿ãžãªã®ãã¢ã«å€§æ¥ãã§é§ãã€ãã仲éã®éçºè ïŒç§ãã¡ã¯æåãç®æããŠããŸãããèŠããŠããŸããïŒïŒ
ãããŸã§ã®ãšããã誰ããæããã¯ã©ã¹å³ïŒã¯ã©ã¹ïŒãåŒçšããã€ããã¯ãããŸãã-å€ãã®ã¯ã©ã¹ã
ã¹ããã4 ããŒã«éžæ
ãã§ã«åæããããã«ãããŸããŸãªãªãã¬ãŒãã£ã³ã°ã·ã¹ãã ãå®è¡ãããã¹ã¯ããããšã¢ãã€ã«ããã€ã¹ã®äž¡æ¹ã§å®è¡ãããã¯ãã¹ãã©ãããã©ãŒã ã¢ããªã±ãŒã·ã§ã³ãéçºããŸããããã°ã©ãã³ã°èšèªãšããŠJavaãéžæããŸããåŸè ã¯ããæ°ããæ°é®®ãªãããKotlinã¯ããã«åªããŠãããåä»»è ãå§åããinãã®æ³¢ã§æ³³ãæéã¯ãŸã ãããŸããïŒåæã«ã誰ãããããææããŠããªãå Žåã¯ãã¬ãŒãã³ã°ããŸãïŒãJVMã¯ãåãã®ãšãããã©ãã§ãå©çšã§ããŸãïŒ30åå°ã®ããã€ã¹ã§ïŒãWindowsãšUNIXã®äž¡æ¹ããµããŒãããSSHæ¥ç¶ãä»ãããªã¢ãŒããµãŒããŒäžã§ãåçã§ããŸãïŒå¿ èŠãªå Žåã¯äžæã§ããããã®ãããªæ©äŒãæäŸããŸãïŒããŸããéæã¡ã«ãªã£ãŠã¢ãŒãã£ã¹ããéããšãã«Androidã«è»¢éããŸãããããã«ã€ããŠã¯åŸã§è©³ãã説æããŸãã
ã©ã€ãã©ãªïŒã©ã€ãã©ãªãªãã§ã¯ã©ãã«ãã¢ã¯ã»ã¹ã§ããŸããïŒã¯ãã¹ãã©ãããã©ãŒã ã®èŠä»¶ã«å¿ããŠéžæããŸããMavenããã«ãã·ã¹ãã ãšããŠäœ¿çšããŸãããŸãã¯GradleããããšããMavenãããããå§ããŸããããããã«ãããŒãžã§ã³ç®¡çã·ã¹ãã ïŒå¥œããªãã®ïŒãã»ããã¢ããããããšããå§ãããŸããããããããšã§ãäœå¹Žãçµã£ãåŸãããããã€ãŠã©ãã»ã©çŽ æŽãããã£ãããæãããæ°æã¡ã§æãåºãããããªããŸããIDEã¯ã䜿ãæ £ããããæ°ã«å ¥ãã®ã䟿å©ãªãã®ãéžæããŸãã
å®éãä»ã«äœãå¿ èŠãããŸãããéçºãéå§ã§ããŸãã
ã¹ããã5 ãããžã§ã¯ãã®äœæãšèšå®
IDEã䜿çšããå Žåããããžã§ã¯ãã®äœæã¯ç°¡åã§ãã ç§ãã¡ã®å°æ¥ã®åäœã®ããã«ããã€ãã®éªæªãªååïŒããšãã°ã Dice ïŒãéžæããå¿ èŠããããèšå®ã§MavenãµããŒããæå¹ã«ããããšãå¿ããã«ã
pom.xml
ãã¡ã€ã«ã«å¿ èŠãªèå¥åãæžã蟌ã¿ãŸãã
<modelVersion>4.0.0</modelVersion> <groupId>my.company</groupId> <artifactId>dice</artifactId> <version>1.0</version> <packaging>jar</packaging>
ãŸããããã©ã«ãã§ã¯ååšããªãKotlinãµããŒããè¿œå ããŸãã
<dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-stdlib</artifactId> <version>${kotlin.version}</version> </dependency>
ãããŠãç§ãã¡ã詳ãã説æããªãããã€ãã®èšå®ïŒ
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <kotlin.version>1.3.20</kotlin.version> <kotlin.compiler.incremental>true</kotlin.compiler.incremental> </properties>
ãã€ããªãããããžã§ã¯ãã«é¢ããå°ãã®æ
å ±
ãããžã§ã¯ãã§JavaãšKotlinã®äž¡æ¹ã䜿çšããå Žåã¯ã
ã«å ããŠã
ãã©ã«ããŒããããŸãã Kotlinéçºè ã¯ãæåã®ãã©ã«ããŒïŒ
ïŒã®ãœãŒã¹ãã¡ã€ã«ã¯2çªç®ã®ãã©ã«ããŒïŒ
ïŒã®ãœãŒã¹ãã¡ã€ã«ãããæ©ãã³ã³ãã€ã«ããå¿ èŠããããšäž»åŒµããŠãããããæšæºã®Mavenã¿ãŒã²ããã®èšå®ãå€æŽããããšã匷ããå§ãããŸãã
ãããã©ãã»ã©éèŠãã¯èšããŸããããã®ã·ãŒãããªããã°ãããžã§ã¯ãã¯é 調ã«é²ãã§ããŸãã ãã ãã念ã®ãããèŠåã衚瀺ãããŸãã
src/main/kotlin
ã«å ããŠã
src/main/java
ãã©ã«ããŒããããŸãã Kotlinéçºè ã¯ãæåã®ãã©ã«ããŒïŒ
*.kt
ïŒã®ãœãŒã¹ãã¡ã€ã«ã¯2çªç®ã®ãã©ã«ããŒïŒ
*.java
ïŒã®ãœãŒã¹ãã¡ã€ã«ãããæ©ãã³ã³ãã€ã«ããå¿ èŠããããšäž»åŒµããŠãããããæšæºã®Mavenã¿ãŒã²ããã®èšå®ãå€æŽããããšã匷ããå§ãããŸãã
<build> <plugins> <plugin> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-maven-plugin</artifactId> <version>${kotlin.version}</version> <executions> <execution> <id>compile</id> <phase>process-sources</phase> <goals> <goal>compile</goal> </goals> <configuration> <sourceDirs> <sourceDir>${project.basedir}/src/main/kotlin</sourceDir> <sourceDir>${project.basedir}/src/main/java</sourceDir> </sourceDirs> </configuration> </execution> <execution> <id>test-compile</id> <goals> <goal>test-compile</goal> </goals> <configuration> <sourceDirs> <sourceDir>${project.basedir}/src/test/kotlin</sourceDir> <sourceDir>${project.basedir}/src/test/java</sourceDir> </sourceDirs> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.5.1</version> <executions> <!-- Replacing default-compile --> <execution> <id>default-compile</id> <phase>none</phase> </execution> <!-- Replacing default-testCompile --> <execution> <id>default-testCompile</id> <phase>none</phase> </execution> <execution> <id>java-compile</id> <phase>compile</phase> <goals> <goal>compile</goal> </goals> </execution> <execution> <id>java-test-compile</id> <phase>test-compile</phase> <goals> <goal>testCompile</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
ãããã©ãã»ã©éèŠãã¯èšããŸããããã®ã·ãŒãããªããã°ãããžã§ã¯ãã¯é 調ã«é²ãã§ããŸãã ãã ãã念ã®ãããèŠåã衚瀺ãããŸãã
äžåºŠã«3ã€ã®ããã±ãŒãžãäœæããŠã¿ãŸãããïŒãªãäœãè©ŠããŠã¿ãŸãããïŒïŒïŒ
-
model
-ã²ãŒã ã¯ãŒã«ãã®ãªããžã§ã¯ããèšè¿°ããã¯ã©ã¹ã - game-ã²ãŒã ãã¬ã€ãå®è£ ããã¯ã©ã¹çšã
-
ui
ãŠãŒã¶ãŒå¯Ÿè©±ãæ åœããã¯ã©ã¹çšã
åŸè ã«ã¯ã€ã³ã¿ãŒãã§ãŒã¹ã®ã¿ãå«ãŸãããã®ã¡ãœããã¯ããŒã¿ã®å ¥åºåã«äœ¿çšããŸãã äžè¬ã«ç¹å®ã®å®è£ ãå¥ã®ãããžã§ã¯ãã«ä¿åããŸãããããã«ã€ããŠã¯åŸã§è©³ãã説æããŸãã ãããŸã§ã¯ãã¹ãã¬ãŒããããªãããã«ããããã®ã¯ã©ã¹ã䞊ã¹ãŠè¿œå ããŸãã
ããã«å®ç§ã«ããããšããªãã§ãã ãããããã±ãŒãžåãã€ã³ã¿ãŒãã§ãŒã¹ãã¯ã©ã¹ãã¡ãœããã®è©³çŽ°ãçèããŠãã ããã ãªããžã§ã¯ãå士ã®çžäºäœçšã培åºçã«èŠå®ããŸã-ããã¯ãã¹ãŠãæ°åå以äžå€åããŸãã ãããžã§ã¯ããçºå±ããã«ã€ããŠãå€ãã®ãã®ãugããŠããã°ããããªãã«ãšã£ãŠãå¹æçã§ãªãããã«èŠããŸã-çŸä»£ã®IDEã§ã®ãªãã¡ã¯ã¿ãªã³ã°ã¯éåžžã«å®äŸ¡ãªæäœãªã®ã§ãæ°è»œã«å€æŽããŠãã ããã
ãŸãã
main
æ©èœãåããã¯ã©ã¹ãäœæããŸããããã§ããã°ãããææã«åããããšãã§ããŸãã IDEèªäœãèµ·åã«äœ¿çšã§ããŸãããåŸã§èŠãããã«ããã®æ¹æ³ã¯ç§ãã¡ã®ç®çã«ã¯é©ããŠããŸããïŒæšæºã®IDEã³ã³ãœãŒã«ã¯ãå¿ èŠã«å¿ããŠã°ã©ãã£ã«ã«ãªçµæã衚瀺ã§ããŸããïŒããããã£ãŠããããïŒãŸãã¯UNIXã·ã¹ãã ã®ã·ã§ã«ïŒã䜿çšããŠå€éšããã®èµ·åãæ§æããŸããã¡ã€ã«ã ãã ãããã®åã«ãããã€ãã®è¿œå èšå®ãè¡ããŸãã
mvn package
æäœãå®äºããåŸãã³ã³ãã€ã«ããããã¹ãŠã®ã¯ã©ã¹ãå«ãJARã¢ãŒã«ã€ãã®åºåãååŸããŸãã ãŸããããã©ã«ãã§ã¯ããã®ã¢ãŒã«ã€ãã«ã¯ããããžã§ã¯ããæ©èœããããã«å¿ èŠãªäŸåé¢ä¿ãå«ãŸããŠããŸããïŒãããŸã§ã®ãšããããããã¯ãããŸããããå°æ¥çã«ã¯ç¢ºå®ã«è¡šç€ºãããŸãïŒã 次ã«ã
main
ã¡ãœãããå«ãã¡ã€ã³ã¯ã©ã¹ãžã®ãã¹ãã¢ãŒã«ã€ããããã§ã¹ããã¡ã€ã«ã§æå®ãããŠããªãããã
java -jar dice-1.0.jar
ãããžã§ã¯ããéå§
java -jar dice-1.0.jar
ãŸããã ãããä¿®æ£ããã«ã¯ã
pom.xml
è¿œå ã®èšå®ãè¿œå ããŸãã
<build> <plugins> <plugin> <artifactId>maven-assembly-plugin</artifactId> <version>2.6</version> <executions> <execution> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> <configuration> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> <archive> <manifest> <mainClass>my.company.dice.MainKt</mainClass> </manifest> </archive> </configuration> </plugin> </plugins> </build>
ã¡ã€ã³ã¯ã©ã¹ã®ååã«æ³šæããŠãã ããã ã¯ã©ã¹ã®å€éšã«å«ãŸããKotliné¢æ°ïŒ
main
é¢æ°ãªã©ïŒã®å Žåãã¯ã©ã¹ã¯ã³ã³ãã€ã«äžã«äœæãããŸãïŒJVMã¯äœãç¥ãããç¥ããããªãããïŒã ã¯ã©ã¹ã®ååã¯ã
Kt
è¿œå ãããã¡ã€ã«ã®ååã§ãã ã€ãŸããã¡ã€ã³ã¯ã©ã¹ã«
Main
ãšããååãä»ããå Žåã
MainKt.class
ãã¡ã€ã«ã«ã³ã³ãã€ã«ãããŸãã jarãã¡ã€ã«ã®ãããã§ã¹ãã§ç€ºãå¿ èŠãããã®ã¯ããã®æåŸã®1ã€ã§ãã
ããã§ããããžã§ã¯ãããã«ããããšãåºåã«2ã€ã®jarãã¡ã€ã«
dice-1.0.jar
ããã³
dice-1.0-jar-with-dependencies.jar
ãååŸãããŸãã 第äºã«èå³ããããŸãã ã¹ã¿ãŒãã¢ããã¹ã¯ãªãããäœæããŸãã
dice.bat ïŒWindowsçšïŒ
@ECHO OFF rem Compiling call "path_to_maven\mvn.bat" -f "path_to_project\Dice\pom.xml" package if errorlevel 1 echo Project compilation failed! & pause & goto :EOF rem Running java -jar path_to_project\Dice\target\dice-1.0-jar-with-dependencies.jar pause
dice.sh ïŒUNIXã®å ŽåïŒ
#!/bin/sh # Compiling mvn -f "path_to_project/Dice/pom.xml" package if [[ "$?" -ne 0 ]] ; then echo 'Project compilation failed!'; exit $rc fi # Running java -jar path_to_project/Dice/target/dice-1.0-jar-with-dependencies.jar
ã³ã³ãã€ã«ã倱æãããšãã¹ã¯ãªããã®äžæã匷å¶ãããããšã«æ³šæããŠãã ããã ãã以å€ã®å ŽåãæåŸã®ããŒãã§ã¯ãªããååã®æ£åžžãªã¢ã»ã³ããªããæ®ã£ãŠãããã¡ã€ã«ãèµ·åãããŸãïŒéããèŠã€ãããªãããšããããŸãïŒã å€ãã®å Žåãéçºè ã¯
mvn clean package
ã³ãã³ãã䜿çšããŠä»¥åã«ã³ã³ãã€ã«ããããã¹ãŠã®ãã¡ã€ã«ãåé€ããŸããããã®å Žåãã³ã³ãã€ã«ããã»ã¹å šäœã¯åžžã«ïŒãœãŒã¹ã³ãŒããå€æŽãããŠããªããŠãïŒæåããéå§ãããå€ãã®æéãããããŸãã ããããåŸ ã€ããšã¯ã§ããŸãã-ã²ãŒã ãäœæããå¿ èŠããããŸãã
ãã®ããããããžã§ã¯ãã¯æ£åžžã«èµ·åããŸããããããŸã§ã®ãšããäœãããŸããã å¿é ããªãã§ãã ãããããã«ä¿®æ£ããŸãã
ã¹ããã6 äž»ãªãªããžã§ã¯ã
åŸã ã«ãã²ãŒã ãã¬ã€ã«å¿ èŠãªã¯ã©ã¹ã
model
ããã±ãŒãžã«è¿œå ãå§ããŸãã
ãã¥ãŒãããã¹ãŠã§ããæåã«è¿œå ããŸãã åãã€ïŒ
Die
ã¯ã©ã¹ã®ã€ã³ã¹ã¿ã³ã¹ïŒã¯ããã®ã¿ã€ãïŒè²ïŒãšãµã€ãºã«ãã£ãŠç¹åŸŽä»ããããŸãã ãã¥ãŒãã®ã¿ã€ãã«ã€ããŠã¯ãåå¥ã®åæïŒ
Die.Type
ïŒãäœæãããµã€ãºã4ãã12ã®æŽæ°ã§ããŒã¯ããŸãããŸãã
roll()
ã¡ãœãããå®è£ ã
roll()
ãããã¯ããã¥ãŒãã§äœ¿çšå¯èœãªç¯å²ïŒ1ãããµã€ãºå€ãŸã§ïŒããä»»æã®åäžã«åæ£ããæ°ãçæããŸãã
ã¯ã©ã¹ã¯ããã¥ãŒããçžäºã«æ¯èŒã§ãã
Comparable
ã€ã³ã¿ãŒãã§ã€ã¹ãå®è£ ããŸãïŒåŸã§è€æ°ã®ãã¥ãŒããé åºä»ããããè¡ã«è¡šç€ºãããšãã«äŸ¿å©ã§ãïŒã ãã倧ããªãã¥ãŒãã¯ããæ©ãé 眮ãããŸãã
class Die(val type: Type, val size: Int) : Comparable<Die> { enum class Type { PHYSICAL, //Blue SOMATIC, //Green MENTAL, //Purple VERBAL, //Yellow DIVINE, //Cyan WOUND, //Gray ENEMY, //Red VILLAIN, //Orange OBSTACLE, //Brown ALLY //White } fun roll() = (1.. size).random() override fun toString() = "d$size" override fun compareTo(other: Die): Int { return compareValuesBy(this, other, Die::type, { -it.size }) } }
ã»ãããéããªãããã«ããã¥ãŒãã¯ãã³ãããã°ïŒ
Bag
ã¯ã©ã¹ã®ã³ããŒïŒã«ä¿ç®¡ãããŸãã ããã°å ã§äœãèµ·ãã£ãŠããããæšæž¬ããããšããã§ããªããããé åºä»ããããã³ã¬ã¯ã·ã§ã³ã䜿çšããæå³ã¯ãããŸããã ã®ããã§ãã ã»ããïŒã»ããïŒã¯å¿ èŠãªã¢ã€ãã¢ãããŸãââå®è£ ããŸããã2ã€ã®çç±ã§é©åããŸããã ãŸãããããã䜿çšãããšãã¯ã
equals()
ããã³
hashCode()
ã¡ãœãããå®è£ ããå¿ èŠããããŸããããã¥ãŒãã®ã¿ã€ããšãµã€ãºãæ¯èŒããã®ã¯ééã£ãŠãããããã©ã®ããã«ããã°ãããæ確ã§ã¯ãããŸãã-ä»»æã®æ°ã®åããã¥ãŒããã»ããã«æ ŒçŽã§ããŸãã 第äºã«ããã¥ãŒããè¢ããåãåºããšãæ¯åç°ãªãé決å®çãªãã®ã ãã§ãªããã©ã³ãã ãªãã®ãåŸãããããšãæåŸ ãããŸãã ãããã£ãŠãé åºä»ããããã³ã¬ã¯ã·ã§ã³ïŒãªã¹ãïŒã䜿çšããæ°ããèŠçŽ ãè¿œå ãããã³
put()
ã¡ãœããã§ïŒãŸãã¯çºè¡ããçŽåïŒ
draw()
ã¡ãœããã§ïŒã·ã£ããã«ããããšããå§ãããŸãã
examine()
ã¡ãœããã¯ãäžç¢ºå®æ§ã«é£œã飜ãããŠãããã¬ãŒã€ãŒãããŒãã®ããŒãã«ã®ããã°ã®äžèº«ãæ¯ãå ŽåïŒãœãŒãã«æ³šæãæãïŒãšã
clear()
ã¡ãœãã-æ¯ããããã¥ãŒããããã°ã«æ»ããªãå Žåã«é©ããŠããŸãã
open class Bag { protected val dice = LinkedList<Die>() val size get() = dice.size fun put(vararg dice: Die) { dice.forEach(this.dice::addLast) this.dice.shuffle() } fun draw(): Die = dice.pollFirst() fun clear() = dice.clear() fun examine() = dice.sorted().toList() }
ãã¥ãŒãä»ãããã°ã«å ããŠããã¥ãŒãä»ãããŒãïŒ
Pile
ã¯ã©ã¹ã®ã€ã³ã¹ã¿ã³ã¹ïŒãå¿ èŠã§ãã 1ã€ç®ãš2ã€ç®ã¯ãã³ã³ãã³ãããã¬ãŒã€ãŒã«è¡šç€ºãããç¹ã§ç°ãªããŸãããããã£ãŠãå¿ èŠã«å¿ããŠããŒããããã¥ãŒããåé€ãããã¬ãŒã€ãŒã¯ç¹å®ã®ã€ã³ã¹ã¿ã³ã¹ãéžæã§ããŸãã
removeDie()
ã¡ãœããã䜿çšããŠãã®ã¢ã€ãã¢ãå®è£ ããŸãã
class Pile : Bag() { fun removeDie(die: Die) = dice.remove(die) }
次ã«ãäž»äººå ¬ã§ããããŒããŒã«ç®ãåããŸãã ã€ãŸããä»åŸãã£ã©ã¯ã¿ãŒãããŒããŒãšåŒã³ãŸãïŒJavaã§
Character
ãšããååã§ã¯ã©ã¹ãåŒã³åºããªãçç±ã¯ãããŸãïŒã ãã£ã©ã¯ã¿ãŒã«ã¯ããŸããŸãªçš®é¡ããããŸããïŒã¯ã©ã¹ã«å ¥ããã«ã¯åèª
class
ã䜿çšããªãã»ããè¯ãã§ãïŒãäœæ¥äžã®ãããã¿ã€ãã§ã¯ã Brawler ïŒã€ãŸãã匷ããšåŒ·ãã«éç¹ã眮ããFighterïŒãšHunter ïŒå¥åRanger / Thiefãã«éç¹ã眮ããŸãïŒåšçšããšã¹ãã«ã¹ïŒã äž»äººå ¬ã®ã¯ã©ã¹ã¯åœŒã®ç¹æ§ãã¹ãã«ããã¥ãŒãã®åæã»ããã決å®ããŸãããåŸã§èŠãããããã«ãäž»äººå ¬ã¯ã¯ã©ã¹ã«å³å¯ã«çµã³ä»ããããªããããå人èšå®ã¯1ãæã§ç°¡åã«å€æŽã§ããŸãã
ãã¶ã€ã³ããã¥ã¡ã³ãã«åŸã£ãŠãããŒããŒã«å¿ èŠãªããããã£ãè¿œå ããŸããããååããæ°ã«å ¥ãã®ãã¥ãŒãã®çš®é¡ããµã€ã³ãã®å¶éãç¿åŸæžã¿ã®ã¹ãã«ãæªçãªã¹ãã«ããªã»ããçšã®æãããã°ããã€ã«ã ã³ã¬ã¯ã·ã§ã³ããããã£ã®å®è£ ã®æ©èœã«æ³šæããŠãã ããã ææäžçå šäœã§ããªããžã§ã¯ãå ã«æ ŒçŽãããŠããã³ã¬ã¯ã·ã§ã³ãžã®å€éšã¢ã¯ã»ã¹ïŒã²ãã¿ãŒã®å©ããåããŠïŒãæäŸããã®ã¯æªã圢ãšèããããŠããŸã-æªåŸ³ããã°ã©ããŒã¯ãã¯ã©ã¹ã®ç¥èããªããŠããããã®ã³ã¬ã¯ã·ã§ã³ã®å 容ãå€æŽã§ããŸãã ããã«å¯ŸåŠãã1ã€ã®æ¹æ³ã¯ãèŠçŽ ãè¿œå ããã³åé€ãããã®çªå·ãååŸããã€ã³ããã¯ã¹ã§ã¢ã¯ã»ã¹ããããã®åå¥ã®ã¡ãœãããå®è£ ããããšã§ãã ã²ãã¿ãŒãå®è£ ã§ããŸãããåæã«ã³ã¬ã¯ã·ã§ã³èªäœã§ã¯ãªãããã®äžå€ã®ã³ããŒãè¿ããŸã-å°æ°ã®èŠçŽ ã®å Žåãããã ããå®è¡ããããšã¯ç¹ã«æããããŸããã
data class Hero(val type: Type) { enum class Type { BRAWLER HUNTER } var name = "" var isAlive = true var favoredDieType: Die.Type = Die.Type.ALLY val hand = Hand(0) val bag: Bag = Bag() val discardPile: Pile = Pile() private val diceLimits = mutableListOf<DiceLimit>() private val skills = mutableListOf<Skill>() private val dormantSkills = mutableListOf<Skill>() fun addDiceLimit(limit: DiceLimit) = diceLimits.add(limit) fun getDiceLimits(): List<DiceLimit> = Collections.unmodifiableList(diceLimits) fun addSkill(skill: Skill) = skills.add(skill) fun getSkills(): List<Skill> = Collections.unmodifiableList(skills) fun addDormantSkill(skill: Skill) = dormantSkills.add(skill) fun getDormantSkills(): List<Skill> = Collections.unmodifiableList(dormantSkills) fun increaseDiceLimit(type: Die.Type) { diceLimits.find { it.type == type }?.let { when { it.current < it.maximal -> it.current++ else -> throw IllegalArgumentException("Already at maximum") } } ?: throw IllegalArgumentException("Incorrect type specified") } fun hideDieFromHand(die: Die) { bag.put(die) hand.removeDie(die) } fun discardDieFromHand(die: Die) { discardPile.put(die) hand.removeDie(die) } fun hasSkill(type: Skill.Type) = skills.any { it.type == type } fun improveSkill(type: Skill.Type) { dormantSkills .find { it.type == type } ?.let { skills.add(it) dormantSkills.remove(it) } skills .find { it.type == type } ?.let { when { it.level < it.maxLevel -> it.level += 1 else -> throw IllegalStateException("Skill already maxed out") } } ?: throw IllegalArgumentException("Skill not found") } }
ããŒããŒã®æïŒåœŒãçŸåšæã£ãŠããç«æ¹äœïŒã¯ãå¥ã®ãªããžã§ã¯ãïŒ
Hand
ã¯ã©ã¹ïŒã«ãã£ãŠèšè¿°ãããŸãã é£åãã¥ãŒããã¡ã€ã³ã¢ãŒã ããåé¢ããŠãããšããèšèšäžã®æ±ºå®ã¯ãæåã«æãã€ãããã®ã®1ã€ã§ããã æåã¯éåžžã«ã¯ãŒã«ãªæ©èœã®ããã«èŠããŸããããåŸã«èšå€§ãªæ°ã®åé¡ãšäžäŸ¿ãçã¿åºããŸããã ããã§ããç°¡åãªæ¹æ³ãæ¢ããŠããããã§ã¯ãªãããã
dice
ãš
allies
ãªã¹ãã¯ãµãŒãã¹ã«ãããè¿œå ãåä¿¡ãåé€ããå¿ èŠããããã¹ãŠã®ã¡ãœããããããŸãïŒãã®ãã¡ã®ããã€ãã¯ã2ã€ã®ãªã¹ãã®ã©ã¡ãã«ã¢ã¯ã»ã¹ããããå·§åŠã«æ±ºå®ããŸãïŒã æãããã¥ãŒããåé€ãããšãåŸç¶ã®ãã¹ãŠã®ãã¥ãŒãããªã¹ãã®æäžéšã«ç§»åãã空çœãåããŸã-å°æ¥çã«ã¯æ€çŽ¢ãå€§å¹ ã«å®¹æã«ãªããŸãïŒ
null
ç¶æ³ãåŠçããå¿ èŠã¯ãããŸããïŒã
class Hand(var capacity: Int) { private val dice = LinkedList<Die>() private val allies = LinkedList<Die>() val dieCount get() = dice.size val allyDieCount get() = allies.size fun dieAt(index: Int) = when { (index in 0 until dieCount) -> dice[index] else -> null } fun allyDieAt(index: Int) = when { (index in 0 until allyDieCount) -> allies[index] else -> null } fun addDie(die: Die) = when { die.type == Die.Type.ALLY -> allies.addLast(die) else -> dice.addLast(die) } fun removeDie(die: Die) = when { die.type == Die.Type.ALLY -> allies.remove(die) else -> dice.remove(die) } fun findDieOfType(type: Die.Type): Die? = when (type) { Die.Type.ALLY -> if (allies.isNotEmpty()) allies.first else null else -> dice.firstOrNull { it.type == type } } fun examine(): List<Die> = (dice + allies).sorted() }
DiceLimit
ã¯ã©ã¹ã®ãªããžã§ã¯ãã®ã³ã¬ã¯ã·ã§ã³ã¯ãããŒããŒãã¹ã¯ãªããã®æåã«æã€ããšãã§ããåã¿ã€ãã®ãã¥ãŒãã®æ°ã«å¶éãèšå®ããŸãã ç¹å¥ãªããšã¯ãããŸãããæåã«ãåã¿ã€ãã®æ倧å€ãšçŸåšã®å€ã決å®ããŸãã
class DiceLimit(val type: Die.Type, val initial: Int, val maximal: Int, var current: Int)
ããããã¹ãã«ãããã°ãã£ãšé¢çœãã§ãã ãããããåå¥ã«å®è£ ããå¿ èŠããããŸãïŒè©³çŽ°ã¯åŸã»ã©èª¬æããŸãïŒãã HitãšShoot ïŒã¯ã©ã¹ããšã«1ã€ïŒã®2ã€ã ããæ€èšããŸãã ã¹ãã«ã¯ãåæã¬ãã«ããæ倧ã¬ãã«ãŸã§éçºïŒããã³ããïŒã§ããŸããããã¯ããµã€ã³ãã®ããŒã«ã«è¿œå ãããä¿®æ£ã«åœ±é¿ãäžããããšããããããŸãã ããã¯ãããããã£
level
ã
maxLevel
ã
modifier1
ããã³
modifier2
maxLevel
ããŸãã
class Skill(val type: Type) { enum class Type { //Brawler HIT, //Hunter SHOOT, } var level = 1 var maxLevel = 3 var isActive = true var modifier1 = 0 var modifier2 = 0 }
Hero
ã¯ã©ã¹ã®è£å©ã¡ãœããã«æ³šæããŠãã ãããããã«ãããæãããã€ã¹ãé ãããããŒã«ããããããŒããŒã«ç¹å®ã®ã¹ãã«ããããã©ããã確èªããããåŠç¿ããã¹ãã«ã®ã¬ãã«ãäžããããæ°ããã¹ãã«ãç¿åŸãããã§ããŸãã ãããã¯ãã¹ãŠé ããæ©ããå¿ èŠã«ãªããŸãããä»ã¯ãããã«ã€ããŠè©³ããã¯èª¬æããŸããã
äœæããªããã°ãªããªãã¯ã©ã¹ã®æ°ãæããªãã§ãã ããã ãã®è€éãªãããžã§ã¯ãã§ã¯ãæ°çŸãäžè¬çãªãã®ã§ãã ããã§ã¯ãããããæ·±å»ãªè·æ¥ãšåæ§ã«ãç§ãã¡ã¯å°ããªããšããå§ããåŸã ã«ããŒã¹ãäžããŠãããŸãã1ãæã§ãç§ãã¡ã¯ãã®ç¯å²ã«æããããªããŸãã å¿ããªãã§ãã ãããç§ãã¡ã¯ãŸã äžäººã®å°ããªã¹ã¿ãžãªã§ã-ç§ãã¡ã¯å§åçãªä»äºã«çŽé¢ããŠããŸããã
ãäœããããããããã ç§ã¯ã¿ãã³ãåžãããäœã...ã
ãããŠãç§ãã¡ã¯ç¶ããŸãã
äž»äººå ¬ãšãã®èœåã説æãããŠããŸãããä»åºŠã¯æµã®å-倧ãããŠæãããã²ãŒã ã¡ã«ãã¯ã¹ã«è¡ããŸãããã ããããããŒããŒã察話ããªããã°ãªããªããªããžã§ã¯ãã
ç§ãã¡ã®åæ¢ãªäž»äººå ¬ã¯ã3çš®é¡ã®ãã¥ãŒããšã«ãŒãã«çŽé¢ããŸãïŒæªåœ¹ïŒ
Villain
ã¯ã©ã¹ïŒãæµïŒ
Enemy
ã¯ã©ã¹ïŒãé害ïŒ
Obstacle
ã¯ã©ã¹ïŒããè åšããšããäžè¬çšèªã®äžã§å£çµããŸãéå®ïŒã åè åšã«ã¯ããã®ãããªè åšã«çŽé¢ãããšãã®è¡åã®ç¹å¥ãªã«ãŒã«ãèšè¿°ããã²ãŒã ãã¬ã€ã«å€æ§æ§ãè¿œå ããäžé£ã®ç¹åŸŽçãªæ©èœïŒ
Trait
ïŒããããŸãã
sealed class Threat { var name: String = "" var description: String = "" private val traits = mutableListOf<Trait>() fun addTrait(trait: Trait) = traits.add(trait) fun getTraits(): List<Trait> = traits } class Obstacle(val tier: Int, vararg val dieTypes: Die.Type) : Threat() class Villain : Threat() class Enemy : Threat() enum class Trait { MODIFIER_PLUS_ONE, //Add +1 modifier MODIFIER_PLUS_TWO, //Add +2 modifier }
Trait
ã¯ã©ã¹ã®ãªããžã§ã¯ãã®ãªã¹ãã¯å¯å€ïŒ
MutableList
ïŒãšããŠå®çŸ©ãããŠããŸãããäžå€ã®
List
ã€ã³ã¿ãŒãã§ãŒã¹ãšããŠæäŸãããŠããããšã«æ³šæããŠãã ããã ããã¯Kotlinã§æ©èœããŸãããçµæã®ãªã¹ããå€æŽå¯èœãªã€ã³ã¿ãŒãã§ã€ã¹ã«å€æãããããããŸããŸãªå€æŽãå ãããããããšã劚ãããã®ããªãããããã®ã¢ãããŒãã¯å®å šã§ã¯ãããŸãã-Javaã³ãŒãããã¯ã©ã¹ã«ã¢ã¯ã»ã¹ããå ŽåïŒ
List
ã€ã³ã¿ãŒãã§ã€ã¹ãå€æŽå¯èœãªå ŽåïŒãããã¯ç¹ã«ç°¡åã§ãã ã³ã¬ã¯ã·ã§ã³ãä¿è·ããæãåŠæ³çãªæ¹æ³ã¯ã次ã®ãããªããšã§ãã
fun getTraits(): List<Trait> = Collections.unmodifiableList(traits)
ããããç§ãã¡ã¯åé¡ã«è¿ã¥ããŠããã®ã«ããã»ã©æ éã§ã¯ãããŸããïŒãã ããèŠåãããŠããŸãïŒã
ã²ãŒã ã¡ã«ãã¯ã¹ã®ç¹æ§ã«ããã
Obstacle
ã¯ã©ã¹ã¯è¿œå ãã£ãŒã«ããããç¹ã§å¯Ÿå¿ããã¯ã©ã¹ãšã¯ç°ãªããŸããããããã«çŠç¹ãåãããŸããã
è åšã«ãŒãïŒããã³èšèšææžã泚ææ·±ãèªãã å Žåããããã¯ã«ãŒãã§ããããšãå¿ããªãã§ãã ããïŒã¯ã
Deck
ã¯ã©ã¹ã§è¡šããããããã«çµåãããŸãã
class Deck<E: Threat> { private val cards = LinkedList<E>() val size get() = cards.size fun addToTop(card: E) = cards.addFirst(card) fun addToBottom(card: E) = cards.addLast(card) fun revealTop(): E = cards.first fun drawFromTop(): E = cards.removeFirst() fun shuffle() = cards.shuffle() fun clear() = cards.clear() fun examine() = cards.toList() }
ããã§ã¯ãã¯ã©ã¹ããã©ã¡ãŒã¿ãŒåãããé©åãªã¡ãœããã䜿çšããŠæ··åã§ããé åºä»ããªã¹ãïŒãŸãã¯åæ¹åãã¥ãŒïŒãå«ãŸããŠããããšãé€ããŠãç°åžžãªãã®ã¯ãããŸããã æµãé害ç©ã®ãããã¯ãæåéãäžç¬ã®ãã¡ã«èæ ®ã«å ¥ããå¿ èŠããããŸã...
...
Location
ã¯ã©ã¹ã®åã€ã³ã¹ã¿ã³ã¹ã¯ãããŒããŒãã¹ã¯ãªããã®äžéšãšããŠèšªåããªããã°ãªããªãäžæã®å Žæãèšè¿°ããŸãã
class Location { var name: String = "" var description: String = "" var isOpen = true var closingDifficulty = 0 lateinit var bag: Bag var villain: Villain? = null lateinit var enemies: Deck<Enemy> lateinit var obstacles: Deck<Obstacle> private val specialRules = mutableListOf<SpecialRule>() fun addSpecialRule(rule: SpecialRule) = specialRules.add(rule) fun getSpecialRules() = specialRules }
åå°åã«ã¯ãååã説æãééã®é£æ床ãããªãŒãã³/ã¯ããŒãºãã®ãµã€ã³ããããŸãã ããã®ã©ããã«ãæªåœ¹ãæœãã§ããå¯èœæ§ããããŸãïŒãŸãã¯ãæœãã§ããªãå¯èœæ§ãããããã®çµæãšããŠã
villain
ããããã£ã
null
ãªãå Žåããã
null
ïŒã åãšãªã¢ã«ã¯ããã¥ãŒãã®å ¥ã£ãããã°ãšè åšã®ãããããããããŸãã å°åœ¢ã«ã¯ãç¬èªã®ã²ãŒã æ©èœïŒ
SpecialRule
ïŒã
SpecialRule
ããšãã§ããŸããããã¯ãè åšã®ããããã£ãšåæ§ã«ãã²ãŒã ãã¬ã€ã«å€æ§æ§ãè¿œå ããŸãã ã芧ã®ãšãããè¿ãå°æ¥ã«å®è£ ããäºå®ããªãå Žåã§ããå°æ¥ã®æ©èœã®åºç€ãæ§ç¯ããŠããŸãïŒå®éã«ã¯ãã¢ããªã³ã°æ®µéãå¿ èŠã§ãïŒã
æåŸã«ãã¹ã¯ãªããïŒ
Scenario
ã¯ã©ã¹ïŒãå®è£ ããŸãã
class Scenario { var name = "" var description = "" var level = 0 var initialTimer = 0 private val allySkills = mutableListOf<AllySkill>() private val specialRules = mutableListOf<SpecialRule>() fun addAllySkill(skill: AllySkill) = allySkills.add(skill) fun getAllySkills(): List<AllySkill> = Collections.unmodifiableList(allySkills) fun addSpecialRule(rule: SpecialRule) = specialRules.add(rule) fun getSpecialRules(): List<SpecialRule> = Collections.unmodifiableList(specialRules) }
åã·ããªãªã¯ãã¿ã€ããŒã®ã¬ãã«ãšåæå€ã«ãã£ãŠç¹åŸŽä»ããããŸãã åã«èŠããã®ãšåæ§ã«ãç¹å¥ãªã«ãŒã«ïŒ
specialRules
ïŒãšåçè ã®ã¹ãã«ãèšå®ãããŸãïŒèæ ®ããéããŸãïŒã ã¹ã¯ãªããã«ã¯ãå ŽæïŒ
Location
ã¯ã©ã¹ã®ãªããžã§ã¯ãïŒã®ãªã¹ããå«ããã¹ãã ãšæããããããŸããããè«ççã«ã¯ãããã¯æ¬åœã«ããã§ãã ããããåŸã§èŠãããããã«ããã®ãããªæ¥ç¶ã¯ã©ãã§ã䜿çšãããæè¡çãªå©ç¹ããããŸããã
ãããŸã§ã«æ€èšãããã¹ãŠã®ã¯ã©ã¹ã
model
ããã±ãŒãžã«å«ãŸããŠããããšãæãåºããŠãã ãããåäŸã®é ã壮倧ãªããã¡ãã®æŠããèŠè¶ããŠãããŒãã«ã®è¡šé¢ã«å µå£«ãé 眮ããŸããã ãããŠä»ãããã€ãã®çã¿ã䌎ãç¬éã®åŸãæé«åžä»€å®ã®åå³ã§ãç§ãã¡ã¯ããã¡ããæŒãåãããã²ãŒã ãã¬ã€ã®çµæã楜ããã§ãæŠãã«çªå ¥ããŸãã ãããããã®åã«ãåã決ãèªäœã«ã€ããŠå°ã説æããŸãã
ããã...ã
7çªç®ã®ã¹ãããã ãã¿ãŒã³ãšãžã§ãã¬ãŒã¿ãŒ
以åã«èæ ®ããããªããžã§ã¯ããçæããããã»ã¹ããããšãã°ãã±ãŒã·ã§ã³ïŒå°åœ¢ïŒã«ãªãããšãå°ãæ³åããŠã¿ãŸããããã¯ã©ã¹ã®ã€ã³ã¹ã¿ã³ã¹ãäœæãã
Location
ãã£ãŒã«ããå€ã§åæåããå¿ èŠããããŸãããããã£ãŠãã²ãŒã ã§äœ¿çšããããŒã«ãªãã£ããšã«å¿ èŠã§ãããã ããåŸ ã£ãŠãã ãããåå Žæã«ã¯ããã°ãå¿ èŠã§ããããããçæããå¿ èŠããããŸãããŸããããã°ã«ã¯ãã¥ãŒãããããŸã-ãããã¯ã察å¿ããã¯ã©ã¹ïŒ
Die
ïŒã®ã€ã³ã¹ã¿ã³ã¹ã§ããããŸããããã¯æµãé害ç©ã®ããšã§ã¯ãããŸãã-ãããã¯äžè¬çã«ãããã«éããããå¿ èŠããããŸãããŸããæªåœ¹ã¯å°åœ¢èªäœã決å®ããã®ã§ã¯ãªããã·ããªãªã®æ©èœã1ã¬ãã«äžã«é 眮ããŸããããŠãããªãã¯ãã€ã³ããåŸãã äžèšã®ãœãŒã¹ã³ãŒãã¯æ¬¡ã®ããã«ãªããŸãã
val location = Location().apply { name = "Some location" description = "Some description" isOpen = true closingDifficulty = 4 bag = Bag().apply { put(Die(Die.Type.PHYSICAL, 4)) put(Die(Die.Type.SOMATIC, 4)) put(Die(Die.Type.MENTAL, 4)) put(Die(Die.Type.ENEMY, 6)) put(Die(Die.Type.OBSTACLE, 6)) put(Die(Die.Type.VILLAIN, 6)) } villain = Villain().apply { name = "Some villain" description = "Some description" addTrait(Trait.MODIFIER_PLUS_ONE) } enemies = Deck<Enemy>().apply { addToTop(Enemy().apply { name = "Some enemy" description = "Some description" }) addToTop(Enemy().apply { name = "Other enemy" description = "Some description" }) shuffle() } obstacles = Deck<Obstacle>().apply { addToTop(Obstacle(1, Die.Type.PHYSICAL, Die.Type.VERBAL).apply { name = "Some obstacle" description = "Some Description" }) } }
ããã¯ãKotlinèšèªãšèšèšã®ãããã§ããã
apply{}
ãŸããJavaã§ã¯ãã³ãŒãã¯2åæ±ãã«ãããªããŸããããã«ãç§ãã¡ãèšã£ãããã«å€ãã®å Žæãããããããã®ã»ãã«ãã·ããªãªãåéºããããŠã¹ãã«ãšç¹åŸŽãæã€ããŒããŒãããŸã-äžè¬ã«ãã²ãŒã ãã¶ã€ããŒãããããšããããŸãã
ãã ããã²ãŒã ãã¶ã€ããŒã¯ã³ãŒããèšè¿°ããªããããã²ãŒã ã®äžçã§ããããªå€åããã£ããšãã«ãããžã§ã¯ããåã³ã³ãã€ã«ããã®ã¯äžäŸ¿ã§ããããã§ã¯ãæèœãªããã°ã©ããŒã¯ãã¯ã©ã¹ã³ãŒããããªããžã§ã¯ãã®èšè¿°ãåé¢ããå¿ èŠãããããšã«å察ããŸã-çæ³çã«ã¯ãåŸè ã®ã€ã³ã¹ã¿ã³ã¹ã¯ãå¿ èŠã«å¿ããŠåè ã«åºã¥ããŠåçã«çæãããŸãããã®ãããªå³é¢ãå®è£ ãããããããã³ãã¬ãŒããšåŒã¶ã ãã§ãç¹å¥ãªã¯ã©ã¹ã®ã€ã³ã¹ã¿ã³ã¹ãšããŠè¡šããŸãããã®ãããªãã¿ãŒã³ã䜿çšãããšãç¹å¥ãªããã°ã©ã ã³ãŒãïŒãžã§ãã¬ãŒã¿ãŒïŒãåè¿°ã®ã¢ãã«ããæçµçãªãªããžã§ã¯ããäœæããŸãã
ãããã£ãŠããªããžã§ã¯ãã®ã¯ã©ã¹ããšã«ããã³ãã¬ãŒãã€ã³ã¿ãŒãã§ã€ã¹ãšãžã§ãã¬ãŒã¿ã¯ã©ã¹ã®2ã€ã®æ°ãããšã³ãã£ãã£ãå®çŸ©ããå¿ èŠããããŸãããããŠãããªãã®éã®ãªããžã§ã¯ããèç©ãããŠãããããå€æ°ã®ãšã³ãã£ãã£ããããŸã...äžåã§ãïŒ
æ°ãæ£ãããªãããã«ãæ·±ãåŒåžãã泚ææ·±ãèããŠãã ããããŸããå³ã«ã¯ã²ãŒã ã¯ãŒã«ãã®ãã¹ãŠã®ãªããžã§ã¯ãã衚瀺ãããŠããããã§ã¯ãªããã¡ã€ã³ã®ãªããžã§ã¯ãã®ã¿ã衚瀺ãããŠããŸãã第äºã«ãäžå¿ èŠãªè©³çŽ°ã§åè·¯ãéè² è·ã«ããªãããã«ãä»ã®å³ã§ãã§ã«è¿°ã¹ãæ¥ç¶ã®ããã€ãã¯çç¥ãããŸããã
ãã¥ãŒãã®çæ-ã·ã³ãã«ãªãã®ããå§ããŸãããã ãã©ãïŒ -ããªãã¯èšããŸãã -ã³ã³ã¹ãã©ã¯ã¿ãŒã足ããŸãããïŒã¯ããã¯ããåããã®ã§ãã¿ã€ããšãµã€ãºããããŸãããããããååã§ã¯ãããŸãããå®éãå€ãã®å ŽåïŒã«ãŒã«ãèªãã§ïŒããã¥ãŒãã¯ä»»æã®éã§ä»»æã«çæããå¿ èŠããããŸãïŒããšãã°ããéãŸãã¯ç·ã®1ã3åã®ãã¥ãŒããïŒãããã«ãã¹ã¯ãªããã®è€éãã®ã¬ãã«ã«å¿ããŠãµã€ãºãéžæããŸãããããã£ãŠãç¹å¥ãªã€ã³ã¿ãŒãã§ã€ã¹ãå°å ¥ããŸã
DieTypeFilter
ã
interface DieTypeFilter { fun test(type: Die.Type): Boolean }
ãã®ã€ã³ã¿ãŒãã§ã€ã¹ã®ããŸããŸãªå®è£ ã¯ããã¥ãŒãã®ã¿ã€ããç°ãªãã«ãŒã«ã»ããïŒæãæµ®ãã¶ãã®ïŒã«å¯Ÿå¿ããŠãããã©ããããã§ãã¯ããŸããããšãã°ãã¿ã€ããå³å¯ã«æå®ãããå€ïŒãéãïŒãŸãã¯å€ã®ç¯å²ïŒãéãé»ãç·ãïŒã«å¯Ÿå¿ãããã©ããããŸãã¯ãå察ã«ãæå®ãããã¿ã€ã以å€ã®ãã¹ãŠã®ã¿ã€ãã«å¯Ÿå¿ããŸãïŒããããã©ããªå Žåã§ãçœã§ãªãã£ãå Žåã-äœã§ããããã ãïŒãå¿ èŠãªç¹å®ã®å®è£ ãäºåã«æ確ã§ã¯ãªãå Žåã§ããéèŠã§ã¯ãããŸãã-åŸã§è¿œå ã§ããŸãããã·ã¹ãã ã¯ãããäžæããŸããïŒå€æ æ§ãèŠããŠããŸããïŒïŒã
class SingleDieTypeFilter(val type: Die.Type): DieTypeFilter { override fun test(type: Die.Type) = (this.type == type) } class InvertedSingleDieTypeFilter(val type: Die.Type): DieTypeFilter { override fun test(type: Die.Type) = (this.type != type) } class MultipleDieTypeFilter(vararg val types: Die.Type): DieTypeFilter { override fun test(type: Die.Type) = (type in types) } class InvertedMultipleDieTypeFilter(vararg val types: Die.Type): DieTypeFilter { override fun test(type: Die.Type) = (type !in types) }
ãã¥ãŒãã®ãµã€ãºãä»»æã«èšå®ãããŸãããããã«ã€ããŠã¯åŸã§è©³ãã説æããŸãããããŸã§ã®éããã¥ãŒããžã§ãã¬ãŒã¿ãŒïŒ
DieGenerator
ïŒãäœæããŸããããã¯ãã¯ã©ã¹ã³ã³ã¹ãã©ã¯ã¿ãŒãšã¯ç°ãªã
Die
ããã¥ãŒãã®æ瀺çãªã¿ã€ããšãµã€ãºã§ã¯ãªãããã£ã«ã¿ãŒãšè€éãã®ã¬ãã«ãåãå ¥ããŸãã
private val DISTRIBUTION_LEVEL1 = intArrayOf(4, 4, 4, 4, 6, 6, 6, 6, 8) private val DISTRIBUTION_LEVEL2 = intArrayOf(4, 6, 6, 6, 6, 8, 8, 8, 8, 10) private val DISTRIBUTION_LEVEL3 = intArrayOf(6, 8, 8, 8, 10, 10, 10, 10, 12, 12, 12) private val DISTRIBUTIONS = arrayOf( intArrayOf(4), DISTRIBUTION_LEVEL1, DISTRIBUTION_LEVEL2, DISTRIBUTION_LEVEL3 ) fun getMaxLevel() = DISTRIBUTIONS.size - 1 fun generateDie(filter: DieTypeFilter, level: Int) = Die(generateDieType(filter), generateDieSize(level)) private fun generateDieType(filter: DieTypeFilter): Die.Type { var type: Die.Type do { type = Die.Type.values().random() } while (!filter.test(type)) return type } private fun generateDieSize(level: Int) = DISTRIBUTIONS[if (level < 1 || level > getMaxLevel()) 0 else level].random()
Javaã§ã¯ããããã®ã¡ãœããã¯éçã«ãªããŸãããKotlinãæ±ãããããã®ãããªã¯ã©ã¹ã¯å¿ èŠãããŸãããããã¯ã以äžã§èª¬æããä»ã®ãžã§ãã¬ãŒã¿ãŒã«ãåœãŠã¯ãŸããŸãïŒããã«ãããããããè«çã¬ãã«ã§ã¯ãã¯ã©ã¹ã®æŠå¿µã䜿çšããŸãïŒã
2ã€ã®ãã©ã€ããŒãã¡ãœããã¯ããã¥ãŒãã®ã¿ã€ããšãµã€ãºãåå¥ã«çæããŸããããããã«ã€ããŠèå³æ·±ãããšãèšããŸãããã®ã¡ãœãã
generateDieType()
ã¯ã次ã®ããã«ãã£ã«ã¿ãŒãæž¡ãããšã§ç¡éã«ãŒãã«é§åã§ããŸãã
override fun test(filter: DieTypeFilter) = false
ïŒäœå®¶ã¯ãã¹ããŒãªãŒäžã«ç»å Žäººç©èªèº«ã芳客ãæãå Žåãè«ççãªççŸããæãåºããç©Žã空ããããšãã§ãããšåŒ·ãä¿¡ããŠããŸãïŒããã®ã¡ãœãã
generateDieSize()
ã¯ãé åïŒåã¬ãã«ã«1ã€ïŒã®åœ¢åŒã§æå®ãããååžã«åºã¥ããŠãç䌌ã©ã³ãã ãµã€ãºãçæããŸããèåŸãéæã¡ã«ãªã£ãŠãã«ãã«ã©ãŒã®ãã¬ã€ãããã¯ãè³Œå ¥ãããšããµã€ã³ãããã¬ã€ããããšãã§ããŸããããªããªãããããããã©ã³ãã ã«ããã°ãéããæ¹æ³ãããããªãããã§ãïŒé£äººã«å°ããŠãã®å Žã§èãåãã以å€ã¯ïŒãããã¯éããŸã«ã·ã£ããã«ã§ããã«ãŒãã®ãããã§ã¯ãªããç¹å¥ãªã¡ã«ããºã ãšããã€ã¹ãå¿ èŠã§ãã誰ããã¢ã€ãã¢ãæã£ãŠããïŒãããŠã圌ããã®æç¹ãŸã§èªãã§ããå¿èãæã£ãŠããïŒå Žåã¯ãã³ã¡ã³ãã§å ±æããŠãã ããã
ãããŠãããã°ã«ã€ããŠè©±ããŠããã®ã§ãããã°çšã®ãã³ãã¬ãŒããéçºããŸããããªãã®ä»²éãšã¯ç°ãªãããã®ãã³ãã¬ãŒãïŒ
BagTemplate
ïŒã¯ç¹å®ã®ã¯ã©ã¹ã«ãªããŸããä»ã®ãã³ãã¬ãŒããå«ãŸããŠããŸã-ãããããã
Plan
1ã€ä»¥äžã®ãã¥ãŒãïŒä»¥åã«äœæãããèŠä»¶ãèŠããŠããŸããïŒïŒãããã°ã«è¿œå ãããã«ãŒã«ïŒãŸãã¯ïŒãèšè¿°ããŸãã
class BagTemplate { class Plan(val minQuantity: Int, val maxQuantity: Int, val filter: DieTypeFilter) val plans = mutableListOf<Plan>() fun addPlan(minQuantity: Int, maxQuantity: Int, filter: DieTypeFilter) { plans.add(Plan(minQuantity, maxQuantity, filter)) } }
åãã©ã³ã¯ããã¥ãŒãã®ã¿ã€ãã®ãã¿ãŒã³ãšããã®ãã¿ãŒã³ãæºãããã¥ãŒãã®æ°ïŒæå°ããã³æ倧ïŒãå®çŸ©ããŸãããã®ã¢ãããŒãã®ãããã§ã掟æãªã«ãŒã«ã«åŸã£ãŠããã°ãçæããããšãã§ããŸãïŒãããŠãç§ã®é£äººã¯ãã£ã±ããšç§ãå©ããããšãæåŠããã®ã§ãç§ã¯ç§ã®å€ã幎霢ã§åã³æ¿ããæ³£ããŸãïŒããã®ãããªãã®ïŒ
private fun realizePlan(plan: BagTemplate.Plan, level: Int): Array<Die> { val count = (plan.minQuantity..plan.maxQuantity).shuffled().last() return (1..count).map { generateDie(plan.filter, level) }.toTypedArray() } fun generateBag(template: BagTemplate, level: Int): Bag { return template.plans.asSequence() .map { realizePlan(it, level) } .fold(Bag()) { b, d -> b.put(*d); b } } }
ç§ãšåãããã«ãããªãããã®ãã¹ãŠã®æ©èœäž»çŸ©ã«ç²ããŠãããªããèªåãç· ããŠãã ãã-ããã¯æªåããã ãã§ãããããããã®åŸãã€ã³ã¿ãŒãããäžã®å€ãã®äžæçãªãã¥ãŒããªã¢ã«ãšã¯ç°ãªããå®éã®ç解å¯èœãªäž»é¡é åã«é¢é£ããŠãããŸããŸãªå·§åŠãªæ¹æ³ã®äœ¿çšãç 究ããæ©äŒããããŸãã
ããèªäœã§ã¯ãããã°ã¯ãã£ãŒã«ãã«æšªãããããšã¯ãããŸãã-ããªãã¯ããŒããŒãšå Žæã«ããããäžããå¿ èŠããããŸããåŸè ããå§ããŸãããã
interface LocationTemplate { val name: String val description: String val bagTemplate: BagTemplate val basicClosingDifficulty: Int val enemyCardsCount: Int val obstacleCardsCount: Int val enemyCardPool: Collection<EnemyTemplate> val obstacleCardPool: Collection<ObstacleTemplate> val specialRules: List<SpecialRule> }
Kotlinèšèªã§ã¯ãã¡ãœããã®ä»£ããã«
get()
ã€ã³ã¿ãŒãã§ã€ã¹ããããã£ã䜿çšã§ããŸã-ããã¯ã¯ããã«ç°¡æœã§ããããã°ãã³ãã¬ãŒãã«ã€ããŠã¯ãã§ã«ããç¥ã£ãŠããŸããæ®ãã®æ¹æ³ãæ€èšããŠãã ããããã®ããããã£
basicClosingDifficulty
ã¯ãå°åœ¢ãéããããã®ãã§ãã¯ã®åºæ¬çãªè€éããèšå®ããŸããããã§ãããåºæ¬ããšã¯ãæçµçãªè€éããã·ããªãªã®ã¬ãã«ã«äŸåããããšã®ã¿ãæå³ãããã®æ®µéã§ã¯äžæã§ããããã«ãæµãšé害ç©ïŒããã³åæã«æªåœ¹ïŒã®ãã¿ãŒã³ãå®çŸ©ããå¿ èŠããããŸããããã«ããã³ãã¬ãŒãã§èª¬æãããŠããããŸããŸãªæµãé害ç©ããããã¹ãŠã䜿çšãããããã§ã¯ãªããéãããæ°ã ãã䜿çšãããŸãïŒãªãã¬ã€å€ãå¢ããããïŒã
SpecialRule
ãšãªã¢ã®ç¹å¥ãªã«ãŒã«ïŒïŒã¯åçŽãªåæïŒ
enum class
ïŒã«ãã£ãŠå®è£ ããããããå¥ã®ãã³ãã¬ãŒãã¯å¿ èŠãããŸããã
interface EnemyTemplate { val name: String val description: String val traits: List<Trait> } interface ObstacleTemplate { val name: String val description: String val tier: Int val dieTypes: Array<Die.Type> val traits: List<Trait> } interface VillainTemplate { val name: String val description: String val traits: List<Trait> }
ãããŠããžã§ãã¬ãŒã¿ãŒã«åã ã®ãªããžã§ã¯ãã ãã§ãªããããããå«ããããå šäœãäœæãããŸãã
fun generateVillain(template: VillainTemplate) = Villain().apply { name = template.name description = template.description template.traits.forEach { addTrait(it) } } fun generateEnemy(template: EnemyTemplate) = Enemy().apply { name = template.name description = template.description template.traits.forEach { addTrait(it) } } fun generateObstacle(template: ObstacleTemplate) = Obstacle(template.tier, *template.dieTypes).apply { name = template.name description = template.description template.traits.forEach { addTrait(it) } } fun generateEnemyDeck(types: Collection<EnemyTemplate>, limit: Int?): Deck<Enemy> { val deck = types .map { generateEnemy(it) } .shuffled() .fold(Deck<Enemy>()) { d, c -> d.addToTop(c); d } limit?.let { while (deck.size > it) deck.drawFromTop() } return deck } fun generateObstacleDeck(templates: Collection<ObstacleTemplate>, limit: Int?): Deck<Obstacle> { val deck = templates .map { generateObstacle(it) } .shuffled() .fold(Deck<Obstacle>()) { d, c -> d.addToTop(c); d } limit?.let { while (deck.size > it) deck.drawFromTop() } return deck }
ãããã«å¿ èŠä»¥äžã®ã«ãŒããããå ŽåïŒãã©ã¡ãŒã¿ãŒ
limit
ïŒãããããåé€ããŸãããã¥ãŒããšã«ãŒãã®ããã¯ã§ããã°ãçæã§ããã®ã§ãæçµçã«å°åœ¢ãäœæã§ããŸãã
fun generateLocation(template: LocationTemplate, level: Int) = Location().apply { name = template.name description = template.description bag = generateBag(template.bagTemplate, level) closingDifficulty = template.basicClosingDifficulty + level * 2 enemies = generateEnemyDeck(template.enemyCardPool, template.enemyCardsCount) obstacles = generateObstacleDeck(template.obstacleCardPool, template.obstacleCardsCount) template.specialRules.forEach { addSpecialRule(it) } }
ç« ã®åé ã§ã³ãŒãã§æ瀺çã«èšå®ããå°åœ¢ã¯ãå®å šã«ç°ãªãå€èŠ³ã«ãªããŸãã
class SomeLocationTemplate: LocationTemplate { override val name = "Some location" override val description = "Some description" override val bagTemplate = BagTemplate().apply { addPlan(1, 1, SingleDieTypeFilter(Die.Type.PHYSICAL)) addPlan(1, 1, SingleDieTypeFilter(Die.Type.SOMATIC)) addPlan(1, 2, SingleDieTypeFilter(Die.Type.MENTAL)) addPlan(2, 2, MultipleDieTypeFilter(Die.Type.ENEMY, Die.Type.OBSTACLE)) } override val basicClosingDifficulty = 2 override val enemyCardsCount = 2 override val obstacleCardsCount = 1 override val enemyCardPool = listOf( SomeEnemyTemplate(), OtherEnemyTemplate() ) override val obstacleCardPool = listOf( SomeObstacleTemplate() ) override val specialRules = emptyList<SpecialRule>() } class SomeEnemyTemplate: EnemyTemplate { override val name = "Some enemy" override val description = "Some description" override val traits = emptyList<Trait>() } class OtherEnemyTemplate: EnemyTemplate { override val name = "Other enemy" override val description = "Some description" override val traits = emptyList<Trait>() } class SomeObstacleTemplate: ObstacleTemplate { override val name = "Some obstacle" override val description = "Some description" override val traits = emptyList<Trait>() override val tier = 1 override val dieTypes = arrayOf( Die.Type.PHYSICAL, Die.Type.VERBAL ) } val location = generateLocation(SomeLocationTemplate(), 1)
ã·ããªãªã®çæãåæ§ã«è¡ãããŸãã
interface ScenarioTemplate { val name: String val description: String val initialTimer: Int val staticLocations: List<LocationTemplate> val dynamicLocationsPool: List<LocationTemplate> val villains: List<VillainTemplate> val specialRules: List<SpecialRule> fun calculateDynamicLocationsCount(numberOfHeroes: Int) = numberOfHeroes + 2 }
èŠåã«åŸã£ãŠãåçã«çæãããå Žæã®æ°ã¯ããŒããŒã®æ°ã«äŸåããŸããã€ã³ã¿ãŒãã§ã€ã¹ã¯ãå¿ èŠã«å¿ããŠç¹å®ã®å®è£ ã§åå®çŸ©ã§ããæšæºã®èšç®é¢æ°ãå®çŸ©ããŸãããã®èŠä»¶ã«ãããã·ããªãªãžã§ãã¬ãŒã¿ãŒã¯ãããã®ã·ããªãªã®å°åœ¢ãçæããŸããåãå Žæã§æªåœ¹ã¯å°åéã§ã©ã³ãã ã«åæ£ãããŸãã
fun generateScenario(template: ScenarioTemplate, level: Int) = Scenario().apply { name =template.name description = template.description this.level = level initialTimer = template.initialTimer template.specialRules.forEach { addSpecialRule(it) } } fun generateLocations(template: ScenarioTemplate, level: Int, numberOfHeroes: Int): List<Location> { val locations = template.staticLocations.map { generateLocation(it, level) } + template.dynamicLocationsPool .map { generateLocation(it, level) } .shuffled() .take(template.calculateDynamicLocationsCount(numberOfHeroes)) val villains = template.villains .map(::generateVillain) .shuffled() locations.forEachIndexed { index, location -> if (index < villains.size) { location.villain = villains[index] location.bag.put(generateDie(SingleDieTypeFilter(Die.Type.VILLAIN), level)) } } return locations }
å€ãã®ç±å¿ãªèªè ã¯ãã¯ã©ã¹ã®ãœãŒã¹ã³ãŒãã§ã¯ãªããäžéšã®ããã¹ããã¡ã€ã«ïŒã¹ã¯ãªããïŒã«ãã³ãã¬ãŒããä¿åããå¿ èŠãããããšã«å察ããã§ããããåæããŸããåžœåãè±ããŸãããé ã«ç°ãæ¯ããããŸãããäžæ¹ãä»æ¹ã«å¹²æžããªãããã§ããå¿ èŠã«å¿ããŠããã³ãã¬ãŒãã®ç¹å¥ãªå®è£ ãå®çŸ©ããã ãã§ããã®ããããã£å€ã¯å€éšãã¡ã€ã«ããããŒããããŸããããããã®çæããã»ã¹ã¯ã1ã€ã®iotaãå€æŽããŸããã
ãŸãã圌ãã¯äœãå¿ããŠããªãããã§ã...ãããããã§ããããŒããŒ-圌ããçæããå¿ èŠããããŸããããã¯åœŒãèªèº«ã®ãã³ãã¬ãŒããå¿ èŠãšããããšãæå³ããŸãã次ã«äŸã瀺ããŸãã
interface HeroTemplate { val type: Hero.Type val initialHandCapacity: Int val favoredDieType: Die.Type val initialDice: Collection<Die> val initialSkills: List<SkillTemplate> val dormantSkills: List<SkillTemplate> fun getDiceCount(type: Die.Type): Pair<Int, Int>? }
ãããŠãããã«2ã€ã®å¥åŠãªç¹ã«æ°ä»ããŸãããŸãããã³ãã¬ãŒãã䜿çšããŠããã°ãšãã¥ãŒããçæããŸããããªãã§ïŒã¯ããããŒããŒã®ã¿ã€ãïŒã¯ã©ã¹ïŒããšã«åæãã¥ãŒãã®ãªã¹ããå³å¯ã«å®çŸ©ãããŠãããããäœæããã»ã¹ãè€éã«ããæå³ã¯ãããŸããã第äºã«ã
getDiceCount()
ããã¯ã©ããªçš®é¡ã®ããã§ããïŒèœã¡çããŠããããã¯
DiceLimit
ãã¥ãŒãã®å¶éãèšå®ãããã®ã§ãããããŠããããã®ãã³ãã¬ãŒãã¯ãç¹å®ã®å€ãããæ確ã«èšé²ããããããªå¥åŠãªåœ¢ã§éžæãããŸãããäŸããèªåã®ç®ã§ç¢ºãããŠãã ããïŒ
class BrawlerHeroTemplate : HeroTemplate { override val type = Hero.Type.BRAWLER override val favoredDieType = PHYSICAL override val initialHandCapacity = 4 override val initialDice = listOf( Die(PHYSICAL, 6), Die(PHYSICAL, 6), Die(PHYSICAL, 4), Die(PHYSICAL, 4), Die(PHYSICAL, 4), Die(PHYSICAL, 4), Die(PHYSICAL, 4), Die(PHYSICAL, 4), Die(SOMATIC, 6), Die(SOMATIC, 4), Die(SOMATIC, 4), Die(SOMATIC, 4), Die(MENTAL, 4), Die(VERBAL, 4), Die(VERBAL, 4) ) override fun getDiceCount(type: Die.Type) = when (type) { PHYSICAL -> 8 to 12 SOMATIC -> 4 to 7 MENTAL -> 1 to 2 VERBAL -> 2 to 4 else -> null } override val initialSkills = listOf( HitSkillTemplate() ) override val dormantSkills = listOf<SkillTemplate>() } class HunterHeroTemplate : HeroTemplate { override val type = Hero.Type.HUNTER override val favoredDieType = SOMATIC override val initialHandCapacity = 5 override val initialDice = listOf( Die(PHYSICAL, 4), Die(PHYSICAL, 4), Die(PHYSICAL, 4), Die(SOMATIC, 6), Die(SOMATIC, 6), Die(SOMATIC, 4), Die(SOMATIC, 4), Die(SOMATIC, 4), Die(SOMATIC, 4), Die(SOMATIC, 4), Die(MENTAL, 6), Die(MENTAL, 4), Die(MENTAL, 4), Die(MENTAL, 4), Die(VERBAL, 4) ) override fun getDiceCount(type: Die.Type) = when (type) { PHYSICAL -> 3 to 5 SOMATIC -> 7 to 11 MENTAL -> 4 to 7 VERBAL -> 1 to 2 else -> null } override val initialSkills = listOf( ShootSkillTemplate() ) override val dormantSkills = listOf<SkillTemplate>() }
ãã ãããžã§ãã¬ãŒã¿ãäœæããåã«ãã¹ãã«ã®ãã³ãã¬ãŒããå®çŸ©ããŸãã
interface SkillTemplate { val type: Skill.Type val maxLevel: Int val modifier1: Int val modifier2: Int val isActive get() = true } class HitSkillTemplate : SkillTemplate { override val type = Skill.Type.HIT override val maxLevel = 3 override val modifier1 = +1 override val modifier2 = +3 } class ShootSkillTemplate : SkillTemplate { override val type = Skill.Type.SHOOT override val maxLevel = 3 override val modifier1 = +0 override val modifier2 = +2 }
æ®å¿µãªãããæµãå°æ¬ãšåãããã«ãããã§ãªãããã¹ãã«ã䜿ãããšã¯ã§ããŸãããæ°ããã¹ãã«ããšã«ã²ãŒã ã¡ã«ãã¯ã¹ã®æ¡åŒµãå¿ èŠã«ãªããã²ãŒã ãšã³ãžã³ã«æ°ããã³ãŒããè¿œå ããŸãããã®ç¹ã§ããŒããŒãããå Žåã§ãç°¡åã§ããããããããã®ããã»ã¹ã¯æœè±¡åã§ããŸãããç§ã¯ãŸã æ¹æ³ãæãã€ããŸãããã¯ããæ£çŽã«èšããšããŸãè©Šã¿ãããŠããŸããã
fun generateSkill(template: SkillTemplate, initialLevel: Int = 1): Skill { val skill = Skill(template.type) skill.isActive = template.isActive skill.level = initialLevel skill.maxLevel = template.maxLevel skill.modifier1 = template.modifier1 skill.modifier2 = template.modifier2 return skill } fun generateHero(type: Hero.Type, name: String = ""): Hero { val template = when (type) { BRAWLER -> BrawlerHeroTemplate() HUNTER -> HunterHeroTemplate() } val hero = Hero(type) hero.name = name hero.isAlive = true hero.favoredDieType = template.favoredDieType hero.hand.capacity = template.initialHandCapacity template.initialDice.forEach { hero.bag.put(it) } for ((t, l) in Die.Type.values().map { it to template.getDiceCount(it) }) { l?.let { hero.addDiceLimit(DiceLimit(t, it.first, it.second, it.first)) } } template.initialSkills .map { generateSkill(it) } .forEach { hero.addSkill(it) } template.dormantSkills .map { generateSkill(it, 0) } .forEach { hero.addDormantSkill(it) } return hero }
ã»ãã®æ°ç¹ãå°è±¡çã§ãããŸããçæã¡ãœããèªäœãããŒããŒã®ã¯ã©ã¹ã«å¿ããŠç®çã®ãã³ãã¬ãŒããéžæããŸãã第äºã«ãååãããã«æå®ããå¿ èŠã¯ãããŸããïŒçæ段éã§ååãç¥ããªãããšããããŸãïŒã第äžã«ãKotlinã¯åäŸã®ãªãéã®ã·ã³ã¿ãã¯ã¹ã·ã¥ã¬ãŒããããããŸãããå°ãæ¥ãããããªãã
ã¹ããã8ãã²ãŒã ãµã€ã¯ã«
æåŸã«ãæãèå³æ·±ãããšã«å°éããŸãã-ã²ãŒã ãµã€ã¯ã«ã®å®è£ ã§ããç°¡åã«èšãã°ã圌ãã¯ãã²ãŒã ãäœããããšãå§ããŸãããå€ãã®åæéçºè ã¯ãã²ãŒã å¶äœãªã©ã®ãã¹ãŠãé€ãããã®æ®µéããæ£ç¢ºã«éå§ããããšããããããŸããç¹ã«ããã¹ãŠã®çš®é¡ã®æå³ã®ãªãå°ããªã¹ããŒã ãpfff ...ããããæ¥ããŸããïŒãŸã æããé ãã§ãïŒããããã£ãŠãããå°ãã¢ããªã³ã°ããŸããã¯ããããäžåºŠã
ã芧ã®ãšãããã²ãŒã ãµã€ã¯ã«ã®ç¹å®ã®ãã©ã°ã¡ã³ãã¯ãäžèšã§åŒçšãããã®ãããæ¡éãã«å°ãããªã£ãŠããŸããã³ãŒã¹ã移è¡ããå°åœ¢ãæ¢çŽ¢ãïŒ2çš®é¡ã®ãã¥ãŒãã®ã¿ã䜿çšããŠäŒè°ã説æããïŒãã¿ãŒã³çµäºæã«ãã¥ãŒããç Žæ£ããããã»ã¹ã®ã¿ãæ€èšããŸãããããŠãã·ããªãªãå®æãããŠè² ããŸããïŒã¯ãããŸã ã²ãŒã ã«åã€ããšã¯ã§ããŸããïŒ-ããããããªãã¯ã©ãã§ããïŒã¿ã€ããŒã¯æ¯ã¿ãŒã³æžå°ãããã®å®äºæã«äœããããå¿ èŠããããŸããããšãã°ãã¡ãã»ãŒãžã衚瀺ããŠã²ãŒã ãçµäºããŸãããã¹ãŠã¯ã«ãŒã«ã«æžãããŠãããšããã§ããããŒããŒãæ»ãã ãå¥ã®ã²ãŒã ãå®äºããå¿ èŠããããŸããã誰ã圌ãã«å±å®³ãå ããããšã¯ãªãã®ã§ããã®ãŸãŸã«ããŸããåã€ããã«ã¯ããã¹ãŠã®ãšãªã¢ãéããå¿ èŠããããŸãããããã1ã€ã ãã§ãã£ãŠãå°é£ã§ãããããã£ãŠããã®ç¬éãæ®ããŸããããã¹ãã¬ãŒããããŠãæå³ããããŸãã-ãšãã»ã³ã¹ãç解ããæ®ãã®æéãåŸã§èªç±æéã«çµããããšãéèŠã§ããããŠããªã-ã²ãŒã ãæžãã«è¡ãããªãã®å€¢ã®ïŒã
ãã®ãããæåã«å¿ èŠãªãªããžã§ã¯ãã決å®ããå¿ èŠããããŸãã
ããŒããŒãºã¹ã¯ãªãããå Žæã
ç§ãã¡ã¯ãã§ã«ãããã®äœæããã»ã¹ãã¬ãã¥ãŒããŸãã-ãããç¹°ãè¿ãããšã¯ããŸãããå°ããªäŸã§äœ¿çšããå°åœ¢ãã¿ãŒã³ã«ã®ã¿æ³šæããŠãã ããã
class TestLocationTemplate : LocationTemplate { override val name = "Test" override val description = "Some Description" override val basicClosingDifficulty = 0 override val enemyCardsCount = 0 override val obstacleCardsCount = 0 override val bagTemplate = BagTemplate().apply { addPlan(2, 2, SingleDieTypeFilter(Die.Type.PHYSICAL)) addPlan(2, 2, SingleDieTypeFilter(Die.Type.SOMATIC)) addPlan(2, 2, SingleDieTypeFilter(Die.Type.MENTAL)) addPlan(2, 2, SingleDieTypeFilter(Die.Type.VERBAL)) addPlan(2, 2, SingleDieTypeFilter(Die.Type.DIVINE)) } override val enemyCardPool = emptyList<EnemyTemplate>() override val obstacleCardPool = emptyList<ObstacleTemplate>() override val specialRules = emptyList<SpecialRule>() }
ã芧ã®ããã«ãããã°ã«ã¯ãããžãã£ãããã¥ãŒãïŒéãç·ã玫ãé»è²ãéïŒãããããŸããããã®å°åã«ã¯æµãé害ç©ã¯ãªããæªåœ¹ãå·ã¯èŠã€ãããŸãããç¹å¥ãªã«ãŒã«ããããŸãã-ãããã®å®è£ ã¯éåžžã«äºæ¬¡çã§ãã
ä¿æããããã¥ãŒãã®ããŒãã
ãŸãã¯ææ¢ãã€ã«ãéãç«æ¹äœãå°åœ¢ã®ããã°ã«å ¥ããã®ã§ããã§ãã¯ã§äœ¿çšãããã䜿çšåŸã«ç¹å¥ãªããŒãã«ä¿ç®¡ãããã§ããŸããããã«ã¯ã¯ã©ã¹ã®ã€ã³ã¹ã¿ã³ã¹ã圹ç«ã¡
Pile
ãŸãã
修食åã
ã€ãŸãããµã€ã³ãã®çµæã«å ç®ãŸãã¯æžç®ããå¿ èŠãããæ°å€ãåãã¥ãŒãã«ã°ããŒãã«ä¿®é£ŸåãŸãã¯åå¥ã®ä¿®é£Ÿåãå®è£ ã§ããŸãã ïŒããæ確ã«ïŒ2çªç®ã®ãªãã·ã§ã³ãéžæãããããåçŽãªã¯ã©ã¹ãäœæããŸã
DiePair
ã
class DiePair(val die: Die, var modifier: Int = 0)
ãšãªã¢å ã®ãã£ã©ã¯ã¿ãŒã®å Žæã
è¯ãæ¹æ³ã§ããã®ç¬éã¯ç¹å¥ãªæ§é ã䜿çšããŠè¿œè·¡ããå¿ èŠããããŸããããšãã°
Map<Location, List<Hero>>
ãåããŒã«ãªãã£ã«çŸåšããããŒããŒã®ãªã¹ããå«ãŸãããã©ãŒã ã®ãããïŒããã³å察ã®æ¹æ³-ç¹å®ã®ããŒããŒãããããŒã«ãªãã£ã決å®ããæ¹æ³ïŒãããªãã¯ãã®ã«ãŒããè¡ãããšã«æ±ºããå Žåã¯ãã¯ã©ã¹ãè¿œå ããããšãå¿ããªãã§ãã ãã
Location
ã¡ãœããã®å®è£ ã
equals()
ãã
hashCode()
ããŸãããã°ãçç±ã説æããå¿ èŠã¯ãããŸãã- ããã®ãšãªã¢ã¯1ã€ã ãã§ãããããŒããŒã¯ãããã©ãã«ãæ®ããªãã®ã§ãããã«æéãç¡é§ã«ããŸããã
äž»äººå ¬ã®æã確èªããŸãã
ã²ãŒã ã®éçšã§ãããŒããŒã¯åžžã«ãã§ãã¯ïŒä»¥äžã§èª¬æïŒãå®è¡ããå¿ èŠããããŸããã€ãŸããæãããã¥ãŒããåãåºããããããã¹ããŒïŒã¢ãã£ãã¡ã€ã¢ãè¿œå ïŒããè€æ°ã®ãã¥ãŒããããå Žåã«çµæãéèšïŒèŠçŽãæ倧/æå°ãå¹³åãªã©ïŒããããããã¹ããŒãšæ¯èŒããŸãå¥ã®ãã¥ãŒãïŒãšãªã¢ã®ããã°ããåãåºããããã®ïŒãããã³çµæã«å¿ããŠã次ã®ã¢ã¯ã·ã§ã³ãå®è¡ããŸãããããããŸã第äžã«ãäž»äººå ¬ãååçã«ãã¹ãã«åæ Œã§ãããã©ãããã€ãŸããå¿ èŠãªãã¥ãŒããæã«æã£ãŠãããã©ãããç解ããå¿ èŠããããŸãããã®ããã«ãã·ã³ãã«ãªã€ã³ã¿ãŒãã§ã€ã¹ãæäŸããŸã
HandFilter
ã
interface HandFilter { fun test(hand: Hand): Boolean }
ã€ã³ã¿ãŒãã§ãŒã¹ã®å®è£ ã¯ãããŒããŒã®æïŒã¯ã©ã¹ãªããžã§ã¯ã
Hand
ïŒãå ¥åãšããŠåãåãããã§ãã¯ã®çµæã«å¿ããŠ
true
ã©ã¡ãããè¿ããŸã
false
ãã²ãŒã ã®ãã©ã°ã¡ã³ãã«ã¯ãåäžã®å®è£ ãå¿ èŠã§ããéãç·ã玫ããŸãã¯é»è²ã®ãã¥ãŒããæºããããå ŽåãããŒããŒã®æã«åãè²ã®ãã¥ãŒãããããã©ãããå€æããå¿ èŠããããŸãã
class SingleDieHandFilter(private vararg val types: Die.Type) : HandFilter { override fun test(hand: Hand) = (0 until hand.dieCount).mapNotNull { hand.dieAt(it) }.any { it.type in types } || (Die.Type.ALLY in types && hand.allyDieCount > 0) }
ã¯ããæ©èœäž»çŸ©ã§ãã
ã¢ã¯ãã£ã/éžæãããã¢ã€ãã ã
äž»äººå ¬ã®æããã¹ãã®å®è¡ã«é©ããŠããããšã確èªããã®ã§ããã¬ã€ã€ãŒã¯ããã®ãã¹ãã«åæ Œãããµã€ã³ãïŒãŸãã¯ãã¥ãŒãïŒãæããéžæããå¿ èŠããããŸãããŸããé©åãªäœçœ®ïŒç®çã®ã¿ã€ãã®ãã¥ãŒããããïŒã匷調衚瀺ïŒãã€ã©ã€ãïŒããå¿ èŠããããŸãã次ã«ãéžæãããã¥ãŒãã«äœããã®æ¹æ³ã§ããŒã¯ãä»ããå¿ èŠããããŸãããããã®èŠä»¶ã®äž¡æ¹ã«ã€ããŠãã¯ã©ã¹ãé©å
HandMask
ã§ãããå®éã«ã¯ãæŽæ°ã®ã»ããïŒéžæãããäœçœ®ã®æ°ïŒãšããããè¿œå ããã³åé€ããããã®ã¡ãœãããå«ãŸããŠããŸãã
class HandMask { private val positions = mutableSetOf<Int>() private val allyPositions = mutableSetOf<Int>() val positionCount get() = positions.size val allyPositionCount get() = allyPositions.size fun addPosition(position: Int) = positions.add(position) fun removePosition(position: Int) = positions.remove(position) fun addAllyPosition(position: Int) = allyPositions.add(position) fun removeAllyPosition(position: Int) = allyPositions.remove(position) fun checkPosition(position: Int) = position in positions fun checkAllyPosition(position: Int) = position in allyPositions fun switchPosition(position: Int) { if (!removePosition(position)) { addPosition(position) } } fun switchAllyPosition(position: Int) { if (!removeAllyPosition(position)) { addAllyPosition(position) } } fun clear() { positions.clear() allyPositions.clear() } }
ãã¯ã€ããã¥ãŒããå¥ã®æã§ä¿ç®¡ãããšãããéªæªãªãã¢ã€ãã¢ã«ã©ã®ããã«èŠããã§ããã®ãããã§ã«è¿°ã¹ãŸããããã®æããã®ããã«ã2ã€ã®ã»ãããåŠçããæ瀺ãããåã¡ãœãããè€è£œããå¿ èŠããããŸãã誰ãããã®èŠä»¶ã®å®è£ ãç°¡çŽ åããæ¹æ³ã«ã€ããŠã¢ã€ãã¢ãæã£ãŠããå ŽåïŒããšãã°ã1ã€ã®ã»ããã䜿çšããŸããããã¯ã€ããã¥ãŒãã®å Žåãã€ã³ããã¯ã¹ã¯100ããå§ãŸã-ãŸãã¯ä»ã®åæ§ã«äžæçãªãã®ïŒ-ã³ã¡ã³ãã§å ±æããŸãã
ãšããã§ãããŒãïŒ
PileMask
ïŒãããã¥ãŒããéžæããã«ã¯ãåæ§ã®ã¯ã©ã¹ãå®è£ ããå¿ èŠããããŸããããã®æ©èœã¯ãã®äŸã®ç¯å²å€ã§ãã
æããã®ãã¥ãŒãã®éžæã
ãã ãã蚱容å¯èœãªäœçœ®ãã匷調ãããã ãã§ã¯äžååã§ãããã¥ãŒããéžæããããã»ã¹ã§ãã®ã匷調ããå€æŽããããšãéèŠã§ããã€ãŸãããã¬ãŒã€ãŒãèªåã®æãããã€ã¹ã1ã€ã ãåãå¿ èŠãããå Žåããã®ãã€ã¹ãéžæãããšãä»ã®ãã¹ãŠã®ããžã·ã§ã³ã«ã¢ã¯ã»ã¹ã§ããªããªããŸããããã«ãå段éã§ããã¬ãŒã€ãŒã®ç®æšã®éæãå¶åŸ¡ããå¿ èŠããããŸããã€ãŸããéžæãããã¥ãŒãã1ã€ãŸãã¯å¥ã®ãã¹ãã«åæ Œããã®ã«ååãã©ãããç解ããå¿ èŠããããŸãããã®ãããªé£ããã¿ã¹ã¯ã«ã¯ãè€éãªã¯ã©ã¹ã®è€éãªã€ã³ã¹ã¿ã³ã¹ãå¿ èŠã§ãã
abstract class HandMaskRule(val hand: Hand) { abstract fun checkMask(mask: HandMask): Boolean abstract fun isPositionActive(mask: HandMask, position: Int): Boolean abstract fun isAllyPositionActive(mask: HandMask, position: Int): Boolean fun getCheckedDice(mask: HandMask): List<Die> { return ((0 until hand.dieCount).filter(mask::checkPosition).map(hand::dieAt)) .plus((0 until hand.allyDieCount).filter(mask::checkAllyPosition).map(hand::allyDieAt)) .filterNotNull() } }
ããªãè€éãªããžãã¯ã§ãããã®ã¯ã©ã¹ãç解ã§ããªãå Žåã¯ãç解ããŠèš±ããŸãããããŠããŸã 説æããããšããŸãããã®ã¯ã©ã¹ã®å®è£ ã¯ãåžžã«åŠç察象ã®æïŒãªããžã§ã¯ã
Hand
ïŒãžã®åç §ãä¿åããŸããåã¡ãœããã¯ãã¹ã¯ïŒ
HandMask
ïŒãåãåããŸããããã¯ãéžæã®çŸåšã®ç¶æ ãåæ ããŸãïŒã©ã®äœçœ®ããã¬ãŒã€ãŒã«ãã£ãŠéžæãããã©ã®äœçœ®ãéžæãããªããïŒããã®ã¡ãœãã
checkMask()
ã¯ãéžæãããã¥ãŒãããã¹ãã«åæ Œããã®ã«ååãã©ãããå ±åããŸãããã®ã¡ãœãã
isPositionActive()
ã¯ãç¹å®ã®äœçœ®ã匷調衚瀺ããå¿ èŠããããã©ããããã®äœçœ®ã«ãã¥ãŒãããã¹ãã«è¿œå ïŒãŸãã¯æ¢ã«éžæãããŠãããã¥ãŒããåé€ïŒã§ãããã©ããã瀺ããŸããæ¹æ³
isAllyPositionActive()
ã¯çœãµã€ã³ãã§ãåãã§ãïŒã¯ããç§ã¯ç¥ã£ãŠããŸããç§ã¯ã°ãã§ãïŒãããŠããã«ããŒã¡ãœãã
getCheckedDice()
åã«ããã¹ã¯ã«å¯Ÿå¿ããæãããã¹ãŠã®ãã¥ãŒãã®ãªã¹ããè¿ããŸã-ããã¯ãããããäžåºŠã«åããããŒãã«ã«æããŠãé¢çœãããã¯ã楜ããããã«å¿ èŠã§ãã
ãã®æœè±¡ã¯ã©ã¹ã®2ã€ã®å®çŸïŒé©ããé©ãïŒïŒãå¿ èŠã§ããæåã¯ãç¹å®ã®ã¿ã€ãïŒçœã§ã¯ãªãïŒã®æ°ãããã¥ãŒããååŸãããšãã«ããã¹ãã«åæ Œããããã»ã¹ãå¶åŸ¡ããŸããèŠããŠããããã«ããã®ãããªãã§ãã¯ã«ã¯ä»»æã®æ°ã®éããã¥ãŒããè¿œå ã§ããŸãã
class StatDieAcquireHandMaskRule(hand: Hand, private val requiredType: Die.Type) : HandMaskRule(hand) { /** * Define how many dice of specified type are currently checked */ private fun checkedDieCount(mask: HandMask) = (0 until hand.dieCount) .filter(mask::checkPosition) .mapNotNull(hand::dieAt) .count { it.type === requiredType } override fun checkMask(mask: HandMask) = (mask.allyPositionCount == 0 && checkedDieCount(mask) == 1) override fun isPositionActive(mask: HandMask, position: Int) = with(hand.dieAt(position)) { when { mask.checkPosition(position) -> true this == null -> false this.type === Die.Type.DIVINE -> true this.type === requiredType && checkedDieCount(mask) < 1 -> true else -> false } } override fun isAllyPositionActive(mask: HandMask, position: Int) = false }
2çªç®ã®å®è£ ã¯ããè€éã§ããã¿ãŒã³çµäºæã«ãµã€ã³ããå¶åŸ¡ããŸãããã®å Žåã2ã€ã®ãªãã·ã§ã³ãå¯èœã§ããæã®ãã¥ãŒãã®æ°ãæ倧蚱容ãµã€ãºïŒå®¹éïŒãè¶ ããå Žåããã¹ãŠã®äœåãªãã¥ãŒãã«å ããŠãå¿ èŠãªæ°ã®è¿œå ã®ãã¥ãŒããç Žæ£ããå¿ èŠããããŸãããµã€ãºãè¶ ããªãå Žåãäœããªã»ããã§ããŸããïŒãŸãã¯ãå¿ èŠã«å¿ããŠãªã»ããã§ããŸãïŒãç°è²ã®ãµã€ã³ããæšãŠãããšã¯ã§ããŸããã
class DiscardExtraDiceHandMaskRule(hand: Hand) : HandMaskRule(hand) { private val minDiceToDiscard = if (hand.dieCount > hand.capacity) min(hand.dieCount - hand.woundCount, hand.dieCount - hand.capacity) else 0 private val maxDiceToDiscard = hand.dieCount - hand.woundCount override fun checkMask(mask: HandMask) = (mask.positionCount in minDiceToDiscard..maxDiceToDiscard) && (mask.allyPositionCount in 0..hand.allyDieCount) override fun isPositionActive(mask: HandMask, position: Int) = when { mask.checkPosition(position) -> true hand.dieAt(position) == null -> false hand.dieAt(position)!!.type == Die.Type.WOUND -> false mask.positionCount < maxDiceToDiscard -> true else -> false } override fun isAllyPositionActive(mask: HandMask, position: Int) = hand.allyDieAt(position) != null }
NezhdanchikïŒã¯ã©ã¹ã«ã¯ã以åã¯ååšããªãã£
Hand
ãããããã£ãçªç¶çŸããŸãã
woundCount
ãå®è£ ã¯èªåã§æžãããšãã§ããŸããç°¡åã§ããåæã«ç·Žç¿ããŸãã
ãã§ãã¯ã«åæ Œã
æçµçã«ãããã«çããŸããããµã€ã³ããæããåãããããããããæããæéã§ãããã¥ãŒãããšã«ããµã€ãºã修食åãã¹ããŒã®çµæãèæ ®ããå¿ èŠããããŸããããã°ããäžåºŠã«åãåºãããšãã§ãããã¥ãŒãã¯1ã€ã ãã§ãããè€æ°ã®ãµã€ã³ããã»ããããŠãããŒã«ã®çµæãéèšã§ããŸããäžè¬çã«ããµã€ã³ãããæœè±¡åããæŠå Žã§ã®è»éã代衚ããŸããããäžæ¹ã§ã¯ãæµãããŸã-圌ã¯ãã äžäººã§ããã圌ã¯åŒ·ããŠå¶æŽã§ããå察ã«ã察æŠçžæã¯åœŒãšåçã®åŒ·ãã§ããããµããŒãããããŸããæŠéã®çµæã¯1åã®çãå°ç«¶ãåãã§æ±ºå®ãããåè ã¯1人ã ãã«ãªãããšãã§ããŸã...
ãã¿ãŸãã äžè¬çãªæŠããã·ãã¥ã¬ãŒãããããã«ãç¹å¥ãªã¯ã©ã¹ãå®è£ ããŸãã
class DieBattleCheck(val method: Method, opponent: DiePair? = null) { enum class Method { SUM, AVG_UP, AVG_DOWN, MAX, MIN } private inner class Wrap(val pair: DiePair, var roll: Int) private infix fun DiePair.with(roll: Int) = Wrap(this, roll) private val opponent: Wrap? = opponent?.with(0) private val heroics = ArrayList<Wrap>() var isRolled = false var result: Int? = null val heroPairCount get() = heroics.size fun getOpponentPair() = opponent?.pair fun getOpponentResult() = when { isRolled -> opponent?.roll ?: 0 else -> throw IllegalStateException("Not rolled yet") } fun addHeroPair(pair: DiePair) { if (method == Method.SUM && heroics.size > 0) { pair.modifier = 0 } heroics.add(pair with 0) } fun addHeroPair(die: Die, modifier: Int) = addHeroPair(DiePair(die, modifier)) fun clearHeroPairs() = heroics.clear() fun getHeroPairAt(index: Int) = heroics[index].pair fun getHeroResultAt(index: Int) = when { isRolled -> when { (index in 0 until heroics.size) -> heroics[index].roll else -> 0 } else -> throw IllegalStateException("Not rolled yet") } fun roll() { fun roll(wrap: Wrap) { wrap.roll = wrap.pair.die.roll() } isRolled = true opponent?.let { roll(it) } heroics.forEach { roll(it) } } fun calculateResult() { if (!isRolled) { throw IllegalStateException("Not rolled yet") } val opponentResult = opponent?.let { it.roll + it.pair.modifier } ?: 0 val stats = heroics.map { it.roll + it.pair.modifier } val heroResult = when (method) { DieBattleCheck.Method.SUM -> stats.sum() DieBattleCheck.Method.AVG_UP -> ceil(stats.average()).toInt() DieBattleCheck.Method.AVG_DOWN -> floor(stats.average()).toInt() DieBattleCheck.Method.MAX -> stats.max() ?: 0 DieBattleCheck.Method.MIN -> stats.min() ?: 0 } result = heroResult - opponentResult } }
åãã¥ãŒãã«ã¯ä¿®é£Ÿåãèšå®ã§ããããããªããžã§ã¯ãã«ããŒã¿ãä¿åããŸã
DiePair
ãã®ããã§ããå®éããããããã¥ãŒããšä¿®é£Ÿåã«å ããŠããã®ã¹ããŒã®çµæãä¿åããå¿ èŠããããŸãïŒãã¥ãŒãèªäœã¯ãã®å€ãçæããŸãããããããã£ã«ã¯ä¿åããŸããïŒããããã£ãŠãåãã¢ãã©ãããŒã§ã©ããããŸãïŒ
Wrap
ïŒãäžçœ®æ³ã«æ³šæããŠãã ãã
with
heheã
ã¯ã©ã¹ã³ã³ã¹ãã©ã¯ã¿ãŒã¯ãéèšã¡ãœããïŒå éšåæã®ã€ã³ã¹ã¿ã³ã¹
Method
ïŒãšå¯ŸæŠçžæïŒååšããªãå ŽåããããŸãïŒãå®çŸ©ããŸããããŒããŒãã¥ãŒãã®ãªã¹ãã¯ãé©åãªæ¹æ³ã䜿çšããŠäœæãããŸãããŸãããã¹ãã«é¢ä¿ãããã¢ãååŸããããã®å€æ°ã®ã¡ãœãããšããããã®ã¹ããŒã®çµæïŒååšããå ŽåïŒãæäŸããŸããã¡ãœããã¯åãã¥ãŒãã®åå
ã¡ãœããã
roll()
åŒã³åºããäžéçµæãä¿åãããã®å®è¡ã®äºå®ããã©ã°ã§ããŒã¯ããŸã
isRolled
ãã¹ããŒã®æçµçµæã¯ããã«èšç®ãããªãããšã«æ³šæããŠãã ããããã
calculateResult()
ã«ã¯ç¹å¥ãªã¡ãœãããããããã®çµæã¯ããããã£ã«æçµå€ãæžã蟌ãããš
result
ã§ãããªããããå¿ èŠãªã®ã§ããïŒåçãªå¹æã®ãããã¡ãœãã
roll()
ã¯æ°åå®è¡ããããã®ãã³ã«ãã¥ãŒãã®é¢ã«ç°ãªãå€ã衚瀺ãããŸãïŒå®éã®ããã«ïŒããããŠããã¥ãŒããããŒãã«ã«èœã¡çãããšãã«ã®ã¿
ã²ãŒã ãšã³ãžã³ã®ç¶æ ã
æŽç·Žããããªããžã§ã¯ããæŽçãããããã·ã³ãã«ã«ãªããŸãããã²ãŒã ãšã³ãžã³ã®çŸåšã®ãé²è¡ããããã眮ãããŠããã¹ããŒãžãŸãã¯ãã§ãŒãºãå¶åŸ¡ããå¿ èŠããããšèšãããšã¯ã倧ããªçºèŠã§ã¯ãããŸãããããã«ã¯ç¹å¥ãªåæã圹ç«ã¡ãŸãã
enum class GamePhase { SCENARIO_START, HERO_TURN_START, HERO_TURN_END, LOCATION_BEFORE_EXPLORATION, LOCATION_ENCOUNTER_STAT, LOCATION_ENCOUNTER_DIVINE, LOCATION_AFTER_EXPLORATION, GAME_LOSS }
å®éãããã«å€ãã®ãã§ãŒãºããããŸããããã®äŸã§äœ¿çšããããã§ãŒãºã®ã¿ãéžæããŸãããã²ãŒã ãšã³ãžã³ã®ãã§ãŒãºãå€æŽãã
changePhaseX()
ã«
X
ã¯ãäžèšã®ãªã¹ãã®å€ã§ããã¡ãœããã䜿çšããŸãããããã®æ¹æ³ã§ã¯ããšã³ãžã³ã®ãã¹ãŠã®å éšå€æ°ã¯ã察å¿ãããã§ãŒãºã®éå§ã«é©ããå€ã«åæžãããŸããããã以éã¯ããã«åæžãããŸãã
ã¡ãã»ãŒãž
ã²ãŒã ãšã³ãžã³ã®ç¶æ ãç¶æããã ãã§ã¯äžååã§ãããŠãŒã¶ãŒãäœããã®åœ¢ã§åœŒã«ã€ããŠéç¥ããããšãéèŠã§ã-ãããªããã°ãåŸè ã¯åœŒã®ç»é¢ã§äœãèµ·ãã£ãŠããããã©ã®ããã«ç¥ãã®ã§ããããïŒãã®ããããã1ã€ãªã¹ããå¿ èŠã§ãã
enum class StatusMessage { EMPTY, CHOOSE_DICE_PERFORM_CHECK, END_OF_TURN_DISCARD_EXTRA, END_OF_TURN_DISCARD_OPTIONAL, CHOOSE_ACTION_BEFORE_EXPLORATION, CHOOSE_ACTION_AFTER_EXPLORATION, ENCOUNTER_PHYSICAL, ENCOUNTER_SOMATIC, ENCOUNTER_MENTAL, ENCOUNTER_VERBAL, ENCOUNTER_DIVINE, DIE_ACQUIRE_SUCCESS, DIE_ACQUIRE_FAILURE, GAME_LOSS_OUT_OF_TIME }
ã芧ã®ãšããããã®äŸã®ãã¹ãŠã®å¯èœãªç¶æ ã¯ããã®åæã®å€ã«ãã£ãŠèšè¿°ãããŠããŸãããããã®ããããã«ã€ããŠãç»é¢ã«è¡šç€ºãããããã¹ãè¡ãæäŸãããŸãïŒãã ãã
EMPTY
ããã¯ç¹å¥ãªæå³ã§ãïŒããããã«ã€ããŠã¯å°ãåŸã§åŠç¿ããŸãã
ã¢ã¯ã·ã§ã³
ãŠãŒã¶ãŒãšã²ãŒã ãšã³ãžã³éã®éä¿¡ã«ã¯ãåçŽãªã¡ãã»ãŒãžã§ã¯äžååã§ãããŸãã圌ãçŸæç¹ã§åãããšãã§ããæåã®ã¢ã¯ã·ã§ã³ãç¥ãããããšãéèŠã§ãïŒèª¿æ»ããããã¯ã®ééã移åã®å®äº-ããã¯ãã¹ãŠè¯ãããšã§ãïŒããããè¡ãããã«ãç¹å¥ãªã¯ã©ã¹ãéçºããŸãã
class Action( val type: Type, var isEnabled: Boolean = true, val data: Int = 0 ) { enum class Type { NONE, //Blank type CONFIRM, //Confirm some action CANCEL, //Cancel action HAND_POSITION, //Some position in hand HAND_ALLY_POSITION, //Some ally position in hand EXPLORE_LOCATION, //Explore current location FINISH_TURN, //Finish current turn ACQUIRE, //Acquire (DIVINE) die FORFEIT, //Remove die from game HIDE, //Put die into bag DISCARD, //Put die to discard pile } }
å éšåæ
Type
ã¯ãå®è¡ãããã¢ã¯ã·ã§ã³ã®ã¿ã€ããèšè¿°ããŸãããã®ãã£ãŒã«ãã¯
isEnabled
ãéã¢ã¯ãã£ãç¶æ ã®ã¢ã¯ã·ã§ã³ã衚瀺ããããã«å¿ èŠã§ããã€ãŸãããã®ã¢ã¯ã·ã§ã³ã¯é垞䜿çšå¯èœã§ããããçŸæç¹ã§ã¯äœããã®çç±ã§å®è¡ã§ããªãããšãå ±åããããšã§ãïŒãã®ãããªè¡šç€ºã¯ãã¢ã¯ã·ã§ã³ããŸã£ãã衚瀺ãããªãå Žåãããã¯ããã«æçã§ãïŒãããããã£
data
ïŒäžéšã®ã¿ã€ãã®ã¢ã¯ã·ã§ã³ã«å¿ èŠïŒã«ã¯ãè¿œå ã®è©³çŽ°ïŒãŠãŒã¶ãŒãéžæããäœçœ®ã®ã€ã³ããã¯ã¹ããªã¹ãããéžæããã¢ã€ãã ã®çªå·ãªã©ïŒãå ±åããç¹å¥ãªå€ãæ ŒçŽãããŸãã
ã¯ã©ã¹
Action
ã²ãŒã ãšã³ãžã³ãšå ¥åºåã·ã¹ãã ã®éã®äž»èŠãªãã€ã³ã¿ãŒãã§ãŒã¹ãã§ãïŒä»¥äžã«ã€ããŠïŒãå€ãã®å Žåãããã€ãã®ã¢ã¯ã·ã§ã³ãããã®ã§ïŒããã§ãªããã°ããªãéžæããã®ã§ããïŒïŒããããã¯ã°ã«ãŒãïŒãªã¹ãïŒã«çµåãããŸããæšæºã®ã³ã¬ã¯ã·ã§ã³ã䜿çšãã代ããã«ãç¬èªã®æ¡åŒµã³ã¬ã¯ã·ã§ã³ãäœæããŸãã
class ActionList : Iterable<Action> { private val actions = mutableListOf<Action>() val size get() = actions.size fun add(action: Action): ActionList { actions.add(action) return this } fun add(type: Action.Type, enabled: Boolean = true): ActionList { add(Action(type, enabled)) return this } fun addAll(actions: ActionList): ActionList { actions.forEach { add(it) } return this } fun remove(type: Action.Type): ActionList { actions.removeIf { it.type == type } return this } operator fun get(index: Int) = actions[index] operator fun get(type: Action.Type) = actions.find { it.type == type } override fun iterator(): Iterator<Action> = ActionListIterator() private inner class ActionListIterator : Iterator<Action> { private var position = -1 override fun hasNext() = (actions.size > position + 1) override fun next() = actions[++position] } companion object { val EMPTY get() = ActionList() } }
ãã®ã¯ã©ã¹ã«ã¯ããªã¹ãã«ã¢ã¯ã·ã§ã³ãè¿œå ããã³åé€ããããã®ããŸããŸãªã¡ãœããïŒãã§ãŒã³åå¯èœïŒãããã³ã€ã³ããã¯ã¹ãšã¿ã€ãã®äž¡æ¹ãååŸããããã®ããŸããŸãªã¡ãœãããå«ãŸããŸãïŒããªãŒããŒããŒããã«æ³šæããŠãã ãã
get()
-è§æ¬åŒ§æŒç®åã¯ãªã¹ãã«é©çšã§ããŸãïŒãã€ã³ã¿ãŒãã§ãŒã¹ã®å®è£ ã«ããã
Iterator
ã¯ã©ã¹ã§
ã¹ã¯ãªãŒã³ã
æåŸã«ãçŸåšè¡šç€ºãããŠããããŸããŸãªçš®é¡ã®ã³ã³ãã³ãã説æããå¥ã®ãªã¹ã...ããªãã¯ç§ãèŠãŠç®ãç¬ããŸãããã®ã¯ã©ã¹ãããæ確ã«èª¬æããæ¹æ³ãèãå§ãããšããç§ã¯æ¬åœã«äœãç解ã§ããªãã£ãã®ã§ãããŒãã«ã«é ãæã¡ãŸãããèªåèªèº«ãç解ããç§ã¯é¡ã£ãŠããŸãã
enum class GameScreen { HERO_TURN_START, LOCATION_INTERIOR, GAME_LOSS }
äŸã§äœ¿çšãããŠãããã®ã®ã¿ãéžæããŸãããåå¥ã®ã¬ã³ããªã³ã°æ¹æ³ããããã®ããããã«æäŸãããŸã...ç§ã¯åã³äžå¯è§£ã«èª¬æããŸãã
ã衚瀺ãããã³ãå ¥åãã
ãããŠä»ãç§ãã¡ã¯ã€ãã«æãéèŠãªãã€ã³ãã«å°éããŸãã-ã²ãŒã ãšã³ãžã³ãšãŠãŒã¶ãŒïŒãã¬ã€ã€ãŒïŒãšã®çžäºäœçšã§ãããã®ãããªé·ãå°å ¥ã«ãŸã 飜ããŠããªãå Žåã¯ãããã2ã€ã®éšåãæ©èœçã«åé¢ããããšã«åæããããšãããããèŠããŠããã§ãããããããã£ãŠãI / Oã·ã¹ãã ã®ç¹å®ã®å®è£ ã®ä»£ããã«ãã€ã³ã¿ãŒãã§ã€ã¹ã®ã¿ãæäŸããŸããããæ£ç¢ºã«ã¯ã2ã
æåã®ã€ã³ã¿ãŒãã§ãŒã¹
GameRenderer
ãç»é¢ã«ç»åã衚瀺ããããã«èšèšãããŠããŸããç»é¢ãµã€ãºãç¹å®ã®ã°ã©ãã£ãã¯ã©ã€ãã©ãªãªã©ããæœè±¡åãããŠããããšãæãåºããŠãã ããã ãdraw me thisããšããã³ãã³ããéä¿¡ããã ãã§ããç»é¢ã«ã€ããŠã®äžæçãªäŒè©±ãç解ããŠãã人ã¯ããããã®ç»é¢ã®ãããããã€ã³ã¿ãŒãã§ã€ã¹å ã«ç¬èªã®ã¡ãœãããæã£ãŠããããšããã§ã«æšæž¬ããŠããŸãã
interface GameRenderer { fun drawHeroTurnStart(hero: Hero) fun drawLocationInteriorScreen( location: Location, heroesAtLocation: List<Hero>, timer: Int, currentHero: Hero, battleCheck: DieBattleCheck?, encounteredDie: DiePair?, pickedDice: HandMask, activePositions: HandMask, statusMessage: StatusMessage, actions: ActionList ) fun drawGameLoss(message: StatusMessage) }
è¿œå ã®èª¬æã¯å¿ èŠãªããšæããŸã-転éããããã¹ãŠã®ãªããžã§ã¯ãã®ç®çã¯äžèšã§è©³çŽ°ã«èª¬æãããŠããŸãã
ãŠãŒã¶ãŒå ¥åã®ããã«ãå¥ã®ã€ã³ã¿ãŒãã§ã€ã¹ãå®è£ ããŸã-
GameInteractor
ïŒã¯ããã¹ãã«ãã§ãã¯ã¹ã¯ãªããã¯åžžã«ãã®åèªã匷調ããŸãããããã¯âŠãšæãããŸããïŒã圌ã®æ¹æ³ã¯ãããŸããŸãªç¶æ³ã§å¿ èŠãªã³ãã³ãããã¬ã€ã€ãŒã«èŠæ±ããŸãïŒææ¡ããããã®ã®ãªã¹ãããã¢ã¯ã·ã§ã³ãéžæãããªã¹ãããèŠçŽ ãéžæããæãããã¥ãŒããéžæããå°ãªããšãäœããæŒãã ããªã©ãå ¥åã¯åæçã§ããïŒã²ãŒã ã¯æ®µéçã§ããïŒããšãã€ãŸãããŠãŒã¶ãŒããªã¯ãšã¹ãã«å¿çãããŸã§ã²ãŒã ãµã€ã¯ã«ã®å®è¡ãäžæãããããšã«æ³šæããŠãã ããã
interface GameInteractor{ fun anyInput() fun pickAction(list: ActionList): Action fun pickDiceFromHand(activePositions: HandMask, actions: ActionList): Action }
æåŸã®æ¹æ³ã«ã€ããŠããå°ããååã瀺ãããã«ãfromã¯ããŠãŒã¶ãŒãæãããã¥ãŒããéžæããããã«æåŸ ãããªããžã§ã¯ã
HandMask
-ã¢ã¯ãã£ããªããžã·ã§ã³ã®æ°ãæäŸããŸããã¡ãœããã®å®è¡ã¯ããããã®ããã€ããéžæããããŸã§ç¶ããŸãããã®å Žåãã¡ãœããã¯ããã£ãŒã«ãå ã®éžæãããäœçœ®ã®çªå·ãšãšãã«ã¿ã€ã
HAND_POSITION
ïŒãŸãã¯
HAND_ALLY_POSITION
mdaïŒã®ã¢ã¯ã·ã§ã³ãè¿ããŸã
data
ãããã«ããªããžã§ã¯ãããå¥ã®ã¢ã¯ã·ã§ã³ïŒ
CONFIRM
ãŸãã¯ãªã©
CANCEL
ïŒãéžæããããšãã§ããŸã
ActionList
ãå ¥åã¡ãœããã®å®è£ ã§ã¯ããã£ãŒã«ãã
isEnabled
èšå®ãããŠããç¶æ³ãåºå¥ã
false
ããã®ãããªã¢ã¯ã·ã§ã³ã®ãŠãŒã¶ãŒå ¥åãç¡èŠããå¿ èŠããããŸãã
ã²ãŒã ãšã³ãžã³ã¯ã©ã¹ã
ç§ãã¡ã¯ä»äºã«å¿ èŠãªãã¹ãŠã®è¯ããæãæ¥ãããããŠå®è£ ããããšã³ãžã³ãèæ ®ããŸãããã¯ã©ã¹ãäœæãã
Game
次ã®ã³ã³ãã³ãïŒ
ç³ãèš³ãããŸããããããã¯å°è±¡çãªäººã«ã¯è¡šç€ºãããŸããã
class Game( private val renderer: GameRenderer, private val interactor: GameInteractor, private val scenario: Scenario, private val locations: List<Location>, private val heroes: List<Hero>) { private var timer = 0 private var currentHeroIndex = -1 private lateinit var currentHero: Hero private lateinit var currentLocation: Location private val deterrentPile = Pile() private var encounteredDie: DiePair? = null private var battleCheck: DieBattleCheck? = null private val activeHandPositions = HandMask() private val pickedHandPositions = HandMask() private var phase: GamePhase = GamePhase.SCENARIO_START private var screen = GameScreen.SCENARIO_INTRO private var statusMessage = StatusMessage.EMPTY private var actions: ActionList = ActionList.EMPTY fun start() { if (heroes.isEmpty()) throw IllegalStateException("Heroes list is empty!") if (locations.isEmpty()) throw IllegalStateException("Location list is empty!") heroes.forEach { it.isAlive = true } timer = scenario.initialTimer //Draw initial hand for each hero heroes.forEach(::drawInitialHand) //First hero turn currentHeroIndex = -1 changePhaseHeroTurnStart() processCycle() } private fun drawInitialHand(hero: Hero) { val hand = hero.hand val favoredDie = hero.bag.drawOfType(hero.favoredDieType) hand.addDie(favoredDie!!) refillHeroHand(hero, false) } private fun refillHeroHand(hero: Hero, redrawScreen: Boolean = true) { val hand = hero.hand while (hand.dieCount < hand.capacity && hero.bag.size > 0) { val die = hero.bag.draw() hand.addDie(die) if (redrawScreen) { Audio.playSound(Sound.DIE_DRAW) drawScreen() Thread.sleep(500) } } } private fun changePhaseHeroTurnEnd() { battleCheck = null encounteredDie = null phase = GamePhase.HERO_TURN_END //Discard extra dice (or optional dice) val hand = currentHero.hand pickedHandPositions.clear() activeHandPositions.clear() val allowCancel = if (hand.dieCount > hand.capacity) { statusMessage = StatusMessage.END_OF_TURN_DISCARD_EXTRA false } else { statusMessage = StatusMessage.END_OF_TURN_DISCARD_OPTIONAL true } val result = pickDiceFromHand(DiscardExtraDiceHandMaskRule(hand), allowCancel) statusMessage = StatusMessage.EMPTY actions = ActionList.EMPTY if (result) { val discardDice = collectPickedDice(hand) val discardAllyDice = collectPickedAllyDice(hand) pickedHandPositions.clear() (discardDice + discardAllyDice).forEach { die -> Audio.playSound(Sound.DIE_DISCARD) currentHero.discardDieFromHand(die) drawScreen() Thread.sleep(500) } } pickedHandPositions.clear() //Replenish hand refillHeroHand(currentHero) changePhaseHeroTurnStart() } private fun changePhaseHeroTurnStart() { phase = GamePhase.HERO_TURN_START screen = GameScreen.HERO_TURN_START //Tick timer timer-- if (timer < 0) { changePhaseGameLost(StatusMessage.GAME_LOSS_OUT_OF_TIME) return } //Pick next hero do { currentHeroIndex = ++currentHeroIndex % heroes.size currentHero = heroes[currentHeroIndex] } while (!currentHero.isAlive) currentLocation = locations[0] //Setup Audio.playMusic(Music.SCENARIO_MUSIC_1) Audio.playSound(Sound.TURN_START) } private fun changePhaseLocationBeforeExploration() { phase = GamePhase.LOCATION_BEFORE_EXPLORATION screen = GameScreen.LOCATION_INTERIOR encounteredDie = null battleCheck = null pickedHandPositions.clear() activeHandPositions.clear() statusMessage = StatusMessage.CHOOSE_ACTION_BEFORE_EXPLORATION actions = ActionList() actions.add(Action.Type.EXPLORE_LOCATION, checkLocationCanBeExplored(currentLocation)) actions.add(Action.Type.FINISH_TURN) } private fun changePhaseLocationEncounterStatDie() { Audio.playSound(Sound.ENCOUNTER_STAT) phase = GamePhase.LOCATION_ENCOUNTER_STAT screen = GameScreen.LOCATION_INTERIOR battleCheck = null pickedHandPositions.clear() activeHandPositions.clear() statusMessage = when (encounteredDie!!.die.type) { Die.Type.PHYSICAL -> StatusMessage.ENCOUNTER_PHYSICAL Die.Type.SOMATIC -> StatusMessage.ENCOUNTER_SOMATIC Die.Type.MENTAL -> StatusMessage.ENCOUNTER_MENTAL Die.Type.VERBAL -> StatusMessage.ENCOUNTER_VERBAL else -> throw AssertionError("Should not happen") } val canAttemptCheck = checkHeroCanAttemptStatCheck(currentHero, encounteredDie!!.die.type) actions = ActionList() actions.add(Action.Type.HIDE, canAttemptCheck) actions.add(Action.Type.DISCARD, canAttemptCheck) actions.add(Action.Type.FORFEIT) } private fun changePhaseLocationEncounterDivineDie() { Audio.playSound(Sound.ENCOUNTER_DIVINE) phase = GamePhase.LOCATION_ENCOUNTER_DIVINE screen = GameScreen.LOCATION_INTERIOR battleCheck = null pickedHandPositions.clear() activeHandPositions.clear() statusMessage = StatusMessage.ENCOUNTER_DIVINE actions = ActionList() actions.add(Action.Type.ACQUIRE, checkHeroCanAcquireDie(currentHero, Die.Type.DIVINE)) actions.add(Action.Type.FORFEIT) } private fun changePhaseLocationAfterExploration() { phase = GamePhase.LOCATION_AFTER_EXPLORATION screen = GameScreen.LOCATION_INTERIOR encounteredDie = null battleCheck = null pickedHandPositions.clear() activeHandPositions.clear() statusMessage = StatusMessage.CHOOSE_ACTION_AFTER_EXPLORATION actions = ActionList() actions.add(Action.Type.FINISH_TURN) } private fun changePhaseGameLost(message: StatusMessage) { Audio.stopMusic() Audio.playSound(Sound.GAME_LOSS) phase = GamePhase.GAME_LOSS screen = GameScreen.GAME_LOSS statusMessage = message } private fun pickDiceFromHand(rule: HandMaskRule, allowCancel: Boolean = true, onEachLoop: (() -> Unit)? = null): Boolean { //Preparations pickedHandPositions.clear() actions = ActionList().add(Action.Type.CONFIRM, false) if (allowCancel) { actions.add(Action.Type.CANCEL) } val hand = rule.hand while (true) { //Recurring action onEachLoop?.invoke() //Define success condition val canProceed = rule.checkMask(pickedHandPositions) actions[Action.Type.CONFIRM]?.isEnabled = canProceed //Prepare active hand commands activeHandPositions.clear() (0 until hand.dieCount) .filter { rule.isPositionActive(pickedHandPositions, it) } .forEach { activeHandPositions.addPosition(it) } (0 until hand.allyDieCount) .filter { rule.isAllyPositionActive(pickedHandPositions, it) } .forEach { activeHandPositions.addAllyPosition(it) } //Draw current phase drawScreen() //Process interaction result val result = interactor.pickDiceFromHand(activeHandPositions, actions) when (result.type) { Action.Type.CONFIRM -> if (canProceed) { activeHandPositions.clear() return true } Action.Type.CANCEL -> if (allowCancel) { activeHandPositions.clear() pickedHandPositions.clear() return false } Action.Type.HAND_POSITION -> { Audio.playSound(Sound.DIE_PICK) pickedHandPositions.switchPosition(result.data) } Action.Type.HAND_ALLY_POSITION -> { Audio.playSound(Sound.DIE_PICK) pickedHandPositions.switchAllyPosition(result.data) } else -> throw AssertionError("Should not happen") } } } private fun collectPickedDice(hand: Hand) = (0 until hand.dieCount) .filter(pickedHandPositions::checkPosition) .mapNotNull(hand::dieAt) private fun collectPickedAllyDice(hand: Hand) = (0 until hand.allyDieCount) .filter(pickedHandPositions::checkAllyPosition) .mapNotNull(hand::allyDieAt) private fun performStatDieAcquireCheck(shouldDiscard: Boolean): Boolean { //Prepare check battleCheck = DieBattleCheck(DieBattleCheck.Method.SUM, encounteredDie) pickedHandPositions.clear() statusMessage = StatusMessage.CHOOSE_DICE_PERFORM_CHECK val hand = currentHero.hand //Try to pick dice from performer's hand if (!pickDiceFromHand(StatDieAcquireHandMaskRule(currentHero.hand, encounteredDie!!.die.type), true) { battleCheck!!.clearHeroPairs() (collectPickedDice(hand) + collectPickedAllyDice(hand)) .map { DiePair(it, if (shouldDiscard) 1 else 0) } .forEach(battleCheck!!::addHeroPair) }) { battleCheck = null pickedHandPositions.clear() return false } //Remove dice from hand collectPickedDice(hand).forEach { hand.removeDie(it) } collectPickedAllyDice(hand).forEach { hand.removeDie(it) } pickedHandPositions.clear() //Perform check Audio.playSound(Sound.BATTLE_CHECK_ROLL) for (i in 0..7) { battleCheck!!.roll() drawScreen() Thread.sleep(100) } battleCheck!!.calculateResult() val result = battleCheck?.result ?: -1 val success = result >= 0 //Process dice which participated in the check (0 until battleCheck!!.heroPairCount) .map(battleCheck!!::getHeroPairAt) .map(DiePair::die) .forEach { d -> if (d.type === Die.Type.DIVINE) { currentHero.hand.removeDie(d) deterrentPile.put(d) } else { if (shouldDiscard) { currentHero.discardDieFromHand(d) } else { currentHero.hideDieFromHand(d) } } } //Show message to user Audio.playSound(if (success) Sound.BATTLE_CHECK_SUCCESS else Sound.BATTLE_CHECK_FAILURE) statusMessage = if (success) StatusMessage.DIE_ACQUIRE_SUCCESS else StatusMessage.DIE_ACQUIRE_FAILURE actions = ActionList.EMPTY drawScreen() interactor.anyInput() //Clean up battleCheck = null //Resolve consequences of the check if (success) { Audio.playSound(Sound.DIE_DRAW) currentHero.hand.addDie(encounteredDie!!.die) } return true } private fun processCycle() { while (true) { drawScreen() when (phase) { GamePhase.HERO_TURN_START -> { interactor.anyInput() changePhaseLocationBeforeExploration() } GamePhase.GAME_LOSS -> { interactor.anyInput() return } GamePhase.LOCATION_BEFORE_EXPLORATION -> when (interactor.pickAction(actions).type) { Action.Type.EXPLORE_LOCATION -> { val die = currentLocation.bag.draw() encounteredDie = DiePair(die, 0) when (die.type) { Die.Type.PHYSICAL, Die.Type.SOMATIC, Die.Type.MENTAL, Die.Type.VERBAL -> changePhaseLocationEncounterStatDie() Die.Type.DIVINE -> changePhaseLocationEncounterDivineDie() else -> TODO("Others") } } Action.Type.FINISH_TURN -> changePhaseHeroTurnEnd() else -> throw AssertionError("Should not happen") } GamePhase.LOCATION_ENCOUNTER_STAT -> { val type = interactor.pickAction(actions).type when (type) { Action.Type.DISCARD, Action.Type.HIDE -> { performStatDieAcquireCheck(type === Action.Type.DISCARD) changePhaseLocationAfterExploration() } Action.Type.FORFEIT -> { Audio.playSound(Sound.DIE_REMOVE) changePhaseLocationAfterExploration() } else -> throw AssertionError("Should not happen") } } GamePhase.LOCATION_ENCOUNTER_DIVINE -> when (interactor.pickAction(actions).type) { Action.Type.ACQUIRE -> { Audio.playSound(Sound.DIE_DRAW) currentHero.hand.addDie(encounteredDie!!.die) changePhaseLocationAfterExploration() } Action.Type.FORFEIT -> { Audio.playSound(Sound.DIE_REMOVE) changePhaseLocationAfterExploration() } else -> throw AssertionError("Should not happen") } GamePhase.LOCATION_AFTER_EXPLORATION -> when (interactor.pickAction(actions).type) { Action.Type.FINISH_TURN -> changePhaseHeroTurnEnd() else -> throw AssertionError("Should not happen") } else -> throw AssertionError("Should not happen") } } } private fun drawScreen() { when (screen) { GameScreen.HERO_TURN_START -> renderer.drawHeroTurnStart(currentHero) GameScreen.LOCATION_INTERIOR -> renderer.drawLocationInteriorScreen(currentLocation, heroes, timer, currentHero, battleCheck, encounteredDie, null, pickedHandPositions, activeHandPositions, statusMessage, actions) GameScreen.GAME_LOSS -> renderer.drawGameLoss(statusMessage) } } private fun checkLocationCanBeExplored(location: Location) = location.isOpen && location.bag.size > 0 private fun checkHeroCanAttemptStatCheck(hero: Hero, type: Die.Type): Boolean { return hero.isAlive && SingleDieHandFilter(type).test(hero.hand) } private fun checkHeroCanAcquireDie(hero: Hero, type: Die.Type): Boolean { if (!hero.isAlive) { return false } return when (type) { Die.Type.ALLY -> hero.hand.allyDieCount < MAX_HAND_ALLY_SIZE else -> hero.hand.dieCount < MAX_HAND_SIZE } } }
ã¡ãœãã
start()
-ã²ãŒã ãžã®ãšã³ããªãã€ã³ããããã§ã¯ãå€æ°ãåæåãããããŒããŒãèšéãããæãç«æ¹äœã§æºããããã¬ããŒã¿ãŒã¯ããããé¢ããã«ã¡ã©ã§èŒããŸããã¡ã€ã³ãµã€ã¯ã«ã¯æ¯åèµ·åããããã®åŸã¯åæ¢ã§ããªããªããŸããã¡ãœãã
drawInitialHand()
ã¯ããèªäœãç©èªã£ãŠããŸãïŒ
drawOfType()
ã¯ã©ã¹ã¡ãœããã®ã³ãŒããèæ ®ããªãã£ãããã§ã
Bag
ããäžç·ã«é·ãéã®ããé²ãã åŸããã®ã³ãŒããèªåã§æžãããšãã§ããŸãïŒããã®ã¡ãœããã«
refillHeroHand()
ã¯2ã€ã®ãªãã·ã§ã³ïŒåŒæ°ã®å€ã«å¿ããŠ
redrawScreen
ïŒããããŸãïŒé«éã§éãïŒã²ãŒã ã®éå§æã«ãã¹ãŠã®ããŒããŒã®æãæºããå¿ èŠãããå ŽåïŒãããã³å€§å£°ã§ããã¹ã§ã移åã®çµããã«ããã°ãããã¥ãŒããäžå¯§ã«åãé€ããæãé©åãªãµã€ãºã«ããå¿ èŠããããŸãã
次ã§å§ãŸãååã®ã¡ãœããã®æ
changePhase
ã-ãã§ã«è¿°ã¹ãããã«ããããã¯çŸåšã®ã²ãŒã ãã§ãŒãºãå€æŽãã圹å²ãæãããã²ãŒã å€æ°ã®å¯Ÿå¿ããå€ã®å²ãåœãŠã«åŸäºããŠããŸããããã§ã¯
actions
ããã®ãã§ãŒãºã«ç¹æã®ã¢ã¯ã·ã§ã³ãè¿œå ããããªã¹ãã圢æãããŸããäžè¬åããã圢åŒ
ã®ãŠãŒãã£ãªãã£ã¡ãœãã
pickDiceFromHand()
ã¯ãæãããã¥ãŒããéžæããŸãããã
HandMaskRule
ã§ã¯ãéžæèŠåãå®çŸ©ãã䜿ãæ £ããã¯ã©ã¹ã®ãªããžã§ã¯ããæž¡ãããŸãããŸããéžæãæåŠãã
allowCancel
æ©èœïŒïŒã
onEachLoop
ããã³éžæãããã¥ãŒãã®ãªã¹ããå€æŽããããã³ã«ã³ãŒããåŒã³åºãå¿ èŠãããé¢æ°ïŒéåžžã¯ç»é¢ã®åæç»ïŒã瀺ããŸãããã®ã¡ãœããã«ãã£ãŠéžæããããã¥ãŒãã¯ã
collectPickedDice()
ããã³ã¡ãœããã䜿çšããŠæã§çµã¿ç«ãŠãããšãã§ããŸã
collectPickedAllyDice()
ã
å¥ã®ãŠãŒãã£ãªãã£ã¡ãœãã
performStatDieAcquireCheck()
æ°ãããã¥ãŒããååŸããããã®ãã¹ãã«åæ ŒããããŒããŒãå®å šã«å®è£ ããŸãããã®ã¡ãœããã®äžå¿çãªåœ¹å²ã¯ããªããžã§ã¯ãã«ãã£ãŠæããããŸã
DieBattleCheck
ãããã»ã¹ã¯ãã¡ãœããã«ãããã¥ãŒãã®éžæããå§ãŸããŸã
pickDiceFromHand()
ïŒåã¹ãããã§ããåå è ãã®ãªã¹ããæŽæ°ãããŸã
DieBattleCheck
ïŒãéžæãããã¥ãŒããæããåé€ããããã®åŸãããŒã«ããçºçããŸã-åãã€ã¯å€ãæŽæ°ãïŒé£ç¶ããŠ8åïŒããã®åŸãçµæãèšç®ãããŠè¡šç€ºãããŸããããŒã«ã«æåãããšãæ°ãããã€ã¹ãããŒããŒã®æã«èœã¡ãŸãããã¹ãã«åå ããŠãããã¥ãŒãã¯ãä¿æãããŠããïŒéã®å Žå
shouldDiscard = true
ïŒããç Žæ£ãããŠããïŒã®å Žå
shouldDiscard = false
ïŒããããã°ã®äžã«é ãããŠããŸãïŒã®å ŽåïŒã
äž»ãªæ¹æ³
processCycle()
ç¡éã«ãŒãïŒç§ã¯å€±ç¥ããã«å°ããŸãïŒãå«ãŸããŸããæåã«ç»é¢ãæç»ããã次ã«ãŠãŒã¶ãŒã«å ¥åãæ±ãããã次ã«ãã®å ¥åãåŠçãããŸã-ãã®åŸã®ãã¹ãŠã®çµæãã¡ãœãã
drawScreen()
ã¯ã
GameRenderer
çŸåšã®å€ã«å¿ããŠãç®çã®ã€ã³ã¿ãŒãã§ã€ã¹ã¡ãœãããåŒã³åºãã
screen
å¿ èŠãªãªããžã§ã¯ããå ¥åã«æž¡ããŸãã
ãŸãããã®ã¯ã©ã¹ã«ã¯ãããã€ãã®ãã«ããŒã¡ãœãããå«ãŸããŠããŸã
checkLocationCanBeExplored()
ã
checkHeroCanAttemptStatCheck()
ãš
checkHeroCanAcquireDie()
ã圌ãã®ååã¯åœŒãèªèº«ã®ããã«èªã£ãŠããŸãããããã£ãŠãç§ãã¡ã¯ãããã«ã€ããŠè©³ããã¯è¿°ã¹ãŸããã
Audio
èµ€ãæ³¢ç·ã§äžç·ãåŒãããã¯ã©ã¹ã¡ãœããåŒã³åºãããããŸãããšããããã³ã¡ã³ãããŠãã ããããã®ç®çã¯åŸã§æ€èšããŸãã
誰ãäœããŸã£ããç解ããŠããªããããã«å³ã瀺ããŸãïŒããããããããããã«ïŒã
以äžã§ãã²ãŒã ã®æºåã¯å®äºã§ãïŒheheïŒãæ®ãã®äºçŽ°ãªäºã¯æ®ããŸããããããã«ã€ããŠã¯ä»¥äžã«ã
ã¹ããã9ãç»åã衚瀺ãã
ããã§ãä»æ¥ã®äŒè©±ã®ã¡ã€ã³ãããã¯ãã€ãŸãã¢ããªã±ãŒã·ã§ã³ã®ã°ã©ãã£ãã¯ã³ã³ããŒãã³ãã«ã€ããŠèª¬æããŸãããåç¥ã®ããã«ãç§ãã¡ã®ä»äºã¯ã€ã³ã¿ãŒãã§ã€ã¹
GameRenderer
ãšãã®3ã€ã®ã¡ãœãããå®è£ ããããšã§ããããŒã ã«ã¯ãŸã æèœã®ããã¢ãŒãã£ã¹ããããªããããç䌌ã°ã©ãã£ãã¯ã䜿çšããŠç¬èªã«ãããè¡ããŸããããããåå¿è ã«ãšã£ãŠã¯ãåºå£ã§äžè¬çã«èŠããããã®ãç解ããŠãããšããã§ãããããããŠãããã次ã®å 容ã®3ã€ã®ç»é¢ãèŠãããšæããŸãã
衚瀺ãããŠããç»åã¯ãJavaã¢ããªã±ãŒã·ã§ã³ã®ã³ã³ãœãŒã«ã§é垞䜿çšããããã®ãšã¯ç°ãªããã®ã§ãããéåžžã®æ©èœã§
prinltn()
ã¯æããã«äžååã§ããããšãã倧å€æ°ããã§ã«èªèããŠãããšæããŸãããŸããç»é¢äžã®ä»»æã®å Žæã«ãžã£ã³ãããŠãç°ãªãè²ã§ã·ã³ãã«ãæç»ã§ããããã«ããããšæããŸãã
ãç§ãã¡ã®å©ãã«é§ãã€ããŸã
<dependency> <groupId>org.fusesource.jansi</groupId> <artifactId>jansi</artifactId> <version>1.17.1</version> <scope>compile</scope> </dependency>
ãããŠãäœæãéå§ã§ããŸãããã®ã©ã€ãã©ãªã¯ããã§ãŒã³åã§ãã䟿å©ãªã¡ãœããã®æãæã€ã¯ã©ã¹ãªããžã§ã¯ã
Ansi
ïŒéçåŒã³åºãã®çµæãšããŠååŸããã
Ansi.ansi()
ïŒãæäŸããŸããããã¯
StringBuilder
'a ã®ååã«åºã¥ããŠåäœããŸã-æåã«ãªããžã§ã¯ããäœæãããããå°å·ã«éããŸãã䟿å©ãªã¡ãœããã®ãã¡ã䟿å©ãªãã®ãèŠã€ããŸãã
-
a()
-æåã衚瀺ããŸãã -
cursor()
-ç»é¢äžã§ã«ãŒãœã«ã移åããŸãã -
eraseLine()
-ãŸãã§èªåèªèº«ã®ããšã話ããã®ããã«ã -
eraseScreen()
-åæ§ã«; -
fg(), bg(), fgBright(), bgBright()
-ããã¹ããšèæ¯è²ãæäœããããã®éåžžã«äžäŸ¿ãªæ¹æ³-ç¬èªã®ãããå¿«é©ãªãã®ã«ããŸãã -
reset()
-èšå®ãããè²èšå®ãããªãã«ãŒãªã©ããªã»ããããŸãã
ConsoleRenderer
ç§ãã¡ã®ä»äºã«åœ¹ç«ã€ãããããªããŠãŒãã£ãªãã£ã¡ãœãããæã€ã¯ã©ã¹ãäœæããŸããããæåã®ããŒãžã§ã³ã¯æ¬¡ã®ããã«ãªããŸãã
abstract class ConsoleRenderer() { protected lateinit var ansi: Ansi init { AnsiConsole.systemInstall() clearScreen() resetAnsi() } private fun resetAnsi() { ansi = Ansi.ansi() } fun clearScreen() { print(Ansi.ansi().eraseScreen(Ansi.Erase.ALL).cursor(1, 1)) } protected fun render() { print(ansi.toString()) resetAnsi() } }
ãã®ã¡ãœãã
resetAnsi()
ã¯
Ansi
ãå¿ èŠãªã³ãã³ãïŒç§»åãåºåãªã©ïŒã§æºããããæ°ããïŒç©ºã®ïŒãªããžã§ã¯ããäœæããŸããèšå ¥ãå®äºãããšãçæããããªããžã§ã¯ãã¯ã¡ãœããã«ãã£ãŠå°å·ã®ããã«éä¿¡ãã
render()
ãå€æ°ã¯æ°ãããªããžã§ã¯ãã§åæåãããŸãããŸã è€éãªããšã¯ãããŸãããïŒãããããªãããã®ã¯ã©ã¹ãä»ã®äŸ¿å©ãªã¡ãœããã§åãå§ããŸãã
ãµã€ãºããå§ããŸããããã»ãšãã©ã®ç«¯æ«ã®æšæºã³ã³ãœãŒã«ã®ãµã€ãºã¯80x24ã§ãã2ã€ã®å®æ°
CONSOLE_WIDTH
ãšã§ãã®äºå®ã«æ³šæã
CONSOLE_HEIGHT
ãŸããç§ãã¡ã¯ç¹å®ã®å€ã«å·çããããšã¯ããããã¶ã€ã³ãå¯èœãªéããŽã ç¶ã«ããããšããŸãïŒãŠã§ãã®ããã«ïŒã座æšã®çªå·ä»ãã¯1ããå§ãŸããæåã®åº§æšã¯è¡ã2çªç®ã®åº§æšã¯åã§ããããããã¹ãŠãç¥ã£ãŠããŠãŒãã£ãªãã£ã¡ãœãããèšè¿°ããŸã
drawHorizontalLine()
æå®ãããæååãæå®ãããæåã§åããŸãã
protected fun drawHorizontalLine(offsetY: Int, filler: Char) { ansi.cursor(offsetY, 1) (1..CONSOLE_WIDTH).forEach { ansi.a(filler) } //for (i in 1..CONSOLE_WIDTH) { ansi.a(filler) } }
ç¹°ãè¿ãã«ãªããŸãããã³ãã³ããåŒã³åºã
a()
ãã
cursor()
ãããã«ãšãã§ã¯ããå®è¡ãããããããšã¯ãªãã
Ansi
察å¿ããäžé£ã®ã³ãã³ãããªããžã§ã¯ãã«è¿œå ããã ãã§ãããããã®ã·ãŒã±ã³ã¹ãå°å·çšã«éä¿¡ããããšãã«ã®ã¿ãç»é¢ã«è¡šç€ºãããŸãã
å€å žçãªãµã€ã¯ã«ã®äœ¿çšãšã®é
for
ã§æ©èœçãªã¢ãããŒã
ClosedRange
ãš
forEach{}
æ ¹æ¬çãªéãã¯ãããŸãã-åéçºè ã¯ãããããã䟿å©ã§ãããšå€æããŸãããããããç§ã¯
å¥ã®ãŠãŒãã£ãªãã£ã¡ãœãããå®è£ ããŸã
drawBlankLine()
drawHorizontalLine(offsetY, ' ')
ãæ¡åŒµåã®ã¿ãå Žåã«ãã£ãŠã¯ãè¡ãå®å šã«ç©ºã«ããå¿ èŠã¯ãããŸããããæåãšæåŸã«åçŽç·ãæ®ãå¿ èŠããããŸãïŒãã¬ãŒã ãããã§ãïŒãã³ãŒãã¯æ¬¡ã®ããã«ãªããŸãã
protected fun drawBlankLine(offsetY: Int, drawBorders: Boolean = true) { ansi.cursor(offsetY, 1) if (drawBorders) { ansi.a('â') (2 until CONSOLE_WIDTH).forEach { ansi.a(' ') } ansi.a('â') } else { ansi.eraseLine(Ansi.Erase.ALL) } }
ã©ã®ããã«ãç䌌ã°ã©ãã£ãã¯ã¹ãããã¬ãŒã ãæç»ããããšã¯ãããŸãããïŒã·ã³ãã«ã¯ããœãŒã¹ã³ãŒãã«çŽæ¥æ¿å ¥ã§ããŸããAltããŒãæŒããªãããæ°åããŒãããã§æåã³ãŒããå ¥åããŸããæŸããŠ å¿ èŠãªASCIIã³ãŒãã¯ã©ã®ãšã³ã³ãŒãã§ãåãã§ãããããæå°éã®çŽ³å£«ã®ã»ããã§ãã
ãããŠãMinecraftã®ããã«ãå¯èœæ§ã¯ããªãã®æ³ååã®éçã«ãã£ãŠã®ã¿å¶éãããŸãããããŠç»é¢ãµã€ãºã
protected fun drawCenteredCaption(offsetY: Int, text: String, color: Color, drawBorders: Boolean = true) { val center = (CONSOLE_WIDTH - text.length) / 2 ansi.cursor(offsetY, 1) ansi.a(if (drawBorders) 'â' else ' ') (2 until center).forEach { ansi.a(' ') } ansi.color(color).a(text).reset() (text.length + center until CONSOLE_WIDTH).forEach { ansi.a(' ') } ansi.a(if (drawBorders) 'â' else ' ') }
è±ã«ã€ããŠå°ã話ããŸãããããã®ã¯ã©ã¹ã«
Ansi
ã¯
Color
ã8ã€ã®åè²ïŒé»ãéãç·ãã·ã¢ã³ãèµ€ã玫ãé»ãã°ã¬ãŒïŒã®å®æ°ãå«ãŸããŠããŸãããããã¯ãè²ãèå¥ããããã«éåžžã«äžäŸ¿ãª
fg()/bg()
æãããŒãžã§ã³ãŸãã¯æããããŒãžã§ã³ã®ã¡ãœããã®å ¥åã«æž¡ãå¿ èŠããããŸã
fgBright()/bgBright()
æ¹æ³ã§ã¯ã1ã€ã®å€ã§ã¯äžååã§ã-å°ãªããšã2ã€ïŒè²ãšæããïŒãå¿ èŠã§ãããããã£ãŠãå®æ°ã®ãªã¹ããšæ¡åŒµã¡ãœãããäœæããŸãïŒãã¥ãŒãã®çš®é¡ãšããŒããŒã®ã¯ã©ã¹ã«è²ãããããã€ã³ãããŸãïŒã
protected enum class Color { BLACK, DARK_BLUE, DARK_GREEN, DARK_CYAN, DARK_RED, DARK_MAGENTA, DARK_YELLOW, LIGHT_GRAY, DARK_GRAY, LIGHT_BLUE, LIGHT_GREEN, LIGHT_CYAN, LIGHT_RED, LIGHT_MAGENTA, LIGHT_YELLOW, WHITE } protected fun Ansi.color(color: Color?): Ansi = when (color) { Color.BLACK -> fgBlack() Color.DARK_BLUE -> fgBlue() Color.DARK_GREEN -> fgGreen() Color.DARK_CYAN -> fgCyan() Color.DARK_RED -> fgRed() Color.DARK_MAGENTA -> fgMagenta() Color.DARK_YELLOW -> fgYellow() Color.LIGHT_GRAY -> fg(Ansi.Color.WHITE) Color.DARK_GRAY -> fgBrightBlack() Color.LIGHT_BLUE -> fgBrightBlue() Color.LIGHT_GREEN -> fgBrightGreen() Color.LIGHT_CYAN -> fgBrightCyan() Color.LIGHT_RED -> fgBrightRed() Color.LIGHT_MAGENTA -> fgBrightMagenta() Color.LIGHT_YELLOW -> fgBrightYellow() Color.WHITE -> fgBright(Ansi.Color.WHITE) else -> this } protected fun Ansi.background(color: Color?): Ansi = when (color) { Color.BLACK -> ansi.bg(Ansi.Color.BLACK) Color.DARK_BLUE -> ansi.bg(Ansi.Color.BLUE) Color.DARK_GREEN -> ansi.bgGreen() Color.DARK_CYAN -> ansi.bg(Ansi.Color.CYAN) Color.DARK_RED -> ansi.bgRed() Color.DARK_MAGENTA -> ansi.bgMagenta() Color.DARK_YELLOW -> ansi.bgYellow() Color.LIGHT_GRAY -> ansi.bg(Ansi.Color.WHITE) Color.DARK_GRAY -> ansi.bgBright(Ansi.Color.BLACK) Color.LIGHT_BLUE -> ansi.bgBright(Ansi.Color.BLUE) Color.LIGHT_GREEN -> ansi.bgBrightGreen() Color.LIGHT_CYAN -> ansi.bgBright(Ansi.Color.CYAN) Color.LIGHT_RED -> ansi.bgBrightRed() Color.LIGHT_MAGENTA -> ansi.bgBright(Ansi.Color.MAGENTA) Color.LIGHT_YELLOW -> ansi.bgBrightYellow() Color.WHITE -> ansi.bgBright(Ansi.Color.WHITE) else -> this } protected val dieColors = mapOf( Die.Type.PHYSICAL to Color.LIGHT_BLUE, Die.Type.SOMATIC to Color.LIGHT_GREEN, Die.Type.MENTAL to Color.LIGHT_MAGENTA, Die.Type.VERBAL to Color.LIGHT_YELLOW, Die.Type.DIVINE to Color.LIGHT_CYAN, Die.Type.WOUND to Color.DARK_GRAY, Die.Type.ENEMY to Color.DARK_RED, Die.Type.VILLAIN to Color.LIGHT_RED, Die.Type.OBSTACLE to Color.DARK_YELLOW, Die.Type.ALLY to Color.WHITE ) protected val heroColors = mapOf( Hero.Type.BRAWLER to Color.LIGHT_BLUE, Hero.Type.HUNTER to Color.LIGHT_GREEN )
çŸåšãå©çšå¯èœãª16è²ã®ããããã¯ãåäžã®å®æ°ã«ãã£ãŠäžæã«èå¥ãããŸããããã«ããã€ãã®ãŠãŒãã£ãªãã£ã¡ãœãããèšè¿°ããŸããããã®åã«ãã1ã€
ãããã¹ãæååã®å®æ°ãã©ãã«ä¿åããããèããŸãã
ãæååå®æ°ã¯ããã¹ãŠ1ãæã«ä¿åãããããã«ãå¥ã ã®ãã¡ã€ã«ã«åãåºãå¿ èŠããããŸããããã«ãããæååå®æ°ã®ä¿å®ã容æã«ãªããŸãããŸããããŒã«ã©ã€ãºã«ãšã£ãŠãéèŠã§ã...ã
æååå®æ°ãå¥ã®ãã¡ã€ã«ã«ç§»åããå¿ èŠããããŸã...ããã§ãããææ ¢ããŸãããã®çš®ã®ãªãœãŒã¹ãæäœããããã®æšæºJavaã¡ã«ããºã ã¯ã
java.util.ResourceBundle
ãã¡ã€ã«ãæäœãããªããžã§ã¯ãã§ã
.properties
ããã®ãããªãã¡ã€ã«ããå§ããŸãã
# Game status messages choose_dice_perform_check=Choose dice to perform check: end_of_turn_discard_extra=END OF TURN: Discard extra dice: end_of_turn_discard_optional=END OF TURN: Discard any dice, if needed: choose_action_before_exploration=Choose your action: choose_action_after_exploration=Already explored this turn. Choose what to do now: encounter_physical=Encountered PHYSICAL die. Need to pass respective check or lose this die. encounter_somatic=Encountered SOMATIC die. Need to pass respective check or lose this die. encounter_mental=Encountered MENTAL die. Need to pass respective check or lose this die. encounter_verbal=Encountered VERBAL die. Need to pass respective check or lose this die. encounter_divine=Encountered DIVINE die. Can be acquired automatically (no checks needed): die_acquire_success=You have acquired the die! die_acquire_failure=You have failed to acquire the die. game_loss_out_of_time=You ran out of time # Die types physical=PHYSICAL somatic=SOMATIC mental=MENTAL verbal=VERBAL divine=DIVINE ally=ALLY wound=WOUND enemy=ENEMY villain=VILLAIN obstacle=OBSTACLE # Hero types and descriptions brawler=Brawler hunter=Hunter # Various labels avg=avg bag=Bag bag_size=Bag size class=Class closed=Closed discard=Discard empty=Empty encountered=Encountered fail=Fail hand=Hand heros_turn=%s's turn max=max min=min perform_check=Perform check: pile=Pile received_new_die=Received new die result=Result success=Success sum=sum time=Time total=Total # Action names and descriptions action_confirm_key=ENTER action_confirm_name=Confirm action_cancel_key=ESC action_cancel_name=Cancel action_explore_location_key=E action_explore_location_name=xplore action_finish_turn_key=F action_finish_turn_name=inish action_hide_key=H action_hide_name=ide action_discard_key=D action_discard_name=iscard action_acquire_key=A action_acquire_name=cquire action_leave_key=L action_leave_name=eave action_forfeit_key=F action_forfeit_name=orfeit
åè¡ã«ã¯ãæåã§åºåãããããŒãšå€ã®ãã¢ãå«ãŸããŠããŸã
=
ããã¡ã€ã«ã¯ã©ãã«ã§ã眮ãããšãã§ããŸã-äž»ãªããšã¯ããã¡ã€ã«ãžã®ãã¹ãã¯ã©ã¹ãã¹ã®äžéšã§ããããšã§ããã¢ã¯ã·ã§ã³ã®ããã¹ãã¯2ã€ã®éšåã§æ§æãããŠããããšã«æ³šæããŠãã ãããæåã®æåã¯ãç»é¢ã«è¡šç€ºããããšãã«é»è²ã§åŒ·èª¿è¡šç€ºãããã ãã§ãªãããã®ã¢ã¯ã·ã§ã³ãå®è¡ããããã«æŒãå¿ èŠãããããŒã決å®ããŸãããããã£ãŠãããããå¥ã ã«ä¿åãããšäŸ¿å©ã§ãã
ãã ããç¹å®ã®åœ¢åŒïŒããšãã°ãAndroidã§ã¯æååã®ä¿åæ¹æ³ãç°ãªãïŒããæœè±¡åããæååå®æ°ãèªã¿èŸŒãããã®ã€ã³ã¿ãŒãã§ã€ã¹ã«ã€ããŠèª¬æããŸãã
interface StringLoader { fun loadString(key: String): String }
ããŒã¯å ¥åã«éä¿¡ãããåºåã¯ç¹å®ã®è¡ã§ããå®è£ ã¯ãã€ã³ã¿ãŒãã§ãŒã¹èªäœãšåããããç°¡åã§ãïŒãã¡ã€ã«ãpathã«æ²¿ã£ãŠãããšä»®å®ããŸã
src/main/resources/text/strings.properties
ïŒã
class PropertiesStringLoader() : StringLoader { private val properties = ResourceBundle.getBundle("text.strings") override fun loadString(key: String) = properties.getString(key) ?: "" }
ãã
drawStatusMessage()
ã§ãã²ãŒã ãšã³ãžã³ã®çŸåšã®ç¶æ ãç»é¢ã«è¡šç€ºãã
StatusMessage
æ¹æ³
drawActionList()
ïŒ
ActionList
ïŒãšäœ¿çšå¯èœãªã¢ã¯ã·ã§ã³ã®ãªã¹ãã衚瀺ããæ¹æ³ïŒïŒãå®è£ ããããšã¯é£ãããããŸãããéã ããæãä»ã®å ¬åŒãªæ¹æ³ãšåæ§ã«ã
ããããã®ã³ãŒãããããŸããããã®äžéšã¯ãã§ã«èŠãŠããŸã...ã ãããããã«ãã¿ãã¬ããããŸã
abstract class ConsoleRenderer(private val strings: StringLoader) { protected lateinit var ansi: Ansi init { AnsiConsole.systemInstall() clearScreen() resetAnsi() } protected fun loadString(key: String) = strings.loadString(key) private fun resetAnsi() { ansi = Ansi.ansi() } fun clearScreen() { print(Ansi.ansi().eraseScreen(Ansi.Erase.ALL).cursor(1, 1)) } protected fun render() { ansi.cursor(CONSOLE_HEIGHT, CONSOLE_WIDTH) System.out.print(ansi.toString()) resetAnsi() } protected fun drawBigNumber(offsetX: Int, offsetY: Int, number: Int): Unit = with(ansi) { var currentX = offsetX cursor(offsetY, currentX) val text = number.toString() text.forEach { when (it) { '0' -> { cursor(offsetY, currentX) a(" âââ ") cursor(offsetY + 1, currentX) a("â â ") cursor(offsetY + 2, currentX) a("â â ") cursor(offsetY + 3, currentX) a("â â ") cursor(offsetY + 4, currentX) a(" âââ ") } '1' -> { cursor(offsetY, currentX) a(" â ") cursor(offsetY + 1, currentX) a(" ââ ") cursor(offsetY + 2, currentX) a("â â ") cursor(offsetY + 3, currentX) a(" â ") cursor(offsetY + 4, currentX) a("âââââ ") } '2' -> { cursor(offsetY, currentX) a(" âââ ") cursor(offsetY + 1, currentX) a("â â ") cursor(offsetY + 2, currentX) a(" â ") cursor(offsetY + 3, currentX) a(" â ") cursor(offsetY + 4, currentX) a("âââââ ") } '3' -> { cursor(offsetY, currentX) a("ââââ ") cursor(offsetY + 1, currentX) a(" â ") cursor(offsetY + 2, currentX) a(" ââ ") cursor(offsetY + 3, currentX) a(" â ") cursor(offsetY + 4, currentX) a("ââââ ") } '4' -> { cursor(offsetY, currentX) a(" â ") cursor(offsetY + 1, currentX) a(" ââ ") cursor(offsetY + 2, currentX) a(" â â ") cursor(offsetY + 3, currentX) a("âââââ ") cursor(offsetY + 4, currentX) a(" â ") } '5' -> { cursor(offsetY, currentX) a("âââââ ") cursor(offsetY + 1, currentX) a("â ") cursor(offsetY + 2, currentX) a("ââââ ") cursor(offsetY + 3, currentX) a(" â ") cursor(offsetY + 4, currentX) a("ââââ ") } '6' -> { cursor(offsetY, currentX) a(" âââ ") cursor(offsetY + 1, currentX) a("â ") cursor(offsetY + 2, currentX) a("ââââ ") cursor(offsetY + 3, currentX) a("â â ") cursor(offsetY + 4, currentX) a(" âââ ") } '7' -> { cursor(offsetY, currentX) a("âââââ ") cursor(offsetY + 1, currentX) a(" â ") cursor(offsetY + 2, currentX) a(" â ") cursor(offsetY + 3, currentX) a(" â ") cursor(offsetY + 4, currentX) a(" â ") } '8' -> { cursor(offsetY, currentX) a(" âââ ") cursor(offsetY + 1, currentX) a("â â ") cursor(offsetY + 2, currentX) a(" âââ ") cursor(offsetY + 3, currentX) a("â â ") cursor(offsetY + 4, currentX) a(" âââ ") } '9' -> { cursor(offsetY, currentX) a(" âââ ") cursor(offsetY + 1, currentX) a("â â ") cursor(offsetY + 2, currentX) a(" ââââ ") cursor(offsetY + 3, currentX) a(" â ") cursor(offsetY + 4, currentX) a(" âââ ") } } currentX += 6 } } protected fun drawHorizontalLine(offsetY: Int, filler: Char) { ansi.cursor(offsetY, 1) (1..CONSOLE_WIDTH).forEach { ansi.a(filler) } } protected fun drawBlankLine(offsetY: Int, drawBorders: Boolean = true) { ansi.cursor(offsetY, 1) if (drawBorders) { ansi.a('â') (2 until CONSOLE_WIDTH).forEach { ansi.a(' ') } ansi.a('â') } else { ansi.eraseLine(Ansi.Erase.ALL) } } protected fun drawCenteredCaption(offsetY: Int, text: String, color: Color, drawBorders: Boolean = true) { val center = (CONSOLE_WIDTH - text.length) / 2 ansi.cursor(offsetY, 1) ansi.a(if (drawBorders) 'â' else ' ') (2 until center).forEach { ansi.a(' ') } ansi.color(color).a(text).reset() (text.length + center until CONSOLE_WIDTH).forEach { ansi.a(' ') } ansi.a(if (drawBorders) 'â' else ' ') } protected fun drawStatusMessage(offsetY: Int, message: StatusMessage, drawBorders: Boolean = true) { //Setup val messageText = loadString(message.toString().toLowerCase()) var currentX = 1 val rightBorder = CONSOLE_WIDTH - if (drawBorders) 1 else 0 //Left border ansi.cursor(offsetY, 1) if (drawBorders) { ansi.a('â') currentX++ } ansi.a(' ') currentX++ //Text ansi.a(messageText) currentX += messageText.length //Right border (currentX..rightBorder).forEach { ansi.a(' ') } if (drawBorders) { ansi.a('â') } } protected fun drawActionList(offsetY: Int, actions: ActionList, drawBorders: Boolean = true) { val rightBorder = CONSOLE_WIDTH - if (drawBorders) 1 else 0 var currentX = 1 //Left border ansi.cursor(offsetY, 1) if (drawBorders) { ansi.a('â') currentX++ } ansi.a(' ') currentX++ //List of actions actions.forEach { action -> val key = loadString("action_${action.toString().toLowerCase()}_key") val name = loadString("action_${action.toString().toLowerCase()}_name") val length = key.length + 2 + name.length if (currentX + length >= rightBorder) { (currentX..rightBorder).forEach { ansi.a(' ') } if (drawBorders) { ansi.a('â') } ansi.cursor(offsetY + 1, 1) currentX = 1 if (drawBorders) { ansi.a('â') currentX++ } ansi.a(' ') currentX++ } if (action.isEnabled) { ansi.color(Color.LIGHT_YELLOW) } ansi.a('(').a(key).a(')').reset() ansi.a(name) ansi.a(" ") currentX += length + 2 } //Right border (currentX..rightBorder).forEach { ansi.a(' ') } if (drawBorders) { ansi.a('â') } } protected enum class Color { BLACK, DARK_BLUE, DARK_GREEN, DARK_CYAN, DARK_RED, DARK_MAGENTA, DARK_YELLOW, LIGHT_GRAY, DARK_GRAY, LIGHT_BLUE, LIGHT_GREEN, LIGHT_CYAN, LIGHT_RED, LIGHT_MAGENTA, LIGHT_YELLOW, WHITE } protected fun Ansi.color(color: Color?): Ansi = when (color) { Color.BLACK -> fgBlack() Color.DARK_BLUE -> fgBlue() Color.DARK_GREEN -> fgGreen() Color.DARK_CYAN -> fgCyan() Color.DARK_RED -> fgRed() Color.DARK_MAGENTA -> fgMagenta() Color.DARK_YELLOW -> fgYellow() Color.LIGHT_GRAY -> fg(Ansi.Color.WHITE) Color.DARK_GRAY -> fgBrightBlack() Color.LIGHT_BLUE -> fgBrightBlue() Color.LIGHT_GREEN -> fgBrightGreen() Color.LIGHT_CYAN -> fgBrightCyan() Color.LIGHT_RED -> fgBrightRed() Color.LIGHT_MAGENTA -> fgBrightMagenta() Color.LIGHT_YELLOW -> fgBrightYellow() Color.WHITE -> fgBright(Ansi.Color.WHITE) else -> this } protected fun Ansi.background(color: Color?): Ansi = when (color) { Color.BLACK -> ansi.bg(Ansi.Color.BLACK) Color.DARK_BLUE -> ansi.bg(Ansi.Color.BLUE) Color.DARK_GREEN -> ansi.bgGreen() Color.DARK_CYAN -> ansi.bg(Ansi.Color.CYAN) Color.DARK_RED -> ansi.bgRed() Color.DARK_MAGENTA -> ansi.bgMagenta() Color.DARK_YELLOW -> ansi.bgYellow() Color.LIGHT_GRAY -> ansi.bg(Ansi.Color.WHITE) Color.DARK_GRAY -> ansi.bgBright(Ansi.Color.BLACK) Color.LIGHT_BLUE -> ansi.bgBright(Ansi.Color.BLUE) Color.LIGHT_GREEN -> ansi.bgBrightGreen() Color.LIGHT_CYAN -> ansi.bgBright(Ansi.Color.CYAN) Color.LIGHT_RED -> ansi.bgBrightRed() Color.LIGHT_MAGENTA -> ansi.bgBright(Ansi.Color.MAGENTA) Color.LIGHT_YELLOW -> ansi.bgBrightYellow() Color.WHITE -> ansi.bgBright(Ansi.Color.WHITE) else -> this } protected val dieColors = mapOf( Die.Type.PHYSICAL to Color.LIGHT_BLUE, Die.Type.SOMATIC to Color.LIGHT_GREEN, Die.Type.MENTAL to Color.LIGHT_MAGENTA, Die.Type.VERBAL to Color.LIGHT_YELLOW, Die.Type.DIVINE to Color.LIGHT_CYAN, Die.Type.WOUND to Color.DARK_GRAY, Die.Type.ENEMY to Color.DARK_RED, Die.Type.VILLAIN to Color.LIGHT_RED, Die.Type.OBSTACLE to Color.DARK_YELLOW, Die.Type.ALLY to Color.WHITE ) protected val heroColors = mapOf( Hero.Type.BRAWLER to Color.LIGHT_BLUE, Hero.Type.HUNTER to Color.LIGHT_GREEN ) protected open fun shortcut(index: Int) = "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ"[index] }
ãªãç§ãã¡å šå¡ããããããã®ã§ããïŒã¯ãããã®çŽ æŽãããclassããã€ã³ã¿ãŒãã§ãŒã¹å®è£ ãç¶æ¿ããããã«
GameRenderer
ã
ããã¯ãæåã®æãåçŽãªã¡ãœããã®å®è£ ãã©ã®ããã«èŠãããã§ãïŒ
override fun drawGameLoss(message: StatusMessage) { val centerY = CONSOLE_HEIGHT / 2 (1 until centerY).forEach { drawBlankLine(it, false) } val data = loadString(message.toString().toLowerCase()).toUpperCase() drawCenteredCaption(centerY, data, LIGHT_RED, false) (centerY + 1..CONSOLE_HEIGHT).forEach { drawBlankLine(it, false) } render() }
è¶ èªç¶çãªãã®ã§ã¯ãªã
data
ãç»é¢ã®äžå€®ã«ããã¹ãè¡ïŒïŒãèµ€ã§1ã€ã ãæç»ããŸãïŒ
drawCenteredCaption()
ïŒãã³ãŒãã®æ®ãã¯ãç»é¢ã®æ®ãã空çœè¡ã§åããŸãããããã誰ãããããå¿ èŠãªçç±ãå°ããã§ããã-çµå±ã®ãšãã
clearScreen()
ãã¡ãœããããããã¡ãœããã®æåã«ãããåŒã³åºããç»é¢ãã¯ãªã¢ããŠãããå¿ èŠãªããã¹ããæç»ããã ãã§ååã§ããæ®å¿µãªãããããã¯ç§ãã¡ã䜿çšããªãæ laãªã¢ãããŒãã§ãããã®çç±ã¯éåžžã«ç°¡åã§ãããã®ã¢ãããŒãã§ã¯ãç»é¢äžã®äžéšã®äœçœ®ã2åæç»ãããç¹ã«ç»é¢ãé£ç¶ããŠæ°åé£ç¶ããŠæç»ãããå ŽåïŒã¢ãã¡ãŒã·ã§ã³äžïŒã«ãã¡ãã€ããé¡èã«ãªããŸãããããã£ãŠãç§ãã¡ã®ã¿ã¹ã¯ã¯ãé©åãªå Žæã«é©åãªãã£ã©ã¯ã¿ãŒãæãã ãã§ãªããå šäœãåããããšã§ã空ã®æåãå«ãç»é¢ã®æ®ãã®éšåïŒä»ã®ã¬ã³ããªã³ã°ããã®ã¢ãŒãã£ãã¡ã¯ããç»é¢ã«æ®ããªãããã«ããããïŒããããŠããã®ã¿ã¹ã¯ã¯ããã»ã©åçŽã§ã¯ãããŸããã
次ã®æ¹æ³ã¯ããã®ååã«åŸããŸãã
override fun drawHeroTurnStart(hero: Hero) { val centerY = (CONSOLE_HEIGHT - 5) / 2 (1 until centerY).forEach { drawBlankLine(it, false) } ansi.color(heroColors[hero.type]) drawHorizontalLine(centerY, 'â') drawHorizontalLine(centerY + 4, 'â') ansi.reset() ansi.cursor(centerY + 1, 1).eraseLine() ansi.cursor(centerY + 3, 1).eraseLine() ansi.cursor(centerY + 2, 1) val text = String.format(loadString("heros_turn"), hero.name.toUpperCase()) val index = text.indexOf(hero.name.toUpperCase()) val center = (CONSOLE_WIDTH - text.length) / 2 ansi.cursor(centerY + 2, center) ansi.eraseLine(Ansi.Erase.BACKWARD) ansi.a(text.substring(0, index)) ansi.color(heroColors[hero.type]).a(hero.name.toUpperCase()).reset() ansi.a(text.substring(index + hero.name.length)) ansi.eraseLine(Ansi.Erase.FORWARD) (centerY + 5..CONSOLE_HEIGHT).forEach { drawBlankLine(it, false) } render() }
ããã§ã¯ãäžå€®æãã®ããã¹ãã«å ããŠã2æ¬ã®æ°Žå¹³ç·ããããŸãïŒäžã®ã¹ã¯ãªãŒã³ã·ã§ãããåç §ïŒãäžå€®ã®ã¬ã¿ãªã³ã°ã¯2è²ã§è¡šç€ºãããããšã«æ³šæããŠãã ããããŸããåŠæ ¡ã§æ°åŠãåŠã¶ããšã¯ãŸã æçšã§ããããšã確èªããŠãã ããã
ããŠãæãåçŽãªã¡ãœãããèŠãŠãå®è£ ãç¥ãæãæ¥ãŸãã
drawLocationInteriorScreen()
ãããªãèªèº«ãç解ããŠããããã«ãããã«ã¯ããã«æ¡éãã®ã³ãŒãããããŸããããã«ãç»é¢ã®ã³ã³ãã³ãã¯ãŠãŒã¶ãŒã®ã¢ã¯ã·ã§ã³ã«å¿ããŠåçã«å€åãããããåžžã«åæç»ããå¿ èŠããããŸãïŒã¢ãã¡ãŒã·ã§ã³ã䜿çšããå ŽåããããŸãïŒãããŠãæçµçã«ããªããçµããããããã«ïŒäžèšã®ã¹ã¯ãªãŒã³ã·ã§ããã«å ããŠããã®ã¡ãœããã®ãã¬ãŒã ã¯ãŒã¯ã§ã¯ãããã«3ã€ã®è¡šç€ºãå®è£ ããå¿ èŠãããããšãæ³åããŠãã ããïŒ
ãããã£ãŠãããã«ããªããžã®ç§ã®çŽ æŽãããã¢ããã€ã¹ããããŸãïŒ1ã€ã®ã¡ãœããã«ãã¹ãŠã®ã³ãŒããæŒã蟌ãŸãªãã§ãã ãããå®è£ ãããã€ãã®ã¡ãœããã«åå²ããŸãïŒåã¡ãœããã1åã ãåŒã³åºãããå Žåã§ãïŒãããŠãããŽã ããå¿ããªãã§ãã ããã
ãããããªãã®ç®ã«æ³¢çŽãå§ããããæ°ç§éãŸã°ããããŠãã ãã-ããã¯åœ¹ç«ã€ã¯ãã§ã
class ConsoleGameRenderer(loader: StringLoader) : ConsoleRenderer(loader), GameRenderer { private fun drawLocationTopPanel(location: Location, heroesAtLocation: List<Hero>, currentHero: Hero, timer: Int) { val closedString = loadString("closed").toLowerCase() val timeString = loadString("time") val locationName = location.name.toString().toUpperCase() val separatorX1 = locationName.length + if (location.isOpen) { 6 + if (location.bag.size >= 10) 2 else 1 } else { closedString.length + 7 } val separatorX2 = CONSOLE_WIDTH - timeString.length - 6 - if (timer >= 10) 1 else 0 //Top border ansi.cursor(1, 1) ansi.a('â') (2 until CONSOLE_WIDTH).forEach { ansi.a(if (it == separatorX1 || it == separatorX2) 'â¬' else 'â') } ansi.a('â') //Center row ansi.cursor(2, 1) ansi.a("â ") if (location.isOpen) { ansi.color(WHITE).a(locationName).reset() ansi.a(": ").a(location.bag.size) } else { ansi.a(locationName).reset() ansi.color(DARK_GRAY).a(" (").a(closedString).a(')').reset() } ansi.a(" â") var currentX = separatorX1 + 2 heroesAtLocation.forEach { hero -> ansi.a(' ') ansi.color(heroColors[hero.type]) ansi.a(if (hero === currentHero) 'â»' else '').reset() currentX += 2 } (currentX..separatorX2).forEach { ansi.a(' ') } ansi.a("â ").a(timeString).a(": ") when { timer <= 5 -> ansi.color(LIGHT_RED) timer <= 15 -> ansi.color(LIGHT_YELLOW) else -> ansi.color(LIGHT_GREEN) } ansi.bold().a(timer).reset().a(" â") //Bottom border ansi.cursor(3, 1) ansi.a('â') (2 until CONSOLE_WIDTH).forEach { ansi.a(if (it == separatorX1 || it == separatorX2) 'âŽ' else 'â') } ansi.a('â€') } private fun drawLocationHeroPanel(offsetY: Int, hero: Hero) { val bagString = loadString("bag").toUpperCase() val discardString = loadString("discard").toUpperCase() val separatorX1 = hero.name.length + 4 val separatorX3 = CONSOLE_WIDTH - discardString.length - 6 - if (hero.discardPile.size >= 10) 1 else 0 val separatorX2 = separatorX3 - bagString.length - 6 - if (hero.bag.size >= 10) 1 else 0 //Top border ansi.cursor(offsetY, 1) ansi.a('â') (2 until CONSOLE_WIDTH).forEach { ansi.a(if (it == separatorX1 || it == separatorX2 || it == separatorX3) 'â¬' else 'â') } ansi.a('â€') //Center row ansi.cursor(offsetY + 1, 1) ansi.a("â ") ansi.color(heroColors[hero.type]).a(hero.name.toUpperCase()).reset() ansi.a(" â") val currentX = separatorX1 + 1 (currentX until separatorX2).forEach { ansi.a(' ') } ansi.a("â ").a(bagString).a(": ") when { hero.bag.size <= hero.hand.capacity -> ansi.color(LIGHT_RED) else -> ansi.color(LIGHT_YELLOW) } ansi.a(hero.bag.size).reset() ansi.a(" â ").a(discardString).a(": ") ansi.a(hero.discardPile.size) ansi.a(" â") //Bottom border ansi.cursor(offsetY + 2, 1) ansi.a('â') (2 until CONSOLE_WIDTH).forEach { ansi.a(if (it == separatorX1 || it == separatorX2 || it == separatorX3) 'âŽ' else 'â') } ansi.a('â€') } private fun drawDieSize(die: Die, checked: Boolean = false) { when { checked -> ansi.background(dieColors[die.type]).color(BLACK) else -> ansi.color(dieColors[die.type]) } ansi.a(die.toString()).reset() } private fun drawDieFrameSmall(offsetX: Int, offsetY: Int, longDieSize: Boolean) { //Top border ansi.cursor(offsetY, offsetX) ansi.a('â') (0 until if (longDieSize) 5 else 4).forEach { ansi.a('â') } ansi.a('â') //Left border ansi.cursor(offsetY + 1, offsetX) ansi.a("â ") //Bottom border ansi.cursor(offsetY + 2, offsetX) ansi.a("â") (0 until if (longDieSize) 5 else 4).forEach { ansi.a('â') } ansi.a('â') //Right border ansi.cursor(offsetY + 1, offsetX + if (longDieSize) 6 else 5) ansi.a('â') } private fun drawDieSmall(offsetX: Int, offsetY: Int, pair: DiePair, rollResult: Int? = null) { ansi.color(dieColors[pair.die.type]) val longDieSize = pair.die.size >= 10 drawDieFrameSmall(offsetX, offsetY, longDieSize) //Roll result or die size ansi.cursor(offsetY + 1, offsetX + 1) if (rollResult != null) { ansi.a(String.format(" %2d %s", rollResult, if (longDieSize) " " else "")) } else { ansi.a(' ').a(pair.die.toString()).a(' ') } //Draw modifier ansi.cursor(offsetY + 3, offsetX) val modString = if (pair.modifier == 0) "" else String.format("%+d", pair.modifier) val frameLength = 4 + if (longDieSize) 3 else 2 var spaces = (frameLength - modString.length) / 2 (0 until spaces).forEach { ansi.a(' ') } ansi.a(modString) spaces = frameLength - spaces - modString.length (0 until spaces).forEach { ansi.a(' ') } ansi.reset() } private fun drawDieFrameBig(offsetX: Int, offsetY: Int, longDieSize: Boolean) { //Top border ansi.cursor(offsetY, offsetX) ansi.a('â') (0 until if (longDieSize) 3 else 2).forEach { ansi.a("ââââââ") } ansi.a("ââ") //Left border (1..5).forEach { ansi.cursor(offsetY + it, offsetX) ansi.a('â') } //Bottom border ansi.cursor(offsetY + 6, offsetX) ansi.a('â') (0 until if (longDieSize) 3 else 2).forEach { ansi.a("ââââââ") } ansi.a("ââ") //Right border val currentX = offsetX + if (longDieSize) 20 else 14 (1..5).forEach { ansi.cursor(offsetY + it, currentX) ansi.a('â') } } private fun drawDieSizeBig(offsetX: Int, offsetY: Int, pair: DiePair) { ansi.color(dieColors[pair.die.type]) val longDieSize = pair.die.size >= 10 drawDieFrameBig(offsetX, offsetY, longDieSize) //Die size ansi.cursor(offsetY + 1, offsetX + 1) ansi.a(" ââââ ") ansi.cursor(offsetY + 2, offsetX + 1) ansi.a(" â â ") ansi.cursor(offsetY + 3, offsetX + 1) ansi.a(" â â ") ansi.cursor(offsetY + 4, offsetX + 1) ansi.a(" â â ") ansi.cursor(offsetY + 5, offsetX + 1) ansi.a(" ââââ ") drawBigNumber(offsetX + 8, offsetY + 1, pair.die.size) //Draw modifier ansi.cursor(offsetY + 7, offsetX) val modString = if (pair.modifier == 0) "" else String.format("%+d", pair.modifier) val frameLength = 4 + 6 * if (longDieSize) 3 else 2 var spaces = (frameLength - modString.length) / 2 (0 until spaces).forEach { ansi.a(' ') } ansi.a(modString) spaces = frameLength - spaces - modString.length - 1 (0 until spaces).forEach { ansi.a(' ') } ansi.reset() } private fun drawBattleCheck(offsetY: Int, battleCheck: DieBattleCheck) { val performCheck = loadString("perform_check") var currentX = 4 var currentY = offsetY //Top message ansi.cursor(offsetY, 1) ansi.a("â ").a(performCheck) (performCheck.length + 4 until CONSOLE_WIDTH).forEach { ansi.a(' ') } ansi.a('â') //Left border (1..4).forEach { ansi.cursor(offsetY + it, 1) ansi.a("â ") } //Opponent var opponentWidth = 0 var vsWidth = 0 (battleCheck.getOpponentPair())?.let { //Die if (battleCheck.isRolled) { drawDieSmall(4, offsetY + 1, it, battleCheck.getOpponentResult()) } else { drawDieSmall(4, offsetY + 1, it) } opponentWidth = 4 + if (it.die.size >= 10) 3 else 2 currentX += opponentWidth //VS ansi.cursor(currentY + 1, currentX) ansi.a(" ") ansi.cursor(currentY + 2, currentX) ansi.color(LIGHT_YELLOW).a(" VS ").reset() ansi.cursor(currentY + 3, currentX) ansi.a(" ") ansi.cursor(currentY + 4, currentX) ansi.a(" ") vsWidth = 4 currentX += vsWidth } //Clear below for (row in currentY + 5..currentY + 8) { ansi.cursor(row, 1) ansi.a('â') (2 until currentX).forEach { ansi.a(' ') } } //Dice for (index in 0 until battleCheck.heroPairCount) { if (index > 0) { ansi.cursor(currentY + 1, currentX) ansi.a(" ") ansi.cursor(currentY + 2, currentX) ansi.a(if (battleCheck.method == DieBattleCheck.Method.SUM) " + " else " / ").reset() ansi.cursor(currentY + 3, currentX) ansi.a(" ") ansi.cursor(currentY + 4, currentX) ansi.a(" ") currentX += 3 } val pair = battleCheck.getHeroPairAt(index) val width = 4 + if (pair.die.size >= 10) 3 else 2 if (currentX + width + 3 > CONSOLE_WIDTH) { //Out of space for (row in currentY + 1..currentY + 4) { ansi.cursor(row, currentX) (currentX until CONSOLE_WIDTH).forEach { ansi.a(' ') } ansi.a('â') } currentY += 4 currentX = 4 + vsWidth + opponentWidth } if (battleCheck.isRolled) { drawDieSmall(currentX, currentY + 1, pair, battleCheck.getHeroResultAt(index)) } else { drawDieSmall(currentX, currentY + 1, pair) } currentX += width } //Clear the rest (currentY + 1..currentY + 4).forEach { row -> ansi.cursor(row, currentX) (currentX until CONSOLE_WIDTH).forEach { ansi.a(' ') } ansi.a('â') } if (currentY == offsetY) { //Still on the first line currentX = 4 + vsWidth + opponentWidth (currentY + 5..currentY + 8).forEach { row -> ansi.cursor(row, currentX) (currentX until CONSOLE_WIDTH).forEach { ansi.a(' ') } ansi.a('â') } } //Draw result (battleCheck.result)?.let { r -> val frameTopY = offsetY + 5 val result = String.format("%+d", r) val message = loadString(if (r >= 0) "success" else "fail").toUpperCase() val color = if (r >= 0) DARK_GREEN else DARK_RED //Frame ansi.color(color) drawHorizontalLine(frameTopY, 'â') drawHorizontalLine(frameTopY + 3, 'â') ansi.cursor(frameTopY + 1, 1).a("ââ") ansi.cursor(frameTopY + 1, CONSOLE_WIDTH - 1).a("ââ") ansi.cursor(frameTopY + 2, 1).a("ââ") ansi.cursor(frameTopY + 2, CONSOLE_WIDTH - 1).a("ââ") ansi.reset() //Top message val resultString = loadString("result") var center = (CONSOLE_WIDTH - result.length - resultString.length - 2) / 2 ansi.cursor(frameTopY + 1, 3) (3 until center).forEach { ansi.a(' ') } ansi.a(resultString).a(": ") ansi.color(color).a(result).reset() (center + result.length + resultString.length + 2 until CONSOLE_WIDTH - 1).forEach { ansi.a(' ') } //Bottom message center = (CONSOLE_WIDTH - message.length) / 2 ansi.cursor(frameTopY + 2, 3) (3 until center).forEach { ansi.a(' ') } ansi.color(color).a(message).reset() (center + message.length until CONSOLE_WIDTH - 1).forEach { ansi.a(' ') } } } private fun drawExplorationResult(offsetY: Int, pair: DiePair) { val encountered = loadString("encountered") ansi.cursor(offsetY, 1) ansi.a("â ").a(encountered).a(':') (encountered.length + 5 until CONSOLE_WIDTH).forEach { ansi.a(' ') } ansi.a('â') val dieFrameWidth = 3 + 6 * if (pair.die.size >= 10) 3 else 2 for (row in 1..8) { ansi.cursor(offsetY + row, 1) ansi.a("â ") ansi.cursor(offsetY + row, dieFrameWidth + 4) (dieFrameWidth + 4 until CONSOLE_WIDTH).forEach { ansi.a(' ') } ansi.a('â') } drawDieSizeBig(4, offsetY + 1, pair) } private fun drawHand(offsetY: Int, hand: Hand, checkedDice: HandMask, activePositions: HandMask) { val handString = loadString("hand").toUpperCase() val alliesString = loadString("allies").toUpperCase() val capacity = hand.capacity val size = hand.dieCount val slots = max(size, capacity) val alliesSize = hand.allyDieCount var currentY = offsetY var currentX = 1 //Hand title ansi.cursor(currentY, currentX) ansi.a("â ").a(handString) //Left border currentY += 1 currentX = 1 ansi.cursor(currentY, currentX) ansi.a("â â") ansi.cursor(currentY + 1, currentX) ansi.a("â â") ansi.cursor(currentY + 2, currentX) ansi.a("â â") ansi.cursor(currentY + 3, currentX) ansi.a("â ") currentX += 3 //Main hand for (i in 0 until min(slots, MAX_HAND_SIZE)) { val die = hand.dieAt(i) val longDieName = die != null && die.size >= 10 //Top border ansi.cursor(currentY, currentX) if (i < capacity) { ansi.a("ââââ").a(if (longDieName) "â" else "") } else { ansi.a("ââââ").a(if (longDieName) "â" else "") } ansi.a(if (i < capacity - 1) 'â€' else if (i == capacity - 1) 'â' else if (i < size - 1) 'â¬' else 'â') //Center row ansi.cursor(currentY + 1, currentX) ansi.a(' ') if (die != null) { drawDieSize(die, checkedDice.checkPosition(i)) } else { ansi.a(" ") } ansi.a(' ') ansi.a(if (i < capacity - 1) 'â' else if (i == capacity - 1) 'â' else 'â') //Bottom border ansi.cursor(currentY + 2, currentX) if (i < capacity) { ansi.a("ââââ").a(if (longDieName) 'â' else "") } else { ansi.a("ââââ").a(if (longDieName) 'â' else "") } ansi.a(if (i < capacity - 1) 'â§' else if (i == capacity - 1) 'â' else if (i < size - 1) 'âŽ' else 'â') //Die number ansi.cursor(currentY + 3, currentX) if (activePositions.checkPosition(i)) { ansi.color(LIGHT_YELLOW) } ansi.a(String.format(" (%s) %s", shortcut(i), if (longDieName) " " else "")) ansi.reset() currentX += 5 + if (longDieName) 1 else 0 } //Ally subhand if (alliesSize > 0) { currentY = offsetY //Ally title ansi.cursor(currentY, handString.length + 5) (handString.length + 5 until currentX).forEach { ansi.a(' ') } ansi.a(" ").a(alliesString) (currentX + alliesString.length + 5 until CONSOLE_WIDTH).forEach { ansi.a(' ') } ansi.a('â') //Left border currentY += 1 ansi.cursor(currentY, currentX) ansi.a(" â") ansi.cursor(currentY + 1, currentX) ansi.a(" â") ansi.cursor(currentY + 2, currentX) ansi.a(" â") ansi.cursor(currentY + 3, currentX) ansi.a(" ") currentX += 4 //Ally slots for (i in 0 until min(alliesSize, MAX_HAND_ALLY_SIZE)) { val allyDie = hand.allyDieAt(i)!! val longDieName = allyDie.size >= 10 //Top border ansi.cursor(currentY, currentX) ansi.a("ââââ").a(if (longDieName) "â" else "") ansi.a(if (i < alliesSize - 1) 'â¬' else 'â') //Center row ansi.cursor(currentY + 1, currentX) ansi.a(' ') drawDieSize(allyDie, checkedDice.checkAllyPosition(i)) ansi.a(" â") //Bottom border ansi.cursor(currentY + 2, currentX) ansi.a("ââââ").a(if (longDieName) "â" else "") ansi.a(if (i < alliesSize - 1) 'âŽ' else 'â') //Die number ansi.cursor(currentY + 3, currentX) if (activePositions.checkAllyPosition(i)) { ansi.color(LIGHT_YELLOW) } ansi.a(String.format(" (%s) %s", shortcut(i + 10), if (longDieName) " " else "")).reset() currentX += 5 + if (longDieName) 1 else 0 } } else { ansi.cursor(offsetY, 9) (9 until CONSOLE_WIDTH).forEach { ansi.a(' ') } ansi.a('â') ansi.cursor(offsetY + 4, currentX) (currentX until CONSOLE_WIDTH).forEach { ansi.a(' ') } ansi.a('â') } //Clear the end of the line (0..3).forEach { row -> ansi.cursor(currentY + row, currentX) (currentX until CONSOLE_WIDTH).forEach { ansi.a(' ') } ansi.a('â') } } override fun drawHeroTurnStart(hero: Hero) { val centerY = (CONSOLE_HEIGHT - 5) / 2 (1 until centerY).forEach { drawBlankLine(it, false) } ansi.color(heroColors[hero.type]) drawHorizontalLine(centerY, 'â') drawHorizontalLine(centerY + 4, 'â') ansi.reset() ansi.cursor(centerY + 1, 1).eraseLine() ansi.cursor(centerY + 3, 1).eraseLine() ansi.cursor(centerY + 2, 1) val text = String.format(loadString("heros_turn"), hero.name.toUpperCase()) val index = text.indexOf(hero.name.toUpperCase()) val center = (CONSOLE_WIDTH - text.length) / 2 ansi.cursor(centerY + 2, center) ansi.eraseLine(Ansi.Erase.BACKWARD) ansi.a(text.substring(0, index)) ansi.color(heroColors[hero.type]).a(hero.name.toUpperCase()).reset() ansi.a(text.substring(index + hero.name.length)) ansi.eraseLine(Ansi.Erase.FORWARD) (centerY + 5..CONSOLE_HEIGHT).forEach { drawBlankLine(it, false) } render() } override fun drawLocationInteriorScreen( location: Location, heroesAtLocation: List<Hero>, timer: Int, currentHero: Hero, battleCheck: DieBattleCheck?, encounteredDie: DiePair?, pickedDice: HandMask, activePositions: HandMask, statusMessage: StatusMessage, actions: ActionList) { //Top panel drawLocationTopPanel(location, heroesAtLocation, currentHero, timer) //Encounter info when { battleCheck != null -> drawBattleCheck(4, battleCheck) encounteredDie != null -> drawExplorationResult(4, encounteredDie) else -> (4..12).forEach { drawBlankLine(it) } } //Fill blank space val bottomHalfTop = CONSOLE_HEIGHT - 11 (13 until bottomHalfTop).forEach { drawBlankLine(it) } //Hero-specific info drawLocationHeroPanel(bottomHalfTop, currentHero) drawHand(bottomHalfTop + 3, currentHero.hand, pickedDice, activePositions) //Separator ansi.cursor(bottomHalfTop + 8, 1) ansi.a('â') (2 until CONSOLE_WIDTH).forEach { ansi.a('â') } ansi.a('â€') //Status and actions drawStatusMessage(bottomHalfTop + 9, statusMessage) drawActionList(bottomHalfTop + 10, actions) //Bottom border ansi.cursor(CONSOLE_HEIGHT, 1) ansi.a('â') (2 until CONSOLE_WIDTH).forEach { ansi.a('â') } ansi.a('â') //Finalize render() } override fun drawGameLoss(message: StatusMessage) { val centerY = CONSOLE_HEIGHT / 2 (1 until centerY).forEach { drawBlankLine(it, false) } val data = loadString(message.toString().toLowerCase()).toUpperCase() drawCenteredCaption(centerY, data, LIGHT_RED, false) (centerY + 1..CONSOLE_HEIGHT).forEach { drawBlankLine(it, false) } render() } }
ãã®ãã¹ãŠã®ã³ãŒãã®åäœããã§ãã¯ããããšã«é¢é£ãã1ã€ã®å°ããªåé¡ããããŸããçµã¿èŸŒã¿ã®IDEã³ã³ãœãŒã«ã¯ANSIãšã¹ã±ãŒãã·ãŒã±ã³ã¹ããµããŒãããªããããå€éšç«¯æ«ã§ã¢ããªã±ãŒã·ã§ã³ãèµ·åããå¿ èŠããããŸãïŒä»¥åã«èµ·åããããã®ã¹ã¯ãªãããæ¢ã«äœæããŸããïŒãããã«ãANSIãµããŒãã§ã¯ãWindowsã§ãã¹ãŠãããŸãããããã§ã¯ãããŸãã-ç§ã®ç¥ãéããæšæºã®cmd.exeã§ã®ã¿é«å質ã®è¡šç€ºãå¯èœã«ãªããŸãïŒãããŠãç§ãã¡ãçŠç¹ãåœãŠãªãåé¡ããããŸãïŒããŸããPowerShellã¯ã·ãŒã±ã³ã¹ãèªèããããšãããã«ã¯åŠç¿ããŸããã§ããïŒçŸåšã®èŠæ±ã«ããããããïŒãéãæªãå Žåã¯ãèœèããªãã§ãã ãã-åžžã«ä»£æ¿ãœãªã¥ãŒã·ã§ã³ããããŸãïŒããšãã°ãããïŒããããŠæ¬¡ã«é²ã¿ãŸãã
ã¹ããã10 ãŠãŒã¶ãŒå ¥å
ç»é¢ã«ç»åã衚瀺ããããšã¯ãæŠãã®ååã§ãããŠãŒã¶ãŒããå¶åŸ¡ã³ãã³ããæ£ããåä¿¡ããããšãåæ§ã«éèŠã§ãããŸãããã®ã¿ã¹ã¯ã¯ã以åã®ãã¹ãŠã®ã¿ã¹ã¯ãããå®è£ ãæè¡çã«ã¯ããã«å°é£ã§ããããšãå€æããå¯èœæ§ããããŸãããããããŸãæåã«ã
ããªããæãåºãããã«ãã¯ã©ã¹ã¡ãœãããå®è£ ããå¿ èŠã«çŽé¢ããŠããŸã
GameInteractor
ããããã¯3ã€ãããããŸããããç¹å¥ãªæ³šæãå¿ èŠã§ãããŸããåæããã¬ã€ã€ãŒãããŒãæŒããŸã§ãã²ãŒã ãšã³ãžã³ã®äœæ¥ãäžæããå¿ èŠããããŸãã第äºã«ãã¯ãªãã¯åŠçãæ®å¿µãªãããæšæºã¯ã©ã¹ã®å®¹éã¯
Reader
ã
Scanner
ã
Console
ãããã®ã»ãšãã©ã®æŒããèªèããããã«ååã§ã¯ãããŸãããç§ãã¡ã¯ãåã³ãã³ãã®åŸã«EnterããŒãæŒãã«ãŠãŒã¶ãå¿ èŠãšããŸããã
KeyListener
'aã®ãããªãã®ãå¿ èŠã§ãããããã¯Swingãã¬ãŒã ã¯ãŒã¯ã«ãã£ãããšæ¥ç¶ãããŠãããã³ã³ãœãŒã«ã¢ããªã±ãŒã·ã§ã³ã«ã¯ãã®ã°ã©ãã£ãã¯èŠæãåãã¯ãããŸããã
ã©ãããïŒãã¡ãããã©ã€ãã©ãªã®æ€çŽ¢ã¯ãä»åã¯å®å šã«ãã€ãã£ãã³ãŒãã«äŸåããŸãã ãããããªããã¯ãã¹ãã©ãããã©ãŒã ããšã¯ã©ãããæå³ã§ããïŒæ®å¿µãªããã軜éã§ãã©ãããã©ãŒã ã«äŸåããªã圢åŒã§ã·ã³ãã«ãªæ©èœãå®è£ ããã©ã€ãã©ãªããŸã èŠã€ããŠããŸããããããŸã§ã®éãã¢ã³ã¹ã¿ãŒjLineã«æ³šç®ããŸããããjLineã¯ãã³ã³ãœãŒã«ã§é«åºŠãªãŠãŒã¶ãŒã€ã³ã¿ãŒãã§ã€ã¹ãæ§ç¯ããããã®ããŒãã¹ã¿ãŒãå®è£ ããŠããŸããã¯ãããã€ãã£ãå®è£ ã§ããã¯ããWindowsãšLinux / UNIXã®äž¡æ¹ããµããŒãããŸãïŒé©åãªã©ã€ãã©ãªãæäŸããããšã«ããïŒããããŠãã¯ãã䜿çšäžã®ãã®æ©èœã®ã»ãšãã©ã¯ãæã ã¯çŸå¹Žã¯å¿ èŠãããŸãããå¿ èŠãªã®ã¯ãå°ããªãäžååã«ææžåãããæ©äŒã ãã§ããããã®äœæ¥ãããããåæããŸãã
<dependency> <groupId>jline</groupId> <artifactId>jline</artifactId> <version>2.14.6</version> <scope>compile</scope> </dependency>
3çªç®ã®ææ°ããŒãžã§ã³ã§ã¯ãªãã
ConsoleReader
methodãæã€ã¯ã©ã¹ããã2çªç®ã®ããŒãžã§ã³ãå¿ èŠã§ããããšã«æ³šæããŠãã ãã
readCharacter()
ãååã瀺ãããã«ããã®ã¡ãœããã¯ããŒããŒãã§æŒãããæåã®ã³ãŒããè¿ããŸãïŒåæã«åäœããŸããããããå¿ èŠã§ãïŒãæ®ãã¯æè¡çãªåé¡ã§ããã·ã³ãã«ãšã¢ã¯ã·ã§ã³ã®ã¿ã€ãéã®å¯Ÿå¿è¡šïŒ
Action.Type
ïŒãäœæããäžæ¹ãã¯ãªãã¯ããŠä»æ¹ãè¿ããŸãã
ãããŒããŒãã®ãã¹ãŠã®ããŒã1æåã§è¡šçŸã§ããããã§ã¯ãªãããšããåç¥ã§ããïŒå€ãã®ããŒã¯ã2ã3ã4æåã®ãšã¹ã±ãŒãã·ãŒã±ã³ã¹ã䜿çšããŸãã圌ããšäžç·ã«ããæ¹æ³ã¯ïŒã
ãæå以å€ã®ããŒãïŒç¢å°ãFããŒãããŒã ãæ¿å ¥ãPgUp / Dnãçµäºãåé€ããã³ããŒãªã©ïŒãèªèãããå Žåãå ¥åã¿ã¹ã¯ã¯è€éã«ãªãããšã«æ³šæããŠãã ãããããããç§ãã¡ã¯æãã§ããªãã®ã§ãç¶ç¶ããŸãã
ConsoleInteractor
å¿ èŠãªãµãŒãã¹ã¡ãœãããæã€ã¯ã©ã¹ãäœæããŸãããã
abstract class ConsoleInteractor { private val reader = ConsoleReader() private val mapper = mapOf( CONFIRM to 13.toChar(), CANCEL to 27.toChar(), EXPLORE_LOCATION to 'e', FINISH_TURN to 'f', ACQUIRE to 'a', LEAVE to 'l', FORFEIT to 'f', HIDE to 'h', DISCARD to 'd', ) protected fun read() = reader.readCharacter().toChar() protected open fun getIndexForKey(key: Char) = "1234567890abcdefghijklmnopqrstuvw".indexOf(key) }
ããã
mapper
ãšã¡ãœãããèšå®ããŸã
read()
ãããã«
getIndexForKey()
ããªã¹ãããã¢ã€ãã ãéžæããå¿ èŠãããå Žåããæãããã¥ãŒããéžæããå¿ èŠãããå Žåã«äœ¿çšããæ¹æ³ãæäŸããŸãããã®ã¯ã©ã¹ããã€ã³ã¿ãŒãã§ãŒã¹å®è£ ãç¶æ¿ããããšã¯æ®ã£ãŠããŸã
GameInteractor
ã
ãããŠãå®éã«ã¯ãã³ãŒãïŒ
class ConsoleGameInteractor : ConsoleInteractor(), GameInteractor { override fun anyInput() { read() } override fun pickAction(list: ActionList): Action { while (true) { val key = read() list .filter(Action::isEnabled) .find { mapper[it.type] == key } ?.let { return it } } } override fun pickDiceFromHand(activePositions: HandMask, actions: ActionList) : Action { while (true) { val key = read() actions.forEach { if (mapper[it.type] == key && it.isEnabled) return it } when (key) { in '1'..'9' -> { val index = key - '1' if (activePositions.checkPosition(index)) { return Action(HAND_POSITION, data = index) } } '0' -> { if (activePositions.checkPosition(9)) { return Action(HAND_POSITION, data = 9) } } in 'a'..'f' -> { val allyIndex = key - 'a' if (activePositions.checkAllyPosition(allyIndex)) { return Action(HAND_ALLY_POSITION, data = allyIndex) } } } } } }
ã¡ãœããã®å®è£ ã¯ãããŸããŸãªäžé©åãªãã³ã»ã³ã¹ãåºããªãããã«ãéåžžã«äžå¯§ã§ç€Œåæ£ãããã®ã§ããéžæããã¢ã¯ã·ã§ã³ãã¢ã¯ãã£ãã§ãããéžæããæã®äœçœ®ã蚱容ã»ããã«å«ãŸããŠããããšã確èªããŸãããããŠãç§ãã¡å šå¡ãç§ãã¡ã®åšãã®äººã ã«å¯ŸããŠäžå¯§ã§ããããšãé¡ã£ãŠããŸãã
ã¹ããã11ãé³ãšé³æ¥œ
ãããããããªãã§ã¯ã©ãã§ããããïŒãµãŠã³ãããªãã«ããŠã²ãŒã ããã¬ã€ããããšãããå ŽåïŒããšãã°ãèªå® ã«èª°ãããªããšãã«ã¿ãã¬ãããã«ããŒã®äžã«çœ®ããå ŽåïŒãã©ãã ãè² ããŠããããå®æã§ãããããããŸãããã²ãŒã ã®ååã ãããã¬ã€ãããããªãã®ã§ããå€ãã®ã²ãŒã ã¯ãé³ã®äŒŽå¥ãªãã§ã¯æ³åã§ããŸãããå€ãã®å Žåãããã¯äžå¯éçãªèŠä»¶ã§ãããéã®ç¶æ³ããããŸãïŒããšãã°ãååãšããŠé³ããªãå ŽåããŸãã¯ãããããªããã°æ²æšã§ããããããããªããã°è¯ãã§ãããïŒãè¯ãä»äºãããããšã¯ãå®éã«ã¯äžèŠããã»ã©ç°¡åã§ã¯ãããŸããïŒçç±ã¯å€§èŠæš¡ãªã¹ã¿ãžãªã§é«åºŠãªå°é家ããããè¡ãããã§ãïŒããããã¯ãã»ãšãã©ã®å Žåãã²ãŒã å ã«ãªãŒãã£ãªã³ã³ããŒãã³ãïŒå°ãªããšãããã€ãïŒãæã£ãŠããæ¹ãã¯ããã«è¯ãããšã§ã圌女ãå šãããªãããæåŸã®æ段ãšããŠãåŸã§é³è³ªãæ¹åã§ããŸãããæéãšæ°åãèš±ããšãã
ãã®ãžã£ã³ã«ã®ç¹æ§ã«ãããç§ãã¡ã®ã²ãŒã ã¯åäœã®ãµãŠã³ããšãã§ã¯ãã«ãã£ãŠç¹åŸŽä»ããããããšã¯ãããŸãããããŒãã²ãŒã ã®ããžã¿ã«çããã¬ã€ããå Žåããã®æå³ã¯ç解ã§ããŸããé³ã¯ãã®åäžæ§ãã¯ãããããã«éå±ã«ãªãããã°ãããããšãé³ãªãã§æŒå¥ããããšã¯æ·±å»ãªæ倱ã®ããã«ã¯èŠããªããªããŸããåé¡ã¯ããã®çŸè±¡ã«å¯ŸåŠããå¹æçãªæ¹æ³ããªããšããäºå®ã«ãã£ãŠæªåããŸããã²ãŒã ãµãŠã³ããå®å šã«ç°ãªããã®ã«çœ®ãæãããšãæéãçµã€ãšããããããŸããè¯ãã²ãŒã ã§ã¯ããµãŠã³ãã¯ã²ãŒã ãã¬ã€ãè£å®ããé²è¡äžã®ã¢ã¯ã·ã§ã³ã®é°å²æ°ãæããã«ããçãçããšãããã®ã«ããŸã-é°å²æ°ããã ã®ãŽãè¢ã®ããããŒãã«ã§ãããã²ãŒã ãã¬ã€å šäœããµã€ã³ããæããããšã§æ§æãããŠããå Žåããããéæããããšã¯å°é£ã§ããããã«ãããããããããã¯ãŸãã«ç§ãã¡ãçºèšãããã®ã§ããã·ã«ã¯ã¯ããã«ããããã£ã¹ãã¯ããã«ãããããããã倧ããªå«ã³å£°ã«ãããããŸãããŸãã§ã¹ã¯ãªãŒã³äžã®ç»åã芳å¯ããŠããªããã®ããã«ãå®éã®ç©ççãªãªããžã§ã¯ããšå®éã«ããåãããŠããããã«ããããã¯å®å šã«ããããæ§ããã«çºå£°ããå¿ èŠããããŸã-ã¹ã¯ãªããå šäœã§åã100åèãããã®ã§ãé³ãåé¢ã«åºãŠã¯ãªããŸãã-ã²ãŒã ãã¬ã€ãç©ããã«ã·ã§ãŒãã£ã³ã°ããã ãã§ãããããæèœã«éæããæ¹æ³ã¯ïŒããããªããé³ãç¹å¥ãããªããç®ç«ã€æ¬ é¥ã«æ°ã¥ãã磚ãããã«ãã§ããã ãã²ãŒã ããã¬ã€ããããšããå§ãããŸãïŒãã®ã¢ããã€ã¹ã¯ãé³ã ãã§ãªãé©çšãããŸãïŒããããæèœã«éæããæ¹æ³ã¯ïŒããããªããé³ãç¹å¥ãããªããç®ç«ã€æ¬ é¥ã«æ°ã¥ãã磚ãããã«ãã§ããã ãã²ãŒã ããã¬ã€ããããšããå§ãããŸãïŒãã®ã¢ããã€ã¹ã¯ãé³ã ãã§ãªãé©çšãããŸãïŒããããæèœã«éæããæ¹æ³ã¯ïŒããããªããé³ãç¹å¥ãããªããç®ç«ã€æ¬ é¥ã«æ°ã¥ãã磚ãããã«ãã§ããã ãã²ãŒã ããã¬ã€ããããšããå§ãããŸãïŒãã®ã¢ããã€ã¹ã¯ãé³ã ãã§ãªãé©çšãããŸãïŒã
çè«ã§ããããæŽçãããããç·Žç¿ã«ç§»ãæãæ¥ãããã§ãããããŠãã®åã«ã質åãããå¿ èŠããããŸããå®éãã²ãŒã ãã¡ã€ã«ãã©ãã§ååŸããã®ã§ãããããæãç°¡åã§ç¢ºå®ãªæ¹æ³-å€ããã€ã¯ã䜿çšããŠããŸãã¯é»è©±ã䜿çšããŠããèŠèŠããå質ã§èªåã§é²é³ããããšãã§ããŸããã€ã³ã¿ãŒãããã«ã¯ããã€ãããã«ã®äžéšã®ãããç·©ããããããŒãã§æ°·ãç ãããããŠã骚ãç ããŠã«ãªã«ãªãšããè骚ã®å¹æãå®çŸããæ¹æ³ã«é¢ãããããªããããããããŸããã·ã¥ã«ã¬ã¢ãªã¹ã ã®çŸåŠã«éŠŽæã¿ã®ãªã人ã¯ãèªåã®å£°ãå°æçšåã楜åšãšããŠäœ¿ãããšãã§ããŸãïŒãããè¡ãããäŸãæåäŸãããããŸãïŒããŸãã¯ãfreesound.orgã«ã¢ã¯ã»ã¹ã§ããŸããã£ãšåã«ä»ã®çŸäººãããªãã®ããã«ãããããå Žæãã©ã€ã»ã³ã¹ã®ã¿ã«æ³šæããŠãã ããïŒå€ãã®èè ã¯ã倧ããªå³ãåºã«æããããã³ã€ã³ã®ãªãŒãã£ãªé²é³ã«éåžžã«ææã§ã-ããªãã¯æ±ºããŠãå ã®äœæè ã«ãéãæã£ããã圌ã®åµé çãªä»®åã«èšåããªãã§åœŒãã®åŽåã®ææãæªçšããããšã¯æ±ºããŠããŸããïŒæã«ã¯éåžžã«å¥åŠã§ãïŒã³ã¡ã³ãã§ã
奜ããªãã¡ã€ã«ããã©ãã°ã¢ã³ãããããããŠãã¯ã©ã¹ãã¹ã®ã©ããã«çœ®ããŸããããããèå¥ããããã«ãåã€ã³ã¹ã¿ã³ã¹ã1ã€ã®ãµãŠã³ããšãã§ã¯ãã«å¯Ÿå¿ããåæã䜿çšããŸãã
enum class Sound { TURN_START, //Hero starts the turn BATTLE_CHECK_ROLL, //Perform check, type BATTLE_CHECK_SUCCESS, //Check was successful BATTLE_CHECK_FAILURE, //Check failed DIE_DRAW, //Draw die from bag DIE_HIDE, //Remove die to bag DIE_DISCARD, //Remove die to pile DIE_REMOVE, //Remove die entirely DIE_PICK, //Check/uncheck the die TRAVEL, //Move hero to another location ENCOUNTER_STAT, //Hero encounters STAT die ENCOUNTER_DIVINE, //Hero encounters DIVINE die ENCOUNTER_ALLY, //Hero encounters ALLY die ENCOUNTER_WOUND, //Hero encounters WOUND die ENCOUNTER_OBSTACLE, //Hero encounters OBSTACLE die ENCOUNTER_ENEMY, //Hero encounters ENEMY die ENCOUNTER_VILLAIN, //Hero encounters VILLAIN die DEFEAT_OBSTACLE, //Hero defeats OBSTACLE die DEFEAT_ENEMY, //Hero defeats ENEMY die DEFEAT_VILLAIN, //Hero defeats VILLAIN die TAKE_DAMAGE, //Hero takes damage HERO_DEATH, //Hero death CLOSE_LOCATION, //Location closed GAME_VICTORY, //Scenario completed GAME_LOSS, //Scenario failed ERROR, //When something unexpected happens }
ãµãŠã³ãã®åçæ¹æ³ã¯ããŒããŠã§ã¢ãã©ãããã©ãŒã ã«ãã£ãŠç°ãªããããã€ã³ã¿ãŒãã§ã€ã¹ã䜿çšããŠç¹å®ã®å®è£ ããæœè±¡åã§ããŸããããšãã°ãããã¯ïŒ
interface SoundPlayer { fun play(sound: Sound) }
åè¿°ã®ã€ã³ã¿ãŒãã§ã€ã¹
GameRenderer
ããã³ãšåæ§ã«
GameInteractor
ããã®å®è£ ãã¯ã©ã¹ã€ã³ã¹ã¿ã³ã¹ãžã®å ¥åã«æž¡ãå¿ èŠããããŸã
Game
ããŸããå®è£ ã¯æ¬¡ã®ããã«ãªããŸãã
class MuteSoundPlayer : SoundPlayer { override fun play(sound: Sound) { //Do nothing } }
ãã®åŸãããèå³æ·±ãå®è£ ãæ€èšããŸãããä»ã¯é³æ¥œã«ã€ããŠè©±ããŸãããã
å¹æé³ãšåæ§ã«ãã²ãŒã ã®é°å²æ°ãäœãåºãäžã§å€§ããªåœ¹å²ãæãããŸããåæ§ã«ãåªããã²ãŒã ã¯äžé©åãªé³æ¥œã«ãã£ãŠå°ç¡ãã«ãããå¯èœæ§ããããŸããé³ãšåæ§ã«ãé³æ¥œã¯æ§ããã§ãèžè¡çãªå¹æãå¿ èŠãªå Žåãé€ããŠãç»é¢äžã®ã¢ã¯ã·ã§ã³ã«é©åã«å¯Ÿå¿ããå¿ èŠããããŸãïŒåŸ ã¡äŒããã容赊ãªã殺ãããã¡ã€ã³ã®éåœã誰ããçå£ã«å¹ã蟌ãã ããšãæåŸ ããªãã§ãã ããïŒããŒããŒã圌ã®æ²åçãªæ»ã®å Žé¢ãåäŸã®æããã®æ¥œããå°ããªé³æ¥œã䌎ãå ŽåïŒããããéæããã®ã¯éåžžã«é£ãããç¹å¥ã«èšç·Žããã人ã ããã®ãããªåé¡ã«å¯ŸåŠããŸãïŒç§ãã¡ã¯ãããã«äžæ £ãã§ãïŒããã²ãŒã æ§ç¯ã®å€©æã®åå¿è ãšããŠãç§ãã¡ãäœããããããšãã§ããŸããããšãã°ãã©ããã«è¡ãfreemusicarchive.orgãŸãã¯soundcloud.comïŒãYouTubeïŒãšèªå奜ã¿ã«äœããèŠã€ããŸãããã¹ã¯ãããã®å Žåãã¢ã³ããšã³ãã¯è¯ãéžæã§ããã¯ã£ããããã¡ããã£ãŒã®ãªãéãã§æ»ãããªé³æ¥œã§ãèæ¯ã®äœæã«é©ããŠããŸããã©ã€ã»ã³ã¹ã«æ³šæãæã£ãŠãã ãããç¡æã®é³æ¥œã§ãããééçãªå ±é ¬ã§ã¯ãªãã«ããŠããå°ãªããšãæ®éçãªèªèã«å€ããæèœã®ããäœæ²å®¶ã«ãã£ãŠæžãããŠããããšããããŸãã
ãã1ã€åæãäœæããŸãããã
enum class Music { SCENARIO_MUSIC_1, SCENARIO_MUSIC_2, SCENARIO_MUSIC_3, }
åæ§ã«ãã€ã³ã¿ãŒãã§ãŒã¹ãšãã®ããã©ã«ãã®å®è£ ãå®çŸ©ããŸãã
interface MusicPlayer { fun play(music: Music) fun stop() } class MuteMusicPlayer : MusicPlayer { override fun play(music: Music) { //Do nothing } override fun stop() { //Do nothing } }
ãã®å Žåãåçãéå§ããæ¹æ³ãšåæ¢ããæ¹æ³ã®2ã€ãå¿ èŠã§ãããŸããè¿œå ã®æ¹æ³ïŒäžæåæ¢/åéãå·»ãæ»ããªã©ïŒãå°æ¥äŸ¿å©ã«ãªãå¯èœæ§ãååã«ãããŸããããããŸã§ã®ãšããããã®2ã€ã§ååã§ãã
æ¯åãªããžã§ã¯ãéã§ãã¬ãŒã€ãŒã¯ã©ã¹ãžã®åç §ãæž¡ãããšã¯ãéåžžã«äŸ¿å©ãªè§£æ±ºçã§ã¯ãªãããã§ããç§ã¯å¥ã®ãªããžã§ã¯ãã«ãµãŠã³ããé³æ¥œã¡ãœãããæŒå¥ããããã«ããããã«å¿ èŠãªãã¹ãŠã®äœãããšãææ¡ãããã³ãã£ãŒäŒæ¥ã§ãã®ã§ã1æéã§ãæã ã¯ãäžã€ã ãekzepmlyarãã¬ãŒã€ãŒãå¿ èŠäžå¹çŒïŒã·ã³ã°ã«ãã³ïŒããããã£ãŠãåãã€ã³ã¹ã¿ã³ã¹ãžã®ãªã³ã¯ã絶ããéä¿¡ããã«ãã¢ããªã±ãŒã·ã§ã³å ã®ã©ãããã§ã責任ãããªãŒãã£ãªãµãã·ã¹ãã ãåžžã«å©çšã§ããŸãã次ã®ããã«ãªããŸãã
ã¯ã©ã¹
Audio
ã¯ç§ãã¡ã®ã·ã³ã°ã«ãã³ã§ãããµãã·ã¹ãã ã«åäžã®ãã¡ãµãŒããæäŸããŸã...ãšããã§ãããã«ãã¡ãµãŒãïŒãã¡ãµãŒãïŒããããŸããããã¯ããããã®ã€ã³ã¿ãŒãããäžã§åŸ¹åºçã«èšèšãããç¹°ãè¿ãïŒäŸãšãšãã«ïŒèšè¿°ãããå¥ã®ãã¶ã€ã³ãã¿ãŒã³ã§ãããããã£ãŠããã§ã«åŸåããäžæºã®å«ã³å£°ãèããã®ã§ãç§ã¯é·ãéç¥ãããŠããããšã®èª¬æããããŠãå ã«é²ã¿ãŸããã³ãŒãã¯æ¬¡ã®ãšããã§ãã
object Audio { private var soundPlayer: SoundPlayer = MuteSoundPlayer() private var musicPlayer: MusicPlayer = MuteMusicPlayer() fun init(soundPlayer: SoundPlayer, musicPlayer: MusicPlayer) { this.soundPlayer = soundPlayer this.musicPlayer = musicPlayer } fun playSound(sound: Sound) = this.soundPlayer.play(sound) fun playMusic(music: Music) = this.musicPlayer.play(music) fun stopMusic() = this.musicPlayer.stop() }
init()
å¿ èŠãªãªããžã§ã¯ãã§åæåããããšã«ãããæåã®ã©ããã§äžåºŠã ãåŒã³åºãã ãã§ååã§ãããå°æ¥çã«ã¯å®è£ ã®è©³çŽ°ãå®å šã«å¿ããŠäŸ¿å©ãªã¡ãœããã䜿çšããŸããããªããããããªããŠããå¿é ããªãã§ãã ãããã·ã¹ãã ã¯æ»ã¬ã§ããã-ãªããžã§ã¯ãã¯ããã©ã«ãã®ã¯ã©ã¹ã«ãã£ãŠåæåãããŸãã
以äžã§ããå®éã®åçãåŠçããããã«æ®ããŸãããµãŠã³ãïŒãŸãã¯ãè³¢ã人ãèšãããã«ããµã³ãã«ïŒã®åçã«é¢ããŠã¯ãJavaã«ã¯äŸ¿å©ãªã¯ã©ã¹
AudioSystem
ãšã€ã³ã¿ãŒãã§ãŒã¹ããããŸã
Clip
ãå¿ èŠãªã®ã¯ããªãŒãã£ãªãã¡ã€ã«ãžã®ãã¹ãæ£ããèšå®ããããšã§ãïŒã¯ã©ã¹ãã¹ã«ãããŸããèŠããŠããŸããïŒïŒã
import javax.sound.sampled.AudioSystem class BasicSoundPlayer : SoundPlayer { private fun pathToFile(sound: Sound) = "/sound/${sound.toString().toLowerCase()}.wav" override fun play(sound: Sound) { val url = javaClass.getResource(pathToFile(sound)) val audioIn = AudioSystem.getAudioInputStream(url) val clip = AudioSystem.getClip() clip.open(audioIn) clip.start() } }
ã¡ãœãã
open()
ã¯ãããæšãŠãããšãã§ããŸã
IOException
ïŒç¹ã«ããã¡ã€ã«åœ¢åŒãæ°ã«å ¥ããªãã£ãå Žå-ãã®å ŽåããªãŒãã£ãªãšãã£ã¿ãŒã§ãã¡ã€ã«ãéããŠåä¿åããããšããå§ãããŸãïŒããã®ããããããã¯ã«ã©ããããããšããå§ãããŸã
try-catch
ããæåã¯ãé³ã«åé¡ããããã³ã«ã¯ã©ãã·ã¥ããŸããã
ãäœãŠèšãã°ããã®ãããããããªã...ã
é³æ¥œã§ã¯äºæ ã¯ããã«æªåããŸããç§ã®ç¥ãéããJavaã§é³æ¥œãã¡ã€ã«ïŒmp3圢åŒãªã©ïŒãåçããæšæºçãªæ¹æ³ã¯ãªãããããããã«ããŠããµãŒãããŒãã£ã®ã©ã€ãã©ãªã䜿çšããå¿ èŠããããŸãïŒããŸããŸãªã©ã€ãã©ãªããããŸãïŒãããªã人æ°ã®ããJLayerãªã©ãæå°éã®æ©èœãåãã軜éã®ãã®ãé©ããŠããŸããäŸåããŠè¿œå ããŸãïŒ
<dependencies> <dependency> <groupId>com.googlecode.soundlibs</groupId> <artifactId>jlayer</artifactId> <version>1.0.1.4</version> <scope>compile</scope> </dependency> </dependencies>
ãããŠããã¬ãŒã€ãŒã®å©ããåããŠå®è£ ããŸãã
class BasicMusicPlayer : MusicPlayer { private var currentMusic: Music? = null private var thread: PlayerThread? = null private fun pathToFile(music: Music) = "/music/${music.toString().toLowerCase()}.mp3" override fun play(music: Music) { if (currentMusic == music) { return } currentMusic = music thread?.finish() Thread.yield() thread = PlayerThread(pathToFile(music)) thread?.start() } override fun stop() { currentMusic = null thread?.finish() } // Thread responsible for playback private inner class PlayerThread(private val musicPath: String) : Thread() { private lateinit var player: Player private var isLoaded = false private var isFinished = false init { isDaemon = true } override fun run() { loop@ while (!isFinished) { try { player = Player(javaClass.getResource(musicPath).openConnection().apply { useCaches = false }.getInputStream()) isLoaded = true player.play() } catch (ex: Exception) { finish() break@loop } player.close() } } fun finish() { isFinished = true this.interrupt() if (isLoaded) { player.close() } } } }
ãŸãããã®ã©ã€ãã©ãªã¯åæçã«åçãå®è¡ãããã¡ã€ã«ã®çµããã«éãããŸã§ã¡ã€ã³ã¹ããªãŒã ããããã¯ããŸãããããã£ãŠãåå¥ã®ã¹ã¬ããïŒ
PlayerThread
ïŒãå®è£ ãããããããªãã·ã§ã³ãïŒããŒã¢ã³ïŒã«ããªããã°ãªããŸãããããããããšã§ãã¢ããªã±ãŒã·ã§ã³ãæ©æã«çµäºããããšã¯ãããŸããã次ã«ãçŸåšåçäžã®é³æ¥œãã¡ã€ã«ã®èå¥åïŒ
currentMusic
ïŒããã¬ãŒã€ãŒã³ãŒãã«ä¿åãããŸãã 2çªç®ã®ã³ãã³ããçªç¶åçãããå Žåãæåããåçãéå§ããŸããã第äžã«ãé³æ¥œãã¡ã€ã«ã®æåŸã«å°éãããšããã®åçãåã³éå§ãããŸã-ã¹ããªãŒã ãã³ãã³ãã«ãã£ãŠæ瀺çã«åæ¢ããããŸã§ç¶ããŸã
finish()
ïŒãŸãã¯æ¢ã«è¿°ã¹ãããã«ãä»ã®ã¹ã¬ãããå®äºãããŸã§ïŒã第4ã«ãäžèšã®ã³ãŒãã¯äžèŠäžå¿ èŠãªãã©ã°ãšã³ãã³ãã§ãã£ã±ãã§ããã培åºçã«ãããã°ããã³ãã¹ããããŠããŸã-ãã¬ã€ã€ãŒã¯æåŸ ã©ããã«åäœããã·ã¹ãã ãé ããããéäžã§çªç¶äžæãããã¡ã¢ãªãªãŒã¯ãåŒãèµ·ããããéºäŒåçµã¿æããªããžã§ã¯ããå«ãŸããèŒããŸã鮮床ãšçŽåºŠããããåãããããžã§ã¯ãã§å€§èã«äœ¿çšããŠãã ããã
ã¹ããã12ãããŒã«ãªãŒãŒã·ã§ã³
ç§ãã¡ã®ã²ãŒã ã¯ã»ãšãã©æºåãã§ããŠããŸããã誰ãããããã¬ã€ããŸããã ãªãã§ïŒ
ããã·ã¢èªã¯ãããŸããïŒ..ãã·ã¢èªã¯ãããŸããïŒ..ãã·ã¢èªãè¿œå ããŠãã ããïŒ..ç¬ãéçºã
ãŠãã ããïŒãåºèã®ãŠã§ããµã€ãã§é¢çœãã¹ããŒãªãŒã²ãŒã ïŒç¹ã«ã¢ãã€ã«ïŒã®ããŒãžãéããã¬ãã¥ãŒãèªãã§ãã ããã圌ãã¯çŽ æŽããããææãã®ã°ã©ãã£ãã¯ãè³è³ãå§ããŸããïŒãŸãã¯å€§æ°ã®ãµãŠã³ããã©ãã¯ã«é©åããŸããïŒãŸãã¯ãæåã®1åéããäžæ¯æ§ããããæåŸãŸã§ææŸããªããšããµã€ãã£ã³ã°ãªã¹ããŒãªãŒã話ããŸããïŒ
ããäžæºãªããã¬ã€ã€ãŒãã¯å€ãã®ãŠããããæ瀺ããéåžžã¯ã²ãŒã ãåé€ããŸãããããŠã圌ãã¯ãŸãè¿éãå¿ èŠãšããŸã-ãããŠãããããã¹ãŠã¯äžã€ã®ç°¡åãªçç±ã®ããã«ãã¯ããããªãã¯åäœã95ã®ãã¹ãŠã®èšèªã«ç¿»èš³ããã®ãå¿ããŠããŸããããããããã£ãªã¢ãæã倧声ã§å«ã¶äººãããã ãã§ãïŒåãããŸããïŒæ°ã¶æã®ããŒãã¯ãŒã¯ãé·ãç ãã¬å€ã絶ãéãªãç¥çµè¡°åŒ±-ããã¯ãã¹ãŠã尻尟ã®äžã®ãã ã¹ã¿ãŒã§ããèšå€§ãªæ°ã®ãã¬ã€ã€ãŒã倱ããŸããããããã¯ä¿®æ£ã§ããŸããã
ã ããå ã«èããŠãã ããã察象èªè ã決å®ããããã€ãã®äž»èŠèšèªãéžæãã翻蚳ãµãŒãã¹ã泚æããŸã...äžè¬çã«ãä»ã®äººãããŒãèšäºã§è€æ°å説æããããšããã¹ãŠè¡ããŸãïŒç§ãããè³¢ãïŒãåé¡ã®æè¡çãªåŽé¢ã«çŠç¹ãåœãŠã補åãå®å šã«ããŒã«ã©ã€ãºããæ¹æ³ã«ã€ããŠèª¬æããŸãã
ãŸãããã³ãã¬ãŒãã«å ¥ããŸããååãšèª¬æãåçŽãªãã®ãšããŠä¿åãããåã«èŠããŠã
String
ãŸããïŒä»ã§ã¯æ©èœããŸãããããã©ã«ãã®èšèªã«å ããŠããµããŒãããäºå®ã®ãã¹ãŠã®èšèªãžã®ç¿»èš³ãæäŸããå¿ èŠããããŸããããšãã°ã次ã®ããã«ïŒ
class TestEnemyTemplate : EnemyTemplate { override val name = "Test enemy" override val description = "Some enemy standing in your way." override val nameLocalizations = mapOf( "ru" to " -", "ar" to "ؚعض اÙعدÙ", "iw" to "×××× ××××", "zh" to "äžäºæµäºº", "ua" to "Ñ " ) override val descriptionLocalizations = mapOf( "ru" to " - .", "ar" to "Ùص٠اÙعدÙ", "iw" to "ת×××ך ×××××", "zh" to "äžäºæµäººçæè¿°", "ua" to " Ñ Ñ ." ) override val traits = listOf<Trait>() }
ãã³ãã¬ãŒãã®å Žåããã®ã¢ãããŒãã¯éåžžã«é©ããŠããŸããã©ã®èšèªã®ç¿»èš³ãæå®ããããªãå Žåã¯ããã®å¿ èŠã¯ãããŸãã-åžžã«ããã©ã«ãå€ããããŸãããã ããæçµçãªãªããžã§ã¯ãã§ã¯ãè€æ°ã®ç°ãªããã£ãŒã«ãã«è¡ãåºããããªãã§ãããããããã£ãŠã1ã€ãæ®ããŸããããã®ã¿ã€ãã¯çœ®ãæããŸãã
class LocalizedString(defaultValue: String, localizations: Map<String, String>) { private val default: String = defaultValue private val values: Map<String, String> = localizations.toMap() operator fun get(lang: String) = values.getOrDefault(lang, default) override fun equals(other: Any?) = when { this === other -> true other !is LocalizedString -> false else -> default == other.default } override fun hashCode(): Int { return default.hashCode() } }
ããã«å¿ããŠãžã§ãã¬ãŒã¿ãŒã³ãŒããä¿®æ£ããŸãã
fun generateEnemy(template: EnemyTemplate) = Enemy().apply { name = LocalizedString(template.name, template.nameLocalizations) description = LocalizedString(template.description, template.descriptionLocalizations) template.traits.forEach { addTrait(it) } }
åœç¶ãåãã¢ãããŒããæ®ãã®ã¿ã€ãã®ãã³ãã¬ãŒãã«é©çšããå¿ èŠããããŸããå€æŽã®æºåãã§ããããåé¡ãªã䜿çšã§ããŸãã
val language = Locale.getDefault().language val enemyName = enemy.name[language]
ãã®äŸã§ã¯ãèšèªã®ã¿ãèæ ®ãããããŒã«ã©ã€ãºã®ç°¡æããŒãžã§ã³ãæäŸããŠããŸããäžè¬ã«ãã¯ã©ã¹ãªããžã§ã¯ã
Locale
ã¯åœãšå°åãå®çŸ©ããŸãããããã¢ããªã±ãŒã·ã§ã³ã§éèŠãªå Žåã¯ã
LocalizedString
å€èŠ³ãå°ãç°ãªããŸããããšã«ããæºè¶³ããŠããŸãã
ãã³ãã¬ãŒããåŠçããŸããããã¢ããªã±ãŒã·ã§ã³ã§äœ¿çšããããµãŒãã¹ã©ã€ã³ãããŒã«ã©ã€ãºããå¿ èŠããããŸãã幞ããªããšã«ãããã«
ResourceBundle
ã¯å¿ èŠãªã¡ã«ããºã ããã¹ãŠå«ãŸããŠããŸãã翻蚳æžã¿ã®ãã¡ã€ã«ãæºåããããŠã³ããŒãæ¹æ³ãå€æŽããã ãã§ãã
# Game status messages choose_dice_perform_check= : end_of_turn_discard_extra= : : end_of_turn_discard_optional= : : choose_action_before_exploration=, : choose_action_after_exploration= . ? encounter_physical= . . encounter_somatic= . . encounter_mental= . . encounter_verbal= . . encounter_divine= . : die_acquire_success= ! die_acquire_failure= . game_loss_out_of_time= # Die types physical= somatic= mental= verbal= divine= ally= wound= enemy= villain= obstacle= # Hero types and descriptions brawler= hunter= # Various labels avg= bag= bag_size= class= closed= discard= empty= encountered= fail= hand= heros_turn= %s max= min= perform_check= : pile= received_new_die= result= success= sum= time= total= # Action names and descriptions action_confirm_key=ENTER action_confirm_name= action_cancel_key=ESC action_cancel_name= action_explore_location_key=E action_explore_location_name= action_finish_turn_key=F action_finish_turn_name= action_hide_key=H action_bag_name= action_discard_key=D action_discard_name= action_acquire_key=A action_acquire_name= action_leave_key=L action_leave_name= action_forfeit_key=F action_forfeit_name=
èšé²ã®ããã«ç§ã¯èšããŸããïŒãã·ã¢èªã§ãã¬ãŒãºãæžãããšã¯è±èªããã¯ããã«è€éã§ãã決å®çãªã±ãŒã¹ã§åè©ã䜿çšããããæ§å¥ããé¢è±ããå¿ èŠãããå ŽåïŒããã³ãã®ãããªèŠä»¶ã¯å¿ ãæç«ããŸãïŒããŸãèŠä»¶ãæºããã次ã«ãµã€ããŒã°ã«ãã£ãŠè¡ãããæ©æ¢°ç¿»èš³ã®ããã«èŠããªãçµæãåŸãããã«æ±ããããªããã°ãªããŸããé¶ã®è³ãæã€ããŸããã¢ã¯ã·ã§ã³ããŒãå€æŽããªãããšã«æ³šæããŠãã ãã-以åãšåãããã«ãåŸè ã¯è±èªãšåãæåã䜿çšããŠå®è¡ãããŸãïŒã¡ãªã¿ã«ãã©ãã³èªä»¥å€ã®ããŒããŒãã¬ã€ã¢ãŠãã§ã¯æ©èœããŸããããããã¯ç§ãã¡ã®ããžãã¹ã§ã¯ãããŸãã-ä»ã®ãšããã¯ãã®ãŸãŸã«ããŠãããŸãïŒã
class PropertiesStringLoader(locale: Locale) : StringLoader { private val properties = ResourceBundle.getBundle("text.strings", locale) override fun loadString(key: String) = properties.getString(key) ?: "" }
ã
ãã§ã«è¿°ã¹ãããã«ã
ResourceBundle
圌èªèº«ãããŒã«ã©ã€ãºãã¡ã€ã«ã®äžããçŸåšã®ãã±ãŒã«ã«æãè¿ããã®ãèŠã€ãã責任ãè² ããŸãããããŠãèŠã€ãããªãå Žåã¯ãããã©ã«ãã®ãã¡ã€ã«ïŒ
string.properties
ïŒãååŸããŸãããããŠããã¹ãŠãããŸããããŸã...
ããïŒãã£ãïŒ
, Unicode
Java 9. ISO-8859-1 â
. , , â . Unicode- â , , :
. , , Java native2ascii , . :
. â . â . , IDE ( ) « », â - ( ), IDE, .
, .
, , ,
â - .
, , :
, ⊠, ( ) â ( Kotlin ). â ,
UTF-8 - .
.properties
Java 9. ISO-8859-1 â
ResourceBundle
. , , â . Unicode- â , , :
'\uXXXX'
. , , Java native2ascii , . :
# Game status messages choose_dice_perform_check=\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043a\u0443\u0431\u0438\u043a\u0438 \u0434\u043b\u044f \u043f\u0440\u043e\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438: end_of_turn_discard_extra=\u041a\u041e\u041d\u0415\u0426 \u0425\u041e\u0414\u0410: \u0421\u0431\u0440\u043e\u0441\u044c\u0442\u0435 \u043b\u0438\u0448\u043d\u0438\u0435 \u043a\u0443\u0431\u0438\u043a\u0438: end_of_turn_discard_optional=\u041a\u041e\u041d\u0415\u0426 \u0425\u041e\u0414\u0410: \u0421\u0431\u0440\u043e\u0441\u044c\u0442\u0435 \u043a\u0443\u0431\u0438\u043a\u0438 \u043f\u043e \u0436\u0435\u043b\u0430\u043d\u0438\u044e: choose_action_before_exploration=\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435, \u0447\u0442\u043e \u0434\u0435\u043b\u0430\u0442\u044c: choose_action_after_exploration=\u0418\u0441\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u043d\u0438\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043e. \u0427\u0442\u043e \u0434\u0435\u043b\u0430\u0442\u044c \u0434\u0430\u043b\u044c\u0448\u0435? encounter_physical=\u0412\u0441\u0442\u0440\u0435\u0447\u0435\u043d \u0424\u0418\u0417\u0418\u0427\u0415\u0421\u041a\u0418\u0419 \u043a\u0443\u0431\u0438\u043a. \u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043f\u0440\u043e\u0439\u0442\u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443. encounter_somatic=\u0412\u0441\u0442\u0440\u0435\u0447\u0435\u043d \u0421\u041e\u041c\u0410\u0422\u0418\u0427\u0415\u0421\u041a\u0418\u0419 \u043a\u0443\u0431\u0438\u043a. \u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043f\u0440\u043e\u0439\u0442\u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443. encounter_mental=\u0412\u0441\u0442\u0440\u0435\u0447\u0435\u043d \u041c\u0415\u041d\u0422\u0410\u041b\u042c\u041d\u042b\u0419 \u043a\u0443\u0431\u0438\u043a. \u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043f\u0440\u043e\u0439\u0442\u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443. encounter_verbal=\u0412\u0441\u0442\u0440\u0435\u0447\u0435\u043d \u0412\u0415\u0420\u0411\u0410\u041b\u042c\u041d\u042b\u0419 \u043a\u0443\u0431\u0438\u043a. \u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043f\u0440\u043e\u0439\u0442\u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443. encounter_divine=\u0412\u0441\u0442\u0440\u0435\u0447\u0435\u043d \u0411\u041e\u0416\u0415\u0421\u0422\u0412\u0415\u041d\u041d\u042b\u0419 \u043a\u0443\u0431\u0438\u043a. \u041c\u043e\u0436\u043d\u043e \u0432\u0437\u044f\u0442\u044c \u0431\u0435\u0437 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438: die_acquire_success=\u0412\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0438 \u043d\u043e\u0432\u044b\u0439 \u043a\u0443\u0431\u0438\u043a! die_acquire_failure=\u0412\u0430\u043c \u043d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u043a\u0443\u0431\u0438\u043a. game_loss_out_of_time=\u0423 \u0432\u0430\u0441 \u0437\u0430\u043a\u043e\u043d\u0447\u0438\u043b\u043e\u0441\u044c \u0432\u0440\u0435\u043c\u044f
. â . â . , IDE ( ) « », â - ( ), IDE, .
, .
getBundle()
, , ,
ResourceBundle.Control
â - .
class PropertiesStringLoader(locale: Locale) : StringLoader { private val properties = ResourceBundle.getBundle( "text.strings", locale, Utf8ResourceBundleControl()) override fun loadString(key: String) = properties.getString(key) ?: "" }
, , :
class Utf8ResourceBundleControl : ResourceBundle.Control() { @Throws(IllegalAccessException::class, InstantiationException::class, IOException::class) override fun newBundle(baseName: String, locale: Locale, format: String, loader: ClassLoader, reload: Boolean): ResourceBundle? { val bundleName = toBundleName(baseName, locale) return when (format) { "java.class" -> super.newBundle(baseName, locale, format, loader, reload) "java.properties" -> with((if ("://" in bundleName) null else toResourceName(bundleName, "properties")) ?: return null) { when { reload -> reload(this, loader) else -> loader.getResourceAsStream(this) }?.let { stream -> InputStreamReader(stream, "UTF-8").use { r -> PropertyResourceBundle(r) } } } else -> throw IllegalArgumentException("Unknown format: $format") } } @Throws(IOException::class) private fun reload(resourceName: String, classLoader: ClassLoader): InputStream { classLoader.getResource(resourceName)?.let { url -> url.openConnection().let { connection -> connection.useCaches = false return connection.getInputStream() } } throw IOException("Unable to load data!") } }
, ⊠, ( ) â ( Kotlin ). â ,
.properties
UTF-8 - .
ããŸããŸãªèšèªã§ã¢ããªã±ãŒã·ã§ã³ã®åäœããã¹ãããããã«ããªãã¬ãŒãã£ã³ã°ã·ã¹ãã ã®èšå®ãå€æŽããå¿ èŠã¯ãããŸãããJREã®èµ·åæã«å¿ èŠãªèšèªãæå®ããã ãã§ãã
java -Duser.language=ru -jar path_to_project\Dice\target\dice-1.0-jar-with-dependencies.jar
ãŸã Windowsã§äœæ¥ããŠããå Žåã¯ãåé¡ãäºæããŠãã ãã
, Windows (cmd.exe) 437 ( DOSLatinUS), â . , UTF-8 , :
Java , , . :
, , Unicode- (, Lucida Console)
chcp 65001
Java , , . :
java -Dfile.encoding=UTF-8 -Duser.language=ru -jar path_to_project\Dice\target\dice-1.0-jar-with-dependencies.jar
, , Unicode- (, Lucida Console)
ãã¹ãŠã®ãšããµã€ãã£ã³ã°ãªåéºã®åŸãçµæã¯èªãããã«äžè¬å€§è¡ã«ç€ºããã倧声ã§å®£èšãããŸãïŒãç§ãã¡ã¯ç¬ã§ã¯ãããŸããïŒã
ãããŠããã¯è¯ãããšã§ãã
ã¹ããã13 ãã¹ãŠããŸãšãã
æ°é ãã®ããèªè ã¯ãç¹å®ã®ããã±ãŒãžã®ååã«äžåºŠã ãèšåãã決ããŠæ»ã£ãŠããªãã£ãããšã«æ°ä»ããã«éããããŸããããŸããåéçºè ã¯ãã©ã®ã¯ã©ã¹ãã©ã®ããã±ãŒãžã«é 眮ãããã«é¢ããŠãç¬èªã®èæ ®äºé ãæã£ãŠããŸãã第äºã«ããããžã§ã¯ãã«åãçµãã«ã€ããŠãããå€ãã®æ°ããã¯ã©ã¹ãè¿œå ããããšãèããå€ãããŸãã第äžã«ãã¢ããªã±ãŒã·ã§ã³ã®æ§é ã®å€æŽã¯ç°¡åã§å®äŸ¡ã§ãïŒãããŠãææ°ã®ããŒãžã§ã³ç®¡çã·ã¹ãã ã移è¡ãæ€åºãããããå±¥æŽã倱ãããšã¯ãããŸããïŒãã¯ã©ã¹ãããã±ãŒãžãã¡ãœãããå€æ°ã®ååã倧èã«å€æŽããŸã-ããã¥ã¡ã³ããæŽæ°ããããšãå¿ããªãã§ãã ãããããã§ããïŒïŒã
ãããŠç§ãã¡ã«æ®ã£ãŠããã®ã¯ããããžã§ã¯ãããŸãšããŠç«ã¡äžããããšã ãã§ããèŠããŠããããã«ãã¡ãœãã
main()
ããã§ã«äœæããŸãããä»åºŠã¯ãã®å 容ãåããŸããå¿ èŠãªãã®ïŒ
- ã¹ã¯ãªãããšå°åœ¢;
- ããŒããŒãº
- ã€ã³ã¿ãŒãã§ã€ã¹ã®å®è£
GameInteractor
ã - ã€ã³ã¿ãŒãã§ã€ã¹ã®å®è£
GameRenderer
ããã³StringLoader
; - ã€ã³ã¿ãŒãã§ã€ã¹ã®å®è£
SoundPlayer
ããã³MusicPlayer
; - ã¯ã©ã¹ãªããžã§ã¯ã
Game
ã - ã·ã£ã³ãã³ã®ããã«ã
è¡ããïŒ
fun main(args: Array<String>) { Audio.init(BasicSoundPlayer(), BasicMusicPlayer()) val loader = PropertiesStringLoader(Locale.getDefault()) val renderer = ConsoleGameRenderer(loader) val interactor = ConsoleGameInteractor() val template = TestScenarioTemplate() val scenario = generateScenario(template, 1) val locations = generateLocations(template, 1, heroes.size) val heroes = listOf( generateHero(Hero.Type.BRAWLER, "Brawler"), generateHero(Hero.Type.HUNTER, "Hunter") ) val game = Game(renderer, interactor, scenario, locations, heroes) game.start() }
ç§ãã¡ã¯æåã®å®çšãããã¿ã€ããç«ã¡äžããŠæ¥œãã¿ãŸããè¡ãã
ã¹ããã14ãã²ãŒã ãã©ã³ã¹
ããŒã...
ã¹ããã15ããã¹ã
ä»ããããšãæžãããæåã®äœæ¥ãããã¿ã€ãã®ã³ãŒãã®äž»èŠãªéšåã¯ããŠããããã¹ãã®ã«ããã«ãè¿œå ããŠããã ãã...
ãã©ã®ããã«ïŒãã£ãä»ïŒã¯ãããã¹ãã¯æåã«èšè¿°ãã次ã«ã³ãŒããèšè¿°ããªããã°ãªããŸããã§ããïŒã
å€ãã®èªè ã¯ãåäœãã¹ãã®èšè¿°ãäœæ¥ã³ãŒãïŒTDDãã®ä»ã®ãã¡ãã·ã§ããã«ãªæ¹æ³è«ïŒãä»ã®äººã¯æ¿æããŸãïŒå°ãªããšãäœããéçºãå§ãããšããŠãã人ã ããã¹ãã§é ãã ãŸãããšã¯äœããããŸãããå¥ã®ã«ããã«ãããŒã¹ããŒãã®ééããofãåºããŠãããããã®ãã¹ããå¿ èŠãªçç±ãããããªã-ãã¹ãŠãç§ã«ãšã£ãŠããŸãããããšâç ã«èšãã§ããã...ãããŠåœŒãã¯ããŒãã§é¡ã«æŒã蟌ãŸããããã«æŒãæ»ãããŸããç§ã¯ã€ããªãã®ãŒç察ç«ãéå§ãå§ããŸããïŒåœŒãã¯ãã§ã«ã€ã³ã¿ãŒãããäžã§ãããã«æºã¡ãŠããŸãïŒããããã£ãŠãç§ã¯ãã¹ãŠã®äººã«éšåçã«åæããŸããã¯ãããã¹ãã¯æã 圹ã«ç«ã¡ãŸãïŒç¹ã«ãé »ç¹ã«å€æŽãããããŸãã¯è€éãªèšç®ã«é¢é£ããã³ãŒãã§ïŒãã¯ãããŠããããã¹ãã¯ãã¹ãŠã®ã³ãŒãã«é©ããŠããŸããïŒããšãã°ããŠãŒã¶ãŒãŸãã¯å€éšã·ã¹ãã ãšã®çžäºäœçšãã«ããŒããŸããïŒãã¯ãããŠããããã¹ã以å€ã«ããããŸãä»ã®çš®ã®å€ãïŒãŸããå°ãªããšã5ã€ãåœåãããïŒããããŠãã¯ããç§ãã¡ã¯ãã¹ããæžãããšã«éäžããŸãã-ç§ãã¡ã®èšäºã¯äœãä»ã®ãã®ã«ã€ããŠã§ãã
å€ãã®ããã°ã©ããŒïŒç¹ã«åå¿è ïŒã¯ãã¹ããæ ããŸããå€ãã®äººã¯ãã¢ããªã±ãŒã·ã§ã³ã®æ©èœããã¹ãã§ååã«ã«ããŒãããŠããªããšèšãããšã§æ£åœåããŠããŸããããšãã°ããŠãŒã¶ãŒã€ã³ã¿ãŒãã§ãŒã¹ããã¹ãããããã®ç¹æ®ãªãã¬ãŒã ã¯ãŒã¯ã䜿çšããŠè€éãªæ§é ããã§ã³ã¹ããããããã¢ããªã±ãŒã·ã§ã³ãèµ·åããŠããã¹ãŠãå€èŠ³ãšçžäºäœçšã«åé¡ããªããã©ããã確èªããæ¹ãã¯ããã«ç°¡åã§ãããããŠãã€ã³ã¿ãŒãã§ã€ã¹ãå®è£ ããŠãããšãã«ãç¥ããããŸã
Renderer
-ããã ãã§ããããã ããã³ãŒãã®äžã«ã¯ãåäœãã¹ãã®æŠå¿µãåªããŠããã¡ãœããããããŸãã
ããšãã°ããžã§ãã¬ãŒã¿ãŒããããŠããã ãã§ããããã¯çæ³çãªãã©ãã¯ããã¯ã¹ã§ãããã³ãã¬ãŒãã¯å ¥åã«éä¿¡ãããã²ãŒã ã¯ãŒã«ãã®ãªããžã§ã¯ãã¯åºåã§ååŸãããŸããå éšã§ã¯äœãèµ·ãããŸããããã¹ãããå¿ èŠãããã®ã¯ãŸãã«åœŒã§ããããšãã°ã次ã®ããã«ïŒ
public class DieGeneratorTest { @Test public void testGetMaxLevel() { assertEquals("Max level should be 3", 3, DieGeneratorKt.getMaxLevel()); } @Test public void testDieGenerationSize() { DieTypeFilter filter = new SingleDieTypeFilter(Die.Type.ALLY); List<? extends List<Integer>> allowedSizes = Arrays.asList( null, Arrays.asList(4, 6, 8), Arrays.asList(4, 6, 8, 10), Arrays.asList(6, 8, 10, 12) ); IntStream.rangeClosed(1, 3).forEach(level -> { for (int i = 0; i < 10; i++) { int size = DieGeneratorKt.generateDie(filter, level).getSize(); assertTrue("Incorrect level of die generated: " + size, allowedSizes.get(level).contains(size)); assertTrue("Incorrect die size: " + size, size >= 4); assertTrue("Incorrect die size: " + size, size <= 12); assertTrue("Incorrect die size: " + size, size % 2 == 0); } }); } @Test public void testDieGenerationType() { List<Die.Type> allowedTypes1 = Arrays.asList(Die.Type.PHYSICAL); List<Die.Type> allowedTypes2 = Arrays.asList(Die.Type.PHYSICAL, Die.Type.SOMATIC, Die.Type.MENTAL, Die.Type.VERBAL); List<Die.Type> allowedTypes3 = Arrays.asList(Die.Type.ALLY, Die.Type.VILLAIN, Die.Type.ENEMY); for (int i = 0; i < 10; i++) { Die.Type type1 = DieGeneratorKt.generateDie(new SingleDieTypeFilter(Die.Type.PHYSICAL), 1).getType(); assertTrue("Incorrect die type: " + type1, allowedTypes1.contains(type1)); Die.Type type2 = DieGeneratorKt.generateDie(new StatsDieTypeFilter(), 1).getType(); assertTrue("Incorrect die type: " + type2, allowedTypes2.contains(type2)); Die.Type type3 = DieGeneratorKt.generateDie(new MultipleDieTypeFilter(Die.Type.ALLY, Die.Type.VILLAIN, Die.Type.ENEMY), 1).getType(); assertTrue("Incorrect die type: " + type3, allowedTypes3.contains(type3)); } } }
ãŸãã¯ïŒ
public class BagGeneratorTest { @Test public void testGenerateBag() { BagTemplate template1 = new BagTemplate(); template1.addPlan(0, 10, new SingleDieTypeFilter(Die.Type.PHYSICAL)); template1.addPlan(5, 5, new SingleDieTypeFilter(Die.Type.SOMATIC)); template1.setFixedDieCount(null); BagTemplate template2 = new BagTemplate(); template2.addPlan(10, 10, new SingleDieTypeFilter(Die.Type.DIVINE)); template2.setFixedDieCount(5); BagTemplate template3 = new BagTemplate(); template3.addPlan(10, 10, new SingleDieTypeFilter(Die.Type.ALLY)); template3.setFixedDieCount(50); for (int i = 0; i < 10; i++) { Bag bag1 = BagGeneratorKt.generateBag(template1, 1); assertTrue("Incorrect bag size: " + bag1.getSize(), bag1.getSize() >= 5 && bag1.getSize() <= 15); assertEquals("Incorrect number of SOMATIC dice", 5, bag1.examine().stream().filter(d -> d.getType() == Die.Type.SOMATIC).count()); Bag bag2 = BagGeneratorKt.generateBag(template2, 1); assertEquals("Incorrect bag size", 5, bag2.getSize()); Bag bag3 = BagGeneratorKt.generateBag(template3, 1); assertEquals("Incorrect bag size", 50, bag3.getSize()); List<Die.Type> dieTypes3 = bag3.examine().stream().map(Die::getType).distinct().collect(Collectors.toList()); assertEquals("Incorrect die types", 1, dieTypes3.size()); assertEquals("Incorrect die types", Die.Type.ALLY, dieTypes3.get(0)); } } }
ãŸãã¯ãã®ããã«ïŒ
public class LocationGeneratorTest { private void testLocationGeneration(String name, LocationTemplate template) { System.out.println("Template: " + template.getName()); assertEquals("Incorrect template type", name, template.getName()); IntStream.rangeClosed(1, 3).forEach(level -> { Location location = LocationGeneratorKt.generateLocation(template, level); assertEquals("Incorrect location type", name, location.getName().get("")); assertTrue("Location not open by default", location.isOpen()); int closingDifficulty = location.getClosingDifficulty(); assertTrue("Closing difficulty too small", closingDifficulty > 0); assertEquals("Incorrect closing difficulty", closingDifficulty, template.getBasicClosingDifficulty() + level * 2); Bag bag = location.getBag(); assertNotNull("Bag is null", bag); assertTrue("Bag is empty", location.getBag().getSize() > 0); Deck<Enemy> enemies = location.getEnemies(); assertNotNull("Enemies are null", enemies); assertEquals("Incorrect enemy threat count", enemies.getSize(), template.getEnemyCardsCount()); if (bag.drawOfType(Die.Type.ENEMY) != null) { assertTrue("Enemy cards not specified", enemies.getSize() > 0); } Deck<Obstacle> obstacles = location.getObstacles(); assertNotNull("Obstacles are null", obstacles); assertEquals("Incorrect obstacle threat count", obstacles.getSize(), template.getObstacleCardsCount()); List<SpecialRule> specialRules = location.getSpecialRules(); assertNotNull("SpecialRules are null", specialRules); }); } @Test public void testGenerateLocation() { testLocationGeneration("Test Location", new TestLocationTemplate()); testLocationGeneration("Test Location 2", new TestLocationTemplate2()); } }
ãã¹ããããã¹ããããã¹ãããïŒãããã¯äœïŒ Java ???ã
ããªãã¯ç解ããŠããŸããããã«ããžã§ãã¬ãŒã¿ãŒèªäœã®å®è£ ãéå§ããåã«ãæåã«ãã®ãããªãã¹ããèšè¿°ããããšããå§ãããŸãããã¡ããããã¹ã察象ã®ã³ãŒãã¯éåžžã«åçŽã§ãããã¡ãœããã¯ãã¹ããªãã§åããŠåäœããå¯èœæ§ãé«ãã§ã
ãã®ä»ãã¯ã©ã¹
HandMaskRule
ãšãã®çžç¶äººãèŠããŠããŸããïŒããæç¹ã§ãã¹ãã«ã䜿çšããããã«ããŒããŒã3ã€ã®ãµã€ã³ããæããåãå¿ èŠãããããããã®ãµã€ã³ãã®çš®é¡ã«ã¯å³ããå¶éãããããšãæ³åããŠãã ããïŒããšãã°ããæåã®ãµã€ã³ãã¯éãç·ãçœã2çªç®ã¯é»è²ãçœãéããããŠ3çªç®-éãŸãã¯çŽ« "-ããªãã¯å°é£ãæããŸããïŒïŒãã¯ã©ã¹ã®å®è£ ã«ã¢ãããŒãããæ¹æ³ã¯ïŒãŸã...ãŸããå ¥åãã©ã¡ãŒã¿ãšåºåãã©ã¡ãŒã¿ã決ããããšãã§ããŸããæããã«ã3ã€ã®é åïŒãŸãã¯ã»ããïŒãåãå ¥ããã¯ã©ã¹ãå¿ èŠã§ããåé åã«ã¯ãããããã1çªç®ã2çªç®ã3çªç®ã®ãã¥ãŒãã®æå¹ãªåãå«ãŸããŠããŸããããããäœïŒã€ã¶ããŸããïŒååž°ïŒäœããèŠéãããã©ããªããŸããïŒæ·±ãå ¥ãå£ãäœããŸããã¯ã©ã¹ã¡ãœããã®å®è£ ã延æãããã¹ããäœæããŸããèŠä»¶ã¯ã·ã³ãã«ã§ç解ãããããæ£åŒã«å®åŒåãããŠããããã§ãããããŠãããã€ãã®ãã¹ããäœæããæ¹ãè¯ãã§ããã...ããããããã§ã¯ãã®ãããªãã®ãæ€èšããŸãïŒ
public class TripleDieHandMaskRuleTest { private Hand hand; @Before public void init() { hand = new Hand(10); hand.addDie(new Die(Die.Type.PHYSICAL, 4)); //0 hand.addDie(new Die(Die.Type.PHYSICAL, 4)); //1 hand.addDie(new Die(Die.Type.SOMATIC, 4)); //2 hand.addDie(new Die(Die.Type.SOMATIC, 4)); //3 hand.addDie(new Die(Die.Type.MENTAL, 4)); //4 hand.addDie(new Die(Die.Type.MENTAL, 4)); //5 hand.addDie(new Die(Die.Type.VERBAL, 4)); //6 hand.addDie(new Die(Die.Type.VERBAL, 4)); //7 hand.addDie(new Die(Die.Type.DIVINE, 4)); //8 hand.addDie(new Die(Die.Type.DIVINE, 4)); //9 hand.addDie(new Die(Die.Type.ALLY, 4)); //A (0) hand.addDie(new Die(Die.Type.ALLY, 4)); //B (1) } @Test public void testRule1() { HandMaskRule rule = new TripleDieHandMaskRule( hand, new Die.Type[]{Die.Type.PHYSICAL, Die.Type.SOMATIC}, new Die.Type[]{Die.Type.MENTAL, Die.Type.VERBAL}, new Die.Type[]{Die.Type.PHYSICAL, Die.Type.ALLY} ); HandMask mask = new HandMask(); assertTrue("Ally should be on", rule.isAllyPositionActive(mask, 0)); assertTrue("Ally should be on", rule.isAllyPositionActive(mask, 1)); assertTrue("Should be on", rule.isPositionActive(mask, 0)); assertTrue("Should be on", rule.isPositionActive(mask, 1)); assertTrue("Should be on", rule.isPositionActive(mask, 2)); assertTrue("Should be on", rule.isPositionActive(mask, 3)); assertTrue("Should be on", rule.isPositionActive(mask, 4)); assertTrue("Should be on", rule.isPositionActive(mask, 5)); assertTrue("Should be on", rule.isPositionActive(mask, 6)); assertTrue("Should be on", rule.isPositionActive(mask, 7)); assertFalse("Should be off", rule.isPositionActive(mask, 8)); assertFalse("Should be off", rule.isPositionActive(mask, 9)); assertFalse("Rule should not be met yet", rule.checkMask(mask)); mask.addPosition(0); assertTrue("Ally should be on", rule.isAllyPositionActive(mask, 0)); assertTrue("Ally should be on", rule.isAllyPositionActive(mask, 1)); assertTrue("Should be on", rule.isPositionActive(mask, 0)); assertTrue("Should be on", rule.isPositionActive(mask, 1)); assertTrue("Should be on", rule.isPositionActive(mask, 2)); assertTrue("Should be on", rule.isPositionActive(mask, 3)); assertTrue("Should be on", rule.isPositionActive(mask, 4)); assertTrue("Should be on", rule.isPositionActive(mask, 5)); assertTrue("Should be on", rule.isPositionActive(mask, 6)); assertTrue("Should be on", rule.isPositionActive(mask, 7)); assertFalse("Should be off", rule.isPositionActive(mask, 8)); assertFalse("Should be off", rule.isPositionActive(mask, 9)); assertFalse("Rule should not be met yet", rule.checkMask(mask)); mask.addPosition(4); assertTrue("Ally should be on", rule.isAllyPositionActive(mask, 0)); assertTrue("Ally should be on", rule.isAllyPositionActive(mask, 1)); assertTrue("Should be on", rule.isPositionActive(mask, 0)); assertTrue("Should be on", rule.isPositionActive(mask, 1)); assertTrue("Should be on", rule.isPositionActive(mask, 2)); assertTrue("Should be on", rule.isPositionActive(mask, 3)); assertTrue("Should be on", rule.isPositionActive(mask, 4)); assertFalse("Should be off", rule.isPositionActive(mask, 5)); assertFalse("Should be off", rule.isPositionActive(mask, 6)); assertFalse("Should be off", rule.isPositionActive(mask, 7)); assertFalse("Should be off", rule.isPositionActive(mask, 8)); assertFalse("Should be off", rule.isPositionActive(mask, 9)); assertFalse("Rule should not be met yet", rule.checkMask(mask)); mask.addAllyPosition(0); assertTrue("Ally should be on", rule.isAllyPositionActive(mask, 0)); assertFalse("Ally should be off", rule.isAllyPositionActive(mask, 1)); assertTrue("Should be on", rule.isPositionActive(mask, 0)); assertFalse("Should be off", rule.isPositionActive(mask, 1)); assertFalse("Should be off", rule.isPositionActive(mask, 2)); assertFalse("Should be off", rule.isPositionActive(mask, 3)); assertTrue("Should be on", rule.isPositionActive(mask, 4)); assertFalse("Should be off", rule.isPositionActive(mask, 5)); assertFalse("Should be off", rule.isPositionActive(mask, 6)); assertFalse("Should be off", rule.isPositionActive(mask, 7)); assertFalse("Should be off", rule.isPositionActive(mask, 8)); assertFalse("Should be off", rule.isPositionActive(mask, 9)); assertTrue("Rule should be met", rule.checkMask(mask)); mask.removePosition(0); assertTrue("Ally should be on", rule.isAllyPositionActive(mask, 0)); assertFalse("Ally should be off", rule.isAllyPositionActive(mask, 1)); assertTrue("Should be on", rule.isPositionActive(mask, 0)); assertTrue("Should be on", rule.isPositionActive(mask, 1)); assertTrue("Should be on", rule.isPositionActive(mask, 2)); assertTrue("Should be on", rule.isPositionActive(mask, 3)); assertTrue("Should be on", rule.isPositionActive(mask, 4)); assertFalse("Should be off", rule.isPositionActive(mask, 5)); assertFalse("Should be off", rule.isPositionActive(mask, 6)); assertFalse("Should be off", rule.isPositionActive(mask, 7)); assertFalse("Should be off", rule.isPositionActive(mask, 8)); assertFalse("Should be off", rule.isPositionActive(mask, 9)); assertFalse("Rule should not be met again", rule.checkMask(mask)); } }
ããã¯éªšãæããŸãããéå§ãããŸã§ã¯èŠããã»ã©ã§ã¯ãããŸããïŒããæç¹ã§ãããã«æ¥œãããªããŸãïŒããããããã®ãããªãã¹ãïŒããã³å¥ã®æ©äŒã®ããã®ããã€ãã®ãã¹ãïŒãæžããšãããªãã¯çªç¶èœã¡çããšèªä¿¡ãæããŸããããã§ãå°ããªã¿ã€ããã¹ã§ã¡ãœãããå°ç¡ãã«ãªããäžæå¿«ãªé©ãã«ã€ãªãããæäœæ¥ã§ãã¹ãããã®ãã¯ããã«é£ãããªããŸããå°ããã€ãã¯ã©ã¹ã®å¿ èŠãªã¡ãœãããå®è£ ãå§ããŸãããããŠæåŸã«ãã¹ããå®è¡ããŠãã©ããã§ééããç¯ããããšã確èªããŸããåé¡ç®æãèŠã€ããŠæžãçŽããŠãã ãããæºåãæŽããŸã§ç¹°ãè¿ããŸãã
class TripleDieHandMaskRule( hand: Hand, types1: Array<Die.Type>, types2: Array<Die.Type>, types3: Array<Die.Type>) : HandMaskRule(hand) { private val types1 = types1.toSet() private val types2 = types2.toSet() private val types3 = types3.toSet() override fun checkMask(mask: HandMask): Boolean { if (mask.positionCount + mask.allyPositionCount != 3) { return false } return getCheckedDice(mask).asSequence() .filter { it.type in types1 } .any { d1 -> getCheckedDice(mask) .filter { d2 -> d2 !== d1 } .filter { it.type in types2 } .any { d2 -> getCheckedDice(mask) .filter { d3 -> d3 !== d1 } .filter { d3 -> d3 !== d2 } .any { it.type in types3 } } } } override fun isPositionActive(mask: HandMask, position: Int): Boolean { if (mask.checkPosition(position)) { return true } val die = hand.dieAt(position) ?: return false return when (mask.positionCount + mask.allyPositionCount) { 0 -> die.type in types1 || die.type in types2 || die.type in types3 1 -> with(getCheckedDice(mask).first()) { (this.type in types1 && (die.type in types2 || die.type in types3)) || (this.type in types2 && (die.type in types1 || die.type in types3)) || (this.type in types3 && (die.type in types1 || die.type in types2)) } 2-> with(getCheckedDice(mask)) { val d1 = this[0] val d2 = this[1] (d1.type in types1 && d2.type in types2 && die.type in types3) || (d2.type in types1 && d1.type in types2 && die.type in types3) || (d1.type in types1 && d2.type in types3 && die.type in types2) || (d2.type in types1 && d1.type in types3 && die.type in types2) || (d1.type in types2 && d2.type in types3 && die.type in types1) || (d2.type in types2 && d1.type in types3 && die.type in types1) } 3 -> false else -> false } } override fun isAllyPositionActive(mask: HandMask, position: Int): Boolean { if (mask.checkAllyPosition(position)) { return true } if (hand.allyDieAt(position) == null) { return false } return when (mask.positionCount + mask.allyPositionCount) { 0 -> ALLY in types1 || ALLY in types2 || ALLY in types3 1 -> with(getCheckedDice(mask).first()) { (this.type in types1 && (ALLY in types2 || ALLY in types3)) || (this.type in types2 && (ALLY in types1 || ALLY in types3)) || (this.type in types3 && (ALLY in types1 || ALLY in types2)) } 2-> with(getCheckedDice(mask)) { val d1 = this[0] val d2 = this[1] (d1.type in types1 && d2.type in types2 && ALLY in types3) || (d2.type in types1 && d1.type in types2 && ALLY in types3) || (d1.type in types1 && d2.type in types3 && ALLY in types2) || (d2.type in types1 && d1.type in types3 && ALLY in types2) || (d1.type in types2 && d2.type in types3 && ALLY in types1) || (d2.type in types2 && d1.type in types3 && ALLY in types1) } 3 -> false else -> false } } }
ãã®ãããªæ©èœãããç°¡åã«å®è£ ããæ¹æ³ã«ã€ããŠã¢ã€ãã¢ãããå Žåã¯ãã³ã¡ã³ããæè¿ããŸãããããŠããã¹ããæžãããšã§ãã®ã¯ã©ã¹ã®å®è£ ãå§ããã®ã«ååãªã»ã©é ãè¯ãã£ããšä¿¡ããããªãã»ã©å¬ããã§ãã
ããããŠãç§ã¯<...>ã<...>éåžžã«<...>ãããã<...>ã§ããå ¥ããïŒ<...>æ»ãïŒ<...>ã®ã£ããã«ïŒã
ã¹ããã16ãã¢ãžã¥ãŒã«æ§
äºæ³éããæçããåäŸã¯äžç芪ã®ä¿è·äžã«ããããšã¯ã§ããŸãããé ããæ©ãããèªåã®éãéžãã§å€§èã«éãé²ã¿ãå°é£ãæ··ä¹±ãå æããªããã°ãªããŸããããã®ãããç§ãã¡ãéçºããã³ã³ããŒãã³ãã¯éåžžã«æçããŠããããã1ã€ã®å±æ ¹ã®äžã§crå±ã«ãªããŸãããããããããã€ãã®éšåã«åããæãæ¥ãŸããã
ããªãäºçŽ°ãªäœæ¥ã«çŽé¢ããŠããŸãããããŸã§ã«äœæããããã¹ãŠã®ã¯ã©ã¹ã3ã€ã®ã°ã«ãŒãã«åããå¿ èŠããããŸãã
- åºæ¬æ©èœïŒã¢ãžã¥ãŒã«ãã²ãŒã ãšã³ãžã³ãã³ãã¯ã¿ã€ã³ã¿ãŒãã§ã€ã¹ããã©ãããã©ãŒã ã«äŸåããªãå®è£ ïŒã³ã¢ïŒ;
- ã·ããªãªãå°åœ¢ãæµãé害ç©ã®ãã³ãã¬ãŒã-ãããããã¢ããã³ãã£ãŒãïŒã¢ããã³ãã£ãŒïŒã®ã³ã³ããŒãã³ãã
- ç¹å®ã®ãã©ãããã©ãŒã ã«åºæã®ã€ã³ã¿ãŒãã§ã€ã¹ã®ç¹å®ã®å®è£ ïŒãã®å Žåãã³ã³ãœãŒã«ã¢ããªã±ãŒã·ã§ã³ïŒcliïŒã
ãã®åé¢ã®çµæã¯ãæçµçã«æ¬¡ã®å³ã®ããã«ãªããŸãã
ã·ã§ãŒã®æåŸã®ä¿³åªã®ããã«ãç§ãã¡ã®ä»æ¥ã®ããŒããŒã¯åã³å
šåã§èå°ã«ç»å ŽããŸã
è¿œå ã®ãããžã§ã¯ããäœæãã察å¿ããã¯ã©ã¹ã転éããŸãããããŠããããžã§ã¯ãéã®çžäºäœçšãæ£ããæ§æããå¿ èŠããããŸãã
ã³ã¢
ãããžã§ã¯ããã®ãããžã§ã¯ãã¯çŽç²ãªãšã³ãžã³ã§ããç¹å®ã®ã¯ã©ã¹ã¯ãã¹ãŠä»ã®ãããžã§ã¯ãã«è»¢éãããŸãã-åºæ¬çãªæ©èœã§ããã³ã¢ã®ã¿ãæ®ããŸãããå¿ èŠã«å¿ããŠã©ã€ãã©ãªãèµ·åã¯ã©ã¹ã¯ãããããŸãããããã±ãŒãžããã«ãããå¿ èŠããããŸããããã®ãããžã§ã¯ãã®ã¢ã»ã³ããªã¯ãããŒã«ã«ã®Mavenãªããžããªã§ãã¹ãããïŒè©³çŽ°ã¯åŸè¿°ïŒãä»ã®ãããžã§ã¯ãã§äŸåé¢ä¿ãšããŠäœ¿çšãããŸãã
ãã¡ã€ã«
pom.xml
ã¯æ¬¡ã®ãšããã§ãã
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>my.company</groupId> <artifactId>dice-core</artifactId> <version>1.0</version> <packaging>jar</packaging> <dependencies> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-stdlib</artifactId> <version>${kotlin.version}</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit-dep</artifactId> <version>4.8.2</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.jetbrains.kotlin</groupId> <!-- other Kotlin setup --> </plugin> </plugins> </build> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <kotlin.version>1.3.20</kotlin.version> <kotlin.compiler.incremental>true</kotlin.compiler.incremental> </properties> </project>
ããããã次ã®ããã«åéããŸãã
mvn -f "path_to_project/DiceCore/pom.xml" install
Cliãããžã§ã¯ã
ããã¯ãã¢ããªã±ãŒã·ã§ã³ãžã®ãšã³ããªãã€ã³ãã§ã-ãšã³ããŠãŒã¶ãŒã察話ããã®ã¯ãã®ãããžã§ã¯ãã§ããã«ãŒãã«ã¯äŸåé¢ä¿ãšããŠäœ¿çšãããŸãããã®äŸã§ã¯ã³ã³ãœãŒã«ã§äœæ¥ããŠããããããããžã§ã¯ãã«ã¯ããã§äœæ¥ããããã«å¿ èŠãªã¯ã©ã¹ãå«ãŸããŸãïŒçªç¶ã³ãŒããŒã¡ãŒã«ãŒã§ã²ãŒã ãéå§ãããå Žåã¯ãåã«ãã®ãããžã§ã¯ãã察å¿ããå®è£ ã®åæ§ã®ãããžã§ã¯ãã«çœ®ãæããŸãïŒãããã«ãªãœãŒã¹ïŒã©ã€ã³ããªãŒãã£ãªãã¡ã€ã«ãªã©ïŒãè¿œå ããŸããå€éšã©ã€ãã©ãªãžã®äŸåé¢ä¿ã¯
ãã¡ã€ã«ã«
pom.xml
転éãããŸãïŒ
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>my.company</groupId> <artifactId>dice-cli</artifactId> <version>1.0</version> <packaging>jar</packaging> <dependencies> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-stdlib</artifactId> <version>${kotlin.version}</version> </dependency> <dependency> <groupId>my.company</groupId> <artifactId>dice-core</artifactId> <version>1.0</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.fusesource.jansi</groupId> <artifactId>jansi</artifactId> <version>1.17.1</version> <scope>compile</scope> </dependency> <dependency> <groupId>jline</groupId> <artifactId>jline</artifactId> <version>2.14.6</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.googlecode.soundlibs</groupId> <artifactId>jlayer</artifactId> <version>1.0.1.4</version> <scope>compile</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.jetbrains.kotlin</groupId> <!-- other Kotlin setup --> </plugin> <plugin> <artifactId>maven-assembly-plugin</artifactId> <version>2.6</version> <executions> <execution> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> <configuration> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> <archive> <manifest> <mainClass>my.company.dice.MainKt</mainClass> </manifest> </archive> </configuration> </plugin> </plugins> </build> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <kotlin.version>1.3.20</kotlin.version> <kotlin.compiler.incremental>true</kotlin.compiler.incremental> </properties> </project>
æ¢ã«èŠããã®ãããžã§ã¯ãããã«ãããŠå®è¡ããã¹ã¯ãªãã-ç¹°ãè¿ããŸããã
åéº
æåŸã«ãå¥ã®ãããžã§ã¯ãã§ãããããåãåºããŸããã€ãŸããããªãã®äŒç€Ÿã®ã·ããªãªéšéã®ã¹ã¿ãããæ³åã§ãããã²ãŒã äžçã®ãã¹ãŠã®ã·ããªãªãå°åœ¢ãæµãããã³ãã®ä»ã®ãŠããŒã¯ãªãªããžã§ã¯ãïŒãŸãããããŸã§ã®ãšãããç§ãã¡èªèº«ã®ç æ°ã®æ³ååã ãã§ã-ç§ãã¡ã¯ãŸã ãã®å°åã§å¯äžã®ã²ãŒã ãã¶ã€ããŒã§ãïŒãã¢ã€ãã¢ã¯ãã¹ã¯ãªãããã»ããã«ã°ã«ãŒãåãïŒã¢ããã³ãã£ãŒïŒããã®ãããªã»ãããåå¥ã®ãããžã§ã¯ããšããŠé åžããããšã§ãïŒããŒãã²ãŒã ããããªã²ãŒã ã®äžçã§è¡ãããæ¹æ³ãšåæ§ïŒãã€ãŸããjarã¢ãŒã«ã€ããåéããããããå¥ã®ãã©ã«ããŒã«é 眮ããŠãã²ãŒã ãšã³ãžã³ããã®ãã©ã«ããŒãã¹ãã£ã³ããããã«å«ãŸãããã¹ãŠã®ã¢ããã³ãã£ãŒãèªåçã«æ¥ç¶ããããã«ããŸãããã ãããã®ã¢ãããŒãã®æè¡çãªå®è£ ã«ã¯éåžžã«å€ãã®å°é£ã䌎ããŸãã
ã©ãããå§ããŸããïŒãŸãããŸããç¹å®ã®Javaã¯ã©ã¹ã®åœ¢åŒã§ãã³ãã¬ãŒããé åžããŠãããšããäºå®ããïŒãããç§ãbeatããoldã-ç§ã¯ãããäºèŠããŠããŸããïŒããã®å Žåããããã®ã¯ã©ã¹ã¯èµ·åæã«ã¢ããªã±ãŒã·ã§ã³ã®ã¯ã©ã¹ãã¹ã«ååšããå¿ èŠããããŸãããã®èŠä»¶ã匷å¶ããããšã¯é£ãããããŸãããé©åãªç°å¢å€æ°ã«jarãã¡ã€ã«ãæ瀺çã«ç»é²ããŸãïŒJava 6以éã*- wildcardsã䜿çšã§ããŸãïŒã
java -classpath "path_to_project/DiceCli/target/adventures/*" -jar path_to_project/DiceCli/target/dice-1.0-jar-with-dependencies.jar
ãã°ãããŸãã¯äœïŒ -jarã¹ã€ããã䜿çšãããšã-classpathã¹ã€ããã¯ç¡èŠãããŸãïŒã
ãã ããããã¯æ©èœããŸãããå®è¡å¯èœjarã¢ãŒã«ã€ãã®ã¯ã©ã¹ãã¹ã¯ãå éšãã¡ã€ã«ã«æ瀺çã«èšè¿°ããå¿ èŠããããŸã
META-INF/MANIFEST.MF
ïŒã»ã¯ã·ã§ã³ã®ååã¯-
Claspath:
ïŒããã®ãããç¹å¥ãªãã©ã°ã€ã³ãå©çšå¯èœã§ãïŒmaven-compiler-pluginãŸãã¯ãææªã®å Žåãmaven-assembly-pluginïŒããã ãããããã§ã¹ãå ã®ã¯ã€ã«ãã«ãŒãã¯æ©èœããŸãããäŸåããjarãã¡ã€ã«ã®ååãæ瀺çã«æå®ããå¿ èŠããããŸããã€ãŸããããããäºåã«ç¥ãããšã§ããããã¯ãç§ãã¡ã®å Žåã¯åé¡ã§ãã
ãšã«ãããç§ã¯ãããæãã§ããŸããã§ããããããžã§ã¯ããåã³ã³ãã€ã«ããå¿ èŠããªãããã«ãããã£ãããã©ã«ããŒãž
adventures/
ä»»æã®æ°ã®ã¢ããã³ãã£ãŒãæããããšãã§ãããããå®è¡äžã«ãã¹ãŠã®ã¢ããã³ãã£ãŒãã²ãŒã ãšã³ãžã³ã«è¡šç€ºãããŸãããæ®å¿µãªãããäžèŠæãããªæ©èœã¯ãJavaã®äžçã®æšæºçãªè¡šçŸãè¶ ããŠããŸãããããã£ãŠãããã¯æè¿ãããŸãããç¬ç«ããåéºãåºããã«ã¯ãç°ãªãã¢ãããŒããå®è£ ããå¿ èŠããããŸããã©ã£ã¡ïŒç§ã¯ç¥ããŸãããã³ã¡ã³ãã«æžããŠãã ãã-確ãã«èª°ããã¹ããŒããªã¢ã€ãã¢ãæã£ãŠããŸãã
ãããŸã§ã¯ããããŸãããååãç¥ããªããŠãããããžã§ã¯ããåã³ã³ãã€ã«ããªããŠããã¯ã©ã¹ãã¹ã«äŸåé¢ä¿ãåçã«è¿œå ã§ããå°ããªïŒãŸãã¯èŠãç®ã«ãã£ãŠã¯å€§ããªïŒããªãã¯ããããŸã
ãWindowsã®å ŽåïŒ
@ECHO OFF call "path_to_maven\mvn.bat" -f "path_to_project\DiceCore\pom.xml" install call "path_to_maven\mvn.bat" -f "path_to_project\DiceCli\pom.xml" package call "path_to_maven\mvn.bat" -f "path_to_project\TestAdventure\pom.xml" package mkdir path_to_project\DiceCli\target\adventures copy "path_to_project\TestAdventure\target\test-adventure-1.0.jar" path_to_project\DiceCli\target\adventures\ chcp 65001 cd path_to_project\DiceCli\target\ java -Dfile.encoding=UTF-8 -cp "dice-cli-1.0-jar-with-dependencies.jar;adventures\*" my.company.dice.MainKt pause
Unixã®å ŽåïŒ
#!/bin/sh mvn -f "path_to_project/DiceCore/pom.xml" install mvn -f "path_to_project/DiceCli/pom.xml" package mvn -f "path_to_project/TestAdventure/pom.xml" package mkdir path_to_project/DiceCli/target/adventures cp path_to_project/TestAdventure/target/test-adventure-1.0.jar path_to_project/DiceCli/target/adventures/ cd path_to_project/DiceCli/target/ java -cp "dice-cli-1.0-jar-with-dependencies.jar:adventures/*" my.company.dice.MainKt
ãããŠãããã«ããªãã¯ããããŸããããŒã䜿çšãã代ããã«
-jar
ãCliãããžã§ã¯ããã¯ã©ã¹ãã¹ã«è¿œå ãããã®äžã«å«ãŸããã¯ã©ã¹ããšã³ããªãã€ã³ããšããŠæ瀺çã«æå®ããŸã
MainKt
ãããã«ãããã§ãã©ã«ããããã¹ãŠã®ã¢ãŒã«ã€ããæ¥ç¶ããŸã
adventures/
ã
ãã®æ²ãã£ã決å®ãã©ãã»ã©å€§ããããããäžåºŠç€ºãå¿ èŠã¯ãããŸãã-ç§èªèº«ãæè¬ããŸããã³ã¡ã³ãã§ããªãã®ã¢ã€ãã¢ãææ¡ããŠãã ããããé¡ãããŸãã ïŒà²¥ï¹à²¥ïŒ
ã¹ããã17ã ãããã
å°ãæè©ã
ç§ãã¡ã®èšäºã¯ã¯ãŒã¯ãããŒã®æè¡çãªåŽé¢ã«é¢ãããã®ã§ãããã²ãŒã ã¯åãªããœãããŠã§ã¢ã³ãŒãã§ã¯ãããŸããããããã¯é¢çœãã€ãã³ããšæŽ»æ°ã®ãããã£ã©ã¯ã¿ãŒãåãããšããµã€ãã£ã³ã°ãªäžçã§ãããããªãã¯é ã§æãã£ããé£ã³èŸŒã¿ãçŸå®ã®äžçãæŸæ£ããŸãããã®ãããªäžçã¯ããããç¬èªã®æ¹æ³ã§çãããç¬èªã®æ¹æ³ã§èå³æ·±ããã®ã§ããããã®å€ãã¯äœå¹Žãçµã£ãä»ã§ãèŠããŠããŸããããªãã®äžçãæž©ããæ°æã¡ã§æãåºãããããªãããããçãããŠé¢çœãããŠãã ããã
ããã§ç§ãã¡ã¯ã¹ã¯ãªããã©ã€ã¿ãŒã§ã¯ãªãããã°ã©ããŒã§ããããšã¯ç¥ã£ãŠããŸãããã²ãŒã ã®ãžã£ã³ã«ã®ç©èªã®èŠçŽ ïŒçµéšã®ããã²ãŒããŒãããã§ããïŒã«ã€ããŠã®åºæ¬çãªã¢ã€ãã¢ã¯ãããŸããã©ããªæ¬ã§ãããã§ãããã¹ããŒãªãŒã«ã¯ç®ïŒäž»äººå ¬ãçŽé¢ããŠããåé¡ãåŸã ã«èª¬æããŸãïŒãçºå±ã2ã3åã®èå³æ·±ãã¿ãŒã³ãã¯ã©ã€ããã¯ã¹ïŒèªè ãè奮ããŠåãã€ããŠæ¯ãããã®ãå¿ãããšãã®ããããã®æãæ¥æ§ã®ç¬éïŒãšåŠèªïŒã©ã®ã€ãã³ããåŸã ã«è«ççãªçµè«ã«éãããïŒãæ§ãããªè¡šçŸãè«ççãªæ ¹æ ã®ãªãããšãããããã®ç©Žãé¿ããŠãã ããããã¹ãŠã®éå§è¡ã¯é©åãªçµè«ã«éããã¯ãã§ãã
ããŠãç§ãã¡ã®è©±ãä»ã®äººã«èªãã§ã¿ãŸããã-åŽããã®å ¬å¹³ãªå€èŠ³ã¯ãäœãããæ¬ é¥ãç解ããããããæéå ã«ä¿®æ£ããã®ã«éåžžã«ãã圹ç«ã¡ãŸãã
ã²ãŒã ã®ãããã
, , . , : ( ) ( ), . , .
â , . , , .
, , - . , , , , . .
â , . , , .
, , - . , , , , . .
幞éãªããšã«ãç§ã¯Tolkienã§ã¯ãããŸãããã²ãŒã ã®äžçãããŸã詳ãã説æããŠããŸããã§ãããããããååã«èå³æ·±ããã®ã«ããããšããŸãããåæã«ã圌ã¯ããã€ãã®ãããŸãããå°å ¥ããããšãèš±å¯ããŸãããåãããŸããã¯èªåã®ããæ¹ã§èªç±ã«è§£éã§ããŸããããšãã°ãå°å»ºå¶åºŠãšçŸä»£ã®æ°äž»çå¶åºŠãéªæªãªæŽåãçµç¹çãªç¯çœªã°ã«ãŒããæé«ã®ç®æšãšåœããåã®çåãå± é å±ã§ã®ãã¹ä¹ããšæŠã-ãã£ã©ã¯ã¿ãŒãäœããã®çç±ã§æ®åœ±ããŸãïŒåŒ/ã¯ãã¹ããŠããŸãã¯ã¢ãµã«ãã©ã€ãã«ãããäžçã«ã¯éæ³ã®ãããªãã®ãããïŒãã®ååšãæŠè¡çãªèœåã«ã²ãŒã ãã¬ã€ãè¿œå ããŸãïŒãç¥ç§äž»çŸ©ã®èŠçŽ ïŒããã¹ã姿ïŒããããŸãã
ç§ã¯é°è¬ã®æ±ºãŸãæå¥ãå¹»æ³çãªæ¶è²»è²¡ããé¢ããããšæã£ãŠããŸãã-ããããã¹ãŠã®ãšã«ããããŒã ããã©ãŽã³ãé»ããããŠçµ¶å¯Ÿçãªäžçã®æªïŒãšåæ§ã«ïŒéžæãããããŒããŒãå€ä»£ã®äºèšãã¹ãŒããŒã¢ãŒãã£ãã¡ã¯ãã壮倧ãªæŠã...ïŒãŸããç§ã¯å®éã«äžçãçãçããšãããããšæã£ãã®ã§ãåãã£ã©ã¯ã¿ãŒãïŒãã€ããŒãªãã®ã§ãïŒåºäŒã£ãŠãã²ãŒã ã®ä»çµã¿ã®èŠçŽ ãäžçã®æ³åã«é©åããããŒããŒã®æé·ãèªç¶ã«çºçããå Žæã®æµãé害ç©ã®ååšãå Žæèªäœã®ç¹åŸŽã«ãã£ãŠè«ççã«æ£åœåãããããã«ãèªåã®ç©èªãšåæ©ä»ããæã¡ãŸãã...ãªã©ãæ®å¿µãªããšã«ããã®é¡æã¯æ®é ·ãªåè«ãæŒããéçºããã»ã¹ãéåžžã«é ãããã²ãŒã ã®æ £ç¿ããé¢ããããšã¯åžžã«å¯èœã§ã¯ãããŸããã§ãããããã«ãããããããæçµè£œåããã®æºè¶³åºŠã¯æ¡éãã«å€§ããããšãå€æããŸããã
ãã®ãã¹ãŠã§ç§ã¯äœãèšãããã§ããïŒããèãæãããèå³æ·±ãããããã¯ããã»ã©å¿ èŠã§ã¯ãªããããããŸããããããªãã®ã²ãŒã ã¯ãã®ååšã«èŠããããšã¯ãããŸããïŒããããããã¬ã€ã€ãŒã¯ããã楜ããã§ããããææªã®å Žåã圌ãã¯åã«ãããç¡èŠããŸãããããŠãç¹ã«ç±å¿ãªäººã¯ãã¹ããŒãªãŒãã©ã®ããã«çµããããç¥ãããã«ãã²ãŒã ã«ããã€ãã®æ©èœçãªæ¬ é¥ãèš±ãããããŸãã
次ã¯ïŒ
ããã«ããã°ã©ãã³ã°ãçµäºããã²ãŒã ãã¶ã€ã³ãéå§ãããŸããä»ã¯ã³ãŒããæžãã®ã§ã¯ãªããã¹ã¯ãªãããå ŽæãæµãçèããŸããããããã¹ãŠã®ããã§ããããªãããŸã äžäººã§åããŠãããªããç§ã¯ããªããç¥çŠããŸã-ããªãã¯ã»ãšãã©ã®ã²ãŒã ãããžã§ã¯ããæ¥ã段éã«éããŸããã倧èŠæš¡ãªAAAã¹ã¿ãžãªã§ã¯ãç¹å¥ãªäººã ããã¶ã€ããŒãèæ¬å®¶ãšããŠåããŠããããã®ããã«ãéãåãåããŸã-圌ãã¯åã«è¡ãå ããããŸãããããããç§ãã¡ã«ã¯ããããã®éžæè¢ããããŸãïŒæ£æ©ã«è¡ããé£äºããããå¹³å¡ãªæ¹æ³ã§å¯ã-ããããããã§ã§ããããšã¯ãèç©ãããçµéšãšç¥èã䜿çšããŠãæ°ãããããžã§ã¯ããéå§ããããšã§ãã
ããªãããŸã ããã«ããŠããã¹ãŠã®è²»çšã§ç¶ç¶ãããå Žåã¯ãå°é£ã«åããŠãã ãããæéã®äžè¶³ãæ ãåµé çãªã€ã³ã¹ãã¬ãŒã·ã§ã³ã®æ¬ åŠ-äœããåžžã«ããªããããããŸããããããã¹ãŠã®é害ãå æããã®ã¯ç°¡åã§ã¯ãããŸããïŒãã®ãããã¯ã«ã€ããŠã¯å€ãã®èšäºãæžãããŠããŸãïŒããå¯èœã§ãããŸãããããžã§ã¯ãã®ãããªãçºå±ãæ éã«èšç»ããããšããå§ãããŸãã幞ããªããšã«ãç§ãã¡ã¯åã³ã®ããã«åããŠããŸããåºç瀟ã¯ç§ãã¡ãæŒãä»ããŸããã誰ãç¹å®ã®æéãå®ãå¿ èŠã¯ãããŸããããããžã§ã¯ãã®ãããŒããããããäœæããäž»èŠãªæ®µéãç¹å®ããïŒåæ°ãããå Žåã¯ïŒå®è£ ã®æ¡ä»¶ãæŠç®ããŸããèªåã§ããŒãããã¯ãå ¥æãïŒé»ååã§ããŸãïŒããã®äžã«ããã¢ã€ãã¢ã絶ããæžãçããŠãã ããïŒå€äžã«çªç¶ç®ãèŠããŠãïŒãè¡šã§é²æç¶æ³ãããŒã¯ããŸãïŒããšãã°ããã®ãããªïŒãŸãã¯ãã®ä»ã®è£å©ãéå§ããã¥ã¡ã³ãïŒå°æ¥ã®å·šå€§ãªãã¡ã³ã³ãã¥ããã£ã®ããã®å€éšãå ¬éïŒããšãã°wikiïŒãšãèªåèªèº«ã®ããã®å éšïŒãªã³ã¯ãå ±æããŸããïŒ-ä¿¡ããŠãã ãããäžè¬çã«ãã²ãŒã ã«é¢ããä»éæ å ±ãã§ããã ãå€ãæžããŸããã²ãŒã èªäœãæžãããšãå¿ããªãã§ãã ãããåºæ¬çãªãªãã·ã§ã³ãææ¡ããŸããããå ·äœçãªã¢ããã€ã¹ã¯ããŸãããåèªãèªåã®äœæ¥ããã»ã¹ãæŽçããã®ããã䟿å©ãªæ¹æ³ãèªåã§æ±ºå®ããŸãã
ãããã§ããã²ãŒã ã®ãã©ã³ã¹ã«ã€ããŠã¯è©±ããããªãã®ã§ããïŒã
å®ç§ãªã²ãŒã ãåããŠäœæããŠãããŸããããªããšããäºå®ã«ããã«åããŸããããåäœãããããã¿ã€ãã¯è¯ãã§ã-æåã¯ãããžã§ã¯ãã®å®è¡å¯èœæ§ã瀺ããããªããçŽåŸãããã倱æããããç¶ç¶ãã䟡å€ã¯ãããŸããïŒããšããéåžžã«éèŠãªè³ªåã«çããŸãããããã圌ã¯ä»ã®å€ãã®è³ªåã«ã¯çããŸããããã®äž»ãªè³ªåã¯ããããããé·æçã«ç§ã®ã²ãŒã ããã¬ã€ããã®ã¯é¢çœãã§ããããïŒãã§ãããã®ããŒãã«ã€ããŠã¯ãéåžžã«å€ãã®çè«ãšèšäºããããŸãïŒç¹°ãè¿ããŸãïŒãåçŽãããã²ãŒã ã¯ãã¬ã€ã€ãŒã«ææŠããªãã®ã§ãé¢çœãã²ãŒã ã¯ããé£ããã¯ãã§ããäžæ¹ãè€éããæ³å€ãªå Žåã¯ãé åºãªããŒãã³ã¢ãã¬ãŒã€ãŒãŸãã¯èª°ãã«äœãã蚌æããããšããŠãã人ã ããã²ãŒã ã®èŠ³å®¢ããæ®ããŸããã²ãŒã ã¯éåžžã«å€æ§ã§ãããçæ³çã«ã¯-ç®æšãéæããããã®ããã€ãã®ãªãã·ã§ã³ãæäŸããåãã¬ã€ã€ãŒã奜ã¿ã®ãªãã·ã§ã³ãéžæã§ããããã«ããŸããäžã€ã®åæ ŒæŠç¥ãæ®ããæ¯é ããã¹ãã§ã¯ãããŸãããããã§ãªããã°ã圌ãã¯ããã䜿çšããã ãã§ã...ãªã©ã
èšãæããã°ãã²ãŒã ã®ãã©ã³ã¹ããšãå¿ èŠããããŸããããã¯ãã«ãŒã«ãæ確ã«åœ¢åŒåãããŠããããŒãã²ãŒã ã«ç¹ã«åœãŠã¯ãŸããŸããã©ããã£ãŠããã®ïŒ ããããªããæ°åŠã¢ãã«ãäœæã§ããæ°åŠè ã®å人ãããªãå ŽåïŒç§ã¯ãããèŠãããšããããŸãïŒãããã«ã€ããŠäœãç解ããŠããªãïŒãããŠç§ãã¡ã¯ç解ããŠããªãïŒå Žåãå¯äžã®æ¹æ³
åè«ã¯ãšããããç§ãã¡ã«...ãã¹ãŠã®çããã®æåãé¡ã£ãŠããŸããç¶ããèªãïŒã ããèããã§ãããïŒïŒ-ã²ãŒã ã®ãã¶ã€ã³ãªã©ã«ã€ããŠãç§ãã¡ã調ã¹ããã¹ãŠã®åé¡ã¯ããã§ã«äœããã®åœ¢ã§èšäºãæç®ã§åãäžããããŠããŸãïŒãã ãããŸã ããã«ããå Žåã¯ãèªãããã«ä¿ãããšã¯æããã«äžèŠã§ãïŒãããªãã®å°è±¡ãå ±æãããã©ãŒã©ã ã§ã³ãã¥ãã±ãŒã·ã§ã³ãåããŸããã-äžè¬ã«ãããªãã¯ãã§ã«ç§ãããè¯ãç¥ã£ãŠããŸããæ ããŠã¯ãããŸãããããªãã¯æåããŸãã
ãã®æ¥œèŠ³çãªã¡ã¢ã§ãããªãã®äŒæãåãããŠãã ããããæž èŽããããšãããããŸãããããããïŒ
ããã£ïŒã©ã£ã¡ãèŠãŸããïŒããããã¹ãŠæºåž¯é»è©±ã§èµ·åããæ¹æ³ã¯ïŒç¡é§ã«åŸ ã£ãŠããŸãããããããšãäœã§ããïŒã
ããšãããAndroid
ã²ãŒã ãšã³ãžã³ãšAndroidãã©ãããã©ãŒã ã®çµ±åã«ã€ããŠèª¬æããããã«ãã¯ã©ã¹ããã®ãŸãŸã«ã
Game
ãŠãåæ§ã®ããããã¯ããã«åçŽãªã¯ã©ã¹ãèããŠã¿ãŸããã
MainMenu
ãååã瀺ãããã«ãã¢ããªã±ãŒã·ã§ã³ã®ã¡ã€ã³ã¡ãã¥ãŒãå®è£ ããããšãç®çãšããŠãããå®éããŠãŒã¶ãŒã察話ãéå§ããæåã®ã¯ã©ã¹ã§ãã
classã®ããã«ã
Game
ç¡éã«ãŒããå®çŸ©ããŸããåå埩ã§ç»é¢ãæç»ããããŠãŒã¶ãŒããã³ãã³ããèŠæ±ãããŸããããã«ã¯è€éãªããžãã¯ã¯ãªãããããã®ã³ãã³ãã¯ã¯ããã«å°ãããªã£ãŠããŸããåºæ¬çã«1ã€ã®ããšãå®è£ ããŠããŸã-ãçµäºãã
ç°¡åã§ããã ãããšã¹ããŒãã«ã€ããŠã ã³ãŒããäžæ¡åçŽã§ãã
class MainMenu( private val renderer: MenuRenderer, private val interactor: MenuInteractor ) { private var actions = ActionList.EMPTY fun start() { Audio.playMusic(Music.MENU_MAIN) actions = ActionList() actions.add(Action.Type.NEW_ADVENTURE) actions.add(Action.Type.CONTINUE_ADVENTURE, false) actions.add(Action.Type.MANUAL, false) actions.add(Action.Type.EXIT) processCycle() } private fun processCycle() { while (true) { renderer.drawMainMenu(actions) when (interactor.pickAction(actions).type) { Action.Type.NEW_ADVENTURE -> TODO() Action.Type.CONTINUE_ADVENTURE -> TODO() Action.Type.MANUAL -> TODO() Action.Type.EXIT -> { Audio.stopMusic() Audio.playSound(Sound.LEAVE) renderer.clearScreen() Thread.sleep(500) return } else -> throw AssertionError("Should not happen") } } } }
ãŠãŒã¶ãŒãšã®å¯Ÿè©±ã¯ã以åã«èŠããããã®ãšåæ§ã«æ©èœããã€ã³ã¿ãŒãã§ãŒã¹
MenuRenderer
ãšã䜿çšããŠå®è£ ãããŸã
MenuInteractor
ã
interface MenuRenderer: Renderer { fun drawMainMenu(actions: ActionList) } interface Interactor { fun anyInput() fun pickAction(list: ActionList): Action }
æ¢ã«ç解ããŠããããã«ãã€ã³ã¿ãŒãã§ã€ã¹ãç¹å®ã®å®è£ ããæå³çã«åé¢ããŸãããå¿ èŠãªã®ã¯ãCliãããžã§ã¯ããæ°ãããããžã§ã¯ãã«çœ®ãæãïŒDroidãšåŒã³ãŸãããïŒãCoreãããžã§ã¯ãã«äŸåé¢ä¿ãè¿œå ããããšã§ãããã£ãŠã¿ãŸãããã
Android Studioãå®è¡ãïŒéåžžã¯Androidåãã®ãããžã§ã¯ããéçºãããŸãïŒãåçŽãªãããžã§ã¯ããäœæããäžèŠãªæšæºèŠæãåãããã¹ãŠåé€ããKotlinèšèªã®ãµããŒãã®ã¿ãæ®ããŸãããŸããCoreãããžã§ã¯ããžã®äŸåé¢ä¿ãè¿œå ããŸããããã¯ããã·ã³ã®ããŒã«ã«Mavenãªããžããªã«ä¿åãããŸãã
apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' android { compileSdkVersion 28 defaultConfig { applicationId "my.company.dice" minSdkVersion 14 targetSdkVersion 28 versionCode 1 versionName "1.0" } } dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "my.company:dice-core:1.0" }
ãã ããããã©ã«ãã§ã¯ã誰ãäŸåé¢ä¿ãèªèããŸããããããžã§ã¯ãã®ãã«ãæã«ããŒã«ã«ãªããžããªïŒmavenLocalïŒã䜿çšããå¿ èŠãããããšãæ瀺çã«ç€ºãå¿ èŠããããŸãã
buildscript { ext.kotlin_version = '1.3.20' repositories { google() jcenter() mavenLocal() } dependencies { classpath 'com.android.tools.build:gradle:3.3.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } allprojects { repositories { google() jcenter() mavenLocal() } }
以åã«éçºããããã¹ãŠã®ã¯ã©ã¹ã䜿çšã®ããã«ã¢ã¯ã»ã¹å¯èœã§ãããå®è£ ã®ããã®ã€ã³ã¿ãŒãã§ãŒã¹ã§ããããšãããããŸããç§ãã¡ã¯ãã«èå³ããããããšã§ã倧èŠæš¡ãæã ã¯ãã§ã«ããªãã¿ã®ã€ã³ã¿ãŒãã§ã€ã¹ã§ãïŒ
SoundPlayer
ã
MusicPlayer
ã
MenuInteractor
ïŒã¢ããã°
GameInteractor
ïŒ
MenuRenderer
ïŒã¢ããã°
GameRenderer
ïŒããã³
StringLoader
ãã®ããç§ã¯ãAndroidã®å®è£ ã«æ°ãããç¹å®ãæžããŸãããã ãããã®åã«ããŠãŒã¶ãŒãæ°ããã·ã¹ãã ãšã©ã®ããã«çžäºäœçšããããäžè¬çã«ææ¡ããŸãã
ã€ã³ã¿ãŒãã§ã€ã¹èŠçŽ ã®ã¬ã³ããªã³ã°ã«ã¯ãAndroidã®æšæºã³ã³ããŒãã³ãïŒãã¿ã³ãç»åãå ¥åãã£ãŒã«ããªã©ïŒã¯äœ¿çšããŸããã代ããã«ãã¯ã©ã¹ã®æ©èœã«å¶éã
Canvas
ãŸãããããè¡ãã«ã¯ãåäžã®ã¯ã©ã¹ã®åå«ãäœæããã ãã§ååã§ã
View
-ãããããã£ã³ãã¹ãã«ãªããŸããå ¥åã®å ŽåãããŒããŒãã¯ãããªããããå°ãè€éã«ãªããŸããç»é¢ã®ç¹å®ã®éšåã§ã®ãŠãŒã¶ãŒå ¥åãã³ãã³ãã®å ¥åãšèŠãªãããããã«ã€ã³ã¿ãŒãã§ã€ã¹ãèšèšããå¿ èŠããããŸãããããè¡ãã«ã¯ãåãçžç¶äººã䜿çšããŸã
View
-ãã®ããã«ããŠã圌ã¯ãŠãŒã¶ãŒãšã²ãŒã ãšã³ãžã³ã®éã®ä»²ä»è ãšããŠæ©èœããŸãïŒã·ã¹ãã ã³ã³ãœãŒã«ããã®ãããªä»²ä»è ãšããŠæ©èœããæ¹æ³ã«äŒŒãŠããŸãïŒã
ãã¥ãŒã®ã¡ã€ã³ã¢ã¯ãã£ããã£ãäœæãããããã§ã¹ãã«æžã蟌ã¿ãŸãããã
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="my.company.dice"> <application android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme"> <activity android:name=".ui.MainActivity" android:screenOrientation="sensorLandscape" android:configChanges="orientation|keyboardHidden|screenSize"> <intent-filter> <category android:name="android.intent.category.LAUNCHER"/> <action android:name="android.intent.action.MAIN"/> </intent-filter> </activity> </application> </manifest>
ã¢ã¯ãã£ããã£ã暪åãã«ä¿®æ£ããŸã-ä»ã®ã»ãšãã©ã®ã²ãŒã ã®å Žåã®ããã«ãããŒãã¬ãŒããããŒãã¬ãŒãã«ããããšã¯ã§ããŸãããããã«ãããã€ã¹ã®ç»é¢å šäœã«æ¡åŒµããããã«å¿ããŠã¡ã€ã³ããŒããåŠæ¹ããŸãã
<resources> <style name="AppTheme" parent="android:Theme.Black.NoTitleBar.Fullscreen"/> </resources>
ãããŠããªãœãŒã¹ã«å ¥ã£ãã®ã§ãå¿ èŠãªããŒã«ã©ã€ãºãããæååãCliãããžã§ã¯ããã転éããç®çã®åœ¢åŒã«ããŸãã
<resources> <string name="action_new_adventure_key">N</string> <string name="action_new_adventure_name">ew adventure</string> <string name="action_continue_adventure_key">C</string> <string name="action_continue_adventure_name">ontinue adventure</string> <string name="action_manual_key">M</string> <string name="action_manual_name">anual</string> <string name="action_exit_key">X</string> <string name="action_exit_name">Exit</string> </resources>
ã¡ã€ã³ã¡ãã¥ãŒã§äœ¿çšããããµãŠã³ããšé³æ¥œã®ãã¡ã€ã«ïŒåã¿ã€ãã®1ã€ïŒãšåæ§ã«ããããããããã
/assets/sound/leave.wav
andã«é 眮ã
/assets/music/menu_main.mp3
ãŸãã
ãªãœãŒã¹ãç解ããããèšèšãè¡ããšãã§ããïŒã¯ãïŒãã³ã³ãœãŒã«ãšã¯ç°ãªããAndroidãã©ãããã©ãŒã ã«ã¯ç¬èªã®ã¢ãŒããã¯ãã£æ©èœããããç¹å®ã®ã¢ãããŒããšæ¹æ³ã䜿çšããå¿ èŠããããŸãã
æ°ãã€ããªãã§ãã ãããä»ç§ã¯ãã¹ãŠã詳现ã«èª¬æããŸãã
ãããããæãé£ããã¯ã©ã¹
DiceSurface
- ã¯ã©ã¹-
View
ã·ã¹ãã ã®ç¬ç«ããéšåãçµåããããã«åŒã³åºãããéåžžã«åŸç¶è ããå§ããŸãïŒå¿ èŠã«å¿ããŠãã¯ã©ã¹ããç¶æ¿ããããšãã§ããŸã
SurfaceView
-ãŸãã¯
GlSurfaceView
-å¥ã®ã¹ã¬ããã§æç»ããããšãã§ããŸãããã¢ãã¡ãŒã·ã§ã³ããŒã¹ã®ã¿ãŒã³ããŒã¹ã®ã²ãŒã ããããŸããããã¯è€éãªã°ã©ãã£ãã¯åºåãå¿ èŠãšããªããããè€éã«ããŸããïŒãåè¿°ã®ããã«ããã®å®è£ ã«ãããç»ååºåãšã¯ãªãã¯åŠçã®2ã€ã®åé¡ãåæã«è§£æ±ºãããŸããããããã«äºæ³å€ã®åé¡ããããŸããããããé çªã«èããŠã¿ãŸãããã
ã³ã³ãœãŒã«ã«ãã€ã³ããããšãã¬ã³ãã©ãŒã¯åºåã³ãã³ããéä¿¡ããç»é¢ã«ç»åã圢æããŸããã Androidã®å Žåãç¶æ³ã¯éã§ããã¬ã³ããªã³ã°ã¯Viewèªäœã«ãã£ãŠéå§ãããã¡ãœãã
onDraw()
ãå®è¡ããããŸã§ã«ãäœããã©ã®ããã«ãã©ãã«æç»ãããããã§ã«ç¥ã£ãŠããã¯ãã§ãããããã
drawMainMenu()
ã€ã³ã¿ãŒãã§ãŒã¹ã¡ãœããã¯
MainMenu
ã©ãã§ããïŒåœŒã¯ä»ãåºåãå¶åŸ¡ããŠããŸãããïŒ
æ©èœçãªã€ã³ã¿ãŒãã§ãŒã¹ã䜿çšããŠãã®åé¡ã解決ããŠã¿ãŸããããã¯ã©ã¹
DiceSurface
ã«ã¯ç¹å¥ãªãã©ã¡ãŒã¿ãŒãå«ãŸããŸã
instructions
ãå®éã«ã¯ãã¡ãœãããåŒã³åºããããã³ã«å®è¡ããå¿ èŠãããã³ãŒãã®ãããã¯ã§ã
onDraw()
ãã¬ã³ãã©ãŒã¯ããããªãã¯ã¡ãœããã䜿çšããŠãã©ã®ç¹å®ã®æ瀺ã«åŸãã¹ããã瀺ããŸããèå³ã®ããæ¹ã¯ããšåŒã°ãããã¿ãŒã³ã䜿çšæŠç¥ïŒæŠç¥ïŒã次ã®ããã«ãªããŸãã
typealias RenderInstructions = (Canvas, Paint) -> Unit class DiceSurface(context: Context) : View(context) { private var instructions: RenderInstructions = { _, _ -> } private val paint = Paint().apply { color = Color.YELLOW style = Paint.Style.STROKE isAntiAlias = true } fun updateInstructions(instructions: RenderInstructions) { this.instructions = instructions this.postInvalidate() } override fun onDraw(canvas: Canvas) { super.onDraw(canvas) canvas.drawColor(Color.BLACK) //Fill background with black color instructions.invoke(canvas, paint) //Execute current render instructions } } class DroidMenuRenderer(private val surface: DiceSurface): MenuRenderer { override fun clearScreen() { surface.updateInstructions { _, _ -> } } override fun drawMainMenu(actions: ActionList) { surface.updateInstructions { c, p -> val canvasWidth = c.width val canvasHeight = c.height //Draw title text p.textSize = canvasHeight / 3f p.strokeWidth = 0f p.color = Color.parseColor("#ff808000") c.drawText( "DICE", (canvasWidth - p.measureText("DICE")) / 2f, (buttonTop - p.ascent() - p.descent()) / 2f, p ) //Other instructions... } } }
ã€ãŸãããã¹ãŠã®ã°ã©ãã£ã«ã«æ©èœã¯ãŸã Rendererã¯ã©ã¹ã«ãããŸãããä»åã¯ã³ãã³ããçŽæ¥å®è¡ããã®ã§ã¯ãªããViewã«ããå®è¡ã®æºåãããŸããããããã£ã®ã¿ã€ãã«æ³šæããŠ
instructions
ãã ãã-å¥ã®ã€ã³ã¿ãŒãã§ã€ã¹ãäœæããŠãã®å¯äžã®ã¡ãœãããåŒã³åºãããšãã§ããŸãããKotlinã¯ã³ãŒãã®éãå€§å¹ ã«åæžã§ããŸãã
次ã«ãInteractorã«ã€ããŠèª¬æããŸãã以åã¯ãããŒã¿å ¥åã¯åæã§ãããã³ã³ãœãŒã«ïŒããŒããŒãïŒããããŒã¿ãèŠæ±ãããšããŠãŒã¶ãŒãããŒãæŒããŸã§ã¢ããªã±ãŒã·ã§ã³ïŒãµã€ã¯ã«ïŒãäžæãããŸããã Androidã§ã¯ããã®ãããªããªãã¯ã¯æ©èœããŸãããç¬èªã®Looperãããããã®æ©èœã¯äžæããããšã¯ãããŸãããã€ãŸããå ¥åã¯éåæã§ãªããã°ãªããŸãããã€ãŸããInteractorã€ã³ã¿ãŒãã§ãŒã¹ã¡ãœããã¯ãšã³ãžã³ãäžæåæ¢ããŠã³ãã³ããåŸ æ©ããŸãããActivityãšãã®ãã¹ãŠã®ãã¥ãŒã¯é ããæ©ãããã®ã³ãã³ããéä¿¡ãããŸã§æ©èœãç¶ããŸãã
ãã®ã¢ãããŒãã¯ãæšæºã€ã³ã¿ãŒãã§ãŒã¹ã䜿çšããŠå®è£ ããã®ãéåžžã«ç°¡å
BlockingQueue
ã§ããã¯ã©ã¹
DroidMenuInteractor
ã¯ã¡ãœãããåŒã³åºããŸã
take()
ãèŠçŽ ïŒæ¢ç¥ã®ã¯ã©ã¹ã®ã€ã³ã¹ã¿ã³ã¹
Action
ïŒããã¥ãŒã«è¡šç€ºããããŸã§ãã²ãŒã ã¹ããªãŒã ã®å®è¡ãäžæåæ¢ããŸãã
DiceSurface
ããŠãŒã¶ãŒã®ã¯ãªãã¯ã«åãããŠèª¿æŽãïŒæšæº
onTouchEvent()
ã¯ã©ã¹ã¡ãœãã
View
ïŒããªããžã§ã¯ããçæããã¡ãœããã«ãã£ãŠãã¥ãŒã«è¿œå ããŸã
offer()
ã次ã®ããã«ãªããŸãã
class DiceSurface(context: Context) : View(context) { private val actionQueue: BlockingQueue<Action> = LinkedBlockingQueue<Action>() fun awaitAction(): Action = actionQueue.take() override fun onTouchEvent(event: MotionEvent): Boolean { if (event.action == MotionEvent.ACTION_UP) { actionQueue.offer(Action(Action.Type.NONE), 200, TimeUnit.MILLISECONDS) } return true } } class DroidMenuInteractor(private val surface: DiceSurface) : Interactor { override fun anyInput() { surface.awaitAction() } override fun pickAction(list: ActionList): Action { while (true) { val type = surface.awaitAction().type list .filter(Action::isEnabled) .find { it.type == type } ?.let { return it } } } }
ã€ãŸããInteractorã¯ã¡ãœãã
awaitAction()
ãåŒã³åºãããã¥ãŒã«äœããããã°ãåä¿¡ããã³ãã³ããåŠçããŸããããŒã ããã¥ãŒã«è¿œå ãããæ¹æ³ã«æ³šæããŠãã ããã UIã¹ããªãŒã ã¯é£ç¶ããŠå®è¡ãããããããŠãŒã¶ãŒã¯ç»é¢ãäœåºŠãé£ç¶ããŠã¯ãªãã¯ããããšãã§ããŸããããã¯ãç¹ã«ã²ãŒã ãšã³ãžã³ãã³ãã³ããåä¿¡ããæºåãã§ããŠããªãå ŽåïŒããšãã°ãã¢ãã¡ãŒã·ã§ã³äžïŒã«ãã³ã°ã¢ããããå¯èœæ§ããããŸããã®å Žåããã¥ãŒã®å®¹éãå¢ããããã¿ã€ã ã¢ãŠãå€ãæžãããããŸãã¯ãã®äž¡æ¹ã圹ç«ã¡ãŸãã
ãã¡ãããã³ãã³ãã転éããŸãããããã¯å¯äžã®ãã®ã§ããæŒã座æšãåºå¥ããå¿ èŠãããããããã®å€ã«å¿ããŠããã®ã³ãã³ããŸãã¯ãã®ã³ãã³ããåŒã³åºããŸãããã ããããã¯äžéã§ã-Interactorã¯ãã¢ã¯ãã£ããªãã¿ã³ãç»é¢äžã®ã©ã®å Žæã«æç»ããããããããŸãã-ã¬ã³ãã©ãŒãã¬ã³ããªã³ã°ãæ åœããŸãã次ã®ããã«çžäºäœçšã確ç«ããŸãããã®ã¯ã©ã¹
DiceSurface
ã¯ãç¹å¥ãªã³ã¬ã¯ã·ã§ã³-ã¢ã¯ãã£ããªé·æ¹åœ¢ïŒãŸãã¯ãã®ãã€ã³ãã«å°éããå Žåã¯ä»ã®åœ¢ç¶ïŒã®ãªã¹ããæ ŒçŽããŸãããã®ãããªé·æ¹åœ¢ã«ã¯ãé ç¹ã®åº§æšãšå¢çã®åº§æšãå«ãŸããŸã
Action
ãã¬ã³ãã©ãŒã¯ãããã®é·æ¹åœ¢ãçæããŠãªã¹ãã«è¿œå ããã¡ãœãã
onTouchEvent()
ã¯ã©ã®é·æ¹åœ¢ãæŒãããããå€æãã察å¿ããé·æ¹åœ¢ããã¥ãŒã«è¿œå ããŸã
Action
ã
private class ActiveRect(val action: Action, left: Float, top: Float, right: Float, bottom: Float) { val rect = RectF(left, top, right, bottom) fun check(x: Float, y: Float, w: Float, h: Float) = rect.contains(x / w, y / h) }
ãã®ã¡ãœãã
check()
ã¯ãæå®ããã座æšãé·æ¹åœ¢ã®å åŽã«ãããã©ããã確èªããŸããã¬ã³ãã©ãŒã®äœæ¥æ®µéã§ã¯ïŒããããŸãã«é·æ¹åœ¢ãäœæãããç¬éã§ãïŒããã£ã³ãã¹ã®ãµã€ãºã«ã€ããŠã¯ããããªãããšã«æ³šæããŠãã ããããããã£ãŠã座æšãçžå¯Ÿå€ïŒç»é¢ã®å¹ ãŸãã¯é«ãã®å²åïŒã§0ã1ã®å€ã§ä¿åããæŒããšãã«åã«ãŠã³ãããå¿ èŠããããŸãããã®ã¢ãããŒãã¯ãã¢ã¹ãã¯ãæ¯ãèæ ®ã«å ¥ããŠããªããããå®å šã«æ£ç¢ºã§ã¯ãããŸãããå°æ¥çã«ã¯ãããçŽãå¿ èŠããããŸãããã ããæåã®æè²çãªã¿ã¹ã¯ã«ã€ããŠã¯ããã§ååã§ãã
ã¯ã©ã¹
DiceSurface
ã«è¿œå ã®ãã£ãŒã«ããå®è£ ãã2ã€ã®ã¡ãœããïŒ
addRectangle()
ããã³
clearRectangles()
ïŒãè¿œå ããŠå€éšããïŒã¬ã³ãã©ãŒåŽããïŒå¶åŸ¡ã
onTouchEvent()
ãé·æ¹åœ¢ã®åº§æšãèæ ®ããŠæ¡åŒµããŸãã
class DiceSurface(context: Context) : View(context) { private val actionQueue: BlockingQueue<Action> = LinkedBlockingQueue<Action>() private val rectangles: MutableSet<ActiveRect> = Collections.newSetFromMap(ConcurrentHashMap<ActiveRect, Boolean>()) private var instructions: RenderInstructions = { _, _ -> } private val paint = Paint().apply { color = Color.YELLOW style = Paint.Style.STROKE isAntiAlias = true } fun updateInstructions(instructions: RenderInstructions) { this.instructions = instructions this.postInvalidate() } fun clearRectangles() { rectangles.clear() } fun addRectangle(action: Action, left: Float, top: Float, right: Float, bottom: Float) { rectangles.add(ActiveRect(action, left, top, right, bottom)) } fun awaitAction(): Action = actionQueue.take() override fun onTouchEvent(event: MotionEvent): Boolean { if (event.action == MotionEvent.ACTION_UP) { with(rectangles.firstOrNull { it.check(event.x, event.y, width.toFloat(), height.toFloat()) }) { if (this != null) { actionQueue.put(action) } else { actionQueue.offer(Action(Action.Type.NONE), 200, TimeUnit.MILLISECONDS) } } } return true } override fun onDraw(canvas: Canvas) { super.onDraw(canvas) canvas.drawColor(Color.BLACK) instructions(canvas, paint) } }
競åããã³ã¬ã¯ã·ã§ã³ã䜿çšããŠåè§åœ¢ãä¿åããŸã-
ConcurrentModificationException
ç°ãªãã¹ã¬ããã«ãã£ãŠã»ãããåæã«æŽæ°ããã³ç§»åãããå Žåã«çºçãåé¿ã§ããŸãïŒãã®å Žåã¯çºçããŸãïŒã
ã¯ã©ã¹ã³ãŒã
DroidMenuInteractor
ã¯å€æŽãããŸãããã
DroidMenuRenderer
å€æŽãããŸããåé ç®ã®è¡šç€ºã«4ã€ã®ãã¿ã³ãè¿œå ããŸã
ActionList
ãç»é¢ã®å¹ å šäœã«åçã«é 眮ãããèŠåºãDICEã®äžã«é 眮ããŸããããŠãã¢ã¯ãã£ããªé·æ¹åœ¢ãå¿ããªãã§ãã ããã
class DroidMenuRenderer ( private val surface: DiceSurface, private val loader: StringLoader ) : MenuRenderer { protected val helper = StringLoadHelper(loader) override fun clearScreen() { surface.clearRectangles() surface.updateInstructions { _, _ -> } } override fun drawMainMenu(actions: ActionList) { //Prepare rectangles surface.clearRectangles() val percentage = 1.0f / actions.size actions.forEachIndexed { i, a -> surface.addRectangle(a, i * percentage, 0.45f, i * percentage + percentage, 1f) } //Prepare instructions surface.updateInstructions { c, p -> val canvasWidth = c.width val canvasHeight = c.height val buttonTop = canvasHeight * 0.45f val buttonWidth = canvasWidth / actions.size val padding = canvasHeight / 144f //Draw title text p.textSize = canvasHeight / 3f p.strokeWidth = 0f p.color = Color.parseColor("#ff808000") p.isFakeBoldText = true c.drawText( "DICE", (canvasWidth - p.measureText("DICE")) / 2f, (buttonTop - p.ascent() - p.descent()) / 2f, p ) p.isFakeBoldText = false //Draw action buttons p.textSize = canvasHeight / 24f actions.forEachIndexed { i, a -> p.color = if (a.isEnabled) Color.YELLOW else Color.LTGRAY p.strokeWidth = canvasHeight / 240f c.drawRect( i * buttonWidth + padding, buttonTop + padding, i * buttonWidth + buttonWidth - padding, canvasHeight - padding, p ) val name = mergeActionData(helper.loadActionData(a)) p.strokeWidth = 0f c.drawText( name, i * buttonWidth + (buttonWidth - p.measureText(name)) / 2f, (canvasHeight + buttonTop - p.ascent() - p.descent()) / 2f, p ) } } } private fun mergeActionData(data: Array<String>) = if (data.size > 1) { if (data[1].first().isLowerCase()) data[0] + data[1] else data[1] } else data.getOrNull(0) ?: "" }
ãã
StringLoader
ã§ããã«ããŒã¯ã©ã¹ã®ã€ã³ã¿ãŒãã§ã€ã¹ãšæ©èœã«æ»ããŸã
StringLoadHelper
ïŒå³ã«ã¯ç€ºãããŠããŸããïŒãæåã®å®è£ ã«ã¯ååã
ResourceStringLoader
ãããïŒæããã«ïŒã¢ããªã±ãŒã·ã§ã³ãªãœãŒã¹ããããŒã«ã©ã€ãºãããæååãããŒãããŸãããã ãããªãœãŒã¹èå¥åãäºåã«ããããªããããããã¯åçã«è¡ãããŸããå€åºå ã§ãªãœãŒã¹èå¥åãäœæããå¿ èŠããããŸãã
class ResourceStringLoader(context: Context) : StringLoader { private val packageName = context.packageName private val resources = context.resources override fun loadString(key: String): String = resources.getString(resources.getIdentifier(key, "string", packageName)) }
é³ãšé³æ¥œã«ã€ããŠè©±ãããšã¯æ®ã£ãŠããŸããAndroid
MediaPlayer
ã«ã¯ããããã®ããšãæ±ãçŽ æŽãããã¯ã©ã¹ããããŸããé³æ¥œãæŒå¥ããããã®è¯ããã®ã¯ãããŸããïŒ
class DroidMusicPlayer(private val context: Context): MusicPlayer { private var currentMusic: Music? = null private val player = MediaPlayer() override fun play(music: Music) { if (currentMusic == music) { return } currentMusic = music player.setAudioStreamType(AudioManager.STREAM_MUSIC) val afd = context.assets.openFd("music/${music.toString().toLowerCase()}.mp3") player.setDataSource(afd.fileDescriptor, afd.startOffset, afd.length) player.setOnCompletionListener { it.seekTo(0) it.start() } player.prepare() player.start() } override fun stop() { currentMusic = null player.release() } }
2ã€ã®ãã€ã³ãããŸããã¡ãœãã
prepare()
ã¯åæçã«å®è¡ãããŸããããã¯ããã¡ã€ã«ãµã€ãºã倧ããå ŽåïŒãããã¡ãªã³ã°ã®ããïŒãã·ã¹ãã ãäžæããŸããå¥ã®ã¹ã¬ããã§å®è¡ããããéåæã¡ãœãã
prepareAsync()
ãšã䜿çšããããšããå§ãããŸã
OnPreparedListener
ã第äºã«ãåçãã¢ã¯ãã£ããã£ã©ã€ããµã€ã¯ã«ïŒãŠãŒã¶ãŒãã¢ããªã±ãŒã·ã§ã³ãæå°åãããšäžæåæ¢ããå埩ãããšåéããïŒãé¢é£ä»ãããšäŸ¿å©ã§ãããããããŸããã§ããã Ai-ai-ai ...
ãµãŠã³ã
MediaPlayer
ã«ãé©ããŠããŸãããæ°ãå°ãªãã·ã³ãã«ãªå ŽåïŒãã®å Žåã®ããã«ïŒãããã§ååã§ã
SoundPool
ããã®å©ç¹ã¯ããµãŠã³ããã¡ã€ã«ãæ¢ã«ã¡ã¢ãªã«ããŒããããŠããå Žåããã®åçãå³åº§ã«éå§ãããããšã§ããæ¬ ç¹ã¯æããã§ã-ååãªã¡ã¢ãªããªãå¯èœæ§ããããŸãïŒããããç§ãã¡ã«ãšã£ãŠã¯ååã§ãããæ§ããã§ãïŒã
class DroidSoundPlayer(context: Context) : SoundPlayer { private val soundPool: SoundPool = SoundPool(2, AudioManager.STREAM_MUSIC, 100) private val sounds = mutableMapOf<Sound, Int>() private val rate = 1f private val lock = ReentrantReadWriteLock() init { Thread(SoundLoader(context)).start() } override fun play(sound: Sound) { if (lock.readLock().tryLock()) { try { sounds[sound]?.let { s -> soundPool.play(s, 1f, 1f, 1, 0, rate) } } finally { lock.readLock().unlock() } } } private inner class SoundLoader(private val context: Context) : Runnable { override fun run() { val assets = context.assets lock.writeLock().lock() try { Sound.values().forEach { s -> sounds[s] = soundPool.load( assets.openFd("sound/${s.toString().toLowerCase()}.wav"), 1 ) } } finally { lock.writeLock().unlock() } } } }
ã¯ã©ã¹ãäœæãããšããåæããã®ãã¹ãŠã®ãµãŠã³ã
Sound
ã¯å¥ã®ã¹ããªãŒã ã§ãªããžããªã«ããŒããããŸããä»åã¯åæã³ã¬ã¯ã·ã§ã³ã䜿çšããŸããããæšæºã¯ã©ã¹ã䜿çšããŠãã¥ãŒããã¯ã¹ãå®è£ ã
ReentrantReadWriteLock
ãŸãã
ããŠãæåŸã«ãç§ãã¡
MainActivity
ã¯å éšã§ãã¹ãŠã®ã³ã³ããŒãã³ããäžç·ã«ãã©ã€ã³ãããŸã-ãããå¿ããŠããŸãããïŒ
MainMenu
ïŒããã³
Game
ãã®åŸïŒå¥ã®ã¹ã¬ããã§èµ·åããå¿ èŠãããããšã«æ³šæããŠãã ããã
class MainActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Audio.init(DroidSoundPlayer(this), DroidMusicPlayer(this)) val surface = DiceSurface(this) val renderer = DroidMenuRenderer(surface) val interactor = DroidMenuInteractor(surface, ResourceStringLoader(this)) setContentView(surface) Thread { MainMenu(renderer, interactor).start() finish() }.start() } override fun onBackPressed() { } }
å®éãããããã¹ãŠã§ãã ãã¹ãŠã®èŠçã®åŸãç§ãã¡ã®ã¢ããªã±ãŒã·ã§ã³ã®ã¡ã€ã³ç»é¢ã¯åçŽã«é©ãã»ã©ã«èŠããŸãïŒ
ãŸããã€ãŸããç¥çãªã¢ãŒãã£ã¹ããç§ãã¡ã®ã©ã³ã¯ã«ç»å Žãããšããããã¯é©ãã»ã©ã«èŠããŸãããããŠã圌ã®å©ãã§ãã®ã¹ã«ã©ãŒã¯å®å šã«åæç»ãããŸãã
䟿å©ãªãªã³ã¯
ç§ã¯ç¥ã£ãŠãããå€ãã¯ãã®ãã€ã³ãã«ãŸã£ããã«ã¹ã¯ããŒã«ããŸããã倧äžå€«ã§ã-ã»ãšãã©ã®èªè ã¯ã¿ããå®å šã«éããŠããŸããããã«ããããããããã®äžè²«æ§ã®ãªããããã¹ãã®æµããã¹ãŠã«èãããŠããã-
ããŠãçªç¶èª°ãããããžã§ã¯ããéå§ããŠèŠãããšæãããã«ãªããèªåã§æ inessãéããããã«ãããã«äœæ¥ããŒãžã§ã³ãžã®ãªã³ã¯ããããŸãïŒLINKïŒ
ããã§ã¯ã䟿å©ãªã©ã³ãã£ãŒã䜿çšããŠèµ·åããŸãïŒäœæã«ã€ããŠå¥ã®èšäºãæžãããšãã§ããŸãïŒã JavaFXã䜿çšãããããOpenJDKïŒæžã蟌ã¿ããã³ãã«ãïŒãæèŒãããã·ã³ã§ã¯èµ·åããªãå ŽåããããŸãããå°ãªããšããã¡ã€ã«ãã¹ãæåã§ç»é²ããå¿ èŠã¯ãããŸãããã€ã³ã¹ããŒã«ã®ãã«ãã¯readme.txtãã¡ã€ã«ã«å«ãŸããŠããŸãïŒèŠããŠããŸããïŒïŒãããŠã³ããŒãããŠãèŠãŠã䜿çšããŠãæåŸã«ç§ã¯é»ã£ãŠããŸãã
ãããžã§ã¯ãã䜿çšããããŒã«ãã¡ã«ããã¯ããŸãã¯èå³æ·±ããœãªã¥ãŒã·ã§ã³ã«èå³ãããå ŽåããŸãã¯ã²ãŒã ãããããªãå Žåã¯ãå¥ã®èšäºã§è©³çŽ°ã調ã¹ãããšãã§ããŸããå¿ èŠã«å¿ããŠãå¿ èŠãªãå Žåã¯ãã³ã¡ã³ããåŸæãææ¡ãéä¿¡ããŠãã ããã話ãããã§ãã
ãã¹ãŠæé«ã