Advertisement
KnightMiner

[CC] stratego

Sep 27th, 2015
467
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 42.36 KB | None | 0 0
  1. -- Stratego version B2.1 by KnightMiner
  2. -- check if we are hosting or joining a game
  3. local args = { ... }
  4. if #args > 3 or #args < 2 or ( args[1] ~= 'host' and args[1] ~= 'join' ) then
  5.   print( 'Usages:' )
  6.   print( 'stratego host [save-game] <name>' )
  7.   print( 'stratego join <name>' )
  8.   return
  9. end
  10.  
  11. -- find a modem to host/join a game on
  12. local openedModem, foundModem
  13. for _, modem in ipairs( peripheral.getNames() ) do
  14.   if peripheral.getType( modem ) == "modem" then
  15.     if not rednet.isOpen( modem ) then
  16.       rednet.open( modem )
  17.       openedModem = modem
  18.     end
  19.     foundModem = true
  20.     break
  21.   end
  22. end
  23. if not foundModem then
  24.   print( "No modems found" )
  25.   return
  26. end
  27.  
  28. -- function for shutdown, including closing the modem and clearing the screen
  29. -- it also prints an optional message before the "Thanks" message
  30. function shutdown( msg )
  31.   shell.run( 'clear' )
  32.   if openedModem then
  33.     rednet.close( openedModem )
  34.   end
  35.   if msg then
  36.     print( msg )
  37.   end
  38.   print( 'Thank you for playing Stratego by KnightMiner' )
  39. end
  40.  
  41. -- load the config from the file "stratego.cfg"
  42. -- note that the file needs to be in the current directory to load,
  43. -- getting it to load from the same directory as the game proves quite hard
  44. local config, configMsg
  45. local cfgFile = shell.resolve( 'stratego.cfg' )
  46. if fs.exists( cfgFile ) then
  47.   local file = fs.open( cfgFile, 'r' )
  48.   config = textutils.unserialize( file.readAll() )
  49.   file.close()
  50.   if not config then
  51.     -- we write the message as a variable as the writeLog function requires the config to load,
  52.     -- and any print will be overwritten in the next step
  53.     configMsg = 'Failed to load config'
  54.   else
  55.     configMsg = 'Successfully loaded config'
  56.   end
  57. end
  58. -- if the config table is invalid or we didn't load one, replace it with a blank table
  59. config = config or {}
  60.  
  61. -- define display stuff based on computer type
  62. shell.run( 'clear' )
  63. local display
  64. -- pocket computers, which define an empty "pocket" table
  65. if pocket then
  66.   display = {
  67.     write = {
  68.       '+----------+-------------+',
  69.       '|          |             |',
  70.       '|          |   Stratego  |',
  71.       '|          |             |',
  72.       '|          |             |',
  73.       '|          +-------------+',
  74.       '|          |             |',
  75.       '|          |             |',
  76.       '|          |             |',
  77.       '+----------+-------------+',
  78.       '|                        |',
  79.       '|                        |',
  80.       '|                        |',
  81.       '|                        |',
  82.       '|                        |',
  83.       '|                        |',
  84.       '|                        |',
  85.       '|                        |',
  86.       '|                        |',
  87.       '|                        |'
  88.     },
  89.     input = { 13, 7, 13 },
  90.     log   = { 2, 11, 24, 11 },
  91.     board = { 2, 2 }
  92.   }
  93. -- turtle computers, with the "turtle" api
  94. elseif turtle then
  95.   display = {
  96.     write = {
  97.       '+----------+----------+---------------+',
  98.       '|          |          |               |',
  99.       '|          | Stratego |               |',
  100.       '|          |          |               |',
  101.       '|          +----------+---------------+',
  102.       '|          |                          |',
  103.       '|          |                          |',
  104.       '|          |                          |',
  105.       '|          |                          |',
  106.       '+----------+                          |',
  107.       '+----------+                          |',
  108.       '+----------+                          |',
  109.       '+----------+                          |'
  110.     },
  111.     input = { 24, 2, 15 },
  112.     log   = { 13, 6, 26, 9 },
  113.     board = { 2, 2 }
  114.   }
  115. -- standard computers
  116. else
  117.   display = {
  118.     write = {
  119.       '+--------------------+----------+-----------------+',
  120.       '|                    |          |                 |',
  121.       '|                    | Stratego |                 |',
  122.       '|                    |          |                 |',
  123.       '|                    +----------+-----------------+',
  124.       '|                    |                            |',
  125.       '|                    |                            |',
  126.       '|                    |                            |',
  127.       '|                    |                            |',
  128.       '|                    |                            |',
  129.       '|                    |                            |',
  130.       '|                    |                            |',
  131.       '|                    |                            |',
  132.       '|                    |                            |',
  133.       '|                    |                            |',
  134.       '|                    |                            |',
  135.       '|                    |                            |',
  136.       '+--------------------+                            |',
  137.       '+--------------------+                            |'
  138.     },
  139.     input = { 34, 2, 17 },
  140.     log   = { 23, 6, 28, 15 },
  141.     board = { 2, 2, large = true }
  142.   }
  143. end
  144. -- if there is config display stuff to load, overwrite any values with ones from the config
  145. if config.display then
  146.   for k in pairs( config.display ) do
  147.     display[k] = config.display[k]
  148.   end
  149. end
  150.  
  151. -- fixes for multishell
  152. if multishell and multishell.getCount() > 1 then
  153.   table.remove( display.write )
  154.   display.log[4] = display.log[4] - 1
  155. end
  156.  
  157. -- draw the game board's borders
  158. write( table.concat( display.write, '\n' ) )
  159.  
  160. -- create the output log as a window
  161. local logBox = window.create( term.current(), display.log[1], display.log[2], display.log[3], display.log[4] )
  162. function writeLog( ... )
  163.   local prev = term.redirect( logBox )
  164.   print( ... )
  165.   term.redirect( prev )
  166. end
  167.  
  168. -- write the config message from eariler
  169. -- helps with debug in case you are wondering why your config is not working
  170. if configMsg then
  171.   writeLog( configMsg )
  172. end
  173. -- create the input box as a window
  174. local inputBox = window.create( term.current(), display.input[1], display.input[2], display.input[3], 3 )
  175.  
  176. -- function to clear the input box
  177. function inputClear( n )
  178.   inputBox.setCursorPos( 1, 1 + ( n or 0 ) )
  179.   inputBox.clearLine()
  180. end
  181.  
  182. -- function to get user input
  183. function input( msg )
  184.   inputBox.setCursorPos( 1, 2 )
  185.   inputBox.clearLine()
  186.   if msg then
  187.     inputBox.write( msg )
  188.   end
  189.   return read()
  190. end
  191.  
  192. -- function to write to the input box
  193. function inputWrite( msg, n )
  194.   inputClear( n )
  195.   inputBox.setCursorPos( 1, 1 + ( n or 0 ) )
  196.   inputBox.clearLine()
  197.   inputBox.write( msg )
  198. end
  199.  
  200. -- define the pieces table, pieces will be added later
  201. local pieces = {
  202.   [1] = {},
  203.   [2] = {},
  204.   [3] = {},
  205.   [4] = {},
  206.   [5] = {},
  207.   [6] = {},
  208.   [7] = {},
  209.   [8] = {}
  210. }
  211.  
  212. -- check if a click is valid
  213. -- used within the main click function, which is defined based on whether a mouse exists
  214. local myTeam, notMyTeam, options, rows
  215. function validClick( x, y, mode, px, py )
  216.   -- location is out of bounds
  217.   if x > 10 or x < 1 or y > 8 or y < 1 then
  218.     writeLog( 'Invalid location' )
  219.     return false
  220.   -- location is beyond the legal columns to set up pieces
  221.   -- note that "rows" and "options" are both defined later, but the click function is needed before they are defined
  222.   elseif mode == 'setup' and options.rotate and ( myTeam == 'red' and ( x > rows ) or myTeam == 'blue' and ( x < ( 11 - rows ))) then
  223.     writeLog( 'Cannot place pieces beyond first ' .. rows .. ' columns' )
  224.     return false
  225.   -- location is beyond the legal rows to set up pieces
  226.   elseif mode == 'setup' and not options.rotate and ( myTeam == 'red' and ( y < ( 9 - rows ) ) or myTeam == 'blue' and ( y > rows )) then
  227.     writeLog( 'Cannot place pieces beyond first ' .. rows .. ' rows' )
  228.     return false
  229.   -- check here if we are deselecting the piece, as otherwise it fails later as being the same team
  230.   elseif mode == 'attack' and x == px and y == py then
  231.     return 'cancel'
  232.   else
  233.     -- check reasons we are not allowed to move
  234.     local piece = pieces[y][x]
  235.     -- if no piece exists, then
  236.     if not piece then
  237.       -- if we are setting up or attacking, that is fine
  238.       if mode then
  239.         return true
  240.       else
  241.         -- otherwise we are moving, where we must start at a piece
  242.         writeLog( 'No piece here' )
  243.         return false
  244.       end
  245.     -- if the piece is a lake
  246.     elseif piece[1] == 'lake' then
  247.       if mode == 'setup' then
  248.         writeLog( 'Cannot place pieces on lakes' )
  249.       elseif mode == 'attack' then
  250.         writeLog( 'Cannot move onto lakes' )
  251.       else
  252.         writeLog( 'Cannot move lakes' )
  253.       end
  254.       return false
  255.     -- if the mode is not "attack" and the piece is not my team
  256.     elseif mode ~= 'attack' and piece[1] == notMyTeam then
  257.       writeLog( 'Cannot move other team' )
  258.       return false
  259.     -- if
  260.     elseif mode == 'attack' and piece[1] == myTeam then
  261.       writeLog( 'Cannot move on to own team' )
  262.       return false
  263.     -- if the piece cannot move, either flags or bombs
  264.     elseif not mode and ( piece[2] == 'F' or piece[2] == '*' ) then
  265.       writeLog( 'Immovable piece' )
  266.       return false
  267.     -- if all cases pass as false, return movable
  268.     else
  269.       return true
  270.     end
  271.   end
  272. end
  273.  
  274. -- determine if we are using an advance system or a regular one
  275. -- then define the colors and the click() function based on that
  276. local names = {
  277.   ['*'] = 'bomb',
  278.   ['!'] = 10,
  279.   S = 'spy',
  280.   F = 'flag',
  281.   [1] = 'spotter'
  282. }
  283. local color = {}
  284. local size = term.getSize()
  285. local click, button
  286. local isColor = term.isColor()
  287. if isColor then
  288.   names.red  = 'red'
  289.   names.blue = 'blue'
  290.   color = {
  291.     red        = colors.red,
  292.     redSelect  = colors.orange,
  293.     blue       = colors.blue,
  294.     blueSelect = colors.purple,
  295.     textSelect = colors.white,
  296.     lake       = colors.lightBlue,
  297.     lakeBg     = colors.blue,
  298.     pos        = colors.lime,
  299.     neg        = colors.green
  300.   }
  301.   term.setCursorPos( size, 1 )
  302.   term.blit( '+', '0', 'e' )
  303.   function click( mode, px, py )
  304.     while true do
  305.       inputWrite( 'Click pos', 1 )
  306.       local _, _, x, y = os.pullEvent( 'mouse_click' )
  307.       if mode == 'setup' and x == size and y == 1 then
  308.         return 'done'
  309.       elseif not mode and x == size and y == 1 then
  310.         return button()
  311.       elseif display.board.large then
  312.         x = math.floor( x / 2 )
  313.         y = math.floor( y / 2 )
  314.       else
  315.         x = x - 1
  316.         y = y - 1
  317.       end
  318.       local valid = validClick( x, y, mode, px, py )
  319.       if valid == 'cancel' then
  320.         return 'cancel'
  321.       elseif valid then
  322.         return x, y
  323.       end
  324.     end
  325.   end
  326. else
  327.   names.red  = 'white'
  328.   names.blue = 'black'
  329.   color = {
  330.     red        = colors.white,
  331.     redSelect  = colors.white,
  332.     blue       = colors.black,
  333.     blueSelect = colors.black,
  334.     textSelect = colors.gray,
  335.     lake       = colors.lightGray,
  336.     lakeBg     = colors.black,
  337.     pos        = colors.lightGray,
  338.     neg        = colors.gray
  339.   }
  340.   function click( mode, px, py )
  341.     while true do
  342.       local x, y
  343.       while true do
  344.         local data = input( 'Pos: ' )
  345.         if mode == 'setup' and data == 'done' then
  346.           return 'done'
  347.         elseif not mode and ( data == 'menu' or data == 'button' ) then
  348.           return button()
  349.         end
  350.         x, y = data:match( '(%d+),%s*(%d+)' )
  351.         if x and y then
  352.           break
  353.         else
  354.           writeLog( 'Invalid location' )
  355.         end
  356.       end
  357.       x = tonumber( x )
  358.       y = tonumber( y )
  359.       local valid = validClick( x, y, mode, px, py )
  360.       if valid == 'cancel' then
  361.         return 'cancel'
  362.       elseif valid then
  363.         return x, y
  364.       end
  365.     end
  366.   end
  367. end
  368.  
  369. -- override defaults with any configured names
  370. if config.names then
  371.   for k in pairs( config.names ) do
  372.     names[k] = config.names[k]
  373.   end
  374. end
  375.  
  376. -- and colors
  377. if config.color then
  378.   for k in pairs( config.color ) do
  379.     color[k] = colors[config.color[k]]
  380.   end
  381. end
  382.  
  383. -- table which converts colors.n into a hexidecimal value to be used by term.blit
  384. local hexColor = {}
  385. for n=1,16 do
  386.     hexColor[2^(n-1)] = string.sub( "0123456789abcdef", n, n )
  387. end
  388.  
  389. -- data to draw the board
  390. local boardData
  391. if display.board.large then
  392.   local pos = string.rep( hexColor[color.pos], 2 )
  393.   local neg = string.rep( hexColor[color.neg], 2 )
  394.   boardData = {
  395.     row  = '                    ',
  396.     pos = string.rep( pos .. neg, 5 ),
  397.     neg = string.rep( neg .. pos, 5 ),
  398.     size = { 20, 16 }
  399.   }
  400. else
  401.   local pos = hexColor[color.pos]
  402.   local neg = hexColor[color.neg]
  403.   boardData = {
  404.     row = '          ',
  405.     pos = string.rep( pos .. neg, 5 ),
  406.     neg = string.rep( neg .. pos, 5 ),
  407.     size = { 10, 8 }
  408.   }
  409. end
  410.  
  411. -- draw the board using the data from above
  412. -- note that "row" is an string of just spaces
  413. local board = window.create( term.current(), display.board[1], display.board[2], boardData.size[1], boardData.size[2] )
  414. board.setCursorPos( 1, 1 )
  415. if display.board.large then
  416.   board.blit( boardData.row, boardData.pos, boardData.pos ) board.setCursorPos( 1, 2 )
  417.   board.blit( boardData.row, boardData.pos, boardData.pos ) board.setCursorPos( 1, 3 )
  418.   board.blit( boardData.row, boardData.neg, boardData.neg ) board.setCursorPos( 1, 4 )
  419.   board.blit( boardData.row, boardData.neg, boardData.neg ) board.setCursorPos( 1, 5 )
  420.   board.blit( boardData.row, boardData.pos, boardData.pos ) board.setCursorPos( 1, 6 )
  421.   board.blit( boardData.row, boardData.pos, boardData.pos ) board.setCursorPos( 1, 7 )
  422.   board.blit( boardData.row, boardData.neg, boardData.neg ) board.setCursorPos( 1, 8 )
  423.   board.blit( boardData.row, boardData.neg, boardData.neg ) board.setCursorPos( 1, 9 )
  424.   board.blit( boardData.row, boardData.pos, boardData.pos ) board.setCursorPos( 1, 10 )
  425.   board.blit( boardData.row, boardData.pos, boardData.pos ) board.setCursorPos( 1, 11 )
  426.   board.blit( boardData.row, boardData.neg, boardData.neg ) board.setCursorPos( 1, 12 )
  427.   board.blit( boardData.row, boardData.neg, boardData.neg ) board.setCursorPos( 1, 13 )
  428.   board.blit( boardData.row, boardData.pos, boardData.pos ) board.setCursorPos( 1, 14 )
  429.   board.blit( boardData.row, boardData.pos, boardData.pos ) board.setCursorPos( 1, 15 )
  430.   board.blit( boardData.row, boardData.neg, boardData.neg ) board.setCursorPos( 1, 16 )
  431.   board.blit( boardData.row, boardData.neg, boardData.neg )
  432. else
  433.   board.blit( boardData.row, boardData.pos, boardData.pos ) board.setCursorPos( 1, 2 )
  434.   board.blit( boardData.row, boardData.neg, boardData.neg ) board.setCursorPos( 1, 3 )
  435.   board.blit( boardData.row, boardData.pos, boardData.pos ) board.setCursorPos( 1, 4 )
  436.   board.blit( boardData.row, boardData.neg, boardData.neg ) board.setCursorPos( 1, 5 )
  437.   board.blit( boardData.row, boardData.pos, boardData.pos ) board.setCursorPos( 1, 6 )
  438.   board.blit( boardData.row, boardData.neg, boardData.neg ) board.setCursorPos( 1, 7 )
  439.   board.blit( boardData.row, boardData.pos, boardData.pos ) board.setCursorPos( 1, 8 )
  440.   board.blit( boardData.row, boardData.neg, boardData.neg )
  441. end
  442.  
  443. -- The file is the second parameter, but only use it if the third is also set
  444. -- as otherwise the second parameter is the host name
  445. local fileName = ( args[3] and args[2] or config.file )
  446. -- and apply the optional file extension from the config (don't apply it if the filename is nil)
  447. fileName = fileName and fileName .. ( config.ext or '' )
  448. local file = {}
  449. -- if the file is set does not exist, skip it
  450. if fileName and not fs.exists( fileName ) then
  451.   writeLog( 'Save file does not exist' )
  452.   fileName = nil
  453. -- otherwise load the file
  454. elseif fileName then
  455.   local f = fs.open( fileName, 'r' )
  456.   local data = textutils.unserialize( f.readAll() )
  457.   f.close()
  458.   -- validate the date
  459.   if type( data ) == 'table' then
  460.     writeLog( 'Loaded save file ' .. fileName )
  461.     file = data
  462.   else
  463.     writeLog( 'Invalid save file' )
  464.     fileName = nil
  465.   end
  466. end
  467.  
  468. local friend, host
  469. local name = args[3] or args[2]
  470. -- if we are the host, load options from the file and host the game on rednet
  471. if args[1] == 'host' then
  472.   host = true
  473.   options = file.options or {}
  474.   writeLog( 'Hosting game with name "' .. name .. '"' )
  475.   rednet.host( 'stratego', name )
  476.   writeLog( 'Waiting for a friend. ' .. ( isColor and 'Click the + to cancel' or 'Press "end" to cancel' ) )
  477.   while true do
  478.     -- wait for either a message from the guest, or a prompt from the user to cancel
  479.     local event, data, x, y = os.pullEvent()
  480.     -- a message means start the game, so send the options to the other player
  481.     if event == 'rednet_message' and y == 'stratego' then
  482.       writeLog( 'Friend found, sending options' )
  483.       -- store the ID for later
  484.       friend = data
  485.       rednet.send( friend, options, 'stratego-options' )
  486.       writeLog( 'Starting game' )
  487.       break
  488.     -- user's prompt (click on advance systems and "end" on normal ones) means cancel
  489.     elseif isColor and ( event == 'mouse_click' and x == size and y == 1 ) or ( event == 'key' and data == keys['end'] ) then
  490.       shutdown( 'Canceled game' )
  491.       return
  492.     end
  493.   end
  494.   -- the host is always red, and it helps to have these for later
  495.   myTeam = 'red'
  496.   notMyTeam = 'blue'
  497. else
  498.   -- if we are the guest, loop up the host
  499.   writeLog( 'Looking up host ' .. name )
  500.   local ID = rednet.lookup( 'stratego', name )
  501.   if ID then
  502.     -- once we find them, send a blank message so they get our ID
  503.     writeLog( 'Found host with an ID of ' .. ID )
  504.     -- store the ID of the host for later
  505.     friend = ID
  506.     rednet.send( friend, '', 'stratego' )
  507.     writeLog( 'Waiting for options' )
  508.     while true do
  509.       -- then wait for the options
  510.       local ID, data = rednet.receive( 'stratego-options' )
  511.       if ID == friend then
  512.         options = data
  513.         break
  514.       end
  515.     end
  516.     writeLog( 'Starting game' )
  517.   else
  518.     -- if it times out, shutdown the game
  519.     shutdown( 'Host name ' .. name .. ' not found on network' )
  520.     return
  521.   end
  522.   -- the guest is always blue, and it helps to have these for later
  523.   myTeam = 'blue'
  524.   notMyTeam = 'red'
  525. end
  526.  
  527. -- define the rows for the setup click
  528. rows = math.min( options.rows or 3, ( options.rotate and 5 or 4 ) )
  529.  
  530. -- how to draw large pieces
  531. local largePieces = {
  532.   ['*'] = { '\\ ', '**' }, -- bomb
  533.   ['!'] = { '^^',  '10' }, -- 10
  534.     S   = { '^^',  'S ' }, -- spy
  535.     F   = { '|>',  '| ' }, -- flag
  536.  
  537.   ['?'] = { '??',  '??' }, -- unknown opponent
  538.  
  539.   [' '] = { '  ',  '  ' }, -- empty space
  540.   ['~'] = { '~~',  '~~' }  -- lake
  541. }
  542.  
  543. -- function to draw a piece
  544. function writePiece( x, y, team, piece, sel )
  545.   -- if a piece is given, then add it to the board
  546.   if piece then
  547.     pieces[y][x] = { team, piece }
  548.     -- set the text to the team color, or the selection color if we are selecting
  549.     board.setTextColor( sel and color.textSelect or color[team] )
  550.     -- but don't show us the piece if it's an opponent
  551.     if team == notMyTeam then
  552.       piece = '?'
  553.     end
  554.   -- otherwise clear the space
  555.   else
  556.     pieces[y][x] = nil
  557.     piece = ' '
  558.   end
  559.   -- if the piece is a lake (used on startup), set the background as the lake color
  560.   if team == 'lake' then
  561.     board.setBackgroundColor( color.lakeBg )
  562.   -- if we are selecting it, set the background color as the team color
  563.   elseif sel then
  564.     board.setBackgroundColor( color[team .. 'Select'] )
  565.   -- otherwise apply the "pos" and "neg" colors in a checkerboard
  566.   elseif ( x + y ) % 2 == 0 then
  567.     board.setBackgroundColor( color.pos )
  568.   else
  569.     board.setBackgroundColor( color.neg )
  570.   end
  571.   -- if we are using a large board, use double sized pieces
  572.   if display.board.large then
  573.     local draw = largePieces[piece] or { '^^', ' ' .. piece }
  574.     -- and double the draw coordinates
  575.     x = ( x * 2 ) - 1
  576.     y = ( y * 2 ) - 1
  577.     board.setCursorPos( x, y )
  578.     board.write( draw[1] )
  579.     board.setCursorPos( x, y + 1 )
  580.     board.write( draw[2] )
  581.   else
  582.     -- otherwise just write as is
  583.     board.setCursorPos( x, y )
  584.     board.write( piece )
  585.   end
  586.   -- for some reason the text color gets used by term later...
  587.   term.setTextColor( colors.white )
  588. end
  589.  
  590. -- where to place lakes, in pairs
  591. local lakes = options.lakes or {
  592.   x = { 3, 4, 7, 8, 3, 4, 7, 8 },
  593.   y = { 4, 4, 4, 4, 5, 5, 5, 5 }
  594. }
  595. -- place the lakes
  596. for i, x in pairs( lakes.x ) do
  597.   writePiece( x, lakes.y[i], 'lake', '~' )
  598. end
  599.  
  600. -- function to save a game
  601. local turn = options.blueFirst and 'blue' or 'red'
  602. function save()
  603.   -- we are running a loop inside a loop, so use a variable to break this one
  604.   local finished
  605.   while not finished do
  606.     -- prompt for the file name to save at
  607.     inputWrite( 'Filename' )
  608.     local data = input() .. config.ext or ''
  609.     -- we are finished unless tole otherwise later
  610.     finished = true
  611.     -- if it exists, prompt to overwrite
  612.     if fs.exists( data ) then
  613.       inputWrite( 'Overwrite?' )
  614.       while true do
  615.         local data = input( '(Y/N): ' ):upper()
  616.         -- if we choose to overwrite, we are done
  617.         if data == 'Y' then
  618.           break
  619.         -- otherwise restart
  620.         elseif data == 'N' then
  621.           finished = false
  622.           break
  623.         end
  624.       end
  625.     end
  626.     -- only run this stuff if we choose to overwrite or no conflict existed
  627.     if finished then
  628.       local f = fs.open( data, 'w' )
  629.       f.write( textutils.serialize( { options = { rotate = options.rotate }, turn = turn, pieces = pieces } ) )
  630.       f.close()
  631.     end
  632.   end
  633. end
  634.  
  635. -- function for when the button is pressed
  636. function button()
  637.   while true do
  638.     -- prompt for options
  639.     local data = input( 'Menu: ' ):lower()
  640.     -- "save" means we save the game, then go right back into it
  641.     if host and ( data == 'save' or data == 's' ) then
  642.       save()
  643.       return click()
  644.     -- "quit" will prompt for saving if you are the host, then will end the game
  645.     elseif data == 'quit' or data == 'q' then
  646.       if host then
  647.         while true do
  648.           local data = input( 'Save? ' ):lower()
  649.           if data == 'yes' or data == 'y' then
  650.             save()
  651.             return 'save'
  652.           elseif data == 'no' or 'n' then
  653.             break
  654.           end
  655.         end
  656.       end
  657.       return 'quit'
  658.     -- "forfeit" will cause you to be the loser, needed if you run out of moves
  659.     elseif data == 'forfeit' or data == 'f' then
  660.       return 'forfeit'
  661.     -- a blank call means close the menu
  662.     elseif data:gsub( ' ', '' ) == '' then
  663.       return click()
  664.     end
  665.   end
  666. end
  667.  
  668. -- some pieces are not numerical, so give them numerical ranks
  669. -- note the flag has no battle value, as it just overrides things later
  670. local battleValues = {
  671.   ['*'] = 11,
  672.   ['!'] = 10,
  673.   S = 0
  674. }
  675.  
  676. local done, winner, canSpot
  677. -- function for when two pieces battle
  678. function battle( attacker, defender )
  679.   local team1 = attacker[1]
  680.   local team2 = defender[1]
  681.   local attacker = attacker[2]
  682.   local defender = defender[2]
  683.   -- if the flag was captured, set victory to our team
  684.   if defender == 'F' then
  685.     writeLog( 'The ' .. names[team2] .. ' flag has been captured' )
  686.     done = true
  687.     winner = team1
  688.     return 'victory'
  689.   -- miners defuse bombs
  690.   elseif attacker == 3 and defender == '*' then
  691.     writeLog( 'The ' .. names[team1] .. ' 3 has defused the ' .. names[team2] .. ' bomb' )
  692.     return true
  693.   -- the spy can kill the 10, but only as the attacker
  694.   elseif attacker == 'S' and defender == '!' then
  695.     writeLog( 'The ' .. names[team1] .. ' spy has killed the ' .. names[team2] .. ' 10' )
  696.     return true
  697.   else
  698.     local attackerName = names[attacker] or attacker
  699.     local defenderName = names[defender] or defender
  700.     local attacker = battleValues[attacker] or attacker
  701.     local defender = battleValues[defender] or defender
  702.     if attacker == defender then
  703.       writeLog( 'The ' .. names[team1] .. ' ' .. attackerName .. ' has met his match, both pieces were killed' )
  704.       return 'tie'
  705.     elseif attacker > defender then
  706.       writeLog( 'The ' .. names[team1] .. ' ' .. attackerName .. ' killed the ' .. names[team2] .. ' ' .. defenderName )
  707.       return true
  708.     elseif attacker < defender then
  709.       writeLog( 'The ' .. names[team1] .. ' ' .. attackerName .. ' was killed by the ' .. names[team2] .. ' ' .. defenderName )
  710.       return false
  711.     end
  712.   end
  713. end
  714.  
  715. -- a list of all valid piece calls. Used for the spotter and game setup
  716. local validPiece = {
  717.   S     = 'S', SPY  = 'S',
  718.   F     = 'F', FLAG = 'F',
  719.   ['*'] = '*', BOMB = '*', B = '*',
  720.   ['!'] = '!', [10] = '!',
  721.   [9] = 9,
  722.   [8] = 8,
  723.   [7] = 7,
  724.   [6] = 6,
  725.   [5] = 5,
  726.   [4] = 4,
  727.   [3] = 3,
  728.   [2] = 2,
  729.   [1] = 1, SPOTTER = 1
  730. }
  731. -- function for an attack with the spotter
  732. function spotter( x, y, override )
  733.   local team = pieces[y][x][1]
  734.   local rotate = options.rotate
  735.   local y2 = rotate and y or ( y + ( team == 'red' and -1 or 1 ) )
  736.   local x2 = rotate and ( x + ( team == 'red' and 1 or -1 ) ) or x
  737.   local defender = pieces[y2][x2]
  738.   if not defender or defender[1] ~= ( team == 'red' and 'blue' or 'red' ) then
  739.     return false
  740.   end
  741.   writeLog( 'The ' .. names[team] .. ' spotter is attempting to snipe the piece at ' .. x2 .. ',' .. y2 )
  742.   while true do
  743.     local call
  744.     if override then
  745.       call = override
  746.     else
  747.       inputWrite( 'Spotter' )
  748.       call = input( 'Call: ' )
  749.       if call:gsub( ' ', '' ) == '' then
  750.         return false
  751.       elseif tonumber( call ) then
  752.         call = tonumber( call )
  753.       else
  754.         call = call:upper()
  755.       end
  756.       call = validPiece[call]
  757.     end
  758.     if call then
  759.       local notteam = ( team == 'red' and 'blue' or 'red' )
  760.       if defender[2] == call then
  761.         writeLog( 'The ' .. names[team] .. ' spotter has sniped the ' .. names[notteam] .. ' ' .. ( names[defender[2]] or defender[2] ) )
  762.         if defender[2] == 'F' then
  763.           done = true
  764.           winner = team
  765.         end
  766.         writePiece( x2, y2 )
  767.       else
  768.         writeLog( 'The ' .. names[team] .. ' spotter failed to snipe the ' .. names[notteam] .. ' ' .. ( names[defender[2]] or defender[2] ) )
  769.       end
  770.       return call
  771.     else
  772.       writeLog( 'Invalid call, recall' )
  773.     end
  774.   end
  775. end
  776.  
  777. -- function to move a piece
  778. function move( x1, y1, x2, y2 )
  779.   local piece = pieces[y1][x1]
  780.   -- scout movement
  781.   if not piece then
  782.     writeLog( 'No piece' )
  783.   elseif piece[2] == 2 then
  784.     -- check that the scout only moved in one direction
  785.     if not (( x1 == x2 ) or ( y1 == y2 )) then
  786.       writeLog( 'Invalid move' )
  787.       return false
  788.     else
  789.       -- and that no pieces or lakes are in the way
  790.       -- we use the temporary variables "c1" and "c2" to make it easier to loop through x or y
  791.       local c1, c2, s
  792.       if x1 == x2 then
  793.         c1 = math.min( y1, y2 )
  794.         c2 = math.max( y1, y2 )
  795.       else
  796.         c1 = math.min( x1, x2 )
  797.         c2 = math.max( x1, x2 )
  798.       end
  799.       for c = c1, c2 do
  800.         local piece
  801.         -- ignore the start and end points
  802.         if c == c1 or c == c2 then
  803.           piece = false
  804.         -- and check again of if we are using x or y, as the c variables don't keep track of which one
  805.         elseif x1 == x2 then
  806.           piece = pieces[c][x1]
  807.         else
  808.           piece = pieces[y1][c]
  809.         end
  810.         if piece then
  811.           writeLog( 'Invalid move' )
  812.           return false
  813.         end
  814.       end        
  815.     end
  816.   -- for everyone else, check that we only moved one space
  817.   elseif math.abs( x2 - x1 ) + math.abs( y2 - y1 ) ~= 1 then
  818.     writeLog( 'Invalid move' )
  819.     return false
  820.   end
  821.   -- state the action, but make sure not to state the piece rank
  822.   writeLog( names[piece[1]]:gsub( '^%l', string.upper ) .. ' moved from ' .. x1 .. ',' .. y1 .. ' to ' .. x2 .. ',' .. y2 )
  823.   local destination = pieces[y2][x2]
  824.   local result
  825.   -- if someone already exists at the target, then battle
  826.   if destination then
  827.     result = battle( piece, destination )
  828.   end
  829.   -- if the battle was a tie, kill the target space's piece
  830.   if result == 'tie' then
  831.     writePiece( x2, y2 )
  832.   -- otherwise if we won or there was no battle, place the current piece at the target
  833.   elseif result or not destination then
  834.     writePiece( x2, y2, piece[1], piece[2] )
  835.   end
  836.   -- clear the current space anyways and return success
  837.   writePiece( x1, y1 )
  838.   return result or true
  839. end
  840.  
  841. local maxPieces = options.maxPieces or {
  842.   S = 1, F = 1, ['!'] = 1, [9] = 1, [8] = 1, [7] = 1,
  843.   [6] = 2, [5] = 2, [4] = 2, [1] = 2,
  844.   [3] = 5, [2] = 5,
  845.   ['*'] = 6
  846. }
  847. -- set maximums for pieces and flags
  848. local maxRequiredPieces = rows * ( options.rotate and 8 or 10 )
  849. local requiredPieces = math.min( options.requiredPieces or maxRequiredPieces, maxRequiredPieces )
  850. local requiredFlags = math.min( options.requiredFlags or 1, 10 )
  851.  
  852. -- called with a file to load the file or run by both side to load piece setup
  853. function setup()
  854.   writeLog( 'Place pieces in the first ' .. rows .. ( options.rotate and ' columns. ' or ' rows. ' ) .. ( isColor and 'Press the + when finished' or 'Type "done" when finished' ) )
  855.   -- tell the user how many flags are required, normally is just 1
  856.   local flagText
  857.   if requiredFlags > 0 then
  858.     flagText = requiredFlags .. ( requiredFlags == 1 and ' flag is required' or ' flags are required' )
  859.     writeLog( flagText )
  860.   end
  861.   -- keep track of pieces for the sake of the "done" button and not placing too many
  862.   local usedPieces = {}
  863.   local placedPieces = 0
  864.   inputWrite( 'Place pieces' )
  865.   while true do
  866.     -- wait for a space to be clicked
  867.     local x,y = click( 'setup' )
  868.     -- if the user clicked the done button, check if they placed enough pieces and flags
  869.     if x == 'done' then
  870.       if placedPieces == requiredPieces then
  871.         if ( usedPieces.F or 0 ) < requiredFlags then
  872.           while true do
  873.             -- if they are not equal, loop until the user does something other than press the done button
  874.             writeLog( flagText )
  875.             x,y = click( 'setup' )
  876.             if x ~= 'done' then
  877.               break
  878.             end
  879.           end
  880.         else
  881.           inputClear()
  882.           inputClear(1)
  883.           inputClear(2)
  884.           break
  885.         end
  886.       else
  887.         while true do
  888.           -- if there is not enough, loop until the user does something other than press the done button
  889.           writeLog( 'Not enough pieces placed' )
  890.           x,y = click( 'setup' )
  891.           if x ~= 'done' then
  892.             break
  893.           end
  894.         end
  895.       end
  896.     end
  897.     -- show selected space
  898.     inputWrite( x .. ',' .. y, 2 )
  899.     -- if a piece is already there, remove it from the count (we will remove it from the board with the selection later
  900.     if pieces[y][x] then
  901.       local piece = pieces[y][x][2]
  902.       usedPieces[piece] = usedPieces[piece] - 1
  903.       placedPieces = placedPieces - 1
  904.     end
  905.     -- don't allow a piece to be placed if we are out of pieces
  906.     if placedPieces == requiredPieces then
  907.       writeLog( 'Out of pieces' )
  908.     else
  909.       -- select the space
  910.       writePiece( x, y, myTeam, nil, true )
  911.       -- tell the user how many pieces remain
  912.       -- TODO: make it display properly on smaller screens
  913.       writeLog( 'Available: (place ' .. requiredPieces - placedPieces .. ' more)' )
  914.       for k, v in pairs( maxPieces ) do
  915.         local remaining = v - ( usedPieces[k] or 0 )
  916.         if remaining ~= 0 then
  917.           writeLog( '* ' .. ( names[k] or k ) .. ': ' .. remaining )
  918.         end
  919.       end
  920.       while true do
  921.         -- if we passed all other steps, ask the user what piece to place
  922.         local data = input( 'Piece: ' )
  923.         -- turn strings into numbers or uppercase (as that is how it's stored
  924.         if tonumber( data ) then
  925.           data = tonumber( data )
  926.         else
  927.           data = data:upper()
  928.         end
  929.         -- if the piece is valid
  930.         local piece = validPiece[data]
  931.         if piece then
  932.           -- if we are out of the piece, ask the user to type the name again
  933.           if usedPieces[piece] == maxPieces[piece] then
  934.             writeLog( 'Out of ' .. ( names[piece] or piece ) .. "s" )
  935.           else
  936.             -- otherwise place the piece and increase the counters for that piece and total pieces
  937.             writePiece( x, y, myTeam, piece )
  938.             usedPieces[piece] = ( usedPieces[piece] or 0 ) + 1
  939.             placedPieces = placedPieces + 1
  940.             break
  941.           end
  942.         -- if the input is blank or only contains spaces, then just remove the selection
  943.         elseif data:gsub( ' ', '' ) == '' then
  944.           writePiece( x, y )
  945.           break
  946.         else
  947.           writeLog( 'Invalid piece' )
  948.         end
  949.       end
  950.       inputClear(2)
  951.     end
  952.   end
  953.   -- once done, host the name under "stratego-done" so the other user sees it upon finishing
  954.   writeLog( 'Waiting for other player' )
  955.   rednet.host( 'stratego-done', name .. ( host and 1 or 0 ) )
  956.   while true do
  957.     local found = rednet.lookup( 'stratego-done', name .. ( host and 0 or 1 ) )
  958.     if found == friend then
  959.       -- once we find the other user is done, start sending pieces back and fourth
  960.       if not host then
  961.         -- the guest sends its pieces to the host
  962.         writeLog( 'Sending pieces to host' )
  963.         os.sleep( 2 ) -- delay to compensate for rednet.lookup
  964.         rednet.send( friend, pieces, 'stratego-pieces-guest' )
  965.         writeLog( 'Waiting for combined pieces' )
  966.         -- then waits to get the host's combination
  967.         while true do
  968.           local ID, data = rednet.receive( 'stratego-pieces-host' )
  969.           if ID == friend then
  970.             writeLog( 'Received pieces from host' )
  971.             pieces = data
  972.             break
  973.           end
  974.         end
  975.       else
  976.         -- the host waits to receive the guest's pieces
  977.         writeLog( 'Waiting for pieces from guest' )
  978.         while true do
  979.           local ID, data = rednet.receive( 'stratego-pieces-guest' )
  980.           if ID == friend then
  981.             writeLog( 'Received pieces from guest' )
  982.             -- then combines the two and sends them back to the guest
  983.             -- if for some reason a value exists in both the guest and host, the host's piece is kept
  984.             -- though this should never happen except for lakes (which are in the same spots on both sides already)
  985.             for x = 1, 10 do
  986.               for y = 1, 8 do
  987.                 pieces[y][x] = pieces[y][x] or data[y][x]
  988.               end
  989.             end
  990.             writeLog( 'Sending combined pieces to guest' )
  991.             rednet.send( friend, pieces, 'stratego-pieces-host' )
  992.             break
  993.           end
  994.         end
  995.       end
  996.       -- wait to unhost until after we finished everything, as otherwise lag can cause both systems to be stuck waiting
  997.       rednet.unhost( 'stratego-done', name .. ( host and 1 or 0 ) )
  998.       break
  999.     end
  1000.   end
  1001.   writeLog( 'Setup complete, starting game' )  
  1002. end
  1003.  
  1004. -- startup
  1005. if host then
  1006.   -- if we loaded a file and it contains pieces, load it instead of running setup
  1007.   if file and file.pieces then
  1008.     rednet.send( friend, file, 'stratego-setup' )
  1009.     pieces = file.pieces
  1010.     turn = file.turn or turn
  1011.   else
  1012.     -- otherwise send a message to the guest indicating we are running setup
  1013.     -- a blank string is used, since the guest uses the type of data to determine the action
  1014.     rednet.send( friend, '', 'stratego-setup' )
  1015.     setup()
  1016.   end
  1017. else
  1018.   -- wait for the host to tell us whether we already have pieces, or still need to load them
  1019.   while true do
  1020.     local ID, file = rednet.receive( 'stratego-setup' )
  1021.     if ID == friend then
  1022.       -- if the file is a table, we know it is the pieces
  1023.       if type( file ) == 'table' then
  1024.         writeLog( 'Loading pieces from host' )
  1025.         pieces = file.pieces
  1026.         turn = file.turn or turn
  1027.       else
  1028.         setup()
  1029.       end
  1030.       break
  1031.     end
  1032.   end
  1033. end
  1034.  
  1035. -- after setting up, write all pieces to the board
  1036. -- as we either loaded setup pieces from the guest/host, or from a file
  1037. for y = 1, 8 do
  1038.   for x = 1, 10 do
  1039.     local piece = pieces[y][x]
  1040.     -- if we don't have a piece, we need to draw a blank space to make sure any improperly added pieces are overwritten
  1041.     -- shouldn't happen, but just in case
  1042.     if piece then
  1043.       writePiece( x, y, pieces[y][x][1], pieces[y][x][2] )
  1044.     else
  1045.       writePiece( x, y, nil, nil )
  1046.     end
  1047.   end
  1048. end
  1049.  
  1050. -- main loop, using the variable "done" to determine completion (as we use multiple loops inside)
  1051. while not done do
  1052.   -- if its our turn
  1053.   if turn == myTeam then
  1054.     -- collecting data of the turn
  1055.     local act = {}
  1056.     inputWrite( 'Move piece' )
  1057.     while true do
  1058.       while true do
  1059.         local x, y = click()
  1060.         -- if we choose to quit, then quit the game and end the program
  1061.         if x == 'quit' or x == 'save' then
  1062.           rednet.send( friend, 'quit', 'stratego-action' )
  1063.           if x == 'save' then
  1064.             shutdown( 'Game saved and quit' )
  1065.           else
  1066.             shutdown( 'Quit game' )
  1067.           end
  1068.           return
  1069.         -- if we choose to forfeit, then set the winner to the opponent and break the loop
  1070.         elseif x == 'forfeit' then
  1071.           act[1] = 'forfeit'
  1072.           writeLog( 'You forfeited' )
  1073.           winner = notMyTeam
  1074.           done = true
  1075.           break
  1076.         -- if the piece cannot move, don't select it
  1077.         elseif  ( y + 1 >  8 or ( pieces[y + 1][x] and pieces[y + 1][x][1] ~= notMyTeam ) )
  1078.         and ( y - 1 <  1 or ( pieces[y - 1][x] and pieces[y - 1][x][1] ~= notMyTeam ) )
  1079.         and ( x + 1 > 10 or ( pieces[y][x + 1] and pieces[y][x + 1][1] ~= notMyTeam ) )
  1080.         and ( x - 1 <  1 or ( pieces[y][x - 1] and pieces[y][x - 1][1] ~= notMyTeam ) ) then
  1081.           writeLog( 'Piece cannot move' )
  1082.         -- otherwise select it
  1083.         else
  1084.           -- display selection
  1085.           inputWrite( x .. ',' .. y, 2 )
  1086.           writePiece( x, y, myTeam, pieces[y][x][2], true )
  1087.           -- and set the data for later use
  1088.           act[1] = x
  1089.           act[2] = y
  1090.           break
  1091.         end
  1092.       end
  1093.       -- if we forfeited, our turn is over
  1094.       if act[1] == 'forfeit' then
  1095.         break
  1096.       -- otherwise if we are a spotter, run the spotter function
  1097.       elseif pieces[act[2]][act[1]][2] == 1 then
  1098.         act[3] = spotter( act[1], act[2] )
  1099.       end
  1100.       -- if the spotter could spot and spotted, then the turn is done
  1101.       if act[3] then
  1102.         -- deselect the piece
  1103.         writePiece( act[1], act[2], myTeam, 1 )
  1104.         break
  1105.       -- otherwise proceed with movement
  1106.       else
  1107.         local canSpot
  1108.         while true do
  1109.           -- another click, but this time check for deselection
  1110.           local x, y = click( 'attack', act[1], act[2] )
  1111.           -- if we cancel, deselect the piece and go back to the first loop
  1112.           if x == 'cancel' then
  1113.             inputClear(2)
  1114.             writePiece( act[1], act[2], myTeam, pieces[act[2]][act[1]][2] )
  1115.             break
  1116.           -- need to check if we can spot before moving, as the target data gets replaced later on
  1117.           elseif not pieces[y][x] and pieces[act[2]][act[1]][2] == 1 then
  1118.             canSpot = true
  1119.           else
  1120.             canSpot = false
  1121.           end
  1122.           -- move the piece
  1123.           local success = move( act[1], act[2], x, y )
  1124.           -- if the move was successful, set the data, otherwise we retry the click
  1125.           if success then
  1126.             act[3] = x
  1127.             act[4] = y
  1128.             break
  1129.           end
  1130.           -- no need to use the spotter's ability if we just won
  1131.           if success == 'victory' then
  1132.             canSpot = false
  1133.           end
  1134.         end
  1135.         -- if we can spot and successfully moved, then run the spotter
  1136.         if canSpot and act[3] and pieces[act[4]][act[3]] then
  1137.           act[5] = spotter( act[3], act[4] )
  1138.           break
  1139.         -- otherwise just stop if we successfully moved, or restart the whole loop if not
  1140.         elseif act[3] then
  1141.           break
  1142.         end
  1143.       end
  1144.     end
  1145.     -- turn is over, so send the results to the other player and set the turn to them
  1146.     rednet.send( friend, act, 'stratego-action' )
  1147.     turn = notMyTeam
  1148.   else
  1149.     --if it's not our turn, wait for information on the opponents turn
  1150.     inputWrite( 'Waiting...' )
  1151.     inputClear(1)
  1152.     inputClear(2)
  1153.     while true do
  1154.       local ID, act = rednet.receive( 'stratego-action' )
  1155.       if ID == friend then
  1156.         -- found information
  1157.         -- if the opponent choose to quit, then quit as well
  1158.         if act == 'quit' then
  1159.           -- and prompt to save the game if we are the host
  1160.           if host then
  1161.             writeLog( 'Game quit by guest' )
  1162.             inputWrite( 'Save game?' )
  1163.             while true do
  1164.               local data = input( '(Y/N): ' ):upper()
  1165.               if data == 'Y' then
  1166.                 save()
  1167.                 break
  1168.               elseif data == 'N' then
  1169.                 break
  1170.               end
  1171.             end
  1172.             shutdown()
  1173.           else
  1174.             shutdown( 'Game quit by host' )
  1175.           end
  1176.           return
  1177.         -- if the opponent forfeited, then set the winner to us, tell the player, and exit the main loop
  1178.         elseif act[1] == 'forfeit' then
  1179.           winner = myTeam
  1180.           writeLog( 'Opponent forfeited' )
  1181.           done = true
  1182.         -- otherwise check if the opponent used a spotter, and if so run the spotter function
  1183.         elseif not act[4] and pieces[act[2]][act[1]][2] == 1 then
  1184.           spotter( act[1], act[2], act[3] )
  1185.         -- otherwise, assume a movement
  1186.         else
  1187.           -- need to check if we can spot before moving, as the target data gets replaced later on
  1188.           -- this is mainly an anti-cheat thing, to prevent a player from both moving and spotting
  1189.           local canSpot
  1190.           if not pieces[act[4]][act[3]] and pieces[act[2]][act[1]][2] == 1 then
  1191.             canSpot = true
  1192.           end
  1193.           -- move the piece, then if we can spot, run the spotter function
  1194.           move( act[1], act[2], act[3], act[4] )
  1195.           if canSpot and act[5] then
  1196.             spotter( act[3], act[4], act[5] )
  1197.           end
  1198.         end
  1199.         -- since we found something, break out of the loop
  1200.         break
  1201.       end
  1202.     end
  1203.     -- and it's our turn next time
  1204.     turn = myTeam
  1205.   end
  1206. end
  1207.  
  1208. -- at the end of the game, state the winner then delay a bit for both sides to read it
  1209. writeLog( winner:gsub( '^%l', string.upper ) .. ' is the winner' )
  1210. os.sleep( 5 )
  1211.  
  1212. -- if we are the host and the file is a save game, prompt to delete it
  1213. if host and fileName and file.pieces then
  1214.   inputWrite( 'Delete game?' )
  1215.   while true do
  1216.     local data = input( '(Y/N): ' ):upper()
  1217.     if data == 'Y' then
  1218.       fs.delete( fileName )
  1219.       break
  1220.     elseif data == 'N' then
  1221.       break
  1222.     end
  1223.   end
  1224. end
  1225.  
  1226. -- and final shutdown stuff
  1227. shutdown()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement