A-level Computing 2009/AQA/Problem Solving, Programming, Data Representation and Practical Exercise/Skeleton code/2011 Exam/Section D

From Wikibooks, open books for an open world
Jump to navigation Jump to search

Code Discussion[edit]

When you first run your code you will probably find that everything appears to work. Don't be deceived, try and load the top scores. It probably won't work; we need to get the top scores file in the correct location, so that when we select the load the option from the menu it actually loads the file:

Correct Load Location[edit]

Inside the LoadTopScores() sub routine you'll find the line that is giving you trouble

FileOpen(1, "HiScores.txt", OpenMode.Input)

All this line is doing is trying to find the HiScores.txt file inside the folder where your code is running. On Windows machines this will be:

ConsoleApplication1\bin\Debug

You can fix this by copying the HiScores.txt file into here. Or alternatively you can copy the file into the root of your project, in this case being the poorly named project:

ConsoleApplication1

We then need to make the line we looked at above point to this file:

FileOpen(1, "../../HiScores.txt", OpenMode.Input)

The ../../ means to take the Debug folder where the code is running, and to look two folders up to find the HiScores.txt file:

  • If the code is running in ConsoleApplication1\bin\Debug
  • "../../HiScores.txt" will find the file in ConsoleApplication1\

Potential Questions[edit]

This list is by no means a list of the questions you will be asked in the exam, but by solving these problems you should get to understand the code a little better.

Validating the LoadTopScores(TopScores)[edit]

You might be worried about people accidentally overwriting the top scores from the main menu. Imagine that you have just played the game for an hour and you accidentally click on option 3 on the main menu, you'd overwrite all your scores! How could we validate the entry of the load game to make sure that this didn't happen:

The option menu[edit]

Add a new option to the menu to:

Reset the top scores[edit]

An option that appears to be missing, is the ability for the user to reset the top scores that are currently stored in the TopScores array. We need to add an option (option 5) to the menu so that users can manually reset the top scores. Make sure that you get all the menu option checks to allow for the new menu option number.

Starting the game[edit]

Making sure each player has a different name[edit]

When you start the game the following scenario could occur:

What is player one's name? Aubrey
What is player two's name? Aubrey

This means that you wouldn't know who was who, which Aubrey is which? It would be far better if we forced the users to have different names. To do this you are going to need a loop to check that the second name is different from the first name:

Save current top scores to the CSV file[edit]

A glaring omission from the code is the ability to save the current top scores back to the CSV file. You might well be asked to implement this. Let's first take a look at the code that reads the data from the file:

    Sub LoadTopScores(ByRef TopScores() As TTopScore)
        Dim Count As Integer
        Dim Count2 As Integer
        Dim LineFromFile As String
        Dim ValuesOnLine(2) As String
        FileOpen(1, "../../HiScores.txt", OpenMode.Input)
        For Count = 1 To MaxSize
            ValuesOnLine(1) = ""
            ValuesOnLine(2) = ""
            LineFromFile = LineInput(1)
            Count2 = 0
            Do
                ValuesOnLine(1) = ValuesOnLine(1) + LineFromFile(Count2)
                Count2 = Count2 + 1
            Loop Until LineFromFile(Count2) = ","
            Count2 = Count2 + 1
            Do
                ValuesOnLine(2) = ValuesOnLine(2) + LineFromFile(Count2)
                Count2 = Count2 + 1
            Loop Until Count2 = LineFromFile.Length
            TopScores(Count).Name = ValuesOnLine(1)
            TopScores(Count).Score = CInt(ValuesOnLine(2))
        Next
        FileClose(1)
    End Sub

There are three important things that you need to gain from this.

1. this line opens the file in read only mode and allocates it to file variable 1, we need another mode that writes to the file

FileOpen(1, "../../HiScores.txt", OpenMode.Input)

2. This code reads a line from the file and saves it into the LineFromFile string. Notice the 1 is referring to the file variable. This line is inside a loop that loops from on the variable Count, why is this?

LineFromFile = LineInput(1)

3. Finally we have some house keeping, where we need to close the file to make sure we free up resources

FileClose(1)

Using these ideas we need to create a sub routine to save the files. Let's get some pseudo code together:

Pseudo Code of Save Data to File

sub savefiles(TopScores())

  1. open the text file for writing
  2. loop through 1 to 4 with count
    1. write TopScores(count) to the text file in a CSV format
  3. close the file

end sub

But what real code can we use to do the above?

1. open file for writing, use Output instead of Input

FileOpen(1, "../../HiScores.txt", OpenMode.Output)

2. Print a line to a file instead of reading from it. Remember you will need a loop to write all 4 lines

PrintLine(1, "...") 'write a line to file 1.  Make sure the line you write is in CSV format

When you are writing to the file, remember that each line you are writing must have this format:

<name><comma><score>
Norman,76

And you will get this data from the TopScore() parameter that you are passing to the sub routine

3. Closing the file is the same as before

