SanaomerUnity commited on
Commit
03d67a8
·
1 Parent(s): dde3070

Delete backgammon-1.0.0

Browse files
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()