Commit 5988c774 by source_reader

lab4 start

parent 2313150e
...@@ -51,3 +51,21 @@ your computer. Code is required in the following locations: ...@@ -51,3 +51,21 @@ your computer. Code is required in the following locations:
|``utilities.py`` | ``draw_misses:35``| add miss sprites to a board | |``utilities.py`` | ``draw_misses:35``| add miss sprites to a board |
|``player_state.py`` | ``all_sunk:72`` | determine if a player has lost | |``player_state.py`` | ``all_sunk:72`` | determine if a player has lost |
|``ship.py`` | ``is_hit:38`` | determine if a ship is hit | |``ship.py`` | ``is_hit:38`` | determine if a ship is hit |
Lab 4: Playing Local Games
--------------------------
By the end of this lab you should be able to play a game of battleship against a local opponent
with a USB serial connection. Code is required in the following locations:
| File Name | Function:Line | Description |
|--------------------|------------------------------|------------------------------------------|
|``remote.py`` | ``get_guess:18`` | get a guess from another computer |
|``remote.py`` | ``send_guess:31`` | send a guess to another computer |
|``remote.py`` | ``get_state:44`` | get a PlayerState from another computer |
|``remote.py`` | ``send_state:65`` | send a PlayerState to another computer |
|``comms.py`` | ``connect_serial_client:44`` | connect to an existing battleship server |
|``player_state.py`` | ``to_dict:106`` | encode PlayerState as a dictionary |
|``player_state.py`` | ``from_dict:119`` | create PlayerState from a dictionary |
|``player_state.py`` | ``all_sunk:72`` | update logic to handle remote players |
|``ship.py`` | ``to_dict:58`` | encode Ship as a dictionary |
|``ship.py`` | ``from_dict:67`` | create a Ship from a dictionary |
from typing import Tuple
from random import randint
import serial
import time
def host_serial_server(port) -> Tuple[int, serial.Serial]:
"""
Host a battleship server on the specified serial port
Uses priority 0
"""
# create a Serial object associated with the specified port
device = serial.Serial(port)
# send synchronization messages until a response is received
# to connect the client waits until it sees a message and then repeats it back to the server
# note: this is not fool proof, if the client does not reply correctly we just crash
while True:
# synchronization is a random number between 0-1000
server_syn = randint(0, 1000)
server_syn_str = "%d\n" % server_syn
server_syn_bytes = server_syn_str.encode('ascii')
device.write(server_syn_bytes)
# wait to see if a client has replied
time.sleep(0.5)
if device.in_waiting > 0:
# yes, a client replied, validate the message
client_ack_bytes = device.readline()
client_ack_str = client_ack_bytes.decode('ascii')
client_ack = int(client_ack_str)
if client_ack == server_syn: # valid acknowledgement?
return 0, device
else:
# corrupt response, client is not synchronized
raise Exception("ERROR: client did not ack")
def connect_serial_client(port) -> Tuple[int, serial.Serial]:
"""
Connect to a battleship server on the specified serial port
Uses priority 3
"""
# --------- BEGIN YOUR CODE ----------
# create a Serial object associated with the specified port
# listen for a synchronize message
# acknowledge the synchronize message
return 3, None # <-- replace None with the Serial object
# --------- END YOUR CODE ----------
import pygame import pygame
from pygame.locals import * from pygame.locals import *
import sys import sys
import argparse
import comms
import engine import engine
import players import players
import sprites import sprites
...@@ -45,19 +47,42 @@ def main(): ...@@ -45,19 +47,42 @@ def main():
# --------- END YOUR CODE ---------- # --------- END YOUR CODE ----------
# parse command line arguments
parser = argparse.ArgumentParser("Battleship Simulator")
parser.add_argument("--type", "-t", choices=['cpu', 'serial-server', 'serial-client'], default='cpu')
parser.add_argument("--interface", "-i", help="serial device")
args = parser.parse_args()
# determine the type of opponent
if args.type == 'cpu':
them = players.Computer()
elif args.type == 'serial-server':
(priority, file) = comms.host_serial_server(args.interface)
them = players.Remote(file, priority=priority)
elif args.type == 'serial-client':
(priority, file) = comms.connect_serial_client(args.interface)
them = players.Remote(file, priority=priority)
else:
print("Invalid player type")
return
me = players.Human() me = players.Human()
my_board.initialize() my_board.initialize()
utilities.draw_ships(my_board, me.state.all_ships()) utilities.draw_ships(my_board, me.state.all_ships())
my_board.draw() my_board.draw()
them = players.Computer()
their_board.initialize() their_board.initialize()
their_board.draw() their_board.draw()
pygame.display.update() pygame.display.update()
# Create an engine to run the game # Create an engine to run the game
if me.priority > them.priority:
# I go first
game = engine.Engine(me, my_board, them, their_board, display_message) game = engine.Engine(me, my_board, them, their_board, display_message)
else:
# They go first
game = engine.Engine(them, their_board, me, my_board, display_message)
# Play battleship! # Play battleship!
winner = game.play() winner = game.play()
......
from .remote import Remote
from .computer import Computer from .computer import Computer
from .human import Human from .human import Human
from .player import Player from .player import Player
from random import randint from random import randint
from typing import List, Optional, Tuple from typing import List, Optional, Tuple, Dict
from ship import Ship from ship import Ship
from ship import from_dict as ship_from_dict
SHIP_TYPES = [2, 3, 3, 4, 5] SHIP_TYPES = [2, 3, 3, 4, 5]
...@@ -69,6 +70,12 @@ class PlayerState: ...@@ -69,6 +70,12 @@ class PlayerState:
return self._hit return self._hit
def all_sunk(self) -> bool: def all_sunk(self) -> bool:
# NOTE: Modify the logic below to check if BOTH
# 1.) All of the ships are present in _ships
# 2.) All of the ships in _ships are sunk
# Remote players only send their sunk ships which means it is not sufficient
# to check if all the Ship objects in _ships are sunk
# --------- BEGIN YOUR CODE ---------- # --------- BEGIN YOUR CODE ----------
# return True if all of the ships are sunk # return True if all of the ships are sunk
...@@ -82,3 +89,43 @@ class PlayerState: ...@@ -82,3 +89,43 @@ class PlayerState:
def sunk_ships(self): def sunk_ships(self):
return [ship for ship in self._ships if ship.is_sunk()] return [ship for ship in self._ships if ship.is_sunk()]
def update(self, new_state: 'PlayerState'):
# update our attributes based on new_state
# new_state is received from a remote computer
# add any newly sunken ships
self._ships = new_state._ships
# whether or not the last guess was a hit
self._hit = new_state._hit
def to_dict(self, reveal=False) -> Dict:
# if reveal is True, encode all ships
if reveal:
ships = self._ships
# otherwise only encode the sunk ships
else:
ships = self.sunk_ships()
data = {}
# --------- BEGIN YOUR CODE ----------
# populate data with the following keys:
# 'ships': an array of ships encoded as dicts
# 'hit': the value of _hit
# --------- END YOUR CODE ----------
return data
def from_dict(data: Dict) -> PlayerState:
state = PlayerState()
# --------- BEGIN YOUR CODE ----------
# set the _hit and _ships attributes based on the provided data
# --------- END YOUR CODE ----------
return state
import json
from typing import Tuple, Callable
from .player import Player
from .player_state import PlayerState, from_dict
class Remote(Player):
""" A remote player"""
def __init__(self, pipe, priority):
# whether this player goes first or second
self.priority = priority
self.pipe = pipe
self._state = None
def get_guess(self, alert: Callable[[str], None]) -> Tuple[int, int]:
# --------- BEGIN YOUR CODE ----------
# read message (blocks until a complete message arrives)
# convert binary data into a JSON string
# convert the JSON string into a guess
# return the guess
return 0, 0 # <-- remove this!
# --------- END YOUR CODE ----------
def send_guess(self, guess: Tuple[int, int]):
# --------- BEGIN YOUR CODE ----------
# encode guess as json string and append a '\n' to terminate the message
# convert string into binary data
# send binary data (make sure to flush the pipe)
pass # <-- remove this!
# --------- END YOUR CODE ----------
def get_state(self) -> PlayerState:
# --------- BEGIN YOUR CODE ----------
# read message (blocks until a complete message arrives)
# convert binary data into a JSON string
# convert the JSON string into a dictionary
# convert the dictionary into a PlayerState
# if _state is None, replace it with the new state
# otherwise update _state with the new state
# --------- END YOUR CODE ----------
return self._state
def send_state(self, state: PlayerState, reveal=False):
# --------- BEGIN YOUR CODE ----------
# encode the state as a dict
# convert the dict into a JSON string and append a '\n' to terminate the message
# convert the message string into binary data
# send binary data (make sure to flush the pipe)
pass # <-- remove this!
# --------- END YOUR CODE ----------
def close(self):
self.pipe.close()
from typing import Dict, Optional, List
class Ship: class Ship:
def __init__(self, length: int, row: int, def __init__(self, length: int, row: int,
col: int, is_vertical: bool): col: int, is_vertical: bool,
hits: Optional[List] = None):
""" Ships are specified by their length, the (row,col) """ Ships are specified by their length, the (row,col)
coordinate of the bow, and whether they are vertical or coordinate of the bow, and whether they are vertical or
horizontal. Two examples are illustrated on the board below: horizontal. Two examples are illustrated on the board below:
...@@ -28,7 +31,11 @@ class Ship: ...@@ -28,7 +31,11 @@ class Ship:
self.is_vertical = is_vertical self.is_vertical = is_vertical
# keep track of which parts of the ship are hit # keep track of which parts of the ship are hit
# the hits parameter is provided when creating a ship with from_dict (see below)
if hits is None:
self._hits = [] self._hits = []
else:
self._hits = hits
def is_hit(self, guess): def is_hit(self, guess):
# ignore duplicate guesses # ignore duplicate guesses
...@@ -46,3 +53,21 @@ class Ship: ...@@ -46,3 +53,21 @@ class Ship:
def is_sunk(self): def is_sunk(self):
return len(self._hits) == self.length return len(self._hits) == self.length
def to_dict(self) -> Dict:
# --------- BEGIN YOUR CODE ----------
# populate data with the following keys based on our attributes:
# 'length', 'row', 'col', 'is_vertical', 'hits'
return {} # <-- replace with your code
# --------- END YOUR CODE ----------
def from_dict(data: Dict):
# --------- BEGIN YOUR CODE ----------
# create a Ship based off the data provided, make sure to include the hits!
pass # <-- remove this!
# --------- END YOUR CODE ----------
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