From 573b5bb43f409e6270fd243ff80a34ba2c727654 Mon Sep 17 00:00:00 2001 From: Tor Andersson Date: Sat, 1 May 2021 00:48:44 +0200 Subject: caesar: Import Julius Caesar. --- Vexilloid_of_the_Roman_Empire.png | Bin 0 -> 15069 bytes Vexilloid_of_the_Roman_Empire.svg | 1 + about.html | 11 + blocks_columbia.css | 69 ++ blocks_columbia.png | Bin 0 -> 728299 bytes blocks_simple.css | 191 +++ blocks_simple.png | Bin 0 -> 448265 bytes cards/card_12.png | Bin 0 -> 356304 bytes cards/card_13.png | Bin 0 -> 357347 bytes cards/card_21.png | Bin 0 -> 379355 bytes cards/card_22.png | Bin 0 -> 355163 bytes cards/card_23.png | Bin 0 -> 382390 bytes cards/card_31.png | Bin 0 -> 354666 bytes cards/card_32.png | Bin 0 -> 354704 bytes cards/card_41.png | Bin 0 -> 357711 bytes cards/card_apollo.png | Bin 0 -> 305241 bytes cards/card_back.png | Bin 0 -> 242462 bytes cards/card_jupiter.png | Bin 0 -> 360398 bytes cards/card_mars.png | Bin 0 -> 406562 bytes cards/card_mercury.png | Bin 0 -> 367213 bytes cards/card_neptune.png | Bin 0 -> 389258 bytes cards/card_pluto.png | Bin 0 -> 418401 bytes cards/card_vulcan.png | Bin 0 -> 357575 bytes cover.1x.jpg | Bin 0 -> 31355 bytes cover.2x.jpg | Bin 0 -> 91239 bytes cover.jpg | Bin 0 -> 66373 bytes data.js | 507 ++++++++ info/Julius Caesar 1.1.pdf | Bin 0 -> 2859444 bytes info/blocks.html | 126 ++ info/cards.html | 36 + info/notes.html | 33 + info/rules.html | 1234 +++++++++++++++++++ info/rules01.jpg | Bin 0 -> 48270 bytes info/rules02.jpg | Bin 0 -> 36396 bytes info/rules03.jpg | Bin 0 -> 12151 bytes info/rules04.jpg | Bin 0 -> 73662 bytes info/rules05.jpg | Bin 0 -> 12165 bytes info/rules06.jpg | Bin 0 -> 12165 bytes info/rules07.jpg | Bin 0 -> 12165 bytes info/rules08.jpg | Bin 0 -> 13959 bytes map150.png | Bin 0 -> 11732761 bytes map75.png | Bin 0 -> 3402300 bytes play.html | 319 +++++ rules.js | 2420 +++++++++++++++++++++++++++++++++++++ thumbnail.jpg | Bin 0 -> 18446 bytes ui.js | 776 ++++++++++++ 46 files changed, 5723 insertions(+) create mode 100644 Vexilloid_of_the_Roman_Empire.png create mode 100644 Vexilloid_of_the_Roman_Empire.svg create mode 100644 about.html create mode 100644 blocks_columbia.css create mode 100644 blocks_columbia.png create mode 100644 blocks_simple.css create mode 100644 blocks_simple.png create mode 100644 cards/card_12.png create mode 100644 cards/card_13.png create mode 100644 cards/card_21.png create mode 100644 cards/card_22.png create mode 100644 cards/card_23.png create mode 100644 cards/card_31.png create mode 100644 cards/card_32.png create mode 100644 cards/card_41.png create mode 100644 cards/card_apollo.png create mode 100644 cards/card_back.png create mode 100644 cards/card_jupiter.png create mode 100644 cards/card_mars.png create mode 100644 cards/card_mercury.png create mode 100644 cards/card_neptune.png create mode 100644 cards/card_pluto.png create mode 100644 cards/card_vulcan.png create mode 100644 cover.1x.jpg create mode 100644 cover.2x.jpg create mode 100644 cover.jpg create mode 100644 data.js create mode 100644 info/Julius Caesar 1.1.pdf create mode 100644 info/blocks.html create mode 100644 info/cards.html create mode 100644 info/notes.html create mode 100644 info/rules.html create mode 100644 info/rules01.jpg create mode 100644 info/rules02.jpg create mode 100644 info/rules03.jpg create mode 100644 info/rules04.jpg create mode 100644 info/rules05.jpg create mode 100644 info/rules06.jpg create mode 100644 info/rules07.jpg create mode 100644 info/rules08.jpg create mode 100644 map150.png create mode 100644 map75.png create mode 100644 play.html create mode 100644 rules.js create mode 100644 thumbnail.jpg create mode 100644 ui.js diff --git a/Vexilloid_of_the_Roman_Empire.png b/Vexilloid_of_the_Roman_Empire.png new file mode 100644 index 0000000..461ef99 Binary files /dev/null and b/Vexilloid_of_the_Roman_Empire.png differ diff --git a/Vexilloid_of_the_Roman_Empire.svg b/Vexilloid_of_the_Roman_Empire.svg new file mode 100644 index 0000000..d25f61a --- /dev/null +++ b/Vexilloid_of_the_Roman_Empire.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/about.html b/about.html new file mode 100644 index 0000000..3447778 --- /dev/null +++ b/about.html @@ -0,0 +1,11 @@ +

+Julius Caesar brings the drama of the Roman Civil War to life. Players take +control of the legions of Caesar or Pompey and fight to determine the future of +Rome: republic or empire. Marc Antony, Cleopatra, Octavian, and Brutus also +play key roles. + +

+Designer: Justin Thompson and Grant Dalgliesh. + +

