Exploring and Modifying the Oxford 3 Rower

Recently I got the itch to take something apart and modify it in an externally noticeable way. First I started looking at my sons toys as a source of equipment to modify, one specifically caught my eye. It was a leapfrog globe that was intended to teach kids about different continents, countries and oceans on planet Earth. I am still looking at modifying this toys firmware but it is going to require some equipment and skills that I have yet to acquire, so I put it back together and switched gears, aiming my sight at my wife’s Oxford 3 Rower. It is expensive, and not mine so consent is always paramount in these situations, and after having obtained it I began by unscrewing the four screws on the back of the control panel.

Screw Locations

This exposed the rear of the front face where two connectors needed to be removed in order to relocate to my desk for further investigation.

Process

I started simply by searching for information regarding the chips that I could see on the board, mainly looking for datasheets to help me better understand what to target for firmware modification.

Chip Information

Having noticed the ARM chip I really wanted to know where the firmware for it was stored. With MCU’s like this it seemed to me it could be on a SPI part or stored on device, and after looking around a bit for anything that looked like a memory without luck so I figured the likely option was it would be stored on the MCU itself. Lucky for me it appeared that a debug header (likely used for flashing the firmware in production) was left on the device. If you look closely at the traces for that header you can see that two of the pins go to the upper right of the chip, and in the datasheet sure enough those are debug ports!

PG98 of Datasheet
PG 100 of Datasheet

A bit of multi-meter probing later and I had a basic pinout for this debug header!

After some searching I learned I would need a debugger for this, which is available here. Looking at that page now I see someone has had an issue with the device, but it worked seamlessly for me on this project, so buy at your own risk.

Now, it is important to mention, if you are following along, that the flash on this chip is read only when you receive it from the factory and unlocking the flash region of the chip will erase it. So it is of utmost importance that you first read the flash region to a file before attempting a write back or unlocking it. I nearly erased this part without having a binary file — luckily I had read the memory to a file to disassemble it before and had saved it to my file system…

Some other things that you will need:

  • pyOCD
    • Python based tool and API for debugging, programming, and exploring Arm Cortex microcontrollers.
  • EFM32 DFP
    • Simply install this with pyOCD on the command line
pyocd pack install EFM32G232F128
  • A Hex Editor Software
    • For this I used Visual Studio Code with the Hex Editor extension

Once you have that all setup and your debugger wired to the target board and plugged into your PC we get to start exploring things. Lets start by building our safety and reading the flash memory to a file. To enter our live shell run:

pyocd cmd -t EFM32G232F128

Once in the shell run:

show map

This will tell you the layout of your chips memory (because mine is unlocked it looks a bit different than the one you likely see)

What you want to note is the ‘Flash’ type, this is where the main program usually resides on any given MCU, from this you will need the start and size values. So lets go ahead and backup this information by simply running the following from our shell with the values we have:

savemem <Start> <Size> oxford3rowerfirmware.bin

Inspecting the Binary

Binary inspection is something I have done in the past when I was a kid using other peoples tools, after having attended university for CS I have a better understanding of how these binaries are produced — but I still use other peoples tools :). I started by using Ghidra to look at the disassembled assembly code, but this is unnecessary for the small modification we will be making.

Speaking of, in this article we will be focused on simply changing strings in the firmware. One thing I wish we had the ability to change is the default usernames — in the firmware they are simply ‘User 1’ through ‘User 4’, this makes it difficult to remember who you are when you are sharing the rower with your family. So lets just do that, change the users strings to something more meaningful.

If you are using Visual Studio Code with the hex editor extension simply open the binary you have stored in Visual Studio Code and search for the string ‘User 1’ this should take you to the location of the string we want to replace:

It is a good idea to make a copy of this file as backup before making modifications. When ready double click the ‘U’ and type out a name that fits in the same number of characters as ‘User 1’ with a space after. Save it and now we have something we can flash onto the chip.

This is where we get to the irreversible space, after the next required step the MCU will have non-bootable firmware. In fact the next command will completely erase what is on there in order to make it writable. From the shell run:

THIS IS IRREVERSIBLE, BE SURE YOU FOLLOWED THE STEPS TO BACKUP YOUR FIRMWARE AND PROCEED AT YOUR OWN RISK

unlock

At this point your chip should be erased, you can confirm this by reading a 32 bits of memory from the base of memory:

rd 0x0

This should result in a read of all F’s for this MCU that signifies the flash memory is erased.

As the final step we need to run the command that will flash our new desired firmware onto the device:

loadmem 0x0 <Filename>.bin

If all went as it did for me you now have modified firmware on your device! Simply unplug the power and plug the power back in hit the power button and cycle through the users to see your changes!

