Storing story data for text adventure AS3 - arrays

I've been on and off creating a text adventure using a rudimentary engine I made for about a year.
For the story, I have a object array(is that what it's called?) with various story data stuff that I parse through
I've been told that using it the way I am is stupid because it's supposed to be used for other stuff but I only use it because it was easy to learn how to parse the data since I was a beginner
It's getting tedious writing the story and doing stuff for each part (creating the backgrounds and such) since it's so long.
Is there any kind of way I can make it easier for me to write the story?
Here's the object array with a single part set up (with choices)
public static var parts:Object =
{
"0":
{
"text":"Text here",
"choices":
{
"response1":
{
"text":"Response1",
"nextPart":"1"
},
"response2":
{
"text":"Response2",
"nextPart":"2"
}
},
"background": Assets.AssetClass.Background1,
"BGM":"bg1"
},
}
Here's an example of how my engine deals with parts and changing them:
I have a input checker to check when enter is pressed and then do stuff depending on what is on the screen
public function onEnter(button:KeyboardEvent):void
{
if (button.keyCode == 32 && !Won)
{
if (Dead && textFinished && !choosing) // pressing enter while dead and the text is still writing
{
curPart = parts[curPart]["lastPart"] // lastPart will only be stored in parts that have the player die
textFinished = false
Dead = false;
myTextField.text = ""
counter = 0;
sInt = setInterval(addCharackter, textSpeed)
if (stage.getChildByName("cText"))
{
stage.removeChild(continueText)
}
if (parts[curPart].hasOwnProperty("background")) //check if the background needs to change.
{
if (stage.getChildByName("img"))
{
stage.removeChild(background)
}
background = new Background(parts[curPart], 800, 600)
stage.addChildAt(background, 0)
}
}
if (!textFinished && !choosing)// pressing enter when there's no choices on the screen and the text isn't finished and the text is still writing
{
this.myTextField.text = this.parts[this.curPart]["text"];
clearInterval(this.sInt);
this.textFinished = true;
if (parts[curPart].hasOwnProperty("choices"))
{
choosing = true
createOptions(); // function for parsing through the responses bit of that part and displaying them
}
else
{
stage.addChildAt(continueText, 2)
}
if (parts[curPart].hasOwnProperty("lastPart"))
{
Dead = true;
dead()
}
}
else if (textFinished && !choosing && !Dead) // pressing enter if there's no choices on the screen and there's no choices (it'll take you to the next part)
{
trace("Text finished!!")
curPart = parts[curPart]["nextPart"]
myTextField.text = ""
counter = 0;
sInt = setInterval(addCharackter, textSpeed)
textFinished = false;
if (parts[curPart].hasOwnProperty("background"))
{
if (stage.getChildByName("img"))
{
trace("Removed!")
stage.removeChild(background)
}
background = new Background(parts[curPart], 800, 600)
stage.addChildAt(background, 0)
}
if (parts[curPart].hasOwnProperty("BGM")) // does the current part have a new background music?
{
trace("Music!!!!")
sndBGMusic = musicArray[parts[curPart]["BGM"]]
sndBGMusicChannel.stop()
sndBGMusicChannel = sndBGMusic.play(0, 9999999999)
stage.addChildAt(background, 0)
}
stage.removeChild(continueText)
}
}
}

A couple ideas here. These are just things I would do differently than what you have done. I don't guarantee that they are better in any way, but see what you think.
I would have a global variable for _curPart. And I would have a custom class called Part. That class would have properties like _BGM, _bgImage etc. It could have a _choicesArray as a property as well. I'd have other global variables like _hasCandle. Or you can store items in an array and if you need the candle just check if candle is in the array. These global variables will persist from one part to the next.
Then you can access the properties of the part you are in by doing _curPart._bgImage. To me, that looks and feels cleaner.
And to create a new part it could look like (incomplete):
var p15:Part = new Part();
p15._bgImage = image15;
p15._BGM = song10;
//...
The last thing I'd recommend is to try to refactor where you can. For example, where you have //pressing enter if there's no choic... replace all of that code in that bracket with a one or a few function calls (whatever makes the most sense and allows you to reuse code). It just makes it easier to see what's going on, I think. So instead of all these if blocks, just a function like nextPart(); and then that function will have all your if blocks in it. Make sense? Personal preference, but when things are getting complicated, refactoring helps me clear out the cobwebs. Just like you do with dead() and createOptions() but I'd just take it one step further. This won't make your code more efficient, but it might make writing your code more efficient which is paramount in my book (until it's not).

Related

How to move a part of text in textBox to another textBox

I have a .txt with content inserted in textBox1 and I already show the content to textBox2.
I want to move the part else { a = c; } to another textBox3 and delete this part in textBox2. How can I do it?
The content of text:
a = Calculation();
all = a;
if (a == 0)
{
a = b;
}
**else
{
a = c;
}**
all = b;
all = c;```
In modern programming, there is a tendency to separate the data (=model) from how this data is displayed (=view). This enables changing how you display the data without having to change the model. You can also change the model, without having to change the view.
For this separation, we need adapter code that couples the Model to the View: quite often called the ViewModel. The abbreviation for this idea is MVVM. Google this if you want to k know more about the theory behind this.
"I have a .txt with content"
First of all: you form is not interested where your data comes from, hide this in your model.
string GetText() {... // Read from file }
If later you decide that text should be read from a different source, for instance from a database, or from the internet, or maybe you decide that you ask the operator to type the text, the rest of your model and view won't have to change.
Besides: it will be easier to unit test this part.
"inserted in textBox1 and I already show the content to textBox2. I want to move the part else { a = c; } to another textBox3 and delete this part in textBox2"
I think you mean to say, that from the text that you fetched in GetText(), you want to show the "else" part (TODO: define the else part), in textBox3, and show the complete text without the "else" part in textBox2. The complete text should be "inserted" in textBox1.
So we need a method to separate your test into two: the "Else" part and the rest. Furthermore we need procedures to "Insert" the complete text, the "Else" text and the "Rest" text:
void InsertText (string text)
{
// not sure what "Insert" means. Do you mean replace?
// or add to the current cursor position?
this.textBox1.Text = ...
}
void ShowElsePart(string elseText)
{
this.textBox3.Text = elseText;
}
void ShowRest(string restText)
{
this.textBox2.Text = restText;
}
So I've hidden how I view "Else" part, the "Rest" part and what I mean with "Insert text". If later you decide you don't want to show it in a text Box, but for instance in a RichTextBox, or in a Listview, only one procedure needs to change.
We need to divide the text into the "Else" part and the "Rest".
You defined the "Else" part as the part else { a = c; }, so without the ** and more important: without new lines, or was this just the description, and could 'else { a = c; }be different text:else a = c;, or else { x = y; }or evenelse { while (x) {...}`?
This uncertainty is a good example of why you should separate the model from the view: if you decide to change your definition of the "Else" part, outsiders don't have to change.
void DivideText(string input, our string elsePart, out string rest)
{
// TODO implement
}
First we'll put it all together, then we'll do the implementation:
void DisplayInput()
{
string text = this.GetText();
this.InsertText(text);
this.DivideText(text, out string elseText, out string restText);
this.ShowElsePart(elseText);
this.ShowRest(restText);
}
Apart from that it is fairly easy to understand what happens, this code is highly reusable, easy to change and easy to text. If you want to read your text from a database, instead of plain text file, only one procedure needs to change. If you don't want to insert the text in textBox1, but in a file: only one place to change. If you define "Else" part differently: again only place to change.
Now about the "Else" part. According to your definition, "Else" is fairly simple:
const string elsePart = "else { a = c; }"
int elseStartIndex = text.IndexOf(elsePart, 0);
if (elseStartIndex < 0)
{
// elsePart not found
string restText = text;
}
else
{
string restText = text.Remove(elseStartIndex, elsePart.Length;
}
It could be that you meant something else with "Else" part: everything from the first else until the first }.
int indexStartElse = text.IndexOf("else", 0);
// TODO: what if no else?
int indexEndElse = text.IndexOf("}", indexStartElse);
// TODO: what if no }?
int elseTextLength = indexEndElse - indexStartElse;
string elseText = text.SubString(indexStartElse, elseTextLength);
string restText = text.Remove(indexStartElse, elseTextLength);

Processing - How to log Strings in txt file?

Im getting more and more frustrated on why this is not doing what i want to. I need to have a text file that logs the last 10 buttons the user pressed, i tried in two ways but…
…this code only saves the last button pressed:
String [][] buttonsPressed = { {"Pressed one"}, {"Pressed two"} };
String[] sum;
void setup() {
size(700, 350);
sum = loadStrings("scores.txt");
}
void draw() {
}
void keyPressed() {
if ((key=='1')) {
saveStrings("scores.txt", buttonsPressed[0]);
}
if ((key=='2')) {
saveStrings("scores.txt", buttonsPressed[1]);
}
if ((key == ENTER)) {
printArray(sum);
}
}
…this code saves the buttons pressed but overwrites itself when i run the sketch again (probably has something to do with createWriter):
PrintWriter sum;
void setup() {
size(700, 350);
sum = createWriter("scores.txt");
}
void draw() {
}
void keyPressed() {
if ((key=='1')) {
sum.println("pressed one");
}
if ((key=='2')) {
sum.println("pressed two");
}
if ((key == ENTER)) {
sum.flush();
sum.close();
String[] lines = loadStrings("scores.txt");
for (int i = 0; i <= 9; i++) {
printArray("[" + i + "] " + lines[i]);
}
}
}
Is there a simple way to do this without using libraries ? Please get me in the right direction :)
Break your problem down into smaller steps.
Step 1: At the beginning of your program, read in the existing history file. Store them in some kind of data structure, like an array or an ArrayList.
Step 2: Make the data structure keep track of the last 10 buttons pressed. You'll need to write code that adds the newest button pressed to the end of the data structure, and if the data structure contains more than 10 items, you have to remove the oldest item. Maybe do this in a separate example sketch where you just print the data structure to the console and don't worry about outputting it to a file just yet.
Step 3: Output the data structure to a file. You don't have to worry about appending to the file because your data structure contains the entire history, so you can just overwrite the file with the entire data structure. You'll probably want to do this every time the data structure changes. Again, maybe do this in a separate example program before worrying about where the data is coming from: maybe make a program that outputs an array of random numbers every time the user clicks?
Focus on one small step at a time, and try creating small example programs for each step. Then when you get stuck, you can ask a specific question and provide an MCVE, and we'll go from there. Good luck.

hitTestPoint or hitTestObject?

I'm needing to make my character land on a ledge and stay there, but it only keeps going straight through it. Would I create an array for all my different ledges and test whenever my character hits them? Any help would be appreciated.
Thanks.
Collision detection for floors and stuff is actually alot diffrent from hitTesting in the idea that needs to consistently see that the objects are touching. Try something like this!
//loop through all the platform objects to generate the level
var level:Array = new Array();
for (var i=0; i<numChildren; i++)
{
if (getChildAt(i) is platform)
{
level.push(getChildAt(i).getRect(this));
}
}
for (i=0; i<level.length; i++)
{
if (player.getRect(this).intersects(level[i]))
{
if (speedX > 0) ////moving right collision and stuffs
{
player.x = level[i].left-player.width/2;
}
if (speedX < 0) ////moving left collision and stuffs
{
player.x = level[i].right+player.width/2;
}
speedX = 0 //kills the speed
}
}
speedX is the speed at which the characters move horizontally, and "platform" is the name of the variable that you're using as the cliff. Also, "player" can be substituted by whatever you are calling your object that's going onto the ledge. That's how I did it in one of my computer classes anyways :) Hope that helps!

Kentico Global Events (ObjectEvents) Causes Loop

I'm using ObjectEvents to give ActivityPoints to current user based on fields user filled.
Now for example if user register and fill FirstName I will give 10 points to user.
The problem is that I'm handling ObjectEvents.Update.After and inside it I'm updating userSettings.This causes a unlimited loop and application stops working.
is there any work around?
this is the code block:
var className = e.Object.TypeInfo.ObjectClassName;
DataClassInfo dci = DataClassInfoProvider.GetDataClass(className);
if (dci != null)
{
var fi = new FormInfo(dci.ClassFormDefinition);
if (fi != null)
{
var stopProccess = true;
var fields = new List<FormFieldInfo>();
foreach (var changedColumn in e.Object.ChangedColumns())
{
var field = fi.GetFormField(changedColumn);
var activityPointMacro = ValidationHelper.GetString(field.Settings["ActivityPointMacro"], "");
if (!string.IsNullOrEmpty(activityPointMacro))
{
fields.Add(field);
stopProccess = false;
}
}
if (!stopProccess)
{
var contextResolver = CMSContext.CurrentResolver.CreateContextChild();
foreach (FormCategoryInfo info in fi.ItemsList.OfType<FormCategoryInfo>())
{
contextResolver.SetNamedSourceData(info.CategoryName, info);
}
EditingFormControl data = new EditingFormControl();
foreach (FormFieldInfo info2 in fi.ItemsList.OfType<FormFieldInfo>())
{
contextResolver.SetNamedSourceData(info2.Name, data);
}
foreach (var field in fields)
{
{
var activityPointMacro = ValidationHelper.GetString(field.Settings["ActivityPointMacro"], "");
var activityPoint =
ValidationHelper.GetInteger(contextResolver.ResolveMacros(activityPointMacro), 0);
CMSContext.CurrentUser.UserSettings.UserActivityPoints += activityPoint;
CMSContext.CurrentUser.UserSettings.Update();
}
}
}
}
}
If you just need to give points for user fields then you could just use ObjectEvents.Update.Before, check fields are not empty and assign points. But i can see from the code, you want to have something more complex bulit over macro expressions. So I have a few suggestions for you.
1) ObjectEvents.Update.Before instead of ObjectEvents.Update.After still may be a good idea. Ideally you set your additional values and all is set during one update.
2) Watch only the class names you need
3) Always prefer Provider.SetInfo methods over info.Update(). In case of user settings it's best to set whole user info, so UserInfoProvider.SetUserInfo. Provider methods may add some additional important logic.
4) The code seems like it'll add the points with every update of a user
5) if you are still running into a loop, you need to flag somehow, that some part of code should not be executed again. The best way is to use RequestStockHelper class - add a bool value with a specificname like "PointsProcessed".

Trouble with array order

I have a couple of if statements within a timer function, checking if the enemy's hp is 0. The enemies are added into an array. When one enemy hp hits 0, they are taken from the array and removed. When the array length is 0, the game goes to the next level. For some reason, when the first enemy's hp reaches 0, then it automatically goes to the next level without even checking the other enemy's hp. It worked on the first level, where I only had 1 enemy, but the second level, I have two enemies. I would have to defeat the second enemy, then the first in order for it to go to the next level. I think it may have something to do with the first enemy being the first in the array. Any ideas on how I can change the code to make it disregard the order in which enemy is defeated first?
public var enemy:Enemy = new Enemy();
public var enemy2:Enemy = new Enemy();
public var enemyArray:Array = [];
Level Timer(Timer that sets up the level, runs once):
if (level.levelNumber == 1)
{
enemyArray.push(enemy);
addChild(player);
addChild(enemy)
enemy.enemyMoveTimer.start();
enemy.enemyAttackTimer.start();
onGameTimer.start();
}
if (level.levelNumber == 2)
{
enemyArray.push(enemy, enemy2);
addChild(player);
addChild(enemy)
addChild(enemy2)
enemy.enemyMoveTimer.start();
enemy.enemyAttackTimer.start();
enemy2.enemyMoveTimer.start();
enemy2.enemyAttackTimer.start();
onGameTimer.start();
}
Game timer(timer that checks the enemy lives, keeps running):
if (enemyArray.length == 0)
{
trace("NEXT LEVEL");
removeChild(player);//remove player from the current level
onLevelTimer.removeEventListener(TimerEvent.TIMER, timerLevel);
levelNumber += 1;
onLevelTimer.addEventListener(TimerEvent.TIMER, timerLevel);
onGameTimer.stop();
}
if (enemy.enemyHP <= 0)
{
enemy.enemyHP = 0;
enemy.enemyMoveTimer.stop();
enemy.enemyAttackTimer.stop();
trace("DEAD");
enemyArray.splice(0,1);
try
{
removeChild(enemy)
}
catch (e:ArgumentError){
}
}
if (enemy2.enemyHP <= 0)
{
enemy2.enemyHP = 0;
enemy2.enemyMoveTimer.stop();
enemy2.enemyAttackTimer.stop();
trace("DEAD");
enemyArray.splice(1,1);
try
{
removeChild(enemy2)
}
catch (e:ArgumentError) {
}
}
There's more code there that could be the cause of the issue, so I'm just going to assume that it looks something like what I've written below. It also dispenses with multiple if statements, in favor of simply iterating over your enemyArray, checking each as you wanted to (behold the power of loops!).
var enemyMoveTimer:Timer = new Timer(1000, 0);
enemyMoveTimer.addEventListener(TimerEvent.TIMER, enemyTimerListener);
enemyMoveTimer.start();
var enemyArray:Array = [new Enemy("enemy1"), new Enemy("enemy2")];
function enemyTimerListener(e:Event):void {
for (var i:String in enemyArray) {
var enemy:Enemy = enemyArray[int(i)];
if (enemy.enemyHP <= 0) {
enemy.enemyMoveTimer.stop();
enemy.enemyAttackTimer.stop();
trace("DEAD");
enemyArray.splice(int(i),1);
if (enemy.parent != null) {
removeChild(enemy);
}
}
}
}
What isn't shown is how you're detecting and advancing to "the next level". How are these enemies being added to the array? Since it seems you've rolled your own Enemy class, what's that class look like? Why are you using try ... catch?
In any case, if the above works, fun; otherwise, we'll need more code to know what's going on.

Resources