Compare commits
27 Commits
9d2f615e16
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| ae74c07948 | |||
| 148fb73f7f | |||
| cc3eca857d | |||
| f6a95f76bd | |||
| 59f32e975b | |||
| 4f7abb5f19 | |||
| 20369ef838 | |||
| 21bf659718 | |||
| 93ca1ea34a | |||
| 29565949b0 | |||
| 556185e095 | |||
| 9255c1d7fa | |||
| ee34493895 | |||
| 019f1088a3 | |||
| cba3ec04d7 | |||
| fbb5a4b503 | |||
| 367b3ac396 | |||
| 5f294cceb2 | |||
| 051bfe46e4 | |||
| d4947a4434 | |||
| dd04399784 | |||
| 13049309b2 | |||
| 0c28bc113d | |||
| 94422b307c | |||
| 6d012dbe6b | |||
| 99a5b03290 | |||
| a251be7827 |
@@ -1,5 +1,8 @@
|
||||
/server/target
|
||||
/client/target
|
||||
*.swp
|
||||
.venv
|
||||
.~*
|
||||
report.odt
|
||||
examples/
|
||||
images/
|
||||
|
||||
+329
-328
@@ -70,152 +70,153 @@ modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
@@ -233,21 +234,21 @@ terms of section 4, provided that you also meet all of these conditions:
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
@@ -291,76 +292,76 @@ in one of these ways:
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
@@ -386,250 +387,250 @@ that material) supplement the terms of this License with terms:
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
# HAPPENING
|
||||
An interactive story telling software.<br/>
|
||||
# Happening
|
||||
|
||||
An interactive story telling software.
|
||||
Read and write people's stories
|
||||
For help with making a story, see [this documentation](/docs/MAKING_A_STORY.md)
|
||||
For help with syntax, see [this documentation](/docs/SYNTAX.md)
|
||||
|
||||
## Install
|
||||
This is not really out of development, but to run it, clone the repo, go into /server/ and use cargo run story.zip to run a file called story.zip that's in the /stories/ directory.
|
||||
|
||||
This is not really out of development, but to run it, clone the repo, go into /server/ and use cargo run story.zip to run a file called story.zip, absolute or relative file location.
|
||||
|
||||
## Technical Details
|
||||
|
||||
### Back-end server -- Rust
|
||||
### File layout
|
||||
|
||||
The server component of Happening will be written in Rust.<br/>
|
||||
Parses the code and sends it via the API.<br/>
|
||||
File layout:<br/>
|
||||
- animations/
|
||||
- features/
|
||||
- scenes/
|
||||
@@ -26,13 +28,27 @@ File layout:<br/>
|
||||
- scenes/
|
||||
- images/
|
||||
|
||||
The variables are stored as a hashmap, characters are objects.<br/>
|
||||
### Back-end server -- Rust
|
||||
|
||||
The server component of Happening will be written in Rust.<br/>
|
||||
Parses the code and sends it via the API.<br/>
|
||||
File layout:
|
||||
|
||||
The variables are stored as a hashmap, characters are structs in a hashmap.<br/>
|
||||
|
||||
### API
|
||||
|
||||
Using the network interface, port 20264.<br/>
|
||||
> [!NOTE]
|
||||
> The port is configurable in config.json of the server, client should be flexible with ports.
|
||||
|
||||
Characters are sent to the frontend and stored there when the character is created on the frontend.<br/>
|
||||
There are 4 endpoints.
|
||||
|
||||
#### /happening
|
||||
|
||||
GET Returns what is happening on the next cycle:
|
||||
|
||||
```
|
||||
{
|
||||
|
||||
@@ -42,136 +58,63 @@ content: String,
|
||||
|
||||
character: String,
|
||||
|
||||
choice: [String,String,String,...]
|
||||
|
||||
}
|
||||
```
|
||||
#### /character/\<character name\>
|
||||
GET Returns the struct of the character, ideally this should only be gotten when a new character is made or a character changes a feature.
|
||||
|
||||
#### /choice
|
||||
POST the choice as a JSON formatting number, eg 1 to choose choice index 1, returns "ack" as acknowledgment.
|
||||
|
||||
#### /input
|
||||
POST not yet fully implemented on the syntax side but allows client to send a user input to the server as text. returns "ack" as acknowlegment.
|
||||
|
||||
### Frontend -- Python
|
||||
|
||||
Things the frontend can do:<br/>
|
||||
Things the frontend should be able to:
|
||||
|
||||
- Output text
|
||||
- Display the character
|
||||
- Move characters around
|
||||
- Display a choice to the user that gets sent back to the server
|
||||
|
||||
### Syntax
|
||||
### Files
|
||||
|
||||
#### Setup
|
||||
For more info on these files see [Making a Story](/docs/MAKING_A_STORY.md)
|
||||
|
||||
This is done in the about.json file,
|
||||
```
|
||||
{
|
||||
#### story.ha
|
||||
|
||||
"title": "My Great Story",
|
||||
This contains the code for the story, syntax for writing the code can be seen in [the syntax section](/docs/SYNTAX.md)
|
||||
|
||||
"description": "please read!"
|
||||
#### about.json
|
||||
|
||||
}
|
||||
```
|
||||
#### Characters
|
||||
This file contains details about your story such as the title and description.
|
||||
|
||||
#### characters.json
|
||||
|
||||
See [Character documentation](/docs/CHARACTER.md) for more info
|
||||
Referencing a character using the @, @NARRATOR is reserved for the Narrator.<br/>
|
||||
Customisation is done with @CHARACTER change \<feature\> into \<feature name\><br/>
|
||||
Move a character with @CHARACTER to fr
|
||||
|
||||
attributes:<br/>
|
||||
> [!NOTE]
|
||||
> fr means front-right for instance.
|
||||
|
||||
- gender
|
||||
- skin_color
|
||||
- name
|
||||
- eye_color
|
||||
- pronoun_subject
|
||||
- pronoun_object
|
||||
- pronoun_deppos
|
||||
- pronoun_indpos
|
||||
- pronoun_reflex
|
||||
- animation
|
||||
- head
|
||||
- hair
|
||||
- torso
|
||||
- arm
|
||||
- leg
|
||||
- hair_color
|
||||
- top_clothing
|
||||
- bottom_clothing
|
||||
- shoes
|
||||
## TODO
|
||||
- /about.json
|
||||
- tokeniser check for lack of END
|
||||
- Fix no closing brace edge case
|
||||
- Support single quotes for strings
|
||||
- backslashes in strings
|
||||
- Brace index getter check for closing
|
||||
- Proper Error messages centralised???
|
||||
|
||||
#### Outputs
|
||||
```
|
||||
@CHARACTER says "this string
|
||||
is multi-line
|
||||
and ends with a"
|
||||
```
|
||||
#### Variables
|
||||
|
||||
Variables are referenced with the \$, only integers will be supported.<br/>
|
||||
|
||||
#### Selection
|
||||
|
||||
Condition based:
|
||||
```
|
||||
if (condition) {
|
||||
|
||||
}
|
||||
|
||||
elif (condition) {
|
||||
|
||||
}
|
||||
|
||||
else
|
||||
|
||||
}
|
||||
```
|
||||
Choice based:
|
||||
```
|
||||
choice "choice 1" {
|
||||
|
||||
}
|
||||
|
||||
or "choice 2" {
|
||||
|
||||
}
|
||||
|
||||
or "choice 3" {
|
||||
|
||||
}
|
||||
```
|
||||
#### Positioning
|
||||
```
|
||||
@CHARACTER to position
|
||||
|
||||
PAN to position
|
||||
```
|
||||
#### Other
|
||||
```
|
||||
label:
|
||||
|
||||
GOTO label
|
||||
```
|
||||
#### Ending
|
||||
`END` to exit out of the story
|
||||
|
||||
## Implemented stuff
|
||||
### Commands
|
||||
| Command | Implemented |
|
||||
|-----------------|-------------|
|
||||
| END | Yes |
|
||||
| CHOICE/OR/OR | Yes |
|
||||
| IF/ELSE IF/ELSE | No |
|
||||
| GOTO | No |
|
||||
| PAN | No |
|
||||
### Character sub-commands
|
||||
| Character Command | Implemented |
|
||||
|-------------------|-------------|
|
||||
| SAYS | Yes |
|
||||
| CHANGE | Yes |
|
||||
| TO | No |
|
||||
| ANIMATE | No |
|
||||
### Other Features
|
||||
| Feature | Implemented |
|
||||
| ------- | ----------- |
|
||||
| Variables | No |
|
||||
## Error codes
|
||||
|
||||
| | | |
|
||||
|---------|---------------------------------------------|-----------------------------------------------------------------------------------------|
|
||||
| ------- | ------------------------------------------- | --------------------------------------------------------------------------------------- |
|
||||
| Code ID | Meaning | Possible remedies |
|
||||
| 0 | Success | N/A |
|
||||
| 1 | File unexpectedly reached termination point | Make sure there is an END statement in the code. |
|
||||
@@ -188,5 +131,4 @@ GOTO label
|
||||
| 12 | Failed to open archive | Make sure the story file is a zip archive. |
|
||||
| 13 | Unable to setup the characters hashmap. | Make sure the characters.json file exists, is valid JSON and contains valid characters. |
|
||||
| 14 | Unable to read the main story file. | Make sure the story.ha file exists and is readable. |
|
||||
| 15 | | |
|
||||
|
||||
| 15 | Unable to tokenise data | Make sure your code is correctly formatted |
|
||||
|
||||
Generated
+2151
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "happening-client"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
home = "0.5.12"
|
||||
macroquad = "0.4.15"
|
||||
phf = {version="0.13.1",features=["macros"]}
|
||||
reqwest = {version="0.13.3",features=["blocking","json"]}
|
||||
serde = {version="1.0.228",features=["derive"]}
|
||||
serde_json = "1.0.149"
|
||||
tokio = {version="1.52.3",features=["full"]}
|
||||
@@ -0,0 +1,223 @@
|
||||
use macroquad::prelude::*;
|
||||
use reqwest::*;
|
||||
use reqwest::blocking;
|
||||
use phf::phf_map;
|
||||
use std::
|
||||
{
|
||||
thread,
|
||||
time::Duration,
|
||||
collections::
|
||||
{
|
||||
HashMap,
|
||||
HashSet,
|
||||
},
|
||||
process::exit,
|
||||
};
|
||||
use serde::
|
||||
{
|
||||
Serialize,
|
||||
Deserialize,
|
||||
};
|
||||
use home::home_dir;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
|
||||
pub struct Data {
|
||||
pub action_type: String,
|
||||
pub content: String,
|
||||
pub character: String,
|
||||
pub choices: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
|
||||
#[serde(default)]
|
||||
pub struct Character
|
||||
{
|
||||
name: String,
|
||||
gender: String,
|
||||
eye_color: Colour,
|
||||
hair_color: Colour,
|
||||
skin_color: Colour,
|
||||
pronoun_subject: String,
|
||||
pronoun_object: String,
|
||||
pronoun_deppos: String,
|
||||
pronoun_indpos: String,
|
||||
pronoun_reflex: String,
|
||||
head_shape: String,
|
||||
hair_style: String,
|
||||
torso_shape: String,
|
||||
arm_shape: String,
|
||||
leg_shape: String,
|
||||
clothing: Clothing,
|
||||
}
|
||||
#[derive(Debug,Deserialize,Serialize,Clone,Default)]
|
||||
#[serde(default)]
|
||||
pub struct Clothing
|
||||
{
|
||||
top: String,
|
||||
bottom: String,
|
||||
shoes: String,
|
||||
}
|
||||
|
||||
#[derive(Debug,Deserialize,Serialize,Clone,Default)]
|
||||
#[serde(default)]
|
||||
pub struct Colour
|
||||
{
|
||||
red: u8,
|
||||
green: u8,
|
||||
blue: u8,
|
||||
}
|
||||
|
||||
|
||||
static POSITIONS: phf::Map<&'static str, [f32;2]> = phf_map! [
|
||||
"fl" => [0.0,500.0],
|
||||
"bl" => [0.0,0.0],
|
||||
"fr" => [500.0,500.0],
|
||||
"br" => [500.0,0.0],
|
||||
];
|
||||
|
||||
#[macroquad::main("happening")]
|
||||
async fn main()
|
||||
{
|
||||
let mut characters: HashMap<String, (Character, Texture2D)> = HashMap::new();
|
||||
characters.insert("narrator".to_string(), (Character { name: "Narrator".to_string(), ..Default::default() }, Texture2D::empty()));
|
||||
let mut textures: HashMap<String,(Texture2D, f32, f32, Color)> = HashMap::new();
|
||||
let mut text: String = String::new();
|
||||
let mut checking_for_choice: bool = false;
|
||||
let mut data: Data = next_happening(); // First one should be begin
|
||||
loop
|
||||
{
|
||||
clear_background(RED);
|
||||
for (name, (texture, x, y, colour)) in &textures
|
||||
{
|
||||
draw_texture(&texture, *x, *y, *colour);
|
||||
}
|
||||
draw_multiline_text(&text, 50.0,30.0,40.0,None,WHITE);
|
||||
if !is_any_key_down()
|
||||
{
|
||||
next_frame().await;
|
||||
continue
|
||||
}
|
||||
let keys: HashSet<KeyCode> = get_keys_pressed();
|
||||
if checking_for_choice
|
||||
{
|
||||
for key in &keys
|
||||
{
|
||||
let keycode = *key as u16;
|
||||
let length: u16 = data.choices.len() as u16;
|
||||
println!("key: {key:?} {keycode}");
|
||||
if keycode > 48 && keycode <= length+48
|
||||
{
|
||||
checking_for_choice = false;
|
||||
println!("Sending POST: {}",keycode-49);
|
||||
let value = keycode - 49;
|
||||
send_choice(value);
|
||||
}
|
||||
else { continue }
|
||||
}
|
||||
}
|
||||
// Get the next character
|
||||
data = next_happening();
|
||||
let character_name: String = data.character.to_lowercase();
|
||||
// Add the character to the HashMap if it's not already
|
||||
if character_name != "" && !characters.contains_key(&character_name)
|
||||
{
|
||||
println!("Fetching {character_name}");
|
||||
let new_character = get_character(&character_name).await;
|
||||
characters.insert(character_name.clone(), new_character);
|
||||
}
|
||||
// Matchbox for all the commands
|
||||
match data.action_type.to_lowercase().as_str()
|
||||
{
|
||||
"choice" =>
|
||||
{
|
||||
for (index, choice) in data.choices.iter().enumerate()
|
||||
{
|
||||
text += format!("\n{}. {}",index+1, choice).as_str();
|
||||
}
|
||||
checking_for_choice = true;
|
||||
},
|
||||
"output" =>
|
||||
{
|
||||
println!("SAYING");
|
||||
text = format!("{}: {}", characters[&character_name].0.name.clone(),wrap_text(&data.content).as_str());
|
||||
},
|
||||
"to" =>
|
||||
{
|
||||
let position = POSITIONS.get(&data.content).cloned().unwrap();
|
||||
let texture = &characters[&character_name].1;
|
||||
textures.insert(character_name.clone(),(texture.clone(), position[0], position[1], WHITE)); // Heavy
|
||||
}
|
||||
"begin" => (),
|
||||
"end" => exit(0),
|
||||
_ => println!("Unknown action, {}", data.action_type),
|
||||
}
|
||||
next_frame().await;
|
||||
}
|
||||
}
|
||||
|
||||
fn wrap_text(text: &str) -> String
|
||||
{
|
||||
text.chars()
|
||||
.collect::<Vec<_>>()
|
||||
.chunks(30)
|
||||
.map(|chunk| chunk.iter().collect::<String>())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
}
|
||||
|
||||
fn next_happening()
|
||||
-> Data
|
||||
{
|
||||
let data: Data = reqwest::blocking::get(format!("http://127.0.0.1:20264/happening")).unwrap().json().unwrap();
|
||||
println!("{data:?}");
|
||||
data
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn send_choice(index: u16)
|
||||
{
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
let res = client.post("http://localhost:20264/choice")
|
||||
.json(&index)
|
||||
.send()
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn get_character(name: &str)
|
||||
-> (Character, Texture2D)
|
||||
{
|
||||
let character: Character = reqwest::blocking::get(format!("http://127.0.0.1:20264/character/{name}")).unwrap().json().unwrap();
|
||||
println!("{character:?}");
|
||||
let skin_colour = character.skin_color.clone();
|
||||
let skin: Color = Color::from_rgba(skin_colour.red,skin_colour.green,skin_colour.blue,255);
|
||||
let hair_colour = character.hair_color.clone();
|
||||
let hair: Color = Color::from_rgba(hair_colour.red,hair_colour.green,hair_colour.blue,255);
|
||||
let head_path: String = format!("/home/deadvey/.local/share/happening/images/head/{}.png",character.head_shape);
|
||||
let head: Texture2D = change_colour(&mut load_image(head_path.as_str()).await.unwrap(), &skin, &hair);
|
||||
(character, head)
|
||||
}
|
||||
|
||||
fn change_colour(image: &mut Image, skin: &Color, hair: &Color)
|
||||
-> Texture2D
|
||||
{
|
||||
let target = Color::from_rgba(255,0,255,255);
|
||||
for y in 0..image.height() {
|
||||
for x in 0..image.width() {
|
||||
let pixel = image.get_pixel(x as u32, y as u32);
|
||||
if pixel == target {
|
||||
image.set_pixel(x as u32, y as u32, *skin);
|
||||
}
|
||||
}
|
||||
}
|
||||
let target = Color::from_rgba(0,255,255,255);
|
||||
for y in 0..image.height() {
|
||||
for x in 0..image.width() {
|
||||
let pixel = image.get_pixel(x as u32, y as u32);
|
||||
if pixel == target {
|
||||
image.set_pixel(x as u32, y as u32, *hair);
|
||||
}
|
||||
}
|
||||
}
|
||||
Texture2D::from_image(&image)
|
||||
}
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
killall happening-server
|
||||
RUST_LOG=debug
|
||||
happening-server $XDG_DATA_HOME/happening/stories/$1 &
|
||||
cargo run .
|
||||
@@ -0,0 +1,75 @@
|
||||
# Characters
|
||||
## Character features
|
||||
|
||||
| Feature | Examples | Notes |
|
||||
| --------------- | --------------------------------------------- | ----------------------------------------- |
|
||||
| Name | Timothy Sharpshooter | |
|
||||
| Gender | Male, Female, NB, Other | |
|
||||
| Skin Color | [255,255,255] | RGB colour |
|
||||
| Eye Color | [14,0,244] | RGB Color |
|
||||
| Hair Color | [0,128,44] | RGB Color |
|
||||
| Pronoun Subject | He, She, They | See https://en.wikipedia.org/wiki/Pronoun |
|
||||
| Pronoun Object | Him, Her, Them | See https://en.wikipedia.org/wiki/Pronoun |
|
||||
| Pronoun Deppos | His, Her, Their | See https://en.wikipedia.org/wiki/Pronoun |
|
||||
| Pronoun Indpos | His, Hers, Theirs | See https://en.wikipedia.org/wiki/Pronoun |
|
||||
| Pronoun Reflex | Himself, Herself, Themselves | See https://en.wikipedia.org/wiki/Pronoun |
|
||||
| Head Shape | | |
|
||||
| Torso Shape | | |
|
||||
| Arm Shape | | |
|
||||
| Leg Shape | | |
|
||||
| Hair Style | | |
|
||||
| Clothing | See [Character Clothing](##Clothing) | |
|
||||
|
||||
> [!NOTE]
|
||||
> Pronouns can be implied and unrequired with a recognised Gender value, however, custom values can be filled in if desired.
|
||||
|
||||
## Clothing
|
||||
|
||||
| Appearal | Examples | Notes |
|
||||
| -------- | -------- | ------------------------ |
|
||||
| Top | | Shirt, Jumper, etc... |
|
||||
| Bottom | | Trousers, tights, etc... |
|
||||
| Shoes | | |
|
||||
| Hat | | |
|
||||
| Gloves | | |
|
||||
| Neck | | Necklace, Scarf, etc... |
|
||||
|
||||
## Characters.json
|
||||
|
||||
Characters are stored in the characters.json file which looks like this:
|
||||
|
||||
```
|
||||
{
|
||||
"tim": {
|
||||
"name": "Timothy Sharpshooter",
|
||||
"gender": "Male",
|
||||
"skin_color": "",
|
||||
"eye_color": "",
|
||||
"hair_color": "",
|
||||
"pronoun_subject": "He",
|
||||
"pronoun_object": "Him",
|
||||
"pronoun_deppos": "His",
|
||||
"pronoun_indpos": "His",
|
||||
"pronoun_reflex": "Himself",
|
||||
"head_shape": "",
|
||||
"hair_style": "",
|
||||
"torso_shape": "",
|
||||
"arm_shape": "",
|
||||
"leg_shape": "",
|
||||
"clothing": {
|
||||
"top": "",
|
||||
"bottom": "",
|
||||
"shoes": "",
|
||||
"hat": "",
|
||||
"gloves": "",
|
||||
"neck": ""
|
||||
}
|
||||
},
|
||||
"bob": {
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> MUST be valid JSON or it can't be deserialised.
|
||||
@@ -0,0 +1,34 @@
|
||||
# Making a Story
|
||||
|
||||
## Setting up
|
||||
|
||||
You need these three files to make a story:
|
||||
|
||||
- about.json
|
||||
|
||||
- story.ha
|
||||
|
||||
- characters.json
|
||||
|
||||
## Filling out the files
|
||||
|
||||
Inside about.json should be the following content:
|
||||
|
||||
```
|
||||
{
|
||||
"title": "My Great Story",
|
||||
"description": "please read!"
|
||||
}
|
||||
```
|
||||
|
||||
Change the title and description to whatever you would like.<br/>
|
||||
Inside story.ha should be the code for your story, see [the documentation on coding](/docs/SYNTAX.md) for help with this.<br/>
|
||||
Finally, inside characters.json should be a JSON formatted object containing all your characters. See [the documentation on characters](/docs/CHARACTERS.md) for help with this file.<br/>
|
||||
|
||||
## Making a story file
|
||||
|
||||
A story file is just a zip file of these files, so zip up story.ha, characters.json and about.json into a zip file (make sure these are in the root of the zip file).
|
||||
|
||||
## Playing your story
|
||||
|
||||
To play your story, simply run cargo run \<your zip file\>
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
# Syntax
|
||||
|
||||
## Setup
|
||||
|
||||
This is done in the about.json file,
|
||||
|
||||
```
|
||||
{
|
||||
|
||||
"title": "My Great Story",
|
||||
|
||||
"description": "please read!"
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
## Characters
|
||||
See [Character documentation](/docs/CHARACTER.md) for more info
|
||||
Referencing a character using the @, @NARRATOR is reserved for the Narrator.<br/>
|
||||
Customisation is done with @CHARACTER change \<feature\> \<feature name\><br/>
|
||||
Move a character with @CHARACTER to fr
|
||||
Outputting can be done with @CHARACTER says "string", see more at (outputs)[##outputs]
|
||||
|
||||
> [!NOTE]
|
||||
> fr means front-right for instance.
|
||||
|
||||
## Variables
|
||||
Currently only strings and integers are supported as variable types (booleans are planned).<br/>
|
||||
The interpreter can assume the data type so this doesn't need to be specified.<br/>
|
||||
To assign new variables (or overwrite existing ones):
|
||||
```
|
||||
$x = 1
|
||||
$an_integer = 3
|
||||
$y = "hello"
|
||||
$a_string = "world"
|
||||
```
|
||||
> [!NOTE]
|
||||
> Variable names can be as long as you want and can contain any characters except whitespaces
|
||||
|
||||
To modify existing variables:
|
||||
```
|
||||
$x + 1 // Adds 1 to an integer x
|
||||
$x - 1 // Subtracts 1 from an integer x
|
||||
$y + " world" // Appends " world" to the end of a string y
|
||||
```
|
||||
|
||||
Other uses:
|
||||
```
|
||||
$x = choice "choice 1" { // Assigns to x the choice made by the client
|
||||
...
|
||||
$x = input // Assigns to x the string input made by the client
|
||||
```
|
||||
|
||||
## Outputs
|
||||
|
||||
```
|
||||
@CHARACTER says "this string
|
||||
is multi-line
|
||||
and ends with a"
|
||||
|
||||
$x = "hello world"
|
||||
@CHARACTER says $x
|
||||
|
||||
$name = "deadvey"
|
||||
@CHARACTER says "hello $name"
|
||||
```
|
||||
> [!NOTE]
|
||||
> Strings only support double quotes now ("") and do not support having quotes within quotes.
|
||||
|
||||
## Variables
|
||||
|
||||
Variables are referenced with the \$, only integers will be supported.<br/>
|
||||
|
||||
## Selection
|
||||
|
||||
Condition based:
|
||||
|
||||
```
|
||||
if condition {
|
||||
|
||||
}
|
||||
|
||||
elif condition { // Only gets checked if the if and all previous elif statements failed
|
||||
|
||||
}
|
||||
|
||||
else // Always passes if all previous if and elif statements failed
|
||||
|
||||
}
|
||||
```
|
||||
> [!NOTE]
|
||||
> See [conditions](##conditions)
|
||||
|
||||
Choice based:
|
||||
|
||||
```
|
||||
choice "choice 1" {
|
||||
|
||||
}
|
||||
|
||||
or "choice 2" {
|
||||
|
||||
}
|
||||
|
||||
or "choice 3" {
|
||||
|
||||
}
|
||||
```
|
||||
You can assign a variable to the result of a choice by doing the following:
|
||||
```
|
||||
$x = choice "choice 1" {
|
||||
...
|
||||
```
|
||||
|
||||
## Positioning
|
||||
|
||||
```
|
||||
@CHARACTER to position
|
||||
|
||||
PAN position
|
||||
```
|
||||
|
||||
## Conditions
|
||||
A condition works works like this:
|
||||
```
|
||||
$x == "hello" // evaluates true only if x is "hello"
|
||||
$x > 1 // evaulates true if x is an integer and is more than 1
|
||||
$x < 1 // evaluates true if x is an integer and is less than 1
|
||||
$x >= 1 // evaluates true if x is an integer and is more than or equal to 1
|
||||
$x <= 1 // evaluates true if x is an integer and is less than or equal to 1
|
||||
|
||||
```
|
||||
> [!NOTE]
|
||||
> == can be used for integers or strings whereas the rest can only be used with integers
|
||||
|
||||
The order (variable operator value) is currently fixed and so must be layed out like this.
|
||||
|
||||
## Other
|
||||
|
||||
```
|
||||
label:
|
||||
|
||||
GOTO label
|
||||
```
|
||||
|
||||
## Ending
|
||||
|
||||
`END` to exit out of the story
|
||||
|
||||
Generated
+13
-1
@@ -457,11 +457,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "happening-server"
|
||||
version = "0.0.2"
|
||||
version = "0.0.3"
|
||||
dependencies = [
|
||||
"env_logger",
|
||||
"log",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde-patch",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"warp",
|
||||
@@ -969,6 +971,16 @@ dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde-patch"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ba8dcbff6509fa9394810d943e0e9d486a4256c23f6fcea8a58865fbe8260c4"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.228"
|
||||
|
||||
+3
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "happening-server"
|
||||
version = "0.0.2"
|
||||
version = "0.0.3"
|
||||
edition = "2024"
|
||||
license = "GPL-3"
|
||||
repository = "https://git.javalsai.tuxcord.net/deadvey/happening/"
|
||||
@@ -25,7 +25,9 @@ unwrap_used = "warn"
|
||||
[dependencies]
|
||||
env_logger = "0.11.10"
|
||||
log = "0.4.29"
|
||||
regex = "1.12.3"
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde-patch = "0.2.3"
|
||||
serde_json = "1.0.149"
|
||||
tokio = { version = "1.51.0", features = ["rt-multi-thread","macros"] }
|
||||
warp = { version = "0.4.2", features = ["server"] }
|
||||
|
||||
+52
-18
@@ -3,11 +3,13 @@ use crate::
|
||||
{
|
||||
// internal code
|
||||
character,
|
||||
UnwrapOrExit,
|
||||
// libraries
|
||||
json,
|
||||
HashMap,
|
||||
Arc,
|
||||
Mutex,
|
||||
VecDeque,
|
||||
config,
|
||||
mpsc::Sender,
|
||||
info,
|
||||
@@ -17,7 +19,7 @@ use crate::
|
||||
Deserialize,
|
||||
};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
|
||||
pub struct DataToSend {
|
||||
pub action_type: String,
|
||||
pub content: String,
|
||||
@@ -30,30 +32,28 @@ pub struct DataToSend {
|
||||
// tx to allow the program executor to move onto the next bit of code
|
||||
pub async fn api_process
|
||||
(
|
||||
data_to_send: Arc<Mutex<DataToSend>>,
|
||||
happening_queue: Arc<Mutex<VecDeque<DataToSend>>>,
|
||||
characters: Arc<Mutex<HashMap::<String,character::Character>>>,
|
||||
tx: Sender<(bool, usize)>,
|
||||
tx: Sender<(usize, String)>,
|
||||
)
|
||||
{
|
||||
// This data must be passed through to the api route in order to be used
|
||||
let data_filter = warp::any().map(move || Arc::clone(&data_to_send));
|
||||
let happening_queue_filter = warp::any().map(move || Arc::clone(&happening_queue));
|
||||
let characters_filter = warp::any().map(move || Arc::clone(&characters));
|
||||
let tx_filter = warp::any().map(move || tx.clone());
|
||||
let tx_filter2 = tx_filter.clone();
|
||||
|
||||
info!("Running server");
|
||||
let tx_filter1 = warp::any().map(move || tx.clone());
|
||||
let tx_filter2 = tx_filter1.clone();
|
||||
|
||||
// The server route is loaded at address:port/happening
|
||||
let main = warp::path("happening")
|
||||
.and(warp::get())
|
||||
.and(data_filter)
|
||||
.and(tx_filter)
|
||||
.and(happening_queue_filter)
|
||||
// Perform this code on a GET request
|
||||
.map(|state: Arc<Mutex<DataToSend>>, tx_handle: Sender<(bool,usize)>|
|
||||
.map(|queue: Arc<Mutex<VecDeque<DataToSend>>>|
|
||||
{
|
||||
debug!("GET: {state:?}");
|
||||
let reply = state.as_ref();
|
||||
let _ = tx_handle.send((true,0));
|
||||
//debug!("GET: {state:?}");
|
||||
let mut queue = queue.lock().unwrap_or_exit("Queue Mutex was poisoned", 2);
|
||||
let reply = queue.pop_front().unwrap_or_default();
|
||||
drop(queue);
|
||||
warp::reply::json(&reply) // Send the reply data (data_to_send formatted as JSON)
|
||||
}).boxed();
|
||||
let characters = warp::path("character")
|
||||
@@ -87,17 +87,51 @@ pub async fn api_process
|
||||
let choice = warp::path("choice")
|
||||
.and(warp::post())
|
||||
.and(warp::body::json())
|
||||
.and(tx_filter2)
|
||||
.map(|index: usize, tx_handle: Sender<(bool,usize)>| {
|
||||
.and(tx_filter1)
|
||||
.map(|index: usize, tx_handle: Sender<(usize,String)>| {
|
||||
debug!("Choice: {index}");
|
||||
let _ = tx_handle.send((true,index));
|
||||
let _ = tx_handle.send((index,String::new()));
|
||||
let reply = "ack";
|
||||
warp::reply::json(&reply)
|
||||
}).boxed();
|
||||
let input = warp::path("input")
|
||||
.and(warp::post())
|
||||
.and(warp::body::json())
|
||||
.and(tx_filter2)
|
||||
.map(|input: String, tx_handle: Sender<(usize, String)>|
|
||||
{
|
||||
let _ = tx_handle.send((0,input));
|
||||
let reply = "ack";
|
||||
warp::reply::json(&reply)
|
||||
}).boxed();
|
||||
|
||||
let routes = main.or(characters).or(choice);
|
||||
let routes = main.or(characters).or(choice).or(input);
|
||||
// Start the server
|
||||
info!("Running server");
|
||||
warp::serve(routes)
|
||||
.run(([127, 0, 0, 1],config::API_PORT))
|
||||
.await;
|
||||
}
|
||||
|
||||
// On fail, quit safely
|
||||
// If successful, return nothing
|
||||
pub fn modify_data // TODO rename
|
||||
(
|
||||
happening_queue: &Arc<Mutex<VecDeque<DataToSend>>>,
|
||||
action_type: String,
|
||||
content: String,
|
||||
character: String,
|
||||
choices: Vec<String>,
|
||||
)
|
||||
{
|
||||
let mut queue = happening_queue.lock().unwrap_or_exit("Data to send Mutex was poisoned",2);
|
||||
let new_data = DataToSend {
|
||||
action_type,
|
||||
content,
|
||||
character,
|
||||
choices,
|
||||
};
|
||||
debug!("{new_data:?}");
|
||||
queue.push_back(new_data);
|
||||
drop(queue);
|
||||
}
|
||||
|
||||
+40
-35
@@ -4,56 +4,57 @@ use crate::{
|
||||
File,
|
||||
HashMap,
|
||||
debug,
|
||||
info,
|
||||
Deserialize,
|
||||
Serialize,
|
||||
apply_mut,
|
||||
Mutex,
|
||||
Arc,
|
||||
};
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
|
||||
#[serde(default)]
|
||||
pub struct Character
|
||||
{
|
||||
name: String,
|
||||
gender: String,
|
||||
eye_color: String,
|
||||
eye_color: Colour,
|
||||
hair_color: Colour,
|
||||
skin_color: Colour,
|
||||
pronoun_subject: String,
|
||||
pronoun_object: String,
|
||||
pronoun_deppos: String,
|
||||
pronoun_indpos: String,
|
||||
pronoun_reflex: String,
|
||||
head: String,
|
||||
hair: String,
|
||||
torso: String,
|
||||
arm: String,
|
||||
leg: String,
|
||||
hair_color: String,
|
||||
top_clothing: String,
|
||||
bottom_clothing: String,
|
||||
head_shape: String,
|
||||
hair_style: String,
|
||||
torso_shape: String,
|
||||
arm_shape: String,
|
||||
leg_shape: String,
|
||||
clothing: Clothing,
|
||||
}
|
||||
#[derive(Debug,Deserialize,Serialize,Clone,Default)]
|
||||
#[serde(default)]
|
||||
pub struct Clothing
|
||||
{
|
||||
top: String,
|
||||
bottom: String,
|
||||
shoes: String,
|
||||
}
|
||||
#[derive(Debug,Deserialize,Serialize,Clone,Default)]
|
||||
#[serde(default)]
|
||||
pub struct Colour
|
||||
{
|
||||
red: u8,
|
||||
green: u8,
|
||||
blue: u8,
|
||||
}
|
||||
|
||||
impl Character {
|
||||
pub fn set_field(&mut self, field: &str, value: &str) -> Result<(), ()> {
|
||||
match field {
|
||||
"name" => self.name = value.to_string(),
|
||||
"gender" => self.gender = value.to_string(),
|
||||
"eye_color" => self.eye_color = value.to_string(),
|
||||
"pronoun_subject" => self.pronoun_subject = value.to_string(),
|
||||
"pronoun_object" => self.pronoun_object = value.to_string(),
|
||||
"pronoun_deppos" => self.pronoun_deppos = value.to_string(),
|
||||
"pronoun_indpos" => self.pronoun_indpos = value.to_string(),
|
||||
"pronoun_reflex" => self.pronoun_reflex = value.to_string(),
|
||||
"head" => self.head = value.to_string(),
|
||||
"hair" => self.hair = value.to_string(),
|
||||
"torso" => self.torso = value.to_string(),
|
||||
"arm" => self.arm = value.to_string(),
|
||||
"leg" => self.leg = value.to_string(),
|
||||
"hair_color" => self.hair_color = value.to_string(),
|
||||
"top_clothing" => self.top_clothing = value.to_string(),
|
||||
"bottom_clothing" => self.bottom_clothing = value.to_string(),
|
||||
"shoes" => self.shoes = value.to_string(),
|
||||
|
||||
_ => return Err(()),
|
||||
}
|
||||
|
||||
// Big ass ugly match case
|
||||
pub fn set_field(&mut self, field: &str, value: &str) -> Result<(), String> {
|
||||
let patch = format!("{{ \"{field}\": \"{value}\" }}");
|
||||
apply_mut(self, patch)
|
||||
.map_err(|_| "Invalid field".to_string())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -70,8 +71,12 @@ pub fn character_parse(archive: &mut ZipArchive<File>)
|
||||
|
||||
// Serialise this to a HashMap
|
||||
let characters: HashMap<String, Character> =
|
||||
serde_json::from_str(&file_contents)
|
||||
.map_err (|err| format!("Invalid JSON in characters.json: {err}"))?;
|
||||
serde_json::from_str::<HashMap<String,Character>>(&file_contents)
|
||||
.map_err (|err| format!("Invalid JSON in characters.json: {err}"))?
|
||||
.into_iter()
|
||||
.map(|(k,v)| (k.to_lowercase(), v))
|
||||
.collect();
|
||||
info!("Parsed characters from characters.json");
|
||||
debug!("{characters:?}");
|
||||
Ok(Arc::new(Mutex::new(characters)))
|
||||
}
|
||||
|
||||
+38
-44
@@ -3,6 +3,7 @@ mod character;
|
||||
mod config;
|
||||
mod api;
|
||||
mod traits;
|
||||
mod tokenise;
|
||||
|
||||
use std::
|
||||
{
|
||||
@@ -10,7 +11,11 @@ use std::
|
||||
env::args,
|
||||
fs::File,
|
||||
io::Read,
|
||||
collections::HashMap,
|
||||
collections::
|
||||
{
|
||||
HashMap,
|
||||
VecDeque,
|
||||
},
|
||||
sync::{Arc, Mutex, mpsc},
|
||||
};
|
||||
use log::
|
||||
@@ -26,6 +31,7 @@ use serde::
|
||||
Serialize
|
||||
};
|
||||
use serde_json::json;
|
||||
use serde_patch::apply_mut;
|
||||
use zip::
|
||||
{
|
||||
ZipArchive,
|
||||
@@ -34,6 +40,7 @@ use crate::
|
||||
{
|
||||
traits::UnwrapOrExit,
|
||||
};
|
||||
use regex::Regex;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main()
|
||||
@@ -44,23 +51,15 @@ async fn main()
|
||||
let (tx,rx) = mpsc::channel();
|
||||
// Unzip zip archive to get data for story
|
||||
let file_name = args().nth(1) // Get filename from arguments
|
||||
.unwrap_or_else
|
||||
.unwrap_or_else // TODO unwrap or exit
|
||||
(|| {
|
||||
error!("No filename specified");
|
||||
exit(10);
|
||||
});
|
||||
let file = File::open(format!("../stories/{file_name}")) // Get the file
|
||||
.unwrap_or_else
|
||||
(|err| {
|
||||
error!("Failed to open file: {err}");
|
||||
exit(11);
|
||||
});
|
||||
let file = File::open(file_name) // Get the file
|
||||
.unwrap_or_exit("Failed to read file.", 11);
|
||||
let mut archive = ZipArchive::new(file) // Open the archive
|
||||
.unwrap_or_else
|
||||
(|err| {
|
||||
error!("Failed to open archive: {err}");
|
||||
exit(12);
|
||||
});
|
||||
.unwrap_or_exit("Failed to open archive", 12);
|
||||
// Setup the characters hashmap which will store each character in it as a Character struct
|
||||
let characters = match character::character_parse(&mut archive)
|
||||
{
|
||||
@@ -76,36 +75,33 @@ async fn main()
|
||||
},
|
||||
};
|
||||
// Initialise the data strcut that will be sent out during API GET requests
|
||||
let data_to_send = Arc::new(Mutex::new(api::DataToSend
|
||||
{
|
||||
let happening_stack = Arc::new(Mutex::new(
|
||||
VecDeque::from([api::DataToSend{
|
||||
action_type: "begin".to_owned(),
|
||||
content: String::new(),
|
||||
content: String::new(), // TODO send title and description
|
||||
character: String::new(),
|
||||
choices: vec![],
|
||||
}));
|
||||
}])
|
||||
));
|
||||
|
||||
// setup the api stuff //
|
||||
|
||||
// Make clones of the data Arc for the two processes
|
||||
let data_clone1 = Arc::clone(&data_to_send);
|
||||
//let data_clone1 = Arc::clone(&data_to_send);
|
||||
let happening_stack1 = Arc::clone(&happening_stack);
|
||||
let characters_clone1 = Arc::clone(&characters);
|
||||
let tx_clone = tx;
|
||||
// Spawn a thread for warp api server
|
||||
tokio::spawn(
|
||||
async move {
|
||||
api::api_process(data_clone1, characters_clone1, tx_clone).await;
|
||||
api::api_process(happening_stack1, characters_clone1, tx_clone).await;
|
||||
});
|
||||
|
||||
|
||||
// setup the parsing stuff //
|
||||
// Read the file and split it into tokens
|
||||
// file read from a zip file /story.ha
|
||||
// file read relative to current directory
|
||||
let mut story_file = archive.by_name("story.ha")
|
||||
.unwrap_or_else
|
||||
(|err| {
|
||||
error!("Unable to read story file: {err}");
|
||||
exit(14);
|
||||
});
|
||||
.unwrap_or_exit("Unable to read story file", 14);
|
||||
let mut file_contents = String::new();
|
||||
story_file.read_to_string(&mut file_contents)
|
||||
.unwrap_or_else
|
||||
@@ -113,32 +109,30 @@ async fn main()
|
||||
error!("Unable to read story file to string: {err}");
|
||||
exit(14);
|
||||
});
|
||||
// Tokenise story file
|
||||
let tokens: Vec<&str> = file_contents
|
||||
.split_whitespace()
|
||||
.collect();
|
||||
if ! tokens.contains(&"END")
|
||||
{
|
||||
warn!("No END statement, story may exit unexpectedly");
|
||||
}
|
||||
debug!("{tokens:?}");
|
||||
|
||||
let data_clone2 = Arc::clone(&data_to_send);
|
||||
// Tokenise the file
|
||||
let (tokens, labels) = tokenise::tokenise(&file_contents)
|
||||
.unwrap_or_exit("Unable to tokenise data", 15);
|
||||
// TESTING
|
||||
//debug!("{tokens:?}\n{labels:?}");
|
||||
for (index,token) in tokens.iter().enumerate()
|
||||
{
|
||||
debug!("{index}: {token:?}");
|
||||
}
|
||||
|
||||
// Run the syntactic parser
|
||||
let characters_clone2 = Arc::clone(&characters);
|
||||
let happening_stack2 = Arc::clone(&happening_stack);
|
||||
// Run the parsing process for the DSL
|
||||
match parsing::token_parse(&tokens, &characters_clone2, &data_clone2, &rx)
|
||||
info!("DSL parsing begun");
|
||||
match parsing::token_parse(&tokens, &labels, &characters_clone2, &happening_stack2, &rx)
|
||||
{
|
||||
// Exit with error or success
|
||||
Ok(()) =>
|
||||
{
|
||||
api::modify_data(&happening_stack, "end".to_string(), String::new(), String::new(), vec![]);
|
||||
let _ = rx.recv();
|
||||
info!("Program exited successfully");
|
||||
let mut data = data_to_send.lock().unwrap_or_exit("Data to send Mutex was poisoned",2); // TODO test
|
||||
data.action_type = String::from("end");
|
||||
data.content = String::new();
|
||||
data.character = String::new();
|
||||
// Manually unlock the mutex
|
||||
std::mem::drop(data);
|
||||
let _ = rx.recv(); // Wait for the client to respond
|
||||
exit(0);
|
||||
},
|
||||
Err(error) =>
|
||||
|
||||
+45
-113
@@ -1,55 +1,66 @@
|
||||
use std::collections::HashMap;
|
||||
use crate::
|
||||
{
|
||||
// Internal code
|
||||
character,
|
||||
api,
|
||||
tokenise,
|
||||
|
||||
// Libraries
|
||||
mpsc::Receiver,
|
||||
Arc,
|
||||
Mutex,
|
||||
VecDeque,
|
||||
HashMap,
|
||||
info,
|
||||
debug,
|
||||
warn,
|
||||
UnwrapOrExit,
|
||||
};
|
||||
|
||||
mod strings;
|
||||
mod character_parse;
|
||||
mod keyword_parse;
|
||||
mod identifier_parse;
|
||||
|
||||
|
||||
// Parse the tokens in a file
|
||||
// Returns success or an error string
|
||||
pub fn token_parse(
|
||||
tokens: &Vec<&str>,
|
||||
tokens: &[tokenise::Token],
|
||||
labels: &HashMap<String,usize>,
|
||||
characters: &Arc<Mutex<HashMap::<String, character::Character>>>,
|
||||
data_to_send: &Arc<Mutex<api::DataToSend>>,
|
||||
rx: &Receiver<(bool,usize)>,
|
||||
happening_queue: &Arc<Mutex<VecDeque<api::DataToSend>>>,
|
||||
rx: &Receiver<(usize,String)>,
|
||||
) -> Result<(),String>
|
||||
{
|
||||
info!("DSL parsing begun");
|
||||
let mut index: usize = 0;
|
||||
if rx.recv().is_err()
|
||||
{
|
||||
warn!("Some issue with api");
|
||||
// TOD eh?
|
||||
}
|
||||
let mut variables: HashMap<String, tokenise::Value> = HashMap::new();
|
||||
info!("Client has connected");
|
||||
// Run an infinite loop
|
||||
'parse_loop: loop
|
||||
loop
|
||||
{
|
||||
debug!("Reading {index}");
|
||||
// Get the next token
|
||||
let token = tokens
|
||||
.get(index)
|
||||
.ok_or_else(|| "File unexpectedly reached termination point".to_string())?;
|
||||
debug!("{index}: {token}");
|
||||
// The instructions are related to characters
|
||||
if token.starts_with('@')
|
||||
match tokens.get(index)
|
||||
{
|
||||
let character_name: String = token.chars().skip(1).collect();
|
||||
debug!("Doing something with a character: {character_name}");
|
||||
// The index is incremented to after the character's instructions
|
||||
index = match character_parse::character_parse(index+1, tokens, character_name, characters, data_to_send)
|
||||
// Keyword, eg IF, CHOICE or GOTO
|
||||
Some(tokenise::Token::Keyword(token)) =>
|
||||
{
|
||||
if token.to_lowercase().as_str() == "end" { return Ok(()); }
|
||||
index = keyword_parse::keyword_parse(tokens, token, index, happening_queue, labels, &mut variables, rx)?;
|
||||
},
|
||||
// Ignore closing braces and jump over opening brace blocks
|
||||
Some(tokenise::Token::Bracket((bracket,new_index))) =>
|
||||
{
|
||||
if bracket == &tokenise::Bracket::Closing { index += 1; }
|
||||
else
|
||||
{
|
||||
warn!("Unexpected brace block, jumping over...");
|
||||
index = new_index + 1;
|
||||
}
|
||||
},
|
||||
// Handle a character
|
||||
Some(tokenise::Token::Character(character_name)) =>
|
||||
{
|
||||
index = match character_parse::character_parse(index+1,tokens,character_name.clone(),characters,happening_queue,&variables)
|
||||
{
|
||||
Ok(increment) => increment,
|
||||
Err((err,increment)) =>
|
||||
@@ -59,106 +70,27 @@ pub fn token_parse(
|
||||
},
|
||||
};
|
||||
}
|
||||
// Miscelleneous instructions
|
||||
else
|
||||
// Identifier
|
||||
Some(tokenise::Token::Identifier(name)) =>
|
||||
{
|
||||
match token.to_lowercase().as_str()
|
||||
index = match identifier_parse::identifier_parse(index+1,name,tokens,&mut variables,rx,happening_queue)
|
||||
{
|
||||
"end" =>
|
||||
Ok((increment,_)) => increment,
|
||||
Err((err,increment)) =>
|
||||
{
|
||||
info!("END command, exiting");
|
||||
return Ok(()) // quit successfully
|
||||
warn!("{err}");
|
||||
increment
|
||||
},
|
||||
"choice" =>
|
||||
{
|
||||
let (_,jump_points) = match choice_parse(index+1, tokens, data_to_send)
|
||||
{
|
||||
Ok((increment,jump_point)) => (increment,jump_point),
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
if rx.recv().is_err() { warn!("Error sending choices to client"); }
|
||||
let (_, choice) = match rx.recv()
|
||||
{
|
||||
Ok((_,choice)) => (None::<bool>, choice),
|
||||
Err(err) =>
|
||||
{
|
||||
warn!("Error receiving choice from client, defaulting to choice 0 {err}");
|
||||
(None::<bool>, 0)
|
||||
}
|
||||
};
|
||||
index = jump_points[choice];
|
||||
info!("CHOICE command with {} choices",jump_points.len());
|
||||
debug!("{jump_points:?} {choice} {index}");
|
||||
continue 'parse_loop
|
||||
},
|
||||
"or" =>
|
||||
{
|
||||
info!("OR command, jumping over");
|
||||
index += match strings::closing_char(&tokens[index..], '{','}')
|
||||
{
|
||||
Some(index) => index,
|
||||
None => return Err(String::from("No closing brace")),
|
||||
};
|
||||
continue
|
||||
},
|
||||
"}" =>
|
||||
Some(tok) =>
|
||||
{
|
||||
warn!("Unexpected token, at index {index}, expected character, keyword or identifier, got {tok:?}");
|
||||
index += 1;
|
||||
continue
|
||||
},
|
||||
_ =>
|
||||
{
|
||||
warn!("Invalid command: {token}");
|
||||
}
|
||||
}
|
||||
}
|
||||
if rx.recv().is_err()
|
||||
{
|
||||
warn!("Some issue with api");
|
||||
None => return Err("File unexpectedly reached termination point".to_string()),
|
||||
}
|
||||
// The instructions are related to characters
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the options in a choice clause and returns the idexes of the code blocks
|
||||
fn choice_parse
|
||||
(
|
||||
index: usize,
|
||||
tokens: &[&str],
|
||||
data_to_send: &Arc<Mutex<api::DataToSend>>,
|
||||
) -> Result<(usize, Vec<usize>), String>
|
||||
{
|
||||
let mut sum_index: usize = index;
|
||||
let mut choices: Vec<String> = Vec::new();
|
||||
let mut choice_indeces: Vec<usize> = Vec::new();
|
||||
// Ensure the index is valid (the index is not beyond the vector)
|
||||
// Get the initial choice
|
||||
let (choice_string, counter) = strings::extract_quoted(&tokens[sum_index..])
|
||||
.ok_or_else(|| "No choice string".to_string())?;
|
||||
sum_index += counter;
|
||||
choices.push(choice_string);
|
||||
choice_indeces.push(sum_index+1);
|
||||
sum_index += strings::closing_char(&tokens[sum_index..], '{','}')
|
||||
.ok_or_else(|| "No closing brace".to_string())? + 1;
|
||||
// Find all the alternate choices labelled with OR
|
||||
// Fill out the choices vector with all the choice strings
|
||||
while tokens[sum_index].to_lowercase() == "or"
|
||||
{
|
||||
let (choice_string, counter) = strings::extract_quoted(&tokens[sum_index+1..])
|
||||
.ok_or_else(|| "No choice string".to_string())?;
|
||||
sum_index += counter;
|
||||
choices.push(choice_string);
|
||||
choice_indeces.push(sum_index+2);
|
||||
sum_index += strings::closing_char(&tokens[sum_index..], '{','}')
|
||||
.ok_or_else(|| "No closing brace".to_string())? + 1;
|
||||
}
|
||||
debug!("{choices:?}");
|
||||
// Send the choices to the Client via the API
|
||||
let mut data = data_to_send.lock().unwrap_or_exit("Data to send Mutex was poisoned",2);
|
||||
data.action_type = String::from("choice");
|
||||
data.content = String::new();
|
||||
data.character = String::new();
|
||||
data.choices = choices;
|
||||
drop(data);
|
||||
// Return the choice indeces
|
||||
Ok((sum_index + 1, choice_indeces))
|
||||
}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
use super::strings;
|
||||
use crate::
|
||||
{
|
||||
// Internal code
|
||||
character,
|
||||
api,
|
||||
tokenise,
|
||||
UnwrapOrExit,
|
||||
//Libs
|
||||
Mutex,
|
||||
Arc,
|
||||
HashMap,
|
||||
VecDeque,
|
||||
info,
|
||||
warn,
|
||||
debug,
|
||||
@@ -20,69 +21,66 @@ use crate::
|
||||
pub fn character_parse
|
||||
(
|
||||
index: usize,
|
||||
tokens: &Vec<&str>,
|
||||
tokens: &[tokenise::Token],
|
||||
character_name: String,
|
||||
characters: &Arc<Mutex<HashMap::<String, character::Character>>>,
|
||||
data_to_send: &Arc<Mutex<api::DataToSend>>,
|
||||
happening_queue: &Arc<Mutex<VecDeque<api::DataToSend>>>,
|
||||
variables: &HashMap<String, tokenise::Value>,
|
||||
) -> Result<usize,(String,usize)>
|
||||
{
|
||||
let mut sum_index: usize = index;
|
||||
let characters_hashmap = characters.lock().unwrap_or_exit("Characters Mutex was poisoned",2);
|
||||
if character_name.to_lowercase() != "narrator" && ! characters_hashmap.contains_key(&character_name)
|
||||
{
|
||||
return Err((format!("Character {character_name} does not exist"),sum_index + 1));
|
||||
}
|
||||
drop(characters_hashmap);
|
||||
// Ensure the index is valid (the index is not beyond the vector)
|
||||
let token = tokens
|
||||
.get(sum_index)
|
||||
.ok_or_else(|| ("File unexpectedly reached termination point".to_string(), sum_index))?;
|
||||
match token.to_lowercase().as_str()
|
||||
let keyword = tokenise::get_keyword_token(tokens, sum_index)
|
||||
.map_err(|err| (err, sum_index))?;
|
||||
match keyword.to_lowercase().as_str()
|
||||
{
|
||||
// The character is saying something, so grab the text and pass it
|
||||
// to the client
|
||||
"says" =>
|
||||
{
|
||||
info!("SAYS command with character {character_name}");
|
||||
match strings::extract_quoted(&tokens[sum_index+1..])
|
||||
{
|
||||
Some((output_string, counter)) =>
|
||||
{
|
||||
debug!("{output_string}");
|
||||
sum_index += counter;
|
||||
let mut data = data_to_send.lock().unwrap_or_exit("Data to send Mutex was poisoned", 2);
|
||||
data.action_type = String::from("output");
|
||||
data.content = output_string;
|
||||
data.character = character_name;
|
||||
data.choices = vec![];
|
||||
drop(data);
|
||||
},
|
||||
None => return Err(("Unable to read output string".to_string(), sum_index)),
|
||||
}
|
||||
sum_index += 1;
|
||||
let output = tokenise::get_string_token(tokens, sum_index, variables)
|
||||
.map_err(|err| (err, sum_index))?;
|
||||
debug!("Saying {output}");
|
||||
api::modify_data(happening_queue, "output".to_string(), output, character_name, vec![]);
|
||||
},
|
||||
// Change the property of the selected character eg @tim CHANGE name "Bill Buffins"
|
||||
// will change the character with ID tim to "Bill Buffins"; a character's ID cannot change
|
||||
"change" =>
|
||||
{
|
||||
sum_index += 1;
|
||||
let feature = tokens
|
||||
.get(sum_index)
|
||||
.ok_or_else(|| ("File unexpectedly reached termination point".to_string(), sum_index))?;
|
||||
let output_string: String;
|
||||
(output_string, sum_index) = match strings::extract_quoted(&tokens[sum_index+1..])
|
||||
{
|
||||
Some((string,counter)) => (string,sum_index+counter),
|
||||
None => return Err(("Unable to parse property to change character".to_string(),sum_index)),
|
||||
};
|
||||
let feature = tokenise::get_keyword_token(tokens, sum_index)
|
||||
.map_err(|err| (err, sum_index))?;
|
||||
sum_index += 1;
|
||||
let string = tokenise::get_string_token(tokens, sum_index,variables)
|
||||
.map_err(|err| (err, sum_index))?;
|
||||
info!("CHANGE command with character {character_name} feature {feature}");
|
||||
let mut characters = characters.lock().unwrap_or_exit("Character Mutex was poisoned",3);
|
||||
if let Some(character) = characters.get_mut(&character_name)
|
||||
&& character.set_field(feature, &output_string)
|
||||
&& character.set_field(&feature, &string)
|
||||
.is_err() { warn!("Feature {feature} does not exist") }
|
||||
drop(characters);
|
||||
let mut data = data_to_send.lock().unwrap_or_exit("Data to send Mutex was poisoned",2); // TODO eh?
|
||||
data.action_type = String::from("change");
|
||||
data.content = String::new();
|
||||
data.character = character_name;
|
||||
drop(data);
|
||||
api::modify_data(happening_queue, "change".to_string(), String::new(), character_name, vec![]);
|
||||
},
|
||||
// These two are mainly just actions performed by the frontend client, so just tell the client to move/animate
|
||||
// the character and not much other processing needed on the serverside
|
||||
"to"|"animate" =>
|
||||
{
|
||||
sum_index += 1;
|
||||
let content = tokenise::get_keyword_token(tokens, sum_index)
|
||||
.map_err(|err| (err, sum_index))?;
|
||||
api::modify_data(happening_queue, keyword.to_lowercase(), content, character_name, vec![]);
|
||||
},
|
||||
// Catch all condition, if the instruction is unrecognised as a
|
||||
// character command
|
||||
_ => return Err((format!("Invalid command: {token}"),sum_index)),
|
||||
_ => return Err((format!("Invalid command: {keyword}"),sum_index)),
|
||||
}
|
||||
sum_index += 1;
|
||||
Ok(sum_index)
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
use crate::
|
||||
{
|
||||
// Internal code
|
||||
tokenise,
|
||||
api,
|
||||
//Libs
|
||||
HashMap,
|
||||
Arc,
|
||||
Mutex,
|
||||
VecDeque,
|
||||
warn,
|
||||
debug,
|
||||
info,
|
||||
mpsc::Receiver,
|
||||
};
|
||||
use super::keyword_parse;
|
||||
|
||||
#[allow(unused_variables)]
|
||||
pub fn identifier_parse
|
||||
(
|
||||
index: usize,
|
||||
identifier: &String,
|
||||
tokens: &[tokenise::Token],
|
||||
variables: &mut HashMap<String, tokenise::Value>,
|
||||
rx: &Receiver<(usize,String)>,
|
||||
happening_queue: &Arc<Mutex<VecDeque<api::DataToSend>>>,
|
||||
) -> Result<(usize,bool),(String,usize)>
|
||||
{
|
||||
let mut sum_index: usize = index;
|
||||
let current = variables
|
||||
.entry(identifier.clone())
|
||||
.or_insert(tokenise::Value::Null)
|
||||
.clone();
|
||||
let operator = tokenise::get_operator_token(tokens, sum_index)
|
||||
.map_err(|err| (err, sum_index))?;
|
||||
sum_index += 1;
|
||||
let result: bool = match tokenise::get_value_token(tokens, sum_index)
|
||||
{
|
||||
// An value that can be directly assigned to or compared against the variable
|
||||
Ok(value) =>
|
||||
{
|
||||
sum_index += 1;
|
||||
operator_match(¤t,value,operator,identifier,variables)
|
||||
},
|
||||
// Another thing like a choice or an input
|
||||
Err(_) =>
|
||||
{
|
||||
if operator != tokenise::Operator::Assignment // Only assignment is valid here
|
||||
{
|
||||
return Err((format!("Unexpected operator: {operator:?} at index {}",sum_index-1),sum_index + 1))
|
||||
};
|
||||
let keyword = tokenise::get_keyword_token(tokens,sum_index)
|
||||
.map_err(|err| (err,sum_index))?;
|
||||
match keyword.to_lowercase().as_str()
|
||||
{
|
||||
"choice" =>
|
||||
{
|
||||
let choice: String;
|
||||
(sum_index, choice) = keyword_parse::choice_parse(tokens, index+1, happening_queue,rx,variables)
|
||||
.map_err(|err| (err,sum_index+1))?;
|
||||
variables.insert(identifier.to_owned(), tokenise::Value::String(choice));
|
||||
},
|
||||
"input" =>
|
||||
{
|
||||
api::modify_data(happening_queue, "input".to_string(), String::new(), String::new(), Vec::new());
|
||||
info!("Waiting for client input");
|
||||
let input = match rx.recv()
|
||||
{
|
||||
Ok((_,input)) => input,
|
||||
Err(err) =>
|
||||
{
|
||||
warn!("Error receiving input from client, defaulting to choice \"\" {err}");
|
||||
"".to_string()
|
||||
}
|
||||
};
|
||||
variables.insert(identifier.to_owned(), tokenise::Value::String(input));
|
||||
sum_index += 1;
|
||||
},
|
||||
_ =>
|
||||
{
|
||||
warn!("Unexpected keyword {keyword}");
|
||||
},
|
||||
}
|
||||
false
|
||||
},
|
||||
};
|
||||
debug!("{variables:?}");
|
||||
Ok((sum_index,result))
|
||||
}
|
||||
|
||||
fn operator_match
|
||||
(
|
||||
current: &tokenise::Value,
|
||||
value: tokenise::Value,
|
||||
operator: tokenise::Operator,
|
||||
identifier: &String,
|
||||
variables: &mut HashMap<String, tokenise::Value>
|
||||
)
|
||||
-> bool
|
||||
{
|
||||
// Operator match box
|
||||
match operator
|
||||
{
|
||||
// Changing a value
|
||||
tokenise::Operator::Assignment =>
|
||||
{
|
||||
variables.insert(identifier.to_owned(), value);
|
||||
} ,
|
||||
tokenise::Operator::Add =>
|
||||
{
|
||||
let result: tokenise::Value = match (value.clone(), current)
|
||||
{
|
||||
(tokenise::Value::Integer(int1),tokenise::Value::Integer(int2)) => tokenise::Value::Integer(int1 + int2),
|
||||
(tokenise::Value::String(str1),tokenise::Value::String(str2)) => tokenise::Value::String(format!("{str1}{str2}")),
|
||||
_ => value, // otherwise invalid
|
||||
|
||||
};
|
||||
variables.insert(identifier.to_owned(), result);
|
||||
},
|
||||
tokenise::Operator::Sub =>
|
||||
{
|
||||
let result: tokenise::Value = match (value.clone(), current)
|
||||
{
|
||||
(tokenise::Value::Integer(int1),tokenise::Value::Integer(int2)) => tokenise::Value::Integer(int2 - int1),
|
||||
_ => value, // otherwise invalid
|
||||
|
||||
};
|
||||
variables.insert(identifier.to_owned(), result);
|
||||
},
|
||||
// Comparisons, return a boolean
|
||||
tokenise::Operator::Comparison(comp) =>
|
||||
{
|
||||
let result = match (current, &value)
|
||||
{
|
||||
// Integer
|
||||
(tokenise::Value::Integer(current), tokenise::Value::Integer(comparing)) =>
|
||||
{
|
||||
match comp
|
||||
{
|
||||
tokenise::Comparison::Equate => current == comparing,
|
||||
tokenise::Comparison::Greater => current > comparing,
|
||||
tokenise::Comparison::Less => current < comparing,
|
||||
tokenise::Comparison::GreaterOrEqual => current >= comparing,
|
||||
tokenise::Comparison::LessOrEqual => current <= comparing,
|
||||
}
|
||||
},
|
||||
// String
|
||||
(tokenise::Value::String(current), tokenise::Value::String(comparing)) =>
|
||||
{
|
||||
match comp
|
||||
{
|
||||
tokenise::Comparison::Equate => current == comparing,
|
||||
tokenise::Comparison::Greater => current > comparing,
|
||||
tokenise::Comparison::Less => current < comparing,
|
||||
tokenise::Comparison::GreaterOrEqual => current >= comparing,
|
||||
tokenise::Comparison::LessOrEqual => current <= comparing,
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
warn!("Invalid comparison of {current:?} and {value:?}, evaluating false");
|
||||
false
|
||||
},
|
||||
};
|
||||
debug!("Comparison {current:?} comp {value:?} evaluates to {result}");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
use crate::
|
||||
{
|
||||
tokenise,
|
||||
api,
|
||||
|
||||
HashMap,
|
||||
Arc,
|
||||
Mutex,
|
||||
VecDeque,
|
||||
warn,
|
||||
debug,
|
||||
info,
|
||||
mpsc::Receiver,
|
||||
};
|
||||
use super::identifier_parse;
|
||||
|
||||
pub fn keyword_parse(
|
||||
tokens: &[tokenise::Token],
|
||||
token: &str,
|
||||
mut index: usize,
|
||||
happening_queue: &Arc<Mutex<VecDeque<api::DataToSend>>>,
|
||||
labels: &HashMap<String, usize>,
|
||||
variables: &mut HashMap<String, tokenise::Value>,
|
||||
rx: &Receiver<(usize,String)>,
|
||||
)
|
||||
-> Result<usize, String>
|
||||
{
|
||||
|
||||
match token.to_lowercase().as_str()
|
||||
{
|
||||
"choice" =>
|
||||
{
|
||||
(index,_) = choice_parse(tokens, index, happening_queue, rx,variables)?;
|
||||
},
|
||||
"if" =>
|
||||
{
|
||||
// TODO can this go in a function?
|
||||
let mut keyword = "if".to_string();
|
||||
while keyword == "if" || keyword == "elif" || keyword == "else" // TODO less beefy??
|
||||
{
|
||||
index += 1;
|
||||
let mut result: bool = true;
|
||||
if keyword != "else"
|
||||
{
|
||||
let identifier = tokenise::get_identifier_token(tokens, index)?;
|
||||
(index,result) = match identifier_parse::identifier_parse(index+1, &identifier, tokens, variables,rx,happening_queue)
|
||||
{
|
||||
Ok((increment, result)) => (increment,result),
|
||||
Err((err,increment)) =>
|
||||
{
|
||||
warn!("{err}");
|
||||
(increment,false)
|
||||
}
|
||||
}
|
||||
}
|
||||
if result { index += 1; break; }
|
||||
index = tokenise::get_closing_index(tokens,index)?;
|
||||
index += 1;
|
||||
keyword = match tokenise::get_keyword_token(tokens,index)
|
||||
{
|
||||
Ok(keyword) => keyword,
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
},
|
||||
"else" =>
|
||||
{
|
||||
index += 1;
|
||||
index = tokenise::get_closing_index(tokens, index)?;
|
||||
},
|
||||
"elif" =>
|
||||
{
|
||||
loop
|
||||
{
|
||||
index += 1;
|
||||
let Ok(new_index) = tokenise::get_closing_index(tokens, index)
|
||||
else { continue; };
|
||||
index = new_index;
|
||||
break
|
||||
}
|
||||
},
|
||||
"or" =>
|
||||
{
|
||||
info!("OR command, jumping over");
|
||||
index += 2;
|
||||
let new_index = tokenise::get_closing_index(tokens, index)?;
|
||||
index = new_index;
|
||||
},
|
||||
// Jump to a particular index based on a label eg GOTO character_check
|
||||
"goto" =>
|
||||
{
|
||||
info!("GOTO command, jumping there");
|
||||
index += 1;
|
||||
let label = tokenise::get_keyword_token(tokens, index)?;
|
||||
index = if let Some(label_index) = labels.get(&label) { *label_index }
|
||||
else
|
||||
{
|
||||
warn!("Label {label} does not exist");
|
||||
index + 1
|
||||
};
|
||||
debug!("Jumping to {index}");
|
||||
},
|
||||
"pan" =>
|
||||
{
|
||||
info!("PAN command, informing client");
|
||||
index += 1;
|
||||
let location = tokenise::get_keyword_token(tokens, index)?;
|
||||
api::modify_data(happening_queue, "pan".to_string(), location, String::new(), Vec::new());
|
||||
},
|
||||
_ =>
|
||||
{
|
||||
warn!("Invalid command: {token}, index {index}");
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
Ok(index)
|
||||
}
|
||||
|
||||
pub fn choice_parse
|
||||
(
|
||||
tokens: &[tokenise::Token],
|
||||
mut index: usize,
|
||||
happening_queue: &Arc<Mutex<VecDeque<api::DataToSend>>>,
|
||||
rx: &Receiver<(usize,String)>,
|
||||
variables: &HashMap<String,tokenise::Value>,
|
||||
)
|
||||
-> Result<(usize,String), String>
|
||||
{
|
||||
let mut next_token: String = "or".to_string();
|
||||
let mut choices: Vec<String> = Vec::new();
|
||||
let mut choice_indeces: Vec<usize> = Vec::new();
|
||||
while next_token == "or"
|
||||
{
|
||||
index += 1;
|
||||
choices.push
|
||||
(
|
||||
tokenise::get_string_token(tokens, index,variables)?
|
||||
);
|
||||
index += 1;
|
||||
choice_indeces.push(index+1);
|
||||
index = match tokenise::get_closing_index(tokens,index)
|
||||
{
|
||||
Ok(new_index) => new_index + 1,
|
||||
Err(_) => break,
|
||||
};
|
||||
next_token = match tokenise::get_keyword_token(tokens, index)
|
||||
{
|
||||
Ok(string) => string,
|
||||
Err(_) => break,
|
||||
}
|
||||
};
|
||||
api::modify_data(happening_queue, "choice".to_string(), String::new(), String::new(), choices.clone());
|
||||
info!("Waiting for client choice");
|
||||
debug!("{choice_indeces:?}");
|
||||
let choice = match rx.recv()
|
||||
{
|
||||
Ok((choice,_)) => choice,
|
||||
Err(err) =>
|
||||
{
|
||||
warn!("Error receiving choice from client, defaulting to choice 0 {err}");
|
||||
0
|
||||
}
|
||||
};
|
||||
Ok((choice_indeces[choice],choices[choice].clone()))
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
pub fn closing_char(parts: &[&str], open: char, close: char)
|
||||
-> Option<usize>
|
||||
{
|
||||
let mut indentation: usize = 0;
|
||||
let mut flag = false; // flag to mark you've passed open
|
||||
for (index, part) in parts.iter().enumerate()
|
||||
{
|
||||
if part.contains(open)
|
||||
{
|
||||
indentation += 1;
|
||||
flag = true;
|
||||
}
|
||||
if part.contains(close) { indentation -= 1; }
|
||||
if indentation == 0 && flag { return Some(index); }
|
||||
}
|
||||
None
|
||||
}
|
||||
pub fn extract_quoted(parts: &[&str])
|
||||
-> Option<(String, usize)>
|
||||
{
|
||||
let mut vec_string = Vec::new();
|
||||
let mut counter: usize = 0;
|
||||
for part in parts
|
||||
{
|
||||
counter += 1;
|
||||
vec_string.push(*part);
|
||||
// End of the string
|
||||
if part.ends_with('\"') // TODO allow for backslashes and '
|
||||
{
|
||||
let final_string: String = vec_string.join(" ");
|
||||
let final_string: String = final_string
|
||||
.chars()
|
||||
.skip(1)
|
||||
.take(
|
||||
final_string.chars()
|
||||
.count() - 2)
|
||||
.collect();
|
||||
return Some((final_string, counter));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
use crate::
|
||||
{
|
||||
HashMap,
|
||||
Regex,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Token
|
||||
{
|
||||
Value(Value),
|
||||
Operator(Operator),
|
||||
Keyword(String), // Keywords aren't checked for validity in this stage
|
||||
Identifier(String),
|
||||
Bracket((Bracket,usize)), // Stores the index of the matching deliminator
|
||||
Character(String),
|
||||
}
|
||||
#[derive(Debug,Clone,PartialEq,Eq)]
|
||||
pub enum Value
|
||||
{
|
||||
String(String),
|
||||
Integer(i64),
|
||||
Bool(bool),
|
||||
Null,
|
||||
}
|
||||
#[derive(Debug,Clone,PartialEq,Eq)]
|
||||
pub enum Bracket
|
||||
{
|
||||
Opening,
|
||||
Closing,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone,PartialEq)]
|
||||
pub enum Operator
|
||||
{
|
||||
// Changing a value
|
||||
Assignment,
|
||||
Add,
|
||||
Sub,
|
||||
Comparison(Comparison),
|
||||
}
|
||||
|
||||
// Comparing a value
|
||||
#[derive(Debug,Clone,PartialEq)]
|
||||
pub enum Comparison
|
||||
{
|
||||
Equate,
|
||||
Greater,
|
||||
Less,
|
||||
GreaterOrEqual,
|
||||
LessOrEqual,
|
||||
}
|
||||
|
||||
impl Value
|
||||
{
|
||||
pub fn as_string(&self) -> String
|
||||
{
|
||||
match self
|
||||
{
|
||||
Value::String(s) => s.clone(),
|
||||
Value::Integer(i) => i.to_string(),
|
||||
Value::Bool(b) => b.to_string(),
|
||||
Value::Null => "".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO error reporting for all of these
|
||||
pub fn get_operator_token(tokens: &[Token], index: usize)
|
||||
-> Result<Operator, String>
|
||||
{
|
||||
match tokens.get(index) {
|
||||
Some(Token::Operator(op)) => Ok(op.clone()),
|
||||
Some(tok) => Err(format!("Unexpected token at index {index}, expected operator, got {tok:?}")),
|
||||
None => Err("File unexpectedly reached termination point".to_string()),
|
||||
}
|
||||
}
|
||||
pub fn get_keyword_token(tokens: &[Token], index: usize)
|
||||
-> Result<String, String>
|
||||
{
|
||||
match tokens.get(index) {
|
||||
Some(Token::Keyword(s)) => Ok(s.clone()),
|
||||
Some(tok) => Err(format!("Unexpected token at index {index}, expected keyword, got {tok:?}")),
|
||||
None => Err("File unexpectedly reached termination point".to_string()),
|
||||
}
|
||||
}
|
||||
pub fn get_closing_index(tokens: &[Token], index: usize)
|
||||
-> Result<usize, String>
|
||||
{
|
||||
match tokens.get(index) {
|
||||
Some(Token::Bracket((_, index))) => Ok(*index), // TODO check for closing
|
||||
Some(tok) => Err(format!("Unexpected token at index {index}, expected opening brace, got {tok:?}")),
|
||||
None => Err("File unexpectedly reached termination point".to_string()),
|
||||
}
|
||||
}
|
||||
pub fn get_value_token(tokens: &[Token], index: usize)
|
||||
-> Result<Value, String>
|
||||
{
|
||||
match tokens.get(index) {
|
||||
Some(Token::Value(val)) => Ok(val.clone()),
|
||||
Some(tok) => Err(format!("Unexpected token at index {index}, expected value (int, string or boolean), got {tok:?}")),
|
||||
None => Err("File unexpectedly reached termination point".to_string()),
|
||||
}
|
||||
}
|
||||
pub fn get_identifier_token(tokens: &[Token], index: usize)
|
||||
-> Result<String, String>
|
||||
{
|
||||
match tokens.get(index) {
|
||||
Some(Token::Identifier(s)) => Ok(s.clone()),
|
||||
Some(tok) => Err(format!("Unexpected token at index {index}, expected identifier, got {tok:?}")),
|
||||
None => Err("File unexpectedly reached termination point".to_string()),
|
||||
}
|
||||
}
|
||||
pub fn get_string_token(tokens: &[Token], index: usize, variables: &HashMap<String, Value>)
|
||||
-> Result<String, String>
|
||||
{
|
||||
match tokens.get(index) {
|
||||
// Either get string directly
|
||||
Some(Token::Value(val)) =>
|
||||
{
|
||||
match val
|
||||
{
|
||||
Value::String(s) =>
|
||||
{
|
||||
let string = insert_variables_into_string(s, variables);
|
||||
Ok(string)
|
||||
},
|
||||
_ => Err("Unexpected value type".to_string()),
|
||||
}
|
||||
},
|
||||
// Or get the string that the identifier references
|
||||
Some(Token::Identifier(iden)) =>
|
||||
{
|
||||
let val = variables.get(iden)
|
||||
.ok_or(format!("Variable {iden} not initialised"))?;
|
||||
match val
|
||||
{
|
||||
Value::String(s) =>
|
||||
{
|
||||
let string = insert_variables_into_string(s, variables);
|
||||
Ok(string)
|
||||
},
|
||||
_ => Err("Unexpected value type".to_string()),
|
||||
}
|
||||
},
|
||||
Some(tok) => Err(format!("Unexpected token at index {index}, expected string value, got {tok:?}")),
|
||||
None => Err("File unexpectedly reached termination point".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
fn insert_variables_into_string(string: &str, variables: &HashMap<String, Value>)
|
||||
-> String
|
||||
{
|
||||
let re = Regex::new(r"\$([A-Za-z0-9-_]*)").unwrap();
|
||||
re.replace_all(string, |caps: ®ex::Captures| {
|
||||
let key = &caps[1].to_lowercase();
|
||||
variables.get(key)
|
||||
.map(|v| v.as_string())
|
||||
.unwrap_or_else(|| caps[0].to_string()) // leave unchanged if missing
|
||||
})
|
||||
.to_string()
|
||||
}
|
||||
|
||||
// Tokenise the story to Vec<Token>
|
||||
// It can contain sub-objects (using recursive calls)
|
||||
// TODO check for END
|
||||
pub fn tokenise(file_contents: &str)
|
||||
-> Result<(Vec<Token>,HashMap<String,usize>),String>
|
||||
{
|
||||
let space_seperated: Vec<&str> = file_contents
|
||||
.split_whitespace()
|
||||
.collect();
|
||||
let mut tokenised_data: Vec<Token> = Vec::new();
|
||||
let mut labels: HashMap<String, usize> = HashMap::new();
|
||||
let mut bracket_stack: Vec<usize> = Vec::new();
|
||||
let mut index: usize = 0;
|
||||
while index < space_seperated.len()
|
||||
{
|
||||
let mut item = space_seperated[index].to_string();
|
||||
// Operator
|
||||
let op: Option<Operator> = match item.as_str()
|
||||
{
|
||||
"=" => Some(Operator::Assignment),
|
||||
"+" => Some(Operator::Add),
|
||||
"-" => Some(Operator::Sub),
|
||||
"==" => Some(Operator::Comparison(Comparison::Equate)),
|
||||
">" => Some(Operator::Comparison(Comparison::Greater)),
|
||||
"<" => Some(Operator::Comparison(Comparison::Less)),
|
||||
">=" => Some(Operator::Comparison(Comparison::GreaterOrEqual)),
|
||||
"<=" => Some(Operator::Comparison(Comparison::LessOrEqual)),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(op) = op
|
||||
{
|
||||
tokenised_data.push(Token::Operator(op));
|
||||
index += 1;
|
||||
continue // skip the rest of this loop
|
||||
}
|
||||
// Characters
|
||||
if item.starts_with('@')
|
||||
{
|
||||
let mut chars = item.chars();
|
||||
chars.next();
|
||||
tokenised_data.push(Token::Character(chars.as_str().to_lowercase().to_string())); // Force character to be lowecase
|
||||
}
|
||||
// Strings
|
||||
else if item.starts_with('"') // TODO support '
|
||||
{
|
||||
let Some((new_index, new_item)) = tokenise_string(&space_seperated, index)
|
||||
else { return Err("File unexpectedly ended: No closing quote".to_string()) };
|
||||
index = new_index;
|
||||
item = new_item;
|
||||
tokenised_data.push(Token::Value(Value::String(item)));
|
||||
}
|
||||
else if let Ok(value) = item.parse::<i64>()
|
||||
{
|
||||
tokenised_data.push(Token::Value(Value::Integer(value))); // unwrap is fine here because I checked
|
||||
}
|
||||
else if item == "true"
|
||||
{
|
||||
tokenised_data.push(Token::Value(Value::Bool(true)));
|
||||
}
|
||||
else if item == "false"
|
||||
{
|
||||
tokenised_data.push(Token::Value(Value::Bool(false)));
|
||||
}
|
||||
// variable/identifier
|
||||
else if item.starts_with('$')
|
||||
{
|
||||
let mut chars = item.chars();
|
||||
chars.next();
|
||||
tokenised_data.push(Token::Identifier(chars.as_str().to_string()));
|
||||
}
|
||||
// Integer
|
||||
// Labels
|
||||
else if item.ends_with(':')
|
||||
{
|
||||
let mut chars = item.chars();
|
||||
chars.next_back();
|
||||
let label = chars.as_str();
|
||||
labels.insert
|
||||
(
|
||||
label.to_string(),
|
||||
tokenised_data.len(),
|
||||
);
|
||||
}
|
||||
else if item == "{"
|
||||
{
|
||||
bracket_stack.push(tokenised_data.len());
|
||||
tokenised_data.push(Token::Bracket((Bracket::Opening,0))); // TODO fix no closing brace edge case
|
||||
}
|
||||
else if item == "}"
|
||||
{
|
||||
let prev_index = bracket_stack.pop()
|
||||
.ok_or_else(|| "Unexpected closing brace".to_string())?;
|
||||
tokenised_data[prev_index] = Token::Bracket((Bracket::Opening, tokenised_data.len()));
|
||||
tokenised_data.push(Token::Bracket((Bracket::Closing,prev_index)));
|
||||
}
|
||||
else { tokenised_data.push(Token::Keyword(item)); }
|
||||
index += 1;
|
||||
}
|
||||
if !bracket_stack.is_empty() { return Err("File unexpectedly ended: No closing brace".to_string()) }
|
||||
Ok((tokenised_data, labels))
|
||||
}
|
||||
|
||||
|
||||
// TODO support strings that contain " in them by using a backslash
|
||||
fn tokenise_string(space_seperated: &[&str], mut index: usize)
|
||||
-> Option<(usize, String)>
|
||||
{
|
||||
let mut string = String::new();
|
||||
let first_word = space_seperated[index];
|
||||
if first_word.ends_with('"') // One word string edge case
|
||||
{
|
||||
let mut chars = first_word.chars();
|
||||
chars.next();
|
||||
chars.next_back();
|
||||
string += chars.as_str();
|
||||
return Some((index,string));
|
||||
}
|
||||
let mut chars = first_word.chars();
|
||||
chars.next();
|
||||
string += chars.as_str();
|
||||
for item in &space_seperated[index+1..]
|
||||
{
|
||||
if item.ends_with('"')
|
||||
{
|
||||
let mut chars = item.chars();
|
||||
chars.next_back();
|
||||
string += format!(" {}", chars.as_str()).as_str();
|
||||
return Some((index+1,string))
|
||||
}
|
||||
string += format!(" {item}").as_str();
|
||||
index += 1;
|
||||
}
|
||||
None
|
||||
}
|
||||
@@ -4,20 +4,25 @@ use crate::
|
||||
exit,
|
||||
};
|
||||
|
||||
// TODO support Options
|
||||
// TODO pass error message back
|
||||
pub trait UnwrapOrExit<T>
|
||||
{
|
||||
fn unwrap_or_exit(self, error_message: &str, error_code: i32) -> T;
|
||||
}
|
||||
|
||||
impl<T, E> UnwrapOrExit<T> for Result<T, E>
|
||||
impl<T, E: std::fmt::Debug> UnwrapOrExit<T> for Result<T, E>
|
||||
{
|
||||
fn unwrap_or_exit(self, error_message: &str, error_code: i32) -> T
|
||||
{
|
||||
if let Ok(value) = self { value }
|
||||
else
|
||||
match self
|
||||
{
|
||||
error!("{error_message}");
|
||||
Ok(value) => value,
|
||||
Err(error) =>
|
||||
{
|
||||
error!("{error_message}: {error:?}");
|
||||
exit(error_code);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"tim": {
|
||||
"name": "Timothy Sharpshooter",
|
||||
"gender": "Male",
|
||||
"skin_color": "",
|
||||
"eye_color": "",
|
||||
"pronoun_subject": "He",
|
||||
"pronoun_object": "Him",
|
||||
"pronoun_deppos": "His",
|
||||
"pronoun_indpos": "His",
|
||||
"pronoun_reflex": "Himself",
|
||||
"head": "",
|
||||
"hair": "",
|
||||
"torso": "",
|
||||
"arm": "",
|
||||
"leg": "",
|
||||
"hair_color": "",
|
||||
"top_clothing": "",
|
||||
"bottom_clothing": "",
|
||||
"shoes": ""
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
@tim says "hello world, it's a good day"
|
||||
@tim change name "Timothy Fineshooter"
|
||||
choice "choice numero uno" {
|
||||
@tim says "super sad"
|
||||
}
|
||||
or "choice numero duo" {
|
||||
@tim says "super unsad"
|
||||
}
|
||||
END
|
||||
Binary file not shown.
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"title": "Once upon a Test",
|
||||
"description": "This story is for testing purposes"
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"tim": {
|
||||
"name": "Timothy Sharpshooter",
|
||||
"gender": "Male",
|
||||
"skin_color": [0,0,0],
|
||||
"eye_color": [0,0,0],
|
||||
"hair_color": [0,0,0],
|
||||
"pronoun_subject": "He",
|
||||
"pronoun_object": "Him",
|
||||
"pronoun_deppos": "His",
|
||||
"pronoun_indpos": "His",
|
||||
"pronoun_reflex": "Himself",
|
||||
"head_shape": "normal-male",
|
||||
"hair_style": "",
|
||||
"torso_shape": "",
|
||||
"arm_shape": "",
|
||||
"leg_shape": "",
|
||||
"clothing": {
|
||||
"top": "",
|
||||
"bottom": "",
|
||||
"shoes": "",
|
||||
"hat": "",
|
||||
"gloves": "",
|
||||
"neck": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
$name = input
|
||||
@tim says "hello"
|
||||
@tim says $name
|
||||
$x = 3
|
||||
$x + 1
|
||||
if $x == 4 {
|
||||
@tim says "5"
|
||||
}
|
||||
elif $x < 5 {
|
||||
|
||||
@tim says "<5"
|
||||
}
|
||||
else {
|
||||
@tim says "whar"
|
||||
}
|
||||
label:
|
||||
$choice_string = choice "choice numero uno" {
|
||||
@tim says "super sad"
|
||||
}
|
||||
or "choice numero duo" {
|
||||
@tim says "super unsad"
|
||||
}
|
||||
or "choice numero tres" {
|
||||
@tim says "hola mi amigos"
|
||||
goto label
|
||||
}
|
||||
END
|
||||
@@ -1,13 +1,21 @@
|
||||
import requests
|
||||
import os
|
||||
import time
|
||||
import sys
|
||||
debug = True
|
||||
try:
|
||||
if sys.argv[1] == "silent": debug = False
|
||||
except:
|
||||
debug = True
|
||||
|
||||
# Loop and get new api
|
||||
def main():
|
||||
response = {}
|
||||
id = -1
|
||||
while True:
|
||||
try:
|
||||
response = api_get()
|
||||
print(response)
|
||||
if debug: print(response)
|
||||
match response["action_type"]:
|
||||
case "output":
|
||||
character = get_character(response["character"])
|
||||
@@ -15,8 +23,12 @@ def main():
|
||||
case "choice":
|
||||
user_choice = choice(response["choices"])
|
||||
continue
|
||||
case "input":
|
||||
get_input()
|
||||
continue
|
||||
case "end":
|
||||
print("Exitting successfully")
|
||||
requests.post("127.0.0.1:20264", json=0);
|
||||
os._exit(0)
|
||||
except:
|
||||
print("Server not up or cannot be reached")
|
||||
@@ -34,6 +46,12 @@ def choice(choices):
|
||||
print("Invalid choice, defaulting to 0")
|
||||
requests.post(api_url, json=choice);
|
||||
|
||||
def get_input():
|
||||
api_url = "http://localhost:20264/input"
|
||||
input_string = input("Input: ")
|
||||
requests.post(api_url, json=input_string);
|
||||
|
||||
|
||||
# Character outputs text to the user
|
||||
def output(character, text):
|
||||
print(character["name"], "says")
|
||||
@@ -41,6 +59,8 @@ def output(character, text):
|
||||
|
||||
# Get user from the backend
|
||||
def get_character(character):
|
||||
if character.lower() == "narrator":
|
||||
return {"name": "narrator"}
|
||||
api_url = f"http://localhost:20264/character/{character}"
|
||||
return requests.get(api_url).json()
|
||||
|
||||
Reference in New Issue
Block a user