A-level Computing 2009/AQA/Problem Solving, Programming, Data Representation and Practical Exercise/Skeleton code/2011 Exam/Section D
Code Discussion
[edit | edit source]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 | edit source]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 | edit source]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 | edit source]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 | edit source]Add a new option to the menu to:
Reset the top scores
[edit | edit source]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.
Answer: | ||
1. Inside the Console.WriteLine("4. Display top scores")
Console.WriteLine("5. Reset top scores")'added option 5
Console.WriteLine("9. Quit")
2. Inside the If OptionChosen < 1 Or (OptionChosen > 4 And OptionChosen <> 9) Then
Update this to: If OptionChosen < 1 Or (OptionChosen > 5 And OptionChosen <> 9) Then
3. Update the Loop Until (OptionSelected >= 1 And OptionSelected <= 4) Or OptionSelected = 9
Update this to: Loop Until (OptionSelected >= 1 And OptionSelected <= 5) Or OptionSelected = 9
4. Finally we need to add the code to run when a person selects option 5 If OptionSelected >= 1 And OptionSelected <= 4 Then
Select Case OptionSelected
Case 1 : PlayDiceGame(PlayerOneName, PlayerTwoName, True, TopScores)
Case 2 : PlayDiceGame(PlayerOneName, PlayerTwoName, False, TopScores)
Case 3 : LoadTopScores(TopScores)
Case 4 : DisplayTopScores(TopScores)
End Select
End If
Make the following changes If OptionSelected >= 1 And OptionSelected <= 5 Then 'change!
Select Case OptionSelected
Case 1 : PlayDiceGame(PlayerOneName, PlayerTwoName, True, TopScores)
Case 2 : PlayDiceGame(PlayerOneName, PlayerTwoName, False, TopScores)
Case 3 : LoadTopScores(TopScores)
Case 4 : DisplayTopScores(TopScores)
Case 5 : ResetTopScores(TopScores) 'addition!
End Select
End If
|
Starting the game
[edit | edit source]Making sure each player has a different name
[edit | edit source]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:
Answer: | ||
Console.Write("What is player one's name? ")
PlayerOneName = GetValidPlayerName()
Console.Write("What is player two's name? ")
Do
PlayerTwoName = GetValidPlayerName()
If PlayerOneName = PlayerTwoName Then
Console.Write("Name is in use already. Please enter new one ")
End If
Loop Until PlayerTwoName <> PlayerOneName
The above would allow: Ryan and ryan If you don't want the check to be case sensitive, we are going to check that the capitalised versions of both names are not the same by using the Console.Write("What is player one's name? ")
PlayerOneName = GetValidPlayerName()
Console.Write("What is player two's name? ")
Do
PlayerTwoName = GetValidPlayerName()
If UCase(PlayerOneName) = UCase(PlayerTwoName) Then
Console.Write("Name is in use already. Please enter new one ")
End If
Loop Until UCase(PlayerTwoName) <> Ucase(PlayerOneName)
The above would not allow: Ryan and ryan |
Save current top scores to the CSV file
[edit | edit source]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)
Answer: | ||
The code for the loop is:
For Count = 1 To MaxSize
...
LineFromFile = LineInput(1)
...
Next
This cycles through 1 to 4, meaning that the code reads all four lines of the text file. |
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())
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:
Answer: | ||
Sub SaveTopScores(ByRef TopScores() As TTopScore)
Dim Count As Integer
FileOpen(1, "../../HiScores.txt", OpenMode.Output)
For Count = 1 To MaxSize
PrintLine(1, TopScores(Count).Name & "," & TopScores(Count).Score)
Next
FileClose(1)
End Sub
You can also achieve this by using a stream writer: Imports System.IO ' you might need this to use StreamWriter
Sub SaveTopScores(ByRef TopScores() As TTopScore)
Dim filewriter As StreamWriter
Dim filename As String
filename = "../../HighScores.txt"
filewriter = New StreamWriter(filename)
For Count = 1 To MaxSize
filewriter.WriteLine(TopScores(Count).Name & "," & TopScores(Count).Score)
Next
filewriter.Close()
End Sub
|
CSV file
[edit | edit source]- add more fields to the CSV file when saving
- number of balls survived
- average score per ball
Displaying scores
[edit | edit source]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 | edit source]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())
end sub |
Try to code this solution yourself:
Answer: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Sub DisplayHighestScore(ByRef TopScores() As TTopScore)
Dim HighestRec As Integer 'stores the location of the highest record
Dim Count as Integer = 1
HighestRec = 1 'set the top score to the first person
For Count = 1 To MaxSize
'if subsequent people have higher scores, place them as top score
If TopScores(Count).Score > TopScores(HighestRec).Score Then
HighestRec = Count
End If
Next
Console.Writeline("Top Score: " & TopScores(HighestRec).Name & " : " & TopScores(HighestRec).Score)
End Sub
Now we have written the code to find out the top score we also need to adjust the code so that the program can call it, by using a menu option, the option 5. See the code above on Resetting Top Scores to do this Draw a trace table for the following: Michael, 45 Dave, 78 Gerald, 23 Colin, 75
What about these people: Michelle, 67 Davina, 54 Geraldine, 13 Coleen, 67
|
Find the lowest score
[edit | edit source]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())
end sub |
Try to code this solution yourself:
Answer: | ||||||||||||||
Sub DisplayLowestScore(ByRef TopScores() As TTopScore)
Dim LowestRec As Integer 'stores the location of the highest record
Dim Count as Integer = 1
LowestRec = 1 'set the top score to the first person
For Count = 1 To MaxSize
'if subsequent people have higher scores, place them as top score
If TopScores(Count).Score < TopScores(LowestRec).Score Then
LowestRec = Count
End If
Next
Console.Writeline("Lowest Score: " & TopScores(LowestRec).Name & " : " & TopScores(LowestRec).Score)
End Sub
Now we have written the code to find out the top score we also need to adjust the code so that the program can call it, by using a menu option, the option 5. See the code above on Resetting Top Scores to do this Draw a trace table for the following: Michael, 45 Dave, 78 Gerald, 23 Colin, 45
What about these people: Michelle, 67 Davina, 13 Geraldine, 54 Coleen, 67
|
Printing out all the scores in order
[edit | edit source]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 | edit source]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:
*TopScores passed as a Parameter, should you use byRef or byVal? | ||
If you want to re-order the TopScores array you'll have to talk to the array directly by using ByRef. ByVal will only make a copy of the array so any changes made to re-order it will be lost when the subroutine finishes. |
- 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.
Answer: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Sub bubbleSort(ByRef TopScores() As TTopScore)
dim swapped as boolean
dim temp as TTopScore
'sort the elements
Do
Swapped = False
For Count = 1 to MaxSize - 1
If TopScores(Count).score < TopScores(Count + 1).score Then
temp.Name = TopScores(Count).Name
temp.Score = TopScores(Count).Score
TopScores(Count) = TopScores(Count + 1)
TopScores(Count + 1).Name = temp.Name
TopScores(Count + 1).Score = temp.Score
Swapped = True
End If
Next
Loop While Swapped = True
'Print out the elements
For Count = 1 to MaxSize
Console.Writeline(Count & ": " & TopScores(Count).Name & " " & TopScores(Count).Score)
Next
End Sub
Now we have written the code to order the scores we also need to adjust the code so that the program can call it, by using a menu option, the option 5. See the code above on Resetting Top Scores to do this Draw a trace table for the following: Michael, 45 Dave, 78 Gerald, 23 Colin, 75
|
Hard Coded
[edit | edit source]List the number of balls survived
[edit | edit source]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:
Answer: | ||||||||
A quick way to solve this is by adding the following as a local variable to the Dim BallsSurvived As Integer = 0
We then need to make sure number BallsSurvived is set to 0 for each new batter. At the beginning of the For PlayerNo = 1 To 2 ' give each player a turn
CurrentPlayerScore = 0 'reset the score
BallsSurvived = 0 'reset the number of balls survived
One successfully surviving each ball we want to increase the number of balls survived Console.WriteLine()
Console.WriteLine("Press the Enter key to continue")
Console.ReadLine()
If PlayerOut = False Then
BallsSurvived = BallsSurvived + 1
End If
Loop Until PlayerOut '=true
Finally we want to boast how many balls they survived Loop Until PlayerOut '=true
Console.WriteLine("You are out. Your final score was: " & CurrentPlayerScore)
Console.WriteLine("You survived " & BallsSurvived &" balls")
Console.WriteLine()
Extensions:
|
Keeping track of sixes and fours
[edit | edit source]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:
Answer: | ||
A quick way to solve this is by adding the following as global variables: Dim numofsixes As Integer
Dim numoffours As Integer
We then need to make sure they are set to 0 for each new batter. At the beginning of the
For PlayerNo = 1 To 2 ' give each player a turn
CurrentPlayerScore = 0 'reset the score
numofsixes = 0
numoffours = 0
We then need to catch each time that they score a four or a six. Remember that the a 3 on the die means four runs are scored, a 4 means six runs. If BowlDieResult >= 1 And BowlDieResult <= 4 Then
RunsScored = CalculateRunsScored(BowlDieResult)
If BowlDieResult = 4 Then
numofsixes = numofsixes + 1
End If
If BowlDieResult = 3 Then
numoffours = numoffours + 1
End If
Finally we need to display the score when they are finally out: Loop Until PlayerOut '=true
Console.WriteLine("You are out. Your final score was: " & CurrentPlayerScore)
Console.WriteLine("You got:" & numoffours & " fours and " & numofsixes & " sixes.")
Alternatively you could add the information on fours and sixes to the record definition: Structure TTopScore
Dim Name As String
Dim Score As Integer
Dim sixes As Integer '##
Dim fours As Integer '##
End Structure
but the coding is a little more complex. Give it a go if you're feeling brave. |
Changing the number of scores in stored in TopScores
[edit | edit source]We'll need to adjust some things here
Answer: | ||
Adjust the MAXSIZE to equal 5 We'll also need to add an extra set of values to the HiScores.txt file. As it will produce an error when loading them for the first time, as it will try to load a line that doesn't exist. |
Adding a new way to be out
[edit | edit source]Add a new way to be out: "Run out".
Answer: | ||
To do this we are going to have to add another option to our appeal dice, making it 5 sided. We are then going to have to build a new case for case 5 which will say "Run out" |
Drawn games
[edit | edit source]- 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:
- Neither score is recorded
- Both scores are recorded, adding an extra record (element) to the array
- Concatenate both player names and assign the playerName field of the Topscores array of records
- 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 | edit source]- Randomise who goes first
Manual dice validation
[edit | edit source]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?
Answer: | ||
Try
BowlDieResult = Console.ReadLine 'line that causes the problem
Catch ex As Exception
BowlDieResult = 0 'set the result to an acceptable error number so they don't score any points for this misbowl
End Try
This is also a problem with the Try
AppealDieResult = Console.ReadLine 'line that causes the problem
Catch ex As Exception
AppealDieResult = 0 'set the result to an acceptable error number so they don't score any points for this appeal
End Try
This is also a problem with the Try
OptionChosen = Console.ReadLine 'line that causes the problem
Catch ex As Exception
OptionChosen = 0 'set the result to an acceptable error number so you don't accidentally launch any functions
End Try
|
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:
Answer: | ||
Dim validation As Boolean = False
Do
Try
BowlDieResult = Console.ReadLine
validation = True
Catch
Console.WriteLine("Invalid answer. Try again.")
End Try
Loop Until validation = True
|
Only one entry per person
[edit | edit source]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 | edit source]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 | edit source]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 executed 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 | edit source]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