Update WebUI to display server, check, and hint info. CURRENT HINT POINTS DO NOT WORK YET

This commit is contained in:
Chris 2020-06-14 18:04:03 -04:00
parent aa7fe2aa9d
commit e11f33b589
8 changed files with 173 additions and 21 deletions

View File

@ -48,9 +48,15 @@ class Context():
self.snes_address = snes_address self.snes_address = snes_address
self.server_address = server_address self.server_address = server_address
# WebUI Stuff
self.ui_node = WebUI.WebUiClient() self.ui_node = WebUI.WebUiClient()
self.custom_address = None self.custom_address = None
self.webui_socket_port: typing.Optional[int] = port self.webui_socket_port: typing.Optional[int] = port
self.hint_cost = 0
self.check_points = 0
self.forfeit_mode = ''
self.remaining_mode = ''
# End WebUI Stuff
self.exit_event = asyncio.Event() self.exit_event = asyncio.Event()
self.watcher_event = asyncio.Event() self.watcher_event = asyncio.Event()
@ -761,7 +767,13 @@ async def process_server_cmd(ctx: Context, cmd, args):
if "forfeit_mode" in args: # could also be version > 2.2.1, but going with implicit content here if "forfeit_mode" in args: # could also be version > 2.2.1, but going with implicit content here
logging.info("Forfeit setting: "+args["forfeit_mode"]) logging.info("Forfeit setting: "+args["forfeit_mode"])
logging.info("Remaining setting: "+args["remaining_mode"]) logging.info("Remaining setting: "+args["remaining_mode"])
logging.info(f"A !hint costs {args['hint_cost']} points and you get {args['location_check_points']} for each location checked.") logging.info(f"A !hint costs {args['hint_cost']} points and you get {args['location_check_points']}"
f" for each location checked.")
ctx.hint_cost = int(args['hint_cost'])
ctx.check_points = int(args['location_check_points'])
ctx.forfeit_mode = args['forfeit_mode']
ctx.remaining_mode = args['remaining_mode']
ctx.ui_node.send_game_info(ctx)
if len(args['players']) < 1: if len(args['players']) < 1:
ctx.ui_node.log_info('No player connected') ctx.ui_node.log_info('No player connected')
else: else:
@ -1045,6 +1057,7 @@ async def track_locations(ctx : Context, roomid, roomdata):
def new_check(location): def new_check(location):
ctx.locations_checked.add(location) ctx.locations_checked.add(location)
ctx.ui_node.log_info("New check: %s (%d/216)" % (location, len(ctx.locations_checked))) ctx.ui_node.log_info("New check: %s (%d/216)" % (location, len(ctx.locations_checked)))
ctx.ui_node.send_location_check(ctx, location)
new_locations.append(Regions.location_table[location][0]) new_locations.append(Regions.location_table[location][0])
for location, (loc_roomid, loc_mask) in location_table_uw.items(): for location, (loc_roomid, loc_mask) in location_table_uw.items():
@ -1223,6 +1236,10 @@ async def websocket_server(websocket: websockets.WebSocketServerProtocol, path,
ctx.ui_node.send_connection_status(ctx) ctx.ui_node.send_connection_status(ctx)
elif data['content'] == 'devices': elif data['content'] == 'devices':
await get_snes_devices(ctx) await get_snes_devices(ctx)
elif data['content'] == 'gameInfo':
ctx.ui_node.send_game_info(ctx)
elif data['content'] == 'checkData':
ctx.ui_node.send_location_check(ctx, 'Waiting for check...')
elif data['type'] == 'webConfig': elif data['type'] == 'webConfig':
if 'serverAddress' in data['content']: if 'serverAddress' in data['content']:

View File

@ -57,6 +57,7 @@ class WebUiClient(Node):
'serverAddress': server_address, 'serverAddress': server_address,
'server': 1 if ctx.server is not None and not ctx.server.socket.closed else 0, 'server': 1 if ctx.server is not None and not ctx.server.socket.closed else 0,
})) }))
def send_device_list(self, devices): def send_device_list(self, devices):
self.broadcast_all(self.build_message('availableDevices', { self.broadcast_all(self.build_message('availableDevices', {
'devices': devices, 'devices': devices,
@ -105,11 +106,20 @@ class WebUiClient(Node):
'entranceLocation': entrance_location, 'entranceLocation': entrance_location,
})) }))
def send_game_state(self, ctx: Context): def send_game_info(self, ctx: Context):
self.broadcast_all(self.build_message('gameState', { self.broadcast_all(self.build_message('gameInfo', {
'hintCost': 0, 'serverVersion': Utils.__version__,
'checkPoints': 0, 'hintCost': ctx.hint_cost,
'playerPoints': 0, 'checkPoints': ctx.check_points,
'forfeitMode': ctx.forfeit_mode,
'remainingMode': ctx.remaining_mode,
}))
def send_location_check(self, ctx: Context, last_check: str):
self.broadcast_all(self.build_message('locationCheck', {
'totalChecks': len(ctx.locations_checked),
'hintPoints': 0,
'lastCheck': last_check,
})) }))
@ -117,22 +127,27 @@ class WaitingForUiException(Exception):
pass pass
webthread = None web_thread = None
PORT = 5050 PORT = 5050
class RequestHandler(http.server.SimpleHTTPRequestHandler): class RequestHandler(http.server.SimpleHTTPRequestHandler):
def log_request(self, code='-', size='-'): def log_request(self, code='-', size='-'):
pass pass
def log_message(self, format, *args): def log_message(self, format, *args):
pass pass
def log_date_time_string(self): def log_date_time_string(self):
pass pass
Handler = partial(RequestHandler, Handler = partial(RequestHandler,
directory=Utils.local_path(os.path.join("data", "web", "public"))) directory=Utils.local_path(os.path.join("data", "web", "public")))
def start_server(socket_port: int, on_start=lambda: None): def start_server(socket_port: int, on_start=lambda: None):
global webthread global web_thread
try: try:
server = socketserver.TCPServer(("", PORT), Handler) server = socketserver.TCPServer(("", PORT), Handler)
except OSError: except OSError:
@ -152,4 +167,4 @@ def start_server(socket_port: int, on_start=lambda: None):
else: else:
print("serving at port", PORT) print("serving at port", PORT)
on_start() on_start()
webthread = threading.Thread(target=server.serve_forever).start() web_thread = threading.Thread(target=server.serve_forever).start()

File diff suppressed because one or more lines are too long

View File

@ -46,6 +46,7 @@ class MonitorControls extends Component {
} }
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
// If there is only one SNES device available, connect to it automatically
if ( if (
prevProps.availableDevices.length !== this.props.availableDevices.length && prevProps.availableDevices.length !== this.props.availableDevices.length &&
this.props.availableDevices.length === 1 this.props.availableDevices.length === 1
@ -57,6 +58,17 @@ class MonitorControls extends Component {
} }
}); });
} }
// If we have moved from a disconnected state (default) into a connected state, request the game information
if (
(
(prevProps.snesConnected !== this.props.snesConnected) || // SNES status changed
(prevProps.serverConnected !== this.props.serverConnected) // OR server status changed
) && ((this.props.serverConnected) && (this.props.snesConnected)) // AND both are connected
) {
this.props.webSocket.send(WebSocketUtils.formatSocketData('webStatus', 'gameInfo'));
this.props.webSocket.send(WebSocketUtils.formatSocketData('webStatus', 'checkData'));
}
} }
increaseTextSize = () => { increaseTextSize = () => {

View File

@ -2,6 +2,17 @@ import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import '../../../styles/WidgetArea/containers/WidgetArea.scss'; import '../../../styles/WidgetArea/containers/WidgetArea.scss';
const mapReduxStateToProps = (reduxState) => ({
serverVersion: reduxState.gameState.serverVersion,
forfeitMode: reduxState.gameState.forfeitMode,
remainingMode: reduxState.gameState.remainingMode,
hintCost: reduxState.gameState.hintCost,
checkPoints: reduxState.gameState.checkPoints,
hintPoints: reduxState.gameState.hintPoints,
totalChecks: reduxState.gameState.totalChecks,
lastCheck: reduxState.gameState.lastCheck,
});
class WidgetArea extends Component { class WidgetArea extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
@ -30,10 +41,67 @@ class WidgetArea extends Component {
{ {
this.state.collapsed ? null : ( this.state.collapsed ? null : (
<div id="widget-area-contents"> <div id="widget-area-contents">
<div id="game-info">
<div id="game-info-title">
Game Info:
<button className="collapse-button" onClick={ this.toggleCollapse }></button>
</div>
<table>
<tbody>
<tr>
<th>Server Version:</th>
<td>{this.props.serverVersion}</td>
</tr>
<tr>
<th>Forfeit Mode:</th>
<td>{this.props.forfeitMode}</td>
</tr>
<tr>
<th>Remaining Mode:</th>
<td>{this.props.remainingMode}</td>
</tr>
</tbody>
</table>
</div>
<div id="check-data">
<div id="check-data-title">Checks:</div>
<table>
<tbody>
<tr>
<th>Total Checks:</th>
<td>{this.props.totalChecks}</td>
</tr>
<tr>
<th>Last Check:</th>
<td>{this.props.lastCheck}</td>
</tr>
</tbody>
</table>
</div>
<div id="hint-data">
<div id="hint-data-title">
Hint Data:
</div>
<table>
<tbody>
<tr>
<th>Hint Cost:</th>
<td>{this.props.hintCost}</td>
</tr>
<tr>
<th>Check Points:</th>
<td>{this.props.checkPoints}</td>
</tr>
<tr>
<th>Current Points:</th>
<td>{this.props.hintPoints}</td>
</tr>
</tbody>
</table>
</div>
<div id="notes"> <div id="notes">
<div id="notes-title"> <div id="notes-title">
<div>Notes:</div> <div>Notes:</div>
<button className="collapse-button" onClick={ this.toggleCollapse }></button>
</div> </div>
<textarea defaultValue={ localStorage.getItem('notes') } onKeyUp={ this.saveNotes } /> <textarea defaultValue={ localStorage.getItem('notes') } onKeyUp={ this.saveNotes } />
</div> </div>
@ -46,4 +114,4 @@ class WidgetArea extends Component {
} }
} }
export default connect()(WidgetArea); export default connect(mapReduxStateToProps)(WidgetArea);

View File

@ -1,17 +1,20 @@
import _assign from 'lodash-es/assign'; import _assign from 'lodash-es/assign';
const initialState = { const initialState = {
serverVersion: null,
forfeitMode: null,
remainingMode: null,
connections: { connections: {
snesDevice: '', snesDevice: '',
snesConnected: false, snesConnected: false,
serverAddress: null, serverAddress: null,
serverConnected: false, serverConnected: false,
}, },
hints: { totalChecks: 0,
hintCost: null, lastCheck: null,
checkPoints: null, hintCost: null,
playerPoints: 0, checkPoints: null,
}, hintPoints: 0,
}; };
const gameStateReducer = (state = initialState, action) => { const gameStateReducer = (state = initialState, action) => {

View File

@ -60,6 +60,22 @@ class WebSocketUtils {
parseInt(data.content.iAmFinder, 10) === 1, parseInt(data.content.iAmRecipient, 10) === 1, parseInt(data.content.iAmFinder, 10) === 1, parseInt(data.content.iAmRecipient, 10) === 1,
data.content.entranceLocation)); data.content.entranceLocation));
case 'gameInfo':
return updateGameState({
serverVersion: data.content.serverVersion,
forfeitMode: data.content.forfeitMode,
remainingMode: data.content.remainingMode,
hintCost: parseInt(data.content.hintCost, 10),
checkPoints: parseInt(data.content.checkPoints, 10),
});
case 'locationCheck':
return updateGameState({
totalChecks: parseInt(data.content.totalChecks, 10),
lastCheck: data.content.lastCheck,
hintPoints: parseInt(data.content.hintPoints, 10),
});
// The client prints several types of messages to the console // The client prints several types of messages to the console
case 'critical': case 'critical':
case 'error': case 'error':

View File

@ -21,15 +21,36 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
#notes{ table{
display: flex; th{
flex-direction: column; text-align: left;
}
td{
padding-left: 1em;
}
}
#notes-title{ #game-info{
margin-bottom: 1em;
#game-info-title{
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;
} }
}
#check-data{
margin-bottom: 1em;
}
#hint-data{
margin-bottom: 1em;
}
#notes{
display: flex;
flex-direction: column;
textarea{ textarea{
height: 10em; height: 10em;