pingv4 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pingv4-0.1.0/.gitignore +15 -0
- pingv4-0.1.0/.python-version +1 -0
- pingv4-0.1.0/Cargo.lock +180 -0
- pingv4-0.1.0/Cargo.toml +15 -0
- pingv4-0.1.0/LICENSE +21 -0
- pingv4-0.1.0/PKG-INFO +355 -0
- pingv4-0.1.0/README.md +328 -0
- pingv4-0.1.0/game_test.py +4 -0
- pingv4-0.1.0/pyproject.toml +41 -0
- pingv4-0.1.0/src/core/game/board.rs +304 -0
- pingv4-0.1.0/src/core/game/cell.rs +15 -0
- pingv4-0.1.0/src/core/game/error.rs +17 -0
- pingv4-0.1.0/src/core/game/mod.rs +14 -0
- pingv4-0.1.0/src/core/game/state.rs +44 -0
- pingv4-0.1.0/src/core/game/test.rs +52 -0
- pingv4-0.1.0/src/core/mod.rs +1 -0
- pingv4-0.1.0/src/lib.rs +14 -0
- pingv4-0.1.0/src/pingv4/__init__.py +15 -0
- pingv4-0.1.0/src/pingv4/_core.pyi +203 -0
- pingv4-0.1.0/src/pingv4/bot/__init__.py +8 -0
- pingv4-0.1.0/src/pingv4/bot/base.py +77 -0
- pingv4-0.1.0/src/pingv4/bot/minimax.py +334 -0
- pingv4-0.1.0/src/pingv4/game.py +496 -0
- pingv4-0.1.0/src/pingv4/py.typed +0 -0
- pingv4-0.1.0/src/wrapper/game_wrapper.rs +107 -0
- pingv4-0.1.0/src/wrapper/mod.rs +157 -0
- pingv4-0.1.0/test.py +227 -0
- pingv4-0.1.0/uv.lock +240 -0
pingv4-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.13
|
pingv4-0.1.0/Cargo.lock
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# This file is automatically @generated by Cargo.
|
|
2
|
+
# It is not intended for manual editing.
|
|
3
|
+
version = 4
|
|
4
|
+
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "autocfg"
|
|
7
|
+
version = "1.5.0"
|
|
8
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
9
|
+
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
|
10
|
+
|
|
11
|
+
[[package]]
|
|
12
|
+
name = "cfg-if"
|
|
13
|
+
version = "1.0.4"
|
|
14
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
15
|
+
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
|
16
|
+
|
|
17
|
+
[[package]]
|
|
18
|
+
name = "heck"
|
|
19
|
+
version = "0.5.0"
|
|
20
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
21
|
+
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
|
22
|
+
|
|
23
|
+
[[package]]
|
|
24
|
+
name = "indoc"
|
|
25
|
+
version = "2.0.7"
|
|
26
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
27
|
+
checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
|
|
28
|
+
dependencies = [
|
|
29
|
+
"rustversion",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[[package]]
|
|
33
|
+
name = "libc"
|
|
34
|
+
version = "0.2.180"
|
|
35
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
36
|
+
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
|
|
37
|
+
|
|
38
|
+
[[package]]
|
|
39
|
+
name = "memoffset"
|
|
40
|
+
version = "0.9.1"
|
|
41
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
42
|
+
checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
|
|
43
|
+
dependencies = [
|
|
44
|
+
"autocfg",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
[[package]]
|
|
48
|
+
name = "once_cell"
|
|
49
|
+
version = "1.21.3"
|
|
50
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
51
|
+
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
|
52
|
+
|
|
53
|
+
[[package]]
|
|
54
|
+
name = "pingv4"
|
|
55
|
+
version = "0.1.0"
|
|
56
|
+
dependencies = [
|
|
57
|
+
"pyo3",
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
[[package]]
|
|
61
|
+
name = "portable-atomic"
|
|
62
|
+
version = "1.13.0"
|
|
63
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
64
|
+
checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950"
|
|
65
|
+
|
|
66
|
+
[[package]]
|
|
67
|
+
name = "proc-macro2"
|
|
68
|
+
version = "1.0.106"
|
|
69
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
70
|
+
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
|
71
|
+
dependencies = [
|
|
72
|
+
"unicode-ident",
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
[[package]]
|
|
76
|
+
name = "pyo3"
|
|
77
|
+
version = "0.22.6"
|
|
78
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
79
|
+
checksum = "f402062616ab18202ae8319da13fa4279883a2b8a9d9f83f20dbade813ce1884"
|
|
80
|
+
dependencies = [
|
|
81
|
+
"cfg-if",
|
|
82
|
+
"indoc",
|
|
83
|
+
"libc",
|
|
84
|
+
"memoffset",
|
|
85
|
+
"once_cell",
|
|
86
|
+
"portable-atomic",
|
|
87
|
+
"pyo3-build-config",
|
|
88
|
+
"pyo3-ffi",
|
|
89
|
+
"pyo3-macros",
|
|
90
|
+
"unindent",
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
[[package]]
|
|
94
|
+
name = "pyo3-build-config"
|
|
95
|
+
version = "0.22.6"
|
|
96
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
97
|
+
checksum = "b14b5775b5ff446dd1056212d778012cbe8a0fbffd368029fd9e25b514479c38"
|
|
98
|
+
dependencies = [
|
|
99
|
+
"once_cell",
|
|
100
|
+
"target-lexicon",
|
|
101
|
+
]
|
|
102
|
+
|
|
103
|
+
[[package]]
|
|
104
|
+
name = "pyo3-ffi"
|
|
105
|
+
version = "0.22.6"
|
|
106
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
107
|
+
checksum = "9ab5bcf04a2cdcbb50c7d6105de943f543f9ed92af55818fd17b660390fc8636"
|
|
108
|
+
dependencies = [
|
|
109
|
+
"libc",
|
|
110
|
+
"pyo3-build-config",
|
|
111
|
+
]
|
|
112
|
+
|
|
113
|
+
[[package]]
|
|
114
|
+
name = "pyo3-macros"
|
|
115
|
+
version = "0.22.6"
|
|
116
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
117
|
+
checksum = "0fd24d897903a9e6d80b968368a34e1525aeb719d568dba8b3d4bfa5dc67d453"
|
|
118
|
+
dependencies = [
|
|
119
|
+
"proc-macro2",
|
|
120
|
+
"pyo3-macros-backend",
|
|
121
|
+
"quote",
|
|
122
|
+
"syn",
|
|
123
|
+
]
|
|
124
|
+
|
|
125
|
+
[[package]]
|
|
126
|
+
name = "pyo3-macros-backend"
|
|
127
|
+
version = "0.22.6"
|
|
128
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
129
|
+
checksum = "36c011a03ba1e50152b4b394b479826cad97e7a21eb52df179cd91ac411cbfbe"
|
|
130
|
+
dependencies = [
|
|
131
|
+
"heck",
|
|
132
|
+
"proc-macro2",
|
|
133
|
+
"pyo3-build-config",
|
|
134
|
+
"quote",
|
|
135
|
+
"syn",
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
[[package]]
|
|
139
|
+
name = "quote"
|
|
140
|
+
version = "1.0.44"
|
|
141
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
142
|
+
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
|
|
143
|
+
dependencies = [
|
|
144
|
+
"proc-macro2",
|
|
145
|
+
]
|
|
146
|
+
|
|
147
|
+
[[package]]
|
|
148
|
+
name = "rustversion"
|
|
149
|
+
version = "1.0.22"
|
|
150
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
151
|
+
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
|
152
|
+
|
|
153
|
+
[[package]]
|
|
154
|
+
name = "syn"
|
|
155
|
+
version = "2.0.114"
|
|
156
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
157
|
+
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
|
|
158
|
+
dependencies = [
|
|
159
|
+
"proc-macro2",
|
|
160
|
+
"quote",
|
|
161
|
+
"unicode-ident",
|
|
162
|
+
]
|
|
163
|
+
|
|
164
|
+
[[package]]
|
|
165
|
+
name = "target-lexicon"
|
|
166
|
+
version = "0.12.16"
|
|
167
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
168
|
+
checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
|
|
169
|
+
|
|
170
|
+
[[package]]
|
|
171
|
+
name = "unicode-ident"
|
|
172
|
+
version = "1.0.22"
|
|
173
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
174
|
+
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
|
175
|
+
|
|
176
|
+
[[package]]
|
|
177
|
+
name = "unindent"
|
|
178
|
+
version = "0.2.4"
|
|
179
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
180
|
+
checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3"
|
pingv4-0.1.0/Cargo.toml
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "pingv4"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
|
|
7
|
+
[lib]
|
|
8
|
+
name = "_core"
|
|
9
|
+
# "cdylib" is necessary to produce a shared library for Python to import from.
|
|
10
|
+
crate-type = ["cdylib"]
|
|
11
|
+
|
|
12
|
+
[dependencies]
|
|
13
|
+
# "extension-module" tells pyo3 we want to build an extension module (skips linking against libpython.so)
|
|
14
|
+
# "abi3-py39" tells pyo3 (and maturin) to build using the stable ABI with minimum Python version 3.9
|
|
15
|
+
pyo3 = { version = "0.22.4", features = ["extension-module", "abi3-py39"] }
|
pingv4-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Developer Student Club - SNU
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
pingv4-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pingv4
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Classifier: Intended Audience :: Developers
|
|
5
|
+
Classifier: Intended Audience :: Education
|
|
6
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
7
|
+
Classifier: Natural Language :: English
|
|
8
|
+
Classifier: Programming Language :: Rust
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
15
|
+
Classifier: Typing :: Typed
|
|
16
|
+
Requires-Dist: pydantic>=2.12.5
|
|
17
|
+
Requires-Dist: pygame>=2.6.1
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Summary: A high-performance Connect Four library with a graphical game interface and bot framework.
|
|
20
|
+
Author-email: lalitm1004 <lalitm1004@gmail.com>
|
|
21
|
+
License: MIT
|
|
22
|
+
Requires-Python: >=3.9
|
|
23
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
24
|
+
Project-URL: Repository, https://github.com/dscsnu/pingv4
|
|
25
|
+
Project-URL: Homepage, https://github.com/dscsnu/pingv4
|
|
26
|
+
Project-URL: Issues, https://github.com/dscsnu/pingv4/issues
|
|
27
|
+
|
|
28
|
+
# pingv4
|
|
29
|
+
|
|
30
|
+
A high-performance Connect Four library with a graphical game interface and bot framework.
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install pingv4
|
|
36
|
+
```
|
|
37
|
+
> *Note: Python 3.9+ required*
|
|
38
|
+
|
|
39
|
+
## Quick Start
|
|
40
|
+
|
|
41
|
+
### Play a Game
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from pingv4 import Connect4Game, MinimaxBot
|
|
45
|
+
|
|
46
|
+
# Human vs Human
|
|
47
|
+
game = Connect4Game()
|
|
48
|
+
game.run()
|
|
49
|
+
|
|
50
|
+
# Human vs Bot
|
|
51
|
+
game = Connect4Game(player1=None, player2=MinimaxBot)
|
|
52
|
+
game.run()
|
|
53
|
+
|
|
54
|
+
# Bot vs Bot
|
|
55
|
+
game = Connect4Game(player1=MinimaxBot, player2=MinimaxBot)
|
|
56
|
+
game.run()
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Use the Board Directly
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from pingv4 import ConnectFourBoard, CellState
|
|
63
|
+
|
|
64
|
+
board = ConnectFourBoard()
|
|
65
|
+
|
|
66
|
+
# Make moves (returns a new board - immutable!)
|
|
67
|
+
board = board.make_move(3) # Red plays center
|
|
68
|
+
board = board.make_move(3) # Yellow plays center
|
|
69
|
+
board = board.make_move(2) # Red plays left of center
|
|
70
|
+
|
|
71
|
+
# Check game state
|
|
72
|
+
print(board.is_in_progress) # True
|
|
73
|
+
print(board.current_player) # CellState.Yellow
|
|
74
|
+
print(board.get_valid_moves()) # [0, 1, 2, 3, 4, 5, 6]
|
|
75
|
+
|
|
76
|
+
# Access cells (column-major: board[col, row])
|
|
77
|
+
print(board[3, 0]) # CellState.Red
|
|
78
|
+
print(board[3, 1]) # CellState.Yellow
|
|
79
|
+
|
|
80
|
+
# Print the board
|
|
81
|
+
print(board)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## API Reference
|
|
87
|
+
|
|
88
|
+
### `CellState`
|
|
89
|
+
|
|
90
|
+
An enum representing the state of a cell.
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from pingv4 import CellState
|
|
94
|
+
|
|
95
|
+
CellState.Red # Red player (plays first)
|
|
96
|
+
CellState.Yellow # Yellow player
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
### `ConnectFourBoard`
|
|
102
|
+
|
|
103
|
+
The core game board class. **Immutable** - all operations return new board instances.
|
|
104
|
+
|
|
105
|
+
#### Creating a Board
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
board = ConnectFourBoard() # Creates an empty 6x7 board
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
#### Properties
|
|
112
|
+
|
|
113
|
+
| Property | Type | Description |
|
|
114
|
+
|----------|------|-------------|
|
|
115
|
+
| `num_rows` | `int` | Number of rows (6) |
|
|
116
|
+
| `num_cols` | `int` | Number of columns (7) |
|
|
117
|
+
| `current_player` | `CellState \| None` | Current player, or `None` if game over |
|
|
118
|
+
| `is_in_progress` | `bool` | `True` if game is still ongoing |
|
|
119
|
+
| `is_victory` | `bool` | `True` if a player has won |
|
|
120
|
+
| `is_draw` | `bool` | `True` if the board is full with no winner |
|
|
121
|
+
| `winner` | `CellState \| None` | The winning player, or `None` |
|
|
122
|
+
| `column_heights` | `list[int]` | Number of pieces in each column |
|
|
123
|
+
| `hash` | `int` | Deterministic hash for the board state |
|
|
124
|
+
| `cell_states` | `list[list[CellState \| None]]` | All cells (column-major) |
|
|
125
|
+
|
|
126
|
+
#### Methods
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
# Get valid moves (columns that aren't full)
|
|
130
|
+
moves: list[int] = board.get_valid_moves()
|
|
131
|
+
|
|
132
|
+
# Make a move (returns NEW board)
|
|
133
|
+
new_board = board.make_move(col_idx) # col_idx: 0-6
|
|
134
|
+
|
|
135
|
+
# Access a cell (column-major!)
|
|
136
|
+
cell = board[col, row] # col: 0-6, row: 0-5 (bottom to top)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
> ⚠️ **Column-Major Access**: Board indexing is `board[column, row]`, not `board[row, column]`.
|
|
140
|
+
|
|
141
|
+
#### Example: Game Loop
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
from pingv4 import ConnectFourBoard
|
|
145
|
+
|
|
146
|
+
board = ConnectFourBoard()
|
|
147
|
+
|
|
148
|
+
while board.is_in_progress:
|
|
149
|
+
move = int(input(f"{board.current_player}'s turn. Column (0-6): "))
|
|
150
|
+
if move in board.get_valid_moves():
|
|
151
|
+
board = board.make_move(move)
|
|
152
|
+
print(board)
|
|
153
|
+
|
|
154
|
+
if board.is_victory:
|
|
155
|
+
print(f"{board.winner} wins!")
|
|
156
|
+
else:
|
|
157
|
+
print("It's a draw!")
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
### `Connect4Game`
|
|
163
|
+
|
|
164
|
+
A pygame-based graphical game interface.
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
from pingv4 import Connect4Game, GameConfig, MinimaxBot
|
|
168
|
+
|
|
169
|
+
# Basic usage
|
|
170
|
+
game = Connect4Game(
|
|
171
|
+
player1=None, # Human player
|
|
172
|
+
player2=MinimaxBot, # Bot class
|
|
173
|
+
config=GameConfig() # Optional configuration
|
|
174
|
+
)
|
|
175
|
+
game.run()
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
#### Constructor Parameters
|
|
179
|
+
|
|
180
|
+
| Parameter | Type | Default | Description |
|
|
181
|
+
|-----------|------|---------|-------------|
|
|
182
|
+
| `player1` | `PlayerConfig` | `None` | First player (see below) |
|
|
183
|
+
| `player2` | `PlayerConfig` | `None` | Second player |
|
|
184
|
+
| `config` | `GameConfig` | `GameConfig()` | Game settings |
|
|
185
|
+
|
|
186
|
+
**`PlayerConfig`** can be:
|
|
187
|
+
- `None` — Human player (manual input)
|
|
188
|
+
- Bot class (e.g., `MinimaxBot`, `RandomBot`) — Will be instantiated automatically
|
|
189
|
+
|
|
190
|
+
#### Controls
|
|
191
|
+
|
|
192
|
+
| Key | Action |
|
|
193
|
+
|-----|--------|
|
|
194
|
+
| Click | Place piece (human turn) |
|
|
195
|
+
| `R` | Restart game |
|
|
196
|
+
| `ESC` | Quit |
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
### `GameConfig`
|
|
201
|
+
|
|
202
|
+
Frozen Pydantic model for game configuration.
|
|
203
|
+
|
|
204
|
+
```python
|
|
205
|
+
from pingv4 import GameConfig
|
|
206
|
+
|
|
207
|
+
config = GameConfig(
|
|
208
|
+
bot_delay_seconds=0.5, # Delay before bot moves (default: 1.0)
|
|
209
|
+
animation_speed=35, # Piece falling speed (default: 25)
|
|
210
|
+
window_width=700, # Window width in pixels
|
|
211
|
+
window_height=700, # Window height in pixels
|
|
212
|
+
cell_size=80, # Size of each cell
|
|
213
|
+
background_color=(30, 30, 40),
|
|
214
|
+
board_color=(0, 80, 180),
|
|
215
|
+
red_color=(220, 50, 50),
|
|
216
|
+
yellow_color=(240, 220, 50),
|
|
217
|
+
)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## Creating Custom Bots
|
|
223
|
+
|
|
224
|
+
Extend `AbstractBot` to create your own Connect Four AI:
|
|
225
|
+
|
|
226
|
+
```python
|
|
227
|
+
from pingv4 import AbstractBot, ConnectFourBoard, CellState
|
|
228
|
+
|
|
229
|
+
class MyBot(AbstractBot):
|
|
230
|
+
@property
|
|
231
|
+
def strategy_name(self) -> str:
|
|
232
|
+
return "My Custom Bot"
|
|
233
|
+
|
|
234
|
+
@property
|
|
235
|
+
def author_name(self) -> str:
|
|
236
|
+
return "Your Name"
|
|
237
|
+
|
|
238
|
+
@property
|
|
239
|
+
def author_netid(self) -> str:
|
|
240
|
+
return "your_id"
|
|
241
|
+
|
|
242
|
+
def get_move(self, board: ConnectFourBoard) -> int:
|
|
243
|
+
"""Return a column index (0-6) for your move."""
|
|
244
|
+
valid_moves = board.get_valid_moves()
|
|
245
|
+
|
|
246
|
+
# Your strategy here!
|
|
247
|
+
# Example: prefer center columns
|
|
248
|
+
for col in [3, 2, 4, 1, 5, 0, 6]:
|
|
249
|
+
if col in valid_moves:
|
|
250
|
+
return col
|
|
251
|
+
|
|
252
|
+
return valid_moves[0]
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Using Your Bot
|
|
256
|
+
|
|
257
|
+
```python
|
|
258
|
+
from pingv4 import Connect4Game
|
|
259
|
+
|
|
260
|
+
game = Connect4Game(player1=MyBot, player2=None)
|
|
261
|
+
game.run()
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### Bot Interface
|
|
265
|
+
|
|
266
|
+
Your bot receives a `ConnectFourBoard` and must return a valid column index.
|
|
267
|
+
|
|
268
|
+
```python
|
|
269
|
+
def get_move(self, board: ConnectFourBoard) -> int:
|
|
270
|
+
# Useful properties:
|
|
271
|
+
board.current_player # Your color (CellState.Red or CellState.Yellow)
|
|
272
|
+
board.get_valid_moves() # List of valid columns
|
|
273
|
+
board.column_heights # How full each column is
|
|
274
|
+
board.hash # For transposition tables
|
|
275
|
+
|
|
276
|
+
# Simulate moves:
|
|
277
|
+
future = board.make_move(col) # Returns new board
|
|
278
|
+
|
|
279
|
+
# Check outcomes:
|
|
280
|
+
future.is_victory # Did someone win?
|
|
281
|
+
future.winner # Who won?
|
|
282
|
+
|
|
283
|
+
return column_index # 0-6
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## Built-in Bots
|
|
289
|
+
|
|
290
|
+
### `RandomBot`
|
|
291
|
+
|
|
292
|
+
Plays random valid moves. Useful for testing.
|
|
293
|
+
|
|
294
|
+
```python
|
|
295
|
+
from pingv4 import RandomBot
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### `MinimaxBot`
|
|
299
|
+
|
|
300
|
+
Strong AI using minimax with alpha-beta pruning.
|
|
301
|
+
|
|
302
|
+
```python
|
|
303
|
+
from pingv4 import MinimaxBot
|
|
304
|
+
|
|
305
|
+
# Default depth is 6
|
|
306
|
+
game = Connect4Game(player1=MinimaxBot, player2=MinimaxBot)
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Features:
|
|
310
|
+
- Alpha-beta pruning
|
|
311
|
+
- Transposition tables (using `board.hash`)
|
|
312
|
+
- Center-preference move ordering
|
|
313
|
+
- Positional evaluation
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## Tips for Bot Development
|
|
318
|
+
|
|
319
|
+
### Use the Hash for Caching
|
|
320
|
+
|
|
321
|
+
The `board.hash` property is highly optimized. Use it for transposition tables:
|
|
322
|
+
|
|
323
|
+
```python
|
|
324
|
+
cache = {}
|
|
325
|
+
|
|
326
|
+
def evaluate(board):
|
|
327
|
+
if board.hash in cache:
|
|
328
|
+
return cache[board.hash]
|
|
329
|
+
|
|
330
|
+
score = expensive_calculation(board)
|
|
331
|
+
cache[board.hash] = score
|
|
332
|
+
return score
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### Board is Immutable
|
|
336
|
+
|
|
337
|
+
`make_move()` returns a new board. The original is unchanged:
|
|
338
|
+
|
|
339
|
+
```python
|
|
340
|
+
board1 = ConnectFourBoard()
|
|
341
|
+
board2 = board1.make_move(3)
|
|
342
|
+
|
|
343
|
+
board1[3, 0] # None (unchanged)
|
|
344
|
+
board2[3, 0] # CellState.Red
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### Column-Major Access
|
|
348
|
+
|
|
349
|
+
Remember: it's `board[column, row]`, not `board[row, column]`.
|
|
350
|
+
|
|
351
|
+
```python
|
|
352
|
+
# Check if column 3 has a red piece at the bottom
|
|
353
|
+
if board[3, 0] == CellState.Red:
|
|
354
|
+
...
|
|
355
|
+
```
|