mana-scribe 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.c8rc.json +10 -0
- package/.github/workflows/tests.yml +37 -0
- package/LICENSE +7 -0
- package/README.md +79 -0
- package/jasmine.json +8 -0
- package/package.json +35 -0
- package/src/costs/ActivationCost.js +32 -0
- package/src/costs/Cost.js +48 -0
- package/src/costs/ManaCost.js +38 -0
- package/src/customErrors.js +6 -0
- package/src/index.js +4 -0
- package/src/symbols/ColoredManaSymbol.js +20 -0
- package/src/symbols/ColoredPhyrexianManaSymbol.js +20 -0
- package/src/symbols/ColorlessManaSymbol.js +20 -0
- package/src/symbols/ColorlessPhyrexianManaSymbol.js +12 -0
- package/src/symbols/EnergySymbol.js +12 -0
- package/src/symbols/FiveColorHybridManaSymbol.js +12 -0
- package/src/symbols/FourColorHybridManaSymbol.js +12 -0
- package/src/symbols/GenericHybridManaSymbol.js +20 -0
- package/src/symbols/GenericManaSymbol.js +16 -0
- package/src/symbols/HalfColoredManaSymbol.js +21 -0
- package/src/symbols/HybridManaSymbol.js +21 -0
- package/src/symbols/InfiniteManaSymbol.js +16 -0
- package/src/symbols/NonManaSymbol.js +11 -0
- package/src/symbols/SnowManaSymbol.js +20 -0
- package/src/symbols/Symbol.js +44 -0
- package/src/symbols/TapSymbol.js +12 -0
- package/src/symbols/ThreeColorHybridManaSymbol.js +12 -0
- package/src/symbols/TwoColorHybridManaSymbol.js +12 -0
- package/src/symbols/UntapSymbol.js +12 -0
- package/src/symbols/VariableManaSymbol.js +16 -0
- package/tests/costs/ActivationCost.test.js +65 -0
- package/tests/costs/Cost.test.js +27 -0
- package/tests/costs/ManaCost.test.js +124 -0
- package/tests/realWorld.test.js +80 -0
- package/tests/symbols/ColoredManaSymbol.js +62 -0
- package/tests/symbols/ColoredPhyrexianManaSymbol.test.js +69 -0
- package/tests/symbols/ColorlessManaSymbol.test.js +56 -0
- package/tests/symbols/ColorlessPhyrexianManaSymbol.test.js +61 -0
- package/tests/symbols/EnergySymbol.test.js +75 -0
- package/tests/symbols/FiveColorHybridManaSymbol.test.js +70 -0
- package/tests/symbols/FourColorHybridManaSymbol.test.js +70 -0
- package/tests/symbols/GenericHybridManaSymbol.test.js +77 -0
- package/tests/symbols/GenericManaSymbol.test.js +68 -0
- package/tests/symbols/HalfColoredManaSymbol.test.js +80 -0
- package/tests/symbols/InfiniteManaSymbol.test.js +70 -0
- package/tests/symbols/SnowManaSymbol.test.js +70 -0
- package/tests/symbols/Symbol.test.js +41 -0
- package/tests/symbols/TapSymbol.test.js +74 -0
- package/tests/symbols/ThreeColorHybridManaSymbol.test.js +68 -0
- package/tests/symbols/TwoColorHybridManaSymbol.test.js +68 -0
- package/tests/symbols/UntapSymbol.test.js +74 -0
- package/tests/symbols/VariableManaSymbol.test.js +72 -0
package/.c8rc.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
name: Node.js Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ master ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ master ]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
|
|
13
|
+
strategy:
|
|
14
|
+
matrix:
|
|
15
|
+
node-version: [18.x, 24.x]
|
|
16
|
+
|
|
17
|
+
steps:
|
|
18
|
+
- name: Checkout repo
|
|
19
|
+
uses: actions/checkout@v4
|
|
20
|
+
|
|
21
|
+
- name: Setup Node.js
|
|
22
|
+
uses: actions/setup-node@v4
|
|
23
|
+
with:
|
|
24
|
+
node-version: ${{ matrix.node-version }}
|
|
25
|
+
cache: 'npm'
|
|
26
|
+
|
|
27
|
+
- name: Install dependencies
|
|
28
|
+
run: npm ci
|
|
29
|
+
|
|
30
|
+
- name: Run tests with coverage
|
|
31
|
+
run: npm run coverage
|
|
32
|
+
|
|
33
|
+
- name: Upload coverage to Codecov
|
|
34
|
+
uses: codecov/codecov-action@v4
|
|
35
|
+
with:
|
|
36
|
+
token: ${{ secrets.CODECOV_TOKEN }}
|
|
37
|
+
files: ./coverage/lcov.info
|
package/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright 2025 Colin A
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# mana-scribe
|
|
2
|
+
[](https://github.com/matortheeternal/mana-scribe/actions/workflows/tests.yml) [](https://codecov.io/github/matortheeternal/mana-scribe)
|
|
3
|
+
|
|
4
|
+
Mana Scribe is a utility for working with Magic: The Gathering mana costs and activation costs.
|
|
5
|
+
|
|
6
|
+
Supports both brace `{3}{R/U}` and shortform `3R/U` notation.
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- Parse MTG mana costs into structured objects
|
|
11
|
+
- Supports all major symbols: generic, colored, hybrid, phyrexian, snow, energy, tap/untap, variable
|
|
12
|
+
- Works with both Scryfall braces and shortform notation
|
|
13
|
+
- Compute:
|
|
14
|
+
- Converted mana cost
|
|
15
|
+
- Color identity
|
|
16
|
+
- Devotion
|
|
17
|
+
- Extensible design — add new symbol types by subclassing
|
|
18
|
+
|
|
19
|
+
Note: When the parser encounters an unrecognized symbol it stops parsing and returns the symbols it parsed so far. You can access the unparsed string component through the property `remainingStr`.
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install mana-scribe
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
```js
|
|
29
|
+
import { ManaCost } from 'mana-scribe';
|
|
30
|
+
|
|
31
|
+
const cost = ManaCost.parse('{3}{R}{R}{R}');
|
|
32
|
+
console.log(cost.cmc); // 6
|
|
33
|
+
console.log(cost.colors); // ['R']
|
|
34
|
+
console.log(cost.colorIdentity); // ['R']
|
|
35
|
+
console.log(cost.getDevotionTo('R')); // 3
|
|
36
|
+
|
|
37
|
+
console.log(cost.toString(true)); // "{3}{R}{R}{R}"
|
|
38
|
+
console.log(cost.toString(false)); // "3RRR"
|
|
39
|
+
```
|
|
40
|
+
### Shortform example
|
|
41
|
+
```js
|
|
42
|
+
const cost = ManaCost.parse('2WU/B');
|
|
43
|
+
console.log(cost.cmc); // 3
|
|
44
|
+
console.log(cost.colors); // ['W','U','B']
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Activation costs
|
|
48
|
+
`ActivationCost` offers the same functionality as the `ManaCost` class, but supports additional symbols such as Tap, Untap, and Energy.
|
|
49
|
+
|
|
50
|
+
```js
|
|
51
|
+
import { ActivationCost } from 'mana-scribe';
|
|
52
|
+
|
|
53
|
+
const cost = ActivationCost.parse('{1}{G}{T}');
|
|
54
|
+
console.log(cost.symbols.map(s => s.type)); // ["generic", "colored", "tap"]
|
|
55
|
+
console.log(cost.toString(true)); // "{1}{G}{T}"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Extending
|
|
59
|
+
|
|
60
|
+
The library is class-based. Each symbol type is a subclass of a base Symbol class and implements:
|
|
61
|
+
|
|
62
|
+
- `static match(str)` → returns a regex match object if it applies
|
|
63
|
+
- `get colors()` → returns an array of the colors associated with the symbol
|
|
64
|
+
- `cmcValue()` → returns an integer corresponding to how much this symbol contributes to a card's overall converted mana cost.
|
|
65
|
+
|
|
66
|
+
This makes it easy to add custom symbols or patch existing ones in your own project.
|
|
67
|
+
|
|
68
|
+
## Project Status
|
|
69
|
+
|
|
70
|
+
This is an early but complete implementation.
|
|
71
|
+
|
|
72
|
+
Current priorities:
|
|
73
|
+
- [x] Support core MTG symbols
|
|
74
|
+
- [x] Add test coverage
|
|
75
|
+
- [ ] Other improvements? TBD
|
|
76
|
+
|
|
77
|
+
## License
|
|
78
|
+
|
|
79
|
+
This project is licensed under the MIT License. See LICENSE for more info.
|
package/jasmine.json
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mana-scribe",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Library for parsing and querying Magic: The Gathering mana costs — with support for CMC, devotion, and color identity.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"mtg",
|
|
7
|
+
"magic the gathering",
|
|
8
|
+
"mana",
|
|
9
|
+
"cost",
|
|
10
|
+
"parser"
|
|
11
|
+
],
|
|
12
|
+
"homepage": "https://github.com/matortheeternal/mana-scribe#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/matortheeternal/mana-scribe/issues"
|
|
15
|
+
},
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/matortheeternal/mana-scribe.git"
|
|
19
|
+
},
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"author": "mator",
|
|
22
|
+
"type": "module",
|
|
23
|
+
"main": "./src/index.js",
|
|
24
|
+
"exports": {
|
|
25
|
+
".": "./src/index.js"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"c8": "^10.1.3",
|
|
29
|
+
"jasmine": "^5.12.0"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"test": "jasmine --config=jasmine.json",
|
|
33
|
+
"coverage": "c8 npm test"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import Cost from './Cost.js';
|
|
2
|
+
import ColoredManaSymbol from '../symbols/ColoredManaSymbol.js';
|
|
3
|
+
import ColoredPhyrexianManaSymbol from '../symbols/ColoredPhyrexianManaSymbol.js';
|
|
4
|
+
import ColorlessManaSymbol from '../symbols/ColorlessManaSymbol.js';
|
|
5
|
+
import ColorlessPhyrexianManaSymbol from '../symbols/ColorlessPhyrexianManaSymbol.js';
|
|
6
|
+
import EnergySymbol from '../symbols/EnergySymbol.js';
|
|
7
|
+
import FiveColorHybridManaSymbol from '../symbols/FiveColorHybridManaSymbol.js';
|
|
8
|
+
import FourColorHybridManaSymbol from '../symbols/FourColorHybridManaSymbol.js';
|
|
9
|
+
import GenericHybridManaSymbol from '../symbols/GenericHybridManaSymbol.js';
|
|
10
|
+
import GenericManaSymbol from '../symbols/GenericManaSymbol.js';
|
|
11
|
+
import HalfColoredManaSymbol from '../symbols/HalfColoredManaSymbol.js';
|
|
12
|
+
import InfiniteManaSymbol from '../symbols/InfiniteManaSymbol.js';
|
|
13
|
+
import SnowManaSymbol from '../symbols/SnowManaSymbol.js';
|
|
14
|
+
import TapSymbol from '../symbols/TapSymbol.js';
|
|
15
|
+
import ThreeColorHybridManaSymbol from '../symbols/ThreeColorHybridManaSymbol.js';
|
|
16
|
+
import TwoColorHybridManaSymbol from '../symbols/TwoColorHybridManaSymbol.js';
|
|
17
|
+
import UntapSymbol from '../symbols/UntapSymbol.js';
|
|
18
|
+
import VariableManaSymbol from '../symbols/VariableManaSymbol.js';
|
|
19
|
+
|
|
20
|
+
export default class ActivationCost extends Cost {
|
|
21
|
+
static get allowedSymbols() {
|
|
22
|
+
return [
|
|
23
|
+
ColoredPhyrexianManaSymbol, GenericHybridManaSymbol, FiveColorHybridManaSymbol,
|
|
24
|
+
FourColorHybridManaSymbol, ThreeColorHybridManaSymbol, TwoColorHybridManaSymbol,
|
|
25
|
+
ColorlessPhyrexianManaSymbol, HalfColoredManaSymbol,
|
|
26
|
+
GenericManaSymbol, VariableManaSymbol,
|
|
27
|
+
InfiniteManaSymbol, SnowManaSymbol,
|
|
28
|
+
ColoredManaSymbol, ColorlessManaSymbol,
|
|
29
|
+
EnergySymbol, TapSymbol, UntapSymbol,
|
|
30
|
+
];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { NotImplementedError } from '../customErrors.js';
|
|
2
|
+
|
|
3
|
+
export default class Cost {
|
|
4
|
+
static get allowedSymbols() {
|
|
5
|
+
throw new NotImplementedError('allowedSymbols');
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
static parse(str) {
|
|
9
|
+
const cost = new this();
|
|
10
|
+
cost.parseSymbols(str);
|
|
11
|
+
return cost;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
constructor(symbols = []) {
|
|
15
|
+
this.symbols = symbols;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
get colors() {
|
|
19
|
+
const set = new Set(this.symbols.flatMap(sym => sym.colors));
|
|
20
|
+
return [...set];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
parseSymbols(str) {
|
|
24
|
+
let remainingStr = str.trim();
|
|
25
|
+
while (remainingStr.length) {
|
|
26
|
+
const symbol = this.parseNextSymbol(remainingStr);
|
|
27
|
+
if (!symbol) break;
|
|
28
|
+
symbol.apply(this.symbols);
|
|
29
|
+
if (remainingStr === symbol.remainingStr) break;
|
|
30
|
+
remainingStr = symbol.remainingStr;
|
|
31
|
+
}
|
|
32
|
+
this.remainingStr = remainingStr;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
parseNextSymbol(str) {
|
|
36
|
+
for (const symbol of this.constructor.allowedSymbols) {
|
|
37
|
+
const match = symbol.match(str);
|
|
38
|
+
if (!match) continue;
|
|
39
|
+
return symbol.parse(match, str);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
toString(useBraces = false) {
|
|
44
|
+
return this.symbols.map(sym => {
|
|
45
|
+
return sym.toString(useBraces);
|
|
46
|
+
}).join('');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import Cost from './Cost.js';
|
|
2
|
+
import ColoredManaSymbol from '../symbols/ColoredManaSymbol.js';
|
|
3
|
+
import ColoredPhyrexianManaSymbol from '../symbols/ColoredPhyrexianManaSymbol.js';
|
|
4
|
+
import ColorlessManaSymbol from '../symbols/ColorlessManaSymbol.js';
|
|
5
|
+
import ColorlessPhyrexianManaSymbol from '../symbols/ColorlessPhyrexianManaSymbol.js';
|
|
6
|
+
import FiveColorHybridManaSymbol from '../symbols/FiveColorHybridManaSymbol.js';
|
|
7
|
+
import FourColorHybridManaSymbol from '../symbols/FourColorHybridManaSymbol.js';
|
|
8
|
+
import GenericHybridManaSymbol from '../symbols/GenericHybridManaSymbol.js';
|
|
9
|
+
import GenericManaSymbol from '../symbols/GenericManaSymbol.js';
|
|
10
|
+
import HalfColoredManaSymbol from '../symbols/HalfColoredManaSymbol.js';
|
|
11
|
+
import InfiniteManaSymbol from '../symbols/InfiniteManaSymbol.js';
|
|
12
|
+
import SnowManaSymbol from '../symbols/SnowManaSymbol.js';
|
|
13
|
+
import ThreeColorHybridManaSymbol from '../symbols/ThreeColorHybridManaSymbol.js';
|
|
14
|
+
import TwoColorHybridManaSymbol from '../symbols/TwoColorHybridManaSymbol.js';
|
|
15
|
+
import VariableManaSymbol from '../symbols/VariableManaSymbol.js';
|
|
16
|
+
|
|
17
|
+
export default class ManaCost extends Cost {
|
|
18
|
+
static get allowedSymbols() {
|
|
19
|
+
return [
|
|
20
|
+
ColoredPhyrexianManaSymbol, GenericHybridManaSymbol, FiveColorHybridManaSymbol,
|
|
21
|
+
FourColorHybridManaSymbol, ThreeColorHybridManaSymbol, TwoColorHybridManaSymbol,
|
|
22
|
+
ColorlessPhyrexianManaSymbol, HalfColoredManaSymbol,
|
|
23
|
+
GenericManaSymbol, VariableManaSymbol,
|
|
24
|
+
InfiniteManaSymbol, SnowManaSymbol,
|
|
25
|
+
ColoredManaSymbol, ColorlessManaSymbol
|
|
26
|
+
];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
get cmc() {
|
|
30
|
+
return this.symbols.reduce((sum, sym) => sum + sym.cmcValue(), 0);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
getDevotionTo(color) {
|
|
34
|
+
return this.symbols.reduce((devotion, sym) => {
|
|
35
|
+
return devotion + (sym.colors.includes(color) ? 1 : 0);
|
|
36
|
+
}, 0);
|
|
37
|
+
}
|
|
38
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import Symbol from './Symbol.js';
|
|
2
|
+
|
|
3
|
+
export default class ColoredManaSymbol extends Symbol {
|
|
4
|
+
static match(str) {
|
|
5
|
+
return str.match(/^{[WUBRG]}/i)
|
|
6
|
+
|| str.match(/^[WUBRG]/i);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
get colors() {
|
|
10
|
+
return [this.raw];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
get type() {
|
|
14
|
+
return 'coloredMana';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
cmcValue() {
|
|
18
|
+
return 1;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import Symbol from './Symbol.js';
|
|
2
|
+
|
|
3
|
+
export default class ColoredPhyrexianManaSymbol extends Symbol {
|
|
4
|
+
static match(str) {
|
|
5
|
+
return str.match(/^{(h\/[wubrg]|[wubrg]\/h)}/i)
|
|
6
|
+
|| str.match(/^(h\/[wubrg]|[wubrg]\/h)/i);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
get colors() {
|
|
10
|
+
return this.raw.split('/').filter(c => 'WUBRG'.includes(c));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
get type() {
|
|
14
|
+
return 'coloredPhyrexianMana';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
cmcValue() {
|
|
18
|
+
return 1;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import Symbol from './Symbol.js';
|
|
2
|
+
|
|
3
|
+
export default class ColorlessManaSymbol extends Symbol {
|
|
4
|
+
static match(str) {
|
|
5
|
+
return str.match(/^{C}/i)
|
|
6
|
+
|| str.match(/^C/i);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
get colors() {
|
|
10
|
+
return [];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
get type() {
|
|
14
|
+
return 'colorlessMana';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
cmcValue() {
|
|
18
|
+
return 1;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import ColorlessManaSymbol from './ColorlessManaSymbol.js';
|
|
2
|
+
|
|
3
|
+
export default class ColorlessPhyrexianManaSymbol extends ColorlessManaSymbol {
|
|
4
|
+
static match(str) {
|
|
5
|
+
return str.match(/^{(h\/c|c\/h|h)}/i)
|
|
6
|
+
|| str.match(/^(h\/c|c\/h|h)/i);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
get type() {
|
|
10
|
+
return 'colorlessPhyrexianMana';
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import HybridManaSymbol from './HybridManaSymbol.js';
|
|
2
|
+
|
|
3
|
+
export default class FiveColorHybridManaSymbol extends HybridManaSymbol {
|
|
4
|
+
static match(str) {
|
|
5
|
+
return super.match(str, 5, /^{[WUBRG](\/[WUBRG]){4}}/i)
|
|
6
|
+
|| super.match(str, 5, /^[WUBRG](\/[WUBRG]){4}/i);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
get type() {
|
|
10
|
+
return 'fiveColorHybridMana';
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import HybridManaSymbol from './HybridManaSymbol.js';
|
|
2
|
+
|
|
3
|
+
export default class FourColorHybridManaSymbol extends HybridManaSymbol {
|
|
4
|
+
static match(str) {
|
|
5
|
+
return super.match(str, 4, /^{[WUBRG](\/[WUBRG]){3}}/i)
|
|
6
|
+
|| super.match(str, 4, /^[WUBRG](\/[WUBRG]){3}/i);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
get type() {
|
|
10
|
+
return 'fourColorHybridMana';
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import Symbol from './Symbol.js';
|
|
2
|
+
|
|
3
|
+
export default class GenericHybridManaSymbol extends Symbol {
|
|
4
|
+
static match(str) {
|
|
5
|
+
return str.match(/^{\d\/([WUBRGC])}/i)
|
|
6
|
+
|| str.match(/^\d\/([WUBRGC])/i)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
get colors() {
|
|
10
|
+
return this.raw.split('/').filter(c => 'WUBRG'.includes(c));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
get type() {
|
|
14
|
+
return 'genericHybridMana';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
cmcValue() {
|
|
18
|
+
return 1;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import Symbol from './Symbol.js';
|
|
2
|
+
|
|
3
|
+
export default class GenericManaSymbol extends Symbol {
|
|
4
|
+
static match(str) {
|
|
5
|
+
return str.match(/^{[0-9][0-9]?}/)
|
|
6
|
+
|| str.match(/^[0-9][0-9]?/);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
get type() {
|
|
10
|
+
return 'genericMana';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
cmcValue() {
|
|
14
|
+
return parseInt(this.raw);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import Symbol from './Symbol.js';
|
|
2
|
+
|
|
3
|
+
export default class HalfColoredManaSymbol extends Symbol {
|
|
4
|
+
static match(str) {
|
|
5
|
+
return str.match(/^{\|[WUBRGS]}/i)
|
|
6
|
+
|| str.match(/^\|[WUBRGS]/i);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
get colors() {
|
|
10
|
+
const c = this.raw[1];
|
|
11
|
+
return c === 'S' ? [] : [c];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
get type() {
|
|
15
|
+
return 'halfColoredMana';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
cmcValue() {
|
|
19
|
+
return 0.5;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import Symbol from './Symbol.js';
|
|
2
|
+
|
|
3
|
+
function getHybridCount(str) {
|
|
4
|
+
return new Set(str.split('/')).size;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export default class HybridManaSymbol extends Symbol {
|
|
8
|
+
static match(str, hybridCount, expr) {
|
|
9
|
+
const matchData = str.match(expr);
|
|
10
|
+
if (!matchData || getHybridCount(matchData[0]) !== hybridCount) return;
|
|
11
|
+
return matchData;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
get colors() {
|
|
15
|
+
return this.raw.split('/');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
cmcValue() {
|
|
19
|
+
return 1;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import Symbol from './Symbol.js';
|
|
2
|
+
|
|
3
|
+
export default class InfiniteManaSymbol extends Symbol {
|
|
4
|
+
static match(str) {
|
|
5
|
+
return str.match(/^{I}/i)
|
|
6
|
+
|| str.match(/^I/i)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
get type() {
|
|
10
|
+
return 'infiniteMana';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
cmcValue() {
|
|
14
|
+
return Infinity;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import Symbol from './Symbol.js';
|
|
2
|
+
|
|
3
|
+
export default class SnowManaSymbol extends Symbol {
|
|
4
|
+
static match(str) {
|
|
5
|
+
return str.match(/^{S}/i)
|
|
6
|
+
|| str.match(/^S/i);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
get colors() {
|
|
10
|
+
return [];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
get type() {
|
|
14
|
+
return 'snowMana';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
cmcValue() {
|
|
18
|
+
return 1;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { NotImplementedError } from '../customErrors.js';
|
|
2
|
+
|
|
3
|
+
export default class Symbol {
|
|
4
|
+
static match(str) {
|
|
5
|
+
throw new NotImplementedError('match');
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
static fromString(str) {
|
|
9
|
+
const matchData = this.match(str);
|
|
10
|
+
if (!matchData)
|
|
11
|
+
throw new Error(`Invalid ${this.name} input: ${str}`);
|
|
12
|
+
return this.parse(matchData, str);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
static parse(match, str) {
|
|
16
|
+
return new this(match, str);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
constructor(match, str) {
|
|
20
|
+
const raw = match[0];
|
|
21
|
+
this.raw = (raw[0] === '{' ? raw.slice(1, -1) : raw).toUpperCase();
|
|
22
|
+
this.remainingStr = str.slice(match[0].length);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
get type() {
|
|
26
|
+
throw new NotImplementedError('type');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
get cmcValue() {
|
|
30
|
+
throw new NotImplementedError('cmcValue');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
get colors() {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
apply(symbols) {
|
|
38
|
+
symbols.push(this);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
toString(useBraces = false) {
|
|
42
|
+
return useBraces ? `{${this.raw}}` : this.raw;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import HybridManaSymbol from './HybridManaSymbol.js';
|
|
2
|
+
|
|
3
|
+
export default class ThreeColorHybridManaSymbol extends HybridManaSymbol {
|
|
4
|
+
static match(str) {
|
|
5
|
+
return super.match(str, 3, /^{[WUBRG](\/[WUBRG]){2}}/i)
|
|
6
|
+
|| super.match(str, 3, /^[WUBRG](\/[WUBRG]){2}/i);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
get type() {
|
|
10
|
+
return 'threeColorHybridMana';
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import HybridManaSymbol from './HybridManaSymbol.js';
|
|
2
|
+
|
|
3
|
+
export default class TwoColorHybridManaSymbol extends HybridManaSymbol {
|
|
4
|
+
static match(str) {
|
|
5
|
+
return super.match(str, 2, /^{[WUBRG]\/[WUBRG]}/i)
|
|
6
|
+
|| super.match(str, 2, /^[WUBRG]\/[WUBRG]/i);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
get type() {
|
|
10
|
+
return 'twoColorHybridMana';
|
|
11
|
+
}
|
|
12
|
+
}
|