Skip to content

Instantly share code, notes, and snippets.

@cyberpunk042
Created December 17, 2024 09:12
Show Gist options
  • Save cyberpunk042/6192d993b5f68791d3d78953cab458f5 to your computer and use it in GitHub Desktop.
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.
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