/* * $Id: VariableInstruction.java 543 2008-02-27 08:05:14Z weiju $ * * Created on 10/03/2005 * Copyright 2005-2008 by Wei-ju Wu * This file is part of The Z-machine Preservation Project (ZMPP). * * ZMPP is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * ZMPP is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ZMPP. If not, see <http://www.gnu.org/licenses/>. */ package org.zmpp.instructions; import org.zmpp.base.Memory; import org.zmpp.encoding.ZCharEncoder; import org.zmpp.media.SoundSystem; import org.zmpp.vm.Machine; import org.zmpp.vm.Output; import org.zmpp.vm.ScreenModel; import org.zmpp.vm.TextCursor; /** * This class represents instructions of type VARIABLE. * * @author Wei-ju Wu * @version 1.0 */ public class VariableInstruction extends AbstractInstruction { /** * The operand count. */ private OperandCount operandCount; /** * Constructor. * * @param machineState a reference to a MachineState object * @param operandCount the operand count * @param opcode the instruction's opcode */ public VariableInstruction(Machine machineState, OperandCount operandCount, int opcode) { super(machineState, opcode); this.operandCount = operandCount; } /** * {@inheritDoc} */ public InstructionForm getInstructionForm() { return InstructionForm.VARIABLE; } /** * {@inheritDoc} */ public OperandCount getOperandCount() { return operandCount; } /** * {@inheritDoc} */ protected InstructionStaticInfo getStaticInfo() { return VariableStaticInfo.getInstance(); } /** * Returns the memory access object. * * @return the memory access object */ private Memory getMemoryAccess() { return getMachine().getGameData().getMemory(); } /** * {@inheritDoc} */ protected void doInstruction() { switch (getOpcode()) { case VariableStaticInfo.OP_CALL: call(); break; case VariableStaticInfo.OP_CALL_VS2: call(); break; case VariableStaticInfo.OP_STOREW: storew(); break; case VariableStaticInfo.OP_STOREB: storeb(); break; case VariableStaticInfo.OP_PUT_PROP: put_prop(); break; case VariableStaticInfo.OP_SREAD: sread(); break; case VariableStaticInfo.OP_PRINT_CHAR: print_char(); break; case VariableStaticInfo.OP_PRINT_NUM: print_num(); break; case VariableStaticInfo.OP_RANDOM: random(); break; case VariableStaticInfo.OP_PUSH: push(); break; case VariableStaticInfo.OP_PULL: pull(); break; case VariableStaticInfo.OP_SPLIT_WINDOW: split_window(); break; case VariableStaticInfo.OP_SET_TEXT_STYLE: set_text_style(); break; case VariableStaticInfo.OP_BUFFER_MODE: buffer_mode(); break; case VariableStaticInfo.OP_SET_WINDOW: set_window(); break; case VariableStaticInfo.OP_OUTPUTSTREAM: output_stream(); break; case VariableStaticInfo.OP_INPUTSTREAM: input_stream(); break; case VariableStaticInfo.OP_SOUND_EFFECT: sound_effect(); break; case VariableStaticInfo.OP_ERASE_WINDOW: erase_window(); break; case VariableStaticInfo.OP_ERASE_LINE: erase_line(); break; case VariableStaticInfo.OP_SET_CURSOR: set_cursor(); break; case VariableStaticInfo.OP_GET_CURSOR: get_cursor(); break; case VariableStaticInfo.OP_READ_CHAR: read_char(); break; case VariableStaticInfo.OP_SCAN_TABLE: scan_table(); break; case VariableStaticInfo.OP_NOT: not(); break; case VariableStaticInfo.OP_CALL_VN: case VariableStaticInfo.OP_CALL_VN2: call(); break; case VariableStaticInfo.OP_TOKENISE: tokenise(); break; case VariableStaticInfo.OP_ENCODE_TEXT: encode_text(); break; case VariableStaticInfo.OP_COPY_TABLE: copy_table(); break; case VariableStaticInfo.OP_PRINT_TABLE: print_table(); break; case VariableStaticInfo.OP_CHECK_ARG_COUNT: check_arg_count(); break; default: throwInvalidOpcode(); } } private void call() { call(getNumOperands() - 1); } private void storew() { final Memory memory = getMemoryAccess(); final int array = getUnsignedValue(0); final int wordIndex = getUnsignedValue(1); final short value = getValue(2); memory.writeShort(array + wordIndex * 2, value); nextInstruction(); } private void storeb() { final Memory memory = getMemoryAccess(); final int array = getUnsignedValue(0); final int byteIndex = getUnsignedValue(1); final byte value = (byte) getValue(2); memory.writeByte(array + byteIndex, value); nextInstruction(); } private void put_prop() { final int obj = getUnsignedValue(0); final int property = getUnsignedValue(1); final short value = getValue(2); if (obj > 0) { getMachine().setProperty(obj, property, value); nextInstruction(); } else { // Issue warning for non-existent object getMachine().warn("@put_prop illegal access to object " + obj); nextInstruction(); } } private void print_char() { final char zchar = (char) getUnsignedValue(0); getMachine().getOutput().printZsciiChar(zchar, false); nextInstruction(); } private void print_num() { final short number = getValue(0); getMachine().getOutput().printNumber(number); nextInstruction(); } private void push() { final short value = getValue(0); getCpu().setVariable(0, value); nextInstruction(); } private void pull() { if (getStoryFileVersion() == 6) { pull_v6(); } else { pull_std(); } nextInstruction(); } private void pull_v6() { int userstack = 0; if (getNumOperands() == 1) { userstack = getUnsignedValue(0); } if (userstack > 0) { storeResult(getCpu().popUserStack(userstack)); } else { storeResult(getCpu().getVariable(0)); } } private void pull_std() { final int varnum = getUnsignedValue(0); final short value = getCpu().getVariable(0); // standard 1.1 if (varnum == 0) { getCpu().setStackTopElement(value); } else { getCpu().setVariable(varnum, value); } } private void output_stream() { // Stream number should be a signed byte final short streamnumber = getValue(0); if (streamnumber < 0 && streamnumber >= -3) { getMachine().getOutput().selectOutputStream(-streamnumber, false); } else if (streamnumber > 0 && streamnumber <= 3) { if (streamnumber == Output.OUTPUTSTREAM_MEMORY) { final int tableAddress = getUnsignedValue(1); int tablewidth = 0; if (getNumOperands() == 3) { tablewidth = getUnsignedValue(2); System.out.printf("@output_stream 3 %x %d\n", tableAddress, tablewidth); } //System.out.printf("Select stream 3 on table: %x\n", tableAddress); getMachine().getOutput().selectOutputStream3(tableAddress, tablewidth); } else { getMachine().getOutput().selectOutputStream(streamnumber, true); } } nextInstruction(); } private void input_stream() { getMachine().getInput().selectInputStream(getUnsignedValue(0)); nextInstruction(); } private void random() { final short range = getValue(0); storeResult(getMachine().random(range)); nextInstruction(); } private void sread() { //System.out.println("@sread()"); final int version = getStoryFileVersion(); if (version <= 3) { getMachine().updateStatusLine(); } final int textbuffer = getUnsignedValue(0); int parsebuffer = 0; int time = 0; short packedAddress = 0; if (getNumOperands() >= 2) { parsebuffer = getUnsignedValue(1); } if (getNumOperands() >= 3) { time = getUnsignedValue(2); } if (getNumOperands() >= 4) { packedAddress = getValue(3); } final char terminal = getMachine().readLine(textbuffer, time, packedAddress); if (version < 5 || (version >= 5 && parsebuffer > 0)) { // Do not tokenise if parsebuffer is 0 (See specification of read) getMachine().tokenize(textbuffer, parsebuffer, 0, false); } if (storesResult()) { // The specification suggests that we store the terminating character // here, this can be NULL or NEWLINE at the moment storeResult((short) terminal); } nextInstruction(); } /** * Implements the sound_effect instruction. */ private void sound_effect() { // Choose some default values int soundnum = SoundSystem.BLEEP_HIGH; int effect = SoundSystem.EFFECT_START; int volume = SoundSystem.VOLUME_DEFAULT; int repeats = 0; int routine = 0; // Truly variable // If no operands are set, this function will still try to send something if (getNumOperands() >= 1) { soundnum = getUnsignedValue(0); } if (getNumOperands() >= 2) { effect = getUnsignedValue(1); } if (getNumOperands() >= 3) { final int volumeRepeats = getUnsignedValue(2); volume = volumeRepeats & 0xff; repeats = (volumeRepeats >>> 8) & 0xff; if (repeats <= 0) { repeats = 1; } } if (getNumOperands() == 4) { routine = getUnsignedValue(3); } System.out.printf("@sound_effect n: %d, fx: %d, vol: %d, rep: %d, routine: $%04x\n", soundnum, effect, volume, repeats, routine); // In version 3 repeats is always 1 if (getStoryFileVersion() == 3) { repeats = 1; } final SoundSystem soundSystem = getMachine().getSoundSystem(); soundSystem.play(soundnum, effect, volume, repeats, routine); nextInstruction(); } private void split_window() { //System.out.printf("@split_window, window: %d\n", getUnsignedValue(0)); final ScreenModel screenModel = getMachine().getScreen(); if (screenModel != null) { screenModel.splitWindow(getUnsignedValue(0)); } nextInstruction(); } private void set_window() { //System.out.printf("@set_window, window: %d\n", getUnsignedValue(0)); final ScreenModel screenModel = getMachine().getScreen(); if (screenModel != null) { screenModel.setWindow(getUnsignedValue(0)); } nextInstruction(); } private void set_text_style() { final ScreenModel screenModel = getMachine().getScreen(); if (screenModel != null) { screenModel.setTextStyle(getUnsignedValue(0)); } nextInstruction(); } private void buffer_mode() { final ScreenModel screenModel = getMachine().getScreen(); if (screenModel != null) { screenModel.setBufferMode(getUnsignedValue(0) > 0); } nextInstruction(); } private void erase_window() { final ScreenModel screenModel = getMachine().getScreen(); if (screenModel != null) { screenModel.eraseWindow(getValue(0)); } nextInstruction(); } private void erase_line() { final ScreenModel screenModel = getMachine().getScreen(); if (screenModel != null) { screenModel.eraseLine(getValue(0)); } nextInstruction(); } private void set_cursor() { final ScreenModel screenModel = getMachine().getScreen(); if (screenModel != null) { final int line = getValue(0); int column = 0; int window = ScreenModel.CURRENT_WINDOW; if (getNumOperands() >= 2) { column = getValue(1); } if (getNumOperands() >= 3) { window = getValue(2); } if (line > 0) { screenModel.setTextCursor(line, column, window); } } nextInstruction(); } private void get_cursor() { final ScreenModel screenModel = getMachine().getScreen(); if (screenModel != null) { final TextCursor cursor = screenModel.getTextCursor(); final Memory memory = getMemoryAccess(); final int arrayAddr = getUnsignedValue(0); memory.writeShort(arrayAddr, (short) cursor.getLine()); memory.writeShort(arrayAddr + 2, (short) cursor.getColumn()); } nextInstruction(); } private void scan_table() { final Memory memory = getMemoryAccess(); final short x = getValue(0); final int table = getUnsignedValue(1); final int length = getUnsignedValue(2); int form = 0x82; // default value if (getNumOperands() == 4) { form = getUnsignedValue(3); } final int fieldlen = form & 0x7f; final boolean isWordTable = (form & 0x80) > 0; int pointer = table; boolean found = false; for (int i = 0; i < length; i++) { final short current = isWordTable ? memory.readShort(pointer) : memory.readByte(pointer); if (current == x) { storeResult((short) pointer); found = true; break; } pointer += fieldlen; } // not found if (!found) { storeResult((short) 0); } //System.out.printf("@SCAN_TABLE, X: %d, TABLE: %d LEN: %d POINTER: %d\n", // x, table, length, pointer); branchOnTest(found); } private void read_char() { //System.out.println("@read_char()"); int time = 0; int routineAddress = 0; if (getNumOperands() >= 2) { time = getUnsignedValue(1); } if (getNumOperands() >= 3) { routineAddress = getValue(2); } storeResult((short) getMachine().readChar(time, routineAddress)); nextInstruction(); } /** * not instruction. Actually a copy from Short1Instruction, probably we can * remove this duplication. */ private void not() { final int notvalue = ~getUnsignedValue(0); storeResult((short) (notvalue & 0xffff)); nextInstruction(); } private void tokenise() { final int textbuffer = getUnsignedValue(0); final int parsebuffer = getUnsignedValue(1); int dictionary = 0; int flag = 0; if (getNumOperands() >= 3) { dictionary = getUnsignedValue(2); } if (getNumOperands() >= 4) { flag = getUnsignedValue(3); } getMachine().tokenize(textbuffer, parsebuffer, dictionary, (flag != 0)); nextInstruction(); } private void check_arg_count() { final int argumentNumber = getUnsignedValue(0); final int currentNumArgs = getCpu().getCurrentRoutineContext().getNumArguments(); branchOnTest(argumentNumber <= currentNumArgs); } private void copy_table() { final int first = getUnsignedValue(0); final int second = getUnsignedValue(1); int size = getValue(2); final Memory memory = getMemoryAccess(); if (second == 0) { // Clear size bytes of first size = Math.abs(size); for (int i = 0; i < size; i++) { memory.writeByte(first + i, (byte) 0); } } else { if (size < 0 || first > second) { // copy forward size = Math.abs(size); for (int i = 0; i < size; i++) { memory.writeByte(second + i, memory.readByte(first + i)); } } else { // backwards size = Math.abs(size); for (int i = size - 1; i >= 0; i--) { memory.writeByte(second + i, memory.readByte(first + i)); } } } nextInstruction(); } /** * Do the print_table instruction. This method takes a text and formats it * in a specified format. It requires access to the cursor position in order * to be implemented correctly, otherwise horizontal home position would * always be set to the left position of the window. Interestingly, the text * is not encoded, so the characters should be accessed one by one in ZSCII * format. */ private void print_table() { final int zsciiText = getUnsignedValue(0); final int width = getUnsignedValue(1); int height = 1; int skip = 0; if (getNumOperands() >= 3) { height = getUnsignedValue(2); } if (getNumOperands() == 4) { skip = getUnsignedValue(3); } //System.out.printf("@print_table, zscii-text = %d, width = %d," + // " height = %d, skip = %d\n", zsciiText, width, height, skip); char zchar = 0; final Memory memory = getMemoryAccess(); final TextCursor cursor = getMachine().getScreen().getTextCursor(); final int column = cursor.getColumn(); int row = cursor.getLine(); for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { final int offset = (width * i) + j; zchar = (char) memory.readUnsignedByte(zsciiText + offset); getMachine().getOutput().printZsciiChar(zchar, false); } row += skip + 1; getMachine().getScreen().setTextCursor(row, column, ScreenModel.CURRENT_WINDOW); } nextInstruction(); } private void encode_text() { final int zsciiText = getUnsignedValue(0); final int length = getUnsignedValue(1); final int from = getUnsignedValue(2); final int codedText = getUnsignedValue(3); final ZCharEncoder encoder = getMachine().getGameData().getZCharEncoder(); encoder.encode(getMemoryAccess(), zsciiText + from, length, codedText); nextInstruction(); } }