import javafx.animation.*; import javafx.scene.*; import javafx.scene.control.*; import javafx.scene.effect.*; import javafx.scene.layout.*; import javafx.scene.paint.*; import javafx.scene.shape.Rectangle; import javafx.stage.Stage; import java.util.*; def CELLSIZE = 30; def NUMROWS = 6; def NUMCOLS = 8; def SPACING = 8; def ROUNDNESS = 8; def ANIMTIME = 1s; def colors = [ Color.web("#d02020"), Color.web("#ff8010"), Color.web("#d0e000"), Color.web("#10c010"), Color.web("#3030f0"), Color.web("#d050ff"), ]; def rand = new Random(); function randColor():Color { colors[rand.nextInt(sizeof colors)] } class Cell extends Rectangle { public-init var row:Integer; public-init var col:Integer; } var cells = [ for (r in [0..= NUMROWS or c >= NUMCOLS) { null } else { cells[r * NUMCOLS + c] } } function findMatchingCells():Set { def set = new HashSet(); // HashSet flood(0, 0, cellAt(0, 0).fill as Color, set); set } function flood(r:Integer, c:Integer, color:Color, set:Set):Void { var cell = cellAt(r, c); if (cell == null) return; if (set.contains(cell)) return; if (cell.fill == color) { set.add(cell); flood(r+1, c, color, set); flood(r, c+1, color, set); flood(r-1, c, color, set); flood(r, c-1, color, set); } } var numMoves = 0; bound function gameWon() { def iColor = cells[0].fill; def matches = for (c in cells) { if (c.fill == iColor) c else null } sizeof matches == NUMROWS * NUMCOLS }; var recolor:function(Set, Color):Void = recolorFlipDelay; function recolorFlipDelay(set:Set, color:Color):Void { Timeline { keyFrames: [ for (elt in set) { def cell = elt as Cell; def delay = (cell.row + cell.col) * 60ms; [ KeyFrame { time: delay values: cell.scaleX => 1.0 } KeyFrame { time: delay + ANIMTIME / 4 values: cell.scaleX => 0.0 action: function() { cell.fill = color } }, KeyFrame { time: delay + ANIMTIME / 2 values: cell.scaleX => 1.0 } ] } ] }.playFromStart(); } function recolorFlip(set:Set, color:Color):Void { Timeline { keyFrames: [ for (elt in set) { def cell = elt as Cell; [ KeyFrame { time: ANIMTIME / 2 values: cell.scaleX => 0.0 action: function() { cell.fill = color } }, KeyFrame { time: ANIMTIME values: cell.scaleX => 1.0 } ] } ] }.playFromStart(); } function recolorSpin(set:Set, color:Color):Void { Timeline { keyFrames: for (elt in set) { def cell = elt as Cell; def ix = cell.x; def iy = cell.y; [ KeyFrame { time: ANIMTIME / 2 values: [ cell.x => ix + CELLSIZE / 2, cell.y => iy + CELLSIZE / 2, cell.width => 0.0, cell.height => 0.0, cell.rotate => 360 ] action: function() { cell.fill = color } }, KeyFrame { time: ANIMTIME values: [ cell.x => ix, cell.y => iy, cell.width => CELLSIZE, cell.height => CELLSIZE, cell.rotate => 0 ] } ] } }.playFromStart(); } function recolorFadeThruTransparent(set:Set, color:Color):Void { Timeline { keyFrames: for (elt in set) { def cell = elt as Cell; [ KeyFrame { time: ANIMTIME / 2 values: cell.opacity => 0.0 action: function() { cell.fill = color } }, KeyFrame { time: ANIMTIME values: cell.opacity => 1.0 } ] } }.playFromStart(); } function recolorDirectFade(set:Set, color:Color):Void { Timeline { keyFrames: KeyFrame { time: ANIMTIME values: [ for (elt in set) { def cell = elt as Cell; cell.fill => color } ] } }.playFromStart(); } function recolorImmediate(set:Set, color:Color):Void { for (elt in set) { def cell = elt as Cell; cell.fill = color; } } Stage { title: "Flood Game" scene: Scene { width: 500 height: 450 fill: LinearGradient { startX: 0.0 endX: 0.0 startY: 0.0 endY: 1.0 stops: [ Stop { offset: 0.0 color: Color.web("#e0e0e0") } Stop { offset: 1.0 color: Color.web("#a0a0a0") } ] } content: [ HBox { spacing: 30 layoutX: 30 layoutY: 30 content: [ Group { content: [ Rectangle { width: (NUMCOLS+1) * CELLSIZE + (NUMCOLS-1) * SPACING height: (NUMROWS+1) * CELLSIZE + (NUMROWS-1) * SPACING fill: Color.web("#606060") arcWidth: ROUNDNESS * 2 arcHeight: ROUNDNESS * 2 } Group { layoutX: CELLSIZE / 2 layoutY: CELLSIZE / 2 content: cells } ] effect: Reflection { fraction: 0.5 topOffset: 30 topOpacity: 0.7 } } VBox { spacing: 10 content: [ for (c in colors) { Button { graphic: Rectangle { width: 40 height: 10 fill: c } action: function() { recolor(findMatchingCells(), c); numMoves++; } } } Label { text: bind "Moves: {numMoves}" } Label { text: "YOU WON!!" visible: bind gameWon() } ] } ] } ] } }