File I/O - very simple
Monkey Forums/Monkey Beginners/File I/O - very simple
| ||
| Hello friends! I need to make a simple code on the I/O file (eg. TXT). I have it like this: Import mojo Import brl Import filesystem Import fileio Global file$ Global data:Int,data2$ Class MyApp Extends App Field data:DataBuffer Method OnCreate() file="monkey://data/text.txt" data=DataBuffer.Load(file) data2=LoadString(file) SetUpdateRate 60 End Method OnUpdate() End Method OnRender() Cls DrawText data2,20,40 DrawText "data.Length="+data.Length,0,12 End End 'main Function Main() New MyApp End But unfortunately, the whole file uploads. I need to be able to first work with the first line, then the second line, and so on. Do you have any advice please? Thank you in advance! P. S. Monkey-X, target PC or HTML5. |
| ||
| If you are going to use data buffers you will have to walk the data to look for the new line marker (CR/LF, LF or CR depending on the operating system the file was saved). To do it with strings, you need to split the string into an array with the new line marker (~n in MonkeyX). So make data2$ in to an empty array with data2$[] Then use data2=LoadString(file).Split("~n") You can access any line with data2[n]. Note: When you post code you should use the code and /code or codebox and /codebox tags encased in square brackets e.g [/code] as opening and closing. You should also indent you code to make it more readable like so Import mojo Import brl Import filesystem Import fileio Global file$ Global data:Int,data2$ Class MyApp Extends App Field data:DataBuffer Method OnCreate() file="monkey://data/text.txt" data=DataBuffer.Load(file) data2=LoadString(file) SetUpdateRate 60 End Method OnUpdate() End Method OnRender() Cls DrawText data2,20,40 DrawText "data.Length="+data.Length,0,12 End End 'main Function Main() New MyApp End |
| ||
| Wow, super, thank you very much! ;) It works perfectly! Thanks! The final code:
Import mojo
Import brl
Import filesystem
Global file$
Global data:Int,data2$[]
Class MyApp Extends App
Field data:DataBuffer
Method OnCreate()
file="monkey://data/text.txt"
data=DataBuffer.Load(file)
data2=LoadString(file).Split("~n")
SetUpdateRate 60
End
Method OnUpdate()
End
Method OnRender()
Cls
DrawText data2[0],20,40
DrawText data2[1],20,60
DrawText data2[2],20,80
DrawText "data.Length="+data.Length,0,12
End
End
'main
Function Main()
New MyApp
End
|
| ||
| Hi zxretrosoft, I made this rather handy class a few years back, you're welcome to use this. ;-) Scroll down further on this comment for of the ways it can be used.
Class C_FileLoader
Field fileData:String[]
Field currentLine:Int
Method New(fileName:String)
fileData = LoadString(fileName).Split("~n")
currentLine = 0
End
Method ReadLine:String()
Local currentData:String
if Eof() = False
currentData = Self.fileData[Self.currentLine]
currentLine = (currentLine + 1)
Else
currentData = "Data Exhausted!"
EndIf
Return currentData
End
Method Eof:Bool()
if Self.currentLine > (fileData.Length() - 1)
Return True
Else
Return False
EndIf
End
Method Purge:Void()
Local counter:Int
For counter = 0 to (fileData.Length() - 1)
fileData[counter] = ""
Next
fileData =[]
End
End
And heres the way to use it in its simplest form, although you would most probably use it in other ways.
Local stream:C_FileLoader = New C_FileLoader("LevelData/testfile.dat")
While not stream.Eof()
Print(stream.ReadLine())
Wend
stream.Purge()
|
| ||
| Hello Steve, thank you very much! Unfortunately, I do not know how to use this class in my program? Can you please be more vivid? :-) Best whole code. Or use it in my code? P. S. I'am sorry, I bought Monkey-X Studio this week, and I'm acquainted with it. |
| ||
For simple testing with the C++-Tool target (no graphics). You can do it like so:Class C_FileLoader
Field fileData:String[]
Field currentLine:Int
Method New(fileName:String)
fileData = LoadString(fileName).Split("~n")
currentLine = 0
End
Method ReadLine:String()
Local currentData:String
if Eof() = False
currentData = Self.fileData[Self.currentLine]
currentLine = (currentLine + 1)
Else
currentData = "Data Exhausted!"
EndIf
Return currentData
End
Method Eof:Bool()
if Self.currentLine > (fileData.Length() - 1)
Return True
Else
Return False
EndIf
End
Method Purge:Void()
Local counter:Int
For counter = 0 to (fileData.Length() - 1)
fileData[counter] = ""
Next
fileData =[]
End
End
Function Main()
Local stream:C_FileLoader = New C_FileLoader("LevelData/testfile.dat")
While not stream.Eof()
Print(stream.ReadLine())
Wend
stream.Purge()
End Function
To include this within your own code you would put the class into a separate file and use Import to include the class, then create a data object either as a local/global variable, or as a field member of one of your own classes, or extend the class to make a new one. |
| ||
| Thanks for what you did there dawlane. I was assuming that zxretrosoft was already sort of comfortable with Monkey-X, hence why I didn't include the Function Main bit. ;-) |
| ||
| Here's a little somthing to get you going, it will output some strings and floats to the browser's console when compiled to HTML5. Graphics cannot render outside the OnRender loop, hence the text output; but you're probably clever enough to make further sense on how to do this once you get the jist of it. ;-) 1) Create a folder, name it "Project" or somthing similar. 2) Create a file named "LoadParse.monkey", create a floder named "LoadParse.data", and then create a subfolder inside that LoadParse.data and name it AppData. You can name the LoadParse bit any way you like as long as the monkey and data prefixes are present. Next open up LoadParse.Monkey, copy and paste the following code into the editor and then save it.
Strict
Import mojo
Global loader:C_FileLoader
Function Main:Int()
Local app:C_App = New C_App()
Return 0
End
Class C_App Extends App
Method OnCreate:Int()
F_Load("AppData/data01.dat")
Return 0
End
Method OnUpdate:Int()
Return 0
End
Method OnRender:Int()
Cls
Return 0
End
End
Function F_Load:Void(fileName:String)
Local lineData:String
If loader
loader.Purge()
loader = Null
EndIf
loader = New C_FileLoader(fileName)
While not loader.Eof()
lineData = loader.ReadLine()
F_ParseUserData(lineData)
Wend
loader.Purge()
End
Function F_ParseUserData:Void(cmdLine:String)
Local tokenList:String[]
Local command:String
tokenList = F_TokeniseCommandLine(cmdLine)
command = tokenList[0]
Select command
Case "Rect"
F_Rect(tokenList)
Case "Ellipse"
F_Ellipse(tokenList)
Case "Circle"
F_Circle(tokenList)
Default
'Do nothing!
End
End
Function F_Rect:Void(tokenList:String[])
Local x:Float, y:Float
Local width:Float, height:Float
x = Float(tokenList[1])
y = Float(tokenList[2])
width = Float(tokenList[3])
height = Float(tokenList[4])
Print("Item: " + tokenList[0])
Print("Coordinates: " + x + ", " + y)
Print("Area: " + width + " x " + height)
Print("")
End
Function F_Ellipse:Void(tokenList:String[])
Local x:Float, y:Float
Local width:Float, height:Float
x = Float(tokenList[1])
y = Float(tokenList[2])
width = Float(tokenList[3])
height = Float(tokenList[4])
Print("Item: " + tokenList[0])
Print("Coordinates: " + x + ", " + y)
Print("Area: " + width + " x " + height)
Print("")
End
Function F_Circle:Void(tokenList:String[])
Local x:Float, y:Float
Local radius:Float
x = Float(tokenList[1])
y = Float(tokenList[2])
radius = Float(tokenList[3])
Print("Item: " + tokenList[0])
Print("Coordinates: " + x + ", " + y)
Print("Radius: " + radius)
Print("")
End
Function F_TokeniseCommandLine:String[] (cmdLine:String)
'Take a string, split it up, put each part in an array and then return it.
Local outputList:String[]
Local commandName:String
Local valueList:String[]
Local index:Int
commandName = cmdLine[0..(cmdLine.Find(" "))].Trim()
valueList = cmdLine[(cmdLine.Find(" ") + 1)..].Trim().Split(", ")
outputList = outputList.Resize(valueList.Length() + 1)
outputList[0] = commandName
For index = 0 To(valueList.Length() -1)
outputList[index + 1] = valueList[index]
Next
Return outputList
End
Class C_FileLoader
Field fileData:String[]
Field currentLine:Int
Method New(fileName:String)
fileData = LoadString(fileName).Split("~n")
currentLine = 0
End
Method ReadLine:String()
Local currentData:String
if Eof() = False
currentData = Self.fileData[Self.currentLine]
currentLine = (currentLine + 1)
Else
currentData = "Data Exhausted!"
EndIf
Return currentData
End
Method Eof:Bool()
if Self.currentLine > (fileData.Length() - 1)
Return True
Else
Return False
EndIf
End
Method Purge:Void()
Local counter:Int
For counter = 0 to (fileData.Length() - 1)
fileData[counter] = ""
Next
fileData =[]
End
End
3 Open up notepad or notepad++, copy and paste the following text into the editor. Next click File->Save As, then name it "data01.dat", select "All Files" from the Save as type dropdown menu and then save it to LoadParse.data/AppData. Rect 50, 50, 100, 75 Ellipse 200, 200, 75, 100 Circle 300, 300, 50 Finally compile as HTML5 and then run it in the browser. Note that output will be outputted as text in this example. |
| ||
| I'm aware that my above explanation is a bit long winded, I'm better at doing than explaining. I hope it helps though. |
| ||
As I think that you may ask about this at some point. Here's a small example of string slicing and some of the String Class functionsFunction Main()
Local myVar:String="abcdefgh"
Local myArray:Int[]=[65,98,67,100,69,102,71,104] ' AbCdEfGh
Local myToArray:Int[]="MONKEY-X".ToChars() ' Convert a string to a number array
Print "myVar is 'abcdefgh'"
Print "myArray is [65,98,67,100,69,102,71,104] ' AbCdEfGh"
Print "myToArray is MONKEY-X"
Print "String are zero index arrays [0=a,1=b,etc]"
Print "Print the fourth letter ('d') using String.FromChar(myVar[3]) this would be equivalent to Chr$(numeric value) -> " + String.FromChar(myVar[3])
Print "Print the ascii character code for the fourth letter ('d') with myVar[3] this would be equivalent the Asc(character_at_variable_index) -> " + myVar[3]
Print String.FromChar(34)+"A"+String.FromChar(34)+ "[0] would be the same as doing Asc(" + String.FromChar(34) + "A" + String.FromChar(34) + ") -> " + "A"[0]
Print "Print the letters 'abcde' using what would be the equivalent of Left(myVar, length) with myVar[..5] -> " + myVar[..5]
Print "Print the middle letters 'def' using what would be the equivalent of Mid(myVar, start, length) with myVar[3..((myVar.Length+1) - 3)] -> " + myVar[3..((myVar.Length+1) - 3)]
Print "Print the last letters 'fgh' using what would be the equivalent of Right(myVar, length from right) with myVar[((myVar.Length) - 3)..] -> " + myVar[((myVar.Length) - 3)..]
Print "Print the letters 'defgh'. Some languages would use Mid(myVar,start) with myVar[3..] -> " + myVar[3..]
Print "Convert an array of integer character codes to a string with String.FromChars(myArray) -> " + String.FromChars(myArray)
Print "'MONKEY-X' that was converted to an array"
For Local i:Int = 0 To myToArray.Length - 1
Print "Index = " + i + ": code: " + myToArray[i] + " -> " + String.FromChar(myToArray[i])
Next
End Function |
| ||
| Thank you so much guys! It works. But I can not do my specific file. In CSV file I have this structure: 2016-09-23 09:10:45.177815 +02:00;00283771;A343;CR8100 2016-09-26 22:33:55.992692 +02:00;00007064;A418;CR7030 2016-09-19 08:23:14.491140 +02:00;00238821;A343;CR8100 2016-09-28 01:47:27.143027 +02:00;72080043;A385;CR323 etc. And now, I need to count how many times, for example, A343 (next A418, next A343...) occurs in the entire group (like COUNTIF() function in Excel). I modified the code. It reads semicolons. But I can still get out of it what I need :/ Rect;50;50;100;75 Ellipse;200;200;75;100 Circle;300;300;50
Strict
Import mojo
Global loader:C_FileLoader
Function Main:Int()
Local app:C_App = New C_App()
Return 0
End
Class C_App Extends App
Method OnCreate:Int()
F_Load("AppData/data01.dat")
Return 0
End
Method OnUpdate:Int()
Return 0
End
Method OnRender:Int()
Cls
Return 0
End
End
Function F_Load:Void(fileName:String)
Local lineData:String
If loader
loader.Purge()
loader = Null
EndIf
loader = New C_FileLoader(fileName)
While not loader.Eof()
lineData = loader.ReadLine()
F_ParseUserData(lineData)
Wend
loader.Purge()
End
Function F_ParseUserData:Void(cmdLine:String)
Local tokenList:String[]
Local command:String
tokenList = F_TokeniseCommandLine(cmdLine)
command = tokenList[0]
Select command
Case "Rect"
F_Rect(tokenList)
Case "Ellipse"
F_Ellipse(tokenList)
Case "Circle"
F_Circle(tokenList)
Default
'Do nothing!
End
End
Function F_Rect:Void(tokenList:String[])
Local x:Float, y:Float
Local width:Float, height:Float
x = Float(tokenList[1])
y = Float(tokenList[2])
width = Float(tokenList[3])
height = Float(tokenList[4])
Print("Item: " + tokenList[0])
Print("Coordinates: " + x + ", " + y)
Print("Area: " + width + " x " + height)
Print("")
End
Function F_Ellipse:Void(tokenList:String[])
Local x:Float, y:Float
Local width:Float, height:Float
x = Float(tokenList[1])
y = Float(tokenList[2])
width = Float(tokenList[3])
height = Float(tokenList[4])
Print("Item: " + tokenList[0])
Print("Coordinates: " + x + ", " + y)
Print("Area: " + width + " x " + height)
Print("")
End
Function F_Circle:Void(tokenList:String[])
Local x:Float, y:Float
Local radius:Float
x = Float(tokenList[1])
y = Float(tokenList[2])
radius = Float(tokenList[3])
Print("Item: " + tokenList[0])
Print("Coordinates: " + x + ", " + y)
Print("Radius: " + radius)
Print("")
End
Function F_TokeniseCommandLine:String[] (cmdLine:String)
'Take a string, split it up, put each part in an array and then return it.
Local outputList:String[]
Local commandName:String
Local valueList:String[]
Local index:Int
commandName = cmdLine[0 .. (cmdLine.Find(";"))].Trim()
valueList = cmdLine[ (cmdLine.Find(";") + 1) ..].Trim().Split(";")
outputList = outputList.Resize(valueList.Length() + 1)
outputList[0] = commandName
For index = 0 To(valueList.Length() -1)
outputList[index + 1] = valueList[index]
Next
Return outputList
End
Class C_FileLoader
Field fileData:String[]
Field currentLine:Int
Method New(fileName:String)
fileData = LoadString(fileName).Split("~n")
currentLine = 0
End
Method ReadLine:String()
Local currentData:String
if Eof() = False
currentData = Self.fileData[Self.currentLine]
currentLine = (currentLine + 1)
Else
currentData = "Data Exhausted!"
EndIf
Return currentData
End
Method Eof:Bool()
if Self.currentLine > (fileData.Length() - 1)
Return True
Else
Return False
EndIf
End
Method Purge:Void()
Local counter:Int
For counter = 0 to (fileData.Length() - 1)
fileData[counter] = ""
Next
fileData =[]
End
End
|
| ||
| OK, I have almost everything. Thank you all! And now, the last thing. When I run this code in GLFW (or HTML5), command Print(a[343]) works OK. But I need this data written to disk. And that's the latest issue :( Thank you in advance! ;)
Import brl.filestream
Method Eof:Bool()
If Self.currentLine > (fileData.Length() -1)
#rem
Local file:= FileStream.Open("data/output.txt", "u")
For Local i:Int = 342 To 343
file.WriteString(i) '+ i + ";" + a[i])
Next i
#end
Print(a[343])
Return True
Else
Return False
EndIf
End Method
|
| ||
| You can write to disk in Windows, Linux and Mac as far as I know, I assume you can save on the web but I've never tried to. As far as I know the only way to save any data on Android and iOS is with SaveState, this does not let you see and actual file however. I'll have a pokesie around when I get back home a bit later. ;-) |
| ||
| OK, Steve, Thank you! |
| ||
| I didn't read the whole thread ... Writing files in HTML5 alone won't work, therefore I use NWJS which gives you a window with a webview in it. Then I use this js function:
var native = new Object();
native.WriteFile = function(filePath, content) {
var fs = require('fs');
if (!fs) return;
fs.writeFileSync(filePath, content, "utf-8");
}
PS: There is a thread around here how to setup NWJS with monkey. |
| ||
| @zxretrosoft: I found something that may be useful to you. Open the folder where you installed Monkey, then open bananas/mak/filetest/filetest.monkey. This seems to work OK for Desktop_game_(Glfw3), the result is saved in the "internal" folder within the buld folder. |
| ||
| I've done a couple of modifications to my C_FileLoader class, if you dont specify a filename when calling C_FileLoader.New() then it can be used for saving data in a desktop application. First you store data, line by line by calling WriteLine("stuff"), and then call the Save("fileName") method, once done you then call the Purge() method. I forgot to mention in my previous comments, you should make the C_FileLoader object Null when finished. P.S: Also note the line fStream.Save("internal/testfile.dat"). The data can only be saved to the "internal" folder, this folder is automatically created when the project is built. If you try to save to a different folder then the program will end prematurely without saving anything, I would assume that a sub folder could probably be put inside the "internal" folder though.
Strict
Import mojo
Import brl.filestream
Global loader:C_FileLoader
Function Main:Int()
Local app:C_App = New C_App()
Return 0
End
Class C_App Extends App
Method OnCreate:Int()
Local fStream:C_FileLoader = New C_FileLoader()
fStream.WriteLine("Big")
fStream.WriteLine("Round")
fStream.WriteLine("Shiny")
fStream.WriteLine("Bulbz")
fStream.Save("internal/testfile.dat")
fStream.Purge()
fStream = Null
Return 0
End
Method OnUpdate:Int()
Return 0
End
Method OnRender:Int()
Cls
Return 0
End
End
Class C_FileLoader
Field fileData:String[]
Field currentLine:Int
Method New(fileName:String = "")
If fileName <> ""
fileData = LoadString(fileName).Split("~n")
currentLine = 0
EndIf
End
Method ReadLine:String()
Local currentData:String
if Eof() = False
currentData = Self.fileData[Self.currentLine]
currentLine = (currentLine + 1)
Else
currentData = "Data Exhausted!"
EndIf
Return currentData
End
Method WriteLine:Void(data:String)
Local index:Int
index = Self.fileData.Length()
Self.fileData = Self.fileData.Resize(index + 1)
Self.fileData[index] = data
End
Method Save:Void(fileName:String)
Local file:Stream
Local data:String
file = FileStream.Open(fileName, "w")
For data = EachIn Self.fileData
file.WriteString(data + "~n")
Next
End
Method Eof:Bool()
if Self.currentLine > (fileData.Length() - 1)
Return True
Else
Return False
EndIf
End
Method Purge:Void()
Local counter:Int
For counter = 0 to (fileData.Length() - 1)
fileData[counter] = ""
Next
fileData =[]
End
End
|
| ||
| Thank you friends! Thank you so much Steve! I'll try it all. I thought of another idea. Would not it be easier to do this using the framework Playniax? The code seems to me easier?
Strict
Import playniax.ignitionx.framework.storage
Function Main:Int()
New MyApp
Return 0
End
Class MyApp Extends App
Method OnCreate:Int()
iSaveState("This is a test")
' iEraseData()
iStorage.WriteString("MyGame/Player/Name", "Jason")
iStorage.WriteInt("MyGame/Player/Lives", 5)
iStorage.WriteFloat("MyGame/Cash", 100.5)
iStorage.Save
Print "Player: " + iStorage.ReadString("MyGame/Player/Name")
Print "Lives: " + iStorage.ReadInt("MyGame/Player/Lives")
Print "Money In the bank: $" + iStorage.ReadFloat("MyGame/Cash")
Print iLoadState()
Print ""
Print "RAW data:"
iStorage.Show
Return 0
End
End
|
| ||
| If the Playniax framework works for you then it's all good. ;-) |