Jim has posted 66 posts at DZone. You can read more from them at their website. View Full User Profile

Create a Yahtzee Dice Roller and Scorer in Compiled JavaFX Script

03.27.2008
| 6462 views |
  • submit to reddit

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 static functions available in the new javafx.lang.Sequences class. 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.

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

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

Published at DZone with permission of its author, Jim Weaver.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Tags:

Comments

Andres Almiray replied on Thu, 2008/03/27 - 11:41am

It looks good Jim, but I'm not quite convinced, it took 443 lines of code (sans comments) to get this example working, surely surrendering some formatting will shrink that number, but that is not exactly the point. I mean there is repetition while laying out the components of the GroupPanel: first rows are defined as vars, then they are assigned to the rows property of the panel, lastly they are used as constraints for each label. the jfx looks like its doing something like the following under the covers

GroupPanel gp = new GroupPanel();
// if Row constructor accepts an alignment as parameter
Row fiveOfKindRow = new Row( Alignment.BASELINE );
// if Row constructor does not accept an alignment parameter
Row largeStraightRow = new Row();
largeStraightRow.setAlignment( Alignment.BASELINE );
...
gp.addRow( fiveOfKindRow );
gp.addRow( largeStraightRow );
/* or it may be like
gp.setRows( new Row[]{ fiveOfKindRow, largeStraightRow, ...} )
*/
gp.getContentPane().add( new SimpleLabel(...),
new RowColumnConstraint(fiveOfKindRow,labelsColumn) );

I'm surely biased when I say that a builder (not necessarily a Groovy one, thought it doesn't hurt) is best suited for this task, as it will keep the DRY-ness in check, as with this approach the code may look like the following one

GroupPanel { 
// we fix the # of columns first
columns {
Column( alignment: Alignment.TRAILING )
Column( alignment: Alignment.LEADING )
}
// add the first row
Row( alignment: alignment.BASELINE ) {
SimpleLabel( text: "Five of a Kind (Yathzee):", font: evalFont )
SimpleLabel( font: evalFont, text: bind if (model.fiveOfKind)
"{model.fineOfKindScore}" else 'N/A" )
}
// add more rows
}

My $0.02

Fran Aviles replied on Thu, 2008/03/27 - 1:25pm

Wow!

 

That code looks almost like a dfm file (Delphi)

 

object FSeg_Soc: TFSeg_Soc
Left = 217
Top = 115
HelpContext = 36
BorderIcons = [biSystemMenu, biMaximize]
BorderStyle = bsSingle
ClientHeight = 352
ClientWidth = 544
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'MS Sans Serif'
Font.Style = []
HelpFile = 'Ayudas\SegSoc.chm'
KeyPreview = True
OldCreateOrder = True
Position = poScreenCenter
OnActivate = FormActivate
OnClose = FormClose
OnCreate = FormCreate
OnDestroy = FormDestroy
OnKeyDown = FormKeyDown
PixelsPerInch = 96
TextHeight = 13
object DBGrid1: TDBGrid
Left = 0
Top = 210
Width = 544
Height = 142
Align = alClient
DataSource = DataBaseAux
Options = [dgEditing, dgTitles, dgIndicator, dgColLines, dgRowLines, dgConfirmDelete, dgCancelOnExit]
PopupMenu = PopupMenu1
TabOrder = 0
TitleFont.Charset = DEFAULT_CHARSET
TitleFont.Color = clWindowText
TitleFont.Height = -11
TitleFont.Name = 'MS Sans Serif'
TitleFont.Style = []
OnColExit = DBGrid1ColExit
OnEditButtonClick = DBGrid1EditButtonClick
OnKeyDown = DBGrid1KeyDown
OnKeyPress = DBGrid1KeyPress
Columns = <
item
ButtonStyle = cbsEllipsis
Expanded = False
FieldName = 'EMPDSDE'
Title.Alignment = taCenter
Title.Caption = 'Enterprise(D)'
Visible = True
end
item
ButtonStyle = cbsEllipsis
Expanded = False
FieldName = 'EMPAST'
Title.Alignment = taCenter
Title.Caption = 'Enterprise(H)'
Visible = True
end
item
Color = 8454143
Expanded = False
FieldName = 'TYPE'
ReadOnly = True
Title.Alignment = taCenter
Width = 67
Visible = True
end
item
Color = 8454143
Expanded = False
FieldName = 'WHO'
ReadOnly = True
Width = 312
Visible = True
end>
end
object CoolBar1: TCoolBar
Left = 0
Top = 0

 

 

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.