Improve connect 4 frontend

This commit is contained in:
AlberLC
2022-11-16 02:59:03 +01:00
parent 781cb8cefa
commit 47874735ec
2 changed files with 262 additions and 0 deletions

View File

@@ -0,0 +1,250 @@
import io
import math
import random
from typing import Sequence
import cairo
from flanabot import constants
from flanabot.models.player import Player
SIZE_MULTIPLIER = 1
LEFT_MARGIN = 20
TOP_MARGIN = 20
CELL_LENGTH = 100 * SIZE_MULTIPLIER
NUMBERS_HEIGHT = 30 * SIZE_MULTIPLIER
TEXT_HEIGHT = 70 * SIZE_MULTIPLIER
NUMBERS_X_INITIAL_POSITION = LEFT_MARGIN + CELL_LENGTH / 2 - 8 * SIZE_MULTIPLIER
NUMBERS_Y_POSITION = TOP_MARGIN + CELL_LENGTH * constants.CONNECT_4_N_ROWS + 40 * SIZE_MULTIPLIER
TEXT_POSITION = (LEFT_MARGIN, TOP_MARGIN + CELL_LENGTH * constants.CONNECT_4_N_ROWS + NUMBERS_HEIGHT + 70 * SIZE_MULTIPLIER)
SURFACE_WIDTH = LEFT_MARGIN * 2 + CELL_LENGTH * constants.CONNECT_4_N_COLUMNS
SURFACE_HEIGHT = TOP_MARGIN * 2 + CELL_LENGTH * constants.CONNECT_4_N_ROWS + NUMBERS_HEIGHT + TEXT_HEIGHT
CIRCLE_RADIUS = 36 * SIZE_MULTIPLIER
CROSS_LINE_WIDTH = 24 * SIZE_MULTIPLIER
FONT_SIZE = 32 * SIZE_MULTIPLIER
TABLE_LINE_WIDTH = 4 * SIZE_MULTIPLIER
BLUE = (66 / 255, 135 / 255, 245 / 255)
BACKGROUND_COLOR = (54 / 255, 57 / 255, 63 / 255)
GRAY = (200 / 255, 200 / 255, 200 / 255)
HIGHLIGHT_COLOR = (104 / 255, 107 / 255, 113 / 255)
RED = (255 / 255, 70 / 255, 70 / 255)
PLAYER_1_COLOR = BLUE
PLAYER_2_COLOR = RED
def center_point(board_position: Sequence[int]) -> tuple[float, float]:
return LEFT_MARGIN + (board_position[1] + 0.5) * CELL_LENGTH, TOP_MARGIN + (board_position[0] + 0.5) * CELL_LENGTH
def draw_circle(board_position: Sequence[int], radius: float, color: tuple[float, float, float], context: cairo.Context):
context.set_source_rgba(*color)
context.arc(*center_point(board_position), radius, 0, 2 * math.pi)
context.fill()
def draw_line(
board_position_start: Sequence[int],
board_position_end: Sequence[int],
line_width: float,
color: tuple[float, float, float],
context: cairo.Context
):
context.set_line_width(line_width)
context.set_line_cap(cairo.LINE_CAP_ROUND)
context.set_source_rgba(*color)
context.move_to(*center_point(board_position_start))
context.line_to(*center_point(board_position_end))
context.stroke()
def draw_table(line_width: float, color: tuple[float, float, float], context: cairo.Context):
context.set_line_width(line_width)
context.set_line_cap(cairo.LINE_CAP_ROUND)
context.set_source_rgba(*color)
x = LEFT_MARGIN
y = TOP_MARGIN
for _ in range(constants.CONNECT_4_N_ROWS + 1):
context.move_to(x, y)
context.line_to(x + CELL_LENGTH * constants.CONNECT_4_N_COLUMNS, y)
context.stroke()
y += CELL_LENGTH
x = LEFT_MARGIN
y = TOP_MARGIN
for _ in range(constants.CONNECT_4_N_COLUMNS + 1):
context.move_to(x, y)
context.line_to(x, y + CELL_LENGTH * constants.CONNECT_4_N_ROWS)
context.stroke()
x += CELL_LENGTH
def draw_text(
text: str,
point: Sequence[float],
color: tuple[float, float, float],
font_size: float,
italic: bool,
context: cairo.Context
):
context.move_to(*point)
context.set_source_rgba(*color)
context.select_font_face("Sans", cairo.FONT_SLANT_ITALIC if italic else cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD)
context.set_font_size(font_size)
context.show_text(text)
def draw_winner_lines(
win_position: Sequence[int],
board: list[list[int | None]],
color: tuple[float, float, float],
context: cairo.Context
):
i, j = win_position
player_number = board[i][j]
# horizontal
j_a = j - 1
while j_a >= 0 and board[i][j_a] == player_number:
j_a -= 1
j_b = j + 1
while j_b < constants.CONNECT_4_N_COLUMNS and board[i][j_b] == player_number:
j_b += 1
if abs(j_a - j) + abs(j_b - j) - 1 >= 4:
draw_line((i, j_a + 1), (i, j_b - 1), CROSS_LINE_WIDTH, color, context)
# vertical
i_a = i - 1
while i_a >= 0 and board[i_a][j] == player_number:
i_a -= 1
i_b = i + 1
while i_b < constants.CONNECT_4_N_ROWS and board[i_b][j] == player_number:
i_b += 1
if abs(i_a - i) + abs(i_b - i) - 1 >= 4:
draw_line((i_a + 1, j), (i_b - 1, j), CROSS_LINE_WIDTH, color, context)
# diagonal 1
i_a = i - 1
j_a = j - 1
while i_a >= 0 and j_a >= 0 and board[i_a][j_a] == player_number:
i_a -= 1
j_a -= 1
i_b = i + 1
j_b = j + 1
while i_b < constants.CONNECT_4_N_ROWS and j_b < constants.CONNECT_4_N_COLUMNS and board[i_b][j_b] == player_number:
i_b += 1
j_b += 1
if abs(i_a - i) + abs(i_b - i) - 1 >= 4:
draw_line((i_a + 1, j_a + 1), (i_b - 1, j_b - 1), CROSS_LINE_WIDTH, color, context)
# diagonal 2
i_a = i - 1
j_a = j + 1
while i_a >= 0 and j_a < constants.CONNECT_4_N_COLUMNS and board[i_a][j_a] == player_number:
i_a -= 1
j_a += 1
i_b = i + 1
j_b = j - 1
while i_b < constants.CONNECT_4_N_ROWS and j_b >= 0 and board[i_b][j_b] == player_number:
i_b += 1
j_b -= 1
if abs(i_a - i) + abs(i_b - i) - 1 >= 4:
draw_line((i_a + 1, j_a - 1), (i_b - 1, j_b + 1), CROSS_LINE_WIDTH, color, context)
def highlight_cell(
board_position: Sequence[int],
color: tuple[float, float, float],
context: cairo.Context
):
x, y = top_left_point(board_position)
context.move_to(x, y)
x += CELL_LENGTH
context.line_to(x, y)
y += CELL_LENGTH
context.line_to(x, y)
x -= CELL_LENGTH
context.line_to(x, y)
context.close_path()
context.set_source_rgba(*color)
context.fill()
def make_image(
board: list[list[int | None]],
player: Player = None,
highlight=None,
win_position: Sequence[int] = None,
tie=False
) -> bytes:
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, SURFACE_WIDTH, SURFACE_HEIGHT)
context = cairo.Context(surface)
paint_background(BACKGROUND_COLOR, context)
if highlight:
highlight_cell(highlight, HIGHLIGHT_COLOR, context)
draw_table(TABLE_LINE_WIDTH, GRAY, context)
write_numbers(GRAY, context)
for i in range(constants.CONNECT_4_N_ROWS):
for j in range(constants.CONNECT_4_N_COLUMNS):
match board[i][j]:
case 1:
draw_circle((i, j), CIRCLE_RADIUS, PLAYER_1_COLOR, context)
case 2:
draw_circle((i, j), CIRCLE_RADIUS, PLAYER_2_COLOR, context)
if tie:
write_tie(context)
elif win_position:
player_color = PLAYER_1_COLOR if player.number == 1 else PLAYER_2_COLOR
draw_winner_lines(win_position, board, player_color, context)
write_winner(player.name, player_color, context)
else:
write_player_turn(player.name, PLAYER_1_COLOR if player.number == 1 else PLAYER_2_COLOR, context)
buffer = io.BytesIO()
surface.write_to_png(buffer)
return buffer.getvalue()
def paint_background(color: tuple[float, float, float], context: cairo.Context):
context.set_source_rgba(*color)
context.paint()
def top_left_point(board_position: Sequence[int]) -> tuple[float, float]:
return LEFT_MARGIN + board_position[1] * CELL_LENGTH, TOP_MARGIN + board_position[0] * CELL_LENGTH
def write_numbers(color: tuple[float, float, float], context: cairo.Context):
x = NUMBERS_X_INITIAL_POSITION
for j in range(constants.CONNECT_4_N_COLUMNS):
draw_text(str(j + 1), (x, NUMBERS_Y_POSITION), color, FONT_SIZE, italic=False, context=context)
x += CELL_LENGTH
def write_player_turn(name: str, color: tuple[float, float, float], context: cairo.Context):
point = TEXT_POSITION
text = 'Turno de '
draw_text(text, point, GRAY, FONT_SIZE, True, context)
point = (point[0] + context.text_extents(text).width + 9 * SIZE_MULTIPLIER, point[1])
draw_text(name, point, color, FONT_SIZE, True, context)
point = (point[0] + context.text_extents(name).width, point[1])
draw_text('.', point, GRAY, FONT_SIZE, True, context)
def write_tie(context: cairo.Context):
draw_text(f"Empate{random.choice(('.', ' :c', ' :/', ' :s'))}", TEXT_POSITION, GRAY, FONT_SIZE, True, context)
def write_winner(name: str, color: tuple[float, float, float], context: cairo.Context):
point = TEXT_POSITION
text = 'Ha ganado '
draw_text(text, TEXT_POSITION, GRAY, FONT_SIZE, True, context)
point = (point[0] + context.text_extents(text).width + 6 * SIZE_MULTIPLIER, point[1])
draw_text(name, point, color, FONT_SIZE, True, context)
point = (point[0] + context.text_extents(name).width, point[1])
draw_text('!!!', point, GRAY, FONT_SIZE, True, context)

12
flanabot/models/player.py Normal file
View File

@@ -0,0 +1,12 @@
__all__ = ['Player']
from dataclasses import dataclass
from flanautils import FlanaBase
@dataclass
class Player(FlanaBase):
id: int
name: str
number: int