home *** CD-ROM | disk | FTP | other *** search
- ############################################################################
- #
- # File: nim.icn
- #
- # Subject: Program to play the game of nim
- #
- # Author: Jerry Nowlin
- #
- # Date: December 24, 1991
- #
- ###########################################################################
- #
- # 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.
- #
- ############################################################################
-
- 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
-
- # use the current time to define a random number seed
- &random := integer(&clock[1:3] || &clock[4:6] || &clock[7:0])
-
- # 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
-