Раздел «Язык Си».Cow006TextDraft:

game.py - Game - сама игра

from player import Cow006Player


class Cow006Game(object):

    def __init__(self, players, rows=4, maxinrow=6, maxhand=10):
        """
        Создает экземпляр игры
        :param players: список игроков
        :param rows: количество рядов на столе
        :param maxinrow: когда "сгорает" карта в ряду
        :param maxhand: начальное количество карт в руке
        """
        self.players = players
        self.maxhand = maxhand

        deck = Cow006Deck()
        deck.shuffle()

        # из колоды раздают карты на стол и игрокам, больше она не нужна
        self.table = Cow006Table(rows, maxinrow)
        self.table.init_cards(deck)

        for p in self.players:
            p.init_hand(deck, maxhand)


    def run(self):
        """
        Процесс игры до конца
        :return: None
        """
        # пока не закончатся карты на руке
        for step in range(self.maxhand):
            print(f"Begin step {step}")
            print(self.table)

            # все игроки кладут закрытые карты
            choosen_cards = {}
            for p in self.players:
                choosen_cards[p.choose_card(self.table)] = p
            print('Choose cards:'+ ', '.join(['{} ({})'.format(c, p.name) for c, p in choosen_cards.items()]))

            # карты открываются и выкладываются на стол
            for card, player in sorted(choosen_cards.items()):
                print('Resolve {} from {}'.format(card, player))
                self.resolve_card(card, player)
                print(self.table)

    def end(self):
        """
        Считаем очки, возвращаем список игроков по возрастанию очков
        Напечатать список, поздравить победителя
        :return:
        """
        pass

    def resolve_card(self, card, player):
        """
        Разыгрываем карту card игрока player по правилам
        :param card: карта
        :param player: игрок, положивший эту карту
        :return:
        """
        # ищем в какой ряд подходит эта карта
        row = self.table.find_row(card)
        print('Row is', row)

        # если карту нельзя положить ни в один ряд (она очень маленькая)
        if row is None:
            ''' My card is too small'''
            # игрок выбирает, в какой ряд положить
            r = player.choose_row(self.table, card)
            # кладет карту в конец ряда
            r.add_card(card)
            # забирает все карты, кроме последней
            cutted = r.cut()
            # карты, что забрал игрок, добавляются на его счет
            player.add_score(cutted)

        else:
            # если карту можно положить в ряд, кладем ее в ряд
            row.add_card(card)
            # это шестая карта?
            if row.overflow():
                # забираем карты, кроме последней и добавляем их в счет игрока
                cutted = row.cut()
                player.add_score(cutted)


if __name__ == '__main__':
    g = Cow006Game([
            Cow006Player("Alice"),
            Cow006Player("Bob"),
            Cow006Player("Charle")
            ],
            rows=3, maxinrow=4, maxhand=5
            )
    g.run()
    g.end()

Надо потом обработать победителей. Но первым шагом просто запустить игру в автоматическом режиме.

player.py - Player - один игрок

Игрок пока только компьютерный

from Card import Cow006Card as Card

class Cow006Player(object):
    def __init__(self, name):
        self.name = name
        self.hand = Hand()
        self._score = 0

    def __repr__(self):
        return '{}: {}'.format(self.name, self.hand)

    def init_hand(self, deck, maxhand):
        """
        Добавляет карты в руку игрока из колоды, maxhand штук
        :param deck: колода, из которой добавляем карты
        :param maxhand: количество карт на руке до начала игры
        :return:
        """
        if maxhand > len(deck):
            raise ValueError(f"Need {maxhand} cards, deck has only {len(deck)} cards")

    def choose_card(self, table=None):
        """ выбираем какую карту играть (в начале хода)"""
        # todo: случайную
        return self.hand.draw()

    def choose_row(self, table, card):
        """ выбирает, какой ряд взять со стола и положить в начало ряда карту card"""
        # todo: случайную
        return table[0]

    def score(self):
        """ считаем очки игрока """
        return self._score

    def add_score(self, card_list):
        """ добавляем в очки игрока очки из карт card_list """
        pass

Надо добавить обработку очков. И тесты.

table.py - Table + Row - стол и ряды стола

from Card import Cow006Card as Card
from container import Container


class Row(Container):
    def __init__(self, maxinrow=6):
        super().__init__()
        self.maxinrow = maxinrow  # какая корова "проваливает" ряд

    def __lt__(self, other):
        pass

    def top(self):
        """ возвращает последнюю карту в ряду """
        pass

    def overflow(self):
        """ проверяет, есть 6 коров в ряду (True) или еще нет (False)"""
        pass

    def acceptable(self, card):
        """ эту карту card можно положить в конец этого ряда? """
        pass

    def cut(self):
        """ Убирает из ряда все карты, кроме последней. Возвращает список убранных карт"""
        pass


class Table:
    def __init__(self, deck, rows=4, maxinrow=6):
        self.maxinrow = maxinrow
        self.rows = [Row(maxinrow) for r in range(rows)]

    def init_cards(self, deck):
        for r in self.rows:
            r.add_card(deck.draw())

    def __repr__(self):
        return '\n'.join(['Row{} : {}'.format(i, r) for i, r in enumerate(self.rows)])

    def find_row(self, card):
        """ ищет, в какие ряды можно положить эту карту, возвращает ряд или None,
        если карту нельзя положить ни в один ряд """
        pass

    def __getitem__(self, i):
        return self.rows[i]

Тесты сначала Row, потом Table.

Пример теста метода top класса Row:

class TestRow(TestCase):
    def create_row(self, card_numbers):
        row = Row()
        for n in card_numbers:
           row.add_card(Card(n))
        return row


    def test_top(self):
        ar = [13, 25, 86]
        row = self.create_row(ar)
        self.assertEqual(row.top(), Card(ar[-1]))
        row.add_card(Card(100))
        self.assertEqual(row.top(), Card(100))

        # тест ряда с единственной картой (ряда без карт быть не должно!)
        ar = [17]
        row = self.create_row(ar)
        self.assertEqual(row.top(), Card(ar[0]))

Если не понимаете зачем метод, не реализуете его, а кидаете в нем NotImplementedYet?. В тесте проверяете, что кинуто именно это исключение. Как поймете, зачем метод - переписываете и метод, и тест.

container.py - Container - базовый класс коллекция карт

Заметьте, карты тут не нужны. TODO (мне) - сразу давать с типами.

import random

# from Card import Cow006Card as Card


class Container:
    def __init__(self):
        self.cards = []

    def __repr__(self):
        return ' '.join(map(str, self.cards))

    def __len__(self):
        return len(self.cards)

    def __getitem__(self, i):
        return self.cards[i]

    def add_card(self, c):
        self.cards.append(c)

    def draw(self):
        return self.cards.pop()

    def shuffle(self):
        random.shuffle(self.cards)

вариант тестирования метода add_card

    def test_add_card(self):
        a = Container()
        ar = [Card(10), Card(5), Card(67)]
        for c in ar:
            a.add_card(c)
        self.assertEqual(str(a), ' '.join(map(str, ar)))

Дописать тесты.

hand.py - Hand - рука 1 игрока

from cow006_container import Container

class Hand(Container):
  pass

Такой класс можно не тестировать.

deck.py - Deck - колода

from cow006_container import Container
from cow006_card import Cow006Card as Card

class Cow006Deck(Container):
    def __init__(self):
        super().__init__()
        for c in Card.all_cards():
            self.add_card(c)

Добавить бы ограничение на диапазон карт.

card.py - Card - 1 карта + делаем набор из всех возможных карт

class Cow006Card:
    def __init__(self, n):
        self.n = n
        self.score = 1  # todo

    def __repr__(self):
        return str(self.n)

    def __lt__(self, other):
        return self.n < other.n

    def __eq__(self, other):
        return self.n == other.n

    def __hash__(self):
        return self.n

    @staticmethod
    def all_cards(maxsize=104):
        return [Cow006Card(i + 1) for i in range(maxsize)]

Тестируем его в test_card.py:

from Card import Cow006Card as Card

import unittest


class CardTest(unittest.TestCase):
    def test_print(self):
        c = Card(10)
        self.assertEqual(str(c), '10')

        c = Card(42)
        self.assertEqual(str(c), '42')

    def test_eq(self):
        c1 = Card(10)
        c2 = Card(10)
        c3 = Card(30)
        self.assertEqual(c1, c2)
        self.assertNotEqual(c1, c3)

    def test_ls(self):
        c1 = Card(18)
        c2 = Card(42)
        c3 = Card(103)
        self.assertLess(c1, c2)
        self.assertLess(c1, c3)
        self.assertLess(c2, c3)


    def test_all_cards(self):
        deck = Card.all_cards(5)
        self.assertEqual(deck, [Card(1), Card(2), Card(3), Card(4), Card(5)])

if __name__ == '__main__':
    unittest.main()

Дописываем тесты и методы.

Тестирование исключений

То, сообщение исключения ровно такое, как мы ожидаем:

Более пристальное исследование исключения. Сначала получаем исключение в context manager, потом его исследуем

https://docs.python.org/2/library/unittest.html#unittest.TestCase.assertRaises

class DailyEntriesTests(TestCase):
def test_cant_have_ip_and_user(self):
    u = createUser(False)
    de = createDailyEntry(u, "1.1.1.1", 1)
    with self.assertRaises(ValidationError) as cm:
        de.full_clean()

    # this line bombs - message doesn't exist. I also tried "error_code" like I saw in the documentation, but that doesn't work
    print(cm.exception.message)

    self.assertTrue(cm.exception.message.contains("Both"))

Или просто проверяем, что сообщение содержит Both:

with self.assertRaisesRegexp(ValidationError, "Both"):
    de.full_clean()

-- TatyanaDerbysheva - 07 Nov 2019