forked from tchernicum/bcapps
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbc-conquerclub-reconstruct-game.pl
executable file
·228 lines (187 loc) · 6.84 KB
/
bc-conquerclub-reconstruct-game.pl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
#!/bin/perl
# reconstructs as much of conquerclub game as possible given full logs
# --nocomment: don't print comments
# --maxrounds: only print equations thru this round (testing only)
# was taking a round-by-round approach, but
# http://userscripts.org/scripts/show/83035 makes me wonder if an
# action-by-action approach is better
# Note: regions always start w/ 3 troops
push(@INC,"/usr/local/lib");
require "bclib.pl";
# sample test game
$all = read_file("sample-data/CONQUERCLUB/7460216.html");
# if --maxrounds not set, set it to infinity
defaults("maxrounds=999999999999999999");
# find end-of-game deployment
$all=~m/map = (.*?);/is;
$map=$1;
$all=~m/armies = (.*?);/is;
$armies=$1;
# armies is an array, map is a hash
@armies = @{JSON::from_json($armies)};
%map = %{JSON::from_json($map)};
# convenience variable (list of country names)
@countries = @{$map{countries}};
for $i (@countries) {
%hash=%{$i};
$i=$hash{name};
}
%countries=list2hash(@countries);
# all countries start w/ 3 troops
# TODO: validate above (ie, is it really true?)
# could probably combine this w/ loop above, but pointless
for $j (@countries) {
push(@out, qq%troops["$j"][0] == 3%);
}
# find log section
$all=~m%<div id="log">(.*?)</div>%s||warn("Can't find log section");
@log = split(/<br>/,$1);
# go thru log lines
for $i (@log) {
# cleanup occasional oddness
$i = trim($i);
# ignore blank lines
if ($i=~/^\s*$/) {next;}
# comment for output
$comment = $i;
$comment=~s/<([^>]*?)>//isg;
# time
$i=~s/^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) -\s*//;
$time = $1;
# find player (if any) + record player number
if ($i=~s%<span class="player(\d+)">(.*?)</span>\s*%%) {
$actor=$2;
$player[$1]=$actor;
} else {
$actor="";
}
# TODO: for every country (even those specifically listed) allow for
# troop loss due to unsuccessful attacks
# ignore end of player turn (not end of entire turn)
if ($i=~/^ended the turn$/) {next;}
# information below is repeated in deployment, so can ignore it here
if ($i=~/^received \d+ troops for \d+ regions$/) {next;}
# other stuff we can ignore
# TODO: combine this w/ 2 regex above?
if ($i=~/(Game has been initialized|eliminated|won the|lost \d+ points|gained \d+ points|Incrementing game to round \d+)/) {next;}
# increment round ($prevround is a convenience var only)
# <h>Part of a series on how to write confusing code</h>
$prevround = $round++;
if ($round>= $globopts{maxrounds}) {next;}
# push log line as comment
# TODO: comment format may change (haven't decided on backend language yet)
unless ($globopts{nocomment}) {push(@out, "(* $comment *)");}
# keep track of which territories are affected by action item
%affected=();
if ($i=~/^received (\d+) troops for holding (.*?)$/) {
# specific region received troops
($ntroop, $terr) = ($1,$2);
unless ($countries{$terr}) {$round--; next;}
push(@out, qq%troops["$terr"][$round] <= troops["$terr"][$prevround] + $ntroop%);
$affected{$terr}=1;
} elsif ($i=~/deployed (\d+) troops on (.*?)$/) {
# troop deployment
($ntroop, $terr) = ($1,$2);
push(@out, qq%troops["$terr"][$round] <= troops["$terr"][$prevround] + $ntroop%);
$affected{$terr}=1;
} elsif ($i=~/^reinforced (.*?) with (\d+) troops from (.*?)$/) {
# reinforcement
($source, $ntroop, $target) = ($3,$2,$1);
push(@out, qq%troops["$target"][$round] <= troops["$target"][$prevround] + $ntroop%);
push(@out, qq%troops["$source"][$round] <= troops["$source"][$prevround] - $ntroop%);
$affected{$source}=1;
$affected{$target}=1;
} elsif ($i=~/^assaulted (.*?) from (.*?) and conquered it from \*?(.*?)\*?$/) {
# conquest
($atkr, $dest, $source, $defn) = ($actor,$1,$2,$3);
# together, source and target have at most as many troops as source did
push(@out, qq%troops["$source"][$round] + troops["$dest"][$round] <= troops["$source"][$prevround]%);
$affected{$source}=1;
$affected{$dest}=1;
$defn=~s%<span.*?>(.*?)</span>%$1%;
$owner{$round}{$dest} = $atkr;
$owner{$round}{$source} = $atkr; # probably redundant
$owner{$prevround}{$dest} = $defn; # redundant
# if a territory is conquered delta is probably wrong
$conquered{$round}{$dest} = 1;
} else {
warnlocal("Unhandled: $i");
}
# now, every other country
for $j (@countries) {
# this applies to affected countries too
push(@out, qq%troops["$j"][$round] >= 1%);
if ($affected{$j}) {next;}
push(@out, qq%troops["$j"][$round] <= troops["$j"][$prevround]%);
}
}
$player[0]="neutral player"; # special case
# end of game stuff we know
for $i (0..$#armies) {
# TODO: not really efficient to put this in for loop!
if ($round > $globopts{maxrounds}) {next;}
%hash = %{$armies[$i]};
# TODO: uncomment below!
# push(@math, qq%owner["$countries[$i]"][$round] = "$player[$hash{player}]"%);
push(@out, qq%troops["$countries[$i]"][$round] == $hash{quantity}%);
# push(@math, qq%troops["$countries[$i]"][$round] == $hash{quantity}%);
# debug("$countries[$i] -> $player[$hash{player}], $hash{quantity}");
}
debug(@out);
write_file("ineq={\n".join(",\n",@out)."\n}\n", "/tmp/math.m");
# TODO: test code to cheat convert Mathematica to Prolog instead of
# doing it correctly above in the first place
$all = read_file("/tmp/math.m");
# vars must start w/ cap
$all=~s/troops/Troops/isg;
# round numbers
$all=~s/\[(\d+)\]/_$1/isg;
# terr names
$all=~s/\[\"(.*?)\"\]/"_".proclean($1)/iseg;
# conditionals
$all=~s/\=\=/\#=/isg;
$all=~s/>\=/\#>=/isg;
# <h>Why doesn't Prolog support "#<="? No one knows, and no one cares!</h>
$all=~s/<\=/\#=</isg;
# junk
$all=~s/ineq\=\{\s*//isg;
$all=~s/\}/./isg;
# collecting vals
@vals = ($all=~m%(Troops\S+?_\d+)%img);
# define the goal
$goal = "goal([".join(",\n",@vals)."]) :-\n";
write_file("$goal$all","/tmp/math.pl");
# cleans up territory names to avoid spaces + other odd chars
sub proclean {
my($str) = @_;
$str=~s/[^a-z0-9]/_/isg;
return $str;
}
die "TESTING";
# TODO: actual stuff here
# reverse sorting by round, last round first
for ($i=$round; $i>=0; $i--) {
# must look at all countries, not just those that are keys
for $j (sort @countries) {
if ($conquered{$i}{$j}) {
# TODO: stuff
# ignoring rounds where territory is conquered (for now)
next;
}
# cleanup
if ($delta{$i}{$j} eq "") {$delta{$i}{$j}=0;}
# start with 3
# TODO: not always true for neuts!
if ($i==0) {
push(@math, qq%troops["$j"][$i]==3%);
next;
}
# unk = unknown number of troops lost (never gained) between rounds i-1 & i
push(@math, qq%troops["$j"][$i] == troops["$j"][$i-1]+$delta{$i}{$j}-unk["$j"][$i]%);
push(@math, qq%troops["$j"][$i] >= 1%, qq%unk["$j"][$i] >= 0%);
# debug("$i,$j: $owner{$i}{$j} and $delta{$i}{$j}");
}
}
write_file("ineq={\n".join(",\n",@math)."\n}\n", "/tmp/math.m");
# this is ugly; warnlocal should have a default
sub warnlocal {warn(@_);}