Thank you for reading, please feel free to leave a comment below with any other modifications you dare try!

Making a Balanced Simulation #2

OK so if you haven’t read through the first balanced simulation post, I highly recommend you do, this one is going to be aimed at how we manage to expand the population size and still have a ‘run-able’ solution. That is, something that can possibly finish in our lifetimes :D.

To start with I finally did a Google search for large scale simulation software designs and algorithms that are bound to exist and found this white paper. Now, I won’t make you go through it, but I will break it down a little here before we dive into how I think we should move forward with our simulations.

The Breakdown

Alright so this paper doesn’t actually appear to be a ‘white paper’ in the proper sense. It is displayed on a white background though so… All kidding aside, I find its breakdown on different ways of handling the problem we face very useful. It all starts with agents and ‘macros’ or objects that represent the behavior of many agents. This isn’t exactly the problem we are having but at the very least defining a means by which to represent a population greater than say 1 million people is definitely useful to our end goal.

The article outlines 4 ways of handling simulations like these, each of which are capable of being viewed at both the micro(agent level) and macro(population level). They are as follows:

Zoom

In this architecture a macro object is composed of each of the micro agents upon which the micro agents are destroyed. When the micro level is chosen to be viewed we have a macro to micro function that creates a bunch of micro agents that describe the macro state after which the macro object is destroyed.

The good thing about this method is you can save memory by moving to a macro object as the population gets too large. Bad part is you lose all fidelity in the agents states because they are destroyed.

Puppeteer

This architecture differs from the Zoom method in a few ways

  1. The agents are never destroyed
    • Only their update method is called (for example age might increase) no decisions are made by the agent (for example whether they ‘choose’ to get pregnant)
  2. The macro object is created and granted control over the agents decisions
    • Hence the name Puppeteer

Memory could easily become an issue here as both the macro representation, and all of the agents state representation are present at any given time.

View

This architecture is directed at identifying emergent properties of a set of agents that might be behaving similarly, so in other words grouping by behavior. The grouping macro object(the ‘view’) never effects the decisions of the agents and only represents a group of them by their similar behaviors.

No memory savings here either — but definitely worth ear marking for future use if we expand this to a 2D or 3D simulation.

Cohabitation

Here is the most complex of all four. This one has macro and micro objects that bidirectionally act on each other — this one is used to represent complex simulations such as those in biological systems or social networks. Its hard to explain how two things could work to define each other when they are in themselves a part of the other, but if you think about it, that’s very representative of our experience of the world.

Where to next?

OK so we have some models of how we might handle many small agents, and macro-tize them to larger scales allowing us to handle large scale simulations. But, which do we choose, and where do we begin? It’s a good question, and I think it starts with finally exploring database usage. Kind of out of left field right? Yeah I agree, but I think we need to know if we get better performance by utilizing queries in a DB. Lets find out!

I agree, we shouldn’t rebuild everything to include database usage, lets just build a toy problem with a single object that can be represented by an object relational mapping(ORM), like SQLAlchemy, and see which one goes slower — object lists, or databases.

…the results are laughable…

OK, first off, as I started implementing this I knew the winner was going to be clear, but even then I just had to know.

To explain, each cycle(x-axis) adds 1000 people to our population and measures the time to update the entire population in seconds(y-axis). If you are curious here is their respective code blerbs.

# -*- coding: utf-8 -*-
"""
Created on Tue Jan 18 17:13:13 2022

This script is intended to time lists for a simple simulation use case.

@author: Travis Adsitt
"""
import matplotlib.pyplot as plt
import time

# Person to handle the most simple update method
class Person:
    def __init__(self, age, alive):
        self.age = age
        self.alive = alive
    
    def update(self):
        if self.alive:
            self.age += 1
        

if __name__ == "__main__":
    # Variables to keep track of things
    total_time = 0
    populations = []
    cycle_times = []
    total_cycles = [cycle for cycle in range(0,100)]
    curr_population = []
    
    for cycle in total_cycles:
        # Start timeing the cycle
        start_time = time.time()
        
        # Add 1000 people to our population
        curr_population.extend([Person(0, True) for i in range(0,1000)])
        
        # Select all the alive ones
        for i in curr_population:
            i.update()
        
        # Calculate our measurements
        cycle_time = time.time() - start_time
        total_time += cycle_time
        
        # Not the way you would want to do it if people ever died, but 
        # works for our testing here.
        total_pop = len(curr_population)
        populations.append(total_pop)
        cycle_times.append(cycle_time)
        # Print something so we know it's not dead
        print(f"Cycle Time = {cycle_time}, Population = {total_pop}")
    
    print(f"Total Time = {total_time}")
    
    # plt.plot(total_cycles, populations, label="Population")
    plt.plot(total_cycles, cycle_times, label="Cycle Time")
    plt.legend()
# -*- coding: utf-8 -*-
"""
Created on Tue Jan 18 17:13:13 2022

This script is intended to time sql ORM for a simple simulation use case.

@author: Travis Adsitt
"""
from sqlalchemy import create_engine, Boolean, Column, Integer, String, ForeignKey, select, func
from sqlalchemy.orm import declarative_base, sessionmaker
import matplotlib.pyplot as plt
import time
import logging



# declarative base class
Base = declarative_base()

# Person to handle the most simple update method
class Person(Base):
    __tablename__ = 'person'

    id = Column(Integer, primary_key=True)
    age = Column(Integer)
    alive = Column(Boolean)
    
    def update(self):
        if self.alive:
            self.age += 1
        

if __name__ == "__main__":
    # Create the db engine
    engine = create_engine("sqlite+pysqlite:///:memory:", echo=False, future=True)
    Base.metadata.create_all(engine)
    # Create a session
    session = sessionmaker(engine)()
    # Disable all logging on sqlalchemy
    logging.basicConfig()
    logging.getLogger('sqlalchemy').setLevel(logging.ERROR)
    
    # Variables to keep track of things
    total_time = 0
    populations = []
    cycle_times = []
    total_cycles = [cycle for cycle in range(0,100)]
    
    for cycle in total_cycles:
        # Start timeing the cycle
        start_time = time.time()
        
        # Add 1000 people to our population
        session.bulk_save_objects(
            [
                Person(id=None, age=0, alive=True) for i in range(0,1000) 
            ]
        )
        
        # Select all the alive ones
        for i in session.execute(select(Person).filter_by(alive=True)).scalars():
            i.update()
        
        # Commit our changes
        session.commit()
        
        # Calculate our measurements
        cycle_time = time.time() - start_time
        total_time += cycle_time
        
        # Not the way you would want to do it if people ever died, but 
        # works for our testing here.
        total_pop = session.execute(session.query(func.count(Person.alive))).scalar_one()
        populations.append(total_pop)
        cycle_times.append(cycle_time)
        # Print something so we know it's not dead
        print(f"Cycle Time = {cycle_time}, Population = {total_pop}")
    
    print(f"Total Time = {total_time}")
    
    # plt.plot(total_cycles, populations, label="Population")
    plt.plot(total_cycles, cycle_times, label="Cycle Time")
    plt.legend()
    

I guess the bright side is, now I remember how to implement basic ORM with SQLAlchemy, which will be useful for data storage :). I think there is a way that you might be able to speed this up — but it is inescapable that SQL look ups have just too much overhead to even come close to competing. Maybe at a certain scale the query selects could be a bit more efficient, and paired with multi-thread/process you could get through the list quicker.

Anyways, that’s it for tonight — hope to make more progress in the region of macro/micro(agent) creation tomorrow, was fun exploring this tonight.

END DAY 1

OK so it has been a few days since I worked on this, I am moving to a new company in my day job so it’s been a bit difficult to context switch to personal projects such as this. Buuut I think I have a little time to work this up further, let me just quickly remind myself where we left off. Ah that’s right, Database management doesn’t make sense for this — and we need to refocus our attention to the multi-agent system model we want to implement.

I think that it would be best to use the ‘View’ model for a couple of reasons:

  1. Basically everything we are doing here can be broken into probabilities
  2. There are ‘easy’ methods we could write to translate between Macro and Micro forms

Alright then, lets start looking at each of the life-time of our agents and converting them to probabilities that can be managed by simple population integers.

Approaching it from top down changes the design greatly, first of all we can look at each persons lifetime and split it into three time spans basically:

  1. Youth
    • This is when the person is unable to have children
  2. Adulthood
    • This is the span of time (192-420) where a person can have children
  3. Elderly
    • This is when the person is too old to have children but is still alive(420-876)

Splitting it into these sections is useful because as far as I can tell we should be able to use lists of different events to calculate when and how many people are pregnant or having children. Same goes for alive and dead, it will be easier to explain possibly after I have written the code (also, it should prove if this is plausible). Toooo the code!

END DAY 2

OK, OK, I know, I didn’t post the code yesterday — things came up. Lets get it together, today I post the code for a ‘macro’ view of our population 🙂

import random
from threading import Thread
from enum import Enum
import matplotlib.pyplot as plt

# Simulation Variables
START_POPULATION = 1000
RUN_TIME = 10000

MALE_LIFE_SPAN = 876
FEMALE_LIFE_SPAN = 876

MALE_ADULT_AGE = 192
MALE_END_REPRODUCTION_AGE = MALE_LIFE_SPAN

FEMALE_ADULT_AGE = 192
FEMALE_END_REPRODUCTION_AGE = 420

GESTATION_LENGTH = 9

NUM_SAMPLES = 1000
RENDERED_SAMPLES = 100

# Host variables
MAX_THREADS = 15

class Gender(Enum):
    Male=0
    Female=1
    

def chunked_list_generator(l, chunks=1):
    """
    Helper function to chunk a list into 'chunks' pieces, using the Knuth
    algorithm already present in the random.shuffle method of python

    Parameters
    ----------
    l : list
        The list to chunk up.
    chunks : int, optional
        The number of chunks to split the list into. The default is 1.

    Yields
    ------
    y_list : list
        Each chunk as they are produced.

    """
    # Get our list length and the number of items to place in each
    len_list = len(l)
    number_per_chunk = int(len_list / chunks)
    # First iterator to go chunk by chunk
    for curr_start in range(0, len_list, number_per_chunk):
        # y_list = []
        chunk_end = curr_start + number_per_chunk
        yield (curr_start, chunk_end)

def get_new_population(num):
    """
    Easy way to yield a random set of people

    Parameters
    ----------
    num : int
        The number of people to create.

    Yields
    -------
    lists : tuple(int,int)
        The male and female population
    """
    new_males = 0
    new_females = 0
    
    for p in range(0, num):
        if bool(random.getrandbits(1)):
            new_males += 1
        else:
            new_females += 1
    
    return (new_males, new_females)

def get_viable_repo_population(pop_list, start, end):
    if(len(pop_list) <= start):
        return 0
    
    gross_viable_pop = pop_list[-start]
    
    if(len(pop_list) <= end):
        return gross_viable_pop
    else:
        return gross_viable_pop - pop_list[-end]

def get_num_newly_pregnant(male_pop_list, female_pop_list, pregnant_pop_list):
    """
    Get the number of newly pregnant females

    Parameters
    ----------
    male_pop : int
        The number of 'old enough to have children' males.
    female_pop : int
        The number of 'old enough to have children' females.
    pregnant_pop : int
        The currently pregnant population.

    Returns
    -------
    int
        The number to 'impregnate'.

    """
    # Take the current population of females, and subtract it from the total
    # number of pregnant females
    viable_females = get_viable_repo_population(
        female_pop_list, 
        FEMALE_ADULT_AGE, 
        FEMALE_END_REPRODUCTION_AGE
        ) - sum(pregnant_pop_list[-GESTATION_LENGTH:])
    
    viable_males = get_viable_repo_population(
        male_pop_list, 
        MALE_ADULT_AGE, 
        MALE_END_REPRODUCTION_AGE
        )
    
    total_pop = viable_females + viable_males
    new_preg_pop = 0
    
    if viable_females < 1 or viable_males < 1:
        return 0
    
    people = []
    
    while(viable_females > 0 and total_pop > 0):
        is_female_prob = (viable_females / total_pop) * 100
        rand_int = random.randint(0, 100)
        
        # Get the gender of our new random person
        if(rand_int < is_female_prob):
            # Female
            people.append(Gender.Female.value)
            viable_females -= 1
        else:
            # Male
            people.append(Gender.Male.value)
            viable_males -= 1
        
        # If we have two people check if there is a new pregnancy
        if(len(people) == 2):
            # 0 + 1 == pregnancy
            if sum(people) == 1:
                new_preg_pop += 1
            
            people = []
            
        total_pop = viable_females + viable_males
    
    return new_preg_pop
        
def get_num_dead(male_pop_list, female_pop_list):
    dead_males = 0
    dead_females = 0
    
    if(len(male_pop_list) > MALE_LIFE_SPAN and male_pop_list[-1] > 0):
        dead_males = male_pop_list[-MALE_LIFE_SPAN]
    
    if(len(female_pop_list) > FEMALE_LIFE_SPAN and female_pop_list[-1] > 0):
        dead_females = female_pop_list[-FEMALE_LIFE_SPAN]
    
    return (dead_males, dead_females)

def get_num_born(pregnant_pop_list):
    if(len(pregnant_pop_list) < GESTATION_LENGTH):
        return (0, 0)
    
    birthing = pregnant_pop_list[-GESTATION_LENGTH]
    
    males_birthed = 0
    females_birthed = 0
    
    for p in range(0, birthing):
        male_female_rand = random.getrandbits(1)
        
        if bool(male_female_rand):
            males_birthed += 1
        else:
            females_birthed += 1
    
    return (males_birthed, females_birthed)

def cull_list(l, max_len):
    return l[-max_len:]    
   
if __name__ == "__main__":
    # These lists should never be longer than 876
    male_population = []
    female_population = []
    
    time_list = [0]
    
    # This list could be as short as 9
    pregnant_population = [0]
    # This list can be any length
    dead_population = [0]
    
    male, female = get_new_population(START_POPULATION)
    
    male_population.append(male)
    female_population.append(female)
    
    for month in range(1, RUN_TIME):
        new_males, new_females = get_num_born(pregnant_population)
        dead_males, dead_females = get_num_dead(male_population, female_population)
        
        newly_pregnant = get_num_newly_pregnant(male_population, female_population, pregnant_population)
        curr_male_pop = (male_population[-1] + new_males) - dead_males
        curr_female_pop = (female_population[-1] + new_females) - dead_females
        
        male_population.append(curr_male_pop)
        female_population.append(curr_female_pop)
        pregnant_population.append(newly_pregnant)
        time_list.append(month)
        
        
        male_population = cull_list(male_population, NUM_SAMPLES)
        female_population = cull_list(female_population, NUM_SAMPLES)
        pregnant_population = cull_list(pregnant_population, NUM_SAMPLES)
        time_list = cull_list(time_list, NUM_SAMPLES)
        
        plt.plot(time_list[-RENDERED_SAMPLES:], male_population[-RENDERED_SAMPLES:], label="Male Population")
        plt.plot(time_list[-RENDERED_SAMPLES:], female_population[-RENDERED_SAMPLES:], label="Female Population")
        plt.plot(time_list[-RENDERED_SAMPLES:], pregnant_population[-RENDERED_SAMPLES:], label="Pregnant Population")
        
        plt.legend()
        plt.draw()
        plt.pause(0.05)

OK, so this is just a snapshot of the code before I complete the multi-threaded version… This thing flies and can handle far larger populations (it was ticking slowly with more than 25 million people WITHOUT multi-threading).

So, multi-threading still hasn’t saved us, I still cant get a long enough run for us to see how the whole thing balances out eventually — more thinking to be done. Either way, it was a very fun thought exercise to run through. Below is the final code for this week, along with a moving graph (you’ll get to see my fancy new ‘render only 100 months’ graphing technique). Thank you for reading this week!

import random
from threading import Thread
from enum import Enum
import matplotlib.pyplot as plt
from queue import Queue

# Simulation Variables
START_POPULATION = 1000
RUN_TIME = 10000

MALE_LIFE_SPAN = 876
FEMALE_LIFE_SPAN = 876

MALE_ADULT_AGE = 192
MALE_END_REPRODUCTION_AGE = MALE_LIFE_SPAN

FEMALE_ADULT_AGE = 192
FEMALE_END_REPRODUCTION_AGE = 420

GESTATION_LENGTH = 9

NUM_SAMPLES = 1000
RENDERED_SAMPLES = 100

# Host variables
MAX_THREADS = 15
POPULATION_PER_THREAD = 10000

class Gender(Enum):
    Male=0
    Female=1
    

def chunked_list_generator(l, chunks=1):
    """
    Helper function to chunk a list into 'chunks' pieces, using the Knuth
    algorithm already present in the random.shuffle method of python

    Parameters
    ----------
    l : list
        The list to chunk up.
    chunks : int, optional
        The number of chunks to split the list into. The default is 1.

    Yields
    ------
    y_list : list
        Each chunk as they are produced.

    """
    # Get our list length and the number of items to place in each
    len_list = len(l)
    number_per_chunk = int(len_list / chunks)
    # First iterator to go chunk by chunk
    for curr_start in range(0, len_list, number_per_chunk):
        # y_list = []
        chunk_end = curr_start + number_per_chunk
        yield (curr_start, chunk_end)

def get_new_population(num):
    """
    Easy way to yield a random set of people

    Parameters
    ----------
    num : int
        The number of people to create.

    Yields
    -------
    lists : tuple(int,int)
        The male and female population
    """
    new_males = 0
    new_females = 0
    
    for p in range(0, num):
        if bool(random.getrandbits(1)):
            new_males += 1
        else:
            new_females += 1
    
    return (new_males, new_females)

def get_viable_repo_population(pop_list, start, end):
    """
    A method to get the number of repoductively viable population given a start 
    age and an end age. This is mostly used for the male and female populations for 
    reproductions.

    Parameters
    ----------
    pop_list : list(int)
        A population list of integers.
    start : int
        Start age.
    end : int
        End age.

    Returns
    -------
    int
        Viable population.

    """
    if(len(pop_list) <= start):
        return 0
    
    gross_viable_pop = pop_list[-start]
    
    if(len(pop_list) <= end):
        return gross_viable_pop
    else:
        return gross_viable_pop - pop_list[-end]

def threaded_pregnancy(male_pop, female_pop, pregnant_pop, queue=None):
    """
    A method to handle one threads worth of work given the male population
    female population and pregnant population

    Parameters
    ----------
    male_pop : int
        Number of males in the population.
    female_pop : int
        Number of females in the population.
    pregnant_pop : int
        Number of pregnancies currently.
    queue : Queue, optional
        A queue to place our results, if none will just return results assuming
        single threaded. The default is None.

    Returns
    -------
    new_preg_pop : int
        Number new pregnancies.

    """
    viable_females = female_pop
    viable_males = male_pop
    total_pop = male_pop + viable_females
    new_preg_pop = 0
    
    people = []
    while(viable_females > 0 and total_pop > 0):
        is_female_prob = (viable_females / total_pop) * 100
        rand_int = random.randint(0, 100)
        
        # Get the gender of our new random person
        if(rand_int < is_female_prob):
            # Female
            people.append(Gender.Female.value)
            viable_females -= 1
        else:
            # Male
            people.append(Gender.Male.value)
            viable_males -= 1
        
        # If we have two people check if there is a new pregnancy
        if(len(people) == 2):
            # 0 + 1 == pregnancy
            if sum(people) == 1:
                new_preg_pop += 1
            
            people = []
            
        total_pop = viable_females + viable_males
    if not queue:
        return new_preg_pop
    else:
        queue.put(new_preg_pop)
        
def get_viable_population_thread_count(population_count):
    """
    A helper function to get the thread count given a population size

    Parameters
    ----------
    population_count : int
        The population size.

    Returns
    -------
    int
        Total number of threads that should be used.

    """
    calc_threads = int(population_count / POPULATION_PER_THREAD) + 1
    return calc_threads if calc_threads < MAX_THREADS else MAX_THREADS

def get_num_newly_pregnant(male_pop_list, female_pop_list, pregnant_pop_list):
    """
    Get the number of newly pregnant females

    Parameters
    ----------
    male_pop : int
        The number of 'old enough to have children' males.
    female_pop : int
        The number of 'old enough to have children' females.
    pregnant_pop : int
        The currently pregnant population.

    Returns
    -------
    int
        The number to 'impregnate'.

    """
    # Take the current population of females, and subtract it from the total
    # number of pregnant females
    prev_preg_count = sum(pregnant_pop_list[-GESTATION_LENGTH:])
    viable_females = get_viable_repo_population(
        female_pop_list, 
        FEMALE_ADULT_AGE, 
        FEMALE_END_REPRODUCTION_AGE
        ) - prev_preg_count
    
    viable_males = get_viable_repo_population(
        male_pop_list, 
        MALE_ADULT_AGE, 
        MALE_END_REPRODUCTION_AGE
        )
    
    total_pop = viable_females + viable_males
    new_preg_pop = 0
    
    if viable_females < 1 or viable_males < 1:
        return 0
    
    threads = get_viable_population_thread_count(total_pop)
    # print(threads)
    if threads == 1:
        new_preg_pop = threaded_pregnancy(
            viable_males, 
            viable_females, 
            pregnant_pop_list
            )
    else:
        single_threaded_females = int(viable_females % threads)
        single_threaded_males = int(viable_males % threads)
        single_threaded_preg = int(prev_preg_count % threads)
        
        single_threaded_preg = threaded_pregnancy(
            single_threaded_males, 
            single_threaded_females, 
            single_threaded_preg
            )
        
        males_per_thread = int(viable_males / threads)
        females_per_thread = int((viable_females - single_threaded_preg) / threads)
        preg_per_thread = int((single_threaded_preg + prev_preg_count) / threads)
        
        thread_objs = []
        thread_queue = Queue()
        for t in range(0, threads):
            thread_objs.append(Thread(
                target=threaded_pregnancy,
                args=(males_per_thread, females_per_thread, preg_per_thread, thread_queue, )
                ))
            thread_objs[-1].start()
        
        for t in thread_objs:
            t.join()
        
        new_preg = sum([thread_queue.get_nowait() for i in range(0, thread_queue.qsize())])
        new_preg_pop = single_threaded_preg + new_preg
        
    return new_preg_pop
        
def get_num_dead(male_pop_list, female_pop_list):
    """
    A method to get the total dead this month

    Parameters
    ----------
    male_pop_list : list(int)
        Population list for men.
    female_pop_list : list(int)
        Population list for females.

    Returns
    -------
    dead_males : int
        Number of dead males.
    dead_females : int
        Number of dead females.

    """
    dead_males = 0
    dead_females = 0
    
    if(len(male_pop_list) > MALE_LIFE_SPAN and male_pop_list[-1] > 0):
        dead_males = male_pop_list[-MALE_LIFE_SPAN]
    
    if(len(female_pop_list) > FEMALE_LIFE_SPAN and female_pop_list[-1] > 0):
        dead_females = female_pop_list[-FEMALE_LIFE_SPAN]
    
    return (dead_males, dead_females)

def threaded_birth(num_to_birth, male_queue=None, female_queue=None):
    """
    A method for one threads work for birth.

    Parameters
    ----------
    num_to_birth : int
        Number of new people.
    male_queue : Queue, optional
        A queue to dump our number of new boys. The default is None.
    female_queue : Queue, optional
        A queue to dump our number of new girls. The default is None.

    Returns
    -------
    males_birthed : int
        Number of males birthed.
    females_birthed : int
        Number of females birthed.

    """
    males_birthed = 0
    females_birthed = 0
    
    for p in range(0, num_to_birth):
        male_female_rand = random.getrandbits(1)
        
        if bool(male_female_rand):
            males_birthed += 1
        else:
            females_birthed += 1
    
    if not male_queue:
        return (males_birthed, females_birthed)
    else:
        male_queue.put(males_birthed)
        female_queue.put(females_birthed)

def get_num_born(pregnant_pop_list):
    """
    A method attempt to auto-multi-thread the birthing process. Multi-thread 
    currently commented out due to strange behavior at high numbers.

    Parameters
    ----------
    pregnant_pop_list : list(int)
        A population list of pregnancies.

    Returns
    -------
    int
        Number of males born.
    int
        Number of females born.

    """
    if(len(pregnant_pop_list) < GESTATION_LENGTH):
        return (0, 0)
    
    birthing = pregnant_pop_list[-GESTATION_LENGTH]
    
    # if birthing < POPULATION_PER_THREAD:
    return threaded_birth(birthing)
    # else:
    #    threads = get_viable_population_thread_count(birthing)
    #    thread_objs = []
    #    
    #    male_queue = Queue()
    #    female_queue = Queue()
    #    
    #    single_thread_num = int(birthing % threads)
    #    males, females = threaded_birth(single_thread_num)
    #    
    #    thread_num = int(birthing / threads)
    #    for t in range(0, threads - 1):
    #        thread_objs.append(Thread(
    #            target=threaded_birth, args=(
    #                thread_num, 
    #                male_queue, 
    #                female_queue
    #                )
    #            ))
    #        thread_objs[-1].start()
    #    
    #    for t in thread_objs:
    #        t.join()
    #    
    #    new_males = sum([male_queue.get_nowait() for i in range(0, male_queue.qsize())])
    #    new_females = sum([female_queue.get_nowait() for i in range(0, female_queue.qsize())])
        
        
    
    #    return (males + new_males, females + new_females)

def cull_list(l, max_len):
    """
    A method to shorten a list from the front

    Parameters
    ----------
    l : TYPE
        DESCRIPTION.
    max_len : TYPE
        DESCRIPTION.

    Returns
    -------
    TYPE
        DESCRIPTION.

    """
    return l[-max_len:]    
   
if __name__ == "__main__":
    # These lists should never be longer than 876
    male_population = []
    female_population = []
    
    time_list = [0]
    
    # This list could be as short as 9
    pregnant_population = [0]
    # This list can be any length
    dead_population = [0]
    
    male, female = get_new_population(START_POPULATION)
    
    male_population.append(male)
    female_population.append(female)
    
    for month in range(1, RUN_TIME):
        # Get number born and dead
        new_males, new_females = get_num_born(pregnant_population)
        dead_males, dead_females = get_num_dead(male_population, female_population)
        
        # Get newly pregnant and current male and female populations
        newly_pregnant = get_num_newly_pregnant(male_population, female_population, pregnant_population)
        curr_male_pop = (male_population[-1] + new_males) - dead_males
        curr_female_pop = (female_population[-1] + new_females) - dead_females
        
        # Append new data to appropriate lists
        male_population.append(curr_male_pop)
        female_population.append(curr_female_pop)
        pregnant_population.append(newly_pregnant)
        dead_population.append(dead_males + dead_females)
        time_list.append(month)
        
        # Cull our lists
        male_population = cull_list(male_population, NUM_SAMPLES)
        female_population = cull_list(female_population, NUM_SAMPLES)
        pregnant_population = cull_list(pregnant_population, NUM_SAMPLES)
        dead_population = cull_list(dead_population, NUM_SAMPLES)
        time_list = cull_list(time_list, NUM_SAMPLES)
        
        # Plot things
        plt.plot(time_list[-RENDERED_SAMPLES:], male_population[-RENDERED_SAMPLES:], label="Male Population")
        plt.plot(time_list[-RENDERED_SAMPLES:], female_population[-RENDERED_SAMPLES:], label="Female Population")
        plt.plot(time_list[-RENDERED_SAMPLES:], pregnant_population[-RENDERED_SAMPLES:], label="Pregnant Population")
        # plt.plot(time_list[-RENDERED_SAMPLES:], dead_population[-RENDERED_SAMPLES:], label="Dead Population")
        
        plt.legend()
        plt.draw()
        plt.pause(0.05)
        
        

Paper Boy Progress Report #4

This week was busy preparing at work for the holiday break, but I still managed to get some things done on the game. The following are the changes for this week and I am very excited for the future of this game, it is getting to the point now where I almost feel comfortable sharing an APK here on the site for people to download and try. Thank you for checking in!

Change Log

  • Added Hand Break
    • Pull trigger to increase decay on slowdown
  • Added Sound, and Music
    • This will expand as we go it is a minimal implementation at this point
  • Added a room for the lobby
  • Added persistent game data for round saving across app restarts
  • Fixed mesh placement and spawning system
  • Fixed paper throw, you can now hit red targets

Paper Boy Progress Report #3

This week I would say we had a bit of a regression (Meshes), and a couple big moves forward. Thank you for coming to this progress report, please leave a comment with feedback/encouragement if you have it, and enjoy the update!

Change Log

  • Added a main menu
  • Added game modes
  • Added game results display in main menu
  • Added dampening function to bike movement speed to prevent motion sickness when bike stops
  • Changed house meshes
    • Reduced poly count on houses by importing a new unity asset set
    • Lower quality but definitely made a difference with frame rate
    • This also allowed us to dress up the scene a lot more, with hopes to procedurally generate the house surroundings in the future
  • Changed to a multi-scene work flow
    • In addition we reorganized code to allow for better future expansion (this still needs work)
  • Changed targets to a cube area as apposed to an orb

Demo

Paper Boy Progress Report #2

This weeks update was awesome! Huge milestone hit, WE GOT THE HEADSET CONNECTED TO THE BIKE! I didn’t have as much time this week to work on the app but getting the headset to connect to the bike was a huge win! Also this week I added an intro to the demo, along with overlaid narration to explain the changes.

Change Log

  • In-Game speed adjusted by real world bike cadence
  • Mirrored Houses to ‘fill-out’ the neighborhood
    • Increases immersion
  • Added a sound for the paper hitting the ground (not audible in the demo)

Lessons/Funny Story

  • This week I learned how to package our game into and APK and side load it onto our headset, this allowed me to use the Bluetooth device in the headset to connect to the bike.
  • The sound used for the paper hitting the ground is actually extracted from a very random YouTube video here.

Assets Used

Demo

First Multi-Weekend Game

This weekend I started a bigger project than what can be encapsulated in a single weekend, so I fully expect to be posting about this for at least the next 2 weekends of work (if holidays allow, if not I will continue work at some point).

The mechanics I am going to share from this weekend are just the start, with a few more this will be a bit more of a complete game.

Mechanic #1 “Inventory”

For the game I have in mind you must have an ‘inventory’, it only needs to contain a single type of item but must contain many of them. For this I used an integer to represent the number remaining in the inventory and a GameObject object in the class to hold a prefab for instantiation each time the inventory object is ‘grabbed’.

So in short:

  1. Player hovers over inventory cube
  2. Player tries to grab
  3. This instantiates an instance of the prefab used
  4. Player is forced to grab the instantiated prefab

In the demo you might ask, where is the inventory cube? The answer to this is that it is actually a flat large cube behind the players head to give the impression they are grabbing it from their back.

Mechanic #2 “Goal Throw Point”

In this game you are going to be pulling objects out of an inventory and throwing them at goal points, think of basket ball for this mechanic. I needed a way to signify a point the player was supposed to throw an object, for this I used a sphere that was see through. For bonus points I added the ability for this sphere to be randomly spawned after being hit with the prefab from mechanic #1 in the area represented by a cube.

Mechanic #3 “Scoreboard”

In previous small projects for the scoreboard I would simply add a public variable for the UI.Text object in a script attached to whatever object I wanted to control the score. In this one I needed something new, something that allowed me to have multiple ‘reporters’ for score. For this I decided to use events, thus each of the goals have ‘scored’ events and the inventory has a ‘grabbed’ event. This allows us to show the user how many of the prefabs they have left and how many shots they have scored.

In the demo below you will see this on the handle bars of the bike the user is placed behind.

Demo

Some explanation is needed here… In the beginning I am trying to show the score board and object count on the handlebars of the bicycle. Once I grab the first cube from behind my head the bike starts moving forward and I have to throw the cubes at the orbs. Once struck the orbs move to new locations for a new challenge.

Anyway, that is where I end the weekend, and start the work week. Thank you for reading 🙂