Skip to content

Instantly share code, notes, and snippets.

@tswaters
Created September 29, 2018 03:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tswaters/0719ab2cb1ada620bbed88f3f9dc31ad to your computer and use it in GitHub Desktop.
Save tswaters/0719ab2cb1ada620bbed88f3f9dc31ad to your computer and use it in GitHub Desktop.
Cards on canvas
<!doctype html>
<html>
<head>
<meta charet="utf-8"/>
<style>
canvas {
image-rendering: crisp-edges;
margin: 5px;
}
canvas.horizontal {
display: block;
}
canvas.vertical {
display: inline-block;
}
</style>
</head>
<body>
<script src="./canvas.js"></script>
</body>
</html>
/* eslint-env browser */
'use strict'
const radius = 10
const width = 75
const height = 97
const offset = 25
const cards = generateCards()
const cards_per_stack = 13
const stacks = cards.reduce((memo, item, index) => {
if (index % cards_per_stack === 0) {
const newStack = [{blank: true}]
newStack.push(item)
memo.push(newStack)
} else {
memo[memo.length - 1].push(item)
}
return memo
}, [])
let header = null
header = document.createElement('h2')
header.innerText = 'horizontal stack'
document.body.appendChild(header)
for (let i = 0; i < stacks.length; i++) {
document.body.appendChild(drawStack(stacks[i], 'horizontal'))
}
header = document.createElement('h2')
header.innerText = 'vertical stack'
document.body.appendChild(header)
for (let i = 0; i < stacks.length; i++) {
document.body.appendChild(drawStack(stacks[i], 'vertical'))
}
header = document.createElement('h2')
header.innerText = 'all cards'
document.body.appendChild(header)
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
canvas.width = cards_per_stack * width
canvas.height = (cards.length / cards_per_stack) * height
document.body.appendChild(canvas)
for (let i = 0; i < cards.length; i++) {
const offsetX = i % cards_per_stack * width
const offsetY = Math.floor(i / cards_per_stack) * height
drawCard(ctx, cards[i], {offsetX, offsetY, radius})
}
function drawStack (stack, direction) {
const canvas = document.createElement('canvas')
canvas.classList.add(direction)
const last = stack.length - 1
canvas.width = 1 + (direction === 'horizontal' ? offset * last + width : width)
canvas.height = 1 + (direction === 'horizontal' ? height : offset * last + height)
const ctx = canvas.getContext('2d')
ctx.translate(0.5, 0.5)
ctx.clearRect(0, 0, canvas.width, canvas.height)
for (let i = 0; i < stack.length; i++) {
const offsetX = direction === 'horizontal' ? i * offset : 0
const offsetY = direction === 'horizontal' ? 0 : i * offset
const clipWidth = direction === 'horizontal' ? offset : width
const clipHeight = direction === 'horizontal' ? height : offset
if (i < stack.length - 1) {
ctx.save()
drawClipRegion(ctx, {offsetX, offsetY, radius, direction, clipWidth, clipHeight})
}
drawCard(ctx, stack[i], {offsetX, offsetY, radius})
if (i < stack.length - 1) {
ctx.restore()
}
}
return canvas
}
function drawClipRegion (ctx, {offsetX, offsetY, radius, direction, clipWidth, clipHeight}) {
ctx.beginPath()
if (direction === 'horizontal') {
ctx.moveTo(offsetX, offsetY)
ctx.lineTo(offsetX + clipWidth + radius, offsetY)
ctx.quadraticCurveTo(offsetX + clipWidth, offsetY, offsetX + clipWidth, offsetY + radius)
ctx.lineTo(offsetX + clipWidth, offsetY + clipHeight - radius)
ctx.quadraticCurveTo(offsetX + clipWidth, offsetY + clipHeight, offsetX + clipWidth + radius, offsetY + clipHeight)
ctx.lineTo(offsetX, offsetY + clipHeight)
} else {
ctx.moveTo(offsetX + clipWidth, offsetY)
ctx.lineTo(offsetX + clipWidth, offsetY + clipHeight + radius)
ctx.quadraticCurveTo(offsetX + clipWidth, offsetY + clipHeight, offsetX + clipWidth - radius, offsetY + clipHeight)
ctx.lineTo(offsetX + radius, offsetY + clipHeight)
ctx.quadraticCurveTo(offsetX, offsetY + clipHeight, offsetX, offsetY + clipHeight + radius)
ctx.lineTo(offsetX, offsetY)
}
ctx.closePath()
ctx.clip()
}
function drawBoxRadius (ctx, {offsetX, offsetY, radius}) {
ctx.beginPath()
ctx.moveTo(offsetX + radius, offsetY)
ctx.lineTo(offsetX + width - radius, offsetY)
ctx.quadraticCurveTo(offsetX + width, offsetY, offsetX + width, offsetY + radius)
ctx.lineTo(offsetX + width, offsetY + height - radius)
ctx.quadraticCurveTo(offsetX + width, offsetY + height, offsetX + width - radius, offsetY + height)
ctx.lineTo(offsetX + radius, offsetY + height)
ctx.quadraticCurveTo(offsetX, offsetY + height, offsetX, offsetY + height - radius)
ctx.lineTo(offsetX, offsetY + radius)
ctx.quadraticCurveTo(offsetX, offsetY, offsetX + radius, offsetY)
ctx.closePath()
}
function drawCard (ctx, card, {offsetY, offsetX, radius}) {
const {blank, value, suit, drawing} = card
drawBoxRadius(ctx, {offsetX, offsetY, radius})
ctx.stroke()
if (blank) {
ctx.fillStyle = '#0aa'
ctx.fill()
return
}
ctx.fillStyle = drawing.color
ctx.textAlign = 'center'
ctx.textBaseline = 'top'
ctx.font = drawing.cornerFont
ctx.fillText(value, drawing.valueXOffset + offsetX, drawing.valueYOffset + offsetY, 12)
ctx.fillText(suit, drawing.suitXOffset + offsetX, drawing.suitYOffset + offsetY)
ctx.save()
ctx.translate(width, height)
ctx.rotate(Math.PI)
ctx.fillText(value, drawing.valueXOffset - offsetX, drawing.valueYOffset - offsetY, 12)
ctx.fillText(suit, drawing.suitXOffset - offsetX, drawing.suitYOffset - offsetY)
ctx.restore()
ctx.textBaseline = 'middle'
for (const pos of drawing.positions) {
const factor = pos.rotated ? -1 : 1
ctx.textAlign = pos.textAlign
if (pos.rotated) {
ctx.save()
ctx.translate(width, height)
ctx.rotate(Math.PI)
}
ctx.font = `${drawing.fontSize} sans-serif`
ctx.fillText(suit, pos.left + offsetX * factor, pos.top + offsetY * factor)
if (pos.rotated) {
ctx.restore()
}
}
}
function generateCards () {
const ValueType = {
ace: 'A',
two: '2',
three: '3',
four: '4',
five: '5',
six: '6',
seven: '7',
eight: '8',
nine: '9',
ten: '10',
jack: 'J',
queen: 'Q',
king: 'K'
}
const SuitType = {
heart: '\u2665',
diamond: '\u2666',
spade: '\u2660',
club: '\u2663'
}
const ret = []
for (const [, suit] of Object.entries(SuitType)) {
for (const [, value] of Object.entries(ValueType)) {
ret.push({
suit,
value,
drawing: getDrawing(suit, value)
})
}
}
function getDrawing (suit, value) {
const color = [
SuitType.diamond,
SuitType.heart
].indexOf(suit) > -1 ? 'red' : 'black'
const fontSize = [
ValueType.ace,
ValueType.jack,
ValueType.queen,
ValueType.king
].indexOf(value) > -1 ? '72px' : '20px'
const pos = []
if ([ValueType.ace, ValueType.three, ValueType.five, ValueType.nine, ValueType.jack, ValueType.queen, ValueType.king].indexOf(value) > -1) {
pos.push({x: 1, y: 3})
}
if ([ValueType.two, ValueType.three].indexOf(value) > -1) {
pos.push({x: 1, y: 0}, {x: 1, y: 6})
}
if ([ValueType.four, ValueType.five, ValueType.six, ValueType.seven, ValueType.eight, ValueType.nine, ValueType.ten].indexOf(value) > -1) {
pos.push({x: 0, y: 0}, {x: 2, y: 0}, {x: 0, y: 6}, {x: 2, y: 6})
}
if ([ValueType.six, ValueType.seven, ValueType.eight].indexOf(value) > -1) {
pos.push({x: 0, y: 3}, {x: 2, y: 3})
}
if ([ValueType.seven, ValueType.ten, ValueType.eight].indexOf(value) > -1) {
pos.push({x: 1, y: 1})
}
if ([ValueType.nine, ValueType.ten].indexOf(value) > -1) {
pos.push({x: 0, y: 2}, {x: 2, y: 2}, {x: 0, y: 4}, {x: 2, y: 4})
}
if ([ValueType.ten, ValueType.eight].indexOf(value) > -1) {
pos.push({x: 1, y: 5})
}
const getTop = y => {
switch (y) {
case 0: case 6: return height * 0.2
case 1: case 5: return height * 0.3
case 2: case 4: return height * 0.4
case 3: return height * 0.5
}
}
const getLeft = x => {
switch (x) {
case 0: return width * 0.25
case 1: return width * 0.50
case 2: return width * 0.75
}
}
const getTextAlign = x => {
switch (x) {
case 0: return 'left'
case 1: return 'center'
case 2: return 'right'
}
}
const positions = pos.map(({x, y}) => {
return {
textAlign: getTextAlign(x),
rotated: y > 3,
left: getLeft(x),
top: getTop(y)
}
})
return {
cornerFont: 'bold 15px sans-serif',
valueXOffset: 9,
valueYOffset: 2,
suitXOffset: 9,
suitYOffset: 12,
color,
fontSize,
positions
}
}
return ret
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment