mirror of
https://github.com/rio-labs/rio.git
synced 2026-02-08 23:00:39 -06:00
preparing for the new tic-tac-toe tutorial
This commit is contained in:
@@ -70,12 +70,12 @@ class Sidebar(component.Component):
|
||||
rio.Text(
|
||||
self.session.app.name,
|
||||
style="heading2",
|
||||
justify='left',
|
||||
justify="left",
|
||||
),
|
||||
rio.Text(
|
||||
"TODO: Subtext",
|
||||
style="dim",
|
||||
justify='left',
|
||||
justify="left",
|
||||
),
|
||||
align_y=0,
|
||||
),
|
||||
|
||||
@@ -102,7 +102,7 @@ class LabeledColumn(Component):
|
||||
def build(self) -> Component:
|
||||
rows = [
|
||||
[
|
||||
rio.Text(label, justify='right'),
|
||||
rio.Text(label, justify="right"),
|
||||
child,
|
||||
]
|
||||
for label, child in self.content.items()
|
||||
|
||||
@@ -195,7 +195,7 @@ class SimpleListItem(Component):
|
||||
text_children = [
|
||||
rio.Text(
|
||||
self.text,
|
||||
justify='left',
|
||||
justify="left",
|
||||
)
|
||||
]
|
||||
|
||||
@@ -205,7 +205,7 @@ class SimpleListItem(Component):
|
||||
self.secondary_text,
|
||||
wrap=True,
|
||||
style="dim",
|
||||
justify='left',
|
||||
justify="left",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ class ComponentDetails(rio.Component):
|
||||
rio.Text(
|
||||
text,
|
||||
style="dim" if is_label else "text",
|
||||
justify='right' if is_label else 'left',
|
||||
justify="right" if is_label else "left",
|
||||
),
|
||||
row_index,
|
||||
column_index,
|
||||
@@ -126,7 +126,7 @@ class ComponentDetails(rio.Component):
|
||||
rio.Text(
|
||||
target.key,
|
||||
style="dim",
|
||||
justify='left',
|
||||
justify="left",
|
||||
),
|
||||
]
|
||||
|
||||
@@ -160,7 +160,7 @@ class ComponentDetails(rio.Component):
|
||||
rio.Text(
|
||||
f"{file} line {line}",
|
||||
style="dim",
|
||||
justify='left',
|
||||
justify="left",
|
||||
),
|
||||
row_index,
|
||||
0,
|
||||
@@ -227,8 +227,8 @@ class ComponentDetails(rio.Component):
|
||||
row_index += 1
|
||||
|
||||
# Header
|
||||
result.add(rio.Text("width", style="dim", justify='left'), row_index, 1)
|
||||
result.add(rio.Text("height", style="dim", justify='left'), row_index, 2)
|
||||
result.add(rio.Text("width", style="dim", justify="left"), row_index, 1)
|
||||
result.add(rio.Text("height", style="dim", justify="left"), row_index, 2)
|
||||
row_index += 1
|
||||
|
||||
# The size as specified in Python
|
||||
|
||||
@@ -8,7 +8,7 @@ class DeployPage(rio.Component):
|
||||
"Deploy",
|
||||
style="heading2",
|
||||
margin=1,
|
||||
justify='left',
|
||||
justify="left",
|
||||
),
|
||||
rio.Column(
|
||||
rio.Icon(
|
||||
|
||||
@@ -8,7 +8,7 @@ class DocsPage(rio.Component):
|
||||
"Documentation",
|
||||
style="heading2",
|
||||
margin=1,
|
||||
justify='left',
|
||||
justify="left",
|
||||
),
|
||||
rio.Column(
|
||||
rio.Text(
|
||||
|
||||
@@ -162,9 +162,11 @@ class IconsPage(rio.Component):
|
||||
|
||||
async def _on_select_icon_by_name(self, icon_name: str) -> None:
|
||||
# Parse the icon name
|
||||
icon_set, icon_name, icon_variant = (
|
||||
rio.icon_registry.IconRegistry.parse_icon_name(icon_name)
|
||||
)
|
||||
(
|
||||
icon_set,
|
||||
icon_name,
|
||||
icon_variant,
|
||||
) = rio.icon_registry.IconRegistry.parse_icon_name(icon_name)
|
||||
|
||||
# Find all available variants
|
||||
for (
|
||||
@@ -210,7 +212,7 @@ class IconsPage(rio.Component):
|
||||
rio.Text(
|
||||
"Configure",
|
||||
style="heading3",
|
||||
justify='left',
|
||||
justify="left",
|
||||
)
|
||||
)
|
||||
|
||||
@@ -285,7 +287,7 @@ class IconsPage(rio.Component):
|
||||
rio.Text(
|
||||
"Example Code",
|
||||
style="heading3",
|
||||
justify='left',
|
||||
justify="left",
|
||||
margin_top=1,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -36,13 +36,13 @@ class ProjectPage(rio.Component):
|
||||
rio.Text(
|
||||
project_name,
|
||||
style="heading2",
|
||||
justify='left',
|
||||
justify="left",
|
||||
),
|
||||
rio.Text(
|
||||
str(self.project.project_directory),
|
||||
style="dim",
|
||||
margin_bottom=2,
|
||||
justify='left',
|
||||
justify="left",
|
||||
),
|
||||
rio.Text(
|
||||
"To launch your project, Rio needs to know the name of your python module and in which variable you've stored your app. You can configure those here.",
|
||||
|
||||
@@ -201,7 +201,7 @@ class PalettePicker(rio.Component): #
|
||||
fill=palette.foreground,
|
||||
),
|
||||
selectable=False,
|
||||
justify='left',
|
||||
justify="left",
|
||||
),
|
||||
rio.Text(
|
||||
f"#{palette.background.hex}",
|
||||
@@ -209,7 +209,7 @@ class PalettePicker(rio.Component): #
|
||||
font_size=1,
|
||||
fill=palette.foreground.replace(opacity=0.5),
|
||||
),
|
||||
justify='left',
|
||||
justify="left",
|
||||
),
|
||||
spacing=0.2,
|
||||
margin_x=1,
|
||||
@@ -419,7 +419,7 @@ class ThemePickerPage(rio.Component):
|
||||
style="heading3",
|
||||
margin_top=1,
|
||||
margin_bottom=1,
|
||||
justify='left',
|
||||
justify="left",
|
||||
),
|
||||
radius_sliders,
|
||||
# Theme Variants
|
||||
@@ -428,7 +428,7 @@ class ThemePickerPage(rio.Component):
|
||||
style="heading3",
|
||||
margin_top=1,
|
||||
margin_bottom=1,
|
||||
justify='left',
|
||||
justify="left",
|
||||
),
|
||||
rio.Row(
|
||||
rio.Spacer(),
|
||||
@@ -451,7 +451,7 @@ class ThemePickerPage(rio.Component):
|
||||
style="heading3",
|
||||
margin_top=1,
|
||||
# margin_bottom=1, Not used for now, since markdown has an oddly large margin anyway
|
||||
justify='left',
|
||||
justify="left",
|
||||
),
|
||||
rio.Markdown(
|
||||
f"""
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import rio
|
||||
|
||||
from . import pages
|
||||
|
||||
app = rio.App(
|
||||
name="Biography",
|
||||
pages=[
|
||||
rio.Page("", pages.BiographyPage),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
fastapi_app = app.as_fastapi()
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 154 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 177 KiB |
@@ -1,5 +0,0 @@
|
||||
from .about_me import AboutMe
|
||||
from .contact import Contact
|
||||
from .history import History
|
||||
from .projects import Projects
|
||||
from .skill_bars import SkillBars
|
||||
@@ -1,41 +0,0 @@
|
||||
from pathlib import Path
|
||||
from typing import * # type: ignore
|
||||
|
||||
import rio
|
||||
|
||||
|
||||
class AboutMe(rio.Component):
|
||||
def build(self) -> rio.Component:
|
||||
return rio.Row(
|
||||
rio.Image(
|
||||
Path("me.jpg"),
|
||||
width=11,
|
||||
height=11,
|
||||
corner_radius=99999,
|
||||
fill_mode="zoom",
|
||||
margin_right=2,
|
||||
),
|
||||
rio.Column(
|
||||
rio.Text(
|
||||
"Jane Doe",
|
||||
style="heading1",
|
||||
justify='left',
|
||||
),
|
||||
rio.Text(
|
||||
"Data Analyst",
|
||||
justify='left',
|
||||
),
|
||||
rio.Text(
|
||||
"AI Researcher",
|
||||
justify='left',
|
||||
),
|
||||
rio.Text(
|
||||
"Python Developer",
|
||||
justify='left',
|
||||
),
|
||||
spacing=0.5,
|
||||
align_y=0,
|
||||
width="grow",
|
||||
),
|
||||
width="grow",
|
||||
)
|
||||
@@ -1,50 +0,0 @@
|
||||
from dataclasses import KW_ONLY
|
||||
from typing import * # type: ignore
|
||||
|
||||
import rio
|
||||
|
||||
|
||||
class Contact(rio.Component):
|
||||
text: str
|
||||
icon_name_or_url: str | rio.URL
|
||||
_: KW_ONLY
|
||||
link: rio.URL | None = None
|
||||
|
||||
def build(self) -> rio.Component:
|
||||
# Prepare the image. This varies depending on whether an icon or an
|
||||
# image should be displayed.
|
||||
if isinstance(self.icon_name_or_url, str):
|
||||
image = rio.Icon(
|
||||
self.icon_name_or_url,
|
||||
width=2,
|
||||
height=2,
|
||||
margin_x=0.5,
|
||||
)
|
||||
else:
|
||||
image = rio.Image(
|
||||
self.icon_name_or_url,
|
||||
width=2,
|
||||
height=2,
|
||||
margin_x=0.5,
|
||||
)
|
||||
|
||||
# Combine everything
|
||||
result = rio.Row(
|
||||
image,
|
||||
rio.Text(
|
||||
self.text,
|
||||
justify='left',
|
||||
width="grow",
|
||||
),
|
||||
width="grow",
|
||||
margin=0.2,
|
||||
)
|
||||
|
||||
# If a link is provided, wrap the result in a link
|
||||
if self.link is not None:
|
||||
result = rio.Link(
|
||||
result,
|
||||
self.link,
|
||||
)
|
||||
|
||||
return result
|
||||
@@ -1,114 +0,0 @@
|
||||
from typing import * # type: ignore
|
||||
|
||||
import rio
|
||||
|
||||
|
||||
class HistoryItem(rio.Component):
|
||||
from_string: str
|
||||
to_string: str
|
||||
title: str
|
||||
organization: str
|
||||
details: str
|
||||
icon: str
|
||||
|
||||
is_open: bool = False
|
||||
|
||||
def on_press(self) -> None:
|
||||
self.is_open = not self.is_open
|
||||
|
||||
def build(self) -> rio.Component:
|
||||
return rio.Card(
|
||||
rio.Row(
|
||||
rio.Icon(
|
||||
self.icon,
|
||||
width=3,
|
||||
height=3,
|
||||
margin_right=2,
|
||||
align_x=0,
|
||||
align_y=0,
|
||||
),
|
||||
rio.Column(
|
||||
rio.Text(
|
||||
f"{self.from_string} - {self.to_string}",
|
||||
justify='left',
|
||||
style=rio.TextStyle(fill=rio.Color.GREY),
|
||||
),
|
||||
rio.Text(
|
||||
f"{self.title} at {self.organization}",
|
||||
style="heading3",
|
||||
justify='left',
|
||||
),
|
||||
rio.Revealer(
|
||||
None,
|
||||
rio.Text(
|
||||
self.details,
|
||||
wrap=True,
|
||||
justify='left',
|
||||
),
|
||||
is_open=self.is_open,
|
||||
),
|
||||
spacing=0.3,
|
||||
width="grow",
|
||||
align_y=0,
|
||||
),
|
||||
),
|
||||
on_press=self.on_press,
|
||||
)
|
||||
|
||||
|
||||
class History(rio.Component):
|
||||
def build(self) -> rio.Component:
|
||||
return rio.Column(
|
||||
rio.Text(
|
||||
"Professional Experience",
|
||||
style="heading2",
|
||||
margin_top=3,
|
||||
),
|
||||
HistoryItem(
|
||||
"2021",
|
||||
"Present",
|
||||
"AI Researcher",
|
||||
"DataWorks Inc.",
|
||||
"Developed and deployed machine learning models to improve the "
|
||||
"accuracy of the company's recommender system. Collaborated "
|
||||
"with a team of 10 researchers to implement a novel algorithm "
|
||||
"for predicting user behavior.",
|
||||
"science",
|
||||
),
|
||||
HistoryItem(
|
||||
"2018",
|
||||
"2021",
|
||||
"Data Analyst",
|
||||
"Insight Technologies",
|
||||
"Conducted in-depth data analysis and implemented machine "
|
||||
"learning algorithms to improve predictive models, resulting "
|
||||
"in a 15% increase in accuracy. Collaborated with "
|
||||
"cross-functional teams to develop and deploy scalable AI "
|
||||
"solutions for optimizing business operations.",
|
||||
"monitoring",
|
||||
),
|
||||
rio.Text(
|
||||
"Education",
|
||||
style="heading2",
|
||||
margin_top=3,
|
||||
),
|
||||
HistoryItem(
|
||||
"2015",
|
||||
"2018",
|
||||
"Master in Data Science",
|
||||
"Carnegie Mellon University, Pittsburgh, PA",
|
||||
'Thesis: "Predictive Modeling in Financial Markets using '
|
||||
'Machine Learning Techniques"',
|
||||
"school",
|
||||
),
|
||||
HistoryItem(
|
||||
"2012",
|
||||
"2015",
|
||||
"Bachelor in Software Engineering",
|
||||
"University of Texas, Austin, TX",
|
||||
'Thesis: "Design and Implementation of an Intelligent Traffic '
|
||||
'Control System"',
|
||||
"school",
|
||||
),
|
||||
spacing=2,
|
||||
)
|
||||
@@ -1,70 +0,0 @@
|
||||
from typing import * # type: ignore
|
||||
|
||||
import rio
|
||||
|
||||
|
||||
class Project(rio.Component):
|
||||
name: str
|
||||
details: str
|
||||
link: rio.URL
|
||||
|
||||
def on_press(self) -> None:
|
||||
self.session.navigate_to(self.link)
|
||||
|
||||
def build(self) -> rio.Component:
|
||||
return rio.Card(
|
||||
rio.Column(
|
||||
rio.Text(
|
||||
self.name,
|
||||
style="heading3",
|
||||
),
|
||||
rio.Text(
|
||||
self.details,
|
||||
wrap=True,
|
||||
),
|
||||
spacing=0.5,
|
||||
),
|
||||
on_press=self.on_press,
|
||||
)
|
||||
|
||||
|
||||
class Projects(rio.Component):
|
||||
def build(self) -> rio.Component:
|
||||
return rio.Grid(
|
||||
(
|
||||
Project(
|
||||
"uniserde",
|
||||
"Pure Python, easy to use Serializer",
|
||||
rio.URL("https://gitlab.com/Vivern/uniserde"),
|
||||
),
|
||||
Project(
|
||||
"uniserde",
|
||||
"Pure Python, easy to use Serializer",
|
||||
rio.URL("https://gitlab.com/Vivern/uniserde"),
|
||||
),
|
||||
Project(
|
||||
"uniserde",
|
||||
"Pure Python, easy to use Serializer",
|
||||
rio.URL("https://gitlab.com/Vivern/uniserde"),
|
||||
),
|
||||
),
|
||||
(
|
||||
Project(
|
||||
"uniserde",
|
||||
"Pure Python, easy to use Serializer",
|
||||
rio.URL("https://gitlab.com/Vivern/uniserde"),
|
||||
),
|
||||
Project(
|
||||
"uniserde",
|
||||
"Pure Python, easy to use Serializer",
|
||||
rio.URL("https://gitlab.com/Vivern/uniserde"),
|
||||
),
|
||||
Project(
|
||||
"uniserde",
|
||||
"Pure Python, easy to use Serializer",
|
||||
rio.URL("https://gitlab.com/Vivern/uniserde"),
|
||||
),
|
||||
),
|
||||
row_spacing=2,
|
||||
column_spacing=2,
|
||||
)
|
||||
@@ -1,30 +0,0 @@
|
||||
from typing import * # type: ignore
|
||||
|
||||
import rio
|
||||
|
||||
|
||||
class SkillBars(rio.Component):
|
||||
skills: dict[str, int]
|
||||
|
||||
def build(self) -> rio.Component:
|
||||
rows = []
|
||||
|
||||
for name, level in self.skills.items():
|
||||
rows.append(
|
||||
rio.Column(
|
||||
rio.Text(
|
||||
name,
|
||||
justify='left',
|
||||
),
|
||||
rio.ProgressBar(
|
||||
level / 10,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
return rio.Card(
|
||||
rio.Column(
|
||||
*rows,
|
||||
spacing=0.5,
|
||||
),
|
||||
)
|
||||
@@ -1 +0,0 @@
|
||||
from .biography_page import BiographyPage
|
||||
@@ -1,125 +0,0 @@
|
||||
from typing import * # type: ignore
|
||||
|
||||
import rio
|
||||
|
||||
from .. import components as comps
|
||||
|
||||
|
||||
class BiographyPage(rio.Component):
|
||||
def build(self) -> rio.Component:
|
||||
thm = self.session.theme
|
||||
|
||||
return rio.Column(
|
||||
rio.Rectangle(
|
||||
fill=rio.LinearGradientFill(
|
||||
(thm.primary_palette.background, 0),
|
||||
(thm.primary_palette.background.brighter(0.2), 1),
|
||||
),
|
||||
height=1.0,
|
||||
),
|
||||
rio.Column(
|
||||
comps.AboutMe(),
|
||||
rio.Grid(
|
||||
(
|
||||
comps.Contact(
|
||||
"123 Main Street, Anytown, USA 12345",
|
||||
"location-on",
|
||||
),
|
||||
comps.Contact(
|
||||
"Gitlab",
|
||||
rio.URL(
|
||||
"https://about.gitlab.com/images/press/logo/svg/gitlab-logo-500.svg"
|
||||
),
|
||||
link=rio.URL("https://gitlab.com"),
|
||||
),
|
||||
),
|
||||
(
|
||||
comps.Contact(
|
||||
"(123) 456-7890",
|
||||
"call:fill",
|
||||
),
|
||||
comps.Contact(
|
||||
"GitHub",
|
||||
rio.URL(
|
||||
"https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png"
|
||||
),
|
||||
link=rio.URL("https://github.com"),
|
||||
),
|
||||
),
|
||||
(
|
||||
comps.Contact(
|
||||
"janedoe@email.com",
|
||||
"mail",
|
||||
link=rio.URL("mailto:janedoe@email.com"),
|
||||
),
|
||||
comps.Contact(
|
||||
"LinkedIn",
|
||||
rio.URL(
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/c/ca/LinkedIn_logo_initials.png/768px-LinkedIn_logo_initials.png"
|
||||
),
|
||||
link=rio.URL("https://linkedin.com"),
|
||||
),
|
||||
),
|
||||
row_spacing=1.5,
|
||||
column_spacing=5,
|
||||
width="grow",
|
||||
margin_y=2,
|
||||
),
|
||||
rio.Row(
|
||||
comps.SkillBars(
|
||||
{
|
||||
"Python": 5,
|
||||
"Rust": 3,
|
||||
"SQL": 4,
|
||||
"R": 3,
|
||||
},
|
||||
width="grow",
|
||||
align_y=0,
|
||||
),
|
||||
comps.SkillBars(
|
||||
{
|
||||
"Pandas": 5,
|
||||
"Numpy": 5,
|
||||
"SciPy": 3,
|
||||
"Matplotlib": 4,
|
||||
"Seaborn": 4,
|
||||
},
|
||||
width="grow",
|
||||
align_y=0,
|
||||
),
|
||||
comps.SkillBars(
|
||||
{
|
||||
"English": 5,
|
||||
"Spanish": 3,
|
||||
"French": 2,
|
||||
},
|
||||
width="grow",
|
||||
align_y=0,
|
||||
),
|
||||
width="grow",
|
||||
spacing=5,
|
||||
),
|
||||
comps.History(margin_x=10),
|
||||
rio.Text(
|
||||
"Personal Projects",
|
||||
style="heading2",
|
||||
margin_x=8,
|
||||
margin_top=3,
|
||||
),
|
||||
comps.Projects(),
|
||||
rio.Overlay(
|
||||
rio.IconButton(
|
||||
icon="material/mail:fill",
|
||||
margin_right=3,
|
||||
margin_bottom=3,
|
||||
align_x=1,
|
||||
align_y=1,
|
||||
)
|
||||
),
|
||||
margin_y=4,
|
||||
spacing=2,
|
||||
width=40,
|
||||
align_x=0.5,
|
||||
),
|
||||
align_y=0,
|
||||
)
|
||||
@@ -1,10 +0,0 @@
|
||||
from typing import * # type: ignore
|
||||
|
||||
import rio
|
||||
|
||||
from .. import components as comps
|
||||
|
||||
|
||||
class BiographyPage(rio.Component):
|
||||
def build(self) -> rio.Component:
|
||||
return comps.AboutMe()
|
||||
@@ -0,0 +1 @@
|
||||
from .field import Field as Field
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import * # type: ignore
|
||||
|
||||
import rio
|
||||
|
||||
|
||||
# <component>
|
||||
class Field(rio.Component):
|
||||
"""
|
||||
This component represents a single field in the Tic Tac Toe game. It can
|
||||
contain an X, an O, or be empty. If the field is empty, the player can press
|
||||
on it to set their respective mark.
|
||||
"""
|
||||
|
||||
# The current value of the field.
|
||||
value: Literal["X", "O", ""]
|
||||
|
||||
# If this is `True`, the field will dim its content. This allows the game to
|
||||
# dim any fields that are not part of the winning combination when a player
|
||||
# has won.
|
||||
dim: bool
|
||||
|
||||
# The game needs to know when a player presses on a field, so it can update
|
||||
# the board accordingly. This custom event handler serves that purpose.
|
||||
on_press: rio.EventHandler[[]] = None
|
||||
|
||||
def build(self) -> rio.Component:
|
||||
# We'll need to specify the size of several components. This variable
|
||||
# makes it easier to adjust the size of the fields.
|
||||
field_size = 3
|
||||
|
||||
# If the field is empty, allow the player to press on it. Since buttons
|
||||
# would look out of place here, cards are a nice alternative.
|
||||
if self.value == "":
|
||||
return rio.Card(
|
||||
content=rio.Spacer(
|
||||
width=field_size,
|
||||
height=field_size,
|
||||
),
|
||||
on_press=self.on_press,
|
||||
)
|
||||
|
||||
# For fields that already contain an X or O, show the respective
|
||||
# icon.
|
||||
#
|
||||
# Vary the color based on the player
|
||||
color = rio.Color.RED if self.value == "X" else rio.Color.BLUE
|
||||
|
||||
# If a player has won, and this field isn't part of the winning
|
||||
# combination, dim it.
|
||||
if self.dim:
|
||||
color = color.replace(opacity=0.2)
|
||||
|
||||
# Create the icon to represent the field
|
||||
return rio.Icon(
|
||||
"material/close" if self.value == "X" else "material/circle",
|
||||
fill=color,
|
||||
width=field_size,
|
||||
height=field_size,
|
||||
)
|
||||
|
||||
|
||||
# </component>
|
||||
@@ -2,11 +2,12 @@ from __future__ import annotations
|
||||
|
||||
# <additional-imports>
|
||||
import functools
|
||||
from dataclasses import field
|
||||
from typing import * # type: ignore
|
||||
|
||||
import rio
|
||||
|
||||
from .. import components as comps
|
||||
|
||||
# </additional-imports>
|
||||
|
||||
|
||||
@@ -21,8 +22,12 @@ class TicTacToePage(rio.Component):
|
||||
Everything is contained right in this class.
|
||||
"""
|
||||
|
||||
# The contents of all fields
|
||||
fields: list[Literal["X", "O", ""]] = field(default_factory=lambda: [""] * 9)
|
||||
# The contents of all fields. Each field can contain an X, an O, or be
|
||||
# empty. The initial state is an empty board.
|
||||
#
|
||||
# The first value is the top-left field. The remaining fields follow from
|
||||
# left to right, top to bottom.
|
||||
fields: list[Literal["X", "O", ""]] = [""] * 9
|
||||
|
||||
# The player who is currently on turn
|
||||
player: Literal["X", "O"] = "X"
|
||||
@@ -32,7 +37,7 @@ class TicTacToePage(rio.Component):
|
||||
|
||||
# If there is a winner, these are the indices of the fields which made them
|
||||
# win
|
||||
winning_indices: set[int] = field(default_factory=set)
|
||||
winning_indices: set[int] = set()
|
||||
|
||||
def find_winner(self) -> None:
|
||||
"""
|
||||
@@ -104,41 +109,15 @@ class TicTacToePage(rio.Component):
|
||||
def build(self) -> rio.Component:
|
||||
# Spawn components for the fields
|
||||
field_components: list[rio.Component] = []
|
||||
field_size = 3
|
||||
|
||||
for index, field in enumerate(self.fields):
|
||||
# Allow the player to press on empty fields. Since buttons would
|
||||
# look out of place here, cards are a nice alternative.
|
||||
if field == "":
|
||||
field_components.append(
|
||||
rio.Card(
|
||||
content=rio.Spacer(
|
||||
width=field_size,
|
||||
height=field_size,
|
||||
),
|
||||
on_press=functools.partial(self.on_press, index),
|
||||
)
|
||||
)
|
||||
|
||||
# For fields that already contain an X or O, show the respective
|
||||
# icon
|
||||
else:
|
||||
# Vary the color based on the player
|
||||
color = rio.Color.RED if field == "X" else rio.Color.BLUE
|
||||
|
||||
# If a player has won highlight the winning fields
|
||||
if self.winner in ("X", "O") and index not in self.winning_indices:
|
||||
color = color.replace(opacity=0.3)
|
||||
|
||||
# Create the icon to represent the field
|
||||
field_components.append(
|
||||
rio.Icon(
|
||||
"material/close" if field == "X" else "material/circle",
|
||||
fill=color,
|
||||
width=field_size,
|
||||
height=field_size,
|
||||
)
|
||||
field_components.append(
|
||||
comps.Field(
|
||||
value=field,
|
||||
dim=self.winner is not None and index not in self.winning_indices,
|
||||
on_press=functools.partial(self.on_press, index),
|
||||
)
|
||||
)
|
||||
|
||||
# Come up with a status message
|
||||
if self.winner in ("X", "O"):
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import * # type: ignore
|
||||
|
||||
import rio
|
||||
|
||||
|
||||
# <code>
|
||||
class TicTacToePage(rio.Component):
|
||||
# The contents of all fields. Each field can contain an X, an O, or be
|
||||
# empty. The initial state is an empty board.
|
||||
fields: list[Literal["X", "O", ""]] = [""] * 9
|
||||
|
||||
def build(self) -> rio.Component:
|
||||
# Spawn components for the fields
|
||||
field_components: list[rio.Component] = []
|
||||
|
||||
for index, field in enumerate(self.fields):
|
||||
field_components.append(rio.Text(f"Field {index}"))
|
||||
|
||||
# Arrange all components in a grid
|
||||
return rio.Grid(
|
||||
field_components[0:3],
|
||||
field_components[3:6],
|
||||
field_components[6:9],
|
||||
row_spacing=1,
|
||||
column_spacing=1,
|
||||
align_x=0.5,
|
||||
)
|
||||
|
||||
|
||||
# </code>
|
||||
@@ -0,0 +1,43 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import * # type: ignore
|
||||
|
||||
import rio
|
||||
|
||||
|
||||
# <code>
|
||||
class Field(rio.Component):
|
||||
value: Literal["X", "O", ""]
|
||||
|
||||
on_press: rio.EventHandler[[]] = None
|
||||
|
||||
def build(self) -> rio.Component:
|
||||
# If the field is empty, allow the player to press on it. Since buttons
|
||||
# would look out of place here, cards are a nice alternative.
|
||||
if self.value == "":
|
||||
return rio.Card(
|
||||
content=rio.Spacer(
|
||||
width=3,
|
||||
height=3,
|
||||
),
|
||||
on_press=self.on_press,
|
||||
)
|
||||
|
||||
# For fields that already contain an X or O, show the respective icon.
|
||||
# Also vary the color based on the player.
|
||||
if self.value == "X":
|
||||
color = rio.Color.RED
|
||||
icon = "material/close"
|
||||
else:
|
||||
color = rio.Color.BLUE
|
||||
icon = "material/circle"
|
||||
|
||||
return rio.Icon(
|
||||
icon=icon,
|
||||
fill=color,
|
||||
width=3,
|
||||
height=3,
|
||||
)
|
||||
|
||||
|
||||
# </code>
|
||||
@@ -0,0 +1,36 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import * # type: ignore
|
||||
|
||||
import rio
|
||||
|
||||
from . import field as comps
|
||||
|
||||
|
||||
# <code>
|
||||
class TicTacToePage(rio.Component):
|
||||
# The contents of all fields. Each field can contain an X, an O, or be
|
||||
# empty. The initial state is an empty board.
|
||||
fields: list[Literal["X", "O", ""]] = [""] * 9
|
||||
|
||||
def build(self) -> rio.Component:
|
||||
# Spawn components for the fields
|
||||
field_components: list[rio.Component] = []
|
||||
|
||||
for index, field in enumerate(self.fields):
|
||||
comps.Field(
|
||||
value=field,
|
||||
)
|
||||
|
||||
# Arrange all components in a grid
|
||||
return rio.Grid(
|
||||
field_components[0:3],
|
||||
field_components[3:6],
|
||||
field_components[6:9],
|
||||
row_spacing=1,
|
||||
column_spacing=1,
|
||||
align_x=0.5,
|
||||
)
|
||||
|
||||
|
||||
# </code>
|
||||
@@ -0,0 +1,40 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import * # type: ignore
|
||||
|
||||
import rio
|
||||
|
||||
|
||||
# <code>
|
||||
class Field(rio.Component):
|
||||
value: Literal["X", "O", ""]
|
||||
|
||||
on_press: rio.EventHandler[[]] = None
|
||||
|
||||
def build(self) -> rio.Component:
|
||||
# If the field is empty, allow the player to press on it. Since buttons
|
||||
# would look out of place here, cards are a nice alternative.
|
||||
if self.value == "":
|
||||
return rio.Card(
|
||||
content=rio.Spacer(
|
||||
width=3,
|
||||
height=3,
|
||||
),
|
||||
on_press=self.on_press,
|
||||
)
|
||||
|
||||
# For fields that already contain an X or O, show the respective
|
||||
# icon.
|
||||
#
|
||||
# Vary the color based on the player
|
||||
color = rio.Color.RED if self.value == "X" else rio.Color.BLUE
|
||||
|
||||
return rio.Icon(
|
||||
"material/close" if self.value == "X" else "material/circle",
|
||||
fill=color,
|
||||
width=3,
|
||||
height=3,
|
||||
)
|
||||
|
||||
|
||||
# </code>
|
||||
@@ -0,0 +1,78 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
from typing import * # type: ignore
|
||||
|
||||
import rio
|
||||
|
||||
from . import field as comps
|
||||
|
||||
|
||||
# <code>
|
||||
class TicTacToePage(rio.Component):
|
||||
# The contents of all fields. Each field can contain an X, an O, or be
|
||||
# empty. The initial state is an empty board.
|
||||
fields: list[Literal["X", "O", ""]] = [""] * 9
|
||||
|
||||
# The player who is currently on turn
|
||||
player: Literal["X", "O"] = "X"
|
||||
|
||||
async def on_press(self, index: int) -> None:
|
||||
"""
|
||||
This function reacts to presses on the fields, and updates the game
|
||||
state accordingly.
|
||||
"""
|
||||
|
||||
# Set the field on that index
|
||||
self.fields[index] = self.player
|
||||
|
||||
# Next player
|
||||
self.player = "X" if self.player == "O" else "O"
|
||||
|
||||
def on_reset(self) -> None:
|
||||
"""
|
||||
Reset the game to its initial state.
|
||||
"""
|
||||
self.fields = [""] * 9
|
||||
self.player = "X"
|
||||
|
||||
def build(self) -> rio.Component:
|
||||
# Spawn components for the fields
|
||||
field_components: list[rio.Component] = []
|
||||
|
||||
for index, field in enumerate(self.fields):
|
||||
field_components.append(
|
||||
comps.Field(
|
||||
value=field,
|
||||
on_press=functools.partial(self.on_press, index),
|
||||
)
|
||||
)
|
||||
|
||||
# Come up with a status message
|
||||
message = f"{self.player}'s turn"
|
||||
|
||||
# Arrange all components in a grid
|
||||
return rio.Column(
|
||||
rio.Text(message, style="heading1"),
|
||||
rio.Grid(
|
||||
field_components[0:3],
|
||||
field_components[3:6],
|
||||
field_components[6:9],
|
||||
row_spacing=1,
|
||||
column_spacing=1,
|
||||
align_x=0.5,
|
||||
),
|
||||
rio.Button(
|
||||
"Reset",
|
||||
icon="material/refresh",
|
||||
style="plain",
|
||||
on_press=self.on_reset,
|
||||
),
|
||||
spacing=2,
|
||||
margin=2,
|
||||
align_x=0.5,
|
||||
align_y=0.0,
|
||||
)
|
||||
|
||||
|
||||
# </code>
|
||||
@@ -0,0 +1,46 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import * # type: ignore
|
||||
|
||||
import rio
|
||||
|
||||
|
||||
# <code>
|
||||
class Field(rio.Component):
|
||||
value: Literal["X", "O", ""]
|
||||
dim: bool
|
||||
|
||||
on_press: rio.EventHandler[[]] = None
|
||||
|
||||
def build(self) -> rio.Component:
|
||||
# If the field is empty, allow the player to press on it. Since buttons
|
||||
# would look out of place here, cards are a nice alternative.
|
||||
if self.value == "":
|
||||
return rio.Card(
|
||||
content=rio.Spacer(
|
||||
width=3,
|
||||
height=3,
|
||||
),
|
||||
on_press=self.on_press,
|
||||
)
|
||||
|
||||
# For fields that already contain an X or O, show the respective
|
||||
# icon.
|
||||
#
|
||||
# Vary the color based on the player
|
||||
color = rio.Color.RED if self.value == "X" else rio.Color.BLUE
|
||||
|
||||
# If a player has won, and this field isn't part of the winning
|
||||
# combination, dim it.
|
||||
if self.dim:
|
||||
color = color.replace(opacity=0.2)
|
||||
|
||||
return rio.Icon(
|
||||
"material/close" if self.value == "X" else "material/circle",
|
||||
fill=color,
|
||||
width=3,
|
||||
height=3,
|
||||
)
|
||||
|
||||
|
||||
# </code>
|
||||
@@ -0,0 +1,139 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
from typing import * # type: ignore
|
||||
|
||||
import rio
|
||||
|
||||
from . import field as comps
|
||||
|
||||
|
||||
# <code>
|
||||
class TicTacToePage(rio.Component):
|
||||
# The contents of all fields. Each field can contain an X, an O, or be
|
||||
# empty. The initial state is an empty board.
|
||||
fields: list[Literal["X", "O", ""]] = [""] * 9
|
||||
|
||||
# The player who is currently on turn
|
||||
player: Literal["X", "O"] = "X"
|
||||
|
||||
# The winner of the game, if any
|
||||
winner: Literal["X", "O", "draw"] | None = None
|
||||
|
||||
# If there is a winner, these are the indices of the fields which made them
|
||||
# win
|
||||
winning_indices: set[int] = set()
|
||||
|
||||
def find_winner(self) -> None:
|
||||
"""
|
||||
Look if there is a winner on the board, and stores it in the component's
|
||||
state. Also updates the winning indices accordingly.
|
||||
"""
|
||||
# Create a list of all possible winning combinations. If all of these
|
||||
# indices have the same value, then that value is the winner.
|
||||
winning_combinations = [
|
||||
[0, 1, 2], # Top row
|
||||
[3, 4, 5], # Middle row
|
||||
[6, 7, 8], # Bottom row
|
||||
[0, 3, 6], # Left column
|
||||
[1, 4, 7], # Middle column
|
||||
[2, 5, 8], # Right column
|
||||
[0, 4, 8], # Top-left to bottom-right diagonal
|
||||
[2, 4, 6], # Top-right to bottom-left diagonal
|
||||
]
|
||||
|
||||
# Look for winners
|
||||
for combination in winning_combinations:
|
||||
values = [self.fields[i] for i in combination]
|
||||
if values.count("X") == 3:
|
||||
self.winner = "X"
|
||||
self.winning_indices = set(combination)
|
||||
return
|
||||
|
||||
if values.count("O") == 3:
|
||||
self.winner = "O"
|
||||
self.winning_indices = set(combination)
|
||||
return
|
||||
|
||||
# If no fields are empty, it's a draw
|
||||
if "" not in self.fields:
|
||||
self.winner = "draw"
|
||||
return
|
||||
|
||||
# There is no winner yet
|
||||
pass
|
||||
|
||||
async def on_press(self, index: int) -> None:
|
||||
"""
|
||||
This function reacts to presses on the fields, and updates the game
|
||||
state accordingly.
|
||||
"""
|
||||
|
||||
# If the game is already over, don't do anything
|
||||
if self.winner:
|
||||
return
|
||||
|
||||
# Set the field on that index
|
||||
self.fields[index] = self.player
|
||||
|
||||
# Next player
|
||||
self.player = "X" if self.player == "O" else "O"
|
||||
|
||||
# See if there is a winner
|
||||
self.find_winner()
|
||||
|
||||
def on_reset(self) -> None:
|
||||
"""
|
||||
Reset the game to its initial state.
|
||||
"""
|
||||
self.fields = [""] * 9
|
||||
self.player = "X"
|
||||
self.winner = None
|
||||
self.winning_indices = set()
|
||||
|
||||
def build(self) -> rio.Component:
|
||||
# Spawn components for the fields
|
||||
field_components: list[rio.Component] = []
|
||||
|
||||
for index, field in enumerate(self.fields):
|
||||
field_components.append(
|
||||
comps.Field(
|
||||
value=field,
|
||||
dim=self.winner is not None and index not in self.winning_indices,
|
||||
on_press=functools.partial(self.on_press, index),
|
||||
)
|
||||
)
|
||||
|
||||
# Come up with a status message
|
||||
if self.winner in ("X", "O"):
|
||||
message = f"{self.winner} has won!"
|
||||
elif self.winner == "draw":
|
||||
message = "It's a draw!"
|
||||
else:
|
||||
message = f"{self.player}'s turn"
|
||||
|
||||
# Arrange all components in a grid
|
||||
return rio.Column(
|
||||
rio.Text(message, style="heading1"),
|
||||
rio.Grid(
|
||||
field_components[0:3],
|
||||
field_components[3:6],
|
||||
field_components[6:9],
|
||||
row_spacing=1,
|
||||
column_spacing=1,
|
||||
align_x=0.5,
|
||||
),
|
||||
rio.Button(
|
||||
"Reset",
|
||||
icon="material/refresh",
|
||||
style="plain",
|
||||
on_press=self.on_reset,
|
||||
),
|
||||
spacing=2,
|
||||
margin=2,
|
||||
align_x=0.5,
|
||||
align_y=0.0,
|
||||
)
|
||||
|
||||
|
||||
# </code>
|
||||
Reference in New Issue
Block a user