Visual Basic Console - A global command & anwser string - vb.net

I'm creating a text based adventure game and would like to create some "global" commands & answers that would work anywhere no matter where your are.
For the global answers if we look at this code:
Module Module1
Private Property answer1 As String
Private Property answer2 As String
Sub Main()
Console.WriteLine("Welocome to the training grounds!")
Console.WriteLine("What would you like to do? 1. I would like to train on dummies")
answer1 = Console.ReadLine()
Console.WriteLine()
If answer1 = "1" Then
Console.WriteLine("Okay what now?")
answer2 = Console.ReadLine
If anwser2 = "1" Then Console.WriteLine("New option")
End If
End Sub
Now as you can see I have to create a string for each new user input. I've tried doing a Public answer As String = Console.ReadLineand then having things react to "answer" but if I reused a keyword like number 1 in the code up top the program would not wait for user input and just go down the path of number 1. The first option seems like a bunch of spagettih code and the second option dose not seem to work or I myself am not getting it to work so any tips here would be nice.
I also want to know if its possible to create a global string or something of sorts. Say that No matter if I were at the point where I'm supposed to give input to answer 1 or 2, if I typed in "inventory" it would open another sub called inventory. Now if possible I want to do this without having to have an if answer = inventory then Inventory().
Thanks in advance everyone ^^
Following the advice given to my by b.pell I was able to create a "GameCommand" sub but I'm not facing a problem of how to implement these into the game itself/make them accessible without ruining the game flow. I am also not quite sure how to write a command that will modify something in GameCommand.vb (say if the player gets an item how would I add it to the inventory list?)
Module Module1
Sub Main()
Dim gc As New GameCommand
If Console.ReadLine = "Go to dummy" Then Dummy() Else
CallByName(gc, Console.ReadLine, CallType.Method, "")
End Sub
Sub Dummy()
Console.WriteLine("I am dummy hear me roar")
Return
End Sub
End Module
The GameCommand.vb file is the same as the one in p.bells comment.

