diff --git a/projects/guessing-game/main.lux b/projects/guessing-game/main.lux new file mode 100644 index 0000000..c836f2f --- /dev/null +++ b/projects/guessing-game/main.lux @@ -0,0 +1,203 @@ +// Number Guessing Game +// The computer picks a random number, you try to guess it! + +// Game configuration using ADTs +type Difficulty = + | Easy // 1-50, unlimited guesses + | Medium // 1-100, 10 guesses + | Hard // 1-200, 7 guesses + +// GameConfig: minNum, maxNum, maxGuesses +type GameConfig = + | GameConfig(Int, Int, Option) + +// GameState: secret, guesses, config +type GameState = + | GameState(Int, Int, GameConfig) + +type GuessResult = + | TooLow + | TooHigh + | Correct + +// Config helpers +fn getMinNum(config: GameConfig): Int = + match config { GameConfig(min, _, _) => min } + +fn getMaxNum(config: GameConfig): Int = + match config { GameConfig(_, max, _) => max } + +fn getMaxGuesses(config: GameConfig): Option = + match config { GameConfig(_, _, maxG) => maxG } + +// State helpers +fn getSecret(state: GameState): Int = + match state { GameState(s, _, _) => s } + +fn getGuessCount(state: GameState): Int = + match state { GameState(_, g, _) => g } + +fn getConfig(state: GameState): GameConfig = + match state { GameState(_, _, c) => c } + +fn difficultyConfig(d: Difficulty): GameConfig = + match d { + Easy => GameConfig(1, 50, None), + Medium => GameConfig(1, 100, Some(10)), + Hard => GameConfig(1, 200, Some(7)) + } + +fn difficultyName(d: Difficulty): String = + match d { + Easy => "Easy (1-50, unlimited guesses)", + Medium => "Medium (1-100, 10 guesses)", + Hard => "Hard (1-200, 7 guesses)" + } + +// Core game logic (pure functions) +fn checkGuess(guess: Int, secret: Int): GuessResult = + if guess < secret then TooLow + else if guess > secret then TooHigh + else Correct + +fn isGameOver(state: GameState, result: GuessResult): Bool = + match result { + Correct => true, + _ => match getMaxGuesses(getConfig(state)) { + Some(max) => getGuessCount(state) >= max, + None => false + } + } + +fn guessesRemaining(state: GameState): Option = + match getMaxGuesses(getConfig(state)) { + Some(max) => Some(max - getGuessCount(state)), + None => None + } + +// Game effects +fn printWelcome(): Unit with {Console} = { + Console.print("") + Console.print("========================================") + Console.print(" NUMBER GUESSING GAME") + Console.print("========================================") + Console.print("") +} + +fn chooseDifficulty(): Difficulty with {Console} = { + Console.print("Choose difficulty:") + Console.print(" 1. " + difficultyName(Easy)) + Console.print(" 2. " + difficultyName(Medium)) + Console.print(" 3. " + difficultyName(Hard)) + Console.print("") + Console.print("Enter choice (1-3): ") + + let choice = Console.readInt() + match choice { + 1 => Easy, + 2 => Medium, + 3 => Hard, + _ => { + Console.print("Invalid choice, defaulting to Medium") + Medium + } + } +} + +fn initGame(difficulty: Difficulty): GameState with {Random} = { + let config = difficultyConfig(difficulty) + let secret = Random.int(getMinNum(config), getMaxNum(config)) + GameState(secret, 0, config) +} + +fn printGameStart(state: GameState): Unit with {Console} = { + let config = getConfig(state) + Console.print("") + Console.print("I'm thinking of a number between " + + toString(getMinNum(config)) + " and " + + toString(getMaxNum(config)) + "...") + match getMaxGuesses(config) { + Some(max) => Console.print("You have " + toString(max) + " guesses."), + None => Console.print("You have unlimited guesses.") + } + Console.print("") +} + +fn getGuess(state: GameState): Int with {Console} = { + match guessesRemaining(state) { + Some(remaining) => + Console.print("Guesses remaining: " + toString(remaining)), + None => () + } + Console.print("Enter your guess: ") + Console.readInt() +} + +fn printResult(result: GuessResult): Unit with {Console} = + match result { + TooLow => Console.print("Too low! Try higher."), + TooHigh => Console.print("Too high! Try lower."), + Correct => Console.print("") + } + +fn printVictory(state: GameState): Unit with {Console} = { + Console.print("========================================") + Console.print(" CONGRATULATIONS! You got it!") + Console.print(" The number was: " + toString(getSecret(state))) + Console.print(" Guesses used: " + toString(getGuessCount(state))) + Console.print("========================================") +} + +fn printDefeat(state: GameState): Unit with {Console} = { + Console.print("========================================") + Console.print(" GAME OVER - Out of guesses!") + Console.print(" The number was: " + toString(getSecret(state))) + Console.print("========================================") +} + +// Main game loop +fn gameLoop(state: GameState): GameState with {Console} = { + let guess = getGuess(state) + let result = checkGuess(guess, getSecret(state)) + let newState = GameState(getSecret(state), getGuessCount(state) + 1, getConfig(state)) + + printResult(result) + + if isGameOver(newState, result) then { + match result { + Correct => printVictory(newState), + _ => printDefeat(newState) + } + newState + } else { + gameLoop(newState) + } +} + +fn askPlayAgain(): Bool with {Console} = { + Console.print("") + Console.print("Play again? (1 = yes, 2 = no): ") + let choice = Console.readInt() + choice == 1 +} + +fn playGame(): Unit with {Console, Random} = { + let difficulty = chooseDifficulty() + let state = initGame(difficulty) + printGameStart(state) + let finalState = gameLoop(state) + () +} + +fn mainLoop(): Unit with {Console, Random} = { + playGame() + if askPlayAgain() then mainLoop() + else Console.print("Thanks for playing! Goodbye!") +} + +fn main(): Unit with {Console, Random} = { + printWelcome() + mainLoop() +} + +let output = run main() with {} diff --git a/src/interpreter.rs b/src/interpreter.rs index fd68167..f3eaaa8 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -2880,7 +2880,7 @@ impl Interpreter { Ok(Value::Unit) } } - ("Console", "read") => { + ("Console", "read") | ("Console", "readLine") => { let mut input = String::new(); std::io::stdin() .read_line(&mut input) @@ -2890,6 +2890,23 @@ impl Interpreter { })?; Ok(Value::String(input.trim().to_string())) } + ("Console", "readInt") => { + let mut input = String::new(); + std::io::stdin() + .read_line(&mut input) + .map_err(|e| RuntimeError { + message: format!("Failed to read input: {}", e), + span: None, + })?; + let trimmed = input.trim(); + match trimmed.parse::() { + Ok(n) => Ok(Value::Int(n)), + Err(_) => Err(RuntimeError { + message: format!("Invalid integer: '{}'", trimmed), + span: None, + }), + } + } ("Fail", "fail") => { let msg = request .args diff --git a/src/types.rs b/src/types.rs index 946906c..2bc9f1c 100644 --- a/src/types.rs +++ b/src/types.rs @@ -780,6 +780,16 @@ impl TypeEnv { params: Vec::new(), return_type: Type::String, }, + EffectOpDef { + name: "readLine".to_string(), + params: Vec::new(), + return_type: Type::String, + }, + EffectOpDef { + name: "readInt".to_string(), + params: Vec::new(), + return_type: Type::Int, + }, ], }, );