Skip to content
This repository has been archived by the owner on Aug 7, 2024. It is now read-only.

Commit

Permalink
fix: adding pause (#1744)
Browse files Browse the repository at this point in the history
* test PR

* fix: adding pause

* move the Reset Code Button logic into IDEEditor

* working in progress

* fix: updated ui

* removing unused components

* removed resetCode that is unused

* remove editorActions

* fix IDEConsole snapshot

* fixing snapshots

* fixing snapshots IDEEditor

* fixing IDEEditor snapshot

* isCodeOnServerDifferent is false

* fix: all snapshots are updated

* implemented razvan command into run.py runner

* implemented razvan command into run.py runner

* updated text to not have titled cases

* lower case

* move clear console button to its own toolbar

* add props validation for ClearConsoleBar

* update prop name

* adjust editor height to fix zoom in/out problem

* update snapshots

* Merge branch 'IDEmain' into pause

* move gamePuase logic to IDEEditor

* return wait action after pausing the game

* add GAME_RESUME after clicking Run code btn

* add gamePausedEpic & APPEND_PAUSE_MESSAGE action

* integrate APPEND_PAUSE_MESSAGE to avatarsNextActionComputed

* send analytics event when clicking pause

* merge IDEmain for UI update

* add tests

* back to add tests

* Merge branch 'master' into pause

* add pyodide tests

* remove unused code

* add tests to IDEEditor

* correct pyodide webWorker jest test

* revision for Reviewable comments

Co-Authored-By: Chi-En Wei <chi-en.wei@ocado.com>
  • Loading branch information
KamilPawel and cewei8483 authored Mar 20, 2023
1 parent 3434c80 commit 764ae03
Show file tree
Hide file tree
Showing 17 changed files with 242 additions and 12 deletions.
8 changes: 7 additions & 1 deletion aimmo_runner/minikube.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import atexit
import os
import platform
import subprocess

import kubernetes
import yaml
Expand Down Expand Up @@ -61,7 +62,12 @@ def restart_pods(game_creator_yaml):
"""
print("Restarting pods")

run_command(["kubectl", "create", "-f", "agones/fleet.yml"])
try:
run_command(["kubectl", "create", "-f", "agones/fleet.yml"])
except subprocess.CalledProcessError:
run_command("kubectl delete fleet aimmo-game --ignore-not-found".split(" "))
run_command("kubectl delete --all deployment -n default".split(" "))
run_command(["kubectl", "create", "-f", "agones/fleet.yml"])

apps_api_instance = AppsV1Api()
apps_api_instance.create_namespaced_deployment(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ exports[`<IDEEditor /> matches snapshot when code is loaded 1`] = `
</Styled(WithStyles(ForwardRef(Button)))>
<Styled(WithStyles(ForwardRef(Button)))
id="game-pause-button"
onClick={[Function]}
startIcon={<UNDEFINED />}
variant="outlined"
>
Expand Down Expand Up @@ -86,6 +87,7 @@ exports[`<IDEEditor /> matches snapshot when code isn't loaded 1`] = `
</Styled(WithStyles(ForwardRef(Button)))>
<Styled(WithStyles(ForwardRef(Button)))
id="game-pause-button"
onClick={[Function]}
startIcon={<UNDEFINED />}
variant="outlined"
>
Expand Down
19 changes: 16 additions & 3 deletions game_frontend/src/containers/IDEEditor/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import { withTheme } from '@material-ui/core/styles'
import RunCodeButton from 'components/RunCodeButton'
import { connect } from 'react-redux'
import { actions as editorActions } from 'features/Editor'
import { PauseCircleFilled, SettingsBackupRestore } from '@material-ui/icons'
import { actions as gameActions } from 'features/Game'
import { PauseCircleFilled, SettingsBackupRestore, Refresh } from '@material-ui/icons'

import 'ace-builds/src-noconflict/mode-python'
// The monokai theme is modified and overridden, see handlebars_template.html
Expand Down Expand Up @@ -44,6 +45,9 @@ export class IDEEditor extends PureComponent {
theme: PropTypes.object,
postCode: PropTypes.func,
runCodeButtonStatus: PropTypes.object,
togglePauseGame: PropTypes.func,
gamePaused: PropTypes.bool,
gameResume: PropTypes.func,
}

state = {
Expand Down Expand Up @@ -86,6 +90,7 @@ export class IDEEditor extends PureComponent {

postCode = () => {
this.props.postCode(this.state.code)
this.props.gameResume()
}

codeChanged = (code) => {
Expand Down Expand Up @@ -119,6 +124,10 @@ export class IDEEditor extends PureComponent {
}
}

onPauseClicked = () => {
this.props.togglePauseGame()
}

render() {
return (
<IDEEditorLayout>
Expand All @@ -135,9 +144,10 @@ export class IDEEditor extends PureComponent {
<MenuButton
id="game-pause-button"
variant="outlined"
startIcon={<PauseCircleFilled />}
onClick={this.onPauseClicked}
startIcon={this.props.gamePaused ? <Refresh /> : <PauseCircleFilled />}
>
Pause
{this.props.gamePaused ? "Resume" : "Pause"}
</MenuButton>
<RunCodeButton
runCodeButtonStatus={this.props.runCodeButtonStatus}
Expand All @@ -156,13 +166,16 @@ const mapStateToProps = (state) => ({
codeOnServer: state.editor.code.codeOnServer,
resetCodeTo: state.editor.code.resetCodeTo,
runCodeButtonStatus: state.editor.runCodeButton,
gamePaused: state.game.gamePaused,
})

const mapDispatchToProps = {
getCode: editorActions.getCodeRequest,
codeReset: editorActions.codeReset,
postCode: editorActions.postCodeRequest,
resetCode: editorActions.resetCode,
togglePauseGame: gameActions.togglePauseGame,
gameResume: gameActions.gameResume,
}

export default connect(mapStateToProps, mapDispatchToProps)(withTheme(IDEEditor))
36 changes: 36 additions & 0 deletions game_frontend/src/containers/IDEEditor/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,42 @@ describe('<IDEEditor />', () => {
component.find('#reset-code-button').simulate('click')
expect(props.resetCode).toBeCalled()
})

it('togglePauseGame called after clicking pause', () => {
const props = {
getCode: jest.fn(),
togglePauseGame: jest.fn()
}

const component = createShallowWithTheme(<IDEEditor {...props} />, 'dark')

component.find('#game-pause-button').simulate('click')
expect(props.togglePauseGame).toBeCalled()
})

it('gameResumed called after clicking post code', () => {
const props = {
getCode: jest.fn(),
postCode: jest.fn(),
gameResume: jest.fn()
}

const component = createShallowWithTheme(<IDEEditor {...props} />, 'dark')

component.instance().postCode();
expect(props.gameResume).toBeCalled()
})

it('gameResumed called after clicking post code', () => {
const props = {
getCode: jest.fn(),
}

const component = createShallowWithTheme(<IDEEditor {...props} />, 'dark')

component.instance().codeChanged("someCode");
expect(component.state('code')).toEqual("someCode");
})
})

describe('<IDEEditorLayout />', () => {
Expand Down
1 change: 1 addition & 0 deletions game_frontend/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ const initialState = (window.Cypress && window.initialState) || {
timeoutStatus: false,
gameLoaded: false,
cameraCenteredOnUserAvatar: true,
gamePaused: false,
},
}

Expand Down
4 changes: 2 additions & 2 deletions game_frontend/src/pyodide/pyodideRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,10 @@ async function runIfWorkerReady(
}
}

export const computeNextAction$ = (gameState: any, avatarState: object) =>
export const computeNextAction$ = (gameState: any, avatarState: object, gamePaused: boolean) =>
defer(() =>
runIfWorkerReady(
() => worker.computeNextAction(gameState, avatarState),
() => worker.computeNextAction(gameState, avatarState, gamePaused),
gameState.turnCount + 1
)
)
42 changes: 41 additions & 1 deletion game_frontend/src/pyodide/webWorker.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-env jest */
import { simplifyErrorMessageInLog } from './webWorker'
import { computeNextAction, simplifyErrorMessageInLog } from './webWorker'
jest.mock('threads/worker')

describe('Error formatting', () => {
Expand All @@ -20,3 +20,43 @@ AttributeError: module 'simulation.direction' has no attribute 'NRTH'
expect(result).toBe(expected)
})
})

describe('pyodide webWorker', () => {
it('returns wait action if game is paused', async () => {
expect.assertions(1);
const isGamePaused = true
const playerAvatarID = 1
const turnCount = 5
const gameState = {
"players": [
{
"location": {
"x": -13,
"y": 13
},
"id": 1,
"orientation": "north"
},
{
"location": {
"x": 6,
"y": 10
},
"id": 2,
"orientation": "north"
}
],
"turnCount": turnCount,
}

const expected = {
action: {
action_type: "wait",
},
log: '',
turnCount: turnCount + 1,
}

return expect(computeNextAction(gameState, playerAvatarID, isGamePaused)).resolves.toEqual(expected);
})
})
12 changes: 10 additions & 2 deletions game_frontend/src/pyodide/webWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,16 @@ def capture_output(stdout=None, stderr=None):
`)
}

async function computeNextAction(gameState, playerAvatarID): Promise<ComputedTurnResult> {
export async function computeNextAction(gameState, playerAvatarID, gamePaused): Promise<ComputedTurnResult> {
const avatarState = getAvatarStateFromGameState(gameState, playerAvatarID)
if (gamePaused) {
return Promise.resolve({
action: { action_type: 'wait' },
log: '',
turnCount: gameState.turnCount + 1,
})
}

try {
return await pyodide.runPythonAsync(`
game_state = ${JSON.stringify(gameState)}
Expand Down Expand Up @@ -103,7 +111,7 @@ export async function updateAvatarCode(
try {
await pyodide.runPythonAsync(userCode)
if (gameState) {
return computeNextAction(gameState, playerAvatarID)
return computeNextAction(gameState, playerAvatarID, false)
}
return Promise.resolve({
action: { action_type: 'wait' },
Expand Down
3 changes: 2 additions & 1 deletion game_frontend/src/redux/features/AvatarWorker/epics.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ const computeNextActionEpic = (
switchMap(({ payload: { gameState } }) =>
computeNextAction$(
gameState,
state$.value.game.connectionParameters.currentAvatarID
state$.value.game.connectionParameters.currentAvatarID,
state$.value.game.gamePaused,
).pipe(timeoutIfWorkerTakesTooLong(state$, resetWorker, scheduler))
),
tap(socket.emitAction),
Expand Down
1 change: 1 addition & 0 deletions game_frontend/src/redux/features/AvatarWorker/operators.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const PYODIDE_WORKER_PROCESSING_TIME = 1000
* @param {*} state$ The state observable that's passed to epics
* @param {*} resetWorker The function that resets the pyodide worker
*/

export const timeoutIfWorkerTakesTooLong =
(state$, resetWorker, scheduler) => (computedTurnResult$) =>
computedTurnResult$.pipe(
Expand Down
5 changes: 4 additions & 1 deletion game_frontend/src/redux/features/ConsoleLog/reducers.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@ const consoleLogReducer = (state = { logs: new Map(), gameLog: '' }, action) =>
}
const nextState = produce(state, (draftState) => {
const logs = draftState.logs
logs.set(turnCount, newLogMessage)
const currentLogMessage = logs.get(turnCount)
const newLogForTheTurn = currentLogMessage ? (currentLogMessage + newLogMessage) : newLogMessage
logs.set(turnCount, newLogForTheTurn)

if (logs.size > MAX_NUMBER_OF_STORED_LOGS) {
logs.delete(logs.keys().next().value)
}
Expand Down
10 changes: 10 additions & 0 deletions game_frontend/src/redux/features/Game/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ const gameLoaded = () => ({
type: types.GAME_LOADED,
})

const togglePauseGame = () => ({
type: types.TOGGLE_PAUSE_GAME,
})

const gameResume = () => ({
type: types.GAME_RESUME,
})

const setTimeout = () => ({
type: types.SET_TIMEOUT,
})
Expand All @@ -55,4 +63,6 @@ export default {
setTimeout,
mapPanned,
centerCameraOnUserAvatar,
togglePauseGame,
gameResume,
}
22 changes: 21 additions & 1 deletion game_frontend/src/redux/features/Game/epics.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import actions from './actions'
import types from './types'
import { avatarWorkerTypes } from 'features/AvatarWorker'
import { avatarWorkerTypes, avatarWorkerActions } from 'redux/features/AvatarWorker'
import { editorTypes } from 'features/Editor'
import { Scheduler, of } from 'rxjs'
import {
Expand All @@ -13,6 +13,7 @@ import {
timeInterval,
retryWhen,
delay,
filter,
} from 'rxjs/operators'
import { ofType } from 'redux-observable'
import { actions as analyticActions } from 'redux/features/Analytics'
Expand Down Expand Up @@ -80,10 +81,29 @@ const codeUpdatingIntervalEpic = (action$, state$, dependencies, scheduler = bac
)
)

const gamePausedEpic = (action$, state$) =>
action$.pipe(
ofType(types.TOGGLE_PAUSE_GAME),
map(() =>
state$.value.game.gamePaused
? avatarWorkerActions.avatarsNextActionComputed({ turnCount: state$.value.game.gameState.turnCount + 1, log: "You have paused the game" })
: avatarWorkerActions.avatarsNextActionComputed({ turnCount: state$.value.game.gameState.turnCount + 1, log: "You have resumed the game" })
)
);

const gamePausedAnalyticsEpic = (action$, state$) =>
action$.pipe(
ofType(types.TOGGLE_PAUSE_GAME),
filter(() => state$.value.game.gamePaused),
mapTo(analyticActions.sendAnalyticsEvent('Kurono', 'Click', 'Pause'))
);

export default {
getConnectionParametersEpic,
connectToGameEpic,
gameLoadedEpic,
gameLoadedIntervalEpic,
codeUpdatingIntervalEpic,
gamePausedEpic,
gamePausedAnalyticsEpic,
}
Loading

0 comments on commit 764ae03

Please sign in to comment.