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  }