github.com/apache/beam/sdks/v2@v2.48.2/go/examples/yatzy/yatzy.go (about) 1 // Licensed to the Apache Software Foundation (ASF) under one or more 2 // contributor license agreements. See the NOTICE file distributed with 3 // this work for additional information regarding copyright ownership. 4 // The ASF licenses this file to You under the Apache License, Version 2.0 5 // (the "License"); you may not use this file except in compliance with 6 // the License. You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 16 // yatzy is an implementation of https://en.wikipedia.org/wiki/Yatzy that shows 17 // that pipeline construction is normal Go code. It can even be 18 // non-deterministic and produce different pipelines on each invocation. 19 package main 20 21 // beam-playground: 22 // name: Yatzy 23 // description: An examples shows that pipeline construction is normal Go code. 24 // It can even be non-deterministic and produce different pipelines on each invocation. 25 // multifile: false 26 // context_line: 50 27 // categories: 28 // - IO 29 // - Side Input 30 // complexity: ADVANCED 31 // tags: 32 // - pipeline 33 // - random 34 // - numbers 35 36 import ( 37 "context" 38 "flag" 39 "fmt" 40 "math/rand" 41 "sort" 42 "time" 43 44 "github.com/apache/beam/sdks/v2/go/pkg/beam" 45 "github.com/apache/beam/sdks/v2/go/pkg/beam/log" 46 "github.com/apache/beam/sdks/v2/go/pkg/beam/register" 47 "github.com/apache/beam/sdks/v2/go/pkg/beam/x/beamx" 48 ) 49 50 var ( 51 real = flag.Int("real_dice", 20, "Actual dice to use (cropped to formal).") 52 dice = flag.Int("dice", 6, "Formal dice to use.") 53 ) 54 55 func init() { 56 register.Function1x1(incFn) 57 register.Function7x0(evalFn) 58 register.DoFn1x1[int, int](&minFn{}) 59 } 60 61 // roll is a composite PTransform for a construction-time dice roll. The value 62 // is encoded in the shape of the pipeline, which will produce a single 63 // element of that value. The shape is as follows: 64 // 65 // 0 -> \x.x+1 -> \x.x+1 -> (N times) -> \x.min(x, 6) 66 // 67 // The single output will be a number between 1 and 6. 68 func roll(ctx context.Context, s beam.Scope) beam.PCollection { 69 num := rand.Intn(*real) + 1 70 log.Debugf(ctx, "Lucky number %v!", num) 71 72 s = s.Scope(fmt.Sprintf("roll[%v]", num)) 73 74 col := beam.Create(s, 0) 75 for i := 0; i < num; i++ { 76 col = beam.ParDo(s, incFn, col) 77 } 78 return beam.ParDo(s, minFn{Num: *dice}, col) 79 } 80 81 // minFn is a DoFn that computes outputs the minimum of a fixed value 82 // and each incoming value. 83 type minFn struct { 84 Num int `json:"num"` 85 } 86 87 func (m minFn) ProcessElement(num int) int { 88 if m.Num < num { 89 return m.Num 90 } 91 return num 92 } 93 94 // incFn is a DoFn that increments each value by 1. 95 func incFn(num int) int { 96 return num + 1 97 } 98 99 func eq(n int, other ...int) bool { 100 for _, num := range other { 101 if num != n { 102 return false 103 } 104 } 105 return true 106 } 107 108 // evalFn is a DoFn that takes 5 dice rolls as singleton side inputs and 109 // evaluates them. It is triggered by an impulse, whose value is ignored. 110 // It does not output any value, but simply logs the result. 111 func evalFn(ctx context.Context, _ []byte, a, b, c, d, e int) { 112 r := []int{a, b, c, d, e} 113 sort.Ints(r) 114 115 log.Infof(ctx, "Roll: %v", r) 116 117 switch { 118 case eq(r[0], r[1], r[2], r[3], r[4]): 119 log.Info(ctx, "Yatzy!") 120 case eq(r[0], r[1], r[2], r[3]) || eq(r[1], r[2], r[3], r[4]): 121 log.Info(ctx, "Four of a kind!") 122 case eq(r[0], r[1], r[2]) && r[3] == r[4], r[0] == r[1] && eq(r[2], r[3], r[4]): 123 log.Info(ctx, "Full house!") 124 case r[0] == 1 && r[1] == 2 && r[2] == 3 && r[3] == 4 && r[4] == 5: 125 log.Info(ctx, "Small straight!") 126 case r[0] == 2 && r[1] == 3 && r[2] == 4 && r[3] == 5 && r[4] == 6: 127 log.Info(ctx, "Big straight!") 128 default: 129 log.Info(ctx, "Sorry, try again.") 130 } 131 } 132 133 func main() { 134 flag.Parse() 135 beam.Init() 136 137 rand.Seed(time.Now().UnixNano()) 138 ctx := context.Background() 139 140 log.Info(ctx, "Running yatzy") 141 142 // Construct a construction-time-randomized pipeline. 143 p := beam.NewPipeline() 144 s := p.Root() 145 beam.ParDo0(s, evalFn, beam.Impulse(s), 146 beam.SideInput{Input: roll(ctx, s)}, 147 beam.SideInput{Input: roll(ctx, s)}, 148 beam.SideInput{Input: roll(ctx, s)}, 149 beam.SideInput{Input: roll(ctx, s)}, 150 beam.SideInput{Input: roll(ctx, s)}, 151 ) 152 153 if err := beamx.Run(context.Background(), p); err != nil { 154 log.Exitf(ctx, "Failed to execute job: %v", err) 155 } 156 }