Files
Archipelago/worlds/apquest/game/play_in_console.py
NewSoupVi e0cbf77dae APQuest: Implement New Game (#5393)
* APQuest

* Add confetti cannon

* ID change on enemy drop

* nevermind

* Write the apworld

* Actually implement hard mode

* split everything into multiple files

* Push out webworld into a file

* Comment

* Enemy health graphics

* more ruff rules

* graphics :)

* heal player when receiving health upgrade

* the dumbest client of all time

* Fix typo

* You can kinda play it now! Now we just need to render the game... :)))

* fix kvui imports again

* It's playable. Kind of

* oops

* Sounds and stuff

* exceptions for audio

* player sprite stuff

* Not attack without sword

* Make sure it plays correctly

* Collect behavior

* ruff

* don't need to clear checked_locations, but do need to still clear finished_game

* Connect calls disconnect, so this is not necessary

* more seemless reconnection

* Ok now I think it's correct

* Bgm

* Bgm

* minor adjustment

* More refactoring of graphics and sound

* add graphics

* Item column

* Fix enemies not regaining their health

* oops

* oops

* oops

* 6 health final boss on hard mode

* boss_6.png

* Display APQuest items correctly

* auto switch tabs

* some mypy stuff

* Intro song

* Confetti Cannon

* a bit more confetti work

* launcher component

* Graphics change

* graphics and cleanup

* fix apworld

* comment out horse and cat for now

* add docs

* copypasta

* ruff made my comment look unhinged

* Move that comment

* Fix typing and don't import kvui in nogui

* lmao that already exists I don't need to do it myself

* Must've just copied this from somewhere

* order change

* Add unit tests

* Notes about the client

* oops

* another intro song case

* Write WebWorld and setup guides

* Yes description provided

* thing

* how to play

* Music and Volume

* Add cat and horse player sprites

* updates

* Add hammer and breakable wall

* TODO

* replace wav with ogg

* Codeowners and readme

* finish unit tests

* lint

* Todid

* Update worlds/apquest/client/ap_quest_client.py

Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com>

* Update worlds/apquest/client/custom_views.py

Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com>

* Filler pattern

* __future__ annotations

* twebhost

* Allow wasd and arrow keys

* correct wording

* oops

* just say the website

* append instead of +=

* qwint is onto my favoritism

* kitty alias

* Add a comment about preplaced items for assertAccessDependency

* Use classvar_matrix instead of MultiworldTestBase

* actually remove multiworld stuff from those tests

* missed one more

* Refactor a bit more

* Fix getting of the user path

* Actually explain components

* Meh

* Be a bit clearer about what's what

* oops

* More comments in the regions.py file

* Nevermind

* clarify regions further

* I use too many brackets

* Ok I'm done fr

* simplify wording

* missing .

* Add precollected example

* add note about precollected advancements

* missing s

* APQuest sound rework

* Volume slider

* I forgot I made this

* a

* fix volume of jingles

* Add math trap to game (only works in play_in_console mode so far)

* Math trap in apworld and client side

* Fix background during math trap

* fix leading 0

* Sound and further ui improvements for Math Trap

* fix music bug

* rename apquest subfolder to game

* Move comment to where it belongs

* Clear up language around components (hopefully)

* Clear up what CommonClient is

* Reword some more

* Mention Archipelago (the program) explicitly

* Update worlds/apquest/docs/en_APQuest.md

Co-authored-by: Ixrec <ericrhitchcock@gmail.com>

* Explain a bit more why you would use classvar matrix

* reword the assert raises stuff

* the volume slider thing is no longer true

* german game page

* Be more clear about why we're overriding Item and Location

* default item classification

* logically considered -> relevant to logic ()

* Update worlds/apquest/items.py

Co-authored-by: Ixrec <ericrhitchcock@gmail.com>

* a word on the ambiguity of the word 'filler'

* more rewording

* amount -> number

* stress the necessity of appending to the multiworld itempool

* Update worlds/apquest/locations.py

Co-authored-by: Ixrec <ericrhitchcock@gmail.com>

* get_location_names_with_ids

* slight rewording of the new helper method

* add some words about creating known location+item pairs

* Add some more words to worlds/apqeust/options.py

* more words in options.py

