The Occult Footprints Log

Some things in the valley do not stay buried. The hills keep their fog and their secrets close. Over time, a pattern has crept into my notebooks. The strange impressions left behind, year after year, on the ground of Roanoke and its neighboring shadows. Barefoot, clawed, or otherwise, they arrive in the quiet hours, vanish just as quick, and never seem quite human.

Below is the log as I have kept it, three years of careful entries. I share them here not as proof, only as record.




Year One — Beginnings

January
Snow over Yellow Sulphur Springs. One set of bare prints trailing to the sycamore, ending abruptly.

February
Sulfur sting in the caverns, dust scuffed by dragging toes.

March
Rain-soft mud near Hanging Rock, prints leaping like no human stride.

April
Loops of tracks near Mill Mountain overlook, rain erasing them before I could trace further.

May
Blackened circles in grass, edged with small strange prints.

June
Three tracks waiting side by side in cavern dust, then gone.

July
Five sets of impressions on the slope, scoured smooth before I returned.

August
Narrow heel to toe prints pacing Patterson Avenue block by block.

September
Circling prints round a tree trunk, as if watching endlessly.

October
Barefoot impressions in alley leading to warm brick wall.

November
Deep riverbank marks ending beneath the current.

December
Two prints in snow at Mill Mountain’s edge, then only white silence.

Visual cue: imagine snow faintly embossed with strange, precise footsteps under the glow of the neon star above the valley.




Year Two — Echoes

January
Thick fog. Footprints appeared overnight across my porch, wet outlines steaming in the cold. None led away.

February
Cavern lantern cracked. The group left early. I stayed and found the wall damp with handprints pressed high overhead.

March
Near the duck pond, muddy prints too wide for a man. Ducks avoided the water that day.

April
Bonfire rumor again. Smoke seen drifting from Yellow Sulphur, though no fire crews came. Prints this time were in pairs, one large, one small, circling.

May
I dreamed of drumming, woke to see dust stirred on the windowsill inside my locked room. Five small prints in a row.

June
Heat lightning in the caverns. Rock smelled of ozone. Prints were not on the floor but high up the wall, like something had climbed sideways.

July
River low. Children playing found impressions of knees, hands, and something tail-like pressed in mud.

August
Neighbor swore she saw pale figures watching from Patterson turret. That night I saw them too, but the prints on the sidewalk below were claw tipped.

September
Circles burned again into grass near the old springhouse. No ash this time, only frost, though the night was warm.

October
Theatre alley same wall as last year. This time the prints were smaller, leading out of the brick.

November
Cold rain. I followed tracks across the cemetery until they stopped before a stone angel, toes pressed against its base.

December
Snowfall heavy. No prints all day, but in the morning writing appeared traced in them. Three words I did not know. Letters sank deep, like weight pressed them.

Imagery cue: envision dim candlelight flickering across footprints and half-erased symbols in mud and dust.




Year Three — Patterns

January
New year. I kept watch at Yellow Sulphur. Snow fell clean, unbroken. Just before dawn a line of prints appeared all at once, stretching across the entire field in a single instant.

February
Inside the caverns faint humming echoed. Dust held no prints, but condensation drew shapes on the stone, toe marks outlined in water beads.

March
Thunderstorm rolled through. After, in the mud, I found impressions moving both forward and backward, as if one figure retraced its steps exactly.

April
Star on the mountain went out for maintenance. For those three nights prints multiplied near its base, hundreds, weaving like a crowd.

May
The old resort showed signs of trespass. Candles melted on the floor, wax pooled thick. Around them overlapping barefoot tracks that never left the building.

June
Heat shimmered. Cavern tour interrupted by sudden blackout. When lights returned dust bore a child-sized footprint leading toward the exit. The group swore no children were present.

July
River swelled with summer storms. Debris floated down. Among branches and trash a shoe, waterlogged, no pair. The mudbank bore a single matching track.

August
Turret house on Patterson Avenue condemned, boards nailed tight. Still, people whisper of a face at the top window. That week I found prints on the sidewalk below, facing outward as if watching the street.

September
At the edge of town a circle of mushrooms appeared. Inside the ring every blade of grass lay flattened by tiny prints spiraling inward.

October
Halloween crowd outside the theatre. Amid their scuffs I noticed a single barefoot trail, crisp, sharp, as if pressed hours later. It led to my own shadow.

November
Frost clung to gravestones. On one, words formed in crystalline footprints: wait.

December
Silent snow, the valley hushed. I expected the usual pair at the overlook, but this year four sets together, lined neatly at the cliff’s edge, gazing out.

Visual cue: picture a cliffside blanketed in snow, four silent watchers outlined in footprints, under the steady glow of Mill Mountain’s star.




Closing

Three years, and the valley has not run out of strangeness. The footprints come and go, unclaimed, unchased, unbroken in their pattern. Some say it is tricks of weather, or the wandering of trespassers, or the fancies of a restless imagination.

But in quiet moments, when I find myself following them, I wonder if it is the other way around. Are they are the ones following me?

Ghost walk – Pinkard

I joined the ghost walk just after sundown, the air cooling quick in that Blue Ridge way, where the warmth of day drains fast once the sun slips behind the ridges. The guide’s lantern cast a soft circle as we moved through Salem’s narrow streets. Crickets stitched their steady chorus into the background, and every so often a breeze carried the faint scent of woodsmoke from a chimney already preparing for autumn.

Most of the stories were familiar shapes, soldiers, sudden deaths, whispered names of old hotels and graveyards, but when the guide began to speak of the Pinkard house the tone shifted. The group leaned in as if pulled closer by the story itself.

She told us about John Henry Pinkard, the folk healer of the late 1800s, known for his jars of roots and tinctures. People came to him when doctors had no answers, and sometimes he helped. Sometimes he did not. What lingered, though, was stranger. The shelves of jugs were said to cry at night, their sealed mouths releasing low moans like breath struggling to escape. Some neighbors said it was only the gases of fermenting herbs. Others believed the jars had absorbed the voices of sickness, that the house itself had become a keeper of grief.

Even after Pinkard was gone, the house never grew quiet. Passersby swore they saw a figure slip across the windows, or felt a heaviness press against their chest as they walked by, as if the building itself was paying close attention. The guide paused then, and the silence of the street seemed to deepen. The insects still sang, but softer, and the cool air carried just enough stillness to make the tale feel near.

There was no violent history to anchor the story, no single bloody tragedy. Just a house that remembered every whispered prayer and every final breath that crossed its threshold. The kind of haunting that does not scream but sighs.

By the time the walk ended and I made my way back toward the car, the streets were nearly empty. Porch lights glowed here and there, throwing shadows across old fences. I found myself glancing up at the darker houses, wondering which one might have been the Pinkard place, wondering if even now it still remembered, if somewhere inside, a jar was letting out a slow, sorrowful breath into the night air.

Day 20663

Saturday night, rain steady outside. One of those nights where the streetlights look blurry and the whole world feels a little softer. Made some chamomile tea and put on Working Girl. Been a while since I last saw it, but it’s still such a comfort watch.

Melanie Griffith’s Tess McGill is the heart of it. A Staten Island secretary with brains, nerve, and that quiet kind of hope you recognize if you’ve ever felt underestimated. Sigourney Weaver is all sleek suits and sharp edges as Katharine Parker. Harrison Ford grins his way through as Jack Trainer, but he softens in the right places.

Mike Nichols captures late 80s New York like a living thing. Office lights buzzing, taxis honking, the smell of copier toner and coffee you can almost taste. Carly Simon’s “Let the River Run” rolls over it all like a hymn, and suddenly you believe Tess can climb her way to the top with nothing but grit and a borrowed suit.

Watching it now feels like finding an old Polaroid in a shoebox — colors faded, but alive. Tess feels real. She could be someone you’d see at a diner counter, scribbling plans in a notebook over a cup of coffee that’s gone cold.

Saw a possum wander past the window while the rain kept on, steady and soft. The kind of small moment that makes you smile without thinking. By the time the credits rolled, I felt lighter. The movie’s a reminder that ambition can be kind, and stubbornness can be gentle.

A good film for a rainy night.

Robin’s nest

Mama Robin on flag
Babies in the nest
Cat / Robin standoff

Morning window-light, cat perched sentinel, ears flicking at the robin’s sharp tut-tut-tut from the flag stake outside. She has claimed the yard as her kingdom for now. Her breast is bright as ember, feathers ruffled with purpose. Every step past the hedge, every tilt of the curtain, draws her vigilance. I imagine her heart beating faster than my own, a drumbeat of protection.

Later, I peek into the maple and find the reason: two tiny beaks stretching skyward, tufted crowns of down and pinfeathers, not yet ready to know the sky. The nest is a woven cradle of sticks and hope, a cradle that breathes with each soft tremor of chicks begging for more. Their eyes are half-closed, but their hunger burns awake.

The robin watches me watching, neither of us entirely trusting, but both bound to this moment. She sings less now, too busy with duty, worms plucked from the damp soil and delivered with tenderness disguised as efficiency. Fierce and tireless, a flame against the green world.

The cat sighs from the sill, thwarted hunter behind glass, while I feel the tug of seasons, how life unfurls in branches, how small voices become the loud chorus of summer. For now, the robin stands guard, and I stand witness.

