Commit f4a61829 by source_reader

lab5 start

parent 5988c774
...@@ -69,3 +69,18 @@ with a USB serial connection. Code is required in the following locations: ...@@ -69,3 +69,18 @@ with a USB serial connection. Code is required in the following locations:
|``player_state.py`` | ``all_sunk:72`` | update logic to handle remote players | |``player_state.py`` | ``all_sunk:72`` | update logic to handle remote players |
|``ship.py`` | ``to_dict:58`` | encode Ship as a dictionary | |``ship.py`` | ``to_dict:58`` | encode Ship as a dictionary |
|``ship.py`` | ``from_dict:67`` | create a Ship from a dictionary | |``ship.py`` | ``from_dict:67`` | create a Ship from a dictionary |
Lab 5: Playing Remote Games
---------------------------
By the end of this lab you should be able to play a game of battleship against a remote opponent
over a network connection. You should also be able to detect if your opponent cheated. Code is
required in the following locations:
| File Name | Function:Line | Description |
|--------------------|------------------------------|------------------------------------------|
|``engine.py`` | ``play:59`` | add cheat detection for player2 |
|``player_state.py`` | ``to_dict:125`` | add hash to dict encoding |
|``player_state.py`` | ``from_dict:155`` | set hash from dict encoding |
|``player_state.py`` | ``is_valid:155`` | verify PlayerState |
|``comms.py`` | ``host_tcp_server:61`` | host a battleship server |
|``comms.py`` | ``connect_tcp_client:79`` | connect to an existing battleship server |
from typing import Tuple from typing import Tuple, BinaryIO
from random import randint from random import randint
import serial import serial
import time import time
...@@ -51,3 +51,38 @@ def connect_serial_client(port) -> Tuple[int, serial.Serial]: ...@@ -51,3 +51,38 @@ def connect_serial_client(port) -> Tuple[int, serial.Serial]:
return 3, None # <-- replace None with the Serial object return 3, None # <-- replace None with the Serial object
# --------- END YOUR CODE ---------- # --------- END YOUR CODE ----------
def host_tcp_server(port) -> Tuple[int, BinaryIO]:
"""
Host a battleship server on the specified port on all interfaces (0.0.0.0)
Uses priority 0
"""
# --------- BEGIN YOUR CODE ----------
# create Socket object
# bind to all interfaces (IP address = 0.0.0.0) on the specified port
# wait for a connection from a client
# return the connected socket
return 0, None
# --------- END YOUR CODE ----------
def connect_tcp_client(server_address) -> Tuple[int, BinaryIO]:
"""
Connect to a battleship server specified by ip_address:port
Uses priority 3
"""
# --------- BEGIN YOUR CODE ----------
# create a Socket object
# parse server_address into ip_address and port
# note the ip_address is a string and the port is an int
# connect the socket to the server
return 3, None
# --------- END YOUR CODE ----------
...@@ -42,10 +42,21 @@ class Engine: ...@@ -42,10 +42,21 @@ class Engine:
# Did Player1 win? # Did Player1 win?
if player2_state.all_sunk(): if player2_state.all_sunk():
# share all of Player1's ships with Player2 (both sunk and unsunk)
# this way if Player2 is remote, they can verify that Player1 did not cheat
player1_solved_state = self.player1.get_state()
self.player2.send_state(player1_solved_state, reveal=True)
# verify for ourselves that Player1 did not cheat
if not player1_solved_state.is_valid():
print("Player 1 cheated!")
else:
print("Player1 played a fair game")
return self.player1 return self.player1
# ==== Player2's turn ==== # ==== Player2's turn ====
# NOTE: Following the pattern above, implement cheat detection for Player2
# --------- BEGIN YOUR CODE ---------- # --------- BEGIN YOUR CODE ----------
# Following the pattern above implement the logic for Player2's turn # Following the pattern above implement the logic for Player2's turn
......
...@@ -49,8 +49,9 @@ def main(): ...@@ -49,8 +49,9 @@ def main():
# parse command line arguments # parse command line arguments
parser = argparse.ArgumentParser("Battleship Simulator") parser = argparse.ArgumentParser("Battleship Simulator")
parser.add_argument("--type", "-t", choices=['cpu', 'serial-server', 'serial-client'], default='cpu') parser.add_argument("--type", "-t", choices=['cpu', 'serial-server', 'serial-client',
parser.add_argument("--interface", "-i", help="serial device") 'tcp-server', 'tcp-client'], default='cpu')
parser.add_argument("--interface", "-i", help="serial device, port or IP address:PORT")
args = parser.parse_args() args = parser.parse_args()
# determine the type of opponent # determine the type of opponent
...@@ -62,6 +63,12 @@ def main(): ...@@ -62,6 +63,12 @@ def main():
elif args.type == 'serial-client': elif args.type == 'serial-client':
(priority, file) = comms.connect_serial_client(args.interface) (priority, file) = comms.connect_serial_client(args.interface)
them = players.Remote(file, priority=priority) them = players.Remote(file, priority=priority)
elif args.type == 'tcp-server':
(priority, file) = comms.host_tcp_server(args.interface)
them = players.Remote(file, priority=priority)
elif args.type == 'tcp-client':
(priority, file) = comms.connect_tcp_client(args.interface)
them = players.Remote(file, priority=priority)
else: else:
print("Invalid player type") print("Invalid player type")
return return
......
from random import randint from random import randint
from typing import List, Optional, Tuple, Dict from typing import List, Optional, Tuple, Dict
import json
import hashlib
from ship import Ship from ship import Ship
from ship import from_dict as ship_from_dict from ship import from_dict as ship_from_dict
...@@ -43,6 +45,11 @@ class PlayerState: ...@@ -43,6 +45,11 @@ class PlayerState:
pass # <-- remove this! pass # <-- remove this!
# --------- END YOUR CODE ---------- # --------- END YOUR CODE ----------
# compute the hash of the ships, used for cheat detection
ship_str = json.dumps([ship.to_dict() for ship in self._ships])
ship_bytes = ship_str.encode('ascii')
self._hash = hashlib.sha256(ship_bytes).hexdigest()
""" """
Print the board as text, useful for debugging Print the board as text, useful for debugging
""" """
...@@ -100,6 +107,8 @@ class PlayerState: ...@@ -100,6 +107,8 @@ class PlayerState:
# whether or not the last guess was a hit # whether or not the last guess was a hit
self._hit = new_state._hit self._hit = new_state._hit
# NOTE: ignore the hash, it cannot be trusted!
def to_dict(self, reveal=False) -> Dict: def to_dict(self, reveal=False) -> Dict:
# if reveal is True, encode all ships # if reveal is True, encode all ships
if reveal: if reveal:
...@@ -109,6 +118,8 @@ class PlayerState: ...@@ -109,6 +118,8 @@ class PlayerState:
ships = self.sunk_ships() ships = self.sunk_ships()
data = {} data = {}
# NOTE: Add 'hash' to the data dictionary
# --------- BEGIN YOUR CODE ---------- # --------- BEGIN YOUR CODE ----------
# populate data with the following keys: # populate data with the following keys:
...@@ -120,9 +131,29 @@ class PlayerState: ...@@ -120,9 +131,29 @@ class PlayerState:
return data return data
def is_valid(self) -> bool:
# --------- BEGIN YOUR CODE ----------
board_matrix: List[List[Optional[Ship]]] = [[None] * 10 for _ in range(10)]
# If any of the following checks fail, print a message and return False
# 1.) Make sure the ships are the right types
# 2.) Make sure ships are in valid locations
# 3.) Make sure the hits and misses are correct
# 4.) Validate board signature (make sure ships have not moved)
return True
# --------- END YOUR CODE ----------
def from_dict(data: Dict) -> PlayerState: def from_dict(data: Dict) -> PlayerState:
state = PlayerState() state = PlayerState()
# NOTE: Set the _hash attribute based on the provided data
# --------- BEGIN YOUR CODE ---------- # --------- BEGIN YOUR CODE ----------
# set the _hit and _ships attributes based on the provided data # set the _hit and _ships attributes based on the provided data
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment