diff options
-rwxr-xr-x | dartbot.rb | 238 |
1 files changed, 238 insertions, 0 deletions
diff --git a/dartbot.rb b/dartbot.rb new file mode 100755 index 0000000..ccf5e38 --- /dev/null +++ b/dartbot.rb @@ -0,0 +1,238 @@ +#!/usr/bin/env ruby + +HORIZONTAL_ACCURACY = 32.0 +VERTICAL_ACCURACY = 32.0 + +# board spec from WDF rules +WIRE_WIDTH = 1.56 +INNER_DIAMETER_BULL = 12.7 +INNER_DIAMETER_25 = 31.8 +DOUBLE_OUTER_EDGE = 170.0 +TREBLE_OUTER_EDGE = 107.4 +DOUBLE_INSIDE_WIDTH = 8.0 +TREBLE_INSIDE_WIDTH = 8.0 +SECTORS = %w[20 1 18 4 13 6 10 15 2 17 3 19 7 16 8 11 14 9 12 5].map(&:to_i) + +SECTOR_ANGLES = SECTORS.each_with_index.inject({}) do |m, (v, i)| + m[v] = (90 - (i * 360 / SECTORS.length)) % 360 + m +end + +# distance from centre to apex of outer wire +# must be ordered outwards from centre +OUTER_DISTS = { + bull: INNER_DIAMETER_BULL/2 + WIRE_WIDTH/2, + '25': INNER_DIAMETER_25/2 + WIRE_WIDTH/2, + small: TREBLE_OUTER_EDGE - WIRE_WIDTH - TREBLE_INSIDE_WIDTH - WIRE_WIDTH/2, + treble: TREBLE_OUTER_EDGE - WIRE_WIDTH/2, + big: DOUBLE_OUTER_EDGE - WIRE_WIDTH - DOUBLE_INSIDE_WIDTH - WIRE_WIDTH/2, + double: DOUBLE_OUTER_EDGE - WIRE_WIDTH/2 +} + +CENTRE_DISTS = OUTER_DISTS.inject([{}, nil]) { |(h, prev), (k, v)| + h[k] = prev ? (v - ((v - prev) / 2)) : 0.0 + [h, v] +}[0] + +def get_coordinates(angle, radius) + t = angle * Math::PI / 180 + x = radius * Math.cos(t) + y = radius * Math.sin(t) + + [x, y] +end + +def throw_dart(angle, radius) + x, y = get_coordinates(angle, radius) + x += rand(-HORIZONTAL_ACCURACY..HORIZONTAL_ACCURACY) + y += rand(-VERTICAL_ACCURACY..VERTICAL_ACCURACY) + + d_angle = Math.atan2(y, x) * 180 / Math::PI + d_radius = Math.sqrt(x**2 + y**2) + + [d_angle, d_radius] +end + +def get_sector(angle) + sector = SECTOR_ANGLES.detect do |v, c| + dist = (c - angle).abs % 360 + dist = 360 - dist if dist > 180 + dist < 360 / SECTORS.length / 2 #TODO hit wire + end + + sector && sector.first +end + +def get_ring(radius) + ring = OUTER_DISTS.detect do |sym, dist| + radius**2 < dist**2 + end + + ring ? ring[0] : :out +end + +def get_segment(angle, radius) + sec = get_sector(angle) + ring = get_ring(radius) + + case ring + when :bull + 'BULL' + when :'25' + '25' + when :small, :big + "#{sec}" + when :double + "D#{sec}" + when :treble + "T#{sec}" + when :out + 'OUT' + end +end + +def get_points(segment) + case segment + when 'BULL' + 50 + when /^T(\d+)$/ + 3 * $1.to_i + when /^D(\d+)$/ + 2 * $1.to_i + when /^(\d+)$/ + $1.to_i + else + 0 + end +end + +def get_segment_coordinates(segment) + sector, ring = + case segment + when 'BULL' + [20, :bull] + when '25' + [20, :'25'] + when /^T(\d+)$/ + [$1.to_i, :treble] + when /^D(\d+)$/ + [$1.to_i, :double] + when /^(\d+)$/ + [$1.to_i, :big] + end + + [SECTOR_ANGLES[sector], CENTRE_DISTS[ring]] +end + +def next_dart(rem) + segment = + if rem > 40 + 'T20' + elsif rem % 2 == 1 + '1' + else + "D#{rem/2}" + end + + get_segment_coordinates(segment) +end + +def play_visit(rem) + v_rem = rem + + darts = [] + bust = false + + 3.times do + t_angle, t_radius = next_dart(v_rem) + d_angle, d_radius = throw_dart(t_angle, t_radius) + + segment = get_segment(d_angle, d_radius) + darts << segment + d_points = get_points(segment) + v_rem -= d_points + + if v_rem < 0 || v_rem == 1 || (v_rem == 0 && segment !~ /^D/) + v_rem = rem + bust = true + break + end + + break if v_rem.zero? + end + + [v_rem, rem - v_rem, darts, bust] +end + +def colour_score(score) + scor = "%3s" % score + + if score == 180 + "\e[1;38;5;82m#{scor}\e[0m" + elsif score > 139 + "\e[38;5;82m#{scor}\e[0m" + elsif score > 99 + "\e[38;5;154m#{scor}\e[0m" + elsif score > 59 + "\e[38;5;226m#{scor}\e[0m" + elsif score > 39 + "\e[38;5;214m#{scor}\e[0m" + elsif score > 19 + "\e[38;5;202m#{scor}\e[0m" + else + "\e[38;5;196m#{scor}\e[0m" + end +end + +def visit_darts(darts) + str = darts.map { |x| '%3s' % x }.join(' ') + str += ' ' * (3 - darts.length) + str +end + +def output_visit(n, rem, points = nil, darts = nil, bust = false) + str = '(%2s) %3s' % [n, rem] + str << ' %s' % colour_score(points) if points + str << " \e[38;5;235m%s\e[0m" % visit_darts(darts) if darts + str << " \e[38;5;88mBUST\e[0m" if bust + str << " \e[38;5;76mWIN\e[0m" if rem.zero? + + puts str +end + +def play_match(start_points = 501, output = true) + rem = start_points + m_darts = [] + + output_visit(m_darts.length, rem) if output + + while rem != 0 + rem, score, darts, bust = play_visit(rem) + m_darts << darts + + output_visit(m_darts.length, rem, score, darts, bust) if output + + if rem.zero? && output + puts + req = (((m_darts.length - 1) * 3) + m_darts[-1].length) + puts "darts: %s" % req + puts "average: %.2f" % (start_points.to_f / req * 3) + end + end + + m_darts +end + +def test_average + averages = (0..10000).map do |x| + m_darts = play_match(501, false) + req = (((m_darts.length - 1) * 3) + m_darts[-1].length) + (501.to_f / req * 3) + end + + puts + puts averages.inject { |sum, x| sum += x } / averages.length +end + +play_match +#test_average |