############################################################################ # # File: nim.icn # # Subject: Program to play the game of nim # # Author: Jerry Nowlin # # Date: June 14, 1994 # ############################################################################ # # This file is in the public domain. # ############################################################################ # # The game of nim focuses on a pile of 15 sticks. Each player can # select 1, 2, or 3 sticks from the sticks remaining in the pile when # it's their turn. The player to pick up the last stick(s) wins. The # loser of the previous game always gets to go first. # # There are two versions of nim in here. The first (default) version # uses an algorithm to make its moves. It will never lose if it gets # the first turn. The second version tries to learn from each game. # You'll have to play a few games before it will get very smart but # after a while it will also never lose if it gets the first turn. This # is assuming of course that you know how to play. Since the learning # version learns from the person it plays against, if you're lousy the # game will be too. # # To invoke the learning version just pass any argument to the program. # If you want to see how the program learns, you can use the string # "show" as the argument and the program's current game memory will be # displayed after each game. If you invoke the game with the string save # as an argument a file called ".nimdump" will be created in the current # directory with a dump of the program's game memory when you quit and # the next time the game is played in learn mode it will initialize its # game memory from the dump. You can invoke this program with more than # one argument so show and save can be used at the same time. # ############################################################################ # # Links: random # ############################################################################ link random global STICKS, # the number of stick left MINE, # my trys for a given game THEIRS, # their trys for a given game TRIED # the combined tried table (game memory) procedure main(args) local resp, # player response turn, # who's turn fp, # file pointer stick, # sticks index take, # take index seed, # random number seed show # show the game memory flag randomize() # check if we should show the thought process of a learning game if !args == "show" then show := "yes" # define game memory TRIED := table() # if this is a learning game and there's a memory dump read it if *args > 0 & fp := open(".nimdump","r") then { every stick := 1 to 15 do { TRIED[stick] := list(3) every take := 1 to 3 do TRIED[stick][take] := (read(fp) | "?") } close(fp) } # otherwise initialize game memory to unknowns else every stick := 1 to 15 do TRIED[stick] := [ "?", "?", "?" ] # start with their turn turn := "theirs" # print the initial message write("\nThis is the game of nim. You must pick up 1, 2 or 3") write("sticks from the pile when it's your turn. The player") write("that picks up the last stick(s) wins. Good luck.") # loop repeat { # initialize the per game variables STICKS := 15 THEIRS := table() MINE := table() # display the initial stick pile dispile() # loop while there are sticks left while STICKS > 0 do # take turns if turn == "theirs" then turn := theirturn(args) else turn := myturn(args) # the player who took the last stick(s) wins if turn == "theirs" then write("\nI won!") else write("\nYou won!") # if this is a thinking game learn from it if *args > 0 then learn(turn,show) # see if they want to play again writes("\nDo you want to play again? ") if not any('yY',read()) then quit(args,"\nGoodbye.\n") } end procedure theirturn(args) local pick # the players pick # find out how many sticks they want writes("How many sticks do you want? ") pick := read() # check their response to see if they want to quit if any('qQ',pick) then quit(args,"\nYou gave up!\n") # check to see if their pick is valid if not numeric(pick) | pick < 1 | pick > (3 | STICKS) then write("\007Invalid Response\007\n") & return "theirs" # save their pick if this is a thinking game if *args > 0 then THEIRS[STICKS] := pick # take away the sticks STICKS -:= pick # if there are any sticks left display them if STICKS > 0 then dispile() # make it my turn return "mine" end procedure myturn(args) local pick # my pick # let them know I'm about to pick writes("I'll take ") # make my choice depending on whether or not this is a thinking game if *args > 0 then { # think about it pick := thinkpick(STICKS) # if I can't make up my mind randomly pick one choice if type(pick) == "list" then pick := ?pick MINE[STICKS] := pick } else pick := algorpick(STICKS) # tell them what I decided write((1 < pick) || " sticks." | "1 stick.") # take away the sticks STICKS -:= pick # if there are any sticks left display them if STICKS > 0 then dispile() # make it their turn return "theirs" end procedure dispile() write() every 1 to STICKS do writes("/ ") write("\n") end # Use an algorithmic method to choose the number of sticks I want. The # decision is made by taking the number of sticks that will leave an even # multiple of 4 in the pile (0 is an even multiple of 4) if possible and if # not then randomly choose 1, 2 or 3 sticks. procedure algorpick(sticks) return (0 ~= (sticks % 4)) | ?3 end # Use a learning method to choose the number of sticks I want. The # decision is made by looking at the choices that have been made for this # number of sticks in the past and the results of the game where it was # made. If there is no pick that resulted in a win make a random pick # from all the unknown picks. If there are no unknown picks just randomly # choose 1, 2 or 3 sticks and hope THEY screw up. procedure thinkpick(sticks,recurse) local picks, # unknown picks take, # take index check, # check list pick # my pick # initialize a list of unknown picks picks := [] # check every possible pick every take := 1 to 3 do { # if this pick won take it if TRIED[sticks][take] == "won" then return take # if this pick is unknown save it if TRIED[sticks][take] == "?" then put(picks,take) } # if there are no unknown picks and no winning picks anything goes if *picks = 0 then picks := [1,2,3] # be smarter and check to see if there is a clear win for THEM # after any of the picks left if /recurse then { check := [] every pick := !picks do if type(thinkpick(0 < (sticks - pick),1)) == "list" then put(check,pick) if *check = 0 then picks := [1,2,3] else picks := check } return picks end # Save the results of each pick in this game in the programs game memory and # if the command line argument was "show" display the updated game memory. procedure learn(turn,show) local them, # their outcome flag me, # my outcome flag stick, # sticks index take # taken index # decide on the outcome if turn == "theirs" then them := "lost" & me := "won" else them := "won" & me := "lost" # check for all the picks made for this game and save the results # in the game memory every stick := 1 to 15 do { if \MINE[stick] then TRIED[stick][MINE[stick]] := comp(TRIED[stick][MINE[stick]],me) if \THEIRS[stick] then TRIED[stick][THEIRS[stick]] := comp(TRIED[stick][THEIRS[stick]],them) } # if the show flag is set print the program's game memory if \show then { writes("\n picks\n ") every writes(center(1 to 3,5)) write("\n ----------------") every stick := 15 to 1 by -1 do { if stick = 8 then writes("sticks ",right(stick,2),"|") else writes(" ",right(stick,2),"|") every take := 1 to 3 do writes(center(TRIED[stick][take],5)) write() } } return end # Compare this game's result with what the program remembers. If the results # were the same fine. If the old result was unknown save the new result. If # the old result is different from the new result the game can't know for # sure anymore so go back to unknown. procedure comp(old,new) return (old == new) | (old == "?" & new) | "?" end procedure quit(args,msg) local fp, # file pointer stick, # sticks index take # take index write(msg) if !args == "save" then if fp := open(".nimdump","w") then { every stick := 1 to 15 do every take := 1 to 3 do write(fp,TRIED[stick][take]) close(fp) } exit() end