Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- Stratego version B2.1 by KnightMiner
- -- check if we are hosting or joining a game
- local args = { ... }
- if #args > 3 or #args < 2 or ( args[1] ~= 'host' and args[1] ~= 'join' ) then
- print( 'Usages:' )
- print( 'stratego host [save-game] <name>' )
- print( 'stratego join <name>' )
- return
- end
- -- find a modem to host/join a game on
- local openedModem, foundModem
- for _, modem in ipairs( peripheral.getNames() ) do
- if peripheral.getType( modem ) == "modem" then
- if not rednet.isOpen( modem ) then
- rednet.open( modem )
- openedModem = modem
- end
- foundModem = true
- break
- end
- end
- if not foundModem then
- print( "No modems found" )
- return
- end
- -- function for shutdown, including closing the modem and clearing the screen
- -- it also prints an optional message before the "Thanks" message
- function shutdown( msg )
- shell.run( 'clear' )
- if openedModem then
- rednet.close( openedModem )
- end
- if msg then
- print( msg )
- end
- print( 'Thank you for playing Stratego by KnightMiner' )
- end
- -- load the config from the file "stratego.cfg"
- -- note that the file needs to be in the current directory to load,
- -- getting it to load from the same directory as the game proves quite hard
- local config, configMsg
- local cfgFile = shell.resolve( 'stratego.cfg' )
- if fs.exists( cfgFile ) then
- local file = fs.open( cfgFile, 'r' )
- config = textutils.unserialize( file.readAll() )
- file.close()
- if not config then
- -- we write the message as a variable as the writeLog function requires the config to load,
- -- and any print will be overwritten in the next step
- configMsg = 'Failed to load config'
- else
- configMsg = 'Successfully loaded config'
- end
- end
- -- if the config table is invalid or we didn't load one, replace it with a blank table
- config = config or {}
- -- define display stuff based on computer type
- shell.run( 'clear' )
- local display
- -- pocket computers, which define an empty "pocket" table
- if pocket then
- display = {
- write = {
- '+----------+-------------+',
- '| | |',
- '| | Stratego |',
- '| | |',
- '| | |',
- '| +-------------+',
- '| | |',
- '| | |',
- '| | |',
- '+----------+-------------+',
- '| |',
- '| |',
- '| |',
- '| |',
- '| |',
- '| |',
- '| |',
- '| |',
- '| |',
- '| |'
- },
- input = { 13, 7, 13 },
- log = { 2, 11, 24, 11 },
- board = { 2, 2 }
- }
- -- turtle computers, with the "turtle" api
- elseif turtle then
- display = {
- write = {
- '+----------+----------+---------------+',
- '| | | |',
- '| | Stratego | |',
- '| | | |',
- '| +----------+---------------+',
- '| | |',
- '| | |',
- '| | |',
- '| | |',
- '+----------+ |',
- '+----------+ |',
- '+----------+ |',
- '+----------+ |'
- },
- input = { 24, 2, 15 },
- log = { 13, 6, 26, 9 },
- board = { 2, 2 }
- }
- -- standard computers
- else
- display = {
- write = {
- '+--------------------+----------+-----------------+',
- '| | | |',
- '| | Stratego | |',
- '| | | |',
- '| +----------+-----------------+',
- '| | |',
- '| | |',
- '| | |',
- '| | |',
- '| | |',
- '| | |',
- '| | |',
- '| | |',
- '| | |',
- '| | |',
- '| | |',
- '| | |',
- '+--------------------+ |',
- '+--------------------+ |'
- },
- input = { 34, 2, 17 },
- log = { 23, 6, 28, 15 },
- board = { 2, 2, large = true }
- }
- end
- -- if there is config display stuff to load, overwrite any values with ones from the config
- if config.display then
- for k in pairs( config.display ) do
- display[k] = config.display[k]
- end
- end
- -- fixes for multishell
- if multishell and multishell.getCount() > 1 then
- table.remove( display.write )
- display.log[4] = display.log[4] - 1
- end
- -- draw the game board's borders
- write( table.concat( display.write, '\n' ) )
- -- create the output log as a window
- local logBox = window.create( term.current(), display.log[1], display.log[2], display.log[3], display.log[4] )
- function writeLog( ... )
- local prev = term.redirect( logBox )
- print( ... )
- term.redirect( prev )
- end
- -- write the config message from eariler
- -- helps with debug in case you are wondering why your config is not working
- if configMsg then
- writeLog( configMsg )
- end
- -- create the input box as a window
- local inputBox = window.create( term.current(), display.input[1], display.input[2], display.input[3], 3 )
- -- function to clear the input box
- function inputClear( n )
- inputBox.setCursorPos( 1, 1 + ( n or 0 ) )
- inputBox.clearLine()
- end
- -- function to get user input
- function input( msg )
- inputBox.setCursorPos( 1, 2 )
- inputBox.clearLine()
- if msg then
- inputBox.write( msg )
- end
- return read()
- end
- -- function to write to the input box
- function inputWrite( msg, n )
- inputClear( n )
- inputBox.setCursorPos( 1, 1 + ( n or 0 ) )
- inputBox.clearLine()
- inputBox.write( msg )
- end
- -- define the pieces table, pieces will be added later
- local pieces = {
- [1] = {},
- [2] = {},
- [3] = {},
- [4] = {},
- [5] = {},
- [6] = {},
- [7] = {},
- [8] = {}
- }
- -- check if a click is valid
- -- used within the main click function, which is defined based on whether a mouse exists
- local myTeam, notMyTeam, options, rows
- function validClick( x, y, mode, px, py )
- -- location is out of bounds
- if x > 10 or x < 1 or y > 8 or y < 1 then
- writeLog( 'Invalid location' )
- return false
- -- location is beyond the legal columns to set up pieces
- -- note that "rows" and "options" are both defined later, but the click function is needed before they are defined
- elseif mode == 'setup' and options.rotate and ( myTeam == 'red' and ( x > rows ) or myTeam == 'blue' and ( x < ( 11 - rows ))) then
- writeLog( 'Cannot place pieces beyond first ' .. rows .. ' columns' )
- return false
- -- location is beyond the legal rows to set up pieces
- elseif mode == 'setup' and not options.rotate and ( myTeam == 'red' and ( y < ( 9 - rows ) ) or myTeam == 'blue' and ( y > rows )) then
- writeLog( 'Cannot place pieces beyond first ' .. rows .. ' rows' )
- return false
- -- check here if we are deselecting the piece, as otherwise it fails later as being the same team
- elseif mode == 'attack' and x == px and y == py then
- return 'cancel'
- else
- -- check reasons we are not allowed to move
- local piece = pieces[y][x]
- -- if no piece exists, then
- if not piece then
- -- if we are setting up or attacking, that is fine
- if mode then
- return true
- else
- -- otherwise we are moving, where we must start at a piece
- writeLog( 'No piece here' )
- return false
- end
- -- if the piece is a lake
- elseif piece[1] == 'lake' then
- if mode == 'setup' then
- writeLog( 'Cannot place pieces on lakes' )
- elseif mode == 'attack' then
- writeLog( 'Cannot move onto lakes' )
- else
- writeLog( 'Cannot move lakes' )
- end
- return false
- -- if the mode is not "attack" and the piece is not my team
- elseif mode ~= 'attack' and piece[1] == notMyTeam then
- writeLog( 'Cannot move other team' )
- return false
- -- if
- elseif mode == 'attack' and piece[1] == myTeam then
- writeLog( 'Cannot move on to own team' )
- return false
- -- if the piece cannot move, either flags or bombs
- elseif not mode and ( piece[2] == 'F' or piece[2] == '*' ) then
- writeLog( 'Immovable piece' )
- return false
- -- if all cases pass as false, return movable
- else
- return true
- end
- end
- end
- -- determine if we are using an advance system or a regular one
- -- then define the colors and the click() function based on that
- local names = {
- ['*'] = 'bomb',
- ['!'] = 10,
- S = 'spy',
- F = 'flag',
- [1] = 'spotter'
- }
- local color = {}
- local size = term.getSize()
- local click, button
- local isColor = term.isColor()
- if isColor then
- names.red = 'red'
- names.blue = 'blue'
- color = {
- red = colors.red,
- redSelect = colors.orange,
- blue = colors.blue,
- blueSelect = colors.purple,
- textSelect = colors.white,
- lake = colors.lightBlue,
- lakeBg = colors.blue,
- pos = colors.lime,
- neg = colors.green
- }
- term.setCursorPos( size, 1 )
- term.blit( '+', '0', 'e' )
- function click( mode, px, py )
- while true do
- inputWrite( 'Click pos', 1 )
- local _, _, x, y = os.pullEvent( 'mouse_click' )
- if mode == 'setup' and x == size and y == 1 then
- return 'done'
- elseif not mode and x == size and y == 1 then
- return button()
- elseif display.board.large then
- x = math.floor( x / 2 )
- y = math.floor( y / 2 )
- else
- x = x - 1
- y = y - 1
- end
- local valid = validClick( x, y, mode, px, py )
- if valid == 'cancel' then
- return 'cancel'
- elseif valid then
- return x, y
- end
- end
- end
- else
- names.red = 'white'
- names.blue = 'black'
- color = {
- red = colors.white,
- redSelect = colors.white,
- blue = colors.black,
- blueSelect = colors.black,
- textSelect = colors.gray,
- lake = colors.lightGray,
- lakeBg = colors.black,
- pos = colors.lightGray,
- neg = colors.gray
- }
- function click( mode, px, py )
- while true do
- local x, y
- while true do
- local data = input( 'Pos: ' )
- if mode == 'setup' and data == 'done' then
- return 'done'
- elseif not mode and ( data == 'menu' or data == 'button' ) then
- return button()
- end
- x, y = data:match( '(%d+),%s*(%d+)' )
- if x and y then
- break
- else
- writeLog( 'Invalid location' )
- end
- end
- x = tonumber( x )
- y = tonumber( y )
- local valid = validClick( x, y, mode, px, py )
- if valid == 'cancel' then
- return 'cancel'
- elseif valid then
- return x, y
- end
- end
- end
- end
- -- override defaults with any configured names
- if config.names then
- for k in pairs( config.names ) do
- names[k] = config.names[k]
- end
- end
- -- and colors
- if config.color then
- for k in pairs( config.color ) do
- color[k] = colors[config.color[k]]
- end
- end
- -- table which converts colors.n into a hexidecimal value to be used by term.blit
- local hexColor = {}
- for n=1,16 do
- hexColor[2^(n-1)] = string.sub( "0123456789abcdef", n, n )
- end
- -- data to draw the board
- local boardData
- if display.board.large then
- local pos = string.rep( hexColor[color.pos], 2 )
- local neg = string.rep( hexColor[color.neg], 2 )
- boardData = {
- row = ' ',
- pos = string.rep( pos .. neg, 5 ),
- neg = string.rep( neg .. pos, 5 ),
- size = { 20, 16 }
- }
- else
- local pos = hexColor[color.pos]
- local neg = hexColor[color.neg]
- boardData = {
- row = ' ',
- pos = string.rep( pos .. neg, 5 ),
- neg = string.rep( neg .. pos, 5 ),
- size = { 10, 8 }
- }
- end
- -- draw the board using the data from above
- -- note that "row" is an string of just spaces
- local board = window.create( term.current(), display.board[1], display.board[2], boardData.size[1], boardData.size[2] )
- board.setCursorPos( 1, 1 )
- if display.board.large then
- board.blit( boardData.row, boardData.pos, boardData.pos ) board.setCursorPos( 1, 2 )
- board.blit( boardData.row, boardData.pos, boardData.pos ) board.setCursorPos( 1, 3 )
- board.blit( boardData.row, boardData.neg, boardData.neg ) board.setCursorPos( 1, 4 )
- board.blit( boardData.row, boardData.neg, boardData.neg ) board.setCursorPos( 1, 5 )
- board.blit( boardData.row, boardData.pos, boardData.pos ) board.setCursorPos( 1, 6 )
- board.blit( boardData.row, boardData.pos, boardData.pos ) board.setCursorPos( 1, 7 )
- board.blit( boardData.row, boardData.neg, boardData.neg ) board.setCursorPos( 1, 8 )
- board.blit( boardData.row, boardData.neg, boardData.neg ) board.setCursorPos( 1, 9 )
- board.blit( boardData.row, boardData.pos, boardData.pos ) board.setCursorPos( 1, 10 )
- board.blit( boardData.row, boardData.pos, boardData.pos ) board.setCursorPos( 1, 11 )
- board.blit( boardData.row, boardData.neg, boardData.neg ) board.setCursorPos( 1, 12 )
- board.blit( boardData.row, boardData.neg, boardData.neg ) board.setCursorPos( 1, 13 )
- board.blit( boardData.row, boardData.pos, boardData.pos ) board.setCursorPos( 1, 14 )
- board.blit( boardData.row, boardData.pos, boardData.pos ) board.setCursorPos( 1, 15 )
- board.blit( boardData.row, boardData.neg, boardData.neg ) board.setCursorPos( 1, 16 )
- board.blit( boardData.row, boardData.neg, boardData.neg )
- else
- board.blit( boardData.row, boardData.pos, boardData.pos ) board.setCursorPos( 1, 2 )
- board.blit( boardData.row, boardData.neg, boardData.neg ) board.setCursorPos( 1, 3 )
- board.blit( boardData.row, boardData.pos, boardData.pos ) board.setCursorPos( 1, 4 )
- board.blit( boardData.row, boardData.neg, boardData.neg ) board.setCursorPos( 1, 5 )
- board.blit( boardData.row, boardData.pos, boardData.pos ) board.setCursorPos( 1, 6 )
- board.blit( boardData.row, boardData.neg, boardData.neg ) board.setCursorPos( 1, 7 )
- board.blit( boardData.row, boardData.pos, boardData.pos ) board.setCursorPos( 1, 8 )
- board.blit( boardData.row, boardData.neg, boardData.neg )
- end
- -- The file is the second parameter, but only use it if the third is also set
- -- as otherwise the second parameter is the host name
- local fileName = ( args[3] and args[2] or config.file )
- -- and apply the optional file extension from the config (don't apply it if the filename is nil)
- fileName = fileName and fileName .. ( config.ext or '' )
- local file = {}
- -- if the file is set does not exist, skip it
- if fileName and not fs.exists( fileName ) then
- writeLog( 'Save file does not exist' )
- fileName = nil
- -- otherwise load the file
- elseif fileName then
- local f = fs.open( fileName, 'r' )
- local data = textutils.unserialize( f.readAll() )
- f.close()
- -- validate the date
- if type( data ) == 'table' then
- writeLog( 'Loaded save file ' .. fileName )
- file = data
- else
- writeLog( 'Invalid save file' )
- fileName = nil
- end
- end
- local friend, host
- local name = args[3] or args[2]
- -- if we are the host, load options from the file and host the game on rednet
- if args[1] == 'host' then
- host = true
- options = file.options or {}
- writeLog( 'Hosting game with name "' .. name .. '"' )
- rednet.host( 'stratego', name )
- writeLog( 'Waiting for a friend. ' .. ( isColor and 'Click the + to cancel' or 'Press "end" to cancel' ) )
- while true do
- -- wait for either a message from the guest, or a prompt from the user to cancel
- local event, data, x, y = os.pullEvent()
- -- a message means start the game, so send the options to the other player
- if event == 'rednet_message' and y == 'stratego' then
- writeLog( 'Friend found, sending options' )
- -- store the ID for later
- friend = data
- rednet.send( friend, options, 'stratego-options' )
- writeLog( 'Starting game' )
- break
- -- user's prompt (click on advance systems and "end" on normal ones) means cancel
- elseif isColor and ( event == 'mouse_click' and x == size and y == 1 ) or ( event == 'key' and data == keys['end'] ) then
- shutdown( 'Canceled game' )
- return
- end
- end
- -- the host is always red, and it helps to have these for later
- myTeam = 'red'
- notMyTeam = 'blue'
- else
- -- if we are the guest, loop up the host
- writeLog( 'Looking up host ' .. name )
- local ID = rednet.lookup( 'stratego', name )
- if ID then
- -- once we find them, send a blank message so they get our ID
- writeLog( 'Found host with an ID of ' .. ID )
- -- store the ID of the host for later
- friend = ID
- rednet.send( friend, '', 'stratego' )
- writeLog( 'Waiting for options' )
- while true do
- -- then wait for the options
- local ID, data = rednet.receive( 'stratego-options' )
- if ID == friend then
- options = data
- break
- end
- end
- writeLog( 'Starting game' )
- else
- -- if it times out, shutdown the game
- shutdown( 'Host name ' .. name .. ' not found on network' )
- return
- end
- -- the guest is always blue, and it helps to have these for later
- myTeam = 'blue'
- notMyTeam = 'red'
- end
- -- define the rows for the setup click
- rows = math.min( options.rows or 3, ( options.rotate and 5 or 4 ) )
- -- how to draw large pieces
- local largePieces = {
- ['*'] = { '\\ ', '**' }, -- bomb
- ['!'] = { '^^', '10' }, -- 10
- S = { '^^', 'S ' }, -- spy
- F = { '|>', '| ' }, -- flag
- ['?'] = { '??', '??' }, -- unknown opponent
- [' '] = { ' ', ' ' }, -- empty space
- ['~'] = { '~~', '~~' } -- lake
- }
- -- function to draw a piece
- function writePiece( x, y, team, piece, sel )
- -- if a piece is given, then add it to the board
- if piece then
- pieces[y][x] = { team, piece }
- -- set the text to the team color, or the selection color if we are selecting
- board.setTextColor( sel and color.textSelect or color[team] )
- -- but don't show us the piece if it's an opponent
- if team == notMyTeam then
- piece = '?'
- end
- -- otherwise clear the space
- else
- pieces[y][x] = nil
- piece = ' '
- end
- -- if the piece is a lake (used on startup), set the background as the lake color
- if team == 'lake' then
- board.setBackgroundColor( color.lakeBg )
- -- if we are selecting it, set the background color as the team color
- elseif sel then
- board.setBackgroundColor( color[team .. 'Select'] )
- -- otherwise apply the "pos" and "neg" colors in a checkerboard
- elseif ( x + y ) % 2 == 0 then
- board.setBackgroundColor( color.pos )
- else
- board.setBackgroundColor( color.neg )
- end
- -- if we are using a large board, use double sized pieces
- if display.board.large then
- local draw = largePieces[piece] or { '^^', ' ' .. piece }
- -- and double the draw coordinates
- x = ( x * 2 ) - 1
- y = ( y * 2 ) - 1
- board.setCursorPos( x, y )
- board.write( draw[1] )
- board.setCursorPos( x, y + 1 )
- board.write( draw[2] )
- else
- -- otherwise just write as is
- board.setCursorPos( x, y )
- board.write( piece )
- end
- -- for some reason the text color gets used by term later...
- term.setTextColor( colors.white )
- end
- -- where to place lakes, in pairs
- local lakes = options.lakes or {
- x = { 3, 4, 7, 8, 3, 4, 7, 8 },
- y = { 4, 4, 4, 4, 5, 5, 5, 5 }
- }
- -- place the lakes
- for i, x in pairs( lakes.x ) do
- writePiece( x, lakes.y[i], 'lake', '~' )
- end
- -- function to save a game
- local turn = options.blueFirst and 'blue' or 'red'
- function save()
- -- we are running a loop inside a loop, so use a variable to break this one
- local finished
- while not finished do
- -- prompt for the file name to save at
- inputWrite( 'Filename' )
- local data = input() .. config.ext or ''
- -- we are finished unless tole otherwise later
- finished = true
- -- if it exists, prompt to overwrite
- if fs.exists( data ) then
- inputWrite( 'Overwrite?' )
- while true do
- local data = input( '(Y/N): ' ):upper()
- -- if we choose to overwrite, we are done
- if data == 'Y' then
- break
- -- otherwise restart
- elseif data == 'N' then
- finished = false
- break
- end
- end
- end
- -- only run this stuff if we choose to overwrite or no conflict existed
- if finished then
- local f = fs.open( data, 'w' )
- f.write( textutils.serialize( { options = { rotate = options.rotate }, turn = turn, pieces = pieces } ) )
- f.close()
- end
- end
- end
- -- function for when the button is pressed
- function button()
- while true do
- -- prompt for options
- local data = input( 'Menu: ' ):lower()
- -- "save" means we save the game, then go right back into it
- if host and ( data == 'save' or data == 's' ) then
- save()
- return click()
- -- "quit" will prompt for saving if you are the host, then will end the game
- elseif data == 'quit' or data == 'q' then
- if host then
- while true do
- local data = input( 'Save? ' ):lower()
- if data == 'yes' or data == 'y' then
- save()
- return 'save'
- elseif data == 'no' or 'n' then
- break
- end
- end
- end
- return 'quit'
- -- "forfeit" will cause you to be the loser, needed if you run out of moves
- elseif data == 'forfeit' or data == 'f' then
- return 'forfeit'
- -- a blank call means close the menu
- elseif data:gsub( ' ', '' ) == '' then
- return click()
- end
- end
- end
- -- some pieces are not numerical, so give them numerical ranks
- -- note the flag has no battle value, as it just overrides things later
- local battleValues = {
- ['*'] = 11,
- ['!'] = 10,
- S = 0
- }
- local done, winner, canSpot
- -- function for when two pieces battle
- function battle( attacker, defender )
- local team1 = attacker[1]
- local team2 = defender[1]
- local attacker = attacker[2]
- local defender = defender[2]
- -- if the flag was captured, set victory to our team
- if defender == 'F' then
- writeLog( 'The ' .. names[team2] .. ' flag has been captured' )
- done = true
- winner = team1
- return 'victory'
- -- miners defuse bombs
- elseif attacker == 3 and defender == '*' then
- writeLog( 'The ' .. names[team1] .. ' 3 has defused the ' .. names[team2] .. ' bomb' )
- return true
- -- the spy can kill the 10, but only as the attacker
- elseif attacker == 'S' and defender == '!' then
- writeLog( 'The ' .. names[team1] .. ' spy has killed the ' .. names[team2] .. ' 10' )
- return true
- else
- local attackerName = names[attacker] or attacker
- local defenderName = names[defender] or defender
- local attacker = battleValues[attacker] or attacker
- local defender = battleValues[defender] or defender
- if attacker == defender then
- writeLog( 'The ' .. names[team1] .. ' ' .. attackerName .. ' has met his match, both pieces were killed' )
- return 'tie'
- elseif attacker > defender then
- writeLog( 'The ' .. names[team1] .. ' ' .. attackerName .. ' killed the ' .. names[team2] .. ' ' .. defenderName )
- return true
- elseif attacker < defender then
- writeLog( 'The ' .. names[team1] .. ' ' .. attackerName .. ' was killed by the ' .. names[team2] .. ' ' .. defenderName )
- return false
- end
- end
- end
- -- a list of all valid piece calls. Used for the spotter and game setup
- local validPiece = {
- S = 'S', SPY = 'S',
- F = 'F', FLAG = 'F',
- ['*'] = '*', BOMB = '*', B = '*',
- ['!'] = '!', [10] = '!',
- [9] = 9,
- [8] = 8,
- [7] = 7,
- [6] = 6,
- [5] = 5,
- [4] = 4,
- [3] = 3,
- [2] = 2,
- [1] = 1, SPOTTER = 1
- }
- -- function for an attack with the spotter
- function spotter( x, y, override )
- local team = pieces[y][x][1]
- local rotate = options.rotate
- local y2 = rotate and y or ( y + ( team == 'red' and -1 or 1 ) )
- local x2 = rotate and ( x + ( team == 'red' and 1 or -1 ) ) or x
- local defender = pieces[y2][x2]
- if not defender or defender[1] ~= ( team == 'red' and 'blue' or 'red' ) then
- return false
- end
- writeLog( 'The ' .. names[team] .. ' spotter is attempting to snipe the piece at ' .. x2 .. ',' .. y2 )
- while true do
- local call
- if override then
- call = override
- else
- inputWrite( 'Spotter' )
- call = input( 'Call: ' )
- if call:gsub( ' ', '' ) == '' then
- return false
- elseif tonumber( call ) then
- call = tonumber( call )
- else
- call = call:upper()
- end
- call = validPiece[call]
- end
- if call then
- local notteam = ( team == 'red' and 'blue' or 'red' )
- if defender[2] == call then
- writeLog( 'The ' .. names[team] .. ' spotter has sniped the ' .. names[notteam] .. ' ' .. ( names[defender[2]] or defender[2] ) )
- if defender[2] == 'F' then
- done = true
- winner = team
- end
- writePiece( x2, y2 )
- else
- writeLog( 'The ' .. names[team] .. ' spotter failed to snipe the ' .. names[notteam] .. ' ' .. ( names[defender[2]] or defender[2] ) )
- end
- return call
- else
- writeLog( 'Invalid call, recall' )
- end
- end
- end
- -- function to move a piece
- function move( x1, y1, x2, y2 )
- local piece = pieces[y1][x1]
- -- scout movement
- if not piece then
- writeLog( 'No piece' )
- elseif piece[2] == 2 then
- -- check that the scout only moved in one direction
- if not (( x1 == x2 ) or ( y1 == y2 )) then
- writeLog( 'Invalid move' )
- return false
- else
- -- and that no pieces or lakes are in the way
- -- we use the temporary variables "c1" and "c2" to make it easier to loop through x or y
- local c1, c2, s
- if x1 == x2 then
- c1 = math.min( y1, y2 )
- c2 = math.max( y1, y2 )
- else
- c1 = math.min( x1, x2 )
- c2 = math.max( x1, x2 )
- end
- for c = c1, c2 do
- local piece
- -- ignore the start and end points
- if c == c1 or c == c2 then
- piece = false
- -- and check again of if we are using x or y, as the c variables don't keep track of which one
- elseif x1 == x2 then
- piece = pieces[c][x1]
- else
- piece = pieces[y1][c]
- end
- if piece then
- writeLog( 'Invalid move' )
- return false
- end
- end
- end
- -- for everyone else, check that we only moved one space
- elseif math.abs( x2 - x1 ) + math.abs( y2 - y1 ) ~= 1 then
- writeLog( 'Invalid move' )
- return false
- end
- -- state the action, but make sure not to state the piece rank
- writeLog( names[piece[1]]:gsub( '^%l', string.upper ) .. ' moved from ' .. x1 .. ',' .. y1 .. ' to ' .. x2 .. ',' .. y2 )
- local destination = pieces[y2][x2]
- local result
- -- if someone already exists at the target, then battle
- if destination then
- result = battle( piece, destination )
- end
- -- if the battle was a tie, kill the target space's piece
- if result == 'tie' then
- writePiece( x2, y2 )
- -- otherwise if we won or there was no battle, place the current piece at the target
- elseif result or not destination then
- writePiece( x2, y2, piece[1], piece[2] )
- end
- -- clear the current space anyways and return success
- writePiece( x1, y1 )
- return result or true
- end
- local maxPieces = options.maxPieces or {
- S = 1, F = 1, ['!'] = 1, [9] = 1, [8] = 1, [7] = 1,
- [6] = 2, [5] = 2, [4] = 2, [1] = 2,
- [3] = 5, [2] = 5,
- ['*'] = 6
- }
- -- set maximums for pieces and flags
- local maxRequiredPieces = rows * ( options.rotate and 8 or 10 )
- local requiredPieces = math.min( options.requiredPieces or maxRequiredPieces, maxRequiredPieces )
- local requiredFlags = math.min( options.requiredFlags or 1, 10 )
- -- called with a file to load the file or run by both side to load piece setup
- function setup()
- writeLog( 'Place pieces in the first ' .. rows .. ( options.rotate and ' columns. ' or ' rows. ' ) .. ( isColor and 'Press the + when finished' or 'Type "done" when finished' ) )
- -- tell the user how many flags are required, normally is just 1
- local flagText
- if requiredFlags > 0 then
- flagText = requiredFlags .. ( requiredFlags == 1 and ' flag is required' or ' flags are required' )
- writeLog( flagText )
- end
- -- keep track of pieces for the sake of the "done" button and not placing too many
- local usedPieces = {}
- local placedPieces = 0
- inputWrite( 'Place pieces' )
- while true do
- -- wait for a space to be clicked
- local x,y = click( 'setup' )
- -- if the user clicked the done button, check if they placed enough pieces and flags
- if x == 'done' then
- if placedPieces == requiredPieces then
- if ( usedPieces.F or 0 ) < requiredFlags then
- while true do
- -- if they are not equal, loop until the user does something other than press the done button
- writeLog( flagText )
- x,y = click( 'setup' )
- if x ~= 'done' then
- break
- end
- end
- else
- inputClear()
- inputClear(1)
- inputClear(2)
- break
- end
- else
- while true do
- -- if there is not enough, loop until the user does something other than press the done button
- writeLog( 'Not enough pieces placed' )
- x,y = click( 'setup' )
- if x ~= 'done' then
- break
- end
- end
- end
- end
- -- show selected space
- inputWrite( x .. ',' .. y, 2 )
- -- if a piece is already there, remove it from the count (we will remove it from the board with the selection later
- if pieces[y][x] then
- local piece = pieces[y][x][2]
- usedPieces[piece] = usedPieces[piece] - 1
- placedPieces = placedPieces - 1
- end
- -- don't allow a piece to be placed if we are out of pieces
- if placedPieces == requiredPieces then
- writeLog( 'Out of pieces' )
- else
- -- select the space
- writePiece( x, y, myTeam, nil, true )
- -- tell the user how many pieces remain
- -- TODO: make it display properly on smaller screens
- writeLog( 'Available: (place ' .. requiredPieces - placedPieces .. ' more)' )
- for k, v in pairs( maxPieces ) do
- local remaining = v - ( usedPieces[k] or 0 )
- if remaining ~= 0 then
- writeLog( '* ' .. ( names[k] or k ) .. ': ' .. remaining )
- end
- end
- while true do
- -- if we passed all other steps, ask the user what piece to place
- local data = input( 'Piece: ' )
- -- turn strings into numbers or uppercase (as that is how it's stored
- if tonumber( data ) then
- data = tonumber( data )
- else
- data = data:upper()
- end
- -- if the piece is valid
- local piece = validPiece[data]
- if piece then
- -- if we are out of the piece, ask the user to type the name again
- if usedPieces[piece] == maxPieces[piece] then
- writeLog( 'Out of ' .. ( names[piece] or piece ) .. "s" )
- else
- -- otherwise place the piece and increase the counters for that piece and total pieces
- writePiece( x, y, myTeam, piece )
- usedPieces[piece] = ( usedPieces[piece] or 0 ) + 1
- placedPieces = placedPieces + 1
- break
- end
- -- if the input is blank or only contains spaces, then just remove the selection
- elseif data:gsub( ' ', '' ) == '' then
- writePiece( x, y )
- break
- else
- writeLog( 'Invalid piece' )
- end
- end
- inputClear(2)
- end
- end
- -- once done, host the name under "stratego-done" so the other user sees it upon finishing
- writeLog( 'Waiting for other player' )
- rednet.host( 'stratego-done', name .. ( host and 1 or 0 ) )
- while true do
- local found = rednet.lookup( 'stratego-done', name .. ( host and 0 or 1 ) )
- if found == friend then
- -- once we find the other user is done, start sending pieces back and fourth
- if not host then
- -- the guest sends its pieces to the host
- writeLog( 'Sending pieces to host' )
- os.sleep( 2 ) -- delay to compensate for rednet.lookup
- rednet.send( friend, pieces, 'stratego-pieces-guest' )
- writeLog( 'Waiting for combined pieces' )
- -- then waits to get the host's combination
- while true do
- local ID, data = rednet.receive( 'stratego-pieces-host' )
- if ID == friend then
- writeLog( 'Received pieces from host' )
- pieces = data
- break
- end
- end
- else
- -- the host waits to receive the guest's pieces
- writeLog( 'Waiting for pieces from guest' )
- while true do
- local ID, data = rednet.receive( 'stratego-pieces-guest' )
- if ID == friend then
- writeLog( 'Received pieces from guest' )
- -- then combines the two and sends them back to the guest
- -- if for some reason a value exists in both the guest and host, the host's piece is kept
- -- though this should never happen except for lakes (which are in the same spots on both sides already)
- for x = 1, 10 do
- for y = 1, 8 do
- pieces[y][x] = pieces[y][x] or data[y][x]
- end
- end
- writeLog( 'Sending combined pieces to guest' )
- rednet.send( friend, pieces, 'stratego-pieces-host' )
- break
- end
- end
- end
- -- wait to unhost until after we finished everything, as otherwise lag can cause both systems to be stuck waiting
- rednet.unhost( 'stratego-done', name .. ( host and 1 or 0 ) )
- break
- end
- end
- writeLog( 'Setup complete, starting game' )
- end
- -- startup
- if host then
- -- if we loaded a file and it contains pieces, load it instead of running setup
- if file and file.pieces then
- rednet.send( friend, file, 'stratego-setup' )
- pieces = file.pieces
- turn = file.turn or turn
- else
- -- otherwise send a message to the guest indicating we are running setup
- -- a blank string is used, since the guest uses the type of data to determine the action
- rednet.send( friend, '', 'stratego-setup' )
- setup()
- end
- else
- -- wait for the host to tell us whether we already have pieces, or still need to load them
- while true do
- local ID, file = rednet.receive( 'stratego-setup' )
- if ID == friend then
- -- if the file is a table, we know it is the pieces
- if type( file ) == 'table' then
- writeLog( 'Loading pieces from host' )
- pieces = file.pieces
- turn = file.turn or turn
- else
- setup()
- end
- break
- end
- end
- end
- -- after setting up, write all pieces to the board
- -- as we either loaded setup pieces from the guest/host, or from a file
- for y = 1, 8 do
- for x = 1, 10 do
- local piece = pieces[y][x]
- -- if we don't have a piece, we need to draw a blank space to make sure any improperly added pieces are overwritten
- -- shouldn't happen, but just in case
- if piece then
- writePiece( x, y, pieces[y][x][1], pieces[y][x][2] )
- else
- writePiece( x, y, nil, nil )
- end
- end
- end
- -- main loop, using the variable "done" to determine completion (as we use multiple loops inside)
- while not done do
- -- if its our turn
- if turn == myTeam then
- -- collecting data of the turn
- local act = {}
- inputWrite( 'Move piece' )
- while true do
- while true do
- local x, y = click()
- -- if we choose to quit, then quit the game and end the program
- if x == 'quit' or x == 'save' then
- rednet.send( friend, 'quit', 'stratego-action' )
- if x == 'save' then
- shutdown( 'Game saved and quit' )
- else
- shutdown( 'Quit game' )
- end
- return
- -- if we choose to forfeit, then set the winner to the opponent and break the loop
- elseif x == 'forfeit' then
- act[1] = 'forfeit'
- writeLog( 'You forfeited' )
- winner = notMyTeam
- done = true
- break
- -- if the piece cannot move, don't select it
- elseif ( y + 1 > 8 or ( pieces[y + 1][x] and pieces[y + 1][x][1] ~= notMyTeam ) )
- and ( y - 1 < 1 or ( pieces[y - 1][x] and pieces[y - 1][x][1] ~= notMyTeam ) )
- and ( x + 1 > 10 or ( pieces[y][x + 1] and pieces[y][x + 1][1] ~= notMyTeam ) )
- and ( x - 1 < 1 or ( pieces[y][x - 1] and pieces[y][x - 1][1] ~= notMyTeam ) ) then
- writeLog( 'Piece cannot move' )
- -- otherwise select it
- else
- -- display selection
- inputWrite( x .. ',' .. y, 2 )
- writePiece( x, y, myTeam, pieces[y][x][2], true )
- -- and set the data for later use
- act[1] = x
- act[2] = y
- break
- end
- end
- -- if we forfeited, our turn is over
- if act[1] == 'forfeit' then
- break
- -- otherwise if we are a spotter, run the spotter function
- elseif pieces[act[2]][act[1]][2] == 1 then
- act[3] = spotter( act[1], act[2] )
- end
- -- if the spotter could spot and spotted, then the turn is done
- if act[3] then
- -- deselect the piece
- writePiece( act[1], act[2], myTeam, 1 )
- break
- -- otherwise proceed with movement
- else
- local canSpot
- while true do
- -- another click, but this time check for deselection
- local x, y = click( 'attack', act[1], act[2] )
- -- if we cancel, deselect the piece and go back to the first loop
- if x == 'cancel' then
- inputClear(2)
- writePiece( act[1], act[2], myTeam, pieces[act[2]][act[1]][2] )
- break
- -- need to check if we can spot before moving, as the target data gets replaced later on
- elseif not pieces[y][x] and pieces[act[2]][act[1]][2] == 1 then
- canSpot = true
- else
- canSpot = false
- end
- -- move the piece
- local success = move( act[1], act[2], x, y )
- -- if the move was successful, set the data, otherwise we retry the click
- if success then
- act[3] = x
- act[4] = y
- break
- end
- -- no need to use the spotter's ability if we just won
- if success == 'victory' then
- canSpot = false
- end
- end
- -- if we can spot and successfully moved, then run the spotter
- if canSpot and act[3] and pieces[act[4]][act[3]] then
- act[5] = spotter( act[3], act[4] )
- break
- -- otherwise just stop if we successfully moved, or restart the whole loop if not
- elseif act[3] then
- break
- end
- end
- end
- -- turn is over, so send the results to the other player and set the turn to them
- rednet.send( friend, act, 'stratego-action' )
- turn = notMyTeam
- else
- --if it's not our turn, wait for information on the opponents turn
- inputWrite( 'Waiting...' )
- inputClear(1)
- inputClear(2)
- while true do
- local ID, act = rednet.receive( 'stratego-action' )
- if ID == friend then
- -- found information
- -- if the opponent choose to quit, then quit as well
- if act == 'quit' then
- -- and prompt to save the game if we are the host
- if host then
- writeLog( 'Game quit by guest' )
- inputWrite( 'Save game?' )
- while true do
- local data = input( '(Y/N): ' ):upper()
- if data == 'Y' then
- save()
- break
- elseif data == 'N' then
- break
- end
- end
- shutdown()
- else
- shutdown( 'Game quit by host' )
- end
- return
- -- if the opponent forfeited, then set the winner to us, tell the player, and exit the main loop
- elseif act[1] == 'forfeit' then
- winner = myTeam
- writeLog( 'Opponent forfeited' )
- done = true
- -- otherwise check if the opponent used a spotter, and if so run the spotter function
- elseif not act[4] and pieces[act[2]][act[1]][2] == 1 then
- spotter( act[1], act[2], act[3] )
- -- otherwise, assume a movement
- else
- -- need to check if we can spot before moving, as the target data gets replaced later on
- -- this is mainly an anti-cheat thing, to prevent a player from both moving and spotting
- local canSpot
- if not pieces[act[4]][act[3]] and pieces[act[2]][act[1]][2] == 1 then
- canSpot = true
- end
- -- move the piece, then if we can spot, run the spotter function
- move( act[1], act[2], act[3], act[4] )
- if canSpot and act[5] then
- spotter( act[3], act[4], act[5] )
- end
- end
- -- since we found something, break out of the loop
- break
- end
- end
- -- and it's our turn next time
- turn = myTeam
- end
- end
- -- at the end of the game, state the winner then delay a bit for both sides to read it
- writeLog( winner:gsub( '^%l', string.upper ) .. ' is the winner' )
- os.sleep( 5 )
- -- if we are the host and the file is a save game, prompt to delete it
- if host and fileName and file.pieces then
- inputWrite( 'Delete game?' )
- while true do
- local data = input( '(Y/N): ' ):upper()
- if data == 'Y' then
- fs.delete( fileName )
- break
- elseif data == 'N' then
- break
- end
- end
- end
- -- and final shutdown stuff
- shutdown()
Add Comment
Please, Sign In to add comment