Skip to content

Instantly share code, notes, and snippets.

@silverhammermba
Created November 15, 2015 18:23
Show Gist options
  • Save silverhammermba/ad2213613336cf6999eb to your computer and use it in GitHub Desktop.
Save silverhammermba/ad2213613336cf6999eb to your computer and use it in GitHub Desktop.
An SVG mancala board generator
def pathdef d
<<-XML
<path style="#$linestyle" d="#{d}" />
XML
end
def roundedpath rad, *points
meth = "M"
sides = points.each_slice(2).map.with_index do |p, i|
corner = "#{meth} #{p[0].join(' ')} A #{rad} #{rad} 0 0 1 #{p[1].join(' ')}"
meth = "L"
corner
end
pathdef "#{sides.join(' ')} Z"
end
def closedpath *points
pathdef "M #{points.map { |p| p.join(' ') }.join(' L ')} Z"
end
def path *points
pathdef "M #{points.map { |p| p.join(' ') }.join(' L ')}"
end
# parametrically shift from s to e by time t where distance=time
def parshiftd s, e, t
d = Math.sqrt((s[0] - e[0]) ** 2 + (s[1] - e[1]) ** 2)
parshift(s, e, Rational(t,d))
end
# parametrically shift from s to e by time t in 0...1
def parshift s, e, t
[s[0] + (e[0] - s[0]) * t, s[1] + (e[1] - s[1]) * t]
end
$linestyle = {
'fill' => 'none',
'fill-rule' => 'evenodd',
'stroke' => '#000000',
'stroke-width' => '1px',
'stroke-linecap' => 'butt',
'stroke-linejoin' => 'miter',
'stroke-opacity' => '1'
}
$linestyle = $linestyle.map { |x| x.join(?:) }.join(?;)
### PARAMETERS ###
# number of sides
n = Integer(ARGV[0])
# number of bins per player
b = 6
# outer radius
r = 300
# shift of big bin lines
s = 5
### SECONDARY PARAMETERS ###
# interior angle
interior = ((n - 2) * Math::PI) / n
# half of the interior angle
half_int = ((n - 2) * Math::PI) / (2 * n)
# sin/cos of interior
sin_int = Math.sin(interior)
cos_int = Math.cos(interior)
# sin/cos of half interior
sin_hint = Math.sin(half_int)
cos_hint = Math.cos(half_int)
# we compute the inner radius as follows: after drawing the big bins, the
# region left on each side is a trapezoid. so to get roughly square-shaped
# bins, we choose an inner radius such that the height of the trapezoid
# is equal to its midsegment divided by the number of bins per side, minus the
# big bin
# this depends on the outer radius, number of bins, and the double line shift
r1 = (r * cos_int - 4 * s * cos_hint + r * sin_int * (1 - b)) / (sin_int * (1 - b) - cos_int - 2)
# how far we need to shift in from an outer corner to get parallel to inner edge
outer_shift = (r - r1) / (2 * cos_hint)
# the radius of the circles that are crammed into the corners
cap_rad = ((r - r1) * sin_hint) / (1 + cos_int)
# outer border
outerb = (0...n).map do |i|
theta = (2 * Math::PI * i) / n
[Math.cos(theta) * r, Math.sin(theta) * r]
end
# compute drawing limits using outer border
limits = [[outerb.map(&:first).min, outerb.map(&:first).max], [outerb.map(&:last).min, outerb.map(&:last).max]]
# shift outer border to center of canvas
outerb.map! { |p| [p[0] - limits[0][0], p[1] - limits[1][0]] }
File.open('mancala.svg', 'w') do |f|
f.puts <<-XML
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.1"
baseProfile="full"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:ev="http://www.w3.org/2001/xml-events"
width="#{limits[0][1] - limits[0][0]}px"
height="#{limits[1][1] - limits[1][0]}px"
>
XML
f.puts "<!-- outer border -->"
outer_border = (0...n).flat_map do |i|
[parshiftd(outerb[i], outerb[(i - 1) % n], outer_shift), parshiftd(outerb[i], outerb[(i + 1) % n], outer_shift)]
end
f.puts roundedpath cap_rad, *outer_border
# inner border
innerb = (0...n).map do |i|
theta = (2 * Math::PI * i) / n
[Math.cos(theta) * r1 - limits[0][0], Math.sin(theta) * r1 - limits[1][0]]
end
f.puts "<!-- inner border -->"
f.puts closedpath(*innerb)
# draw dividing lines
n.times do |i|
# start and end points
inner = [innerb[i], innerb[(i + 1) % n]]
outer = [outerb[i], outerb[(i + 1) % n]]
# shift outer endpoints in by outer_shift
outer = [parshiftd(outer[0], outer[1], outer_shift), parshiftd(outer[1], outer[0], outer_shift)]
f.puts "<!-- lines for side #{i} -->"
# draw big bin lines
f.puts path(inner[0], outer[0])
f.puts path(inner[1], outer[1])
# now shift a little to get double lines
inner = [parshiftd(inner[0], inner[1], s), parshiftd(inner[1], inner[0], s)]
outer = [parshiftd(outer[0], outer[1], s), parshiftd(outer[1], outer[0], s)]
# draw the rest of the lines
(b).times do |j|
f.puts path(parshift(inner[0], inner[1], Rational(j, b - 1)), parshift(outer[0], outer[1], Rational(j, b - 1)))
end
end
f.puts "</svg>"
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment