Skip to content

Instantly share code, notes, and snippets.

@Roland09
Created February 12, 2015 04:01
Show Gist options
  • Save Roland09/e00f7e97852c9113877b to your computer and use it in GitHub Desktop.
Save Roland09/e00f7e97852c9113877b to your computer and use it in GitHub Desktop.
Deck of playing cards. Solely created from plain Java, no images, no svg.
package cards;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.VPos;
import javafx.scene.Node;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.paint.Color;
import javafx.scene.paint.ImagePattern;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Shape;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.scene.text.TextBoundsType;
/**
* Create card faces that consist solely of JavaFX code, no images.
* You can easily exchange this class against another card face creator that creates the faces from images if you wish.
*/
public class CardFaceCreator {
/**
* Create the front face of the card.
* @param suit
* @param rank
* @param width
* @param height
* @return
*/
public static Node createFrontFace( Suit suit, Rank rank, double width, double height) {
CardFace face = new CardFace();
Shape shape = createShape( width, height);
shape.setFill( Color.WHITE);
face.getChildren().add( shape);
addCornerText( face, suit, rank, width, height);
addCenterItems( face, suit, rank, width, height);
return face;
}
/**
* Create the back face of the card.
* @param width
* @param height
* @return
*/
public static Node createBackFace( double width, double height) {
CardFace face = new CardFace();
Shape shape = createShape( width, height);
shape.setFill( Color.LIGHTBLUE);
// if you find the image pattern too disturbing, simply comment out these 2 lines
ImagePattern pattern = ImagePatternCreator.createRectanglePattern();
shape.setFill( pattern);
face.getChildren().add( shape);
return face;
}
/**
* The card's shape, e. g. a rectangle with rounded corners
* @param width
* @param height
* @return
*/
public static Rectangle createShape( double width, double height) {
Rectangle shape = new Rectangle();
shape.setWidth( width);
shape.setHeight( height);
shape.setArcWidth( Settings.CARD_ARC);
shape.setArcHeight( Settings.CARD_ARC);
shape.setStroke( Color.BLACK);
shape.setFill( Color.TRANSPARENT);
return shape;
}
/**
* Create top/left and bottom/right corner text / symbols
* @param face
* @param suit
* @param rank
* @param width
* @param height
*/
private static void addCornerText( CardFace face, Suit suit, Rank rank, double width, double height) {
Text rankText;
Text suitText;
// center suit text below rank text (6 is just an arbitrary value)
double offsetY = 6;
// top left
// -----------------
rankText = createText( rank.getName(), suit, Settings.CARD_CORNER_SYMBOL_SIZE);
rankText.setRotate(0);
rankText.relocate(4, 0);
suitText = createText( suit.getName(), suit, Settings.CARD_CORNER_SYMBOL_SIZE);
rankText.setRotate(0);
suitText.relocate( rankText.getBoundsInParent().getMinX() + (rankText.getBoundsInParent().getWidth() - suitText.getBoundsInParent().getWidth()) / 2.0, rankText.getBoundsInParent().getMaxY() - offsetY);
face.getChildren().addAll( rankText,suitText);
// bottom right
// -----------------
rankText = createText( rank.getName(), suit, Settings.CARD_CORNER_SYMBOL_SIZE);
rankText.setRotate(180);
rankText.relocate( width - rankText.getBoundsInParent().getWidth() - 4, height - rankText.getBoundsInParent().getHeight());
suitText = createText( suit.getName(), suit, Settings.CARD_CORNER_SYMBOL_SIZE);
suitText.setRotate(180);
suitText.relocate( rankText.getBoundsInParent().getMaxX() - (rankText.getBoundsInParent().getWidth() + suitText.getBoundsInParent().getWidth()) / 2.0, rankText.getBoundsInParent().getMinY() - suitText.getBoundsInParent().getHeight() + offsetY);
face.getChildren().addAll( rankText,suitText);
}
/**
* Combines commonly used methods to create common text / symbols for the card.
* @param name
* @param suit
* @param fontSize
* @return
*/
private static Text createText( String name, Suit suit, double fontSize) {
Text text = new Text( name);
text.setTextOrigin( VPos.TOP);
text.setFill( suit.getColor());
text.setFont( Font.font( null, fontSize));
return text;
}
/**
* Centers the text vertically, visually correct.
* If we wouldn't apply the VISUAL bounds type, then there would be an empty gap which would eg be reserved for letters like À.
* You can verify it by applying grid lines, eg:
*
double y = 0;
while( y < height) {
y +=20;
Line line = new Line( 0,y,width,y);
face.getChildren().addAll( line);
}
* @param name
* @param suit
* @param fontSize
* @return
*/
private static Text createTextCentered( String name, Suit suit, double fontSize) {
Text text = new Text( name);
text.setBoundsType(TextBoundsType.VISUAL);
text.setFill( suit.getColor());
text.setFont( Font.font( null, fontSize));
return text;
}
private static void addCenterItems( CardFace face, Suit suit, Rank rank, double width, double height) {
Text rankTextCenter;
if( rank == Rank.ACE) {
// create text / symbol
rankTextCenter = createTextCentered( suit.getName(), suit, Settings.CARD_CENTER_SYMBOL_SIZE);
// center within card
rankTextCenter.relocate( (width - rankTextCenter.getBoundsInParent().getWidth()) / 2, (height - rankTextCenter.getBoundsInParent().getHeight()) / 2);
// add to card
face.getChildren().addAll( rankTextCenter);
}
else if( rank == Rank.JACK || rank == Rank.QUEEN || rank == Rank.KING) {
// create text / symbol
rankTextCenter = createTextCentered( rank.getName(), suit, Settings.CARD_CENTER_SYMBOL_SIZE);
// center within card
rankTextCenter.relocate( (width - rankTextCenter.getBoundsInParent().getWidth()) / 2, (height - rankTextCenter.getBoundsInLocal().getHeight()) / 2);
// add to card
face.getChildren().addAll( rankTextCenter);
}
else {
GridPane gridPane = new GridPane();
gridPane.setGridLinesVisible(false);
gridPane.setHgap(0);
gridPane.setVgap(0);
gridPane.setPrefSize( width, height);
gridPane.setPadding(new Insets(10));
switch( rank) {
case _2:
gridPane.add( createGridSymbol( suit), 0, 0);
gridPane.add( createGridSymbol( suit, true), 0, 1);
break;
case _3:
gridPane.add( createGridSymbol( suit), 0, 0);
gridPane.add( createGridSymbol( suit), 0, 1);
gridPane.add( createGridSymbol( suit, true), 0, 2);
break;
case _4:
gridPane.add( createGridSymbol( suit), 0, 0);
gridPane.add( createGridSymbol( suit, true), 0, 1);
gridPane.add( createGridSymbol( suit), 1, 0);
gridPane.add( createGridSymbol( suit, true), 1, 1);
break;
case _5:
gridPane.add( createGridSymbol( suit), 0, 0);
gridPane.add( createGridSymbol( suit, true), 0, 2);
gridPane.add( createGridSymbol( suit), 1, 1);
gridPane.add( createGridSymbol( suit), 2, 0);
gridPane.add( createGridSymbol( suit, true), 2, 2);
break;
case _6:
gridPane.add( createGridSymbol( suit), 0, 0);
gridPane.add( createGridSymbol( suit), 0, 1);
gridPane.add( createGridSymbol( suit, true), 0, 2);
gridPane.add( createGridSymbol( suit), 1, 0);
gridPane.add( createGridSymbol( suit), 1, 1);
gridPane.add( createGridSymbol( suit, true), 1, 2);
break;
case _7:
gridPane.add( createGridSymbol( suit), 0, 0);
gridPane.add( createGridSymbol( suit), 0, 1);
gridPane.add( createGridSymbol( suit, true), 0, 2);
gridPane.add( createGridSymbol( suit), 1, 0, 1, 2);
gridPane.add( createGridSymbol( suit), 2, 0);
gridPane.add( createGridSymbol( suit), 2, 1);
gridPane.add( createGridSymbol( suit, true), 2, 2);
break;
case _8:
gridPane.add( createGridSymbol( suit), 0, 0);
gridPane.add( createGridSymbol( suit), 0, 1);
gridPane.add( createGridSymbol( suit, true), 0, 2);
gridPane.add( createGridSymbol( suit), 1, 0, 1, 2);
gridPane.add( createGridSymbol( suit, true), 1, 1, 1, 2);
gridPane.add( createGridSymbol( suit), 2, 0);
gridPane.add( createGridSymbol( suit), 2, 1);
gridPane.add( createGridSymbol( suit, true), 2, 2);
break;
case _9:
gridPane.add( createGridSymbol( suit), 0, 0);
gridPane.add( createGridSymbol( suit), 0, 1);
gridPane.add( createGridSymbol( suit, true), 0, 2);
gridPane.add( createGridSymbol( suit, true), 0, 3);
gridPane.add( createGridSymbol( suit), 1, 1, 1, 2);
gridPane.add( createGridSymbol( suit), 2, 0);
gridPane.add( createGridSymbol( suit), 2, 1);
gridPane.add( createGridSymbol( suit, true), 2, 2);
gridPane.add( createGridSymbol( suit, true), 2, 3);
break;
case _10:
gridPane.add( createGridSymbol( suit), 0, 0);
gridPane.add( createGridSymbol( suit), 0, 1);
gridPane.add( createGridSymbol( suit, true), 0, 2);
gridPane.add( createGridSymbol( suit, true), 0, 3);
gridPane.add( createGridSymbol( suit), 1, 0, 1, 2);
gridPane.add( createGridSymbol( suit), 2, 0);
gridPane.add( createGridSymbol( suit), 2, 1);
gridPane.add( createGridSymbol( suit, true), 2, 2);
gridPane.add( createGridSymbol( suit, true), 2, 3);
gridPane.add( createGridSymbol( suit, true), 1, 2, 1, 2);
break;
default:
break;
}
// add to card
face.getChildren().addAll( gridPane);
}
}
private static Text createGridSymbol(Suit suit) {
return createGridSymbol(suit, false);
}
private static Text createGridSymbol(Suit suit, boolean rotate) {
Text text = new Text(suit.getName());
text.setTextOrigin(VPos.CENTER);
text.setFill(suit.getColor());
text.setFont(Font.font(null, Settings.CARD_GRID_SYMBOL_SIZE));
if( rotate) {
text.setRotate(180);
}
GridPane.setHalignment(text, HPos.CENTER);
GridPane.setHgrow(text, Priority.ALWAYS);
GridPane.setValignment(text, VPos.CENTER);
GridPane.setVgrow(text, Priority.ALWAYS);
return text;
}
public static class CardFace extends Pane {
}
}
package cards;
import java.util.EnumSet;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
/**
* Show all available cards in one screen.
*/
public class CardFaceCreatorDemo extends Application {
private static double width = 120;
private static double height = 180;
@Override
public void start(Stage primaryStage) {
Group root = new Group();
VBox vBox = new VBox();
vBox.setPadding(new Insets(10));
for( Suit suit: EnumSet.allOf(Suit.class)) {
HBox hBox = new HBox();
for( Rank rank: EnumSet.allOf( Rank.class)) {
Node card = CardFaceCreator.createFrontFace( suit, rank, width, height);
hBox.getChildren().add( card);
}
vBox.getChildren().add( hBox);
}
root.getChildren().add( vBox);
Node card = CardFaceCreator.createBackFace( width, height);
vBox.getChildren().add( card);
Scene scene = new Scene( root, 1600, 920, Settings.SCENE_BACKGROUND_COLOR);
primaryStage.setScene( scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
package cards;
import javafx.scene.SnapshotParameters;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.Image;
import javafx.scene.paint.Color;
import javafx.scene.paint.ImagePattern;
public class ImagePatternCreator {
public static ImagePattern createRectanglePattern() {
final double width = 12;
final double height = 12;
final Canvas canvas = new Canvas(width, height);
final GraphicsContext ctx = canvas.getGraphicsContext2D();
ctx.setFill(Color.DODGERBLUE);
ctx.fillRect(0, 0, width/2, height/2); // top left
ctx.fillRect(width/2, height/2, width, height); // bottom right
ctx.setFill(Color.DODGERBLUE.deriveColor(1, 1, 1, 0.85));
ctx.fillRect(width/2, 0, width, height/2); // top right
ctx.fillRect(0, height/2, width/2, height); // bottom left
final Image PATTERN_IMAGE = canvas.snapshot(new SnapshotParameters(), null);
final ImagePattern PATTERN = new ImagePattern(PATTERN_IMAGE, 0, 0, width, height, false);
return PATTERN;
}
public static ImagePattern createDiamondPattern() {
final double width = 12;
final double height = 12;
final Canvas canvas = new Canvas(width, height);
final GraphicsContext ctx = canvas.getGraphicsContext2D();
ctx.setFill(Color.DODGERBLUE);
ctx.fillRect(0, 0, width, height); // full area
ctx.setFill(Color.SKYBLUE);
ctx.fillPolygon( new double[] { width/2, width, width/2, 0}, new double[] { 0, height/2, height, height/2}, 4); // diamond
final Image PATTERN_IMAGE = canvas.snapshot(new SnapshotParameters(), null);
final ImagePattern PATTERN = new ImagePattern(PATTERN_IMAGE, 0, 0, width, height, false);
return PATTERN;
}
}
package cards;
public enum Rank {
ACE( "A"),
_2( "2"),
_3("3"),
_4("4"),
_5("5"),
_6("6"),
_7("7"),
_8("8"),
_9("9"),
_10("10"),
JACK("J"),
QUEEN("Q"),
KING("K")
;
String name;
Rank( String name) {
this.name = name;
}
public String getName() {
return name;
}
}
package cards;
import javafx.scene.paint.Color;
public class Settings {
public static double CARD_WIDTH = 120;
public static double CARD_HEIGHT = 180;
// rounding of cards
public static double CARD_ARC = 12;
public static Color SCENE_BACKGROUND_COLOR = Color.GREEN.deriveColor(1, 1, 1, 0.8);
public static double CARD_GRID_SYMBOL_SIZE = 30;
public static double CARD_CENTER_SYMBOL_SIZE = 46;
public static double CARD_CORNER_SYMBOL_SIZE = 14;
}
package cards;
import javafx.scene.paint.Color;
/**
* http://en.wikipedia.org/wiki/Playing_cards_in_Unicode
*
* black fill:
* spade: \u2660
* heart: \u2665
* diamond: \u2666
* club: \u2663
*
* black stroke, transparent fill:
* spade: \u2664
* heart: \u2661
* diamond: \u2662
* club: \u2667
*
* others:
* joker: \uD83C\uDCCF
* white chess king: \u2654
* black chess king: \u265A
* white chess queen: \u2655
* black chess queen: \u265B
*/
public enum Suit {
SPADES(Color.BLACK, "\u2660"),
HEARTS(Color.RED, "\u2665"),
CLUBS(Color.BLACK, "\u2663"),
DIAMONDS(Color.RED, "\u2666"),
;
Color color;
String name;
Suit( Color color, String name) {
this.color = color;
this.name = name;
}
public Color getColor() {
return color;
}
public String getName() {
return name;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment