/Games/Solitaire.foocad

The classic game of Solitaire (not the card game). The script include designs for a box to hold the board and pieces.
I used ball bearings, but you could use the more traditional marbles.
import static uk.co.nickthecoder.foocad.extras.v1.Extras.*
import static uk.co.nickthecoder.foocad.layout.v1.Layout2d.*
import static uk.co.nickthecoder.foocad.layout.v1.Layout3d.*
import static uk.co.nickthecoder.foocad.chamferedextrude.v1.ChamferedExtrude.*
import static uk.co.nickthecoder.foocad.changefilament.v1.ChangeFilament.*
include PuzzleBox.foocad
class Solitaire : AbstractModel() {
// Diameter of the balls/marbles, plus a little slack.
// 21 is suitable for 20mm balls.
var ballSize = 21
// My first print used gap=2, but there needs to be bigger gaps, so that it
// is easier to get your fingers in.
// Also, this gap makes the pieces the same width as the "ring" and "cups"
// so it will fit nicely in the box.
var gap = 6.5
var dist = 40
var thickness = 4.0
var thinnest = 0.8
var chamfer = 0.8
var piece = ""
var slack = 0.5
fun nobble() : Shape2d {
val size = ballSize * 300
val xa = size* 0.11
val xb = size* 0.2
val yb = size* 0.2
val yc = size* 0.35
val delta = size * 0.1
return PolygonBuilder().apply {
moveTo( xa, -slack*100 )
bezierTo( Vector2(xa-delta, delta), Vector2(xb, yb-delta), Vector2(xb, yb) )
bezierTo( Vector2(xb, yb+delta), Vector2(delta, yc), Vector2(0, yc) )
bezierTo( Vector2(-delta,yc), Vector2(-xb,yb+delta), Vector2(-xb, yb) )
bezierTo( Vector2(-xb,yb-delta), Vector2(-xa+delta, delta), Vector2(-xa, -slack*100) )
}.build().scale(0.01)
}
@Piece
fun center() : Shape3d {
val size = (ballSize + gap ) * 3
val hollows = Sphere( ballSize / 2 )
.tileX( 3, gap )
.tileY( 3, gap )
.center()
.translateZ( ballSize/2 + thinnest )
.color("Yellow").darker()
val nibble = nobble().offset(slack).mirrorY().translateY(size/2)
val square = Square( size-slack ).center().roundAllCorners( 2 )
val solid = ( square - nibble.repeatAround(4) )
.chamferedExtrude( thickness, chamfer )
.color( "Yellow" )
return solid - hollows //+ hollows.previewOnly()
}
@Piece
fun side() : Shape3d {
val sizeX = (ballSize+gap)*3
val sizeY = (ballSize+gap)*2
val hollows = Sphere( ballSize / 2 )
.tileX( 3, gap )
.tileY( 3, gap )
.centerX()
.translateY( -sizeY/4 )
.translateZ( ballSize/2 + thinnest )
.color( "Blue" )
val nobble : Shape2d = nobble().translateY(sizeY/2)
val rect = Square( sizeX-slack, sizeY-slack ).center()
.roundCorner( 3, 2 ).roundCorner( 2, 2 )
.roundCorner( 1, 5 ).roundCorner( 0, 5 )
val solid = (rect + nobble)
.chamferedExtrude( thickness, chamfer )
.color("RoyalBlue")
return solid - hollows // + hollows.previewOnly()
}
@Piece
fun triangular() : Shape3d {
val size = (ballSize + gap) * 5 + ballSize / 2
val delta = gap
val solid = Triangle( size )
.translateY( -ballSize/2 )
.roundAllCorners( ballSize /2 )
.chamferedExtrude( thickness, chamfer )
.color( "RoyalBlue" )
val dy = Degrees.sin(60) * ( ballSize + gap )
val hollow = Sphere( ballSize / 2 )
.translateZ( ballSize/2 + thinnest )
val hollows = hollow.tileX( 5, gap ).centerX() +
hollow.tileX( 4, gap ).centerX().translateY( dy ) +
hollow.tileX( 3, gap ).centerX().translateY( dy*2 ) +
hollow.tileX( 2, gap ).centerX().translateY( dy*3 ) +
hollow.translateY( dy*4 )
return solid - hollows // + hollows.previewOnly()
}
@Piece
fun rings() : Shape3d {
return Square(ballSize*4).center().roundAllCorners(5)
.chamferedExtrude( thickness*2, 0.6 ) -
// The 0.5 is so that the balls do not touch each other.
Sphere(ballSize/2).tileX(4).tileY(4).center().translateZ(ballSize/2 + thickness +0.5) -
Sphere(ballSize/2).tileX(4).tileY(4).center().translateZ(-ballSize/2 + thickness -0.5) -
// Rather than do the maths, I've hard coded 0.3 as a resonable radius for the holes.
// But if you change the @Custom parameters, then this may not work ;-(
Cylinder( ballSize, ballSize*0.3 ).repeatX(4,ballSize).repeatY(4,ballSize).center()
}
fun createBox( part : String ) : PuzzleBox {
return PuzzleBox().apply {
width = ballSize * 4 + 1
// Two layers of balls in the "rings" holder a shelf, and 25 for the
// jigsaw pieces.
depth = (ballSize + thickness * 2) * 2 + thickness + thickness * 4 + 2
bottomHeight = ballSize * 4 - ballSize/2
topHeight = ballSize / 2 + 4
radius = 1
cornerEars = 15
pattern = "none"
}
}
@Piece
fun box() : Shape3d {
val box = createBox( "bottom" )
val chamfer = box.thickness / 4
val shelf = Square( box.width, box.bottomHeight + thickness + chamfer )
.chamferedExtrude( thickness, thickness/4 )
.center()
.rotateX(90)
.toOriginZ().toOriginY().translateY( ballSize * 2 + thickness * 2 + 4)
.translateZ( box.thickness - chamfer )
val builtBox = box.build()
val finger = Cylinder( 1000, 14 ).center().rotateX(90)
.translateZ( box.thickness + box.bottomHeight + 4)
val previewPieces = Cube( ballSize * 4, 24 /*thickness * 5 + 4*/, ballSize * 4 )
.centerX().translateZ( box.thickness )
.translateY(box.thickness*2 + ballSize*2 + 9 )
val previewStack = Cube( ballSize * 4, 49 /*ballSize*2 + 7*/, ballSize * 4 )
.centerX().translateZ( box.thickness )
.translateY(box.thickness)
return builtBox.translateY(box.thickness + box.depth/2) + shelf - finger +
previewStack .previewOnly() +
previewPieces.previewOnly() +
lid()
.mirrorZ().toOriginZ().translateZ(box.bottomHeight-4 + 30)
.toOriginY()
.previewOnly()
}
@Piece
fun lid() : Shape3d {
val box = createBox( "top" )
val finger = (Circle( 14-0.5 ) - Square(40).mirrorY().centerX()).extrude( box.thickness )
.rotateX(90)
.translateY( - box.depth/2 )
.translateZ( box.thickness + box.topHeight - 4 )
return box.build() + finger.mirrorY().also()
}
@Piece
fun logo() : Shape3d {
val dist = ballSize*0.48
val lineThickness = 0.6
val thickness = 1.0
val width = 1
val circ = Circle( 3 )
val circs = circ.repeatX(7, dist).repeatY(3,dist).center().rotate(90).also()
val lines = Square(6*dist,width).repeatY(3,dist).center().rotate(90).also() +
Square(width,2*dist).repeatX(7,dist).center().rotate(90).also()
return circs.extrude( thickness ) + lines.extrude( lineThickness )
}
@Piece
fun logo2() : Shape3d {
val backing = Square( 87,82 ).center().roundAllCorners(2).extrude(0.6)
val logo = Text("Solitaire", BOLD, 13).centerX()
.translateY(15)
.extrude(1.2) +
Text( "nickthecoder\n.co.uk", 8).hAlign(CENTER).centerX()
.translateY(-20)
.extrude(1.2) +
Circle( 5 ).tileX( 3, 4 ).center().extrude(1.0)
return backing.color("Blue") + logo.color("Yellow")
}
@Piece
fun logo3() : Shape3d {
val backing = Square( 87,82 ).center().roundAllCorners(2).extrude(0.6)
return backing.color( "Blue" ) + logo().translateZ(0.6).color("Yellow")
}
@Piece( printable = false )
override fun build() : Shape3d {
val center : Shape3d = center()
val side : Shape3d = side()
.rotateZ(180)
.translateY((ballSize + gap) * 2.5)
return center + side.repeatAroundZ(4)
}
/*
// This is the main focus of this example! Also not that the class must implement PostProcessor.
override fun postProcess(gcode: GCode) {
if (piece == "logo2" || piece == "logo3" ) {
pauseAtHeight( gcode, 0.6, "Change Filament" )
}
}
*/
}

