From 3ef05264f7ae80f696e0f2207a792ee2dd7b85d7 Mon Sep 17 00:00:00 2001 From: NikolajDanger Date: Fri, 12 Dec 2025 23:04:05 +0100 Subject: [PATCH] :sparkles: --- data.json | 248 ++++++------- data_test.json | 52 +-- main.py | 971 ++++++++++++++++++++++++------------------------- 3 files changed, 635 insertions(+), 636 deletions(-) diff --git a/data.json b/data.json index 030547e..64e72ba 100644 --- a/data.json +++ b/data.json @@ -1,135 +1,135 @@ { - "words": [ - ["Adrian",0], - ["Andromedes",0], - ["Argyn",0], - ["Aslak",0], - ["Butterswick",0], - ["Celiestra",0], - ["Cynthia",0], - ["Darkwood",0], - ["Edgar",0], - ["Eggert",0], - ["Enkelton",0], - ["Galinndan",0], - ["Goldenleaf",0], - ["Gyro",0], - ["Half-ear",0], - ["Marigold",0], - ["McGalloway",0], - ["Moklus",0], - ["Nix",0], - ["Nuts",0], - ["Paterius",0], - ["Sagemonger",0], - ["Sam",0], - ["Silvan",0], - ["Silvertongue",0], - ["Tymna",0], - ["Valeria",0], - ["Valindis",0], - ["Virtus",0], - ["Volynthia",0], + "words": [ + ["Adrian",0], + ["Andromedes",0], + ["Argyn",0], + ["Aslak",0], + ["Butterswick",0], + ["Celiestra",0], + ["Cynthia",0], + ["Darkwood",0], + ["Edgar",0], + ["Eggert",0], + ["Enkelton",0], + ["Galinndan",0], + ["Goldenleaf",0], + ["Gyro",0], + ["Half-ear",0], + ["Marigold",0], + ["McGalloway",0], + ["Moklus",0], + ["Nix",0], + ["Nuts",0], + ["Paterius",0], + ["Sagemonger",0], + ["Sam",0], + ["Silvan",0], + ["Silvertongue",0], + ["Tymna",0], + ["Valeria",0], + ["Valindis",0], + ["Virtus",0], + ["Volynthia",0], - ["bandit", 1], - ["changeling", 1], - ["diplomat", 1], - ["dragon", 1], - ["dwarf", 1], - ["elf", 1], - ["fight", 1], - ["giant", 1], - ["guard", 1], - ["human", 1], - ["inn", 1], - ["orc", 1], - ["pegasus", 1], - ["power", 1], - ["rose", 1], - ["satyr", 1], - ["sewers", 1], - ["soup", 1], - ["tower", 1], - ["trainee", 1], - ["war", 1], + ["bandit", 1], + ["changeling", 1], + ["diplomat", 1], + ["dragon", 1], + ["dwarf", 1], + ["elf", 1], + ["fight", 1], + ["giant", 1], + ["guard", 1], + ["human", 1], + ["inn", 1], + ["orc", 1], + ["pegasus", 1], + ["power", 1], + ["rose", 1], + ["satyr", 1], + ["sewers", 1], + ["soup", 1], + ["tower", 1], + ["trainee", 1], + ["war", 1], - ["black",2], - ["blue",2], - ["green",2], - ["purple",2], - ["red",2], + ["black",2], + ["blue",2], + ["green",2], + ["purple",2], + ["red",2], - ["attack",3], - ["capture",3], - ["deserted",3], - ["disguised",3], - ["eating",3], - ["escape",3], - ["gathering",3], - ["kidnapped",3], - ["kill",3], - ["save",3], - ["terrorizing",3], - ["trick",3], - ["flying",3], + ["attack",3], + ["capture",3], + ["deserted",3], + ["disguised",3], + ["eating",3], + ["escape",3], + ["gathering",3], + ["kidnapped",3], + ["kill",3], + ["save",3], + ["terrorizing",3], + ["trick",3], + ["flying",3], - ["half",4], - ["high",4], + ["half",4], + ["high",4], - ["Dragonsong",5], - ["Giant's Heart",5], - ["Hammerhome",5], - ["The Empire",5], - ["The Feywilds",5], - ["The Westerlands",5], - ["Volantis",5], + ["Dragonsong",5], + ["Giant's Heart",5], + ["Hammerhome",5], + ["The Empire",5], + ["The Feywilds",5], + ["The Westerlands",5], + ["Volantis",5], - ["Lady", 6], - ["Lord", 6], + ["Lady", 6], + ["Lord", 6], - ["artificer",7], - ["barbarian",7], - ["bard",7], - ["cleric",7], - ["druid",7], - ["fighter",7], - ["monk",7], - ["paladin",7], - ["ranger",7], - ["rogue",7], - ["sorcerer",7], - ["warlock",7], - ["wizard",7] + ["artificer",7], + ["barbarian",7], + ["bard",7], + ["cleric",7], + ["druid",7], + ["fighter",7], + ["monk",7], + ["paladin",7], + ["ranger",7], + ["rogue",7], + ["sorcerer",7], + ["warlock",7], + ["wizard",7] + ], + "pages": [ + [ + "The Party (Before Session)", + "- [Adrian/0+6] [Goldenleaf/0], a [high/4] [elf/1] [rogue/7] from [Volantis/5] and owner of the [Goldenleaf/0] [inn/1].", + "- [Andromedes/0+6] [Moklus+Nuts/0], a [dwarf/1] [artificer/7] from [Hammerhome/5] and [diplomat/1] in [Dragonsong/5].", + "- [Aslak/0], an [orc/1] [barbarian/7]/[monk/7] from [The%Westerlands/5] and [trainee/1] of [Nix/0+6] [McGalloway/0].", + "- [Marigold/0+6] [Sagemonger/0], a [high/4] [elf/1] [wizard/7] from [The%Empire/5] and owner of [Sagemonger/0]'s.", + "- [Nix/0+6] [McGalloway/0], a [changeling/1] [monk/7] from [The%Westerlands/5] and owner of the [flying/3] [pegasus/1].", + "- [Sam/0], a [satyr/1] [cleric/7] from [The%Feywilds/5].", + "- [Silvan/0], a [human/1] [fighter/7] from [The%Empire/5] and formerly a [trainee/1] of [Paterius/0+6] [Enkelton/0]." ], - "pages": [ - [ - "The Party (Before Session)", - "- [Adrian/0+6] [Goldenleaf/0], a [high/4] [elf/1] [rogue/7] from [Volantis/5] and owner of the [Goldenleaf/0] [inn/1].", - "- [Andromedes/0+6] [Moklus+Nuts/0], a [dwarf/1] [artificer/7] from [Hammerhome/5] and [diplomat/1] in [Dragonsong/5].", - "- [Aslak/0], an [orc/1] [barbarian/7]/[monk/7] from [The%Westerlands/5] and [trainee/1] of [Nix/0+6] [McGalloway/0].", - "- [Marigold/0+6] [Sagemonger/0], a [high/4] [elf/1] [wizard/7] from [The%Empire/5] and owner of [Sagemonger/0]'s.", - "- [Nix/0+6] [McGalloway/0], a [changeling/1] [monk/7] from [The%Westerlands/5] and owner of the [flying/3] [pegasus/1].", - "- [Sam/0], a [satyr/1] [cleric/7] from [The%Feywilds/5].", - "- [Silvan/0], a [human/1] [fighter/7] from [The%Empire/5] and formerly a [trainee/1] of [Paterius/0+6] [Enkelton/0]." - ], - [ - "Present Threats", - "- [Lord+Edgar/0+6] [Darkwood/0] is [gathering/3] [power/1] in his [tower/1], far away from civilization.", - "- [Volynthia/0], a [green/2] [dragon/1], is trying to [escape/3] from [The%Feywilds/5], helped by a group of [orc/1]s.", - "- A [war/1] is raging between [Lady+Valeria/0+6] [Argyn/0] and the [giant/1]s, for control of [Giant's%Heart/5]." - ], - [ - "Before the Session", - "The [purple/2] [rose/1], also known as the [soup/1] [bandit/1]s, have been [terrorizing/3] the citizens of [Dragonsong/5] by [eating/3] all of their [soup/1]. They also [kidnapped/3] [Galinndan/0], friend of [Adrian/0+6] [Goldenleaf/0] and chef of the [Goldenleaf/0] [inn/1]. They are lead by [Valindis/0+6] [Half-ear/0] and are headquartered in the [Dragonsong/5] [sewers/1]." - ], - [ - "Into the Dark", - "The party head into the [Dragonsong/5] [sewers/1] to [attack/3] the [purple/2] [rose/1] and [save/3] [Galinndan/0]. They meet and join up with [Cynthia/0], a [human/1] [rogue/7] who has [deserted/3] the [purple/2] [rose/1]. They then [kill/3] the 2 [guard/1]s outside the door." - ], - [ - "The Plan", - "The party [trick/3] [Valindis/0+6] [Half-ear/0] by having [Cynthia/0] \"[capture/3]\" [Nix/0+6] [McGalloway/0] [disguised/3] as [Adrian/0+6] [Goldenleaf/0].", - "A [fight/1] starts, and they [kill/3] every member of the [purple/2] [rose/1]." - ] + [ + "Present Threats", + "- [Lord+Edgar/0+6] [Darkwood/0] is [gathering/3] [power/1] in his [tower/1], far away from civilization.", + "- [Volynthia/0], a [green/2] [dragon/1], is trying to [escape/3] from [The%Feywilds/5], helped by a group of [orc/1]s.", + "- A [war/1] is raging between [Lady+Valeria/0+6] [Argyn/0] and the [giant/1]s, for control of [Giant's%Heart/5]." + ], + [ + "Before the Session", + "The [purple/2] [rose/1], also known as the [soup/1] [bandit/1]s, have been [terrorizing/3] the citizens of [Dragonsong/5] by [eating/3] all of their [soup/1]. They also [kidnapped/3] [Galinndan/0], friend of [Adrian/0+6] [Goldenleaf/0] and chef of the [Goldenleaf/0] [inn/1]. They are lead by [Valindis/0+6] [Half-ear/0] and are headquartered in the [Dragonsong/5] [sewers/1]." + ], + [ + "Into the Dark", + "The party head into the [Dragonsong/5] [sewers/1] to [attack/3] the [purple/2] [rose/1] and [save/3] [Galinndan/0]. They meet and join up with [Cynthia/0], a [human/1] [rogue/7] who has [deserted/3] the [purple/2] [rose/1]. They then [kill/3] the 2 [guard/1]s outside the door." + ], + [ + "The Plan", + "The party [trick/3] [Valindis/0+6] [Half-ear/0] by having [Cynthia/0] \"[capture/3]\" [Nix/0+6] [McGalloway/0] [disguised/3] as [Adrian/0+6] [Goldenleaf/0].", + "A [fight/1] starts, and they [kill/3] every member of the [purple/2] [rose/1]." ] + ] } \ No newline at end of file diff --git a/data_test.json b/data_test.json index cbda3e2..7b56a92 100644 --- a/data_test.json +++ b/data_test.json @@ -1,31 +1,31 @@ { - "words": [ - ["Ansem",0], - ["Mickey",0], - ["Mouse",0], - ["Terra",0], - ["Terranort",0], - ["Xehanort",0], - ["Xemnas",0], + "words": [ + ["Ansem",0], + ["Mickey",0], + ["Mouse",0], + ["Terra",0], + ["Terranort",0], + ["Xehanort",0], + ["Xemnas",0], - ["apprentice",1], - ["darkness",1], - ["door",1], - ["heart",1], - ["nobody",1], - ["seeker",1], - ["shirt",1], + ["apprentice",1], + ["darkness",1], + ["door",1], + ["heart",1], + ["nobody",1], + ["seeker",1], + ["shirt",1], - ["killed",3], - ["possessed",3], - ["ripped",3], + ["killed",3], + ["possessed",3], + ["ripped",3], - ["less",4], - ["off",4], - ["wise",4] - ], - "pages": [ - "[Xehanort/0] [possessed/3] [Terra/0], which created [Terra/0]-[Xehanort/0], also known as [Terranort/0]. He became an [apprentice/1] to [Ansem/0] the [wise/4], and eventually split into the [nobody/1] [Xemnas/0] and the [heart/1][less/4] [Ansem/0], [seeker/1] of [darkness/1].", - "[Mickey/0] [Mouse/0] was [shirt/1][less/4] at the end of Kingdom Hearts because his [shirt/1] had been [ripped/3] [off/4] by [heart/1][less/4] behind the [door/1] to [darkness/1]." - ] + ["less",4], + ["off",4], + ["wise",4] + ], + "pages": [ + "[Xehanort/0] [possessed/3] [Terra/0], which created [Terra/0]-[Xehanort/0], also known as [Terranort/0]. He became an [apprentice/1] to [Ansem/0] the [wise/4], and eventually split into the [nobody/1] [Xemnas/0] and the [heart/1][less/4] [Ansem/0], [seeker/1] of [darkness/1].", + "[Mickey/0] [Mouse/0] was [shirt/1][less/4] at the end of Kingdom Hearts because his [shirt/1] had been [ripped/3] [off/4] by [heart/1][less/4] behind the [door/1] to [darkness/1]." + ] } \ No newline at end of file diff --git a/main.py b/main.py index e9a32e2..4259115 100644 --- a/main.py +++ b/main.py @@ -65,566 +65,565 @@ INDICATOR_MARGIN = 60 INDICATOR_POS = Vector2(TEXT_PAGE_WIDTH-(INDICATOR_SIZE+INDICATOR_MARGIN),TEXT_PAGE_HEIGHT-(INDICATOR_SIZE+INDICATOR_MARGIN)) class WordColor(Enum): - Red = 0 # Names - Green = 1 # Nouns - Lime = 2 # Numbers and such - Blue = 3 # Verbs - Grey = 4 # Other - Purple = 5 # Places - Yellow = 6 # Titles - Orange = 7 # Classes + Red = 0 # Names + Green = 1 # Nouns + Lime = 2 # Numbers and such + Blue = 3 # Verbs + Grey = 4 # Other + Purple = 5 # Places + Yellow = 6 # Titles + Orange = 7 # Classes - def color(self): - return [ - "#b03334", - "#6a5f31", - "#62a032", - "#297cb7", - "#767978", - "#623a75", - "#bda627", - "#b76c2d", - ][self.value] + def color(self): + return [ + "#b03334", + "#6a5f31", + "#62a032", + "#297cb7", + "#767978", + "#623a75", + "#bda627", + "#b76c2d", + ][self.value] class SolvedState(Enum): - NotFinished = 0 - Wrong = 1 - AlmostCorrect = 2 - Correct = 3 + NotFinished = 0 + Wrong = 1 + AlmostCorrect = 2 + Correct = 3 @dataclass class Page(): - visible: bool - text: None|str - position: Vector2 - size: Vector2 - slots: list[tuple[list[str],WordSlot]] + visible: bool + text: None|str + position: Vector2 + size: Vector2 + slots: list[tuple[list[str],WordSlot]] - def solved(self): - # print([s for s in self.slots]) + def solved(self): + # print([s for s in self.slots]) - if any([s[1].word is None for s in self.slots]): - return SolvedState.NotFinished - elif all([s[1].word.word in s[0] for s in self.slots]): - return SolvedState.Correct - elif sum([s[1].word.word in s[0] for s in self.slots]) >= len(self.slots)-2: - return SolvedState.AlmostCorrect - else: - return SolvedState.Wrong + if any([s[1].word is None for s in self.slots]): + return SolvedState.NotFinished + elif all([s[1].word.word in s[0] for s in self.slots]): + return SolvedState.Correct + elif sum([s[1].word.word in s[0] for s in self.slots]) >= len(self.slots)-2: + return SolvedState.AlmostCorrect + else: + return SolvedState.Wrong - def solve(self): - for answers, slot in self.slots: - slot.word = Word(answers[0],slot.colors[0],self,slot.position,slot) + def solve(self): + for answers, slot in self.slots: + slot.word = Word(answers[0],slot.colors[0],self,slot.position,slot) @dataclass class Word(): - word: str - color: WordColor - page: None|Page - position: Vector2 - slot: None|WordSlot = None + word: str + color: WordColor + page: None|Page + position: Vector2 + slot: None|WordSlot = None - def copy(self): - return Word(self.word,self.color,self.page,self.position,self.slot) + def copy(self): + return Word(self.word,self.color,self.page,self.position,self.slot) @dataclass class WordSlot(): - colors: None|list[WordColor] - page: Page - position: Vector2 - word: None|Word = None + colors: None|list[WordColor] + page: Page + position: Vector2 + word: None|Word = None def draw_page(page: Page, screen: pygame.surface.Surface, font: pygame.font.Font): - pygame.draw.rect( - screen, - PAGE_BACKGROUND_COLOR, - pygame.Rect( - page.position.x + PAGE_BORDER_WIDTH, - page.position.y + PAGE_BORDER_WIDTH, - page.size.x - PAGE_BORDER_WIDTH*2, - page.size.y - PAGE_BORDER_WIDTH*2 - ), - border_radius=PAGE_BORDER_RADIUS//2 + pygame.draw.rect( + screen, + PAGE_BACKGROUND_COLOR, + pygame.Rect( + page.position.x + PAGE_BORDER_WIDTH, + page.position.y + PAGE_BORDER_WIDTH, + page.size.x - PAGE_BORDER_WIDTH*2, + page.size.y - PAGE_BORDER_WIDTH*2 + ), + border_radius=PAGE_BORDER_RADIUS//2 + ) + + pygame.draw.rect( + screen, + PAGE_BORDER_COLOR, + pygame.Rect(page.position.x,page.position.y,page.size.x,page.size.y), + PAGE_BORDER_WIDTH, PAGE_BORDER_RADIUS + ) + + if page.text is not None: + for i, t in enumerate(page.text): + text = font.render(t,False,"#000000") + pos = Vector2( + page.position.x + PAGE_BORDER_WIDTH + TEXT_MARGIN, + page.position.y + PAGE_BORDER_WIDTH + TEXT_MARGIN + TEXT_TOP_MARGIN + i*(WORD_FONT_SIZE+TEXT_TOP_MARGIN) + ) + if i == 0: + width,_ = text.get_size() + text = pygame.transform.scale2x(text) + pos.y = page.position.y + PAGE_BORDER_WIDTH + TEXT_MARGIN + pos.x = page.position.x + PAGE_BORDER_WIDTH + TEXT_MARGIN + (TEXT_WIDTH-width*2)//2 + screen.blit(text,pos) + + if page.slots != []: + pygame.draw.circle( + screen, + "#000000", + page.position + INDICATOR_POS, + INDICATOR_SIZE ) + status = page.solved() + if status == SolvedState.Correct: + color = WordColor.Lime.color() + elif status == SolvedState.AlmostCorrect: + color = WordColor.Yellow.color() + elif status == SolvedState.Wrong: + color = WordColor.Red.color() + else: + color = "#000000" - pygame.draw.rect( - screen, - PAGE_BORDER_COLOR, - pygame.Rect(page.position.x,page.position.y,page.size.x,page.size.y), - PAGE_BORDER_WIDTH, PAGE_BORDER_RADIUS + pygame.draw.circle( + screen, + color, + page.position + INDICATOR_POS, + INDICATOR_SIZE-INDICATOR_BORDER ) - if page.text is not None: - for i, t in enumerate(page.text): - text = font.render(t,False,"#000000") - pos = Vector2( - page.position.x + PAGE_BORDER_WIDTH + TEXT_MARGIN, - page.position.y + PAGE_BORDER_WIDTH + TEXT_MARGIN + TEXT_TOP_MARGIN + i*(WORD_FONT_SIZE+TEXT_TOP_MARGIN) - ) - if i == 0: - width,_ = text.get_size() - text = pygame.transform.scale2x(text) - pos.y = page.position.y + PAGE_BORDER_WIDTH + TEXT_MARGIN - pos.x = page.position.x + PAGE_BORDER_WIDTH + TEXT_MARGIN + (TEXT_WIDTH-width*2)//2 - screen.blit(text,pos) - - if page.slots != []: - pygame.draw.circle( - screen, - "#000000", - page.position + INDICATOR_POS, - INDICATOR_SIZE - ) - status = page.solved() - if status == SolvedState.Correct: - color = WordColor.Lime.color() - elif status == SolvedState.AlmostCorrect: - color = WordColor.Yellow.color() - elif status == SolvedState.Wrong: - color = WordColor.Red.color() - else: - color = "#000000" - - pygame.draw.circle( - screen, - color, - page.position + INDICATOR_POS, - INDICATOR_SIZE-INDICATOR_BORDER - ) - def play_sound(page: Page): - click = pygame.mixer.Sound("click.wav") - click.set_volume(0.8) - click.play() + click = pygame.mixer.Sound("click.wav") + click.set_volume(0.8) + click.play() - if page.slots != []: - status = page.solved() - if status == SolvedState.Correct: - sound = pygame.mixer.Sound("correct.wav") - elif status == SolvedState.AlmostCorrect: - sound = pygame.mixer.Sound("incorrect.wav") - elif status == SolvedState.Wrong: - sound = pygame.mixer.Sound("incorrect.wav") - else: - return + if page.slots != []: + status = page.solved() + if status == SolvedState.Correct: + sound = pygame.mixer.Sound("correct.wav") + elif status == SolvedState.AlmostCorrect: + sound = pygame.mixer.Sound("incorrect.wav") + elif status == SolvedState.Wrong: + sound = pygame.mixer.Sound("incorrect.wav") + else: + return - sound.play() + sound.play() def draw_word_slot(word_slot: WordSlot, screen: pygame.surface.Surface): - pos = word_slot.page.position+word_slot.position + pos = word_slot.page.position+word_slot.position - if word_slot.colors is None: - color = "#00000000" - else: - color = word_slot.colors[0].color() + if word_slot.colors is None: + color = "#00000000" + else: + color = word_slot.colors[0].color() + pygame.draw.rect( + screen, + color, + pygame.Rect( + pos.x + WORD_BORDER_WIDTH, + pos.y + WORD_BORDER_WIDTH, + WORD_WIDTH - WORD_BORDER_WIDTH*2, + WORD_HEIGHT - WORD_BORDER_WIDTH*2 + ), + border_radius=WORD_BORDER_RADIUS + ) + + if len(word_slot.colors) > 1: pygame.draw.rect( - screen, - color, - pygame.Rect( - pos.x + WORD_BORDER_WIDTH, - pos.y + WORD_BORDER_WIDTH, - WORD_WIDTH - WORD_BORDER_WIDTH*2, - WORD_HEIGHT - WORD_BORDER_WIDTH*2 - ), - border_radius=WORD_BORDER_RADIUS + screen, + word_slot.colors[1].color(), + pygame.Rect( + pos.x + WORD_BORDER_WIDTH + WORD_WIDTH//4, + pos.y + WORD_BORDER_WIDTH, + (WORD_WIDTH*3)//4 - WORD_BORDER_WIDTH*2, + WORD_HEIGHT - WORD_BORDER_WIDTH*2 + ), + border_radius=WORD_BORDER_RADIUS ) - if len(word_slot.colors) > 1: - pygame.draw.rect( - screen, - word_slot.colors[1].color(), - pygame.Rect( - pos.x + WORD_BORDER_WIDTH + WORD_WIDTH//4, - pos.y + WORD_BORDER_WIDTH, - (WORD_WIDTH*3)//4 - WORD_BORDER_WIDTH*2, - WORD_HEIGHT - WORD_BORDER_WIDTH*2 - ), - border_radius=WORD_BORDER_RADIUS - ) - - pygame.draw.polygon( - screen, - color, - [ - ( - pos.x + WORD_BORDER_WIDTH + WORD_WIDTH//4, - pos.y + WORD_BORDER_WIDTH - ), - ( - pos.x + WORD_BORDER_WIDTH + WORD_WIDTH//2 + SLANT//2, - pos.y + WORD_BORDER_WIDTH - ), - ( - pos.x + WORD_BORDER_WIDTH + WORD_WIDTH//2 - SLANT//2, - pos.y + WORD_BORDER_WIDTH + WORD_HEIGHT - WORD_BORDER_WIDTH*2 - 1 - ), - ( - pos.x + WORD_BORDER_WIDTH + WORD_WIDTH//4, - pos.y + WORD_BORDER_WIDTH + WORD_HEIGHT - WORD_BORDER_WIDTH*2 - 1 - ), - ] - ) + pygame.draw.polygon( + screen, + color, + [ + ( + pos.x + WORD_BORDER_WIDTH + WORD_WIDTH//4, + pos.y + WORD_BORDER_WIDTH + ), + ( + pos.x + WORD_BORDER_WIDTH + WORD_WIDTH//2 + SLANT//2, + pos.y + WORD_BORDER_WIDTH + ), + ( + pos.x + WORD_BORDER_WIDTH + WORD_WIDTH//2 - SLANT//2, + pos.y + WORD_BORDER_WIDTH + WORD_HEIGHT - WORD_BORDER_WIDTH*2 - 1 + ), + ( + pos.x + WORD_BORDER_WIDTH + WORD_WIDTH//4, + pos.y + WORD_BORDER_WIDTH + WORD_HEIGHT - WORD_BORDER_WIDTH*2 - 1 + ), + ] + ) def draw_word(word: Word, screen: pygame.surface.Surface, font: pygame.font.Font): - if word.page is not None: - pos = word.page.position+word.position - else: - pos = word.position + if word.page is not None: + pos = word.page.position+word.position + else: + pos = word.position - pygame.draw.rect( - screen, - WORD_SHADOW_COLOR, - pygame.Rect( - pos.x, - pos.y + WORD_SHADOW_SIZE, - WORD_WIDTH, - WORD_HEIGHT - ), - border_radius=WORD_BORDER_RADIUS - ) + pygame.draw.rect( + screen, + WORD_SHADOW_COLOR, + pygame.Rect( + pos.x, + pos.y + WORD_SHADOW_SIZE, + WORD_WIDTH, + WORD_HEIGHT + ), + border_radius=WORD_BORDER_RADIUS + ) - pygame.draw.rect( - screen, - word.color.color(), - pygame.Rect( - pos.x + WORD_BORDER_WIDTH//2, - pos.y + WORD_BORDER_WIDTH//2, - WORD_WIDTH - WORD_BORDER_WIDTH, - WORD_HEIGHT - WORD_BORDER_WIDTH - ), - border_radius=WORD_BORDER_RADIUS - ) + pygame.draw.rect( + screen, + word.color.color(), + pygame.Rect( + pos.x + WORD_BORDER_WIDTH//2, + pos.y + WORD_BORDER_WIDTH//2, + WORD_WIDTH - WORD_BORDER_WIDTH, + WORD_HEIGHT - WORD_BORDER_WIDTH + ), + border_radius=WORD_BORDER_RADIUS + ) - pygame.draw.rect( - screen, - WORD_BORDER_COLOR, - pygame.Rect( - pos.x, - pos.y, - WORD_WIDTH,WORD_HEIGHT - ), - WORD_BORDER_WIDTH, WORD_BORDER_RADIUS - ) + pygame.draw.rect( + screen, + WORD_BORDER_COLOR, + pygame.Rect( + pos.x, + pos.y, + WORD_WIDTH,WORD_HEIGHT + ), + WORD_BORDER_WIDTH, WORD_BORDER_RADIUS + ) - text = font.render(word.word,False,"#ffffff") - x,y = text.get_size() - screen.blit(text,( - pos.x+(WORD_WIDTH-x)//2, - pos.y+(WORD_HEIGHT-y)//2+1 - )) + text = font.render(word.word,False,"#ffffff") + x,y = text.get_size() + screen.blit(text,( + pos.x+(WORD_WIDTH-x)//2, + pos.y+(WORD_HEIGHT-y)//2+1 + )) def draw_tab(n: int, screen: pygame.surface.Surface, font: pygame.font.Font, selected: bool): - pos = TAB_POS + Vector2((TAB_WIDTH+TAB_MARGIN)*n,0) + pos = TAB_POS + Vector2((TAB_WIDTH+TAB_MARGIN)*n,0) - pygame.draw.rect( - screen, - PAGE_BACKGROUND_COLOR if selected else NOT_SELECTED_COLOR, - pygame.Rect( - pos.x+PAGE_BORDER_WIDTH//2, - pos.y+PAGE_BORDER_WIDTH//2, - TAB_WIDTH-PAGE_BORDER_WIDTH, - TAB_HEIGHT-PAGE_BORDER_WIDTH - ) + pygame.draw.rect( + screen, + PAGE_BACKGROUND_COLOR if selected else NOT_SELECTED_COLOR, + pygame.Rect( + pos.x+PAGE_BORDER_WIDTH//2, + pos.y+PAGE_BORDER_WIDTH//2, + TAB_WIDTH-PAGE_BORDER_WIDTH, + TAB_HEIGHT-PAGE_BORDER_WIDTH ) + ) - pygame.draw.rect( - screen, - PAGE_BORDER_COLOR if selected else BORDER_NOT_SELECTED_COLOR, - pygame.Rect( - pos.x, - pos.y, - TAB_WIDTH, - TAB_HEIGHT - ), - PAGE_BORDER_WIDTH//2, PAGE_BORDER_RADIUS//2 - ) + pygame.draw.rect( + screen, + PAGE_BORDER_COLOR if selected else BORDER_NOT_SELECTED_COLOR, + pygame.Rect( + pos.x, + pos.y, + TAB_WIDTH, + TAB_HEIGHT + ), + PAGE_BORDER_WIDTH//2, PAGE_BORDER_RADIUS//2 + ) - text = font.render(str(n+1),False,"#000000") - x,y = text.get_size() - screen.blit(text,( - pos.x+(TAB_WIDTH-x)//2, - pos.y+(TAB_HEIGHT-y)//2+1 - )) + text = font.render(str(n+1),False,"#000000") + x,y = text.get_size() + screen.blit(text,( + pos.x+(TAB_WIDTH-x)//2, + pos.y+(TAB_HEIGHT-y)//2+1 + )) def main(data: dict): - pygame.init() - pygame.font.init() + pygame.init() + pygame.font.init() - # music - pygame.mixer.music.load("Gentleman Robber.mp3") - pygame.mixer.music.set_volume(0.2) - pygame.mixer.music.play(-1) + # music + pygame.mixer.music.load("Gentleman Robber.mp3") + pygame.mixer.music.set_volume(0.2) + pygame.mixer.music.play(-1) - font = pygame.font.SysFont("Comic Code",WORD_FONT_SIZE,True) - # print(font.size(WORD_SLOT_SPACING)) - screen = pygame.display.set_mode((1280, 720),pygame.RESIZABLE) - drawer = pygame.surface.Surface((SCREEN_WIDTH, SCREEN_HEIGHT)) - clock = pygame.time.Clock() - running = True + font = pygame.font.SysFont("Comic Code",WORD_FONT_SIZE,True) + # print(font.size(WORD_SLOT_SPACING)) + screen = pygame.display.set_mode((1280, 720),pygame.RESIZABLE) + drawer = pygame.surface.Surface((SCREEN_WIDTH, SCREEN_HEIGHT)) + clock = pygame.time.Clock() + running = True - word_page = Page(True,None,Vector2(PAGE_MARGIN,PAGE_MARGIN),Vector2(WORD_PAGE_WIDTH,WORD_PAGE_HEIGHT),[]) - pages = [word_page] + [ - Page( - False, - p, - TEXT_PAGE_POS, - Vector2(TEXT_PAGE_WIDTH,TEXT_PAGE_HEIGHT), - [] - ) for p in data["pages"] - ] - if len(pages) > 1: - pages[1].visible = True - visible_page = 1 + word_page = Page(True,None,Vector2(PAGE_MARGIN,PAGE_MARGIN),Vector2(WORD_PAGE_WIDTH,WORD_PAGE_HEIGHT),[]) + pages = [word_page] + [ + Page( + False, + p, + TEXT_PAGE_POS, + Vector2(TEXT_PAGE_WIDTH,TEXT_PAGE_HEIGHT), + [] + ) for p in data["pages"] + ] + if len(pages) > 1: + pages[1].visible = True + visible_page = 1 + else: + visible_page = 0 + + held_word = None + + word_slots: list[WordSlot] = [] + for y in range(GRID_HEIGHT): + for x in range(GRID_WIDTH): + word_slots.append(WordSlot( + None, + word_page, + Vector2( + PAGE_BORDER_WIDTH+WORD_MARGIN+(WORD_WIDTH+WORD_MARGIN)*x, + PAGE_BORDER_WIDTH+WORD_MARGIN+(WORD_HEIGHT+WORD_MARGIN)*y + ) + )) + + def add_to_text(text,word,space=" "): + width, _ = font.size(text[-1]+space+word) + if width > TEXT_WIDTH: + return text+[word] else: - visible_page = 0 + if text[-1] == "": + space = "" + text[-1] += space+word + return text - held_word = None - - word_slots: list[WordSlot] = [] - for y in range(GRID_HEIGHT): - for x in range(GRID_WIDTH): - word_slots.append(WordSlot( - None, - word_page, - Vector2( - PAGE_BORDER_WIDTH+WORD_MARGIN+(WORD_WIDTH+WORD_MARGIN)*x, - PAGE_BORDER_WIDTH+WORD_MARGIN+(WORD_HEIGHT+WORD_MARGIN)*y - ) - )) - - def add_to_text(text,word,space=" "): - width, _ = font.size(text[-1]+space+word) - if width > TEXT_WIDTH: - return text+[word] + for p in pages[1:]: + new_text = [] + for t in p.text: + new_text.append("") + text_words = t.split(" ") + for w in text_words: + res = re.findall(r"([^\] ]*)\[(.*?)\/([\d\+]+)\]([^\[ ]*)",w) + if res == []: + new_text = add_to_text(new_text,w) else: - if text[-1] == "": - space = "" - text[-1] += space+word - return text + if new_text[0] != "": + new_text = add_to_text(new_text,"") - for p in pages[1:]: - new_text = [] - for t in p.text: - new_text.append("") - text_words = t.split(" ") - for w in text_words: - res = re.findall(r"([^\] ]*)\[(.*?)\/([\d\+]+)\]([^\[ ]*)",w) - if res == []: - new_text = add_to_text(new_text,w) - else: - if new_text[0] != "": - new_text = add_to_text(new_text,"") + for slot in res: + new_text = add_to_text(new_text,slot[0]+WORD_SLOT_SPACING+slot[3],"") + height = WORD_FONT_SIZE*len(new_text) + width, _ = font.size(new_text[-1]) + extra_width, _ = font.size(slot[3]) + pos = Vector2( + TEXT_MARGIN + width - extra_width - WORD_WIDTH, + TEXT_MARGIN + height - WORD_HEIGHT//2 + TEXT_TOP_MARGIN*len(new_text) + WORD_SLOT_NUDGE_DOWN + ) + # if new_text[-1] != WORD_SLOT_SPACING+slot[2]: + pos.x += WORD_SLOT_NUDGING + new_slot = WordSlot([WordColor(int(i)) for i in slot[2].split("+")],p,pos) + word_slots.append(new_slot) + p.slots.append((slot[1].replace("%"," ").split("+"),new_slot)) - for slot in res: - new_text = add_to_text(new_text,slot[0]+WORD_SLOT_SPACING+slot[3],"") - height = WORD_FONT_SIZE*len(new_text) - width, _ = font.size(new_text[-1]) - extra_width, _ = font.size(slot[3]) - pos = Vector2( - TEXT_MARGIN + width - extra_width - WORD_WIDTH, - TEXT_MARGIN + height - WORD_HEIGHT//2 + TEXT_TOP_MARGIN*len(new_text) + WORD_SLOT_NUDGE_DOWN - ) - # if new_text[-1] != WORD_SLOT_SPACING+slot[2]: - pos.x += WORD_SLOT_NUDGING - new_slot = WordSlot([WordColor(int(i)) for i in slot[2].split("+")],p,pos) - word_slots.append(new_slot) - p.slots.append((slot[1].replace("%"," ").split("+"),new_slot)) - - p.text = new_text + p.text = new_text - words: list[Word] = [] - for i,w in enumerate(data["words"]): - slot = word_slots[i] - word = Word(w[0],WordColor(w[1]),word_page,Vector2(slot.position.x,slot.position.y)) - slot.word = word - word.slot = slot - words.append(word) + words: list[Word] = [] + for i,w in enumerate(data["words"]): + slot = word_slots[i] + word = Word(w[0],WordColor(w[1]),word_page,Vector2(slot.position.x,slot.position.y)) + slot.word = word + word.slot = slot + words.append(word) - all_words = [w.word for w in words] + all_words = [w.word for w in words] + for p in pages: + not_present = [word for s in p.slots for word in s[0] if word not in all_words] + if not_present: + print(not_present) + + def focused(): + x, y = pygame.mouse.get_pos() + ratio_x = (screen.get_width() / drawer.get_width()) + ratio_y = (screen.get_height() / drawer.get_height()) + scaled_xy = (x/ratio_x,y/ratio_y) + for w in words: + if w.page is None: + return w + if not w.page.visible: + continue + + pos = w.page.position+w.position + rect = pygame.Rect( + pos.x - WORD_MARGIN//2, + pos.y - WORD_MARGIN//2, + WORD_WIDTH + WORD_MARGIN, + WORD_HEIGHT + WORD_MARGIN + ) + if rect.collidepoint(scaled_xy): + return w + + for i in range(len(pages)-1): + pos = TAB_POS + Vector2((TAB_WIDTH+TAB_MARGIN)*i,0) + rect = pygame.Rect( + pos.x - TAB_MARGIN//2, + pos.y - TAB_MARGIN//2, + TAB_WIDTH + TAB_MARGIN, + TAB_HEIGHT + TAB_MARGIN + ) + if rect.collidepoint(scaled_xy): + return i+1 + + return None + + def click_slot(): + nonlocal held_word + slot = None + x, y = pygame.mouse.get_pos() + ratio_x = (screen.get_width() / drawer.get_width()) + ratio_y = (screen.get_height() / drawer.get_height()) + scaled_xy = (x/ratio_x,y/ratio_y) + for w in word_slots: + if not w.page.visible: + continue + + pos = w.page.position+w.position + rect = pygame.Rect( + pos.x - WORD_MARGIN//2, + pos.y - WORD_MARGIN//2, + WORD_WIDTH + WORD_MARGIN, + WORD_HEIGHT + WORD_MARGIN + ) + if rect.collidepoint(scaled_xy): + slot = w + break + + if slot is None: + if held_word is not None and held_word.slot.colors is not None: + words.remove(held_word) + held_word = None + return + + if held_word is None and slot.word is not None: + held_word = slot.word + held_word.page = None + slot.word = None + elif held_word is not None and slot.word is None: + if slot.colors is not None and held_word.color not in slot.colors: + return + + slot.word = held_word + if slot.colors is not None and held_word.slot.colors is None: + new_word = slot.word.copy() + new_word.page = slot.word.slot.page + new_word.position = slot.word.slot.position + new_word.slot = slot.word.slot + new_word.slot.word = new_word + words.append(new_word) + + if slot.word.slot.colors is None or slot.colors is not None: + slot.word.position = slot.position + slot.word.page = slot.page + slot.word.slot = slot + else: + words.remove(slot.word) + slot.word = None + + held_word = None + + play_sound(slot.page) + + + while running: + for event in pygame.event.get(): + if event.type == pygame.MOUSEBUTTONDOWN: + click_slot() + i = focused() + if isinstance(i,int) and pages[i-1].solved() == SolvedState.Correct: + page_sound = pygame.mixer.Sound("page.wav") + page_sound.set_volume(0.7) + page_sound.play() + pages[visible_page].visible = False + visible_page = i + pages[visible_page].visible = True + elif event.type == pygame.QUIT: + running = False + + key_state = pygame.key.get_pressed() + if key_state[pygame.K_SPACE] and key_state[pygame.K_LCTRL]: + pages[visible_page].solve() + for _,slot in pages[visible_page].slots: + words.append(slot.word) + + # fill the screen with a color to wipe away anything from last frame + drawer.fill("#4d2905") + + # Held word + if held_word is not None: + x, y = pygame.mouse.get_pos() + ratio_x = (screen.get_width() / drawer.get_width()) + ratio_y = (screen.get_height() / drawer.get_height()) + scaled_xy = (x/ratio_x,y/ratio_y) + held_word.position = Vector2(scaled_xy) - Vector2(WORD_WIDTH//2,WORD_HEIGHT//2) + + # render pages for p in pages: - not_present = [word for s in p.slots for word in s[0] if word not in all_words] - if not_present: - print(not_present) + if not p.visible: + continue - def focused(): - x, y = pygame.mouse.get_pos() - ratio_x = (screen.get_width() / drawer.get_width()) - ratio_y = (screen.get_height() / drawer.get_height()) - scaled_xy = (x/ratio_x,y/ratio_y) - for w in words: - if w.page is None: - return w - if not w.page.visible: - continue + draw_page(p, drawer, font) - pos = w.page.position+w.position - rect = pygame.Rect( - pos.x - WORD_MARGIN//2, - pos.y - WORD_MARGIN//2, - WORD_WIDTH + WORD_MARGIN, - WORD_HEIGHT + WORD_MARGIN - ) - if rect.collidepoint(scaled_xy): - return w + # drawing word slots + for w in word_slots: + if not w.page.visible or w.colors is None: + continue - for i in range(len(pages)-1): - pos = TAB_POS + Vector2((TAB_WIDTH+TAB_MARGIN)*i,0) - rect = pygame.Rect( - pos.x - TAB_MARGIN//2, - pos.y - TAB_MARGIN//2, - TAB_WIDTH + TAB_MARGIN, - TAB_HEIGHT + TAB_MARGIN - ) - if rect.collidepoint(scaled_xy): - return i+1 + draw_word_slot(w, drawer) - return None + # drawing words + for w in words: + if w.page is not None and not w.page.visible and w != held_word: + continue - def click_slot(): - nonlocal held_word - slot = None - x, y = pygame.mouse.get_pos() - ratio_x = (screen.get_width() / drawer.get_width()) - ratio_y = (screen.get_height() / drawer.get_height()) - scaled_xy = (x/ratio_x,y/ratio_y) - for w in word_slots: - if not w.page.visible: - continue + draw_word(w, drawer, font) - pos = w.page.position+w.position - rect = pygame.Rect( - pos.x - WORD_MARGIN//2, - pos.y - WORD_MARGIN//2, - WORD_WIDTH + WORD_MARGIN, - WORD_HEIGHT + WORD_MARGIN - ) - if rect.collidepoint(scaled_xy): - slot = w - break + # drawing tabs + for i in range(len(pages)-1): + if i != 0 and pages[i].solved() != SolvedState.Correct: + break - if slot is None: - if held_word is not None and held_word.slot.colors is not None: - words.remove(held_word) - held_word = None - return + draw_tab(i,drawer,font,visible_page==i+1) - if held_word is None and slot.word is not None: - held_word = slot.word - held_word.page = None - slot.word = None - elif held_word is not None and slot.word is None: - if slot.colors is not None and held_word.color not in slot.colors: - return + # drawing held word + if held_word is not None: + draw_word(held_word, drawer, font) - slot.word = held_word - if slot.colors is not None and held_word.slot.colors is None: - new_word = slot.word.copy() - new_word.page = slot.word.slot.page - new_word.position = slot.word.slot.position - new_word.slot = slot.word.slot - new_word.slot.word = new_word - words.append(new_word) + # Draw everything to the screen + frame = pygame.transform.scale(drawer,screen.get_size()) + screen.blit(frame,frame.get_rect()) - if slot.word.slot.colors is None or slot.colors is not None: - slot.word.position = slot.position - slot.word.page = slot.page - slot.word.slot = slot - else: - words.remove(slot.word) - slot.word = None + # if focused() is not None: + # pygame.mouse.set_cursor(*pygame.cursors.tri_left) + # else: + # pygame.mouse.set_cursor(*pygame.cursors.arrow) - held_word = None + pygame.display.flip() - play_sound(slot.page) + clock.tick(60) # limits FPS to 60 - - while running: - for event in pygame.event.get(): - if event.type == pygame.MOUSEBUTTONDOWN: - click_slot() - i = focused() - if isinstance(i,int) and pages[i-1].solved() == SolvedState.Correct: - page_sound = pygame.mixer.Sound("page.wav") - page_sound.set_volume(0.7) - page_sound.play() - pages[visible_page].visible = False - visible_page = i - pages[visible_page].visible = True - elif event.type == pygame.QUIT: - running = False - - key_state = pygame.key.get_pressed() - if key_state[pygame.K_SPACE] and key_state[pygame.K_LCTRL]: - pages[visible_page].solve() - for _,slot in pages[visible_page].slots: - words.append(slot.word) - - # fill the screen with a color to wipe away anything from last frame - drawer.fill("#4d2905") - - # Held word - if held_word is not None: - x, y = pygame.mouse.get_pos() - ratio_x = (screen.get_width() / drawer.get_width()) - ratio_y = (screen.get_height() / drawer.get_height()) - scaled_xy = (x/ratio_x,y/ratio_y) - held_word.position = Vector2(scaled_xy) - Vector2(WORD_WIDTH//2,WORD_HEIGHT//2) - - # render pages - for p in pages: - if not p.visible: - continue - - draw_page(p, drawer, font) - - # drawing word slots - for w in word_slots: - if not w.page.visible or w.colors is None: - continue - - draw_word_slot(w, drawer) - - # drawing words - for w in words: - if w.page is not None and not w.page.visible and w != held_word: - continue - - draw_word(w, drawer, font) - - # drawing tabs - for i in range(len(pages)-1): - if i != 0 and pages[i].solved() != SolvedState.Correct: - break - - draw_tab(i,drawer,font,visible_page==i+1) - - # drawing held word - if held_word is not None: - draw_word(held_word, drawer, font) - - # Draw everything to the screen - frame = pygame.transform.scale(drawer,screen.get_size()) - screen.blit(frame,frame.get_rect()) - - # if focused() is not None: - # pygame.mouse.set_cursor(*pygame.cursors.tri_left) - # else: - # pygame.mouse.set_cursor(*pygame.cursors.arrow) - - # flip() the display to put your work on screen - pygame.display.flip() - - clock.tick(60) # limits FPS to 60 - - pygame.quit() + pygame.quit() if __name__ == "__main__": - with open("data.json","r") as file_pointer: - data = json.load(file_pointer) + with open("data.json","r") as file_pointer: + data = json.load(file_pointer) - main(data) \ No newline at end of file + main(data) \ No newline at end of file