Skip to content

Commit

Permalink
[docs + core + api] Add more VM commands
Browse files Browse the repository at this point in the history
  • Loading branch information
alicerunsonfedora committed May 11, 2020
1 parent bcb7a2b commit 4d1de27
Show file tree
Hide file tree
Showing 9 changed files with 204 additions and 32 deletions.
27 changes: 27 additions & 0 deletions PYREADME.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Fira
**Fira** is the main backend and API code for the minigame in [Unscripted](https://unscripted.marquiskurt.net), a visual novel about software development. Fira provides many facets of the minigame, including a public API that players can use to code solutions to the minigame puzzles, a configuration and data generator from level files, and a virtual machine that runs low-level code that the minigame processes (NadiaVM). Fira is named after Fira Sans, one of the game's characters.

## Getting started
Fira comes pre-packaged in Unscripted but can be installed outside of the game to work better with IDEs and other Python tools such as Poetry.

## Usage
For players installing this package to solve minigame puzzles, using the Fira package to access the API is relatively straightforward:

```py
from uvn_fira.api import get_level_information, CSPlayer, CSWorld

gp, gw = get_level_information(0,
fn_path=renpy.config.savedir + "/minigame",
exists=renpy.loadable,
load=renpy.exports.file)
```

Documentation on the API is located inside of Unscripted by going to **Help › Minigame** or **Settings › Minigame**.

The documentation for the entire package is located at [https://fira.marquiskurt.net](https://fira.marquiskurt.net), which is useful for developers that wish to make custom toolkits that connect to the minigame's virtual machine or for modders that wish to make custom minigame levels.

## Reporting bugs
Bugs and feature requests for Fira can be submitted on GitHub.

## License
The Fira package is licensed under the Mozilla Public License v2.0.
8 changes: 3 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Fira

**Fira** is the main backend and API code for the minigame in [Unscripted](https://unscripted.marquiskurt.net), a visual novel about software development. Fira provides many facets of the minigame, including a public API that players can use to code solutions to the minigame puzzles, a configuration and data generator from level files, and a virtual machine that runs low-level code that the minigame processes. Fira is named after Fira Sans, one of the game's characters.
**Fira** is the main backend and API code for the minigame in [Unscripted](https://unscripted.marquiskurt.net), a visual novel about software development. Fira provides many facets of the minigame, including a public API that players can use to code solutions to the minigame puzzles, a configuration and data generator from level files, and a virtual machine that runs low-level code that the minigame processes (NadiaVM). Fira is named after Fira Sans, one of the game's characters.


[![MPL](https://img.shields.io/github/license/alicerunsonfedora/fira)](LICENSE.txt)
Expand Down Expand Up @@ -49,17 +49,15 @@ from uvn_fira.api import get_level_information, CSPlayer, CSWorld
gp, gw = get_level_information(0,
fn_path=renpy.config.savedir + "/minigame",
exists=renpy.loadable,
load=renpy.exports.file) # type: (CSPlayer, CSWorld)
load=renpy.exports.file)
```

Documentation on the API is located inside of Unscripted by going to **Help › Minigame** or **Settings › Minigame**.

The documentation for the entire package is located at [https://fira.marquiskurt.net](https://fira.marquiskurt.net), which is useful for developers that wish to make custom toolkits that connect to the minigame's virtual machine or for modders that wish to make custom minigame levels.

## Reporting bugs

Bugs and feature requests for Fira can be submitted on YouTrack. [Submit a report ›](https://youtrack.marquiskurt.net/youtrack/newIssue?project=FIRA)
Bugs and feature requests for Fira can be submitted on GitHub.

## License

The Fira package is licensed under the Mozilla Public License v2.0.
14 changes: 7 additions & 7 deletions docs/Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
GEM
specs:
activesupport (6.0.2.1)
activesupport (6.0.2.2)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2)
minitest (~> 5.1)
Expand All @@ -25,7 +25,7 @@ GEM
ffi (>= 1.3.0)
eventmachine (1.2.7)
execjs (2.7.0)
faraday (1.0.0)
faraday (1.0.1)
multipart-post (>= 1.2, < 3)
ffi (1.12.2)
forwardable-extended (2.6.0)
Expand Down Expand Up @@ -205,7 +205,7 @@ GEM
multipart-post (2.1.1)
nokogiri (1.10.9)
mini_portile2 (~> 2.4.0)
octokit (4.17.0)
octokit (4.18.0)
faraday (>= 0.9)
sawyer (~> 0.8.0, >= 0.5.3)
pathutil (0.16.2)
Expand All @@ -215,9 +215,9 @@ GEM
rb-inotify (0.10.1)
ffi (~> 1.0)
rouge (3.13.0)
ruby-enum (0.7.2)
ruby-enum (0.8.0)
i18n
rubyzip (2.2.0)
rubyzip (2.3.0)
safe_yaml (1.0.5)
sass (3.7.4)
sass-listen (~> 4.0.0)
Expand All @@ -232,9 +232,9 @@ GEM
thread_safe (0.3.6)
typhoeus (1.3.1)
ethon (>= 0.9.0)
tzinfo (1.2.6)
tzinfo (1.2.7)
thread_safe (~> 0.1)
unicode-display_width (1.6.1)
unicode-display_width (1.7.0)
zeitwerk (2.3.0)

PLATFORMS
Expand Down
44 changes: 44 additions & 0 deletions docs/core/vm.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,28 @@ Move the player in a given direction.
### collect()
Collect. In the VM, this acts like a pause.

### add()

Add the two topmost values on the stack.

### sub()

Subtract the two topmost values on the stack.

### mult()

Multiply the two topmost values on the stack.

### div()

Divide the two topmost values on the stack.

### neg()

Negate the topmost value on the stack.

Effectively, this is the equivalent of pushing -1 onto the stack and calling [`mult`](#mult).

### exit()
Try to exit the world and end execution of the script.

Expand Down Expand Up @@ -233,6 +255,28 @@ Move the player in a given direction.
### collect()
Collect. In the VM, this acts like a pause.

### add()

Add the two topmost values on the stack.

### sub()

Subtract the two topmost values on the stack.

### mult()

Multiply the two topmost values on the stack.

### div()

Divide the two topmost values on the stack.

### neg()

Negate the topmost value on the stack.

Effectively, this is the equivalent of pushing -1 onto the stack and calling [`mult`](#mult).

### exit()
Try to exit the world and end execution of the script.

Expand Down
5 changes: 5 additions & 0 deletions docs/implementation.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ NadiaVM comes with several commands used to perform tasks in the minigame:
- `set constant (any)`: Set the top of the stack to a constant value.
- `move player (str)`: Move the player in a given direction.
- `exit player`: Try to end the execution script and finish the level.
- <span class="label label-purple">New</span> `add`: Add the two topmost values on the stack.
- <span class="label label-purple">New</span> `sub`: Subtract the two topmost values on the stack.
- <span class="label label-purple">New</span> `mult`: Multiply the two topmost values on the stack.
- <span class="label label-purple">New</span> `div`: Divide the two topmost values on the stack.
- <span class="label label-purple">New</span> `neg`: Negate the topmost value on the stack. Effectively the same as pushing `-1` on the stack and calling `mult`.

### Limitations

Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
[tool.poetry]
name = "uvn-fira"
version = "1.2.11"
version = "1.3.0"
description = "The backend and API code for the Unscripted mini-game."
authors = ["Marquis Kurt <software@marquiskurt.net>"]
license = "MPL-2.0"
readme = "README.md"
readme = "PYREADME.md"
homepage = "https://github.com/UnscriptedVN/fira"
documentation = "https://fira.marquiskurt.net"

Expand Down
2 changes: 1 addition & 1 deletion uvn_fira/api/info.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def get_level_information(level, fn_path="", **kwargs):
if "config_file" in kwargs \
else "core/minigame/levels/level%s.toml" % (level)
w_info = CSWorldConfigReader(conf, **kwargs)
writer = CSNadiaVMWriter(fn_path + "/compiled/lvl%s.nvm" % (level))
writer = CSNadiaVMWriter(fn_path + "/compiled/adv_lvl%s.nvm" % (level))
world = CSWorld(from_data=w_info.data)
player = CSPlayer(in_world=world, vm=writer)
return player, world
9 changes: 5 additions & 4 deletions uvn_fira/core/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ def generate_template(filepath, for_level=0):
# to view the limitations of using the official API as called here.
#
# If you want to use a third-party tool or framework instead of the official Fira API and want to
# sideload in a virtual machine file, do not write any code in this file. More information can be
# found at https://fira.marquiskurt.net/implementation.html#obtaining-code-from-third-party-tools.
# sideload in a virtual machine file, make sure "Force Python compiler" in Settings > Minigame is
# disabled and that your tool compiles the NadiaVM file to your save directory like the following:
# /path/to/RenPy/net.marquiskurt.unscripted/minigame/compiled/adv_lvl%s.nvm
#
# Import the level information APIs.
Expand All @@ -46,9 +47,9 @@ def generate_template(filepath, for_level=0):
game_player, game_world = get_level_information(%s,
fn_path=renpy.config.savedir + "/minigame/",
exists=renpy.loadable,
load=renpy.exports.file) # type: (CSPlayer, CSWorld)
load=renpy.exports.file)
# WRITE CODE HERE
""" % (for_level, for_level)
""" % (for_level, for_level, for_level)
with open(filepath, 'w+') as file_obj:
file_obj.write(template)
123 changes: 110 additions & 13 deletions uvn_fira/core/vm.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@
- `set constant (any)`: Set the top of the stack to a constant value.
- `move player (str)`: Move the player in a given direction.
- `exit player`: Try to end the execution script and finish the level.
- `add`: Add the two topmost values on the stack.
- `sub`: Subtract the two topmost values on the stack.
- `mult`: Multiply the two topmost values on the stack.
- `div`: Divide the two topmost values on the stack.
- `neg`: Negate the topmost value on the stack. Effectively the same as pushing `-1` on the stack
and calling `mult`.
"""
from typing import Optional

Expand Down Expand Up @@ -86,21 +92,34 @@ def next(self):
"""
current = self._instructions.pop(0).split(" ")

if current[0] == "alloc":
self._alloc(current[1], int(current[2]))
elif current[0] == "push":
self._push(current[1], int(current[2]))
elif current[0] == "pop":
self._pop(current[1], int(current[2]))
elif current[0] == "set":
self._set(current[2])
elif current[0] == "move":
self._move(current[2])
elif current[0] == "collect":
pass
elif current[0] != "exit":
instruct = {
"alloc": self._alloc,
"push": self._push,
"pop" : self._pop,
"set": self._set,
"move": self._move,
"add": self._add,
"sub": self._sub,
"mult": self._mult,
"div": self._div,
"neg": self._neg,
"exit": "NOCOMM",
"collect": "NOCOMM",
}

if current[0] not in instruct:
raise CSNadiaVMCommandNotFoundError("Invalid command: '%s'" % (current[0]))

command = instruct.get(current[0])
if current[0] in ["alloc", "push", "pop"]:
command(current[1], int(current[2]))
elif current[0] in ["set", "move"]:
command(current[2])
elif current[0] in ["add", "sub", "mult", "div", "neg"]:
command()
else:
pass

def _alloc(self, name, size):
"""Allocate a space of memory for a given array.
Expand Down Expand Up @@ -155,6 +174,38 @@ def _move(self, direction):
curr_x, curr_y = self._player_pos
self._player_pos = curr_x + trans_x, curr_y + trans_y

def _add(self):
"""Add the two topmost values on the stack."""
x = self._stack.pop()
y = self._stack.pop()
self._stack.append(int(x) + int(y))

def _sub(self):
"""Subtract the two topmost values on the stack."""
x = self._stack.pop()
y = self._stack.pop()
self._stack.append(int(x) - int(y))

def _mult(self):
"""Multiply the two topmost values on the stack."""
x = self._stack.pop()
y = self._stack.pop()
self._stack.append(int(x) * int(y))

def _div(self):
"""Divide the two topmost values on the stack."""
x = self._stack.pop()
y = self._stack.pop()
self._stack.append(int(x) / int(y))

def _neg(self):
"""Negate the topmost value on the stack.
Effectively, this is the equivalent of pushing -1 onto the stack and calling mult.
"""
self._stack.append(-1)
self._mult()

def get(self, name):
# type: (CSNadiaVM, str) -> Optional[list]
"""Get the specified item in the virtual machine.
Expand Down Expand Up @@ -265,6 +316,29 @@ def exit(self):
"""Try to exit the world and end execution of the script."""
self.instructions.append("exit player\n")

def add(self):
"""Add the two topmost values on the stack."""
self.instructions.append("add\n")

def sub(self):
"""Subtract the two topmost values on the stack."""
self.instructions.append("sub\n")

def mult(self):
"""Multiply the two topmost values on the stack."""
self.instructions.append("mult\n")

def div(self):
"""Divide the two topmost values on the stack."""
self.instructions.append("div\n")

def neg(self):
"""Negate the topmost value on the stack.
Effectively, this is the equivalent of pushing -1 onto the stack and calling mult.
"""
self.instructions.append("neg\n")

def write(self):
# type: (CSNadiaVMWriterBuilder) -> None
"""Write the VM code to the requested file."""
Expand Down Expand Up @@ -367,6 +441,29 @@ def exit(self):
"""Try to exit the world and end execution of the script."""
self.code += "exit player\n"

def add(self):
"""Add the two topmost values on the stack."""
self.code += "add\n"

def sub(self):
"""Subtract the two topmost values on the stack."""
self.code += "sub\n"

def mult(self):
"""Multiply the two topmost values on the stack."""
self.code += "mult\n"

def div(self):
"""Divide the two topmost values on the stack."""
self.code += "div\n"

def neg(self):
"""Negate the topmost value on the stack.
Effectively, this is the equivalent of pushing -1 onto the stack and calling mult.
"""
self.code += "neg\n"

def write(self):
# type: (CSNadiaVMWriter) -> None
"""Write the VM code to the requested file."""
Expand Down

0 comments on commit 4d1de27

Please sign in to comment.