Files
aspirin/viewer/OxViewer.java
T
ArdaandClaude Opus 5.5 d1f4e87526 Add aspirinV2: fixed-cell 15-puzzle
Cells never move; each NumericSelectLabel shows the number it holds
on white and the empty cell takes the board colour via a linked
backgroundColor. Adds the V2 generator, build step (with a direct-Java
sedonac fallback), offline verifier mode (--v2) and viewer support.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 19:14:16 +03:00

177 lines
7.1 KiB
Java

import java.awt.*;
import java.net.InetAddress;
import javax.swing.*;
import sedona.*;
import sedona.dasp.DaspSocket;
import sedona.sox.*;
/**
* Live viewer for the aspirin apps running in the Ontrol simulator (headless SVM).
* Connects over SOX, polls /ox/Game widgets 5x per second and draws the 800x480 Orion page.
* Usage: java -cp ".;sedona.jar" OxViewer [host] [port] [user] [password]
*/
public class OxViewer extends JPanel {
static final String[] LABELS = {"Title", "Sub", "Moves", "Shuffle", "Busy", "Solving", "Solved"};
SoxClient client;
SoxComponent page;
SoxComponent[] tiles = new SoxComponent[16];
SoxComponent[] cells = new SoxComponent[17]; // aspirinV2: fixed cells Cell01..Cell16
boolean v2;
SoxComponent[] labels = new SoxComponent[LABELS.length];
String lastBoard = "";
OxViewer(SoxClient client) throws Exception {
this.client = client;
SoxComponent app = client.loadApp();
page = load(app, "ox", "Game");
v2 = load(page, "Cell01") != null;
if (v2) for (int k = 1; k <= 16; k++) cells[k] = load(page, String.format("Cell%02d", k));
else for (int t = 1; t <= 15; t++) tiles[t] = load(page, String.format("Tile%02d", t));
for (int i = 0; i < LABELS.length; i++) labels[i] = load(page, LABELS[i]);
setPreferredSize(new Dimension(800, 480));
}
SoxComponent load(SoxComponent from, String... path) throws Exception {
SoxComponent c = from;
for (String p : path) {
if (c == null) return null;
client.update(c, 0x01); // tree
c = c.child(p);
}
if (c != null) client.update(c, 0x06); // config + runtime
return c;
}
void poll() throws Exception {
for (SoxComponent c : tiles) if (c != null) client.update(c, 0x06);
for (SoxComponent c : cells) if (c != null) client.update(c, 0x06);
for (SoxComponent c : labels) if (c != null) client.update(c, 0x06);
String b = boardText();
if (!b.equals(lastBoard)) {
System.out.println(b + " moves=" + str(labels[2], "in") + " " + status());
System.out.flush();
lastBoard = b;
}
}
static Value val(SoxComponent c, String slot) {
Slot s = c.type.slot(slot);
return s == null ? null : c.get(s);
}
static int i(SoxComponent c, String slot) { return Integer.parseInt(val(c, slot).toString()); }
static String str(SoxComponent c, String slot) {
if (c == null) return "";
Value v = val(c, slot);
if (v == null) return "";
if (v instanceof Buf) { Buf b = (Buf) v; return new String(b.bytes, 0, b.size).replace("\0", ""); }
String s = v.toString();
return s.endsWith(".0") ? s.substring(0, s.length() - 2) : s;
}
static Color c565(int v) {
v &= 0xFFFF;
return new Color(((v >> 11) & 31) * 255 / 31, ((v >> 5) & 63) * 255 / 63, (v & 31) * 255 / 31);
}
String status() {
StringBuilder sb = new StringBuilder();
for (int k = 4; k < LABELS.length; k++)
if (labels[k] != null && "true".equals(str(labels[k], "in"))) sb.append(str(labels[k], "trueText")).append(' ');
return sb.toString().trim();
}
String boardText() {
String[] cells = new String[16];
java.util.Arrays.fill(cells, " .");
int whites = 0;
if (v2) for (int k = 1; k <= 16; k++) {
int n = i(this.cells[k], "in");
if (n != 0) cells[k - 1] = String.format("%2d", n);
if ((i(this.cells[k], "backgroundColor") & 0xFFFF) == 0xFFFF) whites++;
}
else for (int t = 1; t <= 15; t++) {
int col = (i(tiles[t], "x") - 40) / 105, row = (i(tiles[t], "y") - 40) / 105;
if (col >= 0 && col < 4 && row >= 0 && row < 4) cells[row * 4 + col] = String.format("%2d", t);
}
StringBuilder sb = new StringBuilder();
for (int r = 0; r < 4; r++) sb.append(r == 0 ? "" : " | ").append(String.join(" ", java.util.Arrays.copyOfRange(cells, r * 4, r * 4 + 4)));
if (v2) sb.append(" white=").append(whites);
return sb.toString();
}
void box(Graphics2D g, SoxComponent w, String text, int fg, int bg, boolean bold, boolean fill) {
int x = i(w, "x"), y = i(w, "y"), wd = i(w, "width"), ht = i(w, "height");
if (fill) { g.setColor(c565(bg)); g.fillRect(x, y, wd, ht); }
g.setColor(c565(fg));
g.setFont(new Font("Tahoma", bold ? Font.BOLD : Font.PLAIN, Math.max(9, i(w, "fontSize") * 3 / 4)));
FontMetrics fm = g.getFontMetrics();
g.drawString(text, x + (wd - fm.stringWidth(text)) / 2, y + (ht + fm.getAscent() - fm.getDescent()) / 2);
}
@Override protected void paintComponent(Graphics g0) {
Graphics2D g = (Graphics2D) g0;
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g.setColor(c565(i(page, "backgroundColor")));
g.fillRect(0, 0, 800, 480);
g.setColor(c565(0x31A6));
g.fillRect(35, 35, 425, 425);
if (v2) for (int k = 1; k <= 16; k++) {
SoxComponent w = cells[k];
String[] opts = str(w, "text").split(";");
int n = i(w, "in");
box(g, w, n < opts.length ? opts[n].trim() : "", i(w, "textColor"), i(w, "backgroundColor"), true, true);
}
else for (int t = 1; t <= 15; t++) {
SoxComponent w = tiles[t];
box(g, w, str(w, "text"), i(w, "textColor"), i(w, "backgroundColor"), true, true);
}
for (int k = 0; k < LABELS.length; k++) {
SoxComponent w = labels[k];
if (w == null) continue;
String type = w.type.name;
if (type.equals("BooleanLabel")) {
boolean on = "true".equals(str(w, "in"));
box(g, w, str(w, on ? "trueText" : "falseText"), i(w, on ? "trueTextColor" : "falseTextColor"),
i(w, on ? "trueBackgroundColor" : "falseBackgroundColor"), true, true);
} else if (type.equals("NumericLabel")) {
String fmt = str(w, "text").replace("%.0f", str(w, "in"));
box(g, w, fmt, i(w, "textColor"), i(w, "backgroundColor"), false, true);
} else {
box(g, w, str(w, "text"), i(w, "textColor"), i(w, "backgroundColor"), i(w, "fontAttributes") == 1, true);
}
}
}
public static void main(String[] a) throws Exception {
String host = a.length > 0 ? a[0] : "127.0.0.1";
int port = a.length > 1 ? Integer.parseInt(a[1]) : 1876;
String user = a.length > 2 ? a[2] : "guest";
String pass = a.length > 3 ? a[3] : "";
DaspSocket sock = DaspSocket.open(-1, null, DaspSocket.SESSION_QUEUING);
OxViewer v = null;
while (v == null) { // wait for the simulator to come up
try {
SoxClient client = new SoxClient(sock, InetAddress.getByName(host), port, user, pass);
client.connect();
v = new OxViewer(client);
} catch (Exception e) {
System.out.println("waiting for SOX " + host + ":" + port + " (" + e + ")");
Thread.sleep(500);
}
}
if (GraphicsEnvironment.isHeadless()) { // -Djava.awt.headless=true: console log only
while (true) { v.poll(); Thread.sleep(200); }
}
JFrame f = new JFrame("Orion Screen - aspirin (live via SOX " + host + ":" + port + ")");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(v);
f.pack();
f.setLocation(60, 60);
f.setVisible(true);
while (true) {
v.poll();
v.repaint();
Thread.sleep(200);
}
}
}