Array throwing exception - arrays

I can't seem to put my finger on this and why the array is not being initialized.
Basically I am coding a 2d top down spaceship game and the ship is going to be fully customizable. The ship has several allocated slots for certain "Modules" (ie weapons, electronic systems) and these are stored in an array as follows:
protected Array<Weapon> weaponMount;
Upon creating the ship none of the module arrays are initialized, since some ships might have 1 weapon slot, while others have 4.
So when I code new ships, like this example:
public RookieShip(World world, Vector2 position) {
this.width = 35;
this.height = 15;
// Setup ships model
bodyDef.type = BodyType.DynamicBody;
bodyDef.position.set(position);
body = world.createBody(bodyDef);
chassis.setAsBox(width / GameScreen.WORLD_TO_BOX_WIDTH, height / GameScreen.WORLD_TO_BOX_HEIGHT);
fixtureDef.shape = chassis;
fixtureDef.friction = 0.225f;
fixtureDef.density = 0.85f;
fixture = body.createFixture(fixtureDef);
sprite = new Sprite(new Texture(Gdx.files.internal("img/TestShip.png")));
body.setUserData(sprite);
chassis.dispose();
// Ship module properties
setShipName("Rookie Ship");
setCpu(50);
setPower(25);
setFuel(500);
setWeaponMounts(2, world);
setDefenseSlots(1);
addModule(new BasicEngine(), this);
addModule(new BasicBlaster(), this);
// Add hp
setHullHP(50);
setArmorHP(125);
setShieldHP(125);
}
#Override
public void addModule(Module module, Ship currentShip) {
// TODO Auto-generated method stub
super.addModule(module, currentShip);
}
#Override
public void setWeaponMounts(int weaponMounts, World world) {
weaponMount = new Array<Weapon>(weaponMounts);
// super.setWeaponMounts(weaponMounts, world);
}
#Override
public String displayInfo() {
String info = "Everyones first ship, sturdy, reliable and only a little bit shit";
return info;
}
When I set the number of weapon mounts the following method is called:
public void setWeaponMounts(int weaponMounts, World world) {
weaponMount = new Array<Weapon>(weaponMounts);
}
This basically initializes the array with a size (weapon mounts available) to whatever the argument is. Now to me this seems fine but I have setup a hotkey to output the size of the Array, which reports zero. If I try to reference any objects in the array, it throws an outofbounds exception.
The addModule method adds to the array as follows:
public void addModule(Module module, Ship currentShip) {
currentShip.cpu -= module.getCpuUsage();
currentShip.power -= module.getPowerUsage();
if(module instanceof Engine){
engine = (Engine) module;
}else if(module instanceof Weapon){
if(maxWeaponMounts == weaponMount.size){
System.out.println("No more room for weapons!");
}else{
maxWeaponMounts += 1;
weaponMount.add((Weapon)module);
}
}
}
My coding ain't great but heh, better than what I was 2 month ago....
Any ideas?

First of all, You should avoid instanceof. It's not a really big deal performance-wise, but it always points to problems with your general architecture. Implement two different addModule methods. One that takes a Weapon, and one that takes an Engine.
Now back to topic:
else if(module instanceof Weapon){
if (maxWeaponMounts == weaponMount.size) {
System.out.println("No more room for weapons!");
} else{
maxWeaponMounts += 1;
weaponMount.add((Weapon)module);
}
}
It looks like you use maxWeaponMounts as a counter instead of a limit. That's why I assume that it will initially be 0. The same holds for Array.size. It is not the limit, but size also counts how many elements the Array currently holds. Thus you will always have (maxWeaponMounts == weaponMount.size) as 0 == 0 and you will not add the weapon to the array. It will always stay empty and trying to reference any index will end in an OutOfBoundsException.
What you should actually do is using maxWeaponMounts as a fixed limit and not the counter.
public void setWeaponMounts(int weaponMounts, World world) {
weaponMount = new Array<Weapon>(weaponMounts);
maxWeaponMounts = weaponMounts;
}
else if(module instanceof Weapon){
if (weaponMount.size >= maxWeaponMounts) {
System.out.println("No more room for weapons!");
} else{
weaponMount.add((Weapon)module);
}
}

Related

Compare two arrays in memory in Solidity and a return a "not in" result

I have a large contract and I am in the process of splitting it out into two. The goal is to have the functions that are common (and will be used by many other contracts) to be separated out for efficiency.
One of these functions compares items in arrays "ownedSymbols" and "targetAssets". It produces a list "sellSymbols" if any item in "ownedSymbols" is not in "targetAssets".
The below code works fine while "sellSymbols" is stored as a string. As this function will become common, I need it to run in memory so the results aren't confused by calls from different contracts.
pragma solidity >0.8.0;
contract compareArrays {
string[] public ownedSymbols = ["A","B","C"];
string[] public targetAssets = ["A","B"];
string[] sellSymbols;
event sellListEvent(string[]);
function sellList(string[] memory _ownedSymbols, string[] memory _targetAssetsList) internal {
sellSymbols = _ownedSymbols;
for (uint256 i = 0; i < _targetAssetsList.length; i++) {
for (uint256 x = 0; x < sellSymbols.length; x++) {
if (
keccak256(abi.encodePacked((sellSymbols[x]))) ==
keccak256(abi.encodePacked((_targetAssetsList[i])))
) {
if (x < sellSymbols.length) {
sellSymbols[x] = sellSymbols[sellSymbols.length - 1];
sellSymbols.pop();
} else {
delete sellSymbols;
}
}
}
}
emit sellListEvent(sellSymbols);
}
function runSellList() public {
sellList(ownedSymbols,targetAssets);
}
}
Ideally the function would run with "string[] memory sellSymbols", however this kicks back an error.
pragma solidity >0.8.0;
contract compareArrays {
string[] public ownedSymbols = ["A","B","C"];
string[] public targetAssets = ["A","B"];
event sellListEvent(string[]);
function sellList(string[] memory _ownedSymbols, string[] memory _targetAssetsList) internal {
string[] memory sellSymbols = _ownedSymbols;
for (uint256 i = 0; i < _targetAssetsList.length; i++) {
for (uint256 x = 0; x < sellSymbols.length; x++) {
if (
keccak256(abi.encodePacked((sellSymbols[x]))) ==
keccak256(abi.encodePacked((_targetAssetsList[i])))
) {
if (x < sellSymbols.length) {
sellSymbols[x] = sellSymbols[sellSymbols.length - 1];
sellSymbols.pop();
} else {
delete sellSymbols;
}
}
}
}
emit sellListEvent(sellSymbols);
}
function runSellList() public {
sellList(ownedSymbols,targetAssets);
}
}
The error:
TypeError: Member "pop" is not available in string memory[] memory outside of storage.
--> contracts/sellSymbols.sol:20:25:
|
20 | sellSymbols.pop();
| ^^^^^^^^^^^^^^^
Two questions from me:
Is there a way to do this in memory so that the function can be common (i.e. used by multiple contracts at the same time)?
Is there a better way? The below is expensive to run, but it is the only way I have been able to achieve it.
One final comment - I know this would be much easier/cheaper to run off chain. That is not something I am willing to consider as I want this project to be decentralized.
If you want to keep the existing system, the best solution is described here: https://stackoverflow.com/a/49054593/11628256
if (x < sellSymbols.length) {
sellSymbols[x] = sellSymbols[sellSymbols.length - 1];
delete sellSymbols[myArray.length - 1];
sellSymbols.length--;
} else {
delete sellSymbols;
}
If all you care about is the presence or absence of a particular asset (and not enumerating through them), what you're going to want to do to really reduce gas costs is something called "lazy evaluation." Lazy evaluation is when instead of computing all results at once (like increasing all balances by 50% by iterating over an array), you modify the getters so that their return value reflects the operation (such as multiplying an internal variable by 50% and multiplying the original result of getBalance by that variable).
So, if this is the case, what you want to do is use the following function instead:
function except(string _item, mapping(string => bool) _ownedSymbols, mapping(string => bool) _targetAssets) internal returns (bool) {
return _ownedSymbols[_item] && !_targetAssets[_item];
}
<pet peeve>
Finally, I know you say you want this to be decentralized, but I really do feel the urge to say this. If this is a system that doesn't need to be decentralized, don't decentralize it! Decentralization is great for projects that other people rely on - for example, DNS or any sort of token.
From your variable names, it seems that this is probably some sort of system similar to a trading bot. Therefore, the incentive is on you to keep it running, as you are the one that gets the benefits. None of the problems that decentralization solves (censorship, conflict of interest, etc...) apply to your program, as the person running it is incentivized to keep it running and keep a copy of the program. It's cheaper for the user running it to not have security they don't need. You don't need a bank-grade vault to store a $1 bill!
</pet peeve>

Order of init calls in Kotlin Array initialization

In the constructor of an Array is there a guarantee that the init function will be called for the indexes in an increasing order?
It would make sense but I did not find any such information in the docs:
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-array/-init-.html#kotlin.Array%24%28kotlin.Int%2C+kotlin.Function1%28%28kotlin.Int%2C+kotlin.Array.T%29%29%29%2Finit
There is no guarantee for this in the API.
TLDR: If you need the sequential execution, because you have some state that changes see bottom.
First lets have a look at the implementations of the initializer:
Native: It is implemented in increasing order for Kotlin Native.
#InlineConstructor
public constructor(size: Int, init: (Int) -> Char): this(size) {
for (i in 0..size - 1) {
this[i] = init(i)
}
}
JVM: Decompiling the Kotlin byte code for
class test {
val intArray = IntArray(100) { it * 2 }
}
to Java in Android Studio yields:
public final class test {
#NotNull
private final int[] intArray;
#NotNull
public final int[] getIntArray() {
return this.intArray;
}
public test() {
int size$iv = 100;
int[] result$iv = new int[size$iv];
int i$iv = 0;
for(int var4 = result$iv.length; i$iv < var4; ++i$iv) {
int var6 = false;
int var11 = i$iv * 2;
result$iv[i$iv] = var11;
}
this.intArray = result$iv;
}
}
which supports the claim that it is initialized in ascending order.
Conclusion: It commonly is implemented to be executed in ascending order.
BUT: You can not rely on the execution order, as the implementation is not guaranteed by the API. It can change and it can be different for different platforms (although both is unlikely).
Solution: You can initialize the array manually in a loop, then you have control about the execution order.
The following example outlines a possible implementation that has a stable initialisation with random values, e.g. for tests.
val intArray = IntArray(100).also {
val random = Random(0)
for (index in it.indices) {
it[index] = index * random.nextInt()
}
}
Starting from the version 1.3.50 Kotlin has guaranteed sequential array initialization order in its API documentation: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-array/-init-.html
The function init is called for each array element sequentially starting from the first one. It should return the value for an array element given its index.

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.

AS3 remove all instances of an object?

I have a game with in AS3 with a document class and a custom class which is attached to a movieclip in my .fla. Instances of that object are made multiple times/second. I want to delete those instances when, let's say, there are 100 of them. (because of performance problems after a while) The instances are stored in an Array after they are made.
You can remove them by using this.removeChild(obj); and obj is your object from array. So what you need is to loop through array and remove them.
That will remove all objects when objects are over 100.
if(array.length > 100)
{
for(var i:int = array.length - 1; i > -1; i--)
{
stage.removeChild(array[i]);// or any other parent containing instances
array.pop();
//array[i] = null; // if you want to make them null instead of deleting from array
}
}
Tip: Negative Loop (i--) is faster in performance than Positive Loop (i++).
Tip: pop() is faster in performance than unshift().
Update:
That will remove objects only if they are over 100, resulting in only 100 last objects remain on stage.
if(array.length > 100)
{
for(var i:int = array.length - 1; i > -1; i--)
{
if(array.length > 100)
{
stage.removeChild(array[i]);// or any other parent containing instances
array.unshift();// if you want to delete oldest objects, you must use unshift(), instead of pop(), which deletes newer objects
//array[i] = null; // if you want to make them null instead of deleting from array
}
}
/****** MyClass.as *********/
public class MyClass extends Sprite{
private var myVar:int=8567;
private function myClass():void{
//blablabla
}
public class destroy():void{
myVar = null;
this.removeFromParent(true); //method of Starling framework
}
}
/******** Main.as ****************/
public var myInstance:MyClass = new Myclass();
//Oh!! i need remove this instance D:
myInstance.destroy();

Moving objects in array

I have an array which is filled with platforms that are supposed to move.
var MovingPlatformArray:Array = new Array();
for (var c:int = numChildren - 1; c >= 0; c--){
var child3:DisplayObject = getChildAt(c);
if (child3.name == "movingplatform"){
MovingPlatformArray.push(child3);
}
}
this.addEventListener(Event.ENTER_FRAME,ctrl_birdie);
function ctrl_birdie(e:Event):void{
for(var c in MovingPlatformArray){
MovingPlatform[c].y += speed;
if(MovingPlatformArray[c].hitTestPoint(birdie.x,birdie.y,true)){
birdtelleryvertrager=0;
birdtellery = 0;
birdie.y-=14;
}
if(movingplatform.y <= 25){
speed = 2;
}
if(movingplatform.y >= 350){
speed = -2;
}
}
Right now I have 2 moving platforms in this array. But only one moves up and down. But they both register a touch with the birdie. Am I doing something wrong?
In your listener, you're only setting the position of one platform, which ever one "movingplatform" is a reference to. As all your stage instances of moving platforms are named "movingplatform", one lucky platform is getting referenced by name (the rest ignored), instead of what you intended, which is to use the references in your array and adjust each platform.
You probably meant for movingplatform to be a local variable in your event handler, declared something like this:
var movingplatform:DisplayObject = MovingPlatformArray[c] as DisplayObject;
I'd recommend using a for each loop in place of the for in, because I think it's a little cleaner, but this is a minor style thing:
for each (var platform:DisplayObject in MovingPlatformArray)
{
platform.y += speed;
... rest of your code ...
}
For the sake of clarity, I edited the loop variable to be platform instead of movingplatform, to avoid confusion of having a local variable shadow a stage instance (i.e. this.movingplatform). I wanted it to be clear that the stage instance name is not being used here, because the unintentional instance name reference in your code is the source of your problem in the first place.
As far as i'm concerned, you have two options. use a for each, as adam smith suggested or use a for-loop as it was intended to be used :)
for(var c:uint = 0; c < MovingPlatformArray.length; c++){...
and btw: should "MovingPlatform[c].y += speed;" not be "MovingPlatformArray[c].y += speed;"?
edit: looking at your code, i would also suggest you use MovingPlatformArray[c].hitTestObject(birdie) instead of MovingPlatformArray[c].hitTestPoint(birdie.x,birdie.y,true)
If I were you, I would bring the logic for the platform out, and store it in a class. (Ideally you would do this for the birdie object as well). I have created an example below. The movieclips on the stage should extend Platform rather than MovieClip so they invoke the methods at the bottom.
// Use vectors if you know all the items are going to be the same type
var platforms:Vector.<Platform> = new <Platform>[];
for (var c:int = numChildren - 1; c >= 0; c--){
var child:DisplayObject = getChildAt(c);
// You shouldn't check against names (as per the original post). Because
// names should be unique
if (child is Platform){
platforms.push(child);
// This could be random so each platform has a different range
// This means platform 1 could go from y 30 to y 400, platform 2
// could go from y 60 to y 200, etc
child.setRange(25, 400);
}
}
this.addEventListener(Event.ENTER_FRAME, gameLoop);
// Have an overall game loop
function gameLoop(e:Event):void {
// Loop over the platforms
platforms.forEach(function(item:Platform, i:int, a:Vector.<Platform>):void {
// Hit test function in the class means you only have to pass in one mc
// rather than the points and a boolean
if(item.hitTest(birdie)) {
birdtelleryvertrager=0;
birdtellery = 0;
birdie.y-=14;
}
// Removed the movement logic, this should be kept out of the game loop
// plus how much better does this read?
item.move();
});
}
Then in a class location somewhere, like in a folder game/activeObjects
// A class for the platform stored else where
package game.activeObjects
{
import flash.display.MovieClip;
/**
*
*/
public class Platform extends MovieClip {
private const SPEED:Number = 2;
private var _direction:int = 1;
private var _minimumHeight:Number = 25;
private var _maximumHeight:Number = 350;
public function Platform() {
}
public function setRange(minimumHeight:Number, maximumHeight:Number) {
_minimumHeight = minimumHeight;
_maximumHeight = maximumHeight;
}
public function move():void {
this.y += SPEED * _direction;
if(this.y <= _minimumHeight) {
_direction = 1;
} else if(this.y >= _maximumHeight) {
_direction = -1;
}
}
public function hitTest(mc:MovieClip):Boolean {
return hitTestPoint(mc.x,mc.y,true);
}
}
}

Resources