[Question]
  [
## Introduction:
I saw there was only [one other badminton related challenge right now](https://codegolf.stackexchange.com/questions/44900/confused-badminton-players). Since I play badminton myself (for the past 13 years now), I figured I'd add some badminton-related challenges. Here the first one:
## Challenge:
**Input:** Two integers  
**Output:** One of three distinct and unique outputs of your own choice. One indicating that the input is a valid badminton score AND the set has ended with a winner; one indicating that the input is a valid badminton score AND the set is still in play; one indicating the input is not a valid badminton score.
With badminton, both (pairs of) players start with 0 points, and you stop when one of the two (pairs of) players has reached a score of 21, with at least 2 points difference, up to a maximum of 30-29.
So these are all possible input-pairs (in either order) indicating it's a valid badminton score AND the set has ended:
```
[[0,21],[1,21],[2,21],[3,21],[4,21],[5,21],[6,21],[7,21],[8,21],[9,21],[10,21],[11,21],[12,21],[13,21],[14,21],[15,21],[16,21],[17,21],[18,21],[19,21],[20,22],[21,23],[22,24],[23,25],[24,26],[25,27],[26,28],[27,29],[28,30],[29,30]]
```
And these are all possible input-pairs (in either order) indicating it's a valid badminton score BUT the set is still in play:
```
[[0,0],[0,1],[0,2],[0,3],[0,4],[0,5],[0,6],[0,7],[0,8],[0,9],[0,10],[0,11],[0,12],[0,13],[0,14],[0,15],[0,16],[0,17],[0,18],[0,19],[0,20],[1,1],[1,2],[1,3],[1,4],[1,5],[1,6],[1,7],[1,8],[1,9],[1,10],[1,11],[1,12],[1,13],[1,14],[1,15],[1,16],[1,17],[1,18],[1,19],[1,20],[2,2],[2,3],[2,4],[2,5],[2,6],[2,7],[2,8],[2,9],[2,10],[2,11],[2,12],[2,13],[2,14],[2,15],[2,16],[2,17],[2,18],[2,19],[2,20],[3,3],[3,4],[3,5],[3,6],[3,7],[3,8],[3,9],[3,10],[3,11],[3,12],[3,13],[3,14],[3,15],[3,16],[3,17],[3,18],[3,19],[3,20],[4,4],[4,5],[4,6],[4,7],[4,8],[4,9],[4,10],[4,11],[4,12],[4,13],[4,14],[4,15],[4,16],[4,17],[4,18],[4,19],[4,20],[5,5],[5,6],[5,7],[5,8],[5,9],[5,10],[5,11],[5,12],[5,13],[5,14],[5,15],[5,16],[5,17],[5,18],[5,19],[5,20],[6,6],[6,7],[6,8],[6,9],[6,10],[6,11],[6,12],[6,13],[6,14],[6,15],[6,16],[6,17],[6,18],[6,19],[6,20],[7,7],[7,8],[7,9],[7,10],[7,11],[7,12],[7,13],[7,14],[7,15],[7,16],[7,17],[7,18],[7,19],[7,20],[8,8],[8,9],[8,10],[8,11],[8,12],[8,13],[8,14],[8,15],[8,16],[8,17],[8,18],[8,19],[8,20],[9,9],[9,10],[9,11],[9,12],[9,13],[9,14],[9,15],[9,16],[9,17],[9,18],[9,19],[9,20],[10,10],[10,11],[10,12],[10,13],[10,14],[10,15],[10,16],[10,17],[10,18],[10,19],[10,20],[11,11],[11,12],[11,13],[11,14],[11,15],[11,16],[11,17],[11,18],[11,19],[11,20],[12,12],[12,13],[12,14],[12,15],[12,16],[12,17],[12,18],[12,19],[12,20],[13,13],[13,14],[13,15],[13,16],[13,17],[13,18],[13,19],[13,20],[14,14],[14,15],[14,16],[14,17],[14,18],[14,19],[14,20],[15,15],[15,16],[15,17],[15,18],[15,19],[15,20],[16,16],[16,17],[16,18],[16,19],[16,20],[17,17],[17,18],[17,19],[17,20],[18,18],[18,19],[18,20],[19,19],[19,20],[20,20],[20,21],[21,21],[21,22],[22,22],[22,23],[23,23],[23,24],[24,24],[24,25],[25,25],[25,26],[26,26],[26,27],[27,27],[27,28],[28,28],[28,29],[29,29]]
```
Any other pair of integer would be an invalid badminton score.
## Challenge rules:
* I/O is flexible, so:
	+ You can take the input as a list of two numbers; two separated numbers through STDIN or function parameters; two strings; etc.
	+ Output will be three distinct and unique values of your own choice. Can be integers (i.e. `[0,1,2]`, `[1,2,3]`, `[-1,0,1]`, etc.); can be Booleans (i.e. `[true,false,undefined/null/empty]`); can be characters/strings (i.e. `["valid & ended","valid","invalid"]`); etc.
	+ **Please specify the I/O you've used in your answer!**
* **You are allowed to take the input-integers pre-ordered from lowest to highest or vice-versa.**
* The input integers can be negative, in which case they are of course invalid.
## General rules:
* This is [code-golf](/questions/tagged/code-golf "show questions tagged 'code-golf'"), so shortest answer in bytes wins.  
Don't let code-golf languages discourage you from posting answers with non-codegolfing languages. Try to come up with an as short as possible answer for 'any' programming language.
* [Standard rules apply](https://codegolf.meta.stackexchange.com/questions/2419/default-for-code-golf-program-function-or-snippet/2422#2422) for your answer with [default I/O rules](https://codegolf.meta.stackexchange.com/questions/2447/default-for-code-golf-input-output-methods/), so you are allowed to use STDIN/STDOUT, functions/method with the proper parameters and return-type, full programs. Your call.
* [Default Loopholes](https://codegolf.meta.stackexchange.com/questions/1061/loopholes-that-are-forbidden-by-default) are forbidden.
* If possible, please add a link with a test for your code (i.e. [TIO](https://tio.run/#)).
* Also, adding an explanation for your answer is highly recommended.
## Test cases:
These test cases are valid, and the set has ended:
```
0 21
12 21
21 23
28 30
29 30
```
These test cases are valid, but the set is still in play:
```
0 0
0 20
12 12
21 21
21 22
```
These test cases are invalid:
```
-21 19
-19 21
-1 1
12 22
29 31
30 30
42 43
1021 1021
```
  
[Answer]
# [Python 2](https://docs.python.org/2/), 47 bytes
```
lambda a,b:[61>60-a>b<3+max(19,a)for b in-~b,b]
```
[Try it online!](https://tio.run/##jZZPb9tGFMTv/hS8WUYlgLP/N6gDBEgOBYoee3F9oGBJFeDIhqsUzaVf3ZXezJoUkwC9PPnR2vk9UjvDff56/PPp4F63t3@8Pg6f1w9DNyzX7@4S3qd@Nbxf/@x/@jz8s0BdDjfbp5du3e0Pq3/Xy/X969Xzy/5w7K7/Hh73D93m8LB5eHeti9tF1y87h5u3Hu6ydzj1ftKXZef7SV/ZX1L2h@75cfg643STdcbtL7lwM@58Djdy9gcjTQir81dQxyWr09O40OhW52/M7tXN7mXyf99f3ms4fT9MngV6Q/ZnBi9e/f7h118@3t7d9cv@fnmqsOqseqvBarSarGarxWrlKi3manA5uB4UABVACVADFAFVHFXcWQVLVmfVWw1Wo9VkNVstVitX9fyQBpeD60EBUAGUADVAEVDFUcUmcTaDsxmczeBsBmczOJvB2QzOZnCcwXEGxxkcZ3CcwXEGxxkcZ3CcwXEGxxkcZ/BG90b3RvdG90b3RvdG96R70j3pnnRPuifdk@5J96R70j3pnvRg3GDcYNxg3GDcYNxAbiA3kBvIDeQGcgO5gdxAbiA3kBvIjUaMRoxGjEaMRowkRhIjiZHESGIkMZIYSYwkRhIjiZHEZKxkrGSsZKxEViIrkZXISmQlshJZiaxEViIrkZXIykbJRslGyaRkUjIpmZRMSiYlk5JJyaRkUjIpmZRi@sX0C/UL9Qv1C/UL9Qv1C/UL9Qv1C/UL9Qv1qylXKlcqVypXKlcqVypXKlcqVypXKlcqVyWAYgXKFShYoGSBogXKFihcoHSB4gXKFyhg0BKmpUSLiZYTLShaUrSoaFnRwqKlRYsL5QVkecjzkOkh10O2h3wPGR9yPmR9yPuQeyH7Qv6FDAw5GLIw5GHIxJCLIRtCPoSMCDkRsiLkRciMkBshN0F2gvwEGQpyFGQpyFOQJSBPQKaAXAHZAvIFtKuhbQ3ta2hjQzsb2pbQvoQ2JrQzoV0FbStoXzntA9ePvUV0y3uMvWW2U@/G3kLcq/djb6ke1Iext5iP6uPYW@4n9Wns7UWQ1eextzdDUV/eem/3U9XXc39/dfXpt4@f@GrXG7W9zjCNdiWt4k/JpOiQw5sdm23aNm/bs22vtk3az95@zvZztZ/lm8c@f6zzxzZ/LLPb5u3ubicHzeOX58fNYrs4/X0zO@u9ncD@2hwXO/vG@QB6@jyfAu081E5M15eHz@8usKf8tmD3tD/s/geh22@7u9OF@@7wdJzI/Oi0OFM6r3gZDrvNYhVPZ7P@pmtnaF0e7OIcwpt7/Q8 "Python 2 – Try It Online")
Outputs a list of two Booleans. Thanks to [TFeld](https://codegolf.stackexchange.com/users/38592/tfeld) for writing a test suite in [their answer](https://codegolf.stackexchange.com/a/182248/20260) that made it easy to check my solution.
```
ended: [False, True]
going: [True, True]
invalid: [False, False]
```
The key insight is that a valid score ends the game exactly if increasing the higher value `b` makes the score invalid. So, we just code up the validity condition, and check it for `(a,b+1)` in addition to `(a,b)` to see if the game has ended.
Validity is checked via three conditions that are chained together:
* `b<3+max(19,a)`: Checks that the higher score `b` isn't past winning, with either `b<=21` or `b<=a+2` (win by two)
* `60-a>b`: Equivalent to `a+b<=59`, ensuring the score isn't above `(29,30)`
* `61>60-a`: Equivalent to `a>=0`, ensures the lower score is non-negative
---
# [Python 2](https://docs.python.org/2/), 44 bytes
```
lambda a,b:[b-61<~a<a>b/22*b-3for b in-~b,b]
```
[Try it online!](https://tio.run/##jZbNbhtHEITveoq9iQpIZKvnb8ewAgSwDwaCHHORdSAhkiGgUILCBPDFry6TXTVacu0AuTTVK059vcup2nn@cvjzaW@vm9vPr4/Lv1YPy245X727Wy0y3n9dvl/@svrZ7KfVImyeXrpVt9svvq7mq/vXq@eX3f7QXf@7fNw9dOv9w/rh3bUubmZdP@8MN2897LI3HPtw1g/zLvRnfWV/Sdntu@fH5ZcJpztb59z@kgubcKdz2MjZ7Z10RlicvoI6LlmgXmp0i9M3Jvdqk3s5@3/oL@81Hr8fz54Fekf2JwYvXv3x62@fPtze3fXz/n5@rPBqXoPX6DV5zV6L18Fr5Sot5mpwObgeFAAVQAlQAxQBVYwqdlLBnNW8Bq/Ra/KavRavg9fKVT0/pMHl4HpQAFQAJUANUARUMar4JOYzmM9gPoP5DOYzmM9gPoP5DMYZjDMYZzDOYJzBOINxBuMMxhmMMxhnMM4QnB6cHpwenB6cHpwenB5ID6QH0gPpgfRAeiA9kB5ID6QH0gPp0bnRudG50bnRudG5kdxIbiQ3khvJjeRGciO5kdxIbiQ3kpucmJyYnJicmJyYSEwkJhITiYnERGIiMZGYSEwkJhITidlZ2VnZWdlZmaxMViYrk5XJymRlsjJZmaxMViYrk1WcUpxSnFJIKaQUUgophZRCSiGlkFJIKaQUUgopg@sPrj9Qf6D@QP2B@gP1B@oP1B@oP1B/oP5A/YH61ZUrlSuVK5UrlSuVK5UrlSuVK5UrlSuVqxJAsQLlChQsULJA0QJlCxQuULpA8QLlCxQwaAnTUqLFRMuJFhQtKVpUtKxoYdHSosWF8gKyPOR5yPSQ6yHbQ76HjA85H7I@5H3IvZB9If9CBoYcDFkY8jBkYsjFkA0hH0JGhJwIWRHyImRGyI2QmyA7QX6CDAU5CrIU5CnIEpAnIFNAroBsAfkC2tXQtob2NbSxoZ0NbUtoX0IbE9qZ0K6CthW0r0z7wPqx94hueY@x98w29Tb2HuJBfRh7T/WoPo69x3xSn8becz@rz2PvL4Kivoy9vxkG9cNbH/x@qvp66u@vrj7@/uEjX@16o7bXGc6jXUmr@FMyKTrk8GbHZpu2zdv2bNurbZP2s7efs/1c7Wf57rFPH@v0sU0fy@S2ebvb27NT5uGf58f1bDM7/n0zOeu9ncD@Xh9mW//G6QB6/DydAv081E5M15eHzx8u8Kf8tmD7tNtv/weh2226u@OF@27/dDiT@a/T4kTptOJlud@uZ4t0PJv1N107Q@vy0i9OIby5128 "Python 2 – Try It Online")
An improved validity check by TFeld saves 3 bytes. The main idea is to branch on "overtime" `b>21` with `b/22*b` which effectively sets below-21 scores to zero, whereas I'd branched on `a>19` with the longer `max(19,a)`.
---
# [Python 2](https://docs.python.org/2/), 43 bytes
```
lambda a,b:a>>99|cmp(2+max(19,a)%30-a/29,b)
```
[Try it online!](https://tio.run/##jZZPayNHFMTv/hRzCZaJTKZe/5nuBS8Edg@BkGMujg/jtaUV2LJxTMhCvrujeVXtkSYJ5PLEk6br92bUVdPP316/Pu3tbXP129vD@Hh7N3bj@vbD@PFjrX99eXxe2feP458r1PV48V3oL8cfrK5vL97Onl92@9fu/I/xYXfX3e/v7u8@nOvLzarr153h4r2HnfaGQx@O@rLuQn/UV/anlN2@e34Yvy043dE65/anXNiCu5zDZs5u76QjwuV0Ceq85PLwKE40usvpisW92uJejn4P/em9xsP18ehZoHdkPzH45dmvP/7806er6@t@3d@sDxVezWvwGr0mr9nr4LV4rVylxVwNLgfXgwKgAigBaoAioIpRxSYVrFnNa/AavSav2evgtXitXNXzQxpcDq4HBUAFUALUAEVAFaOKT2I@g/kM5jOYz2A@g/kM5jOYz2CcwTiDcQbjDMYZjDMYZzDOYJzBOINxBuMMwenB6cHpwenB6cHpwemB9EB6ID2QHkgPpAfSA@mB9EB6ID2QHp0bnRudG50bnRudG8mN5EZyI7mR3EhuJDeSG8mN5EZyI7nJicmJyYnJicmJicREYiIxkZhITCQmEhOJicREYiIxkZidlZ2VnZWdlcnKZGWyMlmZrExWJiuTlcnKZGWyMlmDUwanDE4ZSBlIGUgZSBlIGUgZSBlIGUgZSBlIGUgprl9cv1C/UL9Qv1C/UL9Qv1C/UL9Qv1C/UL9Qv7pypXKlcqVypXKlcqVypXKlcqVypXKlclUCKFagXIGCBUoWKFqgbIHCBUoXKF6gfIECBi1hWkq0mGg50YKiJUWLipYVLSxaWrS4UF5Aloc8D5kecj1ke8j3kPEh50PWh7wPuReyL@RfyMCQgyELQx6GTAy5GLIh5EPIiJATIStCXoTMCLkRchNkJ8hPkKEgR0GWgjwFWQLyBGQKyBWQLSBfQLsa2tbQvoY2NrSzoW0J7UtoY0I7E9pV0LaC9pVpH1g/9x7RLe8x957Zpt7m3kM8qA9z76ke1ce595hP6tPce@5n9Xnu/UUwqB/m3t8MRX1574PfT1Vfp/7m7OzzL58@89WuN2p7neE42pW0ij8lk6JDDm92bLZp27xtz7a92jZpf3v7O9vf1f6Wfzz25WNdPrblY1ncNm93e3V0yNysDnVxyHs/ev1@/7ra@gWbp5fp8un45wehdlQ6Pz11/usCf7zvC7ZPu/32fxC63aa7Pnxx0@2fXo9k/uuYuFCaVryM@@394fTYTxu5v@imH27nH0Z@u@Tw/t7@Bg "Python 2 – Try It Online")
Outputs:
```
ended: 0
going: -1
invalid: 1
```
Assumes that the inputs are not below \$-2^{99}\$.
[Answer]
# [Python 2](https://docs.python.org/2/), ~~97~~ ~~95~~ ~~75~~ ~~72~~ ~~71~~ ~~70~~ ~~69~~ ~~64~~ ~~55~~ ~~54~~ ~~52~~ ~~51~~ ~~50~~ 48 bytes
```
lambda a,b:(b-61<~a<a>b/22*b-3)*~(19<b-(b<30)>a)
```
[Try it online!](https://tio.run/##jZbNbuNGEITvegreLC0khNXzx1l4FwiwewgQ5JiL4wMFW44ARzYcI8Be9tUdqavGlJgEyGWMpjX1Ncmu4jx/e/396WBvu0@/vT2Of2zvxm5cbz8ut5uM6@/j9fh5@4PZh@0mrD58X6JebzfL7XXoV5/H1dvzy/7w2l39NT7u77r7w9393cerBS/ull2/7gyr9xp2WRuOdTirh3V31J3qynpxQdkfuufH8duM053tc25/yYXNuPM@bOLsD046I2xOP0GdtmxQLzW6zekXs3u12b2c/T/0l/caj7@PZ88CvSP7E4MXF7/@@PNPXz7d3PTr/nZ9XOGr@Rp8jb4mX7OvxdfB18pd2szd4HZwPygAKoASoAYoAqoYVeykgjVX8zX4Gn1NvmZfi6@Dr5W7ev6RBreD@0EBUAGUADVAEVDFqOKdmPdg3oN5D@Y9mPdg3oN5D@Y9GHsw9mDswdiDsQdjD8YejD0YezD2YOzB2ENwenB6cHpwenB6cHpweiA9kB5ID6QH0gPpgfRAeiA9kB5ID6RH50bnRudG50bnRudGciO5kdxIbiQ3khvJjeRGciO5kdxIbnJicmJyYnJicmIiMZGYSEwkJhITiYnERGIiMZGYSEwkZmdlZ2VnZWdlsjJZmaxMViYrk5XJymRlsjJZmaxMVnFKcUpxSiGlkFJIKaQUUgophZRCSiGlkFJIKaQMrj@4/kD9gfoD9QfqD9QfqD9Qf6D@QP2B@gP1B@pXV65UrlSuVK5UrlSuVK5UrlSuVK5UrlSuSgDFCpQrULBAyQJFC5QtULhA6QLFC5QvUMCgJUxLiRYTLSdaULSkaFHRsqKFRUuLFhfKC8jykOch00Ouh2wP@R4yPuR8yPqQ9yH3QvaF/AsZGHIwZGHIw5CJIRdDNoR8CBkRciJkRciLkBkhN0JuguwE@QkyFOQoyFKQpyBLQJ6ATAG5ArIF5AtoqqGxhuYaGmxosqGxhOYSGkxoMqGpgsYKmivTHFg/1R7RLe8x1Z7Zptqm2kM8qA5T7akeVcep9phPqtNUe@5n1Xmq/UNQVJep9i/DoHp4r4PfT1VdT/XtYvH1ly9f@WnXF7V9znAe7UpaxZ@SSdEhhzc7Ntu0MW/j2carjUl77e11ttfVXss/Hvv8sc4f2/yxzG6bt3t@nns/Zf15/7rcLY9Hz9Xu6eV0BD2d9PzM005FV5cHzH/d4E/yfcPD0/7w8D8I3X7X3Rwv3HaHp9czmf86Ec6UTjtexsPD/XKTjuevftWdLm@ny6NfnEN4c29/Aw "Python 2 – Try It Online")
Takes input as pre-ordered `a,b`.
Returns `-2`, `-1`, `0` for `ended`, `in play`, `invalid`.
-1 byte, thanks to Kevin Cruijssen
---
Left part (`b-61<~a<a>b/22*b-3`) is a validity-check, and right part (`19<b-(b<30)>a`) is a check for game ended.
[Answer]
# JavaScript (ES6), ~~55 53~~ 48 bytes
*Thanks to @KevinCruijssen for noticing that I was not fully assuming \$a\le b\$ (saving 5 bytes)*
Takes input as `(a)(b)` with \$a\le b\$. Returns \$0\$ (valid), \$1\$ (ended) or \$2\$ (invalid).
```
a=>b=>a<0|a>29|b>30|b>21&b-a>2?2:b>20&b-a>1|b>29
```
[Try it online!](https://tio.run/##dZFNDsIgEEb3nmJWFhY1MHRhja1noX8GQ2hjjYlJ714HdKGCCyaZ92WGR7jou57bq5luuRu7fh2qVVd1U9X6KBZdY7k0tRJUUG6bnMAJD9SI0EjPy7Ud3TzafmfHM8t61/VdxjefcGAgOEPJf7HEJCbEUMV4z5kSMS7f@Itnd21NB2y@GWvBOJisfvCkGMQ7g69I@tJJ@v55BkZixgW1WCX3AwCyjHVyghQlLgHKfASpyPvSFCYiDAtVakoJ/qpxVISFRfw3Unj5UPn6BA "JavaScript (Node.js) – Try It Online")
[Answer]
# [C# (Visual C# Interactive Compiler)](http://www.mono-project.com/docs/about-mono/releases/5.0.0/#csc), ~~53~~ 52 bytes
```
a=>b=>b<0|a-b>2&a>21|b>29|a>30?3:a>20&a-b>1|a>29?1:2
```
Called as `f(max)(min)`. Returns 3 for invalid, 1 for finished, 2 for ongoing.
Saved 1 byte thanks to Kevin Cruijjsen
[Try it online!](https://tio.run/##dZGxCsIwEIZ3n0IcpAGDuYuDqW2KoGJBuog6t6VCHCrYuvXdYyIotYkhw/H9cPlyVza0bJTePesyUnU7@xb34laVrZTXWOexLMyNWJfTQuI0lwidKUSXS84SHhrApjYDQ1AkEKJejS4P1VYHVVfBZJdm6XG/3YzpGCakH10DBBIw4oGADuXEJgPKmaFLLxWWzn9OX@uU9cRwKMZ8Xj5oRH2y4JNF/ND/Wml2Xh9S68SHTiBIQD1dzVsUhCNmqc/AteXwGddwiNz578KsYeF0APZeGnvb6Rc "C# (Visual C# Interactive Compiler) – Try It Online")
[Answer]
# [Jelly](https://github.com/DennisMitchell/jelly), 25 bytes
```
»19«28‘<‘×+2>ɗʋ⁹×,%Ƒ“œþ‘ɗ
```
[Try it online!](https://tio.run/##y0rNyan8///QbkPLQ6uNLB41zLAB4sPTtY3sTk4/1f2ocefh6TqqxyY@aphzdPLhfUC5k9P///9vaP7fyAgA "Jelly – Try It Online")
Left argument: minimum. Right argument: maximum.  
Invalid: `0`. Ongoing: `1`. Ended: `2`.
Mathematically, this works as below (the left argument is \$x\$, the right is \$y\$):
$$[a]=\cases{a\colon1\\\lnot a\colon0}\\\otimes(a,b)=(a\bmod30,b\bmod31)\\x,y\in\mathbb Z\\X:=\min(\max(x+1,20),29)\\p:=(x,y)\\([X<y]+1)[X+2>y][p=\otimes p]$$
Explanation:
```
»19«28‘<‘×+2>ɗʋ⁹×,%Ƒ“œþ‘ɗ Left argument: x, Right argument: y
»19«28‘                   X := Bound x + 1 in [20, 29]:
»19                         X := max(x, 19).
   «28                      X := min(X, 28).
      ‘                     X := X + 1.
       <‘×+2>ɗʋ⁹          X := If X + 2 <= y, then 0, else if X < y, then 2, else 1:
       <                    t := If X < y, then 1, else 0.
        ‘                   t := t + 1.
          +2>ɗ              u := Check if X + 2 > y:
          +2                  u := X + 2.
            >                 u := If u > y, then 1, else 0.
         ×                  X := t * u.
                 ,%Ƒ“œþ‘ɗ z := If x mod 30 = x and y mod 31 = y, then 1, else 0:
                 ,          z := (x, y).
                  % “œþ‘    m := z mod (30, 31) = (x mod 30, y mod 31).
                   Ƒ        z := If z = m, then 1, else 0.
                ×         X * z.
```
[Answer]
# [VDM-SL](https://raw.githubusercontent.com/overturetool/documentation/master/documentation/VDM10LangMan/VDM10_lang_man.pdf), 80 bytes
```
f(i,j)==if(j-i>2and j>21)or(i<0or i=30or j>30)then{}else{(j>20and j-i>1or j=30)} 
```
This function takes the scores ordered in ascending order and returns the empty set if the score is invalid or the set containing whether the set is complete (so {true} if the set is complete and valid and {false} if the set is incomplete and valid)
A full program to run might look like this: 
```
functions
f:int*int+>set of bool
f(i,j)==if(j-i>2and j>21)or(i<0or i=30or j>30)then{}else{(j>20and j-i>1or j=30)}
```
### Explanation:
```
if(j-i>2 and j>21)             /*if scores are too far apart*/
or(i<0 or i=30 or j>30)        /*or scores not in a valid range*/
then {}                        /*return the empty set*/
else{                       }  /*else return the set containing...*/
     (j>20 and j-i>1 or j=30)  /*if the set is complete*/
```
[Answer]
# [Java (JDK)](http://jdk.java.net/), ~~59~~ 48 bytes
```
a->b->b<0|b>29|a>b+2&a>21|a>30?0:a<21|a<30&a<b+2
```
[Try it online!](https://tio.run/##jZhdb@JGFIbv@RVupI2gIchnxp8FU63UjRqpHxeRepPuhUNM4pQABZNtlOW3pwMhH@cZtNkoAh/7YJ/3GXvOO74p78rjm8t/Huvb@WzRBDcu7q2aetIbr6ajpp5Nez/2W/W0qRbjclQFJw@tZVM29Shw@84/n38O/vr42@kvQRE8TKsvTzsfwm647r4NRYdGh1aHkQ5jHSY6THWY6TBHGSwLdQkKE1QmKE1Qm6A4QXWC8gT1GdRnVH3SRWh0aHUY6TDWYaLDVIeZDnOUESJmmShMUJmgNEFtguIE1QnKE9RnUJ/GaDQ3o7kZzc1obkZzM5qb0dyM5mbAzYCbATcDbgbcDLgZcDPgZsDNgJsBNwNuVoOyGpTVoKwGZTUoq0FZDcoClAUoC1AWoCxAWYCyAGUBygKUBSgLUBagIk0m0mQiTSbSZCJNJtJkIpCJQCYCmQhkIpCJQCYCmQhkIpCJQCYCmQhkYo0i1ihijSLWKGKNIgaKGChioIiBIgaKGChioIiBIgaKGChioIiBItHaE6090doTrT2B9gTaE2hPoD2B9gTaE2hPoD2B9gTaE2hPoD3VYlMtNtViU4hNITaF2BRiU4hNITaF2BRiU4hNITaF2BRiM60u0@oyqMugLoO6DOoyqMugLoO6DOoyqMugLoO6DOpyLSeHnBxycsjJISeHnBxycsjJISeHnBxycsjJabHoEYUmUegShTZR6BOFRlHoFIVWUegVhWZR6BbFs4ueMfOcmWfNPG/mmTPPnXn2zPNnnkHzHBotmtALCc2Q0A0J7ZDQDwkNkdARCS2R0BMJTZHQfAjdh9B@CP2H0IAIHYjQggg9iNCECHu9sNkLu72w3Qv7vbDhCzu@sOULG6uwswpbq7C3CpursLsK26uwhwmbmLCLCduYsI8JG5mwWQi7hbBdCPuFsGEI52ThpCyclYXTsnDiE858wqnPcEIx4Z4MvULxllayJ0MvYgwzzJ4Mvc6xzLB7MvRSKGJGtCdDr5ZiZsR7MvSCKmFGsidDr7lSZqR7MvSyLGNG5mdYPXI5M/JNxrrfaj2/tDk5/eP07NdP3nsbvnHwls7y7ZUP/T5NL40gvRLdhdeevR7ndQ5vgvYmRm@G8uYO7zH3HlHvefuOZ@X9@/z9e/T9@@vde2N3K@xe493N6svgtqyn7bNmUU@v3M1RLq6WnYfTaXOyewU4eLv958VNNWqGw2BcPJbHwwv3Pwi/XgxN/rUcXhyZw3JoxG3a8Ofwp3Kw2R7Y8LAcuGOPT7dgMC7ryWpRLYuw3xrPFu3NvsnsS3Ech333PSjc99GR2@o8tAL395xzXV9dF253f7Oxy9ps7tK2qfW0nATb5GJzqP9mz2T725fUJyVB9d/cfVWX3XLUrMrJ6/F6HLRf34d@XCzK@2Vv2Syq8rb9/AB1euX0/veyGV23746Hd@fh56KYHB7enYvbuO50nk9ejMvJsno9d@Wib15g@1r1u8/eLFY8@cuxUCv64flAr/rXyV22n2QX4145n0/u21ueu@3NCHQ6wSvdLeHd4B0d9dX@s/tlU932ZqumN3d3UjNuH3zaXSr4cLkZQzcI81UTtD/Yy27gPjrd4GrWuIN/Tw@6L8OwKaDrrrwbj87rVdatp891a8@13CWaWeNGeldfe9k56L4U686yXj/@Dw "Java (JDK) – Try It Online")
Returns an `Object`, which is the `Integer` `0` for invalid games and the `Boolean`s `true` and `false` for valid ongoing games and for valid finished games respectively. Takes the score ordered (and curried), with the higher score first.
`-2 bytes` by inverting the end-of-match check.  
`-11 bytes` by currying, using bitwise operators, and some return type autoboxing trickery - thanks to @KevinCruijssen
## Ungolfed
```
a->                      // Curried: Target type IntFunction<IntFunction<Object>>
    b->                  // Target type IntFunction<Object>
                         // Invalid if:
            b<0          //    Any score is negative
          | b > 29       //    Both scores above 29
          |   a > b + 2  //    Lead too big
            & a > 21     //        and leader has at least 21 points
          | a > 30       //    Anyone has 31 points
        ? 0              // If invalid, return 0 (autoboxed to Integer)
                         // If valid, return whether the game is ongoing (autoboxed to Boolean)
                         // Ongoing if:
        :   a < 21       //    Nobody has 21 points
          |   a < 30     //    Leader has fewer than 30 points
            & a < b + 2  //        and lead is small
```
[Answer]
# [APL (Dyalog Unicode)](https://www.dyalog.com/), 35 [bytes](https://codegolf.meta.stackexchange.com/a/9429/43319 "When can APL characters be counted as 1 byte each?")[SBCS](https://github.com/abrudz/SBCS ".dyalog files using a single byte character set")
Infix tacit function where ended is 2, ongoing is 1, invalid is 0, smaller and larger scores are left.
```
(,≡30 31|,)×(⊢<2+X)×1+⊢>X←29⌊20⌈1+⊣
```
[Try it online!](https://tio.run/##fY4xTgMxEEV7n2K6BGWR7HEKFiEaGlIhkRRpDXZYS9YSYYMUiQ4pglWIoEDUwAFyh@QmvsgyG2/oQuP/58vz36ipO9Qz5W5v6rqbxZcvyUGKx@xg89mN1fcJ9sZkRY/86TjO3zCPiwp5XDw32U89oSy@LuPyY3ARq6f1Ssb5O03DyzN6R@eDYd0ZFcYbCMYHuFbeeFB3Bh6UszoDVWoIhQFvAhTKgym10ccdxmECKJjApCgalQyPSCVnmCf9t/vqPvx1Ww8@WOfAljB1atYieALxBBLYgnZA3AOw5RZBJevV9qfIyYk87ZJrovZ4bI8VTPJ0dL/J@5IJnnZJfgE "APL (Dyalog Unicode) – Try It Online")
Implements [Erik the Outgolfer's mathematical formulas](https://codegolf.stackexchange.com/a/182258/43319) combined into 
$$X:=\min(\max(x+1,20),29)\\\ ([X< y]+1)[X+2>y][(x,y)=(x\bmod30,y\bmod31)]$$
rearranged (as if traditional mathematical notation had vectorisation and inline assignments) to
$$[(x,y)=(x,y)\bmod(30,31)]×[y<2+X]×(1+[y< (X:=\min(29,\max(20,1+x)))])$$
and translated directly to APL (which is strictly right-associative, so we avoid some parentheses):
$$((x,y)≡30\ 31​|​x,y)×(y<2+X)×1+y>X←29​⌊​20​⌈​1 +x$$
This can be converted into a tacit function simply by substituting \$⊣\$ for \$x\$ and \$⊢\$ for \$y\$, symbolising the left and right arguments rather than the two variables:
$$((⊣​,⊢)≡30\ 31​|⊣​,⊢)×(⊣​<2+X)×1​+⊢​>X←29​⌊​20​⌈​1​+⊣$$
Now \$⊣⎕⊢\$ is equivalent to \$⎕\$ for any infix function \$⎕\$, so we can simplify to
$$(,​≡30\ 31​|​,)×(⊣​<2+X)×1​+⊢​>X←29​⌊​20​⌈​1​+⊣$$
which is our solution; `(,≡30 31|,)×(⊢<2+X)×1+⊢>X←29⌊20⌈1+⊣`:
`⊣` the left argument; \$x\$  
`1+` one plus that; \$1+x\$  
`20⌈` maximum of 20 and that; \$\max(20,…)\$  
`29⌊` minimum of 29 and that; \$\min(29,…)\$  
`X←` assign that to `X`; \$X:=…\$  
`⊢>` is the right argument greater (0/1)?; \$[y>…]\$  
`1+` add one; \$1+…\$  
`(`…`)×` multiply the following by that; \$(…)×…\$  
 `2+X` two plus `X`; \$2+X\$  
 `⊢<` is the right argument less than that (0/1); \$[y<…]\$  
`(`…`)×` multiply the following by that; \$(…)×…\$  
 `,` concatenate the arguments; \$(x,y)\$  
 `30 31|` remainders when divided by these numbers; \$…\mod(30,31)\$  
 `,≡` are the concatenated arguments identical to that (0/1)?; \$[(x,y)=…]\$
[Answer]
# x86 Assembly, 42 Bytes
Takes input in `ECX` and `EDX` registers. Note that `ECX` must be greater than `EDX`.  
Outputs into `EAX`, where `0` means the game's still on, `1` representing the game being over and `-1` (aka `FFFFFFFF`) representing an invalid score.
```
31 C0 83 F9 1E 77 1F 83 FA 1D 77 1A 83 F9 15 7C 
18 83 F9 1E 74 12 89 CB 29 D3 83 FB 02 74 09 7C 
08 83 F9 15 74 02 48 C3 40 C3
```
Or, more readable in Intel Syntax:
```
check:
    XOR EAX, EAX
    CMP ECX, 30     ; check i_1 against 30
    JA .invalid     ; if >, invalid.
    CMP EDX, 29     ; check i_2 against 29
    JA .invalid     ; if >, invalid.
    CMP ECX, 21     ; check i_1 against 21
    JL .runi        ; if <, running.
    CMP ECX, 30     ; check i_1 against 30
    JE .over        ; if ==, over.
    MOV EBX, ECX
    SUB EBX, EDX    ; EBX = i_1 - i_2
    CMP EBX, 2      ; check EBX against 2
    JE .over        ; if ==, over.
    JL .runi        ; if <, running.
                    ; if >, keep executing!
    CMP ECX, 21     ; check i_1 against 21
    JE .over        ; if ==, over.
                    ; otherwise, it's invalid.
    ; fallthrough!
    .invalid:
        DEC EAX     ; EAX = -1
        RETN
    .over:
        INC EAX     ; EAX = 1
    ; fallthrough!
    .runi:
        RETN        ; EAX = 0 or 1
```
Fun fact: this function *almost* follows the C Calling Convention's rules on which registers to preserve, except I had to clobber `EBX` to save some bytes on stack usage.
---
### Optional (not included in byte-count)
By adding the following 6 bytes directly before start of the code above, you can pass `ECX` and `EDX` unordered:
```
39 D1 7D 02 87 CA
```
Which is the following in readable Intel Syntax:
```
CMP ECX, EDX
JGE check
XCHG ECX, EDX
```
[Answer]
# [Retina 0.8.2](https://github.com/m-ender/retina/wiki/The-Language/a950ad7d925ec9316e3e2fb2cf5d49fd15d23e3d), 92 bytes
```
\d+
$*
^(1{0,19},1{21}|(1{20,28}),11\2|1{29},1{30})$|^(1*,1{0,20}|(1{0,28}),1?\4)$|.+
$#1$#3
```
[Try it online!](https://tio.run/##NYyxDsJADEP3/EaL1EKKYl@H3sTIT1QIJBhYGBBb228/kkMMceT4Oe/H5/m6lV13vpb5fpB2L5cOiynypliIbXVLU05br8DM1W3Nkm19uzq91yjQKvonT/Po6dE/NmibVIoHEDCUUCbhpMmEOdQ0hhYAWIEfRhl8IcuAHLfBTf3C2oQki/5IHZPAgnX5Ag "Retina 0.8.2 – Try It Online") Link includes test cases. Takes input in ascending order. Explanation: The first stage simply converts from decimal to unary so that the scores can be properly compared. The second stage contains six alternate patterns, grouped into three groups so that three distinct values can be output, which are `10` for win, `01` for ongoing and `00` for illegal. The patterns are:
* Against 0-19, a score of 21 is a win
* Against 20-28, a score of +2 is a win
* Against 29, a score of 30 is a win
* Against any (lower) score, a score of 0-20 is ongoing
* Against a score of up to 28, a score of +1 is ongoing
* Anything else (including negative scores) is illegal
[Answer]
# [Stax](https://github.com/tomtheisen/stax), 20 [bytes](https://github.com/tomtheisen/stax/blob/master/docs/packed.md#packed-stax)
```
ÇåπßéD╩¬7▼ß▌ΣU¬í╡S┤╘
```
[Run and debug it](https://staxlang.xyz/#p=8086e3e18244caaa371fe1dde455aaa1b553b4d4&i=0+21%0A12+21%0A21+23%0A28+30%0A29+30%0A0+0%0A0+20%0A12+12%0A21+21%0A21+22%0A-21+19%0A-19+21%0A-1+1%0A12+22%0A29+31%0A30+30%0A42+43%0A1021+1021%0A&a=1&m=2)
It takes input in the same format as the examples. `0` means there's a valid winner. `1` means the game is in progress. `-1` means invalid score.
In pseudo-code, with ordered inputs `x` and `y`, the algorithm is 
```
sign(clamp(x + 2, 21, 30) - y) | (x < 0 || x >= 30 ? 0 : -1)
```
* `sign` means numeric sign (`-1`, `0`, or `1`)
* `clamp` forces its first argument into the specified half-open interval
[Answer]
# [APL (Dyalog Unicode)](https://www.dyalog.com/), ~~33~~ 32 [bytes](https://codegolf.meta.stackexchange.com/a/9429/43319 "When can APL characters be counted as 1 byte each?")[SBCS](https://github.com/abrudz/SBCS ".dyalog files using a single byte character set")
```
{h⍵+1 0}+h←(⊢≡31 30|⊢)×21 2∨.≥-\
```
[Try it online!](https://tio.run/##fY@/SkMxGMX3PsXZqtRK/nTwurrYSbAdXaI3NYFwW0wUijoJxRZadBBnBcGtky9Q3yQvcv1yU5zE5ZyTL8n58amJ65ZT5caXdVy92HGcPbH61sTlV4eD3XcMDXbi4j3O3ySHZHeUd79fBYeIj5/7cf7RPatH9CguV1TQP4mLh81axtkznQanR6TD4/6gbg@N9hpB@4AL5bWHutK4Uc6We1BViWA0vA4wykNXpS4P260RiMOycZFcUiSXDOJg60Xr3@7z6/DbbT18sM7BVpg4NW0QDBnBGuNiS@KZJETyvwm2ahhNCy8AbNb5D28yL9IcOecq5HKZpqLIC4CEUk9S6qVbztK6JD8 "APL (Dyalog Unicode) – Try It Online")
in: a pair in descending order
out: 2=ongoing, 1=ended, 0=invalid
tests stolen from [Adám's answer](https://codegolf.stackexchange.com/questions/182245/valid-badminton-score/182370#182370)
[Answer]
# [Bash](https://www.gnu.org/software/bash/) 4+, ~~97~~ ~~89~~ ~~91~~ 88 bytes
Assume that inputs are ascending. Used concepts from [VDM-SL](https://codegolf.stackexchange.com/a/182261/15940) answer. [Try it Online](https://tio.run/##VY4/b4NADMX3fAoLnRAMSLbJ0IsCHTJEmbtmoeSigAJEDcpAyGenvj@p1OWefX7@@X1X98tyTtInmPoyQNYbiBQdR8XHMVqaQhG0hWKYClwlSZs1JcdtyTQ3W5yl0XNb5vg5FbzBNBXLtKU4EQfG1kwyZp3KnNzcHVHT8vJVtBu629WM5gT7qjP3TbQ6AwKTCHFQ7ZUJOLf6ATla1VY95tDXAfSPg56GnkYcKG@a6zmoXNF/tEd1bU7wVQ8/gZWJn7Qt3nEy@Qgpw35O7pwLt2ZY27CEdlGe5Rc)
  
`z==0` - game in progress   
`z==1` - game completed   
`z==2` - invalid
***-8** by bracket cleanup from `(( & | ))` conditions*  
***+2** fixing a bug, thanks to Kevin Cruijssen*  
***-3** logic improvements by Kevin Cruijssen*  
```
i=$1 j=$2 z=0
((j-i>2&j>21|i<0|i>29|j>30?z=2:0))
((z<1&(j>20&j-i>1|j>29)?z=1:0))
echo $z
```
]