diff --git a/00_Alternate_Languages/01_Acey_Ducey/go/main.go b/00_Alternate_Languages/01_Acey_Ducey/go/main.go new file mode 100644 index 00000000..10e96589 --- /dev/null +++ b/00_Alternate_Languages/01_Acey_Ducey/go/main.go @@ -0,0 +1,112 @@ +package main + +import ( + "bufio" + "fmt" + "math/rand" + "os" + "sort" + "strconv" + "strings" + "time" +) + +var welcome = ` +Acey-Ducey is played in the following manner +The dealer (computer) deals two cards face up +You have an option to bet or not bet depending +on whether or not you feel the card will have +a value between the first two. +If you do not want to bet, input a 0 + ` + +func main() { + rand.Seed(time.Now().UnixNano()) + scanner := bufio.NewScanner(os.Stdin) + + fmt.Println(welcome) + + for { + play(100) + fmt.Println("TRY AGAIN (YES OR NO)") + scanner.Scan() + response := scanner.Text() + if strings.ToUpper(response) != "YES" { + break + } + } + + fmt.Println("O.K., HOPE YOU HAD FUN!") +} + +func play(money int) { + scanner := bufio.NewScanner(os.Stdin) + var bet int + + for { + // Shuffle the cards + cards := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14} + rand.Shuffle(len(cards), func(i, j int) { cards[i], cards[j] = cards[j], cards[i] }) + + // Take the first two for the dealer and sort + dealerCards := cards[0:2] + sort.Ints(dealerCards) + + fmt.Printf("YOU NOW HAVE %d DOLLARS.\n\n", money) + fmt.Printf("HERE ARE YOUR NEXT TWO CARDS:\n%s\n%s", getCardName(dealerCards[0]), getCardName(dealerCards[1])) + fmt.Printf("\n\n") + + //Check if Bet is Valid + for { + fmt.Println("WHAT IS YOUR BET:") + scanner.Scan() + b, err := strconv.Atoi(scanner.Text()) + if err != nil { + fmt.Println("PLEASE ENTER A POSITIVE NUMBER") + continue + } + bet = b + + if bet == 0 { + fmt.Printf("CHICKEN!\n\n") + goto there + } + + if (bet > 0) && (bet <= money) { + break + } + } + + // Draw Players Card + fmt.Printf("YOUR CARD: %s\n", getCardName(cards[2])) + if (cards[2] > dealerCards[0]) && (cards[2] < dealerCards[1]) { + fmt.Println("YOU WIN!!!") + money = money + bet + } else { + fmt.Println("SORRY, YOU LOSE") + money = money - bet + } + fmt.Println() + + if money <= 0 { + fmt.Printf("%s\n", "SORRY, FRIEND, BUT YOU BLEW YOUR WAD.") + return + } + there: + } +} + +func getCardName(c int) string { + switch c { + case 11: + return "JACK" + case 12: + return "QUEEN" + case 13: + return "KING" + case 14: + return "ACE" + default: + return strconv.Itoa(c) + } +} diff --git a/00_Alternate_Languages/02_Amazing/go/main.go b/00_Alternate_Languages/02_Amazing/go/main.go new file mode 100644 index 00000000..11b027ef --- /dev/null +++ b/00_Alternate_Languages/02_Amazing/go/main.go @@ -0,0 +1,217 @@ +package main + +import ( + "bufio" + "fmt" + "log" + "math/rand" + "os" + "strconv" + "time" +) + +func main() { + rand.Seed(time.Now().UnixNano()) + + printWelcome() + + h, w := getDimensions() + m := NewMaze(h, w) + m.draw() +} + +type direction int64 + +const ( + LEFT direction = iota + UP + RIGHT + DOWN +) + +const ( + EXIT_DOWN = 1 + EXIT_RIGHT = 2 +) + +type maze struct { + width int + length int + used [][]int + walls [][]int + enterCol int +} + +func NewMaze(w, l int) maze { + if (w < 2) || (l < 2) { + log.Fatal("invalid dimensions supplied") + } + + m := maze{width: w, length: l} + + m.used = make([][]int, l) + for i := range m.used { + m.used[i] = make([]int, w) + } + + m.walls = make([][]int, l) + for i := range m.walls { + m.walls[i] = make([]int, w) + } + + // randomly determine the entry column + m.enterCol = rand.Intn(w) + + // determine layout of walls + m.build() + + // add an exit + col := rand.Intn(m.width - 1) + row := m.length - 1 + m.walls[row][col] = m.walls[row][col] + 1 + + return m +} + +func (m *maze) build() { + row := 0 + col := 0 + count := 2 + + for { + possibleDirs := m.getPossibleDirections(row, col) + + if len(possibleDirs) != 0 { + row, col, count = m.makeOpening(possibleDirs, row, col, count) + } else { + for { + if col != m.width-1 { + col = col + 1 + } else if row != m.length-1 { + row = row + 1 + col = 0 + } else { + row = 0 + col = 0 + } + + if m.used[row][col] != 0 { + break + } + } + } + + if count == (m.width*m.length)+1 { + break + } + } + +} + +func (m *maze) getPossibleDirections(row, col int) []direction { + possible_dirs := make(map[direction]bool, 4) + possible_dirs[LEFT] = true + possible_dirs[UP] = true + possible_dirs[RIGHT] = true + possible_dirs[DOWN] = true + + if (col == 0) || (m.used[row][col-1] != 0) { + possible_dirs[LEFT] = false + } + if (row == 0) || (m.used[row-1][col] != 0) { + possible_dirs[UP] = false + } + if (col == m.width-1) || (m.used[row][col+1] != 0) { + possible_dirs[RIGHT] = false + } + if (row == m.length-1) || (m.used[row+1][col] != 0) { + possible_dirs[DOWN] = false + } + + ret := make([]direction, 0) + for d, v := range possible_dirs { + if v { + ret = append(ret, d) + } + } + return ret +} + +func (m *maze) makeOpening(dirs []direction, row, col, count int) (int, int, int) { + dir := rand.Intn(len(dirs)) + + if dirs[dir] == LEFT { + col = col - 1 + m.walls[row][col] = int(EXIT_RIGHT) + } else if dirs[dir] == UP { + row = row - 1 + m.walls[row][col] = int(EXIT_DOWN) + } else if dirs[dir] == RIGHT { + m.walls[row][col] = m.walls[row][col] + EXIT_RIGHT + col = col + 1 + } else if dirs[dir] == DOWN { + m.walls[row][col] = m.walls[row][col] + EXIT_DOWN + row = row + 1 + } + + m.used[row][col] = count + count = count + 1 + return row, col, count +} + +// draw the maze +func (m *maze) draw() { + for col := 0; col < m.width; col++ { + if col == m.enterCol { + fmt.Print(". ") + } else { + fmt.Print(".--") + } + } + fmt.Println(".") + + for row := 0; row < m.length; row++ { + fmt.Print("|") + for col := 0; col < m.width; col++ { + if m.walls[row][col] < 2 { + fmt.Print(" |") + } else { + fmt.Print(" ") + } + } + fmt.Println() + for col := 0; col < m.width; col++ { + if (m.walls[row][col] == 0) || (m.walls[row][col] == 2) { + fmt.Print(":--") + } else { + fmt.Print(": ") + } + } + fmt.Println(".") + } +} + +func printWelcome() { + fmt.Println(" AMAZING PROGRAM") + fmt.Print(" CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n\n\n") +} + +func getDimensions() (int, int) { + scanner := bufio.NewScanner(os.Stdin) + + fmt.Println("Enter a width ( > 1 ):") + scanner.Scan() + w, err := strconv.Atoi(scanner.Text()) + if err != nil { + log.Fatal("invalid dimension") + } + + fmt.Println("Enter a height ( > 1 ):") + scanner.Scan() + h, err := strconv.Atoi(scanner.Text()) + if err != nil { + log.Fatal("invalid dimension") + } + + return w, h +} diff --git a/00_Alternate_Languages/03_Animal/go/main.go b/00_Alternate_Languages/03_Animal/go/main.go new file mode 100644 index 00000000..2eb0904e --- /dev/null +++ b/00_Alternate_Languages/03_Animal/go/main.go @@ -0,0 +1,159 @@ +package main + +import ( + "bufio" + "fmt" + "log" + "os" + "strings" +) + +type node struct { + text string + yesNode *node + noNode *node +} + +func newNode(text string, yes_node, no_node *node) *node { + n := node{text: text} + if yes_node != nil { + n.yesNode = yes_node + } + if no_node != nil { + n.noNode = no_node + } + return &n +} + +func (n *node) update(newQuestion, newAnswer, newAnimal string) { + oldAnimal := n.text + + n.text = newQuestion + + if newAnswer == "y" { + n.yesNode = newNode(newAnimal, nil, nil) + n.noNode = newNode(oldAnimal, nil, nil) + } else { + n.yesNode = newNode(oldAnimal, nil, nil) + n.noNode = newNode(newAnimal, nil, nil) + } +} + +func (n *node) isLeaf() bool { + return (n.yesNode == nil) && (n.noNode == nil) +} + +func listKnownAnimals(root *node) { + if root == nil { + return + } + + if root.isLeaf() { + fmt.Printf("%s ", root.text) + return + } + + if root.yesNode != nil { + listKnownAnimals(root.yesNode) + } + + if root.noNode != nil { + listKnownAnimals(root.noNode) + } +} + +func parseInput(message string, checkList bool, rootNode *node) string { + scanner := bufio.NewScanner(os.Stdin) + token := "" + + for { + fmt.Println(message) + scanner.Scan() + inp := strings.ToLower(scanner.Text()) + + if checkList && inp == "list" { + fmt.Println("Animals I already know are:") + listKnownAnimals(rootNode) + fmt.Println() + } + + if len(inp) > 0 { + token = inp + } else { + token = "" + } + + if token == "y" || token == "n" { + break + } + } + return token +} + +func avoidVoidInput(message string) string { + scanner := bufio.NewScanner(os.Stdin) + answer := "" + for { + fmt.Println(message) + scanner.Scan() + answer = scanner.Text() + + if answer != "" { + break + } + } + return answer +} + +func printIntro() { + fmt.Println(" Animal") + fmt.Println(" Creative Computing Morristown, New Jersey") + fmt.Println("\nPlay 'Guess the Animal'") + fmt.Println("Think of an animal and the computer will try to guess it") +} + +func main() { + yesChild := newNode("Fish", nil, nil) + noChild := newNode("Bird", nil, nil) + rootNode := newNode("Does it swim?", yesChild, noChild) + + printIntro() + + keepPlaying := (parseInput("Are you thinking of an animal?", true, rootNode) == "y") + + for keepPlaying { + keepAsking := true + + actualNode := rootNode + + for keepAsking { + if !actualNode.isLeaf() { + answer := parseInput(actualNode.text, false, nil) + + if answer == "y" { + if actualNode.yesNode == nil { + log.Fatal("invalid node") + } + actualNode = actualNode.yesNode + } else { + if actualNode.noNode == nil { + log.Fatal("invalid node") + } + actualNode = actualNode.noNode + } + } else { + answer := parseInput(fmt.Sprintf("Is it a %s?", actualNode.text), false, nil) + if answer == "n" { + newAnimal := avoidVoidInput("The animal you were thinking of was a ?") + newQuestion := avoidVoidInput(fmt.Sprintf("Please type in a question that would distinguish a '%s' from a '%s':", newAnimal, actualNode.text)) + newAnswer := parseInput(fmt.Sprintf("For a '%s' the answer would be", newAnimal), false, nil) + actualNode.update(newQuestion+"?", newAnswer, newAnimal) + } else { + fmt.Println("Why not try another animal?") + } + keepAsking = false + } + } + keepPlaying = (parseInput("Are you thinking of an animal?", true, rootNode) == "y") + } +} diff --git a/00_Alternate_Languages/05_Bagels/go/main.go b/00_Alternate_Languages/05_Bagels/go/main.go new file mode 100644 index 00000000..b0d2b106 --- /dev/null +++ b/00_Alternate_Languages/05_Bagels/go/main.go @@ -0,0 +1,166 @@ +package main + +import ( + "bufio" + "fmt" + "math/rand" + "os" + "strconv" + "strings" + "time" +) + +const MAXGUESSES int = 20 + +func printWelcome() { + fmt.Println("\n Bagels") + fmt.Println("Creative Computing Morristown, New Jersey") + fmt.Println() +} +func printRules() { + fmt.Println() + fmt.Println("I am thinking of a three-digit number. Try to guess") + fmt.Println("my number and I will give you clues as follows:") + fmt.Println(" PICO - One digit correct but in the wrong position") + fmt.Println(" FERMI - One digit correct and in the right position") + fmt.Println(" BAGELS - No digits correct") +} + +func getNumber() []string { + numbers := []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"} + rand.Shuffle(len(numbers), func(i, j int) { numbers[i], numbers[j] = numbers[j], numbers[i] }) + + return numbers[:3] +} + +func getValidGuess(guessNumber int) string { + var guess string + scanner := bufio.NewScanner(os.Stdin) + valid := false + for !valid { + fmt.Printf("Guess # %d?\n", guessNumber) + scanner.Scan() + guess = strings.TrimSpace(scanner.Text()) + + // guess must be 3 characters + if len(guess) == 3 { + // and should be numeric + _, err := strconv.Atoi(guess) + if err != nil { + fmt.Println("What?") + } else { + // and the numbers should be unique + if (guess[0:1] != guess[1:2]) && (guess[0:1] != guess[2:3]) && (guess[1:2] != guess[2:3]) { + valid = true + } else { + fmt.Println("Oh, I forgot to tell you that the number I have in mind") + fmt.Println("has no two digits the same.") + } + } + } else { + fmt.Println("Try guessing a three-digit number.") + } + } + + return guess +} + +func buildResultString(num []string, guess string) string { + result := "" + + // correct digits in wrong place + for i := 0; i < 2; i++ { + if num[i] == guess[i+1:i+2] { + result += "PICO " + } + if num[i+1] == guess[i:i+1] { + result += "PICO " + } + } + if num[0] == guess[2:3] { + result += "PICO " + } + if num[2] == guess[0:1] { + result += "PICO " + } + + // correct digits in right place + for i := 0; i < 3; i++ { + if num[i] == guess[i:i+1] { + result += "FERMI " + } + } + + // nothing right? + if result == "" { + result = "BAGELS" + } + + return result +} + +func main() { + rand.Seed(time.Now().UnixNano()) + scanner := bufio.NewScanner(os.Stdin) + + printWelcome() + + fmt.Println("Would you like the rules (Yes or No)? ") + scanner.Scan() + response := scanner.Text() + if len(response) > 0 { + if strings.ToUpper(response[0:1]) != "N" { + printRules() + } + } else { + printRules() + } + + gamesWon := 0 + stillRunning := true + + for stillRunning { + num := getNumber() + numStr := strings.Join(num, "") + guesses := 1 + + fmt.Println("\nO.K. I have a number in mind.") + guessing := true + for guessing { + guess := getValidGuess(guesses) + + if guess == numStr { + fmt.Println("You got it!!") + gamesWon++ + guessing = false + } else { + fmt.Println(buildResultString(num, guess)) + guesses++ + if guesses > MAXGUESSES { + fmt.Println("Oh well") + fmt.Printf("That's %d guesses. My number was %s\n", MAXGUESSES, numStr) + guessing = false + } + } + } + + validRespone := false + for !validRespone { + fmt.Println("Play again (Yes or No)?") + scanner.Scan() + response := scanner.Text() + if len(response) > 0 { + validRespone = true + if strings.ToUpper(response[0:1]) != "Y" { + stillRunning = false + } + } + } + } + + if gamesWon > 0 { + fmt.Printf("\nA %d point Bagels buff!!\n", gamesWon) + } + + fmt.Println("Hope you had fun. Bye") +} diff --git a/00_Alternate_Languages/08_Batnum/go/main.go b/00_Alternate_Languages/08_Batnum/go/main.go new file mode 100644 index 00000000..10334799 --- /dev/null +++ b/00_Alternate_Languages/08_Batnum/go/main.go @@ -0,0 +1,248 @@ +package main + +import ( + "bufio" + "fmt" + "os" + "strconv" + "strings" +) + +type StartOption int8 + +const ( + StartUndefined StartOption = iota + ComputerFirst + PlayerFirst +) + +type WinOption int8 + +const ( + WinUndefined WinOption = iota + TakeLast + AvoidLast +) + +type GameOptions struct { + pileSize int + winOption WinOption + startOption StartOption + minSelect int + maxSelect int +} + +func NewOptions() *GameOptions { + g := GameOptions{} + + g.pileSize = getPileSize() + if g.pileSize < 0 { + return &g + } + + g.winOption = getWinOption() + g.minSelect, g.maxSelect = getMinMax() + g.startOption = getStartOption() + + return &g +} + +func getPileSize() int { + ps := 0 + var err error + scanner := bufio.NewScanner(os.Stdin) + + for { + fmt.Println("Enter Pile Size ") + scanner.Scan() + ps, err = strconv.Atoi(scanner.Text()) + if err == nil { + break + } + } + return ps +} + +func getWinOption() WinOption { + scanner := bufio.NewScanner(os.Stdin) + + for { + fmt.Println("ENTER WIN OPTION - 1 TO TAKE LAST, 2 TO AVOID LAST:") + scanner.Scan() + w, err := strconv.Atoi(scanner.Text()) + if err == nil && (w == 1 || w == 2) { + return WinOption(w) + } + } +} + +func getStartOption() StartOption { + scanner := bufio.NewScanner(os.Stdin) + + for { + fmt.Println("ENTER START OPTION - 1 COMPUTER FIRST, 2 YOU FIRST ") + scanner.Scan() + s, err := strconv.Atoi(scanner.Text()) + if err == nil && (s == 1 || s == 2) { + return StartOption(s) + } + } +} + +func getMinMax() (int, int) { + minSelect := 0 + maxSelect := 0 + var minErr error + var maxErr error + scanner := bufio.NewScanner(os.Stdin) + + for { + fmt.Println("ENTER MIN AND MAX ") + scanner.Scan() + enteredValues := scanner.Text() + vals := strings.Split(enteredValues, " ") + minSelect, minErr = strconv.Atoi(vals[0]) + maxSelect, maxErr = strconv.Atoi(vals[1]) + if (minErr == nil) && (maxErr == nil) && (minSelect > 0) && (maxSelect > 0) && (maxSelect > minSelect) { + return minSelect, maxSelect + } + } +} + +// This handles the player's turn - asking the player how many objects +// to take and doing some basic validation around that input. Then it +// checks for any win conditions. +// Returns a boolean indicating whether the game is over and the new pile_size. +func playerMove(pile, min, max int, win WinOption) (bool, int) { + scanner := bufio.NewScanner(os.Stdin) + done := false + for !done { + fmt.Println("YOUR MOVE") + scanner.Scan() + m, err := strconv.Atoi(scanner.Text()) + if err != nil { + continue + } + + if m == 0 { + fmt.Println("I TOLD YOU NOT TO USE ZERO! COMPUTER WINS BY FORFEIT.") + return true, pile + } + + if m > max || m < min { + fmt.Println("ILLEGAL MOVE, REENTER IT") + continue + } + + pile -= m + done = true + + if pile <= 0 { + if win == AvoidLast { + fmt.Println("TOUGH LUCK, YOU LOSE.") + } else { + fmt.Println("CONGRATULATIONS, YOU WIN.") + } + return true, pile + } + } + return false, pile +} + +// This handles the logic to determine how many objects the computer +// will select on its turn. +func computerPick(pile, min, max int, win WinOption) int { + var q int + if win == AvoidLast { + q = pile - 1 + } else { + q = pile + } + c := min + max + + pick := q - (c * int(q/c)) + + if pick < min { + pick = min + } else if pick > max { + pick = max + } + + return pick +} + +// This handles the computer's turn - first checking for the various +// win/lose conditions and then calculating how many objects +// the computer will take. +// Returns a boolean indicating whether the game is over and the new pile_size. +func computerMove(pile, min, max int, win WinOption) (bool, int) { + // first check for end-game conditions + if win == TakeLast && pile <= max { + fmt.Printf("COMPUTER TAKES %d AND WINS\n", pile) + return true, pile + } + + if win == AvoidLast && pile <= min { + fmt.Printf("COMPUTER TAKES %d AND LOSES\n", pile) + return true, pile + } + + // otherwise determine the computer's selection + selection := computerPick(pile, min, max, win) + pile -= selection + fmt.Printf("COMPUTER TAKES %d AND LEAVES %d\n", selection, pile) + return false, pile +} + +// This is the main game loop - repeating each turn until one +// of the win/lose conditions is met. +func play(pile, min, max int, start StartOption, win WinOption) { + gameOver := false + playersTurn := (start == PlayerFirst) + + for !gameOver { + if playersTurn { + gameOver, pile = playerMove(pile, min, max, win) + playersTurn = false + if gameOver { + return + } + } + + if !playersTurn { + gameOver, pile = computerMove(pile, min, max, win) + playersTurn = true + } + } +} + +// Print out the introduction and rules of the game +func printIntro() { + fmt.Printf("%33s%s\n", " ", "BATNUM") + fmt.Printf("%15s%s\n", " ", "CREATIVE COMPUTING MORRISSTOWN, NEW JERSEY") + fmt.Printf("\n\n\n") + fmt.Println("THIS PROGRAM IS A 'BATTLE OF NUMBERS' GAME, WHERE THE") + fmt.Println("COMPUTER IS YOUR OPPONENT.") + fmt.Println() + fmt.Println("THE GAME STARTS WITH AN ASSUMED PILE OF OBJECTS. YOU") + fmt.Println("AND YOUR OPPONENT ALTERNATELY REMOVE OBJECTS FROM THE PILE.") + fmt.Println("WINNING IS DEFINED IN ADVANCE AS TAKING THE LAST OBJECT OR") + fmt.Println("NOT. YOU CAN ALSO SPECIFY SOME OTHER BEGINNING CONDITIONS.") + fmt.Println("DON'T USE ZERO, HOWEVER, IN PLAYING THE GAME.") + fmt.Println("ENTER A NEGATIVE NUMBER FOR NEW PILE SIZE TO STOP PLAYING.") + fmt.Println() +} + +func main() { + for { + printIntro() + + g := NewOptions() + + if g.pileSize < 0 { + return + } + + play(g.pileSize, g.minSelect, g.maxSelect, g.startOption, g.winOption) + } +} diff --git a/00_Alternate_Languages/09_Battle/go/main.go b/00_Alternate_Languages/09_Battle/go/main.go new file mode 100644 index 00000000..c0f911f4 --- /dev/null +++ b/00_Alternate_Languages/09_Battle/go/main.go @@ -0,0 +1,266 @@ +package main + +import ( + "bufio" + "fmt" + "math/rand" + "os" + "strconv" + "strings" + "time" +) + +const ( + SEA_WIDTH = 6 + DESTROYER_LENGTH = 2 + CRUISER_LENGTH = 3 + CARRIER_LENGTH = 4 +) + +type Point [2]int +type Vector Point +type Sea [][]int + +func NewSea() Sea { + s := make(Sea, 6) + for r := 0; r < SEA_WIDTH; r++ { + c := make([]int, 6) + s[r] = c + } + + return s +} + +func getRandomVector() Vector { + v := Vector{} + + for { + v[0] = rand.Intn(3) - 1 + v[1] = rand.Intn(3) - 1 + + if !(v[0] == 0 && v[1] == 0) { + break + } + } + return v +} + +func addVector(p Point, v Vector) Point { + newPoint := Point{} + + newPoint[0] = p[0] + v[0] + newPoint[1] = p[1] + v[1] + + return newPoint +} + +func isWithinSea(p Point, s Sea) bool { + return (1 <= p[0] && p[0] <= len(s)) && (1 <= p[1] && p[1] <= len(s)) +} + +func valueAt(p Point, s Sea) int { + return s[p[1]-1][p[0]-1] +} + +func reportInputError() { + fmt.Printf("INVALID. SPECIFY TWO NUMBERS FROM 1 TO %d, SEPARATED BY A COMMA.\n", SEA_WIDTH) +} + +func getNextTarget(s Sea) Point { + scanner := bufio.NewScanner(os.Stdin) + + for { + fmt.Println("\n?") + scanner.Scan() + + vals := strings.Split(scanner.Text(), ",") + + if len(vals) != 2 { + reportInputError() + continue + } + + x, xErr := strconv.Atoi(strings.TrimSpace(vals[0])) + y, yErr := strconv.Atoi(strings.TrimSpace(vals[1])) + + if (len(vals) != 2) || (xErr != nil) || (yErr != nil) { + reportInputError() + continue + } + + p := Point{} + p[0] = x + p[1] = y + if isWithinSea(p, s) { + return p + } + } +} + +func setValueAt(value int, p Point, s Sea) { + s[p[1]-1][p[0]-1] = value +} + +func hasShip(s Sea, code int) bool { + hasShip := false + for r := 0; r < SEA_WIDTH; r++ { + for c := 0; c < SEA_WIDTH; c++ { + if s[r][c] == code { + hasShip = true + break + } + } + } + return hasShip +} + +func countSunk(s Sea, codes []int) int { + sunk := 0 + + for _, c := range codes { + if !hasShip(s, c) { + sunk += 1 + } + } + + return sunk +} + +func placeShip(s Sea, size, code int) { + for { + start := Point{} + start[0] = rand.Intn(SEA_WIDTH) + 1 + start[1] = rand.Intn(SEA_WIDTH) + 1 + vector := getRandomVector() + + point := start + points := []Point{} + + for i := 0; i < size; i++ { + point = addVector(point, vector) + points = append(points, point) + } + + clearPosition := true + for _, p := range points { + if !isWithinSea(p, s) { + clearPosition = false + break + } + if valueAt(p, s) > 0 { + clearPosition = false + break + } + } + if !clearPosition { + continue + } + + for _, p := range points { + setValueAt(code, p, s) + } + break + } +} + +func setupShips(s Sea) { + placeShip(s, DESTROYER_LENGTH, 1) + placeShip(s, DESTROYER_LENGTH, 2) + placeShip(s, CRUISER_LENGTH, 3) + placeShip(s, CRUISER_LENGTH, 4) + placeShip(s, CARRIER_LENGTH, 5) + placeShip(s, CARRIER_LENGTH, 6) +} + +func printIntro() { + fmt.Println(" BATTLE") + fmt.Println("CREATIVE COMPUTING MORRISTOWN, NEW JERSEY") + fmt.Println() + fmt.Println("THE FOLLOWING CODE OF THE BAD GUYS' FLEET DISPOSITION") + fmt.Println("HAS BEEN CAPTURED BUT NOT DECODED: ") + fmt.Println() +} + +func printInstructions() { + fmt.Println() + fmt.Println() + fmt.Println("DE-CODE IT AND USE IT IF YOU CAN") + fmt.Println("BUT KEEP THE DE-CODING METHOD A SECRET.") + fmt.Println() + fmt.Println("START GAME") +} + +func printEncodedSea(s Sea) { + for x := 0; x < SEA_WIDTH; x++ { + fmt.Println() + for y := SEA_WIDTH - 1; y > -1; y-- { + fmt.Printf(" %d", s[y][x]) + } + } + fmt.Println() +} + +func wipeout(s Sea) bool { + for c := 1; c <= 7; c++ { + if hasShip(s, c) { + return false + } + } + return true +} + +func main() { + rand.Seed(time.Now().UnixNano()) + + s := NewSea() + + setupShips(s) + + printIntro() + + printEncodedSea(s) + + printInstructions() + + splashes := 0 + hits := 0 + + for { + target := getNextTarget(s) + targetValue := valueAt(target, s) + + if targetValue < 0 { + fmt.Printf("YOU ALREADY PUT A HOLE IN SHIP NUMBER %d AT THAT POINT.\n", targetValue) + } + + if targetValue <= 0 { + fmt.Println("SPLASH! TRY AGAIN.") + splashes += 1 + continue + } + + fmt.Printf("A DIRECT HIT ON SHIP NUMBER %d\n", targetValue) + hits += 1 + setValueAt(targetValue*-1, target, s) + + if !hasShip(s, targetValue) { + fmt.Println("AND YOU SUNK IT. HURRAH FOR THE GOOD GUYS.") + fmt.Println("SO FAR, THE BAD GUYS HAVE LOST") + fmt.Printf("%d DESTROYER(S), %d CRUISER(S), AND %d AIRCRAFT CARRIER(S).\n", countSunk(s, []int{1, 2}), countSunk(s, []int{3, 4}), countSunk(s, []int{5, 6})) + } + + if !wipeout(s) { + fmt.Printf("YOUR CURRENT SPLASH/HIT RATIO IS %2f\n", float32(splashes)/float32(hits)) + continue + } + + fmt.Printf("YOU HAVE TOTALLY WIPED OUT THE BAD GUYS' FLEET WITH A FINAL SPLASH/HIT RATIO OF %2f\n", float32(splashes)/float32(hits)) + + if splashes == 0 { + fmt.Println("CONGRATULATIONS -- A DIRECT HIT EVERY TIME.") + } + + fmt.Println("\n****************************") + break + } +} diff --git a/00_Alternate_Languages/11_Bombardment/go/main.go b/00_Alternate_Languages/11_Bombardment/go/main.go new file mode 100644 index 00000000..e69ff3ea --- /dev/null +++ b/00_Alternate_Languages/11_Bombardment/go/main.go @@ -0,0 +1,181 @@ +package main + +import ( + "bufio" + "fmt" + "math/rand" + "os" + "strconv" + "strings" + "time" +) + +// Messages correspond to outposts remaining (3, 2, 1, 0) +var PLAYER_PROGRESS_MESSAGES = []string{ + "YOU GOT ME, I'M GOING FAST. BUT I'LL GET YOU WHEN\nMY TRANSISTO&S RECUP%RA*E!", + "THREE DOWN, ONE TO GO.\n\n", + "TWO DOWN, TWO TO GO.\n\n", + "ONE DOWN, THREE TO GO.\n\n", +} + +var ENEMY_PROGRESS_MESSAGES = []string{ + "YOU'RE DEAD. YOUR LAST OUTPOST WAS AT %d. HA, HA, HA.\nBETTER LUCK NEXT TIME.", + "YOU HAVE ONLY ONE OUTPOST LEFT.\n\n", + "YOU HAVE ONLY TWO OUTPOSTS LEFT.\n\n", + "YOU HAVE ONLY THREE OUTPOSTS LEFT.\n\n", +} + +func displayField() { + for r := 0; r < 5; r++ { + initial := r*5 + 1 + for c := 0; c < 5; c++ { + //x := strconv.Itoa(initial + c) + fmt.Printf("\t%d", initial+c) + } + fmt.Println() + } + fmt.Print("\n\n\n\n\n\n\n\n\n") +} + +func printIntro() { + fmt.Println(" BOMBARDMENT") + fmt.Println(" CREATIVE COMPUTING MORRISTOWN, NEW JERSEY") + fmt.Println() + fmt.Println() + fmt.Println("YOU ARE ON A BATTLEFIELD WITH 4 PLATOONS AND YOU") + fmt.Println("HAVE 25 OUTPOSTS AVAILABLE WHERE THEY MAY BE PLACED.") + fmt.Println("YOU CAN ONLY PLACE ONE PLATOON AT ANY ONE OUTPOST.") + fmt.Println("THE COMPUTER DOES THE SAME WITH ITS FOUR PLATOONS.") + fmt.Println() + fmt.Println("THE OBJECT OF THE GAME IS TO FIRE MISSLES AT THE") + fmt.Println("OUTPOSTS OF THE COMPUTER. IT WILL DO THE SAME TO YOU.") + fmt.Println("THE ONE WHO DESTROYS ALL FOUR OF THE ENEMY'S PLATOONS") + fmt.Println("FIRST IS THE WINNER.") + fmt.Println() + fmt.Println("GOOD LUCK... AND TELL US WHERE YOU WANT THE BODIES SENT!") + fmt.Println() + fmt.Println("TEAR OFF MATRIX AND USE IT TO CHECK OFF THE NUMBERS.") + fmt.Print("\n\n\n\n") +} + +func positionList() []int { + positions := make([]int, 25) + for i := 0; i < 25; i++ { + positions[i] = i + 1 + } + return positions +} + +// Randomly choose 4 'positions' out of a range of 1 to 25 +func generateEnemyPositions() []int { + positions := positionList() + rand.Shuffle(len(positions), func(i, j int) { positions[i], positions[j] = positions[j], positions[i] }) + return positions[:4] +} + +func isValidPosition(p int) bool { + return p >= 1 && p <= 25 +} + +func promptForPlayerPositions() []int { + scanner := bufio.NewScanner(os.Stdin) + var positions []int + + for { + fmt.Println("\nWHAT ARE YOUR FOUR POSITIONS (1-25)?") + scanner.Scan() + rawPositions := strings.Split(scanner.Text(), " ") + + if len(rawPositions) != 4 { + fmt.Println("PLEASE ENTER FOUR UNIQUE POSITIONS") + goto there + } + + for _, p := range rawPositions { + pos, err := strconv.Atoi(p) + if (err != nil) || !isValidPosition(pos) { + fmt.Println("ALL POSITIONS MUST RANGE (1-25)") + goto there + } + positions = append(positions, pos) + } + if len(positions) == 4 { + return positions + } + + there: + } +} + +func promptPlayerForTarget() int { + scanner := bufio.NewScanner(os.Stdin) + + for { + fmt.Println("\nWHERE DO YOU WISH TO FIRE YOUR MISSILE?") + scanner.Scan() + target, err := strconv.Atoi(scanner.Text()) + + if (err != nil) || !isValidPosition(target) { + fmt.Println("POSITIONS MUST RANGE (1-25)") + continue + } + return target + } +} + +func generateAttackSequence() []int { + positions := positionList() + rand.Shuffle(len(positions), func(i, j int) { positions[i], positions[j] = positions[j], positions[i] }) + return positions +} + +// Performs attack procedure returning True if we are to continue. +func attack(target int, positions *[]int, hitMsg, missMsg string, progressMsg []string) bool { + for i := 0; i < len(*positions); i++ { + if target == (*positions)[i] { + fmt.Print(hitMsg) + + // remove the target just hit + (*positions)[i] = (*positions)[len((*positions))-1] + (*positions)[len((*positions))-1] = 0 + (*positions) = (*positions)[:len((*positions))-1] + + if len((*positions)) != 0 { + fmt.Print(progressMsg[len((*positions))]) + } else { + fmt.Printf(progressMsg[len((*positions))], target) + } + return len((*positions)) > 0 + } + } + fmt.Print(missMsg) + return len((*positions)) > 0 +} + +func main() { + rand.Seed(time.Now().UnixNano()) + + printIntro() + displayField() + + enemyPositions := generateEnemyPositions() + enemyAttacks := generateAttackSequence() + enemyAttackCounter := 0 + + playerPositions := promptForPlayerPositions() + + for { + // player attacks + if !attack(promptPlayerForTarget(), &enemyPositions, "YOU GOT ONE OF MY OUTPOSTS!\n\n", "HA, HA YOU MISSED. MY TURN NOW:\n\n", PLAYER_PROGRESS_MESSAGES) { + break + } + // computer attacks + hitMsg := fmt.Sprintf("I GOT YOU. IT WON'T BE LONG NOW. POST %d WAS HIT.\n", enemyAttacks[enemyAttackCounter]) + missMsg := fmt.Sprintf("I MISSED YOU, YOU DIRTY RAT. I PICKED %d. YOUR TURN:\n\n", enemyAttacks[enemyAttackCounter]) + if !attack(enemyAttacks[enemyAttackCounter], &playerPositions, hitMsg, missMsg, ENEMY_PROGRESS_MESSAGES) { + break + } + enemyAttackCounter += 1 + } + +} diff --git a/00_Alternate_Languages/12_Bombs_Away/go/main.go b/00_Alternate_Languages/12_Bombs_Away/go/main.go new file mode 100644 index 00000000..616f4451 --- /dev/null +++ b/00_Alternate_Languages/12_Bombs_Away/go/main.go @@ -0,0 +1,188 @@ +package main + +import ( + "bufio" + "fmt" + "math/rand" + "os" + "strconv" + "strings" + "time" +) + +type Choice struct { + idx string + msg string +} + +func playerSurvived() { + fmt.Println("YOU MADE IT THROUGH TREMENDOUS FLAK!!") +} + +func playerDeath() { + fmt.Println("* * * * BOOM * * * *") + fmt.Println("YOU HAVE BEEN SHOT DOWN.....") + fmt.Println("DEARLY BELOVED, WE ARE GATHERED HERE TODAY TO PAY OUR") + fmt.Println("LAST TRIBUTE...") +} + +func missionSuccess() { + fmt.Printf("DIRECT HIT!!!! %d KILLED.\n", int(100*rand.Int())) + fmt.Println("MISSION SUCCESSFUL.") +} + +// Takes a float between 0 and 1 and returns a boolean +// if the player has survived (based on random chance) +// Returns True if death, False if survived +func deathWithChance(probability float64) bool { + return probability > rand.Float64() +} + +func startNonKamikaziAttack() { + numMissions := getIntInput("HOW MANY MISSIONS HAVE YOU FLOWN? ") + + for numMissions > 160 { + fmt.Println("MISSIONS, NOT MILES...") + fmt.Println("150 MISSIONS IS HIGH EVEN FOR OLD-TIMERS") + numMissions = getIntInput("HOW MANY MISSIONS HAVE YOU FLOWN? ") + } + + if numMissions > 100 { + fmt.Println("THAT'S PUSHING THE ODDS!") + } + + if numMissions < 25 { + fmt.Println("FRESH OUT OF TRAINING, EH?") + } + + fmt.Println() + + if float32(numMissions) > (160 * rand.Float32()) { + missionSuccess() + } else { + missionFailure() + } +} + +func missionFailure() { + fmt.Printf("MISSED TARGET BY %d MILES!\n", int(2+30*rand.Float32())) + fmt.Println("NOW YOU'RE REALLY IN FOR IT !!") + fmt.Println() + + enemyWeapons := getInputFromList("DOES THE ENEMY HAVE GUNS(1), MISSILES(2), OR BOTH(3)? ", []Choice{{idx: "1", msg: "GUNS"}, {idx: "2", msg: "MISSILES"}, {idx: "3", msg: "BOTH"}}) + + // If there are no gunners (i.e. weapon choice 2) then + // we say that the gunners have 0 accuracy for the purposes + // of calculating probability of player death + enemyGunnerAccuracy := 0.0 + if enemyWeapons.idx != "2" { + enemyGunnerAccuracy = float64(getIntInput("WHAT'S THE PERCENT HIT RATE OF ENEMY GUNNERS (10 TO 50)? ")) + if enemyGunnerAccuracy < 10.0 { + fmt.Println("YOU LIE, BUT YOU'LL PAY...") + playerDeath() + } + } + + missileThreatWeighting := 35.0 + if enemyWeapons.idx == "1" { + missileThreatWeighting = 0 + } + + death := deathWithChance((enemyGunnerAccuracy + missileThreatWeighting) / 100) + + if death { + playerDeath() + } else { + playerSurvived() + } +} + +func playItaly() { + targets := []Choice{{idx: "1", msg: "SHOULD BE EASY -- YOU'RE FLYING A NAZI-MADE PLANE."}, {idx: "2", msg: "BE CAREFUL!!!"}, {idx: "3", msg: "YOU'RE GOING FOR THE OIL, EH?"}} + target := getInputFromList("YOUR TARGET -- ALBANIA(1), GREECE(2), NORTH AFRICA(3)", targets) + fmt.Println(target.msg) + startNonKamikaziAttack() +} + +func playAllies() { + aircraftMessages := []Choice{{idx: "1", msg: "YOU'VE GOT 2 TONS OF BOMBS FLYING FOR PLOESTI."}, {idx: "2", msg: "YOU'RE DUMPING THE A-BOMB ON HIROSHIMA."}, {idx: "3", msg: "YOU'RE CHASING THE BISMARK IN THE NORTH SEA."}, {idx: "4", msg: "YOU'RE BUSTING A GERMAN HEAVY WATER PLANT IN THE RUHR."}} + aircraft := getInputFromList("AIRCRAFT -- LIBERATOR(1), B-29(2), B-17(3), LANCASTER(4): ", aircraftMessages) + fmt.Println(aircraft.msg) + startNonKamikaziAttack() +} + +func playJapan() { + acknowledgeMessage := []Choice{{idx: "Y", msg: "Y"}, {idx: "N", msg: "N"}} + firstMission := getInputFromList("YOU'RE FLYING A KAMIKAZE MISSION OVER THE USS LEXINGTON.\nYOUR FIRST KAMIKAZE MISSION? (Y OR N): ", acknowledgeMessage) + if firstMission.msg == "N" { + playerDeath() + } + if rand.Float64() > 0.65 { + missionSuccess() + } else { + playerDeath() + } +} + +func playGermany() { + targets := []Choice{{idx: "1", msg: "YOU'RE NEARING STALINGRAD."}, {idx: "2", msg: "NEARING LONDON. BE CAREFUL, THEY'VE GOT RADAR."}, {idx: "3", msg: "NEARING VERSAILLES. DUCK SOUP. THEY'RE NEARLY DEFENSELESS."}} + target := getInputFromList("A NAZI, EH? OH WELL. ARE YOU GOING FOR RUSSIA(1),\nENGLAND(2), OR FRANCE(3)? ", targets) + fmt.Println(target.msg) + startNonKamikaziAttack() +} + +func playGame() { + fmt.Println("YOU ARE A PILOT IN A WORLD WAR II BOMBER.") + side := getInputFromList("WHAT SIDE -- ITALY(1), ALLIES(2), JAPAN(3), GERMANY(4): ", []Choice{{idx: "1", msg: "ITALY"}, {idx: "2", msg: "ALLIES"}, {idx: "3", msg: "JAPAN"}, {idx: "4", msg: "GERMANY"}}) + switch side.idx { + case "1": + playItaly() + case "2": + playAllies() + case "3": + playJapan() + case "4": + playGermany() + } +} + +func main() { + rand.Seed(time.Now().UnixNano()) + + for { + playGame() + if getInputFromList("ANOTHER MISSION (Y OR N):", []Choice{{idx: "Y", msg: "Y"}, {idx: "N", msg: "N"}}).msg == "N" { + break + } + } +} + +func getInputFromList(prompt string, choices []Choice) Choice { + scanner := bufio.NewScanner(os.Stdin) + for { + fmt.Println(prompt) + scanner.Scan() + choice := scanner.Text() + for _, c := range choices { + if strings.EqualFold(strings.ToUpper(choice), strings.ToUpper(c.idx)) { + return c + } + } + fmt.Println("TRY AGAIN...") + } +} + +func getIntInput(prompt string) int { + scanner := bufio.NewScanner(os.Stdin) + for { + fmt.Println(prompt) + scanner.Scan() + choice, err := strconv.Atoi(scanner.Text()) + if err != nil { + fmt.Println("TRY AGAIN...") + continue + } else { + return choice + } + } +} diff --git a/00_Alternate_Languages/20_Buzzword/go/main.go b/00_Alternate_Languages/20_Buzzword/go/main.go new file mode 100644 index 00000000..fdab9afa --- /dev/null +++ b/00_Alternate_Languages/20_Buzzword/go/main.go @@ -0,0 +1,91 @@ +package main + +import ( + "bufio" + "fmt" + "math/rand" + "os" + "strings" + "time" +) + +func main() { + rand.Seed(time.Now().UnixNano()) + words := [][]string{ + { + "Ability", + "Basal", + "Behavioral", + "Child-centered", + "Differentiated", + "Discovery", + "Flexible", + "Heterogeneous", + "Homogenous", + "Manipulative", + "Modular", + "Tavistock", + "Individualized", + }, { + "learning", + "evaluative", + "objective", + "cognitive", + "enrichment", + "scheduling", + "humanistic", + "integrated", + "non-graded", + "training", + "vertical age", + "motivational", + "creative", + }, { + "grouping", + "modification", + "accountability", + "process", + "core curriculum", + "algorithm", + "performance", + "reinforcement", + "open classroom", + "resource", + "structure", + "facility", + "environment", + }, + } + + scanner := bufio.NewScanner(os.Stdin) + + // Display intro text + fmt.Println("\n Buzzword Generator") + fmt.Println("Creative Computing Morristown, New Jersey") + fmt.Println("\n\n") + fmt.Println("This program prints highly acceptable phrases in") + fmt.Println("'educator-speak' that you can work into reports") + fmt.Println("and speeches. Whenever a question mark is printed,") + fmt.Println("type a 'Y' for another phrase or 'N' to quit.") + fmt.Println("\n\nHere's the first phrase:") + + for { + phrase := "" + for _, section := range words { + if len(phrase) > 0 { + phrase += " " + } + phrase += section[rand.Intn(len(section))] + } + fmt.Println(phrase) + fmt.Println() + + // continue? + fmt.Println("?") + scanner.Scan() + if strings.ToUpper(scanner.Text())[0:1] != "Y" { + break + } + } + fmt.Println("Come back when you need help with another report!") +} diff --git a/00_Alternate_Languages/22_Change/go/main.go b/00_Alternate_Languages/22_Change/go/main.go new file mode 100644 index 00000000..fabe4d4b --- /dev/null +++ b/00_Alternate_Languages/22_Change/go/main.go @@ -0,0 +1,115 @@ +package main + +import ( + "bufio" + "fmt" + "math" + "os" + "strconv" +) + +func printWelcome() { + fmt.Println(" CHANGE") + fmt.Println("CREATIVE COMPUTING MORRISTOWN, NEW JERSEY") + fmt.Println() + fmt.Println() + fmt.Println() + fmt.Println("I, YOUR FRIENDLY MICROCOMPUTER, WILL DETERMINE") + fmt.Println("THE CORRECT CHANGE FOR ITEMS COSTING UP TO $100.") + fmt.Println() +} + +func computeChange(cost, payment float64) { + change := int(math.Round((payment - cost) * 100)) + + if change == 0 { + fmt.Println("\nCORRECT AMOUNT, THANK YOU.") + return + } + + if change < 0 { + fmt.Printf("\nSORRY, YOU HAVE SHORT-CHANGED ME $%0.2f\n", float64(change)/-100.0) + print() + return + } + + fmt.Printf("\nYOUR CHANGE, $%0.2f:\n", float64(change)/100.0) + + d := change / 1000 + if d > 0 { + fmt.Printf(" %d TEN DOLLAR BILL(S)\n", d) + change -= d * 1000 + } + + d = change / 500 + if d > 0 { + fmt.Printf(" %d FIVE DOLLAR BILL(S)\n", d) + change -= d * 500 + } + + d = change / 100 + if d > 0 { + fmt.Printf(" %d ONE DOLLAR BILL(S)\n", d) + change -= d * 100 + } + + d = change / 50 + if d > 0 { + fmt.Println(" 1 HALF DOLLAR") + change -= d * 50 + } + + d = change / 25 + if d > 0 { + fmt.Printf(" %d QUARTER(S)\n", d) + change -= d * 25 + } + + d = change / 10 + if d > 0 { + fmt.Printf(" %d DIME(S)\n", d) + change -= d * 10 + } + + d = change / 5 + if d > 0 { + fmt.Printf(" %d NICKEL(S)\n", d) + change -= d * 5 + } + + if change > 0 { + fmt.Printf(" %d PENNY(S)\n", change) + } +} + +func main() { + scanner := bufio.NewScanner(os.Stdin) + + printWelcome() + + var cost, payment float64 + var err error + for { + fmt.Println("COST OF ITEM?") + scanner.Scan() + cost, err = strconv.ParseFloat(scanner.Text(), 64) + if err != nil || cost < 0.0 { + fmt.Println("INVALID INPUT. TRY AGAIN.") + continue + } + break + } + for { + fmt.Println("\nAMOUNT OF PAYMENT?") + scanner.Scan() + payment, err = strconv.ParseFloat(scanner.Text(), 64) + if err != nil { + fmt.Println("INVALID INPUT. TRY AGAIN.") + continue + } + break + } + + computeChange(cost, payment) + fmt.Println() +} diff --git a/00_Alternate_Languages/25_Chief/go/main.go b/00_Alternate_Languages/25_Chief/go/main.go new file mode 100644 index 00000000..19d3e97c --- /dev/null +++ b/00_Alternate_Languages/25_Chief/go/main.go @@ -0,0 +1,116 @@ +package main + +import ( + "bufio" + "fmt" + "os" + "strconv" + "strings" +) + +func printLightning() { + fmt.Println("************************************") + n := 24 + for n > 16 { + var b strings.Builder + b.Grow(n + 3) + for i := 0; i < n; i++ { + b.WriteString(" ") + } + b.WriteString("x x") + fmt.Println(b.String()) + n-- + } + fmt.Println(" x xxx") + fmt.Println(" x x") + fmt.Println(" xx xx") + n-- + for n > 8 { + var b strings.Builder + b.Grow(n + 3) + for i := 0; i < n; i++ { + b.WriteString(" ") + } + b.WriteString("x x") + fmt.Println(b.String()) + n-- + } + fmt.Println(" xx") + fmt.Println(" x") + fmt.Println("************************************") +} + +func printSolution(n float64) { + fmt.Printf("\n%f plus 3 gives %f. This divided by 5 equals %f\n", n, n+3, (n+3)/5) + fmt.Printf("This times 8 gives %f. If we divide 5 and add 5.\n", ((n+3)/5)*8) + fmt.Printf("We get %f, which, minus 1 equals %f\n", (((n+3)/5)*8)/5+5, ((((n+3)/5)*8)/5+5)-1) +} + +func play() { + fmt.Println("\nTake a Number and ADD 3. Now, Divide this number by 5 and") + fmt.Println("multiply by 8. Now, Divide by 5 and add the same. Subtract 1") + + youHave := getFloat("\nWhat do you have?") + compGuess := (((youHave-4)*5)/8)*5 - 3 + if getYesNo(fmt.Sprintf("\nI bet your number was %f was I right(Yes or No)? ", compGuess)) { + fmt.Println("\nHuh, I knew I was unbeatable") + fmt.Println("And here is how i did it") + printSolution(compGuess) + } else { + originalNumber := getFloat("\nHUH!! what was you original number? ") + if originalNumber == compGuess { + fmt.Println("\nThat was my guess, AHA i was right") + fmt.Println("Shamed to accept defeat i guess, don't worry you can master mathematics too") + fmt.Println("Here is how i did it") + printSolution(compGuess) + } else { + fmt.Println("\nSo you think you're so smart, EH?") + fmt.Println("Now, Watch") + printSolution(originalNumber) + + if getYesNo("\nNow do you believe me? ") { + print("\nOk, Lets play again sometime bye!!!!") + } else { + fmt.Println("\nYOU HAVE MADE ME VERY MAD!!!!!") + fmt.Println("BY THE WRATH OF THE MATHEMATICS AND THE RAGE OF THE GODS") + fmt.Println("THERE SHALL BE LIGHTNING!!!!!!!") + printLightning() + fmt.Println("\nI Hope you believe me now, for your own sake") + } + } + } +} + +func getFloat(prompt string) float64 { + scanner := bufio.NewScanner(os.Stdin) + + for { + fmt.Println(prompt) + scanner.Scan() + val, err := strconv.ParseFloat(scanner.Text(), 64) + if err != nil { + fmt.Println("INVALID INPUT, TRY AGAIN") + continue + } + return val + } +} + +func getYesNo(prompt string) bool { + scanner := bufio.NewScanner(os.Stdin) + fmt.Println(prompt) + scanner.Scan() + + return (strings.ToUpper(scanner.Text())[0:1] == "Y") + +} + +func main() { + fmt.Println("I am CHIEF NUMBERS FREEK, The GREAT INDIAN MATH GOD.") + + if getYesNo("\nAre you ready to take the test you called me out for(Yes or No)? ") { + play() + } else { + fmt.Println("Ok, Nevermind. Let me go back to my great slumber, Bye") + } +} diff --git a/00_Alternate_Languages/31_Depth_Charge/go/main.go b/00_Alternate_Languages/31_Depth_Charge/go/main.go new file mode 100644 index 00000000..0ab3f876 --- /dev/null +++ b/00_Alternate_Languages/31_Depth_Charge/go/main.go @@ -0,0 +1,156 @@ +package main + +import ( + "bufio" + "fmt" + "math" + "math/rand" + "os" + "strconv" + "strings" + "time" +) + +type Position []int + +func NewPosition() Position { + p := make([]int, 3) + return Position(p) +} + +func showWelcome() { + fmt.Print("\033[H\033[2J") + fmt.Println(" DEPTH CHARGE") + fmt.Println(" Creative Computing Morristown, New Jersey") + fmt.Println() +} + +func getNumCharges() (int, int) { + scanner := bufio.NewScanner(os.Stdin) + + for { + fmt.Println("Dimensions of search area?") + scanner.Scan() + dim, err := strconv.Atoi(scanner.Text()) + if err != nil { + fmt.Println("Must enter an integer number. Please try again...") + continue + } + return dim, int(math.Log2(float64(dim))) + 1 + } +} + +func askForNewGame() { + scanner := bufio.NewScanner(os.Stdin) + + fmt.Println("Another game (Y or N): ") + scanner.Scan() + if strings.ToUpper(scanner.Text()) == "Y" { + main() + } + fmt.Println("OK. Hope you enjoyed yourself") + os.Exit(1) +} + +func showShotResult(shot, location Position) { + result := "Sonar reports shot was " + + if shot[1] > location[1] { // y-direction + result += "north" + } else if shot[1] < location[1] { // y-direction + result += "south" + } + + if shot[0] > location[0] { // x-direction + result += "east" + } else if shot[0] < location[0] { // x-direction + result += "west" + } + + if shot[1] != location[1] || shot[0] != location[0] { + result += " and " + } + if shot[2] > location[2] { + result += "too low." + } else if shot[2] < location[2] { + result += "too high." + } else { + result += "depth OK." + } + + fmt.Println(result) +} + +func getShot() Position { + scanner := bufio.NewScanner(os.Stdin) + + for { + shotPos := NewPosition() + fmt.Println("Enter coordinates: ") + scanner.Scan() + rawGuess := strings.Split(scanner.Text(), " ") + if len(rawGuess) != 3 { + goto there + } + for i := 0; i < 3; i++ { + val, err := strconv.Atoi(rawGuess[i]) + if err != nil { + goto there + } + shotPos[i] = val + } + return shotPos + there: + fmt.Println("Please enter coordinates separated by spaces") + fmt.Println("Example: 3 2 1") + } +} + +func getRandomPosition(searchArea int) Position { + pos := NewPosition() + for i := 0; i < 3; i++ { + pos[i] = rand.Intn(searchArea) + } + return pos +} + +func playGame(searchArea, numCharges int) { + rand.Seed(time.Now().UTC().UnixNano()) + fmt.Println("\nYou are the captain of the destroyer USS Computer.") + fmt.Println("An enemy sub has been causing you trouble. Your") + fmt.Printf("mission is to destroy it. You have %d shots.\n", numCharges) + fmt.Println("Specify depth charge explosion point with a") + fmt.Println("trio of numbers -- the first two are the") + fmt.Println("surface coordinates; the third is the depth.") + fmt.Println("\nGood luck!") + fmt.Println() + + subPos := getRandomPosition(searchArea) + + for c := 0; c < numCharges; c++ { + fmt.Printf("\nTrial #%d\n", c+1) + + shot := getShot() + + if shot[0] == subPos[0] && shot[1] == subPos[1] && shot[2] == subPos[2] { + fmt.Printf("\nB O O M ! ! You found it in %d tries!\n", c+1) + askForNewGame() + } else { + showShotResult(shot, subPos) + } + } + + // out of depth charges + fmt.Println("\nYou have been torpedoed! Abandon ship!") + fmt.Printf("The submarine was at %d %d %d\n", subPos[0], subPos[1], subPos[2]) + askForNewGame() + +} + +func main() { + showWelcome() + + searchArea, numCharges := getNumCharges() + + playGame(searchArea, numCharges) +} diff --git a/00_Alternate_Languages/33_Dice/Julia/Dice.jl b/00_Alternate_Languages/33_Dice/Julia/Dice.jl new file mode 100644 index 00000000..8cc98292 --- /dev/null +++ b/00_Alternate_Languages/33_Dice/Julia/Dice.jl @@ -0,0 +1,75 @@ +#= +Port of Dice from BASIC Computer Games (1978) + +This "game" simulates a given number of dice rolls, and returns the +count for each possible total. + +The only change that has been made from the original program, is that +when asking if the user wants to play again, any string starting with +y or Y will be accepted, instead of only YES. +=# + +function main() + # Array to store the counts for each total. + # There are 11 possible totals. + counts = [0 for i in 1:11] + + # Print intro text + println("\n Dice") + println("Creative Computing Morristown, New Jersey") + println("\n\n") + println("This program simulates the rolling of a") + println("pair of dice.") + println("You enter the number of times you want the computer to") + println("'roll' the dice. Watch out, very large numbers take") + println("a long time. In particular, numbers over 5000.") + + still_playing = true + while still_playing + println() + print("How many rolls? ") + + # Get user input for number of dice rolls + rolls = readline() + rolls = parse(Int64, rolls) + + # Roll dice the specified number of times and update total count + for _ in 1:rolls + dice_roll = rand(1:6, 2) + dice_sum = sum(dice_roll) + + # The index is one less than the sum, as a sum of 1 is impossible, + # the array will only have 11 values + counts[dice_sum-1] += 1 + end + + # Display results + println("\nTotal Spots Number of Times") + for i in 1:8 + print(" ") + print(i+1) + print(" ") + println(counts[i]) + end + for i in 9:11 + print(" ") + print(i+1) + print(" ") + println(counts[i]) + end + + # Ask try again + print("\nTry Again? ") + input = readline() + if length(input) > 0 && uppercase(input)[1] == 'Y' + # If game is continued, resets total counts + counts = [0 for i in 1:11] + else + still_playing = false + end + end +end + +if abspath(PROGRAM_FILE) == @__FILE__ + main() +end \ No newline at end of file diff --git a/00_Alternate_Languages/33_Dice/Julia/README.md b/00_Alternate_Languages/33_Dice/Julia/README.md new file mode 100644 index 00000000..fb77771d --- /dev/null +++ b/00_Alternate_Languages/33_Dice/Julia/README.md @@ -0,0 +1,3 @@ +Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html) + +Conversion to [Python](https://www.julialang.org/) \ No newline at end of file diff --git a/00_Alternate_Languages/33_Dice/go/main.go b/00_Alternate_Languages/33_Dice/go/main.go new file mode 100644 index 00000000..75bef387 --- /dev/null +++ b/00_Alternate_Languages/33_Dice/go/main.go @@ -0,0 +1,64 @@ +package main + +import ( + "bufio" + "fmt" + "math/rand" + "os" + "strconv" + "strings" +) + +func printWelcome() { + fmt.Println("\n Dice") + fmt.Println("Creative Computing Morristown, New Jersey") + fmt.Println() + fmt.Println() + fmt.Println("This program simulates the rolling of a") + fmt.Println("pair of dice.") + fmt.Println("You enter the number of times you want the computer to") + fmt.Println("'roll' the dice. Watch out, very large numbers take") + fmt.Println("a long time. In particular, numbers over 5000.") + fmt.Println() +} + +func main() { + printWelcome() + scanner := bufio.NewScanner(os.Stdin) + + for { + fmt.Println("\nHow many rolls? ") + scanner.Scan() + numRolls, err := strconv.Atoi(scanner.Text()) + if err != nil { + fmt.Println("Invalid input, try again...") + continue + } + + // We'll track counts of roll outcomes in a 13-element list. + // The first two indices (0 & 1) are ignored, leaving just + // the indices that match the roll values (2 through 12). + results := make([]int, 13) + + for n := 0; n < numRolls; n++ { + d1 := rand.Intn(6) + 1 + d2 := rand.Intn(6) + 1 + results[d1+d2] += 1 + } + + // Display final results + fmt.Println("\nTotal Spots Number of Times") + for i := 2; i < 13; i++ { + fmt.Printf(" %-14d%d\n", i, results[i]) + } + + fmt.Println("\nTry again? ") + scanner.Scan() + if strings.ToUpper(scanner.Text()) == "Y" { + continue + } else { + os.Exit(1) + } + + } +} diff --git a/00_Alternate_Languages/34_Digits/go/main.go b/00_Alternate_Languages/34_Digits/go/main.go new file mode 100644 index 00000000..64326d53 --- /dev/null +++ b/00_Alternate_Languages/34_Digits/go/main.go @@ -0,0 +1,171 @@ +package main + +import ( + "bufio" + "fmt" + "math/rand" + "os" + "strconv" + "time" +) + +func printIntro() { + fmt.Println(" DIGITS") + fmt.Println(" CREATIVE COMPUTING MORRISTOWN, NEW JERSEY") + fmt.Println() + fmt.Println() + fmt.Println("THIS IS A GAME OF GUESSING.") +} + +func readInteger(prompt string) int { + scanner := bufio.NewScanner(os.Stdin) + for { + fmt.Println(prompt) + scanner.Scan() + response, err := strconv.Atoi(scanner.Text()) + + if err != nil { + fmt.Println("INVALID INPUT, TRY AGAIN... ") + continue + } + + return response + } +} + +func printInstructions() { + fmt.Println() + fmt.Println("PLEASE TAKE A PIECE OF PAPER AND WRITE DOWN") + fmt.Println("THE DIGITS '0', '1', OR '2' THIRTY TIMES AT RANDOM.") + fmt.Println("ARRANGE THEM IN THREE LINES OF TEN DIGITS EACH.") + fmt.Println("I WILL ASK FOR THEN TEN AT A TIME.") + fmt.Println("I WILL ALWAYS GUESS THEM FIRST AND THEN LOOK AT YOUR") + fmt.Println("NEXT NUMBER TO SEE IF I WAS RIGHT. BY PURE LUCK,") + fmt.Println("I OUGHT TO BE RIGHT TEN TIMES. BUT I HOPE TO DO BETTER") + fmt.Println("THAN THAT *****") + fmt.Println() +} + +func readTenNumbers() []int { + numbers := make([]int, 10) + + numbers[0] = readInteger("FIRST NUMBER: ") + for i := 1; i < 10; i++ { + numbers[i] = readInteger("NEXT NUMBER:") + } + + return numbers +} + +func printSummary(correct int) { + fmt.Println() + + if correct > 10 { + fmt.Println() + fmt.Println("I GUESSED MORE THAN 1/3 OF YOUR NUMBERS.") + fmt.Println("I WIN.\u0007") + } else if correct < 10 { + fmt.Println("I GUESSED LESS THAN 1/3 OF YOUR NUMBERS.") + fmt.Println("YOU BEAT ME. CONGRATULATIONS *****") + } else { + fmt.Println("I GUESSED EXACTLY 1/3 OF YOUR NUMBERS.") + fmt.Println("IT'S A TIE GAME.") + } +} + +func buildArray(val, row, col int) [][]int { + a := make([][]int, row) + for r := 0; r < row; r++ { + b := make([]int, col) + for c := 0; c < col; c++ { + b[c] = val + } + a[r] = b + } + return a +} + +func main() { + rand.Seed(time.Now().UnixNano()) + + printIntro() + if readInteger("FOR INSTRUCTIONS, TYPE '1', ELSE TYPE '0' ? ") == 1 { + printInstructions() + } + + a := 0 + b := 1 + c := 3 + + m := buildArray(1, 27, 3) + k := buildArray(9, 3, 3) + l := buildArray(3, 9, 3) + + for { + l[0][0] = 2 + l[4][1] = 2 + l[8][2] = 2 + + z := float64(26) + z1 := float64(8) + z2 := 2 + runningCorrect := 0 + + var numbers []int + for round := 1; round <= 4; round++ { + validNumbers := false + for !validNumbers { + numbers = readTenNumbers() + validNumbers = true + for _, n := range numbers { + if n < 0 || n > 2 { + fmt.Println("ONLY USE THE DIGITS '0', '1', OR '2'.") + fmt.Println("LET'S TRY AGAIN.") + validNumbers = false + break + } + } + } + + fmt.Printf("\n%-14s%-14s%-14s%-14s\n", "MY GUESS", "YOUR NO.", "RESULT", "NO. RIGHT") + + for _, n := range numbers { + s := 0 + myGuess := 0 + + for j := 0; j < 3; j++ { + s1 := a*k[z2][j] + b*l[int(z1)][j] + c*m[int(z)][j] + + if s < s1 { + s = s1 + myGuess = j + } else if s1 == s && rand.Float64() > 0.5 { + myGuess = j + } + } + result := "" + + if myGuess != n { + result = "WRONG" + } else { + runningCorrect += 1 + result = "RIGHT" + m[int(z)][n] = m[int(z)][n] + 1 + l[int(z1)][n] = l[int(z1)][n] + 1 + k[int(z2)][n] = k[int(z2)][n] + 1 + z = z - (z/9)*9 + z = 3.0*z + float64(n) + } + fmt.Printf("\n%-14d%-14d%-14s%-14d\n", myGuess, n, result, runningCorrect) + + z1 = z - (z/9)*9 + z2 = n + } + printSummary(runningCorrect) + if readInteger("\nDO YOU WANT TO TRY AGAIN (1 FOR YES, 0 FOR NO) ? ") != 1 { + fmt.Println("\nTHANKS FOR THE GAME.") + os.Exit(0) + } + } + } +} diff --git a/00_Alternate_Languages/35_Even_Wins/go/evenwins.go b/00_Alternate_Languages/35_Even_Wins/go/evenwins.go new file mode 100644 index 00000000..7c65aacc --- /dev/null +++ b/00_Alternate_Languages/35_Even_Wins/go/evenwins.go @@ -0,0 +1,197 @@ +package main + +import ( + "bufio" + "fmt" + "os" + "strconv" + "strings" +) + +const MAXTAKE = 4 + +type PlayerType int8 + +const ( + HUMAN PlayerType = iota + COMPUTER +) + +type Game struct { + table int + human int + computer int +} + +func NewGame() Game { + g := Game{} + g.table = 27 + + return g +} + +func printIntro() { + fmt.Println("Welcome to Even Wins!") + fmt.Println("Based on evenwins.bas from Creative Computing") + fmt.Println() + fmt.Println("Even Wins is a two-person game. You start with") + fmt.Println("27 marbles in the middle of the table.") + fmt.Println() + fmt.Println("Players alternate taking marbles from the middle.") + fmt.Println("A player can take 1 to 4 marbles on their turn, and") + fmt.Println("turns cannot be skipped. The game ends when there are") + fmt.Println("no marbles left, and the winner is the one with an even") + fmt.Println("number of marbles.") + fmt.Println() +} + +func (g *Game) printBoard() { + fmt.Println() + fmt.Printf(" marbles in the middle: %d\n", g.table) + fmt.Printf(" # marbles you have: %d\n", g.human) + fmt.Printf("# marbles computer has: %d\n", g.computer) + fmt.Println() +} + +func (g *Game) gameOver() { + fmt.Println() + fmt.Println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") + fmt.Println("!! All the marbles are taken: Game Over!") + fmt.Println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") + fmt.Println() + g.printBoard() + if g.human%2 == 0 { + fmt.Println("You are the winner! Congratulations!") + } else { + fmt.Println("The computer wins: all hail mighty silicon!") + } + fmt.Println() +} + +func getPlural(count int) string { + m := "marble" + if count > 1 { + m += "s" + } + return m +} + +func (g *Game) humanTurn() { + scanner := bufio.NewScanner(os.Stdin) + maxAvailable := MAXTAKE + if g.table < MAXTAKE { + maxAvailable = g.table + } + + fmt.Println("It's your turn!") + for { + fmt.Printf("Marbles to take? (1 - %d) --> ", maxAvailable) + scanner.Scan() + n, err := strconv.Atoi(scanner.Text()) + if err != nil { + fmt.Printf("\n Please enter a whole number from 1 to %d\n", maxAvailable) + continue + } + if n < 1 { + fmt.Println("\n You must take at least 1 marble!") + continue + } + if n > maxAvailable { + fmt.Printf("\n You can take at most %d %s\n", maxAvailable, getPlural(maxAvailable)) + continue + } + fmt.Printf("\nOkay, taking %d %s ...\n", n, getPlural(n)) + g.table -= n + g.human += n + return + } +} + +func (g *Game) computerTurn() { + marblesToTake := 0 + + fmt.Println("It's the computer's turn ...") + r := float64(g.table - 6*int((g.table)/6)) + + if int(g.human/2) == g.human/2 { + if r < 1.5 || r > 5.3 { + marblesToTake = 1 + } else { + marblesToTake = int(r - 1) + } + } else if float64(g.table) < 4.2 { + marblesToTake = 4 + } else if r > 3.4 { + if r < 4.7 || r > 3.5 { + marblesToTake = 4 + } + } else { + marblesToTake = int(r + 1) + } + + fmt.Printf("Computer takes %d %s ...\n", marblesToTake, getPlural(marblesToTake)) + g.table -= marblesToTake + g.computer += marblesToTake +} + +func (g *Game) play(playersTurn PlayerType) { + g.printBoard() + + for { + if g.table == 0 { + g.gameOver() + return + } else if playersTurn == HUMAN { + g.humanTurn() + g.printBoard() + playersTurn = COMPUTER + } else { + g.computerTurn() + g.printBoard() + playersTurn = HUMAN + } + } +} + +func getFirstPlayer() PlayerType { + scanner := bufio.NewScanner(os.Stdin) + + for { + fmt.Println("Do you want to play first? (y/n) --> ") + scanner.Scan() + + if strings.ToUpper(scanner.Text()) == "Y" { + return HUMAN + } else if strings.ToUpper(scanner.Text()) == "N" { + return COMPUTER + } else { + fmt.Println() + fmt.Println("Please enter 'y' if you want to play first,") + fmt.Println("or 'n' if you want to play second.") + fmt.Println() + } + } +} + +func main() { + scanner := bufio.NewScanner(os.Stdin) + + printIntro() + + for { + g := NewGame() + + g.play(getFirstPlayer()) + + fmt.Println("\nWould you like to play again? (y/n) --> ") + scanner.Scan() + if strings.ToUpper(scanner.Text()) == "Y" { + fmt.Println("\nOk, let's play again ...") + } else { + fmt.Println("\nOk, thanks for playing ... goodbye!") + return + } + + } + +} diff --git a/00_Alternate_Languages/38_Fur_Trader/go/main.go b/00_Alternate_Languages/38_Fur_Trader/go/main.go new file mode 100644 index 00000000..642d48f0 --- /dev/null +++ b/00_Alternate_Languages/38_Fur_Trader/go/main.go @@ -0,0 +1,326 @@ +package main + +import ( + "bufio" + "fmt" + "log" + "math/rand" + "os" + "strconv" + "strings" + "time" +) + +const ( + MAXFURS = 190 + STARTFUNDS = 600 +) + +type Fur int8 + +const ( + FUR_MINK Fur = iota + FUR_BEAVER + FUR_ERMINE + FUR_FOX +) + +type Fort int8 + +const ( + FORT_MONTREAL Fort = iota + FORT_QUEBEC + FORT_NEWYORK +) + +type GameState int8 + +const ( + STARTING GameState = iota + TRADING + CHOOSINGFORT + TRAVELLING +) + +func FURS() []string { + return []string{"MINK", "BEAVER", "ERMINE", "FOX"} +} + +func FORTS() []string { + return []string{"HOCHELAGA (MONTREAL)", "STADACONA (QUEBEC)", "NEW YORK"} +} + +type Player struct { + funds float32 + furs []int +} + +func NewPlayer() Player { + p := Player{} + p.funds = STARTFUNDS + p.furs = make([]int, 4) + return p +} + +func (p *Player) totalFurs() int { + f := 0 + for _, v := range p.furs { + f += v + } + return f +} + +func (p *Player) lostFurs() { + for f := 0; f < len(p.furs); f++ { + p.furs[f] = 0 + } +} + +func printTitle() { + fmt.Println(" FUR TRADER") + fmt.Println(" CREATIVE COMPUTING MORRISTOWN, NEW JERSEY") + fmt.Println() + fmt.Println() + fmt.Println() +} + +func printIntro() { + fmt.Println("YOU ARE THE LEADER OF A FRENCH FUR TRADING EXPEDITION IN ") + fmt.Println("1776 LEAVING THE LAKE ONTARIO AREA TO SELL FURS AND GET") + fmt.Println("SUPPLIES FOR THE NEXT YEAR. YOU HAVE A CHOICE OF THREE") + fmt.Println("FORTS AT WHICH YOU MAY TRADE. THE COST OF SUPPLIES") + fmt.Println("AND THE AMOUNT YOU RECEIVE FOR YOUR FURS WILL DEPEND") + fmt.Println("ON THE FORT THAT YOU CHOOSE.") + fmt.Println() +} + +func getFortChoice() Fort { + scanner := bufio.NewScanner(os.Stdin) + + for { + fmt.Println() + fmt.Println("YOU MAY TRADE YOUR FURS AT FORT 1, FORT 2,") + fmt.Println("OR FORT 3. FORT 1 IS FORT HOCHELAGA (MONTREAL)") + fmt.Println("AND IS UNDER THE PROTECTION OF THE FRENCH ARMY.") + fmt.Println("FORT 2 IS FORT STADACONA (QUEBEC) AND IS UNDER THE") + fmt.Println("PROTECTION OF THE FRENCH ARMY. HOWEVER, YOU MUST") + fmt.Println("MAKE A PORTAGE AND CROSS THE LACHINE RAPIDS.") + fmt.Println("FORT 3 IS FORT NEW YORK AND IS UNDER DUTCH CONTROL.") + fmt.Println("YOU MUST CROSS THROUGH IROQUOIS LAND.") + fmt.Println("ANSWER 1, 2, OR 3.") + fmt.Print(">> ") + scanner.Scan() + + f, err := strconv.Atoi(scanner.Text()) + if err != nil || f < 1 || f > 3 { + fmt.Println("Invalid input, Try again ... ") + continue + } + return Fort(f) + } +} + +func printFortComment(f Fort) { + fmt.Println() + switch f { + case FORT_MONTREAL: + fmt.Println("YOU HAVE CHOSEN THE EASIEST ROUTE. HOWEVER, THE FORT") + fmt.Println("IS FAR FROM ANY SEAPORT. THE VALUE") + fmt.Println("YOU RECEIVE FOR YOUR FURS WILL BE LOW AND THE COST") + fmt.Println("OF SUPPLIES HIGHER THAN AT FORTS STADACONA OR NEW YORK.") + case FORT_QUEBEC: + fmt.Println("YOU HAVE CHOSEN A HARD ROUTE. IT IS, IN COMPARSION,") + fmt.Println("HARDER THAN THE ROUTE TO HOCHELAGA BUT EASIER THAN") + fmt.Println("THE ROUTE TO NEW YORK. YOU WILL RECEIVE AN AVERAGE VALUE") + fmt.Println("FOR YOUR FURS AND THE COST OF YOUR SUPPLIES WILL BE AVERAGE.") + case FORT_NEWYORK: + fmt.Println("YOU HAVE CHOSEN THE MOST DIFFICULT ROUTE. AT") + fmt.Println("FORT NEW YORK YOU WILL RECEIVE THE HIGHEST VALUE") + fmt.Println("FOR YOUR FURS. THE COST OF YOUR SUPPLIES") + fmt.Println("WILL BE LOWER THAN AT ALL THE OTHER FORTS.") + } + fmt.Println() +} + +func getYesOrNo() string { + scanner := bufio.NewScanner(os.Stdin) + for { + fmt.Println("ANSWER YES OR NO") + scanner.Scan() + if strings.ToUpper(scanner.Text())[0:1] == "Y" { + return "Y" + } else if strings.ToUpper(scanner.Text())[0:1] == "N" { + return "N" + } + } +} + +func getFursPurchase() []int { + scanner := bufio.NewScanner(os.Stdin) + fmt.Printf("YOUR %d FURS ARE DISTRIBUTED AMONG THE FOLLOWING\n", MAXFURS) + fmt.Println("KINDS OF PELTS: MINK, BEAVER, ERMINE AND FOX.") + fmt.Println() + + purchases := make([]int, 4) + + for i, f := range FURS() { + retry: + fmt.Printf("HOW MANY %s DO YOU HAVE: ", f) + scanner.Scan() + count, err := strconv.Atoi(scanner.Text()) + if err != nil { + fmt.Println("INVALID INPUT, TRY AGAIN ...") + goto retry + } + purchases[i] = count + } + + return purchases +} + +func main() { + rand.Seed(time.Now().UnixNano()) + + printTitle() + + gameState := STARTING + whichFort := FORT_NEWYORK + var ( + minkPrice int + erminePrice int + beaverPrice int + foxPrice int + ) + player := NewPlayer() + + for { + switch gameState { + case STARTING: + printIntro() + fmt.Println("DO YOU WISH TO TRADE FURS?") + if getYesOrNo() == "N" { + os.Exit(0) + } + gameState = TRADING + case TRADING: + fmt.Println() + fmt.Printf("YOU HAVE $ %1.2f IN SAVINGS\n", player.funds) + fmt.Printf("AND %d FURS TO BEGIN THE EXPEDITION\n", MAXFURS) + player.furs = getFursPurchase() + + if player.totalFurs() > MAXFURS { + fmt.Println() + fmt.Println("YOU MAY NOT HAVE THAT MANY FURS.") + fmt.Println("DO NOT TRY TO CHEAT. I CAN ADD.") + fmt.Println("YOU MUST START AGAIN.") + gameState = STARTING + } else { + gameState = CHOOSINGFORT + } + case CHOOSINGFORT: + whichFort = getFortChoice() + printFortComment(whichFort) + fmt.Println("DO YOU WANT TO TRADE AT ANOTHER FORT?") + changeFort := getYesOrNo() + if changeFort == "N" { + gameState = TRAVELLING + } + case TRAVELLING: + switch whichFort { + case FORT_MONTREAL: + minkPrice = (int((0.2*rand.Float64()+0.70)*100+0.5) / 100) + erminePrice = (int((0.2*rand.Float64()+0.65)*100+0.5) / 100) + beaverPrice = (int((0.2*rand.Float64()+0.75)*100+0.5) / 100) + foxPrice = (int((0.2*rand.Float64()+0.80)*100+0.5) / 100) + + fmt.Println("SUPPLIES AT FORT HOCHELAGA COST $150.00.") + fmt.Println("YOUR TRAVEL EXPENSES TO HOCHELAGA WERE $10.00.") + player.funds -= 160 + case FORT_QUEBEC: + minkPrice = (int((0.30*rand.Float64()+0.85)*100+0.5) / 100) + erminePrice = (int((0.15*rand.Float64()+0.80)*100+0.5) / 100) + beaverPrice = (int((0.20*rand.Float64()+0.90)*100+0.5) / 100) + foxPrice = (int((0.25*rand.Float64()+1.10)*100+0.5) / 100) + + event := int(10*rand.Float64()) + 1 + if event <= 2 { + fmt.Println("YOUR BEAVER WERE TOO HEAVY TO CARRY ACROSS") + fmt.Println("THE PORTAGE. YOU HAD TO LEAVE THE PELTS, BUT FOUND") + fmt.Println("THEM STOLEN WHEN YOU RETURNED.") + player.furs[FUR_BEAVER] = 0 + } else if event <= 6 { + fmt.Println("YOU ARRIVED SAFELY AT FORT STADACONA.") + } else if event <= 8 { + fmt.Println("YOUR CANOE UPSET IN THE LACHINE RAPIDS. YOU") + fmt.Println("LOST ALL YOUR FURS.") + player.lostFurs() + } else if event <= 10 { + fmt.Println("YOUR FOX PELTS WERE NOT CURED PROPERLY.") + fmt.Println("NO ONE WILL BUY THEM.") + player.furs[FUR_FOX] = 0 + } else { + log.Fatal("Unexpected error") + } + + fmt.Println() + fmt.Println("SUPPLIES AT FORT STADACONA COST $125.00.") + fmt.Println("YOUR TRAVEL EXPENSES TO STADACONA WERE $15.00.") + player.funds -= 140 + case FORT_NEWYORK: + minkPrice = (int((0.15*rand.Float64()+1.05)*100+0.5) / 100) + erminePrice = (int((0.15*rand.Float64()+0.95)*100+0.5) / 100) + beaverPrice = (int((0.25*rand.Float64()+1.00)*100+0.5) / 100) + foxPrice = (int((0.25*rand.Float64()+1.05)*100+0.5) / 100) // not in original code + + event := int(10*rand.Float64()) + 1 + if event <= 2 { + fmt.Println("YOU WERE ATTACKED BY A PARTY OF IROQUOIS.") + fmt.Println("ALL PEOPLE IN YOUR TRADING GROUP WERE") + fmt.Println("KILLED. THIS ENDS THE GAME.") + os.Exit(0) + } else if event <= 6 { + fmt.Println("YOU WERE LUCKY. YOU ARRIVED SAFELY") + fmt.Println("AT FORT NEW YORK.") + } else if event <= 8 { + fmt.Println("YOU NARROWLY ESCAPED AN IROQUOIS RAIDING PARTY.") + fmt.Println("HOWEVER, YOU HAD TO LEAVE ALL YOUR FURS BEHIND.") + player.lostFurs() + } else if event <= 10 { + minkPrice /= 2 + foxPrice /= 2 + fmt.Println("YOUR MINK AND BEAVER WERE DAMAGED ON YOUR TRIP.") + fmt.Println("YOU RECEIVE ONLY HALF THE CURRENT PRICE FOR THESE FURS.") + } else { + log.Fatal("Unexpected error") + } + + fmt.Println() + fmt.Println("SUPPLIES AT NEW YORK COST $85.00.") + fmt.Println("YOUR TRAVEL EXPENSES TO NEW YORK WERE $25.00.") + player.funds -= 110 + } + + beaverValue := beaverPrice * player.furs[FUR_BEAVER] + foxValue := foxPrice * player.furs[FUR_FOX] + ermineValue := erminePrice * player.furs[FUR_ERMINE] + minkValue := minkPrice * player.furs[FUR_MINK] + + fmt.Println() + fmt.Printf("YOUR BEAVER SOLD FOR $%6.2f\n", float64(beaverValue)) + fmt.Printf("YOUR FOX SOLD FOR $%6.2f\n", float64(foxValue)) + fmt.Printf("YOUR ERMINE SOLD FOR $%6.2f\n", float64(ermineValue)) + fmt.Printf("YOUR MINK SOLD FOR $%6.2f\n", float64(minkValue)) + + player.funds += float32(beaverValue + foxValue + ermineValue + minkValue) + + fmt.Println() + fmt.Printf("YOU NOW HAVE $%1.2f INCLUDING YOUR PREVIOUS SAVINGS\n", player.funds) + fmt.Println("\nDO YOU WANT TO TRADE FURS NEXT YEAR?") + if getYesOrNo() == "N" { + os.Exit(0) + } else { + gameState = TRADING + } + } + } +} diff --git a/00_Alternate_Languages/41_Guess/go/main.go b/00_Alternate_Languages/41_Guess/go/main.go new file mode 100644 index 00000000..eea6ad22 --- /dev/null +++ b/00_Alternate_Languages/41_Guess/go/main.go @@ -0,0 +1,95 @@ +package main + +import ( + "bufio" + "fmt" + "math" + "math/rand" + "os" + "strconv" + "time" +) + +func printIntro() { + fmt.Println(" Guess") + fmt.Println("Creative Computing Morristown, New Jersey") + fmt.Println() + fmt.Println() + fmt.Println() + fmt.Println("This is a number guessing game. I'll think") + fmt.Println("of a number between 1 and any limit you want.") + fmt.Println("Then you have to guess what it is") +} + +func getLimit() (int, int) { + scanner := bufio.NewScanner(os.Stdin) + + for { + fmt.Println("What limit do you want?") + scanner.Scan() + + limit, err := strconv.Atoi(scanner.Text()) + if err != nil || limit < 0 { + fmt.Println("Please enter a number greater or equal to 1") + continue + } + + limitGoal := int((math.Log(float64(limit)) / math.Log(2)) + 1) + return limit, limitGoal + } + +} + +func main() { + rand.Seed(time.Now().UnixNano()) + printIntro() + + scanner := bufio.NewScanner(os.Stdin) + + limit, limitGoal := getLimit() + + guessCount := 1 + stillGuessing := true + won := false + myGuess := int(float64(limit)*rand.Float64() + 1) + + fmt.Printf("I'm thinking of a number between 1 and %d\n", limit) + fmt.Println("Now you try to guess what it is.") + + for stillGuessing { + scanner.Scan() + n, err := strconv.Atoi(scanner.Text()) + if err != nil { + fmt.Println("Please enter a number greater or equal to 1") + continue + } + + if n < 0 { + break + } + + fmt.Print("\n\n\n") + if n < myGuess { + fmt.Println("Too low. Try a bigger answer") + guessCount += 1 + } else if n > myGuess { + fmt.Println("Too high. Try a smaller answer") + guessCount += 1 + } else { + fmt.Printf("That's it! You got it in %d tries\n", guessCount) + won = true + stillGuessing = false + } + } + + if won { + if guessCount < limitGoal { + fmt.Println("Very good.") + } else if guessCount == limitGoal { + fmt.Println("Good.") + } else { + fmt.Printf("You should have been able to get it in only %d guesses.\n", limitGoal) + } + fmt.Print("\n\n\n") + } +} diff --git a/00_Alternate_Languages/42_Gunner/go/main.go b/00_Alternate_Languages/42_Gunner/go/main.go new file mode 100644 index 00000000..8ea76cec --- /dev/null +++ b/00_Alternate_Languages/42_Gunner/go/main.go @@ -0,0 +1,125 @@ +package main + +import ( + "bufio" + "fmt" + "math" + "math/rand" + "os" + "strconv" + "strings" + "time" +) + +func printIntro() { + fmt.Println(" GUNNER") + fmt.Println(" CREATIVE COMPUTING MORRISTOWN, NEW JERSEY") + fmt.Print("\n\n\n") + fmt.Println("YOU ARE THE OFFICER-IN-CHARGE, GIVING ORDERS TO A GUN") + fmt.Println("CREW, TELLING THEM THE DEGREES OF ELEVATION YOU ESTIMATE") + fmt.Println("WILL PLACE A PROJECTILE ON TARGET. A HIT WITHIN 100 YARDS") + fmt.Println("OF THE TARGET WILL DESTROY IT.") + fmt.Println() +} + +func getFloat() float64 { + scanner := bufio.NewScanner(os.Stdin) + for { + scanner.Scan() + fl, err := strconv.ParseFloat(scanner.Text(), 64) + + if err != nil { + fmt.Println("Invalid input") + continue + } + + return fl + } +} + +func play() { + gunRange := int(40000*rand.Float64() + 20000) + fmt.Printf("\nMAXIMUM RANGE OF YOUR GUN IS %d YARDS\n", gunRange) + + killedEnemies := 0 + S1 := 0 + + for { + targetDistance := int(float64(gunRange) * (0.1 + 0.8*rand.Float64())) + shots := 0 + + fmt.Printf("\nDISTANCE TO THE TARGET IS %d YARDS\n", targetDistance) + + for { + fmt.Print("\n\nELEVATION? ") + elevation := getFloat() + + if elevation > 89 { + fmt.Println("MAXIMUM ELEVATION IS 89 DEGREES") + continue + } + + if elevation < 1 { + fmt.Println("MINIMUM ELEVATION IS 1 DEGREE") + continue + } + + shots += 1 + + if shots < 6 { + B2 := 2 * elevation / 57.3 + shotImpact := int(float64(gunRange) * math.Sin(B2)) + shotProximity := int(targetDistance - shotImpact) + + if math.Abs(float64(shotProximity)) < 100 { // hit + fmt.Printf("*** TARGET DESTROYED *** %d ROUNDS OF AMMUNITION EXPENDED.\n", shots) + S1 += shots + + if killedEnemies == 4 { + fmt.Printf("\n\nTOTAL ROUNDS EXPENDED WERE: %d\n", S1) + if S1 > 18 { + print("BETTER GO BACK TO FORT SILL FOR REFRESHER TRAINING!") + return + } else { + print("NICE SHOOTING !!") + return + } + } else { + killedEnemies += 1 + fmt.Println("\nTHE FORWARD OBSERVER HAS SIGHTED MORE ENEMY ACTIVITY...") + break + } + } else { // missed + if shotProximity > 100 { + fmt.Printf("SHORT OF TARGET BY %d YARDS.\n", int(math.Abs(float64(shotProximity)))) + } else { + fmt.Printf("OVER TARGET BY %d YARDS.\n", int(math.Abs(float64(shotProximity)))) + } + } + } else { + fmt.Print("\nBOOM !!!! YOU HAVE JUST BEEN DESTROYED BY THE ENEMY.\n\n\n") + fmt.Println("BETTER GO BACK TO FORT SILL FOR REFRESHER TRAINING!") + return + } + } + } +} + +func main() { + rand.Seed(time.Now().UnixNano()) + scanner := bufio.NewScanner(os.Stdin) + + printIntro() + + for { + play() + + fmt.Print("TRY AGAIN (Y OR N)? ") + scanner.Scan() + + if strings.ToUpper(scanner.Text())[0:1] != "Y" { + fmt.Println("\nOK. RETURN TO BASE CAMP.") + break + } + } +} diff --git a/00_Alternate_Languages/43_Hammurabi/hammurabi.bas b/00_Alternate_Languages/43_Hammurabi/hammurabi.bas index 8f0998f0..e41fe4fb 100644 --- a/00_Alternate_Languages/43_Hammurabi/hammurabi.bas +++ b/00_Alternate_Languages/43_Hammurabi/hammurabi.bas @@ -108,7 +108,7 @@ 900 PRINT "A FANTASTIC PERFORMANCE!!! CHARLEMANGE, DISRAELI, AND" 905 PRINT "JEFFERSON COMBINED COULD NOT HAVE DONE BETTER!":GOTO 990 940 PRINT "YOUR HEAVY-HANDED PERFORMANCE SMACKS OF NERO AND IVAN IV." -945 PRINT "THE PEOPLE (REMIANING) FIND YOU AN UNPLEASANT RULER, AND," +945 PRINT "THE PEOPLE (REMAINING) FIND YOU AN UNPLEASANT RULER, AND," 950 PRINT "FRANKLY, HATE YOUR GUTS!!":GOTO 990 960 PRINT "YOUR PERFORMANCE COULD HAVE BEEN SOMEWHAT BETTER, BUT" 965 PRINT "REALLY WASN'T TOO BAD AT ALL. ";INT(P*.8*RND(1));"PEOPLE" diff --git a/00_Alternate_Languages/45_Hello/go/main.go b/00_Alternate_Languages/45_Hello/go/main.go new file mode 100644 index 00000000..b0aeda58 --- /dev/null +++ b/00_Alternate_Languages/45_Hello/go/main.go @@ -0,0 +1,240 @@ +package main + +import ( + "bufio" + "fmt" + "os" + "strings" + "time" +) + +type PROBLEM_TYPE int8 + +const ( + SEX PROBLEM_TYPE = iota + HEALTH + MONEY + JOB + UKNOWN +) + +func getYesOrNo() (bool, bool, string) { + scanner := bufio.NewScanner(os.Stdin) + + scanner.Scan() + + if strings.ToUpper(scanner.Text()) == "YES" { + return true, true, scanner.Text() + } else if strings.ToUpper(scanner.Text()) == "NO" { + return true, false, scanner.Text() + } else { + return false, false, scanner.Text() + } +} + +func printTntro() { + fmt.Println(" HELLO") + fmt.Println(" CREATIVE COMPUTING MORRISTOWN, NEW JERSEY") + fmt.Print("\n\n\n") + fmt.Println("HELLO. MY NAME IS CREATIVE COMPUTER.") + fmt.Println("\nWHAT'S YOUR NAME?") +} + +func askEnjoyQuestion(user string) { + fmt.Printf("HI THERE %s, ARE YOU ENJOYING YOURSELF HERE?\n", user) + + for { + valid, value, msg := getYesOrNo() + + if valid { + if value { + fmt.Printf("I'M GLAD TO HEAR THAT, %s.\n", user) + fmt.Println() + } else { + fmt.Printf("OH, I'M SORRY TO HEAR THAT, %s. MAYBE WE CAN\n", user) + fmt.Println("BRIGHTEN UP YOUR VISIT A BIT.") + } + break + } else { + fmt.Printf("%s, I DON'T UNDERSTAND YOUR ANSWER OF '%s'.\n", user, msg) + fmt.Println("PLEASE ANSWER 'YES' OR 'NO'. DO YOU LIKE IT HERE?") + } + } +} + +func promptForProblems(user string) PROBLEM_TYPE { + scanner := bufio.NewScanner(os.Stdin) + fmt.Println() + fmt.Printf("SAY %s, I CAN SOLVE ALL KINDS OF PROBLEMS EXCEPT\n", user) + fmt.Println("THOSE DEALING WITH GREECE. WHAT KIND OF PROBLEMS DO") + fmt.Println("YOU HAVE? (ANSWER SEX, HEALTH, MONEY, OR JOB)") + for { + scanner.Scan() + + switch strings.ToUpper(scanner.Text()) { + case "SEX": + return SEX + case "HEALTH": + return HEALTH + case "MONEY": + return MONEY + case "JOB": + return JOB + default: + return UKNOWN + } + } +} + +func promptTooMuchOrTooLittle() (bool, bool) { + scanner := bufio.NewScanner(os.Stdin) + + scanner.Scan() + + if strings.ToUpper(scanner.Text()) == "TOO MUCH" { + return true, true + } else if strings.ToUpper(scanner.Text()) == "TOO LITTLE" { + return true, false + } else { + return false, false + } +} + +func solveSexProblem(user string) { + fmt.Println("IS YOUR PROBLEM TOO MUCH OR TOO LITTLE?") + for { + valid, tooMuch := promptTooMuchOrTooLittle() + if valid { + if tooMuch { + fmt.Println("YOU CALL THAT A PROBLEM?!! I SHOULD HAVE SUCH PROBLEMS!") + fmt.Printf("IF IT BOTHERS YOU, %s, TAKE A COLD SHOWER.\n", user) + } else { + fmt.Printf("WHY ARE YOU HERE IN SUFFERN, %s? YOU SHOULD BE\n", user) + fmt.Println("IN TOKYO OR NEW YORK OR AMSTERDAM OR SOMEPLACE WITH SOME") + fmt.Println("REAL ACTION.") + } + return + } else { + fmt.Printf("DON'T GET ALL SHOOK, %s, JUST ANSWER THE QUESTION\n", user) + fmt.Println("WITH 'TOO MUCH' OR 'TOO LITTLE'. WHICH IS IT?") + } + } +} + +func solveHealthProblem(user string) { + fmt.Printf("MY ADVICE TO YOU %s IS:\n", user) + fmt.Println(" 1. TAKE TWO ASPRIN") + fmt.Println(" 2. DRINK PLENTY OF FLUIDS (ORANGE JUICE, NOT BEER!)") + fmt.Println(" 3. GO TO BED (ALONE)") +} + +func solveMoneyProblem(user string) { + fmt.Printf("SORRY, %s, I'M BROKE TOO. WHY DON'T YOU SELL\n", user) + fmt.Println("ENCYCLOPEADIAS OR MARRY SOMEONE RICH OR STOP EATING") + fmt.Println("SO YOU WON'T NEED SO MUCH MONEY?") +} + +func solveJobProblem(user string) { + fmt.Printf("I CAN SYMPATHIZE WITH YOU %s. I HAVE TO WORK\n", user) + fmt.Println("VERY LONG HOURS FOR NO PAY -- AND SOME OF MY BOSSES") + fmt.Printf("REALLY BEAT ON MY KEYBOARD. MY ADVICE TO YOU, %s,\n", user) + fmt.Println("IS TO OPEN A RETAIL COMPUTER STORE. IT'S GREAT FUN.") +} + +func askQuestionLoop(user string) { + for { + problem := promptForProblems(user) + + switch problem { + case SEX: + solveSexProblem(user) + case HEALTH: + solveHealthProblem(user) + case MONEY: + solveMoneyProblem(user) + case JOB: + solveJobProblem(user) + case UKNOWN: + fmt.Printf("OH %s, YOUR ANSWER IS GREEK TO ME.\n", user) + } + + for { + fmt.Println() + fmt.Printf("ANY MORE PROBLEMS YOU WANT SOLVED, %s?\n", user) + + valid, value, _ := getYesOrNo() + if valid { + if value { + fmt.Println("WHAT KIND (SEX, MONEY, HEALTH, JOB)") + break + } else { + return + } + } + fmt.Printf("JUST A SIMPLE 'YES' OR 'NO' PLEASE, %s\n", user) + } + } +} + +func goodbyeUnhappy(user string) { + fmt.Println() + fmt.Printf("TAKE A WALK, %s.\n", user) + fmt.Println() + fmt.Println() +} + +func goodbyeHappy(user string) { + fmt.Printf("NICE MEETING YOU %s, HAVE A NICE DAY.\n", user) +} + +func askForFee(user string) { + fmt.Println() + fmt.Printf("THAT WILL BE $5.00 FOR THE ADVICE, %s.\n", user) + fmt.Println("PLEASE LEAVE THE MONEY ON THE TERMINAL.") + time.Sleep(4 * time.Second) + fmt.Print("\n\n\n") + fmt.Println("DID YOU LEAVE THE MONEY?") + + for { + valid, value, msg := getYesOrNo() + if valid { + if value { + fmt.Printf("HEY, %s, YOU LEFT NO MONEY AT ALL!\n", user) + fmt.Println("YOU ARE CHEATING ME OUT OF MY HARD-EARNED LIVING.") + fmt.Println() + fmt.Printf("WHAT A RIP OFF, %s!!!\n", user) + fmt.Println() + } else { + fmt.Printf("THAT'S HONEST, %s, BUT HOW DO YOU EXPECT\n", user) + fmt.Println("ME TO GO ON WITH MY PSYCHOLOGY STUDIES IF MY PATIENTS") + fmt.Println("DON'T PAY THEIR BILLS?") + } + return + } else { + fmt.Printf("YOUR ANSWER OF '%s' CONFUSES ME, %s.\n", msg, user) + fmt.Println("PLEASE RESPOND WITH 'YES' or 'NO'.") + } + } +} + +func main() { + scanner := bufio.NewScanner(os.Stdin) + + printTntro() + scanner.Scan() + userName := scanner.Text() + fmt.Println() + + askEnjoyQuestion(userName) + + askQuestionLoop(userName) + + askForFee(userName) + + if false { + goodbyeHappy(userName) // unreachable + } else { + goodbyeUnhappy(userName) + } + +} diff --git a/00_Alternate_Languages/47_Hi-Lo/go/main.go b/00_Alternate_Languages/47_Hi-Lo/go/main.go new file mode 100644 index 00000000..99bea1c3 --- /dev/null +++ b/00_Alternate_Languages/47_Hi-Lo/go/main.go @@ -0,0 +1,77 @@ +package main + +import ( + "bufio" + "fmt" + "math/rand" + "os" + "strconv" + "strings" + "time" +) + +const MAX_ATTEMPTS = 6 + +func printIntro() { + fmt.Println("HI LO") + fmt.Println("CREATIVE COMPUTING MORRISTOWN, NEW JERSEY") + fmt.Println("\n\n\nTHIS IS THE GAME OF HI LO.") + fmt.Println("\nYOU WILL HAVE 6 TRIES TO GUESS THE AMOUNT OF MONEY IN THE") + fmt.Println("HI LO JACKPOT, WHICH IS BETWEEN 1 AND 100 DOLLARS. IF YOU") + fmt.Println("GUESS THE AMOUNT, YOU WIN ALL THE MONEY IN THE JACKPOT!") + fmt.Println("THEN YOU GET ANOTHER CHANCE TO WIN MORE MONEY. HOWEVER,") + fmt.Println("IF YOU DO NOT GUESS THE AMOUNT, THE GAME ENDS.") + fmt.Println() + fmt.Println() +} + +func main() { + rand.Seed(time.Now().UnixNano()) + scanner := bufio.NewScanner(os.Stdin) + + printIntro() + + totalWinnings := 0 + + for { + fmt.Println() + secret := rand.Intn(1000) + 1 + + guessedCorrectly := false + + for attempt := 0; attempt < MAX_ATTEMPTS; attempt++ { + fmt.Println("YOUR GUESS?") + scanner.Scan() + guess, err := strconv.Atoi(scanner.Text()) + if err != nil { + fmt.Println("INVALID INPUT") + } + + if guess == secret { + fmt.Printf("GOT IT!!!!!!!!!! YOU WIN %d DOLLARS.\n", secret) + guessedCorrectly = true + break + } else if guess > secret { + fmt.Println("YOUR GUESS IS TOO HIGH.") + } else { + fmt.Println("YOUR GUESS IS TOO LOW.") + } + } + + if guessedCorrectly { + totalWinnings += secret + fmt.Printf("YOUR TOTAL WINNINGS ARE NOW $%d.\n", totalWinnings) + } else { + fmt.Printf("YOU BLEW IT...TOO BAD...THE NUMBER WAS %d\n", secret) + } + + fmt.Println() + fmt.Println("PLAYAGAIN (YES OR NO)?") + scanner.Scan() + + if strings.ToUpper(scanner.Text())[0:1] != "Y" { + break + } + } + fmt.Println("\nSO LONG. HOPE YOU ENJOYED YOURSELF!!!") +} diff --git a/00_Alternate_Languages/78_Sine_Wave/C++/README.md b/00_Alternate_Languages/78_Sine_Wave/C++/README.md new file mode 100644 index 00000000..b62763ff --- /dev/null +++ b/00_Alternate_Languages/78_Sine_Wave/C++/README.md @@ -0,0 +1,3 @@ +Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html) + +Conversion to [C++17](https://en.wikipedia.org/wiki/C%2B%2B17) \ No newline at end of file diff --git a/00_Alternate_Languages/78_Sine_Wave/C++/sinewave.cpp b/00_Alternate_Languages/78_Sine_Wave/C++/sinewave.cpp new file mode 100644 index 00000000..9d274e56 --- /dev/null +++ b/00_Alternate_Languages/78_Sine_Wave/C++/sinewave.cpp @@ -0,0 +1,21 @@ +#include // std::cout, std::endl +#include // std::string(size_t n, char c) +#include // std::sin(double x) + +int main() +{ + std::cout << std::string(30, ' ') << "SINE WAVE" << std::endl; + std::cout << std::string(15, ' ') << "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY" << std::endl; + std::cout << std::string(5, '\n'); + + bool b = true; + + for (double t = 0.0; t <= 40.0; t += 0.25) + { + int a = int(26 + 25 * std::sin(t)); + std::cout << std::string(a, ' ') << (b ? "CREATIVE" : "COMPUTING") << std::endl; + b = !b; + } + + return 0; +} diff --git a/00_Utilities/markdown_todo_rust/src/main.rs b/00_Utilities/markdown_todo_rust/src/main.rs index d1f49bf4..9df64567 100644 --- a/00_Utilities/markdown_todo_rust/src/main.rs +++ b/00_Utilities/markdown_todo_rust/src/main.rs @@ -1,4 +1,4 @@ -use std::ffi::{OsString, OsStr}; +use std::ffi::OsStr; use std::{fs, io}; use std::fs::metadata; use std::path::{Path, PathBuf}; @@ -8,8 +8,6 @@ use std::path::{Path, PathBuf}; * @author Anthony Rubick */ - - //DATA const ROOT_DIR: &str = "../../"; const LANGUAGES: [(&str,&str); 10] = [ //first element of tuple is the language name, second element is the file extension @@ -25,13 +23,19 @@ const LANGUAGES: [(&str,&str); 10] = [ //first element of tuple is the language ("vbnet", "vb") ]; const OUTPUT_PATH: &str = "../../todo.md"; -//const INGORE: [&str;5] = ["../../.git","../../.vscode","../../00_Utilities","../../buildJvm","../../node_modules"]; //folders to ignore fn main() { //DATA let mut root_folders:Vec; let mut output_string: String = String::new(); let format_game_first: bool; + let ingore: [PathBuf;5] = [ + PathBuf::from(r"../../.git"), + PathBuf::from(r"../../.github"), + PathBuf::from(r"../../00_Alternate_Languages"), + PathBuf::from(r"../../00_Utilities"), + PathBuf::from(r"../../00_Common"), + ]; //folders to ignore //print welcome message println!(" @@ -66,15 +70,10 @@ fn main() { //for all folders, search for the languages and extensions root_folders = root_folders.into_iter().filter(|path| { - match fs::read_dir(path) { - Err(why) => {println!("! {:?}", why.kind()); false}, - Ok(paths) => { - paths.into_iter().filter(|f| metadata(f.as_ref().unwrap().path()).unwrap().is_dir()) //filter to only folders - .filter_map( |path| path.ok() ) //extract only the DirEntries - .any(|f| LANGUAGES.iter().any(|tup| OsString::from(tup.1).eq_ignore_ascii_case(f.file_name()))) //filter out ones that don't contain folders with the language names - } - } + //not one of the ignored folders + !ingore.contains(path) }).collect(); + root_folders.sort(); //create todo list if format_game_first { diff --git a/03_Animal/lua/Animal.lua b/03_Animal/lua/Animal.lua new file mode 100644 index 00000000..293c53e4 --- /dev/null +++ b/03_Animal/lua/Animal.lua @@ -0,0 +1,181 @@ +-- +-- Animal.lua +-- + +-- maintain a flat table/list of all the known animals +local animals = { + "FISH", + "BIRD" +} + +-- store the questions as a binary tree with each node having a +-- "y" or "n" branch +-- if the node has a member named "a" it's an answer, with an index +-- into the animals list. +-- Otherwise, it's a question that leads to more nodes. +local questionTree = { + q = "DOES IT SWIM", + y = { a = 1 }, + n = { a = 2 } +} + +-- print the given prompt string and then wait for input +-- loops until a non-empty input is given +-- returns the input as an upper-case string +function askPrompt(promptString) + local answered = false + local a + while (not answered) do + print(promptString) + a = io.read() + a = string.upper(a) + if (string.len(a) > 0) then + answered = true + end + end + return a +end + +-- print the given prompt string and then wait for the +-- user to enter a string beginning with "Y" or "N" +function askYesOrNo(promptString) + local a + while ((a ~= "Y") and (a ~= "N")) do + a = askPrompt(promptString) + a = a:sub(1,1) + end + return a +end + +-- prints the introductory text from the original BASIC program +function printIntro() + print(string.format("%32s", " ") .. "ANIMAL") + print(string.format("%15s", " ") .. "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY") + print() + print() + print() + print("PLAY 'GUESS THE ANIMAL'") + print("THINK OF AN ANIMAL AND THE COMPUTER WILL TRY TO GUESS IT.") + print() +end + +-- prints the animals known in the source list +function listKnownAnimals() + print() + print("ANIMALS I ALREADY KNOW ARE:") + + local x + local item + for x = 1,#animals do + -- use string.format to space each animal in a 12-character-wide "cell" + item = string.format("%-12s", animals[x]) + + -- io.write() works like print(), but doesn't automatically add a carriage return/newline + io.write(item) + + -- every fifth item, start a new line + if ((x % 5) == 0) then + io.write("\n") + end + end + + print() + print() +end + +-- Prompts the user for info about the animal they were thinking of, then +-- uses that to add a new branch to the tree +-- curNode: the node in the tree where the computer made a wrong guess +-- branch: the answer the user gave to curNode's question +function addAnimalToTree(curNode, branch) + local newAnimal + local curResponse = curNode[branch] + local guessedIndex = curResponse.a + local guessedAnimal = animals[guessedIndex] + local newQuestion, newAnswer, newIndex + local newNode + + newAnimal = askPrompt("THE ANIMAL YOU WERE THINKING OF WAS A ?") + newQuestion = askPrompt("PLEASE TYPE IN A QUESTION THAT WOULD DISTINGUISH A ".. + tostring(newAnimal).." FROM A "..tostring(guessedAnimal)) + newAnswer = askYesOrNo("FOR A "..tostring(newAnimal).." THE ANSWER WOULD BE?") + + -- add the new animal to the master list at the end, and + -- save off its index in the list + table.insert(animals, newAnimal) + newIndex = #animals + + -- create a new node for the question we just learned + newNode = {} + newNode.q = newQuestion + if (newAnswer == "Y") then + newNode.y = { a = newIndex } + newNode.n = { a = guessedIndex } + else + newNode.y = { a = guessedIndex } + newNode.n = { a = newIndex } + end + + -- replace the previous answer with our new node + curNode[branch] = newNode +end + +-- Starts at the root of the question tree and asks questions about +-- the user's animal until the computer hits an "a" answer node and tries +-- to make a guess +function askAboutAnimal() + local curNode = questionTree + local finished = false + local response, responseIndex + local nextNode, animalName + while (not finished) do + response = askYesOrNo(curNode.q .. "?") + + -- convert the response "Y" or "N" to the lowercase "y" or "n" that we use to name our branches + branch = string.lower(response) + nextNode = curNode[branch] + + -- is the next node an answer node, or another question? + if (nextNode.a ~= nil) then + -- it's an answer, so make a guess + animalName = animals[nextNode.a] + response = askYesOrNo("IS IT A "..tostring(animalName).."?") + if (response == "Y") then + -- we got the correct answer, so prompt for a new animal + print() + print("WHY NOT TRY ANOTHER ANIMAL?") + else + -- incorrect answer, so add a new entry at this point in the tree + addAnimalToTree(curNode, branch) + end + + -- whether we were right or wrong, we're finished with this round + finished = true + else + -- it's another question, so advance down the tree + curNode = nextNode + end + end +end + +-- MAIN CONTROL SECTION + +printIntro() + +-- loop forever until the player requests an exit by entering a blank line +local exitRequested = false +local answer + +while (not exitRequested) do + print("ARE YOU THINKING OF AN ANIMAL?") + answer = io.read() + answer = string.upper(answer) + + if (string.len(answer) == 0) then + exitRequested = true + elseif (answer:sub(1,4) == "LIST") then + listKnownAnimals() + elseif (answer:sub(1,1) == "Y") then + askAboutAnimal() + end +end diff --git a/06_Banner/perl/banner.pl b/06_Banner/perl/banner.pl new file mode 100644 index 00000000..2524ef00 --- /dev/null +++ b/06_Banner/perl/banner.pl @@ -0,0 +1,150 @@ +#!/usr/bin/perl + +# Banner program in Perl +# Translated by Kevin Brannen (kbrannen) + +use strict; +use warnings; + +sub print_lines +{ + my $lines = shift; + print "\n" x $lines; +} + +# each letter is made of 7 slices (or rows); +# the initial & unused 0 is to allow for Perl arrays being 0-based +# but allow the algorithm to be 1-based (like Basic arrays); +# the numbers are essentially the dots/columns per slice in powers of 2. +my %data = ( + " " => [ 0, 0, 0, 0, 0, 0, 0, 0 ], + "." => [ 0, 1, 1, 129, 449, 129, 1, 1 ], + "!" => [ 0, 1, 1, 1, 384, 1, 1, 1 ], + "=" => [ 0, 41, 41, 41, 41, 41, 41, 41 ], + "?" => [ 0, 5, 3, 2, 354, 18, 11, 5 ], + "*" => [ 0, 69, 41, 17, 512, 17, 41, 69 ], + "0" => [ 0, 57, 69, 131, 258, 131, 69, 57 ], + "1" => [ 0, 0, 0, 261, 259, 512, 257, 257 ], + "2" => [ 0, 261, 387, 322, 290, 274, 267, 261 ], + "3" => [ 0, 66, 130, 258, 274, 266, 150, 100 ], + "4" => [ 0, 33, 49, 41, 37, 35, 512, 33 ], + "5" => [ 0, 160, 274, 274, 274, 274, 274, 226 ], + "6" => [ 0, 194, 291, 293, 297, 305, 289, 193 ], + "7" => [ 0, 258, 130, 66, 34, 18, 10, 8 ], + "8" => [ 0, 69, 171, 274, 274, 274, 171, 69 ], + "9" => [ 0, 263, 138, 74, 42, 26, 10, 7 ], + "A" => [ 0, 505, 37, 35, 34, 35, 37, 505 ], + "B" => [ 0, 512, 274, 274, 274, 274, 274, 239 ], + "C" => [ 0, 125, 131, 258, 258, 258, 131, 69 ], + "D" => [ 0, 512, 258, 258, 258, 258, 131, 125 ], + "E" => [ 0, 512, 274, 274, 274, 274, 258, 258 ], + "F" => [ 0, 512, 18, 18, 18, 18, 2, 2 ], + "G" => [ 0, 125, 131, 258, 258, 290, 163, 101 ], + "H" => [ 0, 512, 17, 17, 17, 17, 17, 512 ], + "I" => [ 0, 258, 258, 258, 512, 258, 258, 258 ], + "J" => [ 0, 65, 129, 257, 257, 257, 129, 128 ], + "K" => [ 0, 512, 17, 17, 41, 69, 131, 258 ], + "L" => [ 0, 512, 257, 257, 257, 257, 257, 257 ], + "M" => [ 0, 512, 7, 13, 25, 13, 7, 512 ], + "N" => [ 0, 512, 7, 9, 17, 33, 193, 512 ], + "O" => [ 0, 125, 131, 258, 258, 258, 131, 125 ], + "P" => [ 0, 512, 18, 18, 18, 18, 18, 15 ], + "Q" => [ 0, 125, 131, 258, 258, 322, 131, 381 ], + "R" => [ 0, 512, 18, 18, 50, 82, 146, 271 ], + "S" => [ 0, 69, 139, 274, 274, 274, 163, 69 ], + "T" => [ 0, 2, 2, 2, 512, 2, 2, 2 ], + "U" => [ 0, 128, 129, 257, 257, 257, 129, 128 ], + "V" => [ 0, 64, 65, 129, 257, 129, 65, 64 ], + "W" => [ 0, 256, 257, 129, 65, 129, 257, 256 ], + "X" => [ 0, 388, 69, 41, 17, 41, 69, 388 ], + "Y" => [ 0, 8, 9, 17, 481, 17, 9, 8 ], + "Z" => [ 0, 386, 322, 290, 274, 266, 262, 260 ], +); + +my ($horz, $vert, $center, $char, $msg) = (0, 0, '', '', ''); + +# get args to run with +while ($horz < 1) +{ + print "HORIZONTAL (1 or more): "; + chomp($horz = <>); + $horz = int($horz); +} + +while ($vert < 1) +{ + print "VERTICAL (1 or more): "; + chomp($vert = <>); + $vert = int($vert); +} + +print "CENTERED (Y/N): "; +chomp($center = <>); +$center = ($center =~ m/^Y/i) ? 1 : 0; + +# note you can enter multiple chars and the program will do the right thing +# thanks to the length() calls below, which was in the original Basic +print "CHARACTER TO PRINT (TYPE 'ALL' IF YOU WANT CHARACTER BEING PRINTED): "; +chomp($char = uc(<>)); + +while (!$msg) +{ + print "STATEMENT: "; + chomp($msg = uc(<>)); +} + +print "SET PAGE TO PRINT, HIT RETURN WHEN READY"; +$_ = <>; +print_lines(2 * $horz); + +# print the message +for my $letter ( split(//, $msg) ) +{ + if (!exists($data{$letter})) + { + die "Cannot use letter '$letter'!"; + } + my @s = @{$data{$letter}}; + #if ($letter eq " ") { print_lines(7 * $horz); next; } + + my $print_letter = ($char eq "ALL") ? $letter : $char; + for my $slice (1 .. 7) + { + my (@j, @f); + for (my $k = 8; $k >= 0; $k--) + { + if (2**$k < $s[$slice]) + { + $j[9 - $k] = 1; + $s[$slice] = $s[$slice] - 2**$k; + if ($s[$slice] == 1) + { + $f[$slice] = 9 - $k; + } + } + else + { + $j[9 - $k] = 0; + } + } + + for my $t1 (1 .. $horz) + { + print " " x int((63 - 4.5 * $vert) * $center / (length($print_letter)) + 1); + for my $b (1 .. (defined($f[$slice]) ? $f[$slice] : 0)) + { + my $str = $j[$b] ? $print_letter : (" " x length($print_letter)); + print $str for (1 .. $vert); + } + print "\n"; + } + } + + # space between letters + print_lines(2 * $horz); +} + +# while in the original code, this seems pretty excessive +#print_lines(75); + +exit(0); diff --git a/07_Basketball/perl/README.md b/07_Basketball/perl/README.md index e69c8b81..51373c0c 100644 --- a/07_Basketball/perl/README.md +++ b/07_Basketball/perl/README.md @@ -1,3 +1,21 @@ Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html) Conversion to [Perl](https://www.perl.org/) + +There are two version of the code here, a "faithful" translation (basketball-orig.pl) and +a "modern" translation (basketball.pl). The main difference between the 2 are is that the +faithful translation has 3 GOTOs in it while the modern version has no GOTO. I have added +a "TIME" print when the score is shown so the Clock is visible. Halftime is at "50" and +end of game is at 100 (per the Basic code). + +The 3 GOTOs in the faitful version are because of the way the original code jumped into +the "middle of logic" that has no obivious way to avoid ... that I can see, at least while +still maintaining something of the look and structure of the original Basic. + +The modern version avoided the GOTOs by restructuring the program in the 2 "play()" subs. +Despite the change, this should play the same way as the faithful version. + +All of the percentages remain the same. If writing this from scratch, we really should +have only a single play() sub which uses the same code for both teams, which would also +make the game more fair ... but that wasn't done so the percent edge to Darmouth has been +maintained here. diff --git a/07_Basketball/perl/basketball-orig.pl b/07_Basketball/perl/basketball-orig.pl new file mode 100755 index 00000000..14627ffc --- /dev/null +++ b/07_Basketball/perl/basketball-orig.pl @@ -0,0 +1,415 @@ +#!/usr/bin/perl + +# Basketball program in Perl +# This is fairly faithful translation from the original Basic. +# This becomes apparent because there are actually 3 GOTOs still present +# because of the way the original code jumped into the "middle of logic" +# that has no obivious way to avoid ... that I can see. +# For better structure and no GOTOs, see the other version of this program. +# Translated by Kevin Brannen (kbrannen) + +use strict; +use warnings; + +# globals +my $Defense=0; # dartmouth defense value +my $Opponent; # name of opponent +my @Score = (0, 0); # scores, dart is [0], opponent is [1] +my $Player = 0; # player, 0 = dart, 1 = opp +my $Timer = 0; # time tick, 100 ticks per game, 50 is end of first half, if tie at end then back to T=93 +my $DoPlay = 1; # true if game is still being played +my $ConTeam; # controlling team, "dart" or "opp" +my $ShotType = 0; # current shot type + + +print "\n"; +print " " x 31, "BASKETBALL"; +print " " x 15, "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY"; +print "\n\n\n"; + +print "THIS IS DARTMOUTH COLLEGE BASKETBALL. YOU WILL BE DARTMOUTH\n"; +print "CAPTAIN AND PLAYMAKER. CALL SHOTS AS FOLLOWS:\n"; +print " 1. LONG (30 FT.) JUMP SHOT;\n"; +print " 2. SHORT (15 FT.) JUMP SHOT;\n"; +print " 3. LAY UP;\n"; +print " 4. SET SHOT.\n"; +print "BOTH TEAMS WILL USE THE SAME DEFENSE. CALL DEFENSE AS FOLLOWS:\n"; +print " 6. PRESS;\n"; +print " 6.5 MAN-TO MAN;\n"; +print " 7. ZONE;\n"; +print " 7.5 NONE.\n"; +print "TO CHANGE DEFENSE, JUST TYPE 0 AS YOUR NEXT SHOT.\n\n"; +get_defense(); +print "\n"; +print "CHOOSE YOUR OPPONENT: "; +chomp($Opponent = <>); + +$ConTeam = center_jump(); +while ($DoPlay) +{ + print "\n"; + if ($ConTeam eq "dart") + { + $Player = 0; + get_your_shot(); + dartmouth_play(); + } + else + { + opponent_play(); + } + if ($Timer >= 100) + { + check_end_game(); + last if (!$DoPlay); + $Timer = 93; + $ConTeam = center_jump(); + } +} +exit(0); + +############################################################### + +sub dartmouth_play +{ + if ($ShotType == 1 || $ShotType == 2) + { + $Timer++; + if ($Timer == 50) + { + end_first_half(); + return; + } + if ($Timer == 92) + { + two_min_left(); + } + + print "JUMP SHOT\n"; + if (rand(1) <= 0.341 * $Defense / 8) + { + print "SHOT IS GOOD.\n"; + dartmouth_score(); + $ConTeam = "opp"; + return; + } + + if (rand(1) <= 0.682*$Defense/8) + { + print "SHOT IS OFF TARGET.\n"; + if ($Defense/6*rand(1) > 0.45) + { + print "REBOUND TO ", $Opponent, "\n"; + $ConTeam = "opp"; + return; + } + + print "DARTMOUTH CONTROLS THE REBOUND.\n"; + if (rand(1) <= 0.4) { goto L1300; } + if ($Defense == 6) + { + if (rand(1) <= 0.6) + { + print "PASS STOLEN BY $Opponent, EASY LAYUP.\n"; + opp_score(); + $ConTeam = "dart"; return; + return; + } + } + print "BALL PASSED BACK TO YOU.\n"; + $ConTeam = "dart"; + return; + } + + if (rand(1) <= 0.782*$Defense/8) + { + print "SHOT IS BLOCKED. BALL CONTROLLED BY "; # no NL + if (rand(1) > 0.5) + { + print "$Opponent.\n"; + $ConTeam = "opp"; + return; + } + else + { + print "DARTMOUTH.\n"; + $ConTeam = "dart"; + return; + } + } + + if (rand(1) > 0.843*$Defense/8) + { + print "CHARGING FOUL. DARTMOUTH LOSES BALL.\n"; + $ConTeam = "opp"; + return; + } + else + { + print "SHOOTER IS FOULED. TWO SHOTS.\n"; + foul_shooting(); + $ConTeam = "opp"; + return; + } + } + + L1300: + while (1) + { + $Timer++; + if ($Timer == 50) + { + end_first_half(); + return; + } + if ($Timer == 92) { two_min_left(); } + if ($ShotType == 0) + { + get_defense(); + return; + } + print '', ($ShotType > 3 ? "SET SHOT." : "LAY UP."), "\n"; + if (7 / $Defense * rand(1) <= 0.4) + { + print "SHOT IS GOOD. TWO POINTS.\n"; + dartmouth_score(); + $ConTeam = "opp"; + return; + } + + if (7 / $Defense * rand(1) <= 0.7) + { + print "SHOT IS OFF THE RIM.\n"; + if (rand(1) <= 0.667) + { + print "$Opponent CONTROLS THE REBOUND.\n"; + $ConTeam = "opp"; + return; + } + + print "DARTMOUTH CONTROLS THE REBOUND.\n"; + next if (rand(1) <= 0.4); + + print "BALL PASSED BACK TO YOU.\n"; + $ConTeam = "dart"; + return; + } + + if (7 / $Defense * rand(1) <= 0.875) + { + print "SHOOTER FOULED. TWO SHOTS.\n"; + foul_shooting(); + $ConTeam = "opp"; + return; + } + + if (7 / $Defense * rand(1) <= 0.925) + { + print "SHOT BLOCKED. $Opponent\'S BALL.\n"; + $ConTeam = "opp"; + return; + } + + print "CHARGING FOUL. DARTMOUTH LOSES THE BALL.\n"; + $ConTeam = "opp"; + return; + } +} + +sub get_defense +{ + $Defense = 0; + while ($Defense < 6 || $Defense > 7.5) + { + print "YOUR NEW DEFENSIVE ALLIGNMENT IS (6, 6.5, 7. 7.5): "; + chomp($Defense = <>); + ($Defense) =~ m/(\d(\.\d)?)/; + } +} + +sub opponent_play +{ + $Player = 1; + $Timer++; + if ($Timer == 50) + { + end_first_half(); + $ConTeam = center_jump(); + return; + } + + print "\n"; + while (1) + { + my $shot = 10.0 / 4 * rand(1) + 1; + if ($shot <= 2.0) + { + print "JUMP SHOT.\n"; + if (8.0 / $Defense * rand(1) <= 0.35) + { + print "SHOT IS GOOD.\n"; + opp_score(); + $ConTeam = "dart"; + return; + } + + if (8.0 / $Defense * rand(1) <= 0.75) + { + print "SHOT IS OFF RIM.\n"; + + L3110: + if ($Defense / 6.0 * rand(1) <= 0.5) + { + print "DARTMOUTH CONTROLS THE REBOUND.\n"; + $ConTeam = "dart"; + return; + } + print "$Opponent CONTROLS THE REBOUND.\n"; + if ($Defense == 6) + { + if (rand(1) <= 0.75) + { + print "BALL STOLEN. EASY LAY UP FOR DARTMOUTH.\n"; + dartmouth_score(); + $ConTeam = "opp"; + return; + } + } + if (rand(1) <= 0.5) + { + print "PASS BACK TO $Opponent GUARD.\n"; + $ConTeam = "opp"; + return; + } + goto L3500; + } + + if (8.0 / $Defense * rand(1) <= 0.9) + { + print "PLAYER FOULED. TWO SHOTS.\n"; + foul_shooting(); + $ConTeam = "dart"; + return; + } + print "OFFENSIVE FOUL. DARTMOUTH'S BALL.\n"; + $ConTeam = "dart"; + return; + } + + L3500: + print ($shot > 3 ? "SET SHOT.\n" : "LAY UP.\n"); + if (7.0 / $Defense * rand(1) > 0.413) + { + print "SHOT IS MISSED.\n"; + { + no warnings; + goto L3110; + } + } + else + { + print "SHOT IS GOOD.\n"; + opp_score(); + $ConTeam = "dart"; + return; + } + } +} + +sub opp_score +{ + $Score[0] += 2; + print_score(); +} + +sub dartmouth_score +{ + $Score[1] += 2; + print_score(); +} + +sub print_score +{ + print "SCORE: $Score[1] TO $Score[0]\n"; + print "TIME: $Timer\n"; +} + +sub end_first_half +{ + print "\n ***** END OF FIRST HALF *****\n\n"; + print "SCORE: DARTMOUTH: $Score[1] $Opponent: $Score[0]\n\n\n"; + center_jump(); +} + +sub get_your_shot +{ + $ShotType = -1; + while ($ShotType < 0 || $ShotType > 4) + { + print "YOUR SHOT (0-4): "; + chomp($ShotType = <>); + $ShotType = int($ShotType); + if ($ShotType < 0 || $ShotType > 4) + { + print "INCORRECT ANSWER. RETYPE IT. "; + } + } +} + +sub center_jump +{ + print "CENTER JUMP\n"; + if (rand(1) <= 0.6) + { + print "$Opponent CONTROLS THE TAP.\n"; + return "opp"; + } + print "DARTMOUTH CONTROLS THE TAP.\n"; + return "dart"; +} + +sub check_end_game +{ + print "\n"; + if ($Score[1] != $Score[0]) + { + print " ***** END OF GAME *****\n"; + print "FINAL SCORE: DARTMOUTH: $Score[1] $Opponent: $Score[0]\n\n"; + $DoPlay = 0; + } + else + { + print "\n ***** END OF SECOND HALF *****\n"; + print "SCORE AT END OF REGULATION TIME:\n"; + print " DARTMOUTH: $Score[1] $Opponent: $Score[0]\n\n"; + print "BEGIN TWO MINUTE OVERTIME PERIOD\n"; + } +} + +sub two_min_left +{ + print "\n *** TWO MINUTES LEFT IN THE GAME ***\n\n"; +} + +sub foul_shooting +{ + if (rand(1) > 0.49) + { + if (rand(1) > 0.75) + { + print "BOTH SHOTS MISSED.\n"; + } + else + { + print "SHOOTER MAKES ONE SHOT AND MISSES ONE.\n"; + $Score[1 - $Player]++; + } + } + else + { + print "SHOOTER MAKES BOTH SHOTS.\n"; + $Score[1 - $Player] += 2; + } + + print_score(); +} diff --git a/07_Basketball/perl/basketball.pl b/07_Basketball/perl/basketball.pl new file mode 100755 index 00000000..ac348309 --- /dev/null +++ b/07_Basketball/perl/basketball.pl @@ -0,0 +1,393 @@ +#!/usr/bin/perl + +# Basketball program in Perl +# While this should play the same way as the fairly faithful translation version, +# there are no GOTOs in this code. That was achieved by restructuring the program +# in the 2 *_play() subs. All of the percentages remain the same. If writing this +# from scratch, we really should have only a play() sub which uses the same code +# for both teams, but the percent edge to Darmouth has been maintained here. +# Translated by Kevin Brannen (kbrannen) + +use strict; +use warnings; + +# globals +my $Defense=0; # dartmouth defense value +my $Opponent; # name of opponent +my @Score = (0, 0); # scores, dart is [0], opponent is [1] +my $Player = 0; # player, 0 = dart, 1 = opp +my $Timer = 0; # time tick, 100 ticks per game, 50 is end of first half, if tie at end then back to T=93 +my $DoPlay = 1; # true if game is still being played +my $ConTeam; # controlling team, "dart" or "opp" +my $ShotType = 0; # current shot type + + +print "\n"; +print " " x 31, "BASKETBALL"; +print " " x 15, "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY"; +print "\n\n\n"; + +print "THIS IS DARTMOUTH COLLEGE BASKETBALL. YOU WILL BE DARTMOUTH\n"; +print "CAPTAIN AND PLAYMAKER. CALL SHOTS AS FOLLOWS:\n"; +print " 1. LONG (30 FT.) JUMP SHOT;\n"; +print " 2. SHORT (15 FT.) JUMP SHOT;\n"; +print " 3. LAY UP;\n"; +print " 4. SET SHOT.\n"; +print "BOTH TEAMS WILL USE THE SAME DEFENSE. CALL DEFENSE AS FOLLOWS:\n"; +print " 6. PRESS;\n"; +print " 6.5 MAN-TO MAN;\n"; +print " 7. ZONE;\n"; +print " 7.5 NONE.\n"; +print "TO CHANGE DEFENSE, JUST TYPE 0 AS YOUR NEXT SHOT.\n\n"; +get_defense(); +print "\n"; +print "CHOOSE YOUR OPPONENT: "; +chomp($Opponent = <>); + +$ConTeam = center_jump(); +while ($DoPlay) +{ + print "\n"; + if ($ConTeam eq "dart") + { + get_your_shot(); + dartmouth_play(); + } + else + { + $ShotType = 10.0 / 4 * rand(1) + 1; + opponent_play(); + } + if ($Timer >= 100) + { + check_end_game(); + last if (!$DoPlay); + $Timer = 93; + $ConTeam = center_jump(); + } +} +exit(0); + +############################################################### + +sub dartmouth_play +{ + $Player = 0; + print "\n"; + while (1) + { + $Timer++; + if ($Timer == 50) { end_first_half(); return; } + if ($Timer == 92) { two_min_left(); } + + if ($ShotType == 0) + { + get_defense(); + return; # for new ShotType + } + elsif ($ShotType == 1 || $ShotType == 2) + { + print "JUMP SHOT\n"; + if (rand(1) <= 0.341 * $Defense / 8) + { + print "SHOT IS GOOD.\n"; + dartmouth_score(); + last; + } + + if (rand(1) <= 0.682*$Defense/8) + { + print "SHOT IS OFF TARGET.\n"; + if ($Defense/6*rand(1) > 0.45) + { + print "REBOUND TO $Opponent\n"; + last; + } + + print "DARTMOUTH CONTROLS THE REBOUND.\n"; + if (rand(1) <= 0.4) + { + $ShotType = (rand(1) <= 0.5) ? 3 : 4; + next; + } + if ($Defense == 6) + { + if (rand(1) <= 0.6) + { + print "PASS STOLEN BY $Opponent, EASY LAYUP.\n"; + opp_score(); + next; + } + } + print "BALL PASSED BACK TO YOU.\n"; + next; + } + + if (rand(1) <= 0.782*$Defense/8) + { + print "SHOT IS BLOCKED. BALL CONTROLLED BY "; # no NL + if (rand(1) > 0.5) + { + print "$Opponent.\n"; + last; + } + else + { + print "DARTMOUTH.\n"; + next; + } + } + + if (rand(1) > 0.843*$Defense/8) + { + print "CHARGING FOUL. DARTMOUTH LOSES BALL.\n"; + last; + } + else + { + print "SHOOTER IS FOULED. TWO SHOTS.\n"; + foul_shooting(); + last; + } + } + else # elsif ($ShotType >= 3) + { + print '', ($ShotType > 3 ? "SET SHOT." : "LAY UP."), "\n"; + if (7 / $Defense * rand(1) <= 0.4) + { + print "SHOT IS GOOD. TWO POINTS.\n"; + dartmouth_score(); + last; + } + + if (7 / $Defense * rand(1) <= 0.7) + { + print "SHOT IS OFF THE RIM.\n"; + if (rand(1) <= 0.667) + { + print "$Opponent CONTROLS THE REBOUND.\n"; + last; + } + + print "DARTMOUTH CONTROLS THE REBOUND.\n"; + next if (rand(1) <= 0.4); + + print "BALL PASSED BACK TO YOU.\n"; + next; + } + + if (7 / $Defense * rand(1) <= 0.875) + { + print "SHOOTER FOULED. TWO SHOTS.\n"; + foul_shooting(); + last; + } + + if (7 / $Defense * rand(1) <= 0.925) + { + print "SHOT BLOCKED. $Opponent\'S BALL.\n"; + last; + } + + print "CHARGING FOUL. DARTMOUTH LOSES THE BALL.\n"; + last; + } + } + $ConTeam = "opp"; +} + +sub get_defense +{ + $Defense = 0; + do { + print "YOUR NEW DEFENSIVE ALLIGNMENT IS (6, 6.5, 7. 7.5): "; + chomp($Defense = <>); + ($Defense) =~ m/(\d(\.\d)?)/; + } while ($Defense < 6.0 || $Defense > 7.5) +} + +sub opponent_play +{ + $Player = 1; + print "\n"; + while (1) + { + $Timer++; + if ($Timer == 50) { end_first_half(); return; } + if ($Timer == 92) { two_min_left(); } + + if ($ShotType <= 2.0) + { + print "JUMP SHOT.\n"; + if (8.0 / $Defense * rand(1) <= 0.35) + { + print "SHOT IS GOOD.\n"; + opp_score(); + last; + } + + if (8.0 / $Defense * rand(1) <= 0.75) + { + print "SHOT IS OFF RIM.\n"; + opp_missed(); + return; # for possible new ShotType or team change + } + + if (8.0 / $Defense * rand(1) <= 0.9) + { + print "PLAYER FOULED. TWO SHOTS.\n"; + foul_shooting(); + last; + } + print "OFFENSIVE FOUL. DARTMOUTH'S BALL.\n"; + last; + } + else # ShotType >= 3 + { + print ($ShotType > 3 ? "SET SHOT.\n" : "LAY UP.\n"); + if (7.0 / $Defense * rand(1) > 0.413) + { + print "SHOT IS MISSED.\n"; + { + opp_missed(); + return; # for possible new ShotType or team change + } + } + else + { + print "SHOT IS GOOD.\n"; + opp_score(); + last; + } + } + } + $ConTeam = "dart"; +} + +sub opp_missed +{ + if ($Defense / 6.0 * rand(1) <= 0.5) + { + print "DARTMOUTH CONTROLS THE REBOUND.\n"; + $ConTeam = "dart"; + } + else + { + print "$Opponent CONTROLS THE REBOUND.\n"; + if ($Defense == 6) + { + if (rand(1) <= 0.75) + { + print "BALL STOLEN. EASY LAY UP FOR DARTMOUTH.\n"; + dartmouth_score(); + #$ConTeam = "opp"; + return; # for possible new ShotType + } + } + if (rand(1) <= 0.5) + { + print "PASS BACK TO $Opponent GUARD.\n"; + #$ConTeam = "opp"; + return; # for possible new ShotType + } + $ShotType = (rand(1) <= 0.5) ? 3 : 4; + } +} + +sub opp_score +{ + $Score[0] += 2; + print_score(); +} + +sub dartmouth_score +{ + $Score[1] += 2; + print_score(); +} + +sub print_score +{ + print "SCORE: $Score[1] TO $Score[0]\n"; + print "TIME: $Timer\n"; +} + +sub end_first_half +{ + print "\n ***** END OF FIRST HALF *****\n\n"; + print "SCORE: DARTMOUTH: $Score[1] $Opponent: $Score[0]\n\n\n"; + $ConTeam = center_jump(); +} + +sub get_your_shot +{ + $ShotType = -1; + while ($ShotType < 0 || $ShotType > 4) + { + print "YOUR SHOT (0-4): "; + chomp($ShotType = <>); + $ShotType = int($ShotType); + if ($ShotType < 0 || $ShotType > 4) + { + print "INCORRECT ANSWER. RETYPE IT. "; + } + } +} + +sub center_jump +{ + print "CENTER JUMP\n"; + if (rand(1) <= 0.6) + { + print "$Opponent CONTROLS THE TAP.\n"; + return "opp"; + } + print "DARTMOUTH CONTROLS THE TAP.\n"; + return "dart"; +} + +sub check_end_game +{ + print "\n"; + if ($Score[1] != $Score[0]) + { + print " ***** END OF GAME *****\n"; + print "FINAL SCORE: DARTMOUTH: $Score[1] $Opponent: $Score[0]\n\n"; + $DoPlay = 0; + } + else + { + print "\n ***** END OF SECOND HALF *****\n"; + print "SCORE AT END OF REGULATION TIME:\n"; + print " DARTMOUTH: $Score[1] $Opponent: $Score[0]\n\n"; + print "BEGIN TWO MINUTE OVERTIME PERIOD\n"; + } +} + +sub two_min_left +{ + print "\n *** TWO MINUTES LEFT IN THE GAME ***\n\n"; +} + +sub foul_shooting +{ + if (rand(1) > 0.49) + { + if (rand(1) > 0.75) + { + print "BOTH SHOTS MISSED.\n"; + } + else + { + print "SHOOTER MAKES ONE SHOT AND MISSES ONE.\n"; + $Score[1 - $Player]++; + } + } + else + { + print "SHOOTER MAKES BOTH SHOTS.\n"; + $Score[1 - $Player] += 2; + } + + print_score(); +} diff --git a/13_Bounce/perl/README.md b/13_Bounce/perl/README.md index e69c8b81..6d213dba 100644 --- a/13_Bounce/perl/README.md +++ b/13_Bounce/perl/README.md @@ -1,3 +1,6 @@ Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html) Conversion to [Perl](https://www.perl.org/) + +Added feature so that if "TIME" value is "0" then it will quit, +so you don't have to hit Control-C. Also added a little error checking of the input. diff --git a/13_Bounce/perl/bounce.pl b/13_Bounce/perl/bounce.pl new file mode 100755 index 00000000..4aeacf1f --- /dev/null +++ b/13_Bounce/perl/bounce.pl @@ -0,0 +1,97 @@ +#!/usr/bin/perl + +# Bounce program in Perl +# Translated by Kevin Brannen (kbrannen) + +use strict; +use warnings; + +print "\n"; +print " " x 31,"BOUNCE\n"; +print " " x 15, "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n"; +print "\n\n\n"; + +print "THIS SIMULATION LETS YOU SPECIFY THE INITIAL VELOCITY\n"; +print "OF A BALL THROWN STRAIGHT UP, AND THE COEFFICIENT OF\n"; +print "ELASTICITY OF THE BALL. PLEASE USE A DECIMAL FRACTION\n"; +print "COEFFICIENCY (LESS THAN 1).\n\n"; +print "YOU ALSO SPECIFY THE TIME INCREMENT TO BE USED IN\n"; +print "'STROBING' THE BALL'S FLIGHT (TRY .1 INITIALLY).\n\n"; + +# deal with basic's tab() for line positioning +# line = line string we're starting with +# pos = position to start writing +# s = string to write +# returns the resultant string, which might not have been changed +sub line_tab +{ + my ($line, $pos, $s) = @_; + my $len = length($line); + # if curser is past position, do nothing + if ($len <= $pos) { $line .= " " x ($pos - $len) . $s; } + return $line; +} + +while (1) +{ + my @T; # time slice? + my $time_inc; # time increment, probably in fractions of seconds + my $velocity; # velocity in feet/sec + my $coeff_elas; # coeeficent of elasticity + my $line_pos; # position on line + my $S1 # duration in full seconds? + + # get input + print "TIME INCREMENT (SEC, 0=QUIT): "; chomp($time_inc = <>); + last if ($time_inc == 0); + print "VELOCITY (FPS): "; chomp($velocity = <>); + print "COEFFICIENT: "; chomp($coeff_elas = <>); + if ($coeff_elas >= 1.0 || $coeff_elas <= 0) + { + print "COEFFICIENT MUST BE > 0 AND < 1.0\n\n\n"; + next; + } + + print "\nFEET\n"; + $S1 = int(70.0 / ($velocity / (16.0 * $time_inc))); + for my $i (1 .. $S1) + { + $T[$i] = $velocity * $coeff_elas ** ($i - 1) / 16.0; + } + + # draw graph + for (my $height=int(-16.0 * ($velocity / 32.0) ** 2.0 + $velocity ** 2.0 / 32.0 + .5) ; $height >= 0 ; $height -= .5) + { + if (int($height) == $height) { print sprintf("%2d", $height); } + else { print " "; } + $line_pos = 0; + my $curr_line = ""; + for my $i (1 .. $S1) + { + my $time; + for ($time=0 ; $time <= $T[$i] ; $time += $time_inc) + { + $line_pos += $time_inc; + if (abs($height - (.5 * (-32) * $time ** 2.0 + $velocity * $coeff_elas ** ($i - 1) * $time)) <= .25) + { + $curr_line = line_tab($curr_line, ($line_pos / $time_inc), "0"); + } + } + $time = ($T[$i + 1] // 0) / 2; # we can reach 1 past the end, use 0 if that happens + last if (-16.0 * $time ** 2.0 + $velocity * $coeff_elas ** ($i - 1) * $time < $height); + } + print "$curr_line\n"; + } + + # draw scale + print " ."; + print "." x (int($line_pos + 1) / $time_inc + 1), "\n"; + print " 0"; + my $curr_line = ""; + for my $i (1 .. int($line_pos + .9995)) + { + $curr_line = line_tab($curr_line, int($i / $time_inc), $i); + } + print "$curr_line\n"; + print " " x (int($line_pos + 1) / (2 * $time_inc) - 2), "SECONDS\n\n"; +} diff --git a/14_Bowling/perl/README.md b/14_Bowling/perl/README.md index e69c8b81..9e030cb0 100644 --- a/14_Bowling/perl/README.md +++ b/14_Bowling/perl/README.md @@ -1,3 +1,22 @@ Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html) Conversion to [Perl](https://www.perl.org/) + +###Bowling program in Perl + +Run normally, this is a fairly faithful translation of the Basic game. +The only real differences are a few trivial fix-ups on the prints to make it +look better, and the player/frame/ball line was put before the "get the ball +going" line to make it more obvious who's turn it is. + +However, if you run it with "-a" on the command line, it will go into +"advanced" mode, which means that "." is used to show pin down and "!" for +pin up, current running scores are shown at the end of each frame, and the +scoring also looks more normal at the end. This is all done because I think it +looks better and I wanted to see a score. Having a flag says you can play +whichever version of the game you like. + +Note, the original code doesn't do the 10th frame correctly, in that it will +never do more than 2 balls, so the best score you can get is a 290. +This is true in both modes. That being said, it will always give you a mediocre +game; I don't think I've ever seen a score over 140. diff --git a/14_Bowling/perl/bowling.pl b/14_Bowling/perl/bowling.pl new file mode 100755 index 00000000..1a00cd6b --- /dev/null +++ b/14_Bowling/perl/bowling.pl @@ -0,0 +1,254 @@ +#!/usr/bin/perl + +# Bowling program in Perl +# Run normally, this is a fairly faithful translation of the Basic game. +# The only real differences are a few trivial fix-ups on the prints to make it +# look better, and the player/frame/ball line was put before the "get the ball +# going" line to make it more obvious who's turn it is. +# +# However, if you run it with "-a" on the command line, it will go into +# 'advanced' mode, which means that "." is used to show pin down and "!" for +# pin up, current running scores are shown at the end of each frame, and the +# scoring also looks more normal at the end. This is all done because I think it +# looks better and I wanted to see a score. Having a flag says you can play +# whichever version of the game you like. +# +# Note, the original code doesn't do the 10th frame correctly, in that it will +# never do more than 2 balls, so the best score you can get is a 290. +# This is true in both modes. That being said, it will always give you a mediocre game. +# Translated by Kevin Brannen (kbrannen) + +use strict; +use warnings; + +print "\n"; +print " " x 34, "BASKETBALL\n"; +print " " x 15, "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n\n\n"; + +# globals +my @C; # pin position matraix? +my @Scores; # scores: [player num][frame][ball] # ball3 is end-result, ball4 is frame score (advanced mode) +my $Answer; # get answers +my $Num_players; # number of players +my $Advanced = 0; # flag, 1 to use advanced code, 0 for original code +my $char_down = '0'; # char to show pin is down +my $char_up = '+'; # char to show pin is standing + +if ($ARGV[0] && $ARGV[0] eq "-a") +{ + shift; + $Advanced = 1; + $char_down = '.'; + $char_up = '!'; +} + +print "WELCOME TO THE ALLEY\n"; +print "BRING YOUR FRIENDS\n"; +print "OKAY LET'S FIRST GET ACQUAINTED\n\n"; +print "SEE THE INSTRUCTIONS (Y/N): "; +chomp($Answer = uc(<>)); +if ($Answer eq "Y") +{ + print "THE GAME OF BOWLING TAKES MIND AND SKILL. DURING THE GAME\n"; + print "THE COMPUTER WILL KEEP SCORE.YOU MAY COMPETE WITH\n"; + print "OTHER PLAYERS[UP TO FOUR]. YOU WILL BE PLAYING TEN FRAMES\n"; + print "ON THE PIN DIAGRAM 'O' MEANS THE PIN IS DOWN...'+' MEANS THE\n"; + print "PIN IS STANDING. AFTER THE GAME THE COMPUTER WILL SHOW YOUR SCORES.\n"; +} + +do { + print "FIRST OF ALL...HOW MANY ARE PLAYING (1-4): "; + $Num_players = int(<>); +} while ($Num_players < 1 || $Num_players > 4); + +print "\nVERY GOOD...\n"; + +while (1) +{ + # reset all scores + for my $p (1 .. $Num_players) # players + { + for my $f (1 .. 10) # frames + { + for my $b (1 .. 3) # balls + { + $Scores[$p][$f][$b] = 0; + } + } + } + + # play the game + for my $frame (1 .. 10) # frame + { + for my $curr_player (1 .. $Num_players) # player + { + my $last_pins=0; # pins down for last ball + my $ball=1; # ball number, 1 or 2 + my $end_result=0; # result at end of turn: 3=strike, 2=spare, 1=pins-left + for my $i (1 .. 15) { $C[$i] = 0 } + + while (1) # another ball + { + # ARK BALL GENERATOR USING MOD '15' SYSTEM + my $K=0; + my $curr_pins=0; # pins down for this ball + for my $i (1 .. 20) + { + my $x = int(rand(1) * 100); + my $j; + for ($j=1 ; $j <= 10 ; $j++) + { + last if ($x < 15 * $j); + } + $C[15 * $j - $x] = 1; + } + + # ARK PIN DIAGRAM + print "PLAYER: $curr_player FRAME: $frame BALL: $ball\n"; + print "PRESS ENTER TO GET THE BALL GOING."; + $Answer = <>; # not used, just need an enter + for my $i (0 .. 3) + { + print "\n"; + print " " x $i; # avoid the TAB(), just shift each row over for the triangle + for my $j (1 .. 4 - $i) + { + $K++; + print ($C[$K] == 1 ? " $char_down" : " $char_up"); + } + } + print "\n"; + + # ARK ROLL ANALYSIS + for my $i (1 .. 10) + { + $curr_pins += $C[$i]; + } + if ($curr_pins - $last_pins == 0) + { + print "GUTTER!!\n"; + } + if ($ball == 1 && $curr_pins == 10) + { + print "STRIKE!!!!!\a\a\a\a\n"; # \a is for bell + $end_result = 3; + } + elsif ($ball == 2 && $curr_pins == 10) + { + print "SPARE!!!!\n"; + $end_result = 2; + } + elsif ($ball == 2 && $curr_pins < 10) + { + if ($Advanced) { print 10 - $curr_pins, " PENS LEFT!!!\n"; } + else { print "ERROR!!!\n"; } + $end_result = 1; + } + if ($ball == 1 && $curr_pins < 10) + { + print "ROLL YOUR 2ND BALL\n"; + } + print "\n"; + + # ARK STORAGE OF THE SCORES + if ($Advanced) { $Scores[$curr_player][$frame][$ball] = $curr_pins - $last_pins; } + else { $Scores[$curr_player][$frame][$ball] = $curr_pins; } + if ($ball == 1) + { + $ball = 2; + $last_pins = $curr_pins; + + if ($end_result == 3) # strike, no more rolls, goto last + { + $Scores[$curr_player][$frame][$ball] = $curr_pins; + } + else + { + $Scores[$curr_player][$frame][$ball] = $curr_pins - $last_pins; + next if ($end_result == 0); # next roll + } + } + last; + } + $Scores[$curr_player][$frame][3] = $end_result; + } # next player + if ($Advanced) + { + print "Scores:\n"; + for my $p (1 .. $Num_players) + { + my $total = calc_score($p); + print "\tPlayer $p: $total\n"; + } + print "\n"; + } + } # next frame + + # end of game, show full scoreboard + show_scoreboard(); + + print "DO YOU WANT ANOTHER GAME (Y/N): "; + chomp($Answer = uc(<>)); + print "\n"; + last if ($Answer ne "Y"); +} +exit(0); + +sub show_scoreboard +{ + print "FRAMES\n"; + for my $i (1 .. 10) + { + print " $i "; + } + print "\n"; + my @results = ( "-", ".", "/", "X" ); + for my $p (1 .. $Num_players) + { + print "Player $p\n" if ($Advanced); + my $ball_max = ($Advanced ? 4 : 3); + for my $b (1 .. $ball_max) + { + for my $f (1 .. 10) + { + if ($b != 3) { print sprintf("%2d ", $Scores[$p][$f][$b]); } + else { print sprintf("%2s ", $results[$Scores[$p][$f][$b]]); } + } + print "\n"; + } + print "\n"; + } +} + +sub calc_score +{ + my $player = shift; + my $total = 0; + for my $frame (1 .. 10) + { + my $score = 0; + if ($frame == 10 || $Scores[$player][$frame][3] == 1) # pins + { + $score = $Scores[$player][$frame][1] + $Scores[$player][$frame][2]; + } + elsif ($Scores[$player][$frame][3] == 2) # spare + { + $score = 10 + $Scores[$player][$frame+1][1]; + } + elsif ($Scores[$player][$frame][3] == 3) # strike + { + $score = 10 + $Scores[$player][$frame+1][1]; + if ($Scores[$player][$frame+1][1] == 10) + { + $score += ($frame < 9 ? $Scores[$player][$frame+2][1] : $Scores[$player][$frame+1][2]); + } + else + { + $score += $Scores[$player][$frame+1][2]; + } + } + $Scores[$player][$frame][4] = $score; + $total += $score; + } + return $total; +} diff --git a/15_Boxing/perl/boxing.pl b/15_Boxing/perl/boxing.pl new file mode 100644 index 00000000..5068e58a --- /dev/null +++ b/15_Boxing/perl/boxing.pl @@ -0,0 +1,252 @@ +#!/usr/bin/perl + +# Boxing program in Perl +# Required extensive restructuring to remove all of the GOTO's. +# Translated by Kevin Brannen (kbrannen) + +use strict; +use warnings; + +# globals +my $Opp_won = 0; # num rounds opponent has won +my $You_won = 0; # num rounds you have won +my $Opp_name = ""; # opponent name +my $Your_name = ""; # your name +my $Your_best = 0; # your best punch +my $Your_worst = 0; # your worst punch +my $Opp_best; # opponent best punch +my $Opp_worst; # opponent worst punch +my $Opp_damage; # opponent damage ? +my $Your_damage; # your damage ? + +sub get_punch +{ + my $prompt = shift; + my $p; + while (1) + { + print "$prompt: "; + chomp($p = int(<>)); + last if ($p >= 1 && $p <= 4); + print "DIFFERENT PUNCHES ARE: (1) FULL SWING; (2) HOOK; (3) UPPERCUT; (4) JAB.\n"; + } + return $p; +} + +print "\n"; +print " " x 33, "BOXING\n"; +print " " x 15, "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n\n\n"; + +print "BOXING OLYMPIC STYLE (3 ROUNDS -- 2 OUT OF 3 WINS)\n\n"; +print "WHAT IS YOUR OPPONENT'S NAME: "; +chomp($Opp_name = <>); +print "INPUT YOUR MAN'S NAME: "; +chomp($Your_name = <>); +print "DIFFERENT PUNCHES ARE: (1) FULL SWING; (2) HOOK; (3) UPPERCUT; (4) JAB.\n"; +$Your_best = get_punch("WHAT IS YOUR MANS BEST"); +$Your_worst = get_punch("WHAT IS HIS VULNERABILITY"); + +do { + $Opp_best = int(4*rand(1)+1); + $Opp_worst = int(4*rand(1)+1); +} while ($Opp_best == $Opp_worst); +print "$Opp_name\'S ADVANTAGE IS $Opp_best AND VULNERABILITY IS SECRET.\n\n"; + +for my $R (1 .. 3) # rounds +{ + last if ($Opp_won >= 2 || $You_won >= 2); + $Opp_damage = 0; + $Your_damage = 0; + print "ROUND $R BEGINS...\n"; + for my $R1 (1 .. 7) # 7 events per round? + { + if (int(10*rand(1)+1) <= 5) + { + my $your_punch = get_punch("$Your_name\'S PUNCH"); + $Opp_damage += 2 if ($your_punch == $Your_best); + + if ($your_punch == 1) { punch1(); } + elsif ($your_punch == 2) { punch2(); } + elsif ($your_punch == 3) { punch3(); } + else { punch4(); } + next; + } + + my $Opp_punch = int(4*rand(1)+1); + $Your_damage += 2 if ($Opp_punch == $Opp_best); + + if ($Opp_punch == 1) { opp1(); } + elsif ($Opp_punch == 2) { opp2(); } + elsif ($Opp_punch == 3) { opp3(); } + else { opp4(); } + } + + if ($Opp_damage > $Your_damage) + { + print "\n$Your_name WINS ROUND $R\n\n"; + $You_won++; + } + else + { + print "\n$Opp_name WINS ROUND $R\n\n"; + $Opp_won++; + } +} + +if ($Opp_won >= 2) +{ + done("$Opp_name WINS (NICE GOING, $Opp_name)."); +} + +#else # if ($You_won >= 2) +done("$Your_name AMAZINGLY WINS!!"); + +################################################### + +sub done +{ + my $msg = shift; + print $msg; + print "\n\nAND NOW GOODBYE FROM THE OLYMPIC ARENA.\n\n"; + exit(0); +} + +sub punch1 +{ + # $your_punch == 1, full swing + print "$Your_name SWINGS AND "; + if ($Opp_worst == 4 || int(30*rand(1)+1) < 10) + { + print "HE CONNECTS!\n"; + if ($Opp_damage > 35) + { + done("$Opp_name IS KNOCKED COLD AND $Your_name IS THE WINNER AND CHAMP! "); + } + $Opp_damage += 15; + } + else + { + print "HE MISSES\n"; + print "\n\n" if ($Opp_damage != 1); + } +} + +sub punch2 +{ + # $your_punch == 2, hook + print "$Your_name GIVES THE HOOK... "; + if ($Opp_worst == 2) + { + $Opp_damage += 7; + return; + } + if (int(2*rand(1)+1) == 1) + { + print "BUT IT'S BLOCKED!!!!!!!!!!!!!\n"; + } + else + { + print "CONNECTS...\n"; + $Opp_damage += 7; + } +} + +sub punch3 +{ + # $your_punch == 3, uppercut + print "$Your_name TRIES AN UPPERCUT "; + if ($Opp_worst == 3 || int(100*rand(1)+1) < 51) + { + print "AND HE CONNECTS!\n"; + $Opp_damage += 4; + } + else + { + print "AND IT'S BLOCKED (LUCKY BLOCK!)\n"; + } +} + +sub punch4 +{ + # $your_punch == 4, jab + print "$Your_name JABS AT $Opp_name\'S HEAD "; + if ($Opp_worst == 4 || (int(8*rand(1)+1)) >= 4) + { + $Opp_damage += 3; + print "\n"; + } + else + { + print "IT'S BLOCKED.\n"; + } +} + +sub opp1 +{ + # opp_punch == 1 + print "$Opp_name TAKES A FULL SWING AND "; + if ($Your_worst == 1 || int(60*rand(1)+1) < 30) + { + print " POW!!!!! HE HITS HIM RIGHT IN THE FACE!\n"; + if ($Your_damage > 35) + { + done("$Your_name IS KNOCKED COLD AND $Opp_name IS THE WINNER AND CHAMP!"); + } + $Your_damage += 15; + } + else + { + print " IT'S BLOCKED!\n"; + } +} + +sub opp2 +{ + # opp_punch == 2 + print "$Opp_name GETS $Your_name IN THE JAW (OUCH!)\n"; + $Your_damage += 7; + print "....AND AGAIN!\n"; + $Your_damage += 5; + if ($Your_damage > 35) + { + done("$Your_name IS KNOCKED COLD AND $Opp_name IS THE WINNER AND CHAMP!"); + } + print "\n"; + # 2 continues into opp_punch == 3 + opp3(); +} + +sub opp3() +{ + # opp_punch == 3 + print "$Your_name IS ATTACKED BY AN UPPERCUT (OH,OH)...\n"; + if ($Your_worst != 3 && int(200*rand(1)+1) > 75) + { + print " BLOCKS AND HITS $Opp_name WITH A HOOK.\n"; + $Opp_damage += 5; + } + else + { + print "AND $Opp_name CONNECTS...\n"; + $Your_damage += 8; + } +} + +sub opp4 +{ + # opp_punch == 4 + print "$Opp_name JABS AND "; + if ($Your_worst == 4) + { + $Your_damage += 5; + } + elsif (int(7*rand(1)+1) > 4) + { + print " BLOOD SPILLS !!!\n"; + $Your_damage += 5; + } + else + { + print "IT'S BLOCKED!\n"; + } +} diff --git a/17_Bullfight/README.md b/17_Bullfight/README.md index cf331235..c801b16e 100644 --- a/17_Bullfight/README.md +++ b/17_Bullfight/README.md @@ -27,3 +27,5 @@ http://www.vintage-basic.net/games.html #### Porting Notes (please note any difficulties or challenges in porting here) + +- There is a fundamental assumption in the pre-fight subroutine at line 1610, that the Picadores and Toreadores are more likely to do a bad job (and possibly get killed) with a low-quality bull. This appears to be a mistake in the original code, but should be retained. \ No newline at end of file diff --git a/17_Bullfight/kotlin/src/bullfight/Main.kt b/17_Bullfight/kotlin/src/bullfight/Main.kt new file mode 100644 index 00000000..7a1a0681 --- /dev/null +++ b/17_Bullfight/kotlin/src/bullfight/Main.kt @@ -0,0 +1,356 @@ +package bullfight + +import bullfight.Yorn.* +import kotlin.random.Random +import kotlin.system.exitProcess + +private val Boolean.asInteger get() = if (this) 1 else 2 +private val Float.squared: Float + get() = this * this +val fna: Boolean get() = RandomNumbers.nextBoolean() + +enum class Quality(private val typeName: String) { + Superb("SUPERB"), + Good("GOOD"), + Fair("FAIR"), + Poor("POOR"), + Awful("AWFUL"); + override fun toString() = typeName + + val level get() = (ordinal + 1).toFloat() +} + +enum class BullDeath(val factor: Float) { + Alive(1f), Dead(2f); +} + +var l = 1f +lateinit var bullQuality: Quality +var momentOfTruth = false +var picadoresSuccess = 0f +var toreadoresSuccess = 0f +var passNumber = 0f +var honor = 0f +var bullDeath = BullDeath.Alive + + +interface RandomNumberSource { + fun nextBoolean(): Boolean + fun nextInt(from: Int, until: Int): Int + fun nextFloat(): Float +} + +object RandomNumbers : RandomNumberSource { + override fun nextBoolean() = Random.nextBoolean() + override fun nextInt(from: Int, until: Int) = Random.nextInt(from, until) + override fun nextFloat() = Random.nextFloat() +} + + +fun main() { + intro() + instructions() + bullDeath = BullDeath.Alive + honor = 1f + + bullQuality = Quality.values()[RandomNumbers.nextInt(1, 6)] + println("YOU HAVE DRAWN A $bullQuality BULL.") + when (bullQuality) { + Quality.Superb -> println("GOOD LUCK. YOU'LL NEED IT.") + Quality.Awful -> println("YOU'RE LUCKY.") + else -> Unit + } + + picadoresSuccess = fight(FirstAct.picadores) + toreadoresSuccess = fight(FirstAct.toreadores) + println() + println() + + var gored: Boolean + + gameLoop@ do { + passNumber++ + println("PASS NUMBER ${passNumber.toInt()}") + gored = if (passNumber >= 3) { + print("HERE COMES THE BULL. TRY FOR A KILL") + killAttempt("CAPE MOVE") + } else { + println("THE BULL IS CHARGING AT YOU! YOU ARE THE MATADOR--") + print("DO YOU WANT TO KILL THE BULL") + killAttempt("WHAT MOVE DO YOU MAKE WITH THE CAPE") + } + + if (!gored) { + val move = restrictedInput( + values = Cape.values(), + errorMessage = "DON'T PANIC, YOU IDIOT! PUT DOWN A CORRECT NUMBER" + ) + val m = when (move) { + Cape.Veronica -> 3f + Cape.Outside -> 2f + Cape.Swirl -> 0.5f + } + + l += m + val f = + (6 - bullQuality.level + m / 10f) * RandomNumbers.nextFloat() / ((picadoresSuccess + toreadoresSuccess + passNumber / 10f) * 5f) + if (f < 0.51) + continue + } + + println("THE BULL HAS GORED YOU!") + goreLoop@ do { + when (fna) { + false -> { + println("YOU ARE DEAD.") + honor = 1.5f + gameResult() + } + + true -> { + println("YOU ARE STILL ALIVE.") + println() + print("DO YOU RUN FROM THE RING") + when (Yorn.input()) { + + YES -> { + println("COWARD") + honor = 0f + } + + NO -> { + println("YOU ARE BRAVE. STUPID, BUT BRAVE.") + when (fna) { + true -> { + honor = 2f + continue@gameLoop + } + + false -> { + println("YOU ARE GORED AGAIN!") + continue@goreLoop + } + } + } + } + + } + } + } while (true) + + } while (true) + +} + +fun fnd() = 4.5 + + l / 6 - + (picadoresSuccess + toreadoresSuccess) * 2.5 + + 4 * honor + + 2 * bullDeath.factor - + passNumber.squared / 120f - + bullQuality.level + +fun fnc() = fnd() * RandomNumbers.nextFloat() + +private fun killAttempt(capeMessage: String): Boolean { + when (Yorn.input()) { + + YES -> + when (momentOfTruth()) { + KillResult.Success -> gameResult() + KillResult.Fail -> return true + } + + NO -> + print(capeMessage) + + } + return false +} + +private fun gameResult() { + println() + println() + if (honor == 0f) { + println( + """ + THE CROWD BOOS FOR TEN MINUTES. IF YOU EVER DARE TO SHOW + YOUR FACE IN A RING AGAIN, THEY SWEAR THEY WILL KILL YOU-- + UNLESS THE BULL DOES FIRST. + """.trimIndent() + ) + } else { + if (honor == 2f) + println("THE CROWD CHEERS WILDLY!") + else + if (bullDeath == BullDeath.Dead) { + println("THE CROWD CHEERS!") + println() + } + println("THE CROWD AWARDS YOU") + if (fnc() < 2.4) + println("NOTHING AT ALL.") + else if (fnc() < 4.9) + println("ONE EAR OF THE BULL.") + else + if (fnc() < 7.4) + println("BOTH EARS OF THE BULL!") + else + println("OLE! YOU ARE 'MUY HOMBRE'!! OLE! OLE!") + println() + } + println() + println("ADIOS") + println() + println() + println() + exitProcess(0) +} + +enum class KillResult { Success, Fail } + +fun momentOfTruth(): KillResult { + momentOfTruth = true + print( + """ + + IT IS THE MOMENT OF TRUTH. + + HOW DO YOU WANT TO KILL THE BULL + """.trimIndent() + ) + + val k = + (6 - bullQuality.level) * 10 * RandomNumbers.nextFloat() / ((picadoresSuccess + toreadoresSuccess) * 5 * passNumber) + + val chance = when (stdInput(KillMethod.values())) { + KillMethod.OverHorns -> .8 + KillMethod.Chest -> .2 + null -> { + println("YOU PANICKED. THE BULL GORED YOU.") + return KillResult.Fail + } + } + return if (k <= chance) { + println("YOU KILLED THE BULL!") + bullDeath = BullDeath.Dead + KillResult.Success + } else { + println("THE BULL HAS GORED YOU!") + KillResult.Fail + } +} + +interface InputOption { + val input: String +} + +enum class Cape(override val input: String) : InputOption { + Veronica("0"), + Outside("1"), + Swirl("2"), +} + +enum class KillMethod(override val input: String) : InputOption { + OverHorns("4"), + Chest("5"), +} + +private fun restrictedInput(values: Array, errorMessage: String): T { + do { + stdInput(values)?.let { return it } + println(errorMessage) + } while (true) +} + +private fun stdInput(values: Array): T? { + print("? ") + val z1 = readln() + return values.firstOrNull { z1 == it.input } +} + +enum class Yorn(override val input: String) : InputOption { + YES("YES"), NO("NO"); + + companion object { + fun input(): Yorn { + return restrictedInput(values(), "YES OR NO") + } + } +} + +enum class FirstAct(val str: String) { + picadores("PICADORES"), toreadores("TOREADORES"); + + override fun toString() = str +} + +fun fight(firstAct: FirstAct): Float { + + val b = 3.0 / bullQuality.level * RandomNumbers.nextFloat() + val firstActQuality = when { + b < .37 -> Quality.Awful + b < .5 -> Quality.Poor + b < .63 -> Quality.Fair + b < .87 -> Quality.Good + else -> Quality.Superb + } + val c = firstActQuality.level / 10f + val t = firstActQuality.level + println("THE $firstAct DID A $firstActQuality JOB.") + + if (t >= 4f) { + if (t == 5f) { + if (firstAct != FirstAct.toreadores) { + println("${fna.asInteger} OF THE HORSES OF THE $firstAct KILLED.") + } + println("${fna.asInteger} OF THE $firstAct KILLED.") + } else { + println( + when (fna) { + true -> "ONE OF THE $firstAct WAS KILLED." + false -> "NO $firstAct WERE KILLED." + } + ) + } + } + println() + + return c +} + +private fun instructions() { + print("DO YOU WANT INSTRUCTIONS? ") + if (readln().trim() != "NO") { + println("HELLO, ALL YOU BLOODLOVERS AND AFICIONADOS.") + println("HERE IS YOUR BIG CHANCE TO KILL A BULL.") + println() + println("ON EACH PASS OF THE BULL, YOU MAY TRY") + println("0 - VERONICA (DANGEROUS INSIDE MOVE OF THE CAPE)") + println("1 - LESS DANGEROUS OUTSIDE MOVE OF THE CAPE") + println("2 - ORDINARY SWIRL OF THE CAPE.") + println() + println("INSTEAD OF THE ABOVE, YOU MAY TRY TO KILL THE BULL") + println("ON ANY TURN: 4 (OVER THE HORNS), 5 (IN THE CHEST).") + println("BUT IF I WERE YOU,") + println("I WOULDN'T TRY IT BEFORE THE SEVENTH PASS.") + println() + println("THE CROWD WILL DETERMINE WHAT AWARD YOU DESERVE") + println("(POSTHUMOUSLY IF NECESSARY).") + println("THE BRAVER YOU ARE, THE BETTER THE AWARD YOU RECEIVE.") + println() + println("THE BETTER THE JOB THE PICADORES AND TOREADORES DO,") + println("THE BETTER YOUR CHANCES ARE.") + } + repeat(2) { + println() + } +} + +fun intro() { + println(" ".repeat(34) + "BULL") + println(" ".repeat(15) + "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY") + println() + println() + println() +} diff --git a/21_Calendar/rust/Cargo.toml b/21_Calendar/rust/Cargo.toml new file mode 100644 index 00000000..1ec69633 --- /dev/null +++ b/21_Calendar/rust/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "rust" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/21_Calendar/rust/README.md b/21_Calendar/rust/README.md new file mode 100644 index 00000000..e616424e --- /dev/null +++ b/21_Calendar/rust/README.md @@ -0,0 +1,3 @@ +Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html) + +Conversion to [Rust](https://www.rust-lang.org/) by [Uğur Küpeli](https://github.com/ugurkupeli) \ No newline at end of file diff --git a/21_Calendar/rust/src/main.rs b/21_Calendar/rust/src/main.rs new file mode 100644 index 00000000..a279f95d --- /dev/null +++ b/21_Calendar/rust/src/main.rs @@ -0,0 +1,152 @@ +use std::io::stdin; + +const WIDTH: usize = 64; +const DAYS_WIDTH: usize = WIDTH / 8; +const MONTH_WIDTH: usize = WIDTH - (DAYS_WIDTH * 2); +const DAY_NUMS_WIDTH: usize = WIDTH / 7; + +const DAYS: [&str; 7] = [ + "SUNDAY", + "MONDAY", + "TUESDAY", + "WEDNESDAY", + "THURSDAY", + "FRIDAY", + "SATURDAY", +]; + +fn main() { + println!("\n\t\t CALENDAR"); + println!("CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n"); + + let (starting_day, leap_year) = prompt(); + let (months, total_days) = get_months_and_days(leap_year); + + let mut days_passed = 0; + let mut current_day_index = DAYS.iter().position(|d| *d == starting_day).unwrap(); + + for (month, days) in months { + print_header(month, days_passed, total_days - days_passed); + print_days(&mut current_day_index, days); + days_passed += days as u16; + println!("\n"); + } +} + +fn prompt() -> (String, bool) { + let mut day = String::new(); + + loop { + println!("\nFirst day of the year?"); + if let Ok(_) = stdin().read_line(&mut day) { + day = day.trim().to_uppercase(); + if DAYS.contains(&day.as_str()) { + break; + } else { + day.clear(); + } + } + } + + let mut leap = false; + + loop { + println!("Is this a leap year?"); + let mut input = String::new(); + if let Ok(_) = stdin().read_line(&mut input) { + match input.to_uppercase().trim() { + "Y" | "YES" => { + leap = true; + break; + } + "N" | "NO" => break, + _ => (), + } + } + } + + println!(); + (day, leap) +} + +fn get_months_and_days(leap_year: bool) -> (Vec<(String, u8)>, u16) { + let months = [ + "JANUARY", + "FEBUARY", + "MARCH", + "APRIL", + "MAY", + "JUNE", + "JULY", + "AUGUST", + "SEPTEMBER", + "OCTOBER", + "NOVEMBER", + "DECEMBER", + ]; + + let mut months_with_days = Vec::new(); + let mut total_days: u16 = 0; + + for (i, month) in months.iter().enumerate() { + let days = if i == 1 { + if leap_year { + 29u8 + } else { + 28 + } + } else if if i < 7 { (i % 2) == 0 } else { (i % 2) != 0 } { + 31 + } else { + 30 + }; + + total_days += days as u16; + months_with_days.push((month.to_string(), days)); + } + + (months_with_days, total_days) +} + +fn print_between(s: String, w: usize, star: bool) { + let s = format!(" {s} "); + if star { + print!("{:*^w$}", s); + return; + } + print!("{:^w$}", s); +} + +fn print_header(month: String, days_passed: u16, days_left: u16) { + print_between(days_passed.to_string(), DAYS_WIDTH, true); + print_between(month.to_string(), MONTH_WIDTH, true); + print_between(days_left.to_string(), DAYS_WIDTH, true); + println!(); + + for d in DAYS { + let d = d.chars().nth(0).unwrap(); + print_between(d.to_string(), DAY_NUMS_WIDTH, false); + } + println!(); + + println!("{:*>WIDTH$}", ""); +} + +fn print_days(current_day_index: &mut usize, days: u8) { + let mut current_date = 1u8; + + print!("{:>w$}", " ", w = DAY_NUMS_WIDTH * *current_day_index); + + for _ in 1..=days { + print_between(current_date.to_string(), DAY_NUMS_WIDTH, false); + + if ((*current_day_index + 1) % 7) == 0 { + *current_day_index = 0; + println!(); + } else { + *current_day_index += 1; + } + + current_date += 1; + } +} diff --git a/24_Chemist/README.md b/24_Chemist/README.md index b36f0b04..fc62425a 100644 --- a/24_Chemist/README.md +++ b/24_Chemist/README.md @@ -17,5 +17,7 @@ http://www.vintage-basic.net/games.html (please note any difficulties or challenges in porting here) +There is a type in the original Basic, "...DECIDE **WHO** MUCH WATER..." should be "DECIDE **HOW** MUCH WATER" + #### External Links - C: https://github.com/ericfischer/basic-computer-games/blob/main/24%20Chemist/c/chemist.c diff --git a/24_Chemist/rust/Cargo.toml b/24_Chemist/rust/Cargo.toml new file mode 100644 index 00000000..3b1d02f5 --- /dev/null +++ b/24_Chemist/rust/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "rust" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rand = "0.8.5" diff --git a/24_Chemist/rust/README.md b/24_Chemist/rust/README.md new file mode 100644 index 00000000..7e85f9a1 --- /dev/null +++ b/24_Chemist/rust/README.md @@ -0,0 +1,3 @@ +Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html) + +Conversion to [Rust](https://www.rust-lang.org/) by Anthony Rubick [AnthonyMichaelTDM](https://github.com/AnthonyMichaelTDM) diff --git a/24_Chemist/rust/src/lib.rs b/24_Chemist/rust/src/lib.rs new file mode 100644 index 00000000..b3618ccf --- /dev/null +++ b/24_Chemist/rust/src/lib.rs @@ -0,0 +1,158 @@ +/* + lib.rs contains all the logic of the program +*/ +use rand::{Rng, prelude::thread_rng}; //rng +use std::error::Error; //better errors +use std::io::{self, Write}; //io interactions +use std::{str::FromStr, fmt::Display}; //traits + +//DATA + +/// handles setup for the game +pub struct Config { +} +impl Config { + /// creates and returns a new Config from user input + pub fn new() -> Result> { + //DATA + let config: Config = Config { + }; + + //return new config + return Ok(config); + } +} + +/// run the program +pub fn run(_config: &Config) -> Result<(), Box> { + //DATA + let mut rng = thread_rng(); + let mut lives: i8 = 9; + + let mut amount_of_acid:i8; + + let mut guess:f32; + let mut answer:f32; + + let mut error:f32; + + //Game loop + loop { + //initialize variables + amount_of_acid = rng.gen_range(1..50); + answer = 7.0 * (amount_of_acid as f32/3.0); + + //print starting message / conditions + println!(); + //get guess + guess = loop { + match get_number_from_input(&format!("{} Liters of Kryptocyanic acid. How much water? ", amount_of_acid),0.0,-1.0) { + Ok(num) => break num, + Err(err) => { + eprintln!("{}",err); + continue; + }, + } + }; + + //calculate error + error = (answer as f32 - guess).abs() / guess; + + println!("answer: {} | error: {}%", answer,error*100.); + + //check guess against answer + if error > 0.05 { //error > 5% + println!(" Sizzle! You may have just been desalinated into a blob"); + println!(" of quivering protoplasm!"); + //update lives + lives -= 1; + + if lives <= 0 { + println!(" Your 9 lives are used, but you will be long remembered for"); + println!(" your contributions to the field of comic book chemistry."); + break; + } + else { + println!(" However, you may try again with another life.") + } + } else { + println!(" Good job! You may breathe now, but don't inhale the fumes!"); + println!(); + } + } + + //return to main + Ok(()) +} + +/// gets a string from user input +fn get_string_from_user_input(prompt: &str) -> Result> { + //DATA + let mut raw_input = String::new(); + + //print prompt + print!("{}", prompt); + //make sure it's printed before getting input + io::stdout().flush().expect("couldn't flush stdout"); + + //read user input from standard input, and store it to raw_input, then return it or an error as needed + raw_input.clear(); //clear input + match io::stdin().read_line(&mut raw_input) { + Ok(_num_bytes_read) => return Ok(String::from(raw_input.trim())), + Err(err) => return Err(format!("ERROR: CANNOT READ INPUT!: {}", err).into()), + } +} +/// generic function to get a number from the passed string (user input) +/// pass a min lower than the max to have minimum and maximum bounds +/// pass a min higher than the max to only have a minimum bound +/// pass a min equal to the max to only have a maximum bound +/// +/// Errors: +/// no number on user input +fn get_number_from_input(prompt: &str, min:T, max:T) -> Result> { + //DATA + let raw_input: String; + let processed_input: String; + + + //input loop + raw_input = loop { + match get_string_from_user_input(prompt) { + Ok(input) => break input, + Err(e) => { + eprintln!("{}",e); + continue; + }, + } + }; + + //filter out non-numeric characters from user input + processed_input = raw_input.chars().filter(|c| c.is_numeric()).collect(); + + //from input, try to read a number + match processed_input.trim().parse() { + Ok(i) => { + //what bounds must the input fall into + if min < max { //have a min and max bound: [min,max] + if i >= min && i <= max {//is input valid, within bounds + return Ok(i); //exit the loop with the value i, returning it + } else { //print error message specific to this case + return Err(format!("ONLY BETWEEN {} AND {}, PLEASE!", min, max).into()); + } + } else if min > max { //only a min bound: [min, infinity) + if i >= min { + return Ok(i); + } else { + return Err(format!("NO LESS THAN {}, PLEASE!", min).into()); + } + } else { //only a max bound: (-infinity, max] + if i <= max { + return Ok(i); + } else { + return Err(format!("NO MORE THAN {}, PLEASE!", max).into()); + } + } + }, + Err(_e) => return Err(format!("Error: couldn't find a valid number in {}",raw_input).into()), + } +} diff --git a/24_Chemist/rust/src/main.rs b/24_Chemist/rust/src/main.rs new file mode 100644 index 00000000..91b1f86a --- /dev/null +++ b/24_Chemist/rust/src/main.rs @@ -0,0 +1,46 @@ +use std::process;//allows for some better error handling + +mod lib; //allows access to lib.rs +use lib::Config; + +/// main function +/// responsibilities: +/// - Calling the command line logic with the argument values +/// - Setting up any other configuration +/// - Calling a run function in lib.rs +/// - Handling the error if run returns an error +fn main() { + //greet user + welcome(); + + // set up other configuration + let mut config = Config::new().unwrap_or_else(|err| { + eprintln!("Problem configuring program: {}", err); + process::exit(1); + }); + + // run the program + if let Err(e) = lib::run(&mut config) { + eprintln!("Application Error: {}", e); //use the eprintln! macro to output to standard error + process::exit(1); //exit the program with an error code + } + + //end of program + println!("THANKS FOR PLAYING!"); +} + +/// print the welcome message +fn welcome() { + println!(" + Chemist + CREATIVE COMPUTING MORRISTOWN, NEW JERSEY + + +The fictitious chemical kryptocyanic acid can only be +diluted by the ratio of 7 parts water to 3 parts acid. +If any other ratio is attempted, the acid becomes unstable +and soon explodes. Given the amount of acid, you must +decide how much water to add for dilution. If you miss +you face the consequences. + "); +} diff --git a/25_Chief/lua/README.md b/25_Chief/lua/README.md index c063f42f..937d758c 100644 --- a/25_Chief/lua/README.md +++ b/25_Chief/lua/README.md @@ -1,3 +1,24 @@ Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html) -Conversion to [Lua](https://www.lua.org/) +Conversion to [Lua](https://www.lua.org/) by Alex Conconi + +--- + +### Lua porting notes + +- I did not like the old Western movie language style in the game introduction +and decided to tone it down, even if this deviates from the original BASIC +version. + +- The `craps_game` function contains the main game logic: it + - prints the game credits and presents the intro question; + - asks for the end result and computes the original numer; + - calls `explain_solution` to print the various steps of the computation; + - presents the outro question and prints a `bolt` if necessary. + +- Added basic input validation to accept only valid integers for numeric input. + +- Minor formatting edits (lowercase, punctuation). + +- Any answer to a "yes or no" question is regarded as "yes" if the input line +starts with 'y' or 'Y', else no. diff --git a/25_Chief/lua/chief.lua b/25_Chief/lua/chief.lua new file mode 100644 index 00000000..6ac885b2 --- /dev/null +++ b/25_Chief/lua/chief.lua @@ -0,0 +1,121 @@ +--[[ +Chief + +From: BASIC Computer Games (1978) +Edited by David H. Ahl + + In the words of the program author, John Graham, “CHIEF is designed to + give people (mostly kids) practice in the four operations (addition, + multiplication, subtraction, and division). + + It does this while giving people some fun. And then, if the people are + wrong, it shows them how they should have done it. + + CHIEF was written by John Graham of Upper Brookville, New York. + + +Lua port by Alex Conconi, 2022. +]]-- + + +--- Helper function for tabulating messages. +local function space(n) return string.rep(" ", n) end + + +--- Generates a multi-line string representing a lightning bolt +local function bolt() + local bolt_lines = {} + for n = 29, 21, -1 do + table.insert(bolt_lines, space(n) .. "x x") + end + table.insert(bolt_lines, space(20) .. "x xxx") + table.insert(bolt_lines, space(19) .. "x x") + table.insert(bolt_lines, space(18) .. "xx x") + for n = 19, 12, -1 do + table.insert(bolt_lines, space(n) .. "x x") + end + table.insert(bolt_lines, space(11) .. "xx") + table.insert(bolt_lines, space(10) .. "x") + table.insert(bolt_lines, space(9) .. "*\n") + table.insert(bolt_lines, string.rep("#", 25) .. "\n") + return table.concat(bolt_lines, "\n") +end + + +--- Print the prompt and read a yes/no answer from stdin. +local function ask_yes_or_no(prompt) + io.stdout:write(prompt .. " ") + local answer = string.lower(io.stdin:read("*l")) + -- any line starting with a 'y' or 'Y' is considered a 'yes' + return answer:sub(1, 1) == "y" +end + + +--- Print the prompt and read a valid number from stdin. +local function ask_number(prompt) + io.stdout:write(prompt .. " ") + while true do + local n = tonumber(io.stdin:read("*l")) + if n then + return n + else + print("Enter a valid number.") + end + end +end + + +--- Explain the solution to persuade the player. +local function explain_solution() + local k = ask_number("What was your original number?") + -- For clarity we kept the same variable names of the original BASIC version + local f = k + 3 + local g = f / 5 + local h = g * 8 + local i = h / 5 + 5 + local j = i - 1 + print("So you think you're so smart, eh?") + print("Now watch.") + print(k .. " plus 3 equals " .. f .. ". This divided by 5 equals " .. g .. ";") + print("this times 8 equals " .. h .. ". If we divide by 5 and add 5,") + print("we get " .. i .. ", which, minus 1, equals " .. j .. ".") +end + + +--- Main game function. +local function chief_game() + --- Print game introduction and challenge + print(space(29) .. "Chief") + print(space(14) .. "Creative Computing Morristown, New Jersey\n\n") + print("I am Chief Numbers Freek, the great math god.") + if not ask_yes_or_no("Are you ready to take the test you called me out for?") then + print("Shut up, wise tongue.") + end + + -- Print how to obtain the end result. + print(" Take a number and add 3. Divide this number by 5 and") + print("multiply by 8. Divide by 5 and add the same. Subtract 1.") + + -- Ask the result end and reverse calculate the original number. + local end_result = ask_number(" What do you have?") + local original_number = (end_result + 1 - 5) * 5 / 8 * 5 - 3 + + -- If it is an integer we do not want to print any zero decimals. + local int_part, dec_part = math.modf(original_number) + if dec_part == 0 then original_number = int_part end + + -- If the player challenges the answer, print the explanation. + if not ask_yes_or_no("I bet your number was " .. original_number .. ". Am I right?") then + explain_solution() + -- If the player does not accept the explanation, zap them. + if not ask_yes_or_no("Now do you believe me?") then + print("YOU HAVE MADE ME MAD!!!") + print("THERE MUST BE A GREAT LIGHTNING BOLT!\n\n") + print(bolt()) + print("I hope you believe me now, for your sake!!") + end + end +end + +--- Run the game. +chief_game() diff --git a/28_Combat/perl/combat.pl b/28_Combat/perl/combat.pl new file mode 100755 index 00000000..fc44a0e4 --- /dev/null +++ b/28_Combat/perl/combat.pl @@ -0,0 +1,202 @@ +#!/usr/bin/perl + +# Combat program in Perl +# Translated by Kevin Brannen (kbrannen) + +use strict; +use warnings; + +# globals +my $User_army; +my $User_navy; +my $User_AF; +my $Comp_army = 30000; +my $Comp_navy = 20000; +my $Comp_AF = 22000; +my $Attack_type; +my $Attack_num; + +print "\n"; +print " " x 33, "COMBAT\n"; +print " " x 15, "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n\n\n"; + +print "I AM AT WAR WITH YOU.\nWE HAVE 72000 SOLDIERS APIECE.\n\n"; + +do { + print "DISTRIBUTE YOUR FORCES.\n"; + print "\tME\t YOU\n"; + print "ARMY\t$Comp_army\t"; + chomp($User_army = <>); + print "NAVY\t$Comp_navy\t"; + chomp($User_navy = <>); + print "A. F.\t$Comp_AF\t"; + chomp($User_AF = <>); +} while ($User_army + $User_navy + $User_AF > 72000); + +do { + print "YOU ATTACK FIRST. TYPE (1) FOR ARMY; (2) FOR NAVY;\n"; + print "AND (3) FOR AIR FORCE.\n"; + chomp($Attack_num = <>); +} while ($Attack_type < 1 && $Attack_type > 3); +do { + print "HOW MANY MEN\n"; + chomp($Attack_type = <>); +} while ($Attack_type < 0 + || ($Attack_num == 1 && $Attack_type > $User_army) + || ($Attack_num == 2 && $Attack_type > $User_navy) + || ($Attack_num == 3 && $Attack_type > $User_AF)); + +if ($Attack_num == 1) +{ + if ($Attack_type<$User_army/3) + { + print "YOU LOST $Attack_type MEN FROM YOUR ARMY.\n"; + $User_army = int($User_army-$Attack_type); + } + if ($Attack_type<2*$User_army/3) + { + print "YOU LOST ", int($Attack_type/3), " MEN, BUT I LOST ", int(2*$Comp_army/3), "\n"; + $User_army = int($User_army-$Attack_type/3); + $Comp_army = int(2*$Comp_army/3); + } + else + { + s270(); + } +} +elsif ($Attack_num == 2) +{ + if ($Attack_type < $Comp_navy/3) + { + print "YOUR ATTACK WAS STOPPED!\n"; + $User_navy = int($User_navy-$Attack_type); + } + if ($Attack_type < 2*$Comp_navy/3) + { + print "YOU DESTROYED ", int(2*$Comp_navy/3), " OF MY ARMY.\n"; + $Comp_navy = int(2*$Comp_navy/3); + } + else + { + s270(); + } +} +else # $Attack_num == 3 +{ + if ($Attack_type < $User_AF/3) + { + print "YOUR ATTACK WAS WIPED OUT.\n"; + $User_AF = int($User_AF-$Attack_type); + } + if ($Attack_type < 2*$User_AF/3) + { + print "WE HAD A DOGFIGHT. YOU WON - AND FINISHED YOUR MISSION.\n"; + $Comp_army = int(2*$Comp_army/3); + $Comp_navy = int($Comp_navy/3); + $Comp_AF = int($Comp_AF/3); + } + else + { + print "YOU WIPED OUT ONE OF MY ARMY PATROLS, BUT I DESTROYED\n"; + print "TWO NAVY BASES AND BOMBED THREE ARMY BASES.\n"; + $User_army = int($User_army/4); + $User_navy = int($User_navy/3); + $User_AF = int(2*$User_AF/3); + } +} + +print "\n\tYOU\tME\n"; +print "ARMY\t$User_army\t$Comp_army\n"; +print "NAVY\t$User_navy\t$Comp_navy\n"; +print "A. F.\t$User_AF\t$Comp_AF\n"; +do { + print "WHAT IS YOUR NEXT MOVE?\n"; + print "ARMY=1 NAVY=2 AIR FORCE=3\n"; + chomp($Attack_type = <>); +} while ($Attack_type < 1 && $Attack_type > 3); +do { + print "HOW MANY MEN\n"; + chomp($Attack_num = <>); +} while ($Attack_num < 0 + || ($Attack_type == 1 && $Attack_num > $User_army) + || ($Attack_type == 2 && $Attack_num > $User_navy) + || ($Attack_type == 3 && $Attack_num > $User_AF)); + +if ($Attack_num == 1) +{ + if ($Attack_num < $Comp_army/2) + { + print "I WIPED OUT YOUR ATTACK!\n"; + $User_army -= $Attack_num; + } + else + { + print "YOU DESTROYED MY ARMY!\n"; + $Comp_army = 0; + } +} +elsif ($Attack_num == 2) +{ + if ($Attack_num < $Comp_navy/2) + { + print "I SUNK TWO OF YOUR BATTLESHIPS, AND MY AIR FORCE\n"; + print "WIPED OUT YOUR UNGAURDED CAPITOL.\n"; + $User_army /= 4; + $User_navy /= 2; + } + else + { + print "YOUR NAVY SHOT DOWN THREE OF MY XIII PLANES,\n"; + print "AND SUNK THREE BATTLESHIPS.\n"; + $Comp_AF = 2*$Comp_AF/3; + $Comp_navy /= 2; + } +} +else # $Attack_num == 3 +{ + if ($Attack_num > $Comp_AF/2) + { + print "MY NAVY AND AIR FORCE IN A COMBINED ATTACK LEFT\n"; + print "YOUR COUNTRY IN SHAMBLES.\n"; + $User_army /= 3; + $User_navy /= 3; + $User_AF /= 3; + } + else + { + print "ONE OF YOUR PLANES CRASHED INTO MY HOUSE. I AM DEAD.\n"; + print "MY COUNTRY FELL APART.\n"; + $Comp_army = $Comp_navy = $Comp_AF = 0; + } +} + +print "\nFROM THE RESULTS OF BOTH OF YOUR ATTACKS,\n"; +my $total_user = $User_army+$User_navy+$User_AF; +my $total_comp = $Comp_army+$Comp_navy+$Comp_AF; +if ($total_user > 3/2*($total_comp)) +{ + print "YOU WON, OH! SHUCKS!!!!\n"; +} +elsif ($total_user < 2/3*($total_comp)) +{ + print "YOU LOST-I CONQUERED YOUR COUNTRY. IT SERVES YOU\n"; + print "RIGHT FOR PLAYING THIS STUPID GAME!!!\n"; +} +else +{ + print "THE TREATY OF PARIS CONCLUDED THAT WE TAKE OUR\n"; + print "RESPECTIVE COUNTRIES AND LIVE IN PEACE.\n"; +} +print "\n"; +exit(0); + +####################################################### + +sub s270 +{ + print "YOU SUNK ONE OF MY PATROL BOATS, BUT I WIPED OUT TWO\n"; + print "OF YOUR AIR FORCE BASES AND 3 ARMY BASES.\n"; + $User_army = int($User_army/3); + $User_AF = int($User_AF/3); + $Comp_navy = int(2*$Comp_navy/3); +} diff --git a/29_Craps/lua/README.md b/29_Craps/lua/README.md index c063f42f..aac11dd0 100644 --- a/29_Craps/lua/README.md +++ b/29_Craps/lua/README.md @@ -1,3 +1,17 @@ Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html) -Conversion to [Lua](https://www.lua.org/) +Conversion to [Lua](https://www.lua.org/) by Alex Conconi + +--- + +### Lua porting notes + +- The `craps_main` function contains the main game loop, which iteratively +plays craps rounds by calling `play_round` and tracks winnings and losings. +- Replaced the original routine that tries to scramble the random number +generator with a proper seed initializer in Lua: `math.randomseed(os.time())` +(as advised in the general porting notes). +- Added basic input validation to accept only positive integers for the +wager and the answer to the "If you want to play again print 5" question. +- "If you want to play again print 5 if not print 2" reads a bit odd but +we decided to leave it as is and stay true to the BASIC original version. \ No newline at end of file diff --git a/29_Craps/lua/craps.lua b/29_Craps/lua/craps.lua new file mode 100644 index 00000000..ddd1ce30 --- /dev/null +++ b/29_Craps/lua/craps.lua @@ -0,0 +1,149 @@ +--[[ +Craps + +From: BASIC Computer Games (1978) +Edited by David H. Ahl + + This game simulates the games of craps played according to standard + Nevada craps table rules. That is: + 1. A 7 or 11 on the first roll wins + 2. A 2, 3, or 12 on the first roll loses + 3. Any other number rolled becomes your "point." You continue to roll; + if you get your point you win. If you roll a 7, you lose and the dice + change hands when this happens. + + This version of craps was modified by Steve North of Creative Computing. + It is based on an original which appeared one day one a computer at DEC. + + +Lua port by Alex Conconi, 2022 +--]] + + +--- Throw two dice and return their sum. +local function throw_dice() + return math.random(1, 6) + math.random(1, 6) +end + + +--- Print prompt and read a number > 0 from stdin. +local function input_number(prompt) + while true do + io.write(prompt) + local number = tonumber(io.stdin:read("*l")) + if number and number > 0 then + return number + else + print("Please enter a number greater than zero.") + end + end +end + + +--- Print custom balance message depending on balance value +local function print_balance(balance, under_msg, ahead_msg, even_msg) + if balance < 0 then + print(under_msg) + elseif balance > 0 then + print(ahead_msg) + else + print(even_msg) + end +end + + +--- Play a round and return winnings or losings. +local function play_round() + -- Input the wager + local wager = input_number("Input the amount of your wager: ") + + -- Roll the die for the first time. + print("I will now throw the dice") + local first_roll = throw_dice() + + -- A 7 or 11 on the first roll wins. + if first_roll == 7 or first_roll == 11 then + print(string.format("%d - natural.... a winner!!!!", first_roll)) + print(string.format("%d pays even money, you win %d dollars", first_roll, wager)) + return wager + end + + -- A 2, 3, or 12 on the first roll loses. + if first_roll == 2 or first_roll == 3 or first_roll == 12 then + if first_roll == 2 then + -- Special 'you lose' message for 'snake eyes' + print(string.format("%d - snake eyes.... you lose.", first_roll)) + else + -- Default 'you lose' message + print(string.format("%d - craps.... you lose.", first_roll)) + end + print(string.format("You lose %d dollars", wager)) + return -wager + end + + -- Any other number rolled becomes the "point". + -- Continue to roll until rolling a 7 or point. + print(string.format("%d is the point. I will roll again", first_roll)) + while true do + local second_roll = throw_dice() + if second_roll == first_roll then + -- Player gets point and wins + print(string.format("%d - a winner.........congrats!!!!!!!!", first_roll)) + print(string.format("%d at 2 to 1 odds pays you...let me see... %d dollars", first_roll, 2 * wager)) + return 2 * wager + end + if second_roll == 7 then + -- Player rolls a 7 and loses + print(string.format("%d - craps. You lose.", second_roll)) + print(string.format("You lose $ %d", wager)) + return -wager + end + -- Continue to roll + print(string.format("%d - no point. I will roll again", second_roll)) + end +end + + +--- Main game function. +local function craps_main() + -- Print the introduction to the game + print(string.rep(" ", 32) .. "Craps") + print(string.rep(" ", 14) .. "Creative Computing Morristown, New Jersey\n\n") + print("2,3,12 are losers; 4,5,6,8,9,10 are points; 7,11 are natural winners.") + + -- Initialize random number generator seed + math.randomseed(os.time()) + + -- Initialize balance to track winnings and losings + local balance = 0 + + -- Main game loop + local keep_playing = true + while keep_playing do + -- Play a round + balance = balance + play_round() + + -- If player's answer is 5, then stop playing + keep_playing = (input_number("If you want to play again print 5 if not print 2: ") == 5) + + -- Print an update on winnings or losings + print_balance( + balance, + string.format("You are now under $%d", -balance), + string.format("You are now ahead $%d", balance), + "You are now even at 0" + ) + end + + -- Game over, print the goodbye message + print_balance( + balance, + "Too bad, you are in the hole. Come again.", + "Congratulations---you came out a winner. Come again.", + "Congratulations---you came out even, not bad for an amateur" + ) +end + + +--- Run the game. +craps_main() diff --git a/33_Dice/lua/README.md b/33_Dice/lua/README.md index c063f42f..610e3873 100644 --- a/33_Dice/lua/README.md +++ b/33_Dice/lua/README.md @@ -1,3 +1,16 @@ Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html) -Conversion to [Lua](https://www.lua.org/) +Conversion to [Lua](https://www.lua.org/) by Alex Conconi + +--- + +### Porting notes for Lua + +- This is a straightfoward port with only minor modifications for input +validation and text formatting. + +- The "Try again?" question accepts 'y', 'yes', 'n', 'no' (case insensitive), +whereas the original BASIC version defaults to no unless 'YES' is typed. + +- The "How many rolls?" question presents a more user friendly message +in case of invalid input. diff --git a/33_Dice/lua/dice.lua b/33_Dice/lua/dice.lua new file mode 100644 index 00000000..17f9b8a6 --- /dev/null +++ b/33_Dice/lua/dice.lua @@ -0,0 +1,112 @@ +--[[ +Dice + +From: BASIC Computer Games (1978) +Edited by David H. Ahl + + "Not exactly a game, this program simulates rolling + a pair of dice a large number of times and prints out + the frequency distribution. You simply input the + number of rolls. It is interesting to see how many + rolls are necessary to approach the theoretical + distribution: + + 2 1/36 2.7777...% + 3 2/36 5.5555...% + 4 3/36 8.3333...% + etc. + + "Daniel Freidus wrote this program while in the + seventh grade at Harrison Jr-Sr High School, + Harrison, New York." + + +Lua port by Alex Conconi, 2022. +]]-- + + +local function print_intro() + print("\n" .. string.rep(" ", 19) .. "Dice") + print("Creative Computing Morristown, New Jersey\n\n") + print("This program simulates the rolling of a") + print("pair of dice.") + print("You enter the number of times you want the computer to") + print("'roll' the dice. Watch out, very large numbers take") + print("a long time. In particular, numbers over 5000.") +end + + +local function ask_how_many_rolls() + while true do + -- Print prompt and read a valid number from stdin + print("\nHow many rolls?") + local num_rolls = tonumber(io.stdin:read("*l")) + if num_rolls then + return num_rolls + else + print("Please enter a valid number.") + end + end +end + + +local function ask_try_again() + while true do + -- Print prompt and read a yes/no answer from stdin, + -- accepting only 'yes', 'y', 'no' or 'n' (case insensitive) + print("\nTry again? ([y]es / [n]o)") + local answer = string.lower(io.stdin:read("*l")) + if answer == "yes" or answer == "y" then + return true + elseif answer == "no" or answer == "n" then + return false + else + print("Please answer '[y]es' or '[n]o'.") + end + end +end + + +local function roll_dice(num_rolls) + -- Initialize a table to track counts of roll outcomes + local counts = {} + for i=2, 12 do + counts[i] = 0 + end + + -- Roll the dice num_rolls times and update outcomes counts accordingly + for _=1, num_rolls do + local roll_total = math.random(1, 6) + math.random(1, 6) + counts[roll_total] = counts[roll_total] + 1 + end + + return counts +end + + +local function print_results(counts) + print("\nTotal Spots Number of Times") + for roll_total, count in pairs(counts) do + print(string.format(" %-14d%d", roll_total, count)) + end +end + + +local function dice_main() + print_intro() + + -- initialize the random number generator + math.randomseed(os.time()) + + -- main game loop + local keep_playing = true + while keep_playing do + local num_rolls = ask_how_many_rolls() + local counts = roll_dice(num_rolls) + print_results(counts) + keep_playing = ask_try_again() + end +end + + +dice_main() diff --git a/34_Digits/perl/digits.pl b/34_Digits/perl/digits.pl new file mode 100755 index 00000000..2f49bd0e --- /dev/null +++ b/34_Digits/perl/digits.pl @@ -0,0 +1,125 @@ +#!/usr/bin/perl + +# Digits program in Perl +# Translated by Kevin Brannen (kbrannen) + +use strict; +use warnings; + +# globals +my $Answer; + +print "\n"; +print " " x 33, "DIGITS"; +print " " x 15, "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n\n\n"; + +print "THIS IS A GAME OF GUESSING.\n"; +print "FOR INSTRUCTIONS, TYPE '1', ELSE TYPE '0': "; +chomp($Answer = <>); +if ($Answer == 1) +{ + print "\nPLEASE TAKE A PIECE OF PAPER AND WRITE DOWN\n"; + print "THE DIGITS '0', '1', OR '2' THIRTY TIMES AT RANDOM.\n"; + print "ARRANGE THEM IN THREE LINES OF TEN DIGITS EACH.\n"; + print "I WILL ASK FOR THEN TEN AT A TIME.\n"; + print "I WILL ALWAYS GUESS THEM FIRST AND THEN LOOK AT YOUR\n"; + print "NEXT NUMBER TO SEE IF I WAS RIGHT. BY PURE LUCK,\n"; + print "I OUGHT TO BE RIGHT TEN TIMES. BUT I HOPE TO DO BETTER\n"; + print "THAN THAT. *****\n\n\n"; +} + +my ($A, $B, $C) = (0, 1, 3); +my (@M, @K, @L); # DIM M(26,2),K(2,2),L(8,2) +while (1) +{ + for my $i (0 .. 26) { for my $j (0 .. 2) { $M[$i][$j] = 1; } } + for my $i (0 .. 2) { for my $j (0 .. 2) { $K[$i][$j] = 9; } } + for my $i (0 .. 8) { for my $j (0 .. 2) { $L[$i][$j] = 3; } } + $L[0][0] = $L[4][1] = $L[8][2] = 2; + my $Z = 26; + my $Z1 = 8; + my $Z2 = 2; + my $X = 0; + my @N; + + for my $T (1 .. 3) + { + my $have_input = 0; + while (!$have_input) + { + $have_input = 1; + print "\nTEN NUMBERS, PLEASE: "; + chomp($Answer = <>); + $Answer = "0 " . $Answer; # need to be 1-based, so prepend a throw-away value for [0] + @N = split(/\s+/, $Answer); + for my $i (1 .. 10) + { + if (!defined($N[$i]) || ($N[$i] != 0 && $N[$i] != 1 && $N[$i] != 2)) + { + print "ONLY USE THE DIGITS '0', '1', OR '2'.\n"; + print "LET'S TRY AGAIN."; + $have_input = 0; + last; + } + } + } + + print "\nMY GUESS\tYOUR NO.\tRESULT\tNO. RIGHT\n\n"; + for my $U (1 .. 10) + { + my $num = $N[$U]; + my $S = 0; + my $G; + for my $J (0 .. 2) + { + my $S1 = $A * $K[$Z2][$J] + $B * $L[$Z1][$J] + $C * $M[$Z][$J]; + next if ($S > $S1); + if ($S >= $S1) + { + next if (rand(1) < .5); + } + $S = $S1; + $G = $J; + } # NEXT J + print " $G\t\t$N[$U]\t\t"; + if ($G == $N[$U]) + { + $X++; + print "RIGHT\t$X\n"; + $M[$Z][$num]++; + $L[$Z1][$num]++; + $K[$Z2][$num]++; + $Z -= int($Z / 9) * 9; + $Z = 3 * $Z + $N[$U]; + } + else + { + print "WRONG\t$X\n"; + } + $Z1 = $Z - int($Z / 9) * 9; + $Z2 = $N[$U]; + } # NEXT U + } # NEXT T + + print "\n"; + if ($X == 10) + { + print "I GUESSED EXACTLY 1/3 OF YOUR NUMBERS.\n"; + print "IT'S A TIE GAME.\n"; + } + elsif ($X > 10) + { + print "I GUESSED MORE THAN 1/3, OR $X, OF YOUR NUMBERS.\n"; + print "I WIN.\a\a\a\a\a\a\a\a\a\a" + } + else + { + print "I GUESSED LESS THAN 1/3, OR $X, OF YOUR NUMBERS.\n"; + print "YOU BEAT ME. CONGRATULATIONS *****\n"; + } + + print "\nDO YOU WANT TO TRY AGAIN (1 FOR YES, 0 FOR NO): "; + chomp($Answer = <>); + last if ($Answer != 1); +} +print "\nTHANKS FOR THE GAME.\n"; diff --git a/36_Flip_Flop/perl/flipflop.pl b/36_Flip_Flop/perl/flipflop.pl new file mode 100755 index 00000000..63e3f84e --- /dev/null +++ b/36_Flip_Flop/perl/flipflop.pl @@ -0,0 +1,124 @@ +#!/usr/bin/perl + +# Flip Flop program in Perl +# Translated by Kevin Brannen (kbrannen) + +use strict; +use warnings; +use Math::Trig; + +print "\n"; +print " " x 32, "FLIPFLOP"; +print " " x 15, "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n\n\n"; +# *** CREATED BY MICHAEL CASS + +print "THE OBJECT OF THIS PUZZLE IS TO CHANGE THIS:\n\n"; +print "X X X X X X X X X X\n\n"; +print "TO THIS:\n\n"; +print "O O O O O O O O O O\n\n"; +print "BY TYPING THE NUMBER CORRESPONDING TO THE POSITION OF THE\n"; +print "LETTER ON SOME NUMBERS, ONE POSITION WILL CHANGE, ON\n"; +print "OTHERS, TWO WILL CHANGE. TO RESET LINE TO ALL X'S, TYPE 0\n"; +print "(ZERO) AND TO START OVER IN THE MIDDLE OF A GAME, TYPE \n"; +print "11 (ELEVEN).\n\n"; + +sub initialize +{ + my @a; + print "1 2 3 4 5 6 7 8 9 10\n"; + print "X X X X X X X X X X\n"; + for my $i (0 .. 10) { $a[$i] = "X"; } # make sure [0] has a value just in case + return @a; +} + +while (1) +{ + my $Q = rand(1); + my $C = 0; + print "HERE IS THE STARTING LINE OF X'S.\n\n"; + my @A = initialize(); + + while (1) + { + my $M = 0; + my $N; + while (1) + { + print "\nINPUT THE NUMBER: "; + chomp($N = <>); + if ($N != int($N) || $N < 0 || $N > 11) + { + print "ILLEGAL ENTRY--TRY AGAIN.\n"; + next; + } + last; + } + if ($N == 11) # start a new game + { + print "\n\n"; + last; + } + if ($N == 0) # reset line + { + @A = initialize(); + next; + } + + if ($M != $N) + { + $M = $N; + $A[$N] = ($A[$N] eq "O") ? "X" : "O"; + while ($M == $N) + { + my $R = tan($Q + $N / $Q - $N) - sin($Q / $N) + 336 * sin(8 * $N); + $N = $R - int($R); + $N = int(10 * $N); + if ($A[$N] eq "O") + { + $A[$N] = "X"; + next; + } + $A[$N] = "O"; + last; # GOTO 610 + + $A[$N] = "X"; + } + } + else + { + if ($A[$N] ne "O") { $A[$N] = "O"; } + while ($M == $N) + { + my $R = .592 * (1 / tan($Q / $N + $Q)) / sin( $N * 2 + $Q) - cos($N); + $N = $R - int($R); + $N = int(10 * $N); + if ($A[$N] eq "O") + { + $A[$N] = "X"; + next; + } + $A[$N] = "O"; + last; + } + } + + print "1 2 3 4 5 6 7 8 9 10\n"; + for my $i (1 .. 10) { print "$A[$i] "; } + print "\n"; + $C++; + my $i; + for ($i=1 ; $i <= 10 ; $i++) { + last if ($A[$i] ne "O"); + } + if ($i == 11) + { + if ($C <= 12) { print "VERY GOOD. YOU GUESSED IT IN ONLY $C GUESSES.\n"; } + else { print "TRY HARDER NEXT TIME. IT TOOK YOU $C GUESSES.\n"; } + last; + } + } + print "DO YOU WANT TO TRY ANOTHER PUZZLE (Y/N): "; + $_ = <>; + print "\n"; + last if (m/^n/i); +} diff --git a/38_Fur_Trader/perl/README.md b/38_Fur_Trader/perl/README.md index e69c8b81..c7001be7 100644 --- a/38_Fur_Trader/perl/README.md +++ b/38_Fur_Trader/perl/README.md @@ -1,3 +1,5 @@ Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html) Conversion to [Perl](https://www.perl.org/) + +You can answer yes/no questions in lower case if desired. diff --git a/38_Fur_Trader/perl/furtrader.pl b/38_Fur_Trader/perl/furtrader.pl new file mode 100755 index 00000000..f1e6b01a --- /dev/null +++ b/38_Fur_Trader/perl/furtrader.pl @@ -0,0 +1,265 @@ +#!/usr/bin/perl + +# Fur Trader program in Perl +# Translated by Kevin Brannen (kbrannen) + +use strict; +use warnings; + +# globals +my @Pelts = (qw(0 MINK BEAVER ERMINE FOX )); +my $Num_pelts = 4; +my @Quantity; # how many of each fur +my $Money; +my $Max_pelts = 190; +my $Ermine_price; # like we have @Pelts and @Quantity we could have @Prices +my $Beaver_price; # then have 4 constants as index into the arrays to avoid +my $Fox_price; # the magic numbers 1-4, or better have a array of objects (really a hash) +my $Mink_price; # with the 3 keys (name, number, price), but well keep it + # with 4 vars like the basic program did + +print "\n"; +print " " x 31, "FUR TRADER\n"; +print " " x 15, "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n\n\n"; + +init(); +while (1) +{ + my $ans = get_yesno(); + last if ($ans ne "YES"); + + $Ermine_price = new_price(.15, 0.95); + $Beaver_price = new_price(.25, 1.00); + + print "\nYOU HAVE \$$Money SAVINGS.\n"; + print "AND $Max_pelts FURS TO BEGIN THE EXPEDITION.\n"; + print "\nYOUR $Max_pelts FURS ARE DISTRIBUTED AMONG THE FOLLOWING\n"; + print "KINDS OF PELTS: MINK, BEAVER, ERMINE AND FOX.\n"; + reset_furs(); + + my $total = 0; + for my $j ( 1 .. $Num_pelts) + { + print "\nHOW MANY $Pelts[$j] PELTS DO YOU HAVE? "; + chomp(my $ans = <>); + $Quantity[$j] = int($ans); + $total += $Quantity[$j]; + } + if ($total > $Max_pelts) + { + print "\nYOU MAY NOT HAVE THAT MANY FURS.\n"; + print "DO NOT TRY TO CHEAT. I CAN ADD.\n"; + print "YOU MUST START AGAIN.\n"; + init(); + next; + } + + print "\nYOU MAY TRADE YOUR FURS AT FORT 1, FORT 2,\n"; + print "OR FORT 3. FORT 1 IS FORT HOCHELAGA (MONTREAL)\n"; + print "AND IS UNDER THE PROTECTION OF THE FRENCH ARMY.\n"; + print "FORT 2 IS FORT STADACONA (QUEBEC) AND IS UNDER THE\n"; + print "PROTECTION OF THE FRENCH ARMY. HOWEVER, YOU MUST\n"; + print "MAKE A PORTAGE AND CROSS THE LACHINE RAPIDS.\n"; + print "FORT 3 IS FORT NEW YORK AND IS UNDER DUTCH CONTROL.\n"; + print "YOU MUST CROSS THROUGH IROQUOIS LAND.\n"; + my $done = 0; + while (!$done) + { + $ans = 0; + while ($ans < 1 || $ans > 3) + { + no warnings; # in case user enters alpha chars, then int() will return 0 with no warnings + print "ANSWER 1, 2, OR 3: "; + $ans = int(<>); + } + # returns 0 if they want to go somewhere else; + # or returns the old basic line number to show what to do + if ($ans == 1) { $done = fort1(); } + elsif ($ans == 2) { $done = fort2(); } + elsif ($ans == 3) { $done = fort3(); } + } + + if ($done == 1410) + { + print "YOUR BEAVER SOLD FOR \$", $Beaver_price * $Quantity[2], "\t"; + } + + if ($done <= 1414) + { + print "YOUR FOX SOLD FOR \$", $Fox_price * $Quantity[4], "\n"; + print "YOUR ERMINE SOLD FOR \$", $Ermine_price * $Quantity[3], "\t"; + print "YOUR MINK SOLD FOR \$", $Mink_price * $Quantity[1], "\n"; + } + + # 1418 is always done + $Money += $Mink_price * $Quantity[1] + $Beaver_price * $Quantity[2] + $Ermine_price * $Quantity[3] + $Fox_price * $Quantity[4]; + print "\nYOU NOW HAVE \$$Money INCLUDING YOUR PREVIOUS SAVINGS\n"; + print "\nDO YOU WANT TO TRADE FURS NEXT YEAR? "; +} +exit(0); + +############################################################### + +sub init +{ + print "YOU ARE THE LEADER OF A FRENCH FUR TRADING EXPEDITION IN\n"; + print "1776 LEAVING THE LAKE ONTARIO AREA TO SELL FURS AND GET\n"; + print "SUPPLIES FOR THE NEXT YEAR. YOU HAVE A CHOICE OF THREE\n"; + print "FORTS AT WHICH YOU MAY TRADE. THE COST OF SUPPLIES\n"; + print "AND THE AMOUNT YOU RECEIVE FOR YOUR FURS WILL DEPEND\n"; + print "ON THE FORT THAT YOU CHOOSE.\n"; + + $Money = 600; + print "DO YOU WISH TO TRADE FURS?\n"; +} + +sub new_price +{ + my ($base, $factor) = @_; + return int(($base * rand(1) + $factor) * 100 + .5) / 100; +} + +sub supplies_fs +{ + print "SUPPLIES AT FORT STADACONA COST \$125.00.\n"; + print "YOUR TRAVEL EXPENSES TO STADACONA WERE \$15.00.\n"; +} + +sub supplies_ny +{ + print "SUPPLIES AT NEW YORK COST \$80.00.\n"; + print "YOUR TRAVEL EXPENSES TO NEW YORK WERE \$25.00.\n"; +} + +sub reset_furs +{ + for my $j (1 .. $Num_pelts) { $Quantity[$j] = 0; } +} + +sub get_yesno +{ + my $ans; + print "ANSWER YES OR NO: "; + chomp($ans = uc(<>)); + return $ans; +} + +sub trade_elsewhere +{ + print "DO YOU WANT TO TRADE AT ANOTHER FORT? "; + my $ans = get_yesno(); + return $ans; +} + +sub fort1 +{ + print "\nYOU HAVE CHOSEN THE EASIEST ROUTE. HOWEVER, THE FORT\n"; + print "IS FAR FROM ANY SEAPORT. THE VALUE\n"; + print "YOU RECEIVE FOR YOUR FURS WILL BE LOW AND THE COST\n"; + print "OF SUPPLIES HIGHER THAN AT FORTS STADACONA OR NEW YORK.\n"; + my $ans = trade_elsewhere(); + if ($ans eq "YES") { return 0; } + + $Money -= 160; + $Mink_price = new_price(.2, .7 ); + $Ermine_price = new_price(.2, .65); + $Beaver_price = new_price(.2, .75); + $Fox_price = new_price(.2, .8 ); + print "\nSUPPLIES AT FORT HOCHELAGA COST \$150.00.\n"; + print "YOUR TRAVEL EXPENSES TO HOCHELAGA WERE \$10.00.\n"; + return 1410; +} + +sub fort2 +{ + print "\nYOU HAVE CHOSEN A HARD ROUTE. IT IS, IN COMPARSION,\n"; + print "HARDER THAN THE ROUTE TO HOCHELAGA BUT EASIER THAN\n"; + print "THE ROUTE TO NEW YORK. YOU WILL RECEIVE AN AVERAGE VALUE\n"; + print "FOR YOUR FURS AND THE COST OF YOUR SUPPLIES WILL BE AVERAGE.\n"; + my $ans = trade_elsewhere(); + if ($ans eq "YES") { return 0; } + + $Money -= 140; + print "\n"; + $Mink_price = new_price(.3, .85); + $Ermine_price = new_price(.15, .8); + $Beaver_price = new_price(.2, .9); + my $P = int(10 * rand(1)) + 1; + if ($P <= 2) + { + $Quantity[2] = 0; + print "YOUR BEAVER WERE TOO HEAVY TO CARRY ACROSS\n"; + print "THE PORTAGE. YOU HAD TO LEAVE THE PELTS, BUT FOUND\n"; + print "THEM STOLEN WHEN YOU RETURNED.\n"; + supplies_fs(); + return 1414; + } + elsif ($P <= 6) + { + print "YOU ARRIVED SAFELY AT FORT STADACONA.\n"; + supplies_fs(); + } + elsif ($P <= 8) + { + reset_furs(); + print "YOUR CANOE UPSET IN THE LACHINE RAPIDS. YOU\n"; + print "LOST ALL YOUR FURS.\n"; + supplies_fs(); + return 1418; + } + elsif ($P <= 10) + { + $Quantity[4] = 0; + print "YOUR FOX PELTS WERE NOT CURED PROPERLY.\n"; + print "NO ONE WILL BUY THEM.\n"; + supplies_fs(); + } + return 1410; +} + +sub fort3 +{ + print "\nYOU HAVE CHOSEN THE MOST DIFFICULT ROUTE. AT\n"; + print "FORT NEW YORK YOU WILL RECEIVE THE HIGHEST VALUE\n"; + print "FOR YOUR FURS. THE COST OF YOUR SUPPLIES\n"; + print "WILL BE LOWER THAN AT ALL THE OTHER FORTS.\n"; + my $ans = trade_elsewhere(); + if ($ans eq "YES") { return 0; } + + $Money -= 105; + print "\n"; + $Mink_price = new_price(.15, 1.05); + $Fox_price = new_price(.25, 1.1); + $Fox_price = new_price(.25, 1.1); + my $P = int(10 * rand(1)) + 1; + if ($P <= 2) + { + print "YOU WERE ATTACKED BY A PARTY OF IROQUOIS.\n"; + print "ALL PEOPLE IN YOUR TRADING GROUP WERE\n"; + print "KILLED. THIS ENDS THE GAME.\n"; + exit(0); + } + elsif ($P <= 6) + { + print "YOU WERE LUCKY. YOU ARRIVED SAFELY\n"; + print "AT FORT NEW YORK.\n"; + supplies_ny(); + } + elsif ($P <= 8) + { + reset_furs(); + print "YOU NARROWLY ESCAPED AN IROQUOIS RAIDING PARTY.\n"; + print "HOWEVER, YOU HAD TO LEAVE ALL YOUR FURS BEHIND.\n"; + supplies_ny(); + return 1418; + } + elsif ($P <= 10) + { + $Beaver_price /= 2; + $Mink_price /= 2; + print "YOUR MINK AND BEAVER WERE DAMAGED ON YOUR TRIP.\n"; + print "YOU RECEIVE ONLY HALF THE CURRENT PRICE FOR THESE FURS.\n"; + supplies_ny(); + } + return 1410; +} diff --git a/42_Gunner/perl/gunner.pl b/42_Gunner/perl/gunner.pl new file mode 100755 index 00000000..9df20deb --- /dev/null +++ b/42_Gunner/perl/gunner.pl @@ -0,0 +1,81 @@ +#!/usr/bin/perl + +# Gunner program in Perl +# Required extensive restructuring to remove all of the GOTO's. +# Translated by Kevin Brannen (kbrannen) + +use strict; +use warnings; + +# globals +my $Max_range = int(40000*rand(1)+20000); +my $Total_shots = 0; +my $Games = 0; + +print "\n"; +print " " x 30, "GUNNER\n"; +print " " x 15, "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n\n\n"; + +print "YOU ARE THE OFFICER-IN-CHARGE, GIVING ORDERS TO A GUN\n"; +print "CREW, TELLING THEM THE DEGREES OF ELEVATION YOU ESTIMATE\n"; +print "WILL PLACE A PROJECTILE ON TARGET. A HIT WITHIN 100 YARDS\n"; +print "OF THE TARGET WILL DESTROY IT.\n\n"; +print "MAXIMUM RANGE OF YOUR GUN IS $Max_range YARDS.\n\n"; + +GAME: while (1) +{ + my $target_dist = int($Max_range * (.1 + .8 * rand(1))); + my $shots = 0; + print "DISTANCE TO THE TARGET IS $target_dist YARDS.\n\n"; + while (1) + { + my $elevation = get_elevation(); # in degrees + $shots++; + my $dist = int($target_dist - ($Max_range * sin(2 * $elevation / 57.3))); + if (abs($dist) < 100) + { + print "*** TARGET DESTROYED *** $shots ROUNDS OF AMMUNITION EXPENDED.\n"; + $Total_shots += $shots; + if ($Games++ == 4) + { + print "\n\nTOTAL ROUNDS EXPENDED WERE: $Total_shots\n"; + if ($Total_shots > 18) { print "BETTER GO BACK TO FORT SILL FOR REFRESHER TRAINING!\n"; } + else { print "NICE SHOOTING !!\n"; } + last; + } + print "\nTHE FORWARD OBSERVER HAS SIGHTED MORE ENEMY ACTIVITY...\n"; + next GAME; + } + if ($dist > 100) { print "SHORT OF TARGET BY ", abs($dist)," YARDS.\n"; } + else { print "OVER TARGET BY ", abs($dist), " YARDS.\n"; } + + if ($shots >= 5) + { + print "\nBOOM !!!! YOU HAVE JUST BEEN DESTROYED BY THE ENEMY.\n\n\n\n"; + print "BETTER GO BACK TO FORT SILL FOR REFRESHER TRAINING!\n"; + last; + } + } + + print "\nTRY AGAIN (Y OR N): "; + chomp(my $ans=uc(<>)); + if ($ans ne "Y") { last; } + else { $Games = 0; $Total_shots = 0; } +} + +print "\nOK. RETURN TO BASE CAMP.\n"; + +#################################### + +sub get_elevation +{ + my $elevation; + while (1) + { + print "\nELEVATION: "; + chomp($elevation = <>); + if ($elevation > 89) { print "MAXIMUM ELEVATION IS 89 DEGREES.\n"; } + elsif ($elevation < 1) { print "MINIMUM ELEVATION IS ONE DEGREE.\n"; } + else { return $elevation; } + } +} diff --git a/43_Hammurabi/csharp/View.cs b/43_Hammurabi/csharp/View.cs index fcfb4cd5..e0c5b054 100644 --- a/43_Hammurabi/csharp/View.cs +++ b/43_Hammurabi/csharp/View.cs @@ -133,7 +133,7 @@ namespace Hammurabi break; case PerformanceRating.Bad: Console.WriteLine("YOUR HEAVY-HANDED PERFORMANCE SMACKS OF NERO AND IVAN IV."); - Console.WriteLine("THE PEOPLE (REMIANING) FIND YOU AN UNPLEASANT RULER, AND,"); + Console.WriteLine("THE PEOPLE (REMAINING) FIND YOU AN UNPLEASANT RULER, AND,"); Console.WriteLine("FRANKLY, HATE YOUR GUTS!!"); break; case PerformanceRating.Ok: diff --git a/43_Hammurabi/hammurabi.bas b/43_Hammurabi/hammurabi.bas index 8f0998f0..e41fe4fb 100644 --- a/43_Hammurabi/hammurabi.bas +++ b/43_Hammurabi/hammurabi.bas @@ -108,7 +108,7 @@ 900 PRINT "A FANTASTIC PERFORMANCE!!! CHARLEMANGE, DISRAELI, AND" 905 PRINT "JEFFERSON COMBINED COULD NOT HAVE DONE BETTER!":GOTO 990 940 PRINT "YOUR HEAVY-HANDED PERFORMANCE SMACKS OF NERO AND IVAN IV." -945 PRINT "THE PEOPLE (REMIANING) FIND YOU AN UNPLEASANT RULER, AND," +945 PRINT "THE PEOPLE (REMAINING) FIND YOU AN UNPLEASANT RULER, AND," 950 PRINT "FRANKLY, HATE YOUR GUTS!!":GOTO 990 960 PRINT "YOUR PERFORMANCE COULD HAVE BEEN SOMEWHAT BETTER, BUT" 965 PRINT "REALLY WASN'T TOO BAD AT ALL. ";INT(P*.8*RND(1));"PEOPLE" diff --git a/43_Hammurabi/javascript/hammurabi.js b/43_Hammurabi/javascript/hammurabi.js index 6d878db9..ca4d471d 100644 --- a/43_Hammurabi/javascript/hammurabi.js +++ b/43_Hammurabi/javascript/hammurabi.js @@ -233,7 +233,7 @@ async function main() print("ALSO BEEN DECLARED NATIONAL FINK!!!!\n"); } else if (p1 > 10 || l < 9) { print("YOUR HEAVY-HANDED PERFORMANCE SMACKS OF NERO AND IVAN IV.\n"); - print("THE PEOPLE (REMIANING) FIND YOU AN UNPLEASANT RULER, AND,\n"); + print("THE PEOPLE (REMAINING) FIND YOU AN UNPLEASANT RULER, AND,\n"); print("FRANKLY, HATE YOUR GUTS!!\n"); } else if (p1 > 3 || l < 10) { print("YOUR PERFORMANCE COULD HAVE BEEN SOMEWHAT BETTER, BUT\n"); diff --git a/43_Hammurabi/python/hamurabi.py b/43_Hammurabi/python/hamurabi.py index 79de9ac1..c259ec80 100644 --- a/43_Hammurabi/python/hamurabi.py +++ b/43_Hammurabi/python/hamurabi.py @@ -212,7 +212,7 @@ def main() -> None: national_fink() elif P1 > 10 or L < 9: print("YOUR HEAVY-HANDED PERFORMANCE SMACKS OF NERO AND IVAN IV.") - print("THE PEOPLE (REMIANING) FIND YOU AN UNPLEASANT RULER, AND,") + print("THE PEOPLE (REMAINING) FIND YOU AN UNPLEASANT RULER, AND,") print("FRANKLY, HATE YOUR GUTS!!") elif P1 > 3 or L < 10: print("YOUR PERFORMANCE COULD HAVE BEEN SOMEWHAT BETTER, BUT") diff --git a/45_Hello/lua/hello.lua b/45_Hello/lua/hello.lua new file mode 100644 index 00000000..5628749b --- /dev/null +++ b/45_Hello/lua/hello.lua @@ -0,0 +1,156 @@ +-- HELLO +-- +-- Converted from BASIC to Lua by Recanman + +local function tab(space) + local str = "" + + for _ = space, 1, -1 do + str = str .. " " + end + + return str +end + +-- reused from Bagels.lua +function getInput(prompt) + io.write(prompt) + io.flush() + local input = io.read("l") + if not input then --- test for EOF + print("GOODBYE") + os.exit(0) + end + return input +end + +print(tab(33) .. "HELLO\n") +print(tab(15) .. "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n") +print("\n") +print("\n") +print("\n") + +print("HELLO. MY NAME IS CREATIVE COMPUTER.\n") +print("\n") +print("\n") + +print("WHAT'S YOUR NAME") +local ns = getInput("? ") + +print("\n") +print("HI THERE, " .. ns .. ", ARE YOU ENJOYING YOURSELF HERE") + +while true do + local bs = getInput("? ") + print("\n") + if bs == "YES" then + print("I'M GLAD TO HEAR THAT, " .. ns .. ".\n") + print("\n") + break + elseif bs == "NO" then + print("OH, I'M SORRY TO HEAR THAT, " .. ns .. ". MAYBE WE CAN\n") + print("BRIGHTEN UP YOUR VISIT A BIT.\n") + break + else + print("PLEASE ANSWER 'YES' OR 'NO'. DO YOU LIKE IT HERE") + end +end + +local function main() + print("\n") + print("SAY, " .. ns .. ", I CAN SOLVED ALL KINDS OF PROBLEMS EXCEPT\n") + print("THOSE DEALING WITH GREECE. WHAT KIND OF PROBLEMS DO\n") + print("YOU HAVE (ANSWER SEX, HEALTH, MONEY, OR JOB)") + + while true do + local cs = getInput("? ") + print("\n") + + if cs ~= "SEX" and cs ~= "HEALTH" and cs ~= "MONEY" and cs ~= "JOB" then + print("OH, " .. ns .. ", YOUR ANSWER OF " .. cs .. " IS GREEK TO ME.\n") + elseif cs == "JOB" then + print("I CAN SYMPATHIZE WITH YOU " .. ns .. ". I HAVE TO WORK\n") + print("VERY LONG HOURS FOR NO PAY -- AND SOME OF MY BOSSES\n") + print("REALLY BEAT ON MY KEYBOARD. MY ADVICE TO YOU, " .. ns .. ",\n") + print("IS TO OPEN A RETAIL COMPUTER STORE. IT'S GREAT FUN.\n") + elseif cs == "MONEY" then + print("SORRY, " .. ns .. ", I'M BROKE TOO. WHY DON'T YOU SELL\n") + print("ENCYCLOPEADIAS OR MARRY SOMEONE RICH OR STOP EATING\n") + print("SO YOU WON'T NEED SO MUCH MONEY?\n") + elseif cs == "HEALTH" then + print("MY ADVICE TO YOU " .. ns .. " IS:\n") + print(tab(5) .. "1. TAKE TWO ASPRIN\n") + print(tab(5) .. "2. DRINK PLENTY OF FLUIDS (ORANGE JUICE, NOT BEER!)\n") + print(tab(5) .. "3. GO TO BED (ALONE)\n") + elseif cs == "SEX" then + print("IS YOUR PROBLEM TOO MUCH OR TOO LITTLE") + + while true do + local ds = getInput("? ") + print("\n") + + if ds == "TOO MUCH" then + print("YOU CALL THAT A PROBLEM?!! I SHOULD HAVE SUCH PROBLEMS!\n") + print("IF IT BOTHERS YOU, " .. ns .. ", TAKE A COLD SHOWER.\n") + break + elseif ds == "TOO LITTLE" then + print("WHY ARE YOU HERE IN SUFFERN, " .. ns .. "? YOU SHOULD BE\n") + print("IN TOKYO OR NEW YORK OR AMSTERDAM OR SOMEPLACE WITH SOME\n") + print("REAL ACTION.\n") + break + else + print("DON'T GET ALL SHOOK, " .. ns .. ", JUST ANSWER THE QUESTION\n") + print("WITH 'TOO MUCH' OR 'TOO LITTLE'. WHICH IS IT") + end + end + end + + print("\n") + print("ANY MORE PROBLEMS YOU WANT SOLVED, " .. ns) + + local es = getInput("? ") + + if es == "YES" then + print("WHAT KIND (SEX, MONEY, HEALTH, JOB)") + elseif es == "NO" then + print("THAT WILL BE $5.00 FOR THE ADVICE, " .. ns .. ".\n") + print("PLEASE LEAVE THE MONEY ON THE TERMINAL.\n") + print("\n") + print("\n") + print("\n") + + while true do + print("DID YOU LEAVE THE MONEY") + + local gs = getInput("? ") + print("\n") + + if gs == "YES" then + print("HEY, " .. ns .. "??? YOU LEFT NO MONEY AT ALL!\n") + print("YOU ARE CHEATING ME OUT OF MY HARD-EARNED LIVING.\n") + print("\n") + print("WHAT A RIP OFF, " .. ns .. "!!!\n") + print("\n") + break + elseif gs == "NO" then + print("THAT'S HONEST, " .. ns .. ", BUT HOW DO YOU EXPECT\n") + print("ME TO GO ON WITH MY PSYCHOLOGY STUDIES IF MY PATIENT\n") + print("DON'T PAY THEIR BILLS?\n") + break + else + print("YOUR ANSWER OF '" .. gs .. "' CONFUSES ME, " .. ns .. ".\n") + print("PLEASE RESPOND WITH 'YES' OR 'NO'.\n") + end + end + + break + end + end + + print("\n") + print("TAKE A WALK, " .. ns .. ".\n") + print("\n") + print("\n") +end + +main() \ No newline at end of file diff --git a/53_King/README.md b/53_King/README.md index a363a2be..14a66fe9 100644 --- a/53_King/README.md +++ b/53_King/README.md @@ -1,10 +1,10 @@ ## King -This is one of the most comprehensive, difficult, and interesting games. (If you’ve never played one of these games, start with HAMMURABI.) +This is one of the most comprehensive, difficult, and interesting games. (If you've never played one of these games, start with HAMMURABI.) In this game, you are Premier of Setats Detinu, a small communist island 30 by 70 miles long. Your job is to decide upon the budget of your country and distribute money to your country from the communal treasury. -The money system is Rollods; each person needs 100 Rallods per year to survive. Your country’s income comes from farm produce and tourists visiting your magnificent forests, hunting, fishing, etc. Part of your land is farm land but it also has an excellent mineral content and may be sold to foreign industry for strip mining. Industry import and support their own workers. Crops cost between 10 and 15 Rallods per square mile to plant, cultivate, and harvest. Your goal is to complete an eight-year term of office without major mishap. A word of warning: it isn’t easy! +The money system is Rollods; each person needs 100 Rallods per year to survive. Your country's income comes from farm produce and tourists visiting your magnificent forests, hunting, fishing, etc. Part of your land is farm land but it also has an excellent mineral content and may be sold to foreign industry for strip mining. Industry import and support their own workers. Crops cost between 10 and 15 Rallods per square mile to plant, cultivate, and harvest. Your goal is to complete an eight-year term of office without major mishap. A word of warning: it isn't easy! The author of this program is James A. Storer who wrote it while a student at Lexington High School. @@ -66,3 +66,15 @@ On basic line 1997 it is: but it should be: 1997 PRINT " AND 1,000 SQ. MILES OF FOREST LAND." + +### Bug 4 + +On basic line 1310 we see this: + + 1310 IF C=0 THEN 1324 + 1320 PRINT "OF ";INT(J);"SQ. MILES PLANTED,"; + 1324 ... + +but it should probably be: + + 1310 IF J=0 THEN 1324 diff --git a/53_King/csharp/Country.cs b/53_King/csharp/Country.cs new file mode 100644 index 00000000..1a37bcd8 --- /dev/null +++ b/53_King/csharp/Country.cs @@ -0,0 +1,133 @@ +namespace King; + +internal class Country +{ + private const int InitialLand = 1000; + + private readonly IReadWrite _io; + private readonly IRandom _random; + private float _rallods; + private float _countrymen; + private float _foreigners; + private float _arableLand; + private float _industryLand; + + public Country(IReadWrite io, IRandom random) + : this( + io, + random, + (int)(60000 + random.NextFloat(1000) - random.NextFloat(1000)), + (int)(500 + random.NextFloat(10) - random.NextFloat(10)), + 0, + InitialLand) + { + } + + public Country(IReadWrite io, IRandom random, float rallods, float countrymen, float foreigners, float land) + { + _io = io; + _random = random; + _rallods = rallods; + _countrymen = countrymen; + _foreigners = foreigners; + _arableLand = land; + } + + public string GetStatus(int landValue, int plantingCost) + => Resource.Status(_rallods, _countrymen, _foreigners, _arableLand, landValue, plantingCost); + + public float Countrymen => _countrymen; + public float Workers => _foreigners; + public bool HasWorkers => _foreigners > 0; + private float FarmLand => _arableLand; + public bool HasRallods => _rallods > 0; + public float Rallods => _rallods; + public float IndustryLand => InitialLand - _arableLand; + public int PreviousTourismIncome { get; private set; } + + public bool SellLand(int landValue, out float landSold) + { + if (_io.TryReadValue( + SellLandPrompt, + out landSold, + new ValidityTest(v => v <= FarmLand, () => SellLandError(FarmLand)))) + { + _arableLand = (int)(_arableLand - landSold); + _rallods = (int)(_rallods + landSold * landValue); + return true; + } + + return false; + } + + public bool DistributeRallods(out float rallodsGiven) + { + if (_io.TryReadValue( + GiveRallodsPrompt, + out rallodsGiven, + new ValidityTest(v => v <= _rallods, () => GiveRallodsError(_rallods)))) + { + _rallods = (int)(_rallods - rallodsGiven); + return true; + } + + return false; + } + + public bool PlantLand(int plantingCost, out float landPlanted) + { + if (_io.TryReadValue( + PlantLandPrompt, + out landPlanted, + new ValidityTest(v => v <= _countrymen * 2, PlantLandError1), + new ValidityTest(v => v <= FarmLand, PlantLandError2(FarmLand)), + new ValidityTest(v => v * plantingCost <= _rallods, PlantLandError3(_rallods)))) + { + _rallods -= (int)(landPlanted * plantingCost); + return true; + } + + return false; + } + + public bool ControlPollution(out float rallodsSpent) + { + if (_io.TryReadValue( + PollutionPrompt, + out rallodsSpent, + new ValidityTest(v => v <= _rallods, () => PollutionError(_rallods)))) + { + _rallods = (int)(_rallods - rallodsSpent); + return true; + } + + return false; + } + + public bool TrySpend(float amount, float landValue) + { + if (_rallods >= amount) + { + _rallods -= amount; + return true; + } + + _arableLand = (int)(_arableLand - (int)(amount - _rallods) / landValue); + _rallods = 0; + return false; + } + + public void RemoveTheDead(int deaths) => _countrymen = (int)(_countrymen - deaths); + + public void Migration(int migration) => _countrymen = (int)(_countrymen + migration); + + public void AddWorkers(int newWorkers) => _foreigners = (int)(_foreigners + newWorkers); + + public void SellCrops(int income) => _rallods = (int)(_rallods + income); + + public void EntertainTourists(int income) + { + PreviousTourismIncome = income; + _rallods = (int)(_rallods + income); + } +} diff --git a/53_King/csharp/Game.cs b/53_King/csharp/Game.cs new file mode 100644 index 00000000..6a91010c --- /dev/null +++ b/53_King/csharp/Game.cs @@ -0,0 +1,47 @@ +namespace King; + +internal class Game +{ + const int TermOfOffice = 8; + + private readonly IReadWrite _io; + private readonly IRandom _random; + + public Game(IReadWrite io, IRandom random) + { + _io = io; + _random = random; + } + + public void Play() + { + _io.Write(Title); + + var reign = SetUpReign(); + if (reign != null) + { + while (reign.PlayYear()); + } + + _io.WriteLine(); + _io.WriteLine(); + } + + private Reign? SetUpReign() + { + var response = _io.ReadString(InstructionsPrompt).ToUpper(); + + if (response.Equals("Again", StringComparison.InvariantCultureIgnoreCase)) + { + return _io.TryReadGameData(_random, out var reign) ? reign : null; + } + + if (!response.StartsWith("N", StringComparison.InvariantCultureIgnoreCase)) + { + _io.Write(InstructionsText(TermOfOffice)); + } + + _io.WriteLine(); + return new Reign(_io, _random); + } +} diff --git a/53_King/csharp/IOExtensions.cs b/53_King/csharp/IOExtensions.cs new file mode 100644 index 00000000..dbb3079a --- /dev/null +++ b/53_King/csharp/IOExtensions.cs @@ -0,0 +1,61 @@ +using System.Diagnostics.CodeAnalysis; +using static King.Resources.Resource; + +namespace King; + +internal static class IOExtensions +{ + internal static bool TryReadGameData(this IReadWrite io, IRandom random, [NotNullWhen(true)] out Reign? reign) + { + if (io.TryReadValue(SavedYearsPrompt, v => v < Reign.MaxTerm, SavedYearsError(Reign.MaxTerm), out var years) && + io.TryReadValue(SavedTreasuryPrompt, out var rallods) && + io.TryReadValue(SavedCountrymenPrompt, out var countrymen) && + io.TryReadValue(SavedWorkersPrompt, out var workers) && + io.TryReadValue(SavedLandPrompt, v => v is > 1000 and <= 2000, SavedLandError, out var land)) + { + reign = new Reign(io, random, new Country(io, random, rallods, countrymen, workers, land), years + 1); + return true; + } + + reign = default; + return false; + } + + internal static bool TryReadValue(this IReadWrite io, string prompt, out float value, params ValidityTest[] tests) + { + while (true) + { + var response = value = io.ReadNumber(prompt); + if (response == 0) { return false; } + if (tests.All(test => test.IsValid(response, io))) { return true; } + } + } + + internal static bool TryReadValue(this IReadWrite io, string prompt, out float value) + => io.TryReadValue(prompt, _ => true, "", out value); + + internal static bool TryReadValue( + this IReadWrite io, + string prompt, + Predicate isValid, + string error, + out float value) + => io.TryReadValue(prompt, isValid, () => error, out value); + + internal static bool TryReadValue( + this IReadWrite io, + string prompt, + Predicate isValid, + Func getError, + out float value) + { + while (true) + { + value = io.ReadNumber(prompt); + if (value < 0) { return false; } + if (isValid(value)) { return true; } + + io.Write(getError()); + } + } +} diff --git a/53_King/csharp/King.csproj b/53_King/csharp/King.csproj index d3fe4757..3870320c 100644 --- a/53_King/csharp/King.csproj +++ b/53_King/csharp/King.csproj @@ -6,4 +6,12 @@ enable enable + + + + + + + + diff --git a/53_King/csharp/Program.cs b/53_King/csharp/Program.cs new file mode 100644 index 00000000..5aae4ccb --- /dev/null +++ b/53_King/csharp/Program.cs @@ -0,0 +1,7 @@ +global using Games.Common.IO; +global using Games.Common.Randomness; +global using King.Resources; +global using static King.Resources.Resource; +using King; + +new Game(new ConsoleIO(), new RandomNumberGenerator()).Play(); diff --git a/53_King/csharp/Reign.cs b/53_King/csharp/Reign.cs new file mode 100644 index 00000000..7a96a30f --- /dev/null +++ b/53_King/csharp/Reign.cs @@ -0,0 +1,45 @@ +namespace King; + +internal class Reign +{ + public const int MaxTerm = 8; + + private readonly IReadWrite _io; + private readonly IRandom _random; + private readonly Country _country; + private float _yearNumber; + + public Reign(IReadWrite io, IRandom random) + : this(io, random, new Country(io, random), 1) + { + } + + public Reign(IReadWrite io, IRandom random, Country country, float year) + { + _io = io; + _random = random; + _country = country; + _yearNumber = year; + } + + public bool PlayYear() + { + var year = new Year(_country, _random, _io); + + _io.Write(year.Status); + + var result = year.GetPlayerActions() ?? year.EvaluateResults() ?? IsAtEndOfTerm(); + if (result.IsGameOver) + { + _io.WriteLine(result.Message); + return false; + } + + return true; + } + + private Result IsAtEndOfTerm() + => _yearNumber == MaxTerm + ? Result.GameOver(EndCongratulations(MaxTerm)) + : Result.Continue; +} diff --git a/53_King/csharp/Resources/DeathsPollution.txt b/53_King/csharp/Resources/DeathsPollution.txt new file mode 100644 index 00000000..bf28d9a7 --- /dev/null +++ b/53_King/csharp/Resources/DeathsPollution.txt @@ -0,0 +1 @@ + {0} countrymen died of carbon-monoxide and dust inhalation \ No newline at end of file diff --git a/53_King/csharp/Resources/DeathsStarvation.txt b/53_King/csharp/Resources/DeathsStarvation.txt new file mode 100644 index 00000000..af275cf4 --- /dev/null +++ b/53_King/csharp/Resources/DeathsStarvation.txt @@ -0,0 +1 @@ + {0} countrymen died of starvation \ No newline at end of file diff --git a/53_King/csharp/Resources/Emigration.txt b/53_King/csharp/Resources/Emigration.txt new file mode 100644 index 00000000..01f67094 --- /dev/null +++ b/53_King/csharp/Resources/Emigration.txt @@ -0,0 +1 @@ + {0} countrymen left the island. \ No newline at end of file diff --git a/53_King/csharp/Resources/EndAlso.txt b/53_King/csharp/Resources/EndAlso.txt new file mode 100644 index 00000000..084b9a82 --- /dev/null +++ b/53_King/csharp/Resources/EndAlso.txt @@ -0,0 +1,3 @@ +also had your left eye gouged out! +;have also gained a very bad reputation. +;have also been declared national fink. diff --git a/53_King/csharp/Resources/EndCongratulations.txt b/53_King/csharp/Resources/EndCongratulations.txt new file mode 100644 index 00000000..ef1ff203 --- /dev/null +++ b/53_King/csharp/Resources/EndCongratulations.txt @@ -0,0 +1,10 @@ + + +Congratulations!!!!!!!!!!!!!!!!!! +You have successfully completed your {0} year term +of office. You were, of course, extremely lucky, but +nevertheless, it's quite an achievement. Goodbye and good +luck - you'll probably need it if you're the type that +plays this game. + + diff --git a/53_King/csharp/Resources/EndConsequences.txt b/53_King/csharp/Resources/EndConsequences.txt new file mode 100644 index 00000000..61ebfd25 --- /dev/null +++ b/53_King/csharp/Resources/EndConsequences.txt @@ -0,0 +1,3 @@ +You have been thrown out of office and are now +residing in prison.; +You have been assassinated. diff --git a/53_King/csharp/Resources/EndForeignWorkers.txt b/53_King/csharp/Resources/EndForeignWorkers.txt new file mode 100644 index 00000000..76c04c3d --- /dev/null +++ b/53_King/csharp/Resources/EndForeignWorkers.txt @@ -0,0 +1,7 @@ + + +The number of foreign workers has exceeded the number +of countrymen. As a minority, they have revolted and +taken over the country. +{0} + diff --git a/53_King/csharp/Resources/EndManyDead.txt b/53_King/csharp/Resources/EndManyDead.txt new file mode 100644 index 00000000..70cd95f0 --- /dev/null +++ b/53_King/csharp/Resources/EndManyDead.txt @@ -0,0 +1,5 @@ +{0} countrymen died in one year!!!!! +due to this extreme mismanagement, you have not only +been impeached and thrown out of office, but you +{1} + diff --git a/53_King/csharp/Resources/EndMoneyLeftOver.txt b/53_King/csharp/Resources/EndMoneyLeftOver.txt new file mode 100644 index 00000000..e194a9fd --- /dev/null +++ b/53_King/csharp/Resources/EndMoneyLeftOver.txt @@ -0,0 +1,7 @@ + +Money was left over in the treasury which you did +not spend. As a result, some of your countrymen died +of starvation. The public is enraged and you have +been forced to resign. + + diff --git a/53_King/csharp/Resources/EndOneThirdDead.txt b/53_King/csharp/Resources/EndOneThirdDead.txt new file mode 100644 index 00000000..e1761dfe --- /dev/null +++ b/53_King/csharp/Resources/EndOneThirdDead.txt @@ -0,0 +1,7 @@ + + +Over one third of the population has died since you +were elected to office. The people (remaining) +hate your guts. +{0} + diff --git a/53_King/csharp/Resources/FuneralExpenses.txt b/53_King/csharp/Resources/FuneralExpenses.txt new file mode 100644 index 00000000..3aff58eb --- /dev/null +++ b/53_King/csharp/Resources/FuneralExpenses.txt @@ -0,0 +1 @@ + You were forced to spend {0} rallods on funeral expenses \ No newline at end of file diff --git a/53_King/csharp/Resources/GiveRallodsError.txt b/53_King/csharp/Resources/GiveRallodsError.txt new file mode 100644 index 00000000..df0d7cc0 --- /dev/null +++ b/53_King/csharp/Resources/GiveRallodsError.txt @@ -0,0 +1 @@ + Think again. You've only got {0} rallods in the treasury. diff --git a/53_King/csharp/Resources/GiveRallodsPrompt.txt b/53_King/csharp/Resources/GiveRallodsPrompt.txt new file mode 100644 index 00000000..e8bd345b --- /dev/null +++ b/53_King/csharp/Resources/GiveRallodsPrompt.txt @@ -0,0 +1 @@ +How many rallods will you distribute among your countrymen \ No newline at end of file diff --git a/53_King/csharp/Resources/Goodbye.txt b/53_King/csharp/Resources/Goodbye.txt new file mode 100644 index 00000000..67543c04 --- /dev/null +++ b/53_King/csharp/Resources/Goodbye.txt @@ -0,0 +1,4 @@ +Goodbye. +(If you wish to continue this game at a later date, answer +'again' when asked if you want instructions at the start +of the game). diff --git a/53_King/csharp/Resources/Harvest.txt b/53_King/csharp/Resources/Harvest.txt new file mode 100644 index 00000000..287e0c42 --- /dev/null +++ b/53_King/csharp/Resources/Harvest.txt @@ -0,0 +1,2 @@ + you harvested {0} sq. miles of crops. +{1}making {2} rallods. diff --git a/53_King/csharp/Resources/HarvestReason.txt b/53_King/csharp/Resources/HarvestReason.txt new file mode 100644 index 00000000..82faccb5 --- /dev/null +++ b/53_King/csharp/Resources/HarvestReason.txt @@ -0,0 +1 @@ + (Due to increased air and water pollution from foreign industry.) diff --git a/53_King/csharp/Resources/Immigration.txt b/53_King/csharp/Resources/Immigration.txt new file mode 100644 index 00000000..30d8b6b3 --- /dev/null +++ b/53_King/csharp/Resources/Immigration.txt @@ -0,0 +1 @@ + {0} countrymen came to the island. \ No newline at end of file diff --git a/53_King/csharp/Resources/InstructionsPrompt.txt b/53_King/csharp/Resources/InstructionsPrompt.txt new file mode 100644 index 00000000..0d311b60 --- /dev/null +++ b/53_King/csharp/Resources/InstructionsPrompt.txt @@ -0,0 +1 @@ +Do you want instructions \ No newline at end of file diff --git a/53_King/csharp/Resources/InstructionsText.txt b/53_King/csharp/Resources/InstructionsText.txt new file mode 100644 index 00000000..c576123b --- /dev/null +++ b/53_King/csharp/Resources/InstructionsText.txt @@ -0,0 +1,17 @@ + + + +Congratulations! You've just been elected Premier of Setats +Detinu, a small communist island 30 by 70 miles long. Your +job is to decide upon the country's budget and distribute +money to your countrymen from the communal treasury. +The money system is rallods, and each person needs 100 +rallods per year to survive. Your country's income comes +from farm produce and tourists visiting your magnificent +forests, hunting, fishing, etc. Half your land if farm land +which also has an excellent mineral content and may be sold +to foreign industry (strip mining) who import and support +their own workers. Crops cost between 10 and 15 rallods per +square mile to plant. +Your goal is to complete your {0} year term of office. +Good luck! diff --git a/53_King/csharp/Resources/InsufficientReserves.txt b/53_King/csharp/Resources/InsufficientReserves.txt new file mode 100644 index 00000000..e69de29b diff --git a/53_King/csharp/Resources/LandPlanted.txt b/53_King/csharp/Resources/LandPlanted.txt new file mode 100644 index 00000000..e52529aa --- /dev/null +++ b/53_King/csharp/Resources/LandPlanted.txt @@ -0,0 +1 @@ +Of {0} sq. miles planted, \ No newline at end of file diff --git a/53_King/csharp/Resources/PlantLandError1.txt b/53_King/csharp/Resources/PlantLandError1.txt new file mode 100644 index 00000000..ef959f9f --- /dev/null +++ b/53_King/csharp/Resources/PlantLandError1.txt @@ -0,0 +1 @@ + Sorry, but each countryman can only plant 2 sq. miles. \ No newline at end of file diff --git a/53_King/csharp/Resources/PlantLandError2.txt b/53_King/csharp/Resources/PlantLandError2.txt new file mode 100644 index 00000000..e5844aa1 --- /dev/null +++ b/53_King/csharp/Resources/PlantLandError2.txt @@ -0,0 +1 @@ + Sorry, but you've only {0} sq. miles of farm land. \ No newline at end of file diff --git a/53_King/csharp/Resources/PlantLandError3.txt b/53_King/csharp/Resources/PlantLandError3.txt new file mode 100644 index 00000000..38264442 --- /dev/null +++ b/53_King/csharp/Resources/PlantLandError3.txt @@ -0,0 +1 @@ + Think again, You've only {0} rallods left in the treasury. diff --git a/53_King/csharp/Resources/PlantLandPrompt.txt b/53_King/csharp/Resources/PlantLandPrompt.txt new file mode 100644 index 00000000..f2fe448e --- /dev/null +++ b/53_King/csharp/Resources/PlantLandPrompt.txt @@ -0,0 +1 @@ +How many square miles do you wish to plant \ No newline at end of file diff --git a/53_King/csharp/Resources/PollutionError.txt b/53_King/csharp/Resources/PollutionError.txt new file mode 100644 index 00000000..f99644e3 --- /dev/null +++ b/53_King/csharp/Resources/PollutionError.txt @@ -0,0 +1 @@ + Think again. You only have {0} rallods remaining. diff --git a/53_King/csharp/Resources/PollutionPrompt.txt b/53_King/csharp/Resources/PollutionPrompt.txt new file mode 100644 index 00000000..252fb333 --- /dev/null +++ b/53_King/csharp/Resources/PollutionPrompt.txt @@ -0,0 +1 @@ +How many rallods do you wish to spend on pollution control \ No newline at end of file diff --git a/53_King/csharp/Resources/Resource.cs b/53_King/csharp/Resources/Resource.cs new file mode 100644 index 00000000..5ea09e1c --- /dev/null +++ b/53_King/csharp/Resources/Resource.cs @@ -0,0 +1,112 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace King.Resources; + +internal static class Resource +{ + private static bool _sellLandErrorShown; + + public static Stream Title => GetStream(); + + public static string InstructionsPrompt => GetString(); + public static string InstructionsText(int years) => string.Format(GetString(), years); + + public static string Status( + float rallods, + float countrymen, + float workers, + float land, + float landValue, + float plantingCost) + => string.Format( + workers == 0 ? StatusWithWorkers : StatusSansWorkers, + rallods, + (int)countrymen, + (int)workers, + (int)land, + landValue, + plantingCost); + + private static string StatusWithWorkers => GetString(); + private static string StatusSansWorkers => GetString(); + + public static string SellLandPrompt => GetString(); + public static string SellLandError(float farmLand) + { + var error = string.Format(GetString(), farmLand, _sellLandErrorShown ? "" : SellLandErrorReason); + _sellLandErrorShown = true; + return error; + } + private static string SellLandErrorReason => GetString(); + + public static string GiveRallodsPrompt => GetString(); + public static string GiveRallodsError(float rallods) => string.Format(GetString(), rallods); + + public static string PlantLandPrompt => GetString(); + public static string PlantLandError1 => GetString(); + public static string PlantLandError2(float farmLand) => string.Format(GetString(), farmLand); + public static string PlantLandError3(float rallods) => string.Format(GetString(), rallods); + + public static string PollutionPrompt => GetString(); + public static string PollutionError(float rallods) => string.Format(GetString(), rallods); + + public static string DeathsStarvation(float deaths) => string.Format(GetString(), (int)deaths); + public static string DeathsPollution(int deaths) => string.Format(GetString(), deaths); + public static string FuneralExpenses(int expenses) => string.Format(GetString(), expenses); + public static string InsufficientReserves => GetString(); + + public static string WorkerMigration(int newWorkers) => string.Format(GetString(), newWorkers); + public static string Migration(int migration) + => string.Format(migration < 0 ? Emigration : Immigration, Math.Abs(migration)); + public static string Emigration => GetString(); + public static string Immigration => GetString(); + + public static string LandPlanted(float landPlanted) + => landPlanted > 0 ? string.Format(GetString(), (int)landPlanted) : ""; + public static string Harvest(int yield, int income, bool hasIndustry) + => string.Format(GetString(), yield, HarvestReason(hasIndustry), income); + private static string HarvestReason(bool hasIndustry) => hasIndustry ? GetString() : ""; + + public static string TourismEarnings(int income) => string.Format(GetString(), income); + public static string TourismDecrease(IRandom random) => string.Format(GetString(), TourismReason(random)); + private static string TourismReason(IRandom random) => GetStrings()[random.Next(5)]; + + private static string EndAlso(IRandom random) + => random.Next(10) switch + { + <= 3 => GetStrings()[0], + <= 6 => GetStrings()[1], + _ => GetStrings()[2] + }; + + public static string EndCongratulations(int termLength) => string.Format(GetString(), termLength); + private static string EndConsequences(IRandom random) => GetStrings()[random.Next(2)]; + public static string EndForeignWorkers(IRandom random) => string.Format(GetString(), EndConsequences(random)); + public static string EndManyDead(int deaths, IRandom random) => string.Format(GetString(), deaths, EndAlso(random)); + public static string EndMoneyLeftOver() => GetString(); + public static string EndOneThirdDead(IRandom random) => string.Format(GetString(), EndConsequences(random)); + + public static string SavedYearsPrompt => GetString(); + public static string SavedYearsError(int years) => string.Format(GetString(), years); + public static string SavedTreasuryPrompt => GetString(); + public static string SavedCountrymenPrompt => GetString(); + public static string SavedWorkersPrompt => GetString(); + public static string SavedLandPrompt => GetString(); + public static string SavedLandError => GetString(); + + public static string Goodbye => GetString(); + + private static string[] GetStrings([CallerMemberName] string? name = null) => GetString(name).Split(';'); + + private static string GetString([CallerMemberName] string? name = null) + { + using var stream = GetStream(name); + using var reader = new StreamReader(stream); + return reader.ReadToEnd(); + } + + private static Stream GetStream([CallerMemberName] string? name = null) => + Assembly.GetExecutingAssembly().GetManifestResourceStream($"{typeof(Resource).Namespace}.{name}.txt") + ?? throw new Exception($"Could not find embedded resource stream '{name}'."); +} \ No newline at end of file diff --git a/53_King/csharp/Resources/SavedCountrymenPrompt.txt b/53_King/csharp/Resources/SavedCountrymenPrompt.txt new file mode 100644 index 00000000..120718d2 --- /dev/null +++ b/53_King/csharp/Resources/SavedCountrymenPrompt.txt @@ -0,0 +1 @@ +How many countrymen \ No newline at end of file diff --git a/53_King/csharp/Resources/SavedLandError.txt b/53_King/csharp/Resources/SavedLandError.txt new file mode 100644 index 00000000..9ac66dad --- /dev/null +++ b/53_King/csharp/Resources/SavedLandError.txt @@ -0,0 +1,2 @@ + Come on, you started with 1000 sq. miles of farm land + and 10,000 sq. miles of forest land diff --git a/53_King/csharp/Resources/SavedLandPrompt.txt b/53_King/csharp/Resources/SavedLandPrompt.txt new file mode 100644 index 00000000..e8afdf72 --- /dev/null +++ b/53_King/csharp/Resources/SavedLandPrompt.txt @@ -0,0 +1 @@ +How many square miles of land \ No newline at end of file diff --git a/53_King/csharp/Resources/SavedTreasuryPrompt.txt b/53_King/csharp/Resources/SavedTreasuryPrompt.txt new file mode 100644 index 00000000..f4a50f58 --- /dev/null +++ b/53_King/csharp/Resources/SavedTreasuryPrompt.txt @@ -0,0 +1 @@ +How much did you have in the treasury \ No newline at end of file diff --git a/53_King/csharp/Resources/SavedWorkersPrompt.txt b/53_King/csharp/Resources/SavedWorkersPrompt.txt new file mode 100644 index 00000000..b88a9c10 --- /dev/null +++ b/53_King/csharp/Resources/SavedWorkersPrompt.txt @@ -0,0 +1 @@ +How many workers \ No newline at end of file diff --git a/53_King/csharp/Resources/SavedYearsError.txt b/53_King/csharp/Resources/SavedYearsError.txt new file mode 100644 index 00000000..1c8c3c99 --- /dev/null +++ b/53_King/csharp/Resources/SavedYearsError.txt @@ -0,0 +1 @@ + Come on, your term in office is only {0} years. diff --git a/53_King/csharp/Resources/SavedYearsPrompt.txt b/53_King/csharp/Resources/SavedYearsPrompt.txt new file mode 100644 index 00000000..afbda229 --- /dev/null +++ b/53_King/csharp/Resources/SavedYearsPrompt.txt @@ -0,0 +1 @@ +How many years had you been in office when interrupted \ No newline at end of file diff --git a/53_King/csharp/Resources/SellLandError.txt b/53_King/csharp/Resources/SellLandError.txt new file mode 100644 index 00000000..83e0266c --- /dev/null +++ b/53_King/csharp/Resources/SellLandError.txt @@ -0,0 +1,2 @@ +*** Think again. You only have {0} square miles of farm land. +{1} \ No newline at end of file diff --git a/53_King/csharp/Resources/SellLandErrorReason.txt b/53_King/csharp/Resources/SellLandErrorReason.txt new file mode 100644 index 00000000..836352bb --- /dev/null +++ b/53_King/csharp/Resources/SellLandErrorReason.txt @@ -0,0 +1,4 @@ + +(Foreign industry will only buy farm land because +forest land is uneconomical to strip mine due to trees, +thicker top soil, etc.) diff --git a/53_King/csharp/Resources/SellLandPrompt.txt b/53_King/csharp/Resources/SellLandPrompt.txt new file mode 100644 index 00000000..8d01c2c2 --- /dev/null +++ b/53_King/csharp/Resources/SellLandPrompt.txt @@ -0,0 +1 @@ +How many square miles do you wish to sell to industry \ No newline at end of file diff --git a/53_King/csharp/Resources/StatusSansWorkers.txt b/53_King/csharp/Resources/StatusSansWorkers.txt new file mode 100644 index 00000000..7efb84e7 --- /dev/null +++ b/53_King/csharp/Resources/StatusSansWorkers.txt @@ -0,0 +1,6 @@ + +You now have {0} rallods in the treasury. + {1} countrymen, and {3} sq. miles of land. +This year industry will buy land for {4} rallods per square mile. +Land currently costs {5} rallods per square mile to plant. + diff --git a/53_King/csharp/Resources/StatusWithWorkers.txt b/53_King/csharp/Resources/StatusWithWorkers.txt new file mode 100644 index 00000000..e7bc5c4a --- /dev/null +++ b/53_King/csharp/Resources/StatusWithWorkers.txt @@ -0,0 +1,6 @@ + +You now have {0} rallods in the treasury. + {1} countrymen, {2} foreign workers and {3} sq. miles of land. +This year industry will buy land for {4} rallods per square mile. +Land currently costs {5} rallods per square mile to plant. + diff --git a/53_King/csharp/Resources/Title.txt b/53_King/csharp/Resources/Title.txt new file mode 100644 index 00000000..f9f7854d --- /dev/null +++ b/53_King/csharp/Resources/Title.txt @@ -0,0 +1,5 @@ + King + Creative Computing Morristown, New Jersey + + + diff --git a/53_King/csharp/Resources/TourismDecrease.txt b/53_King/csharp/Resources/TourismDecrease.txt new file mode 100644 index 00000000..dcb45fc7 --- /dev/null +++ b/53_King/csharp/Resources/TourismDecrease.txt @@ -0,0 +1 @@ + Decrease because {0} \ No newline at end of file diff --git a/53_King/csharp/Resources/TourismEarnings.txt b/53_King/csharp/Resources/TourismEarnings.txt new file mode 100644 index 00000000..db9251dd --- /dev/null +++ b/53_King/csharp/Resources/TourismEarnings.txt @@ -0,0 +1 @@ + You made {0} rallods from tourist trade. \ No newline at end of file diff --git a/53_King/csharp/Resources/TourismReason.txt b/53_King/csharp/Resources/TourismReason.txt new file mode 100644 index 00000000..b3535c3d --- /dev/null +++ b/53_King/csharp/Resources/TourismReason.txt @@ -0,0 +1,5 @@ +fish population has dwindled due to water pollution. +;air pollution is killing game bird population. +;mineral baths are being ruined by water pollution. +;unpleasant smog is discouraging sun bathers. +;hotels are looking shabby due to smog grit. diff --git a/53_King/csharp/Resources/WorkerMigration.txt b/53_King/csharp/Resources/WorkerMigration.txt new file mode 100644 index 00000000..d2f3b378 --- /dev/null +++ b/53_King/csharp/Resources/WorkerMigration.txt @@ -0,0 +1 @@ + {0} workers came to the country and \ No newline at end of file diff --git a/53_King/csharp/Result.cs b/53_King/csharp/Result.cs new file mode 100644 index 00000000..185f4617 --- /dev/null +++ b/53_King/csharp/Result.cs @@ -0,0 +1,7 @@ +namespace King; + +internal record struct Result (bool IsGameOver, string Message) +{ + internal static Result GameOver(string message) => new(true, message); + internal static Result Continue => new(false, ""); +} diff --git a/53_King/csharp/ValidityTest.cs b/53_King/csharp/ValidityTest.cs new file mode 100644 index 00000000..f1aeb5ec --- /dev/null +++ b/53_King/csharp/ValidityTest.cs @@ -0,0 +1,26 @@ +namespace King; + +internal class ValidityTest +{ + private readonly Predicate _isValid; + private readonly Func _getError; + + public ValidityTest(Predicate isValid, string error) + : this(isValid, () => error) + { + } + + public ValidityTest(Predicate isValid, Func getError) + { + _isValid = isValid; + _getError = getError; + } + + public bool IsValid(float value, IReadWrite io) + { + if (_isValid(value)) { return true; } + + io.Write(_getError()); + return false; + } +} \ No newline at end of file diff --git a/53_King/csharp/Year.cs b/53_King/csharp/Year.cs new file mode 100644 index 00000000..8e6bb0b4 --- /dev/null +++ b/53_King/csharp/Year.cs @@ -0,0 +1,154 @@ +using System.Text; + +namespace King; + +internal class Year +{ + private readonly Country _country; + private readonly IRandom _random; + private readonly IReadWrite _io; + private readonly int _plantingCost; + private readonly int _landValue; + + private float _landSold; + private float _rallodsDistributed; + private float _landPlanted; + private float _pollutionControlCost; + + private float _citizenSupport; + private int _deaths; + private float _starvationDeaths; + private int _pollutionDeaths; + private int _migration; + + public Year(Country country, IRandom random, IReadWrite io) + { + _country = country; + _random = random; + _io = io; + + _plantingCost = random.Next(10, 15); + _landValue = random.Next(95, 105); + } + + public string Status => _country.GetStatus(_landValue, _plantingCost); + + public Result? GetPlayerActions() + { + var playerSoldLand = _country.SellLand(_landValue, out _landSold); + var playerDistributedRallods = _country.DistributeRallods(out _rallodsDistributed); + var playerPlantedLand = _country.HasRallods && _country.PlantLand(_plantingCost, out _landPlanted); + var playerControlledPollution = _country.HasRallods && _country.ControlPollution(out _pollutionControlCost); + + return playerSoldLand || playerDistributedRallods || playerPlantedLand || playerControlledPollution + ? null + : Result.GameOver(Goodbye); + } + + public Result? EvaluateResults() + { + var rallodsUnspent = _country.Rallods; + + _io.WriteLine(); + _io.WriteLine(); + + return EvaluateDeaths() + ?? EvaluateMigration() + ?? EvaluateAgriculture() + ?? EvaluateTourism() + ?? DetermineResult(rallodsUnspent); + } + + public Result? EvaluateDeaths() + { + var supportedCountrymen = _rallodsDistributed / 100; + _citizenSupport = supportedCountrymen - _country.Countrymen; + _starvationDeaths = -_citizenSupport; + if (_starvationDeaths > 0) + { + if (supportedCountrymen < 50) { return Result.GameOver(EndOneThirdDead(_random)); } + _io.WriteLine(DeathsStarvation(_starvationDeaths)); + } + + var pollutionControl = _pollutionControlCost >= 25 ? _pollutionControlCost / 25 : 1; + _pollutionDeaths = (int)(_random.Next((int)_country.IndustryLand) / pollutionControl); + if (_pollutionDeaths > 0) + { + _io.WriteLine(DeathsPollution(_pollutionDeaths)); + } + + _deaths = (int)(_starvationDeaths + _pollutionDeaths); + if (_deaths > 0) + { + var funeralCosts = _deaths * 9; + _io.WriteLine(FuneralExpenses(funeralCosts)); + + if (!_country.TrySpend(funeralCosts, _landValue)) + { + _io.WriteLine(InsufficientReserves); + } + + _country.RemoveTheDead(_deaths); + } + + return null; + } + + private Result? EvaluateMigration() + { + if (_landSold > 0) + { + var newWorkers = (int)(_landSold + _random.NextFloat(10) - _random.NextFloat(20)); + if (!_country.HasWorkers) { newWorkers += 20; } + _io.Write(WorkerMigration(newWorkers)); + _country.AddWorkers(newWorkers); + } + + _migration = + (int)(_citizenSupport / 10 + _pollutionControlCost / 25 - _country.IndustryLand / 50 - _pollutionDeaths / 2); + _io.WriteLine(Migration(_migration)); + _country.Migration(_migration); + + return null; + } + + private Result? EvaluateAgriculture() + { + var ruinedCrops = (int)Math.Min(_country.IndustryLand * (_random.NextFloat() + 1.5f) / 2, _landPlanted); + var yield = (int)(_landPlanted - ruinedCrops); + var income = (int)(yield * _landValue / 2f); + + _io.Write(LandPlanted(_landPlanted)); + _io.Write(Harvest(yield, income, _country.IndustryLand > 0)); + + _country.SellCrops(income); + + return null; + } + + private Result? EvaluateTourism() + { + var reputationValue = (int)((_country.Countrymen - _migration) * 22 + _random.NextFloat(500)); + var industryAdjustment = (int)(_country.IndustryLand * 15); + var tourismIncome = Math.Abs(reputationValue - industryAdjustment); + + _io.WriteLine(TourismEarnings(tourismIncome)); + if (industryAdjustment > 0 && tourismIncome < _country.PreviousTourismIncome) + { + _io.Write(TourismDecrease(_random)); + } + + _country.EntertainTourists(tourismIncome); + + return null; + } + + private Result? DetermineResult(float rallodsUnspent) + { + if (_deaths > 200) { return Result.GameOver(EndManyDead(_deaths, _random)); } + if (_country.Countrymen < 343) { return Result.GameOver(EndOneThirdDead(_random)); } + if (rallodsUnspent / 100 > 5 && _starvationDeaths >= 2) { return Result.GameOver(EndMoneyLeftOver()); } + if (_country.Workers > _country.Countrymen) { return Result.GameOver(EndForeignWorkers(_random)); } + return null; + } +} diff --git a/55_Life/rust/Cargo.toml b/55_Life/rust/Cargo.toml new file mode 100644 index 00000000..1ec69633 --- /dev/null +++ b/55_Life/rust/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "rust" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/55_Life/rust/README.md b/55_Life/rust/README.md new file mode 100644 index 00000000..19f2d09d --- /dev/null +++ b/55_Life/rust/README.md @@ -0,0 +1,22 @@ +# Conway's Life + +Original from David Ahl's _Basic Computer Games_, downloaded from http://www.vintage-basic.net/games.html. + +Ported to Rust by Jon Fetter-Degges + +Developed and tested on Rust 1.64.0 + +## How to Run + +Install Rust using the instructions at [rust-lang.org](https://www.rust-lang.org/tools/install). + +At a command or shell prompt in the `rust` subdirectory, enter `cargo run`. + +## Differences from Original Behavior + +* The simulation stops if all cells die. +* `.` at the beginning of an input line is supported but optional. +* Input of more than 66 columns is rejected. Input will automatically terminate after 20 rows. Beyond these bounds, the original +implementation would have marked the board as invalid, and beyond 68 cols/24 rows it would have had an out of bounds array access. +* The check for the string "DONE" at the end of input is case-independent. +* The program pauses for half a second between each generation. diff --git a/55_Life/rust/src/main.rs b/55_Life/rust/src/main.rs new file mode 100644 index 00000000..50aa4927 --- /dev/null +++ b/55_Life/rust/src/main.rs @@ -0,0 +1,275 @@ +// Rust implementation of the "Basic Computer Games" version of Conway's Life +// +// Jon Fetter-Degges +// October 2022 + +// I am a Rust newbie. Corrections and suggestions are welcome. + +use std::{cmp, fmt, io, thread, time}; + +// The BASIC implementation uses integers to represent the state of each cell: 1 is +// alive, 2 is about to die, 3 is about to be born, 0 is dead. Here, we'll use an enum +// instead. +// Deriving Copy (which requires Clone) allows us to use this enum value in assignments, +// and deriving Eq (or PartialEq) allows us to use the == operator. These need to be +// explicitly specified because some enums may have associated data that makes copies and +// comparisons more complicated or expensive. +#[derive(Clone, Copy, PartialEq, Eq)] +enum CellState { + Empty, + Alive, + AboutToDie, + AboutToBeBorn, +} + +// Support direct printing of the cell. In this program cells will only be Alive or Empty +// when they are printed. +impl fmt::Display for CellState { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let rep = match *self { + CellState::Empty => ' ', + CellState::Alive => '*', + CellState::AboutToDie => 'o', + CellState::AboutToBeBorn => '.', + }; + write!(f, "{}", rep) + } +} + +// Following the BASIC implementation, we will bound the board at 24 rows x 70 columns. +// The board is an array of CellState. Using an array of arrays gives us bounds checking +// in both dimensions. +const HEIGHT: usize = 24; +const WIDTH: usize = 70; + +struct Board { + cells: [[CellState; WIDTH]; HEIGHT], + min_row: usize, + max_row: usize, + min_col: usize, + max_col: usize, + population: usize, + generation: usize, + invalid: bool, +} + +impl Board { + fn new() -> Board { + Board { + cells: [[CellState::Empty; WIDTH]; HEIGHT], + min_row: 0, + max_row: 0, + min_col: 0, + max_col: 0, + population: 0, + generation: 0, + invalid: false, + } + } +} + +fn main() { + println!(); println!(); println!(); + println!("{:33}{}", " ", "Life"); + println!("{:14}{}", " ", "Creative Computing Morristown, New Jersey"); + println!("Enter your pattern: "); + let mut board = parse_pattern(get_pattern()); + loop { + finish_cell_transitions(&mut board); + print_board(&board); + mark_cell_transitions(&mut board); + if board.population == 0 { + break; // this isn't in the original implementation but it seemed better than + // spewing blank screens + } + delay(); + } +} + +fn get_pattern() -> Vec> { + let max_line_len = WIDTH - 4; + let max_line_count = HEIGHT - 4; + let mut lines = Vec::new(); + loop { + let mut line = String::new(); + // read_line reads into the buffer (appending if it's not empty). It returns the + // number of characters read, including the newline. This will be 0 on EOF. + // unwrap() will panic and terminate the program if there is an error reading + // from stdin. That's reasonable behavior in this case. + let nread = io::stdin().read_line(&mut line).unwrap(); + let line = line.trim_end(); + if nread == 0 || line.eq_ignore_ascii_case("DONE") { + return lines; + } + // Handle Unicode by converting the string to a vector of characters up front. We + // do this here because we check the number of characters several times, so we + // might as well just do the Unicode parsing once. + let line = Vec::from_iter(line.chars()); + if line.len() > max_line_len { + println!("Line too long - the maximum is {max_line_len} characters."); + continue; + } + lines.push(line); + if lines.len() == max_line_count { + println!("Maximum line count reached. Starting simulation."); + return lines; + } + } +} + +fn parse_pattern(rows: Vec>) -> Board { + // This function assumes that the input pattern in rows is in-bounds. If the pattern + // is too large, this function will panic. get_pattern checks the size of the input, + // so it is safe to call this function with its results. + + let mut board = Board::new(); + + // The BASIC implementation puts the pattern roughly in the center of the board, + // assuming that there are no blank rows at the beginning or end, or blanks entered + // at the beginning or end of every row. It wouldn't be hard to check for that, but + // for now we'll preserve the original behavior. + let nrows = rows.len(); + // If rows is empty, the call to max will return None. The unwrap_or then provides a + // default value + let ncols = rows.iter().map(|l| l.len()).max().unwrap_or(0); + + // The min and max values here are unsigned. If nrows >= 24 or ncols >= 68, these + // assignments will panic - they do not wrap around unless we use a function with + // that specific behavior. Again, we expect bounds checking on the input before this + // function is called. + board.min_row = 11 - nrows / 2; + board.min_col = 33 - ncols / 2; + board.max_row = board.min_row + nrows - 1; + board.max_col = board.min_col + ncols - 1; + + // Loop over the rows provided. enumerate() augments the iterator with an index. + for (row_index, pattern) in rows.iter().enumerate() { + let row = board.min_row + row_index; + // Now loop over the non-empty cells in the current row. filter_map takes a + // closure that returns an Option. If the Option is None, filter_map filters out + // that entry from the for loop. If it's Some(x), filter_map executes the loop + // body with the value x. + for col in pattern.iter().enumerate().filter_map(|(col_index, chr)| { + if *chr == ' ' || (*chr == '.' && col_index == 0) { + None + } else { + Some(board.min_col + col_index) + } + }) { + board.cells[row][col] = CellState::Alive; + board.population += 1; + } + } + + board +} + +fn finish_cell_transitions(board: &mut Board) { + // In the BASIC implementation, this happens in the same loop that prints the board. + // We're breaking it out to improve separation of concerns. + let mut min_row = HEIGHT - 1; + let mut max_row = 0usize; + let mut min_col = WIDTH - 1; + let mut max_col = 0usize; + for row_index in board.min_row-1..=board.max_row+1 { + let mut any_alive_this_row = false; + for col_index in board.min_col-1..=board.max_col+1 { + let cell = &mut board.cells[row_index][col_index]; + if *cell == CellState::AboutToBeBorn { + *cell = CellState::Alive; + board.population += 1; + } else if *cell == CellState::AboutToDie { + *cell = CellState::Empty; + board.population -= 1; + } + if *cell == CellState::Alive { + any_alive_this_row = true; + min_col = cmp::min(min_col, col_index); + max_col = cmp::max(max_col, col_index); + } + } + if any_alive_this_row { + min_row = cmp::min(min_row, row_index); + max_row = cmp::max(max_row, row_index); + } + } + // If anything is alive within two cells of the boundary, mark the board invalid and + // clamp the bounds. We need a two-cell margin because we'll count neighbors on cells + // one space outside the min/max, and when we count neighbors we go out by an + // additional space. + if min_row < 2 { + min_row = 2; + board.invalid = true; + } + if max_row > HEIGHT - 3 { + max_row = HEIGHT - 3; + board.invalid = true; + } + if min_col < 2 { + min_col = 2; + board.invalid = true; + } + if max_col > WIDTH - 3 { + max_col = WIDTH - 3; + board.invalid = true; + } + + board.min_row = min_row; + board.max_row = max_row; + board.min_col = min_col; + board.max_col = max_col; +} + +fn print_board(board: &Board) { + println!(); println!(); println!(); + print!("Generation: {} Population: {}", board.generation, board.population); + if board.invalid { + print!(" Invalid!"); + } + println!(); + for row_index in 0..HEIGHT { + for col_index in 0..WIDTH { + // This print uses the Display implementation for cell_state, above. + print!("{}", board.cells[row_index][col_index]); + } + println!(); + } +} + +fn count_neighbors(board: &Board, row_index: usize, col_index: usize) -> i32 { + // Simply loop over all the immediate neighbors of a cell. We assume that the row and + // column indices are not on (or outside) the boundary of the arrays; if they are, + // the function will panic instead of going out of bounds. + let mut count = 0; + for i in row_index-1..=row_index+1 { + for j in col_index-1..=col_index+1 { + if i == row_index && j == col_index { + continue; + } + if board.cells[i][j] == CellState::Alive || board.cells[i][j] == CellState::AboutToDie { + count += 1; + } + } + } + count +} + +fn mark_cell_transitions(board: &mut Board) { + for row_index in board.min_row-1..=board.max_row+1 { + for col_index in board.min_col-1..=board.max_col+1 { + let neighbors = count_neighbors(board, row_index, col_index); + // Borrow a mutable reference to the array cell + let this_cell_state = &mut board.cells[row_index][col_index]; + *this_cell_state = match *this_cell_state { + CellState::Empty if neighbors == 3 => CellState::AboutToBeBorn, + CellState::Alive if !(2..=3).contains(&neighbors) => CellState::AboutToDie, + _ => *this_cell_state, + } + } + } + board.generation += 1; +} + +fn delay() { + thread::sleep(time::Duration::from_millis(500)); +} diff --git a/56_Life_for_Two/README.md b/56_Life_for_Two/README.md index 3d906342..e68c8d64 100644 --- a/56_Life_for_Two/README.md +++ b/56_Life_for_Two/README.md @@ -48,3 +48,17 @@ http://www.vintage-basic.net/games.html #### Porting Notes (please note any difficulties or challenges in porting here) + +Note: The original program has a bug. The instructions say that if both players +enter the same cell that the cell is set to 0 or empty. However, the original +Basic program tells the player "ILLEGAL COORDINATES" and makes another cell be entered, +giving a slightly unfair advantage to the 2nd player. + +The Perl verson of the program fixes the bug and follows the instructions. + +Note: The original code had "GOTO 800" but label 800 didn't exist; it should have gone to label 999. +The Basic program has been fixed. + +Note: The Basic program is written to assume it's being played on a Teletype, i.e. output is printed +on paper. To play on a terminal the input must not be echoed, which can be a challenge to do portably +and without tying the solution to a specific OS. Some versions may tell you how to do this, others might not. diff --git a/56_Life_for_Two/csharp/Board.cs b/56_Life_for_Two/csharp/Board.cs new file mode 100644 index 00000000..f3cb0329 --- /dev/null +++ b/56_Life_for_Two/csharp/Board.cs @@ -0,0 +1,74 @@ +using System.Collections; +using System.Text; + +namespace LifeforTwo; + +internal class Board : IEnumerable +{ + private readonly Piece[,] _cells = new Piece[7, 7]; + private readonly Dictionary _cellCounts = + new() { [Piece.None] = 0, [Piece.Player1] = 0, [Piece.Player2] = 0 }; + + public Piece this[Coordinates coordinates] + { + get => this[coordinates.X, coordinates.Y]; + set => this[coordinates.X, coordinates.Y] = value; + } + + private Piece this[int x, int y] + { + get => _cells[x, y]; + set + { + if (!_cells[x, y].IsEmpty) { _cellCounts[_cells[x, y]] -= 1; } + _cells[x, y] = value; + _cellCounts[value] += 1; + } + } + + public int Player1Count => _cellCounts[Piece.Player1]; + public int Player2Count => _cellCounts[Piece.Player2]; + + internal bool IsEmptyAt(Coordinates coordinates) => this[coordinates].IsEmpty; + + internal void ClearCell(Coordinates coordinates) => this[coordinates] = Piece.NewNone(); + internal void AddPlayer1Piece(Coordinates coordinates) => this[coordinates] = Piece.NewPlayer1(); + internal void AddPlayer2Piece(Coordinates coordinates) => this[coordinates] = Piece.NewPlayer2(); + + public override string ToString() + { + var builder = new StringBuilder(); + + for (var y = 0; y <= 6; y++) + { + builder.AppendLine(); + for (var x = 0; x <= 6; x++) + { + builder.Append(GetCellDisplay(x, y)); + } + } + + return builder.ToString(); + } + + private string GetCellDisplay(int x, int y) => + (x, y) switch + { + (0 or 6, _) => $" {y % 6} ", + (_, 0 or 6) => $" {x % 6} ", + _ => $" {this[x, y]} " + }; + + public IEnumerator GetEnumerator() + { + for (var x = 1; x <= 5; x++) + { + for (var y = 1; y <= 5; y++) + { + yield return new(x, y); + } + } + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); +} diff --git a/56_Life_for_Two/csharp/Coordinates.cs b/56_Life_for_Two/csharp/Coordinates.cs new file mode 100644 index 00000000..09bd3dd4 --- /dev/null +++ b/56_Life_for_Two/csharp/Coordinates.cs @@ -0,0 +1,31 @@ +namespace LifeforTwo; + +internal record Coordinates (int X, int Y) +{ + public static bool TryCreate((float X, float Y) values, out Coordinates coordinates) + { + if (values.X <= 0 || values.X > 5 || values.Y <= 0 || values.Y > 5) + { + coordinates = new(0, 0); + return false; + } + + coordinates = new((int)values.X, (int)values.Y); + return true; + } + + public static Coordinates operator +(Coordinates coordinates, int value) => + new (coordinates.X + value, coordinates.Y + value); + + public IEnumerable GetNeighbors() + { + yield return new(X - 1, Y); + yield return new(X + 1, Y); + yield return new(X, Y - 1); + yield return new(X, Y + 1); + yield return new(X - 1, Y - 1); + yield return new(X + 1, Y - 1); + yield return new(X - 1, Y + 1); + yield return new(X + 1, Y + 1); + } +} diff --git a/56_Life_for_Two/csharp/Game.cs b/56_Life_for_Two/csharp/Game.cs new file mode 100644 index 00000000..de6cb5a5 --- /dev/null +++ b/56_Life_for_Two/csharp/Game.cs @@ -0,0 +1,26 @@ +internal class Game +{ + private readonly IReadWrite _io; + + public Game(IReadWrite io) + { + _io = io; + } + + public void Play() + { + _io.Write(Streams.Title); + + var life = new Life(_io); + + _io.Write(life.FirstGeneration); + + foreach (var generation in life) + { + _io.WriteLine(); + _io.Write(generation); + } + + _io.WriteLine(life.Result ?? "No result"); + } +} diff --git a/56_Life_for_Two/csharp/Generation.cs b/56_Life_for_Two/csharp/Generation.cs new file mode 100644 index 00000000..b96ee478 --- /dev/null +++ b/56_Life_for_Two/csharp/Generation.cs @@ -0,0 +1,89 @@ +internal class Generation +{ + private readonly Board _board; + + public Generation(Board board) + { + _board = board; + CountNeighbours(); + } + + public Board Board => _board; + + public int Player1Count => _board.Player1Count; + public int Player2Count => _board.Player2Count; + + public string? Result => + (Player1Count, Player2Count) switch + { + (0, 0) => Strings.Draw, + (_, 0) => string.Format(Formats.Winner, 1), + (0, _) => string.Format(Formats.Winner, 2), + _ => null + }; + + public static Generation Create(IReadWrite io) + { + var board = new Board(); + + SetInitialPieces(1, coord => board.AddPlayer1Piece(coord)); + SetInitialPieces(2, coord => board.AddPlayer2Piece(coord)); + + return new Generation(board); + + void SetInitialPieces(int player, Action setPiece) + { + io.WriteLine(Formats.InitialPieces, player); + for (var i = 1; i <= 3; i++) + { + setPiece(io.ReadCoordinates(board)); + } + } + } + + public Generation CalculateNextGeneration() + { + var board = new Board(); + + foreach (var coordinates in _board) + { + board[coordinates] = _board[coordinates].GetNext(); + } + + return new(board); + } + + public void AddPieces(IReadWrite io) + { + var player1Coordinate = io.ReadCoordinates(1, _board); + var player2Coordinate = io.ReadCoordinates(2, _board); + + if (player1Coordinate == player2Coordinate) + { + io.Write(Streams.SameCoords); + // This is a bug existing in the original code. The line should be _board[_coordinates[_player]] = 0; + _board.ClearCell(player1Coordinate + 1); + } + else + { + _board.AddPlayer1Piece(player1Coordinate); + _board.AddPlayer2Piece(player2Coordinate); + } + } + + private void CountNeighbours() + { + foreach (var coordinates in _board) + { + var piece = _board[coordinates]; + if (piece.IsEmpty) { continue; } + + foreach (var neighbour in coordinates.GetNeighbors()) + { + _board[neighbour] = _board[neighbour].AddNeighbour(piece); + } + } + } + + public override string ToString() => _board.ToString(); +} \ No newline at end of file diff --git a/56_Life_for_Two/csharp/IOExtensions.cs b/56_Life_for_Two/csharp/IOExtensions.cs new file mode 100644 index 00000000..d7db9bf4 --- /dev/null +++ b/56_Life_for_Two/csharp/IOExtensions.cs @@ -0,0 +1,22 @@ +internal static class IOExtensions +{ + internal static Coordinates ReadCoordinates(this IReadWrite io, int player, Board board) + { + io.Write(Formats.Player, player); + return io.ReadCoordinates(board); + } + + internal static Coordinates ReadCoordinates(this IReadWrite io, Board board) + { + while (true) + { + io.WriteLine("X,Y"); + var values = io.Read2Numbers("&&&&&&\r"); + if (Coordinates.TryCreate(values, out var coordinates) && board.IsEmptyAt(coordinates)) + { + return coordinates; + } + io.Write(Streams.IllegalCoords); + } + } +} \ No newline at end of file diff --git a/56_Life_for_Two/csharp/Life.cs b/56_Life_for_Two/csharp/Life.cs new file mode 100644 index 00000000..7848a348 --- /dev/null +++ b/56_Life_for_Two/csharp/Life.cs @@ -0,0 +1,31 @@ +using System.Collections; + +internal class Life : IEnumerable +{ + private readonly IReadWrite _io; + + public Life(IReadWrite io) + { + _io = io; + FirstGeneration = Generation.Create(io); + } + + public Generation FirstGeneration { get; } + public string? Result { get; private set; } + + public IEnumerator GetEnumerator() + { + var current = FirstGeneration; + while (current.Result is null) + { + current = current.CalculateNextGeneration(); + yield return current; + + if (current.Result is null) { current.AddPieces(_io); } + } + + Result = current.Result; + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); +} \ No newline at end of file diff --git a/56_Life_for_Two/csharp/LifeforTwo.csproj b/56_Life_for_Two/csharp/LifeforTwo.csproj index d3fe4757..3870320c 100644 --- a/56_Life_for_Two/csharp/LifeforTwo.csproj +++ b/56_Life_for_Two/csharp/LifeforTwo.csproj @@ -6,4 +6,12 @@ enable enable + + + + + + + + diff --git a/56_Life_for_Two/csharp/Piece.cs b/56_Life_for_Two/csharp/Piece.cs new file mode 100644 index 00000000..20e5adba --- /dev/null +++ b/56_Life_for_Two/csharp/Piece.cs @@ -0,0 +1,54 @@ +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; + +namespace LifeforTwo; + +public struct Piece +{ + public const int None = 0x0000; + public const int Player1 = 0x0100; + public const int Player2 = 0x1000; + private const int PieceMask = Player1 | Player2; + private const int NeighbourValueOffset = 8; + + private static readonly ImmutableHashSet _willBePlayer1 = + new[] { 0x0003, 0x0102, 0x0103, 0x0120, 0x0130, 0x0121, 0x0112, 0x0111, 0x0012 }.ToImmutableHashSet(); + private static readonly ImmutableHashSet _willBePlayer2 = + new[] { 0x0021, 0x0030, 0x1020, 0x1030, 0x1011, 0x1021, 0x1003, 0x1002, 0x1012 }.ToImmutableHashSet(); + + private int _value; + + private Piece(int value) => _value = value; + + public int Value => _value & PieceMask; + public bool IsEmpty => (_value & PieceMask) == None; + + public static Piece NewNone() => new(None); + public static Piece NewPlayer1() => new(Player1); + public static Piece NewPlayer2() => new(Player2); + + public Piece AddNeighbour(Piece neighbour) + { + _value += neighbour.Value >> NeighbourValueOffset; + return this; + } + + public Piece GetNext() => new( + _value switch + { + _ when _willBePlayer1.Contains(_value) => Player1, + _ when _willBePlayer2.Contains(_value) => Player2, + _ => None + }); + + public override string ToString() => + (_value & PieceMask) switch + { + Player1 => "*", + Player2 => "#", + _ => " " + }; + + public static implicit operator Piece(int value) => new(value); + public static implicit operator int(Piece piece) => piece.Value; +} \ No newline at end of file diff --git a/56_Life_for_Two/csharp/Program.cs b/56_Life_for_Two/csharp/Program.cs new file mode 100644 index 00000000..4c399811 --- /dev/null +++ b/56_Life_for_Two/csharp/Program.cs @@ -0,0 +1,5 @@ +global using Games.Common.IO; +global using static LifeforTwo.Resources.Resource; +global using LifeforTwo; + +new Game(new ConsoleIO()).Play(); diff --git a/56_Life_for_Two/csharp/Resources/Draw.txt b/56_Life_for_Two/csharp/Resources/Draw.txt new file mode 100644 index 00000000..9b9fd8fc --- /dev/null +++ b/56_Life_for_Two/csharp/Resources/Draw.txt @@ -0,0 +1,2 @@ + +A draw \ No newline at end of file diff --git a/56_Life_for_Two/csharp/Resources/IllegalCoords.txt b/56_Life_for_Two/csharp/Resources/IllegalCoords.txt new file mode 100644 index 00000000..ff01dfcb --- /dev/null +++ b/56_Life_for_Two/csharp/Resources/IllegalCoords.txt @@ -0,0 +1 @@ +Illegal coords. Retype diff --git a/56_Life_for_Two/csharp/Resources/InitialPieces.txt b/56_Life_for_Two/csharp/Resources/InitialPieces.txt new file mode 100644 index 00000000..abbadf16 --- /dev/null +++ b/56_Life_for_Two/csharp/Resources/InitialPieces.txt @@ -0,0 +1,2 @@ + +Player {0} - 3 live pieces \ No newline at end of file diff --git a/56_Life_for_Two/csharp/Resources/Player.txt b/56_Life_for_Two/csharp/Resources/Player.txt new file mode 100644 index 00000000..f920e489 --- /dev/null +++ b/56_Life_for_Two/csharp/Resources/Player.txt @@ -0,0 +1,3 @@ + + +Player {0} \ No newline at end of file diff --git a/56_Life_for_Two/csharp/Resources/Resource.cs b/56_Life_for_Two/csharp/Resources/Resource.cs new file mode 100644 index 00000000..c6f59d3b --- /dev/null +++ b/56_Life_for_Two/csharp/Resources/Resource.cs @@ -0,0 +1,37 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace LifeforTwo.Resources; + +internal static class Resource +{ + internal static class Streams + { + public static Stream Title => GetStream(); + public static Stream IllegalCoords => GetStream(); + public static Stream SameCoords => GetStream(); + } + + internal static class Formats + { + public static string InitialPieces => GetString(); + public static string Player => GetString(); + public static string Winner => GetString(); + } + + internal static class Strings + { + public static string Draw => GetString(); + } + + private static string GetString([CallerMemberName] string? name = null) + { + using var stream = GetStream(name); + using var reader = new StreamReader(stream); + return reader.ReadToEnd(); + } + + private static Stream GetStream([CallerMemberName] string? name = null) => + Assembly.GetExecutingAssembly().GetManifestResourceStream($"{typeof(Resource).Namespace}.{name}.txt") + ?? throw new Exception($"Could not find embedded resource stream '{name}'."); +} \ No newline at end of file diff --git a/56_Life_for_Two/csharp/Resources/SameCoords.txt b/56_Life_for_Two/csharp/Resources/SameCoords.txt new file mode 100644 index 00000000..8af7b066 --- /dev/null +++ b/56_Life_for_Two/csharp/Resources/SameCoords.txt @@ -0,0 +1 @@ +Same coord. Set to 0 diff --git a/56_Life_for_Two/csharp/Resources/Title.txt b/56_Life_for_Two/csharp/Resources/Title.txt new file mode 100644 index 00000000..0e3404a7 --- /dev/null +++ b/56_Life_for_Two/csharp/Resources/Title.txt @@ -0,0 +1,6 @@ + Life2 + Creative Computing Morristown, New Jersey + + + + U.B. Life Game diff --git a/56_Life_for_Two/csharp/Resources/Winner.txt b/56_Life_for_Two/csharp/Resources/Winner.txt new file mode 100644 index 00000000..e35c5440 --- /dev/null +++ b/56_Life_for_Two/csharp/Resources/Winner.txt @@ -0,0 +1,2 @@ + +Player {0} is the winner \ No newline at end of file diff --git a/56_Life_for_Two/lifefortwo.bas b/56_Life_for_Two/lifefortwo.bas index e970ef6d..4faf1f12 100644 --- a/56_Life_for_Two/lifefortwo.bas +++ b/56_Life_for_Two/lifefortwo.bas @@ -60,8 +60,8 @@ 571 IF M3=0 THEN B=1: GOTO 575 572 IF M2=0 THEN B=2: GOTO 575 573 GOTO 580 -574 PRINT: PRINT "A DRAW":GOTO 800 -575 PRINT: PRINT "PLAYER";B;"IS THE WINNER":GOTO 800 +574 PRINT: PRINT "A DRAW":GOTO 999 +575 PRINT: PRINT "PLAYER";B;"IS THE WINNER":GOTO 999 580 FOR B=1 TO 2: PRINT: PRINT: PRINT "PLAYER";B;: GOSUB 700 581 IF B=99 THEN 560 582 NEXT B diff --git a/56_Life_for_Two/perl/README.md b/56_Life_for_Two/perl/README.md index e69c8b81..7b3b41a3 100644 --- a/56_Life_for_Two/perl/README.md +++ b/56_Life_for_Two/perl/README.md @@ -1,3 +1,21 @@ Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html) Conversion to [Perl](https://www.perl.org/) + +Note: The original program has a bug (see the README in the above dir). This Perl version fixes it. + +Note: For input, the X value is to the right while the Y value is down. +Therefore, the top right cell is "5,1", not "1,5". + +The original program was made to be played on a Teletype, i.e. a printer on paper. +That allowed the program to "black out" the input line to hide a user's input from his/her +opponent, assuming the opponent was at least looking away. To do the equivalent on a +terminal would require a Perl module that isn't installed by default (i.e. it is not +part of CORE and would also require a C compiler to install), nor do I want to issue a +shell command to "stty" to hide the input because that would restrict the game to Linux/Unix. +This means it would have to be played on the honor system. + +However, if you want to try it, install the module "Term::ReadKey" ("sudo cpan -i Term::ReadKey" +if on Linux/Unix and you have root access). If the code finds that module, it will automatically +use it and hide the input ... and restore echoing input again when the games ends. If the module +is not found, input will be visible. diff --git a/56_Life_for_Two/perl/lifefortwo.pl b/56_Life_for_Two/perl/lifefortwo.pl new file mode 100644 index 00000000..0c50cdef --- /dev/null +++ b/56_Life_for_Two/perl/lifefortwo.pl @@ -0,0 +1,215 @@ +#!/usr/bin/perl + +# Life_For_Two program in Perl +# Required extensive restructuring to remove all of the GOTO's. +# Translated by Kevin Brannen (kbrannen) + +use strict; +use warnings; + +# try to load module to hide input, set RKey to true if found +my $Rkey = eval { require Term::ReadKey } // 0; +END { Term::ReadKey::ReadMode('normal') if ($Rkey); } + +# globals +my @Board; # 2D board +my @X; # ? +my @Y; # ? +my $Player; # 1 or 2 +my $M2 = 0; # ? +my $M3 = 0; # ? + +# add 0 on front to make data 1 based +my @K = (0,3,102,103,120,130,121,112,111,12,21,30,1020,1030,1011,1021,1003,1002,1012); +my @A = (0,-1,0,1,0,0,-1,0,1,-1,-1,1,-1,-1,1,1,1); + +print "\n"; +print " " x 33, "LIFE2\n"; +print " " x 15, "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n\n\n"; +print " " x 10, "U.B. LIFE GAME\n"; +for my $j (1 .. 5) { for my $k (1 .. 5) { $Board[$j][$k] = 0; } } + +for (1 .. 2) +{ + $Player = $_; # if we make $Player the loop var, the global isn't set + my $p1 = ($Player == 2) ? 30 : 3; + print "\nPLAYER $Player - 3 LIVE PIECES.\n"; + for (1 .. 3) + { + get_input(); + $Board[$X[$Player]][$Y[$Player]] = $p1 if ($Player != 99); + } +} +print_board(); # print board after initial input + +while (1) +{ + print "\n"; + calc_board(); # calc new positions + print_board(); # print current board after calc + + if ($M2 == 0 && $M3 == 0) + { + print "\nA DRAW\n"; + last; + } + if ($M3 == 0) + { + win(1); + last; + } + if ($M2 == 0) + { + win(2); + last; + } + + for (1 .. 2) + { + $Player = $_; # if we make $Player the loop var, the global isn't set + print "\n\nPLAYER $Player "; + get_input(); + last if ($Player == 99); + } + next if ($Player == 99); + + $Board[$X[1]][$Y[1]] = 100; + $Board[$X[2]][$Y[2]] = 1000; +} +exit(0); + +########################################################### + +sub win +{ + my $p = shift; + print "\nPLAYER $p IS THE WINNER\n"; +} + +sub calc_board +{ + for my $j (1 .. 5) + { + for my $k (1 .. 5) + { + if ($Board[$j][$k] > 99) + { + $Player = $Board[$j][$k] > 999 ? 10 : 1; + for (my $c = 1 ; $c <= 15 ; $c += 2) + { + $Board[$j+$A[$c]][$k+$A[$c+1]] = ($Board[$j+$A[$c]][$k+$A[$c+1]] // 0) + $Player; + } + } + } + } +} + +sub print_board +{ + $M2 = 0; + $M3 = 0; + for my $j (0 .. 6) + { + print "\n"; + for my $k (0 .. 6) + { + if ($j != 0 && $j != 6) + { + if ($k != 0 && $k != 6) + { + print_row($j, $k); + next; + } + if ($j == 6) + { + print "0\n"; + return; + } + print " $j "; + } + else + { + if ($k == 6) + { + print " 0 "; + last; + } + print " $k "; + } + } + } +} + +sub print_row +{ + my ($j, $k) = @_; + + if ($Board[$j][$k] >= 3) + { + my $c; + for $c (1 .. 18) + { + if ($Board[$j][$k] == $K[$c]) + { + if ($c <= 9) + { + $Board[$j][$k] = 100; + $M2++; + print " * "; + } + else + { + $Board[$j][$k] = 1000; + $M3++; + print " # "; + } + return; + } + } + } + $Board[$j][$k] = 0; + print " "; +} + +sub get_input +{ + while (1) + { + print "X,Y\n"; + my $ans; + + if ($Rkey) + { + # code to hide input + Term::ReadKey::ReadMode('noecho'); + $ans = Term::ReadKey::ReadLine(0); + Term::ReadKey::ReadMode('restore'); + print "\n"; # do this since the one entered was hidden + } + else + { + # normal, input visible + chomp($ans = <>); + } + + ($Y[$Player], $X[$Player]) = split(/[,\s]+/, $ans, 2); + if ($X[$Player] > 5 || $X[$Player] < 1 || $Y[$Player] > 5 || $Y[$Player] < 1) + { + print "ILLEGAL COORDS. RETYPE\n"; + next; + } + # this tells you the cell was already taken not zero it out, bug! + #if ($Board[$X[$Player]][$Y[$Player]] != 0) + #{ + # print "ILLEGAL COORDS. RETYPE\n"; + # next; + #} + last; + } + + return if ($Player == 1 || $X[1] != $X[2] || $Y[1] != $Y[2]); + + print "SAME COORD. SET TO 0\n"; + $Board[$X[$Player]+1][$Y[$Player]+1] = 0; + $Player = 99; +} diff --git a/56_Life_for_Two/python/life_for_two.py b/56_Life_for_Two/python/life_for_two.py index e2116826..da4e1619 100644 --- a/56_Life_for_Two/python/life_for_two.py +++ b/56_Life_for_Two/python/life_for_two.py @@ -10,11 +10,14 @@ Ported by Sajid Sarker (2022). gn = [[0 for i in range(6)] for j in range(6)] gx = [0 for x in range(3)] gy = [0 for x in range(3)] -gk = [0, 3, 102, 103, 120, 130, 121, 112, 111, 12, 21, 30, 1020, 1030, 1011, 1021, 1003, 1002, 1012] +gk = [0, 3, 102, 103, 120, 130, 121, + 112, 111, 12, 21, 30, 1020, 1030, + 1011, 1021, 1003, 1002, 1012] ga = [0, -1, 0, 1, 0, 0, -1, 0, 1, -1, -1, 1, -1, -1, 1, 1, 1] m2 = 0 m3 = 0 + # Helper Functions def tab(number) -> str: t = "" @@ -22,21 +25,24 @@ def tab(number) -> str: t += " " return t + def display_header() -> None: print("{}LIFE2".format(tab(33))) print("{}CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n\n\n".format(tab(15))) print("{}U.B. LIFE GAME".format(tab(10))) + # Board Functions def setup_board() -> None: # Players add symbols to initially setup the board for b in range(1, 3): p1 = 3 if b != 2 else 30 print("\nPLAYER {} - 3 LIVE PIECES.".format(b)) - for k1 in range(1, 4): + for _ in range(1, 4): query_player(b) gn[gx[b]][gy[b]] = p1 + def modify_board() -> None: # Players take turns to add symbols and modify the board for b in range(1, 3): @@ -48,6 +54,7 @@ def modify_board() -> None: gn[gx[1]][gy[1]] = 100 gn[gx[2]][gy[2]] = 1000 + def simulate_board() -> None: # Simulate the board for one step for j in range(1, 6): @@ -55,8 +62,9 @@ def simulate_board() -> None: if gn[j][k] > 99: b = 1 if gn[j][k] <= 999 else 10 for o1 in range(1, 16, 2): - gn[j + ga[o1] - 1][k + ga[o1 + 1] - 1] = gn[j + ga[o1] - 1][k + ga[o1 + 1] - 1] + b - #gn[j + ga[o1]][k + ga[o1 + 1]] = gn[j + ga[o1]][k + ga[o1 + 1]] + b + gn[j + ga[o1] - 1][k + ga[o1 + 1] - 1] += b + # gn[j+ga[o1]][k+ga[o1+1]-1] = gn[j+ga[o1]][k+ga[o1+1]]+b + def display_board() -> None: # Draws the board with all symbols @@ -95,6 +103,7 @@ def display_board() -> None: gn[j][k] = 0 print(" ", end="") + # Player Functions def query_player(b) -> None: # Query player for symbol placement coordinates @@ -108,7 +117,9 @@ def query_player(b) -> None: y_ = [0] if len(y_) == 0 else y_ gx[b] = y_[0] gy[b] = x_[0] - if gx[b] in range(1, 6) and gy[b] in range(1, 6) and gn[gx[b]][gy[b]] == 0: + if gx[b] in range(1, 6)\ + and gy[b] in range(1, 6)\ + and gn[gx[b]][gy[b]] == 0: break print("ILLEGAL COORDS. RETYPE") if b != 1: @@ -117,6 +128,7 @@ def query_player(b) -> None: gn[gx[b] + 1][gy[b] + 1] = 0 b = 99 + # Game Functions def check_winner(m2, m3) -> None: # Check if the game has been won @@ -130,6 +142,7 @@ def check_winner(m2, m3) -> None: print("\nPLAYER 2 IS THE WINNER\n") return + # Program Flow def main() -> None: display_header() @@ -142,5 +155,6 @@ def main() -> None: check_winner(m2, m3) modify_board() + if __name__ == "__main__": main() diff --git a/63_Name/lua/name.lua b/63_Name/lua/name.lua new file mode 100644 index 00000000..32d51c93 --- /dev/null +++ b/63_Name/lua/name.lua @@ -0,0 +1,85 @@ +-- HELLO +-- +-- Converted from BASIC to Lua by Recanman + +local function tab(space) + local str = "" + + for _ = space, 1, -1 do + str = str .. " " + end + + return str +end + +-- reused from Bagels.lua +function getInput(prompt) + io.write(prompt) + io.flush() + local input = io.read("l") + if not input then --- test for EOF + print("GOODBYE") + os.exit(0) + end + return input +end + +print(tab(33) .. "HELLO\n") +print(tab(15) .. "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n") +print("\n") +print("\n") +print("\n") + +print("HELLO. MY NAME IS CREATIVE COMPUTER.\n") +print("\n") +print("\n") + +print("WHAT'S YOUR NAME (FIRST AND LAST)") + +local ns = getInput("? ") +local l = string.len(ns) +print("\n") + +local function main() + print("THANK YOU, " .. string.reverse(ns) .. ".\n") + + print("OOPS! I GUESS I GOT IT BACKWARDS. A SMART") + print("COMPUTER LIKE ME SHOULDN'T MAKE A MISTAKE LIKE THAT!\n") + print("BUT I JUST NOTICED YOUR LETTERS ARE OUT OF ORDER.\n") + print("LET'S PUT THEM IN ORDER LIKE THIS: ") + + local b = {} + + for i = 1, l, 1 do + local letter = string.sub(ns, i, i) + b[i] = string.byte(letter) + end + + table.sort(b, function(v1, v2) + return v1 < v2 + end) + + local str = "" + for _, letter in ipairs(b) do + str = str .. string.char(letter) + end + + str = string.reverse(str) + print(str) + + print("\n\n") + print("DON'T YOU LIKE THAT BETTER") + + local ds = getInput("? ") + + if ds == "YES" then + print("I KNEW YOU'D AGREE!!\n") + else + print("I'M SORRY YOU DON'T LIKE IT THAT WAY.\n") + end + + print("I REALLY ENJOYED MEETING YOU " .. ns .. ".\n") + print("HAVE A NICE DAY!\n") +end + +main() diff --git a/70_Poetry/csharp/Context.cs b/70_Poetry/csharp/Context.cs new file mode 100644 index 00000000..a9297915 --- /dev/null +++ b/70_Poetry/csharp/Context.cs @@ -0,0 +1,109 @@ +namespace Poetry; + +internal class Context +{ + private readonly IReadWrite _io; + private readonly IRandom _random; + private int _phraseNumber; + private int _groupNumber; + private bool _skipComma; + private int _lineCount; + private bool _useGroup2; + private bool _atStartOfLine = true; + + public Context(IReadWrite io, IRandom random) + { + _io = io; + _random = random; + } + + public int PhraseNumber => Math.Max(_phraseNumber - 1, 0); + + public int GroupNumber + { + get + { + var value = _useGroup2 ? 2 : _groupNumber; + _useGroup2 = false; + return Math.Max(value - 1, 0); + } + } + + public int PhraseCount { get; set; } + public bool GroupNumberIsValid => _groupNumber < 5; + + public void WritePhrase() + { + Phrase.GetPhrase(this).Write(_io, this); + _atStartOfLine = false; + } + + public void MaybeWriteComma() + { + if (!_skipComma && _random.NextFloat() <= 0.19F && PhraseCount != 0) + { + _io.Write(","); + PhraseCount = 2; + } + _skipComma = false; + } + + public void WriteSpaceOrNewLine() + { + if (_random.NextFloat() <= 0.65F) + { + _io.Write(" "); + PhraseCount += 1; + } + else + { + EndLine(); + PhraseCount = 0; + } + } + + public void Update(IRandom random) + { + _phraseNumber = random.Next(1, 6); + _groupNumber += 1; + _lineCount += 1; + } + + public void MaybeIndent() + { + if (PhraseCount == 0 && _groupNumber % 2 == 0) + { + _io.Write(" "); + } + } + + public void ResetGroup() + { + _groupNumber = 0; + EndLine(); + } + + public bool MaybeCompleteStanza() + { + if (_lineCount > 20) + { + _io.WriteLine(); + PhraseCount = _lineCount = 0; + _useGroup2 = true; + return true; + } + + return false; + } + + internal string MaybeCapitalise(string text) => + _atStartOfLine ? (char.ToUpper(text[0]) + text[1..]) : text; + + public void SkipNextComma() => _skipComma = true; + + public void EndLine() + { + _io.WriteLine(); + _atStartOfLine = true; + } +} diff --git a/70_Poetry/csharp/Phrase.cs b/70_Poetry/csharp/Phrase.cs new file mode 100644 index 00000000..f70de30b --- /dev/null +++ b/70_Poetry/csharp/Phrase.cs @@ -0,0 +1,78 @@ +namespace Poetry; + +internal class Phrase +{ + private readonly static Phrase[][] _phrases = new Phrase[][] + { + new Phrase[] + { + new("midnight dreary"), + new("fiery eyes"), + new("bird or fiend"), + new("thing of evil"), + new("prophet") + }, + new Phrase[] + { + new("beguiling me", ctx => ctx.PhraseCount = 2), + new("thrilled me"), + new("still sitting....", ctx => ctx.SkipNextComma()), + new("never flitting", ctx => ctx.PhraseCount = 2), + new("burned") + }, + new Phrase[] + { + new("and my soul"), + new("darkness there"), + new("shall be lifted"), + new("quoth the raven"), + new(ctx => ctx.PhraseCount != 0, "sign of parting") + }, + new Phrase[] + { + new("nothing more"), + new("yet again"), + new("slowly creeping"), + new("...evermore"), + new("nevermore") + } + }; + + private readonly Predicate _condition; + private readonly string _text; + private readonly Action _update; + + private Phrase(Predicate condition, string text) + : this(condition, text, _ => { }) + { + } + + private Phrase(string text, Action update) + : this(_ => true, text, update) + { + } + + private Phrase(string text) + : this(_ => true, text, _ => { }) + { + } + + private Phrase(Predicate condition, string text, Action update) + { + _condition = condition; + _text = text; + _update = update; + } + + public static Phrase GetPhrase(Context context) => _phrases[context.GroupNumber][context.PhraseNumber]; + + public void Write(IReadWrite io, Context context) + { + if (_condition.Invoke(context)) + { + io.Write(context.MaybeCapitalise(_text)); + } + + _update.Invoke(context); + } +} \ No newline at end of file diff --git a/70_Poetry/csharp/Poem.cs b/70_Poetry/csharp/Poem.cs new file mode 100644 index 00000000..fa3d5045 --- /dev/null +++ b/70_Poetry/csharp/Poem.cs @@ -0,0 +1,32 @@ +using static Poetry.Resources.Resource; + +namespace Poetry; + +internal class Poem +{ + internal static void Compose(IReadWrite io, IRandom random) + { + io.Write(Streams.Title); + + var context = new Context(io, random); + + while (true) + { + context.WritePhrase(); + context.MaybeWriteComma(); + context.WriteSpaceOrNewLine(); + + while (true) + { + context.Update(random); + context.MaybeIndent(); + + if (context.GroupNumberIsValid) { break; } + + context.ResetGroup(); + + if (context.MaybeCompleteStanza()) { break; } + } + } + } +} \ No newline at end of file diff --git a/70_Poetry/csharp/Poetry.csproj b/70_Poetry/csharp/Poetry.csproj index d3fe4757..3870320c 100644 --- a/70_Poetry/csharp/Poetry.csproj +++ b/70_Poetry/csharp/Poetry.csproj @@ -6,4 +6,12 @@ enable enable + + + + + + + + diff --git a/70_Poetry/csharp/Program.cs b/70_Poetry/csharp/Program.cs new file mode 100644 index 00000000..3a17d72c --- /dev/null +++ b/70_Poetry/csharp/Program.cs @@ -0,0 +1,5 @@ +global using Games.Common.IO; +global using Games.Common.Randomness; +global using Poetry; + +Poem.Compose(new ConsoleIO(), new RandomNumberGenerator()); diff --git a/70_Poetry/csharp/Resources/Resource.cs b/70_Poetry/csharp/Resources/Resource.cs new file mode 100644 index 00000000..b789f035 --- /dev/null +++ b/70_Poetry/csharp/Resources/Resource.cs @@ -0,0 +1,16 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace Poetry.Resources; + +internal static class Resource +{ + internal static class Streams + { + public static Stream Title => GetStream(); + } + + private static Stream GetStream([CallerMemberName] string? name = null) => + Assembly.GetExecutingAssembly().GetManifestResourceStream($"{typeof(Resource).Namespace}.{name}.txt") + ?? throw new Exception($"Could not find embedded resource stream '{name}'."); +} \ No newline at end of file diff --git a/70_Poetry/csharp/Resources/Title.txt b/70_Poetry/csharp/Resources/Title.txt new file mode 100644 index 00000000..86161340 --- /dev/null +++ b/70_Poetry/csharp/Resources/Title.txt @@ -0,0 +1,5 @@ + Poetry + Creative Computing Morristown, New Jersey + + + diff --git a/70_Poetry/poetry.bas b/70_Poetry/poetry.bas index 3661287d..7fa3a1c4 100644 --- a/70_Poetry/poetry.bas +++ b/70_Poetry/poetry.bas @@ -1,3 +1,8 @@ +5 Y=RND(-1) +6 REM FOR X = 1 TO 100 +7 REM PRINT RND(1);"," +8 REM NEXT X +9 REM GOTO 999 10 PRINT TAB(30);"POETRY" 20 PRINT TAB(15);"CREATIVE COMPUTING MORRISTOWN, NEW JERSEY" 30 PRINT:PRINT:PRINT @@ -26,17 +31,21 @@ 133 PRINT "SLOWLY CREEPING";:GOTO 210 134 PRINT "...EVERMORE";:GOTO 210 135 PRINT "NEVERMORE"; -210 IF U=0 OR RND(1)>.19 THEN 212 +210 GOSUB 500 : IF U=0 OR X>.19 THEN 212 211 PRINT ",";:U=2 -212 IF RND(1)>.65 THEN 214 +212 GOSUB 500 : IF X>.65 THEN 214 213 PRINT " ";:U=U+1:GOTO 215 214 PRINT : U=0 -215 I=INT(INT(10*RND(1))/2)+1 +215 GOSUB 500 : I=INT(INT(10*X)/2)+1 220 J=J+1 : K=K+1 +225 REM PRINT "I=";I;"; J=";J;"; K=";K;"; U=";U 230 IF U>0 OR INT(J/2)<>J/2 THEN 240 235 PRINT " "; 240 ON J GOTO 90,110,120,130,250 250 J=0 : PRINT : IF K>20 THEN 270 260 GOTO 215 270 PRINT : U=0 : K=0 : GOTO 110 +500 X = RND(1) +505 REM PRINT "#";X;"#" +510 RETURN 999 END diff --git a/72_Queen/csharp/Computer.cs b/72_Queen/csharp/Computer.cs new file mode 100644 index 00000000..459c7337 --- /dev/null +++ b/72_Queen/csharp/Computer.cs @@ -0,0 +1,34 @@ +namespace Queen; + +internal class Computer +{ + private static readonly HashSet _randomiseFrom = new() { 41, 44, 73, 75, 126, 127 }; + private static readonly HashSet _desirable = new() { 73, 75, 126, 127, 158 }; + private readonly IRandom _random; + + public Computer(IRandom random) + { + _random = random; + } + + public Position GetMove(Position from) + => from + (_randomiseFrom.Contains(from) ? _random.NextMove() : FindMove(from)); + + private Move FindMove(Position from) + { + for (int i = 7; i > 0; i--) + { + if (IsOptimal(Move.Left, out var move)) { return move; } + if (IsOptimal(Move.Down, out move)) { return move; } + if (IsOptimal(Move.DownLeft, out move)) { return move; } + + bool IsOptimal(Move direction, out Move move) + { + move = direction * i; + return _desirable.Contains(from + move); + } + } + + return _random.NextMove(); + } +} diff --git a/72_Queen/csharp/Game.cs b/72_Queen/csharp/Game.cs new file mode 100644 index 00000000..35b1dc9a --- /dev/null +++ b/72_Queen/csharp/Game.cs @@ -0,0 +1,57 @@ +namespace Queen; + +internal class Game +{ + private readonly IReadWrite _io; + private readonly IRandom _random; + private readonly Computer _computer; + + public Game(IReadWrite io, IRandom random) + { + _io = io; + _random = random; + _computer = new Computer(random); + } + + internal void PlaySeries() + { + _io.Write(Streams.Title); + if (_io.ReadYesNo(Prompts.Instructions)) { _io.Write(Streams.Instructions); } + + while (true) + { + var result = PlayGame(); + _io.Write(result switch + { + Result.HumanForfeits => Streams.Forfeit, + Result.HumanWins => Streams.Congratulations, + Result.ComputerWins => Streams.IWin, + _ => throw new InvalidOperationException($"Unexpected result {result}") + }); + + if (!_io.ReadYesNo(Prompts.Anyone)) { break; } + } + + _io.Write(Streams.Thanks); + } + + private Result PlayGame() + { + _io.Write(Streams.Board); + var humanPosition = _io.ReadPosition(Prompts.Start, p => p.IsStart, Streams.IllegalStart, repeatPrompt: true); + if (humanPosition.IsZero) { return Result.HumanForfeits; } + + while (true) + { + var computerPosition = _computer.GetMove(humanPosition); + _io.Write(Strings.ComputerMove(computerPosition)); + if (computerPosition.IsEnd) { return Result.ComputerWins; } + + humanPosition = _io.ReadPosition(Prompts.Move, p => (p - computerPosition).IsValid, Streams.IllegalMove); + if (humanPosition.IsZero) { return Result.HumanForfeits; } + if (humanPosition.IsEnd) { return Result.HumanWins; } + } + } + + private enum Result { ComputerWins, HumanWins, HumanForfeits }; +} diff --git a/72_Queen/csharp/IOExtensions.cs b/72_Queen/csharp/IOExtensions.cs new file mode 100644 index 00000000..51959967 --- /dev/null +++ b/72_Queen/csharp/IOExtensions.cs @@ -0,0 +1,38 @@ +namespace Queen; + +internal static class IOExtensions +{ + internal static bool ReadYesNo(this IReadWrite io, string prompt) + { + while (true) + { + var answer = io.ReadString(prompt).ToLower(); + if (answer == "yes") { return true; } + if (answer == "no") { return false; } + + io.Write(Streams.YesOrNo); + } + } + + internal static Position ReadPosition( + this IReadWrite io, + string prompt, + Predicate isValid, + Stream error, + bool repeatPrompt = false) + { + while (true) + { + var response = io.ReadNumber(prompt); + var number = (int)response; + var position = new Position(number); + if (number == response && (position.IsZero || isValid(position))) + { + return position; + } + + io.Write(error); + if (!repeatPrompt) { prompt = ""; } + } + } +} diff --git a/72_Queen/csharp/Move.cs b/72_Queen/csharp/Move.cs new file mode 100644 index 00000000..4e18647b --- /dev/null +++ b/72_Queen/csharp/Move.cs @@ -0,0 +1,15 @@ +namespace Queen; + +internal record struct Move(int Diagonal, int Row) +{ + public static readonly Move Left = new(1, 0); + public static readonly Move DownLeft = new(2, 1); + public static readonly Move Down = new(1, 1); + + public bool IsValid => Diagonal > 0 && (IsLeft || IsDown || IsDownLeft); + private bool IsLeft => Row == 0; + private bool IsDown => Row == Diagonal; + private bool IsDownLeft => Row * 2 == Diagonal; + + public static Move operator *(Move move, int scale) => new(move.Diagonal * scale, move.Row * scale); +} \ No newline at end of file diff --git a/72_Queen/csharp/Position.cs b/72_Queen/csharp/Position.cs new file mode 100644 index 00000000..69971163 --- /dev/null +++ b/72_Queen/csharp/Position.cs @@ -0,0 +1,24 @@ +namespace Queen; + +internal record struct Position(int Diagonal, int Row) +{ + public static readonly Position Zero = new(0); + + public Position(int number) + : this(Diagonal: number / 10, Row: number % 10) + { + } + + public bool IsZero => Row == 0 && Diagonal == 0; + public bool IsStart => Row == 1 || Row == Diagonal; + public bool IsEnd => Row == 8 && Diagonal == 15; + + public override string ToString() => $"{Diagonal}{Row}"; + + public static implicit operator Position(int value) => new(value); + + public static Position operator +(Position position, Move move) + => new(Diagonal: position.Diagonal + move.Diagonal, Row: position.Row + move.Row); + public static Move operator -(Position to, Position from) + => new(Diagonal: to.Diagonal - from.Diagonal, Row: to.Row - from.Row); +} diff --git a/72_Queen/csharp/Program.cs b/72_Queen/csharp/Program.cs new file mode 100644 index 00000000..5e017df6 --- /dev/null +++ b/72_Queen/csharp/Program.cs @@ -0,0 +1,7 @@ +global using Games.Common.IO; +global using Games.Common.Randomness; +global using static Queen.Resources.Resource; + +using Queen; + +new Game(new ConsoleIO(), new RandomNumberGenerator()).PlaySeries(); \ No newline at end of file diff --git a/72_Queen/csharp/Queen.csproj b/72_Queen/csharp/Queen.csproj index d3fe4757..3870320c 100644 --- a/72_Queen/csharp/Queen.csproj +++ b/72_Queen/csharp/Queen.csproj @@ -6,4 +6,12 @@ enable enable + + + + + + + + diff --git a/72_Queen/csharp/RandomExtensions.cs b/72_Queen/csharp/RandomExtensions.cs new file mode 100644 index 00000000..b4e375fd --- /dev/null +++ b/72_Queen/csharp/RandomExtensions.cs @@ -0,0 +1,12 @@ +namespace Queen; + +internal static class RandomExtensions +{ + internal static Move NextMove(this IRandom random) + => random.NextFloat() switch + { + > 0.6F => Move.Down, + > 0.3F => Move.DownLeft, + _ => Move.Left + }; +} diff --git a/72_Queen/csharp/Resources/AnyonePrompt.txt b/72_Queen/csharp/Resources/AnyonePrompt.txt new file mode 100644 index 00000000..a0289fd6 --- /dev/null +++ b/72_Queen/csharp/Resources/AnyonePrompt.txt @@ -0,0 +1 @@ +Anyone else care to try \ No newline at end of file diff --git a/72_Queen/csharp/Resources/Board.txt b/72_Queen/csharp/Resources/Board.txt new file mode 100644 index 00000000..854be1bb --- /dev/null +++ b/72_Queen/csharp/Resources/Board.txt @@ -0,0 +1,27 @@ + + + 81 71 61 51 41 31 21 11 + + + 92 82 72 62 52 42 32 22 + + + 103 93 83 73 63 53 43 33 + + + 114 104 94 84 74 64 54 44 + + + 125 115 105 95 85 75 65 55 + + + 136 126 116 106 96 86 76 66 + + + 147 137 127 117 107 97 87 77 + + + 158 148 138 128 118 108 98 88 + + + diff --git a/72_Queen/csharp/Resources/ComputerMove.txt b/72_Queen/csharp/Resources/ComputerMove.txt new file mode 100644 index 00000000..f31b8e32 --- /dev/null +++ b/72_Queen/csharp/Resources/ComputerMove.txt @@ -0,0 +1 @@ +Computer moves to square {0} diff --git a/72_Queen/csharp/Resources/Congratulations.txt b/72_Queen/csharp/Resources/Congratulations.txt new file mode 100644 index 00000000..9f1db232 --- /dev/null +++ b/72_Queen/csharp/Resources/Congratulations.txt @@ -0,0 +1,7 @@ + +C O N G R A T U L A T I O N S . . . + +You have won--very well played. +It looks like I have met my match. +Thanks for playing--I can't win all the time. + diff --git a/72_Queen/csharp/Resources/Forfeit.txt b/72_Queen/csharp/Resources/Forfeit.txt new file mode 100644 index 00000000..09858bc1 --- /dev/null +++ b/72_Queen/csharp/Resources/Forfeit.txt @@ -0,0 +1,3 @@ + +It looks like I have won by forfeit. + diff --git a/72_Queen/csharp/Resources/IWin.txt b/72_Queen/csharp/Resources/IWin.txt new file mode 100644 index 00000000..ced5d716 --- /dev/null +++ b/72_Queen/csharp/Resources/IWin.txt @@ -0,0 +1,4 @@ + +Nice try, but it looks like I have won. +Thanks for playing. + diff --git a/72_Queen/csharp/Resources/IllegalMove.txt b/72_Queen/csharp/Resources/IllegalMove.txt new file mode 100644 index 00000000..ae4a3bbc --- /dev/null +++ b/72_Queen/csharp/Resources/IllegalMove.txt @@ -0,0 +1,2 @@ + +Y O U C H E A T . . . Try again \ No newline at end of file diff --git a/72_Queen/csharp/Resources/IllegalStart.txt b/72_Queen/csharp/Resources/IllegalStart.txt new file mode 100644 index 00000000..25402bb6 --- /dev/null +++ b/72_Queen/csharp/Resources/IllegalStart.txt @@ -0,0 +1,3 @@ +Please read the instructions again. +You have begun illegally. + diff --git a/72_Queen/csharp/Resources/Instructions.txt b/72_Queen/csharp/Resources/Instructions.txt new file mode 100644 index 00000000..fc2e85b0 --- /dev/null +++ b/72_Queen/csharp/Resources/Instructions.txt @@ -0,0 +1,15 @@ +We are going to play a game based on one of the chess +moves. Our queen will be able to move only to the left, +down, or diagonally down and to the left. + +The object of the game is to place the queen in the lower +left hand square by alternating moves between you and the +computer. The first one to place the queen there wins. + +You go first and place the queen in any one of the squares +on the top row or right hand column. +That will be your first move. +We alternate moves. +You may forfeit by typing '0' as your move. +Be sure to press the return key after each response. + diff --git a/72_Queen/csharp/Resources/InstructionsPrompt.txt b/72_Queen/csharp/Resources/InstructionsPrompt.txt new file mode 100644 index 00000000..0d311b60 --- /dev/null +++ b/72_Queen/csharp/Resources/InstructionsPrompt.txt @@ -0,0 +1 @@ +Do you want instructions \ No newline at end of file diff --git a/72_Queen/csharp/Resources/MovePrompt.txt b/72_Queen/csharp/Resources/MovePrompt.txt new file mode 100644 index 00000000..8cb18999 --- /dev/null +++ b/72_Queen/csharp/Resources/MovePrompt.txt @@ -0,0 +1 @@ +What is your move \ No newline at end of file diff --git a/72_Queen/csharp/Resources/Resource.cs b/72_Queen/csharp/Resources/Resource.cs new file mode 100644 index 00000000..e8297eca --- /dev/null +++ b/72_Queen/csharp/Resources/Resource.cs @@ -0,0 +1,47 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace Queen.Resources; + +internal static class Resource +{ + internal static class Streams + { + public static Stream Title => GetStream(); + public static Stream Instructions => GetStream(); + public static Stream YesOrNo => GetStream(); + public static Stream Board => GetStream(); + public static Stream IllegalStart => GetStream(); + public static Stream IllegalMove => GetStream(); + public static Stream Forfeit => GetStream(); + public static Stream IWin => GetStream(); + public static Stream Congratulations => GetStream(); + public static Stream Thanks => GetStream(); + } + + internal static class Prompts + { + public static string Instructions => GetPrompt(); + public static string Start => GetPrompt(); + public static string Move => GetPrompt(); + public static string Anyone => GetPrompt(); + } + + internal static class Strings + { + public static string ComputerMove(Position position) => string.Format(GetString(), position); + } + + private static string GetPrompt([CallerMemberName] string? name = null) => GetString($"{name}Prompt"); + + private static string GetString([CallerMemberName] string? name = null) + { + using var stream = GetStream(name); + using var reader = new StreamReader(stream); + return reader.ReadToEnd(); + } + + private static Stream GetStream([CallerMemberName] string? name = null) => + Assembly.GetExecutingAssembly().GetManifestResourceStream($"{typeof(Resource).Namespace}.{name}.txt") + ?? throw new Exception($"Could not find embedded resource stream '{name}'."); +} \ No newline at end of file diff --git a/72_Queen/csharp/Resources/StartPrompt.txt b/72_Queen/csharp/Resources/StartPrompt.txt new file mode 100644 index 00000000..0b6f395b --- /dev/null +++ b/72_Queen/csharp/Resources/StartPrompt.txt @@ -0,0 +1 @@ +Where would you like to start \ No newline at end of file diff --git a/72_Queen/csharp/Resources/Thanks.txt b/72_Queen/csharp/Resources/Thanks.txt new file mode 100644 index 00000000..2e2e7b63 --- /dev/null +++ b/72_Queen/csharp/Resources/Thanks.txt @@ -0,0 +1,3 @@ + + +Ok --- thanks again. \ No newline at end of file diff --git a/72_Queen/csharp/Resources/Title.txt b/72_Queen/csharp/Resources/Title.txt new file mode 100644 index 00000000..63549d09 --- /dev/null +++ b/72_Queen/csharp/Resources/Title.txt @@ -0,0 +1,5 @@ + Queen + Creative Computing Morristown, New Jersey + + + diff --git a/72_Queen/csharp/Resources/YesOrNo.txt b/72_Queen/csharp/Resources/YesOrNo.txt new file mode 100644 index 00000000..657bf8aa --- /dev/null +++ b/72_Queen/csharp/Resources/YesOrNo.txt @@ -0,0 +1 @@ +Please answer 'Yes' or 'No'. diff --git a/75_Roulette/csharp/Bet.cs b/75_Roulette/csharp/Bet.cs new file mode 100644 index 00000000..da0e31b7 --- /dev/null +++ b/75_Roulette/csharp/Bet.cs @@ -0,0 +1,6 @@ +namespace Roulette; + +internal record struct Bet(BetType Type, int Number, int Wager) +{ + public int Payout => Wager * Type.Payout; +} diff --git a/75_Roulette/csharp/BetType.cs b/75_Roulette/csharp/BetType.cs new file mode 100644 index 00000000..79b3b67c --- /dev/null +++ b/75_Roulette/csharp/BetType.cs @@ -0,0 +1,13 @@ +namespace Roulette; + +internal record struct BetType(int Value) +{ + public static implicit operator BetType(int value) => new(value); + + public int Payout => Value switch + { + <= 36 or >= 49 => 35, + <= 42 => 2, + <= 48 => 1 + }; +} diff --git a/75_Roulette/csharp/Croupier.cs b/75_Roulette/csharp/Croupier.cs new file mode 100644 index 00000000..32f76c33 --- /dev/null +++ b/75_Roulette/csharp/Croupier.cs @@ -0,0 +1,41 @@ +namespace Roulette; + +internal class Croupier +{ + private const int _initialHouse = 100_000; + private const int _initialPlayer = 1_000; + + private int _house = _initialHouse; + private int _player = _initialPlayer; + + public string Totals => Strings.Totals(_house, _player); + public bool PlayerIsBroke => _player <= 0; + public bool HouseIsBroke => _house <= 0; + + internal string Pay(Bet bet) + { + _house -= bet.Payout; + _player += bet.Payout; + + if (_house <= 0) + { + _player = _initialHouse + _initialPlayer; + } + + return Strings.Win(bet); + } + + internal string Take(Bet bet) + { + _house += bet.Wager; + _player -= bet.Wager; + + return Strings.Lose(bet); + } + + public void CutCheck(IReadWrite io, IRandom random) + { + var name = io.ReadString(Prompts.Check); + io.Write(Strings.Check(random, name, _player)); + } +} diff --git a/75_Roulette/csharp/Game.cs b/75_Roulette/csharp/Game.cs new file mode 100644 index 00000000..75a6d7e5 --- /dev/null +++ b/75_Roulette/csharp/Game.cs @@ -0,0 +1,42 @@ +namespace Roulette; + +internal class Game +{ + private readonly IReadWrite _io; + private readonly IRandom _random; + private readonly Table _table; + private readonly Croupier _croupier; + + public Game(IReadWrite io, IRandom random) + { + _io = io; + _random = random; + _croupier = new(); + _table = new(_croupier, io, random); + } + + public void Play() + { + _io.Write(Streams.Title); + if (!_io.ReadString(Prompts.Instructions).ToLowerInvariant().StartsWith('n')) + { + _io.Write(Streams.Instructions); + } + + while (_table.Play()); + + if (_croupier.PlayerIsBroke) + { + _io.Write(Streams.LastDollar); + _io.Write(Streams.Thanks); + return; + } + + if (_croupier.HouseIsBroke) + { + _io.Write(Streams.BrokeHouse); + } + + _croupier.CutCheck(_io, _random); + } +} diff --git a/75_Roulette/csharp/IOExtensions.cs b/75_Roulette/csharp/IOExtensions.cs new file mode 100644 index 00000000..49326bef --- /dev/null +++ b/75_Roulette/csharp/IOExtensions.cs @@ -0,0 +1,34 @@ +namespace Roulette; + +internal static class IOExtensions +{ + internal static int ReadBetCount(this IReadWrite io) + { + while (true) + { + var betCount = io.ReadNumber(Prompts.HowManyBets); + if (betCount.IsValidInt(1)) { return (int)betCount; } + } + } + + internal static Bet ReadBet(this IReadWrite io, int number) + { + while (true) + { + var (type, amount) = io.Read2Numbers(Prompts.Bet(number)); + + if (type.IsValidInt(1, 50) && amount.IsValidInt(5, 500)) + { + return new() + { + Type = (int)type, + Number = number, + Wager = (int)amount + }; + } + } + } + + internal static bool IsValidInt(this float value, int minValue, int maxValue = int.MaxValue) + => value == (int)value && value >= minValue && value <= maxValue; +} \ No newline at end of file diff --git a/75_Roulette/csharp/Program.cs b/75_Roulette/csharp/Program.cs new file mode 100644 index 00000000..4be74962 --- /dev/null +++ b/75_Roulette/csharp/Program.cs @@ -0,0 +1,6 @@ +global using Games.Common.IO; +global using Games.Common.Randomness; +global using static Roulette.Resources.Resource; +using Roulette; + +new Game(new ConsoleIO(), new RandomNumberGenerator()).Play(); diff --git a/75_Roulette/csharp/Resources/AgainPrompt.txt b/75_Roulette/csharp/Resources/AgainPrompt.txt new file mode 100644 index 00000000..bd1d18b6 --- /dev/null +++ b/75_Roulette/csharp/Resources/AgainPrompt.txt @@ -0,0 +1 @@ +Again \ No newline at end of file diff --git a/75_Roulette/csharp/Resources/BetAlready.txt b/75_Roulette/csharp/Resources/BetAlready.txt new file mode 100644 index 00000000..be86cbea --- /dev/null +++ b/75_Roulette/csharp/Resources/BetAlready.txt @@ -0,0 +1 @@ +You made that bet once already,dum-dum \ No newline at end of file diff --git a/75_Roulette/csharp/Resources/BetPrompt.txt b/75_Roulette/csharp/Resources/BetPrompt.txt new file mode 100644 index 00000000..50039840 --- /dev/null +++ b/75_Roulette/csharp/Resources/BetPrompt.txt @@ -0,0 +1 @@ +Number {0} \ No newline at end of file diff --git a/75_Roulette/csharp/Resources/BrokeHouse.txt b/75_Roulette/csharp/Resources/BrokeHouse.txt new file mode 100644 index 00000000..638dafb4 --- /dev/null +++ b/75_Roulette/csharp/Resources/BrokeHouse.txt @@ -0,0 +1 @@ +You broke the house! diff --git a/75_Roulette/csharp/Resources/Check.txt b/75_Roulette/csharp/Resources/Check.txt new file mode 100644 index 00000000..ffdd640a --- /dev/null +++ b/75_Roulette/csharp/Resources/Check.txt @@ -0,0 +1,16 @@ + +------------------------------------------------------------------------Check No. {0} + + {1:MMMM d',' yyyy} + + +Pay to the order of-----{2}-----$ {3} + + + The Memory Bank of New YORK + + The Computer + ----------X----- + +--------------------------------------------------------------Come back soon! + diff --git a/75_Roulette/csharp/Resources/CheckPrompt.txt b/75_Roulette/csharp/Resources/CheckPrompt.txt new file mode 100644 index 00000000..16321fe9 --- /dev/null +++ b/75_Roulette/csharp/Resources/CheckPrompt.txt @@ -0,0 +1 @@ +To whom shall I make the check \ No newline at end of file diff --git a/75_Roulette/csharp/Resources/HowManyBetsPrompt.txt b/75_Roulette/csharp/Resources/HowManyBetsPrompt.txt new file mode 100644 index 00000000..0b6abdfa --- /dev/null +++ b/75_Roulette/csharp/Resources/HowManyBetsPrompt.txt @@ -0,0 +1 @@ +How many bets \ No newline at end of file diff --git a/75_Roulette/csharp/Resources/Instructions.txt b/75_Roulette/csharp/Resources/Instructions.txt new file mode 100644 index 00000000..8ced724b --- /dev/null +++ b/75_Roulette/csharp/Resources/Instructions.txt @@ -0,0 +1,48 @@ + +This is the betting layout + (*=RED) + + 1* 2 3* + 4 5* 6 + 7* 8 9* +10 11 12* +--------------- +13 14* 15 +16* 17 18* +19* 20 21* +22 23* 24 +--------------- +25* 26 27* +28 29 30* +31 32* 33 +34* 35 36* +--------------- + 00 0 + +Types of bets + +The numbers 1 to 36 signify a straight bet +on that number. +These pay off 35:1 + +The 2:1 bets are: + 37) 1-12 40) First column + 38) 13-24 41) Second column + 39) 25-36 42) Third column + +The even money bets are: + 43) 1-18 46) Odd + 44) 19-36 47) Red + 45) Even 48) Black + +49)0 and 50)00 pay off 35:1 +Note: 0 and 00 do not count under any + bets except their own. + +When I ask for each bet, type the number +and the amount, separated by a comma. +For example: to bet $500 on Black, type 48,500 +when I ask for a bet. + +The minimum bet is $5, the maximum is $500. + diff --git a/75_Roulette/csharp/Resources/InstructionsPrompt.txt b/75_Roulette/csharp/Resources/InstructionsPrompt.txt new file mode 100644 index 00000000..0d311b60 --- /dev/null +++ b/75_Roulette/csharp/Resources/InstructionsPrompt.txt @@ -0,0 +1 @@ +Do you want instructions \ No newline at end of file diff --git a/75_Roulette/csharp/Resources/LastDollar.txt b/75_Roulette/csharp/Resources/LastDollar.txt new file mode 100644 index 00000000..632516de --- /dev/null +++ b/75_Roulette/csharp/Resources/LastDollar.txt @@ -0,0 +1 @@ +Oops! you just spent your last dollar! diff --git a/75_Roulette/csharp/Resources/Outcome.txt b/75_Roulette/csharp/Resources/Outcome.txt new file mode 100644 index 00000000..30e227f7 --- /dev/null +++ b/75_Roulette/csharp/Resources/Outcome.txt @@ -0,0 +1 @@ +You {0} {1} dollars on bet {2} diff --git a/75_Roulette/csharp/Resources/Resource.cs b/75_Roulette/csharp/Resources/Resource.cs new file mode 100644 index 00000000..dd9c8672 --- /dev/null +++ b/75_Roulette/csharp/Resources/Resource.cs @@ -0,0 +1,59 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using Games.Common.Randomness; + +namespace Roulette.Resources; + +internal static class Resource +{ + internal static class Streams + { + public static Stream Title => GetStream(); + public static Stream Instructions => GetStream(); + public static Stream BetAlready => GetStream(); + public static Stream Spinning => GetStream(); + public static Stream LastDollar => GetStream(); + public static Stream BrokeHouse => GetStream(); + public static Stream Thanks => GetStream(); + } + + internal static class Strings + { + public static string Black(int number) => Slot(number); + public static string Red(int number) => Slot(number); + private static string Slot(int number, [CallerMemberName] string? colour = null) + => string.Format(GetString(), number, colour); + + public static string Lose(Bet bet) => Outcome(bet.Wager, bet.Number); + public static string Win(Bet bet) => Outcome(bet.Payout, bet.Number); + private static string Outcome(int amount, int number, [CallerMemberName] string? winlose = null) + => string.Format(GetString(), winlose, amount, number); + + public static string Totals(int me, int you) => string.Format(GetString(), me, you); + + public static string Check(IRandom random, string payee, int amount) + => string.Format(GetString(), random.Next(100), DateTime.Now, payee, amount); + } + + internal static class Prompts + { + public static string Instructions => GetPrompt(); + public static string HowManyBets => GetPrompt(); + public static string Bet(int number) => string.Format(GetPrompt(), number); + public static string Again => GetPrompt(); + public static string Check => GetPrompt(); + } + + private static string GetPrompt([CallerMemberName] string? name = null) => GetString($"{name}Prompt"); + + private static string GetString([CallerMemberName] string? name = null) + { + using var stream = GetStream(name); + using var reader = new StreamReader(stream); + return reader.ReadToEnd(); + } + + private static Stream GetStream([CallerMemberName] string? name = null) => + Assembly.GetExecutingAssembly().GetManifestResourceStream($"{typeof(Resource).Namespace}.{name}.txt") + ?? throw new Exception($"Could not find embedded resource stream '{name}'."); +} \ No newline at end of file diff --git a/75_Roulette/csharp/Resources/Slot.txt b/75_Roulette/csharp/Resources/Slot.txt new file mode 100644 index 00000000..de02695d --- /dev/null +++ b/75_Roulette/csharp/Resources/Slot.txt @@ -0,0 +1,2 @@ + {0} {1} + diff --git a/75_Roulette/csharp/Resources/Spinning.txt b/75_Roulette/csharp/Resources/Spinning.txt new file mode 100644 index 00000000..0d87fe39 --- /dev/null +++ b/75_Roulette/csharp/Resources/Spinning.txt @@ -0,0 +1,3 @@ +Spinning + + diff --git a/75_Roulette/csharp/Resources/Thanks.txt b/75_Roulette/csharp/Resources/Thanks.txt new file mode 100644 index 00000000..0b835237 --- /dev/null +++ b/75_Roulette/csharp/Resources/Thanks.txt @@ -0,0 +1,3 @@ +Thanks for you money. +I'll use it to buy a solid gold roulette WHEEL + diff --git a/75_Roulette/csharp/Resources/Title.txt b/75_Roulette/csharp/Resources/Title.txt new file mode 100644 index 00000000..0d53f1a8 --- /dev/null +++ b/75_Roulette/csharp/Resources/Title.txt @@ -0,0 +1,7 @@ + Roulette + Creative Computing Morristown, New Jersey + + + +Welcome to the roulette table + diff --git a/75_Roulette/csharp/Resources/Totals.txt b/75_Roulette/csharp/Resources/Totals.txt new file mode 100644 index 00000000..4cecae3f --- /dev/null +++ b/75_Roulette/csharp/Resources/Totals.txt @@ -0,0 +1,3 @@ + +Totals Me You + {0,-14}{1} diff --git a/75_Roulette/csharp/Roulette.csproj b/75_Roulette/csharp/Roulette.csproj index d3fe4757..23d27b76 100644 --- a/75_Roulette/csharp/Roulette.csproj +++ b/75_Roulette/csharp/Roulette.csproj @@ -1,9 +1,17 @@ Exe - net6.0 + net7.0 10 enable enable + + + + + + + + diff --git a/75_Roulette/csharp/Slot.cs b/75_Roulette/csharp/Slot.cs new file mode 100644 index 00000000..df30cea1 --- /dev/null +++ b/75_Roulette/csharp/Slot.cs @@ -0,0 +1,18 @@ +using System.Collections.Immutable; + +namespace Roulette; + +internal class Slot +{ + private readonly ImmutableHashSet _coveringBets; + + public Slot (string name, params BetType[] coveringBets) + { + Name = name; + _coveringBets = coveringBets.ToImmutableHashSet(); + } + + public string Name { get; } + + public bool IsCoveredBy(Bet bet) => _coveringBets.Contains(bet.Type); +} diff --git a/75_Roulette/csharp/Table.cs b/75_Roulette/csharp/Table.cs new file mode 100644 index 00000000..026152d1 --- /dev/null +++ b/75_Roulette/csharp/Table.cs @@ -0,0 +1,71 @@ +namespace Roulette; + +internal class Table +{ + private readonly IReadWrite _io; + private readonly Wheel _wheel; + private readonly Croupier _croupier; + + public Table(Croupier croupier, IReadWrite io, IRandom random) + { + _croupier = croupier; + _io = io; + _wheel = new(random); + } + + public bool Play() + { + var bets = AcceptBets(); + var slot = SpinWheel(); + SettleBets(bets, slot); + + _io.Write(_croupier.Totals); + + if (_croupier.PlayerIsBroke || _croupier.HouseIsBroke) { return false; } + + return _io.ReadString(Prompts.Again).ToLowerInvariant().StartsWith('y'); + } + + private Slot SpinWheel() + { + _io.Write(Streams.Spinning); + var slot = _wheel.Spin(); + _io.Write(slot.Name); + return slot; + } + + private IReadOnlyList AcceptBets() + { + var betCount = _io.ReadBetCount(); + var betTypes = new HashSet(); + var bets = new List(); + for (int i = 1; i <= betCount; i++) + { + while (!TryAdd(_io.ReadBet(i))) + { + _io.Write(Streams.BetAlready); + } + } + + return bets.AsReadOnly(); + + bool TryAdd(Bet bet) + { + if (betTypes.Add(bet.Type)) + { + bets.Add(bet); + return true; + } + + return false; + } + } + + private void SettleBets(IReadOnlyList bets, Slot slot) + { + foreach (var bet in bets) + { + _io.Write(slot.IsCoveredBy(bet) ? _croupier.Pay(bet) : _croupier.Take(bet)); + } + } +} diff --git a/75_Roulette/csharp/Wheel.cs b/75_Roulette/csharp/Wheel.cs new file mode 100644 index 00000000..dfcecd29 --- /dev/null +++ b/75_Roulette/csharp/Wheel.cs @@ -0,0 +1,52 @@ +using System.Collections.Immutable; + +namespace Roulette; + +internal class Wheel +{ + private static readonly ImmutableArray _slots = ImmutableArray.Create( + new Slot(Strings.Red(1), 1, 37, 40, 43, 46, 47), + new Slot(Strings.Black(2), 2, 37, 41, 43, 45, 48), + new Slot(Strings.Red(3), 3, 37, 42, 43, 46, 47), + new Slot(Strings.Black(4), 4, 37, 40, 43, 45, 48), + new Slot(Strings.Red(5), 5, 37, 41, 43, 46, 47), + new Slot(Strings.Black(6), 6, 37, 42, 43, 45, 48), + new Slot(Strings.Red(7), 7, 37, 40, 43, 46, 47), + new Slot(Strings.Black(8), 8, 37, 41, 43, 45, 48), + new Slot(Strings.Red(9), 9, 37, 42, 43, 46, 47), + new Slot(Strings.Black(10), 10, 37, 40, 43, 45, 48), + new Slot(Strings.Black(11), 11, 37, 41, 43, 46, 48), + new Slot(Strings.Red(12), 12, 37, 42, 43, 45, 47), + new Slot(Strings.Black(13), 13, 38, 40, 43, 46, 48), + new Slot(Strings.Red(14), 14, 38, 41, 43, 45, 47), + new Slot(Strings.Black(15), 15, 38, 42, 43, 46, 48), + new Slot(Strings.Red(16), 16, 38, 40, 43, 45, 47), + new Slot(Strings.Black(17), 17, 38, 41, 43, 46, 48), + new Slot(Strings.Red(18), 18, 38, 42, 43, 45, 47), + new Slot(Strings.Red(19), 19, 38, 40, 44, 46, 47), + new Slot(Strings.Black(20), 20, 38, 41, 44, 45, 48), + new Slot(Strings.Red(21), 21, 38, 42, 44, 46, 47), + new Slot(Strings.Black(22), 22, 38, 40, 44, 45, 48), + new Slot(Strings.Red(23), 23, 38, 41, 44, 46, 47), + new Slot(Strings.Black(24), 24, 38, 42, 44, 45, 48), + new Slot(Strings.Red(25), 25, 39, 40, 44, 46, 47), + new Slot(Strings.Black(26), 26, 39, 41, 44, 45, 48), + new Slot(Strings.Red(27), 27, 39, 42, 44, 46, 47), + new Slot(Strings.Black(28), 28, 39, 40, 44, 45, 48), + new Slot(Strings.Black(29), 29, 39, 41, 44, 46, 48), + new Slot(Strings.Red(30), 30, 39, 42, 44, 45, 47), + new Slot(Strings.Black(31), 31, 39, 40, 44, 46, 48), + new Slot(Strings.Red(32), 32, 39, 41, 44, 45, 47), + new Slot(Strings.Black(33), 33, 39, 42, 44, 46, 48), + new Slot(Strings.Red(34), 34, 39, 40, 44, 45, 47), + new Slot(Strings.Black(35), 35, 39, 41, 44, 46, 48), + new Slot(Strings.Red(36), 36, 39, 42, 44, 45, 47), + new Slot("0", 49), + new Slot("00", 50)); + + private readonly IRandom _random; + + public Wheel(IRandom random) => _random = random; + + public Slot Spin() => _slots[_random.Next(_slots.Length)]; +} diff --git a/84_Super_Star_Trek/python/superstartrek.py b/84_Super_Star_Trek/python/superstartrek.py index 21e03e45..2a86efba 100644 --- a/84_Super_Star_Trek/python/superstartrek.py +++ b/84_Super_Star_Trek/python/superstartrek.py @@ -8,7 +8,6 @@ **** LEEDOM - APRIL & DECEMBER 1974, **** WITH A LITTLE HELP FROM HIS FRIENDS . . . -Python translation by Jack Boyce - February 2021 Output is identical to BASIC version except for a few fixes (as noted, search `bug`) and minor cleanup. """ diff --git a/84_Super_Star_Trek/rust/Cargo.toml b/84_Super_Star_Trek/rust/Cargo.toml new file mode 100644 index 00000000..01457249 --- /dev/null +++ b/84_Super_Star_Trek/rust/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "rust" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +ctrlc = "3.2.5" +rand = "0.8.5" diff --git a/84_Super_Star_Trek/rust/readme.md b/84_Super_Star_Trek/rust/readme.md new file mode 100644 index 00000000..ae2f58dc --- /dev/null +++ b/84_Super_Star_Trek/rust/readme.md @@ -0,0 +1,19 @@ +# Super Star Trek - Rust version + +Explanation of modules: + +- [main.rs](./src/main.rs) - creates the galaxy (generation functions are in model.rs as impl methods) then loops listening for commands. after each command checks for victory or defeat condtions. +- [model.rs](./src/model.rs) - all the structs and enums that represent the galaxy. key methods in here (as impl methods) are generation functions on galaxy and quadrant, and various comparison methods on the 'Pos' tuple type. +- [commands.rs](./src/commands.rs) - most of the code that implements instructions given by the player (some code logic is in the model impls, and some in view.rs if its view only). +- [view.rs](./src/view.rs) - all text printed to the output, mostly called by command.rs (like view::bad_nav for example). also contains the prompts printed to the user (e.g. view::prompts::COMMAND). +- [input.rs](./src/input.rs) - utility methods for getting input from the user, including logic for parsing numbers, repeating prompts until a correct value is provided etc. + +Basically the user is asked for the next command, this runs a function that usually checks if the command system is working, and if so will gather additional input (see next note for a slight change here), then either the model is read and info printed, or its mutated in some way (e.g. firing a torpedo, which reduces the torpedo count on the enterprise and can destroy klingons and star bases; finally the klingons fire back and can destroy the enterprise). Finally the win/lose conditions are checked before the loop repeats. + +## Changes from the original + +I have tried to keep it as close as possible. Notable changes are: + +- commands can be given with parameters in line. e.g. while 'nav' will ask for course and then warp speed in the original, here you can *optionally* also do this as one line, e.g. `nav 1 0.1` to move one sector east. I'm sorry - it was driving me insane in its original form (which is still sorted, as is partial application e.g. nav 1 to preset direction and then provide speed). +- text is mostly not uppercase, as text was in the basic version. this would be easy to change however as all text is in view.rs, but I chose not to. +- the navigation system (plotting direction, paths and collision detection) is as close as I could make it to the basic version (by using other language conversions as specification sources) but I suspect is not perfect. seems to work well enough however. \ No newline at end of file diff --git a/84_Super_Star_Trek/rust/src/commands.rs b/84_Super_Star_Trek/rust/src/commands.rs new file mode 100644 index 00000000..c1c4bc8d --- /dev/null +++ b/84_Super_Star_Trek/rust/src/commands.rs @@ -0,0 +1,543 @@ +use rand::Rng; + +use crate::{model::*, view, input::{self, param_or_prompt_value, prompt_two_values}}; + +pub fn perform_short_range_scan(galaxy: &Galaxy) { + if galaxy.enterprise.damaged.contains_key(systems::SHORT_RANGE_SCAN) { + view::scanners_out(); + return; + } + + view::short_range_scan(&galaxy) +} + +pub fn get_amount_and_set_shields(galaxy: &mut Galaxy, provided: Vec) { + + if galaxy.enterprise.damaged.contains_key(systems::SHIELD_CONTROL) { + view::inoperable(&systems::name_for(systems::SHIELD_CONTROL)); + return; + } + + view::energy_available(galaxy.enterprise.total_energy); + let value = input::param_or_prompt_value(&provided, 0, view::prompts::SHIELDS, 0, i32::MAX); + if value.is_none() { + view::shields_unchanged(); + return; + } + + let value = value.unwrap() as u16; + if value > galaxy.enterprise.total_energy { + view::ridiculous(); + view::shields_unchanged(); + return; + } + + galaxy.enterprise.shields = value; + view::shields_set(value); +} + +pub fn gather_dir_and_speed_then_move(galaxy: &mut Galaxy, provided: Vec) { + + let course = input::param_or_prompt_value(&provided, 0, view::prompts::COURSE, 1.0, 9.0); + if course.is_none() { + view::bad_course_data(); + return; + } + + let course = course.unwrap(); + + let mut max_warp = 8.0; + if galaxy.enterprise.damaged.contains_key(systems::WARP_ENGINES) { + max_warp = 0.2; + } + + let speed = input::param_or_prompt_value(&provided, 1, &view::prompts::warp_factor(max_warp), 0.0, 8.0); + if speed.is_none() { + view::bad_course_data(); + return; + } + + let speed = speed.unwrap(); + + if speed > max_warp { + view::damaged_engines(max_warp, speed); + return; + } + + klingons_move(galaxy); + klingons_fire(galaxy); + + if galaxy.enterprise.destroyed { + return; + } + + repair_systems(&mut galaxy.enterprise, speed); + repair_or_damage_random_system(&mut galaxy.enterprise); + + move_enterprise(course, speed, galaxy); +} + +fn repair_systems(enterprise: &mut Enterprise, amount: f32) { + + let keys: Vec = enterprise.damaged.keys().map(|k| k.to_string()).collect(); + let mut repaired = Vec::new(); + for key in keys { + let fully_fixed = enterprise.repair_system(&key, amount); + if fully_fixed { + repaired.push(systems::name_for(&key)); + } + } + + if repaired.len() <= 0 { + return; + } + + view::damage_control_report(); + for name in repaired { + view::system_repair_completed(name); + } +} + +fn repair_or_damage_random_system(enterprise: &mut Enterprise) { + let mut rng = rand::thread_rng(); + + if rng.gen::() > 0.2 { + return; + } + + if rng.gen::() >= 0.6 { + if enterprise.damaged.len() == 0 { + return; + } + + let damaged: Vec = enterprise.damaged.keys().map(|k| k.to_string()).collect(); + let system = damaged[rng.gen_range(0..damaged.len())].to_string(); + let system_name = &systems::name_for(&system); + + enterprise.repair_system(&system, rng.gen::() * 3.0 + 1.0); + view::random_repair_report_for(system_name, false); + return; + } + + let system = systems::KEYS[rng.gen_range(0..systems::KEYS.len())].to_string(); + let system_name = &systems::name_for(&system); + + enterprise.damage_system(&system, rng.gen::() * 5.0 + 1.0); + view::random_repair_report_for(system_name, true); +} + +fn move_enterprise(course: f32, warp_speed: f32, galaxy: &mut Galaxy) { + + let ship = &mut galaxy.enterprise; + + let (mut path, mut hit_edge) = find_nav_path(ship.quadrant, ship.sector, course, warp_speed); + for i in 0..path.len() { + let (quadrant, sector) = path[i].to_local_quadrant_sector(); + if quadrant != ship.quadrant { + break; // have left current quadrant, so collision checks removed. if there is a collision at the dest... /shrug? + } + let quadrant = &galaxy.quadrants[quadrant.as_index()]; + if quadrant.sector_status(sector) != SectorStatus::Empty { + path = path[..i].into(); + hit_edge = false; + if i > 0 { + let (_, last_sector) = path[path.len() - 1].to_local_quadrant_sector(); + view::bad_nav(last_sector); + } else { + view::bad_nav(ship.sector); + return; + } + break; + } + } + + if path.len() == 0 { + if hit_edge { + view::hit_edge(ship.quadrant, ship.sector); + } + return; + } + + let energy_cost = path.len() as u16 + 10; + + if energy_cost > ship.total_energy { + view::insuffient_warp_energy(warp_speed); + return + } + + let (end_quadrant, end_sector) = path[path.len() - 1].to_local_quadrant_sector(); + if hit_edge { + view::hit_edge(end_quadrant, end_sector); + } + + if ship.quadrant != end_quadrant { + view::enter_quadrant(end_quadrant); + galaxy.scanned.insert(end_quadrant); + + if galaxy.quadrants[end_quadrant.as_index()].klingons.len() > 0 { + view::condition_red(); + if ship.shields <= 200 { + view::danger_shields(); + } + } + } + + ship.quadrant = end_quadrant; + ship.sector = end_sector; + + let quadrant = &galaxy.quadrants[end_quadrant.as_index()]; + if quadrant.docked_at_starbase(ship.sector) { + ship.shields = 0; + ship.photon_torpedoes = MAX_PHOTON_TORPEDOES; + ship.total_energy = MAX_ENERGY; + } else { + ship.total_energy = ship.total_energy - energy_cost; + if ship.shields > ship.total_energy { + view::divert_energy_from_shields(); + ship.shields = ship.total_energy; + } + } + + view::short_range_scan(&galaxy) +} + +fn find_nav_path(start_quadrant: Pos, start_sector: Pos, course: f32, warp_speed: f32) -> (Vec, bool) { + + let (dx, dy) = calculate_delta(course); + + let mut distance = (warp_speed * 8.0) as i8; + if distance == 0 { + distance = 1; + } + + let mut last_sector = start_sector.as_galactic_sector(start_quadrant); + let mut path = Vec::new(); + let mut hit_edge; + + loop { + let nx = (last_sector.0 as f32 + dx) as i8; + let ny = (last_sector.1 as f32 + dy) as i8; + hit_edge = nx < 0 || ny < 0 || nx >= 64 || ny >= 64; + if hit_edge { + break; + } + last_sector = Pos(nx as u8, ny as u8); + path.push(last_sector); + + distance -= 1; + if distance == 0 { + break; + } + } + + (path, hit_edge) +} + +fn calculate_delta(course: f32) -> (f32, f32) { + // this course delta stuff is a translation (of a translation, of a translation...) of the original basic calcs + let dir = (course - 1.0) % 8.0; + let (dx1, dy1) = COURSES[dir as usize]; + let (dx2, dy2) = COURSES[(dir + 1.0) as usize]; + let frac = dir - (dir as i32) as f32; + + let dx = dx1 + (dx2 - dx1) * frac; + let dy = dy1 + (dy2 - dy1) * frac; + + (dx, dy) +} + +fn klingons_move(galaxy: &mut Galaxy) { + let quadrant = &mut galaxy.quadrants[galaxy.enterprise.quadrant.as_index()]; + for k in 0..quadrant.klingons.len() { + let new_sector: Pos; + loop { + let candidate = quadrant.find_empty_sector(); + if candidate != galaxy.enterprise.sector { + new_sector = candidate; + break; + } + } + quadrant.klingons[k].sector = new_sector; + } +} + +fn klingons_fire(galaxy: &mut Galaxy) { + let quadrant = &mut galaxy.quadrants[galaxy.enterprise.quadrant.as_index()]; + if quadrant.docked_at_starbase(galaxy.enterprise.sector) { + view::starbase_shields(); + return; + } + + for k in 0..quadrant.klingons.len() { + quadrant.klingons[k].fire_on(&mut galaxy.enterprise); + } +} + +pub fn run_damage_control(galaxy: &Galaxy) { + if galaxy.enterprise.damaged.contains_key(systems::DAMAGE_CONTROL) { + view::inoperable(&systems::name_for(systems::DAMAGE_CONTROL)); + return; + } + + view::damage_control(&galaxy.enterprise); +} + +pub fn try_starbase_ship_repair(galaxy: &mut Galaxy) { + let ship = &mut galaxy.enterprise; + let quadrant = &galaxy.quadrants[ship.quadrant.as_index()]; + if ship.damaged.len() == 0 || !quadrant.docked_at_starbase(ship.sector) { + return; + } + + let repair_delay = quadrant.star_base.as_ref().unwrap().repair_delay; + let repair_time = (ship.damaged.len() as f32 * 0.1 + repair_delay).max(0.9); + + view::repair_estimate(repair_time); + if !input::prompt_yes_no(view::prompts::REPAIR) { + return; + } + + ship.damaged.clear(); + galaxy.stardate += repair_time; + view::damage_control(&ship); +} + +pub fn perform_long_range_scan(galaxy: &mut Galaxy) { + if galaxy.enterprise.damaged.contains_key(systems::LONG_RANGE_SCAN) { + view::inoperable(&systems::name_for(systems::LONG_RANGE_SCAN)); + return; + } + + let seen = view::long_range_scan(galaxy); + for pos in seen { + galaxy.scanned.insert(pos); + } +} + +pub fn access_computer(galaxy: &Galaxy, provided: Vec) { + if galaxy.enterprise.damaged.contains_key(systems::COMPUTER) { + view::inoperable(&systems::name_for(systems::COMPUTER)); + return; + } + + let operation : i32; + loop { + let entered = input::param_or_prompt_value(&provided, 0, view::prompts::COMPUTER, 0, 5); + if entered.is_none() { + view::computer_options(); + } else { + operation = entered.unwrap(); + break; + } + } + + match operation { + 0 => view::galaxy_scanned_map(galaxy), + 1 => { + view::status_report(galaxy); + run_damage_control(galaxy); + }, + 2 => show_klingon_direction_data(galaxy), + 3 => show_starbase_direction_data(galaxy), + 4 => direction_dist_calculator(galaxy), + 5 => view::galaxy_region_map(), + _ => () // unreachable + } +} + +fn show_klingon_direction_data(galaxy: &Galaxy) { + let quadrant = &galaxy.quadrants[galaxy.enterprise.quadrant.as_index()]; + if quadrant.klingons.len() == 0 { + view::no_local_enemies(); + return; + } + + view::klingon_report(quadrant.klingons.len() > 1); + let origin = galaxy.enterprise.sector; + for k in &quadrant.klingons { + let target = k.sector; + view::direction_distance(origin.direction(target), origin.dist(target)) + } +} + +fn show_starbase_direction_data(galaxy: &Galaxy) { + let quadrant = &galaxy.quadrants[galaxy.enterprise.quadrant.as_index()]; + match &quadrant.star_base { + None => { + view::no_local_starbase(); + return; + }, + Some(s) => { + view::starbase_report(); + let origin = galaxy.enterprise.sector; + let target = s.sector; + view::direction_distance(origin.direction(target), origin.dist(target)) + } + } +} + +fn direction_dist_calculator(galaxy: &Galaxy) { + view::direction_dist_intro(&galaxy.enterprise); + loop { + let coords1 = prompt_two_values(view::prompts::INITIAL_COORDS, 1, 8).map(|(x, y)| Pos(x, y)); + if coords1.is_none() { + continue; + } + let coords2 = prompt_two_values(view::prompts::TARGET_COORDS, 1, 8).map(|(x, y)| Pos(x, y)); + if coords2.is_none() { + continue; + } + let dir = coords1.unwrap().direction(coords2.unwrap()); + let dist = coords1.unwrap().dist(coords2.unwrap()); + view::direction_distance(dir, dist); + break; + } +} + +pub fn get_power_and_fire_phasers(galaxy: &mut Galaxy, provided: Vec) { + if galaxy.enterprise.damaged.contains_key(systems::PHASERS) { + view::inoperable(&systems::name_for(systems::PHASERS)); + return; + } + + let quadrant = &mut galaxy.quadrants[galaxy.enterprise.quadrant.as_index()]; + if quadrant.klingons.len() == 0 { + view::no_local_enemies(); + return; + } + + let computer_damaged = galaxy.enterprise.damaged.contains_key(systems::COMPUTER); + if computer_damaged { + view::computer_accuracy_issue(); + } + + let available_energy = galaxy.enterprise.total_energy - galaxy.enterprise.shields; + view::phasers_locked(available_energy); + let mut power: f32; + loop { + let setting = param_or_prompt_value(&provided, 0, view::prompts::PHASERS, 0, available_energy); + if setting.is_some() { + power = setting.unwrap() as f32; + break; + } + } + + if power == 0.0 { + return; + } + + galaxy.enterprise.total_energy -= power as u16; + + let mut rng = rand::thread_rng(); + if computer_damaged { + power *= rng.gen::(); + } + + let per_enemy = power / quadrant.klingons.len() as f32; + + for k in &mut quadrant.klingons { + let dist = k.sector.abs_diff(galaxy.enterprise.sector) as f32; + let hit_strength = per_enemy / dist * (2.0 + rng.gen::()); + if hit_strength < 0.15 * k.energy { + view::no_damage(k.sector); + } else { + k.energy -= hit_strength; + view::hit_on_klingon(hit_strength, k.sector); + if k.energy > 0.0 { + view::klingon_remaining_energy(k.energy); + } else { + view::klingon_destroyed(); + } + } + } + + quadrant.klingons.retain(|k| k.energy > 0.0); + + klingons_fire(galaxy); +} + +pub fn gather_dir_and_launch_torpedo(galaxy: &mut Galaxy, provided: Vec) { + let star_bases = galaxy.remaining_starbases(); + let ship = &mut galaxy.enterprise; + + if ship.damaged.contains_key(systems::TORPEDOES) { + view::inoperable(&systems::name_for(systems::TORPEDOES)); + return; + } + + if ship.photon_torpedoes == 0 { + view::no_torpedoes_remaining(); + return; + } + + let course = input::param_or_prompt_value(&provided, 0, view::prompts::TORPEDO_COURSE, 1.0, 9.0); + if course.is_none() { + view::bad_torpedo_course(); + return; + } + + ship.photon_torpedoes -= 1; + view::torpedo_track(); + + let path = find_torpedo_path(ship.sector, course.unwrap()); + let quadrant = &mut galaxy.quadrants[ship.quadrant.as_index()]; + let mut hit = false; + for p in path { + view::torpedo_path(p); + match quadrant.sector_status(p) { + SectorStatus::Empty => continue, + SectorStatus::Star => { + hit = true; + view::star_absorbed_torpedo(p); + break; + }, + SectorStatus::Klingon => { + hit = true; + quadrant.get_klingon(p).unwrap().energy = 0.0; + quadrant.klingons.retain(|k| k.energy > 0.0); + view::klingon_destroyed(); + break; + }, + SectorStatus::StarBase => { + hit = true; + quadrant.star_base = None; + let remaining = star_bases - 1; + view::destroyed_starbase(remaining > 0); + if remaining == 0 { + ship.destroyed = true; + } + break; + } + } + } + + if ship.destroyed { // if you wiped out the last starbase, trigger game over + return; + } + + if !hit { + view::torpedo_missed(); + } + + klingons_fire(galaxy); +} + +fn find_torpedo_path(start_sector: Pos, course: f32) -> Vec { + + let (dx, dy) = calculate_delta(course); + + let mut last_sector = start_sector; + let mut path = Vec::new(); + + loop { + let nx = (last_sector.0 as f32 + dx) as i8; + let ny = (last_sector.1 as f32 + dy) as i8; + if nx < 0 || ny < 0 || nx >= 8 || ny >= 8 { + break; + } + last_sector = Pos(nx as u8, ny as u8); + path.push(last_sector); + } + + path +} \ No newline at end of file diff --git a/84_Super_Star_Trek/rust/src/input.rs b/84_Super_Star_Trek/rust/src/input.rs new file mode 100644 index 00000000..62d4daee --- /dev/null +++ b/84_Super_Star_Trek/rust/src/input.rs @@ -0,0 +1,73 @@ +use std::{io::{stdin, stdout, Write}, str::FromStr}; + +pub fn prompt(prompt_text: &str) -> Vec { + let stdin = stdin(); + let mut stdout = stdout(); + + print!("{prompt_text} "); + let _ = stdout.flush(); + + let mut buffer = String::new(); + if let Ok(_) = stdin.read_line(&mut buffer) { + return buffer.trim_end().split([' ', ',']).map(|s| s.to_string()).collect(); + } + Vec::new() +} + +pub fn prompt_yes_no(prompt_text: &str) -> bool { + loop { + let response = prompt(&format!("{prompt_text} (Y/N)")); + if response.len() == 0 { + continue; + } + let first_word = response[0].to_uppercase(); + if first_word.starts_with("Y") { + return true; + } + if first_word.starts_with("N") { + return false; + } + } +} + +pub fn prompt_value(prompt_text: &str, min: T, max: T) -> Option { + let passed = prompt(prompt_text); + if passed.len() != 1 { + return None + } + match passed[0].parse::() { + Ok(n) if (n >= min && n <= max) => Some(n), + _ => None + } +} + +pub fn param_or_prompt_value(params: &Vec, param_pos: usize, prompt_text: &str, min: T, max: T) -> Option { + let mut res: Option = None; + if params.len() > param_pos { + match params[param_pos].parse::() { + Ok(n) if (n >= min && n <= max) => res = Some(n), + _ => () + } + } + if res.is_some() { + return res; + } + return prompt_value::(prompt_text, min, max); +} + +pub fn prompt_two_values(prompt_text: &str, min: T, max: T) -> Option<(T, T)> { + let passed = prompt(prompt_text); + if passed.len() != 2 { + return None + } + match passed[0].parse::() { + Ok(n1) if (n1 >= min && n1 <= max) => { + match passed[1].parse::() { + Ok(n2) if (n2 >= min && n2 <= max) => + Some((n1, n2)), + _ => None + } + } + _ => None + } +} \ No newline at end of file diff --git a/84_Super_Star_Trek/rust/src/main.rs b/84_Super_Star_Trek/rust/src/main.rs new file mode 100644 index 00000000..4c1a277e --- /dev/null +++ b/84_Super_Star_Trek/rust/src/main.rs @@ -0,0 +1,70 @@ +use std::process::exit; + +use input::{prompt, prompt_yes_no}; +use model::{Galaxy, systems}; + +mod input; +mod model; +mod commands; +mod view; + +fn main() { + ctrlc::set_handler(move || { exit(0) }) + .expect("Error setting Ctrl-C handler"); + + view::title(); + if prompt_yes_no(view::prompts::INSTRUCTIONS) { + view::full_instructions(); + let _ = input::prompt(view::prompts::WHEN_READY); + } + + let mut galaxy = Galaxy::generate_new(); + let initial_klingons = galaxy.remaining_klingons(); + let initial_stardate = galaxy.stardate; + + view::enterprise(); + view::intro(&galaxy); + let _ = input::prompt(view::prompts::WHEN_READY); + + view::starting_quadrant(galaxy.enterprise.quadrant); + view::short_range_scan(&galaxy); + + loop { + let command = input::prompt(view::prompts::COMMAND); + if command.len() == 0 { + continue; + } + match command[0].to_uppercase().as_str() { // order is weird because i built it in this order :) + systems::SHORT_RANGE_SCAN => commands::perform_short_range_scan(&galaxy), + systems::WARP_ENGINES => commands::gather_dir_and_speed_then_move(&mut galaxy, command[1..].into()), + systems::SHIELD_CONTROL => commands::get_amount_and_set_shields(&mut galaxy, command[1..].into()), + systems::DAMAGE_CONTROL => { + commands::run_damage_control(&galaxy); + commands::try_starbase_ship_repair(&mut galaxy); + } + systems::LONG_RANGE_SCAN => commands::perform_long_range_scan(&mut galaxy), + systems::COMPUTER => commands::access_computer(&galaxy, command[1..].into()), + systems::PHASERS => commands::get_power_and_fire_phasers(&mut galaxy, command[1..].into()), + systems::TORPEDOES => commands::gather_dir_and_launch_torpedo(&mut galaxy, command[1..].into()), + systems::RESIGN => galaxy.enterprise.destroyed = true, + _ => view::print_command_help() + } + + if galaxy.enterprise.destroyed || galaxy.enterprise.is_stranded() || galaxy.stardate >= galaxy.final_stardate { + view::end_game_failure(&galaxy); + if galaxy.remaining_klingons() > 0 && galaxy.remaining_starbases() > 0 && galaxy.stardate < galaxy.final_stardate { + view::replay(); + let result = prompt(""); + if result.len() > 0 && result[0].to_uppercase() == "AYE" { + galaxy.enterprise = Galaxy::new_captain(&galaxy.quadrants); + continue; + } + } + break; + } else if galaxy.remaining_klingons() == 0 { + let efficiency = 1000.0 * f32::powi(initial_klingons as f32 / (galaxy.stardate - initial_stardate), 2); + view::congratulations(efficiency); + break; + } + } +} diff --git a/84_Super_Star_Trek/rust/src/model.rs b/84_Super_Star_Trek/rust/src/model.rs new file mode 100644 index 00000000..aa08d734 --- /dev/null +++ b/84_Super_Star_Trek/rust/src/model.rs @@ -0,0 +1,355 @@ +use std::{ops::{Mul, Add}, fmt::Display, collections::{HashMap, HashSet}}; + +use rand::Rng; + +use crate::view; + +pub struct Galaxy { + pub stardate: f32, + pub final_stardate: f32, + pub quadrants: Vec, + pub scanned: HashSet, + pub enterprise: Enterprise +} + +pub struct Quadrant { + pub stars: Vec, + pub star_base: Option, + pub klingons: Vec +} + +pub struct StarBase { + pub sector: Pos, + pub repair_delay: f32, +} + +pub struct Klingon { + pub sector: Pos, + pub energy: f32 +} + +impl Klingon { + pub fn fire_on(&mut self, enterprise: &mut Enterprise) { + let mut rng = rand::thread_rng(); + let attack_strength = rng.gen::(); + let dist_to_enterprise = self.sector.abs_diff(enterprise.sector) as f32; + let hit_strength = self.energy * (2.0 + attack_strength) / dist_to_enterprise; + + self.energy /= 3.0 + attack_strength; + + enterprise.take_hit(self.sector, hit_strength as u16); + } +} + +pub struct Enterprise { + pub destroyed: bool, + pub damaged: HashMap, + pub quadrant: Pos, + pub sector: Pos, + pub photon_torpedoes: u8, + pub total_energy: u16, + pub shields: u16, +} +impl Enterprise { + fn take_hit(&mut self, sector: Pos, hit_strength: u16) { + if self.destroyed { + return; + } + + view::enterprise_hit(&hit_strength, sector); + + if self.shields <= hit_strength { + view::enterprise_destroyed(); + self.destroyed = true; + return; + } + + self.shields -= hit_strength; + + view::shields_hit(self.shields); + + if hit_strength >= 20 { + self.take_damage(hit_strength) + } + } + + fn take_damage(&mut self, hit_strength: u16) { + let mut rng = rand::thread_rng(); + + let hit_past_shield = hit_strength as f32 / self.shields as f32; + if rng.gen::() > 0.6 || hit_past_shield < 0.02 { + return + } + + let system = systems::KEYS[rng.gen_range(0..systems::KEYS.len())].to_string(); + let damage = hit_past_shield + rng.gen::() * 0.5; + self.damage_system(&system, damage); + } + + pub fn damage_system(&mut self, system: &str, damage: f32) { + self.damaged.entry(system.to_string()).and_modify(|d| *d -= damage).or_insert(-damage); + } + + pub fn repair_system(&mut self, system: &str, amount: f32) -> bool { + let existing_damage = self.damaged[system]; + if existing_damage + amount >= -0.1 { + self.damaged.remove(system); + return true; + } + + self.damaged.entry(system.to_string()).and_modify(|d| *d += amount); + return false; + } + + pub fn is_stranded(&self) -> bool { + if self.total_energy < 10 || (self.shields + 10 > self.total_energy && self.damaged.contains_key(systems::SHIELD_CONTROL)) { + view::stranded(); + return true; + } + return false; + } +} + +pub mod systems { + + pub const SHORT_RANGE_SCAN: &str = "SRS"; + pub const WARP_ENGINES: &str = "NAV"; + pub const SHIELD_CONTROL: &str = "SHE"; + pub const DAMAGE_CONTROL: &str = "DAM"; + pub const LONG_RANGE_SCAN: &str = "LRS"; + pub const COMPUTER: &str = "COM"; + pub const PHASERS: &str = "PHA"; + pub const TORPEDOES: &str = "TOR"; + + pub const RESIGN: &str = "XXX"; + + pub const KEYS: [&str; 8] = [ + SHORT_RANGE_SCAN, WARP_ENGINES, SHIELD_CONTROL, DAMAGE_CONTROL, LONG_RANGE_SCAN, COMPUTER, PHASERS, TORPEDOES + ]; + + pub fn name_for(key: &str) -> String { + match key { + SHORT_RANGE_SCAN => "Short Range Scanners".into(), + WARP_ENGINES => "Warp Engines".into(), + SHIELD_CONTROL => "Shield Control".into(), + DAMAGE_CONTROL => "Damage Control".into(), + LONG_RANGE_SCAN => "Long Range Scanners".into(), + COMPUTER => "Library-Computer".into(), + PHASERS => "Phaser Control".into(), + TORPEDOES => "Photon Tubes".into(), + _ => "Unknown".into() + } + } +} + +#[derive(PartialEq, Clone, Copy, Debug, Hash, Eq)] +pub struct Pos(pub u8, pub u8); + +impl Pos { + pub fn as_index(&self) -> usize { + (self.0 * 8 + self.1).into() + } + + pub fn abs_diff(&self, other: Pos) -> u8 { + self.0.abs_diff(other.0) + self.1.abs_diff(other.1) + } + + pub fn dist(&self, other: Pos) -> u8 { + let dx = other.0 as f32 - self.0 as f32; + let dy = other.1 as f32 - self.1 as f32; + (f32::powi(dx, 2) + f32::powi(dy, 2)).sqrt() as u8 + } + + pub fn direction(&self, other: Pos) -> f32 { + // this is a replication of the original BASIC code + let dx = other.0 as f32 - self.0 as f32; + let dy = other.1 as f32 - self.1 as f32; + let dx_dominant = dx.abs() > dy.abs(); + + let frac = if dx_dominant { dy / dx } else { -dx / dy }; + let nearest_cardinal = + if dx_dominant { + if dx > 0. { 7. } else { 3. } + } else { + if dy > 0. { 1. } else { 5. } + }; + + let mut dir = nearest_cardinal + frac; + if dir < 1. { + dir += 8. + } + dir + } + + pub fn as_galactic_sector(&self, containing_quadrant: Pos) -> Self { + Pos(containing_quadrant.0 * 8 + self.0, containing_quadrant.1 * 8 + self.1) + } + + pub fn to_local_quadrant_sector(&self) -> (Self, Self) { + (Pos(self.0 / 8, self.1 / 8), Pos(self.0 % 8, self.1 % 8)) + } +} + +impl Mul for Pos { + type Output = Self; + + fn mul(self, rhs: u8) -> Self::Output { + Pos(self.0 * rhs, self.1 * rhs) + } +} + +impl Add for Pos { + type Output = Self; + + fn add(self, rhs: Pos) -> Self::Output { + Pos(self.0 + rhs.0, self.1 + rhs.1) + } +} + +impl Display for Pos { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{} , {}", self.0 + 1, self.1 + 1) + } +} + +pub const COURSES : [(f32, f32); 9] = [ + (0., 1.), + (-1., 1.), + (-1., 0.), + (-1., -1.), + (0., -1.), + (1., -1.), + (1., 0.), + (1., 1.), + (0., 1.), // course 9 is equal to course 1 +]; + +#[derive(PartialEq)] +pub enum SectorStatus { + Empty, Star, StarBase, Klingon +} + +pub const MAX_PHOTON_TORPEDOES: u8 = 28; +pub const MAX_ENERGY: u16 = 3000; + +impl Galaxy { + pub fn remaining_klingons(&self) -> u8 { + let quadrants = &self.quadrants; + quadrants.into_iter().map(|q| { q.klingons.len() as u8 }).sum::() + } + + pub fn remaining_starbases(&self) -> u8 { + let quadrants = &self.quadrants; + quadrants.into_iter().filter(|q| q.star_base.is_some()).count() as u8 + } + + pub fn generate_new() -> Self { + let quadrants = Self::generate_quadrants(); + + let mut rng = rand::thread_rng(); + let stardate = rng.gen_range(20..=40) as f32 * 100.0; + + let enterprise = Self::new_captain(&quadrants); + + let mut scanned = HashSet::new(); + scanned.insert(enterprise.quadrant); + + Galaxy { + stardate, + final_stardate: stardate + rng.gen_range(25..=35) as f32, + quadrants, + scanned, + enterprise + } + } + + pub fn new_captain(quadrants: &Vec) -> Enterprise { + let mut rng = rand::thread_rng(); + let enterprise_quadrant = Pos(rng.gen_range(0..8), rng.gen_range(0..8)); + let enterprise_sector = quadrants[enterprise_quadrant.as_index()].find_empty_sector(); + Enterprise { + destroyed: false, + damaged: HashMap::new(), + quadrant: enterprise_quadrant, + sector: enterprise_sector, + photon_torpedoes: MAX_PHOTON_TORPEDOES, + total_energy: MAX_ENERGY, + shields: 0 } + } + + fn generate_quadrants() -> Vec { + let mut rng = rand::thread_rng(); + let mut result = Vec::new(); + for _ in 0..64 { + + let mut quadrant = Quadrant { stars: Vec::new(), star_base: None, klingons: Vec::new() }; + let star_count = rng.gen_range(0..=7); + for _ in 0..star_count { + quadrant.stars.push(quadrant.find_empty_sector()); + } + + if rng.gen::() > 0.96 { + quadrant.star_base = Some(StarBase { sector: quadrant.find_empty_sector(), repair_delay: rng.gen::() * 0.5 }); + } + + let klingon_count = + match rng.gen::() { + n if n > 0.98 => 3, + n if n > 0.95 => 2, + n if n > 0.8 => 1, + _ => 0 + }; + for _ in 0..klingon_count { + quadrant.klingons.push(Klingon { sector: quadrant.find_empty_sector(), energy: rng.gen_range(100..=300) as f32 }); + } + + result.push(quadrant); + } + result + } +} + +impl Quadrant { + pub fn sector_status(&self, sector: Pos) -> SectorStatus { + if self.stars.contains(§or) { + SectorStatus::Star + } else if self.is_starbase(sector) { + SectorStatus::StarBase + } else if self.has_klingon(sector) { + SectorStatus::Klingon + } else { + SectorStatus::Empty + } + } + + fn is_starbase(&self, sector: Pos) -> bool { + match &self.star_base { + None => false, + Some(p) => p.sector == sector + } + } + + fn has_klingon(&self, sector: Pos) -> bool { + let klingons = &self.klingons; + klingons.into_iter().find(|k| k.sector == sector).is_some() + } + + pub fn get_klingon(&mut self, sector: Pos) -> Option<&mut Klingon> { + let klingons = &mut self.klingons; + klingons.into_iter().find(|k| k.sector == sector) + } + + pub fn find_empty_sector(&self) -> Pos { + let mut rng = rand::thread_rng(); + loop { + let pos = Pos(rng.gen_range(0..8), rng.gen_range(0..8)); + if self.sector_status(pos) == SectorStatus::Empty { + return pos + } + } + } + + pub fn docked_at_starbase(&self, enterprise_sector: Pos) -> bool { + self.star_base.is_some() && self.star_base.as_ref().unwrap().sector.abs_diff(enterprise_sector) == 1 + } +} diff --git a/84_Super_Star_Trek/rust/src/view.rs b/84_Super_Star_Trek/rust/src/view.rs new file mode 100644 index 00000000..caf6b74d --- /dev/null +++ b/84_Super_Star_Trek/rust/src/view.rs @@ -0,0 +1,635 @@ +use crate::model::{Galaxy, Pos, SectorStatus, Enterprise, systems}; + +pub mod prompts { + pub const INSTRUCTIONS: &str = "Do you need instructions"; + pub const COURSE: &str = "Course (1-9)?"; + pub const TORPEDO_COURSE: &str = "Photon torpedo course (1-9)?"; + pub const SHIELDS: &str = "Number of units to shields"; + pub const REPAIR: &str = "Will you authorize the repair order"; + pub const COMPUTER: &str = "Computer active and waiting command?"; + pub const PHASERS: &str = "Number of units to fire"; + pub const WHEN_READY: &str = "Press Enter when ready to accept command"; + pub const COMMAND: &str = "Command?"; + pub const INITIAL_COORDS: &str = " Initial coordinates (X/Y)"; + pub const TARGET_COORDS: &str = " Final coordinates (X/Y)"; + + pub fn warp_factor(max_warp: f32) -> String { + format!("Warp Factor (0-{})?", max_warp) + } +} + +pub fn title() { + println!(" + + + + + + + + + + + + ************************************* + * * + * * + * * * SUPER STAR TREK * * * + * * + * * + ************************************* + + + + + + + + + + "); +} + +pub fn full_instructions() { + println!( +" INSTRUCTIONS FOR 'SUPER STAR TREK' + + 1. When you see \"Command ?\" printed, enter one of the legal + commands (NAV, SRS, LRS, PHA, TOR, SHE, DAM, COM, OR XXX). + 2. If you should type in an illegal command, you'll get a short + list of the legal commands printed out. + 3. Some commands require you to enter data (for example, the + 'NAV' command comes back with 'Course (1-9) ?'.) If you + type in illegal data (like negative numbers), then command + will be aborted. + + The galaxy is divided into an 8 X 8 quadrant grid, + and each quadrant is further divided into an 8 X 8 sector grid. + + You will be assigned a starting point somewhere in the + galaxy to begin a tour of duty as commander of the starship + Enterprise; your mission: to seek and destroy the fleet of + Klingon warships which are menacing the United Federation of + Planets. + + You have the following commands available to you as captain + of the starship Enterprise: + + NAV command = Warp Engine Control + Course is in a circular numerical 4 3 2 + vector arrangement as shown . . . + integer and real values may be ... + used. (Thus course 1.5 is half- 5 ---*--- 1 + way between 1 and 2. ... + . . . + Values may approach 9.0, which 6 7 8 + itself is equivalent to 1.0 + COURSE + One warp factor is the size of + one quadrant. Therefore, to get + from quadrant 6,5 to 5,5, you WOULD + use course 3, warp factor 1. + + SRS command = Short Range Sensor Scan + Shows you a scan of your present quadrant. + + Symbology on your sensor screen is as follows: + <*> = Your starship's position + +K+ = Klingon battle cruiser + >!< = Federation starbase (refuel/repair/re-arm here!) + * = Star + + A condensed 'status report' will also be presented. + + LRS command = Long Range Sensor Scan + Shows conditions in space for one quadrant on each side + of the Enterprise (which is in the middle of the scan). + The scan is coded in the form ###, where the units digit + is the number of stars, the tens digit is the number of + starbases, and the hundreds digit is the number of + Klingons. + + Example - 207 = 2 Klingons, No starbases, & 7 stars. + + PHA command = Phaser Control + Allows you to destroy the Klingon battle cruisers by + zapping them with suitably large units of energy to + deplete their shield power. (Remember, Klingons have + phasers, too!) + + TOR command = Photon Torpedo Control + Torpedo course is the same as used in warp engine control. + If you hit the Klingon vessel, he is destroyed and + cannot fire back at you. If you miss, you are subject to + his phaser fire. In either case, you are also subject to + the phaser fire of all other Klingons in the quadrant. + + The library-computer (COM command) has an option to + compute torpedo trajectory for you (Option 2). + + SHE command = Shield Control + Defines the number of energy units to be assigned to the + shields. Energy is taken from total ship's energy. Note + that the status display total energy includes shield energy. + + DAM command = Damage Control Report + Gives the state of repair of all devices. Where a negative + 'state of repair' shows that the device is temporarily + damaged. + + COM command = Library-Computer + The library-computer contains six options: + Option 0 = Cumulative Galactic Record + This option shows computer memory of the results of all + previous short and long range sensor scans. + Option 1 = Status Report + This option shows the number of Klingons, Stardates, + and starbases remaining in the game. + Option 2 = Photon Torpedo Data + Which gives directions and distance from the Enterprise + to all Klingons in your quadrant. + Option 3 = Starbase Nav Data + This option gives direction and distance to any + starbase within your quadrant. + Option 4 = Direction/Distance Calculator + This option allows you to enter coordinates for + direction/distance calculations. + Option 5 = Galactic Region Name Map + This option prints the names of the sixteen major + galactic regions referred to in the game. + +") +} + +pub fn enterprise() { + println!(" + + + + + + + + + + + ,------*------, + ,------------- '--- ------' + '-------- --' / / + ,---' '-------/ /--, + '----------------' + + THE USS ENTERPRISE --- NCC-1701 + + + + + + +") +} + +pub fn intro(model: &Galaxy) { + let star_bases = model.remaining_starbases(); + let mut star_base_message: String = "There is 1 starbase".into(); + if star_bases > 1 { + star_base_message = format!("There are {} starbases", star_bases); + } + println!( +"Your orders are as follows: + Destroy the {} Klingon warships which have invaded + the galaxy before they can attack federation headquarters + on stardate {:.1}. This gives you {} days. {} in the galaxy for resupplying your ship.\n", + model.remaining_klingons(), model.final_stardate, model.final_stardate - model.stardate, star_base_message) +} + +const REGION_NAMES: [&str; 16] = [ + "Antares", + "Sirius", + "Rigel", + "Deneb", + "Procyon", + "Capella", + "Vega", + "Betelgeuse", + "Canopus", + "Aldebaran", + "Altair", + "Regulus", + "Sagittarius", + "Arcturus", + "Pollux", + "Spica" +]; + +const SUB_REGION_NAMES: [&str; 4] = ["I", "II", "III", "IV"]; + +fn quadrant_name(quadrant: Pos) -> String { + format!("{} {}", + REGION_NAMES[((quadrant.0 << 1) + (quadrant.1 >> 2)) as usize], + SUB_REGION_NAMES[(quadrant.1 % 4) as usize]) +} + +pub fn starting_quadrant(quadrant: Pos) { + println!( +"\nYour mission begins with your starship located +in the galactic quadrant, '{}'.\n", quadrant_name(quadrant)) +} + +pub fn enter_quadrant(quadrant: Pos) { + println!("\nNow entering {} quadrant . . .\n", quadrant_name(quadrant)) +} + +pub fn short_range_scan(model: &Galaxy) { + let quadrant = &model.quadrants[model.enterprise.quadrant.as_index()]; + let mut condition = "GREEN"; + if quadrant.docked_at_starbase(model.enterprise.sector) { + println!("Shields dropped for docking purposes"); + condition = "DOCKED"; + } else if quadrant.klingons.len() > 0 { + condition = "*RED*"; + } else if model.enterprise.damaged.len() > 0 { + condition = "YELLOW"; + } + + let data : [String; 8] = [ + format!("Stardate {:.1}", model.stardate), + format!("Condition {}", condition), + format!("Quadrant {}", model.enterprise.quadrant), + format!("Sector {}", model.enterprise.sector), + format!("Photon torpedoes {}", model.enterprise.photon_torpedoes), + format!("Total energy {}", model.enterprise.total_energy), + format!("Shields {}", model.enterprise.shields), + format!("Klingons remaining {}", model.remaining_klingons()), + ]; + + println!("{:-^33}", ""); + for x in 0..=7 { + for y in 0..=7 { + let pos = Pos(x, y); + if &pos == &model.enterprise.sector { + print!("<*> ") + } else { + match quadrant.sector_status(pos) { + SectorStatus::Star => print!(" * "), + SectorStatus::StarBase => print!(">!< "), + SectorStatus::Klingon => print!("+K+ "), + _ => print!(" "), + } + } + } + println!("{:>9}{}", "", data[x as usize]) + } + println!("{:-^33}", ""); +} + +pub fn print_command_help() { + println!( +"Enter one of the following: + NAV (To set course) + SRS (For short range sensor scan) + LRS (For long range sensor scan) + PHA (To fire phasers) + TOR (To fire photon torpedoes) + SHE (To raise or lower shields) + DAM (For damage control reports) + COM (To call on library-computer) + XXX (To resign your command) + ") +} + +pub fn end_game_failure(galaxy: &Galaxy) { + println!( +"Is is stardate {:.1}. +There were {} Klingon battle cruisers left at +the end of your mission. +", galaxy.stardate, galaxy.remaining_klingons()); +} + +pub fn enterprise_destroyed() { + println!("The Enterprise has been destroyed. The Federation will be conquered."); +} + +pub fn bad_course_data() { + println!(" Lt. Sulu reports, 'Incorrect course data, sir!'") +} + +pub fn bad_nav(current_sector: Pos) { + println!("Warp engines shut down at sector {current_sector} dues to bad navigation") +} + +pub fn bad_torpedo_course() { + println!(" Ensign Chekov reports, 'Incorrect course data, sir!'") +} + +pub fn enterprise_hit(hit_strength: &u16, from_sector: Pos) { + println!("{hit_strength} unit hit on Enterprise from sector {from_sector}"); +} + +pub fn hit_edge(quadrant: Pos, sector: Pos) { + println!( +"Lt. Uhura report message from Starfleet Command: + 'Permission to attempt crossing of galactic perimeter + is hereby *Denied*. Shut down your engines.' + Chief Engineer Scott reports, 'Warp engines shut down + at sector {} of quadrant {}.'", sector, quadrant); +} + +pub fn condition_red() { + println!("COMBAT AREA CONDITION RED") +} + +pub fn danger_shields() { + println!(" SHIELDS DANGEROUSLY LOW ") +} + +pub fn insuffient_warp_energy(warp_speed: f32) { + println!( +"Engineering reports, 'Insufficient energy available + for maneuvering at warp {warp_speed} !'") +} + +pub fn divert_energy_from_shields() { + println!("Shield Control supplies energy to complete the maneuver.") +} + +pub fn energy_available(total_energy: u16) { + println!("Energy available = {{{total_energy}}}") +} + +pub fn shields_unchanged() { + println!("") +} + +pub fn ridiculous() { + println!("Shield Control reports, 'This is not the Federation Treasury.'") +} + +pub fn shields_set(value: u16) { + println!( +"Deflector control room report: + 'Shields now at {value} units per your command.'") +} + +pub fn shields_hit(shields: u16) { + println!(" ") +} + +pub fn inoperable(arg: &str) { + println!("{} inoperable", arg) +} + +pub fn scanners_out() { + println!("*** Short Range Sensors are out ***") +} + +pub fn damaged_engines(max_warp: f32, warp_factor: f32) { + println!( +"Warp engines are damaged. Maximum speed = warp {max_warp} + Chief Engineer Scott reports, 'The engines won't take warp {warp_factor} !'") +} + +pub fn damage_control_report() { + println!("Damage Control report:") +} + +pub fn random_repair_report_for(name: &str, damaged: bool) { + let mut message = "state of repair improved"; + if damaged { + message = "damaged"; + } + println!("Damage Control report: {name} {message}") +} + +pub fn system_repair_completed(name: String) { + println!(" {name} repair completed.") +} + +pub fn damage_control(enterprise: &Enterprise) { + println!("Device State of Repair"); + for key in systems::KEYS { + let damage = enterprise.damaged.get(key).unwrap_or(&0.0); + println!("{:<25}{}", systems::name_for(key), damage) + } + println!(); +} + +pub fn long_range_scan(galaxy: &Galaxy) -> Vec { + + let cx = galaxy.enterprise.quadrant.0 as i8; + let cy = galaxy.enterprise.quadrant.1 as i8; + + let mut seen = Vec::new(); + + println!("Long range scan for quadrant {}", galaxy.enterprise.quadrant); + println!("{:-^19}", ""); + for x in cx - 1..=cx + 1 { + for y in cy - 1..=cy + 1 { + let mut klingons = "*".into(); + let mut star_bases = "*".into(); + let mut stars = "*".into(); + + if y >= 0 && y < 8 && x >= 0 && x < 8 { + let pos = Pos(x as u8, y as u8); + seen.push(pos); + + let quadrant = &galaxy.quadrants[pos.as_index()]; + klingons = format!("{}", quadrant.klingons.len()); + star_bases = quadrant.star_base.as_ref().map_or("0", |_| "1"); + stars = format!("{}", quadrant.stars.len()); + } + + print!(": {}{}{} ", klingons, star_bases, stars) + } + println!(":"); + println!("{:-^19}", ""); + } + + seen +} + +pub fn stranded() { + println!( +"** FATAL ERROR ** You've just stranded your ship in space +You have insufficient maneuvering energy, and shield control +is presently incapable of cross-circuiting to engine room!!") +} + +pub fn computer_options() { + println!( +" 0 = Cumulative galactic record + 1 = Status report + 2 = Photon torpedo data + 3 = Starbase nav data + 4 = Direction/distance calculator + 5 = Galaxy 'region name' map") +} + +pub fn galaxy_region_map() { + println!( +" The Galaxy + 1 2 3 4 5 6 7 8 + ----- ----- ----- ----- ----- ----- ----- -----"); + for i in (0..REGION_NAMES.len()-1).step_by(2) { + println!( +"{} {:^23} {:^23} + ----- ----- ----- ----- ----- ----- ----- -----", (i/2)+1, REGION_NAMES[i], REGION_NAMES[i+1]); + } +} + +pub fn galaxy_scanned_map(galaxy: &Galaxy) { + println!( +"Computer record of galaxy for quadrant {} + 1 2 3 4 5 6 7 8 + ----- ----- ----- ----- ----- ----- ----- -----", galaxy.enterprise.quadrant); + for x in 0..8 { + print!("{} ", x+1); + for y in 0..8 { + let pos = Pos(x, y); + if galaxy.scanned.contains(&pos) { + let quadrant = &galaxy.quadrants[pos.as_index()]; + print!(" {}{}{} ", quadrant.klingons.len(), quadrant.star_base.as_ref().map_or("0", |_| "1"), quadrant.stars.len()) + } else { + print!(" *** "); + } + } + println!( +"\n ----- ----- ----- ----- ----- ----- ----- -----") + } +} + +pub fn no_local_enemies() { + println!( +"Science Officer Spock reports, 'Sensors show no enemy ships + in this quadrant'") +} + +pub fn computer_accuracy_issue() { + println!("Computer failure hampers accuracy") +} + +pub fn phasers_locked(available_energy: u16) { + println!("Phasers locked on target; Energy available = {available_energy} units") +} + +pub fn starbase_shields() { + println!("Starbase shields protect the Enterprise") +} + +pub fn repair_estimate(repair_time: f32) { + println!( +"Technicians standing by to effect repairs to your ship; +Estimated time to repair: {repair_time:.1} stardates.") +} + +pub fn no_damage(sector: Pos) { + println!("Sensors show no damage to enemy at {sector}") +} + +pub fn hit_on_klingon(hit_strength: f32, sector: Pos) { + println!("{hit_strength} unit hit on Klingon at sector {sector}") +} + +pub fn klingon_remaining_energy(energy: f32) { + println!(" (sensors show {energy} units remaining)") +} + +pub fn klingon_destroyed() { + println!("*** Klingon destroyed ***") +} + +pub fn congratulations(efficiency: f32) { + println!(" +Congratulations, Captain! The last Klingon battle cruiser +menacing the Federation has been destroyed. + +Your efficiency rating is {efficiency}. + ") +} + +pub fn replay() { + println!(" +The Federation is in need of a new starship commander +for a similar mission -- if there is a volunteer +let him step forward and enter 'Aye'") +} + +pub fn no_torpedoes_remaining() { + println!("All photon torpedoes expended") +} + +pub fn torpedo_track() { + println!("Torpedo track:") +} + +pub fn torpedo_path(sector: Pos) { + println!("{:<16}{}", "", sector) +} + +pub fn torpedo_missed() { + println!("Torpedo missed!") +} + +pub fn star_absorbed_torpedo(sector: Pos) { + println!("Star at {sector} absorbed torpedo energy.") +} + +pub fn destroyed_starbase(not_the_last_starbase: bool) { + println!("*** Starbase destroyed ***"); + if not_the_last_starbase { + println!(" +Starfleet Command reviewing your record to consider +court martial!") + } else { + println!(" +That does it, Captain!! You are hereby relieved of command +and sentenced to 99 stardates at hard labor on Cygnus 12!!") + } +} + +pub fn no_local_starbase() { + println!("Mr. Spock reports, 'Sensors show no starbases in this quadrant.'") +} + +pub fn starbase_report() { + println!("From Enterprise to Starbase:'") +} + +pub fn direction_distance(dir: f32, dist: u8) { + println!( +"Direction = {dir} +Distance = {dist}" + ) +} + +pub fn status_report(galaxy: &Galaxy) { + let klingon_count = galaxy.remaining_klingons(); + let star_bases = galaxy.remaining_starbases(); + let time_remaining = galaxy.final_stardate - galaxy.stardate; + let mut plural_starbase = ""; + if star_bases > 1 { + plural_starbase = "s"; + } + + println!(" Status report: +Klingons left: {klingon_count} +Mission must be completed in {time_remaining:.1} stardates. +The Federation is maintaining {star_bases} starbase{plural_starbase} in the galaxy. +") +} + +pub fn direction_dist_intro(enterprise: &Enterprise) { + let quadrant = enterprise.quadrant; + let sector = enterprise.sector; + println!("Direction/distance calculator: +You are at quadrant {quadrant} sector {sector} +Please enter") +} + +pub fn klingon_report(more_than_one: bool) { + let mut plural = ""; + if more_than_one { + plural = "s"; + } + println!("From Enterprise to Klingon battle cruiser{}", plural) +} \ No newline at end of file diff --git a/86_Target/perl/README.md b/86_Target/perl/README.md index e69c8b81..eca6b9fa 100644 --- a/86_Target/perl/README.md +++ b/86_Target/perl/README.md @@ -1,3 +1,9 @@ Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html) Conversion to [Perl](https://www.perl.org/) + +Modified so that if the user enters "quit" or "stop" for the input, the program will exit. +This way the user doesn't have to enter Contorl-C to quit. + +Target values can be space and/or comma separated, so "1 2 3" is valid, as is "1,2,3" or even "1, 2, 3". +I believe the original Basic program wanted "1,2,3" or else each on a separate line. diff --git a/86_Target/perl/target.pl b/86_Target/perl/target.pl new file mode 100644 index 00000000..cb552a39 --- /dev/null +++ b/86_Target/perl/target.pl @@ -0,0 +1,107 @@ +#!/usr/bin/perl + +# Target program in Perl +# Modified so that if the user enters "quit" or "stop" for the input, the program will exit. +# Values can be space and/or comma separated. +# Translated by Kevin Brannen (kbrannen) + +use strict; +use warnings; + +# globals +my $R = 1; +my $R1 = 57.296; +my $Pi = 3.14159; + +print "\n"; +print " " x 33, "TARGET\n"; +print " " x 15, "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n\n\n"; + +print "YOU ARE THE WEAPONS OFFICER ON THE STARSHIP ENTERPRISE\n"; +print "AND THIS IS A TEST TO SEE HOW ACCURATE A SHOT YOU\n"; +print "ARE IN A THREE-DIMENSIONAL RANGE. YOU WILL BE TOLD\n"; +print "THE RADIAN OFFSET FOR THE X AND Z AXES, THE LOCATION\n"; +print "OF THE TARGET IN THREE DIMENSIONAL RECTANGULAR COORDINATES,\n"; +print "THE APPROXIMATE NUMBER OF DEGREES FROM THE X AND Z\n"; +print "AXES, AND THE APPROXIMATE DISTANCE TO THE TARGET.\n"; +print "YOU WILL THEN PROCEEED TO SHOOT AT THE TARGET UNTIL IT IS\n"; +print "DESTROYED!\n\n"; +print "GOOD LUCK!!\n\n"; + +while (1) +{ + my $A = rand(1) * 2 * $Pi; + my $B = rand(1) * 2 * $Pi; + my $P1 = 100000 * rand(1) + rand(1); + my $X = sin($B) * cos($A) * $P1; + my $Y = sin($B) * sin($A) * $P1; + my $Z = cos($B) * $P1; + print "RADIANS FROM X AXIS = $A FROM Z AXIS = $B\n"; + print "TARGET SIGHTED: APPROXIMATE COORDINATES: X=$X Y=$Y Z=$Z\n"; + + while (1) + { + my $P3; + $R++; + + if ($R == 1) { $P3 = int($P1 * .05) * 20; } + elsif ($R == 2) { $P3 = int($P1 * .1) * 10; } + elsif ($R == 3) { $P3 = int($P1 * .5) * 2; } + elsif ($R == 4) { $P3 = int($P1); } + else { $P3 = $P1; } + + print " ESTIMATED DISTANCE: $P3\n\n"; + print "INPUT ANGLE DEVIATION FROM X, DEVIATION FROM Z, DISTANCE: "; + chomp(my $ans = lc(<>)); + exit(0) if ($ans eq "quit" || $ans eq "stop"); + + my ($A1, $B1, $P2) = split(/[,\s]+/, $ans); + print "\n"; + + if ($P2 >= 20) + { + $A1 /= $R1; + $B1 /= $R1; + print "RADIANS FROM X AXIS = $A1 FROM Z AXIS = $B1\n"; + my $X1 = $P2 * sin($B1) * cos($A1); + my $Y1 = $P2 * sin($B1) * sin($A1); + my $Z1 = $P2 * cos($B1); + my $D = (($X1 - $X) ** 2 + ($Y1 - $Y) ** 2 + ($Z1 - $Z) ** 2) ** (0.5); + + if ($D <= 20) + { + print "\n * * * HIT * * * TARGET IS NON-FUNCTIONAL\n"; + print "\nDISTANCE OF EXPLOSION FROM TARGET WAS $D KILOMETERS.\n"; + print "\nMISSION ACCOMPLISHED IN $R SHOTS.\n"; + last; + } + else + { + my $X2 = $X1 - $X; + my $Y2 = $Y1 - $Y; + my $Z2 = $Z1 - $Z; + + if ($X2 < 0) { print "SHOT BEHIND TARGET ", -$X2, " KILOMETERS.\n"; } + else { print "SHOT IN FRONT OF TARGET $X2 KILOMETERS.\n"; } + + if ($Y2 < 0) { print "SHOT TO RIGHT OF TARGET ", -$Y2, " KILOMETERS.\n"; } + else { print "SHOT TO LEFT OF TARGET $Y2 KILOMETERS.\n"; } + + if ($Z2 < 0) { print "SHOT BELOW TARGET ", -$Z2, " KILOMETERS.\n"; } + else { print "SHOT ABOVE TARGET $Z2 KILOMETERS.\n"; } + + print "APPROX POSITION OF EXPLOSION: X=$X1 Y=$Y1 Z=$Z1\n"; + print " DISTANCE FROM TARGET = $D\n\n\n"; + next; + } + } + else + { + print "YOU BLEW YOURSELF UP!!\n"; + last; + } + } + + $R = 0; + print "\n\n\n\n\nNEXT TARGET...\n\n"; +} diff --git a/92_Trap/rust/Cargo.toml b/92_Trap/rust/Cargo.toml new file mode 100644 index 00000000..ed59870f --- /dev/null +++ b/92_Trap/rust/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "rust" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rand="0.8.5" diff --git a/92_Trap/rust/README.md b/92_Trap/rust/README.md new file mode 100644 index 00000000..362bf922 --- /dev/null +++ b/92_Trap/rust/README.md @@ -0,0 +1,3 @@ +Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html) + +Conversion to [Rust](https://www.rust-lang.org/) by Uğur Küpeli [ugurkupeli](https://github.com/ugurkupeli) \ No newline at end of file diff --git a/92_Trap/rust/src/main.rs b/92_Trap/rust/src/main.rs new file mode 100644 index 00000000..5fc89876 --- /dev/null +++ b/92_Trap/rust/src/main.rs @@ -0,0 +1,90 @@ +use std::io::stdin; + +use rand::Rng; + +fn main() { + println!("\n\t\tTRAP"); + println!("CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n"); + + let max_guess = 6; + let max_number = 100; + + prompt_instructions(); + + loop { + let number = rand::thread_rng().gen_range(1..(max_number + 1)); + let mut guesses = 1u8; + + loop { + let (min, max) = prompt_numbers(guesses); + + if min == number && max == number { + println!("\nYou got it!!!"); + break; + } else if (min..=max).contains(&number) { + println!("You have trapped my number."); + } else if number < min { + println!("My number is smaller than your trap numbers."); + } else if number > max { + println!("My number is bigger than your trap numbers."); + } + + guesses += 1; + if guesses > max_guess { + println!("\nSorry, that was {max_guess} guesses. Number was {number}"); + break; + } + } + + println!("\nTry again."); + } +} + +fn prompt_instructions() { + println!("Instructions?\t"); + + let mut input = String::new(); + if let Ok(_) = stdin().read_line(&mut input) { + match input.to_uppercase().trim() { + "YES" | "Y" => { + println!("\nI am thinking of a number between 1 and 100"); + println!("Try to guess my number. On each guess,"); + println!("you are to enter 2 numbers, trying to trap"); + println!("my number between the two numbers. I will"); + println!("tell you if you have trapped my number, if my"); + println!("number is larger than your two numbers, or if"); + println!("my number is smaller than your two numbers."); + println!("If you want to guess one single number, type"); + println!("your guess for both your trap numbers."); + println!("You get 6 guesses to get my number."); + } + _ => (), + } + } +} + +fn prompt_numbers(guess: u8) -> (u8, u8) { + loop { + let mut nums: Vec = Vec::new(); + println!("\nGuess # {guess} ?"); + + let mut input = String::new(); + if let Ok(_) = stdin().read_line(&mut input) { + let input: Vec<&str> = input.trim().split(",").collect(); + + for string in input { + if let Ok(number) = string.parse::() { + nums.push(number); + } else { + break; + } + } + + if nums.len() == 2 { + if nums[0] <= nums[1] { + return (nums[0], nums[1]); + } + } + } + } +} diff --git a/HOW_TO_RUN_THE_GAMES.md b/HOW_TO_RUN_THE_GAMES.md index 688ee136..36061f16 100644 --- a/HOW_TO_RUN_THE_GAMES.md +++ b/HOW_TO_RUN_THE_GAMES.md @@ -23,9 +23,6 @@ Alternatively, for non-dotnet compatible translations, you will need [Visual Stu ## java -**TIP:** You can build all the java and kotlin games at once -using the instructions in the [buildJvm directory](buildJvm/README.md) - The Java translations can be run via the command line or from an IDE such as [Eclipse](https://www.eclipse.org/downloads/packages/release/kepler/sr1/eclipse-ide-java-developers) or [IntelliJ](https://www.jetbrains.com/idea/) To run from the command line, you will need a Java SDK (eg. [Oracle JDK](https://www.oracle.com/java/technologies/downloads/) or [Open JDK](https://openjdk.java.net/)). @@ -58,8 +55,14 @@ _Hint: Normally javascript files have a `*.js` extension. We are using `*.mjs` t ## kotlin -Use the directions in [buildJvm](buildJvm/README.md) to build for kotlin. You can also use those directions to -build java games. +Kotlin programs are compiled with the Kotlin compiler, and run with the java runtime, just like java programs. +In addition to the java runtime you will need the `kotlinc` compiler, which can be installed using [these instructions](https://kotlinlang.org/docs/command-line.html). + +1. Navigate to the corresponding directory. +1. Compile the program with `kotlinc`: + * eg. `kotlinc AceyDuceyGame.kt -include-runtime -d AceyDuceyGame.jar` +1. Run the compiled program with `java`: + * eg. `java -jar AceyDuceyGame.jar` ## pascal diff --git a/basic-computer-games-gradle b/basic-computer-games-gradle new file mode 160000 index 00000000..367112cc --- /dev/null +++ b/basic-computer-games-gradle @@ -0,0 +1 @@ +Subproject commit 367112cc067a3623bcca174e354ed671c73c8acb