GithubHelp home page GithubHelp logo

eschan145 / kingdomsandcastles Goto Github PK

View Code? Open in Web Editor NEW
2.0 1.0 0.0 2.68 MB

An advanced battlefield simulator.

License: MIT License

Python 100.00%
pyglet python python-arcade simulator wargame opengl medieval

kingdomsandcastles's Introduction

Armies

An advanced battlefield simulator. If you are new to this respository, please note that the development information is needing updates. Do not refer to this for documentation. Use the source code instead.

In this game, you command an army of soldiers. They can be of the following types:

  1. Light infantry — Regular ordinary foot soldiers
  2. Heavy infantry — Heavily armored but slower foot soldiers
  3. Archers — Soldiers specialized in use with a bow

Your job is to battle an enemy army. Your army is split up into multiple units; each one you can command at your will. You can assign commands to inidividual units. Units can contain smaller units with them, culminating into one huge army. This simulation is designed to be as realistic as possible, so soldiers will take longer to move up hills, bodies are not removed, units somewhat dissolve when commander is killed, etc.

A soldier is armed with a sword and a bow. Their damage is based on their strength, range, and morale. For example, an arrow would inflict more damage at close range than far range. They start out with twenty-four arrows (fifty for archers) and their health is set to 100. Soldiers attack individually if commanded to. If enemies are too far for swords, they use arrows. As a commander, you can also tell them to retreat into lines if near defeat.

An arrow is fired every 10,000 frames for light infantry, 7,000 frames for heavy infantry, and 15,000 frames for archers. An arrow's damage is calculated by multiplying its velocity in px/s by 15.

Commands

Commands help you lead your army and attack.

Command Key Details
Volley Q has all archers fire arrows simultaneously at selected targets. Note that this uses up arrows
Split W split unit into individual soldiers to attack. Loses defenses of ranks and volleys
Switch projectile E switch between arrows, fire arrows, spears, ...
Move forward move your army forwards
Move backward move your army backwards
Move left move your army left
Move right move your army right

Installation

Requirements:

  • Python 3.6 or higher. At the time of writing, the latest version is 3.10.5, which can be found at the Python website
  • Python arcade library