+Copyright © 2010 Columbia Games. diff --git a/blocks_columbia.css b/blocks_columbia.css new file mode 100644 index 0000000..164b19f --- /dev/null +++ b/blocks_columbia.css @@ -0,0 +1,69 @@ +.columbia-labels .known.block { + background-image: url('blocks_columbia.webp'); + background-repeat: no-repeat; + background-size: 496px 496px; +} + +.columbia-labels .known.block_0{background-position:-3px -3px} +.columbia-labels .known.block_1{background-position:-65px -3px} +.columbia-labels .known.block_2{background-position:-127px -3px} +.columbia-labels .known.block_3{background-position:-189px -3px} +.columbia-labels .known.block_4{background-position:-251px -3px} +.columbia-labels .known.block_5{background-position:-313px -3px} +.columbia-labels .known.block_6{background-position:-375px -3px} +.columbia-labels .known.block_7{background-position:-437px -3px} +.columbia-labels .known.block_8{background-position:-3px -65px} +.columbia-labels .known.block_9{background-position:-65px -65px} +.columbia-labels .known.block_10{background-position:-127px -65px} +.columbia-labels .known.block_11{background-position:-189px -65px} +.columbia-labels .known.block_12{background-position:-251px -65px} +.columbia-labels .known.block_13{background-position:-313px -65px} +.columbia-labels .known.block_14{background-position:-375px -65px} +.columbia-labels .known.block_15{background-position:-437px -65px} +.columbia-labels .known.block_16{background-position:-3px -127px} +.columbia-labels .known.block_17{background-position:-65px -127px} +.columbia-labels .known.block_18{background-position:-127px -127px} +.columbia-labels .known.block_19{background-position:-189px -127px} +.columbia-labels .known.block_20{background-position:-251px -127px} +.columbia-labels .known.block_21{background-position:-313px -127px} +.columbia-labels .known.block_22{background-position:-375px -127px} +.columbia-labels .known.block_23{background-position:-437px -127px} +.columbia-labels .known.block_24{background-position:-3px -189px} +.columbia-labels .known.block_25{background-position:-65px -189px} +.columbia-labels .known.block_26{background-position:-127px -189px} +.columbia-labels .known.block_27{background-position:-189px -189px} +.columbia-labels .known.block_28{background-position:-251px -189px} +.columbia-labels .known.block_29{background-position:-313px -189px} +.columbia-labels .known.block_30{background-position:-375px -189px} +.columbia-labels .known.block_31{background-position:-437px -189px} +.columbia-labels .known.block_32{background-position:-3px -251px} +.columbia-labels .known.block_33{background-position:-65px -251px} +.columbia-labels .known.block_34{background-position:-127px -251px} +.columbia-labels .known.block_35{background-position:-189px -251px} +.columbia-labels .known.block_36{background-position:-251px -251px} +.columbia-labels .known.block_37{background-position:-313px -251px} +.columbia-labels .known.block_38{background-position:-375px -251px} +.columbia-labels .known.block_39{background-position:-437px -251px} +.columbia-labels .known.block_40{background-position:-3px -313px} +.columbia-labels .known.block_41{background-position:-65px -313px} +.columbia-labels .known.block_42{background-position:-127px -313px} +.columbia-labels .known.block_43{background-position:-189px -313px} +.columbia-labels .known.block_44{background-position:-251px -313px} +.columbia-labels .known.block_45{background-position:-313px -313px} +.columbia-labels .known.block_46{background-position:-375px -313px} +.columbia-labels .known.block_47{background-position:-437px -313px} +.columbia-labels .known.block_48{background-position:-3px -375px} +.columbia-labels .known.block_49{background-position:-65px -375px} +.columbia-labels .known.block_50{background-position:-127px -375px} +.columbia-labels .known.block_51{background-position:-189px -375px} +.columbia-labels .known.block_52{background-position:-251px -375px} +.columbia-labels .known.block_53{background-position:-313px -375px} +.columbia-labels .known.block_54{background-position:-375px -375px} +.columbia-labels .known.block_55{background-position:-437px -375px} +.columbia-labels .known.block_56{background-position:-3px -437px} +.columbia-labels .known.block_57{background-position:-65px -437px} +.columbia-labels .known.block_58{background-position:-127px -437px} +.columbia-labels .known.block_59{background-position:-189px -437px} +.columbia-labels .known.block_60{background-position:-251px -437px} +.columbia-labels .known.block_61{background-position:-313px -437px} +.columbia-labels .known.block_62{background-position:-375px -437px} diff --git a/blocks_columbia.png b/blocks_columbia.png new file mode 100644 index 0000000..1d9fde6 Binary files /dev/null and b/blocks_columbia.png differ diff --git a/blocks_simple.css b/blocks_simple.css new file mode 100644 index 0000000..8856cb6 --- /dev/null +++ b/blocks_simple.css @@ -0,0 +1,191 @@ +.simple-labels .block.known { + background-image: url('blocks_simple.webp'); + background-repeat: no-repeat; + background-size: 600% 3100%; +} +.simple-labels .block_0.r0{background-position:0% 0%} +.simple-labels .block_0.r1{background-position:0% 3.3333333333333335%} +.simple-labels .block_0.r2{background-position:0% 6.666666666666667%} +.simple-labels .block_1.r0{background-position:0% 10%} +.simple-labels .block_1.r1{background-position:0% 13.333333333333334%} +.simple-labels .block_2.r0{background-position:0% 16.666666666666668%} +.simple-labels .block_2.r1{background-position:0% 20%} +.simple-labels .block_2.r2{background-position:0% 23.333333333333332%} +.simple-labels .block_3.r0{background-position:0% 26.666666666666664%} +.simple-labels .block_3.r1{background-position:0% 29.999999999999996%} +.simple-labels .block_3.r2{background-position:0% 33.33333333333333%} +.simple-labels .block_4.r0{background-position:0% 36.666666666666664%} +.simple-labels .block_4.r1{background-position:0% 40%} +.simple-labels .block_4.r2{background-position:0% 43.333333333333336%} +.simple-labels .block_5.r0{background-position:0% 46.66666666666667%} +.simple-labels .block_5.r1{background-position:0% 50.00000000000001%} +.simple-labels .block_5.r2{background-position:0% 53.33333333333334%} +.simple-labels .block_6.r0{background-position:0% 56.66666666666668%} +.simple-labels .block_6.r1{background-position:0% 60.000000000000014%} +.simple-labels .block_6.r2{background-position:0% 63.33333333333335%} +.simple-labels .block_7.r0{background-position:0% 66.66666666666669%} +.simple-labels .block_7.r1{background-position:0% 70.00000000000001%} +.simple-labels .block_7.r2{background-position:0% 73.33333333333334%} +.simple-labels .block_8.r0{background-position:0% 76.66666666666667%} +.simple-labels .block_8.r1{background-position:0% 80%} +.simple-labels .block_8.r2{background-position:0% 83.33333333333333%} +.simple-labels .block_9.r0{background-position:0% 86.66666666666666%} +.simple-labels .block_9.r1{background-position:0% 89.99999999999999%} +.simple-labels .block_9.r2{background-position:0% 93.33333333333331%} +.simple-labels .block_10.r0{background-position:0% 96.66666666666664%} +.simple-labels .block_10.r1{background-position:0% 99.99999999999997%} +.simple-labels .block_10.r2{background-position:20% 0%} +.simple-labels .block_11.r0{background-position:20% 3.3333333333333335%} +.simple-labels .block_11.r1{background-position:20% 6.666666666666667%} +.simple-labels .block_11.r2{background-position:20% 10%} +.simple-labels .block_12.r0{background-position:20% 13.333333333333334%} +.simple-labels .block_12.r1{background-position:20% 16.666666666666668%} +.simple-labels .block_12.r2{background-position:20% 20%} +.simple-labels .block_12.r3{background-position:20% 23.333333333333332%} +.simple-labels .block_13.r0{background-position:20% 26.666666666666664%} +.simple-labels .block_13.r1{background-position:20% 29.999999999999996%} +.simple-labels .block_13.r2{background-position:20% 33.33333333333333%} +.simple-labels .block_13.r3{background-position:20% 36.666666666666664%} +.simple-labels .block_14.r0{background-position:20% 40%} +.simple-labels .block_14.r1{background-position:20% 43.333333333333336%} +.simple-labels .block_14.r2{background-position:20% 46.66666666666667%} +.simple-labels .block_14.r3{background-position:20% 50.00000000000001%} +.simple-labels .block_15.r0{background-position:20% 53.33333333333334%} +.simple-labels .block_15.r1{background-position:20% 56.66666666666668%} +.simple-labels .block_15.r2{background-position:20% 60.000000000000014%} +.simple-labels .block_15.r3{background-position:20% 63.33333333333335%} +.simple-labels .block_16.r0{background-position:20% 66.66666666666669%} +.simple-labels .block_16.r1{background-position:20% 70.00000000000001%} +.simple-labels .block_16.r2{background-position:20% 73.33333333333334%} +.simple-labels .block_16.r3{background-position:20% 76.66666666666667%} +.simple-labels .block_17.r0{background-position:20% 80%} +.simple-labels .block_17.r1{background-position:20% 83.33333333333333%} +.simple-labels .block_17.r2{background-position:20% 86.66666666666666%} +.simple-labels .block_18.r0{background-position:20% 89.99999999999999%} +.simple-labels .block_18.r1{background-position:20% 93.33333333333331%} +.simple-labels .block_19.r0{background-position:20% 96.66666666666664%} +.simple-labels .block_19.r1{background-position:20% 99.99999999999997%} +.simple-labels .block_20.r0{background-position:40% 0%} +.simple-labels .block_20.r1{background-position:40% 3.3333333333333335%} +.simple-labels .block_20.r2{background-position:40% 6.666666666666667%} +.simple-labels .block_21.r0{background-position:40% 10%} +.simple-labels .block_21.r1{background-position:40% 13.333333333333334%} +.simple-labels .block_21.r2{background-position:40% 16.666666666666668%} +.simple-labels .block_21.r3{background-position:40% 20%} +.simple-labels .block_22.r0{background-position:40% 23.333333333333332%} +.simple-labels .block_22.r1{background-position:40% 26.666666666666664%} +.simple-labels .block_22.r2{background-position:40% 29.999999999999996%} +.simple-labels .block_22.r3{background-position:40% 33.33333333333333%} +.simple-labels .block_23.r0{background-position:40% 36.666666666666664%} +.simple-labels .block_23.r1{background-position:40% 40%} +.simple-labels .block_23.r2{background-position:40% 43.333333333333336%} +.simple-labels .block_24.r0{background-position:40% 46.66666666666667%} +.simple-labels .block_24.r1{background-position:40% 50.00000000000001%} +.simple-labels .block_24.r2{background-position:40% 53.33333333333334%} +.simple-labels .block_25.r0{background-position:40% 56.66666666666668%} +.simple-labels .block_25.r1{background-position:40% 60.000000000000014%} +.simple-labels .block_26.r0{background-position:40% 63.33333333333335%} +.simple-labels .block_26.r1{background-position:40% 66.66666666666669%} +.simple-labels .block_27.r0{background-position:40% 70.00000000000001%} +.simple-labels .block_27.r1{background-position:40% 73.33333333333334%} +.simple-labels .block_28.r0{background-position:40% 76.66666666666667%} +.simple-labels .block_28.r1{background-position:40% 80%} +.simple-labels .block_29.r0{background-position:40% 83.33333333333333%} +.simple-labels .block_29.r1{background-position:40% 86.66666666666666%} +.simple-labels .block_30.r0{background-position:40% 89.99999999999999%} +.simple-labels .block_30.r1{background-position:40% 93.33333333333331%} +.simple-labels .block_31.r0{background-position:40% 96.66666666666664%} +.simple-labels .block_31.r1{background-position:40% 99.99999999999997%} +.simple-labels .block_31.r2{background-position:60% 0%} +.simple-labels .block_31.r3{background-position:60% 3.3333333333333335%} +.simple-labels .block_32.r0{background-position:60% 6.666666666666667%} +.simple-labels .block_32.r1{background-position:60% 10%} +.simple-labels .block_32.r2{background-position:60% 13.333333333333334%} +.simple-labels .block_33.r0{background-position:60% 16.666666666666668%} +.simple-labels .block_33.r1{background-position:60% 20%} +.simple-labels .block_33.r2{background-position:60% 23.333333333333332%} +.simple-labels .block_34.r0{background-position:60% 26.666666666666664%} +.simple-labels .block_34.r1{background-position:60% 29.999999999999996%} +.simple-labels .block_35.r0{background-position:60% 33.33333333333333%} +.simple-labels .block_35.r1{background-position:60% 36.666666666666664%} +.simple-labels .block_35.r2{background-position:60% 40%} +.simple-labels .block_35.r3{background-position:60% 43.333333333333336%} +.simple-labels .block_36.r0{background-position:60% 46.66666666666667%} +.simple-labels .block_36.r1{background-position:60% 50.00000000000001%} +.simple-labels .block_36.r2{background-position:60% 53.33333333333334%} +.simple-labels .block_36.r3{background-position:60% 56.66666666666668%} +.simple-labels .block_37.r0{background-position:60% 60.000000000000014%} +.simple-labels .block_37.r1{background-position:60% 63.33333333333335%} +.simple-labels .block_37.r2{background-position:60% 66.66666666666669%} +.simple-labels .block_38.r0{background-position:60% 70.00000000000001%} +.simple-labels .block_38.r1{background-position:60% 73.33333333333334%} +.simple-labels .block_38.r2{background-position:60% 76.66666666666667%} +.simple-labels .block_39.r0{background-position:60% 80%} +.simple-labels .block_39.r1{background-position:60% 83.33333333333333%} +.simple-labels .block_39.r2{background-position:60% 86.66666666666666%} +.simple-labels .block_40.r0{background-position:60% 89.99999999999999%} +.simple-labels .block_40.r1{background-position:60% 93.33333333333331%} +.simple-labels .block_40.r2{background-position:60% 96.66666666666664%} +.simple-labels .block_41.r0{background-position:60% 99.99999999999997%} +.simple-labels .block_41.r1{background-position:80% 0%} +.simple-labels .block_41.r2{background-position:80% 3.3333333333333335%} +.simple-labels .block_42.r0{background-position:80% 6.666666666666667%} +.simple-labels .block_42.r1{background-position:80% 10%} +.simple-labels .block_42.r2{background-position:80% 13.333333333333334%} +.simple-labels .block_43.r0{background-position:80% 16.666666666666668%} +.simple-labels .block_43.r1{background-position:80% 20%} +.simple-labels .block_43.r2{background-position:80% 23.333333333333332%} +.simple-labels .block_44.r0{background-position:80% 26.666666666666664%} +.simple-labels .block_44.r1{background-position:80% 29.999999999999996%} +.simple-labels .block_44.r2{background-position:80% 33.33333333333333%} +.simple-labels .block_45.r0{background-position:80% 36.666666666666664%} +.simple-labels .block_45.r1{background-position:80% 40%} +.simple-labels .block_45.r2{background-position:80% 43.333333333333336%} +.simple-labels .block_45.r3{background-position:80% 46.66666666666667%} +.simple-labels .block_46.r0{background-position:80% 50.00000000000001%} +.simple-labels .block_46.r1{background-position:80% 53.33333333333334%} +.simple-labels .block_46.r2{background-position:80% 56.66666666666668%} +.simple-labels .block_47.r0{background-position:80% 60.000000000000014%} +.simple-labels .block_47.r1{background-position:80% 63.33333333333335%} +.simple-labels .block_47.r2{background-position:80% 66.66666666666669%} +.simple-labels .block_47.r3{background-position:80% 70.00000000000001%} +.simple-labels .block_48.r0{background-position:80% 73.33333333333334%} +.simple-labels .block_48.r1{background-position:80% 76.66666666666667%} +.simple-labels .block_48.r2{background-position:80% 80%} +.simple-labels .block_49.r0{background-position:80% 83.33333333333333%} +.simple-labels .block_49.r1{background-position:80% 86.66666666666666%} +.simple-labels .block_50.r0{background-position:80% 89.99999999999999%} +.simple-labels .block_50.r1{background-position:80% 93.33333333333331%} +.simple-labels .block_50.r2{background-position:80% 96.66666666666664%} +.simple-labels .block_50.r3{background-position:80% 99.99999999999997%} +.simple-labels .block_51.r0{background-position:100% 0%} +.simple-labels .block_51.r1{background-position:100% 3.3333333333333335%} +.simple-labels .block_51.r2{background-position:100% 6.666666666666667%} +.simple-labels .block_52.r0{background-position:100% 10%} +.simple-labels .block_52.r1{background-position:100% 13.333333333333334%} +.simple-labels .block_53.r0{background-position:100% 16.666666666666668%} +.simple-labels .block_53.r1{background-position:100% 20%} +.simple-labels .block_53.r2{background-position:100% 23.333333333333332%} +.simple-labels .block_53.r3{background-position:100% 26.666666666666664%} +.simple-labels .block_54.r0{background-position:100% 29.999999999999996%} +.simple-labels .block_54.r1{background-position:100% 33.33333333333333%} +.simple-labels .block_54.r2{background-position:100% 36.666666666666664%} +.simple-labels .block_54.r3{background-position:100% 40%} +.simple-labels .block_55.r0{background-position:100% 43.333333333333336%} +.simple-labels .block_55.r1{background-position:100% 46.66666666666667%} +.simple-labels .block_55.r2{background-position:100% 50.00000000000001%} +.simple-labels .block_56.r0{background-position:100% 53.33333333333334%} +.simple-labels .block_56.r1{background-position:100% 56.66666666666668%} +.simple-labels .block_56.r2{background-position:100% 60.000000000000014%} +.simple-labels .block_57.r0{background-position:100% 63.33333333333335%} +.simple-labels .block_57.r1{background-position:100% 66.66666666666669%} +.simple-labels .block_58.r0{background-position:100% 70.00000000000001%} +.simple-labels .block_58.r1{background-position:100% 73.33333333333334%} +.simple-labels .block_59.r0{background-position:100% 76.66666666666667%} +.simple-labels .block_59.r1{background-position:100% 80%} +.simple-labels .block_60.r0{background-position:100% 83.33333333333333%} +.simple-labels .block_60.r1{background-position:100% 86.66666666666666%} +.simple-labels .block_61.r0{background-position:100% 89.99999999999999%} +.simple-labels .block_61.r1{background-position:100% 93.33333333333331%} +.simple-labels .block_62.r0{background-position:100% 96.66666666666664%} +.simple-labels .block_62.r1{background-position:100% 99.99999999999997%} diff --git a/blocks_simple.png b/blocks_simple.png new file mode 100644 index 0000000..9e95a6b Binary files /dev/null and b/blocks_simple.png differ diff --git a/cards/card_12.png b/cards/card_12.png new file mode 100644 index 0000000..c07ad51 Binary files /dev/null and b/cards/card_12.png differ diff --git a/cards/card_13.png b/cards/card_13.png new file mode 100644 index 0000000..b94219b Binary files /dev/null and b/cards/card_13.png differ diff --git a/cards/card_21.png b/cards/card_21.png new file mode 100644 index 0000000..44ab682 Binary files /dev/null and b/cards/card_21.png differ diff --git a/cards/card_22.png b/cards/card_22.png new file mode 100644 index 0000000..c933997 Binary files /dev/null and b/cards/card_22.png differ diff --git a/cards/card_23.png b/cards/card_23.png new file mode 100644 index 0000000..3816393 Binary files /dev/null and b/cards/card_23.png differ diff --git a/cards/card_31.png b/cards/card_31.png new file mode 100644 index 0000000..ab97dae Binary files /dev/null and b/cards/card_31.png differ diff --git a/cards/card_32.png b/cards/card_32.png new file mode 100644 index 0000000..f9d6503 Binary files /dev/null and b/cards/card_32.png differ diff --git a/cards/card_41.png b/cards/card_41.png new file mode 100644 index 0000000..a2053c5 Binary files /dev/null and b/cards/card_41.png differ diff --git a/cards/card_apollo.png b/cards/card_apollo.png new file mode 100644 index 0000000..8b257b6 Binary files /dev/null and b/cards/card_apollo.png differ diff --git a/cards/card_back.png b/cards/card_back.png new file mode 100644 index 0000000..dbc854b Binary files /dev/null and b/cards/card_back.png differ diff --git a/cards/card_jupiter.png b/cards/card_jupiter.png new file mode 100644 index 0000000..47e853a Binary files /dev/null and b/cards/card_jupiter.png differ diff --git a/cards/card_mars.png b/cards/card_mars.png new file mode 100644 index 0000000..3f3998f Binary files /dev/null and b/cards/card_mars.png differ diff --git a/cards/card_mercury.png b/cards/card_mercury.png new file mode 100644 index 0000000..4ce3911 Binary files /dev/null and b/cards/card_mercury.png differ diff --git a/cards/card_neptune.png b/cards/card_neptune.png new file mode 100644 index 0000000..d75dec3 Binary files /dev/null and b/cards/card_neptune.png differ diff --git a/cards/card_pluto.png b/cards/card_pluto.png new file mode 100644 index 0000000..b9bf0f2 Binary files /dev/null and b/cards/card_pluto.png differ diff --git a/cards/card_vulcan.png b/cards/card_vulcan.png new file mode 100644 index 0000000..ee0fd47 Binary files /dev/null and b/cards/card_vulcan.png differ diff --git a/cover.1x.jpg b/cover.1x.jpg new file mode 100644 index 0000000..dfdb3d6 Binary files /dev/null and b/cover.1x.jpg differ diff --git a/cover.2x.jpg b/cover.2x.jpg new file mode 100644 index 0000000..864b9ea Binary files /dev/null and b/cover.2x.jpg differ diff --git a/cover.jpg b/cover.jpg new file mode 100644 index 0000000..8dfb28e Binary files /dev/null and b/cover.jpg differ diff --git a/data.js b/data.js new file mode 100644 index 0000000..c19a2ae --- /dev/null +++ b/data.js @@ -0,0 +1,507 @@ +"use strict"; + +const CAESAR = "Caesar"; +const POMPEIUS = "Pompeius"; +const CLEOPATRA = "Cleopatra"; +const BOTH = "Both"; + +const CARDS = { + 1: { name: "Apollo", event: "Apollo", image: "apollo" }, + 2: { name: "Jupiter", event: "Jupiter", image: "jupiter" }, + 3: { name: "Mars", event: "Mars", image: "mars" }, + 4: { name: "Mercury", event: "Mercury", image: "mercury" }, + 5: { name: "Neptune", event: "Neptune", image: "neptune" }, + 6: { name: "Pluto", event: "Pluto", image: "pluto" }, + 7: { name: "Vulcan", event: "Vulcan", image: "vulcan" }, + 8: { name: "4/1", move: 4, levy: 1, image: "41" }, + 9: { name: "3/2", move: 3, levy: 2, image: "32" }, + 10: { name: "3/2", move: 3, levy: 2, image: "32" }, + 11: { name: "3/1", move: 3, levy: 1, image: "31" }, + 12: { name: "3/1", move: 3, levy: 1, image: "31" }, + 13: { name: "3/1", move: 3, levy: 1, image: "31" }, + 14: { name: "2/3", move: 2, levy: 3, image: "23" }, + 15: { name: "2/3", move: 2, levy: 3, image: "23" }, + 16: { name: "2/3", move: 2, levy: 3, image: "23" }, + 17: { name: "2/3", move: 2, levy: 3, image: "23" }, + 18: { name: "2/2", move: 2, levy: 2, image: "22" }, + 19: { name: "2/2", move: 2, levy: 2, image: "22" }, + 20: { name: "2/2", move: 2, levy: 2, image: "22" }, + 21: { name: "2/2", move: 2, levy: 2, image: "22" }, + 22: { name: "2/1", move: 2, levy: 1, image: "21" }, + 23: { name: "2/1", move: 2, levy: 1, image: "21" }, + 24: { name: "2/1", move: 2, levy: 1, image: "21" }, + 25: { name: "1/3", move: 1, levy: 3, image: "13" }, + 26: { name: "1/2", move: 1, levy: 2, image: "12" }, + 27: { name: "1/2", move: 1, levy: 2, image: "12" }, +}; + +const BLOCKS = {}; +const EDGES = {}; + +const SPACES = { + Dead: { x: 300, y: 236 }, + Levy: { x: 767, y: 1191 }, + Aenos: { x: 1872, y: 463 }, + Aleria: { x: 1000, y: 470 }, + Alexandria: { x: 2045, y: 1083 }, + Ambracia: { x: 1582, y: 600 }, + Ancyra: { x: 2195, y: 488 }, + Antiochia: { x: 2360, y: 700 }, + Appia: { x: 2043, y: 553 }, + Aquileia: { x: 1215, y: 206 }, + Asculum: { x: 1233, y: 394 }, + Asturica: { x: 232, y: 565 }, + Athenae: { x: 1739, y: 663 }, + Badias: { x: 902, y: 977 }, + Bilbilis: { x: 459, y: 585 }, + Brundisium: { x: 1441, y: 525 }, + Burdigala: { x: 495, y: 320 }, + Byzantium: { x: 1990, y: 417 }, + Caralis: { x: 990, y: 665 }, + 'Carthago Nova': { x: 479, y: 818 }, + Catabathmus: { x: 1890, y: 1080 }, + Cenabum: { x: 653, y: 133 }, + Corduba: { x: 317, y: 837 }, + Creta: { x: 1794, y: 842 }, + Cyrene: { x: 1584, y: 1044 }, + Damascus: { x: 2415, y: 870 }, + Dyrrachium: { x: 1522, y: 472 }, + Emerita: { x: 200, y: 765 }, + Ephesus: { x: 1905, y: 635 }, + Eusebia: { x: 2310, y: 556 }, + Gades: { x: 232, y: 922 }, + Genua: { x: 995, y: 313 }, + Iomnium: { x: 763, y: 860 }, + Jerusalem: { x: 2324, y: 994 }, + Lilybaeum: { x: 1172, y: 731 }, + Lugdunum: { x: 790, y: 235 }, + Massilia: { x: 795, y: 405 }, + Memphis: { x: 2105, y: 1167 }, + Messana: { x: 1290, y: 708 }, + Narbo: { x: 695, y: 455 }, + Neapolis: { x: 1298, y: 535 }, + Nicomedia: { x: 2056, y: 424 }, + Olisipo: { x: 92, y: 810 }, + Pelusium: { x: 2178, y: 1069 }, + Perga: { x: 2085, y: 700 }, + Pergamum: { x: 1913, y: 535 }, + Portus: { x: 100, y: 630 }, + Pylos: { x: 1633, y: 733 }, + Ravenna: { x: 1144, y: 286 }, + Rhegium: { x: 1357, y: 698 }, + Roma: { x: 1160, y: 450 }, + Sala: { x: 205, y: 1089 }, + Salamis: { x: 2240, y: 800 }, + Salona: { x: 1336, y: 319 }, + Serdica: { x: 1689, y: 354 }, + Siga: { x: 472, y: 987 }, + Sinope: { x: 2303, y: 339 }, + Sipontum: { x: 1325, y: 455 }, + Sirmium: { x: 1460, y: 208 }, + Syracusae: { x: 1302, y: 778 }, + Tacape: { x: 1059, y: 1019 }, + Tarraco: { x: 564, y: 599 }, + Tarsus: { x: 2285, y: 675 }, + Thessalonica: { x: 1689, y: 484 }, + Thubactus: { x: 1276, y: 1084 }, + Tingis: { x: 257, y: 1000 }, + Toletum: { x: 359, y: 712 }, + Treviri: { x: 860, y: 68 }, + Utica: { x: 1045, y: 795 }, + 'Mare Aegaeum': { x: 1830, y: 670 }, + 'Mare Aegypticum': { x: 2020, y: 905 }, + 'Mare Hadriaticum': { x: 1360, y: 390 }, + 'Mare Hispanum': { x: 835, y: 650 }, + 'Mare Internum': { x: 1485, y: 870 }, + 'Mare Tyrrhenum': { x: 1140, y: 640 }, + 'Oceanus Atlanticus': { x: 118, y: 1014 }, + 'Pontus Euxinus': { x: 2147, y: 271 }, + 'Propontis': { x: 1945, y: 468 } +}; + +(function () { + function space(axis, major, minor, wrap, name, type, value) { + SPACES[name].type = type; + SPACES[name].value = value | 0; + SPACES[name].exits = []; + SPACES[name].layout_axis = axis; + SPACES[name].layout_major = (1 - major) / 2; + SPACES[name].layout_minor = (1 - minor) / 2; + SPACES[name].wrap = wrap; + } + + space('X', 0, 0, 3, "Dead", "pool"); + space('X', 0, 0, 18, "Levy", "pool"); + + space('Y', -1, 0, 3, "Aenos", "port"); + space('X', 0, 0, 3, "Aleria", "port"); + space('X', 0, -1, 3, "Alexandria", "major-port", 2); + space('X', 0, 0, 3, "Ambracia", "port"); + space('X', 1, 0, 3, "Ancyra", "city"); + space('Y', 0, 1, 4, "Antiochia", "port", 1); + space('X', 1, 0, 3, "Appia", "city"); + space('X', 1, 0, 3, "Aquileia", "port"); + space('Y', 0, 1, 3, "Asculum", "city"); + space('Y', -1, 0, 3, "Asturica", "city"); + space('Y', 0, 0, 4, "Athenae", "major-port", 1); + space('X', -1, 1, 3, "Badias", "city"); + space('X', -1, 0, 3, "Bilbilis", "city"); + space('Y', 1, 0, 3, "Brundisium", "port"); + space('Y', 1, -1, 3, "Burdigala", "port"); + space('Y', -1, 0, 4, "Byzantium", "port", 1); + space('Y', -1, 0, 3, "Caralis", "port"); + space('X', 1, 0, 4, "Carthago Nova", "major-port", 1); + space('X', -1, 1, 3, "Catabathmus", "port"); + space('X', -1, 0, 3, "Cenabum", "city"); + space('X', 0, 0, 3, "Corduba", "city"); + space('X', 0, 0, 3, "Creta", "major-port"); + space('X', 0, 1, 3, "Cyrene", "port"); + space('X', -1, 0, 3, "Damascus", "city"); + space('Y', -1, 0, 3, "Dyrrachium", "port"); + space('Y', -1, 0, 3, "Emerita", "city"); + space('X', 1, 0, 4, "Ephesus", "major-port", 1); + space('X', 1, 0, 3, "Eusebia", "city"); + space('X', 0, 0, 3, "Gades", "port"); + space('Y', -1, -1, 3, "Genua", "port"); + space('X', 0, 1, 3, "Iomnium", "port"); + space('X', 0, 0, 3, "Jerusalem", "city"); + space('Y', 1, 0, 3, "Lilybaeum", "port"); + space('X', 0, -1, 3, "Lugdunum", "city"); + space('Y', 0, 1, 4, "Massilia", "major-port", 1); + space('X', 0, 1, 3, "Memphis", "city"); + space('Y', -1, 0, 3, "Messana", "port"); + space('X', -1, 0, 3, "Narbo", "port"); + space('X', 0, 1, 3, "Neapolis", "major-port"); + space('X', 1, 0, 3, "Nicomedia", "port"); + space('Y', 1, 0, 3, "Olisipo", "port"); + space('X', 1, 0, 3, "Pelusium", "port"); + space('Y', 1, -1, 3, "Perga", "port"); + space('X', 0, 0, 3, "Pergamum", "city"); + space('Y', 0, 0, 3, "Portus", "port"); + space('X', -1, 0, 3, "Pylos", "port"); + space('Y', -1, -1, 3, "Ravenna", "major-port"); + space('X', 1, 0, 3, "Rhegium", "port"); + space('Y', 0, -1, 4, "Roma", "port", 2); + space('X', 1, 1, 3, "Sala", "port"); + space('X', 0, 0, 3, "Salamis", "major-port"); + space('X', 1, 0, 3, "Salona", "port"); + space('X', 0, 0, 3, "Serdica", "city"); + space('X', 0, 1, 3, "Siga", "port"); + space('X', 0, 0, 3, "Sinope", "port"); + space('X', 1, 0, 3, "Sipontum", "port"); + space('X', 1, -1, 3, "Sirmium", "city"); + space('Y', 1, 0, 4, "Syracusae", "major-port", 1); + space('X', 0, 1, 3, "Tacape", "port"); + space('X', 1, 0, 4, "Tarraco", "port", 1); + space('X', -1, 0, 3, "Tarsus", "port"); + space('X', 0, 0, 3, "Thessalonica", "port"); + space('X', 0, 0, 3, "Thubactus", "port"); + space('X', 0, 0, 3, "Tingis", "port"); + space('X', 0, 0, 3, "Toletum", "city"); + space('X', 1, 1, 3, "Treviri", "city"); + space('X', -1, 0, 4, "Utica", "major-port", 1); + + space('Y', 0, 0, 5, "Mare Aegaeum", "sea"); + space('X', 0, 0, 5, "Mare Aegypticum", "sea"); + space('X', 0, 0, 5, "Mare Hadriaticum", "sea"); + space('Y', 0, 0, 5, "Mare Hispanum", "sea"); + space('X', 0, 0, 5, "Mare Internum", "sea"); + space('X', 0, 0, 5, "Mare Tyrrhenum", "sea"); + space('Y', 0, 0, 5, "Oceanus Atlanticus", "sea"); + space('X', 0, 0, 5, "Pontus Euxinus", "sea"); + space('X', 0, 0, 5, "Propontis", "sea"); + + function edge(a, b, type) { + if (a > b) + [a, b] = [b, a]; + let AB = a + "/" + b; + EDGES[AB] = type; + SPACES[a].exits.push(b); + SPACES[b].exits.push(a); + } + + function major(a,b) { edge(a,b, "major"); } + function minor(a,b) { edge(a,b, "minor"); } + function strait(a,b) { edge(a,b, "strait"); } + function sea(a,b) { edge(a,b, "sea"); } + + major("Alexandria", "Catabathmus"); + major("Alexandria", "Memphis"); + major("Antiochia", "Damascus"); + major("Antiochia", "Tarsus"); + major("Athenae", "Thessalonica"); + major("Aenos", "Byzantium"); + major("Aenos", "Thessalonica"); + major("Ancyra", "Appia"); + major("Ancyra", "Eusebia"); + major("Ancyra", "Nicomedia"); + major("Appia", "Ephesus"); + major("Aquileia", "Ravenna"); + major("Aquileia", "Salona"); + major("Aquileia", "Sirmium"); + major("Asculum", "Ravenna"); + major("Badias", "Utica"); + major("Bilbilis", "Burdigala"); + major("Bilbilis", "Toletum"); + major("Brundisium", "Neapolis"); + major("Burdigala", "Narbo"); + major("Carthago Nova", "Corduba"); + major("Carthago Nova", "Tarraco"); + major("Catabathmus", "Cyrene"); + major("Cenabum", "Lugdunum"); + major("Corduba", "Gades"); + major("Corduba", "Toletum"); + major("Damascus", "Jerusalem"); + major("Dyrrachium", "Thessalonica"); + major("Eusebia", "Tarsus"); + major("Genua", "Massilia"); + major("Genua", "Roma"); + major("Genua", "Ravenna"); + major("Iomnium", "Utica"); + major("Jerusalem", "Pelusium"); + major("Lugdunum", "Massilia"); + major("Massilia", "Narbo"); + major("Memphis", "Pelusium"); + major("Narbo", "Tarraco"); + major("Neapolis", "Roma"); + major("Neapolis", "Rhegium"); + major("Serdica", "Sirmium"); + major("Serdica", "Thessalonica"); + major("Tacape", "Utica"); + + minor("Alexandria", "Pelusium"); + minor("Athenae", "Ambracia"); + minor("Athenae", "Pylos"); + minor("Aenos", "Serdica"); + minor("Ambracia", "Dyrrachium"); + minor("Ancyra", "Sinope"); + minor("Appia", "Nicomedia"); + minor("Appia", "Perga"); + minor("Asculum", "Roma"); + minor("Asculum", "Sipontum"); + minor("Asturica", "Bilbilis"); + minor("Asturica", "Emerita"); + minor("Asturica", "Portus"); + minor("Asturica", "Toletum"); + minor("Badias", "Iomnium"); + minor("Badias", "Tacape"); + minor("Bilbilis", "Tarraco"); + minor("Brundisium", "Sipontum"); + minor("Burdigala", "Cenabum"); + minor("Carthago Nova", "Gades"); + minor("Carthago Nova", "Toletum"); + minor("Cenabum", "Treviri"); + minor("Cyrene", "Thubactus"); + minor("Dyrrachium", "Salona"); + minor("Ephesus", "Perga"); + minor("Ephesus", "Pergamum"); + minor("Emerita", "Gades"); + minor("Emerita", "Olisipo"); + minor("Eusebia", "Sinope"); + minor("Gades", "Olisipo"); + minor("Genua", "Lugdunum"); + minor("Iomnium", "Siga"); + minor("Lilybaeum", "Messana"); + minor("Lilybaeum", "Syracusae"); + minor("Lugdunum", "Treviri"); + minor("Messana", "Syracusae"); + minor("Neapolis", "Sipontum"); + minor("Nicomedia", "Pergamum"); + minor("Nicomedia", "Sinope"); + minor("Olisipo", "Portus"); + minor("Perga", "Tarsus"); + minor("Roma", "Ravenna"); + minor("Sala", "Siga"); + minor("Sala", "Tingis"); + minor("Salona", "Sirmium"); + minor("Siga", "Tingis"); + minor("Tacape", "Thubactus"); + + strait("Aenos", "Pergamum"); + strait("Byzantium", "Nicomedia"); + strait("Gades", "Tingis"); + strait("Messana", "Rhegium"); + + sea("Alexandria", "Mare Aegypticum"); + sea("Antiochia", "Mare Aegypticum"); + sea("Athenae", "Mare Aegaeum"); + sea("Aenos", "Mare Aegaeum"); + sea("Aleria", "Mare Tyrrhenum"); + sea("Ambracia", "Mare Internum"); + sea("Aquileia", "Mare Hadriaticum"); + sea("Byzantium", "Pontus Euxinus"); + sea("Byzantium", "Propontis"); + sea("Brundisium", "Mare Hadriaticum"); + sea("Brundisium", "Mare Internum"); + sea("Burdigala", "Oceanus Atlanticus"); + sea("Carthago Nova", "Mare Hispanum"); + sea("Caralis", "Mare Hispanum"); + sea("Caralis", "Mare Tyrrhenum"); + sea("Catabathmus", "Mare Aegypticum"); + sea("Creta", "Mare Aegaeum"); + sea("Creta", "Mare Aegypticum"); + sea("Creta", "Mare Internum"); + sea("Cyrene", "Mare Internum"); + sea("Dyrrachium", "Mare Hadriaticum"); + sea("Ephesus", "Mare Aegaeum"); + sea("Gades", "Oceanus Atlanticus"); + sea("Genua", "Mare Hispanum"); + sea("Genua", "Mare Tyrrhenum"); + sea("Iomnium", "Mare Hispanum"); + sea("Lilybaeum", "Mare Internum"); + sea("Lilybaeum", "Mare Tyrrhenum"); + sea("Massilia", "Mare Hispanum"); + sea("Mare Aegaeum", "Mare Aegypticum"); + sea("Mare Aegaeum", "Mare Internum"); + sea("Mare Aegaeum", "Propontis"); + sea("Mare Aegaeum", "Thessalonica"); + sea("Mare Aegypticum", "Mare Internum"); + sea("Mare Aegypticum", "Pelusium"); + sea("Mare Aegypticum", "Perga"); + sea("Mare Aegypticum", "Salamis"); + sea("Mare Aegypticum", "Tarsus"); + sea("Mare Hadriaticum", "Mare Internum"); + sea("Mare Hadriaticum", "Ravenna"); + sea("Mare Hadriaticum", "Salona"); + sea("Mare Hadriaticum", "Sipontum"); + sea("Mare Hispanum", "Mare Tyrrhenum"); + sea("Mare Hispanum", "Narbo"); + sea("Mare Hispanum", "Oceanus Atlanticus"); + sea("Mare Hispanum", "Siga"); + sea("Mare Hispanum", "Tarraco"); + sea("Mare Hispanum", "Tingis"); + sea("Mare Hispanum", "Utica"); + sea("Mare Internum", "Mare Tyrrhenum"); + sea("Mare Internum", "Messana"); + sea("Mare Internum", "Pylos"); + sea("Mare Internum", "Rhegium"); + sea("Mare Internum", "Syracusae"); + sea("Mare Internum", "Tacape"); + sea("Mare Internum", "Thubactus"); + sea("Mare Internum", "Utica"); + sea("Mare Tyrrhenum", "Messana"); + sea("Mare Tyrrhenum", "Neapolis"); + sea("Mare Tyrrhenum", "Roma"); + sea("Mare Tyrrhenum", "Rhegium"); + sea("Mare Tyrrhenum", "Utica"); + sea("Nicomedia", "Propontis"); + sea("Oceanus Atlanticus", "Olisipo"); + sea("Oceanus Atlanticus", "Portus"); + sea("Oceanus Atlanticus", "Sala"); + sea("Oceanus Atlanticus", "Tingis"); + sea("Pontus Euxinus", "Propontis"); + sea("Pontus Euxinus", "Sinope"); + + let index = 0; + + function block(owner, CR, steps, type, name, levy) { + let id = name; + let [initiative, firepower] = CR; + if (type != 'leader' && type != 'cleopatra' && type != 'legio') + id = owner[0] + " " + id; + let descr = name; + if (type != 'leader' && type != 'cleopatra' && type != 'legio') + descr = owner[0] + ". " + descr; + if (levy) { + if (levy == "Carthago Nova") + descr += " (C. Nova)"; + else + descr += " (" + levy + ")"; + } + BLOCKS[id] = { + owner: owner, + name: name, + steps: steps, + initiative: initiative, + firepower: firepower, + type: type, + levy: levy, + description: descr, + label: index++, + } + } + + function caesar(CR,S,T,N,L) { block(CAESAR, CR, S, T, N, L); } + function pompeius(CR,S,T,N,L) { block(POMPEIUS, CR, S, T, N, L); } + + caesar("A3", 3, "leader", "Caesar"); + caesar("A2", 2, "leader", "Antonius"); + caesar("A2", 3, "leader", "Octavian"); + + caesar("C2", 3, "legio", "Legio 7", "Tarraco"); + caesar("C3", 3, "legio", "Legio 8", "Tarraco"); + caesar("C2", 3, "legio", "Legio 9", "Carthago Nova"); + caesar("C4", 3, "legio", "Legio 10", "Corduba"); + caesar("C3", 3, "legio", "Legio 11", "Genua"); + caesar("C3", 3, "legio", "Legio 12", "Genua"); + caesar("C3", 3, "legio", "Legio 13", "Ravenna"); + + caesar("C3", 3, "legio", "Legio 14", "Ravenna"); + caesar("C2", 3, "legio", "Legio 16", "Aquileia"); + caesar("C2", 4, "legio", "Legio 17", "Roma"); + caesar("C2", 4, "legio", "Legio 18", "Neapolis"); + caesar("C2", 4, "legio", "Legio 19", "Syracusae"); + caesar("C2", 4, "legio", "Legio 20", "Athenae"); + caesar("C3", 4, "legio", "Legio 21", "Ancyra"); + + caesar("B2", 3, "equitatus", "Equitatus 1", "Lugdunum"); + caesar("B2", 2, "equitatus", "Equitatus 2", "Toletum"); + caesar("B3", 2, "equitatus", "Equitatus 3", "Byzantium"); + caesar("B3", 3, "equitatus", "Equitatus 4", "Antiochia"); + + caesar("B1", 4, "auxilia-b", "Auxilia 1"); + caesar("B1", 4, "auxilia-b", "Auxilia 2"); + caesar("A1", 3, "auxilia-a", "Auxilia 3"); + caesar("A1", 3, "auxilia-a", "Auxilia 4"); + + caesar("X4", 2, "ballista", "Ballista"); + + caesar("D2", 2, "navis", "Navis 1"); + caesar("D2", 2, "navis", "Navis 2"); + caesar("D2", 2, "navis", "Navis 3"); + caesar("D3", 2, "navis", "Navis 4"); + caesar("D3", 2, "navis", "Navis 5"); + + block(POMPEIUS, "C1", 4, "cleopatra", "Cleopatra"); + + pompeius("B3", 3, "leader", "Pompeius"); + pompeius("A2", 3, "leader", "Scipio"); + pompeius("A2", 2, "leader", "Brutus"); + + pompeius("C2", 4, "legio", "Legio 1", "Roma"); + pompeius("C2", 4, "legio", "Legio 2", "Carthago Nova"); + pompeius("C3", 3, "legio", "Legio 3", "Ravenna"); + pompeius("C2", 3, "legio", "Legio 4", "Carthago Nova"); + pompeius("C2", 3, "legio", "Legio 5", "Tarraco"); + pompeius("C2", 3, "legio", "Legio 6", "Tarraco"); + pompeius("C2", 3, "legio", "Legio 32", "Athenae"); + + pompeius("C3", 3, "legio", "Legio 33", "Creta"); + pompeius("C3", 3, "legio", "Legio 34", "Antiochia"); + pompeius("C2", 3, "legio", "Legio 35", "Byzantium"); + pompeius("C2", 4, "legio", "Legio 36", "Ephesus"); + pompeius("C2", 3, "legio", "Legio 37", "Syracusae"); + pompeius("C2", 4, "legio", "Legio 38", "Alexandria"); + pompeius("C2", 3, "legio", "Legio 39", "Utica"); + + pompeius("B3", 2, "equitatus", "Equitatus 1", "Toletum"); + pompeius("B2", 4, "equitatus", "Equitatus 2", "Badias"); + pompeius("B2", 3, "equitatus", "Equitatus 3", "Antiochia"); + pompeius("B3", 2, "elephant", "Elephant", "Utica"); + + pompeius("B1", 4, "auxilia-b", "Auxilia 1"); + pompeius("B1", 4, "auxilia-b", "Auxilia 2"); + pompeius("A1", 3, "auxilia-a", "Auxilia 3"); + pompeius("A1", 3, "auxilia-a", "Auxilia 4"); + + pompeius("X4", 2, "ballista", "Ballista"); + + pompeius("D3", 2, "navis", "Navis 1"); + pompeius("D3", 2, "navis", "Navis 2"); + pompeius("D2", 2, "navis", "Navis 3"); + pompeius("D2", 2, "navis", "Navis 4"); + pompeius("D2", 2, "navis", "Navis 5"); +})(); + +if (typeof module != 'undefined') + module.exports = { CARDS, BLOCKS, SPACES, EDGES } diff --git a/info/Julius Caesar 1.1.pdf b/info/Julius Caesar 1.1.pdf new file mode 100644 index 0000000..552a7d0 Binary files /dev/null and b/info/Julius Caesar 1.1.pdf differ diff --git a/info/blocks.html b/info/blocks.html new file mode 100644 index 0000000..dcf49cd --- /dev/null +++ b/info/blocks.html @@ -0,0 +1,126 @@ + +Julius Caesar - Blocks + + + + +

+Julius Caesar - Blocks +

+ +

+ + +

+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/info/cards.html b/info/cards.html new file mode 100644 index 0000000..ecf52aa --- /dev/null +++ b/info/cards.html @@ -0,0 +1,36 @@ + +Julius Caesar - Cards + + +

Julius Caesar - Cards

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/info/notes.html b/info/notes.html new file mode 100644 index 0000000..7b0f6e0 --- /dev/null +++ b/info/notes.html @@ -0,0 +1,33 @@ + + +Julius Caesar - Notes + + + + + +
+ +

+Julius Caesar: Implementation Notes

+ +
+ +

+How do I designate the attacking main force? +

+The first block that moves into a contested area defines the main force. Any other blocks +who enter the battle from the same road also count as being part of the main force. +The attacking main force move is marked with an asterisk in the game log. + +


+ +

+What happens when Cleopatra would be eliminated by Vulcan and switches side, and there are other enemy blocks in the same location? +

+Cleopatra switches sides and counts as the attacking main force. +The battle is resolved in the normal battle phase. + +


+ +

diff --git a/info/rules.html b/info/rules.html new file mode 100644 index 0000000..e836aa7 --- /dev/null +++ b/info/rules.html @@ -0,0 +1,1234 @@ + + +Julius Caesar 1.1 + + + + +
+

The Roman Calendar

+

Few Romans knew or cared what year it was,

+

but those who did counted the years from the

+

semi-fabled founding of Rome by Romulus in

+

754BC. Hence the civil war began in 705 (49bc),

+

and the assassination of Julius Caesar occurred

+

in 710 (44bc) of the Roman Calendar.

+

Julius Caesar established the Julian Calendar

+

in 709 (45bc). This Calendar corrected a two

+

month error in the solar cycle and established

+

the leap year concept to keep it accurate.

+

The month of July was renamed after Caesar.

+

With minor revisions to leap years, this is the

+

Calendar we still use (in the West) today.

+

Victory

+

City vps total 13. Pompey starts the game

+

holding 7vp while Caesar has only 1vp (Massilia).

+

Rome, Athens, Byzantium, and Ephesus are

+

Vacant. The burden of attack lies with Caesar to

+

avoid an early defeat.

+

Event Cards

+

The deck contains seven (7) event cards, each

+

of them named after a major Roman deity.

+

These cards allow special actions to occur that

+

break the normal rules. See each card for details.

+

Game Turn Example

+

•Card Play: Caesar 2/1, Pompey 2/2. Cards are

+

tied (compare only Moves) but Caesar is Player

+

1 on ties.

+

•Caesar (Player 1): 2 Moves then 1 Levy

+

•Pompey (Player 2): 2 Moves then 2 Levies

+

•Battle Phase: Resolve any battles in the order

+

chosen by Player 1.

+

2.0 GAME TURNS

+

There are five Years in the game, each

+

divided into five Game Turns. Each game

+

turn has three (3) Phases, played in the

+

sequence below.

+

2.1 CARD PHASE

+

There are twenty-seven (27) cards:

+

twenty (20) Command cards and seven

+

(7) Event cards. At the beginning of each

+

Year, the cards are shuffled and six (6)

+

cards are dealt to each player. Examine

+

your cards and discard one (1). The discard

+

is not revealed.

+

Each player starts a game turn by

+

playing one (1) card face-down. The cards

+

are then revealed. Card values are Moves

+

(banner) and Levies (circles on banner

+

staff). The player with the higher Move

+

card is Player 1 that game turn.

+

IMPORTANT: If the cards played are

+

equal (Move number) Caesar is Player 1.

+

Event cards have a special action

+

defined on the card. The player of an

+

Event card is always Player 1. However,

+

if both plays are Event cards, both events

+

are cancelled and the game turn ends.

+

NOTE: Players must play a card, but can

+

elect to take less moves/levies if desired.

+

Commands cannot be saved for future use.

+

2.2 COMMAND PHASE

+

Player 1 moves and levies (or executes

+

an Event), then Player 2 moves and levies.

+

• Move: Each move allows one Group

+

(any/all blocks in one location) to move

+

one or two cities; Navis can move one

+

or two seas. Blocks cannot attack or

+

reinforce if they move two cities/seas.

+

Blocks entering a city or sea containing

+

enemy blocks must stop. See 6.0 for

+

details.

+

• Levy: for each Levy, one (1) step can

+

be added to one (1) existing block, or

+

one (1) new block can be chosen from

+

a player's Levy Pool and deployed on

+

the map at strength I. Choose levies

+

after all movement is complete – they

+

cannot move in the same turn. See 6.4

+

for details.

+

2.3 BATTLE PHASE

+

Battles are fought between opposing

+

blocks in the same city or sea. They are

+

fought one at a time in any sequence

+

determined by Player 1. See: 7.0 for details.

+

JULIUS CAESAR

+

Copyright © 2010 Columbia Games

+

1

+

Version 1.1

+

1.0 INTRODUCTION

+

Julius Caesar brings the drama of the

+

most famous and significant Roman Civil

+

War (49–45 BC) to life. Players take control

+

of the legions of Caesar or Pompey and

+

fight to determine the future of Rome –

+

republic or empire.

+

1.1 PLAYERS

+

The game is intended for two players.

+

One player represents Julius Caesar, the

+

other Pompey the Great.

+

1.2 VICTORY

+

The game is divided into five (5) Years,

+

each with five (5) game turns. After each

+

Year ends, a Winter Turn (8.0) is played

+

when players check to see if either has

+

won.

+

To determine victory, after each Year,

+

players score the total value of Friendly

+

cities, plus one Victory Point (1vp) for each

+

enemy leader killed. To win, a player must

+

have 10 (or more) VPs.

+

If neither player wins by the end of

+

Year 5, the winner is the player with the

+

higher VPs. If still tied, the game is won

+

by the player holding Rome. Otherwise the

+

game is a draw.

+

1.3 CONTENTS

+

• Game Map

+

• 63 blocks (31 tan, 31 green, 1 blue).

+

• Label sheet (for blocks)

+

• Cards (27)

+

• Dice (4)

+

• These Rules

+

COMMAND

+

LEVY 2

+

MOVE 3

+
+
+

JULIUS CAESAR

+

3.22 Legions

+

Legions are identified by

+

an Eagle icon. They have

+

a number ID on the top

+

left, and a levy city on

+

the bottom. Legions have

+

combat ratings of C2, C3,

+

or C4, with veteran legions having the

+

higher ratings.

+

3.23 Auxilia

+

Both players have four (4)

+

Auxilia, two light infantry

+

(B1) and two archers (A1).

+

These troops can be raised

+

in any Friendly city.

+

3.24 Equitatus

+

Equitatus (cavalry) are

+

rated B2 or B3. Like

+

legions, they are raised in

+

specific Friendly cities.

+

These cities have a nearby

+

equitatus symbol on the

+

map. Caesar has four (4) equitatus. Pompey

+

has three (3) equitatus, but also one

+

Elephant (7.41).

+

3.25 Ballista

+

Each player has one

+

(1) Ballista. They have

+

different combat values for

+

defense and offense, see

+

7.42. They can be built in

+

any Friendly city.

+

3.26 Navis

+

Players have five (5) Navis

+

to represent the warships

+

used by both sides. They

+

have D2 or D3 combat. In

+

a sea battle this "D" rating

+

has no impact since all

+

Navis have the same rating, but they are

+

vulnerable in land battles. Navis must be

+

built in Friendly major ports, identified on

+

the map with a Navis symbol.

+

3.27 Cleopatra

+

Cleopatra represents the

+

forces of Egypt and is

+

rated C1. She is not a

+

leader per these rules.

+

Cleopatra starts play on

+

the Pompey side, but can

+

fight for either side. See: 7.52.

+

JULIUS CAESAR

+

Copyright © 2010 Columbia Games

+

2

+

Version 1.1

+

3.0 ARMIES

+

One label must be attached to the face

+

of each block. Lightly position each label,

+

ensure it is straight, and then press firmly

+

to the block.

+

+

+

Blocks

+

Labels

+

+

Caesar

+

Tan

+

Red

+

+

Pompey

+

Green

+

Ochre

+

+

Cleopatra Blue

+

Blue

+

3.1 BLOCK DATA

+

3.11 Strength

+

The current strength of a block is the

+

Roman numeral on the top edge when the

+

block stands upright. Blocks can have a

+

maximum strength of IV, III, or II.

+

Strength determines how many six-

+

sided dice (d6) are rolled for a block in

+

combat. A block at strength IV rolls 4d6

+

(four six-sided dice); a block at strength I

+

rolls 1d6.

+

For each hit taken in combat, the

+

block’s strength is reduced by rotating the

+

block 90 degrees counter-clockwise. The

+

sidebar shows the same block at strength

+

III, II, and I.

+

3.12 Combat Rating

+

The Combat Rating is indicated by

+

a letter and number, such as A2 or B3.

+

The letter (initiative) determines when

+

a block has a battle turn. All A blocks go

+

first, then all B blocks, then all C blocks.

+

If tied, the Defender has the first battle

+

turn. The number (firepower) indicates the

+

maximum roll that will score a hit. See 7.3.

+

3.13 Name

+

Legions have a city name where this

+

block must be recruited when deployed

+

from the Levy Pool.

+

3.2 BLOCK TYPES

+

3.21 Leaders

+

Both sides have three (3)

+

named leaders:

+

Caesar, Antonius, Octavian

+

Pompey, Scipio, Brutus.

+

Leader blocks include

+

their significant guards,

+

generally elite cavalry. Players start the

+

game with two leaders. The third may be

+

brought into play if a leader is killed (see:

+

7.51).

+

Label Sheet

+

The red labels on the die-cut sheet are for

+

Caesar (tan blocks) and ochre labels for Pompey

+

(green blocks). The Cleopatra label goes on the

+

blue block.

+

Fog-of-War

+

Surprise is an exciting aspect of this game.

+

Except when fighting a battle, active blocks

+

stand upright facing the owner. This promotes

+

bluff and innovative strategies because players

+

are uncertain of the strength or identity of an

+

enemy block.

+

Equitatus

+

Romans were never considered exceptionally good

+

horsemen, at least not after the connection between

+

cavalry and the aristocracy was abandoned. By the

+

time of late Republic, the Equitatus was generally

+

made up of non-Roman horsemen from Gallia,

+

Germania, Hispania, Numidia, Syria, and Thracia.

+

Caesar used Germanic cavalry to fight the Gauls

+

and also to serve as his formidable bodyguard.

+

Elephants

+

There is one Elephant block for Pompey. Caesar

+

would not have elephants in his army believing them

+

to be fragile and unpredictable.

+

COMBAT

+

(C3)

+

Strength I

+

Strength III Strength II

+

STEP REDUCTION

+

STRENGTH

+

(Maximum III)

+

LEGION

+

(13)

+

LEVY

+

CITY

+

(RAVENNA)

+
+
+

4.0 MAPBOARD

+

The mapboard depicts the

+

Mediterranean Sea and surrounding

+

territory. The Caesar player sits at the

+

north edge of the map, Pompey player at

+

the south edge.

+

4.1 LOCATIONS

+

Blocks on the map must be located on

+

cities or seas. Navis must be located on

+

seas or in port cities.

+

4.2 CITIES

+

Cities govern the movement and

+

location of blocks. Eleven cities have a

+

value of 1 or 2. These numbers (total 13)

+

are Victory Points (VPs). The numbers

+

are also significant for Wintering (8.4).

+

4.21 City Control

+

The control status of a city can be:

+

Friendly: Occupied by one or more of

+

your blocks.

+

Enemy: Friendly to your opponent.

+

Vacant: Friendly to neither player.

+

Contested: Contains blocks of both

+

players, awaiting Battle Resolution.

+

IMPORTANT: Changes to city control

+

are effective immediately. Friendly cities

+

become immediately neutral when left

+

Vacant. Similarly, attacking an Enemy city,

+

even with one block, immediately converts

+

it to Contested status until the battle is

+

resolved.

+

4.3 ROADS

+

Cities are connected by important

+

roads of the period, some of them named

+

for historical interest. Blocks move from

+

one city to another via these roads.

+

4.31 Road Classes

+

Two classes of road are depicted,

+

Major (solid line) and Minor (dotted line).

+

In one game turn, four (4) blocks can move

+

along a Major Road, but only two (2) along

+

a Minor road. See 6.11.

+

4.32 Straits

+

Four straits appear on the map, each

+

identified by a blue arrow: Herculeum,

+

Messana, Hellespontus, and Bosphorus.

+

Each game turn, two (2) land blocks may

+

cross each strait, but only one (1) land

+

block when the city on the other side is

+

defended.

+

Navis ignore straits when moving from

+

one sea to an adjacent sea. Control of

+

cities on either side of a strait has no effect

+

on Navis or Amphibious movement.

+

4.4 Seas

+

There are nine (9) seas: Atlanticus,

+

Hispanum, Tyrrhenum, Internum,

+

Hadriaticum, Egypticum, Aegaeum, Propontis,

+

and Pontus Euxinus. These seas can only be

+

occupied and controlled by Navis.

+

Friendly: Seas occupied by one or

+

more of your Navis.

+

Enemy: Seas occupied by one or more

+

enemy Navis.

+

Vacant: Friendly to neither player.

+

Contested: Seas containing Navis of

+

both players, awaiting Battle Resolution.

+

SEA CONTROL: As with cities, changes

+

to sea control are effective immediately. A

+

sea immediately becomes neutral when it

+

is left Vacant.

+

4.41 Islands

+

The islands of Corsica, Sardinia, Sicilia,

+

Creta, and Cyprus are playable. All other

+

islands are unplayable. Moves to-from

+

playable islands requires a Navis or

+

Amphibious Move (6.3).

+

4.42 Ports

+

All cities located on a coastline are

+

ports. Some ports have a Navis symbol

+

that designates a major port, which are

+

essential for building Navis.

+

Ports located on sea borders allow

+

access to two (2) seas. Utica and Creta

+

have access to three (3) seas. See

+

sidebar for clarification.

+

JULIUS CAESAR

+

Copyright © 2010 Columbia Games

+

3

+

Version 1.1

+

Battle Sites

+

The main battles of the war are shown on the

+

map, red for victories by Caesar and green for

+

Pompey.

+

Ports

+

Below is a list of ports and their adjacent seas.

+

Major ports are indicated in Caps.

+

+

SEA PORTS

+

+

Atlanticus: Burdigala, Gades, Olisipo, Portus,

+

Sala, Tingis.

+

+

Hispanum: Caralis, Carthago Nova, Genua,

+

Iomnium, Massilia, Narbo, Siga,

+

Tarraco, Tingis, Utica.

+

Tyrrhenum: Aleria, Caralis, Genua, Lilybaeum,

+

Messana, Neapolis, Rhegium,

+

Rome, Utica.

+

+

Internum: Ambracia, Brundisium, Creta,

+

Cyrene, Lilybaeum, Messana,

+

Pylos, Rhegium, Syracuse,

+

Tacape, Thubactus, Utica.

+

Hadriaticum: Aquileia, Brundisium,

+

Dyrrachium, Ravenna, Salone,

+

Sipontum.

+

+

Aegaeum: Aenos, Athena, Creta, Ephesus,

+

Thessalonika.

+

+

Propontis: Byzantium, Nicomedia.

+

+

Euxinus: Byzantium, Sinope.

+

+

Egypticum: Alexandria, Antioch,

+

Catabathmus, Creta, Perga,

+

Pelusium, Salamis, Tarsus.

+

Event Cards

+

Apollo: the trickster Sun God grants you the

+

power to copy the card played by your

+

opponent last turn. If that card was an event

+

card, it copies that card exactly.

+

Jupiter: the King of the Gods grants you a

+

defection of one enemy block adjacent

+

to a friendly city. Navis at sea could be

+

chosen, but note that Leaders and Navis do

+

not defect. They are reduced by one step.

+

Cleopatra is not a leader and can defect

+

using this card.

+

Mars: the God of War grants a surprise attack.

+

All attacking blocks in one battle get to fire

+

before any defending blocks in Round 1.

+

Caution: the Defender may get two fires in a

+

row (last in Round 1 and first in Round 2).

+

Mercury: the Messenger of the Gods allows

+

blocks in one group to move one extra city.

+

Blocks can move in multiple directions, and

+

use the bonus (or not) as desired.

+

Neptune: the God of the Sea favors your sea

+

battle or shore attack. This is essentially a

+

"Mars" card for ships.

+

Pluto: The God of Death likes big battles.

+

He allows road limits to be increased for

+

one Group Move, but not for Regroups or

+

Retreats.

+

Vulcan: Reduces all blocks in a designated city

+

by one step. No exceptions. All blocks at

+

strength I, including leaders, are eliminated.

+
+
+

5.0 DEPLOYMENT

+

5.1 HISTORICAL DEPLOYMENT

+

Both players deploy blocks in cities as

+

noted. Blocks are always deployed upright

