From 29e50d15d532f744418e17e08e667e7bb7f84944 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Mon, 18 Sep 2023 14:44:45 -0700 Subject: [PATCH 01/13] use rx.background, rx.cached_var, and rx.Cookie in the clock example --- clock/clock/clock.py | 23 +++++++++++++---------- clock/requirements.txt | 4 ++-- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/clock/clock/clock.py b/clock/clock/clock.py index 7fe97d85..f93c12b6 100644 --- a/clock/clock/clock.py +++ b/clock/clock/clock.py @@ -1,7 +1,7 @@ """A Reflex example of a analog clock.""" import asyncio -from datetime import datetime +from datetime import datetime, timezone from typing import Any import reflex as rx @@ -36,12 +36,15 @@ class State(rx.State): """The app state.""" # The time zone to display the clock in. - zone: str = "US/Pacific" + zone: str = rx.Cookie("US/Pacific") # Whether the clock is running. running: bool = False - @rx.var + # The last updated timestamp + _now: datetime = datetime.now(timezone.utc) + + @rx.cached_var def time_info(self) -> dict[str, Any]: """Get the current time info. @@ -50,7 +53,7 @@ def time_info(self) -> dict[str, Any]: Returns: A dictionary of the current time info. """ - now = datetime.now(pytz.timezone(self.zone)) + now = self._now.astimezone(pytz.timezone(self.zone)) return { "hour": now.hour if now.hour <= 12 else now.hour % 12, "minute": now.minute, @@ -66,16 +69,16 @@ def time_info(self) -> dict[str, Any]: def on_load(self): """Switch the clock off when the page refreshes.""" self.running = False - self.zone = "US/Pacific" + @rx.background async def tick(self): """Update the clock every second.""" - # Sleep for a second. - await asyncio.sleep(1) + while self.running: + async with self: + self._now = datetime.now(timezone.utc) - # If the clock is running, tick again. - if self.running: - return State.tick + # Sleep for a second. + await asyncio.sleep(1) def flip_switch(self, running: bool): """Start or stop the clock. diff --git a/clock/requirements.txt b/clock/requirements.txt index eac3e8f5..1c390de0 100644 --- a/clock/requirements.txt +++ b/clock/requirements.txt @@ -1,2 +1,2 @@ -reflex>=0.2.0 -pytz==2022.7.1 \ No newline at end of file +git+https://github.com/reflex-dev/reflex@masenf/oob-state-update +pytz==2022.7.1 From 0c4fd948dfeb52f27443204651524760af63e0e4 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Thu, 21 Sep 2023 17:50:13 -0700 Subject: [PATCH 02/13] update the time on load --- clock/clock/clock.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/clock/clock/clock.py b/clock/clock/clock.py index f93c12b6..2ddfc43d 100644 --- a/clock/clock/clock.py +++ b/clock/clock/clock.py @@ -69,13 +69,18 @@ def time_info(self) -> dict[str, Any]: def on_load(self): """Switch the clock off when the page refreshes.""" self.running = False + self.refresh() + + def refresh(self): + """Refresh the clock.""" + self._now = datetime.now(timezone.utc) @rx.background async def tick(self): """Update the clock every second.""" while self.running: async with self: - self._now = datetime.now(timezone.utc) + self.refresh() # Sleep for a second. await asyncio.sleep(1) From a076d498b1b8d6a80cbc2037620601fb4a7abddf Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Thu, 28 Sep 2023 14:31:37 -0700 Subject: [PATCH 03/13] snakegame uses a background task --- snakegame/snakegame/snakegame.py | 148 ++++++++++++++++++------------- 1 file changed, 85 insertions(+), 63 deletions(-) diff --git a/snakegame/snakegame/snakegame.py b/snakegame/snakegame/snakegame.py index e23df403..ffd0c925 100644 --- a/snakegame/snakegame/snakegame.py +++ b/snakegame/snakegame/snakegame.py @@ -12,8 +12,8 @@ HEAD_R = "R" class State(rx.State): - tmpdir:str = HEAD_R dir:str = HEAD_R # direction of what head of snake face + moves:list[str] = [] snake:list[list[int]] = [[10,10], [10,11],[10,12],[10,13],[10,14],[10,15]] # all (X,Y) for snake's body food:list[int] = [5,5] # X, Y of food cells:list[str] = (N*N)*[COLOR_NONE] @@ -21,87 +21,105 @@ class State(rx.State): score: int = 0 magic:int = 1 rate:int = 10 - start: bool = False + running: bool = False + message: str = "" + _n_tasks: int = 0 + def turnOnTick(self): - self.start = True - if self.start: - #print(self.snake) - #print(self.food) - return State.tick + if not self.running: + self.running = True + return State.loop def turnOffTick(self): - self.start = False - if self.start: - return State.tick + self.running = False + def flip_switch(self, start): - self.start = start - if self.start: - return State.tick + if self.running: + return State.turnOffTick + else: + return State.turnOnTick - async def tick(self): - if self.start: + @rx.background + async def loop(self): + async with self: + if self._n_tasks > 0: + return + self._n_tasks += 1 + self.message = "" + + while self.running: + print(f"TICK: {self.tick_cnt}") await asyncio.sleep(0.5) - self.dir = self.tmpdir - head = self.snake[-1].copy() - if(self.dir==HEAD_U): + async with self: + head = self.snake[-1].copy() + # XXX: hack needed until #1876 merges + dir = self.moves[0] if self.moves else self.dir + self.moves.pop(0) if self.moves else None + self.dir = dir + # XXX: end hack (should just use `.pop(0)`) + print(head, dir) + if(dir==HEAD_U): head[1] += (N-1) head[1] %= N - elif(self.dir==HEAD_D): + elif(dir==HEAD_D): head[1] += (N+1) head[1] %= N - elif(self.dir==HEAD_L): + elif(dir==HEAD_L): head[0] += (N-1) head[0] %= N - elif(self.dir==HEAD_R): + elif(dir==HEAD_R): head[0] += (N+1) head[0] %= N - if(head in self.snake): - self.start = False - self.magic = 1 - for i in range(N*N): - self.cells[i] = COLOR_NONE - self.snake = [[10,10], [10,11],[10,12],[10,13],[10,14],[10,15]].copy() - self.food = [5,5] - self.dir = HEAD_R - await asyncio.sleep(3) - return State.tick - - self.cells[head[0]+ N*head[1]] = COLOR_BODY - self.snake.append(head.copy()) - FOOD_EATEN = False - while(self.food in self.snake): - FOOD_EATEN = True - self.food = [random.randint(0,N-1), random.randint(0,N-1)] - self.cells[self.food[0]+ N*self.food[1]] = COLOR_FOOD - if(FOOD_EATEN==False): - self.cells[self.snake[0][0]+ N*self.snake[0][1]] = COLOR_NONE - del self.snake[0] - else: - self.score+=self.magic - self.magic += 1 - #self.rate = (int)(100*((float)self.score / (float)self.tick_cnt)) - self.rate = (int)(100*((float)(self.score)/(float)(self.tick_cnt))) - self.tick_cnt += 1 - #print(self.tick_cnt) + async with self: + if head in self.snake: + self.message = "GAME OVER" + self.running = False + self.magic = 1 + for i in range(N*N): + self.cells[i] = COLOR_NONE + self.snake = [[10,10], [10,11],[10,12],[10,13],[10,14],[10,15]].copy() + self.food = [5,5] + self.dir = HEAD_R + break + + # Move the snake + async with self: + self.cells[head[0]+ N*head[1]] = COLOR_BODY + self.snake.append(head.copy()) + FOOD_EATEN = False + while(self.food in self.snake): + FOOD_EATEN = True + self.food = [random.randint(0,N-1), random.randint(0,N-1)] + self.cells[self.food[0]+ N*self.food[1]] = COLOR_FOOD + if(FOOD_EATEN==False): + self.cells[self.snake[0][0]+ N*self.snake[0][1]] = COLOR_NONE + del self.snake[0] + else: + self.score+=self.magic + self.magic += 1 + self.rate = (int)(100*((float)(self.score)/(float)(self.tick_cnt))) + self.tick_cnt += 1 + + async with self: + self._n_tasks -= 1 - return State.tick def arrow_up(self): - if(self.dir != HEAD_D): - self.tmpdir = HEAD_U - return + if((self.moves[-1] if self.moves else self.dir) != HEAD_D): + self.moves.append(HEAD_U) + def arrow_left(self): - if(self.dir != HEAD_R): - self.tmpdir = HEAD_L - return + if((self.moves[-1] if self.moves else self.dir) != HEAD_R): + self.moves.append(HEAD_L) + def arrow_right(self): - if(self.dir != HEAD_L): - self.tmpdir = HEAD_R - return + if((self.moves[-1] if self.moves else self.dir) != HEAD_L): + self.moves.append(HEAD_R) + def arrow_down(self): - if(self.dir != HEAD_U): - self.tmpdir = HEAD_D - return + if((self.moves[-1] if self.moves else self.dir) != HEAD_U): + self.moves.append(HEAD_D) + def arrow_none(self): return @@ -113,7 +131,7 @@ def index(): rx.hstack( rx.button("PAUSE", on_click=State.turnOffTick, color_scheme="blue", border_radius="1em"), rx.button("RUN", on_click=State.turnOnTick, color_scheme="green", border_radius="1em"), - rx.switch(is_checked=State.start, on_change=State.flip_switch), + rx.switch(is_checked=State.running, on_change=State.flip_switch), ), rx.hstack( @@ -145,6 +163,10 @@ def index(): padding_right="1em", ), ), + rx.cond( + State.message, + rx.heading(State.message) + ), # Usage of foreach, please refer https://reflex.app/docs/library/layout/foreach rx.responsive_grid( rx.foreach( From 4ef11c6931712e6e31f2fd3e157e344756f0fb94 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Thu, 28 Sep 2023 16:22:47 -0700 Subject: [PATCH 04/13] snakegame: keyboard controls --- snakegame/snakegame/snakegame.py | 94 ++++++++++++++++++++++++++++---- 1 file changed, 83 insertions(+), 11 deletions(-) diff --git a/snakegame/snakegame/snakegame.py b/snakegame/snakegame/snakegame.py index ffd0c925..6033bd67 100644 --- a/snakegame/snakegame/snakegame.py +++ b/snakegame/snakegame/snakegame.py @@ -1,3 +1,4 @@ +from typing import Any, Dict, List, Optional import reflex as rx import asyncio import random @@ -6,11 +7,13 @@ COLOR_NONE="#EEEEEE" COLOR_BODY="#008800" COLOR_FOOD="#FF00FF" +COLOR_DEAD="#FF0000" HEAD_U = "U" HEAD_D = "D" HEAD_L = "L" HEAD_R = "R" + class State(rx.State): dir:str = HEAD_R # direction of what head of snake face moves:list[str] = [] @@ -22,7 +25,7 @@ class State(rx.State): magic:int = 1 rate:int = 10 running: bool = False - message: str = "" + died: bool = False _n_tasks: int = 0 def turnOnTick(self): @@ -45,7 +48,15 @@ async def loop(self): if self._n_tasks > 0: return self._n_tasks += 1 - self.message = "" + if self.died: + self.died = False + self.magic = 1 + for i in range(N*N): + self.cells[i] = COLOR_NONE + self.snake = [[10,10], [10,11],[10,12],[10,13],[10,14],[10,15]].copy() + self.food = [5,5] + self.dir = HEAD_R + self.moves = [] while self.running: print(f"TICK: {self.tick_cnt}") @@ -72,14 +83,9 @@ async def loop(self): head[0] %= N async with self: if head in self.snake: - self.message = "GAME OVER" self.running = False - self.magic = 1 - for i in range(N*N): - self.cells[i] = COLOR_NONE - self.snake = [[10,10], [10,11],[10,12],[10,13],[10,14],[10,15]].copy() - self.food = [5,5] - self.dir = HEAD_R + self.died = True + self.cells[head[0]+ N*head[1]] = COLOR_DEAD break # Move the snake @@ -123,6 +129,71 @@ def arrow_down(self): def arrow_none(self): return + def arrow_rel_left(self): + last_dir = self.moves[-1] if self.moves else self.dir + if last_dir == HEAD_U: + self.arrow_left() + elif last_dir == HEAD_L: + self.arrow_down() + elif last_dir == HEAD_D: + self.arrow_right() + elif last_dir == HEAD_R: + self.arrow_up() + + def arrow_rel_right(self): + last_dir = self.moves[-1] if self.moves else self.dir + if last_dir == HEAD_U: + self.arrow_right() + elif last_dir == HEAD_L: + self.arrow_up() + elif last_dir == HEAD_D: + self.arrow_left() + elif last_dir == HEAD_R: + self.arrow_down() + + def handle_key(self, key): + if key == "ArrowUp": + self.arrow_up() + elif key == "ArrowLeft": + self.arrow_left() + elif key == "ArrowRight": + self.arrow_right() + elif key == "ArrowDown": + self.arrow_down() + elif key == ",": + self.arrow_rel_left() + elif key == ".": + self.arrow_rel_right() + else: + print(key) + + +class GlobalKeyWatcher(rx.Fragment): + # List of keys to trigger on + keys: rx.vars.Var[List[str]] = [] + + def _get_hooks(self) -> str | None: + return """ +useEffect(() => { + const handle_key = (_e0) => { + if (%s.includes(_e0.key)) + %s + } + document.addEventListener("keydown", handle_key, false); + return () => { + document.removeEventListener("keydown", handle_key, false); + } +}) +""" % (self.keys, rx.utils.format.format_event_chain(self.event_triggers["on_key_down"])) + def get_event_triggers(self) -> Dict[str, Any]: + return { + "on_key_down": lambda e0: [e0.key], + } + + def render(self) -> str: + return "" + + def colored_box(color, index): return rx.box(bg=color, width="1em", height="1em", border="1px solid white") @@ -164,8 +235,8 @@ def index(): ), ), rx.cond( - State.message, - rx.heading(State.message) + State.died, + rx.heading("Game Over 🐍") ), # Usage of foreach, please refer https://reflex.app/docs/library/layout/foreach rx.responsive_grid( @@ -175,6 +246,7 @@ def index(): ), columns=[N], ), + GlobalKeyWatcher.create(keys=["ArrowUp", "ArrowLeft", "ArrowRight", "ArrowDown", ",", "."], on_key_down=State.handle_key), rx.hstack( rx.vstack( rx.button("○", on_click=State.arrow_none, color_scheme="#FFFFFFFF", border_radius="1em",font_size="2em"), From 4b851b87a4ed4e349488a512dd64166ee763039f Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Thu, 28 Sep 2023 16:23:11 -0700 Subject: [PATCH 05/13] snakegame: blacken --- snakegame/snakegame/snakegame.py | 177 +++++++++++++++++++++---------- 1 file changed, 120 insertions(+), 57 deletions(-) diff --git a/snakegame/snakegame/snakegame.py b/snakegame/snakegame/snakegame.py index 6033bd67..5ff5f69c 100644 --- a/snakegame/snakegame/snakegame.py +++ b/snakegame/snakegame/snakegame.py @@ -3,11 +3,11 @@ import asyncio import random -N = 19 # There is a N*N grid for ground of snake -COLOR_NONE="#EEEEEE" -COLOR_BODY="#008800" -COLOR_FOOD="#FF00FF" -COLOR_DEAD="#FF0000" +N = 19 # There is a N*N grid for ground of snake +COLOR_NONE = "#EEEEEE" +COLOR_BODY = "#008800" +COLOR_FOOD = "#FF00FF" +COLOR_DEAD = "#FF0000" HEAD_U = "U" HEAD_D = "D" HEAD_L = "L" @@ -15,15 +15,22 @@ class State(rx.State): - dir:str = HEAD_R # direction of what head of snake face - moves:list[str] = [] - snake:list[list[int]] = [[10,10], [10,11],[10,12],[10,13],[10,14],[10,15]] # all (X,Y) for snake's body - food:list[int] = [5,5] # X, Y of food - cells:list[str] = (N*N)*[COLOR_NONE] - tick_cnt:int = 1 + dir: str = HEAD_R # direction of what head of snake face + moves: list[str] = [] + snake: list[list[int]] = [ + [10, 10], + [10, 11], + [10, 12], + [10, 13], + [10, 14], + [10, 15], + ] # all (X,Y) for snake's body + food: list[int] = [5, 5] # X, Y of food + cells: list[str] = (N * N) * [COLOR_NONE] + tick_cnt: int = 1 score: int = 0 - magic:int = 1 - rate:int = 10 + magic: int = 1 + rate: int = 10 running: bool = False died: bool = False _n_tasks: int = 0 @@ -51,10 +58,17 @@ async def loop(self): if self.died: self.died = False self.magic = 1 - for i in range(N*N): + for i in range(N * N): self.cells[i] = COLOR_NONE - self.snake = [[10,10], [10,11],[10,12],[10,13],[10,14],[10,15]].copy() - self.food = [5,5] + self.snake = [ + [10, 10], + [10, 11], + [10, 12], + [10, 13], + [10, 14], + [10, 15], + ].copy() + self.food = [5, 5] self.dir = HEAD_R self.moves = [] @@ -69,61 +83,62 @@ async def loop(self): self.dir = dir # XXX: end hack (should just use `.pop(0)`) print(head, dir) - if(dir==HEAD_U): - head[1] += (N-1) + if dir == HEAD_U: + head[1] += N - 1 head[1] %= N - elif(dir==HEAD_D): - head[1] += (N+1) + elif dir == HEAD_D: + head[1] += N + 1 head[1] %= N - elif(dir==HEAD_L): - head[0] += (N-1) + elif dir == HEAD_L: + head[0] += N - 1 head[0] %= N - elif(dir==HEAD_R): - head[0] += (N+1) + elif dir == HEAD_R: + head[0] += N + 1 head[0] %= N async with self: if head in self.snake: self.running = False self.died = True - self.cells[head[0]+ N*head[1]] = COLOR_DEAD + self.cells[head[0] + N * head[1]] = COLOR_DEAD break # Move the snake async with self: - self.cells[head[0]+ N*head[1]] = COLOR_BODY + self.cells[head[0] + N * head[1]] = COLOR_BODY self.snake.append(head.copy()) FOOD_EATEN = False - while(self.food in self.snake): + while self.food in self.snake: FOOD_EATEN = True - self.food = [random.randint(0,N-1), random.randint(0,N-1)] - self.cells[self.food[0]+ N*self.food[1]] = COLOR_FOOD - if(FOOD_EATEN==False): - self.cells[self.snake[0][0]+ N*self.snake[0][1]] = COLOR_NONE + self.food = [random.randint(0, N - 1), random.randint(0, N - 1)] + self.cells[self.food[0] + N * self.food[1]] = COLOR_FOOD + if FOOD_EATEN == False: + self.cells[self.snake[0][0] + N * self.snake[0][1]] = COLOR_NONE del self.snake[0] else: - self.score+=self.magic + self.score += self.magic self.magic += 1 - self.rate = (int)(100*((float)(self.score)/(float)(self.tick_cnt))) + self.rate = (int)( + 100 * ((float)(self.score) / (float)(self.tick_cnt)) + ) self.tick_cnt += 1 async with self: self._n_tasks -= 1 - def arrow_up(self): - if((self.moves[-1] if self.moves else self.dir) != HEAD_D): + if (self.moves[-1] if self.moves else self.dir) != HEAD_D: self.moves.append(HEAD_U) def arrow_left(self): - if((self.moves[-1] if self.moves else self.dir) != HEAD_R): + if (self.moves[-1] if self.moves else self.dir) != HEAD_R: self.moves.append(HEAD_L) def arrow_right(self): - if((self.moves[-1] if self.moves else self.dir) != HEAD_L): + if (self.moves[-1] if self.moves else self.dir) != HEAD_L: self.moves.append(HEAD_R) def arrow_down(self): - if((self.moves[-1] if self.moves else self.dir) != HEAD_U): + if (self.moves[-1] if self.moves else self.dir) != HEAD_U: self.moves.append(HEAD_D) def arrow_none(self): @@ -184,12 +199,16 @@ def _get_hooks(self) -> str | None: document.removeEventListener("keydown", handle_key, false); } }) -""" % (self.keys, rx.utils.format.format_event_chain(self.event_triggers["on_key_down"])) +""" % ( + self.keys, + rx.utils.format.format_event_chain(self.event_triggers["on_key_down"]), + ) + def get_event_triggers(self) -> Dict[str, Any]: return { "on_key_down": lambda e0: [e0.key], } - + def render(self) -> str: return "" @@ -197,14 +216,24 @@ def render(self) -> str: def colored_box(color, index): return rx.box(bg=color, width="1em", height="1em", border="1px solid white") + def index(): return rx.vstack( rx.hstack( - rx.button("PAUSE", on_click=State.turnOffTick, color_scheme="blue", border_radius="1em"), - rx.button("RUN", on_click=State.turnOnTick, color_scheme="green", border_radius="1em"), + rx.button( + "PAUSE", + on_click=State.turnOffTick, + color_scheme="blue", + border_radius="1em", + ), + rx.button( + "RUN", + on_click=State.turnOnTick, + color_scheme="green", + border_radius="1em", + ), rx.switch(is_checked=State.running, on_change=State.flip_switch), ), - rx.hstack( rx.vstack( rx.heading("RATE", font_size="1em"), @@ -213,9 +242,7 @@ def index(): border_width="1px", padding_left="1em", padding_right="1em", - ), - rx.vstack( rx.heading("SCORE", font_size="1em"), rx.heading(State.score, font_size="2em"), @@ -223,7 +250,6 @@ def index(): border_width="1px", padding_left="1em", padding_right="1em", - ), rx.vstack( rx.heading("MAGIC", font_size="1em"), @@ -234,10 +260,7 @@ def index(): padding_right="1em", ), ), - rx.cond( - State.died, - rx.heading("Game Over 🐍") - ), + rx.cond(State.died, rx.heading("Game Over 🐍")), # Usage of foreach, please refer https://reflex.app/docs/library/layout/foreach rx.responsive_grid( rx.foreach( @@ -246,24 +269,64 @@ def index(): ), columns=[N], ), - GlobalKeyWatcher.create(keys=["ArrowUp", "ArrowLeft", "ArrowRight", "ArrowDown", ",", "."], on_key_down=State.handle_key), + GlobalKeyWatcher.create( + keys=["ArrowUp", "ArrowLeft", "ArrowRight", "ArrowDown", ",", "."], + on_key_down=State.handle_key, + ), rx.hstack( rx.vstack( - rx.button("○", on_click=State.arrow_none, color_scheme="#FFFFFFFF", border_radius="1em",font_size="2em"), - rx.button("←", on_click=State.arrow_left, color_scheme="red", border_radius="1em",font_size="2em"), + rx.button( + "○", + on_click=State.arrow_none, + color_scheme="#FFFFFFFF", + border_radius="1em", + font_size="2em", + ), + rx.button( + "←", + on_click=State.arrow_left, + color_scheme="red", + border_radius="1em", + font_size="2em", + ), ), rx.vstack( - rx.button("↑", on_click=State.arrow_up, color_scheme="red", border_radius="1em",font_size="2em"), - rx.button("↓", on_click=State.arrow_down, color_scheme="red", border_radius="1em",font_size="2em"), + rx.button( + "↑", + on_click=State.arrow_up, + color_scheme="red", + border_radius="1em", + font_size="2em", + ), + rx.button( + "↓", + on_click=State.arrow_down, + color_scheme="red", + border_radius="1em", + font_size="2em", + ), ), rx.vstack( - rx.button("○", on_click=State.arrow_none, color_scheme="#FFFFFFFF", border_radius="1em",font_size="2em"), - rx.button("→", on_click=State.arrow_right, color_scheme="red", border_radius="1em",font_size="2em"), + rx.button( + "○", + on_click=State.arrow_none, + color_scheme="#FFFFFFFF", + border_radius="1em", + font_size="2em", + ), + rx.button( + "→", + on_click=State.arrow_right, + color_scheme="red", + border_radius="1em", + font_size="2em", + ), ), ), padding_top="3%", ) + app = rx.App(state=State) app.add_page(index, title="snake game") From 9267d6beac304c86cdc0dde9c675563a39c634bb Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Fri, 29 Sep 2023 15:03:58 -0700 Subject: [PATCH 06/13] snakegame: reformat and clean up code Add docstrings and comments Separate UI into sub functions Move defaults and constants to module level --- snakegame/requirements.txt | 4 +- snakegame/snakegame/snakegame.py | 393 ++++++++++++++++--------------- 2 files changed, 204 insertions(+), 193 deletions(-) diff --git a/snakegame/requirements.txt b/snakegame/requirements.txt index 210218f4..e303bd41 100644 --- a/snakegame/requirements.txt +++ b/snakegame/requirements.txt @@ -1 +1,3 @@ -reflex>=0.2.0 \ No newline at end of file +#reflex>=0.2.9 +# temporary until state.reset() bug is fixed +git+https://github.com/reflex-dev/reflex@masenf/state-reset-deepcopy diff --git a/snakegame/snakegame/snakegame.py b/snakegame/snakegame/snakegame.py index 5ff5f69c..b5e4db0e 100644 --- a/snakegame/snakegame/snakegame.py +++ b/snakegame/snakegame/snakegame.py @@ -1,189 +1,203 @@ -from typing import Any, Dict, List, Optional -import reflex as rx import asyncio import random +from typing import Any, Dict, List + +import reflex as rx N = 19 # There is a N*N grid for ground of snake COLOR_NONE = "#EEEEEE" COLOR_BODY = "#008800" COLOR_FOOD = "#FF00FF" COLOR_DEAD = "#FF0000" -HEAD_U = "U" -HEAD_D = "D" -HEAD_L = "L" -HEAD_R = "R" +# Tuples representing the directions the snake head can move +HEAD_U = (0, -1) +HEAD_D = (0, 1) +HEAD_L = (-1, 0) +HEAD_R = (1, 0) +INITIAL_SNAKE = [ # all (X,Y) for snake's body + (-1, -1), + (-1, -1), + (-1, -1), + (-1, -1), + (-1, -1), + (10, 15), # Starting head position +] +INITIAL_FOOD = (5, 5) # X, Y of food + + +def get_new_head(old_head: tuple[int, int], dir: tuple[int, int]) -> tuple[int, int]: + """Calculate the new head position based on the given direction.""" + x, y = old_head + return (x + dir[0] + N) % N, (y + dir[1] + N) % N + + +def to_cell_index(x: int, y: int) -> int: + """Calculate the index into the game board for the given (X, Y).""" + return x + N * y class State(rx.State): - dir: str = HEAD_R # direction of what head of snake face - moves: list[str] = [] - snake: list[list[int]] = [ - [10, 10], - [10, 11], - [10, 12], - [10, 13], - [10, 14], - [10, 15], - ] # all (X,Y) for snake's body - food: list[int] = [5, 5] # X, Y of food - cells: list[str] = (N * N) * [COLOR_NONE] - tick_cnt: int = 1 - score: int = 0 - magic: int = 1 - rate: int = 10 + dir: str = HEAD_R # Direction the snake head is facing currently + moves: list[tuple[int, int]] = [] # Queue of moves based on user input + snake: list[tuple[int, int]] = INITIAL_SNAKE # Body of snake + food: tuple[int, int] = INITIAL_FOOD # X, Y location of food + cells: list[str] = (N * N) * [COLOR_NONE] # The game board to be rendered + score: int = 0 # Player score + magic: int = 1 # Number of points per food eaten + rate: int = 10 # 5 divide by rate determines tick period + died: bool = False # If the snake is dead (game over) + tick_cnt: int = 1 # How long the game has been running running: bool = False - died: bool = False _n_tasks: int = 0 - def turnOnTick(self): + def play(self): + """Start / resume the game.""" if not self.running: + if self.died: + # If the player is dead, reset game state before beginning. + self.reset() self.running = True return State.loop - def turnOffTick(self): + def pause(self): + """Signal the game to pause.""" self.running = False def flip_switch(self, start): - if self.running: - return State.turnOffTick + """Toggle whether the game is running or paused.""" + if start: + return State.play else: - return State.turnOnTick + return State.pause + + def _next_move(self): + """Returns the next direction the snake head should move in.""" + return self.moves[0] if self.moves else self.dir + + def _last_move(self): + """Returns the last queued direction the snake head should move in.""" + return self.moves[-1] if self.moves else self.dir @rx.background async def loop(self): + """The main game loop, implemented as a singleton background task. + + Responsible for updating the game state on each tick. + """ async with self: if self._n_tasks > 0: + # Only start one loop task at a time. return self._n_tasks += 1 - if self.died: - self.died = False - self.magic = 1 - for i in range(N * N): - self.cells[i] = COLOR_NONE - self.snake = [ - [10, 10], - [10, 11], - [10, 12], - [10, 13], - [10, 14], - [10, 15], - ].copy() - self.food = [5, 5] - self.dir = HEAD_R - self.moves = [] while self.running: - print(f"TICK: {self.tick_cnt}") - await asyncio.sleep(0.5) - async with self: - head = self.snake[-1].copy() - # XXX: hack needed until #1876 merges - dir = self.moves[0] if self.moves else self.dir - self.moves.pop(0) if self.moves else None - self.dir = dir - # XXX: end hack (should just use `.pop(0)`) - print(head, dir) - if dir == HEAD_U: - head[1] += N - 1 - head[1] %= N - elif dir == HEAD_D: - head[1] += N + 1 - head[1] %= N - elif dir == HEAD_L: - head[0] += N - 1 - head[0] %= N - elif dir == HEAD_R: - head[0] += N + 1 - head[0] %= N + # Sleep based on the current rate + await asyncio.sleep(5 / self.rate) async with self: + # Which direction will the snake move? + self.dir = self._next_move() + if self.moves: + # Remove the processed next move from the queue + del self.moves[0] + + # Calculate new head position + head = get_new_head(self.snake[-1], dir=self.dir) if head in self.snake: + # New head position crashes into snake body, Game Over + print(head, self.snake) self.running = False self.died = True - self.cells[head[0] + N * head[1]] = COLOR_DEAD + self.cells[to_cell_index(*head)] = COLOR_DEAD break - # Move the snake - async with self: - self.cells[head[0] + N * head[1]] = COLOR_BODY - self.snake.append(head.copy()) - FOOD_EATEN = False + # Move the snake + self.snake.append(head) + self.cells[to_cell_index(*head)] = COLOR_BODY + food_eaten = False while self.food in self.snake: - FOOD_EATEN = True - self.food = [random.randint(0, N - 1), random.randint(0, N - 1)] - self.cells[self.food[0] + N * self.food[1]] = COLOR_FOOD - if FOOD_EATEN == False: - self.cells[self.snake[0][0] + N * self.snake[0][1]] = COLOR_NONE + food_eaten = True + self.food = (random.randint(0, N - 1), random.randint(0, N - 1)) + self.cells[to_cell_index(*self.food)] = COLOR_FOOD + if not food_eaten: + # Advance the snake + self.cells[to_cell_index(*self.snake[0])] = COLOR_NONE del self.snake[0] else: + # Grow the snake (and the score) self.score += self.magic self.magic += 1 - self.rate = (int)( - 100 * ((float)(self.score) / (float)(self.tick_cnt)) - ) + self.rate = 10 + self.magic self.tick_cnt += 1 async with self: + # Decrement task counter, since we're about to return self._n_tasks -= 1 def arrow_up(self): - if (self.moves[-1] if self.moves else self.dir) != HEAD_D: + """Queue a move up.""" + if self._last_move() != HEAD_D: self.moves.append(HEAD_U) def arrow_left(self): - if (self.moves[-1] if self.moves else self.dir) != HEAD_R: + """Queue a move left.""" + if self._last_move() != HEAD_R: self.moves.append(HEAD_L) def arrow_right(self): - if (self.moves[-1] if self.moves else self.dir) != HEAD_L: + """Queue a move right.""" + if self._last_move() != HEAD_L: self.moves.append(HEAD_R) def arrow_down(self): - if (self.moves[-1] if self.moves else self.dir) != HEAD_U: + """Queue a move down.""" + if self._last_move() != HEAD_U: self.moves.append(HEAD_D) - def arrow_none(self): - return - def arrow_rel_left(self): - last_dir = self.moves[-1] if self.moves else self.dir - if last_dir == HEAD_U: + """Queue a move left relative to the current direction.""" + last_move = self._last_move() + if last_move == HEAD_U: self.arrow_left() - elif last_dir == HEAD_L: + elif last_move == HEAD_L: self.arrow_down() - elif last_dir == HEAD_D: + elif last_move == HEAD_D: self.arrow_right() - elif last_dir == HEAD_R: + elif last_move == HEAD_R: self.arrow_up() def arrow_rel_right(self): - last_dir = self.moves[-1] if self.moves else self.dir - if last_dir == HEAD_U: + """Queue a move right relative to the current direction.""" + last_move = self._last_move() + if last_move == HEAD_U: self.arrow_right() - elif last_dir == HEAD_L: + elif last_move == HEAD_L: self.arrow_up() - elif last_dir == HEAD_D: + elif last_move == HEAD_D: self.arrow_left() - elif last_dir == HEAD_R: + elif last_move == HEAD_R: self.arrow_down() def handle_key(self, key): - if key == "ArrowUp": - self.arrow_up() - elif key == "ArrowLeft": - self.arrow_left() - elif key == "ArrowRight": - self.arrow_right() - elif key == "ArrowDown": - self.arrow_down() - elif key == ",": - self.arrow_rel_left() - elif key == ".": - self.arrow_rel_right() - else: - print(key) + """Handle keyboard press.""" + { + "ArrowUp": self.arrow_up, + "ArrowLeft": self.arrow_left, + "ArrowRight": self.arrow_right, + "ArrowDown": self.arrow_down, + ",": self.arrow_rel_left, + ".": self.arrow_rel_right, + }[key]() class GlobalKeyWatcher(rx.Fragment): + """A component that attaches a keydown handler to the document. + + The handler only calls the backend function if the pressed key is one of the + specified keys. + + Requires custom javascript to support this functionality at the moment. + """ + # List of keys to trigger on keys: rx.vars.Var[List[str]] = [] @@ -210,57 +224,104 @@ def get_event_triggers(self) -> Dict[str, Any]: } def render(self) -> str: + # This component has no visual element. return "" def colored_box(color, index): + """One square of the game grid.""" return rx.box(bg=color, width="1em", height="1em", border="1px solid white") +def stat_box(label, value): + """One of the score, magic, or rate boxes.""" + return rx.vstack( + rx.heading(label, font_size="1em"), + rx.heading(value, font_size="2em"), + bg_color="yellow", + border_width="1px", + padding_left="1em", + padding_right="1em", + ) + + +def control_button(label, on_click): + """One of the arrow buttons for touch/mouse control.""" + return rx.button( + label, + on_click=on_click, + color_scheme="red", + border_radius="1em", + font_size="2em", + ) + + +def padding_button(): + """A button that is used for padding in the controls panel.""" + return rx.button( + "○", + color_scheme="#FFFFFF", + border_radius="1em", + font_size="2em", + ) + + +def controls_panel(): + """The controls panel of arrow buttons.""" + return rx.hstack( + GlobalKeyWatcher.create( + keys=["ArrowUp", "ArrowLeft", "ArrowRight", "ArrowDown", ",", "."], + on_key_down=State.handle_key, + ), + rx.vstack( + padding_button(), + control_button( + "←", + on_click=State.arrow_left, + ), + ), + rx.vstack( + control_button( + "↑", + on_click=State.arrow_up, + ), + control_button( + "↓", + on_click=State.arrow_down, + ), + ), + rx.vstack( + padding_button(), + control_button( + "→", + on_click=State.arrow_right, + ), + ), + ) + + def index(): return rx.vstack( rx.hstack( rx.button( "PAUSE", - on_click=State.turnOffTick, + on_click=State.pause, color_scheme="blue", border_radius="1em", ), rx.button( "RUN", - on_click=State.turnOnTick, + on_click=State.play, color_scheme="green", border_radius="1em", ), rx.switch(is_checked=State.running, on_change=State.flip_switch), ), rx.hstack( - rx.vstack( - rx.heading("RATE", font_size="1em"), - rx.heading(State.rate, font_size="2em"), - bg_color="yellow", - border_width="1px", - padding_left="1em", - padding_right="1em", - ), - rx.vstack( - rx.heading("SCORE", font_size="1em"), - rx.heading(State.score, font_size="2em"), - bg_color="yellow", - border_width="1px", - padding_left="1em", - padding_right="1em", - ), - rx.vstack( - rx.heading("MAGIC", font_size="1em"), - rx.heading(State.magic, font_size="2em"), - bg_color="yellow", - border_width="1px", - padding_left="1em", - padding_right="1em", - ), + stat_box("RATE", State.rate), + stat_box("SCORE", State.score), + stat_box("MAGIC", State.magic), ), - rx.cond(State.died, rx.heading("Game Over 🐍")), # Usage of foreach, please refer https://reflex.app/docs/library/layout/foreach rx.responsive_grid( rx.foreach( @@ -269,60 +330,8 @@ def index(): ), columns=[N], ), - GlobalKeyWatcher.create( - keys=["ArrowUp", "ArrowLeft", "ArrowRight", "ArrowDown", ",", "."], - on_key_down=State.handle_key, - ), - rx.hstack( - rx.vstack( - rx.button( - "○", - on_click=State.arrow_none, - color_scheme="#FFFFFFFF", - border_radius="1em", - font_size="2em", - ), - rx.button( - "←", - on_click=State.arrow_left, - color_scheme="red", - border_radius="1em", - font_size="2em", - ), - ), - rx.vstack( - rx.button( - "↑", - on_click=State.arrow_up, - color_scheme="red", - border_radius="1em", - font_size="2em", - ), - rx.button( - "↓", - on_click=State.arrow_down, - color_scheme="red", - border_radius="1em", - font_size="2em", - ), - ), - rx.vstack( - rx.button( - "○", - on_click=State.arrow_none, - color_scheme="#FFFFFFFF", - border_radius="1em", - font_size="2em", - ), - rx.button( - "→", - on_click=State.arrow_right, - color_scheme="red", - border_radius="1em", - font_size="2em", - ), - ), - ), + rx.cond(State.died, rx.heading("Game Over 🐍")), + controls_panel(), padding_top="3%", ) From c014a051dbc459b16668d0742bc7d4802abb6336 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Mon, 9 Oct 2023 09:53:04 -0700 Subject: [PATCH 07/13] Update requirements.txt Bump to reflex>=0.2.8 --- clock/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clock/requirements.txt b/clock/requirements.txt index 1c390de0..41c68de8 100644 --- a/clock/requirements.txt +++ b/clock/requirements.txt @@ -1,2 +1,2 @@ -git+https://github.com/reflex-dev/reflex@masenf/oob-state-update +reflex>=0.2.8 pytz==2022.7.1 From f9848542675336a4a0c0d52e90059ee575f2bc9e Mon Sep 17 00:00:00 2001 From: Lendemor Date: Mon, 9 Oct 2023 22:31:23 +0200 Subject: [PATCH 08/13] update all examples for 0.2.9 --- chatroom/chatroom/chatroom.py | 6 +- chatroom/requirements.txt | 2 +- clock/requirements.txt | 2 +- counter/requirements.txt | 2 +- crm/requirements.txt | 2 +- dalle/.gitignore | 1 + dalle/dalle/dalle.py | 9 ++- dalle/requirements.txt | 2 +- ecommerce/.gitignore | 1 + ecommerce/ecommerce/ecommerce.py | 2 +- ecommerce/requirements.txt | 2 +- fragments/requirements.txt | 2 +- github-stats/github_stats/github_stats.py | 22 ++++-- github-stats/requirements.txt | 2 +- gpt/requirements.txt | 2 +- local_auth/requirements.txt | 2 +- lorem-stream/lorem_stream/lorem_stream.py | 4 +- lorem-stream/requirements.txt | 2 +- nba/requirements.txt | 2 +- qr-scanner/qr_scanner/component.py | 15 ++-- qr-scanner/qr_scanner/qr_scanner.py | 2 +- qr-scanner/requirements.txt | 2 +- qr-scanner/rxconfig.py | 3 +- quiz/.gitignore | 1 + quiz/quiz/quiz.py | 10 ++- quiz/quiz/results.py | 4 +- quiz/requirements.txt | 2 +- sales/.gitignore | 1 + sales/assets/favicon.ico | Bin 15406 -> 15086 bytes sales/requirements.txt | 2 +- sales/sales/sales.py | 90 ++++++++++++++++------ snakegame/requirements.txt | 2 +- tailwind/requirements.txt | 2 +- todo/requirements.txt | 2 +- translator/requirements.txt | 2 +- traversal/requirements.txt | 2 +- traversal/traversal/traversal.py | 9 +-- 37 files changed, 145 insertions(+), 75 deletions(-) diff --git a/chatroom/chatroom/chatroom.py b/chatroom/chatroom/chatroom.py index 1e3701e6..41d372df 100644 --- a/chatroom/chatroom/chatroom.py +++ b/chatroom/chatroom/chatroom.py @@ -74,7 +74,7 @@ def index() -> rx.Component: ), rx.button("Send", on_click=State.send_message), ), - on_submit=State.send_message, + on_submit=lambda d: State.send_message(), ), width="60vw", align_items="left", @@ -104,7 +104,9 @@ async def broadcast_event(name: str, payload: t.Dict[str, t.Any] = {}) -> None: # Emit the event. responses.append( app.event_namespace.emit( - str(rx.constants.SocketEvent.EVENT), update.json(), to=state.get_sid() + str(rx.constants.SocketEvent.EVENT), + update.json(), + to=state.get_sid(), ), ) for response in responses: diff --git a/chatroom/requirements.txt b/chatroom/requirements.txt index bc6a7f2e..c8ade313 100644 --- a/chatroom/requirements.txt +++ b/chatroom/requirements.txt @@ -1 +1 @@ -reflex>=0.2.0 +reflex>=0.2.9 diff --git a/clock/requirements.txt b/clock/requirements.txt index eac3e8f5..aa2fc63e 100644 --- a/clock/requirements.txt +++ b/clock/requirements.txt @@ -1,2 +1,2 @@ -reflex>=0.2.0 +reflex>=0.2.9 pytz==2022.7.1 \ No newline at end of file diff --git a/counter/requirements.txt b/counter/requirements.txt index bc6a7f2e..c8ade313 100644 --- a/counter/requirements.txt +++ b/counter/requirements.txt @@ -1 +1 @@ -reflex>=0.2.0 +reflex>=0.2.9 diff --git a/crm/requirements.txt b/crm/requirements.txt index 210218f4..825836d9 100644 --- a/crm/requirements.txt +++ b/crm/requirements.txt @@ -1 +1 @@ -reflex>=0.2.0 \ No newline at end of file +reflex>=0.2.9 \ No newline at end of file diff --git a/dalle/.gitignore b/dalle/.gitignore index bf56ff42..f6711845 100644 --- a/dalle/.gitignore +++ b/dalle/.gitignore @@ -1,3 +1,4 @@ +*.db *.py[cod] .web __pycache__/ diff --git a/dalle/dalle/dalle.py b/dalle/dalle/dalle.py index 3bc4dc15..2d715f89 100644 --- a/dalle/dalle/dalle.py +++ b/dalle/dalle/dalle.py @@ -3,14 +3,15 @@ import reflex as rx - class State(rx.State): """The app state.""" + image_url = "" image_processing = False image_made = False + def get_dalle_result(self, form_data: dict[str, str]): - prompt_text:str = form_data["prompt_text"] + prompt_text: str = form_data["prompt_text"] self.image_made = False self.image_processing = True yield @@ -24,6 +25,7 @@ def get_dalle_result(self, form_data: dict[str, str]): self.image_processing = False yield rx.window_alert("Error with OpenAI Execution.") + def index(): return rx.center( rx.vstack( @@ -60,7 +62,8 @@ def index(): background="radial-gradient(circle at 22% 11%,rgba(62, 180, 137,.20),hsla(0,0%,100%,0) 19%),radial-gradient(circle at 82% 25%,rgba(33,150,243,.18),hsla(0,0%,100%,0) 35%),radial-gradient(circle at 25% 61%,rgba(250, 128, 114, .28),hsla(0,0%,100%,0) 55%)", ) + # Add state and page to the app. app = rx.App(state=State) -app.add_page(index, title="Pynecone:DALL-E") +app.add_page(index, title="Reflex:DALL-E") app.compile() diff --git a/dalle/requirements.txt b/dalle/requirements.txt index 743a150e..63a0fc6d 100644 --- a/dalle/requirements.txt +++ b/dalle/requirements.txt @@ -1,2 +1,2 @@ -reflex>=0.2.0 +reflex>=0.2.9 openai diff --git a/ecommerce/.gitignore b/ecommerce/.gitignore index bf56ff42..f6711845 100644 --- a/ecommerce/.gitignore +++ b/ecommerce/.gitignore @@ -1,3 +1,4 @@ +*.db *.py[cod] .web __pycache__/ diff --git a/ecommerce/ecommerce/ecommerce.py b/ecommerce/ecommerce/ecommerce.py index 15443afb..0b0bb73a 100644 --- a/ecommerce/ecommerce/ecommerce.py +++ b/ecommerce/ecommerce/ecommerce.py @@ -127,7 +127,7 @@ def inventory(): data=State.product_data, pagination=True, sort=True, - search=True, + # search=True, ) return rx.vstack(search_bar, table) diff --git a/ecommerce/requirements.txt b/ecommerce/requirements.txt index 685db61e..cc43b3bf 100644 --- a/ecommerce/requirements.txt +++ b/ecommerce/requirements.txt @@ -1,3 +1,3 @@ -reflex>=0.2.0 +reflex>=0.2.9 icecream==2.1.1 pandas==2.1.0 \ No newline at end of file diff --git a/fragments/requirements.txt b/fragments/requirements.txt index 210218f4..825836d9 100644 --- a/fragments/requirements.txt +++ b/fragments/requirements.txt @@ -1 +1 @@ -reflex>=0.2.0 \ No newline at end of file +reflex>=0.2.9 \ No newline at end of file diff --git a/github-stats/github_stats/github_stats.py b/github-stats/github_stats/github_stats.py index a96e334b..c2e1aedb 100644 --- a/github-stats/github_stats/github_stats.py +++ b/github-stats/github_stats/github_stats.py @@ -143,18 +143,24 @@ def index() -> rx.Component: on_submit=State.handle_form, ), rx.box( - rx.bar_chart( - rx.graphing_tooltip(cursor=False), - rx.bar( + rx.recharts.bar_chart( + rx.recharts.graphing_tooltip(cursor=False), + rx.recharts.bar( data_key="repositoriesContributedTo", stroke="#8884d8", fill="#8884d8", ), - rx.bar(data_key="mergedPullRequests", stroke="#82ca9d", fill="#82ca9d"), - rx.bar(data_key="openIssues", stroke="#ffc658", fill="#ffc658"), - rx.bar(data_key="closedIssues", stroke="#ff0000", fill="#ff0000"), - rx.x_axis(data_key="login"), - rx.y_axis(), + rx.recharts.bar( + data_key="mergedPullRequests", stroke="#82ca9d", fill="#82ca9d" + ), + rx.recharts.bar( + data_key="openIssues", stroke="#ffc658", fill="#ffc658" + ), + rx.recharts.bar( + data_key="closedIssues", stroke="#ff0000", fill="#ff0000" + ), + rx.recharts.x_axis(data_key="login"), + rx.recharts.y_axis(), data=State.user_stats, ), width="100%", diff --git a/github-stats/requirements.txt b/github-stats/requirements.txt index 66a9f3f6..c8ade313 100644 --- a/github-stats/requirements.txt +++ b/github-stats/requirements.txt @@ -1 +1 @@ -reflex>=0.3.0a1 +reflex>=0.2.9 diff --git a/gpt/requirements.txt b/gpt/requirements.txt index 743a150e..63a0fc6d 100644 --- a/gpt/requirements.txt +++ b/gpt/requirements.txt @@ -1,2 +1,2 @@ -reflex>=0.2.0 +reflex>=0.2.9 openai diff --git a/local_auth/requirements.txt b/local_auth/requirements.txt index 86a89dac..be916f20 100644 --- a/local_auth/requirements.txt +++ b/local_auth/requirements.txt @@ -1,3 +1,3 @@ -reflex>=0.2.7 +reflex>=0.2.9 passlib bcrypt diff --git a/lorem-stream/lorem_stream/lorem_stream.py b/lorem-stream/lorem_stream/lorem_stream.py index f0e91e5c..2f413732 100644 --- a/lorem-stream/lorem_stream/lorem_stream.py +++ b/lorem-stream/lorem_stream/lorem_stream.py @@ -69,7 +69,9 @@ def render_task(task_id: int) -> rx.Component: is_indeterminate=LoremState.progress[task_id] < 1, ), rx.button( - rx.cond(LoremState.progress[task_id] < LoremState.end_at[task_id], "⏯️", "🔄"), + rx.cond( + LoremState.progress[task_id] < LoremState.end_at[task_id], "⏯️", "🔄" + ), on_click=LoremState.toggle_running(task_id), ), rx.button("❌", on_click=LoremState.kill(task_id)), diff --git a/lorem-stream/requirements.txt b/lorem-stream/requirements.txt index 5f7645ad..ca4f55a6 100644 --- a/lorem-stream/requirements.txt +++ b/lorem-stream/requirements.txt @@ -1,2 +1,2 @@ -reflex>=0.2.8 +reflex>=0.2.9 lorem_text>=2.1 diff --git a/nba/requirements.txt b/nba/requirements.txt index d6145b5c..0e808d41 100644 --- a/nba/requirements.txt +++ b/nba/requirements.txt @@ -1,4 +1,4 @@ -reflex>=0.2.0 +reflex>=0.2.9 plotly-express plotly pandas diff --git a/qr-scanner/qr_scanner/component.py b/qr-scanner/qr_scanner/component.py index c1f95e0e..2e9cfd88 100644 --- a/qr-scanner/qr_scanner/component.py +++ b/qr-scanner/qr_scanner/component.py @@ -20,12 +20,15 @@ class QrScanner(NoSSRComponent): container_style: rx.Var[Dict[str, str]] video_style: rx.Var[Dict[str, str]] - def get_controlled_triggers(self) -> Dict[str, Var]: + def get_event_triggers(self) -> Dict[str, Var]: """Dict mapping (event -> expected arguments).""" - return super().get_controlled_triggers() | { - "on_result": rx.EVENT_ARG, - "on_decode": rx.EVENT_ARG, - "on_error": BaseVar(name=rx.EVENT_ARG.name + "?.message", type_=str), + + return { + **super().get_event_triggers(), + "on_result": lambda e0: [e0], + "on_decode": lambda e0: [e0], + "on_error": lambda e0: [BaseVar(name="_e0" + ".message")], } -qr_scanner = QrScanner.create \ No newline at end of file + +qr_scanner = QrScanner.create diff --git a/qr-scanner/qr_scanner/qr_scanner.py b/qr-scanner/qr_scanner/qr_scanner.py index 165222dd..545e60f0 100644 --- a/qr-scanner/qr_scanner/qr_scanner.py +++ b/qr-scanner/qr_scanner/qr_scanner.py @@ -73,4 +73,4 @@ def index() -> rx.Component: app = rx.App() app.add_page(index) -app.compile() \ No newline at end of file +app.compile() diff --git a/qr-scanner/requirements.txt b/qr-scanner/requirements.txt index 4e22d85e..825836d9 100644 --- a/qr-scanner/requirements.txt +++ b/qr-scanner/requirements.txt @@ -1 +1 @@ -reflex>=0.2.5 \ No newline at end of file +reflex>=0.2.9 \ No newline at end of file diff --git a/qr-scanner/rxconfig.py b/qr-scanner/rxconfig.py index 3e6a082f..2957d6f3 100644 --- a/qr-scanner/rxconfig.py +++ b/qr-scanner/rxconfig.py @@ -1,9 +1,10 @@ import reflex as rx + class QrscannerConfig(rx.Config): pass + config = QrscannerConfig( app_name="qr_scanner", - frontend_packages=["@yudiel/react-qr-scanner"], ) diff --git a/quiz/.gitignore b/quiz/.gitignore index bf56ff42..f6711845 100644 --- a/quiz/.gitignore +++ b/quiz/.gitignore @@ -1,3 +1,4 @@ +*.db *.py[cod] .web __pycache__/ diff --git a/quiz/quiz/quiz.py b/quiz/quiz/quiz.py index edf9b55e..de854b05 100644 --- a/quiz/quiz/quiz.py +++ b/quiz/quiz/quiz.py @@ -16,10 +16,12 @@ class State(rx.State): """The app state.""" + default_answers = [None, None, [False, False, False, False, False]] - answers:List[Any] + answers: List[Any] answer_key = ["False", "[10, 20, 30, 40]", [False, False, True, True, True]] score: int + def onload(self): self.answers = copy.deepcopy(self.default_answers) @@ -38,6 +40,10 @@ def submit(self): self.score = int(correct / total * 100) return rx.redirect("/result") + @rx.var + def percent_score(self): + return str(self.score) + "%" + def header(): return rx.vstack( @@ -63,7 +69,7 @@ def question1(): ["True", "False"], default_value=State.default_answers[0], default_checked=True, - on_change=lambda answer: State.set_answers(answer, 0) + on_change=lambda answer: State.set_answers(answer, 0), ), style=question_style, ) diff --git a/quiz/quiz/results.py b/quiz/quiz/results.py index 8a115a3b..98f999ab 100644 --- a/quiz/quiz/results.py +++ b/quiz/quiz/results.py @@ -33,7 +33,7 @@ def results(State): rx.divider(), rx.center( rx.circular_progress( - rx.circular_progress_label(State.score + "%"), + rx.circular_progress_label(State.percent_score), value=State.score, size="3em", ) @@ -49,7 +49,7 @@ def results(State): ), rx.foreach(State.answers, lambda answer, i: render_answer(State, i)), ), - rx.link(rx.button("Take Quiz Again"), href="/"), + rx.box(rx.link(rx.button("Take Quiz Again"), href="/")), bg="white", padding_x="5em", padding_y="2em", diff --git a/quiz/requirements.txt b/quiz/requirements.txt index bc6a7f2e..c8ade313 100644 --- a/quiz/requirements.txt +++ b/quiz/requirements.txt @@ -1 +1 @@ -reflex>=0.2.0 +reflex>=0.2.9 diff --git a/sales/.gitignore b/sales/.gitignore index bf56ff42..f6711845 100644 --- a/sales/.gitignore +++ b/sales/.gitignore @@ -1,3 +1,4 @@ +*.db *.py[cod] .web __pycache__/ diff --git a/sales/assets/favicon.ico b/sales/assets/favicon.ico index 8a93bfa72ad182e4a21385b6daac8106eb16158e..609f6abcbeb0fec5157b54a36d7b2aeb759f9e1b 100644 GIT binary patch literal 15086 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 CTt?CW diff --git a/sales/requirements.txt b/sales/requirements.txt index 743a150e..63a0fc6d 100644 --- a/sales/requirements.txt +++ b/sales/requirements.txt @@ -1,2 +1,2 @@ -reflex>=0.2.0 +reflex>=0.2.9 openai diff --git a/sales/sales/sales.py b/sales/sales/sales.py index 1cc03020..33f9d183 100644 --- a/sales/sales/sales.py +++ b/sales/sales/sales.py @@ -3,7 +3,45 @@ from .models import Customer -products = {'T-shirt': { 'description': 'A plain white t-shirt made of 100% cotton.', 'price': 10.99 }, 'Jeans': { 'description': 'A pair of blue denim jeans with a straight leg fit.', 'price': 24.99 }, 'Hoodie': { 'description': 'A black hoodie made of a cotton and polyester blend.', 'price': 34.99 }, 'Cardigan': { 'description': 'A grey cardigan with a V-neck and long sleeves.', 'price': 36.99 }, 'Joggers': { 'description': 'A pair of black joggers made of a cotton and polyester blend.', 'price': 44.99 }, 'Dress': { 'description': 'A black dress made of 100% polyester.', 'price': 49.99 }, 'Jacket': { 'description': 'A navy blue jacket made of 100% cotton.', 'price': 55.99 }, 'Skirt': { 'description': 'A brown skirt made of a cotton and polyester blend.', 'price': 29.99 }, 'Shorts': { 'description': 'A pair of black shorts made of a cotton and polyester blend.', 'price': 19.99 }, 'Sweater': { 'description': 'A white sweater with a crew neck and long sleeves.', 'price': 39.99}} +products = { + "T-shirt": { + "description": "A plain white t-shirt made of 100% cotton.", + "price": 10.99, + }, + "Jeans": { + "description": "A pair of blue denim jeans with a straight leg fit.", + "price": 24.99, + }, + "Hoodie": { + "description": "A black hoodie made of a cotton and polyester blend.", + "price": 34.99, + }, + "Cardigan": { + "description": "A grey cardigan with a V-neck and long sleeves.", + "price": 36.99, + }, + "Joggers": { + "description": "A pair of black joggers made of a cotton and polyester blend.", + "price": 44.99, + }, + "Dress": {"description": "A black dress made of 100% polyester.", "price": 49.99}, + "Jacket": { + "description": "A navy blue jacket made of 100% cotton.", + "price": 55.99, + }, + "Skirt": { + "description": "A brown skirt made of a cotton and polyester blend.", + "price": 29.99, + }, + "Shorts": { + "description": "A pair of black shorts made of a cotton and polyester blend.", + "price": 19.99, + }, + "Sweater": { + "description": "A white sweater with a crew neck and long sleeves.", + "price": 39.99, + }, +} class State(rx.State): @@ -43,7 +81,7 @@ def add_customer(self): def customer_page(self): """The customer page.""" return rx.redirect("/") - + def onboarding_page(self): """The onboarding page.""" return rx.redirect("/onboarding") @@ -54,15 +92,16 @@ def delete_customer(self, email: str): session.query(Customer).filter_by(email=email).delete() session.commit() - generate_email_data:dict={} + generate_email_data: dict = {} + async def call_openai(self): - name:str = self.generate_email_data["name"] - email:str = self.generate_email_data["email"] - age:int = self.generate_email_data["age"] - gender:str = self.generate_email_data["gender"] - location:str = self.generate_email_data["location"] - job:str = self.generate_email_data["job"] - salary:int = self.generate_email_data["salary"] + name: str = self.generate_email_data["name"] + email: str = self.generate_email_data["email"] + age: int = self.generate_email_data["age"] + gender: str = self.generate_email_data["gender"] + location: str = self.generate_email_data["location"] + job: str = self.generate_email_data["job"] + salary: int = self.generate_email_data["salary"] response = openai.Completion.create( model="text-davinci-003", prompt=f"Based on these {products} write a sales email to {name} adn email {email} who is {age} years old and a {gender} gender. {name} lives in {location} and works as a {job} and earns {salary} per year. Make sure the email reccomends one product only and is personalized to {name}. The company is named Reflex its website is https://reflex.dev", @@ -73,8 +112,12 @@ async def call_openai(self): presence_penalty=0, ) self.gen_response = False - self.email_content_data = response.choices[0].text # save the data related to email_content - return rx.set_value("email_content", self.email_content_data) # update layout of email_content manually + self.email_content_data = response.choices[ + 0 + ].text # save the data related to email_content + return rx.set_value( + "email_content", self.email_content_data + ) # update layout of email_content manually def generate_email( self, @@ -97,31 +140,30 @@ def generate_email( self.gen_response = True return State.call_openai - - @rx.var def get_users(self) -> list[Customer]: """Get all users from the database.""" with rx.session() as session: self.users = session.query(Customer).all() return self.users + def open_text_area(self): self.text_area_disabled = False + def close_text_area(self): self.text_area_disabled = True - def navbar(): """The navbar for the top of the page.""" return rx.box( rx.hstack( rx.link( rx.hstack( - rx.image(src="favicon.ico", width="50px"), + rx.image(src="/favicon.ico", width="50px"), rx.heading("Reflex | Personalized Sales", size="lg"), ), - href="https://reflex.dev", + href="/", ), rx.menu( rx.menu_button( @@ -130,9 +172,7 @@ def navbar(): rx.menu_list( rx.link( rx.menu_item( - rx.hstack( - rx.text("Customers"), rx.icon(tag="hamburger") - ) + rx.hstack(rx.text("Customers"), rx.icon(tag="hamburger")) ), href="/", ), @@ -186,9 +226,8 @@ def show_customer(user: Customer): user.gender, user.location, user.job, - user.salary, + user.salary, ), - bg="blue", color="white", ) @@ -243,7 +282,12 @@ def index(): rx.vstack( rx.hstack( rx.heading("Customers"), - rx.button(rx.icon(tag="add"), on_click=State.onboarding_page, bg="#F7FAFC", border="1px solid #ddd"), + rx.button( + rx.icon(tag="add"), + on_click=State.onboarding_page, + bg="#F7FAFC", + border="1px solid #ddd", + ), ), rx.table_container( rx.table( diff --git a/snakegame/requirements.txt b/snakegame/requirements.txt index 210218f4..825836d9 100644 --- a/snakegame/requirements.txt +++ b/snakegame/requirements.txt @@ -1 +1 @@ -reflex>=0.2.0 \ No newline at end of file +reflex>=0.2.9 \ No newline at end of file diff --git a/tailwind/requirements.txt b/tailwind/requirements.txt index bc6a7f2e..c8ade313 100644 --- a/tailwind/requirements.txt +++ b/tailwind/requirements.txt @@ -1 +1 @@ -reflex>=0.2.0 +reflex>=0.2.9 diff --git a/todo/requirements.txt b/todo/requirements.txt index bc6a7f2e..c8ade313 100644 --- a/todo/requirements.txt +++ b/todo/requirements.txt @@ -1 +1 @@ -reflex>=0.2.0 +reflex>=0.2.9 diff --git a/translator/requirements.txt b/translator/requirements.txt index cea8d7e2..ec450bbd 100644 --- a/translator/requirements.txt +++ b/translator/requirements.txt @@ -1,3 +1,3 @@ -reflex>=0.2.0 +reflex>=0.2.9 googletrans-py==4.0.0 requests>=2.28.1 diff --git a/traversal/requirements.txt b/traversal/requirements.txt index 210218f4..825836d9 100644 --- a/traversal/requirements.txt +++ b/traversal/requirements.txt @@ -1 +1 @@ -reflex>=0.2.0 \ No newline at end of file +reflex>=0.2.9 \ No newline at end of file diff --git a/traversal/traversal/traversal.py b/traversal/traversal/traversal.py index 06881bd1..870c6dc5 100644 --- a/traversal/traversal/traversal.py +++ b/traversal/traversal/traversal.py @@ -52,10 +52,9 @@ def new_graph(self): def run(self): """Run the selected algorithm.""" if self.option == "DFS": - return self.run_dfs + return State.run_dfs elif self.option == "BFS": - return self.run_bfs - + return State.run_bfs async def run_dfs(self): """DFS algorithm on a 1d array.""" @@ -93,7 +92,7 @@ async def run_dfs(self): and colors[i2][j2] != "blue" ): self.s.append((i2, j2)) - return self.run_dfs + return State.run_dfs return rx.window_alert("No path found") @@ -140,7 +139,7 @@ async def run_bfs(self): ): q.append((i2, j2)) self.q.append((i2, j2)) - return self.run_bfs + return State.run_bfs return rx.window_alert("No path found") From a94995772fdb28260e42bfb2b91c3668f975324d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Brand=C3=A9ho?= Date: Tue, 10 Oct 2023 00:14:52 +0200 Subject: [PATCH 09/13] Update quiz/quiz/quiz.py Co-authored-by: Masen Furer --- quiz/quiz/quiz.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quiz/quiz/quiz.py b/quiz/quiz/quiz.py index de854b05..429db03e 100644 --- a/quiz/quiz/quiz.py +++ b/quiz/quiz/quiz.py @@ -42,7 +42,7 @@ def submit(self): @rx.var def percent_score(self): - return str(self.score) + "%" + return f"{self.score}%" def header(): From 401f3063cc8e99e27fc4ddaae6644eda328c8401 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Brand=C3=A9ho?= Date: Tue, 10 Oct 2023 00:15:43 +0200 Subject: [PATCH 10/13] Update qr-scanner/qr_scanner/component.py Co-authored-by: Masen Furer --- qr-scanner/qr_scanner/component.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qr-scanner/qr_scanner/component.py b/qr-scanner/qr_scanner/component.py index 2e9cfd88..04ca0546 100644 --- a/qr-scanner/qr_scanner/component.py +++ b/qr-scanner/qr_scanner/component.py @@ -27,7 +27,7 @@ def get_event_triggers(self) -> Dict[str, Var]: **super().get_event_triggers(), "on_result": lambda e0: [e0], "on_decode": lambda e0: [e0], - "on_error": lambda e0: [BaseVar(name="_e0" + ".message")], + "on_error": lambda e0: [BaseVar(name="_e0?.message")], } From 908fed79c2e82b1e3a6b3558cdfa01777eb1c44e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Brand=C3=A9ho?= Date: Tue, 10 Oct 2023 00:16:16 +0200 Subject: [PATCH 11/13] Update sales/sales/sales.py Co-authored-by: Masen Furer --- sales/sales/sales.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/sales/sales/sales.py b/sales/sales/sales.py index 33f9d183..dec903f0 100644 --- a/sales/sales/sales.py +++ b/sales/sales/sales.py @@ -112,12 +112,10 @@ async def call_openai(self): presence_penalty=0, ) self.gen_response = False - self.email_content_data = response.choices[ - 0 - ].text # save the data related to email_content - return rx.set_value( - "email_content", self.email_content_data - ) # update layout of email_content manually + # save the data related to email_content + self.email_content_data = response.choices[0].text + # update layout of email_content manually + return rx.set_value("email_content", self.email_content_data) def generate_email( self, From c0ee5b53e5def96dcae1d45c0c2c5e23364d49d5 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Tue, 10 Oct 2023 11:52:25 -0700 Subject: [PATCH 12/13] Update snakegame/requirements.txt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Thomas Brandého --- snakegame/requirements.txt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/snakegame/requirements.txt b/snakegame/requirements.txt index e303bd41..c8ade313 100644 --- a/snakegame/requirements.txt +++ b/snakegame/requirements.txt @@ -1,3 +1 @@ -#reflex>=0.2.9 -# temporary until state.reset() bug is fixed -git+https://github.com/reflex-dev/reflex@masenf/state-reset-deepcopy +reflex>=0.2.9 From 0bcd28f191f8dcc649cc0d00280c9b56a9bf80ae Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Wed, 11 Oct 2023 10:42:18 -0700 Subject: [PATCH 13/13] clock.py: handle zone cookie not set If the zone cookie is blank or invalid return the default zone --- clock/clock/clock.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/clock/clock/clock.py b/clock/clock/clock.py index 2ddfc43d..cb05c752 100644 --- a/clock/clock/clock.py +++ b/clock/clock/clock.py @@ -18,6 +18,7 @@ "US/Pacific", "US/Eastern", ] +DEFAULT_ZONE = TIMEZONES[-2] def rotate(degrees: int) -> str: @@ -36,13 +37,26 @@ class State(rx.State): """The app state.""" # The time zone to display the clock in. - zone: str = rx.Cookie("US/Pacific") + zone: str = rx.Cookie(DEFAULT_ZONE) # Whether the clock is running. running: bool = False # The last updated timestamp - _now: datetime = datetime.now(timezone.utc) + _now: datetime = datetime.fromtimestamp(0) + + @rx.cached_var + def valid_zone(self) -> str: + """Get the current time zone. + + Returns: + The current time zone. + """ + try: + pytz.timezone(self.zone) + except Exception: + return DEFAULT_ZONE + return self.zone @rx.cached_var def time_info(self) -> dict[str, Any]: @@ -53,7 +67,7 @@ def time_info(self) -> dict[str, Any]: Returns: A dictionary of the current time info. """ - now = self._now.astimezone(pytz.timezone(self.zone)) + now = self._now.astimezone(pytz.timezone(self.valid_zone)) return { "hour": now.hour if now.hour <= 12 else now.hour % 12, "minute": now.minute, @@ -169,7 +183,7 @@ def timezone_select() -> rx.Component: TIMEZONES, placeholder="Select a time zone.", on_change=State.set_zone, - value=State.zone, + value=State.valid_zone, bg="#white", )