Created
March 27, 2015 18:05
-
-
Save jorgeconde/6e17b0690df6f6cbd077 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
# 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