Пишем на Питоне игру в кости

newbie

Опубликован:  2018-09-05T08:37:31.155753Z
Отредактирован:  2018-09-06T07:25:17.189739Z
Данная презентация описывает процесс разработки на Python3 в стандартном окружении Linux минимального эмулятора игры в кости. Под катом представлены листинги интерактивной отладки с необходимыми пояснениями, демонстрационный листинг игровой сессии и код полученной программы. Спойлеры ниже кликабельны, комментарии приветствуются.

1. Необходимый набор инструментов

Для реализации задуманного мне понадобится:

  • рабочее окружение Linux;
  • установленный и настроенный Python3;
  • Geany или любой текстовый редактор с подсветкой синтаксиса и терминал.

Всё это можно найти практически в любом дистрибутиве Linux.

2. Подготовка рабочего пространства

Запускаю Geany.

XmkBJFjFOw.jpg

В окне Geany перехожу на вкладку Терминал. В терминале создаю в домашней папке рабочий каталог, в которому буду совершать все действия при проектировании приложения, для этого даю команду:

newbie@stretch:~$ mkdir -p workspace/dice
newbie@stretch:~$

Захожу в созданный каталог:

newbie@stretch:~$ cd workspace/dice/
newbie@stretch:~/workspace/dice$ 

Теперь, находясь в этом каталоге, создаю файл dice.py, для этого вбиваю в терминале команду:

newbie@stretch:~/workspace/dice$ geany dice.py
newbie@stretch:~/workspace/dice$

Перемещаюсь в окне Geany в текстовый редактор и пишу в файл dice.py следующий код:

#!/usr/bin/env python3


def main():
    print('Hello, World!')


if __name__ == '__main__':
    main()

Здесь всё просто. Первой строчкой уточняю, что программа должна исполняться интерпретатором python3. Далее создаю функцию main, которая выводит на экран типичное приветствие Hello, World!, а затем посредством стандартной идиомы Питона вызываю эту функцию только в случаях, когда программа исполнена. При импорте модуля это действие осуществляться не будет.

Сохраняю файл сочетанием ctrl+s. Возвращаюсь в терминал и для удобства запуска программы задаю этому файлу права на исполнение:

newbie@stretch:~/workspace/dice$ chmod u+x dice.py
newbie@stretch:~/workspace/dice$

и создаю ссылку на этот файл в одном из каталогов $PATH:

newbie@stretch:~/workspace/dice$ sudo ln -s -T ~/workspace/dice/dice.py /usr/local/bin/dice
newbie@stretch:~/workspace/dice$

Теперь программу можно запускать:

newbie@stretch:~/workspace/dice$ dice
Hello, World!
newbie@stretch:~/workspace/dice$ 

Всё, рабочий каталог готов, исполняемый файл создан, можно двигаться дальше...

tY4qsz4qxv.jpg

3. Определение набора аргументов командной строки

Игра в кости в самом общем случае требует:

  1. определить игроков, хотя бы двоих;
  2. определить количество брошенных каждым игроком кубиков или количество бросков одного кубика, совершаемых каждым игроком.

Оба условия могут быть заданы посредством аргументов командной строки. Для обработки аргументов командной строки использую стандартный модуль Питона argparse. Пишу в файл программы (dice.py) следующий код:

#!/usr/bin/env python3

import argparse


def parse_args():
    parser = argparse.ArgumentParser()
    parser.add_argument(
        '-n',
        help='the loop length (default 3)',
        dest='loop',
        action='store',
        default=3,
        type=int,
        choices=(1, 2, 3))
    parser.add_argument(
        '-p',
        nargs='+',
        help='participants, e.g. Name1 Name2 ... NameN',
        dest='play',
        action='store',
        required=True)
    return parser.parse_args()

Здесь задаю два аргумента для командной строки:

  • -n - определяет количество брошенных кубиков, либо количество бросков одного кубика совершаемых каждым игроком, аргумент числовой, может принимать значения 1, 2 или 3, имеет дефолтное значение равное 3 и может быть опущен при вызове программы, сохраняется как int;
  • -p - определяет участников игры, которые в свою очередь определяются по именам, аргумент обязательный, имена вводятся через пробел, количество имён участников не ограничивается, сохраняется список содержащий имена - строки.

Для предварительной отладки аргументов меняю функцию main соответствующим образом:

def main():
    args = parse_args()
    print('Количество бросков: {0}'.format(args.loop))
    print('Игроки:')
    for i in args.play:
        print('\t', i)

Сохраняю изменения в файле сочетанием ctrl+s, перемещаюсь в терминал и пробую различные варианты запуска игры. Минимальная справка:

newbie@stretch:~/workspace/dice$ dice --help
usage: dice [-h] [-n {1,2,3}] -p PLAY [PLAY ...]

optional arguments:
  -h, --help          show this help message and exit
  -n {1,2,3}          the loop length (default 3)
  -p PLAY [PLAY ...]  participants, e.g. Name1 Name2 ... NameN
newbie@stretch:~/workspace/dice$ 

Задаю участников игры:

newbie@stretch:~/workspace/dice$ dice -p First Second
Количество бросков: 3
Игроки:
     First
     Second
newbie@stretch:~/workspace/dice$ 

Задаю количество бросков кубика:

newbie@stretch:~/workspace/dice$ dice -n 2 -p First Second
Количество бросков: 2
Игроки:
     First
     Second
newbie@stretch:~/workspace/dice$ 

Ошибаюсь с аргументами:

newbie@stretch:~/workspace/dice$ dice -n 4 First Second
usage: dice [-h] [-n {1,2,3}] -p PLAY [PLAY ...]
dice: error: argument -n: invalid choice: 4 (choose from 1, 2, 3)
newbie@stretch:~/workspace/dice$ 

Отлично, аргументы заработали. Далее займёмся игроками...

p3b68sNBUd.jpg

4. Описание игрока

Игрок, бросающий кубики, будет описан классом. Этому классу задаю два свойства:

  • имя игрока;
  • полученную игроком сумму в результате броска кубиков.

Бросок кубиков эмулирую стандартным генератором случайных чисел. Количество кубиков задаю при инициализации класса. Полученную игроком сумму считаю посредством стандартной функции sum из набора встроенных функций Питона и записываю в соответствующее свойство класса при инициализации.

Дописываю в dice.py следующий код:

#!/usr/bin/env python3

import argparse

from random import randint


class Player:
    def __init__(self, name, loop):
        self.name = name
        self.score = sum(randint(1, 6) for _ in range(loop))


def parse_args():
     ...

Здесь я импортировал функцию randint из модуля random стандартной библиотеки, посредством которой эмулирую бросок кубиков, затем создал класс Player и определил ему метод инициализации (в народе "дандэринит"), в котором свойствам создаваемых экземпляров этого класса будут присваиваться определённые этим методом значения. Сохраняю файл сочетанием ctrl+s.

Возвращаюсь в терминал, запускаю интерпретатор Python3.

newbie@stretch:~/workspace/dice$ python3
Python 3.5.3 (default, Jan 19 2017, 14:11:04) 
[GCC 6.3.0 20170118] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 

И пробую воспользоваться созданным только что классом.

>>> from dice import Player
>>> player = Player('Игрок', 3)
>>> player.name
'Игрок'
>>> player.score
11
>>> second_player = Player('Второй', 3)
>>> second_player.name
'Второй'
>>> second_player.score
10
>>> exit()

MBQ2DrtjDL.jpg

Отлично, игрок готов.

5. Описание игры

Игра будет описана своим классом. При инициализации передаю этому классу список игроков, в котором каждый игрок идентифицирован именем (строкой). Если в списке только один игрок, в игру будет добавлен виртуальный игрок Machine. При инициализации формирую из полученных имён список, состоящий из экземпляров класса Player.

Дописываю в dice.py следующий код:

class Game:
    def __init__(self, names, loop):
        if len(names) == 1:
            names.append('Machine')
        self.players = [Player(name, loop) for name in names]

Далее, мне понадобится метод, который будет вычислять максимальное значение набранных игроками баллов, выводить на экран результат для каждого участника игры и возвращать список игроков, набравших максимальное количество баллов. Решаем.

...

    def count_scores(self):
        win = max(player.score for player in self.players)
        block = max(len(i.name) for i in self.players) + len(str(win)) + 1
        for player in self.players:
            print("{0}:{1:>{2}}"
                  .format(player.name, player.score, block-len(player.name)))
        return [player.name for player in self.players if player.score == win]

Здесь win - максимальное значение набранных игроками баллов, block - ширина блока для выравнивания результатов при выводе на экран.

Сохраняю файл, возвращаюсь в интерпретатор и тестирую полученный класс и его методы:

>>> from dice import Game
>>> loop = 1
>>> game = Game(['Первый'], loop)
>>> winners = game.count_scores()
Первый:  2
Machine: 2
>>> winners
['Первый', 'Machine']
>>> game = Game(winners, loop)
>>> winners = game.count_scores()
Первый:  5
Machine: 3
>>> winners
['Первый']
>>> exit()

Ln5MxdXblG.jpg

Отлично, в игру автоматически включен виртуальный игрок, так как при инициализации было задано имя только одного игрока.

6. Заключительный аккорд

Вся необходимая инфраструктура в программе создана, но при запуске программы игры не получается. Необходимо изменить функцию main должным образом. Ставлю эксперимент в интерпретаторе.

>>> from dice import Game
>>> loop = 1
>>> game = Game(['Первый'], loop)
>>> winners = game.count_scores()
Первый:  4
Machine: 4
>>> while True:
...    if len(winners) == 1:
...        print('\nCongrats {0}!'.format(winners[0]))
...        break
...    else:
...        print('\nNo winner, one more throw:')
...        game = Game(winners, loop)
...        winners = game.count_scores()
...

No winner, one more throw:
Первый:  6
Machine: 6

No winner, one more throw:
Первый:  3
Machine: 5

Congrats Machine!
>>> 

Машина выиграла. Эксперимент успешный.

U7XUBQ9V2u.jpg

Дописываю в функцию main соответствующие изменения:

def main():
    args = parse_args()
    game = Game(args.play, args.loop)
    winners = game.count_scores()
    while True:
        if len(winners) == 1:
            print('\nCongrats {0}!'.format(winners[0]))
            break
        else:
            print('\nNo winner, one more throw:')
            game = Game(winners, args.loop)
            winners = game.count_scores()

Сохраняю файл и запускаю игру:

newbie@stretch:~/workspace/dice$ dice -n 3 -p Yours_truly
Yours_truly: 11
Machine:     11

No winner, one more throw:
Yours_truly: 16
Machine:     10

Congrats Yours_truly!
newbie@stretch:~/workspace/dice$ 

Машина проиграла. Элементарный эмулятор игры в кости готов.

CUCLCtXZBA.jpg

7. Код программы целиком

dice.py

#!/usr/bin/env python3

import argparse

from random import randint


class Player:
    def __init__(self, name, loop):
        self.name = name
        self.score = sum(randint(1, 6) for _ in range(loop))


class Game:
    def __init__(self, names, loop):
        if len(names) == 1:
            names.append('Machine')
        self.players = [Player(name, loop) for name in names]

    def count_scores(self):
        win = max(player.score for player in self.players)
        block = max(len(i.name) for i in self.players) + len(str(win)) + 1
        for player in self.players:
            print("{0}:{1:>{2}}"
                  .format(player.name, player.score, block-len(player.name)))
        return [player.name for player in self.players if player.score == win]


def parse_args():
    parser = argparse.ArgumentParser()
    parser.add_argument(
        '-n',
        help='the loop length (default 3)',
        dest='loop',
        action='store',
        default=3,
        type=int,
        choices=(1, 2, 3))
    parser.add_argument(
        '-p',
        nargs='+',
        help='participants, e.g. Name1 Name2 ... NameN',
        dest='play',
        action='store',
        required=True)
    return parser.parse_args()


def main():
    args = parse_args()
    game = Game(args.play, args.loop)
    winners = game.count_scores()
    while True:
        if len(winners) == 1:
            print('\nCongrats {0}!'.format(winners[0]))
            break
        else:
            print('\nNo winner, one more throw:')
            game = Game(winners, args.loop)
            winners = game.count_scores()


if __name__ == '__main__':
    main()
Комментарии: