Created
December 17, 2024 09:12
-
-
Save cyberpunk042/6192d993b5f68791d3d78953cab458f5 to your computer and use it in GitHub Desktop.
Cube Game: This interactive text-based game lets you navigate a 3D grid of pages, place characters into each cell, and even generate text with GPT assistance. Rotate your cube, select regions, and craft intricate patterns of text, all while exploring a visually represented matrix in a terminal interface.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import curses | |
import argparse | |
import os | |
import sys | |
import time | |
import plotly.graph_objects as go | |
import matplotlib.pyplot as plt | |
import logging | |
import openai | |
openai.api_key = "YOUR_OPENAI_API_KEY" | |
DIMENSIONS = 15 | |
GRID_WIDTH = DIMENSIONS | |
GRID_HEIGHT = DIMENSIONS | |
TOTAL_PAGES = DIMENSIONS | |
DEFAULT_CHAR = "◦" | |
KEY_MAPPING = { | |
"UP": curses.KEY_UP, | |
"DOWN": curses.KEY_DOWN, | |
"LEFT": curses.KEY_LEFT, | |
"RIGHT": curses.KEY_RIGHT, | |
"ENTER": 10, # Move forward a page | |
"BACKSPACE": 8, # Move backward a page | |
} | |
DIRECTIONS = ["ORDINAL", "DOWNWARD", "NONE"] | |
INITIAL_DIRECTIONAL_COMMAND = "NONE" | |
logging.basicConfig( | |
filename="cube_log.log", | |
filemode="w", | |
format="%(asctime)s [%(levelname)s] %(message)s", | |
level=logging.DEBUG | |
) | |
logger = logging.getLogger(__name__) | |
def render_to_3d_plotly(pages, filename="matrix_visualization.html"): | |
logger.info("Rendering 3D plot...") | |
x, y, z, text = [], [], [], [] | |
num_pages = len(pages) | |
for z_index, page in enumerate(pages): | |
for (x_pos, y_pos), char in page.items(): | |
if char.strip(): | |
x.append(x_pos) | |
y.append(y_pos) | |
z.append(z_index) | |
text.append(char) | |
fig = go.Figure() | |
fig.add_trace(go.Scatter3d( | |
x=x, y=y, z=z, mode='text', text=text, | |
textposition="top center", textfont=dict(size=12, color='black') | |
)) | |
fig.update_layout( | |
scene=dict( | |
xaxis=dict(title="Columns (X)", nticks=DIMENSIONS), | |
yaxis=dict(title="Rows (Y)", nticks=DIMENSIONS), | |
zaxis=dict(title="Pages (Z)", nticks=num_pages), | |
), | |
title="3D Matrix Visualization", width=1000, height=800 | |
) | |
fig.write_html(filename) | |
logger.info(f"3D plot saved to {filename}") | |
def rotate_cube_pages(pages, axis, clockwise=True): | |
logger.info(f"Rotating cube around {axis}, clockwise={clockwise}") | |
new_pages = [dict() for _ in range(DIMENSIONS)] | |
if axis == "HORIZONTAL": | |
for z in range(DIMENSIONS): | |
for (x, y), val in pages[z].items(): | |
if clockwise: | |
nx, ny, nz = z, y, DIMENSIONS - 1 - x | |
else: | |
nx, ny, nz = DIMENSIONS - 1 - z, y, x | |
new_pages[nz][(nx, ny)] = val | |
elif axis == "VERTICAL": | |
for z in range(DIMENSIONS): | |
for (x, y), val in pages[z].items(): | |
if clockwise: | |
nx, ny, nz = x, z, DIMENSIONS - 1 - y | |
else: | |
nx, ny, nz = x, DIMENSIONS - 1 - z, y | |
new_pages[nz][(nx, ny)] = val | |
return new_pages | |
def query_gpt(grid, cursor_pos, user_input=None): | |
y, x = cursor_pos | |
summarized_grid = "\n".join("".join(row) for row in grid) | |
additional_context = f"- Theme: {user_input}" if user_input else "" | |
prompt = f""" | |
You are playing a text grid-filling game. | |
INSTRUCTIONS: | |
- Provide between 30 and 60 characters of continuation. | |
- Fit into the matrix without overflow. | |
{additional_context} | |
Respond EXACTLY: | |
<number_of_characters> | |
<generated_characters> | |
""" | |
try: | |
response = openai.ChatCompletion.create( | |
model="gpt-4o-mini", | |
messages=[ | |
{"role": "system", "content": "You are a creative AI filling a text grid."}, | |
{"role": "user", "content": prompt} | |
], | |
max_tokens=1200, | |
temperature=0.7, | |
) | |
content = response.choices[0].message.content.strip() | |
lines = content.split("\n") | |
if len(lines) < 2: | |
logging.error(f"Invalid GPT response: {content}") | |
return 10, DEFAULT_CHAR * 10 | |
try: | |
num_characters = int(lines[0].strip()) | |
except ValueError: | |
logging.error(f"First line not integer: {content}") | |
return 10, DEFAULT_CHAR * 10 | |
generated_text = "\n".join(lines[1:]) | |
return num_characters, generated_text | |
except Exception as e: | |
logging.error(f"GPT query failed: {e}") | |
return 10, DEFAULT_CHAR * 10 | |
def run_simulation(stdscr, pages, input_sequence=None, user_input=None): | |
logger.info("Starting simulation...") | |
directional_command = INITIAL_DIRECTIONAL_COMMAND | |
current_page = 0 | |
cursor_pos = [0, 0] | |
visited = {0: set()} | |
cube_rotation = [0, 0] | |
rotation_mode = False | |
auto_advance_mode = True | |
selected_data = [] | |
selection_mode = False | |
selection_active = False | |
selection_finalized = False | |
selection_start = [0, 0] | |
selection_end = [0, 0] | |
recorded_inputs = [] | |
def draw_page(): | |
stdscr.clear() | |
sel_x1, sel_y1, sel_x2, sel_y2 = 0,0,-1,-1 | |
if selection_active or selection_finalized: | |
sx, sy = selection_start | |
ex, ey = selection_end | |
sel_x1, sel_x2 = min(sx, ex), max(sx, ex) | |
sel_y1, sel_y2 = min(sy, ey), max(sy, ey) | |
for y in range(GRID_HEIGHT): | |
for x in range(GRID_WIDTH): | |
c = pages[current_page].get((x, y), DEFAULT_CHAR) | |
in_selection = False | |
if (selection_active or selection_finalized) and sel_x1 <= x <= sel_x2 and sel_y1 <= y <= sel_y2: | |
in_selection = True | |
if [x, y] == cursor_pos and pages[current_page].get((x, y)): | |
char = "▮" | |
elif [x, y] == cursor_pos: | |
char = "○" | |
else: | |
char = c | |
if in_selection: | |
char = "█" | |
stdscr.addstr(y, x * 2, char) | |
mode_text = "Rotation Mode" if rotation_mode else "Normal Mode" | |
stdscr.addstr(GRID_HEIGHT + 1, 0, f"Page: {current_page+1}/{TOTAL_PAGES} | Cursor: {cursor_pos}") | |
stdscr.addstr(GRID_HEIGHT + 2, 0, f"Cube Rotation: {cube_rotation}") | |
sel_mode_text = "ON" if selection_mode else "OFF" | |
stdscr.addstr(GRID_HEIGHT + 3, 0, f"Sel Mode: {sel_mode_text}") | |
stdscr.refresh() | |
def move(direction): | |
old_cursor = cursor_pos[:] | |
x, y = cursor_pos | |
if direction == "UP": | |
cursor_pos[1] = (y - 1) % GRID_HEIGHT | |
elif direction == "DOWN": | |
cursor_pos[1] = (y + 1) % GRID_HEIGHT | |
elif direction == "LEFT": | |
cursor_pos[0] = (x - 1) % GRID_WIDTH | |
elif direction == "RIGHT": | |
cursor_pos[0] = (x + 1) % GRID_WIDTH | |
visited[current_page].add(tuple(cursor_pos)) | |
if selection_active: | |
selection_end[0], selection_end[1] = cursor_pos | |
if selection_finalized: | |
dx = cursor_pos[0] - old_cursor[0] | |
dy = cursor_pos[1] - old_cursor[1] | |
selection_start[0] += dx | |
selection_start[1] += dy | |
selection_end[0] += dx | |
selection_end[1] += dy | |
def change_page(step): | |
nonlocal current_page | |
current_page = (current_page + step) % TOTAL_PAGES | |
if current_page not in visited: | |
visited[current_page] = set() | |
visited[current_page].add(tuple(cursor_pos)) | |
def write_char(char): | |
pages[current_page][tuple(cursor_pos)] = char | |
draw_page() | |
if not auto_advance_mode: | |
return | |
if directional_command == "ORDINAL": | |
if cursor_pos[0] < GRID_WIDTH - 1: | |
move("RIGHT") | |
else: | |
move("DOWN") | |
for _ in range(GRID_WIDTH - 1): | |
move("LEFT") | |
elif directional_command == "DOWNWARD": | |
if cursor_pos[1] < GRID_HEIGHT - 1: | |
move("DOWN") | |
else: | |
if cursor_pos[0] < GRID_WIDTH - 1: | |
move("RIGHT") | |
for _ in range(GRID_HEIGHT - 1): | |
move("UP") | |
else: | |
for _ in range(GRID_HEIGHT - 1): | |
move("UP") | |
for _ in range(GRID_WIDTH - 1): | |
move("LEFT") | |
elif directional_command == "NONE": | |
pass | |
def pages_to_grid(): | |
grid = [] | |
for yy in range(GRID_HEIGHT): | |
row = [] | |
for xx in range(GRID_WIDTH): | |
row.append(pages[current_page].get((xx, yy), DEFAULT_CHAR)) | |
grid.append(row) | |
return grid | |
def feed_gpt(): | |
grid = pages_to_grid() | |
y, x = cursor_pos[1], cursor_pos[0] | |
num_chars, text = query_gpt(grid, (y, x), user_input=user_input) | |
yy, xx = y, x | |
for ch in text: | |
if yy >= GRID_HEIGHT: | |
break | |
if ch == '\n': | |
yy += 1 | |
xx = 0 | |
else: | |
pages[current_page][(xx, yy)] = ch | |
xx += 1 | |
if xx >= GRID_WIDTH: | |
yy += 1 | |
xx = 0 | |
cursor_pos[0], cursor_pos[1] = xx, yy | |
draw_page() | |
def copy_selection_data(): | |
selected_data.clear() | |
sx, sy = selection_start | |
ex, ey = selection_end | |
x1, x2 = min(sx, ex), max(sx, ex) | |
y1, y2 = min(sy, ey), max(sy, ey) | |
for yy in range(y1, y2+1): | |
row_data = [] | |
for xx in range(x1, x2+1): | |
char = pages[current_page].get((xx, yy), DEFAULT_CHAR) | |
row_data.append(char) | |
selected_data.append(row_data) | |
def clear_old_selection_region(): | |
sx, sy = selection_start | |
ex, ey = selection_end | |
x1, x2 = min(sx, ex), max(sx, ex) | |
y1, y2 = min(sy, ey), max(sy, ey) | |
for yy in range(y1, y2+1): | |
for xx in range(x1, x2+1): | |
pages[current_page][(xx, yy)] = DEFAULT_CHAR | |
def place_selection_data(): | |
sx, sy = selection_start | |
ex, ey = selection_end | |
x1, x2 = min(sx, ex), max(sx, ex) | |
y1, y2 = min(sy, ey), max(sy, ey) | |
height = y2 - y1 + 1 | |
width = x2 - x1 + 1 | |
for row_i in range(height): | |
for col_i in range(width): | |
pages[current_page][(x1 + col_i, y1 + row_i)] = selected_data[row_i][col_i] | |
while True: | |
draw_page() | |
if input_sequence and len(input_sequence) > 0: | |
command = input_sequence.pop(0) | |
key = KEY_MAPPING.get(command, ord(command)) if len(command) == 1 else KEY_MAPPING.get(command, None) | |
time.sleep(0.005) | |
else: | |
if directional_command == "NONE": directional_command = "ORDINAL" | |
key = stdscr.getch() | |
matched_command = None | |
for cmd, mk in KEY_MAPPING.items(): | |
if key == mk: | |
matched_command = cmd | |
break | |
if matched_command: | |
recorded_inputs.append(matched_command) | |
else: | |
if key is not None and key != -1 and 32 <= key < 127: | |
ch = chr(key) | |
recorded_inputs.append(ch) | |
# F3 to cycle directional_command | |
if key == curses.KEY_F3: | |
current_index = DIRECTIONS.index(directional_command) | |
directional_command = DIRECTIONS[(current_index + 1) % len(DIRECTIONS)] | |
# F4 to toggle selection mode | |
if key == curses.KEY_F4: | |
selection_mode = not selection_mode | |
if not selection_mode: | |
selection_active = False | |
selection_finalized = False | |
selected_data.clear() | |
if selection_mode: | |
if key == ord(' '): | |
if not selection_active and not selection_finalized: | |
selection_active = True | |
selection_finalized = False | |
selection_start[0], selection_start[1] = cursor_pos | |
selection_end[0], selection_end[1] = cursor_pos | |
elif selection_active: | |
selection_active = False | |
selection_finalized = True | |
copy_selection_data() | |
clear_old_selection_region() | |
elif selection_finalized: | |
place_selection_data() | |
selection_finalized = False | |
selected_data.clear() | |
if rotation_mode: | |
if key == curses.KEY_UP: | |
pages = rotate_cube_pages(pages, "VERTICAL", True) | |
elif key == curses.KEY_DOWN: | |
pages = rotate_cube_pages(pages, "VERTICAL", False) | |
elif key == curses.KEY_LEFT: | |
pages = rotate_cube_pages(pages, "HORIZONTAL", True) | |
elif key == curses.KEY_RIGHT: | |
pages = rotate_cube_pages(pages, "HORIZONTAL", False) | |
elif key == 27: | |
rotation_mode = False | |
else: | |
# Normal mode keys | |
if key == curses.KEY_UP: | |
move("UP") | |
elif key == curses.KEY_DOWN: | |
move("DOWN") | |
elif key == curses.KEY_LEFT: | |
move("LEFT") | |
elif key == curses.KEY_RIGHT: | |
move("RIGHT") | |
elif key is not None and 32 <= key < 127 and not selection_finalized: | |
if not selection_mode: | |
write_char(chr(key)) | |
elif key == 10: | |
change_page(1) | |
elif key in [8, 127, curses.KEY_BACKSPACE]: | |
change_page(-1) | |
elif key == 4: # Ctrl+D to exit | |
logger.info("User requested exit.") | |
break | |
elif key == 27: # ESC for rotation mode | |
rotation_mode = True | |
elif key == 7: # Ctrl+G for GPT query | |
feed_gpt() | |
return pages, recorded_inputs | |
if __name__ == "__main__": | |
input_sequence = ['RIGHT', 'RIGHT', 'RIGHT', 'RIGHT', 'RIGHT', 'RIGHT', 'RIGHT', 'RIGHT', 'RIGHT', 'RIGHT', 'RIGHT', 'RIGHT', 'RIGHT', 'RIGHT', 'h', 'DOWN', 'DOWN', 'DOWN', 'DOWN', 'LEFT', 'LEFT', 'd', 'DOWN', 'i', 'DOWN', 't', 'DOWN', ' ', 'DOWN', 'p', 'ENTER', 'UP', 'UP', 'UP', 'UP', 'UP', 'UP', 'UP', 'UP', 'RIGHT', 'RIGHT', 'e', 'DOWN', 'DOWN', 'DOWN', 'DOWN', 'LEFT', 'LEFT', ' ', 'DOWN', 'z', 'DOWN', 'h', 'DOWN', 'o', 'DOWN', 'i', 'ENTER', 'UP', 'UP', 'UP', 'UP', 'UP', 'UP', 'UP', 'UP', 'RIGHT', 'RIGHT', 'l', 'DOWN', 'DOWN', 'DOWN', 'LEFT', 'LEFT', 'T', 'DOWN', 'b', 'DOWN', 'o', 'DOWN', 'e', 'DOWN', 'f', 'DOWN', 'n', 'ENTER', 'UP', 'UP', 'UP', 'UP', 'UP', 'UP', 'UP', 'UP', 'RIGHT', 'RIGHT', 'l', 'DOWN', 'DOWN', 'DOWN', 'LEFT', 'LEFT', 'h', 'DOWN', 'e', 'DOWN', 'n', 'DOWN', ' ', 'DOWN', ' ', 'DOWN', 'k', 'ENTER', 'UP', 'UP', 'UP', 'UP', 'UP', 'UP', 'UP', 'UP', 'RIGHT', 'RIGHT', 'o', 'DOWN', 'DOWN', 'DOWN', 'LEFT', 'LEFT', 'e', 'DOWN', 'l', 'DOWN', ',', 'DOWN', 's', 'DOWN', 'o', 'DOWN', '.', 'ENTER', 'UP', 'UP', 'UP', 'UP', 'UP', 'UP', 'UP', 'UP', 'RIGHT', 'RIGHT', ' ', 'DOWN', 'DOWN', 'DOWN', 'LEFT', 'LEFT', ' ', 'DOWN', 'o', 'DOWN', ' ', 'DOWN', 'k', 'DOWN', 'r', 'ENTER', 'UP', 'UP', 'UP', 'UP', 'UP', 'UP', 'UP', 'RIGHT', 'RIGHT', 'w', 'DOWN', 'DOWN', 'DOWN', 'LEFT', 'LEFT', 's', 'DOWN', 'w', 'DOWN', 'p', 'DOWN', 'y', 'DOWN', 'a', 'ENTER', 'UP', 'UP', 'UP', 'UP', 'UP', 'UP', 'UP', 'RIGHT', 'RIGHT', 'o', 'DOWN', 'DOWN', 'DOWN', 'LEFT', 'LEFT', 'u', 'DOWN', ' ', 'DOWN', 'a', 'DOWN', ' ', 'DOWN', 'n', 'ENTER', 'UP', 'UP', 'UP', 'UP', 'UP', 'UP', 'UP', 'RIGHT', 'RIGHT', 'r', 'DOWN', 'DOWN', 'DOWN', 'LEFT', 'LEFT', 'n', 'DOWN', 't', 'DOWN', 'i', 'DOWN', 'i', 'DOWN', 'g', 'ENTER', 'UP', 'UP', 'UP', 'UP', 'UP', 'UP', 'UP', 'RIGHT', 'RIGHT', 'l', 'DOWN', 'DOWN', 'DOWN', 'LEFT', 'LEFT', ' ', 'DOWN', 'h', 'DOWN', 'n', 'DOWN', 'n', 'DOWN', 'e', 'ENTER', 'UP', 'UP', 'UP', 'UP', 'UP', 'UP', 'UP', 'RIGHT', 'RIGHT', 'd', 'DOWN', 'DOWN', 'DOWN', 'LEFT', 'LEFT', 'd', 'DOWN', 'e', 'DOWN', 't', 'DOWN', ' ', 'DOWN', ' ', 'ENTER', 'UP', 'UP', 'UP', 'UP', 'i', 'DOWN', ' ', 'DOWN', 'i', 'DOWN', 'h', 'DOWN', 'a', 'ENTER', 'UP', 'UP', 'UP', 'UP', 'p', 'DOWN', 'h', 'DOWN', 'n', 'DOWN', 'u', 'DOWN', 'n', 'ENTER', 'UP', 'UP', 'UP', 'UP', 'p', 'DOWN', 'o', 'DOWN', 'g', 'DOWN', 'e', 'DOWN', 'd', 'ENTER', 'UP', 'UP', 'UP', 'UP', 'e', 'DOWN', 'r', 'DOWN', ' ', 'DOWN', 's', 'DOWN', ' '] | |
pages = [{} for _ in range(DIMENSIONS)] | |
user_input = None | |
try: | |
parser = argparse.ArgumentParser(description="2D Simulation Game with GPT Support.") | |
parser.add_argument("--gpt", action="store_true", help="Enable GPT mode for auto-filling characters.") | |
parser.add_argument("--input", type=str, help="Optional theme for GPT prompt.") | |
args = parser.parse_args() | |
if args.gpt: | |
openai.api_key = "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" # Insert your key if needed | |
if args.input: | |
user_input = args.input | |
pages, recorded_inputs = curses.wrapper(run_simulation, pages, input_sequence=input_sequence, user_input=user_input) | |
except Exception as e: | |
logger.error(f"Error: {e}", exc_info=True) | |
finally: | |
# Gather all non-default cells | |
non_default_cells = [] | |
for z in range(TOTAL_PAGES): | |
for y in range(GRID_HEIGHT): | |
for x in range(GRID_WIDTH): | |
char = pages[z].get((x, y), DEFAULT_CHAR) | |
if char != DEFAULT_CHAR: | |
non_default_cells.append((z, y, x, char)) | |
# If no non-default cells, final_sequence is empty | |
final_sequence = [] | |
if non_default_cells: | |
# Sort cells by (z, y, x) | |
non_default_cells.sort(key=lambda c: (c[0], c[1], c[2])) | |
current_z, current_y, current_x = 0, 0, 0 | |
for (z, y, x, ch) in non_default_cells: | |
# Move pages | |
while current_z < z: | |
final_sequence.append("ENTER") | |
current_z += 1 | |
while current_z > z: | |
final_sequence.append("BACKSPACE") | |
current_z -= 1 | |
# Move rows | |
while current_y < y: | |
final_sequence.append("DOWN") | |
current_y += 1 | |
while current_y > y: | |
final_sequence.append("UP") | |
current_y -= 1 | |
# Move columns | |
while current_x < x: | |
final_sequence.append("RIGHT") | |
current_x += 1 | |
while current_x > x: | |
final_sequence.append("LEFT") | |
current_x -= 1 | |
# Type the character | |
final_sequence.append(ch) | |
try: | |
render_to_3d_plotly(pages) | |
except Exception as e: | |
logger.error(f"Error rendering 3D: {e}", exc_info=True) | |
logger.info("final_sequence = " + repr(final_sequence)) | |
print("You are the Caretaker of the Infinite Archive. Explore and reveal its secrets!") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment