#!/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