{
  "id": "P008",
  "slug": "number-guessing",
  "key": "P008-number-guessing",
  "title": "Number guessing",
  "summary": "Guess the number at three levels, with higher/lower hints that narrow to a range, a limited number of guesses and a best score per level; the secret numbers come from a random generator written in EML - from a text menu.",
  "entry": "main.eml",
  "ui": "terminal",
  "readme": "# P008 - Number guessing\n\nGuess the number the program is thinking of, at three levels: easy (1 to 20,\n5 guesses), normal (1 to 100, 7 guesses) and hard (1 to 1000, 10 guesses).\nEvery miss says higher or lower, and from the third miss on the program also\nnames the range the number must be in, narrowing it as you go. The fewest\nguesses of each level is kept as the best score. A text menu; the scores live\nwhile the program runs.\n\n- `main.eml` - the menu, the best scores and the seed\n- `game.eml` - one game: the levels, the guesses and their checks, the hints\n  and the narrowing range\n- `rng.eml` - random numbers written in EML: a linear congruential generator\n  (the constants of the C standard's example `rand()`), taking a number from\n  the high bits of its state, because the low bits of such a generator repeat\n  with short periods\n\nPython's `random` module is not used. A seed (2026 unless you set another)\nalways gives the same games on every machine, which is also what lets the\ninterpreter check a whole session against CPython byte for byte.\n\nWhat is checked: a guess must be a whole number in the level's range - one\nthat is not does not use up a guess; a seed must be a whole number.\n\nSessions: `sessions/basic.in` wins a normal game in five guesses with the\nrange hint appearing, an easy game in three, loses a normal game, wins a hard\ngame by halving, and shows the best scores; `sessions/bad-input.in` types an\nunknown choice, shows the empty best scores, types a seed that is a word,\nsets seed 7, types guesses that are words, 0, 21, empty and 5.5 (none of them\ncounts), wins, and then wins in a single guess.\n\nBuilt on the verified corpus case `mvp-number-guessing-game` (guesses\nanswered with higher or lower, and the number of attempts counted).\n",
  "modules": [
    {
      "name": "main.eml",
      "eml": "# P008 number guessing: pick a level and guess the number. The best score of\n# each level is kept while the program runs, and the seed of the random\n# generator can be changed - the same seed always gives the same games.\nimport game\nimport rng\n\ndef plural(n):\n    if n == 1:\n        return \"1 guess\"\n    return str(n) + \" guesses\"\n\ndef show_best(best):\n    \"\" ^0\n    \"-- best scores --\" ^0\n    for i in [0:len(game.levels) - 1]:\n        (\"  \" + (\"%-8s\" % game.levels[i][0])) => line\n        if best[i] == 0:\n            line + \"-\" => line\n        else:\n            line + plural(best[i]) => line\n        line ^0\n\n2026 => seed\nrng.Rng(seed) => r\n[0 for level in game.levels] => best\nTrue => running\nwhile running:\n    \"\" ^0\n    (\"== Number guessing (seed \" + str(seed) + \") ==\") ^0\n    \"1) easy 1-20  2) normal 1-100  3) hard 1-1000  4) best scores  5) new seed  6) quit\" ^0\n    game.trim(input(\"choice> \")) => choice\n    if choice == \"1\" or choice == \"2\" or choice == \"3\":\n        int(choice) - 1 => i\n        game.play(r, game.levels[i]) => used\n        if used > 0 and (best[i] == 0 or used < best[i]):\n            used => best[i]\n            (\"New best for \" + game.levels[i][0] + \": \" + plural(used) + \".\") ^0\n    elif choice == \"4\":\n        show_best(best)\n    elif choice == \"5\":\n        game.number(game.trim(input(\"seed> \"))) => s\n        if s < 0:\n            \"A seed is a whole number.\" ^0\n        else:\n            s => seed\n            rng.Rng(seed) => r\n            (\"Seed set to \" + str(seed) + \".\") ^0\n    elif choice == \"6\":\n        False => running\n    else:\n        \"Pick a number from 1 to 6.\" ^0\n\"Bye.\" ^0\n",
      "python": "import game\nimport rng\n\ndef plural(n):\n    if n == 1:\n        return \"1 guess\"\n    return str(n) + \" guesses\"\n\ndef show_best(best):\n    print(\"\")\n    print(\"-- best scores --\")\n    for i in range(0, len(game.levels)):\n        line = \"  \" + \"%-8s\" % game.levels[i][0]\n        if best[i] == 0:\n            line = line + \"-\"\n        else:\n            line = line + plural(best[i])\n        print(line)\n\nseed = 2026\nr = rng.Rng(seed)\nbest = [0 for level in game.levels]\nrunning = True\nwhile running:\n    print(\"\")\n    print(\"== Number guessing (seed \" + str(seed) + \") ==\")\n    print(\"1) easy 1-20  2) normal 1-100  3) hard 1-1000  4) best scores  5) new seed  6) quit\")\n    choice = game.trim(input(\"choice> \"))\n    if choice == \"1\" or choice == \"2\" or choice == \"3\":\n        i = int(choice) - 1\n        used = game.play(r, game.levels[i])\n        if used > 0 and (best[i] == 0 or used < best[i]):\n            best[i] = used\n            print(\"New best for \" + game.levels[i][0] + \": \" + plural(used) + \".\")\n    elif choice == \"4\":\n        show_best(best)\n    elif choice == \"5\":\n        s = game.number(game.trim(input(\"seed> \")))\n        if s < 0:\n            print(\"A seed is a whole number.\")\n        else:\n            seed = s\n            r = rng.Rng(seed)\n            print(\"Seed set to \" + str(seed) + \".\")\n    elif choice == \"6\":\n        running = False\n    else:\n        print(\"Pick a number from 1 to 6.\")\nprint(\"Bye.\")\n"
    },
    {
      "name": "game.eml",
      "eml": "# P008 number guessing - one game. The player has a limited number of\n# guesses; a guess that is not a whole number in range does not use one.\n# After every miss the game says higher or lower, and from the third miss on\n# it also names the range the number must be in, which it keeps narrowing.\n\n# [name, highest number, guesses allowed]\n[\n    [\"easy\", 20, 5],\n    [\"normal\", 100, 7],\n    [\"hard\", 1000, 10],\n] => levels\n\ndef trim(s):\n    0 => i\n    len(s) => j\n    while i < j and s[i] == \" \":\n        i + 1 => i\n    while j > i and s[j - 1] == \" \":\n        j - 1 => j\n    return s[i:j]\n\ndef number(s):\n    # The value of s if it is digits only (at least one), otherwise -1.\n    if s == \"\":\n        return -1\n    0 => n\n    for c in s:\n        if not (c in \"0123456789\"):\n            return -1\n        n * 10 + int(c) => n\n    return n\n\ndef play(rng, level):\n    # One game at this level. Returns the number of guesses it took, or 0 if\n    # the guesses ran out.\n    level[1] => top\n    level[2] => allowed\n    1 + rng.below(top) => secret\n    (\"I am thinking of a number from 1 to \" + str(top) + \". You have \" + str(allowed) + \" guesses.\") ^0\n    1 => low\n    top => high\n    0 => used\n    while used < allowed:\n        number(trim(input(\"guess \" + str(used + 1) + \"/\" + str(allowed) + \"> \"))) => g\n        if g < 1 or g > top:\n            (\"Guess a whole number from 1 to \" + str(top) + \".\") ^0\n        else:\n            used + 1 => used\n            if g == secret:\n                if used == 1:\n                    (\"Right! \" + str(secret) + \" in 1 guess.\") ^0\n                else:\n                    (\"Right! \" + str(secret) + \" in \" + str(used) + \" guesses.\") ^0\n                return used\n            if g < secret:\n                \"Too low.\" => hint\n                if g + 1 > low:\n                    g + 1 => low\n            else:\n                \"Too high.\" => hint\n                if g - 1 < high:\n                    g - 1 => high\n            if used >= 3:\n                hint + \" It is between \" + str(low) + \" and \" + str(high) + \".\" => hint\n            hint ^0\n    (\"Out of guesses - it was \" + str(secret) + \".\") ^0\n    return 0\n",
      "python": "levels = [[\"easy\", 20, 5], [\"normal\", 100, 7], [\"hard\", 1000, 10]]\n\ndef trim(s):\n    i = 0\n    j = len(s)\n    while i < j and s[i] == \" \":\n        i = i + 1\n    while j > i and s[j - 1] == \" \":\n        j = j - 1\n    return s[i:j]\n\ndef number(s):\n    if s == \"\":\n        return -1\n    n = 0\n    for c in s:\n        if not c in \"0123456789\":\n            return -1\n        n = n * 10 + int(c)\n    return n\n\ndef play(rng, level):\n    top = level[1]\n    allowed = level[2]\n    secret = 1 + rng.below(top)\n    print(\"I am thinking of a number from 1 to \" + str(top) + \". You have \" + str(allowed) + \" guesses.\")\n    low = 1\n    high = top\n    used = 0\n    while used < allowed:\n        g = number(trim(input(\"guess \" + str(used + 1) + \"/\" + str(allowed) + \"> \")))\n        if g < 1 or g > top:\n            print(\"Guess a whole number from 1 to \" + str(top) + \".\")\n        else:\n            used = used + 1\n            if g == secret:\n                if used == 1:\n                    print(\"Right! \" + str(secret) + \" in 1 guess.\")\n                else:\n                    print(\"Right! \" + str(secret) + \" in \" + str(used) + \" guesses.\")\n                return used\n            if g < secret:\n                hint = \"Too low.\"\n                if g + 1 > low:\n                    low = g + 1\n            else:\n                hint = \"Too high.\"\n                if g - 1 < high:\n                    high = g - 1\n            if used >= 3:\n                hint = hint + \" It is between \" + str(low) + \" and \" + str(high) + \".\"\n            print(hint)\n    print(\"Out of guesses - it was \" + str(secret) + \".\")\n    return 0\n"
    },
    {
      "name": "rng.eml",
      "eml": "# P008 number guessing - random numbers written in EML: a linear\n# congruential generator with the constants of the C standard's example\n# rand(). Python's random module is not used, so a seed gives the same games\n# on every machine, and the interpreter can check a whole session byte for\n# byte.\n\nclass Rng:\n    def __init__(self, seed):\n        seed % 2147483648 => self.state\n\n    def step(self):\n        (1103515245 * self.state + 12345) % 2147483648 => self.state\n        return self.state\n\n    def below(self, n):\n        # A number from 0 to n - 1, taken from the high bits of the state: the\n        # low bits of this generator repeat with short periods. Dividing by\n        # 65536 is exact in a float for a state below 2^31.\n        return int(self.step() / 65536) % n\n",
      "python": "class Rng:\n    def __init__(self, seed):\n        self.state = seed % 2147483648\n    def step(self):\n        self.state = (1103515245 * self.state + 12345) % 2147483648\n        return self.state\n    def below(self, n):\n        return int(self.step() / 65536) % n\n"
    }
  ],
  "sessions": [
    {
      "name": "bad-input",
      "input": "9\n4\n5\nabc\n5\n7\n1\nabc\n0\n21\n\n5.5\n10\n3\n5\n1\n7\n4\n6\n",
      "screen": "\n== Number guessing (seed 2026) ==\n1) easy 1-20  2) normal 1-100  3) hard 1-1000  4) best scores  5) new seed  6) quit\nchoice> 9\nPick a number from 1 to 6.\n\n== Number guessing (seed 2026) ==\n1) easy 1-20  2) normal 1-100  3) hard 1-1000  4) best scores  5) new seed  6) quit\nchoice> 4\n\n-- best scores --\n  easy    -\n  normal  -\n  hard    -\n\n== Number guessing (seed 2026) ==\n1) easy 1-20  2) normal 1-100  3) hard 1-1000  4) best scores  5) new seed  6) quit\nchoice> 5\nseed> abc\nA seed is a whole number.\n\n== Number guessing (seed 2026) ==\n1) easy 1-20  2) normal 1-100  3) hard 1-1000  4) best scores  5) new seed  6) quit\nchoice> 5\nseed> 7\nSeed set to 7.\n\n== Number guessing (seed 7) ==\n1) easy 1-20  2) normal 1-100  3) hard 1-1000  4) best scores  5) new seed  6) quit\nchoice> 1\nI am thinking of a number from 1 to 20. You have 5 guesses.\nguess 1/5> abc\nGuess a whole number from 1 to 20.\nguess 1/5> 0\nGuess a whole number from 1 to 20.\nguess 1/5> 21\nGuess a whole number from 1 to 20.\nguess 1/5> \nGuess a whole number from 1 to 20.\nguess 1/5> 5.5\nGuess a whole number from 1 to 20.\nguess 1/5> 10\nToo high.\nguess 2/5> 3\nToo low.\nguess 3/5> 5\nRight! 5 in 3 guesses.\nNew best for easy: 3 guesses.\n\n== Number guessing (seed 7) ==\n1) easy 1-20  2) normal 1-100  3) hard 1-1000  4) best scores  5) new seed  6) quit\nchoice> 1\nI am thinking of a number from 1 to 20. You have 5 guesses.\nguess 1/5> 7\nRight! 7 in 1 guess.\nNew best for easy: 1 guess.\n\n== Number guessing (seed 7) ==\n1) easy 1-20  2) normal 1-100  3) hard 1-1000  4) best scores  5) new seed  6) quit\nchoice> 4\n\n-- best scores --\n  easy    1 guess\n  normal  -\n  hard    -\n\n== Number guessing (seed 7) ==\n1) easy 1-20  2) normal 1-100  3) hard 1-1000  4) best scores  5) new seed  6) quit\nchoice> 6\nBye.\n",
      "interpreter": "equal"
    },
    {
      "name": "basic",
      "input": "2\n50\n25\n12\n18\n21\n1\n10\n15\n19\n2\n10\n20\n30\n40\n50\n60\n70\n3\n500\n750\n625\n562\n593\n577\n585\n581\n583\n4\n6\n",
      "screen": "\n== Number guessing (seed 2026) ==\n1) easy 1-20  2) normal 1-100  3) hard 1-1000  4) best scores  5) new seed  6) quit\nchoice> 2\nI am thinking of a number from 1 to 100. You have 7 guesses.\nguess 1/7> 50\nToo high.\nguess 2/7> 25\nToo high.\nguess 3/7> 12\nToo low. It is between 13 and 24.\nguess 4/7> 18\nToo low. It is between 19 and 24.\nguess 5/7> 21\nRight! 21 in 5 guesses.\nNew best for normal: 5 guesses.\n\n== Number guessing (seed 2026) ==\n1) easy 1-20  2) normal 1-100  3) hard 1-1000  4) best scores  5) new seed  6) quit\nchoice> 1\nI am thinking of a number from 1 to 20. You have 5 guesses.\nguess 1/5> 10\nToo low.\nguess 2/5> 15\nToo low.\nguess 3/5> 19\nRight! 19 in 3 guesses.\nNew best for easy: 3 guesses.\n\n== Number guessing (seed 2026) ==\n1) easy 1-20  2) normal 1-100  3) hard 1-1000  4) best scores  5) new seed  6) quit\nchoice> 2\nI am thinking of a number from 1 to 100. You have 7 guesses.\nguess 1/7> 10\nToo low.\nguess 2/7> 20\nToo low.\nguess 3/7> 30\nToo low. It is between 31 and 100.\nguess 4/7> 40\nToo low. It is between 41 and 100.\nguess 5/7> 50\nToo low. It is between 51 and 100.\nguess 6/7> 60\nToo low. It is between 61 and 100.\nguess 7/7> 70\nToo low. It is between 71 and 100.\nOut of guesses - it was 88.\n\n== Number guessing (seed 2026) ==\n1) easy 1-20  2) normal 1-100  3) hard 1-1000  4) best scores  5) new seed  6) quit\nchoice> 3\nI am thinking of a number from 1 to 1000. You have 10 guesses.\nguess 1/10> 500\nToo low.\nguess 2/10> 750\nToo high.\nguess 3/10> 625\nToo high. It is between 501 and 624.\nguess 4/10> 562\nToo low. It is between 563 and 624.\nguess 5/10> 593\nToo high. It is between 563 and 592.\nguess 6/10> 577\nToo low. It is between 578 and 592.\nguess 7/10> 585\nToo high. It is between 578 and 584.\nguess 8/10> 581\nToo low. It is between 582 and 584.\nguess 9/10> 583\nRight! 583 in 9 guesses.\nNew best for hard: 9 guesses.\n\n== Number guessing (seed 2026) ==\n1) easy 1-20  2) normal 1-100  3) hard 1-1000  4) best scores  5) new seed  6) quit\nchoice> 4\n\n-- best scores --\n  easy    3 guesses\n  normal  5 guesses\n  hard    9 guesses\n\n== Number guessing (seed 2026) ==\n1) easy 1-20  2) normal 1-100  3) hard 1-1000  4) best scores  5) new seed  6) quit\nchoice> 6\nBye.\n",
      "interpreter": "equal"
    }
  ],
  "builtOn": [
    {
      "slug": "mvp-number-guessing-game",
      "caseId": "006-mvp-number-guessing-game",
      "title": "MVP proof: a second real, recognizable program ported to EML"
    }
  ],
  "updated": "2026-09-29"
}
