見出し画像

自作時計アプリ「SteamClock」を壊してみた|Codea実験

前回の記事はこちら

今回は、私が最初に作ったアプリ、
「SteamClock」を少し簡単にしたコードを用意しました。(記事の一番下に置いています↓)

500行以内に収めているので、
制限のある無料版のCodeaでも動かせるはずです。

ただ、これは完成品ではありません。
「壊す」ためのコードです。

もし触ってみたいと思った場合、これだけやってください。
Codeaを開いて、新しいプロジェクトを作る。

そこに、このコードをそのまま
貼る → 再生ボタン → 動く
それだけです。

最初はコードの意味が分からなくても大丈夫です。
触っているうちに分かることがあります。

数値を自分で書き換えて遊ぶのも良いですが、
今回はゼロから触るよりも面白い方法を紹介します。

コードをそのままAIに投げて、「壊してみて」とだけ伝えてみます。

ここからはその実験記録です。


実験①:サイバーパンク化
Make this clock cyberpunk style. Add neon colors, digital glitch, unstable motion.

歯車が不気味に光る。
振り子は途中でノイズのようにブレはじめ、
秒針は滑らかに進むのをやめてしまう。

実験②:スチーム暴走
Break the balance. Make the steam pressure unstable and cause the mechanism to go out of control.

画面全体が小刻みに震えだす。
蒸気の圧力に耐えかねたように空間が歪み、
不自然に明滅を繰り返す。

実験③:かわいい化
Make it cute. Softer motion, rounded shapes, gentle rhythm.

淡く優しい色合いに変わり、
針の動きが弾むようなリズムになる。

実験④:静かな夜
Make it quiet, dark, minimal. Like a late night room. Slow, restrained motion. No decoration.

チクタクという規則正しい動きが、
深海に沈んだような、少しもったりしたリズムに変わる。


ご覧のとおり、元は同じコードのはずなのに、
あっという間に、全部違うものになりました。

どれも正確な時計ではありません。
でも、どれも少しだけ愛着が湧いてきます。

そして、ここで終わらないのが面白いところです。
AIが出してくるのは、あくまでも「それっぽいもの」です。

AIは言葉の意味は知っていますが、
眩しすぎる、歯車がうまく噛み合っていないなど、そういう手触りには無頓着です。

ここからが、人間の出番です。

眩しすぎる点滅の明るさを落とす、ずれている歯車はあえてそのままに、
など自分にとって心地よい違和感になるように整え直す。

正しい時計にしてもいいし、壊し続けても良い。

最初に作ろうとしていた時計は、
もうここにはありません。

でも、触っているうちに、
少しだけ、「自分だけの時計」に近づいていきます。

下のコードをコピーして、Codeaに貼り付けてみてください。

まずは、そのままの姿を見てあげて、
それから、AIと一緒に壊してみてください。

お持ち帰り用コード

--# Main
-- SteamClock Lite (Asset-Free, Minimal Edition)

------------------------------------------------------------
-- グローバル状態
------------------------------------------------------------
local gears = {}
local clockX, clockY, clockSize
local pendulumTime = 0

-- 時刻オフセット
local TimeOffset = 0

------------------------------------------------------------
-- 針の回転・ゼロ向き
------------------------------------------------------------
local HandDir = -1
local HandZero = { second=0, minute=0, hour=0 }

------------------------------------------------------------
-- Utils
------------------------------------------------------------
local function getClockHMS()
    local t = os.date("*t", os.time() + math.floor(TimeOffset))
    return t.hour, t.min, t.sec
end

------------------------------------------------------------
-- 色ユーティリティ
------------------------------------------------------------
local function cfill(r,g,b,a) fill(r,g,b,a or 255) end
local function cstroke(r,g,b,a) stroke(r,g,b,a or 255) end

------------------------------------------------------------
-- 背景
------------------------------------------------------------
local function drawBackgroundPanel()
    background(14, 10, 8)
    
    -- ★★★ Added/Modified Start ★★★
    -- minimal flat background (reduced gradients)
    noStroke()
    cfill(24, 16, 12)
    rect(WIDTH/2, HEIGHT/2, WIDTH, HEIGHT)
    
    -- Single, very subtle gradient layer
    cfill(30, 20, 15, 30)
    rect(WIDTH/2, HEIGHT/2, WIDTH, HEIGHT)
    -- ★★★ Added/Modified End ★★★
end

------------------------------------------------------------
-- 図形パーツ
------------------------------------------------------------
local triMesh = mesh()
local function drawTriangle(x1, y1, x2, y2, x3, y3, r, g, b, a)
    triMesh.vertices = {vec2(x1, y1), vec2(x2, y2), vec2(x3, y3)}
    triMesh:setColors(r, g, b, a or 255)
    triMesh:draw()
end

local function drawPipeSegment(x1, y1, x2, y2, thick)
    cstroke(92, 58, 36)
    strokeWidth(thick)
    line(x1, y1, x2, y2)
    
    cstroke(140, 96, 60, 80)
    strokeWidth(thick*0.25)
    line(x1, y1 + thick*0.13, x2, y2 + thick*0.13)
end

local function drawBolt(x, y, s)
    noStroke()
    cfill(110, 82, 55)
    ellipse(x, y, s)
    cfill(70, 50, 32, 120)
    ellipse(x - s*0.08, y - s*0.08, s*0.55)
end

local function drawGearShape(x, y, size, angle, teeth, innerRatio)
    pushMatrix()
    translate(x, y)
    rotate(angle)
    
    local rOuter = size * 0.5
    local rInner = size * 0.41
    local rHub = size * innerRatio
    
    -- 歯
    for i = 1, teeth do
        local a = math.rad((360/teeth) * i)
        pushMatrix()
        rotate(math.deg(a))
        noStroke()
        -- ★★★ Added/Modified Start ★★★
        cfill(104, 78, 55) -- Darker teeth
        rect(0, rInner + size*0.035, size*0.08, size*0.08) -- Slightly smaller
        -- ★★★ Added/Modified End ★★★
        popMatrix()
    end
    
    -- 本体
    noStroke()
    -- ★★★ Added/Modified Start ★★★
    cfill(96, 73, 51) -- Darker body
    ellipse(0, 0, size*0.88)
    cfill(65, 48, 33, 140) -- Lower contrast inner shadow
    -- ★★★ Added/Modified End ★★★
    ellipse(0, 0, size*0.72)
    
    -- 穴
    cfill(40, 27, 18)
    ellipse(0, 0, rHub*2)
    
    -- ボルト
    for i = 1, 4 do
        local a = math.rad(i * 90 + 18)
        drawBolt(math.cos(a)*size*0.18, math.sin(a)*size*0.18, size*0.06)
    end
    
    popMatrix()
end

local function drawRomanNumerals(radius)
    pushStyle()
    -- ★★★ Added/Modified Start ★★★
    fill(85, 68, 52) -- Lighter color to weaken contrast
    textAlign(CENTER)
    font("TimesNewRomanPSMT")
    fontSize(radius * 0.155) -- Slightly smaller (was 0.17)
    -- ★★★ Added/Modified End ★★★
    
    local romans = {"XII","I","II","III","IV","V","VI","VII","VIII","IX","X","XI"}
    for i = 1, 12 do
        local deg = (i-1) * 30
        local a = math.rad(90 - deg)
        local x = clockX + math.cos(a) * radius * 0.82
        local y = clockY + math.sin(a) * radius * 0.82
        text(romans[i], x, y)
    end
    popStyle()
end

------------------------------------------------------------
-- 針描画
------------------------------------------------------------
local function drawHandShape(which, length)
    if which == "second" then
        noStroke()
        -- ★★★ Added/Modified Start ★★★
        cfill(150, 130, 95, 180) -- Darker and slightly transparent
        rect(0, length*0.4, length*0.012, length) -- Slightly thinner
        cfill(120, 95, 65, 200)
        ellipse(0, 0, length*0.06)
        -- ★★★ Added/Modified End ★★★
    elseif which == "minute" then
        noStroke()
        cfill(110, 85, 55)
        rect(0, length*0.4, length*0.035, length*0.8)
        drawTriangle(-length*0.045, length*0.8, 0, length, length*0.045, length*0.8, 110, 85, 55)
    elseif which == "hour" then
        noStroke()
        cfill(95, 72, 48)
        rect(0, length*0.4, length*0.05, length*0.8)
        drawTriangle(-length*0.06, length*0.8, 0, length, length*0.06, length*0.8, 95, 72, 48)
    end
end

local function drawHand(angle_deg, length, which)
    pushMatrix()
    translate(clockX, clockY)
    rotate(HandDir * angle_deg + HandZero[which])
    drawHandShape(which, length)
    popMatrix()
end

