387 lines
10 KiB
Typst
387 lines
10 KiB
Typst
#let position(..rows) = {
|
|
rows.pos()
|
|
}
|
|
|
|
#let othello(pos, labels: true, cell-size: 30pt) = {
|
|
let rows = pos
|
|
|
|
// Colors
|
|
let board-color = rgb("#2d8659")
|
|
let line-color = rgb("#1a5c3a")
|
|
let white-piece = rgb("#f0f0f0")
|
|
let black-piece = rgb("#1a1a1a")
|
|
let label-color = rgb("#000000")
|
|
let flip-color = rgb("#d32f2f")
|
|
let mask-color = rgb("#000000").transparentize(60%)
|
|
|
|
// Calculate total size
|
|
let board-size = cell-size * 8
|
|
let label-offset = if labels { 15pt } else { 0pt }
|
|
let total-size = board-size + label-offset * 2
|
|
let piece-radius = cell-size * 0.4
|
|
let move-radius = cell-size * 0.25
|
|
|
|
box(
|
|
width: total-size,
|
|
height: total-size,
|
|
{
|
|
place(
|
|
dx: label-offset,
|
|
dy: label-offset,
|
|
// Draw board background
|
|
rect(
|
|
width: board-size,
|
|
height: board-size,
|
|
fill: board-color,
|
|
stroke: line-color + 2pt
|
|
)
|
|
)
|
|
|
|
// Draw grid lines
|
|
for i in range(1, 8) {
|
|
let offset = cell-size * i
|
|
// Vertical lines
|
|
place(
|
|
dx: label-offset + offset,
|
|
dy: label-offset,
|
|
line(
|
|
start: (0pt, 0pt),
|
|
end: (0pt, board-size),
|
|
stroke: line-color + 1pt
|
|
)
|
|
)
|
|
// Horizontal lines
|
|
place(
|
|
dx: label-offset,
|
|
dy: label-offset + offset,
|
|
line(
|
|
start: (0pt, 0pt),
|
|
end: (board-size, 0pt),
|
|
stroke: line-color + 1pt
|
|
)
|
|
)
|
|
}
|
|
|
|
// Draw pieces and markers
|
|
for (row-idx, row) in rows.enumerate() {
|
|
for (col-idx, cell) in row.clusters().enumerate() {
|
|
// Regular pieces
|
|
if cell == "B" or cell == "W" {
|
|
let x = label-offset + col-idx * cell-size + cell-size / 2 - piece-radius
|
|
let y = label-offset + row-idx * cell-size + cell-size / 2 - piece-radius
|
|
let piece-color = if cell == "B" { black-piece } else { white-piece }
|
|
let piece-stroke = if cell == "W" { line-color + 0.5pt } else { none }
|
|
|
|
place(
|
|
dx: x,
|
|
dy: y,
|
|
circle(
|
|
radius: piece-radius,
|
|
fill: piece-color,
|
|
stroke: piece-stroke
|
|
)
|
|
)
|
|
}
|
|
// Pieces to be flipped
|
|
else if cell == "b" or cell == "w" {
|
|
let x = label-offset + col-idx * cell-size + cell-size / 2 - piece-radius
|
|
let y = label-offset + row-idx * cell-size + cell-size / 2 - piece-radius
|
|
let piece-color = if cell == "b" { black-piece } else { white-piece }
|
|
|
|
place(
|
|
dx: x,
|
|
dy: y,
|
|
circle(
|
|
radius: piece-radius,
|
|
fill: piece-color,
|
|
stroke: flip-color + 2pt
|
|
)
|
|
)
|
|
}
|
|
// Available moves
|
|
else if cell == "0" or cell == "1" {
|
|
let x = label-offset + col-idx * cell-size + cell-size / 2 - move-radius
|
|
let y = label-offset + row-idx * cell-size + cell-size / 2 - move-radius
|
|
let move-color = if cell == "0" {
|
|
black-piece.transparentize(50%)
|
|
} else {
|
|
white-piece.transparentize(50%)
|
|
}
|
|
|
|
place(
|
|
dx: x,
|
|
dy: y,
|
|
circle(
|
|
radius: move-radius,
|
|
fill: move-color,
|
|
stroke: none
|
|
)
|
|
)
|
|
}
|
|
// Masked squares
|
|
else if cell == "X" {
|
|
let x = label-offset + col-idx * cell-size
|
|
let y = label-offset + row-idx * cell-size
|
|
|
|
place(
|
|
dx: x,
|
|
dy: y,
|
|
rect(
|
|
width: cell-size,
|
|
height: cell-size,
|
|
fill: mask-color,
|
|
stroke: none
|
|
)
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Draw labels
|
|
if labels {
|
|
let files = ("a", "b", "c", "d", "e", "f", "g", "h")
|
|
let ranks = ("8", "7", "6", "5", "4", "3", "2", "1").rev()
|
|
|
|
// File labels (bottom)
|
|
for (i, file) in files.enumerate() {
|
|
let x = label-offset + i * cell-size + cell-size / 2
|
|
place(
|
|
dx: x,
|
|
dy: board-size + label-offset + 3pt,
|
|
align(center, text(size: 10pt, fill: label-color, weight: "bold", file))
|
|
)
|
|
}
|
|
|
|
// Rank labels (left)
|
|
for (i, rank) in ranks.enumerate() {
|
|
let y = label-offset + i * cell-size + cell-size / 2
|
|
place(
|
|
dx: 3pt,
|
|
dy: y,
|
|
align(horizon, text(size: 10pt, fill: label-color, weight: "bold", rank))
|
|
)
|
|
}
|
|
}
|
|
}
|
|
)
|
|
}
|
|
|
|
#let othello-legend(cell-size: 20pt) = {
|
|
let board-color = rgb("#2d8659")
|
|
let line-color = rgb("#1a5c3a")
|
|
let white-piece = rgb("#f0f0f0")
|
|
let black-piece = rgb("#1a1a1a")
|
|
let flip-color = rgb("#d32f2f")
|
|
let mask-color = rgb("#000000").transparentize(60%)
|
|
let piece-radius = cell-size * 0.4
|
|
let move-radius = cell-size * 0.25
|
|
|
|
let legend-item(symbol, description) = {
|
|
grid(
|
|
columns: (cell-size + 10pt, 1fr),
|
|
align: (center + horizon, left + horizon),
|
|
gutter: 10pt,
|
|
// Symbol cell
|
|
box(
|
|
width: cell-size,
|
|
height: cell-size,
|
|
{
|
|
place(
|
|
rect(
|
|
width: cell-size,
|
|
height: cell-size,
|
|
fill: board-color,
|
|
stroke: line-color + 1pt
|
|
)
|
|
)
|
|
symbol
|
|
}
|
|
),
|
|
// Description
|
|
text(size: 10pt, description)
|
|
)
|
|
}
|
|
|
|
stack(
|
|
dir: ttb,
|
|
spacing: 6pt,
|
|
|
|
legend-item(
|
|
place(
|
|
dx: cell-size / 2 - piece-radius,
|
|
dy: cell-size / 2 - piece-radius,
|
|
circle(radius: piece-radius, fill: black-piece)
|
|
),
|
|
[Black disc]
|
|
),
|
|
|
|
legend-item(
|
|
place(
|
|
dx: cell-size / 2 - piece-radius,
|
|
dy: cell-size / 2 - piece-radius,
|
|
circle(radius: piece-radius, fill: white-piece, stroke: line-color + 0.5pt)
|
|
),
|
|
[White disc]
|
|
),
|
|
|
|
legend-item(
|
|
place(
|
|
dx: cell-size / 2 - piece-radius,
|
|
dy: cell-size / 2 - piece-radius,
|
|
circle(radius: piece-radius, fill: black-piece, stroke: flip-color + 2pt)
|
|
),
|
|
[Black disc to be flipped]
|
|
),
|
|
|
|
legend-item(
|
|
place(
|
|
dx: cell-size / 2 - piece-radius,
|
|
dy: cell-size / 2 - piece-radius,
|
|
circle(radius: piece-radius, fill: white-piece, stroke: flip-color + 2pt)
|
|
),
|
|
[White disc to be flipped]
|
|
),
|
|
|
|
legend-item(
|
|
place(
|
|
dx: cell-size / 2 - move-radius,
|
|
dy: cell-size / 2 - move-radius,
|
|
circle(radius: move-radius, fill: black-piece.transparentize(50%))
|
|
),
|
|
[Available move for black]
|
|
),
|
|
|
|
legend-item(
|
|
place(
|
|
dx: cell-size / 2 - move-radius,
|
|
dy: cell-size / 2 - move-radius,
|
|
circle(radius: move-radius, fill: white-piece.transparentize(50%))
|
|
),
|
|
[Available move for white]
|
|
),
|
|
|
|
legend-item(
|
|
place(
|
|
rect(width: cell-size, height: cell-size, fill: mask-color)
|
|
),
|
|
[Masked square]
|
|
),
|
|
|
|
legend-item(
|
|
[],
|
|
[Empty square]
|
|
),
|
|
)
|
|
}
|
|
|
|
#let shift-indicator(values, cell-size: 30pt) = {
|
|
assert(values.len() == 9, message: "shift-indicator requires exactly 9 values")
|
|
|
|
let board-color = rgb("#2d8659")
|
|
let line-color = rgb("#1a5c3a")
|
|
let white-piece = rgb("#f0f0f0")
|
|
let black-piece = rgb("#1a1a1a")
|
|
|
|
let grid-size = cell-size * 3
|
|
let piece-radius = cell-size * 0.4
|
|
|
|
box(
|
|
width: grid-size,
|
|
height: grid-size,
|
|
{
|
|
// Draw background
|
|
place(
|
|
rect(
|
|
width: grid-size,
|
|
height: grid-size,
|
|
fill: board-color,
|
|
stroke: line-color + 2pt
|
|
)
|
|
)
|
|
|
|
// Draw grid lines
|
|
for i in range(1, 3) {
|
|
let offset = cell-size * i
|
|
// Vertical lines
|
|
place(
|
|
dx: offset,
|
|
line(
|
|
start: (0pt, 0pt),
|
|
end: (0pt, grid-size),
|
|
stroke: line-color + 1pt
|
|
)
|
|
)
|
|
// Horizontal lines
|
|
place(
|
|
dy: offset,
|
|
line(
|
|
start: (0pt, 0pt),
|
|
end: (grid-size, 0pt),
|
|
stroke: line-color + 1pt
|
|
)
|
|
)
|
|
}
|
|
|
|
// Draw cells
|
|
for row in range(3) {
|
|
for col in range(3) {
|
|
let idx = row * 3 + col
|
|
let value = values.at(idx)
|
|
let x = col * cell-size
|
|
let y = row * cell-size
|
|
|
|
if value == 0 {
|
|
// Half-white, half-black disc
|
|
let center-x = x + cell-size / 2
|
|
let center-y = y + cell-size / 2
|
|
|
|
// Draw full black circle
|
|
place(
|
|
dx: center-x - piece-radius,
|
|
dy: center-y - piece-radius,
|
|
circle(radius: piece-radius, fill: black-piece, stroke: none)
|
|
)
|
|
|
|
// Draw white half-circle using clipping
|
|
place(
|
|
dx: center-x,
|
|
dy: center-y - piece-radius,
|
|
box(
|
|
width: piece-radius,
|
|
height: piece-radius * 2,
|
|
clip: true,
|
|
place(
|
|
dx: -piece-radius,
|
|
circle(radius: piece-radius, fill: white-piece, stroke: none)
|
|
)
|
|
)
|
|
)
|
|
|
|
// Draw circle outline
|
|
place(
|
|
dx: center-x - piece-radius,
|
|
dy: center-y - piece-radius,
|
|
circle(radius: piece-radius, fill: none, stroke: line-color + 0.5pt)
|
|
)
|
|
} else {
|
|
// Arrow and number
|
|
let arrow = if value > 0 { "«" } else { "»" }
|
|
let num = str(calc.abs(value))
|
|
place(
|
|
dx: x,
|
|
dy: y,
|
|
box(
|
|
width: cell-size,
|
|
height: cell-size,
|
|
align(
|
|
center + horizon,
|
|
text(fill: white, weight: "bold", size: 10pt, arrow + " " + num)
|
|
)
|
|
)
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
)
|
|
}
|