tcjudge 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. checksums.yaml +7 -0
  2. data/bin/tcjudge +9 -0
  3. data/lib/tcjudge.rb +1720 -0
  4. metadata +87 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: dc2d36b33659b10272c63772bd929c657abe22c9
4
+ data.tar.gz: 04c85a239341cde9f95a4dde793d294473732702
5
+ SHA512:
6
+ metadata.gz: b85e4e0e6eb3654e5691112bc7d21e22ca10666d55b6e97b1894758f30878d4b5b3e79277799509409d9903c41bf5f136895ff90b8bc89bfc8ef608875358d7f
7
+ data.tar.gz: 70fa8111894f313f5a3813fb2075b29401f66479695e73485ddf0ebc29241c03a72e803af23d38f55d7b4ea7c6dc13feccef8d0e7f402023cbb8391eacd5008a
data/bin/tcjudge ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'tcjudge'
4
+
5
+ # TopCoder Local Judge by peryaudo
6
+ # MIT License
7
+
8
+ tcjudge = TCJudge.new
9
+ tcjudge.start ARGV
data/lib/tcjudge.rb ADDED
@@ -0,0 +1,1720 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # TopCoder Local Judge by peryaudo
4
+ # MIT License
5
+
6
+ require 'io/console'
7
+ require 'tmpdir'
8
+ require 'mechanize'
9
+
10
+ class TopCoderScraper
11
+ def initialize(tmpdir)
12
+ @logged_in = false
13
+ @tmpdir = tmpdir
14
+ end
15
+
16
+ # return false if succeeded
17
+ def login(user_name, password)
18
+ if @logged_in
19
+ return false
20
+ end
21
+
22
+ @agent = Mechanize.new
23
+
24
+ next_page_uri = 'http://apps.topcoder.com/wiki/display/tc/Algorithm+Overview'
25
+ login_page_uri = 'http://community.topcoder.com/tc?&module=Login'
26
+
27
+ login_page = @agent.get(login_page_uri)
28
+
29
+ wiki_page = login_page.form_with(:name => 'frmLogin') do |f|
30
+ f.action = 'http://community.topcoder.com/tc'
31
+ f['nextpage'] = next_page_uri
32
+ f['username'] = user_name
33
+ f['password'] = password
34
+ end.click_button
35
+
36
+ if wiki_page.uri.to_s == next_page_uri
37
+ # succeeded
38
+ @logged_in = true
39
+ return false
40
+ else
41
+ return true
42
+ end
43
+
44
+ end
45
+
46
+ def is_cache_available(problem_name, required)
47
+ if required.include?(:problem_definition) && !File.exist?("#{ @tmpdir }/#{ problem_name }.bpd")
48
+ return false
49
+ end
50
+ if required.include?(:test_cases) && !File.exist?("#{ @tmpdir }/#{ problem_name }.btestcase")
51
+ return false
52
+ end
53
+ return true
54
+ end
55
+
56
+ def get_problem_definition(problem_name)
57
+ cache_file_name = "#{ @tmpdir }/#{ problem_name }.bpd"
58
+
59
+ root = nil
60
+ if File.exist?(cache_file_name)
61
+ return Marshal.restore(open(cache_file_name, 'rb'))
62
+
63
+ else
64
+ unless @logged_in
65
+ return nil
66
+ end
67
+
68
+ statement_uri = get_problem_page_uri(problem_name).sub('/tc?module=ProblemDetail', '/stat?c=problem_statement')
69
+ root = @agent.get(statement_uri).root
70
+
71
+ end
72
+
73
+ problem_definition = {}
74
+
75
+ problem_definition[:points] = @agent.get(get_problem_page_uri(problem_name)). \
76
+ root.xpath('//tr[contains(td/text(), "Point Value")]/td/text()').drop(1).map { |td| td.content.to_i }
77
+
78
+ table = root.xpath('//table[contains(tr/td/text(), "Class:")]').first
79
+
80
+ table.xpath('tr').each do |tr|
81
+ td = tr.xpath('td/text()').map { |td| td.content }
82
+ if td.length != 2
83
+ next
84
+ end
85
+
86
+ case td[0]
87
+ when 'Class:'
88
+ problem_definition[:class_name] = td[1]
89
+ when 'Method:'
90
+ problem_definition[:method_name] = td[1]
91
+ when 'Returns:'
92
+ problem_definition[:return_type] = td[1]
93
+ when 'Method signature:'
94
+ td[1].match(/\((.+)\)/) do |backward|
95
+ problem_definition[:parameters] = backward[1].split(',').map do |raw_parameter|
96
+ parameter = raw_parameter.strip.split(' ')
97
+ {:type => parameter[0], :name => parameter[1]}
98
+ end
99
+ end
100
+ end
101
+ end
102
+
103
+ unless File.exist?(cache_file_name)
104
+ Marshal.dump problem_definition, open(cache_file_name, 'wb')
105
+ end
106
+
107
+ return problem_definition
108
+ end
109
+
110
+ def get_test_cases(problem_name)
111
+ cache_file_name = "#{ @tmpdir }/#{ problem_name }.btestcase"
112
+
113
+ root = nil
114
+ if File.exist?(cache_file_name)
115
+ return Marshal.restore(open(cache_file_name, 'rb'))
116
+
117
+ else
118
+ unless @logged_in
119
+ return nil
120
+ end
121
+
122
+ solution_uri = get_solution_page_uri(get_problem_page_uri(problem_name))
123
+
124
+ root = @agent.get(solution_uri).root
125
+
126
+ end
127
+
128
+ test_cases = []
129
+
130
+ table = root.xpath('//comment()[contains(., "System Testing")]/following-sibling::table').first
131
+
132
+ table.xpath('tr[@valign="top"]').each do |tr|
133
+ test_case = tr.xpath('td[@class="statText"]/text()').map { |td| td.content.gsub(/(\r|\n)/, ' ') }
134
+ test_cases.push({:input => test_case[0], :output => test_case[1]})
135
+ end
136
+
137
+ unless File.exist?(cache_file_name)
138
+ Marshal.dump test_cases, open(cache_file_name, 'wb')
139
+ end
140
+ return test_cases
141
+ end
142
+
143
+ private
144
+ def get_problem_page_uri(problem_name)
145
+ unless @logged_in
146
+ return nil
147
+ end
148
+
149
+ unless @problem_page_uri_cache
150
+ @problem_page_uri_cache = {}
151
+ end
152
+
153
+ if @problem_page_uri_cache.include?(problem_name)
154
+ return @problem_page_uri_cache[problem_name]
155
+ end
156
+
157
+ @agent.get('http://community.topcoder.com/tc?module=ProblemArchive&class=' + problem_name) do |page|
158
+ return @problem_page_uri_cache[problem_name] =
159
+ page.root.xpath(
160
+ '//tr[normalize-space(td/a/text())="' + problem_name +
161
+ '"]/td/a[starts-with(@href, "/tc?module=ProblemDetail")]/@href').first.content
162
+ end
163
+ end
164
+
165
+ def get_solution_page_uri(problem_page_uri)
166
+ @agent.get(problem_page_uri) do |page|
167
+ return page.link_with(:href => /\/stat\?c\=problem_solution.+cr=[0-9]+/).href
168
+ end
169
+ end
170
+ end
171
+
172
+ CompileError = Class.new(StandardError)
173
+
174
+ class TopCoderTester
175
+ def generate_timestamp
176
+ return "// TIMESTAMP: #{ Time.now.to_i }"
177
+ end
178
+
179
+ def print_points(code)
180
+ code.match(/^\/\/ TIMESTAMP: ([0-9]+)$/) do |backward|
181
+ seconds = Time.now.to_i - backward[1].to_i
182
+ @problem_definition[:points].each_with_index do |total, i|
183
+ points = convert_seconds_to_points(seconds, total)
184
+ warn "Score: #{ sprintf('%.2f', points) } / #{ total } (#{ seconds } secs)"
185
+ end
186
+ end
187
+ return
188
+ end
189
+
190
+ def convert_seconds_to_points(seconds, mp)
191
+ tt = 60.0
192
+ pt = seconds / 60.0
193
+ return mp * (0.3 + (0.7 * tt ** 2) / (10.0 * pt ** 2 + tt **2))
194
+ end
195
+
196
+ def generate_signature()
197
+ return convert_type(@problem_definition[:return_type]) + ' ' + @problem_definition[:method_name] + '(' + \
198
+ (@problem_definition[:parameters].map { |parameter| convert_type(parameter[:type]) + ' ' + parameter[:name] }).join(', ') + ')'
199
+ end
200
+
201
+ # strictly parse comma separated parameters
202
+ def split_parameters(parameters_string)
203
+
204
+ depth = 0
205
+ prev = 0
206
+ start = false
207
+
208
+ parameters = []
209
+
210
+ parameters_string.length.times do |index|
211
+ case parameters_string[index]
212
+ when '"'
213
+ if start
214
+ depth += 1
215
+ else
216
+ depth -= 1
217
+ end
218
+ start = !start
219
+ when '{'
220
+ depth += 1
221
+ when '}'
222
+ depth -= 1
223
+ when ','
224
+ if depth == 0
225
+ cur = parameters_string[prev .. (index - 1)].strip
226
+ if !(cur.empty?)
227
+ parameters.push(cur)
228
+ end
229
+ prev = index + 1
230
+ end
231
+ end
232
+ end
233
+
234
+ cur = parameters_string[prev .. parameters_string.length - 1].strip
235
+ if !(cur.empty?)
236
+ parameters.push(cur)
237
+ end
238
+
239
+ return parameters
240
+ end
241
+
242
+ def perform_cut(code)
243
+ return code.split(/r?\n/).inject(['', true]) do |succ, cur|
244
+ if cur =~ /^\/\/ CUT begin/
245
+ [succ[0], false]
246
+ elsif cur =~ /^^\/\/ CUT end/
247
+ [succ[0], true]
248
+ else
249
+ if succ[1] == true
250
+ [succ[0] + "\n" + cur, succ[1]]
251
+ else
252
+ succ
253
+ end
254
+ end
255
+ end.first
256
+ end
257
+ end
258
+
259
+ class TopCoderCXXTester < TopCoderTester
260
+ def get_template(template_proc)
261
+ if template_proc.nil?
262
+ return <<EOS
263
+ #include <vector>
264
+ #include <string>
265
+ #include <map>
266
+ #include <set>
267
+ #include <algorithm>
268
+ #include <iostream>
269
+ #include <cmath>
270
+ #include <climits>
271
+ #include <queue>
272
+ #include <stack>
273
+ #include <numeric>
274
+
275
+ using namespace std;
276
+
277
+ // CUT begin
278
+ #{ generate_timestamp() }
279
+ // CUT end
280
+
281
+ class #{ @problem_definition[:class_name] } {
282
+ public:
283
+ #{ generate_signature() } {
284
+ }
285
+ };
286
+ EOS
287
+ else
288
+ return instance_eval(&template_proc)
289
+ end
290
+ end
291
+
292
+ def convert_type(type_name)
293
+ cxx_type_names = {'long' => 'long long', 'String' => 'string'}
294
+
295
+ is_array = false
296
+ base_type_name = type_name
297
+ if type_name =~ /\[\]$/
298
+ is_array = true
299
+ base_type_name = type_name.sub(/\[\]$/, '').strip
300
+ end
301
+
302
+ cxx_type_name = nil
303
+ if cxx_type_names.include?(base_type_name)
304
+ cxx_type_name = cxx_type_names[base_type_name]
305
+ else
306
+ cxx_type_name = base_type_name
307
+ end
308
+
309
+ if is_array
310
+ cxx_type_name = "vector<#{ cxx_type_name }>"
311
+ end
312
+
313
+ return cxx_type_name
314
+ end
315
+
316
+ def generate_tester()
317
+ tester = <<EOS
318
+ #include <iostream>
319
+ #include <vector>
320
+ #include <string>
321
+ #include <cstdlib>
322
+ #include <cmath>
323
+
324
+ using namespace std;
325
+
326
+ #{ generate_result_comparison_function() }
327
+ #{ generate_result_dumper_function() }
328
+ #{ generate_limitter_function() }
329
+
330
+ EOS
331
+
332
+ @test_cases.length.times do |index|
333
+ tester += generate_tester_function(index)
334
+ end
335
+
336
+
337
+ tester += <<EOS
338
+ int main(int argc, char *argv[])
339
+ {
340
+ setLimit();
341
+ switch(atoi(argv[1])) {
342
+ EOS
343
+
344
+ @test_cases.length.times do |index|
345
+ tester += " case #{ index }: return test#{ index }();\n"
346
+ end
347
+
348
+ tester += <<EOS
349
+ }
350
+ }
351
+ EOS
352
+
353
+ return tester
354
+ end
355
+
356
+ def generate_result_comparison_function
357
+ cxx_type = convert_type(@problem_definition[:return_type])
358
+
359
+ func = "bool compareResult(#{ cxx_type } from, #{ cxx_type } to)\n{\n"
360
+ case cxx_type
361
+ when 'double'
362
+ func += " return fabs(from - to) <= 1e-9;\n";
363
+ when 'vector<double>'
364
+ func += <<EOS
365
+ if (from.size() != to.size()) return false;
366
+ bool res = true;
367
+ for (int i = 0; i < from.size(); ++i) {
368
+ if (!(fabs(from[i] - to[i]) <= 1e-9)) {
369
+ res = false;
370
+ break;
371
+ }
372
+ }
373
+ return res;
374
+ EOS
375
+ else
376
+ func += " return from == to;\n"
377
+ end
378
+
379
+ func += "}\n"
380
+
381
+ return func
382
+ end
383
+
384
+ def generate_result_dumper_function
385
+ cxx_type = convert_type(@problem_definition[:return_type])
386
+
387
+ func = "void dumpResult(#{ cxx_type } result_)\n{\n"
388
+ if cxx_type =~ /^vector/
389
+ func += <<EOS
390
+ cout<<"{";
391
+ for (int i = 0; i < result_.size(); ++i) {
392
+ const #{cxx_type.sub(/^vector\<(.+)\>$/, '\\1')}& result = result_[i];
393
+ EOS
394
+ else
395
+ func += " const #{cxx_type.sub(/^vector\<(.+)\>$/, '\\1')}& result = result_;\n"
396
+ end
397
+
398
+ if cxx_type == 'string' || cxx_type == 'vector<string>'
399
+ func += ' cout<<"\""<<result<<"\"";'
400
+ func += "\n"
401
+ else
402
+ func += " cout<<result;\n"
403
+ end
404
+
405
+ if cxx_type =~ /^vector/
406
+ func += <<EOS
407
+ cout<<(i == result_.size() - 1 ? "" : ", ");
408
+ }
409
+ cout<<"}";
410
+ EOS
411
+ end
412
+
413
+ func += "}\n"
414
+
415
+ return func
416
+ end
417
+
418
+ def generate_limitter_function
419
+ return "void setLimit() {return;}\n"
420
+ end
421
+
422
+ def generate_tester_function(index)
423
+ return <<EOS
424
+ int test#{ index }(void)
425
+ {
426
+ #{ @problem_definition[:class_name] } target;
427
+ #{ generate_parameters(@test_cases[index][:input]) }
428
+ #{ generate_tester_call() }
429
+ #{ generate_parameter(@problem_definition[:return_type], "expected", @test_cases[index][:output]) }
430
+ if (compareResult(result, expected)) {
431
+ return 0;
432
+ } else {
433
+ dumpResult(result);
434
+ return 1;
435
+ }
436
+ }
437
+ EOS
438
+ end
439
+
440
+ def generate_parameters(parameters_string)
441
+ result = ''
442
+ parameters = split_parameters(parameters_string)
443
+
444
+ @problem_definition[:parameters].each_with_index do |parameter, index|
445
+ result += generate_parameter(parameter[:type], "param#{index}", parameters[index])
446
+ end
447
+
448
+ return result
449
+ end
450
+
451
+ def generate_primary_value(type, value)
452
+ case type
453
+ when 'String'
454
+ return "string(#{value})"
455
+ when 'long'
456
+ return "#{value}LL"
457
+ else
458
+ return value
459
+ end
460
+ end
461
+
462
+ def generate_parameter(type, name, value)
463
+ if type =~ /\[\]$/
464
+ # vector
465
+ element_type = type.sub(/\[\]$/, '')
466
+ elements = split_parameters(value[1, value.length - 2])
467
+ parameter = " #{ convert_type(type) } #{ name }(#{ elements.length });\n"
468
+ elements.each_with_index do |element, index|
469
+ parameter += " #{ name }[#{index}] = #{ generate_primary_value(element_type, element)};\n"
470
+ end
471
+ return parameter
472
+ else
473
+ return " #{ convert_type(type) } #{ name } = #{ generate_primary_value(type, value) };\n"
474
+ end
475
+ end
476
+
477
+ def generate_tester_call
478
+ return " #{ convert_type(@problem_definition[:return_type]) } result = target.#{ @problem_definition[:method_name] }(" +
479
+ (0 .. (@problem_definition[:parameters].length - 1)).map { |index| "param#{ index }" }.join(', ') + ");"
480
+ end
481
+
482
+ def initialize(problem_definition, test_cases = nil, tmpdir = nil, code = nil, compiler = nil)
483
+ @problem_definition = problem_definition
484
+
485
+ if test_cases != nil && tmpdir != nil && code != nil && compiler != nil
486
+ @test_cases = test_cases
487
+ @tmpdir = tmpdir
488
+
489
+ @tester_source_name = "#{@tmpdir}/tester.cpp"
490
+ @tester_name = "#{@tmpdir}/tester"
491
+
492
+ open(@tester_source_name, 'w').write "#{ perform_cut(code) }\n#{ generate_tester() }"
493
+
494
+ if File.basename(compiler, '.exe').downcase == 'cl' then
495
+ @compile_options = "#{compiler} /Fe\"#{@tester_name}\" \"#{@tester_source_name}\""
496
+ else
497
+ @compile_options = "#{compiler} \"#{@tester_source_name}\" -o \"#{@tester_name}\""
498
+ end
499
+
500
+ unless system(@compile_options)
501
+ raise CompileError
502
+ end
503
+ end
504
+ end
505
+
506
+ def run(index)
507
+ tester_options = "\"#{@tester_name}\" #{index}"
508
+ if system(tester_options)
509
+ return :AC
510
+ else
511
+ case $?.exitstatus
512
+ when 1
513
+ return :WA
514
+ else
515
+ if $?.exited?
516
+ return :UNKNOWN_ERROR
517
+ else
518
+ puts "\nCompiller Options: #{@compile_options}"
519
+ puts "Tester Options: #{tester_options}"
520
+ puts "Exit Status: #{$?}"
521
+ return :RUNTIME_ERROR
522
+ end
523
+ end
524
+ end
525
+ end
526
+ end
527
+
528
+ class TopCoderJavaTester < TopCoderTester
529
+ def get_template(template_proc)
530
+ if template_proc.nil?
531
+ return <<EOS
532
+ // CUT begin
533
+ #{ generate_timestamp() }
534
+ // CUT end
535
+ public class #{ @problem_definition[:class_name] } {
536
+ public #{ generate_signature() } {
537
+ }
538
+ }
539
+ EOS
540
+ else
541
+ return instance_eval(&template_proc)
542
+ end
543
+ end
544
+
545
+ def convert_type(type_name)
546
+ return type_name
547
+ end
548
+
549
+ def generate_tester()
550
+ tester = <<EOS
551
+ public class Tester {
552
+ #{ generate_result_comparison_function() }
553
+ #{ generate_result_dumper_function() }
554
+ EOS
555
+
556
+ @test_cases.length.times do |index|
557
+ tester += generate_tester_function(index)
558
+ end
559
+
560
+ tester += <<EOS
561
+ public static void main(String[] args) {
562
+ switch (Integer.parseInt(args[0])) {
563
+ EOS
564
+ @test_cases.length.times do |index|
565
+ tester += " case #{ index }: System.exit(test#{ index }());\n"
566
+ end
567
+ tester += <<EOS
568
+ }
569
+ return;
570
+ }
571
+ }
572
+ EOS
573
+
574
+ return tester
575
+ end
576
+
577
+ def generate_result_comparison_function
578
+ java_type = convert_type(@problem_definition[:return_type])
579
+
580
+ func = "static boolean compareResult(#{ java_type } from, #{ java_type } to)\n{\n"
581
+ case java_type
582
+ when 'double'
583
+ func += " return Math.abs(from - to) <= 1e-9;\n";
584
+ when 'double[]'
585
+ func += <<EOS
586
+ if (from.length != to.length) return false;
587
+ boolean res = true;
588
+ for (int i = 0; i < from.length; ++i) {
589
+ if (!(Math.abs(from[i] - to[i]) <= 1e-9)) {
590
+ res = false;
591
+ break;
592
+ }
593
+ }
594
+ return res;
595
+ EOS
596
+ else
597
+ if java_type =~ /\[\]$/
598
+ func += <<EOS
599
+ if (from.length != to.length) return false;
600
+ boolean res = true;
601
+ for (int i = 0; i < from.length; ++i) {
602
+ if (from[i] != to[i]) {
603
+ res = false;
604
+ break;
605
+ }
606
+ }
607
+ return res;
608
+ EOS
609
+ else
610
+ func += " return from == to;\n"
611
+ end
612
+ end
613
+
614
+ func += "}\n"
615
+
616
+ return func
617
+ end
618
+
619
+ def generate_result_dumper_function
620
+ java_type = convert_type(@problem_definition[:return_type])
621
+
622
+ func = "static void dumpResult(#{ java_type } result_)\n{\n"
623
+ if java_type =~ /\[\]$/
624
+ func += <<EOS
625
+ System.out.print("{");
626
+ for (int i = 0; i < result_.length; ++i) {
627
+ #{ java_type.sub('[]','') } result = result_[i];
628
+ EOS
629
+ else
630
+ func += " #{ java_type.sub('[]','') } result = result_;\n"
631
+ end
632
+
633
+ if java_type == 'string' || java_type == 'string[]'
634
+ func += ' System.out.print("\"" + result + "\"");'
635
+ func += "\n"
636
+ else
637
+ func += " System.out.print(String.valueOf(result));\n"
638
+ end
639
+
640
+ if java_type =~ /\[\]$/
641
+ func += <<EOS
642
+ System.out.print(i == result_.length - 1 ? "" : ", ");
643
+ }
644
+ System.out.print("}");
645
+ EOS
646
+ end
647
+
648
+ func += "}\n"
649
+
650
+ return func
651
+ end
652
+
653
+ def generate_tester_function(index)
654
+ return <<EOS
655
+ static int test#{ index }()
656
+ {
657
+ #{ @problem_definition[:class_name] } target = new #{ @problem_definition[:class_name] }();
658
+ #{ generate_parameters(@test_cases[index][:input]) }
659
+ #{ generate_tester_call() }
660
+ #{ generate_parameter(@problem_definition[:return_type], "expected", @test_cases[index][:output]) }
661
+ if (compareResult(result, expected)) {
662
+ return 0;
663
+ } else {
664
+ dumpResult(result);
665
+ return 1;
666
+ }
667
+ }
668
+ EOS
669
+ end
670
+
671
+ def generate_parameters(parameters_string)
672
+ result = ''
673
+ parameters = split_parameters(parameters_string)
674
+
675
+ @problem_definition[:parameters].each_with_index do |parameter, index|
676
+ result += generate_parameter(parameter[:type], "param#{index}", parameters[index])
677
+ end
678
+
679
+ return result
680
+ end
681
+
682
+ def generate_primary_value(type, value)
683
+ case type
684
+ when 'String'
685
+ return "#{value}"
686
+ when 'long'
687
+ return "#{value}L"
688
+ else
689
+ return value
690
+ end
691
+ end
692
+
693
+ def generate_parameter(type, name, value)
694
+ if type =~ /\[\]$/
695
+ # array
696
+ element_type = type.sub(/\[\]$/, '')
697
+ elements = split_parameters(value[1, value.length - 2])
698
+ parameter = " #{ convert_type(type) } #{ name } = { "
699
+ parameter += elements.map { |element| generate_primary_value(element_type, element) }.join(', ')
700
+ parameter += " };\n"
701
+ return parameter
702
+ else
703
+ return " #{ convert_type(type) } #{ name } = #{ generate_primary_value(type, value) };\n"
704
+ end
705
+ end
706
+
707
+ def generate_tester_call
708
+ return " #{ convert_type(@problem_definition[:return_type]) } result = target.#{ @problem_definition[:method_name] }(" +
709
+ (0 .. (@problem_definition[:parameters].length - 1)).map { |index| "param#{ index }" }.join(', ') + ");"
710
+ end
711
+
712
+ def initialize(problem_definition, test_cases = nil, tmpdir = nil, code = nil, compiler = nil)
713
+ @problem_definition = problem_definition
714
+
715
+ if test_cases != nil && tmpdir != nil && code != nil && compiler != nil
716
+ @test_cases = test_cases
717
+ @tmpdir = tmpdir
718
+
719
+ @tester_source_name = "#{@tmpdir}/Tester.java"
720
+ @solution_source_name = "#{@tmpdir}/#{@problem_definition[:class_name]}.java"
721
+
722
+ open(@tester_source_name, 'w') {|f| f.write generate_tester() }
723
+ open(@solution_source_name, 'w') {|f| f.write perform_cut(code) }
724
+
725
+ @compile_options = "#{compiler} #{@solution_source_name} #{@tester_source_name}"
726
+ puts @compile_options
727
+ unless system(@compile_options)
728
+ raise CompileError
729
+ end
730
+ end
731
+ end
732
+
733
+ def run(index)
734
+ tester_options = "java -classpath \"#{@tmpdir}\" Tester #{index}"
735
+ if system(tester_options)
736
+ return :AC
737
+ else
738
+ case $?.exitstatus
739
+ when 1
740
+ return :WA
741
+ else
742
+ if $?.exited?
743
+ return :UNKNOWN_ERROR
744
+ else
745
+ puts "\nCompiller Options: #{ @compile_options }"
746
+ puts "Tester Options: #{tester_options}"
747
+ puts "Exit Status: #{$?}"
748
+ return :RUNTIME_ERROR
749
+ end
750
+ end
751
+ end
752
+ end
753
+
754
+ end
755
+
756
+ class TopCoderCSharpTester < TopCoderTester
757
+ def get_template(template_proc)
758
+ if template_proc.nil?
759
+ return <<EOS
760
+ using System;
761
+ using System.Collections.Generic;
762
+ using System.Text;
763
+
764
+ // CUT begin
765
+ #{ generate_timestamp() }
766
+ // CUT end
767
+
768
+ public class #{ @problem_definition[:class_name] }
769
+ {
770
+ public #{ generate_signature() }
771
+ {
772
+ }
773
+ }
774
+ EOS
775
+ else
776
+ return instance_eval(&template_proc)
777
+ end
778
+ end
779
+
780
+ def convert_type(type_name)
781
+ return type_name.sub('String', 'string')
782
+ end
783
+
784
+ def generate_tester()
785
+ tester = <<EOS
786
+ public class Tester {
787
+ #{ generate_result_comparison_function() }
788
+ #{ generate_result_dumper_function() }
789
+ EOS
790
+
791
+ @test_cases.length.times do |index|
792
+ tester += generate_tester_function(index)
793
+ end
794
+
795
+ tester += <<EOS
796
+ static int Main(string[] args) {
797
+ switch (int.Parse(args[0])) {
798
+ EOS
799
+ @test_cases.length.times do |index|
800
+ tester += " case #{ index }: return test#{ index }();\n"
801
+ end
802
+ tester += <<EOS
803
+ }
804
+ return 0;
805
+ }
806
+ }
807
+ EOS
808
+
809
+ return tester
810
+ end
811
+
812
+ def generate_result_comparison_function
813
+ cs_type = convert_type(@problem_definition[:return_type])
814
+
815
+ func = "static bool compareResult(#{ cs_type } from, #{ cs_type } to)\n{\n"
816
+ case cs_type
817
+ when 'double'
818
+ func += " return Math.Abs(from - to) <= 1e-9;\n";
819
+ when 'double[]'
820
+ func += <<EOS
821
+ if (from.Length != to.Length) return false;
822
+ bool res = true;
823
+ for (int i = 0; i < from.Length; ++i) {
824
+ if (!(Math.abs(from[i] - to[i]) <= 1e-9)) {
825
+ res = false;
826
+ break;
827
+ }
828
+ }
829
+ return res;
830
+ EOS
831
+ else
832
+ if cs_type =~ /\[\]$/
833
+ func += <<EOS
834
+ if (from.Length != to.Length) return false;
835
+ bool res = true;
836
+ for (int i = 0; i < from.Length; ++i) {
837
+ if (from[i] != to[i]) {
838
+ res = false;
839
+ break;
840
+ }
841
+ }
842
+ return res;
843
+ EOS
844
+ else
845
+ func += " return from == to;\n"
846
+ end
847
+ end
848
+
849
+ func += "}\n"
850
+
851
+ return func
852
+ end
853
+
854
+ def generate_result_dumper_function
855
+ cs_type = convert_type(@problem_definition[:return_type])
856
+
857
+ func = "static void dumpResult(#{ cs_type } result_)\n{\n"
858
+ if cs_type =~ /\[\]$/
859
+ func += <<EOS
860
+ Console.Write("{");
861
+ for (int i = 0; i < result_.length; ++i) {
862
+ #{ cs_type.sub('[]','') } result = result_[i];
863
+ EOS
864
+ else
865
+ func += " #{ cs_type.sub('[]','') } result = result_;\n"
866
+ end
867
+
868
+ if cs_type == 'string' || cs_type == 'string[]'
869
+ func += ' Console.Write("\"" + result + "\"");'
870
+ func += "\n"
871
+ else
872
+ func += " Console.Write(result.ToString());\n"
873
+ end
874
+
875
+ if cs_type =~ /\[\]$/
876
+ func += <<EOS
877
+ System.out.print(i == result_.length - 1 ? "" : ", ");
878
+ }
879
+ System.out.print("}");
880
+ EOS
881
+ end
882
+
883
+ func += "}\n"
884
+
885
+ return func
886
+ end
887
+
888
+ def generate_tester_function(index)
889
+ return <<EOS
890
+ static int test#{ index }()
891
+ {
892
+ #{ @problem_definition[:class_name] } target = new #{ @problem_definition[:class_name] }();
893
+ #{ generate_parameters(@test_cases[index][:input]) }
894
+ #{ generate_tester_call() }
895
+ #{ generate_parameter(@problem_definition[:return_type], "expected", @test_cases[index][:output]) }
896
+ if (compareResult(result, expected)) {
897
+ return 0;
898
+ } else {
899
+ dumpResult(result);
900
+ return 1;
901
+ }
902
+ }
903
+ EOS
904
+ end
905
+
906
+ def generate_parameters(parameters_string)
907
+ result = ''
908
+ parameters = split_parameters(parameters_string)
909
+
910
+ @problem_definition[:parameters].each_with_index do |parameter, index|
911
+ result += generate_parameter(parameter[:type], "param#{index}", parameters[index])
912
+ end
913
+
914
+ return result
915
+ end
916
+
917
+ def generate_primary_value(type, value)
918
+ case type
919
+ when 'String'
920
+ return "#{value}"
921
+ when 'long'
922
+ return "#{value}L"
923
+ else
924
+ return value
925
+ end
926
+ end
927
+
928
+ def generate_parameter(type, name, value)
929
+ if type =~ /\[\]$/
930
+ # array
931
+ element_type = type.sub(/\[\]$/, '')
932
+ elements = split_parameters(value[1, value.length - 2])
933
+ parameter = " #{ convert_type(type) } #{ name } = new #{ convert_type(type) } { "
934
+ parameter += elements.map { |element| generate_primary_value(element_type, element) }.join(', ')
935
+ parameter += " };\n"
936
+ return parameter
937
+ else
938
+ return " #{ convert_type(type) } #{ name } = #{ generate_primary_value(type, value) };\n"
939
+ end
940
+ end
941
+
942
+ def generate_tester_call
943
+ return " #{ convert_type(@problem_definition[:return_type]) } result = target.#{ @problem_definition[:method_name] }(" +
944
+ (0 .. (@problem_definition[:parameters].length - 1)).map { |index| "param#{ index }" }.join(', ') + ");"
945
+ end
946
+
947
+ def initialize(problem_definition, test_cases = nil, tmpdir = nil, code = nil, compiler = nil)
948
+ @problem_definition = problem_definition
949
+
950
+ if test_cases != nil && tmpdir != nil && code != nil && compiler != nil
951
+ @test_cases = test_cases
952
+ @tmpdir = tmpdir
953
+
954
+ @tester_source_name = "#{@tmpdir}/tester.cs"
955
+ @tester_name = "#{@tmpdir}/tester.exe"
956
+
957
+ open(@tester_source_name, 'w') {|f| f.write perform_cut(code) + generate_tester() }
958
+
959
+ @compiler = compiler
960
+ if File.basename(compiler, '.exe').downcase == 'csc'
961
+ @compile_options = "#{compiler} /r:System.Numerics.dll #{@tester_source_name} /out:\"#{@tester_name}\""
962
+ else
963
+ @compile_options = "#{compiler} /r:System.Numerics.dll #{@tester_source_name} -out:\"#{@tester_name}\""
964
+ end
965
+ puts @compile_options
966
+ unless system(@compile_options)
967
+ raise CompileError
968
+ end
969
+ end
970
+ end
971
+
972
+ def run(index)
973
+ tester_options = nil
974
+ if File.basename(@compiler, '.exe').downcase == 'csc'
975
+ tester_options = "\"#{@tester_name}\" #{index}"
976
+ else
977
+ tester_options = "mono \"#{@tester_name}\" #{index}"
978
+ end
979
+ if system(tester_options)
980
+ return :AC
981
+ else
982
+ case $?.exitstatus
983
+ when 1
984
+ return :WA
985
+ else
986
+ if $?.exited?
987
+ return :UNKNOWN_ERROR
988
+ else
989
+ puts "\nCompiller Options: #{ @compile_options }"
990
+ puts "Tester Options: #{tester_options}"
991
+ puts "Exit Status: #{$?}"
992
+ return :RUNTIME_ERROR
993
+ end
994
+ end
995
+ end
996
+ end
997
+
998
+ end
999
+
1000
+ class TopCoderHaskellTester < TopCoderTester
1001
+ def generate_timestamp
1002
+ return "-- TIMESTAMP: #{ Time.now.to_i }"
1003
+ end
1004
+
1005
+ def print_points(code)
1006
+ code.match(/^-- TIMESTAMP: ([0-9]+)$/) do |backward|
1007
+ seconds = Time.now.to_i - backward[1].to_i
1008
+ @problem_definition[:points].each_with_index do |total, i|
1009
+ points = convert_seconds_to_points(seconds, total)
1010
+ warn "Score: #{ sprintf('%.2f', points) } / #{ total } (#{ seconds } secs)"
1011
+ end
1012
+ end
1013
+ return
1014
+ end
1015
+
1016
+ def generate_signature()
1017
+ return @problem_definition[:method_name] + ' :: ' + \
1018
+ (@problem_definition[:parameters].map { |parameter| convert_type(parameter[:type]) }).join(' -> ') + ' -> ' + convert_type(@problem_definition[:return_type])
1019
+ end
1020
+
1021
+
1022
+ def get_template(template_proc)
1023
+ if template_proc.nil?
1024
+ return <<EOS
1025
+ -- CUT begin
1026
+ #{ generate_timestamp() }
1027
+ -- CUT end
1028
+
1029
+ #{ generate_signature() }
1030
+ #{ @problem_definition[:method_name] } #{ (@problem_definition[:parameters].map { |parameter| parameter[:name] }).join(' ') } =
1031
+ EOS
1032
+ else
1033
+ return instance_eval(&template_proc)
1034
+ end
1035
+ end
1036
+
1037
+ def convert_type(type_name)
1038
+ haskell_type_names = {'int' => 'Int', 'long' => 'Int', 'double' => 'Double', 'String' => 'string'}
1039
+
1040
+ is_array = false
1041
+ base_type_name = type_name
1042
+ if type_name =~ /\[\]$/
1043
+ is_array = true
1044
+ base_type_name = type_name.sub(/\[\]$/, '').strip
1045
+ end
1046
+
1047
+ haskell_type_name = nil
1048
+ if haskell_type_names.include?(base_type_name)
1049
+ haskell_type_name = haskell_type_names[base_type_name]
1050
+ else
1051
+ haskell_type_name = base_type_name
1052
+ end
1053
+
1054
+ if is_array
1055
+ haskell_type_name = "[#{ haskell_type_name }]"
1056
+ end
1057
+
1058
+ return haskell_type_name
1059
+ end
1060
+
1061
+ def generate_tester()
1062
+ tester = <<EOS
1063
+ #{ generate_result_comparison_function() }
1064
+ #{ generate_result_dumper_function() }
1065
+
1066
+ EOS
1067
+
1068
+ @test_cases.length.times do |index|
1069
+ tester += generate_tester_function(index)
1070
+ end
1071
+
1072
+
1073
+ tester += <<EOS
1074
+
1075
+ myExitWith 0 = exitWith (ExitSuccess)
1076
+ myExitWith x = exitWith (ExitFailure x)
1077
+
1078
+ main = do args <- getArgs
1079
+ res <- test (read (head args))
1080
+ myExitWith res
1081
+ EOS
1082
+
1083
+ return tester
1084
+ end
1085
+
1086
+ def generate_result_comparison_function
1087
+ haskell_type = convert_type(@problem_definition[:return_type])
1088
+
1089
+ func = nil
1090
+
1091
+ case haskell_type
1092
+ when 'Double'
1093
+ func = "compareResult from to = abs (from - to) <= 1e-9\n";
1094
+ when '[Double]'
1095
+ func = <<EOS
1096
+ compareResult (x:xs) (y:ys) = if abs (from - to) <= 1e-9 then (compareResult xs ys) else False
1097
+ compareResult [] [] = True
1098
+ compareResult _ _ = False
1099
+ EOS
1100
+ else
1101
+ func = "compareResult from to = from == to\n"
1102
+ end
1103
+
1104
+ return func
1105
+ end
1106
+
1107
+ def generate_result_dumper_function
1108
+ return "dumpResult = print"
1109
+ end
1110
+
1111
+ def generate_tester_function(index)
1112
+ return <<EOS
1113
+ test #{ index } = do
1114
+ #{ generate_parameters(@test_cases[index][:input]) }
1115
+ #{ generate_tester_call() }
1116
+ #{ generate_parameter(@problem_definition[:return_type], "expected", @test_cases[index][:output]) }
1117
+ if (compareResult result expected) then
1118
+ return 0
1119
+ else
1120
+ (dumpResult result) >> (return 1)
1121
+
1122
+ EOS
1123
+ end
1124
+
1125
+ def generate_parameters(parameters_string)
1126
+ result = ''
1127
+ parameters = split_parameters(parameters_string)
1128
+
1129
+ @problem_definition[:parameters].each_with_index do |parameter, index|
1130
+ result += generate_parameter(parameter[:type], "param#{index}", parameters[index])
1131
+ end
1132
+
1133
+ return result
1134
+ end
1135
+
1136
+ def generate_parameter(type, name, value)
1137
+ if type =~ /\[\]$/
1138
+ elements = split_parameters(value[1, value.length - 2])
1139
+ return " let #{name} = [" + elements.join(', ') + "]\n"
1140
+ else
1141
+ return " let #{name} = #{ value }\n"
1142
+ end
1143
+ end
1144
+
1145
+ def generate_tester_call
1146
+ return " let result = #{ @problem_definition[:method_name] } " +
1147
+ (0 .. (@problem_definition[:parameters].length - 1)).map { |index| "param#{ index }" }.join(' ')
1148
+ end
1149
+
1150
+ def initialize(problem_definition, test_cases = nil, tmpdir = nil, code = nil, compiler = nil)
1151
+ @problem_definition = problem_definition
1152
+
1153
+ if test_cases != nil && tmpdir != nil && code != nil && compiler != nil
1154
+ @test_cases = test_cases
1155
+ @tmpdir = tmpdir
1156
+
1157
+ @tester_source_name = "#{@tmpdir}/tester.hs"
1158
+ @tester_name = "#{@tmpdir}/tester"
1159
+
1160
+ open(@tester_source_name, 'w').write <<EOS
1161
+ import System.Environment
1162
+ import System.Exit
1163
+
1164
+ #{ perform_cut(code) }
1165
+ #{ generate_tester() }
1166
+ EOS
1167
+
1168
+ @compile_options = "#{compiler} \"#{@tester_source_name}\" -o \"#{@tester_name}\""
1169
+
1170
+ unless system(@compile_options)
1171
+ raise CompileError
1172
+ end
1173
+ end
1174
+ end
1175
+
1176
+ def run(index)
1177
+ tester_options = "\"#{@tester_name}\" #{index}"
1178
+ if system(tester_options)
1179
+ return :AC
1180
+ else
1181
+ case $?.exitstatus
1182
+ when 1
1183
+ return :WA
1184
+ else
1185
+ if $?.exited?
1186
+ return :UNKNOWN_ERROR
1187
+ else
1188
+ puts "\nCompiller Options: #{@compile_options}"
1189
+ puts "Tester Options: #{tester_options}"
1190
+ puts "Exit Status: #{$?}"
1191
+ return :RUNTIME_ERROR
1192
+ end
1193
+ end
1194
+ end
1195
+ end
1196
+
1197
+ def perform_cut(code)
1198
+ return code.split(/r?\n/).inject(['', true]) do |succ, cur|
1199
+ if cur =~ /^-- CUT begin/
1200
+ [succ[0], false]
1201
+ elsif cur =~ /^-- CUT end/
1202
+ [succ[0], true]
1203
+ else
1204
+ if succ[1] == true
1205
+ [succ[0] + "\n" + cur, succ[1]]
1206
+ else
1207
+ succ
1208
+ end
1209
+ end
1210
+ end.first
1211
+ end
1212
+ end
1213
+
1214
+ class TopCoderPythonTester < TopCoderTester
1215
+ def generate_timestamp
1216
+ return "# TIMESTAMP: #{ Time.now.to_i }"
1217
+ end
1218
+
1219
+ def print_points(code)
1220
+ code.match(/^# TIMESTAMP: ([0-9]+)$/) do |backward|
1221
+ seconds = Time.now.to_i - backward[1].to_i
1222
+ @problem_definition[:points].each_with_index do |total, i|
1223
+ points = convert_seconds_to_points(seconds, total)
1224
+ warn "Score: #{ sprintf('%.2f', points) } / #{ total } (#{ seconds } secs)"
1225
+ end
1226
+ end
1227
+ return
1228
+ end
1229
+
1230
+ def generate_signature()
1231
+ return @problem_definition[:method_name] + '(self, ' + \
1232
+ (@problem_definition[:parameters].map { |parameter| parameter[:name] }).join(', ') + ')'
1233
+ end
1234
+
1235
+
1236
+ def get_template(template_proc)
1237
+ if template_proc.nil?
1238
+ return <<EOS
1239
+ # -*- coding: utf-8 -*-
1240
+
1241
+ import math,string,itertools,fractions,heapq,collections,re,array,bisect
1242
+
1243
+ # CUT begin
1244
+ #{ generate_timestamp() }
1245
+ # CUT end
1246
+
1247
+ class #{ @problem_definition[:class_name] }:
1248
+ def #{ generate_signature() }:
1249
+
1250
+ EOS
1251
+ else
1252
+ return instance_eval(&template_proc)
1253
+ end
1254
+ end
1255
+
1256
+ def convert_type(type_name)
1257
+ return type_name
1258
+ end
1259
+
1260
+ def generate_tester()
1261
+ tester = <<EOS
1262
+ class Tester:
1263
+ #{ generate_result_comparison_function() }
1264
+ #{ generate_result_dumper_function() }
1265
+ EOS
1266
+
1267
+ @test_cases.length.times do |index|
1268
+ tester += generate_tester_function(index)
1269
+ end
1270
+
1271
+ tester += <<EOS
1272
+ import sys
1273
+
1274
+ if __name__ == '__main__':
1275
+ tester = Tester()
1276
+ sys.exit(eval('tester.test' + sys.argv[1] + '()'))
1277
+ EOS
1278
+
1279
+ return tester
1280
+ end
1281
+
1282
+ def generate_result_comparison_function
1283
+ func = <<EOS
1284
+ def compareResult(self, from_, to_):
1285
+ EOS
1286
+ case @problem_definition[:return_type]
1287
+ when 'double'
1288
+ func += <<EOS
1289
+ return abs(from_ - to_) <= 1e-9
1290
+ EOS
1291
+ when 'double[]'
1292
+ func += <<EOS
1293
+ return reduce(lambda x, y: x && (abs(y[0] - y[1]) <= 1e-9), zip(from_, to_), True)
1294
+ EOS
1295
+ else
1296
+ func += <<EOS
1297
+ return from_ == to_
1298
+ EOS
1299
+ end
1300
+
1301
+ return func;
1302
+ end
1303
+
1304
+ def generate_result_dumper_function
1305
+ return " def dumpResult(self, result_):\n print result_\n"
1306
+ end
1307
+
1308
+ def generate_tester_function(index)
1309
+ return <<EOS
1310
+ def test#{index}(self):
1311
+ target = #{ @problem_definition[:class_name] }();
1312
+ #{ generate_parameters(@test_cases[index][:input]) }
1313
+ #{ generate_tester_call() }
1314
+ #{ generate_parameter(@problem_definition[:return_type], "expected", @test_cases[index][:output]) }
1315
+ if self.compareResult(result, expected):
1316
+ return 0
1317
+ else:
1318
+ self.dumpResult(result)
1319
+ return 1
1320
+
1321
+ EOS
1322
+ end
1323
+
1324
+ def generate_parameters(parameters_string)
1325
+ result = ''
1326
+ parameters = split_parameters(parameters_string)
1327
+
1328
+ @problem_definition[:parameters].each_with_index do |parameter, index|
1329
+ result += generate_parameter(parameter[:type], "param#{index}", parameters[index])
1330
+ end
1331
+
1332
+ return result
1333
+ end
1334
+
1335
+ def generate_primary_value(type, value)
1336
+ case type
1337
+ when 'String'
1338
+ return "#{value}"
1339
+ when 'long'
1340
+ return "#{value}L"
1341
+ else
1342
+ return value
1343
+ end
1344
+ end
1345
+
1346
+ def generate_parameter(type, name, value)
1347
+ if type =~ /\[\]$/
1348
+ # array
1349
+ element_type = type.sub(/\[\]$/, '')
1350
+ elements = split_parameters(value[1, value.length - 2])
1351
+ parameter = " #{ name } = [ "
1352
+ parameter += elements.map { |element| generate_primary_value(element_type, element) }.join(', ')
1353
+ parameter += " ]\n"
1354
+ return parameter
1355
+ else
1356
+ return " #{ name } = #{ generate_primary_value(type, value) };\n"
1357
+ end
1358
+ end
1359
+
1360
+ def generate_tester_call
1361
+ return " result = target.#{ @problem_definition[:method_name] }(" +
1362
+ (0 .. (@problem_definition[:parameters].length - 1)).map { |index| "param#{ index }" }.join(', ') + ");"
1363
+ end
1364
+
1365
+ def initialize(problem_definition, test_cases = nil, tmpdir = nil, code = nil, interpreter = nil)
1366
+ @problem_definition = problem_definition
1367
+
1368
+ if test_cases != nil && tmpdir != nil && code != nil && interpreter != nil
1369
+ @test_cases = test_cases
1370
+ @tmpdir = tmpdir
1371
+
1372
+ @tester_source_name = "#{@tmpdir}/tester.py"
1373
+
1374
+ open(@tester_source_name, 'w').write "#{ perform_cut(code) }\n#{ generate_tester() }"
1375
+
1376
+ @interpreter = interpreter
1377
+ end
1378
+ end
1379
+
1380
+ def run(index)
1381
+ tester_options = "#{@interpreter} #{@tester_source_name} #{index}"
1382
+ if system(tester_options)
1383
+ return :AC
1384
+ else
1385
+ case $?.exitstatus
1386
+ when 1
1387
+ return :WA
1388
+ else
1389
+ if $?.exited?
1390
+ return :UNKNOWN_ERROR
1391
+ else
1392
+ puts "\nCompiller Options: #{ @compile_options }"
1393
+ puts "Tester Options: #{tester_options}"
1394
+ puts "Exit Status: #{$?}"
1395
+ return :RUNTIME_ERROR
1396
+ end
1397
+ end
1398
+ end
1399
+ end
1400
+
1401
+ end
1402
+
1403
+ class TCJudge
1404
+ def initialize
1405
+ @languages = { :CXX => TopCoderCXXTester, :Java => TopCoderJavaTester,
1406
+ :CSharp => TopCoderCSharpTester, :VB => nil,
1407
+ :Python => TopCoderPythonTester, :Haskell => TopCoderHaskellTester }
1408
+ @default_compilers = { :CXX => 'g++', :Java => 'javac', :CSharp => 'mcs', :VB => 'vbc', :Python => 'python', :Haskell => 'ghc' }
1409
+ @extensions = { '.cpp' => :CXX, '.cc' => :CXX, '.cxx' => :CXX,
1410
+ '.java' => :Java, '.cs' => :CSharp, '.vb' => :VB , '.py' => :Python, '.hs' => :Haskell }
1411
+
1412
+ tcjudegrc_file_name = File.expand_path('~/.tcjudgerc')
1413
+ if File.exist?(tcjudegrc_file_name)
1414
+ instance_eval File.read(tcjudegrc_file_name)
1415
+ end
1416
+ end
1417
+
1418
+ def start(argv)
1419
+ warn 'TopCoder Local Judge by peryaudo'
1420
+
1421
+ filenames = []
1422
+ options = []
1423
+
1424
+ argv.each do |arg|
1425
+ if arg =~ /^--/
1426
+ options.push arg
1427
+ else
1428
+ filenames.push arg
1429
+ end
1430
+ end
1431
+
1432
+ if filenames.length != 1 && filenames.length != 2
1433
+ warn 'usage: tcjudge MyProblemSolution.cpp'
1434
+ warn " tcjudge [command] MyProblemSolution.cpp\n"
1435
+ warn 'commands: create - create template for the problem'
1436
+ warn ' judge - judge the file'
1437
+ warn ' clean - output the cleaned code to stdout'
1438
+ return
1439
+ end
1440
+
1441
+ command = nil
1442
+ filename = nil
1443
+
1444
+ if filenames.length == 1
1445
+ if filenames[0] == 'solved' then
1446
+ command = 'solved'
1447
+ else
1448
+ command = 'judge'
1449
+ filename = filenames[0]
1450
+ end
1451
+ elsif filenames.length == 2
1452
+ command = filenames[0]
1453
+ filename = filenames[1]
1454
+ end
1455
+
1456
+ case command
1457
+ when 'create'
1458
+ write_template filename
1459
+ when 'judge'
1460
+ judge filename, options.include?('--force')
1461
+ when 'clean'
1462
+ clean filename
1463
+ warn 'tcjudge: wrote the cleaned code to stdout'
1464
+ when 'solved'
1465
+ show_solved
1466
+ else
1467
+ warn 'tcjudge: invalid command line option'
1468
+ end
1469
+ end
1470
+
1471
+ def write_template(file_name)
1472
+ if File.exist?(file_name)
1473
+ warn "tcjudge: file #{ file_name } already exists."
1474
+ return
1475
+ end
1476
+
1477
+ extension = File.extname(file_name)
1478
+ unless @extensions.include?(extension)
1479
+ warn 'tcjudge: cannot detect the language'
1480
+ return
1481
+ end
1482
+
1483
+ language = @languages[@extensions[extension]]
1484
+
1485
+ if language.nil?
1486
+ warn 'tcjudge: the language is not supported yet'
1487
+ return
1488
+ end
1489
+
1490
+ problem_name = File.basename(file_name, extension)
1491
+
1492
+ scraper = TopCoderScraper.new(Dir.tmpdir)
1493
+ unless scraper.is_cache_available(problem_name, [:problem_definition])
1494
+ if login(scraper)
1495
+ return
1496
+ end
1497
+ end
1498
+
1499
+ $stderr.print 'Obtaining problem definition...'
1500
+ problem_definition = nil
1501
+ begin
1502
+ problem_definition = scraper.get_problem_definition(problem_name)
1503
+ rescue
1504
+ warn "\ntcjudge: couldn't retrive the problem definition of #{ problem_name }."
1505
+ return
1506
+ end
1507
+
1508
+ warn 'ok.'
1509
+
1510
+ $stderr.print 'Writing template...'
1511
+
1512
+ open file_name, 'w' do |destination|
1513
+ @template ||= {}
1514
+ @template[@extensions[extension]] ||= nil
1515
+ destination.write language.new(problem_definition).get_template(@template[@extensions[extension]])
1516
+ warn 'ok.'
1517
+ end
1518
+ end
1519
+
1520
+ def clean(file_name)
1521
+ extension = File.extname(file_name)
1522
+ unless @extensions.include?(extension)
1523
+ warn 'tcjudge: cannot detect the language'
1524
+ return
1525
+ end
1526
+
1527
+ language = @languages[@extensions[extension]]
1528
+
1529
+ if language.nil?
1530
+ warn 'tcjudge: the language is not supported yet'
1531
+ return
1532
+ end
1533
+
1534
+ print language.new(nil).perform_cut(open(file_name, 'r').read)
1535
+ return
1536
+ end
1537
+
1538
+ def judge(file_name, force = false)
1539
+ code = nil
1540
+ begin
1541
+ open file_name, 'r' do |file|
1542
+ code = file.read
1543
+ end
1544
+ rescue
1545
+ warn "tcjudge: file #{ file_name } couldn't be opened or doesn't exist."
1546
+ return
1547
+ end
1548
+
1549
+ extension = File.extname(file_name)
1550
+ unless @extensions.include?(extension)
1551
+ warn 'tcjudge: cannot detect the language'
1552
+ return
1553
+ end
1554
+
1555
+ language = @languages[@extensions[extension]]
1556
+
1557
+ if language.nil?
1558
+ warn 'tcjudge: the language is not supported yet'
1559
+ return
1560
+ end
1561
+
1562
+ problem_name = File.basename(file_name, extension)
1563
+
1564
+ scraper = TopCoderScraper.new(Dir.tmpdir)
1565
+ unless scraper.is_cache_available(problem_name, [:problem_definition, :test_cases])
1566
+ if login(scraper)
1567
+ return
1568
+ end
1569
+ end
1570
+
1571
+ $stderr.print 'Obtaining problem definition...'
1572
+ problem_definition = scraper.get_problem_definition(problem_name)
1573
+ warn 'ok.'
1574
+
1575
+ $stderr.print 'Obtaining testcases...'
1576
+ test_cases = scraper.get_test_cases(problem_name)
1577
+ warn " total #{ test_cases.length } cases."
1578
+
1579
+ tester = nil
1580
+
1581
+ begin
1582
+ @compiler ||= {}
1583
+ @compiler[@extensions[extension]] ||= @default_compilers[@extensions[extension]]
1584
+ tester = language.new(problem_definition, test_cases, Dir.tmpdir, code, @compiler[@extensions[extension]])
1585
+ rescue CompileError
1586
+ warn 'tcjudge: compile error'
1587
+ return
1588
+ end
1589
+
1590
+ succeeded = true
1591
+
1592
+ test_cases.length.times do |index|
1593
+ current_succeeded = true
1594
+
1595
+ print "Test Case #{ index } ..."
1596
+ result = tester.run(index)
1597
+ case result
1598
+ when :AC
1599
+ puts 'Accepted.'
1600
+ when :WA
1601
+ puts 'WRONG ANSWER.'
1602
+ current_succeeded = false
1603
+ when :TLE
1604
+ puts 'TIME LIMIT EXCEEDED.'
1605
+ current_succeeded = false
1606
+ when :MLE
1607
+ puts 'MEMORY LIMIT EXCEEDED.'
1608
+ current_succeeded = false
1609
+ when :RUNTIME_ERROR
1610
+ puts 'RUNTIME ERROR.'
1611
+ current_succeeded = false
1612
+ else
1613
+ puts 'UNKNOWN ERROR.'
1614
+ current_succeeded = false
1615
+ end
1616
+
1617
+ unless current_succeeded
1618
+ succeeded = false
1619
+
1620
+ puts "Test Case: #{ test_cases[index][:input] }"
1621
+ puts "Expected Output: #{ test_cases[index][:output] }"
1622
+ if !force
1623
+ break
1624
+ end
1625
+ end
1626
+ end
1627
+
1628
+ if succeeded
1629
+ warn 'All Tests Succeeded.'
1630
+ else
1631
+ warn 'Testing Failed.'
1632
+ end
1633
+
1634
+ tester.print_points(code)
1635
+
1636
+ return
1637
+ end
1638
+
1639
+ # return false if succeeded
1640
+ def login(scraper)
1641
+ @user_name ||= nil
1642
+ if @user_name.nil?
1643
+ $stderr.print 'User Name: '
1644
+ @user_name = $stdin.gets().strip
1645
+ end
1646
+
1647
+ @password ||= nil
1648
+ if @password.nil?
1649
+ $stderr.print 'Password: '
1650
+ @password = $stdin.noecho(&:gets).strip
1651
+ end
1652
+
1653
+ unless scraper.login(@user_name, @password)
1654
+ warn "\ntcjudge: login succeeded"
1655
+ return false
1656
+ else
1657
+ warn "\ntcjudge: login failed"
1658
+ return true
1659
+ end
1660
+ end
1661
+
1662
+ def show_solved
1663
+ agent = Mechanize.new
1664
+
1665
+ template = {}
1666
+ @diary[:difficulties].each do |difficulty|
1667
+ template[difficulty] = false
1668
+ end
1669
+
1670
+ solved = {}
1671
+ (@diary[:from_srm]..@diary[:to_srm]).each do |i|
1672
+ solved["SRM#{i}"] = template.dup
1673
+ end
1674
+
1675
+ month = @diary[:from_month]
1676
+ while month <= @diary[:to_month]
1677
+ page = agent.get("#{@diary[:url]}archive/#{month.strftime('%Y%m')}")
1678
+
1679
+ lis = page.root.xpath('//li[@class="archive archive-section"]')
1680
+ titles = lis.map { |li| li.content }
1681
+
1682
+ titles.each do |title|
1683
+ tags = title.scan(/\[([0-9a-zA-Z]+)\]/).flatten
1684
+ srm_idx = nil
1685
+ probs = []
1686
+ tags.each do |tag|
1687
+ if tag =~ /^SRM/ then
1688
+ srm_idx = tag
1689
+ else
1690
+ probs.push tag
1691
+ end
1692
+ end
1693
+
1694
+ next unless srm_idx
1695
+
1696
+ probs.each do |prob|
1697
+ solved[srm_idx] ||= template.dup
1698
+ solved[srm_idx][prob] = true
1699
+ end
1700
+ end
1701
+
1702
+ month = month.next_month
1703
+ end
1704
+
1705
+ sorted = solved.sort do |a, b|
1706
+ if a.first.length == b.first.length then
1707
+ a.first <=> b.first
1708
+ else
1709
+ a.first.length <=> b.first.length
1710
+ end
1711
+ end
1712
+
1713
+ sorted.each do |srm|
1714
+ state = @diary[:difficulties].map { |difficulty| srm.last[difficulty] ? '*' : '-' }.join
1715
+
1716
+ puts "#{srm.first} #{state}"
1717
+ end
1718
+
1719
+ end
1720
+ end