The butterfly emerges!

Monarch out of the chrysalis

the hanging lantern opened

early afternoon, porch still cool. stepped outside to find a small miracle clinging under the eaves. a monarch had slipped free from its glassy coffin. wings damp, folded tight, slowly breathing into shape. the chrysalis above it, once green and gold, now nothing more than a hollow paper lantern. finished.

the butterfly trembled against it, orange sails heavy with gravity. veins filling, colors darkening, each beat of the heart writing a little more of the story in black and gold.

thoughts drifted toward patience. all that unseen work in silence, days of dissolution and rearrangement, until one quiet morning something new opens its eyes. sunlight stitched into wings. the world noisy all around, but here was proof of stillness doing its work.

fragile. defiant. not yet ready for air. it hangs and waits, wings drying, gathering strength. i wait too. remembering that emergence takes its own time.

milkweed moves in the breeze. far south, a forest waits. for now, just this bright spark, clinging to its empty lantern, preparing.

today, i witnessed transformation. tomorrow, it will be gone, riding the wind.

🦋

current mood: quiet delight
current music: wind through leaves, somewhere a dove calling

#roanokeva
#monarch #butterfly #chrysalis

Day 20,660 It’s the monarch!

Over the front door, a chrysalis hangs. Monarch. Green jade lantern with a seam of gold, balanced in its little hook. I open the door and it sways just slightly, steadying again, as if nothing at all can disturb its long dream.

It changes there, unseen. Inside, the body is rewritten, cells unspooling, wings folded like secret letters. From the outside, only stillness, except for the shimmer of gold dots, like stars pinned to its surface.

Every time I step out, I look up. A reminder. A spell. The threshold feels different with this little guardian over it, a quiet companion of the in between. Doorways are always a kind of crossing. This one now more so.

Soon it will split, and from silence will come the crackle of wings, orange and black, lifting into air. For now, I pass under it, carrying the hush of transformation into my day.

Number converter – python

# number_converter.py
# Works in Pydroid 3 (Android Python environment)

import inflect

# Create engine for number-to-words
p = inflect.engine()

# Function to convert to Roman numerals
def to_roman(num):
    if not (0 < num < 4000):
        return “Roman numerals only support 1–3999”
    values = [
        (1000, “M”), (900, “CM”), (500, “D”), (400, “CD”),
        (100, “C”), (90, “XC”), (50, “L”), (40, “XL”),
        (10, “X”), (9, “IX”), (5, “V”), (4, “IV”), (1, “I”)
    ]
    result = “”
    for val, symbol in values:
        while num >= val:
            result += symbol
            num -= val
    return result

# Function to convert number to binary
def to_binary(num):
    return bin(num)[2:]

# Function to convert number to hexadecimal
def to_hex(num):
    return hex(num)[2:].upper()

# Function to convert number to words
def to_words(num):
    return p.number_to_words(num)

# Main interactive loop
def main():
    while True:
        try:
            num = int(input(“\nEnter a number (or -1 to quit): “))
            if num == -1:
                print(“Goodbye!”)
                break

            print(“\nChoose conversion:”)
            print(“1. Roman Numerals”)
            print(“2. Binary”)
            print(“3. Hexadecimal”)
            print(“4. Words”)

            choice = input(“Enter choice (1-4): “)

            if choice == “1”:
                print(“Roman Numeral:”, to_roman(num))
            elif choice == “2”:
                print(“Binary:”, to_binary(num))
            elif choice == “3”:
                print(“Hexadecimal:”, to_hex(num))
            elif choice == “4”:
                print(“Words:”, to_words(num))
            else:
                print(“Invalid choice!”)

        except ValueError:
            print(“Please enter a valid integer.”)

if __name__ == “__main__”:
    main()

Summoning breakfast in the form of a fish for the kitty-boss

The cat has made me into a wizard, at least in the quiet story she tells with her eyes. She believes I can summon food from the hidden places. An empty bowl waits, and with a few simple gestures, it is full again. To her, this is not routine – it is magic.

She approaches with ceremony: weaving circles around my legs, chanting her meows, tail coiling like a ribbon of smoke. Each time she comes, it feels like a chapter retold – the faithful one calling upon the hearth wizard to bring forth plenty. I answer, as good wizards do. The bowl fills, and the covenant holds true.

In her folklore, I suspect she keeps me as the household mage, draped not in robes but in the ordinary, bending the world to keep her safe and fed. She believes in the ritual completely, and there is power in being so believed in.

She eats, and her purr is the hymn that seals the tale – a soft song of trust, love and satisfaction.

And outside, the day lingers. The cicadas chant, the stars scratch their worn runes across the sky… the sun and moon drift above us like gold and silver lanterns.

