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", "Hint", "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); } } }