+

at full strength.

+

5.2 LEVY POOL

+

Each player maintains a Levy Pool

+

off-map. Blocks in the Levy Pool stand

+

upright to conceal their identity. Players

+

expend Levy Points to deploy blocks from

+

their pool to the map. Except for Leaders

+

(see 7.51) blocks that are eliminated during

+

play are returned to the Levy Pool, but are

+

always placed face-up until the current

+

Year ends. These blocks cannot be levied

+

until the next Year.

+

5.3 FREE DEPLOYMENT

+

An optional deployment method.

+

Players deploy blocks as per historical OB,

+

but may swap any blocks on the map as

+

long as the original number of deployed

+

blocks in each city is maintained. Blocks

+

from the Friendly Levy Pool cannot be

+

substituted.

+

EXAMPLE: In the historical OB, Pompey

+

has three (3) blocks in Neapolis. For free

+

deployment, any three blocks from the

+

historical deployment can be there.

+

CAESAR, 705 (49 BC)

+

Caesar: Ravenna

+

Legio 13: Ravenna

+

Navis 2: Ravenna

+

Antonius: Genua

+

Legio 8: Genua

+

Legio 12: Genua

+

Legio 11: Massilia

+

Legio 14: Massilia

+

Navis 1: Massilia

+

Legio 7: Narbo

+

Legio 9: Narbo

+

Legio 10: Narbo

+

Legio 16: Lugdunum

+

Equitatus 1: Lugdunum

+

LEVY POOL

+

Octavian

+

Legio 17, 18, 19, 20, 21

+

Auxilia 1, 2, 3, 4

+

Equitatus 2, 3, 4

+

Ballista

+

Navis 3, 4, 5

+

JULIUS CAESAR

+

Copyright © 2010 Columbia Games

+

4

+

Version 1.1

+

POMPEY, 705 (49 BC)

+

Pompey: Neapolis

+

Legio 1: Neapolis

+

Navis 1: Neapolis

+

Legio 3: Brundisium

+

Legio 37: Syracuse

+

Scipio: Antioch

+

Legio 34: Antioch

+

Cleopatra: Alexandria

+

Navis 2: Alexandria

+

Legio 39: Utica

+

Navis 3: Utica

+

Legio 2: Carthago Nova

+

Legio 4: Carthago Nova

+

Legio 5: Tarraco

+

Legio 6: Tarraco

+

Equitatus 1: Tarraco

+

LEVY POOL

+

Brutus

+

Legio 32, 33, 35, 36, 38

+

Auxilia 1, 2, 3, 4

+

Equitatus 2, 3, Elephant

+

Ballista

+

Navis 4, 5

+
+
+

JULIUS CAESAR

+

Copyright © 2010 Columbia Games

+

5

+

Version 1.1

+

Move Example

+

For 1mp, a player may move any/all Massilia

+

blocks to one or more of Narbo, Lugdunum, and

+

Genua. If not attacking, blocks can move further

+

to any/all of Tarraco, Burdigala, Cenabum,

+

Treveri, Ravenna, or Rome. If some of the

+

Massilia blocks are Navis, they can move to

+

Mare Hispanum, then to an adjacent sea or port

+

if not attacking.

+

Movement & Attacking

+

Blocks can move only one city/sea when they

+

attack. This has many subtle implications. For

+

example, assuming 6 Caesar blocks located in

+

Massila and 3 Pompey blocks in Tarraco and

+

3 in Genua. The Caesar blocks cannot attack

+

Tarraco since that is two moves away. Genua

+

is adjacent and can therefore be attacked with

+

4 Caesar blocks (road limit). Unlike many

+

other games, however, the remaining 2 Caesar

+

blocks cannot also attack Genua via Lugdunum

+

since that would be a move of two cities. Note

+

however, the effect of the Mercury card which

+

could allow an attack on Tarraco, and/or a

+

two-pronged attack on Genua.

+

Pinning Example

+

Five (5) blocks defend Rome. Four (4) blocks

+

attack from Genua and two (2) from Ravenna,

+

the latter being reserves. Assuming the Genua

+

blocks are the Main Attack, a total of 4 blocks

+

in Rome are pinned, but 1 is unpinned and may

+

move except via the two roads being used by

+

the Attacker.

+

Seapower

+

The function of Navis are to win control of a

+

Sea to enable amphibious movement. They can

+

also attack and occupy enemy ports, or occupy

+

Vacant ports.

+

Navis Move Examples

+

1. Navis located in Massilia can move to a

+

Friendly or Vacant Mare Hispanum. If not

+

attacking, the Navis can then move to Oceanus

+

Atlanticus, or Mare Tyrrhenum, or to any other

+

Friendly or Vacant port on Mare Hispanum

+

(Utica, Caralis, Iomnium, Siga, Tingis, Carthago

+

Nova, Tarraco, Narbo, and Genua).

+

2. Navis located on Mare Internum can move

+

to Mare Tyrrhenum, Mare Hadriaticum, Mare

+

Aegaeum, or Mare Egypticum. If not attacking, a

+

Navis that moved to Mare Aegaeum could move

+

to another adjacent Friendly or Vacant sea

+

(Propontis or Mare Egypticum) or to any Friendly

+

or Vacant port on this sea, (Creta, Athena,

+

Thessalonika, Aenos, or Ephesus). Note that the

+

city of Pergamum is not a port.

+

Amphibious Move Example

+

Caesar has 3 Navis, 1 each on Mare Tyrrhenum,

+

Mare Internum, and Mare Egypticum. He elects to

+

spend 2mp to make an amphibious move of two

+

legions from Rome to Antioch, which is Vacant.

+

This is possible because the three seas crossed

+

are Friendly and adjacent, and the amphibious

+

move is made before any other move. Note that

+

an Amphibious move by Player 1 is completed

+

in the Command Phase, before Player 2 moves.

+

6.14 Response Movement

+

Player 2 can expend MPs to move

+

unpinned blocks to reinforce Defending

+

blocks in Contested cities/seas. Blocks can

+

Respond only from adjacent cities/seas.

+

IMPORTANT: Responding blocks are

+

always placed in Reserve. See: 7.3.

+

6.15 Stacking

+

There is no stacking limit for blocks

+

during a Year. Stacking applies during the

+

Winter Turn. See 8.4.

+

6.2 NAVIS MOVEMENT

+

Navis move from a port to an adjacent

+

sea (or vice-versa), or from one sea to an

+

adjacent sea. They can never move from

+

one port directly to another port, except

+

via the adjacent sea.

+

When located with land blocks, Navis

+

can move to sea as part of a group move

+

for that city. See: Navis Move Examples.

+

Navis can make one (1) move and

+

attack, or two (2) moves and not attack.

+

See sidebar for examples.

+

Navis can attack/respond only from

+

an adjacent sea/port. See Battle Reserves

+

(7.3) for more details about attacking and

+

responding.

+

6.3 AMPHIBIOUS MOVEMENT

+

Land blocks may move from one port

+

to any other Friendly or Vacant port

+

across one or more adjacent Friendly

+

seas. Cost is 1 MP per block.

+

Amphibious movement must be made

+

before any other moves are made that

+

turn. Hence, a sea used in amphibious

+

movement must already be Friendly before

+

any other moves are made that turn.

+

Blocks cannot move by land and sea

+

in the same turn (or vice-versa). 1 Navis

+

block must remain in the sea that was

+

crossed for the entire Command Phase;

+

other Navis may move as desired.

+

Amphibious moves can never be made

+

to Enemy or Contested ports. Unpinned

+

(6.13) land blocks in a Contested city may

+

conduct an amphibious move provided the

+

adjacent sea is Friendly.

+

6.0 COMMAND PHASE

+

Player 1 Moves and then Levies with

+

the values from his active card, then Player

+

2 does the same.

+

6.1 GROUP MOVES

+

Command cards have Move Points

+

(MP) of 1 to 4. Each MP allows any/

+

all block(s) in one location (city or sea)

+

to move to adjacent cities/seas. If not

+

attacking, blocks may continue to the next

+

adjacent location(s).

+

Blocks that move cannot move

+

again this game turn, except to Retreat

+

or Regroup. When a block has finished

+

moving, turn it face-down to show that it

+

cannot move again this turn.

+

6.11 Road Limits

+

The maximum number of blocks that

+

can move along any road varies by type:

+

Major: 4 blocks

+

Minor: 2 blocks

+

Straits: 2 blocks (1 if attacking)

+

Example: A maximum of 4 blocks may

+

move from Genua to Rome, and one or two

+

blocks may move from Ravenna to Rome.

+

Road Limits apply to each player.

+

Hence, both players can move two blocks

+

along the same minor road in the same

+

game turn.

+

Example: Player 1 moves 4 blocks from

+

Massilia to Genua to Ravenna. Player 2

+

now moves 4 blocks from Rome to Genua

+

to Massilia. Both players used the road

+

section between Massilia and Genua, but

+

at different times. Of course, if Player 1

+

had left at least 1 block in Genua, Player 2

+

could not have moved through this block to

+

Massilia without fighting a battle.

+

6.12 Attacking

+

Blocks entering an Enemy city/sea

+

are Attacking; the enemy blocks are

+

Defending.

+

Blocks may attack from adjacent

+

cities/seas only. A player may attack via

+

two or more roads, but each road will

+

require a separate MP. See 6.2 and 7.3.

+

6.13 Pinning

+

Attacking blocks (excluding Reserves)

+

prevent an equal number of defending

+

blocks from moving. The Defender

+

chooses which blocks are pinned. The

+

"unpinned" blocks may move normally and

+

even attack, but cannot use any road or

+

sea border used by the enemy that battle.

+
+
+

JULIUS CAESAR

+

Copyright © 2010 Columbia Games

+

6

+

Version 1.1

+

Battle Sequence

+

Battle sequence (7.1) is controlled by Player

+

1. This can be significant because the results

+

of a battle will change city or sea control from

+

Contested to Friendly for the victor and that

+

impacts Retreats and Regroups.

+

Battle Turns

+

Caesar (A3) and Equitatus (B2) attack Pompey

+

(B3) and Navis (D2). Battle Turn sequence:

+

Caesar: attacking A3

+

Pompey: defending B3

+

Caesar Equitatus: attacking B2

+

Pompey Navis: defending D2

+

Attacker/Defender

+

Because both players move before combat, a

+

player can be the Defender in some battles, and

+

the Attacker in others.

+

Battle Hits

+

Each hit reduces the strongest enemy block at

+

that instant. Hence, if two hits are scored on

+

three enemy blocks at strength III, II, II, the first

+

hit must be taken on the enemy III block. All

+

three blocks are now at strength II, so the next

+

hit can be on any enemy block (owner choice).

+

Pursuit

+

Pursuit is naturally handled by the game system.

+

A block wishing to retreat must await its normal

+

battle turn which allows faster enemy troops

+

to fire before they can retreat. If the Defender

+

survives three Battle Rounds, the Attacker

+

must retreat during round 4, but takes fire from

+

defending blocks that have an equal or earlier

+

battle turn.

+

Treachery

+

Several legions switched sides during the war,

+

This is represented by the Jupiter card, which

+

switches one block to the enemy side. Even the

+

famous 13th legion, which crossed the Rubicon

+

with Caesar, later rebelled and changed sides.

+

7.3 BATTLE RESERVES

+

When attacking via two or more roads

+

or sea borders, one road/border (Attacker

+

choice) must be declared the Main

+

Attack. Blocks using other roads/borders

+

are Reserves.

+

Blocks moved by Player 2 to reinforce

+

a battle started by Player 1 are also

+

Reserves.

+

Reserve blocks may not fire, retreat,

+

or take hits in Round 1. They are revealed

+

and arrive at the start of Round 2 to take

+

normal turns.

+

Example: Caesar attacks Tarraco

+

from Narbo with 4 blocks (main attack)

+

and from Bilbilis with 2 blocks. Pompey

+

has 3 blocks defending Tarraco, but

+

moves 4 blocks from Nova Carthago to

+

Tarraco. Round 1 has the 3 Tarraco blocks

+

defending against 4 Caesar blocks from

+

Narbo. Caesar blocks from Bilbilis and

+

Pompey blocks from Nova Carthago are

+

Reserves that arrive for Round 2.

+

7.31 Disruption

+

Reserve blocks are Disrupted if

+

their main force is eliminated in Round 1.

+

Disrupted blocks immediately lose one (1)

+

step and then fight normally.

+

IMPORTANT: If the disrupted player is

+

the Defender, the Attacker now becomes the

+

Defender for the rest of the battle.

+

7.4 Battle HITS

+

Each firing block in its Battle Turn rolls

+

as many dice as its current strength. A hit

+

is scored for each die roll equal to or lower

+

than the block’s firepower.

+

Example: Caesar 3 rolls 3 dice. He has

+

A3 combat: rolls of 1, 2, 3 are hits.

+

Each hit reduces the strongest enemy

+

block at that instant. When two or more

+

blocks share the highest strength, the

+

owner chooses which to reduce. Except for

+

Leaders, when blocks are reduced below

+

strength I, they are immediately eliminated

+

(see 7.5) and returned to the Levy Pool.

+

Note: combat is not simultaneous. All

+

hits are applied immediately.

+

7.41 Elephant

+

The elephant block has two steps, IV

+

and II. It drops one step per hit which

+

means the block is powerful but fragile.

+

7.42 Ballista

+

The Ballista block fights at B4 when

+

Defending, but at D4 when Attacking.

+

6.4 LEVIES

+

Command cards have 1, 2, or 3 Levy

+

Points (LP). Each LP allows:

+

• One (1) step to be added to one (1)

+

existing block. Multiple steps can be

+

added to the same block, each for LP1.

+

• One (1) new block can be chosen from

+

a player's Levy Pool and deployed in

+

a city at minimum strength. Steps can

+

be added to a new block immediately,

+

each step costing LP1 (including the

+

elephant). Multiple new blocks can be

+

deployed in the same city if desired.

+

Leaders deploy in any Friendly city.

+

Legions deploy in their named city,

+

which must be Friendly.

+

Equitatae/Elephant deploy in their

+

named city, which must be Friendly.

+

Auxilia/Ballista deploy in any

+

Friendly city.

+

Navis deploy in any Friendly major

+

port. Steps can be added to existing

+

Navis in any port, but never at sea.

+

IMPORTANT: In all cases, new blocks

+

and steps must be raised in Friendly cities,

+

meaning a city currently occupied by at

+

least one Friendly block. New blocks and

+

steps can never be added to Vacant or

+

Contested cities.

+

7.0 BATTLES

+

7.1 BATTLE SEQUENCE

+

Battles are fought one by one after all

+

moves are completed. Player 1 determines

+

which battle to fight first. Blocks are not

+

revealed until a battle is fought. Reveal

+

blocks (not Reserves) by tipping them

+

forward at current strength. After the battle

+

is completed, stand all blocks upright, then

+

Player 1 selects the next battle.

+

7.2 BATTLE TURNS

+

Each block has one battle turn per

+

Battle Round. In its turn, a block may

+

either Fire, Retreat, or Pass, except

+

Retreat is not allowed in Round 1.

+

The sequence of turns depends on

+

combat ratings. “A” blocks go before “B”

+

blocks, then “C” blocks, then "D" blocks.

+

Defending “A” blocks go before Attacking

+

“A” blocks, and so on.

+

After all blocks have taken one Battle

+

Turn, one Round has been fought. Battles

+

are fought for a maximum of four (4)

+

Rounds. Attacking blocks must retreat

+

during Round 4 in their normal battle turn.

+
+
+

JULIUS CAESAR

+

Copyright © 2010 Columbia Games

+

7

+

Version 1.1

+

If both players have an adjacent and

+

Friendly sea and a Friendly destination,

+

both can sea retreat.

+

EXAMPLE: with a battle in Utica, if one

+

player has a Navis on Mare Internum, and

+

the other has a Navis on Mare Hispanum,

+

both players can Sea Retreat via their own

+

Friendly sea.

+

7.7 REGROUPS

+

When a battle ends the victor may

+

Regroup. All victorious blocks (including

+

any in Reserve) can move to any adjacent

+

city that is currently Friendly or Vacant.

+

Road Limits (6.11) apply.

+

Amphibious Movement cannot be

+

used to Regroup.

+

7.8 NAVIS BATTLES

+

When enemy Navis occupy the same

+

sea a Navis battle occurs. Navis have D2

+

or D3 combat, Defender first. As with land

+

battles, the attacker must retreat during

+

Round 4 if any defending ships remain.

+

7.81 Shore Combat

+

Navis can also be involved in battles

+

ashore in ports, either as the Attacker

+

or Defender. Navis can attack from an

+

adjacent sea only.

+

7.82 Navis Retreats

+

Navis may retreat in their normal "D"

+

battle turn, starting in Round 2.

+

Attacking Navis Retreat to:

+

• Seas or Ports they came from, provided

+

these locations are still Friendly or

+

Vacant, or

+

• Friendly adjacent seas, or

+

• Friendly ports on the same sea.

+

Defending Navis Retreat to:

+

• Friendly adjacent seas, or

+

• Vacant adjacent seas except where

+

the Attacker came from, or

+

• Friendly port on the same sea.

+

If no Retreat is possible, Navis must

+

win the fight or perish.

+

7.83 Navis Regroups

+

Navis that win a sea battle can

+

Regroup to any adjacent sea that is

+

Friendly or Vacant, or to any Friendly or

+

Vacant port on the same sea.

+

7.5 ELIMINATED BLOCKS

+

Eliminated blocks are returned to their

+

owner's Levy Pool, but are placed face-up

+

(in front of the upright blocks) and cannot be

+

levied again this Year.

+

7.51 Leaders

+

Leaders are permanently eliminated.

+

Give the block as a "trophy" to the enemy

+

player, who counts it as 1vp.

+

When a player loses a leader, the

+

third leader is added to the Levy Pool and

+

becomes available to be built and deployed

+

(normal cost) in any Friendly city.

+

7.52 Cleopatra

+

Cleopatra can fight for either side. If

+

eliminated in battle she immediately joins

+

the other side at strength I and fights for

+

that side on her next battle turn.

+

During each Winter turn, she must

+

return to Alexandria. See 8.1.

+

7.6 Retreats

+

Each block may retreat on its Battle

+

Turn (instead of firing), except blocks can

+

never retreat on Battle Round 1. Blocks

+

that cannot retreat when required are

+

eliminated.

+

7.61 Retreat Limits

+

Road Limits (6.11) apply to all

+

retreating blocks each Battle Round.

+

Blocks can never retreat to Enemy or

+

Contested cities/seas.

+

Retreating across a strait has a limit of

+

one (1) block per round.

+

7.62 Attacker Retreats

+

Attacking blocks can retreat on their

+

battle turn starting in Round 2 and must

+

retreat during Round 4. Blocks may Retreat

+

to an adjacent Vacant city via road(s) used

+

to start or reinforce the battle, or to any

+

Friendly adjacent cities.

+

7.63 Defender Retreats

+

Defending blocks can retreat on their

+

battle turn starting on Round 2. Retreat is

+

made to any adjacent cities, Friendly or

+

Vacant, but not along roads used by the

+

Attacker to enter the battle.

+

7.64 Sea Retreats

+

Players can retreat land blocks by

+

sea provided an adjacent sea is Friendly.

+

A maximum of one (1) block per Battle

+

Round can Sea Retreat. The destination

+

port must be Friendly. Each block can Sea

+

Retreat across one (1) adjacent sea, to a

+

Friendly port(s) on that same sea only.

+

TIMELINE

+

60 BC: First Triumvirate formed between Caesar,

+

Crassius, and Pompey. Caesar is made proconsul

+

of Gaul, Cisalpine Gaul, and Illyricum, commanding

+

four legions. Pompey is made proconsul of

+

Hispania, and Crassius proconsul of Syria.

+

53 BC: Crassius is killed fighting in Parthia ending

+

the First Triumvirate. Pompey governs Hispania

+

from Rome while Caesar fights in Gaul.

+

52 BC: Caesar commands ten legions and defeats

+

Vercingetorix at Alesia, ending the Gallic Wars.

+

Cato, Pompey, and Scipio lead a Senate faction

+

opposed to Caesar's "populist policies". The Senate

+

demands Caesar disband and return to Rome to

+

answer charges of "war crimes". Caesar refuses to

+

disband arguing he is proconsul of Gaul until 49BC.

+

50 BC: Caesar now has nine veteran legions, 3000

+

cavalry, and a 900 man bodyguard. Pompey has

+

seven legions in Hispania, two in Italia, and two

+

in Syria and Africa. Pompey has naval superiority.

+

Senate declares Caesar an enemy of the state.

+

49 BC: Caesar crosses Rubicon with XIII Legion

+

in January. Pompey retreats from Rome to

+

Brundisium. Caesar besieges Brundisium, but

+

Pompey escapes by ship to Greece. Caesar now

+

marches to Spain, where he forces five Pompey

+

Legions to surrender at Llerda.

+

48 BC: Caesar and Antonius assemble five legions

+

at Brundisium and ship them to Greece. The

+

Battle of Dyrrachium is fought in July, ending

+

with a Pompey victory. Caesar retreats but then

+

wins a decisive victory at Pharsalus in Thessaly.

+

Pompey flees to Egypt where he is assassinated by

+

command of Ptolemy XIII. Caesar now becomes

+

involved in a civil war between Ptolemy XIII and

+

his sister Cleopatra VII. Caesar supports Cleopatra

+

and defeats Ptolemy XIII who drowns in the Nile.

+

47 BC: Caesar attacks into Syria and Pontus,

+

defeating Pharnaces II, a petty king who took

+

advantage of the Roman Civil War to expand his

+

power. Pharnaces is crushed at Battle of Zela, said

+

to be the origin of the famous phrase "Veni, Vidi,

+

Vici" (I came, I saw, I conquered).

+

46 BC: Battle of Thapsus. Caesar invades Africa

+

with 10 legions and defeats Scipio's 14 legions.

+

Scipio & Cato take their own lives. Pompey's son,

+

Sextus Pompey, escapes to Hispania to continue

+

the war.

+

45 BC: Battle of Munda. Caesar invades Hispania

+

by sea with 8 legions. He defeats Sextus (13 legions)

+

who is killed, ending the war.

+

44 BC: On the Ides of March, Caesar is

+

assassinated in a conspiracy arranged by Brutus

+

and Cassius. The assassins flee Rome; Antonius and

+

Octavian assume command.

+

42 BC: Battle of Phillipi. Octavian and Antonius

+

defeat Brutus and Cassius, who both commit

+

suicide. Eleven years later, the two victors fight for

+

supremacy, a struggle that Octavian wins at Actium

+

to become Augustus, the first Emperor of Imperial

+

Rome.

+
+
+

JULIUS CAESAR

+

Copyright © 2010 Columbia Games

+

8

+

Version 1.1

+

8.0 WINTER TURN

+

A Year ends when all five (5) cards

+

have been played. A Winter Turn now

+

occurs during which players determine if

+

either has won. Play the winter events in

+

the exact order given.

+

8.1 CLEOPATRA GOES HOME

+

Move Cleopatra to Alexandria. If

+

enemy-occupied, she joins that side

+

immediately at her current strength.

+

8.2 VICTORY

+

Determine if one player has won.

+

See: 1.2.

+