In moments like these, it feels true enough: the simplest acts are often the deepest spells, and love itself is the oldest magic we know.

#doodle #digitalmarkers #roanokeva #cat #magic #love #hungry #fish

Day 20,658



Morning robot divination, the runes clattered out:

ᚱ Raidho – the road, the turning wheel, journeys both on foot and inward.

ᛗ Mannaz – humanity, cooperation, the reflection of self through others.

ᚺ Hagalaz – hail, disruption, sudden ice cracking branches, unasked but clarifying.

Together, a reminder: movement is happening, best shared with others, even if the storm comes along to shake it all loose.

The I Ching followed with Hexagram 53 – 漸 (Jiàn), Gradual Progress. The lines stacked like this:

7 —   (young yang)
9 — — (old yang, changing to yin)
6 —   (old yin, changing to yang)
7 —   (young yang)
8 — — (young yin)
8 — — (young yin)

Like the flight of the wild goose: slow, patient, finding safe landings step by step. The changes in the middle — strength softening, softness firming — say that flexibility is part of progress.

So the whole spread leans into:

ᚱ the road opens,

ᛗ companionship matters,

ᚺ the storm disrupts,

but 漸 Gradual Progress carries through, steady wings in the long arc of travel.


Later, I walked the greenway and watched a gaggle of geese settling at the water’s edge, right on cue. The sky broke open with a shaft of sunlight between gray banks of cloud. A couple of strangers paused to chat at the benches  – small, welcome echoes of ᛗ. Storm brewing on the horizon, but storms move on. Step by step, wheel by wheel, goose by goose.

Polygons python test

I need to work on the 12 sider

Showing 20 sider

import pygame
import math

# — Configuration —
WIDTH, HEIGHT = 800, 600
BACKGROUND_COLOR = (10, 10, 20)      # Dark Blue-Gray
LINE_COLOR = (255, 255, 255)         # White
ROTATION_SPEED = 0.01

# — Menu Configuration —
pygame.font.init()
try:
    MENU_FONT = pygame.font.SysFont(“sans”, 28)
except:
    MENU_FONT = pygame.font.Font(None, 36)

MENU_TEXT_COLOR = (200, 200, 200) # Light Gray
MENU_HIGHLIGHT_COLOR = (255, 215, 0) # Gold
MENU_BACKGROUND_COLOR = (30, 30, 40)
MENU_WIDTH = 220

# — Platonic Solid Data —
def get_platonic_solid_data(name):
    “””Provides the correctly scaled vertices and faces for a given Platonic solid.”””

    if name == “Tetrahedron”:
        scale = 150
        v_base = [(1,1,1), (-1,-1,1), (-1,1,-1), (1,-1,-1)]
        f = [(0,1,2), (0,2,3), (0,3,1), (1,3,2)]
        v = [(p[0]*scale, p[1]*scale, p[2]*scale) for p in v_base]
        return v, f

    if name == “Cube”:
        scale = 130
        v_base = [(-1,-1,1), (1,-1,1), (1,1,1), (-1,1,1),
                  (-1,-1,-1), (1,-1,-1), (1,1,-1), (-1,1,-1)]
        f = [(0,1,2,3), (4,5,1,0), (5,6,2,1), (6,7,3,2), (7,4,0,3), (7,6,5,4)]
        v = [(p[0]*scale, p[1]*scale, p[2]*scale) for p in v_base]
        return v, f

    if name == “Octahedron”:
        scale = 160
        v_base = [(0,0,1), (0,0,-1), (1,0,0), (-1,0,0), (0,1,0), (0,-1,0)]
        f = [(0,2,4), (0,4,3), (0,3,5), (0,5,2), (1,2,5), (1,5,3), (1,3,4), (1,4,2)]
        v = [(p[0]*scale, p[1]*scale, p[2]*scale) for p in v_base]
        return v, f

    if name == “Dodecahedron”:
        # — CORRECTED AND VERIFIED DATA —
        scale = 100
        phi = (1 + math.sqrt(5)) / 2
        v_base = [
            (-1, -1, -1), ( 1, -1, -1), ( 1,  1, -1), (-1,  1, -1),
            (-1, -1,  1), ( 1, -1,  1), ( 1,  1,  1), (-1,  1,  1),
            (0, -1/phi, -phi), (0, -1/phi,  phi), (0,  1/phi, -phi), (0,  1/phi,  phi),
            (-1/phi, -phi, 0), ( 1/phi, -phi, 0), (-1/phi,  phi, 0), ( 1/phi,  phi, 0),
            (-phi, 0, -1/phi), ( phi, 0, -1/phi), ( phi, 0,  1/phi), (-phi, 0,  1/phi)
        ]
        f = [
            (3, 11,  7, 18, 19), (3, 19, 16,  2, 14), (3, 14, 15,  1, 11),
            (7, 11,  1,  5, 18), (1,  5, 13,  0, 10), (5, 13,  4, 17, 18),
            (4, 13,  0,  8, 12), (0,  8, 16, 19, 18), (2, 14, 15,  9, 10),
            (6, 17,  8, 12, 16), (6, 12,  4, 13, 5), (6, 17,  9, 15, 14)
        ]
        v = [(p[0]*scale, p[1]*scale, p[2]*scale) for p in v_base]
        return v, f

    if name == “Icosahedron”:
        scale = 130
        phi = (1 + math.sqrt(5)) / 2
        v_base = [
            (-1, phi, 0), ( 1, phi, 0), (-1, -phi, 0), ( 1, -phi, 0),
            (0, -1, phi), (0, 1, phi), (0, -1, -phi), (0, 1, -phi),
            (phi, 0, -1), (phi, 0, 1), (-phi, 0, -1), (-phi, 0, 1)
        ]
        f = [
            (0, 11, 5), (0, 5, 1), (0, 1, 7), (0, 7, 10), (0, 10, 11), (1, 5, 9),
            (5, 11, 4), (11, 10, 2), (10, 7, 6), (7, 1, 8), (3, 9, 4), (3, 4, 2),
            (3, 2, 6), (3, 6, 8), (3, 8, 9), (4, 9, 5), (2, 4, 11), (6, 2, 10),
            (8, 6, 7), (9, 8, 1)
        ]
        v = [(p[0]*scale, p[1]*scale, p[2]*scale) for p in v_base]
        return v, f

# — State Variables —
solid_names = [“Tetrahedron”, “Cube”, “Octahedron”, “Icosahedron”, “Dodecahedron”]
current_solid_name = solid_names[0]
vertices, faces = [], []
angle_x, angle_y, angle_z = 0, 0, 0

# — Function to load a solid —
def load_solid(name):
    global current_solid_name, vertices, faces, angle_x, angle_y, angle_z
    current_solid_name = name
    vertices, faces = get_platonic_solid_data(name)
    angle_x, angle_y, angle_z = 0, 0, 0

# — Build Menu Items —
menu_rects = {}
y_offset = 20
for name in solid_names:
    text_surf = MENU_FONT.render(name, True, MENU_TEXT_COLOR)
    rect = text_surf.get_rect(topleft=(20, y_offset))
    menu_rects[name] = rect
    y_offset += 40

# — Pygame Setup —
pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption(“Select and Spin a Platonic Solid”)
clock = pygame.time.Clock()
load_solid(current_solid_name)

# — Main Loop —
running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT: running = False
        if event.type == pygame.KEYDOWN and event.key == pygame.K_AC_BACK: running = False
        if event.type == pygame.MOUSEBUTTONDOWN:
            if event.button == 1:
                for name, rect in menu_rects.items():
                    if rect.collidepoint(event.pos) and name != current_solid_name:
                        load_solid(name)
                        break

    angle_x += ROTATION_SPEED
    angle_y += ROTATION_SPEED
    angle_z += ROTATION_SPEED

    rotated_vertices = []
    for x, y, z in vertices:
        y_rot = y * math.cos(angle_x) – z * math.sin(angle_x)
        z_rot = y * math.sin(angle_x) + z * math.cos(angle_x)
        x_rot = x * math.cos(angle_y) – z_rot * math.sin(angle_y)
        z_final = x * math.sin(angle_y) + z_rot * math.cos(angle_y)
        x_final = x_rot * math.cos(angle_z) – y_rot * math.sin(angle_z)
        y_final = x_rot * math.sin(angle_z) + y_rot * math.cos(angle_z)
        rotated_vertices.append((x_final, y_final, z_final))

    projected_vertices = []
    for x, y, z in rotated_vertices:
        distance = 5
        scale = distance / (distance – z / 400)
        proj_x = int(x * scale + (WIDTH + MENU_WIDTH) / 2)
        proj_y = int(y * scale + HEIGHT / 2)
        projected_vertices.append((proj_x, proj_y))

    screen.fill(BACKGROUND_COLOR)

    if projected_vertices:
        # — MODIFIED DRAWING LOGIC FOR WIREFRAME —
        for face in faces:
            points = [projected_vertices[i] for i in face]
            # Draw only the outline of the polygon
            pygame.draw.polygon(screen, LINE_COLOR, points, 1)

    pygame.draw.rect(screen, MENU_BACKGROUND_COLOR, (0, 0, MENU_WIDTH, HEIGHT))
    for name, rect in menu_rects.items():
        color = MENU_HIGHLIGHT_COLOR if name == current_solid_name else MENU_TEXT_COLOR
        text_surf = MENU_FONT.render(name, True, color)
        screen.blit(text_surf, rect)

    pygame.display.flip()
    clock.tick(60)

pygame.quit()

I’m visiting a British seaside town called Margate. There’s a location here called ‘Shell Grotto’. In the 1800s, a landowner uncovered an ancient walkway and altar built underground and covered in thousands of seashells – and nobody knows why. What I’m saying is, prepare for the coming of Cthulu.

Freaky tree on the roanoke river

Carved face in the tree
Closeup on tree face

They say if you walk the banks of the Roanoke near Salem after sundown, best keep your eyes straight ahead and your feet moving. The river twists through those woods quiet as a whisper, and sometimes the quiet is watching back.

I found it in daylight, a tree with a face carved deep into the trunk. Heavy eyes, a frown that sank down like roots. Looked man-made at first, but the longer I stood there the more it felt alive. The cracks in the wood breathed. The shadows under the eyes shifted with no wind.

Old mountain tales say the Roanoke has its watchers. Spirits bound in bark, listening for names carried on the current. Folks claim if you speak too loud near them, the trees remember your voice, and call it back to you years later when you are alone in the hollow.

I didn’t linger. Just tipped my head in respect, the way you’d nod to a grave or a passing crow, and kept to the trail.

Along the river, even in daylight, you can feel the mountains breathing, and sometimes the trees breathe back.

Tic tac toe python test

#!/usr/bin/env python3
“””
Tic-Tac-Toe with simple reinforcement learning (Monte Carlo control)
– Runs in a terminal (works in Pydroid on Android)
– Can train by self-play, play human vs AI, or AI vs AI
– Learns state-action values and improves over time
– Save/Load the learned policy to/from JSON

How it learns (short version):
We record the sequence of (state, action, player) for a game. At the end:
    winner’s actions get +1, loser’s actions get -1, draws get 0
We update a value table Q[(state, player)][action] by incremental averaging.
Policy is epsilon-greedy w.r.t. Q, so it explores a bit while learning.
“””

import json
import os
import random
from collections import defaultdict
from typing import Dict, List, Tuple

# =========================
# Game mechanics
# =========================

EMPTY = ” “
PLAYERS = (“X”, “O”)
WIN_LINES = [
    (0, 1, 2), (3, 4, 5), (6, 7, 8),  # rows
    (0, 3, 6), (1, 4, 7), (2, 5, 8),  # cols
    (0, 4, 8), (2, 4, 6)              # diagonals
]

Board = List[str]
State = str  # 9-char string of ‘X’, ‘O’, or ‘ ‘
Action = int  # index 0..8
QKey = Tuple[State, str]  # (state, current_player)

def new_board() -> Board:
    return [EMPTY] * 9

def board_to_state(board: Board) -> State:
    return “”.join(board)

def legal_actions(board: Board) -> List[Action]:
    return [i for i, c in enumerate(board) if c == EMPTY]

def check_winner(board: Board) -> str:
    for a, b, c in WIN_LINES:
        if board[a] != EMPTY and board[a] == board[b] == board[c]:
            return board[a]
    if EMPTY not in board:
        return “DRAW”
    return “”  # game continues

def print_board(board: Board) -> None:
    b = [c if c != EMPTY else str(i+1) for i, c in enumerate(board)]
    rows = [f” {b[0]} | {b[1]} | {b[2]}”, f” {b[3]} | {b[4]} | {b[5]}”, f” {b[6]} | {b[7]} | {b[8]}”]
    print(“\n” + “\n———–\n”.join(rows) + “\n”)

# =========================
# RL bits (First-Visit Monte Carlo control)
# =========================

