-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdemonic-tutor.sml
385 lines (338 loc) · 13.8 KB
/
demonic-tutor.sml
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
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
structure DemonicTutor = struct
structure PFS = Posix.FileSys
structure PP = Posix.Process
structure PIO = Posix.IO
val flagQuiet = Params.flag false
(SOME ("--quiet", "Suppress most output")) "quiet"
fun printquiet s = if !flagQuiet then () else print (s ^ "\n")
val flagReport = Params.flag true
(SOME ("--noreport", "Do not report to server")) "report"
val flagHelp = Params.flag false
(SOME ("--help", "Print an informative help message")) "help"
val flagMode = Params.param "duel"
(SOME ("--mode", "Tutor mode (duel, stress, costress, auto)")) "mode"
val flagPoll = Params.param "10000"
(SOME ("--poll", "How often the state is reported (0=never)")) "poll"
val flagLogStats = Params.flag false
(SOME ("--logstats", "Log application stats for each player")) "logstats"
val max = 200000
val flagFreq = ref 20000
(*
val flagPoll = Params.param "poll" "How often outputs are reported" "20000"
val flagMode = Params.param "mode" "Run mode (duel, stress, auto)" "duel"
*)
fun err x = TextIO.output (TextIO.stdErr, x ^ "\n")
fun debug x = () (* TextIO.output (TextIO.stdErr, x ^ "\n") *)
(* SETTING UP PIPES *)
fun mkInstream in_file_desc =
TextIO.mkInstream
(TextIO.StreamIO.mkInstream
(PIO.mkTextReader {fd = in_file_desc,
name = "Reader from the pipe",
initBlkMode = true}, ""))
fun mkOutstream out_file_desc =
TextIO.mkOutstream
(TextIO.StreamIO.mkOutstream
(PIO.mkTextWriter {fd = out_file_desc,
name = "Writer to the pipe",
initBlkMode = true,
chunkSize = 1024,
appendMode = true}, IO.NO_BUF))
val isColon = fn x => x = #":"
val enhypenate = fn x => String.concatWith "-" (String.tokens isColon x)
fun mkStatstream player0 player1 tag =
let
val player0 = enhypenate player0
val player1 = enhypenate player1
val filename = "graph-" ^ player0 ^ "-" ^ player1 ^ "-" ^ tag ^ ".dat"
in
if !flagLogStats then SOME (TextIO.openOut filename) else NONE
end
fun goGraph player0 player1 =
if not (!flagLogStats andalso !Make.flagSubmit) then () else
case String.tokens isColon player0 @ String.tokens isColon player1 of
l as [ p0, r0, p1, r1 ] =>
if Make.file_exists ("graph-" ^ String.concatWith "-" l ^ ".png")
then () else
ignore (OS.Process.system("./submitgraph " ^ String.concatWith " " l))
| _ => ()
fun setupPlayer player arg state statstream =
let
val exe = Make.getExe player
(* Set up two pipes *)
val {infd = inServer, outfd = outPlayer} = PIO.pipe ()
val {infd = inPlayer, outfd = outServer} = PIO.pipe ()
in
case PP.fork () of
NONE => (* Set up the player's pipes, exec away *)
let
val devnull = PFS.openf ("/dev/null", PFS.O_WRONLY, PFS.O.append)
in
PIO.dup2 {old = inPlayer, new = PFS.stdin }
; PIO.dup2 {old = outPlayer, new = PFS.stdout }
; if not (!flagQuiet) then ()
else PIO.dup2 {old = devnull, new = PFS.stderr }
; debug "Forking child..."
; PP.execp (exe, [ exe, arg ])
end
| SOME pid => (* Return the pipes to children *)
(PIO.close outPlayer
; PIO.close inPlayer
; {name = player,
broke = ref false,
pid = pid,
instream = mkInstream inServer,
outstream = mkOutstream outServer,
statstream = statstream,
state = state})
end
type process = {name: string,
broke: bool ref,
pid: PIO.pid,
instream: TextIO.instream,
outstream: TextIO.outstream,
statstream: TextIO.outstream option,
state: LTG.side}
fun winner (proponent, opponent) =
let fun lost player =
not (Array.exists (fn x => x > 0) (#2 (#state player)))
in lost proponent orelse lost opponent end
fun playerData player =
let
val vita = #2 (#state player)
in
if !(#broke player) then (0, 0, 256, 256)
else
Array.foldr
(fn (x, (vitality, live, dead, zombie)) =>
if x > 0
then (vitality + IntInf.fromInt x, live + 1, dead, zombie)
else if x = 0
then (vitality, live, dead + 1, zombie)
else (vitality, live, dead, zombie + 1)) (0, 0, 0, 0) vita
end
(*
fun export (n, proponent, opponent) =
(case !cardfax of
SOME server =>
let
val (_, plive, _, _) = playerData proponent
val (_, olive, _, _) = playerData opponent
fun defeat (w, l) =
let val s = "curl 'http://" ^ server ^ "/match?win=" ^ w ^ "&lose=" ^ l ^ "'\n"
in print s; OS.Process.system s; () end
in
case Int.compare (plive, olive) of
GREATER => defeat (#name proponent, #name opponent)
| LESS => defeat (#name opponent, #name proponent)
| EQUAL => ()
end
| NONE => ()) *)
fun report (n, proponent, opponent) =
let
fun printPlayerData player =
let val (vitality, live, dead, zombie) = playerData player
in
print ("DEAD: " ^ Int.toString dead)
; print (" / ZOMB: " ^ Int.toString zombie)
; if live = 0 then print " / LIVE: 0\n"
else print (" / LIVE: " ^ Int.toString live
^ " (average vitality of living: "
^ IntInf.toString (vitality div IntInf.fromInt live)
^ ")\n")
end
val (player0, player1) =
if n mod 2 = 0 then (proponent, opponent) else (opponent, proponent)
in
print ("After " ^ Int.toString (n div 2) ^ " turns...\n")
; print "PLAYER 0 -- "
; printPlayerData player0
; print "PLAYER 1 -- "
; printPlayerData player1
end
exception Broke of int * process * process
fun continue (n, proponent: process, opponent: process) =
if n = max orelse winner (proponent, opponent)
then (report (n, proponent, opponent);
if n mod 2 = 0
then (n, proponent, opponent)
else (n, opponent, proponent))
else let
val () = if n = 0 orelse n mod !flagFreq <> 0 then ()
else report (n, proponent, opponent)
(* val _ = TextIO.inputLine TextIO.stdIn *)
(* GET THE MOVE *)
val () = debug "Receiving in tutor"
val play = LTGParse.rcv (#instream proponent)
handle LTGParse.LTGIO s =>
if n mod 2 = 0
then (err ("PLAYER 0 FUCKED UP: " ^ s ^ " (bad send)")
; #broke proponent := true
; raise Broke (n, proponent, opponent))
else (err ("PLAYER 1 FUCKED UP: " ^ s ^ " (bad send)")
; #broke opponent := true
; raise Broke (n, opponent, proponent))
val () = debug "Done receiving in tutor"
(* SEND THE MOVE *)
val () = debug "Sending in tutor"
val () = LTGParse.send (#outstream opponent) play
handle LTGParse.LTGIO s =>
if n mod 2 = 0
then (err ("PLAYER 1 FUCKED UP: " ^ s ^ " (bad recv)")
; #broke opponent := true
; raise Broke (n, proponent, opponent))
else (err ("PLAYER 0 FUCKED UP: " ^ s ^ " (bad recv)")
; #broke proponent := true
; raise Broke (n, opponent, proponent))
val () = debug "Done sending in tutor"
(* MAYBE LOG SOME DATA ABOUT THE MOVE *)
val () = LTG.application_count_hook :=
Option.map (fn ss => fn n => TextIO.output(ss, Int.toString n ^ "\n"))
(#statstream proponent)
(* DO YOU WANT TO PLAY A GAME? *)
val () = LTG.taketurn (#state proponent, #state opponent) play
in
continue (n+1, opponent, proponent)
end
fun usage stat =
(err ("Usage: "^CommandLine.name()^" [options] [--mode duel] arg arg")
; err (" "^CommandLine.name()^" [options] --mode stress arg")
; err (" "^CommandLine.name()^" [options] --mode auto")
; err ("The argument 'foo' runs (building if needed) player-foo.exe")
; err ("The argument 'foo:12' runs (building if needed) player-foo-12.exe")
; err ("WARNING: ALWAYS CHECK IN BEFORE USING 'player:rev' arguments!\n")
; err (Params.usage ())
; OS.Process.exit stat)
(* Runs a single match between two players *)
fun match player0 player1 =
let
(* Setup state *)
val () = print ("Preparing " ^ player0 ^ " vs " ^ player1 ^ "\n")
val ss0 = mkStatstream player0 player1 "0"
val ss1 = mkStatstream player0 player1 "1"
val process0 = setupPlayer player0 "0" (LTG.initialside ()) ss0
val process1 = setupPlayer player1 "1" (LTG.initialside ()) ss1
(* Run *)
val () = print ("Starting " ^ player0 ^ " vs " ^ player1 ^ "\n")
val (rounds, final0, final1) = continue (0, process0, process1)
handle Broke data => data
(* Cleanup *)
val () = PP.kill (PP.K_PROC (#pid process0), Posix.Signal.kill)
val () = PP.kill (PP.K_PROC (#pid process1), Posix.Signal.kill)
val () = TextIO.closeIn (#instream process0)
val () = TextIO.closeOut (#outstream process0)
val () = TextIO.closeIn (#instream process1)
val () = TextIO.closeOut (#outstream process1)
val () = Option.app TextIO.closeOut (#statstream process0)
val () = Option.app TextIO.closeOut (#statstream process1)
val tok = String.tokens (fn c => c = #":")
in
(* Potentially record output *)
goGraph player0 player1;
if not (!flagReport) then ()
else case (tok player0, tok player1) of
([ name0, rev0 ], [ name1, rev1 ]) =>
(case (Int.fromString rev0, Int.fromString rev1) of
(SOME rev0, SOME rev1) =>
let
val () = print "Reporting versioned match to server...\n"
fun vit vit' live' =
if live' = 0 then "0"
else IntInf.toString (vit' div IntInf.fromInt live')
val (vit0, live0, dead0, zomb0) = playerData final0
val (vit1, live1, dead1, zomb1) = playerData final1
fun f x =
print (RPC.rpc"http://R_E_D_A_C_T_E_D/arena/log.php" x
^ "\n")
in
f [ ("player0", name0),
("player0rev", Int.toString rev0),
("player1", name1),
("player1rev", Int.toString rev1),
("rounds", Int.toString rounds),
("dead0", Int.toString dead0),
("dead1", Int.toString dead1),
("zomb0", Int.toString zomb0),
("zomb1", Int.toString zomb1),
("vit0", vit vit0 live0),
("vit1", vit vit1 live1) ]
end
| _ => ())
| _ => ()
; printquiet "Done.\n\n"
end handle Make.MakeFailed => print "BUILD FAILED. NOTHING CAN BE DONE.\n\n"
(* uses time as a substitute for randomness *)
fun pick n =
IntInf.toInt (Time.toNanoseconds (Time.now ())
mod IntInf.fromInt (valOf Int.maxInt))
mod n
(* Main function*)
(* args are the result of Params.docommandline *)
fun go args =
let
(* Handle special command-line parameters and flags *)
val () =
case Int.fromString (!flagPoll) of
NONE =>
(err ("Bad argument to --poll: " ^ !flagPoll ^ "!")
; usage OS.Process.failure)
| SOME 0 => flagFreq := max
| SOME i => flagFreq := i * 2
val () = if not (!flagHelp) then ()
else usage OS.Process.success
in
case (!flagMode, args) of
("duel", [ player0, player1 ]) => match player0 player1
| ("costress", [ player1 ]) =>
let
val contestants =
String.tokens Char.isSpace
(RPC.rpc "http://R_E_D_A_C_T_E_D/arena/contestants.php" [])
in
List.app (fn x => match x player1) contestants
end
| ("stress", [ player0 ]) =>
let
val contestants =
String.tokens Char.isSpace
(RPC.rpc "http://R_E_D_A_C_T_E_D/arena/contestants.php" [])
in
List.app (match player0) contestants
end
| ("auto", []) =>
let
fun tupleify str =
case String.tokens (fn x => x = #":") str of
[ a, b, c, d, e ] => (a, b, c, d, e)
| _ => (err "match.php!"; OS.Process.exit OS.Process.failure)
fun loop () =
let
(* Get candidates from RPC, filter for all the total noobs *)
val candidates =
map tupleify
(String.tokens Char.isSpace
(RPC.rpc "http://R_E_D_A_C_T_E_D/arena/match.php" []))
val zeroes = List.filter (fn x => "0" = #5 x) candidates
(* Pick a random zero, or else a random low number *)
val (p0, r0, p1, r1, _) =
if null zeroes
then List.nth (candidates, pick (length candidates))
else List.nth (zeroes, pick (length zeroes))
in
match (p0 ^ ":" ^ r0) (p1 ^ ":" ^ r1); loop ()
end
in
loop ()
end
| ("duel", _) =>
(err "Wrong number of arguments (duel)"; usage OS.Process.failure)
| ("stress", _) =>
(err "Wrong number of arguments (stress)"; usage OS.Process.success)
| ("auto", _) =>
(err "Wrong number of argumetns (auto)"; usage OS.Process.success)
| (mode, _) =>
(err ("Invalid mode '" ^ mode ^ "'"); usage OS.Process.success)
end handle LTGParse.LTGIO s => (err ("Error: " ^ s)
; OS.Process.exit OS.Process.failure)
val () = go (Params.docommandline ())
end