Created
November 15, 2015 18:23
-
-
Save silverhammermba/ad2213613336cf6999eb to your computer and use it in GitHub Desktop.
An SVG mancala board generator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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