Copyright (c) 2022 Lorenzo Cavuoti. MIT license retained. Original browser interface and JavaScript port added October31,2026.
MIT license · Pinned upstream source · Play
Aurora Loom modifications October31,2026. MIT. Port of Lorenzo Cavuoti’s BoardClassic scoring and CubeCoordinates spiral; JavaFX replaced by static browser interface. Added seeded shuffle, live line ledger, preview, undo, daily seed, hint and device saves. No external runtime dependencies.
FILE: index.html
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Aurora Loom · Nineteen Tiles</title><link rel="stylesheet" href="./style.css"></head><body><main><header><div class="eyebrow">A SMALL RITUAL OF NUMBERS & LIGHT</div><h1>Aurora Loom<span>Nineteen tiles. A thousand possibilities.</span></h1><p>Weave matching numbers across a little hexagon. Make every line count.</p></header><nav><button id="daily">Daily weave</button><button id="shuffle">Fresh shuffle</button><button id="replay">Replay these tiles</button><button id="lesson">Learn with a line</button><button id="guide">How to play</button></nav><div class="layout"><section class="table"><div class="tablehead"><span id="kind"></span><span id="progress"></span></div><div id="board" aria-label="Nineteen tile board"></div><div id="status" role="status" aria-live="polite"></div><div class="actions"><button id="place">Choose a hex</button><button id="cancel">Cancel preview</button><button id="undo">Undo</button><button id="hint">Suggest a hex</button></div><p class="small">Tab to a hex, Enter to preview, then activate Place. Escape cancels.</p></section><aside><section class="card next"><div><div class="eyebrow">THE NEXT THREAD</div><div id="current"></div></div><div><h2>One tile. Three directions.</h2><p id="numbers"></p><p>The orientation is fixed. Every number belongs to its own direction.</p></div></section><section class="card totals"><div><span>YOUR SCORE</span><strong id="score">0</strong></div><div><span>PERSONAL BEST</span><strong id="best">0</strong></div><p>Local record · undo and hints allowed</p></section><section class="card"><h2>See the pattern</h2><p id="lessonText"></p><p>Preview arrows in the line ledger show points you would earn immediately.</p></section></aside></div><section class="card ledger"><h2>The line ledger</h2><p>Complete, matching lines score number × length. A mixed line scores zero. Empty cells never score.</p><div id="lines"></div></section><section class="card rules" id="rules" hidden><h2>A calm puzzle with sharp decisions</h2><p>Adapted from the classic Take It Easy mechanics. Each unique tile has one down-right number (3, 4 or 8), one up-right number (2, 6 or 7), and one vertical number (1, 5 or 9). There are 27 combinations. Draw 19 without replacement and place each on any empty hex, keeping its orientation fixed.</p><p>Only full straight lines from one board edge to the other count. All tiles in that line must show the same number for that direction. Multiply that number by the line's length. Add scores in all three directions. A tile can contribute to three lines; matching one direction does not guarantee the other two will match.</p><p>Daily weave uses the same UTC date seed for everyone. Fresh shuffle makes a new deck. Replay keeps your sequence. Future tiles stay hidden. The suggestion uses only the current tile and board; it favors compatibility and immediate points, but makes no guarantee of an optimal score. Hints are marked on the final result. Undo is always allowed. Personal records include assisted play and live on this device.</p><p>Click or use Tab and Enter to preview, inspect the ledger, then activate Place. Escape clears the preview. Nothing is placed until you confirm. The guided table begins with two tiles already placed. No account, backend, downloads or external assets are needed.</p><button id="close">Back to weaving</button></section><footer>Independent browser adaptation of <a href="https://github.com/LetteraUnica/Take-It-Easy-Java">Take-It-Easy-Java</a> · MIT · <a href="./source.html">Source & attribution</a><p>Progress saved on this device · no account required</p></footer></main><script type="module" src="./app.js"></script></body></html>
FILE: style.css
:root{color-scheme:dark;--gold:#f5d6a4;--muted:#afbed0}*{box-sizing:border-box}body{margin:0;background:radial-gradient(ellipse at 15% 0%,#264344,transparent 60%),radial-gradient(ellipse at 100% 50%,#322945,transparent 65%),#121b29;color:#f4eddf;font:16px/1.6 system-ui,sans-serif}main{max-width:1140px;margin:auto;padding:42px 28px}header{padding-bottom:22px}.eyebrow{font-size:10px;letter-spacing:.22em;color:#a3dad1}h1{font:65px/1 Georgia,serif;margin:18px 0;color:#f7dfb7}h1 span{display:block;font:23px/1.5 Georgia;color:#bdcbd8;margin-top:12px}header p{color:#c6cdd5}nav{display:flex;gap:9px;flex-wrap:wrap;margin-bottom:24px}button{font:inherit;background:#29374a;color:#f5eee0;border:1px solid #657083;border-radius:10px;padding:9px 13px;cursor:pointer;min-height:44px}button:hover:not(:disabled){background:#40556b;border-color:#f1d6aa}button:focus-visible{outline:3px solid #e6c788;outline-offset:3px}button:disabled{opacity:.4;cursor:default}#daily,#place{background:#d7c59d;color:#1a2835;border-color:#d7c59d}.layout{display:grid;grid-template-columns:minmax(0,1.7fr) minmax(280px,1fr);gap:22px}.table{border-radius:24px;border:1px solid #5b6c79;background:radial-gradient(ellipse at 50% 35%,#3b4a5b,#212d3b 70%);padding:20px;box-shadow:0 22px 55px #0004}.tablehead{display:flex;justify-content:space-between;gap:8px;font-size:12px;color:#cfdbd9}#board{width:100%;aspect-ratio:1;position:relative;margin:8px auto 0}.hex{position:absolute;transform:translate(-50%,-50%);width:22%;min-height:0;padding:0;border:0;background:none;aspect-ratio:1;box-shadow:none;border-radius:30%;filter:drop-shadow(0 4px 3px #0005)}.hex svg{width:100%;height:100%;display:block}.hex.filled:disabled{opacity:1}.hex:hover:not(:disabled){background:none;filter:drop-shadow(0 0 6px #fff5)}.hex.selected{filter:drop-shadow(0 0 5px #ffe3a6)}.hex.selected svg{opacity:.72}.hex.selected polygon{stroke:#ffdd8f;stroke-width:5}.hex:focus-visible{outline-offset:-4px}.actions{display:flex;gap:7px;flex-wrap:wrap;justify-content:center}#status{min-height:62px;text-align:center;font-size:15px;color:#f0dbb7;margin:8px auto 16px;max-width:500px}.small{font-size:11px;color:var(--muted);text-align:center}.card{background:linear-gradient(120deg,#2b3a49,#212a3a);border:1px solid #4a5d6d;border-radius:18px;padding:20px}aside{display:flex;flex-direction:column;gap:16px}.card h2{font:23px/1.25 Georgia;margin:0 0 12px}.card p{font-size:13px;color:#bdc9d6;margin:8px 0}.next #current{max-width:140px;width:100%;margin:auto}#numbers{color:#f4dcab}.totals{display:grid;grid-template-columns:1fr 1fr;text-align:center;gap:12px}.totals span{font-size:10px;letter-spacing:.1em;color:#b9cfd0}.totals strong{display:block;font:48px/1.3 Georgia;color:#f7dfb7}.totals p{grid-column:1/-1;margin:0;font-size:11px}.ledger{margin-top:22px}#lines{display:grid;grid-template-columns:repeat(3,1fr);column-gap:24px}#lines h3{font-size:13px;color:#f2d6a2;grid-row:1;margin:10px 0}#lines .line{font-size:11px;color:#d2d9e0;padding:7px 0;border-bottom:1px solid #56607155}#lines h3:nth-of-type(2){grid-column:2}#lines h3:nth-of-type(3){grid-column:3}#lines .line:nth-child(n+2):nth-child(-n+6){grid-column:1}#lines .line:nth-child(n+8):nth-child(-n+12){grid-column:2}#lines .line:nth-child(n+14):nth-child(-n+18){grid-column:3}#lines .line:nth-child(2),#lines .line:nth-child(8),#lines .line:nth-child(14){grid-row:2}#lines .line:nth-child(3),#lines .line:nth-child(9),#lines .line:nth-child(15){grid-row:3}#lines .line:nth-child(4),#lines .line:nth-child(10),#lines .line:nth-child(16){grid-row:4}#lines .line:nth-child(5),#lines .line:nth-child(11),#lines .line:nth-child(17){grid-row:5}#lines .line:nth-child(6),#lines .line:nth-child(12),#lines .line:nth-child(18){grid-row:6}#lines .earned{color:#a9e2bf}#lines .broken{color:#c4a9a6}.rules{margin-top:22px}.rules p{font-size:15px;max-width:850px}footer{font-size:12px;color:#a8b6c9;margin:28px 0 0}a{color:#e7d3ae}footer p{margin:5px 0}.finished{font:100px Georgia;color:#f2d4a0;text-align:center}[hidden]{display:none!important}@media(max-width:760px){main{padding:26px 16px}h1{font-size:50px}h1 span{font-size:20px}header p{font-size:14px}.layout{grid-template-columns:minmax(0,1fr)}.table{padding:16px 9px}.tablehead{font-size:10px}nav button,.actions button{font-size:13px;padding:8px 10px}.card{padding:18px}.next{display:grid;grid-template-columns:105px 1fr;gap:16px}.next h2{font-size:20px}.next .eyebrow{font-size:8px;letter-spacing:.1em}#lines{display:block}#lines h3{margin-top:20px}#status{font-size:14px}.hex{width:22%}}
FILE: engine.js
/* MIT. Port of BoardClassic.getScore, CubeCoordinates.navigateRing/navigateSpiral,
Tile.getValues and tile Constants from Lorenzo Cavuoti's Take-It-Easy-Java.
Original source pinned in metadata.json. Modified October31,2026: browser arrays,
explicit empty-cell handling for live scoring, detailed line results and seeded deck. */
export const directions=[[1,0,-1],[1,-1,0],[0,-1,1],[-1,0,1],[-1,1,0],[0,1,-1]];
export function ring(radius){if(!radius)return [[0,0,0]];let c=directions[4].map(x=>x*radius),out=[];for(let i=0;i<6;i++)for(let j=0;j<radius;j++){out.push([...c]);c=c.map((x,k)=>x+directions[i][k]);}return out;}
export const coords=[...ring(0),...ring(1),...ring(2)];
const key=c=>c.join(',');const index=new Map(coords.map((c,i)=>[key(c),i]));
export const lines=[];
for(let axis=0;axis<3;axis++){let available=new Set(coords.map((_,i)=>i));while(available.size){let start=available.values().next().value,cells=[start];available.delete(start);for(let d of [axis,axis+3]){let c=[...coords[start]];while(true){c=c.map((x,k)=>x+directions[d][k]);let i=index.get(key(c));if(i===undefined)break;cells.push(i);available.delete(i);}}lines.push({axis,cells});}}
export function scoring(board){return lines.map((l,i)=>{let vals=l.cells.map(c=>board[c]?.[l.axis]);let occupied=vals.filter(v=>v!==undefined),same=new Set(occupied).size<=1,full=occupied.length===vals.length;return {...l,id:i,number:occupied[0]||0,filled:occupied.length,broken:!same,points:full&&same?vals.length*vals[0]:0};});}
export const score=board=>scoring(board).reduce((s,l)=>s+l.points,0);
export const tiles=[];for(let r of [3,4,8])for(let l of [2,6,7])for(let t of [1,5,9])tiles.push([r,l,t]);
export function deck(seed){let h=2166136261;for(let c of String(seed)){h^=c.charCodeAt(0);h=Math.imul(h,16777619);}const rnd=()=>{h^=h<<13;h^=h>>>17;h^=h<<5;return(h>>>0)/4294967296;};let a=tiles.map(t=>[...t]);for(let i=a.length-1;i>0;i--){let j=Math.floor(rnd()*(i+1));[a[i],a[j]]=[a[j],a[i]];}return a;}
export function fresh(seed){return {seed,board:Array(19).fill(null),deck:deck(seed),turn:0,history:[],assisted:false,kind:'shuffle'};}
export function put(state,i){if(i<0||i>=19||state.board[i]||state.turn>=19)return false;state.history.push({board:state.board.map(t=>t&&[...t]),turn:state.turn});state.board[i]=state.deck[state.turn++];return true;}
FILE: app.js
import {coords,lines,score,scoring,fresh,put} from './engine.js';
const $=s=>document.querySelector(s);const labels=coords.map(c=>String.fromCharCode(65+c[0]+2)+(c[1]+c[0]+3));const palette={1:'#c5bed2',2:'#e88ba0',3:'#b98be5',4:'#eea477',5:'#78cdd0',6:'#a5ca85',7:'#f0d48a',8:'#8fa8ec',9:'#f4d5aa'};
let state=fresh('first-light'),selected=-1,hint='',best=0;try{let a=JSON.parse(localStorage.getItem('aurora-loom-v1')||'null');if(a&&a.board?.length===19&&a.deck?.length===27)state=a;best=Number(localStorage.getItem('aurora-loom-best')||0);}catch{}
function save(){try{localStorage.setItem('aurora-loom-v1',JSON.stringify(state));if(state.turn===19&&score(state.board)>best){best=score(state.board);localStorage.setItem('aurora-loom-best',String(best));}}catch{}}
function tile(t,lab=''){if(!t)return `<svg viewBox="0 0 100 100" aria-hidden="true"><polygon points="1,50 25,8 75,8 99,50 75,92 25,92" fill="#242b3b" stroke="#5c677d" stroke-width="2"/><text x="50" y="56" text-anchor="middle" fill="#9caabe" font-size="16">${lab}</text></svg>`;return `<svg viewBox="0 0 100 100" aria-hidden="true"><polygon points="1,50 25,8 75,8 99,50 75,92 25,92" fill="#303e50" stroke="#a9b8c4" stroke-width="2"/><path d="M9 26L91 74" stroke="${palette[t[0]]}" stroke-width="10"/><path d="M9 74L91 26" stroke="${palette[t[1]]}" stroke-width="10"/><path d="M50 7V93" stroke="${palette[t[2]]}" stroke-width="10"/>${[[73,69],[73,36],[50,24]].map(([x,y],i)=>`<circle cx="${x}" cy="${y}" r="11" fill="#182331" stroke="${palette[t[i]]}"/><text x="${x}" y="${y+5}" text-anchor="middle" font-size="16" font-weight="700" fill="#fff7e5">${t[i]}</text>`).join('')}</svg>`;}
function suggest(){let bestI=-1,value=-Infinity;for(let i=0;i<19;i++){if(state.board[i])continue;let b=[...state.board];b[i]=state.deck[state.turn];let v=score(b)*4;for(let l of scoring(b))if(!l.broken)v+=l.filled*l.number/(l.cells.length-l.filled+1);if(v>value){value=v;bestI=i;}}return bestI;}
function render(){const done=state.turn===19,current=state.deck[state.turn];$('#score').textContent=score(state.board);$('#best').textContent=best;$('#progress').textContent=done?'19 / 19 placed':`${state.turn} / 19 placed`;$('#kind').textContent=state.kind==='daily'?'Daily weave · '+state.seed.slice(6):state.kind==='lesson'?'Guided table':'Shuffle · '+state.seed;
$('#current').innerHTML=done?'<div class="finished">✦</div>':tile(current);$('#numbers').textContent=done?'The weave is complete.':`Down-right ${current[0]} · Up-right ${current[1]} · Vertical ${current[2]}`;$('#status').textContent=done?`Weave complete · ${score(state.board)} points${state.assisted?' · hints used':''}. Try the same tiles again or a fresh shuffle.`:hint|| (selected>=0?`Preview at ${labels[selected]}. Place the tile to continue.`:'Choose an empty hex, then place your tile.');
$('#board').replaceChildren();for(let i=0;i<19;i++){let b=document.createElement('button');let [x,y]=coords[i];b.style.left=(50+1.5*x*11.9)+'%';b.style.top=(50+Math.sqrt(3)*(y+x/2)*10.6)+'%';b.className='hex'+(state.board[i]?' filled':'')+(selected===i?' selected':'');b.dataset.cell=i;b.innerHTML=tile(state.board[i]||(selected===i?current:null),labels[i]);b.setAttribute('aria-label',`${labels[i]} ${state.board[i]?state.board[i].join(','):'empty'}${selected===i?' preview':''}`);b.disabled=done||!!state.board[i];b.onclick=()=>{selected=i;hint='';render();};$('#board').append(b);}
$('#place').disabled=selected<0||done;$('#place').textContent=selected<0?'Choose a hex':'Place at '+labels[selected];$('#cancel').disabled=selected<0;$('#undo').disabled=!state.history.length;$('#hint').disabled=done;let actual=scoring(state.board),preview=[...state.board];if(selected>=0)preview[selected]=current;let pr=scoring(preview);$('#lines').replaceChildren();for(let axis=0;axis<3;axis++){let h=document.createElement('h3');h.textContent=['↘ Down-right','↗ Up-right','↓ Vertical'][axis];$('#lines').append(h);for(let l of actual.filter(l=>l.axis===axis)){let el=document.createElement('div'),p=pr[l.id];el.className='line'+(l.points?' earned':l.broken?' broken':'');el.textContent=`${l.cells.map(i=>labels[i]).join('–')} · ${l.points?l.number+' × '+l.cells.length+' = '+l.points:l.broken?'mixed · 0':l.filled+'/'+l.cells.length+' matching'+(l.filled?' '+l.number:'')}${selected>=0&&p.points!==l.points?' → '+p.points:''}`;$('#lines').append(el);}}
$('#lessonText').textContent=state.kind==='lesson'?'Complete the left vertical: the two existing tiles both carry 9. Place the new tile at A3 to score 3 × 9 = 27. After that, continue freely.':'Only complete edge-to-edge lines of identical numbers score. You may create partial lines now and finish them later.';}
function start(kind){let seed=kind==='daily'?'daily-'+new Date().toISOString().slice(0,10):'shuffle-'+Date.now().toString(36);state=fresh(seed);state.kind=kind;selected=-1;hint='';save();render();}
$('#shuffle').onclick=()=>start('shuffle');$('#daily').onclick=()=>start('daily');$('#replay').onclick=()=>{let seed=state.seed,kind=state.kind;state=fresh(seed);state.kind=kind==='lesson'?'shuffle':kind;selected=-1;hint='';save();render();};
$('#lesson').onclick=()=>{state=fresh('learn');state.kind='lesson';let ids=coords.map((c,i)=>c[0]===-2?i:-1).filter(i=>i>=0).sort((a,b)=>coords[a][1]-coords[b][1]);state.board[ids[0]]=[3,2,9];state.board[ids[1]]=[4,6,9];let used=new Set(state.board.filter(Boolean).map(t=>t.join(',')));state.deck=state.deck.filter(t=>!used.has(t.join(',')));let match=state.deck.findIndex(t=>t[2]===9);let t=state.deck.splice(match,1)[0];state.deck=[state.board[ids[0]],state.board[ids[1]],t,...state.deck];state.turn=2;selected=-1;hint='';save();render();};
$('#place').onclick=()=>{if(put(state,selected)){selected=-1;hint='';save();render();}};$('#undo').onclick=()=>{let p=state.history.pop();if(p){state.board=p.board;state.turn=p.turn;selected=-1;hint='';save();render();}};$('#cancel').onclick=()=>{selected=-1;hint='';render();};$('#hint').onclick=()=>{selected=suggest();state.assisted=true;hint=`Suggestion: ${labels[selected]}. This local heuristic favors completed and still-compatible lines; it cannot see future tiles.`;save();render();};$('#guide').onclick=()=>{$('#rules').hidden=false;$('#rules').scrollIntoView({behavior:'smooth'});};$('#close').onclick=()=>{$('#rules').hidden=true;$('#guide').focus();};document.addEventListener('keydown',e=>{if(e.key==='Escape'){selected=-1;hint='';render();}});render();
UPSTREAM engine/model/board/BoardClassic.java
package engine.model.board;
import engine.model.tile.TileInterface;
import javafx.geometry.Point2D;
import utils.boardutils.CubeCoordinates;
import utils.boardutils.HexagonalGrid;
import exceptions.CellNotAvailableException;
import java.util.*;
import java.util.stream.Collectors;
public class BoardClassic implements BoardInterface {
private final ArrayList<TileInterface> board;
private final String nickname;
public BoardClassic(String nickname) {
this.board = new ArrayList<>(Collections.nCopies(19, null));
this.nickname = nickname;
}
public BoardClassic(String nickname, List<TileInterface> board) {
this.board = (ArrayList<TileInterface>) board;
this.nickname = nickname;
}
@Override
public ArrayList<TileInterface> getBoard() {
return board;
}
@Override
public String getNickname() {
return nickname;
}
@Override
public int getScore() {
ArrayList<CubeCoordinates> coordinatesCells = (ArrayList<CubeCoordinates>) new CubeCoordinates(0, 0, 0).navigateSpiral(2);
HashMap<CubeCoordinates, Integer> cellsMap = new HashMap<>();
for (int j = 0; j < this.board.size(); ++j) {
cellsMap.put(coordinatesCells.get(j), j);
}
int score = 0;
for (int i =0; i<3;++i) {
HashSet<CubeCoordinates> availableCells = new HashSet<>(cellsMap.keySet());
while (!availableCells.isEmpty()) {
ArrayList<CubeCoordinates> line = new ArrayList<>();
CubeCoordinates pickedCell = availableCells.iterator().next();
line.add(pickedCell);
availableCells.remove(pickedCell);
exploreDirection(line,pickedCell.cubeNeighbor(i), i, availableCells, cellsMap);
exploreDirection(line,pickedCell.cubeNeighbor(i+3), i+3, availableCells, cellsMap);
int finalI = i;
Set<Integer> path = line.stream().map(item -> this.board.get(cellsMap.get(item)).getValues().get(finalI)).collect(Collectors.toSet());
if(path.size()==1){
score = score + line.size()*path.iterator().next();
}
}
}
return score;
}
public void exploreDirection(List<CubeCoordinates> line,CubeCoordinates currentCell, int direction, Set<CubeCoordinates> availableCells, Map<CubeCoordinates, Integer> cellsMap){
if (cellsMap.containsKey(currentCell)){
line.add(currentCell);
availableCells.remove(currentCell);
currentCell = currentCell.cubeNeighbor(direction);
exploreDirection(line, currentCell, direction, availableCells, cellsMap);
}
}
@Override
public List<Point2D> getEuclideanCoordinates() {
ArrayList<CubeCoordinates> coordinatesCells= (ArrayList<CubeCoordinates>) new CubeCoordinates(0,0,0).navigateSpiral(2);
float[][][] gridNumeric = new HexagonalGrid(3,10).getHexagonalGrid();
return coordinatesCells.stream().map(item ->new Point2D(gridNumeric[item.toEuclidean()[0] + 1][item.toEuclidean()[1] + 5][0], gridNumeric[item.toEuclidean()[0] + 1][item.toEuclidean()[1] + 5][1])).toList();
}
@Override
public BoardInterface copy() {
return new BoardClassic(this.nickname, new ArrayList<>(this.board));
}
@Override
public void placeTile(Integer index, TileInterface placedTile) throws CellNotAvailableException {
if (this.board.get(index) != null || index >= this.board.size()) {
throw new CellNotAvailableException();
}
this.board.set(index, placedTile);
}
@Override
public boolean isBoardFull() {
return !this.board.contains(null);
}
@Override
public TileInterface getTile(int tileId) {return board.get(tileId);
}
}
UPSTREAM utils/boardutils/CubeCoordinates.java
package utils.boardutils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class CubeCoordinates implements Comparable<CubeCoordinates> {
private Integer x;
private Integer y;
private Integer z;
private final int hashCode;
public CubeCoordinates(Integer x, Integer y, Integer z) {
this.x = x;
this.y = y;
this.z = z;
this.hashCode = Objects.hash(x, y);
}
public Integer getX() {
return x;
}
public Integer getY() {
return y;
}
public Integer getZ() {
return z;
}
public CubeCoordinates cubeDirection(int direction) {
CubeCoordinates[] cubeDirectionVectors=new CubeCoordinates[]{new CubeCoordinates(+1, 0, -1), new CubeCoordinates(+1, -1, 0), new CubeCoordinates(0, -1, +1),
new CubeCoordinates(-1, 0, +1), new CubeCoordinates(-1, +1, 0), new CubeCoordinates(0, +1, -1)};
return cubeDirectionVectors[direction];
}
public CubeCoordinates cubeAdd(CubeCoordinates vec) {
return new CubeCoordinates(this.x + vec.x, this.y + vec.y, this.z + vec.z);
}
public CubeCoordinates cubeScale( int factor){
return new CubeCoordinates(this.getX() * factor, this.getY() * factor, this.getZ() * factor);
}
public CubeCoordinates cubeNeighbor(int direction) {
return cubeAdd( cubeDirection(direction));
}
public CubeCoordinates rotateLeft() {
Integer buffer = x;
x = y;
y = z;
z = buffer;
return this;
}
public List<CubeCoordinates> navigateRing(int radius) {
CubeCoordinates center = new CubeCoordinates(this.x,this.y,this.z);
ArrayList<CubeCoordinates> results = new ArrayList<>();
if (radius == 0){
results.add(center);
}
else {
center = center.cubeAdd(center.cubeDirection(4).cubeScale(radius));
for (int i = 0; i < 6; i++) {
for (int j = 0; j < radius; j++) {
results.add(center);
center = center.cubeNeighbor(i);
}
}
}
return results;
}
public List<CubeCoordinates> navigateSpiral(int radius){
return IntStream.range(0, radius+1).mapToObj(this::navigateRing).flatMap(Collection::stream).collect(Collectors.toCollection(ArrayList::new));
}
public int[] toEuclidean(){
int[] coordinates= new int[2];
if (this.x % 2 == 0){
coordinates[0]=(this.x)/2;
coordinates[1]=(this.y+coordinates[0])*2;
}
else{
coordinates[0]=(this.x-1)/2;
coordinates[1]=(this.y+coordinates[0]+1)*2-1;
}
return coordinates;
}
@Override
public int compareTo(CubeCoordinates cubeCoordinates) {
return Math.abs(this.x-cubeCoordinates.getX()) +Math.abs(this.y- cubeCoordinates.getY());
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
CubeCoordinates that = (CubeCoordinates) o;
return x == that.x && y == that.y;
}
@Override
public int hashCode() {
return this.hashCode;
}
}
UPSTREAM engine/model/tile/Tile.java
package engine.model.tile;
import java.util.List;
public class Tile implements TileInterface {
private final int idTile;
private final int leftPath;
private final int rightPath;
private final int topPath;
public Tile(int idTile, int rightPath,int leftPath, int topPath) {
this.idTile = idTile;
this.leftPath = leftPath;
this.rightPath = rightPath;
this.topPath = topPath;
}
@Override
public int getIdTile() {
return idTile;
}
@Override
public int getLeftPath() {
return leftPath;
}
@Override
public int getRightPath() {
return rightPath;
}
@Override
public int getTopPath() {
return topPath;
}
@Override
public List<Integer> getValues() {
return List.of( rightPath,leftPath, topPath);
}
@Override
public String toString() {
return "\"Tile[idTile" + this.idTile + "leftPath" + this.leftPath + "rightPath" + this.rightPath + "topPath" + this.topPath + "]";
}
}
UPSTREAM utils/tile/Constants.java
package utils.tile;
import java.util.List;
public class Constants {
private Constants() {}
public static final List<Integer> leftPathValues = List.of(2, 6, 7);
public static final List<Integer> rightPathValues = List.of(3, 4, 8);
public static final List<Integer> topPathValues = List.of(1, 5, 9);
public static final int BESTSCORE = 307;
}