« My New Favorite IDE for Compiled JavaFX Script Development | Main | Video from the JavaPolis "Intro to JavaFX" Presentation »

March 27, 2008

Create a Yahtzee Dice Roller and Scorer in Compiled JavaFX Script

I'm still at The Server Side Java Symposium in Las Vegas, and I think that walking by all these slot machines has inspired this blog post. Today's example builds on the program in the Roll the Dice post in order to create a Yahtzee dice roller and scorer.  Each time you click the Roll button, the five dice assume random values, and the combination of values is scored according to the possible categories in the bottom portion of a Yahtzee scoring sheet.  Here's a screenshot:

Yahtzee_2

In addition to the Dice.fx and PipPlacement.fx files from the Roll the Dice post, this program consists of the following source code files.

YahtzeeMain.fx

/*
*  YahtzeeMain.fx -
*  A compiled JavaFX program that demonstrates creating custom
*  components with CompositeNode and evaluates Yahtzee dice rolls.
*
*
*  Developed 2008 by James L. Weaver (jim.weaver at lat-inc.com)
*  to serve as a JavaFX Script example.
*/

import javafx.ui.*;
import javafx.ui.canvas.*;
import java.lang.System;

Frame {
  var model = YahtzeeModel {}
  width: 510
  height: 400
  title: "Roll Dice and Evaluate Yahtzee Combinations"
  background: Color.WHITE
  content:
    BorderPanel {
      center:
        Box {
          var evalFont =
            Font {
              size: 20
            }
          orientation: Orientation.VERTICAL
          content: [
            Canvas {
              content:
                for (diceNum in [0 .. model.numDice - 1]) {
                  model.newDice =
                  Dice {
                    x: diceNum * 100 + 10
                    y: 10
                    width: 80
                    height: 80
                    faceColor: Color.RED
                    pipColor: Color.WHITE
                  }
                }
            },
            GroupPanel {
              var fiveOfKindRow = Row { alignment: Alignment.BASELINE }
              var largeStraightRow = Row { alignment: Alignment.BASELINE }
              var smallStraightRow = Row { alignment: Alignment.BASELINE }
              var fullHouseRow = Row { alignment: Alignment.BASELINE }
              var fourOfKindRow = Row { alignment: Alignment.BASELINE }
              var threeOfKindRow = Row { alignment: Alignment.BASELINE }
              var chanceRow = Row { alignment: Alignment.BASELINE }

              var labelsColumn = Column {
                alignment: Alignment.TRAILING
              }
              var fieldsColumn = Column {
                alignment: Alignment.LEADING
              }
              rows: [
                fiveOfKindRow,
                largeStraightRow,
                smallStraightRow,
                fullHouseRow,
                fourOfKindRow,
                threeOfKindRow,
                chanceRow
              ]
              columns: [
                labelsColumn,
                fieldsColumn
              ]
              content: [
                SimpleLabel {
                  font: evalFont
                  row: fiveOfKindRow
                  column: labelsColumn
                  text: "Five of a Kind (Yahtzee):"
                },
                SimpleLabel {
                  font: evalFont
                  row: fiveOfKindRow
                  column: fieldsColumn
                  text: bind
                    if (model.fiveOfKind)
                      "{model.fiveOfKindScore}"
                    else "N/A"
                },
               
                SimpleLabel {
                  font: evalFont
                  row: largeStraightRow
                  column: labelsColumn
                  text: "Large Straight:"
                },
                SimpleLabel {
                  font: evalFont
                  row: largeStraightRow
                  column: fieldsColumn
                  text: bind
                    if (model.largeStraight)
                      "{model.largeStraightScore}"
                    else "N/A"
                },
               
                SimpleLabel {
                  font: evalFont
                  row: smallStraightRow
                  column: labelsColumn
                  text: "Small Straight:"
                },
                SimpleLabel {
                  font: evalFont
                  row: smallStraightRow
                  column: fieldsColumn
                  text: bind
                    if (model.smallStraight)
                      "{model.smallStraightScore}"
                    else "N/A"
                },
               
                SimpleLabel {
                  font: evalFont
                  row: fullHouseRow
                  column: labelsColumn
                  text: "Full House:"
                },
                SimpleLabel {
                  font: evalFont
                  row: fullHouseRow
                  column: fieldsColumn
                  text: bind
                    if (model.fullHouse)
                      "{model.fullHouseScore}"
                    else "N/A"
                },
               
                SimpleLabel {
                  font: evalFont
                  row: fourOfKindRow
                  column: labelsColumn
                  text: "Four of a Kind:"
                },
                SimpleLabel {
                  font: evalFont
                  row: fourOfKindRow
                  column: fieldsColumn
                  text: bind
                    if (model.fourOfKind)
                      "{model.sumOfDiceValues}"
                    else "N/A"
                },
               
                SimpleLabel {
                  font: evalFont
                  row: threeOfKindRow
                  column: labelsColumn
                  text: "Three of a Kind:"
                },
                SimpleLabel {
                  font: evalFont
                  row: threeOfKindRow
                  column: fieldsColumn
                  text: bind
                    if (model.threeOfKind)
                      "{model.sumOfDiceValues}"
                    else "N/A"
                },
               
                SimpleLabel {
                  font: evalFont
                  row: chanceRow
                  column: labelsColumn
                  text: "Chance:"
                },
                SimpleLabel {
                  font: evalFont
                  row: chanceRow
                  column: fieldsColumn
                  text: bind "{model.sumOfDiceValues}"
                },
              ]
            },
          ]
        } 
      bottom:
        FlowPanel {
          content:
            Button {
              text: "Roll"
              defaultButton: true
              action:
                function():Void {
                  model.roll();
                }
            }
        }
    }
  visible: true
  onClose:
    function():Void {
      System.exit(0);
    }
}

YahtzeeModel.fx

/*
*  YahtzeeModel.fx -
*  The model behind the Yahtzee dice roll and combination evaluation
*
*  Developed 2008 by James L. Weaver (jim.weaver at lat-inc.com)
*  to serve as a JavaFX Script example.
*/
import javafx.lang.Sequences;
import java.lang.System;

class YahtzeeModel {
  attribute numDice:Integer = 5;
  attribute diceDistribution:Integer[];
  attribute newDice:Dice on replace {
    insert newDice into dice;
  }
  attribute dice:Dice[];
 
  attribute fiveOfKind:Boolean;
  attribute largeStraight:Boolean;
  attribute smallStraight:Boolean;
  attribute fullHouse:Boolean;
  attribute fourOfKind:Boolean;
  attribute threeOfKind:Boolean;

  attribute fiveOfKindScore:Integer = 50;
  attribute largeStraightScore:Integer = 40;
  attribute smallStraightScore:Integer = 30;
  attribute fullHouseScore:Integer = 25;

  attribute sumOfDiceValues:Integer = 0;
 
  function roll():Void {
    for (die in dice) {
      die.roll();
    }
    evalYahtzeeCombos();
  }
 
  function evalYahtzeeCombos() {
    var values =
    for (val in dice) {
      val.value;
    }

    var maxVal:Integer = Sequences.max(values, null) as Integer;
    var minVal:Integer = Sequences.min(values, null) as Integer;

    // Create a sequence that contains the distribution of values
    // and Calclulate the sum of the dice values
    diceDistribution =
      for (i in [1 .. 6]) 0;

    sumOfDiceValues = 0;
   
    for (val in values) {
      diceDistribution[val - 1]++;
      sumOfDiceValues += val;
    }
   
    // Determine if five-of-a-kind
    fiveOfKind =
      ((for (occurance in diceDistribution
           where occurance >= 5) occurance) <> []);       
   
    // Determine if four-of-a-kind
    fourOfKind =
      ((for (occurance in diceDistribution
           where occurance >= 4) occurance) <> []);
   
    // Determine if three-of-a-kind
    threeOfKind =
      sizeof (for (occurance in diceDistribution
              where occurance >= 3) occurance) > 0;      

    // Determine if full house
    fullHouse =
      sizeof (for (occurance in diceDistribution
              where occurance == 3) occurance) > 0 and      
      sizeof (for (occurance in diceDistribution
              where occurance == 2) occurance) > 0;   

    // Determine if large straight
    largeStraight =
      sizeof (for (occurance in diceDistribution
              where occurance > 1) occurance) == 0 and
      (maxVal - minVal == 4);
             
    // Determine if small straight
    smallStraight =
      sizeof (for (occurance in diceDistribution
              where occurance == 2) occurance) == 1 and
      (maxVal - minVal == 3);
  }
}

A JavaFX Script concept that we haven't covered yet is the set of functions available in the new javafx.lang.Sequences package.  These functions enable you to perform operations (such as sorting) on a sequence. In this program I'm using the max and min functions of that class to find the largest and smallest value in a dice roll.

Enjoy!
Jim Weaver
JavaFX Script: Dynamic Java Scripting for Rich Internet/Client-side Applications

Immediate eBook (PDF) download available at the book's Apress site

TrackBack

TrackBack URL for this entry:
http://www.typepad.com/t/trackback/2709996/27500254

Listed below are links to weblogs that reference Create a Yahtzee Dice Roller and Scorer in Compiled JavaFX Script:

Comments

Great Example, Jim. I just used it to test in the NetBeans IDE 6.1. I noticed that the Preview / FXPad mode is unavailable from the IDE. Is this mode no longer available?


I agree that this is a trivial example. The purpose of many of the examples in this weblog are meant to teach concepts. Please see the Freebase Browser post for an example (albeit in interpreted JavaFX Script) of a more fully featured application, that communicates using JSON protocol with a server:
http://learnjavafx.typepad.com/weblog/2007/10/spotting-javafx.html

Also, here is a simpler example of talking to the same server (freebase.com) that shows the compiled JavaFX Script code:
http://learnjavafx.typepad.com/weblog/2008/02/compiled-javafx.html

As a developer, if I'm going to use JavaFX and recommend to my clients, it better do something substantially better than what people normally do with plain old HTML + JavaScript.

This kind of thing is pretty trivial do do with JavaScript or alm ost any GUI toolset.

Post a comment

If you have a TypeKey or TypePad account, please Sign In