class MonteCarloAgent:
    def __init__(self, epsilon: float = 0.1):
        self.epsilon = float(epsilon)
        # Q maps (state, player) -> vector of 9 action values
        self.Q: Dict[QKey, List[float]] = defaultdict(lambda: [0.0]*9)
        # N maps (state, player, action) -> visit count for incremental mean
        self.N: Dict[Tuple[State, str, Action], int] = defaultdict(int)

    def policy(self, board: Board, player: str) -> Action:
        acts = legal_actions(board)
        if not acts:
            return -1
        if random.random() < self.epsilon:
            return random.choice(acts)
        state = board_to_state(board)
        qvals = self.Q[(state, player)]
        # choose best legal; tie-break randomly among maxima
        best_val = max(qvals[a] for a in acts)
        best_actions = [a for a in acts if qvals[a] == best_val]
        return random.choice(best_actions)

    def episode(self, starting_player: str = “X”, show: bool = False) -> str:
        “””Play one game between two copies of this agent (self-play).
        Returns the result: ‘X’, ‘O’, or ‘DRAW’.”””
        board = new_board()
        current = starting_player
        # history of (state, player, action)
        trajectory: List[Tuple[State, str, Action]] = []

        while True:
            action = self.policy(board, current)
            if action == -1:
                # no legal moves (shouldn’t happen)
                result = “DRAW”
                break
            # record state before taking action
            s = board_to_state(board)
            trajectory.append((s, current, action))
            # apply move
            board[action] = current
            outcome = check_winner(board)
            if show:
                print_board(board)
            if outcome == “X” or outcome == “O”:
                result = outcome
                break
            if outcome == “DRAW”:
                result = “DRAW”
                break
            # switch player
            current = “O” if current == “X” else “X”

        # Assign returns
        if result == “DRAW”:
            G = {“X”: 0.0, “O”: 0.0}
        else:
            G = {“X”: 1.0 if result == “X” else -1.0,
                 “O”: 1.0 if result == “O” else -1.0}

        # First-visit MC update
        seen = set()
        for s, p, a in trajectory:
            key = (s, p, a)
            if key in seen:
                continue
            seen.add(key)
            qkey = (s, p)
            self.N[(s, p, a)] += 1
            n = self.N[(s, p, a)]
            old = self.Q[qkey][a]
            target = G[p]
            self.Q[qkey][a] = old + (target – old) / n
        return result

    def choose_move(self, board: Board, player: str, greedy: bool = True) -> Action:
        acts = legal_actions(board)
        if not acts:
            return -1
        state = board_to_state(board)
        qvals = self.Q[(state, player)]
        if greedy:
            best_val = max(qvals[a] for a in acts)
            best_actions = [a for a in acts if qvals[a] == best_val]
            return random.choice(best_actions)
        else:
            return self.policy(board, player)

    # ———- persistence ———-
    def save(self, path: str):
        data = {f”{k[0]}|{k[1]}”: v for k, v in self.Q.items()}
        with open(path, “w”, encoding=”utf-8″) as f:
            json.dump({“epsilon”: self.epsilon, “Q”: data}, f)
        print(f”Saved model to {path} ({len(self.Q)} states)”)

    def load(self, path: str):
        if not os.path.exists(path):
            print(f”No such file: {path}”)
            return
        with open(path, “r”, encoding=”utf-8″) as f:
            blob = json.load(f)
        self.epsilon = float(blob.get(“epsilon”, 0.1))
        rawQ = blob.get(“Q”, {})
        self.Q = defaultdict(lambda: [0.0]*9)
        for k, v in rawQ.items():
            state, player = k.split(“|”)
            vec = [float(x) for x in v]
            if len(vec) != 9:
                vec = (vec + [0.0]*9)[:9]
            self.Q[(state, player)] = vec
        print(f”Loaded model from {path} ({len(self.Q)} states), epsilon={self.epsilon}”)

# =========================
# Human interaction
# =========================

MODEL_PATH = “tictactoe_q.json”

def human_turn(board: Board, player: str) -> None:
    while True:
        try:
            move = input(f”Your move ({player}). Enter 1-9: “).strip()
            idx = int(move) – 1
            if idx < 0 or idx > 8:
                raise ValueError
            if board[idx] != EMPTY:
                print(“That spot is taken. Try again.”)
                continue
            board[idx] = player
            return
        except ValueError:
            print(“Please type a number 1-9 corresponding to the board position.”)

def ai_turn(board: Board, player: str, agent: MonteCarloAgent, greedy: bool = True) -> None:
    a = agent.choose_move(board, player, greedy=greedy)
    if a == -1:
        return
    board[a] = player

def play_human_vs_ai(agent: MonteCarloAgent):
    print(“\n=== Human vs AI ===”)
    while True:
        choice = input(“Do you want to be X (first) or O (second)? [X/O]: “).strip().upper()
        if choice in (“X”, “O”):
            human = choice
            ai = “O” if human == “X” else “X”
            break
        print(“Please answer X or O.”)

    greedy = input(“Should the AI play greedily (y) or allow exploration (n)? [y/n]: “).strip().lower() != ‘n’

    board = new_board()
    current = “X”
    print_board(board)

    while True:
        if current == human:
            human_turn(board, human)
        else:
            ai_turn(board, ai, agent, greedy=greedy)
            print(“AI moves:”)
        print_board(board)
        outcome = check_winner(board)
        if outcome in (“X”, “O”):
            print(f”{outcome} wins!” + (” (You win!)” if outcome == human else ” (AI wins!)”))
            break
        if outcome == “DRAW”:
            print(“It’s a draw.”)
            break
        current = “O” if current == “X” else “X”

