import Darwin
struct Position { let x, y: Float }
typealias Distance = Float
// use the "key trick" from the FP approach, but in an OO manner
protocol Containsable
{
func contains(position: Position) -> Bool
}
// Make structs that adopt the protocol
struct Circle: Containsable
{
let radius: Distance
func contains(position: Position) -> Bool
{
return hypot(position.x, position.y) <= radius
}
}
// example
let unitCircle = Circle(radius: 1.0)
unitCircle.contains(Position(x: 0.5, y: 0.5))
unitCircle.contains(Position(x: 2, y: 3))
// We can easily do rect also
struct Rectangle: Containsable
{
let width: Distance
let height: Distance
func contains(position: Position) -> Bool {
return (fabs(position.x) <= width/2
&& fabs(position.y) <= height/2)
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Transformations
// First, define a composition (a new struct that adopts the protocol)
struct ComposedRegion: Containsable
{
let containsRule: Position -> Bool ///< holds the complex logic as a function
func contains(position: Position) -> Bool
{
return containsRule(position)
}
}
// Now, do the transformations (that return the new type)
func shift(region: Containsable, by offset: Position) -> ComposedRegion
{
return ComposedRegion(containsRule: {
point in
let shiftedPoint = Position(x: point.x - offset.x, y: point.y - offset.y)
return region.contains(shiftedPoint)
})
}
// example
let shiftedCirlce = shift(unitCircle, by: Position(x: 1.5, y: 3.5))
shiftedCirlce.contains(Position(x: 2, y: 3))
// Other Operations
func invert(region: Containsable) -> ComposedRegion
{
return ComposedRegion(containsRule: { point in !region.contains(point) })
}
func intersection(of a: Containsable, with b: Containsable) -> ComposedRegion
{
return ComposedRegion(containsRule: { point in
a.contains(point) && b.contains(point)
})
}
func union(of a: Containsable, with b: Containsable) -> ComposedRegion
{
return ComposedRegion(containsRule: { point in
a.contains(point) || b.contains(point)
})
}
func difference(of region: Containsable, minusRegion: Containsable) -> ComposedRegion
{
return intersection(of: region, with: invert(minusRegion))
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Now, the complex example
// Note: It should look a lot like the FP solution of this
let ownPosition = Position(x: 10, y: 12)
let weaponRange = Circle(radius: 5.0) // <-- just make this a struct
let safeDistance = Circle(radius: 1.0) // <-- and this
let friendlyPosition = Position(x: 12, y: 9)
let friendlyRegion = shift(safeDistance, by: friendlyPosition)
let shouldFireAtTarget = difference(
of: shift(
difference(
of: weaponRange,
minusRegion: safeDistance),
by: ownPosition),
minusRegion: friendlyRegion)
// Test it
shouldFireAtTarget.contains(Position(x: 0, y: 0)) // too far away
shouldFireAtTarget.contains(Position(x: 9, y: 15)) // hit!
shouldFireAtTarget.contains(Position(x: 10.5, y: 12)) // too close to self
shouldFireAtTarget.contains(Position(x: 12.25, y: 9.25)) // too close to friendly