8.3 NAVIS TO PORT

+

Move all Navis to a Friendly port on

+

the same sea (Caesar first). Navis unable

+

to move to a Friendly port are disbanded,

+

but can be rebuilt in the upcoming Year.

+

8.4 WINTER SUPPLY

+

All cities can supply in winter a

+

maximum of three (3) blocks without

+

penalty. This limit is increased by the city

+

value if any. Hence, Genua can support 3

+

blocks, Massila can support 3+1=4, and

+

Rome can support 3+2=5.

+

Each surplus block (owner choice) is

+

disbanded to the Friendly Levy Pool, but

+

can be rebuilt in the upcoming Year.

+

8.5 DISBANDING

+

Players cannot merge blocks on the

+

map. They may disband any block (except

+

Cleopatra) to their Levy Pool. Steps on

+

disbanded blocks are forfeit, but they can

+

be rebuilt in the upcoming Year.

+

8.6 YEAR RESET

+

All face-up blocks in Levy Pools stand-

+

up and are available to be recruited in the

+

upcoming Year.

+

Shuffle all 27 cards and deal six (6)

+

cards to each player. Examine your cards

+

and discard one (1). The discard is not

+

revealed.

+

INDEX

+

Amphibious Movement

+

6.3

+

Ballista

+

3.25, 7.42

+

Battles

+

2.3, 7.0

+

Disruption

+

7.31

+

Hits

+

7.4

+

Reserves

+

7.3

+

Retreats

+

7.6

+

Sequence

+

7.1

+

Turns

+

7.2

+

Cavalry

+

3.24

+

Cities

+

4.2

+

Control

+

4.21

+

Victory

+

1.2

+

Cleopatra

+

3.27, 7.52, 8.1

+

Combat Rating

+

3.12

+

Deployment

+

5.0

+

Historical

+

5.1

+

Free Deployment

+

5.3

+

Disbanding

+

8.5

+

Disruption

+

7.31

+

Eliminations

+

7.5

+

Equitatus

+

3.24

+

Elephants

+

7.41

+

Friendly

+

4.21

+

Game Turns

+

2.0

+

Islands

+

4.41

+

Leaders

+

1.2, 3.21, 7.51

+

Levy

+

2.3, 6.4

+

Levy Pool

+

5.2

+

Movement

+

6.0

+

Group Move

+

6.1

+

Navis Move

+

6.2

+

Amphibious Move

+

6.3

+

Navis

+

3.26

+

Navis Move

+

6.2

+

Navis Battles

+

7.8

+

Navis Wintering

+

8.3

+

Pinning

+

6.13

+

Ports

+

4.42, 6.3

+

Regroups

+

7.7

+

Navis Regroups

+

7.83

+

Reserves

+

7.3

+

Retreats

+

7.6

+

Attacker Retreats

+

7.62

+

Defender Retreats

+

7.63

+

Navis Retreats

+

7.82

+

Retreat Limits

+

7.61

+

Sea Retreats

+

7.64

+

Roads

+

4.3

+

Major & Minor Roads

+

4.31

+

Sea Moves

+

6.2

+

Seas

+

4.4

+

Straits

+

4.32

+

Supply (Winter)

+

8.4

+

Victory

+

1.2

+

Winter

+

8.0

+

GAME CREDITS

+

Game Design:

+

Justin Thompson

+

+

Grant Dalgliesh

+

Developer:

+

Tom Dalgliesh

+

Art/Graphics: Karim Chakroun

+

+

Mark Churms

+

Contributors:

+

Mark Adams

+

+

Bill Alderman

+

+

Clayton Baisch

+

+

Kevin Duke

+

+

Stan Hilinski

+

+

Steve Koleszar

+

+

Gerald Lientz

+

+

Stuart Pierce

+

+

Dave Platnick

+

+

Bill Powers

+

+

Bruce Reiff

+

+

George Seary

+

Columbia Games, Inc

+

POB 3457, Blaine

+

WA 98231 USA

+

360/366-2228

+

800/636-3631 (toll free)

+

For game updates and discussion, see:

+

www.columbiagames.com