def watch_ai_vs_ai(agent: MonteCarloAgent, games: int = 1, greedy: bool = True):
    print(“\n=== AI vs AI ===”)
    for g in range(1, games+1):
        print(f”Game {g}:”)
        result = agent.episode(starting_player=”X”, show=True)
        if result == “DRAW”:
            print(“Result: draw\n”)
        else:
            print(f”Result: {result} wins\n”)

def train_self_play(agent: MonteCarloAgent, episodes: int = 10000, report_every: int = 1000):
    print(f”Training for {episodes} self-play games (epsilon={agent.epsilon})…”)
    w = {“X”: 0, “O”: 0, “DRAW”: 0}
    for i in range(1, episodes+1):
        # alternate starting player to avoid bias
        starter = “X” if i % 2 else “O”
        result = agent.episode(starting_player=starter, show=False)
        w[result] += 1
        if report_every and i % report_every == 0:
            total = i
            wx, wo, d = w[“X”], w[“O”], w[“DRAW”]
            print(f”  After {total:6d}: X={wx} O={wo} Draw={d} | states learned={len(agent.Q)}”)
    print(“Done.”)

# =========================
# Menu / main
# =========================

def main():
    agent = MonteCarloAgent(epsilon=0.1)
    if os.path.exists(MODEL_PATH):
        try:
            agent.load(MODEL_PATH)
        except Exception as e:
            print(f”(Could not auto-load model: {e})”)

    while True:
        print(“””
==============================
Tic-Tac-Toe RL (console)
==============================
1) Train by self-play
2) Play Human vs AI
3) Watch AI vs AI
4) Set exploration epsilon
5) Save model
6) Load model
7) Reset model
0) Exit
“””)
        choice = input(“Select an option: “).strip()

        if choice == “1”:
            try:
                n = int(input(“How many self-play games? (e.g., 5000): “).strip())
            except ValueError:
                print(“Please enter a number.”)
                continue
            report = max(1000, n // 10)
            train_self_play(agent, episodes=n, report_every=report)
        elif choice == “2”:
            play_human_vs_ai(agent)
        elif choice == “3”:
            try:
                g = int(input(“How many games to show? “).strip())
            except ValueError:
                g = 1
            greedy = input(“Play greedily (y) or with exploration (n)? [y/n]: “).strip().lower() != ‘n’
            watch_ai_vs_ai(agent, games=g, greedy=greedy)
        elif choice == “4”:
            try:
                e = float(input(“Set epsilon (0 = greedy, 0.1 default, 1 = random): “).strip())
                e = min(max(e, 0.0), 1.0)
                agent.epsilon = e
                print(f”epsilon set to {agent.epsilon}”)
            except ValueError:
                print(“Please enter a number (e.g., 0.1)”)
        elif choice == “5”:
            path = input(f”Save path [{MODEL_PATH}]: “).strip() or MODEL_PATH
            agent.save(path)
        elif choice == “6”:
            path = input(f”Load path [{MODEL_PATH}]: “).strip() or MODEL_PATH
            agent.load(path)
        elif choice == “7”:
            agent = MonteCarloAgent(epsilon=agent.epsilon)
            print(“Model reset. (Learning starts fresh.)”)
        elif choice == “0”:
            print(“Goodbye!”)
            break
        else:
            print(“Unknown option. Please choose from the menu.”)

if __name__ == “__main__”:
    main()

Day 20654

Afternoon carried me and my companions into Evie’s Bakery, quiet hum of ovens and glass cases shining with sugar. Amanda, smiling and steady, steered us through the tasting like she’d been waiting just for us.

Better Than Sex cake first. Devil’s food rich and shadow dark, chocolate chips hidden in the crumb. Whipped white frosting smoothed over, tangled with chocolate and caramel. Each forkful heavy with sweetness, melt and bite together, little bursts of joy.

Then sunshine cake. Yellow crumb, tender as a cloud, holding its weight under a plate of milk chocolate icing. The balance of bright and sweet, golden light under a darker crown, lifted everything a little higher.

We lingered, fork tapping the empty plate, warm air rising from the ovens, downstairs. We put an order in for both full cakes for an upcoming birthday party later this month.

When we stepped back outside, the sidewalk carried its own invitation. Hopscotch chalked in pastel squares, the word dance scrawled in the final box. Sunlight caught it just so, and for a moment it felt like the cakes had followed us out, turning the street into something just as sweet.

#EviesBakery #CakeTasting #BetterThanSexCake #SunshineCake #SweetLife #ChalkHopscotch #DanceAtTheEnd