diff --git a/2020/ferguson1/bugs.markdown b/2020/ferguson1/bugs.markdown deleted file mode 100644 index 5ea3bf825..000000000 --- a/2020/ferguson1/bugs.markdown +++ /dev/null @@ -1,295 +0,0 @@ -Current bugs and things that look like bugs but are not, as of 12 September 2020. - - -- [Built-in test unit for some features!](#builtin-test-unit) -- [The snake doesn't seem to be moving](#stillsnake) -- [getch() also accepts input from some input hardware other than keyboards](#getch) -- [SIZE=0 is the same as SIZE=1 and other oddities](#size) -- ['I was a few places behind my tail and I ran into it!'](#bitetail) -- [With negative growth the snake drops from the middle of the body](#middleofsnake) -- [It looks like the bug is in the snake](#buginsnake) -- [On the safety of the bug placement loop](#bugplacement) -- [Vertical snake has more space between snake char than horizontal](#verticalsnake) - -# Built-in test unit for some features! - -Fun fact: There's something of a built-in test unit for some features; much -like some of the other features this was not intentionally designed but is -simply a fun consequence of the features I did implement. - -Let's say you want to verify that the evade mode is at the right count. How -can you verify it without modifying the code? Try: - - SIZE=0 SHED=1 SHEDS=1 EVADE=10 ./prog - -Since you start out at size 0 and you shed every movement then when you're -size 10 (as long as you don't eat a bug of course!) the bug should evade. - -If you set it to `SIZE=10` there would be a difference of 10. Now every -move you will see the size the snake is. But then...Why is it that it -doesn't seem to move until the snake is of size 20? Because you start out at -size 10 of course (but in fact if you observe the snake won't appear to be -of size 20 yet). - -There probably are combinations that can test these types of things I just -don't at this time remember any I might have done. - - -# The snake doesn't seem to be moving - -One way to show this is in full is to set the initial size and growth size to a -high value; then try each one individually. For example: - - SIZE=100 ./prog - -In this case after you make the first move the snake will keep growing and -so the snake tail stays in the same spot until after it's the 'full size'. -When it has reached the full size then the entire body of the snake will -start to move too (i.e. if the tail isn't moving it is growing or else paused). - -In several tests I set the initial size to a high value, the max to a higher -value still and enabled cannibalism and much of the screen was full of the snake -before it started to move. The [cannibalism.log][] has some examples of both -cannibalism and a long snake that didn't move for most of the game. - -If however you let the snake get to the full size then eat a bug there can -be a moment of lingering where it appears the snake moves for a few places -before it then stops moving and just grows until it's the full size. - -It can also appear to be not moving when the snake is extraordinarily long -and more so when the snake has just eaten: which of course is when it would -grow (just like when shedding). Even so sometimes it takes a moment for the -snake tail to catch up with the body (so to speak) so that it could for a -few moments be difficult to know if where you're going is going to be -occupied or not. - -Basically if the snake head is moving you can consider the snake to be -moving or growing and that's the important point (if you have the default -status line you can also see the coordinates being updated to show this). - - -# getch() also accepts input from some input hardware other than keyboards - -On my MacBook Pro the trackpad has actually triggered directions; if I were to -swipe up and then down the snake would run into itself. I believe this has to do -with the function call `keypad(v, 1);` which enables arrow keys. - -# SIZE=0 is the same as SIZE=1 and other oddities - -What this means is that for both you will only have the snake head. At this point -you can safely go back on yourself even without cannibalism enabled. This is -intentional; a snake size of 0 is pointless but why should it not be playable -anyway? - -If SIZE < 0 you'll likely win straight away since it's unsigned and it'll wrap -back to the maximum so you will win automatically (this is the same as the -second mode the judges suggested with `SIZE=-1 ./prog`). - -As for the growth sizes (including shedding) if it's < 0: the snake will still -lose its length and display will still be odd: because the snake size is -decreased any element beyond the reduced size isn't touched so the snake -characters will remain on the screen; only the moving snake is what matters. -But it's likely the game will be won fairly quickly because once it's -1 in size -it'll be at the maximum size. - -For example if I say `SIZE=3 GROW=-2` at two bugs the game is won. - -As for growing: anything > 0 it will grow that many; 0 it will not grow (which I -show as a gameplay mode); and < 0 it will 'shrink' as I explained above. You could -say that it's defying physics by growing and shrinking simultaneously but either -way negative size will force a win. - -Here's a fun output: - - < <<<+ - -That's from: - - SHED=1 SHEDS=-1 ./prog - -And right after that you would see: - - YOU WIN! - X:72/156 Y:20/41 S:997/997 B:0 - -Try this though: - - SHEDS=-1 SHED=25 ./prog - -And you'll see that you start to be the normal size but then you leave your -head several places until you end up getting to the max size (by way of it -being unsigned). This isn't a bug even if it might seem like one. In fact -this is the basis of the grow/shrink mode of play that I talk about in the -[gameplay.md][] ([gameplay.html][]) file. - -Here's another thing to be aware of. Let's say you do: - - SIZE=1 GROW=-1 ./prog - -How many bugs will it take before you win? In fact it's two: you would see the -head twice: the second time once you 'shrink' down to size 0; it would then be -that if you get another bug you will be at the 'max size': - - YOU WIN! - X:81/156 Y:35/41 S:997/997 B:2 - -# 'I was a few places behind my tail and I ran into it!' - -I.e. what happens if you're growing - for example after just eating a bug - and -you're racing towards your tail? If you're behind it should be fine since you're -going the same speed as the snake is, right? Not quite. Because you're growing -you might end up running into your tail (depending on how much you've left to -grow and how far away from your tail)! This happened to me once and it surprised -me and I figured I better note it. - -**Remember: the tail at that point isn't moving yet; the snake is growing!** - - -# With negative growth the snake drops from the middle of the body - -For instance it might look like this (the first line is as it happens, the -second pointing to where it happens and the third being what's left after the -rest of the snake has gone through). Doing `SHED=100 SHEDS=-1`: - - ooooooooo>ooo> - ^ - > - -The ^ points to the part that's split from the snake and which will be -there on its own after the snake passes through it. Here's an example with -it being `SHEDS=-3`: - - It looks like the bug is in the snake - -I show a different example in the [crazy.log][] file but what defines a coordinate -which the snake occupies? If the snake breaks apart then the only part -of the screen the snake is occupying is the place which is actually moving: if -the snake decreases in size by 3 then the three respective characters you can go -through safely. But this also means that a bug can go there! So you might see -something like: - - <*oooooooo - -This is because the snake is no longer there: in fact in this run the snake was -quite a bit lower on the screen and 25 in length. - -# On the safety of the bug placement loop - -Ilya Kurdyukov shared concern for the loop in the bug placement routine: is it -safe? What he refers to is that it's a potential infinite loop. But is it? I -understand the concern and it's something I played with a lot to be sure that it -doesn't happen. This is how it works. - -To explain it I will go back to the make test program `termcaps` (which is -imperfect for the recommended terminal size but the max snake size is exactly -how it's done in Snake and works well). The minimum terminal size I require in -the game is 10 x 10. This includes the walls and score line. - -Now the default max snake size is 997 but when the terminal is too small it -adjusts the max snake size. For 10 lines and 10 columns you would see: - - $ make test - terminal supports cursor movement - terminal supports making cursor invisible - terminal supports bold - terminal supports colours - - terminal rows 10 (7 playable) - terminal cols 10 (8 playable) - - snake size: 49 (max size: 49) - bugs: 9 (max size: 9) - - at least 28 columns recommended for snake size 49 (is 10) - at least 28 columns recommended for snake size 49 (is 10) - - 1 problem detected (0 fatal). - make: *** [test] Error 1 - -First: what's the problem? It's just a warning that for the snake size of 49 the -recommended size of columns is 28. However this is just for the score text; with -only 10 columns it will overflow and wrap to the next line(s). But if you observe -that 49 is the max snake size. How is this derived and why? - -With 10 rows and columns it's calculated as 7 * 7 (i.e. it's -`(cols-3)*(rows-3)`. However the number of playable locations is in this case 56 -(`playable_rows * playable_columns`). I have played with this many -times and I do not believe there should be a problem. If there is it's from a -very poor implementation of rand() and I discuss this in -[troubleshooting.md][] ([troubleshooting.html][]). - -There I also give some test outputs without the cap in place to give an idea of -when it became a problem (or would have). In short this is known but I do not -think it will be a problem and I have given resolutions. In any event without -cannibal mode it's even less likely that you'll ever run into this problem. - -The obvious question is why didn't I put in a break? Because of the IOCCC size -rule 2 and what would it do anyway? I guess it would win but to my mind that is -a bug and I prefer putting in restrictions to try and make it very unlikely. - -# Vertical snake has more space between snake char than horizontal snake - -Ilya Kurdyukov also pointed out that depending on orientation of the snake there -are different amount of spaces between the snake char 'o'. This is what he -had to say on it: - -> Yes, it is the problem, space in character terminal is not isotropic -> from a viewer (gamer) perspective so one step up look like two steps -> right. -> -> MATE Terminal 1.20.0, and font "Ubuntu Mono Regular 13" -> -> But, as I said, there is no terminal problem itself, it's because -> character tiles are not square (except for Japanese/Chinese, Japanese -> even have so called full-width English characters in their encoding, -> presented in UTF too). -> -> [https://en.wikipedia.org/wiki/Halfwidth_and_fullwidth_forms][] -> -> So the typical fonts are Half-width or close to it. -> - - -However the function `mvaddch()` takes a `chtype` which amounts to a C char -with additional information and so the full width Ois too big for the -function call. He reminded me of the old DOS box chars but even if this was -easily duplicable (and it might be and I don't remember) the logic would -have to be changed for drawing the snake and in such a way that would change -the source code layout too much - to say nothing else of the fact I have -also forgotten some of what I did obfuscation wise (and the -[spoilers][] aren't enough here!). - -That being said the font and font size does seem to have somewhat of an -effect on it since his screenshots have wider gaps than mine. - -Once again thank you mate for bringing this to my attention and also playing -with it and providing the resources! Much appreciated! (And although you -suggested it is bugging me I once again here say it's not at all bugging me -but it is very much appreciated and it means a great deal to me. Cheers.) - - -[crazy.log]: crazy.log -[gameplay.md]: gameplay.md -[troubleshooting.md]: troubleshooting.md -[troubleshooting.html]: troubleshooting.html -[gameplay.html]: gameplay.html -[cannibalism.log]: cannibalism.log -[https://en.wikipedia.org/wiki/Halfwidth_and_fullwidth_forms]: https://en.wikipedia.org/wiki/Halfwidth_and_fullwidth_forms -[spoilers]: spoilers.markdown -[screenshot of halfwidth vs fullwidth]: halfwidth_vs_fullwidth.png diff --git a/2020/ferguson1/gameplay.markdown b/2020/ferguson1/gameplay.markdown deleted file mode 100644 index 45a68c22b..000000000 --- a/2020/ferguson1/gameplay.markdown +++ /dev/null @@ -1,786 +0,0 @@ -Playing the game is very simple but because there are many different modes I -have included much more than just the input keys and a few printouts of the -game. For normal play you probably don't even need this file at all unless it be -for the input keys. - - -1. [Testing terminal environment/capabilities](#support) -2. [Input keys](#input) -3. [Gameplay](#gameplay) - * [The initial screen](#initial) - * [Playing the game](#playing) - * [Walls](#walls) - * [Cannibalism](#cannibalism) - * [Stretching (shedding)](#shedding) - * [Winning](#winning) - * [Quitting](#quitting) -4. [Gameplay modes](#modes) - * [**Photosensitive epilepsy/Stimulation overload trigger warnings**](#warning) - * [Drawing / Practice mode](#drawing) - * [Computer playing / Automatic mode (**Photosensitive epilepsy/Stimulation overload trigger**)](#auto) - * [Variations](#auto-variations) - * [Grow-Shrink (Positive/Negative Winning) mode](#grow-shrink) - * [Hypnotic / Sleepy mode (**Photosensitive epilepsy/Stimulation overload trigger**)](#hypnotic) - * [Stimulation / Calming mode](#stim) - * [Other ideas](#other-ideas) -5. [Scripts](#scripts) - * [Colours selection](#colours) - * [Automate the above gameplay modes](#preset) -6. [Game variables](#variables) - * [WAIT - Snake speed](#wait) - * [EVADE - Bug evasion frequency](#evade) - * [SIZE / MAXSIZE - Initial snake / max sizes](#sizes) - * [GROW / SHEDS - Growth / Shedding sizes](#growth) -7. [Conclusion (and inspirational words for everyone)](#conclusions) - -# 1. Testing terminal environment/capabilities - -Most people probably won't have any issue at all but for those who have any -problems with the terminal see the [terminals.md][] file for more -information on the terminal capabilities testing utility I include. - -For troubleshooting other problems see [troubleshooting.md][] -([troubleshooting.html][]) and [bugs.md][] ([bugs.html][]). - - -# 2. Input keys - -- Left, right, up and down **arrow keys**: Move that direction. - -- 'q' (and 'Q'): Quit the game. - -- Space (`' '`): Pause/resume the game (direction keys will also resume). - Note that no other keys will resume the game; however if you press a key - that `getch()` picks up on and the game is not paused then it will be as if - you pressed the current direction key (assuming you already moved the snake). - -# 3. Gameplay - -Here I will show the initial screen as well as what a few things look like. If -you don't feel you need this but you want to configure colours jump to [colour -selection](#colours). You can also jump to [gameplay modes](#modes) or [game -variables](#variables). - -The bug colour by default is white; the snake head colour is red and the snake -body colour is green. The walls are cyan. - - -## Gameplay: The initial screen - -When you load the game you will see a screen that looks something like the -below. Note that the top line is the score/status line: the top wall is at line -1 and the left wall is column 0. Below I use the coordinates in the X/Y order -(ncurses is the opposite: Y/X). I will parenthesise the numbers below to refer -to the number in the diagram: - - X:24/49 Y:8/16 S:5/658 B:0 - ################################################## - # # - # # - # # - # # - # # - # # - # + # - # # - # * # - # # - # # - # # - # # - # # - ################################################## - - -- The snake (`+`) is at (24),(8). The bug char is `*` and the wall char is - `#`. When the snake moves the head will be an arrow facing the direction it - is moving; the body is 'o'. - -- The top wall is always line (1), the left wall is always column (0), the - bottom wall is line (16) and the right wall is column (49). If walls are - impassable and you run into any of these the game is over (you can slither - at the edge of the wall); else you'll come out the opposite side. - -- The snake size is (5): but until you start to move it's technically 1 (even - if you set **SIZE=0** this applies). This is so that you can decide which - direction from the centre of the screen. By default the head colour is red; - the body is green. The snake will grow by one until it reaches the right - size. - -- The maximum snake size is (997): this is the default; it can be changed but - it will always have an upper limit depending on the terminal size. - -- The number of bugs eaten so far is 0. - - -## Gameplay: Playing the game - - - X:39/49 Y:8/16 S:5/658 B:0 - ################################################## - # # - # # - # # - # # - # # - # # - # oooo> # - # # - # * # - # # - # # - # # - # # - # # - ################################################## - -I let the snake grow to its full size (with the head at (39),(8)) and I'm two -places above the bug so let's go down: - - X:39/49 Y:9/16 S:5/658 B:0 - ################################################## - # # - # # - # # - # # - # # - # # - # oooo # - # v # - # * # - # # - # # - # # - # # - # # - ################################################## - - -Now you see that the snake is going down and in one move the bug will be eaten. -Let's do that: - - X:36/49 Y:10/16 S:10/658 B:1 - ################################################## - # # - # # - # # - # * # - # # - # # - # # - # # - # ^ o # - # oooo # - # # - # # - # # - # # - ################################################## - -Observe that the bug was eaten and a new bug spawned elsewhere. I went left -three and then up one and could slither on to the bug. - - -# Gameplay: Walls - -By default walls are passable (**WALLS=1**). Let's get to full size and go -through the top wall: - - - X:36/49 Y:15/16 S:10/658 B:1 - ################################################## - # o # - # o # - # o # - # * o # - # o # - # o # - # o # - # o # - # o # - # # - # # - # # - # # - # ^ # - ################################################## - -There you can see I have grown to the full size and went through the top wall; -the head is now at the opposite wall. Continuing: - - X:36/49 Y:11/16 S:10/658 B:1 - ################################################## - # o # - # o # - # o # - # * o # - # o # - # # - # # - # # - # # - # ^ # - # o # - # o # - # o # - # o # - ################################################## - -I might then have turned to the left and then shortly go up to get the bug. This -goes on until you lose, quit or win the game. - - -When you specify **WALLS=0** the walls become an obstacle that end the game. It -might look like (I set the starting size to 150 and had only just started): - - - X:20/49 Y:2/16 S:150/658 B:0 - ################################################## - # ^ # - # o # - # o * # - # o # - # o # - # o # - # ooooo # - # # - # # - # # - # # - # # - # # - # # - ################################################## - -If I continue going up I will see: - - OUCH! - X:20/49 Y:1/16 S:150/658 B:0 - - - - -# Gameplay: Cannibalism - -By default cannibalism is disabled (**CANNIBAL=0**) so if you run into yourself it might look -something like: - - X:45/49 Y:10/16 S:10/658 B:1 - ################################################## - # # - # # - # # - # * # - # # - # # - # # - # ooooo # - # ^ o # - # ooo # - # # - # # - # # - # # - ################################################## - -If I were to do any movement but turn to the left I would run into myself. I'll -go to the right two spaces: - - OW! - X:47/49 Y:10/16 S:10/658 B:1 - - -But **CANNIBAL=1** can look very funny both in movement and still especially -when you go through yourself several times when the snake is rather long. For -example going through walls and the snake a few times: - - X:4/49 Y:12/16 S:165/658 B:3 - ################################################## - #oo o ooooo o o o# - # oooo o ooo o# - # ooooooooooooooooooo# - # o # - # oooooooooo o # - # o o o # - # o oooo o o # - # o o o o # - # ooooooooooooooo # - # o o* # - # oo> o o # - # o oooooooo o # - #oooooooooooooo o oooo oooooooo# - # o o o o o # - ################################################## - -I'll go back through myself now: - - X:6/49 Y:14/16 S:165/658 B:3 - ################################################## - #oo o ooooo o o o# - # oooo o ooo o# - # ooooooooooooooooooo# - # o # - # oooooooooo o # - # o o o # - # o oooo o o # - # o o o o # - # ooooooooooooooo # - # o o* # - # ooo o o # - # oo oooooooo o # - #ooooo>oooooooo o oooo oooooooo# - # o o o o o # - ################################################## - -If you note that the head is at (6),(14) I will go one more to the right and you -will see that the arrow has moved over: - - X:7/49 Y:14/16 S:165/658 B:3 - ################################################## - #oo o ooooo o o o# - # oooo o ooo o# - # ooooooooooooooooooo# - # o # - # oooooooooo o # - # o o o # - # o oooo o o # - # o o o o # - # ooooooooooooooo # - # o o* # - # ooo o o # - # oo oooooooo o # - #oooooo>ooooooo o oooo oooooooo# - # o o o o o # - ################################################## - -Try this: Get a reasonable length and then go back on yourself (if you're going -left go right etc.) and then after some length (but before you get beyond the -snake) turn (so that you go perpendicular) and then watch as the snake starts -unwinding at one place and then suddenly unwinds another direction. - -An example of extreme cannibalism is in the [cannibalism.log][] file. It has -more than one run the first of one has several dumps. I didn't move for a long -time: the initial size was 1500 and I would have won before moving if I went -after bugs right away. In the last two captures of the first invocation (the -third and second to last dump) the bug, head and tail of the snake are at: - - Bug: line 87, col 47 - Head: line 88, col 62; and line 115, col 48 - Tail: line 84, col 46; and line 112, col 29 - -The final example has the snake inside itself. - -# Gameplay: Shedding (stretching) - -This feature will make you grow by the value of **SHEDS** for every **SHED** -movements (the counter isn't incremented when you eat a bug). Basically growing -without eating a bug (stretching would be better name here). - - -# Gameplay: Winning - -If you ever get to the **MAXSIZE** the game is won. You can force a win by -setting the max size to a smaller value than the size you specify (or the -default) or setting the size to higher than the default (or < 0 which will end -up being > the max size though I suppose there might be some value that makes it -less than again - I'm not going to try and figure that out however). Anyway: - - SIZE=-1 ./prog - YOU WIN! - X:24/49 Y:8/16 S:658/658 B:0 - -Observe that the size isn't the default 997: because the screen size is too -small for that value (see [terminals.md][]). - -The reason I chose 997 is that it leaves a bit of a challenge for some (maybe -most) people but it's still winnable. Previously even I couldn't have won unless -maybe a much bigger screen, if both cheat modes were disabled. I probably could -win without any cheat mode enabled on my 24" monitor but I probably would need -at least one cheat mode on my 13" laptop (I don't need both enabled). - -The beauty though is the game can be winnable by anyone since you can define the -max size to win. Games are meant to be fun: who enjoys games that can never be -won? Very few probably do and those who do enjoy them it probably are games that -are relaxing and stimulating (which admittedly this game can be - but still I -want it to be winnable too). - -Another reason I chose 997: it's prime and the judges love prime numbers. -Actually, this is the main reason for the number: I had changed it down to 1000 -but then it occurred to me that there are prime numbers close to 1000 so why not -choose one instead (that was less than 1000)? And so I did. Another fun fact: at -the default starting size and growth size of 5 the number of bugs needed before -the game is won is 199 - also a prime! - - -# Gameplay: Quitting - -If you wish to quit you can hit 'q' (case-insensitive). For example: - - X:24/49 Y:8/16 S:5/658 B:0 - - - -# 4. Gameplay modes - -Here I list a number of different modes of play. In the [bugs.md][] -([bugs.html][]) file I even show how there's a built-in test unit for some -modes! There are certainly other modes that could be imagined: the modes below -happened over time and some were simply an accidental discovery or something I -thought of by a chance; others were thought out from the beginning but were -improved over time. - -The [play.sh][] script sets up some of these automatically; I describe it after -the modes below. - - -## Photosensitive epilepsy/Stimulation overload trigger warnings - -If you have **photosensitive epilepsy please be careful** especially with low values -for both **WAIT** and **EVADE**. It appears that the [number of hertz (flashes) -per second triggering seizures][] is wide but depending on the variables it -easily can be in the range. - -There are different patterns and lights flashing at different speeds so please -exercise caution. This could trigger seizures and **it might also be too much -stimulation for some people.** - -**Please be careful of these if these types of things have ever affected you or if -you have any form of epilepsy. Thank you.** - -## Gameplay mode: Drawing / Practice mode - -If you want to draw the snake wherever you like on the screen you can do this by -modifying the wait time (for input) to -1 (or any value < 0): then the snake -does not move until you press a movement key. For example: - - WAIT=-1 ./prog - - -When you eat a bug the snake will grow and you can draw more. If you want to -make it so you can go through yourself: - - WAIT=-1 CANNIBAL=1 ./prog - -If you don't want to start out so small (i.e. you want to be able to draw more -quickly) you can just set the size to a bigger number (and you could do the same -with growth and moult/shed): - - SIZE=50 CANNIBAL=1 WAIT=-1 ./prog - -Do note that although you're a bigger size you still grow one movement at a -time. - -Okay but then if you get too many bugs the drawing will be over. If this proves -to be a problem simply do: - - SIZE=100 CANNIBAL=1 WAIT=-1 GROW=0 ./prog - -Or something like that (maybe make the size bigger but that's up to you). You -could also set the max size to be the highest possible by saying - - MAXSIZE=-1 - -Which I've done in the [play.sh][] script. - -I discuss how this mode works in [spoilers.markdown][]. - -Here's an example run where I spelt 'SNAKE'. As you can see it's far from -perfect: the snake after all does not leave gaps and the game does not support -diagonal movement and I also made a mistake and had to modify what I was aiming -for. But you can consider this mode drawing or practice mode. - - oooooooooo ooooo o oo ooooo - o o oo o oo o ooo o - oooo o oo ooooooo oooo ooooo - o o ooo oo o ooo o - oooo o oo ooo oooooooo> - - -## Gameplay mode: Computer playing / Automatic mode (Photosensitive epilepsy/Stimulation overload trigger) - -**Photosensitive epilepsy/stimulation overload trigger warning:** -For those who have epilepsy or are susceptible to stimulation overload from -flashing lights please do be careful with this mode. Depending on your -susceptibility this could very well trigger seizures or other discomforts and I -wouldn't want that to happen to anyone. This mode has a lot of flashing of -different colours and depending on the exact parameters you specify it could -easily be in the trigger range for epilepsy but potentially stimulation overload -too. - -If you're still here you can create this effect like: - - WAIT=0 WALLS=1 CANNIBAL=1 ./prog - -As long as the bug evades you'll find that the snake is growing because it's -actually eating bugs; the computer is actually playing the game by itself! You -can still move the snake though it won't seem like you have much control over -it. If you pause the game you can see that it's bigger (you probably will be -able to even without pausing it). - -Basically the snake is moving so fast that because the bug evades the bug will -inevitably move in the snake path's and therefore be eaten. - -You might find it goes quicker if you set the evade count to a lower value for -example 1. Or you might not. But as long as the bug moves the computer can play -by itself. - -Quick tests suggest that bigger dimensions it gets bigger quicker. But it also -seems to take longer for it to get a bug as it gets longer. I believe these -happen because the bug has to find a X and Y coordinate that's not occupied and -so with fewer available it has a harder time finding a place in that one line. - -### Computer player variations - -Here's how to make the bug move every time the snake moves: - - CANNIBAL=1 WALLS=1 WAIT=0 EVADE=1 ./prog - - -#### Help the computer win fairly quickly - -If you want to see the game 'win' try something like: - - CANNIBAL=1 WALLS=1 WAIT=0 EVADE=1 MAXSIZE=10 ./prog - - -#### Negative growth (Shrink) Automatic mode - -Here's a fun one I just thought of (a long time after these other modes were -thought of): - - WAIT=0 EVADE=1 GROW=-1 SIZE=9 MAXSIZE=10 CANNIBAL=1 ./prog - -What you'll see is that first the snake will get to the size of 9 - whether it -shows to be 10 characters depends on whether or not it gets a bug before it can -get to that length - and then it'll start shrinking until it's just the head -then the head again (i.e. 1 then 0 in size) and then win! This is also in the -[play.sh][] script. - - -#### Adjusting the time/speed - -Of course you can also do something like: - - WAIT=5 WALLS=1 CANNIBAL=1 ./prog - -Which although might not seem like it is significantly slower. - -#### Moving the snake about to create patterns - -If you press different direction keys in some pattern you will see a pattern -form too (entirely dependent on how fast you react, what keys you press and I -suppose also how much the bug moves and the chance of being eaten also). - -#### Other ideas - -Here's an idea: how many moves is needed to make it possible to have some sort -of success in catching bugs? Obviously the dimensions will affect this value. - -How long will it take for the snake to run into itself? For instance by doing: - - WAIT=0 ./prog - -On my 13" MacBook Pro at the default settings going horizontally the snake ran -into itself at: - - OW! - X:57/156 Y:20/41 S:155/997 B:30 - -What if you set the size to a high value? The lowest? There are bound to be -other games to play with this feature! - -## Gameplay mode: Grow-Shrink mode (Positive and Negative Winning) - -In this mode you're simultaneously growing and shrinking but because the max -size is unsigned you can win both by shrinking to the point of losing your body -or growing to the full size directly. This is how it works: - -You have a negative shedding growth and a positive growth size: negative and -positive winning. Depending on the parameters specified it might go one way over -the other. Try: - - SIZE=150 SHEDS=-5 GROW=10 SHED=50 MAXSIZE=250 ./prog - -What this does is set the size to 150 (to make it not go down so quickly) and -then for each bug you eat it adds 5 to the size; for every 50 moves (that you -don't get the bug) you shrink by 5. You will find that at times you might only -decrease in size but then at other times - depending on size of screen and how -lucky you are with where the bug is relative to where it last was - you might be -growing. Combining this with the drawing mode could be interesting but I never -actually tried it. - -The parameters above were chosen after playing with the mode a bit to find a -value that wouldn't take that much time but still would pose a challenge. One -could reverse it so that one shrinks more than growing. - -The [crazy.log][] file has several screen dumps with this idea. - -You could also have it so that eating a bug makes you shrink and moving so many -moves you grow. I tried this and for me it was far more fun. The [play.sh][] -also has this mode: - - SIZE=100 SHEDS=5 GROW=-10 SHED=50 MAXSIZE=250 ./prog - -Obviously there are many other variations. The [play.sh][] script has some -of them (the above is option 5; others are 4, 15 and 16 but only options 4 and 5 -are called Grow / Shrink). - - -## Hypnotic / Sleepy mode (**Photosensitive epilepsy/Stimulation overload trigger**) - -**Photosensitive epilepsy/stimulation overload trigger warning:** -For those who have epilepsy or are susceptible to stimulation overload from -flashing lights and patterns please do be careful with this mode. Depending on your -susceptibility this could very well trigger seizures or other discomforts and I -wouldn't want that to happen to anyone. The default wait time is 231ms, the -typical range of seizures is 3 to 30 flashes per seconds (but can be higher) and -that means that at **EVADE=1** every 231ms there will be a flash in addition to the -snake moving too. - -Anyway this might sound absurd but it can be somewhat hypnotic by doing: - - EVADE=1 CANNIBAL=1 ./prog - -And just moving the snake about not trying to catch any bugs - just let whatever -happens happen. - - -## Gameplay mode: Stimulation / Calming mode - -If you just need something to stimulate your mind to keep you calm or simply you -want to play a game without having to worry about winning or losing you can set -the growth size to 0 and then just move the snake round and round eating bugs -until you want to quit. You can enable cannibalism so that you don't have to -worry about even running into yourself and losing: - - GROW=0 CANNIBAL=1 ./prog - - -## Gameplay mode: Expansibility - -As I was writing this document and the game modes I have thought of other game -modes (they were added) and I have come back to this document to add more modes. -That's the beauty of the variables I have implemented: together with the -imagination and even just chance different modes of play can be devised. The -drawing mode was a happy chance of how the `timeout()` function works for -example. - -As a person who was heavily involved in MUD development and design I know very -well how games need to be fun and have different options, not be too easy but -not be too hard either. And a game developer who does not play their games is a -game developer who is detached from their player base and the wants and needs -are therefore disregarded if even heard. I have played this game a lot for this -reason as well as I simply enjoy it and thinking up new modes. - - -# 5. Scripts - -The following scripts are provided to simplify and automate some of the things -this file discusses as well as allowing to easily change the colours. - -## Colours selection: snake-colours - -To do this use the [snake-colours][] script like so: - - ./snake-colours - -If you want to pass in specific variables you can do that too e.g.: - - CANNIBAL=1 ./snake-colours - -The script will prompt you for the colour of the walls, snake head, body and the -bug. - -## Automate the above gameplay modes - -The [play.sh][] script has the gameplay modes described in this file and is menu -based (there might be differences and some of the modes I include a variation). -The script tests the terminal (allowing you to continue even if problems are -detected), prompts you if you want to select colours (like those in -snake-colours above) and then gives you a list of game modes to play. You can -pass in the game variables but note that if you pass in a variable that the mode -you choose uses then the script will override what you pass in. - -To use you can do something like: - - ./play.sh - -Or to make it so walls are an obstacle: - - WALLS=0 ./play.sh - - -# 6. Game variables - -The game variables can be used for a variety of things including different -gameplay modes as above but here are some notes about a few of them. - -## WAIT: Snake speed - -Besides the different game modes if you find it too fast/slow you can change it -to a value that's better for you. Remember it's a wait time so if you want the -snake to move slower you have to increase the wait time! - - -## EVADE: Bug evasion frequency - -For every **EVADE** snake movements the bug will try and find a new place to -occupy. It might be very close to it and I do not ensure it's a new place at -all. - -Each time the bug is placed the counter will be reset whether from eating the -bug or not reaching it in time. Default 300 moves. - -This can help you get out of a trap but it can also sometimes make it harder. - - -## SIZE / MAXSIZE: Initial / Max snake sizes - -997 might be a bit high for many people. Or maybe you don't have enough time. Or -you're not in the mood to lose. Or you want to get close to a size you were last -time but quickly. It might be in conjunction with drawing mode. - -Note on snake size: there's a stated size (in the score line) which is what -the snake will 'populate' in the snake arrays and the visible size: as I have -discussed here and there the snake doesn't grow immediately so it takes time -before the snake is visibly that many characters. In other words just because it -says SIZE 29 doesn't mean it will instantly be 29 characters on the screen. - - -## GROW / SHEDS : Growth / Shedding sizes - -Similar to the initial size. Notice that the above point on size applies here: -it will take the number of movements before it is 'its full size'. - - - -# 7. Conclusion (and inspirational words for everyone) - -I believe that this file has given more than enough information for anyone to -get an idea of what is possible with my implementation of Snake. I realise also -that most people won't have read this far and that's quite all right; I'm very -thorough and pay attention to details obsessively. Creating this game was a -delight and I am quite proud of what I have done. - -Thank you for whatever level of attention you felt you could or wanted to give -and enjoy it in whatever way you see fit! - -**Much more importantly is that Snake aside I hope I have demonstrated how with -the imagination and creativity almost anything is possible.** This is something -**that applies in our world in every way and *everyone* can do something amazing if -they believe in themselves!** I would have never thought this the case for me -years ago but I know it's true now. **Believe in yourself. Always.** - -[terminals.md]: terminals.md -[terminals.html]: terminals.html -[troubleshooting.md]: troubleshooting.md -[troubleshooting.html]: troubleshooting.html -[bugs.md]: bugs.md -[bugs.html]: bugs.html -[spoilers.markdown]: spoilers.markdown -[snake-colours]: snake-colours -[number of hertz (flashes) per second triggering seizures]: https://www.epilepsysociety.org.uk/photosensitive-epilepsy#.Xjgpwi2ZOgQ -[termcaps.c]: termcaps.c -[cannibalism.log]: cannibalism.log -[crazy.log]: crazy.log -[play.sh]: play.sh diff --git a/2020/ferguson1/spoilers.markdown b/2020/ferguson1/spoilers.markdown deleted file mode 100644 index d67420a74..000000000 --- a/2020/ferguson1/spoilers.markdown +++ /dev/null @@ -1,933 +0,0 @@ -# Introduction (SPOILER ALERT) - -In the [gameplay.md][] ([gameplay.html][]) file I have given enough -information to know how the game looks, how to move the snake, how to pause etc. -In this file I am including (some) information on how the game actually works: -the technical details as it were, obfuscation techniques and also some of the -ways I reduced the iocccsize so I could do more with the code. Probably this -file causes more spoilage than the other files though that's not to say -everything will be answered here. - -If you're still interested in this file (i.e. you're a judge or someone who -wants to know more about it) read on. Yes I write a lot. But you probably -already knew that so no harm there, hey? - - -- [How does it work?](#works) -- [How does the snake movement work?](#movement) -- [Drawing (manual) and computer playing (automatic) modes](#manual-automatic) -- [Collision detection](#collision) - * [Cannibal collision detection](#cannibalcollision) - * [Collision detection when resizing the window of the game](#resizing) -- [Obfuscation](#obfuscation) -- [Skinning the snake (i.e. decreasing the iocccsize)](#skinning) -- [A few more interesting size optimisations](#size) - -# How does it work? - -The gameplay I have already described but this explains the way things are -actually done in more detail. - -First I initialise curses; if this fails I print an error message ('curses -error') and return 1. Next the screen size is determined and I subtract 1 from -each the x/y. If it's too small curses is ended and an error message ('screen -too small') is printed - returning 1. - -Next the max size of the snake is determined: if `MAXSIZE` is specified then -it's the value of that; otherwise it's the default. But in either case if it's -too big based on the screen dimensions it's capsized. The arrays for the snake -are this size + 1. Other environmental variables are also processed. - -Then the arrays are allocated; if either return NULL curses is ended, an error -message ('memory error') is printed and return 1. - -If no errors have occurred I set up colours, turn on bold, set up the keypad -etc., place the snake, the bug, draw the walls and then it waits for the first -move (or quit of course). Technically I don't 'wait' for input as such: unless -the wait time is negative (drawing mode) it's just a non-blocking call up to the -WAIT milliseconds but in no event will the snake move until it is told to at -least once (in drawing mode it will always wait). - -In drawing mode if you pause then when you resume (hit space) the snake will -move the last direction it had been told to move - because the direction is set -and input was detected (the same goes for another key e.g. 'g' - just like in -normal mode). But it will then wait again for input. - -The snake function both draws the snake and updates the arrays; if the new size -is >= the max size the game is won (the winning size will be shown as the max). - -Else the arrays are updated; when the array is being updated the body of the -snake is drawn. After this the head is drawn. Depending on the length of the -snake wrt the size the snake technically it might be that the number of -characters the snake takes is increased rather than having moved the snake -forward (i.e. the tail moved by one). This registers where the snake is and -where it was but no longer is (important particularly for negative growth/shed -sizes). - -After that a bug is placed (but not drawn) in a pseudo-randomly selected area of -the field. This function takes a parameter which if not 0 and the bug had -already been placed somewhere (in other words if the bug was just eaten) call -the snake function. As long as the game isn't won at that point find a new place -to place the bug. - -Next the score line is updated and then whilst the game is not over (player -loses/quits/wins) the movement proceeds; if the game hasn't started yet it will -jump to the top of the loop. If it's paused the same thing until it's resumed. -The rest of the loop can be described thusly. - -It checks the current max y/x, draws the walls, bug, gets any input from -keyboard, and then if there is movement checks for collisions; if current max -x/y are is/are smaller than original then it's automatically a game over from -running into walls regardless of that feature status - this simplifies things -greatly (see below for more details). - -If cannibal mode is disabled (the default) and the snake runs into itself then -it's also game over. Or did it run into the wall? If walls are passable (the -default) it will come out from the other side; else it's game over. If it comes -out on the other side and the snake is there and cannibal mode is disabled then -it's also game over. - -As the snake moves it will for each move grow by one up to the technical size. -This might at times look a bit different from other times but it's a visual -thing based on the internal data of the game and the features enabled for -example shedding and shedding size. - -Finally if the snake's new coordinates are on a bug a new bug is placed on the -field, the snake grows and the score is updated. Otherwise if the bug has waited -the number of moves that it waits before it finds a new place the bug is placed -in another location too (and the snake does not grow). In either event the snake -location is updated and redrawn. - -There's also the processing of any shedding at this point. If the snake eats a -bug the snake will not shed; if it sheds the snake will not move in the same -manner as if it simply moved. Growth/movement can in some ways be conflated. - - -# How does the snake movement work? - -The way the snake looks to move is that every time the snake moves the last -position (the tail) is replaced with a space (`' '`). If the snake eats a bug -then the snake will get bigger in the next `N` movements. Note that if the snake -isn't its full size it will only be growing and so might not appear to be -moving at the tail end; I have discussed this more than enough elsewhere. - -There are two arrays that store the last Y/X coordinates of the snake's -positions that are shifted forward as the snake moves about the field looking -for bugs. - - -# Drawing (manual) and computer playing (automatic) modes - -How do these modes work? The `timeout()` function takes a signed int: if it's < -0 it waits indefinitely; if it's 0 it returns immediately even if there's no -input waiting (if input is there then it's used instead); and if it's > 0 it -waits that many milliseconds. Thus a negative value allows for complete control -over the snake's movement (speed and otherwise), a value of 0 means the snake -will move as long as it's not paused etc. and otherwise it waits up to that many -milliseconds (which is how the 0 value comes into play). - - -# Collision detection - -This was an interesting thing to work out. Because the snake could occupy more -than one row and more than one column at a time the two arrays approach seemed -good to me. - -I already mentioned how the array is shifted but to be clear: the snake head is -always at element size - 1 and the tail is at 0 and so it's just a matter of -verifying if the snake head is at a place that's occupied by the snake, the wall -or the bug. - -The bug does its best to avoid landing where the snake is but given that snakes -are very flexible and cunning who can tell what might happen? As I noted these -bugs are not bugs! :) - - -## Cannibal collision detection - -How does cannibalism change collision detection? The fact that each time the -snake function is called there is a space printed at the tail end was a problem -to resolve: what happened is if the snake went through itself then at some point -there would be a space that breaks the snake into more than one piece (where the -tail was but that had been another part of the snake)! - -So what I do: first the tail is cleared, then as the snake coordinates arrays is -being updated I redraw the snake chars. Then at the end the head is drawn (it -used to be the head was drawn first but by drawing it after you can see where -the head is inside itself, creepy as it is). Thinking on it this very possibly -is why (or partly) the negative shedding leaves the head somewhere in the middle -of the snake (or that the head of the snake is left behind) but again I don't -think that as a problem. - -There are other strange ways this looks. Not only does it look like lines -intersecting at any number of places but it looks particularly strange when the -snake goes the opposite direction: it has to unwind as it were, and for a moment -you might not see what is truly happening. There are two ways you can look at -this I think. - -First is that it's physically impossible in real life so the fact this cheat -mode exists for a different kind of play means it should look unnatural: for it -*is* unnatural! - -The second is you can look at it as if two more snakes are having fun with each -other. This can also appear to defy physics! - -Choose your poison. Or rather your venom. - - -## Collision detection when resizing the window of the game - -What happens if the window is resized during the game? If the size is increased -then the border can (and will) stay the same; but if the window is shrunk then -what? It would be very difficult if not impossible to redraw the snake: where -would the snake be and what about the border? The arrays would have to be -adjusted too! Another issue: what if the snake is beyond the new walls? - -The best thing I could think of is that if the window is shrunk then the snake -is assumed to run into the wall: in other words the game is at that point over. -If the window is increased in size nothing happens other than the game won't be -the full window size (or technically a few rows/columns fewer): everything will -remain the same. This GREATLY simplified things. - -There was another problem at some point where the snake would quickly grow if it -was increased in size but I believe that's resolved. Anyway what kind of field -in real life increases in size like that so suddenly? :) - - -# Obfuscation - - -- I make use of the C token pasting operator. Although this is well known it's -probably less known and either way it's not as simple as just having the code -that it translates to. For example why have: - - attroff(COLOR_PAIR(L)); - - When you can have instead: - - attroff(m(PAIR(L))); - - It's an extra layer of indirection. This used to be more used but when - making it easier for users to change the colour pairs I no longer used it to - set up colour pairs. It might not defeat `cpp` but it does make for more - reading otherwise (depending on how you look at it maybe less?). - - It also helps save iocccsize bytes (but not as many as it used to!). - - -- What is this block of code? - - F(MAXSIZE)g d g = f((a)((*y-2)*(y[-2]-2)),g); - F(GROW)N d - F(SIZE)A d - F(WAIT)y[-3] = (int) strtol(z,NULL,0)); - F(EVADE)W d - /* and so on */ - - There are several things going on here but I'll focus on the main one here and - leave the others to their own: the `F` macro and the associated `d` macro are - here important. - - In addition to saving many bytes this also makes for interesting reading. - What this actually is doing is checking for environmental variables. If - `getenv()` does not return NULL then strtoul() is called on it (except for - the one that clearly shows strtol()). - - There are other things going on (below). - - Now it might not defeat the cpp but can you tell me what this is even with the - cpp? - - f((a)((*y-2)*(y[-2]-2)),g); - - The cpp might get it closer to what you might expect in a normal program but I - refer to the array offsets (below also). - -- Stringification: there isn't a single `"` in the source code! The `F` macro -uses the macro `Z` which does the real work. At first `F` was only `getenv` but -I changed it to use stringification. Following the creation of the `Z` macro I -made it use it instead. If I remember rightly that uses extra bytes but somehow -it felt better this way in my head. Then to save many bytes I made the macro do -much more than that (that is the `F` macro). - - As for the Z() macro it means that I could remove `"`s; for example: - - #define J Z(X:%d/%d Y:%d/%d S:%zu/%zu B:%zu\n), Y, y[-2], X,*y,A,g, c); - - Which as I have discussed elsewhere is the entire set of parameters to both -`mvprintw()` and `printf()` plus the end of the function call (i.e. `);`. - - BTW: There's another really cool benefit of stringification: because more than -one space is converted to a single space I can have it spread across columns on -the format and even new lines if necessary. This fact along with the next -technique causes some beautifiers problems too. - -- Another use of the `Z` macro is this curious line: - - static char s[]=Z(; +<>^v); - - There are a number of curious parts. First is the Z() part makes it look - like a function call. Of course that wouldn't work as such because an array - initialiser needs an initialiser list or a string literal. Whether that's - immediately obvious to others I don't know but then how is this even possible? - A `;` followed by ` `, `+`, `<`, `>`, `^`, `v`? - - A very minor factor is that the character 'v' is there and it's (`v`) also - used as a macro (well, define) 'might' be of something to some people. Or - programs maybe? For instance using in vim `*` on the `v` in that array and - it takes you other instances of `v`: as in `\`: which happens to be - something else entirely. That itself however might or might not be - significant for this instance! - - But what function would take the string (...function and string?) that it takes? - The sequence of characters would normally cause compiler errors like that. - I have it in the function that uses it; static since it need never change: - only one copy is needed. Why I have it in the function is to move it further - away from the definition of `Z()`. I will also be returning to this one - again. - - Another example is using the `E()` function e.g.: - - E(Z(YOU WIN!)); - - Which happens to be in the same function the above char array is defined. Also -it's more like a string than anything but yet it's not visibly a string. - - Some beautifiers don't like this. - -- There's an `#undef` following an `if` in a way to draw attention from what to -do if the if is true. - -- There are a couple case statements that fall through to the next case -condition. Actually one falls twice: into the one below which falls into the one -below that. This means the order it is in is required. It also reduces the -iocccsize a reasonable amount (by having it in the main loop I could do these -things which reduced the number of keywords needed). - - When quit is requested it falls into pause. But then pause falls into the - default (so no recognised key is pressed) which does `x = 0;`. Outside the - switch block there's `if (x) P = 0;`. So what this amounts to is: if quit - is requested pause will be changed to the opposite state. This will then - fall to setting x to 0. And because x is 0 the state of pause won't be - updated again. Now because `Q = 1;` (or actually `l`) it doesn't matter if - `P || !P` because Q will also jump to the top of the 'loop' only to exit - since the condition no longer is true. - - But then if pause is pressed the same thing applies: it will be updated to - the opposite state and then it will fall to the default case which sets `x = - 0;` which means it won't update pause outside the switch block. If pause was - enabled then the loop will start over only this time because quit wasn't - requested it will re-enter the loop. - - So basically quit can both pause and resume but it doesn't matter because it - will still quit; and pause will fall to the default statement without being - updated again either. In other words the only time pause will be updated - (disabled) *outside the switch* is if a direction key is pressed. But of - course it's disabled inside the switch if pause key is pressed when it's - already paused! This removed three break statements because the order allows - it; if the order was changed then the code would break in another way and - not the good kind! - - -- On the note of pausing there's at least one instance of removing `if..else` -(besides those in e.g. environmental variable processing) that's curious: I -converted this: - - if (!P) P = 1; - else P = 0; - - To this: - - (!P && (P = 1))||(P=0); - - -- Makes use of the different 'namespaces' to have variables and labels be the -same identifier. For example in main() there's a variable that also have the -same name label for `goto` (there used to be two pairs in main()). - -- And on that note: makes use of the ugly `goto` in numerous places. This -includes simulating a while() loop, breaking out of that 'loop', breaking out of -two loops (a for() in the 'while()'), re-entering (as in starting over) a for() -loop and for dealing with errors. - -- Why do I check for `Q || P` and then goto x only to then check for `!Q && !D` -to then goto x? Do I need to check Q at all? In fact I do but at a quick glance -it might be easy to miss. - -- Uses an unnamed array (compound literal) for several different game variables. -The pointer to the first element is defined like `int *j`: - - static int Y,X,*j = (int[8]){231,0}, *U, *V, *e, *n, Q, P,x,*y,l=4,*D; - - Does the 231 seem familiar? It should: it's the default key delay for the - `timeout()` call (which I will return to again). The rest are 0 default. But - what about `j`? Is it even used? Actually in main: - - y = j+3; - - But aside from that `j` is not used. However... - -- Major abuse of negative pointer offsets along with confusing names. I'll say -this in two parts: the negative offsets and then the confusing names. - - Can you honestly tell me that this is easy to follow? - - if (Y < 1 || Y >= y[-2] || X < 2 || X >= *y || y[-2] >= y[-1] || *y >= 1[y]) - { - if (*p && !(*y>=1[y]||y[-2]>=y[-1])) - { - if (Y<1||Y>=y[-2]) - Y = Y<1?y[-2]-1:1; - if (X<2||X>=*y) - X = X<2?*y-1:2; - } - /* ... */ - } - - That's not the only place negative offsets occur! Observe the ternary operators - there too. - - I'll go back to the F() macro and in particular what follows one of them: - - g d g = f((a)((*y-2)*(y[-2]-2)),g); - - To parse that at the very least you have to know that `y` points to `j + 3`. - Then you have to know that `j` points to the first element of an unnamed - array (compound literal). But then you also have to know that that array has - different types of data. Once you figure out the elements in question you - then have to account for the offset that `y` is. Then once you have that - figured out you need to consider the negative offsets to `y` (this also - applies to the block above of course and maybe more so). Then you have to - think of operator precedence etc. I mean what is: - - (*y-2) * (y[-2]-2]) - - ...about? An offset of -2 and subtract -2 from that. Multiply that by the `*y` - (which is `j[3]`) - 2. - - But why is there a `g d g =` when the other lines only have the variable (or - location) and a d? (Excepting the one that doesn't refer to `d`.) - -- On the confusing names etc.: the `getmaxyx()` function gets the maximum Y, X -dimensions. Now in the first call in main() I have the code and tests following: - - getmaxyx(v, *y, y[-2]); - --*y; --y[-2]; - if (y[-2] < 9 || *y < 9) - return E(Z(term too small)); - - Clearly the max x must be stored in `*y` and the max y must be stored in .. - `y[-2]`. After all anyone knows that the coordinates are in the order (X,Y)! - In fact the output of my entry and my documentation demonstrates this - clearly: X before Y! It's so true in fact that the documentation for curses - more than once states that it's actually (Y,X)! But the fact I don't even - refer to a `x` is possibly confusing even without the negative offset. - - Of course I don't have to use negative offsets at all! I could use positive - offsets for every time I use negative offsets. But where's the fun in that? - :) - - BTW: Some of these are not in the table of expressions in the HACKING file. - Also there might be some code in there that is no longer correct (it was - before I started obfuscation) though it might be able to give an idea. - Spoilers abound in that file (in addition to this file) but the HACKING file - states things that are no longer true: a way of obfuscation itself! Instead - of misleading comments it's misleading statements in the file that explains - what some of the things are for! - -- Misleading variable names for coordinates. In other words the Y coordinate is -called X and the X coordinate is called Y. This adds to the already confusing -array references but it also adds to the confusion of the order of the curses -functions coordinates: it appears that yes X does come first because the -function calls that take coordinates suggest it - but in actuality it's just -that the names are deliberately swapped. Of course that's after you determine -that the variables `X` and `Y` are only the snake head but at that point you -still have to figure out the array confusions. Also if you're tired `V` could -easily be read as `Y` and I have a lot of fun with `V` which I will get to next -(and again). Finally why not have them be two different names unrelated to -coordinates? I thought of this and I think it's more confusing to have something -that is associated to what it is but incorrectly rather than something unrelated -entirely: the mind constructs things from what it has seen, what it experiences, -what it knows, and since X and Y are used for coordinates the natural response -of the brain is to assume Y is what is really X and X is what is really Y. - - -- More confusion with variable names: If you notice the `int *U, *V` they are -also part of the compound literal: that is they point into it: - - U=y+2; V=U+1; - - Except that these two are done in two different places: away from the - assignment of `y` to remove them further away from the contexts they are - used in. - - There's another reason for moving it further down: if you look at the bug - drawing function (which comes before main()) you will see that there I set - the bug location to refer to the `y` 'array'. I do blank out the old - location at `*U, *V` but then assign the new location differently. Then in - main() I draw it at `*U, *V` and also check for the bug being eaten there - too. - - -- There's another array that holds a variety of data. - - static k p[5]={1,0},W=300; - - It holds the following information (but not necessarily in this order): - - WALLS - CANNIBAL - SHED - SHEDS - EVADE - - But how will you know that just looking at the declaration? Many variables - and they're part of collections but the many layers of indirection cause - confusion to the already unhelpful variable names (for those that even have - names!). - - -- There's an array of pointers to functions too: it holds three functions but -rather than always use the array I only do some of the times. I even use the -same function in the same general area with both names in order to mislead. -Incidentally `splint` seems to have a parse error on the declaration though I -don't know why. - - -- On arrays/pointers in general I also use e.g. `3[p]` and `p[3]` (there are -many other examples that I have already shown). - -- Expanding on referring to the same thing in more than one way take a look at: - - case KEY_LEFT: - V[1] = l-3; - break; - case KEY_RIGHT: - V[l/l] = l/2; - break; - case KEY_UP: - V[l-3] = l-1; - break; - case KEY_DOWN: - V[l/4] = l; - break; - - What's going on there? First of all the letter ell looks very much like the - digit one but above there are only two references to the digit `1`; the - others are the letter `l`. If you're tired though, don't have the best - vision (I'm speaking from experience here and that's not even considering - the font) it would be easy to miss. Online in a text editor you have syntax - highlighting of course but there's more to it than that. Also in other parts - of the code there are references to the digit 1 instead of the letter ell as - part of array indexes too (so which is it?). - - Second is that the index referenced in each of the case statements evaluate - to the same: 1. That's all accomplished by taking advantage of the value of - `l`: 4. - - And then to further make use I have done the same for the value to assign to - that element: 4 - 3 is 1, 4 / 2 is 2, 4 - 1 is 3 and 4 is obviously 4. - - But doing it this way means you have to first of all know that there's the - variable `l` (conveniently declared at the top of the file); then you have - to determine (once you're at this place in the code) if it's the digit or - the letter - for each reference (since both exist)! After all it's entirely - possible that I could have negative offsets here (and there's nothing that - would prevent negating a variable either in this case): I have done it many - other places! But even after all of that you still need to do the - calculations when I could simply have had: - - V[1] = 1; V[1] = 2; ... - - Etc. - -- Variable reuse. For example in the function E() there's a variable that keeps -track of whether or not it's been called; that variable is `h` and it says -whether to call `endwin()` or not in as in some cases E() is called more than -once. Now instead of setting the variable `h` to 1 I set it to `l`: it looks -like a 1 yes but it's actually 4. Does it matter that it's 4? No. The only -thing that matters is that it's a non-zero value. The same applies to `Q` which -I also set to `l`. - -* I also use `*y` as a boolean. If you observe that I set x to `*y` (which -itself might make one believe they're related in some form): it used to be just -a 1. However it is only used as a boolean. `*y` as I described as the LINES -variable on invocation of the program. Since I check if it's < 10 before -continuing the game I know that it will always be non-zero. But what if someone -tries LINES=0? In fact it will be ignored: if the terminal is 25 lines and you -specify 0 lines then it'll be 25. Therefore I can use it as a boolean here since -it needs there to be non-zero. Then in order to get the opposite, 0, I can do -`!*y` which I do elsewhere. - -- More generally on variable reuse: the compound literals and arrays of the same -data type but which are completely different things means I can use the same -name in different parts and so it's then a matter of determining which -element/offset to use. - -- Why do I compare if the original dimensions are >= the current when the idea -is to make sure that they're actually bigger? How does that work? Why not just -check if it's > than the current? - -- More confusion with the max x/y dimensions: Going back to max x/y dimensions -and coordinates of the snake; I showed this code before: - - if (Y < 1 || Y >= y[-2] || X < 2 || X >= *y || y[-2] >= y[-1] || *y >= 1[y]) - { - if (*p && !(*y>=1[y]||y[-2]>=y[-1])) - { - if (Y<1||Y>=y[-2]) - Y = Y<1?y[-2]-1:1; - if (X<2||X>=*y) - X = X<2?*y-1:2; - } - else - { - /* ... */ - } - } - - I asked if you could honestly figure that out easily. But here's a bit more - elaboration. I already talked about the negative offsets - and they should - talk enough themselves - but look at the mess. Even if they weren't negative - offsets there are a lot of array references (though by a pointer to an - element in the unnamed array) and comparisons all jumbled together. There's - more. - - I have the `!` of a `||` to flip the logic: that's not how a normal person - would write code. Well...not usually, I guess; I have done it in times where - it seems more appropriate but it's not the way it's usually done (I think?). - - Even so that part is doing more than one thing: Not only is that code making - sure the max dimensions aren't smaller (from the original) but it also makes - sure that the wall isn't hit (when it's not allowed) as well as making the - snake go through to the other side of the wall if the snake is allowed to - and hits the wall! And even when the snake is allowed to go through walls if - the screen size has shrunk it will reach the else part. - - It also has the ternary operator to adjust the coordinates of the snake head - should the snake go through a wall and those also have negative offsets. But - how can you figure that out just looking at that mess? Unhelpfully the X/Y - seem to both be compared to `y` but never `x` (that variable is unrelated - and not even in this part of the code). - - -- The function `B()` ends with a `goto`. Why? It's also another place where -there's at least one negative offset. - -- A note on the many `#define`s in my entry. You say you dislike them: - - Doing masses of #defines to obscure the source has become 'old'. We - tend to 'see thru' masses of #defines due to our pre-processor tests - that we apply. Simply abusing #defines or -Dfoo=bar won't go as far - as a program that is more well rounded in confusion. - - As I have noted though many of these save quite a lot of bytes too. It's - true that they might aid (but barely) in obfuscation but I think that my - entry is more rounded in confusion; certainly the other techniques I have - mentioned include much more than just `#define`s. One of the `#define`s that - saves a significant number of bytes is of course `J`: I use that twice and - it would be a huge burden to include it twice! - - At the same time I have removed some `#define`s that have added bytes; I've - also removed some that saved bytes and in fact I think it makes it more - obscure if in no other way but making it more difficult to look at (for - example I might have been able to save bytes by defining for the array - offsets but I find that it looks much better - which is to say much worse - - the way I have it now). - -- Going back to the curious `static char s[]=Z(; +<>^v);`: there's something -magical about the number 7 isn't there? I do not refer to primes although -certainly one could say that primes have a magic to them. No. I'm instead -referring to another property: bitwise AND. For example consider the following: - - 7 & 0 - 7 & 1 - 7 & 2 - 7 & 3 - 7 & 4 - 7 & 5 - 7 & 6 - 7 & 7 - - What is the result of each operation? Quite simply it's the number itself! - How does this relate to the `s` array though? Let me back up. Is there any - place in the code that has the number 7 (by itself - 997 doesn't count)? A - quick check will answer that: no. But here's a curious thing: - - q X, Y, s[*D+2&(1<<3)-!Q]); - - We KNOW that at this place `Q` is 0 so the `!Q` has to be 1. Now `1 << 3` is 8. Therefore - - (1 << 3) - !Q == 7 - - But what's `*D`? If the snake hasn't moved once yet it's 0; otherwise it's the number - of the direction the snake is currently facing. Thus - - *D&(1<<3)-!Q - - ...refers to `*D`. However if I were to have that be the element it wouldn't - work right: I wanted the semicolon in there but the space was needed to - reduce the iocccsize down by one (ironic that adding a byte reduces it by - one but there it is). I could have had it later in the array but this felt - slightly more confusing: more operators (so precedence) to consider (and in - fact GCC complains about this but as is clearly demonstrated it's not - important). By adding a `+2` it adjusts the index to be the correct value in - order to show the proper index of the char array. - - But how do you know this? You have to know first of all that Q will always - be 0 at this point (so therefore !Q == 1). You also have to understand that - in the range N = 0..7 the value of `N & 7` is `N` (which might not be - something everyone realises). Obviously you need to know that (1 << 3) is 8 - in order to get the final `*D & 7`. - - You furthermore need to know that `*D` will always be in the range `0..4` - which means that `*D & 7` will always be in the range `0..4` (If I were to - have added a `& 7` to the end it'd still be the correct value! But the use - of bytes couldn't be justified there.) Finally adding 2 will get the correct - value of the array index. After all that it will print the correct character - for the head! - - But there's another trick here: there's never a direct assignment to `*D`: - `D` is set to `1 + V` and `*D` is never touched directly: it's all through - `1[V]`. The references to directions i.e. `KEY_LEFT` etc. and also the - switch() to see how to modify X/Y are all done through the `V` pointer. But - then what is `V` anyway? It's `U + 1`; and `U` happens to be `y + 2`. But - `y` happens to be `j + 3`. Of course `j` is a pointer to the first element - of the compound literal! So there are quite a lot of levels of indirection - in this part alone (not to mention other places)! - - -# Obfuscation in prog.3.c and prog.3-j.c - -There are a few differences in this version that are worth noting. First of all -is a define that's in the compiler invocation Makefile is in the source file -itself. Second are the following obfuscation techniques. To simplify integrating -the explanations I have simply yanked and pasted them below: - -- Variable reuse in ways that might not be so intuitive. For example in the -function E() there's a variable that keeps track of whether or not it's been -called; that variable is `h` and it says whether to call `endwin()` or not as in -some cases E() is called more than once. Now instead of setting the variable `h` -to 1 I set it to `*y`. Now as you can see `*y` starts as a NULL pointer; this is -set to the compound literal `j` + 3. This in turn is what LINES is (number of -lines). So `y = j + 3;` and `j` I discuss elsewhere. But what if LINES is set to -0? LINES being set to 0 sets it to the terminal max size but in any event I test -for values < 10 and if either LINES or COLUMNS is < 10 then E() is called. -However E() always returns 1 and since this is in main() and it's an error I -simply return E() and thus it's only called once. Similarly for `Q` I set it to -`l`. But then..what is `l`? - -- In fact `l` is assigned a value 4 via a compound literal. It's declared at the -top of the code and then in main() assigned like: `l = ++(int) { 3 } ;` which -equates to 4. I think that's pretty cool! - -- What is this? `for(I=0; A&&I 0 so `A-A` is 0; however because `A` -is non-zero the expression `A-A||A` is equivalent to `0 || A` and since A is -non-zero we know that the value is `1` and thus it's `A - 1`! I quite like this -technique: it doesn't make sense at first glance and it probably remains elusive -until you look at it more thoroughly. It compares that `I < A - 1` but the `1` -is calculated by subtracting from `A` the value of subtracting `A` from itself -`|| A` and `A` is always non-zero (as the condition says) which means it's -`A - 1`! - -There might be more things that differ but those are the significant ones. - - - -Of course none of the above is so easy to see without a beautifier. Why did I -choose the format I did? Well it came about in a sequence of possible layouts. I -tried first to make it look like the game might itself but the trouble with that -is it's too random. Then I thought maybe I could spell out the words SNAKE 2020 -or IOCCC 2020 or something like that. But these were proving to be a problem for -me; what I was seeing on the screen just wasn't looking right in my head. - -At that point I just started playing with different patterns and I quite like -what it's turned out to be. I don't know what I would call it but one could look -at it and think of columns holding up a structure. There's also a symmetry to it -and almost certainly that's what is most attractive about it to me for I love -symmetry. - -Some final notes on the layout I'd like to point out. First there are 53 lines -counting blank lines. Second is that if you count the number of lines (of code) -in each 'block' they should all be a prime (except the two blocks of 1, I -guess). Third is that I tried to make sure that each column of the code (not -counting the preprocessor - see below on that) start and stop at the same -position (as in column in the row/column kind) on each row. Below I show the -start and end column of each horizontal block. The `#undef` in the middle should -be centred based on its length and the 120 columns for total. The preprocessor -directives should also be centred in that way (that was my goal anyway). - -The following were intentional: - -* 53 lines. - -* The number of lines per vertical block (3, 5, 3, 11, 7, 1, 1, 5, 7). - -* Number of horizontal blocks (5) - -* Number of vertical blocks (9) - -* iocccsize 2053. - -The symmetry was even more important to my mind; observe that each vertical -block (or column) starts and ends on the same column (or at least that was my -goal; I think I did it but maybe I missed one here and/or there): - -* Vertical column #1: 5, 22 -* Vertical column #2: 25, 41 -* Vertical column #3: 49, 64 -* Vertical column #4: 73, 90 -* Vertical column #5: 97, 120 - -Furthermore based on the length of each C preprocessor directive (the -`#define`s, the `#includes`, the `#undef`) I have attempted to centre them in -the file based on the 120 columns total. I believe I have succeeded in that -though again maybe I have an off by one or two here and/or there. - -Most of the primes above were intentional. - -# Skinning the snake (i.e. decreasing the iocccsize) - -This happened in a number of ways and I have already documented some in other -parts. Now there are a number of reasons to do this: amongst them to make it -possible to obfuscate (plus a more unique layout) but also to add more features. - -Interestingly some of the obfuscation saved bytes; others of course added bytes. -For example token pasting saved a few bytes; on the other hand the combining -`if`s to the complicated one above added 9 bytes: but another one saved a byte. -I have done this more than once so it's entirely possible there are others I am -forgetting. - -Obviously a more significant one is `#define q mvprintw`: this saved a total of -47 bytes! This was however done towards the end (or so I thought it was the end; -it was anything but) when I started looking at obfuscation rather than adding -features. But then because of this I looked at adding more features! - -Another thing that reduced a lot of bytes - 34 to be exact - is using the -`mvvline()` and `mvhline()` ncurses functions rather than use three for loops to -draw the walls. - -Much later on I made it so that the E() function returns 1; this was a clever -way to have what I already did have - return 1 if there's an error (i.e. memory -error or curses error or screen too small) but otherwise return 0 without having -to have a separate int and I could get rid of two `{}` pairs as well as add -another use for the E() function: it calls endwin() exactly once (even though -when there isn't an error condition it can be called more than once) as well as -printing the final score - or else an error. And now it also allows for printing -an error and returning from main() at the same time! This also means that by -returning E() I am returning 1 but if the end of main() is reached 0 is -returned. In either case curses is ended. - -One of these optimisations saved four bytes simply by taking advantage of the -value of a loop iterator after the loop. The loop is: - - for (I = 0; A && I < A - 1; ++I) - -However after the loop I had this: - - A > 1 && q(n[A - 1],e[A - 1], 'o'); - -When I was considering moving that line to before the loop it occurred to me -that as long as `A > 0` then `A - 1 == I` after the loop. I still had to check -for `A > 1` but I could refer to just `I` instead of `A - 1`! - -There were quite a few other things besides these and these gains were useful to -add additional features and also to add proper error reporting (when `calloc()` -failed I called `abort()` and before that if initialising ncurses failed I also -called `abort()` and did not report any errors in either event but I do now). - -# A few more interesting size optimisations - -As I was getting to the end of obfuscation (wrt size available - and it was -getting pretty obfuscated too - the majority of the techniques were already in -place at this point) I was looking at the code to see if I could figure out how -to save even more bytes. Here are some that I found interesting ways to go about -the problem. - -First, the function that turns on colours looked like: - - if (h) attron(m(PAIR(L))); - else attroff(m(PAIR(L))); - -But I thought is that else actually needed? If I were to switch the order so -that the `attroff()` is called first then I could just do an `if (h)` - -therefore getting rid of the `else`! But then I thought why not do what I've -done elsewhere? I had: - - attroff(m(PAIR(L))); - if (h) attron(m(PAIR(L))); - -But I then saved an extra byte by making it: - - attroff(m(PAIR(L))); - h && attron(m(PAIR(L))); - -That saved two bytes. - -Second let's look at the B() function: there were two things I thought of that -would save some bytes (I no longer know how many but a few at least possibly six -or even more). I had the following code: - - if (V && U && o) { S(N); b(1); } - -This made sure that only if the snake has caught a bug should it grow by the -growth size and increase the bug count. But was checking for the `V` or `U` -necessary? I gave it a look over and it occurred to me: the only time B() is -called with `1` (thus `o` is 1 so the if is true) was if the bug actually is -caught. That means I could remove the `V && U &&` part of the if! Thus it became -instead: - - if (o) { S(N); b(1); } - - -Then this - - !o && V && U && q U,V,' '); - -...is to make sure to only clear the spot the bug is (or was) at if -the bug was NOT eaten. Why not eaten? Because if it was eaten it means the -snake's head will be there! - -But why did I check for the bug having coordinates? Because I thought that if -the bug has coordinates obviously the function has been called before which -means that we do clear (write a space) over a place the bug was actually at. But -I thought could there maybe be another way of verifying if the game has started -yet? - -Before I ask that question I'll point out this: is there a reason it has to be -checked? Yes because right after this line new coordinates are found. But -unfortunately the snake has to be placed first since the bug cannot be placed -where the snake already is. So if I had to make sure the game had started (the -snake has moved at least once) what could I check? In fact that means the -direction is not 0! So I could just replace it with: - - !o && D && q *U,*V,' '); - -That reduced the count further. But then I wondered if even that much is needed. -The condition is that the bug has evaded which means the snake has moved already -(so game started). What calls to this function happen before the game starts -though? The original placement of the bug. Well if the above check makes sure -that the bug isn't where the snake is at it means that the snake has already -been placed, right? Going further the snake always starts out at the centre (or -rather the close as possible - rounding maybe an issue depending on the max -y/x): in other words not at 0, 0. This means that the only thing I should have -to check for is that `!o`: because after this call before the game even starts -the score line is updated: so the empty space will be overwritten anyway. And if -the score line is made an empty string it doesn't matter because it'll appear as -all spaces anyway. Thus I could save another three bytes by changing it to: - - !o && q *U,*V,' '); - -Technically it would be a `" "` (see [HACKING][] file) else for N (not referring to -the variable though it might very well matter here too) movements (i.e. due to -sizes) you will at times (e.g. after eating a bug) see the snake body char at -0,0. I'm not bothered about that though because the score line is meant to -display something, it squeezes a few more bytes and having a space is hardly an -imposition on usage (and for that matter having a 'o' at 0,0 isn't either). - - -[gameplay.md]: gameplay.md -[gameplay.html]: gameplay.html -[COMPILING]: COMPILING -[HACKING]: HACKING diff --git a/2020/ferguson1/terminals.markdown b/2020/ferguson1/terminals.markdown deleted file mode 100644 index 22553075d..000000000 --- a/2020/ferguson1/terminals.markdown +++ /dev/null @@ -1,340 +0,0 @@ -# Terminals - -Terminals are very easy to mess up and for that reason they deserve their own -file. I discuss terminal capabilities, dimensions (setting and how to restore -sanity if it causes any problems) as well as colours (limitations etc.). - - - -- [Capabilities support testing program](#termcaps) -- [Setting the game dimensions](#dimensions) - * [The LINES and COLUMNS environmental variables](#linescolumns) - * [Terminal insanity](#insanity) - * [Restoring the terminal](#restore) - * [Terminal size abuse](#abuse) -- [Terminal limitations](#termlimitations) -- [Terminal colours](#colours) - * [Curses background](#background) - * [Monochrome terminals](#monochrome) - * [Colour limitations](#colourlimitations) - * [Disabling colour](#disablecolour) - -# Capabilities support testing program - -Although it should be playable without the features I use (except that cursor -movement is required) I have provided a source file that can test your terminal -for the capabilities I use in the entry. - -It also tries to determine the minimum number of columns to play without the -status line overflowing - even with the dynamic length. Try: - - $ make test - -This compiles the program and runs it; you should see something like: - - terminal supports cursor movement - terminal supports making cursor invisible - terminal supports bold - terminal supports colours - - terminal rows 42 (39 playable) - terminal cols 157 (155 playable) - - snake size: 997 (max size: 6006) - bugs: 199 (max size: 1201) - - at least 34 columns recommended for snake size 997 (is 157) - at least 37 columns recommended for snake size 6006 (is 157) - - No problems detected. - -If I pass in any of the variables **GROW**, **MAXSIZE**, **SIZE** and/or -**LINES** or **COLUMNS** it bases its calculations on those variables. For -example: - - $ MAXSIZE=-1 GROW=1 make test - terminal supports cursor movement - terminal supports making cursor invisible - terminal supports bold - terminal supports colours - - terminal rows 42 (39 playable) - terminal cols 157 (155 playable) - - snake size: 6006 (max size: 6006) - bugs: 6001 (max size: 6001) - - at least 37 columns recommended for snake size 6006 (is 157) - at least 37 columns recommended for snake size 6006 (is 157) - - No problems detected. - - -As you can see it tells you the max size (the 'capsized' snake) as well as -the requested max size (if the requested size is bigger than the capsize then it -will be set to that value instead) of the snake. The capped size is like in the -Snake game itself but the suggested minimum number of columns is based on a -number of variables. - -If I were to do the above but set columns to less < 35 then there would be a -problem. But why if the utility recommends at least 37? This is because the -max size of the snake also depends on the terminal size. This utility is not -perfect: I don't consider shedding; I set growth to 5 if it's 0 (since it would -be indeterminate how many bugs are possible before 'winning' since there would -be no 'winning'); and there are probably other things too. - -As for capabilities: besides cursor movement the others are just nice to have. -From my tests keeping the cursor visible doesn't ruin gameplay but it is less -distracting if nothing else to have it invisible (in fact due to the sequence of -function calls the cursor would mostly appear to be not in the game field itself - -from memory at least). - -Maybe there are other capabilities I should check for that I'm unaware of but -this should give you an idea. - -If there are any problems it will say how many (fatal and otherwise). The fatal -problems are if curses fails to initialise or if movement of cursor cannot be -done (both seem odd). - -Both scripts run this utility and prompt if there are any problems; -you can continue either way. - -# Setting the game dimensions - -There are a number of ways to do this and only one will keep the terminal sane -all the time but that's by running it under a terminal emulator that you can -resize the window with first. But what if you want exacts? - - -## The LINES and COLUMNS environmental variables - -To specify the lines/columns of curses program per instance use the `LINES` and -`COLUMNS` environmental variables. - -For demonstration consider the following: - - $ echo $LINES $COLUMNS - 42 157 - -Note: I subtract 1 from the max y/x and the max coords in the game output -include the walls and score line but these values are for the terminal itself. - -Say you want 55 columns: - - $ COLUMNS=55 ./prog - -This will force curses to detect the max columns (X) of your terminal (for the -game dimensions) - to be 55; it might look like this instead: - - X:27/54 Y:20/41 S:5/997 B:0 - -But problems can arise with messing with dimensions: in my tests this held only -to lines. - -## Terminal insanity - -Terminal sanity can be compromised and it can cause all sorts of output -problems; it might not appear right away but it will show its ugly head -eventually if you use it. For example pager output might be screwy, echo might -be turned off, output of commands might overwrite the input of those commands, -etc. For the game you might not see the final score. - - -### Restoring the terminal - -In my tests there is one thing that at least allows you to see the final score -and that is by running `clear` before running the program. That's what the -prog.alt version does. You might run it as one of: - - LINES=20 ./prog.alt - LINES=20 ./snake.alt - - -Even if the final score is visible though the terminal may very well still be -messed up. There are numerous things you might try but probably the easiest is -to run the game again without modifying the dimensions (and then just quit) or -even just the `reset` command. - - -The Linux man page says: - - You might have to run the `reset` tool like: - - reset - - (the line-feed character is normally control-J) to get the terminal - to work, as carriage-return may no longer work in the abnormal state. - - -If you don't have the reset utility you can try: - - echo -e \\033c - -Otherwise try `clear; stty sane` (that will turn on echo also) or if all else -fails log out and back in. - -For more information on this subject generally see the [Linux keyboard and -console HOWTO][] (specifically [Linux keyboard and console HOWTO section 4][]). -Yes this works under macOS (at least it did for me). - -### Terminal size abuse - -So players 1, 2 and 3 are content with not meddling with the terminal sizes -outside what's actually possible with their screen. But then player 4 comes -along and thinks he/she will be clever and mess with the dimensions a bit. Let's -say they have a 13" monitor and they think it'd be funny to see how 997 lines -would work; they might try something like: - - LINES=997 ./prog - -What then? Well expect trouble; curses will not know any better and the max -number of lines will be 997. If they were to do: - - COLUMNS=997 ./prog - -Then they will see a different effect. Again the same reason: the player trying -to be funny. - -The game will try and be funny in return and you can expect it to not function -properly. Because that's what funny means in this context. - - -# Terminal limitations - -It'd be nice if curses could detect hitting more than one arrow key at the -same time so that diagonal directions would be possible but unfortunately it's -not (`cat -v` confirms this). Even if I were to define four extra keys what -would they be? And what about the head character? The HACKING file has -information on this. - -I also limit the terminal size to 10 lines/columns but most likely it would -require the cheat modes to win and in any event allowing certain low values -causes problems e.g. **LINES=6 COLUMNS=6** and **LINES=3 COLUMNS=3** caused some -nasty issues. - - -# Terminal colours - -N.B.: If all you're after is wanting to change the colours see the script -[snake-colours.sh][] instead. The rest of this file was mostly notes for the -judges; here I discuss the background, foreground as well as limitations (in -particular runtime limitations). - -## Curses background - -The documentation I have seen says that the background will always be black and -from a quick test this holds even with a terminal that has a white background. -There is a way to force the background to remain white though that's by way of -monochrome (so no colours); I discuss that and another related terminal specific -in the troubleshooting guide (there's another way but it would take me over the -iocccsize; I talk about it in the HACKING file). - -## Monochrome terminals - - -According to my tests for monochrome terminals the screen has the white and -black background; if terminal is configured to be black background white -foreground that is the order it is too. You can try that like: - - TERM=linux-m ./prog - -But I do not specifically check if the terminal has colours. Is there a curses -implementation that has a problem here? Below I explain what to comment out -should you need colours to be disabled but I personally have not run into any -problems even with monochrome terminals. Later I say what to do to remove colour -support in the source code itself. - -## Colour limitations - -According to X/Open Curses, Issue 7 (pg. 57): - - With init_pair() and pair_content(), the value of pair must be in a range - from 0 to and including COLOR_PAIRS-1. (There may be an - implementation-specific upper limit on the valid value of pair, but any such - limit is at least 63.) Valid values for f and b are the range from 0 to and - including COLORS-1. - -The manpage for `init_pair()` says this: - - These limits apply to color values and color pairs. Values outside these limits are not legal, and may result in a runtime error: - - [...] - - COLOR_PAIRS corresponds to the terminal database's max_pairs capability, (see terminfo(5)). - -Based on the two as well as the fact that monochrome terminals (I've tried under -both Linux and macOS) seem to work fine I believe I am fine here too. And to -confirm this I wrote the following code with the output below: - - #include - - int main() - { - initscr(); - start_color(); - printw("COLORS: %d\nCOLOR_PAIRS: %d\n", COLORS, COLOR_PAIRS); - getch(); - endwin(); - } - - TERM=linux-m ./colour_pairs - COLORS: 0 - COLOR_PAIRS: 0 - -Without specifying the TERM I get: - - COLORS: 256 - COLOR_PAIRS: 32767 - -In the case that colour is supported the standard states that there will be more -possible colour pairs than I have used so that's not a problem either. Perhaps -even it would only be a runtime error if the colours are supported and the -number is out of the range? To try and figure this out I did another test: - - int main() - { - initscr(); - start_color(); - - init_pair(COLOR_PAIRS+1, COLOR_WHITE, COLOR_GREEN); - bkgd(COLOR_PAIR(COLOR_PAIRS+1)); - init_pair(1, COLOR_WHITE, COLOR_BLACK); - attron(COLOR_PAIR(1)); - printw("COLORS: %d\nCOLOR_PAIRS: %d\n", COLORS, COLOR_PAIRS); - - getch(); - endwin(); - } - -Running it like - - TERM=linux-m ./colour_pairs - -I got the output as if colours weren't used: 0 and 0. With coloured terminals -however it was not visible. If I however comment out the call to `bkgd()` I get -instead: - - COLORS: 256 - COLOR_PAIRS: 32767 - -Which leads me to believe that if you try using a value outside the range it -won't work for a colour-capable terminal but it doesn't matter for monochrome -terminals one way or another. And since I don't go beyond the ranges (as I cited -earlier) there shouldn't be any problem here. - -## Disabling colour - -Nevertheless if it is a problem and you don't want to use a monochrome terminal -(or can't): - -First comment out the code that has `start_color();` and then add `return;` to the -top of the `C()` function (the rest of the code won't be reached). If necessary -you can also comment out the `init_pair()` calls. - -Perhaps you only need to add the `return;` to `C()`. My guess is that's the case -but I do not know for certain. - - -[Linux keyboard and console HOWTO]: https://www.tldp.org/HOWTO/Keyboard-and-Console-HOWTO.html -[Linux keyboard and console HOWTO section 4]: https://www.tldp.org/HOWTO/Keyboard-and-Console-HOWTO-4.html -[snake-colours.sh]: snake-colours.sh diff --git a/2020/ferguson1/troubleshooting.markdown b/2020/ferguson1/troubleshooting.markdown deleted file mode 100644 index ebabcdc7d..000000000 --- a/2020/ferguson1/troubleshooting.markdown +++ /dev/null @@ -1,423 +0,0 @@ -# Troubleshooting Snake - -In this file I have a list of a number of possible problems that could show up -under some situations as well as some things that might appear to be happening -or be a problem but actually are not. For each one I have included a summary, a -more thorough description and any resolutions (from easiest to hardest). For -anything that can cause more than one issue I will list those separately to keep -it as structured as possible/easiest to find. - - -- [The top wall isn't (all/some of the time) visible!](#topwall) -- [The snake isn't moving!](#notmoving) -- [I can't see the snake head (or other part of the snake)!](#invisiblehead) -- [I can't see the bug!](#invisiblebug) -- [The game froze!](#froze) -- [The game gets slower as the snake gets bigger!](#bigsnake) -- [I set SIZE=0 GROW=1, ate a bug but still only the head was visible! Why?](#headonly) -- [I like black text on a white background but even with a white background terminal it ends up black!](#monochrome) -- [The text is hard to see (not bright enough)!](#toodark) -- [Can I move the snake faster temporarily without having to modify the wait time?](#movefaster) -- [On environmental variables more generally and string to int conversions](#envvars) -- [Errors and error reporting](#errors) - -## The top wall isn't (all/some of the time) visible! - -Because the score line is dynamic in length depending on the terminal size it -can happen that the score/status wraps to the next line(s). This can be fixed by -modifying the status line (see [HACKING][]) but you shouldn't need it to be very -wide. - -* RESOLUTION #0: Use a bigger screen (it doesn't take that many columns) - - **Tip:** try running `make test` to get an estimate of the minimum number of columns - needed. This won't work right if you change the score line! - - Note that this utility is not perfect; I describe this elsewhere. Either way a - small screen will make it harder to play esp without the cheat modes enabled - (but they can still be fun with large screens). - - -* RESOLUTION #1: Open the [prog.c][] and change the line - - #define J Z(X:%d/%d Y:%d/%d S:%zu/%zu B:%zu\n),Y,y[-2],X, *y, A, g, c ); - - To be something like: - - #define J Z(B:%zu\n), c); - - Recompile and try again. That will then show only the number of bugs eaten. See - the HACKING file for other data you can include (though there are some - discrepancies here and there as you'll see). - - -## The snake isn't moving! - -Unless the game is paused this should not be happening. It can appear sometimes -that there isn't movement and sometimes it might have to take a moment to -'unwind' but as long as the head is moving then everything is fine. - -The tail might not always be moving which could make it seem like the snake -isn't moving but as long as the head is moving then the snake can be assumed to -be moving. - - -* RESOLUTION #0: Unless the game is paused as long as the snake head is moving -everything is fine even if it seems sometimes that the snake tail is still: this -is normal and there are a few conditions that this might especially show itself -e.g. when the snake is growing. - - If you want to see this in action try: - - SIZE=150 SHEDS=10 SHED=1 ./prog - - Once you start moving you'll see that the tail stays still for a while and - when it does move you'll see it doesn't move for long. - - Sometimes also it might appear that the movement is delayed but there are - numerous causes for this some of which are a busy computer but ultimately - it'll catch up; just give it the time it needs. - -* BOTTOM LINE: If you want more information then see the [bugs.md][] file. - - -## I can't see the snake head (or other part of the snake)! - -Possibly this could happen with too long a score line which I already discussed. -It also used to be possible during cannibal mode whilst cannibalising: you would -just have to continue moving on and you'd eventually find your head (assuming -the rest of the screen isn't covered with your body!). And what else could you -expect if you decided to shove your head up your .... ? :)) - -* RESOLUTION #0: Actually I thought it would look better that you could see the -head even when cannibalising so you should be able to lose your head and at the -same time see it! - -* RESOLUTION #1: If the terminal width is too small then it could be that the -score line is covering the wall, snake and even bug; I gave two resolutions to -that earlier in this file. To get a recommended number of columns use `make -test`. - - -## I can't see the bug! - -This has happened to me a number of times and early in development there were -times that I could swear I even saw it beyond the walls but this should not be a -problem now if it ever was. - -However I have made a lot of effort to think of situations that might make the -bug location calculation and placement be a problem and even did some debug -output and wrote another test program to calculate the maximum number of places -the bug could be put etc. - -One of the reasons I have the terminal dimensions have a minimum and also a -maximum has to do with the way the bug placement and max size of the snake (and -so arrays) are calculated: it could happen that because of it being unsigned -without there being restrictions it could end up being a bad value that causes -the bug placement loop to never end (in this case the snake would also stop -moving). - -* RESOLUTION #0: If evasion mode is enabled (default after 300 moves) just wait -it out and the bug will move to a new place and it might be easier to see in the -new place. - -* RESOLUTION #1: The terminal size is too small for the score line. See the -earlier problem for how to resolve this if that's the case. - -* RESOLUTION #2: Sometimes you have to look closely for it can be easy to miss. -If it's against the wall and the screen is [Snakey][] or you're tired or the colour -contrast isn't right for you or something like that it might be harder to see. -Try pausing the game (space) and scan the screen for it. If you really are -desperate copy and paste the entire file into a document and search for the bug -char (if it's an editor like vi/vim you will have to be sure to escape the char -since `*` is a regexp metacharacter). This has happened to me numerous times. - -* RESOLUTION #3: If the colour contrast is not good for your eyes use the -[snake-colours][] script to select a different colour scheme. - - -## The game froze! - -This should not be possible either but without certain safety checks in place I -have caused this (and I did this deliberately in development to find all the -conditions I could to resolve them). - -* RESOLUTION #0: If this does happen it likely is to do with placement of the -bug; that is why I have the max size of the snake capped a fair bit below the -maximum number of coordinates in the game field: because the bug isn't to be at -a place the snake already occupies. - -* RESOLUTION #1: If the system `rand()` implementation is really bad maybe it's -having a problem finding a location to place the bug. The best I can think of is -to try increasing the screen size or decrease the max size of the snake. - -* BOTTOM LINE: If the bug cannot find a place to be this will likely cause the -game to freeze because the game loops until it finds an available place for the -bug to be; if the pRNG is so bad wrt where the snake occupies it could -theoretically happen. If this happens you will have to kill the game by e.g. -ctrl-c. Try increasing the screen size and/or decreasing the max size of the -snake. See below for some tests. - - I removed caps in a test and then: - - GROW=1 COLUMNS=30 LINES=10 CANNIBAL=1 MAXSIZE=10000 ./prog - - It froze at this point: - - X:14/29 Y:5/9 S:323/10000 B:318 - - This is because there was only one place for the bug - where the snake head - was to be. With these parameters the limit is actually 189! This could - perhaps be increased but the calculation I have used is easy and although I - certainly could make it so it's one less than the max that takes more bytes - and I don't see it actually mattering. - - Another time I tried: - - LINES=6 COLUMNS=6 - - And it also froze (as noted I impose a minimum of >= 10 for both LINES and - COLUMNS). - - Ultimately I had to kill the program due to the game being stuck in the bug - placement routine. I could have put in a maximum number of loops but what - good would that do? It would use more bytes and what would it do anyway, - win? Well that's what the max size is for. - - -* TERMINAL SIZE TO MAX SNAKE SIZE CALCULATION ETC. - - For this information and more use the termcaps utility: - - make test - - An example: - - terminal supports cursor movement - terminal supports making cursor invisible - terminal supports bold - terminal supports colours - - terminal rows 42 (39 playable) - terminal cols 157 (155 playable) - - snake size: 997 (max size: 6006) - bugs: 199 (max size: 1201) - - at least 34 columns recommended for snake size 997 (is 157) - at least 37 columns recommended for snake size 6006 (is 157) - - No problems detected. - - Why are there only 39 playable lines for 42 rows? The first row is the score - line, the second is the top wall, the last is the bottom wall. For columns - you have the left and rightmost columns for the walls. - - The calculation for max size takes place after decrementing the max y/x; - then it's `(maxy - 2) * (maxx - 2)`; above that would be: - - (41 - 2) * (156 - 2) == 6006 - - If for some reason the game were to stall for this purpose you would have to - kill the game by e.g. ctrl-c since the bug placement is stuck in a loop. - This shouldn't happen though. - - -## The game gets slower as the snake gets bigger! - -Realistically the game shouldn't take that much memory but maybe in some very -busy systems it would have a problem. - -* RESOLUTION #0: If the rand() implementation is particularly poor then it might -be that the bug placement is taking more time. - -* RESOLUTION #1: It could also be the arrays being updated as the snake gets -bigger. - -* BOTTOM LINE: I find it unlikely that either of these happen and I don't know -what to say; you can try decreasing the max size to see if it doesn't reach this -problem. Otherwise it would be good to see if you can find a pattern: is it when -the snake is bigger i.e. the screen is rather fuller than when you first begin? -Or is the system rather busy? - - -## I set `SIZE=0 GROW=1`, ate a bug but still only the head was visible! Why? - -* RESOLUTION #0: The size includes the head but what should it show if the size -is 0? The head is the obvious choice so once you do get to size 1 you will still -only have the head. When you get to size 2 you'll have part of the body too. -Try: - - SIZE=1 GROW=1 - - And you will see that at one bug you'll have size two and just as if you had - two bugs with the previous invocation. - - BTW: When you are size 1 you can go back on yourself even if cannibalism is - disabled because there really isn't anything to go back through: it's just - you moving the head until you find a body to use! Once you have size >= 2 - then cannibalism mode counts. - -* BOTTOM LINE: In some senses SIZE=0 and SIZE=1 are the same thing. - - -## I like black text on a white background but even with a white background terminal it ends up black! - -This is a curses thing but fortunately it should be easy enough to solve. - -* RESOLUTION #0: Try setting the terminal to monochrome for the invocation of -the game. Depending on what is installed on your system you might not have a -monochrome terminal but you can certainly get them. In Red Hat systems these can -be found in the package ncurses-term but maybe that will vary on others. I don't -recall having to install it under macOS nor Fedora but those systems have GUIs -so that's probably why; in CentOS I did have to install it specially. - - The `-m` suffix to terminal names means monochrome. Under Linux this - terminal is under `/usr/share/terminfo/l/linux-m` and under macOS (Catalina - at least) it's at `/usr/share/terminfo/6c/linux-m`. - -* BOTTOM LINE: Try using a monochrome terminal for the game by e.g.: - - TERM=linux-m ./prog - - And then on your white background terminal the game should keep the - background white and the foreground black. This will of course mean you have - no colour but unfortunately changing the background colour of curses would - eat too many bytes in the entry (I cite the functions in the HACKING file). - - I discuss colours and terminals in much more detail in - [terminals.md][] ([terminals.html][]). - - -## The text is hard to see (not bright enough)! - -* RESOLUTION #0: Make sure that your terminal settings have for bold text a -brighter font. The way I have my terminal profiles set up is to use bright -colours for bold text. I specifically enable bold in the program so try and find -out if enabling brighter colours is possible. - - If it's already set at brighter colours I do not know what to say. Try - playing with the colours in your terminal, maybe the font size or various - other settings. Probably better to make a new profile for this first in case - you mess up your old one. FWIW I haven't had this problem on consoles only - and in my systems I have the option to enable brighter colours for bold - it - was only in testing that I came across this problem. - -## Can I move the snake faster temporarily without having to modify the wait time? - -Yes. The `getch()` call is non-blocking when the timeout value is positive so -it's up to that number of milliseconds. - -* RESOLUTION #0: All you need to do then is hold down the direction key (or for -that matter any other key that `getch()` will pick up on). You can also rapidly -push the key instead of holding it down. - -* BOTTOM LINE: Depending on the key repeat rate and/or how fast you repeatedly -hit the key you can have more control over the speed - as long as it is quicker -than the regular wait time. For negative wait time (drawing mode) you have to -hit a key every time to move because it is blocking. - - - -## On environmental variables more generally and string to int conversions - -What happens if you do: - - WALLS= ./prog - -Can you run into the wall since you didn't specify a value and the default is 1? -No. That's not the way it works. By saying WALLS= you have told the system that -there is that environmental variable. However the way the `strtoul()` functions -work is that if there's no valid input it returns 0. This can be detected but -it's wasteful (iocccsize wise) and I have never felt that having invalid input -for a number could have a better value than 0. - -If however you were to give valid input followed by invalid input the valid -input would be parsed. For example: - - WALLS=1test ./prog - -Would let you go through the walls. On the other hand - - WALLS=test1 ./prog - -Would not. Furthermore if you specify the same variable more than once it's the -last one that counts. For example: - - WALLS=0 WALLS=1 CANNIBAL=1 CANNIBAL=0 ./prog - -Would allow you to go through walls but you could not cannibalise. - -Also note that I specify base 0 which means that you can also have it in hex and -octal. These are all the same: - - SIZE=0x3 - SIZE=3 - SIZE=03 - -The way the functions work is if 0x (or 0X) it's considered hex; if prefixed -with a 0 it's octal and otherwise it's decimal. If you specify invalid data -(even if only at the beginning of the string) the functions return 0. For -example - - SIZE=0 - SIZE=f - SIZE=- - SIZE=f1 - -Are all equivalent. - -Observe that - - SIZE=08 - -Is invalid input because octal only has digits 0 - 7: the resulting size will be -0. - - -I discuss the signed/unsigned issue to do with curses and sizes in C being -unsigned in [bugs.md][] but probably there isn't anything more to say here. - -## Errors and error reporting - -If curses fails to initialise you should get an error that says 'curses error' -and you have every right to curse it to hell if that's what you so wish. -Apparently curses will report an error itself if that function fails but that -escaped my notice until later on so you might get two errors in that case. - -Assuming it succeeds the game obtains the maximum y/x of the terminal. The -screen size must be at least 10 lines/columns (though in code the number will -not be 10); if either is smaller you will get 'screen too small' and the game -will end (yes things that haven't begun can be ended). - -If calloc() fails to obtain the needed memory you'll see something like (here it -was before I added proper - to make the perfect and obvious pun - capsizing -so that by setting the size to -1 it went to the max unsigned value): - - $ MAXSIZE=-1 ./prog - memory error - X:0/157 Y:0/42 S:3/18446744073709551614 B:0 - - -BTW: There are two arrays that have to be **MAXSIZE** (technically + 1). - -Is it possible that some value specified by the user could mess this up? I do -not know but what I do know is that because it's unsigned it can't be negative; -if the max size is 0 then the array size will be 1 but it won't matter because -the snake size will be >= that max anyway. But here's a curious output: - - SIZE=1 MAXSIZE=0 ./prog - YOU WIN! - X:78/156 Y:20/41 S:0/0 B:0 - -Why does it show snake size as 0 when I specified 1? It's because the max size -is 0 and I limit the size to be no bigger than the max size. - - -[HACKING]: HACKING -[prog.c]: prog.c -[bugs.md]: bugs.md -[terminals.md]: terminals.md -[terminals.html]: terminals.html -[snake-colours]: snake-colours -[Snakey]: https://www.hp-lexicon.org/character/gaunt-family/morfin-gaunt/ diff --git a/2020/ferguson2/recode.markdown b/2020/ferguson2/recode.markdown deleted file mode 100644 index 6ab04699d..000000000 --- a/2020/ferguson2/recode.markdown +++ /dev/null @@ -1,564 +0,0 @@ -# The recode configurator documentation (and a fun way to explore my 2020 IOCCC Enigma simulator) - -N.B. At the end of this file I have given a fun exercise that can be used to -explore my Enigma simulator. It's especially worth doing if you you like -good chocolate and could use a delicious [Double-layered Chocolate Fudge Cake][]. - -Alternatively if you just want the recipe you could look at my Snake entry -'Don't tread on me award' since it's also there and not enciphered. - - -- [What is recode and why is it recommended?](#recode) -- [Options](#options) - * [Protecting against the same I/O file](#samefile) - * [File options parser limitation](#fileoptions) - * [-R option validation?](#validation) -- [Piping commands to recode](#pipingtorecode) -- [Piping recode to the Enigma simulator](#pipingtoenigma) -- [Manually test the recode program with the Enigma program](#manuallytest) -- [Only write config file](#onlyconfig) -- [Reusing a configuration](#reuseconfig) -- [Write config file, read data file and pass to Enigma simulator](#passtoenigma) -- [Piping directly to the Enigma code once (with randomised settings, saved)](#directlytoenigma) -- [Usage conclusion](#conclusion) -- [Example additions](#additions) -- [Example uses](#exampleuses) -- [Delicious Enigma: Double-layered Chocolate Fudge Cake](#cake) - -# What is recode and why is it recommended? - -To demonstrate how to and why you should use the recode program with the Enigma -simulator I have included some examples here as well as brief ('brief?!' I am sure -many would think but it's brief for me) commentary. - -It would not be possible to do all that the recode program does in my entry -itself but I wanted to demonstrate the features of my entry and this seemed the -best way to do that. It also enhances the use of the simulator to make it -reusable etc. - -The recode name idea is that it recodes the input (validating first) to be input -for the simulator itself. It can pseudo-randomly select all the settings, it can -write the settings to a file, read from input file (e.g. for the text to -encipher if piped to the simulator) etc. - -Basically it's a configurator for the Enigma simulator that makes it easier to -use the simulator as well as to reuse the configuration to decipher messages -that have been enciphered by those settings. But it also tries to make it look -more like the real thing in that it shows the actual rotors (labelling them with -the Roman numeral), the reflectors (labelling them with B, C and showing the -actual strings) etc. - -In this file I will show an example of each feature and then get to the -wonderful 'challenge' for everyone. - -# Options - - usage: ./recode [options] - -v display selected settings - -r pseudo-randomly select settings - -f read file for Enigma input - -R init settings from settings string or file - -o write settings to file - - - -`-r` randomises settings. It won't prompt you for settings but it will for input -if you don't give the option to read in a file for input. - -`-v` shows selected settings. For example combining `-r` and `-v`: - - $ echo IOCCC | ./recode -r -v - Ring 1: ( I) EKMFLGDQVZNTOWYHXUSPAIBRCJ - Ring 2: ( V) VZBRGITYUPSDNHLXAWMJQOFECK - Ring 3: ( IV) ESOVPZJAYQUIRHXLNFTGKDCMWB - Ring settings: WAR - Ring positions: XKE - Reflector: (B) YRUHQSLDPXNGOKMIEBFZCWVJAT - Plugboard pair #01: XE - Plugboard pair #02: HJ - Plugboard pair #03: SA - Plugboard pair #04: PY - Plugboard pair #05: CI - Plugboard pair #06: ZB - Plugboard pair #07: WD - Plugboard pair #08: QN - Plugboard pair #09: RU - Plugboard pair #10: VG - 1WX5AK4RE1XEHJSAPYCIZBWDQNRUVGIOCCC - -Though you won't find such strings in recode.c or prog.c. (You will find the -Roman numerals for the rotors and the 'A' and 'B' for the reflectors in recode.c -though). Yes the 'WAR' for the Ringstellung (Ring settings) is an amusing -coincidence *but it really did happen!* - -As for `-f` the contents would be output with the settings e.g. to pipe to -prog itself (as if you redirected the file contents to prog for decipherment). -If the file cannot be opened it sets back to stdin. - -`-o` outputs the settings to the file. If the file cannot be opened for writing -or is the same file name (see below) then only stdout will be used. Example of -randomised output going to a file: - - $ ./recode -r -oconfig - IOCCC - 3PP4UB5GG1ELGTKFCOPVBNUYXJAWSDIOCCC - $ cat config - 3PP4UB5GG1ELGTKFCOPVBNUYXJAWSD$ - -Notice that a terminating newline isn't added. Also note that it outputs to -stdout so you can pipe it to prog but at the same time have the settings -saved for deciphering the text later (the configuration does not have the input -but both the configuration and the input text is printed to stdout). - -If you want to initialise the settings from previously saved settings (as a -string or an input file that has the same format) use the `-R` option. -Again note that this does not validate input! Although it detects out of range -for the arrays it doesn't detect duplicates. This is because it wasn't meant to -be any given file nor anything but the direct output of a previous run (thus -validated). I'm not even guaranteeing that I got the array tests valid in -recode.c -R option! - - -## Protecting against the same I/O file - -Because some file systems are case-insensitive I have implemented a -case-insensitive strcmp(); if the input file name is the same as the output file -name I won't write to it. From a quick test this is important particularly if -neither file exist because if both calls to stat() fail then the inodes will be -the same (since what else could they be set to but the same initial value?). -This might be overprotective but I believe it better to prevent user error where -possible. Of course if neither file exists it might be argued that it's not -necessary to worry since you can't read from a file that doesn't exist. But then -what about if one exists but the other does not? - -## File options parser limitation - -Due to the way the parsing of options is done (no spaces between option char and -argument) shell expansion will not work right so for example doing: - - $ ./recode -R~/config - -Will not read the file 'config' in the home directory. I show another way to -achieve this later. - -This limitation also applies to the `-f` and `-o` options! - - -## -R option validation? - -Again the `-R` option does not validate input. For validation you have to run -the program interactively. - -# Piping commands to recode - -If stdin is not a tty (`!isatty(0)`) and neither randomised mode nor reading in -from a file is specified then you might just see something like: - - $ echo testing test tests | ./recode - 1AB2BB3CC1testing test tests - - $ echo testing test tests | ./recode | ./prog - 2>/dev/null - mfrwxan lzng ofnlu - - $ echo testing test tests | ./recode | ./prog - 2>/dev/null | ./recode - 1AB2BB3CC1mfrwxan lzng ofnlu - - $ echo testing test tests | ./recode | ./prog - 2>/dev/null | ./recode | ./prog - 2>/dev/null - testing test tests - - -In other words just the defaults. This was to simplify the problems with -prompting again for input if it's invalid (though as you'll see it does a good -job at this anyway). - - -# Piping recode to the Enigma simulator - -If you're using recode to recode the input so you don't have to type in the -input manually (if you randomise or read in from previously saved file no input -need be done at all) then make sure to redirect stderr to /dev/null. If you -don't you might see something like: - - $ ./recode -r | ./prog - - Ring 1: IOCCC - Setting 1: Position 1: Ring 2: Setting 2: Position 2: Ring 3: Setting 3: Position 3: Reflector: Plugboard pair 1: Plugboard pair 2: Plugboard pair 3: Plugboard pair 4: Plugboard pair 5: Plugboard pair 6: Plugboard pair 7: Plugboard pair 8: Plugboard pair 9: Plugboard pair 10: DEVIY - -What happened is that `Ring 1: ` is printed to stderr but since it's not been -redirected to /dev/null it prints this out; at this point however it prompts for -input. Why or how it shows the first prompt instead of later on I do not know. -Anyway at this point the simulator is already expecting input to en/decipher. -That's why after I sent EOF it printed out the remaining strings to stderr. - - - -# Manually test the recode program with the Enigma program - - $ cat input - IOCCC - - $ ./recode -finput - Rotors: - (1) I: EKMFLGDQVZNTOWYHXUSPAIBRCJ - (2) II: AJDKSIRUXBLHWTMCQGZNPYFVOE - (3) III: BDFHJLCPRTXVZNYEIWGAKMUSQO - (4) IV: ESOVPZJAYQUIRHXLNFTGKDCMWB - (5) V: VZBRGITYUPSDNHLXAWMJQOFECK - Input rotor 1 (1 - 5): 1 - Input rotor 2 (1 - 5): 2 - Input rotor 3 (1 - 5): 3 - Input setting 1 (A - Z): A - Input setting 2 (A - Z): A - Input setting 3 (A - Z): A - Input position 1 (A - Z): A - Input position 2 (A - Z): A - Input position 3 (A - Z): A - Reflector: - (1) (B) YRUHQSLDPXNGOKMIEBFZCWVJAT - (2) (C) FVPJIAOYEDRZXWGCTKUQSBNMHL - Input reflector (1 - 2): 2 - Input plugboard pair 1: AB - Input plugboard pair 2: CD - Input plugboard pair 3: .. - Input plugboard pair 4: EF - Input plugboard pair 5: GH - Input plugboard pair 6: IJ - Input plugboard pair 7: KL - Input plugboard pair 8: MN - Input plugboard pair 9: OP - Input plugboard pair 10: QR - 1AA2AA3AA2ABCD..EFGHIJKLMNOPQRIOCCC - -Notice that I skipped a plugboard pair; this can be done in a number of ways but -two dots is one. Notice also that it's in a different order than the simulator: -because with the size restrictions on my entry I saved bytes by using the same -loop where I could. It just felt more natural to have them grouped together in a -configurator for the simulator. - - $ echo 1AA2AA3AA2ABCD..EFGHIJKLMNOPQRIOCCC | ./prog - 2>/dev/null - HQNVO - $ echo 1AA2AA3AA2ABCD..EFGHIJKLMNOPQRIOCCC | ./prog - 2>/dev/null | echo 1AA2AA3AA2ABCD..EFGHIJKLMNOPQRHQNVO | ./prog - 2>/dev/null - IOCCC - - -As you can see the recode program prompts for the settings and outputs the -input for the Enigma program. I then copied/pasted that output to the program -and got the result HQNVO. - -Following this I piped the original output (of recode) to the simulator and then -piped that to the echo of the input settings (replacing the IOCCC with the -Enigma enciphered output) and then got the result IOCCC: the original input! -(This was before I implemented the `-R` option to greatly simplify things.) - -Of course the last command was redundant in that I didn't need to show the first -part again; I could have just done the latter part but I wanted to show the way -the pipeline works with settings etc. - -What happens if the plugboard pairs are the same but in a different order? It's -the same output: - - $ echo 1AA2AA3AA2AB..CDEFGHIJKLMNOPQRIOCCC|./prog - 2>/dev/null - HQNVO - -Also if you flip the order of the letters in the pair e.g. the AB as BA instead: - - $ echo 1AA2AA3AA2BA..CDEFGHIJKLMNOPQRIOCCC|./prog - 2>/dev/null - HQNVO - - -# Only write config file - - $ ./recode -v -oconfig -finput >/dev/null - Rotors: - (1) I: EKMFLGDQVZNTOWYHXUSPAIBRCJ - (2) II: AJDKSIRUXBLHWTMCQGZNPYFVOE - (3) III: BDFHJLCPRTXVZNYEIWGAKMUSQO - (4) IV: ESOVPZJAYQUIRHXLNFTGKDCMWB - (5) V: VZBRGITYUPSDNHLXAWMJQOFECK - Input rotor 1 (1 - 5): 1 - Input rotor 2 (1 - 5): 2 - Input rotor 3 (1 - 5): 3 - Input setting 1 (A - Z): A - Input setting 2 (A - Z): C - Input setting 3 (A - Z): E - Input position 1 (A - Z): B - Input position 2 (A - Z): D - Input position 3 (A - Z): F - Reflector: - (1) (B) YRUHQSLDPXNGOKMIEBFZCWVJAT - (2) (C) FVPJIAOYEDRZXWGCTKUQSBNMHL - Input reflector (1 - 2): 2 - Input plugboard pair 1: AB - Input plugboard pair 2: CD - Input plugboard pair 3: EF - Input plugboard pair 4: GHIJ - Input plugboard pair 5: IJ - Input plugboard pair 6: KL - Input plugboard pair 7: MN - Input plugboard pair 8: OP - Input plugboard pair 9: QR - Input plugboard pair 10: ST - Ring 1: ( I) EKMFLGDQVZNTOWYHXUSPAIBRCJ - Ring 2: ( II) AJDKSIRUXBLHWTMCQGZNPYFVOE - Ring 3: (III) BDFHJLCPRTXVZNYEIWGAKMUSQO - Ring settings: ACE - Ring positions: BDF - Reflector: (C) FVPJIAOYEDRZXWGCTKUQSBNMHL - Plugboard pair #01: AB - Plugboard pair #02: CD - Plugboard pair #03: EF - Plugboard pair #04: GH - Plugboard pair #05: IJ - Plugboard pair #06: KL - Plugboard pair #07: MN - Plugboard pair #08: OP - Plugboard pair #09: QR - Plugboard pair #10: ST - -Notice that I redirect stdout to /dev/null so that the config is only written to -the file config. But also notice that for plugboard pair 4 I typed in `GHIJ` and -it didn't report an error. Neither did it complain about `IJ` in the next pair. -This is because it consumes input overflow. - -It shouldn't be this way for the simulator though because that could cause -inconsistent number of characters expected before the text to encipher. - - - $ cat config - 1AB2CD3EF2ABCDEFGHIJKLMNOPQRST - - $ ( cat config ; cat input ) | ./prog - 2>/dev/null - LRAVE - - $ ( cat config ; cat input ) | ./prog - 2>/dev/null | ( cat config ; echo LRAVE ) | ./prog - 2>/dev/null - IOCCC - - -What if you don't want to worry about having to specify input but just have the -settings written? You can do that too by either sending a newline or else -directly type ctrl-d (or whatever you have EOF set to) or give it an empty file. - -The following are all equivalent in that they will write to a settings file (the -first one will print a newline with the echo but that won't matter): - - $ echo | ./recode -r -osettings - $ echo -n | ./recode -r -osettings - $ > empty - $ ./recode -r -osettings < empty - $ cat empty | ./recode -r -osettings - $ ./recode -r -osettings - ^D - -(Above ^D was not shown but I typed it.) The way it works: if nothing is read -the buffer remains NULL and so I don't print it but still do print the settings -file. Actually even if getdelim() fails you're supposed to free the buffer so I -set another variable to indicate if it should be printed. - -# Reusing a configuration - - $ echo -n|./recode -r -oconfig - 4UH5II1RJ1SPIFEJLYGZWRTBHVCAMN$ - - $ cat input.txt - IOCCC 2020 Enigma simulator example text file - - This is a sentence. - - This is another sentence. - - $ ( cat config ; cat input.txt ) | ./prog - 2>/dev/null > output - $ cat output - AAPRT 2020 Dkemny tpgnjsjai xpovrgl jvgs wxra - - Fytt ly m xyiilaqh. - - Ybbp ox dmuzerm oscwgytb. - $ ( cat config ; cat output ) | ./prog - 2>/dev/null > io - $ diff io input.txt - $ - -Alternatively you can just use the `-R` and `-f` options: - - $ ./recode -finput.txt -Rconfig | ./prog - 2>/dev/null > output2 - $ diff output output2 - $ - - -# Write config file, read data file and pass to Enigma simulator - - $ ./recode -finput.txt -oconfig | ./prog - 2>/dev/null > output - Rotors: - (1) I: EKMFLGDQVZNTOWYHXUSPAIBRCJ - (2) II: AJDKSIRUXBLHWTMCQGZNPYFVOE - (3) III: BDFHJLCPRTXVZNYEIWGAKMUSQO - (4) IV: ESOVPZJAYQUIRHXLNFTGKDCMWB - (5) V: VZBRGITYUPSDNHLXAWMJQOFECK - Input rotor 1 (1 - 5): 2 - Input rotor 2 (1 - 5): 5 - Input rotor 3 (1 - 5): 3 - Input setting 1 (A - Z): C - Input setting 2 (A - Z): O - Input setting 3 (A - Z): D - Input position 1 (A - Z): E - Input position 2 (A - Z): L - Input position 3 (A - Z): O - Reflector: - (1) (B) YRUHQSLDPXNGOKMIEBFZCWVJAT - (2) (C) FVPJIAOYEDRZXWGCTKUQSBNMHL - Input reflector (1 - 2): 2 - Input plugboard pair 1: TR - Input plugboard pair 2: SN - Input plugboard pair 3: OG - Input plugboard pair 4: ME - Input plugboard pair 5: XY - Input plugboard pair 6: WZ - Input plugboard pair 7: UV - Input plugboard pair 8: AB - Input plugboard pair 9: CD - Input plugboard pair 10: FI - - $ cat config - 2CE5OL3DO2TRSNOGMEXYWZUVABCDFI - -Besides a famous book that I happen to love (who doesn't? Ah, but what is it -Cody?) there's also a reference to code which I also love (just as all -participants of the IOCCC do). But what matters is the output: - - $ cat output - DBFAF 2020 Ittejg ltblwriac uqdfqvr dhqh ghhf - - Jsjy ce v pxdmullo. - - Sara tj rtaayak uxfhjcti. - - $ ./recode -Rconfig -foutput | ./prog - 2>/dev/null - IOCCC 2020 Enigma simulator example text file - - This is a sentence. - - This is another sentence. - - - -# Piping directly to the Enigma code once (with randomised settings, saved) - -This is like the above only randomised: - - $ ./recode -r -oconfig|./prog - 2>/dev/null - IOCCC - UGEVP - - -Note that I typed in IOCCC and sent EOF (ctrl-d); the settings were written to -the config file (for deciphering later) and then all of it was piped to the -Enigma entry resulting in the line below IOCCC. But let's verify it worked out -okay: - - $ cat config - 2BM4GJ3HK1LNCBXWRMGKSYIAZUTPVJ - -Without having to copy/paste intermediate results: - - $ echo IOCCC | ./recode -Rconfig | ./prog - 2>/dev/null | ./recode -Rconfig | ./prog - 2>/dev/null - IOCCC - -You can also use a string for the `-R` option. I'll use command substitution and -actual pasting to demonstrate: - - $ echo IOCCC | ./recode -R$(cat config) | ./prog - 2>/dev/null | ./recode -R2BM4GJ3HK1LNCBXWRMGKSYIAZUTPVJ | ./prog - 2>/dev/null - IOCCC - - -# Usage conclusion - -That should give you an idea of how to use the recode program. I leave it as -an exercise to the reader to implement additional features but here are some -ideas of what could be done and how this entry might be used (including that -delicious challenge). - -# Example additions - -* Add code (to prog.c) that tries to determine possible decipherments based on -a given crib (or set of cribs). - -# Example uses - -* Take an actual key sheet and message for that day and try and decipher the -message (might help if you know German!). I haven't actually done this for -reasons I explained in the remarks (including that I cannot easily verify one -particular as well as the fact that there were different procedures, different -machines etc. - plus my vision is not good enough for the quality of the -pictures). - -* Try making sense of a politician's speeches by putting them into the -simulator. It might not make much sense but can it honestly be that much worse ? -:) Actually yes it could be (and we don't need the Enigma helping them either) -and I can think of a whole host of other things more entertaining including -watching dry paint stay dry on a wall but why not poke fun at politicians -anyway? - -* Best idea: try the below experiment! - -# Delicious Enigma: Double-layered Chocolate Fudge Cake - -As I was making the above silly list something occurred to me. My other entries -this year include a recipe for a wonderful double-layered chocolate fudge cake -but why would it not be included in this one too? But then if that's the case -why on Earth should it be unscrambled? - -So this is what I offer: if you take one of the settings below and run the -simulator on the file [chocolate-cake.html][] you will have a wonderful -chocolate cake recipe (that even my late stepmum who didn't even like chocolate -loved - it's a speciality of my mum's and they were the best of friends too). If -you're lazy just go and look at my other entry. Then again if you're lazy you're -probably not reading this far! :) - -The settings is one of the below but a couple questions you might ask yourself: - -1. Are any invalid input? - -2. Is there some other kind of trickery involved here? - -I can think of the best way to answer these and I think I need not even mention -it. Have fun and enjoy! BTW: Depending on where you are in this world and how -warm it is the icing can be a bit of an issue - I give some ideas in the file on -what can be done for this. At worst though the icing won't come out as good as -it usually does. - - -Hints: - -1. It's not the middle one. - -2. Yes there are some things kind of spelt here. These include things about -cake and chocolate. Some things spelt mean nothing whatever and can be -considered coincidences. Some were randomised. - -3. There is an obscure thing about my entry that can help locate the proper key -of the 16 given below! The questions are what is it and is is it worth going -this way? (I think the second answer is 'no' but that's me.) - -4. The above points might or might not be relevant. What is relevant is one -will reveal a wonderful [Double-layered Chocolate Fudge Cake][] recipe: - - 142CHOCOLATESVCAKESTBJLKMUMVWX - 0EZ4PC1CC0PGTAQSOXWNMDRYKFUZEI - 3UY0XF4QG1GTJQLARPKHBEMCOFVUIN - 0WE1HK4PL0RVSGXFDKENZYTPCMAHUW - 2LV0KC1IE0HTFPCLWOJDBQIRVMEAGN - 1SS4QX2RY1SRAQGKUYHCBLPFJDVTNZ - 3IW1QR0TZ0GQEDWIYJUPMFZBRAONHS - 3OP1KN4MO0HPFZCEAKYQIRXGVMOTUW - 4OC2CC0AK1CODELUVRSNGKIZBJPTMY - 4MU0MS1CA1KELUVRSXTABJNOCDMQFG - 0MU4MC2HO1CLATEFBJMONPQRVWXYZD - 125CHOCOLATESEXCAKESJADFNOPQRS - 0CH3OC1OL1ATEMBJNOCDLSIFWHRVXY - 4MU1MS3CA0KECHOLATBJSNPXVUFGZY - 4VW0SL1YB0IYJSLBECKTNAXGPUVHRF - 0MC4OH2CO1LATEUMBJCDFGHINOPRQS - - -BTW: What's the middle of a list with even numbered items anyway? - -[chocolate-cake.html]: chocolate-cake.html -[Double-layered Chocolate Fudge Cake]: chocolate-cake.html diff --git a/years.html b/years.html index 352e2e024..bcdd22a65 100644 --- a/years.html +++ b/years.html @@ -124,16 +124,14 @@ a summary description of all of the winners.

  • HACKING
  • Makefile
  • bugs.html -
  • bugs.markdown -
  • bugs.text +
  • bugs.md
  • cake.jpg
  • cannibalism.log
  • chocolate-cake.html -
  • chocolate-cake.text +
  • chocolate-cake.md
  • crazy.log
  • gameplay.html -
  • gameplay.markdown -
  • gameplay.text +
  • gameplay.md
  • halfwidth_vs_fullwidth.png
  • README.md
  • ioccc-cake.jpg @@ -146,15 +144,12 @@ a summary description of all of the winners.

  • snake-colours.sh
  • snake.1
  • spoilers.html -
  • spoilers.markdown -
  • spoilers.text +
  • spoilers.md
  • termcaps.c
  • terminals.html -
  • terminals.markdown -
  • terminals.text +
  • terminals.md
  • troubleshooting.html -
  • troubleshooting.markdown -
  • troubleshooting.text +
  • troubleshooting.md

    ferguson2

    @@ -170,15 +165,13 @@ a summary description of all of the winners.

  • input.txt
  • ioccc-cake.jpg
  • obfuscation.key -
  • obfuscation.markdown -
  • obfuscation.text +
  • obfuscation.md
  • obfuscation.txt
  • prog.c
  • recode.1
  • recode.c
  • recode.html -
  • recode.markdown -
  • recode.text +
  • recode.md
  • try.this.txt