summaryrefslogtreecommitdiff
path: root/dartbot.rb
diff options
context:
space:
mode:
authorDavid Vazgenovich Shakaryan <dvshakaryan@gmail.com>2022-03-27 09:13:25 -0700
committerDavid Vazgenovich Shakaryan <dvshakaryan@gmail.com>2022-03-27 09:13:25 -0700
commitf974e0349ff5b5730bbee1ac733d3f993762b660 (patch)
tree49e387a8d4520fcc4aecb49b5bbb24f79d8eab02 /dartbot.rb
downloaddartboat-f974e0349ff5b5730bbee1ac733d3f993762b660.tar.gz
dartboat-f974e0349ff5b5730bbee1ac733d3f993762b660.tar.xz
initial import
Diffstat (limited to 'dartbot.rb')
-rwxr-xr-xdartbot.rb238
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