* 120 chars (thanks Ixrec >:((( LOL)

* Less confusing wording about rules, hopefully?

* victory -> completion

* remove the immediate creation of the hammer rule on the option region entrance

* access rule performance

* Make all imports module-level in world.py

* formatting

* get rid of noqa RUF012 (and also disable the rule in my local ruff.toml

* move comment for docstring closer to docstring in another place

* advancement????

* Missing function type annotations

* pass mypy again (I don't love this one but all the alternatives are equally bad)

* subclass instead of override

* I forgor to remove these

* Get rid of classvar_matrix and instead talk about some other stuff

* protect people a bit from the assertAccessDependency nonsense

* reword a bit more

* word

* More accessdependency text

* More accessdependency text

* More accessdependency text

* More accessdependency text

* oops

* this is supposed to be absolute

* Add some links to docs

* that's called game now

* Add an archipelago.json and explain what it means

* new line who dis

* reorganize a bit

* ignore instead of skip

* Update archipelago.json

* She new on my line till I

* Update archipelago.json

* add controls tab

* new ruff rule? idk

* WHOOPS

* Pack graphics into fewer files

* annoying ruff format thing

* Cleanup + mypy

* relative import

* Update worlds/apquest/client/custom_views.py

Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>

* Update generate_math_problem.py

* Update worlds/apquest/game/player.py

Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>

---------

Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com>
Co-authored-by: Ixrec <ericrhitchcock@gmail.com>
Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
2025-11-25 00:38:06 +01:00

144 lines
4.5 KiB
Python

from .events import ConfettiFired, MathProblemSolved
try:
from pynput import keyboard
from pynput.keyboard import Key, KeyCode
except ImportError as e:
raise ImportError("In order to play APQuest from console, you have to install pynput.") from e
from .game import Game
from .graphics import Graphic
from .inputs import Input
from .items import ITEM_TO_GRAPHIC
graphic_to_char = {
Graphic.EMPTY: " ",
Graphic.WALL: "W",
Graphic.BUTTON_NOT_ACTIVATED: "B",
Graphic.BUTTON_ACTIVATED: "A",
Graphic.KEY_DOOR: "D",
Graphic.BUTTON_DOOR: "?",
Graphic.CHEST: "C",
Graphic.BUSH: "T",
Graphic.BREAKABLE_BLOCK: "~",
Graphic.NORMAL_ENEMY_2_HEALTH: "2",
Graphic.NORMAL_ENEMY_1_HEALTH: "1",
Graphic.BOSS_5_HEALTH: "5",
Graphic.BOSS_4_HEALTH: "4",
Graphic.BOSS_3_HEALTH: "3",
Graphic.BOSS_2_HEALTH: "2",
Graphic.BOSS_1_HEALTH: "1",
Graphic.PLAYER_DOWN: "v",
Graphic.PLAYER_UP: "^",
Graphic.PLAYER_LEFT: "<",
Graphic.PLAYER_RIGHT: ">",
Graphic.KEY: "K",
Graphic.SHIELD: "X",
Graphic.SWORD: "S",
Graphic.HAMMER: "H",
Graphic.HEART: "",
Graphic.CONFETTI_CANNON: "?",
Graphic.REMOTE_ITEM: "I",
Graphic.UNKNOWN: "ß",
Graphic.ZERO: "0",
Graphic.ONE: "1",
Graphic.TWO: "2",
Graphic.THREE: "3",
Graphic.FOUR: "4",
Graphic.FIVE: "5",
Graphic.SIX: "6",
Graphic.SEVEN: "7",
Graphic.EIGHT: "8",
Graphic.NINE: "9",
Graphic.PLUS: "+",
Graphic.MINUS: "-",
Graphic.TIMES: "x",
Graphic.DIVIDE: "/",
Graphic.LETTER_A: "A",
Graphic.LETTER_E: "E",
Graphic.LETTER_H: "H",
Graphic.LETTER_I: "I",
Graphic.LETTER_M: "M",
Graphic.LETTER_T: "T",
Graphic.EQUALS: "=",
Graphic.NO: "X",
}
KEY_CONVERSION = {
keyboard.KeyCode.from_char("w"): Input.UP,
Key.up: Input.UP,
keyboard.KeyCode.from_char("s"): Input.DOWN,
Key.down: Input.DOWN,
keyboard.KeyCode.from_char("a"): Input.LEFT,
Key.left: Input.LEFT,
keyboard.KeyCode.from_char("d"): Input.RIGHT,
Key.right: Input.RIGHT,
Key.space: Input.ACTION,
keyboard.KeyCode.from_char("c"): Input.CONFETTI,
keyboard.KeyCode.from_char("0"): Input.ZERO,
keyboard.KeyCode.from_char("1"): Input.ONE,
keyboard.KeyCode.from_char("2"): Input.TWO,
keyboard.KeyCode.from_char("3"): Input.THREE,
keyboard.KeyCode.from_char("4"): Input.FOUR,
keyboard.KeyCode.from_char("5"): Input.FIVE,
keyboard.KeyCode.from_char("6"): Input.SIX,
keyboard.KeyCode.from_char("7"): Input.SEVEN,
keyboard.KeyCode.from_char("8"): Input.EIGHT,
keyboard.KeyCode.from_char("9"): Input.NINE,
Key.backspace: Input.BACKSPACE,
}
def render_to_text(game: Game) -> str:
player = game.player
rendered_graphics = game.render()
output_string = f"Health: {player.current_health}/{player.max_health}\n"
inventory = []
for item, count in player.inventory.items():
inventory += [graphic_to_char[ITEM_TO_GRAPHIC[item]] for _ in range(count)]
inventory.sort()
output_string += f"Inventory: {', '.join(inventory)}\n"
if player.has_won:
output_string += "VICTORY!!!\n"
while game.queued_events:
next_event = game.queued_events.pop(0)
if isinstance(next_event, ConfettiFired):
output_string += "Confetti fired! You feel motivated :)\n"
if isinstance(next_event, MathProblemSolved):
output_string += "Math problem solved!\n"
for row in rendered_graphics:
output_string += " ".join(graphic_to_char[graphic] for graphic in row)
output_string += "\n"
return output_string
if __name__ == "__main__":
hard_mode = input("Do you want to play hard mode? (Y/N)").lower().strip() in ("y", "yes")
hammer_exists = input("Do you want the hammer to exist in the game? (Y/N)").lower().strip() in ("y", "yes")
extra_chest = input("Do you want the extra starting chest to exist in the game?").lower().strip() in ("y", "yes")
math_trap_percentage = int(input("What should the percentage of math traps be?"))
game = Game(hard_mode, hammer_exists, extra_chest)
game.gameboard.fill_default_location_content(math_trap_percentage)
def input_and_rerender(input_key: Input) -> None:
game.input(input_key)
print(render_to_text(game))
def on_press(key: Key | KeyCode | None) -> None:
if key in KEY_CONVERSION:
input_and_rerender(KEY_CONVERSION[key])
print(render_to_text(game))
with keyboard.Listener(on_press=on_press) as listener:
while True:
listener.join()