+
+ + diff --git a/info/rules01.jpg b/info/rules01.jpg new file mode 100644 index 0000000..04145b3 Binary files /dev/null and b/info/rules01.jpg differ diff --git a/info/rules02.jpg b/info/rules02.jpg new file mode 100644 index 0000000..db005dd Binary files /dev/null and b/info/rules02.jpg differ diff --git a/info/rules03.jpg b/info/rules03.jpg new file mode 100644 index 0000000..1075415 Binary files /dev/null and b/info/rules03.jpg differ diff --git a/info/rules04.jpg b/info/rules04.jpg new file mode 100644 index 0000000..1d3ffae Binary files /dev/null and b/info/rules04.jpg differ diff --git a/info/rules05.jpg b/info/rules05.jpg new file mode 100644 index 0000000..0e73abc Binary files /dev/null and b/info/rules05.jpg differ diff --git a/info/rules06.jpg b/info/rules06.jpg new file mode 100644 index 0000000..0e73abc Binary files /dev/null and b/info/rules06.jpg differ diff --git a/info/rules07.jpg b/info/rules07.jpg new file mode 100644 index 0000000..0e73abc Binary files /dev/null and b/info/rules07.jpg differ diff --git a/info/rules08.jpg b/info/rules08.jpg new file mode 100644 index 0000000..5240cd3 Binary files /dev/null and b/info/rules08.jpg differ diff --git a/map150.png b/map150.png new file mode 100644 index 0000000..9543c6a Binary files /dev/null and b/map150.png differ diff --git a/map75.png b/map75.png new file mode 100644 index 0000000..8720d07 Binary files /dev/null and b/map75.png differ diff --git a/play.html b/play.html new file mode 100644 index 0000000..9285f6e --- /dev/null +++ b/play.html @@ -0,0 +1,319 @@ + + + + + +JULIUS CAESAR + + + + + + + + + + + + + + +
+ +
+
Chat
+
+
+
+ + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + +
+
+
+ +
Connecting...
+ + + + + +
+ +
+ +
+
1 VP
+
Caesar ($USER)
+
+
+ +
+
7 VP
+
Pompeius ($USER)
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ +
+ +
+ diff --git a/rules.js b/rules.js new file mode 100644 index 0000000..fe18901 --- /dev/null +++ b/rules.js @@ -0,0 +1,2420 @@ +"use strict"; + +exports.scenarios = [ + "Historical", + // TODO: Free Deployment + // TODO: Avalon Digital scenarios? +]; + +const { CARDS, SPACES, EDGES, BLOCKS } = require('./data'); + +const APOLLO = 1; + +const CAESAR = "Caesar"; +const POMPEIUS = "Pompeius"; +const CLEOPATRA = "Cleopatra"; +const OCTAVIAN = "Octavian"; +const BRUTUS = "Brutus"; +const ANTONIUS = "Antonius"; +const SCIPIO = "Scipio"; + +const ALEXANDRIA = "Alexandria"; +const ROMA = "Roma"; +const DEAD = "Dead"; +const LEVY = "Levy"; + +// serif cirled numbers +const DIE_HIT = [ 0, '\u2776', '\u2777', '\u2778', '\u2779', '\u277A', '\u277B' ]; +const DIE_MISS = [ 0, '\u2460', '\u2461', '\u2462', '\u2463', '\u2464', '\u2465' ]; + +const ATTACK_MARK = " *"; +const RESERVE_MARK = ""; + +let game = null; + +function log(...args) { + let s = Array.from(args).join(""); + game.log.push(s); +} + +function logp(...args) { + let s = Array.from(args).join(""); + game.log.push(game.active + " " + s); +} + +function log_move_start(from, to, mark = false) { + if (mark) + game.turn_buf = [ from, to + mark ]; + else + game.turn_buf = [ from, to ]; +} + +function log_move_continue(to, mark = false) { + if (mark) + game.turn_buf.push(to + mark); + else + game.turn_buf.push(to); +} + +function log_move_end() { + if (game.turn_buf) { + game.turn_log.push(game.turn_buf); + delete game.turn_buf; + } +} + +function log_levy(where) { + game.turn_log.push([where]); +} + +function print_turn_log(verb) { + function print_move(last) { + return "\n" + n + " " + last.join(" \u2192 "); + } + let text = game.active + " " + verb + ":"; + game.turn_log.sort(); + let last = game.turn_log[0]; + let n = 0; + for (let entry of game.turn_log) { + if (entry.toString() != last.toString()) { + text += print_move(last); + n = 0; + } + ++n; + last = entry; + } + if (n > 0) + text += print_move(last); + else + text += "\nnothing."; + log(text); + delete game.turn_log; +} + +function is_active_player(current) { + return (current == game.active) || (game.active == "All" && (current == CAESAR || current == POMPEIUS)); +} + +function is_inactive_player(current) { + return current == "Observer" || (game.active != current && game.active != "All"); +} + +function remove_from_array(array, item) { + let i = array.indexOf(item); + if (i >= 0) + array.splice(i, 1); +} + +function clear_undo() { + if (game.undo) + game.undo.length = 0; + else + game.undo = []; +} + +function push_undo() { + game.undo.push(JSON.stringify(game, (k,v) => { + if (k === 'undo') return undefined; + if (k === 'log') return v.length; + return v; + })); +} + +function pop_undo() { + let undo = game.undo; + let log = game.log; + Object.assign(game, JSON.parse(undo.pop())); + game.undo = undo; + log.length = game.log; + game.log = log; +} + +function gen_action_undo(view) { + if (!view.actions) + view.actions = {} + if (game.undo && game.undo.length > 0) + view.actions.undo = 1; + else + view.actions.undo = 0; +} + +function gen_action_pass(view, text) { + if (!view.actions) + view.actions = {} + view.actions['pass'] = text; +} + +function gen_action(view, action, argument) { + if (!view.actions) + view.actions = {} + if (argument != undefined) { + if (!(action in view.actions)) + view.actions[action] = [ argument ]; + else + view.actions[action].push(argument); + } else { + view.actions[action] = 1; + } +} + +function edge_id(A, B) { + if (A > B) + return B + "/" + A; + return A + "/" + B; +} + +function roll_d6() { + return Math.floor(Math.random() * 6) + 1; +} + +function reset_deck() { + let deck = []; + for (let c = 1; c <= 27; ++c) + deck.push(c); + return deck; +} + +function deal_cards(deck, n) { + let hand = []; + for (let i = 0; i < n; ++i) { + let c = Math.floor(Math.random() * deck.length); + hand.push(deck[c]); + deck.splice(c, 1); + } + return hand; +} + +function reset_road_limits() { + game.sea_moved = {}; + game.sea_retreated = false; + game.limits = {}; +} + +function road_limit(e) { + return game.limits[e]|0; +} + +function move_to(who, from, to) { + let e = edge_id(from, to); + game.location[who] = to; + game.last_from = from; + game.limits[e] = road_limit(e) + 1; + if (is_contested_space(to)) + game.last_used[e] = game.active; +} + +function block_owner(who) { + if (who in game.owner) + return game.owner[who]; + return BLOCKS[who].owner; +} + +function block_name(who) { + return BLOCKS[who].name; +} + +function block_type(who) { + return BLOCKS[who].type; +} + +function block_initiative(who) { + if (block_type(who) == 'ballista') + return is_defender(who) ? 'B' : 'D'; + return BLOCKS[who].initiative; +} + +function block_fire_power(who) { + return BLOCKS[who].firepower; +} + +function block_strength(who) { + if (block_type(who) == 'elephant') + return game.steps[who] * 2; + return game.steps[who]; +} + +function is_dead(b) { + return game.location[b] == DEAD; +} + +function eliminate_block(who) { + if (who == CLEOPATRA) { + let new_owner = enemy(game.owner[who]); + game.flash = "Cleopatra is captured."; + log("Cleopatra joins " + new_owner + "!"); + game.owner[who] = new_owner; + } else { + game.flash = block_name(who) + " is eliminated."; + log(block_name(who), " is eliminated."); + game.location[who] = DEAD; + game.steps[who] = BLOCKS[who].steps; + } +} + +function disband_block(who) { + game.owner[who] = BLOCKS[who].owner; + game.location[who] = LEVY; + game.steps[who] = BLOCKS[who].steps; +} + +function reduce_block(who) { + if (game.steps[who] == 1) { + eliminate_block(who); + } else { + game.steps[who]--; + } +} + +/* Game state queries */ + +function enemy(p) { + return p == CAESAR ? POMPEIUS : CAESAR; +} + +function enemy_player() { + return enemy(game.active); +} + +function count_friendly(where) { + let count = 0; + let p = game.active; + for (let b in BLOCKS) { + if (game.location[b] == where && block_owner(b) == p) + ++count; + } + return count; +} + +function count_enemy(where) { + let count = 0; + let p = enemy_player(); + for (let b in BLOCKS) { + if (game.location[b] == where && block_owner(b) == p) + ++count; + } + return count; +} + +function count_pinning(where) { + let count = 0; + if (game.active == game.p2) { + let p = enemy_player(); + for (let b in BLOCKS) { + if (game.location[b] == where && block_owner(b) == p) + if (!game.reserves.includes(b)) + ++count; + } + } + return count; +} + +function count_pinned(where) { + let count = 0; + for (let b in BLOCKS) { + if (game.location[b] == where && block_owner(b) == game.active) + if (!game.reserves.includes(b)) + ++count; + } + return count; +} + +function is_pinned(who) { + if (game.active == game.p2) { + let where = game.location[who]; + if (count_pinned(where) <= count_pinning(where)) + return true; + } + return false; +} + +function is_city(where) { + let t = SPACES[where].type; + return t == 'city' || t == 'major-port' || t == 'port'; +} + +function is_sea(where) { + return SPACES[where].type == 'sea'; +} + +function is_map_space(where) { + return is_city(where) || is_sea(where); +} + +function is_navis(b) { + return BLOCKS[b].type == 'navis'; +} + +function is_friendly_space(where) { return count_friendly(where) > 0 && count_enemy(where) == 0; } +function is_enemy_space(where) { return count_friendly(where) == 0 && count_enemy(where) > 0; } +function is_vacant_space(where) { return count_friendly(where) == 0 && count_enemy(where) == 0; } +function is_contested_space(where) { return count_friendly(where) > 0 && count_enemy(where) > 0; } + +function is_friendly_city(where) { return is_city(where) && is_friendly_space(where); } +function is_enemy_city(where) { return is_city(where) && is_enemy_space(where); } +function is_contested_city(where) { return is_city(where) && is_contested_space(where); } + +function is_friendly_sea(where) { return is_sea(where) && is_friendly_space(where); } +function is_vacant_sea(where) { return is_sea(where) && is_vacant_space(where); } +function is_contested_sea(where) { return is_sea(where) && is_contested_space(where); } + +function have_contested_spaces() { + for (let where in SPACES) + if (is_map_space(where) && is_contested_space(where)) + return true; + return false; +} + +function supply_limit(where) { + if (SPACES[where].type == 'sea') + return 0; + return 3 + SPACES[where].value; +} + +function is_over_supply_limit(where) { + let count = 0; + for (let b in BLOCKS) { + if (game.location[b] == where) + ++count; + } + return count > supply_limit(where); +} + +function count_vp() { + let old_active = game.active; + game.active = CAESAR; + game.c_vp = 0; + game.p_vp = 0; + game.active = CAESAR; + for (let s in SPACES) { + if (is_friendly_city(s)) + game.c_vp += SPACES[s].value; + if (is_enemy_city(s)) + game.p_vp += SPACES[s].value; + } + if (is_dead(POMPEIUS)) game.c_vp += 1; + if (is_dead(SCIPIO)) game.c_vp += 1; + if (is_dead(BRUTUS)) game.c_vp += 1; + if (is_dead(CAESAR)) game.p_vp += 1; + if (is_dead(ANTONIUS)) game.p_vp += 1; + if (is_dead(OCTAVIAN)) game.p_vp += 1; + game.active = old_active; +} + +/* Game ability queries */ + +function can_amphibious_move_to(b, from, to) { + let e = edge_id(from, to); + if (EDGES[e] == 'sea') { + if (is_city(to)) { + if (is_friendly_space(to) || is_vacant_space(to)) { + return true; + } + } else { + if (is_friendly_space(to)) { + return true; + } + } + } +} + +function can_amphibious_move(b) { + if (block_owner(b) == game.active && !game.moved[b]) { + if (BLOCKS[b].type == 'navis') + return false; + if (is_pinned(b)) + return false; + let from = game.location[b]; + for (let to of SPACES[from].exits) + if (can_amphibious_move_to(b, from, to)) + return true; + } + return false; +} + +function can_regroup_to(b, from, to) { + if (is_vacant_space(to) || is_friendly_space(to)) { + let e = edge_id(from, to); + let b_type = BLOCKS[b].type; + let e_type = EDGES[e]; + if (b_type == 'navis') + return e_type == 'sea'; + if (e_type == 'major') + return road_limit(e) < 4; + if (e_type == 'minor') + return road_limit(e) < 2; + if (e_type == 'strait') + return road_limit(e) < 2; + } + return false; +} + +function can_block_use_road(b, from, to) { + let e = edge_id(from, to); + let b_type = BLOCKS[b].type; + let e_type = EDGES[e]; + + if (b_type == 'navis') { + if (game.mars == game.active) + return false; + if (game.mercury == game.active) + return false; + if (game.pluto == game.active) + return false; + return e_type == 'sea'; + } else { + if (game.neptune == game.active) + return false; + } + + if (game.pluto == game.active) { + if (is_enemy_space(to) || is_contested_space(to)) { + if (e_type == 'major') + return road_limit(e) < 6; + if (e_type == 'minor') + return road_limit(e) < 3; + if (e_type == 'strait') + return road_limit(e) < 2; + } + } + + if (e_type == 'major') + return road_limit(e) < 4; + if (e_type == 'minor') + return road_limit(e) < 2; + if (e_type == 'strait') { + if (is_enemy_space(to) || is_contested_space(to)) + return road_limit(e) < 1; + else + return road_limit(e) < 2; + } + return false; +} + +function can_block_use_road_to_retreat(b, from, to) { + let e = edge_id(from, to); + let b_type = BLOCKS[b].type; + let e_type = EDGES[e]; + if (b_type == 'navis') + return e_type == 'sea'; + if (e_type == 'major') + return road_limit(e) < 4; + if (e_type == 'minor') + return road_limit(e) < 2; + if (e_type == 'strait') + return road_limit(e) < 1; + return false; +} + +function can_block_move_to(b, to) { + let from = game.location[b]; + if (can_block_use_road(b, from, to)) { + if (count_pinning(from) > 0) { + let e = edge_id(from, to); + if (game.last_used[e] == enemy_player()) + return false; + } + return true; + } + return false; +} + +function can_block_continue_to(b, to) { + let from = game.location[b]; + if (is_friendly_space(to) || is_vacant_space(to)) + if (can_block_use_road(b, from, to)) + return true; + return false; +} + +function can_block_move(b) { + if (block_owner(b) == game.active && !game.moved[b]) { + let from = game.location[b]; + if (is_pinned(b)) + return false; + if (game.sea_moved[from] && count_friendly(from) <= 1) + return false; + for (let to of SPACES[from].exits) { + if (can_block_move_to(b, to)) { + return true; + } + } + } + return false; +} + +function can_block_continue(b, last_from) { + let here = game.location[b]; + if (is_enemy_space(here) || is_contested_space(here)) + return false; + for (let to of SPACES[here].exits) { + if (to != last_from && can_block_continue_to(b, to)) + return true; + } + return false; +} + +function can_sea_retreat(who, from, to) { + if (game.sea_retreated) + return false; + if (BLOCKS[who].type == 'navis') + return false; + if (is_friendly_sea(to)) { + for (let next of SPACES[to].exits) + if (is_friendly_city(next)) + return true; + } + return false; +} + +function can_attacker_retreat_to(who, from, to) { + let e = edge_id(from, to); + if (can_sea_retreat(who, from, to)) + return true; + if (is_vacant_space(to)) + if (can_block_use_road_to_retreat(who, from, to)) + if (game.last_used[e] == game.active) + return true; + if (is_friendly_space(to)) + if (can_block_use_road_to_retreat(who, from, to)) + return true; + return false; +} + +function can_defender_retreat_to(who, from, to) { + let e = edge_id(from, to); + if (BLOCKS[who].type == 'navis') { + // Navis can only retreat to vacant seas, not ports! + if (is_vacant_sea(to)) { + if (can_block_use_road_to_retreat(who, from, to)) + if (game.last_used[e] != enemy_player()) + return true; + } + // Navis can retreat to any friendly sea or port, even ones used by the attacker! + if (is_friendly_space(to)) { + if (can_block_use_road_to_retreat(who, from, to)) + return true; + } + + } else { + if (can_sea_retreat(who, from, to)) + return true; + if (is_vacant_space(to) || is_friendly_space(to)) { + if (can_block_use_road_to_retreat(who, from, to)) + if (game.last_used[e] != enemy_player()) + return true; + } + } + return false; +} + +function can_attacker_retreat(who) { + let from = game.location[who]; + for (let to of SPACES[from].exits) + if (can_attacker_retreat_to(who, from, to)) + return true; +} + +function can_defender_retreat(who) { + let from = game.location[who]; + for (let to of SPACES[from].exits) + if (can_defender_retreat_to(who, from, to)) + return true; +} + +function can_regroup(who) { + let from = game.location[who]; + for (let to of SPACES[from].exits) + if (can_regroup_to(who, from, to)) + return true; + return false; +} + +function can_navis_move_to_port(who) { + let from = game.location[who]; + for (let to of SPACES[from].exits) { + if (is_friendly_city(to)) + return true; + } + return false; +} + +function can_levy_to(b, to) { + if (is_friendly_city(to)) { + if (BLOCKS[b].levy) + return BLOCKS[b].levy == to; + if (BLOCKS[b].type == 'navis') + return SPACES[to].type == 'major-port'; + if (b == OCTAVIAN) + return is_dead(CAESAR) || is_dead(ANTONIUS); + if (b == BRUTUS) + return is_dead(POMPEIUS) || is_dead(SCIPIO); + return true; + } + return false; +} + +function can_levy(b) { + let location = game.location[b]; + if (block_owner(b) != game.active) + return false; + if (location == LEVY) { + for (let to in SPACES) + if (can_levy_to(b, to)) + return true; + return false; + } + if (game.steps[b] == BLOCKS[b].steps) + return false; + return is_friendly_city(game.location[b]); +} + +// == --- == --- == --- == --- == --- == // + +let states = {}; +let events = {}; + +function start_year() { + log(""); + log("Start Year ", game.year, "."); + game.turn = 1; + let deck = reset_deck(); + game.c_hand = deal_cards(deck, 6); + game.p_hand = deal_cards(deck, 6); + game.prior_c_card = 0; + game.prior_p_card = 0; + game.c_card = 0; + game.p_card = 0; + game.c_discard = 0; + game.p_discard = 0; + game.active = "All"; + game.state = 'discard_and_play_card'; + game.show_cards = false; +} + +function resume_discard_and_play_card() { + if (game.c_card && game.p_card) + start_first_turn(); + else if (game.c_card) + game.active = POMPEIUS; + else if (game.p_card) + game.active = CAESAR; + else + game.active = "All"; +} + +states.discard_and_play_card = { + prompt: function (view, current) { + if (current == "Observer") + return view.prompt = "Waiting for players to discard one card and play one card."; + if (current == CAESAR) { + if (!game.c_discard) { + view.prompt = "Discard a card."; + for (let c of game.c_hand) + gen_action(view, 'card', c); + } else if (!game.c_card) { + view.prompt = "Play a card."; + for (let c of game.c_hand) + if (c != APOLLO) + gen_action(view, 'card', c); + gen_action(view, 'undo'); + } else { + view.prompt = "Waiting for Pompeius..."; + gen_action(view, 'undo'); + } + } + else if (current == POMPEIUS) { + if (!game.p_discard) { + view.prompt = "Discard a card."; + for (let c of game.p_hand) + gen_action(view, 'card', c); + } else if (!game.p_card) { + view.prompt = "Play a card."; + for (let c of game.p_hand) + if (c != APOLLO) + gen_action(view, 'card', c); + gen_action(view, 'undo'); + } else { + view.prompt = "Waiting for Caesar..."; + gen_action(view, 'undo'); + } + } + }, + card: function (c, current) { + if (current == CAESAR) { + if (!game.c_discard) + game.c_discard = c; + else + game.c_card = c; + remove_from_array(game.c_hand, c); + } + if (current == POMPEIUS) { + if (!game.p_discard) + game.p_discard = c; + else + game.p_card = c; + remove_from_array(game.p_hand, c); + } + resume_discard_and_play_card(); + }, + undo: function (_, current) { + if (current == CAESAR) { + if (game.c_discard) { + game.c_hand.push(game.c_discard); + game.c_discard = 0; + } + if (game.c_card) { + game.c_hand.push(game.c_card); + game.c_card = 0; + } + } + if (current == POMPEIUS) { + if (game.p_discard) { + game.p_hand.push(game.p_discard); + game.p_discard = 0; + } + if (game.p_card) { + game.p_hand.push(game.p_card); + game.p_card = 0; + } + } + resume_discard_and_play_card(); + } +} + +function start_first_turn() { + game.last_used = {}; + game.attacker = {}; + game.main_road = {}; + game.moved = {}; + game.reserves = []; + log(""); + log("Start Turn ", game.turn, " of Year ", game.year, "."); + reveal_cards(); +} + +function start_turn() { + game.last_used = {}; + game.attacker = {}; + game.main_road = {}; + game.moved = {}; + game.reserves = []; + game.c_card = 0; + game.p_card = 0; + game.active = "All"; + game.state = 'play_card'; + game.show_cards = false; + log(""); + log("Start Turn ", game.turn, " of Year ", game.year, "."); +} + +function resume_play_card() { + if (game.c_card && game.p_card) + reveal_cards(); + else if (game.c_card) + game.active = POMPEIUS; + else if (game.p_card) + game.active = CAESAR; + else + game.active = "All"; +} + +states.play_card = { + prompt: function (view, current) { + if (current == "Observer") + return view.prompt = "Waiting for players to play a card."; + if (current == CAESAR) { + view.prior_p_card = game.prior_p_card; + if (game.c_card) { + view.prompt = "Waiting for Pompeius to play a card."; + gen_action(view, 'undo'); + } else { + view.prompt = "Play a card."; + for (let c of game.c_hand) + gen_action(view, 'card', c); + } + } + if (current == POMPEIUS) { + view.prior_c_card = game.prior_c_card; + if (game.p_card) { + view.prompt = "Waiting for Caesar to play a card."; + gen_action(view, 'undo'); + } else { + view.prompt = "Play a card."; + for (let c of game.p_hand) + gen_action(view, 'card', c); + } + } + }, + card: function (card, current) { + if (current == CAESAR) { + remove_from_array(game.c_hand, card); + game.c_card = card; + } + if (current == POMPEIUS) { + remove_from_array(game.p_hand, card); + game.p_card = card; + } + resume_play_card(); + }, + undo: function (_, current) { + if (current == CAESAR) { + if (game.c_card) { + game.c_hand.push(game.c_card); + game.c_card = 0; + } + } + if (current == POMPEIUS) { + if (game.p_card) { + game.p_hand.push(game.p_card); + game.p_card = 0; + } + } + resume_play_card(); + } +} + +function reveal_cards() { + delete game.mars; + delete game.mercury; + delete game.neptune; + delete game.pluto; + + log("Caesar plays ", CARDS[game.c_card].name, "."); + log("Pompeius plays ", CARDS[game.p_card].name, "."); + + if (CARDS[game.c_card].event && CARDS[game.p_card].event) { + log("Events cancel each other."); + game.prior_c_card = game.c_card; + game.prior_p_card = game.p_card; + end_turn(); + return; + } + + if (game.c_card == APOLLO) + game.c_card = game.prior_p_card; + if (game.p_card == APOLLO) + game.p_card = game.prior_c_card; + + game.prior_c_card = game.c_card; + game.prior_p_card = game.p_card; + + // Tournament rule: Caesar always goes first on the first turn of the game. + if (game.year == 705 && game.turn == 1) { + game.p1 = CAESAR; + game.p2 = POMPEIUS; + } else if (CARDS[game.c_card].event) { + game.p1 = CAESAR; + game.p2 = POMPEIUS; + } else if (CARDS[game.p_card].event) { + game.p1 = POMPEIUS; + game.p2 = CAESAR; + } else if (CARDS[game.p_card].move > CARDS[game.c_card].move) { + game.p1 = POMPEIUS; + game.p2 = CAESAR; + } else { + game.p1 = CAESAR; + game.p2 = POMPEIUS; + } + + game.show_cards = true; + game.active = game.p1; + start_player_turn(); +} + +function start_player_turn() { + log(""); + log("Start ", game.active, " turn."); + reset_road_limits(); + game.activated = []; + + let card = (game.active == CAESAR ? CARDS[game.c_card] : CARDS[game.p_card]); + if (card.event) { + switch (card.event) { + // Apollo has already been handled in reveal_cards! + case 'Jupiter': + game.state = 'jupiter'; + break; + case 'Vulcan': + game.state = 'vulcan'; + break; + case 'Mercury': + game.mercury = game.active; + game.moves = 1; + game.levies = 0; + game.amphibious_available = false; + game.state = 'move_who'; + game.turn_log = []; + break; + case 'Pluto': + game.pluto = game.active; + game.moves = 1; + game.levies = 0; + game.amphibious_available = false; + game.state = 'move_who'; + game.turn_log = []; + break; + case 'Mars': + game.mars = game.active; + game.moves = 1; + game.levies = 0; + game.amphibious_available = false; + game.state = 'move_who'; + game.turn_log = []; + break; + case 'Neptune': + game.neptune = game.active; + game.moves = 1; + game.levies = 0; + game.amphibious_available = false; + game.state = 'move_who'; + game.turn_log = []; + break; + } + } else { + game.moves = card.move; + game.levies = card.levy; + game.amphibious_available = true; + game.state = 'move_who'; + game.turn_log = []; + } + + clear_undo(); +} + +function jupiter_block(b) { + let type = BLOCKS[b].type; + if (type == 'navis' || type == 'leader') { + log("Jupiter reduces ", block_name(b), "."); + reduce_block(b); + end_player_turn(); + } else { + game.owner[b] = game.active; + game.who = b; + game.state = 'jupiter_to'; + } +} + +states.jupiter = { + prompt: function (view, current) { + if (is_inactive_player(current)) + return view.prompt = "Waiting for " + game.active + "..."; + view.prompt = "Jupiter: Choose one enemy army adjacent to a friendly city."; + for (let s in SPACES) { + if (is_friendly_city(s)) { + for (let to of SPACES[s].exits) + if (is_enemy_city(to) || is_contested_city(to)) + gen_action(view, 'secret', to); + } + } + gen_action_pass(view, "Pass"); + }, + space: function (where) { + /* pick a random block */ + let list = []; + for (let x in BLOCKS) + if (game.location[x] == where) + list.push(x); + let i = Math.floor(Math.random() * list.length); + jupiter_block(list[i]); + }, + secret: function (args) { + let [where, owner] = args; + /* pick a random block of the same color as the selected block */ + if (owner == CLEOPATRA) { + jupiter_block(CLEOPATRA); + } else { + let list = []; + for (let b in BLOCKS) + if (game.location[b] == where && BLOCKS[b].owner == owner) + list.push(b); + let i = Math.floor(Math.random() * list.length); + jupiter_block(list[i]); + } + }, + pass: function () { + end_player_turn(); + }, +} + +states.jupiter_to = { + prompt: function (view, current) { + if (is_inactive_player(current)) + return view.prompt = "Waiting for " + game.active + "..."; + view.prompt = "Jupiter: Move " + block_name(game.who) + " to your city."; + let from = game.location[game.who]; + for (let to of SPACES[from].exits) + if (is_friendly_city(to)) + gen_action(view, 'space', to); + }, + space: function (to) { + log(block_name(game.who) + " joins " + game.active + ":\n" + + game.location[game.who] + " \u2192 " + to + "."); + game.location[game.who] = to; + game.who = null; + end_player_turn(); + }, +} + +states.vulcan = { + prompt: function (view, current) { + if (is_inactive_player(current)) + return view.prompt = "Waiting for " + game.active + "..."; + view.prompt = "Vulcan: Choose an enemy city to suffer a volcanic eruption."; + for (let s in SPACES) + if (is_enemy_city(s)) + gen_action(view, 'space', s); + }, + space: function (city) { + log("Vulcan strikes " + city + "!"); + for (let b in BLOCKS) { + if (game.location[b] == city) { + reduce_block(b); + } + } + // uh-oh! cleopatra switched sides! + if (is_contested_city(city)) { + game.attacker[city] = game.active; + } + end_player_turn(); + }, +} + +function is_amphibious_move(who, from, to) { + if (BLOCKS[who].type == 'navis') + return false; + let e = edge_id(from, to); + if (EDGES[e] == 'sea') + return true; + return false; +} + +function move_or_attack(to) { + let from = game.location[game.who]; + move_to(game.who, from, to); + if (is_enemy_space(to) || is_contested_space(to)) { + if (!game.attacker[to]) { + game.attacker[to] = game.active; + game.main_road[to] = from; + return ATTACK_MARK; + } else { + if (game.attacker[to] != game.active || game.main_road[to] != from) { + game.reserves.push(game.who); + return RESERVE_MARK; + } + return ATTACK_MARK; + } + } + return false; // not a combat move +} + +states.move_who = { + prompt: function (view, current) { + if (is_inactive_player(current)) + return view.prompt = "Waiting for " + game.active + " to move..."; + if (game.pluto == game.active) + view.prompt = "Pluto: Move one group. Road limits increase for attacks. No Navis movement."; + else if (game.mars == game.active) + view.prompt = "Mars: Move one group. No Navis movement."; + else if (game.neptune == game.active) + view.prompt = "Neptune: Move only the Navis in one group."; + else if (game.mercury == game.active) + view.prompt = "Mercury: Move one group three cities, or two cities and attack. No Navis movement."; + else if (game.amphibious_available) + view.prompt = "Choose an army to group or amphibious move. "+game.moves+"MP left."; + else + view.prompt = "Choose an army to group move. "+game.moves+"MP left."; + if (game.moves == 0) { + for (let b in BLOCKS) { + let from = game.location[b]; + if (game.activated.includes(from)) + if (can_block_move(b)) + gen_action(view, 'block', b); + } + } else { + let have_amphibious = false; + for (let b in BLOCKS) { + let can_move = false; + if (game.amphibious_available && can_amphibious_move(b)) { + can_move = true; + have_amphibious = true; + } + if (can_block_move(b)) + can_move = true; + if (can_move) + gen_action(view, 'block', b); + } + if (!have_amphibious) + game.amphibious_available = false; + } + gen_action_pass(view, "End movement phase"); + gen_action_undo(view); + }, + block: function (who) { + push_undo(); + game.who = who; + if (game.mercury == game.active) + game.state = 'mercury_move_1'; + else + game.state = 'move_where'; + }, + pass: function () { + end_movement(); + }, + undo: pop_undo, +} + +states.move_where = { + prompt: function (view, current) { + if (is_inactive_player(current)) + return view.prompt = "Waiting for " + game.active + " to move..."; + view.prompt = "Move " + block_name(game.who) + "."; + let from = game.location[game.who]; + for (let to of SPACES[from].exits) { + let can_move_to = false; + if (game.amphibious_available && can_amphibious_move_to(game.who, from, to)) + can_move_to = true; + if (can_block_move_to(game.who, to)) + can_move_to = true; + if (can_move_to) + gen_action(view, 'space', to); + } + gen_action(view, 'block', game.who); // for canceling move + gen_action_undo(view); + }, + space: function (to) { + let from = game.location[game.who]; + if (is_amphibious_move(game.who, from, to)) { + game.moves --; + game.location[game.who] = to; + game.last_from = from; + log_move_start(from, to); + logp("amphibious moves."); + if (is_sea(to)) { + game.sea_moved[to] = true; + game.state = 'amphibious_move_to'; + } else { + game.moved[game.who] = true; + game.who = null; + game.state = 'move_who'; + log_move_end(); + } + } else { + if (!game.activated.includes(from)) { + logp("activates " + from + "."); + game.moves --; + game.activated.push(from); + } + game.amphibious_available = false; + let mark = move_or_attack(to); + log_move_start(from, to, mark); + if (can_block_continue(game.who, game.last_from)) { + game.state = 'move_where_2'; + } else { + game.moved[game.who] = true; + game.who = null; + game.state = 'move_who'; + log_move_end(); + } + } + }, + block: pop_undo, + undo: pop_undo, +} + +states.move_where_2 = { + prompt: function (view, current) { + if (is_inactive_player(current)) + return view.prompt = "Waiting for " + game.active + " to move..."; + view.prompt = "Move " + block_name(game.who) + "."; + let from = game.location[game.who]; + for (let to of SPACES[from].exits) + if (to != game.last_from && can_block_continue_to(game.who, to)) + gen_action(view, 'space', to); + gen_action(view, 'space', from); // For ending move early. + gen_action_undo(view); + }, + space: function (to) { + let from = game.location[game.who]; + if (to != from) { + log_move_continue(to); + move_to(game.who, from, to); + } + game.moved[game.who] = true; + game.who = null; + game.state = 'move_who'; + log_move_end(); + }, + undo: pop_undo, +} + +states.amphibious_move_to = { + prompt: function (view, current) { + if (is_inactive_player(current)) + return view.prompt = "Waiting for " + game.active + " to move..."; + view.prompt = "Move " + block_name(game.who) + " to a friendly sea or port."; + let from = game.location[game.who]; + for (let to of SPACES[from].exits) + if (to != game.last_from && can_amphibious_move_to(game.who, from, to)) + gen_action(view, 'space', to); + gen_action_undo(view); + }, + space: function (to) { + let from = game.location[game.who]; + game.last_from = from; + game.location[game.who] = to; + log_move_continue(to); + if (is_sea(to)) { + game.sea_moved[to] = true; + game.state = 'amphibious_move_to'; + } else { + game.moved[game.who] = true; + game.who = null; + game.state = 'move_who'; + log_move_end(); + } + }, + undo: pop_undo, +} + +states.mercury_move_1 = { + prompt: function (view, current) { + if (is_inactive_player(current)) + return view.prompt = "Waiting for " + game.active + " to move..."; + view.prompt = "Mercury: Move " + block_name(game.who) + "."; + let from = game.location[game.who]; + for (let to of SPACES[from].exits) + if (can_block_move_to(game.who, to)) + gen_action(view, 'space', to); + gen_action_undo(view); + }, + space: function (to) { + let from = game.location[game.who]; + if (!game.activated.includes(from)) { + logp("activates " + from + "."); + game.moves --; + game.activated.push(from); + } + let mark = move_or_attack(to); + log_move_start(from, to, mark); + if (!is_contested_space(to) && can_block_move(game.who)) { + game.state = 'mercury_move_2'; + } else { + game.who = null; + game.state = 'move_who'; + log_move_end(); + } + }, + undo: pop_undo, +} + +states.mercury_move_2 = { + prompt: function (view, current) { + if (is_inactive_player(current)) + return view.prompt = "Waiting for " + game.active + " to move..."; + view.prompt = "Mercury: Move " + block_name(game.who) + "."; + let from = game.location[game.who]; + for (let to of SPACES[from].exits) + if (to != game.last_from && can_block_move_to(game.who, to)) + gen_action(view, 'space', to); + gen_action(view, 'space', from); // For ending move early. + gen_action_undo(view); + }, + space: function (to) { + let from = game.location[game.who]; + if (to != from) { + let mark = move_or_attack(to); + log_move_continue(to, mark); + if (can_block_continue(game.who, game.last_from)) { + game.state = 'mercury_move_3'; + } else { + game.moved[game.who] = true; + game.who = null; + game.state = 'move_who'; + log_move_end(); + } + } else { + game.moved[game.who] = true; + game.who = null; + game.state = 'move_who'; + log_move_end(); + } + }, + undo: pop_undo, +} + +states.mercury_move_3 = { + prompt: function (view, current) { + if (is_inactive_player(current)) + return view.prompt = "Waiting for " + game.active + " to move..."; + view.prompt = "Mercury: Move " + block_name(game.who) + "."; + let from = game.location[game.who]; + for (let to of SPACES[from].exits) + if (to != game.last_from && can_block_continue_to(game.who, to)) + gen_action(view, 'space', to); + gen_action(view, 'space', from); // For ending move early. + gen_action_undo(view); + }, + space: function (to) { + let from = game.location[game.who]; + if (to != from) { + log_move_continue(to); + move_to(game.who, from, to); + } + game.moved[game.who] = true; + game.who = null; + game.state = 'move_who'; + log_move_end(); + }, + undo: pop_undo, +} + +function end_movement() { + print_turn_log("moves"); + + if (game.pluto == game.active || + game.mars == game.active || + game.neptune == game.active || + game.mercury == game.active) + return end_player_turn(); + + game.moves = 0; + game.state = 'levy'; + game.turn_log = []; + + clear_undo(); +} + +states.levy = { + prompt: function (view, current) { + if (is_inactive_player(current)) + return view.prompt = "Waiting for " + game.active + " to levy..."; + view.prompt = "Choose an army to levy. "+game.levies+"LP left."; + if (game.levies > 0) { + for (let b in BLOCKS) { + if (can_levy(b)) + gen_action(view, 'block', b); + } + } + gen_action_pass(view, "End levy phase"); + gen_action_undo(view); + }, + block: function (who) { + push_undo(); + if (game.location[who] == LEVY) { + if (BLOCKS[who].levy) { + let to = BLOCKS[who].levy; + log_levy(to); + game.levies --; + game.steps[who] = 1; + game.location[who] = to; + } else { + game.who = who; + game.state = 'levy_where'; + } + } else { + log_levy(game.location[who]); + game.levies --; + game.steps[who]++; + game.state = 'levy'; + } + }, + pass: function () { + end_player_turn(); + }, + undo: pop_undo, +} + +states.levy_where = { + prompt: function (view, current) { + if (is_inactive_player(current)) + return view.prompt = "Waiting for " + game.active + " to levy..."; + view.prompt = "Choose a friendly city to levy " + block_name(game.who) + " in."; + for (let to in SPACES) + if (can_levy_to(game.who, to)) + gen_action(view, 'space', to); + gen_action(view, 'block', game.who); // for canceling levy + gen_action_undo(view); + }, + space: function (to) { + log_levy(to); + game.levies --; + game.steps[game.who] = 1; + game.location[game.who] = to; + game.who = null; + game.state = 'levy'; + }, + block: pop_undo, + undo: pop_undo, +} + +function end_player_turn() { + clear_undo(); + + if (game.turn_log) + print_turn_log("levies"); + + if (game.active == game.p1) { + game.active = game.p2; + start_player_turn(); + } else { + goto_pick_battle(); + } +} + +function goto_pick_battle() { + game.active = game.p1; + if (have_contested_spaces()) + game.state = 'pick_battle'; + else + end_turn(); +} + +states.pick_battle = { + prompt: function (view, current) { + if (is_inactive_player(current)) + return view.prompt = "Waiting for " + game.active + " to pick a battle..."; + view.prompt = "Choose the next battle to fight!"; + for (let s in SPACES) + if (is_contested_city(s) || is_contested_sea(s)) + gen_action(view, 'space', s); + }, + space: function (where) { + game.where = where; + if (game.mars == game.attacker[where]) { + game.state = 'use_battle_event'; + } else if (game.neptune == game.attacker[where]) { + game.state = 'use_battle_event'; + } else { + start_battle(false); + } + }, +} + +states.use_battle_event = { + prompt: function (view, current) { + if (is_inactive_player(current)) + return view.prompt = "Waiting for " + game.active + " to pick a battle..."; + if (game.mars) + view.prompt = "Do you want to use the surprise attack granted by Mars?"; + else + view.prompt = "Do you want to use the surprise attack granted by Neptune?"; + gen_action(view, 'surprise'); + gen_action_pass(view, "No"); + }, + surprise: function () { + delete game.mars; /* Used up the event! */ + delete game.neptune; /* Used up the event! */ + start_battle(true); + }, + pass: function () { + start_battle(false); + } +} + +function is_attacker(b) { + if (game.location[b] == game.where && block_owner(b) == game.attacker[game.where]) + return !game.reserves.includes(b); + return false; +} + +function is_defender(b) { + if (game.location[b] == game.where && block_owner(b) != game.attacker[game.where]) + return !game.reserves.includes(b); + return false; +} + +function count_attackers() { + let count = 0; + for (let b in BLOCKS) { + if (is_attacker(b)) + ++count; + } + return count; +} + +function count_defenders() { + let count = 0; + for (let b in BLOCKS) { + if (is_defender(b)) + ++count; + } + return count; +} + +function start_battle(surprise) { + game.surprise = surprise; + game.battle_round = 0; + game.flash = ""; + log(""); + if (game.surprise) + log("Surprise attack in ", game.where, "."); + else + log("Battle in ", game.where, "."); + game.state = 'battle_round'; + start_battle_round(); +} + +function resume_battle() { + game.who = null; + game.state = 'battle_round'; + pump_battle_round(); +} + +function end_battle() { + game.flash = ""; + game.battle_round = 0; + reset_road_limits(); + game.moved = {}; + goto_regroup(); +} + +function disrupt_attacking_reserves() { + for (let b in BLOCKS) + if (game.location[b] == game.where && block_owner(b) == game.attacker[game.where]) + if (game.reserves.includes(b)) + reduce_block(b); +} + +function bring_on_reserves() { + for (let b in BLOCKS) { + if (game.location[b] == game.where) { + remove_from_array(game.reserves, b); + } + } +} + +function start_battle_round() { + if (++game.battle_round <= 4) { + log("~ Battle round " + game.battle_round + " ~"); + + reset_road_limits(); + game.moved = {}; + + if (game.battle_round == 2) { + game.surprise = false; + if (count_defenders() == 0) { + log("Defending main force was eliminated. The attacker is now the defender."); + game.attacker[game.where] = enemy(game.attacker[game.where]); + disrupt_attacking_reserves(); + } else if (count_attackers() == 0) { + log("Attacking main force was eliminated."); + disrupt_attacking_reserves(); + } + bring_on_reserves(); + } + + pump_battle_round(); + } else { + end_battle(); + } +} + +function pump_battle_round() { + function filter_battle_blocks(ci, is_candidate) { + let output = null; + for (let b in BLOCKS) { + if (is_candidate(b) && !game.moved[b]) { + if (block_initiative(b) == ci) { + if (!output) + output = []; + output.push(b); + } + } + } + return output; + } + + function battle_step(active, initiative, candidate) { + game.battle_list = filter_battle_blocks(initiative, candidate); + if (game.battle_list) { + game.active = active; + return true; + } + return false; + } + + if (is_friendly_space(game.where) || is_enemy_space(game.where)) { + end_battle(); + } else if (count_attackers() == 0 || count_defenders() == 0) { + start_battle_round(); + } else { + let attacker = game.attacker[game.where]; + let defender = enemy(attacker); + + if (game.surprise) { + if (battle_step(attacker, 'A', is_attacker)) return; + if (battle_step(attacker, 'B', is_attacker)) return; + if (battle_step(attacker, 'C', is_attacker)) return; + if (battle_step(attacker, 'D', is_attacker)) return; + if (battle_step(defender, 'A', is_defender)) return; + if (battle_step(defender, 'B', is_defender)) return; + if (battle_step(defender, 'C', is_defender)) return; + if (battle_step(defender, 'D', is_defender)) return; + } else { + if (battle_step(defender, 'A', is_defender)) return; + if (battle_step(attacker, 'A', is_attacker)) return; + if (battle_step(defender, 'B', is_defender)) return; + if (battle_step(attacker, 'B', is_attacker)) return; + if (battle_step(defender, 'C', is_defender)) return; + if (battle_step(attacker, 'C', is_attacker)) return; + if (battle_step(defender, 'D', is_defender)) return; + if (battle_step(attacker, 'D', is_attacker)) return; + } + + start_battle_round(); + } +} + +function end_battle() { + game.flash = ""; + game.battle_round = 0; + reset_road_limits(); + game.moved = {}; + goto_regroup(); +} + +function can_fire_with_block(b) { + if (is_attacker(b)) + return game.battle_round < 4; + if (is_defender(b)) + return true; + return false; +} + +function fire_with_block(b) { + game.moved[b] = true; + game.hits = 0; + let strength = block_strength(b); + let fire = block_fire_power(b, game.where); + let rolls = []; + for (let i = 0; i < strength; ++i) { + let die = roll_d6(); + if (die <= fire) { + rolls.push(DIE_HIT[die]); + ++game.hits; + } else { + rolls.push(DIE_MISS[die]); + } + } + game.flash = block_name(b) + " " + BLOCKS[b].initiative + BLOCKS[b].firepower; + game.flash += "\nfires " + rolls.join(" ") + "\n"; + if (game.hits == 0) + game.flash += "and misses."; + else if (game.hits == 1) + game.flash += "and scores 1 hit."; + else + game.flash += "and scores " + game.hits + " hits."; + log(game.flash); + if (game.hits > 0) { + game.active = enemy(game.active); + goto_battle_hits(); + } else { + resume_battle(); + } +} + +function can_retreat_with_block(who) { + if (game.location[who] == game.where) { + if (game.battle_round > 1) { + if (block_owner(who) == game.attacker[game.where]) + return can_attacker_retreat(who); + else + return can_defender_retreat(who); + } + } + return false; +} + +function must_retreat_with_block(who) { + if (game.location[who] == game.where) + if (game.battle_round == 4) + return (block_owner(who) == game.attacker[where]); + return false; +} + +function retreat_with_block(who) { + if (can_retreat_with_block(who)) { + game.who = who; + game.state = 'retreat'; + } else { + eliminate_block(who); + resume_battle(); + } +} + +function pass_with_block(who) { + game.flash = block_name(who) + " passes."; + log(block_name(who) + " passes."); + game.moved[who] = true; + resume_battle(); +} + +states.battle_round = { + show_battle: true, + prompt: function (view, current) { + if (is_inactive_player(current)) + return view.prompt = "Waiting for " + game.active + " to choose a combat action..."; + let can_fire = false; + let can_retreat = false; + let must_retreat = false; + let can_pass = false; + if (game.active == game.attacker[game.where]) { + if (game.battle_round < 4) can_fire = true; + if (game.battle_round > 1) can_retreat = true; + if (game.battle_round < 4) can_pass = true; + if (game.battle_round == 4) must_retreat = true; + } else { + can_fire = true; + if (game.battle_round > 1) can_retreat = true; + can_pass = true; + } + if (can_fire && can_retreat) + view.prompt = "Fire, retreat, or pass with an army."; + else if (can_fire) + view.prompt = "Fire or pass with an army."; + else + view.prompt = "Retreat with an army."; + for (let b of game.battle_list) { + if (can_fire) gen_action(view, 'battle_fire', b); + if (must_retreat || (can_retreat && can_retreat_with_block(b))) + gen_action(view, 'battle_retreat', b); + if (can_pass) gen_action(view, 'battle_pass', b); + gen_action(view, 'block', b); + } + }, + block: function (who) { + if (can_fire_with_block(who)) + fire_with_block(who); + else if (can_retreat_with_block(who)) + retreat_with_block(who); + else if (must_retreat_with_block(who)) + retreat_with_block(who); + else + pass_with_block(who); + }, + battle_fire: function (who) { + fire_with_block(who); + }, + battle_retreat: function (who) { + retreat_with_block(who); + }, + battle_pass: function (who) { + pass_with_block(who); + }, +} + +function goto_battle_hits() { + game.battle_list = list_victims(game.active); + if (game.battle_list.length == 0) + resume_battle(); + else + game.state = 'battle_hits'; +} + +function list_victims(p) { + let is_candidate = (p == game.attacker[game.where]) ? is_attacker : is_defender; + let max = 0; + for (let b in BLOCKS) + if (is_candidate(b) && block_strength(b) > max) + max = block_strength(b); + let list = []; + for (let b in BLOCKS) + if (is_candidate(b) && block_strength(b) == max) + list.push(b); + return list; +} + +function apply_hit(who) { + game.flash = block_name(who) + " takes a hit."; + reduce_block(who, 'combat'); + game.hits--; + if (game.hits == 0) + resume_battle(); + else { + game.battle_list = list_victims(game.active); + if (game.battle_list.length == 0) + resume_battle(); + else + game.flash += " " + game.hits + (game.hits == 1 ? " hit left." : " hits left."); + } +} + +states.battle_hits = { + show_battle: true, + prompt: function (view, current) { + if (is_inactive_player(current)) + return view.prompt = "Waiting for " + game.active + " to apply hits..."; + view.prompt = "Assign " + game.hits + (game.hits != 1 ? " hits" : " hit") + " to your armies."; + for (let b of game.battle_list) { + gen_action(view, 'battle_hit', b); + gen_action(view, 'block', b); + } + }, + block: function (who) { + apply_hit(who); + }, + battle_hit: function (who) { + apply_hit(who); + }, +} + +states.retreat = { + show_battle: false, + prompt: function (view, current) { + if (is_inactive_player(current)) + return view.prompt = "Waiting for " + game.active + " to retreat..."; + view.prompt = "Retreat " + block_name(game.who) + "."; + let from = game.location[game.who]; + for (let to of SPACES[from].exits) { + if (block_owner(game.who) == game.attacker[from]) { + if (can_attacker_retreat_to(game.who, from, to)) + gen_action(view, 'space', to); + } else { + if (can_defender_retreat_to(game.who, from, to)) + gen_action(view, 'space', to); + } + } + gen_action(view, 'undo'); + }, + space: function (to) { + let from = game.location[game.who]; + if (is_sea(to) && !is_navis(game.who)) { + move_to(game.who, from, to); + game.sea_retreated = true; + game.state = 'sea_retreat'; + } else { + log(block_name(game.who), " retreats to ", to + "."); + move_to(game.who, from, to); + game.flash = block_name(game.who) + " retreats to " + to + "."; + game.moved[game.who] = true; + resume_battle(); + } + }, + pass: function () { + eliminate_block(game.who); + resume_battle(); + }, + undo: function () { + resume_battle(); + }, +} + +states.sea_retreat = { + show_battle: false, + prompt: function (view, current) { + if (is_inactive_player(current)) + return view.prompt = "Waiting for " + game.active + " to retreat..."; + view.prompt = "Retreat " + block_name(game.who) + " to a friendly port."; + let from = game.location[game.who]; + for (let to of SPACES[from].exits) { + if (is_friendly_city(to)) + gen_action(view, 'space', to); + } + // TODO: check if there are any valid destinations before going to this state + gen_action_pass(view, "Eliminate army"); + }, + space: function (to) { + let from = game.location[game.who]; + log(block_name(game.who), " sea retreats to ", to + "."); + game.flash = block_name(game.who) + " sea retreats to " + to + "."; + move_to(game.who, from, to); + game.moved[game.who] = true; + resume_battle(); + }, + pass: function () { + eliminate_block(game.who); + resume_battle(); + } +} + +function goto_regroup() { + game.active = game.attacker[game.where]; + if (is_enemy_space(game.where)) + game.active = enemy(game.active); + log("~ " + game.active + " wins the battle ~"); + game.state = 'regroup'; + game.turn_log = []; + clear_undo(); +} + +states.regroup = { + prompt: function (view, current) { + if (is_inactive_player(current)) + return view.prompt = "Waiting for " + game.active + " to regroup..."; + view.prompt = "Regroup: Choose an army to move."; + for (let b in BLOCKS) { + if (game.location[b] == game.where) { + if (can_regroup(b)) + gen_action(view, 'block', b); + } + } + gen_action_pass(view, "End regroup"); + gen_action_undo(view); + }, + block: function (who) { + push_undo(); + game.who = who; + game.state = 'regroup_to'; + }, + pass: function () { + print_turn_log("regroups"); + clear_undo(); + game.where = null; + goto_pick_battle(); + }, + undo: pop_undo, +} + +states.regroup_to = { + prompt: function (view, current) { + if (is_inactive_player(current)) + return view.prompt = "Waiting for " + game.active + " to regroup..."; + view.prompt = "Regroup: Move " + block_name(game.who) + " to a friendly or vacant location."; + let from = game.location[game.who]; + for (let to of SPACES[from].exits) { + if (can_regroup_to(game.who, from, to)) + gen_action(view, 'space', to); + } + gen_action(view, 'block', game.who); // for canceling move + gen_action_undo(view); + }, + space: function (to) { + let from = game.location[game.who]; + game.turn_log.push([from, to]); + move_to(game.who, from, to); + game.who = null; + game.state = 'regroup'; + }, + block: pop_undo, + undo: pop_undo, +} + +function end_turn() { + game.moved = {}; + if (game.turn == 5) { + cleopatra_goes_home(); + check_victory(); + } else { + game.turn ++; + start_turn(); + } +} + +function cleopatra_goes_home() { + game.active = CAESAR; + if (game.location[CLEOPATRA] != ALEXANDRIA) + log("Cleopatra goes home to Alexandria."); + if (is_friendly_space(ALEXANDRIA)) + game.owner[CLEOPATRA] = CAESAR; + else + game.owner[CLEOPATRA] = POMPEIUS; + game.location[CLEOPATRA] = ALEXANDRIA; +} + +function check_victory() { + count_vp(); + if (game.c_vp >= 10) { + game.result = CAESAR; + game.active = null; + game.state = 'game_over'; + game.victory = "Caesar wins an early victory."; + log(""); + log(game.victory); + } else if (game.p_vp >= 10) { + game.victory = "Pompeius wins an early victory."; + game.result = POMPEIUS; + game.active = null; + game.state = 'game_over'; + log(""); + log(game.victory); + } else { + if (game.year == 709) { + end_game(); + } else { + log(""); + log("Start Winter Turn of Year " + game.year); + start_navis_to_port(); + } + } +} + +function count_navis_to_port() { + let count = 0; + for (let b in BLOCKS) { + if (block_owner(b) == game.active && BLOCKS[b].type == 'navis') + if (SPACES[game.location[b]].type == 'sea') + if (can_navis_move_to_port(b)) + ++count; + } + return count; +} + +function start_navis_to_port() { + game.active = CAESAR; + let count = count_navis_to_port(); + if (count > 0) { + game.state = 'navis_to_port'; + game.turn_log = []; + clear_undo(); + } else { + next_navis_to_port(); + } +} + +function next_navis_to_port() { + if (game.active == CAESAR) { + game.active = POMPEIUS; + let count = count_navis_to_port(); + if (count > 0) { + game.state = 'navis_to_port'; + game.turn_log = []; + clear_undo(); + return; + } + } + clear_undo(); + winter_supply(); +} + +states.navis_to_port = { + prompt: function (view, current) { + if (is_inactive_player(current)) + return view.prompt = "Waiting for " + game.active + " to move navis to port..."; + view.prompt = "Move all Navis to a friendly port on the same sea."; + for (let b in BLOCKS) { + if (block_owner(b) == game.active && BLOCKS[b].type == 'navis') + if (SPACES[game.location[b]].type == 'sea') + + if (can_navis_move_to_port(b)) + gen_action(view, 'block', b); + } + gen_action_pass(view, "End navis to port"); + gen_action_undo(view); + }, + block: function (who) { + push_undo(); + game.who = who; + game.state = 'navis_to_port_where'; + }, + pass: function () { + print_turn_log("moves to port"); + next_navis_to_port(); + }, + undo: pop_undo, +} + +states.navis_to_port_where = { + prompt: function (view, current) { + if (is_inactive_player(current)) + return view.prompt = "Waiting for " + game.active + " to move navis to port..."; + view.prompt = "Move " + block_name(game.who) + " to a friendly port."; + let from = game.location[game.who]; + for (let to of SPACES[from].exits) { + if (is_friendly_city(to)) + gen_action(view, 'space', to); + } + gen_action(view, 'block', game.who); // for canceling move + gen_action_undo(view); + }, + space: function (to) { + let from = game.location[game.who]; + game.turn_log.push([from, to]); + game.location[game.who] = to; + game.who = null; + game.state = 'navis_to_port'; + }, + block: pop_undo, + undo: pop_undo, +} + +function winter_supply() { + game.active = CAESAR; + game.state = 'disband'; + game.turn_log = []; + clear_undo(); +} + +states.disband = { + prompt: function (view, current) { + if (is_inactive_player(current)) + return view.prompt = "Waiting for " + game.active + " to disband..."; + let okay_to_end = true; + for (let b in BLOCKS) { + if (block_owner(b) == game.active && is_map_space(game.location[b]) && b != CLEOPATRA) { + if (is_over_supply_limit(game.location[b])) { + okay_to_end = false; + gen_action(view, 'block', b); + } + } + } + + if (!okay_to_end) { + view.prompt = "Disband armies in excess of supply."; + } else { + view.prompt = "You may disband armies to your levy pool."; + for (let b in BLOCKS) { + if (is_map_space(game.location[b])) + if (block_owner(b) == game.active && b != CLEOPATRA) + gen_action(view, 'block', b); + } + gen_action_pass(view, "End disbanding"); + + } + gen_action_undo(view); + }, + block: function (who) { + push_undo(); + game.turn_log.push([game.location[who]]); + disband_block(who); + }, + pass: function () { + print_turn_log("disbands"); + if (game.active == CAESAR) { + game.turn_log = []; + game.active = POMPEIUS; + clear_undo(); + } else { + clear_undo(); + end_year(); + } + }, + undo: pop_undo, +} + +function end_year() { + game.year ++; + for (let b in BLOCKS) { + if (game.location[b] == DEAD && BLOCKS[b].type != 'leader') { + disband_block(b); + } + } + start_year(); +} + +function end_game() { + count_vp(); + if (game.c_vp > game.p_vp) { + game.result = CAESAR; + } else if (game.c_vp < game.p_vp) { + game.result = POMPEIUS; + } else { + game.active = CAESAR; + if (is_friendly_space(ROMA)) + game.result = CAESAR; + else if (is_enemy_space(ROMA)) + game.result = POMPEIUS; + else + game.result = null; + } + if (game.result == CAESAR) + game.victory = "Caesar wins!"; + else if (game.result == POMPEIUS) + game.victory = "Pompeius wins!"; + else + game.victory = "The game is a draw."; + game.active = null; + game.state = 'game_over'; + log(""); + log(game.victory); +} + +states.game_over = { + prompt: function (view, current) { + return view.prompt = game.victory; + }, +} + +exports.setup = function (scenario, players) { + if (players.length != 2) + throw new Error("Invalid player count: " + players.length); + game = { + state: null, + show_cards: false, + year: 705, + location: {}, + steps: {}, + owner: {}, + moved: {}, + limits: {}, + last_used: {}, + sea_moved: {}, + attacker: {}, + main_road: {}, + reserves: [], + log: [], + }; + setup_historical_deployment(); + start_year(); + return game; +} + +function deploy_block(owner, location, name) { + for (let b in BLOCKS) { + if (BLOCKS[b].owner == owner && BLOCKS[b].name == name) { + game.steps[b] = BLOCKS[b].steps; + game.location[b] = location; + return; + } + } +} + +function setup_historical_deployment() { + for (let b in BLOCKS) { + game.location[b] = LEVY; + game.steps[b] = BLOCKS[b].steps; + } + + deploy_block("Caesar", "Ravenna", "Caesar"); + deploy_block("Caesar", "Ravenna", "Legio 13"); + deploy_block("Caesar", "Ravenna", "Navis 2"); + deploy_block("Caesar", "Genua", "Antonius"); + deploy_block("Caesar", "Genua", "Legio 8"); + deploy_block("Caesar", "Genua", "Legio 12"); + deploy_block("Caesar", "Massilia", "Legio 11"); + deploy_block("Caesar", "Massilia", "Legio 14"); + deploy_block("Caesar", "Massilia", "Navis 1"); + deploy_block("Caesar", "Narbo", "Legio 7"); + deploy_block("Caesar", "Narbo", "Legio 9"); + deploy_block("Caesar", "Narbo", "Legio 10"); + deploy_block("Caesar", "Lugdunum", "Legio 16"); + deploy_block("Caesar", "Lugdunum", "Equitatus 1"); + + deploy_block("Pompeius", "Neapolis", "Pompeius"); + deploy_block("Pompeius", "Neapolis", "Legio 1"); + deploy_block("Pompeius", "Neapolis", "Navis 1"); + deploy_block("Pompeius", "Brundisium", "Legio 3"); + deploy_block("Pompeius", "Syracusae", "Legio 37"); + deploy_block("Pompeius", "Antiochia", "Scipio"); + deploy_block("Pompeius", "Antiochia", "Legio 34"); + deploy_block("Pompeius", "Alexandria", "Cleopatra"); + deploy_block("Pompeius", "Alexandria", "Navis 2"); + deploy_block("Pompeius", "Utica", "Legio 39"); + deploy_block("Pompeius", "Utica", "Navis 3"); + deploy_block("Pompeius", "Carthago Nova", "Legio 2"); + deploy_block("Pompeius", "Carthago Nova", "Legio 4"); + deploy_block("Pompeius", "Tarraco", "Legio 5"); + deploy_block("Pompeius", "Tarraco", "Legio 6"); + deploy_block("Pompeius", "Tarraco", "Equitatus 1"); +} + +exports.action = function (state, current, action, arg) { + game = state; + // TODO: check against action list + if (true) { + let S = states[game.state]; + if (action in S) { + S[action](arg, current); + } else { + throw new Error("Invalid action: " + action); + } + } + return state; +} + +exports.resign = function (state, current) { + game = state; + if (game.state != 'game_over') { + log(""); + log(current + " resigned."); + count_vp(); + game.active = null; + game.state = 'game_over'; + game.result = enemy(current); + game.victory = current + " resigned." + } +} + +function make_battle_view() { + let bv = { + CA: [], CB: [], CC: [], CD: [], CR: [], + PA: [], PB: [], PC: [], PD: [], PR: [], + flash: game.flash + }; + + bv.title = game.attacker[game.where]; + if (game.surprise && game.battle_round == 1) + bv.title += " surprise attacks "; + else + bv.title += " attacks "; + bv.title += game.where + bv.title += " \u2014 round " + game.battle_round + " of 4"; + + function is_battle_reserve(b) { + return game.battle_round == 1 && game.reserves.includes(b); + } + + function fill_cell(name, p, fn) { + for (let b in BLOCKS) { + if (game.location[b] == game.where & block_owner(b) == p && fn(b)) { + bv[name].push([b, game.steps[b], game.moved[b]?1:0]) + } + } + } + + fill_cell("CR", CAESAR, b => is_battle_reserve(b)); + fill_cell("CA", CAESAR, b => !is_battle_reserve(b) && block_initiative(b) == 'A'); + fill_cell("CB", CAESAR, b => !is_battle_reserve(b) && block_initiative(b) == 'B'); + fill_cell("CC", CAESAR, b => !is_battle_reserve(b) && block_initiative(b) == 'C'); + fill_cell("CD", CAESAR, b => !is_battle_reserve(b) && block_initiative(b) == 'D'); + fill_cell("PR", POMPEIUS, b => is_battle_reserve(b)); + fill_cell("PA", POMPEIUS, b => !is_battle_reserve(b) && block_initiative(b) == 'A'); + fill_cell("PB", POMPEIUS, b => !is_battle_reserve(b) && block_initiative(b) == 'B'); + fill_cell("PC", POMPEIUS, b => !is_battle_reserve(b) && block_initiative(b) == 'C'); + fill_cell("PD", POMPEIUS, b => !is_battle_reserve(b) && block_initiative(b) == 'D'); + + return bv; +} + +exports.view = function(state, current) { + game = state; + + count_vp(); + + let view = { + log: game.log, + year: game.year, + turn: game.turn, + c_vp: game.c_vp, + p_vp: game.p_vp, + c_card: (game.show_cards || current == CAESAR) ? game.c_card : 0, + p_card: (game.show_cards || current == POMPEIUS) ? game.p_card : 0, + hand: (current == CAESAR) ? game.c_hand : (current == POMPEIUS) ? game.p_hand : [], + who: (game.active == current) ? game.who : null, + where: game.where, + known: {}, + secret: { Caesar: {}, Pompeius: {}, Cleopatra: {} }, + battle: null, + active: game.active, + prompt: null, + actions: null, + }; + + states[game.state].prompt(view, current); + + if (states[game.state].show_battle) + view.battle = make_battle_view(); + + for (let b in BLOCKS) { + if (game.state == 'game_over') { + if (game.location[b] != LEVY) + view.known[b] = [ game.location[b], game.steps[b], 0 ]; + } else if (block_owner(b) == current || game.location[b] == DEAD) { + view.known[b] = [ game.location[b], game.steps[b], game.moved[b]?1:0 ]; + } else { + let o = BLOCKS[b].owner; + let a = game.location[b]; + if (b == CLEOPATRA) + o = CLEOPATRA; + if (a != LEVY) { + let list = view.secret[o]; + if (!(a in list)) + list[a] = [0, 0]; + list[a][0]++; + if (game.moved[b]) + list[a][1]++; + } + } + } + + return view; +} diff --git a/thumbnail.jpg b/thumbnail.jpg new file mode 100644 index 0000000..efc565e Binary files /dev/null and b/thumbnail.jpg differ diff --git a/ui.js b/ui.js new file mode 100644 index 0000000..ba06321 --- /dev/null +++ b/ui.js @@ -0,0 +1,776 @@ +"use strict"; + +const DEAD = "Dead"; +const LEVY = "Levy"; + +let label_style = window.localStorage['julius-caesar/label-style'] || 'columbia'; +let label_layout = window.localStorage['julius-caesar/label-layout'] || 'spread'; + +function toggle_blocks() { + document.getElementById("blocks").classList.toggle("hide_blocks"); +} + +function set_simple_labels() { + label_style = 'simple'; + document.querySelector(".blocks").classList.remove("columbia-labels"); + document.querySelector(".battle").classList.remove("columbia-labels"); + document.querySelector(".blocks").classList.add("simple-labels"); + document.querySelector(".battle").classList.add("simple-labels"); + window.localStorage['julius-caesar/label-style'] = label_style; + update_map(); +} + +function set_columbia_labels() { + label_style = 'columbia'; + document.querySelector(".blocks").classList.remove("simple-labels"); + document.querySelector(".battle").classList.remove("simple-labels"); + document.querySelector(".blocks").classList.add("columbia-labels"); + document.querySelector(".battle").classList.add("columbia-labels"); + window.localStorage['julius-caesar/label-style'] = label_style; + update_map(); +} + +function set_spread_layout() { + label_layout = 'spread'; + window.localStorage['julius-caesar/label-layout'] = label_layout; + update_map(); +} + +function set_stack_layout() { + label_layout = 'stack'; + window.localStorage['julius-caesar/label-layout'] = label_layout; + update_map(); +} + +// Levy and hit animations for 'simple' blocks. +const step_down_animation = [ + { transform: 'translateY(0px)' }, + { transform: 'translateY(10px)' }, + { transform: 'translateY(0px)' }, +]; +const step_up_animation = [ + { transform: 'translateY(0px)' }, + { transform: 'translateY(-10px)' }, + { transform: 'translateY(0px)' }, +]; + +let game = null; + +let ui = { + spaces: {}, + known: {}, + secret: { + Caesar: { offmap: [] }, + Pompeius: { offmap: [] }, + Cleopatra: { offmap: [] }, + }, + seen: new Set(), + present: new Set(), + battle_block: {}, + battle_menu: {}, + map_steps: {}, + map_location: {}, + battle_steps: {}, + cards: {}, +}; + +const STEPS = [ 0, "I", "II", "III", "IIII" ]; + +function block_description(b) { + let s = ui.map_steps[b] || ui.battle_steps[b]; + let c = BLOCKS[b].initiative + BLOCKS[b].firepower; + return BLOCKS[b].name + " " + STEPS[s] + "-" + c; +} + +function block_name(b) { + return BLOCKS[b].name; +} + +function on_focus_space(evt) { + document.getElementById("status").textContent = evt.target.space; +} + +function on_blur_space(evt) { + document.getElementById("status").textContent = ""; +} + +function on_focus_block(evt) { + document.getElementById("status").textContent = block_description(evt.target.block); +} + +function on_blur_block(evt) { + document.getElementById("status").textContent = ""; +} + +function on_focus_battle_block(evt) { + let b = evt.target.block; + let msg = block_name(b); + if (!evt.target.classList.contains("known")) + document.getElementById("status").textContent = "Reserves"; + + if (game.actions && game.actions.battle_fire && game.actions.battle_fire.includes(b)) + msg = "Fire with " + msg; + else if (game.actions && game.actions.battle_retreat && game.actions.battle_retreat.includes(b)) + msg = "Retreat with " + msg; + else if (game.actions && game.actions.battle_hit && game.actions.battle_hit.includes(b)) + msg = "Take hit on " + msg; + + document.getElementById("status").textContent = msg; +} + +function on_blur_battle_block(evt) { + document.getElementById("status").textContent = ""; +} + +function on_focus_battle_fire(evt) { + document.getElementById("status").textContent = + "Fire with " + block_name(evt.target.block); +} +function on_focus_battle_retreat(evt) { + document.getElementById("status").textContent = + "Retreat with " + block_name(evt.target.block); +} +function on_focus_battle_pass(evt) { + document.getElementById("status").textContent = + "Pass with " + block_name(evt.target.block); +} +function on_focus_battle_hit(evt) { + document.getElementById("status").textContent = + "Take hit on " + block_name(evt.target.block); +} +function on_blur_battle_button(evt) { + document.getElementById("status").textContent = ""; +} + +function build_map() { + // These must match up with the sizes in play.html + const city_size = 60+10; + const sea_size = 70+10; + + for (let s in SPACES) { + let space = SPACES[s]; + let element = document.createElement("div"); + element.classList.add("space"); + let size = (space.type == 'sea') ? sea_size : city_size; + if (space.type == "sea") + element.classList.add("sea"); + else + element.classList.add("city"); + element.setAttribute("draggable", "false"); + element.addEventListener("mouseenter", on_focus_space); + element.addEventListener("mouseleave", on_blur_space); + element.addEventListener("click", select_space); + element.style.left = (space.x - size/2) + "px"; + element.style.top = (space.y - size/2) + "px"; + if (space.type != 'pool') + document.getElementById("spaces").appendChild(element); + element.space = s; + ui.spaces[s] = element; + + ui.secret[CLEOPATRA][s] = []; + ui.secret[CAESAR][s] = []; + ui.secret[POMPEIUS][s] = []; + } + + function build_known_block(b, block, color) { + let element = document.createElement("div"); + element.classList.add("block"); + element.classList.add("known"); + element.classList.add(color); + element.classList.add("block_"+block.label); + element.addEventListener("mouseenter", on_focus_block); + element.addEventListener("mouseleave", on_blur_block); + element.addEventListener("click", select_block); + document.getElementById("known_blocks").appendChild(element); + element.style.visibility = 'hidden'; + element.block = b; + ui.known[b] = element; + } + + function build_secret_block(b, block, color) { + let element = document.createElement("div"); + element.secret_index = ui.secret[color].offmap.length; + element.classList.add("block"); + element.classList.add("secret"); + element.classList.add(color); + element.addEventListener("click", select_secret_block); + document.getElementById("secret_blocks").appendChild(element); + element.style.visibility = 'hidden'; + element.owner = BLOCKS[b].owner; + ui.secret[color].offmap.unshift(element); + } + + function build_battle_button(menu, b, c, click, enter, img_src) { + let img = new Image(); + img.draggable = false; + img.classList.add("action"); + img.classList.add(c); + img.setAttribute("src", img_src); + img.addEventListener("click", click); + img.addEventListener("mouseenter", enter); + img.addEventListener("mouseleave", on_blur_battle_button); + img.block = b; + menu.appendChild(img); + } + + function build_battle_block(b, block, color) { + let element = document.createElement("div"); + element.classList.add("block"); + element.classList.add("known"); + element.classList.add(color); + element.classList.add("block_"+block.label); + element.addEventListener("mouseenter", on_focus_battle_block); + element.addEventListener("mouseleave", on_blur_battle_block); + element.addEventListener("click", select_block); + element.block = b; + ui.battle_block[b] = element; + + let action_list = document.createElement("div"); + action_list.classList.add("battle_menu_list"); + action_list.appendChild(element); + build_battle_button(action_list, b, "hit", + select_battle_hit, on_focus_battle_hit, + "/images/cross-mark.svg"); + build_battle_button(action_list, b, "fire", + select_battle_fire, on_focus_battle_fire, + "/images/pointy-sword.svg"); + build_battle_button(action_list, b, "retreat", + select_battle_retreat, on_focus_battle_retreat, + "/images/flying-flag.svg"); + build_battle_button(action_list, b, "pass", + select_battle_pass, on_focus_battle_pass, + "/images/sands-of-time.svg"); + + let menu = document.createElement("div"); + menu.classList.add("battle_menu"); + menu.appendChild(element); + menu.appendChild(action_list); + ui.battle_menu[b] = menu; + } + + for (let b in BLOCKS) { + let block = BLOCKS[b]; + let color = (block.name == "Cleopatra" ? "Cleopatra" : block.owner); + build_known_block(b, block, color); + build_secret_block(b, block, color); + build_battle_block(b, block, color); + } + + for (let c = 1; c <= 27; ++c) { + ui.cards[c] = document.getElementById("card+" + c); + } +} + +function update_steps(memo, block, steps, element, animate) { + let old_steps = memo[block] || steps; + memo[block] = steps; + + if (label_style == 'simple' && steps != old_steps && animate) { + let options = { duration: 700, easing: 'ease', iterations: Math.abs(steps-old_steps) } + if (steps < old_steps) + element.animate(step_down_animation, options); + if (steps > old_steps) + element.animate(step_up_animation, options); + } + + element.classList.remove("r0"); + element.classList.remove("r1"); + element.classList.remove("r2"); + element.classList.remove("r3"); + element.classList.add("r"+(BLOCKS[block].steps - steps)); +} + +function layout_blocks(location, secret, known) { + if (label_layout == 'spread' || (location == LEVY || location == DEAD)) + layout_blocks_spread(location, secret, known); + else + layout_blocks_stacked(location, secret, known); +} + +function layout_blocks_spread(location, secret, known) { + let wrap = SPACES[location].wrap; + let s = secret.length; + let k = known.length; + let n = s + k; + let row, rows = []; + let i = 0; + + function new_line() { + rows.push(row = []); + i = 0; + } + + new_line(); + + while (secret.length > 0) { + if (i == wrap) + new_line(); + row.push(secret.shift()); + ++i; + } + + // Break early if secret and known fit in exactly two rows, and more than three blocks total + if (s > 0 && s <= wrap && k > 0 && k <= wrap && n > 3) + new_line(); + + while (known.length > 0) { + if (i == wrap) + new_line(); + row.push(known.shift()); + ++i; + } + + if (SPACES[location].layout_minor > 0.5) + rows.reverse(); + + for (let j = 0; j < rows.length; ++j) + for (i = 0; i < rows[j].length; ++i) + position_block_spread(location, j, rows.length, i, rows[j].length, rows[j][i]); +} + +function position_block_spread(location, row, n_rows, col, n_cols, element) { + let space = SPACES[location]; + let block_size = (label_style == 'columbia') ? 56+6 : 48+4; + let padding = (location == LEVY || location == DEAD) ? 6 : 3; + let offset = block_size + padding; + let row_size = (n_rows-1) * offset; + let col_size = (n_cols-1) * offset; + let x = space.x - block_size/2; + let y = space.y - block_size/2; + + if (space.layout_axis == 'X') { + x -= col_size * space.layout_major; + y -= row_size * space.layout_minor; + x += col * offset; + y += row * offset; + } else { + y -= col_size * space.layout_major; + x -= row_size * space.layout_minor; + y += col * offset; + x += row * offset; + } + + element.style.left = (x|0)+"px"; + element.style.top = (y|0)+"px"; +} + +function layout_blocks_stacked(location, secret, known) { + let s = secret.length; + let k = known.length; + let n = s + k; + let i = 0; + while (secret.length > 0) + position_block_stacked(location, i++, n, secret.shift()); + while (known.length > 0) + position_block_stacked(location, i++, n, known.shift()); +} + +function position_block_stacked(location, i, n, element) { + let space = SPACES[location]; + let block_size = (label_style == 'columbia') ? 56+6 : 48+4; + let x = space.x - block_size/2 - (n-1) * 9 + i * 18; + let y = space.y - block_size/2 - (n-1) * 9 + i * 18; + element.style.left = x+"px"; + element.style.top = y+"px"; +} + +function sort_secret(a,b) { + return a.secret_index - b.secret_index; +} + +function update_map() { + let overflow = { Caesar: [], Pompeius: [], Cleopatra: [] }; + let layout = {}; + + for (let s in SPACES) + layout[s] = { secret: [], known: [] }; + + // Move secret blocks to overflow queue if there are too many in a location + for (let s in SPACES) { + for (let color of [CAESAR, POMPEIUS, CLEOPATRA]) { + let max = 0; + if (game.secret[color] && game.secret[color][s]) + max = game.secret[color][s][0]; + while (ui.secret[color][s].length > max) + overflow[color].push(ui.secret[color][s].pop()); + } + } + + // Add secret blocks if there are too few in a location + for (let s in SPACES) { + for (let color of [CAESAR, POMPEIUS, CLEOPATRA]) { + let max = 0; + if (game.secret[color] && game.secret[color][s]) + max = game.secret[color][s][0]; + while (ui.secret[color][s].length < max) { + if (overflow[color].length > 0) { + ui.secret[color][s].push(overflow[color].pop()); + } else { + let element = ui.secret[color].offmap.pop(); + element.style.visibility = 'visible'; + ui.secret[color][s].push(element); + } + } + } + } + + // Remove any blocks left in the overflow queue + for (let color of [CAESAR, POMPEIUS, CLEOPATRA]) { + while (overflow[color].length > 0) { + let element = overflow[color].pop(); + element.style.visibility = 'hidden'; + // Prevent move animation when blocks are revived. + element.style.left = null; + element.style.top = null; + ui.secret[color].offmap.push(element); + } + } + + // Hide formerly known blocks + for (let b in BLOCKS) { + if (!(b in game.known)) { + ui.known[b].style.visibility = 'hidden'; + // Prevent move animation when blocks are revived. + ui.known[b].style.left = null; + ui.known[b].style.top = null; + } + } + + // Add secret blocks to layout + for (let location in SPACES) { + for (let color of [CAESAR, POMPEIUS, CLEOPATRA]) { + if (location != LEVY) { + let i = 0, n = 0, m = 0; + if (game.secret[color] && game.secret[color][location]) { + n = game.secret[color][location][0]; + m = game.secret[color][location][1]; + } + // Preserve block stacking order, but lets the user track block identities... + if (label_layout == 'stack') + ui.secret[color][location].sort((a,b) => a.secret_index - b.secret_index); + for (let element of ui.secret[color][location]) { + if (i++ < n - m) + element.classList.remove('moved'); + else + element.classList.add('moved'); + element.style.visibility = 'visible'; + + layout[location].secret.push(element); + } + } + } + } + + // Add known blocks to layout + for (let block in game.known) { + let location = game.known[block][0]; + let steps = game.known[block][1]; + let moved = game.known[block][2]; + let element = ui.known[block]; + + element.style.visibility = 'visible'; + + layout[location].known.push(element); + + let old_location = ui.map_location[block]; + update_steps(ui.map_steps, block, steps, element, location == old_location); + ui.map_location[block] = location; + + if (moved || (location == DEAD && BLOCKS[block].type != 'leader')) + element.classList.add("moved"); + else + element.classList.remove("moved"); + + ui.seen.add(block); + } + + // Layout blocks on map + for (let location in SPACES) + layout_blocks(location, layout[location].secret, layout[location].known); + + // Mark selections and highlights + + for (let where in SPACES) { + if (ui.spaces[where]) { + ui.spaces[where].classList.remove('highlight'); + ui.spaces[where].classList.remove('where'); + } + } + if (game.actions && game.actions.space) + for (let where of game.actions.space) + ui.spaces[where].classList.add('highlight'); + if (game.where) + ui.spaces[game.where].classList.add('where'); + + for (let b in BLOCKS) { + ui.known[b].classList.remove('highlight'); + ui.known[b].classList.remove('selected'); + } + if (!game.battle) { + if (game.actions && game.actions.block) + for (let b of game.actions.block) + ui.known[b].classList.add('highlight'); + if (game.who) + ui.known[game.who].classList.add('selected'); + } + + for (let o in ui.secret) { + for (let s in ui.secret[o]) { + if (game.actions && game.actions.secret && game.actions.secret.includes(s)) { + for (let e of ui.secret[o][s]) + e.classList.add("highlight"); + } else { + for (let e of ui.secret[o][s]) + e.classList.remove("highlight"); + } + } + } +} + +function update_battle(player) { + function fill_cell(name, list, reserve) { + let cell = window[name]; + + ui.present.clear(); + + for (let [block, steps, moved] of list) { + ui.seen.add(block); + ui.present.add(block); + + if (block == game.who) + ui.battle_menu[block].classList.add("selected"); + else + ui.battle_menu[block].classList.remove("selected"); + + ui.battle_block[block].classList.remove("highlight"); + ui.battle_menu[block].classList.remove('hit'); + ui.battle_menu[block].classList.remove('fire'); + ui.battle_menu[block].classList.remove('retreat'); + ui.battle_menu[block].classList.remove('pass'); + + if (game.actions && game.actions.block && game.actions.block.includes(block)) + ui.battle_block[block].classList.add("highlight"); + if (game.actions && game.actions.battle_fire && game.actions.battle_fire.includes(block)) + ui.battle_menu[block].classList.add('fire'); + if (game.actions && game.actions.battle_retreat && game.actions.battle_retreat.includes(block)) + ui.battle_menu[block].classList.add('retreat'); + if (game.actions && game.actions.battle_pass && game.actions.battle_pass.includes(block)) + ui.battle_menu[block].classList.add('pass'); + if (game.actions && game.actions.battle_hit && game.actions.battle_hit.includes(block)) + ui.battle_menu[block].classList.add('hit'); + + update_steps(ui.battle_steps, block, steps, ui.battle_block[block], true); + if (reserve) + ui.battle_block[block].classList.add("secret"); + else + ui.battle_block[block].classList.remove("secret"); + if (moved || reserve) + ui.battle_block[block].classList.add("moved"); + else + ui.battle_block[block].classList.remove("moved"); + if (reserve) + ui.battle_block[block].classList.remove("known"); + else + ui.battle_block[block].classList.add("known"); + } + + for (let b in BLOCKS) { + if (ui.present.has(b)) { + if (!cell.contains(ui.battle_menu[b])) + cell.appendChild(ui.battle_menu[b]); + } else { + if (cell.contains(ui.battle_menu[b])) + cell.removeChild(ui.battle_menu[b]); + } + } + } + + if (player == CAESAR) { + fill_cell("FR", game.battle.CR, true); + fill_cell("FA", game.battle.CA, false); + fill_cell("FB", game.battle.CB, false); + fill_cell("FC", game.battle.CC, false); + fill_cell("FD", game.battle.CD, false); + fill_cell("EA", game.battle.PA, false); + fill_cell("EB", game.battle.PB, false); + fill_cell("EC", game.battle.PC, false); + fill_cell("ED", game.battle.PD, false); + fill_cell("ER", game.battle.PR, true); + } else { + fill_cell("ER", game.battle.CR, true); + fill_cell("EA", game.battle.CA, false); + fill_cell("EB", game.battle.CB, false); + fill_cell("EC", game.battle.CC, false); + fill_cell("ED", game.battle.CD, false); + fill_cell("FA", game.battle.PA, false); + fill_cell("FB", game.battle.PB, false); + fill_cell("FC", game.battle.PC, false); + fill_cell("FD", game.battle.PD, false); + fill_cell("FR", game.battle.PR, true); + } +} + +function update_card_display(element, card, prior_card) { + if (!card && !prior_card) { + element.className = "small_card card_back"; + } else if (prior_card) { + element.className = "small_card prior card_" + CARDS[prior_card].image; + } else { + element.className = "small_card card_" + CARDS[card].image; + } +} + +function update_cards() { + update_card_display(document.getElementById("caesar_card"), game.c_card, game.prior_c_card); + update_card_display(document.getElementById("pompeius_card"), game.p_card, game.prior_p_card); + + for (let c = 1; c <= 27; ++c) { + let element = ui.cards[c]; + if (game.hand.includes(c)) { + element.classList.add("show"); + if (game.actions && game.actions.card) { + if (game.actions.card.includes(c)) { + element.classList.add("enabled"); + element.classList.remove("disabled"); + } else { + element.classList.remove("enabled"); + element.classList.add("disabled"); + } + } else { + element.classList.remove("enabled"); + element.classList.remove("disabled"); + } + } else { + element.classList.remove("show"); + } + } +} + +function on_update(state, player) { + game = state; + + document.getElementById("turn").className = "year_" + game.year; + document.getElementById("caesar_vp").textContent = game.c_vp + " VP"; + document.getElementById("pompeius_vp").textContent = game.p_vp + " VP"; + + show_action_button("#undo_button", "undo"); + show_action_button("#surprise_button", "surprise"); + show_action_button("#pass_button", "pass", true); + + ui.seen.clear(); + + update_cards(); + update_map(); + + for (let b in BLOCKS) + if (!ui.seen.has(b)) + ui.map_steps[b] = 0; + + ui.seen.clear(); + + if (game.battle) { + document.querySelector(".battle_header").textContent = game.battle.title; + document.querySelector(".battle_message").textContent = game.battle.flash; + document.querySelector(".battle").classList.add("show"); + update_battle(player); + } else { + document.querySelector(".battle").classList.remove("show"); + } + + for (let b in BLOCKS) + if (!ui.seen.has(b)) + ui.battle_steps[b] = 0; + + if (game.spaceList) { + if (game.actionList.includes("secret")) { + for (let o in ui.secret) { + for (let s in ui.secret[o]) { + if (game.spaceList.includes(s)) { + for (let e of ui.secret[o][s]) + e.classList.add("highlight"); + } else { + for (let e of ui.secret[o][s]) + e.classList.remove("highlight"); + } + } + } + for (let s in SPACES) + if (game.spaceList.includes(s)) + ui.spaces[s].classList.remove("highlight"); + } else { + for (let o in ui.secret) + for (let s in ui.secret[o]) + for (let e of ui.secret[o][s]) + e.classList.remove("highlight"); + for (let s in SPACES) { + if (game.spaceList.includes(s)) + ui.spaces[s].classList.add("highlight"); + else + ui.spaces[s].classList.remove("highlight"); + } + } + } + + if (game.blockList) { + for (let b in BLOCKS) { + if (game.blockList.includes(b)) + ui.known[b].classList.add("highlight"); + else + ui.known[b].classList.remove("highlight"); + if (b == game.who) + ui.known[b].classList.add("selected"); + else + ui.known[b].classList.remove("selected"); + } + } +} + +function select_card(c) { + send_action('card', c); +} + +function select_space(evt) { + send_action('space', evt.target.space); +} + +function select_block(evt) { + send_action('block', evt.target.block); +} + +function select_secret_block(evt) { + let element = evt.target; + let owner = null; + let where = null; + for (let o in ui.secret) { + for (let s in ui.secret[o]) { + if (ui.secret[o][s].includes(element)) { + owner = o; + where = s; + break; + } + } + } + if (game.actions && game.actions.secret && game.actions.secret.includes(where)) { + socket.emit('action', 'secret', [where, owner]); + game.actions = null; + } +} + +function select_surprise() { send_action('surprise'); } +function select_pass() { send_action('pass'); } +function select_undo() { send_action('undo'); } + +function select_battle_hit(evt) { send_action('battle_hit', evt.target.block); } +function select_battle_fire(evt) { send_action('battle_fire', evt.target.block); } +function select_battle_retreat(evt) { send_action('battle_retreat', evt.target.block); } +function select_battle_pass(evt) { send_action('battle_pass', evt.target.block); } + +build_map(); + +document.querySelector(".blocks").classList.add(label_style+'-labels'); +document.querySelector(".battle").classList.add(label_style+'-labels'); + +drag_element_with_mouse(".battle", ".battle_header"); +scroll_with_middle_mouse(".grid_center"); + +init_client([ "Caesar", "Pompeius" ]); -- cgit v1.2.3