diff --git a/.gitignore b/.gitignore index 8b972d9..69cde2e 100644 --- a/.gitignore +++ b/.gitignore @@ -100,6 +100,6 @@ ENV/ # mypy .mypy_cache/ -#pytest +# pytest .pytest_cache .idea/ diff --git a/.travis.yml b/.travis.yml index 4d285ec..93754e9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,4 +20,5 @@ deploy: on: branch: master python: '3.6' + # tags: true # only deploys it when the commit is tagged skip_existing: true \ No newline at end of file diff --git a/LICENSE b/LICENSE index d03affe..dc8071c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,7 @@ MIT License Copyright (c) 2017 Samuel Kurath +Copyright (c) 2018 Joel Niklaus Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 919a81a..72fdd00 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,28 @@ -[![Build Status](https://travis-ci.org/Murthy10/pyschieber.svg?branch=master)](https://travis-ci.org/Murthy10/pyschieber) +[![Build Status](https://travis-ci.org/JoelNiklaus/schieber.svg?branch=master)](https://travis-ci.org/JoelNiklaus/schieber) -# pyschieber -Pyschieber is an implementation of the well known Swiss Schieber Jass game. +# schieber +Schieber is an implementation of the well known Swiss Schieber Jass game. As OpenAI Gym provides APIs for several popular games to learn your algorithms master these games. -Pyschieber aims to offer an API in the same manner. +Schieber aims to offer an API in the same manner. ## Usage -To install pyschieber, simply: +To install schieber, simply: ```bash -pip install pyschieber +pip install schieber ``` -pyschieber officially supports Python 3.4, 3.5, 3.6, 3.7, 3.5-dev, 3.6-dev, 3.7-dev, nightly and PyPy3. +schieber officially supports Python 3.4, 3.5, 3.6, 3.7, 3.5-dev, 3.6-dev, 3.7-dev, nightly and PyPy3. ### CLI :computer: -Beside of the API, pyschieber provides a CLI client to play the funny Scheiber Jass game. +Beside of the API, schieber provides a CLI client to play the funny Scheiber Jass game. Currently your opponent will be a bot choosing a random card. -After the pip installation you could run the ```pyschieber``` command on the console to play a game: +After the pip installation you could run the ```schieber``` command on the console to play a game: ```bash -$ pyschieber +$ schieber Tournament starts, the goal are 1500 points. -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Round 1 starts. @@ -56,34 +56,34 @@ Please chose the trumpf by the number from 0 to 6: ### Jass Challenge The usage of a CLI to play Schieber Jass could be boring. -Therefore pyschieber provides a wrapper for your bots to play on the Zühlke Jass Server. +Therefore schieber provides a wrapper for your bots to play on the Zühlke Jass Server. -The [ServerPlayer](pyschieber/player/server_player/server_player.py) takes a pyschieber conform player -An example how to launch is provide under [Server Launcher](pyschieber/example/server_launcher.py). +The [ServerPlayer](schieber/player/server_player/server_player.py) takes a schieber conform player +An example how to launch is provide under [Server Launcher](schieber/example/server_launcher.py). For further information have a look at: * https://github.com/webplatformz/challenge * https://github.com/jakeret/elbotto ## API :clipboard: -The idea of pyschieber is to extend the game with your own implemented player. -Henc schieber provides entry points to fulfill this requirement. +The idea of schieber is to extend the game with your own implemented player. +Hence schieber provides entry points to fulfill this requirement. -## Environemnt introduction -To get a first feeling for the pyschieber playground let's have a look at a runable example. +## Environment introduction +To get a first feeling for the schieber playground let's have a look at a runable example. 1. The first thing you have to do, is to instantiate a new Tournament. ```python -from pyschieber.tournament import Tournament +from schieber.tournament import Tournament tournament = Tournament(point_limit=1500) ``` 2. Add the players to your tournament. In our example we use the erratic RandomPlayers Tick, Trick, Track and the GreedyPlayer Dagobert. ```python -from pyschieber.player.random_player import RandomPlayer -from pyschieber.player.greedy_player.greedy_player import GreedyPlayer +from schieber.player.random_player import RandomPlayer +from schieber.player.greedy_player.greedy_player import GreedyPlayer players = [RandomPlayer(name='Tick'), RandomPlayer(name='Trick'), @@ -117,8 +117,8 @@ To get more familiar with this concept let's have a look at the already mentione ```python import random -from pyschieber.player.base_player import BasePlayer -from pyschieber.trumpf import Trumpf +from schieber.player.base_player import BasePlayer +from schieber.trumpf import Trumpf class RandomPlayer(BasePlayer): @@ -142,11 +142,12 @@ What's going on here? The Random Player is pretty naive and he simply chooses randomly a card or a trumpf from the list of choices. If the turn is not allowed he randomly chooses a new one until the rules of Schieber are satisfied. -Other player examples are the [GreedyPlayer](pyschieber/player/greedy_player/greedy_player.py) or the [CliPlayer](pyschieber/player/cli_player.py). +Other player examples are the [GreedyPlayer](schieber/player/greedy_player/greedy_player.py) or the [CliPlayer](schieber/player/cli_player.py). Now you should be ready to get your hands dirty to implement your own player and beat the random players Tick, Trick and Track! :trophy: ## Enhancements * Add Wiesen to the game * Beautify the CLI :trollface: -* Provide a simple network player \ No newline at end of file +* Provide a simple network player +* Implement Matschbonus! \ No newline at end of file diff --git a/bin/pyschieber b/bin/schieber similarity index 84% rename from bin/pyschieber rename to bin/schieber index f3e37e3..028d56b 100644 --- a/bin/pyschieber +++ b/bin/schieber @@ -2,11 +2,11 @@ import sys, signal, argparse, logging -from pyschieber.player.cli_player import CliPlayer -from pyschieber.player.challenge_player.challenge_player import ChallengePlayer -from pyschieber.player.greedy_player.greedy_player import GreedyPlayer -from pyschieber.player.random_player import RandomPlayer -from pyschieber.tournament import Tournament +from schieber.player.cli_player import CliPlayer +from schieber.player.challenge_player.challenge_player import ChallengePlayer +from schieber.player.greedy_player.greedy_player import GreedyPlayer +from schieber.player.random_player import RandomPlayer +from schieber.tournament import Tournament def parse_player_choice(player_choice=1, name_suffix=''): @@ -36,14 +36,14 @@ def set_logging(): def handler(signum, frame): - print('\n pyschieber terminated! Goodbye!') + print('\n schieber terminated! Goodbye!') sys.exit(0) if __name__ == "__main__": set_logging() signal.signal(signal.SIGINT, handler) - parser = argparse.ArgumentParser(description='CLI pyschieber', ) + parser = argparse.ArgumentParser(description='CLI schieber', ) parser.add_argument('-p', '--points', dest='points', type=int, help='Tournament points') parser.add_argument('-f', '--first_opponent', dest='first_opponent', type=int, help='Chose your first opponent. (1: Random player, 2: Greedy player, 3: Challenge player)') diff --git a/pyschieber/card.py b/pyschieber/card.py deleted file mode 100644 index 32b310d..0000000 --- a/pyschieber/card.py +++ /dev/null @@ -1,58 +0,0 @@ -import re - -from pyschieber.suit import Suit - - -class Card: - names = {6: '6', 7: '7', 8: '8', 9: '9', 10: 'Banner', 11: 'Under', 12: 'Ober', 13: 'Koennig', 14: 'Ass'} - trumpf_rank = {6: 6, 7: 7, 8: 8, 10: 10, 12: 12, 13: 13, 14: 14, 9: 15, 11: 16} - format_string = '<{0}:{1}>' - - def __init__(self, suit, value): - self.suit = suit - self.value = value - - def __lt__(self, other): - return self.value < other.value - - def __eq__(self, other): - return self.suit == other.suit and self.value == other.value - - def __hash__(self): - return hash(str(self)) - - def __str__(self): - name = str(self.value) - if self.value > 9: - name = Card.names[self.value] - return self.format_string.format(self.suit.name, name) - - def __repr__(self): - return str(self) - - def get_trumpf_rank(self): - return self.trumpf_rank[self.value] - - def is_higher_trumpf_than(self, other): - return self.trumpf_rank > other.trumpf_rank - - def is_higher_than(self, other): - return self.suit == other.suit and self.value > other.value - - def get_score(self, trumpf): - if trumpf.name == self.suit.name: - return 50 + self.get_trumpf_rank() - else: - return self.value - - -def from_string_to_card(card_string): - regex = re.sub(r'{(.+?)}', r'(?P<_\1>.+)', Card.format_string) - values = list(re.search(regex, card_string).groups()) - suit = Suit[values[0]] - card_value = '' - for key, value in Card.names.items(): - if value == values[1]: - card_value = key - break - return Card(suit=suit, value=card_value) diff --git a/pyschieber/dealer.py b/pyschieber/dealer.py deleted file mode 100644 index a8b89ad..0000000 --- a/pyschieber/dealer.py +++ /dev/null @@ -1,16 +0,0 @@ -from random import shuffle - -from pyschieber.deck import Deck - - -class Dealer: - def __init__(self, players): - self.players = players - self.deck = Deck() - - def shuffle_cards(self): - shuffle(self.deck.cards) - - def deal_cards(self): - for i, card in enumerate(self.deck.cards): - self.players[i % 4].set_card(card=card) diff --git a/pyschieber/game.py b/pyschieber/game.py deleted file mode 100644 index b86373c..0000000 --- a/pyschieber/game.py +++ /dev/null @@ -1,110 +0,0 @@ -import logging - -from pyschieber.dealer import Dealer -from pyschieber.rules.stich_rules import stich_rules, card_allowed -from pyschieber.rules.trumpf_rules import trumpf_allowed -from pyschieber.rules.count_rules import count_stich, counting_factor -from pyschieber.stich import PlayedCard, stich_dict, played_cards_dict -from pyschieber.trumpf import Trumpf - -logger = logging.getLogger(__name__) - - -class Game: - def __init__(self, teams=None, point_limit=1500, use_counting_factor=True): - self.teams = teams - self.point_limit = point_limit - self.players = [teams[0].players[0], teams[1].players[0], teams[0].players[1], teams[1].players[1]] - self.dealer = Dealer(players=self.players) - self.geschoben = False - self.trumpf = None - self.stiche = [] - self.cards_on_table = [] - self.use_counting_factor = use_counting_factor - - def play(self, start_player_index=0, whole_rounds=False): - self.dealer.shuffle_cards() - self.dealer.deal_cards() - self.define_trumpf(start_player_index=start_player_index) - logger.info('Chosen Trumpf: {0} \n'.format(self.trumpf.name)) - for i in range(9): - stich = self.play_stich(start_player_index) - self.count_points(stich, last=(i == 8)) - logger.info('\nStich: {0} \n'.format(stich.player)) - logger.info('{}{}\n'.format('-' * 180, self.trumpf)) - start_player_index = self.players.index(stich.player) - self.stiche.append(stich) - self.stich_over_information() - if (self.teams[0].won(self.point_limit) or self.teams[1].won(self.point_limit)) and not whole_rounds: - return True - return False - - def define_trumpf(self, start_player_index): - is_allowed_trumpf = False - generator = self.players[start_player_index].choose_trumpf(geschoben=self.geschoben) - chosen_trumpf = next(generator) - if chosen_trumpf == Trumpf.SCHIEBEN: - self.geschoben = True - generator = self.players[(start_player_index + 2) % 4].choose_trumpf(geschoben=self.geschoben) - chosen_trumpf = next(generator) - while not is_allowed_trumpf: - is_allowed_trumpf = trumpf_allowed(chosen_trumpf=chosen_trumpf, geschoben=self.geschoben) - trumpf = generator.send(is_allowed_trumpf) - chosen_trumpf = chosen_trumpf if trumpf is None else trumpf - self.trumpf = chosen_trumpf - - def play_stich(self, start_player_index): - self.cards_on_table = [] - first_card = self.play_card(table_cards=self.cards_on_table, player=self.players[start_player_index]) - self.move_made(self.players[start_player_index].id, first_card) - self.cards_on_table = [PlayedCard(player=self.players[start_player_index], card=first_card)] - for i in get_player_index(start_index=start_player_index): - current_player = self.players[i] - card = self.play_card(table_cards=self.cards_on_table, player=current_player) - self.move_made(current_player.id, card) - self.cards_on_table.append(PlayedCard(player=current_player, card=card)) - stich = stich_rules[self.trumpf](played_cards=self.cards_on_table) - return stich - - def play_card(self, table_cards, player): - cards = [played_card.card for played_card in table_cards] - is_allowed_card = False - generator = player.choose_card(state=self.get_status()) - chosen_card = next(generator) - while not is_allowed_card: - is_allowed_card = card_allowed(table_cards=cards, chosen_card=chosen_card, hand_cards=player.cards, - trumpf=self.trumpf) - card = generator.send(is_allowed_card) - chosen_card = chosen_card if card is None else card - else: - logger.info('Table: {0}:{1}'.format(player, chosen_card)) - player.cards.remove(chosen_card) - return chosen_card - - def move_made(self, player_id, card): - for player in self.players: - player.move_made(player_id, card, self.get_status()) - - def stich_over_information(self): - [player.stich_over(state=self.get_status()) for player in self.players] - - def count_points(self, stich, last): - stich_player_index = self.players.index(stich.player) - cards = [played_card.card for played_card in stich.played_cards] - self.add_points(team_index=(stich_player_index % 2), cards=cards, last=last) - - def add_points(self, team_index, cards, last): - points = count_stich(cards, self.trumpf, last=last) - points = points * counting_factor[self.trumpf] if self.use_counting_factor else points - self.teams[team_index].points += points - - def get_status(self): - return dict(stiche=[stich_dict(stich) for stich in self.stiche], trumpf=self.trumpf.name, - geschoben=self.geschoben, point_limit=self.point_limit, - table=[played_cards_dict(played_card) for played_card in self.cards_on_table], - teams=[dict(points=team.points) for team in self.teams]) - - -def get_player_index(start_index): - for i in range(1, 4): - yield (i + start_index) % 4 diff --git a/pyschieber/player/base_player.py b/pyschieber/player/base_player.py deleted file mode 100644 index 1b974b0..0000000 --- a/pyschieber/player/base_player.py +++ /dev/null @@ -1,39 +0,0 @@ -import inspect - -from pyschieber.card import from_string_to_card -from pyschieber.trumpf import Trumpf -from pyschieber.rules.stich_rules import allowed_cards - - -class BasePlayer: - def __init__(self, name='unknown'): - self.name = name - self.cards = [] - self.trumpf_list = list(Trumpf) - self.id = None - - def get_dict(self): - return dict(name=self.name, type=type(self).__name__) - - def set_card(self, card): - self.cards.append(card) - - def choose_trumpf(self, geschoben): - raise NotImplementedError(str(inspect.stack()[1][3])) - - def choose_card(self, state=None): - raise NotImplementedError(str(inspect.stack()[1][3])) - - def move_made(self, player_id, card, state): - pass - - def stich_over(self, state=None): - pass - - def allowed_cards(self, state): - table_cards = [from_string_to_card(entry['card']) for entry in state['table']] - trumpf = Trumpf[state['trumpf']] - return allowed_cards(hand_cards=self.cards, table_cards=table_cards, trumpf=trumpf) - - def __str__(self): - return ''.format(self.name) diff --git a/pyschieber/player/random_player.py b/pyschieber/player/random_player.py deleted file mode 100644 index f713ba9..0000000 --- a/pyschieber/player/random_player.py +++ /dev/null @@ -1,22 +0,0 @@ -import random - -from pyschieber.player.base_player import BasePlayer -from pyschieber.trumpf import Trumpf - - -class RandomPlayer(BasePlayer): - def choose_trumpf(self, geschoben): - return move(choices=list(Trumpf)) - - def choose_card(self, state=None): - cards = self.allowed_cards(state=state) - return move(choices=cards) - - -def move(choices): - allowed = False - while not allowed: - choice = random.choice(choices) - allowed = yield choice - if allowed: - yield None diff --git a/pyschieber/rules/trumpf_rules.py b/pyschieber/rules/trumpf_rules.py deleted file mode 100644 index 68c72b1..0000000 --- a/pyschieber/rules/trumpf_rules.py +++ /dev/null @@ -1,5 +0,0 @@ -from pyschieber.trumpf import Trumpf - - -def trumpf_allowed(chosen_trumpf, geschoben): - return not (chosen_trumpf == Trumpf.SCHIEBEN and geschoben) diff --git a/pyschieber/stich.py b/pyschieber/stich.py deleted file mode 100644 index f221406..0000000 --- a/pyschieber/stich.py +++ /dev/null @@ -1,19 +0,0 @@ -from collections import namedtuple - -PlayedCard = namedtuple('PlayedCard', ['player', 'card']) -Stich = namedtuple('Stich', ['player', 'played_cards', 'trumpf']) - - -def played_cards_dict(played_card): - return { - 'player_id': played_card.player.id, - 'card': str(played_card.card) - } - - -def stich_dict(stich): - return { - 'player_id': stich.player.id, - 'trumpf': stich.trumpf.name, - 'played_cards': [played_cards_dict(played_card) for played_card in stich.played_cards] - } diff --git a/pyschieber/suit.py b/pyschieber/suit.py deleted file mode 100644 index bc2f3e6..0000000 --- a/pyschieber/suit.py +++ /dev/null @@ -1,3 +0,0 @@ -from enum import Enum - -Suit = Enum('Suit',['ROSE', 'BELL', 'ACORN', 'SHIELD']) \ No newline at end of file diff --git a/pyschieber/team.py b/pyschieber/team.py deleted file mode 100644 index 7fd6b98..0000000 --- a/pyschieber/team.py +++ /dev/null @@ -1,13 +0,0 @@ -class Team: - def __init__(self, players=None): - self.points = 0 - self.players = players - - def player_by_number(self, number): - for player in self.players: - if player.number == number: - return player - return None - - def won(self, point_limit): - return self.points >= point_limit diff --git a/release.sh b/release.sh new file mode 100755 index 0000000..53754e0 --- /dev/null +++ b/release.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +# Check if version number has been incremented! + +rm -rf dist # Clean dist folder +python3 setup.py sdist bdist_wheel # Build package +gpg --detach-sign -a dist/* # Sign package +twine upload dist/* # Upload package and signature \ No newline at end of file diff --git a/pyschieber/__init__.py b/schieber/__init__.py similarity index 100% rename from pyschieber/__init__.py rename to schieber/__init__.py diff --git a/schieber/card.py b/schieber/card.py new file mode 100644 index 0000000..34871c5 --- /dev/null +++ b/schieber/card.py @@ -0,0 +1,160 @@ +import math +import re + +import numpy as np + +from schieber.suit import Suit + + +class Card: + """ + Defines a card used in the game of Jassen. + """ + names = {6: '6', 7: '7', 8: '8', 9: '9', 10: 'Banner', 11: 'Under', 12: 'Ober', 13: 'Koennig', 14: 'Ass'} + trumpf_rank = {6: 6, 7: 7, 8: 8, 10: 10, 12: 12, 13: 13, 14: 14, 9: 15, 11: 16} + format_string = '<{0}:{1}>' + + def __init__(self, suit, value): + self.suit = suit + self.value = value + + def __lt__(self, other): + return self.value < other.value + + def __eq__(self, other): + return self.suit == other.suit and self.value == other.value + + def __hash__(self): + return hash(str(self)) + + def __str__(self): + name = str(self.value) + if self.value > 9: + name = Card.names[self.value] + return self.format_string.format(self.suit.name, name) + + def __repr__(self): + return str(self) + + def get_trumpf_rank(self): + return self.trumpf_rank[self.value] + + def is_higher_trumpf_than(self, other): + return self.trumpf_rank > other.trumpf_rank + + def is_higher_than(self, other): + return self.suit == other.suit and self.value > other.value + + def get_score(self, trumpf): + if trumpf.name == self.suit.name: + return 50 + self.get_trumpf_rank() + else: + return self.value + + +# TODO transform these methods to @staticmethod in order to eliminate unnecessarily long imports + +def from_card_to_string(card): + return str(card) + + +def from_string_to_card(card_string): + """ + Converts a string representation of a card back to a card object. + :param card_string: + :return: + """ + regex = re.sub(r'{(.+?)}', r'(?P<_\1>.+)', Card.format_string) + values = list(re.search(regex, card_string).groups()) + suit = Suit[values[0]] + card_value = '' + for key, value in Card.names.items(): + if value == values[1]: + card_value = key + break + return Card(suit=suit, value=card_value) + + +def from_card_to_tuple(card): + return card.suit.value, card.value - 6 + + +def from_tuple_to_card(card_tuple): + return Card(suit=Suit(card_tuple[0]), value=card_tuple[1] + 6) + + +def from_string_to_tuple(card_string): + card = from_string_to_card(card_string) + return from_card_to_tuple(card) + + +def from_tuple_to_string(card_tuple): + card = from_tuple_to_card(card_tuple) + return from_card_to_string(card) + + +def from_string_to_index(card_string): + card = from_string_to_card(card_string) + return from_card_to_index(card) + + +def from_index_to_string(card_index): + card = from_index_to_card(card_index) + return from_card_to_string(card) + + +def from_index_to_card(card_index): + """ + The index is a number between 1 and 36 representing a card in the following way: + SUIT 6 7 8 9 Banner Under Ober Koennig Ass + ROSE 1 2 3 4 5 6 7 8 9 + BELL 10 11 12 13 14 15 16 17 18 + ACORN 19 20 21 22 23 24 25 26 27 + SHIELD 28 29 30 31 32 33 34 35 36 + An index of 0 denotes an empty Card --> None + :param card_index: + :return: + """ + assert 0 <= card_index <= 36 + if card_index == 0: + return None + return Card(suit=Suit(_get_suit(card_index)), value=_get_value(card_index)) + + +def from_card_to_index(card): + if card is None: + return 0 + return card.value + card.suit.value * 9 - 5 + + +def from_card_to_onehot(card): + suit_onehot = 4 * [0] + suit_onehot[card.suit.value] = 1 + value_onehot = 9 * [0] + value_onehot[card.value - 6] = 1 + return suit_onehot + value_onehot + + +def from_onehot_to_card(card_onehot): + ones = np.where(np.array(card_onehot) == 1)[0] # gets the indices where there is a one in the vector + suit = ones[0] # suit encoded in the first 4 bits (index 0 to 3) + value = ones[1] - 4 + 6 # value encoded in the following 9 bits (index 4 to 12) + return Card(Suit(suit), value) + + +def from_onehot_to_string(card_onehot): + card = from_onehot_to_card(card_onehot) + return from_card_to_string(card) + + +def from_string_to_onehot(card_string): + card = from_string_to_card(card_string) + return from_card_to_onehot(card) + + +def _get_suit(card_index): + return int(math.floor(card_index / 9.1)) + + +def _get_value(card_index): + return card_index - _get_suit(card_index) * 9 + 5 diff --git a/schieber/dealer.py b/schieber/dealer.py new file mode 100644 index 0000000..e56b629 --- /dev/null +++ b/schieber/dealer.py @@ -0,0 +1,26 @@ +import random + +from schieber.deck import Deck + + +class Dealer: + def __init__(self, players): + self.players = players + self.deck = Deck() + + def shuffle_cards(self, seed=None): + """ + Shuffles the cards to a random ordering. + :param seed + :return: + """ + random.seed(seed) + random.shuffle(self.deck.cards) + + def deal_cards(self): + """ + Deals 9 cards for every one of the 4 players participating in the game. + :return: + """ + for i, card in enumerate(self.deck.cards): + self.players[i % 4].set_card(card=card) diff --git a/pyschieber/deck.py b/schieber/deck.py similarity index 54% rename from pyschieber/deck.py rename to schieber/deck.py index da1b438..502c446 100644 --- a/pyschieber/deck.py +++ b/schieber/deck.py @@ -1,9 +1,12 @@ -from pyschieber.suit import Suit -from pyschieber.card import Card +from schieber.suit import Suit +from schieber.card import Card class Deck: def __init__(self): + """ + Initializes a deck of cards used for Jassen (from 6 to 10, Jack, Queen, King and Ace; each card in 4 suits) + """ self.cards = [] for suit in Suit: self.cards += [Card(suit=suit, value=i) for i in range(6, 15)] diff --git a/pyschieber/example/__init__.py b/schieber/example/__init__.py similarity index 100% rename from pyschieber/example/__init__.py rename to schieber/example/__init__.py diff --git a/pyschieber/example/main.py b/schieber/example/main.py similarity index 59% rename from pyschieber/example/main.py rename to schieber/example/main.py index ef361ef..8585710 100644 --- a/pyschieber/example/main.py +++ b/schieber/example/main.py @@ -1,7 +1,7 @@ -from pyschieber.player.challenge_player.challenge_player import ChallengePlayer -from pyschieber.player.greedy_player.greedy_player import GreedyPlayer -from pyschieber.player.random_player import RandomPlayer -from pyschieber.tournament import Tournament +from schieber.player.challenge_player.challenge_player import ChallengePlayer +from schieber.player.greedy_player.greedy_player import GreedyPlayer +from schieber.player.random_player import RandomPlayer +from schieber.tournament import Tournament def start_tournament(points): diff --git a/pyschieber/example/server_launcher.py b/schieber/example/server_launcher.py similarity index 90% rename from pyschieber/example/server_launcher.py rename to schieber/example/server_launcher.py index ec71511..026607a 100644 --- a/pyschieber/example/server_launcher.py +++ b/schieber/example/server_launcher.py @@ -2,8 +2,8 @@ import logging from multiprocessing import Process -from pyschieber.player.greedy_player.greedy_player import GreedyPlayer -from pyschieber.player.server_player.server_player import ServerPlayer +from schieber.player.greedy_player.greedy_player import GreedyPlayer +from schieber.player.server_player.server_player import ServerPlayer logging.basicConfig(level=logging.INFO, format='%(asctime)s %(name)-12s %(levelname)-8s %(message)s', ) diff --git a/schieber/game.py b/schieber/game.py new file mode 100644 index 0000000..736fb5b --- /dev/null +++ b/schieber/game.py @@ -0,0 +1,222 @@ +import logging +from multiprocessing import Condition + +from schieber.dealer import Dealer +from schieber.rules.stich_rules import stich_rules, card_allowed +from schieber.rules.trumpf_rules import trumpf_allowed +from schieber.rules.count_rules import count_stich, counting_factor +from schieber.stich import PlayedCard, stich_dict, played_card_dict +from schieber.trumpf import Trumpf + +logger = logging.getLogger(__name__) + + +class Game: + def __init__(self, teams=None, point_limit=1500, use_counting_factor=False, seed=None): + self.teams = teams + self.point_limit = point_limit + self.players = [teams[0].players[0], teams[1].players[0], teams[0].players[1], teams[1].players[1]] + self.dealer = Dealer(players=self.players) + self.geschoben = False + self.trumpf = None + self.stiche = [] + self.cards_on_table = [] + self.use_counting_factor = use_counting_factor + self.seed = seed + self.endless_play_control = Condition() # used to control the termination of the play_endless method + self.stop_playing = False # has to be set to true in order to stop the endless play + + def play_endless(self, start_player_index=0, whole_rounds=True): + """ + Plays one game after the other with no end. This can be used for training a RL Player. Like this we can reuse + one game. When we are training with tournaments, each time we play a game, it is added to the list of games. + This could result in very high unneeded memory usage. + :param start_player_index: + :param whole_rounds: + :return: + """ + while True: + logger.debug("start playing game") + self.play(start_player_index, whole_rounds) + logger.debug("game finished") + try: + self.endless_play_control.acquire() + # timeout in case something goes wrong in the reset, or reset is not called for any reason. + # In the normal case we just want to continue playing + received = self.endless_play_control.wait(0.01) + if received: + logger.debug("endless play received control message") + else: + logger.debug( + "endless play did not receive control message! Timeout occurred. Endless play resuming.") + if self.stop_playing: + logger.debug("stopping endless play") + break + finally: + self.endless_play_control.release() + logger.debug("reset game") + self.reset() + + def reset(self): + """ + Resets the game so that a new game can be started. Used in the endless mode + :return: + """ + self.reset_points() + self.stiche = [] + + def play(self, start_player_index=0, whole_rounds=False): + """ + Plays a game from the start to the end in the following manner: + 1. The dealer shuffles the cards + 2. The dealer deals 9 cards to each player + 3. The player on the right side of the dealer chooses the trumpf. If he/she chooses 'geschoben' his/her partner + can choose the trumpf. + 4. For 9 rounds/stichs let the players play their cards. + 5. After each stich count the points, update the starting player based on who won the stich and add the cards + played in the stich to the already played stichs. + 6. Check if a team has reached the point limit + :param start_player_index: + :param whole_rounds: + :return: + """ + if self.seed is not None: + # Increment seed by one so that each game is different. + # But still the sequence of games is the same each time + self.seed += 1 + self.dealer.shuffle_cards(self.seed) + self.dealer.deal_cards() + self.define_trumpf(start_player_index=start_player_index) + logger.info('Chosen Trumpf: {0} \n'.format(self.trumpf.name)) + for i in range(9): + stich = self.play_stich(start_player_index) + self.count_points(stich, last=(i == 8)) + logger.info('\nStich: {0} \n'.format(stich.player)) + logger.info('{}{}\n'.format('-' * 180, self.trumpf)) + start_player_index = self.players.index(stich.player) + self.stiche.append(stich) + self.stich_over_information() + if (self.teams[0].won(self.point_limit) or self.teams[1].won(self.point_limit)) and not whole_rounds: + return True + return False + + def define_trumpf(self, start_player_index): + """ + Sets the trumpf based on the choice of the player assigned to choose the trumpf + :param start_player_index: The player which is on the right side of the dealer + :return: + """ + is_allowed_trumpf = False + generator = self.players[start_player_index].choose_trumpf(geschoben=self.geschoben) + chosen_trumpf = next(generator) + if chosen_trumpf == Trumpf.SCHIEBEN: + self.geschoben = True + generator = self.players[(start_player_index + 2) % 4].choose_trumpf(geschoben=self.geschoben) + chosen_trumpf = next(generator) + while not is_allowed_trumpf: + is_allowed_trumpf = trumpf_allowed(chosen_trumpf=chosen_trumpf, geschoben=self.geschoben) + trumpf = generator.send(is_allowed_trumpf) + chosen_trumpf = chosen_trumpf if trumpf is None else trumpf + self.trumpf = chosen_trumpf + return self.trumpf + + def play_stich(self, start_player_index): + """ + Plays one entire stich + :param start_player_index: the index of the player who won the last stich or was assigned to choose the trumpf + :return: the stich containing the played cards and the winner + """ + self.cards_on_table = [] + first_card = self.play_card(table_cards=self.cards_on_table, player=self.players[start_player_index]) + self.move_made(self.players[start_player_index].id, first_card) + self.cards_on_table = [PlayedCard(player=self.players[start_player_index], card=first_card)] + for i in get_player_index(start_index=start_player_index): + current_player = self.players[i] + card = self.play_card(table_cards=self.cards_on_table, player=current_player) + self.move_made(current_player.id, card) + self.cards_on_table.append(PlayedCard(player=current_player, card=card)) + stich = stich_rules[self.trumpf](played_cards=self.cards_on_table) + return stich + + def play_card(self, table_cards, player): + """ + Checks if the card played by the player is allowed. If yes removes the card from the players hand. + :param table_cards: + :param player: + :return: the card chosen by the player + """ + cards = [played_card.card for played_card in table_cards] + is_allowed_card = False + generator = player.choose_card(state=self.get_status()) + chosen_card = next(generator) + while not is_allowed_card: + is_allowed_card = card_allowed(table_cards=cards, chosen_card=chosen_card, hand_cards=player.cards, + trumpf=self.trumpf) + card = generator.send(is_allowed_card) + chosen_card = chosen_card if card is None else card + else: + logger.info('Table: {0}:{1}'.format(player, chosen_card)) + player.cards.remove(chosen_card) + return chosen_card + + def move_made(self, player_id, card): + for player in self.players: + player.move_made(player_id, card, self.get_status()) + + def stich_over_information(self): + [player.stich_over(state=self.get_status()) for player in self.players] + + def count_points(self, stich, last): + """ + Gets the team of the winner of the stich and counts the points. + :param stich: + :param last: True if it is the last stich of the Game, False otherwise + :return: + """ + stich_player_index = self.players.index(stich.player) + cards = [played_card.card for played_card in stich.played_cards] + self.add_points(team_index=(stich_player_index % 2), cards=cards, last=last) + + def add_points(self, team_index, cards, last): + """ + Adds the points of the cards to the score of the team who won the stich. + :param team_index: + :param cards: + :param last: + :return: + """ + points = count_stich(cards, self.trumpf, last=last) + points = points * counting_factor[self.trumpf] if self.use_counting_factor else points + self.teams[team_index].points += points + + def get_status(self): + """ + Returns the status of the game in a dictionary containing + - the stiche + - the trumpf + - if it has been geschoben + - the point limit + - the cards currently on the table + - the teams + :return: + """ + return dict( + stiche=[stich_dict(stich) for stich in self.stiche], + trumpf=self.trumpf.name, + geschoben=self.geschoben, + point_limit=self.point_limit, + table=[played_card_dict(played_card) for played_card in self.cards_on_table], + teams=[dict(points=team.points) for team in self.teams] + ) + + def reset_points(self): + """ + Resets the points of the teams to 0. This is used when single games are played. + :return: + """ + [team.reset_points() for team in self.teams] + + +def get_player_index(start_index): + for i in range(1, 4): + yield (i + start_index) % 4 diff --git a/pyschieber/helpers/__init__.py b/schieber/helpers/__init__.py similarity index 100% rename from pyschieber/helpers/__init__.py rename to schieber/helpers/__init__.py diff --git a/pyschieber/helpers/game_helper.py b/schieber/helpers/game_helper.py similarity index 93% rename from pyschieber/helpers/game_helper.py rename to schieber/helpers/game_helper.py index 1429725..6d6e6f4 100644 --- a/pyschieber/helpers/game_helper.py +++ b/schieber/helpers/game_helper.py @@ -1,4 +1,4 @@ -from pyschieber.suit import Suit +from schieber.suit import Suit def split_card_values_by_suit(cards): diff --git a/pyschieber/player/__init__.py b/schieber/player/__init__.py similarity index 100% rename from pyschieber/player/__init__.py rename to schieber/player/__init__.py diff --git a/schieber/player/base_player.py b/schieber/player/base_player.py new file mode 100644 index 0000000..a1359e2 --- /dev/null +++ b/schieber/player/base_player.py @@ -0,0 +1,62 @@ +import inspect + +from schieber.card import from_string_to_card +from schieber.trumpf import Trumpf +from schieber.rules.stich_rules import allowed_cards + + +class BasePlayer: + def __init__(self, name='unknown', seed=None, trumps='all'): + """ + + :param name: + :param seed: + :param trumps: if 'all': no restriction on trumps available, if 'obe_abe': only OBE_ABE allowed + """ + self.name = name + self.cards = [] + self.trumpf_list = list(Trumpf) + self.id = name + self.seed = seed + self.trumps = trumps + + def get_dict(self): + """ + Returns a dictionary containing: + - the name + - the type (RandomPlayer, GreedyPlayer, etc.) + :return: + """ + return dict(name=self.name, type=type(self).__name__) + + def set_card(self, card): + self.cards.append(card) + + def choose_trumpf(self, geschoben): + raise NotImplementedError(str(inspect.stack()[1][3])) + + def choose_card(self, state=None): + raise NotImplementedError(str(inspect.stack()[1][3])) + + def move_made(self, player_id, card, state): + pass + + def stich_over(self, state=None): + pass + + def allowed_cards(self, state): + return self.allowed_cards_with_hand_cards(state, self.cards) + + def allowed_cards_with_hand_cards(self, state, hand_cards): + """ + Returns the cards on the hand of the player which he/she is allowed to play in the current state according to the rules + :param hand_cards: + :param state: + :return: + """ + table_cards = [from_string_to_card(entry['card']) for entry in state['table']] + trumpf = Trumpf[state['trumpf']] + return allowed_cards(hand_cards=hand_cards, table_cards=table_cards, trumpf=trumpf) + + def __str__(self): + return ''.format(self.name) diff --git a/pyschieber/player/challenge_player/__init__.py b/schieber/player/challenge_player/__init__.py similarity index 100% rename from pyschieber/player/challenge_player/__init__.py rename to schieber/player/challenge_player/__init__.py diff --git a/pyschieber/player/challenge_player/challenge_player.py b/schieber/player/challenge_player/challenge_player.py similarity index 77% rename from pyschieber/player/challenge_player/challenge_player.py rename to schieber/player/challenge_player/challenge_player.py index 5205d65..0f1f982 100644 --- a/pyschieber/player/challenge_player/challenge_player.py +++ b/schieber/player/challenge_player/challenge_player.py @@ -1,8 +1,9 @@ import random -from pyschieber.player.base_player import BasePlayer -from pyschieber.player.challenge_player.strategy.jass_strategy import JassStrategy -from pyschieber.card import Card +from schieber.player.base_player import BasePlayer +from schieber.player.challenge_player.strategy.jass_strategy import JassStrategy +from schieber.card import Card +from schieber.trumpf import Trumpf class ChallengePlayer(BasePlayer): @@ -15,7 +16,10 @@ def choose_trumpf(self, geschoben): allowed = False while not allowed: trumpf = self.strategy.chose_trumpf(self.cards, geschoben) - allowed = yield trumpf + if self.trumps == 'all': + allowed = yield trumpf + elif self.trumps == 'obe_abe': + allowed = yield Trumpf.OBE_ABE if allowed: yield None @@ -42,11 +46,11 @@ def choose_card(self, state=None): while not allowed: card = self.strategy.choose_card(cards, state, self.role) if not isinstance(card, Card): + random.seed(self.seed) card = random.choice(cards) allowed = yield card if allowed: yield None - def move_made(self, player_id, card, state): self.strategy.move_made(player_id, card, state) diff --git a/pyschieber/player/challenge_player/strategy/__init__.py b/schieber/player/challenge_player/strategy/__init__.py similarity index 100% rename from pyschieber/player/challenge_player/strategy/__init__.py rename to schieber/player/challenge_player/strategy/__init__.py diff --git a/pyschieber/player/challenge_player/strategy/card_counter.py b/schieber/player/challenge_player/strategy/card_counter.py similarity index 90% rename from pyschieber/player/challenge_player/strategy/card_counter.py rename to schieber/player/challenge_player/strategy/card_counter.py index 4a0ddd5..33e3c5a 100644 --- a/pyschieber/player/challenge_player/strategy/card_counter.py +++ b/schieber/player/challenge_player/strategy/card_counter.py @@ -1,16 +1,16 @@ -from pyschieber.player.challenge_player.strategy.mode.trumpf_color_mode import * -from pyschieber.player.challenge_player.strategy.mode.top_down_mode import * -from pyschieber.player.challenge_player.strategy.mode.bottom_up_mode import * -from pyschieber.player.challenge_player.strategy.flags.doesnt_habe_card_flag import DoesntHaveCardFlag -from pyschieber.player.challenge_player.strategy.flags.previously_had_stich_flag import PreviouslyHadStichFlag -from pyschieber.player.challenge_player.strategy.flags.falied_to_serve_suit_flag import FailedToServeSuitFlag -from pyschieber.player.challenge_player.strategy.flags.suit_verworfen_flag import SuitVerworfenFlag -from pyschieber.deck import Deck -from pyschieber.card import from_string_to_card -from pyschieber.rules.stich_rules import stich_rules -from pyschieber.trumpf import get_trumpf -from pyschieber.stich import PlayedCard -from pyschieber.suit import Suit +from schieber.player.challenge_player.strategy.mode.trumpf_color_mode import * +from schieber.player.challenge_player.strategy.mode.top_down_mode import * +from schieber.player.challenge_player.strategy.mode.bottom_up_mode import * +from schieber.player.challenge_player.strategy.flags.doesnt_habe_card_flag import DoesntHaveCardFlag +from schieber.player.challenge_player.strategy.flags.previously_had_stich_flag import PreviouslyHadStichFlag +from schieber.player.challenge_player.strategy.flags.falied_to_serve_suit_flag import FailedToServeSuitFlag +from schieber.player.challenge_player.strategy.flags.suit_verworfen_flag import SuitVerworfenFlag +from schieber.deck import Deck +from schieber.card import from_string_to_card +from schieber.rules.stich_rules import stich_rules +from schieber.trumpf import get_trumpf +from schieber.stich import PlayedCard +from schieber.suit import Suit from math import floor diff --git a/pyschieber/player/challenge_player/strategy/flags/__init__.py b/schieber/player/challenge_player/strategy/flags/__init__.py similarity index 100% rename from pyschieber/player/challenge_player/strategy/flags/__init__.py rename to schieber/player/challenge_player/strategy/flags/__init__.py diff --git a/pyschieber/player/challenge_player/strategy/flags/doesnt_habe_card_flag.py b/schieber/player/challenge_player/strategy/flags/doesnt_habe_card_flag.py similarity index 100% rename from pyschieber/player/challenge_player/strategy/flags/doesnt_habe_card_flag.py rename to schieber/player/challenge_player/strategy/flags/doesnt_habe_card_flag.py diff --git a/pyschieber/player/challenge_player/strategy/flags/falied_to_serve_suit_flag.py b/schieber/player/challenge_player/strategy/flags/falied_to_serve_suit_flag.py similarity index 100% rename from pyschieber/player/challenge_player/strategy/flags/falied_to_serve_suit_flag.py rename to schieber/player/challenge_player/strategy/flags/falied_to_serve_suit_flag.py diff --git a/pyschieber/player/challenge_player/strategy/flags/previously_had_stich_flag.py b/schieber/player/challenge_player/strategy/flags/previously_had_stich_flag.py similarity index 100% rename from pyschieber/player/challenge_player/strategy/flags/previously_had_stich_flag.py rename to schieber/player/challenge_player/strategy/flags/previously_had_stich_flag.py diff --git a/pyschieber/player/challenge_player/strategy/flags/suit_verworfen_flag.py b/schieber/player/challenge_player/strategy/flags/suit_verworfen_flag.py similarity index 100% rename from pyschieber/player/challenge_player/strategy/flags/suit_verworfen_flag.py rename to schieber/player/challenge_player/strategy/flags/suit_verworfen_flag.py diff --git a/pyschieber/player/challenge_player/strategy/jass_strategy.py b/schieber/player/challenge_player/strategy/jass_strategy.py similarity index 74% rename from pyschieber/player/challenge_player/strategy/jass_strategy.py rename to schieber/player/challenge_player/strategy/jass_strategy.py index d55b944..661b9b5 100644 --- a/pyschieber/player/challenge_player/strategy/jass_strategy.py +++ b/schieber/player/challenge_player/strategy/jass_strategy.py @@ -1,8 +1,8 @@ -from pyschieber.player.challenge_player.strategy.mode.trumpf_color_mode import * -from pyschieber.player.challenge_player.strategy.mode.top_down_mode import * -from pyschieber.player.challenge_player.strategy.mode.bottom_up_mode import * -from pyschieber.player.challenge_player.strategy.card_counter import * -from pyschieber.trumpf import Trumpf +from schieber.player.challenge_player.strategy.mode.trumpf_color_mode import * +from schieber.player.challenge_player.strategy.mode.top_down_mode import * +from schieber.player.challenge_player.strategy.mode.bottom_up_mode import * +from schieber.player.challenge_player.strategy.card_counter import * +from schieber.trumpf import Trumpf class JassStrategy: diff --git a/pyschieber/player/challenge_player/strategy/mode/__init__.py b/schieber/player/challenge_player/strategy/mode/__init__.py similarity index 100% rename from pyschieber/player/challenge_player/strategy/mode/__init__.py rename to schieber/player/challenge_player/strategy/mode/__init__.py diff --git a/pyschieber/player/challenge_player/strategy/mode/bottom_up_mode.py b/schieber/player/challenge_player/strategy/mode/bottom_up_mode.py similarity index 95% rename from pyschieber/player/challenge_player/strategy/mode/bottom_up_mode.py rename to schieber/player/challenge_player/strategy/mode/bottom_up_mode.py index d3c6f30..7fbec38 100644 --- a/pyschieber/player/challenge_player/strategy/mode/bottom_up_mode.py +++ b/schieber/player/challenge_player/strategy/mode/bottom_up_mode.py @@ -1,7 +1,7 @@ -from pyschieber.player.challenge_player.strategy.mode.uncolored_trumpf import UncoloredTrumpf -from pyschieber.helpers.game_helper import * -from pyschieber.trumpf import Trumpf -from pyschieber.card import from_string_to_card +from schieber.player.challenge_player.strategy.mode.uncolored_trumpf import UncoloredTrumpf +from schieber.helpers.game_helper import * +from schieber.trumpf import Trumpf +from schieber.card import from_string_to_card class BottomUpMode(UncoloredTrumpf): diff --git a/pyschieber/player/challenge_player/strategy/mode/mode.py b/schieber/player/challenge_player/strategy/mode/mode.py similarity index 94% rename from pyschieber/player/challenge_player/strategy/mode/mode.py rename to schieber/player/challenge_player/strategy/mode/mode.py index 7b10717..00dea90 100644 --- a/pyschieber/player/challenge_player/strategy/mode/mode.py +++ b/schieber/player/challenge_player/strategy/mode/mode.py @@ -1,6 +1,6 @@ -from pyschieber.helpers.game_helper import * -from pyschieber.card import from_string_to_card -from pyschieber.trumpf import get_trumpf +from schieber.helpers.game_helper import * +from schieber.card import from_string_to_card +from schieber.trumpf import get_trumpf class Mode: def is_bock(self, c, c_counter): diff --git a/pyschieber/player/challenge_player/strategy/mode/top_down_mode.py b/schieber/player/challenge_player/strategy/mode/top_down_mode.py similarity index 95% rename from pyschieber/player/challenge_player/strategy/mode/top_down_mode.py rename to schieber/player/challenge_player/strategy/mode/top_down_mode.py index 69dc93f..2da3c9a 100644 --- a/pyschieber/player/challenge_player/strategy/mode/top_down_mode.py +++ b/schieber/player/challenge_player/strategy/mode/top_down_mode.py @@ -1,7 +1,7 @@ -from pyschieber.player.challenge_player.strategy.mode.uncolored_trumpf import UncoloredTrumpf -from pyschieber.helpers.game_helper import * -from pyschieber.trumpf import Trumpf -from pyschieber.card import from_string_to_card +from schieber.player.challenge_player.strategy.mode.uncolored_trumpf import UncoloredTrumpf +from schieber.helpers.game_helper import * +from schieber.trumpf import Trumpf +from schieber.card import from_string_to_card class TopDownMode(UncoloredTrumpf): diff --git a/pyschieber/player/challenge_player/strategy/mode/trumpf_color_mode.py b/schieber/player/challenge_player/strategy/mode/trumpf_color_mode.py similarity index 98% rename from pyschieber/player/challenge_player/strategy/mode/trumpf_color_mode.py rename to schieber/player/challenge_player/strategy/mode/trumpf_color_mode.py index 1596d2a..b8210bb 100644 --- a/pyschieber/player/challenge_player/strategy/mode/trumpf_color_mode.py +++ b/schieber/player/challenge_player/strategy/mode/trumpf_color_mode.py @@ -1,7 +1,7 @@ -from pyschieber.player.challenge_player.strategy.mode.mode import Mode -from pyschieber.helpers.game_helper import * -from pyschieber.trumpf import Trumpf -from pyschieber.card import from_string_to_card +from schieber.player.challenge_player.strategy.mode.mode import Mode +from schieber.helpers.game_helper import * +from schieber.trumpf import Trumpf +from schieber.card import from_string_to_card class TrumpfColorMode(Mode): diff --git a/pyschieber/player/challenge_player/strategy/mode/uncolored_trumpf.py b/schieber/player/challenge_player/strategy/mode/uncolored_trumpf.py similarity index 98% rename from pyschieber/player/challenge_player/strategy/mode/uncolored_trumpf.py rename to schieber/player/challenge_player/strategy/mode/uncolored_trumpf.py index 18ef441..b00503f 100644 --- a/pyschieber/player/challenge_player/strategy/mode/uncolored_trumpf.py +++ b/schieber/player/challenge_player/strategy/mode/uncolored_trumpf.py @@ -1,6 +1,6 @@ -from pyschieber.player.challenge_player.strategy.mode.mode import Mode -from pyschieber.helpers.game_helper import * -from pyschieber.card import from_string_to_card +from schieber.player.challenge_player.strategy.mode.mode import Mode +from schieber.helpers.game_helper import * +from schieber.card import from_string_to_card class UncoloredTrumpf(Mode): diff --git a/pyschieber/player/cli_player.py b/schieber/player/cli_player.py similarity index 97% rename from pyschieber/player/cli_player.py rename to schieber/player/cli_player.py index bb2530a..1506783 100644 --- a/pyschieber/player/cli_player.py +++ b/schieber/player/cli_player.py @@ -1,4 +1,4 @@ -from pyschieber.player.base_player import BasePlayer +from schieber.player.base_player import BasePlayer class CliPlayer(BasePlayer): diff --git a/schieber/player/external_player.py b/schieber/player/external_player.py new file mode 100644 index 0000000..6d0ce59 --- /dev/null +++ b/schieber/player/external_player.py @@ -0,0 +1,136 @@ +import logging +from multiprocessing import Condition + +from schieber.player.greedy_player.greedy_player import GreedyPlayer + +from schieber.player.challenge_player.challenge_player import ChallengePlayer + +from schieber.player.base_player import BasePlayer +from schieber.trumpf import Trumpf + +logger = logging.getLogger(__name__) + + +class ExternalPlayer(GreedyPlayer): + """ + The RL player in the gym environment wants to initiate control by + invoking the step() function. This step function sends an action, lets the environment simulate and then + receives an observation back from the environment. + In this schieber environment the control is initiated by the Game and not by the player. This is why we need this + architecture with this external player. The external player blocks when its choose_card() method is called and + sends the current state received by the Game as an observation to the rl player from gym who connects via a + websocket. Then the rl agent selects an action and sends it back to this external player. The external player + submits this action as the chosen card to the Game. The Game simulates the game and this process starts over. + With the help of this architecture we can use the benefits of the standardized gym environments with many + rl methods which are already implemented (openai baselines: https://github.com/openai/baselines). + """ + + def __init__(self, name='unknown', seed=None, trumps='all'): + super().__init__(name, seed, trumps) + self.action_received = Condition() + self.observation_received = Condition() + + self.action = {} + self.observation = {} + + def choose_card(self, state=None): + """ + Chooses the card and verifies if the chosen card is allowed to be played in the current game state. + :param state: + :return: + """ + # if self.at_last_stich(): + # allowed = yield self.cards[0] + # else: + self.observation_received.acquire() + self.observation = self.build_observation(state, self.cards) + logger.debug(f"choose_card received observation: {self.observation}") + self.observation_received.notify_all() # notify all threads to be sure + self.observation_received.release() + + self.action_received.acquire() + received = self.action_received.wait() + if not received: + logger.debug("Timeout occurred. action_received condition has not been notified.") + logger.debug(f"choose_card received action: {self.action}") + allowed_cards = self.allowed_cards(state=state) + chosen_card = allowed_cards[0] # set chosen_card to the first allowed card in case anything goes south + chosen_card = self.set_chosen_card(allowed_cards, chosen_card) + self.action_received.release() + + allowed = yield chosen_card + + if allowed: + yield None + + @staticmethod + def build_observation(state, cards): + observation = state + observation["cards"] = cards + return observation + + def set_chosen_card(self, allowed_cards, chosen_card): + """ + Sets the chosen card based on the action of the RL player. + :param allowed_cards: + :param chosen_card: + :return: + """ + if self.action is not None: + if self.action in allowed_cards: + logger.info(f"Successfully chose the card: {self.action}") + chosen_card = self.action + else: + logger.error(f"{self.action} is not a valid card! Choosing the first allowed card now.") + else: + logger.debug("chosen card is None") + return chosen_card + + def get_observation(self): + """ + Gets the observation obtained by the game + :return: + """ + self.observation_received.acquire() + received = self.observation_received.wait(0.01) + if not received: + print("Timeout occurred. observation_received condition has not been notified.") + observation = self.observation + logger.debug(f"get_observation {observation}") + self.observation_received.release() + return observation + + def set_action(self, action): + """ + Sets the action chosen by the RL player + :param action: + :return: + """ + if self.hand_empty(): + logger.error("set_action: There are no cards on my hand, so I cannot choose any card!") + self.action_received.acquire() + self.action = action + logger.debug(f"set_action: {self.action}") + self.action_received.notify_all() # notify all threads to be sure + self.action_received.release() + + def before_first_stich(self): + """ + Checks if the player has already played any cards in this game + :return: + """ + return len(self.cards) == 9 + + def at_last_stich(self): + """ + Checks if the player is at the last stich where there is no choice anymore + :return: + """ + return len(self.cards) == 1 + + def hand_empty(self): + """ + Checks if the hand is empty or if there are any cards left. + :return: + """ + return len(self.cards) == 0 diff --git a/pyschieber/player/greedy_player/__init__.py b/schieber/player/greedy_player/__init__.py similarity index 100% rename from pyschieber/player/greedy_player/__init__.py rename to schieber/player/greedy_player/__init__.py diff --git a/pyschieber/player/greedy_player/greedy_player.py b/schieber/player/greedy_player/greedy_player.py similarity index 69% rename from pyschieber/player/greedy_player/greedy_player.py rename to schieber/player/greedy_player/greedy_player.py index 64e0a01..f8d9a9f 100644 --- a/pyschieber/player/greedy_player/greedy_player.py +++ b/schieber/player/greedy_player/greedy_player.py @@ -1,14 +1,17 @@ -from pyschieber.player.base_player import BasePlayer -from pyschieber.player.greedy_player.trumpf_decision import choose_trumpf -from pyschieber.trumpf import Trumpf +from schieber.player.base_player import BasePlayer +from schieber.player.greedy_player import trumpf_decision +from schieber.trumpf import Trumpf class GreedyPlayer(BasePlayer): def choose_trumpf(self, geschoben): allowed = False while not allowed: - trumpf, _ = choose_trumpf(cards=self.cards, geschoben=geschoben) - allowed = yield trumpf + trumpf, _ = trumpf_decision.choose_trumpf(cards=self.cards, geschoben=geschoben) + if self.trumps == 'all': + allowed = yield trumpf + elif self.trumps == 'obe_abe': + allowed = yield Trumpf.OBE_ABE if allowed: yield None diff --git a/pyschieber/player/greedy_player/trumpf_decision.py b/schieber/player/greedy_player/trumpf_decision.py similarity index 96% rename from pyschieber/player/greedy_player/trumpf_decision.py rename to schieber/player/greedy_player/trumpf_decision.py index 3fe4463..109846e 100644 --- a/pyschieber/player/greedy_player/trumpf_decision.py +++ b/schieber/player/greedy_player/trumpf_decision.py @@ -1,9 +1,9 @@ from enum import Enum from operator import itemgetter -from pyschieber.helpers.game_helper import * -from pyschieber.trumpf import Trumpf -from pyschieber.rules.count_rules import counting_factor +from schieber.helpers.game_helper import * +from schieber.trumpf import Trumpf +from schieber.rules.count_rules import counting_factor # https://www.jassverzeichnis.ch/index.php/blog/95-jass-tipps-trumpfansagen-schieber TrumpfType = Enum('TrumpfType', diff --git a/schieber/player/model_player.py b/schieber/player/model_player.py new file mode 100644 index 0000000..fc7ca20 --- /dev/null +++ b/schieber/player/model_player.py @@ -0,0 +1,40 @@ +import logging + +from gym_jass.envs import SchieberEnv +from jass_bot.rl import JassPolicy +from schieber.player.greedy_player.greedy_player import GreedyPlayer +from stable_baselines import PPO2 +from stable_baselines.common.vec_env import DummyVecEnv + +from schieber.player.challenge_player.challenge_player import ChallengePlayer + +from schieber.player.base_player import BasePlayer + +from schieber.player.external_player import ExternalPlayer + +logger = logging.getLogger(__name__) + + +class ModelPlayer(GreedyPlayer): + """ + This player can be used to evaluate the strength of a trained RL model. + """ + + def __init__(self, name='unknown', seed=None, trumps='all', + model_path="/Users/joelito/MEGA/Studium/Master/Informatik/Courses/Data Science/Very Deep Learning/Project/schieber/tests/benchmarks/models/stich-higher-learning-rate_env=Schieber-v0_gamma=0.89_nsteps=90_learning_rate=0.001_policy=JassPolicy_model=PPO2_time=2019-01-13_12:08:28_final.pkl"): + """ + Inits the player with a trained model. This model is based on the stable_baselines framework. + (It has to implement the predict() function) + :param model_path: + """ + super().__init__(name, seed, trumps) + env = SchieberEnv() + env = DummyVecEnv([lambda: env]) + self.model = PPO2.load(load_path=model_path, env=env, policy=JassPolicy) + + +def choose_card(self, state=None): + obs_dict = ExternalPlayer.build_observation(state, self.cards) + obs = SchieberEnv.observation_dict_to_onehot_matrix(obs_dict) + action = self.model.predict(obs)[0] + return self.cards[action] diff --git a/schieber/player/random_player.py b/schieber/player/random_player.py new file mode 100644 index 0000000..3f1f4b9 --- /dev/null +++ b/schieber/player/random_player.py @@ -0,0 +1,25 @@ +import random + +from schieber.player.base_player import BasePlayer +from schieber.trumpf import Trumpf + + +class RandomPlayer(BasePlayer): + def choose_trumpf(self, geschoben): + if self.trumps == 'all': + return self.move(choices=list(Trumpf)) + elif self.trumps == 'obe_abe': + return self.move(choices=[Trumpf.OBE_ABE]) + + def choose_card(self, state=None): + cards = self.allowed_cards(state=state) + return self.move(choices=cards) + + def move(self, choices): + allowed = False + while not allowed: + random.seed(self.seed) + choice = random.choice(choices) + allowed = yield choice + if allowed: + yield None diff --git a/pyschieber/player/server_player/__init__.py b/schieber/player/server_player/__init__.py similarity index 100% rename from pyschieber/player/server_player/__init__.py rename to schieber/player/server_player/__init__.py diff --git a/pyschieber/player/server_player/helpers/__init__.py b/schieber/player/server_player/helpers/__init__.py similarity index 100% rename from pyschieber/player/server_player/helpers/__init__.py rename to schieber/player/server_player/helpers/__init__.py diff --git a/pyschieber/player/server_player/helpers/messages.py b/schieber/player/server_player/helpers/messages.py similarity index 99% rename from pyschieber/player/server_player/helpers/messages.py rename to schieber/player/server_player/helpers/messages.py index b2b8c51..87a4000 100644 --- a/pyschieber/player/server_player/helpers/messages.py +++ b/schieber/player/server_player/helpers/messages.py @@ -1,6 +1,6 @@ from enum import Enum -from pyschieber.player.server_player.helpers.server_cards import ServerCard, Color +from schieber.player.server_player.helpers.server_cards import ServerCard, Color class GameType: diff --git a/pyschieber/player/server_player/helpers/parser/__init__.py b/schieber/player/server_player/helpers/parser/__init__.py similarity index 100% rename from pyschieber/player/server_player/helpers/parser/__init__.py rename to schieber/player/server_player/helpers/parser/__init__.py diff --git a/pyschieber/player/server_player/helpers/parser/card_parser.py b/schieber/player/server_player/helpers/parser/card_parser.py similarity index 60% rename from pyschieber/player/server_player/helpers/parser/card_parser.py rename to schieber/player/server_player/helpers/parser/card_parser.py index e9317a0..b02df49 100644 --- a/pyschieber/player/server_player/helpers/parser/card_parser.py +++ b/schieber/player/server_player/helpers/parser/card_parser.py @@ -1,6 +1,6 @@ -from pyschieber.card import Card as PyschieberCard -from pyschieber.player.server_player.helpers.parser.color_parser import suit_to_color, color_to_suit -from pyschieber.player.server_player.helpers.server_cards import ServerCard +from schieber.card import Card as PyschieberCard +from schieber.player.server_player.helpers.parser.color_parser import suit_to_color, color_to_suit +from schieber.player.server_player.helpers.server_cards import ServerCard def pyscheiber_card_to_challenge_card(pyschieber_card): diff --git a/pyschieber/player/server_player/helpers/parser/color_parser.py b/schieber/player/server_player/helpers/parser/color_parser.py similarity index 77% rename from pyschieber/player/server_player/helpers/parser/color_parser.py rename to schieber/player/server_player/helpers/parser/color_parser.py index edd8b81..a487a06 100644 --- a/pyschieber/player/server_player/helpers/parser/color_parser.py +++ b/schieber/player/server_player/helpers/parser/color_parser.py @@ -1,5 +1,5 @@ -from pyschieber.suit import Suit -from pyschieber.player.server_player.helpers.server_cards import Color +from schieber.suit import Suit +from schieber.player.server_player.helpers.server_cards import Color suit_to_color_dict = {Suit.ROSE: Color.HEARTS, Suit.ACORN: Color.DIAMONDS, Suit.BELL: Color.CLUBS, Suit.SHIELD: Color.SPADES} diff --git a/pyschieber/player/server_player/helpers/parser/game_type_parser.py b/schieber/player/server_player/helpers/parser/game_type_parser.py similarity index 77% rename from pyschieber/player/server_player/helpers/parser/game_type_parser.py rename to schieber/player/server_player/helpers/parser/game_type_parser.py index accbf87..1aa5ebd 100644 --- a/pyschieber/player/server_player/helpers/parser/game_type_parser.py +++ b/schieber/player/server_player/helpers/parser/game_type_parser.py @@ -1,7 +1,7 @@ -from pyschieber.player.server_player.helpers.messages import GameType -from pyschieber.player.server_player.helpers.parser.color_parser import suit_to_color, color_to_suit -from pyschieber.suit import Suit -from pyschieber.trumpf import Trumpf +from schieber.player.server_player.helpers.messages import GameType +from schieber.player.server_player.helpers.parser.color_parser import suit_to_color, color_to_suit +from schieber.suit import Suit +from schieber.trumpf import Trumpf def pyschieber_trumpf_to_game_type(pyschieber_trumpf): diff --git a/pyschieber/player/server_player/helpers/server_cards.py b/schieber/player/server_player/helpers/server_cards.py similarity index 100% rename from pyschieber/player/server_player/helpers/server_cards.py rename to schieber/player/server_player/helpers/server_cards.py diff --git a/pyschieber/player/server_player/helpers/web_socket_handler.py b/schieber/player/server_player/helpers/web_socket_handler.py similarity index 91% rename from pyschieber/player/server_player/helpers/web_socket_handler.py rename to schieber/player/server_player/helpers/web_socket_handler.py index 1b95450..98c458a 100644 --- a/pyschieber/player/server_player/helpers/web_socket_handler.py +++ b/schieber/player/server_player/helpers/web_socket_handler.py @@ -3,7 +3,7 @@ import asyncio import websockets -from pyschieber.player.server_player.helpers import messages +from schieber.player.server_player.helpers import messages logger = logging.getLogger(__name__) @@ -21,6 +21,7 @@ async def handler(self): message = await websocket.recv() except websockets.exceptions.ConnectionClosed: self.started = False + logger.debug("Stopped, because connection is closed.") break logger.debug("Received message {}".format(message)) payload = json.loads(message) diff --git a/pyschieber/player/server_player/requirements.txt b/schieber/player/server_player/requirements.txt similarity index 100% rename from pyschieber/player/server_player/requirements.txt rename to schieber/player/server_player/requirements.txt diff --git a/pyschieber/player/server_player/server_player.py b/schieber/player/server_player/server_player.py similarity index 93% rename from pyschieber/player/server_player/server_player.py rename to schieber/player/server_player/server_player.py index 3b345f4..4f18f32 100644 --- a/pyschieber/player/server_player/server_player.py +++ b/schieber/player/server_player/server_player.py @@ -2,17 +2,17 @@ import logging from enum import Enum -from pyschieber.player.server_player.helpers.parser.game_type_parser import pyschieber_trumpf_to_game_type, \ +from schieber.player.server_player.helpers.parser.game_type_parser import pyschieber_trumpf_to_game_type, \ game_type_to_pyschieber_trumpf -from pyschieber.player.server_player.helpers import messages -from pyschieber.player.server_player.helpers.messages import MessageType -from pyschieber.player.server_player.helpers.parser.card_parser import pyscheiber_card_to_challenge_card, \ +from schieber.player.server_player.helpers import messages +from schieber.player.server_player.helpers.messages import MessageType +from schieber.player.server_player.helpers.parser.card_parser import pyscheiber_card_to_challenge_card, \ challenge_card_to_pyschieber_card -from pyschieber.player.server_player.helpers.server_cards import ServerCard -from pyschieber.player.server_player.helpers.web_socket_handler import WebSocketHandler -from pyschieber.rules.stich_rules import card_allowed -from pyschieber.rules.trumpf_rules import trumpf_allowed +from schieber.player.server_player.helpers.server_cards import ServerCard +from schieber.player.server_player.helpers.web_socket_handler import WebSocketHandler +from schieber.rules.stich_rules import card_allowed +from schieber.rules.trumpf_rules import trumpf_allowed logger = logging.getLogger(__name__) @@ -185,7 +185,7 @@ def handle_reject_card(self, data): logger.warning(" ###### SERVER REJECTED CARD #######") logger.warning("Player: {}".format(self.pyschieber_bot.name)) logger.warning("Rejected card: %s", data) - logger.warning("Hand cards pyschieber: %s", self.pyschieber_bot.cards) + logger.warning("Hand cards schieber: %s", self.pyschieber_bot.cards) logger.warning("Hand cards: %s", self.hand_cards) logger.warning("Table cards: %s", self.table) logger.warning("Gametype: %s", self.game_type) @@ -269,4 +269,4 @@ def pyschieber_stich(self, stich_player_id): self.stiche.append(stich) def is_max_game_reached(self): - return self.max_games >= 0 and self.max_games <= self.count_games + return 0 <= self.max_games <= self.count_games diff --git a/pyschieber/rules/__init__.py b/schieber/rules/__init__.py similarity index 100% rename from pyschieber/rules/__init__.py rename to schieber/rules/__init__.py diff --git a/pyschieber/rules/count_rules.py b/schieber/rules/count_rules.py similarity index 85% rename from pyschieber/rules/count_rules.py rename to schieber/rules/count_rules.py index 27ffbfc..7f74d68 100644 --- a/pyschieber/rules/count_rules.py +++ b/schieber/rules/count_rules.py @@ -1,4 +1,4 @@ -from pyschieber.trumpf import Trumpf +from schieber.trumpf import Trumpf counting_factor = {Trumpf.ROSE: 1, Trumpf.ACORN: 1, Trumpf.BELL: 2, Trumpf.SHIELD: 2, Trumpf.OBE_ABE: 3, Trumpf.UNDE_UFE: 3} @@ -15,6 +15,13 @@ def count_stich(cards, trumpf, last=False): + """ + Counts the points of a stich based on the rules of Jassen + :param cards: + :param trumpf: + :param last: + :return: + """ points = 0 if not last else 5 for card in cards: if trumpf == Trumpf.OBE_ABE or trumpf == Trumpf.UNDE_UFE or card.suit.name == trumpf.name: diff --git a/pyschieber/rules/stich_rules.py b/schieber/rules/stich_rules.py similarity index 98% rename from pyschieber/rules/stich_rules.py rename to schieber/rules/stich_rules.py index 30ff3a0..126efde 100644 --- a/pyschieber/rules/stich_rules.py +++ b/schieber/rules/stich_rules.py @@ -1,7 +1,7 @@ from functools import partial -from pyschieber.stich import Stich -from pyschieber.trumpf import Trumpf +from schieber.stich import Stich +from schieber.trumpf import Trumpf UNDER = 11 NAELL = 9 diff --git a/schieber/rules/trumpf_rules.py b/schieber/rules/trumpf_rules.py new file mode 100644 index 0000000..1a86794 --- /dev/null +++ b/schieber/rules/trumpf_rules.py @@ -0,0 +1,11 @@ +from schieber.trumpf import Trumpf + + +def trumpf_allowed(chosen_trumpf, geschoben): + """ + Disallows 'geschoben' when the partner already has chose 'geschoben'. All other trumpfs are always allowed. + :param chosen_trumpf: + :param geschoben: + :return: + """ + return not (chosen_trumpf == Trumpf.SCHIEBEN and geschoben) diff --git a/pyschieber/rules/wies_rules.py b/schieber/rules/wies_rules.py similarity index 100% rename from pyschieber/rules/wies_rules.py rename to schieber/rules/wies_rules.py diff --git a/schieber/stich.py b/schieber/stich.py new file mode 100644 index 0000000..6577f7d --- /dev/null +++ b/schieber/stich.py @@ -0,0 +1,34 @@ +from collections import namedtuple + +PlayedCard = namedtuple('PlayedCard', ['player', 'card']) +Stich = namedtuple('Stich', ['player', 'played_cards', 'trumpf']) + + +def played_card_dict(played_card): + """ + Returns a dictionary containing: + - the player who played the card + - the played card + :param played_card: + :return: + """ + return { + 'player_id': played_card.player.id, + 'card': str(played_card.card) + } + + +def stich_dict(stich): + """ + Returns a dictionary of the stich containing: + - the id of the player who is winner of the stich + - the trumpf + - the cards played in the stich + :param stich: + :return: + """ + return { + 'player_id': stich.player.id, + 'trumpf': stich.trumpf.name, + 'played_cards': [played_card_dict(played_card) for played_card in stich.played_cards] + } diff --git a/schieber/suit.py b/schieber/suit.py new file mode 100644 index 0000000..b23c469 --- /dev/null +++ b/schieber/suit.py @@ -0,0 +1,10 @@ +from enum import Enum + + +# Suit = Enum('Suit',['ROSE', 'BELL', 'ACORN', 'SHIELD']) + +class Suit(Enum): + ROSE = 0 + BELL = 1 + ACORN = 2 + SHIELD = 3 diff --git a/schieber/team.py b/schieber/team.py new file mode 100644 index 0000000..d7e57c0 --- /dev/null +++ b/schieber/team.py @@ -0,0 +1,30 @@ +class Team: + def __init__(self, players=None): + self.points = 0 + self.players = players + + def player_by_number(self, number): + """ + Returns the player by the number in the team. The number should be either 0 or 1. + :param number: + :return: + """ + for player in self.players: + if player.number == number: + return player + return None + + def won(self, point_limit): + """ + Checks if the team already won + :param point_limit: + :return: true if the points of the team are larger than the point limit and false otherwise + """ + return self.points >= point_limit + + def reset_points(self): + """ + Resets the points to 0. This is used when single games and no tournaments are played. + :return: + """ + self.points = 0 diff --git a/pyschieber/tournament.py b/schieber/tournament.py similarity index 59% rename from pyschieber/tournament.py rename to schieber/tournament.py index 695f8ed..0b21a93 100644 --- a/pyschieber/tournament.py +++ b/schieber/tournament.py @@ -1,44 +1,73 @@ import logging -from pyschieber.game import Game -from pyschieber.team import Team +from schieber.game import Game +from schieber.team import Team logger = logging.getLogger(__name__) class Tournament: - def __init__(self, point_limit=1500): + def __init__(self, point_limit=1500, seed=None): + """ + Sets the point limit and initializes the players, teams and games arrays. + :param point_limit: + """ self.point_limit = point_limit self.players = [] self.teams = [] self.games = [] + self.seed = seed def check_players(self): + """ + Checks if there are really 4 players in the array + :return: + """ player_numbers = [] for index, player in enumerate(self.players): player_numbers.append(index) assert {0, 1, 2, 3} == set(player_numbers) def register_player(self, player): + """ + Adds another player if there are still less than 4 players + :param player: + :return: + """ number_of_players = len(self.players) assert number_of_players < 4 self.players.append(player) player.id = number_of_players def build_teams(self): + """ + Builds the teams based on the players array + :return: the team list + """ self.check_players() team_1 = Team(players=[self.players[0], self.players[2]]) team_2 = Team(players=[self.players[1], self.players[3]]) self.teams = [team_1, team_2] + return self.teams - def play(self, rounds=0, use_counting_factor=True): + def play(self, rounds=0, use_counting_factor=False): + """ + Plays a tournament until one team reaches the point_limit. + :param rounds: + :param use_counting_factor: if True: Undenufe and Obenabe are counted 3-fold and Shield and Bell are counted 2-fold + :return: + """ self.build_teams() logger.info('Tournament starts, the point limit is {}.'.format(self.point_limit)) end = False whole_rounds = True if rounds > 0 else False round_counter = 0 while not end: - game = Game(teams=self.teams, point_limit=self.point_limit, use_counting_factor=use_counting_factor) + if self.seed is not None: + # Increment seed by one so that each game is different. + # But still the sequence of games is the same each time + self.seed += 1 + game = Game(teams=self.teams, point_limit=self.point_limit, use_counting_factor=use_counting_factor, seed=self.seed) self.games.append(game) logger.info('-' * 200) logger.info('Round {} starts.'.format(len(self.games))) @@ -54,12 +83,20 @@ def play(self, rounds=0, use_counting_factor=True): self.reset() def get_status(self): + """ + Returns the status of the tournament + :return: + """ return { 'games': [game.get_status() for game in self.games], 'players': [player.get_dict() for player in self.players] } def reset(self): + """ + Resets the tournament. Deletes the games array and deletes the cards of the players. + :return: + """ self.games = [] for player in self.players: player.cards = [] diff --git a/pyschieber/trumpf.py b/schieber/trumpf.py similarity index 91% rename from pyschieber/trumpf.py rename to schieber/trumpf.py index 945710e..e22909b 100644 --- a/pyschieber/trumpf.py +++ b/schieber/trumpf.py @@ -1,6 +1,6 @@ from enum import Enum -from pyschieber.suit import Suit +from schieber.suit import Suit Trumpf = Enum('Trumpf', ['OBE_ABE', 'UNDE_UFE'] + [str(suit.name) for suit in Suit] + ['SCHIEBEN']) diff --git a/setup.py b/setup.py index 995f06a..0236e4f 100644 --- a/setup.py +++ b/setup.py @@ -1,17 +1,15 @@ -from setuptools import setup, find_packages +import setuptools -with open('README.md') as f: - readme = f.read() - - setup( - name='pyschieber', - version='1.3.0', - description='pyschieber is a terminal application of the popular swiss card game Schieber and provides an API to the game', - long_description=readme, - author='Samuel Kurath', - author_email='samuel.kurath@gmail.com', - url='https://github.com/Murthy10/pyschieber', - license='MIT', - packages=find_packages(exclude=('tests', 'docs')), - scripts=['bin/pyschieber'], - ) +setuptools.setup( + name='schieber', + version='0.1.10', + description='Schieber is a terminal application of the popular Swiss card game Schieber and provides an API to the game', + long_description=open('README.md', "r").read(), + long_description_content_type="text/markdown", + author='Joel Niklaus', + author_email='me@joelniklaus.ch', + url='https://github.com/JoelNiklaus/schieber', + license=open('LICENSE', "r").read(), + packages=setuptools.find_packages(exclude=('tests', 'docs')), + scripts=['bin/schieber'], +) diff --git a/tests/example/__init__.py b/tests/benchmarks/__init__.py similarity index 100% rename from tests/example/__init__.py rename to tests/benchmarks/__init__.py diff --git a/tests/benchmarks/benchmark_challenge_player.py b/tests/benchmarks/benchmark_challenge_player.py new file mode 100644 index 0000000..1f480e7 --- /dev/null +++ b/tests/benchmarks/benchmark_challenge_player.py @@ -0,0 +1,38 @@ +import pytest + +from schieber.player.challenge_player.challenge_player import ChallengePlayer +from schieber.player.greedy_player.greedy_player import GreedyPlayer +from schieber.player.random_player import RandomPlayer +from tests.benchmarks.statistical_helper import run_statistics + + +@pytest.mark.statistical +def test_against_random(): + players = [ChallengePlayer(name='ChallengeActor'), RandomPlayer(name='RandomOpponent1'), + ChallengePlayer(name='ChallengePartner'), RandomPlayer(name='RandomOpponent2')] + + run_statistics(players=players) + + +@pytest.mark.statistical +def test_with_and_against_random(): + players = [ChallengePlayer(name='ChallengeActor'), RandomPlayer(name='RandomOpponent1'), + RandomPlayer(name='RandomPartner'), RandomPlayer(name='RandomOpponent2')] + + run_statistics(players=players) + + +@pytest.mark.statistical +def test_against_greedy(): + players = [ChallengePlayer(name='ChallengeActor'), GreedyPlayer(name='GreedyOpponent1'), + ChallengePlayer(name='ChallengePartner'), GreedyPlayer(name='GreedyOpponent2')] + + run_statistics(players=players) + + +@pytest.mark.statistical +def test_with_and_against_greedy(): + players = [ChallengePlayer(name='ChallengeActor'), GreedyPlayer(name='GreedyOpponent1'), + GreedyPlayer(name='GreedyPartner'), GreedyPlayer(name='GreedyOpponent2')] + + run_statistics(players=players) diff --git a/tests/benchmarks/benchmark_greedy_player.py b/tests/benchmarks/benchmark_greedy_player.py new file mode 100644 index 0000000..091bf15 --- /dev/null +++ b/tests/benchmarks/benchmark_greedy_player.py @@ -0,0 +1,19 @@ +import pytest + +from schieber.player.greedy_player.greedy_player import GreedyPlayer +from schieber.player.random_player import RandomPlayer +from tests.benchmarks.statistical_helper import run_statistics + + +@pytest.mark.statistical +def test_against_random(): + players = [GreedyPlayer(name='GreedyActor'), RandomPlayer(name='RandomOpponent1'), + GreedyPlayer(name='GreedyPartner'), RandomPlayer(name='RandomOpponent2')] + run_statistics(players=players) + + +@pytest.mark.statistical +def test_with_and_against_random(): + players = [GreedyPlayer(name='GreedyActor'), RandomPlayer(name='RandomOpponent1'), + RandomPlayer(name='RandomPartner'), RandomPlayer(name='RandomOpponent2')] + run_statistics(players=players) diff --git a/tests/benchmarks/benchmark_model_player.py b/tests/benchmarks/benchmark_model_player.py new file mode 100644 index 0000000..c818cfd --- /dev/null +++ b/tests/benchmarks/benchmark_model_player.py @@ -0,0 +1,53 @@ +import pytest +from schieber.player.challenge_player.challenge_player import ChallengePlayer + +from schieber.player.greedy_player.greedy_player import GreedyPlayer +from schieber.player.model_player import ModelPlayer +from schieber.player.random_player import RandomPlayer +from tests.benchmarks.statistical_helper import run_statistics + + +@pytest.mark.statistical +def test_against_random(): + players = [ModelPlayer(name='ModelActor'), RandomPlayer(name='RandomOpponent1'), + ModelPlayer(name='ModelPartner'), RandomPlayer(name='RandomOpponent2')] + run_statistics(players=players) + + +@pytest.mark.statistical +def test_with_and_against_random(): + players = [ModelPlayer(name='ModelActor'), RandomPlayer(name='RandomOpponent1'), + RandomPlayer(name='RandomPartner'), RandomPlayer(name='RandomOpponent2')] + run_statistics(players=players) + + +@pytest.mark.statistical +def test_against_greedy(): + players = [ModelPlayer(name='ModelActor'), GreedyPlayer(name='GreedyOpponent1'), + ModelPlayer(name='ModelPartner'), GreedyPlayer(name='GreedyOpponent2')] + + run_statistics(players=players) + + +@pytest.mark.statistical +def test_with_and_against_greedy(): + players = [ModelPlayer(name='ModelActor'), GreedyPlayer(name='GreedyOpponent1'), + GreedyPlayer(name='GreedyPartner'), GreedyPlayer(name='GreedyOpponent2')] + + run_statistics(players=players) + + +@pytest.mark.statistical +def test_against_challenge(): + players = [ModelPlayer(name='ModelActor'), ChallengePlayer(name='ChallengeOpponent1'), + ModelPlayer(name='ModelPartner'), ChallengePlayer(name='ChallengeOpponent2')] + + run_statistics(players=players) + + +@pytest.mark.statistical +def test_with_and_against_challenge(): + players = [ModelPlayer(name='ModelActor'), ChallengePlayer(name='ChallengeOpponent1'), + ChallengePlayer(name='ChallengePartner'), ChallengePlayer(name='ChallengeOpponent2')] + + run_statistics(players=players) diff --git a/tests/benchmarks/models/stich-higher-learning-rate_env=Schieber-v0_gamma=0.89_nsteps=90_learning_rate=0.001_policy=JassPolicy_model=PPO2_time=2019-01-13_12:08:28_final.pkl b/tests/benchmarks/models/stich-higher-learning-rate_env=Schieber-v0_gamma=0.89_nsteps=90_learning_rate=0.001_policy=JassPolicy_model=PPO2_time=2019-01-13_12:08:28_final.pkl new file mode 100644 index 0000000..8ab5cb1 Binary files /dev/null and b/tests/benchmarks/models/stich-higher-learning-rate_env=Schieber-v0_gamma=0.89_nsteps=90_learning_rate=0.001_policy=JassPolicy_model=PPO2_time=2019-01-13_12:08:28_final.pkl differ diff --git a/tests/example/statistical_helper.py b/tests/benchmarks/statistical_helper.py similarity index 73% rename from tests/example/statistical_helper.py rename to tests/benchmarks/statistical_helper.py index fddbd8e..454c274 100644 --- a/tests/example/statistical_helper.py +++ b/tests/benchmarks/statistical_helper.py @@ -1,13 +1,10 @@ from timeit import default_timer as timer -from pyschieber.tournament import Tournament +from schieber.tournament import Tournament -def run_statistics(players): - point_limit = 1000 - number_of_tournaments = 1000 - - tournament = Tournament(point_limit=point_limit) +def run_statistics(players, number_of_tournaments=10, point_limit=1000): + tournament = Tournament(point_limit=point_limit, seed=42) [tournament.register_player(player=player) for player in players] team_1_won = 0 @@ -25,7 +22,7 @@ def run_statistics(players): end = timer() print("\nTo run {0} tournaments it took {1:.2f} seconds.".format(number_of_tournaments, end - start)) - difference = abs(team_1_won - team_2_won) + difference = team_1_won - team_2_won print("Difference: ", difference) print("Team 1: ", team_1_won) print("Team 2: ", team_2_won) diff --git a/tests/helpers/test_game_helper.py b/tests/helpers/test_game_helper.py index 6c21559..b9126be 100644 --- a/tests/helpers/test_game_helper.py +++ b/tests/helpers/test_game_helper.py @@ -1,9 +1,9 @@ -from pyschieber.suit import Suit +from schieber.suit import Suit -from pyschieber.card import Card -from pyschieber.deck import Deck +from schieber.card import Card +from schieber.deck import Deck -from pyschieber.helpers.game_helper import split_card_values_by_suit, split_cards_by_suit +from schieber.helpers.game_helper import split_card_values_by_suit, split_cards_by_suit def test_split_card_values_by_suit(): diff --git a/tests/player/challenge_player/strategy/mode/test_bottom_up_mode.py b/tests/player/challenge_player/strategy/mode/test_bottom_up_mode.py index 3fe10ed..a7c7d76 100644 --- a/tests/player/challenge_player/strategy/mode/test_bottom_up_mode.py +++ b/tests/player/challenge_player/strategy/mode/test_bottom_up_mode.py @@ -1,8 +1,8 @@ import pytest -from pyschieber.card import Card -from pyschieber.suit import Suit -from pyschieber.player.challenge_player.strategy.mode.bottom_up_mode import BottomUpMode +from schieber.card import Card +from schieber.suit import Suit +from schieber.player.challenge_player.strategy.mode.bottom_up_mode import BottomUpMode @pytest.fixture diff --git a/tests/player/challenge_player/strategy/mode/test_mode.py b/tests/player/challenge_player/strategy/mode/test_mode.py index 0d4f807..5fd010f 100644 --- a/tests/player/challenge_player/strategy/mode/test_mode.py +++ b/tests/player/challenge_player/strategy/mode/test_mode.py @@ -1,8 +1,8 @@ import pytest -from pyschieber.card import Card -from pyschieber.suit import Suit -from pyschieber.player.challenge_player.strategy.mode.mode import Mode +from schieber.card import Card +from schieber.suit import Suit +from schieber.player.challenge_player.strategy.mode.mode import Mode @pytest.fixture diff --git a/tests/player/challenge_player/strategy/mode/test_top_down_mode.py b/tests/player/challenge_player/strategy/mode/test_top_down_mode.py index 8748e16..e52159e 100644 --- a/tests/player/challenge_player/strategy/mode/test_top_down_mode.py +++ b/tests/player/challenge_player/strategy/mode/test_top_down_mode.py @@ -1,8 +1,8 @@ import pytest -from pyschieber.card import Card -from pyschieber.suit import Suit -from pyschieber.player.challenge_player.strategy.mode.top_down_mode import TopDownMode +from schieber.card import Card +from schieber.suit import Suit +from schieber.player.challenge_player.strategy.mode.top_down_mode import TopDownMode @pytest.fixture diff --git a/tests/player/challenge_player/strategy/mode/test_trumpf_color_mode.py b/tests/player/challenge_player/strategy/mode/test_trumpf_color_mode.py index e9f9f27..eae8981 100644 --- a/tests/player/challenge_player/strategy/mode/test_trumpf_color_mode.py +++ b/tests/player/challenge_player/strategy/mode/test_trumpf_color_mode.py @@ -1,8 +1,8 @@ import pytest -from pyschieber.card import Card -from pyschieber.suit import Suit -from pyschieber.player.challenge_player.strategy.mode.trumpf_color_mode import TrumpfColorMode +from schieber.card import Card +from schieber.suit import Suit +from schieber.player.challenge_player.strategy.mode.trumpf_color_mode import TrumpfColorMode @pytest.mark.parametrize("suit, cards, score", [ diff --git a/tests/player/challenge_player/strategy/test_jass_strategy.py b/tests/player/challenge_player/strategy/test_jass_strategy.py index b16a8ee..5070636 100644 --- a/tests/player/challenge_player/strategy/test_jass_strategy.py +++ b/tests/player/challenge_player/strategy/test_jass_strategy.py @@ -1,10 +1,10 @@ import pytest -from pyschieber.card import Card -from pyschieber.suit import Suit -from pyschieber.trumpf import Trumpf -from pyschieber.player.challenge_player.strategy.jass_strategy import JassStrategy -from pyschieber.player.challenge_player.challenge_player import ChallengePlayer +from schieber.card import Card +from schieber.suit import Suit +from schieber.trumpf import Trumpf +from schieber.player.challenge_player.strategy.jass_strategy import JassStrategy +from schieber.player.challenge_player.challenge_player import ChallengePlayer @pytest.fixture diff --git a/tests/player/challenge_player/test_challenge_player.py b/tests/player/challenge_player/test_challenge_player.py deleted file mode 100644 index bbfc9d0..0000000 --- a/tests/player/challenge_player/test_challenge_player.py +++ /dev/null @@ -1,22 +0,0 @@ -import pytest - -from pyschieber.player.challenge_player.challenge_player import ChallengePlayer -from pyschieber.player.greedy_player.greedy_player import GreedyPlayer -from pyschieber.player.random_player import RandomPlayer -from tests.example.statistical_helper import run_statistics - - -@pytest.mark.statistical -def test_greedy(): - players = [GreedyPlayer(name='Greedy1'), RandomPlayer(name='Track1'), GreedyPlayer(name='Greedy2'), - RandomPlayer(name='Track2')] - - run_statistics(players=players) - - -@pytest.mark.statistical -def test_challenge(): - players = [ChallengePlayer(name='Trick1'), GreedyPlayer(name='Greedy1'), ChallengePlayer(name='Trick2'), - GreedyPlayer(name='Greedy2')] - - run_statistics(players=players) diff --git a/tests/player/greedy_player/test_example_trumpf.py b/tests/player/greedy_player/test_example_trumpf.py index 5bceb51..77aae16 100644 --- a/tests/player/greedy_player/test_example_trumpf.py +++ b/tests/player/greedy_player/test_example_trumpf.py @@ -1,9 +1,9 @@ import pytest -from pyschieber.card import Card -from pyschieber.player.greedy_player.trumpf_decision import choose_trumpf, TrumpfType -from pyschieber.suit import Suit -from pyschieber.trumpf import Trumpf +from schieber.card import Card +from schieber.player.greedy_player.trumpf_decision import choose_trumpf, TrumpfType +from schieber.suit import Suit +from schieber.trumpf import Trumpf @pytest.mark.parametrize("cards, trumpf, trumpf_type", [ diff --git a/tests/player/greedy_player/test_greedy_player.py b/tests/player/greedy_player/test_greedy_player.py deleted file mode 100644 index dbd7799..0000000 --- a/tests/player/greedy_player/test_greedy_player.py +++ /dev/null @@ -1,12 +0,0 @@ -import pytest - -from pyschieber.player.greedy_player.greedy_player import GreedyPlayer -from pyschieber.player.random_player import RandomPlayer -from tests.example.statistical_helper import run_statistics - - -@pytest.mark.statistical -def test_greedy(): - players = [GreedyPlayer(name='Greedy1'), RandomPlayer(name='Track'), GreedyPlayer(name='Greedy2'), - RandomPlayer(name='Track')] - run_statistics(players=players) diff --git a/tests/player/test_base_player.py b/tests/player/test_base_player.py index 33380a1..e714275 100644 --- a/tests/player/test_base_player.py +++ b/tests/player/test_base_player.py @@ -1,4 +1,4 @@ -from pyschieber.player.base_player import BasePlayer +from schieber.player.base_player import BasePlayer from itertools import count diff --git a/tests/player/test_external_player.py b/tests/player/test_external_player.py new file mode 100644 index 0000000..1df5eaf --- /dev/null +++ b/tests/player/test_external_player.py @@ -0,0 +1,66 @@ +import logging +import sys +import time +from threading import Thread + +import pytest +from timeit import default_timer as timer +from math import sqrt, floor + +from schieber.suit import Suit + +from schieber.card import Card + +from schieber.game import Game + +from schieber.team import Team + +from schieber.player.external_player import ExternalPlayer + +from schieber.player.random_player import RandomPlayer +from schieber.tournament import Tournament + + +def test_control(): + logging.basicConfig(stream=sys.stdout, level=logging.DEBUG, format='%(asctime)s %(levelname)-8s %(message)s') + + players = [RandomPlayer(name='Tick', ), RandomPlayer(name='Trick'), + RandomPlayer(name='Track'), ExternalPlayer(name='Dagobert')] + + team_1 = Team(players=[players[0], players[2]]) + team_2 = Team(players=[players[1], players[3]]) + teams = [team_1, team_2] + game = Game(teams, point_limit=1000, use_counting_factor=False, seed=1) + + thread = Thread(target=game.play_endless) + thread.start() + + player = players[3] + action = Card(Suit.ROSE, 9) + + print("Test starts") + + assert not game.stop_playing + + player.get_observation() + assert player.before_first_stich() + print(len(player.cards)) + player.set_action(action) + + for i in range(7): + print(f"Round {i}") + player.get_observation() + print(len(player.cards)) + assert not player.before_first_stich() + player.set_action(action) + print(len(player.cards)) + + print(len(player.cards)) + assert player.at_last_stich() + + game.endless_play_control.acquire() + game.stop_playing = True + game.endless_play_control.notify_all() + game.endless_play_control.release() + + assert game.stop_playing diff --git a/tests/player/test_random_player.py b/tests/player/test_random_player.py index 46bf618..76a74a5 100644 --- a/tests/player/test_random_player.py +++ b/tests/player/test_random_player.py @@ -2,20 +2,24 @@ from timeit import default_timer as timer from math import sqrt, floor -from pyschieber.player.random_player import RandomPlayer -from pyschieber.tournament import Tournament +from schieber.player.random_player import RandomPlayer +from schieber.tournament import Tournament @pytest.mark.statistical def test_is_random(): + """ + This test may fail sometimes if the number of tournaments is too low. But for performance reasons, this number is set low. + :return: + """ point_limit = 1000 - number_of_tournaments = 1000 + number_of_tournaments = 10 mean = number_of_tournaments * 0.5 # assume that a RandomPlayer has a 50% chance to win variance = mean * (1 - 0.5) standard_deviation = int(floor(sqrt(variance))) random_players = [RandomPlayer(name=i) for i in range(4)] - tournament = Tournament(point_limit=point_limit) + tournament = Tournament(point_limit=point_limit, seed=1) [tournament.register_player(player=player) for player in random_players] team_1_won = 0 @@ -37,4 +41,4 @@ def test_is_random(): print("Difference: ", difference) print("Team 1: ", team_1_won) print("Team 2: ", team_2_won) - assert difference in range(0, 2 * standard_deviation) + assert difference in range(0, 4 * standard_deviation) # if a harder constraint is required replace 4 by 2 or 1 diff --git a/tests/rules/test_count_rules.py b/tests/rules/test_count_rules.py index ca49bfa..c2b096c 100644 --- a/tests/rules/test_count_rules.py +++ b/tests/rules/test_count_rules.py @@ -1,8 +1,8 @@ import pytest -from pyschieber.deck import Deck -from pyschieber.trumpf import Trumpf -from pyschieber.rules.count_rules import count_stich +from schieber.deck import Deck +from schieber.trumpf import Trumpf +from schieber.rules.count_rules import count_stich @pytest.mark.parametrize("trumpf", list(Trumpf)[:6]) diff --git a/tests/rules/test_stich_rules.py b/tests/rules/test_stich_rules.py index b310fe4..1ae6d95 100644 --- a/tests/rules/test_stich_rules.py +++ b/tests/rules/test_stich_rules.py @@ -1,12 +1,12 @@ import pytest -from pyschieber.rules.stich_rules import stich_rules, card_allowed, allowed_cards, is_trumpf_under, does_under_trumpf, \ +from schieber.rules.stich_rules import stich_rules, card_allowed, allowed_cards, is_trumpf_under, does_under_trumpf, \ is_chosen_card_best_trumpf -from pyschieber.trumpf import Trumpf -from pyschieber.card import Card -from pyschieber.player.random_player import RandomPlayer -from pyschieber.stich import PlayedCard -from pyschieber.suit import Suit +from schieber.trumpf import Trumpf +from schieber.card import Card +from schieber.player.random_player import RandomPlayer +from schieber.stich import PlayedCard +from schieber.suit import Suit @pytest.fixture(scope="module", autouse=True) diff --git a/tests/rules/test_trumpf_rules.py b/tests/rules/test_trumpf_rules.py index 02b6c8d..3592d66 100644 --- a/tests/rules/test_trumpf_rules.py +++ b/tests/rules/test_trumpf_rules.py @@ -1,7 +1,7 @@ import pytest -from pyschieber.trumpf import Trumpf -from pyschieber.rules.trumpf_rules import trumpf_allowed +from schieber.trumpf import Trumpf +from schieber.rules.trumpf_rules import trumpf_allowed @pytest.mark.parametrize("trumpf, geschoben, result", [ diff --git a/tests/test_card.py b/tests/test_card.py index f93e3a0..910043e 100644 --- a/tests/test_card.py +++ b/tests/test_card.py @@ -1,8 +1,15 @@ import pytest -from pyschieber.suit import Suit +from schieber.suit import Suit -from pyschieber.card import Card, from_string_to_card +from schieber.card import Card, from_string_to_card, from_card_to_tuple, from_tuple_to_card, from_string_to_tuple, \ + from_tuple_to_string, from_index_to_card, from_card_to_index, from_index_to_string, from_string_to_index, \ + from_card_to_onehot, from_onehot_to_card, from_onehot_to_string, from_string_to_onehot + + +@pytest.fixture(scope='module') +def card(): + return Card(suit=Suit.ROSE, value=6) @pytest.fixture(scope='module') @@ -11,8 +18,18 @@ def card_string(): @pytest.fixture(scope='module') -def card(): - return Card(suit=Suit.ROSE, value=6) +def card_tuple(): + return 0, 0 + + +@pytest.fixture(scope='module') +def card_index(): + return 1 + + +@pytest.fixture(scope='module') +def card_onehot(): + return [1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0] def test_card_str(card, card_string): @@ -23,6 +40,54 @@ def test_from_string_to_card(card, card_string): assert card == from_string_to_card(card_string) +def test_from_card_to_tuple(card, card_tuple): + assert card_tuple == from_card_to_tuple(card) + + +def test_from_tuple_to_card(card, card_tuple): + assert card == from_tuple_to_card(card_tuple) + + +def test_from_string_to_tuple(card_tuple, card_string): + assert card_tuple == from_string_to_tuple(card_string) + + +def test_from_tuple_to_string(card_tuple, card_string): + assert card_string == from_tuple_to_string(card_tuple) + + +def test_from_card_to_index(card, card_index): + assert card_index == from_card_to_index(card) + + +def test_from_index_to_card(card, card_index): + assert card == from_index_to_card(card_index) + + +def test_from_index_to_string(card_string, card_index): + assert card_string == from_index_to_string(card_index) + + +def test_from_string_to_index(card_string, card_index): + assert card_index == from_string_to_index(card_string) + + +def test_from_card_to_onehot(card, card_onehot): + assert card_onehot == from_card_to_onehot(card) + + +def test_from_onehot_to_card(card, card_onehot): + assert card == from_onehot_to_card(card_onehot) + + +def test_from_onehot_to_string(card_string, card_onehot): + assert card_string == from_onehot_to_string(card_onehot) + + +def test_from_string_to_onehot(card_string, card_onehot): + assert card_onehot == from_string_to_onehot(card_string) + + def test_from_string_not_card(card): rose_7 = Card(suit=Suit.ROSE, value=7) assert card != from_string_to_card(str(rose_7)) diff --git a/tests/test_dealer.py b/tests/test_dealer.py index f6a6d2a..9db946d 100644 --- a/tests/test_dealer.py +++ b/tests/test_dealer.py @@ -1,8 +1,8 @@ import pytest -from pyschieber.player.random_player import RandomPlayer +from schieber.player.random_player import RandomPlayer -from pyschieber.dealer import Dealer +from schieber.dealer import Dealer @pytest.fixture(scope="function") diff --git a/tests/test_deck.py b/tests/test_deck.py index 5005270..ddea579 100644 --- a/tests/test_deck.py +++ b/tests/test_deck.py @@ -1,5 +1,5 @@ -from pyschieber.card import Card -from pyschieber.deck import Deck +from schieber.card import Card +from schieber.deck import Deck def test_deck_count(): diff --git a/tests/test_game.py b/tests/test_game.py index 4c72691..23ba0fd 100644 --- a/tests/test_game.py +++ b/tests/test_game.py @@ -1,14 +1,14 @@ import pytest -from pyschieber.rules.count_rules import counting_factor +from schieber.rules.count_rules import counting_factor -from pyschieber.deck import Deck +from schieber.deck import Deck -from pyschieber.trumpf import Trumpf +from schieber.trumpf import Trumpf -from pyschieber.player.random_player import RandomPlayer +from schieber.player.random_player import RandomPlayer -from pyschieber.game import Game, get_player_index -from pyschieber.team import Team +from schieber.game import Game, get_player_index +from schieber.team import Team @pytest.mark.parametrize("start_key, last_key", [ @@ -39,6 +39,20 @@ def test_game(): assert len(player.cards) == 0 +def test_reset_points(): + random_players = [RandomPlayer(name=i) for i in range(4)] + team_1 = Team(players=[random_players[0], random_players[1]]) + team_2 = Team(players=[random_players[1], random_players[2]]) + teams = [team_1, team_2] + game = Game(teams=teams, point_limit=1500) + game.play() + + game.reset_points() + + for team in game.teams: + assert team.points == 0 + + @pytest.mark.parametrize("start_key, next_key", [ (0, 1), (1, 2), diff --git a/tests/test_team.py b/tests/test_team.py index a6bf2de..6ee9ca4 100644 --- a/tests/test_team.py +++ b/tests/test_team.py @@ -1,7 +1,7 @@ import pytest -from pyschieber.player.random_player import RandomPlayer -from pyschieber.team import Team +from schieber.player.random_player import RandomPlayer +from schieber.team import Team @pytest.mark.parametrize("points, point_limit, won", [ diff --git a/tests/test_tournament.py b/tests/test_tournament.py index 44b520f..d896d92 100644 --- a/tests/test_tournament.py +++ b/tests/test_tournament.py @@ -1,6 +1,6 @@ import pytest -from pyschieber.player.random_player import RandomPlayer -from pyschieber.tournament import Tournament +from schieber.player.random_player import RandomPlayer +from schieber.tournament import Tournament @pytest.fixture(scope='module') diff --git a/tests/test_trumpf.py b/tests/test_trumpf.py index 5a6ff8c..ff84519 100644 --- a/tests/test_trumpf.py +++ b/tests/test_trumpf.py @@ -1,4 +1,4 @@ -from pyschieber.trumpf import Trumpf +from schieber.trumpf import Trumpf def test_trumpf_count():