Nim game

From Rosetta Code


Nim game is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

Nim is a simple game where the second player - if they know the trick - will always win.

The game has only 3 rules.

  • start with 12 tokens
  • each player takes 1, 2, or 3 tokens in turn
  • the player who takes the last token wins.


To win every time, the second player simply takes 4 minus the number the first player took. So if the first player takes 1, the second takes 3 - if the first player takes 2, the second should take 2 - and if the first player takes 3, the second player will take 1.


Task

Design a simple Nim game where the human player goes first, and the computer always wins. The game should enforce the rules.

AsciiDots

<lang AsciiDots> %$MXTRL .-$"Nim Dots"-$""-

                                                 /$_"Number must be "\
            T                               /----~------\            |
            *M                          /---+-*-[o]     |            |
         R [-]\                        .>#3-+[>][<]-1#<.|            |
    .-#12>--^ \"stod "$-#_$-" ekat uoY"_$---/ \--*----*-/            |

.>$_"How many dots would you like to take"---#?---/ |

\X                                     X---------<".3 dna 1 neewteb"$/
           /-----*L                              |
          [-]--\ R                               |
           |   *-$_"Computer takes "-$_#-$" dots"/
         M-*#4[%]
           \---/ 
           
                 /----------------$"computer wins!"-&
             /---~--
             *#0[=]

L-------------*---*>$_#-$" dots remaining."-$""

                  T

</lang>

Output:
Nim Dots

How many dots would you like to take?: 3
You take 3 dots
9 dots remaining.

Computer takes 1 dots
8 dots remaining.

How many dots would you like to take?: 1
You take 1 dots
7 dots remaining.

Computer takes 3 dots
4 dots remaining.

How many dots would you like to take?: 2
You take 2 dots
2 dots remaining.

Computer takes 2 dots
0 dots remaining.

computer wins!

BlooP

Bloop has no input capabilites, so the game is defined as a procedure, called with 3 numbers (since the game will last only 3 rounds anyhow). The procedure can be called with more numbers - extra inputs are ignored in most implementations I have found. Since there is no easy way to get more inputs, any incorrect inputs are converted to correct ones. <lang BlooP> DEFINE PROCEDURE DIVIDE [A,B]: BLOCK 0: BEGIN

 IF A < B, THEN:
   QUIT BLOCK 0;
 CELL(0) <= 1;
 OUTPUT <= 1;
 LOOP AT MOST A TIMES:
 BLOCK 2: BEGIN
   IF OUTPUT * B = A, THEN:
   QUIT BLOCK 0;
   OUTPUT <= OUTPUT + 1;
   IF OUTPUT * B > A, THEN:
   BLOCK 3: BEGIN
     OUTPUT <= CELL(0);
     QUIT BLOCK 0;
   BLOCK 3: END;
   CELL(0) <= OUTPUT;
 BLOCK 2: END;

BLOCK 0: END.

DEFINE PROCEDURE MINUS [A,B]: BLOCK 0: BEGIN

 IF A < B, THEN:
   QUIT BLOCK 0;
 LOOP AT MOST A TIMES:
 BLOCK 1: BEGIN
   IF OUTPUT + B = A, THEN:
     QUIT BLOCK 0;
   OUTPUT <= OUTPUT + 1;
 BLOCK 1: END;

BLOCK 0: END.

DEFINE PROCEDURE MODULUS [A,B]: BLOCK 0: BEGIN

 CELL(0) <= DIVIDE[A,B];
 OUTPUT <= MINUS[A,CELL(0) * B];

BLOCK 0: END.

DEFINE PROCEDURE PLAYER_TURN [TOKENS_LEFT, TAKE]: BLOCK 0: BEGIN

 CELL(0) <= TAKE;
 IF TAKE > 3, THEN:
 BLOCK 1: BEGIN
   CELL(0) <= MODULUS [TAKE, 3] + 1;
   PRINT ['take must be between 1 and 3. setting take to ', CELL(0), '.'];
 BLOCK 1: END;
 IF TAKE < 1, THEN:
 BLOCK 2: BEGIN
   CELL(0) <= 1;
   PRINT ['take must be between 1 and 3. setting take to 1.'];
 BLOCK 2: END;
 OUTPUT <= MINUS [TOKENS_LEFT, CELL(0)];
 PRINT ['player takes ', CELL(0), ' tokens.'];
 PRINT ['tokens remaining: ', OUTPUT];
 PRINT [];

BLOCK 0: END.

DEFINE PROCEDURE COMPUTER_TURN [TOKENS_LEFT]: BLOCK 0: BEGIN

 CELL(0) <= MODULUS [TOKENS_LEFT, 4];
 OUTPUT <= MINUS [TOKENS_LEFT, CELL(0)];
 PRINT ['computer takes ', CELL(0), ' tokens.'];
 PRINT ['tokens remaining: ', OUTPUT];
 PRINT [];

BLOCK 0: END.

DEFINE PROCEDURE PLAY_GAME [FST, SEC, THD]: BLOCK 0: BEGIN

 CELL(0) <= FST;
 CELL(1) <= SEC;
 CELL(2) <= THD;
 OUTPUT <= 12;
 LOOP 3 TIMES:
 BLOCK 1: BEGIN
   OUTPUT <= PLAYER_TURN [OUTPUT, CELL(0)];
   CELL(0) <= CELL(1);
   CELL(1) <= CELL(2);
   OUTPUT <= COMPUTER_TURN [OUTPUT];
 BLOCK 1: END;
 PRINT ['computer wins!'];

BLOCK 0: END.

PLAY_GAME [1,2,3]; </lang>

Output:

Sample game:

 > PLAYER TAKES 1 TOKENS.
 > TOKENS REMAINING: 11
 > 
 > COMPUTER TAKES 3 TOKENS.
 > TOKENS REMAINING: 8
 > 
 > TAKE MUST BE BETWEEN 1 AND 3. SETTING TAKE TO 2.
 > PLAYER TAKES 2 TOKENS.
 > TOKENS REMAINING: 6
 > 
 > COMPUTER TAKES 2 TOKENS.
 > TOKENS REMAINING: 4
 > 
 > PLAYER TAKES 3 TOKENS.
 > TOKENS REMAINING: 1
 > 
 > COMPUTER TAKES 1 TOKENS.
 > TOKENS REMAINING: 0
 > 
 > COMPUTER WINS!
=> 0

C++

Translation of: Go

<lang cpp>#include <iostream>

  1. include <limits>

using namespace std;

void showTokens(int tokens) {

   cout << "Tokens remaining " << tokens << endl << endl;

}

int main() {

   int tokens = 12;
   while (true) {
       showTokens(tokens);
       cout << "  How many tokens 1, 2 or 3? ";
       int t;
       cin >> t;
       if (cin.fail()) {
           cin.clear();
           cin.ignore(numeric_limits<streamsize>::max(), '\n');
           cout << endl << "Invalid input, try again." << endl << endl;
       } else if (t < 1 || t > 3) {
           cout << endl << "Must be a number between 1 and 3, try again." << endl << endl;
       } else {
           int ct = 4 - t;
           string s  = (ct > 1) ? "s" : "";
           cout << "  Computer takes " << ct << " token" << s << endl << endl;
           tokens -= 4;
       }
       if (tokens == 0) {
           showTokens(0);
           cout << "  Computer wins!" << endl;
           return 0;
       }
   }

}</lang>

Output:

Sample game:

Tokens remaining 12

  How many tokens 1, 2 or 3? nim

Invalid input, try again.

Tokens remaining 12

  How many tokens 1, 2 or 3? 1
  Computer takes 3 tokens

Tokens remaining 8

  How many tokens 1, 2 or 3? 0

Must be a number between 1 and 3, try again.

Tokens remaining 8

  How many tokens 1, 2 or 3? 2
  Computer takes 2 tokens

Tokens remaining 4

  How many tokens 1, 2 or 3? 3
  Computer takes 1 token

Tokens remaining 0

  Computer wins!

Common Lisp

<lang lisp> (defun pturn (curTokens) (write-string "How many tokens would you like to take?: ") (setq ans (read)) (setq tokensRemaining (- curTokens ans)) (format t "You take ~D tokens~%" ans) (printRemaining tokensRemaining) tokensRemaining)

(defun cturn (curTokens) (setq take (mod curTokens 4)) (setq tokensRemaining (- curTokens take)) (format t "Computer takes ~D tokens~%" take) (printRemaining tokensRemaining) tokensRemaining)

(defun printRemaining (remaining) (format t "~D tokens remaining~%~%" remaining))


(format t "LISP Nim~%~%") (setq tok 12) (loop (setq tok (pturn tok)) (setq tok (cturn tok)) (if (<= tok 0) (return))) (write-string "Computer wins!") </lang>

Output:
LISP Nim

How many tokens would you like to take?: 2
You take 2 tokens
10 tokens remaining

Computer takes 2 tokens
8 tokens remaining

How many tokens would you like to take?: 1
You take 1 tokens
7 tokens remaining

Computer takes 3 tokens
4 tokens remaining

How many tokens would you like to take?: 3
You take 3 tokens
1 tokens remaining

Computer takes 1 tokens
0 tokens remaining

Computer wins!

Go

<lang go>package main

import (

   "bufio"
   "fmt"
   "os"
   "strconv"

)

func showTokens(tokens int) {

   fmt.Println("Tokens remaining", tokens, "\n")    

}

func main() {

   tokens := 12
   scanner := bufio.NewScanner(os.Stdin)
   for {
       showTokens(tokens)
       fmt.Print("  How many tokens 1, 2 or 3? ")
       scanner.Scan()
       if scerr := scanner.Err(); scerr != nil {
           fmt.Println("Error reading standard input:", scerr)
           return
       }
       t, err := strconv.Atoi(scanner.Text())
       if err != nil || t < 1 || t > 3 {
           fmt.Println("\nMust be a number between 1 and 3, try again.\n")
       } else {
           ct := 4 - t
           s := "s"
           if ct == 1 {
               s = ""
           }
           fmt.Print("  Computer takes ", ct, " token", s, "\n\n")
           tokens -= 4
       }
       if tokens == 0 {
           showTokens(0)
           fmt.Println("  Computer wins!")
           return
       }
   }

}</lang>

Output:

Sample game:

Tokens remaining 12 

  How many tokens 1, 2 or 3? 2
  Computer takes 2 tokens

Tokens remaining 8 

  How many tokens 1, 2 or 3? 4

Must be a number between 1 and 3, try again.

Tokens remaining 8 

  How many tokens 1, 2 or 3? 1
  Computer takes 3 tokens

Tokens remaining 4 

  How many tokens 1, 2 or 3? 3
  Computer takes 1 token

Tokens remaining 0 

  Computer wins!

Kotlin

Translation of: Go

<lang scala>// Version 1.3.21

fun showTokens(tokens: Int) {

   println("Tokens remaining $tokens\n")

}

fun main() {

   var tokens = 12
   while (true) {
       showTokens(tokens)
       print("  How many tokens 1, 2 or 3? ")
       var t = readLine()!!.toIntOrNull()
       if (t == null || t < 1 || t > 3) {
           println("\nMust be a number between 1 and 3, try again.\n")
       } else {
           var ct = 4 - t
           var s = if (ct > 1) "s" else ""
           println("  Computer takes $ct token$s\n")
           tokens -= 4
       }
       if (tokens == 0) {
           showTokens(0)
           println("  Computer wins!")
           return
       }
   }

}</lang>

Output:

Sample game:

Tokens remaining 12

  How many tokens 1, 2 or 3? 3
  Computer takes 1 token

Tokens remaining 8

  How many tokens 1, 2 or 3? nim

Must be a number between 1 and 3, try again.

Tokens remaining 8

  How many tokens 1, 2 or 3? 2
  Computer takes 2 tokens

Tokens remaining 4

  How many tokens 1, 2 or 3? 1
  Computer takes 3 tokens

Tokens remaining 0

  Computer wins!

Perl 6

Works with: Rakudo version 2019.03

<lang perl6>say my $tokens = 12, " tokens remaining.\n";

while my $player = prompt "How many tokens do you want to remove; 1, 2 or 3? : " {

   say "Nice try. $tokens tokens remaining.\n" and
   next unless $player eq any <1 2 3>;
   $tokens -= 4;
   say "Computer takes {4 - $player}.\n$tokens tokens remaining.\n";
   say "Computer wins." and last if $tokens <= 0;

}</lang>

Sample output:
12 tokens remaining.

How many tokens do you want to remove; 1, 2 or 3? : 3
Computer takes 1.
8 tokens remaining.

How many tokens do you want to remove; 1, 2 or 3? : 6
Nice try. 8 tokens remaining.

How many tokens do you want to remove; 1, 2 or 3? : G
Nice try. 8 tokens remaining.

How many tokens do you want to remove; 1, 2 or 3? : 2
Computer takes 2.
4 tokens remaining.

How many tokens do you want to remove; 1, 2 or 3? : 1
Computer takes 3.
0 tokens remaining.

Computer wins.

Python

Works on Python 3 <lang Python> print("Py Nim\n")

def getTokens(curTokens): global tokens

print("How many tokens would you like to take? ", end=) take = int(input())

if (take < 1 or take > 3): print("Number must be between 1 and 3.\n") getTokens(curTokens) return

tokens = curTokens - take print(f'You take {take} tokens.') print(f'{tokens} tokens remaining.\n')

def compTurn(curTokens): global tokens

take = curTokens % 4 tokens = curTokens - take print (f'Computer takes {take} tokens.') print (f'{tokens} tokens remaining.\n')


tokens = 12 while (tokens > 0): getTokens(tokens) compTurn(tokens)

print("Computer wins!") </lang>

Output:
Py Nim

How many tokens would you like to take? 2
You take 2 tokens.
10 tokens remaining.

Computer takes 2 tokens.
8 tokens remaining.

How many tokens would you like to take? 1
You take 1 tokens.
7 tokens remaining.

Computer takes 3 tokens.
4 tokens remaining.

How many tokens would you like to take? 3
You take 3 tokens.
1 tokens remaining.

Computer takes 1 tokens.
0 tokens remaining.

Computer wins!

REXX

Programming notes:   extra error checking was done with specific informative error messages.   Also included was a method of quitting the game.   The number of (starting) tokens   (the pot)   can be specified on the command line,   the default is   12. <lang rexx>/*REXX program plays the NIM game with a human opponent; the pot size can be specified. */ pad= copies('─', 8) /*eyecatcher literal used in messages. */ parse arg pot _ . 1 __ /*obtain optional argument from the CL.*/ if pot== | pot=="," then pot= 12 /*Not specified? Then use the default.*/ if _\== then do; call ser "Too many arguments entered: " __; exit 13; end if \isNum(pot) then do; call ser "argument isn't numeric: " pot; exit 13; end if \isInt(pot) then do; call ser "argument isn't an integer: " pot; exit 13; end if pot<4 then do; call ser "The pot number is too small: " pot; exit 13; end if pot>100 then do; call ser "The pot number is too large: " pot; exit 13; end pot= pot/1 /*normalize the pot (number). */

    do forever;   call show pot
           do  until ok;                   ok=1;               say
           say pad "How many tokens do you want to take away  (1, 2, or 3)    (or QUIT)?"
           parse pull t _ . 1 q 1 __;      upper q;            say
           if abbrev('QUIT',q,1)  then do;  say pad 'Quitting.';         exit 1;      end
           if t=                then call ser "No arguments entered."
           if _\==              then call ser "Too many arguments entered: "        __
           if \isNum(t)           then call ser "Argument isn't numeric: "             t
           if \isInt(t)           then call ser "Argument isn't an integer: "          t
           if t<1                 then call ser "Argument can't be less than 1: "      t
           if t>3                 then call ser "Argument can't be greater than 3: "   t
           end   /*while*/
    t= t/1                                      /*Normalize the number:  001   2.  +3  */
    #= 4-t                                      /*calculate the computer's take─away.  */
    say pad "The computer takes "    #    " token"s(#).
    pot= pot - t - #                            /*calculate the number of tokens in pot*/
    if pot==0  then do;   say pad 'No tokens left.'       /*No tokens left in the pot? */
                          say pad "The computer wins!"    /*Display a braggart message.*/
                          exit                            /*exit this computer program.*/
                    end
    end   /*forever*/                           /*keep looping until there's a winner. */

exit /*stick a fork in it, we're all done. */ /*──────────────────────────────────────────────────────────────────────────────────────*/ isNum: return datatype( arg(1), 'N') /*verify that the arg is a number. */ isInt: return datatype( arg(1), 'W') /* " " " " " an integer. */ show: say; say pad "Tokens remaining: " arg(1)' ' pad; say; return s: if arg(1)==1 then return arg(3); return word(arg(2) 's',1) ser: if ok then say pad '***error***' arg(1); ok= 0; return</lang>

output   when using the default input:
──────── Tokens remaining:  12  ────────


──────── How many tokens do you want to take away  (1, 2, or 3)    (or QUIT)?
2                                            ◄■■■■■■■■■■■ user input                                             

──────── The computer takes  2  tokens.

──────── Tokens remaining:  8  ────────


──────── How many tokens do you want to take away  (1, 2, or 3)    (or QUIT)?
3                                            ◄■■■■■■■■■■■ user input

──────── The computer takes  1  token.

──────── Tokens remaining:  4  ────────


──────── How many tokens do you want to take away  (1, 2, or 3)    (or QUIT)?
1                                            ◄■■■■■■■■■■■ user input 

──────── The computer takes  3  tokens.
──────── No tokens left.
──────── The computer wins!