From c8cff794dce81636b1e0d9becec75383b72c144b Mon Sep 17 00:00:00 2001 From: David Vazgenovich Shakaryan Date: Sat, 9 Apr 2022 04:48:19 -0700 Subject: we've reached feature parity! There's not enough room on my server for inferior languages. --- dartbot.rb | 451 ------------------------------------------------------------- 1 file changed, 451 deletions(-) delete mode 100755 dartbot.rb 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 -- cgit v1.2.3-70-g09d2