A-level Computing/AQA/Paper 1/Skeleton program/2025

This is for the 2025 AQA A-level Computer Science Specification (7517).

This is where suggestions can be made about what some of the questions might be and how we can solve them.

Please be respectful and do not vandalise the page, as this may affect students' preparation for exams!

Section C Predictions

The 2025 paper 1 will contain four questions worth 2 marks each.

Predictions:

  1. MaxNumber is used as a parameter in CheckValidNumber. there is no difference whether it is used or not, making it obsolete. maybe it will ask a question using about using Maxnumber since there is not yet a practical use for the variable.

Mark distribution comparison

Mark distribution for this year:

  • The 2025 paper 1 contains 4 questions: a 5 mark, an 8 mark, a 12 mark, and a 14 mark question(s).

Mark distribution for previous years:

  • The 2024 paper 1 contained 4 questions: a 5 mark, a 6 mark question, a 14 mark question and one 14 mark question(s).
  • The 2023 paper 1 contained 4 questions: a 5 mark, a 9 mark, a 10 mark question, and a 13 mark question(s).
  • The 2022 paper 1 contained 4 questions: a 5 mark, a 9 mark, an 11 mark, and a 13 mark question(s).
  • The 2021 paper 1 contained 4 questions: a 6 mark, an 8 mark, a 9 mark, and a 14 mark question(s).
  • The 2020 paper 1 contained 4 questions: a 6 mark, an 8 mark, an 11 mark and a 12 mark question(s).
  • The 2019 paper 1 contained 4 questions: a 5 mark, an 8 mark, a 9 mark, and a 13 mark question(s).
  • The 2018 paper 1 contained 5 questions: a 2 mark, a 5 mark, two 9 mark, and a 12 mark question(s).
  • The 2017 paper 1 contained 5 questions: a 5 mark question, three 6 mark questions, and one 12 mark question.

Please note the marks above include the screen capture(s), so the likely marks for the coding will be 2-3 marks lower.

Section D Predictions

Current questions are speculation by contributors to this page.

Fix the scoring bug whereby n targets cleared give you 2n-1 points instead of n points:

Python:

Instead of a single score decrementation happening in every iteration of PlayGame, we want three, on failure of each of the three validation (valid expression, numbers allowed, evaluates to a target):

def PlayGame(Targets, NumbersAllowed, TrainingGame, MaxTarget, MaxNumber):
    Score = 0
    GameOver = False
    while not GameOver:
        DisplayState(Targets, NumbersAllowed, Score)
        UserInput = input("Enter an expression: ")
        print()
        if CheckIfUserInputValid(UserInput):
            UserInputInRPN = ConvertToRPN(UserInput)
            if CheckNumbersUsedAreAllInNumbersAllowed(NumbersAllowed, UserInputInRPN, MaxNumber):
                IsTarget, Score = CheckIfUserInputEvaluationIsATarget(Targets, UserInputInRPN, Score)
                if IsTarget:
                    NumbersAllowed = RemoveNumbersUsed(UserInput, MaxNumber, NumbersAllowed)
                    NumbersAllowed = FillNumbers(NumbersAllowed, TrainingGame, MaxNumber)
                else:
                    Score -= 1
            else:
                Score -= 1
        else:
            Score -= 1
        if Targets[0] != -1:
            GameOver = True
        else:
            Targets = UpdateTargets(Targets, TrainingGame, MaxTarget)        
    print("Game over!")
    DisplayScore(Score)

We also want to change the score incrementation in CheckIfUserInputEvaluationIsATarget, so each target gives us one point, not two:

def CheckIfUserInputEvaluationIsATarget(Targets, UserInputInRPN, Score):
    UserInputEvaluation = EvaluateRPN(UserInputInRPN)
    UserInputEvaluationIsATarget = False
    if UserInputEvaluation != -1:
        for Count in range(0, len(Targets)):
            if Targets[Count] == UserInputEvaluation:
                Score += 1
                Targets[Count] = -1
                UserInputEvaluationIsATarget = True        
    return UserInputEvaluationIsATarget, Score

C#:

In the function CheckIfUserInputEvaluationIsATarget, each score increment upon popping a target should be set to an increment of one instead of two. In addition, if a target has been popped, the score should be incremented by one an extra time - this is to negate the score decrement by one once the function ends (in the PlayGame procedure).

static bool CheckIfUserInputEvaluationIsATarget(List<int> Targets, List<string> UserInputInRPN, ref int Score)
{
    int UserInputEvaluation = EvaluateRPN(UserInputInRPN);
    bool UserInputEvaluationIsATarget = false;
    if (UserInputEvaluation != -1)
    {
        for (int Count = 0; Count < Targets.Count; Count++)
        {
            if (Targets[Count] == UserInputEvaluation)
            {
                Score += 1; // code modified
                Targets[Count] = -1;
                UserInputEvaluationIsATarget = true;
            }
        }
    }
    if (UserInputEvaluationIsATarget) // code modified
    {
        Score++; // code modified
    }
    return UserInputEvaluationIsATarget;
}


Modify the program so that (1) the player can use each number more than once and (2) the game does not allow repeats within the 5 numbers given:

C#:

C# - DM - Riddlesdown

________________________________________________________________

In CheckNumbersUsedAreAllInNumbersAllowed you can remove the line Temp.Remove(Convert.ToInt32(Item)) around line 150

static bool CheckNumbersUsedAreAllInNumbersAllowed(List<int> NumbersAllowed, List<string> UserInputInRPN, int MaxNumber)
        {
            List<int> Temp = new List<int>();
            foreach (int Item in NumbersAllowed)
            {
                Temp.Add(Item);
            }
            foreach (string Item in UserInputInRPN)
            {
                if (CheckValidNumber(Item, MaxNumber))
                {
                    if (Temp.Contains(Convert.ToInt32(Item)))
                    {
                        // CHANGE START (line removed)
                        
                        // CHANGE END
                    }
                    else
                    {
                        return false;
                    }
                }
            }
            return true;
        }

C# KN - Riddlesdown

________________________________________________________________

In FillNumbers in the while (NumbersAllowed < 5) loop, you should add a ChosenNumber int variable and check it’s not already in the allowed numbers so there are no duplicates (near line 370):

static List<int> FillNumbers(List<int> NumbersAllowed, bool TrainingGame, int MaxNumber)
        {
            if (TrainingGame)
            {
                return new List<int> { 2, 3, 2, 8, 512 };
            }
            else
            {
                while (NumbersAllowed.Count < 5)
                {
                    // CHANGE START
                    int ChosenNumber = GetNumber(MaxNumber);
                    while (NumbersAllowed.Contains(ChosenNumber))
                    {
                        ChosenNumber = GetNumber(MaxNumber);
                    }

                    NumbersAllowed.Add(ChosenNumber);
                    // CHANGE END
                }
                return NumbersAllowed;
            }
        }

Python:

Edit FillNumbers() to add a selection (if) statement to ensure that the number returned from GetNumber() has not already been appended to the list NumbersAllowed.

def FillNumbers(NumbersAllowed, TrainingGame, MaxNumber):
    if TrainingGame:
        return [2, 3, 2, 8, 512]
    else:
        while len(NumbersAllowed) < 5:
            NewNumber = GetNumber(MaxNumber)
            if NewNumber not in NumbersAllowed:
                NumbersAllowed.append(NewNumber)
            else: continue
        return NumbersAllowed

Edit CheckNumbersUsedAreAllInNumbersAllowed() to ensure that numbers that the user has inputted are not removed from the Temp list, allowing numbers to be re-used.

def CheckNumbersUsedAreAllInNumbersAllowed(NumbersAllowed, UserInputInRPN, MaxNumber):
    Temp = []
    for Item in NumbersAllowed:
        Temp.append(Item)
    for Item in UserInputInRPN:
        if CheckValidNumber(Item, MaxNumber):
            if int(Item) in Temp:
                continue
            else:
                return False            
    return True


Modify the program so expressions with whitespace ( ) are accepted:

C#:


If you put spaces between values the program doesn't recognise it as a valid input and hence you lose points even for correct solutions. To fix the issue, modify the PlayGame method as follows:

Modify this:

UserInput = Console.ReadLine()

To this:

UserInput = Console.ReadLine().Replace(" ","");

Note that this is unlikely to come up because of the simplicity of the solution, being resolvable in half a line of code.

Python:

Create a function to remove spaces from the input.

def PlayGame(Targets, NumbersAllowed, TrainingGame, MaxTarget, MaxNumber): 
    Score = 0 
    GameOver = False
    while not GameOver: 
        DisplayState(Targets, NumbersAllowed, Score) 
        UserInput = input("Enter an expression: ") 
        print()
        UserInput = RemoveWhitespace(UserInput)
def RemoveWhitespace(UserInputWithWhitespace):
    return UserInputWithWhitespace.replace(" ","")

datb2

#whitespace at the start and end of a string can be removed with:
UserInput.strip()


Add exponentials (using ^ or similar):

C#:

Riddlesdown - Unknown

_________________________________________________________________________

static bool CheckIfUserInputValid(string UserInput)
{
    // CHANGE START
    return Regex.IsMatch(UserInput, @"^([0-9]+[\+\-\*\/\^])+[0-9]+$");
    // CHANGE END
}

Answer:


In  ConvertToRPN() add the ^ to the list of operators and give it the highest precedence

}}

