Lua (CC) GUI class draws all components in the same window when told to draw them in separate windows - oop

Prelude
ComputerCraft is a mod for Minecraft (Forge) that adds a crude lua-based Computer to the game. Using this Computer, one can write programs to interact with the Minecraft world in various ways. Whether a ComputerCraft question is applicable to StackOverflow has been previously debated in other questions, but I believe it is applicable, as the mod is, for the most part, about programming, and while some ComputerCraft proprietary API calls are made, there is no concept in this question that would not apply to other, non-ComputerCraft-related lua programs (unless of course the problem is caused by a bug in ComputerCraft itself). Documentation for the used APIs can be found at http://www.computercraft.info/wiki/Category:APIs.
Note: Do not be alarmed if you have no ComputerCraft experience; I believe that this issue may be completely unrelated to ComputerCraft, and instead be caused by some intricacy of OOP in lua that I have failed to grasp. I have commented the code where I felt it necessary to explain the most important aspects of the proprietary calls I am making. If anything is unclear, please comment and I will clarify.
If you want to be able to run the code examples without Minecraft, there is an excellent ComputerCraft emulator available called CCEmuRedux. I have tested my code on both actual ComputerCraft and CCEmuRedux with identical results, although CCEmuRedux doesn't seem to support Monitors. An "Advanced" Computer is necessary to see the colours.
Problem
In ComputerCraft 1.75 (and CCEmuRedux # ComputerCraft 1.79), given the following class gui, and a test program that attempts to draw a rudimentary button in each of two different windows using the gui class, both buttons are drawn in the second window. Graphically, the result of guiTest.lua is https://i.imgur.com/llFDlYI.png, while I would expect the first (orange) button to be drawn in Window 1. While I have some theories as to why it behaves this way, I don't have the necessary lua experience to figure out how to fix it. This is an MWE.
Code example
gui.lua
--Meta class
gui = {t, vpx, vpy}
function gui:new(t, title) -- I'm aware this constructor is not in keeping with the referenced Tutorialspoint article, it is of no consequence in this example
local o = o or {}
setmetatable(o, self)
self.__index = self
self.t = t
local sX, sY = self.t.getSize() -- get the size of the virtual terminal and save it to vpx, vpy
self.vpx = sX
self.vpy = sY
self.t.setCursorPos(1, 1) -- put cursor at the start of the virtual terminal
self.t.write(tostring(title)) -- note that this WORKS, it prints one title per Window as seen in the screenshot
return o
end
function gui:drawButton(x, y, sX, sY, colour)
self.t.setCursorPos(x, y) -- set the cursor to the button's first x- and y-coords
self.t.setTextColor(colours.black) -- set text colour to black
self.t.setBackgroundColor(colour) -- set background colour to the colour of the button
for iY = 1, sY do
for iX = 1, sX do
self.t.write("#") -- print hashtags to represent the button until we reach sX and sY
end
self.t.setCursorPos(x, y + iY) -- move cursor a line down, and back to button's first x-coord
end
self.t.setCursorPos(self.vpx, self.vpy) -- get cursor out of the way so the screenshot will be prettier
end
guiTest.lua
dofile('gui.lua')
local w1 = window.create(term.current(), 2, 2, 22, 15)
local w2 = window.create(term.current(), 26, 2, 22, 15) -- creates virtual windows in a terminal, acting as terminals of their own
-- window.create() arguments: terminal object to create window on, x position, y position, x size, y size
local g1 = gui:new(w1, "Window 1") -- create gui object for the first window
local g2 = gui:new(w2, "Window 2") -- create gui object for the second window
g1:drawButton(5, 3, 3, 2, colours.orange) -- should draw in w1, draws in w2
g2:drawButton(10, 8, 4, 4, colours.green) -- should draw in w2, draws in w2
Attempted solutions
For what it's worth, I've been following the Lua OOP recipe # https://www.tutorialspoint.com/lua/lua_object_oriented.htm. This is my second lua-based program, so I expect it to be an "easy" problem. I have more than a basic understanding of how OOP works in several other languages (particularly Java), though, and as such my programmer's "Spidey-Sense" is telling me that either some variable, such as t, isn't "local enough" (same variable gets used by both windows), or some reference in one of the gui objects gets overwritten when a new gui object gets created.
Therefore, I tried making the table gui local, to ensure it was not being overwritten:
local gui = {t, vpx, vpy}
... but it spat an error attempt to index ? on line 6 of "gui.lua" (setmetatable(o, self)), so instead I tried (realising that I would be unable to access the function from outside gui.lua, due to it being local):
local function gui:drawButton(x, y, sX, sY, colour)
... which resulted in guiTest.lua:1: bios.lua:14 [string "gui.lua"]:17:'(' expected. Line 17 is the definition of gui:drawButton() in the code tag above. In my admittedly limited ComputerCraft experience, such poorly formatted error messages generally mean that the lua interpreter or CraftOS is Exceptionally Confused™, but I assume the gist of it is "you can't make an object method local", as I can make other functions local in a similar fashion to what I've tried here.
It is not a problem with window.create() or with using the window API in general, as the same thing happens when using separate Monitors instead of just separate windows on the same Monitor. Essentially:
dofile('gui.lua')
local w = window.create(term.current(), 2, 2, 22, 15)
local m = peripheral.wrap('top') -- m becomes the Monitor physically on top of the ComputerCraft Computer
local gw = gui:new(w, "Window") -- create gui object for the Window
-- m is a terminal object, just like w, so we can still do
local gm = gui:new(m, "Monitor") -- create gui object for the Monitor
gw:drawButton(5, 3, 3, 2, colours.orange) -- should draw in w, draws in m
gm:drawButton(10, 8, 4, 6, colours.green) -- should draw in m, draws in m
Perhaps there is a way of storing the function as a local variable, along the lines of
local gui:printFoo = function() print("foo") end
self:printFoo() -- prints "foo"...?
... or perhaps more likely, the issue is something I have entirely missed.
Conclusion
To make a long question short, defining two gui objects, one for each of two virtual console windows, and attempting to draw one button on each of the virtual console windows using their respective gui objects, results in both buttons being drawn on the same virtual console window. Why?

Yes, OOP in Lua is hard for Lua beginners, despite of excellent knowledge of OOP languages (such as Java).
--Meta class
gui = {} -- class is a global variable, no default properties exist
function gui:new(t, title) -- t = window, self = your class "gui"
local o = {} -- creating NEW object
setmetatable(o, self) -- link the object with the class
self.__index = self
o.t = t -- save window into object (not into class)
local sX, sY = t.getSize() -- get the size of the virtual terminal
o.vpx = sX -- save window's properties into object (not into class)
o.vpy = sY
t.setCursorPos(1, 1)
t.write(tostring(title))
return o
end
function gui:drawButton(x, y, sX, sY, colour) -- self = object
....
end

Related

Is there a way to turn off a vehicle signal in SUMO?

I know that you can turn on a vehicle signal (for example, the left indicator) in traci using:
traci.vehicle.setSignals(vehID, int)
where the integer related to the specific signal can be found using the following link (https://sumo.dlr.de/docs/TraCI/Vehicle_Signalling.html#signaling), but is there a way of turning off a specific signal that would be otherwise turned on by the program (i.e., a setSignalOff)?
I think that there is a function in the underlying C++ code (switchOffSignal() in MSVehicle.h) but there doesn't appear to be a traci command that turns off a specific signal.
I appreciate that it is (generally) a pleasant visual aesthetic and has no impact on vehicle behaviour, but it would be very useful for what I am trying to do!
Switching off signals should work from traci. By using sometihng like traci.vehicle.setSignals("ego", 0), I can switch them off. Be aware that this will be reset after the step, so you may have to do that in every timestep.
So, Michael is right in that:
traci.vehicle.setSignals("ego", 0)
should turn off all signals (although the signals still appeared on for me visually, which confused me initially).
To turn off individual signals but keep the others on you need to:
For all the "on" signals find the value of 2^n, where n is the bit integer (which can be found using the following link: https://sumo.dlr.de/docs/TraCI/Vehicle_Signalling.html)
Sum all these 2^n values (let's call this variable x) and use this value in the setSignals function: traci.vehicle.setSignals("ego", x).
So for example, if we want the brake light, the right indicator and the high beam on (but all the other signals off) we would do:
RightIndicatorValue = pow(2,0)
BrakeLightValue = pow(2,3)
HighBeamValue = (2,6)
SignalValue = RightIndicatorValue + BrakeLightValue + HighBeamValue
traci.vehicle.setSignals(("ego", SignalValue)

Player doesn't spawn correctly in procedural generated map

I've followed "Procedural Generation in Godot: Dungeon Generation" by KidsCanCode #https://www.youtube.com/watch?v=o3fwlk1NI-w and find myself unable to debug the current problem.
This specific commit has the code, but I'll try to explain in more detail bellow.
My main scene has a Camera2D node, a generic Node2D calles Rooms and a TileMap, everything is empty.
When the script starts, it runs a
func make_room(_pos, _size):
position = _pos
size = _size
var s = RectangleShape2D.new()
s.custom_solver_bias = 0.5
s.extents = size
$CollisionShape2D.shape = s
A few times and it fills $Rooms using .add_child(r) where r is a instance of the node that has the make_room() function. It will then iterate over $Rooms.get_children() a few times to create a AStar node to link all the rooms:
The magic comes when make_map() is called after afterwards, it fills the map with non-walkable blocks and then it carves the empty spaces, which works fine too:
There is a find_start_room() that is called to find the initial room, it also sets a global variable to the Main script start_room, which is used to write 'Start' on the map using draw_string(font, start_room.position - Vector2(125,0),"start",Color(3,4,8))
When I hit 'esc' it runs this simple code to instance the player:
player = Player.instance()
add_child(player)
player.position = start_room.position + Vector2(start_room.size.x/2, start_room.size.y/2)
play_mode = true
The problem comes when spawning the player. I tried doing some 'blind' fixing, such as adding or subtracting a Vector2(start_room.size.x/2, start_room.size.y/2) to player.position to see if I could make it fall within the room, to no avail.
Turning to the debugger didn't help, as the positions expressed by the variable inspectors don't seem to mean anything.
I tried implementing a simple 'mouse click print location':
print("Mouse Click/Unclick at: ", event.position)
print("Node thing",get_node("/root/Main/TileMap").world_to_map(event.position))
And also a 'start_room' print location:
print(get_node("/root/Main/TileMap").world_to_map(start_room.position))
And a when player moves print location, written directly into the Character script:
print(get_node("/root/Main/TileMap").world_to_map(self.position))
Getting results like the ones bellow:
Mouse Click/Unclick at: (518, 293)
Node thing(16, 9)
(-142, 0)
(-147, -3)
So, the player doesn't spawn on the same position as the start_room and the mouse position information is not the same as anything else.
Why is the player now spawning correctly? How can I debug this situation?
EDIT1: User Theraot mentioned about how the RigidBody2D is doing some weird collisions, and from what I understood, changing their collision behavior should fix the whole thing.
There's a section on the code that -after generating the random rooms- it removes some of the rooms like this:
for room in $Rooms.get_children():
if randf() < cull:
room.queue_free()
else:
room.mode = RigidBody2D.MODE_STATIC
room_positions.append(Vector3(room.position.x, room.position.y, 0))
From what I understand, if the room is randomly selected it will be deleted using queue_free() OR it will be appended to a room_positions for further processing. This means if I shift all the rooms to a different collision layer, the player/character instance would be alone with the TileMap on the same collision layer.
So I just added a simple room.collision_layer = 3 changing this section of the code to
for room in $Rooms.get_children():
if randf() < cull:
room.queue_free()
else:
room.mode = RigidBody2D.MODE_STATIC
room.collision_layer = 3
room_positions.append(Vector3(room.position.x, room.position.y, 0))
It doesn't seem to have changed anything, the player still spawns outside the room.
Do you see the rooms spread outwards?
You didn't write code to move the rooms. Sure, the code gives them a random position. But even if you set their position to Vector2.ZERO they move outwards, avoiding overlaps.
Why? Because these rooms are RigidBody2D, and they do push other physics objects. Such as other rooms or the player character.
That's the problem: These rooms are RigidBody2D, and you put your KinematicBody2D player character on top of one of them. The RigidBody2D pushes it out.
The tutorial you followed is exploiting this behavior of RigidBody2Ds to spread the rooms. However you don't need these RigidBody2D after you are done populating your TileMap.
Instead, you can store the start position in a variable for later placing the player character (you don't need offsets - by the way - the position of the room is the center of the room), and then remove the RigidBody2Ds. If you want to keep the code that writes the text, you would also have to modify it, so it does not fail when the room no longer exists.
Alternatively, you can edit their collision layer and mask so they don't collide with the player character (or anything for that matter, but why would you want these RigidBody2Ds that collide with nothing?).
Addendum post edit: Collision layers and mask don't work as you expect.
First of all, the collision layer and mask are flags. The values of the layers are powers of two (1, 2, 4, 8...). So, when you set it to 3, it is the layer 1 plus the layer 2. So it still collides with a collision mask of 1.
And second, even if you changed the collision layer of the rooms to 2 (so it does not match the collision mask of 1 that the player character has). The player character still has a layer 1 which match the collision mask of the rooms.
See also the proposal Make physics layers and masks logic simple and consistent.
Thus, you would need to change the layer and mask. Both. in such way that they don't collide. For example, you can set layer and mask to 0 (which disable all collisions). The algorithm that populates the TileMap does not use the layer and mask.

The function to calculate differential/derivative in DM

I remember that is a function to calculate the differential/derivative for a line plot in a DM version, it looks lik in the process- non linear filter- derivative. But I do not remember which version has this function, any suggestion?
The UI functionality for spectral filtering is found in the Spectrum menu:
Since GMS 3 this functionality is part oft the free software, before it was part of the Spectroscopy license (any).
The menu only works on line profiles which are spectra, for which the Convert Data To menu would be used when required.
As all "menu" commands, you can access them using the ChooseMenuItem command as in:
GetFrontImage().SelectImage() // Make sure the image window is selected, or the menu is disabled if the script-window is frontmost!
ChooseMenuItem("Spectrum","Numerical Filters","First derivative")
The mathematical functions behind this menu are also available as (unofficial, undocumented) script commands. They do not use the preferences but the parameters directly, using uncalibrated 'channel' scale.
So you could also use:
image src := GetFrontImage()
number chWidth = 5 // The values matching the settings
number chDelta = 1 // The values matching the settings
number chShift = trunc((chWidth + chDelta)/2 + 0.5)
number norm = chWidth + chDelta
image fDev := src.FDeriv_Spectrum( chWidth, chShift, norm )
fDev.ShowImage()
Just be warned that there is no guarantee that the command FDeriv_Spectrum will be continued in future versions of GMS (It is not an officially supported command.)
Finally, the math of a first derivative are really simple, so you could just recreate the function with pure DM-script commands like offset and arithmetic operators.
A simple, non-smoothed 1-channel derivative would simply be:
image src := GetFrontImage()
image fdev := src - src.offset(-1,0)
fdev.ShowImage()

Elm Game of life program becomes unresponsive - is there a way to fail gracefully?

I have a basic implementation of Conway's game of life written in elm running at uminokirin.com.
The source is visible here.
The program let users adjust the size of the toroïdal grid, click on cells to change their status, and randomize the world. It works well for small values (less than 50) on my computer.
However when attempting to use the randomize grid function on bigger grids (the threshold value doesn't seem to be always the same), the program becomes unresponsive without any warning and the only way to recover is to reload the app.
There is zero optimization in the GOL algorithm and using a single svg rectangle for every cell is probably horribly inefficient, but it sill doesn't explain why the program behaves in this way instead of say, slowing down.
Is this the elm runtime giving up? Or some kind of browser safeguard?
More importantly is there a way to prevent this behavior other than arbitrarily capping the maximum size of the grid?
The behavior you are observing is due to a Javascript stack overflow. After pressing the "randomize" button, in the browser console you can see the message "Uncaught RangeError: Maximum call stack size exceeded"
This happens because the randomize function allocates several large temporary variables. In particular, the shuffle function (which is called from the randomize function) appears to allocate two temporary lists that each have one element for every cell in the life grid. Elm may be smart about releasing these on a timely basis but this appears to push it too far.
To fix this you can use a simpler randomize function. The version shown below uses Elm Generators to generate a single list of Dead/Alive values and then initializes the randomized array from that list.
randomize2 : Array Cell -> Int -> Int -> Int -> Array Cell
randomize2 grid gs sd n =
let floatGen = Random.float 0.0 1.0
lifeGen = Random.map (\b -> if (b < toFloat n/100) then Alive else Dead) floatGen
listGen = Random.list (gs*gs) lifeGen
in fst (Random.step listGen (initialSeed sd)) |> fromList
Using this randomize function I was able to resize the grid up to 600x600 and randomize successfully. At that point I stopped testing.

In Gimp script-fu, how can you access QuickMask functionality?

In the Gimp GUI, the QuickMask is very useful for many things, but this functionality doesn't seem to be directly available through script-fu. No obvious equivalents were apparent to me in the procedure browser.
In particular, putting the (value/gray) pixels of a layer into the selection mask is the basic thing I need to do. I tried using gimp-image-get-selection to get the selection channel's id number, then gimp-edit-paste into it, but the following anchor operation caused Gimp to crash.
My other answer contains the "theoretical" way of doing it - however, the O.P. found a bug in GIMP, as of version 2.6.5, as can be seem on the comments to that answer.
I got a workaround for what the O.P. intends to do: paste the contents of a given image layer to the image selection. As denoted, edit-copy -> edit-paste on the selection drawable triggers a program crash.
The workaround is to create a new image channel with the desired contents, through the copy and paste method, and then use gimp-selection-load to make the selection equal the channel contents:
The functions that need to be called are thus (I won't paste scheme code, as I am not proficient in all the parenthesis - I did the tests using the Python console in GIMP):
>>> img = gimp.image_list()[0]
>>> ch = pdb.gimp_channel_new(img, img.width, img.height, "bla", 0, (0,0,0))
>>> ch
<gimp.Channel 'bla'>
>>> pdb.gimp_edit_copy(img.layers[0])
1
>>> pdb.gimp_image_add_channel(img, ch, 0)
>>> fl = pdb.gimp_edit_paste(ch, 0)
> >> fl
<gimp.Layer 'Pasted Layer'>
>>> pdb.gimp_floating_sel_anchor(fl)
>>> pdb.gimp_selection_load(ch)
Using QuickMask through the User interface is exactly equivalent to draw on the Selection, treating the selection as a drawable object.
So, to use the equivalent of "quickmask" on script-fu all one needs to is to retrieve the Selection as a drawable and pass that as a parameter to the calls that will modify it -
And to get the selection, one just have to call 'gimp-image-get-selection'