Spaces:
Paused
Paused
Commit
·
03d67a8
1
Parent(s):
dde3070
Delete backgammon-1.0.0
Browse files- backgammon-1.0.0/.DS_Store +0 -0
- backgammon-1.0.0/LICENSE +0 -201
- backgammon-1.0.0/PKG-INFO +0 -79
- backgammon-1.0.0/README.rst +0 -61
- backgammon-1.0.0/backgammon.egg-info/PKG-INFO +0 -79
- backgammon-1.0.0/backgammon.egg-info/SOURCES.txt +0 -16
- backgammon-1.0.0/backgammon.egg-info/dependency_links.txt +0 -1
- backgammon-1.0.0/backgammon.egg-info/top_level.txt +0 -2
- backgammon-1.0.0/backgammon/__init__.py +0 -15
- backgammon-1.0.0/backgammon/__main__.py +0 -109
- backgammon-1.0.0/backgammon/backgammon.py +0 -383
- backgammon-1.0.0/backgammon/match.py +0 -177
- backgammon-1.0.0/backgammon/position.py +0 -214
- backgammon-1.0.0/setup.cfg +0 -4
- backgammon-1.0.0/setup.py +0 -36
- backgammon-1.0.0/tests/__init__.py +0 -13
- backgammon-1.0.0/tests/test_backgammon.py +0 -414
- backgammon-1.0.0/tests/test_match.py +0 -106
- backgammon-1.0.0/tests/test_position.py +0 -177
backgammon-1.0.0/.DS_Store
DELETED
|
Binary file (6.15 kB)
|
|
|
backgammon-1.0.0/LICENSE
DELETED
|
@@ -1,201 +0,0 @@
|
|
| 1 |
-
Apache License
|
| 2 |
-
Version 2.0, January 2004
|
| 3 |
-
http://www.apache.org/licenses/
|
| 4 |
-
|
| 5 |
-
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
| 6 |
-
|
| 7 |
-
1. Definitions.
|
| 8 |
-
|
| 9 |
-
"License" shall mean the terms and conditions for use, reproduction,
|
| 10 |
-
and distribution as defined by Sections 1 through 9 of this document.
|
| 11 |
-
|
| 12 |
-
"Licensor" shall mean the copyright owner or entity authorized by
|
| 13 |
-
the copyright owner that is granting the License.
|
| 14 |
-
|
| 15 |
-
"Legal Entity" shall mean the union of the acting entity and all
|
| 16 |
-
other entities that control, are controlled by, or are under common
|
| 17 |
-
control with that entity. For the purposes of this definition,
|
| 18 |
-
"control" means (i) the power, direct or indirect, to cause the
|
| 19 |
-
direction or management of such entity, whether by contract or
|
| 20 |
-
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
| 21 |
-
outstanding shares, or (iii) beneficial ownership of such entity.
|
| 22 |
-
|
| 23 |
-
"You" (or "Your") shall mean an individual or Legal Entity
|
| 24 |
-
exercising permissions granted by this License.
|
| 25 |
-
|
| 26 |
-
"Source" form shall mean the preferred form for making modifications,
|
| 27 |
-
including but not limited to software source code, documentation
|
| 28 |
-
source, and configuration files.
|
| 29 |
-
|
| 30 |
-
"Object" form shall mean any form resulting from mechanical
|
| 31 |
-
transformation or translation of a Source form, including but
|
| 32 |
-
not limited to compiled object code, generated documentation,
|
| 33 |
-
and conversions to other media types.
|
| 34 |
-
|
| 35 |
-
"Work" shall mean the work of authorship, whether in Source or
|
| 36 |
-
Object form, made available under the License, as indicated by a
|
| 37 |
-
copyright notice that is included in or attached to the work
|
| 38 |
-
(an example is provided in the Appendix below).
|
| 39 |
-
|
| 40 |
-
"Derivative Works" shall mean any work, whether in Source or Object
|
| 41 |
-
form, that is based on (or derived from) the Work and for which the
|
| 42 |
-
editorial revisions, annotations, elaborations, or other modifications
|
| 43 |
-
represent, as a whole, an original work of authorship. For the purposes
|
| 44 |
-
of this License, Derivative Works shall not include works that remain
|
| 45 |
-
separable from, or merely link (or bind by name) to the interfaces of,
|
| 46 |
-
the Work and Derivative Works thereof.
|
| 47 |
-
|
| 48 |
-
"Contribution" shall mean any work of authorship, including
|
| 49 |
-
the original version of the Work and any modifications or additions
|
| 50 |
-
to that Work or Derivative Works thereof, that is intentionally
|
| 51 |
-
submitted to Licensor for inclusion in the Work by the copyright owner
|
| 52 |
-
or by an individual or Legal Entity authorized to submit on behalf of
|
| 53 |
-
the copyright owner. For the purposes of this definition, "submitted"
|
| 54 |
-
means any form of electronic, verbal, or written communication sent
|
| 55 |
-
to the Licensor or its representatives, including but not limited to
|
| 56 |
-
communication on electronic mailing lists, source code control systems,
|
| 57 |
-
and issue tracking systems that are managed by, or on behalf of, the
|
| 58 |
-
Licensor for the purpose of discussing and improving the Work, but
|
| 59 |
-
excluding communication that is conspicuously marked or otherwise
|
| 60 |
-
designated in writing by the copyright owner as "Not a Contribution."
|
| 61 |
-
|
| 62 |
-
"Contributor" shall mean Licensor and any individual or Legal Entity
|
| 63 |
-
on behalf of whom a Contribution has been received by Licensor and
|
| 64 |
-
subsequently incorporated within the Work.
|
| 65 |
-
|
| 66 |
-
2. Grant of Copyright License. Subject to the terms and conditions of
|
| 67 |
-
this License, each Contributor hereby grants to You a perpetual,
|
| 68 |
-
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 69 |
-
copyright license to reproduce, prepare Derivative Works of,
|
| 70 |
-
publicly display, publicly perform, sublicense, and distribute the
|
| 71 |
-
Work and such Derivative Works in Source or Object form.
|
| 72 |
-
|
| 73 |
-
3. Grant of Patent License. Subject to the terms and conditions of
|
| 74 |
-
this License, each Contributor hereby grants to You a perpetual,
|
| 75 |
-
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 76 |
-
(except as stated in this section) patent license to make, have made,
|
| 77 |
-
use, offer to sell, sell, import, and otherwise transfer the Work,
|
| 78 |
-
where such license applies only to those patent claims licensable
|
| 79 |
-
by such Contributor that are necessarily infringed by their
|
| 80 |
-
Contribution(s) alone or by combination of their Contribution(s)
|
| 81 |
-
with the Work to which such Contribution(s) was submitted. If You
|
| 82 |
-
institute patent litigation against any entity (including a
|
| 83 |
-
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
| 84 |
-
or a Contribution incorporated within the Work constitutes direct
|
| 85 |
-
or contributory patent infringement, then any patent licenses
|
| 86 |
-
granted to You under this License for that Work shall terminate
|
| 87 |
-
as of the date such litigation is filed.
|
| 88 |
-
|
| 89 |
-
4. Redistribution. You may reproduce and distribute copies of the
|
| 90 |
-
Work or Derivative Works thereof in any medium, with or without
|
| 91 |
-
modifications, and in Source or Object form, provided that You
|
| 92 |
-
meet the following conditions:
|
| 93 |
-
|
| 94 |
-
(a) You must give any other recipients of the Work or
|
| 95 |
-
Derivative Works a copy of this License; and
|
| 96 |
-
|
| 97 |
-
(b) You must cause any modified files to carry prominent notices
|
| 98 |
-
stating that You changed the files; and
|
| 99 |
-
|
| 100 |
-
(c) You must retain, in the Source form of any Derivative Works
|
| 101 |
-
that You distribute, all copyright, patent, trademark, and
|
| 102 |
-
attribution notices from the Source form of the Work,
|
| 103 |
-
excluding those notices that do not pertain to any part of
|
| 104 |
-
the Derivative Works; and
|
| 105 |
-
|
| 106 |
-
(d) If the Work includes a "NOTICE" text file as part of its
|
| 107 |
-
distribution, then any Derivative Works that You distribute must
|
| 108 |
-
include a readable copy of the attribution notices contained
|
| 109 |
-
within such NOTICE file, excluding those notices that do not
|
| 110 |
-
pertain to any part of the Derivative Works, in at least one
|
| 111 |
-
of the following places: within a NOTICE text file distributed
|
| 112 |
-
as part of the Derivative Works; within the Source form or
|
| 113 |
-
documentation, if provided along with the Derivative Works; or,
|
| 114 |
-
within a display generated by the Derivative Works, if and
|
| 115 |
-
wherever such third-party notices normally appear. The contents
|
| 116 |
-
of the NOTICE file are for informational purposes only and
|
| 117 |
-
do not modify the License. You may add Your own attribution
|
| 118 |
-
notices within Derivative Works that You distribute, alongside
|
| 119 |
-
or as an addendum to the NOTICE text from the Work, provided
|
| 120 |
-
that such additional attribution notices cannot be construed
|
| 121 |
-
as modifying the License.
|
| 122 |
-
|
| 123 |
-
You may add Your own copyright statement to Your modifications and
|
| 124 |
-
may provide additional or different license terms and conditions
|
| 125 |
-
for use, reproduction, or distribution of Your modifications, or
|
| 126 |
-
for any such Derivative Works as a whole, provided Your use,
|
| 127 |
-
reproduction, and distribution of the Work otherwise complies with
|
| 128 |
-
the conditions stated in this License.
|
| 129 |
-
|
| 130 |
-
5. Submission of Contributions. Unless You explicitly state otherwise,
|
| 131 |
-
any Contribution intentionally submitted for inclusion in the Work
|
| 132 |
-
by You to the Licensor shall be under the terms and conditions of
|
| 133 |
-
this License, without any additional terms or conditions.
|
| 134 |
-
Notwithstanding the above, nothing herein shall supersede or modify
|
| 135 |
-
the terms of any separate license agreement you may have executed
|
| 136 |
-
with Licensor regarding such Contributions.
|
| 137 |
-
|
| 138 |
-
6. Trademarks. This License does not grant permission to use the trade
|
| 139 |
-
names, trademarks, service marks, or product names of the Licensor,
|
| 140 |
-
except as required for reasonable and customary use in describing the
|
| 141 |
-
origin of the Work and reproducing the content of the NOTICE file.
|
| 142 |
-
|
| 143 |
-
7. Disclaimer of Warranty. Unless required by applicable law or
|
| 144 |
-
agreed to in writing, Licensor provides the Work (and each
|
| 145 |
-
Contributor provides its Contributions) on an "AS IS" BASIS,
|
| 146 |
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
| 147 |
-
implied, including, without limitation, any warranties or conditions
|
| 148 |
-
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
| 149 |
-
PARTICULAR PURPOSE. You are solely responsible for determining the
|
| 150 |
-
appropriateness of using or redistributing the Work and assume any
|
| 151 |
-
risks associated with Your exercise of permissions under this License.
|
| 152 |
-
|
| 153 |
-
8. Limitation of Liability. In no event and under no legal theory,
|
| 154 |
-
whether in tort (including negligence), contract, or otherwise,
|
| 155 |
-
unless required by applicable law (such as deliberate and grossly
|
| 156 |
-
negligent acts) or agreed to in writing, shall any Contributor be
|
| 157 |
-
liable to You for damages, including any direct, indirect, special,
|
| 158 |
-
incidental, or consequential damages of any character arising as a
|
| 159 |
-
result of this License or out of the use or inability to use the
|
| 160 |
-
Work (including but not limited to damages for loss of goodwill,
|
| 161 |
-
work stoppage, computer failure or malfunction, or any and all
|
| 162 |
-
other commercial damages or losses), even if such Contributor
|
| 163 |
-
has been advised of the possibility of such damages.
|
| 164 |
-
|
| 165 |
-
9. Accepting Warranty or Additional Liability. While redistributing
|
| 166 |
-
the Work or Derivative Works thereof, You may choose to offer,
|
| 167 |
-
and charge a fee for, acceptance of support, warranty, indemnity,
|
| 168 |
-
or other liability obligations and/or rights consistent with this
|
| 169 |
-
License. However, in accepting such obligations, You may act only
|
| 170 |
-
on Your own behalf and on Your sole responsibility, not on behalf
|
| 171 |
-
of any other Contributor, and only if You agree to indemnify,
|
| 172 |
-
defend, and hold each Contributor harmless for any liability
|
| 173 |
-
incurred by, or claims asserted against, such Contributor by reason
|
| 174 |
-
of your accepting any such warranty or additional liability.
|
| 175 |
-
|
| 176 |
-
END OF TERMS AND CONDITIONS
|
| 177 |
-
|
| 178 |
-
APPENDIX: How to apply the Apache License to your work.
|
| 179 |
-
|
| 180 |
-
To apply the Apache License to your work, attach the following
|
| 181 |
-
boilerplate notice, with the fields enclosed by brackets "[]"
|
| 182 |
-
replaced with your own identifying information. (Don't include
|
| 183 |
-
the brackets!) The text should be enclosed in the appropriate
|
| 184 |
-
comment syntax for the file format. We also recommend that a
|
| 185 |
-
file or class name and description of purpose be included on the
|
| 186 |
-
same "printed page" as the copyright notice for easier
|
| 187 |
-
identification within third-party archives.
|
| 188 |
-
|
| 189 |
-
Copyright [yyyy] [name of copyright owner]
|
| 190 |
-
|
| 191 |
-
Licensed under the Apache License, Version 2.0 (the "License");
|
| 192 |
-
you may not use this file except in compliance with the License.
|
| 193 |
-
You may obtain a copy of the License at
|
| 194 |
-
|
| 195 |
-
http://www.apache.org/licenses/LICENSE-2.0
|
| 196 |
-
|
| 197 |
-
Unless required by applicable law or agreed to in writing, software
|
| 198 |
-
distributed under the License is distributed on an "AS IS" BASIS,
|
| 199 |
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 200 |
-
See the License for the specific language governing permissions and
|
| 201 |
-
limitations under the License.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
backgammon-1.0.0/PKG-INFO
DELETED
|
@@ -1,79 +0,0 @@
|
|
| 1 |
-
Metadata-Version: 2.1
|
| 2 |
-
Name: backgammon
|
| 3 |
-
Version: 1.0.0
|
| 4 |
-
Summary: Backgammon engine for the Backgammon Network.
|
| 5 |
-
Home-page: https://github.com/softwerks/backgammon
|
| 6 |
-
Author: Softwerks
|
| 7 |
-
Author-email: info@softwerks.com
|
| 8 |
-
License: UNKNOWN
|
| 9 |
-
Platform: UNKNOWN
|
| 10 |
-
Classifier: Programming Language :: Python :: 3
|
| 11 |
-
Classifier: License :: OSI Approved :: Apache Software License
|
| 12 |
-
Classifier: Operating System :: OS Independent
|
| 13 |
-
Requires-Python: >=3.7
|
| 14 |
-
Description-Content-Type: text/x-rst
|
| 15 |
-
License-File: LICENSE
|
| 16 |
-
|
| 17 |
-
backgammon
|
| 18 |
-
==========
|
| 19 |
-
|
| 20 |
-
Backgammon engine for the `Backgammon Network <https://www.bkgmn.net>`_.
|
| 21 |
-
|
| 22 |
-
Installation
|
| 23 |
-
------------
|
| 24 |
-
|
| 25 |
-
.. code-block:: bash
|
| 26 |
-
|
| 27 |
-
$ pip install backgammon
|
| 28 |
-
|
| 29 |
-
Getting Started
|
| 30 |
-
---------------
|
| 31 |
-
|
| 32 |
-
.. code-block:: pycon
|
| 33 |
-
|
| 34 |
-
>>> import backgammon
|
| 35 |
-
>>> b = backgammon.Backgammon("4OvgATDgc+QBUA", "cInpAAAAAAAA")
|
| 36 |
-
>>> print(b)
|
| 37 |
-
Position ID: 4OvgATDgc+QBUA
|
| 38 |
-
Match ID : cInpAAAAAAAA
|
| 39 |
-
+13-14-15-16-17-18------19-20-21-22-23-24-+
|
| 40 |
-
| X O O | | O X |
|
| 41 |
-
| X O | | O |
|
| 42 |
-
| X O | | O |
|
| 43 |
-
| X | | O |
|
| 44 |
-
| | | O |
|
| 45 |
-
v| |BAR| |
|
| 46 |
-
| | | X |
|
| 47 |
-
| O | | X |
|
| 48 |
-
| O X | | X |
|
| 49 |
-
| O X | | X O |
|
| 50 |
-
| O X X | X | X O |
|
| 51 |
-
+12-11-10--9--8--7-------6--5--4--3--2--1-+
|
| 52 |
-
>>> for play in b.generate_plays():
|
| 53 |
-
... print(play.moves)
|
| 54 |
-
... print(play.position)
|
| 55 |
-
...
|
| 56 |
-
(Move(pips=3, source=None, destination=22), Move(pips=2, source=13, destination=11))
|
| 57 |
-
Position(board_points=(-2, 0, 0, 0, 0, 5, 0, 3, 0, 0, 2, -4, 3, 0, 0, 0, -3, -1, -5, 0, 0, 1, 0, 1), player_bar=0, player_off=1, opponent_bar=0, opponent_off=0)
|
| 58 |
-
(Move(pips=2, source=None, destination=23), Move(pips=3, source=24, destination=21))
|
| 59 |
-
Position(board_points=(-2, 0, 0, 0, 0, 5, 0, 3, 0, 0, 1, -4, 4, 0, 0, 0, -3, -1, -5, 0, 1, 0, 1, 0), player_bar=0, player_off=1, opponent_bar=0, opponent_off=0)
|
| 60 |
-
(Move(pips=3, source=None, destination=22), Move(pips=2, source=11, destination=9))
|
| 61 |
-
Position(board_points=(-2, 0, 0, 0, 0, 5, 0, 3, 1, 0, 0, -4, 4, 0, 0, 0, -3, -1, -5, 0, 0, 1, 0, 1), player_bar=0, player_off=1, opponent_bar=0, opponent_off=0)
|
| 62 |
-
(Move(pips=3, source=None, destination=22), Move(pips=2, source=22, destination=20))
|
| 63 |
-
Position(board_points=(-2, 0, 0, 0, 0, 5, 0, 3, 0, 0, 1, -4, 4, 0, 0, 0, -3, -1, -5, 1, 0, 0, 0, 1), player_bar=0, player_off=1, opponent_bar=0, opponent_off=0)
|
| 64 |
-
(Move(pips=2, source=None, destination=23), Move(pips=3, source=13, destination=10))
|
| 65 |
-
Position(board_points=(-2, 0, 0, 0, 0, 5, 0, 3, 0, 1, 1, -4, 3, 0, 0, 0, -3, -1, -5, 0, 0, 0, 1, 1), player_bar=0, player_off=1, opponent_bar=0, opponent_off=0)
|
| 66 |
-
(Move(pips=2, source=None, destination=23), Move(pips=3, source=8, destination=5))
|
| 67 |
-
Position(board_points=(-2, 0, 0, 0, 1, 5, 0, 2, 0, 0, 1, -4, 4, 0, 0, 0, -3, -1, -5, 0, 0, 0, 1, 1), player_bar=0, player_off=1, opponent_bar=0, opponent_off=0)
|
| 68 |
-
(Move(pips=3, source=None, destination=22), Move(pips=2, source=24, destination=22))
|
| 69 |
-
Position(board_points=(-2, 0, 0, 0, 0, 5, 0, 3, 0, 0, 1, -4, 4, 0, 0, 0, -3, -1, -5, 0, 0, 2, 0, 0), player_bar=0, player_off=1, opponent_bar=0, opponent_off=0)
|
| 70 |
-
(Move(pips=3, source=None, destination=22), Move(pips=2, source=6, destination=4))
|
| 71 |
-
Position(board_points=(-2, 0, 0, 1, 0, 4, 0, 3, 0, 0, 1, -4, 4, 0, 0, 0, -3, -1, -5, 0, 0, 1, 0, 1), player_bar=0, player_off=1, opponent_bar=0, opponent_off=0)
|
| 72 |
-
(Move(pips=2, source=None, destination=23), Move(pips=3, source=6, destination=3))
|
| 73 |
-
Position(board_points=(-2, 0, 1, 0, 0, 4, 0, 3, 0, 0, 1, -4, 4, 0, 0, 0, -3, -1, -5, 0, 0, 0, 1, 1), player_bar=0, player_off=1, opponent_bar=0, opponent_off=0)
|
| 74 |
-
(Move(pips=2, source=None, destination=23), Move(pips=3, source=11, destination=8))
|
| 75 |
-
Position(board_points=(-2, 0, 0, 0, 0, 5, 0, 4, 0, 0, 0, -4, 4, 0, 0, 0, -3, -1, -5, 0, 0, 0, 1, 1), player_bar=0, player_off=1, opponent_bar=0, opponent_off=0)
|
| 76 |
-
(Move(pips=3, source=None, destination=22), Move(pips=2, source=8, destination=6))
|
| 77 |
-
Position(board_points=(-2, 0, 0, 0, 0, 6, 0, 2, 0, 0, 1, -4, 4, 0, 0, 0, -3, -1, -5, 0, 0, 1, 0, 1), player_bar=0, player_off=1, opponent_bar=0, opponent_off=0)
|
| 78 |
-
|
| 79 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
backgammon-1.0.0/README.rst
DELETED
|
@@ -1,61 +0,0 @@
|
|
| 1 |
-
backgammon
|
| 2 |
-
==========
|
| 3 |
-
|
| 4 |
-
Backgammon engine for the `Backgammon Network <https://www.bkgmn.net>`_.
|
| 5 |
-
|
| 6 |
-
Installation
|
| 7 |
-
------------
|
| 8 |
-
|
| 9 |
-
.. code-block:: bash
|
| 10 |
-
|
| 11 |
-
$ pip install backgammon
|
| 12 |
-
|
| 13 |
-
Getting Started
|
| 14 |
-
---------------
|
| 15 |
-
|
| 16 |
-
.. code-block:: pycon
|
| 17 |
-
|
| 18 |
-
>>> import backgammon
|
| 19 |
-
>>> b = backgammon.Backgammon("4OvgATDgc+QBUA", "cInpAAAAAAAA")
|
| 20 |
-
>>> print(b)
|
| 21 |
-
Position ID: 4OvgATDgc+QBUA
|
| 22 |
-
Match ID : cInpAAAAAAAA
|
| 23 |
-
+13-14-15-16-17-18------19-20-21-22-23-24-+
|
| 24 |
-
| X O O | | O X |
|
| 25 |
-
| X O | | O |
|
| 26 |
-
| X O | | O |
|
| 27 |
-
| X | | O |
|
| 28 |
-
| | | O |
|
| 29 |
-
v| |BAR| |
|
| 30 |
-
| | | X |
|
| 31 |
-
| O | | X |
|
| 32 |
-
| O X | | X |
|
| 33 |
-
| O X | | X O |
|
| 34 |
-
| O X X | X | X O |
|
| 35 |
-
+12-11-10--9--8--7-------6--5--4--3--2--1-+
|
| 36 |
-
>>> for play in b.generate_plays():
|
| 37 |
-
... print(play.moves)
|
| 38 |
-
... print(play.position)
|
| 39 |
-
...
|
| 40 |
-
(Move(pips=3, source=None, destination=22), Move(pips=2, source=13, destination=11))
|
| 41 |
-
Position(board_points=(-2, 0, 0, 0, 0, 5, 0, 3, 0, 0, 2, -4, 3, 0, 0, 0, -3, -1, -5, 0, 0, 1, 0, 1), player_bar=0, player_off=1, opponent_bar=0, opponent_off=0)
|
| 42 |
-
(Move(pips=2, source=None, destination=23), Move(pips=3, source=24, destination=21))
|
| 43 |
-
Position(board_points=(-2, 0, 0, 0, 0, 5, 0, 3, 0, 0, 1, -4, 4, 0, 0, 0, -3, -1, -5, 0, 1, 0, 1, 0), player_bar=0, player_off=1, opponent_bar=0, opponent_off=0)
|
| 44 |
-
(Move(pips=3, source=None, destination=22), Move(pips=2, source=11, destination=9))
|
| 45 |
-
Position(board_points=(-2, 0, 0, 0, 0, 5, 0, 3, 1, 0, 0, -4, 4, 0, 0, 0, -3, -1, -5, 0, 0, 1, 0, 1), player_bar=0, player_off=1, opponent_bar=0, opponent_off=0)
|
| 46 |
-
(Move(pips=3, source=None, destination=22), Move(pips=2, source=22, destination=20))
|
| 47 |
-
Position(board_points=(-2, 0, 0, 0, 0, 5, 0, 3, 0, 0, 1, -4, 4, 0, 0, 0, -3, -1, -5, 1, 0, 0, 0, 1), player_bar=0, player_off=1, opponent_bar=0, opponent_off=0)
|
| 48 |
-
(Move(pips=2, source=None, destination=23), Move(pips=3, source=13, destination=10))
|
| 49 |
-
Position(board_points=(-2, 0, 0, 0, 0, 5, 0, 3, 0, 1, 1, -4, 3, 0, 0, 0, -3, -1, -5, 0, 0, 0, 1, 1), player_bar=0, player_off=1, opponent_bar=0, opponent_off=0)
|
| 50 |
-
(Move(pips=2, source=None, destination=23), Move(pips=3, source=8, destination=5))
|
| 51 |
-
Position(board_points=(-2, 0, 0, 0, 1, 5, 0, 2, 0, 0, 1, -4, 4, 0, 0, 0, -3, -1, -5, 0, 0, 0, 1, 1), player_bar=0, player_off=1, opponent_bar=0, opponent_off=0)
|
| 52 |
-
(Move(pips=3, source=None, destination=22), Move(pips=2, source=24, destination=22))
|
| 53 |
-
Position(board_points=(-2, 0, 0, 0, 0, 5, 0, 3, 0, 0, 1, -4, 4, 0, 0, 0, -3, -1, -5, 0, 0, 2, 0, 0), player_bar=0, player_off=1, opponent_bar=0, opponent_off=0)
|
| 54 |
-
(Move(pips=3, source=None, destination=22), Move(pips=2, source=6, destination=4))
|
| 55 |
-
Position(board_points=(-2, 0, 0, 1, 0, 4, 0, 3, 0, 0, 1, -4, 4, 0, 0, 0, -3, -1, -5, 0, 0, 1, 0, 1), player_bar=0, player_off=1, opponent_bar=0, opponent_off=0)
|
| 56 |
-
(Move(pips=2, source=None, destination=23), Move(pips=3, source=6, destination=3))
|
| 57 |
-
Position(board_points=(-2, 0, 1, 0, 0, 4, 0, 3, 0, 0, 1, -4, 4, 0, 0, 0, -3, -1, -5, 0, 0, 0, 1, 1), player_bar=0, player_off=1, opponent_bar=0, opponent_off=0)
|
| 58 |
-
(Move(pips=2, source=None, destination=23), Move(pips=3, source=11, destination=8))
|
| 59 |
-
Position(board_points=(-2, 0, 0, 0, 0, 5, 0, 4, 0, 0, 0, -4, 4, 0, 0, 0, -3, -1, -5, 0, 0, 0, 1, 1), player_bar=0, player_off=1, opponent_bar=0, opponent_off=0)
|
| 60 |
-
(Move(pips=3, source=None, destination=22), Move(pips=2, source=8, destination=6))
|
| 61 |
-
Position(board_points=(-2, 0, 0, 0, 0, 6, 0, 2, 0, 0, 1, -4, 4, 0, 0, 0, -3, -1, -5, 0, 0, 1, 0, 1), player_bar=0, player_off=1, opponent_bar=0, opponent_off=0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
backgammon-1.0.0/backgammon.egg-info/PKG-INFO
DELETED
|
@@ -1,79 +0,0 @@
|
|
| 1 |
-
Metadata-Version: 2.1
|
| 2 |
-
Name: backgammon
|
| 3 |
-
Version: 1.0.0
|
| 4 |
-
Summary: Backgammon engine for the Backgammon Network.
|
| 5 |
-
Home-page: https://github.com/softwerks/backgammon
|
| 6 |
-
Author: Softwerks
|
| 7 |
-
Author-email: info@softwerks.com
|
| 8 |
-
License: UNKNOWN
|
| 9 |
-
Platform: UNKNOWN
|
| 10 |
-
Classifier: Programming Language :: Python :: 3
|
| 11 |
-
Classifier: License :: OSI Approved :: Apache Software License
|
| 12 |
-
Classifier: Operating System :: OS Independent
|
| 13 |
-
Requires-Python: >=3.7
|
| 14 |
-
Description-Content-Type: text/x-rst
|
| 15 |
-
License-File: LICENSE
|
| 16 |
-
|
| 17 |
-
backgammon
|
| 18 |
-
==========
|
| 19 |
-
|
| 20 |
-
Backgammon engine for the `Backgammon Network <https://www.bkgmn.net>`_.
|
| 21 |
-
|
| 22 |
-
Installation
|
| 23 |
-
------------
|
| 24 |
-
|
| 25 |
-
.. code-block:: bash
|
| 26 |
-
|
| 27 |
-
$ pip install backgammon
|
| 28 |
-
|
| 29 |
-
Getting Started
|
| 30 |
-
---------------
|
| 31 |
-
|
| 32 |
-
.. code-block:: pycon
|
| 33 |
-
|
| 34 |
-
>>> import backgammon
|
| 35 |
-
>>> b = backgammon.Backgammon("4OvgATDgc+QBUA", "cInpAAAAAAAA")
|
| 36 |
-
>>> print(b)
|
| 37 |
-
Position ID: 4OvgATDgc+QBUA
|
| 38 |
-
Match ID : cInpAAAAAAAA
|
| 39 |
-
+13-14-15-16-17-18------19-20-21-22-23-24-+
|
| 40 |
-
| X O O | | O X |
|
| 41 |
-
| X O | | O |
|
| 42 |
-
| X O | | O |
|
| 43 |
-
| X | | O |
|
| 44 |
-
| | | O |
|
| 45 |
-
v| |BAR| |
|
| 46 |
-
| | | X |
|
| 47 |
-
| O | | X |
|
| 48 |
-
| O X | | X |
|
| 49 |
-
| O X | | X O |
|
| 50 |
-
| O X X | X | X O |
|
| 51 |
-
+12-11-10--9--8--7-------6--5--4--3--2--1-+
|
| 52 |
-
>>> for play in b.generate_plays():
|
| 53 |
-
... print(play.moves)
|
| 54 |
-
... print(play.position)
|
| 55 |
-
...
|
| 56 |
-
(Move(pips=3, source=None, destination=22), Move(pips=2, source=13, destination=11))
|
| 57 |
-
Position(board_points=(-2, 0, 0, 0, 0, 5, 0, 3, 0, 0, 2, -4, 3, 0, 0, 0, -3, -1, -5, 0, 0, 1, 0, 1), player_bar=0, player_off=1, opponent_bar=0, opponent_off=0)
|
| 58 |
-
(Move(pips=2, source=None, destination=23), Move(pips=3, source=24, destination=21))
|
| 59 |
-
Position(board_points=(-2, 0, 0, 0, 0, 5, 0, 3, 0, 0, 1, -4, 4, 0, 0, 0, -3, -1, -5, 0, 1, 0, 1, 0), player_bar=0, player_off=1, opponent_bar=0, opponent_off=0)
|
| 60 |
-
(Move(pips=3, source=None, destination=22), Move(pips=2, source=11, destination=9))
|
| 61 |
-
Position(board_points=(-2, 0, 0, 0, 0, 5, 0, 3, 1, 0, 0, -4, 4, 0, 0, 0, -3, -1, -5, 0, 0, 1, 0, 1), player_bar=0, player_off=1, opponent_bar=0, opponent_off=0)
|
| 62 |
-
(Move(pips=3, source=None, destination=22), Move(pips=2, source=22, destination=20))
|
| 63 |
-
Position(board_points=(-2, 0, 0, 0, 0, 5, 0, 3, 0, 0, 1, -4, 4, 0, 0, 0, -3, -1, -5, 1, 0, 0, 0, 1), player_bar=0, player_off=1, opponent_bar=0, opponent_off=0)
|
| 64 |
-
(Move(pips=2, source=None, destination=23), Move(pips=3, source=13, destination=10))
|
| 65 |
-
Position(board_points=(-2, 0, 0, 0, 0, 5, 0, 3, 0, 1, 1, -4, 3, 0, 0, 0, -3, -1, -5, 0, 0, 0, 1, 1), player_bar=0, player_off=1, opponent_bar=0, opponent_off=0)
|
| 66 |
-
(Move(pips=2, source=None, destination=23), Move(pips=3, source=8, destination=5))
|
| 67 |
-
Position(board_points=(-2, 0, 0, 0, 1, 5, 0, 2, 0, 0, 1, -4, 4, 0, 0, 0, -3, -1, -5, 0, 0, 0, 1, 1), player_bar=0, player_off=1, opponent_bar=0, opponent_off=0)
|
| 68 |
-
(Move(pips=3, source=None, destination=22), Move(pips=2, source=24, destination=22))
|
| 69 |
-
Position(board_points=(-2, 0, 0, 0, 0, 5, 0, 3, 0, 0, 1, -4, 4, 0, 0, 0, -3, -1, -5, 0, 0, 2, 0, 0), player_bar=0, player_off=1, opponent_bar=0, opponent_off=0)
|
| 70 |
-
(Move(pips=3, source=None, destination=22), Move(pips=2, source=6, destination=4))
|
| 71 |
-
Position(board_points=(-2, 0, 0, 1, 0, 4, 0, 3, 0, 0, 1, -4, 4, 0, 0, 0, -3, -1, -5, 0, 0, 1, 0, 1), player_bar=0, player_off=1, opponent_bar=0, opponent_off=0)
|
| 72 |
-
(Move(pips=2, source=None, destination=23), Move(pips=3, source=6, destination=3))
|
| 73 |
-
Position(board_points=(-2, 0, 1, 0, 0, 4, 0, 3, 0, 0, 1, -4, 4, 0, 0, 0, -3, -1, -5, 0, 0, 0, 1, 1), player_bar=0, player_off=1, opponent_bar=0, opponent_off=0)
|
| 74 |
-
(Move(pips=2, source=None, destination=23), Move(pips=3, source=11, destination=8))
|
| 75 |
-
Position(board_points=(-2, 0, 0, 0, 0, 5, 0, 4, 0, 0, 0, -4, 4, 0, 0, 0, -3, -1, -5, 0, 0, 0, 1, 1), player_bar=0, player_off=1, opponent_bar=0, opponent_off=0)
|
| 76 |
-
(Move(pips=3, source=None, destination=22), Move(pips=2, source=8, destination=6))
|
| 77 |
-
Position(board_points=(-2, 0, 0, 0, 0, 6, 0, 2, 0, 0, 1, -4, 4, 0, 0, 0, -3, -1, -5, 0, 0, 1, 0, 1), player_bar=0, player_off=1, opponent_bar=0, opponent_off=0)
|
| 78 |
-
|
| 79 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
backgammon-1.0.0/backgammon.egg-info/SOURCES.txt
DELETED
|
@@ -1,16 +0,0 @@
|
|
| 1 |
-
LICENSE
|
| 2 |
-
README.rst
|
| 3 |
-
setup.py
|
| 4 |
-
backgammon/__init__.py
|
| 5 |
-
backgammon/__main__.py
|
| 6 |
-
backgammon/backgammon.py
|
| 7 |
-
backgammon/match.py
|
| 8 |
-
backgammon/position.py
|
| 9 |
-
backgammon.egg-info/PKG-INFO
|
| 10 |
-
backgammon.egg-info/SOURCES.txt
|
| 11 |
-
backgammon.egg-info/dependency_links.txt
|
| 12 |
-
backgammon.egg-info/top_level.txt
|
| 13 |
-
tests/__init__.py
|
| 14 |
-
tests/test_backgammon.py
|
| 15 |
-
tests/test_match.py
|
| 16 |
-
tests/test_position.py
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
backgammon-1.0.0/backgammon.egg-info/dependency_links.txt
DELETED
|
@@ -1 +0,0 @@
|
|
| 1 |
-
|
|
|
|
|
|
backgammon-1.0.0/backgammon.egg-info/top_level.txt
DELETED
|
@@ -1,2 +0,0 @@
|
|
| 1 |
-
backgammon
|
| 2 |
-
tests
|
|
|
|
|
|
|
|
|
backgammon-1.0.0/backgammon/__init__.py
DELETED
|
@@ -1,15 +0,0 @@
|
|
| 1 |
-
# Copyright 2020 Softwerks LLC
|
| 2 |
-
#
|
| 3 |
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
-
# you may not use this file except in compliance with the License.
|
| 5 |
-
# You may obtain a copy of the License at
|
| 6 |
-
#
|
| 7 |
-
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
-
#
|
| 9 |
-
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
-
# See the License for the specific language governing permissions and
|
| 13 |
-
# limitations under the License.
|
| 14 |
-
|
| 15 |
-
from backgammon.backgammon import Backgammon
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
backgammon-1.0.0/backgammon/__main__.py
DELETED
|
@@ -1,109 +0,0 @@
|
|
| 1 |
-
# Copyright 2020 Softwerks LLC
|
| 2 |
-
#
|
| 3 |
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
-
# you may not use this file except in compliance with the License.
|
| 5 |
-
# You may obtain a copy of the License at
|
| 6 |
-
#
|
| 7 |
-
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
-
#
|
| 9 |
-
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
-
# See the License for the specific language governing permissions and
|
| 13 |
-
# limitations under the License.
|
| 14 |
-
|
| 15 |
-
import cmd
|
| 16 |
-
from typing import cast, List, Optional, Tuple
|
| 17 |
-
|
| 18 |
-
try:
|
| 19 |
-
import readline
|
| 20 |
-
except ImportError:
|
| 21 |
-
pass
|
| 22 |
-
|
| 23 |
-
from backgammon.backgammon import Backgammon, BackgammonError
|
| 24 |
-
from backgammon.match import GameState, Player
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
class BackgammonShell(cmd.Cmd):
|
| 28 |
-
intro: str = "Type 'help' or '?' to list commands."
|
| 29 |
-
prompt: str = "backgammon> "
|
| 30 |
-
game: Backgammon
|
| 31 |
-
|
| 32 |
-
def do_new(self, arg: str) -> None:
|
| 33 |
-
"""Start a new game."""
|
| 34 |
-
die_1: int
|
| 35 |
-
die_2: int
|
| 36 |
-
|
| 37 |
-
if (
|
| 38 |
-
hasattr(self, "game")
|
| 39 |
-
and self.game.match.game_state is not GameState.NOT_STARTED
|
| 40 |
-
):
|
| 41 |
-
print("Game already started.")
|
| 42 |
-
return
|
| 43 |
-
|
| 44 |
-
self.game = Backgammon()
|
| 45 |
-
die_1, die_2 = self.game.first_roll()
|
| 46 |
-
self.game.match.game_state = GameState.PLAYING
|
| 47 |
-
|
| 48 |
-
print(f"Rolled {die_1} {die_2}")
|
| 49 |
-
print(self.game)
|
| 50 |
-
|
| 51 |
-
def do_move(self, arg: str) -> None:
|
| 52 |
-
"""Make a backgammon move: move <from> <to> ..."""
|
| 53 |
-
Moves = Tuple[Tuple[Optional[int], Optional[int]], ...]
|
| 54 |
-
|
| 55 |
-
def parse_arg(arg: str) -> Moves:
|
| 56 |
-
arg_ints: List[Optional[int]] = list(
|
| 57 |
-
map(lambda n: int(n) if n.isdigit() else None, arg.split())
|
| 58 |
-
)
|
| 59 |
-
|
| 60 |
-
if len(arg_ints) % 2 == 1:
|
| 61 |
-
raise ValueError("Incomplete move.")
|
| 62 |
-
if len(arg_ints) > 8:
|
| 63 |
-
raise ValueError("Too many moves.")
|
| 64 |
-
|
| 65 |
-
return cast(
|
| 66 |
-
Moves,
|
| 67 |
-
tuple(tuple(arg_ints[i : i + 2]) for i in range(0, len(arg_ints), 2)),
|
| 68 |
-
)
|
| 69 |
-
|
| 70 |
-
if (
|
| 71 |
-
not hasattr(self, "game")
|
| 72 |
-
or self.game.match.game_state is not GameState.PLAYING
|
| 73 |
-
):
|
| 74 |
-
print("Game not in progress.")
|
| 75 |
-
return
|
| 76 |
-
|
| 77 |
-
try:
|
| 78 |
-
moves: Moves = parse_arg(arg)
|
| 79 |
-
except ValueError as e:
|
| 80 |
-
print(e)
|
| 81 |
-
return
|
| 82 |
-
|
| 83 |
-
try:
|
| 84 |
-
self.game.play(moves)
|
| 85 |
-
except BackgammonError:
|
| 86 |
-
print("Illegal move.")
|
| 87 |
-
return
|
| 88 |
-
|
| 89 |
-
if self.game.match.game_state is GameState.GAME_OVER:
|
| 90 |
-
print(f"P{1 if self.game.match.player is Player.ZERO else 2} wins")
|
| 91 |
-
print(self.game)
|
| 92 |
-
del self.game
|
| 93 |
-
else:
|
| 94 |
-
self.game.end_turn()
|
| 95 |
-
|
| 96 |
-
die_1, die_2 = self.game.roll()
|
| 97 |
-
print(f"Rolled {die_1} {die_2}")
|
| 98 |
-
print(self.game)
|
| 99 |
-
|
| 100 |
-
def do_quit(self, arg: str) -> bool:
|
| 101 |
-
"""Exit the program."""
|
| 102 |
-
return True
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
if __name__ == "__main__":
|
| 106 |
-
try:
|
| 107 |
-
BackgammonShell().cmdloop()
|
| 108 |
-
except KeyboardInterrupt:
|
| 109 |
-
print("^C")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
backgammon-1.0.0/backgammon/backgammon.py
DELETED
|
@@ -1,383 +0,0 @@
|
|
| 1 |
-
# Copyright 2020 Softwerks LLC
|
| 2 |
-
#
|
| 3 |
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
-
# you may not use this file except in compliance with the License.
|
| 5 |
-
# You may obtain a copy of the License at
|
| 6 |
-
#
|
| 7 |
-
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
-
#
|
| 9 |
-
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
-
# See the License for the specific language governing permissions and
|
| 13 |
-
# limitations under the License.
|
| 14 |
-
|
| 15 |
-
import enum
|
| 16 |
-
import itertools
|
| 17 |
-
import json
|
| 18 |
-
import operator
|
| 19 |
-
import random
|
| 20 |
-
from typing import Callable, List, NamedTuple, Optional, Tuple, Set
|
| 21 |
-
|
| 22 |
-
import backgammon.match
|
| 23 |
-
from backgammon.match import Player, GameState, Resign
|
| 24 |
-
import backgammon.position
|
| 25 |
-
|
| 26 |
-
MatchType = backgammon.match.Match
|
| 27 |
-
PositionType = backgammon.position.Position
|
| 28 |
-
|
| 29 |
-
STARTING_POSITION_ID = "4HPwATDgc/ABMA"
|
| 30 |
-
STARTING_MATCH_ID = "cAgAAAAAAAAA"
|
| 31 |
-
|
| 32 |
-
CHECKERS = 15
|
| 33 |
-
POINTS = 24
|
| 34 |
-
POINTS_PER_QUADRANT = int(POINTS / 4)
|
| 35 |
-
|
| 36 |
-
ASCII_BOARD_HEIGHT = 11
|
| 37 |
-
ASCII_MAX_CHECKERS = 5
|
| 38 |
-
ASCII_13_24 = "+13-14-15-16-17-18------19-20-21-22-23-24-+"
|
| 39 |
-
ASCII_12_01 = "+12-11-10--9--8--7-------6--5--4--3--2--1-+"
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
class BackgammonError(Exception):
|
| 43 |
-
pass
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
class MoveState(enum.Enum):
|
| 47 |
-
BEAR_OFF = enum.auto()
|
| 48 |
-
ENTER_FROM_BAR = enum.auto()
|
| 49 |
-
DEFAULT = enum.auto()
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
class Move(NamedTuple):
|
| 53 |
-
pips: int
|
| 54 |
-
source: Optional[int]
|
| 55 |
-
destination: Optional[int]
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
class Play(NamedTuple):
|
| 59 |
-
moves: Tuple[Move, ...]
|
| 60 |
-
position: PositionType
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
class Backgammon:
|
| 64 |
-
def __init__(
|
| 65 |
-
self, position_id: str = STARTING_POSITION_ID, match_id: str = STARTING_MATCH_ID
|
| 66 |
-
):
|
| 67 |
-
self.position: PositionType = backgammon.position.decode(position_id)
|
| 68 |
-
self.match: MatchType = backgammon.match.decode(match_id)
|
| 69 |
-
|
| 70 |
-
def generate_plays(self) -> List[Play]:
|
| 71 |
-
"""Generate and return legal plays."""
|
| 72 |
-
|
| 73 |
-
def generate(
|
| 74 |
-
position: PositionType,
|
| 75 |
-
dice: Tuple[int, ...],
|
| 76 |
-
die: int = 0,
|
| 77 |
-
moves: Tuple[Move, ...] = (),
|
| 78 |
-
plays: List[Play] = [],
|
| 79 |
-
) -> List[Play]:
|
| 80 |
-
"""Generate and return all plays."""
|
| 81 |
-
new_position: Optional[PositionType]
|
| 82 |
-
destination: Optional[int]
|
| 83 |
-
point: int
|
| 84 |
-
num_checkers: int
|
| 85 |
-
pips: int
|
| 86 |
-
|
| 87 |
-
if die < len(dice):
|
| 88 |
-
pips = dice[die]
|
| 89 |
-
|
| 90 |
-
if position.player_bar > 0:
|
| 91 |
-
new_position, destination = position.enter(pips)
|
| 92 |
-
if new_position:
|
| 93 |
-
generate(
|
| 94 |
-
new_position,
|
| 95 |
-
dice,
|
| 96 |
-
die + 1,
|
| 97 |
-
moves + (Move(pips, None, destination),),
|
| 98 |
-
plays,
|
| 99 |
-
)
|
| 100 |
-
elif sum(position.player_home()) + position.player_off == CHECKERS:
|
| 101 |
-
for point, num_checkers in enumerate(
|
| 102 |
-
position.board_points[:POINTS_PER_QUADRANT]
|
| 103 |
-
):
|
| 104 |
-
new_position, destination = position.off(point, pips)
|
| 105 |
-
if new_position:
|
| 106 |
-
generate(
|
| 107 |
-
new_position,
|
| 108 |
-
dice,
|
| 109 |
-
die + 1,
|
| 110 |
-
moves + (Move(pips, point, destination),),
|
| 111 |
-
plays,
|
| 112 |
-
)
|
| 113 |
-
else:
|
| 114 |
-
for point, num_checkers in enumerate(position.board_points):
|
| 115 |
-
new_position, destination = position.move(point, pips)
|
| 116 |
-
if new_position:
|
| 117 |
-
generate(
|
| 118 |
-
new_position,
|
| 119 |
-
dice,
|
| 120 |
-
die + 1,
|
| 121 |
-
moves + (Move(pips, point, destination),),
|
| 122 |
-
plays,
|
| 123 |
-
)
|
| 124 |
-
|
| 125 |
-
if len(moves) > 0:
|
| 126 |
-
plays.append(Play(moves, position))
|
| 127 |
-
|
| 128 |
-
return plays
|
| 129 |
-
|
| 130 |
-
doubles: bool = self.match.dice[0] == self.match.dice[1]
|
| 131 |
-
dice: Tuple[int, ...] = self.match.dice * 2 if doubles else self.match.dice
|
| 132 |
-
|
| 133 |
-
plays: List[Play] = generate(self.position, dice)
|
| 134 |
-
if not doubles:
|
| 135 |
-
plays += generate(self.position, dice[::-1])
|
| 136 |
-
|
| 137 |
-
if plays:
|
| 138 |
-
max_moves: int = max(len(p.moves) for p in plays)
|
| 139 |
-
if max_moves == 1:
|
| 140 |
-
max_pips: int = max(dice)
|
| 141 |
-
higher_plays: List[Play] = list(
|
| 142 |
-
filter(lambda p: p.moves[0].pips == max_pips, plays)
|
| 143 |
-
)
|
| 144 |
-
if higher_plays:
|
| 145 |
-
plays = higher_plays
|
| 146 |
-
else:
|
| 147 |
-
plays = list(filter(lambda p: len(p.moves) == max_moves, plays))
|
| 148 |
-
|
| 149 |
-
key_func: Callable = lambda p: hash(p.position)
|
| 150 |
-
plays = sorted(plays, key=key_func)
|
| 151 |
-
plays = list(
|
| 152 |
-
map(
|
| 153 |
-
next,
|
| 154 |
-
map(operator.itemgetter(1), itertools.groupby(plays, key_func)),
|
| 155 |
-
)
|
| 156 |
-
)
|
| 157 |
-
|
| 158 |
-
return plays
|
| 159 |
-
|
| 160 |
-
def start(self, length: int = 3) -> "Backgammon":
|
| 161 |
-
self.match.game_state = GameState.PLAYING
|
| 162 |
-
self.match.length = length
|
| 163 |
-
self.first_roll()
|
| 164 |
-
|
| 165 |
-
return self
|
| 166 |
-
|
| 167 |
-
def roll(self) -> Tuple[int, int]:
|
| 168 |
-
if self.match.dice != (0, 0):
|
| 169 |
-
raise BackgammonError(f"Dice have already been rolled: {self.match.dice}")
|
| 170 |
-
|
| 171 |
-
self.match.dice = (
|
| 172 |
-
random.SystemRandom().randrange(1, 6),
|
| 173 |
-
random.SystemRandom().randrange(1, 6),
|
| 174 |
-
)
|
| 175 |
-
return self.match.dice
|
| 176 |
-
|
| 177 |
-
def first_roll(self) -> Tuple[int, int]:
|
| 178 |
-
while True:
|
| 179 |
-
self.match.dice = (
|
| 180 |
-
random.SystemRandom().randrange(1, 6),
|
| 181 |
-
random.SystemRandom().randrange(1, 6),
|
| 182 |
-
)
|
| 183 |
-
if self.match.dice[0] != self.match.dice[1]:
|
| 184 |
-
break
|
| 185 |
-
if self.match.dice[0] > self.match.dice[1]:
|
| 186 |
-
self.match.player = Player.ZERO
|
| 187 |
-
self.match.turn = Player.ZERO
|
| 188 |
-
else:
|
| 189 |
-
self.match.player = Player.ONE
|
| 190 |
-
self.match.turn = Player.ONE
|
| 191 |
-
return self.match.dice
|
| 192 |
-
|
| 193 |
-
def play(
|
| 194 |
-
self, moves: Tuple[Tuple[Optional[int], Optional[int]], ...]
|
| 195 |
-
) -> "Backgammon":
|
| 196 |
-
"""Excecute a play, a sequence of moves."""
|
| 197 |
-
new_position: PositionType = self.position
|
| 198 |
-
for source, destination in moves:
|
| 199 |
-
new_position = new_position.apply_move(source, destination)
|
| 200 |
-
|
| 201 |
-
legal_plays: List[Play] = self.generate_plays()
|
| 202 |
-
|
| 203 |
-
if new_position in [play.position for play in legal_plays]:
|
| 204 |
-
self.position = new_position
|
| 205 |
-
|
| 206 |
-
if self.position.player_off == CHECKERS:
|
| 207 |
-
multiplier: int = 1
|
| 208 |
-
if self.position.opponent_off == 0:
|
| 209 |
-
if (
|
| 210 |
-
self.position.opponent_bar > 0
|
| 211 |
-
or sum(self.position.board_points[:POINTS_PER_QUADRANT]) != 0
|
| 212 |
-
):
|
| 213 |
-
multiplier = 3
|
| 214 |
-
else:
|
| 215 |
-
multiplier = 2
|
| 216 |
-
self.match.update_score(multiplier)
|
| 217 |
-
if self.match.game_state is GameState.PLAYING:
|
| 218 |
-
self.match.reset_cube()
|
| 219 |
-
self.position = backgammon.position.decode(STARTING_POSITION_ID)
|
| 220 |
-
self.first_roll()
|
| 221 |
-
else:
|
| 222 |
-
self.end_turn()
|
| 223 |
-
|
| 224 |
-
else:
|
| 225 |
-
position_id: str = self.position.encode()
|
| 226 |
-
match_id: str = self.match.encode()
|
| 227 |
-
raise BackgammonError(f"Invalid move: {position_id}:{match_id} {moves}")
|
| 228 |
-
|
| 229 |
-
return self
|
| 230 |
-
|
| 231 |
-
def double(self) -> "Backgammon":
|
| 232 |
-
if self.match.dice != (0, 0):
|
| 233 |
-
raise BackgammonError("Cannot double: dice have been rolled")
|
| 234 |
-
elif (
|
| 235 |
-
self.match.cube_holder is not Player.CENTERED
|
| 236 |
-
and self.match.cube_holder is not self.match.player
|
| 237 |
-
):
|
| 238 |
-
raise BackgammonError("Cannot double: not cube holder")
|
| 239 |
-
elif self.match.double:
|
| 240 |
-
raise BackgammonError("Cannot double: already doubled")
|
| 241 |
-
elif (
|
| 242 |
-
self.match.player_0_score
|
| 243 |
-
if self.match.player is Player.ZERO
|
| 244 |
-
else self.match.player_1_score + self.match.cube_value >= self.match.length
|
| 245 |
-
):
|
| 246 |
-
raise BackgammonError("Cannot double: dead cube")
|
| 247 |
-
elif self.match.crawford:
|
| 248 |
-
raise BackgammonError("Cannot double: crawford game")
|
| 249 |
-
|
| 250 |
-
self.match.double = True
|
| 251 |
-
self.match.swap_turn()
|
| 252 |
-
|
| 253 |
-
return self
|
| 254 |
-
|
| 255 |
-
def accept_double(self) -> "Backgammon":
|
| 256 |
-
if self.match.double:
|
| 257 |
-
self.match.double = False
|
| 258 |
-
self.match.cube_value *= 2
|
| 259 |
-
self.match.cube_holder = (
|
| 260 |
-
Player.ZERO if self.match.turn is Player.ZERO else Player.ONE
|
| 261 |
-
)
|
| 262 |
-
self.match.swap_turn()
|
| 263 |
-
else:
|
| 264 |
-
raise BackgammonError("Cannot accept double: double not offered")
|
| 265 |
-
|
| 266 |
-
return self
|
| 267 |
-
|
| 268 |
-
def reject_double(self) -> "Backgammon":
|
| 269 |
-
if self.match.double:
|
| 270 |
-
self.match.drop_cube()
|
| 271 |
-
if self.match.game_state is GameState.PLAYING:
|
| 272 |
-
self.match.reset_cube()
|
| 273 |
-
self.position = backgammon.position.decode(STARTING_POSITION_ID)
|
| 274 |
-
self.first_roll()
|
| 275 |
-
else:
|
| 276 |
-
raise BackgammonError("Cannot reject double: double not offered")
|
| 277 |
-
|
| 278 |
-
return self
|
| 279 |
-
|
| 280 |
-
def skip(self) -> "Backgammon":
|
| 281 |
-
num_plays: int = len(self.generate_plays())
|
| 282 |
-
if num_plays == 0:
|
| 283 |
-
self.end_turn()
|
| 284 |
-
else:
|
| 285 |
-
raise BackgammonError(f"Cannot skip turn: {num_plays} possible plays")
|
| 286 |
-
|
| 287 |
-
return self
|
| 288 |
-
|
| 289 |
-
def end_turn(self) -> "Backgammon":
|
| 290 |
-
self.position = self.position.swap_players()
|
| 291 |
-
self.match.swap_players()
|
| 292 |
-
self.match.reset_dice()
|
| 293 |
-
|
| 294 |
-
return self
|
| 295 |
-
|
| 296 |
-
def encode(self) -> str:
|
| 297 |
-
return f"{self.position.encode()}:{self.match.encode()}"
|
| 298 |
-
|
| 299 |
-
def __repr__(self):
|
| 300 |
-
position_id: str = self.position.encode()
|
| 301 |
-
match_id: str = self.match.encode()
|
| 302 |
-
return f"{__name__}.{self.__class__.__name__}('{position_id}', '{match_id}')"
|
| 303 |
-
|
| 304 |
-
def __str__(self):
|
| 305 |
-
def checkers(top: List[int], bottom: List[int]) -> List[List[str]]:
|
| 306 |
-
"""Return an ASCII checker matrix."""
|
| 307 |
-
ascii_checkers: List[List[str]] = [
|
| 308 |
-
[" " for j in range(len(top))] for i in range(ASCII_BOARD_HEIGHT)
|
| 309 |
-
]
|
| 310 |
-
|
| 311 |
-
for half in (top, bottom):
|
| 312 |
-
for col, num_checkers in enumerate(half):
|
| 313 |
-
row: int = 0 if half is top else len(ascii_checkers) - 1
|
| 314 |
-
for i in range(abs(num_checkers)):
|
| 315 |
-
if (
|
| 316 |
-
abs(num_checkers) > ASCII_MAX_CHECKERS
|
| 317 |
-
and i == ASCII_MAX_CHECKERS - 1
|
| 318 |
-
):
|
| 319 |
-
ascii_checkers[row][col] = f" {abs(num_checkers)} "
|
| 320 |
-
break
|
| 321 |
-
ascii_checkers[row][col] = " O " if num_checkers > 0 else " X "
|
| 322 |
-
row += 1 if half is top else -1
|
| 323 |
-
|
| 324 |
-
return ascii_checkers
|
| 325 |
-
|
| 326 |
-
def split(position: List[int]) -> Tuple[List[int], List[int]]:
|
| 327 |
-
"""Return a position split into top (Player.ZERO 12-1) and bottom (Player.ZERO 13-24) halves."""
|
| 328 |
-
|
| 329 |
-
def normalize(position: List[int]) -> List[int]:
|
| 330 |
-
"""Return position for Player.ZERO"""
|
| 331 |
-
if self.match.player is Player.ONE:
|
| 332 |
-
position = list(map(lambda n: -n, position[::-1]))
|
| 333 |
-
return position
|
| 334 |
-
|
| 335 |
-
position = normalize(position)
|
| 336 |
-
|
| 337 |
-
half_len: int = int(len(position) / 2)
|
| 338 |
-
top: List[int] = position[:half_len][::-1]
|
| 339 |
-
bottom: List[int] = position[half_len:]
|
| 340 |
-
|
| 341 |
-
return top, bottom
|
| 342 |
-
|
| 343 |
-
points: List[List[str]] = checkers(*split(self.position.board_points))
|
| 344 |
-
|
| 345 |
-
bar: List[List[str]] = checkers(
|
| 346 |
-
*split(
|
| 347 |
-
[
|
| 348 |
-
self.position.player_bar,
|
| 349 |
-
-self.position.opponent_bar,
|
| 350 |
-
]
|
| 351 |
-
)
|
| 352 |
-
)
|
| 353 |
-
|
| 354 |
-
ascii_board: str = ""
|
| 355 |
-
position_id: str = self.position.encode()
|
| 356 |
-
ascii_board += f" Position ID: {position_id}\n"
|
| 357 |
-
match_id: str = self.match.encode()
|
| 358 |
-
ascii_board += f" Match ID : {match_id}\n"
|
| 359 |
-
ascii_board += (
|
| 360 |
-
" "
|
| 361 |
-
+ (ASCII_12_01 if self.match.player is Player.ZERO else ASCII_13_24)
|
| 362 |
-
+ "\n"
|
| 363 |
-
)
|
| 364 |
-
for i in range(len(points)):
|
| 365 |
-
ascii_board += (
|
| 366 |
-
("^|" if self.match.player is Player.ZERO else "v|")
|
| 367 |
-
if i == int(ASCII_BOARD_HEIGHT / 2)
|
| 368 |
-
else " |"
|
| 369 |
-
)
|
| 370 |
-
ascii_board += "".join(points[i][:POINTS_PER_QUADRANT])
|
| 371 |
-
ascii_board += "|"
|
| 372 |
-
ascii_board += "BAR" if i == int(ASCII_BOARD_HEIGHT / 2) else bar[i][0]
|
| 373 |
-
ascii_board += "|"
|
| 374 |
-
ascii_board += "".join(points[i][POINTS_PER_QUADRANT:])
|
| 375 |
-
ascii_board += "|"
|
| 376 |
-
ascii_board += "\n"
|
| 377 |
-
ascii_board += (
|
| 378 |
-
" "
|
| 379 |
-
+ (ASCII_13_24 if self.match.player is Player.ZERO else ASCII_12_01)
|
| 380 |
-
+ "\n"
|
| 381 |
-
)
|
| 382 |
-
|
| 383 |
-
return ascii_board
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
backgammon-1.0.0/backgammon/match.py
DELETED
|
@@ -1,177 +0,0 @@
|
|
| 1 |
-
# Copyright 2020 Softwerks LLC
|
| 2 |
-
#
|
| 3 |
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
-
# you may not use this file except in compliance with the License.
|
| 5 |
-
# You may obtain a copy of the License at
|
| 6 |
-
#
|
| 7 |
-
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
-
#
|
| 9 |
-
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
-
# See the License for the specific language governing permissions and
|
| 13 |
-
# limitations under the License.
|
| 14 |
-
|
| 15 |
-
import base64
|
| 16 |
-
import dataclasses
|
| 17 |
-
import enum
|
| 18 |
-
import math
|
| 19 |
-
import struct
|
| 20 |
-
from typing import Tuple
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
@enum.unique
|
| 24 |
-
class Player(enum.IntEnum):
|
| 25 |
-
ZERO = 0b00
|
| 26 |
-
ONE = 0b01
|
| 27 |
-
CENTERED = 0b11
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
@enum.unique
|
| 31 |
-
class GameState(enum.IntEnum):
|
| 32 |
-
NOT_STARTED = 0b000
|
| 33 |
-
PLAYING = 0b001
|
| 34 |
-
GAME_OVER = 0b010
|
| 35 |
-
RESIGNED = 0b011
|
| 36 |
-
DROPPED_CUBE = 0b100
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
@enum.unique
|
| 40 |
-
class Resign(enum.IntEnum):
|
| 41 |
-
NONE = 0b00
|
| 42 |
-
SINGLE_GAME = 0b01
|
| 43 |
-
GAMMON = 0b10
|
| 44 |
-
BACKGAMMON = 0b11
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
@dataclasses.dataclass
|
| 48 |
-
class Match:
|
| 49 |
-
cube_value: int
|
| 50 |
-
cube_holder: Player
|
| 51 |
-
player: Player
|
| 52 |
-
crawford: bool
|
| 53 |
-
game_state: GameState
|
| 54 |
-
turn: Player
|
| 55 |
-
double: bool
|
| 56 |
-
resign: Resign
|
| 57 |
-
dice: Tuple[int, int]
|
| 58 |
-
length: int
|
| 59 |
-
player_0_score: int
|
| 60 |
-
player_1_score: int
|
| 61 |
-
|
| 62 |
-
def swap_players(self) -> "Match":
|
| 63 |
-
self.player = self.turn = (
|
| 64 |
-
Player.ZERO if self.player is Player.ONE else Player.ONE
|
| 65 |
-
)
|
| 66 |
-
|
| 67 |
-
return self
|
| 68 |
-
|
| 69 |
-
def swap_turn(self) -> "Match":
|
| 70 |
-
self.turn = Player.ZERO if self.turn is Player.ONE else Player.ONE
|
| 71 |
-
|
| 72 |
-
return self
|
| 73 |
-
|
| 74 |
-
def reset_dice(self) -> "Match":
|
| 75 |
-
self.dice = (0, 0)
|
| 76 |
-
|
| 77 |
-
return self
|
| 78 |
-
|
| 79 |
-
def reset_cube(self) -> "Match":
|
| 80 |
-
self.cube_holder = Player.CENTERED
|
| 81 |
-
self.cube_value = 1
|
| 82 |
-
|
| 83 |
-
return self
|
| 84 |
-
|
| 85 |
-
def drop_cube(self) -> "Match":
|
| 86 |
-
if self.player is Player.ZERO:
|
| 87 |
-
self.player_0_score += self.cube_value
|
| 88 |
-
else:
|
| 89 |
-
self.player_1_score += self.cube_value
|
| 90 |
-
|
| 91 |
-
self.double = False
|
| 92 |
-
|
| 93 |
-
if self.player_0_score >= self.length or self.player_1_score >= self.length:
|
| 94 |
-
self.game_state = GameState.DROPPED_CUBE
|
| 95 |
-
|
| 96 |
-
return self
|
| 97 |
-
|
| 98 |
-
def update_score(self, multiplier: int) -> "Match":
|
| 99 |
-
points: int = self.cube_value * multiplier
|
| 100 |
-
|
| 101 |
-
self.crawford = False
|
| 102 |
-
|
| 103 |
-
if self.player is Player.ZERO:
|
| 104 |
-
self.player_0_score += points
|
| 105 |
-
if (
|
| 106 |
-
self.length - self.player_0_score == 1
|
| 107 |
-
and self.length - self.player_1_score > 1
|
| 108 |
-
):
|
| 109 |
-
self.crawford = True
|
| 110 |
-
else:
|
| 111 |
-
self.player_1_score += points
|
| 112 |
-
if (
|
| 113 |
-
self.length - self.player_1_score == 1
|
| 114 |
-
and self.length - self.player_0_score > 1
|
| 115 |
-
):
|
| 116 |
-
self.crawford = True
|
| 117 |
-
|
| 118 |
-
self.double = False
|
| 119 |
-
|
| 120 |
-
if self.player_0_score >= self.length or self.player_1_score >= self.length:
|
| 121 |
-
self.game_state = GameState.GAME_OVER
|
| 122 |
-
|
| 123 |
-
return self
|
| 124 |
-
|
| 125 |
-
def encode(self) -> str:
|
| 126 |
-
"""Encode the match and return a match ID.
|
| 127 |
-
|
| 128 |
-
>>> match = Match(cube_value=2, cube_holder=Player.ZERO, player=Player.ONE, crawford=False, game_state=GameState.PLAYING, turn=Player.ONE, double=False, resign=Resign.NONE, dice=(5, 2), length=9, player_0_score=2, player_1_score=4)
|
| 129 |
-
>>> match.encode()
|
| 130 |
-
'QYkqASAAIAAA'
|
| 131 |
-
"""
|
| 132 |
-
match_key: str = "".join(
|
| 133 |
-
(
|
| 134 |
-
f"{int(math.log(self.cube_value, 2)):04b}"[::-1],
|
| 135 |
-
f"{self.cube_holder.value:02b}"[::-1],
|
| 136 |
-
f"{self.player.value:b}",
|
| 137 |
-
f"{self.crawford:b}",
|
| 138 |
-
f"{self.game_state.value:03b}"[::-1],
|
| 139 |
-
f"{self.turn:b}",
|
| 140 |
-
f"{self.double:b}",
|
| 141 |
-
f"{self.resign.value:02b}"[::-1],
|
| 142 |
-
f"{self.dice[0]:03b}"[::-1],
|
| 143 |
-
f"{self.dice[1]:03b}"[::-1],
|
| 144 |
-
f"{self.length:015b}"[::-1],
|
| 145 |
-
f"{self.player_0_score:015b}"[::-1],
|
| 146 |
-
f"{self.player_1_score:015b}"[::-1],
|
| 147 |
-
)
|
| 148 |
-
)
|
| 149 |
-
byte_strings: Tuple[str, ...] = tuple(
|
| 150 |
-
match_key[i : i + 8][::-1] for i in range(0, len(match_key), 8)
|
| 151 |
-
)
|
| 152 |
-
match_bytes: bytes = struct.pack("9B", *(int(b, 2) for b in byte_strings))
|
| 153 |
-
return base64.b64encode(bytes(match_bytes)).decode()
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
def decode(match_id: str) -> Match:
|
| 157 |
-
"""Decode a match ID and return a Match.
|
| 158 |
-
|
| 159 |
-
>>> decode("QYkqASAAIAAA")
|
| 160 |
-
Match(cube_value=2, cube_holder=<Player.ZERO: 0>, player=<Player.ONE: 1>, crawford=False, game_state=<GameState.PLAYING: 1>, turn=<Player.ONE: 1>, double=False, resign=<Resign.NONE: 0>, dice=(5, 2), length=9, player_0_score=2, player_1_score=4)
|
| 161 |
-
"""
|
| 162 |
-
match_bytes: bytes = base64.b64decode(match_id)
|
| 163 |
-
match_key: str = "".join([format(b, "08b")[::-1] for b in match_bytes])
|
| 164 |
-
return Match(
|
| 165 |
-
cube_value=2 ** int(match_key[0:4][::-1], 2),
|
| 166 |
-
cube_holder=Player(int(match_key[4:6][::-1], 2)),
|
| 167 |
-
player=Player(int(match_key[6])),
|
| 168 |
-
crawford=bool(int(match_key[7])),
|
| 169 |
-
game_state=GameState(int(match_key[8:11][::-1], 2)),
|
| 170 |
-
turn=Player(int(match_key[11])),
|
| 171 |
-
double=bool(int(match_key[12])),
|
| 172 |
-
resign=Resign(int(match_key[13:15][::-1], 2)),
|
| 173 |
-
dice=(int(match_key[15:18][::-1], 2), int(match_key[18:21][::-1], 2)),
|
| 174 |
-
length=int(match_key[21:36][::-1], 2),
|
| 175 |
-
player_0_score=int(match_key[36:51][::-1], 2),
|
| 176 |
-
player_1_score=int(match_key[51:66][::-1], 2),
|
| 177 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
backgammon-1.0.0/backgammon/position.py
DELETED
|
@@ -1,214 +0,0 @@
|
|
| 1 |
-
# Copyright 2020 Softwerks LLC
|
| 2 |
-
#
|
| 3 |
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
-
# you may not use this file except in compliance with the License.
|
| 5 |
-
# You may obtain a copy of the License at
|
| 6 |
-
#
|
| 7 |
-
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
-
#
|
| 9 |
-
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
-
# See the License for the specific language governing permissions and
|
| 13 |
-
# limitations under the License.
|
| 14 |
-
|
| 15 |
-
# Position ID
|
| 16 |
-
# https://www.gnu.org/software/gnubg/manual/html_node/A-technical-description-of-the-Position-ID.html
|
| 17 |
-
# Official documentation is inaccurate. Position key (binary string) starts from the opponent's ace point. See:
|
| 18 |
-
# https://lists.gnu.org/archive/html/bug-gnubg/2005-01/msg00081.html
|
| 19 |
-
# https://lists.gnu.org/archive/html/bug-gnubg/2013-01/msg00010.html
|
| 20 |
-
|
| 21 |
-
import base64
|
| 22 |
-
import dataclasses
|
| 23 |
-
import struct
|
| 24 |
-
from typing import List, Optional, Tuple
|
| 25 |
-
|
| 26 |
-
POINTS = 24
|
| 27 |
-
POINTS_PER_QUADRANT = int(POINTS / 4)
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
@dataclasses.dataclass(frozen=True)
|
| 31 |
-
class Position:
|
| 32 |
-
board_points: Tuple[int, ...]
|
| 33 |
-
player_bar: int
|
| 34 |
-
player_off: int
|
| 35 |
-
opponent_bar: int
|
| 36 |
-
opponent_off: int
|
| 37 |
-
|
| 38 |
-
def enter(self, pips: int) -> Tuple[Optional["Position"], Optional[int]]:
|
| 39 |
-
"""Try to enter from the bar and return the new position and destination."""
|
| 40 |
-
destination: int = POINTS - pips
|
| 41 |
-
|
| 42 |
-
if self.board_points[destination] >= -1:
|
| 43 |
-
return self.apply_move(None, destination), destination
|
| 44 |
-
|
| 45 |
-
return None, None
|
| 46 |
-
|
| 47 |
-
def player_home(self) -> Tuple[int, ...]:
|
| 48 |
-
"""Return checkers in the player's home board."""
|
| 49 |
-
home_board: Tuple[int, ...] = self.board_points[:POINTS_PER_QUADRANT]
|
| 50 |
-
|
| 51 |
-
return tuple(point if point > 0 else 0 for point in home_board)
|
| 52 |
-
|
| 53 |
-
def off(self, point: int, pips: int) -> Tuple[Optional["Position"], Optional[int]]:
|
| 54 |
-
"""Try to move a checker in the player's home board and return the new position and destination."""
|
| 55 |
-
if self.board_points[point] > 0:
|
| 56 |
-
destination: int = point - pips
|
| 57 |
-
if destination < 0:
|
| 58 |
-
checkers_on_higher_points: int = sum(
|
| 59 |
-
self.player_home()[point + 1 : POINTS_PER_QUADRANT]
|
| 60 |
-
)
|
| 61 |
-
if destination == -1 or checkers_on_higher_points == 0:
|
| 62 |
-
return self.apply_move(point, None), None
|
| 63 |
-
elif self.board_points[destination] >= -1:
|
| 64 |
-
return self.apply_move(point, destination), destination
|
| 65 |
-
|
| 66 |
-
return None, None
|
| 67 |
-
|
| 68 |
-
def move(self, point: int, pips: int) -> Tuple[Optional["Position"], Optional[int]]:
|
| 69 |
-
"""Try to move a checker and return the new position and destination."""
|
| 70 |
-
if self.board_points[point] > 0:
|
| 71 |
-
destination: int = point - pips
|
| 72 |
-
if destination >= 0 and self.board_points[destination] >= -1:
|
| 73 |
-
return self.apply_move(point, destination), destination
|
| 74 |
-
|
| 75 |
-
return None, None
|
| 76 |
-
|
| 77 |
-
def apply_move(
|
| 78 |
-
self, source: Optional[int], destination: Optional[int]
|
| 79 |
-
) -> "Position":
|
| 80 |
-
"""Apply a move and return a new position."""
|
| 81 |
-
board_points: List[int] = list(self.board_points)
|
| 82 |
-
player_bar: int = self.player_bar
|
| 83 |
-
player_off: int = self.player_off
|
| 84 |
-
opponent_bar: int = self.opponent_bar
|
| 85 |
-
opponent_off: int = self.opponent_off
|
| 86 |
-
|
| 87 |
-
if source is None:
|
| 88 |
-
player_bar -= 1
|
| 89 |
-
else:
|
| 90 |
-
board_points[source] -= 1
|
| 91 |
-
|
| 92 |
-
if destination is None:
|
| 93 |
-
player_off += 1
|
| 94 |
-
else:
|
| 95 |
-
hit: bool = True if board_points[destination] == -1 else False
|
| 96 |
-
if hit:
|
| 97 |
-
board_points[destination] = 1
|
| 98 |
-
opponent_bar += 1
|
| 99 |
-
else:
|
| 100 |
-
board_points[destination] += 1
|
| 101 |
-
|
| 102 |
-
return Position(
|
| 103 |
-
tuple(board_points), player_bar, player_off, opponent_bar, opponent_off
|
| 104 |
-
)
|
| 105 |
-
|
| 106 |
-
def swap_players(self) -> "Position":
|
| 107 |
-
return Position(
|
| 108 |
-
tuple(map(lambda n: -n, self.board_points[::-1])),
|
| 109 |
-
self.opponent_bar,
|
| 110 |
-
self.opponent_off,
|
| 111 |
-
self.player_bar,
|
| 112 |
-
self.player_off,
|
| 113 |
-
)
|
| 114 |
-
|
| 115 |
-
def encode(self) -> str:
|
| 116 |
-
"""Encode the position and return a position ID.
|
| 117 |
-
|
| 118 |
-
>>> position = Position(board_points=(-2, 0, 0, 0, 0, 5, 0, 3, 0, 0, 0, -5, 5, 0, 0, 0, -3, 0, -5, 0, 0, 0, 0, 2), player_bar=0, player_off=0, opponent_bar=0, opponent_off=0)
|
| 119 |
-
>>> position.encode()
|
| 120 |
-
'4HPwATDgc/ABMA'
|
| 121 |
-
"""
|
| 122 |
-
player_points, opponent_points = _unmerge_points(self.board_points)
|
| 123 |
-
checkers: Tuple[int, ...] = (
|
| 124 |
-
opponent_points + (self.opponent_bar,) + player_points + (self.player_bar,)
|
| 125 |
-
)
|
| 126 |
-
|
| 127 |
-
position_key: str = _key_from_checkers(checkers)
|
| 128 |
-
|
| 129 |
-
position_id: str = _id_from_key(position_key)
|
| 130 |
-
|
| 131 |
-
return position_id
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
def _unmerge_points(
|
| 135 |
-
board_points: Tuple[int, ...]
|
| 136 |
-
) -> Tuple[Tuple[int, ...], Tuple[int, ...]]:
|
| 137 |
-
"""Return player and opponent board positions starting from their respective ace points."""
|
| 138 |
-
player: Tuple[int, ...] = tuple(
|
| 139 |
-
map(
|
| 140 |
-
lambda n: 0 if n < 0 else n,
|
| 141 |
-
board_points,
|
| 142 |
-
)
|
| 143 |
-
)
|
| 144 |
-
opponent: Tuple[int, ...] = tuple(
|
| 145 |
-
map(
|
| 146 |
-
lambda n: 0 if n > 0 else -n,
|
| 147 |
-
board_points[::-1],
|
| 148 |
-
)
|
| 149 |
-
)
|
| 150 |
-
return player, opponent
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
def _key_from_checkers(checkers: Tuple[int, ...]) -> str:
|
| 154 |
-
"""Return a position key (bit string)."""
|
| 155 |
-
return "".join("1" * n + "0" for n in checkers).ljust(80, "0")
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
def _id_from_key(position_key: str) -> str:
|
| 159 |
-
"""Encode the position key and return the ID."""
|
| 160 |
-
byte_strings: Tuple[str, ...] = tuple(
|
| 161 |
-
position_key[i : i + 8][::-1] for i in range(0, len(position_key), 8)
|
| 162 |
-
)
|
| 163 |
-
position_bytes: bytes = struct.pack("10B", *(int(b, 2) for b in byte_strings))
|
| 164 |
-
return base64.b64encode(position_bytes).decode()[:-2]
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
def decode(position_id: str) -> Position:
|
| 168 |
-
"""Decode a position ID and return a Position.
|
| 169 |
-
|
| 170 |
-
>>> decode('4HPwATDgc/ABMA')
|
| 171 |
-
Position(board_points=(-2, 0, 0, 0, 0, 5, 0, 3, 0, 0, 0, -5, 5, 0, 0, 0, -3, 0, -5, 0, 0, 0, 0, 2), player_bar=0, player_off=0, opponent_bar=0, opponent_off=0)
|
| 172 |
-
"""
|
| 173 |
-
position_key: str = _key_from_id(position_id)
|
| 174 |
-
|
| 175 |
-
checkers: Tuple[int, ...] = _checkers_from_key(position_key)
|
| 176 |
-
|
| 177 |
-
player_points: Tuple[int, ...] = checkers[25:49]
|
| 178 |
-
opponent_points: Tuple[int, ...] = checkers[:24]
|
| 179 |
-
board_points: Tuple[int, ...] = _merge_points(player_points, opponent_points)
|
| 180 |
-
|
| 181 |
-
player_bar: int = checkers[49]
|
| 182 |
-
player_off: int = abs(15 - sum(player_points) - player_bar)
|
| 183 |
-
|
| 184 |
-
opponent_bar: int = checkers[24]
|
| 185 |
-
opponent_off: int = abs(15 - sum(opponent_points) - abs(opponent_bar))
|
| 186 |
-
|
| 187 |
-
return Position(
|
| 188 |
-
board_points=board_points,
|
| 189 |
-
player_bar=player_bar,
|
| 190 |
-
player_off=player_off,
|
| 191 |
-
opponent_bar=opponent_bar,
|
| 192 |
-
opponent_off=opponent_off,
|
| 193 |
-
)
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
def _key_from_id(position_id: str) -> str:
|
| 197 |
-
"""Decode the the position ID and return the key (bit string)."""
|
| 198 |
-
position_bytes: bytes = base64.b64decode(position_id + "==")
|
| 199 |
-
position_key: str = "".join([format(b, "08b")[::-1] for b in position_bytes])
|
| 200 |
-
return position_key
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
def _checkers_from_key(position_key: str) -> Tuple[int, ...]:
|
| 204 |
-
"""Return a list of checkers."""
|
| 205 |
-
return tuple(sum(int(n) for n in pos) for pos in position_key.split("0")[:50])
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
def _merge_points(
|
| 209 |
-
player: Tuple[int, ...], opponent: Tuple[int, ...]
|
| 210 |
-
) -> Tuple[int, ...]:
|
| 211 |
-
"""Merge player and opponent board positions and return the combined points."""
|
| 212 |
-
return tuple(
|
| 213 |
-
i + j for i, j in zip(player, tuple(map(lambda n: -n, opponent[::-1])))
|
| 214 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
backgammon-1.0.0/setup.cfg
DELETED
|
@@ -1,4 +0,0 @@
|
|
| 1 |
-
[egg_info]
|
| 2 |
-
tag_build =
|
| 3 |
-
tag_date = 0
|
| 4 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
backgammon-1.0.0/setup.py
DELETED
|
@@ -1,36 +0,0 @@
|
|
| 1 |
-
# Copyright 2019 Softwerks LLC
|
| 2 |
-
#
|
| 3 |
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
-
# you may not use this file except in compliance with the License.
|
| 5 |
-
# You may obtain a copy of the License at
|
| 6 |
-
#
|
| 7 |
-
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
-
#
|
| 9 |
-
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
-
# See the License for the specific language governing permissions and
|
| 13 |
-
# limitations under the License.
|
| 14 |
-
|
| 15 |
-
from setuptools import setup, find_packages
|
| 16 |
-
|
| 17 |
-
with open("README.rst", encoding="utf-8") as f:
|
| 18 |
-
long_description = f.read()
|
| 19 |
-
|
| 20 |
-
setup(
|
| 21 |
-
name="backgammon",
|
| 22 |
-
version="1.0.0",
|
| 23 |
-
author="Softwerks",
|
| 24 |
-
author_email="info@softwerks.com",
|
| 25 |
-
description="Backgammon engine for the Backgammon Network.",
|
| 26 |
-
long_description=long_description,
|
| 27 |
-
long_description_content_type="text/x-rst",
|
| 28 |
-
url="https://github.com/softwerks/backgammon",
|
| 29 |
-
packages=find_packages(),
|
| 30 |
-
classifiers=[
|
| 31 |
-
"Programming Language :: Python :: 3",
|
| 32 |
-
"License :: OSI Approved :: Apache Software License",
|
| 33 |
-
"Operating System :: OS Independent",
|
| 34 |
-
],
|
| 35 |
-
python_requires=">=3.7",
|
| 36 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
backgammon-1.0.0/tests/__init__.py
DELETED
|
@@ -1,13 +0,0 @@
|
|
| 1 |
-
# Copyright 2021 Softwerks LLC
|
| 2 |
-
#
|
| 3 |
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
-
# you may not use this file except in compliance with the License.
|
| 5 |
-
# You may obtain a copy of the License at
|
| 6 |
-
#
|
| 7 |
-
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
-
#
|
| 9 |
-
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
-
# See the License for the specific language governing permissions and
|
| 13 |
-
# limitations under the License.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
backgammon-1.0.0/tests/test_backgammon.py
DELETED
|
@@ -1,414 +0,0 @@
|
|
| 1 |
-
# Copyright 2021 Softwerks LLC
|
| 2 |
-
#
|
| 3 |
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
-
# you may not use this file except in compliance with the License.
|
| 5 |
-
# You may obtain a copy of the License at
|
| 6 |
-
#
|
| 7 |
-
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
-
#
|
| 9 |
-
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
-
# See the License for the specific language governing permissions and
|
| 13 |
-
# limitations under the License.
|
| 14 |
-
|
| 15 |
-
import unittest
|
| 16 |
-
from unittest import mock
|
| 17 |
-
|
| 18 |
-
from backgammon import backgammon
|
| 19 |
-
from backgammon.backgammon import Play, Move
|
| 20 |
-
from backgammon.position import Position
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
class TestBackgammon(unittest.TestCase):
|
| 24 |
-
# fmt: off
|
| 25 |
-
def test_enter(self):
|
| 26 |
-
# -----19-20-21-22-23-24-+
|
| 27 |
-
# | | O O O O O O |
|
| 28 |
-
# | | O O O O O O |
|
| 29 |
-
# | | |
|
| 30 |
-
# |BAR| (5, 4) |
|
| 31 |
-
# | X | |
|
| 32 |
-
self.assertEqual(
|
| 33 |
-
backgammon.Backgammon("27YBAAAAACAAAA", "cInyAAAAAAAE").generate_plays(),
|
| 34 |
-
[],
|
| 35 |
-
)
|
| 36 |
-
|
| 37 |
-
# -----19-20-21-22-23-24-+
|
| 38 |
-
# | | O O O O O |
|
| 39 |
-
# | | O O O O |
|
| 40 |
-
# | | |
|
| 41 |
-
# |BAR| (5, 4) |
|
| 42 |
-
# | X | |
|
| 43 |
-
self.assertEqual(
|
| 44 |
-
backgammon.Backgammon("2zIAAAAAAAQAAA", "cInyAAAAAAAE").generate_plays(),
|
| 45 |
-
[
|
| 46 |
-
Play(
|
| 47 |
-
moves=(
|
| 48 |
-
Move(pips=4, source=None, destination=20),
|
| 49 |
-
Move(pips=5, source=20, destination=15),
|
| 50 |
-
),
|
| 51 |
-
position=Position(
|
| 52 |
-
board_points=(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, -2, 0, 0, -2, -2, -2),
|
| 53 |
-
player_bar=0,
|
| 54 |
-
player_off=14,
|
| 55 |
-
opponent_bar=1,
|
| 56 |
-
opponent_off=6,
|
| 57 |
-
),
|
| 58 |
-
),
|
| 59 |
-
Play(
|
| 60 |
-
moves=(
|
| 61 |
-
Move(pips=5, source=None, destination=19),
|
| 62 |
-
Move(pips=4, source=19, destination=15),
|
| 63 |
-
),
|
| 64 |
-
position=Position(
|
| 65 |
-
board_points=(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, -2, 0, -1, -2, -2, -2),
|
| 66 |
-
player_bar=0,
|
| 67 |
-
player_off=14,
|
| 68 |
-
opponent_bar=0,
|
| 69 |
-
opponent_off=6,
|
| 70 |
-
),
|
| 71 |
-
),
|
| 72 |
-
],
|
| 73 |
-
)
|
| 74 |
-
|
| 75 |
-
def test_bear_off(self):
|
| 76 |
-
# | (4, 3)
|
| 77 |
-
# | X X O |
|
| 78 |
-
# | X X O |
|
| 79 |
-
# --6--5--4--3--2--1-+
|
| 80 |
-
self.assertEqual(
|
| 81 |
-
backgammon.Backgammon("AACAYQMAAAAAAA", "cAnuAAAAAAAE").generate_plays(),
|
| 82 |
-
[
|
| 83 |
-
Play(
|
| 84 |
-
moves=(
|
| 85 |
-
Move(pips=4, source=3, destination=None),
|
| 86 |
-
Move(pips=3, source=2, destination=None),
|
| 87 |
-
),
|
| 88 |
-
position=Position(
|
| 89 |
-
board_points=(-2, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
| 90 |
-
player_bar=0,
|
| 91 |
-
player_off=13,
|
| 92 |
-
opponent_bar=0,
|
| 93 |
-
opponent_off=13,
|
| 94 |
-
),
|
| 95 |
-
)
|
| 96 |
-
],
|
| 97 |
-
)
|
| 98 |
-
|
| 99 |
-
# | (4, 2)
|
| 100 |
-
# | X O |
|
| 101 |
-
# | X X O |
|
| 102 |
-
# --6--5--4--3--2--1-+
|
| 103 |
-
self.assertEqual(
|
| 104 |
-
backgammon.Backgammon("AACAMQEAAAAAAA", "cAnqAAAAAAAE").generate_plays(),
|
| 105 |
-
[
|
| 106 |
-
Play(
|
| 107 |
-
moves=(
|
| 108 |
-
Move(pips=2, source=3, destination=1),
|
| 109 |
-
Move(pips=4, source=1, destination=None),
|
| 110 |
-
),
|
| 111 |
-
position=Position(
|
| 112 |
-
board_points=(-2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
| 113 |
-
player_bar=0,
|
| 114 |
-
player_off=13,
|
| 115 |
-
opponent_bar=0,
|
| 116 |
-
opponent_off=13,
|
| 117 |
-
),
|
| 118 |
-
),
|
| 119 |
-
Play(
|
| 120 |
-
moves=(
|
| 121 |
-
Move(pips=4, source=3, destination=None),
|
| 122 |
-
Move(pips=2, source=1, destination=None),
|
| 123 |
-
),
|
| 124 |
-
position=Position(
|
| 125 |
-
board_points=(-2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
| 126 |
-
player_bar=0,
|
| 127 |
-
player_off=14,
|
| 128 |
-
opponent_bar=0,
|
| 129 |
-
opponent_off=13,
|
| 130 |
-
),
|
| 131 |
-
),
|
| 132 |
-
],
|
| 133 |
-
)
|
| 134 |
-
|
| 135 |
-
# | (6, 4)
|
| 136 |
-
# | X O |
|
| 137 |
-
# | X X O |
|
| 138 |
-
# --6--5--4--3--2--1-+
|
| 139 |
-
self.assertEqual(
|
| 140 |
-
backgammon.Backgammon("AACAIQMAAAAAAA", "cAnzAAAAAAAE").generate_plays(),
|
| 141 |
-
[
|
| 142 |
-
Play(
|
| 143 |
-
moves=(
|
| 144 |
-
Move(pips=6, source=4, destination=None),
|
| 145 |
-
),
|
| 146 |
-
position=Position(
|
| 147 |
-
board_points=(-2, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
| 148 |
-
player_bar=0,
|
| 149 |
-
player_off=13,
|
| 150 |
-
opponent_bar=0,
|
| 151 |
-
opponent_off=13,
|
| 152 |
-
),
|
| 153 |
-
)
|
| 154 |
-
],
|
| 155 |
-
)
|
| 156 |
-
|
| 157 |
-
# | (6, 4)
|
| 158 |
-
# | X O |
|
| 159 |
-
# | X X X O |
|
| 160 |
-
# --6--5--4--3--2--1-+
|
| 161 |
-
self.assertEqual(
|
| 162 |
-
backgammon.Backgammon("AACAIQsAAAAAAA", "cAnzAAAAAAAE").generate_plays(),
|
| 163 |
-
[
|
| 164 |
-
Play(
|
| 165 |
-
moves=(
|
| 166 |
-
Move(pips=4, source=5, destination=1),
|
| 167 |
-
Move(pips=6, source=4, destination=None),
|
| 168 |
-
),
|
| 169 |
-
position=Position(
|
| 170 |
-
board_points=(-2, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
| 171 |
-
player_bar=0,
|
| 172 |
-
player_off=12,
|
| 173 |
-
opponent_bar=0,
|
| 174 |
-
opponent_off=13,
|
| 175 |
-
),
|
| 176 |
-
)
|
| 177 |
-
],
|
| 178 |
-
)
|
| 179 |
-
|
| 180 |
-
def test_move(self):
|
| 181 |
-
# +13-14-15-16-17-18------19-20-21-22-23-24-+
|
| 182 |
-
# | O O | | O O O O X |
|
| 183 |
-
# | O O | | O O O O |
|
| 184 |
-
# | O | | O O |
|
| 185 |
-
# | | | |
|
| 186 |
-
# | | | |
|
| 187 |
-
# | |BAR| (6, 6) |
|
| 188 |
-
# | | | X |
|
| 189 |
-
# | | | X |
|
| 190 |
-
# | | | X X |
|
| 191 |
-
# | | | X X X X X |
|
| 192 |
-
# | | | X X X X X |
|
| 193 |
-
# +12-11-10--9--8--7-------6--5--4--3--2--1-+
|
| 194 |
-
self.assertEqual(
|
| 195 |
-
backgammon.Backgammon("bHc3AADfbQMAIA", "cAn7AAAAAAAE").generate_plays(),
|
| 196 |
-
[],
|
| 197 |
-
)
|
| 198 |
-
|
| 199 |
-
# -19-20-21-22-23-24-+
|
| 200 |
-
# | O O X X X |
|
| 201 |
-
# | O X |
|
| 202 |
-
# | X |
|
| 203 |
-
# | (1, 1) |
|
| 204 |
-
self.assertEqual(
|
| 205 |
-
backgammon.Backgammon("mAAAAAAArgAAAA", "cInkAAAAAAAE").generate_plays(),
|
| 206 |
-
[
|
| 207 |
-
Play(
|
| 208 |
-
moves=(
|
| 209 |
-
Move(pips=1, source=22, destination=21),
|
| 210 |
-
Move(pips=1, source=23, destination=22),
|
| 211 |
-
Move(pips=1, source=22, destination=21),
|
| 212 |
-
),
|
| 213 |
-
position=Position(
|
| 214 |
-
board_points=(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, -2, 5, 0, 0),
|
| 215 |
-
player_bar=0,
|
| 216 |
-
player_off=10,
|
| 217 |
-
opponent_bar=0,
|
| 218 |
-
opponent_off=12,
|
| 219 |
-
),
|
| 220 |
-
)
|
| 221 |
-
],
|
| 222 |
-
)
|
| 223 |
-
|
| 224 |
-
# +13-14-15-16-17-18------19-20-21-22-23-24-+
|
| 225 |
-
# | O O O O X | | O O O X |
|
| 226 |
-
# | O O O | | O O |
|
| 227 |
-
# | O | | O O |
|
| 228 |
-
# | | | (3, 2) |
|
| 229 |
-
self.assertEqual(
|
| 230 |
-
backgammon.Backgammon("rsPOAgAAAAIBAA", "cImpAAAAAAAE").generate_plays(),
|
| 231 |
-
[
|
| 232 |
-
Play(
|
| 233 |
-
moves=(
|
| 234 |
-
Move(pips=3, source=17, destination=14),
|
| 235 |
-
Move(pips=2, source=14, destination=12),
|
| 236 |
-
),
|
| 237 |
-
position=Position(
|
| 238 |
-
board_points=(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, -2, 0, -3, -2, 0, 0, 0, -3, -1, -3, 1),
|
| 239 |
-
player_bar=0,
|
| 240 |
-
player_off=13,
|
| 241 |
-
opponent_bar=1,
|
| 242 |
-
opponent_off=0,
|
| 243 |
-
),
|
| 244 |
-
),
|
| 245 |
-
Play(
|
| 246 |
-
moves=(
|
| 247 |
-
Move(pips=3, source=17, destination=14),
|
| 248 |
-
Move(pips=2, source=23, destination=21),
|
| 249 |
-
),
|
| 250 |
-
position=Position(
|
| 251 |
-
board_points=(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -2, 1, -3, -2, 0, 0, 0, -3, 1, -3, 0),
|
| 252 |
-
player_bar=0,
|
| 253 |
-
player_off=13,
|
| 254 |
-
opponent_bar=1,
|
| 255 |
-
opponent_off=0,
|
| 256 |
-
),
|
| 257 |
-
),
|
| 258 |
-
Play(
|
| 259 |
-
moves=(
|
| 260 |
-
Move(pips=2, source=23, destination=21),
|
| 261 |
-
Move(pips=3, source=21, destination=18),
|
| 262 |
-
),
|
| 263 |
-
position=Position(
|
| 264 |
-
board_points=(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -2, 0, -3, -2, 1, 1, 0, -3, 0, -3, 0),
|
| 265 |
-
player_bar=0,
|
| 266 |
-
player_off=13,
|
| 267 |
-
opponent_bar=1,
|
| 268 |
-
opponent_off=0,
|
| 269 |
-
),
|
| 270 |
-
),
|
| 271 |
-
],
|
| 272 |
-
)
|
| 273 |
-
# fmt: on
|
| 274 |
-
|
| 275 |
-
@mock.patch("random.SystemRandom.randrange", side_effect=[3, 4])
|
| 276 |
-
def test_start(self, randrange_mock):
|
| 277 |
-
self.assertEqual(
|
| 278 |
-
backgammon.Backgammon().start().encode(), "4HPwATDgc/ABMA:cIlxAAAAAAAA"
|
| 279 |
-
)
|
| 280 |
-
|
| 281 |
-
@mock.patch("random.SystemRandom.randrange", side_effect=[3, 4])
|
| 282 |
-
def test_roll(self, randrange_mock):
|
| 283 |
-
with self.assertRaises(backgammon.BackgammonError):
|
| 284 |
-
backgammon.Backgammon("4HPwATDgc/ABMA", "MAAOAAAAAAAA").roll()
|
| 285 |
-
|
| 286 |
-
self.assertEqual(backgammon.Backgammon().roll(), (3, 4))
|
| 287 |
-
|
| 288 |
-
@mock.patch("random.SystemRandom.randrange", side_effect=[3, 3, 4, 3, 3, 4])
|
| 289 |
-
def test_first_roll(self, randrange_mock):
|
| 290 |
-
p0: backgammon.Backgammon = backgammon.Backgammon()
|
| 291 |
-
self.assertEqual(p0.first_roll(), (4, 3))
|
| 292 |
-
self.assertEqual(p0.encode(), "4HPwATDgc/ABMA:MAAOAAAAAAAA")
|
| 293 |
-
|
| 294 |
-
p1: backgammon.Backgammon = backgammon.Backgammon()
|
| 295 |
-
self.assertEqual(p1.first_roll(), (3, 4))
|
| 296 |
-
self.assertEqual(p1.encode(), "4HPwATDgc/ABMA:cIgRAAAAAAAA")
|
| 297 |
-
|
| 298 |
-
@mock.patch("random.SystemRandom.randrange", side_effect=[5, 4, 3, 4, 4, 1])
|
| 299 |
-
def test_play(self, randrange_mock):
|
| 300 |
-
self.assertEqual(
|
| 301 |
-
backgammon.Backgammon("4NvBEQiYz+ABAw", "cAlqAAAAAAAE")
|
| 302 |
-
.play(((12, 8), (12, 10)))
|
| 303 |
-
.encode(),
|
| 304 |
-
"mM+SAQPg28EBRA:MAFgAAAAAAAA",
|
| 305 |
-
)
|
| 306 |
-
|
| 307 |
-
self.assertEqual(
|
| 308 |
-
backgammon.Backgammon("CwAAiAIAAAAAAA", "cAluAAAAAAAE")
|
| 309 |
-
.play(((3, None), (2, None)))
|
| 310 |
-
.encode(),
|
| 311 |
-
"4HPwATDgc/ABMA:MIFyAAAACAAA",
|
| 312 |
-
)
|
| 313 |
-
|
| 314 |
-
self.assertEqual(
|
| 315 |
-
backgammon.Backgammon("2+4FAEAhAAAAAA", "cAlvAAAAAAAE")
|
| 316 |
-
.play(((4, None), (0, None)))
|
| 317 |
-
.encode(),
|
| 318 |
-
"2+4FAEAAAAAAAA:cApvAAAAGAAA",
|
| 319 |
-
)
|
| 320 |
-
|
| 321 |
-
self.assertEqual(
|
| 322 |
-
backgammon.Backgammon("2+4FAAQhAAAAAA", "cAlvAAAAAAAE")
|
| 323 |
-
.play(((4, None), (0, None)))
|
| 324 |
-
.encode(),
|
| 325 |
-
"2+4FAAQAAAAAAA:cApvAAAAGAAA",
|
| 326 |
-
)
|
| 327 |
-
|
| 328 |
-
self.assertEqual(
|
| 329 |
-
backgammon.Backgammon("2+4NAAAhAAAAAA", "cAlvAAAAAAAE")
|
| 330 |
-
.play(((4, None), (0, None)))
|
| 331 |
-
.encode(),
|
| 332 |
-
"4HPwATDgc/ABMA:8IlxAAAAEAAA",
|
| 333 |
-
)
|
| 334 |
-
|
| 335 |
-
self.assertEqual(
|
| 336 |
-
backgammon.Backgammon("XwAAgAEAAAAAAA", "cIltACAAAAAA")
|
| 337 |
-
.play(((0, None), (0, None)))
|
| 338 |
-
.encode(),
|
| 339 |
-
"4HPwATDgc/ABMA:MAFmACAACAAA",
|
| 340 |
-
)
|
| 341 |
-
|
| 342 |
-
with self.assertRaises(backgammon.BackgammonError):
|
| 343 |
-
backgammon.Backgammon("4HPwATDgc/ABMA", "cAlqAAAAAAAE").play(
|
| 344 |
-
((12, 8), (19, 17))
|
| 345 |
-
)
|
| 346 |
-
|
| 347 |
-
def test_double(self):
|
| 348 |
-
with self.assertRaises(backgammon.BackgammonError):
|
| 349 |
-
backgammon.Backgammon("4HPwATDgc/ABMA", "cInxABAAAAAA").double()
|
| 350 |
-
|
| 351 |
-
with self.assertRaises(backgammon.BackgammonError):
|
| 352 |
-
backgammon.Backgammon("4HPhASLgc/ABMA", "EQHgAAAAAAAA").double()
|
| 353 |
-
|
| 354 |
-
with self.assertRaises(backgammon.BackgammonError):
|
| 355 |
-
backgammon.Backgammon("4HPhASLgc/ABMA", "MBngAAAAAAAE").double()
|
| 356 |
-
|
| 357 |
-
with self.assertRaises(backgammon.BackgammonError):
|
| 358 |
-
backgammon.Backgammon("4HPhASLgc/ABMA", "MAHgAGAAKAAA").double()
|
| 359 |
-
|
| 360 |
-
with self.assertRaises(backgammon.BackgammonError):
|
| 361 |
-
backgammon.Backgammon("4HPhASLgc/ABMA", "8AmgAEAAGAAA").double()
|
| 362 |
-
|
| 363 |
-
self.assertEqual(
|
| 364 |
-
backgammon.Backgammon("0PPgATDgc+EBIg", "UQngAAAAAAAA").double().encode(),
|
| 365 |
-
"0PPgATDgc+EBIg:URHgAAAAAAAA",
|
| 366 |
-
)
|
| 367 |
-
|
| 368 |
-
def test_accept_double(self):
|
| 369 |
-
self.assertEqual(
|
| 370 |
-
backgammon.Backgammon("4HPhASLgc/ABMA", "MBngAAAAAAAE")
|
| 371 |
-
.accept_double()
|
| 372 |
-
.encode(),
|
| 373 |
-
"4HPhASLgc/ABMA:EQHgAAAAAAAA",
|
| 374 |
-
)
|
| 375 |
-
|
| 376 |
-
with self.assertRaises(backgammon.BackgammonError):
|
| 377 |
-
backgammon.Backgammon("4HPhASLgc/ABMA", "MAHgAAAAAAAA").accept_double()
|
| 378 |
-
|
| 379 |
-
@mock.patch("random.SystemRandom.randrange", side_effect=[3, 4])
|
| 380 |
-
def test_reject_double(self, randrange_mock):
|
| 381 |
-
self.assertEqual(
|
| 382 |
-
backgammon.Backgammon("4HPhASLgc/ABMA", "MBngAAAAAAAE")
|
| 383 |
-
.reject_double()
|
| 384 |
-
.encode(),
|
| 385 |
-
"4HPwATDgc/ABMA:cInxABAAAAAA",
|
| 386 |
-
)
|
| 387 |
-
|
| 388 |
-
with self.assertRaises(backgammon.BackgammonError):
|
| 389 |
-
backgammon.Backgammon("4HPhASLgc/ABMA", "MAHgAAAAAAAA").reject_double()
|
| 390 |
-
|
| 391 |
-
def test_skip(self):
|
| 392 |
-
self.assertEqual(
|
| 393 |
-
backgammon.Backgammon("ZgAAAAAAIAAAAA", "cAnqAAAAAAAE").skip().encode(),
|
| 394 |
-
"AAAAmQEAAAAAAA:MAHgAAAAAAAA",
|
| 395 |
-
)
|
| 396 |
-
|
| 397 |
-
with self.assertRaises(backgammon.BackgammonError):
|
| 398 |
-
backgammon.Backgammon("MgAAAAAAEAAAAA", "cAnqAAAAAAAE").skip()
|
| 399 |
-
|
| 400 |
-
def test_end_turn(self):
|
| 401 |
-
self.assertEqual(
|
| 402 |
-
backgammon.Backgammon("ywEXAIGDAQEMAA", "MIGxABAAEAAA").end_turn().encode(),
|
| 403 |
-
"OBgQwJYDLgACAA:cAmgABAAEAAA",
|
| 404 |
-
)
|
| 405 |
-
|
| 406 |
-
def test_encode(self):
|
| 407 |
-
self.assertEqual(
|
| 408 |
-
backgammon.Backgammon("4HPwATDgc/ABMA", "cAgAAAAAAAAA").encode(),
|
| 409 |
-
"4HPwATDgc/ABMA:cAgAAAAAAAAA",
|
| 410 |
-
)
|
| 411 |
-
|
| 412 |
-
|
| 413 |
-
if __name__ == "__main__":
|
| 414 |
-
unittest.main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
backgammon-1.0.0/tests/test_match.py
DELETED
|
@@ -1,106 +0,0 @@
|
|
| 1 |
-
# Copyright 2021 Softwerks LLC
|
| 2 |
-
#
|
| 3 |
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
-
# you may not use this file except in compliance with the License.
|
| 5 |
-
# You may obtain a copy of the License at
|
| 6 |
-
#
|
| 7 |
-
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
-
#
|
| 9 |
-
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
-
# See the License for the specific language governing permissions and
|
| 13 |
-
# limitations under the License.
|
| 14 |
-
|
| 15 |
-
import unittest
|
| 16 |
-
|
| 17 |
-
from backgammon import match
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
class TestMatch(unittest.TestCase):
|
| 21 |
-
def test_swap_players(self):
|
| 22 |
-
self.assertEqual(
|
| 23 |
-
match.decode("cImxABAAEAAA").swap_players(), match.decode("MIGxABAAEAAA")
|
| 24 |
-
)
|
| 25 |
-
|
| 26 |
-
def test_swap_turn(self):
|
| 27 |
-
self.assertEqual(
|
| 28 |
-
match.decode("cImxABAAEAAA").swap_turn(), match.decode("cIGxABAAEAAA")
|
| 29 |
-
)
|
| 30 |
-
|
| 31 |
-
def test_reset_dice(self):
|
| 32 |
-
self.assertEqual(
|
| 33 |
-
match.decode("cImxABAAEAAA").reset_dice(), match.decode("cAmgABAAEAAA")
|
| 34 |
-
)
|
| 35 |
-
|
| 36 |
-
def test_reset_cube(self):
|
| 37 |
-
self.assertEqual(
|
| 38 |
-
match.decode("QYmxABAAEAAA").reset_cube(), match.decode("cImxABAAEAAA")
|
| 39 |
-
)
|
| 40 |
-
|
| 41 |
-
def test_drop_cube(self):
|
| 42 |
-
self.assertEqual(
|
| 43 |
-
match.decode("cBFgABAACAAA").drop_cube(), match.decode("cAFgABAAEAAA")
|
| 44 |
-
)
|
| 45 |
-
self.assertEqual(
|
| 46 |
-
match.decode("ARlgABAAEAAA").drop_cube(), match.decode("AQxgADAAEAAA")
|
| 47 |
-
)
|
| 48 |
-
|
| 49 |
-
def test_update_score(self):
|
| 50 |
-
self.assertEqual(
|
| 51 |
-
match.decode("QYkqASAAIAAA").update_score(1), match.decode("QYkqASAAMAAA")
|
| 52 |
-
)
|
| 53 |
-
self.assertEqual(
|
| 54 |
-
match.decode("MIFlABAAEAAA").update_score(3), match.decode("MIJlAEAAEAAA")
|
| 55 |
-
)
|
| 56 |
-
self.assertEqual(
|
| 57 |
-
match.decode("MIGqACAAGAAA").update_score(2), match.decode("sIGqAEAAGAAA")
|
| 58 |
-
)
|
| 59 |
-
self.assertEqual(
|
| 60 |
-
match.decode("QYnqACAAIAAA").update_score(1), match.decode("wYnqACAAMAAA")
|
| 61 |
-
)
|
| 62 |
-
self.assertEqual(
|
| 63 |
-
match.decode("MIGqACAAIAAA").update_score(2), match.decode("MIGqAEAAIAAA")
|
| 64 |
-
)
|
| 65 |
-
|
| 66 |
-
def test_encode(self):
|
| 67 |
-
self.assertEqual(
|
| 68 |
-
match.Match(
|
| 69 |
-
cube_value=2,
|
| 70 |
-
cube_holder=match.Player.ZERO,
|
| 71 |
-
player=match.Player.ONE,
|
| 72 |
-
crawford=False,
|
| 73 |
-
game_state=match.GameState.PLAYING,
|
| 74 |
-
turn=match.Player.ONE,
|
| 75 |
-
double=False,
|
| 76 |
-
resign=match.Resign.NONE,
|
| 77 |
-
dice=(5, 2),
|
| 78 |
-
length=9,
|
| 79 |
-
player_0_score=2,
|
| 80 |
-
player_1_score=4,
|
| 81 |
-
).encode(),
|
| 82 |
-
"QYkqASAAIAAA",
|
| 83 |
-
)
|
| 84 |
-
|
| 85 |
-
def test_decode(self):
|
| 86 |
-
self.assertEqual(
|
| 87 |
-
match.decode("QYkqASAAIAAA"),
|
| 88 |
-
match.Match(
|
| 89 |
-
cube_value=2,
|
| 90 |
-
cube_holder=match.Player.ZERO,
|
| 91 |
-
player=match.Player.ONE,
|
| 92 |
-
crawford=False,
|
| 93 |
-
game_state=match.GameState.PLAYING,
|
| 94 |
-
turn=match.Player.ONE,
|
| 95 |
-
double=False,
|
| 96 |
-
resign=match.Resign.NONE,
|
| 97 |
-
dice=(5, 2),
|
| 98 |
-
length=9,
|
| 99 |
-
player_0_score=2,
|
| 100 |
-
player_1_score=4,
|
| 101 |
-
),
|
| 102 |
-
)
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
if __name__ == "__main__":
|
| 106 |
-
unittest.main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
backgammon-1.0.0/tests/test_position.py
DELETED
|
@@ -1,177 +0,0 @@
|
|
| 1 |
-
# Copyright 2021 Softwerks LLC
|
| 2 |
-
#
|
| 3 |
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
-
# you may not use this file except in compliance with the License.
|
| 5 |
-
# You may obtain a copy of the License at
|
| 6 |
-
#
|
| 7 |
-
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
-
#
|
| 9 |
-
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
-
# See the License for the specific language governing permissions and
|
| 13 |
-
# limitations under the License.
|
| 14 |
-
|
| 15 |
-
from typing import Tuple
|
| 16 |
-
import unittest
|
| 17 |
-
|
| 18 |
-
from backgammon import position
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
class TestPosition(unittest.TestCase):
|
| 22 |
-
def test_enter(self):
|
| 23 |
-
# ----19-20-21-22-23-24-+
|
| 24 |
-
# |BAR| O O O O O |
|
| 25 |
-
# | X | O O O O O |
|
| 26 |
-
pos: position.Position = position.decode("m20AAAAAAAgAAA")
|
| 27 |
-
self.assertEqual(pos.enter(3), (position.decode("m20AAAAAAAEAAA"), 21))
|
| 28 |
-
self.assertEqual(pos.enter(2), (None, None))
|
| 29 |
-
|
| 30 |
-
def test_player_home(self):
|
| 31 |
-
# | X |
|
| 32 |
-
# | X X O |
|
| 33 |
-
# | X X O X O |
|
| 34 |
-
# --6--5--4--3--2--1-+
|
| 35 |
-
pos: position.Position = position.decode("AAAQ41gAAAAAAA")
|
| 36 |
-
self.assertEqual(pos.player_home(), (0, 3, 0, 0, 2, 1))
|
| 37 |
-
|
| 38 |
-
def test_off(self):
|
| 39 |
-
# | X O |
|
| 40 |
-
# | X X X O |
|
| 41 |
-
# --6--5--4--3--2--1-+
|
| 42 |
-
pos: position.Position = position.decode("AACAMQUAAAAAAA")
|
| 43 |
-
self.assertEqual(pos.off(1, 2), (position.decode("AACAkQIAAAAAAA"), None))
|
| 44 |
-
self.assertEqual(pos.off(3, 4), (position.decode("AACAMQIAAAAAAA"), None))
|
| 45 |
-
self.assertEqual(pos.off(4, 6), (position.decode("AACAMQEAAAAAAA"), None))
|
| 46 |
-
self.assertEqual(pos.off(3, 2), (position.decode("AACAcQQAAAAAAA"), 1))
|
| 47 |
-
self.assertEqual(pos.off(3, 6), (None, None))
|
| 48 |
-
|
| 49 |
-
def test_move(self):
|
| 50 |
-
# -19-20-21-22-23-24-+
|
| 51 |
-
# | O O O O X |
|
| 52 |
-
# | O O O |
|
| 53 |
-
pos: position.Position = position.decode("tgwAAAAAgAAAAA")
|
| 54 |
-
self.assertEqual(pos.move(23, 3), (position.decode("NgYAQAAAEAAAAA"), 20))
|
| 55 |
-
self.assertEqual(pos.move(23, 4), (position.decode("tgwAAAAACAAAAA"), 19))
|
| 56 |
-
self.assertEqual(pos.move(23, 5), (None, None))
|
| 57 |
-
|
| 58 |
-
def test_apply_move(self):
|
| 59 |
-
# -----19-20-21-22-23-24-+
|
| 60 |
-
# |BAR| X O O |
|
| 61 |
-
# | X | O |
|
| 62 |
-
pos: position.Position = position.decode("CwAAAACAIAAAAA")
|
| 63 |
-
self.assertEqual(pos.apply_move(None, 22), position.decode("AwAABACACAAAAA"))
|
| 64 |
-
self.assertEqual(pos.apply_move(None, 21), position.decode("CwAAAACABAAAAA"))
|
| 65 |
-
self.assertEqual(pos.apply_move(None, 19), position.decode("CwAAAACAAQAAAA"))
|
| 66 |
-
# | X O |
|
| 67 |
-
# | X O O X |
|
| 68 |
-
# --6--5--4--3--2--1-+
|
| 69 |
-
pos: position.Position = position.decode("AABgEQwAAAAAAA")
|
| 70 |
-
self.assertEqual(pos.apply_move(5, 3), position.decode("AABgEQkAAAAAAA"))
|
| 71 |
-
self.assertEqual(pos.apply_move(5, 1), position.decode("AABgVAgAAAAAAA"))
|
| 72 |
-
self.assertEqual(pos.apply_move(5, 0), position.decode("AABgMQgAAAAAAA"))
|
| 73 |
-
self.assertEqual(pos.apply_move(5, None), position.decode("AABgEQQAAAAAAA"))
|
| 74 |
-
|
| 75 |
-
def test_swap_players(self):
|
| 76 |
-
# +13-14-15-16-17-18------19-20-21-22-23-24-+
|
| 77 |
-
# | O O X | | O O O |
|
| 78 |
-
# | O | | O O |
|
| 79 |
-
# | O | | O |
|
| 80 |
-
# | | | |
|
| 81 |
-
# | |BAR| X |
|
| 82 |
-
# | X | X | X |
|
| 83 |
-
# | X | X | X O |
|
| 84 |
-
# +12-11-10--9--8--7-------6--5--4--3--2--1-+
|
| 85 |
-
pos: position.Position = position.decode("ywEXAIGDAQEMAA")
|
| 86 |
-
self.assertEqual(pos.swap_players(), position.decode("OBgQwJYDLgACAA"))
|
| 87 |
-
|
| 88 |
-
# fmt: off
|
| 89 |
-
|
| 90 |
-
def test_encode(self):
|
| 91 |
-
pos: position.Position = position.Position(
|
| 92 |
-
board_points=(-2, 0, 0, 0, 0, 5, 0, 3, 0, 0, 0, -5, 5, 0, 0, 0, -3, 0, -5, 0, 0, 0, 0, 2),
|
| 93 |
-
player_bar=0,
|
| 94 |
-
player_off=0,
|
| 95 |
-
opponent_bar=0,
|
| 96 |
-
opponent_off=0,
|
| 97 |
-
)
|
| 98 |
-
self.assertEqual(pos.encode(), "4HPwATDgc/ABMA")
|
| 99 |
-
|
| 100 |
-
def test_unmerge_points(self):
|
| 101 |
-
player: Tuple[int, ...]
|
| 102 |
-
opponent: Tuple[int, ...]
|
| 103 |
-
|
| 104 |
-
board_points: Tuple[int, ...] = (-2, 0, 0, 0, 0, 5, 0, 3, 0, 0, 0, -5, 5, 0, 0, 0, -3, 0, -5, 0, 0, 0, 0, 2)
|
| 105 |
-
player, opponent = position._unmerge_points(board_points)
|
| 106 |
-
|
| 107 |
-
self.assertEqual(
|
| 108 |
-
player,
|
| 109 |
-
(0, 0, 0, 0, 0, 5, 0, 3, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2),
|
| 110 |
-
)
|
| 111 |
-
self.assertEqual(
|
| 112 |
-
opponent,
|
| 113 |
-
(0, 0, 0, 0, 0, 5, 0, 3, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2),
|
| 114 |
-
)
|
| 115 |
-
|
| 116 |
-
def test_key_from_checkers(self):
|
| 117 |
-
unmerged_points: Tuple[int, ...] = (0, 0, 0, 0, 0, 5, 0, 3, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2)
|
| 118 |
-
bar: Tuple[int, ...] = (0,)
|
| 119 |
-
|
| 120 |
-
self.assertEqual(
|
| 121 |
-
position._key_from_checkers(unmerged_points + bar + unmerged_points + bar),
|
| 122 |
-
"00000111110011100000111110000000000011000000011111001110000011111000000000001100",
|
| 123 |
-
)
|
| 124 |
-
|
| 125 |
-
def test_id_from_key(self):
|
| 126 |
-
self.assertEqual(
|
| 127 |
-
position._id_from_key(
|
| 128 |
-
"00000111110011100000111110000000000011000000011111001110000011111000000000001100"
|
| 129 |
-
),
|
| 130 |
-
"4HPwATDgc/ABMA",
|
| 131 |
-
)
|
| 132 |
-
|
| 133 |
-
def test_decode(self):
|
| 134 |
-
self.assertEqual(
|
| 135 |
-
position.decode("4HPwATDgc/ABMA"),
|
| 136 |
-
position.Position(
|
| 137 |
-
board_points=(-2, 0, 0, 0, 0, 5, 0, 3, 0, 0, 0, -5, 5, 0, 0, 0, -3, 0, -5, 0, 0, 0, 0, 2),
|
| 138 |
-
player_bar=0,
|
| 139 |
-
player_off=0,
|
| 140 |
-
opponent_bar=0,
|
| 141 |
-
opponent_off=0,
|
| 142 |
-
),
|
| 143 |
-
)
|
| 144 |
-
|
| 145 |
-
def test_key_from_id(self):
|
| 146 |
-
self.assertEqual(
|
| 147 |
-
position._key_from_id("4HPwATDgc/ABMA"),
|
| 148 |
-
"00000111110011100000111110000000000011000000011111001110000011111000000000001100",
|
| 149 |
-
)
|
| 150 |
-
|
| 151 |
-
def test_checkers_from_key(self):
|
| 152 |
-
self.assertEqual(
|
| 153 |
-
position._checkers_from_key(
|
| 154 |
-
"00000111110011100000111110000000000011000000011111001110000011111000000000001100"
|
| 155 |
-
),
|
| 156 |
-
(
|
| 157 |
-
0, 0, 0, 0, 0, 5, 0, 3, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2,
|
| 158 |
-
0,
|
| 159 |
-
0, 0, 0, 0, 0, 5, 0, 3, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2,
|
| 160 |
-
0,
|
| 161 |
-
),
|
| 162 |
-
)
|
| 163 |
-
|
| 164 |
-
def test_merge_points(self):
|
| 165 |
-
self.assertEqual(
|
| 166 |
-
position._merge_points(
|
| 167 |
-
(0, 0, 0, 0, 0, 5, 0, 3, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2),
|
| 168 |
-
(0, 0, 0, 0, 0, 5, 0, 3, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2),
|
| 169 |
-
),
|
| 170 |
-
(-2, 0, 0, 0, 0, 5, 0, 3, 0, 0, 0, -5, 5, 0, 0, 0, -3, 0, -5, 0, 0, 0, 0, 2),
|
| 171 |
-
)
|
| 172 |
-
|
| 173 |
-
# fmt: on
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
if __name__ == "__main__":
|
| 177 |
-
unittest.main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|