FileClose(1)

Try the code out yourself and see if you can get it to work:

CSV file[edit]

  • add more fields to the CSV file when saving
  • number of balls survived
    • average score per ball

Displaying scores[edit]

You might be asked to display results in different ways, there are several potential questions, be ready for them all.

Find the highest score[edit]

To do this we need to add a new sub routine, let's call it DisplayHighestScore, and we'll need to pass it the TopScore data structure:

Pseudo Code of displaying a top score

sub displaytopscore(TopScores())

  1. create a pointer to the topscore: hiscore
  2. set hiscore = 1 so that it points to the first element in TopScores
  3. go through each of the TopScores
    1. if topscores value is bigger than value being pointed to by hiscores
    2. then set hiscores to point to new top score
  4. print out the TopScores(hiscore)

end sub

Try to code this solution yourself:

Find the lowest score[edit]

To do this we need to add a new sub routine, let's call it DisplayLowestScore, and we'll need to pass it the TopScore data structure:

Pseudo Code of displaying a top score

sub displaylowestscore(TopScores())

  1. create a pointer to the topscore: hiscore
  2. set hiscore = 1 so that it points to the first element in TopScores
  3. go through each of the TopScores
    1. if topscores value is smaller than value being pointed to by hiscores
    2. then set hiscores to point to new lowest score
  4. print out the TopScores(hiscore)

end sub

Try to code this solution yourself:

Printing out all the scores in order[edit]

You might be asked to print out all the high scores in order. That means we need to order the items. We can do this in two ways:

Bubble Sort[edit]

They could well ask you to perform a full sort on the high score table. A bubble sort is a potential solution to doing this. Take a look at the following pseudo code to perform a bubble sort. You need to make your own version of this to sort all the data:

procedure bubbleSort( A : list of sortable items )
  do
    swapped = false
    for each i in 1 to length(A) - 1 inclusive do:
      if A[i-1] > A[i] then
        swap( A[i-1], A[i] )
        swapped = true
      end if
    end for
  while swapped
end procedure

Create a sub routine called bubbleSort. It should have the following features:

  • reorder the TopScores array
    • look at the pseudo code above
    • remember you are dealing with an array of records
  • prints out the sorted array using: Console.Writeline(Count & ": " & TopScores(Count).Name & " " & TopScores(Count).Name)
  • called from menu item 5.
Hard Coded[edit]

List the number of balls survived[edit]

To do this we are going to need to implement a counting variable to keep track of the number of balls survived by each person and print this out when they are out:

You are out.  Your final score was: 176
You survived 34 balls

As this variable is a whole number we are going to use an integer to store it:

Keeping track of sixes and fours[edit]

You might be asked to put some code in place to keep track of how many sixes and fours were scored by each side. Once each person has finished batting the score of that person should be displayed alongside the numbers of 4s and 6s:

You are out.  Your final score was: 176
You got: 2 sixes and 9 fours.

You can solve this in several ways so don't be worried if you have a different solution to the one below:

Changing the number of scores in stored in TopScores[edit]

We'll need to adjust some things here

Adding a new way to be out[edit]

Add a new way to be out: "Run out".

Drawn games[edit]

  • put a routine in to handle what happens when a game is drawn

This isn't too hard...

    If PlayerOneScore = PlayerTwoScore Then
        Console.WriteLine("You have drawn!")
    End If

Handling how drawn scores update the high score list if there is only one current high score is less than the two drawn scores is much trickier!

Monty,1 is the current lowest score.. so if both players score 2 runs and draw the game what scores should be recorded.

The program currently will record the high score for player one.... then when the routine checks it returns that player two doesn't have a high score (i.e. a score that is higher than any of the scores held in the topscores array). This is because the playerOne score has already been recorded. There are only four possible solutions:

  1. Neither score is recorded
  2. Both scores are recorded, adding an extra record (element) to the array
  3. Concatenate both player names and assign the playerName field of the Topscores array of records
  4. Have only one score recorded BUT... select which of the players Names is added
  • Randomly
  • Using a prompt to allow the user to decide

Playing the game[edit]

  • Randomise who goes first

Manual dice validation[edit]

If you play the game with real dice and you type in a string of letters instead of a number the program will crash. We need to catch this error and fix it. By now you should be familiar with the Try Catch code

Try
    something that might go wrong
Catch ex As Exception
    do something to fix or flag up the problem
End Try

What line in the RollBowlDie function is the problem occurring at? There are two other areas that need fixing, where are they?

They might ask you to keep looping until with an error message until the user inputs a correct answer, to do this you'll need a loop:

Only one entry per person[edit]

If the player's name is already in the TopScores array then update the value rather than adding someone more than once.

The useless: DisplayCurrentPlayerNewScore[edit]

This subroutine isn't used (isn't called anywhere). They might ask you to call this function from the PlayDiceGame sub, or elsewhere in the code.

This code has one parameter: CurrentPlayerScore As Integer

Sub DisplayCurrentPlayerNewScore(ByVal CurrentPlayerScore CurrentPlayerScore As Integer)

Where might we use it? Where is there similar functionality already? Take a look at the PlayDiceGame code:

  CurrentPlayerScore = CurrentPlayerScore + RunsScored
  Console.WriteLine("Your new score is: " & CurrentPlayerScore) 'this line performs the same function
End If

So we could easily replace this line with the a call to the code above:

  CurrentPlayerScore = CurrentPlayerScore + RunsScored
  DisplayCurrentPlayerNewScore(CurrentPlayerScore) 'this line replaces the previous one
End If

They might also ask you to do add a little to the complexity of the DisplayCurrentPlayerNewScore sub routine. For example, make it so that each score displayed looks like:

###############
#Current Score#
###############
#    45       #
###############

Try and implement this

Set limit on the number of balls[edit]

In the current skeleton program, the players are permitted to carry on playing the game as long as the appeal dice is not greater than or equal to 2. Therefore, we may be asked to set a constraint on the maximum number of balls available for each player. In the following program code, I will demonstrate how the aforementioned problem can be tackled. The limit is typed in each time the game is played, making it possible for the players to set the limit in the way they want.

Function BallLimit() As Integer
        Dim NumberOfBalls As Integer = 0
        Console.Write("Do you want the game to have a limit on the number of balls?(Y/N) ")
        Dim Choice As Char = Console.ReadLine
        If Choice = "Y" Then
            Do
                Console.WriteLine()
                Console.Write("Enter the maximum number of balls: ")
                NumberOfBalls = Console.ReadLine
            Loop Until NumberOfBalls > 0
        ElseIf Choice = "N" Then
            NumberOfBalls = 0
        End If
        BallLimit = NumberOfBalls
    End Function

I create a function call BallLimit The limit is represented by the variable NumberOfBalls Firstly, the player is asked whether he wants to set a limit or not. If the answer is "Y", he will have to enter the limit, which must be greater than 0 (it is not realistic to have no balls). Otherwise, the limit is set to 0 The value of the variable is copied to the integer BallLimit, which is then returned to the subroutine PlayDiceGame as you will see

The following code is written in the PlayDiceGame subroutine

Dim x As Integer
Dim NumberOfBalls As Integer
NumberOfBalls = BallLimit()

2 new variables are declared. The variable NumberOfBalls will take the value returned by the function BallLimit as stated above

For PlayerNo = 1 To 2
            x = 0
            CurrentPlayerScore = 0
            PlayerOut = False
            Console.WriteLine()
            If PlayerNo = 1 Then
                Console.WriteLine(PlayerOneName & " is batting")
            Else
                Console.WriteLine(PlayerTwoName & " is batting")
            End If

Each time a new player starts the game, the value of x is set to 0

 Do
                x = x + 1
                BowlDieResult = RollBowlDie(VirtualDiceGame)
                If BowlDieResult >= 1 And BowlDieResult <= 4 Then

Each time the Do loop is excuted the value of x is incremented by 1. We must also change the condition of the Do loop as follow:

            Loop Until x = NumberOfBalls or PlayerOut

So how does this actually work?

If the player set a limit on the number of balls, say 10, the number is passed to the variable NumberOfBalls in the PlayDiceGame subroutine. Each time the player rolls the bowl dice, one ball is deemed to be lost. That's why the x is incremented by 1 each time. It is used to keep track of the number of balls that have been used so far. Therefore, when the value of x reaches 10, the loop will stop because the player runs out of balls. The player may be out before the limit is reached. In this case, the condition PlayerOut is executed.

If the player did not set a limit, the number 0 is passed to the variable NumberOfBalls in the PlayDiceGame subroutine. The value of x when it enters the first Do loop is x = 0 + 1 = 1. It will keep increasing thereafter and so x is always greater than 1. In other words, the condition x = NumberOfBalls will NEVER be satisfied in this case. PlayerOut is the only condition that may work. Therefore, there is no limit on the number of balls

Linear Search[edit]

You need to implement a linear search:

Please insert the name you are looking for: Kenny

to tell you either:

Kenny scored 5.

or:

We couldn't find a user named Kenny. Try again.
Sub LinearSearch(ByRef TopScores() As TTopScore)

Dim nameSought As String
Dim found As Boolean
Dim position As Integer

Do
    Console.WriteLine("Enter the name you'd like to search for: ")
    nameSought = Console.ReadLine()
    found = False
    position = 1

    Do         
        If TopScores(position).Name = nameSought Then
            found = True
        Else
            position = position + 1
        End If
    Loop Until position > MaxSize Or found = True

    If found <> True Then
        Console.WriteLine("We couldn't find a user named " & nameSought & ". Try again.")
    Else
        Console.WriteLine(TopScores(position).Name & " scored " & TopScores(position).Score & ".")
    End If
Loop While found <> True 'loops back to re-entering a name if found = false.

End Sub