------------------------------------------------------------
-- setup
------------------------------------------------------------
function setup()
    viewer.mode = FULLSCREEN_NO_BUTTONS
    
    spriteMode(CENTER)
    ellipseMode(CENTER)
    rectMode(CENTER)
    
    clockX = WIDTH/2
    clockY = HEIGHT/1.83
    clockSize = WIDTH * 0.69
    
    gears = {}
    table.insert(gears, {type="linked", x=WIDTH*0.455, y=HEIGHT*0.77, size=160, z=11, speed=-23, angle=10, parent=nil, teeth=10})
    table.insert(gears, {type="linked", size=110, z=10, parent=1, phase=18, parentAngle=210, parentDistanceFactor=0.8, teeth=8})
    do
        local pG = gears[gears[2].parent]
        local cG = gears[2]
        local aR = math.rad(cG.parentAngle)
        local d = (pG.size/2 + cG.size/2) * cG.parentDistanceFactor
        cG.x = pG.x + math.cos(aR) * d
        cG.y = pG.y + math.sin(aR) * d
    end
    table.insert(gears, {type="decorative", x=WIDTH*0.75, y=HEIGHT*0.70, size=90, speed=20, angle=45, teeth=7})
    
    -- ★★★ Added/Modified Start ★★★
    -- Cleaned up unused and commented-out legacy effects
    -- ★★★ Added/Modified End ★★★
end

------------------------------------------------------------
-- draw
------------------------------------------------------------
function draw()
    drawBackgroundPanel()
    
    drawPipes()
    drawGears()
    drawPendulum()
    
    drawAnalogClock()
    drawDigitalClock()
    
    pendulumTime = pendulumTime + DeltaTime
end

------------------------------------------------------------
-- 配管枠 (Minimal Framework)
------------------------------------------------------------
function drawPipes()
    local leftX = WIDTH * 0.08
    local rightX = WIDTH * 0.92
    local btmY = HEIGHT * 0.05
    local topY = HEIGHT * 0.95
    local thick = 12
    
    -- Outer rectangle
    drawPipeSegment(leftX, btmY, rightX, btmY, thick)
    drawPipeSegment(leftX, topY, rightX, topY, thick)
    drawPipeSegment(leftX, btmY, leftX, topY, thick)
    drawPipeSegment(rightX, btmY, rightX, topY, thick)
    
    -- Corner bolts
    local boltS = 16
    drawBolt(leftX, btmY, boltS)
    drawBolt(rightX, btmY, boltS)
    drawBolt(leftX, topY, boltS)
    drawBolt(rightX, topY, boltS)
end

------------------------------------------------------------
-- 歯車
------------------------------------------------------------
function drawGears()
    for _, g in ipairs(gears) do
        if g.type == "linked" then
            if g.parent then
                local p = gears[g.parent]
                if p then
                    g.angle = -p.angle * (p.z / g.z) + g.phase
                end
            else
                g.angle = g.angle + g.speed * DeltaTime
            end
        else
            g.angle = g.angle + g.speed * DeltaTime
        end
    end
    
    for _, g in ipairs(gears) do
        drawGearShape(g.x, g.y, g.size, g.angle, g.teeth or 8, 0.18)
    end
end

------------------------------------------------------------
-- アナログ時計
------------------------------------------------------------
function drawAnalogClock()
    local r = clockSize * 0.5
    
    -- 外枠
    noStroke()
    cfill(86, 55, 34)
    ellipse(clockX, clockY, clockSize*1.02)
    cfill(48, 28, 18)
    ellipse(clockX, clockY, clockSize*0.96)
    
    -- 盤面
    cfill(239, 229, 208)
    ellipse(clockX, clockY, clockSize*0.88)
    
    -- 内枠
    noFill()
    cstroke(120, 88, 52)
    strokeWidth(5)
    ellipse(clockX, clockY, clockSize*0.52)
    
    -- 目盛り
    pushMatrix()
    translate(clockX, clockY)
    for i = 0, 59 do
        local a = math.rad(i * 6)
        local len = (i % 5 == 0) and r*0.08 or r*0.035
        local y1 = r*0.38
        local y2 = y1 + len
        local x1 = 0
        local x2 = 0
        pushMatrix()
        rotate(-i * 6)
        if i % 5 == 0 then
            cstroke(60, 46, 34)
            strokeWidth(3)
        else
            cstroke(100, 86, 70, 180)
            strokeWidth(1.5)
        end
        line(x1, y1, x2, y2)
        popMatrix()
    end
    popMatrix()
    
    drawRomanNumerals(r)
    
    local h, m, s = getClockHMS()
    
    local secL = clockSize * 0.43
    local minL = clockSize * 0.40
    local hL   = clockSize * 0.28
    
    drawHand(s * 6, secL, "second")
    drawHand((m + s/60) * 6, minL, "minute")
    drawHand(((h%12) + m/60) * 30, hL, "hour")
    
    -- センターキャップ
    cfill(132, 100, 62)
    ellipse(clockX, clockY, clockSize*0.11)
    cfill(80, 58, 38)
    ellipse(clockX, clockY, clockSize*0.05)
end

------------------------------------------------------------
-- デジタル(ニキシー風)
------------------------------------------------------------
local function drawNixieTube(x, y, w, h, char)
    -- ガラス
    noStroke()
    cfill(230, 220, 200, 30)
    rect(x, y, w, h, 18)
    cfill(255, 255, 255, 10)
    rect(x - w*0.10, y + h*0.05, w*0.12, h*0.80, 8)
    
    -- 内部
    cfill(20, 15, 12, 220)
    rect(x, y, w*0.82, h*0.86, 14)
    
    -- 下部ソケット
    cfill(98, 70, 42)
    rect(x, y - h*0.44, w*0.95, h*0.10)
    
    -- 数字
    local flickerAlphaCore = math.random(180, 255)
    local flickerAlphaGlow = flickerAlphaCore * 0.35
    textAlign(CENTER)
    font("HelveticaNeue-UltraLight")
    blendMode(ADDITIVE)
    
    local fsGlow = h * 0.62
    local fsCore = h * 0.58
    local offsetY = h * 0.05
    local offsetX = 0
    
    if char == "1" then
        fsGlow = h * 0.68
        fsCore = h * 0.64
        offsetX = -w * 0.02
    end
    
    fontSize(fsGlow)
    fill(255, 130, 30, flickerAlphaGlow)
    text(char, x + offsetX, y - offsetY)
    
    fontSize(fsCore)
    fill(255, 200, 110, flickerAlphaCore)
    text(char, x + offsetX, y - offsetY)
    
    blendMode(NORMAL)
end

function drawDigitalClock()
    local n = 4
    local h = HEIGHT * 0.18
    local w = h * 0.60
    local y = HEIGHT * 0.20
    local sp = w * 1.25
    
    local h_val, m_val = getClockHMS()
    local s = string.format("%02d%02d", h_val, m_val)
    
    local totalW = sp * (n-1)
    local sx = WIDTH/2 - totalW/2
    
    -- 配管ベース(ニキシー専用の台座)
    drawPipeSegment(sx - 40, y - h*0.47, sx + totalW + 40, y - h*0.47, 10)
    
    for i = 1, n do
        local x = sx + (i-1) * sp
        drawNixieTube(x, y, w, h, string.sub(s, i, i))
    end
end

------------------------------------------------------------
-- 振り子
------------------------------------------------------------
function drawPendulum()
    local pivotX = clockX
    -- ★★★ Added/Modified Start ★★★
    local pivotY = clockY - clockSize*0.48 -- Moved slightly up
    local maxAngle = 15
    local speed = 2.0
    local angle = maxAngle * math.sin(pendulumTime * speed)
    local rodLength = clockSize * 0.45 -- Shortened to prevent Nixie overlap
    -- ★★★ Added/Modified End ★★★
    
    pushMatrix()
    translate(pivotX, pivotY)
    rotate(angle)
    
    cstroke(150, 120, 82)
    strokeWidth(3)
    line(0, 0, 0, -rodLength)
    
    noStroke()
    cfill(180, 144, 72)
    ellipse(0, -rodLength, 34)
    cfill(120, 90, 48, 120)
    ellipse(-2, -rodLength-2, 22)
    
    popMatrix()
end

------------------------------------------------------------
-- タッチ操作
------------------------------------------------------------
function touched(touch)
    if touch.state == ENDED and touch.tapCount == 2 then
        local now = os.date("*t")
        local targetTime = os.time({
            year = now.year,
            month = now.month,
            day = now.day,
            hour = 10,
            min = 8,
            sec = 42
        })
        local currentTime = os.time(now)
        
        TimeOffset = targetTime - currentTime
        
        print("Video Mode: 10:08:42 (Running)")
    end
end


いいなと思ったら応援しよう!