Skip to content

Commit

Permalink
Prepare repo for PyPI release (#5)
Browse files Browse the repository at this point in the history
* Add features to make the package more usable

* Prepare package for PyPI release

* Add Travis CI

* Add pip requirements

* Add Travis CI badge

* Fix setup.py

* Add PyPI to README
  • Loading branch information
neelsomani authored Apr 26, 2020
1 parent 3bbf0e7 commit 0148ec5
Show file tree
Hide file tree
Showing 18 changed files with 107 additions and 38 deletions.
6 changes: 6 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
language: python
python:
- "3.6"
install:
- pip install -r requirements.txt
script: pytest
File renamed without changes.
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
# Literature
![Travis CI](https://travis-ci.org/neelsomani/literature.svg?branch=master)

Literature card game implementation: https://en.wikipedia.org/wiki/Literature_(card_game)

## Setup

Install with `pip install literature`.

Start with `python3 -i literature.py`. Built for Python 3.6.0.

See how to train a model to play against with `python3 learning.py -h`. Play against a model that I trained with `learning.play_against_model('model_10000.out')`.

Current limitations:
## Limitations

* The bots only consider asking for a `Card` that they know a `Player` does not possess in the case that there are no other possible `Moves`. I made this simplification because the initial training took too long otherwise.
* The `Players` consider what other `Players` know about them, but they don't consider any levels further than that, e.g., the `Players` don't consider what other `Players` know that other `Players` know about them.
* There is definitely information there that is not represented in the current state. For example, player 0 might have all of the minor hearts except for the 5 of hearts. If player 1 asks for a minor hearts, player 0 knows that player 1 does not know that player 0 knows that player 1 has the 5 of hearts. Alternately, if player 0 had previously revealed that they have every other minor heart, then player 0 would know that player 1 knows that player 0 knows what card they have.
Expand Down
13 changes: 13 additions & 0 deletions literature/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from literature.actor import Actor
from literature.card import Card, deserialize, HalfSuit, Rank, Suit
from literature.constants import *
from literature.knowledge import ConcreteKnowledge, Knowledge
from literature.learning import (
GameHandler,
Model,
model_from_file,
play_against_model
)
from literature.literature import get_game, Literature, Team
from literature.move import Move, Request
from literature.player import Player
2 changes: 1 addition & 1 deletion actor.py → literature/actor.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from functools import total_ordering
from typing import Union, Set

from card import Card
from literature.card import Card


@total_ordering
Expand Down
2 changes: 1 addition & 1 deletion card.py → literature/card.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
Union
)

from constants import (
from literature.constants import (
Half,
MINOR,
MAJOR,
Expand Down
4 changes: 2 additions & 2 deletions constants.py → literature/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ def __lt__(self, other):
}

# Convenience constants
MINOR = SETS[Half.MINOR]
MAJOR = SETS[Half.MAJOR]
MINOR: Set[int] = SETS[Half.MINOR]
MAJOR: Set[int] = SETS[Half.MAJOR]

RANK_NAMES = {i: str(i) for i in range(2, 11)}
RANK_NAMES[1] = "A"
Expand Down
4 changes: 2 additions & 2 deletions knowledge.py → literature/knowledge.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
Classes to express whether `Players` have or don't have particular `Cards`.
"""

from card import Card
from move import Actor
from literature.card import Card
from literature.move import Actor


class ConcreteKnowledge:
Expand Down
12 changes: 6 additions & 6 deletions learning.py → literature/learning.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@
import numpy as np
from sklearn.neural_network import MLPRegressor

from actor import Actor
from card import Card, Suit
from constants import Half, SETS
from literature import get_game, Team
from move import Move
from player import Player
from literature.actor import Actor
from literature.card import Card, Suit
from literature.constants import Half, SETS
from literature.literature import get_game, Team
from literature.move import Move
from literature.player import Player

# The number of elements in the serialized move
MOVE_LENGTH = 1149
Expand Down
20 changes: 12 additions & 8 deletions literature.py → literature/literature.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,19 +57,18 @@
from typing import Callable, Dict, List
import random

from actor import Actor
from card import (
from literature.actor import Actor
from literature.card import (
Card,
deserialize,
get_hands,
HalfSuit,
Rank,
Suit
)
from constants import Half, SETS
from move import Move
from player import Player
from util import PrintableDict
from literature.constants import Half, SETS
from literature.move import Move
from literature.player import Player
from literature.util import PrintableDict


class Team(Enum):
Expand Down Expand Up @@ -107,14 +106,16 @@ def __init__(self,
hands = hands_fn(n_players)
self.players = [Player(i, hands[i], n_players)
for i in range(n_players)]

self.turn: Player = self.players[int(turn_picker() * n_players)]
self.logger = logging.getLogger(__name__)
# `self.claims` maps `HalfSuits` to the `Team` who successfully made
# the claim.
self.claims = PrintableDict({
HalfSuit(h, s): Team.NEITHER for h in Half for s in Suit
})
# `self.actual_possessions` saves the correct mapping of cards for
# a `HalfSuit`
self.actual_possessions: Dict[HalfSuit, Dict[Card.Name, Actor]] = {}
self.move_ledger: List[Move] = []
self.move_success: List[bool] = []

Expand Down Expand Up @@ -226,10 +227,13 @@ def commit_claim(self,
claimed = set()
_random_key = list(possessions.keys())[0]
half_suit = _random_key.half_suit()
if half_suit in self.actual_possessions:
raise ValueError('{} has already been claimed'.format(half_suit))

# Once a claim is submitted, all players must show the cards they
# have for that half suit
actual = self._claim_for_half_suit(half_suit)
self.actual_possessions[half_suit] = actual
for p in self.players:
p.memorize_claim(actual)

Expand Down
File renamed without changes.
6 changes: 3 additions & 3 deletions move.py → literature/move.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

from typing import List

from actor import Actor
from card import Card
from constants import SETS
from literature.actor import Actor
from literature.card import Card
from literature.constants import SETS


class Move:
Expand Down
16 changes: 10 additions & 6 deletions player.py → literature/player.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,24 @@
Union
)

from actor import Actor
from card import (
from literature.actor import Actor
from literature.card import (
Card,
HalfSuit,
Suit
)
from constants import (
from literature.constants import (
Half,
MINOR,
MAJOR,
SETS
)
from knowledge import (
from literature.knowledge import (
ConcreteKnowledge,
Knowledge
)
from move import Move, Request
from util import PrintableDict
from literature.move import Move, Request
from literature.util import PrintableDict


class Player(Actor):
Expand Down Expand Up @@ -100,6 +100,10 @@ def __init__(self,
self._memorize(Knowledge.that(self).has(card))
self.suit_knowledge[self][card.half_suit()] += 1

def unclaimed_cards(self) -> int:
""" Return the number of unclaimed cards this Player has. """
return len([c for c in self.hand if c.half_suit() not in self.claims])

def hand_to_dict(self) -> PrintableDict:
""" Get a `PrintableDict` of this `Player`'s hand. """
suits: Dict[Suit, List[Card.Name]] = {s: [] for s in Suit}
Expand Down
10 changes: 6 additions & 4 deletions test_game.py → literature/test_game.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@

import pytest

from actor import Actor
from card import (
from literature.actor import Actor
from literature.card import (
Card,
Suit,
HalfSuit,
Half
)
from constants import SETS
from literature import Literature, Team
from literature.constants import SETS
from literature.literature import Literature, Team

MISSING_CARD = Card.Name(3, Suit.CLUBS)

Expand Down Expand Up @@ -49,6 +49,8 @@ def test_turn_change(game):
c = claims_1.pop(HalfSuit(Half.MAJOR, Suit.DIAMONDS))
game.commit_claim(Actor(1), c)
assert game.turn == Actor(1)
with pytest.raises(ValueError):
game.commit_claim(Actor(1), c)


def test_end_game_condition(game):
Expand Down
8 changes: 4 additions & 4 deletions test_player.py → literature/test_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@

import pytest

from actor import Actor
from card import (
from literature.actor import Actor
from literature.card import (
Card,
Suit,
HalfSuit,
Half
)
from constants import SETS
from literature import Literature, Team
from literature.constants import SETS
from literature.literature import Literature, Team


def get_mock_hands(_: int) -> List[List[Card.Name]]:
Expand Down
File renamed without changes.
2 changes: 2 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[metadata]
description-file = README.md
31 changes: 31 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from distutils.core import setup
setup(
name='literature',
packages=['literature'],
version='0.1.0',
license='MIT',
description='Literature card game implementation',
author='Neel Somani',
author_email='neeljaysomani@gmail.com',
url='https://github.com/neelsomani/literature',
download_url='https://github.com/neelsomani/literature/releases',
keywords=[
'machine-learning',
'q-learning',
'neural-network',
'artificial-intelligence',
'card-game'
],
install_requires=[
'numpy==1.17.0',
'pytest==5.0.1',
'scikit-learn==0.21.3'
],
classifiers=[
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
'Topic :: Games/Entertainment',
'License :: OSI Approved :: MIT License',
'Programming Language :: Python :: 3.6'
],
)

0 comments on commit 0148ec5

Please sign in to comment.