Text Generation
Transformers
Safetensors
qwen2
godot
gdscript
qlora
game-development
conversational
text-generation-inference
Instructions to use khursheed/VibeGame-8B with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use khursheed/VibeGame-8B with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="khursheed/VibeGame-8B") messages = [ {"role": "user", "content": "Who are you?"}, ] pipe(messages)# pip install -U transformers accelerate # Load model directly from transformers import AutoTokenizer, AutoModelForCausalLM tokenizer = AutoTokenizer.from_pretrained("khursheed/VibeGame-8B") model = AutoModelForCausalLM.from_pretrained("khursheed/VibeGame-8B", device_map="auto") messages = [ {"role": "user", "content": "Who are you?"}, ] inputs = tokenizer.apply_chat_template( messages, add_generation_prompt=True, tokenize=True, return_dict=True, return_tensors="pt", ).to(model.device) outputs = model.generate(**inputs, max_new_tokens=256) print(tokenizer.decode(outputs[0][inputs["input_ids"].shape[-1]:])) - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use khursheed/VibeGame-8B with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "khursheed/VibeGame-8B" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "khursheed/VibeGame-8B", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker
docker model run hf.co/khursheed/VibeGame-8B
- SGLang
How to use khursheed/VibeGame-8B with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "khursheed/VibeGame-8B" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "khursheed/VibeGame-8B", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "khursheed/VibeGame-8B" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "khursheed/VibeGame-8B", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }' - Docker Model Runner
How to use khursheed/VibeGame-8B with Docker Model Runner:
docker model run hf.co/khursheed/VibeGame-8B
Download code/scripts/create_teacher_batch_c.py from khursheed/VibeGame-8B: direct link, hf CLI and curl.
- Browser
- Download file 85.2 kB
-
https://huggingface.co/khursheed/VibeGame-8B/resolve/main/code/scripts/create_teacher_batch_c.py
- Command line
-
hf download hf://khursheed/VibeGame-8B/code/scripts/create_teacher_batch_c.py
-
curl -L -o create_teacher_batch_c.py https://huggingface.co/khursheed/VibeGame-8B/resolve/main/code/scripts/create_teacher_batch_c.py
85.2 kB
| #!/usr/bin/env python3 | |
| """Original in-session Codex teacher games for train-only prompt IDs 033–044. | |
| This reproducible authoring script does NOT run an engine, call another model, | |
| read blind prompts, or claim that static contract checks establish correctness. | |
| Linux Godot import and full 60-second validation are required for SFT admission. | |
| """ | |
| import hashlib | |
| import json | |
| from pathlib import Path | |
| import sys | |
| ROOT = Path(__file__).resolve().parents[1] | |
| sys.path.insert(0, str(ROOT / "src")) | |
| from vibegame.project import canonical_json, parse_project, project_sha256 | |
| from vibegame.seeds import PROJECT, SCENE, UI | |
| GAMES = { | |
| 33: ("Pocket Golf", '''extends Node2D | |
| var ball = Vector2.ZERO | |
| var velocity = Vector2.ZERO | |
| var hole = Vector2.ZERO | |
| var walls: Array[Rect2] = [] | |
| var course = 0 | |
| var shots = 0 | |
| var dragging = false | |
| var state = 0 | |
| func _ready() -> void: | |
| \treset() | |
| func reset() -> void: | |
| \tcourse = 0 | |
| \tshots = 0 | |
| \tstate = 0 | |
| \tsetup_course() | |
| func setup_course() -> void: | |
| \tball = Vector2(130, 490) | |
| \tvelocity = Vector2.ZERO | |
| \tdragging = false | |
| \tif course == 0: | |
| \t\thole = Vector2(800, 225) | |
| \t\twalls = [Rect2(360, 260, 38, 330)] | |
| \telif course == 1: | |
| \t\thole = Vector2(820, 500) | |
| \t\twalls = [Rect2(290, 120, 34, 290), Rect2(620, 305, 34, 285)] | |
| \telse: | |
| \t\thole = Vector2(800, 180) | |
| \t\twalls = [Rect2(260, 260, 360, 32), Rect2(620, 420, 210, 32)] | |
| func _unhandled_input(event: InputEvent) -> void: | |
| \tif event is InputEventKey and event.pressed and not event.echo and event.keycode == KEY_R: | |
| \t\treset() | |
| \tif state != 0: | |
| \t\treturn | |
| \tif event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT: | |
| \t\tif event.pressed and velocity.length() < 6 and event.position.distance_to(ball) < 30: | |
| \t\t\tdragging = true | |
| \t\telif not event.pressed and dragging: | |
| \t\t\tdragging = false | |
| \t\t\tvelocity = (ball - event.position).limit_length(170) * 4.0 | |
| \t\t\tif velocity.length() > 4: | |
| \t\t\t\tshots += 1 | |
| func _process(dt: float) -> void: | |
| \tif state == 0 and not dragging: | |
| \t\tfor step in range(4): | |
| \t\t\tvar old = ball | |
| \t\t\tball.x += velocity.x * dt / 4.0 | |
| \t\t\tif ball.x < 52 or ball.x > 908: | |
| \t\t\t\tball.x = clampf(ball.x, 52, 908) | |
| \t\t\t\tvelocity.x *= -0.9 | |
| \t\t\tfor wall in walls: | |
| \t\t\t\tif wall.grow(10).has_point(ball): | |
| \t\t\t\t\tball.x = old.x | |
| \t\t\t\t\tvelocity.x *= -0.9 | |
| \t\t\told = ball | |
| \t\t\tball.y += velocity.y * dt / 4.0 | |
| \t\t\tif ball.y < 132 or ball.y > 578: | |
| \t\t\t\tball.y = clampf(ball.y, 132, 578) | |
| \t\t\t\tvelocity.y *= -0.9 | |
| \t\t\tfor wall in walls: | |
| \t\t\t\tif wall.grow(10).has_point(ball): | |
| \t\t\t\t\tball.y = old.y | |
| \t\t\t\t\tvelocity.y *= -0.9 | |
| \t\tvelocity *= pow(0.975, dt * 60.0) | |
| \t\tif velocity.length() < 6: | |
| \t\t\tvelocity = Vector2.ZERO | |
| \t\tif ball.distance_to(hole) < 20 and velocity.length() < 180: | |
| \t\t\tcourse += 1 | |
| \t\t\tif course == 3: | |
| \t\t\t\tstate = 1 | |
| \t\t\telse: | |
| \t\t\t\tsetup_course() | |
| \t\telif shots >= 18 and velocity == Vector2.ZERO: | |
| \t\t\tstate = -1 | |
| \tqueue_redraw() | |
| func _draw() -> void: | |
| \tbackdrop("POCKET GOLF", "Drag backward from the resting ball, release to shoot. Complete 3 holes. R: restart") | |
| \tdraw_rect(Rect2(40, 120, 880, 470), Color("164a40")) | |
| \tfor wall in walls: | |
| \t\tdraw_rect(wall, Color("72a7ac")) | |
| \tdraw_circle(hole, 19, Color("081a23")) | |
| \tdraw_line(hole, hole - Vector2(0, 45), Color.WHITE, 2) | |
| \tdraw_circle(ball, 10, Color.WHITE) | |
| \tif dragging: | |
| \t\tdraw_line(ball, ball + (ball - get_global_mouse_position()).limit_length(170), Color("ffd166"), 3) | |
| \ttext_at("Course %d/3 Shots %d/18" % [mini(course + 1, 3), shots], Vector2(45, 620), 22) | |
| \tif state != 0: | |
| \t\ttext_at("THREE HOLES COMPLETE! R: play again" if state == 1 else "OUT OF SHOTS. R: retry", Vector2(255, 350), 28) | |
| '''), | |
| 34: ("Color Contagion", '''extends Node2D | |
| const N = 8 | |
| const ORIGIN = Vector2(280, 140) | |
| const COLORS = [Color("ec6b73"), Color("f6c85f"), Color("6cdbb3"), Color("6aa7ed"), Color("b58ce8")] | |
| var board: Array[int] = [] | |
| var owned: Dictionary = {} | |
| var active_color = 0 | |
| var moves = 0 | |
| var state = 0 | |
| func _ready() -> void: | |
| \treset() | |
| func reset() -> void: | |
| \tboard.clear() | |
| \towned.clear() | |
| \tmoves = 0 | |
| \tstate = 0 | |
| \tvar rng = RandomNumberGenerator.new() | |
| \trng.seed = 34034 | |
| \tfor i in range(N * N): | |
| \t\tboard.append(rng.randi_range(0, 4)) | |
| func flood(color: int) -> void: | |
| \tactive_color = color | |
| \tfor cell in owned.keys(): | |
| \t\tboard[cell.y * N + cell.x] = color | |
| \tvar changed = true | |
| \twhile changed: | |
| \t\tchanged = false | |
| \t\tfor cell in owned.keys(): | |
| \t\t\tfor direction in [Vector2i.LEFT, Vector2i.RIGHT, Vector2i.UP, Vector2i.DOWN]: | |
| \t\t\t\tvar neighbor: Vector2i = cell + direction | |
| \t\t\t\tif neighbor.x >= 0 and neighbor.x < N and neighbor.y >= 0 and neighbor.y < N: | |
| \t\t\t\t\tif not owned.has(neighbor) and board[neighbor.y * N + neighbor.x] == color: | |
| \t\t\t\t\t\towned[neighbor] = true | |
| \t\t\t\t\t\tchanged = true | |
| func choose(color: int) -> void: | |
| \tif state != 0 or owned.is_empty() or color == active_color: | |
| \t\treturn | |
| \tmoves += 1 | |
| \tflood(color) | |
| \tif owned.size() == N * N: | |
| \t\tstate = 1 | |
| \telif moves >= 24: | |
| \t\tstate = -1 | |
| func _unhandled_input(event: InputEvent) -> void: | |
| \tif event is InputEventKey and event.pressed and not event.echo: | |
| \t\tif event.keycode == KEY_R: | |
| \t\t\treset() | |
| \t\telif event.keycode >= KEY_1 and event.keycode <= KEY_5: | |
| \t\t\tchoose(int(event.keycode) - int(KEY_1)) | |
| \tif event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT and state == 0: | |
| \t\tif owned.is_empty() and Rect2(ORIGIN, Vector2(400, 400)).has_point(event.position): | |
| \t\t\tvar cell = Vector2i((event.position - ORIGIN) / 50.0) | |
| \t\t\towned[cell] = true | |
| \t\t\tflood(board[cell.y * N + cell.x]) | |
| \t\tfor i in range(5): | |
| \t\t\tif Rect2(280 + i * 80, 561, 72, 43).has_point(event.position): | |
| \t\t\t\tchoose(i) | |
| func _process(_dt: float) -> void: | |
| \tqueue_redraw() | |
| func _draw() -> void: | |
| \tbackdrop("COLOR CONTAGION", "Click a starting tile. Then click a color or press 1–5 to absorb it. R: restart") | |
| \tfor y in range(N): | |
| \t\tfor x in range(N): | |
| \t\t\tvar cell = Vector2i(x, y) | |
| \t\t\tvar area = Rect2(ORIGIN + Vector2(cell) * 50, Vector2(47, 47)) | |
| \t\t\tdraw_rect(area, COLORS[board[y * N + x]]) | |
| \t\t\tif owned.has(cell): | |
| \t\t\t\tdraw_circle(area.get_center(), 5, Color.WHITE) | |
| \tfor i in range(5): | |
| \t\tdraw_rect(Rect2(280 + i * 80, 561, 72, 43), COLORS[i]) | |
| \t\ttext_at(str(i + 1), Vector2(308 + i * 80, 591), 24, Color("132237")) | |
| \ttext_at("Claimed %d/64 Moves %d/24" % [owned.size(), moves], Vector2(280, 121), 22) | |
| \tif state != 0: | |
| \t\ttext_at("BOARD ABSORBED! R: again" if state == 1 else "MOVE LIMIT! R: retry", Vector2(290, 632), 24) | |
| '''), | |
| 35: ("Wildlife Window", '''extends Node2D | |
| var frame = Rect2(350, 250, 230, 175) | |
| var subjects: Array[Vector2] = [] | |
| var dragging = false | |
| var drag_offset = Vector2.ZERO | |
| var clock = 0.0 | |
| var remaining = 45.0 | |
| var shots = 5 | |
| var state = 0 | |
| var flash = 0.0 | |
| func _ready() -> void: | |
| \treset() | |
| func reset() -> void: | |
| \tframe.position = Vector2(350, 250) | |
| \tsubjects = [Vector2(420, 300), Vector2(570, 400), Vector2(340, 380)] | |
| \tclock = 0.0 | |
| \tremaining = 45.0 | |
| \tshots = 5 | |
| \tstate = 0 | |
| \tdragging = false | |
| \tflash = 0.0 | |
| func framed() -> int: | |
| \tvar count = 0 | |
| \tfor p in subjects: | |
| \t\tif frame.grow(-16).has_point(p): | |
| \t\t\tcount += 1 | |
| \treturn count | |
| func _unhandled_input(event: InputEvent) -> void: | |
| \tif event is InputEventKey and event.pressed and not event.echo: | |
| \t\tif event.keycode == KEY_R: | |
| \t\t\treset() | |
| \t\telif event.keycode == KEY_SPACE and state == 0: | |
| \t\t\tshots -= 1 | |
| \t\t\tflash = 0.2 | |
| \t\t\tif framed() == 3: | |
| \t\t\t\tstate = 1 | |
| \t\t\telif shots == 0: | |
| \t\t\t\tstate = -1 | |
| \tif event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT: | |
| \t\tdragging = event.pressed and state == 0 and frame.has_point(event.position) | |
| \t\tdrag_offset = event.position - frame.position | |
| \tif event is InputEventMouseMotion and dragging: | |
| \t\tframe.position = (event.position - drag_offset).clamp(Vector2(45, 135), Vector2(685, 390)) | |
| func _process(dt: float) -> void: | |
| \tflash = maxf(0, flash - dt) | |
| \tif state == 0: | |
| \t\tclock += dt | |
| \t\tremaining -= dt | |
| \t\tvar spread = 95.0 + 55.0 * sin(clock * 0.7) | |
| \t\tfor i in range(3): | |
| \t\t\tvar angle = clock * 0.55 + float(i) * TAU / 3.0 | |
| \t\t\tsubjects[i] = Vector2(480 + cos(angle) * spread, 345 + sin(angle) * spread * 0.7) | |
| \t\tif remaining <= 0: | |
| \t\t\tstate = -1 | |
| \tqueue_redraw() | |
| func _draw() -> void: | |
| \tbackdrop("WILDLIFE WINDOW", "Drag the viewfinder. Space: photograph all three subjects inside it. R: restart") | |
| \tdraw_rect(Rect2(40, 130, 880, 440), Color("213e42")) | |
| \tfor i in range(18): | |
| \t\tvar p = Vector2(70 + (i * 157) % 830, 158 + (i * 79) % 380) | |
| \t\tdraw_circle(p, 22, Color("2c5550")) | |
| \tfor i in range(subjects.size()): | |
| \t\tdraw_circle(subjects[i], 15, Color("ffbd69")) | |
| \t\tdraw_line(subjects[i] + Vector2(-19, 0), subjects[i] + Vector2(19, 0), Color("ffbd69"), 5) | |
| \t\ttext_at(str(i + 1), subjects[i] + Vector2(-6, 6), 17, Color("172236")) | |
| \tdraw_rect(frame, Color("72efdd"), false, 4) | |
| \tdraw_line(frame.get_center() - Vector2(12, 0), frame.get_center() + Vector2(12, 0), Color.WHITE, 1) | |
| \tdraw_line(frame.get_center() - Vector2(0, 12), frame.get_center() + Vector2(0, 12), Color.WHITE, 1) | |
| \ttext_at("Framed %d/3 Photos %d Time %.0f" % [framed(), shots, maxf(0, remaining)], Vector2(50, 615), 24) | |
| \tif state != 0: | |
| \t\ttext_at("PERFECT SHOT! R: again" if state == 1 else "MISSED THE MOMENT. R: retry", Vector2(250, 115), 26) | |
| \tif flash > 0: | |
| \t\tdraw_rect(Rect2(40, 130, 880, 440), Color(1, 1, 1, flash * 3)) | |
| '''), | |
| 36: ("Ant Delivery", '''extends Node2D | |
| const ORIGIN = Vector2(210, 150) | |
| const STEP = 54 | |
| const NEST = Vector2i(1, 5) | |
| const FOOD = Vector2i(8, 1) | |
| const DIRS = [Vector2i.RIGHT, Vector2i.DOWN, Vector2i.LEFT, Vector2i.UP] | |
| var arrows: Dictionary = {} | |
| var ant = NEST | |
| var direction = Vector2i.RIGHT | |
| var route: Array[Vector2i] = [] | |
| var returning = false | |
| var return_index = 0 | |
| var delivered = 0 | |
| var timer = 0.0 | |
| var running = false | |
| var state = 0 | |
| func _ready() -> void: | |
| \treset() | |
| func reset() -> void: | |
| \tarrows.clear() | |
| \tdelivered = 0 | |
| \trunning = false | |
| \tstate = 0 | |
| \tant = NEST | |
| \troute.clear() | |
| func blocked(cell: Vector2i) -> bool: | |
| \treturn cell.x < 0 or cell.x >= 10 or cell.y < 0 or cell.y >= 7 or (cell.x == 5 and cell.y >= 2 and cell.y <= 4) | |
| func launch() -> void: | |
| \tant = NEST | |
| \tdirection = Vector2i.RIGHT | |
| \troute = [NEST] | |
| \treturning = false | |
| \trunning = true | |
| \ttimer = 0.2 | |
| func _unhandled_input(event: InputEvent) -> void: | |
| \tif event is InputEventKey and event.pressed and not event.echo: | |
| \t\tif event.keycode == KEY_R: | |
| \t\t\treset() | |
| \t\telif event.keycode == KEY_SPACE and not running and state == 0: | |
| \t\t\tlaunch() | |
| \tif event is InputEventMouseButton and event.pressed and not running and state == 0: | |
| \t\tvar local = event.position - ORIGIN | |
| \t\tif not Rect2(Vector2.ZERO, Vector2(540, 378)).has_point(local): | |
| \t\t\treturn | |
| \t\tvar cell = Vector2i(local / STEP) | |
| \t\tif blocked(cell) or cell == NEST or cell == FOOD: | |
| \t\t\treturn | |
| \t\tif event.button_index == MOUSE_BUTTON_RIGHT: | |
| \t\t\tarrows.erase(cell) | |
| \t\telif event.button_index == MOUSE_BUTTON_LEFT: | |
| \t\t\tif arrows.has(cell): | |
| \t\t\t\tarrows[cell] = (int(arrows[cell]) + 1) % 4 | |
| \t\t\telif arrows.size() < 6: | |
| \t\t\t\tarrows[cell] = 0 | |
| func walk() -> void: | |
| \tif returning: | |
| \t\tant = route[return_index] | |
| \t\treturn_index -= 1 | |
| \t\tif ant == NEST: | |
| \t\t\tdelivered += 1 | |
| \t\t\tif delivered == 3: | |
| \t\t\t\tstate = 1 | |
| \t\t\t\trunning = false | |
| \t\t\telse: | |
| \t\t\t\tlaunch() | |
| \t\treturn | |
| \tif arrows.has(ant): | |
| \t\tdirection = DIRS[int(arrows[ant])] | |
| \tvar next = ant + direction | |
| \tif blocked(next) or route.size() >= 60: | |
| \t\tstate = -1 | |
| \t\trunning = false | |
| \t\treturn | |
| \tant = next | |
| \troute.append(ant) | |
| \tif ant == FOOD: | |
| \t\treturning = true | |
| \t\treturn_index = route.size() - 2 | |
| func _process(dt: float) -> void: | |
| \tif running: | |
| \t\ttimer -= dt | |
| \t\tif timer <= 0: | |
| \t\t\ttimer += 0.18 | |
| \t\t\twalk() | |
| \tqueue_redraw() | |
| func _draw() -> void: | |
| \tbackdrop("ANT DELIVERY", "Click: place/rotate arrows. Right click: erase. Space: send ants. R: restart") | |
| \tfor y in range(7): | |
| \t\tfor x in range(10): | |
| \t\t\tvar cell = Vector2i(x, y) | |
| \t\t\tvar p = ORIGIN + Vector2(cell) * STEP | |
| \t\t\tdraw_rect(Rect2(p, Vector2(51, 51)), Color("52617a") if blocked(cell) else Color("21304c")) | |
| \tfor cell in arrows: | |
| \t\tvar p = ORIGIN + Vector2(cell) * STEP + Vector2(26, 26) | |
| \t\tvar d = Vector2(DIRS[int(arrows[cell])]) | |
| \t\tdraw_line(p - d * 16, p + d * 15, Color("71e6cd"), 4) | |
| \t\tdraw_line(p + d * 15, p + d * 5 + d.orthogonal() * 9, Color("71e6cd"), 4) | |
| \t\tdraw_line(p + d * 15, p + d * 5 - d.orthogonal() * 9, Color("71e6cd"), 4) | |
| \tdraw_circle(ORIGIN + Vector2(NEST) * STEP + Vector2(26, 26), 21, Color("9c735a")) | |
| \tdraw_circle(ORIGIN + Vector2(FOOD) * STEP + Vector2(26, 26), 16, Color("e5c15d")) | |
| \tvar a = ORIGIN + Vector2(ant) * STEP + Vector2(26, 26) | |
| \tdraw_circle(a, 10, Color("e5868d")) | |
| \tdraw_circle(a - Vector2(9, 0), 7, Color("e5868d")) | |
| \tif returning: | |
| \t\tdraw_circle(a + Vector2(0, -10), 6, Color("e5c15d")) | |
| \ttext_at("Arrows %d/6 Food delivered %d/3 Nest: brown / Food: gold" % [arrows.size(), delivered], Vector2(150, 580), 21) | |
| \tif state != 0: | |
| \t\ttext_at("COLONY FED! R: again" if state == 1 else "TRAIL FAILED. R: rebuild", Vector2(300, 620), 26) | |
| '''), | |
| 37: ("Shape Fishing", '''extends Node2D | |
| const COLORS = [Color("6ce6c5"), Color("f5bb67"), Color("c39afb")] | |
| const NAMES = ["CIRCLE", "SQUARE", "TRIANGLE"] | |
| var fish: Array[Vector2] = [] | |
| var kinds: Array[int] = [] | |
| var speeds: Array[float] = [] | |
| var held = -1 | |
| var target = 0 | |
| var score = 0 | |
| var mistakes = 0 | |
| var state = 0 | |
| var clock = 0.0 | |
| var net = Vector2(400, 300) | |
| func _ready() -> void: | |
| \treset() | |
| func reset() -> void: | |
| \tfish.clear() | |
| \tkinds.clear() | |
| \tspeeds.clear() | |
| \tfor i in range(9): | |
| \t\tfish.append(Vector2(100 + (i * 149) % 620, 190 + (i * 67) % 330)) | |
| \t\tkinds.append(i % 3) | |
| \t\tspeeds.append(55.0 + i * 5.0) | |
| \theld = -1 | |
| \ttarget = 0 | |
| \tscore = 0 | |
| \tmistakes = 0 | |
| \tstate = 0 | |
| \tclock = 0.0 | |
| func _unhandled_input(event: InputEvent) -> void: | |
| \tif event is InputEventKey and event.pressed and not event.echo and event.keycode == KEY_R: | |
| \t\treset() | |
| \tif state != 0: | |
| \t\treturn | |
| \tif event is InputEventMouseButton and event.pressed: | |
| \t\tif event.button_index == MOUSE_BUTTON_RIGHT and held >= 0: | |
| \t\t\tfish[held] = net.clamp(Vector2(65, 160), Vector2(770, 550)) | |
| \t\t\theld = -1 | |
| \t\telif event.button_index == MOUSE_BUTTON_LEFT and held == -1: | |
| \t\t\tfor i in range(fish.size()): | |
| \t\t\t\tif fish[i].distance_to(net) < 35: | |
| \t\t\t\t\theld = i | |
| \t\t\t\t\tbreak | |
| func _process(dt: float) -> void: | |
| \tnet = get_global_mouse_position().clamp(Vector2(45, 145), Vector2(915, 560)) | |
| \tif state == 0: | |
| \t\tclock += dt | |
| \t\tfor i in range(fish.size()): | |
| \t\t\tif i == held: | |
| \t\t\t\tfish[i] = net | |
| \t\t\telse: | |
| \t\t\t\tfish[i].x += speeds[i] * dt | |
| \t\t\t\tfish[i].y = clampf(fish[i].y + sin(clock + i) * dt * 13, 165, 540) | |
| \t\t\t\tif fish[i].x < 65 or fish[i].x > 770: | |
| \t\t\t\t\tfish[i].x = clampf(fish[i].x, 65, 770) | |
| \t\t\t\t\tspeeds[i] *= -1 | |
| \t\tif held >= 0 and net.x > 805: | |
| \t\t\tif kinds[held] == target: | |
| \t\t\t\tscore += 1 | |
| \t\t\t\ttarget = (target + 1) % 3 | |
| \t\t\telse: | |
| \t\t\t\tmistakes += 1 | |
| \t\t\tfish[held] = Vector2(90, 190 + (held * 67) % 330) | |
| \t\t\theld = -1 | |
| \t\t\tif score >= 8: | |
| \t\t\t\tstate = 1 | |
| \t\t\telif mistakes >= 3: | |
| \t\t\t\tstate = -1 | |
| \tqueue_redraw() | |
| func _draw() -> void: | |
| \tbackdrop("SHAPE FISHING", "Mouse: net / Left click: catch / Right click: release / Carry correct fish to shore / R: reset") | |
| \tdraw_rect(Rect2(40, 140, 760, 430), Color("183e61")) | |
| \tdraw_rect(Rect2(800, 140, 125, 430), Color("806648")) | |
| \ttext_at("SHORE", Vector2(814, 180), 22) | |
| \tfor i in range(fish.size()): | |
| \t\tvar p = fish[i] | |
| \t\tdraw_circle(p, 20, Color("80a6c8")) | |
| \t\tdraw_colored_polygon(PackedVector2Array([p + Vector2(-17, 0), p + Vector2(-33, -13), p + Vector2(-33, 13)]), Color("80a6c8")) | |
| \t\tif kinds[i] == 0: | |
| \t\t\tdraw_circle(p, 10, COLORS[0]) | |
| \t\telif kinds[i] == 1: | |
| \t\t\tdraw_rect(Rect2(p - Vector2(10, 10), Vector2(20, 20)), COLORS[1]) | |
| \t\telse: | |
| \t\t\tdraw_colored_polygon(PackedVector2Array([p + Vector2(0, -12), p + Vector2(12, 10), p + Vector2(-12, 10)]), COLORS[2]) | |
| \tdraw_arc(net, 34, 0, TAU, 40, Color.WHITE, 3) | |
| \ttext_at("Requested: %s Delivered %d/8 Wrong catches %d/3" % [NAMES[target], score, mistakes], Vector2(45, 614), 23) | |
| \tif state != 0: | |
| \t\ttext_at("ORDER COMPLETE! R: again" if state == 1 else "TOO MANY WRONG FISH! R: retry", Vector2(245, 116), 26) | |
| '''), | |
| 38: ("Chromatic Gate", '''extends Node2D | |
| const COLORS = [Color("f58187"), Color("6be0be"), Color("7ab5ff")] | |
| var ball = Vector2(150, 290) | |
| var velocity = Vector2(230, 130) | |
| var gate_y = 350.0 | |
| var gate_color = 0 | |
| var ball_color = 0 | |
| var passes = 0 | |
| var mistakes = 0 | |
| var state = 0 | |
| var time_left = 60.0 | |
| func _ready() -> void: | |
| \treset() | |
| func reset() -> void: | |
| \tball = Vector2(150, 290) | |
| \tvelocity = Vector2(230, 130) | |
| \tgate_y = 350 | |
| \tgate_color = 0 | |
| \tball_color = 0 | |
| \tpasses = 0 | |
| \tmistakes = 0 | |
| \tstate = 0 | |
| \ttime_left = 60 | |
| func _unhandled_input(event: InputEvent) -> void: | |
| \tif event is InputEventKey and event.pressed and not event.echo: | |
| \t\tif event.keycode == KEY_R: | |
| \t\t\treset() | |
| \t\telif event.keycode == KEY_SPACE and state == 0: | |
| \t\t\tgate_color = (gate_color + 1) % 3 | |
| func _process(dt: float) -> void: | |
| \tif state == 0: | |
| \t\tgate_y = clampf(gate_y + Input.get_axis("ui_up", "ui_down") * 280 * dt, 220, 500) | |
| \t\ttime_left -= dt | |
| \t\tvar old_x = ball.x | |
| \t\tball += velocity * dt | |
| \t\tif ball.y < 164 or ball.y > 556: | |
| \t\t\tball.y = clampf(ball.y, 164, 556) | |
| \t\t\tvelocity.y *= -1 | |
| \t\tif ball.x < 74 or ball.x > 886: | |
| \t\t\tball.x = clampf(ball.x, 74, 886) | |
| \t\t\tvelocity.x *= -1 | |
| \t\tif (old_x < 480 and ball.x >= 480) or (old_x > 480 and ball.x <= 480): | |
| \t\t\tif absf(ball.y - gate_y) < 54 and ball_color == gate_color: | |
| \t\t\t\tpasses += 1 | |
| \t\t\t\tball_color = (ball_color + 1) % 3 | |
| \t\t\telse: | |
| \t\t\t\tmistakes += 1 | |
| \t\t\t\tball.x = old_x | |
| \t\t\t\tvelocity.x *= -1 | |
| \t\tif passes >= 6: | |
| \t\t\tstate = 1 | |
| \t\telif mistakes >= 5 or time_left <= 0: | |
| \t\t\tstate = -1 | |
| \tqueue_redraw() | |
| func _draw() -> void: | |
| \tbackdrop("CHROMATIC GATE", "Up/down: move opening. Space: cycle gate color. Match the ball and opening. R: restart") | |
| \tdraw_rect(Rect2(60, 150, 840, 420), Color("14243b")) | |
| \tdraw_rect(Rect2(469, 150, 22, gate_y - 220), Color("52627c")) | |
| \tdraw_rect(Rect2(469, gate_y + 70, 22, 500 - gate_y), Color("52627c")) | |
| \tdraw_rect(Rect2(459, gate_y - 70, 42, 140), COLORS[gate_color], false, 4) | |
| \ttext_at(str(gate_color + 1), Vector2(515, gate_y + 8), 24, COLORS[gate_color]) | |
| \tdraw_circle(ball, 13, COLORS[ball_color]) | |
| \ttext_at(str(ball_color + 1), ball + Vector2(-6, 6), 17, Color("111a2b")) | |
| \ttext_at("Passes %d/6 Misses %d/5 Time %.0f" % [passes, mistakes, maxf(0, time_left)], Vector2(60, 615), 25) | |
| \tif state != 0: | |
| \t\ttext_at("GATES CLEARED! R: again" if state == 1 else "GATE CLOSED. R: retry", Vector2(280, 116), 27) | |
| '''), | |
| 39: ("Twin Lamp Circuit", '''extends Node2D | |
| const ORIGIN = Vector2(260, 160) | |
| const DIRS = [Vector2i.UP, Vector2i.RIGHT, Vector2i.DOWN, Vector2i.LEFT] | |
| var tiles: Array[int] = [] | |
| var powered: Array[bool] = [] | |
| var turns = 0 | |
| var state = 0 | |
| func _ready() -> void: | |
| \treset() | |
| func rotate_mask(value: int) -> int: | |
| \treturn ((value << 1) & 15) | (value >> 3) | |
| func direction(a: Vector2i, b: Vector2i) -> int: | |
| \tfor i in range(4): | |
| \t\tif b - a == DIRS[i]: | |
| \t\t\treturn i | |
| \treturn 0 | |
| func reset() -> void: | |
| \ttiles.clear() | |
| \tturns = 0 | |
| \tstate = 0 | |
| \tfor i in range(30): | |
| \t\ttiles.append(3 if i % 3 == 0 else 10) | |
| \tvar path: Array[Vector2i] = [Vector2i(0, 2), Vector2i(1, 2), Vector2i(1, 1), Vector2i(2, 1), Vector2i(3, 1), Vector2i(4, 1), Vector2i(4, 2), Vector2i(4, 3), Vector2i(5, 3)] | |
| \tfor i in range(path.size()): | |
| \t\tvar entering = 3 if i == 0 else direction(path[i], path[i - 1]) | |
| \t\tvar leaving = 1 if i == path.size() - 1 else direction(path[i], path[i + 1]) | |
| \t\ttiles[path[i].y * 6 + path[i].x] = (1 << entering) | (1 << leaving) | |
| \tfor i in range(30): | |
| \t\tfor spin in range((i * 5 + 3) % 4): | |
| \t\t\ttiles[i] = rotate_mask(tiles[i]) | |
| \tupdate_power() | |
| func update_power() -> void: | |
| \tpowered.clear() | |
| \tfor i in range(30): | |
| \t\tpowered.append(false) | |
| \tvar queue: Array[int] = [] | |
| \tif (tiles[12] & 8) != 0: | |
| \t\tpowered[12] = true | |
| \t\tqueue.append(12) | |
| \tvar cursor = 0 | |
| \twhile cursor < queue.size(): | |
| \t\tvar current = queue[cursor] | |
| \t\tcursor += 1 | |
| \t\tvar cell = Vector2i(current % 6, current / 6) | |
| \t\tfor d in range(4): | |
| \t\t\tvar next: Vector2i = cell + DIRS[d] | |
| \t\t\tif next.x < 0 or next.x >= 6 or next.y < 0 or next.y >= 5: | |
| \t\t\t\tcontinue | |
| \t\t\tvar index = next.y * 6 + next.x | |
| \t\t\tif (tiles[current] & (1 << d)) != 0 and (tiles[index] & (1 << ((d + 2) % 4))) != 0 and not powered[index]: | |
| \t\t\t\tpowered[index] = true | |
| \t\t\t\tqueue.append(index) | |
| \tif powered[9] and powered[23]: | |
| \t\tstate = 1 | |
| \telif turns >= 50: | |
| \t\tstate = -1 | |
| func _unhandled_input(event: InputEvent) -> void: | |
| \tif event is InputEventKey and event.pressed and not event.echo and event.keycode == KEY_R: | |
| \t\treset() | |
| \tif event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT and state == 0: | |
| \t\tvar local = event.position - ORIGIN | |
| \t\tif Rect2(Vector2.ZERO, Vector2(420, 350)).has_point(local): | |
| \t\t\tvar cell = Vector2i(local / 70) | |
| \t\t\tvar index = cell.y * 6 + cell.x | |
| \t\t\ttiles[index] = rotate_mask(tiles[index]) | |
| \t\t\tturns += 1 | |
| \t\t\tupdate_power() | |
| func _process(_dt: float) -> void: | |
| \tqueue_redraw() | |
| func _draw() -> void: | |
| \tbackdrop("TWIN LAMP CIRCUIT", "Click to rotate a wire tile. Connect the left battery to BOTH lamps. R: restart") | |
| \tdraw_rect(Rect2(182, 314, 52, 43), Color("66dab6")) | |
| \tdraw_line(Vector2(234, 335), Vector2(260, 335), Color("ffe08a"), 6) | |
| \ttext_at("+", Vector2(198, 345), 30, Color("132438")) | |
| \tfor i in range(30): | |
| \t\tvar p = ORIGIN + Vector2(i % 6, int(i / 6)) * 70 | |
| \t\tdraw_rect(Rect2(p, Vector2(67, 67)), Color("263550")) | |
| \t\tvar color = Color("ffe08a") if powered[i] else Color("7e8aa5") | |
| \t\tvar center = p + Vector2(35, 35) | |
| \t\tfor d in range(4): | |
| \t\t\tif (tiles[i] & (1 << d)) != 0: | |
| \t\t\t\tdraw_line(center, center + Vector2(DIRS[d]) * 35, color, 7) | |
| \t\tdraw_circle(center, 7, color) | |
| \t\tif i == 9 or i == 23: | |
| \t\t\tdraw_circle(center, 17, color) | |
| \t\t\tdraw_arc(center, 21, 0, TAU, 24, Color.WHITE, 2) | |
| \ttext_at("Rotations %d/50 Lamps %d/2" % [turns, int(powered[9]) + int(powered[23])], Vector2(265, 564), 24) | |
| \tif state != 0: | |
| \t\ttext_at("BOTH LAMPS LIT! R: again" if state == 1 else "OUT OF ROTATIONS. R: retry", Vector2(280, 612), 25) | |
| '''), | |
| 40: ("Mailbox Compass", '''extends Node2D | |
| const CENTER = Vector2(480, 360) | |
| var selected = 0 | |
| var target = 3 | |
| var score = 0 | |
| var errors = 0 | |
| var state = 0 | |
| var time_left = 60.0 | |
| var flight = -1.0 | |
| var destination = 0 | |
| func _ready() -> void: | |
| \treset() | |
| func reset() -> void: | |
| \tselected = 0 | |
| \ttarget = 3 | |
| \tscore = 0 | |
| \terrors = 0 | |
| \tstate = 0 | |
| \ttime_left = 60 | |
| \tflight = -1 | |
| func slot(index: int) -> Vector2: | |
| \tvar angle = float(index) * TAU / 8.0 - PI / 2.0 | |
| \treturn CENTER + Vector2(cos(angle), sin(angle)) * 205 | |
| func _unhandled_input(event: InputEvent) -> void: | |
| \tif event is InputEventKey and event.pressed and not event.echo: | |
| \t\tif event.keycode == KEY_R: | |
| \t\t\treset() | |
| \t\telif event.keycode == KEY_SPACE and state == 0 and flight < 0: | |
| \t\t\tdestination = selected | |
| \t\t\tflight = 0 | |
| func _process(dt: float) -> void: | |
| \tif state == 0: | |
| \t\ttime_left -= dt | |
| \t\tif Input.is_action_just_pressed("ui_left"): | |
| \t\t\tselected = posmod(selected - 1, 8) | |
| \t\tif Input.is_action_just_pressed("ui_right"): | |
| \t\t\tselected = (selected + 1) % 8 | |
| \t\tif flight >= 0: | |
| \t\t\tflight += dt * 2.2 | |
| \t\t\tif flight >= 1: | |
| \t\t\t\tflight = -1 | |
| \t\t\t\tif destination == target: | |
| \t\t\t\t\tscore += 1 | |
| \t\t\t\t\ttarget = (target + 3 + score % 3) % 8 | |
| \t\t\t\telse: | |
| \t\t\t\t\terrors += 1 | |
| \t\tif score >= 12: | |
| \t\t\tstate = 1 | |
| \t\telif errors >= 3 or time_left <= 0: | |
| \t\t\tstate = -1 | |
| \tqueue_redraw() | |
| func _draw() -> void: | |
| \tbackdrop("MAILBOX COMPASS", "Left/right: rotate arrow. Space: send the letter to its destination number. R: restart") | |
| \tdraw_arc(CENTER, 205, 0, TAU, 64, Color("324763"), 2) | |
| \tfor i in range(8): | |
| \t\tvar p = slot(i) | |
| \t\tdraw_rect(Rect2(p - Vector2(28, 26), Vector2(56, 52)), Color("7bd9bd") if i == selected else Color("344a6a")) | |
| \t\ttext_at(str(i + 1), p + Vector2(-9, 9), 28, Color("13263b") if i == selected else Color.WHITE) | |
| \tvar direction = (slot(selected) - CENTER).normalized() | |
| \tdraw_circle(CENTER, 47, Color("283c57")) | |
| \tdraw_line(CENTER, CENTER + direction * 90, Color("ffc870"), 7) | |
| \tdraw_colored_polygon(PackedVector2Array([CENTER + direction * 106, CENTER + direction * 79 + direction.orthogonal() * 13, CENTER + direction * 79 - direction.orthogonal() * 13]), Color("ffc870")) | |
| \ttext_at("TO: %d" % [target + 1], CENTER + Vector2(-34, 8), 22) | |
| \tif flight >= 0: | |
| \t\tvar letter = CENTER.lerp(slot(destination), flight) | |
| \t\tdraw_rect(Rect2(letter - Vector2(13, 9), Vector2(26, 18)), Color.WHITE) | |
| \ttext_at("Delivered %d/12 Wrong %d/3 Time %.0f" % [score, errors, maxf(0, time_left)], Vector2(245, 620), 24) | |
| \tif state != 0: | |
| \t\ttext_at("MAIL RUN COMPLETE! R: again" if state == 1 else "DELIVERY FAILED. R: retry", Vector2(280, 118), 25) | |
| '''), | |
| 41: ("Gem Excavator", '''extends Node2D | |
| const W = 12 | |
| const H = 9 | |
| const ORIGIN = Vector2(240, 150) | |
| const EXIT = Vector2i(10, 1) | |
| var board: Array[int] = [] | |
| var player = Vector2i(1, 1) | |
| var gems = 0 | |
| var state = 0 | |
| var move_timer = 0.0 | |
| var rock_timer = 0.0 | |
| func _ready() -> void: | |
| \treset() | |
| func reset() -> void: | |
| \tboard.clear() | |
| \tfor y in range(H): | |
| \t\tfor x in range(W): | |
| \t\t\tboard.append(4 if x == 0 or y == 0 or x == W - 1 or y == H - 1 else 1) | |
| \tfor cell in [Vector2i(3, 2), Vector2i(8, 2), Vector2i(4, 5), Vector2i(8, 6), Vector2i(2, 7), Vector2i(10, 7)]: | |
| \t\tboard[cell.y * W + cell.x] = 3 | |
| \tfor cell in [Vector2i(4, 2), Vector2i(6, 3), Vector2i(9, 4), Vector2i(5, 6)]: | |
| \t\tboard[cell.y * W + cell.x] = 2 | |
| \tplayer = Vector2i(1, 1) | |
| \tboard[player.y * W + player.x] = 0 | |
| \tboard[EXIT.y * W + EXIT.x] = 0 | |
| \tgems = 0 | |
| \tstate = 0 | |
| \tmove_timer = 0 | |
| \trock_timer = 0 | |
| func _unhandled_input(event: InputEvent) -> void: | |
| \tif event is InputEventKey and event.pressed and not event.echo and event.keycode == KEY_R: | |
| \t\treset() | |
| func move_player(direction: Vector2i) -> void: | |
| \tvar next = player + direction | |
| \tif next.x < 0 or next.x >= W or next.y < 0 or next.y >= H: | |
| \t\treturn | |
| \tvar index = next.y * W + next.x | |
| \tif board[index] == 2 or board[index] == 4: | |
| \t\treturn | |
| \tif board[index] == 3: | |
| \t\tgems += 1 | |
| \tboard[index] = 0 | |
| \tplayer = next | |
| \tif gems == 6 and player == EXIT: | |
| \t\tstate = 1 | |
| func fall_rocks() -> void: | |
| \tfor y in range(H - 2, 0, -1): | |
| \t\tfor x in range(1, W - 1): | |
| \t\t\tvar index = y * W + x | |
| \t\t\tif board[index] == 2 and board[index + W] == 0: | |
| \t\t\t\tboard[index] = 0 | |
| \t\t\t\tboard[index + W] = 2 | |
| \t\t\t\tif player == Vector2i(x, y + 1): | |
| \t\t\t\t\tstate = -1 | |
| func _process(dt: float) -> void: | |
| \tif state == 0: | |
| \t\tmove_timer -= dt | |
| \t\trock_timer -= dt | |
| \t\tif move_timer <= 0: | |
| \t\t\tvar direction = Vector2i.ZERO | |
| \t\t\tif Input.is_action_pressed("ui_left"): | |
| \t\t\t\tdirection = Vector2i.LEFT | |
| \t\t\telif Input.is_action_pressed("ui_right"): | |
| \t\t\t\tdirection = Vector2i.RIGHT | |
| \t\t\telif Input.is_action_pressed("ui_up"): | |
| \t\t\t\tdirection = Vector2i.UP | |
| \t\t\telif Input.is_action_pressed("ui_down"): | |
| \t\t\t\tdirection = Vector2i.DOWN | |
| \t\t\tif direction != Vector2i.ZERO: | |
| \t\t\t\tmove_player(direction) | |
| \t\t\t\tmove_timer = 0.12 | |
| \t\tif rock_timer <= 0 and state == 0: | |
| \t\t\trock_timer = 0.18 | |
| \t\t\tfall_rocks() | |
| \tqueue_redraw() | |
| func _draw() -> void: | |
| \tbackdrop("GEM EXCAVATOR", "Arrows: move and dig. Collect all gems, then return to the exit. Beware falling rocks. R: restart") | |
| \tfor y in range(H): | |
| \t\tfor x in range(W): | |
| \t\t\tvar p = ORIGIN + Vector2(x, y) * 40 | |
| \t\t\tvar tile = board[y * W + x] | |
| \t\t\tdraw_rect(Rect2(p, Vector2(38, 38)), Color("76513b") if tile == 1 else Color("1b2940")) | |
| \t\t\tif tile == 4: | |
| \t\t\t\tdraw_rect(Rect2(p, Vector2(38, 38)), Color("58637a")) | |
| \t\t\telif tile == 2: | |
| \t\t\t\tdraw_circle(p + Vector2(19, 19), 17, Color("a4a9b6")) | |
| \t\t\telif tile == 3: | |
| \t\t\t\tdraw_colored_polygon(PackedVector2Array([p + Vector2(19, 5), p + Vector2(33, 19), p + Vector2(19, 33), p + Vector2(5, 19)]), Color("66efd0")) | |
| \tdraw_rect(Rect2(ORIGIN + Vector2(EXIT) * 40 + Vector2(5, 5), Vector2(29, 29)), Color("ffe18c"), false, 3) | |
| \tdraw_circle(ORIGIN + Vector2(player) * 40 + Vector2(19, 19), 13, Color("ff9397")) | |
| \ttext_at("Gems %d/6 Exit: gold frame" % gems, Vector2(270, 567), 26) | |
| \tif state != 0: | |
| \t\ttext_at("ALL GEMS RECOVERED! R: again" if state == 1 else "CRUSHED! R: retry", Vector2(270, 612), 26) | |
| '''), | |
| 42: ("Pendulum Reach", '''extends Node2D | |
| const PIVOT = Vector2(480, 130) | |
| var rope = 250.0 | |
| var angle = 1.0 | |
| var angular_velocity = 0.0 | |
| var ball = Vector2.ZERO | |
| var targets: Array[Vector2] = [Vector2(680, 300), Vector2(315, 330), Vector2(510, 500)] | |
| var hit: Array[bool] = [false, false, false] | |
| var remaining = 60.0 | |
| var state = 0 | |
| func _ready() -> void: | |
| \treset() | |
| func reset() -> void: | |
| \trope = 250.0 | |
| \tangle = 1.0 | |
| \tangular_velocity = 0.0 | |
| \thit = [false, false, false] | |
| \tremaining = 60 | |
| \tstate = 0 | |
| \tball = PIVOT + Vector2(sin(angle), cos(angle)) * rope | |
| func _unhandled_input(event: InputEvent) -> void: | |
| \tif event is InputEventKey and event.pressed and not event.echo and event.keycode == KEY_R: | |
| \t\treset() | |
| func hits() -> int: | |
| \treturn int(hit[0]) + int(hit[1]) + int(hit[2]) | |
| func _process(dt: float) -> void: | |
| \tif state == 0: | |
| \t\tremaining -= dt | |
| \t\tvar old_length = rope | |
| \t\trope = clampf(rope + Input.get_axis("ui_up", "ui_down") * 150 * dt, 120, 460) | |
| \t\tangular_velocity *= old_length * old_length / (rope * rope) | |
| \t\tangular_velocity += -520.0 / rope * sin(angle) * dt | |
| \t\tangular_velocity *= pow(0.999, dt * 60) | |
| \t\tangle += angular_velocity * dt | |
| \t\tball = PIVOT + Vector2(sin(angle), cos(angle)) * rope | |
| \t\tfor i in range(3): | |
| \t\t\tif ball.distance_to(targets[i]) < 28: | |
| \t\t\t\thit[i] = true | |
| \t\tif hits() == 3: | |
| \t\t\tstate = 1 | |
| \t\telif ball.y >= 566 or remaining <= 0: | |
| \t\t\tstate = -1 | |
| \tqueue_redraw() | |
| func _draw() -> void: | |
| \tbackdrop("PENDULUM REACH", "Hold up/down: shorten/lengthen the rope. Touch all targets. Avoid the spikes. R: restart") | |
| \tfor i in range(3): | |
| \t\tdraw_arc(targets[i], 22, 0, TAU, 32, Color("66eacb") if hit[i] else Color("ffc76f"), 4) | |
| \t\ttext_at(str(i + 1), targets[i] + Vector2(-7, 8), 22) | |
| \tdraw_line(PIVOT, ball, Color("a2b4cc"), 3) | |
| \tdraw_circle(PIVOT, 9, Color.WHITE) | |
| \tdraw_circle(ball, 14, Color("f38f98")) | |
| \tfor x in range(30, 930, 30): | |
| \t\tdraw_colored_polygon(PackedVector2Array([Vector2(x, 605), Vector2(x + 15, 580), Vector2(x + 30, 605)]), Color("db686f")) | |
| \ttext_at("Targets %d/3 Rope %.0f Time %.0f" % [hits(), rope, maxf(0, remaining)], Vector2(230, 632), 24) | |
| \tif state != 0: | |
| \t\ttext_at("ALL TARGETS HIT! R: again" if state == 1 else "SWING FAILED. R: retry", Vector2(275, 113), 26) | |
| '''), | |
| 43: ("Midnight Patrol", '''extends Node2D | |
| const LAMP = Vector2(480, 345) | |
| var player = Vector2(135, 500) | |
| var checkpoints: Array[Vector2] = [Vector2(820, 185), Vector2(145, 185), Vector2(820, 515), Vector2(485, 345)] | |
| var next_checkpoint = 0 | |
| var visibility = 100.0 | |
| var elapsed = 0.0 | |
| var remaining = 75.0 | |
| var in_light = false | |
| var state = 0 | |
| func _ready() -> void: | |
| \treset() | |
| func reset() -> void: | |
| \tplayer = Vector2(135, 500) | |
| \tnext_checkpoint = 0 | |
| \tvisibility = 100 | |
| \telapsed = 0 | |
| \tremaining = 75 | |
| \tin_light = false | |
| \tstate = 0 | |
| func _unhandled_input(event: InputEvent) -> void: | |
| \tif event is InputEventKey and event.pressed and not event.echo and event.keycode == KEY_R: | |
| \t\treset() | |
| func _process(dt: float) -> void: | |
| \tif state == 0: | |
| \t\telapsed += dt | |
| \t\tremaining -= dt | |
| \t\tplayer += Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down") * 240 * dt | |
| \t\tplayer = player.clamp(Vector2(85, 155), Vector2(875, 545)) | |
| \t\tvar difference = player - LAMP | |
| \t\tin_light = difference.length() < 440 and absf(wrapf(difference.angle() - elapsed * 0.72, -PI, PI)) < 0.34 | |
| \t\tvisibility = clampf(visibility + (-30.0 if in_light else 8.0) * dt, 0, 100) | |
| \t\tif player.distance_to(checkpoints[next_checkpoint]) < 25: | |
| \t\t\tnext_checkpoint += 1 | |
| \t\t\tif next_checkpoint == 4: | |
| \t\t\t\tstate = 1 | |
| \t\tif visibility <= 0 or remaining <= 0: | |
| \t\t\tstate = -1 | |
| \tqueue_redraw() | |
| func _draw() -> void: | |
| \tbackdrop("MIDNIGHT PATROL", "Arrows: move. Visit checkpoints 1→4 in order. Stay out of the sweeping light. R: restart") | |
| \tdraw_rect(Rect2(70, 140, 820, 420), Color("172842")) | |
| \tfor x in range(100, 900, 80): | |
| \t\tdraw_line(Vector2(x, 145), Vector2(x, 555), Color("20334e"), 1) | |
| \tvar cone = PackedVector2Array([LAMP]) | |
| \tfor i in range(19): | |
| \t\tvar a = elapsed * 0.72 - 0.34 + float(i) * 0.68 / 18 | |
| \t\tcone.append(LAMP + Vector2(cos(a), sin(a)) * 440) | |
| \tdraw_colored_polygon(cone, Color(1.0, 0.85, 0.45, 0.18)) | |
| \tdraw_circle(LAMP, 12, Color("f9d783")) | |
| \tfor i in range(4): | |
| \t\tvar color = Color("65e4c3") if i == next_checkpoint else Color("64748b") | |
| \t\tif i < next_checkpoint: | |
| \t\t\tcolor = Color("277561") | |
| \t\tdraw_arc(checkpoints[i], 24, 0, TAU, 32, color, 4) | |
| \t\ttext_at(str(i + 1), checkpoints[i] + Vector2(-7, 8), 22, color) | |
| \tdraw_circle(player, 12, Color("ff7b85") if in_light else Color("91c6ff")) | |
| \tdraw_rect(Rect2(70, 588, 300, 19), Color("34435c")) | |
| \tdraw_rect(Rect2(70, 588, visibility * 3, 19), Color("ffbd6f") if in_light else Color("65e4c3")) | |
| \ttext_at("Cover %.0f%% Checkpoints %d/4 Time %.0f" % [visibility, next_checkpoint, maxf(0, remaining)], Vector2(70, 632), 22) | |
| \tif state != 0: | |
| \t\ttext_at("PATROL COMPLETE! R: again" if state == 1 else "SPOTTED OR OUT OF TIME. R: retry", Vector2(235, 116), 25) | |
| '''), | |
| 44: ("Three Piece Fit", '''extends Node2D | |
| const BOARD = Vector2(350, 220) | |
| const CELL = 68 | |
| const TRAYS = [Vector2(80, 230), Vector2(740, 180), Vector2(735, 385)] | |
| const COLORS = [Color("70dfc2"), Color("ed939d"), Color("f1ca75")] | |
| var pieces: Array = [] | |
| var positions: Array[Vector2] = [] | |
| var placed: Array[Vector2i] = [] | |
| var dragging = -1 | |
| var offset = Vector2.ZERO | |
| var won = false | |
| func _ready() -> void: | |
| \treset() | |
| func reset() -> void: | |
| \tpieces = [[Vector2i(0, 0), Vector2i(0, 1), Vector2i(0, 2), Vector2i(1, 2)], [Vector2i(0, 0), Vector2i(1, 0), Vector2i(0, 1), Vector2i(1, 1)], [Vector2i(1, 0), Vector2i(1, 1), Vector2i(1, 2), Vector2i(0, 2)]] | |
| \tpositions = [TRAYS[0], TRAYS[1], TRAYS[2]] | |
| \tplaced = [Vector2i(-99, -99), Vector2i(-99, -99), Vector2i(-99, -99)] | |
| \tdragging = -1 | |
| \twon = false | |
| func anchor(index: int) -> Vector2i: | |
| \tvar p = (positions[index] - BOARD) / CELL | |
| \treturn Vector2i(roundi(p.x), roundi(p.y)) | |
| func fits(index: int, start: Vector2i) -> bool: | |
| \tfor cell in pieces[index]: | |
| \t\tvar target: Vector2i = start + cell | |
| \t\tif target.x < 0 or target.x >= 4 or target.y < 0 or target.y >= 3: | |
| \t\t\treturn false | |
| \t\tfor other in range(3): | |
| \t\t\tif other == index or placed[other].x == -99: | |
| \t\t\t\tcontinue | |
| \t\t\tfor other_cell in pieces[other]: | |
| \t\t\t\tif placed[other] + other_cell == target: | |
| \t\t\t\t\treturn false | |
| \treturn true | |
| func rotate_piece() -> void: | |
| \tif dragging < 0: | |
| \t\treturn | |
| \tvar rotated: Array[Vector2i] = [] | |
| \tvar minimum = Vector2i(99, 99) | |
| \tfor cell in pieces[dragging]: | |
| \t\tvar p = Vector2i(-cell.y, cell.x) | |
| \t\trotated.append(p) | |
| \t\tminimum.x = mini(minimum.x, p.x) | |
| \t\tminimum.y = mini(minimum.y, p.y) | |
| \tfor i in range(rotated.size()): | |
| \t\trotated[i] -= minimum | |
| \tpieces[dragging] = rotated | |
| func _unhandled_input(event: InputEvent) -> void: | |
| \tif event is InputEventKey and event.pressed and not event.echo: | |
| \t\tif event.keycode == KEY_R: | |
| \t\t\treset() | |
| \t\telif event.keycode == KEY_SPACE: | |
| \t\t\trotate_piece() | |
| \tif won: | |
| \t\treturn | |
| \tif event is InputEventMouseButton: | |
| \t\tif event.button_index == MOUSE_BUTTON_RIGHT and event.pressed: | |
| \t\t\trotate_piece() | |
| \t\tif event.button_index == MOUSE_BUTTON_LEFT: | |
| \t\t\tif event.pressed: | |
| \t\t\t\tfor i in range(2, -1, -1): | |
| \t\t\t\t\tfor cell in pieces[i]: | |
| \t\t\t\t\t\tif Rect2(positions[i] + Vector2(cell) * CELL, Vector2(CELL, CELL)).has_point(event.position): | |
| \t\t\t\t\t\t\tdragging = i | |
| \t\t\t\t\t\t\toffset = event.position - positions[i] | |
| \t\t\t\t\t\t\tplaced[i] = Vector2i(-99, -99) | |
| \t\t\t\t\t\t\treturn | |
| \t\t\telif dragging >= 0: | |
| \t\t\t\tvar start = anchor(dragging) | |
| \t\t\t\tif fits(dragging, start): | |
| \t\t\t\t\tplaced[dragging] = start | |
| \t\t\t\t\tpositions[dragging] = BOARD + Vector2(start) * CELL | |
| \t\t\t\telse: | |
| \t\t\t\t\tpositions[dragging] = TRAYS[dragging] | |
| \t\t\t\tdragging = -1 | |
| \t\t\t\twon = placed[0].x != -99 and placed[1].x != -99 and placed[2].x != -99 | |
| \tif event is InputEventMouseMotion and dragging >= 0: | |
| \t\tpositions[dragging] = event.position - offset | |
| func _process(_dt: float) -> void: | |
| \tqueue_redraw() | |
| func _draw() -> void: | |
| \tbackdrop("THREE PIECE FIT", "Drag pieces onto the board. Space/right click: rotate held piece. Fill every square. R: restart") | |
| \tfor y in range(3): | |
| \t\tfor x in range(4): | |
| \t\t\tdraw_rect(Rect2(BOARD + Vector2(x, y) * CELL, Vector2(CELL - 2, CELL - 2)), Color("374864"), false, 2) | |
| \tvar count = 0 | |
| \tfor i in range(3): | |
| \t\tif placed[i].x != -99: | |
| \t\t\tcount += 4 | |
| \t\tfor cell in pieces[i]: | |
| \t\t\tdraw_rect(Rect2(positions[i] + Vector2(cell) * CELL + Vector2(3, 3), Vector2(CELL - 6, CELL - 6)), COLORS[i]) | |
| \tif dragging >= 0: | |
| \t\tvar start = anchor(dragging) | |
| \t\tvar color = Color("72f2c9") if fits(dragging, start) else Color("ff6b83") | |
| \t\tfor cell in pieces[dragging]: | |
| \t\t\tdraw_rect(Rect2(BOARD + Vector2(start + cell) * CELL, Vector2(CELL - 2, CELL - 2)), color, false, 3) | |
| \ttext_at("Board filled: %d/12 squares" % count, Vector2(330, 565), 26) | |
| \tif won: | |
| \t\ttext_at("PERFECT FIT! R: play again", Vector2(330, 612), 27) | |
| '''), | |
| } | |
| GAMES.update({ | |
| 85: ("Gravity Courier", '''extends Node2D | |
| const START = Vector2(130, 350) | |
| const WELL = Vector2(480, 345) | |
| var targets: Array[Vector2] = [Vector2(785, 235), Vector2(770, 490), Vector2(730, 170)] | |
| var particle = START | |
| var velocity = Vector2.ZERO | |
| var trail: Array[Vector2] = [] | |
| var target = 0 | |
| var shots = 0 | |
| var flying = false | |
| var age = 0.0 | |
| var state = 0 | |
| func _ready() -> void: | |
| \treset() | |
| func reset() -> void: | |
| \ttarget = 0 | |
| \tshots = 0 | |
| \tstate = 0 | |
| \treturn_to_start() | |
| func return_to_start() -> void: | |
| \tparticle = START | |
| \tvelocity = Vector2.ZERO | |
| \tflying = false | |
| \ttrail.clear() | |
| \tif shots >= 9 and state == 0: | |
| \t\tstate = -1 | |
| func _unhandled_input(event: InputEvent) -> void: | |
| \tif event is InputEventKey and event.pressed and not event.echo and event.keycode == KEY_R: | |
| \t\treset() | |
| \tif event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT and state == 0 and not flying: | |
| \t\tvar aim = event.position - START | |
| \t\tif aim.length() > 20: | |
| \t\t\tvelocity = aim.normalized() * clampf(aim.length() * 1.3, 180, 680) | |
| \t\t\tshots += 1 | |
| \t\t\tif shots <= 9: | |
| \t\t\t\tflying = true | |
| \t\t\t\tage = 0 | |
| func _process(dt: float) -> void: | |
| \tif flying and state == 0: | |
| \t\tage += dt | |
| \t\tfor step in range(4): | |
| \t\t\tvar pull = WELL - particle | |
| \t\t\tvelocity += pull.normalized() * (18000.0 / maxf(pull.length(), 45)) * dt / 4 | |
| \t\t\tparticle += velocity * dt / 4 | |
| \t\ttrail.append(particle) | |
| \t\tif trail.size() > 160: | |
| \t\t\ttrail.remove_at(0) | |
| \t\tif particle.distance_to(targets[target]) < 26: | |
| \t\t\ttarget += 1 | |
| \t\t\tif target == 3: | |
| \t\t\t\tstate = 1 | |
| \t\t\telse: | |
| \t\t\t\treturn_to_start() | |
| \t\telif particle.distance_to(WELL) < 24 or not Rect2(30, 130, 900, 455).has_point(particle) or age > 6: | |
| \t\t\treturn_to_start() | |
| \tqueue_redraw() | |
| func _draw() -> void: | |
| \tbackdrop("GRAVITY COURIER", "Aim with mouse; click to launch. Longer aim = more power. Hit targets 1→3. R: restart") | |
| \tfor radius in [45, 85, 125]: | |
| \t\tdraw_arc(WELL, radius, 0, TAU, 48, Color("273e65"), 2) | |
| \tdraw_circle(WELL, 24, Color("8465c4")) | |
| \tfor i in range(3): | |
| \t\tdraw_arc(targets[i], 22, 0, TAU, 32, Color("74e9c7") if i == target else Color("4a607e"), 3) | |
| \t\ttext_at(str(i + 1), targets[i] + Vector2(-7, 8), 23) | |
| \tfor i in range(1, trail.size()): | |
| \t\tdraw_line(trail[i - 1], trail[i], Color("658cac"), 2) | |
| \tdraw_circle(START, 18, Color("3c5e77")) | |
| \tdraw_circle(particle, 7, Color("ffe08b")) | |
| \tif not flying and state == 0: | |
| \t\tdraw_line(START, START + (get_global_mouse_position() - START).limit_length(180), Color("ffe08b"), 2) | |
| \ttext_at("Targets %d/3 Shots %d/9" % [target, shots], Vector2(300, 625), 26) | |
| \tif state != 0: | |
| \t\ttext_at("DELIVERY COMPLETE! R: again" if state == 1 else "OUT OF PARTICLES. R: retry", Vector2(275, 113), 25) | |
| '''), | |
| 86: ("Warehouse Memory", '''extends Node2D | |
| var round_number = 0 | |
| var inside = 3 | |
| var starting_count = 3 | |
| var correct = 0 | |
| var phase = 0 | |
| var timer = 0.0 | |
| var event_number = 0 | |
| var delta_count = 1 | |
| var state = 0 | |
| var feedback = "" | |
| func _ready() -> void: | |
| \treset() | |
| func reset() -> void: | |
| \tround_number = 0 | |
| \tcorrect = 0 | |
| \tstate = 0 | |
| \tbegin_round() | |
| func begin_round() -> void: | |
| \tstarting_count = 2 + round_number % 3 | |
| \tinside = starting_count | |
| \tphase = 0 | |
| \ttimer = 2.0 | |
| \tevent_number = 0 | |
| \tfeedback = "" | |
| func next_event() -> void: | |
| \tdelta_count = 1 if (round_number * 3 + event_number * 5) % 7 < 4 else -1 | |
| \tif inside == 0: | |
| \t\tdelta_count = 1 | |
| \tif inside == 9: | |
| \t\tdelta_count = -1 | |
| \ttimer = 1.0 | |
| func answer(number: int) -> void: | |
| \tif phase != 2 or state != 0: | |
| \t\treturn | |
| \tif number == inside: | |
| \t\tcorrect += 1 | |
| \t\tfeedback = "CORRECT: %d crates" % inside | |
| \telse: | |
| \t\tfeedback = "There were %d crates, not %d" % [inside, number] | |
| \tphase = 3 | |
| \ttimer = 1.8 | |
| func _unhandled_input(event: InputEvent) -> void: | |
| \tif event is InputEventKey and event.pressed and not event.echo: | |
| \t\tif event.keycode == KEY_R: | |
| \t\t\treset() | |
| \t\telif event.keycode >= KEY_0 and event.keycode <= KEY_9: | |
| \t\t\tanswer(int(event.keycode) - int(KEY_0)) | |
| \tif event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT: | |
| \t\tfor i in range(10): | |
| \t\t\tif Rect2(165 + i * 64, 530, 56, 48).has_point(event.position): | |
| \t\t\t\tanswer(i) | |
| func _process(dt: float) -> void: | |
| \tif state == 0 and phase != 2: | |
| \t\ttimer -= dt | |
| \t\tif timer <= 0: | |
| \t\t\tif phase == 0: | |
| \t\t\t\tphase = 1 | |
| \t\t\t\tnext_event() | |
| \t\t\telif phase == 1: | |
| \t\t\t\tinside += delta_count | |
| \t\t\t\tevent_number += 1 | |
| \t\t\t\tif event_number == 6: | |
| \t\t\t\t\tphase = 2 | |
| \t\t\t\telse: | |
| \t\t\t\t\tnext_event() | |
| \t\t\telse: | |
| \t\t\t\tround_number += 1 | |
| \t\t\t\tif round_number == 5: | |
| \t\t\t\t\tstate = 1 if correct >= 4 else -1 | |
| \t\t\t\telse: | |
| \t\t\t\t\tbegin_round() | |
| \tqueue_redraw() | |
| func _draw() -> void: | |
| \tbackdrop("WAREHOUSE MEMORY", "Watch crates enter left and leave right. When doors close, choose 0–9. R: restart") | |
| \tdraw_rect(Rect2(260, 195, 440, 275), Color("33415a")) | |
| \tdraw_rect(Rect2(232, 305, 28, 95), Color("8fbc9e") if phase == 1 and delta_count > 0 else Color("121e30")) | |
| \tdraw_rect(Rect2(700, 305, 28, 95), Color("8fbc9e") if phase == 1 and delta_count < 0 else Color("121e30")) | |
| \tif phase == 0: | |
| \t\tfor i in range(starting_count): | |
| \t\t\tdraw_rect(Rect2(315 + i * 65, 325, 43, 43), Color("d6a467")) | |
| \t\ttext_at("Starts with %d crates" % starting_count, Vector2(340, 270), 27) | |
| \telif phase == 1: | |
| \t\tvar x = lerpf(75, 250, 1.0 - timer) if delta_count > 0 else lerpf(712, 880, 1.0 - timer) | |
| \t\tdraw_rect(Rect2(x - 20, 335, 40, 40), Color("d6a467")) | |
| \t\ttext_at("Watch the doors...", Vector2(354, 260), 26) | |
| \telif phase == 2: | |
| \t\ttext_at("How many are inside?", Vector2(335, 332), 29) | |
| \telse: | |
| \t\ttext_at(feedback, Vector2(295, 330), 24) | |
| \tfor i in range(10): | |
| \t\tdraw_rect(Rect2(165 + i * 64, 530, 56, 48), Color("3f6282")) | |
| \t\ttext_at(str(i), Vector2(185 + i * 64, 564), 27) | |
| \ttext_at("Round %d/5 Correct %d (need 4)" % [mini(round_number + 1, 5), correct], Vector2(275, 618), 25) | |
| \tif state != 0: | |
| \t\ttext_at("COUNTING CHAMPION! R: again" if state == 1 else "NEED FOUR CORRECT. R: retry", Vector2(255, 115), 25) | |
| '''), | |
| 87: ("Orbit Transfer", '''extends Node2D | |
| const PLANETS = [Vector2(185, 405), Vector2(480, 240), Vector2(785, 405)] | |
| const RADIUS = 78.0 | |
| var planet = 0 | |
| var angle = 0.0 | |
| var dot = Vector2.ZERO | |
| var velocity = Vector2.ZERO | |
| var flying = false | |
| var flight_time = 0.0 | |
| var lives = 4 | |
| var state = 0 | |
| func _ready() -> void: | |
| \treset() | |
| func reset() -> void: | |
| \tplanet = 0 | |
| \tangle = 0 | |
| \tlives = 4 | |
| \tstate = 0 | |
| \tflying = false | |
| \tdot = PLANETS[0] + Vector2(RADIUS, 0) | |
| func _unhandled_input(event: InputEvent) -> void: | |
| \tif event is InputEventKey and event.pressed and not event.echo: | |
| \t\tif event.keycode == KEY_R: | |
| \t\t\treset() | |
| \t\telif event.keycode == KEY_SPACE and state == 0 and not flying: | |
| \t\t\tflying = true | |
| \t\t\tflight_time = 0 | |
| \t\t\tvelocity = Vector2(-sin(angle), cos(angle)) * 310 | |
| func _process(dt: float) -> void: | |
| \tif state == 0: | |
| \t\tif flying: | |
| \t\t\tdot += velocity * dt | |
| \t\t\tflight_time += dt | |
| \t\t\tif dot.distance_to(PLANETS[planet + 1]) <= RADIUS + 6: | |
| \t\t\t\tplanet += 1 | |
| \t\t\t\tangle = (dot - PLANETS[planet]).angle() | |
| \t\t\t\tflying = false | |
| \t\t\t\tif planet == 2: | |
| \t\t\t\t\tstate = 1 | |
| \t\t\telif not Rect2(20, 115, 920, 495).has_point(dot) or flight_time > 4: | |
| \t\t\t\tlives -= 1 | |
| \t\t\t\tflying = false | |
| \t\t\t\tangle = 0 | |
| \t\t\t\tif lives <= 0: | |
| \t\t\t\t\tstate = -1 | |
| \t\telse: | |
| \t\t\tangle += dt * 1.25 | |
| \t\t\tdot = PLANETS[planet] + Vector2(cos(angle), sin(angle)) * RADIUS | |
| \tqueue_redraw() | |
| func _draw() -> void: | |
| \tbackdrop("ORBIT TRANSFER", "Space: launch along the tangent. Catch the next orbit, then the final one. R: restart") | |
| \tfor i in range(3): | |
| \t\tdraw_arc(PLANETS[i], RADIUS, 0, TAU, 64, Color("6fcbbb") if i <= planet else Color("526586"), 2) | |
| \t\tdraw_circle(PLANETS[i], 30, Color("5c7ab4")) | |
| \t\ttext_at(str(i + 1), PLANETS[i] + Vector2(-9, 10), 29) | |
| \tif not flying and state == 0: | |
| \t\tdraw_line(dot, dot + Vector2(-sin(angle), cos(angle)) * 75, Color("ffcf7b"), 2) | |
| \tdraw_circle(dot, 9, Color("ffe19a")) | |
| \ttext_at("Orbit %d/3 Attempts left %d" % [planet + 1, lives], Vector2(310, 620), 26) | |
| \tif state != 0: | |
| \t\ttext_at("FINAL ORBIT REACHED! R: again" if state == 1 else "LOST IN SPACE. R: retry", Vector2(270, 116), 25) | |
| '''), | |
| 88: ("Linked Locks", '''extends Node2D | |
| const ORIGIN = Vector2(170, 155) | |
| const A = Vector2i(4, 3) | |
| const B = Vector2i(7, 3) | |
| const EXIT = Vector2i(9, 1) | |
| var player = Vector2i(1, 5) | |
| var door_a = false | |
| var keys: Array[bool] = [false, false] | |
| var moves = 0 | |
| var won = false | |
| func _ready() -> void: | |
| \treset() | |
| func reset() -> void: | |
| \tplayer = Vector2i(1, 5) | |
| \tdoor_a = false | |
| \tkeys = [false, false] | |
| \tmoves = 0 | |
| \twon = false | |
| func wall(cell: Vector2i) -> bool: | |
| \tif cell.x <= 0 or cell.x >= 10 or cell.y <= 0 or cell.y >= 6: | |
| \t\treturn true | |
| \tif (cell.x == 4 or cell.x == 7) and cell.y != 3: | |
| \t\treturn true | |
| \treturn cell in [Vector2i(2, 4), Vector2i(5, 4), Vector2i(8, 2)] | |
| func step(direction: Vector2i) -> void: | |
| \tvar next = player + direction | |
| \tif wall(next) or (next == A and not door_a) or (next == B and door_a): | |
| \t\treturn | |
| \tplayer = next | |
| \tmoves += 1 | |
| \tif player == Vector2i(2, 3) or player == Vector2i(6, 5): | |
| \t\tdoor_a = true | |
| \telif player == Vector2i(5, 3): | |
| \t\tdoor_a = false | |
| \tif player == Vector2i(2, 1): | |
| \t\tkeys[0] = true | |
| \tif player == Vector2i(5, 1): | |
| \t\tkeys[1] = true | |
| \tif player == EXIT and keys[0] and keys[1]: | |
| \t\twon = true | |
| func _unhandled_input(event: InputEvent) -> void: | |
| \tif event is InputEventKey and event.pressed and not event.echo: | |
| \t\tif event.keycode == KEY_R: | |
| \t\t\treset() | |
| \t\telif not won: | |
| \t\t\tif event.is_action_pressed("ui_left"): | |
| \t\t\t\tstep(Vector2i.LEFT) | |
| \t\t\telif event.is_action_pressed("ui_right"): | |
| \t\t\t\tstep(Vector2i.RIGHT) | |
| \t\t\telif event.is_action_pressed("ui_up"): | |
| \t\t\t\tstep(Vector2i.UP) | |
| \t\t\telif event.is_action_pressed("ui_down"): | |
| \t\t\t\tstep(Vector2i.DOWN) | |
| func _process(_dt: float) -> void: | |
| \tqueue_redraw() | |
| func _draw() -> void: | |
| \tbackdrop("LINKED LOCKS", "Arrows: one step. Switches open one door and close the other. Get both keys, then exit. R: reset") | |
| \tfor y in range(7): | |
| \t\tfor x in range(11): | |
| \t\t\tvar cell = Vector2i(x, y) | |
| \t\t\tvar p = ORIGIN + Vector2(cell) * 56 | |
| \t\t\tdraw_rect(Rect2(p, Vector2(53, 53)), Color("53627c") if wall(cell) else Color("21304b")) | |
| \tfor cell in [Vector2i(2, 3), Vector2i(5, 3), Vector2i(6, 5)]: | |
| \t\tvar p = ORIGIN + Vector2(cell) * 56 + Vector2(27, 27) | |
| \t\tdraw_circle(p, 18, Color("ae8de3")) | |
| \t\ttext_at("B" if cell == Vector2i(5, 3) else "A", p + Vector2(-7, 8), 22) | |
| \tfor i in range(2): | |
| \t\tvar cell = A if i == 0 else B | |
| \t\tvar opened = door_a if i == 0 else not door_a | |
| \t\tvar area = Rect2(ORIGIN + Vector2(cell) * 56 + Vector2(4, 4), Vector2(45, 45)) | |
| \t\tdraw_rect(area, Color("6ee0bc") if opened else Color("e07d88"), not opened, 3) | |
| \t\tif not keys[i]: | |
| \t\t\tvar key = Vector2i(2, 1) if i == 0 else Vector2i(5, 1) | |
| \t\t\tdraw_circle(ORIGIN + Vector2(key) * 56 + Vector2(27, 27), 10, Color("ffd67b")) | |
| \tdraw_rect(Rect2(ORIGIN + Vector2(EXIT) * 56 + Vector2(8, 8), Vector2(37, 37)), Color("ffe09d"), false, 3) | |
| \tdraw_circle(ORIGIN + Vector2(player) * 56 + Vector2(27, 27), 15, Color("8ed6ff")) | |
| \ttext_at("Keys %d/2 Moves %d" % [int(keys[0]) + int(keys[1]), moves], Vector2(330, 587), 25) | |
| \tif won: | |
| \t\ttext_at("BOTH KEYS, OPEN EXIT! R: again", Vector2(275, 626), 25) | |
| '''), | |
| 89: ("Shield Escort", '''extends Node2D | |
| var vip = Vector2(100, 350) | |
| var bullets: Array[Vector2] = [] | |
| var speeds: Array[Vector2] = [] | |
| var shield = Rect2(470, 250, 20, 110) | |
| var horizontal = false | |
| var health = 5 | |
| var spawned = 0 | |
| var blocked = 0 | |
| var timer = 0.6 | |
| var state = 0 | |
| func _ready() -> void: | |
| \treset() | |
| func reset() -> void: | |
| \tvip = Vector2(100, 350) | |
| \tbullets.clear() | |
| \tspeeds.clear() | |
| \thealth = 5 | |
| \tspawned = 0 | |
| \tblocked = 0 | |
| \ttimer = 0.6 | |
| \tstate = 0 | |
| \thorizontal = false | |
| func _unhandled_input(event: InputEvent) -> void: | |
| \tif event is InputEventKey and event.pressed and not event.echo: | |
| \t\tif event.keycode == KEY_R: | |
| \t\t\treset() | |
| \t\telif event.keycode == KEY_SPACE: | |
| \t\t\thorizontal = not horizontal | |
| func _process(dt: float) -> void: | |
| \tvar size = Vector2(110, 20) if horizontal else Vector2(20, 110) | |
| \tshield = Rect2(get_global_mouse_position() - size / 2, size) | |
| \tif state == 0: | |
| \t\tvip.x += 23 * dt | |
| \t\tvip.y = 355 + sin((vip.x - 100) / 745 * TAU) * 85 | |
| \t\ttimer -= dt | |
| \t\tif timer <= 0: | |
| \t\t\ttimer = 1.05 | |
| \t\t\tvar origin = Vector2(160 + (spawned * 137) % 650, 125 if spawned % 2 == 0 else 575) | |
| \t\t\tbullets.append(origin) | |
| \t\t\tspeeds.append((vip - origin).normalized() * 170) | |
| \t\t\tspawned += 1 | |
| \t\tfor i in range(bullets.size() - 1, -1, -1): | |
| \t\t\tbullets[i] += speeds[i] * dt | |
| \t\t\tvar erase = false | |
| \t\t\tif shield.grow(5).has_point(bullets[i]): | |
| \t\t\t\tblocked += 1 | |
| \t\t\t\terase = true | |
| \t\t\telif bullets[i].distance_to(vip) < 22: | |
| \t\t\t\thealth -= 1 | |
| \t\t\t\terase = true | |
| \t\t\telif not Rect2(20, 100, 920, 510).has_point(bullets[i]): | |
| \t\t\t\terase = true | |
| \t\t\tif erase: | |
| \t\t\t\tbullets.remove_at(i) | |
| \t\t\t\tspeeds.remove_at(i) | |
| \t\tif health <= 0: | |
| \t\t\tstate = -1 | |
| \t\telif vip.x >= 845: | |
| \t\t\tstate = 1 | |
| \tqueue_redraw() | |
| func _draw() -> void: | |
| \tbackdrop("SHIELD ESCORT", "Mouse: position shield. Space: turn shield. Protect the VIP until the finish. R: restart") | |
| \tvar route = PackedVector2Array() | |
| \tfor i in range(50): | |
| \t\tvar x = 100 + i * 745.0 / 49 | |
| \t\troute.append(Vector2(x, 355 + sin((x - 100) / 745 * TAU) * 85)) | |
| \tdraw_polyline(route, Color("34516c"), 3) | |
| \tdraw_rect(Rect2(836, 290, 20, 135), Color("6cdeba")) | |
| \tdraw_circle(vip, 17, Color("ffd383")) | |
| \ttext_at("VIP", vip + Vector2(-15, -25), 18) | |
| \tfor p in bullets: | |
| \t\tdraw_circle(p, 6, Color("ed7786")) | |
| \tdraw_rect(shield, Color("82ccff")) | |
| \ttext_at("Health %d/5 Shots blocked %d Route %.0f%%" % [health, blocked, clampf((vip.x - 100) / 745 * 100, 0, 100)], Vector2(190, 625), 25) | |
| \tif state != 0: | |
| \t\ttext_at("VIP SAFE! R: again" if state == 1 else "ESCORT LOST. R: retry", Vector2(305, 116), 27) | |
| '''), | |
| 90: ("Springboard Route", '''extends Node2D | |
| const ORIGIN = Vector2(245, 175) | |
| const DIRS = [Vector2i.RIGHT, Vector2i.DOWN, Vector2i.LEFT, Vector2i.UP] | |
| const MARKS = [Vector2i(1, 1), Vector2i(3, 2)] | |
| const CUP = Vector2i(4, 0) | |
| var arrows: Array[int] = [] | |
| var cell = Vector2i(0, 3) | |
| var next = Vector2i(1, 3) | |
| var visited: Array[bool] = [false, false] | |
| var fraction = 0.0 | |
| var hops = 0 | |
| var running = false | |
| var state = 0 | |
| func _ready() -> void: | |
| \treset() | |
| func reset() -> void: | |
| \tarrows.clear() | |
| \tfor i in range(20): | |
| \t\tarrows.append(0) | |
| \tcell = Vector2i(0, 3) | |
| \tnext = Vector2i(1, 3) | |
| \tvisited = [false, false] | |
| \tfraction = 0 | |
| \thops = 0 | |
| \trunning = false | |
| \tstate = 0 | |
| func center(at: Vector2i) -> Vector2: | |
| \treturn ORIGIN + Vector2(at) * 95 | |
| func _unhandled_input(event: InputEvent) -> void: | |
| \tif event is InputEventKey and event.pressed and not event.echo: | |
| \t\tif event.keycode == KEY_R: | |
| \t\t\treset() | |
| \t\telif event.keycode == KEY_SPACE and state == 0 and not running: | |
| \t\t\tnext = cell + DIRS[arrows[cell.y * 5 + cell.x]] | |
| \t\t\trunning = true | |
| \tif event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT and state == 0: | |
| \t\tfor y in range(4): | |
| \t\t\tfor x in range(5): | |
| \t\t\t\tif event.position.distance_to(center(Vector2i(x, y))) < 33: | |
| \t\t\t\t\tarrows[y * 5 + x] = (arrows[y * 5 + x] + 1) % 4 | |
| func _process(dt: float) -> void: | |
| \tif running and state == 0: | |
| \t\tfraction += dt * 1.6 | |
| \t\tif fraction >= 1: | |
| \t\t\tfraction = 0 | |
| \t\t\tcell = next | |
| \t\t\thops += 1 | |
| \t\t\tif cell.x < 0 or cell.x >= 5 or cell.y < 0 or cell.y >= 4 or hops > 35: | |
| \t\t\t\tstate = -1 | |
| \t\t\telse: | |
| \t\t\t\tfor i in range(2): | |
| \t\t\t\t\tif cell == MARKS[i]: | |
| \t\t\t\t\t\tvisited[i] = true | |
| \t\t\t\tif cell == CUP: | |
| \t\t\t\t\tstate = 1 if visited[0] and visited[1] else -1 | |
| \t\t\t\telse: | |
| \t\t\t\t\tnext = cell + DIRS[arrows[cell.y * 5 + cell.x]] | |
| \tqueue_redraw() | |
| func _draw() -> void: | |
| \tbackdrop("SPRINGBOARD ROUTE", "Click pads to rotate launch arrows. Visit both gold pads before the cup. Space: start. R: reset") | |
| \tfor y in range(4): | |
| \t\tfor x in range(5): | |
| \t\t\tvar at = Vector2i(x, y) | |
| \t\t\tvar p = center(at) | |
| \t\t\tdraw_circle(p, 33, Color("304968")) | |
| \t\t\tvar d = Vector2(DIRS[arrows[y * 5 + x]]) | |
| \t\t\tdraw_line(p - d * 18, p + d * 17, Color("86d6ce"), 4) | |
| \t\t\tdraw_line(p + d * 17, p + d * 5 + d.orthogonal() * 10, Color("86d6ce"), 4) | |
| \t\t\tdraw_line(p + d * 17, p + d * 5 - d.orthogonal() * 10, Color("86d6ce"), 4) | |
| \tfor i in range(2): | |
| \t\tdraw_arc(center(MARKS[i]), 39, 0, TAU, 36, Color("6df0bc") if visited[i] else Color("ffd178"), 3) | |
| \tdraw_circle(center(CUP), 22, Color("101c31")) | |
| \tvar p = center(cell).lerp(center(next), fraction) if running else center(cell) | |
| \tp.y -= sin(fraction * PI) * 35 | |
| \tdraw_circle(p, 11, Color("ffe49e")) | |
| \ttext_at("Marked pads %d/2 Hops %d/35" % [int(visited[0]) + int(visited[1]), hops], Vector2(280, 585), 24) | |
| \tif state != 0: | |
| \t\ttext_at("IN THE CUP! R: again" if state == 1 else "ROUTE FAILED. R: retry", Vector2(315, 627), 25) | |
| '''), | |
| 91: ("Coupled Timers", '''extends Node2D | |
| var phases: Array[float] = [0.0, 0.7, 1.4] | |
| var speeds: Array[float] = [1.7, 2.1, 2.7] | |
| var values: Array[float] = [0.5, 0.82, 0.99] | |
| var locked: Array[bool] = [false, false, false] | |
| var targets: Array[float] = [0.3, 0.65, 0.46] | |
| var mistakes = 0 | |
| var remaining = 45.0 | |
| var state = 0 | |
| func reset() -> void: | |
| \tphases = [0.0, 0.7, 1.4] | |
| \tspeeds = [1.7, 2.1, 2.7] | |
| \tvalues = [0.5, 0.82, 0.99] | |
| \tlocked = [false, false, false] | |
| \tmistakes = 0 | |
| \tremaining = 45 | |
| \tstate = 0 | |
| func _unhandled_input(event: InputEvent) -> void: | |
| \tif event is InputEventKey and event.pressed and not event.echo: | |
| \t\tif event.keycode == KEY_R: | |
| \t\t\treset() | |
| \t\telif event.keycode >= KEY_1 and event.keycode <= KEY_3 and state == 0: | |
| \t\t\tvar index = int(event.keycode) - int(KEY_1) | |
| \t\t\tif locked[index]: | |
| \t\t\t\treturn | |
| \t\t\tif absf(values[index] - targets[index]) <= 0.065: | |
| \t\t\t\tlocked[index] = true | |
| \t\t\t\tfor other in range(3): | |
| \t\t\t\t\tif not locked[other]: | |
| \t\t\t\t\t\tspeeds[other] *= 1.35 | |
| \t\t\telse: | |
| \t\t\t\tmistakes += 1 | |
| \t\t\tif locked[0] and locked[1] and locked[2]: | |
| \t\t\t\tstate = 1 | |
| \t\t\telif mistakes >= 3: | |
| \t\t\t\tstate = -1 | |
| func _process(dt: float) -> void: | |
| \tif state == 0: | |
| \t\tremaining -= dt | |
| \t\tfor i in range(3): | |
| \t\t\tif not locked[i]: | |
| \t\t\t\tphases[i] += speeds[i] * dt | |
| \t\t\t\tvalues[i] = (sin(phases[i]) + 1.0) / 2.0 | |
| \t\tif remaining <= 0: | |
| \t\t\tstate = -1 | |
| \tqueue_redraw() | |
| func _draw() -> void: | |
| \tbackdrop("COUPLED TIMERS", "Press 1, 2, 3 to stop that bar in its gold zone. Each lock speeds up the others. R: restart") | |
| \tfor i in range(3): | |
| \t\tvar y = 220 + i * 120 | |
| \t\ttext_at(str(i + 1), Vector2(140, y + 11), 34) | |
| \t\tdraw_rect(Rect2(210, y - 22, 570, 44), Color("293b59")) | |
| \t\tdraw_rect(Rect2(210 + (targets[i] - 0.065) * 570, y - 22, 0.13 * 570, 44), Color("967543")) | |
| \t\tdraw_line(Vector2(210 + values[i] * 570, y - 35), Vector2(210 + values[i] * 570, y + 35), Color("72e8bd") if locked[i] else Color.WHITE, 5) | |
| \t\ttext_at("LOCKED" if locked[i] else "MOVING", Vector2(795, y + 8), 18) | |
| \ttext_at("Misses %d/3 Time %.0f" % [mistakes, maxf(0, remaining)], Vector2(335, 594), 26) | |
| \tif state != 0: | |
| \t\ttext_at("ALL THREE LOCKED! R: again" if state == 1 else "TIMING FAILED. R: retry", Vector2(280, 632), 25) | |
| '''), | |
| 92: ("Bucket Brigade", '''extends Node2D | |
| const LEVELS = [200.0, 330.0, 460.0] | |
| const LOW = [80.0, 300.0, 570.0] | |
| const HIGH = [350.0, 630.0, 840.0] | |
| var buckets: Array[float] = [100.0, 360.0, 620.0] | |
| var selected = 0 | |
| var holder = -1 | |
| var drop = Vector2(100, 115) | |
| var target = 0 | |
| var falling = true | |
| var score = 0 | |
| var spills = 0 | |
| var state = 0 | |
| func reset() -> void: | |
| \tbuckets = [100.0, 360.0, 620.0] | |
| \tselected = 0 | |
| \tscore = 0 | |
| \tspills = 0 | |
| \tstate = 0 | |
| \tnew_packet() | |
| func new_packet() -> void: | |
| \tdrop = Vector2(100, 115) | |
| \ttarget = 0 | |
| \tholder = -1 | |
| \tfalling = true | |
| func _unhandled_input(event: InputEvent) -> void: | |
| \tif event is InputEventKey and event.pressed and not event.echo: | |
| \t\tif event.keycode == KEY_R: | |
| \t\t\treset() | |
| \t\telif event.keycode >= KEY_1 and event.keycode <= KEY_3: | |
| \t\t\tselected = int(event.keycode) - int(KEY_1) | |
| \t\telif event.keycode == KEY_SPACE and state == 0 and holder == selected: | |
| \t\t\tdrop = Vector2(buckets[holder], LEVELS[holder] + 28) | |
| \t\t\ttarget = holder + 1 | |
| \t\t\tholder = -1 | |
| \t\t\tfalling = true | |
| func _process(dt: float) -> void: | |
| \tif state == 0: | |
| \t\tbuckets[selected] = clampf(buckets[selected] + Input.get_axis("ui_left", "ui_right") * 230 * dt, LOW[selected], HIGH[selected]) | |
| \t\tif falling: | |
| \t\t\tdrop.y += 220 * dt | |
| \t\t\tvar level = LEVELS[target] if target < 3 else 580.0 | |
| \t\t\tif drop.y >= level: | |
| \t\t\t\tif target < 3 and absf(drop.x - buckets[target]) < 28: | |
| \t\t\t\t\tholder = target | |
| \t\t\t\t\tfalling = false | |
| \t\t\t\telif target == 3 and drop.x >= 800 and drop.x <= 900: | |
| \t\t\t\t\tscore += 1 | |
| \t\t\t\t\tnew_packet() | |
| \t\t\t\telse: | |
| \t\t\t\t\tspills += 1 | |
| \t\t\t\t\tnew_packet() | |
| \t\tif score >= 4: | |
| \t\t\tstate = 1 | |
| \t\telif spills >= 5: | |
| \t\t\tstate = -1 | |
| \tqueue_redraw() | |
| func _draw() -> void: | |
| \tbackdrop("BUCKET BRIGADE", "1–3: choose bucket / Arrows: slide / Space: pour into next bucket, then tank / R: restart") | |
| \tdraw_rect(Rect2(85, 95, 30, 25), Color("aebad0")) | |
| \tfor i in range(3): | |
| \t\tdraw_line(Vector2(LOW[i] - 28, LEVELS[i] + 34), Vector2(HIGH[i] + 28, LEVELS[i] + 34), Color("465875"), 3) | |
| \t\tvar area = Rect2(buckets[i] - 28, LEVELS[i], 56, 28) | |
| \t\tdraw_rect(area, Color("70c9ff") if holder == i else Color("2e476b")) | |
| \t\tdraw_rect(area, Color("ffd27b") if selected == i else Color.WHITE, false, 3) | |
| \t\ttext_at(str(i + 1), Vector2(buckets[i] - 6, LEVELS[i] + 21), 20) | |
| \tdraw_rect(Rect2(800, 580, 100, 36), Color("207594")) | |
| \ttext_at("TANK", Vector2(821, 606), 20) | |
| \tif falling: | |
| \t\tdraw_circle(drop, 7, Color("8be5ff")) | |
| \ttext_at("Delivered %d/4 Spills %d/5" % [score, spills], Vector2(60, 630), 24) | |
| \tif state != 0: | |
| \t\ttext_at("RESERVOIR FILLED! R: again" if state == 1 else "TOO MUCH WATER LOST. R: retry", Vector2(260, 145), 25) | |
| '''), | |
| 93: ("Silhouette Shift", '''extends Node2D | |
| const PLAYER = Vector2(250, 350) | |
| var shape = 0 | |
| var angle = 0.0 | |
| var requested = 1 | |
| var required_angle = 0.0 | |
| var wall_x = 930.0 | |
| var passed = 0 | |
| var missed = 0 | |
| var state = 0 | |
| func reset() -> void: | |
| \tshape = 0 | |
| \tangle = 0 | |
| \trequested = 1 | |
| \trequired_angle = 0 | |
| \twall_x = 930 | |
| \tpassed = 0 | |
| \tmissed = 0 | |
| \tstate = 0 | |
| func _unhandled_input(event: InputEvent) -> void: | |
| \tif event is InputEventKey and event.pressed and not event.echo: | |
| \t\tif event.keycode == KEY_R: | |
| \t\t\treset() | |
| \t\telif event.keycode == KEY_SPACE and state == 0: | |
| \t\t\tshape = (shape + 1) % 3 | |
| func _process(dt: float) -> void: | |
| \tif state == 0: | |
| \t\tangle += Input.get_axis("ui_left", "ui_right") * 2.0 * dt | |
| \t\twall_x -= 140 * dt | |
| \t\tif wall_x <= PLAYER.x: | |
| \t\t\tvar period = PI / 2 if shape == 1 else TAU / 3 | |
| \t\t\tvar aligned = shape == 0 or absf(wrapf(angle - required_angle, -period / 2, period / 2)) < 0.18 | |
| \t\t\tif shape == requested and aligned: | |
| \t\t\t\tpassed += 1 | |
| \t\t\telse: | |
| \t\t\t\tmissed += 1 | |
| \t\t\twall_x = 930 | |
| \t\t\trequested = (requested + 1) % 3 | |
| \t\t\trequired_angle = (passed + missed) * PI / 6 | |
| \t\tif passed >= 6: | |
| \t\t\tstate = 1 | |
| \t\telif missed >= 3: | |
| \t\t\tstate = -1 | |
| \tqueue_redraw() | |
| func shape_at(p: Vector2, kind: int, rotation: float, size: float, color: Color) -> void: | |
| \tif kind == 0: | |
| \t\tdraw_circle(p, size, color) | |
| \telse: | |
| \t\tvar points = PackedVector2Array() | |
| \t\tvar count = 4 if kind == 1 else 3 | |
| \t\tvar extra = PI / 4 if kind == 1 else -PI / 2 | |
| \t\tfor i in range(count): | |
| \t\t\tvar a = rotation + extra + float(i) * TAU / count | |
| \t\t\tpoints.append(p + Vector2(cos(a), sin(a)) * size) | |
| \t\tdraw_colored_polygon(points, color) | |
| func _draw() -> void: | |
| \tbackdrop("SILHOUETTE SHIFT", "Space: circle/square/triangle. Left/right: rotate. Match the approaching opening. R: restart") | |
| \tdraw_rect(Rect2(50, 150, 865, 400), Color("17273e")) | |
| \tdraw_line(Vector2(60, 350), Vector2(910, 350), Color("32435c"), 2) | |
| \tdraw_rect(Rect2(wall_x - 20, 160, 40, 380), Color("7693b4")) | |
| \tshape_at(Vector2(wall_x, 350), requested, required_angle, 49, Color("17273e")) | |
| \tshape_at(PLAYER, shape, angle, 35, Color("ffd17b")) | |
| \tshape_at(Vector2(780, 115), requested, required_angle, 23, Color("88edcf")) | |
| \ttext_at("Required angle: %.0f deg" % rad_to_deg(fposmod(required_angle, TAU)), Vector2(500, 123), 20) | |
| \ttext_at("Passes %d/6 Misses %d/3 Your angle %.0f deg" % [passed, missed, rad_to_deg(fposmod(angle, TAU))], Vector2(165, 610), 24) | |
| \tif state != 0: | |
| \t\ttext_at("ALL WALLS CLEARED! R: again" if state == 1 else "SHAPE MISMATCH. R: retry", Vector2(290, 575), 24) | |
| '''), | |
| 94: ("Sled Slalom", '''extends Node2D | |
| var sled_x = 480.0 | |
| var distance = 0.0 | |
| var cleared: Array[bool] = [] | |
| var rocks_hit: Array[bool] = [false, false] | |
| var penalties = 0 | |
| var passed = 0 | |
| var state = 0 | |
| func _ready() -> void: | |
| \treset() | |
| func reset() -> void: | |
| \tsled_x = 480 | |
| \tdistance = 0 | |
| \tcleared.clear() | |
| \tfor i in range(8): | |
| \t\tcleared.append(false) | |
| \trocks_hit = [false, false] | |
| \tpenalties = 0 | |
| \tpassed = 0 | |
| \tstate = 0 | |
| func _unhandled_input(event: InputEvent) -> void: | |
| \tif event is InputEventKey and event.pressed and not event.echo and event.keycode == KEY_R: | |
| \t\treset() | |
| func _process(dt: float) -> void: | |
| \tif state == 0: | |
| \t\tdistance += 155 * dt | |
| \t\tsled_x = clampf(sled_x + Input.get_axis("ui_left", "ui_right") * 340 * dt, 155, 805) | |
| \t\tfor i in range(8): | |
| \t\t\tif not cleared[i] and distance >= 400 + i * 250: | |
| \t\t\t\tcleared[i] = true | |
| \t\t\t\tpassed += 1 | |
| \t\t\t\tvar center = 315.0 if i % 2 == 0 else 645.0 | |
| \t\t\t\tif absf(sled_x - center) > 82: | |
| \t\t\t\t\tpenalties += 1 | |
| \t\tfor i in range(2): | |
| \t\t\tvar rock = Vector2(480 if i == 0 else 320, 490 - (760 + i * 900 - distance)) | |
| \t\t\tif not rocks_hit[i] and rock.distance_to(Vector2(sled_x, 490)) < 36: | |
| \t\t\t\trocks_hit[i] = true | |
| \t\t\t\tpenalties += 1 | |
| \t\tif penalties >= 3: | |
| \t\t\tstate = -1 | |
| \t\telif passed == 8 and distance > 2320: | |
| \t\t\tstate = 1 | |
| \tqueue_redraw() | |
| func _draw() -> void: | |
| \tbackdrop("SLED SLALOM", "Left/right: steer through alternating gate openings. Avoid rocks. Fewer than 3 penalties. R: restart") | |
| \tdraw_rect(Rect2(130, 125, 700, 455), Color("c9deea")) | |
| \tfor i in range(8): | |
| \t\tvar y = 490 - (400 + i * 250 - distance) | |
| \t\tif y < 125 or y > 580: | |
| \t\t\tcontinue | |
| \t\tvar center = 315.0 if i % 2 == 0 else 645.0 | |
| \t\tfor x in [center - 92, center + 92]: | |
| \t\t\tdraw_line(Vector2(x, y - 32), Vector2(x, y + 10), Color("d15d78"), 5) | |
| \t\tdraw_line(Vector2(center - 92, y), Vector2(center + 92, y), Color("639386"), 3) | |
| \t\ttext_at(str(i + 1), Vector2(center - 8, y - 15), 23, Color("213452")) | |
| \tfor i in range(2): | |
| \t\tvar p = Vector2(480 if i == 0 else 320, 490 - (760 + i * 900 - distance)) | |
| \t\tif p.y > 125 and p.y < 580: | |
| \t\t\tdraw_circle(p, 23, Color("6c7a90")) | |
| \tdraw_rect(Rect2(sled_x - 13, 473, 26, 35), Color("347198")) | |
| \tdraw_line(Vector2(sled_x - 20, 470), Vector2(sled_x - 20, 515), Color("243957"), 4) | |
| \tdraw_line(Vector2(sled_x + 20, 470), Vector2(sled_x + 20, 515), Color("243957"), 4) | |
| \ttext_at("Gates %d/8 Penalties %d/3 Distance %.0f" % [passed, penalties, distance], Vector2(220, 624), 25) | |
| \tif state != 0: | |
| \t\ttext_at("FINISH! R: race again" if state == 1 else "TOO MANY PENALTIES. R: retry", Vector2(290, 116), 24) | |
| '''), | |
| 95: ("Crack Patrol", '''extends Node2D | |
| const ORIGIN = Vector2(230, 155) | |
| var damage: Array[float] = [] | |
| var player = Vector2i(1, 1) | |
| var repairs = 0 | |
| var repair_time = 0.0 | |
| var walk_time = 0.0 | |
| var spread_time = 1.2 | |
| var cursor = 0 | |
| var state = 0 | |
| func _ready() -> void: | |
| \treset() | |
| func reset() -> void: | |
| \tdamage.clear() | |
| \tfor i in range(70): | |
| \t\tdamage.append(0.0) | |
| \tfor i in [22, 46, 18]: | |
| \t\tdamage[i] = 0.1 | |
| \tplayer = Vector2i(1, 1) | |
| \trepairs = 0 | |
| \trepair_time = 0 | |
| \twalk_time = 0 | |
| \tspread_time = 1.2 | |
| \tcursor = 0 | |
| \tstate = 0 | |
| func spread() -> void: | |
| \tfor offset in range(70): | |
| \t\tvar index = (cursor + offset) % 70 | |
| \t\tif damage[index] <= 0: | |
| \t\t\tcontinue | |
| \t\tvar cell = Vector2i(index % 10, int(index / 10)) | |
| \t\tfor d in [Vector2i.RIGHT, Vector2i.DOWN, Vector2i.LEFT, Vector2i.UP]: | |
| \t\t\tvar next: Vector2i = cell + d | |
| \t\t\tif next.x >= 0 and next.x < 10 and next.y >= 0 and next.y < 7: | |
| \t\t\t\tvar target = next.y * 10 + next.x | |
| \t\t\t\tif damage[target] == 0: | |
| \t\t\t\t\tdamage[target] = 0.1 | |
| \t\t\t\t\tcursor = (index + 1) % 70 | |
| \t\t\t\t\treturn | |
| func _unhandled_input(event: InputEvent) -> void: | |
| \tif event is InputEventKey and event.pressed and not event.echo and event.keycode == KEY_R: | |
| \t\treset() | |
| func _process(dt: float) -> void: | |
| \tif state == 0: | |
| \t\tfor i in range(70): | |
| \t\t\tif damage[i] > 0: | |
| \t\t\t\tdamage[i] += dt | |
| \t\twalk_time -= dt | |
| \t\tspread_time -= dt | |
| \t\tif spread_time <= 0: | |
| \t\t\tspread_time = 1.2 | |
| \t\t\tspread() | |
| \t\tif walk_time <= 0: | |
| \t\t\tvar d = Vector2i.ZERO | |
| \t\t\tif Input.is_action_pressed("ui_left"): | |
| \t\t\t\td = Vector2i.LEFT | |
| \t\t\telif Input.is_action_pressed("ui_right"): | |
| \t\t\t\td = Vector2i.RIGHT | |
| \t\t\telif Input.is_action_pressed("ui_up"): | |
| \t\t\t\td = Vector2i.UP | |
| \t\t\telif Input.is_action_pressed("ui_down"): | |
| \t\t\t\td = Vector2i.DOWN | |
| \t\t\tif d != Vector2i.ZERO: | |
| \t\t\t\tplayer = (player + d).clamp(Vector2i.ZERO, Vector2i(9, 6)) | |
| \t\t\t\twalk_time = 0.12 | |
| \t\t\t\trepair_time = 0 | |
| \t\tvar index = player.y * 10 + player.x | |
| \t\tif Input.is_physical_key_pressed(KEY_SPACE) and damage[index] > 0 and damage[index] < 12: | |
| \t\t\trepair_time += dt | |
| \t\t\tif repair_time >= 0.6: | |
| \t\t\t\tdamage[index] = 0 | |
| \t\t\t\trepairs += 1 | |
| \t\t\t\trepair_time = 0 | |
| \t\telse: | |
| \t\t\trepair_time = 0 | |
| \t\tvar broken = 0 | |
| \t\tvar cracked = 0 | |
| \t\tfor value in damage: | |
| \t\t\tbroken += int(value >= 12) | |
| \t\t\tcracked += int(value > 0) | |
| \t\tif broken >= 35: | |
| \t\t\tstate = -1 | |
| \t\telif repairs >= 12 or cracked == 0: | |
| \t\t\tstate = 1 | |
| \tqueue_redraw() | |
| func _draw() -> void: | |
| \tbackdrop("CRACK PATROL", "Arrows: move. Hold Space 0.6s to repair a cracked tile. Repair 12 before half break. R: reset") | |
| \tvar broken = 0 | |
| \tfor i in range(70): | |
| \t\tvar p = ORIGIN + Vector2(i % 10, int(i / 10)) * 50 | |
| \t\tbroken += int(damage[i] >= 12) | |
| \t\tdraw_rect(Rect2(p, Vector2(47, 47)), Color("19243a") if damage[i] >= 12 else Color("3e5c78")) | |
| \t\tif damage[i] > 0: | |
| \t\t\tdraw_line(p + Vector2(8, 5), p + Vector2(28, 24), Color("f39a80"), 3) | |
| \t\t\tdraw_line(p + Vector2(28, 24), p + Vector2(12, 44), Color("f39a80"), 3) | |
| \tdraw_circle(ORIGIN + Vector2(player) * 50 + Vector2(24, 24), 15, Color("73e8c4")) | |
| \ttext_at("Repairs %d/12 Broken %d/35 Repair %.0f%%" % [repairs, broken, repair_time / 0.6 * 100], Vector2(215, 568), 23) | |
| \tif state != 0: | |
| \t\ttext_at("FLOOR SAVED! R: again" if state == 1 else "FLOOR COLLAPSED. R: retry", Vector2(285, 616), 25) | |
| '''), | |
| 96: ("Eight Step Sum", '''extends Node2D | |
| const ORIGIN = Vector2(275, 150) | |
| const EXIT = Vector2i(4, 4) | |
| var numbers: Array[int] = [] | |
| var path: Array[Vector2i] = [Vector2i.ZERO] | |
| var score = 0 | |
| var state = 0 | |
| func _ready() -> void: | |
| \treset() | |
| func reset() -> void: | |
| \tnumbers.clear() | |
| \tfor i in range(25): | |
| \t\tnumbers.append((i * 7) % 9 - 3) | |
| \tnumbers[0] = 0 | |
| \tvar route = [1, 2, 3, 4, 9, 14, 19, 24] | |
| \tvar values = [2, 3, 1, 4, 2, 1, 3, 2] | |
| \tfor i in range(8): | |
| \t\tnumbers[route[i]] = values[i] | |
| \tpath = [Vector2i.ZERO] | |
| \tscore = 0 | |
| \tstate = 0 | |
| func choose(cell: Vector2i) -> void: | |
| \tif state != 0 or cell.x < 0 or cell.x > 4 or cell.y < 0 or cell.y > 4 or cell in path: | |
| \t\treturn | |
| \tvar last = path[path.size() - 1] | |
| \tif absi(cell.x - last.x) + absi(cell.y - last.y) != 1: | |
| \t\treturn | |
| \tpath.append(cell) | |
| \tscore += numbers[cell.y * 5 + cell.x] | |
| \tif path.size() == 9: | |
| \t\tstate = 1 if cell == EXIT and score == 18 else -1 | |
| func _unhandled_input(event: InputEvent) -> void: | |
| \tif event is InputEventKey and event.pressed and not event.echo: | |
| \t\tif event.keycode == KEY_R: | |
| \t\t\treset() | |
| \t\telif event.keycode == KEY_BACKSPACE and path.size() > 1 and state == 0: | |
| \t\t\tvar last = path.pop_back() | |
| \t\t\tscore -= numbers[last.y * 5 + last.x] | |
| \t\telif state == 0: | |
| \t\t\tvar last = path[path.size() - 1] | |
| \t\t\tif event.is_action_pressed("ui_left"): | |
| \t\t\t\tchoose(last + Vector2i.LEFT) | |
| \t\t\telif event.is_action_pressed("ui_right"): | |
| \t\t\t\tchoose(last + Vector2i.RIGHT) | |
| \t\t\telif event.is_action_pressed("ui_up"): | |
| \t\t\t\tchoose(last + Vector2i.UP) | |
| \t\t\telif event.is_action_pressed("ui_down"): | |
| \t\t\t\tchoose(last + Vector2i.DOWN) | |
| \tif event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT: | |
| \t\tvar p = event.position - ORIGIN | |
| \t\tif Rect2(Vector2.ZERO, Vector2(400, 400)).has_point(p): | |
| \t\t\tchoose(Vector2i(p / 80)) | |
| func _process(_dt: float) -> void: | |
| \tqueue_redraw() | |
| func _draw() -> void: | |
| \tbackdrop("EIGHT STEP SUM", "Arrows or click adjacent squares. Exactly 8 steps to EXIT with sum 18. Backspace: undo. R: reset") | |
| \tfor y in range(5): | |
| \t\tfor x in range(5): | |
| \t\t\tvar cell = Vector2i(x, y) | |
| \t\t\tvar p = ORIGIN + Vector2(cell) * 80 | |
| \t\t\tdraw_rect(Rect2(p, Vector2(76, 76)), Color("367f73") if cell in path else Color("2e4160")) | |
| \t\t\ttext_at(str(numbers[y * 5 + x]), p + Vector2(29, 46), 28) | |
| \t\t\tif cell == EXIT: | |
| \t\t\t\ttext_at("EXIT", p + Vector2(17, 68), 17, Color("ffdb8c")) | |
| \tfor i in range(1, path.size()): | |
| \t\tdraw_line(ORIGIN + Vector2(path[i - 1]) * 80 + Vector2(38, 38), ORIGIN + Vector2(path[i]) * 80 + Vector2(38, 38), Color(1, 1, 1, 0.35), 5) | |
| \ttext_at("Steps %d/8 Sum %d/18" % [path.size() - 1, score], Vector2(325, 588), 26) | |
| \tif state != 0: | |
| \t\ttext_at("PERFECT PATH! R: again" if state == 1 else "WRONG END OR SUM. R: retry", Vector2(280, 630), 24) | |
| '''), | |
| 97: ("Return to Sender", '''extends Node2D | |
| const CENTER = Vector2(480, 350) | |
| const ENEMIES = [Vector2(180, 170), Vector2(780, 170), Vector2(780, 535), Vector2(180, 535)] | |
| var alive: Array[bool] = [true, true, true, true] | |
| var shots: Array[Dictionary] = [] | |
| var angle = 0.0 | |
| var health = 3 | |
| var timer = 0.5 | |
| var turn = 0 | |
| var state = 0 | |
| func reset() -> void: | |
| \talive = [true, true, true, true] | |
| \tshots.clear() | |
| \tangle = 0 | |
| \thealth = 3 | |
| \ttimer = 0.5 | |
| \tturn = 0 | |
| \tstate = 0 | |
| func _unhandled_input(event: InputEvent) -> void: | |
| \tif event is InputEventKey and event.pressed and not event.echo and event.keycode == KEY_R: | |
| \t\treset() | |
| func _process(dt: float) -> void: | |
| \tif state == 0: | |
| \t\tangle += Input.get_axis("ui_left", "ui_right") * 3 * dt | |
| \t\ttimer -= dt | |
| \t\tif timer <= 0: | |
| \t\t\ttimer = 0.9 | |
| \t\t\tfor offset in range(4): | |
| \t\t\t\tvar owner = (turn + offset) % 4 | |
| \t\t\t\tif alive[owner]: | |
| \t\t\t\t\tshots.append({"p": ENEMIES[owner], "v": (CENTER - ENEMIES[owner]).normalized() * 155, "owner": owner, "reflected": false}) | |
| \t\t\t\t\tturn = (owner + 1) % 4 | |
| \t\t\t\t\tbreak | |
| \t\tfor i in range(shots.size() - 1, -1, -1): | |
| \t\t\tvar p: Vector2 = shots[i]["p"] | |
| \t\t\tvar v: Vector2 = shots[i]["v"] | |
| \t\t\tp += v * dt | |
| \t\t\tshots[i]["p"] = p | |
| \t\t\tvar erase = false | |
| \t\t\tif not shots[i]["reflected"] and p.distance_to(CENTER) < 84 and p.distance_to(CENTER) > 60: | |
| \t\t\t\tif absf(wrapf((p - CENTER).angle() - angle, -PI, PI)) < 0.5: | |
| \t\t\t\t\tshots[i]["v"] = -v * 1.6 | |
| \t\t\t\t\tshots[i]["reflected"] = true | |
| \t\t\tif shots[i]["reflected"] and p.distance_to(ENEMIES[int(shots[i]["owner"])]) < 24: | |
| \t\t\t\talive[int(shots[i]["owner"])] = false | |
| \t\t\t\terase = true | |
| \t\t\telif p.distance_to(CENTER) < 22: | |
| \t\t\t\thealth -= 1 | |
| \t\t\t\terase = true | |
| \t\t\telif not Rect2(20, 105, 920, 505).has_point(p): | |
| \t\t\t\terase = true | |
| \t\t\tif erase: | |
| \t\t\t\tshots.remove_at(i) | |
| \t\tif health <= 0: | |
| \t\t\tstate = -1 | |
| \t\telif not alive[0] and not alive[1] and not alive[2] and not alive[3]: | |
| \t\t\tstate = 1 | |
| \tqueue_redraw() | |
| func _draw() -> void: | |
| \tbackdrop("RETURN TO SENDER", "Left/right: rotate your short shield. Reflect shots back at their own turrets. R: restart") | |
| \tvar remaining = 0 | |
| \tfor i in range(4): | |
| \t\tif alive[i]: | |
| \t\t\tremaining += 1 | |
| \t\t\tdraw_circle(ENEMIES[i], 23, Color("bd6b88")) | |
| \t\t\tdraw_line(ENEMIES[i], ENEMIES[i] + (CENTER - ENEMIES[i]).normalized() * 31, Color("ed99aa"), 8) | |
| \tdraw_circle(CENTER, 18, Color("e4c078")) | |
| \tdraw_arc(CENTER, 75, angle - 0.5, angle + 0.5, 20, Color("7adcf0"), 8) | |
| \tfor shot in shots: | |
| \t\tdraw_circle(shot["p"], 6, Color("76edbd") if shot["reflected"] else Color("ff8c94")) | |
| \ttext_at("Health %d/3 Turrets defeated %d/4" % [health, 4 - remaining], Vector2(265, 625), 26) | |
| \tif state != 0: | |
| \t\ttext_at("ALL TURRETS SILENCED! R: again" if state == 1 else "SHIELD BREACHED. R: retry", Vector2(265, 115), 25) | |
| '''), | |
| 98: ("Color Courier", '''extends Node2D | |
| const STATIONS = [Vector2(160, 340), Vector2(480, 185), Vector2(800, 340)] | |
| const COLORS = [Color("f27682"), Color("73e49b"), Color("79b5fc")] | |
| const RECIPES = [Vector3i(2, 1, 1), Vector3i(1, 2, 2), Vector3i(1, 1, 3)] | |
| const CUSTOMER = Vector2(480, 530) | |
| var player = Vector2(480, 350) | |
| var tank = Vector3i.ZERO | |
| var delivered = 0 | |
| var errors = 0 | |
| var state = 0 | |
| func reset() -> void: | |
| \tplayer = Vector2(480, 350) | |
| \ttank = Vector3i.ZERO | |
| \tdelivered = 0 | |
| \terrors = 0 | |
| \tstate = 0 | |
| func total() -> int: | |
| \treturn tank.x + tank.y + tank.z | |
| func _unhandled_input(event: InputEvent) -> void: | |
| \tif event is InputEventKey and event.pressed and not event.echo: | |
| \t\tif event.keycode == KEY_R: | |
| \t\t\treset() | |
| \t\telif event.keycode == KEY_X and state == 0: | |
| \t\t\ttank = Vector3i.ZERO | |
| \t\telif event.keycode == KEY_SPACE and state == 0: | |
| \t\t\tfor i in range(3): | |
| \t\t\t\tif player.distance_to(STATIONS[i]) < 58 and total() < 6: | |
| \t\t\t\t\ttank[i] += 1 | |
| \t\t\tif player.distance_to(CUSTOMER) < 58: | |
| \t\t\t\tif tank == RECIPES[delivered]: | |
| \t\t\t\t\tdelivered += 1 | |
| \t\t\t\telse: | |
| \t\t\t\t\terrors += 1 | |
| \t\t\t\ttank = Vector3i.ZERO | |
| \t\t\tif delivered == 3: | |
| \t\t\t\tstate = 1 | |
| \t\t\telif errors == 3: | |
| \t\t\t\tstate = -1 | |
| func _process(dt: float) -> void: | |
| \tif state == 0: | |
| \t\tplayer += Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down") * 250 * dt | |
| \t\tplayer = player.clamp(Vector2(80, 140), Vector2(880, 570)) | |
| \tqueue_redraw() | |
| func _draw() -> void: | |
| \tbackdrop("COLOR COURIER", "Arrows: move / Space near station: add 1 unit; near customer: deliver / X: empty tank / R: restart") | |
| \tfor i in range(3): | |
| \t\tdraw_circle(STATIONS[i], 38, COLORS[i]) | |
| \t\ttext_at(["R", "G", "B"][i], STATIONS[i] + Vector2(-12, 12), 32, Color("17233b")) | |
| \tdraw_circle(CUSTOMER, 37, Color("e8d5aa")) | |
| \ttext_at("ORDER", CUSTOMER + Vector2(-33, 7), 20, Color("17233b")) | |
| \tvar recipe: Vector3i = RECIPES[mini(delivered, 2)] | |
| \ttext_at("Need R%d G%d B%d" % [recipe.x, recipe.y, recipe.z], Vector2(360, 125), 26, Color("ffe3a1")) | |
| \tvar mixture = Color(float(tank.x) / maxi(1, total()), float(tank.y) / maxi(1, total()), float(tank.z) / maxi(1, total())) | |
| \tdraw_circle(player, 18, Color("bfd8e9")) | |
| \tdraw_circle(player, 11, mixture if total() > 0 else Color("203452")) | |
| \ttext_at("Tank R%d G%d B%d (%d/6) Delivered %d/3 Wrong %d/3" % [tank.x, tank.y, tank.z, total(), delivered, errors], Vector2(90, 625), 23) | |
| \tif state != 0: | |
| \t\ttext_at("ALL COLORS DELIVERED! R: again" if state == 1 else "WRONG MIXTURES. R: retry", Vector2(265, 585), 25) | |
| '''), | |
| 99: ("Three Stage Relay", '''extends Node2D | |
| var stage = 0 | |
| var player = Vector2(120, 532) | |
| var vertical = 0.0 | |
| var grounded = true | |
| var total_time = 0.0 | |
| var health = 3 | |
| var immune = 0.0 | |
| var state = 0 | |
| const HURDLES = [Rect2(300, 475, 45, 70), Rect2(470, 445, 45, 100), Rect2(640, 480, 45, 65)] | |
| func reset() -> void: | |
| \tstage = 0 | |
| \ttotal_time = 0 | |
| \thealth = 3 | |
| \tstate = 0 | |
| \tplace_player() | |
| func place_player() -> void: | |
| \tplayer = Vector2(105, 340) if stage == 1 else Vector2(120, 532) | |
| \tvertical = 0 | |
| \tgrounded = true | |
| \timmune = 1.0 | |
| func fail_stage() -> void: | |
| \thealth -= 1 | |
| \tif health <= 0: | |
| \t\tstate = -1 | |
| \telse: | |
| \t\tplace_player() | |
| func _unhandled_input(event: InputEvent) -> void: | |
| \tif event is InputEventKey and event.pressed and not event.echo: | |
| \t\tif event.keycode == KEY_R: | |
| \t\t\treset() | |
| \t\telif event.keycode == KEY_SPACE and state == 0 and stage != 1 and grounded: | |
| \t\t\tvertical = -620.0 if stage == 2 else -405.0 | |
| \t\t\tgrounded = false | |
| func _process(dt: float) -> void: | |
| \tif state == 0: | |
| \t\ttotal_time += dt | |
| \t\timmune = maxf(0, immune - dt) | |
| \t\tif stage == 1: | |
| \t\t\tplayer += Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down") * 235 * dt | |
| \t\t\tplayer = player.clamp(Vector2(75, 145), Vector2(880, 555)) | |
| \t\t\tfor i in range(4): | |
| \t\t\t\tvar hazard = Vector2(280 + i * 155, 350 + sin(total_time * 2 + i) * 170) | |
| \t\t\t\tif hazard.distance_to(player) < 37 and immune <= 0: | |
| \t\t\t\t\tfail_stage() | |
| \t\t\t\t\tbreak | |
| \t\t\tif player.x > 850: | |
| \t\t\t\tstage = 2 | |
| \t\t\t\tplace_player() | |
| \t\telse: | |
| \t\t\tvar old = player | |
| \t\t\tplayer.x = clampf(player.x + Input.get_axis("ui_left", "ui_right") * (420 if stage == 2 else 230) * dt, 75, 885) | |
| \t\t\tif stage == 0: | |
| \t\t\t\tfor wall in HURDLES: | |
| \t\t\t\t\tif Rect2(player - Vector2(13, 13), Vector2(26, 26)).intersects(wall): | |
| \t\t\t\t\t\tplayer.x = old.x | |
| \t\t\tvertical += 760 * dt | |
| \t\t\tplayer.y += vertical * dt | |
| \t\t\tgrounded = false | |
| \t\t\tvar platforms: Array[Rect2] = [Rect2(60, 545, 840, 25)] if stage == 0 else [Rect2(60, 545, 160, 25), Rect2(650, 430, 130, 20)] | |
| \t\t\tif stage == 0: | |
| \t\t\t\tfor wall in HURDLES: | |
| \t\t\t\t\tplatforms.append(wall) | |
| \t\t\tfor platform in platforms: | |
| \t\t\t\tif vertical >= 0 and old.y + 13 <= platform.position.y + 1 and player.y + 13 >= platform.position.y and player.x > platform.position.x - 8 and player.x < platform.end.x + 8: | |
| \t\t\t\t\tplayer.y = platform.position.y - 13 | |
| \t\t\t\t\tvertical = 0 | |
| \t\t\t\t\tgrounded = true | |
| \t\t\t\t\tif stage == 2 and platform.position.x == 650: | |
| \t\t\t\t\t\tstate = 1 | |
| \t\t\tif player.y > 640: | |
| \t\t\t\tfail_stage() | |
| \t\t\tif stage == 0 and player.x > 850: | |
| \t\t\t\tstage = 1 | |
| \t\t\t\tplace_player() | |
| \t\tif total_time > 120: | |
| \t\t\tstate = -1 | |
| \tqueue_redraw() | |
| func _draw() -> void: | |
| \tbackdrop("THREE STAGE RELAY", "Arrows: move / Space: jump in stages 1 and 3 / Reach the right edge, then land on gold / R: reset") | |
| \tif stage == 0: | |
| \t\tdraw_rect(Rect2(60, 545, 840, 25), Color("45677a")) | |
| \t\tfor wall in HURDLES: | |
| \t\t\tdraw_rect(wall, Color("7e9aab")) | |
| \telif stage == 1: | |
| \t\tdraw_rect(Rect2(65, 130, 835, 440), Color("19334a")) | |
| \t\tfor i in range(4): | |
| \t\t\tdraw_circle(Vector2(280 + i * 155, 350 + sin(total_time * 2 + i) * 170), 24, Color("ea8295")) | |
| \telse: | |
| \t\tdraw_rect(Rect2(60, 545, 160, 25), Color("6b8798")) | |
| \t\tdraw_rect(Rect2(650, 430, 130, 20), Color("e5c474")) | |
| \t\ttext_at("LAND HERE", Vector2(650, 406), 21, Color("e5c474")) | |
| \tdraw_circle(player, 13, Color("80ebcd")) | |
| \ttext_at("Stage %d/3 Lives %d/3 Total time %.1fs" % [stage + 1, health, total_time], Vector2(235, 618), 25) | |
| \tif state != 0: | |
| \t\ttext_at("RELAY COMPLETE! R: again" if state == 1 else "RELAY FAILED. R: retry", Vector2(295, 115), 25) | |
| '''), | |
| 100: ("Moving Constellation", '''extends Node2D | |
| const STARS = [Vector2(230, 230), Vector2(500, 160), Vector2(760, 260), Vector2(650, 490), Vector2(320, 500)] | |
| var path: Array[Vector2] = [] | |
| var next_star = 1 | |
| var drawing = false | |
| var cursor = Vector2.ZERO | |
| var hazard = Vector2(780, 340) | |
| var clock = 0.0 | |
| var remaining = 40.0 | |
| var state = 0 | |
| func reset() -> void: | |
| \tpath.clear() | |
| \tnext_star = 1 | |
| \tdrawing = false | |
| \tclock = 0 | |
| \tremaining = 40 | |
| \tstate = 0 | |
| func segment_distance(point: Vector2, a: Vector2, b: Vector2) -> float: | |
| \tvar direction = b - a | |
| \tif direction.length_squared() < 0.01: | |
| \t\treturn point.distance_to(a) | |
| \tvar t = clampf((point - a).dot(direction) / direction.length_squared(), 0, 1) | |
| \treturn point.distance_to(a + direction * t) | |
| func _unhandled_input(event: InputEvent) -> void: | |
| \tif event is InputEventKey and event.pressed and not event.echo and event.keycode == KEY_R: | |
| \t\treset() | |
| \tif event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and state == 0: | |
| \t\tif event.pressed and not drawing and event.position.distance_to(STARS[0]) < 25: | |
| \t\t\tdrawing = true | |
| \t\t\tpath = [STARS[0]] | |
| \t\t\tcursor = event.position | |
| \t\telif not event.pressed and drawing: | |
| \t\t\tdrawing = false | |
| \t\t\tstate = -1 | |
| func _process(dt: float) -> void: | |
| \tif state == 0: | |
| \t\tclock += dt | |
| \t\thazard = Vector2(480 + cos(clock * 0.7) * 300, 340 + sin(clock * 1.1) * 170) | |
| \t\tif drawing: | |
| \t\t\tremaining -= dt | |
| \t\t\tcursor = get_global_mouse_position() | |
| \t\t\tvar last = path[path.size() - 1] | |
| \t\t\tif segment_distance(hazard, last, cursor) < 27 or remaining <= 0: | |
| \t\t\t\tstate = -1 | |
| \t\t\telse: | |
| \t\t\t\tvar target: Vector2 = STARS[next_star % 5] | |
| \t\t\t\tif cursor.distance_to(target) < 23: | |
| \t\t\t\t\tpath.append(target) | |
| \t\t\t\t\tnext_star += 1 | |
| \t\t\t\t\tif next_star == 6: | |
| \t\t\t\t\t\tstate = 1 | |
| \t\t\t\t\t\tdrawing = false | |
| \tqueue_redraw() | |
| func _draw() -> void: | |
| \tbackdrop("MOVING CONSTELLATION", "Hold left mouse from star 1, drag through 2→3→4→5→1. Keep the active line clear of red. R: reset") | |
| \tfor i in range(5): | |
| \t\tdraw_circle(STARS[i], 15, Color("ffe4a1")) | |
| \t\tdraw_arc(STARS[i], 23, 0, TAU, 24, Color("637caa"), 2) | |
| \t\ttext_at(str(i + 1), STARS[i] + Vector2(-7, -30), 23) | |
| \tfor i in range(1, path.size()): | |
| \t\tdraw_line(path[i - 1], path[i], Color("ffda85"), 4) | |
| \tif drawing and not path.is_empty(): | |
| \t\tdraw_line(path[path.size() - 1], cursor, Color("6de8d0"), 3) | |
| \tdraw_circle(hazard, 23, Color("ef7b90")) | |
| \tdraw_arc(hazard, 29, 0, TAU, 30, Color("7d425b"), 2) | |
| \ttext_at("Next star %d Lines %d/5 Time %.0f" % [next_star % 5 + 1, maxi(0, path.size() - 1), maxf(0, remaining)], Vector2(260, 620), 25) | |
| \tif state != 0: | |
| \t\ttext_at("CONSTELLATION COMPLETE! R: again" if state == 1 else "LINE BROKEN. R: retry", Vector2(245, 115), 25) | |
| '''), | |
| }) | |
| def main() -> None: | |
| prompt_file = ROOT / "data/teacher_prompts.jsonl" | |
| prompts = {int(row["id"].rsplit("-", 1)[1]): row | |
| for row in (json.loads(line) for line in prompt_file.read_text(encoding="utf-8").splitlines() if line.strip()) | |
| if int(row["id"].rsplit("-", 1)[1]) in GAMES} | |
| if set(prompts) != set(GAMES): | |
| raise ValueError("Expected precisely assigned train-only prompt IDs 033–044 and 085–100") | |
| rows, evidence = [], [] | |
| for number, (title, source) in sorted(GAMES.items()): | |
| files = {"project.godot": PROJECT % title, "main.tscn": SCENE, "main.gd": source + UI} | |
| output = canonical_json({"files": files}) | |
| parse_project(output) | |
| instruction = prompts[number]["instruction"] | |
| rows.append({"instruction": instruction, "output": output}) | |
| evidence.append({"prompt_id": prompts[number]["id"], "title": title, | |
| "instruction_sha256": hashlib.sha256(instruction.encode()).hexdigest(), | |
| "output_sha256": hashlib.sha256(output.encode()).hexdigest(), | |
| "project_sha256": project_sha256(files), "gdscript_characters": len(source + UI), | |
| "static_contract_passed": True, "engine_import_verified": False, | |
| "clean_60_second_run_verified": False, "admitted_to_training": False}) | |
| destination = ROOT / "data/codex_teacher_batch_c.jsonl" | |
| temporary = destination.with_suffix(".jsonl.tmp") | |
| temporary.write_text("".join(json.dumps(row, ensure_ascii=False) + "\n" for row in rows), encoding="utf-8") | |
| temporary.replace(destination) | |
| provenance = {"schema_version": 1, "batch": "codex_teacher_batch_c", "count": len(rows), | |
| "teacher": "In-session Codex assistant authored these original complete projects directly", | |
| "teacher_model_identifier": "not separately exposed by the authoring runtime", | |
| "not_generated_by": "Qwen2.5-Coder-14B or a separate paid teacher API", | |
| "source_material": "Original game logic and geometric visuals; repository canonical PROJECT, SCENE, and UI shell", | |
| "license": "Apache-2.0, matching this project's original code license", | |
| "target_runtime": "Godot 4.5.1; prompt wording retained exactly, including Godot 4.4 compatibility request", | |
| "blind_eval_prompts_read": False, | |
| "validation": "Only parse_project static schema/API checks have run. Linux Godot import and a full clean 60-second run remain mandatory before admission.", | |
| "dataset_sha256": hashlib.sha256(destination.read_bytes()).hexdigest(), "examples": evidence} | |
| temporary_provenance = destination.with_suffix(".provenance.json.tmp") | |
| temporary_provenance.write_text(json.dumps(provenance, indent=2) + "\n", encoding="utf-8") | |
| temporary_provenance.replace(destination.with_suffix(".provenance.json")) | |
| print(json.dumps({"candidates": len(rows), "output": str(destination), | |
| "max_gdscript_characters": max(row["gdscript_characters"] for row in evidence), | |
| "static_contract": "passed", "engine_validation": "pending"}, indent=2)) | |
| if __name__ == "__main__": | |
| main() | |