diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<https://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<https://www.gnu.org/licenses/why-not-lgpl.html>.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,58 @@
+# turncoat
+
+A minimalistic bitboard based UCI chess engine written in Haskell with a strong focus on a clear and modular implementation with adequate performance when compared with other open source engines. 
+
+It relies heavily on GHC as well as strictness annotations in order to optimize a high level program that leverages laziness in key areas (such as staged move generation and ordering) producing an efficient binary that can compete in a field where lower level languages (C, C++, Rust) are usually employed.
+
+Turncoat has an official account on Lichess: https://lichess.org/@/TurncoatEngine
+
+
+## Characteristics
+
+### Move Generation
+
+- Legal Move Generation
+- Specialized Static Exchanges Move Generator
+- Staged Move Generation (captures & promotions, quiet moves)
+
+
+### Search
+
+- Negamax
+- Alpha Beta Pruning
+- Principal Variation Search
+- Quiescence Search
+- Iterative Deepening
+- Transposition Table
+- Null Move Pruning
+- Futility Pruning
+- Check Extensions
+- Single move extensions
+- Late Move Reductions
+
+
+### Move Ordering
+
+- Staged Move Ordering 
+- Static Exchange Evaluation
+- Killer Moves
+- Static Evaluation Ordering (for quiet moves)
+
+
+### Evaluation
+
+- Hand Tuned Evaluation
+- Tapered Evaluation
+- Material
+- Piece Square Tables
+- King Safety
+- Mobility
+- Passed Pawns
+- Pawn King Shield
+- Isolated & Doubled Pawns
+- Knight Outposts
+- Bishop Pair
+- Rooks on Open Files
+- Piece Threats
+
+
diff --git a/app/Main.hs b/app/Main.hs
new file mode 100644
--- /dev/null
+++ b/app/Main.hs
@@ -0,0 +1,37 @@
+module Main where
+
+import           AppPrelude
+
+import           Models.Command
+import           Parsers.Command     (parseCommand)
+import           Uci                 (executeCommand, initialEngineState,
+                                      putStrLnFlush)
+
+import           Control.Monad.State
+import qualified Data.Char           as Char
+
+
+main :: IO ()
+main = do
+  printHeader
+  st <- initialEngineState
+  evalStateT (forever repl) st
+  where
+    repl = do
+      input <- getLine
+      unless (all Char.isSpace input)
+             (eval input)
+    eval =
+      either (const $ putStrLnFlush parseErrorMsg) executeCommand
+      . parseCommand
+
+
+printHeader :: IO ()
+printHeader =
+  putStrLnFlush (name <> " " <> version <> " by " <> author)
+  where
+    EngineInfo {..} = engineInfo
+
+
+parseErrorMsg :: Text
+parseErrorMsg = "Error when parsing command"
diff --git a/app/Uci.hs b/app/Uci.hs
new file mode 100644
--- /dev/null
+++ b/app/Uci.hs
@@ -0,0 +1,324 @@
+module Uci (executeCommand, initialEngineState, putStrLnFlush) where
+
+import           AppPrelude
+
+import           Evaluation.Evaluation
+import           Models.Command
+import           Models.Move
+import           Models.Position
+import           Models.Score
+import           MoveGen.MakeMove
+import           Search.Perft
+import           Search.Search
+import           Search.TimeManagement
+import qualified Utils.KillersTable              as KillersTable
+import           Utils.KillersTable              (KillersTable)
+import qualified Utils.TranspositionTable        as TTable
+import           Utils.TranspositionTable        (TTable)
+
+import           Control.Concurrent.Thread.Delay
+import           Control.Monad.State
+import           Data.Composition
+import qualified Data.Map                        as Map
+import           Data.Time.Clock.System
+import           System.Exit
+
+
+executeCommand :: Command -> CommandM ()
+executeCommand = \case
+  Uci              -> handleStart
+  UciNewGame       -> handleNewGame
+  IsReady          -> printReady
+  Search opts      -> whenAvailable $ runSearch opts
+  Perft  depth     -> whenAvailable $ runPerft  depth
+  Divide depth     -> whenAvailable $ runDivide depth
+  Ponderhit        -> handlePonderhit
+  Stop             -> stop
+  Quit             -> quit
+  SetPosition pos  -> setPosition pos
+  SetOption option -> setOption   option
+  MakeMoves mvs    -> makeMoves   mvs
+  Evaluate         -> printStaticEval
+  Display          -> displayBoard
+  Flip             -> flipPosition
+
+
+runSearch :: SearchOptions -> CommandM ()
+runSearch searchOpts = do
+  EngineState{..} <- get
+  killersTable   <- liftIO KillersTable.create
+  let ?opts         = options
+      ?tTable       = tTable
+      ?killersTable = killersTable
+      ?age          = age
+  runTask $ printSearch searchOpts position
+  startTime <- liftIO getSystemTime
+  modify' \st -> st { searchStart = startTime, searchOptions = searchOpts }
+  incrementAge
+
+
+runPerft :: Depth -> CommandM ()
+runPerft depth = do
+  EngineState{..} <- get
+  runTask $ printPerft depth position
+
+
+runDivide :: Depth -> CommandM ()
+runDivide depth = do
+  EngineState{..} <- get
+  runTask $ printDivide depth position
+
+
+printSearch
+  :: (?opts :: EngineOptions, ?tTable :: TTable,
+     ?killersTable :: KillersTable, ?age :: Age)
+  => SearchOptions -> Position -> IO ()
+printSearch searchOpts pos = do
+  bracket acquire release go
+  where
+    acquire =
+      newIORef $ emptySearchResult 0
+
+    release searchResultRef =
+      printBestMove =<< readIORef searchResultRef
+
+    go searchResultRef =
+      search searchOpts searchResultRef pos
+
+
+printPerft :: Depth -> Position -> IO ()
+printPerft = putStrLnFlush . tshow .: perft
+
+
+printDivide :: Depth -> Position -> IO ()
+printDivide =
+  void
+  . Map.traverseWithKey (\k v -> putStrLnFlush (tshow k <> ": " <> tshow v))
+  .: divide
+
+
+printStaticEval :: CommandM ()
+printStaticEval = withPosition go
+  where
+    go = putStrLnFlush
+         . ("\n" <>)
+         . tshow
+         . evaluatePositionBreakdown
+
+
+handlePonderhit :: CommandM ()
+handlePonderhit = do
+  EngineState {..} <- get
+  now <- liftIO getSystemTime
+  let elapsedTime = now |-| searchStart
+  case getMoveTime searchOptions position of
+    Just moveTime
+      | elapsedTime > moveTime -> stop
+      | otherwise -> liftIO $ void $ async do
+          delay $ fromIntegral (moveTime - elapsedTime)
+          stopTask taskRef
+    Nothing -> pure ()
+
+
+stop :: CommandM ()
+stop = do
+  st <- get
+  liftIO $ stopTask st.taskRef
+
+
+quit :: CommandM ()
+quit = do
+  stop
+  liftIO exitSuccess
+
+
+handleNewGame :: CommandM ()
+handleNewGame = do
+  EngineState {tTable} <- get
+  liftIO $ TTable.reset tTable
+  updatePosition $ Just startPosition
+  modify' \st -> st {age = 0}
+
+
+handleStart :: CommandM ()
+handleStart = do
+  printEngineInfo
+  putStrLnFlush mempty
+  printEngineOptions
+  printUciReady
+
+
+setPosition :: PositionSpec -> CommandM ()
+setPosition PositionSpec {..} =
+  updatePosition
+    $ foldM (flip makeUnknownMove) initialPosition moves
+
+
+setOption :: OptionSpec -> CommandM ()
+setOption = \case
+  Ponder ponder ->
+    modifyOptions \x -> x { ponder = ponder }
+
+  HashSize size -> do
+    modifyOptions \x -> x { hashSize = fromIntegral size }
+    EngineState {options} <- get
+    newTTable <- liftIO $ TTable.create options
+    modify' \st -> st { tTable = newTTable }
+
+  ClearHash -> do
+    EngineState {tTable} <- get
+    liftIO $ TTable.reset tTable
+
+
+printBestMove :: MonadIO m => SearchResult -> m ()
+printBestMove SearchResult {..}
+  | Just mv  <- bestMove
+  , Just mv2 <- ponderMove
+    = putStrLnFlush ("bestmove " <> tshow mv <> " ponder " <> tshow mv2)
+  | Just mv  <- bestMove
+    = putStrLnFlush ("bestmove " <> tshow mv)
+  | otherwise
+    = putStrLnFlush mempty
+
+
+printUciReady :: CommandM ()
+printUciReady = putStrLnFlush "uciok"
+
+
+printReady :: CommandM ()
+printReady = putStrLnFlush "readyok"
+
+
+printEngineInfo :: CommandM ()
+printEngineInfo = do
+  go "name"  (name <> " " <> version)
+  go "author" author
+  where
+    EngineInfo {..} = engineInfo
+    go param value =
+      putStrLnFlush ("id" <> " " <> param <> " " <> value)
+
+
+printEngineOptions :: CommandM ()
+printEngineOptions = do
+  go "Hash"       (SpinOption hashSize 1 maxBound)
+  go "Clear Hash" ButtonOption
+  go "Ponder"     (CheckOption ponder)
+  where
+    EngineOptions {..} = defaultEngineOptions
+    go param option =
+      putStrLnFlush ("option name " <> param <> " " <> tshow option)
+
+
+makeMoves :: [UnknownMove] -> CommandM ()
+makeMoves mvs = do
+  st <- get
+  setPosition $ PositionSpec st.position mvs
+
+
+flipPosition :: CommandM ()
+flipPosition =
+  modifyPosition makeNullMove
+
+
+displayBoard :: CommandM ()
+displayBoard = do
+  putStrLnFlush mempty
+  withPosition print
+  putStrLnFlush mempty
+
+
+updatePosition :: Maybe Position -> CommandM ()
+updatePosition = \case
+  Just pos -> modifyPosition $ const pos
+  Nothing  -> putStrLnFlush "Error: Invalid Position"
+
+
+makeUnknownMove :: UnknownMove -> Position -> Maybe Position
+makeUnknownMove UnknownMove {..} pos =
+  (`makeMove` pos) <$> find isSameMove (allMoves pos)
+  where
+    isSameMove mv =
+      mv.start == start && mv.end == end && mv.promotion == promotion
+
+
+whenAvailable :: CommandM () -> CommandM ()
+whenAvailable action = do
+  task <- getTask
+  when (isNothing task) action
+
+
+runTask :: IO () -> CommandM ()
+runTask action = do
+  st <- get
+  task        <- liftIO $ async (action *> writeIORef st.taskRef Nothing)
+  writeIORef st.taskRef $ Just task
+
+
+stopTask :: IORef (Maybe Task) -> IO ()
+stopTask taskRef = do
+  task <- readIORef taskRef
+  traverse_ cancel task
+  writeIORef taskRef Nothing
+
+
+getTask :: CommandM (Maybe Task)
+getTask = do
+  st <- get
+  readIORef st.taskRef
+
+
+withPosition :: (Position -> CommandM a) -> CommandM a
+withPosition f = do
+  st <- get
+  f st.position
+
+
+modifyOptions :: (EngineOptions -> EngineOptions) -> CommandM ()
+modifyOptions f =
+  modify' \st -> st { options = f st.options }
+
+
+modifyPosition :: (Position -> Position) -> CommandM ()
+modifyPosition f =
+  modify' \st -> st { position = f st.position }
+
+
+incrementAge :: CommandM ()
+incrementAge =
+  modify' \st -> st {age = if st.age == 127 then 0 else st.age + 1}
+
+
+data EngineState = EngineState
+  { position      :: Position
+  , options       :: EngineOptions
+  , taskRef       :: IORef (Maybe Task)
+  , searchStart   :: SystemTime
+  , searchOptions :: SearchOptions
+  , tTable        :: TTable
+  , age           :: Word8
+  }
+
+
+initialEngineState :: IO EngineState
+initialEngineState = do
+  taskRef <- newIORef Nothing
+  tTable  <- TTable.create defaultEngineOptions
+  pure EngineState
+    { position      = startPosition
+    , options       = defaultEngineOptions
+    , taskRef       = taskRef
+    , searchStart   = MkSystemTime 0 0
+    , searchOptions = defaultSearchOptions
+    , tTable        = tTable
+    , age           = 0
+    }
+
+
+putStrLnFlush :: MonadIO m => Text -> m ()
+putStrLnFlush message = do
+  putStrLn message
+  hFlush stdout
+
+
+type CommandM = StateT EngineState IO
diff --git a/src/AppPrelude.hs b/src/AppPrelude.hs
new file mode 100644
--- /dev/null
+++ b/src/AppPrelude.hs
@@ -0,0 +1,64 @@
+module AppPrelude (module ClassyPrelude, module Data.Ord, Vector, (!!), (/), (%), inRange, maybeFilter, findTraverseIndex, untilM, elem, notElem) where
+
+import           ClassyPrelude        hiding (Vector, elem, mask, notElem, (/), (>>), (^))
+
+import           Data.Foldable        (elem, notElem)
+import           Data.Int             (Int16)
+import           Data.Ord
+import           Data.Vector.Storable (Vector)
+
+
+{-# SPECIALIZE (!!) :: Vector Int16 -> Int -> Int16 #-}
+{-# SPECIALIZE (!!) :: Vector Word64 -> Int -> Word64 #-}
+{-# INLINE (!!)  #-}
+infixl 9 !!
+(!!) :: Storable a => Vector a -> Int -> a
+(!!) !x !y = unsafeIndex x y
+
+
+{-# SPECIALIZE (/) :: Int16 -> Int16 -> Int16 #-}
+{-# SPECIALIZE (/) :: Int -> Int -> Int #-}
+{-# INLINE (/)  #-}
+infixl 7 /
+(/) :: Integral a => a -> a -> a
+(/) !x !y = quot x y
+
+{-# SPECIALIZE (%) :: Int16 -> Int16 -> Int16 #-}
+{-# SPECIALIZE (%) :: Int -> Int -> Int #-}
+{-# SPECIALIZE (%) :: Word64 -> Word64 -> Word64 #-}
+{-# INLINE (%)  #-}
+infixl 7 %
+(%) :: Integral a => a -> a -> a
+(%) !x !y = rem x y
+
+
+inRange :: Int -> Int -> Int -> Bool
+inRange lo hi x = lo <= x && x <= hi
+
+
+{-# INLINE maybeFilter  #-}
+maybeFilter :: (a -> Bool) -> Maybe a -> Maybe a
+maybeFilter !predicate ma = do
+  !a <- ma
+  if predicate a
+    then ma
+    else Nothing
+
+
+{-# INLINE findTraverseIndex  #-}
+findTraverseIndex :: Monad m => (Int -> a -> m (Maybe b)) -> [a] -> m (Maybe b)
+findTraverseIndex !f = go 0
+  where
+    go !i (!x : xs) = do
+      result <- f i x
+      maybe (go (i + 1) xs) (pure . Just) result
+    go _ [] =
+      pure Nothing
+
+
+{-# INLINE untilM  #-}
+untilM :: Monad m => (a -> m Bool) -> [a] -> m ()
+untilM !f = go
+  where
+    go (!x : xs) = unlessM (f x) (go xs)
+    go []        = pure ()
diff --git a/src/Evaluation/Evaluation.hs b/src/Evaluation/Evaluation.hs
new file mode 100644
--- /dev/null
+++ b/src/Evaluation/Evaluation.hs
@@ -0,0 +1,386 @@
+module Evaluation.Evaluation (evaluatePosition, evaluatePositionBreakdown, evaluateExchange, evaluateMvvLva)
+where
+
+import           AppPrelude
+
+import           Evaluation.Material
+import           Evaluation.Parameters
+import           Evaluation.ScoreBreakdown
+import           Models.Move
+import           Models.Piece
+import           Models.Position
+import           Models.Score
+import           MoveGen.MakeMove
+import           MoveGen.PieceAttacks
+import           MoveGen.PieceCaptures
+import           MoveGen.PositionQueries
+import           Utils.Board
+
+
+
+evaluatePosition :: Position -> Score
+evaluatePosition =
+ evalScore . evaluatePositionBreakdown
+
+
+evaluatePositionBreakdown :: Position -> ScoreBreakdown
+evaluatePositionBreakdown pos =
+  let
+    !enemyPos         = makeNullMove pos
+    !scoresBatch      = getScoresBatch pos
+    !enemyScoresBatch = getScoresBatch enemyPos
+    !playerBreakdown =
+      evaluatePlayerBreakdown scoresBatch enemyScoresBatch pos
+    !enemyBreakdown  =
+      evaluatePlayerBreakdown enemyScoresBatch scoresBatch enemyPos
+    !materialScore   =
+         evalScore playerBreakdown.materialBreakdown
+       - evalScore enemyBreakdown.materialBreakdown
+    !materialTradesScore =
+      evaluateMaterialTrades materialScore pos
+  in
+    ScoreBreakdown {..}
+  where
+    ?phase       = pos.phase
+    ?colorToMove = pos.color
+
+
+evaluatePlayerBreakdown :: (?phase :: Phase, ?colorToMove :: Color)
+  => ScoresBatch -> ScoresBatch -> Position -> PlayerScoreBreakdown
+evaluatePlayerBreakdown scoresBatch enemyScoresBatch pos =
+  PlayerScoreBreakdown {
+    materialBreakdown =
+      evaluatePlayerMaterial pos pos.player pos.color
+  , bonusBreakdown    =
+      evaluatePositionBonuses scoresBatch pos
+  , penaltyBreakdown  =
+      evaluatePositionPenalties scoresBatch enemyScoresBatch pos
+  }
+
+
+evaluatePositionBonuses :: (?phase :: Phase, ?colorToMove :: Color)
+  => ScoresBatch -> Position -> BonusBreakdown
+evaluatePositionBonuses ScoresBatch {..} pos =
+  BonusBreakdown {
+    mobility        = mobility
+  , passedPawns     = evaluatePassedPawns      pos
+  , bishopPair      = evaluateBishopPair       pos
+  , knightOutposts  = evaluateKnightOutposts   pos
+  , rooksOnOpenFile = evaluateRooksOnOpenFiles pos
+  , kingPawnShield  = evaluateKingPawnShield   pos
+  , castlingRights  = evaluateCastlingRights   pos
+  }
+
+
+evaluatePositionPenalties
+  :: ScoresBatch -> ScoresBatch -> Position -> PenaltyBreakdown
+evaluatePositionPenalties
+  ScoresBatch {threats} ScoresBatch {kingThreats} Position {player, pawns} =
+  PenaltyBreakdown {
+    threats        = threats
+  , kingThreats    = kingThreats
+  , isolatedPawns  = evaluateIsolatedPawns (player & pawns)
+  , doubledPawns   = evaluateDoubledPawns  (player & pawns)
+  }
+
+
+evaluateBishopPair :: (?phase :: Phase) => Position -> Score
+evaluateBishopPair Position {player, bishops} =
+   bishopPairBonus
+   * fromIntegral (clamp (0, 1) (popCount (player & bishops) - 1))
+
+
+evaluateKnightOutposts :: (?phase :: Phase) => Position -> Score
+evaluateKnightOutposts Position {..} =
+  knightOutpostBonus
+  * fromIntegral (foldlBoard 0 (+) mapFn
+    (knights&player & defended & ranks & knightOupostFiles))
+  where
+    defended = pawnAttacks color (player&pawns)
+    mapFn !n = toReverseCondition (attackersVec !! n & enemy&pawns)
+    (!ranks, !attackersVec) = case color of
+      White -> (whiteKnightOutpostRanks , whiteKnightOutpostAttackersVec)
+      Black -> (blackKnightOutpostRanks , blackKnightOutpostAttackersVec)
+
+
+evaluateRooksOnOpenFiles :: (?phase :: Phase) => Position -> Score
+evaluateRooksOnOpenFiles Position {..} =
+  rookOnSemiOpenFileBonus
+  * (eval file_A + eval file_B + eval file_C + eval file_D
+   + eval file_E + eval file_F + eval file_G + eval file_H)
+  where
+    eval fileBoard
+      | playerRooksInFile == 0                                          = 0
+      | pawnsInFile       == 0 || lastPieceSquare pawnsInFile       <! n = 2
+      | playerPawnsInFile == 0 || lastPieceSquare playerPawnsInFile <! n = 1
+      | otherwise                                                      = 0
+      where
+        playerRooksInFile = player & rooks & fileBoard
+        playerPawnsInFile = player & pawnsInFile
+        pawnsInFile       = pawns  & fileBoard
+        n                 = lastPieceSquare playerRooksInFile
+
+    (!(<!), !lastPieceSquare) = case color of
+      White -> ((<), msb)
+      Black -> ((>), lsb)
+
+
+evaluatePassedPawns ::
+  (?phase :: Phase, ?colorToMove :: Color) => Position -> Score
+evaluatePassedPawns pos@Position {..} =
+  eval file_A + eval file_B + eval file_C + eval file_D
+  + eval file_E + eval file_F + eval file_G + eval file_H
+  where
+    eval fileBoard
+      | pawnsInFile == 0 || blockersVec !! n & enemy&pawns /= 0 = 0
+      | otherwise = passerScore + escortedPasserScore
+      where
+        passerScore
+          | isUnstoppablePawn n = unstoppablePawnBonus
+          | isFreePasser rank n = freePassedPawnTable !!% rank
+          | otherwise           = passedPawnTable     !!% rank
+        escortedPasserScore = kingEscortedPassedPawnBonus *
+           fromIntegral (getSquareDistance n enemyKingSquare
+                       - getSquareDistance n kingSquare)
+        rank            = normalizeRank $ toRank n
+        pawnsInFile     = player & pawns & fileBoard
+        n               = lastPawnSquare pawnsInFile
+        kingSquare      = lsb (player & kings)
+        enemyKingSquare = lsb (enemy & kings)
+
+    isFreePasser rank n =
+      testSquare noPieces (nextRank n)
+      && evaluateExchange (Move Pawn promotion n $ nextRank n) pos >= 0
+      where
+        promotion | rank == 7 = QueenProm
+                  | otherwise = NoProm
+
+    isUnstoppablePawn pawnSquare =
+        enemy & (knights .| bishops .| rooks .| queens) == 0
+      && kingDistance > pawnDistance
+      where
+        pawnDistance = getSquareDistance pawnSquare promotionSquare
+        kingDistance =
+          getSquareDistance kingSquare promotionSquare - kingDistanceOffset
+
+        promotionSquare = 8 * promotionRank + pawnFile
+        pawnFile        = toFile pawnSquare
+        kingSquare      = lsb (enemy & kings)
+        kingDistanceOffset
+          | color /= ?colorToMove = 1
+          | otherwise            = 0
+
+    (!normalizeRank, !nextRank, !lastPawnSquare, !blockersVec,
+     !promotionRank) =
+      case color of
+        White ->
+          (id   , (+ 8)     , msb, whitePassedPawnBlockersVec, 7)
+        Black ->
+          ((7 -), \n -> n - 8, lsb, blackPassedPawnBlockersVec, 0)
+    !noPieces = (~) (player .| enemy)
+
+
+evaluateKingPawnShield :: (?phase :: Phase) => Position -> Score
+evaluateKingPawnShield Position {..} =
+  maybe 0 go
+    $ find ((`testSquare` kingSquare) . (kingRank &))
+      [shortCastleFiles, longCastleFiles]
+  where
+    go board =
+        pawnShield1RankBonus
+        * popCountToScore (pawnShield1Rank & board & player & pawns)
+      + pawnShield2RankBonus
+        * popCountToScore (pawnShield2Rank & board & player & pawns)
+    (kingRank, pawnShield1Rank, pawnShield2Rank) = case color of
+      White -> (rank_1, rank_2, rank_3)
+      Black -> (rank_8, rank_7, rank_6)
+    kingSquare = lsb (player & kings)
+
+
+evaluateCastlingRights :: Position -> Score
+evaluateCastlingRights Position{castling}
+  | castling /= 0 = castlingRightsBonus
+  | otherwise    = 0
+
+
+evaluateIsolatedPawns :: Board -> Score
+evaluateIsolatedPawns pawns =
+  isolatedPawnPenalty * fromIntegral isolatedPawnsCount
+  where
+    isolatedPawnsCount =
+        popCountToBoard (file_B & pawns)
+        * toReverseCondition (pawns & (file_A .| file_C))
+      + popCountToBoard (file_C & pawns)
+        * toReverseCondition (pawns & (file_B .| file_D))
+      + popCountToBoard (file_D & pawns)
+        * toReverseCondition (pawns & (file_C .| file_E))
+      + popCountToBoard (file_E & pawns)
+        * toReverseCondition (pawns & (file_D .| file_F))
+      + popCountToBoard (file_F & pawns)
+        * toReverseCondition (pawns & (file_E .| file_G))
+      + popCountToBoard (file_G & pawns)
+        * toReverseCondition (pawns & (file_F .| file_H))
+
+
+evaluateDoubledPawns :: Board -> Score
+evaluateDoubledPawns pawns =
+  doubledPawnPenalty * fromIntegral doubledPawnsCount
+  where
+    doubledPawnsCount =
+        max 1 (popCount (file_A & pawns))
+      + max 1 (popCount (file_B & pawns))
+      + max 1 (popCount (file_C & pawns))
+      + max 1 (popCount (file_D & pawns))
+      + max 1 (popCount (file_E & pawns))
+      + max 1 (popCount (file_F & pawns))
+      + max 1 (popCount (file_G & pawns))
+      + max 1 (popCount (file_H & pawns))
+      - 8
+
+
+evaluateMaterialTrades :: Score -> Position -> Score
+evaluateMaterialTrades materialScore Position {..} =
+    convert (losingPenalty - winningPenalty)
+  where
+    losingPenalty =
+      pieceTradesPenalty * losingMissingPieces * absoluteMaterialScore / 700
+    winningPenalty =
+      pawnTradesPenalty  * winningMissingPawns * absoluteMaterialScore / 800
+    losingMissingPieces = max 0
+      (7 - popCountToScore (losingBoard & (knights .| bishops .| rooks .| queens)))
+    winningMissingPawns =
+      8 - popCountToScore (winningBoard & pawns)
+    absoluteMaterialScore = abs materialScore
+    (winningBoard, losingBoard, convert)
+      | materialScore > 0 = (player, enemy , id)
+      | otherwise         = (enemy , player, negate)
+
+
+getScoresBatch :: (?phase :: Phase) => Position -> ScoresBatch
+getScoresBatch pos
+  | isKingInCheck pos = emptyScoresBatch
+getScoresBatch Position {..} = ScoresBatch {..}
+  where
+    mobility =
+      knightsMobility + bishopsMobility + rooksMobility + queensMobility
+
+    kingThreats =
+      (kingThreatPiecesTable !! piecesCount) * kingThreatScore / 100
+
+    piecesCount =
+      knightsCount + bishopsCount + rooksCount + queensCount
+
+    threats =
+      queenThreat
+        * popCountToScore (player & queens & (minorDefended .| pawnDefended))
+      + rookThreat
+        * popCountToScore (player & rooks & pawnDefended)
+      + minorPieceThreat
+        * popCountToScore (player & (knights .| bishops) & pawnDefended)
+
+    kingThreatScore =
+        threatByMinorPenalty * fromIntegral
+          (byKnightThreats + byBishopThreats)
+      + threatByRookPenalty  * fromIntegral byRookThreats
+      + threatByQueenPenalty * fromIntegral byQueenThreats
+
+    (knightsMobility, byKnightThreats, knightsCount) =
+      foldBoardScores knightMobilityTable
+        knightAttacks
+        pawnDefended
+        (unpinned&knights)
+
+    (bishopsMobility, byBishopThreats, bishopsCount) =
+      foldBoardScores bishopMobilityTable
+        (bishopMoves (allPieces .\ player & queens) pinnedPieces king)
+        pawnDefended
+        (player&bishops)
+
+    (rooksMobility, byRookThreats, rooksCount) =
+      foldBoardScores rookMobilityTable
+        (rookMoves (allPieces .\ player & queens)
+         pinnedPieces king)
+        (pawnDefended .| minorDefended)
+        (player&rooks)
+
+    (queensMobility, byQueenThreats, queensCount) =
+      foldBoardScores queenMobilityTable
+        (queenMoves allPieces pinnedPieces king)
+        (pawnDefended .| minorDefended .| rookDefended)
+        (player&queens)
+
+
+    foldBoardScores !mobilityTable !movesFn !defended !board =
+      foldlBoard (0, 0, 0) foldFn movesFn board
+      where
+        foldFn (!x, !y, !z) !attackArea =
+          (x + mobilityTable
+           !!% popCount (attackArea .\ (player .| defended)),
+           y + threatenedSquares,
+           z + toCondition threatenedSquares)
+          where
+            !threatenedSquares = popCount (enemyKingArea & attackArea)
+
+    !king           = player & kings
+    !unpinned       = player .\ pinnedPieces
+    !allPieces      = player .| enemy
+
+    !enemyKingArea  =
+      kingAttacks (lsb (enemy&kings))
+    !pawnDefended   =
+      pawnAttacks (reverseColor color) (enemy&pawns)
+    !minorDefended =
+         foldBoardAttacks knightAttacks (enemy&knights)
+      .| foldBoardAttacks (bishopAttacks allPieces) (enemy&bishops)
+    !rookDefended =
+       foldBoardAttacks (rookAttacks allPieces) (enemy&rooks)
+
+
+evaluateExchange :: Move -> Position -> Score
+evaluateExchange initialMv initialPos =
+  let ?phase = initialPos.phase
+  in go initialMv initialPos
+  where
+    !square = initialMv.end
+    go !mv !pos =
+      let newPos = makeMove mv pos
+      in evaluateCapturedPiece mv pos
+      - case headMay $ staticExchangeCaptures square newPos of
+        Just newMv -> max 0 $! go newMv newPos
+        Nothing    -> 0
+
+
+{-# INLINE evaluateMvvLva #-}
+evaluateMvvLva :: Move -> Position -> Word8
+evaluateMvvLva Move {..} pos =
+  promotionValue + maybe 0 exchangeValue (maybeCapturedPieceAt end pos)
+  where
+    exchangeValue capturedPiece =
+      10 * (getPieceValue capturedPiece + 1)
+      - getPieceValue piece
+    promotionValue = 10 * (promotionPieceValue + 1)
+    promotionPieceValue = case promotion of
+      QueenProm  -> getPieceValue Queen
+      KnightProm -> getPieceValue Knight
+      BishopProm -> getPieceValue Bishop
+      RookProm   -> getPieceValue Rook
+      NoProm     -> 0
+
+
+data ScoresBatch = ScoresBatch {
+    mobility    :: Score
+  , threats     :: Score
+  , kingThreats :: Score
+
+}
+
+emptyScoresBatch :: ScoresBatch
+emptyScoresBatch = ScoresBatch {
+    mobility    = 0
+  , threats     = 0
+  , kingThreats = 0
+}
+
+
+popCountToScore :: Board -> Score
+popCountToScore = fromIntegral . popCount
diff --git a/src/Evaluation/Material.hs b/src/Evaluation/Material.hs
new file mode 100644
--- /dev/null
+++ b/src/Evaluation/Material.hs
@@ -0,0 +1,66 @@
+module Evaluation.Material (evaluatePlayerMaterial, evaluateCapturedPiece) where
+
+import           AppPrelude
+
+import           Evaluation.Parameters
+import           Evaluation.ScoreBreakdown
+import           Models.Move
+import           Models.Piece
+import           Models.Position
+import           Models.Score
+import           Utils.Board
+
+
+evaluatePlayerMaterial
+  :: (?phase :: Phase) => Position -> Board -> Color -> MaterialBreakdown
+evaluatePlayerMaterial Position {..} !board = \case
+    White -> MaterialBreakdown
+          (boardScore queenScore                     (board & queens))
+          (boardScore rookScore                      (board & rooks))
+          (boardScore bishopScore                    (board & bishops))
+          (boardScore knightScore                    (board & knights))
+          (boardScore pawnScore                      (board & pawns))
+          (ScorePair 0 (whiteKingSquareTable !!% lsb (board & kings)))
+    Black -> MaterialBreakdown
+          (boardScore queenScore                     (board & queens))
+          (boardScore rookScore                      (board & rooks))
+          (boardScore bishopScore                    (board & bishops))
+          (boardScore knightScore                    (board & knights))
+          (boardScore pawnScore                      (board & pawns))
+          (ScorePair 0 (blackKingSquareTable !!% lsb (board & kings)))
+
+
+{-# INLINE  boardScore #-}
+boardScore :: Score -> Board -> ScorePair
+boardScore !pieceTypeScore !board =
+  ScorePair materialScore 0
+  where
+    materialScore = pieceTypeScore * fromIntegral (popCount board)
+
+
+{-# INLINE  evaluateCapturedPiece #-}
+evaluateCapturedPiece :: (?phase :: Phase) => Move -> Position -> Score
+evaluateCapturedPiece Move {..} pos =
+  evaluatePromotion promotion
+  + maybe 0 evaluatePiece (maybeCapturedPieceAt end pos)
+
+
+{-# INLINE  evaluatePromotion #-}
+evaluatePromotion :: (?phase :: Phase) => Promotion -> Score
+evaluatePromotion = \case
+  KnightProm -> knightScore
+  BishopProm -> bishopScore
+  RookProm   -> rookScore
+  QueenProm  -> queenScore
+  NoProm     -> 0
+
+
+{-# INLINE  evaluatePiece #-}
+evaluatePiece :: (?phase :: Phase) => Piece -> Score
+evaluatePiece = \case
+  Pawn   -> pawnScore
+  Knight -> knightScore
+  Bishop -> bishopScore
+  Rook   -> rookScore
+  Queen  -> queenScore
+  King   -> 0
diff --git a/src/Evaluation/Parameters.hs b/src/Evaluation/Parameters.hs
new file mode 100644
--- /dev/null
+++ b/src/Evaluation/Parameters.hs
@@ -0,0 +1,162 @@
+module Evaluation.Parameters where
+
+import           AppPrelude
+import           Data.List.Split           (chunksOf)
+import           Evaluation.ScoreBreakdown
+import           Models.Piece
+import           Models.Score
+import           Utils.Board
+
+import qualified Data.Vector.Storable      as Vector
+
+
+-- Material scores
+pawnScore :: Score
+pawnScore = 100
+
+bishopScore :: Score
+bishopScore = 340
+
+knightScore :: Score
+knightScore = 340
+
+rookScore :: (?phase :: Phase) => Score
+rookScore = taperScore $ ScorePair 500 550
+
+queenScore :: (?phase :: Phase) => Score
+queenScore = taperScore $ ScorePair 1000 1050
+
+
+-- Bonuses
+castlingRightsBonus :: Score
+castlingRightsBonus = 20
+
+bishopPairBonus :: (?phase :: Phase) => Score
+bishopPairBonus = taperScore $ ScorePair 25 100
+
+knightOutpostBonus :: (?phase :: Phase) => Score
+knightOutpostBonus = taperScore $ ScorePair 50 0
+
+rookOnSemiOpenFileBonus :: (?phase :: Phase) => Score
+rookOnSemiOpenFileBonus = taperScore $ ScorePair 10 0
+
+pawnShield1RankBonus :: (?phase :: Phase) => Score
+pawnShield1RankBonus = taperScore $ ScorePair 10 0
+
+pawnShield2RankBonus :: (?phase :: Phase) => Score
+pawnShield2RankBonus = taperScore $ ScorePair 5 0
+
+unstoppablePawnBonus :: Score
+unstoppablePawnBonus = 700
+
+kingEscortedPassedPawnBonus :: (?phase :: Phase) => Score
+kingEscortedPassedPawnBonus = taperScore $ ScorePair 0 20
+
+passedPawnTable :: Vector ScorePair
+passedPawnTable = Vector.fromList $ map (uncurry ScorePair)
+  [(0, 0), (0, 5), (5, 20), (20, 50),
+   (30, 100), (40, 150), (50, 200)]
+
+freePassedPawnTable :: Vector ScorePair
+freePassedPawnTable = Vector.fromList $ map (uncurry ScorePair)
+  [(0, 0), (0, 10), (5, 35), (20, 80),
+   (30, 150), (40, 230), (50, 350)]
+
+
+knightMobilityTable :: Vector ScorePair
+knightMobilityTable = Vector.fromList $ map (uncurry ScorePair)
+  [(-15, -30), (-5, -10), (-1, -2), (2, 4), (5, 10),
+   (8, 16), (11, 21), (13, 26), (15, 30)]
+
+bishopMobilityTable :: Vector ScorePair
+bishopMobilityTable = Vector.fromList $ map (uncurry ScorePair)
+  [(-25, -75), (-11, -33), (-6, -18), (-1, -3),
+   (3, 9), (6, 18), (9, 27), (12, 36), (14, 42),
+   (17, 51), (19, 57), (21, 63), (23, 69), (25, 75)]
+
+rookMobilityTable :: Vector ScorePair
+rookMobilityTable = Vector.fromList $ map (uncurry ScorePair)
+  [(-10, -50), (-4, -20), (-2, -10), (0, 0), (1, 5),
+   (2, 10), (3, 15), (4, 20), (5, 25), (6, 30),
+   (7, 34), (8, 38), (9, 42), (10, 46), (10, 50)]
+
+queenMobilityTable :: Vector ScorePair
+queenMobilityTable = Vector.fromList $ map (uncurry ScorePair)
+  [(-10, -50), (-6, -30), (-5, -22), (-4, -16), (-2, -10),
+   (-1, -5), (0, -1), (1, 3), (1, 7), (2, 11), (2, 14),
+   (3, 17), (3, 20), (4, 23), (4, 26), (5, 28), (5, 30),
+   (6, 32), (6, 34), (7, 36), (7, 38), (8, 40), (8, 42),
+   (9, 44), (9, 46), (10, 48), (10, 49), (10, 50)]
+
+
+
+-- Penalties
+threatByMinorPenalty :: Score
+threatByMinorPenalty = 20
+
+threatByRookPenalty :: Score
+threatByRookPenalty = 40
+
+threatByQueenPenalty :: Score
+threatByQueenPenalty = 80
+
+isolatedPawnPenalty :: Score
+isolatedPawnPenalty = 25
+
+doubledPawnPenalty :: Score
+doubledPawnPenalty = 20
+
+minorPieceThreat :: Score
+minorPieceThreat = 20
+
+rookThreat :: Score
+rookThreat = 30
+
+queenThreat :: Score
+queenThreat = 40
+
+pieceTradesPenalty :: Score
+pieceTradesPenalty = 15
+
+pawnTradesPenalty :: Score
+pawnTradesPenalty = 30
+
+
+kingThreatPiecesTable :: Vector Score
+kingThreatPiecesTable = Vector.fromList
+  [0, 0, 0, 50, 75, 88, 94, 97, 99, 100,
+   100, 100, 100, 100, 100, 100, 100]
+
+
+blackKingSquareTable :: Vector ScorePair
+blackKingSquareTable = Vector.fromList $ map (`ScorePair` 0)
+  [-30,-40,-40,-50,-50,-40,-40,-30,
+   -30,-40,-40,-50,-50,-40,-40,-30,
+   -30,-40,-40,-50,-50,-40,-40,-30,
+   -30,-40,-40,-50,-50,-40,-40,-30,
+   -20,-30,-30,-40,-40,-30,-30,-20,
+   -10,-20,-20,-30,-30,-20,-20,-10,
+     10, 10, 0, -10, -10, 0,  10, 10,
+    30, 30, 30,  0,  0, 0, 30, 30]
+
+whiteKingSquareTable :: Vector ScorePair
+whiteKingSquareTable = reverseSquareTable blackKingSquareTable
+
+infixl 9 !!%
+(!!%) :: (?phase::Phase) => Vector ScorePair -> Int -> Score
+(!!%) table idx = taperScore (table !! idx)
+
+
+taperScore :: (?phase :: Phase) => ScorePair -> Score
+taperScore (ScorePair mgScore egScore) =
+  (?phase * mgScore + (totalPhase - ?phase) * egScore) / totalPhase
+
+
+getSquareTableIndex :: Board -> Color -> Square
+getSquareTableIndex board color =
+  lsb board + 64 * fromIntegral color
+
+
+reverseSquareTable :: Storable a => Vector a -> Vector a
+reverseSquareTable =
+  fromList . fold . reverse . chunksOf 8 . toList
diff --git a/src/Evaluation/ScoreBreakdown.hs b/src/Evaluation/ScoreBreakdown.hs
new file mode 100644
--- /dev/null
+++ b/src/Evaluation/ScoreBreakdown.hs
@@ -0,0 +1,160 @@
+module Evaluation.ScoreBreakdown where
+
+import           AppPrelude
+
+import           Models.Score
+
+import           Bookhound.Utils.Text
+import           Foreign.Storable.Generic
+
+
+data ScoreBreakdown = ScoreBreakdown
+  { playerBreakdown     :: PlayerScoreBreakdown
+  , enemyBreakdown      :: PlayerScoreBreakdown
+  , materialTradesScore :: Score
+  }
+
+data PlayerScoreBreakdown = PlayerScoreBreakdown
+  { materialBreakdown :: MaterialBreakdown
+  , bonusBreakdown    :: BonusBreakdown
+  , penaltyBreakdown  :: PenaltyBreakdown
+  }
+
+data MaterialBreakdown = MaterialBreakdown
+  { queens  :: ScorePair
+  , rooks   :: ScorePair
+  , bishops :: ScorePair
+  , knights :: ScorePair
+  , pawns   :: ScorePair
+  , kings   :: ScorePair
+  }
+
+data BonusBreakdown = BonusBreakdown
+  { mobility        :: Score
+  , passedPawns     :: Score
+  , bishopPair      :: Score
+  , knightOutposts  :: Score
+  , rooksOnOpenFile :: Score
+  , kingPawnShield  :: Score
+  , castlingRights  :: Score
+  }
+
+data PenaltyBreakdown = PenaltyBreakdown
+  { threats       :: Score
+  , kingThreats   :: Score
+  , isolatedPawns :: Score
+  , doubledPawns  :: Score
+  }
+
+data ScorePair = ScorePair Score Score
+  deriving Generic
+
+instance GStorable ScorePair
+
+class EvalScore a where
+  evalScore :: a -> Score
+
+
+instance EvalScore ScoreBreakdown where
+  evalScore ScoreBreakdown {..} =
+    evalScore playerBreakdown - evalScore enemyBreakdown
+
+
+instance EvalScore PlayerScoreBreakdown where
+  evalScore PlayerScoreBreakdown {..} =
+      evalScore materialBreakdown
+    + evalScore bonusBreakdown
+    - evalScore penaltyBreakdown
+
+instance EvalScore MaterialBreakdown where
+  evalScore MaterialBreakdown {..} =
+      evalScore queens  + evalScore rooks
+    + evalScore bishops + evalScore knights
+    + evalScore pawns   + evalScore kings
+
+
+instance EvalScore BonusBreakdown where
+  evalScore BonusBreakdown {..} =
+      mobility        + passedPawns
+    + bishopPair      + knightOutposts
+    + rooksOnOpenFile + kingPawnShield
+    + castlingRights
+
+
+instance EvalScore PenaltyBreakdown where
+  evalScore PenaltyBreakdown {..} =
+      threats       + kingThreats
+    + isolatedPawns + doubledPawns
+
+instance EvalScore ScorePair where
+  evalScore (ScorePair x y) = x + y
+
+
+instance Show ScoreBreakdown where
+  show breakdown@ScoreBreakdown {..} = unlines
+    ["Player: " <> indentBreak (show playerBreakdown),
+     "Enemy:  " <> indentBreak (show enemyBreakdown),
+     totalScoreLine,
+     "Total: "  <> show (evalScore breakdown),
+     totalScoreLine]
+
+
+
+instance Show PlayerScoreBreakdown where
+  show breakdown@PlayerScoreBreakdown {..} = unlines
+    ["Material:       " <> indentBreak (show materialBreakdown),
+     "Bonus Scores:   " <> indentBreak (show bonusBreakdown),
+     "Penalty Scores: " <> indentBreak (show penaltyBreakdown),
+     totalScoreLine,
+     "Player Total: "   <> show (evalScore breakdown),
+     totalScoreLine]
+
+instance Show MaterialBreakdown where
+  show breakdown@MaterialBreakdown {..} = unlines
+    ["Queens:  "        <> show queens,
+     "Rooks:   "        <> show rooks,
+     "Bishops: "        <> show bishops,
+     "Knights: "        <> show knights,
+     "Pawns:   "        <> show pawns,
+     "Kings:   "        <> show kings,
+     totalScoreLine,
+     "Material Total: " <> show (evalScore breakdown),
+     totalScoreLine]
+
+
+instance Show BonusBreakdown where
+  show breakdown@BonusBreakdown {..} = unlines
+    ["Mobility:            " <> show mobility,
+     "Passed Pawns:        " <> show passedPawns,
+     "Bishop Pair:         " <> show bishopPair,
+     "Knight Outposts:     " <> show knightOutposts,
+     "Rooks On Open Files: " <> show rooksOnOpenFile,
+     "King Pawn Shield:    " <> show kingPawnShield,
+     "Castling Rights:     " <> show castlingRights,
+     totalScoreLine,
+     "Bonus Total: "         <> show (evalScore breakdown),
+     totalScoreLine]
+
+
+instance Show PenaltyBreakdown where
+  show breakdown@PenaltyBreakdown {..} = unlines
+    ["Threats:        " <> show threats,
+     "King Threats:   " <> show kingThreats,
+     "Isolated pawns: " <> show isolatedPawns,
+     "Doubled pawns:  " <> show doubledPawns,
+     totalScoreLine,
+     "Penalty Total: "  <> show (evalScore breakdown),
+     totalScoreLine]
+
+instance Show ScorePair where
+  show (ScorePair x y) = "(" <> fill 4 (show x) <> ", " <> show y <> ")"
+
+
+fill :: Int -> String -> String
+fill n str = replicate (n - length str) ' ' <> str
+
+indentBreak :: String -> String
+indentBreak str = "\n" <> indent 2 str
+
+totalScoreLine :: String
+totalScoreLine = replicate 20 '-'
diff --git a/src/Models/Command.hs b/src/Models/Command.hs
new file mode 100644
--- /dev/null
+++ b/src/Models/Command.hs
@@ -0,0 +1,123 @@
+module Models.Command where
+
+import           AppPrelude
+
+import           Models.Piece
+import           Models.Position
+import           Models.Score
+import           Utils.Board
+
+import           Data.Word
+
+
+data Command
+  = Uci
+  | UciNewGame
+  | IsReady
+  | Search SearchOptions
+  | SetPosition PositionSpec
+  | SetOption OptionSpec
+  | Ponderhit
+  | Stop
+  | Quit
+  | MakeMoves [UnknownMove]
+  | Perft Depth
+  | Divide Depth
+  | Evaluate
+  | Display
+  | Flip
+
+
+data EngineInfo = EngineInfo
+  { name    :: Text
+  , version :: Text
+  , author  :: Text
+  }
+
+data EngineOptions = EngineOptions
+  { hashSize :: Word16
+  , ponder   :: Bool
+  }
+
+data EngineOption = SpinOption
+  { deflt :: Word16
+  , lo    :: Word16
+  , hi    :: Word16
+  }
+  | CheckOption Bool
+  | ButtonOption
+
+
+data OptionSpec
+  = HashSize Int
+  | Ponder Bool
+  | ClearHash
+
+data SearchOptions = SearchOptions
+  { searchMoves        :: [UnknownMove]
+  , infinite           :: Bool
+  , targetDepth        :: Depth
+  , moveTime           :: Maybe Int
+  , whiteTime          :: Maybe Int
+  , whiteIncrement     :: Maybe Int
+  , blackTime          :: Maybe Int
+  , blackIncrement     :: Maybe Int
+  , movesUntilNextTime :: Maybe Int
+  , findMate           :: Maybe Int
+  , maxNodes           :: Maybe Word64
+  }
+
+
+data PositionSpec = PositionSpec
+  { initialPosition :: Position,
+    moves           :: [UnknownMove]
+  }
+
+
+data UnknownMove = UnknownMove
+  { start     :: Square,
+    end       :: Square,
+    promotion :: Promotion
+  }
+
+
+engineInfo :: EngineInfo
+engineInfo = EngineInfo
+  { name    = "Turncoat"
+  , version = "1.0"
+  , author  = "Alberto Perez"
+  }
+
+
+defaultEngineOptions :: EngineOptions
+defaultEngineOptions = EngineOptions
+  { hashSize = 16
+  , ponder   = False
+  }
+
+
+defaultSearchOptions :: SearchOptions
+defaultSearchOptions = SearchOptions
+  { searchMoves        = []
+  , infinite           = False
+  , targetDepth        = maxBound
+  , findMate           = Nothing
+  , whiteTime          = Nothing
+  , whiteIncrement     = Nothing
+  , blackTime          = Nothing
+  , blackIncrement     = Nothing
+  , movesUntilNextTime = Nothing
+  , maxNodes           = Nothing
+  , moveTime           = Nothing
+  }
+
+
+instance Show EngineOption where
+  show = \case
+    SpinOption {..}    -> "type spin" <> " default " <> show deflt
+                        <> " min " <> show lo <> " max " <> show hi
+    CheckOption deflt -> "type check" <> " default " <> toLower (show deflt)
+    ButtonOption      -> "type button"
+
+
+type Task = Async ()
diff --git a/src/Models/Move.hs b/src/Models/Move.hs
new file mode 100644
--- /dev/null
+++ b/src/Models/Move.hs
@@ -0,0 +1,141 @@
+module Models.Move (Move(..), StorableMove(..), encodeMove, decodeMove,  foldBoardAttacks, foldBoardMoves, foldBoardMovesConst, foldBoardPawnMovesConst, foldBoardSquares, foldlBoard, showBoard) where
+
+import           AppPrelude
+import           Models.Piece
+import           Utils.Board
+
+import           Data.Bits
+import           Data.List            (iterate)
+import qualified Data.Vector.Storable as Vector
+import           Test.QuickCheck      (Arbitrary (..), chooseInt)
+
+
+data Move = Move {
+  piece     :: Piece,
+  promotion :: Promotion,
+  start     :: Square,
+  end       :: Square
+} deriving (Eq, Ord, Generic)
+
+instance Hashable Move
+
+instance Arbitrary Move where
+  arbitrary = Move
+    <$> arbitrary <*> arbitrary
+    <*> chooseInt (0, 63) <*> chooseInt (0, 63)
+
+newtype StorableMove = StorableMove Word32
+  deriving (Eq, Ord, Num, Bits, Generic, Storable)
+
+
+encodeMove :: Maybe Move -> StorableMove
+encodeMove Nothing = bit 31
+encodeMove (Just Move {..}) = StorableMove
+  (fromIntegral start
+  .|. (fromIntegral end << 8)
+  .|. (fromIntegral pieceN << 16)
+  .|. (fromIntegral promotionN << 24))
+  where
+    Piece pieceN = piece
+    Promotion promotionN = promotion
+
+decodeMove :: StorableMove -> Maybe Move
+decodeMove (StorableMove n)
+  | testBit n 31 = Nothing
+  | otherwise = Just $! Move {
+      start = start,
+      end = end,
+      piece = piece,
+      promotion = promotion
+    }
+    where
+      start = fromIntegral n .&. 63
+      end = fromIntegral (n >> 8) .&. 63
+      piece = Piece $ fromIntegral (n >> 16)
+      promotion = Promotion $ fromIntegral ((n >> 24) .&. 7)
+
+
+{-# INLINE  foldBoardAttacks #-}
+foldBoardAttacks :: (Square -> Board) -> Board -> Board
+foldBoardAttacks !f !board = foldlBoard 0 (.|) f board
+
+
+{-# INLINE  foldBoardMoves #-}
+foldBoardMoves :: Piece -> (Square -> Board) -> Board -> [Move] -> [Move]
+foldBoardMoves !piece !f !board moves =
+  foldlBoard moves (foldBoardSquares piece f) id board
+
+
+{-# INLINE  foldBoardMovesConst #-}
+foldBoardMovesConst :: Piece -> Square -> Board -> [Move] -> [Move]
+foldBoardMovesConst !piece !end !board moves =
+  foldlBoard moves genMoves id board
+  where
+    genMoves xs start = Move piece NoProm start end : xs
+
+
+{-# INLINE  foldBoardPawnMovesConst #-}
+foldBoardPawnMovesConst :: Square -> Board -> [Move] -> [Move]
+foldBoardPawnMovesConst !end !board moves =
+  foldlBoard moves genMoves id board
+  where
+    genMoves xs start = Move Pawn promotion start end : xs
+    !promotion
+      | testSquare (rank_1 .| rank_8) end = QueenProm
+      | otherwise                         = NoProm
+
+
+{-# INLINE  foldBoardSquares #-}
+foldBoardSquares :: Piece -> (Square -> Board) -> [Move] -> Square -> [Move]
+foldBoardSquares Pawn !f moves !start =
+    foldlBoard moves (flip $ genPawnMoves start) id $! f start
+
+foldBoardSquares piece !f moves !start =
+    foldlBoard moves (flip cons) (Move piece NoProm start) (f start)
+
+
+{-# INLINE  genPawnMoves #-}
+genPawnMoves :: Square -> Square -> [Move] -> [Move]
+genPawnMoves !start !end xs
+  | testSquare (rank_1 .| rank_8) end =
+    Move Pawn QueenProm start end
+    : Move Pawn KnightProm start end
+    : Move Pawn RookProm start end
+    : Move Pawn BishopProm start end
+    : xs
+  | otherwise =
+    Move Pawn NoProm start end : xs
+
+
+{-# INLINE  foldlBoard #-}
+foldlBoard :: a -> (a -> b -> a) -> (Square -> b) -> Board -> a
+foldlBoard !initial !foldFn !mapFn = go 0 initial
+  where
+    go  _ acc  0     = acc
+    go !i !acc board = go (i' + 1) acc' board'
+      where
+        acc'    = foldFn acc $! mapFn i'
+        i'      = i + current
+        board'  = (board >> current) >> 1
+        current = lsb board
+
+
+showBoard :: Board -> String
+showBoard board = unlines $ map showBin
+                          $ reverse $ take 8
+                          $ iterate (>> 8) board
+  where
+    showBin w = intersperse ' ' [sb (testSquare w i) | i <- [0 .. 7]]
+    sb False = '0'
+    sb True  = 'X'
+
+
+showSquare :: Square -> String
+showSquare n = [fileChars !! toFile n, rankChars !! toRank n]
+  where
+    fileChars = Vector.fromList ['a' .. 'h']
+    rankChars = Vector.fromList ['1' .. '8']
+
+
+instance Show Move where
+  show (Move {..}) = showSquare start <> showSquare end <> show promotion
diff --git a/src/Models/Piece.hs b/src/Models/Piece.hs
new file mode 100644
--- /dev/null
+++ b/src/Models/Piece.hs
@@ -0,0 +1,130 @@
+module Models.Piece where
+
+import           AppPrelude
+import           Data.Char                 (isUpper)
+import qualified Data.Char                 as Char
+import qualified Data.Set                  as Set
+import           Test.QuickCheck           (Arbitrary, elements)
+import           Test.QuickCheck.Arbitrary (Arbitrary (..))
+
+
+newtype Piece = Piece Word8
+  deriving (Eq, Ord, Enum, Bounded, Hashable, Storable)
+
+instance Arbitrary Piece where
+  arbitrary =
+    elements [Pawn, Knight, Bishop, Rook, Queen]
+
+
+newtype Promotion = Promotion Word8
+  deriving (Eq, Ord, Enum, Hashable, Storable)
+
+instance Arbitrary Promotion where
+  arbitrary =
+    elements [KnightProm, BishopProm, RookProm, QueenProm]
+
+newtype Color = Color Word8
+  deriving (Eq, Ord, Enum, Num, Real, Integral)
+
+data CastlingRights = KingSide | QueenSide
+
+
+bestPromotions :: Set Promotion
+bestPromotions = Set.fromList [NoProm, QueenProm]
+
+
+{-# COMPLETE Pawn, Knight, Bishop, Rook, Queen, King #-}
+pattern Pawn, Knight, Bishop, Rook, Queen, King :: Piece
+pattern Pawn   = Piece 0
+pattern Knight = Piece 1
+pattern Bishop = Piece 2
+pattern Rook   = Piece 3
+pattern Queen  = Piece 4
+pattern King   = Piece 5
+
+{-# COMPLETE  NoProm, KnightProm, BishopProm, RookProm, QueenProm #-}
+pattern NoProm, KnightProm, BishopProm, RookProm, QueenProm :: Promotion
+pattern NoProm     = Promotion 0
+pattern KnightProm = Promotion 1
+pattern BishopProm = Promotion 2
+pattern RookProm   = Promotion 3
+pattern QueenProm  = Promotion 4
+
+{-# COMPLETE White, Black #-}
+pattern White, Black :: Color
+pattern White = Color 0
+pattern Black = Color 1
+
+
+getPieceValue :: Piece -> Word8
+getPieceValue (Piece n) = n
+
+reverseColor :: Color -> Color
+reverseColor (Color color) =
+  Color (1 - color)
+
+pieceToChar :: (Piece, Color) -> Char
+pieceToChar (piece, color) =
+  case color of
+    White -> Char.toUpper char
+    Black -> char
+  where
+    char = case piece of
+      Pawn   -> 'p'
+      Knight -> 'n'
+      Bishop -> 'b'
+      Rook   -> 'r'
+      Queen  -> 'q'
+      King   -> 'k'
+
+charToPiece :: Char -> Maybe (Piece, Color)
+charToPiece char = (,color) <$> piece
+  where
+    color | isUpper char = White
+          | otherwise    = Black
+    piece = case Char.toLower char of
+      'p' -> Just Pawn
+      'n' -> Just Knight
+      'b' -> Just Bishop
+      'r' -> Just Rook
+      'q' -> Just Queen
+      'k' -> Just King
+      _   -> Nothing
+
+charToColor :: Char -> Maybe Color
+charToColor char = case char of
+  'w' -> Just White
+  'b' -> Just Black
+  _   -> Nothing
+
+charToCastlingRights :: Char -> Maybe (CastlingRights, Color)
+charToCastlingRights char = (,color) <$> piece
+  where
+    color | isUpper char = White
+          | otherwise    = Black
+    piece = case Char.toLower char of
+      'k' -> Just KingSide
+      'q' -> Just QueenSide
+      _   -> Nothing
+
+instance Show Piece where
+  show = \case
+    Pawn -> "Pawn"
+    Knight -> "Knight"
+    Bishop -> "Bishop"
+    Rook -> "Rook"
+    Queen -> "Queen"
+    King -> "King"
+
+instance Show Promotion where
+  show = \case
+    NoProm -> mempty
+    KnightProm -> "n"
+    BishopProm -> "b"
+    RookProm -> "r"
+    QueenProm -> "q"
+
+instance Show Color where
+  show = \case
+    White -> "White"
+    Black -> "Black"
diff --git a/src/Models/Position.hs b/src/Models/Position.hs
new file mode 100644
--- /dev/null
+++ b/src/Models/Position.hs
@@ -0,0 +1,170 @@
+module Models.Position where
+
+
+import           AppPrelude
+import           Models.Move        (foldlBoard)
+import           Models.Piece
+import           Models.Score
+import           Utils.Board
+
+import           Data.Bitraversable (bisequence)
+import           Data.List.Split    (chunksOf)
+import           Data.Maybe         (fromJust)
+import           Test.QuickCheck    (Arbitrary (..))
+
+
+data Position = Position {
+    previousPositions :: ~[ZKey]
+  , halfMoveClock     :: Ply
+  , phase             :: Phase
+  , color             :: Color
+  , player            :: Board
+  , enemy             :: Board
+  , pawns             :: Board
+  , knights           :: Board
+  , bishops           :: Board
+  , rooks             :: Board
+  , queens            :: Board
+  , kings             :: Board
+  , enPassant         :: Board
+  , castling          :: Board
+  , attacked          :: Board
+  , leapingCheckers   :: Board
+  , sliderCheckers    :: Board
+  , pinnedPieces      :: Board
+}
+
+newtype ZKey = ZKey Word64
+  deriving (Eq, Show, Generic, Ord, Num, Hashable, Storable, Arbitrary)
+
+
+startPosition :: Position
+startPosition = emptyPosition {
+    color    = White
+  , phase    = totalPhase
+  , player   = rank_1  .| rank_2
+  , enemy    = rank_7  .| rank_8
+  , pawns    = rank_2  .| rank_7
+  , rooks    = (rank_1 .| rank_8) & (file_A .| file_H)
+  , knights  = (rank_1 .| rank_8) & (file_B .| file_G)
+  , bishops  = (rank_1 .| rank_8) & (file_C .| file_F)
+  , queens   = (rank_1 .| rank_8) & file_D
+  , kings    = (rank_1 .| rank_8) & file_E
+  , castling = (rank_1 .| rank_8) & (file_A .| file_E .| file_H)
+}
+
+
+emptyPosition :: Position
+emptyPosition = Position {
+    previousPositions = []
+  , color             = White
+  , halfMoveClock     = 0
+  , phase             = 0
+  , player            = 0
+  , enemy             = 0
+  , pawns             = 0
+  , rooks             = 0
+  , knights           = 0
+  , bishops           = 0
+  , queens            = 0
+  , kings             = 0
+  , castling          = 0
+  , attacked          = 0
+  , enPassant         = 0
+  , leapingCheckers   = 0
+  , sliderCheckers    = 0
+  , pinnedPieces      = 0
+}
+
+
+{-# INLINE  getZobristKey #-}
+getZobristKey :: Position -> ZKey
+getZobristKey pos@Position {..} = ZKey
+  (piecesHash ^ castlingHash ^ enPassantHash ^ sideToMoveHash)
+  where
+
+    !piecesHash =
+      foldlBoard 0 (^) getPieceHash (player .| enemy)
+
+    !castlingHash = castlingRngVec !! idx
+      where
+      idx =   inBoard file_A + 2 * inBoard file_H
+        + 4 * inBoard rank_1 + 8 * inBoard rank_8
+      inBoard x = fromIntegral $ toCondition (castling & x)
+
+    !enPassantHash = toCondition enPassant * enPassantRngVec !! idx
+      where
+        idx = toFile (lsb enPassant)
+
+    !sideToMoveHash = fromIntegral colorN * sideToMoveRng
+      where
+        Color colorN = color
+
+    getPieceHash n = pieceRngVec !! idx
+      where
+      idx = n + 64 * (fromIntegral piece + 6 * fromIntegral pieceColor)
+      (Piece piece, Color pieceColor) = fromJust $ pieceAt n pos
+
+
+{-# INLINE  pieceAt #-}
+pieceAt :: Square -> Position -> Maybe (Piece, Color)
+pieceAt n (Position {..}) = bisequence (piece, color')
+  where
+    piece  | testSquare pawns n   = Just Pawn
+           | testSquare knights n = Just Knight
+           | testSquare bishops n = Just Bishop
+           | testSquare rooks n   = Just Rook
+           | testSquare queens n  = Just Queen
+           | testSquare kings n   = Just King
+           | otherwise            = Nothing
+
+    color' | testSquare player n = Just color
+           | testSquare enemy n  = Just $ reverseColor color
+           | otherwise           = Nothing
+
+
+{-# INLINE  maybeCapturedPieceAt #-}
+maybeCapturedPieceAt :: Square -> Position -> Maybe Piece
+maybeCapturedPieceAt n (Position {..})
+  | testSquare pawns n   = Just Pawn
+  | testSquare knights n = Just Knight
+  | testSquare bishops n = Just Bishop
+  | testSquare rooks n   = Just Rook
+  | testSquare queens n  = Just Queen
+  | otherwise            = Nothing
+
+
+{-# INLINE  isPieceAt #-}
+isPieceAt :: Piece -> Square -> Position -> Bool
+isPieceAt piece n Position {..} =
+  case piece of
+    Pawn   -> testSquare pawns n
+    Knight -> testSquare knights n
+    Bishop -> testSquare bishops n
+    Rook   -> testSquare rooks n
+    Queen  -> testSquare queens n
+    King   -> testSquare kings n
+
+
+{-# INLINE  isRepeatedPosition #-}
+isRepeatedPosition :: ZKey -> Position -> Bool
+isRepeatedPosition zKey Position {..} =
+  zKey `elem` previousPositions
+
+
+{-# INLINE  getPhase #-}
+getPhase :: Position -> Phase
+getPhase Position {..} = min totalPhase
+   (minorPiecePhase * fromIntegral (popCount (knights .| bishops))
+  + rookPhase       * fromIntegral (popCount rooks)
+  + queenPhase      * fromIntegral (popCount queens))
+
+
+instance Show Position where
+  show pos =
+    show pos.color <> " to play: \n\n" <> boardStr
+    where
+      boardStr = intercalate "\n" (intersperse ' ' <$> pieces)
+      pieces = reverse $ chunksOf 8 do
+        n <- toList squares
+        pure $ maybe ' ' pieceToChar (pieceAt n pos)
diff --git a/src/Models/Score.hs b/src/Models/Score.hs
new file mode 100644
--- /dev/null
+++ b/src/Models/Score.hs
@@ -0,0 +1,66 @@
+module Models.Score where
+
+import           AppPrelude
+
+import           Data.Int        (Int16)
+import           Models.Piece
+import           Test.QuickCheck (Arbitrary (..), elements)
+
+
+type Score = Int16
+type Phase = Int16
+type Depth = Word8
+type Ply   = Word8
+type Age   = Word8
+
+
+newtype NodeType = NodeType Word8
+  deriving (Eq, Ord, Num, Storable, Show)
+
+instance Arbitrary NodeType where
+  arbitrary = elements [PV, Cut, All]
+
+
+{-# COMPLETE PV, Cut, All #-}
+pattern PV, Cut, All :: NodeType
+pattern PV  = NodeType 0
+pattern Cut = NodeType 1
+pattern All = NodeType 2
+
+
+getNodeType :: Score -> Score -> Score -> NodeType
+getNodeType !alpha !beta !score
+  | score >= beta  = Cut
+  | score > alpha = PV
+  | otherwise     = All
+
+
+pieceToPhase :: Piece -> Phase
+pieceToPhase = \case
+  Knight -> minorPiecePhase
+  Bishop -> minorPiecePhase
+  Rook   -> rookPhase
+  Queen  -> queenPhase
+  _      -> 0
+
+
+minorPiecePhase :: Phase
+minorPiecePhase = 1
+
+rookPhase :: Phase
+rookPhase = 2
+
+queenPhase :: Phase
+queenPhase = 4
+
+
+totalPhase :: Phase
+totalPhase =
+  minorPiecePhase * 8 + rookPhase * 4 + queenPhase * 2
+
+
+minScore :: Score
+minScore = minBound + 1
+
+maxScore :: Score
+maxScore = maxBound - 1
diff --git a/src/MoveGen/MakeMove.hs b/src/MoveGen/MakeMove.hs
new file mode 100644
--- /dev/null
+++ b/src/MoveGen/MakeMove.hs
@@ -0,0 +1,145 @@
+module MoveGen.MakeMove (makeMove, makeNullMove) where
+
+import           AppPrelude
+
+import           Models.Move
+import           Models.Piece
+import           Models.Position
+import           MoveGen.PieceAttacks
+import           Utils.Board
+
+
+makeMove :: Move -> Position -> Position
+makeMove Move {..} pos =
+  makeNullMove
+  $ movePiece piece promotion startBoard endBoard
+  $ updatePlayerBoards startBoard endBoard pos
+  where
+    !startBoard = toBoard start
+    !endBoard = toBoard end
+
+
+makeNullMove :: Position -> Position
+makeNullMove pos@Position {color, player,  enemy, enPassant} =
+  pos {
+    color           = reverseColor color
+  , phase           = getPhase pos
+  , player          = enemy
+  , enemy           = player
+  , attacked        = allAttacks pos
+  , enPassant       = toReverseCondition enPassantPinnedPawns * enPassant
+  , leapingCheckers = getLeapingCheckers pos
+  , sliderCheckers  = getSliderCheckers bishopCheckerRays rookCheckerRays
+                                        pos
+  , pinnedPieces    = getPinnedPieces bishopCheckerRays rookCheckerRays
+                                      sliderRays        pos
+  }
+  where
+    bishopCheckerRays    = getBishopCheckerRays    pos
+    rookCheckerRays      = getRookCheckerRays      pos
+    sliderRays           = getEnemyKingSliderRays  pos
+    enPassantPinnedPawns = getEnPassantPinnedPawns pos
+
+
+updatePlayerBoards :: Board -> Board -> Position -> Position
+updatePlayerBoards start end pos@Position {..} =
+  pos {
+      previousPositions    = getZobristKey pos
+        : if halfMoveClock == 0 then [] else previousPositions
+    , halfMoveClock        = fromIntegral
+        (toReverseCondition (enemy & end)) * (halfMoveClock + 1)
+    , player               = (player ^ start) .| end
+    , enemy                = enemy .\ end
+    , pawns                = pawns .\ end
+    , knights              = knights .\ end
+    , bishops              = bishops .\ end
+    , rooks                = rooks .\ end
+    , queens               = queens .\ end
+  }
+
+
+movePiece :: Piece -> Promotion -> Board -> Board -> Position -> Position
+movePiece Pawn NoProm start end pos@Position {..} =
+  pos {
+      halfMoveClock = 0
+    , pawns         = (pawns ^ (start .| enPassantCapture)) .| end
+    , enemy         = enemy ^ enPassantCapture
+    , enPassant     = start <<! 8 & end !>> 8
+  }
+  where
+    ((<<!), (!>>)) = case color of
+      White -> ((<<), (>>))
+      Black -> ((>>), (<<))
+    enPassantCapture = (enPassant & end) !>> 8
+
+movePiece Pawn KnightProm start end pos@Position {..} =
+  pos {
+      halfMoveClock = 0
+    , pawns         = pawns ^ start
+    , knights       = knights .| end
+    , enPassant     = 0
+  }
+
+movePiece Pawn BishopProm start end pos@Position {..} =
+  pos {
+      halfMoveClock = 0
+    , pawns         = pawns ^ start
+    , bishops       = bishops .| end
+    , enPassant     = 0
+  }
+
+movePiece Pawn RookProm !start !end pos@Position {..} =
+  pos {
+      halfMoveClock = 0
+    , pawns         = pawns ^ start
+    , rooks         = rooks .| end
+    , enPassant     = 0
+  }
+
+movePiece Pawn QueenProm start end pos@Position {..} =
+  pos {
+      halfMoveClock = 0
+    , pawns         = pawns ^ start
+    , queens        = queens .| end
+    , enPassant     = 0
+  }
+
+movePiece Knight _ start end pos@Position {..} =
+  pos {
+      knights   = (knights ^ start) .| end
+    , enPassant = 0
+  }
+
+movePiece Bishop _ start end pos@Position {..} =
+  pos {
+      bishops   = (bishops ^ start) .| end
+    , enPassant = 0
+  }
+
+movePiece Rook _ start end pos@Position {..} =
+  pos {
+      rooks     = (rooks ^ start) .| end
+    , castling  = castling .\ start
+    , enPassant = 0
+  }
+
+movePiece Queen _ start end pos@Position {..} =
+  pos {
+      queens    = (queens ^ start) .| end
+    , enPassant = 0
+  }
+
+movePiece King _ start end pos@Position {..} =
+  pos {
+      kings     = (kings ^ start) .| end
+    , castling  = castling .\ kingRank
+    , player    = (player ^ rookStart) .| rookEnd
+    , rooks     = (rooks ^ rookStart) .| rookEnd
+    , enPassant = 0
+  }
+  where
+    rookStart   = shortCastle << 1 .| longCastle >> 2
+    rookEnd     = shortCastle >> 1 .| longCastle << 1
+    shortCastle = (start << 2) & end
+    longCastle  = (start >> 2) & end
+    kingRank    = fileMovesVec !! lsb start
diff --git a/src/MoveGen/MoveQueries.hs b/src/MoveGen/MoveQueries.hs
new file mode 100644
--- /dev/null
+++ b/src/MoveGen/MoveQueries.hs
@@ -0,0 +1,56 @@
+module MoveGen.MoveQueries (isQuietMove, isLegalQuietMove, isWinningCapture, isCheckMove, isPromotionPush, isCastlingMove, isCapture) where
+
+import           AppPrelude
+
+import           Evaluation.Evaluation
+import           Models.Move
+import           Models.Piece
+import           Models.Position
+import           MoveGen.MakeMove
+import           MoveGen.PieceQuietMoves (allQuietMoves)
+import           MoveGen.PositionQueries
+import           Utils.Board
+
+
+isCastlingMove :: Move -> Bool
+isCastlingMove Move {..} =
+  piece == King && abs (start - end) == 2
+
+
+isCheckMove :: Move -> Position -> Bool
+isCheckMove mv pos =
+  isKingInCheck $ makeMove mv pos
+
+
+isCapture :: Move -> Position -> Bool
+isCapture Move {..} Position {..} =
+  promotion /= NoProm
+    || testSquare enemy end
+    || piece == Pawn && testSquare enPassant end
+
+
+isWinningCapture :: Move -> Position -> Bool
+isWinningCapture mv pos =
+  isCapture mv pos
+    && member mv.promotion bestPromotions
+    && evaluateExchange mv pos >= 0
+
+
+isPromotionPush :: Move -> Bool
+isPromotionPush Move{..} =
+  piece == Pawn && toRank end `elem` [2, 7]
+
+
+isQuietMove :: Move -> Position -> Bool
+isQuietMove mv pos =
+  not (isCheckMove mv pos || isCapture mv pos)
+
+
+isLegalQuietMove :: Move -> Position -> Bool
+isLegalQuietMove mv@Move {..} pos@Position {..} =
+  testSquare player start
+    && not (testSquare allPieces end)
+    && isPieceAt piece start pos
+    && elem mv (allQuietMoves pos)
+  where
+    allPieces = enemy .| player
diff --git a/src/MoveGen/PieceAttacks.hs b/src/MoveGen/PieceAttacks.hs
new file mode 100644
--- /dev/null
+++ b/src/MoveGen/PieceAttacks.hs
@@ -0,0 +1,206 @@
+module MoveGen.PieceAttacks (getLeapingCheckers, getSliderCheckers, getEnemyKingSliderRays, getBishopCheckerRays, getRookCheckerRays, getKingBishopRay, getPinnedPieces, getEnPassantPinnedPawns, getKingQueenRay, getKingRookRay, allAttacks, knightAttacks, bishopAttacks, rookAttacks, queenAttacks, kingAttacks, pawnAttacks, pawnDiagAttacks, pawnAntiDiagAttacks, bishopMoves, rookMoves, queenMoves) where
+
+import           AppPrelude
+
+import           Models.Move
+import           Models.Piece
+import           Models.Position
+import           Utils.Board
+
+
+getLeapingCheckers :: Position -> Board
+getLeapingCheckers Position {..} =
+  player & checkers
+  where
+    checkers = pawns   & pawnAttacks   (reverseColor color) (enemy&kings)
+            .| knights & knightAttacks (lsb (enemy&kings))
+
+
+getSliderCheckers :: Board -> Board -> Position -> Board
+getSliderCheckers bishopCheckerRays rookCheckerRays Position {..} =
+  player & checkers
+  where
+    checkers = bishopCheckerRays & bishops
+            .| rookCheckerRays   & rooks
+            .| (bishopCheckerRays .| rookCheckerRays) & queens
+
+
+getEnemyKingSliderRays :: Position -> Board
+getEnemyKingSliderRays Position {..} =
+  queenAttacks player (lsb (enemy&kings))
+
+
+getBishopCheckerRays :: Position -> Board
+getBishopCheckerRays Position {..} =
+  bishopAttacks (enemy .| player) (lsb (enemy&kings))
+
+
+getRookCheckerRays :: Position -> Board
+getRookCheckerRays Position {..} =
+  rookAttacks (enemy .| player) (lsb (enemy&kings))
+
+
+getPinnedPieces :: Board -> Board -> Board -> Position -> Board
+getPinnedPieces !bishopCheckerRays !rookCheckerRays !sliderRays Position {..} =
+  enemy &
+  (foldBoardAttacks bishopPins (attackers & bishops)
+  .| foldBoardAttacks rookPins (attackers & rooks)
+  .| foldBoardAttacks queenPins (attackers & queens))
+  where
+    bishopPins n = getKingBishopRay king n
+      & bishopCheckerRays
+      & bishopAttacks allPieces n
+    rookPins n = getKingRookRay king n
+      & rookCheckerRays
+      & rookAttacks allPieces n
+    queenPins n = getKingQueenRay king n
+      & (bishopCheckerRays .| rookCheckerRays)
+      & queenAttacks allPieces n
+    attackers = sliderRays & player
+    !king     = enemy & kings
+    allPieces = player .| enemy
+
+
+getEnPassantPinnedPawns :: Position -> Board
+getEnPassantPinnedPawns pos@Position {..}
+  | enPassant == 0 || enPassantRank & kingRank == 0 = 0
+  | otherwise =
+    enemy & pawns & getPinnedPieces 0 rookCheckerRays sliderRays pos'
+    where
+      rookCheckerRays = getRookCheckerRays pos' & kingRank
+      sliderRays      = getEnemyKingSliderRays pos' & enPassantRank
+      pos' = pos {
+        pawns  = pawns  ^ enPassantPawn,
+        player = player ^ enPassantPawn
+      }
+      enPassantPawn   = case color of
+        White -> enPassant << 8
+        Black -> enPassant >> 8
+      enPassantSquare = lsb enPassantPawn
+      kingRank        = fileMovesVec !! lsb (kings & enemy)
+      enPassantRank   = fileMovesVec !! enPassantSquare
+
+
+getKingQueenRay :: Board -> Square -> Board
+getKingQueenRay !king !n
+  | file & king /= 0     = file
+  | rank & king /= 0     = rank
+  | diag & king /= 0     = diag
+  | antiDiag & king /= 0 = antiDiag
+  | otherwise           = 0
+  where
+    file     = rankMovesVec     !! n
+    rank     = fileMovesVec     !! n
+    diag     = antiDiagMovesVec !! n
+    antiDiag = diagMovesVec     !! n
+
+
+getKingBishopRay :: Board -> Square -> Board
+getKingBishopRay !king !n
+  | diag & king /= 0     = diag
+  | antiDiag & king /= 0 = antiDiag
+  | otherwise           = 0
+  where
+    diag     = antiDiagMovesVec !! n
+    antiDiag = diagMovesVec     !! n
+
+
+getKingRookRay :: Board -> Square -> Board
+getKingRookRay !king !n
+  | file & king /= 0 = file
+  | rank & king /= 0 = rank
+  | otherwise       = 0
+  where
+    file = rankMovesVec !! n
+    rank = fileMovesVec !! n
+
+
+allAttacks :: Position -> Board
+allAttacks Position {..} =
+     pawnAttacks  color                         (player&pawns)
+  .| foldBoardAttacks knightAttacks             (player&knights)
+  .| foldBoardAttacks (bishopAttacks allPieces) (player&bishops)
+  .| foldBoardAttacks (rookAttacks allPieces)   (player&rooks)
+  .| foldBoardAttacks (queenAttacks allPieces)  (player&queens)
+  .| kingAttacks                           (lsb (player&kings))
+  where
+    !allPieces = player .| (enemy .\ kings)
+
+
+pawnAttacks :: Color -> Board -> Board
+pawnAttacks color board = case color of
+  White -> (board .\ file_A) << 7 .| (board .\ file_H) << 9
+  Black -> (board .\ file_A) >> 9 .| (board .\ file_H) >> 7
+
+
+pawnDiagAttacks :: Color -> Board -> Board
+pawnDiagAttacks color board = case color of
+  White -> (board .\ file_H) << 9
+  Black -> (board .\ file_A) >> 9
+
+
+pawnAntiDiagAttacks :: Color -> Board -> Board
+pawnAntiDiagAttacks color board = case color of
+  White -> (board .\ file_A) << 7
+  Black -> (board .\ file_H) >> 7
+
+
+knightAttacks :: Square -> Board
+knightAttacks !n = knightMovesVec !! n
+
+
+kingAttacks :: Square -> Board
+kingAttacks !n = kingMovesVec !! n
+
+
+bishopMoves :: Board -> Board -> Board -> Square -> Board
+bishopMoves allPieces pinnedPieces king n
+  | testSquare pinnedPieces n = attacks & getKingBishopRay king n
+  | otherwise                 = attacks
+  where
+    !attacks = bishopAttacks allPieces n
+
+rookMoves :: Board -> Board -> Board -> Square -> Board
+rookMoves allPieces pinnedPieces king n
+  | testSquare pinnedPieces n = attacks & getKingRookRay king n
+  | otherwise                 = attacks
+  where
+    !attacks = rookAttacks allPieces n
+
+queenMoves :: Board -> Board -> Board -> Square -> Board
+queenMoves allPieces pinnedPieces king n
+  | testSquare pinnedPieces n = attacks & getKingQueenRay king n
+  | otherwise                 = attacks
+  where
+    !attacks = queenAttacks allPieces n
+
+bishopAttacks :: Board -> Square -> Board
+bishopAttacks !allPieces !n =
+     sliding lsb northEastMovesVec allPieces n
+  .| sliding lsb northWestMovesVec allPieces n
+  .| sliding msb southWestMovesVec allPieces n
+  .| sliding msb southEastMovesVec allPieces n
+
+
+rookAttacks :: Board -> Square -> Board
+rookAttacks !allPieces !n =
+     sliding lsb northMovesVec allPieces n
+  .| sliding lsb eastMovesVec allPieces n
+  .| sliding msb westMovesVec allPieces n
+  .| sliding msb southMovesVec allPieces n
+
+
+queenAttacks :: Board -> Square -> Board
+queenAttacks !allPieces !n =
+     rookAttacks allPieces n
+  .| bishopAttacks allPieces n
+
+
+sliding :: (Board -> Square) -> Vector Board -> Board -> Square -> Board
+sliding !findBlocker !mask !allPieces !n =
+   ray ^ blockerRay
+  where
+    !firstBlocker = findBlocker blockers
+    !blockers     = ray & allPieces
+    !blockerRay   = mask !! firstBlocker
+    !ray          = mask !! n
diff --git a/src/MoveGen/PieceCaptures.hs b/src/MoveGen/PieceCaptures.hs
new file mode 100644
--- /dev/null
+++ b/src/MoveGen/PieceCaptures.hs
@@ -0,0 +1,207 @@
+module MoveGen.PieceCaptures (allCaptures, staticExchangeCaptures, knightCaptures, bishopCaptures, rookCaptures, queenCaptures) where
+
+import           AppPrelude
+
+import           Models.Move
+import           Models.Piece
+import           Models.Position
+import           MoveGen.PieceAttacks
+import           Utils.Board
+
+
+-- Captures legal move generator:
+-- - Piece captures
+-- - En passant captures
+-- - Pawn promotions
+
+allCaptures :: Position -> [Move]
+allCaptures pos@Position {..}
+
+  | allCheckers          == 0 = genCaptures id id
+  | popCount allCheckers > 1 = allKingCaptures
+  | sliderCheckers /= 0       = genCaptures captureChecker pawnCaptureChecker
+  | otherwise                = genCaptures captureChecker pawnCaptureChecker
+
+  where
+    genCaptures =
+      allCapturesHelper allPieces king allKingCaptures pos
+    pawnCaptureChecker board =
+      board & (allCheckers .| enPassantChecker)
+    captureChecker board = board & allCheckers
+    allKingCaptures =
+      foldBoardSquares King (kingCaptures enemy attacked) [] kingSquare
+    allCheckers = leapingCheckers .| sliderCheckers
+    enPassantChecker =
+      let checker = allCheckers & pawns
+      in enPassant & (checker << 8 .| checker >> 8)
+    allPieces  = player .| enemy
+    kingSquare = lsb king
+    king       = player&kings
+
+
+allCapturesHelper :: Board -> Board -> [Move] -> Position -> (Board -> Board) -> (Board -> Board) -> [Move]
+allCapturesHelper allPieces king allKingCaptures Position {..} !f !g =
+
+    foldBoardMoves     Pawn   (g . pawnCapturesAndPromotions
+                                     enemy noPieces enPassant color)
+                              (unpinned&pawns)
+
+    $ foldBoardMoves   Pawn   (g . pawnPromotions noPieces color
+                                 . toBoard)
+                              filePinnedPawns
+
+    $ foldBoardMoves   Pawn   (g . diagPawnCaptures enemy color)
+                              diagPinnedPawns
+
+    $ foldBoardMoves   Pawn   (g . antiDiagPawnCaptures enemy color)
+                              antiDiagPinnedPawns
+
+    $ foldBoardMoves   Knight (f . knightCaptures enemy)
+                              (unpinned&knights)
+
+    $ foldBoardMoves   Bishop (f . bishopCaptures
+                                     enemy allPieces pinnedPieces king)
+                              (player&bishops)
+
+    $ foldBoardMoves   Rook   (f . rookCaptures
+                                     enemy allPieces pinnedPieces king)
+                              (player&rooks)
+
+    $ foldBoardMoves   Queen  (f . queenCaptures
+                                     enemy allPieces pinnedPieces king)
+                              (player&queens)
+    allKingCaptures
+
+    where
+    unpinned            = player .\ pinnedPieces
+    pinnedPawns         = pinnedPieces & pawns
+    filePinnedPawns     = pinnedPawns & kingFile
+    diagPinnedPawns     = pinnedPawns & kingDiag
+    antiDiagPinnedPawns = pinnedPawns & kingAntiDiag
+    kingFile            = rankMovesVec !! kingSquare
+    kingDiag            = antiDiagMovesVec !! kingSquare
+    kingAntiDiag        = diagMovesVec !! kingSquare
+    kingSquare          = lsb king
+    noPieces            = (~) allPieces
+
+
+staticExchangeCaptures :: Square -> Position -> [Move]
+staticExchangeCaptures target pos@Position {..}
+  | allCheckers == 0               = genCaptures
+  | popCount allCheckers > 1      = allKingCaptures
+  | testSquare allCheckers target = genCaptures
+  | otherwise                     = []
+
+  where
+    genCaptures =
+      staticExchangeCapturesHelper target kingSquare allPieces
+                                   allKingCaptures pos
+    allKingCaptures =
+      foldBoardSquares King (kingCaptures (toBoard target) attacked)
+      [] kingSquare
+    allCheckers     = leapingCheckers .| sliderCheckers
+    allPieces       = player .| enemy
+    kingSquare      = lsb king
+    king            = player&kings
+
+
+staticExchangeCapturesHelper
+  :: Square -> Square -> Board -> [Move] -> Position -> [Move]
+staticExchangeCapturesHelper
+  target king allPieces allKingCaptures Position {..} =
+      foldBoardPawnMovesConst    target (validSliders&pawnAttackers)
+    $ foldBoardMovesConst Knight target (unpinned&knightAttackers)
+    $ foldBoardMovesConst Bishop target (validSliders&bishopAttackers)
+    $ foldBoardMovesConst Rook   target (validSliders&rookAttackers)
+    $ foldBoardMovesConst Queen  target (validSliders&queenAttackers)
+      allKingCaptures
+
+    where
+    pawnAttackers   = pawns & pawnAttacks (reverseColor color) targetBoard
+    knightAttackers = knights & knightAttacks target
+    bishopAttackers = bishops & bishopRays
+    rookAttackers   = rooks & rookRays
+    queenAttackers  = queens & (bishopRays .| rookRays)
+    bishopRays      = bishopAttacks allPieces target
+    rookRays        = rookAttacks allPieces target
+    targetBoard     = toBoard target
+    unpinned        = player .\ pinnedPieces
+
+    validSliders
+      | pinnedPieces .\ knights == 0 = player
+      | otherwise = unpinned .| pinnedPieces & getPinningRay target king
+
+
+getPinningRay :: Square -> Square -> Board
+getPinningRay !target !king
+    | testSquare kingDiag target     = kingDiag
+    | testSquare kingAntiDiag target = kingAntiDiag
+    | testSquare kingFile target     = kingFile
+    | testSquare kingRank target     = kingRank
+    | otherwise                      = 0
+  where
+  kingDiag        = antiDiagMovesVec !! king
+  kingAntiDiag    = diagMovesVec     !! king
+  kingFile        = rankMovesVec     !! king
+  kingRank        = fileMovesVec     !! king
+
+
+pawnCapturesAndPromotions :: Board -> Board -> Board -> Color -> Square -> Board
+pawnCapturesAndPromotions enemy noPieces enPassant color n =
+  pawnPromotions noPieces color board
+  .| pawnCaptures enemy enPassant color board
+  where
+    board = toBoard n
+
+
+pawnPromotions :: Board -> Color -> Board -> Board
+pawnPromotions noPieces color board =
+  promotions & noPieces
+  where
+    promotions = case color of
+      White -> (board & rank_7) << 8
+      Black -> (board & rank_2) >> 8
+
+
+pawnCaptures :: Board -> Board -> Color -> Board -> Board
+pawnCaptures enemy enPassant color board =
+  pawnAttacks color board & (enemy .| enPassant)
+
+
+diagPawnCaptures :: Board -> Color -> Square -> Board
+diagPawnCaptures enemy color n =
+  pawnDiagAttacks color board & enemy
+  where
+    board = toBoard n
+
+
+antiDiagPawnCaptures :: Board -> Color -> Square -> Board
+antiDiagPawnCaptures enemy color n =
+  pawnAntiDiagAttacks color board & enemy
+  where
+    board = toBoard n
+
+
+knightCaptures :: Board -> Square -> Board
+knightCaptures enemy n =
+  knightAttacks n & enemy
+
+
+kingCaptures :: Board -> Board -> Square -> Board
+kingCaptures enemy attacked n =
+  (kingAttacks n & enemy) .\ attacked
+
+
+bishopCaptures :: Board -> Board -> Board -> Board -> Square -> Board
+bishopCaptures enemy allPieces pinnedPieces king n =
+  bishopMoves allPieces pinnedPieces king n & enemy
+
+
+rookCaptures :: Board -> Board -> Board -> Board -> Square -> Board
+rookCaptures enemy allPieces pinnedPieces king n =
+  rookMoves allPieces pinnedPieces king n & enemy
+
+
+queenCaptures :: Board -> Board -> Board -> Board -> Square -> Board
+queenCaptures enemy allPieces pinnedPieces king n =
+  queenMoves allPieces pinnedPieces king n & enemy
diff --git a/src/MoveGen/PieceQuietMoves.hs b/src/MoveGen/PieceQuietMoves.hs
new file mode 100644
--- /dev/null
+++ b/src/MoveGen/PieceQuietMoves.hs
@@ -0,0 +1,124 @@
+module MoveGen.PieceQuietMoves (allQuietMoves) where
+
+
+import           AppPrelude
+
+import           Models.Move
+import           Models.Piece
+import           Models.Position
+import           MoveGen.PieceAttacks
+import           MoveGen.PieceCaptures
+import           Utils.Board
+
+
+-- Quiet moves legal move generator:
+-- - Non capture moves except for pawn promotions
+
+allQuietMoves :: Position -> [Move]
+allQuietMoves pos@Position {..}
+
+  | allCheckers == 0                                = genMoves id
+  | leapingCheckers /= 0 || popCount allCheckers > 1 = allKingMoves
+  | otherwise                                      = genMoves blockChecker
+
+  where
+    genMoves = quietMovesHelper allPieces king allKingMoves pos
+    blockChecker board = board & checkerRay
+    checkerRay = getKingQueenRay king checkerSquare
+      & queenAttacks allPieces kingSquare
+      & queenAttacks allPieces checkerSquare
+    checkerSquare = lsb allCheckers
+    allKingMoves =
+      foldBoardSquares King
+      (kingQuietMoves allPieces attacked castling (player&rooks) king) [] kingSquare
+    allCheckers = leapingCheckers .| sliderCheckers
+    allPieces = player .| enemy
+    kingSquare = lsb king
+    king = player&kings
+
+
+quietMovesHelper :: Board -> Board -> [Move] -> Position -> (Board -> Board)
+                 -> [Move]
+quietMovesHelper allPieces king allKingMoves Position {..} !f =
+
+    foldBoardMoves     Pawn   (f . pawnAdvances noPieces color
+                                 . toBoard)
+                              (unpinned&pawns .| filePinnedPawns)
+
+    $ foldBoardMoves   Knight (f . knightCaptures noPieces)
+                              (unpinned&knights)
+
+    $ foldBoardMoves   Bishop (f . bishopQuietMoves noPieces
+                                     allPieces pinnedPieces king)
+                              (player&bishops)
+
+    $ foldBoardMoves   Rook   (f . rookQuietMoves noPieces
+                                     allPieces pinnedPieces king)
+                              (player&rooks)
+
+    $ foldBoardMoves   Queen  (f . queenQuietMoves noPieces
+                                     allPieces pinnedPieces king)
+                              (player&queens)
+
+      allKingMoves
+
+    where
+    unpinned = player .\ pinnedPieces
+    pinnedPawns = pinnedPieces & pawns
+    filePinnedPawns = pinnedPawns & kingFile
+    kingFile = rankMovesVec !! kingSquare
+    kingSquare = lsb king
+    noPieces = (~) allPieces
+
+
+pawnAdvances :: Board -> Color -> Board -> Board
+pawnAdvances noPieces color board = advances & noPieces
+  where
+
+    advances = case color of
+      White -> (board .\ rank_7) << 8
+           .| ((board & rank_2) << 8 & noPieces) << 8
+      Black -> (board .\ rank_2) >> 8
+           .| ((board & rank_7) >> 8 & noPieces) >> 8
+
+bishopQuietMoves :: Board -> Board -> Board -> Board -> Square -> Board
+bishopQuietMoves noPieces allPieces pinnedPieces king n =
+  bishopMoves allPieces pinnedPieces king n & noPieces
+
+
+rookQuietMoves :: Board -> Board -> Board -> Board -> Square -> Board
+rookQuietMoves noPieces allPieces pinnedPieces king n =
+  rookMoves allPieces pinnedPieces king n & noPieces
+
+
+queenQuietMoves :: Board -> Board -> Board -> Board -> Square -> Board
+queenQuietMoves noPieces allPieces pinnedPieces king n =
+  queenMoves allPieces pinnedPieces king n & noPieces
+
+
+kingQuietMoves :: Board -> Board -> Board -> Board -> Board -> Square -> Board
+kingQuietMoves allPieces attacked castling rooks king n =
+  (kingAttacks n
+  .| kingCastlingMoves allPieces attacked castling rooks king n)
+  .\ (attacked .| allPieces)
+
+
+kingCastlingMoves :: Board -> Board -> Board -> Board -> Board -> Square -> Board
+kingCastlingMoves allPieces attacked castling rooks king n =
+  (shortCastlingCond
+      * fromIntegral (popCount (castling & rooks & file_H))
+      * ((castling & king) << 2))
+  .|
+  (longCastlingCond
+      * fromIntegral (popCount (castling & rooks & file_A))
+      * ((castling & king) >> 2))
+  where
+    shortCastlingCond =
+     toReverseCondition (shortCastleSlidingFiles & collisions .| inCheck)
+    longCastlingCond =
+     toReverseCondition (longCastleSlidingFiles  & collisions
+            .| inCheck
+            .| allPieces & kingRank & file_B)
+    collisions = kingRank & (attacked .| allPieces)
+    kingRank = fileMovesVec !! n
+    inCheck = king & attacked
diff --git a/src/MoveGen/PositionQueries.hs b/src/MoveGen/PositionQueries.hs
new file mode 100644
--- /dev/null
+++ b/src/MoveGen/PositionQueries.hs
@@ -0,0 +1,196 @@
+module MoveGen.PositionQueries (isDefeat, isNotWinnable, isDraw, isEndgame, isKingInCheck, hasSingleMove, isWonEndgame) where
+
+import           AppPrelude
+
+import           Models.Position
+import           Search.Perft
+import           Utils.Board
+
+
+isEndgame :: Position -> Bool
+isEndgame Position {..} =
+  popCount (player & majorAndMinorPieces) == 0
+  where
+    majorAndMinorPieces = queens .| rooks .| bishops .| knights
+
+
+isKingInCheck :: Position -> Bool
+isKingInCheck Position {..} =
+  sliderCheckers .| leapingCheckers /= 0
+
+
+hasSingleMove :: Position -> Bool
+hasSingleMove = hasOne . allMoves
+  where
+  hasOne [_] = True
+  hasOne _   = False
+
+
+isDefeat :: Position -> Bool
+isDefeat = isCheckmate
+
+
+isNotWinnable :: Position -> Bool
+isNotWinnable Position {..} =
+  player & (pawns .| majorPieces) == 0
+  && popCount (player & minorPieces) <= 1
+  && enemy & pawns /= 0
+  where
+    minorPieces = bishops .| knights
+    majorPieces = queens  .| rooks
+
+
+isDraw :: Position -> Bool
+isDraw pos =
+    isDrawByHalfMoves pos
+  || isPawnlessEndgame pos && isDrawByMaterial pos
+  || isStalemate pos
+  || isDrawByRepetition pos
+
+
+isCheckmate :: Position -> Bool
+isCheckmate pos = null (allMoves pos) && isKingInCheck pos
+
+
+isStalemate :: Position -> Bool
+isStalemate pos = null (allMoves pos) && not (isKingInCheck pos)
+
+
+isDrawByHalfMoves :: Position -> Bool
+isDrawByHalfMoves Position {halfMoveClock} =
+  halfMoveClock >= 100
+
+
+isDrawByRepetition :: Position -> Bool
+isDrawByRepetition pos =
+  isRepeatedPosition (getZobristKey pos) pos
+
+
+isDrawByMaterial :: Position -> Bool
+isDrawByMaterial Position {..} =
+
+   allPieces == majorPieces .| minorPieces &&
+     forBoth queenVsRookAndMinor
+
+  || allPieces == majorPieces &&
+    forBoth queenVsRooks
+
+  || allPieces == queens &&
+    (popCount (player & queens) == 1
+    && popCount (enemy & queens) == 1)
+
+  || allPieces == rooks &&
+    (popCount (player & rooks) == 1
+    && popCount (enemy & rooks) == 1)
+
+  || allPieces == queens .| minorPieces &&
+    (forBoth queenVsBishops
+    || forBoth queenVsKnights)
+
+  || allPieces == rooks .| minorPieces &&
+    (forBoth rooksVsRookAndMinor
+    || forBoth rooksVsRookBishopAndKnight
+    || forBoth rookVsMinor
+    || forBoth rookAndMinorVsRook
+    || forBoth rookVsBishopAndKnight)
+
+  || allPieces == minorPieces &&
+
+    ((playerMinorPiecesCount <= 1
+        && enemyMinorPiecesCount <= 1)
+
+    || (popCount bishops == 3 && knights == 0)
+
+    || forBoth knightsVsMinor
+
+    || forBoth bishopAndKnightVsMinor)
+
+  where
+    knightsVsMinor !board !enemyBoard =
+      popCount (board & knights)              <= 2
+        && board & bishops                     == 0
+        && popCount (enemyBoard & minorPieces) <= 1
+
+    bishopAndKnightVsMinor !board !enemyBoard =
+      popCount (board & bishops)              == 1
+        && popCount (board & knights)          == 1
+        && popCount (enemyBoard & minorPieces) == 1
+
+    queenVsRooks !board !enemyBoard =
+      board & rooks                     == 0
+        && popCount (board & queens)     == 1
+        && enemyBoard & queens           == 0
+        && popCount (enemyBoard & rooks) == 2
+
+    rookVsMinor !board !enemyBoard =
+      board & minorPieces                    == 0
+       && popCount (board & rooks)            == 1
+       && enemyBoard & rooks                  == 0
+       && popCount (enemyBoard & minorPieces) == 1
+
+    rookAndMinorVsRook !board !enemyBoard =
+      popCount (board & rooks)         == 1
+      && popCount (board & minorPieces) == 1
+      && popCount (enemyBoard & rooks)  == 1
+      && enemyBoard & minorPieces       == 0
+
+    rookVsBishopAndKnight !board !enemyBoard =
+      board & rooks                == 0
+      && popCount (board & bishops) == 1
+      && popCount (board & knights) == 1
+      && enemyBoard & minorPieces   == 0
+      && popCount (board & rooks)   == 1
+
+    queenVsRookAndMinor !board !enemyBoard =
+      board & (rooks .| minorPieces)        == 0
+      && popCount (board & queens)           == 1
+      && enemyBoard & queens                 == 0
+      && popCount (enemyBoard & rooks)       == 1
+      && popCount (enemyBoard & minorPieces) == 1
+
+    rooksVsRookAndMinor !board !enemyBoard =
+      board & minorPieces        == 0
+      && popCount (board & rooks)           == 2
+      && popCount (enemyBoard & rooks)       == 1
+      && popCount (enemyBoard & minorPieces) == 1
+
+    rooksVsRookBishopAndKnight !board !enemyBoard =
+      board & minorPieces               == 0
+      && popCount (board & rooks)        == 2
+      && popCount (enemyBoard & rooks)   == 1
+      && popCount (enemyBoard & knights) == 1
+      && popCount (enemyBoard & rooks)   == 1
+
+    queenVsKnights !board !enemyBoard =
+      board & minorPieces == 0
+      && popCount (board & queens) == 1
+      && enemyBoard & (queens .| bishops) == 0
+      && popCount (enemyBoard & knights) == 2
+
+    queenVsBishops !board !enemyBoard =
+      board & minorPieces == 0
+      && popCount (board & queens) == 1
+      && enemyBoard & (queens .| knights) == 0
+      && popCount (enemyBoard & bishops) == 2
+
+
+    !playerMinorPiecesCount = popCount (player & minorPieces)
+    !enemyMinorPiecesCount  = popCount (enemy  & minorPieces)
+    !allPieces              = (player .| enemy) .\ kings
+    !minorPieces            = knights .| bishops
+    !majorPieces            = rooks   .| queens
+    forBoth fn = fn player enemy || fn enemy player
+
+
+isWonEndgame :: Position -> Bool
+isWonEndgame Position {..} = enemy == enemy & kings
+  && (player & majorPieces /= 0
+     || popCount (player & minorPieces) >= 2 && bishops /= 0)
+  where
+  !minorPieces      = knights .| bishops
+  !majorPieces      = rooks   .| queens
+
+
+isPawnlessEndgame :: Position -> Bool
+isPawnlessEndgame Position {..} =
+  pawns == 0
diff --git a/src/Parsers/Command.hs b/src/Parsers/Command.hs
new file mode 100644
--- /dev/null
+++ b/src/Parsers/Command.hs
@@ -0,0 +1,105 @@
+module Parsers.Command (parseCommand) where
+
+import           AppPrelude
+
+import           Models.Command
+import           Models.Position
+import           Parsers.Position
+
+import           Bookhound.Parser
+import           Bookhound.ParserCombinators
+import           Bookhound.Parsers.Number
+import           Bookhound.Parsers.Text
+import           Control.Newtype
+import           Data.Monoid
+import           Models.Piece
+
+
+parseCommand :: Text -> Either [ParseError] Command
+parseCommand = runParser command
+  where
+
+  command =
+        stringToken "ucinewgame" $> UciNewGame
+    <|> stringToken "uci"        $> Uci
+    <|> stringToken "isready"    $> IsReady
+    <|> stringToken "position"   *> (SetPosition <$> positionSpec)
+    <|> stringToken "setoption"  *> (SetOption   <$> optionSpec)
+    <|> stringToken "move"       *> (MakeMoves   <$> unknownMoves)
+    <|> stringToken "ponderhit"  $> Ponderhit
+    <|> stringToken "stop"       $> Stop
+    <|> stringToken "quit"       $> Quit
+    <|> stringToken "eval"       $> Evaluate
+    <|> stringToken "display"    $> Display
+    <|> stringToken "flip"       $> Flip
+    <|> stringToken "go" *>
+         (stringToken "perft"    *> (Perft  <$> depth)
+      <|> stringToken "divide"   *> (Divide <$> depth)
+      <|>                           (Search <$> searchOptions))
+
+  searchOption =
+        setInfinite       <$  token (oneOf @Text ["infinite", "ponder"])
+    <|> setSearchMoves    <$> (stringToken "searchmoves" *> unknownMoves)
+    <|> setTargetDepth    <$> (stringToken "depth"       *> depth)
+    <|> setWhiteTime      <$> (stringToken "wtime"       *> unsignedInt)
+    <|> setBlackTime      <$> (stringToken "btime"       *> unsignedInt)
+    <|> setWhiteIncrement <$> (stringToken "winc"        *> unsignedInt)
+    <|> setBlackIncrement <$> (stringToken "binc"        *> unsignedInt)
+    <|> setMovesUntil     <$> (stringToken "movestogo"   *> unsignedInt)
+    <|> setFindMate       <$> (stringToken "mate"        *> unsignedInt)
+    <|> setMoveTime       <$> (stringToken "movetime"    *> unsignedInt)
+    <|> setMaxNodes       <$> (stringToken "nodes"       *> unsignedBigInt)
+
+  positionSpec = PositionSpec
+    <$> initialPositionSpec
+    <*> (stringToken "moves" *> unknownMoves <|> pure [])
+
+  optionSpec = stringToken "name" *>
+    (    (stringToken "Hash"      $> HashSize)
+     <*> (stringToken "value"     *> unsignedInt)
+
+     <|> (stringToken "Ponder"    $> Ponder)
+     <*> (stringToken "value"     *> boolean)
+
+     <|> stringToken "Clear Hash" $> ClearHash)
+
+  searchOptions =
+    ($ defaultSearchOptions) . ala Endo foldMap <$> (searchOption |*)
+
+  initialPositionSpec =
+        stringToken "startpos" $> startPosition
+    <|> stringToken "fen"      *> positionFenParser
+
+  unknownMoves = (token unknownMove |+)
+  unknownMove  = UnknownMove
+    <$> squareParser
+    <*> squareParser
+    <*> (fromMaybe NoProm <$> (promotionParser |?))
+
+  promotionParser =
+        string "k" $> KnightProm
+    <|> string "b" $> BishopProm
+    <|> string "r" $> RookProm
+    <|> string "q" $> QueenProm
+
+  boolean      =
+        stringToken "true"  $> True
+    <|> stringToken "false" $> False
+
+  token          = withTransform maybeBetweenSpacing
+  stringToken    = token . string
+  depth          = fromIntegral <$> satisfy (inRange 1 255) unsignedInt
+  unsignedBigInt = fromIntegral <$> unsignedInt
+
+
+  setTargetDepth x opts    = opts { targetDepth        = x }
+  setSearchMoves x opts    = opts { searchMoves        = x }
+  setInfinite opts         = opts { infinite           = True }
+  setMoveTime x opts       = opts { moveTime           = Just x }
+  setWhiteTime x opts      = opts { whiteTime          = Just x }
+  setWhiteIncrement x opts = opts { whiteIncrement     = Just x }
+  setBlackTime x opts      = opts { blackTime          = Just x }
+  setBlackIncrement x opts = opts { blackIncrement     = Just x }
+  setMovesUntil x opts     = opts { movesUntilNextTime = Just x }
+  setFindMate x opts       = opts { findMate           = Just x }
+  setMaxNodes x opts       = opts { maxNodes           = Just x }
diff --git a/src/Parsers/Position.hs b/src/Parsers/Position.hs
new file mode 100644
--- /dev/null
+++ b/src/Parsers/Position.hs
@@ -0,0 +1,111 @@
+module Parsers.Position (positionFenParser, squareParser) where
+
+import           AppPrelude
+
+import           Bookhound.Parser
+import           Bookhound.ParserCombinators
+import           Bookhound.Parsers.Char      (space)
+import           Bookhound.Parsers.Number    (unsignedInt)
+import           Data.Char                   (digitToInt)
+
+import           Models.Piece
+import           Models.Position
+import           Models.Score
+import           MoveGen.MakeMove
+import           Utils.Board
+
+
+positionFenParser :: Parser Position
+positionFenParser = do
+  (pieces, color, castling, enPassant, halfMoveClock, _) <- position
+  pure
+    $ newPosition
+    $ foldrFlipped includePiece pieces
+    $ foldrFlipped includeCastling castling
+    $ foldrFlipped includeEnPassant enPassant
+    $ includeHalfMoveClock halfMoveClock
+    $ includeColor color
+    emptyPosition
+  where
+  position = (,,,,,)
+    <$> (piecesP    <* space)
+    <*> (colorP     <* space)
+    <*> (castlingP  <* space)
+    <*> (enPassantP <* space)
+    <*> (ply        <* space)
+    <*> unsignedInt
+  piecesP =
+    map (mapMaybe (\(x, y) -> (x,) <$> y) . zip [0 ..] . fold . reverse)
+    $ satisfy lengthCheck
+    $ satisfy (all lengthCheck)
+    $ manySepBy (is '/') (fold <$> ((emptySquaresN <|> piece) |+))
+  colorP = mandatory (map charToColor anyChar)
+  castlingP = (mandatory (map charToCastlingRights anyChar) |+)
+           <|> [] <$ is '-'
+  enPassantP = Just <$> squareParser
+           <|> Nothing <$ is '-'
+  emptySquaresN = (`replicate` Nothing) . digitToInt <$> oneOf ['1' .. '8']
+  piece = pure . pure <$> mandatory (map charToPiece anyChar)
+  lengthCheck xs = length xs == 8
+  ply = fromIntegral <$> unsignedInt
+  mandatory = (=<<) (fromMaybe empty . map pure)
+  foldrFlipped f = flip $ foldr f
+
+
+squareParser :: Parser Square
+squareParser = (+) <$> column <*> map (* 8) row
+  where
+    column = (\x -> x - fromEnum 'a') . fromEnum <$> oneOf ['a' .. 'h']
+    row = (\x -> x - 1) . digitToInt <$> oneOf ['1' .. '8']
+
+
+newPosition :: Position -> Position
+newPosition = setInitialValues . makeNullMove . makeNullMove
+  where
+  setInitialValues pos = pos {
+    phase = getPhase pos
+  }
+
+
+includePiece :: (Square, (Piece, Color)) -> Position -> Position
+includePiece (square, (piece, pieceColor)) pos@Position {..} =
+  if pieceColor == color then
+    pos' { player = player .| board }
+  else
+    pos' { enemy  = enemy  .| board }
+  where
+  pos' = case piece of
+    Pawn   -> pos { pawns   = pawns   .| board }
+    Knight -> pos { knights = knights .| board }
+    Bishop -> pos { bishops = bishops .| board }
+    Rook   -> pos { rooks   = rooks   .| board }
+    Queen  -> pos { queens  = queens  .| board }
+    King   -> pos { kings   = kings   .| board }
+  board = toBoard square
+
+
+includeHalfMoveClock :: Ply -> Position -> Position
+includeHalfMoveClock halfMoveClock pos =
+  pos { halfMoveClock = halfMoveClock }
+
+
+includeColor :: Color -> Position -> Position
+includeColor color pos =
+  pos { color = color }
+
+
+includeCastling :: (CastlingRights, Color) -> Position -> Position
+includeCastling (castlingRights, castlingColor) pos@Position {..} =
+  pos { castling = castling .| row & (column .| file_E) }
+  where
+  row = case castlingColor of
+    White -> rank_1
+    Black -> rank_8
+  column = case castlingRights of
+    QueenSide -> file_A
+    KingSide  -> file_H
+
+
+includeEnPassant :: Square -> Position -> Position
+includeEnPassant square pos =
+  pos { enPassant = toBoard square }
diff --git a/src/Search/MoveOrdering.hs b/src/Search/MoveOrdering.hs
new file mode 100644
--- /dev/null
+++ b/src/Search/MoveOrdering.hs
@@ -0,0 +1,81 @@
+module Search.MoveOrdering (getSortedMoves) where
+
+import           AppPrelude
+
+import           Evaluation.Evaluation
+import           Models.Command           (EngineOptions)
+import           Models.Move
+import           Models.Piece
+import           Models.Position
+import           Models.Score
+import           MoveGen.MakeMove         (makeMove)
+import           MoveGen.MoveQueries
+import           MoveGen.PieceCaptures    (allCaptures)
+import           MoveGen.PieceQuietMoves  (allQuietMoves)
+import           MoveGen.PositionQueries
+import qualified Utils.KillersTable       as KillersTable
+import           Utils.KillersTable       (KillersTable)
+import qualified Utils.TranspositionTable as TTable
+import           Utils.TranspositionTable (TTable)
+
+
+
+-- Move Ordering:
+-- - Transposition table (PV / Refutation) move
+-- - Winning captures (SEE >= 0) (Ordered by SEE)
+-- - 2 Killer moves
+-- - Quiet moves (Ordered by Static Eval)
+-- - Losing captures (SEE < 0) (Ordered by SEE)
+
+-- Reduced moves:
+-- Late killers / quiet moves
+
+getSortedMoves :: (?killersTable :: KillersTable, ?tTable :: TTable,
+                  ?opts :: EngineOptions)
+  => Depth -> Ply -> Position -> IO ([Move], [Move])
+getSortedMoves !depth !ply pos = do
+  ttMove      <- toList <$> TTable.lookupBestMove (getZobristKey pos)
+  killerMoves <- getKillers ply pos
+  let
+    bestMoves =
+         ttMove
+      <> filter (`notElem` ttMove) winningCaptures
+      <> filter (`notElem` ttMove) killerMoves
+
+    worstMoves =
+         filter (`notElem` (ttMove <> killerMoves)) quietMoves
+      <> filter (`notElem` ttMove)                  losingCaptures
+
+  pure if depth >= 3 && not (isKingInCheck pos)
+    then (bestMoves, worstMoves)
+    else (bestMoves <> worstMoves, [])
+  where
+    quietMoves                        = getSortedQuietMoves depth pos
+    (winningCaptures, losingCaptures) = getSortedCaptures pos
+
+
+getKillers
+  :: (?killersTable :: KillersTable) => Ply -> Position -> IO [Move]
+getKillers !ply pos =
+   filter (`isLegalQuietMove` pos) <$> KillersTable.lookupMoves ply
+
+
+getSortedCaptures :: Position -> ([Move], [Move])
+getSortedCaptures pos =
+  bimap mapFn mapFn
+    $ partition ((>= 0) . snd)
+    $ map attachEval
+    $ filter ((`member` bestPromotions) . (.promotion))
+    $ allCaptures pos
+  where
+    mapFn         = map fst . sortOn (Down . snd)
+    attachEval mv = (mv, evaluateExchange mv pos)
+
+
+getSortedQuietMoves :: Depth -> Position -> [Move]
+getSortedQuietMoves !depth pos
+  | depth <= 2 = quietMoves
+  | otherwise = sortOn (Down . eval) quietMoves
+   where
+    eval mv         = - evaluatePosition (makeMove mv pos)
+    quietMoves      = allQuietMoves pos
diff --git a/src/Search/Parameters.hs b/src/Search/Parameters.hs
new file mode 100644
--- /dev/null
+++ b/src/Search/Parameters.hs
@@ -0,0 +1,30 @@
+module Search.Parameters where
+
+import           AppPrelude           hiding ((/))
+import           ClassyPrelude        ((/))
+
+import           Models.Score
+
+import qualified Data.Vector.Storable as Vector
+
+
+futilityMargins :: Vector Score
+futilityMargins = Vector.fromList
+  [300, 600, 1200]
+
+
+getLmrDepth :: Int -> Depth -> Depth
+getLmrDepth mvIdx depth =
+  min (depth - 1) lmrDepth
+  where
+    lmrDepth = ceiling (lmrFactor * (fromIntegral depth / 2)
+                + (1 - lmrFactor) * (fromIntegral depth - 1))
+    lmrFactor = min @Double 1 (fromIntegral mvIdx / 80)
+
+
+initialAlpha :: Score
+initialAlpha = minScore
+
+
+initialBeta :: Score
+initialBeta = maxScore
diff --git a/src/Search/Perft.hs b/src/Search/Perft.hs
new file mode 100644
--- /dev/null
+++ b/src/Search/Perft.hs
@@ -0,0 +1,37 @@
+module Search.Perft (perft, divide, allMoves) where
+
+import           AppPrelude
+
+import           Models.Move
+import           Models.Position
+import           MoveGen.MakeMove
+import           MoveGen.PieceCaptures
+import           MoveGen.PieceQuietMoves
+
+import qualified Data.Map                as Map
+import           Models.Score
+
+
+perft :: Depth -> Position -> Int
+perft = go 0
+  where
+    go !nodes !depth !pos
+      | depth == 1 = nodes + length moves
+      | otherwise = foldl' f nodes moves
+      where
+        f !acc mv  = go acc (depth - 1) (makeMove mv pos)
+        moves = allMoves pos
+
+
+divide :: Depth -> Position -> Map Move Int
+divide !depth !pos
+  | depth == 1 = Map.fromList $ toList ((,1) <$> moves)
+  | otherwise = Map.fromList
+  (second (perft (depth - 1)) . getResults <$> toList moves)
+  where
+    getResults !mv = (mv, makeMove mv pos)
+    moves = allMoves pos
+
+
+allMoves :: Position -> [Move]
+allMoves = allCaptures <> allQuietMoves
diff --git a/src/Search/Quiescence.hs b/src/Search/Quiescence.hs
new file mode 100644
--- /dev/null
+++ b/src/Search/Quiescence.hs
@@ -0,0 +1,71 @@
+module Search.Quiescence (quiesceSearch) where
+
+import           AppPrelude
+
+import           Evaluation.Evaluation
+import           Models.Move
+import           Models.Position
+import           Models.Score
+import           MoveGen.MakeMove
+import           MoveGen.MoveQueries
+import           MoveGen.PieceCaptures
+import           MoveGen.PositionQueries
+import           MoveGen.PieceQuietMoves
+import           Search.Perft
+
+import           Control.Monad.State
+
+
+quiesceSearch :: (?nodes :: IORef Word64)
+  => Score -> Score -> Ply -> Position -> IO Score
+quiesceSearch !alpha !beta !ply !pos
+
+  | standPat >= beta = pure beta
+
+  | otherwise       = do
+      modifyIORef' ?nodes (+ 1)
+      (score, finalAlpha) <- runStateT scoreState newAlpha
+      pure $! fromMaybe finalAlpha score
+  where
+    scoreState = findTraverseIndex (getMoveScore beta ply pos) moves
+    moves
+      | isKingInCheck pos = allMoves pos
+      | ply <= 1           = getWinningCapturesAndChecks pos
+      | otherwise         = getWinningCaptures pos
+    !newAlpha  = max alpha standPat
+    !standPat  = evaluatePosition pos
+
+
+getMoveScore :: (?nodes :: IORef Word64)
+  => Score -> Ply -> Position -> Int -> Move -> QuiesceM (Maybe Score)
+getMoveScore !beta !ply !pos _ mv =
+  do !alpha <- get
+     !score <- liftIO (negate <$> quiesceSearch (-beta) (-alpha)
+                                               (ply + 1)
+                                               (makeMove mv pos))
+     let !nodeType = getNodeType alpha beta score
+     advanceState beta score nodeType
+
+
+advanceState :: Score -> Score -> NodeType -> QuiesceM (Maybe Score)
+advanceState !beta !score !nodeType =
+  case nodeType of
+    PV  -> Nothing <$ put score
+    Cut -> pure $ Just beta
+    All -> pure Nothing
+
+
+
+getWinningCapturesAndChecks :: Position -> [Move]
+getWinningCapturesAndChecks pos =
+  getWinningCaptures pos
+    <> filter (`isCheckMove` pos) (allQuietMoves pos)
+
+
+getWinningCaptures :: Position -> [Move]
+getWinningCaptures pos =
+  filter (`isWinningCapture` pos) (allCaptures pos)
+
+
+
+type QuiesceM = StateT Score IO
diff --git a/src/Search/Search.hs b/src/Search/Search.hs
new file mode 100644
--- /dev/null
+++ b/src/Search/Search.hs
@@ -0,0 +1,299 @@
+module Search.Search (search, emptySearchResult, SearchResult(..)) where
+
+import           AppPrelude
+
+import           Evaluation.Evaluation
+import           Models.Command
+import           Models.Move
+import           Models.Position
+import           Models.Score
+import           MoveGen.MakeMove
+import           MoveGen.MoveQueries
+import           MoveGen.PositionQueries
+import           Search.MoveOrdering
+import           Search.Parameters
+import           Search.Perft
+import           Search.Quiescence
+import           Search.TimeManagement
+import qualified Utils.KillersTable       as KillersTable
+import           Utils.KillersTable       (KillersTable)
+import qualified Utils.TranspositionTable as TEntry (TEntry (..))
+import qualified Utils.TranspositionTable as TTable
+import           Utils.TranspositionTable (TEntry (TEntry), TTable)
+
+import           Control.Concurrent
+import           Control.Monad.State
+import           Data.Time.Clock.System
+
+
+-- Features:
+-- - Iterative deepening
+
+search
+  :: (?killersTable :: KillersTable, ?tTable :: TTable,
+     ?opts :: EngineOptions, ?age :: Age)
+  => SearchOptions -> IORef SearchResult -> Position -> IO ()
+search searchOpts@SearchOptions{..} resultRef pos = do
+  startTime <- getSystemTime
+  nodesRef <- newIORef 0
+  let ?nodes = nodesRef
+  maybeTimeout timeToMove $ untilM (go startTime nodesRef) [1 .. targetDepth]
+  when infinite $ forever $ threadDelay maxBound
+  where
+    timeToMove =
+      maybeFilter (const $ not infinite) $ getMoveTime searchOpts pos
+
+    go startTime nodesRef depth = do
+      result  <- getNodeResult initialAlpha initialBeta depth 0 pos
+      endTime <- getSystemTime
+      nodes   <- readIORef nodesRef
+      let bestMove = result.bestMove <|> headMay (allMoves pos)
+          result'  = result {bestMove = bestMove}
+      printSearchInfo depth nodes (endTime |-| startTime) result'
+      unless (null bestMove) $ writeIORef resultRef result'
+      pure (isTimeOver endTime startTime timeToMove
+            || not infinite && hasSingleMove pos
+            || any (nodes >=) maxNodes
+            || isDraw pos
+            || getGameResult result.score `elem` [Victory, Defeat])
+
+
+-- Features:
+-- - Transposition table score caching
+-- - Search extensions (Check, Single move)
+
+negamax
+  :: (?killersTable :: KillersTable, ?tTable :: TTable,
+     ?opts :: EngineOptions, ?nodes :: IORef Word64, ?age :: Age)
+  => Score -> Score -> Depth -> Ply -> Position -> IO SearchResult
+negamax !alpha !beta !depth !ply pos
+
+  | isDefeat pos =
+    pure $! emptySearchResult minScore
+
+  | ply > 0 && isDraw pos =
+    pure $! emptySearchResult 0
+
+  | ply > 0 && isWonEndgame pos =
+    pure $! emptySearchResult (maxScore - 1000)
+
+  | otherwise = do
+  ttResult <- liftIO $ TTable.lookupScore alpha beta extendedDepth zKey
+  case ttResult of
+    Just (!score, !bestMove) -> pure $! SearchResult score bestMove Nothing
+    Nothing -> cacheNodeResult alpha beta extendedDepth ply zKey pos
+  where
+    zKey = getZobristKey pos
+    extendedDepth =
+      if isKingInCheck pos || (hasSingleMove pos && ply < 30)
+      then depth + 1
+      else depth
+
+
+cacheNodeResult
+  :: (?killersTable :: KillersTable, ?tTable :: TTable,
+     ?opts :: EngineOptions, ?nodes :: IORef Word64, ?age :: Age)
+  => Score -> Score -> Depth -> Ply -> ZKey -> Position -> IO SearchResult
+cacheNodeResult !alpha !beta !depth !ply !zKey pos = do
+  modifyIORef' ?nodes (+ 1)
+  searchResult@SearchResult {..} <- getNodeResult alpha beta depth ply pos
+  let
+    !nodeType = getNodeType alpha beta score
+    !ttScore = case nodeType of
+      PV  -> score
+      Cut -> beta
+      All -> alpha
+    !newTEntry = TEntry {
+        TEntry.depth      = depth
+      , TEntry.bestMove   = bestMove
+      , TEntry.score      = ttScore
+      , TEntry.nodeType   = nodeType
+      , TEntry.zobristKey = zKey
+      , TEntry.age        = ?age
+    }
+  TTable.insert zKey newTEntry
+  pure searchResult
+
+
+-- Features:
+-- - Quiescence search
+-- - Null move prunning
+-- - Futility prunning
+
+getNodeResult
+  :: (?killersTable :: KillersTable, ?tTable :: TTable,
+     ?opts :: EngineOptions, ?nodes :: IORef Word64, ?age :: Age)
+  => Score -> Score -> Depth -> Ply -> Position -> IO SearchResult
+getNodeResult !alpha !beta !depth !ply pos
+
+  | depth == 0
+  || depth <= 3
+    && not (isKingInCheck pos)
+    && inRange (-1000) 1000 (fromIntegral staticEval)
+    && staticEval + futilityMargin <= alpha =
+    emptySearchResult <$> quiesceSearch alpha beta 0 pos
+
+  | otherwise = do
+    nullMoveScore <- getNullMoveScore beta depth ply pos
+    if any (>= beta) nullMoveScore
+      then pure $! emptySearchResult beta
+      else traverseMoves =<< getSortedMoves depth ply pos
+
+  where
+    staticEval          = evaluatePosition pos
+    !futilityMargin     = futilityMargins !! (fromIntegral depth - 1)
+    traverseMoves moves = do
+      let movesSearch   = getMovesScore beta depth ply moves pos
+      (!score, !searchResult) <-
+        runStateT movesSearch (emptySearchResult alpha)
+      let
+        !newAlpha = searchResult.score
+        !newScore = fromMaybe newAlpha score
+      pure $! searchResult {score = newScore}
+
+
+getMovesScore
+  :: (?killersTable :: KillersTable, ?tTable :: TTable,
+    ?opts :: EngineOptions, ?nodes :: IORef Word64, ?age :: Age)
+  => Score -> Depth -> Ply -> ([Move], [Move]) -> Position
+  -> SearchM (Maybe Score)
+getMovesScore !beta !depth !ply (mainMoves, reducedMoves) pos = do
+  mainSearchScore <- mainMovesSearch
+  maybe reducedMovesSearch (pure . Just) mainSearchScore
+
+  where
+    mainMovesSearch       = movesSearch False mainMoves
+    reducedMovesSearch    = movesSearch True  reducedMoves
+    movesSearch isReduced =
+      findTraverseIndex (getMoveScore beta depth ply isReduced pos)
+
+
+-- Features:
+-- - Principal Variation Search
+-- - Late Move Reductions
+
+getMoveScore
+  :: (?killersTable :: KillersTable, ?tTable :: TTable,
+     ?opts :: EngineOptions, ?nodes :: IORef Word64, ?age :: Age)
+  => Score -> Depth -> Ply -> Bool -> Position -> Int -> Move -> SearchM (Maybe Score)
+getMoveScore !beta !depth !ply !isReduced pos !mvIdx !mv = do
+  SearchResult {score = alpha} <- get
+  if | alpha > 0 && isNotWinnable pos -> pure Nothing
+     | isReduced || mvIdx > 0         -> nullWindowSearch
+     | otherwise                      -> fullSearch
+  where
+    !lmrDepth
+      | isReduced && quietMove = getLmrDepth mvIdx depth
+      | otherwise             = depth
+
+    quietMove = isQuietMove mv pos
+
+    nullWindowSearch = do
+     st@SearchResult {score = alpha} <- get
+     SearchResult {score} <- getScore alpha (alpha + 1)
+     put st
+     if score > alpha
+       then fullSearch
+       else pure Nothing
+
+    fullSearch = do
+      SearchResult {score = alpha} <- get
+      SearchResult{..} <- getScore alpha beta
+      let nodeType    = getNodeType alpha beta score
+      !newScore <- advanceState beta score ply nodeType mv bestMove pos
+      pure newScore
+
+    getScore !alpha' !beta' = liftIO
+      $ getNegamaxScore alpha' beta' lmrDepth ply (makeMove mv pos)
+
+
+getNullMoveScore
+  :: (?killersTable :: KillersTable, ?tTable :: TTable,
+     ?opts :: EngineOptions, ?nodes :: IORef Word64, ?age :: Age)
+  => Score -> Depth -> Ply -> Position -> IO (Maybe Score)
+getNullMoveScore !beta !depth !ply pos
+
+  | depth > r && not (isKingInCheck pos)
+              && not (isEndgame pos) = do
+    SearchResult {..} <- getNegamaxScore alpha beta reducedDepth ply
+                                       (makeNullMove pos)
+    pure $ Just score
+
+  | otherwise = pure Nothing
+
+  where
+    reducedDepth = depth - r
+    alpha        = beta - 1
+    r            = 2
+
+
+getNegamaxScore
+  :: (?killersTable :: KillersTable, ?tTable :: TTable,
+     ?opts :: EngineOptions, ?nodes :: IORef Word64, ?age :: Age)
+  => Score -> Score -> Depth -> Ply -> Position -> IO SearchResult
+getNegamaxScore !alpha !beta !depth !ply !pos = do
+  result <- negamax (-beta) (-alpha) (depth - 1) (ply + 1) pos
+  pure $ result {score = - result.score}
+
+
+advanceState :: (?killersTable :: KillersTable)
+  => Score -> Score -> Ply -> NodeType -> Move -> Maybe Move -> Position
+  -> SearchM (Maybe Score)
+advanceState !beta !score !ply !nodeType !mv !enemyMv pos =
+  case nodeType of
+    PV  -> put searchResult
+            $> Nothing
+    Cut -> put searchResult
+            *> liftIO (KillersTable.insert ply pos mv)
+            $> Just beta
+    All -> pure Nothing
+    where
+      !searchResult = SearchResult score (Just mv) enemyMv
+
+
+getGameResult :: Score -> GameResult
+getGameResult score
+  | score == maxScore = Victory
+  | score == minScore = Defeat
+  | otherwise        = InProgress
+
+
+printSearchInfo :: Depth -> Word64 -> MicroSeconds -> SearchResult -> IO ()
+printSearchInfo depth nodes elapsedTime SearchResult{..} = do
+  putStrLn ("info"
+    <> " depth " <> tshow depth
+    <> " score " <> showScore
+    <> " nodes " <> tshow nodes
+    <> " nps "   <> tshow nps
+    <> " time "  <> tshow timeMillis
+    <> " pv "    <> unwords (tshow <$> pv))
+  hFlush stdout
+  where
+    nps        = nodes * 1_000_000 `div` elapsedTime
+    timeMillis = elapsedTime `div` 1_000
+    pv         = catMaybes [bestMove, ponderMove]
+    showScore = case getGameResult score of
+      InProgress -> "cp "   <> tshow score
+      Victory    -> "mate " <> tshow @Int 1
+      Defeat     -> "mate " <> tshow @Int (-1)
+
+
+emptySearchResult :: Score -> SearchResult
+emptySearchResult score = SearchResult score Nothing Nothing
+
+
+data SearchResult = SearchResult {
+    score      :: Score
+  , bestMove   :: Maybe Move
+  , ponderMove :: Maybe Move
+}
+
+
+data GameResult
+  = InProgress
+  | Victory
+  | Defeat
+  deriving (Eq)
+
+
+type SearchM = StateT SearchResult IO
diff --git a/src/Search/TimeManagement.hs b/src/Search/TimeManagement.hs
new file mode 100644
--- /dev/null
+++ b/src/Search/TimeManagement.hs
@@ -0,0 +1,65 @@
+module Search.TimeManagement (MicroSeconds, getMoveTime, maybeTimeout, isTimeOver, (|-|))  where
+
+import           AppPrelude                 hiding (timeout)
+import           Evaluation.Parameters
+import           Evaluation.ScoreBreakdown
+import           Models.Command
+import           Models.Piece
+import           Models.Position
+
+import           Control.Concurrent.Timeout (timeout)
+import           Data.Time.Clock.System
+
+
+getMoveTime :: SearchOptions -> Position -> Maybe MicroSeconds
+getMoveTime SearchOptions {..} Position {phase, color} =
+  fromIntegral . (* 1000) <$> (moveTime <|> timeToMove)
+  where
+    timeToMove
+      | Just t <- time
+      , Just i <- inc
+        = Just (min (t / 2) (3 * t / (2 * movesUntil) + i))
+      | Just t <- time
+        = Just (t / movesUntil)
+      | Just i <- inc
+        = Just i
+      | otherwise
+        = Nothing
+    (time, inc) = case color of
+      White -> (whiteTime, whiteIncrement)
+      Black -> (blackTime, blackIncrement)
+    movesUntil = fromMaybe movesUntilDefault movesUntilNextTime
+    movesUntilDefault =
+      let ?phase = phase in fromIntegral $ taperScore (ScorePair 40 10)
+
+
+maybeTimeout :: Maybe MicroSeconds -> IO () -> IO ()
+maybeTimeout (Just duration) action =
+  timeout (fromIntegral duration) action $> ()
+maybeTimeout Nothing action         =
+  action
+
+
+isTimeOver :: SystemTime -> SystemTime -> Maybe MicroSeconds -> Bool
+isTimeOver endTime startTime (Just moveTime) =
+  endTime |-| startTime > getTimeOver moveTime
+isTimeOver _ _ Nothing =
+  False
+
+
+getTimeOver :: MicroSeconds -> MicroSeconds
+getTimeOver moveTime =
+  moveTime * 30 / 100
+
+
+infixl 9 |-|
+(|-|) :: SystemTime -> SystemTime -> MicroSeconds
+(|-|) endTime startTime =
+  systemTimeToMicros endTime - systemTimeToMicros startTime
+  where
+  systemTimeToMicros MkSystemTime {..} =
+    fromIntegral systemSeconds * 1_000_000
+    + fromIntegral systemNanoseconds / 1000
+
+
+type MicroSeconds = Word64
diff --git a/src/Utils/Board.hs b/src/Utils/Board.hs
new file mode 100644
--- /dev/null
+++ b/src/Utils/Board.hs
@@ -0,0 +1,440 @@
+{- HLINT ignore "Use camelCase" -}
+
+module Utils.Board (Board, Square, (>>), (<<), (&), (.\), (.|), (^), (~),  popCount, popCountToBoard, lsb, msb, toBoard, toCondition, getSquareDistance, toReverseCondition, testSquare, toFile, toRank, knightMovesVec, kingMovesVec, fileMovesVec, rankMovesVec, diagMovesVec, antiDiagMovesVec, northEastMovesVec, northWestMovesVec, southEastMovesVec, southWestMovesVec, northMovesVec, westMovesVec, southMovesVec, eastMovesVec, shortCastleSlidingFiles, longCastleSlidingFiles, shortCastleFiles, longCastleFiles, whiteKnightOutpostRanks, blackKnightOutpostRanks, knightOupostFiles, castlingRngVec, enPassantRngVec, sideToMoveRng, pieceRngVec, whiteKnightOutpostAttackersVec, blackKnightOutpostAttackersVec, whitePassedPawnBlockersVec, blackPassedPawnBlockersVec, squares, rank_1, rank_2, rank_3, rank_4, rank_5, rank_6, rank_7, rank_8, file_A, file_B, file_C, file_D, file_E, file_F, file_G, file_H)  where
+
+import           AppPrelude           hiding (foldl', map)
+
+import qualified Data.Bits            as Bits
+import           Data.Vector.Storable (foldl', foldl1, foldl1', map, slice)
+import           System.IO.Unsafe     (unsafePerformIO)
+import           System.Random
+
+type Board = Word64
+
+type Square = Int
+type SideSquare = Int
+type Diagonal = Int
+type Rank = Int
+type File = Int
+type Diag = Int
+
+
+{-# SPECIALIZE (<<) :: Board -> Square -> Board #-}
+infixl 9 <<
+(<<) :: Bits.Bits a => a -> Square -> a
+(<<) !x !y = Bits.unsafeShiftL x y
+
+{-# SPECIALIZE (>>) :: Board -> Square -> Board #-}
+infixl 9 >>
+(>>) :: Bits.Bits a => a -> Square -> a
+(>>) !x !y = Bits.unsafeShiftR x y
+
+infixl 8 &
+(&) :: Board -> Board -> Board
+(&) !x !y = (Bits..&.) x y
+
+infixl 7 .\
+(.\) :: Board -> Board -> Board
+(.\) !x !y = x & (~) y
+
+infixl 7 .|
+(.|) :: Board -> Board -> Board
+(.|) !x !y = (Bits..|.) x y
+
+infixl 7 ^
+(^) :: Board -> Board -> Board
+(^) !x !y = Bits.xor x y
+
+infixl 9 ~
+(~) :: Board -> Board
+(~) !x = Bits.complement x
+
+popCount :: Board -> Int
+popCount !x = Bits.popCount x
+
+popCountToBoard :: Board -> Board
+popCountToBoard !x = fromIntegral $! popCount x
+
+lsb :: Board -> Square
+lsb !x = Bits.countTrailingZeros x
+
+msb :: Board -> Square
+msb !x = 65 * (zeros / 64) + 63 - zeros
+  where
+    !zeros = Bits.countLeadingZeros x
+
+{-# SPECIALIZE toReverseCondition :: Board -> Board #-}
+{-# SPECIALIZE toReverseCondition :: Square -> Square #-}
+toReverseCondition :: (Num a, Ord a) => a -> a
+toReverseCondition !x = 1 - toCondition x
+
+{-# SPECIALIZE toCondition :: Board -> Board #-}
+{-# SPECIALIZE toCondition :: Square -> Square #-}
+toCondition :: (Ord a, Num a) => a -> a
+toCondition !x = min 1 x
+
+toBoard :: Square -> Board
+toBoard !n = fromIntegral (1 - n / 64) * (1 << n)
+
+testSquare :: Board -> Square -> Bool
+testSquare !x !n = x & toBoard n /= 0
+
+toFile :: Square -> File
+toFile !n = n % 8
+
+toRank :: Square -> Rank
+toRank !n = n / 8
+
+knightMove :: Square -> Board
+knightMove n =
+    move1 .| move2 .| move3 .| move4 .| move5
+          .| move6 .| move7 .| move8
+  where
+    move1 = (board .\ (file_H .| file_G .| rank_8)) << 10
+    move3 = (board .\ (file_H .| rank_7 .| rank_8)) << 17
+    move4 = (board .\ (file_A .| rank_7 .| rank_8)) << 15
+    move5 = (board .\ (file_A .| file_B .| rank_8)) << 6
+    move6 = (board .\ (file_A .| file_B .| rank_1)) >> 10
+    move7 = (board .\ (file_A .| rank_2 .| rank_1)) >> 17
+    move8 = (board .\ (file_H .| rank_2 .| rank_1)) >> 15
+    move2 = (board .\ (file_H .| file_G .| rank_1)) >> 6
+    board = toBoard n
+
+kingMove :: Square -> Board
+kingMove n =
+    move1 .| move2 .| move3 .| move4 .| move5
+          .| move6 .| move7 .| move8
+  where
+    move1 = (board .\ file_H) << 1
+    move2 = (board .\ (file_H .| rank_8)) << 9
+    move3 = (board .\ file_A) >> 1
+    move4 = (board .\ (file_A .| rank_1)) >> 9
+    move5 = (board .\ rank_8) << 8
+    move6 = (board .\ (file_A .| rank_8)) << 7
+    move7 = (board .\ rank_1) >> 8
+    move8 = (board .\ (file_H .| rank_1)) >> 7
+    board = toBoard n
+
+
+knightOutpostAttackers :: Bool -> Int -> Board
+knightOutpostAttackers above n = filesBoard & ranksBoard
+  where
+    filesBoard = getPreviousFile file .| getNextFile file
+    ranksBoard = foldl' (.|) 0
+      $ map getRank
+      $ filter (compareFn rank) sideSquares
+    rank = toRank n
+    file = toFile n
+    compareFn | above     = (<)
+              | otherwise = (>)
+
+passedPawnBlockers :: Bool -> Int -> Board
+passedPawnBlockers above n = filesBoard & ranksBoard
+  where
+    filesBoard = getPreviousFile file .| getFile file .| getNextFile file
+    ranksBoard = foldl' (.|) 0
+      $ map getRank
+      $ filter (compareFn rank) sideSquares
+    rank = toRank n
+    file = toFile n
+    compareFn | above     = (<)
+              | otherwise = (>)
+
+
+squareDistance :: Square -> Square -> Int
+squareDistance n1 n2 = max fileDistance rankDistance
+  where
+    fileDistance = abs (file1 - file2)
+    rankDistance = abs (rank1 - rank2)
+    file1        = toFile n1
+    file2        = toFile n2
+    rank1        = toRank n1
+    rank2        = toRank n2
+
+
+fileMove :: Square -> Board
+fileMove n = ranks !! toRank n
+
+rankMove :: Square -> Board
+rankMove n = files !! toFile n
+
+diagMove :: Square -> Board
+diagMove n = antiDiags !! (toFile n + toRank n)
+
+antiDiagMove :: Square -> Board
+antiDiagMove n = diags !! (7 - toFile n + toRank n)
+
+getRank :: Rank -> Board
+getRank n = foldl1' (.|) (map f sideSquares)
+  where
+    f = (1 << (8 * n) <<)
+
+getFile :: File -> Board
+getFile n = foldl1' (.|) (map f sideSquares)
+  where
+    f = (1 << n <<) . (* 8)
+
+getPreviousFile :: File -> Board
+getPreviousFile n
+  | n == 0 = 0
+  | otherwise = getFile (n - 1)
+
+getNextFile :: File -> Board
+getNextFile n
+  | n == 7 = 0
+  | otherwise = getFile (n + 1)
+
+getSquareDistance :: Square -> Square -> Int
+getSquareDistance n1 n2 =
+  squareDistanceVec !! (n1 * 64 + n2)
+
+
+getDiag :: Diag -> Board
+getDiag = diagHelper (7 -)
+
+getAntiDiag :: Int -> Board
+getAntiDiag = diagHelper id
+
+diagHelper :: (Int -> Int) -> Int -> Board
+diagHelper f n = foldl1 (.|) xs
+  where
+  xs = fromList do
+    x <- toList sideSquares
+    let y = f (n - x) `rem` 8
+    guard $ inRange 0 7 (n - x)
+    pure $ toBoard (y + 8 * x)
+
+
+-- Move Functions
+northMove :: Square -> Board
+northMove n = aboveMask n & rankMovesVec !! n
+
+eastMove :: Square -> Board
+eastMove n = aboveMask n & fileMovesVec !! n
+
+westMove :: Square -> Board
+westMove n = belowMask n & fileMovesVec !! n
+
+southMove :: Square -> Board
+southMove n = belowMask n & rankMovesVec !! n
+
+northWestMove :: Square -> Board
+northWestMove n = aboveMask n & diagMovesVec !! n
+
+northEastMove :: Square -> Board
+northEastMove n = aboveMask n & antiDiagMovesVec !! n
+
+southEastMove :: Square -> Board
+southEastMove n = belowMask n & diagMovesVec !! n
+
+southWestMove :: Square -> Board
+southWestMove n = belowMask n & antiDiagMovesVec !! n
+
+
+-- Masks
+aboveMask :: Square -> Board
+aboveMask n = (toBoard 63 - toBoard n) << 1
+
+belowMask :: Square -> Board
+belowMask n = toBoard n - 1
+
+
+-- Directions
+ranks :: Vector Board
+ranks = map getRank sideSquares
+
+files :: Vector Board
+files = map getFile sideSquares
+
+diags :: Vector Board
+diags = map getDiag diagonals
+
+antiDiags :: Vector Board
+antiDiags = map getAntiDiag diagonals
+
+
+-- Cached Piece moves
+knightMovesVec :: Vector Board
+knightMovesVec = map knightMove squares
+
+kingMovesVec :: Vector Board
+kingMovesVec = map kingMove squares
+
+
+-- Sliding moves
+fileMovesVec :: Vector Board
+fileMovesVec = snoc (map fileMove squares) 0
+
+rankMovesVec :: Vector Board
+rankMovesVec = snoc (map rankMove squares) 0
+
+diagMovesVec :: Vector Board
+diagMovesVec = snoc (map diagMove squares) 0
+
+antiDiagMovesVec :: Vector Board
+antiDiagMovesVec = snoc (map antiDiagMove squares) 0
+
+westMovesVec :: Vector Board
+westMovesVec = snoc (map westMove squares) 0
+
+northMovesVec :: Vector Board
+northMovesVec = snoc (map northMove squares) 0
+
+eastMovesVec :: Vector Board
+eastMovesVec = snoc (map eastMove squares) 0
+
+southMovesVec :: Vector Board
+southMovesVec = snoc (map southMove squares) 0
+
+northWestMovesVec :: Vector Board
+northWestMovesVec = snoc (map northWestMove squares) 0
+
+northEastMovesVec :: Vector Board
+northEastMovesVec = snoc (map northEastMove squares) 0
+
+southEastMovesVec :: Vector Board
+southEastMovesVec = snoc (map southEastMove squares) 0
+
+southWestMovesVec :: Vector Board
+southWestMovesVec = snoc (map southWestMove squares) 0
+
+
+-- Evaluation
+whiteKnightOutpostAttackersVec :: Vector Board
+whiteKnightOutpostAttackersVec =
+  map (knightOutpostAttackers True) squares
+
+blackKnightOutpostAttackersVec :: Vector Board
+blackKnightOutpostAttackersVec =
+  map (knightOutpostAttackers False) squares
+
+whitePassedPawnBlockersVec :: Vector Board
+whitePassedPawnBlockersVec =
+  map (passedPawnBlockers True) squares
+
+blackPassedPawnBlockersVec :: Vector Board
+blackPassedPawnBlockersVec =
+  map (passedPawnBlockers False) squares
+
+squareDistanceVec :: Vector Int
+squareDistanceVec = fromList do
+  n1 <- toList squares
+  n2 <- toList squares
+  pure $ squareDistance n1 n2
+
+
+{-# NOINLINE pieceRngVec #-}
+pieceRngVec :: Vector Board
+pieceRngVec = unsafePerformIO
+  (slice 0 768 <$> genRandoms)
+
+
+{-# NOINLINE castlingRngVec #-}
+castlingRngVec :: Vector Board
+castlingRngVec = unsafePerformIO
+  (slice 768 16 <$> genRandoms)
+
+
+{-# NOINLINE enPassantRngVec #-}
+enPassantRngVec :: Vector Board
+enPassantRngVec = unsafePerformIO
+  (slice 784 8 <$> genRandoms)
+
+
+{-# NOINLINE sideToMoveRng #-}
+sideToMoveRng :: Board
+sideToMoveRng = unsafePerformIO
+  ((!! 792) <$> genRandoms)
+
+
+genRandoms :: IO (Vector Board)
+genRandoms = fromList
+  . take 1000 . randoms
+  <$> getStdGen
+
+
+-- Ranges
+squares :: Vector Square
+squares = fromList [0 .. 63]
+
+sideSquares :: Vector SideSquare
+sideSquares = fromList [0 .. 7]
+
+diagonals :: Vector Diagonal
+diagonals = fromList [0 .. 14]
+
+
+-- Useful boards
+shortCastleSlidingFiles :: Board
+shortCastleSlidingFiles = file_F .| file_G
+
+longCastleSlidingFiles :: Board
+longCastleSlidingFiles = file_C .| file_D
+
+
+shortCastleFiles :: Board
+shortCastleFiles = file_F .| file_G .| file_H
+
+longCastleFiles :: Board
+longCastleFiles = file_A .| file_B .| file_C
+
+whiteKnightOutpostRanks :: Board
+whiteKnightOutpostRanks = rank_5 .| rank_6 .| rank_7
+
+blackKnightOutpostRanks :: Board
+blackKnightOutpostRanks = rank_4 .| rank_3 .| rank_2
+
+knightOupostFiles :: Board
+knightOupostFiles = file_C .| file_D .| file_E .| file_F
+
+
+-- Ranks
+rank_1 :: Board
+rank_1 = getRank 0
+
+rank_2 :: Board
+rank_2 = getRank 1
+
+rank_3 :: Board
+rank_3 = getRank 2
+
+rank_4 :: Board
+rank_4 = getRank 3
+
+rank_5 :: Board
+rank_5 = getRank 4
+
+rank_6 :: Board
+rank_6 = getRank 5
+
+rank_7 :: Board
+rank_7 = getRank 6
+
+rank_8 :: Board
+rank_8 = getRank 7
+
+
+-- Files
+file_A :: Board
+file_A = getFile 0
+
+file_B :: Board
+file_B = getFile 1
+
+file_C :: Board
+file_C = getFile 2
+
+file_D :: Board
+file_D = getFile 3
+
+file_E :: Board
+file_E = getFile 4
+
+file_F :: Board
+file_F = getFile 5
+
+file_G :: Board
+file_G = getFile 6
+
+file_H :: Board
+file_H = getFile 7
diff --git a/src/Utils/KillersTable.hs b/src/Utils/KillersTable.hs
new file mode 100644
--- /dev/null
+++ b/src/Utils/KillersTable.hs
@@ -0,0 +1,49 @@
+module Utils.KillersTable where
+
+import           AppPrelude
+
+import           Models.Move
+import           Models.Position
+import           Models.Score
+import           MoveGen.MoveQueries
+
+import           Data.Vector.Storable.Mutable (IOVector)
+import qualified Data.Vector.Storable.Mutable as Vector
+
+
+type KillersTable = IOVector StorableMove
+
+
+lookupMoves :: (?killersTable :: KillersTable) => Ply -> IO [Move]
+lookupMoves !ply = do
+  firstMove  <- Vector.unsafeRead ?killersTable idx
+  secondMove <- Vector.unsafeRead ?killersTable (idx + 1)
+  pure $ mapMaybe decodeMove [firstMove, secondMove]
+  where
+    !idx = killerSlots * fromIntegral ply
+
+
+insert :: (?killersTable :: KillersTable) => Ply -> Position -> Move -> IO ()
+insert !ply pos mv =
+  when (isQuietMove mv pos) do
+  firstMove  <- Vector.unsafeRead ?killersTable idx
+  unless (mv `elem` decodeMove firstMove) do
+    Vector.unsafeWrite ?killersTable idx
+                                    (encodeMove $ Just mv)
+    Vector.unsafeWrite ?killersTable (idx + 1)
+                                     firstMove
+  where
+    !idx = killerSlots * fromIntegral ply
+
+
+create :: IO KillersTable
+create = Vector.replicate (killerSlots * (fromIntegral (maxBound @Ply) + 1))
+                          (encodeMove Nothing)
+
+
+clear ::  KillersTable -> IO ()
+clear = Vector.clear
+
+
+killerSlots :: Int
+killerSlots = 2
diff --git a/src/Utils/TranspositionTable.hs b/src/Utils/TranspositionTable.hs
new file mode 100644
--- /dev/null
+++ b/src/Utils/TranspositionTable.hs
@@ -0,0 +1,154 @@
+module Utils.TranspositionTable where
+
+import           AppPrelude
+
+import           Models.Command
+import           Models.Move
+import           Models.Position              (ZKey (..))
+import           Models.Score
+import           Utils.Board
+
+import           Data.Vector.Storable.Mutable (IOVector)
+import qualified Data.Vector.Storable.Mutable as Vector
+import           Data.Word
+import           Foreign.Storable.Generic
+import           Test.QuickCheck
+
+
+type TTable = IOVector StorableTEntry
+
+
+data TEntry = TEntry {
+  zobristKey :: ZKey,
+  bestMove   :: Maybe Move,
+  score      :: Score,
+  depth      :: Depth,
+  nodeType   :: NodeType,
+  age        :: Age
+} deriving (Eq, Show, Generic)
+
+instance Arbitrary TEntry where
+  arbitrary = TEntry <$> arbitrary <*> arbitrary
+                     <*> arbitrary <*> arbitrary
+                     <*> arbitrary <*> choose (0, 31)
+
+data StorableTEntry = StorableTEntry {
+  zobristKey :: ZKey,
+  info       :: Word64
+} deriving Generic
+
+instance GStorable StorableTEntry
+
+
+tTableSize :: (?opts :: EngineOptions) => Word64
+tTableSize | ?opts.hashSize == 0 = 0
+           | otherwise          = toBoard bits
+  where
+    bits = msb (fromIntegral ?opts.hashSize) + 16
+
+
+hashZKey :: (?opts :: EngineOptions) => ZKey -> Int
+hashZKey (ZKey zKey) =
+  fromIntegral (zKey % tTableSize)
+
+
+encodeTEntry :: TEntry -> StorableTEntry
+encodeTEntry TEntry {..} = StorableTEntry {
+  zobristKey = zobristKey,
+  info = fromIntegral bestMoveN
+    .| fromIntegral (fromIntegral score :: Word16) << 32
+    .| fromIntegral depth << 48
+    .| fromIntegral nodeTypeN << 56
+    .| fromIntegral age << 58
+  }
+  where
+    StorableMove bestMoveN = encodeMove bestMove
+    NodeType nodeTypeN     = nodeType
+
+
+decodeTEntry :: StorableTEntry -> Maybe TEntry
+decodeTEntry StorableTEntry {..}
+  | testSquare info 63 = Nothing
+  | otherwise          = Just TEntry {
+      zobristKey = zobristKey
+      , bestMove   = decodeMove $ fromIntegral info
+      , score      = fromIntegral (info >> 32)
+      , depth      = fromIntegral (info >> 48)
+      , nodeType   = fromIntegral ((info >> 56) & 3)
+      , age        = fromIntegral ((info >> 58) & 31)
+  }
+
+
+lookupEntry
+  :: (?tTable :: TTable, ?opts :: EngineOptions) => ZKey -> IO (Maybe TEntry)
+lookupEntry !zKey
+  | tTableSize == 0 = pure Nothing
+  | otherwise      = do
+  entry <- decodeTEntry <$> Vector.unsafeRead ?tTable (hashZKey zKey)
+  pure (maybeFilter ((== zKey) . (.zobristKey)) entry)
+
+
+lookupScore
+  :: (?tTable :: TTable, ?opts :: EngineOptions)
+  => Score -> Score -> Depth -> ZKey -> IO (Maybe (Score, Maybe Move))
+lookupScore !alpha !beta !depth !zKey = do
+  entry <- maybeFilter ((>= depth) . (.depth)) <$> lookupEntry zKey
+  let !score = getScore =<< entry
+      !bestMove = (.bestMove) =<< entry
+  pure ((, bestMove) <$> score)
+  where
+    getScore TEntry {score, nodeType} =
+      case nodeType of
+        PV                   -> Just score
+        Cut | score >= beta  -> Just beta
+        All | score <= alpha -> Just alpha
+        _                    -> Nothing
+
+
+lookupBestMove
+  :: (?tTable :: TTable, ?opts :: EngineOptions) => ZKey -> IO (Maybe Move)
+lookupBestMove !zKey = do
+  entry <- lookupEntry zKey
+  pure ((.bestMove) =<< entry)
+
+
+insert
+  :: (?tTable :: TTable, ?opts :: EngineOptions) => ZKey -> TEntry -> IO ()
+insert !zKey !newEntry
+ | tTableSize == 0 = pure ()
+ | otherwise       = do
+  entry <- decodeTEntry <$> Vector.unsafeRead ?tTable hashKey
+  when (isStaleEntry entry newEntry)
+    $ Vector.unsafeWrite ?tTable hashKey (encodeTEntry newEntry)
+  where
+    hashKey = hashZKey zKey
+
+
+isStaleEntry :: Maybe TEntry -> TEntry -> Bool
+isStaleEntry (Just entry) newEntry =
+  newEntry.age - entry.age > 1
+  || newEntry.depth >= entry.depth
+  && (newEntry.nodeType == PV || entry.nodeType /= PV)
+isStaleEntry Nothing _ =
+  True
+
+
+emptyTEntry :: StorableTEntry
+emptyTEntry = StorableTEntry {
+  zobristKey = 0,
+  info       = toBoard 63
+}
+
+
+create ::  EngineOptions -> IO TTable
+create opts = Vector.replicate (fromIntegral tTableSize) emptyTEntry
+  where
+    ?opts = opts
+
+
+reset :: TTable -> IO ()
+reset = flip Vector.set emptyTEntry
+
+
+clear :: TTable -> IO ()
+clear = Vector.clear
diff --git a/test/Search/PerftSpec.hs b/test/Search/PerftSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Search/PerftSpec.hs
@@ -0,0 +1,68 @@
+module Search.PerftSpec where
+
+import           AppPrelude
+
+import           Control.Error.Util (hush)
+import           Data.Maybe         (fromJust)
+import           Test.Hspec
+
+import           Bookhound.Parser
+import           Models.Position
+import           Parsers.Position
+import           Search.Perft
+
+spec :: Spec
+spec = do
+
+  describe "Perft tests" do
+
+    it "Initial Position" do
+      perft 6 startPosition `shouldBe` 119_060_324
+
+    it "Kiwipete" do
+      perft 5 kiwipete `shouldBe` 193_690_690
+
+    it "Extra Position 1" do
+      perft 7 extraPos1 `shouldBe` 178_633_661
+
+    it "Extra Position 2" do
+      perft 6 extraPos2 `shouldBe` 706_045_033
+
+    it "Extra Position 3" do
+      perft 5 extraPos3 `shouldBe` 89_941_194
+
+    it "Extra Position 4" do
+      perft 5 extraPos4 `shouldBe` 164_075_551
+
+    it "Extra Position 5" do
+      perft 5 extraPos5 `shouldBe` 73_007_159
+
+
+
+kiwipete :: Position
+kiwipete = fenPosition
+  "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1"
+
+extraPos1 :: Position
+extraPos1 = fenPosition
+  "8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - - 0 1"
+
+extraPos2 :: Position
+extraPos2 = fenPosition
+  "r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq - 0 1"
+
+extraPos3 :: Position
+extraPos3 = fenPosition
+  "rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ - 0 1"
+
+extraPos4 :: Position
+extraPos4 = fenPosition
+  "r4rk1/1pp1qppp/p1np1n2/2b1p1B1/2B1P1b1/P1NP1N2/1PP1QPPP/R4RK1 w - - 0 1"
+
+extraPos5 :: Position
+extraPos5 = fenPosition
+  "1k1r2r1/1pp2p1p/np1p1pb1/3P2q1/1PPN4/P6Q/4BPPP/3RR1K1 b - - 0 1"
+
+
+fenPosition :: Text -> Position
+fenPosition = fromJust . hush . runParser positionFenParser
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,1 @@
+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
diff --git a/test/Utils/TranspositionTableSpec.hs b/test/Utils/TranspositionTableSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Utils/TranspositionTableSpec.hs
@@ -0,0 +1,21 @@
+module Utils.TranspositionTableSpec where
+
+import           AppPrelude
+
+import           Test.QuickCheck.Property
+
+import           Test.Hspec               (Spec, it, shouldBe)
+import           Test.QuickCheck          (Args (maxSuccess), quickCheckWith,
+                                           stdArgs)
+import           Utils.TranspositionTable
+
+
+spec :: Spec
+spec = do
+
+  it "Encode then decode Table entries" $
+    quickCheckWith stdArgs {maxSuccess = 1_000 }
+    \tEntry -> (decodeTEntry . encodeTEntry) tEntry === Just tEntry
+
+  it "Decode empty Table entry" $
+     decodeTEntry emptyTEntry `shouldBe` Nothing
diff --git a/turncoat.cabal b/turncoat.cabal
new file mode 100644
--- /dev/null
+++ b/turncoat.cabal
@@ -0,0 +1,187 @@
+cabal-version: 1.12
+
+-- This file has been generated from package.yaml by hpack version 0.37.0.
+--
+-- see: https://github.com/sol/hpack
+
+name:           turncoat
+version:        1.0
+synopsis:       Elegant UCI chess engine
+description:    Please see the README on GitHub at <https://github.com/albertprz/turncoat#readme>
+category:       Chess Engine
+homepage:       https://github.com/albertprz/turncoat#readme
+bug-reports:    https://github.com/albertprz/turncoat/issues
+author:         Alberto Perez
+maintainer:     albertoperez1994@gmail.com
+copyright:      2023 Alberto Perez
+license:        BSD3
+license-file:   LICENSE
+build-type:     Simple
+extra-source-files:
+    README.md
+
+source-repository head
+  type: git
+  location: https://github.com/albertprz/turncoat
+
+library
+  exposed-modules:
+      AppPrelude
+      Evaluation.Evaluation
+      Evaluation.Material
+      Evaluation.Parameters
+      Evaluation.ScoreBreakdown
+      Models.Command
+      Models.Move
+      Models.Piece
+      Models.Position
+      Models.Score
+      MoveGen.MakeMove
+      MoveGen.MoveQueries
+      MoveGen.PieceAttacks
+      MoveGen.PieceCaptures
+      MoveGen.PieceQuietMoves
+      MoveGen.PositionQueries
+      Parsers.Command
+      Parsers.Position
+      Search.MoveOrdering
+      Search.Parameters
+      Search.Perft
+      Search.Quiescence
+      Search.Search
+      Search.TimeManagement
+      Utils.Board
+      Utils.KillersTable
+      Utils.TranspositionTable
+  other-modules:
+      Paths_turncoat
+  hs-source-dirs:
+      src
+  default-extensions:
+      NoImplicitPrelude
+      StrictData
+      LambdaCase
+      MultiWayIf
+      BlockArguments
+      PostfixOperators
+      DuplicateRecordFields
+      NoFieldSelectors
+      RecordWildCards
+      OverloadedRecordDot
+      OverloadedStrings
+      ViewPatterns
+      PatternSynonyms
+      ImplicitParams
+  ghc-options: -O2 -funbox-strict-fields -fspecialize-aggressively -fexpose-all-unfoldings -funfolding-use-threshold=200 -fspec-constr-count=20 -flate-dmd-anal -flate-specialise -Wall -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wredundant-constraints
+  build-depends:
+      QuickCheck
+    , async
+    , base ==4.*
+    , bookhound
+    , classy-prelude
+    , composition
+    , containers
+    , derive-storable
+    , errors
+    , mtl
+    , newtype
+    , random
+    , split
+    , text
+    , time
+    , unbounded-delays
+    , vector
+  default-language: GHC2021
+
+executable turncoat
+  main-is: Main.hs
+  other-modules:
+      Uci
+      Paths_turncoat
+  hs-source-dirs:
+      app
+  default-extensions:
+      NoImplicitPrelude
+      StrictData
+      LambdaCase
+      MultiWayIf
+      BlockArguments
+      PostfixOperators
+      DuplicateRecordFields
+      NoFieldSelectors
+      RecordWildCards
+      OverloadedRecordDot
+      OverloadedStrings
+      ViewPatterns
+      PatternSynonyms
+      ImplicitParams
+  ghc-options: -O2 -funbox-strict-fields -fspecialize-aggressively -fexpose-all-unfoldings -funfolding-use-threshold=200 -fspec-constr-count=20 -flate-dmd-anal -flate-specialise -Wall -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N
+  build-depends:
+      QuickCheck
+    , async
+    , base ==4.*
+    , bookhound
+    , classy-prelude
+    , composition
+    , containers
+    , derive-storable
+    , errors
+    , mtl
+    , newtype
+    , random
+    , split
+    , text
+    , time
+    , turncoat
+    , unbounded-delays
+    , vector
+  default-language: GHC2021
+
+test-suite test
+  type: exitcode-stdio-1.0
+  main-is: Spec.hs
+  other-modules:
+      Search.PerftSpec
+      Utils.TranspositionTableSpec
+      Paths_turncoat
+  hs-source-dirs:
+      test
+  default-extensions:
+      NoImplicitPrelude
+      StrictData
+      LambdaCase
+      MultiWayIf
+      BlockArguments
+      PostfixOperators
+      DuplicateRecordFields
+      NoFieldSelectors
+      RecordWildCards
+      OverloadedRecordDot
+      OverloadedStrings
+      ViewPatterns
+      PatternSynonyms
+      ImplicitParams
+  ghc-options: -O2 -funbox-strict-fields -fspecialize-aggressively -fexpose-all-unfoldings -funfolding-use-threshold=200 -fspec-constr-count=20 -flate-dmd-anal -flate-specialise -Wall -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wredundant-constraints -Wno-type-defaults
+  build-tool-depends:
+      hspec-discover:hspec-discover
+  build-depends:
+      QuickCheck
+    , async
+    , base ==4.*
+    , bookhound
+    , classy-prelude
+    , composition
+    , containers
+    , derive-storable
+    , errors
+    , hspec
+    , mtl
+    , newtype
+    , random
+    , split
+    , text
+    , time
+    , turncoat
+    , unbounded-delays
+    , vector
+  default-language: GHC2021