To install this, you must download the Python arcade library.

  1. Open up the Command Prompt (Type "cmd" in the search bar and press Enter
  2. Type in py -m pip install arcade --user or python -m pip install arcade --user
  3. Press Enter

If the download is successful, download this respository and open it with your favorite code editor.

Formations

Formations are in a three-dimensional list.

  • 1 signifies light infantry
  • 2 signifies heavy infantry
  • 3 signifies archer
  • 4 signifies unit commander

Development information

TODO

At this point, soldiers need to have more realistic melee attacks. They just swarm into the enemy, instead of pushing their way in. They also can flow through each other, and collision checks need to make them not run into each other.

Source files

Geometry

This file contains geometric functions to be used in Armies.

Point

cube(value)

square(value)

are_polygons_intersecting(a, b)

check_collision(a, b)

convert_one_to_four_quadrants(x, y, width, height)

get_closest(object, list)

get_distance(a, b)

is_point_in_polygon(x, y, points)

set_hitbox(object)

set_polygon(object)

_check_collision(a, b)

GUI Documentation

Source code: https://github.com/eschan145/Armies/blob/main/widgets.py

The GUI interface is completely created by Ethan Chan. It includes several different types of interactive widgets, and more are to be added. API is provided to create your own widgets, which can subclass the Widget base class. All events are supported. All states can be accessed with .hover, .press, and .disable properties. Many widgets have components, which are basically other widgets added within it. For example, the toggle widget has three components: label (for the text), image (for the bar), and image (for the knob). Its main component is the bar, which takes the hover event and hitbox. I worked really hard on the docs and code so please enjoy it.

To start a GUI interface, use the Container class. Initialize this once in your __init__ function. To start adding widgets, create widgets with their parameters and properties. Add them to the container. In the on_draw function, call the container's draw function. To end the container and terminate its events, call its exit function. If you want to draw each of the widgets's hitboxes, call its draw_bbox(width, padding). Calling destroy() on a widget disconnects it from the event framework and removes it from the container. check_collision(x, y) sees if the x and y point is colliding with the widget. If that fails, use _check_collision(x, y).

Currently, the GUI toolkit is being upgraded to support more features, like sizing of Buttons and more customization options. The shapes are also going to be upgraded. This upgrade is scheduled to be finished by the end of August 2022.

List of widget events:

Event Parameters Details
on_key keys, modifiers a key is pressed
on_lift keys, modifiers a key is released
on_hover x, y, dx, dy the widget is hovered
on_press x, y, buttons, modifiers the widget is pressed
on_release x, y, buttons, modifiers the widget is released
on_drag x, y, dx, dy, buttons, modifiers the widget is dragged (only for sliders)
on_scroll x, y, mouse, direction the widget is scrolled (only for sliders)
on_focus the widget has focus
on_text_select motion the widget has text selected (only for Entry widgets)
draw draw the widget
update update the widget

image

Source code: (NOTE: none additional commands, properties, or events were used to save space)

class MyWindow(Window):
    def __init__(self, title, width, height):
        Window.__init__(
            width, height, title
        )

        self.container = Container()

        self.label = Label(
            "Label",
            10,
            60,
            multiline=True,
            width=500)

        self.button = Button(
            "Click me!",
            250,
            250,
            command=None)

        self.toggle = Toggle(
            "Show fps",
            250,
            350)
        
        self.slider = Slider(
            None,
            250,
            300)

        self.entry = Entry(
            250,
            160)
        
        self.container.append(self.label)
        self.container.append(self.button)
        self.container.append(self.toggle)
        self.container.append(self.slider)
        self.container.append(self.entry)

    def on_draw(self):
        self.clear()
        
        self.container.draw()

Labels

A label is a great and easy way to draw text. Labels are used as components in many widgets, including buttons and sliders. They are fast, but as the number of them approaches the dozens, the FPS drastically slows down. About 100 labels drop the FPS from 60 to 8.

Parameter Details
text str or None, HTML text of label
x int x coordinate of label
y int y coordinate of label
colors list, [normal, (hover, press, disable)]. Has default colors of label in RGB
font tuple, (family, size). Defaults to ("Montserrat", 12) font of label
title bool. Defaults to False label displayed as title?
justify str, (LEFT, CENTER, or RIGHT). Defaults to LEFT justification of label
width int. Defaults to 0 maximum width of the label (used with multiline)
multiline bool. Defaults to False label with multiple lines?
command callable. Defaults to None command called when pressed
parameters list. Defaults to [] parameters used in command
outline tuple, (color, padding, width). Defaults to None create outline surrounding label

All properties, including others like .alpha, document (pyglet HTML document), length (length of text), and height can be accessed.

Buttons

A button is the simplest interactive widget. It can be given a command as a function when clicked.

Parameter Details
text str, HTML text of button
x int x coordinate of button
y int y coordinate of button
command callable. Defaults to None command called when pressed
parameters list. Defaults to [] parameters of command
colors list, [button, text]. Has default colors of button in str and RGB
font list, [family, size]. Defaults to ["Montserrat", 12] font of button
callback str, (SINGLE, DOUBLE, or MULTIPLE). Defaults to SINGLE frequency of invoking command

Components:

  • Image (self.image)
  • Label (self.label)

A button can be invoked by using the invoke() function. This sets the state of the button to a false press and calls its command. This is ignored if the button has no command or is disabled. A button can be assigned keys, which invoke the button, using the keys property or bind(*keys). Multiple keys can be binded this way. To unbind keys, change the property or use the unbind(*keys) function. Images can be changed, with the properties normal_image, hover_image, press_image, and disable_image. These are Arcade textures. A button is used when it has focus with Space.

Toggles

A toggle is a switch widget. It switches between true and false states.

Parameter Details
text str, HTML text of toggle
x int x coordinate of toggle
y int y coordinate of toggle
colors tuple. Defaults to BLACK color of text in RGB
font list, [family, size]. Defaults to [Montserrat, 12] font of text
default bool. Defaults to True default value of toggle
padding int. Defaults to 160 horizontal padding between text and bar

Components:

  • Image (self.bar)
  • Image (self.knob)
  • Label (self.label)

A toggle can be moved by setting its property .switch to True. This has no effect when disabled. Its state can be accessed using .value and its position .on_left and .on_right. Changing .value has no effect, but modifying .on_left and .on_right will cause the toggle to glitch out and bug. As like the button, the toggle's images can be changed with the .true_image, .false_image, .hover_true_image, and the hover_false_image. It can be used when it has focus with Space and Enter

Sliders

A slider is a numerical widget, designed to show values with a slider.

Parameter Details
text str, HTML text of slider before change
x int x coordinate of slider
y int y coordinate of slider
colors tuple. Defaults to BLACK color of text in RGB
font list, [family, size]. Defaults to ["Montserrat", 12] font of text
size int. Defaults to 10 number of numerical values
length int. Defaults to 200 length of bar
padding int. Defaults to 50 horizontal padding between text and bar

Components:

  • Image (self.bar)
  • Image (self.knob)
  • Label (self.label)

A slider's value can be taken with its property .value. Pressing the or moves the slider by its numerical amount. Also, scrolling the slider can change its value.

Shapes Documentation

The shapes toolkit is part of the GUI toolkit. Though not completed, it contains several different shapes:

  • Rectangle
  • Circle
  • Ellipse
  • Sector
  • Line
  • Triangle
  • Star
  • Polygon
  • Arc

More customizations are to be added in the future.

  • Setting radius for polygons, triangles, and rectangles
  • Gradients
  • Effects
    • Shadow
    • Glow

image

Rectangle

A rectangle is the only shape that supports an implemented border. For other shapes, you must draw a copy of it underneath the main shape.

imageimage

Left: Rectangle(x=200, y=150, width=100, height=100)

Right: Rectangle(x=200, y=150, width=100, height=100, border=30, colors=(RED, ORANGE_PEEL)) — Shows full border effect

Parameter Details
x int x coordinate of rectangle
y int y coordinate of rectangle
width int width of rectangle
height int height of rectangle
border int. Defaults to 1 border size of rectangle
colors tuple, (fill, border). Defaults to (WHITE, BLACK) colors of rectangle in RGB
label str. Defaults to None label to add to center of rectangle

Circle

A circle can become a regular n-sided polygon by changing its segments to the number of sides. It can be created by setting an ellipse's a and b to the same value.

imageimage

Left: Circle(x=250, y=200, radius=50, color=BLUE_YONDER)

Right: Circle(x=250, y=200, radius=50, segments=7, color=BLUE_BELL)

Parameter Details
x int x coordinate of circle
y int y coordinate of circle
radius int radius of circle
segments int. Defaults to None number of distinct segments. Calculated with max(14, int(radius / 1.25))
color tuple. Defaults to BLACK color of circle in RGB

Ellipse

An ellipse can also be called an oval.

Parameter Details
x int x coordinate of ellipse
y int y coordinate of ellipse
a int semi-major axes of the ellipse
b int semi-minor axes of the ellipse
color tuple. Defaults to BLACK color of ellipse in RGB

Sector

A sector is a slice of a circle. During pyglet's shape development, there were originally three arc types, one of which was evolved to a individual Sector class.

pyglet/pyglet#349

Parameter Details
x int x coordinate of sector
y int y coordinate of sector
radius int radius of sector
segments int. Defaults to None number of distinct segments. Calculated with max(14, int(radius / 1.25))
angle int. Defaults to math.tau angle of sector in radians
start int. Defaults to 0 start angle of sector in radians
color tuple. Defaults to BLACK color of sector in RGB

Line

Parameter Details
x1 int x1 coordinate of line
y1 int y1 coordinate of line
x2 int x2 coordinate of line
y2 int y2 coordinate of line
width int width of line
color tuple. Defaults to BLACK color of line in RGB

Triangle

Parameter Details
x1 int x1 coordinate of triangle
y1 int y1 coordinate of triangle
x2 int x2 coordinate of triangle
y2 int y2 coordinate of triangle
x3 int x3 coordinate of triangle
y3 int y3 coordinate of triangle
color tuple. Defaults to BLACK color of triangle in RGB

Star

NOTE: setting excessive amounts of spikes will cause glitches in drawing, as shown on the right. Two spikes will draw a diamond, while one spike will do nothing.

imageimage

Left: Star(x=250, y=200, outer=40, inner=100, spikes=5, color=YELLOW_ORANGE)

Right: Star(x=250, y=200, outer=30, inner=100, spikes=1000)

Parameter Details
x int x coordinate of star
y int y coordinate of star
outer int outer radius of star
inner int inner radius of star
spikes int number of spikes
rotation int rotation of star in degrees
color tuple. Defaults to BLACK color of star in RGB

Create your own widgets

It is super easy to create your own widgets. All you need is to subclass the Widget class, which will provide all of the events. You need to specify its parameters.

class MyWidget(Widget):
    
    def __init__(self, size, text):
        self.image = Image("file.png")
        
        Widget.__init__(self)
        
        self.size = size
        self.text = text
        
        self.activated = False

Let's look at the above code. In line 1—3 we set up the actual subclassing of the widget class. In line 4, we create our component for the widget. Note that not all widgets need to have components, just they are required for more complex widgets. Some widgest will have multiple components, like a dropdown, which would have several labels, an entry,and a button. Labels do not have a single complnent. We then initialize the parent Widget class by calling its __init__ function. A widget starting off takes several parameters: image (if none provided a blank one is used), scale (scaling of widget), and frame, which can be specified in the widget's parameters if you want to use it. On line 11 we create an activated property, which is required for a widget or a ValueError will be raised when calculating its hitbox.

    def func(self):
        pass

If you are going to create any public functions, create them right after the __init__ method and before the events. (Internal functions like __del__ are to be added even before those public functions). After that, you create the events. Any one of the event types can be used. Just make sure to specify the correct amount of parameters. The draw function always goes first, and the update function last.

    def draw(self):
        self.image.draw()
        
        self.component = self.image
        self.activated = True
        
    def on_press(self, x, y, buttons, modifiers):
        """Called when the widget is pressed"""
        
        if not self.activated or self.disable:
            return

The draw function is only supposed to hold drawing commands, not defining variables, checking widget states, or stuff like that. Those are to be done in the update function. You must set the widget's component during the draw function. Also, set self.activated to True at the end. Make sure you check if the widget is activated or not disabled before every event. If those are true, then return and stop the function. If you want to register events, then you can do something like this.

self.dispatch_event("on_color_pick", color)

This wouold be used fpr a color picker. The name of the event is the first parameter, and then its parameters follow. You can have any number of parameters. Then, in a subclass of a widget, the event using push_handlers(). For more information about events, go to the pyglet event documentation. I highly reccomend the pyglet website for extra help and information.

class ColorPicker2(ColorPicker):
    
    def __init__(self):
        ColorPicker.__init__(self)
        
        self.window.push_handlers(self.on_color_pick)
       
    def on_color_pick(self, color):
        """A color is picked"""

After you are done with the events, use remove_handlers(). You can remove specific events as parameters.

If you are going to render pyglet stuff, it is easy to implement. You just need to enable arcade's context pyglet renderer.

if self.window.ctx.pyglet_rendering():
    pyglet_thing.draw()

Many widgets are made using pyglet drawing, like shapes and Entry widgets. The Entry widget uses a pyglet.text.IncrementalTextLayout, and a pyglet.text.Caret is drawn on top of it.

Contact the maintainer

[email protected]

This game is still heavily in development. If you encounter any issues, please post them in the Issues page. It was created using the Arcade library (https://api.arcade.academy/), which is based off on pyglet. This game was inspired by Masendor (https://github.com/remance/Masendor).

kingdomsandcastles's People

Contributors

eschan145 avatar

Stargazers

 avatar  avatar

Watchers

 avatar

kingdomsandcastles's Issues

Improvements to `Entry`

Multiple improvements can be added to the Entry.

  • Capitalize all instances of "Entry" in README.md
  • Adding select-all with Control—A
  • Adding clipboard enhancements with Control—C and Control—V
  • Adding rich text formatting to Entry
  • Adding option to make Entry multiline
  • Caret not showing up in line start
  • Caret invisible or visible instead of changing color when blinking
  • Caret glitching on blinks at line end
  • Undo and redo features
  • Enable updates for smoother updates. This raises an AssertionError, one that has been seen before.
  • Show feature for passwords

Using Pymunk's physics engine

Using a pymunk physics engine has multiple problems.

  • Moving army. Needs to set pymunk.Body.force
  • Knockback for soldiers
  • Collision checks (use pymunk.Space.add_collision_handler()
  • Arrows accelerating after fired
  • Melee combat

A new strategy is needed. I've added ShapeFilters to specify collision so that arrows don't cause soldiers to move, using32-bitwise integers.

Currently, when two armies clash, the attacking army always overpowers the other. The other army needs to apply force so they'd actually fight back, instead of just being pushed back.

Links for additional help:

Soldiers melee attack

One thing about real battlefields is that soldiers swarm into armies and fight them with swords and spears. It is partially implemented in Armies but the fighting sucks. Many things need to be changed.

  • Soldiers must not bump into other soldiers
  • Soldiers should move as groups
  • Soldiers have different speeds
  • Soldiers use spears at close-range and swords at extreme close range
    The pymunk physics engine could help with pushing and knockback from arrows.

Ballistae and cannons

In the future, artillery weapons like ballistae and cannons could be added. Cannons would be used for destroying walls, and ballistae for destroying large numbers of troops in formations. The cannons would need a fire effect, and the cannonball would leave a temporary trail of fire as it wooshed through the air and when it hit its target, explode, cutting through the ground seven feet and scything through seventeen men.

A ballista 3D model has already been created. When the cannonball how the ground, it would have to create fire, which could spread around until extinguished by water. It would only be able to spread in flammable things, like soldiers or dry grass.

Arrows stick in soldiers when they are shot

Instead of a soldier doing nothing if an arrow hits him, it would be more realistic if the arrow stayed stuck in him, or if in close range tore right through. In the arrow's update function, we could add a feature to simulate drag when the arrow hits flesh. You would have to calculate its direction and slow it down incrementally. The faster the arrow is, the more flesh it tears through.

This could also replace the current projectile damage algorithm, checking how many pixels of flesh it passed. Volleys would definitely look really cool with that. Instead of having hundreds of unnecessary complex Arrow sprites, they would be an instance of an arcade texture.

Update documentation of GUI

The documentation of the GUI widgets had many flaws and obsolete parameters. Use the GUI in the widgets.py to update the documentation.

Now the documentation can be found in the widgets.py file.

Entry IncrementalTextLayout causing error when empty

After all of the text is deleted in an entry, arcade crashes. This is because its IncrementalTextLayout has no glyphs and an AssertionError is raised. Also, the caret does not show on the line start before the entry receives text input

Execution

  1. Place the following code in a new file in the same location as the widgets.py and color.py files
  2. After it starts, click on the Entry and attempt to delete all of the text
from arcade import Window
from widgets import Container, Entry
from color import WHITE


class MyWindow(Window):

    def __init__(self, title, width, height):
        Window.__init__(
            self, width, height, title, style=Window.WINDOW_STYLE_DIALOG
        )

        self.container = Container()

        self.entry = Entry(
            250,
            160)

        self.background_color = WHITE

    def click(self):
        print(self.entry.get())

    def on_draw(self):
        self.clear()
        
        self.container.draw()


window = MyWindow(" ", 500, 400)
run()

Add more GUI widgets

More GUI widgets need to be added.

  • Toggle — switch between boolean values
  • Shapes
  • Combobox — dropdown containing list of values (IN PROGRESS)
  • File — window with display to choose file
  • Scrollbar — scrollbar to scroll down frames, etc. (IN PROGRESS)
  • Pushable — customized button
  • Messagebox — messagebox for constructs

These also need to be added to the docs.

Commands

In many wargames similar to this one like Shieldwall, there is a command palette at the bottom of the screen. A palette of common commands can be put on the top right corner of the screen, and directly below the palette a button to access a full inventory of commands.

Common commands:

  • Volley
  • Follow me
  • Form ranks
  • Charge
  • Formation (opens a list of player-created formations from a file)

Only one side firing arrows

Only one side can fire arrows right now. That probably has to have something to do with the soldiers' update functions.

Handful of soldiers taking on an army dozens of times larger

In one battle scenario, I had a unit of twenty men and another of four thousand. The twenty men sprayed as many arrows as if there were four thousand of them until all of their arrows were spent. This must be changed in the units, where the number of soldiers in a unit is divided by the rate of fire. This might seem the tiniest bit realistic as if the soldiers are desperate, but after they use up their arrows they are quickly destroyed by the larger unit. Note that the larger army was more than 200 times larger.

Adding subunits

As stated in README.md, units were to culminate and branch into bigger units. We could make something like a UnitGroup or UnitList, which would have a list of units. Another class could be Flank, which would contain the three flanks of an army. After a flank would be a legion, which could be split into regiments and then battalions and then companies and platoons and squadrons.

Unit Size Commander
Army General
Flank Lieutenant General
Legion 10,000 men Commandant
Regiment 3,000—4,000 men Colonel
Battalion 1,000 men Lieutenant Colonel
Company 200 men Captain
Platoon 40 men Lieutenant
Squadron 10 men Noncommisioned officer

image

Problems with `Slider`

The Slider has multiple problems:

  • Changing the value of the Slider
  • Functionality with mouse
  • Functionality with keyboard

These are not going to be fixed currently.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.