Генерация 2D мира с помощью клеточного автомата на Python / Хабр
Всем привет! На написание этой статьи меня вдохновил автор YouTube канала PeaAshMeter. В своем видео автор показывает простейший генератор 2D мира, который основан на простейшем правиле клеточного автомата. Что такое клеточный автомат? Какие клеточные автоматы бывают? На эти и многие другие вопросы я попробую ответить.
Проект я решил написать на Python, но поскольку не являюсь экспертом в этой области, то любые замечания, предложения по улучшению кода или проекта — приветствуются!
Последовательность генерации 2D мира.
1. Что такое клеточный автомат?
Клеточным автоматом называют множество клеток, которые можно представить в виде матрицы с x‑строк и y‑столбцов. Пересечение x и y даёт координаты текущей клетки. Состояние у клеток может быть разным. Простейшие клеточные автоматы могут быть лишь в двух состояниях, закрашенным(1) или не закрашенным(0). Для каждой такой клетки определяется окрестность — соседние клетки вокруг текущей. Радиус такой окрестности может быть разным в разных автоматах, как и правило того, каких соседей можно учитывать, например только соседей слева и справа, или только сверху и снизу. Нужна такая окрестность для того, чтобы исходя из состояния клеток соседей изменять состояние текущей клетке, на каждом витке итерации. Изменение состояние текущей клетки происходит по определенным правилам. Например, если текущая клетка имеет состояние не закрашенная(0), а в окрестности есть 3 и более соседа, которые закрашены(1), тогда мы закрашиваем текущую клетку.
Простейшее преобразование клеток, которое называют «Жаба».
Один шаг автомата подразумевает обход всех клеток и на основе данных о текущем состоянии клетки и её окрестности определение нового состояния клетки, которое будет у неё при следующем шаге. Перед стартом автомата оговаривается начальное состояние клеток, которое может устанавливаться целенаправленно или случайным образом. Набор таких простых правил создаёт удивительные анимации, вот один из таких примеров.
Планерное ружьё Госпера в клеточном автомате «Жизнь»
Чтобы не растягивать статью, то ограничимся этой вводной, если вы хотите узнать больше, то ниже я приведу ссылки на полезные материалы по теме этой статьи.
2. Хаотичное распределение как начальное состояние
Для этого проекта мы будем использовать PyGame, почему именно его я расскажу в самом конце статьи, а пока примем это как данность. Набросаем основную структуру приложения для PyGame. В инициализации класса App определим разрешение нашего окна, таймер для вывода кадров и класс Biomes, в нём и будет вся логика работы с биомами. В методе run будем отлавливать слушатели кнопок и показывать счетчик fps. Еще нужно создать файл settings, в нем будем хранить константы для проекта, через такой файл мы сможем легко менять настройки проекта, например, ограничитель кадров или базовое разрешение окна.
class App: def __init__(self): self. screen = pg.display.set_mode(settings.RES) self.clock = pg.time.Clock() self.biomes = bm.Biomes(app=self, pg=pg) def run(self): while True: for event in pg.event.get(): if event.type == pg.QUIT: pg.quit() elif event.type == pg.KEYDOWN: if event.key == pg.K_SPACE: self.biomes.main_render_biomes() self.clock.tick(settings.FPS) pg.display.set_caption(f'FPS: {self.clock.get_fps()}') if __name__ == '__main__': app = App() app.run()
Теперь когда базовый класс готов, перейдем к работе с рендером кадров. На первом кадре будет хаотичное распределение суши и моря. Для этого нам подойдет матрица, которая будет заполнена случайным образом. Но чтобы заполнять такую матрицу, нам понадобятся типы биомов поэтому без enum нам не обойтись, в нем мы определим основные биомы с которыми будем работать. У нас будет всего 5 биомов, а именно: суша, море, песок, побережье, лес.
class BiomesType(Enum): LAND = 1 SEA = 2 SAND = 3 SEA_SHORE = 4 WOODS = 5
Теперь создадим метод, который будет инициализировать матрицу случайным распределением суши и моря, их вероятность я выбрал 50%. Нужно еще отметить, что количество столбцов и строк будет равно 300, таким образом при разрешении 600 на 600 пикселей, каждый “пиксель” биома будет равен размеру 2 на 2 реальных пикселя, это нужно учитывать при отрисовке реальных пикселей, поскольку их точка будет смещаться на величину размера “пикселя” биома.
def create_start_matrix(self): rows = settings.Rows cols = settings.Columns matrix = [[0] * cols for _ in range(rows)] for i in range(rows): for j in range(cols): r = random.randint(1, 2) matrix[i][j] = BiomesType.SEA if (r == 1) else BiomesType.LAND self.paint_pixel_element(matrix[i][j], i, j) self.pg.display.update() return matrix
Теперь пару слов про отрисовку. После инициализации каждого элемента матрицы, мы сразу наносим его на холст.
def paint_pixel_element(self, biome, x, y): if biome == BiomesType.LAND: color = settings.COLOR_LAND elif biome == BiomesType.SEA: color = settings.COLOR_SEA elif biome == BiomesType.SAND: color = settings.COLOR_SAND elif biome == BiomesType.SEA_SHORE: color = settings.COLOR_SEA_SHORE elif biome == BiomesType.WOODS: color = settings.COLOR_WOODS self.pg.draw.rect(self.app.screen, color, (x * settings.basicX, y * settings.basicY, settings.basicX, settings.basicY))
После того как мы создали матрицу и отрисовали каждый элемент, нам нужно вызвать метод display.update() для обновления текущего холста, и мы увидим случайное распределение.
Начальный слой (случайное распределение суши и моря).
3. Порядок из хаоса
Для того чтобы получить упорядоченные группы клеток, которые будут напоминать острова нам следует применить одно из правил клеточного автомата, в этом проекте мы будем использовать правило “День и ночь”(B3678/S34678). Следует отметить, что новое состояние всей матрицы после применение правила называют поколением. С каждым поколением случайное распределение будет носить упорядоченный характер, так что выбор количества поколений может быть разным.
Итак, приступим к созданию первого слоя. Для этого нам необходимо пройтись по сгенерированной матрице и просчитать количество соседей для каждого итерируемого элемента, ещё нужно учитывать углы, чтобы не выйти за индексы матрицы. Если текущая клетка это суша, а счётчик соседних клеток моря равен 3, 6, 7, 8 тогда мы изменяем текущую клетку на море и наоборот для клеток моря. Радиус соседей примем за 1 клетку. Это можно изобразить следующим образом.
Матрица распределение суши и моря.
def next_generation_lands(self): for x in range(len(self.matrix)): for y in range(len(self.matrix[x])): counter_sea = 0 counter_land = 0 if (x - 1) >= 0: if self.matrix[x - 1][y] == BiomesType.SEA: counter_sea += 1 else: counter_land += 1 if (y - 1) >= 0: if self.matrix[x][y - 1] == BiomesType.SEA: counter_sea += 1 else: counter_land += 1 if (x + 1) <= settings.Columns - 1: if self.matrix[x + 1][y] == BiomesType.SEA: counter_sea += 1 else: counter_land += 1 if (y + 1) <= settings.Rows - 1: if self.matrix[x][y + 1] == BiomesType.SEA: counter_sea += 1 else: counter_land += 1 if (y - 1) >= 0 and (x + 1) <= settings. Columns - 1: if self.matrix[x + 1][y - 1] == BiomesType.SEA: counter_sea += 1 else: counter_land += 1 if (y + 1) <= settings.Rows - 1 and (x + 1) <= settings.Columns - 1: if self.matrix[x + 1][y + 1] == BiomesType.SEA: counter_sea += 1 else: counter_land += 1 if (y - 1) >= 0 and (x - 1) >= 0: if self.matrix[x - 1][y - 1] == BiomesType.SEA: counter_sea += 1 else: counter_land += 1 if (y + 1) <= settings.Rows - 1 and (x - 1) >= 0: if self.matrix[x - 1][y + 1] == BiomesType.SEA: counter_sea += 1 else: counter_land += 1 if self.matrix[x][y] == BiomesType.LAND: if counter_sea == 3 or counter_sea == 6 \ or counter_sea == 7 or counter_sea == 8: self.matrix[x][y] = BiomesType.SEA self.paint_pixel_element(self.matrix[x][y], x, y) if self.matrix[x][y] == BiomesType.SEA: if counter_land == 3 or counter_land == 6 \ or counter_land == 7 or counter_land == 8: self.matrix[x][y] = BiomesType.LAND self.paint_pixel_element(self.matrix[x][y], x, y) self.pg.display.update()
Приблизительно через 200 поколений мы наблюдаем, что наш шум теперь выглядит, как архипелаг островов. Я думаю, что такой результат вполне удовлетворительный, поэтому можно приступать к работе над следующим слоем.
Первый слой (упорядоченное состояние суши и моря).
4. Пляж и мелководье
Следующим шагом будет создание песочного берега вокруг каждого из островов. Фактически нам просто необходимо сделать обводку вокруг каждого острова, сделать это довольно просто. Для каждой клетки нужно проверить условие: если текущая клетка суша, и она находится между клетками море и сушей, тогда мы меняем ее на песок.
Матрица распределение пляжа, суши и моря.
Но, следует отметить, что простой контур островов в виде песка выглядит совсем не естественно, поэтому необходимо добавить случайный эффект для преобразования суши в песок. Воспользуемся старым правилом для формирования суши, только модифицируем его, если у текущего элемента более 5 соседей клеток песка, то с вероятностью в 2% мы будем изменять текущую клетку на песок. После того как пройдёт 100 поколений, мы получим вот такой результат.
Второй слой (обводка суши пляжем со случайной вероятностью).
Формирование мелководья происходить по такому же принципу, разница лишь в том, что теперь мы проверяем, что клетка находится между песком и морем. Точно так же обводим контур, а после добавляем случайное распределение в 2%.
Матрица распределение пляжа, мелководья и моря.
В итоге после 100 поколений мы получаем мелководье.
Третий слой (обводка пляжа мелководьем со случайной вероятностью).
5. Лес
Последним слоем мы будем создавать густой лес. Для начала нужно сделать хаотичное распределение, которое будем группировать всё тем же правилом “День и ночь”(B3678/S34678), так что процесс создание леса такой же, как и у первого слоя (суши и моря). Главным отличием есть то, что распределение будет в границах островов, а значит только на клетках суши.
Матрица распределение суши и густого леса.
Поколений для отрисовки леса требуется меньше, приблизительно 30. Это связано с тем, что из-за граничных условий количество клеток для леса сильно меньше, поэтому для группировки требуется меньше поколений.
Четвертый слой (упорядоченное распределение суши и густого леса).
6. PyGame вместо Kivy
Теперь пара слов о том, почему я выбрал PyGame. Изначально я использовал Kivy, у которого есть возможность рисовать примитивы на холсте, но сразу столкнулся с тем, что этот способ крайне неэффективный. Отрисовка каждого поколения происходила только после полного просчёта матрицы, что не давало наглядности, это не удивительно поскольку Kivy больше подходить для построения небольшого UI. В PyGame есть буферизация кадров из коробки (она рисует только те участки, которые были изменены), это позволяет в реал тайме видеть отрисовку карты. Еще одним плюсом будет ограничитель кадров, он позволяет косвенно судить о производительности. Всё это в сумме с хорошей документацией и отличными примерами, заставило меня выбрать PyGame.
В дальнейшем я бы хотел повысить производительность и увеличить разрешение рендеринга карты. В планах реализовать следующие пункты:
В заключении хотелось бы сказать, что потенциал клеточных автоматов большой и применить их можно в разных сферах, генерация 2D мира одно из них. Такой способ довольно простой и универсальный. Если брать только процедурную генерацию ландшафта, то у меня в планах создать генератор на основе алгоритма diamond square или шума Перлина, эти варианты хорошо себя зарекомендовали и их часто используют, поэтому вызывают у меня большой интерес. Спасибо за ваше внимание! Надеюсь, что эта статья была познавательной. И отдельное спасибо, если дочитали до этого места!
Ссылка на исходники проекта.
Анимашка в подарок.
8. Рекомендованные материалы
Видеоматериал который меня вдохновил
Большая статья с разбором генератора Minecraft
Вики Клеточный автомат
Моделирование лесных пожаров: теория, клеточный автомат на Python
10 удивительно зрелищных простейших клеточных автоматов
Битва дроидов и джедаев на клеточном автомате
Сыграем в «Жизнь»! Клеточный автомат на Python [ Pygame ]
Онлайн конструктор клеточных автоматов
Три крутые игры на Python с исходниками — Разработка на vc.ru
Игра №1. Арканоид
338 просмотров
Уверен, что вы хоть раз играли в эту интересную, но простую игру.
Цель этой игры, отбивать мяч от платформы и не упустить его.
При создании игры используются такие библиотеки как:
- tkinter, эта библиотека предустановленна на большинстве версиях Python и используется для создания самого оконного приложения.
- time, в нашем случае будет использоваться, что бы задать скорость мячу и платформе. Для установки зайдите в командную строку от имени администратора и напишите: pip install time проверьте что pip у вас установлен.
- random, в нашем случае будет использоваться , что бы мяч отскакивал в разных направлениях. Эта библиотека так же предустановленна на всех версиях Python.
- pygame, используется для создания графического интерфейса нашей игры. Эту библиотеку нужно скачать через командную строку, прописав: pip install pygame
from tkinter import * import time import random import pygame class Ball(): def __init__(self, canvas, platform, color): self.canvas = canvas self.platform = platform self.oval = canvas.create_oval(200, 200, 215, 215, fill=color) self.dir = [-3, -2, -1, 1, 2, 3] self.x = random.choice(self.dir) self.y = -1 self. touch_bottom = False def touch_platform(self, ball_pos): platform_pos = self.canvas.coords(self.platform.rect) if ball_pos[2] >= platform_pos[0] and ball_pos[0] <= platform_pos[2]: if ball_pos[3] >= platform_pos[1] and ball_pos[3] <= platform_pos[3]: return True return False def draw(self): self.canvas.move(self.oval, self.x, self.y) pos = self.canvas.coords(self.oval) if pos[1] <= 0: self.y = 3 if pos[3] >= 400: self.touch_bottom = True if self.touch_platform(pos) == True: self.y = -3 if pos[0] <= 0: self.x = 3 if pos[2] >= 500: self.x = -3 class Platform(): def __init__(self, canvas, color): self.canvas = canvas self.rect = canvas.create_rectangle(230, 300, 330, 310, fill=color) self.x = 0 self.canvas.bind_all(‘<KeyPress-Left>’, self.left) self.
Вот и весь код для этой интересной, простой и увлекательной игры. Вы так же можете модернизировать игру, добавив в неё например количество балов за отбитые мячи, или второй мяч.
Игра №2. Тетрис.
Эту игру знают все! Главная задача игрока не дать разным, геометрическим фигурам достигнуть «ФИНИША».
import sys, random from PyQt5.QtWidgets import QMainWindow, QFrame, QDesktopWidget, QApplication from PyQt5.QtCore import Qt, QBasicTimer, pyqtSignal from PyQt5.QtGui import QPainter, QColor
Вы видите библиотеки, которые будут использоваться при создании данной игры, всех их нужно загрузить через командную строку вашего компьютера.
После того как установили нужные нам библиотеки, создаём класс с нашими переменными. класс назовём Tetris и будем использовать свойства отцовского класса, чтобы каждый раз не прописывать все переменные заново для каждого последующего класса.
class Tetris(QMainWindow): def __init__(self): super().__init__() self.initUI() def initUI(self): self.tboard = Board(self) self.setCentralWidget(self.tboard) self.statusbar = self.statusBar() self.tboard.msg2Statusbar[str].connect(self.statusbar.showMessage) self.tboard.start() self.resize(180, 380) self.center() self. setWindowTitle(‘Tetris’) self.show() def center(self): screen = QDesktopWidget().screenGeometry() size = self.geometry() self.move((screen.width()-size.width())/2, (screen.height()-size.height())/2)
После создания отцовского класса Tetris, создаём все остальные классы и переменные для уже других функций.
class Board(QFrame): msg2Statusbar = pyqtSignal(str) BoardWidth = 10 BoardHeight = 22 Speed = 300 def __init__(self, parent): super().__init__(parent) self.initBoard() def initBoard(self): self.timer = QBasicTimer() self.isWaitingAfterLine = False self.curX = 0 self.curY = 0 self.numLinesRemoved = 0 self.board = [] self.setFocusPolicy(Qt.StrongFocus) self.isStarted = False self.isPaused = False self.clearBoard() def shapeAt(self, x, y): return self.board[(y * Board.BoardWidth) + x] def setShapeAt(self, x, y, shape): self. board[(y * Board.BoardWidth) + x] = shape def squareWidth(self): return self.contentsRect().width() // Board.BoardWidth def squareHeight(self): return self.contentsRect().height() // Board.BoardHeight def start(self): if self.isPaused: return self.isStarted = True self.isWaitingAfterLine = False self.numLinesRemoved = 0 self.clearBoard() self.msg2Statusbar.emit(str(self.numLinesRemoved)) self.newPiece() self.timer.start(Board.Speed, self) def pause(self): if not self.isStarted: return self.isPaused = not self.isPaused if self.isPaused: self.timer.stop() self.msg2Statusbar.emit(«paused») else: self.timer.start(Board.Speed, self) self.msg2Statusbar.emit(str(self.numLinesRemoved)) self.update()
def paintEvent(self, event): painter = QPainter(self) rect = self.contentsRect() boardTop = rect. bottom() — Board.BoardHeight * self.squareHeight() for i in range(Board.BoardHeight): for j in range(Board.BoardWidth): shape = self.shapeAt(j, Board.BoardHeight — i — 1) if shape != Tetrominoe.NoShape: self.drawSquare(painter, rect.left() + j * self.squareWidth(), boardTop + i * self.squareHeight(), shape) if self.curPiece.shape() != Tetrominoe.NoShape: for i in range(4): x = self.curX + self.curPiece.x(i) y = self.curY — self.curPiece.y(i) self.drawSquare(painter, rect.left() + x * self.squareWidth(), boardTop + (Board.BoardHeight — y — 1) * self.squareHeight(), self.curPiece.shape()) def keyPressEvent(self, event): if not self.isStarted or self.curPiece.shape() == Tetrominoe.NoShape: super(Board, self).keyPressEvent(event) return key = event. key() if key == Qt.Key_P: self.pause() return if self.isPaused: return elif key == Qt.Key_Left: self.tryMove(self.curPiece, self.curX — 1, self.curY) elif key == Qt.Key_Right: self.tryMove(self.curPiece, self.curX + 1, self.curY) elif key == Qt.Key_Down: self.tryMove(self.curPiece.rotateRight(), self.curX, self.curY) elif key == Qt.Key_Up: self.tryMove(self.curPiece.rotateLeft(), self.curX, self.curY) elif key == Qt.Key_Space: self.dropDown() elif key == Qt.Key_D: self.oneLineDown() else: super(Board, self).keyPressEvent(event) def timerEvent(self, event): if event.timerId() == self.timer.timerId(): if self.isWaitingAfterLine: self.isWaitingAfterLine = False self.newPiece() else: self.oneLineDown() else: super(Board, self). timerEvent(event) def clearBoard(self): for i in range(Board.BoardHeight * Board.BoardWidth): self.board.append(Tetrominoe.NoShape) def dropDown(self): newY = self.curY while newY > 0: if not self.tryMove(self.curPiece, self.curX, newY — 1): break newY -= 1 self.pieceDropped() def oneLineDown(self): if not self.tryMove(self.curPiece, self.curX, self.curY — 1): self.pieceDropped() def pieceDropped(self): for i in range(4): x = self.curX + self.curPiece.x(i) y = self.curY — self.curPiece.y(i) self.setShapeAt(x, y, self.curPiece.shape()) self.removeFullLines() if not self.isWaitingAfterLine: self.newPiece()
def removeFullLines(self): numFullLines = 0 rowsToRemove = [] for i in range(Board.BoardHeight): n = 0 for j in range(Board.BoardWidth): if not self. shapeAt(j, i) == Tetrominoe.NoShape: n = n + 1 if n == 10: rowsToRemove.append(i) rowsToRemove.reverse() for m in rowsToRemove: for k in range(m, Board.BoardHeight): for l in range(Board.BoardWidth): self.setShapeAt(l, k, self.shapeAt(l, k + 1)) numFullLines = numFullLines + len(rowsToRemove) if numFullLines > 0: self.numLinesRemoved = self.numLinesRemoved + numFullLines self.msg2Statusbar.emit(str(self.numLinesRemoved)) self.isWaitingAfterLine = True self.curPiece.setShape(Tetrominoe.NoShape) self.update() def newPiece(self): self.curPiece = Shape() self.curPiece.setRandomShape() self.curX = Board.BoardWidth // 2 + 1 self.curY = Board.BoardHeight — 1 + self.curPiece.minY() if not self.tryMove(self.curPiece, self.curX, self.curY): self.curPiece. setShape(Tetrominoe.NoShape) self.timer.stop() self.isStarted = False self.msg2Statusbar.emit(«Game over») def tryMove(self, newPiece, newX, newY): for i in range(4): x = newX + newPiece.x(i) y = newY — newPiece.y(i) if x < 0 or x >= Board.BoardWidth or y < 0 or y >= Board.BoardHeight: return False if self.shapeAt(x, y) != Tetrominoe.NoShape: return False self.curPiece = newPiece self.curX = newX self.curY = newY self.update() return True def drawSquare(self, painter, x, y, shape): colorTable = [0x000000, 0xCC6666, 0x66CC66, 0x6666CC, 0xCCCC66, 0xCC66CC, 0x66CCCC, 0xDAAA00] color = QColor(colorTable[shape]) painter.fillRect(x + 1, y + 1, self.squareWidth() — 2, self.squareHeight() — 2, color) painter.setPen(color.lighter()) painter.drawLine(x, y + self. squareHeight() — 1, x, y) painter.drawLine(x, y, x + self.squareWidth() — 1, y) painter.setPen(color.darker()) painter.drawLine(x + 1, y + self.squareHeight() — 1, x + self.squareWidth() — 1, y + self.squareHeight() — 1) painter.drawLine(x + self.squareWidth() — 1, y + self.squareHeight() — 1, x + self.squareWidth() — 1, y + 1)
После создаём класс уже для самих объектов( фигур), назовём его Tetrominoe.
class Tetrominoe(object): NoShape = 0 ZShape = 1 SShape = 2 LineShape = 3 TShape = 4 SquareShape = 5 LShape = 6 MirroredLShape = 7
И ещё один класс, но уже будем задавать координаты фигурам.
class Shape(object): coordsTable = ( ((0, 0), (0, 0), (0, 0), (0, 0)), ((0, -1), (0, 0), (-1, 0), (-1, 1)), ((0, -1), (0, 0), (1, 0), (1, 1)), ((0, -1), (0, 0), (0, 1), (0, 2)), ((-1, 0), (0, 0), (1, 0), (0, 1)), ((0, 0), (1, 0), (0, 1), (1, 1)), ((-1, -1), (0, -1), (0, 0), (0, 1)), ((1, -1), (0, -1), (0, 0), (0, 1)) ) def __init__(self): self. coords = [[0,0] for i in range(4)] self.pieceShape = Tetrominoe.NoShape self.setShape(Tetrominoe.NoShape) def shape(self): return self.pieceShape def setShape(self, shape): table = Shape.coordsTable[shape] for i in range(4): for j in range(2): self.coords[i][j] = table[i][j] self.pieceShape = shape def setRandomShape(self): self.setShape(random.randint(1, 7)) def x(self, index): return self.coords[index][0] def y(self, index): return self.coords[index][1] def setX(self, index, x): self.coords[index][0] = x def setY(self, index, y): self.coords[index][1] = y def minX(self): m = self.coords[0][0] for i in range(4): m = min(m, self.coords[i][0]) return m
И завершаем наш код
def maxX(self): m = self.coords[0][0] for i in range(4): m = max(m, self.coords[i][0]) return m def minY(self): m = self. coords[0][1] for i in range(4): m = min(m, self.coords[i][1]) return m def maxY(self): m = self.coords[0][1] for i in range(4): m = max(m, self.coords[i][1]) return m def rotateLeft(self): if self.pieceShape == Tetrominoe.SquareShape: return self result = Shape() result.pieceShape = self.pieceShape for i in range(4): result.setX(i, self.y(i)) result.setY(i, -self.x(i)) return result def rotateRight(self): if self.pieceShape == Tetrominoe.SquareShape: return self result = Shape() result.pieceShape = self.pieceShape for i in range(4): result.setX(i, -self.y(i)) result.setY(i, self.x(i)) return result if __name__ == ‘__main__’: app = QApplication([]) tetris = Tetris() sys.exit(app.exec_())
Прикрепил код по кусочкам, код длинный единым кодом его не прикрепить.
Игра №3. Танки.
Это немного не то, о чём вы подумали, это танки «на бумаге» они работают без графического интерфейса, выводя информацию на экран.
Для создания данной игры нам потребуется всего ода библиотека, random.
Создадим два обычных танка, которые будут иметь рандомный домаг, и один супер танк, у которого будет много xp и урона. У всех танков будет определённое количество xp, урона и брони, а так же свой экипаж.
import random class Tank: «»»Template of tanks»»» def __init__(self, model, armor, min_damage, max_damage, health): self.model = model self.armor = armor self.damage = random.randint(min_damage, max_damage) self.health = health def print_info(self): print(f»{self.model} имеет лобовую броню {self.armor}мм при {self.health}ед. здоровья и урон в {self.damage} единиц») def health_down(self, enemy_damage): self.health -= enemy_damage print(f»\n{self.model}:») print(f»Командир, по экипажу {self. model} попали, у нас осталось {self.health} очков здоровья») def shot(self, enemy): if enemy.health <= 0 or self.damage >= self.health: self.health = 0 print(f»Экипаж танка {enemy.model} уничтожен») else: enemy.health_down(enemy.damage) print(f»\n{self.model}:») print(f»Точно в цель, у противника {enemy.model} осталось {enemy.health} единиц здоровья») class SuperTank(Tank): «»»Template of superTanks»»» def __init__(self, model, armor, min_damage, max_damage, health): super().__init__(model, armor, min_damage, max_damage, health) self.forceArmor = True def health_down(self, enemy_damage): super().health_down(enemy_damage / 2)
Но если вы запустите нашу игру, ничего не произойдёт. Нужно прописать команду, которой танки будут стрелять друг по другу.
tank1 = Tank(«T-34», 90, 20, 30, 100) tank2 = Tank(«Tiger», 120, 10, 50, 120) tank1. print_info() tank2.print_info() tank1.shot(tank2) tank1.shot(tank2) tank1.shot(tank2) tank1.shot(tank2) tank1.shot(tank2) tank1.shot(tank2) tank1.shot(tank2)
Вот полный код программы:
import random class Tank: «»»Template of tanks»»» def __init__(self, model, armor, min_damage, max_damage, health): self.model = model self.armor = armor self.damage = random.randint(min_damage, max_damage) self.health = health def print_info(self): print(f»{self.model} имеет лобовую броню {self.armor}мм при {self.health}ед. здоровья и урон в {self.damage} единиц») def health_down(self, enemy_damage): self.health -= enemy_damage print(f»\n{self.model}:») print(f»Командир, по экипажу {self.model} попали, у нас осталось {self.health} очков здоровья») def shot(self, enemy): if enemy.health <= 0 or self.damage >= self.health: self.health = 0 print(f»Экипаж танка {enemy. model} уничтожен») else: enemy.health_down(enemy.damage) print(f»\n{self.model}:») print(f»Точно в цель, у противника {enemy.model} осталось {enemy.health} единиц здоровья») class SuperTank(Tank): «»»Template of superTanks»»» def __init__(self, model, armor, min_damage, max_damage, health): super().__init__(model, armor, min_damage, max_damage, health) self.forceArmor = True def health_down(self, enemy_damage): super().health_down(enemy_damage / 2) tank1 = Tank(«T-34», 90, 20, 30, 100) tank2 = Tank(«Tiger», 120, 10, 50, 120) tank1.print_info() tank2.print_info() tank1.shot(tank2) tank1.shot(tank2) tank1.shot(tank2) tank1.shot(tank2) tank1.shot(tank2) tank1.shot(tank2) tank1.shot(tank2)
Вы можете менять количество выстрелов и т.п.
ИНФОРМАЦИЯ FAA ДЕЙСТВУЕТ С 23 ФЕВРАЛЯ 2023 ГОДАLocation
Операции в аэропорту
Связь с аэропортом
Ближайшие радионавигационные средства
Услуги аэропортаИнформация о взлетно-посадочной полосеВзлетно-посадочная полоса 17/35
Владение и управление аэропортом из официальных записей FAA
Операционная статистика аэропорта
Additional Remarks
Инструментальные процедурыНа 4Y1 нет опубликованных процедур по приборам.Некоторые близлежащие аэропорты с процедурами по приборам: KOZW — аэропорт имени Спенсера Дж. Харди округа Ливингстон (7 морских миль на северо-запад)Y47 — юго-западный аэропорт Окленда (11 морских миль на восток) 9G2 — аэропорт Прайса (15 морских миль на север) KPTK — международный аэропорт округа Окленд (20 морских миль на восток) KARB — Энн Муниципальный аэропорт Арбора (21 морская миля к югу) |
Дорожные карты по адресу: MapQuest Бинг Google
Toyota 4Y1, Оранжевый кадий металлик, TricoatСопутствующие товарыВыберите параметры Быстрый просмотр Toyota 3T5, инфракрасный, трехслойныйПоставка красок R&E Сейчас: $43,90 — $499,90 Toyota 3T5, Infrared, Tricoat Краска Toyota 3T5, Infrared, Tricoat разработана с использованием уретанового базового покрытия X-Prime Professional Coatings с низким содержанием летучих органических соединений, поскольку оно демонстрирует исключительную точность цвета и… TOY 3T5 Выберите параметры Быстрый просмотр Toyota K1X, Halo, Tri-CoatПоставка красок R&E Сейчас: $43,90 — $499,90 Toyota K1X, Halo, Tricoat Краска Toyota K1X, Halo, Tricoat разработана с использованием уретанового базового покрытия X-Prime Professional Coatings с низким содержанием летучих органических соединений, поскольку оно обеспечивает исключительную точность цветопередачи и отличные. .. TOY K1X Выберите параметры Быстрый просмотр Toyota 3T7, светящийся красный металлик, трехслойное покрытиеПоставка красок R&E Сейчас: $43,90 — $499,90 Toyota 3T7, Luminous Red Metallic, Tricoat Toyota 3T7, Luminous Red Metallic, Tricoat Краска разработана с использованием уретанового базового покрытия X-Prime Professional Coatings с низким содержанием летучих органических соединений, поскольку оно обладает исключительными… TOY 3T7 Выберите параметры Быстрый просмотр Toyota 4W7, оранжевая лава, трехслойное покрытиеПоставка красок R&E Сейчас: 43,9 доллара США0 — $499,90 Toyota 4W7, Оранжевый лава, Tricoat Краска Toyota 4W7, Lava Orange, Tricoat разработана с использованием уретанового базового покрытия X-Prime Professional Coatings с низким содержанием летучих органических соединений, поскольку оно обеспечивает исключительную точность цвета и. .. TOY 4W7 Выберите параметры Быстрый просмотр Toyota 4X3, оранжево-мандариновый цвет, трехслойное покрытиеПоставка красок R&E Сейчас: 43,90–499,90 долл. США Toyota 4X3, Tangerine Orange, Tricoat Toyota 4X3, Tangerine Orange, Tricoat краска разработана с использованием уретанового базового покрытия X-Prime Professional Coatings с низким содержанием летучих органических соединений, поскольку она обладает исключительным цветом… TOY 4X3 Клиенты также просмотрелиВыберите параметры Быстрый просмотр Toyota 3T7, светящийся красный металлик, трехслойное покрытиеПоставка красок R&E Сейчас: 43,90–49 долларов США9.90 Toyota 3T7, Luminous Red Metallic, Tricoat Краска Toyota 3T7, Luminous Red Metallic, Tricoat разработана с использованием уретанового базового покрытия X-Prime Professional Coatings с низким содержанием летучих органических соединений, поскольку оно обладает исключительными. .. TOY 3T7 Выберите параметры Быстрый просмотр Toyota 082, трехслойное покрытие MoonglowПоставка красок R&E Сейчас: $43,90 — $499,90 Toyota 082, Moonglow Toyota 082, краска Moonglow разработана с использованием уретанового базового покрытия X-Prime Professional Coatings с низким содержанием летучих органических соединений, поскольку оно демонстрирует исключительную точность цветопередачи, отличную укрывистость и… ИГРУШКА 082 Выберите параметры Быстрый просмотр BMW B44, оранжевая жемчужина ВаленсииПоставка красок R&E Сейчас: $15,99 — $249,95 BMW B44, Valencia Orange Pearl BMW B44, Valencia Orange Pearl Краска разработана с использованием уретанового базового покрытия X-Prime Professional Coatings с низким содержанием летучих органических соединений, поскольку оно демонстрирует исключительную точность цвета и. .. BMW B44 Выберите параметры Быстрый просмотр Mitsubishi PW7, арктический белыйПоставка красок R&E Сейчас: $15,99 — $249,95 Mitsubishi PW7, Arctic White Краска Mitsubishi PW7, Arctic White разработана с использованием уретанового базового покрытия X-Prime Professional Coatings с низким содержанием летучих органических соединений, поскольку оно обеспечивает исключительную точность цвета и… MIT PW7 Выберите параметры Быстрый просмотр AUDI LZ5D, жемчужина научных исследованийПоставка красок R&E Сейчас: $15,99 — $249,95 AUDI LZ5D, Nauchtblau Pearl AUDI LZ5D, Nauchtblau Pearl Краска разработана с использованием уретанового базового покрытия X-Prime Professional Coatings с низким содержанием летучих органических соединений, поскольку оно демонстрирует исключительную точность цвета и превосходные. .. AUDI LZ5D Выберите параметры Быстрый просмотр Mazda 42B, Синий Рефлекс МеталликПоставка красок R&E Сейчас: $15,99 — $249,95 MAZDA 42B, Blue Reflex Metallic Краска MAZDA 42B, Blue Reflex Metallic разработана с использованием уретанового базового покрытия X-Prime Professional Coatings с низким содержанием летучих органических соединений, поскольку оно обеспечивает исключительную точность цвета и… MAZDA 42B Выберите параметры Быстрый просмотр GM WA413C, РаздавитьПоставка красок R&E 904:30 Сейчас: $15,99 — $249,95GM WA413C, CrushGM WA413C, краска Crush разработана с использованием уретанового базового покрытия X-Prime Professional Coatings с низким содержанием летучих органических соединений, поскольку оно обеспечивает исключительную точность цвета и превосходное покрытие и. .. GM WA413C Выберите параметры Быстрый просмотр GM WA324E, тигровая лилия металликПоставка красок R&E Сейчас: 15,99–249 долларов США.95 GM WA442E, Занзибар Металлик GM WA442E, краска Zanzibar Metallic разработана с использованием уретанового базового покрытия X-Prime Professional Coatings с низким содержанием летучих органических соединений, поскольку оно обеспечивает исключительную точность цвета и… GM WA324E Выберите параметры Быстрый просмотр AUDI LY3J, БриллиантротПоставка красок R&E Сейчас: $15,99 — $249,95 Audi LY3J, Brilliantrot Audi LY3J, Brilliantrot разработан с использованием уретанового базового покрытия X-Prime Professional Coatings с низким содержанием летучих органических соединений, поскольку оно демонстрирует исключительную точность цвета, превосходное покрытие и. |