Public
Edited
Sep 29, 2024
Fork of Candyland
2 forks
1 star
Insert cell
Insert cell
Insert cell
// This cell does not re-run when gamestate changes
shell = {
const {height, width, margins: {top, left, bottom, right}} = options,
{spaces, bridges, curve} = board,

// At first, just create the basic SVG
sel = d3.create("svg")
.attr("width", width)
.attr("height", height),
container = sel.append("g")
.attr("id", "container")
.attr("transform", `translate(${left}, ${top})`),
bridgeContainer = container.append("g"),
//create the board's spaces
w = width - left - right,
h = height - top - bottom,
xScale = d3.scaleLinear(d3.extent(curve, d => d[0]), [left, w]),
yScale = d3.scaleLinear(d3.extent(curve, d => d[1]), [top, h]),
line = d3.line()
.curve(d3.curveBasis)
.x(d => xScale(d[0]))
.y(d => yScale(d[1])),

//create curved path for the spaces to appear on
path = container.append("path")
.attr('class', 'twistypath')
.attr('d', line(curve)),
distance = path.node().getTotalLength(),
//create a scale to create the spaces, and where the pieces go
// |--0--|--1--|--2--|
// 0-----1-----2-----3
// 0.0---5.1---10.2--15.3
scaleSpacesX = d3.scaleLinear()
.domain([0, spaces.length-1])
.range([0, distance]),
spaceW = scaleSpacesX(1)
path.attr('stroke-width', Math.floor(spaceW *.9) )

//angle for spaces along the path
const getNormal = (dist) => {
const delta = distance * .0001

if (dist < (distance - delta)) {
const {x:x1, y:y1} = path.node().getPointAtLength(dist),
{x:x2, y:y2} = path.node().getPointAtLength(dist + delta)
const theta = Math.atan2(y2-y1, x2-x1) * 180 / Math.PI
return ((theta + 270) % 180) - 90 //flip upside down ones right side up (ensure between -90 and 90)
}
return 0
}

//get coords for spaces along the path
const positionAtIndex = (spaces, path) => ({index}, delta = 0) => {
const len = path.node().getTotalLength(),
scale = d3.scaleLinear()
.domain([0, spaces.length - 1])
.range([0, len])

return path.node().getPointAtLength(scale(index + delta))
},
getBoardPosition = positionAtIndex(board.spaces, path)

//LAYOUT THE BOARD
//------------------
//Regular spaces and holes along the Path
container.selectAll(".space")
.data(spaces)
.join(enter => {
const s= enter.append("g").attr("class", d => "space")

s.append("rect")
.attr("class", d => d.space)
.attr("fill", d => d.space)
.attr("ry", spaceW/4)
.attr("width", spaceW * .8)
.attr("height", spaceW * .5)
return s} ,
update => update,
exit => exit.remove() )
.attr("transform", d => {
const dx = (getBoardPosition(d).x ) - spaceW * .4,
dy = getBoardPosition(d).y - spaceW * .25
return `translate(${dx} ${dy}) rotate(${getNormal(scaleSpacesX(d.index))} ${spaceW * .4} ${spaceW * .25})`
})

//LICORICE SPACES
const licorice = container.selectAll(".space")
.filter(d => d.action == "licorice")
.each(function(d) {
d3.select(this).append("circle")
.attr("fill","black").attr("r", spaceW/10)
.attr("cx", spaceW * .4)
.attr("cy", spaceW * .25)
})
//SPECIAL CHARACTER SPACES
const character = container.selectAll(".space")
.filter(d => d.action == "character")
.each(function({space}) {
d3.select(this).select("rect")
.attr("stroke", "none")
.attr("fill", "white")
.attr("class", "character")

d3.select(this)
.append("text")
.html(icons[space])
.attr("dx", spaceW * .4)
.attr("dy", spaceW * .25)
})
//BRIDGES
const bLines = bridges.map(b => {
const {start, end, pattern} = b,
// scale = d3.scaleLinear([start, end], [getBoardPosition({index: start}), getBoardPosition({index: end})]),
scaleX = d3.scaleLinear([pattern[0][0], pattern[pattern.length-1][0]], [getBoardPosition({index: start}).x, getBoardPosition({index: end}).x]),
scaleY = d3.scaleLinear([pattern[0][1], pattern[pattern.length-1][1]], [getBoardPosition({index: start}).y, getBoardPosition({index: end}).y]),
line = d3.line()
// .x(d => scale(d).x)
// .y(d => scale(d).y)
.x(d => scaleX(d[0]))
.y(d => scaleY(d[1]))
return Object.assign(b, {"line" : line})
}),
bridgePaths = bridgeContainer.selectAll(".bridge")
.data(bLines)
// .join(enter => enter.append("line") //TODO: convert to path with line()
.join(enter => enter.append("path") //TODO: convert to path with line()
.attr("id", d => d.label.replace(/\s+/g, '-').toLowerCase())
.attr("class", "bridge")
// .attr("d", d => d.line([d.start, d.end])),
.attr("d", d => d.line(d.pattern)),
update => update,
exit => exit.remove() )
console.log("blines", bLines)

//LAYOUT THE PLAYERS
return Object.assign(sel.node(), {
//update game state with each player's turn
update: function(data) {
//place the players
const players = container.selectAll(".player")
// .data(data.players, d => d.color)
.data([data.players[data.currentPlayer]], d => d.color)
.join(enter => enter
.append("path")
.attr("d", gingerbread)
.attr("class", "player")
.attr("fill", d => d.color)
.attr("transform", (d) => `translate(${getBoardPosition({index:0}).x-17.5} ${getBoardPosition({index:0}).y - 40}) scale(0.5)`),
update => update,
//don't remove players that aren't being updated
//leave them on the board where they are, and they won't be tweened
exit => exit)

players
.each(function(d) {
const that = this,
player = d3.select(this)
player.raise()

if (d.moves.length > 0) {
d.moves.reduce((delay, b , i, array) => {
const [start, end, bridge] = b,
delta = (end.index - start.index), duration = 150 * Math.abs(delta)

player.transition()
.delay(delay)
.duration(duration)
.ease(d3.easeCubicOut)
.tween(`hop${Math.floor(Math.random()*1000)}`, d => {
const bLabel = (bridge === undefined) ? "board" : bridge.label.replace(/\s+/g, '-').toLowerCase(),
// bridge = bridges.filter(d => d.label == label)[0],
path = d3.select("#"+ bLabel),
// getPositionAlongBridge = positionAtIndex([0,1], path),
getPos = (bridge !== undefined) ? positionAtIndex([0,1], path) : getBoardPosition


console.log("tween", bLabel, bridge, path, getPos)
return function(t) {
// player.attr("transform", `translate(${getBoardPosition(start, t * delta).x - 17.5} ${getBoardPosition(start, t * delta).y - 40} ) scale(.5)`)
player.attr("transform", `translate(${getPos(start, t * delta).x - 17.5} ${getPos(start, t * delta).y - 40} ) scale(.5)`)
}
})
return delay + duration
},2000)
}
})
}
})
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
[[${stroke.join("],[")}]]
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more