// // 8""""8 8"""88 8 8"""" // 8 " 8 8 8 8 // 8e 8 8 8e 8eeee // 88 ee 8 8 88 88 // 88 8 8 8 88 88 // 88eee8 8eeee8 88eee 88 // // GOLF // // C# // .NET Core // TargetFramework: netcoreapp 3.1 // // Run source: // dotnet run // // Linux compile: // dotnet publish --self-contained -c Release -r linux-x64 /p:PublishSingleFile=true /p:PublishTrimmed=true // // Windows compile: // dotnet publish -r win-x64 -c Release /p:PublishSingleFile=true // // // INDEX // ----------------- methods // constructor // NewHole // TeeUp // Stroke // PlotBall // InterpretResults // ReportCurrentScore // FindBall // IsOnFairway // IsOnGreen // IsInHazard // IsInRough // IsOutOfBounds // ScoreCardNewHole // ScoreCardRecordStroke // ScoreCardGetPreviousStroke // ScoreCardGetTotal // Ask // Wait // ReviewBag // Quit // GameOver // ----------------- DATA // Clubs // CourseInfo // ----------------- classes // HoleInfo // CircleGameObj // RectGameObj // HoleGeometry // Plot // ----------------- helper methods // GetDistance // IsInRectangle // ToRadians // ToDegrees360 // Odds // // Despite being a text based game, the code uses simple geometry to simulate a course. // Fairways are 40 yard wide rectangles, surrounded by 5 yards of rough around the perimeter. // The green is a circle of 10 yards radius around the cup. // The cup is always at point (0,0). // // Using basic trigonometry we can plot the ball's location using the distance of the stroke and // and the angle of deviation (hook/slice). // // The stroke distances are based on real world averages of different club types. // Lots of randomization, "business rules", and luck influence the game play. // Probabilities are commented in the code. // // note: 'courseInfo', 'clubs', & 'scoreCard' arrays each include an empty object so indexing // can begin at 1. Like all good programmers we count from zero, but in this context, // it's more natural when hole number one is at index one // // // |-----------------------------| // | rough | // | ---------------------- | // | | | | // | r | = = | r | // | o | = = | o | // | u | = . = | u | // | g | = green = | g | // | h | = = | h | // | | = = | | // | | | | // | | | | // | | Fairway | | // | | | | // | | ------ | // | | -- -- | // | | -- hazard --| // | | -- -- | // | | ------ | // | | | | // | | | | out // | | | | of // | | | | bounds // | | | | // | | | | // | tee | // // // Typical green size: 20-30 yards // Typical golf course fairways are 35 to 45 yards wide // Our fairway extends 5 yards past green // Our rough is a 5 yard perimeter around fairway // // We calculate the new position of the ball given the ball's point, the distance // of the stroke, and degrees off line (hook or slice). // // Degrees off (for a right handed golfer): // Slice: positive degrees = ball goes right // Hook: negative degrees = left goes left // // The cup is always at point: 0,0. // We use atan2 to compute the angle between the cup and the ball. // Setting the cup's vector to 0,-1 on a 360 circle is equivalent to: // 0 deg = 12 o'clock; 90 deg = 3 o'clock; 180 deg = 6 o'clock; 270 = 9 o'clock // The reverse angle between the cup and the ball is a difference of PI (using radians). // // Given the angle and stroke distance (hypotenuse), we use cosine to compute // the opposite and adjacent sides of the triangle, which, is the ball's new position. // // 0 // | // 270 - cup - 90 // | // 180 // // // cup // | // | // | opp // |-----* new position // | / // | / // adj | / // | / hyp // |/ // tee // // <- hook slice -> // // // Given the large number of combinations needed to describe a particular stroke / ball location, // we use the technique of "bitwise masking" to describe stroke results. // With bit masking, multiple flags (bits) are combined into a single binary number that can be // tested by applying a mask. A mask is another binary number that isolates a particular bit that // you are interested in. You can then apply your language's bitwise opeartors to test or // set a flag. // // Game design by Jason Bonthron, 2021 // www.bonthron.com // for my father, Raymond Bonthron, an avid golfer // // Inspired by the 1978 "Golf" from "Basic Computer Games" // by Steve North, who modified an existing golf game by an unknown author // // using System; using System.Collections.Generic; using System.Drawing; using System.Threading; namespace Golf { using Ball = Golf.CircleGameObj; using Hazard = Golf.CircleGameObj; // --------------------------------------------------------------------------- Program class Program { static void Main(string[] args) { Golf g = new Golf(); } } // --------------------------------------------------------------------------- Golf public class Golf { Ball BALL; int HOLE_NUM = 0; int STROKE_NUM = 0; int Handicap = 0; int PlayerDifficulty = 0; HoleGeometry holeGeometry; // all fairways are 40 yards wide, extend 5 yards beyond the cup, and // have 5 yards of rough around the perimeter const int FairwayWidth = 40; const int FairwayExtension = 5; const int RoughAmt = 5; // ScoreCard records the ball position after each stroke // a new list for each hole // include a blank list so index 1 == hole 1 List> ScoreCard = new List> { new List() }; static void w(string s) { Console.WriteLine(s); } // WRITE Random RANDOM = new Random(); // --------------------------------------------------------------- constructor public Golf() { Console.Clear(); w(" "); w(" 8\"\"\"\"8 8\"\"\"88 8 8\"\"\"\" "); w(" 8 \" 8 8 8 8 "); w(" 8e 8 8 8e 8eeee "); w(" 88 ee 8 8 88 88 "); w(" 88 8 8 8 88 88 "); w(" 88eee8 8eeee8 88eee 88 "); w(" "); w("Welcome to the Creative Computing Country Club,"); w("an eighteen hole championship layout located a short"); w("distance from scenic downtown Lambertville, New Jersey."); w("The game will be explained as you play."); w("Enjoy your game! See you at the 19th hole..."); w(" "); w("Type QUIT at any time to leave the game."); w("Type BAG at any time to review the clubs in your bag."); w(" "); Wait((z) => { w(" "); w(" YOUR BAG"); ReviewBag(); w("Type BAG at any time to review the clubs in your bag."); w(" "); Wait((zz) => { w(" "); Ask("PGA handicaps range from 0 to 30.\nWhat is your handicap?", 0, 30, (i) => { Handicap = i; w(" "); Ask("Common difficulties at golf include:\n1=Hook, 2=Slice, 3=Poor Distance, 4=Trap Shots, 5=Putting\nWhich one is your worst?", 1, 5, (j) => { PlayerDifficulty = j; Console.Clear(); NewHole(); }); }); }); }); } // --------------------------------------------------------------- NewHole void NewHole() { HOLE_NUM++; STROKE_NUM = 0; HoleInfo info = CourseInfo[HOLE_NUM]; int yards = info.Yards; // from tee to cup int par = info.Par; var cup = new CircleGameObj(0, 0, 0, GameObjType.CUP); var green = new CircleGameObj(0, 0, 10, GameObjType.GREEN); var fairway = new RectGameObj(0 - (FairwayWidth / 2), 0 - (green.Radius + FairwayExtension), FairwayWidth, yards + (green.Radius + FairwayExtension) + 1, GameObjType.FAIRWAY); var rough = new RectGameObj(fairway.X - RoughAmt, fairway.Y - RoughAmt, fairway.Width + (2 * RoughAmt), fairway.Length + (2 * RoughAmt), GameObjType.ROUGH); BALL = new Ball(0, yards, 0, GameObjType.BALL); ScoreCardStartNewHole(); holeGeometry = new HoleGeometry(cup, green, fairway, rough, info.Hazard); w(" |> " + HOLE_NUM); w(" | "); w(" | "); w(" ^^^^^^^^^^^^^^^"); Console.WriteLine("Hole #{0}. You are at the tee. Distance {1} yards, par {2}.", HOLE_NUM, info.Yards, info.Par); w(info.Description); TeeUp(); } // --------------------------------------------------------------- TeeUp // on the green? automatically select putter // otherwise Ask club and swing strength void TeeUp() { if (IsOnGreen(BALL) && !IsInHazard(BALL, GameObjType.SAND)) { var putt = 10; w("[PUTTER: average 10 yards]"); var msg = Odds(20) ? "Keep your head down.\n" : ""; Ask(msg + "Choose your putt potency. (1-10)", 1, 10, (strength) => { var putter = Clubs[putt]; Stroke(Convert.ToDouble((double)putter.Item2 * ((double)strength / 10.0)), putt); }); } else { Ask("What club do you choose? (1-10)", 1, 10, (c) => { var club = Clubs[c]; w(" "); Console.WriteLine("[{0}: average {1} yards]", club.Item1.ToUpper(), club.Item2); Ask("Now gauge your distance by a percentage of a full swing. (1-10)", 1, 10, (strength) => { Stroke(Convert.ToDouble((double)club.Item2 * ((double)strength / 10.0)), c); }); }); }; } // -------------------------------------------------------- bitwise Flags int dub = 0b00000000000001; int hook = 0b00000000000010; int slice = 0b00000000000100; int passedCup = 0b00000000001000; int inCup = 0b00000000010000; int onFairway = 0b00000000100000; int onGreen = 0b00000001000000; int inRough = 0b00000010000000; int inSand = 0b00000100000000; int inTrees = 0b00001000000000; int inWater = 0b00010000000000; int outOfBounds = 0b00100000000000; int luck = 0b01000000000000; int ace = 0b10000000000000; // --------------------------------------------------------------- Stroke void Stroke(double clubAmt, int clubIndex) { STROKE_NUM++; var flags = 0b000000000000; // fore! only when driving if ((STROKE_NUM == 1) && (clubAmt > 210) && Odds(30)) { w("\"...Fore !\""); }; // dub if (Odds(5)) { flags |= dub; }; // there's always a 5% chance of dubbing it // if you're in the rough, or sand, you really should be using a wedge if ((IsInRough(BALL) || IsInHazard(BALL, GameObjType.SAND)) && !(clubIndex == 8 || clubIndex == 9)) { if (Odds(40)) { flags |= dub; }; }; // trap difficulty if (IsInHazard(BALL, GameObjType.SAND) && PlayerDifficulty == 4) { if (Odds(20)) { flags |= dub; }; } // hook/slice // There's 10% chance of a hook or slice // if it's a known playerDifficulty then increase chance to 30% // if it's a putt & putting is a playerDifficulty increase to 30% bool randHookSlice = (PlayerDifficulty == 1 || PlayerDifficulty == 2 || (PlayerDifficulty == 5 && IsOnGreen(BALL))) ? Odds(30) : Odds(10); if (randHookSlice) { if (PlayerDifficulty == 1) { if (Odds(80)) { flags |= hook; } else { flags |= slice; }; } else if (PlayerDifficulty == 2) { if (Odds(80)) { flags |= slice; } else { flags |= hook; }; } else { if (Odds(50)) { flags |= hook; } else { flags |= slice; }; }; }; // beginner's luck ! // If handicap is greater than 15, there's a 10% chance of avoiding all errors if ((Handicap > 15) && (Odds(10))) { flags |= luck; }; // ace // there's a 10% chance of an Ace on a par 3 if (CourseInfo[HOLE_NUM].Par == 3 && Odds(10) && STROKE_NUM == 1) { flags |= ace; }; // distance: // If handicap is < 15, there a 50% chance of reaching club average, // a 25% of exceeding it, and a 25% of falling short // If handicap is > 15, there's a 25% chance of reaching club average, // and 75% chance of falling short // The greater the handicap, the more the ball falls short // If poor distance is a known playerDifficulty, then reduce distance by 10% double distance; int rnd = RANDOM.Next(1, 101); if (Handicap < 15) { if (rnd <= 25) { distance = clubAmt - (clubAmt * ((double)Handicap / 100.0)); } else if (rnd > 25 && rnd <= 75) { distance = clubAmt; } else { distance = clubAmt + (clubAmt * 0.10); }; } else { if (rnd <= 75) { distance = clubAmt - (clubAmt * ((double)Handicap / 100.0)); } else { distance = clubAmt; }; }; if (PlayerDifficulty == 3) // poor distance { if (Odds(80)) { distance = (distance * 0.80); }; }; if ((flags & luck) == luck) { distance = clubAmt; } // angle // For all strokes, there's a possible "drift" of 4 degrees // a hooks or slice increases the angle between 5-10 degrees, hook uses negative degrees int angle = RANDOM.Next(0, 5); if ((flags & slice) == slice) { angle = RANDOM.Next(5, 11); }; if ((flags & hook) == hook) { angle = 0 - RANDOM.Next(5, 11); }; if ((flags & luck) == luck) { angle = 0; }; var plot = PlotBall(BALL, distance, Convert.ToDouble(angle)); // calculate a new location if ((flags & luck) == luck) { if(plot.Y > 0){ plot.Y = 2; }; }; flags = FindBall(new Ball(plot.X, plot.Y, plot.Offline, GameObjType.BALL), flags); InterpretResults(plot, flags); } // --------------------------------------------------------------- plotBall Plot PlotBall(Ball ball, double strokeDistance, double degreesOff) { var cupVector = new Point(0, -1); double radFromCup = Math.Atan2((double)ball.Y, (double)ball.X) - Math.Atan2((double)cupVector.Y, (double)cupVector.X); double radFromBall = radFromCup - Math.PI; var hypotenuse = strokeDistance; var adjacent = Math.Cos(radFromBall + ToRadians(degreesOff)) * hypotenuse; var opposite = Math.Sqrt(Math.Pow(hypotenuse, 2) - Math.Pow(adjacent, 2)); Point newPos; if (ToDegrees360(radFromBall + ToRadians(degreesOff)) > 180) { newPos = new Point(Convert.ToInt32(ball.X - opposite), Convert.ToInt32(ball.Y - adjacent)); } else { newPos = new Point(Convert.ToInt32(ball.X + opposite), Convert.ToInt32(ball.Y - adjacent)); } return new Plot(newPos.X, newPos.Y, Convert.ToInt32(opposite)); } // --------------------------------------------------------------- InterpretResults void InterpretResults(Plot plot, int flags) { int cupDistance = Convert.ToInt32(GetDistance(new Point(plot.X, plot.Y), new Point(holeGeometry.Cup.X, holeGeometry.Cup.Y))); int travelDistance = Convert.ToInt32(GetDistance(new Point(plot.X, plot.Y), new Point(BALL.X, BALL.Y))); w(" "); if ((flags & ace) == ace) { w("Hole in One! You aced it."); ScoreCardRecordStroke(new Ball(0, 0, 0, GameObjType.BALL)); ReportCurrentScore(); return; }; if ((flags & inTrees) == inTrees) { w("Your ball is lost in the trees. Take a penalty stroke."); ScoreCardRecordStroke(BALL); TeeUp(); return; }; if ((flags & inWater) == inWater) { var msg = Odds(50) ? "Your ball has gone to a watery grave." : "Your ball is lost in the water."; w(msg + " Take a penalty stroke."); ScoreCardRecordStroke(BALL); TeeUp(); return; }; if ((flags & outOfBounds) == outOfBounds) { w("Out of bounds. Take a penalty stroke."); ScoreCardRecordStroke(BALL); TeeUp(); return; }; if ((flags & dub) == dub) { w("You dubbed it."); ScoreCardRecordStroke(BALL); TeeUp(); return; }; if ((flags & inCup) == inCup) { var msg = Odds(50) ? "You holed it." : "It's in!"; w(msg); ScoreCardRecordStroke(new Ball(plot.X, plot.Y, 0, GameObjType.BALL)); ReportCurrentScore(); return; }; if (((flags & slice) == slice) && !((flags & onGreen) == onGreen)) { var bad = ((flags & outOfBounds) == outOfBounds) ? " badly" : ""; Console.WriteLine("You sliced{0}: {1} yards offline.", bad, plot.Offline); }; if (((flags & hook) == hook) && !((flags & onGreen) == onGreen)) { var bad = ((flags & outOfBounds) == outOfBounds) ? " badly" : ""; Console.WriteLine("You hooked{0}: {1} yards offline.", bad, plot.Offline); }; if (STROKE_NUM > 1) { var prevBall = ScoreCardGetPreviousStroke(); var d1 = GetDistance(new Point(prevBall.X, prevBall.Y), new Point(holeGeometry.Cup.X, holeGeometry.Cup.Y)); var d2 = cupDistance; if (d2 > d1) { w("Too much club."); }; }; if ((flags & inRough) == inRough) { w("You're in the rough."); }; if ((flags & inSand) == inSand) { w("You're in a sand trap."); }; if ((flags & onGreen) == onGreen) { var pd = (cupDistance < 4) ? ((cupDistance * 3) + " feet") : (cupDistance + " yards"); Console.WriteLine("You're on the green. It's {0} from the pin.", pd); }; if (((flags & onFairway) == onFairway) || ((flags & inRough) == inRough)) { Console.WriteLine("Shot went {0} yards. It's {1} yards from the cup.", travelDistance, cupDistance); }; ScoreCardRecordStroke(new Ball(plot.X, plot.Y, 0, GameObjType.BALL)); BALL = new Ball(plot.X, plot.Y, 0, GameObjType.BALL); TeeUp(); } // --------------------------------------------------------------- ReportCurrentScore void ReportCurrentScore() { var par = CourseInfo[HOLE_NUM].Par; if (ScoreCard[HOLE_NUM].Count == par + 1) { w("A bogey. One above par."); }; if (ScoreCard[HOLE_NUM].Count == par) { w("Par. Nice."); }; if (ScoreCard[HOLE_NUM].Count == (par - 1)) { w("A birdie! One below par."); }; if (ScoreCard[HOLE_NUM].Count == (par - 2)) { w("An Eagle! Two below par."); }; if (ScoreCard[HOLE_NUM].Count == (par - 3)) { w("Double Eagle! Unbelievable."); }; int totalPar = 0; for (var i = 1; i <= HOLE_NUM; i++) { totalPar += CourseInfo[i].Par; }; w(" "); w("-----------------------------------------------------"); Console.WriteLine(" Total par for {0} hole{1} is: {2}. Your total is: {3}.", HOLE_NUM, ((HOLE_NUM > 1) ? "s" : ""), //plural totalPar, ScoreCardGetTotal()); w("-----------------------------------------------------"); w(" "); if (HOLE_NUM == 18) { GameOver(); } else { Thread.Sleep(2000); NewHole(); }; } // --------------------------------------------------------------- FindBall int FindBall(Ball ball, int flags) { if (IsOnFairway(ball) && !IsOnGreen(ball)) { flags |= onFairway; } if (IsOnGreen(ball)) { flags |= onGreen; } if (IsInRough(ball)) { flags |= inRough; } if (IsOutOfBounds(ball)) { flags |= outOfBounds; } if (IsInHazard(ball, GameObjType.WATER)) { flags |= inWater; } if (IsInHazard(ball, GameObjType.TREES)) { flags |= inTrees; } if (IsInHazard(ball, GameObjType.SAND)) { flags |= inSand; } if (ball.Y < 0) { flags |= passedCup; } // less than 2, it's in the cup var d = GetDistance(new Point(ball.X, ball.Y), new Point(holeGeometry.Cup.X, holeGeometry.Cup.Y)); if (d < 2) { flags |= inCup; }; return flags; } // --------------------------------------------------------------- IsOnFairway bool IsOnFairway(Ball ball) { return IsInRectangle(ball, holeGeometry.Fairway); } // --------------------------------------------------------------- IsOngreen bool IsOnGreen(Ball ball) { var d = GetDistance(new Point(ball.X, ball.Y), new Point(holeGeometry.Cup.X, holeGeometry.Cup.Y)); return d < holeGeometry.Green.Radius; } // --------------------------------------------------------------- IsInHazard bool IsInHazard(Ball ball, GameObjType hazard) { bool result = false; Array.ForEach(holeGeometry.Hazards, (Hazard h) => { var d = GetDistance(new Point(ball.X, ball.Y), new Point(h.X, h.Y)); if ((d < h.Radius) && h.Type == hazard) { result = true; }; }); return result; } // --------------------------------------------------------------- IsInRough bool IsInRough(Ball ball) { return IsInRectangle(ball, holeGeometry.Rough) && (IsInRectangle(ball, holeGeometry.Fairway) == false); } // --------------------------------------------------------------- IsOutOfBounds bool IsOutOfBounds(Ball ball) { return (IsOnFairway(ball) == false) && (IsInRough(ball) == false); } // --------------------------------------------------------------- ScoreCardNewHole void ScoreCardStartNewHole() { ScoreCard.Add(new List()); } // --------------------------------------------------------------- ScoreCardRecordStroke void ScoreCardRecordStroke(Ball ball) { var clone = new Ball(ball.X, ball.Y, 0, GameObjType.BALL); ScoreCard[HOLE_NUM].Add(clone); } // ------------------------------------------------------------ ScoreCardGetPreviousStroke Ball ScoreCardGetPreviousStroke() { return ScoreCard[HOLE_NUM][ScoreCard[HOLE_NUM].Count - 1]; } // --------------------------------------------------------------- ScoreCardGetTotal int ScoreCardGetTotal() { int total = 0; ScoreCard.ForEach((h) => { total += h.Count; }); return total; } // --------------------------------------------------------------- Ask // input from console is always an integer passed to a callback // or "quit" to end game void Ask(string question, int min, int max, Action callback) { w(question); string i = Console.ReadLine().Trim().ToLower(); if (i == "quit") { Quit(); return; }; if (i == "bag") { ReviewBag(); }; int n; bool success = Int32.TryParse(i, out n); if (success) { if (n >= min && n <= max) { callback(n); } else { Ask(question, min, max, callback); } } else { Ask(question, min, max, callback); }; } // --------------------------------------------------------------- Wait void Wait(Action callback) { w("Press any key to continue."); ConsoleKeyInfo keyinfo; do { keyinfo = Console.ReadKey(true); } while (keyinfo.KeyChar < 0); Console.Clear(); callback(0); } // --------------------------------------------------------------- ReviewBag void ReviewBag() { w(" "); w(" # Club Average Yardage"); w("-----------------------------------"); w(" 1 Driver 250"); w(" 2 3 Wood 225"); w(" 3 5 Wood 200"); w(" 4 Hybrid 190"); w(" 5 4 Iron 170"); w(" 6 7 Iron 150"); w(" 7 9 Iron 125"); w(" 8 Pitching wedge 110"); w(" 9 Sand wedge 75"); w(" 10 Putter 10"); w(" "); } // --------------------------------------------------------------- Quit void Quit() { w(""); w("Looks like rain. Goodbye!"); w(""); Wait((z) => { }); return; } // --------------------------------------------------------------- GameOver void GameOver() { var net = ScoreCardGetTotal() - Handicap; w("Good game!"); w("Your net score is: " + net); w("Let's visit the pro shop..."); w(" "); Wait((z) => { }); return; } // YOUR BAG // ======================================================== Clubs (string, int)[] Clubs = new (string, int)[] { ("",0), // name, average yardage ("Driver", 250), ("3 Wood", 225), ("5 Wood", 200), ("Hybrid", 190), ("4 Iron", 170), ("7 Iron", 150), ("9 Iron", 125), ("Pitching wedge", 110), ("Sand wedge", 75), ("Putter", 10) }; // THE COURSE // ======================================================== CourseInfo HoleInfo[] CourseInfo = new HoleInfo[]{ new HoleInfo(0, 0, 0, new Hazard[]{}, ""), // include a blank so index 1 == hole 1 // -------------------------------------------------------- front 9 // hole, yards, par, hazards, (description) new HoleInfo(1, 361, 4, new Hazard[]{ new Hazard( 20, 100, 10, GameObjType.TREES), new Hazard(-20, 80, 10, GameObjType.TREES), new Hazard(-20, 100, 10, GameObjType.TREES) }, "There are a couple of trees on the left and right."), new HoleInfo(2, 389, 4, new Hazard[]{ new Hazard(0, 160, 20, GameObjType.WATER) }, "There is a large water hazard across the fairway about 150 yards."), new HoleInfo(3, 206, 3, new Hazard[]{ new Hazard( 20, 20, 5, GameObjType.WATER), new Hazard(-20, 160, 10, GameObjType.WATER), new Hazard( 10, 12, 5, GameObjType.SAND) }, "There is some sand and water near the green."), new HoleInfo(4, 500, 5, new Hazard[]{ new Hazard(-14, 12, 12, GameObjType.SAND) }, "There's a bunker to the left of the green."), new HoleInfo(5, 408, 4, new Hazard[]{ new Hazard(20, 120, 20, GameObjType.TREES), new Hazard(20, 160, 20, GameObjType.TREES), new Hazard(10, 20, 5, GameObjType.SAND) }, "There are some trees to your right."), new HoleInfo(6, 359, 4, new Hazard[]{ new Hazard( 14, 0, 4, GameObjType.SAND), new Hazard(-14, 0, 4, GameObjType.SAND) }, ""), new HoleInfo(7, 424, 5, new Hazard[]{ new Hazard(20, 200, 10, GameObjType.SAND), new Hazard(10, 180, 10, GameObjType.SAND), new Hazard(20, 160, 10, GameObjType.SAND) }, "There are several sand traps along your right."), new HoleInfo(8, 388, 4, new Hazard[]{ new Hazard(-20, 340, 10, GameObjType.TREES) }, ""), new HoleInfo(9, 196, 3, new Hazard[]{ new Hazard(-30, 180, 20, GameObjType.TREES), new Hazard( 14, -8, 5, GameObjType.SAND) }, ""), // -------------------------------------------------------- back 9 // hole, yards, par, hazards, (description) new HoleInfo(10, 400, 4, new Hazard[]{ new Hazard(-14, -8, 5, GameObjType.SAND), new Hazard( 14, -8, 5, GameObjType.SAND) }, ""), new HoleInfo(11, 560, 5, new Hazard[]{ new Hazard(-20, 400, 10, GameObjType.TREES), new Hazard(-10, 380, 10, GameObjType.TREES), new Hazard(-20, 260, 10, GameObjType.TREES), new Hazard(-20, 200, 10, GameObjType.TREES), new Hazard(-10, 180, 10, GameObjType.TREES), new Hazard(-20, 160, 10, GameObjType.TREES) }, "Lots of trees along the left of the fairway."), new HoleInfo(12, 132, 3, new Hazard[]{ new Hazard(-10, 120, 10, GameObjType.WATER), new Hazard( -5, 100, 10, GameObjType.SAND) }, "There is water and sand directly in front of you. A good drive should clear both."), new HoleInfo(13, 357, 4, new Hazard[]{ new Hazard(-20, 200, 10, GameObjType.TREES), new Hazard(-10, 180, 10, GameObjType.TREES), new Hazard(-20, 160, 10, GameObjType.TREES), new Hazard( 14, 12, 8, GameObjType.SAND) }, ""), new HoleInfo(14, 294, 4, new Hazard[]{ new Hazard(0, 20, 10, GameObjType.SAND) }, ""), new HoleInfo(15, 475, 5, new Hazard[]{ new Hazard(-20, 20, 10, GameObjType.WATER), new Hazard( 10, 20, 10, GameObjType.SAND) }, "Some sand and water near the green."), new HoleInfo(16, 375, 4, new Hazard[]{ new Hazard(-14, -8, 5, GameObjType.SAND) }, ""), new HoleInfo(17, 180, 3, new Hazard[]{ new Hazard( 20, 100, 10, GameObjType.TREES), new Hazard(-20, 80, 10, GameObjType.TREES) }, ""), new HoleInfo(18, 550, 5, new Hazard[]{ new Hazard(20, 30, 15, GameObjType.WATER) }, "There is a water hazard near the green.") }; // -------------------------------------------------------- HoleInfo class HoleInfo { public int Hole { get; } public int Yards { get; } public int Par { get; } public Hazard[] Hazard { get; } public string Description { get; } public HoleInfo(int hole, int yards, int par, Hazard[] hazard, string description) { Hole = hole; Yards = yards; Par = par; Hazard = hazard; Description = description; } } public enum GameObjType { BALL, CUP, GREEN, FAIRWAY, ROUGH, TREES, WATER, SAND } // -------------------------------------------------------- CircleGameObj public class CircleGameObj { public GameObjType Type { get; } public int X { get; } public int Y { get; } public int Radius { get; } public CircleGameObj(int x, int y, int r, GameObjType type) { Type = type; X = x; Y = y; Radius = r; } } // -------------------------------------------------------- RectGameObj public class RectGameObj { public GameObjType Type { get; } public int X { get; } public int Y { get; } public int Width { get; } public int Length { get; } public RectGameObj(int x, int y, int w, int l, GameObjType type) { Type = type; X = x; Y = y; Width = w; Length = l; } } // -------------------------------------------------------- HoleGeometry public class HoleGeometry { public CircleGameObj Cup { get; } public CircleGameObj Green { get; } public RectGameObj Fairway { get; } public RectGameObj Rough { get; } public Hazard[] Hazards { get; } public HoleGeometry(CircleGameObj cup, CircleGameObj green, RectGameObj fairway, RectGameObj rough, Hazard[] haz) { Cup = cup; Green = green; Fairway = fairway; Rough = rough; Hazards = haz; } } // -------------------------------------------------------- Plot public class Plot { public int X { get; } public int Y { get; set; } public int Offline { get; } public Plot(int x, int y, int offline) { X = x; Y = y; Offline = offline; } } // -------------------------------------------------------- GetDistance // distance between 2 points double GetDistance(Point pt1, Point pt2) { return Math.Sqrt(Math.Pow((pt2.X - pt1.X), 2) + Math.Pow((pt2.Y - pt1.Y), 2)); } // -------------------------------------------------------- IsInRectangle bool IsInRectangle(CircleGameObj pt, RectGameObj rect) { return ((pt.X > rect.X) && (pt.X < rect.X + rect.Width) && (pt.Y > rect.Y) && (pt.Y < rect.Y + rect.Length)); } // -------------------------------------------------------- ToRadians double ToRadians(double angle) { return angle * (Math.PI / 180.0); } // -------------------------------------------------------- ToDegrees360 // radians to 360 degrees double ToDegrees360(double angle) { double deg = angle * (180.0 / Math.PI); if (deg < 0.0) { deg += 360.0; } return deg; } // -------------------------------------------------------- Odds // chance an integer is <= the given argument // between 1-100 Random RND = new Random(); bool Odds(int x) { return RND.Next(1, 101) <= x; } } }