Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can't run Python's documentation example #2

Open
RemiFabre opened this issue Jan 24, 2024 · 3 comments
Open

Can't run Python's documentation example #2

RemiFabre opened this issue Jan 24, 2024 · 3 comments

Comments

@RemiFabre
Copy link

When trying to run this code:

import random

from ceramic.game import Action, Game, GameHelper, Player
from ceramic.players import RandomPlayer
from ceramic.rules import Rules
from ceramic.state import Tile


class TestPlayer(Player):
    def __init__(self):
        Player.__init__(self)

    def play(self, state):
        special_action = Action(1, Tile.from_letter("B"), 3)
        if GameHelper.legal(special_action, state):
            return special_action
        legal_actions = GameHelper.all_legal(state)
        return random.choice(legal_actions)


game = Game(Rules.BASE)
game.add_player(TestPlayer())
game.add_players([RandomPlayer() for i in range(0, 3)])
game.roll_game()  # Plays a random game until the end
print("The winner is:", game.state.winning_player())
print(f"Game state: {game.state}")

I get this error:

    game.roll_game()  # Plays a random game until the end
RuntimeError: Tried to call pure virtual function "Player::play"

When calling "roll_round", the code runs but the state is empty, e.g:

Score: 0
[abcde] [ ]
[eabcd] [  ]
[deabc] [   ]
[cdeab] [    ]
[bcdea] [     ]
Floor: 0 (-0)

tox did work (although I'm using python 3.10). Any ideas about what is happening? Maybe the Python binding is failing?

@Swynfel
Copy link
Owner

Swynfel commented Jan 30, 2024

Hello (again)!

Ok, this is weird, and if the tests worked it means tests/game/test_game.py#L58 worked, which should test exactly this. It's probably due to weird a behaviour of pybind11? Maybe things changed in the last years?

It might be that TestPlayer() is destroyed in python once going into add_player (a c++ method), and then the reference in c++ doesn't know it had overriden methods, unlike in the test where it keeps a reference in python (python_random_player = PythonRandomPlayer())...

I will investigate

@Swynfel
Copy link
Owner

Swynfel commented Jan 30, 2024

So, this came from a very interesting behaviour of pybind11 (most information can be found in #pybind/pybind11/pull/2839), and indeed the work-around is to store all python players in python while they are used in a game. Namely:

import random

from ceramic.game import Action, Game, GameHelper, Player
from ceramic.players import RandomPlayer
from ceramic.rules import Rules
from ceramic.state import Tile


class TestPlayer(Player):
    def __init__(self):
        Player.__init__(self)

    def play(self, state):
        special_action = Action(1, Tile.from_letter("B"), 3)
        if GameHelper.legal(special_action, state):
            return special_action
        legal_actions = GameHelper.all_legal(state)
        return random.choice(legal_actions)


game = Game(Rules.BASE)
test_player = TestPlayer() # <-- here
game.add_player(test_player)
game.add_players([RandomPlayer() for i in range(0, 3)])
game.roll_game()  # Plays a random game until the end
print("The winner is:", game.state.winning_player())
print(f"Game state: {game.state}")

I will try to implement a better work-around directly in c++ following the comments in the thread, or take the not-yet-merged "smart_holder" branch of pybind11 in the building pipeline (will be easier to do once moved to pyproject.toml)

@RemiFabre
Copy link
Author

It works, great!
Thanks a lot

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants