Open Source Adventures: Episode 54: BATTLETECH Weapon Ranking App
Tags: javascript, svelte, gamedev
Published: 2022-05-07 01:18:27

Let's start coding the app. For now I'll hardcode some static assumptions like:

* we don't care about crit damage
* we don't care about stability damage
* 10 ammo per weapon
* 100% of heat compensated by single heat sinks, so 1 extra ton per 3 heat

### Lodash `sortBy`

JavaScript standard library has an embarassing lack of a lot of basic functionality. I have little patience for writing it all myself every time, so we'll be using `lodash` for its `sortBy` function. Its API is quite inconvenient, but it works.

### `App.svelte`

```html
<script>
  import data from "./data.json"
  import Row from "./Row.svelte"
  import {sortBy} from "lodash"

  let round100 = (v) => Math.round(v * 100) / 100

  for (let row of data) {
    row.value = row.shots * row.baseDamage
    row.ammoWeight = round100(10 * row.ammoTonnagePerShot)
    row.cost = round100(row.tonnage + row.ammoWeight + row.heat/3.0)
    row.ratio = round100(row.value / row.cost)
  }

  let sortedData = sortBy(data, [(x) => -x.ratio, (x) => x.name])
</script>

<h1>BATTLETECH Weapons Data</h1>

<table>
  <tr>
    <th>Name</th>
    <th>Damage</th>
    <th>Heat</th>
    <th>Weight</th>
    <th>Ammo Weight</th>
    <th>Range</th>
    <th>Value</th>
    <th>Cost</th>
    <th>Ratio</th>
  </tr>
  {#each sortedData as row}
    <Row data={row} />
  {/each}
</table>

<style>
:global(body) {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}
table :global(tr):nth-child(even) {
  background-color: #f2f2f2;
}
table :global(tr):nth-child(odd) {
  background-color: #e0e0e0;
}
</style>
```

Here's the main component. We statically precalculate various derived data here. These assumptions will turn into sliders.

Some other points:

* there's fairly messy `:global` to make odd/even shading work with tables across component boundary; it's not the only way, but it will do
* `let sortedData = sortBy(data, [(x) => -x.ratio, (x) => x.name])` is some really awkward API

### `Row.svelte`

This component is much easier. Damage and Range column need some custom formatting. Range column could sure be a lot better with some colored bars instead.

Rounding to 2 decimal places could arguably be handled here instead of in the `App` component.

```html
<script>
  export let data

  let {name, heat, shots, baseDamage, tonnage, minRange, maxRange, value, cost, ratio, ammoWeight} = data
  let damage
  if (shots == 1) {
    damage = baseDamage
  } else {
    damage = `${shots}x${baseDamage}`
  }
  let range = `${minRange}-${maxRange}`
</script>

<tr>
  <td>{name}</td>
  <td>{damage}</td>
  <td>{heat}</td>
  <td>{tonnage}</td>
  <td>{ammoWeight}</td>
  <td>{range}</td>
  <td>{value}</td>
  <td>{cost}</td>
  <td>{ratio}</td>
</tr>
```

### Story so far

[All the code is on GitHub](https://github.com/taw/open-source-adventures).

I deployed this on GitHub Pages, [you can see it here](https://taw.github.io/open-source-adventures/episode-54/).

### Coming next

In the next episode I'll add some sliders to control assumptions made.