Compare commits
76 commits
sprint_05_
...
main
Author | SHA1 | Date | |
---|---|---|---|
Stephen Seo | 2d7c8c37e5 | ||
Stephen Seo | 090b8bbd30 | ||
Stephen Seo | 59b2bc34fb | ||
Stephen Seo | 1872c4877e | ||
Stephen Seo | b5529cb542 | ||
Stephen Seo | f4f3ad7a5b | ||
Stephen Seo | 4331a20daa | ||
Stephen Seo | 047549ecb5 | ||
Stephen Seo | bc6c234314 | ||
Stephen Seo | 1f27defe11 | ||
Stephen Seo | 3eb663c305 | ||
Stephen Seo | b2ea79a7f7 | ||
Stephen Seo | b4eaba09c5 | ||
Stephen Seo | d88e8ef9f3 | ||
Stephen Seo | 105cd880f2 | ||
Stephen Seo | 36dd43bb70 | ||
Stephen Seo | 5381578b08 | ||
Stephen Seo | f498f2c475 | ||
Stephen Seo | 8eb30fc5d5 | ||
Stephen Seo | 6ef8667382 | ||
Stephen Seo | a4bf4cbd25 | ||
Stephen Seo | f799bae530 | ||
Stephen Seo | b158e7347e | ||
Stephen Seo | e6152331b0 | ||
Stephen Seo | e77d25996d | ||
Stephen Seo | e0ed5fc5d8 | ||
Stephen Seo | 6b430660b7 | ||
Stephen Seo | 174875b88b | ||
Stephen Seo | 694da61bd6 | ||
Stephen Seo | 3172af19f8 | ||
Stephen Seo | dcc9400483 | ||
Stephen Seo | 665dff94fe | ||
Stephen Seo | edd3b0c65c | ||
Stephen Seo | 059d0608b6 | ||
Stephen Seo | f9338d4093 | ||
Stephen Seo | 87d93e5b4f | ||
Stephen Seo | e060d94186 | ||
Stephen Seo | 96e28b9d68 | ||
Stephen Seo | b26c9ff6d1 | ||
Stephen Seo | d55e43cc6c | ||
Stephen Seo | b7e0b522af | ||
Stephen Seo | e54d239260 | ||
Stephen Seo | a7c67fd098 | ||
Stephen Seo | 975a878eb6 | ||
Stephen Seo | f8da5f4997 | ||
Stephen Seo | 501ce91ac3 | ||
Stephen Seo | 27ff10293a | ||
Stephen Seo | c98e4723bb | ||
Stephen Seo | 73ac99b7cc | ||
Stephen Seo | 381c5d3b29 | ||
Stephen Seo | fdbef5f6df | ||
Stephen Seo | 7a4821ac26 | ||
Stephen Seo | bb7d150196 | ||
Stephen Seo | 6aa2927b42 | ||
Stephen Seo | 2aa4200835 | ||
Stephen Seo | 57b782ad4d | ||
Stephen Seo | 70eb1ca121 | ||
Stephen Seo | ee8187cabb | ||
Stephen Seo | 284f294c6d | ||
Stephen Seo | 392fc5e7f0 | ||
Stephen Seo | 1e6e5ff1f5 | ||
Stephen Seo | c9ba80e6a4 | ||
Stephen Seo | bc3de10d4e | ||
Stephen Seo | ca0e9f55f6 | ||
Stephen Seo | 23d9b5647e | ||
Stephen Seo | 9fc0408d16 | ||
Stephen Seo | 74ca3fd696 | ||
Stephen Seo | 62431f0d0e | ||
Stephen Seo | 50221a0356 | ||
Stephen Seo | 32d58fae2c | ||
Stephen Seo | 91c9eab05e | ||
Stephen Seo | 59bdcc2a34 | ||
Stephen Seo | 2e0b29ed70 | ||
Stephen Seo | 567cec9500 | ||
Stephen Seo | 89b9bf860b | ||
Stephen Seo | 8c84aae173 |
20
README.md
20
README.md
|
@ -1,5 +1,8 @@
|
|||
# Four-Line Dropper: A project developed via Agile methodologies
|
||||
|
||||
Note that this project was made for a course for the JHU Engineering for
|
||||
Professionals program.
|
||||
|
||||
The directory `front_end` holds a Rust project for the "front\_end" code. Or in
|
||||
other words, it holds the client-side code for the project. The MVP (Minimum
|
||||
Viable Product) started with just the front\_end, and if all the goals are met,
|
||||
|
@ -9,12 +12,25 @@ The directory `back_end` holds a Rust project for the "back\_end" code. It holds
|
|||
the server-side code for the project. It is mainly used to handle "Networked
|
||||
Multiplayer" mode for the game.
|
||||
|
||||
The directory `specifications` holds defined specifications. It currently holds
|
||||
the back-end specifications (database and protocol).
|
||||
|
||||
The directory `spreadsheets` hold LibreOffice Calc documents that are
|
||||
spreadsheets organizing the work. There is a document for User Stories, a
|
||||
document for the Product Backlogs, and there will be a document for each Sprint.
|
||||
|
||||
The directory `retrospectives` holds the retrospectives of Sprints 3 and onward.
|
||||
|
||||
The directory `plans` contains the release plans.
|
||||
|
||||
The directory `pictures` holds pertinent images to the project. It includes the
|
||||
"Simple Model" of the project.
|
||||
|
||||
## Tags
|
||||
|
||||
The git repository is tagged per Sprint and per Day. [One can visualize the
|
||||
progress here.](https://git.seodisparate.com/stephenseo/EN605.607.81.SP22_ASDM_Project/graph)
|
||||
|
||||
## What is Four-Line Dropper?
|
||||
|
||||
Four-Line Dropper is a game where two players take turns dropping tokens into
|
||||
|
@ -23,3 +39,7 @@ diagonally is the win condition of the game. If the board fills up with no
|
|||
four-line matches, then the game ends in a draw. The game is called "Four-Line
|
||||
Dropper" to avoid clashing with the game's original name that is trademarked
|
||||
(this game is a clone of an existing game).
|
||||
|
||||
# Link to a hosted instance
|
||||
|
||||
[I have hosted an instance of the front-end/back-end here.](https://asdm.seodisparate.com)
|
||||
|
|
674
back_end/COPYING
Normal file
674
back_end/COPYING
Normal file
|
@ -0,0 +1,674 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
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.
|
||||
|
||||
"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.
|
||||
|
||||
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.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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 Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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:
|
||||
|
||||
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".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
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.
|
||||
|
||||
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:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
"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.
|
||||
|
||||
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).
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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).
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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".
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
842
back_end/Cargo.lock
generated
842
back_end/Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "four_line_dropper_backend"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
@ -14,3 +14,5 @@ rusqlite = "0.27.0"
|
|||
rand = "0.8.4"
|
||||
futures = "0.3"
|
||||
oorandom = "11.1.3"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_derive = "1.0"
|
||||
|
|
10
back_end/README.md
Normal file
10
back_end/README.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
# How to run
|
||||
|
||||
Just invoke `cargo run --release` to start the server locally. It expects to
|
||||
have sqlite already installed on your system. Note that the server listens on
|
||||
port 1237.
|
||||
|
||||
# Other Notes
|
||||
|
||||
See [choose-a-license](https://choosealicense.com/licenses/gpl-3.0) for details
|
||||
about the software license used.
|
|
@ -1,23 +1,44 @@
|
|||
//Four Line Dropper Backend - A server enabling networked multiplayer for Four Line Dropper
|
||||
//Copyright (C) 2022 Stephen Seo
|
||||
//
|
||||
//This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
//
|
||||
//This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
//
|
||||
//You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
use crate::ai::{get_ai_choice, AIDifficulty};
|
||||
use crate::constants::{
|
||||
COLS, GAME_CLEANUP_TIMEOUT, PLAYER_CLEANUP_TIMEOUT, PLAYER_COUNT_LIMIT, ROWS, TURN_SECONDS,
|
||||
BACKEND_CLEANUP_INTERVAL_SECONDS, COLS, GAME_CLEANUP_TIMEOUT, PLAYER_CLEANUP_TIMEOUT,
|
||||
PLAYER_COUNT_LIMIT, ROWS, TURN_SECONDS,
|
||||
};
|
||||
use crate::state::{
|
||||
board_from_string, new_string_board, string_from_board, BoardState, EmoteEnum, Turn,
|
||||
};
|
||||
use crate::state::{board_from_string, new_string_board, string_from_board, BoardState, Turn};
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::mpsc::{Receiver, RecvTimeoutError, SyncSender};
|
||||
use std::time::Duration;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::{fmt, thread};
|
||||
|
||||
use rand::{thread_rng, Rng};
|
||||
use rusqlite::{params, Connection, Error as RusqliteError};
|
||||
|
||||
pub type GetIDSenderType = (u32, Option<bool>);
|
||||
/// first value is ID, None if too many players
|
||||
/// second value is true if player is cyan_player, None if not paired yet
|
||||
pub type GetIDSenderType = (Option<u32>, Option<bool>);
|
||||
/// first bool is player exists,
|
||||
/// second bool is if paired,
|
||||
/// third bool is if cyan player
|
||||
pub type CheckPairingType = (bool, bool, bool);
|
||||
|
||||
pub type BoardStateType = (DBGameState, Option<String>);
|
||||
/// second String is board string, third String is date updated, fourth value
|
||||
/// is EmoteEnum
|
||||
pub type BoardStateType = (
|
||||
DBGameState,
|
||||
Option<String>,
|
||||
Option<String>,
|
||||
Option<EmoteEnum>,
|
||||
);
|
||||
|
||||
pub type PlaceResultType = Result<(DBPlaceStatus, Option<String>), DBPlaceError>;
|
||||
|
||||
|
@ -107,7 +128,10 @@ impl fmt::Display for DBPlaceError {
|
|||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum DBHandlerRequest {
|
||||
GetID(SyncSender<GetIDSenderType>),
|
||||
GetID {
|
||||
response_sender: SyncSender<GetIDSenderType>,
|
||||
phrase: Option<String>,
|
||||
},
|
||||
CheckPairing {
|
||||
id: u32,
|
||||
response_sender: SyncSender<CheckPairingType>,
|
||||
|
@ -125,6 +149,11 @@ pub enum DBHandlerRequest {
|
|||
pos: usize,
|
||||
response_sender: SyncSender<PlaceResultType>,
|
||||
},
|
||||
SendEmote {
|
||||
id: u32,
|
||||
emote_type: EmoteEnum,
|
||||
response_sender: SyncSender<Result<(), ()>>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
|
@ -152,7 +181,10 @@ impl DBHandler {
|
|||
}
|
||||
let db_request = rx_recv_result.unwrap();
|
||||
match db_request {
|
||||
DBHandlerRequest::GetID(player_tx) => {
|
||||
DBHandlerRequest::GetID {
|
||||
response_sender,
|
||||
phrase,
|
||||
} => {
|
||||
// got request to create new player, create new player
|
||||
let conn_result = self.get_conn(DBFirstRun::NotFirstRun);
|
||||
if let Err(e) = conn_result {
|
||||
|
@ -161,9 +193,10 @@ impl DBHandler {
|
|||
}
|
||||
let conn = conn_result.unwrap();
|
||||
|
||||
let create_player_result = self.create_new_player(Some(&conn));
|
||||
let create_player_result = self.create_new_player(Some(&conn), phrase);
|
||||
if let Err(e) = create_player_result {
|
||||
println!("{}", e);
|
||||
response_sender.send((None, None)).ok();
|
||||
// don't stop server because player limit may have been reached
|
||||
return false;
|
||||
}
|
||||
|
@ -185,11 +218,11 @@ impl DBHandler {
|
|||
if paired {
|
||||
// don't stop server on send fail, may have timed
|
||||
// out and dropped the receiver
|
||||
player_tx.send((player_id, Some(is_cyan))).ok();
|
||||
response_sender.send((Some(player_id), Some(is_cyan))).ok();
|
||||
} else {
|
||||
// don't stop server on send fail, may have timed
|
||||
// out and dropped the receiver
|
||||
player_tx.send((player_id, None)).ok();
|
||||
response_sender.send((Some(player_id), None)).ok();
|
||||
}
|
||||
} else {
|
||||
println!("Internal error, created player doesn't exist");
|
||||
|
@ -222,7 +255,9 @@ impl DBHandler {
|
|||
println!("{}", e);
|
||||
// don't stop server on send fail, may have timed out and
|
||||
// dropped the receiver
|
||||
response_sender.send((DBGameState::UnknownID, None)).ok();
|
||||
response_sender
|
||||
.send((DBGameState::UnknownID, None, None, None))
|
||||
.ok();
|
||||
return false;
|
||||
}
|
||||
// don't stop server on send fail, may have timed out and
|
||||
|
@ -249,6 +284,23 @@ impl DBHandler {
|
|||
// dropped the receiver
|
||||
response_sender.send(place_result).ok();
|
||||
}
|
||||
DBHandlerRequest::SendEmote {
|
||||
id,
|
||||
emote_type,
|
||||
response_sender,
|
||||
} => {
|
||||
let result = self.create_new_sent_emote(None, id, emote_type);
|
||||
if let Err(error_string) = result {
|
||||
println!("{}", error_string);
|
||||
// don't stop server on send fail, may have timed
|
||||
// out and dropped the receiver
|
||||
response_sender.send(Err(())).ok();
|
||||
} else {
|
||||
// don't stop server on send fail, may have timed
|
||||
// out and dropped the receiver
|
||||
response_sender.send(Ok(())).ok();
|
||||
}
|
||||
}
|
||||
} // match db_request
|
||||
|
||||
false
|
||||
|
@ -263,6 +315,7 @@ impl DBHandler {
|
|||
CREATE TABLE players (id INTEGER PRIMARY KEY NOT NULL,
|
||||
date_added TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
game_id INTEGER,
|
||||
phrase TEXT,
|
||||
FOREIGN KEY(game_id) REFERENCES games(id) ON DELETE CASCADE);
|
||||
",
|
||||
[],
|
||||
|
@ -273,6 +326,9 @@ impl DBHandler {
|
|||
}
|
||||
} else if first_run == DBFirstRun::FirstRun {
|
||||
println!("\"players\" table exists");
|
||||
if let Err(e) = self.db_check_migration(&conn) {
|
||||
println!("{}", e);
|
||||
}
|
||||
}
|
||||
|
||||
let result = conn.execute(
|
||||
|
@ -296,17 +352,66 @@ impl DBHandler {
|
|||
} else if first_run == DBFirstRun::FirstRun {
|
||||
println!("\"games\" table exists");
|
||||
}
|
||||
|
||||
let result = conn.execute(
|
||||
"
|
||||
CREATE TABLE emotes (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
date_added TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
receiver_id INTEGER NOT NULL,
|
||||
FOREIGN KEY(receiver_id) REFERENCES players (id) ON DELETE CASCADE);
|
||||
",
|
||||
[],
|
||||
);
|
||||
if result.is_ok() {
|
||||
if first_run == DBFirstRun::FirstRun {
|
||||
println!("Created \"emotes\" table");
|
||||
}
|
||||
} else if first_run == DBFirstRun::FirstRun {
|
||||
println!("\"emotes\" table exists");
|
||||
}
|
||||
|
||||
Ok(conn)
|
||||
} else {
|
||||
Err(String::from("Failed to open connection"))
|
||||
}
|
||||
}
|
||||
|
||||
fn create_new_player(&self, conn: Option<&Connection>) -> Result<u32, String> {
|
||||
if conn.is_none() {
|
||||
return self.create_new_player(Some(&self.get_conn(DBFirstRun::NotFirstRun)?));
|
||||
fn db_check_migration(&self, conn: &Connection) -> Result<(), String> {
|
||||
let mut table_entries_stmt = conn
|
||||
.prepare("PRAGMA table_info(players);")
|
||||
.map_err(|e| format!("{:?}", e))?;
|
||||
let mut table_entries_rows = table_entries_stmt
|
||||
.query([])
|
||||
.map_err(|e| format!("{:?}", e))?;
|
||||
// check if "phrase" column exists
|
||||
let mut phrase_exists = false;
|
||||
while let Some(row) = table_entries_rows.next().map_err(|e| format!("{:?}", e))? {
|
||||
let column_name: String = row.get(1).map_err(|e| format!("{:?}", e))?;
|
||||
if column_name.contains("phrase") {
|
||||
phrase_exists = true;
|
||||
}
|
||||
}
|
||||
let conn = conn.unwrap();
|
||||
if !phrase_exists {
|
||||
conn.execute("ALTER TABLE players ADD COLUMN phrase TEXT;", [])
|
||||
.map_err(|e| format!("{:?}", e))?;
|
||||
println!("Added \"phrase\" column to \"players\" in db.");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_new_player(
|
||||
&self,
|
||||
conn: Option<&Connection>,
|
||||
phrase: Option<String>,
|
||||
) -> Result<u32, String> {
|
||||
let mut _conn_result = Err(String::new());
|
||||
let conn = if let Some(c) = conn {
|
||||
c
|
||||
} else {
|
||||
_conn_result = self.get_conn(DBFirstRun::NotFirstRun);
|
||||
_conn_result.as_ref().unwrap()
|
||||
};
|
||||
|
||||
let row_result: Result<usize, _> =
|
||||
conn.query_row("SELECT count(id) FROM players;", [], |row| row.get(0));
|
||||
|
@ -338,7 +443,10 @@ impl DBHandler {
|
|||
}
|
||||
}
|
||||
|
||||
let insert_result = conn.execute("INSERT INTO players (id) VALUES (?);", [player_id]);
|
||||
let insert_result = conn.execute(
|
||||
"INSERT INTO players (id, phrase) VALUES (?, ?);",
|
||||
params![player_id, phrase],
|
||||
);
|
||||
if let Err(e) = insert_result {
|
||||
return Err(format!("Failed to insert player into db: {:?}", e));
|
||||
}
|
||||
|
@ -347,29 +455,49 @@ impl DBHandler {
|
|||
}
|
||||
|
||||
fn pair_up_players(&self, conn: Option<&Connection>) -> Result<(), String> {
|
||||
if conn.is_none() {
|
||||
return self.pair_up_players(Some(&self.get_conn(DBFirstRun::NotFirstRun)?));
|
||||
}
|
||||
let conn = conn.unwrap();
|
||||
let mut _conn_result = Err(String::new());
|
||||
let conn = if let Some(c) = conn {
|
||||
c
|
||||
} else {
|
||||
_conn_result = self.get_conn(DBFirstRun::NotFirstRun);
|
||||
_conn_result.as_ref().unwrap()
|
||||
};
|
||||
|
||||
let mut to_pair: Option<u32> = None;
|
||||
let mut unpaired_players_stmt = conn
|
||||
.prepare("SELECT id FROM players WHERE game_id ISNULL ORDER BY date_added;")
|
||||
.prepare("SELECT id, phrase FROM players WHERE game_id ISNULL ORDER BY date_added;")
|
||||
.map_err(|e| format!("{:?}", e))?;
|
||||
let mut unpaired_players_rows = unpaired_players_stmt
|
||||
.query([])
|
||||
.map_err(|e| format!("{:?}", e))?;
|
||||
let mut phrase_map: HashMap<String, u32> = HashMap::new();
|
||||
while let Some(row) = unpaired_players_rows
|
||||
.next()
|
||||
.map_err(|e| format!("{:?}", e))?
|
||||
{
|
||||
if to_pair.is_none() {
|
||||
to_pair = Some(row.get(0).map_err(|e| format!("{:?}", e))?);
|
||||
if let Ok(phrase_text) = row.get::<usize, String>(1) {
|
||||
// pair players with matching phrases
|
||||
if let Some(matching_player_id) = phrase_map.get(&phrase_text) {
|
||||
let players: [u32; 2] = [
|
||||
*matching_player_id,
|
||||
row.get(0).map_err(|e| format!("{:?}", e))?,
|
||||
];
|
||||
self.create_game(Some(conn), &players)?;
|
||||
phrase_map.remove(&phrase_text);
|
||||
} else {
|
||||
phrase_map.insert(phrase_text, row.get(0).map_err(|e| format!("{:?}", e))?);
|
||||
}
|
||||
} else {
|
||||
let players: [u32; 2] = [
|
||||
to_pair.take().unwrap(),
|
||||
row.get(0).map_err(|e| format!("{:?}", e))?,
|
||||
];
|
||||
self.create_game(Some(conn), &players)?;
|
||||
// pair players that did not use a phrase
|
||||
if to_pair.is_none() {
|
||||
to_pair = Some(row.get(0).map_err(|e| format!("{:?}", e))?);
|
||||
} else {
|
||||
let players: [u32; 2] = [
|
||||
to_pair.take().unwrap(),
|
||||
row.get(0).map_err(|e| format!("{:?}", e))?,
|
||||
];
|
||||
self.create_game(Some(conn), &players)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -377,10 +505,14 @@ impl DBHandler {
|
|||
}
|
||||
|
||||
fn create_game(&self, conn: Option<&Connection>, players: &[u32; 2]) -> Result<u32, String> {
|
||||
if conn.is_none() {
|
||||
return self.create_game(Some(&self.get_conn(DBFirstRun::NotFirstRun)?), players);
|
||||
}
|
||||
let conn = conn.unwrap();
|
||||
let mut _conn_result = Err(String::new());
|
||||
let conn = if let Some(c) = conn {
|
||||
c
|
||||
} else {
|
||||
_conn_result = self.get_conn(DBFirstRun::NotFirstRun);
|
||||
_conn_result.as_ref().unwrap()
|
||||
};
|
||||
|
||||
let mut game_id: u32 = thread_rng().gen();
|
||||
{
|
||||
let mut get_game_stmt = conn
|
||||
|
@ -424,13 +556,13 @@ impl DBHandler {
|
|||
}
|
||||
}
|
||||
|
||||
if conn.is_none() {
|
||||
return self.check_if_player_is_paired(
|
||||
Some(&self.get_conn(DBFirstRun::NotFirstRun)?),
|
||||
player_id,
|
||||
);
|
||||
}
|
||||
let conn = conn.unwrap();
|
||||
let mut _conn_result = Err(String::new());
|
||||
let conn = if let Some(c) = conn {
|
||||
c
|
||||
} else {
|
||||
_conn_result = self.get_conn(DBFirstRun::NotFirstRun);
|
||||
_conn_result.as_ref().unwrap()
|
||||
};
|
||||
|
||||
let check_player_row = conn.query_row("SELECT games.cyan_player FROM players JOIN games where games.id = players.game_id AND players.id = ?;", [player_id], |row| row.get::<usize, u32>(0));
|
||||
if let Ok(cyan_player) = check_player_row {
|
||||
|
@ -467,11 +599,14 @@ impl DBHandler {
|
|||
conn: Option<&Connection>,
|
||||
player_id: u32,
|
||||
) -> Result<bool, String> {
|
||||
if conn.is_none() {
|
||||
return self
|
||||
.check_if_player_exists(Some(&self.get_conn(DBFirstRun::NotFirstRun)?), player_id);
|
||||
}
|
||||
let conn = conn.unwrap();
|
||||
let mut _conn_result = Err(String::new());
|
||||
let conn = if let Some(c) = conn {
|
||||
c
|
||||
} else {
|
||||
_conn_result = self.get_conn(DBFirstRun::NotFirstRun);
|
||||
_conn_result.as_ref().unwrap()
|
||||
};
|
||||
|
||||
let check_player_row: Result<u32, _> =
|
||||
conn.query_row("SELECT id FROM players WHERE id = ?;", [player_id], |row| {
|
||||
row.get(0)
|
||||
|
@ -488,13 +623,13 @@ impl DBHandler {
|
|||
conn: Option<&Connection>,
|
||||
player_id: u32,
|
||||
) -> Result<bool, String> {
|
||||
if conn.is_none() {
|
||||
return self.check_if_player_in_game(
|
||||
Some(&self.get_conn(DBFirstRun::NotFirstRun)?),
|
||||
player_id,
|
||||
);
|
||||
}
|
||||
let conn = conn.unwrap();
|
||||
let mut _conn_result = Err(String::new());
|
||||
let conn = if let Some(c) = conn {
|
||||
c
|
||||
} else {
|
||||
_conn_result = self.get_conn(DBFirstRun::NotFirstRun);
|
||||
_conn_result.as_ref().unwrap()
|
||||
};
|
||||
|
||||
let check_player_game_row: Result<u32, _> = conn.query_row(
|
||||
"SELECT games.id FROM games JOIN players WHERE players.id = ? AND players.game_id NOTNULL AND players.game_id = games.id;",
|
||||
|
@ -512,68 +647,128 @@ impl DBHandler {
|
|||
conn: Option<&Connection>,
|
||||
player_id: u32,
|
||||
) -> Result<BoardStateType, String> {
|
||||
if conn.is_none() {
|
||||
return self.get_board_state(Some(&self.get_conn(DBFirstRun::NotFirstRun)?), player_id);
|
||||
let mut _conn_result = Err(String::new());
|
||||
let conn = if let Some(c) = conn {
|
||||
c
|
||||
} else {
|
||||
_conn_result = self.get_conn(DBFirstRun::NotFirstRun);
|
||||
_conn_result.as_ref().unwrap()
|
||||
};
|
||||
|
||||
let mut received_emote: Option<EmoteEnum> = None;
|
||||
{
|
||||
let row_result: Result<(u64, String), RusqliteError> = conn.query_row(
|
||||
"SELECT id, type FROM emotes WHERE receiver_id = ? ORDER BY date_added ASC;",
|
||||
[player_id],
|
||||
|row| {
|
||||
Ok((
|
||||
row.get(0).expect("emotes.id should exist"),
|
||||
row.get(1).expect("emotes.type should exist"),
|
||||
))
|
||||
},
|
||||
);
|
||||
if let Err(RusqliteError::QueryReturnedNoRows) = row_result {
|
||||
// no-op
|
||||
} else if let Err(e) = row_result {
|
||||
println!("Error while fetching received emotes: {:?}", e);
|
||||
} else {
|
||||
let (emote_id, emote_type) = row_result.unwrap();
|
||||
received_emote = emote_type.as_str().try_into().ok();
|
||||
if received_emote.is_none() {
|
||||
println!("WARNING: Invalid emote type \"{}\" in db", emote_type);
|
||||
}
|
||||
conn.execute("DELETE FROM emotes WHERE id = ?;", [emote_id])
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
let conn = conn.unwrap();
|
||||
|
||||
// TODO maybe handle "opponent_disconnected" case
|
||||
let row_result: Result<(String, i64, Option<u32>, Option<u32>), RusqliteError> = conn.query_row(
|
||||
"SELECT games.board, games.status, games.cyan_player, games.magenta_player FROM games JOIN players WHERE players.id = ? AND games.id = players.game_id;",
|
||||
type ResultTuple = (String, i64, Option<u32>, Option<u32>, String);
|
||||
let row_result: Result<ResultTuple, RusqliteError> = conn.query_row(
|
||||
"SELECT games.board, games.status, games.cyan_player, games.magenta_player, games.turn_time_start FROM games JOIN players WHERE players.id = ? AND games.id = players.game_id;",
|
||||
[player_id],
|
||||
|row| {
|
||||
let board_result = row.get(0);
|
||||
let status_result = row.get(1);
|
||||
let cyan_player = row.get(2);
|
||||
let magenta_player = row.get(3);
|
||||
if board_result.is_ok() && status_result.is_ok() && cyan_player.is_ok() && magenta_player.is_ok() {
|
||||
if let (Ok(board), Ok(status), Ok(cyan_id), Ok(magenta_id)) = (board_result, status_result, cyan_player, magenta_player) {
|
||||
Ok((board, status, cyan_id, magenta_id))
|
||||
let updated_time = row.get(4);
|
||||
if board_result.is_ok() && status_result.is_ok() && cyan_player.is_ok() && magenta_player.is_ok() && updated_time.is_ok() {
|
||||
if let (Ok(board), Ok(status), Ok(cyan_id), Ok(magenta_id), Ok(updated_time)) = (board_result, status_result, cyan_player, magenta_player, updated_time) {
|
||||
Ok((board, status, cyan_id, magenta_id, updated_time))
|
||||
} else {
|
||||
unreachable!("Both row items should be Ok");
|
||||
unreachable!("All row items should be Ok");
|
||||
}
|
||||
} else if board_result.is_err() {
|
||||
board_result
|
||||
.map(|_| (String::from("this value should never be returned"), 0, None, None))
|
||||
.map(|_| (String::from("this value should never be returned"), 0, None, None, String::new()))
|
||||
} else if status_result.is_err() {
|
||||
status_result
|
||||
.map(|_| (String::from("this value should never be returned"), 0, None, None))
|
||||
.map(|_| (String::from("this value should never be returned"), 0, None, None, String::new()))
|
||||
} else if cyan_player.is_err() {
|
||||
cyan_player
|
||||
.map(|_| (String::from("this value should never be returned"), 0, None, None))
|
||||
} else {
|
||||
.map(|_| (String::from("this value should never be returned"), 0, None, None, String::new()))
|
||||
} else if magenta_player.is_err() {
|
||||
magenta_player
|
||||
.map(|_| (String::from("this value should never be returned"), 0, None, None))
|
||||
.map(|_| (String::from("this value should never be returned"), 0, None, None, String::new()))
|
||||
} else {
|
||||
updated_time
|
||||
.map(|_| (String::from("this value should never be returned"), 0, None, None, String::new()))
|
||||
}
|
||||
}
|
||||
);
|
||||
if let Ok((board, status, cyan_opt, magenta_opt)) = row_result {
|
||||
if let Ok((board, status, cyan_opt, magenta_opt, updated_time)) = row_result {
|
||||
if board.len() != (ROWS * COLS) as usize {
|
||||
// board is invalid size
|
||||
Ok((DBGameState::InternalError, None))
|
||||
Ok((
|
||||
DBGameState::InternalError,
|
||||
None,
|
||||
Some(updated_time),
|
||||
received_emote,
|
||||
))
|
||||
} else if cyan_opt.is_none() || magenta_opt.is_none() {
|
||||
// One player disconnected
|
||||
self.disconnect_player(Some(conn), player_id).ok();
|
||||
// Remove the game(s) with disconnected players
|
||||
if self.clear_empty_games(Some(conn)).is_err() {
|
||||
Ok((DBGameState::InternalError, None))
|
||||
Ok((
|
||||
DBGameState::InternalError,
|
||||
None,
|
||||
Some(updated_time),
|
||||
received_emote,
|
||||
))
|
||||
} else if status == 2 || status == 3 {
|
||||
Ok((DBGameState::from(status), Some(board)))
|
||||
Ok((
|
||||
DBGameState::from(status),
|
||||
Some(board),
|
||||
Some(updated_time),
|
||||
received_emote,
|
||||
))
|
||||
} else {
|
||||
Ok((DBGameState::OpponentDisconnected, Some(board)))
|
||||
Ok((
|
||||
DBGameState::OpponentDisconnected,
|
||||
Some(board),
|
||||
Some(updated_time),
|
||||
received_emote,
|
||||
))
|
||||
}
|
||||
} else {
|
||||
// Game in progress, or other state depending on "status"
|
||||
Ok((DBGameState::from(status), Some(board)))
|
||||
Ok((
|
||||
DBGameState::from(status),
|
||||
Some(board),
|
||||
Some(updated_time),
|
||||
received_emote,
|
||||
))
|
||||
}
|
||||
} else if let Err(RusqliteError::QueryReturnedNoRows) = row_result {
|
||||
// No rows is either player doesn't exist or not paired
|
||||
let (exists, is_paired, _is_cyan) =
|
||||
self.check_if_player_is_paired(Some(conn), player_id)?;
|
||||
if !exists {
|
||||
Ok((DBGameState::UnknownID, None))
|
||||
Ok((DBGameState::UnknownID, None, None, received_emote))
|
||||
} else if !is_paired {
|
||||
Ok((DBGameState::NotPaired, None))
|
||||
Ok((DBGameState::NotPaired, None, None, received_emote))
|
||||
} else {
|
||||
unreachable!("either exists or is_paired must be false");
|
||||
}
|
||||
|
@ -584,11 +779,13 @@ impl DBHandler {
|
|||
}
|
||||
|
||||
fn disconnect_player(&self, conn: Option<&Connection>, player_id: u32) -> Result<(), String> {
|
||||
if conn.is_none() {
|
||||
return self
|
||||
.disconnect_player(Some(&self.get_conn(DBFirstRun::NotFirstRun)?), player_id);
|
||||
}
|
||||
let conn = conn.unwrap();
|
||||
let mut _conn_result = Err(String::new());
|
||||
let conn = if let Some(c) = conn {
|
||||
c
|
||||
} else {
|
||||
_conn_result = self.get_conn(DBFirstRun::NotFirstRun);
|
||||
_conn_result.as_ref().unwrap()
|
||||
};
|
||||
|
||||
let stmt_result = conn.execute("DELETE FROM players WHERE id = ?;", [player_id]);
|
||||
if let Ok(1) = stmt_result {
|
||||
|
@ -599,10 +796,13 @@ impl DBHandler {
|
|||
}
|
||||
|
||||
fn clear_empty_games(&self, conn: Option<&Connection>) -> Result<(), String> {
|
||||
if conn.is_none() {
|
||||
return self.clear_empty_games(Some(&self.get_conn(DBFirstRun::NotFirstRun)?));
|
||||
}
|
||||
let conn = conn.unwrap();
|
||||
let mut _conn_result = Err(String::new());
|
||||
let conn = if let Some(c) = conn {
|
||||
c
|
||||
} else {
|
||||
_conn_result = self.get_conn(DBFirstRun::NotFirstRun);
|
||||
_conn_result.as_ref().unwrap()
|
||||
};
|
||||
|
||||
// Only fails if no rows were removed, and that is not an issue
|
||||
conn.execute(
|
||||
|
@ -620,18 +820,13 @@ impl DBHandler {
|
|||
player_id: u32,
|
||||
pos: usize,
|
||||
) -> PlaceResultType {
|
||||
if conn.is_none() {
|
||||
return self.place_token(
|
||||
Some(
|
||||
&self
|
||||
.get_conn(DBFirstRun::NotFirstRun)
|
||||
.map_err(|_| DBPlaceError::InternalError)?,
|
||||
),
|
||||
player_id,
|
||||
pos,
|
||||
);
|
||||
}
|
||||
let conn = conn.unwrap();
|
||||
let mut _conn_result = Err(String::new());
|
||||
let conn = if let Some(c) = conn {
|
||||
c
|
||||
} else {
|
||||
_conn_result = self.get_conn(DBFirstRun::NotFirstRun);
|
||||
_conn_result.as_ref().unwrap()
|
||||
};
|
||||
|
||||
// check if player exists
|
||||
let player_exist_check_result = self.check_if_player_exists(Some(conn), player_id);
|
||||
|
@ -719,33 +914,21 @@ impl DBHandler {
|
|||
}
|
||||
2 => {
|
||||
// game over, cyan won
|
||||
self.disconnect_player(Some(conn), player_id).ok();
|
||||
if self.clear_empty_games(Some(conn)).is_err() {
|
||||
return Err(DBPlaceError::InternalError);
|
||||
}
|
||||
return Ok((DBPlaceStatus::GameEndedCyanWon, Some(board_string)));
|
||||
}
|
||||
3 => {
|
||||
// game over, magenta won
|
||||
self.disconnect_player(Some(conn), player_id).ok();
|
||||
if self.clear_empty_games(Some(conn)).is_err() {
|
||||
return Err(DBPlaceError::InternalError);
|
||||
}
|
||||
return Ok((DBPlaceStatus::GameEndedMagentaWon, Some(board_string)));
|
||||
}
|
||||
4 => {
|
||||
// game over, draw
|
||||
self.disconnect_player(Some(conn), player_id).ok();
|
||||
if self.clear_empty_games(Some(conn)).is_err() {
|
||||
return Err(DBPlaceError::InternalError);
|
||||
}
|
||||
return Ok((DBPlaceStatus::GameEndedDraw, Some(board_string)));
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
// get board state
|
||||
let board = board_from_string(board_string);
|
||||
let board = board_from_string(&board_string);
|
||||
|
||||
// find placement position or return "illegal move" if unable to
|
||||
let mut final_pos = pos;
|
||||
|
@ -771,7 +954,7 @@ impl DBHandler {
|
|||
}
|
||||
|
||||
// board back to string
|
||||
let (board_string, ended_state_opt) = string_from_board(board, final_pos);
|
||||
let (board_string, ended_state_opt) = string_from_board(&board, final_pos);
|
||||
|
||||
// update DB
|
||||
let update_result = if ended_state_opt.is_none() {
|
||||
|
@ -802,7 +985,6 @@ impl DBHandler {
|
|||
}
|
||||
|
||||
if let Some(ended_state) = ended_state_opt {
|
||||
self.disconnect_player(Some(conn), player_id).ok();
|
||||
Ok((
|
||||
match ended_state {
|
||||
BoardState::Empty => DBPlaceStatus::GameEndedDraw,
|
||||
|
@ -851,7 +1033,7 @@ impl DBHandler {
|
|||
|
||||
for row_result in rows {
|
||||
if let Ok((id, status, board)) = row_result {
|
||||
self.have_ai_take_players_turn(Some(&conn), id, status, board)?;
|
||||
self.have_ai_take_players_turn(Some(&conn), id, status, &board)?;
|
||||
} else {
|
||||
unreachable!("This part should never execute");
|
||||
}
|
||||
|
@ -865,7 +1047,7 @@ impl DBHandler {
|
|||
conn: Option<&Connection>,
|
||||
game_id: u32,
|
||||
status: u32,
|
||||
board_string: String,
|
||||
board_string: &str,
|
||||
) -> Result<(), String> {
|
||||
if status > 1 {
|
||||
return Err(String::from(
|
||||
|
@ -873,15 +1055,13 @@ impl DBHandler {
|
|||
));
|
||||
}
|
||||
|
||||
if conn.is_none() {
|
||||
return self.have_ai_take_players_turn(
|
||||
Some(&self.get_conn(DBFirstRun::NotFirstRun)?),
|
||||
game_id,
|
||||
status,
|
||||
board_string,
|
||||
);
|
||||
}
|
||||
let conn = conn.unwrap();
|
||||
let mut _conn_result = Err(String::new());
|
||||
let conn = if let Some(c) = conn {
|
||||
c
|
||||
} else {
|
||||
_conn_result = self.get_conn(DBFirstRun::NotFirstRun);
|
||||
_conn_result.as_ref().unwrap()
|
||||
};
|
||||
|
||||
let is_cyan = status == 0;
|
||||
let board = board_from_string(board_string);
|
||||
|
@ -920,7 +1100,7 @@ impl DBHandler {
|
|||
});
|
||||
|
||||
// get board string from board while checking if game has ended
|
||||
let (board_string, end_state_opt) = string_from_board(board, ai_choice_pos);
|
||||
let (board_string, end_state_opt) = string_from_board(&board, ai_choice_pos);
|
||||
|
||||
let state;
|
||||
if let Some(board_state) = end_state_opt {
|
||||
|
@ -947,10 +1127,13 @@ impl DBHandler {
|
|||
}
|
||||
|
||||
fn cleanup_stale_games(&self, conn: Option<&Connection>) -> Result<(), String> {
|
||||
if conn.is_none() {
|
||||
return self.cleanup_stale_games(Some(&self.get_conn(DBFirstRun::NotFirstRun)?));
|
||||
}
|
||||
let conn = conn.unwrap();
|
||||
let mut _conn_result = Err(String::new());
|
||||
let conn = if let Some(c) = conn {
|
||||
c
|
||||
} else {
|
||||
_conn_result = self.get_conn(DBFirstRun::NotFirstRun);
|
||||
_conn_result.as_ref().unwrap()
|
||||
};
|
||||
|
||||
conn.execute(
|
||||
"DELETE FROM games WHERE unixepoch() - unixepoch(date_added) > ?;",
|
||||
|
@ -962,10 +1145,13 @@ impl DBHandler {
|
|||
}
|
||||
|
||||
fn cleanup_stale_players(&self, conn: Option<&Connection>) -> Result<(), String> {
|
||||
if conn.is_none() {
|
||||
return self.cleanup_stale_games(Some(&self.get_conn(DBFirstRun::NotFirstRun)?));
|
||||
}
|
||||
let conn = conn.unwrap();
|
||||
let mut _conn_result = Err(String::new());
|
||||
let conn = if let Some(c) = conn {
|
||||
c
|
||||
} else {
|
||||
_conn_result = self.get_conn(DBFirstRun::NotFirstRun);
|
||||
_conn_result.as_ref().unwrap()
|
||||
};
|
||||
|
||||
conn.execute(
|
||||
"DELETE FROM players WHERE unixepoch() - unixepoch(date_added) > ? AND game_id ISNULL;",
|
||||
|
@ -975,6 +1161,79 @@ impl DBHandler {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cleanup_stale_emotes(&self, conn: Option<&Connection>) -> Result<(), String> {
|
||||
let mut _conn_result = Err(String::new());
|
||||
let conn = if let Some(c) = conn {
|
||||
c
|
||||
} else {
|
||||
_conn_result = self.get_conn(DBFirstRun::NotFirstRun);
|
||||
_conn_result.as_ref().unwrap()
|
||||
};
|
||||
|
||||
conn.execute(
|
||||
"DELETE FROM emotes WHERE unixepoch() - unixepoch(date_added) > ?;",
|
||||
[GAME_CLEANUP_TIMEOUT],
|
||||
)
|
||||
.ok();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_new_sent_emote(
|
||||
&self,
|
||||
conn: Option<&Connection>,
|
||||
sender_id: u32,
|
||||
emote: EmoteEnum,
|
||||
) -> Result<(), String> {
|
||||
let mut _conn_result = Err(String::new());
|
||||
let conn = if let Some(c) = conn {
|
||||
c
|
||||
} else {
|
||||
_conn_result = self.get_conn(DBFirstRun::NotFirstRun);
|
||||
_conn_result.as_ref().unwrap()
|
||||
};
|
||||
|
||||
let mut prepared_stmt = conn.prepare("SELECT games.cyan_player, games.magenta_player FROM games JOIN players WHERE players.id = ? AND games.id = players.game_id;")
|
||||
.map_err(|_| String::from("Failed to prepare db query for getting opponent id for sending emote"))?;
|
||||
let row_result: Result<(Option<u32>, Option<u32>), RusqliteError> =
|
||||
prepared_stmt.query_row([sender_id], |row| Ok((row.get(0).ok(), row.get(1).ok())));
|
||||
if let Err(RusqliteError::QueryReturnedNoRows) = row_result {
|
||||
return Err(String::from("Failed to send emote, game doesn't exist"));
|
||||
} else if let Err(e) = row_result {
|
||||
return Err(format!("Failed to send emote: {:?}", e));
|
||||
}
|
||||
let (cyan_player_opt, magenta_player_opt) = row_result.unwrap();
|
||||
if cyan_player_opt.is_none() {
|
||||
return Err(String::from(
|
||||
"Failed to send emote, cyan player disconnected",
|
||||
));
|
||||
} else if magenta_player_opt.is_none() {
|
||||
return Err(String::from(
|
||||
"Failed to send emote, magenta player disconnected",
|
||||
));
|
||||
}
|
||||
let cyan_player_id = cyan_player_opt.unwrap();
|
||||
let magenta_player_id = magenta_player_opt.unwrap();
|
||||
|
||||
let receiver_id = if cyan_player_id == sender_id {
|
||||
magenta_player_id
|
||||
} else {
|
||||
cyan_player_id
|
||||
};
|
||||
|
||||
conn.execute(
|
||||
"INSERT INTO emotes (type, receiver_id) VALUES (?, ?);",
|
||||
params![String::from(emote), receiver_id],
|
||||
)
|
||||
.map_err(|_| {
|
||||
format!(
|
||||
"Failed to store emote from player {} to player {}",
|
||||
sender_id, receiver_id
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_db_handler_thread(
|
||||
|
@ -996,6 +1255,8 @@ pub fn start_db_handler_thread(
|
|||
return;
|
||||
}
|
||||
|
||||
let mut cleanup_instant = Instant::now();
|
||||
let cleanup_duration = Duration::from_secs(BACKEND_CLEANUP_INTERVAL_SECONDS);
|
||||
'outer: loop {
|
||||
if handler.handle_request() {
|
||||
handler.shutdown_tx.send(()).ok();
|
||||
|
@ -1006,11 +1267,21 @@ pub fn start_db_handler_thread(
|
|||
println!("{}", e);
|
||||
}
|
||||
|
||||
if let Err(e) = handler.cleanup_stale_games(None) {
|
||||
println!("{}", e);
|
||||
}
|
||||
if let Err(e) = handler.cleanup_stale_players(None) {
|
||||
println!("{}", e);
|
||||
if cleanup_instant.elapsed() > cleanup_duration {
|
||||
let conn = handler.get_conn(DBFirstRun::NotFirstRun).ok();
|
||||
cleanup_instant = Instant::now();
|
||||
if let Err(e) = handler.cleanup_stale_games(conn.as_ref()) {
|
||||
println!("{}", e);
|
||||
}
|
||||
if let Err(e) = handler.cleanup_stale_players(conn.as_ref()) {
|
||||
println!("{}", e);
|
||||
}
|
||||
if let Err(e) = handler.cleanup_stale_emotes(conn.as_ref()) {
|
||||
println!("{}", e);
|
||||
}
|
||||
if let Err(e) = handler.clear_empty_games(conn.as_ref()) {
|
||||
println!("{}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,4 +1,16 @@
|
|||
use crate::db_handler::{CheckPairingType, DBHandlerRequest, GetIDSenderType};
|
||||
//Four Line Dropper Backend - A server enabling networked multiplayer for Four Line Dropper
|
||||
//Copyright (C) 2022 Stephen Seo
|
||||
//
|
||||
//This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
//
|
||||
//This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
//
|
||||
//You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
use crate::{
|
||||
constants::BACKEND_PHRASE_MAX_LENGTH,
|
||||
db_handler::{CheckPairingType, DBHandlerRequest, GetIDSenderType},
|
||||
state::EmoteEnum,
|
||||
};
|
||||
|
||||
use std::{
|
||||
sync::mpsc::{sync_channel, SyncSender},
|
||||
|
@ -16,11 +28,12 @@ pub fn handle_json(
|
|||
) -> Result<String, String> {
|
||||
if let Some(Value::String(type_str)) = root.get("type") {
|
||||
match type_str.as_str() {
|
||||
"pairing_request" => handle_pairing_request(tx),
|
||||
"pairing_request" => handle_pairing_request(root, tx),
|
||||
"check_pairing" => handle_check_pairing(root, tx),
|
||||
"place_token" => handle_place_token(root, tx),
|
||||
"disconnect" => handle_disconnect(root, tx),
|
||||
"game_state" => handle_game_state(root, tx),
|
||||
"send_emote" => handle_send_emote(root, tx),
|
||||
_ => Err("{\"type\":\"invalid_type\"}".into()),
|
||||
}
|
||||
} else {
|
||||
|
@ -28,12 +41,44 @@ pub fn handle_json(
|
|||
}
|
||||
}
|
||||
|
||||
fn handle_pairing_request(tx: SyncSender<DBHandlerRequest>) -> Result<String, String> {
|
||||
fn handle_pairing_request(root: Value, tx: SyncSender<DBHandlerRequest>) -> Result<String, String> {
|
||||
let (player_tx, player_rx) = sync_channel::<GetIDSenderType>(1);
|
||||
if tx.send(DBHandlerRequest::GetID(player_tx)).is_err() {
|
||||
let mut phrase: Option<String> = None;
|
||||
if let Some(phrase_text) = root.get("phrase") {
|
||||
if let Some(mut phrase_str) = phrase_text.as_str() {
|
||||
if !phrase_str.is_empty() {
|
||||
if phrase_str.len() > BACKEND_PHRASE_MAX_LENGTH {
|
||||
let mut idx = BACKEND_PHRASE_MAX_LENGTH;
|
||||
while idx > 0 && !phrase_str.is_char_boundary(idx) {
|
||||
idx -= 1;
|
||||
}
|
||||
if idx == 0 {
|
||||
phrase_str = "";
|
||||
} else {
|
||||
phrase_str = phrase_str.split_at(idx).0;
|
||||
}
|
||||
}
|
||||
|
||||
if !phrase_str.is_empty() {
|
||||
phrase = Some(phrase_str.to_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if tx
|
||||
.send(DBHandlerRequest::GetID {
|
||||
response_sender: player_tx,
|
||||
phrase,
|
||||
})
|
||||
.is_err()
|
||||
{
|
||||
return Err("{\"type\":\"pairing_response\", \"status\":\"internal_error\"}".into());
|
||||
}
|
||||
if let Ok((pid, is_cyan_opt)) = player_rx.recv_timeout(DB_REQUEST_TIMEOUT) {
|
||||
if let Ok((pid_opt, is_cyan_opt)) = player_rx.recv_timeout(DB_REQUEST_TIMEOUT) {
|
||||
if pid_opt.is_none() {
|
||||
return Ok("{\"type\":\"pairing_response\", \"status\":\"too_many_players\"}".into());
|
||||
}
|
||||
let pid = pid_opt.unwrap();
|
||||
if let Some(is_cyan) = is_cyan_opt {
|
||||
Ok(format!(
|
||||
"{{\"type\":\"pairing_response\", \"id\": {}, \"status\": \"paired\", \"color\": \"{}\"}}",
|
||||
|
@ -71,21 +116,21 @@ fn handle_check_pairing(root: Value, tx: SyncSender<DBHandlerRequest>) -> Result
|
|||
})
|
||||
.is_err()
|
||||
{
|
||||
return Err("{\"type\":\"pairing_response\", \"status\":\"internal_error\"}".into());
|
||||
return Err("{\"type\":\"pairing_status\", \"status\":\"internal_error\"}".into());
|
||||
}
|
||||
if let Ok((exists, is_paired, is_cyan)) = request_rx.recv_timeout(DB_REQUEST_TIMEOUT) {
|
||||
if !exists {
|
||||
Err("{\"type\":\"pairing_response\", \"status\":\"unknown_id\"}".into())
|
||||
Err("{\"type\":\"pairing_status\", \"status\":\"unknown_id\"}".into())
|
||||
} else if is_paired {
|
||||
Ok(format!(
|
||||
"{{\"type\":\"pairing_response\", \"status\":\"paired\", \"color\":\"{}\"}}",
|
||||
"{{\"type\":\"pairing_status\", \"status\":\"paired\", \"color\":\"{}\"}}",
|
||||
if is_cyan { "cyan" } else { "magenta" }
|
||||
))
|
||||
} else {
|
||||
Ok("{\"type\"\"pairing_response\", \"status\":\"waiting\"}".into())
|
||||
Ok("{\"type\":\"pairing_status\", \"status\":\"waiting\"}".into())
|
||||
}
|
||||
} else {
|
||||
Err("{\"type\":\"pairing_response\", \"status\":\"internal_error_timeout\"}".into())
|
||||
Err("{\"type\":\"pairing_status\", \"status\":\"internal_error_timeout\"}".into())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -221,12 +266,27 @@ fn handle_game_state(root: Value, tx: SyncSender<DBHandlerRequest>) -> Result<St
|
|||
return Err("{\"type\":\"game_state\", \"status\":\"internal_error\"}".into());
|
||||
}
|
||||
|
||||
if let Ok((db_game_state, board_string_opt)) = resp_rx.recv_timeout(DB_REQUEST_TIMEOUT) {
|
||||
if let Ok((db_game_state, board_string_opt, updated_time_opt, received_emote_opt)) =
|
||||
resp_rx.recv_timeout(DB_REQUEST_TIMEOUT)
|
||||
{
|
||||
if let Some(board_string) = board_string_opt {
|
||||
Ok(format!(
|
||||
"{{\"type\":\"game_state\", \"status\":\"{}\", \"board\":\"{}\"}}",
|
||||
db_game_state, board_string
|
||||
))
|
||||
let updated_time = if let Some(time_string) = updated_time_opt {
|
||||
time_string
|
||||
} else {
|
||||
return Err("{\"type\":\"game_state\", \"status\":\"internal_error\"}".into());
|
||||
};
|
||||
|
||||
if let Some(emote) = received_emote_opt {
|
||||
Ok(format!(
|
||||
"{{\"type\":\"game_state\", \"status\":\"{}\", \"board\":\"{}\", \"peer_emote\": \"{}\", \"updated_time\": \"{}\"}}",
|
||||
db_game_state, board_string, emote, updated_time
|
||||
))
|
||||
} else {
|
||||
Ok(format!(
|
||||
"{{\"type\":\"game_state\", \"status\":\"{}\", \"board\":\"{}\", \"updated_time\": \"{}\"}}",
|
||||
db_game_state, board_string, updated_time
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Ok(format!(
|
||||
"{{\"type\":\"game_state\", \"status\":\"{}\"}}",
|
||||
|
@ -237,3 +297,56 @@ fn handle_game_state(root: Value, tx: SyncSender<DBHandlerRequest>) -> Result<St
|
|||
Err("{\"type\":\"game_state\", \"status\":\"internal_error_timeout\"}".into())
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_send_emote(root: Value, tx: SyncSender<DBHandlerRequest>) -> Result<String, String> {
|
||||
let id_option = root.get("id");
|
||||
if id_option.is_none() {
|
||||
return Err("{\"type\":\"invalid_syntax\"}".into());
|
||||
}
|
||||
let player_id = id_option
|
||||
.unwrap()
|
||||
.as_u64()
|
||||
.ok_or_else(|| String::from("{\"type\":\"invalid_syntax\"}"))?;
|
||||
let player_id: u32 = player_id
|
||||
.try_into()
|
||||
.map_err(|_| String::from("{\"type\":\"invalid_syntax\"}"))?;
|
||||
|
||||
let emote_type_option = root.get("emote");
|
||||
if emote_type_option.is_none() {
|
||||
return Err("{\"type\":\"invalid_syntax\"}".into());
|
||||
}
|
||||
let emote_type_option = emote_type_option.unwrap().as_str();
|
||||
if emote_type_option.is_none() {
|
||||
return Err("{\"type\":\"invalid_syntax\"}".into());
|
||||
}
|
||||
let emote_type = emote_type_option.unwrap();
|
||||
|
||||
let emote_enum: Result<EmoteEnum, ()> = emote_type.try_into();
|
||||
if emote_enum.is_err() {
|
||||
return Err("{\"type\":\"invalid_syntax\"}".into());
|
||||
}
|
||||
let emote_enum = emote_enum.unwrap();
|
||||
|
||||
let (resp_tx, resp_rx) = sync_channel(1);
|
||||
|
||||
if tx
|
||||
.send(DBHandlerRequest::SendEmote {
|
||||
id: player_id,
|
||||
emote_type: emote_enum,
|
||||
response_sender: resp_tx,
|
||||
})
|
||||
.is_err()
|
||||
{
|
||||
return Err("{\"type\":\"send_emote\", \"status\":\"internal_error\"}".into());
|
||||
}
|
||||
|
||||
if let Ok(db_response) = resp_rx.recv_timeout(DB_REQUEST_TIMEOUT) {
|
||||
if db_response.is_ok() {
|
||||
Ok("{\"type\":\"send_emote\", \"status\":\"ok\"}".into())
|
||||
} else {
|
||||
Err("{\"type\":\"send_emote\", \"status\":\"internal_error\"}".into())
|
||||
}
|
||||
} else {
|
||||
Err("{\"type\":\"send_emote\", \"status\":\"internal_error\"}".into())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,11 @@
|
|||
//Four Line Dropper Backend - A server enabling networked multiplayer for Four Line Dropper
|
||||
//Copyright (C) 2022 Stephen Seo
|
||||
//
|
||||
//This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
//
|
||||
//This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
//
|
||||
//You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
mod ai;
|
||||
mod constants;
|
||||
mod db_handler;
|
||||
|
@ -43,19 +51,29 @@ async fn main() {
|
|||
let db_tx_clone = db_tx_clone.clone();
|
||||
let s_helper_tx_clone = s_helper_tx.clone();
|
||||
async move {
|
||||
let body_str_result = dbg!(std::str::from_utf8(bytes.as_ref()));
|
||||
let body_str_result = std::str::from_utf8(bytes.as_ref());
|
||||
if let Ok(body_str) = body_str_result {
|
||||
let json_result = serde_json::from_str(body_str);
|
||||
if let Ok(json_value) = json_result {
|
||||
Ok(warp::reply::json(
|
||||
&json_handlers::handle_json(json_value, db_tx_clone, s_helper_tx_clone)
|
||||
Ok(warp::reply::with_header(
|
||||
json_handlers::handle_json(json_value, db_tx_clone, s_helper_tx_clone)
|
||||
.unwrap_or_else(|e| e),
|
||||
"Content-Type",
|
||||
"application/json",
|
||||
))
|
||||
} else {
|
||||
Ok(warp::reply::json(&String::from("{\"type\": \"invalid_syntax\"}")))
|
||||
Ok(warp::reply::with_header(
|
||||
String::from("{\"type\": \"invalid_syntax\"}"),
|
||||
"Content-Type",
|
||||
"application/json",
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Ok::<warp::reply::Json, Rejection>(warp::reply::json(&String::from("{\"type\": \"invalid_syntax\"}")))
|
||||
Ok::<warp::reply::WithHeader<String>, Rejection>(warp::reply::with_header(
|
||||
String::from("{\"type\": \"invalid_syntax\"}"),
|
||||
"Content-Type",
|
||||
"application/json",
|
||||
))
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,3 +1,11 @@
|
|||
//Four Line Dropper Backend - A server enabling networked multiplayer for Four Line Dropper
|
||||
//Copyright (C) 2022 Stephen Seo
|
||||
//
|
||||
//This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
//
|
||||
//This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
//
|
||||
//You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
use oorandom::Rand32;
|
||||
use rand::prelude::*;
|
||||
|
||||
|
|
674
front_end/COPYING
Normal file
674
front_end/COPYING
Normal file
|
@ -0,0 +1,674 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
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.
|
||||
|
||||
"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.
|
||||
|
||||
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.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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 Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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:
|
||||
|
||||
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".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
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.
|
||||
|
||||
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:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
"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.
|
||||
|
||||
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).
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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).
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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".
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
182
front_end/Cargo.lock
generated
182
front_end/Cargo.lock
generated
|
@ -4,9 +4,9 @@ version = 3
|
|||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80"
|
||||
|
||||
[[package]]
|
||||
name = "boolinator"
|
||||
|
@ -16,9 +16,9 @@ checksum = "cfa8873f51c92e232f9bac4065cddef41b714152812bfc5f7672ba16d6ef8cd9"
|
|||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.9.1"
|
||||
version = "3.15.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899"
|
||||
checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
|
@ -71,10 +71,11 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gloo-console"
|
||||
version = "0.2.1"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3907f786f65bbb4f419e918b0c5674175ef1c231ecda93b2dbd65fd1e8882637"
|
||||
checksum = "82b7ce3c05debe147233596904981848862b068862e9ec3e34be446077190d3f"
|
||||
dependencies = [
|
||||
"gloo-utils",
|
||||
"js-sys",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
|
@ -83,9 +84,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gloo-dialogs"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ffb557a2ea2ed283f1334423d303a336fad55fb8572d51ae488f828b1464b40"
|
||||
checksum = "67062364ac72d27f08445a46cab428188e2e224ec9e37efdba48ae8c289002e6"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
|
@ -93,9 +94,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gloo-events"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "088514ec8ef284891c762c88a66b639b3a730134714692ee31829765c5bc814f"
|
||||
checksum = "68b107f8abed8105e4182de63845afcc7b69c098b7852a813ea7462a320992fc"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
|
@ -103,9 +104,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gloo-file"
|
||||
version = "0.2.1"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa5d6084efa4a2b182ef3a8649cb6506cb4843f22cf907c6e0a799944248ae90"
|
||||
checksum = "a8d5564e570a38b43d78bdc063374a0c3098c4f0d64005b12f9bbe87e869b6d7"
|
||||
dependencies = [
|
||||
"gloo-events",
|
||||
"js-sys",
|
||||
|
@ -115,9 +116,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gloo-render"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b4cda6e149df3bb4a3c6a343873903e5bcc2448a9877d61bb8274806ad67f6e"
|
||||
checksum = "2fd9306aef67cfd4449823aadcd14e3958e0800aa2183955a309112a84ec7764"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
|
@ -125,9 +126,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gloo-storage"
|
||||
version = "0.2.0"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b5057761927af1b1929d02b1f49cf83553dd347a473ee7c8bb08420f2673ffc"
|
||||
checksum = "5d6ab60bf5dbfd6f0ed1f7843da31b41010515c745735c970e821945ca91e480"
|
||||
dependencies = [
|
||||
"gloo-utils",
|
||||
"js-sys",
|
||||
|
@ -140,9 +141,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gloo-timers"
|
||||
version = "0.2.3"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d12a7f4e95cfe710f1d624fb1210b7d961a5fb05c4fd942f4feab06e61f590e"
|
||||
checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
|
@ -150,26 +151,28 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gloo-utils"
|
||||
version = "0.1.2"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05c77af6f96a4f9e27c8ac23a88407381a31f4a74c3fb985c85aa79b8d898136"
|
||||
checksum = "037fcb07216cb3a30f7292bd0176b050b7b9a052ba830ef7d5d65f6dc64ba58e"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.11.2"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.8.0"
|
||||
version = "1.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223"
|
||||
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown",
|
||||
|
@ -177,15 +180,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.1"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
|
||||
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.56"
|
||||
version = "0.3.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04"
|
||||
checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
@ -198,12 +201,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
|||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.14"
|
||||
version = "0.4.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||
|
||||
[[package]]
|
||||
name = "oorandom"
|
||||
|
@ -220,7 +226,7 @@ dependencies = [
|
|||
"proc-macro-error-attr",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 1.0.109",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
|
@ -237,59 +243,59 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.36"
|
||||
version = "1.0.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
|
||||
checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.15"
|
||||
version = "1.0.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
|
||||
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.9"
|
||||
version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f"
|
||||
checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
|
||||
|
||||
[[package]]
|
||||
name = "scoped-tls-hkt"
|
||||
version = "0.1.2"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2e9d7eaddb227e8fbaaa71136ae0e1e913ca159b86c7da82f3e8f0044ad3a63"
|
||||
checksum = "3ddc765d3410d9f6c6ca071bf0b67f6b01e3ec4595dc3892f02677e75819dddc"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.136"
|
||||
version = "1.0.197"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
|
||||
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.136"
|
||||
version = "1.0.197"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
|
||||
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.58",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.79"
|
||||
version = "1.0.115"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95"
|
||||
checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
|
@ -298,46 +304,60 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.5"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5"
|
||||
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.86"
|
||||
version = "1.0.109"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
|
||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-xid",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.30"
|
||||
version = "1.0.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
|
||||
checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.30"
|
||||
version = "1.0.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
|
||||
checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.58",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.2"
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
|
@ -347,9 +367,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.79"
|
||||
version = "0.2.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06"
|
||||
checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"serde",
|
||||
|
@ -359,24 +379,24 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.79"
|
||||
version = "0.2.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca"
|
||||
checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.58",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.29"
|
||||
version = "0.4.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2eb6ec270a31b1d3c7e266b999739109abce8b6c87e4b31fcfcd788b65267395"
|
||||
checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
|
@ -386,9 +406,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.79"
|
||||
version = "0.2.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01"
|
||||
checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
|
@ -396,22 +416,22 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.79"
|
||||
version = "0.2.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc"
|
||||
checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.58",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.79"
|
||||
version = "0.2.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2"
|
||||
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-logger"
|
||||
|
@ -426,9 +446,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.56"
|
||||
version = "0.3.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb"
|
||||
checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
|
@ -464,5 +484,5 @@ dependencies = [
|
|||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
|
|
@ -9,7 +9,7 @@ edition = "2021"
|
|||
yew = "0.19"
|
||||
log = "0.4.6"
|
||||
wasm-logger = "0.2.0"
|
||||
web-sys = { version = "0.3.56", features = ["Window", "Document", "Element", "Request", "RequestInit", "Headers", "RequestMode", "Response", "ReadableStream"] }
|
||||
web-sys = { version = "0.3.56", features = ["Window", "Document", "Element", "Request", "RequestInit", "Headers", "RequestMode", "Response", "ReadableStream", "AddEventListenerOptions", "EventListenerOptions", "EventTarget"] }
|
||||
js-sys = "0.3.56"
|
||||
oorandom = "11.1.3"
|
||||
wasm-bindgen = { version = "0.2.79", features = ["serde-serialize"] }
|
||||
|
|
|
@ -14,3 +14,6 @@
|
|||
The webserver hosting this `front_end` is [here](https://asdm.seodisparate.com).
|
||||
|
||||
Note that this website may not host the latest version of this `front_end`.
|
||||
|
||||
See [choose-a-license](https://choosealicense.com/licenses/gpl-3.0) for details
|
||||
about the software license used.
|
||||
|
|
|
@ -42,9 +42,50 @@
|
|||
grid-row: 2;
|
||||
grid-column: 3;
|
||||
}
|
||||
button.menuMultiplayer {
|
||||
div.multiplayerMenu {
|
||||
grid-row: 2;
|
||||
grid-column: 4;
|
||||
|
||||
display: grid;
|
||||
}
|
||||
div.emote_wrapper {
|
||||
grid-row: 4;
|
||||
grid-column: 8;
|
||||
display: grid;
|
||||
}
|
||||
button.emote {
|
||||
font-size: 2em;
|
||||
}
|
||||
b.emote {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
button#emote_smile {
|
||||
grid-row: 1;
|
||||
grid-column: 1;
|
||||
}
|
||||
button#emote_neutral {
|
||||
grid-row: 1;
|
||||
grid-column: 2;
|
||||
}
|
||||
button#emote_frown {
|
||||
grid-row: 1;
|
||||
grid-column: 3;
|
||||
}
|
||||
button#emote_think {
|
||||
grid-row: 1;
|
||||
grid-column: 4;
|
||||
}
|
||||
button.networkedMultiplayer {
|
||||
grid-row: 1;
|
||||
grid-column: 1;
|
||||
}
|
||||
button.NMPhrase {
|
||||
grid-row: 2;
|
||||
grid-column: 1;
|
||||
}
|
||||
input.NMPhrase {
|
||||
grid-row: 3;
|
||||
grid-column: 1;
|
||||
}
|
||||
b.menuText {
|
||||
color: #FFF;
|
||||
|
@ -188,6 +229,12 @@
|
|||
opacity: 1;
|
||||
}
|
||||
}
|
||||
button#resetbutton {
|
||||
background-color: #C55;
|
||||
color: #FFF;
|
||||
grid-row: 2;
|
||||
grid-column: 8;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
</html>
|
||||
|
|
|
@ -1,3 +1,11 @@
|
|||
//Four Line Dropper Frontend/Backend - A webapp that allows one to play a game of Four Line Dropper
|
||||
//Copyright (C) 2022 Stephen Seo
|
||||
//
|
||||
//This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
//
|
||||
//This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
//
|
||||
//You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::constants::{AI_EASY_MAX_CHOICES, AI_NORMAL_MAX_CHOICES, COLS, ROWS};
|
||||
|
@ -5,6 +13,9 @@ use crate::game_logic::check_win_draw;
|
|||
use crate::random_helper::get_seeded_random;
|
||||
use crate::state::{board_deep_clone, BoardState, BoardType, Turn};
|
||||
|
||||
const AI_THIRD_MAX_UTILITY: f64 = 0.89;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum AIDifficulty {
|
||||
Easy,
|
||||
|
@ -168,25 +179,25 @@ fn get_utility_for_slot(player: Turn, slot: SlotChoice, board: &BoardType) -> Op
|
|||
|
||||
// check if placing a token here connects 2 pieces
|
||||
if get_block_amount(player.get_opposite(), idx, 2, board) {
|
||||
utility *= 1.5;
|
||||
if utility >= 0.8 {
|
||||
utility = 0.8;
|
||||
utility *= 1.22;
|
||||
if utility >= AI_THIRD_MAX_UTILITY {
|
||||
utility = AI_THIRD_MAX_UTILITY;
|
||||
}
|
||||
}
|
||||
|
||||
// check if placing a token here blocks 2 pieces
|
||||
if get_block_amount(player, idx, 2, board) {
|
||||
utility *= 1.2;
|
||||
if utility >= 0.8 {
|
||||
utility = 0.8;
|
||||
utility *= 1.11;
|
||||
if utility >= AI_THIRD_MAX_UTILITY {
|
||||
utility = AI_THIRD_MAX_UTILITY;
|
||||
}
|
||||
}
|
||||
|
||||
// check if placing a token here connects 1 piece
|
||||
if get_block_amount(player.get_opposite(), idx, 1, board) {
|
||||
utility *= 1.09;
|
||||
if utility >= 0.8 {
|
||||
utility = 0.8;
|
||||
utility *= 1.05;
|
||||
if utility >= AI_THIRD_MAX_UTILITY {
|
||||
utility = AI_THIRD_MAX_UTILITY;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -322,3 +333,24 @@ fn get_block_amount(player: Turn, idx: usize, amount: usize, board: &BoardType)
|
|||
// exhausted all possible potential wins, therefore does not block a win
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::state::new_empty_board;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_get_block_amount() {
|
||||
let board = new_empty_board();
|
||||
board[51].set(BoardState::Cyan);
|
||||
board[52].set(BoardState::Cyan);
|
||||
board[53].set(BoardState::Cyan);
|
||||
assert!(!get_block_amount(Turn::MagentaPlayer, 50, 4, &board));
|
||||
assert!(get_block_amount(Turn::MagentaPlayer, 50, 3, &board));
|
||||
assert!(get_block_amount(Turn::MagentaPlayer, 50, 2, &board));
|
||||
assert!(!get_block_amount(Turn::MagentaPlayer, 54, 4, &board));
|
||||
assert!(get_block_amount(Turn::MagentaPlayer, 54, 3, &board));
|
||||
assert!(get_block_amount(Turn::MagentaPlayer, 54, 2, &board));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,44 @@
|
|||
//Four Line Dropper Frontend/Backend - A webapp that allows one to play a game of Four Line Dropper
|
||||
//Copyright (C) 2022 Stephen Seo
|
||||
//
|
||||
//This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
//
|
||||
//This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
//
|
||||
//You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
pub const ROWS: u8 = 8;
|
||||
pub const COLS: u8 = 7;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub const INFO_TEXT_MAX_ITEMS: u32 = 100;
|
||||
|
||||
pub const AI_EASY_MAX_CHOICES: usize = 5;
|
||||
pub const AI_NORMAL_MAX_CHOICES: usize = 3;
|
||||
#[allow(dead_code)]
|
||||
pub const AI_CHOICE_DURATION_MILLIS: i32 = 1000;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub const PLAYER_COUNT_LIMIT: usize = 1000;
|
||||
#[allow(dead_code)]
|
||||
pub const TURN_SECONDS: u64 = 25;
|
||||
#[allow(dead_code)]
|
||||
pub const GAME_CLEANUP_TIMEOUT: u64 = (TURN_SECONDS + 1) * ((ROWS * COLS) as u64 + 5u64);
|
||||
#[allow(dead_code)]
|
||||
pub const PLAYER_CLEANUP_TIMEOUT: u64 = 300;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub const BACKEND_TICK_DURATION_MILLIS: i32 = 500;
|
||||
#[allow(dead_code)]
|
||||
pub const BACKEND_CLEANUP_INTERVAL_SECONDS: u64 = 120;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub const BACKEND_PHRASE_MAX_LENGTH: usize = 128;
|
||||
|
||||
// TODO: Change this to "https://asdm.seodisparate.com/api" when backend is installed
|
||||
pub const BACKEND_URL: &str = "http://localhost:1237/";
|
||||
#[allow(dead_code)]
|
||||
#[cfg(debug_assertions)]
|
||||
pub const BACKEND_URL: &str = "http://testlocalhost/api";
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[cfg(not(debug_assertions))]
|
||||
pub const BACKEND_URL: &str = "https://asdm.seodisparate.com/api";
|
||||
|
|
|
@ -1,3 +1,11 @@
|
|||
//Four Line Dropper Frontend/Backend - A webapp that allows one to play a game of Four Line Dropper
|
||||
//Copyright (C) 2022 Stephen Seo
|
||||
//
|
||||
//This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
//
|
||||
//This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
//
|
||||
//You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
use crate::constants::{COLS, ROWS};
|
||||
use crate::state::{BoardState, BoardType};
|
||||
|
||||
|
|
|
@ -1,5 +1,18 @@
|
|||
use wasm_bindgen::JsValue;
|
||||
use web_sys::{window, Document, Request, RequestInit, Window};
|
||||
//Four Line Dropper Frontend - A webapp that allows one to play a game of Four Line Dropper
|
||||
//Copyright (C) 2022 Stephen Seo
|
||||
//
|
||||
//This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
//
|
||||
//This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
//
|
||||
//You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
use js_sys::{Function, JsString, Promise};
|
||||
use std::collections::HashMap;
|
||||
use wasm_bindgen::{JsCast, JsValue};
|
||||
use wasm_bindgen_futures::JsFuture;
|
||||
use web_sys::{window, Document, Window};
|
||||
|
||||
use crate::constants::BACKEND_URL;
|
||||
|
||||
pub fn get_window_document() -> Result<(Window, Document), String> {
|
||||
let window = window().ok_or_else(|| String::from("Failed to get window"))?;
|
||||
|
@ -59,7 +72,7 @@ pub fn element_append_class(document: &Document, id: &str, class: &str) -> Resul
|
|||
.get_element_by_id(id)
|
||||
.ok_or_else(|| format!("Failed to get element with id \"{}\"", id))?;
|
||||
let new_class = format!("{} {}", element.class_name(), class);
|
||||
element.set_class_name(&new_class);
|
||||
element.set_class_name(&string_remove_extra_whitespace(&new_class));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -74,35 +87,89 @@ pub fn element_remove_class(document: &Document, id: &str, class: &str) -> Resul
|
|||
let mut remaining = element_class.split_off(idx);
|
||||
element_class += &remaining.split_off(class.len());
|
||||
}
|
||||
element.set_class_name(&element_class);
|
||||
element.set_class_name(&string_remove_extra_whitespace(&element_class));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn create_json_request(target_url: &str, json_body: &str) -> Result<Request, String> {
|
||||
let mut req_init: RequestInit = RequestInit::new();
|
||||
req_init.body(Some(&JsValue::from_str(json_body)));
|
||||
req_init.method("POST");
|
||||
// TODO omit the NoCors when hosted on website
|
||||
req_init.mode(web_sys::RequestMode::NoCors);
|
||||
// req_init.headers(
|
||||
// &JsValue::from_str("{'Content-Type': 'application/json'}"),
|
||||
// &JsValue::from_serde("{'Content-Type': 'application/json'}")
|
||||
// .map_err(|e| format!("{}", e))?,
|
||||
// &JsValue::from_serde("'headers': { 'Content-Type': 'application/json' }")
|
||||
// .map_err(|e| format!("{}", e))?,
|
||||
// );
|
||||
fn string_remove_extra_whitespace(s: &str) -> String {
|
||||
let mut replacement_string = String::with_capacity(s.len());
|
||||
let mut is_space = false;
|
||||
for c in s.chars() {
|
||||
if c != ' ' {
|
||||
replacement_string.push(c);
|
||||
is_space = false;
|
||||
} else if is_space {
|
||||
continue;
|
||||
} else {
|
||||
replacement_string.push(c);
|
||||
is_space = true;
|
||||
}
|
||||
}
|
||||
|
||||
let request: Request =
|
||||
Request::new_with_str_and_init(target_url, &req_init).map_err(|e| format!("{:?}", e))?;
|
||||
request
|
||||
.headers()
|
||||
.set("Content-Type", "application/json")
|
||||
.map_err(|e| format!("{:?}", e))?;
|
||||
request
|
||||
.headers()
|
||||
.set("Accept", "application/json")
|
||||
.map_err(|e| format!("{:?}", e))?;
|
||||
|
||||
Ok(request)
|
||||
replacement_string
|
||||
}
|
||||
|
||||
pub fn element_has_class(document: &Document, id: &str, class: &str) -> Result<bool, String> {
|
||||
let element = document
|
||||
.get_element_by_id(id)
|
||||
.ok_or_else(|| format!("Failed to get element with id \"{}\"", id))?;
|
||||
let element_class: String = element.class_name();
|
||||
|
||||
Ok(element_class.contains(class))
|
||||
}
|
||||
|
||||
pub async fn send_to_backend(entries: HashMap<String, String>) -> Result<String, String> {
|
||||
let mut send_json_string = String::from("{");
|
||||
for (key, value) in entries {
|
||||
send_json_string.push('"');
|
||||
send_json_string.push_str(&key);
|
||||
send_json_string.push_str("\":");
|
||||
if key == "id" || key == "position" {
|
||||
send_json_string.push_str(&value);
|
||||
} else {
|
||||
send_json_string.push('"');
|
||||
send_json_string.push_str(&value);
|
||||
send_json_string.push('"');
|
||||
}
|
||||
send_json_string.push(',');
|
||||
}
|
||||
send_json_string.truncate(send_json_string.len() - 1);
|
||||
send_json_string.push('}');
|
||||
|
||||
// TODO check usage of "no-cors"
|
||||
let function = Function::new_no_args(&format!(
|
||||
"
|
||||
let fetch_settings = {{}};
|
||||
fetch_settings.method = 'POST';
|
||||
fetch_settings.headers = {{}};
|
||||
fetch_settings.headers['Content-Type'] = 'application/json';
|
||||
fetch_settings.headers['Accept'] = 'text/html,application/json';
|
||||
//fetch_settings.mode = 'no-cors';
|
||||
fetch_settings.body = '{}';
|
||||
|
||||
return fetch('{}', fetch_settings)
|
||||
.then((response) => {{
|
||||
return response.text();
|
||||
}});
|
||||
",
|
||||
send_json_string, BACKEND_URL,
|
||||
));
|
||||
|
||||
let jsvalue: JsValue = function
|
||||
.call0(&function)
|
||||
.map_err(|e| format!("Failed to POST to backend: {:?}", e))?;
|
||||
let promise: Promise = jsvalue.dyn_into().map_err(|e| {
|
||||
format!(
|
||||
"Failed to get Promise out of JsValue when POSTing to backend: {:?}",
|
||||
e
|
||||
)
|
||||
})?;
|
||||
let future_result: JsValue = JsFuture::from(promise)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to await promise when POSTing to backend: {:?}", e))?;
|
||||
|
||||
let json_string = String::from(JsString::from(future_result));
|
||||
|
||||
Ok(json_string)
|
||||
}
|
||||
|
|
|
@ -1,3 +1,11 @@
|
|||
//Four Line Dropper Frontend - A webapp that allows one to play a game of Four Line Dropper
|
||||
//Copyright (C) 2022 Stephen Seo
|
||||
//
|
||||
//This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
//
|
||||
//This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
//
|
||||
//You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
mod ai;
|
||||
mod constants;
|
||||
mod game_logic;
|
||||
|
|
|
@ -1,3 +1,11 @@
|
|||
//Four Line Dropper Frontend - A webapp that allows one to play a game of Four Line Dropper
|
||||
//Copyright (C) 2022 Stephen Seo
|
||||
//
|
||||
//This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
//
|
||||
//This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
//
|
||||
//You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
use js_sys::Math::random;
|
||||
use oorandom::Rand32;
|
||||
|
||||
|
|
|
@ -1,13 +1,24 @@
|
|||
//Four Line Dropper Frontend/Backend - A webapp that allows one to play a game of Four Line Dropper
|
||||
//Copyright (C) 2022 Stephen Seo
|
||||
//
|
||||
//This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
//
|
||||
//This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
//
|
||||
//You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
use crate::ai::AIDifficulty;
|
||||
use crate::constants::{COLS, ROWS};
|
||||
use crate::game_logic::{check_win_draw, WinType};
|
||||
|
||||
use std::cell::Cell;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::collections::hash_set::HashSet;
|
||||
use std::fmt::Display;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum GameState {
|
||||
MainMenu,
|
||||
SinglePlayer(Turn, AIDifficulty),
|
||||
|
@ -16,21 +27,119 @@ pub enum GameState {
|
|||
paired: bool,
|
||||
current_side: Option<Turn>,
|
||||
current_turn: Turn,
|
||||
phrase: Option<String>,
|
||||
},
|
||||
PostGameResults(BoardState),
|
||||
}
|
||||
|
||||
impl GameState {
|
||||
pub fn is_networked_multiplayer(self) -> bool {
|
||||
#[allow(dead_code)]
|
||||
pub fn is_networked_multiplayer(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
*self,
|
||||
GameState::NetworkedMultiplayer {
|
||||
paired,
|
||||
current_side,
|
||||
current_turn
|
||||
paired: _,
|
||||
current_side: _,
|
||||
current_turn: _,
|
||||
phrase: _,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn set_networked_paired(&mut self) {
|
||||
if let GameState::NetworkedMultiplayer {
|
||||
ref mut paired,
|
||||
current_side: _,
|
||||
current_turn: _,
|
||||
phrase: _,
|
||||
} = self
|
||||
{
|
||||
*paired = true;
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_networked_current_side(&self) -> Option<Turn> {
|
||||
if let GameState::NetworkedMultiplayer {
|
||||
paired: _,
|
||||
current_side,
|
||||
current_turn: _,
|
||||
phrase: _,
|
||||
} = *self
|
||||
{
|
||||
current_side
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn set_networked_current_side(&mut self, side: Option<Turn>) {
|
||||
if let GameState::NetworkedMultiplayer {
|
||||
paired: _,
|
||||
ref mut current_side,
|
||||
current_turn: _,
|
||||
phrase: _,
|
||||
} = self
|
||||
{
|
||||
*current_side = side;
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_current_turn(&self) -> Turn {
|
||||
if let GameState::SinglePlayer(turn, _) = *self {
|
||||
turn
|
||||
} else if let GameState::NetworkedMultiplayer {
|
||||
paired: _,
|
||||
current_side: _,
|
||||
current_turn,
|
||||
phrase: _,
|
||||
} = *self
|
||||
{
|
||||
current_turn
|
||||
} else {
|
||||
Turn::CyanPlayer
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn set_networked_current_turn(&mut self, turn: Turn) {
|
||||
if let GameState::NetworkedMultiplayer {
|
||||
paired: _,
|
||||
current_side: _,
|
||||
ref mut current_turn,
|
||||
phrase: _,
|
||||
} = self
|
||||
{
|
||||
*current_turn = turn;
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_phrase(&self) -> Option<String> {
|
||||
if let GameState::NetworkedMultiplayer {
|
||||
paired: _,
|
||||
current_side: _,
|
||||
current_turn: _,
|
||||
phrase,
|
||||
} = self
|
||||
{
|
||||
phrase.clone()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_singleplayer_current_side(&self) -> Option<Turn> {
|
||||
if let GameState::SinglePlayer(turn, _) = *self {
|
||||
Some(turn)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for GameState {
|
||||
|
@ -44,15 +153,17 @@ impl From<MainMenuMessage> for GameState {
|
|||
match msg {
|
||||
MainMenuMessage::SinglePlayer(t, ai) => GameState::SinglePlayer(t, ai),
|
||||
MainMenuMessage::LocalMultiplayer => GameState::LocalMultiplayer,
|
||||
MainMenuMessage::NetworkedMultiplayer => GameState::NetworkedMultiplayer {
|
||||
MainMenuMessage::NetworkedMultiplayer(phrase) => GameState::NetworkedMultiplayer {
|
||||
paired: false,
|
||||
current_side: None,
|
||||
current_turn: Turn::CyanPlayer,
|
||||
phrase,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum BoardState {
|
||||
Empty,
|
||||
|
@ -90,10 +201,12 @@ impl From<Turn> for BoardState {
|
|||
}
|
||||
|
||||
impl BoardState {
|
||||
#[allow(dead_code)]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
*self == BoardState::Empty
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn is_win(self) -> bool {
|
||||
match self {
|
||||
BoardState::Empty | BoardState::Cyan | BoardState::Magenta => false,
|
||||
|
@ -101,6 +214,7 @@ impl BoardState {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn into_win(self) -> Self {
|
||||
match self {
|
||||
BoardState::Empty => BoardState::Empty,
|
||||
|
@ -109,8 +223,9 @@ impl BoardState {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn from_win(&self) -> Self {
|
||||
match *self {
|
||||
#[allow(dead_code, clippy::wrong_self_convention)]
|
||||
pub fn from_win(self) -> Self {
|
||||
match self {
|
||||
BoardState::Empty => BoardState::Empty,
|
||||
BoardState::Cyan | BoardState::CyanWin => BoardState::Cyan,
|
||||
BoardState::Magenta | BoardState::MagentaWin => BoardState::Magenta,
|
||||
|
@ -118,6 +233,7 @@ impl BoardState {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Turn {
|
||||
CyanPlayer,
|
||||
|
@ -143,6 +259,7 @@ impl From<BoardState> for Turn {
|
|||
}
|
||||
|
||||
impl Turn {
|
||||
#[allow(dead_code)]
|
||||
pub fn get_color(&self) -> &str {
|
||||
match *self {
|
||||
Turn::CyanPlayer => "cyan",
|
||||
|
@ -160,6 +277,7 @@ impl Turn {
|
|||
|
||||
pub type BoardType = [Rc<Cell<BoardState>>; 56];
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn new_empty_board() -> BoardType {
|
||||
[
|
||||
Rc::new(Cell::new(BoardState::default())),
|
||||
|
@ -221,6 +339,7 @@ pub fn new_empty_board() -> BoardType {
|
|||
]
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn board_deep_clone(board: &BoardType) -> BoardType {
|
||||
let cloned_board = new_empty_board();
|
||||
for i in 0..board.len() {
|
||||
|
@ -232,6 +351,7 @@ pub fn board_deep_clone(board: &BoardType) -> BoardType {
|
|||
|
||||
pub type PlacedType = [Rc<Cell<bool>>; 56];
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn new_placed() -> PlacedType {
|
||||
[
|
||||
Rc::new(Cell::new(false)),
|
||||
|
@ -293,10 +413,11 @@ pub fn new_placed() -> PlacedType {
|
|||
]
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct SharedState {
|
||||
pub board: BoardType,
|
||||
pub game_state: Rc<Cell<GameState>>,
|
||||
pub game_state: Rc<RefCell<GameState>>,
|
||||
pub turn: Rc<Cell<Turn>>,
|
||||
pub placed: PlacedType,
|
||||
}
|
||||
|
@ -306,7 +427,7 @@ impl Default for SharedState {
|
|||
Self {
|
||||
// cannot use [<type>; 56] because Rc does not impl Copy
|
||||
board: new_empty_board(),
|
||||
game_state: Rc::new(Cell::new(GameState::default())),
|
||||
game_state: Rc::new(RefCell::new(GameState::default())),
|
||||
turn: Rc::new(Cell::new(Turn::CyanPlayer)),
|
||||
placed: new_placed(),
|
||||
}
|
||||
|
@ -315,13 +436,15 @@ impl Default for SharedState {
|
|||
|
||||
// This enum moved from yew_components module so that this module would have no
|
||||
// dependencies on the yew_components module
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum MainMenuMessage {
|
||||
SinglePlayer(Turn, AIDifficulty),
|
||||
LocalMultiplayer,
|
||||
NetworkedMultiplayer,
|
||||
NetworkedMultiplayer(Option<String>),
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn new_string_board() -> String {
|
||||
let mut board = String::with_capacity(56);
|
||||
for _i in 0..56 {
|
||||
|
@ -330,14 +453,17 @@ pub fn new_string_board() -> String {
|
|||
board
|
||||
}
|
||||
|
||||
pub fn board_from_string(board_string: String) -> BoardType {
|
||||
#[allow(dead_code)]
|
||||
pub fn board_from_string(board_string: &str) -> BoardType {
|
||||
let board = new_empty_board();
|
||||
|
||||
for (idx, c) in board_string.chars().enumerate() {
|
||||
match c {
|
||||
'a' => board[idx].replace(BoardState::Empty),
|
||||
'b' | 'd' | 'f' => board[idx].replace(BoardState::Cyan),
|
||||
'c' | 'e' | 'g' => board[idx].replace(BoardState::Magenta),
|
||||
'b' | 'f' => board[idx].replace(BoardState::Cyan),
|
||||
'd' | 'h' => board[idx].replace(BoardState::CyanWin),
|
||||
'c' | 'g' => board[idx].replace(BoardState::Magenta),
|
||||
'e' | 'i' => board[idx].replace(BoardState::MagentaWin),
|
||||
_ => BoardState::Empty,
|
||||
};
|
||||
}
|
||||
|
@ -347,12 +473,13 @@ pub fn board_from_string(board_string: String) -> BoardType {
|
|||
|
||||
/// Returns the board as a String, and None if game has not ended, Empty if game
|
||||
/// ended in a draw, or a player if that player has won
|
||||
pub fn string_from_board(board: BoardType, placed: usize) -> (String, Option<BoardState>) {
|
||||
#[allow(dead_code)]
|
||||
pub fn string_from_board(board: &BoardType, placed: usize) -> (String, Option<BoardState>) {
|
||||
let mut board_string = String::with_capacity(56);
|
||||
|
||||
// check for winning pieces
|
||||
let mut win_set: HashSet<usize> = HashSet::new();
|
||||
let win_opt = check_win_draw(&board);
|
||||
let win_opt = check_win_draw(board);
|
||||
if let Some((_board_state, win_type)) = win_opt {
|
||||
match win_type {
|
||||
WinType::Horizontal(pos) => {
|
||||
|
@ -389,7 +516,11 @@ pub fn string_from_board(board: BoardType, placed: usize) -> (String, Option<Boa
|
|||
}
|
||||
BoardState::Cyan | BoardState::CyanWin => {
|
||||
if win_set.contains(&idx) {
|
||||
'd'
|
||||
if idx == placed {
|
||||
'h'
|
||||
} else {
|
||||
'd'
|
||||
}
|
||||
} else if idx == placed {
|
||||
'f'
|
||||
} else {
|
||||
|
@ -398,7 +529,11 @@ pub fn string_from_board(board: BoardType, placed: usize) -> (String, Option<Boa
|
|||
}
|
||||
BoardState::Magenta | BoardState::MagentaWin => {
|
||||
if win_set.contains(&idx) {
|
||||
'e'
|
||||
if idx == placed {
|
||||
'i'
|
||||
} else {
|
||||
'e'
|
||||
}
|
||||
} else if idx == placed {
|
||||
'g'
|
||||
} else {
|
||||
|
@ -411,9 +546,11 @@ pub fn string_from_board(board: BoardType, placed: usize) -> (String, Option<Boa
|
|||
if is_full && win_set.is_empty() {
|
||||
(board_string, Some(BoardState::Empty))
|
||||
} else if !win_set.is_empty() {
|
||||
let winning_char: char =
|
||||
board_string.chars().collect::<Vec<char>>()[*win_set.iter().next().unwrap()];
|
||||
(
|
||||
board_string.clone(),
|
||||
if board_string.chars().collect::<Vec<char>>()[*win_set.iter().next().unwrap()] == 'd' {
|
||||
if winning_char == 'd' || winning_char == 'h' {
|
||||
Some(BoardState::CyanWin)
|
||||
} else {
|
||||
Some(BoardState::MagentaWin)
|
||||
|
@ -424,6 +561,127 @@ pub fn string_from_board(board: BoardType, placed: usize) -> (String, Option<Boa
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct PairingRequestResponse {
|
||||
pub r#type: String,
|
||||
pub id: u32,
|
||||
pub status: String,
|
||||
pub color: Option<String>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct PairingStatusResponse {
|
||||
pub r#type: String,
|
||||
pub status: String,
|
||||
pub color: Option<String>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct GameStateResponse {
|
||||
pub r#type: String,
|
||||
pub status: String,
|
||||
pub board: Option<String>,
|
||||
pub peer_emote: Option<String>,
|
||||
pub updated_time: Option<String>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct PlaceTokenResponse {
|
||||
pub r#type: String,
|
||||
pub status: String,
|
||||
pub board: String,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct SendEmoteRequestResponse {
|
||||
pub r#type: String,
|
||||
pub status: String,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum NetworkedGameState {
|
||||
CyanTurn,
|
||||
MagentaTurn,
|
||||
CyanWon,
|
||||
MagentaWon,
|
||||
Draw,
|
||||
Disconnected,
|
||||
InternalError,
|
||||
NotPaired,
|
||||
UnknownID,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum PlacedEnum {
|
||||
Accepted,
|
||||
Illegal,
|
||||
NotYourTurn,
|
||||
Other(NetworkedGameState),
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
pub enum EmoteEnum {
|
||||
Smile,
|
||||
Neutral,
|
||||
Frown,
|
||||
Think,
|
||||
}
|
||||
|
||||
impl Display for EmoteEnum {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match *self {
|
||||
EmoteEnum::Smile => f.write_str("smile"),
|
||||
EmoteEnum::Neutral => f.write_str("neutral"),
|
||||
EmoteEnum::Frown => f.write_str("frown"),
|
||||
EmoteEnum::Think => f.write_str("think"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for EmoteEnum {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
match value.to_lowercase().as_str() {
|
||||
"smile" => Ok(Self::Smile),
|
||||
"neutral" => Ok(Self::Neutral),
|
||||
"frown" => Ok(Self::Frown),
|
||||
"think" => Ok(Self::Think),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EmoteEnum> for String {
|
||||
fn from(e: EmoteEnum) -> Self {
|
||||
match e {
|
||||
EmoteEnum::Smile => "smile".into(),
|
||||
EmoteEnum::Neutral => "neutral".into(),
|
||||
EmoteEnum::Frown => "frown".into(),
|
||||
EmoteEnum::Think => "think".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EmoteEnum {
|
||||
pub fn get_unicode(&self) -> char {
|
||||
match *self {
|
||||
EmoteEnum::Smile => '🙂',
|
||||
EmoteEnum::Neutral => '😐',
|
||||
EmoteEnum::Frown => '🙁',
|
||||
EmoteEnum::Think => '🤔',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -438,12 +696,59 @@ mod tests {
|
|||
paired: false,
|
||||
current_side: None,
|
||||
current_turn: Turn::CyanPlayer,
|
||||
phrase: None,
|
||||
};
|
||||
assert!(state.is_networked_multiplayer());
|
||||
let state = GameState::NetworkedMultiplayer {
|
||||
paired: true,
|
||||
current_side: Some(Turn::CyanPlayer),
|
||||
current_turn: Turn::MagentaPlayer,
|
||||
phrase: None,
|
||||
};
|
||||
assert!(state.is_networked_multiplayer());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_board_string() {
|
||||
let board = new_empty_board();
|
||||
board[49].set(BoardState::Cyan);
|
||||
board[51].set(BoardState::Cyan);
|
||||
board[52].set(BoardState::Cyan);
|
||||
board[53].set(BoardState::Cyan);
|
||||
board[54].set(BoardState::Cyan);
|
||||
board[55].set(BoardState::Magenta);
|
||||
|
||||
let (board_string, state_opt) = string_from_board(&board, 51);
|
||||
|
||||
let board_chars: Vec<char> = board_string.chars().collect();
|
||||
assert_eq!(board_chars[49], 'b');
|
||||
assert_eq!(board_chars[50], 'a');
|
||||
assert_eq!(board_chars[51], 'h');
|
||||
assert_eq!(board_chars[52], 'd');
|
||||
assert_eq!(board_chars[53], 'd');
|
||||
assert_eq!(board_chars[54], 'd');
|
||||
assert_eq!(board_chars[55], 'c');
|
||||
|
||||
assert_eq!(state_opt, Some(BoardState::CyanWin));
|
||||
|
||||
board[49].set(BoardState::Magenta);
|
||||
board[51].set(BoardState::Magenta);
|
||||
board[52].set(BoardState::Magenta);
|
||||
board[53].set(BoardState::Magenta);
|
||||
board[54].set(BoardState::Magenta);
|
||||
board[55].set(BoardState::Cyan);
|
||||
|
||||
let (board_string, state_opt) = string_from_board(&board, 51);
|
||||
|
||||
let board_chars: Vec<char> = board_string.chars().collect();
|
||||
assert_eq!(board_chars[49], 'c');
|
||||
assert_eq!(board_chars[50], 'a');
|
||||
assert_eq!(board_chars[51], 'i');
|
||||
assert_eq!(board_chars[52], 'e');
|
||||
assert_eq!(board_chars[53], 'e');
|
||||
assert_eq!(board_chars[54], 'e');
|
||||
assert_eq!(board_chars[55], 'b');
|
||||
|
||||
assert_eq!(state_opt, Some(BoardState::MagentaWin));
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
BIN
pictures/ASDM_Project_NonTechnical_SimpleModel.png
Normal file
BIN
pictures/ASDM_Project_NonTechnical_SimpleModel.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 44 KiB |
BIN
pictures/ASDM_Project_Technical_SimpleModel.png
Normal file
BIN
pictures/ASDM_Project_Technical_SimpleModel.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
18
plans/release_plan.md
Normal file
18
plans/release_plan.md
Normal file
|
@ -0,0 +1,18 @@
|
|||
# Release Plan
|
||||
|
||||
The first Sprint will focus on developing the Minimum Viable Product (MVP).
|
||||
|
||||
Every Sprint afterwards will iterate on the MVP until it reaches enough
|
||||
maturity, or in other words, has implemented all the main features.
|
||||
|
||||
After the main features have been implemented, every Sprint afterwards will
|
||||
refine the product. See the `technical_release_plan.md` for technical details.
|
||||
|
||||
# Sprints
|
||||
|
||||
The first Sprint will focus on creating the prototype that will serve as the
|
||||
Minimum Viable Product (MVP).
|
||||
|
||||
Every Sprint afterwards will choose and work on User Stories that are ideally
|
||||
the highest priority (at the time) and of lowest estimated size (priority takes
|
||||
precedence, but low sizes are ideal).
|
51
plans/technical_release_plan.md
Normal file
51
plans/technical_release_plan.md
Normal file
|
@ -0,0 +1,51 @@
|
|||
# Disclaimer
|
||||
|
||||
This Release Plan was created in April, a few weeks after the project weas
|
||||
actually started. However, the intended plan for releases has not changed during
|
||||
the creation of the project.
|
||||
|
||||
# Technical Release Plan
|
||||
|
||||
## First Stage: Web-app Minimum Viable Product front-end
|
||||
|
||||
The first release will be the Minimum Viable Product (MVP) of the front-end.
|
||||
This front-end will display the game board and other pertinent info.
|
||||
|
||||
## Second Stage: Develop the front-end MVP
|
||||
|
||||
The front-end MVP will be incrementally and iteratively improved until the
|
||||
following features are finished:
|
||||
- Local Multiplayer (Two people playing the game on the same device)
|
||||
- Single Player (One person playing against an AI of varying difficulties)
|
||||
|
||||
## Third Stage: Define the back-end protocol
|
||||
|
||||
In preparation for Networked Multiplayer for the project, the back-end must
|
||||
have a protocol defined that both the front-end and back-end must use to
|
||||
communicate properly.
|
||||
|
||||
## Fourth Stage: Define the back-end database
|
||||
|
||||
The back-end must store state and keep track of ongoing Networked Multiplayer
|
||||
games. Thus, it is required to set up the database schema that the back-end will
|
||||
use for this purpose.
|
||||
|
||||
## Fifth Stage: Develop the back-end software
|
||||
|
||||
The back-end will be created based on the back-end protocol and back-end
|
||||
database schema. Note that the protocol and database schema may change as
|
||||
necessary due to further discoveries and feedback regarding the development of
|
||||
the back-end
|
||||
|
||||
## Sixth Stage: Connect the front-end to the back-end
|
||||
|
||||
The front-end will be improved upon to enable the Networked Multiplayer feature
|
||||
by communicating with the back-end as described by the back-end protocol. This
|
||||
stage will probably also encounter bugs that may arise in either the back-end or
|
||||
front-end as this feature is being developed, so bug fixing will probably occur
|
||||
as well.
|
||||
|
||||
## Seventh Stage: Refinement
|
||||
|
||||
At this point, all the main features of Four-Line Dropper are implemented.
|
||||
What's left is bug-fixing and implementation of additional extra features.
|
42
retrospectives/Sprint5_Retrospective.rtf
Normal file
42
retrospectives/Sprint5_Retrospective.rtf
Normal file
|
@ -0,0 +1,42 @@
|
|||
{\rtf1\ansi\deff3\adeflang1025
|
||||
{\fonttbl{\f0\froman\fprq2\fcharset0 Times New Roman;}{\f1\froman\fprq2\fcharset2 Symbol;}{\f2\fswiss\fprq2\fcharset0 Arial;}{\f3\froman\fprq2\fcharset0 Liberation Serif{\*\falt Times New Roman};}{\f4\fswiss\fprq2\fcharset0 Liberation Sans{\*\falt Arial};}{\f5\fnil\fprq2\fcharset0 Noto Sans CJK SC;}{\f6\fswiss\fprq0\fcharset128 Noto Sans Devanagari;}{\f7\fnil\fprq2\fcharset0 Noto Sans Devanagari;}}
|
||||
{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0;\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0;\red128\green0\blue128;\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;}
|
||||
{\stylesheet{\s0\snext0\rtlch\af7\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar0\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af8\langfe2052 Normal;}
|
||||
{\s1\sbasedon16\snext17\rtlch\af7\afs36\ab \ltrch\hich\af4\loch\ilvl0\outlinelevel0\sb240\sa120\keepn\f4\fs36\b\dbch\af5 Heading 1;}
|
||||
{\*\cs15\snext15\rtlch\alang255 \ltrch\lang255\langfe255\loch\cf9\lang255\ul\ulc0\dbch\langfe255 Hyperlink;}
|
||||
{\s16\sbasedon0\snext17\rtlch\af7\afs28 \ltrch\hich\af4\loch\sb240\sa120\keepn\f4\fs28\dbch\af5 Heading;}
|
||||
{\s17\sbasedon0\snext17\loch\sl276\slmult1\sb0\sa140 Text Body;}
|
||||
{\s18\sbasedon17\snext18\rtlch\af6 \ltrch\loch\sl276\slmult1\sb0\sa140 List;}
|
||||
{\s19\sbasedon0\snext19\rtlch\af6\afs24\ai \ltrch\loch\sb120\sa120\noline\fs24\i Caption;}
|
||||
{\s20\sbasedon0\snext20\rtlch\af6\alang255 \ltrch\lang255\langfe255\loch\noline\lang255\dbch\langfe255 Index;}
|
||||
{\s21\sbasedon16\snext17\rtlch\af7\afs56\ab \ltrch\hich\af4\loch\qc\sb240\sa120\keepn\f4\fs56\b\dbch\af5 Title;}
|
||||
}{\*\listtable{\list\listtemplateid1
|
||||
{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0}
|
||||
{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0}
|
||||
{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0}
|
||||
{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0}
|
||||
{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0}
|
||||
{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0}
|
||||
{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0}
|
||||
{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0}
|
||||
{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0}\listid1}
|
||||
}{\listoverridetable{\listoverride\listid1\listoverridecount0\ls1}}{\*\generator LibreOffice/7.3.2.2$Linux_X86_64 LibreOffice_project/30$Build-2}{\info{\creatim\yr2022\mo4\dy15\hr17\min25}{\revtim\yr2022\mo4\dy15\hr17\min31}{\printim\yr0\mo0\dy0\hr0\min0}}{\*\userprops}\deftab709
|
||||
\hyphauto1\viewscale130
|
||||
{\*\pgdsctbl
|
||||
{\pgdsc0\pgdscuse451\pgwsxn12240\pghsxn15840\marglsxn1134\margrsxn1134\margtsxn1134\margbsxn1134\pgdscnxt0 Default Page Style;}}
|
||||
\formshade\paperh15840\paperw12240\margl1134\margr1134\margt1134\margb1134\sectd\sbknone\pgndec\sftnnar\saftnnrlc\sectunlocked1\pgwsxn12240\pghsxn15840\marglsxn1134\margrsxn1134\margtsxn1134\margbsxn1134\ftnbj\ftnstart1\ftnrstcont\ftnnar\aenddoc\aftnrstcont\aftnstart1\aftnnrlc
|
||||
{\*\ftnsep\chftnsep}\pgndec\pard\plain \s21\rtlch\af7\afs56\ab \ltrch\hich\af4\loch\qc\sb240\sa120\keepn\f4\fs56\b\dbch\af5\loch\sb240\sa120\ltrpar{\loch
|
||||
Sprint 5 Retrospective}
|
||||
\par \pard\plain \s1\rtlch\af7\afs36\ab \ltrch\hich\af4\loch\ilvl0\outlinelevel0\sb240\sa120\keepn\f4\fs36\b\dbch\af5\loch{\listtext\pard\plain \tab}\ls1 \li0\ri0\lin0\rin0\fi0\ql\ltrpar{\loch
|
||||
What was done this Sprint}
|
||||
\par \pard\plain \s17\loch\sl276\slmult1\sb0\sa140\loch\ql\ltrpar{\loch
|
||||
The connection between the back-end and the front-end was set up (with some bug-fixes along the way). This part wasn\u8217\'92t difficult because of the previously planned out back-end protocol made it easier to integrate the connection to the back-end in the front-end. There was an issue at first connecting to the back-end, but it was discovered that it was a browser \u8220\'93feature\u8221\'94 involving \u8220\'93CORS\u8221\'94 (I think) where basically the back-end and the front-end needed to be hosted by the same server (and port), otherwise the front-end would fail to connect to the back-end. This required setting up a testing environment that set this up properly, which also would reflect how the deployed set-up would work (front-end at \u8220\'93}{{\field{\*\fldinst HYPERLINK "https://asdm.seodisparate.com/game" }{\fldrslt {\loch\rtlch\alang255 \ltrch\lang255\langfe255\loch\cf9\lang255\ul\ulc0\dbch\langfe255\loch
|
||||
https://asdm.seodisparate.com/game}{}}}\loch
|
||||
\u8221\'94 and back-end at \u8220\'93}{{\field{\*\fldinst HYPERLINK "https://asdm.seodisparate.com/api" }{\fldrslt {\loch\rtlch\alang255 \ltrch\lang255\langfe255\loch\cf9\lang255\ul\ulc0\dbch\langfe255\loch
|
||||
https://asdm.seodisparate.com/api}{}}}\loch
|
||||
\u8221\'94).}
|
||||
\par \pard\plain \s1\rtlch\af7\afs36\ab \ltrch\hich\af4\loch\ilvl0\outlinelevel0\sb240\sa120\keepn\f4\fs36\b\dbch\af5\loch{\listtext\pard\plain \tab}\ls1 \li0\ri0\lin0\rin0\fi0\ql\ltrpar{\loch
|
||||
What\u8217\'92s Left}
|
||||
\par \pard\plain \s17\loch\sl276\slmult1\sb0\sa140\loch\ql\sb0\sa140\ltrpar{\loch
|
||||
Now that all the main features of the game have been implemented, what\u8217\'92s left is refinement and possible implementation of \u8220\'93nice-to-have\u8221\'94 features.}
|
||||
\par }
|
41
retrospectives/Sprint6_Retrospective.rtf
Normal file
41
retrospectives/Sprint6_Retrospective.rtf
Normal file
|
@ -0,0 +1,41 @@
|
|||
{\rtf1\ansi\deff3\adeflang1025
|
||||
{\fonttbl{\f0\froman\fprq2\fcharset0 Times New Roman;}{\f1\froman\fprq2\fcharset2 Symbol;}{\f2\fswiss\fprq2\fcharset0 Arial;}{\f3\froman\fprq2\fcharset0 Liberation Serif{\*\falt Times New Roman};}{\f4\fswiss\fprq2\fcharset0 Liberation Sans{\*\falt Arial};}{\f5\fnil\fprq2\fcharset0 Noto Sans CJK SC;}{\f6\fnil\fprq2\fcharset0 Noto Sans Devanagari;}{\f7\fswiss\fprq0\fcharset128 Noto Sans Devanagari;}}
|
||||
{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0;\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0;\red128\green0\blue128;\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;}
|
||||
{\stylesheet{\s0\snext0\rtlch\af6\afs24\alang1081 \ltrch\lang1033\langfe2052\hich\af3\loch\widctlpar\hyphpar0\ltrpar\cf0\f3\fs24\lang1033\kerning1\dbch\af8\langfe2052 Normal;}
|
||||
{\s1\sbasedon15\snext16\rtlch\af6\afs36\ab \ltrch\hich\af4\loch\ilvl0\outlinelevel0\sb240\sa120\keepn\f4\fs36\b\dbch\af5 Heading 1;}
|
||||
{\s15\sbasedon0\snext16\rtlch\af6\afs28 \ltrch\hich\af4\loch\sb240\sa120\keepn\f4\fs28\dbch\af5 Heading;}
|
||||
{\s16\sbasedon0\snext16\loch\sl276\slmult1\sb0\sa140 Text Body;}
|
||||
{\s17\sbasedon16\snext17\rtlch\af7 \ltrch\loch\sl276\slmult1\sb0\sa140 List;}
|
||||
{\s18\sbasedon0\snext18\rtlch\af7\afs24\ai \ltrch\loch\sb120\sa120\noline\fs24\i Caption;}
|
||||
{\s19\sbasedon0\snext19\rtlch\af7\alang255 \ltrch\lang255\langfe255\loch\noline\lang255\dbch\langfe255 Index;}
|
||||
{\s20\sbasedon15\snext16\rtlch\af6\afs56\ab \ltrch\hich\af4\loch\qc\sb240\sa120\keepn\f4\fs56\b\dbch\af5 Title;}
|
||||
}{\*\listtable{\list\listtemplateid1
|
||||
{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0}
|
||||
{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0}
|
||||
{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0}
|
||||
{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0}
|
||||
{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0}
|
||||
{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0}
|
||||
{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0}
|
||||
{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0}
|
||||
{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0}\listid1}
|
||||
}{\listoverridetable{\listoverride\listid1\listoverridecount0\ls1}}{\*\generator LibreOffice/7.3.2.2$Linux_X86_64 LibreOffice_project/30$Build-2}{\info{\creatim\yr2022\mo5\dy2\hr13\min45}{\revtim\yr2022\mo5\dy2\hr13\min53}{\printim\yr0\mo0\dy0\hr0\min0}}{\*\userprops}\deftab709
|
||||
\hyphauto1\viewscale160
|
||||
{\*\pgdsctbl
|
||||
{\pgdsc0\pgdscuse451\pgwsxn12240\pghsxn15840\marglsxn1134\margrsxn1134\margtsxn1134\margbsxn1134\pgdscnxt0 Default Page Style;}}
|
||||
\formshade\paperh15840\paperw12240\margl1134\margr1134\margt1134\margb1134\sectd\sbknone\pgndec\sftnnar\saftnnrlc\sectunlocked1\pgwsxn12240\pghsxn15840\marglsxn1134\margrsxn1134\margtsxn1134\margbsxn1134\ftnbj\ftnstart1\ftnrstcont\ftnnar\aenddoc\aftnrstcont\aftnstart1\aftnnrlc
|
||||
{\*\ftnsep\chftnsep}\pgndec\pard\plain \s20\rtlch\af6\afs56\ab \ltrch\hich\af4\loch\qc\sb240\sa120\keepn\f4\fs56\b\dbch\af5\loch\sb240\sa120\ltrpar{\loch
|
||||
Sprint 6 Retrospective}
|
||||
\par \pard\plain \s1\rtlch\af6\afs36\ab \ltrch\hich\af4\loch\ilvl0\outlinelevel0\sb240\sa120\keepn\f4\fs36\b\dbch\af5\loch{\listtext\pard\plain \tab}\ls1 \li0\ri0\lin0\rin0\fi0\ql\ltrpar{\loch
|
||||
What was completed}
|
||||
\par \pard\plain \s16\loch\sl276\slmult1\sb0\sa140\loch\ql\ltrpar{\loch
|
||||
By the end of Sprint 6, almost all User Stories have been marked as done, except for one \u8220\'93Exciter\u8221\'94 User Story (\u8220\'93Board Column Emotes\u8221\'94).}
|
||||
\par \pard\plain \s16\loch\sl276\slmult1\sb0\sa140\loch\ql\ltrpar{\loch
|
||||
This Sprint marked the following User Stories as DONE: \u8220\'93End Game Options\u8221\'94, \u8220\'93AI Implementation\u8221\'94 (fine tuning AI), \u8220\'93In-game Emotes\u8221\'94, and \u8220\'93Multiplayer Phrase Pairing\u8221\'94.}
|
||||
\par \pard\plain \s1\rtlch\af6\afs36\ab \ltrch\hich\af4\loch\ilvl0\outlinelevel0\sb240\sa120\keepn\f4\fs36\b\dbch\af5\loch{\listtext\pard\plain \tab}\ls1 \li0\ri0\lin0\rin0\fi0\ql\ltrpar{\loch
|
||||
Thoughts}
|
||||
\par \pard\plain \s16\loch\sl276\slmult1\sb0\sa140\loch\ql\ltrpar{\loch
|
||||
It took some work adding the additional functionality for \u8220\'93In-game Emotes\u8221\'94 and \u8220\'93Multiplayer Phrase Pairing\u8221\'94 such that both protocol and database specifications had to be updated (and their usage in the back-end/front-end). In the end, it was nice to get nearly all of the User Stories marked as done. Setting up a MVP (Minimum Viable Product) and building upon it proved to be a good strategy for building up this project. The Scrum process facilitated the organization and set-up for the programming work, and has proven its usefulness.}
|
||||
\par \pard\plain \s16\loch\sl276\slmult1\sb0\sa140\loch\ql\sb0\sa140\ltrpar{\loch
|
||||
As this final Sprint focused more on \u8220\'93refinement\u8221\'94, the Sprint resulted in a few new (add-on) features and some refactoring/fixes.}
|
||||
\par }
|
|
@ -10,9 +10,14 @@ IDs, and paired state), and games in progress.
|
|||
PRAGMA foreign_keys = ON;
|
||||
|
||||
// fields should be self explanatory for the players table
|
||||
|
||||
// "phrase" is used to connect players with identical "phrase" text to make it
|
||||
// easier to connect with the player one wants to play with
|
||||
|
||||
CREATE TABLE players (id INTEGER PRIMARY KEY NOT NULL,
|
||||
date_added TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
game_id INTEGER,
|
||||
phrase TEXT,
|
||||
FOREIGN KEY(game_id) REFERENCES games(id) ON DELETE CASCADE);
|
||||
|
||||
// "cyan_player" and "magenta_player" should correspond to an existing entry in
|
||||
|
@ -32,6 +37,14 @@ CREATE TABLE games (id INTEGER PRIMARY KEY NOT NULL,
|
|||
turn_time_start TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY(cyan_player) REFERENCES players (id) ON DELETE SET NULL,
|
||||
FOREIGN KEY(magenta_player) REFERENCES players (id) ON DELETE SET NULL);
|
||||
|
||||
// "type" is one of the four possible emotes
|
||||
|
||||
CREATE TABLE emotes (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
date_added TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
receiver_id INTEGER NOT NULL,
|
||||
FOREIGN KEY(receiver_id) REFERENCES players (id) ON DELETE CASCADE);
|
||||
```
|
||||
|
||||
"date" entries are used for garbage collection of the database. A predefined
|
|
@ -1,7 +1,7 @@
|
|||
# Backend Protocol
|
||||
|
||||
The backend will be available at https://asdm.seodisparate.com/api .
|
||||
The frontend will send http GET/POST requests to the URL with JSON as the body
|
||||
The frontend will send http POST requests to the URL with JSON as the body
|
||||
of the request, and the backend will respond with JSON.
|
||||
|
||||
## Requests
|
||||
|
@ -14,6 +14,15 @@ of the request, and the backend will respond with JSON.
|
|||
}
|
||||
```
|
||||
|
||||
An optional "phrase" parameter can be sent to match against other players with
|
||||
the same "phrase".
|
||||
```
|
||||
{
|
||||
"type": "pairing_request",
|
||||
"phrase": "user_defined_phrase",
|
||||
}
|
||||
```
|
||||
|
||||
2. Check pairing status
|
||||
|
||||
```
|
||||
|
@ -51,6 +60,16 @@ of the request, and the backend will respond with JSON.
|
|||
}
|
||||
```
|
||||
|
||||
6. Chat Emote Send:
|
||||
|
||||
```
|
||||
{
|
||||
"id": "id given by backend",
|
||||
"type": "send_emote",
|
||||
"emote": "smile", // or "frown", or "neutral", or "think"
|
||||
}
|
||||
```
|
||||
|
||||
## Responses
|
||||
|
||||
1. Request ID Response
|
||||
|
@ -85,14 +104,14 @@ then the back-end will respond with "too\_many\_players".
|
|||
|
||||
```
|
||||
{
|
||||
"type": "pairing_response",
|
||||
"type": "pairing_status",
|
||||
"status": "waiting", // or "unknown_id"
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
{
|
||||
"type": "pairing_response",
|
||||
"type": "pairing_status",
|
||||
"status": "paired",
|
||||
"color": "magenta", // or "cyan"
|
||||
}
|
||||
|
@ -130,7 +149,7 @@ then the back-end will respond with "too\_many\_players".
|
|||
// "opponent_disconnected", "internal_error"
|
||||
|
||||
// "board" may not be in the response if "unknown_id" is the status
|
||||
"board": "abcdefg..." // 56-char long string with mapping:
|
||||
"board": "abcdefg...",// 56-char long string with mapping:
|
||||
// a - empty
|
||||
// b - cyan
|
||||
// c - magenta
|
||||
|
@ -138,6 +157,23 @@ then the back-end will respond with "too\_many\_players".
|
|||
// e - magenta winning piece
|
||||
// f - cyan placed
|
||||
// g - magenta placed
|
||||
// h - cyan winning and placed piece
|
||||
// i - magenta winning and placed piece
|
||||
// optional "peer_emote" entry is message from opponent
|
||||
"peer_emote": "smile",// or "frown", or "neutral", or "think"
|
||||
|
||||
// should always be available when "board" is available
|
||||
"updated_time": "2022-04-30 12:00:00"
|
||||
}
|
||||
```
|
||||
|
||||
6. Send Emote Request Response
|
||||
|
||||
```
|
||||
{
|
||||
"type": "send_emote",
|
||||
"status": "ok", // or "invalid_emote", "peer_disconnected",
|
||||
// "internal_error"
|
||||
}
|
||||
```
|
||||
|
File diff suppressed because it is too large
Load diff
BIN
spreadsheets/product_backlog_CONVERTED_2022-04-16.xlsx
Normal file
BIN
spreadsheets/product_backlog_CONVERTED_2022-04-16.xlsx
Normal file
Binary file not shown.
BIN
spreadsheets/product_backlog_CONVERTED_2022-05-02.xlsx
Normal file
BIN
spreadsheets/product_backlog_CONVERTED_2022-05-02.xlsx
Normal file
Binary file not shown.
BIN
spreadsheets/sprint_backlog_sprint_01_CONVERTED_2022-04-16.xlsx
Normal file
BIN
spreadsheets/sprint_backlog_sprint_01_CONVERTED_2022-04-16.xlsx
Normal file
Binary file not shown.
BIN
spreadsheets/sprint_backlog_sprint_02_CONVERTED_2022-04-16.xlsx
Normal file
BIN
spreadsheets/sprint_backlog_sprint_02_CONVERTED_2022-04-16.xlsx
Normal file
Binary file not shown.
BIN
spreadsheets/sprint_backlog_sprint_03_CONVERTED_2022-04-16.xlsx
Normal file
BIN
spreadsheets/sprint_backlog_sprint_03_CONVERTED_2022-04-16.xlsx
Normal file
Binary file not shown.
BIN
spreadsheets/sprint_backlog_sprint_04_CONVERTED_2022-04-16.xlsx
Normal file
BIN
spreadsheets/sprint_backlog_sprint_04_CONVERTED_2022-04-16.xlsx
Normal file
Binary file not shown.
File diff suppressed because it is too large
Load diff
BIN
spreadsheets/sprint_backlog_sprint_05_CONVERTED_2022-04-16.xlsx
Normal file
BIN
spreadsheets/sprint_backlog_sprint_05_CONVERTED_2022-04-16.xlsx
Normal file
Binary file not shown.
1393
spreadsheets/sprint_backlog_sprint_06.fods
Normal file
1393
spreadsheets/sprint_backlog_sprint_06.fods
Normal file
File diff suppressed because it is too large
Load diff
BIN
spreadsheets/sprint_backlog_sprint_06_CONVERTED_2022-05-02.xlsx
Normal file
BIN
spreadsheets/sprint_backlog_sprint_06_CONVERTED_2022-05-02.xlsx
Normal file
Binary file not shown.
|
@ -1,29 +1,29 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<office:document xmlns:presentation="urn:oasis:names:tc:opendocument:xmlns:presentation:1.0" xmlns:css3t="http://www.w3.org/TR/css3-text/" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rpt="http://openoffice.org/2005/report" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:xforms="http://www.w3.org/2002/xforms" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.spreadsheet">
|
||||
<office:meta><meta:creation-date>2022-02-27T14:56:15.671560282</meta:creation-date><meta:generator>LibreOffice/7.3.1.3$Linux_X86_64 LibreOffice_project/30$Build-3</meta:generator><dc:date>2022-03-13T22:01:54.524481726</dc:date><meta:editing-duration>PT2H8M22S</meta:editing-duration><meta:editing-cycles>38</meta:editing-cycles><meta:document-statistic meta:table-count="1" meta:cell-count="298" meta:object-count="0"/></office:meta>
|
||||
<office:meta><meta:creation-date>2022-02-27T14:56:15.671560282</meta:creation-date><meta:generator>LibreOffice/7.3.2.2$Linux_X86_64 LibreOffice_project/30$Build-2</meta:generator><dc:date>2022-04-25T13:54:57.087569254</dc:date><meta:editing-duration>PT2H15M22S</meta:editing-duration><meta:editing-cycles>40</meta:editing-cycles><meta:document-statistic meta:table-count="1" meta:cell-count="316" meta:object-count="0"/></office:meta>
|
||||
<office:settings>
|
||||
<config:config-item-set config:name="ooo:view-settings">
|
||||
<config:config-item config:name="VisibleAreaTop" config:type="int">0</config:config-item>
|
||||
<config:config-item config:name="VisibleAreaLeft" config:type="int">0</config:config-item>
|
||||
<config:config-item config:name="VisibleAreaWidth" config:type="int">46383</config:config-item>
|
||||
<config:config-item config:name="VisibleAreaHeight" config:type="int">36844</config:config-item>
|
||||
<config:config-item config:name="VisibleAreaHeight" config:type="int">40502</config:config-item>
|
||||
<config:config-item-map-indexed config:name="Views">
|
||||
<config:config-item-map-entry>
|
||||
<config:config-item config:name="ViewId" config:type="string">view1</config:config-item>
|
||||
<config:config-item-map-named config:name="Tables">
|
||||
<config:config-item-map-entry config:name="User Stories">
|
||||
<config:config-item config:name="CursorPositionX" config:type="int">12</config:config-item>
|
||||
<config:config-item config:name="CursorPositionY" config:type="int">23</config:config-item>
|
||||
<config:config-item config:name="CursorPositionX" config:type="int">5</config:config-item>
|
||||
<config:config-item config:name="CursorPositionY" config:type="int">27</config:config-item>
|
||||
<config:config-item config:name="HorizontalSplitMode" config:type="short">2</config:config-item>
|
||||
<config:config-item config:name="VerticalSplitMode" config:type="short">2</config:config-item>
|
||||
<config:config-item config:name="HorizontalSplitPosition" config:type="int">1</config:config-item>
|
||||
<config:config-item config:name="VerticalSplitPosition" config:type="int">1</config:config-item>
|
||||
<config:config-item config:name="ActiveSplitRange" config:type="short">3</config:config-item>
|
||||
<config:config-item config:name="PositionLeft" config:type="int">0</config:config-item>
|
||||
<config:config-item config:name="PositionRight" config:type="int">5</config:config-item>
|
||||
<config:config-item config:name="PositionRight" config:type="int">1</config:config-item>
|
||||
<config:config-item config:name="PositionTop" config:type="int">0</config:config-item>
|
||||
<config:config-item config:name="PositionBottom" config:type="int">1</config:config-item>
|
||||
<config:config-item config:name="PositionBottom" config:type="int">18</config:config-item>
|
||||
<config:config-item config:name="ZoomType" config:type="short">0</config:config-item>
|
||||
<config:config-item config:name="ZoomValue" config:type="int">100</config:config-item>
|
||||
<config:config-item config:name="PageViewZoomValue" config:type="int">60</config:config-item>
|
||||
|
@ -32,7 +32,7 @@
|
|||
</config:config-item-map-entry>
|
||||
</config:config-item-map-named>
|
||||
<config:config-item config:name="ActiveTable" config:type="string">User Stories</config:config-item>
|
||||
<config:config-item config:name="HorizontalScrollbarWidth" config:type="int">1203</config:config-item>
|
||||
<config:config-item config:name="HorizontalScrollbarWidth" config:type="int">2483</config:config-item>
|
||||
<config:config-item config:name="ZoomType" config:type="short">0</config:config-item>
|
||||
<config:config-item config:name="ZoomValue" config:type="int">100</config:config-item>
|
||||
<config:config-item config:name="PageViewZoomValue" config:type="int">60</config:config-item>
|
||||
|
@ -357,7 +357,7 @@
|
|||
<text:p><text:sheet-name>???</text:sheet-name><text:s/>(<text:title>???</text:title>)</text:p>
|
||||
</style:region-left>
|
||||
<style:region-right>
|
||||
<text:p><text:date style:data-style-name="N2" text:date-value="2022-03-13">00/00/0000</text:date>, <text:time style:data-style-name="N2" text:time-value="21:56:11.588245896">00:00:00</text:time></text:p>
|
||||
<text:p><text:date style:data-style-name="N2" text:date-value="2022-04-25">00/00/0000</text:date>, <text:time style:data-style-name="N2" text:time-value="13:53:35.055125671">00:00:00</text:time></text:p>
|
||||
</style:region-right>
|
||||
</style:header>
|
||||
<style:header-left style:display="false"/>
|
||||
|
@ -442,17 +442,17 @@
|
|||
<table:table-cell table:style-name="ce45" office:value-type="string" calcext:value-type="string">
|
||||
<text:p>Mandatory</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce45" table:formula="of:=SUM([.$G3:.$G100])" office:value-type="float" office:value="800" calcext:value-type="float">
|
||||
<text:p>800</text:p>
|
||||
<table:table-cell table:style-name="ce45" table:formula="of:=SUM([.$G3:.$G100])" office:value-type="float" office:value="845" calcext:value-type="float">
|
||||
<text:p>845</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce45" table:formula="of:=SUM([.$H3:.$H100])" office:value-type="float" office:value="174" calcext:value-type="float">
|
||||
<text:p>174</text:p>
|
||||
<table:table-cell table:style-name="ce45" table:formula="of:=SUM([.$H3:.$H100])" office:value-type="float" office:value="182" calcext:value-type="float">
|
||||
<text:p>182</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce45" table:formula="of:=SUM([.$I3:.$I100])" office:value-type="float" office:value="148" calcext:value-type="float">
|
||||
<text:p>148</text:p>
|
||||
<table:table-cell table:style-name="ce45" table:formula="of:=SUM([.$I3:.$I100])" office:value-type="float" office:value="151" calcext:value-type="float">
|
||||
<text:p>151</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce45" table:formula="of:=SUM([.$H2:.$I2])" office:value-type="float" office:value="322" calcext:value-type="float">
|
||||
<text:p>322</text:p>
|
||||
<table:table-cell table:style-name="ce45" table:formula="of:=SUM([.$H2:.$I2])" office:value-type="float" office:value="333" calcext:value-type="float">
|
||||
<text:p>333</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:number-columns-repeated="2" table:style-name="ce51" office:value-type="percentage" office:value="1" calcext:value-type="percentage">
|
||||
<text:p>100.00%</text:p>
|
||||
|
@ -490,14 +490,14 @@
|
|||
<table:table-cell table:style-name="ce46" table:formula="of:=SUM([.$H3:.$I3])" office:value-type="float" office:value="16" calcext:value-type="float">
|
||||
<text:p>16</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J3]/[.$J$2]" office:value-type="percentage" office:value="0.0496894409937888" calcext:value-type="percentage">
|
||||
<text:p>4.97%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J3]/[.$J$2]" office:value-type="percentage" office:value="0.0480480480480481" calcext:value-type="percentage">
|
||||
<text:p>4.80%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G3]/[.$G$2]" office:value-type="percentage" office:value="0.0125" calcext:value-type="percentage">
|
||||
<text:p>1.25%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G3]/[.$G$2]" office:value-type="percentage" office:value="0.0118343195266272" calcext:value-type="percentage">
|
||||
<text:p>1.18%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K3]/[.$L3]" office:value-type="float" office:value="3.9751552795031" calcext:value-type="float">
|
||||
<text:p>3.9751552795031</text:p>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K3]/[.$L3]" office:value-type="float" office:value="4.06006006006006" calcext:value-type="float">
|
||||
<text:p>4.06006006006006</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:number-columns-repeated="1011"/>
|
||||
</table:table-row>
|
||||
|
@ -527,14 +527,14 @@
|
|||
<table:table-cell table:style-name="ce46" table:formula="of:=SUM([.$H4:.$I4])" office:value-type="float" office:value="16" calcext:value-type="float">
|
||||
<text:p>16</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J4]/[.$J$2]" office:value-type="percentage" office:value="0.0496894409937888" calcext:value-type="percentage">
|
||||
<text:p>4.97%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J4]/[.$J$2]" office:value-type="percentage" office:value="0.0480480480480481" calcext:value-type="percentage">
|
||||
<text:p>4.80%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G4]/[.$G$2]" office:value-type="percentage" office:value="0.05" calcext:value-type="percentage">
|
||||
<text:p>5.00%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G4]/[.$G$2]" office:value-type="percentage" office:value="0.0473372781065089" calcext:value-type="percentage">
|
||||
<text:p>4.73%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K4]/[.$L4]" office:value-type="float" office:value="0.993788819875776" calcext:value-type="float">
|
||||
<text:p>0.993788819875776</text:p>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K4]/[.$L4]" office:value-type="float" office:value="1.01501501501502" calcext:value-type="float">
|
||||
<text:p>1.01501501501502</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:number-columns-repeated="1011"/>
|
||||
</table:table-row>
|
||||
|
@ -567,14 +567,14 @@
|
|||
<table:table-cell table:style-name="ce47" table:formula="of:=SUM([.$H5:.$I5])" office:value-type="float" office:value="8" calcext:value-type="float">
|
||||
<text:p>8</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$J5]/[.$J$2]" office:value-type="percentage" office:value="0.0248447204968944" calcext:value-type="percentage">
|
||||
<text:p>2.48%</text:p>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$J5]/[.$J$2]" office:value-type="percentage" office:value="0.024024024024024" calcext:value-type="percentage">
|
||||
<text:p>2.40%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$G5]/[.$G$2]" office:value-type="percentage" office:value="0.04625" calcext:value-type="percentage">
|
||||
<text:p>4.63%</text:p>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$G5]/[.$G$2]" office:value-type="percentage" office:value="0.0437869822485207" calcext:value-type="percentage">
|
||||
<text:p>4.38%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce47" table:formula="of:=[.$K5]/[.$L5]" office:value-type="float" office:value="0.537183145878798" calcext:value-type="float">
|
||||
<text:p>0.537183145878798</text:p>
|
||||
<table:table-cell table:style-name="ce47" table:formula="of:=[.$K5]/[.$L5]" office:value-type="float" office:value="0.548656764872981" calcext:value-type="float">
|
||||
<text:p>0.548656764872981</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce47" table:number-columns-repeated="1011"/>
|
||||
</table:table-row>
|
||||
|
@ -607,14 +607,14 @@
|
|||
<table:table-cell table:style-name="ce46" table:formula="of:=SUM([.$H6:.$I6])" office:value-type="float" office:value="16" calcext:value-type="float">
|
||||
<text:p>16</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J6]/[.$J$2]" office:value-type="percentage" office:value="0.0496894409937888" calcext:value-type="percentage">
|
||||
<text:p>4.97%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J6]/[.$J$2]" office:value-type="percentage" office:value="0.0480480480480481" calcext:value-type="percentage">
|
||||
<text:p>4.80%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G6]/[.$G$2]" office:value-type="percentage" office:value="0.01625" calcext:value-type="percentage">
|
||||
<text:p>1.63%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G6]/[.$G$2]" office:value-type="percentage" office:value="0.0153846153846154" calcext:value-type="percentage">
|
||||
<text:p>1.54%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K6]/[.$L6]" office:value-type="float" office:value="3.05781175346393" calcext:value-type="float">
|
||||
<text:p>3.05781175346393</text:p>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K6]/[.$L6]" office:value-type="float" office:value="3.12312312312312" calcext:value-type="float">
|
||||
<text:p>3.12312312312312</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:number-columns-repeated="1011"/>
|
||||
</table:table-row>
|
||||
|
@ -644,14 +644,14 @@
|
|||
<table:table-cell table:style-name="ce46" table:formula="of:=SUM([.$H7:.$I7])" office:value-type="float" office:value="16" calcext:value-type="float">
|
||||
<text:p>16</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J7]/[.$J$2]" office:value-type="percentage" office:value="0.0496894409937888" calcext:value-type="percentage">
|
||||
<text:p>4.97%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J7]/[.$J$2]" office:value-type="percentage" office:value="0.0480480480480481" calcext:value-type="percentage">
|
||||
<text:p>4.80%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G7]/[.$G$2]" office:value-type="percentage" office:value="0.0225" calcext:value-type="percentage">
|
||||
<text:p>2.25%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G7]/[.$G$2]" office:value-type="percentage" office:value="0.021301775147929" calcext:value-type="percentage">
|
||||
<text:p>2.13%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K7]/[.$L7]" office:value-type="float" office:value="2.20841959972395" calcext:value-type="float">
|
||||
<text:p>2.20841959972395</text:p>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K7]/[.$L7]" office:value-type="float" office:value="2.25558892225559" calcext:value-type="float">
|
||||
<text:p>2.25558892225559</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:number-columns-repeated="1011"/>
|
||||
</table:table-row>
|
||||
|
@ -686,14 +686,14 @@
|
|||
<table:table-cell table:style-name="ce46" table:formula="of:=SUM([.$H8:.$I8])" office:value-type="float" office:value="17" calcext:value-type="float">
|
||||
<text:p>17</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J8]/[.$J$2]" office:value-type="percentage" office:value="0.0527950310559006" calcext:value-type="percentage">
|
||||
<text:p>5.28%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J8]/[.$J$2]" office:value-type="percentage" office:value="0.0510510510510511" calcext:value-type="percentage">
|
||||
<text:p>5.11%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G8]/[.$G$2]" office:value-type="percentage" office:value="0.03125" calcext:value-type="percentage">
|
||||
<text:p>3.13%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G8]/[.$G$2]" office:value-type="percentage" office:value="0.029585798816568" calcext:value-type="percentage">
|
||||
<text:p>2.96%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K8]/[.$L8]" office:value-type="float" office:value="1.68944099378882" calcext:value-type="float">
|
||||
<text:p>1.68944099378882</text:p>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K8]/[.$L8]" office:value-type="float" office:value="1.72552552552553" calcext:value-type="float">
|
||||
<text:p>1.72552552552553</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:number-columns-repeated="1011"/>
|
||||
</table:table-row>
|
||||
|
@ -725,14 +725,14 @@
|
|||
<table:table-cell table:style-name="ce46" table:formula="of:=SUM([.$H9:.$I9])" office:value-type="float" office:value="18" calcext:value-type="float">
|
||||
<text:p>18</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J9]/[.$J$2]" office:value-type="percentage" office:value="0.0559006211180124" calcext:value-type="percentage">
|
||||
<text:p>5.59%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J9]/[.$J$2]" office:value-type="percentage" office:value="0.0540540540540541" calcext:value-type="percentage">
|
||||
<text:p>5.41%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G9]/[.$G$2]" office:value-type="percentage" office:value="0.02875" calcext:value-type="percentage">
|
||||
<text:p>2.88%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G9]/[.$G$2]" office:value-type="percentage" office:value="0.0272189349112426" calcext:value-type="percentage">
|
||||
<text:p>2.72%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K9]/[.$L9]" office:value-type="float" office:value="1.94436943019174" calcext:value-type="float">
|
||||
<text:p>1.94436943019174</text:p>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K9]/[.$L9]" office:value-type="float" office:value="1.98589894242068" calcext:value-type="float">
|
||||
<text:p>1.98589894242068</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:number-columns-repeated="1011"/>
|
||||
</table:table-row>
|
||||
|
@ -762,14 +762,14 @@
|
|||
<table:table-cell table:style-name="ce46" table:formula="of:=SUM([.$H10:.$I10])" office:value-type="float" office:value="16" calcext:value-type="float">
|
||||
<text:p>16</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J10]/[.$J$2]" office:value-type="percentage" office:value="0.0496894409937888" calcext:value-type="percentage">
|
||||
<text:p>4.97%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J10]/[.$J$2]" office:value-type="percentage" office:value="0.0480480480480481" calcext:value-type="percentage">
|
||||
<text:p>4.80%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G10]/[.$G$2]" office:value-type="percentage" office:value="0.025" calcext:value-type="percentage">
|
||||
<text:p>2.50%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G10]/[.$G$2]" office:value-type="percentage" office:value="0.0236686390532544" calcext:value-type="percentage">
|
||||
<text:p>2.37%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K10]/[.$L10]" office:value-type="float" office:value="1.98757763975155" calcext:value-type="float">
|
||||
<text:p>1.98757763975155</text:p>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K10]/[.$L10]" office:value-type="float" office:value="2.03003003003003" calcext:value-type="float">
|
||||
<text:p>2.03003003003003</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:number-columns-repeated="1011"/>
|
||||
</table:table-row>
|
||||
|
@ -802,14 +802,14 @@
|
|||
<table:table-cell table:style-name="ce47" table:formula="of:=SUM([.$H11:.$I11])" office:value-type="float" office:value="9" calcext:value-type="float">
|
||||
<text:p>9</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$J11]/[.$J$2]" office:value-type="percentage" office:value="0.0279503105590062" calcext:value-type="percentage">
|
||||
<text:p>2.80%</text:p>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$J11]/[.$J$2]" office:value-type="percentage" office:value="0.027027027027027" calcext:value-type="percentage">
|
||||
<text:p>2.70%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$G11]/[.$G$2]" office:value-type="percentage" office:value="0.01625" calcext:value-type="percentage">
|
||||
<text:p>1.63%</text:p>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$G11]/[.$G$2]" office:value-type="percentage" office:value="0.0153846153846154" calcext:value-type="percentage">
|
||||
<text:p>1.54%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce47" table:formula="of:=[.$K11]/[.$L11]" office:value-type="float" office:value="1.72001911132346" calcext:value-type="float">
|
||||
<text:p>1.72001911132346</text:p>
|
||||
<table:table-cell table:style-name="ce47" table:formula="of:=[.$K11]/[.$L11]" office:value-type="float" office:value="1.75675675675676" calcext:value-type="float">
|
||||
<text:p>1.75675675675676</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce47" table:number-columns-repeated="1011"/>
|
||||
</table:table-row>
|
||||
|
@ -842,14 +842,14 @@
|
|||
<table:table-cell table:style-name="ce48" table:formula="of:=SUM([.$H12:.$I12])" office:value-type="float" office:value="17" calcext:value-type="float">
|
||||
<text:p>17</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce54" table:formula="of:=[.$J12]/[.$J$2]" office:value-type="percentage" office:value="0.0527950310559006" calcext:value-type="percentage">
|
||||
<text:p>5.28%</text:p>
|
||||
<table:table-cell table:style-name="ce54" table:formula="of:=[.$J12]/[.$J$2]" office:value-type="percentage" office:value="0.0510510510510511" calcext:value-type="percentage">
|
||||
<text:p>5.11%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce54" table:formula="of:=[.$G12]/[.$G$2]" office:value-type="percentage" office:value="0.03" calcext:value-type="percentage">
|
||||
<text:p>3.00%</text:p>
|
||||
<table:table-cell table:style-name="ce54" table:formula="of:=[.$G12]/[.$G$2]" office:value-type="percentage" office:value="0.0284023668639053" calcext:value-type="percentage">
|
||||
<text:p>2.84%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce48" table:formula="of:=[.$K12]/[.$L12]" office:value-type="float" office:value="1.75983436853002" calcext:value-type="float">
|
||||
<text:p>1.75983436853002</text:p>
|
||||
<table:table-cell table:style-name="ce48" table:formula="of:=[.$K12]/[.$L12]" office:value-type="float" office:value="1.79742242242242" calcext:value-type="float">
|
||||
<text:p>1.79742242242242</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce48" table:number-columns-repeated="1011"/>
|
||||
</table:table-row>
|
||||
|
@ -884,14 +884,14 @@
|
|||
<table:table-cell table:style-name="ce47" table:formula="of:=SUM([.$H13:.$I13])" office:value-type="float" office:value="11" calcext:value-type="float">
|
||||
<text:p>11</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$J13]/[.$J$2]" office:value-type="percentage" office:value="0.0341614906832298" calcext:value-type="percentage">
|
||||
<text:p>3.42%</text:p>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$J13]/[.$J$2]" office:value-type="percentage" office:value="0.033033033033033" calcext:value-type="percentage">
|
||||
<text:p>3.30%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$G13]/[.$G$2]" office:value-type="percentage" office:value="0.0875" calcext:value-type="percentage">
|
||||
<text:p>8.75%</text:p>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$G13]/[.$G$2]" office:value-type="percentage" office:value="0.0828402366863905" calcext:value-type="percentage">
|
||||
<text:p>8.28%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce47" table:formula="of:=[.$K13]/[.$L13]" office:value-type="float" office:value="0.390417036379769" calcext:value-type="float">
|
||||
<text:p>0.390417036379769</text:p>
|
||||
<table:table-cell table:style-name="ce47" table:formula="of:=[.$K13]/[.$L13]" office:value-type="float" office:value="0.398755898755899" calcext:value-type="float">
|
||||
<text:p>0.398755898755899</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce47" table:number-columns-repeated="1011"/>
|
||||
</table:table-row>
|
||||
|
@ -921,14 +921,14 @@
|
|||
<table:table-cell table:style-name="ce46" table:formula="of:=SUM([.$H14:.$I14])" office:value-type="float" office:value="16" calcext:value-type="float">
|
||||
<text:p>16</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J14]/[.$J$2]" office:value-type="percentage" office:value="0.0496894409937888" calcext:value-type="percentage">
|
||||
<text:p>4.97%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J14]/[.$J$2]" office:value-type="percentage" office:value="0.0480480480480481" calcext:value-type="percentage">
|
||||
<text:p>4.80%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G14]/[.$G$2]" office:value-type="percentage" office:value="0.0375" calcext:value-type="percentage">
|
||||
<text:p>3.75%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G14]/[.$G$2]" office:value-type="percentage" office:value="0.0355029585798817" calcext:value-type="percentage">
|
||||
<text:p>3.55%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K14]/[.$L14]" office:value-type="float" office:value="1.32505175983437" calcext:value-type="float">
|
||||
<text:p>1.32505175983437</text:p>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K14]/[.$L14]" office:value-type="float" office:value="1.35335335335335" calcext:value-type="float">
|
||||
<text:p>1.35335335335335</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:number-columns-repeated="1011"/>
|
||||
</table:table-row>
|
||||
|
@ -958,14 +958,14 @@
|
|||
<table:table-cell table:style-name="ce46" table:formula="of:=SUM([.$H15:.$I15])" office:value-type="float" office:value="16" calcext:value-type="float">
|
||||
<text:p>16</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J15]/[.$J$2]" office:value-type="percentage" office:value="0.0496894409937888" calcext:value-type="percentage">
|
||||
<text:p>4.97%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J15]/[.$J$2]" office:value-type="percentage" office:value="0.0480480480480481" calcext:value-type="percentage">
|
||||
<text:p>4.80%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G15]/[.$G$2]" office:value-type="percentage" office:value="0.08125" calcext:value-type="percentage">
|
||||
<text:p>8.13%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G15]/[.$G$2]" office:value-type="percentage" office:value="0.0769230769230769" calcext:value-type="percentage">
|
||||
<text:p>7.69%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K15]/[.$L15]" office:value-type="float" office:value="0.611562350692785" calcext:value-type="float">
|
||||
<text:p>0.611562350692785</text:p>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K15]/[.$L15]" office:value-type="float" office:value="0.624624624624625" calcext:value-type="float">
|
||||
<text:p>0.624624624624625</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:number-columns-repeated="1011"/>
|
||||
</table:table-row>
|
||||
|
@ -1000,14 +1000,14 @@
|
|||
<table:table-cell table:style-name="ce46" table:formula="of:=SUM([.$H16:.$I16])" office:value-type="float" office:value="15" calcext:value-type="float">
|
||||
<text:p>15</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J16]/[.$J$2]" office:value-type="percentage" office:value="0.046583850931677" calcext:value-type="percentage">
|
||||
<text:p>4.66%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J16]/[.$J$2]" office:value-type="percentage" office:value="0.045045045045045" calcext:value-type="percentage">
|
||||
<text:p>4.50%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G16]/[.$G$2]" office:value-type="percentage" office:value="0.0875" calcext:value-type="percentage">
|
||||
<text:p>8.75%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G16]/[.$G$2]" office:value-type="percentage" office:value="0.0828402366863905" calcext:value-type="percentage">
|
||||
<text:p>8.28%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K16]/[.$L16]" office:value-type="float" office:value="0.532386867790595" calcext:value-type="float">
|
||||
<text:p>0.532386867790595</text:p>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K16]/[.$L16]" office:value-type="float" office:value="0.543758043758044" calcext:value-type="float">
|
||||
<text:p>0.543758043758044</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:number-columns-repeated="1011"/>
|
||||
</table:table-row>
|
||||
|
@ -1040,14 +1040,14 @@
|
|||
<table:table-cell table:style-name="ce47" table:formula="of:=SUM([.$H17:.$I17])" office:value-type="float" office:value="13" calcext:value-type="float">
|
||||
<text:p>13</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$J17]/[.$J$2]" office:value-type="percentage" office:value="0.0403726708074534" calcext:value-type="percentage">
|
||||
<text:p>4.04%</text:p>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$J17]/[.$J$2]" office:value-type="percentage" office:value="0.039039039039039" calcext:value-type="percentage">
|
||||
<text:p>3.90%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$G17]/[.$G$2]" office:value-type="percentage" office:value="0.09625" calcext:value-type="percentage">
|
||||
<text:p>9.63%</text:p>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$G17]/[.$G$2]" office:value-type="percentage" office:value="0.0911242603550296" calcext:value-type="percentage">
|
||||
<text:p>9.11%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce47" table:formula="of:=[.$K17]/[.$L17]" office:value-type="float" office:value="0.419456320077438" calcext:value-type="float">
|
||||
<text:p>0.419456320077438</text:p>
|
||||
<table:table-cell table:style-name="ce47" table:formula="of:=[.$K17]/[.$L17]" office:value-type="float" office:value="0.428415428415428" calcext:value-type="float">
|
||||
<text:p>0.428415428415428</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce47" table:number-columns-repeated="1011"/>
|
||||
</table:table-row>
|
||||
|
@ -1077,14 +1077,14 @@
|
|||
<table:table-cell table:style-name="ce46" table:formula="of:=SUM([.$H18:.$I18])" office:value-type="float" office:value="18" calcext:value-type="float">
|
||||
<text:p>18</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J18]/[.$J$2]" office:value-type="percentage" office:value="0.0559006211180124" calcext:value-type="percentage">
|
||||
<text:p>5.59%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J18]/[.$J$2]" office:value-type="percentage" office:value="0.0540540540540541" calcext:value-type="percentage">
|
||||
<text:p>5.41%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G18]/[.$G$2]" office:value-type="percentage" office:value="0.03125" calcext:value-type="percentage">
|
||||
<text:p>3.13%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G18]/[.$G$2]" office:value-type="percentage" office:value="0.029585798816568" calcext:value-type="percentage">
|
||||
<text:p>2.96%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K18]/[.$L18]" office:value-type="float" office:value="1.7888198757764" calcext:value-type="float">
|
||||
<text:p>1.7888198757764</text:p>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K18]/[.$L18]" office:value-type="float" office:value="1.82702702702703" calcext:value-type="float">
|
||||
<text:p>1.82702702702703</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:number-columns-repeated="1011"/>
|
||||
</table:table-row>
|
||||
|
@ -1117,14 +1117,14 @@
|
|||
<table:table-cell table:style-name="ce46" table:formula="of:=SUM([.$H19:.$I19])" office:value-type="float" office:value="17" calcext:value-type="float">
|
||||
<text:p>17</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J19]/[.$J$2]" office:value-type="percentage" office:value="0.0527950310559006" calcext:value-type="percentage">
|
||||
<text:p>5.28%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J19]/[.$J$2]" office:value-type="percentage" office:value="0.0510510510510511" calcext:value-type="percentage">
|
||||
<text:p>5.11%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G19]/[.$G$2]" office:value-type="percentage" office:value="0.025" calcext:value-type="percentage">
|
||||
<text:p>2.50%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G19]/[.$G$2]" office:value-type="percentage" office:value="0.0236686390532544" calcext:value-type="percentage">
|
||||
<text:p>2.37%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K19]/[.$L19]" office:value-type="float" office:value="2.11180124223602" calcext:value-type="float">
|
||||
<text:p>2.11180124223602</text:p>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K19]/[.$L19]" office:value-type="float" office:value="2.15690690690691" calcext:value-type="float">
|
||||
<text:p>2.15690690690691</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:number-columns-repeated="1011"/>
|
||||
</table:table-row>
|
||||
|
@ -1154,14 +1154,14 @@
|
|||
<table:table-cell table:style-name="ce46" table:formula="of:=SUM([.$H20:.$I20])" office:value-type="float" office:value="16" calcext:value-type="float">
|
||||
<text:p>16</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J20]/[.$J$2]" office:value-type="percentage" office:value="0.0496894409937888" calcext:value-type="percentage">
|
||||
<text:p>4.97%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$J20]/[.$J$2]" office:value-type="percentage" office:value="0.0480480480480481" calcext:value-type="percentage">
|
||||
<text:p>4.80%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G20]/[.$G$2]" office:value-type="percentage" office:value="0.025" calcext:value-type="percentage">
|
||||
<text:p>2.50%</text:p>
|
||||
<table:table-cell table:style-name="ce52" table:formula="of:=[.$G20]/[.$G$2]" office:value-type="percentage" office:value="0.0236686390532544" calcext:value-type="percentage">
|
||||
<text:p>2.37%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K20]/[.$L20]" office:value-type="float" office:value="1.98757763975155" calcext:value-type="float">
|
||||
<text:p>1.98757763975155</text:p>
|
||||
<table:table-cell table:style-name="ce46" table:formula="of:=[.$K20]/[.$L20]" office:value-type="float" office:value="2.03003003003003" calcext:value-type="float">
|
||||
<text:p>2.03003003003003</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce46" table:number-columns-repeated="1011"/>
|
||||
</table:table-row>
|
||||
|
@ -1191,14 +1191,14 @@
|
|||
<table:table-cell table:style-name="ce48" table:formula="of:=SUM([.$H21:.$I21])" office:value-type="float" office:value="18" calcext:value-type="float">
|
||||
<text:p>18</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce54" table:formula="of:=[.$J21]/[.$J$2]" office:value-type="percentage" office:value="0.0559006211180124" calcext:value-type="percentage">
|
||||
<text:p>5.59%</text:p>
|
||||
<table:table-cell table:style-name="ce54" table:formula="of:=[.$J21]/[.$J$2]" office:value-type="percentage" office:value="0.0540540540540541" calcext:value-type="percentage">
|
||||
<text:p>5.41%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce54" table:formula="of:=[.$G21]/[.$G$2]" office:value-type="percentage" office:value="0.06625" calcext:value-type="percentage">
|
||||
<text:p>6.63%</text:p>
|
||||
<table:table-cell table:style-name="ce54" table:formula="of:=[.$G21]/[.$G$2]" office:value-type="percentage" office:value="0.0627218934911243" calcext:value-type="percentage">
|
||||
<text:p>6.27%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce48" table:formula="of:=[.$K21]/[.$L21]" office:value-type="float" office:value="0.843782960271886" calcext:value-type="float">
|
||||
<text:p>0.843782960271886</text:p>
|
||||
<table:table-cell table:style-name="ce48" table:formula="of:=[.$K21]/[.$L21]" office:value-type="float" office:value="0.861805201427843" calcext:value-type="float">
|
||||
<text:p>0.861805201427843</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce48" table:number-columns-repeated="1011"/>
|
||||
</table:table-row>
|
||||
|
@ -1231,14 +1231,14 @@
|
|||
<table:table-cell table:style-name="ce48" table:formula="of:=SUM([.$H22:.$I22])" office:value-type="float" office:value="15" calcext:value-type="float">
|
||||
<text:p>15</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce54" table:formula="of:=[.$J22]/[.$J$2]" office:value-type="percentage" office:value="0.046583850931677" calcext:value-type="percentage">
|
||||
<text:p>4.66%</text:p>
|
||||
<table:table-cell table:style-name="ce54" table:formula="of:=[.$J22]/[.$J$2]" office:value-type="percentage" office:value="0.045045045045045" calcext:value-type="percentage">
|
||||
<text:p>4.50%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce54" table:formula="of:=[.$G22]/[.$G$2]" office:value-type="percentage" office:value="0.06" calcext:value-type="percentage">
|
||||
<text:p>6.00%</text:p>
|
||||
<table:table-cell table:style-name="ce54" table:formula="of:=[.$G22]/[.$G$2]" office:value-type="percentage" office:value="0.0568047337278107" calcext:value-type="percentage">
|
||||
<text:p>5.68%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce48" table:formula="of:=[.$K22]/[.$L22]" office:value-type="float" office:value="0.77639751552795" calcext:value-type="float">
|
||||
<text:p>0.77639751552795</text:p>
|
||||
<table:table-cell table:style-name="ce48" table:formula="of:=[.$K22]/[.$L22]" office:value-type="float" office:value="0.792980480480481" calcext:value-type="float">
|
||||
<text:p>0.792980480480481</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce48" table:number-columns-repeated="1011"/>
|
||||
</table:table-row>
|
||||
|
@ -1271,14 +1271,14 @@
|
|||
<table:table-cell table:style-name="ce47" table:formula="of:=SUM([.$H23:.$I23])" office:value-type="float" office:value="9" calcext:value-type="float">
|
||||
<text:p>9</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$J23]/[.$J$2]" office:value-type="percentage" office:value="0.0279503105590062" calcext:value-type="percentage">
|
||||
<text:p>2.80%</text:p>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$J23]/[.$J$2]" office:value-type="percentage" office:value="0.027027027027027" calcext:value-type="percentage">
|
||||
<text:p>2.70%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$G23]/[.$G$2]" office:value-type="percentage" office:value="0.05875" calcext:value-type="percentage">
|
||||
<text:p>5.88%</text:p>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$G23]/[.$G$2]" office:value-type="percentage" office:value="0.0556213017751479" calcext:value-type="percentage">
|
||||
<text:p>5.56%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce47" table:formula="of:=[.$K23]/[.$L23]" office:value-type="float" office:value="0.475749966961808" calcext:value-type="float">
|
||||
<text:p>0.475749966961808</text:p>
|
||||
<table:table-cell table:style-name="ce47" table:formula="of:=[.$K23]/[.$L23]" office:value-type="float" office:value="0.485911443358252" calcext:value-type="float">
|
||||
<text:p>0.485911443358252</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce47" table:number-columns-repeated="1011"/>
|
||||
</table:table-row>
|
||||
|
@ -1311,14 +1311,14 @@
|
|||
<table:table-cell table:style-name="ce47" table:formula="of:=SUM([.$H24:.$I24])" office:value-type="float" office:value="9" calcext:value-type="float">
|
||||
<text:p>9</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$J24]/[.$J$2]" office:value-type="percentage" office:value="0.0279503105590062" calcext:value-type="percentage">
|
||||
<text:p>2.80%</text:p>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$J24]/[.$J$2]" office:value-type="percentage" office:value="0.027027027027027" calcext:value-type="percentage">
|
||||
<text:p>2.70%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$G24]/[.$G$2]" office:value-type="percentage" office:value="0.065" calcext:value-type="percentage">
|
||||
<text:p>6.50%</text:p>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$G24]/[.$G$2]" office:value-type="percentage" office:value="0.0615384615384615" calcext:value-type="percentage">
|
||||
<text:p>6.15%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce47" table:formula="of:=[.$K24]/[.$L24]" office:value-type="float" office:value="0.430004777830865" calcext:value-type="float">
|
||||
<text:p>0.430004777830865</text:p>
|
||||
<table:table-cell table:style-name="ce47" table:formula="of:=[.$K24]/[.$L24]" office:value-type="float" office:value="0.439189189189189" calcext:value-type="float">
|
||||
<text:p>0.439189189189189</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce47" table:number-columns-repeated="1011"/>
|
||||
</table:table-row>
|
||||
|
@ -1343,7 +1343,70 @@
|
|||
<table:table-cell table:style-name="ce21" table:number-columns-repeated="2"/>
|
||||
<table:table-cell table:style-name="ce13" table:number-columns-repeated="1012"/>
|
||||
</table:table-row>
|
||||
<table:table-row table:style-name="ro1" table:number-rows-repeated="1048550">
|
||||
<table:table-row table:style-name="ro6">
|
||||
<table:table-cell table:style-name="ce41" office:value-type="string" calcext:value-type="string">
|
||||
<text:p>Multiplayer Phrase Pairing</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce41" office:value-type="string" calcext:value-type="string">
|
||||
<text:p>Networking</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce41" office:value-type="string" calcext:value-type="string">
|
||||
<text:p>As a player, I would like to be able to pair with another Player I know, by setting up a common phrase that will match me with the Player I know, so that I can play a game with the Player I know.</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce41" office:value-type="string" calcext:value-type="string">
|
||||
<text:p>Set up a “room code” system where Players can specify a “phrase” or “code word” that will only pair them with other Players that use the same “phrase”.</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce41" office:value-type="string" calcext:value-type="string">
|
||||
<text:p>The back-end should be able to pair Players with identical “phrases” or “code words”. The front-end should allow the Player to set a “phrase” or “code word”.</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce47" office:value-type="string" calcext:value-type="string">
|
||||
<text:p>Exciter</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce47" office:value-type="float" office:value="45" calcext:value-type="float">
|
||||
<text:p>45</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce47" office:value-type="float" office:value="8" calcext:value-type="float">
|
||||
<text:p>8</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce47" office:value-type="float" office:value="3" calcext:value-type="float">
|
||||
<text:p>3</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce47" office:value-type="float" office:value="11" calcext:value-type="float">
|
||||
<text:p>11</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$J26]/[.$J$2]" office:value-type="percentage" office:value="0.033033033033033" calcext:value-type="percentage">
|
||||
<text:p>3.30%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce53" table:formula="of:=[.$G26]/[.$G$2]" office:value-type="percentage" office:value="0.0532544378698225" calcext:value-type="percentage">
|
||||
<text:p>5.33%</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce47" table:formula="of:=[.$K26]/[.$L26]" office:value-type="float" office:value="0.620286953620287" calcext:value-type="float">
|
||||
<text:p>0.620286953620287</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce47" table:number-columns-repeated="1011"/>
|
||||
</table:table-row>
|
||||
<table:table-row table:style-name="ro4">
|
||||
<table:table-cell table:style-name="ce6" office:value-type="string" calcext:value-type="string">
|
||||
<text:p>Refactoring and Code Improvement</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce6" office:value-type="string" calcext:value-type="string">
|
||||
<text:p>Constraint</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce6" office:value-type="string" calcext:value-type="string">
|
||||
<text:p>As I developer, I expect the code to be improved when improvements can be made to improve code quality and prevent technical debt.</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce6"/>
|
||||
<table:table-cell table:style-name="ce6" office:value-type="string" calcext:value-type="string">
|
||||
<text:p>The front-end and back-end code should be reasonably maintained, with fixes and/or refactorings where possible.</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce13" office:value-type="string" calcext:value-type="string">
|
||||
<text:p>Mandatory</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="ce13" table:number-columns-repeated="4"/>
|
||||
<table:table-cell table:style-name="ce21" table:number-columns-repeated="2"/>
|
||||
<table:table-cell table:style-name="ce13" table:number-columns-repeated="1012"/>
|
||||
</table:table-row>
|
||||
<table:table-row table:style-name="ro1" table:number-rows-repeated="1048548">
|
||||
<table:table-cell table:number-columns-repeated="1024"/>
|
||||
</table:table-row>
|
||||
<table:table-row table:style-name="ro1">
|
||||
|
|
BIN
spreadsheets/user_stories_CONVERTED_2022-04-16.xlsx
Normal file
BIN
spreadsheets/user_stories_CONVERTED_2022-04-16.xlsx
Normal file
Binary file not shown.
BIN
spreadsheets/user_stories_CONVERTED_2022-05-04.xlsx
Normal file
BIN
spreadsheets/user_stories_CONVERTED_2022-05-04.xlsx
Normal file
Binary file not shown.
Loading…
Reference in a new issue