Skip to content

Instantly share code, notes, and snippets.

@tirinox
Last active November 24, 2018 09:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tirinox/4efdc240902d88bfc4261d4c0af8aa33 to your computer and use it in GitHub Desktop.
Save tirinox/4efdc240902d88bfc4261d4c0af8aa33 to your computer and use it in GitHub Desktop.
import random
from flask import Flask, request
class Game:
X = 'X'
O = 'O'
N = ' '
def __init__(self, size=3, to_win=3):
"""
:param size: Размер стороны квадрата поля
:param to_win: Скольно нужно поставить в одну линию, чтобы победить
"""
assert 3 <= size <= 30
assert 2 <= to_win <= size
self.size = size
self._to_win = to_win
# текущий ход - Х
self._turn = self.X
# поле будет линейный массив сначала первая строка, потом вторая и т.д.
self._field = [self.N for _ in range(0, self.size * self.size)]
# тут будем хранить координаты победной линии (чтобы выделить ее в конце игры)
self.win_line = []
def _get_field(self, x, y):
"""
Получить состояние клетки по координатам (x, y)
При этом 0, 0 - верхний левый угол
:param x: колонка
:param y: ряд
:return: состояние клетки (X, O, N) или None (координаты за пределами)
"""
if 0 <= x < self.size and 0 <= y < self.size:
return self._field[x + y * self.size]
else:
return None
def _set_field(self, x, y, symbol):
"""
Записать в пустую ячейку крестик или нолик
:param x: колонка
:param y: ряд
:param symbol: символ
:return: Bool удалось ли записать
"""
cur_symbol = self._get_field(x, y)
if cur_symbol == self.N and symbol in [self.X, self.O]:
self._field[x + y * self.size] = symbol
return True
else:
return False
def _change_turn(self):
"""
Просто передает ход другому игроку
:return:
"""
if self._turn == self.X:
self._turn = self.O
else:
self._turn = self.X
def is_field_full(self):
"""
Проверяет, заполнилось ли поле целиком
:return: bool
"""
for i in range(0, self.size * self.size):
if self._field[i] == self.N:
return False
return True
def _scan_line(self, x0, y0, dx, dy):
"""
Сканируем поле с точки x0, y0 в напралвении dx, dy
При этом заполняет win_line (выигрышная линия) парами (x, y)
:param x0: начало х
:param y0: начало у
:param dx: сдвиг по х
:param dy: сдвиг по у
:return: возвращает None, если по этой линии нет выигрыша,
или возвращает X или О (если линия выигрышная)
"""
assert abs(dx) + abs(dy) > 0
self.win_line = []
owner = None # чья линия?
x, y = x0, y0
for step in range(self._to_win): # максимум _to_win шагов, не больше, не меньше
cell = self._get_field(x, y)
if cell is None or cell == self.N: # если мы вышли за пределы или клетка пуста
return None
if owner is None: # если клетка не пуста, то узнаем, кто будет владеть линией
owner = cell
elif owner != cell: # если владелец был определон на прошлых шагах, но этой клеткой владеет не он
return None
# пока идем хорошо, заполняем линию
self.win_line.append((x, y))
x += dx
y += dy
return owner
TIED = 'tied'
INVALID = 'invalid'
PLAYING = 'playing'
def is_game_over(self):
"""
Проверяет, не кончилась ли игра
:return: X или O (победители) или TIED (ничья) или PLAYING (игра не окончена)
"""
for x in range(self.size):
for y in range(self.size):
# из каждой клетки сканируем в 4 направления
# впрваво, вниз, вправо-вниз, влево-вниз
r = self._scan_line(x, y, 1, 0) or \
self._scan_line(x, y, 0, 1) or \
self._scan_line(x, y, 1, 1) or \
self._scan_line(x, y, -1, 1)
# до первого выигрышного результата
if r is not None:
return r
# нет победителя - нет его линии
self.win_line = []
# если поле заполнено, но победительн не выявлен в цилке сверху, то - ничья
if self.is_field_full():
return Game.TIED
else: # иначе - играем
return Game.PLAYING
def do_turn(self, x, y):
"""
Попытаемся сделать ход игроку
:param x: коорд. хода х
:param y: коорд. хода y
:return: результат игры (см. is_game_over) или INVALID (запрещенный ход)
"""
if self._set_field(x, y, self._turn):
self._change_turn()
return self.is_game_over()
else:
return Game.INVALID
def do_turn_ai(self):
"""
Сделать ход Искуственному интелленкту
:return: (см. do_turn)
"""
empty_cells = [i for i, cell in enumerate(self._field) if cell == self.N]
if len(empty_cells) > 0:
turn_index = random.choice(empty_cells)
x = int(turn_index % self.size)
y = int(turn_index / self.size)
return self.do_turn(x, y)
else:
return self.is_game_over()
def tag(t, c, **attributes):
"""
Оборачивает строку в HTML тэг
tag('a', 'Click me!', href='http://site.com') => <a href="http://site.com">Click me!</a>
:param t: тэг
:param c: содержание
:param attributes: именованые параметры атрибутов
:return: обернутая в тэг строка
"""
attrs = [f'{name}="{value}"' for name, value in attributes.items()]
return f'<{t} {" ".join(attrs)}>{c}</{t}>'
def redirect_js(to_url):
"""
Редирект через JS (будем писать его в onclick)
Из браузера сообщим серверу о действии игрока
:param to_url: куда перенаправить
:return: JS код
"""
return f"window.location = '{to_url}'"
def td(content, onclick='', bg_color='#fff'):
"""
Ячейка таблицы
:param content: содержимое
:param onclick: действиее при клике
:param bg_color: цвет фона
:return: HTML код <td>...
"""
return tag('td', content,
width=65,
height=65,
align='center',
style=f'font-size: 38pt; background: {bg_color}',
onclick=onclick)
def render_game_to_string(game: Game):
"""
Рендерим игру в HTML
:param game: игра
:return: HTML код
"""
# не кончилась ли игра?
state = game.is_game_over()
if state == game.TIED:
header1 = 'Ничья!'
elif state == game.X:
header1 = 'Победили крестики (вы)!'
elif state == game.O:
header1 = 'Победили нолики!'
else:
header1 = 'Ваш ход (Х)'
# заголовок
header = tag('h1', header1) + '<br>' + tag('a', 'Новая игра', href='/?action=new') + '<br>'
# игровое поле
rows = ''
for y in range(game.size): # во всем рядам
row = ''
for x in range(game.size): # по клетке в ряду
# берем клетку
cell_state = game._get_field(x, y)
# событие по клику делаем, только если клетка пустая (N) - редирект на эту же страницу с действием action=turn и координатами
onclick = redirect_js(f'/?action=turn&x={x}&y={y}') if cell_state == game.N else ''
# если координаты на выигрышной линии - закрасим их красным, иначе фон - белый
color = '#f99' if (x, y) in game.win_line else '#fff'
# добавим ячейку в ряд
row += td(cell_state, onclick=onclick, bg_color=color)
# каждый ряд обернут в тэг tr
rows += tag('tr', row)
# знаю, что border уже не используют, но нам проще всего
field_html = tag('table', rows, border='1')
# финализируем html
html = header + field_html
html = tag('body', html)
html = tag('html', html)
return html
def new_game():
"""
Создает новую игру (6 клеток, 4 в линии для победы)
:return: Game
"""
return Game(size=6, to_win=4)
game = new_game()
app = Flask(__name__)
@app.route('/')
def index():
"""
Обработка запроса к игре через бразуер
:return:
"""
# в Python есть global! WOW
global game
# действие
action = request.args.get('action')
# если требуют делать ход, и мы еще играем
if action == 'turn' and game.is_game_over() == game.PLAYING:
# координаты хода
x = int(request.args.get('x'))
y = int(request.args.get('y'))
# делаем ход
result = game.do_turn(x, y)
# если еще играем
if result == game.PLAYING:
# ход делает интеллект
game.do_turn_ai()
else:
# в любой непонятной ситуации - новая игра
game = new_game()
# а возвращем мы HTML код с видом нашей игры
return render_game_to_string(game)
# запуск веб-сервера
app.run(port=10345, host='localhost')
@tirinox
Copy link
Author

tirinox commented Nov 24, 2018

Скриншот
screen shot 2018-11-24 at 12 00 11

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment