Skip to content

Commit 61b797a

Browse files
committed
Main loop for Python bots, example Python bot
1 parent 5e923ba commit 61b797a

File tree

9 files changed

+124
-6
lines changed

9 files changed

+124
-6
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

+11-3
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,19 @@ See the `--help` text of the judge for more options.
2020
The protocol that the bots use to play consists of JSON requests and responses via standard input/output, with the judge being the client and the bots being the servers.
2121
For instance, the judge will send the bot a JSON message on its stdin asking it to choose up to five cards to play, and the bots will reply via stdout with a JSON message of its own.
2222

23-
You can either
23+
You can either implement this protocol yourself in any language you want, or use one of the existing libraries.
2424

25-
If you use the [Rust](gomori), [Python](gomori-py) or [C#](https://github.com/phwitti/gomori-bot-csharp-template) helper packages, the protocol and game logic is already implemented for you. See their READMEs for more information.
25+
### Option A: Using one of the libraries
2626

27-
### JSON protocol
27+
There are libraries for:
28+
29+
* [Rust](gomori)
30+
* [Python](gomori-py)
31+
* [C#](https://github.com/phwitti/gomori-bot-csharp-template)
32+
33+
They implement the protocol and game logic for you. See their READMEs for more information.
34+
35+
### Option B: Implementing the JSON protocol
2836

2937
To see what the messages look like, you can run the judge with `--log-level trace`.
3038
Messages are newline-delimited, i.e. the JSON must be in a compact format with no newlines, and followed by a newline.

bots/schwarzenegger_bot.json

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"nick": "SchwarzeneggerBot",
3+
"cmd": [".venv/bin/python3", "bots/schwarzenegger_bot.py"]
4+
}

bots/schwarzenegger_bot.py

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from gomori import *
2+
import sys
3+
4+
class SchwarzeneggerBot(Bot):
5+
def new_game(self, color: Color):
6+
pass
7+
8+
def play_first_turn(self, cards: List[Card]) -> Card:
9+
print("I was elected to lead, not to read. Number 3!", file=sys.stderr)
10+
return cards[2]
11+
12+
def play_turn(
13+
self,
14+
cards: List[Card],
15+
board: Board,
16+
cards_won_by_opponent: CardsSet
17+
) -> PlayTurnResponse:
18+
ctp = CardToPlay(card=cards[2], i=0, j=0)
19+
return PlayTurnResponse([ctp])
20+
21+
run_bot(SchwarzeneggerBot())

gomori-py/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ doc = false
1212
[dependencies]
1313
pyo3 = "0.18.1"
1414
gomori = { path = "../gomori", features = ["python"] }
15+
gomori_bot_utils = { path = "../gomori_bot_utils" }

gomori-py/python/gomori/__init__.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
from gomori._gomori import *
22
from typing import List
33

4-
class Bot():
4+
import json
5+
6+
class Bot:
57
def new_game(self, color: Color):
68
raise NotImplementedError()
79

8-
def play_first_turn(cards: List[Card]) -> Card:
10+
def play_first_turn(self, cards: List[Card]) -> Card:
911
raise NotImplementedError()
1012

1113
def play_turn(
14+
self,
1215
cards: List[Card],
1316
board: Board,
1417
cards_won_by_opponent: CardsSet

gomori-py/src/bot.rs

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
use gomori::{Board, Card, CardsSet, Color, Field, PlayTurnResponse};
2+
use gomori_bot_utils::Bot;
3+
use pyo3::{pyfunction, types::PyDict, Py, PyObject, Python};
4+
5+
struct PythonBot {
6+
bot: PyObject,
7+
}
8+
9+
// TODO: Re-evaluate this whole design
10+
impl gomori_bot_utils::Bot for PythonBot {
11+
fn new_game(&mut self, color: Color) {
12+
Python::with_gil(|py| {
13+
let kwargs = PyDict::new(py);
14+
kwargs
15+
.set_item("color", Py::new(py, color).unwrap())
16+
.unwrap();
17+
self.bot
18+
.call_method(py, "new_game", (), Some(kwargs))
19+
.expect("Call to new_game() failed");
20+
})
21+
}
22+
23+
fn play_first_turn(&mut self, cards: [Card; 5]) -> Card {
24+
Python::with_gil(|py| {
25+
let kwargs = PyDict::new(py);
26+
kwargs
27+
.set_item("cards", cards.map(|card| Py::new(py, card).unwrap()))
28+
.unwrap();
29+
self.bot
30+
.call_method(py, "play_first_turn", (), Some(kwargs))
31+
.expect("Call to play_first_turn() failed")
32+
.extract(py)
33+
.expect("play_first_turn() returned wrong type")
34+
})
35+
}
36+
37+
fn play_turn(
38+
&mut self,
39+
cards: [Card; 5],
40+
fields: Vec<Field>,
41+
cards_won_by_opponent: CardsSet,
42+
) -> PlayTurnResponse {
43+
Python::with_gil(|py| {
44+
let kwargs = PyDict::new(py);
45+
kwargs
46+
.set_item("cards", cards.map(|card| Py::new(py, card).unwrap()))
47+
.unwrap();
48+
kwargs
49+
.set_item("board", Py::new(py, Board::new(&fields)).unwrap())
50+
.unwrap();
51+
kwargs
52+
.set_item(
53+
"cards_won_by_opponent",
54+
Py::new(py, cards_won_by_opponent).unwrap(),
55+
)
56+
.unwrap();
57+
self.bot
58+
.call_method(py, "play_turn", (), Some(kwargs))
59+
.expect("Call to play_turn() failed")
60+
.extract(py)
61+
.expect("play_turn() returned wrong type")
62+
})
63+
}
64+
}
65+
66+
#[pyfunction]
67+
pub fn run_bot(bot: PyObject) {
68+
PythonBot { bot }.run().unwrap()
69+
}

gomori-py/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use pyo3::prelude::*;
22

3+
mod bot;
4+
35
/// A Python module implemented in Rust.
46
#[pymodule]
57
#[pyo3(name = "_gomori")]
@@ -25,5 +27,6 @@ fn gomori(py: Python, m: &PyModule) -> PyResult<()> {
2527
m.add_class::<::gomori::PyCalculatedEffects>()?;
2628
m.add_class::<::gomori::Rank>()?;
2729
m.add_class::<::gomori::Suit>()?;
30+
m.add_function(wrap_pyfunction!(bot::run_bot, m)?)?;
2831
Ok(())
2932
}

gomori/src/protocol_types.rs

+9-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ mod python {
103103
impl CardToPlay {
104104
#[new]
105105
#[pyo3(signature = (*, card, i, j, target_field_for_king_ability=None))]
106-
pub(crate) fn py_new(
106+
fn py_new(
107107
card: Card,
108108
i: i8,
109109
j: i8,
@@ -117,4 +117,12 @@ mod python {
117117
}
118118
}
119119
}
120+
121+
#[pymethods]
122+
impl PlayTurnResponse {
123+
#[new]
124+
fn py_new(cards_to_play: Vec<CardToPlay>) -> Self {
125+
Self(cards_to_play)
126+
}
127+
}
120128
}

0 commit comments

Comments
 (0)