/* * The game of NUCLEAR. * by Chris Gray, Edmonton Alberta Canada * Initial implemenation in CP/M Draco, September 6, 1982. */ package /Nuclear; use /Sys; use /Char; use /Terminal; use /Fmt; uint ROWS = 6, // # rows in game board COLS = 6; // # columns in game board [2] uint LastMoveLine; // lines for last moves uint TIMER_COUNT = 1, // delay to wait on explosions (sec) NAME_LENGTH = 20; // length for player names /* codes stored in Sign, indexes to SignChar: */ enum Occupant_t { occ_player1, occ_player2, occ_empty }; uint NLines, // number of lines on screen NCols, // number of columns on screen LineOffset, // line of first board row ColOffset, // column of first board column RowMult, // multiplier for row position ColMult; // multiplier for col position var Term := Terminal/DummyTerm; // my Terminal_t bool Quit; /* array of values used to find the code for the other player */ [2] Occupant_t Other; /* array of characters used to display the owner of a position */ [3] char SignChar; /* arrays, indexed by the row and column on the board. These contain the neutron count, critical mass, and owner of that position */ [ROWS, COLS] uint Current, Critical; [ROWS, COLS] Occupant_t Sign; /* the names of the two players */ string Player1Name, Player2Name; /* the number of games won in this session by the two players */ uint Player1Wins, Player2Wins; /* the total numbers of neutrons currently held by the players, and the values of these currently displayed on the screen. This second pair is used to reduce the amount of I/O to the screen during explosions. */ [2] uint PlayerCount, LastPlayerCount; /* the last moves of the players (needed only to redraw screen) */ [2] char LastMove; /* * instructions - print the instructions. */ proc instructions()void: Terminal/Fmt(Term, 0, " The game of NUCLEAR is played on a " + Sys/UintToString(ROWS) + " x " + Sys/UintToString(COLS) + " board. " "The board is displayed on the screen, along with a position indicator " "board to the right of the actual playing board. " "The two players alternate by placing 'neutrons' on the playing board by " "entering the character corresponding to the position at which they wish to " "place their neutron. " "Unoccupied positions are indicated by '0', occupied positions are " "indicated by the number of neutrons there, preceeded by a '+' or '-' " "to indicate which player they belong to. " "Players cannot place neutrons on positions occupied by the other player. " "Each position has a 'critical mass' which is equal to the number of " "orthogonal (left-right or up-down) neighbours which it has. " "When the number of neutrons at any position equals or exceeds it's " "critical mass, that position explodes, sending neutrons to each of its " "neighbours. " "If the neighbour belonged to the other player, it is taken over by the " "current player, complete with it's neutrons. " "The neighbours can then themselves explode, in a kind of chain reaction. " "The game ends when one player, by means of explosions, is able to take " "over all of the neutrons of the other player. " "\n\n" " Counts of the number of neutrons currently held by each player, and " "the number of games they have won so far, are kept in the upper right-hand " "corner of the screen. " ); corp; /* * initialize - set up the arrays, set the critical masses, and set the * won game counters. */ proc initialize()void: if NLines >= 22 then LineOffset := 8; RowMult := 2; else LineOffset := 7; RowMult := 1; fi; if NCols >= 64 then ColOffset := (NCols - COLS * 6 - 4) / 2; ColMult := 3; else ColOffset := (NCols - COLS * 4 - 2) / 2; ColMult := 2; fi; SignChar[occ_player1] := "+"; SignChar[occ_player2] := "-"; SignChar[occ_empty] := " "; Other[occ_player1] := occ_player2; Other[occ_player2] := occ_player1; for r from 1 upto ROWS - 2 do for c from 1 upto COLS - 2 do Critical[r, c] := 4; od; Critical[r, 0] := 3; Critical[r, COLS - 1] := 3; od; for c from 1 upto COLS - 2 do Critical[0, c] := 3; Critical[ROWS - 1, c] := 3; od; Critical[0, 0] := 2; Critical[0, COLS - 1] := 2; Critical[ROWS - 1, 0] := 2; Critical[ROWS - 1, COLS - 1] := 2; Player1Wins := 0; Player2Wins := 0; corp; /* * resetBoard - reset the board to the pre-game state, ready for next game */ proc resetBoard()void: for r from 0 upto ROWS - 1 do for c from 0 upto COLS - 1 do Current[r, c] := 0; Sign[r, c] := occ_empty; od; od; PlayerCount[occ_player1] := 0; PlayerCount[occ_player2] := 0; LastPlayerCount[occ_player1] := 0; LastPlayerCount[occ_player2] := 0; LastMove[occ_player1] := " "; LastMove[occ_player2] := " "; corp; /* * drawBoard - display the current game state on the screen */ proc drawBoard()void: Terminal/Clear(Term); Terminal/Text(Term, 0, (NCols - 19) / 2, "The game of NUCLEAR"); Terminal/Text(Term, 2, 0, "Player #1(+): " + Player1Name); Terminal/Text(Term, 2, NCols - 6, Fmt/FmtS(PlayerCount[occ_player1] : 3) + Fmt/FmtS(Player1Wins : 3)); Terminal/Text(Term, 3, 0, "Player #2(-): " + Player2Name); Terminal/Text(Term, 3, NCols - 6, Fmt/FmtS(PlayerCount[occ_player2] : 3) + Fmt/FmtS(Player2Wins : 3)); Terminal/Text(Term, 4, 0, "Player #1(+)'s last move: " + Sys/CharString(LastMove[occ_player1])); Terminal/Text(Term, 5, 0, "Player #2(-)'s last move: " + Sys/CharString(LastMove[occ_player2])); uint pos := 0; for r from 0 upto ROWS - 1 do uint row := r * RowMult + LineOffset, col := ColOffset; for c from 0 upto COLS - 1 do Terminal/DChar(Term, row, col + c * 3, SignChar[Sign[r, c]]); Terminal/DChar(Term, row, col + c * 3 + 1, Current[r, c] + "0"); od; col := col + 2 + COLS * 3; for c from 0 upto COLS - 1 do char ch := if pos >= 10 then pos + ("a" - 10) else pos + "0" fi; Terminal/DChar(Term, row, col + c * 3, ch); pos := pos + 1; od; od; corp; /* * update - update the given position on the screen. Also neutron counts */ proc update(uint r, c)void: uint row := r * RowMult + LineOffset, col := c * ColMult + ColOffset; Terminal/DChar(Term, row, col, SignChar[Sign[r, c]]); Terminal/DChar(Term, row, col + 1, Current[r, c] + "0"); for i from occ_player1 upto occ_player2 do if PlayerCount[i] ~= LastPlayerCount[i] then Terminal/Text(Term, i - occ_player1 + 2, NCols - 6, Fmt/FmtS(PlayerCount[i] : 3) + " "); LastPlayerCount[i] := PlayerCount[i]; fi; od; corp; /* * bump - a neutron from an explosion by the given player enters the * given position */ proc bump(Occupant_t player; uint r, c)void: if Sign[r, c] ~= occ_empty then PlayerCount[Sign[r, c]] := PlayerCount[Sign[r, c]] - Current[r, c]; PlayerCount[player] := PlayerCount[player] + Current[r, c]; fi; Current[r, c] := Current[r, c] + 1; Sign[r, c] := player; update(r, c); corp; /* * explode - an explosion at the given position has been caused by the * given player */ proc explode(Occupant_t player; uint r, c)void: Current[r, c] := Current[r, c] - Critical[r, c]; Sign[r, c] := if Current[r, c] = 0 then occ_empty else player fi; update(r, c); if r ~= 0 then bump(player, r - 1, c); fi; if c ~= COLS - 1 then bump(player, r, c + 1); fi; if r ~= ROWS - 1 then bump(player, r + 1, c); fi; if c ~= 0 then bump(player, r, c - 1); fi; /* wait long enough for the players to see the explosion */ Terminal/Sleep(Term, TIMER_COUNT); corp; /* * moveAt - the given player is moving at the given position */ proc moveAt(Occupant_t player; uint r, c)void: Occupant_t other; bool hadExplosion; PlayerCount[player] := PlayerCount[player] + 1; Current[r, c] := Current[r, c] + 1; Sign[r, c] := player; update(r, c); other := Other[player]; hadExplosion := true; while hadExplosion and PlayerCount[other] ~= 0 do hadExplosion := false; r := 0; while r ~= ROWS and PlayerCount[other] ~= 0 do c := 0; while c ~= COLS and PlayerCount[other] ~= 0 do if Current[r, c] >= Critical[r, c] then hadExplosion := true; explode(player, r, c); fi; c := c + 1; od; r := r + 1; od; od; corp; /* * getMove - get a valid move for the given player. The line and column of * that move are returned */ proc getMove(Occupant_t player; @ uint rptr, cptr)void: uint r, c; char ch; /* loop until we have a valid move: */ while Terminal/ClearLine(Term, NLines - 1); Terminal/Text(Term, NLines - 1, 0, if player = occ_player1 then Player1Name else Player2Name fi + "'s move: "); ch := Terminal/WaitChar(Term); if Char/IsAlphaNumeric(ch) then uint pos; if Char/IsDigit(ch) then pos := ch - "0"; else pos := Char/ToLower(ch) - ("a" - 10); fi; r := pos / ROWS; c := pos % COLS; if Sign[r, c] = Other[player] then Terminal/Text(Term, NLines - 2, 0, "Illegal move, try again. "); true else false fi elif ch = "\12" then /* if get a CNTL-R, then redraw the screen: */ drawBoard(); true elif ch = "?" then /* print the instructions: */ instructions(); Terminal/Continue(Term); drawBoard(); true elif ch = "$" then Quit := true; return; false else Terminal/Text(Term, NLines - 2, 0, "Invalid command, \"?\" for help."); true fi do od; Terminal/DChar(Term, 4 + (player - occ_player1), 26, ch); LastMove[player] := ch; Terminal/ClearLine(Term, NLines - 2); Terminal/ClearLine(Term, NLines - 1); rptr@ := r; cptr@ := c; corp; /* * play - play the game. The first two moves are special, since before * they are made, the players don't have any neutrons, which is * normally the condition for game over. Note that the second * player's move is skipped if the first player wins. */ proc play()void: uint r, c; getMove(occ_player1, @r, @c); if Quit then return; fi; moveAt(occ_player1, r, c); getMove(occ_player2, @r, @c); if Quit then return; fi; moveAt(occ_player2, r, c); while PlayerCount[occ_player1] ~= 0 and PlayerCount[occ_player2] ~= 0 do getMove(occ_player1, @r, @c); if Quit then return; fi; moveAt(occ_player1, r, c); if PlayerCount[occ_player2] ~= 0 then getMove(occ_player2, @r, @c); if Quit then return; fi; moveAt(occ_player2, r, c); fi; od; Terminal/ClearLine(Term, NLines - 2); if PlayerCount[occ_player2] = 0 then Player1Wins := Player1Wins + 1; Terminal/Text(Term, 2, NCols - 3, Fmt/FmtS(Player1Wins : 3)); Terminal/Text(Term, NLines - 2, 0, Player1Name + " wins!"); else Player2Wins := Player2Wins + 1; Terminal/Text(Term, 3, NCols - 3, Fmt/FmtS(Player2Wins : 3)); Terminal/Text(Term, NLines - 2, 0, Player2Name + " wins!"); fi; corp; /* * main - the main program. The general flow of the game is contained * here, including all information prompting and final statistics * output. */ export proc main()void: Quit := false; NLines := 24; NCols := 80; Term := Terminal/Create24x80("Nuclear"); Terminal/Text(Term, (NLines - 4) / 2, (NCols - 30) / 2, "Welcome to the game of NUCLEAR"); Terminal/Text(Term, NLines / 2, (NCols - 28) / 2, "Copyright 1984 by Chris Gray"); if Terminal/Ask(Term, "Would you like instructions? ") then instructions(); fi; Player1Name := Terminal/GetString(Term, "First player: ", NAME_LENGTH); Player2Name := Terminal/GetString(Term, "Second player: ", NAME_LENGTH); initialize(); while resetBoard(); drawBoard(); play(); Terminal/Ask(Term, "Another game? ") do od; Terminal/ClearLine(Term, NLines - 3); Terminal/Text(Term, NLines - 3, 0, Player1Name + " won " + Fmt/FmtS(Player1Wins) + if Player1Wins = 1 then " game." else " games." fi); Terminal/Text(Term, NLines - 2, 0, Player2Name + " won " + Fmt/FmtS(Player2Wins) + if Player2Wins = 1 then " game." else " games." fi); Terminal/Continue(Term); corp; package /; export proc main()void: Nuclear/main(); corp;