Skip to content

Instantly share code, notes, and snippets.

@jorgeconde
Created March 27, 2015 18:05
Show Gist options
  • Save jorgeconde/6e17b0690df6f6cbd077 to your computer and use it in GitHub Desktop.
Save jorgeconde/6e17b0690df6f6cbd077 to your computer and use it in GitHub Desktop.
# Jorge Conde V00723209
# Assignment #7
# a little language for 2D geometry objects
# each subclass of GeometryExpression, including subclasses of GeometryValue,
# needs to respond to messages preprocess_prog and eval_prog
#
# each subclass of GeometryValue additionally needs:
# * shift
# * intersect, which uses the double-dispatch pattern
# * intersectPoint, intersectLine, and intersectVerticalLine for
# for being called by intersect of appropriate clases and doing
# the correct intersection calculuation
# * (We would need intersectNoPoints and intersectLineSegment, but these
# are provided by GeometryValue and should not be overridden.)
# * intersectWithSegmentAsLineResult, which is used by
# intersectLineSegment as described in the assignment
#
# you can define other helper methods, but will not find much need to
# Note: geometry objects should be immutable: assign to fields only during
# object construction
# Note: For eval_prog, represent environments as arrays of 2-element arrays
# as described in the assignment
class GeometryExpression
# do *not* change this class definition
Epsilon = 0.00001
end
class GeometryValue
# do *not* change methods in this class definition
# you can add methods if you wish
private
# some helper methods that may be generally useful
def real_close(r1,r2)
(r1 - r2).abs < GeometryExpression::Epsilon
end
def real_close_point(x1,y1,x2,y2)
real_close(x1,x2) && real_close(y1,y2)
end
# two_points_to_line could return a Line or a VerticalLine
def two_points_to_line(x1,y1,x2,y2)
if real_close(x1,x2)
VerticalLine.new x1
else
m = (y2 - y1).to_f / (x2 - x1)
b = y1 - m * x1
Line.new(m,b)
end
end
public
# we put this in this class so all subclasses can inherit it:
# the intersection of self with a NoPoints is a NoPoints object
def intersectNoPoints np
np # could also have NoPoints.new here instead
end
# we put this in this class so all subclasses can inhert it:
# the intersection of self with a LineSegment is computed by
# first intersecting with the line containing the segment and then
# calling the result's intersectWithSegmentAsLineResult with the segment
def intersectLineSegment seg
line_result = intersect(two_points_to_line(seg.x1,seg.y1,seg.x2,seg.y2))
line_result.intersectWithSegmentAsLineResult seg
end
end
class NoPoints < GeometryValue
# do *not* change this class definition: everything is done for you
# (although this is the easiest class, it shows what methods every subclass
# of geometry values needs)
# Note: no initialize method only because there is nothing it needs to do
def eval_prog env
self # all values evaluate to self
end
def preprocess_prog
self # no pre-processing to do here
end
def shift(dx,dy)
self # shifting no-points is no-points
end
def intersect other
other.intersectNoPoints self # will be NoPoints but follow double-dispatch
end
def intersectPoint p
self # intersection with point and no-points is no-points
end
def intersectLine line
self # intersection with line and no-points is no-points
end
def intersectVerticalLine vline
self # intersection with line and no-points is no-points
end
# if self is the intersection of (1) some shape s and (2)
# the line containing seg, then we return the intersection of the
# shape s and the seg. seg is an instance of LineSegment
def intersectWithSegmentAsLineResult seg
self
end
end
class Point < GeometryValue
# *add* methods to this class -- do *not* change given code and do not
# override any methods
# Note: You may want a private helper method like the local
# helper function inbetween in the ML code
attr_reader :x, :y
def initialize(x,y)
@x = x
@y = y
end
def eval_prog env
self
end
def preprocess_prog
self
end
def shift(dx,dy)
Point.new(@x+dx, @y+dy)
end
def intersect other
other.intersectPoint self
end
def intersectPoint p
if real_close(@x, p.x) and real_close(@y, p.y)
p
else
NoPoints.new
end
end
def intersectLine l
if real_close(@y, (l.m*@x) + l.b)
self
else
NoPoints.new
end
end
def intersectVerticalLine vl
vl.intersectPoint self
end
private
def inBetween(v, end1, end2)
epsilon = GeometryExpression::Epsilon
(end1 - epsilon <= v and v <= end2 + epsilon) or
(end2 - epsilon <= v and v <= end1 + epsilon)
end
public
def intersectWithSegmentAsLineResult seg
if inBetween(@x,seg.x1,seg.x2) and inBetween(@y,seg.y1,seg.y2)
self
else
NoPoints.new
end
end
end
class Line < GeometryValue
# *add* methods to this class -- do *not* change given code and do not
# override any methods
attr_reader :m, :b
def initialize(m,b)
@m = m
@b = b
end
def eval_prog env
self
end
def preprocess_prog
self
end
def shift(dx,dy)
Line.new(@m, (@b+dy) - (m*dx))
end
def intersect other
other.intersectLine self
end
def intersectLine l
if real_close(@m,l.m)
if real_close(@b,l.b)
self
else
NoPoints.new
end
else
x = (l.b - @b)/(@m - l.m)
Point.new(x, @m * x + @b)
end
end
def intersectPoint p
if real_close(p.y, (m*p.x) + b)
p
else
NoPoints.new
end
end
def intersectVerticalLine vl
Point.new(vl.x,@m * vl.x + @b)
end
def intersectWithSegmentAsLineResult seg
seg # I have to CHANGE this
end
end
class VerticalLine < GeometryValue
# *add* methods to this class -- do *not* change given code and do not
# override any methods
attr_reader :x
def initialize x
@x = x
end
def eval_prog env
self
end
def preprocess_prog
self
end
def shift(dx,dy)
VerticalLine.new(@x+dx)
end
def intersect other
other.intersectVerticalLine self
end
def intersectVerticalLine vl
if real_close(@x, vl.x)
vl
else
NoPoints.new
end
end
def intersectPoint p
if real_close(@x, p.x)
p
else
NoPoints.new
end
end
def intersectLine l
Point.new(@x,l.m * @x + l.b)
end
def intersectWithSegmentAsLineResult seg
seg
end
end
class LineSegment < GeometryValue
# *add* methods to this class -- do *not* change given code and do not
# override any methods
# Note: This is the most difficult class. In the sample solution,
# preprocess_prog is about 15 lines long and
# intersectWithSegmentAsLineResult is about 40 lines long
attr_reader :x1, :y1, :x2, :y2
def initialize (x1,y1,x2,y2)
@x1 = x1
@y1 = y1
@x2 = x2
@y2 = y2
end
def eval_prog env
self
end
private
def comp(v1,v2,if_true) # helper function for preprocess_prog
v1 < v2 ? if_true : LineSegment.new(@x2,@y2,@x1,@y1)
end
public
def preprocess_prog
close_x = real_close(@x1,@x2)
close_y = real_close(@y1,@y2)
if close_x and close_y
Point.new(@x1,@y1)
elsif close_x and @y2 < @y1
LineSegment.new(@x2,@y2,@x1,@y1)
elsif close_x and @y2 > @y1
self
elsif @x2 < @x1
LineSegment.new(@x2,@y2,@x1,@y1)
else
self
end
end
def shift(dx,dy)
LineSegment.new(@x1+dx,@y1+dy,@x2+dx,@y2+dy)
end
def intersect other
other.intersectLineSegment self
end
def intersectWithSegmentAsLineResult seg
seg2 = self
if real_close(seg.x1, seg.x2)
b = seg
a = seg2
if seg.y1 < seg2.y1
a = seg
b = seg2
end
if real_close(a.y2, b.y1)
Point.new(a.x2, a.y2) # Just touching
elsif a.y2 < b.y1
NoPoints.new # disjoint
elsif a.y2 > b.y2
LineSegment.new(b.x1, b.y1, b.x2, b.y2) # b inside a
else
LineSegment.new(b.x1, b.y1, a.x2, a.y2) # overlapping
end
else
a = seg2
b = seg
if seg.x1 < seg2.x1
a = seg
b = seg2
end
if real_close(a.x2, b.x1)
Point.new(a.x2, a.y2) # Just touching
elsif a.x2 < b.x1
NoPoints.new # disjoint
elsif a.x2 > b.x2
LineSegment.new(b.x1, b.y1, b.x2, b.y2) # b inside a
else
LineSegment.new(b.x1, b.y1, a.x2, a.y2) # overlapping
end
end
end
def intersectPoint p
p.intersectLineSegment self
end
def intersectLine l
l.intersectLineSegment self
end
def intersectVerticalLine vl
if real_close(@x1,@x2) and real_close(vl.x,@x1)
self
elsif real_close(@y1,@y2) and real_close(vl.x,@y1)
Point.new(@y1,@y2)
else
self.intersectPoint(Point.new(vl.x, vl.x))
end
end
end
# Note: there is no need for getter methods for the non-value classes
class Intersect < GeometryExpression
# *add* methods to this class -- do *not* change given code and do not
# override any methods
def initialize(e1,e2)
@e1 = e1
@e2 = e2
end
def eval_prog env
(@e1.eval_prog env).intersect (@e2.eval_prog env)
end
def preprocess_prog
Intersect.new(@e1.preprocess_prog,@e2.preprocess_prog)
end
end
class Let < GeometryExpression
# *add* methods to this class -- do *not* change given code and do not
# override any methods
# Note: Look at Var to guide how you implement Let
def initialize(s,e1,e2)
@s = s
@e1 = e1
@e2 = e2
end
def eval_prog env
@e2.eval_prog([[@s, @e1.eval_prog(env)]] + env)
end
def preprocess_prog
Let.new(@s,@e1.preprocess_prog,@e2.preprocess_prog)
end
end
class Var < GeometryExpression
# *add* methods to this class -- do *not* change given code and do not
# override any methods
def initialize s
@s = s
end
def eval_prog env # remember: do not change this method
pr = env.assoc @s
raise "undefined variable" if pr.nil?
pr[1]
end
def preprocess_prog
self
end
end
class Shift < GeometryExpression
# *add* methods to this class -- do *not* change given code and do not
# override any methods
def initialize(dx,dy,e)
@dx = dx
@dy = dy
@e = e
end
def eval_prog env # remember: do not change this method
(@e.eval_prog env).shift(@dx,@dy)
end
def preprocess_prog
Shift.new(@dx,@dy,@e.preprocess_prog)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment