Пишем игру Питон на Python ;). Часть 1

В рамках компании по попытке обучения одного товарища программированию начал писать классическую игру Питон. С правилами: питон двигается по полю, если он что-то ест, то растёт. Если натыкается на стену или на самого себя — игрок проигрывает.

В этой части обучающей статьи, мы научимся вырисовывать питона и управлять им с клавиатуры. Результатом будет что-то вроде:

Для отрисовки графики и получения событий нажатия клавиш будем использовать библиотеку pygame

Итак начнем. Сначала зададим настроечные переменные для игры, как то размер поля, цвета и т.п.:

# объявляем переменные
FPS = 60
width_field=800 # размер поля по ширине
height_field=600 # размер поля по высоте
size_x=20   # количество клеток по ширине
size_y=20   # количество клеток по высоте
color_grid=(0,200,100)  # цвет линий ячеек
fill_grid=(0,0,0)       # цвет игровогого поля
color_python=(50,200,100) # цвет тела питона
step_x=round(width_field/size_x,0)  # шаг клетки по ширине
step_y=round(height_field/size_y,0) # шаг клетки по высоте
python_body=[[round(size_x/2,0),round(size_y/2,0)],[round((size_x-2)/2,0),round(size_y/2,0)],[round((size_x-4)/2,0),round(size_y/2,0)]] # массив тела питона
moved_direction_x=0 # текущее направление движения питона по оси x
moved_direction_y=0 # текущее направление движения питона по оси y

Затем создадим окно с указанными размерами , закрасим его чёрным цветом, и нарисуем сетку:

# Расчерчиваем поле
def Draw_grid_field(screen):
 xx=0;
 for x in range(size_x):
   pygame.draw.line(screen,color_grid,(xx,0),(xx,height_field))
   xx=xx+step_x;
 yy=0;
 for y in range(size_y):
   pygame.draw.line(screen,color_grid,(0,yy),(width_field,yy))
   yy=yy+step_y;

# Да начнётся игра..
pygame.init()
screen = pygame.display.set_mode((width_field, height_field))
screen.fill(fill_grid)
Draw_grid_field(screen)

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

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_LEFT:
                moved_direction_x=-1
                moved_direction_y=0
            if event.key == pygame.K_RIGHT:
                moved_direction_x = 1
                moved_direction_y = 0
            if event.key == pygame.K_UP:
                moved_direction_y = -1
                moved_direction_x=0
            if event.key == pygame.K_DOWN:
                moved_direction_x=0
                moved_direction_y = 1
        if event.type == pygame.QUIT:
           running = False
    pygame.display.flip()
    pygame.display.update()
    clock.tick(FPS)
pygame.quit()
print("нормально вышли")

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

start_time=time.time()
running = True
while running:
    if (time.time()-start_time)>1:
        start_time=time.time()
        Draw_python(screen)
    for event in pygame.event.get():
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_LEFT:
                moved_direction_x=-1
                moved_direction_y=0
            if event.key == pygame.K_RIGHT:
                moved_direction_x = 1
                moved_direction_y = 0
            if event.key == pygame.K_UP:
                moved_direction_y = -1
                moved_direction_x=0
            if event.key == pygame.K_DOWN:
                moved_direction_x=0
                moved_direction_y = 1
        if event.type == pygame.QUIT:
           running = False
    pygame.display.flip()
    pygame.display.update()
    clock.tick(FPS)
pygame.quit()
print("нормально вышли")

Функции отрисовки питона (Draw_python) пока нет. Создадим её.. В начале функции посчитаем радиус кружка тела. Потом если вектор направления задан, то голову передвинем на вектор, а жопу питона удалим. Таким образом создадим видимость движения питона:

# рисуем питона
def Draw_python(screen):
    radius = (round(step_x / 2)-1) if round(step_x / 2) < round(step_y / 2) else (round(step_y / 2)-1)  # выбираем радиус по ширине или высоте шага, смотря что меньше, чтоб вписаться в прямоугольник
    if moved_direction_x!=0 or moved_direction_y!=0:
        print(f"Пришли:{python_body}")
        head_coor = python_body[0].copy()
        head_coor[0] = head_coor[0] + moved_direction_x;
        head_coor[1] = head_coor[1] + moved_direction_y;
        print(f"-тело{python_body},голова: {head_coor}");
        python_body.insert(0, head_coor) # передвигаю голову
        print(f"Передвинули голову:{python_body}")

        # удаляю жопу
        element=python_body[len(python_body) - 1]
        coors=[element[0]*step_x-step_x/2,element[1]*step_y-step_y/2]
        pygame.draw.circle(screen,(0,0,0),coors,radius)

        python_body.pop(-1)  # удаляю последний элемент хвоста
        print(f"удалили жопу:{python_body}")

    # рисую тушку
    for element in python_body:
        coors=[element[0]*step_x-step_x/2,element[1]*step_y-step_y/2]
        pygame.draw.circle(screen,color_python,coors,radius)

Скачать результат

Тенарные операторы в python

Терпеть не могу их на самом деле, и избегаю использовать, т.к. считаю что лучше написать 2-3 лишних строчки кода. зато они будут читаться намного лучше. Однако же иногда попадаются, и иногда использую. Вот пример как использовать его в python

Синтаксис:

переменная=значение_1 if (условие выполняется) else значение_2

Пример:

radius=a if a>b else b

Читаем так: radius будет равно a, если a больше b, иначе radius будет b. Или тоже самое в классическом виде:

if a>b:
 radius=a
else:
 radius=b

Python: пишем игру «успей нажать клавишу»

В общем в рамках компании по попытке обучения одного товарища программированию, написал небольшую консольную игру, дабы с ним попробовать её воспроизвести.

Суть игры: компьютер загадывает случайную кнопку из списка и время за которое игрок должен успеть её нажать. Если игрок успевает нажать — ему добавляется балл.

Итак, для написания понадобится знать что такое циклы, массивы, случайные числа. Использовать будем модули keyboard, random и time

  • keyboard — для получения нажатий клавиш на клавиатуре
  • random — для получения случайных числел
  • time — для расчета времени реакции

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

keys=["q","w","e","r","t","y"];

Далее создадим бесконечный цикл, внутри которого будем выбирать случайную клавишу для нажатия:

import keyboard
import random
import time

keys=["q","w","e","r","t","y"];
while True:
    key_to_press=keys[random.randint(0, len(keys)-1)]
    print(f"-нажми кнопку {key_to_press}")

Что делает этот код? Бесконечно выводит в консоль сообщение о том, что нужно нажать случайно выбранную кнопку. Давайте теперь чуть переделаем код, чтобы случайно выбиралась не только клавиша которую нужноно нажать игроку, но и время за которое ему нужно её нажать:

import keyboard
import random
import time

keys=["q","w","e","r","t","y"];
while True:
    key_to_press=keys[random.randint(0, len(keys)-1)]
    time_to_press=random.randint(1, 3)
    print(f"Нажми кнопку {key_to_press} за {time_to_press} секунды");

Что дальше? А дальше нам нужно объявить еще один цикл и ждать пока не истечет время отведенное для того чтобы игрок нажал кнопку. Для этого, перед началом цикла запомним время начала игры, и внутри цикла постоянно будем проверять, на сколько оно отличается от текущего времени:

import keyboard
import random
import time

keys=["q","w","e","r","t","y"];
while True:
    key_to_press=keys[random.randint(0, len(keys)-1)]
    time_to_press=random.randint(1, 3) # время в секундах ожидания реакции
    print(f"Нажми кнопку {key_to_press} за {time_to_press} секунды");
    start_time=time.time() # запоминаем время начала игры
    while True:
      if (time.time()-start_time>time_to_press): # разница между текущее временем и временем начала игры больше чем time_to_press, то игрок не успел нажать кнопку
         print("- ты проиграл, не успел нажать!")
         break;	

Теперь добавим чуточку интерактивности, а именно проверку, «а не нажата ли клавиша загаданная компьютером»:

import keyboard
import random
import time

keys=["q","w","e","r","t","y"];
while True:
    key_to_press=keys[random.randint(0, len(keys)-1)]
    time_to_press=random.randint(1, 3) # время в секундах ожидания реакции
    print(f"Нажми кнопку {key_to_press} за {time_to_press} секунды");
    start_time=time.time() # запоминаем время начала игры
    while True:
      if (time.time()-start_time>time_to_press): # разница между текущее временем и временем начала игры больше чем time_to_press, то игрок не успел нажать кнопку
         print("- ты проиграл, не успел нажать!")
         break;	
     if keyboard.is_pressed(key_to_press):
        print(f'-Ты выиграл!')
        break

Ну и осталось чуток прилизать код, добавив возможность подсчета кто круче, компьютер или игрок:

import keyboard
import random
import time

# создадим массив с доступными клавишами
keys=["q","w","e","r","t","y"];
count_win_gamer=0
count_win_pc=0
while True:
    start_time=time.time()
    key_to_press=keys[random.randint(0, len(keys)-1)]
    time_to_press=random.randint(1, 3)
    print(f"Нажми кнопку {key_to_press} за {time_to_press} секунды");
    win=False;
    while True:
        if (time.time()-start_time>time_to_press):
            print(f"- ты проиграл уже {count_win_pc} , не успел нажать! Общий счёт: {count_win_pc}/{count_win_gamer} (ты)")
            count_win_pc = count_win_pc + 1
            break;
        if keyboard.is_pressed(key_to_press):
            count_win_gamer=count_win_gamer+1
            print(f'-Ты выиграл уже {count_win_gamer} раз!  Общий счёт: {count_win_pc}/{count_win_gamer} (ты)')
            win = True;
            break

Ход игры:

FastApi: авторизированный вход

Из предыдущей статьи у нас есть некий вызов API после выполнения которого у нас на руках будет некий сессионый ключ.

Попробуем теперь используя этот сессионый ключ, запросить некую информацию о пользователе. Для этого добавим в схему auth.py классы:

class info_class(BaseModel):
    username : Optional[str] = Field(None, title="Имя пользователя", example="Вася Пупкин")
    phone    : Optional[str] = Field(None, title="Номер телефона", example="+79222347594")
    email    : Optional[str] = Field(None, title="Email пользователя", example="dowiurefh@mail.ru")

class info_out(BaseModel):
    error     : Optional[bool]  = Field(..., title="Результат выполнения запроса", example="false")
    comment   : Optional[str]   = Field(None, title="Текст ошибки", example="Пользователь удалён")
    result    : Optional[info_class]

Следующим шагом будет защитить доступ к остальным компонентам API, от доступа без сессионного ключа. Для этого воспользуемся классом FastApi security и добавим в требования заголовка запроса тег авторизации.

Получим итоговый роутинг auth.py:

# -*- coding: utf-8 -*-
"""Different helper-functions to work with users."""
import json
import fastapi;
from fastapi import APIRouter,Depends
from app.schemas import auth as auth_models
from pydantic import BaseModel

token_key = fastapi.security.APIKeyHeader(name="Authorization")

class Token(BaseModel):
    token: str

# получить текущий токен
def get_current_token(oauth_header: str = fastapi.Security(token_key)):
    #print(f"Token: {oauth_header}")
    oauth_header=oauth_header.replace("Token ","")
    return oauth_header

router = APIRouter()

@router.post('/auth',response_model=auth_models.auth_out,tags=["auth"]) # заданы исходящие параметры
async def auth(into = Depends(auth_models.auth_in)): # заданы входящие параметры
    # если параметры не заданы
    if into.password==None: into.password="NONE"
    if into.login == None: into.password = "NONE"
    hash="12345678"
    # сформируем ответ
    out=auth_models.auth_out(hash=hash)
    return out

@router.post('/GetInfo',response_model=auth_models.info_out,tags=["auth"]) # заданы исходящие параметры
async def GetInfo(token: Token=Depends(get_current_token)): # заданы входящие параметры
    print(f"Токен: {token}")
    if token!="12345678":
        print("Токен не верен!")
        return auth_models.info_out(error=True,comment="Токен не найден")
    # сформируем ответ

    return auth_models.info_out(error=False,result={"username":"Pavel","email":"vasya@mail.ru"})

В результате мы можем увидеть в документации появившийся «замочек»:

При попытке выполнения запроса без авторизации, соответственно получим ошибку аторизации:

Not authenticated

Настройка внешнего доступа к API

В предыдущей статье был рассмотрен запуск сервера и доступ к нему. Одно НО, доступ этот осуществляется или с локального ПК (в main.py), как вы видели присутствует строчка:

uvicorn.run(app, host="127.0.0.1", port=8000, log_level="info")

Если host поменять например на 0.0.0.0, сервер «из вне» будет конечно доступен, но по нестандартному порту и не по защищенному протоколу. Однако есть способ «завернуть» весь трафик в https через проксирование в apache/ngnix.

Для apache необходимо установить модуль proxy:

sudo a2enmod proxy proxy_http
sudo service apache2 restart

И в настройки VirtualHost сайта добавить:

ProxyPass / http://127.0.0.1:8000
ProxyPassReverse / http://127.0.0.1:8000

Для ngnix к сожалению не могу дать примера, т.к. обычно не использую.

1 2 3 4 5 6 14