Riddlesdown - Unknown 
static List<string> ConvertToRPN(string UserInput)
{
    int Position = 0;
    Dictionary<string, int> Precedence = new Dictionary<string, int>
    {
        // CHANGE START
        { "+", 2 }, { "-", 2 }, { "*", 4 }, { "/", 4 }, { "^", 5 }
        // CHANGE END
    };
    List<string> Operators = new List<string>();
In EvaluateRPN() add the check to see if the current user input contains the ^, and make it evaluate the exponential if it does
Riddlesdown - Unknown<br></br>
_________________________________________________________________________<br></br>

static int EvaluateRPN(List<string> UserInputInRPN)
{
    List<string> S = new List<string>();
    while (UserInputInRPN.Count > 0)
    {
        // CHANGE START
        while (!"+-*/^".Contains(UserInputInRPN[0]))
        // CHANGE END
        {
            S.Add(UserInputInRPN[0]);
            UserInputInRPN.RemoveAt(0);
        }
        double Num2 = Convert.ToDouble(S[S.Count - 1]);
        S.RemoveAt(S.Count - 1);
        double Num1 = Convert.ToDouble(S[S.Count - 1]);
        S.RemoveAt(S.Count - 1);
        double Result = 0;
        switch (UserInputInRPN[0])
        {
            case "+":
                Result = Num1 + Num2;
                break;
            case "-":
                Result = Num1 - Num2;
                break;
            case "*":
                Result = Num1 * Num2;
                break;
            case "/":
                Result = Num1 / Num2;
                break;
            // CHANGE START
            case "^":
                Result = Math.Pow(Num1, Num2);
                break;
            // CHANGE END
        }
        UserInputInRPN.RemoveAt(0);
        S.Add(Convert.ToString(Result));
    }

Python:


def ConvertToRPN(UserInput):
    Position = 0
    Precedence = {"+": 2, "-": 2, "*": 4, "/": 4, "^": 5} # Add exponent value to dictionary
    Operators = []
def EvaluateRPN(UserInputInRPN):
    S = []
    while len(UserInputInRPN) > 0:
        while UserInputInRPN[0] not in ["+", "-", "*", "/", "^"]: # Define ^ as a valid operator
            S.append(UserInputInRPN[0])
            UserInputInRPN.pop(0)        
        Num2 = float(S[-1])
        S.pop()
        Num1 = float(S[-1])
        S.pop()
        Result = 0.0
        if UserInputInRPN[0] == "+":
            Result = Num1 + Num2
        elif UserInputInRPN[0] == "-":
            Result = Num1 - Num2
        elif UserInputInRPN[0] == "*":
            Result = Num1 * Num2
        elif UserInputInRPN[0] == "/":
            Result = Num1 / Num2
        elif UserInputInRPN[0] == "^":
            Result = Num1 ** Num2
        UserInputInRPN.pop(0)
        S.append(str(Result))       
    if float(S[0]) - math.floor(float(S[0])) == 0.0:
        return math.floor(float(S[0]))
    else:
        return -1
def CheckIfUserInputValid(UserInput):
    if re.search("^([0-9]+[\\+\\-\\*\\/\\^])+[0-9]+$", UserInput) is not None: # Add the operator here to make sure its recognized when the RPN is searched, otherwise it wouldn't be identified correctly.
        return True
    else:
        return False
def ConvertToRPN(UserInput): # Need to adjust for "^" right associativity
    Position = 0
    Precedence = {"+": 2, "-": 2, "*": 4, "/": 4, "^": 5}
    Operators = []
    Operand, Position = GetNumberFromUserInput(UserInput, Position)
    UserInputInRPN = []
    UserInputInRPN.append(str(Operand))
    Operators.append(UserInput[Position - 1])
    while Position < len(UserInput):
        Operand, Position = GetNumberFromUserInput(UserInput, Position)
        UserInputInRPN.append(str(Operand))
        if Position < len(UserInput):
            CurrentOperator = UserInput[Position - 1]
            while len(Operators) > 0 and Precedence[Operators[-1]] > Precedence[CurrentOperator]:
                UserInputInRPN.append(Operators[-1])
                Operators.pop()                
            if len(Operators) > 0 and Precedence[Operators[-1]] == Precedence[CurrentOperator]:
                if CurrentOperator != "^": # Just add this line, "^" does not care if it is next to an operator of same precedence
         
                    UserInputInRPN.append(Operators[-1])
                    Operators.pop()
            Operators.append(CurrentOperator)
        else:
            while len(Operators) > 0:
                UserInputInRPN.append(Operators[-1])
                Operators.pop()
    return UserInputInRPN



Allow User to Add Brackets

C#:

C# - AC - Coombe Wood

________________________________________________________________

while (Position < UserInput.Length)
{
               char CurrentChar = UserInput[Position];
               if (char.IsDigit(CurrentChar))
               {
                   string Number = "";
                   while (Position < UserInput.Length && char.IsDigit(UserInput[Position]))
                   {
                       Number += UserInput[Position];
                       Position++;
                   }
                   UserInputInRPN.Add(Number);
               }
               else if (CurrentChar == '(')
               {
                   Operators.Add("(");
                   Position++;
               }
               else if (CurrentChar == ')')
               {
                   while (Operators.Count > 0 && Operators[Operators.Count - 1] != "(")
                   {
                       UserInputInRPN.Add(Operators[Operators.Count - 1]);
                       Operators.RemoveAt(Operators.Count - 1);
                   }
                   Operators.RemoveAt(Operators.Count - 1);
                   Position++;
               }
               else if ("+-*/^".Contains(CurrentChar))
               {
                   string CurrentOperator = CurrentChar.ToString();
                   while (Operators.Count > 0 && Operators[Operators.Count - 1] != "(" && Precedence[Operators[Operators.Count - 1]] >= Precedence[CurrentOperator])
                   {
                       UserInputInRPN.Add(Operators[Operators.Count - 1]);
                       Operators.RemoveAt(Operators.Count - 1);
                   }
                   Operators.Add(CurrentOperator);
                   Position++;
               }
           }

Python:


Python - evokekw

________________________________________________________________

from collections import deque

# New function ConvertToRPNWithBrackets
def ConvertToRPNWithBrackets(UserInput):
    Precedence = {"+": 2, "-": 2, "*": 4, "/": 4, "^": 6}
    Operators = []
    Operands = []    
    i = 0
    # Iterate through the expression
    while i < len(UserInput):
        Token = UserInput[i]
        # If the token is a digit we check too see if it has mutltiple digits
        if Token.isdigit():
            Number, NewPos = GetNumberFromUserInput(UserInput, i)
            # append the number to queue
            Operands.append(str(Number))
            if i == len(UserInput) - 1:
                break
            else:
                i = NewPos - 1
                continue
        # If the token is an open bracket we push it to the stack
        elif Token == "(":
            Operators.append(Token)
        # If the token is a closing bracket then
        elif Token == ")":
            # We pop off all the operators and enqueue them until we get to an opening bracket
            while Operators and Operators[-1] != "(":
                Operands.append(Operators.pop())
            if Operators and Operators[-1] == "(":
                # We pop the opening bracket
                Operators.pop()
        # If the token is an operator 
        elif Token in Precedence:
            # If the precedence of the operator is less than the precedence of the operator on top of the stack we pop and enqueue 
            while Operators and Operators[-1] != "(" and Precedence[Token] < Precedence[Operators[-1]]:
                Operands.append(Operators.pop())
            Operators.append(Token)
        i += 1
    # Make sure to empty stack after all tokens have been computed
    while Operators:
        Operands.append(Operators.pop())
    return list(Operands)


Allow User to Add Brackets (Alternative Solution)

C#:

C# - Conor Carton

________________________________________________________________

//new method ConvertToRPNWithBrackets
static List<string> ConvertToRPNWithBrackets(string UserInput)
        {
            int Position = 0;
            Dictionary<string, int> Precedence = new Dictionary<string, int>
            {
                { "+", 2 }, { "-", 2 }, { "*", 4 }, { "/", 4 }
            };
            List<string> Operators = new List<string>();
            List<string> UserInputInRPN = new List<string>();
            while (Position < UserInput.Length)
            {
                //handle numbers
                if (char.IsDigit(UserInput[Position]))
                {
                    string number = "";
                    while (Position < UserInput.Length && char.IsDigit(UserInput[Position]))
                    {
                        number += Convert.ToString(UserInput[Position]);
                        Position++;
                    }
                    UserInputInRPN.Add(number);
                }

                //handle open bracket
                else if (UserInput[Position] == '(')
                {
                    Operators.Add("(");
                    Position++;
                }

                //handle close bracket
                else if (UserInput[Position] == ')')
                {
                    while (Operators[Operators.Count - 1] != "(" && Operators.Count > 0)
                    {
                        UserInputInRPN.Add(Operators[Operators.Count - 1]);
                        Operators.RemoveAt(Operators.Count - 1);
                    }
                    Operators.RemoveAt(Operators.Count - 1);
                    Position++;
                }

                //handle operators
                else if ("+/*-".Contains(UserInput[Position]) )
                {
                    string CurrentOperator =  Convert.ToString(UserInput[Position]);
                    while (Operators.Count > 0 && Operators[Operators.Count - 1] != "(" && Precedence[Operators[Operators.Count - 1]] >= Precedence[CurrentOperator])
                    {
                        UserInputInRPN.Add(Operators[Operators.Count - 1]);
                        Operators.RemoveAt(Operators.Count - 1);
                    }
                    Operators.Add(CurrentOperator);
                    Position++;
                }   
            }

            //add remaining items to the queue
            while (Operators.Count > 0)
            {
                UserInputInRPN.Add(Operators[Operators.Count - 1]);
                Operators.RemoveAt(Operators.Count - 1);
            }
            return UserInputInRPN;

        }

//change to PlayGame()

UserInputInRPN = ConvertToRPNWithBrackets(UserInput);

//change in RemoveNumbersUsed()

 List<string> UserInputInRPN = ConvertToRPNWithBrackets(UserInput);

//change to CheckIfUserInputValid()

static bool CheckIfUserInputValid(string UserInput)
        {
            return Regex.IsMatch(UserInput, @"^(\(*[0-9]+\)*[\+\-\*\/])+\(*[0-9]+\)*$");
        }

VB:

VB - Daniel Dovey

________________________________________________________________

' new method ConvertToRPNWithBrackets
Function ConvertToRPNWithBrackets(UserInput As String) As List(Of String)
    Dim Position As Integer = 0
    Dim Precedence As New Dictionary(Of String, Integer) From {{"+", 2}, {"-", 2}, {"*", 4}, {"/", 4}}
    Dim Operators As New List(Of String)
    Dim UserInputInRPN As New List(Of String)

    While Position < UserInput.Length
        If Char.IsDigit(UserInput(Position)) Then
            Dim Number As String = ""
            While Position < UserInput.Length AndAlso Char.IsDigit(UserInput(Position))
                Number &= UserInput(Position)
                Position += 1
            End While
            UserInputInRPN.Add(Number)

        ElseIf UserInput(Position) = "(" Then
            Operators.Add("(")
            Position += 1

        ElseIf UserInput(Position) = ")" Then
            While Operators.Count > 0 AndAlso Operators(Operators.Count - 1) <> "("
                UserInputInRPN.Add(Operators(Operators.Count - 1))
                Operators.RemoveAt(Operators.Count - 1)
            End While
            Position += 1
            Operators.RemoveAt(Operators.Count - 1)

        ElseIf "+-*/".Contains(UserInput(Position)) Then
            Dim CurrentOperator As String = UserInput(Position)
            While Operators.Count > 0 AndAlso Operators(Operators.Count - 1) <> "(" AndAlso
                Precedence(Operators(Operators.Count - 1)) >= Precedence(CurrentOperator)

                UserInputInRPN.Add(Operators(Operators.Count - 1))
                Operators.RemoveAt(Operators.Count - 1)
            End While
            Operators.Add(CurrentOperator)
            Position += 1
        End If
    End While

    While Operators.Count > 0
        UserInputInRPN.Add(Operators(Operators.Count - 1))
        Operators.RemoveAt(Operators.Count - 1)
    End While

    Return UserInputInRPN
End Function

' change to PlayGame()

UserInputInRPN = ConvertToRPNWithBrackets(UserInput);

' change in RemoveNumbersUsed()

Dim UserInputInRPN As List(Of String) = ConvertToRPNWithBrackets(UserInput)

' change to CheckIfUserInputValid()

Function CheckIfUserInputValid(UserInput As String) As Boolean
    Return Regex.IsMatch(UserInput, "^(\(*[0-9]+\)*[\+\-\*\/])+\(*[0-9]+\)*$")
End Function


Modify the program to accept input in Postfix (Reverse Polish Notation) instead of Infix

Python:

First, ask the user to input a comma-separated list. Instead of ConvertToRPN(), we call a new SplitIntoRPN() function which splits the user's input into a list object.

def PlayGame(Targets, NumbersAllowed, TrainingGame, MaxTarget, MaxNumber): 
    Score = 0 
    GameOver = False 
    while not GameOver: 
        DisplayState(Targets, NumbersAllowed, Score) 
        UserInput = input("Enter an expression in RPN separated by commas e.g. 1,1,+  : ") 
        print()
        UserInputInRPN, IsValidRPN = SplitIntoRPN(UserInput)
        if not IsValidRPN:
            print("Invalid RPN input")
        else:
            if CheckNumbersUsedAreAllInNumbersAllowed(NumbersAllowed, UserInputInRPN, MaxNumber): 
                IsTarget, Score = CheckIfUserInputEvaluationIsATarget(Targets, UserInputInRPN, Score)
                if IsTarget: 
                    NumbersAllowed = RemoveNumbersUsed(UserInput, MaxNumber, NumbersAllowed)
                    NumbersAllowed = FillNumbers(NumbersAllowed, TrainingGame, MaxNumber) 
        Score -= 1 
        if Targets[0] != -1: 
            GameOver = True 
        else: 
            Targets = UpdateTargets(Targets, TrainingGame, MaxTarget)         
    print("Game over!") 
    DisplayScore(Score)

This function splits the user's input string, e.g. "10,5,/" into a list object, e.g. ["10", "5", "/"]. It also checks that at least 3 items have been entered, and also that the first character is a digit. The function returns the list and a boolean indicating whether or not the input looks valid.

def SplitIntoRPN(UserInput): 
    #Extra RPN validation could be added here
    if len(UserInput) < 3 or not UserInput[0].isdigit():
        return [], False
    Result = UserInput.split(",")
    return Result, True

Also update RemoveNumbersUsed() to replace the ConvertToRPN() function with the new SplitIntoRPN() function.

def RemoveNumbersUsed(UserInput, MaxNumber, NumbersAllowed): 
    UserInputInRPN, IsValidRPN = SplitIntoRPN(UserInput) 
    for Item in UserInputInRPN: 
        if CheckValidNumber(Item, MaxNumber): 
            if int(Item) in NumbersAllowed: 
                NumbersAllowed.remove(int(Item)) 
    return NumbersAllowed

It may help to display the evaluated user input:

def CheckIfUserInputEvaluationIsATarget(Targets, UserInputInRPN, Score): 
    UserInputEvaluation = EvaluateRPN(UserInputInRPN)
    print("Your input evaluated to: " + str(UserInputEvaluation))
    UserInputEvaluationIsATarget = False 
    if UserInputEvaluation != -1: 
        for Count in range(0, len(Targets)): 
            if Targets[Count] == UserInputEvaluation: 
                Score += 2 
                Targets[Count] = -1 
                UserInputEvaluationIsATarget = True         
    return UserInputEvaluationIsATarget, Score

Typical game output:

Enter y to play the training game, anything else to play a random game: 

| | | | | |3|9|12|3|36|7|26|42|3|17|12|29|30|10|44|

Numbers available: 8  6  10  7  3  

Current score: 0


Enter an expression as RPN separated by commas e.g. 1,1,+  : 10,7,-

Your input evaluated to: 3
| | | | | |9|12| |36|7|26|42| |17|12|29|30|10|44|48|

Numbers available: 8  6  3  6  7  

Current score: 5


Enter an expression as RPN separated by commas e.g. 1,1,+  :

datb2


Allow the program to not stop after 'Game Over!' with no way for the user to exit.

Python:

def PlayGame(Targets, NumbersAllowed, TrainingGame, MaxTarget, MaxNumber):
    Score = 0
    GameOver = False
    while not GameOver:
        DisplayState(Targets, NumbersAllowed, Score)
        UserInput = input("Enter an expression (+, -, *, /, ^) : ")
        print()
        if CheckIfUserInputValid(UserInput):
            UserInputInRPN = ConvertToRPN(UserInput)
            if CheckNumbersUsedAreAllInNumbersAllowed(NumbersAllowed, UserInputInRPN, MaxNumber):
                IsTarget, Score = CheckIfUserInputEvaluationIsATarget(Targets, UserInputInRPN, Score)
                if IsTarget:
                    NumbersAllowed = RemoveNumbersUsed(UserInput, MaxNumber, NumbersAllowed)
                    NumbersAllowed = FillNumbers(NumbersAllowed, TrainingGame, MaxNumber)
        Score -= 1
        if Targets[0] != -1:
            GameOver = True
        else:
            Targets = UpdateTargets(Targets, TrainingGame, MaxTarget)        
    print("Game over!")
    DisplayScore(Score)

=== '''Practice game - only generates 119 as new targets''' ===
{{CPTAnswerTab|Python}}
The way that it is currently appending new targets to the list is just by re-appending the value on the end of the original list
<syntaxhighlight lang = "python" line="1" start = "1">
        Targets = [-1, -1, -1, -1, -1, 23, 9, 140, 82, 121, 34, 45, 68, 75, 34, 23, 119, 43, 23, 119]

So by adding the PredefinedTargets list to the program, it will randomly pick one of those targets, rather than re-appending 119.

def UpdateTargets(Targets, TrainingGame, MaxTarget):
    for Count in range (0, len(Targets) - 1):
        Targets[Count] = Targets[Count + 1]
    Targets.pop()
    if TrainingGame:
        PredefinedTargets = [23, 9, 140, 82, 121, 34, 45, 68, 75, 34, 23, 119, 43] # Updated list that is randomly picked from by using the random.choice function from the random library
        Targets.append(random.choice(PredefinedTargets))
    else:
        Targets.append(GetTarget(MaxTarget))
    return Targets

C#:

Coombe Wood - AA

if (Targets[0] != -1) // If any target is still active, the game continues {

   Console.WriteLine("Do you want to play again or continue? If yes input 'y'");
   string PlayAgain = Console.ReadLine();
   if (PlayAgain == "y")
   {
       Console.WriteLine("To continue press 'c' or to play again press any other key");
       string Continue = Console.ReadLine();
       if (Continue == "c")
       {
           GameOver = false;
       }
       else
       {
           Restart();
           static void Restart()
           {
               // Get the path to the current executable
               string exePath = Process.GetCurrentProcess().MainModule.FileName;
               // Start a new instance of the application
               Process.Start(exePath);
               // Exit the current instance
               Environment.Exit(0);
           }
       }
   }
   else
   {
       Console.WriteLine("Do you want to stop playing game? If yes type 'y'.");
       string Exit = Console.ReadLine();
       if (Exit == "y")
       {
           GameOver = true;
       }
   }
}


Allow the user to quit the game

C#:

Modify the program to allow inputs which exit the program.

Add the line if (UserInput.ToLower() == "exit") { Environment.Exit(0); } to the PlayGame method as shown:

if (UserInput.ToLower() == "exit") { Environment.Exit(0); }
if (CheckIfUserInputValid(UserInput))
{
    UserInputInRPN = ConvertToRPN(UserInput);
    if (CheckNumbersUsedAreAllInNumbersAllowed(NumbersAllowed, UserInputInRPN, MaxNumber))
    {
        if (CheckIfUserInputEvaluationIsATarget(Targets, UserInputInRPN, ref Score))
        {
            RemoveNumbersUsed(UserInput, MaxNumber, NumbersAllowed);
            NumbersAllowed = FillNumbers(NumbersAllowed, TrainingGame, MaxNumber);
        }
    }
}

Python:

If the user types q during the game it will end. The return statement causes the PlayGame function to exit.

def PlayGame(Targets, NumbersAllowed, TrainingGame, MaxTarget, MaxNumber): 
    Score = 0 
    GameOver = False
    print("Enter q to quit")
    print()
    while not GameOver: 
        DisplayState(Targets, NumbersAllowed, Score) 
        UserInput = input("Enter an expression: ") 
        print()
        if UserInput == "q":
            print("Quitting...")
            DisplayScore(Score)
            return
        if CheckIfUserInputValid(UserInput): 
            UserInputInRPN = ConvertToRPN(UserInput) 
            if CheckNumbersUsedAreAllInNumbersAllowed(NumbersAllowed, UserInputInRPN, MaxNumber): 
                IsTarget, Score = CheckIfUserInputEvaluationIsATarget(Targets, UserInputInRPN, Score) 
                if IsTarget: 
                    NumbersAllowed = RemoveNumbersUsed(UserInput, MaxNumber, NumbersAllowed) 
                    NumbersAllowed = FillNumbers(NumbersAllowed, TrainingGame, MaxNumber) 
        Score -= 1 
        if Targets[0] != -1: 
            GameOver = True 
        else: 
            Targets = UpdateTargets(Targets, TrainingGame, MaxTarget)         
    print("Game over!") 
    DisplayScore(Score)

datb2


Add the ability to clear any target

C#:

C# - AC - Coombe Wood

________________________________________________________________

Console.WriteLine("Do you want to remove a target?");
               UserInput1 = Console.ReadLine().ToUpper();
               if (UserInput1 == "YES")
               {
                   if (Score >= 10)
                   {
                       Console.WriteLine("What number?");
                       UserInput3 = Console.Read();
                       Score = Score - 10;


                   }
                   else
                   {
                       Console.WriteLine("You do not have enough points");
                       Console.ReadKey();
                   }
               }
               else if (UserInput1 == "NO")
               {
                   Console.WriteLine("You selected NO");
               }
               Score--;
               if (Targets[0] != -1)
               {
                   GameOver = true;
               }
               else
               {
                   UpdateTargets(Targets, TrainingGame, MaxTarget);

C# - KH- Riddlesdown

_____________________________________________________________________________


  static bool RemoveTarget(List<int> Targets, string Userinput)
        {
            bool removed = false;
            int targetremove = int.Parse(Userinput);
            for (int Count = 0; Count < Targets.Count - 1; Count++)
            {
                if(targetremove == Targets[Count])
                {
                    removed = true;
                    Targets.RemoveAt(Count);
                }

            }
            if (!removed)
            {
                Console.WriteLine("invalid target");
                return (false);
            }
            return (true);
        }




 while (!GameOver)
            {
                DisplayState(Targets, NumbersAllowed, Score);
                Console.Write("Enter an expression: ");
                UserInput = Console.ReadLine();
                Console.WriteLine(UserInput);
                Console.ReadKey();
                Console.WriteLine();
                if(UserInput.ToUpper() == "R")
                {
                    Console.WriteLine("Enter the target");
                    UserInput = Console.ReadLine();
                    if(RemoveTarget(Targets, UserInput))
                    {
                        Score -= 5;
                    }
                }
                else
                {
                    if (CheckIfUserInputValid(UserInput))
                    {
                        UserInputInRPN = ConvertToRPN(UserInput);
                        if (CheckNumbersUsedAreAllInNumbersAllowed(NumbersAllowed, UserInputInRPN, MaxNumber))
                        {
                            if (CheckIfUserInputEvaluationIsATarget(Targets, UserInputInRPN, ref Score))
                            {
                                RemoveNumbersUsed(UserInput, MaxNumber, NumbersAllowed);
                                NumbersAllowed = FillNumbers(NumbersAllowed, TrainingGame, MaxNumber);
                            }
                        }
                    }
                    Score--;
                }
              
                if (Targets[0] != -1)
                {
                    GameOver = true;
                }
                else
                {
                    UpdateTargets(Targets, TrainingGame, MaxTarget);
                }
            }
            Console.WriteLine("Game over!");
            DisplayScore(Score);
        }

Python:

Call a new ClearTarget() function when the player enters c.

def PlayGame(Targets, NumbersAllowed, TrainingGame, MaxTarget, MaxNumber): 
    Score = 0 
    GameOver = False
    print("Enter c to clear any target (costs 10 points)")
    print()
    while not GameOver: 
        DisplayState(Targets, NumbersAllowed, Score) 
        UserInput = input("Enter an expression: ")
        if UserInput.lower().strip() == "c":
                Targets, Score = ClearTarget(Targets, Score)
                continue
        print()

The new function:

def ClearTarget(Targets, Score):
    InputTargetString = input("Enter target to clear: ")
    try:
        InputTarget = int(InputTargetString)
    except:
        print(InputTargetString + " is not a number")
        return Targets, Score
    if (InputTarget not in Targets):
        print(InputTargetString + " is not a target")
        return Targets, Score
    #Replace the first matching element with -1
    Targets[Targets.index(InputTarget)] = -1
    Score -= 10
    print(InputTargetString + " has been cleared")
    print()
    return Targets, Score

Typical game output:

Enter y to play the training game, anything else to play a random game: 

Enter c to clear any target (costs 10 points)

| | | | | |19|29|38|39|35|34|16|7|19|16|40|37|10|7|49|

Numbers available: 8  3  8  8  10  

Current score: 0

Enter an expression: c
Enter target to clear: 19
19 has been cleared

| | | | | | |29|38|39|35|34|16|7|19|16|40|37|10|7|49|

Numbers available: 8  3  8  8  10  

Current score: -10

Enter an expression:

datb2


Allowed numbers don’t have any duplicate numbers, and can be used multiple times

C#:

C# - KH- Riddlesdown

________________________________________________________________

In CheckNumbersUsedAreAllInNumbersAllowed you can remove the line Temp.Remove(Convert.ToInt32(Item)) around line 150


static bool CheckNumbersUsedAreAllInNumbersAllowed(List<int> NumbersAllowed, List<string> UserInputInRPN, int MaxNumber)
        {
            List<int> Temp = new List<int>();
            foreach (int Item in NumbersAllowed)
            {
                Temp.Add(Item);
            }
            foreach (string Item in UserInputInRPN)
            {
                if (CheckValidNumber(Item, MaxNumber))
                {
                    if (Temp.Contains(Convert.ToInt32(Item)))
                    {
                        // CHANGE START (line removed)
                        
                        // CHANGE END
                    }
                    else
                    {
                        return false;
                    }
                }
            }
            return true;
        }

In FillNumbers in the while (NumbersAllowed < 5) loop, you should add a ChosenNumber int variable and check it’s not already in the allowed numbers so there are no duplicates (near line 370):

static List<int> FillNumbers(List<int> NumbersAllowed, bool TrainingGame, int MaxNumber)
        {
            if (TrainingGame)
            {
                return new List<int> { 2, 3, 2, 8, 512 };
            }
            else
            {
                while (NumbersAllowed.Count < 5)
                {
                    // CHANGE START
                    int ChosenNumber = GetNumber(MaxNumber);
                    while (NumbersAllowed.Contains(ChosenNumber))
                    {
                        ChosenNumber = GetNumber(MaxNumber);
                    }

                    NumbersAllowed.Add(ChosenNumber);
                    // CHANGE END
                }
                return NumbersAllowed;
            }
        }


Update program so expressions with whitespace are validated

C#:

C# - KH- Riddlesdown

________________________________________________________________

At the moment if you put spaces between your numbers or operators it doesn't work so you will have to fix that

Put RemoveSpaces() under user input.


 static string RemoveSpaces(string UserInput)
        {
            char[] temp = new char[UserInput.Length];
            string bufferstring = "";
            bool isSpaces = true;

            

            for (int i = 0; i < UserInput.Length; i++)
            {
                temp[i] = UserInput[i];
            }

            while (isSpaces)
            {
                int spaceCounter = 0;
                for (int i = 0; i < temp.Length-1 ; i++)
                {
                    if(temp[i]==' ')
                    {   
                        
                        spaceCounter++;
                        temp = shiftChars(temp, i);
                        
                    }
                }
                if (spaceCounter == 0)
                {
                    isSpaces = false;
                }
                else
                {
                    temp[(temp.Length - 1)] = '¬';
                }
                
            }
            for (int i = 0; i < temp.Length; i++)
            {
                if(temp[i] != ' ' && temp[i] != '¬')
                {
                    bufferstring += temp[i];
                }
            }
            
            return (bufferstring);
        }
        static char[] shiftChars(char[] Input, int startInt)
        {
            for (int i = startInt; i < Input.Length; i++)
            {
                if(i != Input.Length - 1)
                {
                    Input[i] = Input[i + 1];
                }
                else
                {
                    Input[i] = ' ';
                }
            }
            return (Input);
        }

A shorter way

static string RemoveSpaces2(string UserInput)
{
    string newString = "";
    foreach (char c in UserInput)
    {
        if (c != ' ')
        {
            newString += c;
        }
    }

    return newString;
}

Also don’t forget to add a call to it around line 58

static void PlayGame(List<int> Targets, List<int> NumbersAllowed, bool TrainingGame, int MaxTarget, int MaxNumber)
{
    ...
    while (!GameOver)
    {
        DisplayState(Targets, NumbersAllowed, Score);
        Console.Write("Enter an expression: ");
        UserInput = Console.ReadLine();
        // Add remove spaces func here
        UserInput = RemoveSpaces2(UserInput);
        Console.WriteLine();
        ...


PS
__________________________________________________________________

Alternative shorter solution:

        
static bool CheckIfUserInputValid(string UserInput)
        {
            string[] S = UserInput.Split(' ');
            string UserInputWithoutSpaces = "";
            foreach(string s in S)
            {
                UserInputWithoutSpaces += s;
            }
            return Regex.IsMatch(UserInputWithoutSpaces, @"^([0-9]+[\+\-\*\/])+[0-9]+$");
        }

Python:

Python - JR - Comberton

________________________________________________________________

To remove all instances of a substring from a string, we can pass the string through a filter() function. The filter function takes 2 inputs - the first is a function, and the second is the iterable to operate on. An iterable is just any object that can return its elements one-at-a-time; it's just a fancy way of saying any object that can be put into a for loop. For example, a range object (as in, for i in range()) is an iterable! It returns each number from its start value to its stop value, one at a time, on each iteration of the for loop. Lists, strings, and dictionaries (including just the keys or values) are good examples of iterables.

All elements in the iterable are passed into the function individually - if the function returns false, then that element is removed from the iterable. Else, the element is maintained in the iterable (just as you'd expect a filter in real-life to operate).

 filter(lambda x: x != ' ', input("Enter an expression: ")

If we were to expand this out, this code is a more concise way of writing the following:

UserInput = input("Enter an expression: ")
UserInputWithSpacesRemoved = ""
for char in UserInput:
     if char != ' ':
          UserInputWithSpacesRemoved += char
UserInput = UserInputWithSpacesRemoved

Both do the same job, but the former is much easier to read, understand, and modify if needed. Plus, having temporary variables like UserInputWithSpacesRemoved should be a warning sign that there's probably an easier way to do the thing you're doing.

From here, all that needs to be done is to convert the filter object that's returned back into a string. Nicely, this filter object is an iterable, so this can be done by joining the elements together using "".join()

 "".join(filter(lambda x: x != ' ', input("Enter an expression: "))))

This pattern of using a higher-order function like filter(), map(), or reduce(), then casting to a string using "".join(), is a good one to learn.

Here is the resultant modification in context:

def PlayGame(Targets, NumbersAllowed, TrainingGame, MaxTarget, MaxNumber):
    Score = 0
    GameOver = False
    while not GameOver:
        DisplayState(Targets, NumbersAllowed, Score)
        # Just remove the whitespaces here, so that input string collapses to just expression
        UserInput = "".join(filter(lambda x: x != ' ', input("Enter an expression: "))) #
        print()
        if CheckIfUserInputValid(UserInput):
            UserInputInRPN = ConvertToRPN(UserInput)
            if CheckNumbersUsedAreAllInNumbersAllowed(NumbersAllowed, UserInputInRPN, MaxNumber):
                IsTarget, Score = CheckIfUserInputEvaluationIsATarget(Targets, UserInputInRPN, Score)
                if IsTarget:
                    NumbersAllowed = RemoveNumbersUsed(UserInput, MaxNumber, NumbersAllowed)
                    NumbersAllowed = FillNumbers(NumbersAllowed, TrainingGame, MaxNumber)
        Score -= 1
        if Targets[0] != -1:
            GameOver = True
        else:
            Targets = UpdateTargets(Targets, TrainingGame, MaxTarget)        
    print("Game over!")
    DisplayScore(Score)


Implement a hard mode where the same target can't appear twice

C#:

C# - Riddlesdown

________________________________________________________________

static void UpdateTargets(List<int> Targets, bool TrainingGame, int MaxTarget)
{
    for (int Count = 0; Count < Targets.Count - 1; Count++)
    {
        Targets[Count] = Targets[Count + 1];
    }
    Targets.RemoveAt(Targets.Count - 1);
    if (TrainingGame)
    {
        Targets.Add(Targets[Targets.Count - 1]);
    }
    else
    {
        // CHANGE START
        int NewTarget = GetTarget(MaxTarget);
        while (Targets.Contains(NewTarget))
        {
            NewTarget = GetTarget(MaxTarget);
        }

        Targets.Add(NewTarget);
        // CHANGE END
    }
}

Also, at line 355 update CreateTargets:

static List<int> CreateTargets(int SizeOfTargets, int MaxTarget)
{
    List<int> Targets = new List<int>();
    for (int Count = 1; Count <= 5; Count++)
    {
        Targets.Add(-1);
    }
    for (int Count = 1; Count <= SizeOfTargets - 5; Count++)
    {
        // CHANGE START
        int NewTarget = GetTarget(MaxTarget);
        while (Targets.Contains(NewTarget))
        {
            NewTarget = GetTarget(MaxTarget);
        }

        Targets.Add(NewTarget);
        // CHANGE END
    }
    return Targets;
}

Python:

Python - JR - Comberton

________________________________________________________________

There are lots of ways to do this, but the key idea is to ask the user if they want to play in hard mode, and then update GetTarget to accommodate hard mode.

The modification to Main is pretty self explanatory.

def Main():
    NumbersAllowed = []
    Targets = []
    MaxNumberOfTargets = 20
    MaxTarget = 0
    MaxNumber = 0
    TrainingGame = False
    Choice = input("Enter y to play the training game, anything else to play a random game: ").lower()
    HardModeChoice = input("Enter y to play on hard mode: ").lower()
    HardMode = True if HardModeChoice == 'y' else False
    print()
    if Choice == "y":
.....

The GetTarget uses a guard clause at the start to return early if the user hasn't selected hard mode. This makes the module a bit easier to read, as both cases are clearly separated. A good rule of thumb is to deal with the edge cases first, so you can focus on the general case uninterrupted.

def GetTarget(Targets, MaxTarget, HardMode):
    if not HardMode:
        return random.randint(1, MaxTarget)

   while True:
            newTarget = random.randint(1, MaxTarget)
            if newTarget not in Targets:
                return newTarget
Only other thing that needs to be done here is to feed HardMode into the GetTarget subroutine on both UpdateTargets and CreateTargets - which is just a case of passing it in as a parameter to subroutines until you stop getting error messages.


Once a target is cleared, prevent it from being generated again

C#:

C# - Riddlesdown

________________________________________________________________


internal class Program
    {
        static Random RGen = new Random();
        // CHANGE START
        static List<int> TargetsUsed = new List<int>();
        // CHANGE END
        ...

Next create these new functions at the bottom:

// CHANGE START
static bool CheckIfEveryPossibleTargetHasBeenUsed(int MaxNumber)
{
    if (TargetsUsed.Count >= MaxNumber)
    {
        return true;
    }
    else
    {
        return false;
    }
}

static bool CheckAllTargetsCleared(List<int> Targets)
{
    bool allCleared = true;
    foreach (int i in Targets)
    {
        if (i != -1)
        {
            allCleared = false;
        }
    }

    return allCleared;
}
// CHANGE END

Update CreateTargets (line 366) and UpdateTargets (line 126) so that they check if the generated number is in the TargetsUsed list we made

static List<int> CreateTargets(int SizeOfTargets, int MaxTarget)
{
    List<int> Targets = new List<int>();
    for (int Count = 1; Count <= 5; Count++)
    {
        Targets.Add(-1);
    }
            
    for (int Count = 1; Count <= SizeOfTargets - 5; Count++)
    {
        // CHANGE START
        int SelectedTarget = GetTarget(MaxTarget);
        Targets.Add(SelectedTarget);
        if (!TargetsUsed.Contains(SelectedTarget))
        {
            TargetsUsed.Add(SelectedTarget);
        }
        // CHANGE END
    }
    return Targets;
}


static void UpdateTargets(List<int> Targets, bool TrainingGame, int MaxTarget)
{
    for (int Count = 0; Count < Targets.Count - 1; Count++)
    {
        Targets[Count] = Targets[Count + 1];
    }
    Targets.RemoveAt(Targets.Count - 1);
    if (TrainingGame)
    {
        Targets.Add(Targets[Targets.Count - 1]);
    }
    else
    {
        // CHANGE START
        if (TargetsUsed.Count == MaxTarget)
        {
            Targets.Add(-1);
            return;
        }
        int ChosenTarget = GetTarget(MaxTarget);
        while (TargetsUsed.Contains(ChosenTarget))
        {
            ChosenTarget = GetTarget(MaxTarget);    
        }
        Targets.Add(ChosenTarget);
        TargetsUsed.Add(ChosenTarget);
        // CHANGE END
    }
}

Finally in the main game loop (line 57) you should add the check to make sure that the user has cleared every possible number

    ...
    while (!GameOver)
    {
        DisplayState(Targets, NumbersAllowed, Score);
        Console.Write("Enter an expression: ");
        UserInput = Console.ReadLine();
        Console.WriteLine();
        if (CheckIfUserInputValid(UserInput))
        {
            UserInputInRPN = ConvertToRPN(UserInput);
            if (CheckNumbersUsedAreAllInNumbersAllowed(NumbersAllowed, UserInputInRPN, MaxNumber))
            {
                if (CheckIfUserInputEvaluationIsATarget(Targets, UserInputInRPN, ref Score))
                {
                    RemoveNumbersUsed(UserInput, MaxNumber, NumbersAllowed);
                    NumbersAllowed = FillNumbers(NumbersAllowed, TrainingGame, MaxNumber);
                }
            }
        }
        Score--;
        // CHANGE START
        if (Targets[0] != -1 || (CheckIfEveryPossibleTargetHasBeenUsed(MaxNumber) && CheckAllTargetsCleared(Targets)))
        {
            GameOver = true;
        }
        else
        {
            UpdateTargets(Targets, TrainingGame, MaxTarget);
        }
        // CHANGE END
    }
    Console.WriteLine("Game over!");
    DisplayScore(Score);



Python:

Python - JR - Comberton

________________________________________________________________

This one becomes quite difficult if you don't have a good understanding of how the modules work. The solution is relatively simple - you just create a list that stores all the targets that have been used, and then compare new targets generated to that list, to ensure no duplicates are ever added again. The code for initialising the list and ammending the GetTarget subroutine are included below:

def Main():
    NumbersAllowed = []
    Targets = []
    MaxNumberOfTargets = 20
    MaxTarget = 0
    MaxNumber = 0
    TrainingGame = False
    Choice = input("Enter y to play the training game, anything else to play a random game: ").lower()
    print()
    if Choice == "y":
        MaxNumber = 1000
        MaxTarget = 1000
        TrainingGame = True
        Targets = [-1, -1, -1, -1, -1, 23, 9, 140, 82, 121, 34, 45, 68, 75, 34, 23, 119, 43, 23, 119]
    else:
        MaxNumber = 10
        MaxTarget = 50
        Targets = CreateTargets(MaxNumberOfTargets, MaxTarget)        
    NumbersAllowed = FillNumbers(NumbersAllowed, TrainingGame, MaxNumber)
    TargetsNotAllowed = [] # Contains targets that have been guessed once, so can't be generated again
    PlayGame(Targets, NumbersAllowed, TrainingGame, MaxTarget, MaxNumber, TargetsNotAllowed) # 
    input()
def GetTarget(MaxTarget, TargetsNotAllowed):
    num = random.randint(1, MaxTarget)
    # Try again until valid number found
    if num in TargetsNotAllowed:
        return GetTarget(MaxTarget, TargetsNotAllowed)
    return num

(tbc..)


Make the program object oriented

Answer:

import re import random import math

class Game:

   def __init__(self):
       self.numbers_allowed = []
       self.targets = []
       self.max_number_of_targets = 20
       self.max_target = 0
       self.max_number = 0
       self.training_game = False
       self.score = 0
   def main(self):
       choice = input("Enter y to play the training game, anything else to play a random game: ").lower()
       print()
       if choice == "y":
           self.max_number = 1000
           self.max_target = 1000
           self.training_game = True
           self.targets = [-1, -1, -1, -1, -1, 23, 9, 140, 82, 121, 34, 45, 68, 75, 34, 23, 119, 43, 23, 119]
       else:
           self.max_number = 10
           self.max_target = 50
           self.targets = TargetManager.create_targets(self.max_number_of_targets, self.max_target)
       self.numbers_allowed = NumberManager.fill_numbers([], self.training_game, self.max_number)
       self.play_game()
       input()
   def play_game(self):
       game_over = False
       while not game_over:
           self.display_state()
           user_input = input("Enter an expression: ")
           print()
           if ExpressionValidator.is_valid(user_input):
               user_input_rpn = ExpressionConverter.to_rpn(user_input)
               if NumberManager.check_numbers_used(self.numbers_allowed, user_input_rpn, self.max_number):
                   is_target, self.score = TargetManager.check_if_target(self.targets, user_input_rpn, self.score)
                   if is_target:
                       self.numbers_allowed = NumberManager.remove_numbers(user_input, self.max_number, self.numbers_allowed)
                       self.numbers_allowed = NumberManager.fill_numbers(self.numbers_allowed, self.training_game, self.max_number)
           self.score -= 1
           if self.targets[0] != -1:
               game_over = True
           else:
               self.targets = TargetManager.update_targets(self.targets, self.training_game, self.max_target)
       print("Game over!")
       print(f"Final Score: {self.score}")
   def display_state(self):
       TargetManager.display_targets(self.targets)
       NumberManager.display_numbers(self.numbers_allowed)
       print(f"Current score: {self.score}\n")

class TargetManager:

   @staticmethod
   def create_targets(size_of_targets, max_target):
       targets = [-1] * 5
       for _ in range(size_of_targets - 5):
           targets.append(TargetManager.get_target(max_target))
       return targets
   @staticmethod
   def update_targets(targets, training_game, max_target):
       for i in range(len(targets) - 1):
           targets[i] = targets[i + 1]
       targets.pop()
       if training_game:
           targets.append(targets[-1])
       else:
           targets.append(TargetManager.get_target(max_target))
       return targets
   @staticmethod
   def check_if_target(targets, user_input_rpn, score):
       user_input_eval = ExpressionEvaluator.evaluate_rpn(user_input_rpn)
       is_target = False
       if user_input_eval != -1:
           for i in range(len(targets)):
               if targets[i] == user_input_eval:
                   score += 2
                   targets[i] = -1
                   is_target = True
       return is_target, score
   @staticmethod
   def display_targets(targets):
       print("|", end=)
       for target in targets:
           print(f"{target if target != -1 else ' '}|", end=)
       print("\n")
   @staticmethod
   def get_target(max_target):
       return random.randint(1, max_target)

class NumberManager:

   @staticmethod
   def fill_numbers(numbers_allowed, training_game, max_number):
       if training_game:
           return [2, 3, 2, 8, 512]
       while len(numbers_allowed) < 5:
           numbers_allowed.append(NumberManager.get_number(max_number))
       return numbers_allowed
   @staticmethod
   def check_numbers_used(numbers_allowed, user_input_rpn, max_number):
       temp = numbers_allowed.copy()
       for item in user_input_rpn:
           if ExpressionValidator.is_valid_number(item, max_number):
               if int(item) in temp:
                   temp.remove(int(item))
               else:
                   return False
       return True
   @staticmethod
   def remove_numbers(user_input, max_number, numbers_allowed):
       user_input_rpn = ExpressionConverter.to_rpn(user_input)
       for item in user_input_rpn:
           if ExpressionValidator.is_valid_number(item, max_number):
               if int(item) in numbers_allowed:
                   numbers_allowed.remove(int(item))
       return numbers_allowed
   @staticmethod
   def display_numbers(numbers_allowed):
       print("Numbers available: ", "  ".join(map(str, numbers_allowed)))
   @staticmethod
   def get_number(max_number):
       return random.randint(1, max_number)

class ExpressionValidator:

   @staticmethod
   def is_valid(expression):
       return re.search(r"^([0-9]+[\+\-\*\/])+[0-9]+$", expression) is not None
   @staticmethod
   def is_valid_number(item, max_number):
       if re.search(r"^[0-9]+$", item):
           item_as_int = int(item)
           return 0 < item_as_int <= max_number
       return False

class ExpressionConverter:

   @staticmethod
   def to_rpn(expression):
       precedence = {"+": 2, "-": 2, "*": 4, "/": 4}
       operators = []
       position = 0
       operand, position = ExpressionConverter.get_number_from_expression(expression, position)
       rpn = [str(operand)]
       operators.append(expression[position - 1])
       while position < len(expression):
           operand, position = ExpressionConverter.get_number_from_expression(expression, position)
           rpn.append(str(operand))
           if position < len(expression):
               current_operator = expression[position - 1]

while operators and precedence[operators[-1]] > precedence[current_operator]:

                   rpn.append(operators.pop())

if operators and precedence[operators[-1]] == precedence[current_operator]:

                   rpn.append(operators.pop())
               operators.append(current_operator)
           else:
               while operators:
                   rpn.append(operators.pop())
       return rpn
   @staticmethod
   def get_number_from_expression(expression, position):
       number = ""
       while position < len(expression) and re.search(r"[0-9]", expression[position]):
           number += expression[position]
           position += 1
       position += 1
       return int(number), position

class ExpressionEvaluator:

   @staticmethod
   def evaluate_rpn(user_input_rpn):
       stack = []
       while user_input_rpn:
           token = user_input_rpn.pop(0)
           if token not in ["+", "-", "*", "/"]:
               stack.append(float(token))
           else:
               num2 = stack.pop()
               num1 = stack.pop()
               if token == "+":
                   stack.append(num1 + num2)
               elif token == "-":
                   stack.append(num1 - num2)
               elif token == "*":
                   stack.append(num1 * num2)
               elif token == "/":
                   stack.append(num1 / num2)
       result = stack[0]
       return math.floor(result) if result.is_integer() else -1

if __name__ == "__main__":

   game = Game()
   game.main()


Allow the user to save and load the game

Python:

If the user enters s or l then save or load the state of the game. The saved game file could also store MaxTarget and MaxNumber. The continue statements let the while loop continue.

def PlayGame(Targets, NumbersAllowed, TrainingGame, MaxTarget, MaxNumber): 
    Score = 0
    print("Enter s to save the game")
    print("Enter l to load the game")
    print()
    GameOver = False 
    while not GameOver: 
        DisplayState(Targets, NumbersAllowed, Score) 
        UserInput = input("Enter an expression: ") 
        print()
        if UserInput == "s":
            SaveGameToFile(Targets, NumbersAllowed, Score)
            continue
        if UserInput == "l":
            Targets, NumbersAllowed, Score = LoadGameFromFile()
            continue
        if CheckIfUserInputValid(UserInput): 
            UserInputInRPN = ConvertToRPN(UserInput) 
            if CheckNumbersUsedAreAllInNumbersAllowed(NumbersAllowed, UserInputInRPN, MaxNumber): 
                IsTarget, Score = CheckIfUserInputEvaluationIsATarget(Targets, UserInputInRPN, Score) 
                if IsTarget: 
                    NumbersAllowed = RemoveNumbersUsed(UserInput, MaxNumber, NumbersAllowed) 
                    NumbersAllowed = FillNumbers(NumbersAllowed, TrainingGame, MaxNumber) 
        Score -= 1 
        if Targets[0] != -1: 
            GameOver = True 
        else: 
            Targets = UpdateTargets(Targets, TrainingGame, MaxTarget)         
    print("Game over!") 
    DisplayScore(Score)

Create a SaveGameToFile() function:

  • Targets, NumbersAllowed and Score are written to 3 lines in a text file.
  • map() applies the str() function to each item in the Targets list. So each integer is converted to a string.
  • join() concatenates the resulting list of strings using a space character as the separator.
  • The special code \n adds a newline character.
  • The same process is used for the NumbersAllowed list of integers.
def SaveGameToFile(Targets, NumbersAllowed, Score):
    try:
        with open("savegame.txt","w") as f:
            f.write(" ".join(map(str, Targets)))
            f.write("\n")
            f.write(" ".join(map(str, NumbersAllowed)))
            f.write("\n")
            f.write(str(Score))
            f.write("\n")
            print("____Game saved____")
            print()
    except:
        print("Failed to save the game")

Create a LoadGameFromFile() function:

  • The first line is read as a string using readline().
  • The split() function separates the line into a list of strings, using the default separator which is a space character.
  • map() then applies the int() function to each item in the list of strings, producing a list of integers.
  • In Python 3, the list() function is also needed to convert the result from a map object to a list.
  • The function returns Targets, NumbersAllowed and Score.
def LoadGameFromFile():
    try:
        with open("savegame.txt","r") as f:
            Targets = list(map(int, f.readline().split()))
            NumbersAllowed = list(map(int, f.readline().split()))
            Score = int(f.readline())
            print("____Game loaded____")
            print()
    except:
        print("Failed to load the game")
        print()
        return [],[],-1
    return Targets, NumbersAllowed, Score

Example "savegame.txt" file, showing Targets, NumbersAllowed and Score:

-1 -1 -1 -1 -1 24 5 19 39 31 1 30 13 8 46 41 32 33 7 4
3 10 2 8 2
0

Typical game output:

Enter y to play the training game, anything else to play a random game: 

Enter s to save the game
Enter l to load the game

| | | | | |17|30|27|18|13|1|1|23|31|3|41|6|41|27|34|

Numbers available: 4  1  2  3  7  

Current score: 0


Enter an expression: s

____Game saved____

| | | | | |17|30|27|18|13|1|1|23|31|3|41|6|41|27|34|

Numbers available: 4  1  2  3  7  

Current score: 0


Enter an expression: 4+1+2+3+7

| | | | | |30|27|18|13|1|1|23|31|3|41|6|41|27|34|28|

Numbers available: 9  3  4  1  5  

Current score: 1


Enter an expression: l

____Game loaded____

| | | | | |17|30|27|18|13|1|1|23|31|3|41|6|41|27|34|

Numbers available: 4  1  2  3  7  

Current score: 0


Enter an expression:

datb2

C#:


I only Focused on saving as loading data probably wont come up.
static void SaveState(int Score, List<int> Targets, List<int>NumbersAllowed)
{
    string targets = "", AllowedNumbers = "";
    for (int i = 0; i < Targets.Count; i++)
    {
        targets += Targets[i];
        if (Targets[i] != Targets[Targets.Count - 1])
        {
            targets += '|';
        }
    }
    for (int i = 0; i < NumbersAllowed.Count; i++)
    {
        AllowedNumbers += NumbersAllowed[i];
        if (NumbersAllowed[i] != NumbersAllowed[NumbersAllowed.Count - 1])
        {
            AllowedNumbers += '|';
        }
    }
    File.WriteAllText("GameState.txt", Score.ToString() + '\n' + targets + '\n' + AllowedNumbers);
}
Updated PlayGame:
        Console.WriteLine();
        Console.Write("Enter an expression or enter \"s\" to save the game state: ");
        UserInput = Console.ReadLine();
        Console.WriteLine();
        //change start
        if (UserInput.ToLower() == "s")
        {
            SaveState(Score, Targets, NumbersAllowed);
            continue;
        }
        //change end
        if (CheckIfUserInputValid(UserInput))

Python:

This solution focuses on logging all values that had been printed the previous game, overwriting the score, and then continuing the game as before. A game log can be saved, reloaded and then continued before being saved again without loss of data.

Add selection (if) statement at the top of PlayGame() to allow for user to input desired action

Score = 0
    GameOver = False
    if (input("Load last saved game? (y/n): ").lower() == "y"):
        with open("savegame.txt", "r") as f:
            lines = []
            for line in f:
                lines.append(line)
                print(line)
            Score = re.search(r'\d+', lines[-2])
    else:
        open("savegame.txt", 'w').close()
    while not GameOver:

Change DisplayScore(), DisplayNumbersAllowed() and DisplayTargets() to allow for the values to be returned instead of being printed.

def DisplayScore(Score):
    output = str("Current score: " + str(Score))
    print(output)
    print()
    print()
    return output
    
def DisplayNumbersAllowed(NumbersAllowed):
    list_of_numbers = []
    for N in NumbersAllowed:
        list_of_numbers.append((str(N) + "  "))
    output = str("Numbers available: " + "".join(list_of_numbers))
    print(output)
    print()
    print()
    return output
    
def DisplayTargets(Targets):
    list_of_targets = []
    for T in Targets:
        if T == -1:
            list_of_targets.append(" ")
        else:
            list_of_targets.append(str(T))
        list_of_targets.append("|")
    output = str("|" + "".join(list_of_targets))
    print(output)
    print()
    print()
    return output

Print returned values, as well as appending to the save file.

def DisplayState(Targets, NumbersAllowed, Score):
    DisplayTargets(Targets)
    DisplayNumbersAllowed(NumbersAllowed)
    DisplayScore(Score)
    try:
        with open("savegame.txt", "a") as f:
            f.write(DisplayTargets(Targets) + "\n\n" + DisplayNumbersAllowed(NumbersAllowed) + "\n\n" + DisplayScore(Score) + "\n\n")
    except Exception as e:
        print(f"There was an exception: {e}")


Display a hint if the player is stuck

Python:

If the user types h during the game, then up to 5 hints will be shown. The continue statement lets the while loop continue, so that the player's score is unchanged.

def PlayGame(Targets, NumbersAllowed, TrainingGame, MaxTarget, MaxNumber): 
    Score = 0 
    GameOver = False
    print("Type h for a hint")
    print()
    while not GameOver: 
        DisplayState(Targets, NumbersAllowed, Score) 
        UserInput = input("Enter an expression: ") 
        print()
        if UserInput == "h":
            DisplayHints(Targets, NumbersAllowed)
            continue
        if CheckIfUserInputValid(UserInput): 
            UserInputInRPN = ConvertToRPN(UserInput) 
            if CheckNumbersUsedAreAllInNumbersAllowed(NumbersAllowed, UserInputInRPN, MaxNumber): 
                IsTarget, Score = CheckIfUserInputEvaluationIsATarget(Targets, UserInputInRPN, Score) 
                if IsTarget: 
                    NumbersAllowed = RemoveNumbersUsed(UserInput, MaxNumber, NumbersAllowed) 
                    NumbersAllowed = FillNumbers(NumbersAllowed, TrainingGame, MaxNumber) 
        Score -= 1 
        if Targets[0] != -1: 
            GameOver = True 
        else: 
            Targets = UpdateTargets(Targets, TrainingGame, MaxTarget)         
    print("Game over!") 
    DisplayScore(Score)

Add a new DisplayHints() function which finds several hints by guessing valid inputs.

def DisplayHints(Targets, NumbersAllowed):
    Loop = 10000
    OperationsList = list("+-*/")
    NumberOfHints = 5
    while Loop > 0 and NumberOfHints > 0:
        Loop -= 1
        TempNumbersAllowed = NumbersAllowed
        random.shuffle(TempNumbersAllowed)
        Guess = str(TempNumbersAllowed[0])
        for i in range(1, random.randint(1,4) + 1):
            Guess += OperationsList[random.randint(0,3)]
            Guess += str(TempNumbersAllowed[i])
        EvaluatedAnswer = EvaluateRPN(ConvertToRPN(Guess))
        if EvaluatedAnswer != -1 and EvaluatedAnswer in Targets:
            print("Hint: " + Guess + " = " + str(EvaluatedAnswer))
            NumberOfHints -= 1
    if Loop <= 0 and NumberOfHints == 5:
        print("Sorry I could not find a solution for you!")
    print()

(Optional) Update the EvaluateRPN() function to prevent any division by zero errors:

def EvaluateRPN(UserInputInRPN): 
    S = [] 
    while len(UserInputInRPN) > 0: 
        while UserInputInRPN[0] not in ["+", "-", "*", "/"]: 
            S.append(UserInputInRPN[0]) 
            UserInputInRPN.pop(0)         
        Num2 = float(S[-1]) 
        S.pop() 
        Num1 = float(S[-1]) 
        S.pop() 
        Result = 0.0 
        if UserInputInRPN[0] == "+": 
            Result = Num1 + Num2 
        elif UserInputInRPN[0] == "-": 
            Result = Num1 - Num2 
        elif UserInputInRPN[0] == "*": 
            Result = Num1 * Num2 
        elif UserInputInRPN[0] == "/":
            if Num2 == 0.0:
                return -1
            Result = Num1 / Num2 
        UserInputInRPN.pop(0) 
        S.append(str(Result))        
    if float(S[0]) - math.floor(float(S[0])) == 0.0: 
        return math.floor(float(S[0])) 
    else: 
        return -1

Example output:

Enter y to play the training game, anything else to play a random game: y

Type h for a hint

| | | | | |23|9|140|82|121|34|45|68|75|34|23|119|43|23|119|

Numbers available: 2  3  2  8  512  

Current score: 0


Enter an expression: h

Hint: 3-2+8 = 9
Hint: 3*2+512/8-2 = 68
Hint: 512/2/8+2 = 34
Hint: 3*8-2/2 = 23
Hint: 8+2/2 = 9

datb2


If a player uses very large numbers, i.e. numbers that lie beyond the defined MaxNumber that aren't allowed in NumbersAllowed, the program does not recognise this and will still reward a hit target. Make changes to penalise the player for doing so.

Python:

def CheckNumbersUsedAreAllInNumbersAllowed(NumbersAllowed, UserInputInRPN, MaxNumber):
    Temp = []
    for Item in NumbersAllowed:
        Temp.append(Item)
    for Item in UserInputInRPN:
        if CheckValidNumber(Item, MaxNumber):
            if int(Item) in Temp:
                Temp.remove(int(Item))
            else:
                return False
        else:
            return False
    return True


Support negative numbers, exponentiation (^), modulus (%), and brackets/parenthesis

Python:


def ConvertToRPN(UserInput):
    output = []
    operatorsStack = []
    digits = ''
    
    allowedOperators = {'+' : [2, True], '-': [2, True], '*': [3, True], '/': [3, True], '%': [3, True], '^': [4, False]}
    
    for char in UserInput:        
        if re.search("^[0-9]$", char) is not None: # if is digit
            digits += char
            continue
        
        if digits == '' and char == '-': # negative numbers
            digits += '#'
            continue
    
        if digits != '': 
            output.append(str(int(digits)))
            digits = ''
        
        operator = allowedOperators.get(char)
        if operator is not None:
            if len(operatorsStack) == 0:
                operatorsStack.append(char)
                continue
            topOperator = operatorsStack[-1]
            while topOperator != '(' and (allowedOperators[topOperator][0] > operator[0] or (allowedOperators[topOperator][0] == operator[0] and operator[1])):
                output.append(topOperator)
                del operatorsStack[-1]
                topOperator = operatorsStack[-1]
            operatorsStack.append(char)
            continue
        
        if char == '(':
            operatorsStack.append(char)
            continue
        
        if char == ')':
            topOperator = operatorsStack[-1]
            while topOperator != '(':
                if len(operatorsStack) == 0:
                    return None
                output.append(topOperator)
                del operatorsStack[-1]
                topOperator = operatorsStack[-1]
            
            del operatorsStack[-1]
            continue
        
        return None
    
    if digits != '':
        output.append(digits)
    
    while len(operatorsStack) != 0:
        topOperator = operatorsStack[-1]
        if topOperator == '(':
            return None
        output.append(topOperator)
        del operatorsStack[-1]
    
    return output

This is an implementation of the shunting yard algorithm, which could also be extended to support functions (but I think it's unlikely that will be a task). It's unlikely that any single question will ask to support this many features, but remembering this roughly might help if can't figure out how to implement something more specific.

It's worth noting that this technically does not fully support negative numbers - "--3" might be consider valid and equal to "3". This algorithm does not support that.

This implementation removes the need for the `Position` management & `GetNumberFromUserInput` method. It rolls it into a single-pass.

def EvaluateRPN(UserInputInRPN):
    S = []
    while len(UserInputInRPN) > 0:
        while UserInputInRPN[0] not in ["+", "-", "*", "/", '^', '%']:
            char = UserInputInRPN[0]
            S.append(char)
            UserInputInRPN.pop(0)        
        Num2 = float(S[-1].replace('#', '-'))
        S.pop()
        Num1 = float(S[-1].replace('#', '-'))
        S.pop()
        Result = 0.0
        if UserInputInRPN[0] == "+":
            Result = Num1 + Num2
        elif UserInputInRPN[0] == "-":
            Result = Num1 - Num2
        elif UserInputInRPN[0] == "*":
            Result = Num1 * Num2
        elif UserInputInRPN[0] == "/":
            Result = Num1 / Num2
        elif UserInputInRPN[0] == "^":
            Result = Num1 ** Num2
        elif UserInputInRPN[0] == "%":
            Result = Num1 % Num2
        # ...

This solution also changes the regular expression required for `CheckIfUserInputValid` significantly. I chose to disable this validation, since the regular expression would be more involved.

Checking for matching brackets is ~. In languages with support for recursive 'Regex', it might technically be possible to write a Regex (for example in the PCRE dialect). However, as all questions must be of equal difficulty in all languages, this is an interesting problem that's unlikely to come up. Remember that simply counting opening and closing brackets may or may not be sufficient.


Fix the bug where two digit numbers in random games can be entered as sums of numbers that don't occur in the allowed numbers list. Ie the target is 48, you can enter 48-0 and it is accepted.

C#:

Modify the CheckNumbersUsedAreAllInNumbersAllowed method so that it returns false for invalid inputs:

static bool CheckNumbersUsedAreAllInNumbersAllowed(List<int> NumbersAllowed, List<string> UserInputInRPN, int MaxNumber)
{
    List<int> Temp = new List<int>();
    foreach (int Item in NumbersAllowed)
    {
        Temp.Add(Item);
    }
    foreach (string Item in UserInputInRPN)
    {
        Console.WriteLine(Item);
        if (CheckValidNumber(Item, MaxNumber, NumbersAllowed))
        {
            if (Temp.Contains(Convert.ToInt32(Item)))
            {
                Temp.Remove(Convert.ToInt32(Item));
            }
            else
            {
                return false;
            }
            return true;
        }
    }
    return false;
}


Implement a feature where every round, a random target (and all its occurrences) is shielded, and cannot be targeted for the duration of the round. If targeted, the player loses a point as usual. The target(s) should be displayed surrounded with brackets like this: |(n)|

Python:

Modify PlayGame to call ShieldTarget at the start of each round.

def PlayGame(Targets, NumbersAllowed, TrainingGame, MaxTarget, MaxNumber):
    Score = 0
    GameOver = False
    while not GameOver:
        ShieldTarget(Targets)
        DisplayState(Targets, NumbersAllowed, Score)
        UserInput = input("Enter an expression: ")
        print()
        if CheckIfUserInputValid(UserInput):
            UserInputInRPN = ConvertToRPN(UserInput)
            if CheckNumbersUsedAreAllInNumbersAllowed(NumbersAllowed, UserInputInRPN, MaxNumber):
                IsTarget, Score = CheckIfUserInputEvaluationIsATarget(Targets, UserInputInRPN, Score)
                if IsTarget:
                    NumbersAllowed = RemoveNumbersUsed(UserInput, MaxNumber, NumbersAllowed)
                    NumbersAllowed = FillNumbers(NumbersAllowed, TrainingGame, MaxNumber)
        Score -= 1
        if Targets[0] != -1:
            GameOver = True
        else:
            Targets = UpdateTargets(Targets, TrainingGame, MaxTarget)        
    print("Game over!")
    DisplayScore(Score)

Create a SheildTarget function. Before creating new shielded target(s), unshield existing target(s). Loop through Targets list to check for existing shielded target(s), identify shielded target(s) as they will have type str, convert shielded target back to a normal target by using .strip() to remove preceding and trailing brackets and type cast back to an int. Setup new shielded targets: loop until a random non-empty target is found, shield all occurrences of the chosen target by converting it to a string and concatenating with brackets e.g. 3 becomes "(3)" marking it as "shielded".

def ShieldTarget(Targets):
    for i in range(len(Targets)):
        if type(Targets[i]) == str:
            Targets[i] = int(Targets[i].strip("()"))
    
    FoundRandomTarget = False
    while not FoundRandomTarget:
        RandomTarget = Targets[random.randint(0, len(Targets)-1)]
        if RandomTarget != -1:
            FoundRandomTarget = True
    
    for i in range(len(Targets)):
        if Targets[i] == RandomTarget:
            Targets[i] = "(" + str(RandomTarget) + ")"

evokekw

Sample output:

| | | | | |(23)|9|140|82|121|34|45|68|75|34|(23)|119|43|(23)|119|

Numbers available: 2  3  2  8  512  

Current score: 0


Enter an expression: 8+3-2

| | | | |23| |140|82|121|34|45|68|75|34|23|(119)|43|23|(119)|(119)|

Numbers available: 2  3  2  8  512  

Current score: 1


Enter an expression: 2+2

| | | |23| |140|82|121|(34)|45|68|75|(34)|23|119|43|23|119|119|119|

Numbers available: 2  3  2  8  512

Current score: 0


Enter an expression: 512/8/2+2

| | |23| |140|82|121|34|45|68|75|34|23|(119)|43|23|(119)|(119)|(119)|(119)|

Numbers available: 2  3  2  8  512

Current score: -1


Enter an expression:


Do not advance the target list forward for invalid entries, instead inform the user the entry was invalid and prompt them for a new one

Python:

Modify the PlayGame function, removing an if clause in favour of a loop. Additionally, remove the line score -= 1 and as a consequence partially fix a score bug in the program due to this line not being contained in an else clause.

def PlayGame(Targets, NumbersAllowed, TrainingGame, MaxTarget, MaxNumber):
    Score = 0
    GameOver = False
    while not GameOver:
        DisplayState(Targets, NumbersAllowed, Score)
        UserInput = input("Enter an expression: ")
        print()
        while not CheckIfUserInputValid(UserInput):
            print("That expression was invalid. ")
            UserInput = input("Enter an expression: ")
            print()
            
        UserInputInRPN = ConvertToRPN(UserInput)
        if CheckNumbersUsedAreAllInNumbersAllowed(NumbersAllowed, UserInputInRPN, MaxNumber):
            IsTarget, Score = CheckIfUserInputEvaluationIsATarget(Targets, UserInputInRPN, Score)
            if IsTarget:
                NumbersAllowed = RemoveNumbersUsed(UserInput, MaxNumber, NumbersAllowed)
                NumbersAllowed = FillNumbers(NumbersAllowed, TrainingGame, MaxNumber)
        
        
        if Targets[0] != -1:
            GameOver = True
        else:
            Targets = UpdateTargets(Targets, TrainingGame, MaxTarget)        
    print("Game over!")
    DisplayScore(Score)


Implement a menu where the user can start a new game or quit their current game

TBA

Ensure the program ends when the game is over

TBA

Give the user a single-use ability to generate a new set of allowable numbers

TBA

Allow the user to input and work with negative numbers

  • Assume that the targets and allowed numbers may be finitely negative - to test your solution, change one of the targets to "6" and the allowed number "2" to "-2": "-2+8 = 6"
  • How will you prevent conflicts with the -1 used as a placeholder?
  • The current RPN processing doesn't understand a difference between "-" used as subtract vs to indicate a negative number. What's the easiest way to solve this?
  • The regular expressions used for validation will need fixing to allow "-" in the correct places. Where are those places, how many "-"s are allowed in front of a number, and how is "-" written in Regex?
  • A number is now formed from more than just digits. How will `GetNumberFromUserInput` need to change to get the whole number including the "-"?

Python:

# Manually change the training game targets from -1 to `None`. Also change anywhere where a -1 is used as the empty placeholder to `None`.

# Change the condition for display of the targets
def DisplayTargets(Targets):
    print("|", end="")
    for T in Targets:
        if T == None:
            print(" ", end="")
        else:
            print(T, end="")
        print("|", end="")
    print()
    print()

# We solve an intermediate problem of the maxNumber not being treated correctly by making this return status codes instead - there's one other place not shown where we need to check the output code
def CheckValidNumber(Item, MaxNumber):
    if re.search("^\\-?[0-9]+$", Item) is not None:
        ItemAsInteger = int(Item)
        if ItemAsInteger > MaxNumber:
            return -1
        return 0
    return -2

# Change to handle the new status codes
def CheckNumbersUsedAreAllInNumbersAllowed(NumbersAllowed, UserInputInRPN, MaxNumber):
    Temp = []
    for Item in NumbersAllowed:
        Temp.append(Item)
    for Item in UserInputInRPN:
        result = CheckValidNumber(Item, MaxNumber)
        if result == 0:
            if not int(Item) in Temp:
                return False
            Temp.remove(int(Item))
        elif result == -1:
            return False
    return True

# We change some lines - we're treating the negative sign used to indicate a negative number as a new special symbol: '#'
def ConvertToRPN(UserInput):
     # ...
     # When appending to the final expression we need to change the sign in both places necessary
     UserInputInRPN.append(str(Operand).replace("-", "#"))

# And same in reverse here:
def EvaluateRPN(UserInputInRPN):
     # ...
     Num2 = float(S[-1].replace("#", "-")) # and Num1

# Update this with a very new implementation which handles "-"
def GetNumberFromUserInput(UserInput, Position):
    Number = ""
    Position -= 1
    hasSeenNum = False

    while True:
        Position += 1

        if Position >= len(UserInput):
            break

        char = UserInput[Position]

        if char == "-":
            if hasSeenNum or Number == "-":
                break
            Number += "-"
            continue

        if re.search("[0-9]", str(UserInput[Position])) is not None:
            hasSeenNum = True
            Number += UserInput[Position]
            continue

        break

    if hasSeenNum:
        return int(Number), Position + 1
    else:
        return -1, Position + 1

# Update the regexes here and elsewhere (not shown)
def CheckIfUserInputValid(UserInput):
    if re.search("^(\\-?[0-9]+[\\+\\-\\*\\/])+\\-?[0-9]+$", UserInput) is not None:
        # This is entirely unnecessary - why not just return the statement in the comparison above
       # Maybe this indicates a potential question which will change something here, so there's some nice templating...
       # But in Java, this isn't here?
        return True
    else:
        return False


Increase the score with a bonus equal to the quantity of allowable numbers used in a qualifying expression

Python:

Modify the PlayGame function, so that we can receive the bonus count from CheckNumbersUsedAreAllInNumbersAllowed, where the bonus will be calculated depending on how many numbers from NumbersAllowed were used in an expression. This bonus value will be passed back into PlayGame, where it will be passed as a parameter for CheckIfUserInputEvaluationIsATarget. In this function if UserInputEvaluationIsATarget is True then we apply the bonus to Score

def PlayGame(Targets, NumbersAllowed, TrainingGame, MaxTarget, MaxNumber):
    Score = 0
    GameOver = False
    while not GameOver:
        DisplayState(Targets, NumbersAllowed, Score)
        UserInput = input("Enter an expression: ")
        print()
        if CheckIfUserInputValid(UserInput):
            UserInputInRPN = ConvertToRPN(UserInput)
            NumbersUsedAreAllInNumbersAllowed, Bonus = CheckNumbersUsedAreAllInNumbersAllowed(NumbersAllowed, UserInputInRPN, MaxNumber)
            if NumbersUsedAreAllInNumbersAllowed:
                IsTarget, Score = CheckIfUserInputEvaluationIsATarget(Targets, Bonus, UserInputInRPN, Score)
                if IsTarget:
                    NumbersAllowed = RemoveNumbersUsed(UserInput, MaxNumber, NumbersAllowed)
                    NumbersAllowed = FillNumbers(NumbersAllowed, TrainingGame, MaxNumber)
        Score -= 1
        if Targets[0] != -1:
            GameOver = True
        else:
            Targets = UpdateTargets(Targets, TrainingGame, MaxTarget)        
    print("Game over!")
    DisplayScore(Score)

def CheckNumbersUsedAreAllInNumbersAllowed(NumbersAllowed, UserInputInRPN, MaxNumber):
    Temp = []
    for Item in NumbersAllowed:
        Temp.append(Item)
    for Item in UserInputInRPN:
        if CheckValidNumber(Item, MaxNumber):
            if int(Item) in Temp:
                Temp.remove(int(Item))
            else:
                return False, 0
    Bonus = len(NumbersAllowed) - len(Temp)
    print(f"You have used {Bonus} numbers from NumbersAllowed, this will be your bonus")
    return True, Bonus

def CheckIfUserInputEvaluationIsATarget(Targets, Bonus, UserInputInRPN, Score):
    UserInputEvaluation = EvaluateRPN(UserInputInRPN)
    UserInputEvaluationIsATarget = False
    if UserInputEvaluation != -1:
        for Count in range(0, len(Targets)):
            if Targets[Count] == UserInputEvaluation:
                Score += 2
                Targets[Count] = -1
                UserInputEvaluationIsATarget = True
    if UserInputEvaluationIsATarget:
        Score += Bonus
    return UserInputEvaluationIsATarget, Score


Implement a multiplicative score bonus for each priority (first number in the target list) number completed sequentially

TBA

If the user creates a qualifying expression which uses all the allowable numbers, grant the user a special reward ability (one use until unlocked again) to allow the user to enter any number of choice and this value will be removed from the target list

Python:

Modify the PlayGame function, so you can enter a keyword (here is used 'hack' but can be anything unique), then check if the user has gained the ability. If the ability flag is set to true then allow the user to enter a number of their choice from the Targets list. Once the ability is used set the ability flag to false so it cannot be used. We will also modify CheckNumbersUsedAreAllInNumbersAllowed to confirm the user has used all of the numbers from NumbersAllowed. This is so that the ability flag can be activated and the user informed using a suitable message.

def PlayGame(Targets, NumbersAllowed, TrainingGame, MaxTarget, MaxNumber):
    NumberAbility = False
    Score = 0
    GameOver = False
    while not GameOver:
        DisplayState(Targets, NumbersAllowed, Score)
        UserInput = input("Enter an expression: ")
        print()
        if UserInput == "hack" and NumberAbility == True:
            print("Here is the Targets list. Pick any number and it will be removed!")
            DisplayTargets(Targets)
            Choice = 0
            while Choice not in Targets:
                Choice = int(input("Enter a number > "))
            while Choice in Targets:
                Targets[Targets.index(Choice)] = -1
            continue
        if CheckIfUserInputValid(UserInput):
            UserInputInRPN = ConvertToRPN(UserInput)
            NumbersUsedInNumbersAllowed, NumberAbility = CheckNumbersUsedAreAllInNumbersAllowed(NumbersAllowed, UserInputInRPN, MaxNumber)
            if NumberAbility:
                print("You have received a special one-time reward ability to remove one target from the Targets list.\nUse the keyword 'hack' to activate!")
            if NumbersUsedInNumbersAllowed:
                IsTarget, Score = CheckIfUserInputEvaluationIsATarget(Targets, UserInputInRPN, Score)
                if IsTarget:
                    NumbersAllowed = RemoveNumbersUsed(UserInput, MaxNumber, NumbersAllowed)
                    NumbersAllowed = FillNumbers(NumbersAllowed, TrainingGame, MaxNumber)
        Score -= 1
        if Targets[0] != -1:
            GameOver = True
        else:
            Targets = UpdateTargets(Targets, TrainingGame, MaxTarget)        
    print("Game over!")
    DisplayScore(Score)

def CheckNumbersUsedAreAllInNumbersAllowed(NumbersAllowed, UserInputInRPN, MaxNumber):
    Temp = []
    for Item in NumbersAllowed:
        Temp.append(Item)
    for Item in UserInputInRPN:
        if CheckValidNumber(Item, MaxNumber):
            if int(Item) in Temp:
                Temp.remove(int(Item))
            else:
                return False, False
    Ability = False
    if len(Temp) == 0:
        Ability = True
    return True, Ability


When a target is cleared, put a £ symbol in its position in the target list. When the £ symbol reaches the end of the tracker, increase the score by 1

Python:

Modify the PlayGame function, so that we can check to see if the first item in the Targets list is a '£' symbol, so that the bonus point can be added to the score. Also modify the GameOver condition so that it will not end the game if '£' is at the front of the list. Also we will modify CheckNumbersUsedAreAllInNumbersAllowed so that when a target is cleared, instead of replacing it with '-1', we will instead replace it with '£'.

def PlayGame(Targets, NumbersAllowed, TrainingGame, MaxTarget, MaxNumber):
    Score = 0
    GameOver = False
    while not GameOver:
        DisplayState(Targets, NumbersAllowed, Score)
        UserInput = input("Enter an expression: ")
        print()
        if CheckIfUserInputValid(UserInput):
            UserInputInRPN = ConvertToRPN(UserInput)
            if CheckNumbersUsedAreAllInNumbersAllowed(NumbersAllowed, UserInputInRPN, MaxNumber):
                IsTarget, Score = CheckIfUserInputEvaluationIsATarget(Targets, UserInputInRPN, Score)
                if IsTarget:
                    NumbersAllowed = RemoveNumbersUsed(UserInput, MaxNumber, NumbersAllowed)
                    NumbersAllowed = FillNumbers(NumbersAllowed, TrainingGame, MaxNumber)
        if Targets[0] == "£":
            Score += 1
        Score -= 1
        if Targets[0] != -1 and Targets[0] != "£":
            GameOver = True
        else:
            Targets = UpdateTargets(Targets, TrainingGame, MaxTarget)        
    print("Game over!")
    DisplayScore(Score)

def CheckIfUserInputEvaluationIsATarget(Targets, UserInputInRPN, Score):
    UserInputEvaluation = EvaluateRPN(UserInputInRPN)
    UserInputEvaluationIsATarget = False
    if UserInputEvaluation != -1:
        for Count in range(0, len(Targets)):
            if Targets[Count] == UserInputEvaluation:
                Score += 2
                Targets[Count] = "£"
                UserInputEvaluationIsATarget = True        
    return UserInputEvaluationIsATarget, Score


Implement a victory condition which allows the player to win the game by achieving a certain score. Allow the user to pick difficulty, e.g. easy (10), normal (20), hard (40)

Python:

Modify the Main function so that the user has the option to select a Victory condition. This Victory condition will be selected and passed into the PlayGame function. We will place the Victory condition before the check to see if the item at the front of Targets is -1. If the user score is equal to or greater than the victory condition, we will display a victory message and set GameOver to True, thus ending the game

def Main():
    NumbersAllowed = []
    Targets = []
    MaxNumberOfTargets = 20
    MaxTarget = 0
    MaxNumber = 0
    TrainingGame = False
    Choice = input("Enter y to play the training game, anything else to play a random game: ").lower()
    print()
    if Choice == "y":
        MaxNumber = 1000
        MaxTarget = 1000
        TrainingGame = True
        Targets = [-1, -1, -1, -1, -1, 23, 9, 140, 82, 121, 34, 45, 68, 75, 34, 23, 119, 43, 23, 119]
    else:
        MaxNumber = 10
        MaxTarget = 50
        Targets = CreateTargets(MaxNumberOfTargets, MaxTarget)        
    NumbersAllowed = FillNumbers(NumbersAllowed, TrainingGame, MaxNumber)
    VictoryPoints = {"e":10,"m":20,"h":40}
    Choice = input("Enter\n - e for Easy Victory (10 Score Points)\n - m for Medium Victory Condition (20 Score Points)\n - h for Hard Victory Condition (40 Score Points)\n - n for Normal Game Mode\n : ").lower()
    if Choice in VictoryPoints.keys():
        VictoryCondition = VictoryPoints[Choice]
    else:
        VictoryCondition = False
    PlayGame(Targets, NumbersAllowed, TrainingGame, MaxTarget, MaxNumber, VictoryCondition)
    input()
    
def PlayGame(Targets, NumbersAllowed, TrainingGame, MaxTarget, MaxNumber, VictoryCondition):
    if VictoryCondition:
        print(f"You need {VictoryCondition} points to win")
    Score = 0
    GameOver = False
    while not GameOver:
        DisplayState(Targets, NumbersAllowed, Score)
        UserInput = input("Enter an expression: ")
        print()
        if CheckIfUserInputValid(UserInput):
            UserInputInRPN = ConvertToRPN(UserInput)
            if CheckNumbersUsedAreAllInNumbersAllowed(NumbersAllowed, UserInputInRPN, MaxNumber):
                IsTarget, Score = CheckIfUserInputEvaluationIsATarget(Targets, UserInputInRPN, Score)
                if IsTarget:
                    NumbersAllowed = RemoveNumbersUsed(UserInput, MaxNumber, NumbersAllowed)
                    NumbersAllowed = FillNumbers(NumbersAllowed, TrainingGame, MaxNumber)
        Score -= 1
        if VictoryCondition:
            if Score >= VictoryCondition:
                print("You have won the game!")
                GameOver = True
        if Targets[0] != -1:
            GameOver = True
        else:
            Targets = UpdateTargets(Targets, TrainingGame, MaxTarget)        
    print("Game over!")
    DisplayScore(Score)


Shotgun. If an expression exactly evaluates to a target, the score is increased by 3 and remove the target as normal. If an evaluation is within 1 of the target, the score is increased by 1 and remove those targets too

TBA

Every time the user inputs an expression, shuffle the current position of all targets (cannot push a number closer to the end though)

TBA

Speed Demon. Implement a mode where the target list moves a number of places equal to the current player score. Instead of ending the game when a target gets to the end of the tracker, subtract 1 from their score. If their score ever goes negative, the player loses

TBA

Allow the user to save the current state of the game using a text file and implement the ability to load up a game when they begin the program

TBA

Multiple of X. The program should randomly generate a number each turn, e.g. 3 and if the user creates an expression which removes a target which is a multiple of that number, give them a bonus of their score equal to the multiple (in this case, 3 extra score)

TBA

Validate a user's entry to confirm their choice before accepting an expression

TBA

Prime time punch. If the completed target was a prime number, destroy the targets on either side of the prime number (count them as scored)

TBA

Do not use reverse polish notation

TBA

Allow the user to specify the highest number within the five NumbersAllowed

TBA