How can I use a string in a table? - string

I need to use a string value as a table, in order to restore points to a player when they reconnect to a game server.
This string value is their profile ID, which never changes and I need to put data inside the string value (Kills, deaths, head shots) in order to effectively restore these points. I have had a quick look on the internet but I have not found much because I don't know what this specific thing is actually called.
To make it easier, here's what I have so far:
if (not Omega.Playertable) then
Omega.Playertable = {};
System.LogAlways("Set static record table on first connect");
end
local ID = g_gameRules.game:GetProfileId(player.id);
if (not Omega.Playertable.ID) then
table.insert(Omega.Playertable, ID);
Omega.Playertable.g_gameRules.game:GetProfileId(player.id).Kills=0;
Omega.Playertable.g_gameRules.game:GetProfileId(player.id).Deaths=0;
Omega.Playertable.g_gameRules.game:GetProfileId(player.id).Headshots=0;
else
local Kills=Omega.Playertable.g_gameRules.game:GetProfileId(player.id).Kills;
local Deaths=Omega.Playertable.g_gameRules.game:GetProfileId(player.id).Deaths;
local Headshots=Omega.Playertable.g_gameRules.game:GetProfileId(player.id).Headshots;
g_gameRules.game:SetSynchedEntityValue(playerId, 101, Kills);
g_gameRules.game:SetSynchedEntityValue(playerId, 100, Deaths);
g_gameRules.game:SetSynchedEntityValue(playerId, 102, Headshots);
end
As you can see, I've tried adding their ID into the table and adding info based on this. I cannot get the system to read the 'ID' value that I set before, so I tried adding the code that gets the ID instead, and it doesn't work. The ID is unique to each player so I cannot use a simple number system for this.
Could someone point out to me what I have done wrong here? If I manage to fix the problem, I will answer my own question on here so that it can be helpful to other users.

It seems to me that you are using the wrong table indexing syntax.
Indexing a table by a variables value in Lua is done with the [] syntax.
Furthermore, in Lua Foo.bar is syntactic sugar for Foo["bar"] both formats are interchangeable, but the . variant has limitations on which characters you can use with it. For example Foo["\n.*#%!"] is a valid table index, but you certainly can't write this: Foo.\n.*#%!
Also table.insert(t, v) inserts v at the end of the array part of the table. That means if you do this
foo = {};
foo.X = "Some value";
table.insert(foo, "X");
This is what you get
{
X = "Some value"
[1] = "X"
}
That means, if I apply this to the code you gave us, this is what you probably had in mind:
if (not Omega.Playertable) then
Omega.Playertable = {};
System.LogAlways("Set static record table on first connect");
end
local ID = g_gameRules.game:GetProfileId(player.id);
if (not Omega.Playertable[ID]) then
Omega.Playertable[ID] = {};
Omega.Playertable[ID].Kills=0;
Omega.Playertable[ID].Deaths=0;
Omega.Playertable[ID].Headshots=0;
else
local Kills = Omega.Playertable[ID].Kills;
local Deaths = Omega.Playertable[ID].Deaths;
local Headshots = Omega.Playertable[ID].Headshots;
g_gameRules.game:SetSynchedEntityValue(playerId, 101, Kills);
g_gameRules.game:SetSynchedEntityValue(playerId, 100, Deaths);
g_gameRules.game:SetSynchedEntityValue(playerId, 102, Headshots);
end

Try this:
s="35638846.12.34.45"
id,kills,deaths,headshots=s:match("(.-)%.(.-)%.(.-)%.(.-)$")
print(id,kills,deaths,headshots)
But note that these values are strings. If you're using them as numbers, use tonumber to convert them.

Related

Lua weak tables memory leak

I don't use often weak tables. However now I need to manage certain attributes for my objects which should be stored somewhere else. Thats when weak tables come in handy. My issue is, that they don't work es expected. I need weak keys, so that the entire key/value pair is removed, when the key is no longer referenced and I need strong values, since what is stored are tables with meta information which is only used inside that table, which also have a reference to the key, but somehow those pairs are never collected.
Code example:
local key = { }
local value = {
ref = key,
somevalue = "Still exists"
}
local tab = setmetatable({}, { __mode = "k" })
tab[key] = value
function printtab()
for k, v in pairs(tab) do
print(v.somevalue)
end
end
printtab()
key = nil
value = nil
print("Delete values")
collectgarbage()
printtab()
Expected output:
Still exists
Delete values
Got:
Still exists
Delete values
Still exists
Why is the key/value pair not deleted? The only reference to value is effectivly a weak reference inside tab, and the reference inside value is not relevant, since the value itself is not used anywhere.
Ephemeron tables are supported since Lua 5.2.
The Lua 5.2 manual says:
A table with weak keys and strong values is also called an ephemeron table. In an ephemeron table, a value is considered reachable only if its key is reachable. In particular, if the only reference to a key comes through its value, the pair is removed.
Lua 5.1 does not support ephemeron tables correctly.
You are making too many assumptions about the garbage collector. Your data will be collected eventually. In this particular example it should work if you call collectgarbage() twice, but if you have some loops in your weak table it might take even longer.
EDIT: this actually only matters when you're waiting for the __cg event
I went over your code in more detail and noticed you have another problem.
Your value is referencing the key as well, creating a loop that is probably just too much for the GC of your Lua version to handle. In PUC Lua 5.3 this works as expected, but in LuaJIT the loop seems to keep the value from being collected.
This actually makes a lot of sense if you think about it; from what I can tell, the whole thing works by first removing weak elements from a table when they're not referenced anywhere else and thus leave them to be collected normally the next time the GC runs.
However, when this step runs, the key is still in the table, so the (not weak) value is a valid reference in the GCs eyes, as it is accessible from the code. So the GC kind of deadlocks itself into not being able to remove the key-value pair.
Possible solutions would be:
Don't save a reference to the key in the value
Make the value a weak table as well so it doesn't count as a reference either
Upgrade to another Lua version
Wrap the reference in a weak-valued single-element array
you can change the code like this. Then you will get the expected output. tips: do not reference key variable when you want it to be week.
local key = { }
local value = {
-- ref = key,
somevalue = "Still exists"
}
local tab = setmetatable({}, { __mode = "k" })
tab[key] = value
function printtab()
for k, v in pairs(tab) do
print(v.somevalue)
end
end
printtab()
key = nil
value = nil
print("Delete values")
collectgarbage()
printtab()

Typescript Multi Dimensional Array's Values Not Updating (to null)

What I am Doing
I am trying to create a Sudoku solver and generator in Vue. Right now, I have the solving algorithm set up, and just need to generate new problems. I am generating problems by creating a completed Sudoku problem (complete with no bugs), then I have to remove nodes so that there is still only 1 solution to the problem.
The Problem
When I try to access a node from the multi-dimensional array that represents the board, and change it to null (what I am using to display a blank node), the board does not update that value. I am changing it with the following code: newGrid[pos[0]][pos[1]] = null; (where pos[0] is the row, pos[1] is the column , and newGrid is grid we want to mutate). Note that the array is an array with 9 arrays inside, and each of those arrays has 9 numbers (or null) which represent the values for that position in the grid. To elaborate on the bug, if I put a console.log(newGrid), there are normal looking values, and no null.
What I Know and Have Tried
I know it has to do with this specific line, and the fact that I am setting the value equal to null because changing null to another value (i.e. newGrid[pos[0]][pos[1]] = 0;) works and changes the array. The reason I don't just use a value other than null is: null renders and nothing and other values (0) render as something (null nodes should be blank), null is simple to understand in this situation (the logic is node has null, node has nothing, node is blank), and null is already implemented throughout my codebase.
Additionally, if I use console.log(newGrid[pos[0]][pos[1]]), null (the correct output) is outputted, even though console.log(newGrid) shows a number there, not null. Also, oddly enough, this works for one specific node. In row 1 (indexing starts at 0), column 8, null is set. Even though the input (completed) grid is always different, this node is always set to null. Edit: this bug had to do with the input grid already having null here, so it actually doesn't let any nulls be set.
To summarize: I expect an array with a null in a few positions I update, but I get a number instead. Also, there are no errors when the Typescript compiles to Javascript or during runtime.
Code
Given that I am not exactly sure where the problem may be (i.e. maybe I create the array wrong) I am including the minimum code with a pastebin link to the whole file (this is the full code). To restate, the goal of this function is to remove nodes from the list (by replacing them with null) in order to create a Sudoku puzzle with one solution. The code on Stack Overflow only includes some of the whole file, and the pastebin link includes the rest.
//global.d.ts
type Nullable<T> = T | null;
type Grid = Array<Array<number | null>>;
import { Solver } from './Solve';
// Inside the function that does the main work
const rowLen: number = grid.length;
const colLen: number = grid[0].length;
let newGrid: Grid = grid; // Grid is a argument for this function
let fullNodes = GetFirstFull(grid, colLen, rowLen);
let fullNodesLen: number = fullNodes.length;
// Some stuff that figures out how many solutions there are (we only want 1) is excluded
if (solutions != 1) {
fullNodesLen++;
rounds--;
} else {
newGrid[pos[0]][pos[1]] = null;
}
Note that if anything seems confusing check out the pastebin or ask. Thank you so much for taking the time to look at my problem!
Also, it isn't just 0 that works, undefined also makes it set correctly. So, this problem seems to be something with the null keyword...
EDIT:
Given that no one has responded yet, I assume: my problem is a bit hard, there isn't enough information, my post isn't good quality, or not enough people have seen it. To control the problem of not enough information, I would like to include the function that calls this function (just to see if that might be related).
generate(context: ActionContext<State, any>) {
let emptyArray = new Array(9);
for (let i = 0; i < 9; ++i)
emptyArray[i] = [null, null, null, null, null, null, null, null, null];
const fullGrid = Solver(emptyArray);
const puzzle = fullGrid ? Remover(fullGrid, 6) : state.gridLayout;
context.commit('resetBoard', puzzle);
},
Note: If you aren't familiar with Vuex, what context.commit does is changes the state (except it is changing a global state rather than a component state). Given that this function isn't refactored or very easy to read code in the first place, if you have any questions, please ask.
To solve other potential problems: I have been working on this, I have tried a lot of console.log()ing, changing the reference (newGrid) to a deepcopy, moving stuff out of the if statements, verifying code execution, and changing the way the point on the newGrid is set (i.e. by using newgrid.map() with logic to return that point as null). If you have any questions or I can help at all, please ask.

How can I search one table for a value from another table?

This is for a simple script I'm writing for an oldschool MUD game for those who know what that is.
Basically I am searching through one table (which I'm reading from gmcp) and trying to search the value's on that table to see if any of them match any of the value's on another table which I'm storing the value's I'm looking for.
I've successfully managed to do it with singular value's by simply using a "for" loop to grab the value from gmcp and store it as a variable and then using another "for" loop to search the other table to see if any of the values there match the variable.
Trouble is, it only works for a singular value and misses all the others if there is more than one value I need to check in that table.
The code I have is as follows,
for _, v in pairs(gmcptable) do
checkvalue = v
end
for _, v in pairs(mytable) do
if v == checkvalue then
echo("yay")
else
echo("no!")
end
end
again this works fine for gmcp tables with one value, but fails if more. I tried doing this to,
for _, v in pairs(gmcptable) do
checkvalue = v
for _, v in pairs(mytable) do
if v == checkvalue then
echo("yay")
else
echo("no!")
end
end
end
my hope was that it might set the variable, run the second for loop to check the variable and then repeat for the next value on the gmcp table since it's a for loop and the second loop was within the loop, but that didn't work either. I also tried making my own function to add to the mix and simplify it,
function searchtable(table, element)
for _, v in pairs(table) do
if v == element then
return true
else
return false
end
end
end
for _, v in pairs(gmcptable) do
if searchtable(mytable, v) == true then
echo("yay")
else
echo("no!")
end
end
that was a bust also... I'm sure I'm just overlooking something or showing what an amateur I am, but I've googled loads and tried everything I can think of, but I'm just self taught and only recently started understanding how tables and for loops even work. Hopefully someone out there can get back to me with something that works soonish!
UPDATE!
#Piglet Okay so, gmcptable was actually me trying to simplify the question for those who could answer the coding question. gmcptable actually is a long list of tables received by my client via the connection from the server the game this is for. so in all actuality, I have 3 tables I'm parsing data from. "gmcp.Char.Items.List.items", "gmcp.Char.Items.Add" and "gmcp.Char.Items.Remove". Now gmcp.Char.Items.List.items is the list of everything in the room I'm in within the game. gmcp.Char.Items.Add is the list of anything that enter the room and is sent each time anything enters the room aside from other players and gmcp.Char.Items.Remove is the same, but for when anything leaves the room. I'm trying to use this information to create a targeting table that will automatically add desired targets to my targeting que and remove them if they are not in the room. the room list (gmcp.Char.Items.List) is updated only when I enter or exit the room and possibly when I look, but for now I'm assuming it doesn't update when I look because that will be a whole other problem to solve later.
I currently have a simple script in what my client ID's as a trigger, this is set to fire once when I log into the game in question and the script define the tables that hold the value's I'm cross referencing the gmcp tables with to figure out if it's information I want added to my target table, this script also defines the target table as empty, which is meant to ensure that for the duration of the session, both tables exist and are defined.
I then added three separate scripts that parse the three gmcp tables and figure out whether that are on my desired targets table and if so adds it or in the of the case of the remove table checks if its currently on the targets table and if so removes it. below I'll show the current scripts I'm using (which have changed several times over since yesterday and might change again before I get a look at any future replies to this. I will also include a what the gmcp tables in question look like and if I'm currently seeing any error or debug details from my client I'll include that as well.
log on trigger
match on > ^Password correct\. in perl regex
bashtargets = {}
bashlist = {
"a baby rat",
"a young rat",
"a rat",
"an old rat",
"a black rat"
}
(the above trigger appears to be working properly and I can print the tables accurately)
script in the room
event handlers > gmcp.Char.Items.List
for _, v in pairs(gmcp.Char.Items.List.items) do
bashname = v.name
bashid = v.id
for _, v in pairs(bashlist) do
if v == bashname then
table.insert(bashtargets, bashid)
end
end
end
script addcheck
event handlers "gmcp.Char.Items.Add"
for _, v in pairs(gmcp.Char.Items.Add) do
addname = v.name
addid = v.id
for _, v in pairs(bashlist) do
if v == addname then
table.insert(bashtargets, addid)
end
end
end
script removecheck
event handlers "gmcp.Char.Items.Remove"
for _, v in pairs(gmcp.Char.Items.Remove) do
delid = v.id
for _, v in pairs(bashtargets) do
if v == delid then
table.remove(bashtargets, delid)
end
end
end
gmcp table "gmcp.Char.Items"
{
Remove = {
location = "room",
item = {
id = "150558",
name = "a filthy gutter mutt",
attrib = "m"
}
},
Add = {
location = "room",
item = {
id = "150558",
name = "a filthy gutter mutt",
attrib = "m"
}
},
List = {
location = "room",
items = {
{
id = "59689",
name = "a statue of a decaying gargoyle",
icon = "profile"
},
{
id = "84988",
name = "a gas lamp"
},
{
id = "101594",
attrib = "t",
name = "a monolith sigil",
icon = "rune"
},
{
id = "196286",
name = "a wooden sign"
},
{
id = "166410",
name = "Lain, the Lamplighter",
attrib = "m"
}
}
}
}
I have parsed the information successfully several times, so I've got the right tables and syntax and what have you where gmcp is concerned.
using this I have also managed to get it to half work. currently the set up seems to capture single targets at a time even if there are dozens and add that one, sometimes it oddly enough adds the same target 3 - 5 times for some reason, not sure why, haven't been able to figure it out yet.
these two error messages have been output by my client repeatedly, no idea what to do about them though or how to fix them... "left the room" and "entered the room" are the names currently assigned to the scripts for adding and removing data from the tables in my client.
[ERROR:] object:<event handler function> function:<left the room>
<Lua error:[string "return left the room"]:1: '<eof>' expected near 'the'>
[ERROR:] object:<event handler function> function:<entered the room>
<Lua error:[string "return entered the room"]:1: '<eof>' expected near 'the'>
I have no idea what '' means though, or why it's expected near 'the' it's all got my head pounding though...
I can see through the debug feature on my client that all the handlers are being sent by the server so it's not the gmcp... I'm not actually seeing any bugs on the debug feature (which btw is separate from the error feature that keeps putting out those other two errors I mentions.
anyways that's my update... Hopefully that give some people a better handle on what I'm doing wrong so I can get this figured out and learn something new.
Thanks again in advance and extra thanks to you #Piglet for you answer I definitely learned something new from it and thought it was very helpful.
In your first attempt you have 2 separate loops. You overwrite checkvalue for every element in gmcptable. Once you enter your second loop checkvalue will have the value last asigned in your first loop. So you only have 1 checkvalue and you only run across your table once as you only run your second loop once.
for _, v in pairs(gmcptable) do
checkvalue = v
end
for _, v in pairs(mytable) do
if v == checkvalue then
echo("yay")
else
echo("no!")
end
end
Your second attempt should work if I understood your problem.
You iterate over every element of gmcptable and compare it to every element in mytable. So whenever gmcptable contains a value that is also contained in mytable you should get a "yay".
for _, v in pairs(gmcptable) do
checkvalue = v
for _, v in pairs(mytable) do
if v == checkvalue then
echo("yay")
else
echo("no!")
end
end
end
One remark on your third attempt with a function. You should not call arguments table as you will then have no access to the global table functions inside your function. A call to table.sort for example would result in an error as you will index your local parameter table instead.
Okay so I tinkered with it a bit more tonight and kept getting the same error, eventually I googled the error and came to understand that the error was an end of file error, the client I use uses the name of a script as the name for a function if you don't declare it in the actual script, or at least that is my understanding of it, and so I didn't bother defining the function, and if I added an additional end to the script the client threw up another error because it wasn't defined, so I guess the client adds it's own definition to the function but doesn't add it's own end to the function which created the mix up. So long story short, the solution was to define each of the scripts as a function and add another end to close the function and that fixed the problem.
Thanks again to #Piglet for his answer, it was very helpful and dead accurate too! Thanks mate!

How to maintain counters with LinqToObjects?

I have the following c# code:
private XElement BuildXmlBlob(string id, Part part, out int counter)
{
// return some unique xml particular to the parameters passed
// remember to increment the counter also before returning.
}
Which is called by:
var counter = 0;
result.AddRange(from rec in listOfRecordings
from par in rec.Parts
let id = GetId("mods", rec.CKey + par.UniqueId)
select BuildXmlBlob(id, par, counter));
Above code samples are symbolic of what I am trying to achieve.
According to the Eric Lippert, the out keyword and linq does not mix. OK fair enough but can someone help me refactor the above so it does work? A colleague at work mentioned accumulator and aggregate functions but I am novice to Linq and my google searches were bearing any real fruit so I thought I would ask here :).
To Clarify:
I am counting the number of parts I might have which could be any number of them each time the code is called. So every time the BuildXmlBlob() method is called, the resulting xml produced will have a unique element in there denoting the 'partNumber'.
So if the counter is currently on 7, that means we are processing 7th part so far!! That means XML returned from BuildXmlBlob() will have the counter value embedded in there somewhere. That's why I need it somehow to be passed and incremented every time the BuildXmlBlob() is called per run through.
If you want to keep this purely in LINQ and you need to maintain a running count for use within your queries, the cleanest way to do so would be to make use of the Select() overloads that includes the index in the query to get the current index.
In this case, it would be cleaner to do a query which collects the inputs first, then use the overload to do the projection.
var inputs =
from recording in listOfRecordings
from part in recording.Parts
select new
{
Id = GetId("mods", recording.CKey + part.UniqueId),
Part = part,
};
result.AddRange(inputs.Select((x, i) => BuildXmlBlob(x.Id, x.Part, i)));
Then you wouldn't need to use the out/ref parameter.
XElement BuildXmlBlob(string id, Part part, int counter)
{
// implementation
}
Below is what I managed to figure out on my own:.
result.AddRange(listOfRecordings.SelectMany(rec => rec.Parts, (rec, par) => new {rec, par})
.Select(#t => new
{
#t,
Id = GetStructMapItemId("mods", #t.rec.CKey + #t.par.UniqueId)
})
.Select((#t, i) => BuildPartsDmdSec(#t.Id, #t.#t.par, i)));
I used resharper to convert it into a method chain which constructed the basics for what I needed and then i simply tacked on the select statement right at the end.

What is wrong in this LINQ Query, getting compile error

I have a list AllIDs:
List<IAddress> AllIDs = new List<IAddress>();
I want to do substring operation on a member field AddressId based on a character "_".
I am using below LINQ query but getting compilation error:
AllIDs= AllIDs.Where(s => s.AddressId.Length >= s.AddressId.IndexOf("_"))
.Select(s => s.AddressId.Substring(s.AddressId.IndexOf("_")))
.ToList();
Error:
Cannot implicitly convert type 'System.Collections.Generic.List<string>' to 'System.Collections.Generic.List<MyCompany.Common.Users.IAddress>'
AllIDs is a list of IAddress but you are selecting a string. The compiler is complaining it cannot convert a List<string> to a List<IAddress>. Did you mean the following instead?
var substrings = AllIDs.Where(...).Select(...).ToList();
If you want to put them back into Address objects (assuming you have an Address class in addition to your IAddress interface), you can do something like this (assuming the constructor for Address is in place):
AllIDs = AllIDs.Where(...).Select(new Address(s.AddressID.Substring(s.AddressID.IndexOf("_")))).ToList();
You should also look at using query syntax for LINQ instead of method syntax, it can clean up and improve the readability of a lot of queries like this. Your original (unmodified) query is roughly equivalent to this:
var substrings = from a in AllIDs
let id = a.AddressId
let idx = id.IndexOf("_")
where id.Length >= idx
select id.Substring(idx);
Though this is really just a style thing, and this compiles to the same thing as the original. One slight difference is that you only have to call String.IndexOf() one per entry, instead of twice per entry. let is your friend.
Maybe this?
var boundable =
from s id in AllIDs
where s.AddressId.Length >= s.AddressId.IndexOf("_")
select new { AddressId = s.AddressId.Substring(s.AddressId.IndexOf("_")) };
boundable = boundable.ToList();

Resources