```"""Utilities
"""
# pylint: disable=no-member

import collections.abc
import math
import os

[docs]def floor(value, size, offset=200):
"""Floor of `value` given `size` and `offset`.

The floor function is best understood with a diagram of the number line::

-200  -100    0    100   200
<--|--x--|-----|--y--|--z--|-->

The number line shown has offset 200 denoted by the left-hand tick mark at
-200 and size 100 denoted by the tick marks at -100, 0, 100, and 200. The
floor of a value is the left-hand tick mark of the range where it lies. So
for the points show above: ``floor(x)`` is -200, ``floor(y)`` is 0, and
``floor(z)`` is 100.

>>> floor(10, 100)
0.0
>>> floor(120, 100)
100.0
>>> floor(-10, 100)
-100.0
>>> floor(-150, 100)
-200.0
>>> floor(50, 167)
-33.0

"""
return float(((value + offset) // size) * size - offset)

[docs]def path(filename):
"""Return full path to `filename` in freegames module."""
filepath = os.path.realpath(__file__)
dirpath = os.path.dirname(filepath)
fullpath = os.path.join(dirpath, filename)
return fullpath

[docs]def line(a, b, x, y):
"""Draw line from `(a, b)` to `(x, y)`."""
import turtle

turtle.up()
turtle.goto(a, b)
turtle.down()
turtle.goto(x, y)

[docs]def square(x, y, size, name):
"""Draw square at `(x, y)` with side length `size` and fill color `name`.

The square is oriented so the bottom left corner is at (x, y).

"""
import turtle

turtle.up()
turtle.goto(x, y)
turtle.down()
turtle.color(name)
turtle.begin_fill()

for count in range(4):
turtle.forward(size)
turtle.left(90)

turtle.end_fill()

[docs]class vector(collections.abc.Sequence):
"""Two-dimensional vector.

Vectors can be modified in-place.

>>> v = vector(0, 1)
>>> v.move(1)
>>> v
vector(1, 2)
>>> v.rotate(90)
>>> v
vector(-2.0, 1.0)

"""

# pylint: disable=invalid-name
PRECISION = 6

__slots__ = ('_x', '_y', '_hash')

[docs]    def __init__(self, x, y):
"""Initialize vector with coordinates: x, y.

>>> v = vector(1, 2)
>>> v.x
1
>>> v.y
2

"""
self._hash = None
self._x = round(x, self.PRECISION)
self._y = round(y, self.PRECISION)

@property
def x(self):
"""X-axis component of vector.

>>> v = vector(1, 2)
>>> v.x
1
>>> v.x = 3
>>> v.x
3

"""
return self._x

@x.setter
def x(self, value):
if self._hash is not None:
raise ValueError('cannot set x after hashing')
self._x = round(value, self.PRECISION)

@property
def y(self):
"""Y-axis component of vector.

>>> v = vector(1, 2)
>>> v.y
2
>>> v.y = 5
>>> v.y
5

"""
return self._y

@y.setter
def y(self, value):
if self._hash is not None:
raise ValueError('cannot set y after hashing')
self._y = round(value, self.PRECISION)

[docs]    def __hash__(self):
"""v.__hash__() -> hash(v)

>>> v = vector(1, 2)
>>> h = hash(v)
>>> v.x = 2
Traceback (most recent call last):
...
ValueError: cannot set x after hashing

"""
if self._hash is None:
pair = (self.x, self.y)
self._hash = hash(pair)
return self._hash

[docs]    def set(self, other):
"""Set vector x, y to other x, y.

>>> a = vector(0, 0)
>>> b = vector(1, 2)
>>> a.set(b)
>>> a
vector(1, 2)

"""
self.x = other.x
self.y = other.y

[docs]    def __len__(self):
"""v.__len__() -> len(v)

>>> v = vector(1, 2)
>>> len(v)
2

"""
return 2

[docs]    def __getitem__(self, index):
"""v.__getitem__(v, i) -> v[i]

>>> v = vector(3, 4)
>>> v[0]
3
>>> v[1]
4
>>> v[2]
Traceback (most recent call last):
...
IndexError

"""
if index == 0:
return self.x
if index == 1:
return self.y
raise IndexError

[docs]    def copy(self):
"""Return copy of vector.

>>> v = vector(1, 2)
>>> w = v.copy()
>>> v is w
False

"""
type_self = type(self)
return type_self(self.x, self.y)

[docs]    def __eq__(self, other):
"""v.__eq__(w) -> v == w

>>> v = vector(1, 2)
>>> w = vector(1, 2)
>>> v == w
True

"""
if isinstance(other, vector):
return self.x == other.x and self.y == other.y
return NotImplemented

[docs]    def __ne__(self, other):
"""v.__ne__(w) -> v != w

>>> v = vector(1, 2)
>>> w = vector(3, 4)
>>> v != w
True

"""
if isinstance(other, vector):
return self.x != other.x or self.y != other.y
return NotImplemented

>>> v = vector(1, 2)
>>> w = vector(3, 4)
>>> v += w
>>> v
vector(4, 6)
>>> v += 1
>>> v
vector(5, 7)

"""
if self._hash is not None:
raise ValueError('cannot add vector after hashing')
if isinstance(other, vector):
self.x += other.x
self.y += other.y
else:
self.x += other
self.y += other
return self

>>> v = vector(1, 2)
>>> w = vector(3, 4)
>>> v + w
vector(4, 6)
>>> v + 1
vector(2, 3)
>>> 2.0 + v
vector(3.0, 4.0)

"""
copy = self.copy()

[docs]    def move(self, other):
"""Move vector by other (in-place).

>>> v = vector(1, 2)
>>> w = vector(3, 4)
>>> v.move(w)
>>> v
vector(4, 6)
>>> v.move(3)
>>> v
vector(7, 9)

"""

[docs]    def __isub__(self, other):
"""v.__isub__(w) -> v -= w

>>> v = vector(1, 2)
>>> w = vector(3, 4)
>>> v -= w
>>> v
vector(-2, -2)
>>> v -= 1
>>> v
vector(-3, -3)

"""
if self._hash is not None:
raise ValueError('cannot subtract vector after hashing')
if isinstance(other, vector):
self.x -= other.x
self.y -= other.y
else:
self.x -= other
self.y -= other
return self

[docs]    def __sub__(self, other):
"""v.__sub__(w) -> v - w

>>> v = vector(1, 2)
>>> w = vector(3, 4)
>>> v - w
vector(-2, -2)
>>> v - 1
vector(0, 1)

"""
copy = self.copy()
return copy.__isub__(other)

[docs]    def __imul__(self, other):
"""v.__imul__(w) -> v *= w

>>> v = vector(1, 2)
>>> w = vector(3, 4)
>>> v *= w
>>> v
vector(3, 8)
>>> v *= 2
>>> v
vector(6, 16)

"""
if self._hash is not None:
raise ValueError('cannot multiply vector after hashing')
if isinstance(other, vector):
self.x *= other.x
self.y *= other.y
else:
self.x *= other
self.y *= other
return self

[docs]    def __mul__(self, other):
"""v.__mul__(w) -> v * w

>>> v = vector(1, 2)
>>> w = vector(3, 4)
>>> v * w
vector(3, 8)
>>> v * 2
vector(2, 4)
>>> 3.0 * v
vector(3.0, 6.0)

"""
copy = self.copy()
return copy.__imul__(other)

__rmul__ = __mul__

[docs]    def scale(self, other):
"""Scale vector by other (in-place).

>>> v = vector(1, 2)
>>> w = vector(3, 4)
>>> v.scale(w)
>>> v
vector(3, 8)
>>> v.scale(0.5)
>>> v
vector(1.5, 4.0)

"""
self.__imul__(other)

[docs]    def __itruediv__(self, other):
"""v.__itruediv__(w) -> v /= w

>>> v = vector(2, 4)
>>> w = vector(4, 8)
>>> v /= w
>>> v
vector(0.5, 0.5)
>>> v /= 2
>>> v
vector(0.25, 0.25)

"""
if self._hash is not None:
raise ValueError('cannot divide vector after hashing')
if isinstance(other, vector):
self.x /= other.x
self.y /= other.y
else:
self.x /= other
self.y /= other
return self

[docs]    def __truediv__(self, other):
"""v.__truediv__(w) -> v / w

>>> v = vector(1, 2)
>>> w = vector(3, 4)
>>> w / v
vector(3.0, 2.0)
>>> v / 2
vector(0.5, 1.0)

"""
copy = self.copy()
return copy.__itruediv__(other)

[docs]    def __neg__(self):
"""v.__neg__() -> -v

>>> v = vector(1, 2)
>>> -v
vector(-1, -2)

"""
# pylint: disable=invalid-unary-operand-type
copy = self.copy()
copy.x = -copy.x
copy.y = -copy.y
return copy

[docs]    def __abs__(self):
"""v.__abs__() -> abs(v)

>>> v = vector(3, 4)
>>> abs(v)
5.0

"""
return (self.x**2 + self.y**2) ** 0.5

[docs]    def rotate(self, angle):
"""Rotate vector counter-clockwise by angle (in-place).

>>> v = vector(1, 2)
>>> v.rotate(90)
>>> v == vector(-2, 1)
True

"""
if self._hash is not None:
raise ValueError('cannot rotate vector after hashing')
radians = angle * math.pi / 180.0
x = self.x
y = self.y
self.x = x * cosine - y * sine
self.y = y * cosine + x * sine

[docs]    def __repr__(self):
"""v.__repr__() -> repr(v)

>>> v = vector(1, 2)
>>> repr(v)
'vector(1, 2)'

"""
type_self = type(self)
name = type_self.__name__
return '{}({!r}, {!r})'.format(name, self.x, self.y)
```