Keeping track of game state for a Card Trading Game - lua

I am building a card game. Let's say it's similar to Magic the Gathering, Hearthstone, etc.
The problem I am trying to figure out is how to architect "auras" and how much damage each card takes.
Right now I have a deck and I store card data as follows. I've made up names for the types of cards that will exist.
M.card = {}
-- Minion cards have health and damage
M.card[1].name = "Minion"
M.card[1].hp = 1
M.deck[1].dmg = 1
-- Super Minions have more health and damage
M.card[2].name = "Super Minion"
M.card[2].hp = 4
M.card[2].dmg = 4
-- Spell cards have no health and damage. Instead they affect the health and damage of other cards.
M.card[3].name = "Heal"
M.card[3].details = "This card heals any character for +2 health"
M.card[3].healthboost = 2
M.card[4].name = "Damage Boost"
M.card[4].details = "This card gives + 1 damage to any other card for 1 turn"
M.card[4].dmgboost = 1
-- Super damage boost gives more damage boost to other cards
M.card[5].name = "Super Damage Boost"
M.card[5].details = "This card gives +3 damage to any other card permanently"
M.card[5].dmgboost = 3
So when one card attacks another card, I need to keep track of the damage taken by both cards. I don't want to change the base stats of each card so I need to keep track of adjustments.
I could do something like this
-- Super Minion takes 3 damage
M.card[2].newHp = 1
-- or
M.card[2].adjHp = -3
-- Not sure which is better.
During the battle I need to keep track of which auras are played. So for example if the Damage boost card is played. I need to give another card +1 damage for just one turn.
Let's say that I am keep track of each turn number starting from 1.
Should I do something like this
M.aura[1] = 4 -- ( aura 1 is card # 4)
M.aura[1].target = 2 -- (this aura is applied to card 2)
M.aura[1].expires = 5 -- (this aura expires on turn 5)
M.aura[2] = 3 -- ( second active aura is heal, card #3 )
M.aura[2].target = 2
M.aura[2].expires = 0 -- this is a one time aura. So I need to apply it to card #2 and then immediately expire it so it never activates again.
Then on every new turn I loop through all the auras and make sure they are still active before a fight begins?
Just wondering architecturally what is the best way to keep track of Damage that characters have taken and spells that are active that are giving characters special abilities.

Why not make the cards instances of immutable reference cards which are never altered during gameplay and compare against the parent of the instance? You could do that with some simple object orienting.
Alternatively, if that doesn't suit you, you could give each card a max or base HP field.
table.insert(M.card, {
name = "Wizard",
hp = 10,
basehp = 10,
dmg = 5
})
As for time-limited effects on cards, you could track that on the card itself.
M.card[1].effects = { { dmgboost = 2, expires = 3 } }
Give the base card a function such that it calculates the card's stats based off of effects.
function Card:damage()
local d = self.dmg
for _, v in ipairs(self.effects) do
if v.dmgboost then d = d + v.dmgboost end
end
return d
end
function Card:processEffects()
for i = #self.effects, 1, -1 do
if turn >= self.effects[i].expires then
table.remove(self.effects, i)
end
end
end
These are only a few examples out of many ways that you could handle this. However, I'm sure this will be enough to give you some ideas for now.

In order to make the existence of a card temporarily affect the qualities of another card, use an observer or callback pattern. When you "draw" (out of the deck or hand) the special card which adds +1 damage to all other friendly cards, presumably you have a function findAllOtherFriendlyCards(yourSpecialCard, allCards) which does that. Then in this function, for each friendly card you find, you "register" the friendly card with the special card by calling specialCard:registerDiscard(yourFriendlyCard). Then when the special card gets discarded, which presumably happens by calling specialCard:discard(), this method looks at all registered cards and re-adjusts the attribute that the special card affected.
In fact, you don't need a separate register method, you could just have a specialCard:startEffect(friendlyCard): this method adjusts some attributes on friendlyCard and keeps it in a list; and you have a specialCard:endEffect(friendlyCard) which does the opposite, i.e. adjusts the attributes in the opposite direction (+1 if it -1'd at start, etc) and removes the card from its list of affected cards.
If friendly cards can be discarded before the special cards get discarded, you use a similar technique: the friendly card must keep track of every special card that adjusted it and notify the special card when it gets discarded, so the special card can remove it from its list.

Related

Getting the value of an "IntValue" always gets default value

I am a student and I am trying to get the value of an "IntValue" to use has level, what I mean by this is that I need to have a skill level for each individual player and use this skill level to multiply the amount of damage the skill does.
for example: Skill level is 5
the damage should be: baseDamage * SkillLevel
in my case. base damage is 2 so the end result should be 10 damage.
but when I try doing this whit code it doesn't work. (I'm not the best at LUA and I'm fairly new to stack so I apologize in advance)
Code (So far I got this):
local XP = 0 --Exp Amount
local LevelValue = player.Backpack.ScriptStorage.Player.SkillLevel.Value --Gets the value of the skill level from the "IntValue"
--Other code that I don't want to show (it just checks if a remote event has fired the server, and it adds .5 to the XP every time it fires)
--This is the line that should add 1 to the level
LevelValue = LevelValue + 1
--But everytime it gets to 2 it simply gets set back to 1 (the default level)
I just showed the relevant pieces of code. everything that was not relevant to this wasn't shown (except for: XP = XP + .5 which is in the code I'm not showing)
hope this helps figure out what the problem is. as said above: "I'm not the best at LUA and I'm fairly new to stack so I apologize in advance"
In your code, you store SkillLevel.Value into the LevelValue local variable. This takes a snapshot of that value and stores it in the variable. So when you modify the local variable, you are not updating the IntValue object that is storing SkillLevel.
When you want to update SkillLevel, you need to update the IntValue directly :
local SkillLevel = player.Backpack.ScriptStorage.Player.SkillLevel
local LevelValue = SkillLevel.Value
-- .. do some other stuff
-- add 1 to the level
SkillLevel.Value = SkillLevel.Value + 1

Issue returning desired data with Lua

Wondering if I could get some help with this:
function setupRound()
local gameModes = {'mode 1','mode 2','mode 3'} -- Game modes
local maps = {'map1','map2','map3'}
--local newMap = maps[math.random(1,#maps)]
local mapData = {maps[math.random(#maps)],gameModes[math.random(#gameModes)]}
local mapData = mapData
return mapData
end
a = setupRound()
print(a[1],a[2]) --Fix from Egor
What the problem is:
`
When trying to get the info from setupRound() I get table: 0x18b7b20
How I am trying to get mapData:
a = setupRound()
print(a)
Edit:
Output Issues
With the current script I will always the the following output: map3 mode 2.
What is the cause of this?
Efficiency; is this the best way to do it?
While this really isn't a question, I just wanted to know if this method that I am using is truly the most efficient way of doing this.
First of all
this line does nothing useful and can be removed (it does something, just not something you'd want)
local mapData = mapData
Output Issues
The problem is math.random. Write a script that's just print(math.random(1,100)) and run it 100 times. It will print the same number each time. This is because Lua, by default, does not set its random seed on startup. The easiest way is to call math.randomseed(os.time()) at the beginning of your program.
Efficiency; is this the best way to do it?
Depends. For what you seem to want, yes, it's definitely efficient enough. If anything, I'd change it to the following to avoid magic numbers which will make it harder to understand the code in the future.
--- etc.
local mapData = {
map = maps[math.random(#maps)],
mode = gameModes[math.random(#gameModes)]
}
-- etc.
print(a.map, a.mode)
And remember:
Premature optimization is the root of all evil.
— Donald Knuth
You did very good by creating a separate function for generating your modes and maps. This separates code and is modular and neat.
Now, you have your game modes in a table modes = {} (=which is basically a list of strings).
And you have your maps in another table maps = {}.
Each of the table items has a key, that, when omitted, becomes a number counted upwards. In your case, there are 3 items in modes and 3 items in maps, so keys would be 1, 2, 3. The key is used to grab a certain item in that table (=list). E.g. maps[2] would grab the second item in the maps table, whose value is map 2. Same applies to the modes table. Hence your output you asked about.
To get a random game mode, you just call math.random(#mode). math.random can accept up to two parameters. With these you define your range, to pick the random number from. You can also pass a single parameter, then Lua assumes to you want to start at 1. So math.random(3) becomes actually math.random(1, 3). #mode in this case stand for "count all game modes in that table and give me that count" which is 3.
To return your chosen map and game mode from that function we could use another table, just to hold both values. This time however the table would have different keys to access the values inside it; namely "map" and "mode".
Complete example would be:
local function setupRound()
local modes = {"mode 1", "mode 2", "mode 3"} -- different game modes
local maps = {"map 1", "map 2", "map 3"} -- different maps
return {map = maps[math.random(#maps)], mode = modes[math.random(#modes)]}
end
for i = 1, 10 do
local freshRound = setupRound()
print(freshRound.map, freshRound.mode)
end

How can I get values from multiple instances of a class?

I am making a roguelike in Love2D as a hobby project. My approach is to try and use as much of the native capabilities of Lua and the Love2D (0.10.1) API as possible, without relying on fancy libraries like middleclass or HUMP, so as to learn more about the language.
After reading PiL's chapters on OOP and seeing the power there, I decided to set up a Mob class (using metamethods to emulate class functionality) that encompasses the players, monsters, and other NPCs (anything that can move). So, far, it's working beautifully, I can create all kinds of instances easily that share methods and all that stuff. But there's a lot of things I don't know how to do, yet, and one of them is holding my prototype up from further progress.
Setting up collision with the map itself wasn't too bad. My maps are tables full of tables full of integers, with 0 being the floor. The game draws "." and "#" and "+" and such to denote various inanimate objects, from each table. Player 1 moves using the numpad, and their position is tracked by dividing their raw pixel position by 32 to create a grid of 32x32 "tiles". Then, inside love.keypressed(key), I have lines like:
if key == "kp8" and currentmap[player1.grid_y - 1][player1.grid_x] == 0 then
player1.grid_y = player1.grid_y - 1
and so on, with elseifs for each key the player can press. This prevents them from walking through anything that isn't an open floor tile in the map itself.
But, I'm trying to implement some kind of "collision detection" to prevent MOBs from walking through each other and to use in writing the rules for combat, and this is trickier. I had a method in place to calculate the distance between mobs, but I'm told this might eventually cause rounding errors, plus it had to be written for each combination of mobs I want to test, individually.
What I'd like to know is: Is there a known (preferably elegant) way to get all instances of a particular class to pass some number of values to a table?
What I'd like to do is "ask" every Mob on a given map where they are, and have them "report" self.grid_x and self.grid_y to another layer of map that's just for tracking mobs (1 if self.is_here is true, 0 if not, or similar), that gets updated every turn. Then, I could implement collision rules based on coordinates being equal, or maybe a foo.is_here flag or something.
I have only vague ideas about how to proceed, however. Any help would be appreciated, including (and maybe especially) feedback as to a better way to do what I'm trying to do. Thanks!
A simple idea is to store "who is here" information for every cell of the field and update this information on every move of every object.
function create_game_field()
-- initialize a table for storing "who is here" information
who_is_here = {}
for y = 1,24 do
who_is_here[y] = {}
for x = 1,38 do
who_is_here[y][x] = 0
end
end
end
function Mob:can_move(dx, dy)
local u = currentmap[self.y + dy][self.x + dx]
local v = who_is_here[self.y + dy][self.x + dx]
if u == 0 and v == 0 then
return true
else
end
end
function Mob:move(dx, dy)
-- update "who is here"
who_is_here[self.y][self.x] = 0
self.x, self.y = self.x + dx, self.y + dy
who_is_here[self.y][self.x] = 1
end
function Mob:who_is_there(dx, dy) -- look who is standing on adjacent cell
return who_is_here[self.y + dy][self.x + dx] -- return mob or nil
end
function Mob:roll_call()
who_is_here[self.y][self.x] = 1
end
Usage example:
-- player1 spawns in at (6,9) on the grid coords
player1 = Mob:spawn(6,9)
-- player1 added to who_is_here
player1:roll_call()
Then, in love.keypressed(key):
if key == "kp8" and player1:can_move(0, -1) then
player1:move(0, -1)
end
There are a few ways you could get all your instances data but one of the simpler ones is probably to have them all be added to a table when they are created. Providing you add the entire table for that instance, all the values will update in the main table because it acts like a collection of pointers.
function mob:new( x, y, type )
self.x = 100
self.y = 200
self.type = type
-- any other declarations you need
table.insert(allMobs, self)
return self
end
Here we insert all the mobs into the table 'allMobs'. Once we have that we can simply iterate through and get all our coordinates.
for i, v in ipairs(allMobs) do
local x, y = v.x, v.y
-- Do whatever you need with the coordinates. Add them to another table, compare
-- them to others, etc.
end
Now we have a table with all our mobs in it and a way to access each of their positions. If you have any further inquiries then let me know.

How to calculate RPG Level Progression as percentage

I'm designing an RPG game where a user may accumulate experience points (XP) and level up, based on XP. Calculating the current level is trivial; if else seems to be most efficent.
I would like to calculate percent of progression for the current level. Based on the assumption that I have 10 levels, where each level is capped at a somewhat exponential value:
typedef NS_ENUM(NSInteger, RPGLevelCap) {
RPGLevelCap1=499,
RPGLevelCap2=1249,
RPGLevelCap3=2249,
RPGLevelCap4=3499,
RPGLevelCap5=4999,
RPGLevelCap6=6999,
RPGLevelCap7=9999,
RPGLevelCap8=14999,
RPGLevelCap9=19999,
RPGLevelCap10=19999 // Anything beyond previous level is Lvl 10; display as 100%
};
What's an efficient, yet easily understandable way, to calculate a user's level progression based on their current level?
An if else statement is both hard to understand and maintain, but may be fairly efficient:
float levelProgression=0;
// Calculate level progression as a fraction of 1
if(xp <= RPGLevelCap1)
{
levelProgression = ((float)xp / RPGLevelCap1);
}
else if (xp <=RPGLevelCap2)
{
levelProgression = ((float)(xp-RPGLevelCap1) / (RPGLevelCap2-RPGLevelCap1));
}
else if (xp <=RPGLevelCap3)
{
levelProgression = ((float)(xp-RPGLevelCap2) / (RPGLevelCap3-RPGLevelCap2));
}
...
else if (xp>RPGLevelCap10)
{
levelProgression = 1;
}
Given that the level caps are inconsistent...how should I handle this problem?
Hmm. A simple way would be to store the level cap values in an array. Find the player's current level based on the largest value it's less than. (level one is 0 to 499, level two is 500 to 1249, etc.) Use a loop to find the user's level rather than a set of if/else statements.
Then calculate the range of the player's current level, (end_of_range - start_of_range)
0 - 499 = 499
500 - 1249 = 749,
etc.
If a player is at 600 points, he's a level 2 character, in the 500-1249 range.
He's at 600-500 or 100 points into the range. (600-500)/749*100 is the player's percent complete in that range. 13.35% complete, in this example.
There are a few ways you can approach this. My weapon of choice here is to embody the XP values within the concept of LevelData versus using an enum, array of XP values, etc. The benefit of something like this is that for each level, you'll typically have many configurable values (for example level based multipliers) based on level. This way they are all in once place.
In your LevelData, there are different ways you can encode XP. These range from the XP total for the next level, or the beginning and end XP total for the next level. Obviously there are other permutations of this.
I usually use the later, mainly because it prevents me from having to "know" the LevelData for the previous level.
So I would typically have this in JSON
{
"levelData": [
{
"levelStartXP": "0",
"levelUpXP": "250",
"levelUpBonus": 0
},
{
"levelStartXP": "250",
"levelUpXP": "1000",
"levelUpBonus": 50
},
{
"levelStartXP": "1000",
"levelUpXP": "2500",
"levelUpBonus": 100
},
]
}
This is just a boring example. I then of course have a LevelData class which is embodies each level. I also have a LevelDataManager. That manager is used to vend out information per level. Having convenience methods help. For examples, good ones to have are:
- (NSInteger)levelWithXP:(NSInteger)xp; // for a given XP, give me the appropriate level
- (LevelData *)levelDataWithXP:(NSInteger)xp; //for a given XP, give me the appropriate LevelData
- (LevelData *)levelDataWithLevel:(NSInteger)level; // for a given level, give me the appropriate LevelData
- (NSInteger)levelUpXPForLevel:(NSInteger)level; // for a given level, give me the XP value needed for the next level)
I just arbitrarily used NSInteger, use the appropriate data type for your case.
Just what you want to support is really up to you.
The gist of the whole thing is try not to store individual level components. Rather aggregate them in LevelData or some other collection, so you have all the info per level at your disposal and create some form of manager/interface to get you the information you need.
So back to you your question. Let's say we have a class for LevelData (assume it is using the JSON above, and those fields are represented by properties) and a LevelDataManager instance called theLevelDataMgr, you could compute the % based on something like:
LevelData *levelData = [theLevelDataMgr levelDataWithLevel:currLevel]; // currLevel is the current level
float xpDiffInLevel = levelData.levelUpXP - levelStartXP;
float xpInLevel = currXP - levelStartXP; // currXP is the current XP of the user
float pctOfXP = xpInLevel / xpDiffInLevel; // You should add divide by zero check
And yes, if you wanted, you could have the LevelData class contain a method to do this calculation for you to help encapsulate it even better.
Code isn't tested and is listed to just give you a better idea on how to do it. Also, how you decide to store your XP for each level dictates how this would work. This code is based on the method I usually use.

Computercraft variables

I am making a bank on Minecraft.
I am having trouble with saving a variable after addition or subtraction has been done to it.
For example, if x="balance", x=15, say I want to withdraw from my balance:
x = 15 - y(withdrawn money)
The variable is not saved when the program is run again.
If you want data persistence between program runs, you need to store the data in files. For example, you could save the variable x to a file like this:
h = fs.open("filename","w")
h.writeLine(x)
h.close()
And you could load it like this:
h = fs.open("filename","r")
x = tonumber(h.readLine())
h.close()
Here is the documentation. http://computercraft.info/wiki/Fs.open
Here is a first stab at it. I suppose the account balance is stored in x. Then the following function will withdraw and return money from x.
-- wa is amount to withdraw
-- this function withdraws the maximum allowable
function withdraw(wa)
if wa>0 then
wt=math.min(x,wa)
if wa <= x then
x=x-wt
return wt
end
end
return 0
end
A far more sophisticated way to keep accounts is available in the PiL book: http://www.lua.org/pil/16.html

Resources