summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xdartbot.rb451
1 files changed, 0 insertions, 451 deletions
diff --git a/dartbot.rb b/dartbot.rb
deleted file mode 100755
index 5a61471..0000000
--- a/dartbot.rb
+++ /dev/null
@@ -1,451 +0,0 @@
-#!/usr/bin/env ruby
-
-require 'curses'
-
-HORIZONTAL_STDEV = 24
-VERTICAL_STDEV = 24
-
-# 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]
-
-CHECKOUTS = { # FIXME better checkout shots
- 1 => {
- 50=>"BULL", 40=>"D20", 38=>"D19", 36=>"D18", 34=>"D17", 32=>"D16",
- 30=>"D15", 28=>"D14", 26=>"D13", 24=>"D12", 22=>"D11", 20=>"D10", 18=>"D9",
- 16=>"D8", 14=>"D7", 12=>"D6", 10=>"D5", 8=>"D4", 6=>"D3", 4=>"D2", 2=>"D1"
- },
- 2 => {
- 110=>"T20", 107=>"T19", 104=>"T18", 101=>"T17", 100=>"T20", 98=>"T20",
- 97=>"T19", 96=>"T20", 95=>"T19", 94=>"T18", 93=>"T19", 92=>"T20",
- 91=>"T17", 90=>"T18", 89=>"T19", 88=>"T16", 87=>"T17", 86=>"T18",
- 85=>"T15", 84=>"T20", 83=>"T17", 82=>"T14", 81=>"T19", 80=>"T20",
- 79=>"T19", 78=>"T18", 77=>"T19", 76=>"T20", 75=>"T17", 74=>"T16",
- 73=>"T19", 72=>"T16", 71=>"T19", 70=>"T20", 69=>"T19", 68=>"T18",
- 67=>"T17", 66=>"T16", 65=>"T15", 64=>"T14", 63=>"T13", 62=>"T12",
- 61=>"T11", 60=>"20", 59=>"19", 58=>"18", 57=>"17", 56=>"16", 55=>"15",
- 54=>"14", 53=>"13", 52=>"20", 51=>"19", 50=>"18", 49=>"17", 48=>"16",
- 47=>"15", 46=>"14", 45=>"13", 44=>"12", 43=>"3", 42=>"10", 41=>"17",
- 40=>"D20", 39=>"7", 38=>"D19", 37=>"17", 36=>"D18", 35=>"3", 34=>"D17",
- 33=>"17", 32=>"D16", 31=>"7", 30=>"D15", 29=>"17", 28=>"D14", 27=>"3",
- 26=>"D13", 25=>"17", 24=>"D12", 23=>"7", 22=>"D11", 21=>"17", 20=>"D10",
- 19=>"3", 18=>"D9", 17=>"1", 16=>"D8", 15=>"7", 14=>"D7", 13=>"5", 12=>"D6",
- 11=>"3", 10=>"D5", 9=>"1", 8=>"D4", 7=>"3", 6=>"D3", 5=>"1", 4=>"D2",
- 3=>"1", 2=>"D1"
- },
- 3 => {
- 170=>"T20", 167=>"T19", 164=>"T19", 161=>"T20", 160=>"T20", 158=>"T20",
- 157=>"T20", 156=>"T20", 155=>"T20", 154=>"T19", 153=>"T20", 152=>"T20",
- 151=>"T20", 150=>"T20", 149=>"T20", 148=>"T20", 147=>"T19", 146=>"T19",
- 145=>"T20", 144=>"T20", 143=>"T20", 142=>"T20", 141=>"T19", 140=>"T15",
- 139=>"T19", 138=>"T20", 137=>"T14", 136=>"T20", 135=>"BULL", 134=>"T14",
- 133=>"T14", 132=>"BULL", 131=>"T19", 130=>"T20", 129=>"T19", 128=>"T20",
- 127=>"T20", 126=>"T19", 125=>"BULL", 124=>"T20", 123=>"T19", 122=>"T18",
- 121=>"T20", 120=>"T20", 119=>"T19", 118=>"T20", 117=>"T20", 116=>"T19",
- 115=>"T19", 114=>"T20", 113=>"T19", 112=>"T20", 111=>"T19", 110=>"T19",
- 109=>"T20", 108=>"T20", 107=>"T19", 106=>"T20", 105=>"T20", 104=>"T19",
- 103=>"T19", 102=>"T20", 101=>"T19", 100=>"T20", 99=>"T19", 98=>"T20",
- 97=>"T19", 96=>"T20", 95=>"T19", 94=>"T18", 93=>"T19", 92=>"T20",
- 91=>"T17", 90=>"T20", 89=>"T19", 88=>"T20", 87=>"T17", 86=>"T18",
- 85=>"T15", 84=>"T20", 83=>"T17", 82=>"BULL", 81=>"T19", 80=>"T20",
- 79=>"T19", 78=>"T18", 77=>"T19", 76=>"T16", 75=>"T17", 74=>"T14",
- 73=>"T19", 72=>"T16", 71=>"T13", 70=>"T18", 69=>"T15", 68=>"T12",
- 67=>"T17", 66=>"T14", 65=>"BULL", 64=>"T16", 63=>"T13", 62=>"T10",
- 61=>"BULL", 60=>"20", 59=>"19", 58=>"18", 57=>"17", 56=>"16", 55=>"15",
- 54=>"14", 53=>"13", 52=>"20", 51=>"19", 50=>"18", 49=>"17", 48=>"16",
- 47=>"15", 46=>"14", 45=>"13", 44=>"12", 43=>"11", 42=>"10", 41=>"9",
- 40=>"D20", 39=>"7", 38=>"D19", 37=>"5", 36=>"D18", 35=>"3", 34=>"D17",
- 33=>"1", 32=>"D16", 31=>"15", 30=>"D15", 29=>"13", 28=>"D14", 27=>"11",
- 26=>"D13", 25=>"9", 24=>"D12", 23=>"7", 22=>"D11", 21=>"5", 20=>"D10",
- 19=>"3", 18=>"D9", 17=>"1", 16=>"D8", 15=>"7", 14=>"D7", 13=>"5", 12=>"D6",
- 11=>"3", 10=>"D5", 9=>"1", 8=>"D4", 7=>"3", 6=>"D3", 5=>"1", 4=>"D2",
- 3=>"1", 2=>"D1"
- }
-}
-
-def get_coordinates(angle, radius)
- t = angle * Math::PI / 180
- x = radius * Math.cos(t)
- y = radius * Math.sin(t)
-
- [x, y]
-end
-
-def gauss
- theta = 2 * Math::PI * rand
- r = Math.sqrt(-2 * Math.log(1 - rand))
- [r * Math.cos(theta), r * Math.sin(theta)]
-end
-
-def throw_dart(angle, radius)
- x, y = get_coordinates(angle, radius)
- x_offset, y_offset = gauss
- x += HORIZONTAL_STDEV * x_offset
- y += VERTICAL_STDEV * y_offset
-
- 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, in_hand)
- segment = CHECKOUTS[in_hand][rem] || (in_hand == 1 ? '20' : 'T20')
-
- get_segment_coordinates(segment)
-end
-
-def play_visit(rem)
- v_rem = rem
-
- darts = []
-
- 3.times do
- t_angle, t_radius = next_dart(v_rem, 3 - darts.length)
- 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 != 'BULL' && segment !~ /^D/))
-
- v_rem = rem
- break
- end
-
- break if v_rem.zero?
- end
-
- [v_rem, rem - v_rem, darts]
-end
-
-def colour_points(points)
- str = "%3s" % points
-
- if points == 180
- "\e[1;38;5;82m#{str}\e[0m"
- elsif points > 139
- "\e[38;5;82m#{str}\e[0m"
- elsif points > 99
- "\e[38;5;154m#{str}\e[0m"
- elsif points > 59
- "\e[38;5;226m#{str}\e[0m"
- elsif points > 39
- "\e[38;5;214m#{str}\e[0m"
- elsif points > 19
- "\e[38;5;202m#{str}\e[0m"
- else
- "\e[38;5;196m#{str}\e[0m"
- end
-end
-
-def curses_output_points(win, points)
- win.addstr(' ')
-
- unless points
- win.addstr(' ')
- return
- end
-
- str = "%3s" % points
-
- [180, 140, 100, 60, 40, 20, 1, 0].each do |pmin|
- if points >= pmin
- a = COLOUR_PAIRS["p#{pmin}".intern]
-
- win.attron(a) do
- win.addstr(str)
- end
- break
- end
- end
-end
-
-COLOURS = {
- p180: [[82, 0], [Curses::A_BOLD]],
- p140: [[82, 0]],
- p100: [[154, 0]],
- p60: [[226, 0]],
- p40: [[214, 0]],
- p20: [[202, 0]],
- p1: [[196, 0]],
- p0: [[196, 0], [Curses::A_BOLD]],
- bw: [[0, 7]],
- visit: [[235, 0]]
-}
-
-COLOUR_PAIRS = {}
-
-def curses_init_colours
- COLOURS.each.with_index do |(k, v), i|
- Curses.init_pair(i + 1, *v[0])
- col = Curses.color_pair(i + 1)
- if (mods = v[1])
- mods.each { |x| col |= x }
- end
- COLOUR_PAIRS[k] = col
- 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)
- str = '(%2s) %3s' % [n, rem]
- str << ' %s' % colour_points(points) if points
- str << " \e[38;5;235m%s\e[0m" % visit_darts(darts) if darts
-
- puts str
-end
-
-def curses_output_visit(win, n, p_rem, rem, p_points = nil, points = nil, darts = nil)
- win.addstr('(%2s)' % n)
- curses_output_points(win, p_points)
- win.addstr(' %3s' % p_rem)
- win.addstr(' %3s' % rem)
- curses_output_points(win, points)
- if darts
- win.attron(COLOUR_PAIRS[:visit]) do
- win.addstr(' %s' % visit_darts(darts))
- end
- end
-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, points, darts = play_visit(rem)
- m_darts << darts
-
- output_visit(m_darts.length, rem, points, darts) 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
- avgs = (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
-
- avg = avgs.sum / avgs.length
- stdev = Math.sqrt(avgs.sum { |x| (x - avg) ** 2 } / (avgs.length - 1))
-
- puts
- puts avg
- puts stdev
-end
-
-def play_curses_match(start_points = 501)
- Curses.init_screen
- Curses.start_color
- curses_init_colours
- Curses.curs_set(0)
- Curses.noecho
- win = Curses::Window.new(Curses.lines, Curses.cols, 0, 0)
- m_lines = []
- draw(win, m_lines)
-
- p_rem = start_points
- rem = start_points
- m_darts = []
-
- m_lines << [m_darts.length, p_rem, rem]
- p_buff = ''
-
- draw(win, m_lines)
-
- status = nil
- loop do
- p_buff = ''
- loop do
- c = win.getch
- if c == Curses::Key::RESIZE
- win.resize(Curses.lines, Curses.cols)
- draw(win, m_lines, p_buff)
- elsif c == 10
- break
- elsif c == 127
- p_buff = p_buff[0..-2]
- draw(win, m_lines, p_buff)
- elsif c.chr >= '0' && c.chr <= '9'
- p_buff << c
- draw(win, m_lines, p_buff)
- end
- end
-
- p_points = p_buff.to_i
- p_rem -= p_points
- m_lines << [m_darts.length + 1, p_rem, rem, p_points, nil, nil]
- if p_rem == 0
- status = 'You win! :)'
- break
- end
-
- rem, points, darts = play_visit(rem)
- m_darts << darts
- m_lines[m_lines.length-1] = [m_darts.length, p_rem, rem, p_points, points, darts]
- draw(win, m_lines)
-
- if rem == 0
- status = 'Dartbot wins. :('
- break
- end
- end
-
- draw(win, m_lines, p_buff, status)
- loop do
- c = win.getch
- if c == Curses::Key::RESIZE
- win.resize(Curses.lines, Curses.cols)
- draw(win, m_lines, p_buff, status)
- end
- end
-
- m_darts
-end
-
-def draw(win, m_lines, buf = '', status = nil)
- win.erase
- win.box
- win.setpos(0,2)
- win.addstr("dartbot")
-
- max_ml = Curses.lines - 5
-
- m_lines.last(max_ml).each.with_index do |line, i|
- win.setpos(2+i, 2)
- curses_output_visit(win, *line)
- end
-
- win.setpos(Curses.lines - 2, 1)
- win.attron(COLOUR_PAIRS[:bw]) do
- if status
- win.addstr(status.ljust(Curses.cols-2))
- else
- win.addstr(('Enter points: ' + buf).ljust(Curses.cols-2))
- end
- end
-
- win.refresh
-end
-
-play_curses_match
-#test_average