You could create an interpreter function that processes all commands and then do what you want in there. So everytime you read input in, just pass it along to the interpreter sub.
interpreter("inventory")
Inside of there, you could do an break that command up (if it has arguments). You could do a basic if statement and execute your sub procedures based off of that.
You could go above and beyond (this answers your question about not having to write the if's) and create a commands class that has all of your command methods on it (Inventory, North, South, East, West, Look) and then when a user enters a command you could use reflection to check that class to see if that sub/function exists (and then invoke it). The benefit of that is, as you add methods to the command class you never have to update the if logic in your interpreter again.
E.g. You would add a "Public Sub Look()" or "Public Sub Look(args as String) to the command class.. your interpreter would then try to invoke the look command (and maybe pass it the rest of the arguments). If it wasn't found, you tell the user it wasn't a command, if it was, it executes (google search for invoking). This means as you add Sub's the interpreter just picks them up.
Here is an MSDN article that should get you going, you can use VB's CallByName function, you pass it your class, then the proc name you want to execute with an args:
https://msdn.microsoft.com/en-us/library/chsc1tx6(v=vs.80).aspx
Here is a simple example (a console application):
Module1:
Module Module1
Sub Main()
Dim gc As New GameCommand
CallByName(gc, "Inventory", CallType.Method, "")
' Will look south
CallByName(gc, "Look", CallType.Method, "south")
' Will try to look southeast, but we don't accept that as a valid direction
CallByName(gc, "Look", CallType.Method, "southeast")
Console.ReadKey()
End Sub
End Module
GameCommand.vb:
Public Class GameCommand
Sub New()
End Sub
Public Shared Sub Inventory(arg As String)
Console.WriteLine("Execute code to show inventory")
End Sub
Public Shared Sub Look(direction As String)
If direction <> "north" And _
direction <> "south" And _
direction <> "east" And _
direction <> "west" Then
Console.WriteLine("Invalid direction")
Exit Sub
End If
Console.WriteLine("You look " & direction)
End Sub
End Class

I would approach a text adventure with the following general structure:
In your main module, loop through the process of each "turn".
Display any prompt text, as appropriate.
Read user input.
Check list of global commands and execute as appropriate.
Check list of local commands and execute as appropriate.
Display an error message.
Each command you execute should do the following:
Calculate changes to any variables affected by the command
Check each possible change in game status from the top down, and execute subsequent commands as appropriate. (E.G. Player death, mission success/failure, enemy defeated, etc.)
Advance the story to the appropriate place.

Related

Microsoft Project VBA to update Custom field on task change

I have been wracking my brain trying to work out how to write a small piece of code that will activate only when particular fields at a task level have been modified.
I tried to make this code work at the project change level with a for each loop and select cases but that lags the whole program and still doesn't give me the result I need. I also tried to make it work when run manually with a for each loop and select cases or a bunch of If statements, but again, it can't tell me which field changed, but it can highlight a discrepancy between two fields.
The goal is to have a change log field (Text10) that auto updates based on the field that is modified and the date of the change. I only care about 4 fields changing (Date1, Date2, Date3, Date4).
e.g. If [Date1] is modified, Text10 = "Date1 modified 10/11/21"
Note: If 2 fields are modified, I would be happy enough with just listing the last one.
I was hoping there was some sort of "On Change, If Target = xxx" but I have not been able to find anything like that.
I also tried implementing the code as defined here >> Microsoft Documents: Project.Change Event but I am unclear what this is supposed to do and couldn't actually see it doing anything / I never got the message box I believe was supposed to appear.
I am using Microsoft Project Standard 2019.
After much research and trial and error, I ended up solving this.
To get it working, I added a Class Module and ran a piece of code on open to initialize it. This essentially tells Project to start watching for events. I then use the "Field" variant to fill the field name amongst the text string and "NewVal" variant to fill the result. This was an easy solution in the end. The code I found that worked is below:
In Class Module "cm_Events"
Public WithEvents MyMSPApp As MSProject.Application
Private Sub Class_Initialize()
Set MyMSPApp = Application
End Sub
Private Sub MyMSPApp_ProjectBeforeTaskChange(ByVal tsk As Task, ByVal Field As PjField, ByVal NewVal As Variant, Cancel As Boolean)
'What you want the code to do
End Sub
In Module "m_Events"
Public oMSPEvents As New cm_Events
Sub StartEvents()
Set oMSPEvents.MyMSPApp = MSProject.Application
End Sub
In ThisProject code
Private Sub Project_Open(ByVal pj As Project)
Call m_Events.StartEvents
End Sub

What role do modules play in writing code that is less memory-intensive?

At work, I'm venturing into the world of VBA to try to make a spreadsheet template, which will run a report when a command button is clicked.
On several occasions I have encountered an "Out of Memory" run-time error, which has been easily fixed by jigging with the code using combinations of suggestions by other users on this site!
However, I am now curious about how to make codes less memory-intensive. In particular, do modules help make code less memory-intensive and, if so, how should I use them effectively in this regard? For example, should I assign a Sub to each module or would that be overkill?
I'm new to VBA so any/all help and criticism is very welcome!
Short answer: None whatsoever.
An "out of memory" error has many reasons to occur, none of which relate to how many modules your code is organized in. Because that's all modules are: an organizational tool at your disposal.
It is completely impossible to diagnose an "out of memory" error without knowing anything about what the code is doing: <meta> you would have to narrow down the problem to a particular specific piece of code before you can ask about it on this site (dumping a whole module and asking "what's wrong with this code?" isn't going to fly) </meta>
Look for very, very large arrays, clipboard operations perhaps. Maybe you're running a loop that's attempting to create and store a bajillion New large objects - could be pretty much anything... just not the number of modules.
should I assign a Sub to each module or would that be overkill?
As with many things, the answer is "it depends". Once you've familiarized yourself with the language, and start learning about Object-Oriented Programming (OOP), you'll come across design guidelines and principles that strongly advocate towards keeping public interfaces as simple as possible: adhering to the Interface Segregation Principle (the "I" of "SOLID") means that this is a perfectly complete and acceptable class module to have:
'#Interface IComparable
'#ModuleDescription("Defines a generalized type-specific comparison method that a class implements to order or sort its instances.")
Option Explicit
'#Description("Compares the current instance with another object of the same type and returns an integer that indicates whether the current instance precedes (-1), follows (1), or occurs in the same position in the sort order (0) as the other object.")
Public Function CompareTo(ByVal other As Object) As Integer
End Function
But that's well beyond beginner-level procedural programming concepts.
[...] which will run a report when a command button is clicked.
That command button could be an ActiveX button, or a Shape object that's attached to a macro. Assuming it's ActiveX, the worksheet's code-behind might look like this:
Option Explicit
Private Sub RunSalesReportButton_Click()
SalesReportMacro.CreateWeeklyReport
End Sub
And then you can have a SalesReportMacro standard module that might start out like this:
'#ModuleDescription("A macro that generates the weekly sales report.")
Option Explicit
Option Private Module
'#Descrition("Generates the weekly sales report.")
Public Sub CreateWeeklyReport()
If Not RefreshSalesData Then Exit Sub
CreatePivotReport
End Sub
The Public procedure at the top has a very high abstraction level: it's easy to tell at a glance what the procedure is going to be doing, because the lower-level procedures have meaningful names that tell us exactly what's going on. These lower-level procedures would be right underneath:
Private Function RefreshSalesData() As Boolean
Dim reportWeek As String
reportWeek = PromptForReportWeek
If Not IsNumeric(reportWeek) Then Exit Function
On Error GoTo CleanFail
AdjustDataConnectionCommand reportWeek
RefreshSalesData = True
CleanExit:
Exit Function
CleanFail:
MsgBox "Could not refresh the data for week " & reportWeek & ".", vbExclamation
Resume CleanExit
End Function
And the deeper you go, the lower the abstraction level becomes:
Private Function PromptForReportWeek() As String
Dim currentWeek As Integer
currentWeek = GetCurrentWeekNumber
PromptForReportWeek = InputBox("Please specify week#", "Generate Report", currentWeek)
End Function
Private Function GetCurrentWeekNumber()
GetCurrentWeekNumber = 42 'todo
End Function
Private Sub AdjustDataConnectionCommand(ByVal weekNumber As String)
With ThisWorkbook.Connections(1).OLEDBConnection
.CommandText = "dbo.WeeklySalesReport " & weekNumber
.Refresh
End With
End Sub
Private Sub CreatePivotReport()
'todo
End Sub
Procedures (Sub, Function, and others you'll discover in due time) should have one purpose, and be kept as short and focused on that one task as possible. By doing that, you make it easier to later refactor your code - say in 2 months you need to add another ActiveX button for a new CreateWeeklyInventoryReport macro: there's a good chance that this new report will also need to PromptForReportWeek - if that functionality is already well-abstracted into its own scope, you can more easily just remove/cut it from that module, add a new CalendarParameterPrompt module (which... might end up only having that procedure for a while.. until you need a good place to put a similar PromptForReportMonth function), add/paste it in there, make it Public, and invoke it from any other macro/module.
Doing this instead of massive omnipotent procedures that know everything and do everything (and eventually stop compiling because they got so large VBA refuses to compile them), will spare you lots of headaches in the near future.

Visual Basic - newbie question - unable to assign user input to a class property

I am trying to take the user input and assign it to a property defined in a class. When I run the program, it asks for user input as expected, but displays a different result. Can someone point out where my mistake is ?
I was trying to base my simple program on this tutorial
https://learn.microsoft.com/en-us/dotnet/core/tutorials/vb-with-visual-studio
but trying to extend it to classes.
I am using the latest version of Visual Studio and Visual Basic. It's a visual basic Console App
Module Module1
Sub Main()
Dim ClassInstance As New Class1()
Console.WriteLine("Input Property 1: ")
ClassInstance.Property1 = Console.Read()
Console.Write(ClassInstance.Property1)
Console.ReadKey(True)
End Sub
Public Class Class1
Public Property1 As Integer
Public Property2 As Integer
End Class
End Module
Expected output:
"Input Property 1:" |
User input 50 |
Output 50
Console.Read reads the next character from the input, and gives you that character's code. If, for instance, you typed 5 at the prompt1, Console.Read would return 53. Why? Because that's the ASCII/Unicode code for that character (in Unicode terms, it's U+0035, which is the same number represented in hexadecimal).
If you want to read multiple characters and interpret them as an integer, you should a) be using something other than Console.Read to take the input and b) use Int32.TryParse to try to turn it into a number (because users don't always give us the input we expect).
Something like:
Module Module1
Sub Main()
Dim ClassInstance As New Class1()
Console.WriteLine("Input Property 1: ")
Dim inp = Console.ReadLine()
Dim value as Int32
If Int32.TryParse(inp, value) Then
ClassInstance.Property1 = value
Console.Write(ClassInstance.Property1)
Console.ReadKey(True)
End If
End Sub
Public Class Class1
Public Property1 As Integer
Public Property2 As Integer
End Class
End Module
(With apologies if I've made syntax errors - my VB's quite rusty)
In reality, you'd probably want to write some form of loop that prompts for user input and doesn't terminate until it successfully parses. I think Do/While would fit there - but if you're going to prompt the user more than once, you probably would want to extract the "Loop until valid input received" code into a function that takes the prompt as a parameter.
More reading - ASCII/Unicode. For characters in the "7-bit ASCII" range, basic latin characters without accents, it doesn't make much difference which references you check
1And it doesn't matter if you carried on and typed any more characters, your program only asks for/gets one of them

VB.Net App Stuck in Loop

I would just like the program to end itself.
Application.Exit just keeps rolling me back in a loop.
EDITED to Include Code::
Module Module 1
Sub Main()
Sub1()
Sub2()
End Sub
Sub1()
EndSub
Sub2()
End Sub
End Module
EDIT: It seems to be looping back here to Sub ChooseDomain2.. I am including Sub 1 as well.
Sub ChooseDomain1()
Dim DomainName As Object
'Get List of all users on Domain using WinNT
DomainName = InputBox(messageOK, Title, defaultValue)
de.Path = "WinNT://****".Replace("****", DomainName)
If DomainName Is "" Then ChooseDomain2() Else StoreUserData1()
End Sub
Sub ChooseDomain2()
MsgBox("Welcome to the Domain Searcher. Click OK to Auto Search for Domain")
Dim MsgBoxResult As Object = ActiveDirectory.Domain.GetCurrentDomain.Name
MsgBoxResult = InputBox(messageCan, Title, MsgBoxResult)
de.Path = "WinNT://*****".Replace("*****", MsgBoxResult)
StoreUserData1()
End Sub
When it hits end Module it Just starts back from Square one.
Modules don’t execute at all – so it never “hits end module” and never starts “from square one”. Modules merely group methods that can be executed, and Main is a special method that serves as the start of your application.
That said, your code is guaranteed (!) not to execute repeatedly. Also, there is no Application.Exit anywhere in your code so it’s hard to see what you are actually executing. Not the code you showed, anyway.
Note that VB potentially executes code that you didn’t write (code can be auto-generated by the compiler, in particular the application framework) but this doesn’t seem to be happening in your case, and shouldn’t loop in any case. But again, this is impossible to say from the information you have given.
Application.Exit is not required as the console app will quit after it finishes executing the last line in Sub Main. As previously mentioned it is likely you have Sub1 calling Sub2 (or something similar), so set a breakpoint on the start of each sub to find which one is continually being called. Then you can do a search in your code to find where this sub is being called from.

Profiling VBA code for microsoft word

I have some legacy code that uses VBA to parse a word document and build some XML output;
Needless to say it runs like a dog but I was interested in profiling it to see where it's breaking down and maybe if there are some options to make it faster.
I don't want to try anything until I can start measuring my results so profiling is a must - I've done a little searching around but can't find anything that would do this job easily. There was one tool by brentwood? that requires modifying your code but it didn't work and I ran outa time.
Anyone know anything simple that works?
Update: The code base is about 20 or so files, each with at least 100 methods - manually adding in start/end calls for each method just isn't appropriate - especially removing them all afterwards - I was actually thinking about doing some form of REGEX to solve this issue and another to remove them all after but its just a little too intrusive but may be the only solution. I've found some nice timing code on here earlier so the timing part of it isn't an issue.
Using a class and #if would make that "adding code to each method" a little easier...
Profiler Class Module::
#If PROFILE = 1 Then
Private m_locationName As String
Private Sub Class_Initialize()
m_locationName = "unknown"
End Sub
Public Sub Start(locationName As String)
m_locationName = locationName
MsgBox m_locationName
End Sub
Private Sub Class_Terminate()
MsgBox m_locationName & " end"
End Sub
#Else
Public Sub Start(locationName As String)
'no op
End Sub
#End If
some other code module:
' helper "factory" since VBA classes don't have ctor params (or do they?)
Private Function start_profile(location As String) As Profiler
Set start_profile = New Profiler
start_profile.Start location
End Function
Private Sub test()
Set p = start_profile("test")
MsgBox "do work"
subroutine
End Sub
Private Sub subroutine()
Set p = start_profile("subroutine")
End Sub
In Project Properties set Conditional Compilation Arguments to:
PROFILE = 1
Remove the line for normal, non-profiled versions.
Adding the lines is a pain, I don't know of any way to automatically get the current method name which would make adding the profiling line to each function easy. You could use the VBE object model to inject the code for you - but I wonder is doing this manually would be ultimately faster.
It may be possible to use a template to add a line to each procedure:
http://msdn.microsoft.com/en-us/library/aa191135(office.10).aspx
Error handler templates usually include an ExitHere label of some description.. The first line after the label could be the timer print.
It is also possible to modify code through code: "Example: Add some lines required for DAO" is an Access example, but something similar could be done with Word.
This would, hopefully, narrow down the area to search for problems. The line could then be commented out, or you could revert to back-ups.
Insert a bunch of
Debug.Print "before/after foo", Now
before and after snippets that you think might run for long terms, then just compare them and voila there you are.
My suggestion would be to divide and conquer, by inserting some timing lines in a few key places to try to isolate the problem, and then drill down on that area.
If the problem is more diffused and not obvious, I'd suggest simplifying by progressively disabling whole chunks of code one at a time, as far as is possible without breaking the process. This is the analogy of finding speed bumps in an Excel workbook by progressively hard coding sheets or parts of sheets until the speed problem disappears.
About that "Now" function (above, svinto) ...
I've used the "Timer" function (in Excel VBA), which returns a Single.
It seems to work just fine. Larry