Parser uses game object to filter impossible commands

main
Ashelyn Dawn 4 years ago
parent 230eef2585
commit 246418bcc4

@ -1,12 +1,19 @@
import {enableMapSet, createDraft, finishDraft, Draft} from 'immer'
import GameState, { Room, Door, Item, ObjectType } from './types/GameState'
import GameState, { GameObject, Room, Door, Item, ObjectType } from './types/GameState'
import ParsedCommand, { TokenType, ParsedTokenExpression, ValidCommandDetails, InvalidCommandDetails } from './types/ParsedCommand'
enableMapSet()
type ChangeListener = (state : GameState) => void
export type CommandValidateResult = {
validCommands: ValidCommandDetails [],
invalidCommands: InvalidCommandDetails []
}
export default class Game {
private gameState : GameState = {directions: new Map(), rooms: new Map(), doors: new Map(), items: new Map()}
private gameState : GameState = {directions: new Map(), rooms: new Map(), doors: new Map(), items: new Map(), player: {location: ''}}
private draft : Draft<GameState> | null = null
private onChangeListeners : ChangeListener [] = []
@ -22,6 +29,25 @@ export default class Game {
this.saveDraft()
}
filterValidCommands(commands: ParsedCommand[]) : CommandValidateResult {
let validCommands : ValidCommandDetails[] = []
let invalidCommands : InvalidCommandDetails[] = []
for(const command of commands) {
const commandValidationResult = command.areNounsValid(this)
if(commandValidationResult.isValid){
validCommands.push(commandValidationResult)
} else {
invalidCommands.push(commandValidationResult)
}
}
invalidCommands.sort((a,b) => a.severity - b.severity)
return {validCommands, invalidCommands}
}
onChange(callback : ChangeListener) {
this.onChangeListeners.push(callback)
}
@ -51,6 +77,12 @@ export default class Game {
return this.draft!
}
getCurrentRoom() : Draft<Room> | null {
const state = this.getState()
return Array.from(state.rooms.values())
.find(room => room.name === state.player.location) || null
}
addRoom(room : Room) {
let state = this.getState()
state.rooms.set(room.name, room)
@ -65,4 +97,68 @@ export default class Game {
let state = this.getState()
state.items.set(item.name, item)
}
findObjectByName(name : string, type : ObjectType) : GameObject | null {
let collection
switch(type) {
case ObjectType.Door:
collection = this.getState().doors
break
case ObjectType.Item:
collection = this.getState().items
break
case ObjectType.Room:
collection = this.getState().rooms
break
case ObjectType.Direction:
collection = this.getState().directions
break
}
const objects = [...collection!.values()]
const exactMatch = objects.find((object) => name === object.name)
if(exactMatch)
return exactMatch
const aliasMatch = objects.find(({aliases}) => aliases.includes(name))
if(aliasMatch)
return aliasMatch
return null
}
isVisible(object : GameObject) : boolean {
const state = this.getState()
const currentRoom = this.getCurrentRoom()
switch(object.type) {
case ObjectType.Direction:
return true
case ObjectType.Room:
return state.player.location === (object as Room).name
case ObjectType.Door:
if(!currentRoom)
return false;
const neighborIDs = Array.from(currentRoom.neighbors.values())
let neighbors = neighborIDs.map(name => state.doors.get(name)).filter(object => object !== undefined).map(o => o!)
for(const neighbor of neighbors)
if(neighbor.name === object.name)
return true;
return false
case ObjectType.Item:
return state.player.location === (object as Item).location
default:
return false
}
}
}

@ -14,10 +14,14 @@ export default class Parser {
}
handleCommand(rawCommand : string) {
// Parse command for syntactical validity
// (according to known verb templates)
const grammaticalParsings : ParsedCommand[] = this.parseCommandString(rawCommand)
console.log(grammaticalParsings)
// const validParsings : ParsedCommand[] = this.game.filterCommandsForValidity(grammaticalParsings)
// Ask the game state container to filter commands for object validity
// (nouns refer to valid objects, all objects are visible, etc)
const validationResult = this.game.filterValidCommands(grammaticalParsings)
console.log(validationResult)
}
parseCommandString(rawCommand: string): ParsedCommand[] {

@ -1,8 +1,11 @@
type GameState = {
readonly directions: Map<ObjectID, GameObject>,
readonly rooms: Map<ObjectID, GameObject>,
readonly doors: Map<ObjectID, GameObject>,
readonly items: Map<ObjectID, GameObject>
readonly directions: Map<ObjectID, Direction>,
readonly rooms: Map<ObjectID, Room>,
readonly doors: Map<ObjectID, Door>,
readonly items: Map<ObjectID, Item>,
readonly player: {
readonly location: ObjectID
}
}
export default GameState
@ -16,21 +19,29 @@ export enum ObjectType {
type ObjectID = string
type GameObject = {
export type GameObject = {
readonly type : ObjectType,
readonly name : ObjectID,
readonly aliases : string[]
}
export type Direction = GameObject & {
readonly type : ObjectType.Direction
}
export type Room = GameObject & {
readonly type : ObjectType.Room,
readonly neighbors : Map<ObjectID, ObjectID>
}
export type Door = Room & {
export type Door = GameObject & {
readonly type : ObjectType.Door,
readonly neighbors : Map<ObjectID, ObjectID>
readonly locked : boolean,
readonly key : ObjectID
}
export type Item = GameObject & {
readonly type : ObjectType.Item,
readonly location: ObjectID | 'inventory'
}

@ -1,6 +1,7 @@
import { ObjectType } from "./GameState"
import { ObjectType, GameObject } from "./GameState"
import NounPosition from "./NounPosition"
import Verb from "./Verb"
import Game from "../Game"
export enum TokenType {
Expression = 'expression',
@ -37,6 +38,14 @@ export class ParsedTokenExpression extends ParsedToken {
}
}
export enum ParsingErrorSeverity {
NotVisible = 0,
NoSuchObject = 1
}
export type ValidCommandDetails = {isValid: true, command: ParsedCommand, subject: GameObject | null, object: GameObject | null}
export type InvalidCommandDetails = {isValid: false, command: ParsedCommand, reason: string, severity: ParsingErrorSeverity}
export default class ParsedCommand {
readonly verb : Verb
private tokens : ParsedToken[] = []
@ -50,4 +59,42 @@ export default class ParsedCommand {
newCommand.tokens = [token, ...this.tokens]
return newCommand
}
areNounsValid(game : Game) : ValidCommandDetails | InvalidCommandDetails {
const nouns = this.tokens.filter(({type}) => type === TokenType.Expression).map(token => token as ParsedTokenExpression)
let subject : GameObject | null = null
let object : GameObject | null = null
for(const noun of nouns) {
let gameObject = game.findObjectByName(noun.name, noun.itemType)
if(!gameObject)
return {
isValid: false,
command: this,
reason: `You used the word ${noun.name} as if it was a ${noun.itemType}, but there is no such object`,
severity: ParsingErrorSeverity.NoSuchObject
}
if(!game.isVisible(gameObject))
return {
isValid: false,
command: this,
reason: `You cannot see ${noun.name}`,
severity: ParsingErrorSeverity.NotVisible
}
if(noun.sentencePosition === NounPosition.Subject)
subject = gameObject
else
object = gameObject
}
return {
isValid: true,
command: this,
subject,
object
}
}
}

@ -16,6 +16,16 @@ parser.understand('look')
parser.understand('lookDirection')
.as('look [direction]')
parser.understand('lookAt')
.as('look [item]')
.as('look [door]')
.as('look at [item]')
.as('look at [door]')
.as('examine [item]')
.as('examine [door]')
.as('x [item]')
.as('x [door]')
parser.understand('go')
.as('go [direction]')
.as('[direction]')
@ -76,6 +86,8 @@ game.addItem({
location: 'entry'
})
game.getState().player.location = 'entry'
game.saveDraft()

@ -17,7 +17,8 @@
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react"
"jsx": "react",
"downlevelIteration": true
},
"include": [
"src"

Loading…
Cancel
Save