Space Fleet Battles

Community Forums/Showcase/Space Fleet Battles

Mainsworthy(Posted 2017) [#1]
https://numbergamer8.itch.io/space-battles-2d-hard-core-wargame





the boardgame star fleet battles was about a small number of ships, battling on a 2d hex map, it used energy to power weapons shields and movement.

I have made a computer game , but not a remake of the boardgame, it follows the type of play, this was because the boardgame did things like move each unit one hex at a time in an impulse, but I have made the full movement phase into move points, I have made an energy allocation phase for powering shuttles , decoys, misiles, nuclear misiles, emp attack shuttles, movement and shields, the shields can be angled towards incoming attacks, you can face in any of the 6 directions of the hex grid just like the boardgame, it has an A.I. but its more fun playing solo with enforced rules I think.

its free and is for windows


Mainsworthy(Posted 2017) [#2]
shuttles, misiles, energy allocation phase, plantes, moons, space stations, radio beacons, mines, asteroids, planetary and moon aswell as space station defences, hotseat and A.I.

I DONT KNOW IF PICARD ACTUALY CARE BUT I added PLANTS, MOONS, RADIO BEACONS(for kirks orders) , MINES, MINE SWEEPING, ASTEROIDS,

MAJOR UPDATES TO MISSILE EFFECTS. & ENERGY ALLOCATION SCREEN OPTIONS UPDATE


coffeedotbean(Posted 2017) [#3]
Mr Mainsworthy - you are a machine, knocking these games out faster than EA knocks out Call of duty sequels.


Mainsworthy(Posted 2017) [#4]
I think I'm on to much coffee :)


Mainsworthy(Posted 2017) [#5]
Ive updated some of the art, pics above


AdamStrange(Posted 2017) [#6]
looking good

ok, can we talk files and data?
looking in the folder there are lots of files.
further still there is a modules with modue1,2, etc
and. side this are duplicate graphics and a large .dat file (4.3mb).

Could I ask exactly how you store your data?

Do you use a binary or text format and why so big a file?
assuming you have a map of 128x128 with an int (4 bytes) for each map position. that would give you a block of 64k.

given 4.3mb that would give you approx 69 map of the above (128x128x4)

What is the actual data you are storing?


Mainsworthy(Posted 2017) [#7]
I did wartac the same way, then I had to internaly compress it, then decompress it, If you carryon and do a war game in hexes you will find out for yourself how much data there is, keeping track is really intense, you can program in a way that dosnt use data storage, but I keep track better by having a slot for every piece in an array, its not best but it works for me, and to be honest I have learned as I went and found many things I could of done better, but if I had not done them wrong in the first place I would not of known anyway.

its a simple matter of compressing the zeros, but winzip and WinRAR do that anyway, so its just a few mb on your hard drive when unzipped.

each module has its own chits, and saves, aswell as settings , and hextiles, so a mix of modules can be made a 17th century fort, and a space module can co exist in the same module folder

Adam I have learned from you to be better at planning and organisation, my future projects will improve I think.


AdamStrange(Posted 2017) [#8]
no problem.

I'm using a layered map system, where each 'map' is an 2x x,y array of shorts (short is a 16bit int)
The first x,y array is 'generally' used as a designator (how it's designated is up to you) and the second I am using as a color reference.

so each map position has a kind (designator) and a color. So you could have a basic hex graphic as kind 1. (0 is usually null/none)
then you have a color reference, e.g. green, light green, blue, etc

I say layered as the data format allows multiple maps to be stored, so a second map is then 'overlaid' on the first map. Lets sake kind 1 is a tree and kind 2 is a bush.

we now have a map position with a base + color and something on top of it (tree or bush) also in different colors.

Lets assume the kind reference points to a bitmap font (0 to 255) and the color reference to a palette.

To change the color, you just give a different palette, to change the graphics a different bitmap font. the fonts could be completely different sizes. E.G. 8x8 or 32x32 for higher resolutions

Firestarter (the hex game i'm working on) has a total unpacked data (graphics included) of 171k with most of it being the graphics (158k)
the map file is currently 8k. but I will need to add more for more levels.


Derron(Posted 2017) [#9]
Using "defaults" and saving "deltas" should lower storage consumption.

So instead of writing
tile 1,1 = grass
tile 1,2 = grass
tile 1,3 = water
tile 1,4 = grass

you could save (edit: "write"):
default = grass
tile 1,3 = water


Also: instead of storing the current state, you could always just store the "initialization params" + done actions.
So on load up you "replay" what happened till then.

This is useful for certain multiplayer architectures - also it allows for easy "replay" functionality (maybe you are interested in tackling this for one of your projects - to learn something new).



But aside of that: we talk about some K of used space, today a really really small amount.


bye
Ron


AdamStrange(Posted 2017) [#10]
I think what darron means is:
- default = grass
set all tiles = grass

- tile 1,3 = water
set tile 1,3 = water

today a really really small amount.

true, but can also be a really good indicator of where you possible need to look at different ways of doing things.

Remember desktops have generally lots of memory but handheld not so much. and this is also the same for app stores where there are preferred ways of deal with file-sizes.

I would say that you should really be aiming to have the least possible sizes for downloads, memory, graphics, etc. So anything that is unused should be removed.
This goes for data too. I personally think that 4.3mb for a data save (in mainsworthys case) is overly large. hence wondering what was being stored in it as I couldn't work out why it should be too big.


Derron(Posted 2017) [#11]
I cut down data size by using defaults and getters. So instead of RAM I use a small amount of additional CPU cycles. It is always a tradeoff.

Instead of storing 20 different sprites we colorize or do compound-images like these spline-based-animations (arms + legs + body + ...).


@ saving
When saving "known" data-structures, you could skip saving X and Y. You already know that information.

So if your cells contain the information:
cellX, cellY, type
And your map is sized (in this level) 10x10 you store:
10
10
typeAt1,1
typeAt2,1
...
typeAt10,10

When loading in you at first read the level size and then fill/create the "tiles" accordingly.


Next thing to "shape off" memory used: bitmasks.
If you only have 8 types of tiles and eg. additional states like "undiscovered/fogOfWar" ... you store them all in one integer.
While for "types" it is a bit odd (you normale use a enum instead of a bitmask) it should still work.

TYPE_GRASS:int = 1
TYPE_FOREST:int = 2
TYPE_WATER:int = 4
...
TYPE_MOUNTAIN:int = 64
OPTION_FOG_OF_WAR:int = 128
OPTION_DEATH_AND_DECAY:int = 256

An undiscovered grass tile with an acid cloud would store "options= 385" (1+128+256). Then each tile has a Getter-function to retrieve certain information (IsType(TYPE_GRASS), HasOption(OPTION_FOG_OF_WAR)).

You could store 32 "on/off"-variables in an BlitzMax-integer (and of course more in the 64bit variants like "long")


I would also use it for indicators like "players on this tile". Eg. you have player1onTtile:int = False
player2onTtile:int = False
and so on.

But you could encode that information in a bitmask. Player1=1, P2=2, P3=4 and P4=8
if all occupy a tile then you would store 1+2+4+8 (15), if only P1 and P2 are there, then 1+8 (9).

Pretty easy hmm?

so remember: every "on/off" (or true/false) variable could be stored in such a bitmask to save some space in the data structures.


If you think you never came accross bitmasks: the FLAGS when doing a LoadImage() is such a bitmask.


bye
Ron


Mainsworthy(Posted 2017) [#12]
That's great advice, I never realised it before you pointed it out that I programed a chess engine in binary that uses the bitmask approach, but without stepping back and looking at what I was doing I didn't absorb it as a method to be used in other things. I used a lot of logic gates in that chess program, bitmasks are logic gates really.



its on itch.io too https://numbergamer8.itch.io/marks-chess


Derron(Posted 2017) [#13]
The interesting thing with bitmasks is the combination.

So eg. a "swamp" tile might not be defined as "swamp" itself, but as "grass" or "forest".
Now add the Option/State "swampy" or "overwatered" and it becomes a swamp.
Now let someone build a mine on the next tile - drying out the surrounding tiles (removing "swampy"): tiles become "grass" or "forest" again automatically.

Also tiles could now have a "contains"-bitmask: You do not need to have a tile of type "mountain" to be able to construct an iron ore mine there - just have a flag indicating that this tile contains an (unknown/not-set) amount of iron ore.


bye
Ron


Mainsworthy(Posted 2017) [#14]
I never really found the motivation to optimise because destops are so good these days, but what your both saying really makes a lot of sence, I'm like einstien I simplfy rather than spend time on something I can get away with without the trouble.


Mainsworthy(Posted 2017) [#15]
chess algorythms use bitmasks for the board, if you want to know what squares a knight can move to use a bitmask on the board of all possible night moves cut off by the edge of the board


Derron(Posted 2017) [#16]
Especially for big loops it is often a good thing to optimize. Just ask Brucey what he optimized in my game ... he checked what was done most often - and if possible reduced that.

So if your loops do a lot of "if then ..." a bitmask might be faster.

Think again about the example above:

if type = TYPE_FOREST then
  if is_watered then
    if is_snowy then
      'ohh icey forest... decrease walking speed / use more turns
...


If you used a bitmask, you could prepare one flag which contains all of them and then just check if the current bitmask contains that number. You reduced 3 if's with 1 if.



Of course most often you just should use it for boolean option-flags to save some space, the other things are only useful when not just "protoyping out games" (which you often do: sketching out ideas to binaries and then going on to the next experiment/idea). Optimization is not needed then - except for learning something (which is good - imho).



@ Chess
Yes, 8x8 is really fitting well into bitmasks.
Like said, it is often used to store options: in that case:
1,1 open?
1,2 open?
1,3 open?
...

For GUI elements it gets more important:
- visible?
- clickable?
- can contain children?
- can get parented?
- can ...

bye
Ron


Mainsworthy(Posted 2017) [#17]
I seen this snippet of code in the forum, can you explain it, it boggling me how TCar can have TWheel that contains Tcar isn't that a crazy loop, Ive never done much like this type of programing, it looks like its dynamic .


Type TWheel
Field parentCar:TCar
End Type

Type TCar
Field name:string
Field wheels:TWheel[4]
End Type

global car:TCar = new TCar
car.wheels[0] = new TWheel
car.wheels[0].parentCar = car

car.wheels[0].name = "testcar"
print car.name


AdamStrange(Posted 2017) [#18]
ok, in simple terms.

Yes it could be thought of as circular, but... the fields, when referencing another type (parentCar is not an int but a type) are just pointers.

so car.wheels[0].parentCar = car allows stuff in TWheel to reference it's parent.
E.G. wheel has a hubcap that is the same color as the car. The wheel then would need to be able to reverse (get its parent) backwards to get the car color.

The wheel doesn't have a color itself and one is not set. but using parent references the wheel can examine the parent color.

Now... Circular
It is definitely possible that you 'could' also do the following
wheel1 = car.wheels[0].parentCar.wheels[1]

did you see what happened?
we took a car and looked at it's first wheel, then it's parent and then wheel 1

In practice you would never do this, and it's a bit complex and stupid, but you 'could'

You are right about it being dynamic. When you say:
new Tcar
What you are really doing is allocating memory space that the class TCar would need.


In practice TCar + TWheel is a bit 'educational' in that it is showing the relationship of oop classes and how they can reference themselves.

A Class is a basic oop principle, but you can get lost quickly. especially when someone throws in pointers and lists, etc.


Mainsworthy(Posted 2017) [#19]
you just pointed out the magic key, its a type , not a real variable yet, thanks


Derron(Posted 2017) [#20]
That example was there to explain "circular references" too.

See that "global car:TCar" ? It is the only variable we define. The wheels are stored in "fields", so are only accessible when knowing the "car" using it.

So garbage collector now runs ... and sees:
- wheels in the fields are still referenced (by car), do not free them
- wheels.parentCar is still existing, do not free it
- global car is still used, do not free its memory



but what happens if we now set "car" to "null" ?

The only anchor to the "wheels" is gone. We cannot access the wheels anylonger and we cannot "null" the parentWheel reference.

Now garbage collector wants to run again:
- wheels? I know of some wheels, they are no longer used by "car" (which is null now) but they are still referenced by "wheel.parentCar" - keep them
- wheels.parentCar still references the object behind "car" - which references wheels, so do not remove wheels and parentCar

So this means: we lost availability to free the occupied memory again because we do not have a variable referencing one of the wheels or the car.
If we would have stored "wheel1:Twheel" before - we could reaccess them with "wheel1.parentCar.wheels...". But this does not cure the cyclic-dependency problem.


cyclic dependency?

TCar needs to know about TWheel
TWheel needs to know about TCar

in a world without the Blitzmax command "import" this is not a big problem, everything is in a big source file and therefor able to see the other classes/types.
But once you use "import" (means you import a self-contained object + dependencies" you will not be able to do this.

"car.bmx" would import "wheel.bmx"
"wheel.bmx" would try to import "car.bmx"
-> compilation fails because of this cyclic dependency.

To get rid of this you could use interfaces or "base classes".


"car.base.bmx" contains "TCarBase" which does not contain references to "TWheel" (and maybe "TCarDriver")
"car.bmx" contains "TCar extends TCarBase" and imports "car.base.bmx" and also "wheel.bmx"

Now TWheel does no longer have "parentCar:TCar" but "parentCar:TCarBase" - and is therefor able to troublefree import "car.base.bmx" (instead of "car.bmx").

Of course you could even have a "TWheelBase" (without access to TCarBase) and so on.
Within the base classes you could add "stub functions" which you override in the standard implementation (TCar, TWheel, ...).

So TCarBase.GetWheelCount:int() will return "0", and overridden "TCar.GetWheelCount:int()" will return "wheels.length".

Same for "wheelhub-color": base class returns "none", the standard implementation returns the color of the parentCar.




bye
Ron


Derron(Posted 2017) [#21]
@ type vs variable

A variable contains an instance of a type ...


bye
ron


Mainsworthy(Posted 2017) [#22]
I get it now, pointers, Ive done pointers in C++ *pointer and dereferenced &pointer , and I passed global variables as an array, but I never really bothered to use them much, but I completely understand now. thanks guys. blitz is good having pointers .


AdamStrange(Posted 2017) [#23]
another way to think abut blitzmax types is a type 'could and usually is' a mini program

E.G.
lets have a very quick particle system
type TParticle
		field x:float
		field y:float
		
		method SetXY( xin:float, yin:float)
			x = xin
			y = yin
		end method
		
		method Update()
		end method
end type


we can see there are two variables x and y
plus some methods. a method is like a function, but only available to the type.
so we can have a new particle by using
particle:TParticle = new TParticle

set the xy location to 10, 20 by using
particle.SetXY( 10, 20 )

and update it by using
particle.Update()

Notice this wont do anything as there is no update code

here's some clever stuff
lets extend the particle type and make is move

add this:
type TLeftParticle Extends TParticle
		method Update() 'this will be called not the one in TParticle
			x = x + 0.1
		end method
end type


now when we create a new particle we use the new version
particle:TLeftParticle = new TLeftParticle

when we call Update(), the value of x will change


Mainsworthy(Posted 2017) [#24]
that's great Adam , I never got into this sort of programing, looks very interesting.


Mainsworthy(Posted 2017) [#25]





new pics of the extra modules added, one is MAD MAX , and the other is a battleship strike (not pearl Harbor :)


AdamStrange(Posted 2017) [#26]
mmmmm, Mad Max

filmed in Austrailia, so the locale is road with tundra not green grass with no roads

key elements are cars - police, and Max's final uber car. plus bikes and some footwork.

So you really need to use the cars and local. here's some pics for you to think about ;) these are mad max - not 2 or 3 or the new one












Derron(Posted 2017) [#27]
particle:TLeftParticle = new TLeftParticle


The real benefit is to not need to know about the actual implementation:
particle:TParticle = new TLeftParticle

So you can still call all methods also declared in the base type definition.


Bye
Ron


Mainsworthy(Posted 2017) [#28]



http://www.sjgames.com/car-wars/games/classic/

the rulebook is free, you could do your own art for this adam, Id want it , it was a huge game.


Imperium(Posted 2017) [#29]
You should look into the old RoadWar games. Especially if you like Post Apocalyptic car combat! The tactical combat is freaken intense!